Torque3D Documentation / _generateds / guiMeshRoadEditorCtrl.cpp

guiMeshRoadEditorCtrl.cpp

Engine/source/environment/editors/guiMeshRoadEditorCtrl.cpp

More...

Public Functions

ConsoleDocClass(GuiMeshRoadEditorCtrl , "@brief GUI tool that makes up the Mesh Road <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editor\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , deleteNode , void , () , "deleteNode()" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , getMode , const char * , () , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , getNodeDepth , F32 , () , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , getNodeNormal , Point3F , () , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , getNodePosition , Point3F , () , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , getNodeWidth , F32 , () , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , getSelectedRoad , S32 , () , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , matchTerrainToRoad , void , () , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , regenerate , void , () , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , setMode , void , (const char *mode) , "setMode( <a href="/coding/class/classstring/">String</a> <a href="/coding/file/zipobject_8cpp/#zipobject_8cpp_1ac6c3dfb4c3a68f849f32cbfb21da4e77">mode</a> )" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , setNodeDepth , void , (F32 depth) , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , setNodeNormal , void , (Point3F normal) , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , setNodePosition , void , (Point3F pos) , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , setNodeWidth , void , (F32 width) , "" )
DefineEngineMethod(GuiMeshRoadEditorCtrl , setSelectedRoad , void , (const char *objName) , ("") , "" )

Detailed Description

Public Functions

_NodeIndexCmp(U32 const * a, U32 const * b)

ConsoleDocClass(GuiMeshRoadEditorCtrl , "@brief GUI tool that makes up the Mesh Road <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editor\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , deleteNode , void , () , "deleteNode()" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , getMode , const char * , () , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , getNodeDepth , F32 , () , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , getNodeNormal , Point3F , () , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , getNodePosition , Point3F , () , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , getNodeWidth , F32 , () , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , getSelectedRoad , S32 , () , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , matchTerrainToRoad , void , () , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , regenerate , void , () , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , setMode , void , (const char *mode) , "setMode( <a href="/coding/class/classstring/">String</a> <a href="/coding/file/zipobject_8cpp/#zipobject_8cpp_1ac6c3dfb4c3a68f849f32cbfb21da4e77">mode</a> )" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , setNodeDepth , void , (F32 depth) , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , setNodeNormal , void , (Point3F normal) , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , setNodePosition , void , (Point3F pos) , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , setNodeWidth , void , (F32 width) , "" )

DefineEngineMethod(GuiMeshRoadEditorCtrl , setSelectedRoad , void , (const char *objName) , ("") , "" )

IMPLEMENT_CONOBJECT(GuiMeshRoadEditorCtrl )

   1
   2//-----------------------------------------------------------------------------
   3// Copyright (c) 2012 GarageGames, LLC
   4//
   5// Permission is hereby granted, free of charge, to any person obtaining a copy
   6// of this software and associated documentation files (the "Software"), to
   7// deal in the Software without restriction, including without limitation the
   8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   9// sell copies of the Software, and to permit persons to whom the Software is
  10// furnished to do so, subject to the following conditions:
  11//
  12// The above copyright notice and this permission notice shall be included in
  13// all copies or substantial portions of the Software.
  14//
  15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21// IN THE SOFTWARE.
  22//-----------------------------------------------------------------------------
  23
  24#include "platform/platform.h"
  25#include "environment/editors/guiMeshRoadEditorCtrl.h"
  26
  27#include "console/consoleTypes.h"
  28#include "console/engineAPI.h"
  29#include "environment/meshRoad.h"
  30#include "renderInstance/renderPassManager.h"
  31#include "collision/collision.h"
  32#include "math/util/frustum.h"
  33#include "math/mathUtils.h"
  34#include "gfx/gfxPrimitiveBuffer.h"
  35#include "gfx/gfxTextureHandle.h"
  36#include "gfx/gfxTransformSaver.h"
  37#include "gfx/primBuilder.h"
  38#include "gfx/gfxDrawUtil.h"
  39#include "scene/sceneRenderState.h"
  40#include "scene/sceneManager.h"
  41#include "gui/core/guiCanvas.h"
  42#include "gui/buttons/guiButtonCtrl.h"
  43#include "gui/worldEditor/undoActions.h"
  44#include "T3D/gameBase/gameConnection.h"
  45#include "gfx/sim/debugDraw.h"
  46#include "materials/materialDefinition.h"
  47#include "T3D/prefab.h"
  48
  49#include "T3D/Scene.h"
  50
  51IMPLEMENT_CONOBJECT(GuiMeshRoadEditorCtrl);
  52
  53ConsoleDocClass( GuiMeshRoadEditorCtrl,
  54   "@brief GUI tool that makes up the Mesh Road Editor\n\n"
  55   "Editor use only.\n\n"
  56   "@internal"
  57);
  58
  59S32 _NodeIndexCmp( U32 const *a, U32 const *b )
  60{
  61   S32 a2 = (*a);
  62   S32 b2 = (*b);
  63   S32 diff = a2 - b2;
  64   return diff < 0 ? 1 : diff > 0 ? -1 : 0;
  65}
  66
  67GuiMeshRoadEditorCtrl::GuiMeshRoadEditorCtrl()
  68 : 
  69   // Each of the mode names directly correlates with the Mesh Road Editor's
  70   // tool palette
  71   mSelectMeshRoadMode("MeshRoadEditorSelectMode"),
  72   mAddMeshRoadMode("MeshRoadEditorAddRoadMode"),
  73   mAddNodeMode("MeshRoadEditorAddNodeMode"),
  74   mInsertPointMode("MeshRoadEditorInsertPointMode"),
  75   mRemovePointMode("MeshRoadEditorRemovePointMode"),
  76   mMovePointMode("MeshRoadEditorMoveMode"),
  77    mScalePointMode("MeshRoadEditorScaleMode"),
  78   mRotatePointMode("MeshRoadEditorRotateMode"),
  79    mSavedDrag(false),
  80    mIsDirty( false ),
  81    mSavedProfileDrag( false ),
  82    mDeselectProfileNode( false ),
  83    mProfileNode( -1 ),
  84    mProfileColor( 255,255,0 ),
  85    mRoadSet( NULL ),
  86    mSelNode( -1 ),
  87    mHoverNode( -1 ),
  88    mAddNodeIdx( 0 ),
  89    mSelRoad( NULL ),
  90    mHoverRoad( NULL ),
  91    mMode(mSelectMeshRoadMode),
  92    mDefaultWidth( 10.0f ),
  93    mDefaultDepth( 5.0f ),
  94    mDefaultNormal( 0,0,1 ),
  95    mNodeHalfSize( 4,4 ),
  96    mHoverSplineColor( 255,0,0,255 ),
  97    mSelectedSplineColor( 0,255,0,255 ),
  98    mHoverNodeColor( 255,255,255,255 ),
  99    mHasCopied( false )
 100{
 101   mTopMaterialAssetId = Con::getVariable("$MeshRoadEditor::defaultTopMaterialAsset");
 102   mBottomMaterialAssetId = Con::getVariable("$MeshRoadEditor::defaultBottomMaterialAsset");
 103   mSideMaterialAssetId = Con::getVariable("$MeshRoadEditor::defaultSideMaterialAsset");
 104}
 105
 106GuiMeshRoadEditorCtrl::~GuiMeshRoadEditorCtrl()
 107{
 108   // nothing to do
 109}
 110
 111void GuiMeshRoadEditorUndoAction::undo()
 112{
 113   MeshRoad *object = NULL;
 114   if ( !Sim::findObject( mObjId, object ) )
 115      return;
 116
 117   // Temporarily save the Roads current data.
 118   //F32 metersPerSeg = object->mMetersPerSegment;
 119   Vector<MeshRoadNode> nodes;   
 120   nodes.merge( object->mNodes );
 121
 122   // Restore the River properties saved in the UndoAction
 123   //object->mMetersPerSegment = mMetersPerSegment;
 124
 125   // Restore the Nodes saved in the UndoAction
 126   object->mNodes.clear();
 127   for ( U32 i = 0; i < mNodes.size(); i++ )
 128   {
 129      object->_addNode( mNodes[i].point, mNodes[i].width, mNodes[i].depth, mNodes[i].normal );      
 130   }
 131
 132   // Temporarily save the Roads current profile data.
 133   Vector<MeshRoadProfileNode> profNodes;
 134   Vector<U8> profMtrls;
 135   profNodes.merge( object->mSideProfile.mNodes );
 136   profMtrls.merge( object->mSideProfile.mSegMtrls );
 137
 138   // Restore the Profile Nodes saved in the UndoAction
 139   Point3F pos;
 140   object->mSideProfile.mNodes.clear();
 141   object->mSideProfile.mSegMtrls.clear();
 142   for ( U32 i = 0; i < mProfileNodes.size(); i++ )
 143   {
 144      MeshRoadProfileNode newNode;
 145
 146      pos = mProfileNodes[i].getPosition();
 147      newNode.setSmoothing( mProfileNodes[i].isSmooth() );
 148
 149      object->mSideProfile.mNodes.push_back( newNode );
 150      object->mSideProfile.mNodes.last().setPosition( pos.x, pos.y );
 151
 152      if(i)
 153         object->mSideProfile.mSegMtrls.push_back(mProfileMtrls[i-1]);
 154   }
 155
 156   // Set the first node position to trigger packet update to client
 157   pos.set(0.0f, 0.0f, 0.0f);
 158   object->mSideProfile.setNodePosition(0,pos);
 159
 160   // Regenerate the Road
 161   object->mSideProfile.generateNormals();
 162
 163   // If applicable set the selected Road and node
 164   mEditor->mProfileNode = -1;
 165
 166   // Now save the previous Road data in this UndoAction
 167   // since an undo action must become a redo action and vice-versa
 168   //mMetersPerSegment = metersPerSeg;
 169   mProfileNodes.clear();
 170   mProfileNodes.merge( profNodes );
 171   mProfileMtrls.clear();
 172   mProfileMtrls.merge( profMtrls );
 173
 174   // Regenerate the Road
 175   object->regenerate();
 176
 177   // If applicable set the selected Road and node
 178   mEditor->mSelRoad = object;
 179   mEditor->mSelNode = -1;
 180
 181   // Now save the previous Road data in this UndoAction
 182   // since an undo action must become a redo action and vice-versa
 183   //mMetersPerSegment = metersPerSeg;
 184   mNodes.clear();
 185   mNodes.merge( nodes );
 186}
 187
 188bool GuiMeshRoadEditorCtrl::onAdd()
 189{
 190   if( !Parent::onAdd() )
 191      return false;
 192
 193   mRoadSet = MeshRoad::getServerSet();   
 194
 195   GFXStateBlockDesc desc;
 196   desc.fillMode = GFXFillSolid;      
 197   desc.blendDefined = true;
 198   desc.blendEnable = false;      
 199   desc.zDefined = true;
 200   desc.zEnable = false;
 201   desc.cullDefined = true;
 202   desc.cullMode = GFXCullNone;
 203
 204   mZDisableSB = GFX->createStateBlock(desc);
 205
 206   desc.zEnable = true;
 207   mZEnableSB = GFX->createStateBlock(desc);
 208
 209   bindMaterialAsset(TopMaterial);
 210   bindMaterialAsset(BottomMaterial);
 211   bindMaterialAsset(SideMaterial);
 212
 213   return true;
 214}
 215
 216void GuiMeshRoadEditorCtrl::initPersistFields()
 217{
 218   addField( "DefaultWidth",        TypeF32,    Offset( mDefaultWidth, GuiMeshRoadEditorCtrl ) );
 219   addField( "DefaultDepth",        TypeF32,    Offset( mDefaultDepth, GuiMeshRoadEditorCtrl ) );
 220   addField( "DefaultNormal",       TypePoint3F,Offset( mDefaultNormal, GuiMeshRoadEditorCtrl ) );
 221   addField( "HoverSplineColor",    TypeColorI, Offset( mHoverSplineColor, GuiMeshRoadEditorCtrl ) );
 222   addField( "SelectedSplineColor", TypeColorI, Offset( mSelectedSplineColor, GuiMeshRoadEditorCtrl ) );
 223   addField( "HoverNodeColor",      TypeColorI, Offset( mHoverNodeColor, GuiMeshRoadEditorCtrl ) );
 224   addField( "isDirty",             TypeBool,   Offset( mIsDirty, GuiMeshRoadEditorCtrl ) );
 225
 226   addField("topMaterial", TypeMaterialAssetId, Offset(mTopMaterialAssetId, GuiMeshRoadEditorCtrl), "Default Material used by the Mesh Road Editor on upper surface road creation.");
 227   addField("bottomMaterial", TypeMaterialAssetId, Offset(mBottomMaterialAssetId, GuiMeshRoadEditorCtrl), "Default Material used by the Mesh Road Editor on bottom surface road creation.");
 228   addField("sideMaterial", TypeMaterialAssetId, Offset(mSideMaterialAssetId, GuiMeshRoadEditorCtrl), "Default Material used by the Mesh Road Editor on side surface road creation.");
 229
 230   //addField( "MoveNodeCursor", TYPEID< SimObject >(), Offset( mMoveNodeCursor, GuiMeshRoadEditorCtrl) );
 231   //addField( "AddNodeCursor", TYPEID< SimObject >(), Offset( mAddNodeCursor, GuiMeshRoadEditorCtrl) );
 232   //addField( "InsertNodeCursor", TYPEID< SimObject >(), Offset( mInsertNodeCursor, GuiMeshRoadEditorCtrl) );
 233   //addField( "ResizeNodeCursor", TYPEID< SimObject >(), Offset( mResizeNodeCursor, GuiMeshRoadEditorCtrl) );
 234
 235   Parent::initPersistFields();
 236}
 237
 238void GuiMeshRoadEditorCtrl::onSleep()
 239{
 240   Parent::onSleep();
 241
 242   mMode = mSelectMeshRoadMode;  
 243   mHoverNode = -1;
 244   mHoverRoad = NULL;
 245   setSelectedNode(-1);   
 246}
 247
 248void GuiMeshRoadEditorCtrl::get3DCursor( GuiCursor *&cursor, 
 249                                       bool &visible, 
 250                                       const Gui3DMouseEvent &event_ )
 251{
 252   //cursor = mAddNodeCursor;
 253   //visible = false;
 254   
 255   cursor = NULL;
 256   visible = false;
 257
 258   GuiCanvas *root = getRoot();
 259   if ( !root )
 260      return;
 261
 262   S32 currCursor = PlatformCursorController::curArrow;
 263
 264   if ( root->mCursorChanged == currCursor )
 265      return;
 266
 267   PlatformWindow *window = root->getPlatformWindow();
 268   PlatformCursorController *controller = window->getCursorController();
 269   
 270   // We've already changed the cursor, 
 271   // so set it back before we change it again.
 272   if( root->mCursorChanged != -1)
 273      controller->popCursor();
 274
 275   // Now change the cursor shape
 276   controller->pushCursor(currCursor);
 277   root->mCursorChanged = currCursor;   
 278}
 279
 280void GuiMeshRoadEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event)
 281{
 282   mHasCopied = false;
 283
 284   mGizmo->on3DMouseDown( event );
 285
 286   if ( !isFirstResponder() )
 287      setFirstResponder();
 288   
 289   if( MeshRoad::smShowRoadProfile && mSelRoad )
 290   {
 291      // Ctrl-Click = Add Node
 292      if(event.modifier & SI_CTRL)
 293      {
 294         S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
 295
 296         if(clickedNode != -1)
 297         {
 298            // If clicked node is already in list, remove it, else add it to list
 299            if(!mSelProfNodeList.remove(clickedNode) && clickedNode > 0)
 300               mSelProfNodeList.push_back(clickedNode);
 301
 302            return;
 303         }
 304
 305         Point3F pos;
 306
 307         PlaneF xy( mSelRoad->mSlices[0].p2, -mSelRoad->mSlices[0].fvec );
 308
 309         xy.intersect(event.pos, event.vec, &pos);
 310
 311         mSelRoad->mSideProfile.worldToObj(pos);
 312
 313         U32 node = mSelRoad->mSideProfile.clickOnLine(pos);
 314
 315         if(node != -1)
 316         {
 317            submitUndo( "Add Profile Node" );
 318            mSelRoad->mSideProfile.addPoint(node, pos);
 319            mProfileNode = node;
 320            mSelProfNodeList.clear();
 321            mSelProfNodeList.push_back(node);
 322            mIsDirty = true;
 323         }
 324
 325         return;
 326      }
 327
 328      // Alt-Click = Delete Node
 329      if(event.modifier & SI_ALT)
 330      {
 331         S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
 332
 333
 334         if(mSelProfNodeList.find_next(clickedNode) != -1)
 335         {
 336            submitUndo( "Delete Profile Node" );
 337
 338            mSelProfNodeList.sort( _NodeIndexCmp );
 339            for(U32 i=0; i < mSelProfNodeList.size(); i++)
 340               mSelRoad->mSideProfile.removePoint( mSelProfNodeList[i] );
 341
 342            mProfileNode = -1;
 343            mSelProfNodeList.clear();
 344            mIsDirty = true;
 345         }
 346         else if(clickedNode > 0 && clickedNode < mSelRoad->mSideProfile.mNodes.size()-1)
 347         {
 348            submitUndo( "Delete Profile Node" );
 349            mSelRoad->mSideProfile.removePoint( clickedNode );
 350            mProfileNode = -1;
 351            mSelProfNodeList.clear();
 352            mIsDirty = true;
 353         }
 354
 355         return;
 356      }
 357
 358      // Shift-Click = Toggle Node Smoothing
 359      if(event.modifier & SI_SHIFT)
 360      {
 361         S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
 362
 363         if(clickedNode != -1)
 364         {
 365            submitUndo( "Smooth Profile Node" );
 366
 367            if(mSelProfNodeList.find_next(clickedNode) != -1)
 368            {
 369               for(U32 i=0; i < mSelProfNodeList.size(); i++)
 370                  mSelRoad->mSideProfile.toggleSmoothing(mSelProfNodeList[i]);
 371            }
 372            else
 373            {
 374               mSelRoad->mSideProfile.toggleSmoothing(clickedNode);
 375
 376               if(clickedNode != 0)
 377               {
 378                  mProfileNode = clickedNode;
 379                  mSelProfNodeList.clear();
 380                  mSelProfNodeList.push_back(clickedNode);
 381               }
 382            }
 383
 384            mIsDirty = true;
 385            return;
 386         }
 387
 388         Point3F pos;
 389         PlaneF xy( mSelRoad->mSlices[0].p2, -mSelRoad->mSlices[0].fvec );
 390         xy.intersect(event.pos, event.vec, &pos);
 391         mSelRoad->mSideProfile.worldToObj(pos);
 392         U32 node = mSelRoad->mSideProfile.clickOnLine(pos);
 393
 394         if(node > 0)
 395         {
 396            submitUndo( "Profile Material" );
 397            mSelRoad->mSideProfile.toggleSegMtrl(node-1);
 398            mIsDirty = true;
 399         }
 400
 401         return;
 402      }
 403
 404      // Click to select/deselect nodes
 405      S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
 406
 407      if(clickedNode != -1)
 408      {
 409         if(mSelProfNodeList.find_next(clickedNode) != -1)
 410         {
 411            mProfileNode = clickedNode;
 412            mDeselectProfileNode = true;
 413         }
 414         else if(clickedNode != 0)
 415         {
 416            mProfileNode = clickedNode;
 417            mSelProfNodeList.clear();
 418            mSelProfNodeList.push_back(clickedNode);
 419         }
 420         else
 421         {
 422            mProfileNode = -1;
 423            mSelProfNodeList.clear();
 424
 425            // Reset profile if Node 0 is double-clicked
 426            if( event.mouseClickCount > 1 )
 427            {
 428               submitUndo( "Reset Profile" );
 429               mSelRoad->mSideProfile.resetProfile(mSelRoad->mSlices[0].depth);
 430               mSelRoad->regenerate();
 431            }
 432         }
 433
 434         return;
 435      }
 436
 437      mProfileNode = -1;
 438      mSelProfNodeList.clear();
 439   }
 440
 441   // Get the raycast collision position
 442   Point3F tPos;
 443   if ( !getStaticPos( event, tPos ) )
 444      return;  
 445
 446   mouseLock();
 447
 448   // Construct a LineSegment from the camera position to 1000 meters away in
 449   // the direction clicked.
 450   // If that segment hits the terrain, truncate the ray to only be that length.
 451
 452   // We will use a LineSegment/Sphere intersection test to determine if a MeshRoadNode
 453   // was clicked.   
 454
 455   MeshRoad *pRoad = NULL;
 456   MeshRoad *pClickedRoad = NULL;
 457   U32 insertNodeIdx = -1;
 458   Point3F collisionPnt;
 459
 460   Point3F startPnt = event.pos;
 461   Point3F endPnt = event.pos + event.vec * 2000.0f;
 462   RayInfo ri;   
 463
 464   if ( gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri) )
 465      endPnt = ri.point;
 466
 467   DebugDrawer *ddraw = DebugDrawer::get();
 468   if ( false && ddraw )
 469   {
 470      ddraw->drawLine( startPnt, endPnt, ColorI(255,0,0,255) );
 471      ddraw->setLastTTL(DebugDrawer::DD_INFINITE);
 472   }
 473
 474   // Check for collision with nodes of the currently selected or highlighted MeshRoad
 475
 476   // Did we click on a MeshRoad? check currently selected road first
 477   if ( mSelRoad != NULL && mSelRoad->collideRay( event.pos, event.vec, &insertNodeIdx, &collisionPnt ) )
 478   {
 479      pClickedRoad = mSelRoad;
 480   }
 481   else
 482   {
 483      for ( SimSetIterator iter(mRoadSet); *iter; ++iter )
 484      {
 485         pRoad = static_cast<MeshRoad*>( *iter );
 486
 487         // Do not select or edit a MeshRoad within a Prefab.
 488         if ( Prefab::getPrefabByChild(pRoad) )
 489            continue;
 490
 491         if ( pRoad->collideRay( event.pos, event.vec, &insertNodeIdx, &collisionPnt ) )
 492         {
 493            pClickedRoad = pRoad;
 494            break;
 495         }
 496      }
 497   }
 498
 499   /*
 500   else if ( gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri) )
 501   {
 502      MeshRoad *pRoad = NULL;
 503      pRoad = dynamic_cast<MeshRoad*>(ri.object);
 504
 505      if ( pRoad && pRoad->collideRay( event.pos, event.vec, &insertNodeIdx, &collisionPnt ) )
 506         pClickedRoad = pRoad;
 507   }
 508   */
 509
 510   // Did we click on a node?
 511   bool nodeClicked = false;
 512   S32 clickedNodeIdx = -1;
 513
 514   // If we clicked on the currently selected road, only scan its nodes
 515   if ( mSelRoad != NULL && pClickedRoad == mSelRoad )
 516   {
 517      clickedNodeIdx = _getNodeAtScreenPos( mSelRoad, event.mousePoint );
 518      nodeClicked = clickedNodeIdx != -1;
 519   }
 520   else
 521   {
 522      for ( SimSetIterator iter(mRoadSet); *iter; ++iter )
 523      {
 524         pRoad = static_cast<MeshRoad*>( *iter );
 525
 526         // Do not select or edit a MeshRoad within a Prefab.
 527         if ( Prefab::getPrefabByChild(pRoad) )
 528            continue;
 529         
 530         clickedNodeIdx = _getNodeAtScreenPos( pRoad, event.mousePoint );
 531
 532         if ( clickedNodeIdx != -1 )
 533         {
 534            nodeClicked = true;
 535            pClickedRoad = pRoad;
 536            break;
 537         }
 538      }
 539   }
 540   
 541   // shortcuts
 542   bool dblClick = ( event.mouseClickCount > 1 );
 543   if( dblClick )
 544   { 
 545      if( mMode == mSelectMeshRoadMode )
 546      {
 547         setMode( mAddMeshRoadMode, true );
 548         return;
 549      }
 550      if( mMode == mAddNodeMode )
 551      {
 552         // Delete the node attached to the cursor.
 553         deleteSelectedNode();
 554         mMode = mAddMeshRoadMode;
 555         return;
 556      }
 557   }
 558   
 559   //this check is here in order to bounce back from deleting a whole road with ctrl+z
 560   //this check places the editor back into addrivermode
 561   if ( mMode == mAddNodeMode )
 562   {
 563      if ( !mSelRoad )
 564         mMode = mAddMeshRoadMode;
 565   }
 566
 567   if ( mMode == mSelectMeshRoadMode )
 568   {
 569      // Did not click on a MeshRoad or a node.
 570      if ( !pClickedRoad  )
 571      {
 572         setSelectedRoad( NULL );
 573         setSelectedNode( -1 );
 574         
 575         return;
 576      }
 577
 578      // Clicked on a MeshRoad that wasn't the currently selected River.
 579      if ( pClickedRoad != mSelRoad )
 580      {
 581         setSelectedRoad( pClickedRoad );
 582         setSelectedNode( clickedNodeIdx );
 583         return;
 584      }
 585
 586     // Clicked on a node in the currently selected River that wasn't
 587      // the currently selected node.
 588      if ( nodeClicked )
 589      {
 590         setSelectedNode( clickedNodeIdx );
 591         return;
 592      }
 593   }
 594   else if ( mMode == mAddMeshRoadMode )
 595   {
 596      if ( nodeClicked )
 597      {
 598         // A double-click on a node in Normal mode means set AddNode mode.  
 599         if ( clickedNodeIdx == 0 )
 600         {
 601            setSelectedRoad( pClickedRoad  );
 602            setSelectedNode( clickedNodeIdx );
 603
 604            mAddNodeIdx = clickedNodeIdx;
 605            mMode = mAddNodeMode; 
 606
 607            mSelNode = mSelRoad->insertNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal, mAddNodeIdx );
 608            mIsDirty = true;
 609
 610            return;
 611         }
 612         else if ( clickedNodeIdx == pClickedRoad->mNodes.size() - 1 )
 613         {
 614            setSelectedRoad( pClickedRoad  );
 615            setSelectedNode( clickedNodeIdx );
 616
 617            mAddNodeIdx = U32_MAX;
 618            mMode = mAddNodeMode;
 619
 620            mSelNode = mSelRoad->addNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal);
 621            mIsDirty = true;
 622            setSelectedNode( mSelNode );
 623
 624            return;
 625         } 
 626      }
 627
 628      MeshRoad *newRoad = new MeshRoad;  
 629
 630      if(mTopMaterialAsset.notNull())
 631         newRoad->setTopMaterialAssetId(mTopMaterialAssetId);
 632      if (mBottomMaterialAsset.notNull())
 633         newRoad->setBottomMaterialAssetId(mBottomMaterialAssetId);
 634      if (mSideMaterialAsset.notNull())
 635         newRoad->setSideMaterialAssetId(mSideMaterialAssetId);
 636         
 637      newRoad->registerObject();
 638
 639      // Add to scene                              
 640      Scene *scene;
 641
 642      scene = Scene::getRootScene();
 643      if ( !scene)
 644         Con::errorf( "GuiMeshRoadEditorCtrl - could not find Scene to add new MeshRoad" );
 645      else
 646         scene->addObject( newRoad );
 647
 648      Point3F pos( endPnt );
 649      pos.z += mDefaultDepth * 0.5f;
 650
 651      newRoad->insertNode( pos, mDefaultWidth, mDefaultDepth, mDefaultNormal, 0 );
 652      U32 newNode = newRoad->insertNode( pos, mDefaultWidth, mDefaultDepth, mDefaultNormal, 1 );
 653
 654      // Always add to the end of the road, the first node is the start.
 655      mAddNodeIdx = U32_MAX;
 656
 657      setSelectedRoad( newRoad );      
 658      setSelectedNode( newNode );
 659
 660      mMode = mAddNodeMode;
 661
 662      // Disable the hover node while in addNodeMode, we
 663      // don't want some random node enlarged.
 664      mHoverNode = -1;
 665
 666      // Grab the mission editor undo manager.
 667      UndoManager *undoMan = NULL;
 668      if ( !Sim::findObject( "EUndoManager", undoMan ) )
 669      {
 670         Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" );
 671         return;           
 672      }
 673
 674      // Create the UndoAction.
 675      MECreateUndoAction *action = new MECreateUndoAction("Create MeshRoad");
 676      action->addObject( newRoad );
 677      
 678      // Submit it.               
 679      undoMan->addAction( action );
 680
 681      return;
 682   }
 683   else if ( mMode == mAddNodeMode )
 684   {
 685      // Oops the road got deleted, maybe from an undo action?
 686      // Back to NormalMode.
 687      if ( mSelRoad )
 688      {
 689         // A double-click on a node in Normal mode means set AddNode mode.  
 690         if ( clickedNodeIdx == 0 )
 691         {
 692            submitUndo( "Add Node" );
 693            mAddNodeIdx = clickedNodeIdx;
 694            mMode = mAddNodeMode;
 695            mSelNode = mSelRoad->insertNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal, mAddNodeIdx );
 696            mIsDirty = true;
 697            setSelectedNode( mSelNode );
 698
 699            return;
 700         }
 701         else
 702         {
 703            if( pClickedRoad && clickedNodeIdx == pClickedRoad->mNodes.size() - 1 )
 704            {
 705               submitUndo( "Add Node" );
 706               mAddNodeIdx = U32_MAX;
 707               mMode = mAddNodeMode;
 708               U32 newNode = mSelRoad->addNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal);
 709               mIsDirty = true;
 710               setSelectedNode( newNode );
 711               return;
 712            }
 713            else
 714            {
 715               submitUndo( "Insert Node" );
 716               // A single-click on empty space while in
 717               // AddNode mode means insert / add a node.
 718               //submitUndo( "Add Node" );
 719               U32 newNode = mSelRoad->insertNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal, mAddNodeIdx);
 720               mIsDirty = true;
 721               setSelectedNode( newNode );
 722               return;
 723            }
 724         }
 725      }
 726
 727   }
 728   else if ( mMode == mInsertPointMode && mSelRoad != NULL )
 729   {
 730      if ( pClickedRoad == mSelRoad && insertNodeIdx != -1 )
 731      {
 732         // NOTE: I guess we have to determine the if the clicked ray intersects a road but not a specific node...
 733         // in order to handle inserting nodes in the same way as for fxRoad
 734
 735         U32 prevNodeIdx = insertNodeIdx;
 736         U32 nextNodeIdx = ( prevNodeIdx + 1 > mSelRoad->mNodes.size() - 1 ) ? prevNodeIdx : prevNodeIdx + 1;
 737
 738         const MeshRoadNode &prevNode = mSelRoad->mNodes[prevNodeIdx];
 739         const MeshRoadNode &nextNode = mSelRoad->mNodes[nextNodeIdx];
 740
 741         F32 width = ( prevNode.width + nextNode.width ) * 0.5f;
 742         F32 depth = ( prevNode.depth + nextNode.depth ) * 0.5f;
 743         Point3F normal = ( prevNode.normal + nextNode.normal ) * 0.5f;
 744         normal.normalize();
 745
 746         submitUndo( "Insert Node" );
 747         U32 newNode = mSelRoad->insertNode( collisionPnt, width, depth, normal, insertNodeIdx + 1 );
 748         mIsDirty = true;
 749         setSelectedNode( newNode );
 750         return;
 751       }
 752   }
 753   else if ( mMode == mRemovePointMode && mSelRoad != NULL )
 754   {
 755      if ( nodeClicked && pClickedRoad == mSelRoad )
 756      {
 757         setSelectedNode( clickedNodeIdx );
 758         deleteSelectedNode();
 759         return;
 760      }
 761   }
 762   else if ( mMode == mMovePointMode )
 763   {
 764      if ( nodeClicked && pClickedRoad == mSelRoad )
 765      {
 766         setSelectedNode( clickedNodeIdx );
 767         return;
 768      }
 769   }
 770   else if ( mMode == mScalePointMode )
 771   {
 772      if ( nodeClicked && pClickedRoad == mSelRoad )
 773      {
 774         setSelectedNode( clickedNodeIdx );
 775         return;
 776      }
 777   }
 778   else if ( mMode == mRotatePointMode )
 779   {
 780      if ( nodeClicked && pClickedRoad == mSelRoad )
 781      {
 782         setSelectedNode( clickedNodeIdx );
 783         return;
 784      }
 785   }   
 786}
 787
 788void GuiMeshRoadEditorCtrl::on3DRightMouseDown(const Gui3DMouseEvent & event)
 789{
 790   //mIsPanning = true;
 791}
 792
 793void GuiMeshRoadEditorCtrl::on3DRightMouseUp(const Gui3DMouseEvent & event)
 794{
 795   //mIsPanning = false;
 796}
 797
 798void GuiMeshRoadEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event)
 799{
 800   mGizmo->on3DMouseUp(event);
 801
 802   mSavedDrag = false;
 803
 804   mSavedProfileDrag = false;
 805
 806   if( MeshRoad::smShowRoadProfile && mSelRoad )
 807   {
 808      // If we need to deselect node... this means we clicked on a selected node without dragging
 809      if( mDeselectProfileNode )
 810      {
 811         S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
 812
 813         if(clickedNode == mProfileNode)
 814         {
 815            mProfileNode = -1;
 816            mSelProfNodeList.clear();
 817         }
 818
 819         mDeselectProfileNode = false;
 820      }
 821      // Else if we dragged a node, update the road
 822      else
 823      {
 824         S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
 825
 826         if(clickedNode == mProfileNode)
 827            mSelRoad->regenerate();       // This regens the road for collision purposes on the server
 828      }
 829   }
 830
 831   mouseUnlock();
 832}
 833
 834void GuiMeshRoadEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event)
 835{
 836   if ( mSelRoad != NULL && mMode == mAddNodeMode )
 837   {
 838      Point3F pos;
 839
 840      mSelRoad->disableCollision();
 841      if ( getStaticPos( event, pos ) )         
 842      {
 843         pos.z += mSelRoad->getNodeDepth( mSelNode ) * 0.5f;
 844         mSelRoad->setNodePosition( mSelNode, pos );
 845         mIsDirty = true;
 846      }
 847      mSelRoad->enableCollision();
 848
 849      return;
 850   }
 851   
 852   if ( mSelRoad != NULL && mSelNode != -1 )
 853   {
 854      mGizmo->on3DMouseMove( event );
 855      //mGizmo.collideAxisGizmo( event );
 856      //Con::printf( "SelectedAxis: %i", mGizmo.getSelectedAxis() );
 857   }
 858
 859   // Is cursor hovering over a river?
 860   if ( mMode == mSelectMeshRoadMode )
 861   {
 862      mHoverRoad = NULL;
 863
 864      Point3F startPnt = event.pos;
 865      Point3F endPnt = event.pos + event.vec * 1000.0f;
 866
 867      RayInfo ri;   
 868
 869      if ( gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri) )
 870      {         
 871         MeshRoad *pRoad = NULL;
 872         pRoad = dynamic_cast<MeshRoad*>(ri.object);         
 873
 874         // Do not select or edit a MeshRoad within a Prefab.         
 875         if ( pRoad && !Prefab::getPrefabByChild(pRoad) )
 876            mHoverRoad = pRoad;
 877      }
 878   }
 879
 880   // Is cursor over a node?
 881   if ( mHoverRoad )
 882   {
 883      MeshRoad *pRoad = NULL;
 884      S32 nodeIdx = -1;
 885      for ( SimSetIterator iter(mRoadSet); *iter; ++iter )
 886      {
 887         pRoad = static_cast<MeshRoad*>( *iter );
 888
 889         nodeIdx = _getNodeAtScreenPos( pRoad, event.mousePoint );
 890
 891         if ( nodeIdx != -1 )
 892         {
 893            mHoverRoad = pRoad;
 894            break;
 895         }
 896      }
 897
 898      mHoverNode = nodeIdx;
 899   }
 900}
 901
 902void GuiMeshRoadEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event)
 903{   
 904   if( MeshRoad::smShowRoadProfile && mProfileNode > 0 && mSelRoad)
 905   {
 906      // If we haven't already saved,
 907      // save an undo action to get back to this state,
 908      // before we make any modifications to the selected node.
 909      if ( !mSavedProfileDrag )
 910      {
 911         submitUndo( "Modify Profile Node" );
 912         mSavedProfileDrag = true;
 913         mIsDirty = true;
 914      }
 915
 916      U32 idx;
 917      Point3F pos, diff;
 918
 919      PlaneF xy( mSelRoad->mSlices[0].p2, -mSelRoad->mSlices[0].fvec );
 920      xy.intersect(event.pos, event.vec, &pos);
 921
 922      mSelRoad->mSideProfile.worldToObj(pos);
 923      diff = pos - mSelRoad->mSideProfile.mNodes[mProfileNode].getPosition();
 924
 925      for(U32 i=0; i < mSelProfNodeList.size(); i++)
 926      {
 927         idx = mSelProfNodeList[i];
 928         pos = mSelRoad->mSideProfile.mNodes[idx].getPosition();
 929         pos += diff;
 930
 931         if(pos.x < -mSelRoad->mSlices[0].width/2.0f)
 932            pos.x = -mSelRoad->mSlices[0].width/2.0f + 1e-6;
 933
 934         mSelRoad->mSideProfile.setNodePosition( idx, pos );
 935      }
 936
 937      mDeselectProfileNode = false;
 938      return;
 939   }
 940
 941   // Drags are only used to transform nodes
 942   if ( !mSelRoad || mSelNode == -1 ||
 943      ( mMode != mMovePointMode && mMode != mScalePointMode && mMode != mRotatePointMode ) )
 944      return;
 945
 946   // If we haven't already saved,
 947   // save an undo action to get back to this state,
 948   // before we make any modifications to the selected node.
 949   if ( !mSavedDrag )
 950   {
 951      submitUndo( "Modify Node" );
 952      mSavedDrag = true;
 953   }
 954
 955   // If shift is held and we haven't already copied the node,
 956   // make a copy of the selected node and select it.
 957   if ( event.modifier & SI_SHIFT && !mHasCopied && mSelRoad->isEndNode( mSelNode ) )
 958   {
 959      const MeshRoadNode &data = mSelRoad->getNode( mSelNode );
 960      
 961      U32 insertIdx = ( mSelNode == 0 ) ? 0 : U32_MAX;      
 962      U32 newNodeIdx = mSelRoad->insertNode( data.point, data.width, data.depth, data.normal, insertIdx );
 963      mIsDirty = true;
 964         
 965      mSelNode = -1;
 966      setSelectedNode( newNodeIdx );
 967
 968      mHasCopied = true;
 969   }
 970
 971   // Let the Gizmo handle the drag, eg, modify its transforms
 972   mGizmo->on3DMouseDragged( event );
 973   if ( mGizmo->isDirty() )
 974   {
 975      Point3F pos = mGizmo->getPosition();
 976      Point3F scale = mGizmo->getScale();      
 977      const MatrixF &mat = mGizmo->getTransform();
 978      VectorF normal;
 979      mat.getColumn( 2, &normal );
 980
 981      mSelRoad->setNode( pos, scale.x, scale.z, normal, mSelNode );
 982      mIsDirty = true;
 983      mGizmo->markClean();
 984   }
 985
 986   Con::executef( this, "onNodeModified", Con::getIntArg(mSelNode) );
 987}
 988
 989void GuiMeshRoadEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event)
 990{
 991   // nothing to do
 992}
 993
 994void GuiMeshRoadEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event)
 995{
 996   // nothing to do
 997}
 998
 999bool GuiMeshRoadEditorCtrl::onKeyDown(const GuiEvent& event)
