mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 20:54:46 +00:00
1113 lines
29 KiB
C++
1113 lines
29 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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<RoadNode> 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<DecalRoad*>( *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<DecalRoad*>( *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<DecalRoad*>( *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();
|
|
}
|