//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "environment/editors/guiRoadEditorCtrl.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" #include "scene/sceneManager.h" #include "collision/collision.h" #include "math/util/frustum.h" #include "gfx/gfxPrimitiveBuffer.h" #include "gfx/gfxTextureHandle.h" #include "gfx/gfxTransformSaver.h" #include "gfx/gfxDrawUtil.h" #include "gfx/primBuilder.h" #include "T3D/gameBase/gameConnection.h" #include "gui/core/guiCanvas.h" #include "gui/buttons/guiButtonCtrl.h" #include "gui/worldEditor/undoActions.h" #include "materials/materialDefinition.h" #include "T3D/Scene.h" IMPLEMENT_CONOBJECT(GuiRoadEditorCtrl); ConsoleDocClass( GuiRoadEditorCtrl, "@brief GUI tool that makes up the Decal Road Editor\n\n" "Editor use only.\n\n" "@internal" ); GuiRoadEditorCtrl::GuiRoadEditorCtrl() { // Each of the mode names directly correlates with the River Editor's // tool palette mSelectRoadMode = "RoadEditorSelectMode"; mAddRoadMode = "RoadEditorAddRoadMode"; mMovePointMode = "RoadEditorMoveMode"; mScalePointMode = "RoadEditorScaleMode"; mAddNodeMode = "RoadEditorAddNodeMode"; mInsertPointMode = "RoadEditorInsertPointMode"; mRemovePointMode = "RoadEditorRemovePointMode"; mMode = mSelectRoadMode; mRoadSet = NULL; mSelNode = -1; mHoverNode = -1; mSelRoad = NULL; mHoverRoad = NULL; mAddNodeIdx = 0; mDefaultWidth = 10.0f; mInsertIdx = -1; mStartWidth = -1.0f; mStartX = 0; mNodeHalfSize.set(4,4); mHoverSplineColor.set( 255,0,0,255 ); mSelectedSplineColor.set( 0,255,0,255 ); mHoverNodeColor.set( 255,255,255,255 ); mSavedDrag = false; mIsDirty = false; mMaterialAssetId = Con::getVariable("$DecalRoadEditor::defaultMaterialAsset"); } GuiRoadEditorCtrl::~GuiRoadEditorCtrl() { // nothing to do } void GuiRoadEditorUndoAction::undo() { DecalRoad *road = NULL; if ( !Sim::findObject( mObjId, road ) ) return; // Temporarily save the roads current data. String materialAssetId = road->mMaterialAssetId; F32 textureLength = road->mTextureLength; F32 breakAngle = road->mBreakAngle; F32 segmentsPerBatch = road->mSegmentsPerBatch; Vector nodes; nodes.merge( road->mNodes ); // Restore the Road properties saved in the UndoAction road->_setMaterial(materialAssetId); road->mBreakAngle = breakAngle; road->mSegmentsPerBatch = segmentsPerBatch; road->mTextureLength = textureLength; road->inspectPostApply(); // Restore the Nodes saved in the UndoAction road->mNodes.clear(); for ( U32 i = 0; i < mNodes.size(); i++ ) { road->_addNode( mNodes[i].point, mNodes[i].width ); } // Regenerate the road road->regenerate(); // If applicable set the selected road and node mRoadEditor->mSelRoad = road; mRoadEditor->mSelNode = -1; // Now save the previous Road data in this UndoAction // since an undo action must become a redo action and vice-versa mMaterialAssetId = materialAssetId; mBreakAngle = breakAngle; mSegmentsPerBatch = segmentsPerBatch; mTextureLength = textureLength; mNodes.clear(); mNodes.merge( nodes ); } bool GuiRoadEditorCtrl::onAdd() { if( !Parent::onAdd() ) return false; mRoadSet = DecalRoad::getServerSet(); GFXStateBlockDesc desc; desc.setCullMode( GFXCullNone ); desc.setBlend(false); desc.setZReadWrite( false, false ); mZDisableSB = GFX->createStateBlock(desc); return true; } void GuiRoadEditorCtrl::initPersistFields() { docsURL; addField( "DefaultWidth", TypeF32, Offset( mDefaultWidth, GuiRoadEditorCtrl ) ); addField( "HoverSplineColor", TypeColorI, Offset( mHoverSplineColor, GuiRoadEditorCtrl ) ); addField( "SelectedSplineColor", TypeColorI, Offset( mSelectedSplineColor, GuiRoadEditorCtrl ) ); addField( "HoverNodeColor", TypeColorI, Offset( mHoverNodeColor, GuiRoadEditorCtrl ) ); addField( "isDirty", TypeBool, Offset( mIsDirty, GuiRoadEditorCtrl ) ); INITPERSISTFIELD_MATERIALASSET(Material, GuiRoadEditorCtrl, "Default Material used by the Road Editor on road creation."); //addField( "MoveNodeCursor", TYPEID< SimObject >(), Offset( mMoveNodeCursor, GuiRoadEditorCtrl) ); //addField( "AddNodeCursor", TYPEID< SimObject >(), Offset( mAddNodeCursor, GuiRoadEditorCtrl) ); //addField( "InsertNodeCursor", TYPEID< SimObject >(), Offset( mInsertNodeCursor, GuiRoadEditorCtrl) ); //addField( "ResizeNodeCursor", TYPEID< SimObject >(), Offset( mResizeNodeCursor, GuiRoadEditorCtrl) ); Parent::initPersistFields(); } void GuiRoadEditorCtrl::onSleep() { Parent::onSleep(); mMode = mSelectRoadMode; mHoverNode = -1; mHoverRoad = NULL; setSelectedNode(-1); } void GuiRoadEditorCtrl::get3DCursor( GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event_ ) { //cursor = mAddNodeCursor; //visible = false; cursor = NULL; visible = false; GuiCanvas *root = getRoot(); if ( !root ) return; S32 currCursor = PlatformCursorController::curArrow; if ( root->mCursorChanged == currCursor ) return; PlatformWindow *window = root->getPlatformWindow(); PlatformCursorController *controller = window->getCursorController(); // We've already changed the cursor, // so set it back before we change it again. if( root->mCursorChanged != -1) controller->popCursor(); // Now change the cursor shape controller->pushCursor(currCursor); root->mCursorChanged = currCursor; } void GuiRoadEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) { if ( !isFirstResponder() ) setFirstResponder(); // Get the clicked terrain position. Point3F tPos; if ( !getTerrainPos( event, tPos ) ) return; mouseLock(); // Find any road / node at the clicked position. // TODO: handle overlapping roads/nodes somehow, cycle through them. DecalRoad *roadPtr = NULL; S32 closestNodeIdx = -1; F32 closestDist = F32_MAX; DecalRoad *closestNodeRoad = NULL; // First, find the closest node in any road to the clicked position. for ( SimSetIterator iter(mRoadSet); *iter; ++iter ) { roadPtr = static_cast( *iter ); U32 idx; if ( roadPtr->getClosestNode( tPos, idx ) ) { Point3F nodePos = roadPtr->getNodePosition(idx); F32 dist = ( nodePos - tPos ).len(); if ( dist < closestDist ) { closestNodeIdx = idx; closestDist = dist; closestNodeRoad = roadPtr; } } } // // Second, determine if the screen-space node rectangle // contains the clicked position. bool nodeClicked = false; S32 clickedNodeIdx = -1; if ( closestNodeIdx != -1 ) { Point3F nodePos = closestNodeRoad->getNodePosition( closestNodeIdx ); Point3F temp; project( nodePos, &temp ); Point2I screenPos( temp.x, temp.y ); RectI nodeRect( screenPos - mNodeHalfSize, mNodeHalfSize * 2 ); nodeClicked = nodeRect.pointInRect( event.mousePoint ); if ( nodeClicked ) clickedNodeIdx = closestNodeIdx; } // // Determine the clickedRoad // DecalRoad *clickedRoadPtr = NULL; U32 insertNodeIdx = 0; if ( nodeClicked && (mSelRoad == NULL || closestNodeRoad == mSelRoad) ) { // If a node was clicked, the owning road is always // considered the clicked road. clickedRoadPtr = closestNodeRoad; } else { // check the selected road first if ( mSelRoad != NULL && mSelRoad->containsPoint( tPos, &insertNodeIdx ) ) { clickedRoadPtr = mSelRoad; nodeClicked = false; clickedNodeIdx = -1; } else { // Otherwise, we must ask each road if it contains // the clicked pos. for ( SimSetIterator iter(mRoadSet); *iter; ++iter ) { roadPtr = static_cast( *iter ); if ( roadPtr->containsPoint( tPos, &insertNodeIdx ) ) { clickedRoadPtr = roadPtr; break; } } } } // shortcuts bool dblClick = ( event.mouseClickCount > 1 ); if( dblClick ) { if( mMode == mSelectRoadMode ) { setMode( mAddRoadMode, true ); return; } if( mMode == mAddNodeMode ) { // Delete the node attached to the cursor. deleteSelectedNode(); mMode = mAddRoadMode; return; } } //this check is here in order to bounce back from deleting a whole road with ctrl+z //this check places the editor back into addroadmode if ( mMode == mAddNodeMode ) { if ( !mSelRoad ) mMode = mAddRoadMode; } if ( mMode == mSelectRoadMode ) { // Did not click on a road or a node. if ( !clickedRoadPtr ) { setSelectedRoad( NULL ); setSelectedNode( -1 ); return; } // Clicked on a road that wasn't the currently selected road. if ( clickedRoadPtr != mSelRoad ) { setSelectedRoad( clickedRoadPtr ); setSelectedNode( -1 ); return; } // Clicked on a node in the currently selected road that wasn't // the currently selected node. if ( nodeClicked ) { setSelectedNode( clickedNodeIdx ); return; } // Clicked a position on the currently selected road // that did not contain a node. //U32 newNode = clickedRoadPtr->insertNode( tPos, mDefaultWidth, insertNodeIdx ); //setSelectedNode( newNode ); } else if ( mMode == mAddRoadMode ) { if ( nodeClicked && clickedRoadPtr ) { // A double-click on a node in Normal mode means set AddNode mode. if ( clickedNodeIdx == 0 ) { setSelectedRoad( clickedRoadPtr ); setSelectedNode( clickedNodeIdx ); mAddNodeIdx = clickedNodeIdx; mMode = mAddNodeMode; mSelNode = mSelRoad->insertNode( tPos, mDefaultWidth, mAddNodeIdx ); mIsDirty = true; return; } else if ( clickedNodeIdx == clickedRoadPtr->mNodes.size() - 1 ) { setSelectedRoad( clickedRoadPtr ); setSelectedNode( clickedNodeIdx ); mAddNodeIdx = U32_MAX; mMode = mAddNodeMode; mSelNode = mSelRoad->addNode( tPos, mDefaultWidth ); mIsDirty = true; setSelectedNode( mSelNode ); return; } } DecalRoad *newRoad = new DecalRoad; if (mMaterialAsset.notNull()) newRoad->_setMaterial(mMaterialAssetId); newRoad->registerObject(); // Add to scene Scene* scene = Scene::getRootScene(); if ( !scene ) Con::errorf( "GuiDecalRoadEditorCtrl - could not find scene to add new DecalRoad" ); else scene->addObject( newRoad ); newRoad->insertNode( tPos, mDefaultWidth, 0 ); U32 newNode = newRoad->insertNode( tPos, mDefaultWidth, 1 ); // Always add to the end of the road, the first node is the start. mAddNodeIdx = U32_MAX; setSelectedRoad( newRoad ); setSelectedNode( newNode ); mMode = mAddNodeMode; // Disable the hover node while in addNodeMode, we // don't want some random node enlarged. mHoverNode = -1; // Grab the mission editor undo manager. UndoManager *undoMan = NULL; if ( !Sim::findObject( "EUndoManager", undoMan ) ) { Con::errorf( "GuiRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); return; } // Create the UndoAction. MECreateUndoAction *action = new MECreateUndoAction("Create Road"); action->addObject( newRoad ); // Submit it. undoMan->addAction( action ); //send a callback to script after were done here if one exists if ( isMethod( "onRoadCreation" ) ) Con::executef( this, "onRoadCreation" ); return; } else if ( mMode == mAddNodeMode ) { // Oops the road got deleted, maybe from an undo action? // Back to NormalMode. if ( mSelRoad ) { // A double-click on a node in Normal mode means set AddNode mode. if ( clickedNodeIdx == 0 ) { submitUndo( "Add Node" ); mAddNodeIdx = clickedNodeIdx; mMode = mAddNodeMode; mSelNode = mSelRoad->insertNode( tPos, mDefaultWidth, mAddNodeIdx ); mIsDirty = true; setSelectedNode( mSelNode ); return; } else { if( clickedRoadPtr && clickedNodeIdx == clickedRoadPtr->mNodes.size() - 1 ) { submitUndo( "Add Node" ); mAddNodeIdx = U32_MAX; mMode = mAddNodeMode; mSelNode = mSelRoad->addNode( tPos, mDefaultWidth ); mIsDirty = true; setSelectedNode( mSelNode ); return; } else { submitUndo( "Insert Node" ); // A single-click on empty space while in // AddNode mode means insert / add a node. //submitUndo( "Add Node" ); //F32 width = mSelRoad->getNodeWidth( mSelNode ); U32 newNode = mSelRoad->insertNode( tPos, mDefaultWidth, mAddNodeIdx); mIsDirty = true; setSelectedNode( newNode ); return; } } } } else if ( mMode == mInsertPointMode && mSelRoad != NULL) { if ( clickedRoadPtr == mSelRoad ) { F32 w0 = mSelRoad->getNodeWidth( insertNodeIdx ); F32 w1 = mSelRoad->getNodeWidth( insertNodeIdx + 1 ); F32 width = ( w0 + w1 ) * 0.5f; submitUndo( "Insert Node" ); U32 newNode = mSelRoad->insertNode( tPos, width, insertNodeIdx + 1); mIsDirty = true; setSelectedNode( newNode ); return; } } else if ( mMode == mRemovePointMode && mSelRoad != NULL) { if ( nodeClicked && clickedRoadPtr == mSelRoad ) { setSelectedNode( clickedNodeIdx ); deleteSelectedNode(); return; } } else if ( mMode == mMovePointMode ) { if ( nodeClicked && clickedRoadPtr == mSelRoad ) { setSelectedNode( clickedNodeIdx ); return; } } else if ( mMode == mScalePointMode ) { if ( nodeClicked && clickedRoadPtr == mSelRoad ) { setSelectedNode( clickedNodeIdx ); return; } } } void GuiRoadEditorCtrl::on3DRightMouseDown(const Gui3DMouseEvent & event) { //mIsPanning = true; } void GuiRoadEditorCtrl::on3DRightMouseUp(const Gui3DMouseEvent & event) { //mIsPanning = false; } void GuiRoadEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) { mStartWidth = -1.0f; mSavedDrag = false; mouseUnlock(); } void GuiRoadEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) { if ( mSelRoad != NULL && mMode == mAddNodeMode ) { Point3F startPnt = event.pos; Point3F endPnt = event.pos + event.vec * 1000.0f; RayInfo ri; if ( gServerContainer.castRay(startPnt, endPnt, TerrainObjectType, &ri) ) { mSelRoad->setNodePosition( mSelNode, ri.point ); mIsDirty = true; } return; } // Is cursor hovering over a road? if ( mMode == mSelectRoadMode ) { mHoverRoad = NULL; Point3F startPnt = event.pos; Point3F endPnt = event.pos + event.vec * 1000.0f; RayInfo ri; if ( gServerContainer.castRay(startPnt, endPnt, TerrainObjectType, &ri) ) { DecalRoad *pRoad = NULL; for ( SimSetIterator iter(mRoadSet); *iter; ++iter ) { pRoad = static_cast( *iter ); if ( pRoad->containsPoint( ri.point ) ) { mHoverRoad = pRoad; break; } } } } // Is cursor hovering over a RoadNode? if ( mHoverRoad ) { DecalRoad *pRoad = mHoverRoad; S32 hoverNodeIdx = -1; F32 hoverNodeDist = F32_MAX; for ( U32 i = 0; i < pRoad->mNodes.size(); i++ ) { const Point3F &nodePos = pRoad->mNodes[i].point; Point3F screenPos; project( nodePos, &screenPos ); RectI rect( Point2I((S32)screenPos.x,(S32)screenPos.y) - mNodeHalfSize, mNodeHalfSize * 2 ); if ( rect.pointInRect( event.mousePoint ) && screenPos.z < hoverNodeDist ) { hoverNodeDist = screenPos.z; hoverNodeIdx = i; } } mHoverNode = hoverNodeIdx; } } void GuiRoadEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event) { // Drags are only used to transform nodes if ( !mSelRoad || mSelNode == -1 || ( mMode != mMovePointMode && mMode != mScalePointMode ) ) return; if ( !mSavedDrag ) { submitUndo( "Modify Node" ); mSavedDrag = true; } if ( mMode == mScalePointMode ) { Point3F tPos; if ( !getTerrainPos( event, tPos ) ) return; if ( mStartWidth == -1.0f ) { mStartWidth = mSelRoad->mNodes[mSelNode].width; mStartX = event.mousePoint.x; mStartWorld = tPos; } S32 deltaScreenX = event.mousePoint.x - mStartX; F32 worldDist = ( event.pos - mStartWorld ).len(); F32 deltaWorldX = ( deltaScreenX * worldDist ) / getWorldToScreenScale().y; F32 width = mStartWidth + deltaWorldX; mSelRoad->setNodeWidth( mSelNode, width ); mIsDirty = true; } else if( mMode == mMovePointMode ) { Point3F tPos; if ( !getTerrainPos( event, tPos ) ) return; mSelRoad->setNodePosition( mSelNode, tPos ); mIsDirty = true; } Con::executef( this, "onNodeModified", Con::getIntArg(mSelNode) ); } void GuiRoadEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event) { // nothing to do } void GuiRoadEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event) { // nothing to do } bool GuiRoadEditorCtrl::onKeyDown(const GuiEvent& event) { if( event.keyCode == KEY_RETURN && mMode == mAddNodeMode ) { // Delete the node attached to the cursor. deleteSelectedNode(); mMode = mAddRoadMode; return true; } return false; } void GuiRoadEditorCtrl::updateGuiInfo() { // nothing to do } void GuiRoadEditorCtrl::onRender( Point2I offset, const RectI &updateRect ) { PROFILE_SCOPE( GuiRoadEditorCtrl_OnRender ); Parent::onRender( offset, updateRect ); return; } void GuiRoadEditorCtrl::renderScene(const RectI & updateRect) { GFX->setStateBlock( mZDisableSB ); // Draw the spline based from the client-side road // because the serverside spline is not actually reliable... // Can be incorrect if the DecalRoad is before the TerrainBlock // in the scene. if ( mHoverRoad && mHoverRoad != mSelRoad ) { DecalRoad *pRoad = (DecalRoad*)mHoverRoad->getClientObject(); if ( pRoad ) _drawRoadSpline( pRoad, mHoverSplineColor ); } if ( mSelRoad ) { DecalRoad *pRoad = (DecalRoad*)mSelRoad->getClientObject(); if ( pRoad ) _drawRoadSpline( pRoad, mSelectedSplineColor ); } } void GuiRoadEditorCtrl::renderGui( Point2I offset, const RectI &updateRect ) { // Draw Control nodes for selected and highlighted roads if ( mHoverRoad ) _drawRoadControlNodes( mHoverRoad, mHoverSplineColor ); if ( mSelRoad ) _drawRoadControlNodes( mSelRoad, mSelectedSplineColor ); Parent::renderGui(offset, updateRect); } void GuiRoadEditorCtrl::_drawRoadSpline( DecalRoad *road, const ColorI &color ) { if ( road->mEdges.size() <= 1 ) return; GFXTransformSaver saver; if ( DecalRoad::smShowSpline ) { // Render the center-line PrimBuild::color( color ); PrimBuild::begin( GFXLineStrip, road->mEdges.size() ); for ( U32 i = 0; i < road->mEdges.size(); i++ ) { PrimBuild::vertex3fv( road->mEdges[i].p1 ); } PrimBuild::end(); } if ( DecalRoad::smWireframe ) { // Left-side line PrimBuild::color3i( 100, 100, 100 ); PrimBuild::begin( GFXLineStrip, road->mEdges.size() ); for ( U32 i = 0; i < road->mEdges.size(); i++ ) { PrimBuild::vertex3fv( road->mEdges[i].p0 ); } PrimBuild::end(); // Right-side line PrimBuild::begin( GFXLineStrip, road->mEdges.size() ); for ( U32 i = 0; i < road->mEdges.size(); i++ ) { PrimBuild::vertex3fv( road->mEdges[i].p2 ); } PrimBuild::end(); // Cross-sections PrimBuild::begin( GFXLineList, road->mEdges.size() * 2 ); for ( U32 i = 0; i < road->mEdges.size(); i++ ) { PrimBuild::vertex3fv( road->mEdges[i].p0 ); PrimBuild::vertex3fv( road->mEdges[i].p2 ); } PrimBuild::end(); } } void GuiRoadEditorCtrl::_drawRoadControlNodes( DecalRoad *road, const ColorI &color ) { if ( !DecalRoad::smShowSpline ) return; RectI bounds = getBounds(); GFXDrawUtil *drawer = GFX->getDrawUtil(); bool isSelected = ( road == mSelRoad ); bool isHighlighted = ( road == mHoverRoad ); for ( U32 i = 0; i < road->mNodes.size(); i++ ) { if ( false && isSelected && mSelNode == i ) continue; const Point3F &wpos = road->mNodes[i].point; Point3F spos; project( wpos, &spos ); if ( spos.z > 1.0f ) continue; Point2I posi; posi.x = spos.x; posi.y = spos.y; if ( !bounds.pointInRect( posi ) ) continue; ColorI theColor = color; Point2I nodeHalfSize = mNodeHalfSize; if ( isHighlighted && mHoverNode == i ) nodeHalfSize += Point2I(2,2); if ( isSelected ) { if ( mSelNode == i ) { theColor.set(0,0,255); } else if ( i == 0 ) { theColor.set(0,255,0); } else if ( i == road->mNodes.size() - 1 ) { theColor.set(255,0,0); } } drawer->drawRectFill( posi - nodeHalfSize, posi + nodeHalfSize, theColor ); } } bool GuiRoadEditorCtrl::getTerrainPos( const Gui3DMouseEvent & event, Point3F &tpos ) { // Find clicked point on the terrain Point3F startPnt = event.pos; Point3F endPnt = event.pos + event.vec * 10000.0f; RayInfo ri; bool hit; hit = gServerContainer.castRay(startPnt, endPnt, TerrainObjectType, &ri); tpos = ri.point; return hit; } void GuiRoadEditorCtrl::deleteSelectedNode() { if ( !mSelRoad || mSelNode == -1 ) return; // If the road has only two nodes remaining, // delete the whole road. if ( mSelRoad->mNodes.size() <= 2 ) { deleteSelectedRoad(); } else { // Only submit undo if we weren't in AddMode if ( mMode != mAddNodeMode ) submitUndo( "Delete Node" ); // Delete the SelectedNode of the SelectedRoad mSelRoad->deleteNode(mSelNode); mIsDirty = true; // We deleted the Node but not the Road (it has nodes left) // so decrement the currently selected node. if ( mSelRoad->mNodes.size() <= mSelNode ) mSelNode--; } } void GuiRoadEditorCtrl::deleteSelectedRoad( bool undoAble ) { AssertFatal( mSelRoad != NULL, "GuiRoadEditorCtrl::deleteSelectedRoad() - No road IS selected" ); // Not undo-able? Just delete it. if ( !undoAble ) { DecalRoad *lastRoad = mSelRoad; setSelectedRoad(NULL); lastRoad->deleteObject(); mIsDirty = true; return; } // Grab the mission editor undo manager. UndoManager *undoMan = NULL; if ( !Sim::findObject( "EUndoManager", undoMan ) ) { // Couldn't find it? Well just delete the road. Con::errorf( "GuiRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); return; } else { DecalRoad *lastRoad = mSelRoad; setSelectedRoad(NULL); // Create the UndoAction. MEDeleteUndoAction *action = new MEDeleteUndoAction("Deleted Road"); action->deleteObject( lastRoad ); mIsDirty = true; // Submit it. undoMan->addAction( action ); } } void GuiRoadEditorCtrl::setMode( String mode, bool sourceShortcut = false ) { mMode = mode; if( sourceShortcut ) Con::executef( this, "paletteSync", mode ); } void GuiRoadEditorCtrl::setSelectedRoad( DecalRoad *road ) { mSelRoad = road; if ( road != NULL ) Con::executef( this, "onRoadSelected", road->getIdString() ); else Con::executef( this, "onRoadSelected" ); if ( mSelRoad != road ) setSelectedNode(-1); } void GuiRoadEditorCtrl::setNodeWidth( F32 width ) { if ( mSelRoad && mSelNode != -1 ) { mSelRoad->setNodeWidth( mSelNode, width ); mIsDirty = true; } } F32 GuiRoadEditorCtrl::getNodeWidth() { if ( mSelRoad && mSelNode != -1 ) return mSelRoad->getNodeWidth( mSelNode ); return 0.0f; } void GuiRoadEditorCtrl::setNodePosition(const Point3F& pos) { if ( mSelRoad && mSelNode != -1 ) { mSelRoad->setNodePosition( mSelNode, pos ); mIsDirty = true; } } Point3F GuiRoadEditorCtrl::getNodePosition() { if ( mSelRoad && mSelNode != -1 ) return mSelRoad->getNodePosition( mSelNode ); return Point3F( 0, 0, 0 ); } void GuiRoadEditorCtrl::setSelectedNode( S32 node ) { //if ( mSelNode == node ) // return; mSelNode = node; if ( mSelNode != -1 && mSelRoad != NULL ) Con::executef( this, "onNodeSelected", Con::getIntArg(mSelNode), Con::getFloatArg(mSelRoad->mNodes[mSelNode].width) ); else Con::executef( this, "onNodeSelected", Con::getIntArg(-1) ); } void GuiRoadEditorCtrl::submitUndo( const UTF8 *name ) { // Grab the mission editor undo manager. UndoManager *undoMan = NULL; if ( !Sim::findObject( "EUndoManager", undoMan ) ) { Con::errorf( "GuiRoadEditorCtrl::submitUndo() - EUndoManager not found!" ); return; } // Setup the action. GuiRoadEditorUndoAction *action = new GuiRoadEditorUndoAction( name ); action->mObjId = mSelRoad->getId(); action->mBreakAngle = mSelRoad->mBreakAngle; action->mMaterialAssetId = mSelRoad->mMaterialAssetId; action->mSegmentsPerBatch = mSelRoad->mSegmentsPerBatch; action->mTextureLength = mSelRoad->mTextureLength; action->mRoadEditor = this; for( U32 i = 0; i < mSelRoad->mNodes.size(); i++ ) { action->mNodes.push_back( mSelRoad->mNodes[i] ); } undoMan->addAction( action ); } DefineEngineMethod( GuiRoadEditorCtrl, deleteNode, void, (), , "deleteNode()" ) { object->deleteSelectedNode(); } DefineEngineMethod( GuiRoadEditorCtrl, getMode, const char*, (), , "" ) { return object->getMode(); } DefineEngineMethod( GuiRoadEditorCtrl, setMode, void, ( const char * mode ), , "setMode( String mode )" ) { String newMode = ( mode ); object->setMode( newMode ); } DefineEngineMethod( GuiRoadEditorCtrl, getNodeWidth, F32, (), , "" ) { return object->getNodeWidth(); } DefineEngineMethod( GuiRoadEditorCtrl, setNodeWidth, void, ( F32 width ), , "" ) { object->setNodeWidth( width ); } DefineEngineMethod( GuiRoadEditorCtrl, getNodePosition, Point3F, (), , "" ) { return object->getNodePosition(); } DefineEngineMethod( GuiRoadEditorCtrl, setNodePosition, void, ( Point3F pos ), , "" ) { object->setNodePosition( pos ); } DefineEngineMethod( GuiRoadEditorCtrl, setSelectedRoad, void, ( const char * pathRoad ), (""), "" ) { if (String::compare( pathRoad,"")==0 ) object->setSelectedRoad(NULL); else { DecalRoad *road = NULL; if ( Sim::findObject( pathRoad, road ) ) object->setSelectedRoad(road); } } DefineEngineMethod( GuiRoadEditorCtrl, getSelectedRoad, S32, (), , "" ) { DecalRoad *road = object->getSelectedRoad(); if ( road ) return road->getId(); return 0; } DefineEngineMethod( GuiRoadEditorCtrl, getSelectedNode, S32, (), , "" ) { return object->getSelectedNode(); } DefineEngineMethod( GuiRoadEditorCtrl, deleteRoad, void, (), , "" ) { object->deleteSelectedRoad(); }