Torque3D/Engine/source/navigation/guiNavEditorCtrl.cpp
marauder2k7 d1771756c2 updated drawmodes and rendering
DebugDraw for recast now caches the results
We now have a drawmode dropdown selector
drawmode changes come from the gui itself no longer from console values
all recast drawmodes are supported with the exception of drawmodes that add abilities like navqueries until the nav tester tool is imlpemented.
2025-07-24 14:25:02 +01:00

720 lines
18 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2014 Daniel Buckmaster
//
// 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 "torqueConfig.h"
#ifdef TORQUE_TOOLS
#include "platform/platform.h"
#include "guiNavEditorCtrl.h"
#include "duDebugDrawTorque.h"
#include "console/engineAPI.h"
#include "console/consoleTypes.h"
#include "math/util/frustum.h"
#include "math/mathUtils.h"
#include "gfx/primBuilder.h"
#include "gfx/gfxDrawUtil.h"
#include "scene/sceneRenderState.h"
#include "scene/sceneManager.h"
#include "gui/core/guiCanvas.h"
#include "gui/buttons/guiButtonCtrl.h"
#include "gui/worldEditor/undoActions.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/AI/AIController.h"
IMPLEMENT_CONOBJECT(GuiNavEditorCtrl);
ConsoleDocClass(GuiNavEditorCtrl,
"@brief GUI tool that makes up the Navigation Editor\n\n"
"Editor use only.\n\n"
"@internal"
);
// Each of the mode names directly correlates with the Nav Editor's tool palette.
const String GuiNavEditorCtrl::mSelectMode = "SelectMode";
const String GuiNavEditorCtrl::mLinkMode = "LinkMode";
const String GuiNavEditorCtrl::mCoverMode = "CoverMode";
const String GuiNavEditorCtrl::mTestMode = "TestMode";
GuiNavEditorCtrl::GuiNavEditorCtrl()
{
mMode = mSelectMode;
mIsDirty = false;
mStartDragMousePoint = InvalidMousePoint;
mMesh = NULL;
mPlayer = mCurPlayer = NULL;
mSpawnClass = mSpawnDatablock = "";
mLinkStart = Point3F::Max;
mLink = mCurLink = -1;
}
GuiNavEditorCtrl::~GuiNavEditorCtrl()
{
}
void GuiNavEditorUndoAction::undo()
{
}
bool GuiNavEditorCtrl::onAdd()
{
if(!Parent::onAdd())
return false;
GFXStateBlockDesc desc;
desc.fillMode = GFXFillSolid;
desc.setBlend(false);
desc.setZReadWrite(false, false);
desc.setCullMode(GFXCullNone);
mZDisableSB = GFX->createStateBlock(desc);
desc.setZReadWrite(true, true);
mZEnableSB = GFX->createStateBlock(desc);
SceneManager::getPreRenderSignal().notify(this, &GuiNavEditorCtrl::_prepRenderImage);
return true;
}
void GuiNavEditorCtrl::initPersistFields()
{
docsURL;
addField("isDirty", TypeBool, Offset(mIsDirty, GuiNavEditorCtrl));
addField("spawnClass", TypeRealString, Offset(mSpawnClass, GuiNavEditorCtrl),
"Class of object to spawn in test mode.");
addField("spawnDatablock", TypeRealString, Offset(mSpawnDatablock, GuiNavEditorCtrl),
"Datablock to give new objects in test mode.");
Parent::initPersistFields();
}
void GuiNavEditorCtrl::onSleep()
{
Parent::onSleep();
//mMode = mSelectMode;
}
void GuiNavEditorCtrl::selectMesh(NavMesh *mesh)
{
mesh->setSelected(true);
mMesh = mesh;
}
DefineEngineMethod(GuiNavEditorCtrl, selectMesh, void, (S32 id),,
"@brief Select a NavMesh object.")
{
NavMesh *obj;
if(Sim::findObject(id, obj))
object->selectMesh(obj);
}
S32 GuiNavEditorCtrl::getMeshId()
{
return mMesh.isNull() ? 0 : mMesh->getId();
}
DefineEngineMethod(GuiNavEditorCtrl, getMesh, S32, (),,
"@brief Select a NavMesh object.")
{
return object->getMeshId();
}
S32 GuiNavEditorCtrl::getPlayerId()
{
return mPlayer.isNull() ? 0 : mPlayer->getId();
}
DefineEngineMethod(GuiNavEditorCtrl, getPlayer, S32, (),,
"@brief Select a NavMesh object.")
{
return object->getPlayerId();
}
void GuiNavEditorCtrl::deselect()
{
if(!mMesh.isNull())
mMesh->setSelected(false);
mMesh = NULL;
mPlayer = mCurPlayer = NULL;
mLinkStart = Point3F::Max;
mLink = mCurLink = -1;
}
DefineEngineMethod(GuiNavEditorCtrl, deselect, void, (),,
"@brief Deselect whatever is currently selected in the editor.")
{
object->deselect();
}
void GuiNavEditorCtrl::deleteLink()
{
if(!mMesh.isNull() && mLink != -1)
{
mMesh->selectLink(mLink, false);
mMesh->deleteLink(mLink);
mLink = -1;
Con::executef(this, "onLinkDeselected");
}
}
DefineEngineMethod(GuiNavEditorCtrl, deleteLink, void, (),,
"@brief Delete the currently selected link.")
{
object->deleteLink();
}
void GuiNavEditorCtrl::setLinkFlags(const LinkData &d)
{
if(mMode == mLinkMode && !mMesh.isNull() && mLink != -1)
{
mMesh->setLinkFlags(mLink, d);
}
}
DefineEngineMethod(GuiNavEditorCtrl, setLinkFlags, void, (U32 flags),,
"@Brief Set jump and drop properties of the selected link.")
{
object->setLinkFlags(LinkData(flags));
}
void GuiNavEditorCtrl::spawnPlayer(const Point3F &pos)
{
SceneObject *obj = (SceneObject*)Sim::spawnObject(mSpawnClass, mSpawnDatablock);
if(obj)
{
MatrixF mat(true);
mat.setPosition(pos);
obj->setTransform(mat);
SimObject* cleanup = Sim::findObject("MissionCleanup");
if(cleanup)
{
SimGroup* missionCleanup = dynamic_cast<SimGroup*>(cleanup);
missionCleanup->addObject(obj);
}
mPlayer = obj;
#ifdef TORQUE_NAVIGATION_ENABLED
AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(obj);
if (asAIPlayer) //try direct
{
Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags()));
}
else
{
ShapeBase* sbo = dynamic_cast<ShapeBase*>(obj);
if (sbo->getAIController())
{
if (sbo->getAIController()->mControllerData)
Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags()));
}
else
{
#endif
Con::executef(this, "onPlayerSelected");
#ifdef TORQUE_NAVIGATION_ENABLED
}
}
#endif
}
}
DefineEngineMethod(GuiNavEditorCtrl, spawnPlayer, void, (),,
"@brief Spawn an AIPlayer at the centre of the screen.")
{
Point3F c;
if(object->get3DCentre(c))
object->spawnPlayer(c);
}
void GuiNavEditorCtrl::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;
}
bool GuiNavEditorCtrl::get3DCentre(Point3F &pos)
{
Point3F screen, start, end;
screen.set(getExtent().x / 2, getExtent().y / 2, 0);
unproject(screen, &start);
screen.z = 1.0f;
unproject(screen, &end);
RayInfo ri;
if(gServerContainer.castRay(start, end, StaticObjectType, &ri))
{
pos = ri.point;
return true;
}
return false;
}
void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event)
{
if (!mMesh)
return;
mGizmo->on3DMouseDown(event);
if (mTool)
mTool->on3DMouseDown(event);
mouseLock();
return;
// Construct a LineSegment from the camera position to 1000 meters away in
// the direction clicked.
// If that segment hits the terrain, truncate the ray to only be that length.
Point3F startPnt = event.pos;
Point3F endPnt = event.pos + event.vec * 1000.0f;
RayInfo ri;
U8 keys = Input::getModifierKeys();
bool shift = keys & SI_LSHIFT;
bool ctrl = keys & SI_LCTRL;
if(mMode == mLinkMode && !mMesh.isNull())
{
if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
{
U32 link = mMesh->getLink(ri.point);
if(link != -1)
{
if(mLink != -1)
mMesh->selectLink(mLink, false);
mMesh->selectLink(link, true, false);
mLink = link;
LinkData d = mMesh->getLinkFlags(mLink);
Con::executef(this, "onLinkSelected", Con::getIntArg(d.getFlags()));
}
else
{
if(mLink != -1)
{
mMesh->selectLink(mLink, false);
mLink = -1;
Con::executef(this, "onLinkDeselected");
}
else
{
if(mLinkStart != Point3F::Max)
{
mMesh->addLink(mLinkStart, ri.point);
if(!shift)
mLinkStart = Point3F::Max;
}
else
{
mLinkStart = ri.point;
}
}
}
}
else
{
mMesh->selectLink(mLink, false);
mLink = -1;
Con::executef(this, "onLinkDeselected");
}
}
if(mMode == mTestMode)
{
// Spawn new character
if(ctrl)
{
if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
spawnPlayer(ri.point);
}
// Deselect character
else if(shift)
{
mPlayer = NULL;
Con::executef(this, "onPlayerDeselected");
}
// Select/move character
else
{
if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri))
{
if(ri.object)
{
mPlayer = ri.object;
#ifdef TORQUE_NAVIGATION_ENABLED
AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(mPlayer.getPointer());
if (asAIPlayer) //try direct
{
Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags()));
}
else
{
ShapeBase* sbo = dynamic_cast<ShapeBase*>(mPlayer.getPointer());
if (sbo->getAIController())
{
if (sbo->getAIController()->mControllerData)
Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags()));
}
else
{
#endif
Con::executef(this, "onPlayerSelected");
}
#ifdef TORQUE_NAVIGATION_ENABLED
}
}
#endif
}
else if (!mPlayer.isNull() && gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
{
AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(mPlayer.getPointer());
if (asAIPlayer) //try direct
{
#ifdef TORQUE_NAVIGATION_ENABLED
asAIPlayer->setPathDestination(ri.point);
#else
asAIPlayer->setMoveDestination(ri.point,false);
#endif
}
else
{
ShapeBase* sbo = dynamic_cast<ShapeBase*>(mPlayer.getPointer());
if (sbo->getAIController())
{
if (sbo->getAIController()->mControllerData)
sbo->getAIController()->getNav()->setPathDestination(ri.point, true);
}
}
}
}
}
}
void GuiNavEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event)
{
if (!mMesh)
return;
// Keep the Gizmo up to date.
mGizmo->on3DMouseUp(event);
if (mTool)
mTool->on3DMouseUp(event);
mouseUnlock();
}
void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event)
{
if (!mMesh)
return;
if (mTool)
mTool->on3DMouseMove(event);
return;
//if(mSelRiver != NULL && mSelNode != -1)
//mGizmo->on3DMouseMove(event);
Point3F startPnt = event.pos;
Point3F endPnt = event.pos + event.vec * 1000.0f;
RayInfo ri;
if(mMode == mLinkMode && !mMesh.isNull())
{
if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
{
U32 link = mMesh->getLink(ri.point);
if(link != -1)
{
if(link != mLink)
{
if(mCurLink != -1)
mMesh->selectLink(mCurLink, false);
mMesh->selectLink(link, true, true);
}
mCurLink = link;
}
else
{
if(mCurLink != mLink)
mMesh->selectLink(mCurLink, false);
mCurLink = -1;
}
}
else
{
mMesh->selectLink(mCurLink, false);
mCurLink = -1;
}
}
if(mMode == mTestMode)
{
if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri))
mCurPlayer = ri.object;
else
mCurPlayer = NULL;
}
}
void GuiNavEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event)
{
mGizmo->on3DMouseDragged(event);
if(mGizmo->isDirty())
{
Point3F scale = mGizmo->getScale();
const MatrixF &mat = mGizmo->getTransform();
VectorF normal;
mat.getColumn(2, &normal);
//mSelRiver->setNode(pos, scale.x, scale.z, normal, mSelNode);
mIsDirty = true;
}
}
void GuiNavEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event)
{
}
void GuiNavEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event)
{
}
void GuiNavEditorCtrl::updateGuiInfo()
{
if (mTool)
{
if (mTool->updateGuiInfo())
return;
}
}
void GuiNavEditorCtrl::onRender(Point2I offset, const RectI &updateRect)
{
PROFILE_SCOPE(GuiNavEditorCtrl_OnRender);
Parent::onRender(offset, updateRect);
return;
}
static void renderBoxOutline(const Box3F &box, const ColorI &col)
{
if(box != Box3F::Invalid)
{
GFXStateBlockDesc desc;
desc.setCullMode(GFXCullNone);
desc.setFillModeSolid();
desc.setZReadWrite(true, false);
desc.setBlend(true);
GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 20));
desc.setFillModeWireframe();
desc.setBlend(false);
GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 255));
}
}
void GuiNavEditorCtrl::renderScene(const RectI & updateRect)
{
GFX->setStateBlock(mZDisableSB);
// get the projected size...
GameConnection* connection = GameConnection::getConnectionToServer();
if(!connection)
return;
// Grab the camera's transform
MatrixF mat;
connection->getControlCameraTransform(0, &mat);
// Get the camera position
Point3F camPos;
mat.getColumn(3,&camPos);
if (mTool)
mTool->onRender3D();
if(mMode == mLinkMode)
{
if(mLinkStart != Point3F::Max)
{
GFXStateBlockDesc desc;
desc.setBlend(false);
desc.setZReadWrite(true ,true);
MatrixF linkMat(true);
linkMat.setPosition(mLinkStart);
Point3F scale(0.8f, 0.8f, 0.8f);
GFX->getDrawUtil()->drawTransform(desc, linkMat, &scale);
}
}
if(mMode == mTestMode)
{
if(!mCurPlayer.isNull())
renderBoxOutline(mCurPlayer->getWorldBox(), ColorI::BLUE);
if(!mPlayer.isNull())
renderBoxOutline(mPlayer->getWorldBox(), ColorI::GREEN);
}
duDebugDrawTorque d;
if(!mMesh.isNull())
mMesh->renderLinks(d);
// Now draw all the 2d stuff!
GFX->setClipRect(updateRect);
}
bool GuiNavEditorCtrl::getStaticPos(const Gui3DMouseEvent & event, Point3F &tpos)
{
// Find clicked point on the terrain
Point3F startPnt = event.pos;
Point3F endPnt = event.pos + event.vec * 1000.0f;
RayInfo ri;
bool hit;
hit = gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri);
tpos = ri.point;
return hit;
}
void GuiNavEditorCtrl::setMode(String mode, bool sourceShortcut = false)
{
mMode = mode;
Con::executef(this, "onModeSet", mode);
if(sourceShortcut)
Con::executef(this, "paletteSync", mode);
}
void GuiNavEditorCtrl::submitUndo(const UTF8 *name)
{
// Grab the mission editor undo manager.
UndoManager *undoMan = NULL;
if(!Sim::findObject("EUndoManager", undoMan))
{
Con::errorf("GuiNavEditorCtrl::submitUndo() - EUndoManager not found!");
return;
}
// Setup the action.
GuiNavEditorUndoAction *action = new GuiNavEditorUndoAction(name);
action->mNavEditor = this;
undoMan->addAction(action);
}
void GuiNavEditorCtrl::_prepRenderImage(SceneManager* sceneGraph, const SceneRenderState* state)
{
/*if(isAwake() && River::smEditorOpen && mSelRiver)
{
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
ri->type = RenderPassManager::RIT_Editor;
ri->renderDelegate.bind(this, &GuiNavEditorCtrl::_renderSelectedRiver);
ri->defaultKey = 100;
state->getRenderPass()->addInst(ri);
}*/
}
void GuiNavEditorCtrl::setActiveTool(NavMeshTool* tool)
{
if (mTool)
{
mTool->onDeactivated();
}
mTool = tool;
if (mTool)
{
mTool->setActiveNavMesh(mMesh);
mTool->onActivated(mLastEvent);
}
}
void GuiNavEditorCtrl::setDrawMode(S32 id)
{
if (mMesh.isNull())
return;
mMesh->setDrawMode((NavMesh::DrawMode)id);
}
DefineEngineMethod(GuiNavEditorCtrl, setDrawMode, void, (S32 id), ,
"@brief Deselect whatever is currently selected in the editor.")
{
object->setDrawMode(id);
}
DefineEngineMethod(GuiNavEditorCtrl, setActiveTool, void, (const char* toolName), , "( NavMeshTool tool )")
{
NavMeshTool* tool = dynamic_cast<NavMeshTool*>(Sim::findObject(toolName));
object->setActiveTool(tool);
}
DefineEngineMethod(GuiNavEditorCtrl, getMode, const char*, (), , "")
{
return object->getMode();
}
DefineEngineMethod(GuiNavEditorCtrl, setMode, void, (String mode),, "setMode(String mode)")
{
object->setMode(mode);
}
#endif