guiMeshRoadEditorCtrl.cpp
Engine/source/environment/editors/guiMeshRoadEditorCtrl.cpp
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