1000{
1001   if( event.keyCode == KEY_RETURN && mMode == mAddNodeMode )
1002   {
1003      // Delete the node attached to the cursor.
1004      deleteSelectedNode();
1005      mMode = mAddMeshRoadMode;
1006      return true;
1007   }
1008
1009   return false;
1010}
1011
1012void GuiMeshRoadEditorCtrl::updateGuiInfo()
1013{
1014   // nothing to do
1015}
1016      
1017void GuiMeshRoadEditorCtrl::renderScene(const RectI & updateRect)
1018{
1019   //GFXDrawUtil *drawer = GFX->getDrawUtil();            
1020
1021   GFX->setStateBlock( mZDisableSB );
1022
1023   // get the projected size...
1024   GameConnection* connection = GameConnection::getConnectionToServer();
1025   if(!connection)
1026      return;
1027
1028   // Grab the camera's transform
1029   MatrixF mat;
1030   connection->getControlCameraTransform(0, &mat);
1031
1032   // Get the camera position
1033   Point3F camPos;
1034   mat.getColumn(3,&camPos);
1035
1036   // Set up transform
1037   if( mSelRoad )
1038   {
1039      MatrixF profileMat(true);
1040
1041      profileMat.setRow(0, mSelRoad->mSlices[0].rvec);
1042      profileMat.setRow(1, mSelRoad->mSlices[0].uvec);
1043      profileMat.setRow(2, -mSelRoad->mSlices[0].fvec);
1044
1045      mSelRoad->mSideProfile.setTransform(profileMat, mSelRoad->mSlices[0].p2);
1046   }
1047
1048   if ( mHoverRoad && mHoverRoad != mSelRoad )
1049   {
1050      _drawSpline( mHoverRoad, mHoverSplineColor );
1051   }
1052
1053   if ( mSelRoad )
1054   {
1055      _drawSpline( mSelRoad, mSelectedSplineColor );            
1056
1057      // Render Gizmo for selected node if were in either of the three transform modes
1058      if ( mSelNode != -1 && ( mMode == mMovePointMode || mMode == mScalePointMode || mMode == mRotatePointMode ) )
1059      {
1060         if( mMode == mMovePointMode )
1061         {
1062            mGizmo->getProfile()->mode = MoveMode;
1063         }
1064         else if( mMode == mScalePointMode )
1065         {
1066            mGizmo->getProfile()->mode = ScaleMode;
1067         }
1068         else if( mMode == mRotatePointMode )
1069         {
1070            mGizmo->getProfile()->mode = RotateMode;
1071         }
1072
1073         const MeshRoadNode &node = mSelRoad->mNodes[mSelNode];
1074
1075         MatrixF objMat = mSelRoad->getNodeTransform(mSelNode);      
1076         Point3F objScale( node.width, 1.0f, node.depth );
1077         Point3F worldPos = node.point;
1078
1079         mGizmo->set( objMat, worldPos, objScale );
1080
1081         mGizmo->renderGizmo( mLastCameraQuery.cameraMatrix, mLastCameraQuery.fov );
1082         
1083         // Render Gizmo text
1084         mGizmo->renderText( mSaveViewport, mSaveModelview, mSaveProjection );     
1085      }    
1086   }
1087
1088   DebugDrawer::get()->render();
1089
1090   // Now draw all the 2d stuff!
1091   GFX->setClipRect(updateRect); 
1092   
1093   // Draw Control nodes for selecting and highlighted rivers
1094   if ( mHoverRoad )
1095      _drawControlNodes( mHoverRoad, mHoverSplineColor );
1096   if ( mSelRoad )
1097      _drawControlNodes( mSelRoad, mSelectedSplineColor );
1098
1099   if(MeshRoad::smShowRoadProfile)
1100   {
1101      char buf[64];
1102      Point2I posi;
1103
1104      posi.x = 10;
1105      posi.y = updateRect.len_y() - 80;
1106
1107      GFX->getDrawUtil()->setBitmapModulation(ColorI(128, 128, 128));
1108      dStrcpy(buf, "Reset Profile: Double-click Start Node", 64);
1109      GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
1110      posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
1111      dStrcpy(buf, "Move Node: Click and Drag Node", 64);
1112      GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
1113      posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
1114      dStrcpy(buf, "Select Multiple Nodes: Ctrl-click Nodes", 64);
1115      GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
1116      posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
1117      dStrcpy(buf, "Toggle Material: Shift-click Spline Segment", 64);
1118      GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
1119      posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
1120      dStrcpy(buf, "Toggle Smoothing: Shift-click Node", 64);
1121      GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
1122      posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
1123      dStrcpy(buf, "Delete Node: Alt-click Node", 64);
1124      GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
1125      posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
1126      dStrcpy(buf, "Add Node: Ctrl-click Spline", 64);
1127      GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
1128   }
1129} 
1130
1131S32 GuiMeshRoadEditorCtrl::_getNodeAtScreenPos( const MeshRoad *pRoad, const Point2I &posi )
1132{
1133   for ( U32 i = 0; i < pRoad->mNodes.size(); i++ )
1134   {
1135      const Point3F &nodePos = pRoad->mNodes[i].point;
1136
1137      Point3F screenPos;
1138      project( nodePos, &screenPos );
1139
1140      if ( screenPos.z < 0.0f )
1141         continue;
1142
1143      Point2I screenPosI( (S32)screenPos.x, (S32)screenPos.y );
1144
1145      RectI nodeScreenRect( screenPosI - mNodeHalfSize, mNodeHalfSize * 2 );
1146
1147      if ( nodeScreenRect.pointInRect(posi) )
1148      {
1149         // we found a hit!         
1150         return i;         
1151      }           
1152   }      
1153
1154   return -1;
1155}
1156
1157S32 GuiMeshRoadEditorCtrl::_getProfileNodeAtScreenPos( MeshRoadProfile *pProfile, const Point2I &posi)
1158{
1159   for ( U32 i = 0; i < pProfile->mNodes.size(); i++ )
1160   {
1161      Point3F nodePos;
1162      pProfile->getNodeWorldPos(i, nodePos);
1163
1164      Point3F screenPos;
1165      project( nodePos, &screenPos );
1166
1167      if ( screenPos.z < 0.0f )
1168         continue;
1169
1170      Point2I screenPosI( (S32)screenPos.x, (S32)screenPos.y );
1171
1172      RectI nodeScreenRect( screenPosI - mNodeHalfSize, mNodeHalfSize * 2 );
1173
1174      if ( nodeScreenRect.pointInRect(posi) )
1175      {
1176         // we found a hit!
1177         return i;
1178      }
1179   }
1180
1181   return -1;
1182}
1183
1184void GuiMeshRoadEditorCtrl::_drawSpline( MeshRoad *river, const ColorI &color )
1185{
1186   if ( river->mSlices.size() <= 1 )
1187      return;
1188
1189   if ( MeshRoad::smShowSpline )
1190   {
1191      // Render the River center-line
1192      if( MeshRoad::smShowRoadProfile )
1193         PrimBuild::color( ColorI(100,100,100) );
1194      else
1195         PrimBuild::color( color );
1196
1197      PrimBuild::begin( GFXLineStrip, river->mSlices.size() );
1198      for ( U32 i = 0; i < river->mSlices.size(); i++ )
1199      {                       
1200         PrimBuild::vertex3fv( river->mSlices[i].p1 );            
1201      }
1202      PrimBuild::end();
1203   }
1204   
1205   if ( MeshRoad::smWireframe )
1206   {
1207      // Left-side line
1208      PrimBuild::color3i( 100, 100, 100 );
1209      PrimBuild::begin( GFXLineStrip, river->mSlices.size() );            
1210      for ( U32 i = 0; i < river->mSlices.size(); i++ )
1211      {                       
1212         PrimBuild::vertex3fv( river->mSlices[i].p0 );            
1213      }
1214      PrimBuild::end();
1215
1216      // Right-side line
1217      PrimBuild::begin( GFXLineStrip, river->mSlices.size() );            
1218      for ( U32 i = 0; i < river->mSlices.size(); i++ )
1219      {                       
1220         PrimBuild::vertex3fv( river->mSlices[i].p2 );            
1221      }
1222      PrimBuild::end();
1223
1224      // Cross-sections
1225      PrimBuild::begin( GFXLineList, river->mSlices.size() * 2 );            
1226      for ( U32 i = 0; i < river->mSlices.size(); i++ )
1227      {                       
1228         PrimBuild::vertex3fv( river->mSlices[i].p0 );
1229         PrimBuild::vertex3fv( river->mSlices[i].p2 );            
1230      }
1231      PrimBuild::end();
1232   }
1233
1234   // If we are in Profile Edit Mode, draw the profile spline and node normals
1235   if ( MeshRoad::smShowRoadProfile )
1236   {
1237      Point3F nodePos;
1238      Point3F normEndPos;
1239      U32 numSide, numTop, numBottom;
1240
1241      numSide = numTop = numBottom = 0;
1242
1243      for ( U32 i = 0; i < river->mSideProfile.mSegMtrls.size(); i++ )
1244      {
1245         switch(river->mSideProfile.mSegMtrls[i])
1246         {
1247         case MeshRoad::Side:    numSide++;     break;
1248         case MeshRoad::Top:     numTop++;      break;
1249         case MeshRoad::Bottom:  numBottom++;   break;
1250         }
1251      }
1252
1253      // Render the profile spline
1254      // Side
1255      if(numSide)
1256      {
1257         PrimBuild::color( mProfileColor );
1258         PrimBuild::begin( GFXLineList, 2*numSide );
1259         for ( U32 i = 0; i < river->mSideProfile.mSegMtrls.size(); i++ )
1260         {
1261            if(river->mSideProfile.mSegMtrls[i] == MeshRoad::Side)
1262            {
1263               river->mSideProfile.getNodeWorldPos(i, nodePos);
1264               PrimBuild::vertex3fv( nodePos );
1265
1266               river->mSideProfile.getNodeWorldPos(i+1, nodePos);
1267               PrimBuild::vertex3fv( nodePos );
1268            }
1269         }
1270         PrimBuild::end();
1271      }
1272
1273      // Top
1274      if(numTop)
1275      {
1276         PrimBuild::color( ColorI(0,255,0) );
1277         PrimBuild::begin( GFXLineList, 2*numTop );
1278         for ( U32 i = 0; i < river->mSideProfile.mSegMtrls.size(); i++ )
1279         {
1280            if(river->mSideProfile.mSegMtrls[i] == MeshRoad::Top)
1281            {
1282               river->mSideProfile.getNodeWorldPos(i, nodePos);
1283               PrimBuild::vertex3fv( nodePos );
1284
1285               river->mSideProfile.getNodeWorldPos(i+1, nodePos);
1286               PrimBuild::vertex3fv( nodePos );
1287            }
1288         }
1289         PrimBuild::end();
1290      }
1291
1292      // Bottom
1293      if(numBottom)
1294      {
1295         PrimBuild::color( ColorI(255,0,255) );
1296         PrimBuild::begin( GFXLineList, 2*numBottom );
1297         for ( U32 i = 0; i < river->mSideProfile.mSegMtrls.size(); i++ )
1298         {
1299            if(river->mSideProfile.mSegMtrls[i] == MeshRoad::Bottom)
1300            {
1301               river->mSideProfile.getNodeWorldPos(i, nodePos);
1302               PrimBuild::vertex3fv( nodePos );
1303
1304               river->mSideProfile.getNodeWorldPos(i+1, nodePos);
1305               PrimBuild::vertex3fv( nodePos );
1306            }
1307         }
1308         PrimBuild::end();
1309      }
1310
1311      // Render node normals
1312      PrimBuild::color( ColorI(255,0,0) );
1313      PrimBuild::begin( GFXLineList, 4*river->mSideProfile.mNodes.size() - 4 );
1314      for ( U32 i = 0; i < river->mSideProfile.mNodes.size()-1; i++ )
1315      {
1316         for( U32 j = 0; j < 2; j++)
1317         {
1318            river->mSideProfile.getNodeWorldPos(i+j, nodePos);
1319            PrimBuild::vertex3fv( nodePos );
1320
1321            river->mSideProfile.getNormWorldPos(2*i+j, normEndPos);
1322            PrimBuild::vertex3fv( normEndPos );
1323         }
1324      }
1325      PrimBuild::end();
1326   }
1327
1328   // Segment 
1329}
1330
1331void GuiMeshRoadEditorCtrl::_drawControlNodes( MeshRoad *river, const ColorI &color )
1332{
1333   if ( !MeshRoad::smShowSpline )
1334      return;
1335
1336   RectI bounds = getBounds();
1337
1338   GFXDrawUtil *drawer = GFX->getDrawUtil();
1339
1340   bool isSelected = ( river == mSelRoad );
1341   bool isHighlighted = ( river == mHoverRoad );
1342   
1343   for ( U32 i = 0; i < river->mNodes.size(); i++ )
1344   {
1345      if ( false && isSelected && mSelNode == i  )
1346         continue;
1347
1348      const Point3F &wpos = river->mNodes[i].point;
1349
1350      Point3F spos;
1351      project( wpos, &spos );                  
1352
1353      if ( spos.z > 1.0f )
1354         continue;
1355
1356      Point2I posi;
1357      posi.x = spos.x;
1358      posi.y = spos.y;
1359
1360      if ( !bounds.pointInRect( posi ) )
1361         continue;
1362
1363      ColorI theColor = color;
1364      Point2I nodeHalfSize = mNodeHalfSize;
1365            
1366      if ( isHighlighted && mHoverNode == i )
1367      {
1368         //theColor = mHoverNodeColor;
1369         nodeHalfSize += Point2I(2,2);
1370      }
1371
1372      if ( isSelected )
1373      {   
1374         if ( mSelNode == i )
1375         {
1376            theColor.set(0,0,255);
1377         }
1378         else if ( i == 0 )
1379         {
1380            theColor.set(0,255,0);
1381         }
1382         else if ( i == river->mNodes.size() - 1 )
1383         {
1384            theColor.set(255,0,0);
1385         }         
1386      }
1387
1388      if( MeshRoad::smShowRoadProfile && isSelected )
1389         theColor.set(100,100,100);
1390
1391      drawer->drawRectFill( posi - nodeHalfSize, posi + nodeHalfSize, theColor );
1392   }
1393
1394   // Draw profile control nodes
1395   if( MeshRoad::smShowRoadProfile && isSelected )
1396   {
1397      Point3F wpos;
1398      Point3F spos;
1399      Point2I posi;
1400      ColorI theColor;
1401
1402      for( U32 i = 0; i < river->mSideProfile.mNodes.size(); i++)
1403      {
1404         river->mSideProfile.getNodeWorldPos(i, wpos);
1405
1406         project( wpos, &spos );
1407
1408         if ( spos.z > 1.0f )
1409            continue;
1410
1411         posi.x = spos.x;
1412         posi.y = spos.y;
1413
1414         if ( !bounds.pointInRect( posi ) )
1415            continue;
1416
1417         if(i == 0)
1418            theColor.set(mProfileColor.red/3, mProfileColor.green/3, mProfileColor.blue/3,255);
1419         else
1420            theColor.set(mProfileColor,255);
1421
1422         if( mSelProfNodeList.find_next(i) != -1 )
1423            theColor.set(0,0,255);
1424
1425         drawer->drawRectFill( posi - mNodeHalfSize, posi + mNodeHalfSize, theColor );
1426      }
1427   }
1428}
1429
1430bool GuiMeshRoadEditorCtrl::getStaticPos( const Gui3DMouseEvent & event, Point3F &tpos )
1431{     
1432   // Find clicked point on the terrain
1433
1434   Point3F startPnt = event.pos;
1435   Point3F endPnt = event.pos + event.vec * 1000.0f;
1436
1437   RayInfo ri;
1438   bool hit;         
1439         
1440   hit = gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri);    
1441   tpos = ri.point;
1442   
1443   return hit;
1444}
1445
1446void GuiMeshRoadEditorCtrl::deleteSelectedNode()
1447{    
1448   if ( !mSelRoad || mSelNode == -1 )
1449      return;
1450
1451   // If the Road has only two nodes remaining,
1452   // delete the whole Road.
1453   if ( mSelRoad->mNodes.size() <= 2 )
1454   {      
1455      deleteSelectedRoad( mMode != mAddNodeMode );
1456   }
1457   else
1458   {
1459      if ( mMode != mAddNodeMode )
1460         submitUndo( "Delete Node" );
1461
1462      // Delete the SelectedNode of the SelectedRoad
1463      mSelRoad->deleteNode( mSelNode );
1464
1465      // We deleted the Node but not the Road (it has nodes left)
1466      // so decrement the currently selected node.
1467      if ( mSelRoad->mNodes.size() <= mSelNode )
1468         setSelectedNode( mSelNode - 1 );
1469      else
1470      {
1471         // force gizmo to update to the selected nodes position
1472         // the index didn't change but the node it refers to did.
1473         U32 i = mSelNode;
1474         mSelNode = -1;
1475         setSelectedNode( i );
1476      }
1477   }
1478
1479   // If you were in addNodeMode, 
1480   // deleting a node should ends it.
1481   //mMode = smNormalMode;
1482}
1483
1484void GuiMeshRoadEditorCtrl::deleteSelectedRoad( bool undoAble )
1485{
1486   AssertFatal( mSelRoad != NULL, "GuiMeshRoadEditorCtrl::deleteSelectedRoad() - No Road is selected" );
1487
1488   // Not undoAble? Just delete it.
1489   if ( !undoAble )
1490   {
1491      mSelRoad->deleteObject();
1492      mIsDirty = true;
1493      Con::executef( this, "onRoadSelected" );
1494      mSelNode = -1;
1495
1496      return;
1497   }
1498
1499   // Grab the mission editor undo manager.
1500   UndoManager *undoMan = NULL;
1501   if ( !Sim::findObject( "EUndoManager", undoMan ) )
1502   {
1503      // Couldn't find it? Well just delete the Road.
1504      Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" );    
1505      return;
1506   }
1507   else
1508   {
1509      // Create the UndoAction.
1510      MEDeleteUndoAction *action = new MEDeleteUndoAction("Deleted Road");
1511      action->deleteObject( mSelRoad );
1512      mIsDirty = true;
1513
1514      // Submit it.               
1515      undoMan->addAction( action );
1516   }
1517
1518   // ScriptCallback with 'NULL' parameter for no Road currently selected.
1519   Con::executef( this, "onRoadSelected" );
1520
1521   // Clear the SelectedNode (it has been deleted along with the River).  
1522   setSelectedNode( -1 );
1523   mSelNode = -1;
1524}
1525
1526void GuiMeshRoadEditorCtrl::setMode( String mode, bool sourceShortcut = false )
1527{
1528   mMode = mode;
1529
1530   if( sourceShortcut )
1531      Con::executef( this, "paletteSync", mode );
1532}
1533
1534void GuiMeshRoadEditorCtrl::setSelectedRoad( MeshRoad *road )
1535{
1536   mSelRoad = road;
1537
1538   if ( mSelRoad != NULL )
1539      Con::executef( this, "onRoadSelected", road->getIdString() );
1540   else
1541      Con::executef( this, "onRoadSelected" );
1542
1543   if ( mSelRoad != road )
1544      setSelectedNode(-1);
1545}
1546
1547void GuiMeshRoadEditorCtrl::setNodeWidth( F32 width )
1548{
1549   if ( mSelRoad && mSelNode != -1 )
1550   {
1551      mSelRoad->setNodeWidth( mSelNode, width );
1552      mIsDirty = true;
1553   }
1554}
1555
1556F32 GuiMeshRoadEditorCtrl::getNodeWidth()
1557{
1558   if ( mSelRoad && mSelNode != -1 )
1559      return mSelRoad->getNodeWidth( mSelNode );
1560
1561   return 0.0f;   
1562}
1563
1564void GuiMeshRoadEditorCtrl::setNodeDepth(F32 depth)
1565{
1566   if ( mSelRoad && mSelNode != -1 )
1567   {
1568      mSelRoad->setNodeDepth( mSelNode, depth );
1569      mIsDirty = true;
1570   }
1571}
1572
1573F32 GuiMeshRoadEditorCtrl::getNodeDepth()
1574{
1575   if ( mSelRoad && mSelNode != -1 )
1576      return mSelRoad->getNodeDepth( mSelNode );
1577
1578   return 0.0f;
1579}
1580
1581void GuiMeshRoadEditorCtrl::setNodePosition(const Point3F& pos)
1582{
1583   if ( mSelRoad && mSelNode != -1 )
1584   {
1585      mSelRoad->setNodePosition( mSelNode, pos );
1586      mIsDirty = true;
1587   }
1588}
1589
1590Point3F GuiMeshRoadEditorCtrl::getNodePosition()
1591{
1592   if ( mSelRoad && mSelNode != -1 )
1593      return mSelRoad->getNodePosition( mSelNode );
1594
1595   return Point3F( 0, 0, 0 );   
1596}
1597
1598void GuiMeshRoadEditorCtrl::setNodeNormal( const VectorF &normal )
1599{
1600   if ( mSelRoad && mSelNode != -1 )
1601   {
1602      mSelRoad->setNodeNormal( mSelNode, normal );
1603      mIsDirty = true;
1604   }
1605}
1606
1607VectorF GuiMeshRoadEditorCtrl::getNodeNormal()
1608{
1609   if ( mSelRoad && mSelNode != -1 )
1610      return mSelRoad->getNodeNormal( mSelNode );
1611
1612   return VectorF::Zero;
1613}
1614
1615void GuiMeshRoadEditorCtrl::setSelectedNode( S32 node )
1616{
1617   if ( mSelNode == node )
1618      return;
1619
1620   mSelNode = node;
1621   if ( mSelNode != -1 )
1622   {
1623      const MeshRoadNode &curNode = mSelRoad->mNodes[mSelNode];
1624
1625      MatrixF objMat = mSelRoad->getNodeTransform(mSelNode);      
1626      Point3F objScale(curNode.width, 1.0f, curNode.depth );
1627      Point3F worldPos = curNode.point;
1628      
1629      mGizmo->set( objMat, worldPos, objScale );
1630   }
1631   
1632   if ( mSelNode != -1 )
1633      Con::executef( this, "onNodeSelected", Con::getIntArg(mSelNode) );
1634   else
1635      Con::executef( this, "onNodeSelected", Con::getIntArg(-1) );
1636}
1637
1638void GuiMeshRoadEditorCtrl::submitUndo( const UTF8 *name )
1639{
1640   // Grab the mission editor undo manager.
1641   UndoManager *undoMan = NULL;
1642   if ( !Sim::findObject( "EUndoManager", undoMan ) )
1643   {
1644      Con::errorf( "GuiMeshRoadEditorCtrl::submitUndo() - EUndoManager not found!" );
1645      return;           
1646   }
1647
1648   // Setup the action.
1649   GuiMeshRoadEditorUndoAction *action = new GuiMeshRoadEditorUndoAction( name );
1650
1651   action->mObjId = mSelRoad->getId();
1652   //action->mMetersPerSegment = mSelRoad->mMetersPerSegment;
1653   action->mEditor = this;
1654
1655   for( U32 i = 0; i < mSelRoad->mNodes.size(); i++ )
1656   {
1657      action->mNodes.push_back( mSelRoad->mNodes[i] );      
1658   }
1659      
1660   // Save profile nodes and materials
1661   for( U32 i = 0; i < mSelRoad->mSideProfile.mNodes.size(); i++)
1662   {
1663      action->mProfileNodes.push_back( mSelRoad->mSideProfile.mNodes[i] );
1664
1665      if(i)
1666         action->mProfileMtrls.push_back( mSelRoad->mSideProfile.mSegMtrls[i-1] );
1667   }
1668
1669   undoMan->addAction( action );
1670}
1671
1672void GuiMeshRoadEditorCtrl::matchTerrainToRoad()
1673{
1674   if ( !mSelRoad )
1675      return;
1676
1677   // Not implemented, but a potentially useful idea.
1678   // Move manipulate the terrain so that the MeshRoad appears to be sitting 
1679   // on top of it.
1680
1681   // The opposite could also be useful, manipulate the MeshRoad to line up
1682   // with the terrain underneath it.
1683}
1684
1685DefineEngineMethod( GuiMeshRoadEditorCtrl, deleteNode, void, (), , "deleteNode()" )
1686{
1687   object->deleteSelectedNode();
1688}
1689
1690DefineEngineMethod( GuiMeshRoadEditorCtrl, getMode, const char*, (), , "" )
1691{
1692   return object->getMode();
1693}
1694
1695DefineEngineMethod( GuiMeshRoadEditorCtrl, setMode, void, (const char * mode), , "setMode( String mode )" )
1696{
1697   String newMode = ( mode );
1698   object->setMode( newMode );
1699}
1700
1701DefineEngineMethod( GuiMeshRoadEditorCtrl, getNodeWidth, F32, (), , "" )
1702{
1703   return object->getNodeWidth();
1704}
1705
1706DefineEngineMethod( GuiMeshRoadEditorCtrl, setNodeWidth, void, ( F32 width ), , "" )
1707{
1708   object->setNodeWidth( width );
1709}
1710
1711DefineEngineMethod( GuiMeshRoadEditorCtrl, getNodeDepth, F32, (), , "" )
1712{
1713   return object->getNodeDepth();
1714}
1715
1716DefineEngineMethod( GuiMeshRoadEditorCtrl, setNodeDepth, void, ( F32 depth ), , "" )
1717{
1718   object->setNodeDepth( depth );
1719}
1720
1721DefineEngineMethod( GuiMeshRoadEditorCtrl, getNodePosition, Point3F, (), , "" )
1722{
1723
1724   return object->getNodePosition();
1725}
1726
1727DefineEngineMethod( GuiMeshRoadEditorCtrl, setNodePosition, void, (Point3F pos), , "" )
1728{
1729
1730   object->setNodePosition( pos );
1731}
1732
1733DefineEngineMethod( GuiMeshRoadEditorCtrl, getNodeNormal, Point3F, (), , "" )
1734{
1735
1736   return object->getNodeNormal();
1737}
1738
1739DefineEngineMethod( GuiMeshRoadEditorCtrl, setNodeNormal, void, (Point3F normal), , "" )
1740{
1741
1742   object->setNodeNormal( normal );
1743}
1744
1745DefineEngineMethod( GuiMeshRoadEditorCtrl, setSelectedRoad, void, (const char * objName), (""), "" )
1746{
1747   if ( String::isEmpty(objName) )
1748      object->setSelectedRoad(NULL);
1749   else
1750   {
1751      MeshRoad *road = NULL;
1752      if ( Sim::findObject( objName, road ) )
1753         object->setSelectedRoad(road);
1754   }
1755}
1756
1757DefineEngineMethod( GuiMeshRoadEditorCtrl, getSelectedRoad, S32, (), , "" )
1758{
1759   MeshRoad *road = object->getSelectedRoad();
1760   if ( !road )
1761      return NULL;
1762
1763   return road->getId();
1764}
1765
1766DefineEngineMethod( GuiMeshRoadEditorCtrl, regenerate, void, (), , "" )
1767{
1768   MeshRoad *road = object->getSelectedRoad();
1769   if ( road )
1770      road->regenerate();
1771}
1772
1773DefineEngineMethod( GuiMeshRoadEditorCtrl, matchTerrainToRoad, void, (), , "" )
1774{
1775   object->matchTerrainToRoad();
1776}
1777