//----------------------------------------------------------------------------- // 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" 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::mTileMode = "TileMode"; const String GuiNavEditorCtrl::mTestMode = "TestMode"; GuiNavEditorCtrl::GuiNavEditorCtrl() { mMode = mSelectMode; mIsDirty = false; mStartDragMousePoint = InvalidMousePoint; mMesh = NULL; mCurTile = mTile = -1; 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; mCurTile = mTile = -1; 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::buildTile() { if(!mMesh.isNull() && mTile != -1) mMesh->buildTile(mTile); } DefineEngineMethod(GuiNavEditorCtrl, buildTile, void, (),, "@brief Build the currently selected tile.") { object->buildTile(); } 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(cleanup); missionCleanup->addObject(obj); } mPlayer = static_cast(obj); Con::executef(this, "onPlayerSelected", Con::getIntArg(mPlayer->mLinkTypes.getFlags())); } } 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) { mGizmo->on3DMouseDown(event); if(!isFirstResponder()) setFirstResponder(); mouseLock(); // 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 == mTileMode && !mMesh.isNull()) { if(gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri)) { mTile = mMesh->getTile(ri.point); dd.clear(); mMesh->renderTileData(dd, mTile); } } 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, &ri)) { if(dynamic_cast(ri.object)) { mPlayer = dynamic_cast(ri.object); Con::executef(this, "onPlayerSelected", Con::getIntArg(mPlayer->mLinkTypes.getFlags())); } } else if(!mPlayer.isNull() && gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) mPlayer->setPathDestination(ri.point); } } } void GuiNavEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) { // Keep the Gizmo up to date. mGizmo->on3DMouseUp(event); mouseUnlock(); } void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) { //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; } } // Select a tile from our current NavMesh. if(mMode == mTileMode && !mMesh.isNull()) { if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) mCurTile = mMesh->getTile(ri.point); else mCurTile = -1; } if(mMode == mTestMode) { if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType, &ri)) mCurPlayer = dynamic_cast(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() { } 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(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 == mTileMode && !mMesh.isNull()) { renderBoxOutline(mMesh->getTileBox(mCurTile), ColorI::BLUE); renderBoxOutline(mMesh->getTileBox(mTile), ColorI::GREEN); if(Con::getBoolVariable("$Nav::Editor::renderVoxels", false)) dd.renderGroup(0); if(Con::getBoolVariable("$Nav::Editor::renderInput", false)) { dd.depthMask(false); dd.renderGroup(1); dd.depthMask(true); } } 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); d.render(); // 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(); ri->type = RenderPassManager::RIT_Editor; ri->renderDelegate.bind(this, &GuiNavEditorCtrl::_renderSelectedRiver); ri->defaultKey = 100; state->getRenderPass()->addInst(ri); }*/ } DefineEngineMethod(GuiNavEditorCtrl, getMode, const char*, (), , "") { return object->getMode(); } DefineEngineMethod(GuiNavEditorCtrl, setMode, void, (String mode),, "setMode(String mode)") { object->setMode(mode); } #endif