text: //-----------------------------------------------------------------------------
  // Copyright (c) 2012 GarageGames, LLC
  //
  // Permission is hereby granted, free of charge, to any person obtaining a copy
  // of this software and associated documentation files (the "Software"), to
  // deal in the Software without restriction, including without limitation the
  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  // sell copies of the Software, and to permit persons to whom the Software is
  // furnished to do so, subject to the following conditions:
  //
  // The above copyright notice and this permission notice shall be included in
  // all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  // IN THE SOFTWARE.
  //-----------------------------------------------------------------------------
  
  #include "platform/platform.h"
  #include "gui/worldEditor/guiConvexShapeEditorCtrl.h"
  
  #include "console/consoleTypes.h"
  #include "console/engineAPI.h"
  #include "T3D/convexShape.h"
  #include "renderInstance/renderPassManager.h"
  #include "collision/collision.h"
  #include "math/util/frustum.h"
  #include "math/mathUtils.h"
  #include "gfx/gfxPrimitiveBuffer.h"
  #include "gfx/gfxTextureHandle.h"
  #include "gfx/gfxTransformSaver.h"
  #include "gfx/primBuilder.h"
  #include "gfx/gfxDrawUtil.h"
  #include "scene/sceneRenderState.h"
  #include "scene/sceneManager.h"
  #include "gui/core/guiCanvas.h"
  #include "gui/buttons/guiButtonCtrl.h"
  #include "gui/worldEditor/undoActions.h"
  #include "T3D/gameBase/gameConnection.h"
  #include "gfx/sim/debugDraw.h"
  #include "collision/optimizedPolyList.h"
  #include "core/volume.h"
  #include "gui/worldEditor/worldEditor.h"
  #include "T3D/prefab.h"
  #include "T3D/trigger.h"
  #include "T3D/zone.h"
  #include "T3D/portal.h"
  #include "math/mPolyhedron.impl.h"
  
  #include "T3D/Scene.h"
  
  IMPLEMENT_CONOBJECT( GuiConvexEditorCtrl );
  
  ConsoleDocClass( GuiConvexEditorCtrl,
     "@brief The base class for the sketch tool\n\n"
     "Editor use only.\n\n"
     "@internal"
  );
  
  
  GuiConvexEditorCtrl::GuiConvexEditorCtrl()
   : mIsDirty( false ),
     mFaceSEL( -1 ),
     mFaceHL( -1 ),
     mFaceSavedXfm( true ),
     mSavedUndo( false ),
     mHasGeometry(false),
     mDragging( false ),
     mGizmoMatOffset( Point3F::Zero ),
     mPivotPos( Point3F::Zero ),
     mUsingPivot( false ),
     mSettingPivot( false ),
     mActiveTool( NULL ),
     mMouseDown( false ),
     mCreateTool( NULL ),
     mSavedGizmoFlags( -1 ),
     mHasCopied( false ),
     mLastUndo( NULL ),
     mUndoManager( NULL ),
     mCtrlDown( false ),
     mGridSnap(false)
  {   
  	mMaterialName = StringTable->insert("Prototyping:WallOrange");
  }
  
  GuiConvexEditorCtrl::~GuiConvexEditorCtrl()
  {
  }
  
  bool GuiConvexEditorCtrl::onAdd()
  {
     if ( !Parent::onAdd() )
        return false;   
  
     SceneManager::getPreRenderSignal().notify( this, &GuiConvexEditorCtrl::_prepRenderImage );
  
     mCreateTool = new ConvexEditorCreateTool( this );
  
     return true;
  }
  
  void GuiConvexEditorCtrl::onRemove()
  {
     SceneManager::getPreRenderSignal().remove( this, &GuiConvexEditorCtrl::_prepRenderImage );
  
     SAFE_DELETE( mCreateTool );
  
     Parent::onRemove();
  }
  
  void GuiConvexEditorCtrl::initPersistFields()
  {   
     addField( "isDirty", TypeBool, Offset( mIsDirty, GuiConvexEditorCtrl ) );
  	addField( "materialName", TypeMaterialAssetId, Offset(mMaterialName, GuiConvexEditorCtrl) );
  
     Parent::initPersistFields();
  }
  
  bool GuiConvexEditorCtrl::onWake()
  {
     if ( !Parent::onWake() )
        return false;
  
     Scene* scene = Scene::getRootScene();
     if ( !scene )
        return true;
  
     SimGroup::iterator itr = scene->begin();
     for ( ; itr != scene->end(); itr++ )
     {
        if ( String::compare( (*itr)->getClassName(), "ConvexShape" ) == 0 )
        {
           mConvexSEL = static_cast<ConvexShape*>( *itr );
           mGizmo->set( mConvexSEL->getTransform(), mConvexSEL->getPosition(), mConvexSEL->getScale() );
           return true;
        }
     }
  
     return true;   
  }
  
  void GuiConvexEditorCtrl::onSleep()
  {
     Parent::onSleep();
  
     mConvexSEL = NULL;
     mConvexHL = NULL;   
  }
  
  void GuiConvexEditorCtrl::setVisible( bool val )
  {
     //ConvexShape::smRenderEdges = value;
  
     if ( isProperlyAdded() )
     {
        if ( !val )
        {         
           mFaceHL = -1;
           mConvexHL = NULL;			
  
           setSelection( NULL, -1 );
  
           if ( mSavedGizmoFlags != -1 )
           {
              mGizmoProfile->flags = mSavedGizmoFlags;
              mSavedGizmoFlags = -1;
           }
  
           Scene* scene = Scene::getRootScene();
           if (scene != nullptr)
           {
              //Make our proxy objects "real" again
              for (U32 i = 0; i < mProxyObjects.size(); ++i)
              {
                 if (!mProxyObjects[i].shapeProxy || !mProxyObjects[i].targetObject)
                    continue;
  
                 AbstractClassRep* classRep = AbstractClassRep::findClassRep(mProxyObjects[i].targetObjectClass);
                 if (!classRep)
                 {
                    Con::errorf("WorldEditor::createPolyhedralObject - No such class: %s", mProxyObjects[i].targetObjectClass.c_str());
                    continue;
                 }
  
                 SceneObject* polyObj = createPolyhedralObject(mProxyObjects[i].targetObjectClass.c_str(), mProxyObjects[i].shapeProxy);
  
                 scene->addObject(polyObj);
  
                 //Now, remove the convex proxy
                 mProxyObjects[i].shapeProxy->deleteObject();
                 mProxyObjects[i].targetObject->deleteObject();
                 mProxyObjects.erase(i);
                 --i;
              }
  
           }
        }
        else
        {
  			mConvexHL = NULL;			
  			mFaceHL = -1;
  
           setSelection( NULL, -1 );
  
  			WorldEditor *wedit;
  			if ( Sim::findObject( "EWorldEditor", wedit ) )
  			{
  				S32 count = wedit->getSelectionSize();
  				for ( S32 i = 0; i < count; i++ )
  				{
  					S32 objId = wedit->getSelectObject(i);
  					ConvexShape *pShape;
  					if ( Sim::findObject( objId, pShape ) )
  					{
  						mConvexSEL = pShape;						
  						wedit->clearSelection();
  						wedit->selectObject( String::ToString("%i",objId) );
  						break;
  					}
  				}
  			}
           updateGizmoPos();
           mSavedGizmoFlags = mGizmoProfile->flags;
  
           Scene* scene = Scene::getRootScene();
           if (scene != nullptr)
           {
              for (U32 c = 0; c < scene->size(); ++c)
              {
                 bool isTrigger = (scene->at(c)->getClassName() == StringTable->insert("Trigger"));
                 bool isZone = (scene->at(c)->getClassName() == StringTable->insert("Zone"));
                 bool isPortal = (scene->at(c)->getClassName() == StringTable->insert("Portal"));
                 bool isOccluder = (scene->at(c)->getClassName() == StringTable->insert("OcclusionVolume"));
  
                 if (isTrigger || isZone || isPortal || isOccluder)
                 {
                    SceneObject* sceneObj = static_cast<SceneObject*>(scene->at(c));
                    if (!sceneObj)
                    {
                       Con::errorf("WorldEditor::createConvexShapeFrom - Invalid object");
                       continue;
                    }
  
                    ConvexShape* proxyShape = createConvexShapeFrom(sceneObj);
  
                    if (proxyShape == NULL)
                       continue;
  
                    //Set the texture to a representatory one so we know what's what
                    if (isTrigger)
                       proxyShape->_setMaterial(StringTable->insert("ToolsModule:TriggerProxyMaterial"));
                    else if (isPortal)
                       proxyShape->_setMaterial(StringTable->insert("ToolsModule:PortalProxyMaterial"));
                    else if (isZone)
                       proxyShape->_setMaterial(StringTable->insert("ToolsModule:ZoneProxyMaterial"));
                    else if (isOccluder)
                       proxyShape->_setMaterial(StringTable->insert("ToolsModule:OccluderProxyMaterial"));
  
                    proxyShape->_updateMaterial();
  
                    sceneObj->setHidden(true);
  
                    //set up the proxy object
                    ConvexShapeProxy newProxy;
                    newProxy.shapeProxy = proxyShape;
                    newProxy.targetObject = sceneObj;
  
                    if (isTrigger)
                       newProxy.targetObjectClass = "Trigger";
                    else if (isPortal)
                       newProxy.targetObjectClass = "Portal";
                    else if (isZone)
                       newProxy.targetObjectClass = "Zone";
                    else
                       newProxy.targetObjectClass = "OcclusionVolume";
  
                    mProxyObjects.push_back(newProxy);
                 }
              }
           }
        }
     }
  
     Parent::setVisible( val );
  }
  
  void GuiConvexEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event)
  {
     mouseLock();   
  
     mMouseDown = true;
  
     if ( event.modifier & SI_ALT )
     {
        setActiveTool( mCreateTool );
        mActiveTool->on3DMouseDown( event );
        return;
     }
  
     if ( mConvexSEL && isShapeValid( mConvexSEL ) )      
        mLastValidShape = mConvexSEL->mSurfaces;  
  
     if ( mConvexSEL &&
          mFaceSEL != -1 &&
          mGizmo->getMode() == RotateMode &&
          mGizmo->getSelection() == Gizmo::Centroid )
     {      
        mSettingPivot = true;      
        mSavedPivotPos = mGizmo->getPosition();
        setPivotPos( mConvexSEL, mFaceSEL, event );
        updateGizmoPos();
        return;
     }
  
     mGizmo->on3DMouseDown( event );   
  }
  
  void GuiConvexEditorCtrl::on3DRightMouseDown(const Gui3DMouseEvent & event)
  {
     return;
  
  	/*
     if ( mConvexSEL && mFaceSEL != -1 && mFaceSEL == mFaceHL )
     {
        _submitUndo( "Split ConvexShape face." );
  
        const MatrixF &surf = mConvexSEL->mSurfaces[mFaceSEL];
  
        MatrixF newSurf( surf );
  
        MatrixF rotMat( EulerF( 0.0f, mDegToRad( 2.0f ), 0.0f ) );
  
        newSurf *= rotMat;           
  
        mConvexSEL->mSurfaces.insert( mFaceSEL+1, newSurf );
     }
  	*/
  }
  
  void GuiConvexEditorCtrl::on3DRightMouseUp(const Gui3DMouseEvent & event)
  {
     //ConvexShape *hitShape;
     //S32 hitFace;
     //bool hit = _cursorCast( event, &hitShape, &hitFace );
     //Con::printf( hit ? "HIT" : "MISS" );
  }
  
  void GuiConvexEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event)
  {
     mouseUnlock();
  
     mMouseDown = false;
  
     mHasCopied = false;
     mHasGeometry = false;   
  
     if ( mActiveTool )
     {
        ConvexEditorTool::EventResult result = mActiveTool->on3DMouseUp( event );
  
        if ( result == ConvexEditorTool::Done )      
           setActiveTool( NULL );         
        
        return;
     }
  
     if ( !mSettingPivot && !mDragging && ( mGizmo->getSelection() == Gizmo::None || !mConvexSEL ) )
     {
        if ( mConvexSEL != mConvexHL )
        {         
           setSelection( mConvexHL, -1 );
        }
        else
        {
           if ( mFaceSEL != mFaceHL )         
              setSelection( mConvexSEL, mFaceHL );         
           else
              setSelection( mConvexSEL, -1 );
        }
  
        mUsingPivot = false;
     }
  
     mSettingPivot = false;
     mSavedPivotPos = mGizmo->getPosition();
     mSavedUndo = false;   
  
     mGizmo->on3DMouseUp( event );
  
     if ( mDragging )
     {
        mDragging = false;
  
        if ( mConvexSEL )
        {         
           Vector< U32 > removedPlanes;
           mConvexSEL->cullEmptyPlanes( &removedPlanes );
  
           // If a face has been removed we need to validate / remap
           // our selected and highlighted faces.
           if ( !removedPlanes.empty() )
           {
              S32 prevFaceHL = mFaceHL;
              S32 prevFaceSEL = mFaceSEL;
  
              if ( removedPlanes.contains( mFaceHL ) )
                 prevFaceHL = mFaceHL = -1;
              if ( removedPlanes.contains( mFaceSEL ) )
                 prevFaceSEL = mFaceSEL = -1;
              
              for ( S32 i = 0; i < removedPlanes.size(); i++ )
              {
                 if ( (S32)removedPlanes[i] < prevFaceSEL )
                    mFaceSEL--;               
                 if ( (S32)removedPlanes[i] < prevFaceHL )
                    mFaceHL--;     
              }        
  
              setSelection( mConvexSEL, mFaceSEL );
  
              // We need to reindex faces.
              updateShape( mConvexSEL );
           }
        }
     }
  
     updateGizmoPos();   
  }
  
  void GuiConvexEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event)
  {
     if ( mActiveTool )
     {
        // If we have an active tool pass this event to it.
        // If it handled it, consume the event.
        if ( mActiveTool->on3DMouseMove( event ) )
           return;
     }
  
     ConvexShape *hitShape = NULL;
     S32 hitFace = -1;
     
     _cursorCast( event, &hitShape, &hitFace );
  
     if ( !mConvexSEL )
     {
        mConvexHL = hitShape;
        mFaceHL = -1;
     }
     else
     {
        if ( mConvexSEL == hitShape )
        {
           mConvexHL = hitShape;        
           mFaceHL = hitFace;
        }
        else
        {
           // Mousing over a shape that is not the one currently selected.
  
           if ( mFaceSEL != -1 )
           {
              mFaceHL = -1;
           }
           else
           {
              mConvexHL = hitShape;
              mFaceHL = -1;
           } 
        }
     }
  
     if ( mConvexSEL )
        mGizmo->on3DMouseMove( event );
  }
  
  void GuiConvexEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event)
  {      
     if ( mActiveTool )
     {
        // If we have an active tool pass this event to it.
        // If it handled it, consume the event.
        if ( mActiveTool->on3DMouseDragged( event ) )
           return;
     }
  
     //mGizmoProfile->rotateScalar = 0.55f;
     //mGizmoProfile->scaleScalar = 0.55f;
  
     if ( !mConvexSEL )
        return;
  
     if ( mGizmo->getMode() == RotateMode &&
          mGizmo->getSelection() == Gizmo::Centroid )
     {            
        setPivotPos( mConvexSEL, mFaceSEL, event );      
        mDragging = true;
        return;
     }
  
     mGizmo->on3DMouseDragged( event );
        
     if ( event.modifier & SI_SHIFT && 
         ( mGizmo->getMode() == MoveMode || mGizmo->getMode() == RotateMode ) &&
          !mHasCopied )
     {
        if ( mFaceSEL != -1 )
        {
           ConvexShape *newShape = mCreateTool->extrudeShapeFromFace( mConvexSEL, mFaceSEL );
           //newShape->_updateGeometry();
  
           submitUndo( CreateShape, newShape );
           setSelection( newShape, 0 );         
           updateGizmoPos();
  
           mGizmo->on3DMouseDown( event );
  
           mHasCopied = true;
           mSavedUndo = true;
        }
        else
        {
           ConvexShape *newShape = new ConvexShape();
           newShape->setTransform( mConvexSEL->getTransform() );
           newShape->setScale( mConvexSEL->getScale() );
           newShape->mSurfaces.clear();
           newShape->mSurfaces.merge( mConvexSEL->mSurfaces );
           
           setupShape( newShape );
  
           newShape->_setMaterial(mConvexSEL->getMaterial());
  
           submitUndo( CreateShape, newShape );
  
           setSelection( newShape, -1 );
  
           updateGizmoPos();
  
           mHasCopied = true;
           mSavedUndo = true;
        }
  
        return;
     }
  
     if ( mGizmo->getMode() == RotateMode &&
          event.modifier & SI_CTRL &&
          !mHasCopied &&
          mFaceSEL != -1 )
     {
        // Can must verify that splitting the face at the current angle 
        // ( of the gizmo ) will generate a valid shape.  If not enough rotation
        // has occurred we will have two faces that are coplanar and must wait
        // until later in the drag to perform the split.
  
        //AssertFatal( isShapeValid( mConvexSEL ), "Shape was already invalid at beginning of split operation." );
  
        if ( !isShapeValid( mConvexSEL ) )
           return;
  
        mLastValidShape = mConvexSEL->mSurfaces;
  
        Point3F rot = mGizmo->getDeltaTotalRot();
        rot.normalize();
        rot *= mDegToRad( 10.0f );
  
        MatrixF rotMat( (EulerF)rot );
  
        MatrixF worldToObj( mConvexSEL->getTransform() );
        worldToObj.scale( mConvexSEL->getScale() );
        worldToObj.inverse();      
  
        mConvexSEL->mSurfaces.increment();
        MatrixF &newSurf = mConvexSEL->mSurfaces.last();
        newSurf = mConvexSEL->mSurfaces[mFaceSEL] * rotMat;
        
        //worldToObj.mul( mGizmo->getTransform() );
        //Point3F pos( mPivotPos );
        //worldToObj.mulP( pos );
        //newSurf.setPosition( pos );
  
        updateShape( mConvexSEL );
  
        if ( !isShapeValid( mConvexSEL ) )
        {
           mConvexSEL->mSurfaces = mLastValidShape;
           updateShape( mConvexSEL );
        }
        else
        {
           mHasCopied = true;
           mSavedUndo = true;
  
           mLastValidShape = mConvexSEL->mSurfaces;
  
           submitUndo( ModifyShape, mConvexSEL );           
  
           setSelection( mConvexSEL, mConvexSEL->mSurfaces.size() - 1 );
  
           updateGizmoPos();
        }      
        
        return;
     }
  
     // If we are dragging, but no gizmo selection...
     // Then treat this like a regular mouse move, update the highlighted
     // convex/face under the cursor and handle onMouseUp as we normally would
     // to change the selection.
     if ( mGizmo->getSelection() == Gizmo::None )
     {
        ConvexShape *hitShape = NULL;
        S32 hitFace = -1;
  
        _cursorCast( event, &hitShape, &hitFace );
        mFaceHL = hitFace;
        mConvexHL = hitShape;      
  
        return;
     }
  
     mDragging = true;
  
     // Manipulating a face.
  
     if ( mFaceSEL != -1 )
     {
        if ( !mSavedUndo )
        {
           mSavedUndo = true;
           submitUndo( ModifyShape, mConvexSEL );
        }      
  
        if ( mGizmo->getMode() == ScaleMode &&  !(event.modifier & SI_CTRL))
        {
           scaleFace( mConvexSEL, mFaceSEL, mGizmo->getScale() );
        }
  
  	  else if ( mGizmo->getMode() == ScaleMode &&  (event.modifier & SI_CTRL) )
        {
            Point3F scale = mGizmo->getDeltaScale();
  
  	     F32 scalar = 1;
  		  mConvexSEL->mSurfaceUVs[mFaceSEL].scale += (Point2F(scale.x, scale.y) * scalar);
  
          if (mConvexSEL->mSurfaceUVs[mFaceSEL].scale.x < 0.01)
             mConvexSEL->mSurfaceUVs[mFaceSEL].scale.x = 0.01;
  
          if (mConvexSEL->mSurfaceUVs[mFaceSEL].scale.y < 0.01)
             mConvexSEL->mSurfaceUVs[mFaceSEL].scale.y = 0.01;
  
          if (mConvexSEL->mSurfaceUVs[mFaceSEL].scale.x > 100)
             mConvexSEL->mSurfaceUVs[mFaceSEL].scale.x = 100;
  
          if (mConvexSEL->mSurfaceUVs[mFaceSEL].scale.y > 100)
             mConvexSEL->mSurfaceUVs[mFaceSEL].scale.y = 100;
  
          Point2F test = mConvexSEL->mSurfaceUVs[mFaceSEL].scale;
  		  mConvexSEL->setMaskBits( ConvexShape::UpdateMask );
  
  		  updateShape( mConvexSEL, mFaceSEL );
       }
  	  /*else if ( mGizmo->getMode() == MoveMode && event.modifier & SI_CTRL ) {
  		  Point3F scale = mGizmo->getOffset();
  
            F32 scalar = 0.8;
  		  mConvexSEL->mSurfaceTextures[mFaceSEL].offset += (Point2F(-scale.x, scale.z) * scalar);
  		  mConvexSEL->setMaskBits( ConvexShape::UpdateMask );
  
  		  updateShape( mConvexSEL, mFaceSEL );
  	  }*/
        else
        {
           // Why does this have to be so ugly.
           if ( mGizmo->getMode() == RotateMode || 
                ( mGizmo->getMode() == MoveMode  && 
                  ( event.modifier & SI_CTRL  ||
                    ( mGizmo->getSelection() == Gizmo::Axis_Z && mHasCopied ) 
                  )
                )
              )
           {
              const MatrixF &gMat = mGizmo->getTransform();      
              MatrixF surfMat;
              surfMat.mul( mConvexSEL->mWorldToObj, gMat );
  
              MatrixF worldToObj ( mConvexSEL->getTransform() );
              worldToObj.scale( mConvexSEL->getScale() );
              worldToObj.inverse();
  
              Point3F newPos;            
              newPos = gMat.getPosition();      
  
              worldToObj.mulP( newPos );
              surfMat.setPosition( newPos );
              
              // Clear out floating point errors.
              cleanMatrix( surfMat );
  
              if (mGizmo->getSelection() == Gizmo::Axis_Z)
              {
                 MatrixF curSurfMat = mConvexSEL->mSurfaces[mFaceSEL];
                 EulerF curSufRot = curSurfMat.toEuler();
  
                 EulerF newSufRot = surfMat.toEuler();
  
                 float zRot = mRadToDeg(newSufRot.z - curSufRot.z);
  
                 mConvexSEL->mSurfaceUVs[mFaceSEL].zRot += zRot;
              }
  
              mConvexSEL->mSurfaces[mFaceSEL] = surfMat;
  
              updateShape( mConvexSEL, mFaceSEL );         
           }
           else
           {
              // Translating a face in x/y/z
  
              translateFace( mConvexSEL, mFaceSEL, mGizmo->getTotalOffset() );
           }
        }
  
        if ( isShapeValid( mConvexSEL ) )          
        {
           AssertFatal( mConvexSEL->mSurfaces.size() > mFaceSEL, "mFaceSEL out of range." );
           mLastValidShape = mConvexSEL->mSurfaces; 
        }
        else
        {
           AssertFatal( mLastValidShape.size() > mFaceSEL, "mFaceSEL out of range." );
           mConvexSEL->mSurfaces = mLastValidShape;
           updateShape( mConvexSEL );
        }
  
        return;
     }
  
     // Manipulating a whole Convex.
  
     if ( !mSavedUndo )
     {
        mSavedUndo = true;
        submitUndo( ModifyShape, mConvexSEL );
     }
  
     if ( mGizmo->getMode() == MoveMode )
     {
        //mConvexSEL->setPosition( mGizmo->getPosition() );
  
        //MatrixF mat = mGizmo->getTransform();
        Point3F wPos = mGizmo->getPosition();
        //mat.getColumn(3, &wPos);
  
        // adjust
        //wPos += offset;
  
        if (mGridSnap && mGridPlaneSize != 0.f)
        {
           if (mGizmo->getSelection() == Gizmo::Selection::Axis_X || mGizmo->getSelection() == Gizmo::Selection::Plane_XY || mGizmo->getSelection() == Gizmo::Selection::Plane_XZ)
              wPos.x -= mFmod(wPos.x, mGridPlaneSize);
  
           if (mGizmo->getSelection() == Gizmo::Selection::Axis_Y || mGizmo->getSelection() == Gizmo::Selection::Plane_XY || mGizmo->getSelection() == Gizmo::Selection::Plane_YZ)
              wPos.y -= mFmod(wPos.y, mGridPlaneSize);
  
           if (mGizmo->getSelection() == Gizmo::Selection::Axis_Z || mGizmo->getSelection() == Gizmo::Selection::Plane_XZ || mGizmo->getSelection() == Gizmo::Selection::Plane_YZ)
              wPos.z -= mFmod(wPos.z, mGridPlaneSize);
        }
  
        mConvexSEL->setPosition(wPos);
     }
     else if ( mGizmo->getMode() == RotateMode )
     {   
        mConvexSEL->setTransform( mGizmo->getTransform() );      
     }
     else
     {
        mConvexSEL->setScale( mGizmo->getScale() );
     }   
  
     if ( mConvexSEL->getClientObject() )
     {
        ConvexShape *clientObj = static_cast< ConvexShape* >( mConvexSEL->getClientObject() );
        clientObj->setTransform( mConvexSEL->getTransform() );
        clientObj->setScale( mConvexSEL->getScale() );
     }      
  }
  
  void GuiConvexEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event)
  {
  
  }
  
  void GuiConvexEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event)
  {
  
  }
  
  bool GuiConvexEditorCtrl::onKeyDown( const GuiEvent &evt )
  {
     bool handled = false;
  
     switch ( evt.keyCode )
     {
     case KEY_ESCAPE:
        handled = handleEscape();      
        break;   
     case KEY_A:
        if ( evt.modifier & SI_ALT )
        {
           GizmoAlignment align = mGizmo->getProfile()->alignment;
           if ( align == World )
              mGizmo->getProfile()->alignment = Object;
           else
              mGizmo->getProfile()->alignment = World;
           handled = true;
        }
        break;
     case KEY_LCONTROL:
        //mCtrlDown = true;
        break;  
     default:
        break;
     }
     
     return handled;
  }
  
  bool GuiConvexEditorCtrl::onKeyUp( const GuiEvent &evt )
  {
     bool handled = false;
  
     switch ( evt.keyCode )
     {      
     case KEY_LCONTROL:
        //mCtrlDown = false;
        break;   
     default:
        break;
     }
  
     return handled;
  }
  
  void GuiConvexEditorCtrl::get3DCursor( GuiCursor *&cursor, 
                                         bool &visible, 
                                         const Gui3DMouseEvent &event_ )
  {
     //cursor = mAddNodeCursor;
     //visible = false;
  
     cursor = NULL;
     visible = false;
  
     GuiCanvas *root = getRoot();
     if ( !root )
        return;
  
     S32 currCursor = PlatformCursorController::curArrow;
  
     if ( root->mCursorChanged == currCursor )
        return;
  
     PlatformWindow *window = root->getPlatformWindow();
     PlatformCursorController *controller = window->getCursorController();
  
     // We've already changed the cursor, 
     // so set it back before we change it again.
     if( root->mCursorChanged != -1)
        controller->popCursor();
  
     // Now change the cursor shape
     controller->pushCursor(currCursor);
     root->mCursorChanged = currCursor;   
  }
  
  void GuiConvexEditorCtrl::updateGizmo()
  {
     mGizmoProfile->restoreDefaultState();
     
     const GizmoMode &mode = mGizmoProfile->mode;
     S32 &flags = mGizmoProfile->flags;
     GizmoAlignment &align = mGizmoProfile->alignment;
  
     U8 keys = Input::getModifierKeys();
  
     mCtrlDown = keys & ( SI_LCTRL | SI_LSHIFT );
  
     bool altDown = keys & ( SI_LALT );
  
     if ( altDown )
     {
        flags = 0;
        return;
     }
  
     if ( mFaceSEL != -1 )
     {
        align = Object;    
        flags |= GizmoProfile::CanRotateUniform;
        flags &= ~GizmoProfile::CanRotateScreen;
     }
     else
     {
        flags &= ~GizmoProfile::CanRotateUniform;
        flags |= GizmoProfile::CanRotateScreen;
     }
  
     if ( mFaceSEL != -1 && mode == ScaleMode )
        flags &= ~GizmoProfile::CanScaleZ;
     else
        flags |= GizmoProfile::CanScaleZ;         
  
     if ( mFaceSEL != -1 && mode == MoveMode )
     {
        if ( mCtrlDown )  
           //hijacking the CTRL modifier for texture offsetting control
           //flags &= ~( GizmoProfile::CanTranslateX | GizmoProfile::CanTranslateY | GizmoProfile::PlanarHandlesOn ); 
           flags &= ~GizmoProfile::CanScaleZ;
        else      
           flags |= ( GizmoProfile::CanTranslateX | GizmoProfile::CanTranslateY | GizmoProfile::PlanarHandlesOn );      
     }
  }
  
  void GuiConvexEditorCtrl::renderScene(const RectI & updateRect)
  {      
  	// Synch selected ConvexShape with the WorldEditor.
  
  	WorldEditor *wedit;
  	if ( Sim::findObject( "EWorldEditor", wedit) )
  	{
  		S32 count = wedit->getSelectionSize();
  
  		if ( !mConvexSEL && count != 0 )
  			wedit->clearSelection();
  		else if ( mConvexSEL && count != 1 )
  		{
  			wedit->clearSelection();
  			wedit->selectObject( mConvexSEL->getIdString() );
  		}
  		else if ( mConvexSEL && count == 1 )
  		{
  			if ( wedit->getSelectObject(0) != mConvexSEL->getId() )
  			{
  				wedit->clearSelection();
  				wedit->selectObject( mConvexSEL->getIdString() );
  			}
  		}
  	}   
  
     // Update status bar text.
  
     SimObject *statusbar;
     if ( Sim::findObject( "EditorGuiStatusBar", statusbar ) )
     {
        String text( "Sketch Tool." );      
        GizmoMode mode = mGizmo->getMode();
  
        if ( mMouseDown && mGizmo->getSelection() != Gizmo::None && mConvexSEL )
        {
           Point3F delta;
           String qualifier;
  
           if ( mode == RotateMode )   
           {
              if ( mSettingPivot )            
                 delta = mGizmo->getPosition() - mSavedPivotPos;
              else
                 delta = mGizmo->getDeltaTotalRot();         
           }
           else if ( mode == MoveMode )         
              delta = mGizmo->getTotalOffset();         
           else if ( mode == ScaleMode )
              delta = mGizmo->getDeltaTotalScale();            
           
           if ( mGizmo->getAlignment() == Object && 
                mode != ScaleMode )
           {            
              mConvexSEL->mWorldToObj.mulV( delta );            
              if ( mFaceSEL != -1 && mode != RotateMode )
              {
                 MatrixF objToSurf( mConvexSEL->mSurfaces[ mFaceSEL ] );
                 objToSurf.scale( mConvexSEL->getScale() );
                 objToSurf.inverse();
                 objToSurf.mulV( delta );
              }
           }
  
           if ( mIsZero( delta.x, 0.0001f ) )
              delta.x = 0.0f;
           if ( mIsZero( delta.y, 0.0001f ) )
              delta.y = 0.0f;
           if ( mIsZero( delta.z, 0.0001f ) )
              delta.z = 0.0f;
           
           if ( mode == RotateMode )         
           {
              if ( mSettingPivot )            
                 text = String::ToString( "Delta position ( x: %4.2f, y: %4.2f, z: %4.2f ).", delta.x, delta.y, delta.z ); 
              else
              {
                 delta.x = mRadToDeg( delta.x );
                 delta.y = mRadToDeg( delta.y );
                 delta.z = mRadToDeg( delta.z );
                 text = String::ToString( "Delta angle ( x: %4.2f, y: %4.2f, z: %4.2f ).", delta.x, delta.y, delta.z ); 
              }
           }
           else if ( mode == MoveMode )     
              text = String::ToString( "Delta position ( x: %4.2f, y: %4.2f, z: %4.2f ).", delta.x, delta.y, delta.z ); 
           else if ( mode == ScaleMode )
              text = String::ToString( "Delta scale ( x: %4.2f, y: %4.2f, z: %4.2f ).", delta.x, delta.y, delta.z ); 
        }
        else 
        {     
           if ( !mConvexSEL )
              text = "Sketch Tool.  ALT + Click-Drag to create a new ConvexShape.";
           else if ( mFaceSEL == -1 )
           {
              if ( mode == MoveMode )            
                 text = "Move selection.  SHIFT while dragging duplicates objects.";
              else if ( mode == RotateMode )            
                 text = "Rotate selection.";
              else if ( mode == ScaleMode )            
                 text = "Scale selection.";        
           }
           else 
           {
              if ( mode == MoveMode )            
                 text = "Move face.  SHIFT while beginning a drag EXTRUDES a new convex. Press CTRL for alternate translation mode.";
              else if ( mode == RotateMode )            
                 text = "Rotate face.  Gizmo/Pivot is draggable. CTRL while dragging splits/folds a new face. SHIFT while dragging extrudes a new convex.";
              else if ( mode == ScaleMode )            
              text = "Scale face.";
           }
        }
     
        // Issue a warning in the status bar
        // if this convex has an excessive number of surfaces...
        if ( mConvexSEL && mConvexSEL->getSurfaces().size() > ConvexShape::smMaxSurfaces )
        {
            text = "WARNING: Reduce the number of surfaces on the selected ConvexShape, only the first 100 will be saved!";
        }
  
        Con::executef( statusbar, "setInfo", text.c_str() );
  
  	Con::executef( statusbar, "setSelectionObjectsByCount", Con::getIntArg( mConvexSEL == NULL ? 0 : 1 ) );
     }   
  
     if ( mActiveTool )
        mActiveTool->renderScene( updateRect );
  
     ColorI colorHL( 255, 50, 255, 255 );
     ColorI colorSEL( 255, 50, 255, 255 );
     ColorI colorNA( 255, 255, 255, 100 );
  
     GFXDrawUtil *drawer = GFX->getDrawUtil();
  
     if ( mConvexSEL && !mDragging )
     {
        if ( mFaceSEL == -1 )
        {
           GFXStateBlockDesc desc;
           desc.setBlend( true );
           desc.setZReadWrite( true, true );
  
           Box3F objBox = mConvexSEL->getObjBox();
           objBox.scale( mConvexSEL->getScale() );
  
           const MatrixF &objMat = mConvexSEL->getTransform();
  
           Point3F boxPos = objBox.getCenter();
           objMat.mulP( boxPos );
           
           drawer->drawObjectBox( desc, objBox.getExtents(), boxPos, objMat, ColorI::WHITE );
        }
        else
        {
           mConvexSEL->renderFaceEdges( -1, colorNA );     
  
           drawFacePlane( mConvexSEL, mFaceSEL );         
        }
  
        if ( mConvexHL == mConvexSEL &&
             mFaceHL != -1 && 
             mFaceHL != mFaceSEL && 
             mGizmo->getSelection() == Gizmo::None )
        {
           mConvexSEL->renderFaceEdges( mFaceHL, colorHL );
        }
     }
  
     if ( mConvexHL && mConvexHL != mConvexSEL )
     {
        mConvexHL->renderFaceEdges( -1 );      
     }
  
     if ( mGizmo->getMode() != RotateMode && mUsingPivot )
     {
        mUsingPivot = false;
        updateGizmoPos();
     }
  
     F32 gizmoAlpha = 1.0f;
  	if ( !mConvexSEL )
  		gizmoAlpha = 0.0f;
  
     if ( mMouseDown && mGizmo->getSelection() != Gizmo::None && mConvexSEL )
     {
        if ( mSettingPivot )
           gizmoAlpha = 1.0f;
        else
           gizmoAlpha = 0.0f;
     }
  
     DebugDrawer::get()->render();
  
     {
        GFXTransformSaver saver;
        // Now draw all the 2d stuff!
        GFX->setClipRect(updateRect); 
  
        if ( mConvexSEL && mFaceSEL != -1 )
        {      
           Vector< Point3F > lineList;
           mConvexSEL->getSurfaceLineList( mFaceSEL, lineList );
  
           MatrixF objToWorld( mConvexSEL->getTransform() );
           objToWorld.scale( mConvexSEL->getScale() );      
  
           for ( S32 i = 0; i < lineList.size(); i++ )     
              objToWorld.mulP( lineList[i] );			
  
           for ( S32 i = 0; i < lineList.size() - 1; i++ )
           {
  			   Point3F p0( lineList[i] );
  			   Point3F p1( lineList[i+1] );
  
  			   drawLine( p0, p1, colorSEL, 3.0f );
           }
  	   }
  
        if ( gizmoAlpha == 1.0f )
        {
           if ( mGizmoProfile->mode != NoneMode )
              mGizmo->renderText( mSaveViewport, mSaveModelview, mSaveProjection );   	
        }
  
        if ( mActiveTool )
           mActiveTool->render2D();
     }
  
     if ( gizmoAlpha == 1.0f )   
        mGizmo->renderGizmo( mLastCameraQuery.cameraMatrix, mLastCameraQuery.fov );
  } 
  
  void GuiConvexEditorCtrl::drawFacePlane( ConvexShape *shape, S32 faceId )
  {
     // Build a vb of the face points ( in world space ) scaled outward in
     // the surface space in x/y with uv coords.
  
     /*
     Vector< Point3F > points;
     Vector< Point2F > coords;
  
     shape->getSurfaceTriangles( faceId, &points, &coords, false );
  
     if ( points.empty() )
        return;
  
     GFXVertexBufferHandle< GFXVertexPCT > vb;
     vb.set( GFX, points.size(), GFXBufferTypeVolatile );
     GFXVertexPCT *vert = vb.lock();
  
     for ( S32 i = 0; i < points.size(); i++ )
     {
        vert->point = points[i];
        vert->color.set( 255, 255, 255, 200 );
        vert->texCoord = coords[i];
        vert++;
     }
  
     vb.unlock();
  
     GFXTransformSaver saver;
     MatrixF renderMat( shape->getTransform() );
     renderMat.scale( shape->getScale() );
     GFX->multWorld( renderMat );
  
     GFXStateBlockDesc desc;
     desc.setBlend( true );
     desc.setCullMode( GFXCullNone );
     desc.setZReadWrite( true, false );
     desc.samplersDefined = true;
     desc.samplers[0] = GFXSamplerStateDesc::getWrapLinear();
     GFX->setStateBlockByDesc( desc );
  
     GFX->setVertexBuffer( vb );
  
     GFXTexHandle tex( "core/art/grids/512_transp", &GFXStaticTextureSRGBProfile, "ConvexEditor_grid" );
     GFX->setTexture( 0, tex );
     GFX->setupGenericShaders();
     GFX->drawPrimitive( GFXTriangleList, 0, points.size() / 3 );
     */
  }
  
  
  void GuiConvexEditorCtrl::scaleFace( ConvexShape *shape, S32 faceId, Point3F scale )
  {
     if ( !mHasGeometry )
     {
        mHasGeometry = true;
        
        mSavedGeometry = shape->mGeometry;
        mSavedSurfaces = shape->mSurfaces;      
     }
     else
     {
        shape->mGeometry = mSavedGeometry;
        shape->mSurfaces = mSavedSurfaces;
     }
  
     if ( shape->mGeometry.faces.size() <= faceId )
        return;
     
     ConvexShape::Face &face = shape->mGeometry.faces[faceId];
  
     Vector< Point3F > &pointList = shape->mGeometry.points;
  
     AssertFatal( shape->mSurfaces[ face.id ].isAffine(), "ConvexShapeEditor - surface not affine." );
        
     Point3F projScale;
     scale.z = 1.0f;
  
     const MatrixF &surfToObj = shape->mSurfaces[ face.id ];
     MatrixF objToSurf( surfToObj );
     objToSurf.inverse();
  
     for ( S32 i = 0; i < face.points.size(); i++ )
     {                  
        Point3F &pnt = pointList[ face.points[i] ];   
  
        objToSurf.mulP( pnt );
        pnt *= scale;
        surfToObj.mulP( pnt );
     }
  
     updateModifiedFace( shape, faceId );
  }
  
  void GuiConvexEditorCtrl::translateFace( ConvexShape *shape, S32 faceId, const Point3F &displace )
  {
     if ( !mHasGeometry )
     {
        mHasGeometry = true;
  
        mSavedGeometry = shape->mGeometry;
        mSavedSurfaces = shape->mSurfaces;      
     }
     else
     {
        shape->mGeometry = mSavedGeometry;
        shape->mSurfaces = mSavedSurfaces;
     }
  
     if ( shape->mGeometry.faces.size() <= faceId )
        return;
  
     ConvexShape::Face &face = shape->mGeometry.faces[faceId];
  
     Vector< Point3F > &pointList = shape->mGeometry.points;
  
     AssertFatal( shape->mSurfaces[ face.id ].isAffine(), "ConvexShapeEditor - surface not affine." );
  
     Point3F modDisplace = Point3F(displace.x, displace.y, displace.z);
  
     //snapping
     if (mGridSnap && mGridPlaneSize != 0)
     {
        Point3F faceCenter = Point3F::Zero;
  
        for (S32 i = 0; i < face.points.size(); i++)
        {
           Point3F &pnt = pointList[face.points[i]];
           faceCenter += pnt;
        }
  
        faceCenter /= face.points.size();
  
        // Transform displacement into object space.    
        MatrixF objToWorld(shape->getWorldTransform());
        objToWorld.scale(shape->getScale());
        objToWorld.inverse();
  
        objToWorld.mulP(faceCenter);
  
        modDisplace = faceCenter + displace;
        Point3F fMod = Point3F::Zero;
  
        if (!mIsZero(displace.x))
           fMod.x = mFmod(modDisplace.x - (displace.x > 0 ? mGridPlaneSize : -mGridPlaneSize), mGridPlaneSize);
  
        if (!mIsZero(displace.y))
           fMod.y = mFmod(modDisplace.y - (displace.y > 0 ? mGridPlaneSize : -mGridPlaneSize), mGridPlaneSize);
  
        if (!mIsZero(displace.z))
           fMod.z = mFmod(modDisplace.z - (displace.z > 0 ? mGridPlaneSize : -mGridPlaneSize), mGridPlaneSize);
  
        modDisplace -= fMod;
        modDisplace -= faceCenter;
     }
  
     // Transform displacement into object space.    
     MatrixF worldToObj( shape->getTransform() );
     worldToObj.scale( shape->getScale() );
     worldToObj.inverse();
  
     Point3F displaceOS;
     worldToObj.mulV(modDisplace, &displaceOS);
  
     for ( S32 i = 0; i < face.points.size(); i++ )
     {                  
        Point3F &pnt = pointList[ face.points[i] ];   
        pnt += displaceOS;      
     }
  
     updateModifiedFace( shape, faceId );
  }
  
  void GuiConvexEditorCtrl::updateModifiedFace( ConvexShape *shape, S32 faceId )
  {
     if ( shape->mGeometry.faces.size() <= faceId )
        return;
  
     ConvexShape::Face &face = shape->mGeometry.faces[faceId];
  
     Vector< Point3F > &pointList = shape->mGeometry.points;
  
     Vector< ConvexShape::Face > &faceList = shape->mGeometry.faces;
  
     for ( S32 i = 0; i < faceList.size(); i++ )
     {
        ConvexShape::Face &curFace = faceList[i];      
        MatrixF &curSurface = shape->mSurfaces[ curFace.id ];
  
        U32 curPntCount = curFace.points.size();
  
        if ( curPntCount < 3 )
           continue;
  
        // Does this face use any of the points which we have modified?
        // Collect them in correct winding order.
  
        S32 pId0 = -1;
  
        for ( S32 j = 0; j < curFace.winding.size(); j++ )
        {
           if ( face.points.contains( curFace.points[ curFace.winding[ j ] ] ) )
           {
              pId0 = j;
              break;
           }
        }         
  
        if ( pId0 == -1 )
           continue;
  
        S32 pId1 = -1, pId2 = -1;
  
        pId1 = ( pId0 + 1 ) % curFace.winding.size();
        pId2 = ( pId0 + 2 ) % curFace.winding.size();
  
        const Point3F &p0 = pointList[ curFace.points[ curFace.winding[ pId0 ] ] ];
        const Point3F &p1 = pointList[ curFace.points[ curFace.winding[ pId1 ] ] ];
        const Point3F &p2 = pointList[ curFace.points[ curFace.winding[ pId2 ] ] ];
  
        PlaneF newPlane( p0, p1, p2 );
        Point3F uvec = newPlane.getNormal();
        Point3F fvec = curSurface.getForwardVector();
        Point3F rvec = curSurface.getRightVector();
  
        F32 dt0 = mDot( uvec, fvec );
        F32 dt1 = mDot( uvec, rvec );
  
        if ( mFabs( dt0 ) < mFabs( dt1 ) )
        {
           rvec = mCross( fvec, uvec );
           rvec.normalizeSafe();
           fvec = mCross( uvec, rvec );
           fvec.normalizeSafe();
        }
        else
        {
           fvec = mCross( uvec, rvec );
           fvec.normalizeSafe();
           rvec = mCross( fvec, uvec );
           rvec.normalizeSafe();
        }
  
        curSurface.setColumn( 0, rvec );
        curSurface.setColumn( 1, fvec );
        curSurface.setColumn( 2, uvec );   
        curSurface.setPosition( newPlane.getPosition() );
     }
  
     updateShape( shape );
  }
  
  bool GuiConvexEditorCtrl::isShapeValid( ConvexShape *shape )
  {
     // Test for no-geometry.
     if ( shape->mGeometry.points.empty() )
        return false;
  
     const Vector<Point3F> &pointList = shape->mGeometry.points;
     const Vector<ConvexShape::Face> &faceList = shape->mGeometry.faces;
  
     // Test that all points are shared by at least 3 faces.
  
     for ( S32 i = 0; i < pointList.size(); i++ )
     {
        U32 counter = 0;
  
        for ( S32 j = 0; j < faceList.size(); j++ )
        {
           if ( faceList[j].points.contains( i ) )
              counter++;
        }
  
        if ( counter < 3 )
           return false;
     }
  
     // Test for co-planar faces.
     for ( S32 i = 0; i < shape->mPlanes.size(); i++ )
     {
        for ( S32 j = i + 1; j < shape->mPlanes.size(); j++ )
        {
           F32 d = mDot( shape->mPlanes[i], shape->mPlanes[j] );
           if ( d > 0.999f )         
              return false;         
        }
     }
  
     // Test for faces with zero or negative area.
     for ( S32 i = 0; i < shape->mGeometry.faces.size(); i++ )
     {
        if ( shape->mGeometry.faces[i].area < 0.0f )
           return false;
  
        if ( shape->mGeometry.faces[i].triangles.empty() )
           return false;
     }
  
     return true;
  }
  
  void GuiConvexEditorCtrl::setupShape( ConvexShape *shape )
  {
     shape->registerObject();
     shape->_setMaterial(mMaterialName);
     updateShape( shape );
  
     Scene* scene = Scene::getRootScene();
     if ( scene )
        scene->addObject( shape );
  }
  
  void GuiConvexEditorCtrl::updateShape( ConvexShape *shape, S32 offsetFace )
  {
     shape->_updateGeometry( true );
  
     /*
     if ( offsetFace != -1 )
     {
        shape->mSurfaces[ offsetFace ].setPosition( mPivotPos );
     }*/
  
     synchClientObject( shape );
  }
  
  void GuiConvexEditorCtrl::synchClientObject( const ConvexShape *serverConvex )
  {
     if ( serverConvex->getClientObject() )
     {
        ConvexShape *clientConvex = static_cast< ConvexShape* >( serverConvex->getClientObject() );
        clientConvex->setScale( serverConvex->getScale() );
        clientConvex->setTransform( serverConvex->getTransform() );
        clientConvex->mSurfaces.clear();
        clientConvex->mSurfaces.merge( serverConvex->mSurfaces );
        clientConvex->_updateGeometry(true);
     }
  }
  
  void GuiConvexEditorCtrl::updateGizmoPos()
  {
     if ( mConvexSEL )
     {
        if ( mFaceSEL != -1 )
        {
           MatrixF surfMat = mConvexSEL->getSurfaceWorldMat( mFaceSEL );  
  
           MatrixF objToWorld( mConvexSEL->getTransform() );
           objToWorld.scale( mConvexSEL->getScale() );
  
           Point3F gizmoPos(0,0,0);
  
           if ( mUsingPivot )
           {
              gizmoPos = mPivotPos;
           }
           else
           {
              Point3F faceCenterPnt = mConvexSEL->mSurfaces[ mFaceSEL ].getPosition();
              objToWorld.mulP( faceCenterPnt );
  
              mGizmoMatOffset = surfMat.getPosition() - faceCenterPnt;
  
              gizmoPos = faceCenterPnt;
           }
  
           mGizmo->set( surfMat, gizmoPos, Point3F::One );        
        }
        else
        {
           mGizmoMatOffset = Point3F::Zero;
           mGizmo->set( mConvexSEL->getTransform(), mConvexSEL->getPosition(), mConvexSEL->getScale() ); 
        }
     }   
  }
  
  bool GuiConvexEditorCtrl::setActiveTool( ConvexEditorTool *tool )
  {   
     if ( mActiveTool == tool )
        return false;
  
     ConvexEditorTool *prevTool = mActiveTool;
     ConvexEditorTool *newTool = tool;
  
     if ( prevTool )
        prevTool->onDeactivated( newTool );
  
     mActiveTool = newTool;
  
     if ( newTool )
        newTool->onActivated( prevTool );
  
     return true;
  }
  
  bool GuiConvexEditorCtrl::handleEscape()
  {
     if ( mActiveTool )
     {
        mActiveTool->onDeactivated( NULL );
        mActiveTool = NULL;
  
        return true;
     }
  
     if ( mFaceSEL != -1 )
     {
        setSelection( mConvexSEL, -1 );
        return true;
     }
  
     if ( mConvexSEL )
     {         
        setSelection( NULL, -1 );
        return true;
     }
  
     return false;
  }
  
  bool GuiConvexEditorCtrl::handleDelete()
  {
     if ( mActiveTool )
     {
        mActiveTool->onDeactivated( NULL );
        mActiveTool = NULL;
     }
  
     if ( mConvexSEL )
     {
        if ( mFaceSEL != -1 )
        {
           submitUndo( ModifyShape, mConvexSEL );
  
           mConvexSEL->mSurfaces.erase_fast( mFaceSEL );
           updateShape( mConvexSEL );
  
           if ( !isShapeValid( mConvexSEL ) )
           {
              S32 selFace = mFaceSEL;
              mLastUndo->undo();
              mFaceSEL = selFace;
              updateShape( mConvexSEL );
              updateGizmoPos();
           }
           else
           {
              setSelection( mConvexSEL, -1 );
           }
        }
        else
        {
           // Grab the mission editor undo manager.
           UndoManager *undoMan = NULL;
           if ( !Sim::findObject( "EUndoManager", undoMan ) )
           {            
              Con::errorf( "GuiConvexEditorCtrl::on3DMouseDown() - EUndoManager not found!" );    
           }
           else
           {
              // Create the UndoAction.
              MEDeleteUndoAction *action = new MEDeleteUndoAction("Deleted ConvexShape");
              action->deleteObject( mConvexSEL );
              mIsDirty = true;
              
              mFaceHL = -1; 
  
              setSelection( NULL, -1 );
  
              // Submit it.               
              undoMan->addAction( action );
           }
        }
     }
  
     return true;
  }
  
  bool GuiConvexEditorCtrl::hasSelection() const
  {
     return mConvexSEL != NULL;   
  }
  
  void GuiConvexEditorCtrl::clearSelection()
  {
     mFaceHL = -1;
     mConvexHL = NULL;
     setSelection( NULL, -1 );
  }
  
  void GuiConvexEditorCtrl::handleDeselect()
  {
     if ( mActiveTool )
     {
        mActiveTool->onDeactivated( NULL );
        mActiveTool = NULL;
     }
  
     mFaceHL = -1;
     mConvexHL = NULL;
     setSelection( NULL, -1 );
  }
  
  void GuiConvexEditorCtrl::setSelection( ConvexShape *shape, S32 faceId )
  {
     mFaceSEL = faceId;
     mConvexSEL = shape;
     updateGizmoPos();
  
     Con::executef( this, "onSelectionChanged", shape ? shape->getIdString() : "", Con::getIntArg(faceId) );
  }
  
  void GuiConvexEditorCtrl::_prepRenderImage( SceneManager* sceneGraph, const SceneRenderState* state )
  {   
     if ( !isAwake() )
        return;
  
     /*
     ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
     ri->type = RenderPassManager::RIT_Editor;
     ri->renderDelegate.bind( this, &GuiConvexEditorCtrl::_renderObject );
     ri->defaultKey = 100;
     state->getRenderPass()->addInst( ri );   
     */
  }
  
  void GuiConvexEditorCtrl::_renderObject( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *matInst )
  {  
  }
  
  void GuiConvexEditorCtrl::submitUndo( UndoType type, ConvexShape *shape )
  {
     Vector< ConvexShape* > shapes;
     shapes.push_back( shape );
     submitUndo( type, shapes );
  }
  
  void GuiConvexEditorCtrl::submitUndo( UndoType type, const Vector<ConvexShape*> &shapes )
  {   
     // Grab the mission editor undo manager.
     Sim::findObject( "EUndoManager", mUndoManager );   
     
     if ( !mUndoManager )   
     {
        Con::errorf( "GuiConvexEditorCtrl::submitUndo() - EUndoManager not found!" );
        return;           
     }
  
     if ( type == ModifyShape )
     {
        // Setup the action.
        GuiConvexEditorUndoAction *action = new GuiConvexEditorUndoAction( "Modified a ConvexShape" );
  
        ConvexShape *shape = shapes.first();
  
        action->mObjId = shape->getId();   
        action->mEditor = this;   
        action->mSavedObjToWorld = shape->getTransform();
        action->mSavedScale = shape->getScale();
        action->mSavedSurfaces.merge( shape->mSurfaces );             
        action->mUndoManager = mUndoManager;
  
        mUndoManager->addAction( action );
  
        mLastUndo = action;
     }
     else if ( type == CreateShape )
     {
        MECreateUndoAction *action = new MECreateUndoAction( "Create ConvexShape" );
  
        for ( S32 i = 0; i < shapes.size(); i++ )
           action->addObject( shapes[i] );
           
        mUndoManager->addAction( action );
        
        mLastUndo = action;
     }
     else if ( type == DeleteShape )
     {
        MEDeleteUndoAction *action = new MEDeleteUndoAction( "Deleted ConvexShape" );
  
        for ( S32 i = 0; i < shapes.size(); i++ )
           action->deleteObject( shapes[i] );         
  
        mUndoManager->addAction( action );
  
        mLastUndo = action;
     }
     else if ( type == HollowShape )
     {
        CompoundUndoAction *action = new CompoundUndoAction( "Hollow ConvexShape" );
  
        MECreateUndoAction *createAction = new MECreateUndoAction();
        MEDeleteUndoAction *deleteAction = new MEDeleteUndoAction();
  
        deleteAction->deleteObject( shapes.first() );
        
        for ( S32 i = 1; i < shapes.size(); i++ )      
           createAction->addObject( shapes[i] );
        
        action->addAction( deleteAction );
        action->addAction( createAction );
  
        mUndoManager->addAction( action );
  
        mLastUndo = action;
     }
  
  	mIsDirty = true;
  }
  
  bool GuiConvexEditorCtrl::_cursorCastCallback( RayInfo* ri )
  {
     // Reject anything that's not a ConvexShape.
     return dynamic_cast< ConvexShape* >( ri->object );
  }
  
  bool GuiConvexEditorCtrl::_cursorCast( const Gui3DMouseEvent &event, ConvexShape **hitShape, S32 *hitFace )
  {
     RayInfo ri;
     
     if ( gServerContainer.castRay( event.pos, event.pos + event.vec * 10000.0f, StaticShapeObjectType, &ri, &GuiConvexEditorCtrl::_cursorCastCallback ) &&
          dynamic_cast< ConvexShape* >( ri.object ) )
     {
        // Do not select or edit ConvexShapes that are within a Prefab.
        if ( Prefab::getPrefabByChild( ri.object ) )
           return false;
  
        *hitShape = static_cast< ConvexShape* >( ri.object );
        *hitFace = ri.face;
        mLastRayInfo = ri;
  
        return true;
     }
  
     return false;
  }
  
  void GuiConvexEditorCtrl::setPivotPos( ConvexShape *shape, S32 faceId, const Gui3DMouseEvent &event )
  {
     PlaneF plane;
     mTransformPlane( shape->getTransform(), shape->getScale(), shape->mPlanes[ faceId ], &plane );
  
     Point3F start( event.pos );
     Point3F end( start + event.vec * 10000.0f );
  
     F32 t = plane.intersect( start, end );
  
     if ( t >= 0.0f && t <= 1.0f )
     {
        Point3F hitPos;
        hitPos.interpolate( start, end, t );
  
        mPivotPos = hitPos;
        mUsingPivot = true;
  
        MatrixF worldToObj( shape->getTransform() );
        worldToObj.scale( shape->getScale() );
        worldToObj.inverse();
  
        Point3F objPivotPos( mPivotPos );
        worldToObj.mulP( objPivotPos );
  
        updateGizmoPos();
     }
  }
  
  void GuiConvexEditorCtrl::cleanMatrix( MatrixF &mat )
  {
     if ( mat.isAffine() )
        return;
  
     VectorF col0 = mat.getColumn3F(0);
     VectorF col1 = mat.getColumn3F(1);
     VectorF col2 = mat.getColumn3F(2);
  
     col0.normalize();
     col1.normalize();
     col2.normalize();
  
     col2 = mCross( col0, col1 );
     col2.normalize();
     col1 = mCross( col2, col0 );
     col1.normalize();
     col0 = mCross( col1, col2 );
     col0.normalize();
  
     mat.setColumn(0,col0);
     mat.setColumn(1,col1);
     mat.setColumn(2,col2);
  
     AssertFatal( mat.isAffine(), "GuiConvexEditorCtrl::cleanMatrix, non-affine matrix" );
  }
  
  S32 GuiConvexEditorCtrl::getEdgeByPoints( ConvexShape *shape, S32 faceId, S32 p0, S32 p1 )
  {
     const ConvexShape::Face &face = shape->mGeometry.faces[faceId];
  
     for ( S32 i = 0; i < face.edges.size(); i++ )
     {
        const ConvexShape::Edge &edge = face.edges[i];
  
        if ( edge.p0 != p0 && edge.p0 != p1 )
           continue;
        if ( edge.p1 != p0 && edge.p1 != p1 )
           continue;
  
        return i;      
     }
  
     return -1;
  }
  
  bool GuiConvexEditorCtrl::getEdgesTouchingPoint( ConvexShape *shape, S32 faceId, S32 pId, Vector< U32 > &edgeIdxList, S32 excludeEdge )
  {
     const ConvexShape::Face &face = shape->mGeometry.faces[faceId];   
     const Vector< ConvexShape::Edge > &edgeList = face.edges;
  
     for ( S32 i = 0; i < edgeList.size(); i++ )
     {
        if ( i == excludeEdge )
           continue;
  
        const ConvexShape::Edge &curEdge = edgeList[i];
  
        if ( curEdge.p0 == pId || curEdge.p1 == pId )      
           edgeIdxList.push_back(i);
     }
  
     return !edgeIdxList.empty();
  }
  
  Point2F GuiConvexEditorCtrl::getSelectedFaceUVOffset()
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return Point2F(0, 0);
  
     return mConvexSEL->mSurfaceUVs[mFaceSEL].offset;
  }
  
  Point2F GuiConvexEditorCtrl::getSelectedFaceUVScale()
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return Point2F(0, 0);
  
     return mConvexSEL->mSurfaceUVs[mFaceSEL].scale;
  }
  
  const char* GuiConvexEditorCtrl::getSelectedFaceMaterial()
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return "";
  
     if (mConvexSEL->mSurfaceUVs[mFaceSEL].matID == 0)
     {
        return mConvexSEL->getMaterial();
     }
     else
     {
        return mConvexSEL->mSurfaceTextures[mConvexSEL->mSurfaceUVs[mFaceSEL].matID - 1].getMaterial();
     }
  }
  
  bool GuiConvexEditorCtrl::getSelectedFaceHorzFlip()
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return false;
  
     return mConvexSEL->mSurfaceUVs[mFaceSEL].horzFlip;
  }
  
  bool GuiConvexEditorCtrl::getSelectedFaceVertFlip()
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return false;
  
     return mConvexSEL->mSurfaceUVs[mFaceSEL].vertFlip;
  }
  
  float GuiConvexEditorCtrl::getSelectedFaceZRot()
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return false;
  
     return mConvexSEL->mSurfaceUVs[mFaceSEL].zRot;
  }
  
  void GuiConvexEditorCtrl::setSelectedFaceUVOffset(Point2F offset)
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return;
  
     mConvexSEL->mSurfaceUVs[mFaceSEL].offset = offset;
  
     mConvexSEL->setMaskBits(ConvexShape::UpdateMask);
  }
  
  void GuiConvexEditorCtrl::setSelectedFaceUVScale(Point2F scale)
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return;
  
     mConvexSEL->mSurfaceUVs[mFaceSEL].scale = scale;
  
     mConvexSEL->setMaskBits(ConvexShape::UpdateMask);
  }
  
  void GuiConvexEditorCtrl::setSelectedFaceMaterial(const char* materialName)
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return;
  
     if (mConvexSEL->mSurfaceUVs.size() < mFaceSEL)
        return;
  
     //first, see if the mat already exists in our list
     bool found = false;
     U32 oldmatID = mConvexSEL->mSurfaceUVs[mFaceSEL].matID;
  
     if (String::compare(materialName, mConvexSEL->getMaterialName().c_str()))
     {
        for (U32 i = 0; i < mConvexSEL->mSurfaceTextures.size(); i++)
        {
           if (!String::compare(mConvexSEL->mSurfaceTextures[i].getMaterial(), materialName))
           {
              //found a match
              mConvexSEL->mSurfaceUVs[mFaceSEL].matID = i + 1;
              found = true;
           }
        }
  
        if (!found)
        {
           //add a new one
           ConvexShape::surfaceMaterial newMat;
           newMat._setMaterial(materialName);
  
           mConvexSEL->mSurfaceTextures.push_back(newMat);
  
           mConvexSEL->mSurfaceUVs[mFaceSEL].matID = mConvexSEL->mSurfaceTextures.size();
        }
     }
     else
     {
        mConvexSEL->mSurfaceUVs[mFaceSEL].matID = 0;
     }
  
     //run through and find out if there are any other faces still using the old mat texture
     if (oldmatID != 0)
     {
        bool used = false;
        for (U32 i = 0; i < mConvexSEL->mSurfaceUVs.size(); i++)
        {
           if (mConvexSEL->mSurfaceUVs[i].matID == oldmatID)
           {
              used = true;
              break;
           }
        }
  
        if (!used)
        {
           //that was the last reference, so let's update the listings on the shape
           if (mConvexSEL->mSurfaceTextures[oldmatID - 1].materialInst)
              SAFE_DELETE(mConvexSEL->mSurfaceTextures[oldmatID - 1].materialInst);
  
           mConvexSEL->mSurfaceTextures.erase(oldmatID-1);
  
           for (U32 i = 0; i < mConvexSEL->mSurfaceUVs.size(); i++)
           {
              if (mConvexSEL->mSurfaceUVs[i].matID > oldmatID)
                 mConvexSEL->mSurfaceUVs[i].matID--;
           }
        }
     }
  
     //mConvexSEL->mSurfaceUVs[mFaceSEL].materialName = materialName;
  
     mConvexSEL->setMaskBits(ConvexShape::UpdateMask);
  }
  
  void GuiConvexEditorCtrl::setSelectedFaceHorzFlip(bool flipped)
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return;
  
     mConvexSEL->mSurfaceUVs[mFaceSEL].horzFlip = flipped;
  
     mConvexSEL->setMaskBits(ConvexShape::UpdateMask);
  }
  
  void GuiConvexEditorCtrl::setSelectedFaceVertFlip(bool flipped)
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return;
  
     mConvexSEL->mSurfaceUVs[mFaceSEL].vertFlip = flipped;
  
     mConvexSEL->setMaskBits(ConvexShape::UpdateMask);
  }
  
  void GuiConvexEditorCtrl::setSelectedFaceZRot(float degrees)
  {
     if (mFaceSEL == -1 || mConvexSEL == NULL)
        return;
  
     F32 oldRot = mDegToRad(mConvexSEL->mSurfaceUVs[mFaceSEL].zRot);
     mConvexSEL->mSurfaceUVs[mFaceSEL].zRot = degrees;
  
     EulerF curEul = mConvexSEL->mSurfaces[mFaceSEL].toEuler();
  
     MatrixF oldRotMat = MatrixF(EulerF(0, 0, -oldRot));
  
     mConvexSEL->mSurfaces[mFaceSEL].mul(oldRotMat);
  
     MatrixF newRotMat = MatrixF(EulerF(0, 0, mDegToRad(mConvexSEL->mSurfaceUVs[mFaceSEL].zRot)));
  
     mConvexSEL->mSurfaces[mFaceSEL].mul(newRotMat);
  
     //Point3F curPos = mConvexSEL->mSurfaces[mFaceSEL].getPosition();
  
     //we roll back our existing modified rotation before setting the new one, just to keep it consistent
     //const MatrixF &gMat = MatrixF(EulerF(curEul.x, curEul.y, curEul.z - oldRot + mDegToRad(mConvexSEL->mSurfaceUVs[mFaceSEL].zRot)), curPos);
  
     //mConvexSEL->mSurfaces[mFaceSEL] = MatrixF(EulerF(curEul.x, curEul.y, mDegToRad(mConvexSEL->mSurfaceUVs[mFaceSEL].zRot)), curPos);
  
     /*MatrixF surfMat;
     surfMat.mul(mConvexSEL->mWorldToObj, gMat);
  
     MatrixF worldToObj(mConvexSEL->getTransform());
     worldToObj.scale(mConvexSEL->getScale());
     worldToObj.inverse();
  
     Point3F newPos;
     newPos = gMat.getPosition();
  
     worldToObj.mulP(newPos);
     surfMat.setPosition(newPos);
  
     // Clear out floating point errors.
     cleanMatrix(surfMat);
  
     mConvexSEL->mSurfaces[mFaceSEL] = surfMat;*/
  
     updateShape(mConvexSEL, mFaceSEL);
  
     mConvexSEL->setMaskBits(ConvexShape::UpdateMask);
  
     /*
     const MatrixF &gMat = mGizmo->getTransform();
     MatrixF surfMat;
     surfMat.mul( mConvexSEL->mWorldToObj, gMat );
  
     MatrixF worldToObj ( mConvexSEL->getTransform() );
     worldToObj.scale( mConvexSEL->getScale() );
     worldToObj.inverse();
  
     Point3F newPos;
     newPos = gMat.getPosition();
  
     worldToObj.mulP( newPos );
     surfMat.setPosition( newPos );
  
     // Clear out floating point errors.
     cleanMatrix( surfMat );
  
     if (mGizmo->getSelection() == Gizmo::Axis_Z)
     {
     MatrixF curSurfMat = mConvexSEL->mSurfaces[mFaceSEL];
     EulerF curSufRot = curSurfMat.toEuler();
  
     EulerF newSufRot = surfMat.toEuler();
  
     float zRot = mRadToDeg(newSufRot.z - curSufRot.z);
  
     float curZRot = mConvexSEL->mSurfaceTextures[mFaceSEL].zRot;
  
     mConvexSEL->mSurfaceTextures[mFaceSEL].zRot += zRot;
     }
  
     mConvexSEL->mSurfaces[mFaceSEL] = surfMat;
  
     updateShape( mConvexSEL, mFaceSEL );
     */
  }
  
  void GuiConvexEditorCtrl::toggleGridSnapping()
  {
     if (mGridSnap)
        mGridSnap = false;
     else
        mGridSnap = true;
  }
  
  void GuiConvexEditorCtrl::updateShape()
  {
     if (mConvexSEL)
        mConvexSEL->inspectPostApply();
  }
  
  void GuiConvexEditorCtrl::setGridSnapSize(float gridSize)
  {
     mGridPlaneSize = gridSize;
  }
  
  void GuiConvexEditorUndoAction::undo()
  {
     ConvexShape *object = NULL;
     if ( !Sim::findObject( mObjId, object ) )
        return;
  
     // Temporarily save the ConvexShape current data.   
     Vector< MatrixF > tempSurfaces;   
     tempSurfaces.merge( object->mSurfaces );
     MatrixF tempObjToWorld( object->getTransform() );
     Point3F tempScale( object->getScale() );
  
     // Restore the Object to the UndoAction state.
     object->mSurfaces.clear();
     object->mSurfaces.merge( mSavedSurfaces );   
     object->setScale( mSavedScale );
     object->setTransform( mSavedObjToWorld );
  
     // Regenerate the ConvexShape and synch the client object.
     object->_updateGeometry();
     GuiConvexEditorCtrl::synchClientObject( object );
  
     // If applicable set the selected ConvexShape and face
     // on the editor.   
     mEditor->setSelection( object, -1 );
     mEditor->updateGizmoPos();
  
     // Now save the previous ConvexShape data in this UndoAction
     // since an undo action must become a redo action and vice-versa
     
     mSavedObjToWorld = tempObjToWorld;
     mSavedScale = tempScale;
     mSavedSurfaces.clear();
     mSavedSurfaces.merge( tempSurfaces );   
  }
  
  ConvexEditorCreateTool::ConvexEditorCreateTool( GuiConvexEditorCtrl *editor )
   : Parent( editor ),
     mStage( -1 ),
     mNewConvex( NULL )
  {
  }
  
  void ConvexEditorCreateTool::onActivated( ConvexEditorTool *prevTool )
  {
     mEditor->clearSelection();
     mStage = -1;
     mNewConvex = NULL;
  }
  
  void ConvexEditorCreateTool::onDeactivated( ConvexEditorTool *newTool )
  {
     if ( mNewConvex )
        mNewConvex->deleteObject();
  
     mStage = -1;
     mNewConvex = NULL;
     mEditor->mouseUnlock();
  }
  
  ConvexEditorTool::EventResult ConvexEditorCreateTool::on3DMouseDown( const Gui3DMouseEvent &event )
  {
     if ( mStage == -1 )
     {
        mEditor->setFirstResponder();
        mEditor->mouseLock();
  
        Point3F start( event.pos );
        Point3F end( event.pos + event.vec * 10000.0f );      
        RayInfo ri;
        
        bool hit = gServerContainer.castRay( event.pos, end, STATIC_COLLISION_TYPEMASK, &ri );
  
        MatrixF objMat( true );
  
        // Calculate the orientation matrix of the new ConvexShape
        // based on what has been clicked.
  
        if ( !hit )
        {
           objMat.setPosition( event.pos + event.vec * 100.0f );      
        }
        else
        {
           if ( dynamic_cast< ConvexShape* >( ri.object ) )
           {
              ConvexShape *hitShape = static_cast< ConvexShape* >( ri.object );
              objMat = hitShape->getSurfaceWorldMat( ri.face );
              objMat.setPosition( ri.point );
           }
           else
           {
              Point3F rvec;
              Point3F fvec( mEditor->getCameraMat().getForwardVector() );
              Point3F uvec( ri.normal );
  
              rvec = mCross( fvec, uvec );
  
              if ( rvec.isZero() )
              {
                 fvec = mEditor->getCameraMat().getRightVector();
                 rvec = mCross( fvec, uvec );
              }
  
              rvec.normalizeSafe();
              fvec = mCross( uvec, rvec );
              fvec.normalizeSafe();
              uvec = mCross( rvec, fvec );
              uvec.normalizeSafe();
  
              objMat.setColumn( 0, rvec );
              objMat.setColumn( 1, fvec );
              objMat.setColumn( 2, uvec );
  
              objMat.setPosition( ri.point );
           }
        }
  
        mNewConvex = new ConvexShape();                       
  
        mNewConvex->setTransform( objMat );   
  		
        mNewConvex->registerObject();
  
        mNewConvex->_setMaterial(Parent::mEditor->mMaterialName);
  
        mPlaneSizes.set( 0.1f, 0.1f, 0.1f );
        mNewConvex->resizePlanes( mPlaneSizes );
        mEditor->updateShape( mNewConvex );
        
        mTransform = objMat;     
  
        mCreatePlane.set( objMat.getPosition(), objMat.getUpVector() );
     }
     else if ( mStage == 0 )
     {
        // Handle this on mouseUp
     }
     
     return Handled;
  }
  
  ConvexEditorTool::EventResult ConvexEditorCreateTool::on3DMouseUp( const Gui3DMouseEvent &event )
  {
     if ( mNewConvex && mStage == -1 )
     {
        mStage = 0;      
  
        mCreatePlane = PlaneF( mNewConvex->getPosition(), mNewConvex->getTransform().getForwardVector() );
  
        mTransform.setPosition( mNewConvex->getPosition() );      
  
        return Handled;
     }
     else if ( mStage == 0 )
     {
        SimGroup *scene = Scene::getRootScene();
        scene->addObject( mNewConvex );
  
        mStage = -1;
  
        // Grab the mission editor undo manager.
        UndoManager *undoMan = NULL;
        if ( !Sim::findObject( "EUndoManager", undoMan ) )
        {
           Con::errorf( "ConvexEditorCreateTool::on3DMouseDown() - EUndoManager not found!" );
           mNewConvex = NULL;
           return Failed;           
        }
  
        // Create the UndoAction.
        MECreateUndoAction *action = new MECreateUndoAction("Create ConvexShape");
        action->addObject( mNewConvex );
  
        // Submit it.               
        undoMan->addAction( action );
  
        mEditor->setField( "isDirty", "1" );
  
        mEditor->setSelection( mNewConvex, -1 );      
  
        mNewConvex = NULL;
  
        mEditor->mouseUnlock();
  
        return Done;
     }
  
     return Done;
  }
  
  ConvexEditorTool::EventResult ConvexEditorCreateTool::on3DMouseMove( const Gui3DMouseEvent &event )
  {
     if ( mStage == 0 )
     {
        Point3F start( event.pos );
        Point3F end( start + event.vec * 10000.0f );
        
        F32 t = mCreatePlane.intersect( start, end );
  
        Point3F hitPos;
  
        if ( t < 0.0f || t > 1.0f )
           return Handled;
  
        hitPos.interpolate( start, end, t );      
  
        MatrixF worldToObj( mTransform );
        worldToObj.inverse();
        worldToObj.mulP( hitPos );
  
        F32 delta = ( hitPos.z );
  
        mPlaneSizes.z = getMax( 0.1f, delta );
  
        mNewConvex->resizePlanes( mPlaneSizes );
  
        mEditor->updateShape( mNewConvex );
  
        Point3F pos( mTransform.getPosition() );
        pos += mPlaneSizes.z * 0.5f * mTransform.getUpVector();
        mNewConvex->setPosition( pos );
     }
  
     return Handled;
  }
  
  ConvexEditorTool::EventResult ConvexEditorCreateTool::on3DMouseDragged( const Gui3DMouseEvent &event )
  {
     if ( !mNewConvex || mStage != -1 )
        return Handled;
  
     Point3F start( event.pos );
     Point3F end( event.pos + event.vec * 10000.0f );
  
     F32 t = mCreatePlane.intersect( start, end );
  
     if ( t < 0.0f || t > 1.0f )
        return Handled;
  
     Point3F hitPos;
     hitPos.interpolate( start, end, t );
     
     MatrixF xfm( mTransform );
     xfm.inverse();      
     xfm.mulP( hitPos);      
     
     Point3F scale;
     scale.x = getMax( mFabs( hitPos.x ), 0.1f );
     scale.y = getMax( mFabs( hitPos.y ), 0.1f );
     scale.z = 0.1f;
  
     mNewConvex->resizePlanes( scale );
     mPlaneSizes = scale;
     mEditor->updateShape( mNewConvex );   
  
     Point3F pos( mTransform.getPosition() );
     pos += mTransform.getRightVector() * hitPos.x * 0.5f;
     pos += mTransform.getForwardVector() * hitPos.y * 0.5f;
  
     mNewConvex->setPosition( pos );
  
     return Handled;
  }
  
  void ConvexEditorCreateTool::renderScene( const RectI &updateRect )
  {
    
  }
  
  ConvexShape* ConvexEditorCreateTool::extrudeShapeFromFace( ConvexShape *inShape, S32 inFaceId )
  {
     ConvexShape::Geometry &inShapeGeometry = inShape->getGeometry();
     ConvexShape::Face &inFace = inShapeGeometry.faces[inFaceId];
     Vector< Point3F > &inShapePointList = inShapeGeometry.points;
     Vector< MatrixF > &inShapeSurfaces = inShape->getSurfaces();
     
     S32 shapeFaceCount = inFace.edges.size() + 2;   
     
     MatrixF inShapeToWorld( inShape->getTransform() );
     inShapeToWorld.scale( inShape->getScale() );
     //MatrixF inWorldToShape( inShapeToWorld );
     //inWorldToShape.inverse();
  
     MatrixF shapeToWorld;
     shapeToWorld.mul( inShape->getTransform(), inShapeSurfaces[inFaceId] );
     Point3F tmp( inShapeSurfaces[inFaceId].getPosition() );
     inShapeToWorld.mulP( tmp );
     shapeToWorld.setPosition( tmp );
     MatrixF worldToShape( shapeToWorld );
     worldToShape.inverse();
  
     MatrixF inShapeToNewShape;
     inShapeToNewShape.mul( inShapeToWorld, worldToShape );   
  
     ConvexShape *newShape = new ConvexShape;   
     newShape->setTransform( shapeToWorld );   
  
     Vector< MatrixF > &shapeSurfaces = newShape->getSurfaces();
     shapeSurfaces.setSize( shapeFaceCount );
     //shapeSurfaces.setSize( 2 );
  
     const Point3F &shapePos = shapeToWorld.getPosition();
     
     shapeSurfaces[0].identity();
     shapeSurfaces[1].identity();   
     shapeSurfaces[1].setColumn( 0, -shapeSurfaces[1].getColumn3F(0) );
     shapeSurfaces[1].setColumn( 2, -shapeSurfaces[1].getColumn3F(2) );
  
     for ( S32 i = 0; i < inFace.winding.size(); i++ )
     {      
        Point3F p0 = inShapePointList[ inFace.points[ inFace.winding[ i ] ] ];
        Point3F p1;
        
        if ( i+1 < inFace.winding.size() )
           p1 = inShapePointList[ inFace.points[ inFace.winding[ i+1 ] ] ];
        else
           p1 = inShapePointList[ inFace.points[ inFace.winding[ 0 ] ] ];
  
        inShapeToWorld.mulP( p0 );
        inShapeToWorld.mulP( p1 );
  
        Point3F newPos = MathUtils::mClosestPointOnSegment( p0, p1, shapePos );      
  
        Point3F rvec = p0 - p1;
        rvec.normalizeSafe();
  
        Point3F fvec = shapeToWorld.getUpVector();
  
        Point3F uvec = mCross( rvec, fvec );      
  
        if ( i + 2 >= shapeSurfaces.size() )
           continue;
        
        //F32 dt = mDot( shapeToWorld.getUpVector(), rvec );
        //AssertFatal( mIsZero( dt ), "bad" );
        
        MatrixF &surf = shapeSurfaces[i+2];
        surf.identity();
        surf.setColumn( 0, rvec );
        surf.setColumn( 1, fvec );
        surf.setColumn( 2, uvec );
        surf.setPosition( newPos );
  
        surf.mulL( worldToShape );      
     }
  
  	//newShape->setField( "material", Parent::mEditor->mMaterialName );
     newShape->_setMaterial(inShape->getMaterial());
  
     newShape->registerObject();
     mEditor->updateShape( newShape );
  
     Scene* scene = Scene::getRootScene();
     if ( scene )
        scene->addObject( newShape );
  
     return newShape;
  }
  
  void GuiConvexEditorCtrl::hollowShape( ConvexShape *shape, F32 thickness )
  {
     // Create a new Convex for each face of the original shape.
     // This is the same as an extrude from face operation going inward by the thickness
     // for every face.
  
     Vector< ConvexShape* > convexList;
  
     for ( S32 i = 0; i < shape->mGeometry.faces.size(); i++ )
     {
        ConvexShape *faceShape = mCreateTool->extrudeShapeFromFace( shape, i );
        MatrixF &inwardFace = faceShape->mSurfaces[1];
        //MatrixF &outwardFace = faceShape->mSurfaces[0];      
  
        Point3F invec = inwardFace.getUpVector();
  
        inwardFace.setPosition( inwardFace.getPosition() + invec * thickness );
  
        updateShape( faceShape );
  
        convexList.push_back( faceShape );
     }
  
     convexList.push_front( shape );
     submitUndo( HollowShape, convexList );   
  }
  
  void GuiConvexEditorCtrl::hollowSelection()
  {
     if ( mConvexSEL )
     {
        hollowShape( mConvexSEL, 0.15f );
        setSelection( NULL, -1 );      
     }
  }
  
  void GuiConvexEditorCtrl::recenterSelection()
  {
     if ( mConvexSEL )    
     {
        recenterShape( mConvexSEL );   
        updateGizmoPos();
     }
  }
  
  void GuiConvexEditorCtrl::recenterShape( ConvexShape *shape )
  {
     submitUndo( ModifyShape, shape );
     shape->recenter();
     synchClientObject( shape );
  }
  
  void GuiConvexEditorCtrl::dropSelectionAtScreenCenter()
  {
     // This code copied from WorldEditor.
     // All the dropping code would be moved to somewhere common, but its not.
  
     if ( !mConvexSEL )
        return;
  
     // Calculate the center of the screen (in global screen coordinates)
     Point2I offset = localToGlobalCoord(Point2I(0,0));
     Point3F sp(F32(offset.x + F32(getExtent().x / 2)), F32(offset.y + (getExtent().y / 2)), 1.0f);
  
     // Calculate the view distance to fit the selection
     // within the camera's view.
     const Box3F bounds = mConvexSEL->getWorldBox();
     F32 radius = bounds.len()*0.5f;
     F32 viewdist = calculateViewDistance(radius);
  
     // Be careful of infinite sized objects, or just large ones in general.
     if(viewdist > 100.0f )
        viewdist = 100.0f;
  
     // Position the selection   
     mConvexSEL->setPosition( smCamPos + smCamMatrix.getForwardVector() * viewdist );
  
     synchClientObject( mConvexSEL );
  
     updateGizmoPos();
  }
  
  void GuiConvexEditorCtrl::splitSelectedFace()
  {
     if ( !mConvexSEL || mFaceSEL == -1 )
        return;
  
     if ( !isShapeValid( mConvexSEL ) )
        return;
  
     mLastValidShape = mConvexSEL->mSurfaces;
  
     const F32 radians = mDegToRad( 15.0f );
     Point3F rot( 0, 0, 0 );
     MatrixF rotMat( true );
  
     mConvexSEL->mSurfaces.increment();
     MatrixF &dstMat = mConvexSEL->mSurfaces.last();   
     const MatrixF &srcMat = mConvexSEL->mSurfaces[mFaceSEL];
  
     for ( S32 i = 0; i < 6; i++ )
     {
        F32 sign = i > 2 ? -1.0f : 1.0f;
        U32 idx = i % 3;
  
        rot.zero();
        rot[idx] = sign * radians;
        rotMat.set( (EulerF)rot );
  
        dstMat = srcMat * rotMat;
  
        updateShape( mConvexSEL );
  
        if ( isShapeValid( mConvexSEL ) )
        {
           mSavedSurfaces = mConvexSEL->mSurfaces;
           mConvexSEL->mSurfaces = mLastValidShape;
  
           submitUndo( ModifyShape, mConvexSEL );  
  
           mConvexSEL->mSurfaces = mSavedSurfaces;
           mLastValidShape = mSavedSurfaces;
  
           setSelection( mConvexSEL, mConvexSEL->mSurfaces.size() - 1 );
  
           return;
        }      
     }
  
     mConvexSEL->mSurfaces = mLastValidShape;
     updateShape( mConvexSEL );
     updateGizmoPos();
  }
  
  SceneObject* GuiConvexEditorCtrl::createPolyhedralObject(const char* className, SceneObject* geometryProvider)
  {
     if (!geometryProvider)
     {
        Con::errorf("WorldEditor::createPolyhedralObject - Invalid geometry provider!");
        return NULL;
     }
  
     if (!className || !className[0])
     {
        Con::errorf("WorldEditor::createPolyhedralObject - Invalid class name");
        return NULL;
     }
  
     AbstractClassRep* classRep = AbstractClassRep::findClassRep(className);
     if (!classRep)
     {
        Con::errorf("WorldEditor::createPolyhedralObject - No such class: %s", className);
        return NULL;
     }
  
     // We don't want the extracted poly list to be affected by the object's
     // current transform and scale so temporarily reset them.
  
     MatrixF savedTransform = geometryProvider->getTransform();
     Point3F savedScale = geometryProvider->getScale();
  
     geometryProvider->setTransform(MatrixF::Identity);
     geometryProvider->setScale(Point3F(1.f, 1.f, 1.f));
  
     // Extract the geometry.  Use the object-space bounding volumes
     // as we have moved the object to the origin for the moment.
  
     OptimizedPolyList polyList;
     if (!geometryProvider->buildPolyList(PLC_Export, &polyList, geometryProvider->getObjBox(), geometryProvider->getObjBox().getBoundingSphere()))
     {
        Con::errorf("WorldEditor::createPolyhedralObject - Failed to extract geometry!");
        return NULL;
     }
  
     // Restore the object's original transform.
  
     geometryProvider->setTransform(savedTransform);
     geometryProvider->setScale(savedScale);
  
     // Create the object.
  
     SceneObject* object = dynamic_cast< SceneObject* >(classRep->create());
     if (!Object)
     {
        Con::errorf("WorldEditor::createPolyhedralObject - Could not create SceneObject with class '%s'", className);
        return NULL;
     }
  
     // Convert the polylist to a polyhedron.
  
     Polyhedron polyhedron = polyList.toPolyhedron();
  
     // Add the vertex data.
  
     const U32 numPoints = polyhedron.getNumPoints();
     const Point3F* points = polyhedron.getPoints();
  
     for (U32 i = 0; i < numPoints; ++i)
     {
        static StringTableEntry sPoint = StringTable->insert("point");
        object->setDataField(sPoint, NULL, EngineMarshallData(points[i]));
     }
  
     // Add the plane data.
  
     const U32 numPlanes = polyhedron.getNumPlanes();
     const PlaneF* planes = polyhedron.getPlanes();
  
     for (U32 i = 0; i < numPlanes; ++i)
     {
        static StringTableEntry sPlane = StringTable->insert("plane");
        const PlaneF& plane = planes[i];
  
        char buffer[1024];
        dSprintf(buffer, sizeof(buffer), "%g %g %g %g", plane.x, plane.y, plane.z, plane.d);
  
        object->setDataField(sPlane, NULL, buffer);
     }
  
     // Add the edge data.
  
     const U32 numEdges = polyhedron.getNumEdges();
     const Polyhedron::Edge* edges = polyhedron.getEdges();
  
     for (U32 i = 0; i < numEdges; ++i)
     {
        static StringTableEntry sEdge = StringTable->insert("edge");
        const Polyhedron::Edge& edge = edges[i];
  
        char buffer[1024];
        dSprintf(buffer, sizeof(buffer), "%i %i %i %i ",
           edge.face[0], edge.face[1],
           edge.vertex[0], edge.vertex[1]
        );
  
        object->setDataField(sEdge, NULL, buffer);
     }
  
     // Set the transform.
  
     object->setTransform(savedTransform);
     object->setScale(savedScale);
  
     // Register and return the object.
  
     if (!object->registerObject())
     {
        Con::errorf("WorldEditor::createPolyhedralObject - Failed to register object!");
        delete object;
        return NULL;
     }
  
     return object;
  }
  
  ConvexShape* GuiConvexEditorCtrl::createConvexShapeFrom(SceneObject* polyObject)
  {
     if (!polyObject)
     {
        Con::errorf("WorldEditor::createConvexShapeFrom - Invalid object");
        return NULL;
     }
  
     IScenePolyhedralObject* iPoly = dynamic_cast< IScenePolyhedralObject* >(polyObject);
     if (!iPoly)
     {
        Con::errorf("WorldEditor::createConvexShapeFrom - Not a polyhedral object!");
        return NULL;
     }
  
     // Get polyhedron.
  
     AnyPolyhedron polyhedron = iPoly->ToAnyPolyhedron();
     const U32 numPlanes = polyhedron.getNumPlanes();
     if (!numPlanes)
     {
        Con::errorf("WorldEditor::createConvexShapeFrom - Object returned no valid polyhedron");
        return NULL;
     }
  
     // Create a ConvexShape.
  
     ConvexShape* shape = new ConvexShape();
  
     // Add all planes.
  
     for (U32 i = 0; i < numPlanes; ++i)
     {
        const PlaneF& plane = polyhedron.getPlanes()[i];
  
        // Polyhedron planes are facing inwards so we need to
        // invert the normal here.
  
        Point3F normal = plane.getNormal();
        normal.neg();
  
        // Turn the orientation of the plane into a quaternion.
        // The normal is our up vector (that's what's expected
        // by ConvexShape for the surface orientation).
  
        MatrixF orientation(true);
        MathUtils::getMatrixFromUpVector(normal, &orientation);
        const QuatF quat(orientation);
  
        // Get the plane position.
  
        const Point3F position = plane.getPosition();
  
        // Turn everything into a "surface" property for the ConvexShape.
  
        char buffer[1024];
        dSprintf(buffer, sizeof(buffer), "%g %g %g %g %g %g %g",
           quat.x, quat.y, quat.z, quat.w,
           position.x, position.y, position.z
        );
  
        // Add the surface.
  
        static StringTableEntry sSurface = StringTable->insert("surface");
        shape->setDataField(sSurface, NULL, buffer);
     }
  
     // Copy the transform.
  
     shape->setTransform(polyObject->getTransform());
     shape->setScale(polyObject->getScale());
  
     // Register the shape.
  
     if (!shape->registerObject())
     {
        Con::errorf("WorldEditor::createConvexShapeFrom - Could not register ConvexShape!");
        delete shape;
        return NULL;
     }
  
     return shape;
  }
  
  DefineEngineMethod( GuiConvexEditorCtrl, hollowSelection, void, (), , "" )
  {
     object->hollowSelection();
  }
  
  DefineEngineMethod( GuiConvexEditorCtrl, recenterSelection, void, (), , "" )
  {
     object->recenterSelection();
  }
  
  DefineEngineMethod( GuiConvexEditorCtrl, hasSelection, S32, (), , "" )
  {
     return object->hasSelection();
  }
  
  DefineEngineMethod( GuiConvexEditorCtrl, handleDelete, void, (), , "" )
  {
     object->handleDelete();
  }
  
  DefineEngineMethod( GuiConvexEditorCtrl, handleDeselect, void, (), , "" )
  {
     object->handleDeselect();
  }
  
  DefineEngineMethod( GuiConvexEditorCtrl, dropSelectionAtScreenCenter, void, (), , "" )
  {
     object->dropSelectionAtScreenCenter();
  }
  
  DefineEngineMethod( GuiConvexEditorCtrl, selectConvex, void, (ConvexShape *convex), , "( ConvexShape )" )
  {
  if (convex)
        object->setSelection( convex, -1 );
  }
  
  DefineEngineMethod( GuiConvexEditorCtrl, splitSelectedFace, void, (), , "" )
  {
     object->splitSelectedFace();
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, getSelectedFaceUVOffset, Point2F, (), ,
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     //return Point2F(0, 0);
     return object->getSelectedFaceUVOffset();
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, getSelectedFaceUVScale, Point2F, (), ,
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     //return Point2F(0, 0);
     return object->getSelectedFaceUVScale();
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, setSelectedFaceUVOffset, void, ( Point2F offset ), ( Point2F(0,0) ),
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     //return Point2F(0, 0);
     return object->setSelectedFaceUVOffset(offset);
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, setSelectedFaceUVScale, void, (Point2F scale), (Point2F(0, 0)),
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     //return Point2F(0, 0);
     return object->setSelectedFaceUVScale(scale);
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, setSelectedFaceMaterial, void, (const char* materialName), (""),
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     //return Point2F(0, 0);
     if (!String::compare(materialName, ""))
        return;
  
     object->setSelectedFaceMaterial(materialName);
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, getSelectedFaceMaterial, const char*, (), ,
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     return object->getSelectedFaceMaterial();
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, setSelectedFaceHorzFlip, void, (bool flipped), (false),
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     object->setSelectedFaceHorzFlip(flipped);
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, setSelectedFaceVertFlip, void, (bool flipped), (false),
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     object->setSelectedFaceVertFlip(flipped);
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, getSelectedFaceHorzFlip, bool, (), ,
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     return object->getSelectedFaceHorzFlip();
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, getSelectedFaceVertFlip, bool, (), ,
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     return object->getSelectedFaceVertFlip();
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, setSelectedFaceZRot, void, (float degrees), (0.0),
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     object->setSelectedFaceZRot(degrees);
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, getSelectedFaceZRot, float, (), ,
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     return object->getSelectedFaceZRot();
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, toggleGridSnapping, void, (),,
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     object->toggleGridSnapping();
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, setGridSnapSize, void, (float gridSize), (1.0),
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     object->setGridSnapSize(gridSize);
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, getGridSnapSize, float, (),,
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     return object->getGridSnapSize();
  }
  
  DefineEngineMethod(GuiConvexEditorCtrl, updateShape, void, (),,
     "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
  
     "@param objB  Object to mount onto us\n"
     "@param slot  Mount slot ID\n"
     "@param txfm (optional) mount offset transform\n"
     "@return true if successful, false if failed (objB is not valid)")
  {
     //return Point2F(0, 0);
     return object->updateShape();
  }
  