worldEditor.cpp
Engine/source/gui/worldEditor/worldEditor.cpp
Classes:
class
Public Variables
Public Functions
ConsoleDocClass(WorldEditor , "@brief The <a href="/coding/file/x86unixmain_8cpp/#x86unixmain_8cpp_1a217dbf8b442f20279ea00b898af96f52">main</a> <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a81f4537631c9ab219ec74de554483adc">World</a> Editor tool <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">class\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
ConsoleMethod(WorldEditor , ignoreObjClass , void , 3 , 0 , "(string class_name, ...)" )
DefineConsoleMethod(WorldEditor , setActiveSelection , void , (WorldEditorSelection *selection) , "Set the currently active <a href="/coding/class/classworldeditorselection/">WorldEditorSelection</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@param selection A WorldEditorSelectionSet object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the selection container." )
DefineEngineMethod(WorldEditor , addUndoState , void , () , "Adds/Submits an undo state <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the undo manager." )
DefineEngineMethod(WorldEditor , alignByAxis , void , (S32 axis) , "Align all selected objects along the given axis." "@param axis Axis <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> align all selected objects along." )
DefineEngineMethod(WorldEditor , alignByBounds , void , (S32 boundsAxis) , "Align all selected objects against the given bounds axis." "@param boundsAxis Bounds axis <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> align all selected objects against." )
DefineEngineMethod(WorldEditor , canPasteSelection , bool , () , "Check <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> we can paste the current selection." "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> we can paste the current selection, false <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> not." )
DefineEngineMethod(WorldEditor , clearIgnoreList , void , () , "Clear the ignore class <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">list.\n</a>" )
DefineEngineMethod(WorldEditor , clearSelection , void , () , "Clear the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">selection.\n</a>" )
DefineEngineMethod(WorldEditor , colladaExportSelection , void , (const char *path) , "Export the combined geometry of all selected objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the specified path in collada format." "@param path Path <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> export collada format to." )
DefineEngineMethod(WorldEditor , copySelection , void , () , "Copy the current selection <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be pasted later." )
DefineEngineMethod(WorldEditor , createConvexShapeFrom , ConvexShape * , (SceneObject *polyObject) , "Create a <a href="/coding/class/classconvexshape/">ConvexShape</a> from the given polyhedral object." )
DefineEngineMethod(WorldEditor , createPolyhedralObject , SceneObject * , (const char *className, SceneObject *geometryProvider) , "Grab the geometry from @a geometryProvider, create a @a className object, and assign it the extracted geometry." )
DefineEngineMethod(WorldEditor , cutSelection , void , () , "Cut the current selection <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be pasted later." )
DefineEngineMethod(WorldEditor , dropSelection , void , (bool skipUndo) , (false) , "Drop the current selection." "@param skipUndo True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> skip creating undo's <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this action, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> create an undo." )
DefineEngineMethod(WorldEditor , explodeSelectedPrefab , void , () , "Replace selected <a href="/coding/class/classprefab/">Prefab</a> objects with a <a href="/coding/class/classsimgroup/">SimGroup</a> containing all children objects defined in the .prefab." )
DefineEngineMethod(WorldEditor , getActiveSelection , S32 , () , "Return the currently active <a href="/coding/class/classworldeditorselection/">WorldEditorSelection</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@return currently active <a href="/coding/class/classworldeditorselection/">WorldEditorSelection</a> object or 0 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> no selection set is available." )
DefineEngineMethod(WorldEditor , getSelectedObject , S32 , (S32 index) , "Return the selected object and the given index." "@param index Index of selected object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> get." "@return selected object at given index or -1 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> index is incorrect." )
DefineEngineMethod(WorldEditor , getSelectionCentroid , Point3F , () , "Get centroid of the selection." "@return centroid of the selection." )
DefineEngineMethod(WorldEditor , getSelectionExtent , Point3F , () , "Get extent of the selection." "@return extent of the selection." )
DefineEngineMethod(WorldEditor , getSelectionRadius , F32 , () , "Get the radius of the current selection." "@return radius of the current selection." )
DefineEngineMethod(WorldEditor , getSelectionSize , S32 , () , "Return the number of objects currently selected in the editor." "@return number of objects currently selected in the editor." )
DefineEngineMethod(WorldEditor , getSoftSnap , bool , () , "Is soft snapping always on?" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> soft snap is on, false <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> not." )
DefineEngineMethod(WorldEditor , getSoftSnapAlignment , WorldEditor::AlignmentType , () , "Get the soft snap alignment." "@return soft snap alignment." )
DefineEngineMethod(WorldEditor , getSoftSnapBackfaceTolerance , F32 , () , "Get the fraction of the soft snap radius that backfaces may be included." "@return fraction of the soft snap radius that backfaces may be included." )
DefineEngineMethod(WorldEditor , getSoftSnapSize , F32 , () , "Get the absolute <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> trigger a soft snap." "@return absolute <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> trigger a soft snap." )
DefineEngineMethod(WorldEditor , getTerrainSnapAlignment , WorldEditor::AlignmentType , () , "Get the terrain snap alignment." "@return terrain snap alignment type." )
DefineEngineMethod(WorldEditor , hideObject , void , (SceneObject *obj, bool hide) , "Hide/show the given object." "@param obj <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> hide/show." "@param hide True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> hide the object, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> show it." )
DefineEngineMethod(WorldEditor , hideSelection , void , (bool hide) , "Hide/show the selection." "@param hide True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> hide the selection, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> show it." )
DefineEngineMethod(WorldEditor , invalidateSelectionCentroid , void , () , "Invalidate the selection sets centroid." )
DefineEngineMethod(WorldEditor , lockSelection , void , (bool lock) , "Lock/unlock the selection." "@param lock True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> lock the selection, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> unlock it." )
DefineEngineMethod(WorldEditor , makeSelectionAMesh , void , (const char *filename) , "Save selected objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> a .dae collada <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> and replace them in the level with a <a href="/coding/class/classtsstatic/">TSStatic</a> object." "@param filename collada <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save the selected objects to." )
DefineEngineMethod(WorldEditor , makeSelectionPrefab , void , (const char *filename) , "Save selected objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> a .prefab <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> and replace them in the level with a <a href="/coding/class/classprefab/">Prefab</a> object." "@param filename <a href="/coding/class/classprefab/">Prefab</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save the selected objects to." )
DefineEngineMethod(WorldEditor , mountRelative , void , (SceneObject *objA, SceneObject *objB) , "Mount object B relatively <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> object A." "@param objA <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> mount to." "@param objB <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> mount." )
DefineEngineMethod(WorldEditor , pasteSelection , void , () , "Paste the current selection." )
DefineEngineMethod(WorldEditor , redirectConsole , void , (S32 objID) , "Redirect console." "@param objID <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> id." )
DefineEngineMethod(WorldEditor , resetSelectedRotation , void , () , "Reset the rotation of the selection." )
DefineEngineMethod(WorldEditor , resetSelectedScale , void , () , "Reset the scale of the selection." )
DefineEngineMethod(WorldEditor , selectObject , void , (SimObject *obj) , "Selects a single object." "@param obj <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> select." )
DefineEngineMethod(WorldEditor , setSoftSnap , void , (bool softSnap) , "Allow soft snapping all of the time." "@param softSnap True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> turn soft snap on, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> turn it off." )
DefineEngineMethod(WorldEditor , setSoftSnapAlignment , void , (WorldEditor::AlignmentType type) , "Set the soft snap alignment." "@param type Soft snap alignment type." )
DefineEngineMethod(WorldEditor , setSoftSnapBackfaceTolerance , void , (F32 tolerance) , "Set the fraction of the soft snap radius that backfaces may be included." "@param tolerance Fraction of the soft snap radius that backfaces may be included (range of 0..1)." )
DefineEngineMethod(WorldEditor , setSoftSnapSize , void , (F32 size) , "Set the absolute <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> trigger a soft snap." "@param <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> Absolute <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> trigger a soft snap." )
DefineEngineMethod(WorldEditor , setTerrainSnapAlignment , void , (WorldEditor::AlignmentType alignment) , "Set the terrain snap alignment." "@param alignment New terrain snap alignment type." )
DefineEngineMethod(WorldEditor , softSnapDebugRender , void , (F32 debugRender) , "Toggle soft snapping debug rendering." "@param debugRender True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> turn on soft snapping debug rendering, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> turn it off." )
DefineEngineMethod(WorldEditor , softSnapRender , void , (F32 render) , "Render the soft snapping bounds." "@param render True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> render the soft snapping bounds, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> not." )
DefineEngineMethod(WorldEditor , softSnapRenderTriangle , void , (F32 renderTriangle) , "Render the soft snapped triangle." "@param renderTriangle True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> render the soft snapped triangle, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> not." )
DefineEngineMethod(WorldEditor , softSnapSizeByBounds , void , (bool useBounds) , "Use selection bounds <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> as soft snap bounds." "@param useBounds True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use selection bounds <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> as soft snap bounds, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> not." )
DefineEngineMethod(WorldEditor , transformSelection , void , (bool position, Point3F point, bool relativePos, bool rotate, Point3F rotation, bool relativeRot, bool rotLocal, S32 scaleType, Point3F scale, bool sRelative, bool sLocal) , "Transform selection by given parameters." "@param position True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> transform the selection's position." "@param point Position <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> transform by." "@param relativePos True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use relative position." "@param rotate True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> transform the selection's rotation." "@param rotation Rotation <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> transform by." "@param relativeRot True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use the relative rotation." "@param rotLocal True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use the local rotation." "@param scaleType Scale type <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use." "@param scale Scale <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> transform by." "@param sRelative True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use a relative scale." "@param sLocal True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use a local scale." )
DefineEngineMethod(WorldEditor , unselectObject , void , (SimObject *obj) , "Unselects a single object." "@param obj <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> unselect." )
findDragMeshCallback(SceneObject * obj, void * data)
findObjectsCallback(SceneObject * obj, void * val)
ImplementEnumType(WorldEditorAlignmentType , "How <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> snap when snapping is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">enabled.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">internal\n\n</a>" )
ImplementEnumType(WorldEditorDropType , "How <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> drop objects when placed or dropped in the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">world.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">internal\n\n</a>" )
Detailed Description
Public Variables
EndImplementEnumType
Frustum gDragFrustum
Public Functions
ConsoleDocClass(WorldEditor , "@brief The <a href="/coding/file/x86unixmain_8cpp/#x86unixmain_8cpp_1a217dbf8b442f20279ea00b898af96f52">main</a> <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a81f4537631c9ab219ec74de554483adc">World</a> Editor tool <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">class\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
ConsoleMethod(WorldEditor , ignoreObjClass , void , 3 , 0 , "(string class_name, ...)" )
DefineConsoleMethod(WorldEditor , setActiveSelection , void , (WorldEditorSelection *selection) , "Set the currently active <a href="/coding/class/classworldeditorselection/">WorldEditorSelection</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@param selection A WorldEditorSelectionSet object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the selection container." )
DefineEngineMethod(WorldEditor , addUndoState , void , () , "Adds/Submits an undo state <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the undo manager." )
DefineEngineMethod(WorldEditor , alignByAxis , void , (S32 axis) , "Align all selected objects along the given axis." "@param axis Axis <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> align all selected objects along." )
DefineEngineMethod(WorldEditor , alignByBounds , void , (S32 boundsAxis) , "Align all selected objects against the given bounds axis." "@param boundsAxis Bounds axis <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> align all selected objects against." )
DefineEngineMethod(WorldEditor , canPasteSelection , bool , () , "Check <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> we can paste the current selection." "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> we can paste the current selection, false <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> not." )
DefineEngineMethod(WorldEditor , clearIgnoreList , void , () , "Clear the ignore class <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">list.\n</a>" )
DefineEngineMethod(WorldEditor , clearSelection , void , () , "Clear the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">selection.\n</a>" )
DefineEngineMethod(WorldEditor , colladaExportSelection , void , (const char *path) , "Export the combined geometry of all selected objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the specified path in collada format." "@param path Path <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> export collada format to." )
DefineEngineMethod(WorldEditor , copySelection , void , () , "Copy the current selection <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be pasted later." )
DefineEngineMethod(WorldEditor , createConvexShapeFrom , ConvexShape * , (SceneObject *polyObject) , "Create a <a href="/coding/class/classconvexshape/">ConvexShape</a> from the given polyhedral object." )
DefineEngineMethod(WorldEditor , createPolyhedralObject , SceneObject * , (const char *className, SceneObject *geometryProvider) , "Grab the geometry from @a geometryProvider, create a @a className object, and assign it the extracted geometry." )
DefineEngineMethod(WorldEditor , cutSelection , void , () , "Cut the current selection <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be pasted later." )
DefineEngineMethod(WorldEditor , dropSelection , void , (bool skipUndo) , (false) , "Drop the current selection." "@param skipUndo True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> skip creating undo's <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this action, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> create an undo." )
DefineEngineMethod(WorldEditor , explodeSelectedPrefab , void , () , "Replace selected <a href="/coding/class/classprefab/">Prefab</a> objects with a <a href="/coding/class/classsimgroup/">SimGroup</a> containing all children objects defined in the .prefab." )
DefineEngineMethod(WorldEditor , getActiveSelection , S32 , () , "Return the currently active <a href="/coding/class/classworldeditorselection/">WorldEditorSelection</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@return currently active <a href="/coding/class/classworldeditorselection/">WorldEditorSelection</a> object or 0 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> no selection set is available." )
DefineEngineMethod(WorldEditor , getSelectedObject , S32 , (S32 index) , "Return the selected object and the given index." "@param index Index of selected object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> get." "@return selected object at given index or -1 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> index is incorrect." )
DefineEngineMethod(WorldEditor , getSelectionCentroid , Point3F , () , "Get centroid of the selection." "@return centroid of the selection." )
DefineEngineMethod(WorldEditor , getSelectionExtent , Point3F , () , "Get extent of the selection." "@return extent of the selection." )
DefineEngineMethod(WorldEditor , getSelectionRadius , F32 , () , "Get the radius of the current selection." "@return radius of the current selection." )
DefineEngineMethod(WorldEditor , getSelectionSize , S32 , () , "Return the number of objects currently selected in the editor." "@return number of objects currently selected in the editor." )
DefineEngineMethod(WorldEditor , getSoftSnap , bool , () , "Is soft snapping always on?" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> soft snap is on, false <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> not." )
DefineEngineMethod(WorldEditor , getSoftSnapAlignment , WorldEditor::AlignmentType , () , "Get the soft snap alignment." "@return soft snap alignment." )
DefineEngineMethod(WorldEditor , getSoftSnapBackfaceTolerance , F32 , () , "Get the fraction of the soft snap radius that backfaces may be included." "@return fraction of the soft snap radius that backfaces may be included." )
DefineEngineMethod(WorldEditor , getSoftSnapSize , F32 , () , "Get the absolute <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> trigger a soft snap." "@return absolute <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> trigger a soft snap." )
DefineEngineMethod(WorldEditor , getTerrainSnapAlignment , WorldEditor::AlignmentType , () , "Get the terrain snap alignment." "@return terrain snap alignment type." )
DefineEngineMethod(WorldEditor , hideObject , void , (SceneObject *obj, bool hide) , "Hide/show the given object." "@param obj <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> hide/show." "@param hide True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> hide the object, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> show it." )
DefineEngineMethod(WorldEditor , hideSelection , void , (bool hide) , "Hide/show the selection." "@param hide True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> hide the selection, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> show it." )
DefineEngineMethod(WorldEditor , invalidateSelectionCentroid , void , () , "Invalidate the selection sets centroid." )
DefineEngineMethod(WorldEditor , lockSelection , void , (bool lock) , "Lock/unlock the selection." "@param lock True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> lock the selection, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> unlock it." )
DefineEngineMethod(WorldEditor , makeSelectionAMesh , void , (const char *filename) , "Save selected objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> a .dae collada <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> and replace them in the level with a <a href="/coding/class/classtsstatic/">TSStatic</a> object." "@param filename collada <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save the selected objects to." )
DefineEngineMethod(WorldEditor , makeSelectionPrefab , void , (const char *filename) , "Save selected objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> a .prefab <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> and replace them in the level with a <a href="/coding/class/classprefab/">Prefab</a> object." "@param filename <a href="/coding/class/classprefab/">Prefab</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save the selected objects to." )
DefineEngineMethod(WorldEditor , mountRelative , void , (SceneObject *objA, SceneObject *objB) , "Mount object B relatively <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> object A." "@param objA <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> mount to." "@param objB <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> mount." )
DefineEngineMethod(WorldEditor , pasteSelection , void , () , "Paste the current selection." )
DefineEngineMethod(WorldEditor , redirectConsole , void , (S32 objID) , "Redirect console." "@param objID <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> id." )
DefineEngineMethod(WorldEditor , resetSelectedRotation , void , () , "Reset the rotation of the selection." )
DefineEngineMethod(WorldEditor , resetSelectedScale , void , () , "Reset the scale of the selection." )
DefineEngineMethod(WorldEditor , selectObject , void , (SimObject *obj) , "Selects a single object." "@param obj <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> select." )
DefineEngineMethod(WorldEditor , setSoftSnap , void , (bool softSnap) , "Allow soft snapping all of the time." "@param softSnap True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> turn soft snap on, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> turn it off." )
DefineEngineMethod(WorldEditor , setSoftSnapAlignment , void , (WorldEditor::AlignmentType type) , "Set the soft snap alignment." "@param type Soft snap alignment type." )
DefineEngineMethod(WorldEditor , setSoftSnapBackfaceTolerance , void , (F32 tolerance) , "Set the fraction of the soft snap radius that backfaces may be included." "@param tolerance Fraction of the soft snap radius that backfaces may be included (range of 0..1)." )
DefineEngineMethod(WorldEditor , setSoftSnapSize , void , (F32 size) , "Set the absolute <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> trigger a soft snap." "@param <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> Absolute <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> trigger a soft snap." )
DefineEngineMethod(WorldEditor , setTerrainSnapAlignment , void , (WorldEditor::AlignmentType alignment) , "Set the terrain snap alignment." "@param alignment New terrain snap alignment type." )
DefineEngineMethod(WorldEditor , softSnapDebugRender , void , (F32 debugRender) , "Toggle soft snapping debug rendering." "@param debugRender True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> turn on soft snapping debug rendering, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> turn it off." )
DefineEngineMethod(WorldEditor , softSnapRender , void , (F32 render) , "Render the soft snapping bounds." "@param render True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> render the soft snapping bounds, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> not." )
DefineEngineMethod(WorldEditor , softSnapRenderTriangle , void , (F32 renderTriangle) , "Render the soft snapped triangle." "@param renderTriangle True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> render the soft snapped triangle, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> not." )
DefineEngineMethod(WorldEditor , softSnapSizeByBounds , void , (bool useBounds) , "Use selection bounds <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> as soft snap bounds." "@param useBounds True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use selection bounds <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> as soft snap bounds, false <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> not." )
DefineEngineMethod(WorldEditor , transformSelection , void , (bool position, Point3F point, bool relativePos, bool rotate, Point3F rotation, bool relativeRot, bool rotLocal, S32 scaleType, Point3F scale, bool sRelative, bool sLocal) , "Transform selection by given parameters." "@param position True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> transform the selection's position." "@param point Position <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> transform by." "@param relativePos True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use relative position." "@param rotate True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> transform the selection's rotation." "@param rotation Rotation <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> transform by." "@param relativeRot True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use the relative rotation." "@param rotLocal True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use the local rotation." "@param scaleType Scale type <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use." "@param scale Scale <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> transform by." "@param sRelative True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use a relative scale." "@param sLocal True <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use a local scale." )
DefineEngineMethod(WorldEditor , unselectObject , void , (SimObject *obj) , "Unselects a single object." "@param obj <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> unselect." )
findDragMeshCallback(SceneObject * obj, void * data)
findObjectsCallback(SceneObject * obj, void * val)
IMPLEMENT_CONOBJECT(WorldEditor )
ImplementEnumType(WorldEditorAlignmentType , "How <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> snap when snapping is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">enabled.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">internal\n\n</a>" )
ImplementEnumType(WorldEditorDropType , "How <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> drop objects when placed or dropped in the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">world.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">internal\n\n</a>" )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "gui/worldEditor/worldEditor.h" 26 27#include "gui/worldEditor/gizmo.h" 28#include "gui/worldEditor/undoActions.h" 29#include "gui/worldEditor/editorIconRegistry.h" 30#include "gui/worldEditor/worldEditorSelection.h" 31#include "core/stream/memStream.h" 32#include "scene/simPath.h" 33#include "scene/mixin/scenePolyhedralObject.h" 34#include "gui/core/guiCanvas.h" 35#include "T3D/gameBase/gameConnection.h" 36#include "T3D/groundPlane.h" 37#include "collision/earlyOutPolyList.h" 38#include "collision/concretePolyList.h" 39#include "console/consoleInternal.h" 40#include "console/engineAPI.h" 41#include "T3D/shapeBase.h" 42#include "T3D/cameraSpline.h" 43#include "T3D/convexShape.h" 44#include "gfx/primBuilder.h" 45#include "gfx/gfxTransformSaver.h" 46#include "gfx/gfxDrawUtil.h" 47#include "gfx/gfxDebugEvent.h" 48#include "platform/typetraits.h" 49#include "T3D/prefab.h" 50#include "math/mEase.h" 51#include "T3D/tsStatic.h" 52 53 54IMPLEMENT_CONOBJECT( WorldEditor ); 55 56ConsoleDocClass( WorldEditor, 57 "@brief The main World Editor tool class\n\n" 58 "Editor use only.\n\n" 59 "@internal" 60); 61 62ImplementEnumType( WorldEditorDropType, 63 "How to drop objects when placed or dropped in the world.\n" 64 "@internal\n\n") 65 { WorldEditor::DropAtOrigin, "atOrigin", "Place at the scene origin (usually 0,0,0)\n" }, 66 { WorldEditor::DropAtCamera, "atCamera", "Places at the same position as the camera, without rotation.\n" }, 67 { WorldEditor::DropAtCameraWithRot, "atCameraRot", "Places at the same position as the camera, with the camera's rotation.\n" }, 68 { WorldEditor::DropBelowCamera, "belowCamera", "Places below the camera.\n" }, 69 { WorldEditor::DropAtScreenCenter, "screenCenter", "Places at a position projected outwards from the screen's center.\n" }, 70 { WorldEditor::DropAtCentroid, "atCentroid", "Places at the center position of the current centroid.\n" }, 71 { WorldEditor::DropToTerrain, "toTerrain", "Places on the terrain.\n" }, 72 { WorldEditor::DropBelowSelection, "belowSelection", "Places at a position below the selected object.\n" } 73EndImplementEnumType; 74 75ImplementEnumType( WorldEditorAlignmentType, 76 "How to snap when snapping is enabled.\n" 77 "@internal\n\n") 78 { WorldEditor::AlignNone, "None", "No alignement type.\n" }, 79 { WorldEditor::AlignPosX, "+X", "Snap towards the higher position on the X plane.\n" }, 80 { WorldEditor::AlignPosY, "+Y", "Snap towards the higher position on the Y plane.\n" }, 81 { WorldEditor::AlignPosZ, "+Z", "Snap towards the higher position on the Z plane.\n" }, 82 { WorldEditor::AlignNegX, "-X", "Snap towards the lower position on the X plane.\n" }, 83 { WorldEditor::AlignNegY, "-Y", "Snap towards the lower position on the Y plane.\n" }, 84 { WorldEditor::AlignNegZ, "-Z", "Snap towards the lower position on the Z plane.\n" }, 85EndImplementEnumType; 86 87 88// unnamed namespace for static data 89namespace { 90 91 static VectorF axisVector[3] = { 92 VectorF(1.0f,0.0f,0.0f), 93 VectorF(0.0f,1.0f,0.0f), 94 VectorF(0.0f,0.0f,1.0f) 95 }; 96 97 static Point3F BoxPnts[] = { 98 Point3F(0.0f,0.0f,0.0f), 99 Point3F(0.0f,0.0f,1.0f), 100 Point3F(0.0f,1.0f,0.0f), 101 Point3F(0.0f,1.0f,1.0f), 102 Point3F(1.0f,0.0f,0.0f), 103 Point3F(1.0f,0.0f,1.0f), 104 Point3F(1.0f,1.0f,0.0f), 105 Point3F(1.0f,1.0f,1.0f) 106 }; 107 108 static U32 BoxVerts[][4] = { 109 {0,2,3,1}, // -x 110 {7,6,4,5}, // +x 111 {0,1,5,4}, // -y 112 {3,2,6,7}, // +y 113 {0,4,6,2}, // -z 114 {3,7,5,1} // +z 115 }; 116 117 // 118 U32 getBoxNormalIndex(const VectorF & normal) 119 { 120 const F32 * pNormal = ((const F32 *)normal); 121 122 F32 max = 0; 123 S32 index = -1; 124 125 for(U32 i = 0; i < 3; i++) 126 if(mFabs(pNormal[i]) >= mFabs(max)) 127 { 128 max = pNormal[i]; 129 index = i*2; 130 } 131 132 AssertFatal(index >= 0, "Failed to get best normal"); 133 if(max > 0.f) 134 index++; 135 136 return(index); 137 } 138 139 // 140 Point3F getBoundingBoxCenter(SceneObject * obj) 141 { 142 Box3F box = obj->getObjBox(); 143 MatrixF mat = obj->getTransform(); 144 VectorF scale = obj->getScale(); 145 146 Point3F center(0,0,0); 147 Point3F projPnts[8]; 148 149 for(U32 i = 0; i < 8; i++) 150 { 151 Point3F pnt; 152 pnt.set(BoxPnts[i].x ? box.maxExtents.x : box.minExtents.x, 153 BoxPnts[i].y ? box.maxExtents.y : box.minExtents.y, 154 BoxPnts[i].z ? box.maxExtents.z : box.minExtents.z); 155 156 // scale it 157 pnt.convolve(scale); 158 mat.mulP(pnt, &projPnts[i]); 159 center += projPnts[i]; 160 } 161 162 center /= 8; 163 return(center); 164 } 165 166 // 167 const char * parseObjectFormat(SimObject * obj, const char * format) 168 { 169 static char buf[1024]; 170 171 U32 curPos = 0; 172 U32 len = dStrlen(format); 173 174 for(U32 i = 0; i < len; i++) 175 { 176 if(format[i] == '$') 177 { 178 U32 j; 179 for(j = i+1; j < len; j++) 180 if(format[j] == '$') 181 break; 182 183 if(j == len) 184 break; 185 186 char token[80]; 187 188 AssertFatal((j - i) < (sizeof(token) - 1), "token too long"); 189 dStrncpy(token, &format[i+1], (j - i - 1)); 190 token[j-i-1] = 0; 191 192 U32 remaining = sizeof(buf) - curPos - 1; 193 194 // look at the token 195 if(!dStricmp(token, "id")) 196 curPos += dSprintf(buf + curPos, remaining, "%d", obj->getId()); 197 else if( dStricmp( token, "name|class" ) == 0 ) 198 { 199 const char* str; 200 if( obj->getName() && obj->getName()[ 0 ] ) 201 str = obj->getName(); 202 else 203 str = obj->getClassName(); 204 205 curPos += dSprintf( buf + curPos, remaining, "%s", str ); 206 } 207 else if(!dStricmp(token, "name|internal")) 208 { 209 if( obj->getName() || !obj->getInternalName() ) 210 curPos += dSprintf(buf + curPos, remaining, "%s", obj->getName()); 211 else 212 curPos += dSprintf(buf + curPos, remaining, "[%s]", obj->getInternalName()); 213 } 214 else if(!dStricmp(token, "name")) 215 curPos += dSprintf(buf + curPos, remaining, "%s", obj->getName()); 216 else if(!dStricmp(token, "class")) 217 curPos += dSprintf(buf + curPos, remaining, "%s", obj->getClassName()); 218 else if(!dStricmp(token, "namespace") && obj->getNamespace()) 219 curPos += dSprintf(buf + curPos, remaining, "%s", obj->getNamespace()->mName); 220 221 // 222 i = j; 223 } 224 else 225 buf[curPos++] = format[i]; 226 } 227 228 buf[curPos] = 0; 229 return(buf); 230 } 231 232 // 233 F32 snapFloat(F32 val, F32 snap) 234 { 235 if(snap == 0.f) 236 return(val); 237 238 F32 a = mFmod(val, snap); 239 240 if(mFabs(a) > (snap / 2)) 241 val < 0.f ? val -= snap : val += snap; 242 243 return(val - a); 244 } 245 246 // 247 EulerF extractEuler(const MatrixF & matrix) 248 { 249 const F32 * mat = (const F32*)matrix; 250 251 EulerF r; 252 r.x = mAsin(mat[MatrixF::idx(2,1)]); 253 254 if(mCos(r.x) != 0.f) 255 { 256 r.y = mAtan2(-mat[MatrixF::idx(2,0)], mat[MatrixF::idx(2,2)]); 257 r.z = mAtan2(-mat[MatrixF::idx(0,1)], mat[MatrixF::idx(1,1)]); 258 } 259 else 260 { 261 r.y = 0.f; 262 r.z = mAtan2(mat[MatrixF::idx(1,0)], mat[MatrixF::idx(0,0)]); 263 } 264 265 return(r); 266 } 267} 268 269F32 WorldEditor::smProjectDistance = 20000.0f; 270 271SceneObject* WorldEditor::getClientObj(SceneObject * obj) 272{ 273 AssertFatal(obj->isServerObject(), "WorldEditor::getClientObj: not a server object!"); 274 275 NetConnection * toServer = NetConnection::getConnectionToServer(); 276 NetConnection * toClient = NetConnection::getLocalClientConnection(); 277 if (!toServer || !toClient) 278 return NULL; 279 280 S32 index = toClient->getGhostIndex(obj); 281 if(index == -1) 282 return(0); 283 284 return(dynamic_cast<SceneObject*>(toServer->resolveGhost(index))); 285} 286 287void WorldEditor::markAsSelected( SimObject* object, bool state ) 288{ 289 object->setSelected( state ); 290 291 if( dynamic_cast< SceneObject* >( object ) ) 292 { 293 SceneObject* clientObj = WorldEditor::getClientObj( static_cast< SceneObject* >( object ) ); 294 if( clientObj ) 295 clientObj->setSelected( state ); 296 } 297} 298 299void WorldEditor::setClientObjInfo(SceneObject * obj, const MatrixF & mat, const VectorF & scale) 300{ 301 SceneObject * clientObj = getClientObj(obj); 302 if(!clientObj) 303 return; 304 305 clientObj->setTransform(mat); 306 clientObj->setScale(scale); 307} 308 309void WorldEditor::updateClientTransforms(Selection* sel) 310{ 311 for( U32 i = 0; i < sel->size(); ++ i ) 312 { 313 SceneObject* serverObj = dynamic_cast< SceneObject* >( ( *sel )[ i ] ); 314 if( !serverObj ) 315 continue; 316 317 SceneObject* clientObj = getClientObj( serverObj ); 318 if(!clientObj) 319 continue; 320 321 clientObj->setTransform(serverObj->getTransform()); 322 clientObj->setScale(serverObj->getScale()); 323 } 324} 325 326void WorldEditor::addUndoState() 327{ 328 submitUndo( mSelected ); 329} 330 331void WorldEditor::submitUndo( Selection* sel, const UTF8* label ) 332{ 333 // Grab the world editor undo manager. 334 UndoManager *undoMan = NULL; 335 if ( !Sim::findObject( "EUndoManager", undoMan ) ) 336 { 337 Con::errorf( "WorldEditor::createUndo() - EUndoManager not found!" ); 338 return; 339 } 340 341 // Setup the action. 342 WorldEditorUndoAction *action = new WorldEditorUndoAction( label ); 343 for(U32 i = 0; i < sel->size(); i++) 344 { 345 SceneObject* object = dynamic_cast< SceneObject* >( ( *sel )[ i ] ); 346 if( !object ) 347 continue; 348 349 WorldEditorUndoAction::Entry entry; 350 351 entry.mMatrix = object->getTransform(); 352 entry.mScale = object->getScale(); 353 entry.mObjId = object->getId(); 354 action->mEntries.push_back( entry ); 355 } 356 357 // Submit it. 358 action->mWorldEditor = this; 359 undoMan->addAction( action ); 360 361 // Mark the world editor as dirty! 362 setDirty(); 363} 364 365void WorldEditor::WorldEditorUndoAction::undo() 366{ 367 // NOTE: This function also handles WorldEditorUndoAction::redo(). 368 369 MatrixF oldMatrix; 370 VectorF oldScale; 371 for( U32 i = 0; i < mEntries.size(); i++ ) 372 { 373 SceneObject *obj; 374 if ( !Sim::findObject( mEntries[i].mObjId, obj ) ) 375 continue; 376 377 mWorldEditor->setClientObjInfo( obj, mEntries[i].mMatrix, mEntries[i].mScale ); 378 379 // Grab the current state. 380 oldMatrix = obj->getTransform(); 381 oldScale = obj->getScale(); 382 383 // Restore the saved state. 384 obj->setTransform( mEntries[i].mMatrix ); 385 obj->setScale( mEntries[i].mScale ); 386 387 // Store the previous state so the next time 388 // we're called we can restore it. 389 mEntries[i].mMatrix = oldMatrix; 390 mEntries[i].mScale = oldScale; 391 } 392 393 // Mark the world editor as dirty! 394 mWorldEditor->setDirty(); 395 mWorldEditor->mSelected->invalidateCentroid(); 396 397 // Let the script get a chance at it. 398 Con::executef( mWorldEditor, "onWorldEditorUndo" ); 399} 400 401//------------------------------------------------------------------------------ 402// edit stuff 403 404bool WorldEditor::cutSelection(Selection* sel) 405{ 406 if ( !sel->size() ) 407 return false; 408 409 // First copy the selection. 410 copySelection( sel ); 411 412 // Grab the world editor undo manager. 413 UndoManager *undoMan = NULL; 414 if ( !Sim::findObject( "EUndoManager", undoMan ) ) 415 { 416 Con::errorf( "WorldEditor::cutSelection() - EUndoManager not found!" ); 417 return false; 418 } 419 420 // Setup the action. 421 MEDeleteUndoAction *action = new MEDeleteUndoAction(); 422 while ( sel->size() ) 423 action->deleteObject( ( *sel )[0] ); 424 undoMan->addAction( action ); 425 426 // Mark the world editor as dirty! 427 setDirty(); 428 429 return true; 430} 431 432bool WorldEditor::copySelection(Selection* sel) 433{ 434 mCopyBuffer.clear(); 435 436 for( U32 i = 0; i < sel->size(); i++ ) 437 { 438 mCopyBuffer.increment(); 439 mCopyBuffer.last().save( ( *sel )[i] ); 440 } 441 442 return true; 443} 444 445bool WorldEditor::pasteSelection( bool dropSel ) 446{ 447 clearSelection(); 448 449 // Grab the world editor undo manager. 450 UndoManager *undoMan = NULL; 451 if ( !Sim::findObject( "EUndoManager", undoMan ) ) 452 { 453 Con::errorf( "WorldEditor::pasteSelection() - EUndoManager not found!" ); 454 return false; 455 } 456 457 SimGroup *missionGroup = NULL; 458 if( isMethod( "getNewObjectGroup" ) ) 459 { 460 const char* targetGroupName = Con::executef( this, "getNewObjectGroup" ); 461 if( targetGroupName && targetGroupName[ 0 ] && !Sim::findObject( targetGroupName, missionGroup ) ) 462 Con::errorf( "WorldEditor::pasteSelection() - no SimGroup called '%s'", targetGroupName ); 463 } 464 465 if( !missionGroup ) 466 { 467 if( !Sim::findObject( "MissionGroup", missionGroup ) ) 468 { 469 Con::errorf( "WorldEditor::pasteSelection() - MissionGroup not found" ); 470 return false; 471 } 472 } 473 474 // Setup the action. 475 MECreateUndoAction *action = new MECreateUndoAction( "Paste" ); 476 477 for( U32 i = 0; i < mCopyBuffer.size(); i++ ) 478 { 479 SimObject* obj = mCopyBuffer[i].restore(); 480 if ( !obj ) 481 continue; 482 483 if ( missionGroup ) 484 missionGroup->addObject( obj ); 485 486 action->addObject( obj ); 487 488 if ( !mSelectionLocked ) 489 { 490 mSelected->addObject( obj ); 491 Con::executef( this, "onSelect", obj->getIdString() ); 492 } 493 } 494 495 // Its safe to submit the action before the selection 496 // is dropped below because the state of the objects 497 // are not stored until they are first undone. 498 undoMan->addAction( action ); 499 500 // drop it ... 501 if ( dropSel ) 502 dropSelection( mSelected ); 503 504 if ( mSelected->size() ) 505 { 506 char buf[16]; 507 dSprintf( buf, sizeof(buf), "%d", ( *mSelected )[0]->getId() ); 508 509 SimObject *obj = NULL; 510 if(mRedirectID) 511 obj = Sim::findObject(mRedirectID); 512 Con::executef(obj ? obj : this, "onClick", buf); 513 } 514 515 // Mark the world editor as dirty! 516 setDirty(); 517 518 return true; 519} 520 521//------------------------------------------------------------------------------ 522 523WorldEditorSelection* WorldEditor::getActiveSelectionSet() const 524{ 525 return mSelected; 526} 527 528void WorldEditor::makeActiveSelectionSet( WorldEditorSelection* selection ) 529{ 530 Selection* oldSelection = getActiveSelectionSet(); 531 Selection* newSelection = selection; 532 533 if( oldSelection == newSelection ) 534 return; 535 536 // Unset the selection set so that calling onSelect/onUnselect callbacks 537 // on the editor object will not affect the sets we have. 538 539 mSelected = NULL; 540 541 // Go through all objects in the old selection and for each 542 // one that is not also in the new selection, signal an 543 // unselect. 544 545 if( oldSelection ) 546 { 547 for( Selection::iterator iter = oldSelection->begin(); iter != oldSelection->end(); ++ iter ) 548 if( !newSelection || !newSelection->objInSet( *iter ) ) 549 { 550 Con::executef( this, "onUnselect", ( *iter )->getIdString() ); 551 markAsSelected( *iter, false ); 552 } 553 554 oldSelection->setAutoSelect( false ); 555 } 556 557 // Go through all objects in the new selection and for each 558 // one that is not also in the old selection, signal a 559 // select. 560 561 if( newSelection ) 562 { 563 for( Selection::iterator iter = newSelection->begin(); iter != newSelection->end(); ++ iter ) 564 if( !oldSelection || !oldSelection->objInSet( *iter ) ) 565 { 566 markAsSelected( *iter, true ); 567 Con::executef( this, "onSelect", ( *iter )->getIdString() ); 568 } 569 570 newSelection->setAutoSelect( true ); 571 } 572 573 // Install the new selection set. 574 575 mSelected = newSelection; 576 577 if( isMethod( "onSelectionSetChanged" ) ) 578 Con::executef( this, "onSelectionSetChanged" ); 579} 580 581//------------------------------------------------------------------------------ 582 583void WorldEditor::hideObject(SceneObject* serverObj, bool hide) 584{ 585 // client 586 SceneObject * clientObj = getClientObj( serverObj ); 587 if( clientObj ) 588 clientObj->setHidden( hide ); 589 590 // server 591 serverObj->setHidden( hide ); 592} 593 594void WorldEditor::hideSelection(bool hide) 595{ 596 SimGroup* pGroup = dynamic_cast<SimGroup*>(Sim::findObject("MissionGroup")); 597 598 // set server/client objects hide field 599 for(U32 i = 0; i < mSelected->size(); i++) 600 { 601 SceneObject* serverObj = dynamic_cast< SceneObject* >( ( *mSelected )[ i ] ); 602 if( !serverObj ) 603 continue; 604 605 // Prevent non-mission group objects (i.e. Player) from being hidden. 606 // Otherwise it is difficult to show them again. 607 if(!serverObj->isChildOfGroup(pGroup)) 608 continue; 609 610 hideObject(serverObj, hide); 611 } 612} 613 614void WorldEditor::lockSelection(bool lock) 615{ 616 // 617 for(U32 i = 0; i < mSelected->size(); i++) 618 ( *mSelected )[i]->setLocked(lock); 619} 620 621//------------------------------------------------------------------------------ 622// the centroid get's moved to the drop point... 623void WorldEditor::dropSelection(Selection* sel) 624{ 625 if(!sel->size()) 626 return; 627 628 setDirty(); 629 630 Point3F centroid = mObjectsUseBoxCenter ? sel->getBoxCentroid() : sel->getCentroid(); 631 632 switch(mDropType) 633 { 634 case DropAtCentroid: 635 // already there 636 break; 637 638 case DropAtOrigin: 639 { 640 if(mDropAtBounds && !sel->containsGlobalBounds()) 641 { 642 const Point3F& boxCenter = sel->getBoxCentroid(); 643 const Box3F& bounds = sel->getBoxBounds(); 644 Point3F offset = -boxCenter; 645 offset.z += bounds.len_z() * 0.5f; 646 647 sel->offset( offset, mGridSnap ? mGridPlaneSize : 0.f ); 648 } 649 else 650 sel->offset( Point3F( -centroid ), mGridSnap ? mGridPlaneSize : 0.f ); 651 652 break; 653 } 654 655 case DropAtCameraWithRot: 656 { 657 Point3F center = centroid; 658 if(mDropAtBounds && !sel->containsGlobalBounds()) 659 center = sel->getBoxBottomCenter(); 660 661 sel->offset( Point3F( smCamPos - center ), mGridSnap ? mGridPlaneSize : 0.f ); 662 sel->orient(smCamMatrix, center); 663 break; 664 } 665 666 case DropAtCamera: 667 { 668 Point3F center = centroid; 669 if(mDropAtBounds && !sel->containsGlobalBounds()) 670 sel->getBoxBottomCenter(); 671 672 sel->offset( Point3F( smCamPos - center ), mGridSnap ? mGridPlaneSize : 0.f ); 673 break; 674 } 675 676 case DropBelowCamera: 677 { 678 Point3F center = centroid; 679 if(mDropAtBounds && !sel->containsGlobalBounds()) 680 center = sel->getBoxBottomCenter(); 681 682 Point3F offset = smCamPos - center; 683 offset.z -= mDropBelowCameraOffset; 684 sel->offset( offset, mGridSnap ? mGridPlaneSize : 0.f ); 685 break; 686 } 687 688 case DropAtScreenCenter: 689 { 690 // Use the center of the selection bounds 691 Point3F center = sel->getBoxCentroid(); 692 693 Gui3DMouseEvent event; 694 event.pos = smCamPos; 695 696 // Calculate the center of the sceen (in global screen coordinates) 697 Point2I offset = localToGlobalCoord(Point2I(0,0)); 698 Point3F sp(F32(offset.x + F32(getExtent().x / 2)), F32(offset.y + (getExtent().y / 2)), 1.0f); 699 700 // Calculate the view distance to fit the selection 701 // within the camera's view. 702 const Box3F bounds = sel->getBoxBounds(); 703 F32 radius = bounds.len()*0.5f; 704 F32 viewdist = calculateViewDistance(radius) * mDropAtScreenCenterScalar; 705 706 // Be careful of infinite sized objects, or just large ones in general. 707 if(viewdist > mDropAtScreenCenterMax) 708 viewdist = mDropAtScreenCenterMax; 709 710 // Position the selection 711 Point3F wp; 712 unproject(sp, &wp); 713 event.vec = wp - smCamPos; 714 event.vec.normalizeSafe(); 715 event.vec *= viewdist; 716 sel->offset( Point3F( event.pos - center ) += event.vec, mGridSnap ? mGridPlaneSize : 0.f ); 717 718 break; 719 } 720 721 case DropToTerrain: 722 { 723 terrainSnapSelection(sel, 0, mGizmo->getPosition(), true); 724 break; 725 } 726 727 case DropBelowSelection: 728 { 729 dropBelowSelection(sel, centroid, mDropAtBounds); 730 break; 731 } 732 } 733 734 // 735 updateClientTransforms(sel); 736} 737 738void WorldEditor::dropBelowSelection(Selection* sel, const Point3F & centroid, bool useBottomBounds) 739{ 740 if(!sel->size()) 741 return; 742 743 Point3F start; 744 if(useBottomBounds && !sel->containsGlobalBounds()) 745 start = sel->getBoxBottomCenter(); 746 else 747 start = centroid; 748 749 Point3F end = start; 750 end.z -= 4000.f; 751 752 sel->disableCollision(); // Make sure we don't hit ourselves. 753 754 RayInfo ri; 755 bool hit = gServerContainer.castRay(start, end, STATIC_COLLISION_TYPEMASK, &ri); 756 757 sel->enableCollision(); 758 759 if( hit ) 760 sel->offset( ri.point - start, mGridSnap ? mGridPlaneSize : 0.f ); 761} 762 763//------------------------------------------------------------------------------ 764 765void WorldEditor::terrainSnapSelection(Selection* sel, U8 modifier, Point3F gizmoPos, bool forceStick) 766{ 767 mStuckToGround = false; 768 769 if ( !mStickToGround && !forceStick ) 770 return; 771 772 if(!sel->size()) 773 return; 774 775 if(sel->containsGlobalBounds()) 776 return; 777 778 Point3F centroid; 779 if(mDropAtBounds && !sel->containsGlobalBounds()) 780 centroid = sel->getBoxBottomCenter(); 781 else 782 centroid = mObjectsUseBoxCenter ? sel->getBoxCentroid() : sel->getCentroid(); 783 784 Point3F start = centroid; 785 Point3F end = start; 786 start.z -= 2000; 787 end.z += 2000.f; 788 789 sel->disableCollision(); // Make sure we don't hit ourselves. 790 791 RayInfo ri; 792 bool hit; 793 if(mBoundingBoxCollision) 794 hit = gServerContainer.collideBox(start, end, TerrainObjectType, &ri); 795 else 796 hit = gServerContainer.castRay(start, end, TerrainObjectType, &ri); 797 798 sel->enableCollision(); 799 800 if( hit ) 801 { 802 mStuckToGround = true; 803 804 sel->offset( ri.point - centroid, mGridSnap ? mGridPlaneSize : 0.f ); 805 806 if(mTerrainSnapAlignment != AlignNone) 807 { 808 EulerF rot(0.0f, 0.0f, 0.0f); // Equivalent to AlignPosY 809 switch(mTerrainSnapAlignment) 810 { 811 case AlignPosX: 812 rot.set(0.0f, 0.0f, mDegToRad(-90.0f)); 813 break; 814 815 case AlignPosZ: 816 rot.set(mDegToRad(90.0f), 0.0f, mDegToRad(180.0f)); 817 break; 818 819 case AlignNegX: 820 rot.set(0.0f, 0.0f, mDegToRad(90.0f)); 821 break; 822 823 case AlignNegY: 824 rot.set(0.0f, 0.0f, mDegToRad(180.0f)); 825 break; 826 827 case AlignNegZ: 828 rot.set(mDegToRad(-90.0f), 0.0f, mDegToRad(180.0f)); 829 break; 830 } 831 832 MatrixF mat = MathUtils::createOrientFromDir(ri.normal); 833 MatrixF rotMat(rot); 834 835 sel->orient(mat.mul(rotMat), Point3F::Zero); 836 } 837 } 838} 839 840void WorldEditor::softSnapSelection(Selection* sel, U8 modifier, Point3F gizmoPos) 841{ 842 mSoftSnapIsStuck = false; 843 mSoftSnapActivated = false; 844 845 // If soft snap is activated, holding CTRL will temporarily deactivate it. 846 // Conversely, if soft snapping is deactivated, holding CTRL will activate it. 847 if( (mSoftSnap && (modifier & SI_PRIMARY_CTRL)) || (!mSoftSnap && !(modifier & SI_PRIMARY_CTRL)) ) 848 return; 849 850 if(!sel->size()) 851 return; 852 853 if(sel->containsGlobalBounds()) 854 return; 855 856 mSoftSnapActivated = true; 857 858 Point3F centroid = mObjectsUseBoxCenter ? sel->getBoxCentroid() : sel->getCentroid(); 859 860 // Find objects we may stick against 861 Vector<SceneObject*> foundobjs; 862 863 SceneObject *controlObj = getControlObject(); 864 if ( controlObj ) 865 controlObj->disableCollision(); 866 867 sel->disableCollision(); 868 869 if(mSoftSnapSizeByBounds) 870 { 871 mSoftSnapBounds = sel->getBoxBounds(); 872 mSoftSnapBounds.setCenter(centroid); 873 } 874 else 875 { 876 mSoftSnapBounds.set(Point3F(mSoftSnapSize, mSoftSnapSize, mSoftSnapSize)); 877 mSoftSnapBounds.setCenter(centroid); 878 } 879 880 mSoftSnapPreBounds = mSoftSnapBounds; 881 mSoftSnapPreBounds.setCenter(gizmoPos); 882 883 SphereF sphere(gizmoPos, mSoftSnapPreBounds.len()*0.5f); 884 885 gServerContainer.findObjectList(mSoftSnapPreBounds, 0xFFFFFFFF, &foundobjs); 886 887 sel->enableCollision(); 888 889 if ( controlObj ) 890 controlObj->enableCollision(); 891 892 ConcretePolyList polys; 893 for(S32 i=0; i<foundobjs.size(); ++i) 894 { 895 SceneObject* so = foundobjs[i]; 896 polys.setTransform(&(so->getTransform()), so->getScale()); 897 polys.setObject(so); 898 so->buildPolyList(PLC_Selection, &polys, mSoftSnapPreBounds, sphere); 899 } 900 901 // Calculate sticky point 902 bool found = false; 903 F32 foundDist = mSoftSnapPreBounds.len(); 904 Point3F foundPoint(0.0f, 0.0f, 0.0f); 905 PlaneF foundPlane; 906 MathUtils::IntersectInfo info; 907 908 if(mSoftSnapDebugRender) 909 { 910 mSoftSnapDebugPoint.set(0.0f, 0.0f, 0.0f); 911 mSoftSnapDebugTriangles.clear(); 912 } 913 914 F32 backfaceToleranceSize = mSoftSnapBackfaceTolerance*mSoftSnapSize; 915 for(S32 i=0; i<polys.mPolyList.size(); ++i) 916 { 917 ConcretePolyList::Poly p = polys.mPolyList[i]; 918 919 if(p.vertexCount >= 3) 920 { 921 S32 vertind[3]; 922 vertind[0] = polys.mIndexList[p.vertexStart]; 923 vertind[1] = polys.mIndexList[p.vertexStart + 1]; 924 vertind[2] = polys.mIndexList[p.vertexStart + 2]; 925 926 // Distance to the triangle 927 F32 d = MathUtils::mTriangleDistance(polys.mVertexList[vertind[0]], polys.mVertexList[vertind[1]], polys.mVertexList[vertind[2]], gizmoPos, &info); 928 929 // Cull backface polys that are not within tolerance 930 if(p.plane.whichSide(gizmoPos) == PlaneF::Back && d > backfaceToleranceSize) 931 continue; 932 933 bool changed = false; 934 if(d < foundDist) 935 { 936 changed = true; 937 found = true; 938 foundDist = d; 939 foundPoint = info.segment.p1; 940 foundPlane = p.plane; 941 942 if(mSoftSnapRenderTriangle) 943 { 944 mSoftSnapTriangle.p0 = polys.mVertexList[vertind[0]]; 945 mSoftSnapTriangle.p1 = polys.mVertexList[vertind[1]]; 946 mSoftSnapTriangle.p2 = polys.mVertexList[vertind[2]]; 947 } 948 } 949 950 if(mSoftSnapDebugRender) 951 { 952 Triangle debugTri; 953 debugTri.p0 = polys.mVertexList[vertind[0]]; 954 debugTri.p1 = polys.mVertexList[vertind[1]]; 955 debugTri.p2 = polys.mVertexList[vertind[2]]; 956 mSoftSnapDebugTriangles.push_back(debugTri); 957 958 if(changed) 959 { 960 mSoftSnapDebugSnapTri = debugTri; 961 mSoftSnapDebugPoint = foundPoint; 962 } 963 } 964 } 965 } 966 967 if(found) 968 { 969 // Align selection to foundPlane normal 970 if(mSoftSnapAlignment != AlignNone) 971 { 972 EulerF rot(0.0f, 0.0f, 0.0f); // Equivalent to AlignPosY 973 switch(mSoftSnapAlignment) 974 { 975 case AlignPosX: 976 rot.set(0.0f, 0.0f, mDegToRad(-90.0f)); 977 break; 978 979 case AlignPosZ: 980 rot.set(mDegToRad(90.0f), 0.0f, mDegToRad(180.0f)); 981 break; 982 983 case AlignNegX: 984 rot.set(0.0f, 0.0f, mDegToRad(90.0f)); 985 break; 986 987 case AlignNegY: 988 rot.set(0.0f, 0.0f, mDegToRad(180.0f)); 989 break; 990 991 case AlignNegZ: 992 rot.set(mDegToRad(-90.0f), 0.0f, mDegToRad(180.0f)); 993 break; 994 } 995 996 MatrixF mat = MathUtils::createOrientFromDir(foundPlane.getNormal()); 997 MatrixF rotMat(rot); 998 999 sel->orient(mat.mul(rotMat), Point3F::Zero); 1000 } 1001 1002 // Cast ray towards the selection to find how close to move it to the foundPoint 1003 F32 rayLength = mSoftSnapBounds.len() * 2; 1004 Point3F start = sel->getCentroid() - foundPlane.getNormal() * rayLength / 2; 1005 Point3F end = start + foundPlane.getNormal() * rayLength; 1006 1007 RayInfo ri; 1008 F32 minT = TypeTraits< F32 >::MAX; 1009 1010 for( U32 i = 0; i < sel->size(); ++ i ) 1011 { 1012 SceneObject* object = dynamic_cast< SceneObject* >( ( *sel )[ i ] ); 1013 if( !object ) 1014 continue; 1015 1016 // Convert start and end points to object space 1017 Point3F s, e; 1018 MatrixF mat = object->getTransform(); 1019 mat.inverse(); 1020 mat.mulP( start, &s ); 1021 mat.mulP( end, &e ); 1022 1023 if ( object->castRayRendered( s, e, &ri ) ) 1024 minT = getMin( minT, ri.t ); 1025 } 1026 1027 if ( minT <= 1.0f ) 1028 foundPoint += ( end - start ) * (0.5f - minT); 1029 1030 sel->offset( foundPoint - sel->getCentroid(), mGridSnap ? mGridPlaneSize : 0.f ); 1031 } 1032 1033 mSoftSnapIsStuck = found; 1034} 1035 1036//------------------------------------------------------------------------------ 1037 1038SceneObject * WorldEditor::getControlObject() 1039{ 1040 GameConnection * connection = GameConnection::getLocalClientConnection(); 1041 if(connection) 1042 return(dynamic_cast<SceneObject*>(connection->getControlObject())); 1043 return(0); 1044} 1045 1046bool WorldEditor::collide( const Gui3DMouseEvent &event, SceneObject **hitObj ) 1047{ 1048 // Collide against the screen-space class icons... 1049 1050 S32 collidedIconIdx = -1; 1051 F32 collidedIconDist = F32_MAX; 1052 1053 for ( U32 i = 0; i < mIcons.size(); i++ ) 1054 { 1055 const IconObject &icon = mIcons[i]; 1056 1057 if ( icon.rect.pointInRect( event.mousePoint ) && 1058 icon.dist < collidedIconDist ) 1059 { 1060 collidedIconIdx = i; 1061 collidedIconDist = icon.dist; 1062 } 1063 } 1064 1065 if ( collidedIconIdx != -1 ) 1066 { 1067 *hitObj = mIcons[collidedIconIdx].object; 1068 return true; 1069 } 1070 1071 if ( mBoundingBoxCollision ) 1072 { 1073 // Raycast against sceneObject bounding boxes... 1074 1075 SceneObject *controlObj = getControlObject(); 1076 if ( controlObj ) 1077 controlObj->disableCollision(); 1078 1079 Point3F startPnt = event.pos; 1080 Point3F endPnt = event.pos + event.vec * smProjectDistance; 1081 RayInfo ri; 1082 1083 bool hit = gServerContainer.collideBox(startPnt, endPnt, 0xFFFFFFFF, &ri); 1084 1085 if ( controlObj ) 1086 controlObj->enableCollision(); 1087 1088 if ( hit ) 1089 { 1090 // If we hit an object that is in a Prefab... 1091 // we really want to hit / select that Prefab. 1092 Prefab *prefab = Prefab::getPrefabByChild( ri.object ); 1093 1094 if ( prefab ) 1095 *hitObj = prefab; 1096 else 1097 *hitObj = ri.object; 1098 1099 return true; 1100 } 1101 } 1102 1103 // No icon hit so check against the mesh 1104 if ( mObjectMeshCollision ) 1105 { 1106 SceneObject *controlObj = getControlObject(); 1107 if ( controlObj ) 1108 controlObj->disableCollision(); 1109 1110 Point3F startPnt = event.pos; 1111 Point3F endPnt = event.pos + event.vec * smProjectDistance; 1112 RayInfo ri; 1113 1114 bool hit = gServerContainer.castRayRendered(startPnt, endPnt, 0xFFFFFFFF, &ri); 1115 if(hit && ri.object && ( ri.object->getTypeMask() & (TerrainObjectType) || dynamic_cast< GroundPlane* >( ri.object ))) 1116 { 1117 // We don't want to mesh select terrain 1118 hit = false; 1119 } 1120 1121 if ( controlObj ) 1122 controlObj->enableCollision(); 1123 1124 if ( hit ) 1125 { 1126 // If we hit an object that is in a Prefab... 1127 // we really want to hit / select that Prefab. 1128 Prefab *prefab = Prefab::getPrefabByChild( ri.object ); 1129 1130 if ( prefab ) 1131 *hitObj = prefab; 1132 else 1133 *hitObj = ri.object; 1134 1135 return true; 1136 } 1137 } 1138 1139 return false; 1140} 1141 1142//------------------------------------------------------------------------------ 1143// main render functions 1144 1145void WorldEditor::renderSelectionWorldBox(Selection* sel) 1146{ 1147 if( !mRenderSelectionBox || !sel->size() ) 1148 return; 1149 1150 // Compute the world bounds of the selection. 1151 1152 Box3F selBox( TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX, 1153 TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN ); 1154 1155 for( U32 i = 0; i < sel->size(); ++ i ) 1156 { 1157 SceneObject* object = dynamic_cast< SceneObject* >( ( *sel )[ i ] ); 1158 if( !object ) 1159 continue; 1160 1161 const Box3F & wBox = object->getWorldBox(); 1162 selBox.minExtents.setMin(wBox.minExtents); 1163 selBox.maxExtents.setMax(wBox.maxExtents); 1164 } 1165 1166 // Set up the render state block, if we haven't done so 1167 // already. 1168 1169 if ( mRenderSelectionBoxSB.isNull() ) 1170 { 1171 GFXStateBlockDesc desc; 1172 1173 desc.setCullMode( GFXCullNone ); 1174 desc.alphaDefined = true; 1175 desc.alphaTestEnable = true; 1176 desc.setZReadWrite( true, true ); 1177 mRenderSelectionBoxSB = GFX->createStateBlock( desc ); 1178 } 1179 1180 GFX->setStateBlock(mRenderSelectionBoxSB); 1181 1182 PrimBuild::color( mSelectionBoxColor ); 1183 1184 // create the box points 1185 Point3F projPnts[8]; 1186 for( U32 i = 0; i < 8; ++ i ) 1187 { 1188 Point3F pnt; 1189 pnt.set(BoxPnts[i].x ? selBox.maxExtents.x : selBox.minExtents.x, 1190 BoxPnts[i].y ? selBox.maxExtents.y : selBox.minExtents.y, 1191 BoxPnts[i].z ? selBox.maxExtents.z : selBox.minExtents.z); 1192 projPnts[i] = pnt; 1193 } 1194 1195 // do the box 1196 for(U32 j = 0; j < 6; j++) 1197 { 1198 PrimBuild::begin( GFXLineStrip, 4 ); 1199 for(U32 k = 0; k < 4; k++) 1200 { 1201 PrimBuild::vertex3fv( projPnts[BoxVerts[j][k]] ); 1202 } 1203 PrimBuild::end(); 1204 } 1205} 1206 1207void WorldEditor::renderObjectBox( SceneObject *obj, const ColorI &color ) 1208{ 1209 if ( mRenderObjectBoxSB.isNull() ) 1210 { 1211 GFXStateBlockDesc desc; 1212 desc.setCullMode( GFXCullNone ); 1213 desc.setZReadWrite( true, true ); 1214 mRenderObjectBoxSB = GFX->createStateBlock( desc ); 1215 } 1216 1217 GFX->setStateBlock(mRenderObjectBoxSB); 1218 1219 GFXTransformSaver saver; 1220 1221 Box3F objBox = obj->getObjBox(); 1222 Point3F objScale = obj->getScale(); 1223 Point3F boxScale = objBox.getExtents(); 1224 Point3F boxCenter = obj->getWorldBox().getCenter(); 1225 1226 MatrixF objMat = obj->getTransform(); 1227 objMat.scale(objScale); 1228 objMat.scale(boxScale); 1229 objMat.setPosition(boxCenter); 1230 1231 //GFX->multWorld( objMat ); 1232 1233 PrimBuild::color( ColorI(255,255,255,255) ); 1234 PrimBuild::begin( GFXLineList, 48 ); 1235 1236 //Box3F objBox = obj->getObjBox(); 1237 //Point3F size = objBox.getExtents(); 1238 //Point3F halfSize = size * 0.5f; 1239 1240 static const Point3F cubePoints[8] = 1241 { 1242 Point3F(-0.5, -0.5, -0.5), Point3F(-0.5, -0.5, 0.5), Point3F(-0.5, 0.5, -0.5), Point3F(-0.5, 0.5, 0.5), 1243 Point3F( 0.5, -0.5, -0.5), Point3F( 0.5, -0.5, 0.5), Point3F( 0.5, 0.5, -0.5), Point3F( 0.5, 0.5, 0.5) 1244 }; 1245 1246 // 8 corner points of the box 1247 for ( U32 i = 0; i < 8; i++ ) 1248 { 1249 //const Point3F &start = cubePoints[i]; 1250 1251 // 3 lines per corner point 1252 for ( U32 j = 0; j < 3; j++ ) 1253 { 1254 Point3F start = cubePoints[i]; 1255 Point3F end = start; 1256 end[j] *= 0.8f; 1257 1258 objMat.mulP(start); 1259 PrimBuild::vertex3fv(start); 1260 objMat.mulP(end); 1261 PrimBuild::vertex3fv(end); 1262 } 1263 } 1264 1265 PrimBuild::end(); 1266} 1267 1268void WorldEditor::renderObjectFace(SceneObject * obj, const VectorF & normal, const ColorI & col) 1269{ 1270 if ( mRenderObjectFaceSB.isNull() ) 1271 { 1272 GFXStateBlockDesc desc; 1273 desc.setCullMode( GFXCullNone ); 1274 desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); 1275 desc.setZReadWrite( false ); 1276 mRenderObjectFaceSB = GFX->createStateBlock( desc ); 1277 } 1278 1279 GFX->setStateBlock(mRenderObjectFaceSB); 1280 1281 // get the normal index 1282 VectorF objNorm; 1283 obj->getWorldTransform().mulV(normal, &objNorm); 1284 1285 U32 normI = getBoxNormalIndex(objNorm); 1286 1287 // 1288 Box3F box = obj->getObjBox(); 1289 MatrixF mat = obj->getTransform(); 1290 VectorF scale = obj->getScale(); 1291 1292 Point3F projPnts[4]; 1293 for(U32 i = 0; i < 4; i++) 1294 { 1295 Point3F pnt; 1296 pnt.set(BoxPnts[BoxVerts[normI][i]].x ? box.maxExtents.x : box.minExtents.x, 1297 BoxPnts[BoxVerts[normI][i]].y ? box.maxExtents.y : box.minExtents.y, 1298 BoxPnts[BoxVerts[normI][i]].z ? box.maxExtents.z : box.minExtents.z); 1299 1300 // scale it 1301 pnt.convolve(scale); 1302 mat.mulP(pnt, &projPnts[i]); 1303 } 1304 1305 PrimBuild::color( col ); 1306 1307 PrimBuild::begin( GFXTriangleStrip, 4 ); 1308 for(U32 k = 0; k < 4; k++) 1309 { 1310 PrimBuild::vertex3f(projPnts[k].x, projPnts[k].y, projPnts[k].z); 1311 } 1312 PrimBuild::end(); 1313} 1314 1315void WorldEditor::renderMousePopupInfo() 1316{ 1317 if ( !mMouseDragged ) 1318 return; 1319 1320 1321 if ( mGizmoProfile->mode == NoneMode || !mGizmoProfile->renderInfoText ) 1322 return; 1323 1324 char buf[256]; 1325 1326 switch ( mGizmoProfile->mode ) 1327 { 1328 case MoveMode: 1329 { 1330 if ( !bool(mSelected)|| !mSelected->size() ) 1331 return; 1332 1333 Point3F pos = getSelectionCentroid(); 1334 dSprintf(buf, sizeof(buf), "x: %0.3f, y: %0.3f, z: %0.3f", pos.x, pos.y, pos.z); 1335 1336 break; 1337 } 1338 1339 case RotateMode: 1340 { 1341 if ( !bool(mHitObject) || !bool(mSelected) || (mSelected->size() != 1) ) 1342 return; 1343 1344 // print out the angle-axis 1345 AngAxisF aa(mHitObject->getTransform()); 1346 1347 dSprintf(buf, sizeof(buf), "x: %0.3f, y: %0.3f, z: %0.3f, a: %0.3f", 1348 aa.axis.x, aa.axis.y, aa.axis.z, mRadToDeg(aa.angle)); 1349 1350 break; 1351 } 1352 1353 case ScaleMode: 1354 { 1355 if ( !bool(mHitObject) || !bool(mSelected) || (mSelected->size() != 1) ) 1356 return; 1357 1358 VectorF scale = mHitObject->getScale(); 1359 1360 Box3F box = mHitObject->getObjBox(); 1361 box.minExtents.convolve(scale); 1362 box.maxExtents.convolve(scale); 1363 1364 box.maxExtents -= box.minExtents; 1365 dSprintf(buf, sizeof(buf), "w: %0.3f, h: %0.3f, d: %0.3f", box.maxExtents.x, box.maxExtents.y, box.maxExtents.z); 1366 1367 break; 1368 } 1369 1370 default: 1371 return; 1372 } 1373 1374 U32 width = mProfile->mFont->getStrWidth((const UTF8 *)buf); 1375 Point2I posi( mLastMouseEvent.mousePoint.x, mLastMouseEvent.mousePoint.y + 12 ); 1376 1377 if ( mRenderPopupBackground ) 1378 { 1379 Point2I minPt(posi.x - width / 2 - 2, posi.y - 1); 1380 Point2I maxPt(posi.x + width / 2 + 2, posi.y + mProfile->mFont->getHeight() + 1); 1381 1382 GFX->getDrawUtil()->drawRectFill(minPt, maxPt, mPopupBackgroundColor); 1383 } 1384 1385 GFX->getDrawUtil()->setBitmapModulation(mPopupTextColor); 1386 GFX->getDrawUtil()->drawText(mProfile->mFont, Point2I(posi.x - width / 2, posi.y), buf); 1387} 1388 1389void WorldEditor::renderPaths(SimObject *obj) 1390{ 1391 if (obj == NULL) 1392 return; 1393 bool selected = false; 1394 1395 // Loop through subsets 1396 if (SimSet *set = dynamic_cast<SimSet*>(obj)) 1397 for(SimSetIterator itr(set); *itr; ++itr) { 1398 renderPaths(*itr); 1399 if ((*itr)->isSelected()) 1400 selected = true; 1401 } 1402 1403 // Render the path if it, or any of it's immediate sub-objects, is selected. 1404 if (SimPath::Path *path = dynamic_cast<SimPath::Path*>(obj)) 1405 if (selected || path->isSelected()) 1406 renderSplinePath(path); 1407} 1408 1409 1410void WorldEditor::renderSplinePath(SimPath::Path *path) 1411{ 1412 // at the time of writing the path properties are not part of the path object 1413 // so we don't know to render it looping, splined, linear etc. 1414 // for now we render all paths splined+looping 1415 1416 Vector<Point3F> positions; 1417 Vector<QuatF> rotations; 1418 1419 path->sortMarkers(); 1420 CameraSpline spline; 1421 1422 for(SimSetIterator itr(path); *itr; ++itr) 1423 { 1424 Marker *pathmarker = dynamic_cast<Marker*>(*itr); 1425 if (!pathmarker) 1426 continue; 1427 Point3F pos; 1428 pathmarker->getTransform().getColumn(3, &pos); 1429 1430 QuatF rot; 1431 rot.set(pathmarker->getTransform()); 1432 CameraSpline::Knot::Type type; 1433 switch (pathmarker->mKnotType) 1434 { 1435 case Marker::KnotTypePositionOnly: type = CameraSpline::Knot::POSITION_ONLY; break; 1436 case Marker::KnotTypeKink: type = CameraSpline::Knot::KINK; break; 1437 case Marker::KnotTypeNormal: 1438 default: type = CameraSpline::Knot::NORMAL; break; 1439 1440 } 1441 1442 CameraSpline::Knot::Path path; 1443 switch (pathmarker->mSmoothingType) 1444 { 1445 case Marker::SmoothingTypeLinear: path = CameraSpline::Knot::LINEAR; break; 1446 case Marker::SmoothingTypeSpline: 1447 default: path = CameraSpline::Knot::SPLINE; break; 1448 1449 } 1450 1451 spline.push_back(new CameraSpline::Knot(pos, rot, 1.0f, type, path)); 1452 } 1453 1454 F32 t = 0.0f; 1455 S32 size = spline.size(); 1456 if (size <= 1) 1457 return; 1458 1459 // DEBUG 1460 //spline.renderTimeMap(); 1461 1462 if (mSplineSB.isNull()) 1463 { 1464 GFXStateBlockDesc desc; 1465 1466 desc.setCullMode( GFXCullNone ); 1467 desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); 1468 desc.samplersDefined = true; 1469 desc.samplers[0].textureColorOp = GFXTOPDisable; 1470 1471 mSplineSB = GFX->createStateBlock( desc ); 1472 } 1473 1474 GFX->setStateBlock(mSplineSB); 1475 GFX->setupGenericShaders(); 1476 1477 if (path->isLooping()) 1478 { 1479 CameraSpline::Knot *front = new CameraSpline::Knot(*spline.front()); 1480 CameraSpline::Knot *back = new CameraSpline::Knot(*spline.back()); 1481 spline.push_back(front); 1482 spline.push_front(back); 1483 t = 1.0f; 1484 size += 2; 1485 } 1486 1487 VectorF a(-0.45f, -0.55f, 0.0f); 1488 VectorF b( 0.0f, 0.55f, 0.0f); 1489 VectorF c( 0.45f, -0.55f, 0.0f); 1490 1491 U32 vCount=0; 1492 1493 F32 tmpT = t; 1494 while (tmpT < size - 1) 1495 { 1496 tmpT = spline.advanceDist(tmpT, 1.0f); 1497 vCount++; 1498 } 1499 1500 // Build vertex buffer 1501 1502 U32 batchSize = vCount; 1503 1504 if(vCount > 4000) 1505 batchSize = 4000; 1506 1507 GFXVertexBufferHandle<GFXVertexPCT> vb; 1508 vb.set(GFX, 3*batchSize, GFXBufferTypeVolatile); 1509 void *lockPtr = vb.lock(); 1510 if(!lockPtr) return; 1511 1512 U32 vIdx=0; 1513 1514 while (t < size - 1) 1515 { 1516 CameraSpline::Knot k; 1517 spline.value(t, &k); 1518 t = spline.advanceDist(t, 1.0f); 1519 1520 k.mRotation.mulP(a, &vb[vIdx+0].point); 1521 k.mRotation.mulP(b, &vb[vIdx+1].point); 1522 k.mRotation.mulP(c, &vb[vIdx+2].point); 1523 1524 vb[vIdx+0].point += k.mPosition; 1525 vb[vIdx+1].point += k.mPosition; 1526 vb[vIdx+2].point += k.mPosition; 1527 1528 vb[vIdx+0].color.set(0, 255, 0, 0); 1529 vb[vIdx+1].color.set(0, 255, 0, 255); 1530 vb[vIdx+2].color.set(0, 255, 0, 0); 1531 1532 // vb[vIdx+3] = vb[vIdx+1]; 1533 1534 vIdx+=3; 1535 1536 // Do we have to knock it out? 1537 if(vIdx > 3 * batchSize - 10) 1538 { 1539 vb.unlock(); 1540 1541 // Render the buffer 1542 GFX->setVertexBuffer(vb); 1543 GFX->drawPrimitive(GFXTriangleList,0,vIdx/3); 1544 1545 // Reset for next pass... 1546 vIdx = 0; 1547 void *lockPtr = vb.lock(); 1548 if(!lockPtr) return; 1549 } 1550 } 1551 1552 vb.unlock(); 1553 1554 // Render the buffer 1555 GFX->setVertexBuffer(vb); 1556 //GFX->drawPrimitive(GFXLineStrip,0,3); 1557 1558 if(vIdx) 1559 GFX->drawPrimitive(GFXTriangleList,0,vIdx/3); 1560} 1561 1562void WorldEditor::renderScreenObj( SceneObject *obj, const Point3F& projPos, const Point3F& wPos ) 1563{ 1564 // Do not render control objects, hidden objects, 1565 // or objects that are within a prefab. 1566 if(obj == getControlObject() || obj->isHidden() || Prefab::getPrefabByChild(obj)) 1567 return; 1568 1569 GFXDrawUtil *drawer = GFX->getDrawUtil(); 1570 1571 // Lookup the ClassIcon - TextureHandle 1572 GFXTexHandle classIcon = gEditorIcons.findIcon( obj ); 1573 1574 if ( classIcon.isNull() ) 1575 classIcon = mDefaultClassEntry.mDefaultHandle; 1576 1577 U32 iconWidth = classIcon->getWidth(); 1578 U32 iconHeight = classIcon->getHeight(); 1579 1580 bool isHighlight = ( obj == mHitObject || mDragSelected->objInSet(obj) ); 1581 1582 if ( isHighlight ) 1583 { 1584 iconWidth += 0; 1585 iconHeight += 0; 1586 } 1587 1588 Point2I sPos( (S32)projPos.x, (S32)projPos.y ); 1589 //if ( obj->isSelected() ) 1590 // sPos.y += 4; 1591 Point2I renderPos = sPos; 1592 renderPos.x -= iconWidth / 2; 1593 renderPos.y -= iconHeight / 2; 1594 1595 Point2I iconSize( iconWidth, iconHeight ); 1596 1597 RectI renderRect( renderPos, iconSize ); 1598 1599 // Render object icon, except if the object is the 1600 // only selected object. Do render the icon when there are 1601 // multiple selection as otherwise, objects like lights are 1602 // difficult to place. 1603 1604 if( mRenderObjHandle && ( !obj->isSelected() || getSelectionSize() > 1 ) ) 1605 { 1606 // Compute icon fade. 1607 1608 S32 iconAlpha = 255; 1609 if( mFadeIcons && getDisplayType() == DisplayTypePerspective ) 1610 { 1611 Point3F objDist = smCamPos - wPos; 1612 F32 dist = objDist.len(); 1613 1614 if( dist > mFadeIconsDist ) 1615 { 1616 F32 iconDist = dist - mFadeIconsDist; 1617 iconAlpha = mClampF( 255 - ( 255 * ( iconDist / 10.f ) ), 0.f, 255.f ); 1618 } 1619 } 1620 1621 if ( isHighlight ) 1622 drawer->setBitmapModulation( ColorI(255,255,255, mClamp(iconAlpha + 50, 0, 255)) ); 1623 else 1624 drawer->setBitmapModulation( ColorI(255,255,255,iconAlpha) ); 1625 1626 drawer->drawBitmapStretch( classIcon, renderRect ); 1627 drawer->clearBitmapModulation(); 1628 1629 if ( obj->isLocked() ) 1630 drawer->drawBitmap( mDefaultClassEntry.mLockedHandle, renderPos ); 1631 1632 // Save an IconObject for performing icon-click testing later. 1633 1634 mIcons.increment(); 1635 IconObject& lastIcon = mIcons.last(); 1636 lastIcon.object = obj; 1637 lastIcon.rect = renderRect; 1638 lastIcon.dist = projPos.z; 1639 lastIcon.alpha = iconAlpha; 1640 } 1641 1642 // 1643 if ( mRenderObjText && ( obj == mHitObject || obj->isSelected() ) ) 1644 { 1645 const char * str = parseObjectFormat(obj, mObjTextFormat); 1646 1647 Point2I extent(mProfile->mFont->getStrWidth((const UTF8 *)str), mProfile->mFont->getHeight()); 1648 1649 Point2I pos(sPos); 1650 1651 if(mRenderObjHandle) 1652 { 1653 pos.x += (classIcon->getWidth() / 2) - (extent.x / 2); 1654 pos.y += (classIcon->getHeight() / 2) + 3; 1655 } 1656 1657 1658 if(mGizmoProfile->mode == NoneMode){ 1659 1660 drawer->drawBitmapStretch( classIcon, renderRect ); 1661 drawer->setBitmapModulation( ColorI(255,255,255,255) ); 1662 drawer->drawText(mProfile->mFont, pos, str); 1663 if ( obj->isLocked() ) 1664 drawer->drawBitmap( mDefaultClassEntry.mLockedHandle, renderPos ); 1665 1666 // Save an IconObject for performing icon-click testing later. 1667 { 1668 IconObject icon; 1669 icon.object = obj; 1670 icon.rect = renderRect; 1671 icon.dist = projPos.z; 1672 mIcons.push_back( icon ); 1673 } 1674 }else{ 1675 drawer->setBitmapModulation(mObjectTextColor); 1676 drawer->drawText(mProfile->mFont, pos, str); 1677 }; 1678 } 1679} 1680 1681//------------------------------------------------------------------------------ 1682// ClassInfo stuff 1683 1684WorldEditor::ClassInfo::~ClassInfo() 1685{ 1686 for(U32 i = 0; i < mEntries.size(); i++) 1687 delete mEntries[i]; 1688} 1689 1690bool WorldEditor::objClassIgnored(const SimObject * obj) 1691{ 1692 ClassInfo::Entry * entry = getClassEntry(obj); 1693 if(mToggleIgnoreList) 1694 return(!(entry ? entry->mIgnoreCollision : false)); 1695 else 1696 return(entry ? entry->mIgnoreCollision : false); 1697} 1698 1699WorldEditor::ClassInfo::Entry * WorldEditor::getClassEntry(StringTableEntry name) 1700{ 1701 AssertFatal(name, "WorldEditor::getClassEntry - invalid args"); 1702 for(U32 i = 0; i < mClassInfo.mEntries.size(); i++) 1703 if(!dStricmp(name, mClassInfo.mEntries[i]->mName)) 1704 return(mClassInfo.mEntries[i]); 1705 return(0); 1706} 1707 1708WorldEditor::ClassInfo::Entry * WorldEditor::getClassEntry(const SimObject * obj) 1709{ 1710 AssertFatal(obj, "WorldEditor::getClassEntry - invalid args"); 1711 return(getClassEntry(obj->getClassName())); 1712} 1713 1714bool WorldEditor::addClassEntry(ClassInfo::Entry * entry) 1715{ 1716 AssertFatal(entry, "WorldEditor::addClassEntry - invalid args"); 1717 if(getClassEntry(entry->mName)) 1718 return(false); 1719 1720 mClassInfo.mEntries.push_back(entry); 1721 return(true); 1722} 1723 1724//------------------------------------------------------------------------------ 1725// Mouse cursor stuff 1726void WorldEditor::setCursor(U32 cursor) 1727{ 1728 mCurrentCursor = cursor; 1729} 1730 1731//------------------------------------------------------------------------------ 1732Signal<void(WorldEditor*)> WorldEditor::smRenderSceneSignal; 1733 1734WorldEditor::WorldEditor() 1735 : mCurrentCursor(PlatformCursorController::curArrow) 1736{ 1737 VECTOR_SET_ASSOCIATION( mIcons ); 1738 1739 // init the field data 1740 mDropType = DropAtScreenCenter; 1741 mBoundingBoxCollision = true; 1742 mObjectMeshCollision = true; 1743 mRenderPopupBackground = true; 1744 mPopupBackgroundColor.set(100,100,100); 1745 mPopupTextColor.set(255,255,0); 1746 mSelectHandle = StringTable->insert("tools/worldEditor/images/SelectHandle"); 1747 mDefaultHandle = StringTable->insert("tools/worldEditor/images/DefaultHandle"); 1748 mLockedHandle = StringTable->insert("tools/worldEditor/images/LockedHandle"); 1749 mObjectTextColor.set(255,255,255); 1750 mObjectsUseBoxCenter = true; 1751 1752 mObjSelectColor.set(255,0,0,200); 1753 mObjMultiSelectColor.set(128,0,0,200); 1754 mObjMouseOverSelectColor.set(0,0,255); 1755 mObjMouseOverColor.set(0,255,0); 1756 mShowMousePopupInfo = true; 1757 mDragRectColor.set(255,255,0); 1758 mRenderObjText = true; 1759 mRenderObjHandle = true; 1760 mObjTextFormat = StringTable->insert("$id$: $name|internal$"); 1761 mFaceSelectColor.set(0,0,100,100); 1762 mRenderSelectionBox = true; 1763 mSelectionBoxColor.set(255,255,0,100); 1764 mSelectionLocked = false; 1765 1766 mToggleIgnoreList = false; 1767 1768 mIsDirty = false; 1769 1770 mRedirectID = 0; 1771 1772 // 1773 mHitObject = NULL; 1774 1775 // 1776 //mDefaultMode = mCurrentMode = Move; 1777 mMouseDown = false; 1778 mDragSelect = false; 1779 1780 mStickToGround = false; 1781 mStuckToGround = false; 1782 mTerrainSnapAlignment = AlignNone; 1783 mDropAtBounds = false; 1784 mDropBelowCameraOffset = 15.0f; 1785 mDropAtScreenCenterScalar = 1.0f; 1786 mDropAtScreenCenterMax = 100.0f; 1787 1788 // Create the drag selection set. 1789 1790 mDragSelected = new Selection(); 1791 mDragSelected->registerObject( "EWorldEditorDragSelection" ); 1792 Sim::getRootGroup()->addObject( mDragSelected ); 1793 mDragSelected->setAutoSelect(false); 1794 1795 // 1796 mSoftSnap = false; 1797 mSoftSnapActivated = false; 1798 mSoftSnapIsStuck = false; 1799 mSoftSnapAlignment = AlignNone; 1800 mSoftSnapRender = true; 1801 mSoftSnapRenderTriangle = false; 1802 mSoftSnapSizeByBounds = false; 1803 mSoftSnapSize = 2.0f; 1804 mSoftSnapBackfaceTolerance = 0.5f; 1805 mSoftSnapDebugRender = false; 1806 mSoftSnapDebugPoint.set(0.0f, 0.0f, 0.0f); 1807 1808 mGridSnap = false; 1809 1810 mFadeIcons = true; 1811 mFadeIconsDist = 8.f; 1812} 1813 1814WorldEditor::~WorldEditor() 1815{ 1816} 1817 1818//------------------------------------------------------------------------------ 1819 1820bool WorldEditor::onAdd() 1821{ 1822 if(!Parent::onAdd()) 1823 return(false); 1824 1825 // create the default class entry 1826 mDefaultClassEntry.mName = 0; 1827 mDefaultClassEntry.mIgnoreCollision = false; 1828 mDefaultClassEntry.mDefaultHandle = GFXTexHandle(mDefaultHandle, &GFXDefaultStaticDiffuseProfile, avar("%s() - mDefaultClassEntry.mDefaultHandle (line %d)", __FUNCTION__, __LINE__)); 1829 mDefaultClassEntry.mSelectHandle = GFXTexHandle(mSelectHandle, &GFXDefaultStaticDiffuseProfile, avar("%s() - mDefaultClassEntry.mSelectHandle (line %d)", __FUNCTION__, __LINE__)); 1830 mDefaultClassEntry.mLockedHandle = GFXTexHandle(mLockedHandle, &GFXDefaultStaticDiffuseProfile, avar("%s() - mDefaultClassEntry.mLockedHandle (line %d)", __FUNCTION__, __LINE__)); 1831 1832 if(!(mDefaultClassEntry.mDefaultHandle && mDefaultClassEntry.mSelectHandle && mDefaultClassEntry.mLockedHandle)) 1833 return false; 1834 1835 //mGizmo = new Gizmo(); 1836 //mGizmo->registerObject("WorldEditorGizmo"); 1837 mGizmo->assignName("WorldEditorGizmo"); 1838 1839 return true; 1840} 1841 1842//------------------------------------------------------------------------------ 1843 1844void WorldEditor::onEditorEnable() 1845{ 1846 // go through and copy the hidden field to the client objects... 1847 for(SimSetIterator itr(Sim::getRootGroup()); *itr; ++itr) 1848 { 1849 SceneObject * obj = dynamic_cast<SceneObject *>(*itr); 1850 if(!obj) 1851 continue; 1852 1853 // only work with a server obj... 1854 if(obj->isClientObject()) 1855 continue; 1856 1857 // grab the client object 1858 SceneObject * clientObj = getClientObj(obj); 1859 if(!clientObj) 1860 continue; 1861 1862 // 1863 clientObj->setHidden(obj->isHidden()); 1864 } 1865} 1866 1867//------------------------------------------------------------------------------ 1868 1869void WorldEditor::get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event) 1870{ 1871 TORQUE_UNUSED(event); 1872 cursor = NULL; 1873 visible = false; 1874 1875 GuiCanvas *pRoot = getRoot(); 1876 if( !pRoot ) 1877 return Parent::get3DCursor(cursor,visible,event); 1878 1879 if(pRoot->mCursorChanged != mCurrentCursor) 1880 { 1881 PlatformWindow *pWindow = static_cast<GuiCanvas*>(getRoot())->getPlatformWindow(); 1882 AssertFatal(pWindow != NULL,"GuiControl without owning platform window! This should not be possible."); 1883 PlatformCursorController *pController = pWindow->getCursorController(); 1884 AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!"); 1885 1886 // We've already changed the cursor, 1887 // so set it back before we change it again. 1888 if(pRoot->mCursorChanged != -1) 1889 pController->popCursor(); 1890 1891 // Now change the cursor shape 1892 pController->pushCursor(mCurrentCursor); 1893 pRoot->mCursorChanged = mCurrentCursor; 1894 } 1895} 1896 1897//TODO: [rene 03/10 -- The entire event handling code here needs cleanup] 1898 1899void WorldEditor::on3DMouseMove(const Gui3DMouseEvent & event) 1900{ 1901 setCursor(PlatformCursorController::curArrow); 1902 mHitObject = NULL; 1903 1904 // 1905 mUsingAxisGizmo = false; 1906 1907 if ( bool(mSelected) && mSelected->size() > 0 ) 1908 { 1909 mGizmo->on3DMouseMove( event ); 1910 1911 if ( mGizmo->getSelection() != Gizmo::None ) 1912 { 1913 mUsingAxisGizmo = true; 1914 mHitObject = dynamic_cast< SceneObject* >( ( *mSelected )[0] ); 1915 } 1916 } 1917 1918 if ( !mHitObject ) 1919 { 1920 SceneObject *hitObj = NULL; 1921 if ( collide(event, &hitObj) && hitObj->isSelectionEnabled() && !objClassIgnored(hitObj) ) 1922 { 1923 mHitObject = hitObj; 1924 } 1925 } 1926 1927 mLastMouseEvent = event; 1928} 1929 1930void WorldEditor::on3DMouseDown(const Gui3DMouseEvent & event) 1931{ 1932 mMouseDown = true; 1933 mMouseDragged = false; 1934 mPerformedDragCopy = false; 1935 mDragGridSnapToggle = false; 1936 mLastMouseDownEvent = event; 1937 1938 mouseLock(); 1939 1940 // check gizmo first 1941 mUsingAxisGizmo = false; 1942 mNoMouseDrag = false; 1943 1944 if ( bool(mSelected) && mSelected->size() > 0 ) 1945 { 1946 // Update the move grid settings depending on the 1947 // bounds of the current selection. 1948 1949 const Box3F& selBounds = getActiveSelectionSet()->getBoxBounds(); 1950 const F32 maxDim = getMax( selBounds.len_x(), getMax( selBounds.len_y(), selBounds.len_z() ) ); 1951 const F32 size = mCeil( maxDim + 10.f ); 1952 const F32 spacing = mCeil( size / 20.f ); 1953 1954 if( dynamic_cast< SceneObject* >( ( *mSelected )[0] )) 1955 { 1956 1957 if (size > 0) 1958 { 1959 mGizmo->setMoveGridSize( size ); 1960 mGizmo->setMoveGridSpacing( spacing ); 1961 } 1962 1963 // Let the gizmo handle the event. 1964 1965 mGizmo->on3DMouseDown( event ); 1966 } 1967 1968 if ( mGizmo->getSelection() != Gizmo::None ) 1969 { 1970 mUsingAxisGizmo = true; 1971 mHitObject = dynamic_cast< SceneObject* >( ( *mSelected )[0] ); 1972 1973 return; 1974 } 1975 } 1976 1977 SceneObject *hitObj = NULL; 1978 if ( collide( event, &hitObj ) && hitObj->isSelectionEnabled() && !objClassIgnored( hitObj ) ) 1979 { 1980 mPossibleHitObject = hitObj; 1981 mNoMouseDrag = true; 1982 } 1983 else if ( !mSelectionLocked ) 1984 { 1985 if ( !(event.modifier & ( SI_RANGESELECT | SI_MULTISELECT ) ) ) 1986 clearSelection(); 1987 1988 mDragSelect = true; 1989 mDragSelected->clear(); 1990 mDragRect.set( Point2I(event.mousePoint), Point2I(0,0) ); 1991 mDragStart = event.mousePoint; 1992 } 1993 1994 mLastMouseEvent = event; 1995} 1996 1997void WorldEditor::on3DMouseUp( const Gui3DMouseEvent &event ) 1998{ 1999 const bool wasUsingAxisGizmo = mUsingAxisGizmo; 2000 2001 mMouseDown = false; 2002 mStuckToGround = false; 2003 mSoftSnapIsStuck = false; 2004 mSoftSnapActivated = false; 2005 mUsingAxisGizmo = false; 2006 mGizmo->on3DMouseUp(event); 2007 2008 // Restore grid snap if we temporarily toggled it. 2009 2010 if( mDragGridSnapToggle ) 2011 { 2012 mDragGridSnapToggle = false; 2013 const bool snapToGrid = !mGridSnap; 2014 mGridSnap = snapToGrid; 2015 mGizmo->getProfile()->snapToGrid = snapToGrid; 2016 } 2017 2018 // check if selecting objects.... 2019 if ( mDragSelect ) 2020 { 2021 mDragSelect = false; 2022 mPossibleHitObject = NULL; 2023 2024 const bool addToSelection = ( event.modifier & ( SI_RANGESELECT | SI_MULTISELECT ) ); 2025 2026 // add all the objects from the drag selection into the normal selection 2027 if( !addToSelection ) 2028 clearSelection(); 2029 2030 if ( mDragSelected->size() > 1 ) 2031 { 2032 for ( U32 i = 0; i < mDragSelected->size(); i++ ) 2033 mSelected->addObject( ( *mDragSelected )[i] ); 2034 2035 Con::executef( this, "onMultiSelect", mDragSelected->getIdString(), addToSelection ? "1" : "0" ); 2036 mDragSelected->clear(); 2037 2038 SimObject *obj = NULL; 2039 if ( mRedirectID ) 2040 obj = Sim::findObject( mRedirectID ); 2041 Con::executef( obj ? obj : this, "onClick", ( *mSelected )[ 0 ]->getIdString() ); 2042 } 2043 else if ( mDragSelected->size() == 1 ) 2044 { 2045 mSelected->addObject( ( *mDragSelected )[0] ); 2046 Con::executef( this, "onSelect", ( *mDragSelected )[ 0 ]->getIdString() ); 2047 mDragSelected->clear(); 2048 2049 SimObject *obj = NULL; 2050 if ( mRedirectID ) 2051 obj = Sim::findObject( mRedirectID ); 2052 Con::executef( obj ? obj : this, "onClick", ( *mSelected )[ 0 ]->getIdString() ); 2053 } 2054 2055 mouseUnlock(); 2056 return; 2057 } 2058 else if( mPossibleHitObject.isValid() && !wasUsingAxisGizmo ) 2059 { 2060 if ( !mSelectionLocked ) 2061 { 2062 if ( event.modifier & ( SI_RANGESELECT | SI_MULTISELECT ) ) 2063 { 2064 mNoMouseDrag = true; 2065 if ( mSelected->objInSet( mPossibleHitObject ) ) 2066 { 2067 mSelected->removeObject( mPossibleHitObject ); 2068 mSelected->storeCurrentCentroid(); 2069 Con::executef( this, "onUnSelect", mPossibleHitObject->getIdString() ); 2070 } 2071 else 2072 { 2073 mSelected->addObject( mPossibleHitObject ); 2074 mSelected->storeCurrentCentroid(); 2075 Con::executef( this, "onSelect", mPossibleHitObject->getIdString() ); 2076 } 2077 } 2078 else 2079 { 2080 if ( bool(mSelected) && !mSelected->objInSet( mPossibleHitObject ) ) 2081 { 2082 mNoMouseDrag = true; 2083 2084 // Call onUnSelect. Because of the whole treeview<->selection synchronization, 2085 // this may actually cause things to disappear from mSelected so do the loop 2086 // in reverse. This will make the loop work even if items are removed as 2087 // we go along. 2088 for( S32 i = mSelected->size() - 1; i >= 0; -- i ) 2089 Con::executef( this, "onUnSelect", ( *mSelected )[ i ]->getIdString() ); 2090 2091 mSelected->clear(); 2092 mSelected->addObject( mPossibleHitObject ); 2093 mSelected->storeCurrentCentroid(); 2094 Con::executef( this, "onSelect", mPossibleHitObject->getIdString() ); 2095 } 2096 } 2097 } 2098 2099 if ( event.mouseClickCount > 1 ) 2100 { 2101 // 2102 char buf[16]; 2103 dSprintf(buf, sizeof(buf), "%d", mPossibleHitObject->getId()); 2104 2105 SimObject *obj = NULL; 2106 if ( mRedirectID ) 2107 obj = Sim::findObject( mRedirectID ); 2108 Con::executef( obj ? obj : this, "onDblClick", buf ); 2109 } 2110 else 2111 { 2112 char buf[16]; 2113 dSprintf( buf, sizeof(buf), "%d", mPossibleHitObject->getId() ); 2114 2115 SimObject *obj = NULL; 2116 if ( mRedirectID ) 2117 obj = Sim::findObject( mRedirectID ); 2118 Con::executef( obj ? obj : this, "onClick", buf ); 2119 } 2120 2121 mHitObject = mPossibleHitObject; 2122 } 2123 2124 if ( bool(mSelected) && mSelected->hasCentroidChanged() ) 2125 { 2126 Con::executef( this, "onSelectionCentroidChanged"); 2127 } 2128 2129 if ( mMouseDragged && bool(mSelected) && mSelected->size() ) 2130 { 2131 if ( mSelected->size() ) 2132 { 2133 if ( isMethod("onEndDrag") ) 2134 { 2135 SimObject * obj = 0; 2136 if ( mRedirectID ) 2137 obj = Sim::findObject( mRedirectID ); 2138 Con::executef( obj ? obj : this, "onEndDrag", ( *mSelected )[ 0 ]->getIdString() ); 2139 } 2140 } 2141 } 2142 2143 //if ( mHitObject ) 2144 // mHitObject->inspectPostApply(); 2145 //mHitObject = NULL; 2146 2147 // 2148 //mHitObject = hitObj; 2149 mouseUnlock(); 2150} 2151 2152void WorldEditor::on3DMouseDragged(const Gui3DMouseEvent & event) 2153{ 2154 if ( !mMouseDown ) 2155 return; 2156 2157 if ( mNoMouseDrag && !mUsingAxisGizmo ) 2158 { 2159 // Perhaps we should start the drag after all 2160 if( mAbs(mLastMouseDownEvent.mousePoint.x - event.mousePoint.x) > 2 || mAbs(mLastMouseDownEvent.mousePoint.y - event.mousePoint.y) > 2 ) 2161 { 2162 if ( !(event.modifier & ( SI_RANGESELECT | SI_MULTISELECT ) ) ) 2163 clearSelection(); 2164 2165 mDragSelect = true; 2166 mDragSelected->clear(); 2167 mDragRect.set( Point2I(mLastMouseDownEvent.mousePoint), Point2I(0,0) ); 2168 mDragStart = mLastMouseDownEvent.mousePoint; 2169 2170 mNoMouseDrag = false; 2171 mHitObject = NULL; 2172 } 2173 else 2174 { 2175 return; 2176 } 2177 } 2178 2179 // 2180 if ( !mMouseDragged ) 2181 { 2182 if ( !mUsingAxisGizmo ) 2183 { 2184 // vert drag on new object.. reset hit offset 2185 if ( mHitObject && bool(mSelected) &&!mSelected->objInSet( mHitObject ) ) 2186 { 2187 if ( !mSelectionLocked ) 2188 mSelected->addObject( mHitObject ); 2189 } 2190 } 2191 2192 // create and add an undo state 2193 if ( !mDragSelect ) 2194 submitUndo( mSelected ); 2195 2196 mMouseDragged = true; 2197 } 2198 2199 // update the drag selection 2200 if ( mDragSelect ) 2201 { 2202 // build the drag selection on the renderScene method - make sure no neg extent! 2203 mDragRect.point.x = (event.mousePoint.x < mDragStart.x) ? event.mousePoint.x : mDragStart.x; 2204 mDragRect.extent.x = (event.mousePoint.x > mDragStart.x) ? event.mousePoint.x - mDragStart.x : mDragStart.x - event.mousePoint.x; 2205 mDragRect.point.y = (event.mousePoint.y < mDragStart.y) ? event.mousePoint.y : mDragStart.y; 2206 mDragRect.extent.y = (event.mousePoint.y > mDragStart.y) ? event.mousePoint.y - mDragStart.y : mDragStart.y - event.mousePoint.y; 2207 return; 2208 } 2209 2210 if ( !mUsingAxisGizmo && ( !mHitObject || !mSelected->objInSet( mHitObject ) ) ) 2211 return; 2212 2213 // anything locked? 2214 for ( U32 i = 0; i < mSelected->size(); i++ ) 2215 if ( ( *mSelected )[i]->isLocked() ) 2216 return; 2217 2218 if ( mUsingAxisGizmo ) 2219 mGizmo->on3DMouseDragged( event ); 2220 2221 switch ( mGizmoProfile->mode ) 2222 { 2223 case MoveMode: 2224 2225 // grabbed axis gizmo? 2226 if ( mUsingAxisGizmo ) 2227 { 2228 // Check if a copy should be made 2229 if ( event.modifier & SI_SHIFT && !mPerformedDragCopy ) 2230 { 2231 mPerformedDragCopy = true; 2232 mPossibleHitObject = NULL; 2233 2234 copySelection( mSelected ); 2235 pasteSelection( false ); 2236 } 2237 2238 // Check for grid snap toggle with ALT. 2239 2240 if( event.modifier & SI_PRIMARY_ALT ) 2241 { 2242 if( !mDragGridSnapToggle ) 2243 { 2244 mDragGridSnapToggle = true; 2245 const bool snapToGrid = !mGridSnap; 2246 mGridSnap = snapToGrid; 2247 mGizmo->getProfile()->snapToGrid = snapToGrid; 2248 } 2249 } 2250 else if( mDragGridSnapToggle ) 2251 { 2252 mDragGridSnapToggle = false; 2253 const bool snapToGrid = !mGridSnap; 2254 mGridSnap = snapToGrid; 2255 mGizmo->getProfile()->snapToGrid = snapToGrid; 2256 } 2257 2258 mSelected->offset( mGizmo->getOffset() ); 2259 2260 // Handle various sticking 2261 terrainSnapSelection( mSelected, event.modifier, mGizmo->getPosition() ); 2262 softSnapSelection( mSelected, event.modifier, mGizmo->getPosition() ); 2263 2264 updateClientTransforms( mSelected ); 2265 } 2266 break; 2267 2268 case ScaleMode: 2269 if ( mUsingAxisGizmo ) 2270 { 2271 Point3F scale = mGizmo->getScale() / (mGizmo->getScale() - mGizmo->getDeltaScale()); 2272 2273 // Can scale each object independently around its own origin, or scale 2274 // the selection as a group around the centroid 2275 if ( mObjectsUseBoxCenter ) 2276 mSelected->scale( scale, getSelectionCentroid() ); 2277 else 2278 mSelected->scale( scale ); 2279 2280 updateClientTransforms(mSelected); 2281 } 2282 2283 break; 2284 2285 case RotateMode: 2286 { 2287 Point3F centroid = getSelectionCentroid(); 2288 EulerF rot = mGizmo->getDeltaRot(); 2289 2290 mSelected->rotate(rot, centroid); 2291 updateClientTransforms(mSelected); 2292 2293 break; 2294 } 2295 2296 default: 2297 break; 2298 } 2299 2300 mLastMouseEvent = event; 2301} 2302 2303void WorldEditor::on3DMouseEnter(const Gui3DMouseEvent &) 2304{ 2305} 2306 2307void WorldEditor::on3DMouseLeave(const Gui3DMouseEvent &) 2308{ 2309} 2310 2311void WorldEditor::on3DRightMouseDown(const Gui3DMouseEvent & event) 2312{ 2313} 2314 2315void WorldEditor::on3DRightMouseUp(const Gui3DMouseEvent & event) 2316{ 2317} 2318 2319//------------------------------------------------------------------------------ 2320 2321void WorldEditor::updateGuiInfo() 2322{ 2323 SimObject * obj = 0; 2324 if ( mRedirectID ) 2325 obj = Sim::findObject( mRedirectID ); 2326 2327 char buf[] = ""; 2328 Con::executef( obj ? obj : this, "onGuiUpdate", buf ); 2329} 2330 2331//------------------------------------------------------------------------------ 2332 2333static void findObjectsCallback( SceneObject *obj, void *val ) 2334{ 2335 Vector<SceneObject*> * list = (Vector<SceneObject*>*)val; 2336 list->push_back(obj); 2337} 2338 2339struct DragMeshCallbackData { 2340 WorldEditor* mWorldEditor; 2341 Box3F mBounds; 2342 SphereF mSphereBounds; 2343 Vector<SceneObject*> mObjects; 2344 EarlyOutPolyList mPolyList; 2345 MatrixF mStandardMat; 2346 Point3F mStandardScale; 2347 2348 DragMeshCallbackData(WorldEditor* we, Box3F &bounds, SphereF &sphereBounds) 2349 { 2350 mWorldEditor = we; 2351 mBounds = bounds; 2352 mSphereBounds = sphereBounds; 2353 mStandardMat.identity(); 2354 mStandardScale.set(1.0f, 1.0f, 1.0f); 2355 } 2356}; 2357 2358static Frustum gDragFrustum; 2359static void findDragMeshCallback( SceneObject *obj, void *data ) 2360{ 2361 DragMeshCallbackData* dragData = reinterpret_cast<DragMeshCallbackData*>(data); 2362 2363 if ( dragData->mWorldEditor->objClassIgnored( obj ) || 2364 !obj->isSelectionEnabled() || 2365 obj->getTypeMask() & ( TerrainObjectType | ProjectileObjectType ) || 2366 dynamic_cast< GroundPlane* >( obj ) || 2367 Prefab::getPrefabByChild( obj ) ) 2368 { 2369 return; 2370 } 2371 2372 // Reset the poly list for us 2373 dragData->mPolyList.clear(); 2374 dragData->mPolyList.setTransform(&(dragData->mStandardMat), dragData->mStandardScale); 2375 2376 // Do the work 2377 obj->buildPolyList(PLC_Selection, &(dragData->mPolyList), dragData->mBounds, dragData->mSphereBounds); 2378 if (!dragData->mPolyList.isEmpty()) 2379 { 2380 dragData->mObjects.push_back(obj); 2381 } 2382} 2383 2384void WorldEditor::renderScene( const RectI &updateRect ) 2385{ 2386 GFXDEBUGEVENT_SCOPE( Editor_renderScene, ColorI::RED ); 2387 2388 smRenderSceneSignal.trigger(this); 2389 2390 // Grab this before anything here changes it. 2391 Frustum frustum; 2392 { 2393 F32 left, right, top, bottom, nearPlane, farPlane; 2394 bool isOrtho = false; 2395 GFX->getFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho ); 2396 2397 MatrixF cameraMat = GFX->getWorldMatrix(); 2398 cameraMat.inverse(); 2399 2400 frustum.set( isOrtho, left, right, top, bottom, nearPlane, farPlane, cameraMat ); 2401 } 2402 2403 // Render the paths 2404 renderPaths(Sim::findObject("MissionGroup")); 2405 2406 // walk selected 2407 Selection* selection = getActiveSelectionSet(); 2408 if( selection ) 2409 { 2410 bool isMultiSelection = mSelected->size() > 1; 2411 for( U32 i = 0; i < mSelected->size(); i++ ) 2412 { 2413 if ( (const SceneObject *)mHitObject == ( *mSelected )[i] ) 2414 continue; 2415 SceneObject* object = dynamic_cast< SceneObject* >( ( *mSelected )[ i ] ); 2416 if( object && mRenderSelectionBox ) 2417 renderObjectBox( object, isMultiSelection ? mObjMultiSelectColor : mObjSelectColor ); 2418 } 2419 } 2420 2421 // do the drag selection 2422 for ( U32 i = 0; i < mDragSelected->size(); i++ ) 2423 { 2424 SceneObject* object = dynamic_cast< SceneObject* >( ( *mDragSelected )[ i ] ); 2425 if( object && mRenderSelectionBox ) 2426 renderObjectBox(object, mObjSelectColor); 2427 } 2428 2429 if( selection ) 2430 { 2431 // draw the mouse over obj 2432 if ( bool(mHitObject) && mRenderSelectionBox ) 2433 { 2434 ColorI & col = selection->objInSet(mHitObject) ? mObjMouseOverSelectColor : mObjMouseOverColor; 2435 renderObjectBox(mHitObject, col); 2436 } 2437 2438 // stuff to do if there is a selection 2439 if ( selection->size() ) 2440 { 2441 if ( mRenderSelectionBox && selection->size() > 1 ) 2442 renderSelectionWorldBox(selection); 2443 2444 SceneObject* singleSelectedSceneObject = NULL; 2445 if ( selection->size() == 1 ) 2446 singleSelectedSceneObject = dynamic_cast< SceneObject* >( ( *selection )[ 0 ] ); 2447 2448 MatrixF objMat(true); 2449 if( singleSelectedSceneObject ) 2450 objMat = singleSelectedSceneObject->getTransform(); 2451 2452 Point3F worldPos = getSelectionCentroid(); 2453 2454 Point3F objScale = singleSelectedSceneObject ? singleSelectedSceneObject->getScale() : Point3F(1,1,1); 2455 2456 mGizmo->set( objMat, worldPos, objScale ); 2457 2458 // Change the gizmo's centroid highlight based on soft sticking state 2459 if( mSoftSnapIsStuck || mStuckToGround ) 2460 mGizmo->setCentroidHandleHighlight( true ); 2461 2462 mGizmo->renderGizmo( mLastCameraQuery.cameraMatrix, mLastCameraQuery.fov ); 2463 2464 // Reset any highlighting 2465 if( mSoftSnapIsStuck || mStuckToGround ) 2466 mGizmo->setCentroidHandleHighlight( false ); 2467 2468 // Soft snap box rendering 2469 if( (mSoftSnapRender || mSoftSnapRenderTriangle) && mSoftSnapActivated) 2470 { 2471 GFXDrawUtil *drawUtil = GFX->getDrawUtil(); 2472 ColorI color; 2473 2474 GFXStateBlockDesc desc; 2475 2476 if(mSoftSnapRenderTriangle && mSoftSnapIsStuck) 2477 { 2478 desc.setBlend( false ); 2479 desc.setZReadWrite( false, false ); 2480 desc.fillMode = GFXFillWireframe; 2481 desc.cullMode = GFXCullNone; 2482 2483 color.set( 255, 255, 128, 255 ); 2484 drawUtil->drawTriangle( desc, mSoftSnapTriangle.p0, mSoftSnapTriangle.p1, mSoftSnapTriangle.p2, color ); 2485 } 2486 2487 if(mSoftSnapRender) 2488 { 2489 desc.setBlend( true ); 2490 desc.blendSrc = GFXBlendOne; 2491 desc.blendDest = GFXBlendOne; 2492 desc.blendOp = GFXBlendOpAdd; 2493 desc.setZReadWrite( true, false ); 2494 desc.cullMode = GFXCullCCW; 2495 2496 color.set( 64, 64, 0, 255 ); 2497 2498 desc.fillMode = GFXFillWireframe; 2499 drawUtil->drawCube(desc, mSoftSnapPreBounds, color); 2500 2501 desc.fillMode = GFXFillSolid; 2502 drawUtil->drawSphere(desc, mSoftSnapPreBounds.len()*0.05f, mSoftSnapPreBounds.getCenter(), color); 2503 } 2504 } 2505 } 2506 } 2507 2508 // Debug rendering of the soft stick 2509 if(mSoftSnapDebugRender) 2510 { 2511 GFXDrawUtil *drawUtil = GFX->getDrawUtil(); 2512 ColorI color( 255, 0, 0, 255 ); 2513 2514 GFXStateBlockDesc desc; 2515 desc.setBlend( false ); 2516 desc.setZReadWrite( false, false ); 2517 2518 if(mSoftSnapIsStuck) 2519 { 2520 drawUtil->drawArrow( desc, getSelectionCentroid(), mSoftSnapDebugPoint, color ); 2521 2522 color.set(255, 255, 255); 2523 desc.fillMode = GFXFillWireframe; 2524 for(S32 i=0; i<mSoftSnapDebugTriangles.size(); ++i) 2525 { 2526 drawUtil->drawTriangle( desc, mSoftSnapDebugTriangles[i].p0, mSoftSnapDebugTriangles[i].p1, mSoftSnapDebugTriangles[i].p2, color ); 2527 } 2528 2529 color.set(255, 255, 0); 2530 desc.fillMode = GFXFillSolid; 2531 desc.cullMode = GFXCullNone; 2532 drawUtil->drawTriangle( desc, mSoftSnapDebugSnapTri.p0, mSoftSnapDebugSnapTri.p1, mSoftSnapDebugSnapTri.p2, color ); 2533 } 2534 2535 } 2536 2537 // Now do the 2D stuff... 2538 // icons and text 2539 GFX->setClipRect(updateRect); 2540 2541 // update what is in the selection 2542 if ( mDragSelect ) 2543 mDragSelected->clear(); 2544 2545 // Determine selected objects based on the drag box touching 2546 // a mesh if a drag operation has begun. 2547 if( mDragSelect && mDragRect.extent.x > 1 && mDragRect.extent.y > 1 ) 2548 { 2549 // Build the drag frustum based on the rect 2550 F32 wwidth; 2551 F32 wheight; 2552 F32 aspectRatio = F32(getWidth()) / F32(getHeight()); 2553 2554 if(!mLastCameraQuery.ortho) 2555 { 2556 wheight = mLastCameraQuery.nearPlane * mTan(mLastCameraQuery.fov / 2); 2557 wwidth = aspectRatio * wheight; 2558 } 2559 else 2560 { 2561 wheight = mLastCameraQuery.fov; 2562 wwidth = aspectRatio * wheight; 2563 } 2564 2565 F32 hscale = wwidth * 2 / F32(getWidth()); 2566 F32 vscale = wheight * 2 / F32(getHeight()); 2567 2568 F32 left = (mDragRect.point.x - getPosition().x) * hscale - wwidth; 2569 F32 right = (mDragRect.point.x - getPosition().x + mDragRect.extent.x) * hscale - wwidth; 2570 F32 top = wheight - vscale * (mDragRect.point.y - getPosition().y); 2571 F32 bottom = wheight - vscale * (mDragRect.point.y - getPosition().y + mDragRect.extent.y); 2572 gDragFrustum.set(mLastCameraQuery.ortho, left, right, top, bottom, mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane, mLastCameraQuery.cameraMatrix ); 2573 2574 // Create the search bounds and callback data 2575 Box3F bounds = gDragFrustum.getBounds(); 2576 SphereF sphere; 2577 sphere.center = bounds.getCenter(); 2578 sphere.radius = (bounds.maxExtents - sphere.center).len(); 2579 DragMeshCallbackData data(this, bounds, sphere); 2580 2581 // Set up the search normal and planes 2582 Point3F vec; 2583 mLastCameraQuery.cameraMatrix.getColumn(1,&vec); 2584 vec.neg(); 2585 data.mPolyList.mNormal.set(vec); 2586 const PlaneF* planes = gDragFrustum.getPlanes(); 2587 for( U32 i=0; i<Frustum::PlaneCount; ++i) 2588 { 2589 data.mPolyList.mPlaneList.push_back(planes[i]); 2590 2591 // Invert the planes as the poly list routines require a different 2592 // facing from gServerContainer.findObjects(). 2593 data.mPolyList.mPlaneList.last().invert(); 2594 } 2595 2596 // If we're in first-person view currently, disable 2597 // hitting the control object. 2598 2599 const bool isFirstPerson = GameConnection::getLocalClientConnection() ? GameConnection::getLocalClientConnection()->isFirstPerson() : false; 2600 if( isFirstPerson ) 2601 GameConnection::getLocalClientConnection()->getControlObject()->disableCollision(); 2602 2603 // Find objects in the region. 2604 2605 gServerContainer.findObjects( gDragFrustum, 0xFFFFFFFF, findDragMeshCallback, &data); 2606 for ( U32 i = 0; i < data.mObjects.size(); i++ ) 2607 { 2608 SceneObject *obj = data.mObjects[i]; 2609 2610 // Filter out unwanted objects. 2611 2612 if( objClassIgnored( obj ) 2613 || !obj->isSelectionEnabled() 2614 || ( obj->getTypeMask() & ( TerrainObjectType | ProjectileObjectType ) ) 2615 || ( obj->getTypeMask() & StaticShapeObjectType && dynamic_cast< GroundPlane* >( obj ) ) ) 2616 continue; 2617 2618 // Add the object to the drag selection. 2619 2620 mDragSelected->addObject(obj); 2621 } 2622 2623 // Re-enable collision on control object when in first-person view. 2624 2625 if( isFirstPerson ) 2626 GameConnection::getLocalClientConnection()->getControlObject()->enableCollision(); 2627 } 2628 2629 // Clear the vector of onscreen icons, will populate this below 2630 // Necessary for performing click testing efficiently 2631 mIcons.clear(); 2632 2633 // Cull Objects and perform icon rendering 2634 Vector<SceneObject*> objects; 2635 gServerContainer.findObjects( frustum, 0xFFFFFFFF, findObjectsCallback, &objects); 2636 for ( U32 i = 0; i < objects.size(); i++ ) 2637 { 2638 SceneObject *obj = objects[i]; 2639 if( objClassIgnored(obj) || !obj->isSelectionEnabled() ) 2640 continue; 2641 2642 Point3F wPos; 2643 if ( obj->isGlobalBounds() || !mObjectsUseBoxCenter ) 2644 obj->getTransform().getColumn(3, &wPos); 2645 else 2646 wPos = getBoundingBoxCenter(obj); 2647 2648 Point3F sPos; 2649 if ( project(wPos, &sPos) ) 2650 { 2651 Point2I sPosI( (S32)sPos.x,(S32)sPos.y ); 2652 if ( !updateRect.pointInRect(sPosI) ) 2653 continue; 2654 2655 // check if object needs to be added into the regions select 2656 2657 // Probably should test the entire icon screen-rect instead of just the centerpoint 2658 // but would need to move some code from renderScreenObj to here. 2659 if (mDragSelect && selection) 2660 if ( mDragRect.pointInRect(sPosI) && !selection->objInSet(obj) ) 2661 mDragSelected->addObject(obj); 2662 2663 // 2664 renderScreenObj( obj, sPos, wPos ); 2665 } 2666 } 2667 2668 //// Debug render rect around icons 2669 //for ( U32 i = 0; i < mIcons.size(); i++ ) 2670 // GFX->getDrawUtil()->drawRect( mIcons[i].rect, ColorI(255,255,255,255) ); 2671 2672 if ( mShowMousePopupInfo && mMouseDown ) 2673 renderMousePopupInfo(); 2674 2675 if ( mDragSelect && mDragRect.extent.x > 1 && mDragRect.extent.y > 1 ) 2676 GFX->getDrawUtil()->drawRect( mDragRect, mDragRectColor ); 2677 2678 if ( selection && selection->size() ) 2679 mGizmo->renderText( mSaveViewport, mSaveModelview, mSaveProjection ); 2680} 2681 2682//------------------------------------------------------------------------------ 2683// Console stuff 2684 2685void WorldEditor::initPersistFields() 2686{ 2687 addGroup( "Grid" ); 2688 2689 addField( "gridSnap", TypeBool, Offset( mGridSnap, WorldEditor ), 2690 "If true, transform operations will snap to the grid." ); 2691 2692 endGroup( "Grid" ); 2693 2694 addGroup( "Dropping" ); 2695 2696 addField( "dropAtBounds", TypeBool, Offset(mDropAtBounds, WorldEditor) ); 2697 addField( "dropBelowCameraOffset", TypeF32, Offset(mDropBelowCameraOffset, WorldEditor) ); 2698 addField( "dropAtScreenCenterScalar",TypeF32, Offset(mDropAtScreenCenterScalar, WorldEditor) ); 2699 addField( "dropAtScreenCenterMax", TypeF32, Offset(mDropAtScreenCenterMax, WorldEditor) ); 2700 addField( "dropType", TYPEID< DropType >(), Offset(mDropType, WorldEditor) ); 2701 2702 endGroup( "Dropping" ); 2703 2704 addGroup( "Colors" ); 2705 2706 addField( "popupBackgroundColor", TypeColorI, Offset(mPopupBackgroundColor, WorldEditor) ); 2707 addField( "popupTextColor", TypeColorI, Offset(mPopupTextColor, WorldEditor) ); 2708 addField( "objectTextColor", TypeColorI, Offset(mObjectTextColor, WorldEditor) ); 2709 addField( "selectionBoxColor", TypeColorI, Offset(mSelectionBoxColor, WorldEditor) ); 2710 addField( "objSelectColor", TypeColorI, Offset(mObjSelectColor, WorldEditor) ); 2711 addField( "objMouseOverSelectColor",TypeColorI, Offset(mObjMouseOverSelectColor, WorldEditor) ); 2712 addField( "objMouseOverColor", TypeColorI, Offset(mObjMouseOverColor, WorldEditor) ); 2713 addField( "dragRectColor", TypeColorI, Offset(mDragRectColor, WorldEditor) ); 2714 addField( "faceSelectColor", TypeColorI, Offset(mFaceSelectColor, WorldEditor) ); 2715 2716 endGroup( "Colors" ); 2717 2718 addGroup( "Selections" ); 2719 2720 addField( "boundingBoxCollision", TypeBool, Offset(mBoundingBoxCollision, WorldEditor) ); 2721 addField( "objectMeshCollision", TypeBool, Offset(mObjectMeshCollision, WorldEditor) ); 2722 addField( "selectionLocked", TypeBool, Offset(mSelectionLocked, WorldEditor) ); 2723 addProtectedField( "objectsUseBoxCenter", TypeBool, Offset(mObjectsUseBoxCenter, WorldEditor), &setObjectsUseBoxCenter, &defaultProtectedGetFn, "" ); 2724 2725 endGroup( "Selections" ); 2726 2727 addGroup( "Rendering" ); 2728 2729 addField( "objTextFormat", TypeString, Offset(mObjTextFormat, WorldEditor) ); 2730 addField( "renderPopupBackground", TypeBool, Offset(mRenderPopupBackground, WorldEditor) ); 2731 addField( "showMousePopupInfo", TypeBool, Offset(mShowMousePopupInfo, WorldEditor) ); 2732 addField( "renderObjText", TypeBool, Offset(mRenderObjText, WorldEditor) ); 2733 addField( "renderObjHandle", TypeBool, Offset(mRenderObjHandle, WorldEditor) ); 2734 addField( "renderSelectionBox", TypeBool, Offset(mRenderSelectionBox, WorldEditor) ); 2735 addField( "selectHandle", TypeFilename, Offset(mSelectHandle, WorldEditor) ); 2736 addField( "defaultHandle", TypeFilename, Offset(mDefaultHandle, WorldEditor) ); 2737 addField( "lockedHandle", TypeFilename, Offset(mLockedHandle, WorldEditor) ); 2738 2739 endGroup( "Rendering" ); 2740 2741 addGroup( "Rendering: Icons" ); 2742 2743 addField( "fadeIcons", TypeBool, Offset( mFadeIcons, WorldEditor ), 2744 "Whether object icons should fade out with distance to camera pos." ); 2745 addField( "fadeIconsDist", TypeF32, Offset( mFadeIconsDist, WorldEditor ), 2746 "Distance from camera pos at which to start fading out icons." ); 2747 2748 endGroup( "Rendering: Icons" ); 2749 2750 addGroup( "Misc" ); 2751 2752 addField( "isDirty", TypeBool, Offset(mIsDirty, WorldEditor) ); 2753 addField( "stickToGround", TypeBool, Offset(mStickToGround, WorldEditor) ); 2754 //addField("sameScaleAllAxis", TypeBool, Offset(mSameScaleAllAxis, WorldEditor)); 2755 addField( "toggleIgnoreList", TypeBool, Offset(mToggleIgnoreList, WorldEditor) ); 2756 2757 endGroup( "Misc" ); 2758 2759 Parent::initPersistFields(); 2760} 2761 2762//------------------------------------------------------------------------------ 2763// These methods are needed for the console interfaces. 2764 2765void WorldEditor::ignoreObjClass( U32 argc, ConsoleValueRef *argv ) 2766{ 2767 for(S32 i = 2; i < argc; i++) 2768 { 2769 ClassInfo::Entry * entry = getClassEntry(argv[i]); 2770 if(entry) 2771 entry->mIgnoreCollision = true; 2772 else 2773 { 2774 entry = new ClassInfo::Entry; 2775 entry->mName = StringTable->insert(argv[i]); 2776 entry->mIgnoreCollision = true; 2777 if(!addClassEntry(entry)) 2778 delete entry; 2779 } 2780 } 2781} 2782 2783void WorldEditor::clearIgnoreList() 2784{ 2785 for(U32 i = 0; i < mClassInfo.mEntries.size(); i++) 2786 mClassInfo.mEntries[i]->mIgnoreCollision = false; 2787} 2788 2789void WorldEditor::setObjectsUseBoxCenter(bool state) 2790{ 2791 mObjectsUseBoxCenter = state; 2792 if( getActiveSelectionSet() && isMethod( "onSelectionCentroidChanged" ) ) 2793 Con::executef( this, "onSelectionCentroidChanged" ); 2794} 2795 2796void WorldEditor::clearSelection() 2797{ 2798 if( mSelectionLocked || !mSelected ) 2799 return; 2800 2801 // Call onUnSelect. Because of the whole treeview<->selection synchronization, 2802 // this may actually cause things to disappear from mSelected so do the loop 2803 // in reverse. This will make the loop work even if items are removed as 2804 // we go along. 2805 for( S32 i = mSelected->size() - 1; i >= 0; -- i ) 2806 Con::executef( this, "onUnSelect", ( *mSelected )[ i ]->getIdString() ); 2807 2808 Con::executef(this, "onClearSelection"); 2809 mSelected->clear(); 2810} 2811 2812void WorldEditor::selectObject( SimObject *obj ) 2813{ 2814 if ( mSelectionLocked || !mSelected || !obj ) 2815 return; 2816 2817 // Don't check isSelectionEnabled of SceneObjects here as we 2818 // want to still allow manual selection in treeviews. 2819 2820 if ( !objClassIgnored( obj ) && !mSelected->objInSet( obj ) ) 2821 { 2822 mSelected->addObject( obj ); 2823 Con::executef( this, "onSelect", obj->getIdString() ); 2824 } 2825} 2826 2827void WorldEditor::selectObject( const char* obj ) 2828{ 2829 SimObject *select; 2830 2831 if ( Sim::findObject( obj, select ) ) 2832 selectObject( select ); 2833} 2834 2835void WorldEditor::unselectObject( SimObject *obj ) 2836{ 2837 if ( mSelectionLocked || !mSelected || !obj ) 2838 return; 2839 2840 if ( !objClassIgnored( obj ) && mSelected->objInSet( obj ) ) 2841 { 2842 mSelected->removeObject( obj ); 2843 Con::executef( this, "onUnSelect", obj->getIdString() ); 2844 } 2845} 2846 2847void WorldEditor::unselectObject( const char *obj ) 2848{ 2849 SimObject *select; 2850 2851 if ( Sim::findObject( obj, select ) ) 2852 unselectObject( select ); 2853} 2854 2855S32 WorldEditor::getSelectionSize() 2856{ 2857 if( !mSelected ) 2858 return 0; 2859 2860 return mSelected->size(); 2861} 2862 2863S32 WorldEditor::getSelectObject(S32 index) 2864{ 2865 AssertFatal( mSelected != NULL, "WorldEditor::getSelectedObject - no active selection set!" ); 2866 2867 // Return the object's id 2868 return ( *mSelected )[index]->getId(); 2869} 2870 2871const Point3F& WorldEditor::getSelectionCentroid() 2872{ 2873 if( !mSelected ) 2874 return Point3F::Zero; 2875 2876 if( mSelected->containsGlobalBounds() ) 2877 { 2878 return mSelected->getCentroid(); 2879 } 2880 2881 return mObjectsUseBoxCenter ? mSelected->getBoxCentroid() : mSelected->getCentroid(); 2882} 2883 2884const Box3F& WorldEditor::getSelectionBounds() 2885{ 2886 return mSelected->getBoxBounds(); 2887} 2888 2889Point3F WorldEditor::getSelectionExtent() 2890{ 2891 const Box3F& box = getSelectionBounds(); 2892 return box.getExtents(); 2893} 2894 2895F32 WorldEditor::getSelectionRadius() 2896{ 2897 const Box3F box = getSelectionBounds(); 2898 return box.len() * 0.5f; 2899} 2900 2901void WorldEditor::dropCurrentSelection( bool skipUndo ) 2902{ 2903 if ( !bool(mSelected) || !mSelected->size() ) 2904 return; 2905 2906 if ( !skipUndo ) 2907 submitUndo( mSelected ); 2908 2909 dropSelection( mSelected ); 2910 2911 if ( mSelected->hasCentroidChanged() ) 2912 Con::executef( this, "onSelectionCentroidChanged" ); 2913} 2914 2915void WorldEditor::redirectConsole( S32 objID ) 2916{ 2917 mRedirectID = objID; 2918} 2919 2920//------------------------------------------------------------------------------ 2921 2922bool WorldEditor::alignByBounds( S32 boundsAxis ) 2923{ 2924 if(boundsAxis < 0 || boundsAxis > 5) 2925 return false; 2926 2927 if(mSelected->size() < 2) 2928 return true; 2929 2930 S32 axis = boundsAxis >= 3 ? boundsAxis-3 : boundsAxis; 2931 bool useMax = boundsAxis >= 3 ? false : true; 2932 2933 // Find out which selected object has its bounds the farthest out 2934 F32 pos; 2935 S32 baseObj = 0; 2936 if(useMax) 2937 pos = TypeTraits< F32 >::MIN; 2938 else 2939 pos = TypeTraits< F32 >::MAX; 2940 2941 for(S32 i=1; i<mSelected->size(); ++i) 2942 { 2943 SceneObject* object = dynamic_cast< SceneObject* >( ( *mSelected )[ i ] ); 2944 if( !object ) 2945 continue; 2946 2947 const Box3F& bounds = object->getWorldBox(); 2948 2949 if(useMax) 2950 { 2951 if(bounds.maxExtents[axis] > pos) 2952 { 2953 pos = bounds.maxExtents[axis]; 2954 baseObj = i; 2955 } 2956 } 2957 else 2958 { 2959 if(bounds.minExtents[axis] < pos) 2960 { 2961 pos = bounds.minExtents[axis]; 2962 baseObj = i; 2963 } 2964 } 2965 } 2966 2967 submitUndo( mSelected, "Align By Bounds" ); 2968 2969 // Move all selected objects to align with the calculated bounds 2970 for(S32 i=0; i<mSelected->size(); ++i) 2971 { 2972 if(i == baseObj) 2973 continue; 2974 2975 SceneObject* object = dynamic_cast< SceneObject* >( ( *mSelected )[ i ] ); 2976 if( !object ) 2977 continue; 2978 2979 const Box3F& bounds = object->getWorldBox(); 2980 F32 delta; 2981 if(useMax) 2982 delta = pos - bounds.maxExtents[axis]; 2983 else 2984 delta = pos - bounds.minExtents[axis]; 2985 2986 Point3F objPos = object->getPosition(); 2987 objPos[axis] += delta; 2988 object->setPosition(objPos); 2989 } 2990 2991 return true; 2992} 2993 2994bool WorldEditor::alignByAxis( S32 axis ) 2995{ 2996 if(axis < 0 || axis > 2) 2997 return false; 2998 2999 if(mSelected->size() < 2) 3000 return true; 3001 3002 SceneObject* object = dynamic_cast< SceneObject* >( ( *mSelected )[ 0 ] ); 3003 if( !object ) 3004 return false; 3005 3006 submitUndo( mSelected, "Align By Axis" ); 3007 3008 // All objects will be repositioned to line up with the 3009 // first selected object 3010 Point3F pos = object->getPosition(); 3011 3012 for(S32 i=0; i<mSelected->size(); ++i) 3013 { 3014 SceneObject* object = dynamic_cast< SceneObject* >( ( *mSelected )[ i ] ); 3015 if( !object ) 3016 continue; 3017 3018 Point3F objPos = object->getPosition(); 3019 objPos[axis] = pos[axis]; 3020 object->setPosition(objPos); 3021 } 3022 3023 return true; 3024} 3025 3026//------------------------------------------------------------------------------ 3027 3028void WorldEditor::transformSelection(bool position, Point3F& p, bool relativePos, bool rotate, EulerF& r, bool relativeRot, bool rotLocal, S32 scaleType, Point3F& s, bool sRelative, bool sLocal) 3029{ 3030 if(mSelected->size() == 0) 3031 return; 3032 3033 submitUndo( mSelected, "Transform Selection" ); 3034 3035 if( position ) 3036 { 3037 if( relativePos ) 3038 { 3039 mSelected->offset( p, mGridSnap ? mGridPlaneSize : 0.f ); 3040 } 3041 else 3042 { 3043 mSelected->setCentroidPosition(mObjectsUseBoxCenter, p); 3044 } 3045 } 3046 3047 if( rotate ) 3048 { 3049 Point3F centroid; 3050 if( mSelected->containsGlobalBounds() ) 3051 { 3052 centroid = mSelected->getCentroid(); 3053 } 3054 else 3055 { 3056 centroid = mObjectsUseBoxCenter ? mSelected->getBoxCentroid() : mSelected->getCentroid(); 3057 } 3058 3059 if( relativeRot ) 3060 { 3061 if( rotLocal ) 3062 { 3063 mSelected->rotate(r); 3064 } 3065 else 3066 { 3067 mSelected->rotate(r, centroid); 3068 } 3069 } 3070 else if( rotLocal ) 3071 { 3072 // Can only do absolute rotation for multiple objects about 3073 // object center 3074 mSelected->setRotate(r); 3075 } 3076 } 3077 3078 if( scaleType == 1 ) 3079 { 3080 // Scale 3081 3082 Point3F centroid; 3083 if( mSelected->containsGlobalBounds() ) 3084 { 3085 centroid = mSelected->getCentroid(); 3086 } 3087 else 3088 { 3089 centroid = mObjectsUseBoxCenter ? mSelected->getBoxCentroid() : mSelected->getCentroid(); 3090 } 3091 3092 if( sRelative ) 3093 { 3094 if( sLocal ) 3095 { 3096 mSelected->scale(s); 3097 } 3098 else 3099 { 3100 mSelected->scale(s, centroid); 3101 } 3102 } 3103 else 3104 { 3105 if( sLocal ) 3106 { 3107 mSelected->setScale(s); 3108 } 3109 else 3110 { 3111 mSelected->setScale(s, centroid); 3112 } 3113 } 3114 } 3115 else if( scaleType == 2 ) 3116 { 3117 // Size 3118 3119 if( mSelected->containsGlobalBounds() ) 3120 return; 3121 3122 if( sRelative ) 3123 { 3124 // Size is always local/object based 3125 mSelected->addSize(s); 3126 } 3127 else 3128 { 3129 // Size is always local/object based 3130 mSelected->setSize(s); 3131 } 3132 } 3133 3134 updateClientTransforms(mSelected); 3135 3136 if(mSelected->hasCentroidChanged()) 3137 { 3138 Con::executef( this, "onSelectionCentroidChanged"); 3139 } 3140 3141 if ( isMethod("onEndDrag") ) 3142 { 3143 SimObject * obj = 0; 3144 if ( mRedirectID ) 3145 obj = Sim::findObject( mRedirectID ); 3146 Con::executef( obj ? obj : this, "onEndDrag", ( *mSelected )[ 0 ]->getIdString() ); 3147 } 3148} 3149 3150//------------------------------------------------------------------------------ 3151 3152void WorldEditor::resetSelectedRotation() 3153{ 3154 for(S32 i=0; i<mSelected->size(); ++i) 3155 { 3156 SceneObject* object = dynamic_cast< SceneObject* >( ( *mSelected )[ i ] ); 3157 if( !object ) 3158 continue; 3159 3160 MatrixF mat(true); 3161 mat.setPosition(object->getPosition()); 3162 object->setTransform(mat); 3163 } 3164} 3165 3166void WorldEditor::resetSelectedScale() 3167{ 3168 for(S32 i=0; i<mSelected->size(); ++i) 3169 { 3170 SceneObject* object = dynamic_cast< SceneObject* >( ( *mSelected )[ i ] ); 3171 if( object ) 3172 object->setScale(Point3F(1,1,1)); 3173 } 3174} 3175 3176//------------------------------------------------------------------------------ 3177 3178ConsoleMethod( WorldEditor, ignoreObjClass, void, 3, 0, "(string class_name, ...)") 3179{ 3180 object->ignoreObjClass(argc, argv); 3181} 3182 3183DefineEngineMethod( WorldEditor, clearIgnoreList, void, (),, 3184 "Clear the ignore class list.\n") 3185{ 3186 object->clearIgnoreList(); 3187} 3188 3189DefineEngineMethod( WorldEditor, clearSelection, void, (),, 3190 "Clear the selection.\n") 3191{ 3192 object->clearSelection(); 3193} 3194 3195DefineEngineMethod( WorldEditor, getActiveSelection, S32, (),, 3196 "Return the currently active WorldEditorSelection object.\n" 3197 "@return currently active WorldEditorSelection object or 0 if no selection set is available.") 3198{ 3199 if( !object->getActiveSelectionSet() ) 3200 return 0; 3201 3202 return object->getActiveSelectionSet()->getId(); 3203} 3204 3205DefineConsoleMethod( WorldEditor, setActiveSelection, void, ( WorldEditorSelection* selection), , 3206 "Set the currently active WorldEditorSelection object.\n" 3207 "@param selection A WorldEditorSelectionSet object to use for the selection container.") 3208{ 3209 if (selection) 3210 object->makeActiveSelectionSet( selection ); 3211} 3212 3213DefineEngineMethod( WorldEditor, selectObject, void, (SimObject* obj),, 3214 "Selects a single object." 3215 "@param obj Object to select.") 3216{ 3217 object->selectObject(obj); 3218} 3219 3220DefineEngineMethod( WorldEditor, unselectObject, void, (SimObject* obj),, 3221 "Unselects a single object." 3222 "@param obj Object to unselect.") 3223{ 3224 object->unselectObject(obj); 3225} 3226 3227DefineEngineMethod( WorldEditor, invalidateSelectionCentroid, void, (),, 3228 "Invalidate the selection sets centroid.") 3229{ 3230 WorldEditor::Selection* sel = object->getActiveSelectionSet(); 3231 if(sel) 3232 sel->invalidateCentroid(); 3233} 3234 3235DefineEngineMethod( WorldEditor, getSelectionSize, S32, (),, 3236 "Return the number of objects currently selected in the editor." 3237 "@return number of objects currently selected in the editor.") 3238{ 3239 return object->getSelectionSize(); 3240} 3241 3242DefineEngineMethod( WorldEditor, getSelectedObject, S32, (S32 index),, 3243 "Return the selected object and the given index." 3244 "@param index Index of selected object to get." 3245 "@return selected object at given index or -1 if index is incorrect.") 3246{ 3247 if(index < 0 || index >= object->getSelectionSize()) 3248 { 3249 Con::errorf(ConsoleLogEntry::General, "WorldEditor::getSelectedObject: invalid object index"); 3250 return(-1); 3251 } 3252 3253 return(object->getSelectObject(index)); 3254} 3255 3256DefineEngineMethod( WorldEditor, getSelectionRadius, F32, (),, 3257 "Get the radius of the current selection." 3258 "@return radius of the current selection.") 3259{ 3260 return object->getSelectionRadius(); 3261} 3262 3263DefineEngineMethod( WorldEditor, getSelectionCentroid, Point3F, (),, 3264 "Get centroid of the selection." 3265 "@return centroid of the selection.") 3266{ 3267 return object->getSelectionCentroid(); 3268} 3269 3270DefineEngineMethod( WorldEditor, getSelectionExtent, Point3F, (),, 3271 "Get extent of the selection." 3272 "@return extent of the selection.") 3273{ 3274 return object->getSelectionExtent(); 3275} 3276 3277DefineEngineMethod( WorldEditor, dropSelection, void, (bool skipUndo), (false), 3278 "Drop the current selection." 3279 "@param skipUndo True to skip creating undo's for this action, false to create an undo.") 3280{ 3281 3282 object->dropCurrentSelection( skipUndo ); 3283} 3284 3285void WorldEditor::cutCurrentSelection() 3286{ 3287 cutSelection(mSelected); 3288} 3289 3290void WorldEditor::copyCurrentSelection() 3291{ 3292 copySelection(mSelected); 3293} 3294 3295DefineEngineMethod( WorldEditor, cutSelection, void, (), , 3296 "Cut the current selection to be pasted later.") 3297{ 3298 object->cutCurrentSelection(); 3299} 3300 3301DefineEngineMethod( WorldEditor, copySelection, void, (), , 3302 "Copy the current selection to be pasted later.") 3303{ 3304 object->copyCurrentSelection(); 3305} 3306 3307DefineEngineMethod( WorldEditor, pasteSelection, void, (), , 3308 "Paste the current selection.") 3309{ 3310 object->pasteSelection(); 3311} 3312 3313bool WorldEditor::canPasteSelection() 3314{ 3315 return mCopyBuffer.empty() != true; 3316} 3317 3318DefineEngineMethod( WorldEditor, canPasteSelection, bool, (), , 3319 "Check if we can paste the current selection." 3320 "@return True if we can paste the current selection, false if not.") 3321{ 3322 return object->canPasteSelection(); 3323} 3324 3325DefineEngineMethod( WorldEditor, hideObject, void, (SceneObject* obj, bool hide), , 3326 "Hide/show the given object." 3327 "@param obj Object to hide/show." 3328 "@param hide True to hide the object, false to show it.") 3329{ 3330 3331 if (obj) 3332 object->hideObject(obj, hide); 3333} 3334 3335DefineEngineMethod( WorldEditor, hideSelection, void, (bool hide), , 3336 "Hide/show the selection." 3337 "@param hide True to hide the selection, false to show it.") 3338{ 3339 object->hideSelection(hide); 3340} 3341 3342DefineEngineMethod( WorldEditor, lockSelection, void, (bool lock), , 3343 "Lock/unlock the selection." 3344 "@param lock True to lock the selection, false to unlock it.") 3345{ 3346 object->lockSelection(lock); 3347} 3348 3349//TODO: Put in the param possible options and what they mean 3350DefineEngineMethod( WorldEditor, alignByBounds, void, (S32 boundsAxis), , 3351 "Align all selected objects against the given bounds axis." 3352 "@param boundsAxis Bounds axis to align all selected objects against.") 3353{ 3354 if(!object->alignByBounds(boundsAxis)) 3355 Con::warnf(ConsoleLogEntry::General, avar("worldEditor.alignByBounds: invalid bounds axis '%s'", boundsAxis)); 3356} 3357 3358//TODO: Put in the param possible options and what they mean (assuming x,y,z) 3359DefineEngineMethod( WorldEditor, alignByAxis, void, (S32 axis), , 3360 "Align all selected objects along the given axis." 3361 "@param axis Axis to align all selected objects along.") 3362{ 3363 if(!object->alignByAxis(axis)) 3364 Con::warnf(ConsoleLogEntry::General, avar("worldEditor.alignByAxis: invalid axis '%s'", axis)); 3365} 3366 3367DefineEngineMethod( WorldEditor, resetSelectedRotation, void, (), , 3368 "Reset the rotation of the selection.") 3369{ 3370 object->resetSelectedRotation(); 3371} 3372 3373DefineEngineMethod( WorldEditor, resetSelectedScale, void, (), , 3374 "Reset the scale of the selection.") 3375{ 3376 object->resetSelectedScale(); 3377} 3378 3379//TODO: Better documentation on exactly what this does. 3380DefineEngineMethod( WorldEditor, redirectConsole, void, (S32 objID), , 3381 "Redirect console." 3382 "@param objID Object id.") 3383{ 3384 object->redirectConsole(objID); 3385} 3386 3387DefineEngineMethod( WorldEditor, addUndoState, void, (), , 3388 "Adds/Submits an undo state to the undo manager.") 3389{ 3390 object->addUndoState(); 3391} 3392 3393//----------------------------------------------------------------------------- 3394 3395DefineEngineMethod( WorldEditor, getSoftSnap, bool, (), , 3396 "Is soft snapping always on?" 3397 "@return True if soft snap is on, false if not.") 3398{ 3399 return object->mSoftSnap; 3400} 3401 3402DefineEngineMethod( WorldEditor, setSoftSnap, void, (bool softSnap), , 3403 "Allow soft snapping all of the time." 3404 "@param softSnap True to turn soft snap on, false to turn it off.") 3405{ 3406 object->mSoftSnap = softSnap; 3407} 3408 3409DefineEngineMethod( WorldEditor, getSoftSnapSize, F32, (), , 3410 "Get the absolute size to trigger a soft snap." 3411 "@return absolute size to trigger a soft snap.") 3412{ 3413 return object->mSoftSnapSize; 3414} 3415 3416DefineEngineMethod( WorldEditor, setSoftSnapSize, void, (F32 size), , 3417 "Set the absolute size to trigger a soft snap." 3418 "@param size Absolute size to trigger a soft snap.") 3419{ 3420 object->mSoftSnapSize = size; 3421} 3422 3423DefineEngineMethod( WorldEditor, getSoftSnapAlignment, WorldEditor::AlignmentType, (),, 3424 "Get the soft snap alignment." 3425 "@return soft snap alignment.") 3426{ 3427 return object->mSoftSnapAlignment; 3428} 3429 3430DefineEngineMethod( WorldEditor, setSoftSnapAlignment, void, ( WorldEditor::AlignmentType type ),, 3431 "Set the soft snap alignment." 3432 "@param type Soft snap alignment type.") 3433{ 3434 object->mSoftSnapAlignment = type; 3435} 3436 3437DefineEngineMethod( WorldEditor, softSnapSizeByBounds, void, (bool useBounds), , 3438 "Use selection bounds size as soft snap bounds." 3439 "@param useBounds True to use selection bounds size as soft snap bounds, false to not.") 3440{ 3441 object->mSoftSnapSizeByBounds = useBounds; 3442} 3443 3444DefineEngineMethod( WorldEditor, getSoftSnapBackfaceTolerance, F32, (),, 3445 "Get the fraction of the soft snap radius that backfaces may be included." 3446 "@return fraction of the soft snap radius that backfaces may be included.") 3447{ 3448 return object->mSoftSnapBackfaceTolerance; 3449} 3450 3451DefineEngineMethod( WorldEditor, setSoftSnapBackfaceTolerance, void, (F32 tolerance),, 3452 "Set the fraction of the soft snap radius that backfaces may be included." 3453 "@param tolerance Fraction of the soft snap radius that backfaces may be included (range of 0..1).") 3454{ 3455 object->mSoftSnapBackfaceTolerance = tolerance; 3456} 3457 3458DefineEngineMethod( WorldEditor, softSnapRender, void, (F32 render),, 3459 "Render the soft snapping bounds." 3460 "@param render True to render the soft snapping bounds, false to not.") 3461{ 3462 object->mSoftSnapRender = render; 3463} 3464 3465DefineEngineMethod( WorldEditor, softSnapRenderTriangle, void, (F32 renderTriangle),, 3466 "Render the soft snapped triangle." 3467 "@param renderTriangle True to render the soft snapped triangle, false to not.") 3468{ 3469 object->mSoftSnapRenderTriangle = renderTriangle; 3470} 3471 3472DefineEngineMethod( WorldEditor, softSnapDebugRender, void, (F32 debugRender),, 3473 "Toggle soft snapping debug rendering." 3474 "@param debugRender True to turn on soft snapping debug rendering, false to turn it off.") 3475{ 3476 object->mSoftSnapDebugRender = debugRender; 3477} 3478 3479DefineEngineMethod( WorldEditor, getTerrainSnapAlignment, WorldEditor::AlignmentType, (),, 3480 "Get the terrain snap alignment." 3481 "@return terrain snap alignment type.") 3482{ 3483 return object->mTerrainSnapAlignment; 3484} 3485 3486DefineEngineMethod( WorldEditor, setTerrainSnapAlignment, void, ( WorldEditor::AlignmentType alignment ),, 3487 "Set the terrain snap alignment." 3488 "@param alignment New terrain snap alignment type.") 3489{ 3490 object->mTerrainSnapAlignment = alignment; 3491} 3492 3493DefineEngineMethod( WorldEditor, transformSelection, void, 3494 ( bool position, 3495 Point3F point, 3496 bool relativePos, 3497 bool rotate, 3498 Point3F rotation, 3499 bool relativeRot, 3500 bool rotLocal, 3501 S32 scaleType, 3502 Point3F scale, 3503 bool sRelative, 3504 bool sLocal ), , 3505 "Transform selection by given parameters." 3506 "@param position True to transform the selection's position." 3507 "@param point Position to transform by." 3508 "@param relativePos True to use relative position." 3509 "@param rotate True to transform the selection's rotation." 3510 "@param rotation Rotation to transform by." 3511 "@param relativeRot True to use the relative rotation." 3512 "@param rotLocal True to use the local rotation." 3513 "@param scaleType Scale type to use." 3514 "@param scale Scale to transform by." 3515 "@param sRelative True to use a relative scale." 3516 "@param sLocal True to use a local scale.") 3517{ 3518 object->transformSelection(position, point, relativePos, rotate, rotation, relativeRot, rotLocal, scaleType, scale, sRelative, sLocal); 3519} 3520 3521#include "core/strings/stringUnit.h" 3522#include "collision/optimizedPolyList.h" 3523#include "core/volume.h" 3524#ifdef TORQUE_COLLADA 3525 #include "ts/collada/colladaUtils.h" 3526#endif 3527 3528void WorldEditor::colladaExportSelection( const String &path ) 3529{ 3530#ifdef TORQUE_COLLADA 3531 3532 Vector< SceneObject*> objectList; 3533 3534 for ( S32 i = 0; i < mSelected->size(); i++ ) 3535 { 3536 SceneObject *pObj = dynamic_cast< SceneObject* >( ( *mSelected )[i] ); 3537 if ( pObj ) 3538 objectList.push_back( pObj ); 3539 } 3540 3541 if ( objectList.empty() ) 3542 return; 3543 3544 Point3F centroid; 3545 MatrixF orientation; 3546 3547 if ( objectList.size() == 1 ) 3548 { 3549 orientation = objectList[0]->getTransform(); 3550 centroid = objectList[0]->getPosition(); 3551 } 3552 else 3553 { 3554 orientation.identity(); 3555 centroid.zero(); 3556 3557 S32 count = 0; 3558 3559 for ( S32 i = 0; i < objectList.size(); i++ ) 3560 { 3561 SceneObject *pObj = objectList[i]; 3562 if ( pObj->isGlobalBounds() ) 3563 continue; 3564 3565 centroid += pObj->getPosition(); 3566 count++; 3567 } 3568 3569 centroid /= count; 3570 } 3571 3572 orientation.setPosition( centroid ); 3573 orientation.inverse(); 3574 3575 OptimizedPolyList polyList; 3576 polyList.setBaseTransform( orientation ); 3577 3578 for ( S32 i = 0; i < objectList.size(); i++ ) 3579 { 3580 SceneObject *pObj = objectList[i]; 3581 if ( !pObj->buildPolyList( PLC_Export, &polyList, pObj->getWorldBox(), pObj->getWorldSphere() ) ) 3582 Con::warnf( "colladaExportObjectList() - object %i returned no geometry.", pObj->getId() ); 3583 } 3584 3585 // Use a ColladaUtils function to do the actual export to a Collada file 3586 ColladaUtils::exportToCollada( path, polyList ); 3587 3588#endif 3589} 3590 3591DefineEngineMethod( WorldEditor, colladaExportSelection, void, ( const char* path ),, 3592 "Export the combined geometry of all selected objects to the specified path in collada format." 3593 "@param path Path to export collada format to.") 3594{ 3595 object->colladaExportSelection( path ); 3596} 3597 3598void WorldEditor::makeSelectionPrefab( const char *filename ) 3599{ 3600 if ( mSelected->size() == 0 ) 3601 { 3602 Con::errorf( "WorldEditor::makeSelectionPrefab - Nothing selected." ); 3603 return; 3604 } 3605 3606 SimGroup *missionGroup; 3607 if ( !Sim::findObject( "MissionGroup", missionGroup ) ) 3608 { 3609 Con::errorf( "WorldEditor::makeSelectionPrefab - Could not find MissionGroup." ); 3610 return; 3611 } 3612 3613 Vector< SimObject*> stack; 3614 Vector< SimObject*> found; 3615 3616 for ( S32 i = 0; i < mSelected->size(); i++ ) 3617 { 3618 SimObject *obj = ( *mSelected )[i]; 3619 stack.push_back( obj ); 3620 } 3621 3622 Vector< SimGroup*> cleanup; 3623 3624 while ( !stack.empty() ) 3625 { 3626 SimObject *obj = stack.last(); 3627 SimGroup *grp = dynamic_cast< SimGroup* >( obj ); 3628 3629 stack.pop_back(); 3630 3631 if ( grp ) 3632 { 3633 for ( S32 i = 0; i < grp->size(); i++ ) 3634 stack.push_back( grp->at(i) ); 3635 3636 SceneObject* scn = dynamic_cast< SceneObject* >(grp); 3637 if (scn) 3638 { 3639 if (Prefab::isValidChild(obj, true)) 3640 found.push_back(obj); 3641 } 3642 else 3643 { 3644 //Only push the cleanup of the group if it's ONLY a SimGroup. 3645 cleanup.push_back(grp); 3646 } 3647 } 3648 else 3649 { 3650 if ( Prefab::isValidChild( obj, true ) ) 3651 found.push_back( obj ); 3652 } 3653 } 3654 3655 if ( found.empty() ) 3656 { 3657 Con::warnf( "WorldEditor::makeSelectionPrefab - No valid objects selected." ); 3658 return; 3659 } 3660 3661 // SimGroup we collect prefab objects into. 3662 SimGroup *group = new SimGroup(); 3663 group->registerObject(); 3664 3665 // Transform from World to Prefab space. 3666 MatrixF fabMat(true); 3667 fabMat.setPosition( mSelected->getCentroid() ); 3668 fabMat.inverse(); 3669 3670 MatrixF objMat; 3671 SimObject *obj = NULL; 3672 SceneObject *sObj = NULL; 3673 3674 for ( S32 i = 0; i < found.size(); i++ ) 3675 { 3676 obj = found[i]; 3677 sObj = dynamic_cast< SceneObject* >( obj ); 3678 3679 obj->assignName( "" ); 3680 3681 if ( sObj ) 3682 { 3683 objMat.mul( fabMat, sObj->getTransform() ); 3684 sObj->setTransform( objMat ); 3685 } 3686 3687 group->addObject( obj ); 3688 } 3689 3690 // Save out .prefab file. 3691 group->save( filename, false, "$ThisPrefab = " ); 3692 3693 // Allocate Prefab object and add to level. 3694 Prefab *fab = new Prefab(); 3695 fab->setFile( filename ); 3696 fabMat.inverse(); 3697 fab->setTransform( fabMat ); 3698 fab->registerObject(); 3699 missionGroup->addObject( fab ); 3700 3701 // Select it, mark level as dirty. 3702 clearSelection(); 3703 selectObject( fab ); 3704 setDirty(); 3705 3706 // Delete original objects and temporary SimGroup. 3707 group->deleteObject(); 3708 for ( S32 i = 0; i < cleanup.size(); i++ ) 3709 cleanup[i]->deleteObject(); 3710} 3711 3712void WorldEditor::explodeSelectedPrefab() 3713{ 3714 Vector<Prefab*> prefabList; 3715 3716 for ( S32 i = 0; i < mSelected->size(); i++ ) 3717 { 3718 Prefab *obj = dynamic_cast<Prefab*>( ( *mSelected )[i] ); 3719 if ( obj ) 3720 prefabList.push_back(obj); 3721 } 3722 3723 if ( prefabList.empty() ) 3724 return; 3725 3726 UndoManager *undoMan = NULL; 3727 if ( !Sim::findObject( "EUndoManager", undoMan ) ) 3728 { 3729 Con::errorf( "WorldEditor::createUndo() - EUndoManager not found!" ); 3730 return; 3731 } 3732 3733 CompoundUndoAction *action = new CompoundUndoAction("Explode Prefab"); 3734 3735 clearSelection(); 3736 3737 for ( S32 i = 0; i < prefabList.size(); i++ ) 3738 { 3739 Prefab *prefab = prefabList[i]; 3740 3741 ExplodePrefabUndoAction *explodeAction = new ExplodePrefabUndoAction(prefab); 3742 action->addAction( explodeAction ); 3743 3744 selectObject( explodeAction->mGroup ); 3745 3746 MEDeleteUndoAction *delAction = new MEDeleteUndoAction(); 3747 delAction->deleteObject( prefab ); 3748 3749 action->addAction( delAction ); 3750 } 3751 3752 undoMan->addAction( action ); 3753 3754 setDirty(); 3755} 3756 3757void WorldEditor::makeSelectionAMesh(const char *filename) 3758{ 3759 if (mSelected->size() == 0) 3760 { 3761 Con::errorf("WorldEditor::makeSelectionAMesh - Nothing selected."); 3762 return; 3763 } 3764 3765 SimGroup *missionGroup; 3766 if (!Sim::findObject("MissionGroup", missionGroup)) 3767 { 3768 Con::errorf("WorldEditor::makeSelectionAMesh - Could not find MissionGroup."); 3769 return; 3770 } 3771 3772 Vector< SimObject*> stack; 3773 Vector< SimObject*> found; 3774 3775 for (S32 i = 0; i < mSelected->size(); i++) 3776 { 3777 SimObject *obj = (*mSelected)[i]; 3778 stack.push_back(obj); 3779 } 3780 3781 Vector< SimGroup*> cleanup; 3782 3783 while (!stack.empty()) 3784 { 3785 SimObject *obj = stack.last(); 3786 SimGroup *grp = dynamic_cast< SimGroup* >(obj); 3787 3788 stack.pop_back(); 3789 3790 if (grp) 3791 { 3792 for (S32 i = 0; i < grp->size(); i++) 3793 stack.push_back(grp->at(i)); 3794 3795 SceneObject* scn = dynamic_cast< SceneObject* >(grp); 3796 if (scn) 3797 { 3798 if (Prefab::isValidChild(obj, true)) 3799 found.push_back(obj); 3800 } 3801 else 3802 { 3803 //Only push the cleanup of the group if it's ONLY a SimGroup. 3804 cleanup.push_back(grp); 3805 } 3806 } 3807 else 3808 { 3809 if (Prefab::isValidChild(obj, true)) 3810 found.push_back(obj); 3811 } 3812 } 3813 3814 if (found.empty()) 3815 { 3816 Con::warnf("WorldEditor::makeSelectionPrefab - No valid objects selected."); 3817 return; 3818 } 3819 3820 // SimGroup we collect prefab objects into. 3821 SimGroup *group = new SimGroup(); 3822 group->registerObject(); 3823 3824 // Transform from World to Prefab space. 3825 MatrixF fabMat(true); 3826 fabMat.setPosition(mSelected->getCentroid()); 3827 fabMat.inverse(); 3828 3829 MatrixF objMat; 3830 SimObject *obj = NULL; 3831 SceneObject *sObj = NULL; 3832 3833 Vector< SceneObject*> objectList; 3834 3835 for ( S32 i = 0; i < mSelected->size(); i++ ) 3836 { 3837 SceneObject *pObj = dynamic_cast< SceneObject* >( ( *mSelected )[i] ); 3838 if ( pObj ) 3839 objectList.push_back( pObj ); 3840 } 3841 3842 if ( objectList.empty() ) 3843 return; 3844 3845 // 3846 Point3F centroid; 3847 MatrixF orientation; 3848 3849 if (objectList.size() == 1) 3850 { 3851 orientation = objectList[0]->getTransform(); 3852 centroid = objectList[0]->getPosition(); 3853 } 3854 else 3855 { 3856 orientation.identity(); 3857 centroid.zero(); 3858 3859 S32 count = 0; 3860 3861 for (S32 i = 0; i < objectList.size(); i++) 3862 { 3863 SceneObject *pObj = objectList[i]; 3864 if (pObj->isGlobalBounds()) 3865 continue; 3866 3867 centroid += pObj->getPosition(); 3868 count++; 3869 } 3870 3871 centroid /= count; 3872 } 3873 3874 orientation.setPosition(centroid); 3875 orientation.inverse(); 3876 3877 OptimizedPolyList polyList; 3878 polyList.setBaseTransform(orientation); 3879 3880 for (S32 i = 0; i < objectList.size(); i++) 3881 { 3882 SceneObject *pObj = objectList[i]; 3883 if (!pObj->buildPolyList(PLC_Export, &polyList, pObj->getWorldBox(), pObj->getWorldSphere())) 3884 Con::warnf("colladaExportObjectList() - object %i returned no geometry.", pObj->getId()); 3885 } 3886 3887 // Use a ColladaUtils function to do the actual export to a Collada file 3888 ColladaUtils::exportToCollada(filename, polyList); 3889 // 3890 3891 // Allocate TSStatic object and add to level. 3892 TSStatic *ts = new TSStatic(); 3893 ts->setShapeFileName(StringTable->insert(filename)); 3894 fabMat.inverse(); 3895 ts->setTransform(fabMat); 3896 ts->registerObject(); 3897 missionGroup->addObject(ts); 3898 3899 // Select it, mark level as dirty. 3900 clearSelection(); 3901 selectObject(ts); 3902 setDirty(); 3903 3904 // Delete original objects and temporary SimGroup. 3905 for (S32 i = 0; i < objectList.size(); i++) 3906 objectList[i]->deleteObject(); 3907} 3908 3909DefineEngineMethod( WorldEditor, makeSelectionPrefab, void, ( const char* filename ),, 3910 "Save selected objects to a .prefab file and replace them in the level with a Prefab object." 3911 "@param filename Prefab file to save the selected objects to.") 3912{ 3913 object->makeSelectionPrefab( filename ); 3914} 3915 3916DefineEngineMethod( WorldEditor, explodeSelectedPrefab, void, (),, 3917 "Replace selected Prefab objects with a SimGroup containing all children objects defined in the .prefab.") 3918{ 3919 object->explodeSelectedPrefab(); 3920} 3921 3922DefineEngineMethod(WorldEditor, makeSelectionAMesh, void, (const char* filename), , 3923 "Save selected objects to a .dae collada file and replace them in the level with a TSStatic object." 3924 "@param filename collada file to save the selected objects to.") 3925{ 3926 object->makeSelectionAMesh(filename); 3927} 3928 3929DefineEngineMethod( WorldEditor, mountRelative, void, ( SceneObject *objA, SceneObject *objB ),, 3930 "Mount object B relatively to object A." 3931 "@param objA Object to mount to." 3932 "@param objB Object to mount.") 3933{ 3934 if (!objA || !objB) 3935 return; 3936 3937 MatrixF xfm = objB->getTransform(); 3938 MatrixF mat = objA->getWorldTransform(); 3939 xfm.mul( mat ); 3940 3941 Point3F pos = objB->getPosition(); 3942 MatrixF temp = objA->getTransform(); 3943 temp.scale( objA->getScale() ); 3944 temp.inverse(); 3945 temp.mulP( pos ); 3946 3947 xfm.setPosition( pos ); 3948 3949 3950 objA->mountObject( objB, -1, xfm ); 3951} 3952 3953//----------------------------------------------------------------------------- 3954 3955DefineEngineMethod( WorldEditor, createPolyhedralObject, SceneObject*, ( const char* className, SceneObject* geometryProvider ),, 3956 "Grab the geometry from @a geometryProvider, create a @a className object, and assign it the extracted geometry." ) 3957{ 3958 if( !geometryProvider ) 3959 { 3960 Con::errorf( "WorldEditor::createPolyhedralObject - Invalid geometry provider!" ); 3961 return NULL; 3962 } 3963 3964 if( !className || !className[ 0 ] ) 3965 { 3966 Con::errorf( "WorldEditor::createPolyhedralObject - Invalid class name" ); 3967 return NULL; 3968 } 3969 3970 AbstractClassRep* classRep = AbstractClassRep::findClassRep( className ); 3971 if( !classRep ) 3972 { 3973 Con::errorf( "WorldEditor::createPolyhedralObject - No such class: %s", className ); 3974 return NULL; 3975 } 3976 3977 // We don't want the extracted poly list to be affected by the object's 3978 // current transform and scale so temporarily reset them. 3979 3980 MatrixF savedTransform = geometryProvider->getTransform(); 3981 Point3F savedScale = geometryProvider->getScale(); 3982 3983 geometryProvider->setTransform( MatrixF::Identity ); 3984 geometryProvider->setScale( Point3F( 1.f, 1.f, 1.f ) ); 3985 3986 // Extract the geometry. Use the object-space bounding volumes 3987 // as we have moved the object to the origin for the moment. 3988 3989 OptimizedPolyList polyList; 3990 if( !geometryProvider->buildPolyList( PLC_Export, &polyList, geometryProvider->getObjBox(), geometryProvider->getObjBox().getBoundingSphere() ) ) 3991 { 3992 Con::errorf( "WorldEditor::createPolyhedralObject - Failed to extract geometry!" ); 3993 return NULL; 3994 } 3995 3996 // Restore the object's original transform. 3997 3998 geometryProvider->setTransform( savedTransform ); 3999 geometryProvider->setScale( savedScale ); 4000 4001 // Create the object. 4002 4003 SceneObject* object = dynamic_cast< SceneObject* >( classRep->create() ); 4004 if( !Object ) 4005 { 4006 Con::errorf( "WorldEditor::createPolyhedralObject - Could not create SceneObject with class '%s'", className ); 4007 return NULL; 4008 } 4009 4010 // Convert the polylist to a polyhedron. 4011 4012 Polyhedron polyhedron = polyList.toPolyhedron(); 4013 4014 // Add the vertex data. 4015 4016 const U32 numPoints = polyhedron.getNumPoints(); 4017 const Point3F* points = polyhedron.getPoints(); 4018 4019 for( U32 i = 0; i < numPoints; ++ i ) 4020 { 4021 static StringTableEntry sPoint = StringTable->insert( "point" ); 4022 object->setDataField( sPoint, NULL, EngineMarshallData( points[ i ] ) ); 4023 } 4024 4025 // Add the plane data. 4026 4027 const U32 numPlanes = polyhedron.getNumPlanes(); 4028 const PlaneF* planes = polyhedron.getPlanes(); 4029 4030 for( U32 i = 0; i < numPlanes; ++ i ) 4031 { 4032 static StringTableEntry sPlane = StringTable->insert( "plane" ); 4033 const PlaneF& plane = planes[ i ]; 4034 4035 char buffer[ 1024 ]; 4036 dSprintf( buffer, sizeof( buffer ), "%g %g %g %g", plane.x, plane.y, plane.z, plane.d ); 4037 4038 object->setDataField( sPlane, NULL, buffer ); 4039 } 4040 4041 // Add the edge data. 4042 4043 const U32 numEdges = polyhedron.getNumEdges(); 4044 const Polyhedron::Edge* edges = polyhedron.getEdges(); 4045 4046 for( U32 i = 0; i < numEdges; ++ i ) 4047 { 4048 static StringTableEntry sEdge = StringTable->insert( "edge" ); 4049 const Polyhedron::Edge& edge = edges[ i ]; 4050 4051 char buffer[ 1024 ]; 4052 dSprintf( buffer, sizeof( buffer ), "%i %i %i %i ", 4053 edge.face[ 0 ], edge.face[ 1 ], 4054 edge.vertex[ 0 ], edge.vertex[ 1 ] 4055 ); 4056 4057 object->setDataField( sEdge, NULL, buffer ); 4058 } 4059 4060 // Set the transform. 4061 4062 object->setTransform( savedTransform ); 4063 object->setScale( savedScale ); 4064 4065 // Register and return the object. 4066 4067 if( !object->registerObject() ) 4068 { 4069 Con::errorf( "WorldEditor::createPolyhedralObject - Failed to register object!" ); 4070 delete object; 4071 return NULL; 4072 } 4073 4074 return object; 4075} 4076 4077//----------------------------------------------------------------------------- 4078 4079DefineEngineMethod( WorldEditor, createConvexShapeFrom, ConvexShape*, ( SceneObject* polyObject ),, 4080 "Create a ConvexShape from the given polyhedral object." ) 4081{ 4082 if( !polyObject ) 4083 { 4084 Con::errorf( "WorldEditor::createConvexShapeFrom - Invalid object" ); 4085 return NULL; 4086 } 4087 4088 IScenePolyhedralObject* iPoly = dynamic_cast< IScenePolyhedralObject* >( polyObject ); 4089 if( !iPoly ) 4090 { 4091 Con::errorf( "WorldEditor::createConvexShapeFrom - Not a polyhedral object!" ); 4092 return NULL; 4093 } 4094 4095 // Get polyhedron. 4096 4097 AnyPolyhedron polyhedron = iPoly->ToAnyPolyhedron(); 4098 const U32 numPlanes = polyhedron.getNumPlanes(); 4099 if( !numPlanes ) 4100 { 4101 Con::errorf( "WorldEditor::createConvexShapeFrom - Object returned no valid polyhedron" ); 4102 return NULL; 4103 } 4104 4105 // Create a ConvexShape. 4106 4107 ConvexShape* shape = new ConvexShape(); 4108 4109 // Add all planes. 4110 4111 for( U32 i = 0; i < numPlanes; ++ i ) 4112 { 4113 const PlaneF& plane = polyhedron.getPlanes()[ i ]; 4114 4115 // Polyhedron planes are facing inwards so we need to 4116 // invert the normal here. 4117 4118 Point3F normal = plane.getNormal(); 4119 normal.neg(); 4120 4121 // Turn the orientation of the plane into a quaternion. 4122 // The normal is our up vector (that's what's expected 4123 // by ConvexShape for the surface orientation). 4124 4125 MatrixF orientation( true ); 4126 MathUtils::getMatrixFromUpVector( normal, &orientation ); 4127 const QuatF quat( orientation ); 4128 4129 // Get the plane position. 4130 4131 const Point3F position = plane.getPosition(); 4132 4133 // Turn everything into a "surface" property for the ConvexShape. 4134 4135 char buffer[ 1024 ]; 4136 dSprintf( buffer, sizeof( buffer ), "%g %g %g %g %g %g %g", 4137 quat.x, quat.y, quat.z, quat.w, 4138 position.x, position.y, position.z 4139 ); 4140 4141 // Add the surface. 4142 4143 static StringTableEntry sSurface = StringTable->insert( "surface" ); 4144 shape->setDataField( sSurface, NULL, buffer ); 4145 } 4146 4147 // Copy the transform. 4148 4149 shape->setTransform( polyObject->getTransform() ); 4150 shape->setScale( polyObject->getScale() ); 4151 4152 // Register the shape. 4153 4154 if( !shape->registerObject() ) 4155 { 4156 Con::errorf( "WorldEditor::createConvexShapeFrom - Could not register ConvexShape!" ); 4157 delete shape; 4158 return NULL; 4159 } 4160 4161 return shape; 4162} 4163
