Torque3D Documentation / _generateds / worldEditor.cpp

worldEditor.cpp

Engine/source/gui/worldEditor/worldEditor.cpp

More...

Classes:

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." )
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