From 26ebdd093bdf341bafd6530943fe16c633da2143 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 20 Jul 2025 16:10:27 +0100 Subject: [PATCH 01/38] test commit to fix debug draw pass all draws through duDebugDraw instead of calling our class directly. --- .../gfx/gl/gfxGLCircularVolatileBuffer.h | 67 +++--- .../source/navigation/duDebugDrawTorque.cpp | 204 +++++++----------- Engine/source/navigation/duDebugDrawTorque.h | 122 ++++------- Engine/source/navigation/guiNavEditorCtrl.cpp | 8 +- Engine/source/navigation/navMesh.cpp | 62 ++---- Engine/source/navigation/navPath.cpp | 2 - 6 files changed, 172 insertions(+), 293 deletions(-) diff --git a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h index 6abeaf8e4..cd961a19e 100644 --- a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h +++ b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h @@ -178,39 +178,59 @@ public: U32 mSize = 0; }_getBufferData; - void lock(const U32 size, U32 offsetAlign, U32 &outOffset, void* &outPtr) + void lock(const U32 size, U32 offsetAlign, U32& outOffset, void*& outPtr) { - if( !size ) + if (!size) { - AssertFatal(0, ""); + AssertFatal(0, "GLCircularVolatileBuffer::lock - size must be > 0"); outOffset = 0; - outPtr = NULL; + outPtr = nullptr; + return; } - mLockManager.waitFirstRange( mBufferFreePos, (mBufferFreePos + size)-1 ); + // Align free pos first (before wraparound check) + if (offsetAlign) + { + mBufferFreePos = ((mBufferFreePos + offsetAlign - 1) / offsetAlign) * offsetAlign; + } - if( mBufferFreePos + size > mBufferSize ) - { - mUsedRanges.push_back( UsedRange( mBufferFreePos, mBufferSize-1 ) ); + // If the size won't fit from current pos to end, wrap around + if (mBufferFreePos + size > mBufferSize) + { + // Protect the remaining space + if (mBufferFreePos < mBufferSize) + mUsedRanges.push_back(UsedRange(mBufferFreePos, mBufferSize - 1)); + + // Reset free pos mBufferFreePos = 0; - } - // force offset buffer align - if( offsetAlign ) - mBufferFreePos = ( (mBufferFreePos/offsetAlign) + 1 ) * offsetAlign; + // Realign after wrap + if (offsetAlign) + { + mBufferFreePos = ((mBufferFreePos + offsetAlign - 1) / offsetAlign) * offsetAlign; + } + + // Now check for overlaps *after* wrapping + mLockManager.waitOverlapRanges(mBufferFreePos, mBufferFreePos + size - 1); + } + else + { + // Normal range wait + mLockManager.waitOverlapRanges(mBufferFreePos, mBufferFreePos + size - 1); + } outOffset = mBufferFreePos; - if( GFXGL->mCapabilities.bufferStorage ) - { - outPtr = (U8*)(mBufferPtr) + mBufferFreePos; - } - else if( GFXGL->glUseMap() ) + if (GFXGL->mCapabilities.bufferStorage) { - PRESERVE_BUFFER( mBinding ); + outPtr = static_cast(mBufferPtr) + mBufferFreePos; + } + else if (GFXGL->glUseMap()) + { + PRESERVE_BUFFER(mBinding); glBindBuffer(mBinding, mBufferName); - const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT; + const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT; outPtr = glMapBufferRange(mBinding, outOffset, size, access); } else @@ -218,14 +238,13 @@ public: _getBufferData.mOffset = outOffset; _getBufferData.mSize = size; - outPtr = mFrameAllocator.lock( size ); - } + outPtr = mFrameAllocator.lock(size); + } - //set new buffer pos - mBufferFreePos = mBufferFreePos + size; + mBufferFreePos += size; //align 4bytes - mBufferFreePos = ( (mBufferFreePos/4) + 1 ) * 4; + mBufferFreePos = ((mBufferFreePos + 4 - 1) / 4) * 4; } void unlock() diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index 0c911e181..5799b837e 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -38,19 +38,15 @@ duDebugDrawTorque::duDebugDrawTorque() { + VECTOR_SET_ASSOCIATION(mVertList); mPrimType = 0; mQuadsMode = false; mVertCount = 0; - mGroup = 0; - mCurrColor = 0; - mOverrideColor = 0; - mOverride = false; dMemset(&mStore, 0, sizeof(mStore)); } duDebugDrawTorque::~duDebugDrawTorque() { - clear(); } void duDebugDrawTorque::depthMask(bool state) @@ -60,6 +56,22 @@ void duDebugDrawTorque::depthMask(bool state) void duDebugDrawTorque::texture(bool state) { + // need a checker texture?...... if(state is true) then set first slot to that texture. +} + +unsigned int duDebugDrawTorque::areaToCol(unsigned int area) +{ + switch (area) + { + // Ground (0) : light blue + case GroundArea: return duRGBA(0, 192, 255, 255); + // Water : blue + case WaterArea: return duRGBA(0, 0, 255, 255); + // Road : brown + case OffMeshArea: return duRGBA(50, 20, 12, 255); + // Unexpected : red + default: return duRGBA(255, 0, 0, 255); + } } /// Begin drawing primitives. @@ -67,29 +79,25 @@ void duDebugDrawTorque::texture(bool state) /// @param size [in] size of a primitive, applies to point size and line width only. void duDebugDrawTorque::begin(duDebugDrawPrimitives prim, float size) { - mCurrColor = -1; + if (!mVertList.empty()) + mVertList.clear(); + mQuadsMode = false; mVertCount = 0; mPrimType = 0; - switch(prim) + + switch (prim) { - case DU_DRAW_POINTS: mPrimType = GFXPointList; break; - case DU_DRAW_LINES: mPrimType = GFXLineList; break; + case DU_DRAW_POINTS: mPrimType = GFXPointList; break; + case DU_DRAW_LINES: mPrimType = GFXLineList; break; case DU_DRAW_TRIS: mPrimType = GFXTriangleList; break; - case DU_DRAW_QUADS: mPrimType = GFXTriangleList; - mQuadsMode = true; break; + case DU_DRAW_QUADS: mPrimType = GFXTriangleList; mQuadsMode = true; break; } - mBuffers.push_back(Buffer(mPrimType)); - mBuffers.last().group = mGroup; + mDesc.setCullMode(GFXCullNone); mDesc.setBlend(true); } -void duDebugDrawTorque::beginGroup(U32 group) -{ - mGroup = group; -} - /// Submit a vertex /// @param pos [in] position of the verts. /// @param color [in] color of the verts. @@ -103,30 +111,7 @@ void duDebugDrawTorque::vertex(const float* pos, unsigned int color) /// @param color [in] color of the verts. void duDebugDrawTorque::vertex(const float x, const float y, const float z, unsigned int color) { - if(mQuadsMode) - { - if(mVertCount == 3) - { - _vertex(x, -z, y, color); - _vertex(mStore[0][0], mStore[0][1], mStore[0][2], color); - _vertex(mStore[1][0], mStore[1][1], mStore[1][2], color); - _vertex(mStore[1][0], mStore[1][1], mStore[1][2], color); - _vertex(mStore[2][0], mStore[2][1], mStore[2][2], color); - _vertex(x, -z, y, color); - mVertCount = 0; - } - else - { - mStore[mVertCount][0] = x; - mStore[mVertCount][1] = -z; - mStore[mVertCount][2] = y; - mVertCount++; - } - } - else - { - _vertex(x, -z, y, color); - } + _vertex(x, -z, y, color); } /// Submit a vertex @@ -148,105 +133,60 @@ void duDebugDrawTorque::vertex(const float x, const float y, const float z, unsi /// Push a vertex onto the buffer. void duDebugDrawTorque::_vertex(const float x, const float y, const float z, unsigned int color) { - // Use override color if we must. - //if(mOverride) - //color = mOverrideColor; - if(mCurrColor != color || !mBuffers.last().buffer.size()) - { - U8 r, g, b, a; - // Convert color integer to components. - rcCol(color, r, g, b, a); - mBuffers.last().buffer.push_back(Instruction(r, g, b, a)); - mCurrColor = color; - } - // Construct vertex data. - mBuffers.last().buffer.push_back(Instruction(x, y, z)); + GFXVertexPCT vert; + vert.point.set(x, y, z); + + U8 r, g, b, a; + // Convert color integer to components. + rcCol(color, r, g, b, a); + + vert.color.set(r, g, b, a); + + mVertList.push_back(vert); } /// End drawing primitives. void duDebugDrawTorque::end() { -} + if (mVertList.empty()) + return; -void duDebugDrawTorque::overrideColor(unsigned int col) -{ - mOverride = true; - mOverrideColor = col; -} + const U32 maxVertsPerDraw = GFX_MAX_DYNAMIC_VERTS; -void duDebugDrawTorque::cancelOverride() -{ - mOverride = false; -} + U32 totalVerts = mVertList.size(); + U32 stride = 1; + U32 stripStart = 0; -void duDebugDrawTorque::renderBuffer(Buffer &b) -{ - PrimBuild::begin(b.primType, b.buffer.size()); - Vector &buf = b.buffer; - for(U32 i = 0; i < buf.size(); i++) + switch (mPrimType) { - switch(buf[i].type) - { - case Instruction::POINT: - PrimBuild::vertex3f(buf[i].data.point.x, - buf[i].data.point.y, - buf[i].data.point.z); - break; - - case Instruction::COLOR: - if(mOverride) - break; - PrimBuild::color4i(buf[i].data.color.r, - buf[i].data.color.g, - buf[i].data.color.b, - buf[i].data.color.a); - break; - - default: - break; - } + case GFXLineList: stride = 2; break; + case GFXTriangleList: stride = 3; break; + case GFXLineStrip: stripStart = 1; stride = 1; break; + case GFXTriangleStrip:stripStart = 2; stride = 1; break; + default: stride = 1; break; } - PrimBuild::end(); + + GFX->setPrimitiveBuffer(NULL); + GFX->setStateBlockByDesc(mDesc); + GFX->setupGenericShaders(GFXDevice::GSColor); + + for (U32 i = 0; i < totalVerts; i += maxVertsPerDraw) + { + U32 batchSize = getMin(maxVertsPerDraw, totalVerts - i); + + mVertexBuffer.set(GFX, batchSize, GFXBufferTypeDynamic); + GFXVertexPCT* verts = mVertexBuffer.lock(); + + if (verts) + dMemcpy(verts, &mVertList[i], sizeof(GFXVertexPCT) * batchSize); + mVertexBuffer.unlock(); + + GFX->setVertexBuffer(mVertexBuffer); + + U32 numPrims = (batchSize / stride) - stripStart; + GFX->drawPrimitive((GFXPrimitiveType)mPrimType, 0, numPrims); + } + + mVertList.clear(); } -void duDebugDrawTorque::render() -{ - GFXStateBlockRef sb = GFX->createStateBlock(mDesc); - GFX->setStateBlock(sb); - // Use override color for all rendering. - if(mOverride) - { - U8 r, g, b, a; - rcCol(mOverrideColor, r, g, b, a); - PrimBuild::color4i(r, g, b, a); - } - for(U32 b = 0; b < mBuffers.size(); b++) - { - renderBuffer(mBuffers[b]); - } -} - -void duDebugDrawTorque::renderGroup(U32 group) -{ - GFXStateBlockRef sb = GFX->createStateBlock(mDesc); - GFX->setStateBlock(sb); - // Use override color for all rendering. - if(mOverride) - { - U8 r, g, b, a; - rcCol(mOverrideColor, r, g, b, a); - PrimBuild::color4i(r, g, b, a); - } - for(U32 b = 0; b < mBuffers.size(); b++) - { - if(mBuffers[b].group == group) - renderBuffer(mBuffers[b]); - } -} - -void duDebugDrawTorque::clear() -{ - for(U32 b = 0; b < mBuffers.size(); b++) - mBuffers[b].buffer.clear(); - mBuffers.clear(); -} diff --git a/Engine/source/navigation/duDebugDrawTorque.h b/Engine/source/navigation/duDebugDrawTorque.h index 782f51d13..594be0228 100644 --- a/Engine/source/navigation/duDebugDrawTorque.h +++ b/Engine/source/navigation/duDebugDrawTorque.h @@ -23,12 +23,33 @@ #ifndef _DU_DEBUG_DRAW_TORQUE_H_ #define _DU_DEBUG_DRAW_TORQUE_H_ +#ifndef _TVECTOR_H_ #include "core/util/tVector.h" +#endif #include -#include "gfx/gfxStateBlock.h" -/// @brief Implements the duDebugDraw interface in Torque. -class duDebugDrawTorque: public duDebugDraw { +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif + +#ifndef _GFXVERTEXTYPES_H_ +#include "gfx/gfxVertexTypes.h" +#endif + +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif + +/** +* @class duDebugDrawTorque +* @brief Implements the duDebugDraw interface in Torque. +* +* Every debug draw from recast goes through a process of begin, add vertex and then end +* just like our primbuilder class, but we need to catch these vertices +* and add them to a GFXVertexBuffer as recast supports GL_QUADS which is now +* deprecated. +*/ +class duDebugDrawTorque : public duDebugDraw { public: duDebugDrawTorque(); ~duDebugDrawTorque(); @@ -36,23 +57,11 @@ public: /// Enable/disable Z read. void depthMask(bool state) override; - /// Enable/disable texturing. Not used. - void texture(bool state) override; - - /// Special colour overwrite for when I get picky about the colours Mikko chose. - void overrideColor(unsigned int col); - - /// Stop the colour override. - void cancelOverride(); - /// Begin drawing primitives. /// @param prim [in] primitive type to draw, one of rcDebugDrawPrimitives. /// @param size [in] size of a primitive, applies to point size and line width only. void begin(duDebugDrawPrimitives prim, float size = 1.0f) override; - /// All new buffers go into this group. - void beginGroup(U32 group); - /// Submit a vertex /// @param pos [in] position of the verts. /// @param color [in] color of the verts. @@ -66,27 +75,35 @@ public: /// Submit a vertex /// @param pos [in] position of the verts. /// @param color [in] color of the verts. + /// @param uv [in] the uv coordinates. void vertex(const float* pos, unsigned int color, const float* uv) override; /// Submit a vertex /// @param x,y,z [in] position of the verts. /// @param color [in] color of the verts. + /// @param u [in] the u coordinate. + /// @param v [in] the v coordinate. void vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v) override; + /// Set a texture + /// @param state, use a texture in this draw, usually a checker texture. + void texture(bool state) override; + + /// + /// Assigns a colour to an area type. + /// + /// The area type. + /// The colour in recast format for the area. + unsigned int areaToCol(unsigned int area) override; + /// End drawing primitives. void end() override; - /// Render buffered primitive. - void render(); - - /// Render buffered primitives in a group. - void renderGroup(U32 group); - - /// Delete buffered primitive. - void clear(); - private: + GFXStateBlockDesc mDesc; + Vector mVertList; // Our vertex list for setting up vertexBuffer in the End function. + GFXVertexBufferHandle mVertexBuffer; // our vertex buffer for drawing. U32 mPrimType; bool mQuadsMode; @@ -94,64 +111,7 @@ private: U32 mVertCount; F32 mStore[3][3]; - U32 mGroup; - - struct Instruction { - // Contain either a point or a color command. - union { - struct { - U8 r, g, b, a; - } color; - struct { - float x, y, z; - } point; - U32 primType; - } data; - // Which type of data do we store? - enum { - COLOR, - POINT, - PRIMTYPE, - } type; - // Construct as color instruction. - Instruction(U8 r, U8 g, U8 b, U8 a) { - type = COLOR; - data.color.r = r; - data.color.g = g; - data.color.b = b; - data.color.a = a; - } - // Construct as point. - Instruction(float x, float y, float z) { - type = POINT; - data.point.x = x; - data.point.y = y; - data.point.z = z; - } - Instruction(U32 t = 0) { - type = PRIMTYPE; - data.primType = t; - } - }; - - struct Buffer { - U32 group; - Vector buffer; - GFXPrimitiveType primType; - Buffer(U32 type = 0) { - primType = (GFXPrimitiveType)type; - group = 0; - } - }; - Vector mBuffers; - - U32 mCurrColor; - U32 mOverrideColor; - bool mOverride; - void _vertex(const float x, const float y, const float z, unsigned int color); - - void renderBuffer(Buffer &b); }; #endif diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp index eae204447..2981a50e0 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.cpp +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -383,7 +383,6 @@ void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) if(gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri)) { mTile = mMesh->getTile(ri.point); - dd.clear(); mMesh->renderTileData(dd, mTile); } } @@ -610,13 +609,13 @@ void GuiNavEditorCtrl::renderScene(const RectI & updateRect) { 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)) + /*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) @@ -630,7 +629,6 @@ void GuiNavEditorCtrl::renderScene(const RectI & updateRect) duDebugDrawTorque d; if(!mMesh.isNull()) mMesh->renderLinks(d); - d.render(); // Now draw all the 2d stuff! GFX->setClipRect(updateRect); diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 253ee7922..2cdeaddeb 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -1329,23 +1329,6 @@ bool NavMesh::testEdgeCover(const Point3F &pos, const VectorF &dir, CoverPointDa void NavMesh::renderToDrawer() { - mDbgDraw.clear(); - // Recast debug draw - NetObject *no = getServerObject(); - if(no) - { - NavMesh *n = static_cast(no); - - if(n->nm) - { - mDbgDraw.beginGroup(0); - duDebugDrawNavMesh (&mDbgDraw, *n->nm, 0); - mDbgDraw.beginGroup(1); - duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm); - mDbgDraw.beginGroup(2); - duDebugDrawNavMeshBVTree (&mDbgDraw, *n->nm); - } - } } void NavMesh::prepRenderImage(SceneRenderState *state) @@ -1374,37 +1357,17 @@ void NavMesh::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInsta { NavMesh *n = static_cast(no); - if(n->isSelected()) + if ((!gEditingMission && n->mAlwaysRender) || (gEditingMission && Con::getBoolVariable("$Nav::Editor::renderMesh", 1))) { - GFXDrawUtil *drawer = GFX->getDrawUtil(); - - GFXStateBlockDesc desc; - desc.setZReadWrite(true, false); - desc.setBlend(true); - desc.setCullMode(GFXCullNone); - - drawer->drawCube(desc, getWorldBox(), n->mBuilding - ? ColorI(255, 0, 0, 80) - : ColorI(136, 228, 255, 45)); - desc.setFillModeWireframe(); - drawer->drawCube(desc, getWorldBox(), ColorI::BLACK); + if (n->nm) + { + duDebugDrawNavMesh(&mDbgDraw, *n->nm, 0); + if (Con::getBoolVariable("$Nav::Editor::renderPortals")) + duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm); + if (Con::getBoolVariable("$Nav::Editor::renderBVTree")) + duDebugDrawNavMeshBVTree(&mDbgDraw, *n->nm); + } } - - if(n->mBuilding) - { - int alpha = 80; - if(!n->isSelected() || !Con::getBoolVariable("$Nav::EditorOpen")) - alpha = 20; - mDbgDraw.overrideColor(duRGBA(255, 0, 0, alpha)); - } - else - { - mDbgDraw.cancelOverride(); - } - - if((!gEditingMission && n->mAlwaysRender) || (gEditingMission && Con::getBoolVariable("$Nav::Editor::renderMesh", 1))) mDbgDraw.renderGroup(0); - if(Con::getBoolVariable("$Nav::Editor::renderPortals")) mDbgDraw.renderGroup(1); - if(Con::getBoolVariable("$Nav::Editor::renderBVTree")) mDbgDraw.renderGroup(2); } } @@ -1445,10 +1408,11 @@ void NavMesh::renderTileData(duDebugDrawTorque &dd, U32 tile) return; if(nm) { - dd.beginGroup(0); - if(mTileData[tile].chf) duDebugDrawCompactHeightfieldSolid(&dd, *mTileData[tile].chf); + duDebugDrawNavMesh(&dd, *nm, 0); + if(mTileData[tile].chf) + duDebugDrawCompactHeightfieldSolid(&dd, *mTileData[tile].chf); - dd.beginGroup(1); + duDebugDrawNavMeshPortals(&dd, *nm); int col = duRGBA(255, 0, 255, 255); RecastPolyList &in = mTileData[tile].geom; dd.begin(DU_DRAW_LINES); diff --git a/Engine/source/navigation/navPath.cpp b/Engine/source/navigation/navPath.cpp index 50d25408b..857af9730 100644 --- a/Engine/source/navigation/navPath.cpp +++ b/Engine/source/navigation/navPath.cpp @@ -640,9 +640,7 @@ void NavPath::renderSimple(ObjectRenderInst *ri, SceneRenderState *state, BaseMa if(np->mQuery && !dtStatusSucceed(np->mStatus)) { duDebugDrawTorque dd; - dd.overrideColor(duRGBA(250, 20, 20, 255)); duDebugDrawNavMeshNodes(&dd, *np->mQuery); - dd.render(); } } } From d4d552e8e075ea35829cac88e244f70c740f4ae8 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 22 Jul 2025 14:39:36 +0100 Subject: [PATCH 02/38] recast update Added chunkytrimesh - this class splits up the geometry the navmesh is interested in into kdtree for fast traversal, makes the actual navmesh generation work with smaller chunks. Now only 1 RecastPolylist per navmesh this can be saved out in a future commit. This is a history commit, all functionality works same as it did before but it matches recasts recommended setup more closely. Future additions may break backwards compatibility. --- Engine/source/gfx/gl/gfxGLDevice.cpp | 3 + Engine/source/navigation/ChunkyTriMesh.cpp | 315 ++++++++++ Engine/source/navigation/ChunkyTriMesh.h | 59 ++ .../source/navigation/duDebugDrawTorque.cpp | 4 +- Engine/source/navigation/navMesh.cpp | 586 +++++++++++------- Engine/source/navigation/navMesh.h | 15 +- Engine/source/navigation/recastPolyList.cpp | 24 +- Engine/source/navigation/recastPolyList.h | 9 + 8 files changed, 800 insertions(+), 215 deletions(-) create mode 100644 Engine/source/navigation/ChunkyTriMesh.cpp create mode 100644 Engine/source/navigation/ChunkyTriMesh.h diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index 8019f21bf..e9ba531a9 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -704,6 +704,9 @@ inline void GFXGLDevice::postDrawPrimitive(U32 primitiveCount) { mDeviceStatistics.mDrawCalls++; mDeviceStatistics.mPolyCount += primitiveCount; + + mVolatileVBs.clear(); + mVolatilePBs.clear(); } void GFXGLDevice::drawPrimitive( GFXPrimitiveType primType, U32 vertexStart, U32 primitiveCount ) diff --git a/Engine/source/navigation/ChunkyTriMesh.cpp b/Engine/source/navigation/ChunkyTriMesh.cpp new file mode 100644 index 000000000..242bf285a --- /dev/null +++ b/Engine/source/navigation/ChunkyTriMesh.cpp @@ -0,0 +1,315 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include "ChunkyTriMesh.h" +#include +#include +#include + +struct BoundsItem +{ + float bmin[2]; + float bmax[2]; + int i; +}; + +static int compareItemX(const void* va, const void* vb) +{ + const BoundsItem* a = (const BoundsItem*)va; + const BoundsItem* b = (const BoundsItem*)vb; + if (a->bmin[0] < b->bmin[0]) + return -1; + if (a->bmin[0] > b->bmin[0]) + return 1; + return 0; +} + +static int compareItemY(const void* va, const void* vb) +{ + const BoundsItem* a = (const BoundsItem*)va; + const BoundsItem* b = (const BoundsItem*)vb; + if (a->bmin[1] < b->bmin[1]) + return -1; + if (a->bmin[1] > b->bmin[1]) + return 1; + return 0; +} + +static void calcExtends(const BoundsItem* items, const int /*nitems*/, + const int imin, const int imax, + float* bmin, float* bmax) +{ + bmin[0] = items[imin].bmin[0]; + bmin[1] = items[imin].bmin[1]; + + bmax[0] = items[imin].bmax[0]; + bmax[1] = items[imin].bmax[1]; + + for (int i = imin+1; i < imax; ++i) + { + const BoundsItem& it = items[i]; + if (it.bmin[0] < bmin[0]) bmin[0] = it.bmin[0]; + if (it.bmin[1] < bmin[1]) bmin[1] = it.bmin[1]; + + if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0]; + if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1]; + } +} + +inline int longestAxis(float x, float y) +{ + return y > x ? 1 : 0; +} + +static void subdivide(BoundsItem* items, int nitems, int imin, int imax, int trisPerChunk, + int& curNode, rcChunkyTriMeshNode* nodes, const int maxNodes, + int& curTri, int* outTris, const int* inTris) +{ + int inum = imax - imin; + int icur = curNode; + + if (curNode >= maxNodes) + return; + + rcChunkyTriMeshNode& node = nodes[curNode++]; + + if (inum <= trisPerChunk) + { + // Leaf + calcExtends(items, nitems, imin, imax, node.bmin, node.bmax); + + // Copy triangles. + node.i = curTri; + node.n = inum; + + for (int i = imin; i < imax; ++i) + { + const int* src = &inTris[items[i].i*3]; + int* dst = &outTris[curTri*3]; + curTri++; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + } + else + { + // Split + calcExtends(items, nitems, imin, imax, node.bmin, node.bmax); + + int axis = longestAxis(node.bmax[0] - node.bmin[0], + node.bmax[1] - node.bmin[1]); + + if (axis == 0) + { + // Sort along x-axis + qsort(items+imin, static_cast(inum), sizeof(BoundsItem), compareItemX); + } + else if (axis == 1) + { + // Sort along y-axis + qsort(items+imin, static_cast(inum), sizeof(BoundsItem), compareItemY); + } + + int isplit = imin+inum/2; + + // Left + subdivide(items, nitems, imin, isplit, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris); + // Right + subdivide(items, nitems, isplit, imax, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris); + + int iescape = curNode - icur; + // Negative index means escape. + node.i = -iescape; + } +} + +bool rcCreateChunkyTriMesh(const float* verts, const int* tris, int ntris, + int trisPerChunk, rcChunkyTriMesh* cm) +{ + int nchunks = (ntris + trisPerChunk-1) / trisPerChunk; + + cm->nodes = new rcChunkyTriMeshNode[nchunks*4]; + if (!cm->nodes) + return false; + + cm->tris = new int[ntris*3]; + if (!cm->tris) + return false; + + cm->ntris = ntris; + + // Build tree + BoundsItem* items = new BoundsItem[ntris]; + if (!items) + return false; + + for (int i = 0; i < ntris; i++) + { + const int* t = &tris[i*3]; + BoundsItem& it = items[i]; + it.i = i; + // Calc triangle XZ bounds. + it.bmin[0] = it.bmax[0] = verts[t[0]*3+0]; + it.bmin[1] = it.bmax[1] = verts[t[0]*3+2]; + for (int j = 1; j < 3; ++j) + { + const float* v = &verts[t[j]*3]; + if (v[0] < it.bmin[0]) it.bmin[0] = v[0]; + if (v[2] < it.bmin[1]) it.bmin[1] = v[2]; + + if (v[0] > it.bmax[0]) it.bmax[0] = v[0]; + if (v[2] > it.bmax[1]) it.bmax[1] = v[2]; + } + } + + int curTri = 0; + int curNode = 0; + subdivide(items, ntris, 0, ntris, trisPerChunk, curNode, cm->nodes, nchunks*4, curTri, cm->tris, tris); + + delete [] items; + + cm->nnodes = curNode; + + // Calc max tris per node. + cm->maxTrisPerChunk = 0; + for (int i = 0; i < cm->nnodes; ++i) + { + rcChunkyTriMeshNode& node = cm->nodes[i]; + const bool isLeaf = node.i >= 0; + if (!isLeaf) continue; + if (node.n > cm->maxTrisPerChunk) + cm->maxTrisPerChunk = node.n; + } + + return true; +} + + +inline bool checkOverlapRect(const float amin[2], const float amax[2], + const float bmin[2], const float bmax[2]) +{ + bool overlap = true; + overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; + overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; + return overlap; +} + +int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm, + float bmin[2], float bmax[2], + int* ids, const int maxIds) +{ + // Traverse tree + int i = 0; + int n = 0; + while (i < cm->nnodes) + { + const rcChunkyTriMeshNode* node = &cm->nodes[i]; + const bool overlap = checkOverlapRect(bmin, bmax, node->bmin, node->bmax); + const bool isLeafNode = node->i >= 0; + + if (isLeafNode && overlap) + { + if (n < maxIds) + { + ids[n] = i; + n++; + } + } + + if (overlap || isLeafNode) + i++; + else + { + const int escapeIndex = -node->i; + i += escapeIndex; + } + } + + return n; +} + + + +static bool checkOverlapSegment(const float p[2], const float q[2], + const float bmin[2], const float bmax[2]) +{ + static const float EPSILON = 1e-6f; + + float tmin = 0; + float tmax = 1; + float d[2]; + d[0] = q[0] - p[0]; + d[1] = q[1] - p[1]; + + for (int i = 0; i < 2; i++) + { + if (fabsf(d[i]) < EPSILON) + { + // Ray is parallel to slab. No hit if origin not within slab + if (p[i] < bmin[i] || p[i] > bmax[i]) + return false; + } + else + { + // Compute intersection t value of ray with near and far plane of slab + float ood = 1.0f / d[i]; + float t1 = (bmin[i] - p[i]) * ood; + float t2 = (bmax[i] - p[i]) * ood; + if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; } + if (t1 > tmin) tmin = t1; + if (t2 < tmax) tmax = t2; + if (tmin > tmax) return false; + } + } + return true; +} + +int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm, + float p[2], float q[2], + int* ids, const int maxIds) +{ + // Traverse tree + int i = 0; + int n = 0; + while (i < cm->nnodes) + { + const rcChunkyTriMeshNode* node = &cm->nodes[i]; + const bool overlap = checkOverlapSegment(p, q, node->bmin, node->bmax); + const bool isLeafNode = node->i >= 0; + + if (isLeafNode && overlap) + { + if (n < maxIds) + { + ids[n] = i; + n++; + } + } + + if (overlap || isLeafNode) + i++; + else + { + const int escapeIndex = -node->i; + i += escapeIndex; + } + } + + return n; +} diff --git a/Engine/source/navigation/ChunkyTriMesh.h b/Engine/source/navigation/ChunkyTriMesh.h new file mode 100644 index 000000000..0870d64ef --- /dev/null +++ b/Engine/source/navigation/ChunkyTriMesh.h @@ -0,0 +1,59 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef CHUNKYTRIMESH_H +#define CHUNKYTRIMESH_H + +struct rcChunkyTriMeshNode +{ + float bmin[2]; + float bmax[2]; + int i; + int n; +}; + +struct rcChunkyTriMesh +{ + inline rcChunkyTriMesh() : nodes(0), nnodes(0), tris(0), ntris(0), maxTrisPerChunk(0) {} + inline ~rcChunkyTriMesh() { delete [] nodes; delete [] tris; } + + rcChunkyTriMeshNode* nodes; + int nnodes; + int* tris; + int ntris; + int maxTrisPerChunk; + +private: + // Explicitly disabled copy constructor and copy assignment operator. + rcChunkyTriMesh(const rcChunkyTriMesh&); + rcChunkyTriMesh& operator=(const rcChunkyTriMesh&); +}; + +/// Creates partitioned triangle mesh (AABB tree), +/// where each node contains at max trisPerChunk triangles. +bool rcCreateChunkyTriMesh(const float* verts, const int* tris, int ntris, + int trisPerChunk, rcChunkyTriMesh* cm); + +/// Returns the chunk indices which overlap the input rectable. +int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm, float bmin[2], float bmax[2], int* ids, const int maxIds); + +/// Returns the chunk indices which overlap the input segment. +int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm, float p[2], float q[2], int* ids, const int maxIds); + + +#endif // CHUNKYTRIMESH_H diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index 5799b837e..1079e22a7 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -51,7 +51,7 @@ duDebugDrawTorque::~duDebugDrawTorque() void duDebugDrawTorque::depthMask(bool state) { - mDesc.setZReadWrite(state, state); + mDesc.setZReadWrite(state, false); } void duDebugDrawTorque::texture(bool state) @@ -94,7 +94,7 @@ void duDebugDrawTorque::begin(duDebugDrawPrimitives prim, float size) case DU_DRAW_QUADS: mPrimType = GFXTriangleList; mQuadsMode = true; break; } - mDesc.setCullMode(GFXCullNone); + mDesc.setCullMode(GFXCullCW); mDesc.setBlend(true); } diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 2cdeaddeb..1d6090d65 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -173,6 +173,12 @@ DefineEngineFunction(NavMeshUpdateOne, void, (S32 meshid, S32 objid, bool remove } NavMesh::NavMesh() +: m_triareas(0), + m_solid(0), + m_chf(0), + m_cset(0), + m_pmesh(0), + m_dmesh(0) { mTypeMask |= StaticShapeObjectType | MarkerObjectType; mFileName = StringTable->EmptyString(); @@ -184,8 +190,7 @@ NavMesh::NavMesh() mWaterMethod = Ignore; - dMemset(&cfg, 0, sizeof(cfg)); - mCellSize = mCellHeight = 0.2f; + mCellSize = mCellHeight = 0.01f; mWalkableHeight = 2.0f; mWalkableClimb = 0.3f; mWalkableRadius = 0.5f; @@ -599,6 +604,13 @@ DefineEngineMethod(NavMesh, deleteLinks, void, (),, //object->eraseLinks(); } +static void buildCallback(SceneObject* object, void* key) +{ + SceneContainer::CallbackInfo* info = reinterpret_cast(key); + if (!object->mPathfindingIgnore) + object->buildPolyList(info->context, info->polyList, info->boundingBox, info->boundingSphere); +} + bool NavMesh::build(bool background, bool saveIntermediates) { if(mBuilding) @@ -622,14 +634,53 @@ bool NavMesh::build(bool background, bool saveIntermediates) return false; } - updateConfig(); + Box3F worldBox = getWorldBox(); + SceneContainer::CallbackInfo info; + info.context = PLC_Navigation; + info.boundingBox = worldBox; + m_geo = new RecastPolyList; + info.polyList = m_geo; + info.key = this; + getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); + + // Parse water objects into the same list, but remember how much geometry was /not/ water. + U32 nonWaterVertCount = m_geo->getVertCount(); + U32 nonWaterTriCount = m_geo->getTriCount(); + if (mWaterMethod != Ignore) + { + getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info); + } + + // Check for no geometry. + if (!m_geo->getVertCount()) + { + m_geo->clear(); + return false; + } + + m_geo->getChunkyMesh(); + + // Needed for the recast config and generation params. + Box3F rc_box = DTStoRC(getWorldBox()); + S32 gw = 0, gh = 0; + rcCalcGridSize(rc_box.minExtents, rc_box.maxExtents, mCellSize, &gw, &gh); + const S32 ts = (S32)mTileSize; + const S32 tw = (gw + ts - 1) / ts; + const S32 th = (gh + ts - 1) / ts; + Con::printf("NavMesh::Build - Tiles %d x %d", tw, th); + + U32 tileBits = mMin(getNextBinLog2(tw * th), 14); + if (tileBits > 14) tileBits = 14; + U32 maxTiles = 1 << tileBits; + U32 polyBits = 22 - tileBits; + mMaxPolysPerTile = 1 << polyBits; // Build navmesh parameters from console members. dtNavMeshParams params; - rcVcopy(params.orig, cfg.bmin); - params.tileWidth = cfg.tileSize * mCellSize; - params.tileHeight = cfg.tileSize * mCellSize; - params.maxTiles = mCeil(getWorldBox().len_x() / params.tileWidth) * mCeil(getWorldBox().len_y() / params.tileHeight); + rcVcopy(params.orig, rc_box.minExtents); + params.tileWidth = mTileSize * mCellSize; + params.tileHeight = mTileSize * mCellSize; + params.maxTiles = maxTiles; params.maxPolys = mMaxPolysPerTile; // Initialise our navmesh. @@ -690,29 +741,29 @@ void NavMesh::inspectPostApply() void NavMesh::updateConfig() { - // Build rcConfig object from our console members. - dMemset(&cfg, 0, sizeof(cfg)); - cfg.cs = mCellSize; - cfg.ch = mCellHeight; - Box3F box = DTStoRC(getWorldBox()); - rcVcopy(cfg.bmin, box.minExtents); - rcVcopy(cfg.bmax, box.maxExtents); - rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height); + //// Build rcConfig object from our console members. + //dMemset(&cfg, 0, sizeof(cfg)); + //cfg.cs = mCellSize; + //cfg.ch = mCellHeight; + //Box3F box = DTStoRC(getWorldBox()); + //rcVcopy(cfg.bmin, box.minExtents); + //rcVcopy(cfg.bmax, box.maxExtents); + //rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height); - cfg.walkableHeight = mCeil(mWalkableHeight / mCellHeight); - cfg.walkableClimb = mCeil(mWalkableClimb / mCellHeight); - cfg.walkableRadius = mCeil(mWalkableRadius / mCellSize); - cfg.walkableSlopeAngle = mWalkableSlope; - cfg.borderSize = cfg.walkableRadius + 3; + //cfg.walkableHeight = mCeil(mWalkableHeight / mCellHeight); + //cfg.walkableClimb = mCeil(mWalkableClimb / mCellHeight); + //cfg.walkableRadius = mCeil(mWalkableRadius / mCellSize); + //cfg.walkableSlopeAngle = mWalkableSlope; + //cfg.borderSize = cfg.walkableRadius + 3; - cfg.detailSampleDist = mDetailSampleDist; - cfg.detailSampleMaxError = mDetailSampleMaxError; - cfg.maxEdgeLen = mMaxEdgeLen; - cfg.maxSimplificationError = mMaxSimplificationError; - cfg.maxVertsPerPoly = mMaxVertsPerPoly; - cfg.minRegionArea = mMinRegionArea; - cfg.mergeRegionArea = mMergeRegionArea; - cfg.tileSize = mTileSize / cfg.cs; + //cfg.detailSampleDist = mDetailSampleDist; + //cfg.detailSampleMaxError = mDetailSampleMaxError; + //cfg.maxEdgeLen = mMaxEdgeLen; + //cfg.maxSimplificationError = mMaxSimplificationError; + //cfg.maxVertsPerPoly = mMaxVertsPerPoly; + //cfg.minRegionArea = mMinRegionArea; + //cfg.mergeRegionArea = mMergeRegionArea; + //cfg.tileSize = mTileSize / cfg.cs; } S32 NavMesh::getTile(const Point3F& pos) @@ -740,6 +791,36 @@ void NavMesh::updateTiles(bool dirty) if(!isProperlyAdded()) return; + // this is just here so that load regens the mesh, we should be saving it out. + if (!m_geo) + { + Box3F worldBox = getWorldBox(); + SceneContainer::CallbackInfo info; + info.context = PLC_Navigation; + info.boundingBox = worldBox; + m_geo = new RecastPolyList; + info.polyList = m_geo; + info.key = this; + getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); + + // Parse water objects into the same list, but remember how much geometry was /not/ water. + U32 nonWaterVertCount = m_geo->getVertCount(); + U32 nonWaterTriCount = m_geo->getTriCount(); + if (mWaterMethod != Ignore) + { + getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info); + } + + // Check for no geometry. + if (!m_geo->getVertCount()) + { + m_geo->clear(); + return; + } + + m_geo->getChunkyMesh(); + } + mTiles.clear(); mTileData.clear(); mDirtyTiles.clear(); @@ -748,13 +829,15 @@ void NavMesh::updateTiles(bool dirty) if(box.isEmpty()) return; - updateConfig(); - // Calculate tile dimensions. - const U32 ts = cfg.tileSize; - const U32 tw = (cfg.width + ts-1) / ts; - const U32 th = (cfg.height + ts-1) / ts; - const F32 tcs = cfg.tileSize * cfg.cs; + const F32* bmin = box.minExtents; + const F32* bmax = box.maxExtents; + S32 gw = 0, gh = 0; + rcCalcGridSize(bmin, bmax, mCellSize, &gw, &gh); + const S32 ts = (S32)mTileSize; + const S32 tw = (gw + ts - 1) / ts; + const S32 th = (gh + ts - 1) / ts; + const F32 tcs = mTileSize * mCellSize; // Iterate over tiles. F32 tileBmin[3], tileBmax[3]; @@ -762,13 +845,13 @@ void NavMesh::updateTiles(bool dirty) { for(U32 x = 0; x < tw; ++x) { - tileBmin[0] = cfg.bmin[0] + x*tcs; - tileBmin[1] = cfg.bmin[1]; - tileBmin[2] = cfg.bmin[2] + y*tcs; + tileBmin[0] = bmin[0] + x*tcs; + tileBmin[1] = bmin[1]; + tileBmin[2] = bmin[2] + y*tcs; - tileBmax[0] = cfg.bmin[0] + (x+1)*tcs; - tileBmax[1] = cfg.bmax[1]; - tileBmax[2] = cfg.bmin[2] + (y+1)*tcs; + tileBmax[0] = bmin[0] + (x+1)*tcs; + tileBmax[1] = bmax[1]; + tileBmax[2] = bmin[2] + (y+1)*tcs; mTiles.push_back( Tile(RCtoDTS(tileBmin, tileBmax), @@ -846,112 +929,127 @@ void NavMesh::buildNextTile() } } -static void buildCallback(SceneObject* object,void *key) -{ - SceneContainer::CallbackInfo* info = reinterpret_cast(key); - if (!object->mPathfindingIgnore) - object->buildPolyList(info->context,info->polyList,info->boundingBox,info->boundingSphere); -} - unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dataSize) { + + cleanup(); + + const rcChunkyTriMesh* chunkyMesh = m_geo->getChunkyMesh(); + // Push out tile boundaries a bit. F32 tileBmin[3], tileBmax[3]; rcVcopy(tileBmin, tile.bmin); rcVcopy(tileBmax, tile.bmax); - tileBmin[0] -= cfg.borderSize * cfg.cs; - tileBmin[2] -= cfg.borderSize * cfg.cs; - tileBmax[0] += cfg.borderSize * cfg.cs; - tileBmax[2] += cfg.borderSize * cfg.cs; - - // Parse objects from level into RC-compatible format. - Box3F box = RCtoDTS(tileBmin, tileBmax); - SceneContainer::CallbackInfo info; - info.context = PLC_Navigation; - info.boundingBox = box; - data.geom.clear(); - info.polyList = &data.geom; - info.key = this; - getContainer()->findObjects(box, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); - - // Parse water objects into the same list, but remember how much geometry was /not/ water. - U32 nonWaterVertCount = data.geom.getVertCount(); - U32 nonWaterTriCount = data.geom.getTriCount(); - if(mWaterMethod != Ignore) - { - getContainer()->findObjects(box, WaterObjectType, buildCallback, &info); - } - - // Check for no geometry. - if (!data.geom.getVertCount()) - { - data.geom.clear(); - return NULL; - } - - // Figure out voxel dimensions of this tile. - U32 width = 0, height = 0; - width = cfg.tileSize + cfg.borderSize * 2; - height = cfg.tileSize + cfg.borderSize * 2; + // Setup our rcConfig + dMemset(&m_cfg, 0, sizeof(m_cfg)); + m_cfg.cs = mCellSize; + m_cfg.ch = mCellHeight; + m_cfg.walkableSlopeAngle = mWalkableSlope; + m_cfg.walkableHeight = (S32)mCeil(mWalkableHeight / m_cfg.ch); + m_cfg.walkableClimb = (S32)mFloor(mWalkableClimb / m_cfg.ch); + m_cfg.walkableRadius = (S32)mCeil(mWalkableRadius / m_cfg.cs); + m_cfg.maxEdgeLen = (S32)(mMaxEdgeLen / mCellSize); + m_cfg.maxSimplificationError = mMaxSimplificationError; + m_cfg.minRegionArea = (S32)mSquared((F32)mMinRegionArea); + m_cfg.mergeRegionArea = (S32)mSquared((F32)mMergeRegionArea); + m_cfg.maxVertsPerPoly = (S32)mMaxVertsPerPoly; + m_cfg.tileSize = (S32)mTileSize; + m_cfg.borderSize = mMax(m_cfg.walkableRadius + 3, mBorderSize); // use the border size if it is bigger. + m_cfg.width = m_cfg.tileSize + m_cfg.borderSize * 2; + m_cfg.height = m_cfg.tileSize + m_cfg.borderSize * 2; + m_cfg.detailSampleDist = mDetailSampleDist < 0.9f ? 0 : mCellSize * mDetailSampleDist; + m_cfg.detailSampleMaxError = mCellHeight * mDetailSampleMaxError; + rcVcopy(m_cfg.bmin, tileBmin); + rcVcopy(m_cfg.bmax, tileBmax); + m_cfg.bmin[0] -= m_cfg.borderSize * m_cfg.cs; + m_cfg.bmin[2] -= m_cfg.borderSize * m_cfg.cs; + m_cfg.bmax[0] += m_cfg.borderSize * m_cfg.cs; + m_cfg.bmax[2] += m_cfg.borderSize * m_cfg.cs; // Create a heightfield to voxelise our input geometry. - data.hf = rcAllocHeightfield(); - if(!data.hf) + m_solid = rcAllocHeightfield(); + if(!m_solid) { Con::errorf("Out of memory (rcHeightField) for NavMesh %s", getIdString()); return NULL; } - if(!rcCreateHeightfield(ctx, *data.hf, width, height, tileBmin, tileBmax, cfg.cs, cfg.ch)) + if (!rcCreateHeightfield(ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) { Con::errorf("Could not generate rcHeightField for NavMesh %s", getIdString()); return NULL; } - unsigned char *areas = new unsigned char[data.geom.getTriCount()]; - - dMemset(areas, 0, data.geom.getTriCount() * sizeof(unsigned char)); - - // Mark walkable triangles with the appropriate area flags, and rasterize. - if(mWaterMethod == Solid) + m_triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; + if (!m_triareas) { - // Treat water as solid: i.e. mark areas as walkable based on angle. - rcMarkWalkableTriangles(ctx, cfg.walkableSlopeAngle, - data.geom.getVerts(), data.geom.getVertCount(), - data.geom.getTris(), data.geom.getTriCount(), areas); - } - else - { - // Treat water as impassable: leave all area flags 0. - rcMarkWalkableTriangles(ctx, cfg.walkableSlopeAngle, - data.geom.getVerts(), nonWaterVertCount, - data.geom.getTris(), nonWaterTriCount, areas); - } - rcRasterizeTriangles(ctx, - data.geom.getVerts(), data.geom.getVertCount(), - data.geom.getTris(), areas, data.geom.getTriCount(), - *data.hf, cfg.walkableClimb); - - delete[] areas; - - // Filter out areas with low ceilings and other stuff. - rcFilterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, *data.hf); - rcFilterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf); - rcFilterWalkableLowHeightSpans(ctx, cfg.walkableHeight, *data.hf); - - data.chf = rcAllocCompactHeightfield(); - if(!data.chf) - { - Con::errorf("Out of memory (rcCompactHeightField) for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Out of memory 'm_triareas' (%d).", chunkyMesh->maxTrisPerChunk); return NULL; } - if(!rcBuildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf, *data.chf)) + + F32 tbmin[2], tbmax[2]; + tbmin[0] = m_cfg.bmin[0]; + tbmin[1] = m_cfg.bmin[2]; + tbmax[0] = m_cfg.bmax[0]; + tbmax[1] = m_cfg.bmax[2]; + int cid[512]; + const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); + if (!ncid) + return 0; + + for (int i = 0; i < ncid; ++i) { - Con::errorf("Could not generate rcCompactHeightField for NavMesh %s", getIdString()); + const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]]; + const int* ctris = &chunkyMesh->tris[node.i * 3]; + const int nctris = node.n; + + memset(m_triareas, 0, nctris * sizeof(unsigned char)); + rcMarkWalkableTriangles(ctx, m_cfg.walkableSlopeAngle, + m_geo->getVerts(), m_geo->getVertCount(), ctris, nctris, m_triareas); + + if (!rcRasterizeTriangles(ctx, m_geo->getVerts(), m_geo->getVertCount(), ctris, m_triareas, nctris, *m_solid, m_cfg.walkableClimb)) + return NULL; + } + + if (!mSaveIntermediates) + { + delete[] m_triareas; + m_triareas = 0; + } + + // these should be optional. + //if (m_filterLowHangingObstacles) + rcFilterLowHangingWalkableObstacles(ctx, m_cfg.walkableClimb, *m_solid); + //if (m_filterLedgeSpans) + rcFilterLedgeSpans(ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); + //if (m_filterWalkableLowHeightSpans) + rcFilterWalkableLowHeightSpans(ctx, m_cfg.walkableHeight, *m_solid); + + + // Compact the heightfield so that it is faster to handle from now on. + // This will result more cache coherent data as well as the neighbours + // between walkable cells will be calculated. + m_chf = rcAllocCompactHeightfield(); + if (!m_chf) + { + Con::errorf("NavMesh::buildTileData: Out of memory 'chf'."); return NULL; } - if(!rcErodeWalkableArea(ctx, cfg.walkableRadius, *data.chf)) + if (!rcBuildCompactHeightfield(ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf)) { - Con::errorf("Could not erode walkable area for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not build compact data."); + return NULL; + } + + if (!mSaveIntermediates) + { + rcFreeHeightField(m_solid); + m_solid = NULL; + } + + // Erode the walkable area by agent radius. + if (!rcErodeWalkableArea(ctx, m_cfg.walkableRadius, *m_chf)) + { + Con::errorf("NavMesh::buildTileData: Could not erode."); return NULL; } @@ -962,132 +1060,186 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat //rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf); //-------------------------- - if(false) + // Partition the heightfield so that we can use simple algorithm later to triangulate the walkable areas. + // There are 3 martitioning methods, each with some pros and cons: + // These should be implemented. + // 1) Watershed partitioning + // - the classic Recast partitioning + // - creates the nicest tessellation + // - usually slowest + // - partitions the heightfield into nice regions without holes or overlaps + // - the are some corner cases where this method creates produces holes and overlaps + // - holes may appear when a small obstacles is close to large open area (triangulation can handle this) + // - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail + // * generally the best choice if you precompute the nacmesh, use this if you have large open areas + // 2) Monotone partioning + // - fastest + // - partitions the heightfield into regions without holes and overlaps (guaranteed) + // - creates long thin polygons, which sometimes causes paths with detours + // * use this if you want fast navmesh generation + // 3) Layer partitoining + // - quite fast + // - partitions the heighfield into non-overlapping regions + // - relies on the triangulation code to cope with holes (thus slower than monotone partitioning) + // - produces better triangles than monotone partitioning + // - does not have the corner cases of watershed partitioning + // - can be slow and create a bit ugly tessellation (still better than monotone) + // if you have large open areas with small obstacles (not a problem if you use tiles) + // * good choice to use for tiled navmesh with medium and small sized tiles + + + if (/*m_partitionType == SAMPLE_PARTITION_WATERSHED*/ true) { - if(!rcBuildRegionsMonotone(ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) + // Prepare for region partitioning, by calculating distance field along the walkable surface. + if (!rcBuildDistanceField(ctx, *m_chf)) { - Con::errorf("Could not build regions for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not build distance field."); + return 0; + } + + // Partition the walkable surface into simple regions without holes. + if (!rcBuildRegions(ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) + { + Con::errorf("NavMesh::buildTileData: Could not build watershed regions."); return NULL; } } - else + else if (/*m_partitionType == SAMPLE_PARTITION_MONOTONE*/ false) { - if(!rcBuildDistanceField(ctx, *data.chf)) + // Partition the walkable surface into simple regions without holes. + // Monotone partitioning does not need distancefield. + if (!rcBuildRegionsMonotone(ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { - Con::errorf("Could not build distance field for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not build monotone regions."); return NULL; } - if(!rcBuildRegions(ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) + } + else // SAMPLE_PARTITION_LAYERS + { + // Partition the walkable surface into simple regions without holes. + if (!rcBuildLayerRegions(ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea)) { - Con::errorf("Could not build regions for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not build layer regions."); return NULL; } } - data.cs = rcAllocContourSet(); - if(!data.cs) + m_cset = rcAllocContourSet(); + if (!m_cset) { - Con::errorf("Out of memory (rcContourSet) for NavMesh %s", getIdString()); - return NULL; - } - if(!rcBuildContours(ctx, *data.chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *data.cs)) - { - Con::errorf("Could not construct rcContourSet for NavMesh %s", getIdString()); - return NULL; - } - if(data.cs->nconts <= 0) - { - Con::errorf("No contours in rcContourSet for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Out of memory 'cset'"); return NULL; } - data.pm = rcAllocPolyMesh(); - if(!data.pm) + if (!rcBuildContours(ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset)) { - Con::errorf("Out of memory (rcPolyMesh) for NavMesh %s", getIdString()); - return NULL; - } - if(!rcBuildPolyMesh(ctx, *data.cs, cfg.maxVertsPerPoly, *data.pm)) - { - Con::errorf("Could not construct rcPolyMesh for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not create contours"); return NULL; } - data.pmd = rcAllocPolyMeshDetail(); - if(!data.pmd) + if (m_cset->nconts == 0) + return NULL; + + // Build polygon navmesh from the contours. + m_pmesh = rcAllocPolyMesh(); + if (!m_pmesh) { - Con::errorf("Out of memory (rcPolyMeshDetail) for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Out of memory 'pmesh'."); return NULL; } - if(!rcBuildPolyMeshDetail(ctx, *data.pm, *data.chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *data.pmd)) + if (!rcBuildPolyMesh(ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) { - Con::errorf("Could not construct rcPolyMeshDetail for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not triangulate contours."); return NULL; } - if(data.pm->nverts >= 0xffff) + // Build detail mesh. + m_dmesh = rcAllocPolyMeshDetail(); + if (!m_dmesh) { - Con::errorf("Too many vertices in rcPolyMesh for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Out of memory 'dmesh'."); return NULL; } - for(U32 i = 0; i < data.pm->npolys; i++) - { - if(data.pm->areas[i] == RC_WALKABLE_AREA) - data.pm->areas[i] = GroundArea; - if(data.pm->areas[i] == GroundArea) - data.pm->flags[i] |= WalkFlag; - if(data.pm->areas[i] == WaterArea) - data.pm->flags[i] |= SwimFlag; + if (!rcBuildPolyMeshDetail(ctx, *m_pmesh, *m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, *m_dmesh)) + { + Con::errorf("NavMesh::buildTileData: Could build polymesh detail."); + return NULL; + } + + if (!mSaveIntermediates) + { + rcFreeCompactHeightfield(m_chf); + m_chf = 0; + rcFreeContourSet(m_cset); + m_cset = 0; } unsigned char* navData = 0; int navDataSize = 0; - - dtNavMeshCreateParams params; - dMemset(¶ms, 0, sizeof(params)); - - params.verts = data.pm->verts; - params.vertCount = data.pm->nverts; - params.polys = data.pm->polys; - params.polyAreas = data.pm->areas; - params.polyFlags = data.pm->flags; - params.polyCount = data.pm->npolys; - params.nvp = data.pm->nvp; - - params.detailMeshes = data.pmd->meshes; - params.detailVerts = data.pmd->verts; - params.detailVertsCount = data.pmd->nverts; - params.detailTris = data.pmd->tris; - params.detailTriCount = data.pmd->ntris; - - params.offMeshConVerts = mLinkVerts.address(); - params.offMeshConRad = mLinkRads.address(); - params.offMeshConDir = mLinkDirs.address(); - params.offMeshConAreas = mLinkAreas.address(); - params.offMeshConFlags = mLinkFlags.address(); - params.offMeshConUserID = mLinkIDs.address(); - params.offMeshConCount = mLinkIDs.size(); - - params.walkableHeight = mWalkableHeight; - params.walkableRadius = mWalkableRadius; - params.walkableClimb = mWalkableClimb; - params.tileX = tile.x; - params.tileY = tile.y; - params.tileLayer = 0; - rcVcopy(params.bmin, data.pm->bmin); - rcVcopy(params.bmax, data.pm->bmax); - params.cs = cfg.cs; - params.ch = cfg.ch; - params.buildBvTree = true; - - if(!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) + if (m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) { - Con::errorf("Could not create dtNavMeshData for tile (%d, %d) of NavMesh %s", - tile.x, tile.y, getIdString()); - return NULL; - } + if (m_pmesh->nverts >= 0xffff) + { + // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. + Con::errorf("NavMesh::buildTileData: Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff); + return NULL; + } + for (U32 i = 0; i < m_pmesh->npolys; i++) + { + if (m_pmesh->areas[i] == RC_WALKABLE_AREA) + m_pmesh->areas[i] = GroundArea; + + if (m_pmesh->areas[i] == GroundArea) + m_pmesh->flags[i] |= WalkFlag; + if (m_pmesh->areas[i] == WaterArea) + m_pmesh->flags[i] |= SwimFlag; + } + + dtNavMeshCreateParams params; + dMemset(¶ms, 0, sizeof(params)); + + params.verts = m_pmesh->verts; + params.vertCount = m_pmesh->nverts; + params.polys = m_pmesh->polys; + params.polyAreas = m_pmesh->areas; + params.polyFlags = m_pmesh->flags; + params.polyCount = m_pmesh->npolys; + params.nvp = m_pmesh->nvp; + + params.detailMeshes = m_dmesh->meshes; + params.detailVerts = m_dmesh->verts; + params.detailVertsCount = m_dmesh->nverts; + params.detailTris = m_dmesh->tris; + params.detailTriCount = m_dmesh->ntris; + + params.offMeshConVerts = mLinkVerts.address(); + params.offMeshConRad = mLinkRads.address(); + params.offMeshConDir = mLinkDirs.address(); + params.offMeshConAreas = mLinkAreas.address(); + params.offMeshConFlags = mLinkFlags.address(); + params.offMeshConUserID = mLinkIDs.address(); + params.offMeshConCount = mLinkIDs.size(); + + params.walkableHeight = mWalkableHeight; + params.walkableRadius = mWalkableRadius; + params.walkableClimb = mWalkableClimb; + params.tileX = tile.x; + params.tileY = tile.y; + params.tileLayer = 0; + rcVcopy(params.bmin, m_pmesh->bmin); + rcVcopy(params.bmax, m_pmesh->bmax); + params.cs = m_cfg.cs; + params.ch = m_cfg.ch; + params.buildBvTree = true; + + if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) + { + Con::errorf("NavMesh::buildTileData: Could not build Detour navmesh."); + return NULL; + } + } dataSize = navDataSize; return navData; @@ -1331,6 +1483,22 @@ void NavMesh::renderToDrawer() { } +void NavMesh::cleanup() +{ + delete[] m_triareas; + m_triareas = 0; + rcFreeHeightField(m_solid); + m_solid = 0; + rcFreeCompactHeightfield(m_chf); + m_chf = 0; + rcFreeContourSet(m_cset); + m_cset = 0; + rcFreePolyMesh(m_pmesh); + m_pmesh = 0; + rcFreePolyMeshDetail(m_dmesh); + m_dmesh = 0; +} + void NavMesh::prepRenderImage(SceneRenderState *state) { ObjectRenderInst *ri = state->getRenderPass()->allocInst(); diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 5c78f81f9..5342a700e 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -366,9 +366,6 @@ private: /// @name Intermediate data /// @{ - /// Config struct. - rcConfig cfg; - /// Updates our config from console members. void updateConfig(); @@ -419,6 +416,18 @@ private: /// Use this object to manage update events. static SimObjectPtr smEventManager; + +protected: + RecastPolyList* m_geo; + unsigned char* m_triareas; + rcHeightfield* m_solid; + rcCompactHeightfield* m_chf; + rcContourSet* m_cset; + rcPolyMesh* m_pmesh; + rcPolyMeshDetail* m_dmesh; + rcConfig m_cfg; + + void cleanup(); }; typedef NavMesh::WaterMethod NavMeshWaterMethod; diff --git a/Engine/source/navigation/recastPolyList.cpp b/Engine/source/navigation/recastPolyList.cpp index 565a47892..5736e27fc 100644 --- a/Engine/source/navigation/recastPolyList.cpp +++ b/Engine/source/navigation/recastPolyList.cpp @@ -27,7 +27,7 @@ #include "gfx/primBuilder.h" #include "gfx/gfxStateBlock.h" -RecastPolyList::RecastPolyList() +RecastPolyList::RecastPolyList() : mChunkyMesh(0) { nverts = 0; verts = NULL; @@ -44,6 +44,28 @@ RecastPolyList::~RecastPolyList() clear(); } +rcChunkyTriMesh* RecastPolyList::getChunkyMesh() +{ + if (!mChunkyMesh) + { + mChunkyMesh = new rcChunkyTriMesh; + if (!mChunkyMesh) + { + Con::errorf("Build tile navigation: out of memory"); + return NULL; + } + + if (!rcCreateChunkyTriMesh(getVerts(), getTris(), getTriCount(), 256, mChunkyMesh)) + { + Con::errorf("Build tile navigation: out of memory"); + return NULL; + } + + } + + return mChunkyMesh; +} + void RecastPolyList::clear() { nverts = 0; diff --git a/Engine/source/navigation/recastPolyList.h b/Engine/source/navigation/recastPolyList.h index 4425c2317..b57aeb099 100644 --- a/Engine/source/navigation/recastPolyList.h +++ b/Engine/source/navigation/recastPolyList.h @@ -26,6 +26,10 @@ #include "collision/abstractPolyList.h" #include "core/util/tVector.h" +#ifndef CHUNKYTRIMESH_H +#include "ChunkyTriMesh.h" +#endif + /// Represents polygons in the same manner as the .obj file format. Handy for /// padding data to Recast, since it expects this data format. At the moment, /// this class only accepts triangles. @@ -70,6 +74,9 @@ public: /// Default destructor. ~RecastPolyList(); + rcChunkyTriMesh* getChunkyMesh(); + + protected: /// Number of vertices defined. U32 nverts; @@ -93,6 +100,8 @@ protected: /// Another inherited utility function. const PlaneF& getIndexedPlane(const U32 index) override { return planes[index]; } + rcChunkyTriMesh* mChunkyMesh; + private: }; From ab83ecb5918a36869314e828314a0c37f0aef04c Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Wed, 23 Jul 2025 05:55:05 +0100 Subject: [PATCH 03/38] make sure tilesize is in world units tilesize now stays the same size as the world units provided --- .../source/navigation/duDebugDrawTorque.cpp | 5 ++- Engine/source/navigation/navMesh.cpp | 45 +++++++++---------- Engine/source/navigation/navMesh.h | 40 +---------------- 3 files changed, 24 insertions(+), 66 deletions(-) diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index 1079e22a7..a7d6c6e71 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -51,7 +51,7 @@ duDebugDrawTorque::~duDebugDrawTorque() void duDebugDrawTorque::depthMask(bool state) { - mDesc.setZReadWrite(state, false); + mDesc.setZReadWrite(state); } void duDebugDrawTorque::texture(bool state) @@ -95,7 +95,8 @@ void duDebugDrawTorque::begin(duDebugDrawPrimitives prim, float size) } mDesc.setCullMode(GFXCullCW); - mDesc.setBlend(true); + mDesc.setBlend(false); + mDesc.setZReadWrite(true); } /// Submit a vertex diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 1d6090d65..ca3263761 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -187,10 +187,11 @@ NavMesh::NavMesh() mSaveIntermediates = false; nm = NULL; ctx = NULL; + m_geo = NULL; mWaterMethod = Ignore; - mCellSize = mCellHeight = 0.01f; + mCellSize = mCellHeight = 0.2f; mWalkableHeight = 2.0f; mWalkableClimb = 0.3f; mWalkableRadius = 0.5f; @@ -664,12 +665,12 @@ bool NavMesh::build(bool background, bool saveIntermediates) Box3F rc_box = DTStoRC(getWorldBox()); S32 gw = 0, gh = 0; rcCalcGridSize(rc_box.minExtents, rc_box.maxExtents, mCellSize, &gw, &gh); - const S32 ts = (S32)mTileSize; + const S32 ts = (S32)(mTileSize / mCellSize); const S32 tw = (gw + ts - 1) / ts; const S32 th = (gh + ts - 1) / ts; Con::printf("NavMesh::Build - Tiles %d x %d", tw, th); - U32 tileBits = mMin(getNextBinLog2(tw * th), 14); + U32 tileBits = mMin(getBinLog2(getNextPow2(tw * th)), 14); if (tileBits > 14) tileBits = 14; U32 maxTiles = 1 << tileBits; U32 polyBits = 22 - tileBits; @@ -678,8 +679,8 @@ bool NavMesh::build(bool background, bool saveIntermediates) // Build navmesh parameters from console members. dtNavMeshParams params; rcVcopy(params.orig, rc_box.minExtents); - params.tileWidth = mTileSize * mCellSize; - params.tileHeight = mTileSize * mCellSize; + params.tileWidth = mTileSize; + params.tileHeight = mTileSize; params.maxTiles = maxTiles; params.maxPolys = mMaxPolysPerTile; @@ -822,7 +823,6 @@ void NavMesh::updateTiles(bool dirty) } mTiles.clear(); - mTileData.clear(); mDirtyTiles.clear(); const Box3F &box = DTStoRC(getWorldBox()); @@ -837,7 +837,7 @@ void NavMesh::updateTiles(bool dirty) const S32 ts = (S32)mTileSize; const S32 tw = (gw + ts - 1) / ts; const S32 th = (gh + ts - 1) / ts; - const F32 tcs = mTileSize * mCellSize; + const F32 tcs = mTileSize; // Iterate over tiles. F32 tileBmin[3], tileBmax[3]; @@ -860,9 +860,6 @@ void NavMesh::updateTiles(bool dirty) if(dirty) mDirtyTiles.push_back_unique(mTiles.size() - 1); - - if(mSaveIntermediates) - mTileData.increment(); } } } @@ -881,16 +878,13 @@ void NavMesh::buildNextTile() U32 i = mDirtyTiles.front(); mDirtyTiles.pop_front(); const Tile &tile = mTiles[i]; - // Intermediate data for tile build. - TileData tempdata; - TileData &tdata = mSaveIntermediates ? mTileData[i] : tempdata; - + // Remove any previous data. nm->removeTile(nm->getTileRefAt(tile.x, tile.y, 0), 0, 0); // Generate navmesh for this tile. U32 dataSize = 0; - unsigned char* data = buildTileData(tile, tdata, dataSize); + unsigned char* data = buildTileData(tile, dataSize); if(data) { // Add new data (navmesh owns and deletes the data). @@ -929,7 +923,7 @@ void NavMesh::buildNextTile() } } -unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dataSize) +unsigned char *NavMesh::buildTileData(const Tile &tile, U32 &dataSize) { cleanup(); @@ -953,7 +947,7 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat m_cfg.minRegionArea = (S32)mSquared((F32)mMinRegionArea); m_cfg.mergeRegionArea = (S32)mSquared((F32)mMergeRegionArea); m_cfg.maxVertsPerPoly = (S32)mMaxVertsPerPoly; - m_cfg.tileSize = (S32)mTileSize; + m_cfg.tileSize = (S32)(mTileSize / mCellSize); m_cfg.borderSize = mMax(m_cfg.walkableRadius + 3, mBorderSize); // use the border size if it is bigger. m_cfg.width = m_cfg.tileSize + m_cfg.borderSize * 2; m_cfg.height = m_cfg.tileSize + m_cfg.borderSize * 2; @@ -1572,21 +1566,22 @@ void NavMesh::renderLinks(duDebugDraw &dd) void NavMesh::renderTileData(duDebugDrawTorque &dd, U32 tile) { - if(tile >= mTileData.size()) - return; if(nm) { duDebugDrawNavMesh(&dd, *nm, 0); - if(mTileData[tile].chf) - duDebugDrawCompactHeightfieldSolid(&dd, *mTileData[tile].chf); + if(m_chf) + duDebugDrawCompactHeightfieldSolid(&dd, *m_chf); duDebugDrawNavMeshPortals(&dd, *nm); + + if (!m_geo) + return; + int col = duRGBA(255, 0, 255, 255); - RecastPolyList &in = mTileData[tile].geom; dd.begin(DU_DRAW_LINES); - const F32 *verts = in.getVerts(); - const S32 *tris = in.getTris(); - for(U32 t = 0; t < in.getTriCount(); t++) + const F32 *verts = m_geo->getVerts(); + const S32 *tris = m_geo->getTris(); + for(U32 t = 0; t < m_geo->getTriCount(); t++) { dd.vertex(&verts[tris[t*3]*3], col); dd.vertex(&verts[tris[t*3+1]*3], col); diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 5342a700e..20f480e22 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -254,10 +254,6 @@ protected: dtNavMesh const* getNavMesh() { return nm; } private: - /// Generates a navigation mesh for the collection of objects in this - /// mesh. Returns true if successful. Stores the created mesh in tnm. - bool generateMesh(); - /// Builds the next tile in the dirty list. void buildNextTile(); @@ -288,43 +284,9 @@ private: } }; - /// Intermediate data for tile creation. - struct TileData { - RecastPolyList geom; - rcHeightfield *hf; - rcCompactHeightfield *chf; - rcContourSet *cs; - rcPolyMesh *pm; - rcPolyMeshDetail *pmd; - TileData() - { - hf = NULL; - chf = NULL; - cs = NULL; - pm = NULL; - pmd = NULL; - } - void freeAll() - { - geom.clear(); - rcFreeHeightField(hf); - rcFreeCompactHeightfield(chf); - rcFreeContourSet(cs); - rcFreePolyMesh(pm); - rcFreePolyMeshDetail(pmd); - } - ~TileData() - { - freeAll(); - } - }; - /// List of tiles. Vector mTiles; - /// List of tile intermediate data. - Vector mTileData; - /// List of indices to the tile array which are dirty. Vector mDirtyTiles; @@ -332,7 +294,7 @@ private: void updateTiles(bool dirty = false); /// Generates navmesh data for a single tile. - unsigned char *buildTileData(const Tile &tile, TileData &data, U32 &dataSize); + unsigned char *buildTileData(const Tile &tile, U32 &dataSize); /// @} From 80473e10b5fd4fe492189135c06b1e211020fc3d Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Wed, 23 Jul 2025 15:08:29 +0100 Subject: [PATCH 04/38] added first tool Added the tileTool with the ability to select tiles Abstraction layer for navmesh tools created. --- Engine/source/navigation/guiNavEditorCtrl.cpp | 100 +++++++------- Engine/source/navigation/guiNavEditorCtrl.h | 15 ++- Engine/source/navigation/navMesh.cpp | 2 +- Engine/source/navigation/navMesh.h | 3 +- Engine/source/navigation/navMeshTool.cpp | 39 ++++++ Engine/source/navigation/navMeshTool.h | 51 ++++++++ .../navigation/navMeshTools/tileTool.cpp | 123 ++++++++++++++++++ .../source/navigation/navMeshTools/tileTool.h | 31 +++++ .../game/tools/navEditor/NavEditorGui.gui | 2 +- .../game/tools/navEditor/main.tscript | 23 +++- .../game/tools/navEditor/navEditor.tscript | 38 ++++++ Tools/CMake/modules/navigation.cmake | 2 +- 12 files changed, 363 insertions(+), 66 deletions(-) create mode 100644 Engine/source/navigation/navMeshTool.cpp create mode 100644 Engine/source/navigation/navMeshTool.h create mode 100644 Engine/source/navigation/navMeshTools/tileTool.cpp create mode 100644 Engine/source/navigation/navMeshTools/tileTool.h diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp index 2981a50e0..e3657960f 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.cpp +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -51,7 +51,6 @@ ConsoleDocClass(GuiNavEditorCtrl, 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() @@ -60,7 +59,6 @@ GuiNavEditorCtrl::GuiNavEditorCtrl() mIsDirty = false; mStartDragMousePoint = InvalidMousePoint; mMesh = NULL; - mCurTile = mTile = -1; mPlayer = mCurPlayer = NULL; mSpawnClass = mSpawnDatablock = ""; mLinkStart = Point3F::Max; @@ -158,7 +156,6 @@ void GuiNavEditorCtrl::deselect() mMesh->setSelected(false); mMesh = NULL; mPlayer = mCurPlayer = NULL; - mCurTile = mTile = -1; mLinkStart = Point3F::Max; mLink = mCurLink = -1; } @@ -200,18 +197,6 @@ DefineEngineMethod(GuiNavEditorCtrl, setLinkFlags, void, (U32 flags),, 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); @@ -313,13 +298,18 @@ bool GuiNavEditorCtrl::get3DCentre(Point3F &pos) void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) { + if (!mMesh) + return; + mGizmo->on3DMouseDown(event); - if(!isFirstResponder()) - setFirstResponder(); + 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. @@ -378,15 +368,6 @@ void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) } } - if(mMode == mTileMode && !mMesh.isNull()) - { - if(gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri)) - { - mTile = mMesh->getTile(ri.point); - mMesh->renderTileData(dd, mTile); - } - } - if(mMode == mTestMode) { // Spawn new character @@ -460,14 +441,28 @@ void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) 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); @@ -505,15 +500,6 @@ void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) } } - // 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 | VehicleObjectType, &ri)) @@ -548,6 +534,11 @@ void GuiNavEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event) void GuiNavEditorCtrl::updateGuiInfo() { + if (mTool) + { + if (mTool->updateGuiInfo()) + return; + } } void GuiNavEditorCtrl::onRender(Point2I offset, const RectI &updateRect) @@ -591,6 +582,9 @@ void GuiNavEditorCtrl::renderScene(const RectI & updateRect) Point3F camPos; mat.getColumn(3,&camPos); + if (mTool) + mTool->onRender3D(); + if(mMode == mLinkMode) { if(mLinkStart != Point3F::Max) @@ -605,19 +599,6 @@ void GuiNavEditorCtrl::renderScene(const RectI & updateRect) } } - 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()) @@ -689,6 +670,29 @@ void GuiNavEditorCtrl::_prepRenderImage(SceneManager* sceneGraph, const SceneRen }*/ } +void GuiNavEditorCtrl::setActiveTool(NavMeshTool* tool) +{ + if (mTool) + { + mTool->onDeactivated(); + } + + mTool = tool; + + if (mTool) + { + mTool->setActiveNavMesh(mMesh); + mTool->onActivated(mLastEvent); + } +} + +DefineEngineMethod(GuiNavEditorCtrl, setActiveTool, void, (const char* toolName), , "( NavMeshTool tool )") +{ + NavMeshTool* tool = dynamic_cast(Sim::findObject(toolName)); + object->setActiveTool(tool); +} + + DefineEngineMethod(GuiNavEditorCtrl, getMode, const char*, (), , "") { return object->getMode(); diff --git a/Engine/source/navigation/guiNavEditorCtrl.h b/Engine/source/navigation/guiNavEditorCtrl.h index c5d2f1b5a..f9cca7b49 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.h +++ b/Engine/source/navigation/guiNavEditorCtrl.h @@ -34,6 +34,10 @@ #include "gui/worldEditor/gizmo.h" #endif +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + #include "navMesh.h" #include "T3D/aiPlayer.h" @@ -51,7 +55,6 @@ public: static const String mSelectMode; static const String mLinkMode; static const String mCoverMode; - static const String mTileMode; static const String mTestMode; GuiNavEditorCtrl(); @@ -110,12 +113,10 @@ public: void deleteLink(); void setLinkFlags(const LinkData &d); - - void buildTile(); - void spawnPlayer(const Point3F &pos); /// @} + void setActiveTool(NavMeshTool* tool); protected: @@ -133,6 +134,9 @@ protected: /// Currently-selected NavMesh. SimObjectPtr mMesh; + /// The active tool in used by the editor. + SimObjectPtr mTool; + /// @name Link mode /// @{ @@ -145,9 +149,6 @@ protected: /// @name Tile mode /// @{ - S32 mCurTile; - S32 mTile; - duDebugDrawTorque dd; /// @} diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index ca3263761..7dccffce6 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -335,7 +335,7 @@ void NavMesh::initPersistFields() "Sets the sampling distance to use when generating the detail mesh."); addFieldV("detailSampleError", TypeRangedF32, Offset(mDetailSampleMaxError, NavMesh), &CommonValidators::PositiveFloat, "The maximum distance the detail mesh surface should deviate from heightfield data."); - addFieldV("maxEdgeLen", TypeRangedS32, Offset(mDetailSampleDist, NavMesh), &CommonValidators::PositiveInt, + addFieldV("maxEdgeLen", TypeRangedS32, Offset(mMaxEdgeLen, NavMesh), &CommonValidators::PositiveInt, "The maximum allowed length for contour edges along the border of the mesh."); addFieldV("simplificationError", TypeRangedF32, Offset(mMaxSimplificationError, NavMesh), &CommonValidators::PositiveFloat, "The maximum distance a simplfied contour's border edges should deviate from the original raw contour."); diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 20f480e22..16d08db65 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -102,6 +102,7 @@ public: U32 mMergeRegionArea; F32 mTileSize; U32 mMaxPolysPerTile; + duDebugDrawTorque mDbgDraw; /// @} /// @name Water @@ -367,8 +368,6 @@ private: /// @name Rendering /// @{ - duDebugDrawTorque mDbgDraw; - void renderToDrawer(); /// @} diff --git a/Engine/source/navigation/navMeshTool.cpp b/Engine/source/navigation/navMeshTool.cpp new file mode 100644 index 000000000..5fa05b663 --- /dev/null +++ b/Engine/source/navigation/navMeshTool.cpp @@ -0,0 +1,39 @@ + +#include "platform/platform.h" +#include "navigation/navMeshTool.h" + +#include "util/undo.h" +#include "math/mMath.h" +#include "math/mathUtils.h" + +IMPLEMENT_CONOBJECT(NavMeshTool); + +ConsoleDocClass(NavMeshTool, + "@brief Base class for NavMesh Editor specific tools\n\n" + "Editor use only.\n\n" + "@internal" +); + +void NavMeshTool::_submitUndo(UndoAction* action) +{ + AssertFatal(action, "NavMeshTool::_submitUndo() - No undo action!"); + + // Grab the mission editor undo manager. + UndoManager* undoMan = NULL; + if (!Sim::findObject("EUndoManager", undoMan)) + { + Con::errorf("NavMeshTool::_submitUndo() - EUndoManager not found!"); + return; + } + + undoMan->addAction(action); +} + +NavMeshTool::NavMeshTool() + : mNavMesh(NULL) +{ +} + +NavMeshTool::~NavMeshTool() +{ +} diff --git a/Engine/source/navigation/navMeshTool.h b/Engine/source/navigation/navMeshTool.h new file mode 100644 index 000000000..d03f12503 --- /dev/null +++ b/Engine/source/navigation/navMeshTool.h @@ -0,0 +1,51 @@ +#pragma once +#ifndef _NAVMESH_TOOL_H_ +#define _NAVMESH_TOOL_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _NAVMESH_H_ +#include "navigation/navMesh.h" +#endif + +class UndoAction; + +class NavMeshTool : public SimObject +{ + typedef SimObject Parent; +protected: + SimObjectPtr mNavMesh; + void _submitUndo(UndoAction* action); + +public: + + NavMeshTool(); + virtual ~NavMeshTool(); + + DECLARE_CONOBJECT(NavMeshTool); + + virtual void setActiveNavMesh(NavMesh* nav_mesh) { mNavMesh = nav_mesh; } + + virtual void onActivated(const Gui3DMouseEvent& lastEvent) {} + virtual void onDeactivated() {} + + virtual void on3DMouseDown(const Gui3DMouseEvent& evt) {} + virtual void on3DMouseUp(const Gui3DMouseEvent& evt) {} + virtual void on3DMouseMove(const Gui3DMouseEvent& evt) {} + virtual void on3DMouseDragged(const Gui3DMouseEvent& evt) {} + virtual void on3DMouseEnter(const Gui3DMouseEvent& evt) {} + virtual void on3DMouseLeave(const Gui3DMouseEvent& evt) {} + virtual bool onMouseWheel(const GuiEvent& evt) { return false; } + virtual void onRender3D() {} + virtual void onRender2D() {} + virtual void updateGizmo() {} + virtual bool updateGuiInfo() { return false; } + virtual void onUndoAction() {} + +}; + +#endif // !_NAVMESH_TOOL_H_ diff --git a/Engine/source/navigation/navMeshTools/tileTool.cpp b/Engine/source/navigation/navMeshTools/tileTool.cpp new file mode 100644 index 000000000..5cd16547a --- /dev/null +++ b/Engine/source/navigation/navMeshTools/tileTool.cpp @@ -0,0 +1,123 @@ +#include "TileTool.h" +#include "navigation/guiNavEditorCtrl.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" +#include "scene/sceneManager.h" +#include "math/mathUtils.h" + +IMPLEMENT_CONOBJECT(TileTool); + +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 TileTool::onActivated(const Gui3DMouseEvent& lastEvent) +{ + Con::executef(this, "onActivated"); +} + +void TileTool::onDeactivated() +{ + Con::executef(this, "onDeactivated"); +} + +void TileTool::on3DMouseDown(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F start = evt.pos; + Point3F end = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + if (gServerContainer.castRay(start, end, StaticObjectType, &ri)) + { + mSelTile = mNavMesh->getTile(ri.point); + if (mSelTile != -1) + { + mNavMesh->renderTileData(mNavMesh->mDbgDraw, mSelTile); + //mNavMesh->buildTile(tile); // Immediate rebuild + } + } +} + +void TileTool::on3DMouseMove(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + + if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + mCurTile = mNavMesh->getTile(ri.point); + else + mCurTile = -1; +} + +void TileTool::onRender3D() +{ + if (mNavMesh.isNull()) + return; + + // Optional: Draw all tile bounds as overlays + //mNavMesh->renderTilesOverlay(DebugDraw::get()->getDD()); + + if(mCurTile != -1) + renderBoxOutline(mNavMesh->getTileBox(mCurTile), ColorI::BLUE); + + if(mSelTile != -1) + renderBoxOutline(mNavMesh->getTileBox(mSelTile), ColorI::GREEN); +} + +void TileTool::buildTile() +{ + if (!mNavMesh.isNull() && mSelTile != -1) + mNavMesh->buildTile(mSelTile); +} + +bool TileTool::updateGuiInfo() +{ + GuiTextCtrl* statusbar; + Sim::findObject("EWorldEditorStatusBarInfo", statusbar); + + GuiTextCtrl* selectionBar; + Sim::findObject("EWorldEditorStatusBarSelection", selectionBar); + + String text; + + text = "LMB To select NavMesh Tile"; + + if (statusbar) + statusbar->setText(text); + + if (mSelTile != -1) + text = String::ToString("Selected Tile: %d", mSelTile); + else + text = ""; + + if (selectionBar) + selectionBar->setText(text); + + return true; +} + +DefineEngineMethod(TileTool, buildTile, void, (), , + "@brief Build the currently selected tile.") +{ + return object->buildTile(); +} diff --git a/Engine/source/navigation/navMeshTools/tileTool.h b/Engine/source/navigation/navMeshTools/tileTool.h new file mode 100644 index 000000000..a973e7bad --- /dev/null +++ b/Engine/source/navigation/navMeshTools/tileTool.h @@ -0,0 +1,31 @@ +#ifndef _TILETOOL_H_ +#define _TILETOOL_H_ + + +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + +class TileTool : public NavMeshTool +{ + typedef NavMeshTool Parent; + S32 mCurTile; + S32 mSelTile; +public: + DECLARE_CONOBJECT(TileTool); + + TileTool() { mCurTile = -1; mSelTile = -1; } + virtual ~TileTool() {} + + void onActivated(const Gui3DMouseEvent& evt) override; + void onDeactivated() override; + + void on3DMouseDown(const Gui3DMouseEvent& evt) override; + void on3DMouseMove(const Gui3DMouseEvent& evt) override; + void onRender3D() override; + void buildTile(); + + bool updateGuiInfo() override; +}; + +#endif diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui index 18646fe1a..08e63d5a5 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui @@ -422,7 +422,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { VertSizing = "bottom"; Extent = "182 18"; text = "Rebuild tile"; - command = "NavEditorGui.buildTile();"; + command = "NavMeshTools->TileTool.buildTile();"; }; }; new GuiStackControl() diff --git a/Templates/BaseGame/game/tools/navEditor/main.tscript b/Templates/BaseGame/game/tools/navEditor/main.tscript index 3c2ef7aab..b8c7816ce 100644 --- a/Templates/BaseGame/game/tools/navEditor/main.tscript +++ b/Templates/BaseGame/game/tools/navEditor/main.tscript @@ -57,6 +57,16 @@ function initializeNavEditor() editorGui = NavEditorGui; }; + new SimSet(NavMeshTools) + { + new TileTool() + { + internalName = "TileTool"; + toolTip = "Tile selection tool"; + buttonImage = "ToolsModule:select_bounds_n_image"; + }; + }; + // Bind shortcuts for the nav editor. %map = new ActionMap(); %map.bindCmd(keyboard, "1", "ENavEditorSelectModeBtn.performClick();", ""); @@ -118,12 +128,13 @@ function EditorGui::SetNavPalletBar() EWToolsPaletteWindow.setActionMap(WorldEditorInspectorPlugin.map); //Adds a button to the pallete stack - //Name Icon Click Command Tooltip text Keybind - EWToolsPaletteWindow.addButton("ViewNavMesh", "ToolsModule:visibility_toggle_n_image", "NavEditorGui.prepSelectionMode();", "", "View NavMesh", "1"); - EWToolsPaletteWindow.addButton("LinkMode", "ToolsModule:nav_link_n_image", "NavEditorGui.setMode(\"LinkMode\");", "", "Create off-mesh links", "2"); - EWToolsPaletteWindow.addButton("CoverMode", "ToolsModule:nav_cover_n_image", "NavEditorGui.setMode(\"CoverMode\");", "", "Edit cover", "3"); - EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setMode(\"TileMode\");", "", "View tiles", "4"); - EWToolsPaletteWindow.addButton("TestMode", "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setMode(\"TestMode\");", "", "Test pathfinding", "5"); + //Name Icon Click Command Tooltip text Keybind + EWToolsPaletteWindow.addButton("ViewNavMesh", "ToolsModule:visibility_toggle_n_image", "NavEditorGui.prepSelectionMode();", "", "View NavMesh", "1"); + // EWToolsPaletteWindow.addButton("LinkMode", "ToolsModule:nav_link_n_image", "NavEditorGui.setMode(\"LinkMode\");", "", "Create off-mesh links", "2"); + // EWToolsPaletteWindow.addButton("CoverMode", "ToolsModule:nav_cover_n_image", "NavEditorGui.setMode(\"CoverMode\");", "","Edit cover", "3"); + // EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setMode(\"TileMode\");", "", "View tiles", "4"); + // EWToolsPaletteWindow.addButton("TestMode", "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setMode(\"TestMode\");", "", "Test pathfinding", "5"); + EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TileTool);" , "", "View and Edit Tiles", "4"); EWToolsPaletteWindow.refresh(); } diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 1acbab8ef..667c5e9da 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -298,6 +298,44 @@ function NavEditorGui::showSidePanel() //------------------------------------------------------------------------------ +function TileTool::onActivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->SelectActions.setVisible(false); + %actions->LinkActions.setVisible(false); + %actions->CoverActions.setVisible(false); + %actions->TileActions.setVisible(false); + %actions->TestActions.setVisible(false); + + %properties = NavEditorOptionsWindow->PropertiesBox; + %properties->LinkProperties.setVisible(false); + %properties->TileProperties.setVisible(false); + %properties->TestProperties.setVisible(false); + + %actions->TileActions.setVisible(true); + %properties->TileProperties.setVisible(true); +} + +function TileTool::onDeactivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->SelectActions.setVisible(false); + %actions->LinkActions.setVisible(false); + %actions->CoverActions.setVisible(false); + %actions->TileActions.setVisible(false); + %actions->TestActions.setVisible(false); + + %properties = NavEditorOptionsWindow->PropertiesBox; + %properties->LinkProperties.setVisible(false); + %properties->TileProperties.setVisible(false); + %properties->TestProperties.setVisible(false); +} + + function NavEditorGui::onModeSet(%this, %mode) { // Callback when the nav editor changes mode. Set the appropriate dynamic diff --git a/Tools/CMake/modules/navigation.cmake b/Tools/CMake/modules/navigation.cmake index a76868107..e7dba5390 100644 --- a/Tools/CMake/modules/navigation.cmake +++ b/Tools/CMake/modules/navigation.cmake @@ -4,7 +4,7 @@ option(TORQUE_NAVIGATION "Enable Navigation module" ON) if(TORQUE_NAVIGATION) message("Enabling Navigation Module") - file(GLOB_RECURSE TORQUE_NAV_SOURCES "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.h" ) + file(GLOB_RECURSE TORQUE_NAV_SOURCES "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.h" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/navMeshTools/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navMeshTools/navigation/*.h") set(TORQUE_SOURCE_FILES ${TORQUE_SOURCE_FILES} ${TORQUE_NAV_SOURCES}) set(TORQUE_LINK_LIBRARIES ${TORQUE_LINK_LIBRARIES} recast) set(TORQUE_COMPILE_DEFINITIONS ${TORQUE_COMPILE_DEFINITIONS} recast TORQUE_NAVIGATION_ENABLED) From 1f21efc9e8e590c9624554f32eb9a5481cbc1b02 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Wed, 23 Jul 2025 16:05:15 +0100 Subject: [PATCH 05/38] Update tileTool.cpp fix linux --- Engine/source/navigation/navMeshTools/tileTool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/navigation/navMeshTools/tileTool.cpp b/Engine/source/navigation/navMeshTools/tileTool.cpp index 5cd16547a..26d2b4631 100644 --- a/Engine/source/navigation/navMeshTools/tileTool.cpp +++ b/Engine/source/navigation/navMeshTools/tileTool.cpp @@ -1,4 +1,4 @@ -#include "TileTool.h" +#include "tileTool.h" #include "navigation/guiNavEditorCtrl.h" #include "console/consoleTypes.h" #include "gfx/gfxDrawUtil.h" From 30b9502e907e7151df9d9ab53b194fc54dae9e3e Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Wed, 23 Jul 2025 21:02:44 +0100 Subject: [PATCH 06/38] navmesh cache tiles data if keep intermediate is on (we only need to cache the results of recast) fix tile generation (again) Add !m_geo check so that buildTile can regen the geometry needed to build the tile again. --- Engine/source/navigation/navMesh.cpp | 124 +++++++++++++++++++-------- Engine/source/navigation/navMesh.h | 26 +++++- 2 files changed, 114 insertions(+), 36 deletions(-) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 7dccffce6..466d957f4 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -40,6 +40,7 @@ #include "math/mathIO.h" #include "core/stream/fileStream.h" +#include "T3D/assets/LevelAsset.h" extern bool gEditingMission; @@ -365,7 +366,7 @@ bool NavMesh::onAdd() if(gEditingMission || mAlwaysRender) { mNetFlags.set(Ghostable); - if(isClientObject()) + if (isClientObject()) renderToDrawer(); } @@ -740,6 +741,27 @@ void NavMesh::inspectPostApply() cancelBuild(); } +void NavMesh::createNewFile() +{ + // We need to construct a default file name + String levelAssetId(Con::getVariable("$Client::LevelAsset")); + + LevelAsset* levelAsset; + if (!Sim::findObject(levelAssetId.c_str(), levelAsset)) + { + Con::errorf("NavMesh::createNewFile() - Unable to find current level's LevelAsset. Unable to construct NavMesh filePath"); + return; + } + + Torque::Path basePath(levelAsset->getNavmeshPath()); + + if (basePath.isEmpty()) + basePath = (Torque::Path)(levelAsset->getLevelPath()); + + String fileName = Torque::FS::MakeUniquePath(basePath.getPath(), basePath.getFileName(), "nav"); + mFileName = StringTable->insert(fileName.c_str()); +} + void NavMesh::updateConfig() { //// Build rcConfig object from our console members. @@ -792,36 +814,6 @@ void NavMesh::updateTiles(bool dirty) if(!isProperlyAdded()) return; - // this is just here so that load regens the mesh, we should be saving it out. - if (!m_geo) - { - Box3F worldBox = getWorldBox(); - SceneContainer::CallbackInfo info; - info.context = PLC_Navigation; - info.boundingBox = worldBox; - m_geo = new RecastPolyList; - info.polyList = m_geo; - info.key = this; - getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); - - // Parse water objects into the same list, but remember how much geometry was /not/ water. - U32 nonWaterVertCount = m_geo->getVertCount(); - U32 nonWaterTriCount = m_geo->getTriCount(); - if (mWaterMethod != Ignore) - { - getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info); - } - - // Check for no geometry. - if (!m_geo->getVertCount()) - { - m_geo->clear(); - return; - } - - m_geo->getChunkyMesh(); - } - mTiles.clear(); mDirtyTiles.clear(); @@ -834,7 +826,7 @@ void NavMesh::updateTiles(bool dirty) const F32* bmax = box.maxExtents; S32 gw = 0, gh = 0; rcCalcGridSize(bmin, bmax, mCellSize, &gw, &gh); - const S32 ts = (S32)mTileSize; + const S32 ts = (S32)(mTileSize / mCellSize); const S32 tw = (gw + ts - 1) / ts; const S32 th = (gh + ts - 1) / ts; const F32 tcs = mTileSize; @@ -872,12 +864,43 @@ void NavMesh::processTick(const Move *move) void NavMesh::buildNextTile() { PROFILE_SCOPE(NavMesh_buildNextTile); + + // this is just here so that load regens the mesh, also buildTile needs to regen incase geometry has changed. + if (!m_geo) + { + Box3F worldBox = getWorldBox(); + SceneContainer::CallbackInfo info; + info.context = PLC_Navigation; + info.boundingBox = worldBox; + m_geo = new RecastPolyList; + info.polyList = m_geo; + info.key = this; + getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); + + // Parse water objects into the same list, but remember how much geometry was /not/ water. + U32 nonWaterVertCount = m_geo->getVertCount(); + U32 nonWaterTriCount = m_geo->getTriCount(); + if (mWaterMethod != Ignore) + { + getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info); + } + + // Check for no geometry. + if (!m_geo->getVertCount()) + { + m_geo->clear(); + return; + } + + m_geo->getChunkyMesh(); + } + if(!mDirtyTiles.empty()) { // Pop a single dirty tile and process it. U32 i = mDirtyTiles.front(); mDirtyTiles.pop_front(); - const Tile &tile = mTiles[i]; + Tile &tile = mTiles[i]; // Remove any previous data. nm->removeTile(nm->getTileRefAt(tile.x, tile.y, 0), 0, 0); @@ -885,6 +908,33 @@ void NavMesh::buildNextTile() // Generate navmesh for this tile. U32 dataSize = 0; unsigned char* data = buildTileData(tile, dataSize); + // cache our result (these only exist if keep intermediates is ticked) + if (m_chf) + { + tile.chf = m_chf; + m_chf = 0; + } + if (m_solid) + { + tile.solid = m_solid; + m_solid = 0; + } + if (m_cset) + { + tile.cset = m_cset; + m_cset = 0; + } + if (m_pmesh) + { + tile.pmesh = m_pmesh; + m_pmesh = 0; + } + if (m_dmesh) + { + tile.dmesh = m_dmesh; + m_dmesh = 0; + } + if(data) { // Add new data (navmesh owns and deletes the data). @@ -1275,6 +1325,7 @@ void NavMesh::buildTile(const U32 &tile) { mDirtyTiles.push_back_unique(tile); ctx->startTimer(RC_TIMER_TOTAL); + m_geo = NULL; } } @@ -1757,8 +1808,13 @@ DefineEngineMethod(NavMesh, load, bool, (),, bool NavMesh::save() { - if(!dStrlen(mFileName) || !nm) + if (!nm) return false; + + if (!dStrlen(mFileName) || !nm) + { + createNewFile(); + } FileStream stream; if(!stream.open(mFileName, Torque::FS::File::Write)) diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 16d08db65..7b1102052 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -250,6 +250,8 @@ public: void inspectPostApply() override; + void createNewFile(); + protected: dtNavMesh const* getNavMesh() { return nm; } @@ -272,17 +274,37 @@ private: /// Recast min and max points. F32 bmin[3], bmax[3]; /// Default constructor. - Tile() : box(Box3F::Invalid), x(0), y(0) + Tile() : box(Box3F::Invalid), x(0), y(0), chf(0), solid(0), cset(0), pmesh(0), dmesh(0) { bmin[0] = bmin[1] = bmin[2] = bmax[0] = bmax[1] = bmax[2] = 0.0f; } /// Value constructor. Tile(const Box3F &b, U32 _x, U32 _y, const F32 *min, const F32 *max) - : box(b), x(_x), y(_y) + : box(b), x(_x), y(_y), chf(0), solid(0), cset(0), pmesh(0), dmesh(0) { rcVcopy(bmin, min); rcVcopy(bmax, max); } + + ~Tile() + { + if (chf) + delete chf; + if (cset) + delete cset; + if (solid) + delete solid; + if (pmesh) + delete pmesh; + if (dmesh) + delete dmesh; + } + + rcCompactHeightfield* chf; + rcHeightfield* solid; + rcContourSet* cset; + rcPolyMesh* pmesh; + rcPolyMeshDetail* dmesh; }; /// List of tiles. From d1771756c288024348f4bdc54f425cf836c5c638 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 24 Jul 2025 14:25:02 +0100 Subject: [PATCH 07/38] 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. --- .../source/navigation/duDebugDrawTorque.cpp | 291 ++++++++++++++++-- Engine/source/navigation/duDebugDrawTorque.h | 22 +- Engine/source/navigation/guiNavEditorCtrl.cpp | 14 + Engine/source/navigation/guiNavEditorCtrl.h | 2 + Engine/source/navigation/navMesh.cpp | 141 ++++++++- Engine/source/navigation/navMesh.h | 26 ++ .../navigation/navMeshTools/tileTool.cpp | 5 - Engine/source/navigation/recastPolyList.cpp | 47 +++ Engine/source/navigation/recastPolyList.h | 9 + .../game/tools/navEditor/NavEditorGui.gui | 6 + .../game/tools/navEditor/main.tscript | 3 + .../game/tools/navEditor/navEditor.tscript | 35 +++ 12 files changed, 548 insertions(+), 53 deletions(-) diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index a7d6c6e71..48b570c6d 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -39,10 +39,9 @@ duDebugDrawTorque::duDebugDrawTorque() { VECTOR_SET_ASSOCIATION(mVertList); + VECTOR_SET_ASSOCIATION(mDrawCache); mPrimType = 0; - mQuadsMode = false; mVertCount = 0; - dMemset(&mStore, 0, sizeof(mStore)); } duDebugDrawTorque::~duDebugDrawTorque() @@ -82,21 +81,19 @@ void duDebugDrawTorque::begin(duDebugDrawPrimitives prim, float size) if (!mVertList.empty()) mVertList.clear(); - mQuadsMode = false; mVertCount = 0; mPrimType = 0; switch (prim) { - case DU_DRAW_POINTS: mPrimType = GFXPointList; break; - case DU_DRAW_LINES: mPrimType = GFXLineList; break; - case DU_DRAW_TRIS: mPrimType = GFXTriangleList; break; - case DU_DRAW_QUADS: mPrimType = GFXTriangleList; mQuadsMode = true; break; + case DU_DRAW_POINTS: mPrimType = DU_DRAW_POINTS; break; + case DU_DRAW_LINES: mPrimType = DU_DRAW_LINES; break; + case DU_DRAW_TRIS: mPrimType = DU_DRAW_TRIS; break; + case DU_DRAW_QUADS: mPrimType = DU_DRAW_QUADS; break; } mDesc.setCullMode(GFXCullCW); mDesc.setBlend(false); - mDesc.setZReadWrite(true); } /// Submit a vertex @@ -153,41 +150,273 @@ void duDebugDrawTorque::end() return; const U32 maxVertsPerDraw = GFX_MAX_DYNAMIC_VERTS; - - U32 totalVerts = mVertList.size(); - U32 stride = 1; - U32 stripStart = 0; + Box3F box; + box.minExtents.set(F32_MAX, F32_MAX, F32_MAX); + box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); switch (mPrimType) { - case GFXLineList: stride = 2; break; - case GFXTriangleList: stride = 3; break; - case GFXLineStrip: stripStart = 1; stride = 1; break; - case GFXTriangleStrip:stripStart = 2; stride = 1; break; - default: stride = 1; break; + case DU_DRAW_POINTS: + { + const U32 totalPoints = mVertList.size(); + + for (U32 p = 0; p < totalPoints;) + { + const U32 pointsThisBatch = getMin(maxVertsPerDraw, totalPoints - p); + const U32 batchVerts = pointsThisBatch; + + GFXVertexBufferHandle buffer; + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + GFXVertexPCT* verts = buffer.lock(); + + for (U32 i = 0; i < pointsThisBatch; ++i) + { + verts[i] = mVertList[p + i]; + box.minExtents.setMin(verts[i].point); + box.maxExtents.setMax(verts[i].point); + } + buffer.unlock(); + + // --- Build index buffer + GFXPrimitiveBufferHandle pb; + pb.set(GFX, pointsThisBatch, pointsThisBatch, GFXBufferTypeStatic); + U16* indices = nullptr; + pb.lock(&indices); + + for (U32 i = 0; i < pointsThisBatch; ++i) + { + indices[i] = i; + } + + pb.unlock(); + + CachedDraw batch; + batch.primType = GFXPointList; + batch.buffer = buffer; + batch.vertexCount = batchVerts; + batch.primitiveBuffer = pb; + batch.primitiveCount = pointsThisBatch; + batch.state = mDesc; + batch.bounds = box; + + mDrawCache.push_back(batch); + + p += pointsThisBatch; + } + break; } - GFX->setPrimitiveBuffer(NULL); - GFX->setStateBlockByDesc(mDesc); - GFX->setupGenericShaders(GFXDevice::GSColor); - - for (U32 i = 0; i < totalVerts; i += maxVertsPerDraw) + case DU_DRAW_LINES: { - U32 batchSize = getMin(maxVertsPerDraw, totalVerts - i); + AssertFatal(mVertList.size() % 2 == 0, "DU_DRAW_LINES given invalid vertex count."); - mVertexBuffer.set(GFX, batchSize, GFXBufferTypeDynamic); - GFXVertexPCT* verts = mVertexBuffer.lock(); + const U32 vertsPerLine = 2; + const U32 totalLines = mVertList.size() / vertsPerLine; - if (verts) - dMemcpy(verts, &mVertList[i], sizeof(GFXVertexPCT) * batchSize); - mVertexBuffer.unlock(); + for (U32 l = 0; l < totalLines;) + { + const U32 linesThisBatch = getMin(maxVertsPerDraw / vertsPerLine, totalLines - l); + const U32 batchVerts = linesThisBatch * vertsPerLine; - GFX->setVertexBuffer(mVertexBuffer); + GFXVertexBufferHandle buffer; + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + GFXVertexPCT* verts = buffer.lock(); + + for (U32 i = 0; i < linesThisBatch * vertsPerLine; ++i) + { + verts[i] = mVertList[l * vertsPerLine + i]; + box.minExtents.setMin(verts[i].point); + box.maxExtents.setMax(verts[i].point); + } + buffer.unlock(); + + // --- Build index buffer + GFXPrimitiveBufferHandle pb; + pb.set(GFX, linesThisBatch * 2, linesThisBatch, GFXBufferTypeStatic); + U16* indices = nullptr; + pb.lock(&indices); + + for (U32 i = 0; i < linesThisBatch; ++i) + { + indices[i * 2 + 0] = i * 2; + indices[i * 2 + 1] = i * 2 + 1; + } + + pb.unlock(); + + CachedDraw batch; + batch.primType = GFXLineList; + batch.buffer = buffer; + batch.vertexCount = batchVerts; + batch.primitiveBuffer = pb; + batch.primitiveCount = linesThisBatch; + batch.state = mDesc; + batch.bounds = box; + + mDrawCache.push_back(batch); + + l += linesThisBatch; + } + + break; + } + + case DU_DRAW_TRIS: + { + AssertFatal(mVertList.size() % 3 == 0, "DU_DRAW_TRIS given invalid vertex count."); + + const U32 vertsPerTri = 3; + const U32 totalTris = mVertList.size() / vertsPerTri; + + for (U32 t = 0; t < totalTris;) + { + const U32 trisThisBatch = getMin(maxVertsPerDraw / vertsPerTri, totalTris - t); + const U32 batchVerts = trisThisBatch * vertsPerTri; + + GFXVertexBufferHandle buffer; + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + GFXVertexPCT* verts = buffer.lock(); + + for (U32 i = 0; i < trisThisBatch * vertsPerTri; ++i) + { + verts[i] = mVertList[t * vertsPerTri + i]; + box.minExtents.setMin(verts[i].point); + box.maxExtents.setMax(verts[i].point); + } + + buffer.unlock(); + + // --- Build index buffer + GFXPrimitiveBufferHandle pb; + pb.set(GFX, trisThisBatch*3, trisThisBatch, GFXBufferTypeStatic); + U16* indices = nullptr; + pb.lock(&indices); + + for (U32 i = 0; i < trisThisBatch; ++i) + { + indices[i * 3 + 0] = i * 3 + 0; + indices[i * 3 + 1] = i * 3 + 1; + indices[i * 3 + 2] = i * 3 + 2; + } + + pb.unlock(); + + CachedDraw batch; + batch.primType = GFXTriangleList; + batch.buffer = buffer; + batch.vertexCount = batchVerts; + batch.primitiveBuffer = pb; + batch.primitiveCount = trisThisBatch; + batch.state = mDesc; + batch.bounds = box; + + mDrawCache.push_back(batch); + + t += trisThisBatch; + } + + break; + } + + case DU_DRAW_QUADS: + { + AssertFatal(mVertList.size() % 4 == 0, "DU_DRAW_QUADS given wrong number of vertices."); + const U32 vertsPerQuad = 4; + const U32 totalQuads = mVertList.size() / 4; + + for (U32 q = 0; q < totalQuads;) + { + const U32 quadsThisBatch = getMin(maxVertsPerDraw / vertsPerQuad, totalQuads - q); + const U32 batchVerts = quadsThisBatch * vertsPerQuad; + const U32 batchIndices = quadsThisBatch * 6; + + GFXVertexBufferHandle buffer; + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + GFXVertexPCT* verts = buffer.lock(); + + U32 outIdx = 0; + for (U32 i = 0; i < quadsThisBatch; ++i) + { + const GFXVertexPCT& v0 = mVertList[(q + i) * 4 + 0]; + const GFXVertexPCT& v1 = mVertList[(q + i) * 4 + 1]; + const GFXVertexPCT& v2 = mVertList[(q + i) * 4 + 2]; + const GFXVertexPCT& v3 = mVertList[(q + i) * 4 + 3]; + + verts[outIdx++] = v0; + verts[outIdx++] = v1; + verts[outIdx++] = v2; + verts[outIdx++] = v3; + } + + buffer.unlock(); + + GFXPrimitiveBufferHandle pb; + pb.set(GFX, batchIndices, quadsThisBatch*2, GFXBufferTypeStatic); + U16* indices = nullptr; + pb.lock(&indices); + + for (U32 i = 0; i < quadsThisBatch; ++i) + { + const U16 base = i * 4; + indices[i * 6 + 0] = base + 0; + indices[i * 6 + 1] = base + 1; + indices[i * 6 + 2] = base + 2; + indices[i * 6 + 3] = base + 0; + indices[i * 6 + 4] = base + 2; + indices[i * 6 + 5] = base + 3; + } + + pb.unlock(); + + CachedDraw batch; + batch.primType = GFXTriangleList; + batch.buffer = buffer; + batch.vertexCount = batchVerts; + batch.primitiveBuffer = pb; + batch.primitiveCount = quadsThisBatch*2; + batch.state = mDesc; + + mDrawCache.push_back(batch); + + q += quadsThisBatch; + } + break; + } - U32 numPrims = (batchSize / stride) - stripStart; - GFX->drawPrimitive((GFXPrimitiveType)mPrimType, 0, numPrims); } mVertList.clear(); } +void duDebugDrawTorque::clearCache() +{ + mDrawCache.clear(); +} + +void duDebugDrawTorque::render(SceneRenderState* state) +{ + const Frustum& frustum = state->getCameraFrustum(); + + for (U32 i = 0; i < mDrawCache.size(); ++i) + { + const CachedDraw& draw = mDrawCache[i]; + + if (!frustum.getBounds().isOverlapped(draw.bounds)) + continue; + + GFX->setPrimitiveBuffer(draw.primitiveBuffer); + GFX->setStateBlockByDesc(draw.state); + GFX->setupGenericShaders(GFXDevice::GSColor); + GFX->setVertexBuffer(draw.buffer); + + GFX->drawIndexedPrimitive( + draw.primType, + 0, // start vertex + 0, // min vertex index + draw.vertexCount, // vertex count + 0, // start index + draw.primitiveCount // primitive count + ); + } +} + diff --git a/Engine/source/navigation/duDebugDrawTorque.h b/Engine/source/navigation/duDebugDrawTorque.h index 594be0228..6ffcd378f 100644 --- a/Engine/source/navigation/duDebugDrawTorque.h +++ b/Engine/source/navigation/duDebugDrawTorque.h @@ -40,6 +40,10 @@ #include "gfx/gfxVertexBuffer.h" #endif +#ifndef _SCENERENDERSTATE_H_ +#include "scene/sceneRenderState.h" +#endif + /** * @class duDebugDrawTorque * @brief Implements the duDebugDraw interface in Torque. @@ -99,17 +103,29 @@ public: /// End drawing primitives. void end() override; + void clearCache(); + void render(SceneRenderState* state); + private: + struct CachedDraw { + GFXPrimitiveType primType; + GFXVertexBufferHandle buffer; + GFXPrimitiveBufferHandle primitiveBuffer; + U32 vertexCount; + U32 primitiveCount; + GFXStateBlockDesc state; + Box3F bounds; + }; + + Vector mDrawCache; + GFXStateBlockDesc mDesc; Vector mVertList; // Our vertex list for setting up vertexBuffer in the End function. GFXVertexBufferHandle mVertexBuffer; // our vertex buffer for drawing. U32 mPrimType; - bool mQuadsMode; - U32 mVertCount; - F32 mStore[3][3]; void _vertex(const float x, const float y, const float z, unsigned int color); }; diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp index e3657960f..c055acd5b 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.cpp +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -686,6 +686,20 @@ void GuiNavEditorCtrl::setActiveTool(NavMeshTool* tool) } } +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(Sim::findObject(toolName)); diff --git a/Engine/source/navigation/guiNavEditorCtrl.h b/Engine/source/navigation/guiNavEditorCtrl.h index f9cca7b49..82d980e1c 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.h +++ b/Engine/source/navigation/guiNavEditorCtrl.h @@ -118,6 +118,8 @@ public: /// @} void setActiveTool(NavMeshTool* tool); + void setDrawMode(S32 id); + protected: void _prepRenderImage(SceneManager* sceneGraph, const SceneRenderState* sceneState); diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 466d957f4..cd8ce2d10 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -179,7 +179,8 @@ NavMesh::NavMesh() m_chf(0), m_cset(0), m_pmesh(0), - m_dmesh(0) + m_dmesh(0), + m_drawMode(DRAWMODE_NAVMESH) { mTypeMask |= StaticShapeObjectType | MarkerObjectType; mFileName = StringTable->EmptyString(); @@ -1526,6 +1527,119 @@ bool NavMesh::testEdgeCover(const Point3F &pos, const VectorF &dir, CoverPointDa void NavMesh::renderToDrawer() { + mDbgDraw.clearCache(); + // Recast debug draw + NetObject* no = getServerObject(); + if (no) + { + NavMesh* n = static_cast(no); + + mDbgDraw.depthMask(false); + + if (n->nm && m_drawMode != DRAWMODE_NAVMESH_TRANS) + { + if (m_drawMode != DRAWMODE_NAVMESH_INVIS) + duDebugDrawNavMesh(&mDbgDraw, *n->nm, 0); + + ////const F32 texScale = 1.0f / (n->mCellSize * 10.0f); this draw mode is useless for us. + ////duDebugDrawNavMesh(&mDbgDraw, *n->nm, 0); + //if (n->m_geo != NULL) + //{ + // duDebugDrawTriMeshSlope(&mDbgDraw, n->m_geo->getVerts(), n->m_geo->getVertCount(), + // n->m_geo->getTris(), n->m_geo->getNormals(), n->m_geo->getTriCount(), n->mWalkableSlope, texScale); + //} + } + + if (n->nm && + (m_drawMode == DRAWMODE_NAVMESH || + m_drawMode == DRAWMODE_NAVMESH_TRANS || + m_drawMode == DRAWMODE_NAVMESH_BVTREE || + m_drawMode == DRAWMODE_NAVMESH_NODES || + m_drawMode == DRAWMODE_NAVMESH_PORTALS || + m_drawMode == DRAWMODE_NAVMESH_INVIS)) + { + if(m_drawMode == DRAWMODE_NAVMESH_BVTREE) + duDebugDrawNavMeshBVTree(&mDbgDraw, *n->nm); + if(m_drawMode == DRAWMODE_NAVMESH_PORTALS) + duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm); + } + + mDbgDraw.depthMask(true); + + for (Tile& tile : n->mTiles) + { + if (tile.chf && m_drawMode == DRAWMODE_COMPACT) + { + duDebugDrawCompactHeightfieldSolid(&mDbgDraw, *tile.chf); + } + + if (tile.chf && m_drawMode == DRAWMODE_COMPACT_DISTANCE) + { + duDebugDrawCompactHeightfieldDistance(&mDbgDraw, *tile.chf); + } + + if (tile.chf && m_drawMode == DRAWMODE_COMPACT_REGIONS) + { + duDebugDrawCompactHeightfieldRegions(&mDbgDraw, *tile.chf); + } + + if (tile.solid && m_drawMode == DRAWMODE_VOXELS) + { + duDebugDrawHeightfieldSolid(&mDbgDraw, *tile.solid); + } + + if (tile.solid && m_drawMode == DRAWMODE_VOXELS_WALKABLE) + { + duDebugDrawHeightfieldWalkable(&mDbgDraw, *tile.solid); + } + + if (tile.cset && m_drawMode == DRAWMODE_RAW_CONTOURS) + { + mDbgDraw.depthMask(false); + duDebugDrawRawContours(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); + } + + if (tile.cset && m_drawMode == DRAWMODE_BOTH_CONTOURS) + { + mDbgDraw.depthMask(false); + duDebugDrawRawContours(&mDbgDraw, *tile.cset); + duDebugDrawContours(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); + } + + if (tile.cset && m_drawMode == DRAWMODE_CONTOURS) + { + mDbgDraw.depthMask(false); + duDebugDrawContours(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); + } + + if (tile.chf && tile.cset && m_drawMode == DRAWMODE_REGION_CONNECTIONS) + { + duDebugDrawCompactHeightfieldRegions(&mDbgDraw, *tile.chf); + + mDbgDraw.depthMask(false); + duDebugDrawRegionConnections(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); + } + + if (tile.pmesh && m_drawMode == DRAWMODE_POLYMESH) + { + mDbgDraw.depthMask(false); + duDebugDrawPolyMesh(&mDbgDraw, *tile.pmesh); + mDbgDraw.depthMask(true); + } + + if (tile.dmesh && m_drawMode == DRAWMODE_POLYMESH_DETAIL) + { + mDbgDraw.depthMask(false); + duDebugDrawPolyMeshDetail(&mDbgDraw, *tile.dmesh); + mDbgDraw.depthMask(true); + } + + } + } } void NavMesh::cleanup() @@ -1570,16 +1684,9 @@ void NavMesh::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInsta { NavMesh *n = static_cast(no); - if ((!gEditingMission && n->mAlwaysRender) || (gEditingMission && Con::getBoolVariable("$Nav::Editor::renderMesh", 1))) + if ((!gEditingMission && n->mAlwaysRender) || gEditingMission) { - if (n->nm) - { - duDebugDrawNavMesh(&mDbgDraw, *n->nm, 0); - if (Con::getBoolVariable("$Nav::Editor::renderPortals")) - duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm); - if (Con::getBoolVariable("$Nav::Editor::renderBVTree")) - duDebugDrawNavMeshBVTree(&mDbgDraw, *n->nm); - } + mDbgDraw.render(state); } } } @@ -1617,11 +1724,14 @@ void NavMesh::renderLinks(duDebugDraw &dd) void NavMesh::renderTileData(duDebugDrawTorque &dd, U32 tile) { + if (tile > mTiles.size()) + return; + if(nm) { - duDebugDrawNavMesh(&dd, *nm, 0); - if(m_chf) - duDebugDrawCompactHeightfieldSolid(&dd, *m_chf); + //duDebugDrawNavMesh(&dd, *nm, 0); + if(mTiles[tile].chf) + duDebugDrawCompactHeightfieldSolid(&dd, *mTiles[tile].chf); duDebugDrawNavMeshPortals(&dd, *nm); @@ -1669,6 +1779,7 @@ U32 NavMesh::packUpdate(NetConnection *conn, U32 mask, BitStream *stream) mathWrite(*stream, getTransform()); mathWrite(*stream, getScale()); stream->writeFlag(mAlwaysRender); + stream->write((U32)m_drawMode); return retMask; } @@ -1680,8 +1791,10 @@ void NavMesh::unpackUpdate(NetConnection *conn, BitStream *stream) mathRead(*stream, &mObjToWorld); mathRead(*stream, &mObjScale); mAlwaysRender = stream->readFlag(); - setTransform(mObjToWorld); + U32 draw; + stream->read(&draw); + m_drawMode = (DrawMode)draw; renderToDrawer(); } diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 7b1102052..d81df419e 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -113,6 +113,29 @@ public: Impassable }; + enum DrawMode + { + DRAWMODE_NAVMESH, + DRAWMODE_NAVMESH_TRANS, + DRAWMODE_NAVMESH_BVTREE, + DRAWMODE_NAVMESH_NODES, + DRAWMODE_NAVMESH_PORTALS, + DRAWMODE_NAVMESH_INVIS, + DRAWMODE_MESH, + DRAWMODE_VOXELS, + DRAWMODE_VOXELS_WALKABLE, + DRAWMODE_COMPACT, + DRAWMODE_COMPACT_DISTANCE, + DRAWMODE_COMPACT_REGIONS, + DRAWMODE_REGION_CONNECTIONS, + DRAWMODE_RAW_CONTOURS, + DRAWMODE_BOTH_CONTOURS, + DRAWMODE_CONTOURS, + DRAWMODE_POLYMESH, + DRAWMODE_POLYMESH_DETAIL, + MAX_DRAWMODE + }; + WaterMethod mWaterMethod; /// @} @@ -148,6 +171,8 @@ public: /// Set flags used by a link. void setLinkFlags(U32 idx, const LinkData &d); + void setDrawMode(DrawMode mode) { m_drawMode = mode; setMaskBits(LoadFlag); } + /// Set the selected state of a link. void selectLink(U32 idx, bool select, bool hover = true); @@ -409,6 +434,7 @@ protected: rcPolyMesh* m_pmesh; rcPolyMeshDetail* m_dmesh; rcConfig m_cfg; + DrawMode m_drawMode; void cleanup(); }; diff --git a/Engine/source/navigation/navMeshTools/tileTool.cpp b/Engine/source/navigation/navMeshTools/tileTool.cpp index 26d2b4631..1ad3a4742 100644 --- a/Engine/source/navigation/navMeshTools/tileTool.cpp +++ b/Engine/source/navigation/navMeshTools/tileTool.cpp @@ -45,11 +45,6 @@ void TileTool::on3DMouseDown(const Gui3DMouseEvent& evt) if (gServerContainer.castRay(start, end, StaticObjectType, &ri)) { mSelTile = mNavMesh->getTile(ri.point); - if (mSelTile != -1) - { - mNavMesh->renderTileData(mNavMesh->mDbgDraw, mSelTile); - //mNavMesh->buildTile(tile); // Immediate rebuild - } } } diff --git a/Engine/source/navigation/recastPolyList.cpp b/Engine/source/navigation/recastPolyList.cpp index 5736e27fc..0513ccbb5 100644 --- a/Engine/source/navigation/recastPolyList.cpp +++ b/Engine/source/navigation/recastPolyList.cpp @@ -33,6 +33,10 @@ RecastPolyList::RecastPolyList() : mChunkyMesh(0) verts = NULL; vertcap = 0; + nnormals = 0; + normals = NULL; + normalcap = 0; + ntris = 0; tris = NULL; tricap = 0; @@ -73,6 +77,11 @@ void RecastPolyList::clear() verts = NULL; vertcap = 0; + nnormals = 0; + delete[] normals; + normals = NULL; + normalcap = 0; + ntris = 0; delete[] tris; tris = NULL; @@ -156,6 +165,39 @@ void RecastPolyList::vertex(U32 vi) void RecastPolyList::end() { + // Fetch current triangle indices + const U32 i0 = tris[ntris * 3 + 0]; + const U32 i1 = tris[ntris * 3 + 1]; + const U32 i2 = tris[ntris * 3 + 2]; + + // Rebuild vertices + Point3F v0(verts[i0 * 3 + 0], verts[i0 * 3 + 1], verts[i0 * 3 + 2]); + Point3F v1(verts[i1 * 3 + 0], verts[i1 * 3 + 1], verts[i1 * 3 + 2]); + Point3F v2(verts[i2 * 3 + 0], verts[i2 * 3 + 1], verts[i2 * 3 + 2]); + + // Compute normal + Point3F edge1 = v1 - v0; + Point3F edge2 = v2 - v0; + Point3F normal = mCross(edge1, edge2); + normal.normalizeSafe(); + + // Allocate/resize normal buffer if needed + if (nnormals == normalcap) + { + normalcap = (normalcap == 0) ? 16 : normalcap * 2; + F32* newNormals = new F32[normalcap * 3]; + if (normals) + dMemcpy(newNormals, normals, nnormals * 3 * sizeof(F32)); + delete[] normals; + normals = newNormals; + } + + // Store normal + normals[nnormals * 3 + 0] = normal.x; + normals[nnormals * 3 + 1] = normal.y; + normals[nnormals * 3 + 2] = normal.z; + + nnormals++; ntris++; } @@ -169,6 +211,11 @@ const F32 *RecastPolyList::getVerts() const return verts; } +const F32* RecastPolyList::getNormals() const +{ + return normals; +} + U32 RecastPolyList::getTriCount() const { return ntris; diff --git a/Engine/source/navigation/recastPolyList.h b/Engine/source/navigation/recastPolyList.h index b57aeb099..62d43ff91 100644 --- a/Engine/source/navigation/recastPolyList.h +++ b/Engine/source/navigation/recastPolyList.h @@ -61,6 +61,8 @@ public: U32 getVertCount() const; const F32 *getVerts() const; + const F32* getNormals() const; + U32 getTriCount() const; const S32 *getTris() const; @@ -85,6 +87,13 @@ protected: /// Size of vertex array. U32 vertcap; + // Number of normals defined. + U32 nnormals; + // Array of normals (xyz in float array) + F32* normals; + // Size of normal array (matches verts) + U32 normalcap; + /// Number of triangles defined. U32 ntris; /// Array of triangle vertex indices. Size ntris*3 diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui index 08e63d5a5..e835ad8c1 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui @@ -292,6 +292,12 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { Extent = "86 18"; text = "Actions"; }; + new GuiPopUpMenuCtrl(DrawModeSelector) { + position = "155 0"; + extent = "189 20"; + profile = "ToolsGuiPopUpMenuProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; new GuiStackControl() { internalName = "SelectActions"; diff --git a/Templates/BaseGame/game/tools/navEditor/main.tscript b/Templates/BaseGame/game/tools/navEditor/main.tscript index b8c7816ce..cc8f5555f 100644 --- a/Templates/BaseGame/game/tools/navEditor/main.tscript +++ b/Templates/BaseGame/game/tools/navEditor/main.tscript @@ -188,6 +188,9 @@ function NavEditorPlugin::onActivated(%this) Parent::onActivated(%this); EditorGui.SetNavPalletBar(); + + DrawModeSelector.init(); + DrawModeSelector.selectDefault(); } function NavEditorPlugin::onDeactivated(%this) diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 667c5e9da..344877d27 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -673,3 +673,38 @@ singleton GuiControlProfile(NavEditorProfile) fillColor = "192 192 192 192"; category = "Editor"; }; + +function DrawModeSelector::init(%this) +{ + %this.clear(); + + %this.add("Draw NavMesh", 0); + %this.add("Draw NavMesh Transparent", 1); + %this.add("Draw NavMesh BVTree", 2); + %this.add("Draw NavMesh Nodes", 3); + %this.add("Draw NavMesh Portals", 4); + %this.add("Draw NavMesh Invis", 5); + %this.add("Draw Mesh", 6); + %this.add("Draw Voxels", 7); + %this.add("Draw Walkable Voxels", 8); + %this.add("Draw Compact Heightfield", 9); + %this.add("Draw Compact Distance", 10); + %this.add("Draw Compact Regions", 11); + %this.add("Draw Region Connections", 12); + %this.add("Draw Raw Contours", 13); + %this.add("Draw Both Contours", 14); + %this.add("Draw Contours", 15); + %this.add("Draw PolyMesh", 16); + %this.add("Draw PolyMesh Detail", 17); + +} + +function DrawModeSelector::selectDefault(%this) +{ + %this.setSelected(0); +} + +function DrawModeSelector::onSelect(%this, %id) +{ + NavEditorGui.setDrawMode(%id); +} \ No newline at end of file From de1642c33ef3e857157dbf5501b1a73a56f71d9d Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 24 Jul 2025 15:01:28 +0100 Subject: [PATCH 08/38] remove depth changes changing depth between draw modes is a bit confusing to the viewer could revist PolyAreas must start with 1, 0 = null area in recast. --- .../source/navigation/duDebugDrawTorque.cpp | 3 +- Engine/source/navigation/navMesh.cpp | 32 ++----------------- Engine/source/navigation/torqueRecast.h | 2 +- 3 files changed, 5 insertions(+), 32 deletions(-) diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index 48b570c6d..a1e3f1c0d 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -62,7 +62,7 @@ unsigned int duDebugDrawTorque::areaToCol(unsigned int area) { switch (area) { - // Ground (0) : light blue + // Ground (1) : light blue case GroundArea: return duRGBA(0, 192, 255, 255); // Water : blue case WaterArea: return duRGBA(0, 0, 255, 255); @@ -94,6 +94,7 @@ void duDebugDrawTorque::begin(duDebugDrawPrimitives prim, float size) mDesc.setCullMode(GFXCullCW); mDesc.setBlend(false); + mDesc.setZReadWrite(true); } /// Submit a vertex diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index cd8ce2d10..13164d780 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -1534,22 +1534,6 @@ void NavMesh::renderToDrawer() { NavMesh* n = static_cast(no); - mDbgDraw.depthMask(false); - - if (n->nm && m_drawMode != DRAWMODE_NAVMESH_TRANS) - { - if (m_drawMode != DRAWMODE_NAVMESH_INVIS) - duDebugDrawNavMesh(&mDbgDraw, *n->nm, 0); - - ////const F32 texScale = 1.0f / (n->mCellSize * 10.0f); this draw mode is useless for us. - ////duDebugDrawNavMesh(&mDbgDraw, *n->nm, 0); - //if (n->m_geo != NULL) - //{ - // duDebugDrawTriMeshSlope(&mDbgDraw, n->m_geo->getVerts(), n->m_geo->getVertCount(), - // n->m_geo->getTris(), n->m_geo->getNormals(), n->m_geo->getTriCount(), n->mWalkableSlope, texScale); - //} - } - if (n->nm && (m_drawMode == DRAWMODE_NAVMESH || m_drawMode == DRAWMODE_NAVMESH_TRANS || @@ -1558,14 +1542,14 @@ void NavMesh::renderToDrawer() m_drawMode == DRAWMODE_NAVMESH_PORTALS || m_drawMode == DRAWMODE_NAVMESH_INVIS)) { + if (m_drawMode != DRAWMODE_NAVMESH_INVIS) + duDebugDrawNavMesh(&mDbgDraw, *n->nm, 0); if(m_drawMode == DRAWMODE_NAVMESH_BVTREE) duDebugDrawNavMeshBVTree(&mDbgDraw, *n->nm); if(m_drawMode == DRAWMODE_NAVMESH_PORTALS) duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm); } - mDbgDraw.depthMask(true); - for (Tile& tile : n->mTiles) { if (tile.chf && m_drawMode == DRAWMODE_COMPACT) @@ -1595,47 +1579,35 @@ void NavMesh::renderToDrawer() if (tile.cset && m_drawMode == DRAWMODE_RAW_CONTOURS) { - mDbgDraw.depthMask(false); duDebugDrawRawContours(&mDbgDraw, *tile.cset); - mDbgDraw.depthMask(true); } if (tile.cset && m_drawMode == DRAWMODE_BOTH_CONTOURS) { - mDbgDraw.depthMask(false); duDebugDrawRawContours(&mDbgDraw, *tile.cset); duDebugDrawContours(&mDbgDraw, *tile.cset); - mDbgDraw.depthMask(true); } if (tile.cset && m_drawMode == DRAWMODE_CONTOURS) { - mDbgDraw.depthMask(false); duDebugDrawContours(&mDbgDraw, *tile.cset); - mDbgDraw.depthMask(true); } if (tile.chf && tile.cset && m_drawMode == DRAWMODE_REGION_CONNECTIONS) { duDebugDrawCompactHeightfieldRegions(&mDbgDraw, *tile.chf); - mDbgDraw.depthMask(false); duDebugDrawRegionConnections(&mDbgDraw, *tile.cset); - mDbgDraw.depthMask(true); } if (tile.pmesh && m_drawMode == DRAWMODE_POLYMESH) { - mDbgDraw.depthMask(false); duDebugDrawPolyMesh(&mDbgDraw, *tile.pmesh); - mDbgDraw.depthMask(true); } if (tile.dmesh && m_drawMode == DRAWMODE_POLYMESH_DETAIL) { - mDbgDraw.depthMask(false); duDebugDrawPolyMeshDetail(&mDbgDraw, *tile.dmesh); - mDbgDraw.depthMask(true); } } diff --git a/Engine/source/navigation/torqueRecast.h b/Engine/source/navigation/torqueRecast.h index f6e41affd..36187f255 100644 --- a/Engine/source/navigation/torqueRecast.h +++ b/Engine/source/navigation/torqueRecast.h @@ -52,7 +52,7 @@ inline void rcCol(unsigned int col, U8 &r, U8 &g, U8 &b, U8 &a) } enum PolyAreas { - GroundArea, + GroundArea = 1, WaterArea, OffMeshArea, NumAreas From 2df2cb5c15ecab905e671ed76c1414ee4dc02010 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 24 Jul 2025 23:43:35 +0100 Subject: [PATCH 09/38] off mesh connection tool Adds off mesh connection tool upgrade functionality to allow setting the direction to be bi-directional added immediate draw to duDebugDrawtorque so we can draw offmesh connections --- .../source/navigation/duDebugDrawTorque.cpp | 22 ++ Engine/source/navigation/duDebugDrawTorque.h | 1 + Engine/source/navigation/guiNavEditorCtrl.cpp | 74 ------- Engine/source/navigation/navMesh.cpp | 29 ++- Engine/source/navigation/navMesh.h | 6 +- .../navMeshTools/offMeshConnTool.cpp | 189 ++++++++++++++++++ .../navigation/navMeshTools/offMeshConnTool.h | 44 ++++ .../navigation/navMeshTools/tileTool.cpp | 9 +- 8 files changed, 287 insertions(+), 87 deletions(-) create mode 100644 Engine/source/navigation/navMeshTools/offMeshConnTool.cpp create mode 100644 Engine/source/navigation/navMeshTools/offMeshConnTool.h diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index a1e3f1c0d..8bcd5a48c 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -421,3 +421,25 @@ void duDebugDrawTorque::render(SceneRenderState* state) } } +void duDebugDrawTorque::immediateRender() +{ + for (U32 i = 0; i < mDrawCache.size(); ++i) + { + const CachedDraw& draw = mDrawCache[i]; + + GFX->setPrimitiveBuffer(draw.primitiveBuffer); + GFX->setStateBlockByDesc(draw.state); + GFX->setupGenericShaders(GFXDevice::GSColor); + GFX->setVertexBuffer(draw.buffer); + + GFX->drawIndexedPrimitive( + draw.primType, + 0, // start vertex + 0, // min vertex index + draw.vertexCount, // vertex count + 0, // start index + draw.primitiveCount // primitive count + ); + } +} + diff --git a/Engine/source/navigation/duDebugDrawTorque.h b/Engine/source/navigation/duDebugDrawTorque.h index 6ffcd378f..c18daf66e 100644 --- a/Engine/source/navigation/duDebugDrawTorque.h +++ b/Engine/source/navigation/duDebugDrawTorque.h @@ -105,6 +105,7 @@ public: void clearCache(); void render(SceneRenderState* state); + void immediateRender(); private: diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp index c055acd5b..e825cefb2 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.cpp +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -323,51 +323,6 @@ void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) 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 @@ -471,35 +426,6 @@ void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) 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)) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 13164d780..3d7e714c9 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -405,7 +405,7 @@ void NavMesh::setScale(const VectorF &scale) Parent::setScale(scale); } -S32 NavMesh::addLink(const Point3F &from, const Point3F &to, U32 flags) +S32 NavMesh::addLink(const Point3F &from, const Point3F &to, bool biDir, U32 flags) { Point3F rcFrom = DTStoRC(from), rcTo = DTStoRC(to); mLinkVerts.push_back(rcFrom.x); @@ -416,7 +416,7 @@ S32 NavMesh::addLink(const Point3F &from, const Point3F &to, U32 flags) mLinkVerts.push_back(rcTo.z); mLinksUnsynced.push_back(true); mLinkRads.push_back(mWalkableRadius); - mLinkDirs.push_back(0); + mLinkDirs.push_back(biDir ? 1 : 0); mLinkAreas.push_back(OffMeshArea); if (flags == 0) { Point3F dir = to - from; @@ -435,11 +435,11 @@ S32 NavMesh::addLink(const Point3F &from, const Point3F &to, U32 flags) return mLinkIDs.size() - 1; } -DefineEngineMethod(NavMesh, addLink, S32, (Point3F from, Point3F to, U32 flags), (0), +DefineEngineMethod(NavMesh, addLink, S32, (Point3F from, Point3F to, bool biDir, U32 flags), (0), "Add a link to this NavMesh between two points.\n\n" "") { - return object->addLink(from, to, flags); + return object->addLink(from, to, biDir, flags); } S32 NavMesh::getLink(const Point3F &pos) @@ -482,6 +482,23 @@ LinkData NavMesh::getLinkFlags(U32 idx) return LinkData(); } +bool NavMesh::getLinkDir(U32 idx) +{ + if (idx < mLinkIDs.size()) + { + return mLinkDirs[idx]; + } +} + +void NavMesh::setLinkDir(U32 idx, bool biDir) +{ + if (idx < mLinkIDs.size()) + { + mLinkDirs[idx] = biDir ? 1 : 0; + mLinksUnsynced[idx] = true; + } +} + DefineEngineMethod(NavMesh, getLinkFlags, S32, (U32 id),, "Get the flags set for a particular off-mesh link.") { @@ -1667,8 +1684,8 @@ void NavMesh::renderLinks(duDebugDraw &dd) { if(mBuilding) return; - dd.depthMask(true); dd.begin(DU_DRAW_LINES); + dd.depthMask(false); for(U32 i = 0; i < mLinkIDs.size(); i++) { U32 col = 0; @@ -1686,7 +1703,7 @@ void NavMesh::renderLinks(duDebugDraw &dd) s[0], s[1], s[2], e[0], e[1], e[2], 0.3f, - 0.0f, mLinkFlags[i] == DropFlag ? 0.0f : 0.4f, + (mLinkDirs[i]&1) ? 0.6f : 0.0f, mLinkFlags[i] == DropFlag ? 0.0f : 0.6f, col); if(!mDeleteLinks[i]) duAppendCircle(&dd, e[0], e[1], e[2], mLinkRads[i], col); diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index d81df419e..1e6d7376e 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -151,7 +151,7 @@ public: /// @{ /// Add an off-mesh link. - S32 addLink(const Point3F &from, const Point3F &to, U32 flags = 0); + S32 addLink(const Point3F &from, const Point3F &to, bool biDir, U32 flags = 0); /// Get the ID of the off-mesh link near the point. S32 getLink(const Point3F &pos); @@ -168,6 +168,10 @@ public: /// Get the flags used by a link. LinkData getLinkFlags(U32 idx); + bool getLinkDir(U32 idx); + + void setLinkDir(U32 idx, bool biDir); + /// Set flags used by a link. void setLinkFlags(U32 idx, const LinkData &d); diff --git a/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp b/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp new file mode 100644 index 000000000..088abdb26 --- /dev/null +++ b/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp @@ -0,0 +1,189 @@ +#include "offMeshConnTool.h" +#include "navigation/guiNavEditorCtrl.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" +#include "scene/sceneManager.h" +#include "math/mathUtils.h" + +IMPLEMENT_CONOBJECT(OffMeshConnectionTool); + +void OffMeshConnectionTool::onActivated(const Gui3DMouseEvent& evt) +{ + Con::executef(this, "onActivated"); +} + +void OffMeshConnectionTool::onDeactivated() +{ + Con::executef(this, "onDeactivated"); +} + +void OffMeshConnectionTool::on3DMouseDown(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + bool shift = evt.modifier & SI_LSHIFT; + bool ctrl = evt.modifier & SI_LCTRL; + + if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + { + U32 link = mNavMesh->getLink(ri.point); + if (link != -1) + { + if (mLink != -1) + mNavMesh->selectLink(mLink, false); + mNavMesh->selectLink(link, true, false); + mLink = link; + + if (ctrl) + { + mNavMesh->selectLink(mLink, false); + mNavMesh->deleteLink(mLink); + mLink = -1; + Con::executef(this, "onLinkDeselected"); + return; + } + else + { + LinkData d = mNavMesh->getLinkFlags(mLink); + bool biDir = mNavMesh->getLinkDir(mLink); + Con::executef(this, "onLinkSelected", Con::getIntArg(d.getFlags()), Con::getBoolArg(biDir)); + } + } + else + { + if (mLink != -1) + { + mNavMesh->selectLink(mLink, false); + mLink = -1; + Con::executef(this, "onLinkDeselected"); + } + + if (mLinkStart != Point3F::Max) + { + mLink = mNavMesh->addLink(mLinkStart, ri.point, mBiDir); + mNavMesh->selectLink(mLink, true, false); + mLinkStart = Point3F::Max; + Con::executef(this, "onLinkSelected", Con::getIntArg(mLinkCache.getFlags()), Con::getBoolArg(mBiDir)); + } + else + { + mLinkStart = ri.point; + } + } + } + else + { + if (mLink != -1) + { + mNavMesh->selectLink(mLink, false); + mLink = -1; + Con::executef(this, "onLinkDeselected"); + } + } + +} + +void OffMeshConnectionTool::on3DMouseMove(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + { + U32 link = mNavMesh->getLink(ri.point); + if (link != -1) + { + if (link != mLink) + { + if (mCurLink != -1) + mNavMesh->selectLink(mCurLink, false); + mNavMesh->selectLink(link, true, true); + } + mCurLink = link; + } + else + { + if (mCurLink != mLink) + mNavMesh->selectLink(mCurLink, false); + mCurLink = -1; + } + } + else + { + mNavMesh->selectLink(mCurLink, false); + mCurLink = -1; + } +} + +void OffMeshConnectionTool::onRender3D() +{ + if (mNavMesh.isNull()) + return; + + duDebugDrawTorque dd; + + if (mLinkStart != Point3F::Max) + { + Point3F rcFrom = DTStoRC(mLinkStart); + dd.begin(DU_DRAW_LINES); + dd.depthMask(false); + duAppendCircle(&dd, rcFrom.x, rcFrom.y, rcFrom.z, mNavMesh->mWalkableRadius, duRGBA(0, 255, 0, 255)); + dd.end(); + } + + mNavMesh->renderLinks(dd); + + dd.immediateRender(); +} + +bool OffMeshConnectionTool::updateGuiInfo() +{ + SimObject* statusbar; + Sim::findObject("EditorGuiStatusBar", statusbar); + + GuiTextCtrl* selectionBar; + Sim::findObject("EWorldEditorStatusBarSelection", selectionBar); + + String text; + text = "LMB To Select Link. CTRL+LMB To Delete Link"; + + if (statusbar) + Con::executef(statusbar, "setInfo", text.c_str()); + + if (mLink != -1) + text = String::ToString("Selected Link: %d", mLink); + else + text = ""; + + if (selectionBar) + selectionBar->setText(text); + + return true; +} + +void OffMeshConnectionTool::setLinkProperties(const LinkData& d, bool biDir) +{ + if (!mNavMesh.isNull() && mLink != -1) + { + mNavMesh->setLinkFlags(mLink, d); + mNavMesh->setLinkDir(mLink, biDir); + } + + mLinkCache = d; + mBiDir = biDir; +} + +DefineEngineMethod(OffMeshConnectionTool, setLinkProperties, void, (U32 flags, bool biDir), , + "@Brief Set properties of the selected link.") +{ + object->setLinkProperties(LinkData(flags), biDir); +} diff --git a/Engine/source/navigation/navMeshTools/offMeshConnTool.h b/Engine/source/navigation/navMeshTools/offMeshConnTool.h new file mode 100644 index 000000000..6e6732bdf --- /dev/null +++ b/Engine/source/navigation/navMeshTools/offMeshConnTool.h @@ -0,0 +1,44 @@ +#ifndef _OFFMESHCONNTOOL_H_ +#define _OFFMESHCONNTOOL_H_ + + +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + +class OffMeshConnectionTool : public NavMeshTool +{ + typedef NavMeshTool Parent; + bool mStartPosSet; + bool mBiDir; + S32 mLink; + S32 mCurLink; + Point3F mLinkStart; + LinkData mLinkCache; +public: + + DECLARE_CONOBJECT(OffMeshConnectionTool); + + OffMeshConnectionTool() { + mStartPosSet = false; + mBiDir = false; + mLink = -1; + mCurLink = -1; + mLinkStart = Point3F::Max; + mLinkCache = LinkData(0); + } + virtual ~OffMeshConnectionTool() {} + + void onActivated(const Gui3DMouseEvent& evt) override; + void onDeactivated() override; + + void on3DMouseDown(const Gui3DMouseEvent& evt) override; + void on3DMouseMove(const Gui3DMouseEvent& evt) override; + void onRender3D() override; + + bool updateGuiInfo() override; + + void setLinkProperties(const LinkData& d, bool biDir); +}; + +#endif diff --git a/Engine/source/navigation/navMeshTools/tileTool.cpp b/Engine/source/navigation/navMeshTools/tileTool.cpp index 1ad3a4742..c77923832 100644 --- a/Engine/source/navigation/navMeshTools/tileTool.cpp +++ b/Engine/source/navigation/navMeshTools/tileTool.cpp @@ -69,9 +69,6 @@ void TileTool::onRender3D() if (mNavMesh.isNull()) return; - // Optional: Draw all tile bounds as overlays - //mNavMesh->renderTilesOverlay(DebugDraw::get()->getDD()); - if(mCurTile != -1) renderBoxOutline(mNavMesh->getTileBox(mCurTile), ColorI::BLUE); @@ -87,8 +84,8 @@ void TileTool::buildTile() bool TileTool::updateGuiInfo() { - GuiTextCtrl* statusbar; - Sim::findObject("EWorldEditorStatusBarInfo", statusbar); + SimObject* statusbar; + Sim::findObject("EditorGuiStatusBar", statusbar); GuiTextCtrl* selectionBar; Sim::findObject("EWorldEditorStatusBarSelection", selectionBar); @@ -98,7 +95,7 @@ bool TileTool::updateGuiInfo() text = "LMB To select NavMesh Tile"; if (statusbar) - statusbar->setText(text); + Con::executef(statusbar, "setInfo", text.c_str()); if (mSelTile != -1) text = String::ToString("Selected Tile: %d", mSelTile); From a0b4b8627ff6b99a149ca519d991f1a2358cbded Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 25 Jul 2025 08:46:55 +0100 Subject: [PATCH 10/38] cleanup Clean out the link vars and functions from guinaveditorctrl its now handled by the tool offmeshcontool: Add ability to continue to draw from the last links end point holding shift --- Engine/source/navigation/guiNavEditorCtrl.cpp | 52 ++----------------- Engine/source/navigation/guiNavEditorCtrl.h | 7 --- .../navMeshTools/offMeshConnTool.cpp | 10 +++- 3 files changed, 12 insertions(+), 57 deletions(-) diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp index e825cefb2..3e71ba240 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.cpp +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -61,8 +61,6 @@ GuiNavEditorCtrl::GuiNavEditorCtrl() mMesh = NULL; mPlayer = mCurPlayer = NULL; mSpawnClass = mSpawnDatablock = ""; - mLinkStart = Point3F::Max; - mLink = mCurLink = -1; } GuiNavEditorCtrl::~GuiNavEditorCtrl() @@ -118,6 +116,9 @@ void GuiNavEditorCtrl::selectMesh(NavMesh *mesh) { mesh->setSelected(true); mMesh = mesh; + + if (mTool) + mTool->setActiveNavMesh(mMesh); } DefineEngineMethod(GuiNavEditorCtrl, selectMesh, void, (S32 id),, @@ -156,8 +157,6 @@ void GuiNavEditorCtrl::deselect() mMesh->setSelected(false); mMesh = NULL; mPlayer = mCurPlayer = NULL; - mLinkStart = Point3F::Max; - mLink = mCurLink = -1; } DefineEngineMethod(GuiNavEditorCtrl, deselect, void, (),, @@ -166,37 +165,6 @@ DefineEngineMethod(GuiNavEditorCtrl, deselect, void, (),, 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); @@ -511,20 +479,6 @@ void GuiNavEditorCtrl::renderScene(const RectI & updateRect) 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()) diff --git a/Engine/source/navigation/guiNavEditorCtrl.h b/Engine/source/navigation/guiNavEditorCtrl.h index 82d980e1c..db60c4c85 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.h +++ b/Engine/source/navigation/guiNavEditorCtrl.h @@ -139,13 +139,6 @@ protected: /// The active tool in used by the editor. SimObjectPtr mTool; - /// @name Link mode - /// @{ - - Point3F mLinkStart; - S32 mCurLink; - S32 mLink; - /// @} /// @name Tile mode diff --git a/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp b/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp index 088abdb26..6b10298e4 100644 --- a/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp +++ b/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp @@ -67,7 +67,12 @@ void OffMeshConnectionTool::on3DMouseDown(const Gui3DMouseEvent& evt) { mLink = mNavMesh->addLink(mLinkStart, ri.point, mBiDir); mNavMesh->selectLink(mLink, true, false); - mLinkStart = Point3F::Max; + + if (shift) + mLinkStart = ri.point; + else + mLinkStart = Point3F::Max; + Con::executef(this, "onLinkSelected", Con::getIntArg(mLinkCache.getFlags()), Con::getBoolArg(mBiDir)); } else @@ -156,6 +161,9 @@ bool OffMeshConnectionTool::updateGuiInfo() String text; text = "LMB To Select Link. CTRL+LMB To Delete Link"; + if (mLinkStart != Point3F::Max) + text = "LinkStarted: LMB To place End Point. Hold Left Shift to start a new Link from the end point."; + if (statusbar) Con::executef(statusbar, "setInfo", text.c_str()); From 496e427d76a5949cee3a242f447d7d953af19552 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 25 Jul 2025 09:44:49 +0100 Subject: [PATCH 11/38] add radius controls update the scripts for the offmeshcontool radius controlled by slider ctrl --- Engine/source/navigation/navMesh.cpp | 20 +- Engine/source/navigation/navMesh.h | 6 +- .../navMeshTools/offMeshConnTool.cpp | 17 +- .../navigation/navMeshTools/offMeshConnTool.h | 11 +- .../game/tools/navEditor/NavEditorGui.gui | 39 +++- .../game/tools/navEditor/main.tscript | 9 +- .../game/tools/navEditor/navEditor.tscript | 171 ++++++++++++------ 7 files changed, 203 insertions(+), 70 deletions(-) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 3d7e714c9..4a7e3dd06 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -405,7 +405,7 @@ void NavMesh::setScale(const VectorF &scale) Parent::setScale(scale); } -S32 NavMesh::addLink(const Point3F &from, const Point3F &to, bool biDir, U32 flags) +S32 NavMesh::addLink(const Point3F &from, const Point3F &to, bool biDir, F32 rad, U32 flags) { Point3F rcFrom = DTStoRC(from), rcTo = DTStoRC(to); mLinkVerts.push_back(rcFrom.x); @@ -415,7 +415,7 @@ S32 NavMesh::addLink(const Point3F &from, const Point3F &to, bool biDir, U32 fla mLinkVerts.push_back(rcTo.y); mLinkVerts.push_back(rcTo.z); mLinksUnsynced.push_back(true); - mLinkRads.push_back(mWalkableRadius); + mLinkRads.push_back(rad); mLinkDirs.push_back(biDir ? 1 : 0); mLinkAreas.push_back(OffMeshArea); if (flags == 0) { @@ -490,6 +490,14 @@ bool NavMesh::getLinkDir(U32 idx) } } +F32 NavMesh::getLinkRadius(U32 idx) +{ + if (idx < mLinkIDs.size()) + { + return mLinkRads[idx]; + } +} + void NavMesh::setLinkDir(U32 idx, bool biDir) { if (idx < mLinkIDs.size()) @@ -499,6 +507,14 @@ void NavMesh::setLinkDir(U32 idx, bool biDir) } } +void NavMesh::setLinkRadius(U32 idx, F32 rad) +{ + if (idx < mLinkIDs.size()) + { + mLinkRads[idx] = rad; + } +} + DefineEngineMethod(NavMesh, getLinkFlags, S32, (U32 id),, "Get the flags set for a particular off-mesh link.") { diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 1e6d7376e..641907a1e 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -151,7 +151,7 @@ public: /// @{ /// Add an off-mesh link. - S32 addLink(const Point3F &from, const Point3F &to, bool biDir, U32 flags = 0); + S32 addLink(const Point3F &from, const Point3F &to, bool biDir, F32 rad, U32 flags = 0); /// Get the ID of the off-mesh link near the point. S32 getLink(const Point3F &pos); @@ -170,8 +170,12 @@ public: bool getLinkDir(U32 idx); + F32 getLinkRadius(U32 idx); + void setLinkDir(U32 idx, bool biDir); + void setLinkRadius(U32 idx, F32 rad); + /// Set flags used by a link. void setLinkFlags(U32 idx, const LinkData &d); diff --git a/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp b/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp index 6b10298e4..e0a97e18a 100644 --- a/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp +++ b/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp @@ -51,7 +51,8 @@ void OffMeshConnectionTool::on3DMouseDown(const Gui3DMouseEvent& evt) { LinkData d = mNavMesh->getLinkFlags(mLink); bool biDir = mNavMesh->getLinkDir(mLink); - Con::executef(this, "onLinkSelected", Con::getIntArg(d.getFlags()), Con::getBoolArg(biDir)); + F32 linkRad = mNavMesh->getLinkRadius(mLink); + Con::executef(this, "onLinkSelected", Con::getIntArg(d.getFlags()), Con::getBoolArg(biDir), Con::getFloatArg(linkRad)); } } else @@ -65,7 +66,7 @@ void OffMeshConnectionTool::on3DMouseDown(const Gui3DMouseEvent& evt) if (mLinkStart != Point3F::Max) { - mLink = mNavMesh->addLink(mLinkStart, ri.point, mBiDir); + mLink = mNavMesh->addLink(mLinkStart, ri.point, mBiDir, mLinkRadius); mNavMesh->selectLink(mLink, true, false); if (shift) @@ -73,7 +74,7 @@ void OffMeshConnectionTool::on3DMouseDown(const Gui3DMouseEvent& evt) else mLinkStart = Point3F::Max; - Con::executef(this, "onLinkSelected", Con::getIntArg(mLinkCache.getFlags()), Con::getBoolArg(mBiDir)); + Con::executef(this, "onLinkSelected", Con::getIntArg(mLinkCache.getFlags()), Con::getBoolArg(mBiDir), Con::getFloatArg(mLinkRadius)); } else { @@ -141,7 +142,7 @@ void OffMeshConnectionTool::onRender3D() Point3F rcFrom = DTStoRC(mLinkStart); dd.begin(DU_DRAW_LINES); dd.depthMask(false); - duAppendCircle(&dd, rcFrom.x, rcFrom.y, rcFrom.z, mNavMesh->mWalkableRadius, duRGBA(0, 255, 0, 255)); + duAppendCircle(&dd, rcFrom.x, rcFrom.y, rcFrom.z, mLinkRadius, duRGBA(0, 255, 0, 255)); dd.end(); } @@ -178,20 +179,22 @@ bool OffMeshConnectionTool::updateGuiInfo() return true; } -void OffMeshConnectionTool::setLinkProperties(const LinkData& d, bool biDir) +void OffMeshConnectionTool::setLinkProperties(const LinkData& d, bool biDir, F32 rad) { if (!mNavMesh.isNull() && mLink != -1) { mNavMesh->setLinkFlags(mLink, d); mNavMesh->setLinkDir(mLink, biDir); + mNavMesh->setLinkRadius(mLink, rad); } mLinkCache = d; mBiDir = biDir; + mLinkRadius = rad; } -DefineEngineMethod(OffMeshConnectionTool, setLinkProperties, void, (U32 flags, bool biDir), , +DefineEngineMethod(OffMeshConnectionTool, setLinkProperties, void, (U32 flags, bool biDir, F32 rad), , "@Brief Set properties of the selected link.") { - object->setLinkProperties(LinkData(flags), biDir); + object->setLinkProperties(LinkData(flags), biDir, rad); } diff --git a/Engine/source/navigation/navMeshTools/offMeshConnTool.h b/Engine/source/navigation/navMeshTools/offMeshConnTool.h index 6e6732bdf..97b2ffa46 100644 --- a/Engine/source/navigation/navMeshTools/offMeshConnTool.h +++ b/Engine/source/navigation/navMeshTools/offMeshConnTool.h @@ -15,6 +15,7 @@ class OffMeshConnectionTool : public NavMeshTool S32 mCurLink; Point3F mLinkStart; LinkData mLinkCache; + F32 mLinkRadius; public: DECLARE_CONOBJECT(OffMeshConnectionTool); @@ -26,9 +27,17 @@ public: mCurLink = -1; mLinkStart = Point3F::Max; mLinkCache = LinkData(0); + mLinkRadius = 1.0; } virtual ~OffMeshConnectionTool() {} + void setActiveNavMesh(NavMesh* nav_mesh) override { + mNavMesh = nav_mesh; + + if (!mNavMesh.isNull()) + mLinkRadius = mNavMesh->mWalkableRadius; + } + void onActivated(const Gui3DMouseEvent& evt) override; void onDeactivated() override; @@ -38,7 +47,7 @@ public: bool updateGuiInfo() override; - void setLinkProperties(const LinkData& d, bool biDir); + void setLinkProperties(const LinkData& d, bool biDir, F32 rad); }; #endif diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui index e835ad8c1..17b443670 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui @@ -573,7 +573,44 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { position = "7 21"; extent = "186 64"; padding = "2 2 2 2"; - + new GuiTextEditSliderCtrl() { + internalName = "LinkRadius"; + class = "NavMeshLinkRadius"; + extent = "50 15"; + format = "%3.2f"; + range = "0 1e+03"; + increment = "0.1"; + focusOnMouseWheel = "0"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + profile = "ToolsGuiCheckBoxProfile"; + tooltipProfile = "GuiToolTipProfile"; + toolTip = "The radius for this link."; + AltCommand = "NavMeshTools->LinkTool.updateRadius();"; + }; + new GuiCheckBoxCtrl() { + internalName = "LinkBiDirection"; + class = "NavMeshLinkBiDirection"; + text = " Link Bi-Directional"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + extent = "159 15"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiCheckBoxProfile"; + visible = "1"; + active = "0"; + tooltipProfile = "GuiToolTipProfile"; + toolTip = "This link is bidirectional."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiCheckBoxCtrl() { internalName = "LinkWalkFlag"; class = "NavMeshLinkFlagButton"; diff --git a/Templates/BaseGame/game/tools/navEditor/main.tscript b/Templates/BaseGame/game/tools/navEditor/main.tscript index cc8f5555f..a40d2f772 100644 --- a/Templates/BaseGame/game/tools/navEditor/main.tscript +++ b/Templates/BaseGame/game/tools/navEditor/main.tscript @@ -59,6 +59,13 @@ function initializeNavEditor() new SimSet(NavMeshTools) { + new OffMeshConnectionTool() + { + internalName = "LinkTool"; + toolTip = "Link tool"; + buttonImage = "ToolsModule:nav_link_n_image"; + }; + new TileTool() { internalName = "TileTool"; @@ -130,7 +137,7 @@ function EditorGui::SetNavPalletBar() //Adds a button to the pallete stack //Name Icon Click Command Tooltip text Keybind EWToolsPaletteWindow.addButton("ViewNavMesh", "ToolsModule:visibility_toggle_n_image", "NavEditorGui.prepSelectionMode();", "", "View NavMesh", "1"); - // EWToolsPaletteWindow.addButton("LinkMode", "ToolsModule:nav_link_n_image", "NavEditorGui.setMode(\"LinkMode\");", "", "Create off-mesh links", "2"); + EWToolsPaletteWindow.addButton("LinkMode", "ToolsModule:nav_link_n_image", "NavEditorGui.setActiveTool(NavMeshTools->LinkTool);", "", "Create off-mesh links", "2"); // EWToolsPaletteWindow.addButton("CoverMode", "ToolsModule:nav_cover_n_image", "NavEditorGui.setMode(\"CoverMode\");", "","Edit cover", "3"); // EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setMode(\"TileMode\");", "", "View tiles", "4"); // EWToolsPaletteWindow.addButton("TestMode", "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setMode(\"TestMode\");", "", "Test pathfinding", "5"); diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 344877d27..2cfd23104 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -298,6 +298,119 @@ function NavEditorGui::showSidePanel() //------------------------------------------------------------------------------ +function OffMeshConnectionTool::onActivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->SelectActions.setVisible(false); + %actions->LinkActions.setVisible(false); + %actions->CoverActions.setVisible(false); + %actions->TileActions.setVisible(false); + %actions->TestActions.setVisible(false); + + %properties = NavEditorOptionsWindow->PropertiesBox; + %properties->LinkProperties.setVisible(false); + %properties->TileProperties.setVisible(false); + %properties->TestProperties.setVisible(false); + + %actions->LinkActions.setVisible(true); + %properties->LinkProperties.setVisible(true); +} + +function OffMeshConnectionTool::onDeactivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->SelectActions.setVisible(false); + %actions->LinkActions.setVisible(false); + %actions->CoverActions.setVisible(false); + %actions->TileActions.setVisible(false); + %actions->TestActions.setVisible(false); + + %properties = NavEditorOptionsWindow->PropertiesBox; + %properties->LinkProperties.setVisible(false); + %properties->TileProperties.setVisible(false); + %properties->TestProperties.setVisible(false); +} + +function OffMeshConnectionTool::updateLinkFlags(%this) +{ + %properties = NavEditorOptionsWindow-->LinkProperties; + %this.setLinkProperties(getLinkFlags(%properties), %properties->LinkBiDirection.isStateOn(), %properties->LinkRadius.getValue()); +} + +function updateLinkData(%control, %flags, %biDir, %radius) +{ + %control->LinkRadius.setActive(true); + %control->LinkBiDirection.setActive(true); + %control->LinkWalkFlag.setActive(true); + %control->LinkJumpFlag.setActive(true); + %control->LinkDropFlag.setActive(true); + %control->LinkLedgeFlag.setActive(true); + %control->LinkClimbFlag.setActive(true); + %control->LinkTeleportFlag.setActive(true); + + %control->LinkRadius.setValue(%radius); + %control->LinkBiDirection.setStateOn(%biDir); + %control->LinkWalkFlag.setStateOn(%flags & $Nav::WalkFlag); + %control->LinkJumpFlag.setStateOn(%flags & $Nav::JumpFlag); + %control->LinkDropFlag.setStateOn(%flags & $Nav::DropFlag); + %control->LinkLedgeFlag.setStateOn(%flags & $Nav::LedgeFlag); + %control->LinkClimbFlag.setStateOn(%flags & $Nav::ClimbFlag); + %control->LinkTeleportFlag.setStateOn(%flags & $Nav::TeleportFlag); +} + +function getLinkFlags(%control) +{ + return (%control->LinkWalkFlag.isStateOn() ? $Nav::WalkFlag : 0) | + (%control->LinkJumpFlag.isStateOn() ? $Nav::JumpFlag : 0) | + (%control->LinkDropFlag.isStateOn() ? $Nav::DropFlag : 0) | + (%control->LinkLedgeFlag.isStateOn() ? $Nav::LedgeFlag : 0) | + (%control->LinkClimbFlag.isStateOn() ? $Nav::ClimbFlag : 0) | + (%control->LinkTeleportFlag.isStateOn() ? $Nav::TeleportFlag : 0); +} + +function disableLinkData(%control) +{ + %control->LinkRadius.setActive(false); + %control->LinkBiDirection.setActive(false); + %control->LinkWalkFlag.setActive(false); + %control->LinkJumpFlag.setActive(false); + %control->LinkDropFlag.setActive(false); + %control->LinkLedgeFlag.setActive(false); + %control->LinkClimbFlag.setActive(false); + %control->LinkTeleportFlag.setActive(false); +} + +function OffMeshConnectionTool::onLinkSelected(%this, %flags, %biDir, %radius) +{ + updateLinkData(NavEditorOptionsWindow-->LinkProperties, %flags, %biDir, %radius); +} + +function OffMeshConnectionTool::onLinkDeselected(%this) +{ + disableLinkData(NavEditorOptionsWindow-->LinkProperties); +} + +function OffMeshConnectionTool::updateRadius(%this) +{ + %this.updateLinkFlags(); +} + +function NavMeshLinkFlagButton::onClick(%this) +{ + NavMeshTools->LinkTool.updateLinkFlags(); +} + +function NavMeshLinkBiDirection::onClick(%this) +{ + NavMeshTools->LinkTool.updateLinkFlags(); +} + +//------------------------------------------------------ + function TileTool::onActivated(%this) { NavInspector.setVisible(false); @@ -335,6 +448,7 @@ function TileTool::onDeactivated(%this) %properties->TestProperties.setVisible(false); } +//------------------------------------------------------ function NavEditorGui::onModeSet(%this, %mode) { @@ -453,43 +567,6 @@ function NavEditorGui::buildLinks(%this) } } -function updateLinkData(%control, %flags) -{ - %control->LinkWalkFlag.setActive(true); - %control->LinkJumpFlag.setActive(true); - %control->LinkDropFlag.setActive(true); - %control->LinkLedgeFlag.setActive(true); - %control->LinkClimbFlag.setActive(true); - %control->LinkTeleportFlag.setActive(true); - - %control->LinkWalkFlag.setStateOn(%flags & $Nav::WalkFlag); - %control->LinkJumpFlag.setStateOn(%flags & $Nav::JumpFlag); - %control->LinkDropFlag.setStateOn(%flags & $Nav::DropFlag); - %control->LinkLedgeFlag.setStateOn(%flags & $Nav::LedgeFlag); - %control->LinkClimbFlag.setStateOn(%flags & $Nav::ClimbFlag); - %control->LinkTeleportFlag.setStateOn(%flags & $Nav::TeleportFlag); -} - -function getLinkFlags(%control) -{ - return (%control->LinkWalkFlag.isStateOn() ? $Nav::WalkFlag : 0) | - (%control->LinkJumpFlag.isStateOn() ? $Nav::JumpFlag : 0) | - (%control->LinkDropFlag.isStateOn() ? $Nav::DropFlag : 0) | - (%control->LinkLedgeFlag.isStateOn() ? $Nav::LedgeFlag : 0) | - (%control->LinkClimbFlag.isStateOn() ? $Nav::ClimbFlag : 0) | - (%control->LinkTeleportFlag.isStateOn() ? $Nav::TeleportFlag : 0); -} - -function disableLinkData(%control) -{ - %control->LinkWalkFlag.setActive(false); - %control->LinkJumpFlag.setActive(false); - %control->LinkDropFlag.setActive(false); - %control->LinkLedgeFlag.setActive(false); - %control->LinkClimbFlag.setActive(false); - %control->LinkTeleportFlag.setActive(false); -} - function NavEditorGui::onLinkSelected(%this, %flags) { updateLinkData(NavEditorOptionsWindow-->LinkProperties, %flags); @@ -508,16 +585,6 @@ function NavEditorGui::onPlayerSelected(%this, %flags) updateLinkData(NavEditorOptionsWindow-->TestProperties, %flags); } -function NavEditorGui::updateLinkFlags(%this) -{ - if(isObject(%this.getMesh())) - { - %properties = NavEditorOptionsWindow-->LinkProperties; - %this.setLinkFlags(getLinkFlags(%properties)); - %this.isDirty = true; - } -} - function NavEditorGui::updateTestFlags(%this) { if(isObject(%this.getPlayer())) @@ -536,11 +603,6 @@ function NavEditorGui::updateTestFlags(%this) } } -function NavEditorGui::onLinkDeselected(%this) -{ - disableLinkData(NavEditorOptionsWindow-->LinkProperties); -} - function NavEditorGui::onPlayerDeselected(%this) { disableLinkData(NavEditorOptionsWindow-->TestProperties); @@ -656,11 +718,6 @@ function ENavEditorPaletteButton::onClick(%this) //----------------------------------------------------------------------------- -function NavMeshLinkFlagButton::onClick(%this) -{ - NavEditorGui.updateLinkFlags(); -} - function NavMeshTestFlagButton::onClick(%this) { NavEditorGui.updateTestFlags(); From e55d3b6f82e24f70de8f0c49d000da2b4c32b932 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 25 Jul 2025 10:11:08 +0100 Subject: [PATCH 12/38] Update NavEditorGui.gui fix textsliderctrl not losing focus, reason for the issue was incorrect profile --- Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui index 17b443670..3445d3752 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui @@ -585,7 +585,8 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { password = "0"; tabComplete = "0"; sinkAllKeyEvents = "0"; - profile = "ToolsGuiCheckBoxProfile"; + hovertime = "1000"; + profile = "ToolsGuiTextEditProfile"; tooltipProfile = "GuiToolTipProfile"; toolTip = "The radius for this link."; AltCommand = "NavMeshTools->LinkTool.updateRadius();"; From 5c2ed84b2461557df959cb7e00002e895a8cbf46 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 25 Jul 2025 11:11:58 +0100 Subject: [PATCH 13/38] duDebugDrawTorque add override added an override flag to stop detour from setting our depth mask state. This was causing navmesh to draw through other objects when it wasnt meant to Reset our bounds box for each draw cache --- .../source/navigation/duDebugDrawTorque.cpp | 40 +++++++++++++++---- Engine/source/navigation/duDebugDrawTorque.h | 8 ++++ Engine/source/navigation/navMesh.cpp | 18 ++++++++- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index 8bcd5a48c..637c483b2 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -42,6 +42,7 @@ duDebugDrawTorque::duDebugDrawTorque() VECTOR_SET_ASSOCIATION(mDrawCache); mPrimType = 0; mVertCount = 0; + mOverrideState = false; } duDebugDrawTorque::~duDebugDrawTorque() @@ -50,7 +51,26 @@ duDebugDrawTorque::~duDebugDrawTorque() void duDebugDrawTorque::depthMask(bool state) { + if (mOverrideState) + return; + mDesc.setZReadWrite(state); + if (!state) + { + mDesc.setCullMode(GFXCullNone); + mDesc.setBlend(true); + } + else + { + mDesc.setCullMode(GFXCullCW); + mDesc.setBlend(false); + } +} + +void duDebugDrawTorque::depthMask(bool state, bool isOverride) +{ + depthMask(state); + mOverrideState = isOverride; } void duDebugDrawTorque::texture(bool state) @@ -92,9 +112,6 @@ void duDebugDrawTorque::begin(duDebugDrawPrimitives prim, float size) case DU_DRAW_QUADS: mPrimType = DU_DRAW_QUADS; break; } - mDesc.setCullMode(GFXCullCW); - mDesc.setBlend(false); - mDesc.setZReadWrite(true); } /// Submit a vertex @@ -151,10 +168,7 @@ void duDebugDrawTorque::end() return; const U32 maxVertsPerDraw = GFX_MAX_DYNAMIC_VERTS; - Box3F box; - box.minExtents.set(F32_MAX, F32_MAX, F32_MAX); - box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); - + switch (mPrimType) { case DU_DRAW_POINTS: @@ -165,6 +179,9 @@ void duDebugDrawTorque::end() { const U32 pointsThisBatch = getMin(maxVertsPerDraw, totalPoints - p); const U32 batchVerts = pointsThisBatch; + Box3F box; + box.minExtents.set(F32_MAX, F32_MAX, F32_MAX); + box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); GFXVertexBufferHandle buffer; buffer.set(GFX, batchVerts, GFXBufferTypeStatic); @@ -218,6 +235,9 @@ void duDebugDrawTorque::end() { const U32 linesThisBatch = getMin(maxVertsPerDraw / vertsPerLine, totalLines - l); const U32 batchVerts = linesThisBatch * vertsPerLine; + Box3F box; + box.minExtents.set(F32_MAX, F32_MAX, F32_MAX); + box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); GFXVertexBufferHandle buffer; buffer.set(GFX, batchVerts, GFXBufferTypeStatic); @@ -273,6 +293,9 @@ void duDebugDrawTorque::end() { const U32 trisThisBatch = getMin(maxVertsPerDraw / vertsPerTri, totalTris - t); const U32 batchVerts = trisThisBatch * vertsPerTri; + Box3F box; + box.minExtents.set(F32_MAX, F32_MAX, F32_MAX); + box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); GFXVertexBufferHandle buffer; buffer.set(GFX, batchVerts, GFXBufferTypeStatic); @@ -330,6 +353,9 @@ void duDebugDrawTorque::end() const U32 quadsThisBatch = getMin(maxVertsPerDraw / vertsPerQuad, totalQuads - q); const U32 batchVerts = quadsThisBatch * vertsPerQuad; const U32 batchIndices = quadsThisBatch * 6; + Box3F box; + box.minExtents.set(F32_MAX, F32_MAX, F32_MAX); + box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); GFXVertexBufferHandle buffer; buffer.set(GFX, batchVerts, GFXBufferTypeStatic); diff --git a/Engine/source/navigation/duDebugDrawTorque.h b/Engine/source/navigation/duDebugDrawTorque.h index c18daf66e..a1c69ac4f 100644 --- a/Engine/source/navigation/duDebugDrawTorque.h +++ b/Engine/source/navigation/duDebugDrawTorque.h @@ -61,6 +61,13 @@ public: /// Enable/disable Z read. void depthMask(bool state) override; + /// + /// Enable/disable Z read and overrides any setting that will come from detour. + /// + /// Z read state. + /// Set to true to override any future changes. + void depthMask(bool state, bool isOverride); + /// Begin drawing primitives. /// @param prim [in] primitive type to draw, one of rcDebugDrawPrimitives. /// @param size [in] size of a primitive, applies to point size and line width only. @@ -127,6 +134,7 @@ private: U32 mPrimType; U32 mVertCount; + bool mOverrideState; void _vertex(const float x, const float y, const float z, unsigned int color); }; diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 4a7e3dd06..d3c974141 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -1566,7 +1566,7 @@ void NavMesh::renderToDrawer() if (no) { NavMesh* n = static_cast(no); - + mDbgDraw.depthMask(true, true); if (n->nm && (m_drawMode == DRAWMODE_NAVMESH || m_drawMode == DRAWMODE_NAVMESH_TRANS || @@ -1583,6 +1583,8 @@ void NavMesh::renderToDrawer() duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm); } + mDbgDraw.depthMask(true, false); + for (Tile& tile : n->mTiles) { if (tile.chf && m_drawMode == DRAWMODE_COMPACT) @@ -1612,35 +1614,47 @@ void NavMesh::renderToDrawer() if (tile.cset && m_drawMode == DRAWMODE_RAW_CONTOURS) { + mDbgDraw.depthMask(false); duDebugDrawRawContours(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); } if (tile.cset && m_drawMode == DRAWMODE_BOTH_CONTOURS) { + mDbgDraw.depthMask(false); duDebugDrawRawContours(&mDbgDraw, *tile.cset); duDebugDrawContours(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); } if (tile.cset && m_drawMode == DRAWMODE_CONTOURS) { + mDbgDraw.depthMask(false); duDebugDrawContours(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); } if (tile.chf && tile.cset && m_drawMode == DRAWMODE_REGION_CONNECTIONS) { + duDebugDrawCompactHeightfieldRegions(&mDbgDraw, *tile.chf); - + mDbgDraw.depthMask(false); duDebugDrawRegionConnections(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); } if (tile.pmesh && m_drawMode == DRAWMODE_POLYMESH) { + mDbgDraw.depthMask(false); duDebugDrawPolyMesh(&mDbgDraw, *tile.pmesh); + mDbgDraw.depthMask(true); } if (tile.dmesh && m_drawMode == DRAWMODE_POLYMESH_DETAIL) { + mDbgDraw.depthMask(false); duDebugDrawPolyMeshDetail(&mDbgDraw, *tile.dmesh); + mDbgDraw.depthMask(true); } } From edf4d47be07f8518666af0b45e8f8c350684f7b9 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 25 Jul 2025 12:41:59 +0100 Subject: [PATCH 14/38] handle water verts now we correctly mark water verts as water area and add the swim flag to them --- Engine/source/navigation/navMesh.cpp | 46 +++++++++++++++++++++++++--- Engine/source/navigation/navMesh.h | 2 ++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index d3c974141..4cc863361 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -222,6 +222,9 @@ NavMesh::NavMesh() mBuilding = false; mCurLinkID = 0; + + mWaterVertStart = 0; + mWaterTriStart = 0; } NavMesh::~NavMesh() @@ -488,6 +491,8 @@ bool NavMesh::getLinkDir(U32 idx) { return mLinkDirs[idx]; } + + return false; } F32 NavMesh::getLinkRadius(U32 idx) @@ -496,6 +501,8 @@ F32 NavMesh::getLinkRadius(U32 idx) { return mLinkRads[idx]; } + + return -1.0f; } void NavMesh::setLinkDir(U32 idx, bool biDir) @@ -680,8 +687,8 @@ bool NavMesh::build(bool background, bool saveIntermediates) getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); // Parse water objects into the same list, but remember how much geometry was /not/ water. - U32 nonWaterVertCount = m_geo->getVertCount(); - U32 nonWaterTriCount = m_geo->getTriCount(); + mWaterVertStart = m_geo->getVertCount(); + mWaterTriStart = m_geo->getTriCount(); if (mWaterMethod != Ignore) { getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info); @@ -912,8 +919,8 @@ void NavMesh::buildNextTile() getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); // Parse water objects into the same list, but remember how much geometry was /not/ water. - U32 nonWaterVertCount = m_geo->getVertCount(); - U32 nonWaterTriCount = m_geo->getTriCount(); + mWaterVertStart = m_geo->getVertCount(); + mWaterTriStart = m_geo->getTriCount(); if (mWaterMethod != Ignore) { getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info); @@ -1084,6 +1091,37 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, U32 &dataSize) rcMarkWalkableTriangles(ctx, m_cfg.walkableSlopeAngle, m_geo->getVerts(), m_geo->getVertCount(), ctris, nctris, m_triareas); + // Post-process triangle areas if we need to capture water. + if (mWaterMethod != Ignore) + { + for (int t = 0; t < nctris; ++t) + { + const int* tri = &ctris[t * 3]; + + bool isWater = false; + for (int j = 0; j < 3; ++j) + { + if (tri[j] >= mWaterVertStart) + { + isWater = true; + break; + } + } + + if (isWater) + { + if (mWaterMethod == Solid) + { + m_triareas[t] = WaterArea; // Custom enum you define, like 64 + } + else if (mWaterMethod == Impassable) + { + m_triareas[t] = RC_NULL_AREA; + } + } + } + } + if (!rcRasterizeTriangles(ctx, m_geo->getVerts(), m_geo->getVertCount(), ctris, m_triareas, nctris, *m_solid, m_cfg.walkableClimb)) return NULL; } diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 641907a1e..947a64774 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -443,6 +443,8 @@ protected: rcPolyMeshDetail* m_dmesh; rcConfig m_cfg; DrawMode m_drawMode; + U32 mWaterVertStart; + U32 mWaterTriStart; void cleanup(); }; From 6d36e17d915fb29b95b3b0dbc2d44471dbee3e81 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sat, 26 Jul 2025 10:34:19 +0100 Subject: [PATCH 15/38] added navmesh tester tool Added ground work for tester tool tester tool works but needs to fill out list of acceptable datablocks and spawnclasses navpaths now share 1 navmeshquery AIControllerData now has a vector of area costs for different polyareas General cleanup --- Engine/source/T3D/AI/AIController.cpp | 25 ++ Engine/source/T3D/AI/AIController.h | 2 + Engine/source/T3D/AI/AINavigation.cpp | 1 + Engine/source/T3D/aiPlayer.cpp | 19 +- Engine/source/navigation/guiNavEditorCtrl.cpp | 203 ------------ Engine/source/navigation/guiNavEditorCtrl.h | 17 - Engine/source/navigation/navMesh.cpp | 77 ++--- Engine/source/navigation/navMesh.h | 11 +- .../navMeshTools/navMeshTestTool.cpp | 302 ++++++++++++++++++ .../navigation/navMeshTools/navMeshTestTool.h | 47 +++ Engine/source/navigation/navPath.cpp | 13 +- Engine/source/navigation/navPath.h | 3 +- Engine/source/navigation/torqueRecast.h | 7 +- .../game/tools/navEditor/NavEditorGui.gui | 22 +- .../game/tools/navEditor/NavEditorToolbar.gui | 63 ---- .../game/tools/navEditor/main.tscript | 17 +- .../game/tools/navEditor/navEditor.tscript | 196 +++++++++--- 17 files changed, 604 insertions(+), 421 deletions(-) create mode 100644 Engine/source/navigation/navMeshTools/navMeshTestTool.cpp create mode 100644 Engine/source/navigation/navMeshTools/navMeshTestTool.h diff --git a/Engine/source/T3D/AI/AIController.cpp b/Engine/source/T3D/AI/AIController.cpp index 8546826c7..73f179d7d 100644 --- a/Engine/source/T3D/AI/AIController.cpp +++ b/Engine/source/T3D/AI/AIController.cpp @@ -535,6 +535,10 @@ AIControllerData::AIControllerData() #ifdef TORQUE_NAVIGATION_ENABLED mLinkTypes = LinkData(AllFlags); + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + mFilter.setExcludeFlags(0); + mAreaCosts.setSize(PolyAreas::NumAreas); + mAreaCosts.fill(1.0f); mNavSize = AINavigation::Regular; mFlocking.mChance = 90; mFlocking.mMin = 1.0f; @@ -560,6 +564,8 @@ AIControllerData::AIControllerData(const AIControllerData& other, bool temp_clon #ifdef TORQUE_NAVIGATION_ENABLED mLinkTypes = other.mLinkTypes; + mFilter = other.mFilter; + mAreaCosts = other.mAreaCosts; mNavSize = other.mNavSize; mFlocking.mChance = other.mFlocking.mChance; mFlocking.mMin = other.mFlocking.mMin; @@ -629,6 +635,8 @@ void AIControllerData::initPersistFields() addFieldV("FlockSideStep", TypeRangedF32, Offset(mFlocking.mSideStep, AIControllerData), &CommonValidators::PositiveFloat, "@brief Distance from destination before we stop moving out of the way."); + addField("areaCosts", TypeF32Vector, Offset(mAreaCosts, AIControllerData), + "Vector of costs for each PolyArea."); addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIControllerData), "Allow the character to walk on dry land."); addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIControllerData), @@ -662,6 +670,10 @@ void AIControllerData::packData(BitStream* stream) #ifdef TORQUE_NAVIGATION_ENABLED //enums + stream->write(mAreaCosts.size()); + for (U32 i = 0; i < mAreaCosts.size(); i++) { + stream->write(mAreaCosts[i]); + } stream->write(mLinkTypes.getFlags()); stream->write((U32)mNavSize); // end enums @@ -684,10 +696,23 @@ void AIControllerData::unpackData(BitStream* stream) stream->read(&mFollowTolerance); #ifdef TORQUE_NAVIGATION_ENABLED + U32 num; + stream->read(&num); + mAreaCosts.setSize(num); + for (U32 i = 0; i < num; i++) + { + stream->read(&mAreaCosts[i]); + } //enums U16 linkFlags; stream->read(&linkFlags); mLinkTypes = LinkData(linkFlags); + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + mFilter.setExcludeFlags(mLinkTypes.getExcludeFlags()); + for (U32 i = 0; i < PolyAreas::NumAreas; i++) + { + mFilter.setAreaCost((PolyAreas)i, mAreaCosts[i]); + } U32 navSize; stream->read(&navSize); mNavSize = (AINavigation::NavSize)(navSize); diff --git a/Engine/source/T3D/AI/AIController.h b/Engine/source/T3D/AI/AIController.h index 18d95e210..340ee5d04 100644 --- a/Engine/source/T3D/AI/AIController.h +++ b/Engine/source/T3D/AI/AIController.h @@ -168,6 +168,8 @@ public: /// Types of link we can use. LinkData mLinkTypes; + dtQueryFilter mFilter; + Vector mAreaCosts; AINavigation::NavSize mNavSize; #endif Delegate resolveYawPtr; diff --git a/Engine/source/T3D/AI/AINavigation.cpp b/Engine/source/T3D/AI/AINavigation.cpp index 258939f06..ffb29fdd9 100644 --- a/Engine/source/T3D/AI/AINavigation.cpp +++ b/Engine/source/T3D/AI/AINavigation.cpp @@ -77,6 +77,7 @@ bool AINavigation::setPathDestination(const Point3F& pos, bool replace) path->mAlwaysRender = true; path->mLinkTypes = getCtrl()->mControllerData->mLinkTypes; path->mXray = true; + path->mFilter = getCtrl()->mControllerData->mFilter; // Paths plan automatically upon being registered. if (!path->registerObject()) { diff --git a/Engine/source/T3D/aiPlayer.cpp b/Engine/source/T3D/aiPlayer.cpp index 68032893a..a3e54d9ab 100644 --- a/Engine/source/T3D/aiPlayer.cpp +++ b/Engine/source/T3D/aiPlayer.cpp @@ -114,6 +114,10 @@ AIPlayer::AIPlayer() mJump = None; mNavSize = Regular; mLinkTypes = LinkData(AllFlags); + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + mFilter.setExcludeFlags(0); + mAreaCosts.setSize(PolyAreas::NumAreas); + mAreaCosts.fill(1.0f); #endif mIsAiControlled = true; @@ -163,7 +167,8 @@ void AIPlayer::initPersistFields() #ifdef TORQUE_NAVIGATION_ENABLED addGroup("Pathfinding"); - + addField("areaCosts", TypeF32Vector, Offset(mAreaCosts, AIPlayer), + "Vector of costs for each PolyArea."); addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIPlayer), "Allow the character to walk on dry land."); addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIPlayer), @@ -785,6 +790,7 @@ void AIPlayer::moveToNode(S32 node) bool AIPlayer::setPathDestination(const Point3F &pos) { +#ifdef TORQUE_NAVIGATION_ENABLED // Pathfinding only happens on the server. if(!isServerObject()) return false; @@ -799,6 +805,13 @@ bool AIPlayer::setPathDestination(const Point3F &pos) return false; } + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + mFilter.setExcludeFlags(mLinkTypes.getExcludeFlags()); + for (U32 i = 0; i < PolyAreas::NumAreas; i++) + { + mFilter.setAreaCost((PolyAreas)i, mAreaCosts[i]); + } + // Create a new path. NavPath *path = new NavPath(); @@ -808,6 +821,7 @@ bool AIPlayer::setPathDestination(const Point3F &pos) path->mFromSet = path->mToSet = true; path->mAlwaysRender = true; path->mLinkTypes = mLinkTypes; + path->mFilter = mFilter; path->mXray = true; // Paths plan automatically upon being registered. if(!path->registerObject()) @@ -839,6 +853,9 @@ bool AIPlayer::setPathDestination(const Point3F &pos) path->deleteObject(); return false; } +#else + setMoveDestination(pos, false); +#endif } DefineEngineMethod(AIPlayer, setPathDestination, bool, (Point3F goal),, diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp index 3e71ba240..72cd5ff71 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.cpp +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -59,8 +59,6 @@ GuiNavEditorCtrl::GuiNavEditorCtrl() mIsDirty = false; mStartDragMousePoint = InvalidMousePoint; mMesh = NULL; - mPlayer = mCurPlayer = NULL; - mSpawnClass = mSpawnDatablock = ""; } GuiNavEditorCtrl::~GuiNavEditorCtrl() @@ -97,11 +95,6 @@ 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(); } @@ -140,79 +133,6 @@ DefineEngineMethod(GuiNavEditorCtrl, getMesh, S32, (),, 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; -} - -DefineEngineMethod(GuiNavEditorCtrl, deselect, void, (),, - "@brief Deselect whatever is currently selected in the editor.") -{ - object->deselect(); -} - -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 = obj; -#ifdef TORQUE_NAVIGATION_ENABLED - AIPlayer* asAIPlayer = dynamic_cast(obj); - if (asAIPlayer) //try direct - { - Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags())); - } - else - { - ShapeBase* sbo = dynamic_cast(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_) @@ -277,89 +197,6 @@ void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & 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 == 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(mPlayer.getPointer()); - if (asAIPlayer) //try direct - { - Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags())); - } - else - { - ShapeBase* sbo = dynamic_cast(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(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(mPlayer.getPointer()); - if (sbo->getAIController()) - { - if (sbo->getAIController()->mControllerData) - sbo->getAIController()->getNav()->setPathDestination(ri.point, true); - } - } - } - } - } } void GuiNavEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) @@ -385,22 +222,6 @@ void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) 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 == mTestMode) - { - if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri)) - mCurPlayer = ri.object; - else - mCurPlayer = NULL; - } } void GuiNavEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event) @@ -443,22 +264,6 @@ void GuiNavEditorCtrl::onRender(Point2I offset, const RectI &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); @@ -479,14 +284,6 @@ void GuiNavEditorCtrl::renderScene(const RectI & updateRect) if (mTool) mTool->onRender3D(); - 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); diff --git a/Engine/source/navigation/guiNavEditorCtrl.h b/Engine/source/navigation/guiNavEditorCtrl.h index db60c4c85..5d1642143 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.h +++ b/Engine/source/navigation/guiNavEditorCtrl.h @@ -103,17 +103,8 @@ public: String getMode() { return mMode; } void selectMesh(NavMesh *mesh); - void deselect(); S32 getMeshId(); - S32 getPlayerId(); - - String mSpawnClass; - String mSpawnDatablock; - - void deleteLink(); - void setLinkFlags(const LinkData &d); - void spawnPlayer(const Point3F &pos); /// @} void setActiveTool(NavMeshTool* tool); @@ -148,14 +139,6 @@ protected: /// @} - /// @name Test mode - /// @{ - - SimObjectPtr mPlayer; - SimObjectPtr mCurPlayer; - - /// @} - Gui3DMouseEvent mLastMouseEvent; #define InvalidMousePoint Point2I(-100,-100) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 4cc863361..cea2922a0 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -225,10 +225,13 @@ NavMesh::NavMesh() mWaterVertStart = 0; mWaterTriStart = 0; + + mQuery = NULL; } NavMesh::~NavMesh() { + dtFreeNavMeshQuery(mQuery); dtFreeNavMesh(nm); nm = NULL; delete ctx; @@ -677,32 +680,6 @@ bool NavMesh::build(bool background, bool saveIntermediates) return false; } - Box3F worldBox = getWorldBox(); - SceneContainer::CallbackInfo info; - info.context = PLC_Navigation; - info.boundingBox = worldBox; - m_geo = new RecastPolyList; - info.polyList = m_geo; - info.key = this; - getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); - - // Parse water objects into the same list, but remember how much geometry was /not/ water. - mWaterVertStart = m_geo->getVertCount(); - mWaterTriStart = m_geo->getTriCount(); - if (mWaterMethod != Ignore) - { - getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info); - } - - // Check for no geometry. - if (!m_geo->getVertCount()) - { - m_geo->clear(); - return false; - } - - m_geo->getChunkyMesh(); - // Needed for the recast config and generation params. Box3F rc_box = DTStoRC(getWorldBox()); S32 gw = 0, gh = 0; @@ -803,33 +780,6 @@ void NavMesh::createNewFile() mFileName = StringTable->insert(fileName.c_str()); } -void NavMesh::updateConfig() -{ - //// Build rcConfig object from our console members. - //dMemset(&cfg, 0, sizeof(cfg)); - //cfg.cs = mCellSize; - //cfg.ch = mCellHeight; - //Box3F box = DTStoRC(getWorldBox()); - //rcVcopy(cfg.bmin, box.minExtents); - //rcVcopy(cfg.bmax, box.maxExtents); - //rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height); - - //cfg.walkableHeight = mCeil(mWalkableHeight / mCellHeight); - //cfg.walkableClimb = mCeil(mWalkableClimb / mCellHeight); - //cfg.walkableRadius = mCeil(mWalkableRadius / mCellSize); - //cfg.walkableSlopeAngle = mWalkableSlope; - //cfg.borderSize = cfg.walkableRadius + 3; - - //cfg.detailSampleDist = mDetailSampleDist; - //cfg.detailSampleMaxError = mDetailSampleMaxError; - //cfg.maxEdgeLen = mMaxEdgeLen; - //cfg.maxSimplificationError = mMaxSimplificationError; - //cfg.maxVertsPerPoly = mMaxVertsPerPoly; - //cfg.minRegionArea = mMinRegionArea; - //cfg.mergeRegionArea = mMergeRegionArea; - //cfg.tileSize = mTileSize / cfg.cs; -} - S32 NavMesh::getTile(const Point3F& pos) { if(mBuilding) @@ -938,6 +888,8 @@ void NavMesh::buildNextTile() if(!mDirtyTiles.empty()) { + dtFreeNavMeshQuery(mQuery); + mQuery = NULL; // Pop a single dirty tile and process it. U32 i = mDirtyTiles.front(); mDirtyTiles.pop_front(); @@ -1002,6 +954,12 @@ void NavMesh::buildNextTile() // Did we just build the last tile? if(mDirtyTiles.empty()) { + mQuery = dtAllocNavMeshQuery(); + if (dtStatusFailed(mQuery->init(nm, 2048))) + { + Con::errorf("NavMesh - Failed to create navmesh query"); + } + ctx->stopTimer(RC_TIMER_TOTAL); if(getEventManager()) { @@ -1012,6 +970,15 @@ void NavMesh::buildNextTile() mBuilding = false; } } + + if (mQuery == NULL) + { + mQuery = dtAllocNavMeshQuery(); + if (dtStatusFailed(mQuery->init(nm, 2048))) + { + Con::errorf("NavMesh - Failed to create navmesh query"); + } + } } unsigned char *NavMesh::buildTileData(const Tile &tile, U32 &dataSize) @@ -1614,11 +1581,13 @@ void NavMesh::renderToDrawer() m_drawMode == DRAWMODE_NAVMESH_INVIS)) { if (m_drawMode != DRAWMODE_NAVMESH_INVIS) - duDebugDrawNavMesh(&mDbgDraw, *n->nm, 0); + duDebugDrawNavMeshWithClosedList(&mDbgDraw, *n->nm, *n->mQuery, 0); if(m_drawMode == DRAWMODE_NAVMESH_BVTREE) duDebugDrawNavMeshBVTree(&mDbgDraw, *n->nm); if(m_drawMode == DRAWMODE_NAVMESH_PORTALS) duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm); + if (m_drawMode == DRAWMODE_NAVMESH_NODES) + duDebugDrawNavMeshNodes(&mDbgDraw, *n->mQuery); } mDbgDraw.depthMask(true, false); diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 947a64774..61db2a32f 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -187,6 +187,8 @@ public: /// Delete the selected link. void deleteLink(U32 idx); + dtNavMeshQuery* getNavMeshQuery() { return mQuery; } + /// @} /// Should small characters use this mesh? @@ -381,16 +383,9 @@ private: /// @} - /// @name Intermediate data - /// @{ - - /// Updates our config from console members. - void updateConfig(); - dtNavMesh *nm; rcContext *ctx; - - /// @} + dtNavMeshQuery* mQuery; /// @name Cover /// @{ diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp new file mode 100644 index 000000000..751cb537b --- /dev/null +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp @@ -0,0 +1,302 @@ +#include "navMeshTestTool.h" +#include "navigation/guiNavEditorCtrl.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" +#include "scene/sceneManager.h" +#include "math/mathUtils.h" +#include "T3D/gameBase/gameConnection.h" +#include "T3D/AI/AIController.h" + +IMPLEMENT_CONOBJECT(NavMeshTestTool); + +//// take control +// GameConnection* conn = GameConnection::getConnectionToServer(); +// if (conn) +// conn->setControlObject(static_cast(obj)); +//} + +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 NavMeshTestTool::spawnPlayer(const Point3F& position) +{ + if (mSpawnClass.isEmpty() || mSpawnDatablock.isEmpty()) + { + Con::warnf("NavMeshTestTool::spawnPlayer - spawn class/datablock not set!"); + return; + } + + SimObject* spawned = Sim::spawnObject(mSpawnClass, mSpawnDatablock); + SceneObject* obj = dynamic_cast(spawned); + + if (!obj) + { + Con::warnf("NavMeshTestTool::spawnPlayer - spawn failed or not a SceneObject"); + if (spawned) + spawned->deleteObject(); + return; + } + + obj->setPosition(position); + obj->registerObject(); + SimObject* cleanup = Sim::findObject("MissionCleanup"); + if (cleanup) + { + SimGroup* missionCleanup = dynamic_cast(cleanup); + missionCleanup->addObject(obj); + } + mPlayer = obj; + + Con::executef(this, "onPlayerSpawned", obj->getIdString()); +} + +void NavMeshTestTool::drawAgent(duDebugDrawTorque& dd, const F32* pos, F32 r, F32 h, F32 c, const U32 col) +{ + dd.depthMask(false); + + // Agent dimensions. + duDebugDrawCylinderWire(&dd, pos[0] - r, pos[1] + 0.02f, pos[2] - r, pos[0] + r, pos[1] + h, pos[2] + r, col, 2.0f); + + duDebugDrawCircle(&dd, pos[0], pos[1] + c, pos[2], r, duRGBA(0, 0, 0, 64), 1.0f); + + U32 colb = duRGBA(0, 0, 0, 196); + dd.begin(DU_DRAW_LINES); + dd.vertex(pos[0], pos[1] - c, pos[2], colb); + dd.vertex(pos[0], pos[1] + c, pos[2], colb); + dd.vertex(pos[0] - r / 2, pos[1] + 0.02f, pos[2], colb); + dd.vertex(pos[0] + r / 2, pos[1] + 0.02f, pos[2], colb); + dd.vertex(pos[0], pos[1] + 0.02f, pos[2] - r / 2, colb); + dd.vertex(pos[0], pos[1] + 0.02f, pos[2] + r / 2, colb); + dd.end(); + + dd.depthMask(true); +} + +NavMeshTestTool::NavMeshTestTool() +{ + mSpawnClass = String::EmptyString; + mSpawnDatablock = String::EmptyString; + mPlayer = NULL; + mCurPlayer = NULL; + + mPathStart = Point3F::Max; + mPathEnd = Point3F::Max; +} + +void NavMeshTestTool::onActivated(const Gui3DMouseEvent& evt) +{ + Con::executef(this, "onActivated"); +} + +void NavMeshTestTool::onDeactivated() +{ + Con::executef(this, "onDeactivated"); +} + +void NavMeshTestTool::on3DMouseDown(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + bool shift = evt.modifier & SI_LSHIFT; + bool ctrl = evt.modifier & SI_LCTRL; + + if (ctrl) + { + if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + { + spawnPlayer(ri.point); + } + return; + } + + if (shift) + { + mPlayer = NULL; + Con::executef(this, "onPlayerDeselected"); + return; + } + + if (gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri)) + { + if (!ri.object) + return; + + mPlayer = ri.object; + +#ifdef TORQUE_NAVIGATION_ENABLED + AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); + if (asAIPlayer) + { + Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags())); + } + else + { + ShapeBase* sbo = dynamic_cast(mPlayer.getPointer()); + if (sbo && sbo->getAIController() && sbo->getAIController()->mControllerData) + { + Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags())); + } + else + { + Con::executef(this, "onPlayerSelected"); + } + } +#else + Con::executef(this, "onPlayerSelected"); +#endif + } + else if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + { + if (mPlayer.isNull()) + { + if (mPathStart != Point3F::Max && mPathEnd != Point3F::Max) // start a new path. + { + mPathStart = Point3F::Max; + mPathEnd = Point3F::Max; + } + + if (mPathStart != Point3F::Max) + { + mPathEnd = ri.point; + } + else + { + mPathStart = ri.point; + } + } + else + { + AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); + if (asAIPlayer) //try direct + { + asAIPlayer->setPathDestination(ri.point); + mPathStart = mPlayer->getPosition(); + mPathEnd = ri.point; + } + else + { + ShapeBase* sbo = dynamic_cast(mPlayer.getPointer()); + if (sbo->getAIController()) + { + if (sbo->getAIController()->mControllerData) + { + sbo->getAIController()->getNav()->setPathDestination(ri.point, true); + mPathStart = mPlayer->getPosition(); + mPathEnd = ri.point; + } + } + } + } + } + +} + +void NavMeshTestTool::on3DMouseMove(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + + if (gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri)) + mCurPlayer = ri.object; + else + mCurPlayer = NULL; + +} + +void NavMeshTestTool::onRender3D() +{ + if (mNavMesh.isNull()) + return; + + static const U32 startCol = duRGBA(128, 25, 0, 192); + static const U32 endCol = duRGBA(51, 102, 0, 129); + + const F32 agentRadius = mNavMesh->mWalkableRadius; + const F32 agentHeight = mNavMesh->mWalkableHeight; + const F32 agentClimb = mNavMesh->mWalkableClimb; + + duDebugDrawTorque dd; + dd.depthMask(false); + if (mPathStart != Point3F::Max) + { + drawAgent(dd, DTStoRC(mPathStart), agentRadius, agentHeight, agentClimb, startCol); + } + + if (mPathEnd != Point3F::Max) + { + drawAgent(dd, DTStoRC(mPathEnd), agentRadius, agentHeight, agentClimb, endCol); + } + dd.depthMask(true); + + dd.immediateRender(); + + if (!mCurPlayer.isNull()) + renderBoxOutline(mCurPlayer->getWorldBox(), ColorI::BLUE); + if (!mPlayer.isNull()) + renderBoxOutline(mPlayer->getWorldBox(), ColorI::GREEN); +} + +bool NavMeshTestTool::updateGuiInfo() +{ + SimObject* statusbar; + Sim::findObject("EditorGuiStatusBar", statusbar); + + GuiTextCtrl* selectionBar; + Sim::findObject("EWorldEditorStatusBarSelection", selectionBar); + + String text; + + if (statusbar) + Con::executef(statusbar, "setInfo", text.c_str()); + + text = ""; + + if (selectionBar) + selectionBar->setText(text); + + return true; +} + +S32 NavMeshTestTool::getPlayerId() +{ + return mPlayer.isNull() ? 0 : mPlayer->getId(); +} + +DefineEngineMethod(NavMeshTestTool, getPlayer, S32, (), , + "@brief Return the current player id.") +{ + return object->getPlayerId(); +} + +DefineEngineMethod(NavMeshTestTool, setSpawnClass, void, (String className), , "") +{ + object->setSpawnClass(className); +} + +DefineEngineMethod(NavMeshTestTool, setSpawnDatablock, void, (String dbName), , "") +{ + object->setSpawnDatablock(dbName); +} diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.h b/Engine/source/navigation/navMeshTools/navMeshTestTool.h new file mode 100644 index 000000000..a229c2255 --- /dev/null +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.h @@ -0,0 +1,47 @@ +#ifndef _NAVMESHTESTTOOL_H_ +#define _NAVMESHTESTTOOL_H_ + + +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + +class NavMeshTestTool : public NavMeshTool +{ + typedef NavMeshTool Parent; +protected: + String mSpawnClass; + String mSpawnDatablock; + SimObjectPtr mPlayer; + SimObjectPtr mCurPlayer; + Point3F mPathStart; + Point3F mPathEnd; +public: + DECLARE_CONOBJECT(NavMeshTestTool); + + void spawnPlayer(const Point3F& position); + + void drawAgent(duDebugDrawTorque& dd, const F32* pos, F32 r, F32 h, F32 c, const U32 col); + + NavMeshTestTool(); + + virtual ~NavMeshTestTool() {} + + void onActivated(const Gui3DMouseEvent& evt) override; + void onDeactivated() override; + + void on3DMouseDown(const Gui3DMouseEvent& evt) override; + void on3DMouseMove(const Gui3DMouseEvent& evt) override; + void onRender3D() override; + + bool updateGuiInfo() override; + + S32 getPlayerId(); + + void setSpawnClass(String className) { mSpawnClass = className; } + void setSpawnDatablock(String dbName) { mSpawnDatablock = dbName; } + +}; + + +#endif // !_NAVMESHTESTTOOL_H_ diff --git a/Engine/source/navigation/navPath.cpp b/Engine/source/navigation/navPath.cpp index 857af9730..db9ba20ee 100644 --- a/Engine/source/navigation/navPath.cpp +++ b/Engine/source/navigation/navPath.cpp @@ -69,14 +69,11 @@ NavPath::NavPath() : mXray = false; mRenderSearch = false; - mQuery = NULL; mStatus = DT_FAILURE; } NavPath::~NavPath() { - dtFreeNavMeshQuery(mQuery); - mQuery = NULL; } void NavPath::checkAutoUpdate() @@ -264,9 +261,6 @@ bool NavPath::onAdd() if(isServerObject()) { - mQuery = dtAllocNavMeshQuery(); - if(!mQuery) - return false; checkAutoUpdate(); if(!plan()) setProcessTick(true); @@ -293,7 +287,8 @@ bool NavPath::init() return false; // Initialise our query. - if(dtStatusFailed(mQuery->init(mMesh->getNavMesh(), MaxPathLen))) + mQuery = mMesh->getNavMeshQuery(); + if(!mQuery) return false; mPoints.clear(); @@ -372,9 +367,6 @@ void NavPath::resize() bool NavPath::plan() { PROFILE_SCOPE(NavPath_plan); - // Initialise filter. - mFilter.setIncludeFlags(mLinkTypes.getFlags()); - // Initialise query and visit locations. if(!init()) return false; @@ -641,6 +633,7 @@ void NavPath::renderSimple(ObjectRenderInst *ri, SceneRenderState *state, BaseMa { duDebugDrawTorque dd; duDebugDrawNavMeshNodes(&dd, *np->mQuery); + dd.immediateRender(); } } } diff --git a/Engine/source/navigation/navPath.h b/Engine/source/navigation/navPath.h index fe6246c40..194058b8a 100644 --- a/Engine/source/navigation/navPath.h +++ b/Engine/source/navigation/navPath.h @@ -57,7 +57,7 @@ public: /// What sort of link types are we allowed to move on? LinkData mLinkTypes; - + dtQueryFilter mFilter; /// Plan the path. bool plan(); @@ -152,7 +152,6 @@ private: dtNavMeshQuery *mQuery; dtStatus mStatus; - dtQueryFilter mFilter; S32 mCurIndex; Vector mPoints; Vector mFlags; diff --git a/Engine/source/navigation/torqueRecast.h b/Engine/source/navigation/torqueRecast.h index 36187f255..914de002c 100644 --- a/Engine/source/navigation/torqueRecast.h +++ b/Engine/source/navigation/torqueRecast.h @@ -52,7 +52,8 @@ inline void rcCol(unsigned int col, U8 &r, U8 &g, U8 &b, U8 &a) } enum PolyAreas { - GroundArea = 1, + NullArea = 0, + GroundArea, WaterArea, OffMeshArea, NumAreas @@ -99,6 +100,10 @@ struct LinkData { (climb ? ClimbFlag : 0) | (teleport ? TeleportFlag : 0); } + U16 getExcludeFlags() const + { + return AllFlags & ~getFlags(); + } }; #endif diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui index 3445d3752..ac7b55db0 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui @@ -857,7 +857,27 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { visible = "1"; active = "0"; tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character walk on ground?"; + toolTip = "Can this character walk on ground?"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiCheckBoxCtrl() { + internalName = "LinkSwimFlag"; + class = "NavMeshTestFlagButton"; + text = " Swim"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + extent = "159 15"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiCheckBoxProfile"; + visible = "1"; + active = "0"; + tooltipProfile = "GuiToolTipProfile"; + toolTip = "Can this character swim?"; hovertime = "1000"; isContainer = "0"; canSave = "1"; diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorToolbar.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorToolbar.gui index 38c86f678..8068d3526 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorToolbar.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorToolbar.gui @@ -77,68 +77,5 @@ $guiContent = new GuiControl(NavEditorToolbar,EditorGuiGroup) { canSave = "1"; canSaveDynamicFields = "0"; }; - new GuiCheckBoxCtrl() { - text = "Mesh"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - position = "167 1"; - extent = "50 30"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "1"; - variable = "$Nav::Editor::renderMesh"; - tooltipProfile = "GuiToolTipProfile"; - hovertime = "1000"; - isContainer = "0"; - internalName = "MeshButton"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - text = "Portals"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - position = "224 1"; - extent = "54 30"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "1"; - variable = "$Nav::Editor::renderPortals"; - tooltipProfile = "GuiToolTipProfile"; - hovertime = "1000"; - isContainer = "0"; - internalName = "PortalButton"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - text = "BV tree"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - position = "286 1"; - extent = "140 30"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "1"; - variable = "$Nav::Editor::renderBVTree"; - tooltipProfile = "GuiToolTipProfile"; - hovertime = "1000"; - isContainer = "0"; - internalName = "BVTreeButton"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; }; //--- OBJECT WRITE END --- diff --git a/Templates/BaseGame/game/tools/navEditor/main.tscript b/Templates/BaseGame/game/tools/navEditor/main.tscript index a40d2f772..d4c47a1ec 100644 --- a/Templates/BaseGame/game/tools/navEditor/main.tscript +++ b/Templates/BaseGame/game/tools/navEditor/main.tscript @@ -66,6 +66,13 @@ function initializeNavEditor() buttonImage = "ToolsModule:nav_link_n_image"; }; + new NavMeshTestTool() + { + internalName = "TestTool"; + toolTip = "PathFinding Test tool"; + buttonImage = "ToolsModule:3rd_person_camera_n_image"; + }; + new TileTool() { internalName = "TileTool"; @@ -140,7 +147,7 @@ function EditorGui::SetNavPalletBar() EWToolsPaletteWindow.addButton("LinkMode", "ToolsModule:nav_link_n_image", "NavEditorGui.setActiveTool(NavMeshTools->LinkTool);", "", "Create off-mesh links", "2"); // EWToolsPaletteWindow.addButton("CoverMode", "ToolsModule:nav_cover_n_image", "NavEditorGui.setMode(\"CoverMode\");", "","Edit cover", "3"); // EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setMode(\"TileMode\");", "", "View tiles", "4"); - // EWToolsPaletteWindow.addButton("TestMode", "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setMode(\"TestMode\");", "", "Test pathfinding", "5"); + EWToolsPaletteWindow.addButton("TestMode", "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TestTool);", "", "Test pathfinding", "5"); EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TileTool);" , "", "View and Edit Tiles", "4"); EWToolsPaletteWindow.refresh(); } @@ -263,7 +270,7 @@ function NavEditorPlugin::initSettings(%this) EditorSettings.beginGroup("NavEditor", true); EditorSettings.setDefaultValue("SpawnClass", "AIPlayer"); - EditorSettings.setDefaultValue("SpawnDatablock", "DefaultPlayerData"); + EditorSettings.setDefaultValue("SpawnDatablock", "ProtoPlayer"); EditorSettings.endGroup(); } @@ -273,9 +280,6 @@ function NavEditorPlugin::readSettings(%this) EditorSettings.beginGroup("NavEditor", true); // Currently these are globals because of the way they are accessed in navMesh.cpp. - $Nav::Editor::renderMesh = EditorSettings.value("RenderMesh"); - $Nav::Editor::renderPortals = EditorSettings.value("RenderPortals"); - $Nav::Editor::renderBVTree = EditorSettings.value("RenderBVTree"); NavEditorGui.spawnClass = EditorSettings.value("SpawnClass"); NavEditorGui.spawnDatablock = EditorSettings.value("SpawnDatablock"); NavEditorGui.backgroundBuild = EditorSettings.value("BackgroundBuild"); @@ -295,9 +299,6 @@ function NavEditorPlugin::writeSettings(%this) { EditorSettings.beginGroup("NavEditor", true); - EditorSettings.setValue("RenderMesh", $Nav::Editor::renderMesh); - EditorSettings.setValue("RenderPortals", $Nav::Editor::renderPortals); - EditorSettings.setValue("RenderBVTree", $Nav::Editor::renderBVTree); EditorSettings.setValue("SpawnClass", NavEditorGui.spawnClass); EditorSettings.setValue("SpawnDatablock", NavEditorGui.spawnDatablock); EditorSettings.setValue("BackgroundBuild", NavEditorGui.backgroundBuild); diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 2cfd23104..31d5e5eec 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -411,6 +411,142 @@ function NavMeshLinkBiDirection::onClick(%this) //------------------------------------------------------ +function NavMeshTestTool::onActivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->SelectActions.setVisible(false); + %actions->LinkActions.setVisible(false); + %actions->CoverActions.setVisible(false); + %actions->TileActions.setVisible(false); + %actions->TestActions.setVisible(false); + + %properties = NavEditorOptionsWindow->PropertiesBox; + %properties->LinkProperties.setVisible(false); + %properties->TileProperties.setVisible(false); + %properties->TestProperties.setVisible(false); + + %actions->TestActions.setVisible(true); + %properties->TestProperties.setVisible(true); +} + +function NavMeshTestTool::onDeactivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->SelectActions.setVisible(false); + %actions->LinkActions.setVisible(false); + %actions->CoverActions.setVisible(false); + %actions->TileActions.setVisible(false); + %actions->TestActions.setVisible(false); + + %properties = NavEditorOptionsWindow->PropertiesBox; + %properties->LinkProperties.setVisible(false); + %properties->TileProperties.setVisible(false); + %properties->TestProperties.setVisible(false); +} + +function updateTestData(%control, %flags) +{ + %control->LinkWalkFlag.setActive(true); + %control->LinkSwimFlag.setActive(true); + %control->LinkJumpFlag.setActive(true); + %control->LinkDropFlag.setActive(true); + %control->LinkLedgeFlag.setActive(true); + %control->LinkClimbFlag.setActive(true); + %control->LinkTeleportFlag.setActive(true); + + %control->LinkWalkFlag.setStateOn(%flags & $Nav::WalkFlag); + %control->LinkSwimFlag.setStateOn(%flags & $Nav::SwimFlag); + %control->LinkJumpFlag.setStateOn(%flags & $Nav::JumpFlag); + %control->LinkDropFlag.setStateOn(%flags & $Nav::DropFlag); + %control->LinkLedgeFlag.setStateOn(%flags & $Nav::LedgeFlag); + %control->LinkClimbFlag.setStateOn(%flags & $Nav::ClimbFlag); + %control->LinkTeleportFlag.setStateOn(%flags & $Nav::TeleportFlag); +} + +function getTestFlags(%control) +{ + return (%control->LinkWalkFlag.isStateOn() ? $Nav::WalkFlag : 0) | + (%control->LinkSwimFlag.isStateOn() ? $Nav::SwimFlag : 0) | + (%control->LinkJumpFlag.isStateOn() ? $Nav::JumpFlag : 0) | + (%control->LinkDropFlag.isStateOn() ? $Nav::DropFlag : 0) | + (%control->LinkLedgeFlag.isStateOn() ? $Nav::LedgeFlag : 0) | + (%control->LinkClimbFlag.isStateOn() ? $Nav::ClimbFlag : 0) | + (%control->LinkTeleportFlag.isStateOn() ? $Nav::TeleportFlag : 0); +} + +function disableTestData(%control) +{ + %control->LinkWalkFlag.setActive(false); + %control->LinkSwimFlag.setActive(false); + %control->LinkJumpFlag.setActive(false); + %control->LinkDropFlag.setActive(false); + %control->LinkLedgeFlag.setActive(false); + %control->LinkClimbFlag.setActive(false); + %control->LinkTeleportFlag.setActive(false); +} + +function getControllerDataFlags(%controllerData) +{ + return (%controllerData.allowWalk ? $Nav::WalkFlag : 0 ) | + (%controllerData.allowSwim ? $Nav::SwimFlag : 0 ) | + (%controllerData.allowJump ? $Nav::JumpFlag : 0 ) | + (%controllerData.allowDrop ? $Nav::DropFlag : 0 ) | + (%controllerData.allowLedge ? $Nav::LedgeFlag : 0) | + (%controllerData.allowClimb ? $Nav::ClimbFlag : 0) | + (%controllerData.allowTeleport ? $Nav::TeleportFlag : 0 ); +} + +function NavMeshTestTool::updateTestFlags(%this) +{ + if(isObject(%this.getPlayer())) + { + %properties = NavEditorOptionsWindow-->TestProperties; + %player = %this.getPlayer(); + %controllerData = %player.getDatablock().aiControllerData; + + %controllerData.allowWalk = %properties->LinkWalkFlag.isStateOn(); + %controllerData.allowSwim = %properties->LinkSwimFlag.isStateOn(); + %controllerData.allowJump = %properties->LinkJumpFlag.isStateOn(); + %controllerData.allowDrop = %properties->LinkDropFlag.isStateOn(); + %controllerData.allowLedge = %properties->LinkLedgeFlag.isStateOn(); + %controllerData.allowClimb = %properties->LinkClimbFlag.isStateOn(); + %controllerData.allowTeleport = %properties->LinkTeleportFlag.isStateOn(); + + %player.aiController = new AIController(){ ControllerData = %controllerData; }; + %player.setAIController(%player.aiController); + } +} + +function NavMeshTestTool::onPlayerSelected(%this, %flags) +{ + if (!isObject(%this.getPlayer().aiController) || (isObject(%this.getPlayer().aiController) && !(%this.getPlayer().isMemberOfClass("AIPlayer")))) + { + %this.getPlayer().aiController = new AIController(){ ControllerData = %this.getPlayer().getDatablock().aiControllerData; }; + %this.getPlayer().setAIController(%this.getPlayer().aiController); + %flags = getControllerDataFlags(%this.getPlayer().getDatablock().aiControllerData); + } + NavMeshIgnore(%this.getPlayer(), true); + %this.getPlayer().setDamageState("Enabled"); + + updateTestData(NavEditorOptionsWindow-->TestProperties, %flags); +} + +function NavMeshTestTool::onPlayerDeselected(%this) +{ + disableTestData(NavEditorOptionsWindow-->TestProperties); +} + +function NavMeshTestFlagButton::onClick(%this) +{ + NavMeshTools->TestTool.updateTestFlags(); +} + +//------------------------------------------------------ + function TileTool::onActivated(%this) { NavInspector.setVisible(false); @@ -540,13 +676,13 @@ function NavEditorGui::deleteSelected(%this) toolsMessageBoxYesNo("Warning", "Are you sure you want to delete" SPC NavEditorGui.selectedObject.getName(), "NavEditorGui.deleteMesh();"); - case "TestMode": - %this.getPlayer().delete(); - %this.onPlayerDeselected(); - case "LinkMode": - %this.deleteLink(); - %this.isDirty = true; - } + // case "TestMode": + // %this.getPlayer().delete(); + // %this.onPlayerDeselected(); + // case "LinkMode": + // %this.deleteLink(); + // %this.isDirty = true; + // } } function NavEditorGui::buildSelectedMeshes(%this) @@ -567,47 +703,6 @@ function NavEditorGui::buildLinks(%this) } } -function NavEditorGui::onLinkSelected(%this, %flags) -{ - updateLinkData(NavEditorOptionsWindow-->LinkProperties, %flags); -} - -function NavEditorGui::onPlayerSelected(%this, %flags) -{ - if (!isObject(%this.getPlayer().aiController) || (isObject(%this.getPlayer().aiController) && !(%this.getPlayer().isMemberOfClass("AIPlayer")))) - { - %this.getPlayer().aiController = new AIController(){ ControllerData = %this.getPlayer().getDatablock().aiControllerData; }; - %this.getPlayer().setAIController(%this.getPlayer().aiController); - } - NavMeshIgnore(%this.getPlayer(), true); - %this.getPlayer().setDamageState("Enabled"); - - updateLinkData(NavEditorOptionsWindow-->TestProperties, %flags); -} - -function NavEditorGui::updateTestFlags(%this) -{ - if(isObject(%this.getPlayer())) - { - %properties = NavEditorOptionsWindow-->TestProperties; - %player = %this.getPlayer(); - - %player.allowWwalk = %properties->LinkWalkFlag.isStateOn(); - %player.allowJump = %properties->LinkJumpFlag.isStateOn(); - %player.allowDrop = %properties->LinkDropFlag.isStateOn(); - %player.allowLedge = %properties->LinkLedgeFlag.isStateOn(); - %player.allowClimb = %properties->LinkClimbFlag.isStateOn(); - %player.allowTeleport = %properties->LinkTeleportFlag.isStateOn(); - - %this.isDirty = true; - } -} - -function NavEditorGui::onPlayerDeselected(%this) -{ - disableLinkData(NavEditorOptionsWindow-->TestProperties); -} - function NavEditorGui::createCoverPoints(%this) { if(isObject(%this.getMesh())) @@ -718,11 +813,6 @@ function ENavEditorPaletteButton::onClick(%this) //----------------------------------------------------------------------------- -function NavMeshTestFlagButton::onClick(%this) -{ - NavEditorGui.updateTestFlags(); -} - singleton GuiControlProfile(NavEditorProfile) { canKeyFocus = true; From 9039435d12d5f2d1ddbd5fd948c7d6f66babe9c4 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sat, 26 Jul 2025 10:41:03 +0100 Subject: [PATCH 16/38] Update navEditor.tscript woops --- Templates/BaseGame/game/tools/navEditor/navEditor.tscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 31d5e5eec..440ba518f 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -682,7 +682,7 @@ function NavEditorGui::deleteSelected(%this) // case "LinkMode": // %this.deleteLink(); // %this.isDirty = true; - // } + } } function NavEditorGui::buildSelectedMeshes(%this) From cc047cf07f123e97ddadb0d123fff94d25f39300 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sat, 26 Jul 2025 10:41:25 +0100 Subject: [PATCH 17/38] Update aiPlayer.h --- Engine/source/T3D/aiPlayer.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Engine/source/T3D/aiPlayer.h b/Engine/source/T3D/aiPlayer.h index 1274de0b8..e5d41330d 100644 --- a/Engine/source/T3D/aiPlayer.h +++ b/Engine/source/T3D/aiPlayer.h @@ -228,6 +228,8 @@ public: /// Types of link we can use. LinkData mLinkTypes; + dtQueryFilter mFilter; + Vector mAreaCosts; /// @} #endif // TORQUE_NAVIGATION_ENABLED From a5e969a8fd9e57553e80593f84232ef65b85b0f0 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sat, 26 Jul 2025 10:48:49 +0100 Subject: [PATCH 18/38] render links in editor Render the navmesh links in the guiNavEditor not the offmeshconnectiontool, this way they are always visible. --- Engine/source/navigation/guiNavEditorCtrl.cpp | 5 ++++- Engine/source/navigation/navMeshTools/offMeshConnTool.cpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp index 72cd5ff71..fd80f579b 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.cpp +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -285,8 +285,11 @@ void GuiNavEditorCtrl::renderScene(const RectI & updateRect) mTool->onRender3D(); duDebugDrawTorque d; - if(!mMesh.isNull()) + if (!mMesh.isNull()) + { mMesh->renderLinks(d); + d.immediateRender(); + } // Now draw all the 2d stuff! GFX->setClipRect(updateRect); diff --git a/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp b/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp index e0a97e18a..ef01db43b 100644 --- a/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp +++ b/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp @@ -146,7 +146,7 @@ void OffMeshConnectionTool::onRender3D() dd.end(); } - mNavMesh->renderLinks(dd); + //mNavMesh->renderLinks(dd); dd.immediateRender(); } From f730d0bf1ca4f1b99979f4c3d3c4a76e8bf02127 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 27 Jul 2025 12:43:19 +0100 Subject: [PATCH 19/38] added spawning Tile test tool now spawns classes and data Test avoidance added to aicontroller and ainavigation --- Engine/source/T3D/AI/AIController.cpp | 13 +- Engine/source/T3D/AI/AINavigation.cpp | 58 +++++ Engine/source/T3D/AI/AINavigation.h | 1 + Engine/source/navigation/navMesh.cpp | 3 +- .../navMeshTools/navMeshTestTool.cpp | 66 +++++- .../navigation/navMeshTools/navMeshTestTool.h | 3 + .../game/tools/navEditor/NavEditorGui.gui | 220 ++++------------- .../game/tools/navEditor/navEditor.tscript | 223 ++++++++---------- 8 files changed, 286 insertions(+), 301 deletions(-) diff --git a/Engine/source/T3D/AI/AIController.cpp b/Engine/source/T3D/AI/AIController.cpp index 73f179d7d..161a60dcf 100644 --- a/Engine/source/T3D/AI/AIController.cpp +++ b/Engine/source/T3D/AI/AIController.cpp @@ -168,8 +168,19 @@ bool AIController::getAIMove(Move* movePtr) { obj = getAIInfo()->mObj; } + bool adjusted = false; + if (getNav()->avoidObstacles()) + { + adjusted = true; + } + else if (mRandI(0, 100) < mControllerData->mFlocking.mChance && getNav()->flock()) + { + adjusted = true; + } + + // Only repath if not already adjusted and on risky ground RayInfo info; - if (obj->getContainer()->castRay(obj->getPosition(), obj->getPosition() - Point3F(0, 0, mControllerData->mHeightTolerance), StaticShapeObjectType, &info)) + if (!adjusted && obj->getContainer()->castRay(obj->getPosition(), obj->getPosition() - Point3F(0, 0, mControllerData->mHeightTolerance), StaticShapeObjectType, &info)) { getNav()->repath(); } diff --git a/Engine/source/T3D/AI/AINavigation.cpp b/Engine/source/T3D/AI/AINavigation.cpp index ffb29fdd9..86a400f8c 100644 --- a/Engine/source/T3D/AI/AINavigation.cpp +++ b/Engine/source/T3D/AI/AINavigation.cpp @@ -343,6 +343,10 @@ void AINavigation::repath() { mPathData.path->mTo = mMoveDestination; } + else if (avoidObstacles()) + { + mPathData.path->mTo = mMoveDestination; + } else { // If we're following, get their position. @@ -380,6 +384,60 @@ void AINavigation::clearPath() mPathData = PathData(); } +bool AINavigation::avoidObstacles() +{ + SimObjectPtr obj = getCtrl()->getAIInfo()->mObj; + obj->disableCollision(); + + Point3F pos = obj->getBoxCenter(); + VectorF forward = obj->getTransform().getForwardVector(); + forward.normalizeSafe(); + + // Generate forward-left and forward-right by rotating forward vector + VectorF right = mCross(forward, Point3F(0, 0, 1)); + VectorF leftDir = forward + right * -0.5f; // front-left + VectorF rightDir = forward + right * 0.5f; // front-right + + leftDir.normalizeSafe(); + rightDir.normalizeSafe(); + + F32 rayLength = getCtrl()->mMovement.getMoveSpeed(); + Point3F directions[3] = { + forward, + leftDir, + rightDir + }; + + bool hit[3] = { false, false, false }; + RayInfo info; + for (int i = 0; i < 3; ++i) + { + Point3F end = pos + directions[i] * rayLength; + if (obj->getContainer()->castRay(pos, end, sAILoSMask, &info)) + { + hit[i] = true; + } + } + + Point3F avoidance = Point3F::Zero; + if (hit[0]) avoidance += right * 1.0f; + if (hit[1]) avoidance += right * 1.5f; + if (hit[2]) avoidance -= right * 1.5f; + + if (!avoidance.isZero()) + { + avoidance.normalizeSafe(); + F32 clearance = getCtrl()->getAIInfo()->mRadius * 1.5f; + Point3F newDest = info.point + avoidance * rayLength; + mMoveDestination = newDest; + obj->enableCollision(); + return true; + } + + obj->enableCollision(); + return false; +} + bool AINavigation::flock() { AIControllerData::Flocking flockingData = getCtrl()->mControllerData->mFlocking; diff --git a/Engine/source/T3D/AI/AINavigation.h b/Engine/source/T3D/AI/AINavigation.h index 4d4f1b966..abc514011 100644 --- a/Engine/source/T3D/AI/AINavigation.h +++ b/Engine/source/T3D/AI/AINavigation.h @@ -98,6 +98,7 @@ struct AINavigation /// Move to the specified node in the current path. void moveToNode(S32 node); + bool avoidObstacles(); bool flock(); #endif }; diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index cea2922a0..1591d82ae 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -668,6 +668,7 @@ bool NavMesh::build(bool background, bool saveIntermediates) } mBuilding = true; + m_geo = NULL; ctx->startTimer(RC_TIMER_TOTAL); @@ -1083,7 +1084,7 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, U32 &dataSize) } else if (mWaterMethod == Impassable) { - m_triareas[t] = RC_NULL_AREA; + m_triareas[t] = NullArea; } } } diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp index 751cb537b..1c254277f 100644 --- a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp @@ -51,7 +51,6 @@ void NavMeshTestTool::spawnPlayer(const Point3F& position) } obj->setPosition(position); - obj->registerObject(); SimObject* cleanup = Sim::findObject("MissionCleanup"); if (cleanup) { @@ -59,8 +58,29 @@ void NavMeshTestTool::spawnPlayer(const Point3F& position) missionCleanup->addObject(obj); } mPlayer = obj; - Con::executef(this, "onPlayerSpawned", obj->getIdString()); + +#ifdef TORQUE_NAVIGATION_ENABLED + AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); + if (asAIPlayer) + { + Con::executef(this, "onPlayerSelected"); + } + else + { + ShapeBase* sbo = dynamic_cast(mPlayer.getPointer()); + if (sbo && sbo->getAIController() && sbo->getAIController()->mControllerData) + { + Con::executef(this, "onPlayerSelected"); + } + else + { + Con::executef(this, "onPlayerSelected"); + } + } +#else + Con::executef(this, "onPlayerSelected"); +#endif } void NavMeshTestTool::drawAgent(duDebugDrawTorque& dd, const F32* pos, F32 r, F32 h, F32 c, const U32 col) @@ -94,6 +114,12 @@ NavMeshTestTool::NavMeshTestTool() mPathStart = Point3F::Max; mPathEnd = Point3F::Max; + + mTestPath = NULL; + + mLinkTypes = LinkData(AllFlags); + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + mFilter.setExcludeFlags(0); } void NavMeshTestTool::onActivated(const Gui3DMouseEvent& evt) @@ -103,6 +129,17 @@ void NavMeshTestTool::onActivated(const Gui3DMouseEvent& evt) void NavMeshTestTool::onDeactivated() { + if (mTestPath != NULL) { + mTestPath->deleteObject(); + mTestPath = NULL; + } + + if (mPlayer != NULL) + { + mPlayer = NULL; + Con::executef(this, "onPlayerDeselected"); + } + Con::executef(this, "onDeactivated"); } @@ -145,14 +182,14 @@ void NavMeshTestTool::on3DMouseDown(const Gui3DMouseEvent& evt) AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); if (asAIPlayer) { - Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags())); + Con::executef(this, "onPlayerSelected"); } else { ShapeBase* sbo = dynamic_cast(mPlayer.getPointer()); if (sbo && sbo->getAIController() && sbo->getAIController()->mControllerData) { - Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags())); + Con::executef(this, "onPlayerSelected"); } else { @@ -176,10 +213,30 @@ void NavMeshTestTool::on3DMouseDown(const Gui3DMouseEvent& evt) if (mPathStart != Point3F::Max) { mPathEnd = ri.point; + mTestPath = new NavPath(); + + mTestPath->mMesh = mNavMesh; + mTestPath->mFrom = mPathStart; + mTestPath->mTo = mPathEnd; + mTestPath->mFromSet = mTestPath->mToSet = true; + mTestPath->mAlwaysRender = true; + mTestPath->mLinkTypes = mLinkTypes; + mTestPath->mFilter = mFilter; + mTestPath->mXray = true; + // Paths plan automatically upon being registered. + if (!mTestPath->registerObject()) + { + delete mTestPath; + return; + } } else { mPathStart = ri.point; + if (mTestPath != NULL) { + mTestPath->deleteObject(); + mTestPath = NULL; + } } } else @@ -300,3 +357,4 @@ DefineEngineMethod(NavMeshTestTool, setSpawnDatablock, void, (String dbName), , { object->setSpawnDatablock(dbName); } + diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.h b/Engine/source/navigation/navMeshTools/navMeshTestTool.h index a229c2255..d68e00b90 100644 --- a/Engine/source/navigation/navMeshTools/navMeshTestTool.h +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.h @@ -16,6 +16,9 @@ protected: SimObjectPtr mCurPlayer; Point3F mPathStart; Point3F mPathEnd; + NavPath* mTestPath; + LinkData mLinkTypes; + dtQueryFilter mFilter; public: DECLARE_CONOBJECT(NavMeshTestTool); diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui index ac7b55db0..d22792360 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui @@ -279,22 +279,14 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { HorizSizing = "width"; VertSizing = "bottom"; Position = "4 24"; - Extent = "202 85"; + Extent = "202 136"; Docking = "Top"; Margin = "3 3 3 3"; internalName = "ActionsBox"; - new GuiTextCtrl(){ - Profile = "EditorTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - Position = "5 0"; - Extent = "86 18"; - text = "Actions"; - }; new GuiPopUpMenuCtrl(DrawModeSelector) { - position = "155 0"; - extent = "189 20"; + position = "7 0"; + extent = "190 20"; profile = "ToolsGuiPopUpMenuProfile"; tooltipProfile = "GuiToolTipProfile"; }; @@ -436,15 +428,46 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { internalName = "TestActions"; position = "7 21"; extent = "190 64"; - - new GuiButtonCtrl() { - Profile = "ToolsGuiButtonProfile"; - buttonType = "PushButton"; - HorizSizing = "right"; - VertSizing = "bottom"; - Extent = "180 18"; - text = "Spawn"; - command = "NavEditorGui.spawnPlayer();"; + new GuiControl() { + profile = "GuiDefaultProfile"; + Extent = "190 20"; + new GuiTextCtrl(){ + Profile = "EditorTextProfile"; + extent = "89 20"; + text = "Spawn Class"; + }; + new GuiTextCtrl(){ + Profile = "EditorTextProfile"; + Position = "100 0"; + extent = "89 20"; + text = "Spawn Datablock"; + }; + }; + new GuiControl() { + profile = "GuiDefaultProfile"; + Extent = "190 20"; + new GuiPopUpMenuCtrl(SpawnClassSelector) { + extent = "89 20"; + profile = "ToolsGuiPopUpMenuProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + + new GuiPopUpMenuCtrl(SpawnDatablockSelector) { + position = "100 0"; + extent = "89 20"; + profile = "ToolsGuiPopUpMenuProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + }; + + new GuiControl() { + profile = "GuiDefaultProfile"; + Extent = "190 20"; + new GuiTextCtrl(){ + Profile = "EditorTextProfile"; + extent = "89 20"; + text = "AI Actions"; + }; }; new GuiControl() { profile = "GuiDefaultProfile"; @@ -457,7 +480,9 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { VertSizing = "bottom"; Extent = "90 18"; text = "Delete"; - command = "NavEditorGui.getPlayer().delete();"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Delete Selected."; + command = "NavMeshTools->TestTool.getPlayer().delete();NavInspector.inspect();"; }; new GuiButtonCtrl() { position = "100 0"; @@ -467,7 +492,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { VertSizing = "bottom"; Extent = "90 18"; text = "Find cover"; - command = "NavEditorGui.findCover();"; + command = "NavMeshTools->TestTool.findCover();"; }; }; new GuiControl() { @@ -481,7 +506,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { VertSizing = "bottom"; Extent = "90 18"; text = "Follow"; - command = "NavEditorGui.followObject();"; + command = "NavMeshTools->TestTool.followObject();"; }; new GuiButtonCtrl() { position = "100 0"; @@ -491,7 +516,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { VertSizing = "bottom"; Extent = "90 18"; text = "Stop"; - command = "NavEditorGui.stop();"; + command = "NavMeshTools->TestTool.stop();"; }; }; }; @@ -836,153 +861,6 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { tooltipProfile = "GuiToolTipProfile"; toolTip = "Object to follow."; }; - new GuiTextCtrl() { - text = "Movement"; - profile = "ToolsGuiTextProfile"; - extent = "180 20"; - minExtent = "8 2"; - visible = "1"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkWalkFlag"; - class = "NavMeshTestFlagButton"; - text = " Walk"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character walk on ground?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkSwimFlag"; - class = "NavMeshTestFlagButton"; - text = " Swim"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character swim?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkJumpFlag"; - class = "NavMeshTestFlagButton"; - text = " Jump"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character jump?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkDropFlag"; - class = "NavMeshTestFlagButton"; - text = " Drop"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character drop over edges?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkLedgeFlag"; - class = "NavMeshTestFlagButton"; - text = " Ledge"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character jump from ledges?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkClimbFlag"; - class = "NavMeshTestFlagButton"; - text = " Climb"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character climb?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkTeleportFlag"; - class = "NavMeshTestFlagButton"; - text = " Teleport"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character teleport?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; }; }; new GuiMLTextCtrl(NavFieldInfoControl) { diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 440ba518f..eeda5932c 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -428,7 +428,18 @@ function NavMeshTestTool::onActivated(%this) %properties->TestProperties.setVisible(false); %actions->TestActions.setVisible(true); - %properties->TestProperties.setVisible(true); + %properties->TestProperties.setVisible(false); + + %classList = enumerateConsoleClasses("Player") TAB enumerateConsoleClasses("Vehicle"); + echo(%classList); + + SpawnClassSelector.clear(); + foreach$(%class in %classList) + { + SpawnClassSelector.add(%class); + } + + SpawnClassSelector.setFirstSelected(true); } function NavMeshTestTool::onDeactivated(%this) @@ -448,103 +459,109 @@ function NavMeshTestTool::onDeactivated(%this) %properties->TestProperties.setVisible(false); } -function updateTestData(%control, %flags) +function NavMeshTestTool::onPlayerSelected(%this) { - %control->LinkWalkFlag.setActive(true); - %control->LinkSwimFlag.setActive(true); - %control->LinkJumpFlag.setActive(true); - %control->LinkDropFlag.setActive(true); - %control->LinkLedgeFlag.setActive(true); - %control->LinkClimbFlag.setActive(true); - %control->LinkTeleportFlag.setActive(true); - - %control->LinkWalkFlag.setStateOn(%flags & $Nav::WalkFlag); - %control->LinkSwimFlag.setStateOn(%flags & $Nav::SwimFlag); - %control->LinkJumpFlag.setStateOn(%flags & $Nav::JumpFlag); - %control->LinkDropFlag.setStateOn(%flags & $Nav::DropFlag); - %control->LinkLedgeFlag.setStateOn(%flags & $Nav::LedgeFlag); - %control->LinkClimbFlag.setStateOn(%flags & $Nav::ClimbFlag); - %control->LinkTeleportFlag.setStateOn(%flags & $Nav::TeleportFlag); -} - -function getTestFlags(%control) -{ - return (%control->LinkWalkFlag.isStateOn() ? $Nav::WalkFlag : 0) | - (%control->LinkSwimFlag.isStateOn() ? $Nav::SwimFlag : 0) | - (%control->LinkJumpFlag.isStateOn() ? $Nav::JumpFlag : 0) | - (%control->LinkDropFlag.isStateOn() ? $Nav::DropFlag : 0) | - (%control->LinkLedgeFlag.isStateOn() ? $Nav::LedgeFlag : 0) | - (%control->LinkClimbFlag.isStateOn() ? $Nav::ClimbFlag : 0) | - (%control->LinkTeleportFlag.isStateOn() ? $Nav::TeleportFlag : 0); -} - -function disableTestData(%control) -{ - %control->LinkWalkFlag.setActive(false); - %control->LinkSwimFlag.setActive(false); - %control->LinkJumpFlag.setActive(false); - %control->LinkDropFlag.setActive(false); - %control->LinkLedgeFlag.setActive(false); - %control->LinkClimbFlag.setActive(false); - %control->LinkTeleportFlag.setActive(false); -} - -function getControllerDataFlags(%controllerData) -{ - return (%controllerData.allowWalk ? $Nav::WalkFlag : 0 ) | - (%controllerData.allowSwim ? $Nav::SwimFlag : 0 ) | - (%controllerData.allowJump ? $Nav::JumpFlag : 0 ) | - (%controllerData.allowDrop ? $Nav::DropFlag : 0 ) | - (%controllerData.allowLedge ? $Nav::LedgeFlag : 0) | - (%controllerData.allowClimb ? $Nav::ClimbFlag : 0) | - (%controllerData.allowTeleport ? $Nav::TeleportFlag : 0 ); -} - -function NavMeshTestTool::updateTestFlags(%this) -{ - if(isObject(%this.getPlayer())) - { - %properties = NavEditorOptionsWindow-->TestProperties; - %player = %this.getPlayer(); - %controllerData = %player.getDatablock().aiControllerData; - - %controllerData.allowWalk = %properties->LinkWalkFlag.isStateOn(); - %controllerData.allowSwim = %properties->LinkSwimFlag.isStateOn(); - %controllerData.allowJump = %properties->LinkJumpFlag.isStateOn(); - %controllerData.allowDrop = %properties->LinkDropFlag.isStateOn(); - %controllerData.allowLedge = %properties->LinkLedgeFlag.isStateOn(); - %controllerData.allowClimb = %properties->LinkClimbFlag.isStateOn(); - %controllerData.allowTeleport = %properties->LinkTeleportFlag.isStateOn(); - - %player.aiController = new AIController(){ ControllerData = %controllerData; }; - %player.setAIController(%player.aiController); - } -} - -function NavMeshTestTool::onPlayerSelected(%this, %flags) -{ - if (!isObject(%this.getPlayer().aiController) || (isObject(%this.getPlayer().aiController) && !(%this.getPlayer().isMemberOfClass("AIPlayer")))) + if (!isObject(%this.getPlayer().aiController)) { %this.getPlayer().aiController = new AIController(){ ControllerData = %this.getPlayer().getDatablock().aiControllerData; }; %this.getPlayer().setAIController(%this.getPlayer().aiController); - %flags = getControllerDataFlags(%this.getPlayer().getDatablock().aiControllerData); } + + if(%this.getPlayer().isMemberOfClass("AIPlayer")) + { + NavInspector.inspect(%this.getPlayer()); + NavInspector.setVisible(true); + } + else + { + NavInspector.inspect(%this.getPlayer().getDatablock().aiControllerData); + NavInspector.setVisible(true); + } + NavMeshIgnore(%this.getPlayer(), true); %this.getPlayer().setDamageState("Enabled"); - - updateTestData(NavEditorOptionsWindow-->TestProperties, %flags); } function NavMeshTestTool::onPlayerDeselected(%this) { - disableTestData(NavEditorOptionsWindow-->TestProperties); + NavInspector.inspect(); } -function NavMeshTestFlagButton::onClick(%this) +function NavMeshTestTool::stop(%this) { - NavMeshTools->TestTool.updateTestFlags(); + if (isObject(%this.getPlayer().aiController)) + %this.getPlayer().aiController.stop(); + else + { + %this.getPlayer().stop(); + } } +function SpawnClassSelector::onSelect(%this, %id) +{ + %className = %this.getTextById(%id); + + NavMeshTools->TestTool.setSpawnClass(%className); + + SpawnDatablockSelector.clear(); + %classData = %className @ "Data"; + if(%className $= "AIPlayer") + { + %classData = "PlayerData"; + } + + // add the datablocks + for(%i = 0; %i < DataBlockGroup.getCount(); %i++) + { + %obj = DataBlockGroup.getObject(%i); + if( isMemberOfClass( %obj.getClassName(), %classData )) + SpawnDatablockSelector.add(%obj.getName()); + } + + SpawnDatablockSelector.setFirstSelected(true); + +} + +function SpawnDatablockSelector::onSelect(%this, %id) +{ + %className = %this.getTextById(%id); + NavMeshTools->TestTool.setSpawnDatablock(%className); +} + + +// function NavEditorGui::findCover(%this) +// { +// if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer())) +// { +// %pos = LocalClientConnection.getControlObject().getPosition(); +// %text = NavEditorOptionsWindow-->TestProperties->CoverPosition.getText(); +// if(%text !$= "") +// %pos = eval("return " @ %text); +// %this.getPlayer().getAIController().findCover(%pos, NavEditorOptionsWindow-->TestProperties->CoverRadius.getText()); +// } +// } + +// function NavEditorGui::followObject(%this) +// { +// if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer())) +// { +// %obj = LocalClientConnection.player; +// %text = NavEditorOptionsWindow-->TestProperties->FollowObject.getText(); +// if(%text !$= "") +// { +// %command = "return " @ %text; +// if(!endsWith(%command, ";")) +// %command = %command @ ";"; + +// %obj = eval(%command); +// if(!isObject(%obj)) +// toolsMessageBoxOk("Error", "Cannot find object" SPC %text); +// } +// if(isObject(%obj)) +// %this.getPlayer().getAIController().followObject(%obj, NavEditorOptionsWindow-->TestProperties->FollowRadius.getText()); +// } +// } + //------------------------------------------------------ function TileTool::onActivated(%this) @@ -721,48 +738,6 @@ function NavEditorGui::deleteCoverPoints(%this) } } -function NavEditorGui::findCover(%this) -{ - if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer())) - { - %pos = LocalClientConnection.getControlObject().getPosition(); - %text = NavEditorOptionsWindow-->TestProperties->CoverPosition.getText(); - if(%text !$= "") - %pos = eval("return " @ %text); - %this.getPlayer().getAIController().findCover(%pos, NavEditorOptionsWindow-->TestProperties->CoverRadius.getText()); - } -} - -function NavEditorGui::followObject(%this) -{ - if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer())) - { - %obj = LocalClientConnection.player; - %text = NavEditorOptionsWindow-->TestProperties->FollowObject.getText(); - if(%text !$= "") - { - %command = "return " @ %text; - if(!endsWith(%command, ";")) - %command = %command @ ";"; - - %obj = eval(%command); - if(!isObject(%obj)) - toolsMessageBoxOk("Error", "Cannot find object" SPC %text); - } - if(isObject(%obj)) - %this.getPlayer().getAIController().followObject(%obj, NavEditorOptionsWindow-->TestProperties->FollowRadius.getText()); - } -} - -function NavEditorGui::stop(%this) -{ - if (isObject(%this.getPlayer().aiController)) - %this.getPlayer().aiController.stop(); - else - { - NavEditorGui.getPlayer().stop(); - } -} function NavInspector::inspect(%this, %obj) { %name = ""; From 715a2484a004e8aa3d636d7830ae44f89656e39c Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 27 Jul 2025 12:47:26 +0100 Subject: [PATCH 20/38] Update navEditor.tscript filter out vehicle --- Templates/BaseGame/game/tools/navEditor/navEditor.tscript | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index eeda5932c..44cdb0377 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -436,7 +436,8 @@ function NavMeshTestTool::onActivated(%this) SpawnClassSelector.clear(); foreach$(%class in %classList) { - SpawnClassSelector.add(%class); + if(%class !$= "Vehicle") // vehicle doesnt work, purely virtual class. + SpawnClassSelector.add(%class); } SpawnClassSelector.setFirstSelected(true); From 3924c6590abb19779ecdf0315d946ff830e6083b Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 27 Jul 2025 15:43:13 +0100 Subject: [PATCH 21/38] Update navigation.cmake --- Tools/CMake/modules/navigation.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/CMake/modules/navigation.cmake b/Tools/CMake/modules/navigation.cmake index e7dba5390..59a6b7325 100644 --- a/Tools/CMake/modules/navigation.cmake +++ b/Tools/CMake/modules/navigation.cmake @@ -4,7 +4,7 @@ option(TORQUE_NAVIGATION "Enable Navigation module" ON) if(TORQUE_NAVIGATION) message("Enabling Navigation Module") - file(GLOB_RECURSE TORQUE_NAV_SOURCES "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.h" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/navMeshTools/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navMeshTools/navigation/*.h") + file(GLOB_RECURSE TORQUE_NAV_SOURCES "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.h" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/navMeshTools/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/navMeshTools/*.h") set(TORQUE_SOURCE_FILES ${TORQUE_SOURCE_FILES} ${TORQUE_NAV_SOURCES}) set(TORQUE_LINK_LIBRARIES ${TORQUE_LINK_LIBRARIES} recast) set(TORQUE_COMPILE_DEFINITIONS ${TORQUE_COMPILE_DEFINITIONS} recast TORQUE_NAVIGATION_ENABLED) From 0b96579adacf33e73078f1e2d1790a0ae7d98c65 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 27 Jul 2025 16:22:00 +0100 Subject: [PATCH 22/38] Update navMeshTestTool.h fix linux and mac --- Engine/source/navigation/navMeshTools/navMeshTestTool.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.h b/Engine/source/navigation/navMeshTools/navMeshTestTool.h index d68e00b90..1dbeb8341 100644 --- a/Engine/source/navigation/navMeshTools/navMeshTestTool.h +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.h @@ -6,6 +6,10 @@ #include "navigation/navMeshTool.h" #endif +#ifndef _NAVPATH_H_ +#include "navigation/navPath.h" +#endif + class NavMeshTestTool : public NavMeshTool { typedef NavMeshTool Parent; From 3946017556894f103208f66c41dbbe381e5e50e8 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 27 Jul 2025 17:25:09 +0100 Subject: [PATCH 23/38] add follow logic select follow target and toggle follow for a specific object. Only way to unfollow is to move the following bot to an arbitrary location --- Engine/source/T3D/AI/AIController.cpp | 20 +++--- Engine/source/T3D/AI/AINavigation.cpp | 68 +++++++++--------- .../navMeshTools/navMeshTestTool.cpp | 69 +++++++++++++++++-- .../navigation/navMeshTools/navMeshTestTool.h | 6 ++ .../game/tools/navEditor/NavEditorGui.gui | 28 ++++++-- .../game/tools/navEditor/navEditor.tscript | 19 ++++- 6 files changed, 155 insertions(+), 55 deletions(-) diff --git a/Engine/source/T3D/AI/AIController.cpp b/Engine/source/T3D/AI/AIController.cpp index 161a60dcf..d798585c4 100644 --- a/Engine/source/T3D/AI/AIController.cpp +++ b/Engine/source/T3D/AI/AIController.cpp @@ -168,22 +168,20 @@ bool AIController::getAIMove(Move* movePtr) { obj = getAIInfo()->mObj; } - bool adjusted = false; - if (getNav()->avoidObstacles()) - { - adjusted = true; - } - else if (mRandI(0, 100) < mControllerData->mFlocking.mChance && getNav()->flock()) - { - adjusted = true; - } + + Point3F start = obj->getPosition(); + Point3F end = start; + start.z = obj->getBoxCenter().z; + end.z -= mControllerData->mHeightTolerance; + obj->disableCollision(); // Only repath if not already adjusted and on risky ground RayInfo info; - if (!adjusted && obj->getContainer()->castRay(obj->getPosition(), obj->getPosition() - Point3F(0, 0, mControllerData->mHeightTolerance), StaticShapeObjectType, &info)) + if (obj->getContainer()->castRay(start, end, StaticShapeObjectType, &info)) { getNav()->repath(); } + obj->enableCollision(); getGoal()->mInRange = false; } if (getGoal()->getDist() < mControllerData->mFollowTolerance ) @@ -541,7 +539,7 @@ AIControllerData::AIControllerData() mAttackRadius = 2.0f; mMoveStuckTolerance = 0.01f; mMoveStuckTestDelay = 30; - mHeightTolerance = 0.001f; + mHeightTolerance = 0.1f; mFollowTolerance = 1.0f; #ifdef TORQUE_NAVIGATION_ENABLED diff --git a/Engine/source/T3D/AI/AINavigation.cpp b/Engine/source/T3D/AI/AINavigation.cpp index 86a400f8c..3ff12eda4 100644 --- a/Engine/source/T3D/AI/AINavigation.cpp +++ b/Engine/source/T3D/AI/AINavigation.cpp @@ -23,7 +23,7 @@ #include "AIController.h" #include "T3D/shapeBase.h" -static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType | AIObjectType; +static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType; AINavigation::AINavigation(AIController* controller) { @@ -339,11 +339,11 @@ void AINavigation::repath() if (mPathData.path.isNull() || !mPathData.owned) return; - if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock()) + if (avoidObstacles()) { mPathData.path->mTo = mMoveDestination; } - else if (avoidObstacles()) + else if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock()) { mPathData.path->mTo = mMoveDestination; } @@ -401,7 +401,7 @@ bool AINavigation::avoidObstacles() leftDir.normalizeSafe(); rightDir.normalizeSafe(); - F32 rayLength = getCtrl()->mMovement.getMoveSpeed(); + F32 rayLength = obj->getVelocity().lenSquared() * TickSec * 2 + getCtrl()->getAIInfo()->mRadius; Point3F directions[3] = { forward, leftDir, @@ -445,9 +445,10 @@ bool AINavigation::flock() obj->disableCollision(); Point3F pos = obj->getBoxCenter(); - Point3F searchArea = Point3F(flockingData.mMin / 2, flockingData.mMax / 2, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2); F32 maxFlocksq = flockingData.mMax * flockingData.mMax; + Point3F searchArea = Point3F(maxFlocksq, maxFlocksq, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2); + bool flocking = false; U32 found = 0; if (getCtrl()->getGoal()) @@ -471,41 +472,35 @@ bool AINavigation::flock() sql.mList.remove(obj); Point3F avoidanceOffset = Point3F::Zero; + F32 avoidanceAmtSq = 0; - //avoid objects in the way RayInfo info; - if (obj->getContainer()->castRay(pos, dest + Point3F(0, 0, obj->getObjBox().len_z() / 2), sAILoSMask, &info)) - { - Point3F blockerOffset = (info.point - dest); - blockerOffset.z = 0; - avoidanceOffset += blockerOffset; - } - //avoid bots that are too close for (U32 i = 0; i < sql.mList.size(); i++) { ShapeBase* other = dynamic_cast(sql.mList[i]); Point3F objectCenter = other->getBoxCenter(); - F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin; + F32 sumMinRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin; F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius; - sumRad += separation; + separation += sumMinRad; Point3F offset = (pos - objectCenter); F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares - if ((flockingData.mMin > 0) && (offsetLensq < (sumRad * sumRad))) + if ((flockingData.mMin > 0) && (offsetLensq < (sumMinRad * sumMinRad))) { other->disableCollision(); - if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info)) + if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask | AIObjectType, &info)) { found++; - offset.normalizeSafe(); - offset *= sumRad + separation; + offset *= separation; avoidanceOffset += offset; //accumulate total group, move away from that + avoidanceAmtSq += offsetLensq; } other->enableCollision(); } } + //if we don't have to worry about bumping into one another (nothing found lower than minFLock), see about grouping up if (found == 0) { @@ -514,20 +509,20 @@ bool AINavigation::flock() ShapeBase* other = static_cast(sql.mList[i]); Point3F objectCenter = other->getBoxCenter(); - F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin; + F32 sumMaxRad = flockingData.mMax + other->getAIController()->mControllerData->mFlocking.mMax; F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius; - sumRad += separation; + separation += sumMaxRad; Point3F offset = (pos - objectCenter); - if ((flockingData.mMin > 0) && ((sumRad * sumRad) < (maxFlocksq))) + F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares + if ((flockingData.mMax > 0) && (offsetLensq < (sumMaxRad * sumMaxRad))) { other->disableCollision(); - if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info)) + if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask | AIObjectType, &info)) { found++; - offset.normalizeSafe(); - offset *= sumRad + separation; avoidanceOffset -= offset; // subtract total group, move toward it + avoidanceAmtSq -= offsetLensq; } other->enableCollision(); } @@ -535,27 +530,36 @@ bool AINavigation::flock() } if (found > 0) { + //ephasize the *side* portion of sidestep to better avoid clumps + if (avoidanceOffset.x < avoidanceOffset.y) + avoidanceOffset.x *= 2.0; + else + avoidanceOffset.y *= 2.0; + + //add fuzz to sidestepping avoidanceOffset.z = 0; avoidanceOffset.x = (mRandF() * avoidanceOffset.x) * 0.5 + avoidanceOffset.x * 0.75; avoidanceOffset.y = (mRandF() * avoidanceOffset.y) * 0.5 + avoidanceOffset.y * 0.75; - if (avoidanceOffset.lenSquared() < (maxFlocksq)) + + avoidanceOffset.normalizeSafe(); + avoidanceOffset *= avoidanceAmtSq; + + if ((avoidanceAmtSq) > flockingData.mMin * flockingData.mMin) { - dest += avoidanceOffset; + dest = obj->getPosition()+avoidanceOffset; } //if we're not jumping... if (mJump == None) { dest.z = obj->getPosition().z; + //make sure we don't run off a cliff Point3F zlen(0, 0, getCtrl()->mControllerData->mHeightTolerance); if (obj->getContainer()->castRay(dest + zlen, dest - zlen, TerrainObjectType | StaticShapeObjectType | StaticObjectType, &info)) { - if ((mMoveDestination - dest).len() > getCtrl()->mControllerData->mMoveTolerance) - { - mMoveDestination = dest; - flocking = true; - } + mMoveDestination = dest; + flocking = true; } } } diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp index 1c254277f..e5d8e2875 100644 --- a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp @@ -112,6 +112,9 @@ NavMeshTestTool::NavMeshTestTool() mPlayer = NULL; mCurPlayer = NULL; + mFollowObject = NULL; + mCurFollowObject = NULL; + mPathStart = Point3F::Max; mPathEnd = Point3F::Max; @@ -120,10 +123,12 @@ NavMeshTestTool::NavMeshTestTool() mLinkTypes = LinkData(AllFlags); mFilter.setIncludeFlags(mLinkTypes.getFlags()); mFilter.setExcludeFlags(0); + mSelectFollow = false; } void NavMeshTestTool::onActivated(const Gui3DMouseEvent& evt) { + mSelectFollow = false; Con::executef(this, "onActivated"); } @@ -140,6 +145,8 @@ void NavMeshTestTool::onDeactivated() Con::executef(this, "onPlayerDeselected"); } + mSelectFollow = false; + Con::executef(this, "onDeactivated"); } @@ -175,8 +182,17 @@ void NavMeshTestTool::on3DMouseDown(const Gui3DMouseEvent& evt) { if (!ri.object) return; - - mPlayer = ri.object; + if (mSelectFollow) + { + mFollowObject = ri.object; + Con::executef(this, "onFollowSelected"); + mSelectFollow = false; + return; + } + else + { + mPlayer = ri.object; + } #ifdef TORQUE_NAVIGATION_ENABLED AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); @@ -277,10 +293,17 @@ void NavMeshTestTool::on3DMouseMove(const Gui3DMouseEvent& evt) RayInfo ri; if (gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri)) - mCurPlayer = ri.object; + { + if (mSelectFollow) + mCurFollowObject = ri.object; + else + mCurPlayer = ri.object; + } else + { + mCurFollowObject = NULL; mCurPlayer = NULL; - + } } void NavMeshTestTool::onRender3D() @@ -310,10 +333,15 @@ void NavMeshTestTool::onRender3D() dd.immediateRender(); + if (!mCurFollowObject.isNull()) + renderBoxOutline(mCurFollowObject->getWorldBox(), ColorI::LIGHT); if (!mCurPlayer.isNull()) renderBoxOutline(mCurPlayer->getWorldBox(), ColorI::BLUE); if (!mPlayer.isNull()) renderBoxOutline(mPlayer->getWorldBox(), ColorI::GREEN); + if (!mFollowObject.isNull()) + renderBoxOutline(mFollowObject->getWorldBox(), ColorI::WHITE); + } bool NavMeshTestTool::updateGuiInfo() @@ -326,10 +354,25 @@ bool NavMeshTestTool::updateGuiInfo() String text; + if (mPlayer) + text = "LMB To Select move Destination. LSHIFT+LMB To Deselect Current Bot."; + if (mCurPlayer != NULL && mCurPlayer != mPlayer) + text = "LMB To select Bot."; + + if (mPlayer == NULL) + { + text = "LMB To place start/end for test path."; + } + + if (mSpawnClass != String::EmptyString && mSpawnDatablock != String::EmptyString) + text += " CTRL+LMB To spawn a new Bot."; + if (statusbar) Con::executef(statusbar, "setInfo", text.c_str()); text = ""; + if (mPlayer) + text = String::ToString("Bot Selected: %d", mPlayer->getId()); if (selectionBar) selectionBar->setText(text); @@ -342,12 +385,24 @@ S32 NavMeshTestTool::getPlayerId() return mPlayer.isNull() ? 0 : mPlayer->getId(); } +S32 NavMeshTestTool::getFollowObjectId() +{ + return mFollowObject.isNull() ? 0 : mFollowObject->getId(); +} + + DefineEngineMethod(NavMeshTestTool, getPlayer, S32, (), , "@brief Return the current player id.") { return object->getPlayerId(); } +DefineEngineMethod(NavMeshTestTool, getFollowObject, S32, (), , + "@brief Return the current follow object id.") +{ + return object->getFollowObjectId(); +} + DefineEngineMethod(NavMeshTestTool, setSpawnClass, void, (String className), , "") { object->setSpawnClass(className); @@ -358,3 +413,9 @@ DefineEngineMethod(NavMeshTestTool, setSpawnDatablock, void, (String dbName), , object->setSpawnDatablock(dbName); } +DefineEngineMethod(NavMeshTestTool, followSelectMode, void, (), , + "@brief Set NavMeshTool to select a follow object.") +{ + return object->followSelectMode(); +} + diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.h b/Engine/source/navigation/navMeshTools/navMeshTestTool.h index 1dbeb8341..2ee56e43e 100644 --- a/Engine/source/navigation/navMeshTools/navMeshTestTool.h +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.h @@ -18,11 +18,15 @@ protected: String mSpawnDatablock; SimObjectPtr mPlayer; SimObjectPtr mCurPlayer; + SimObjectPtr mFollowObject; + SimObjectPtr mCurFollowObject; Point3F mPathStart; Point3F mPathEnd; NavPath* mTestPath; LinkData mLinkTypes; dtQueryFilter mFilter; + bool mSelectFollow; + public: DECLARE_CONOBJECT(NavMeshTestTool); @@ -44,9 +48,11 @@ public: bool updateGuiInfo() override; S32 getPlayerId(); + S32 getFollowObjectId(); void setSpawnClass(String className) { mSpawnClass = className; } void setSpawnDatablock(String dbName) { mSpawnDatablock = dbName; } + void followSelectMode() { mSelectFollow = true; } }; diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui index d22792360..ee81b620e 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui @@ -294,7 +294,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "SelectActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -370,7 +370,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "LinkActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -386,7 +386,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "CoverActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -411,7 +411,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "TileActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -427,7 +427,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "TestActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiControl() { profile = "GuiDefaultProfile"; Extent = "190 20"; @@ -481,7 +481,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { Extent = "90 18"; text = "Delete"; tooltipProfile = "GuiToolTipProfile"; - tooltip = "Delete Selected."; + tooltip = "Delete Selected Bot."; command = "NavMeshTools->TestTool.getPlayer().delete();NavInspector.inspect();"; }; new GuiButtonCtrl() { @@ -505,7 +505,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { HorizSizing = "right"; VertSizing = "bottom"; Extent = "90 18"; - text = "Follow"; + text = "Select Follow"; command = "NavMeshTools->TestTool.followObject();"; }; new GuiButtonCtrl() { @@ -519,6 +519,20 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { command = "NavMeshTools->TestTool.stop();"; }; }; + new GuiControl() { + profile = "GuiDefaultProfile"; + Extent = "190 18"; + + new GuiButtonCtrl() { + Profile = "ToolsGuiButtonProfile"; + buttonType = "PushButton"; + HorizSizing = "right"; + VertSizing = "bottom"; + Extent = "90 18"; + text = "Toggle Follow"; + command = "NavMeshTools->TestTool.toggleFollow();"; + }; + }; }; }; new GuiContainer(NavEditorInspector){ diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 44cdb0377..232a160b4 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -431,7 +431,7 @@ function NavMeshTestTool::onActivated(%this) %properties->TestProperties.setVisible(false); %classList = enumerateConsoleClasses("Player") TAB enumerateConsoleClasses("Vehicle"); - echo(%classList); + //echo(%classList); SpawnClassSelector.clear(); foreach$(%class in %classList) @@ -498,6 +498,23 @@ function NavMeshTestTool::stop(%this) } } +function NavMeshTestTool::toggleFollow(%this) +{ + + if(isObject(%this.getFollowObject()) && isObject(%this.getPlayer())) + { + if(%this.getPlayer().isMemberOfClass("AIPlayer")) + %this.getPlayer().followObject(%this.getFollowObject(), "2.0"); + else + %this.getPlayer().getAIController().followObject(%this.getFollowObject(), %this.getPlayer().getDatablock().aiControllerData.mFollowTolerance); + } +} + +function NavMeshTestTool::followObject(%this) +{ + %this.followSelectMode(); +} + function SpawnClassSelector::onSelect(%this, %id) { %className = %this.getTextById(%id); From 24ec55e8bc31bf4a9ac39c906fe139a6f7927842 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 27 Jul 2025 19:32:52 +0100 Subject: [PATCH 24/38] cleanup add select tool cleanup more from guinaveditorctrl and scripts --- Engine/source/navigation/guiNavEditorCtrl.cpp | 30 +--- Engine/source/navigation/guiNavEditorCtrl.h | 12 +- Engine/source/navigation/navMeshTool.h | 7 + .../navMeshTools/navMeshSelectTool.cpp | 112 +++++++++++++ .../navMeshTools/navMeshSelectTool.h | 30 ++++ .../navMeshTools/navMeshTestTool.cpp | 3 + .../game/tools/navEditor/NavEditorGui.gui | 86 +++------- .../game/tools/navEditor/main.tscript | 31 +++- .../game/tools/navEditor/navEditor.tscript | 149 ++++++------------ 9 files changed, 252 insertions(+), 208 deletions(-) create mode 100644 Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp create mode 100644 Engine/source/navigation/navMeshTools/navMeshSelectTool.h diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp index fd80f579b..74418ddf5 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.cpp +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -38,6 +38,7 @@ #include "gui/worldEditor/undoActions.h" #include "T3D/gameBase/gameConnection.h" #include "T3D/AI/AIController.h" +#include "navigation/navMeshTool.h" IMPLEMENT_CONOBJECT(GuiNavEditorCtrl); @@ -47,15 +48,8 @@ ConsoleDocClass(GuiNavEditorCtrl, "@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; @@ -101,8 +95,6 @@ void GuiNavEditorCtrl::initPersistFields() void GuiNavEditorCtrl::onSleep() { Parent::onSleep(); - - //mMode = mSelectMode; } void GuiNavEditorCtrl::selectMesh(NavMesh *mesh) @@ -311,15 +303,6 @@ bool GuiNavEditorCtrl::getStaticPos(const Gui3DMouseEvent & event, Point3F &tpos 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. @@ -361,6 +344,7 @@ void GuiNavEditorCtrl::setActiveTool(NavMeshTool* tool) if (mTool) { + mTool->setActiveEditor(this); mTool->setActiveNavMesh(mMesh); mTool->onActivated(mLastEvent); } @@ -386,14 +370,4 @@ DefineEngineMethod(GuiNavEditorCtrl, setActiveTool, void, (const char* 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 diff --git a/Engine/source/navigation/guiNavEditorCtrl.h b/Engine/source/navigation/guiNavEditorCtrl.h index 5d1642143..c6eeea58f 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.h +++ b/Engine/source/navigation/guiNavEditorCtrl.h @@ -34,9 +34,9 @@ #include "gui/worldEditor/gizmo.h" #endif -#ifndef _NAVMESH_TOOL_H_ -#include "navigation/navMeshTool.h" -#endif +//#ifndef _NAVMESH_TOOL_H_ +//#include "navigation/navMeshTool.h" +//#endif #include "navMesh.h" #include "T3D/aiPlayer.h" @@ -45,6 +45,7 @@ struct ObjectRenderInst; class SceneManager; class SceneRenderState; class BaseMatInstance; +class NavMeshTool; class GuiNavEditorCtrl : public EditTSCtrl { @@ -99,9 +100,6 @@ public: bool getStaticPos(const Gui3DMouseEvent & event, Point3F &tpos); - void setMode(String mode, bool sourceShortcut); - String getMode() { return mMode; } - void selectMesh(NavMesh *mesh); S32 getMeshId(); @@ -122,8 +120,6 @@ protected: bool mIsDirty; - String mMode; - /// Currently-selected NavMesh. SimObjectPtr mMesh; diff --git a/Engine/source/navigation/navMeshTool.h b/Engine/source/navigation/navMeshTool.h index d03f12503..d6ad66aa2 100644 --- a/Engine/source/navigation/navMeshTool.h +++ b/Engine/source/navigation/navMeshTool.h @@ -12,6 +12,10 @@ #include "navigation/navMesh.h" #endif +#ifndef _GUINAVEDITORCTRL_H_ +#include "navigation/guiNavEditorCtrl.h" +#endif + class UndoAction; class NavMeshTool : public SimObject @@ -19,6 +23,8 @@ class NavMeshTool : public SimObject typedef SimObject Parent; protected: SimObjectPtr mNavMesh; + SimObjectPtr mCurEditor; + void _submitUndo(UndoAction* action); public: @@ -29,6 +35,7 @@ public: DECLARE_CONOBJECT(NavMeshTool); virtual void setActiveNavMesh(NavMesh* nav_mesh) { mNavMesh = nav_mesh; } + virtual void setActiveEditor(GuiNavEditorCtrl* nav_editor) { mCurEditor = nav_editor; } virtual void onActivated(const Gui3DMouseEvent& lastEvent) {} virtual void onDeactivated() {} diff --git a/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp b/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp new file mode 100644 index 000000000..a51c614b5 --- /dev/null +++ b/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp @@ -0,0 +1,112 @@ +#include "navMeshSelectTool.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" + +IMPLEMENT_CONOBJECT(NavMeshSelectTool); + +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)); + } +} + +NavMeshSelectTool::NavMeshSelectTool() +{ + mCurMesh = NULL; +} + +void NavMeshSelectTool::onActivated(const Gui3DMouseEvent& evt) +{ + Con::executef(this, "onActivated"); +} + +void NavMeshSelectTool::onDeactivated() +{ + Con::executef(this, "onDeactivated"); +} + +void NavMeshSelectTool::on3DMouseDown(const Gui3DMouseEvent& evt) +{ + if (mCurEditor.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + if (gServerContainer.castRay(startPnt, endPnt, MarkerObjectType, &ri)) + { + if (!ri.object) + return; + + NavMesh* selNavMesh = dynamic_cast(ri.object); + if (selNavMesh) + { + mCurEditor->selectMesh(selNavMesh); + return; + } + } + +} + +void NavMeshSelectTool::on3DMouseMove(const Gui3DMouseEvent& evt) +{ + if (mCurEditor.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + if (gServerContainer.castRay(startPnt, endPnt, MarkerObjectType, &ri)) + { + NavMesh* selNavMesh = dynamic_cast(ri.object); + if (selNavMesh) + { + mCurMesh = selNavMesh; + } + else + { + mCurMesh = NULL; + } + } + else + { + mCurMesh = NULL; + } +} + +void NavMeshSelectTool::onRender3D() +{ + if (!mCurMesh.isNull()) + renderBoxOutline(mCurMesh->getWorldBox(), ColorI::LIGHT); +} + +bool NavMeshSelectTool::updateGuiInfo() +{ + SimObject* statusbar; + Sim::findObject("EditorGuiStatusBar", statusbar); + + GuiTextCtrl* selectionBar; + Sim::findObject("EWorldEditorStatusBarSelection", selectionBar); + + String text; + + if (statusbar) + Con::executef(statusbar, "setInfo", text.c_str()); + + if (selectionBar) + selectionBar->setText(text); + + return true; +} diff --git a/Engine/source/navigation/navMeshTools/navMeshSelectTool.h b/Engine/source/navigation/navMeshTools/navMeshSelectTool.h new file mode 100644 index 000000000..6497d1648 --- /dev/null +++ b/Engine/source/navigation/navMeshTools/navMeshSelectTool.h @@ -0,0 +1,30 @@ +#ifndef _NAVMESHSELECTTOOL_H_ +#define _NAVMESHSELECTTOOL_H_ + + +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + +class NavMeshSelectTool : public NavMeshTool +{ + typedef NavMeshTool Parent; +protected: + SimObjectPtr mCurMesh; +public: + DECLARE_CONOBJECT(NavMeshSelectTool); + + NavMeshSelectTool(); + virtual ~NavMeshSelectTool() {} + + void onActivated(const Gui3DMouseEvent& evt) override; + void onDeactivated() override; + + void on3DMouseDown(const Gui3DMouseEvent& evt) override; + void on3DMouseMove(const Gui3DMouseEvent& evt) override; + void onRender3D() override; + + bool updateGuiInfo() override; +}; + +#endif diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp index e5d8e2875..90292df84 100644 --- a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp @@ -367,6 +367,9 @@ bool NavMeshTestTool::updateGuiInfo() if (mSpawnClass != String::EmptyString && mSpawnDatablock != String::EmptyString) text += " CTRL+LMB To spawn a new Bot."; + if (mSelectFollow) + text = "LMB To select Follow Target."; + if (statusbar) Con::executef(statusbar, "setInfo", text.c_str()); diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui index ee81b620e..064a56ad0 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui @@ -491,8 +491,8 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { HorizSizing = "right"; VertSizing = "bottom"; Extent = "90 18"; - text = "Find cover"; - command = "NavMeshTools->TestTool.findCover();"; + text = "Stop"; + command = "NavMeshTools->TestTool.stop();"; }; }; new GuiControl() { @@ -515,8 +515,8 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { HorizSizing = "right"; VertSizing = "bottom"; Extent = "90 18"; - text = "Stop"; - command = "NavMeshTools->TestTool.stop();"; + text = "Find cover"; + command = "NavMeshTools->TestTool.findCover();"; }; }; new GuiControl() { @@ -532,6 +532,23 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { text = "Toggle Follow"; command = "NavMeshTools->TestTool.toggleFollow();"; }; + + new GuiTextEditSliderCtrl(CoverRadius) { + position = "100 0"; + extent = "90 18"; + format = "%3.2f"; + range = "0 1e+03"; + increment = "0.1"; + focusOnMouseWheel = "0"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + hovertime = "1000"; + profile = "ToolsGuiTextEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + toolTip = "The radius to search for cover"; + }; }; }; }; @@ -815,67 +832,6 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { variable = "$Nav::Editor::renderVoxels"; }; }; - new GuiStackControl() { - internalName = "TestProperties"; - position = "7 21"; - extent = "186 64"; - padding = "2 2 2 2"; - - new GuiTextCtrl() { - text = "Cover"; - profile = "ToolsGuiTextProfile"; - extent = "180 20"; - minExtent = "8 2"; - visible = "1"; - }; - new GuiTextEditCtrl() { - internalName = "CoverRadius"; - text = "10"; - profile = "ToolsGuiTextEditProfile"; - extent = "40 20"; - minExtent = "8 2"; - visible = "1"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Radius for cover-finding."; - }; - new GuiTextEditCtrl() { - internalName = "CoverPosition"; - text = "LocalClientConnection.getControlObject().getPosition();"; - profile = "ToolsGuiTextEditProfile"; - extent = "140 20"; - minExtent = "8 2"; - visible = "1"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Position to find cover from."; - }; - new GuiTextCtrl() { - text = "Follow"; - profile = "ToolsuiTextProfile"; - extent = "180 20"; - minExtent = "8 2"; - visible = "1"; - }; - new GuiTextEditCtrl() { - internalName = "FollowRadius"; - text = "1"; - profile = "ToolsGuiTextEditProfile"; - extent = "40 20"; - minExtent = "8 2"; - visible = "1"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Radius for following."; - }; - new GuiTextEditCtrl() { - internalName = "FollowObject"; - text = "LocalClientConnection.player"; - profile = "ToolsGuiTextEditProfile"; - extent = "140 20"; - minExtent = "8 2"; - visible = "1"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Object to follow."; - }; - }; }; new GuiMLTextCtrl(NavFieldInfoControl) { canSaveDynamicFields = "0"; diff --git a/Templates/BaseGame/game/tools/navEditor/main.tscript b/Templates/BaseGame/game/tools/navEditor/main.tscript index d4c47a1ec..f444bbded 100644 --- a/Templates/BaseGame/game/tools/navEditor/main.tscript +++ b/Templates/BaseGame/game/tools/navEditor/main.tscript @@ -59,6 +59,13 @@ function initializeNavEditor() new SimSet(NavMeshTools) { + new NavMeshSelectTool() + { + internalName = "SelectTool"; + toolTip = "Edit NavMesh"; + buttonImage = "ToolsModule:visibility_toggle_n_image"; + }; + new OffMeshConnectionTool() { internalName = "LinkTool"; @@ -143,12 +150,11 @@ function EditorGui::SetNavPalletBar() //Adds a button to the pallete stack //Name Icon Click Command Tooltip text Keybind - EWToolsPaletteWindow.addButton("ViewNavMesh", "ToolsModule:visibility_toggle_n_image", "NavEditorGui.prepSelectionMode();", "", "View NavMesh", "1"); + EWToolsPaletteWindow.addButton("EditMode", "ToolsModule:visibility_toggle_n_image", "NavEditorGui.setActiveTool(NavMeshTools->SelectTool);", "", "Edit NavMesh", "1"); EWToolsPaletteWindow.addButton("LinkMode", "ToolsModule:nav_link_n_image", "NavEditorGui.setActiveTool(NavMeshTools->LinkTool);", "", "Create off-mesh links", "2"); // EWToolsPaletteWindow.addButton("CoverMode", "ToolsModule:nav_cover_n_image", "NavEditorGui.setMode(\"CoverMode\");", "","Edit cover", "3"); - // EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setMode(\"TileMode\");", "", "View tiles", "4"); - EWToolsPaletteWindow.addButton("TestMode", "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TestTool);", "", "Test pathfinding", "5"); - EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TileTool);" , "", "View and Edit Tiles", "4"); + EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TileTool);" , "", "View and Edit Tiles", "4"); + EWToolsPaletteWindow.addButton("TestMode", "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TestTool);", "", "Test pathfinding", "5"); EWToolsPaletteWindow.refresh(); } @@ -160,7 +166,22 @@ function NavEditorPlugin::onActivated(%this) $Nav::EditorOpen = true; // Start off in Select mode. - ToolsPaletteArray->NavEditorSelectMode.performClick(); + // Callback when the nav editor changes mode. Set the appropriate dynamic + // GUI contents in the properties/actions boxes. + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->SelectActions.setVisible(false); + %actions->LinkActions.setVisible(false); + %actions->CoverActions.setVisible(false); + %actions->TileActions.setVisible(false); + %actions->TestActions.setVisible(false); + + %properties = NavEditorOptionsWindow->PropertiesBox; + %properties->LinkProperties.setVisible(false); + %properties->TileProperties.setVisible(false); + + ENavEditorSelectModeBtn.performClick(); EditorGui.bringToFront(NavEditorGui); NavEditorGui.setVisible(true); diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 232a160b4..3b3a5a03b 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -296,23 +296,37 @@ function NavEditorGui::showSidePanel() %parent.panelHidden = false; } -//------------------------------------------------------------------------------ +//------------------------------------------------------ +// NAVMESHTESTTOOL +//------------------------------------------------------ + +function NavMeshSelectTool::onActivated(%this) +{ + NavInspector.setVisible(false); + %actions = NavEditorOptionsWindow->ActionsBox; + NavInspector.setVisible(true); + %actions->SelectActions.setVisible(true); + + NavInspector.inspect(NavEditorGui.getMesh()); +} + +function NavMeshSelectTool::onDeactivated(%this) +{ + NavInspector.setVisible(false); + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->SelectActions.setVisible(false); +} + +//------------------------------------------------------ +// OffMeshConnectionTool +//------------------------------------------------------ function OffMeshConnectionTool::onActivated(%this) { NavInspector.setVisible(false); %actions = NavEditorOptionsWindow->ActionsBox; - %actions->SelectActions.setVisible(false); - %actions->LinkActions.setVisible(false); - %actions->CoverActions.setVisible(false); - %actions->TileActions.setVisible(false); - %actions->TestActions.setVisible(false); - %properties = NavEditorOptionsWindow->PropertiesBox; - %properties->LinkProperties.setVisible(false); - %properties->TileProperties.setVisible(false); - %properties->TestProperties.setVisible(false); %actions->LinkActions.setVisible(true); %properties->LinkProperties.setVisible(true); @@ -323,16 +337,9 @@ function OffMeshConnectionTool::onDeactivated(%this) NavInspector.setVisible(false); %actions = NavEditorOptionsWindow->ActionsBox; - %actions->SelectActions.setVisible(false); %actions->LinkActions.setVisible(false); - %actions->CoverActions.setVisible(false); - %actions->TileActions.setVisible(false); - %actions->TestActions.setVisible(false); - %properties = NavEditorOptionsWindow->PropertiesBox; %properties->LinkProperties.setVisible(false); - %properties->TileProperties.setVisible(false); - %properties->TestProperties.setVisible(false); } function OffMeshConnectionTool::updateLinkFlags(%this) @@ -409,6 +416,8 @@ function NavMeshLinkBiDirection::onClick(%this) NavMeshTools->LinkTool.updateLinkFlags(); } +//------------------------------------------------------ +// NAVMESHTESTTOOL //------------------------------------------------------ function NavMeshTestTool::onActivated(%this) @@ -416,19 +425,7 @@ function NavMeshTestTool::onActivated(%this) NavInspector.setVisible(false); %actions = NavEditorOptionsWindow->ActionsBox; - %actions->SelectActions.setVisible(false); - %actions->LinkActions.setVisible(false); - %actions->CoverActions.setVisible(false); - %actions->TileActions.setVisible(false); - %actions->TestActions.setVisible(false); - - %properties = NavEditorOptionsWindow->PropertiesBox; - %properties->LinkProperties.setVisible(false); - %properties->TileProperties.setVisible(false); - %properties->TestProperties.setVisible(false); - %actions->TestActions.setVisible(true); - %properties->TestProperties.setVisible(false); %classList = enumerateConsoleClasses("Player") TAB enumerateConsoleClasses("Vehicle"); //echo(%classList); @@ -448,16 +445,7 @@ function NavMeshTestTool::onDeactivated(%this) NavInspector.setVisible(false); %actions = NavEditorOptionsWindow->ActionsBox; - %actions->SelectActions.setVisible(false); - %actions->LinkActions.setVisible(false); - %actions->CoverActions.setVisible(false); - %actions->TileActions.setVisible(false); %actions->TestActions.setVisible(false); - - %properties = NavEditorOptionsWindow->PropertiesBox; - %properties->LinkProperties.setVisible(false); - %properties->TileProperties.setVisible(false); - %properties->TestProperties.setVisible(false); } function NavMeshTestTool::onPlayerSelected(%this) @@ -503,10 +491,11 @@ function NavMeshTestTool::toggleFollow(%this) if(isObject(%this.getFollowObject()) && isObject(%this.getPlayer())) { - if(%this.getPlayer().isMemberOfClass("AIPlayer")) - %this.getPlayer().followObject(%this.getFollowObject(), "2.0"); + %player = %this.getPlayer(); + if(%player.isMemberOfClass("AIPlayer")) + %player.followObject(%this.getFollowObject(), "2.0"); else - %this.getPlayer().getAIController().followObject(%this.getFollowObject(), %this.getPlayer().getDatablock().aiControllerData.mFollowTolerance); + %player.getAIController().followObject(%this.getFollowObject(), %player.getDatablock().aiControllerData.mFollowTolerance); } } @@ -515,6 +504,20 @@ function NavMeshTestTool::followObject(%this) %this.followSelectMode(); } +function NavMeshTestTool::findCover(%this) +{ + if(isObject(%this.getPlayer())) + { + %player = %this.getPlayer(); + %pos = %player.getPosition(); + + if(%player.isMemberOfClass("AIPlayer")) + %player.findCover(%pos, CoverRadius.getText()); + else + %player.getAIController().findCover(%pos, CoverRadius.getText()); + } +} + function SpawnClassSelector::onSelect(%this, %id) { %className = %this.getTextById(%id); @@ -546,40 +549,8 @@ function SpawnDatablockSelector::onSelect(%this, %id) NavMeshTools->TestTool.setSpawnDatablock(%className); } - -// function NavEditorGui::findCover(%this) -// { -// if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer())) -// { -// %pos = LocalClientConnection.getControlObject().getPosition(); -// %text = NavEditorOptionsWindow-->TestProperties->CoverPosition.getText(); -// if(%text !$= "") -// %pos = eval("return " @ %text); -// %this.getPlayer().getAIController().findCover(%pos, NavEditorOptionsWindow-->TestProperties->CoverRadius.getText()); -// } -// } - -// function NavEditorGui::followObject(%this) -// { -// if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer())) -// { -// %obj = LocalClientConnection.player; -// %text = NavEditorOptionsWindow-->TestProperties->FollowObject.getText(); -// if(%text !$= "") -// { -// %command = "return " @ %text; -// if(!endsWith(%command, ";")) -// %command = %command @ ";"; - -// %obj = eval(%command); -// if(!isObject(%obj)) -// toolsMessageBoxOk("Error", "Cannot find object" SPC %text); -// } -// if(isObject(%obj)) -// %this.getPlayer().getAIController().followObject(%obj, NavEditorOptionsWindow-->TestProperties->FollowRadius.getText()); -// } -// } - +//------------------------------------------------------ +// TILETOOL //------------------------------------------------------ function TileTool::onActivated(%this) @@ -587,17 +558,7 @@ function TileTool::onActivated(%this) NavInspector.setVisible(false); %actions = NavEditorOptionsWindow->ActionsBox; - %actions->SelectActions.setVisible(false); - %actions->LinkActions.setVisible(false); - %actions->CoverActions.setVisible(false); - %actions->TileActions.setVisible(false); - %actions->TestActions.setVisible(false); - %properties = NavEditorOptionsWindow->PropertiesBox; - %properties->LinkProperties.setVisible(false); - %properties->TileProperties.setVisible(false); - %properties->TestProperties.setVisible(false); - %actions->TileActions.setVisible(true); %properties->TileProperties.setVisible(true); } @@ -607,16 +568,9 @@ function TileTool::onDeactivated(%this) NavInspector.setVisible(false); %actions = NavEditorOptionsWindow->ActionsBox; - %actions->SelectActions.setVisible(false); - %actions->LinkActions.setVisible(false); - %actions->CoverActions.setVisible(false); %actions->TileActions.setVisible(false); - %actions->TestActions.setVisible(false); - %properties = NavEditorOptionsWindow->PropertiesBox; - %properties->LinkProperties.setVisible(false); %properties->TileProperties.setVisible(false); - %properties->TestProperties.setVisible(false); } //------------------------------------------------------ @@ -659,14 +613,6 @@ function NavEditorGui::onModeSet(%this, %mode) } } -function NavEditorGui::paletteSync(%this, %mode) -{ - // Synchronise the palette (small buttons on the left) with the actual mode - // the nav editor is in. - %evalShortcut = "ToolsPaletteArray-->" @ %mode @ ".setStateOn(1);"; - eval(%evalShortcut); -} - function NavEditorGui::onEscapePressed(%this) { return false; @@ -791,8 +737,7 @@ function NavTreeView::onSelect(%this, %obj) function NavEditorGui::prepSelectionMode(%this) { - %this.setMode("SelectMode"); - ToolsPaletteArray-->NavEditorSelectMode.setStateOn(1); + NavEditorGui.setActiveTool(NavMeshTools->SelectTool); } //----------------------------------------------------------------------------- From b5d6601b9605eec95f9e17c1dcaf0900144b1c07 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Mon, 28 Jul 2025 08:24:20 +0100 Subject: [PATCH 25/38] add cover tool add cover tool some more cleanup navmeshselecttool needs to use collideBox duDebugDrawTorque now has the transparent blending option --- .../source/navigation/duDebugDrawTorque.cpp | 5 + Engine/source/navigation/duDebugDrawTorque.h | 2 + Engine/source/navigation/guiNavEditorCtrl.cpp | 9 -- Engine/source/navigation/navMesh.cpp | 5 + .../navigation/navMeshTools/coverTool.cpp | 40 ++++++++ .../navigation/navMeshTools/coverTool.h | 33 +++++++ .../navMeshTools/navMeshSelectTool.cpp | 8 +- .../navMeshTools/navMeshSelectTool.h | 1 + .../game/tools/navEditor/main.tscript | 19 ++-- .../game/tools/navEditor/navEditor.tscript | 91 ++++++------------- .../NavEditorPalette.ed.gui | 17 ++-- 11 files changed, 142 insertions(+), 88 deletions(-) create mode 100644 Engine/source/navigation/navMeshTools/coverTool.cpp create mode 100644 Engine/source/navigation/navMeshTools/coverTool.h diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index 637c483b2..570908c8f 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -73,6 +73,11 @@ void duDebugDrawTorque::depthMask(bool state, bool isOverride) mOverrideState = isOverride; } +void duDebugDrawTorque::blend(bool blend) +{ + mDesc.setBlend(true); +} + void duDebugDrawTorque::texture(bool state) { // need a checker texture?...... if(state is true) then set first slot to that texture. diff --git a/Engine/source/navigation/duDebugDrawTorque.h b/Engine/source/navigation/duDebugDrawTorque.h index a1c69ac4f..ba7a0c9dd 100644 --- a/Engine/source/navigation/duDebugDrawTorque.h +++ b/Engine/source/navigation/duDebugDrawTorque.h @@ -68,6 +68,8 @@ public: /// Set to true to override any future changes. void depthMask(bool state, bool isOverride); + void blend(bool blend); + /// Begin drawing primitives. /// @param prim [in] primitive type to draw, one of rcDebugDrawPrimitives. /// @param size [in] size of a primitive, applies to point size and line width only. diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp index 74418ddf5..d01470817 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.cpp +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -178,9 +178,6 @@ bool GuiNavEditorCtrl::get3DCentre(Point3F &pos) void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) { - if (!mMesh) - return; - mGizmo->on3DMouseDown(event); if (mTool) @@ -193,9 +190,6 @@ void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) void GuiNavEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) { - if (!mMesh) - return; - // Keep the Gizmo up to date. mGizmo->on3DMouseUp(event); @@ -207,9 +201,6 @@ void GuiNavEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) { - if (!mMesh) - return; - if (mTool) mTool->on3DMouseMove(event); diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 1591d82ae..f1476e3e3 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -1582,7 +1582,12 @@ void NavMesh::renderToDrawer() m_drawMode == DRAWMODE_NAVMESH_INVIS)) { if (m_drawMode != DRAWMODE_NAVMESH_INVIS) + { + if (m_drawMode == DRAWMODE_NAVMESH_TRANS) + mDbgDraw.blend(true); duDebugDrawNavMeshWithClosedList(&mDbgDraw, *n->nm, *n->mQuery, 0); + mDbgDraw.blend(false); + } if(m_drawMode == DRAWMODE_NAVMESH_BVTREE) duDebugDrawNavMeshBVTree(&mDbgDraw, *n->nm); if(m_drawMode == DRAWMODE_NAVMESH_PORTALS) diff --git a/Engine/source/navigation/navMeshTools/coverTool.cpp b/Engine/source/navigation/navMeshTools/coverTool.cpp new file mode 100644 index 000000000..fda36ba8d --- /dev/null +++ b/Engine/source/navigation/navMeshTools/coverTool.cpp @@ -0,0 +1,40 @@ +#include "coverTool.h" + +IMPLEMENT_CONOBJECT(CoverTool); + +CoverTool::CoverTool() +{ +} + +void CoverTool::onActivated(const Gui3DMouseEvent& evt) +{ + Con::executef(this, "onActivated"); +} + +void CoverTool::onDeactivated() +{ + Con::executef(this, "onDeactivated"); +} + +void CoverTool::on3DMouseDown(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; +} + +void CoverTool::on3DMouseMove(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; +} + +void CoverTool::onRender3D() +{ + if (mNavMesh.isNull()) + return; +} + +bool CoverTool::updateGuiInfo() +{ + return false; +} diff --git a/Engine/source/navigation/navMeshTools/coverTool.h b/Engine/source/navigation/navMeshTools/coverTool.h new file mode 100644 index 000000000..73978a26c --- /dev/null +++ b/Engine/source/navigation/navMeshTools/coverTool.h @@ -0,0 +1,33 @@ +#ifndef _COVERTOOL_H_ +#define _COVERTOOL_H_ + +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + +#ifndef _NAVPATH_H_ +#include "navigation/navPath.h" +#endif + +class CoverTool : public NavMeshTool +{ + typedef NavMeshTool Parent; +public: + DECLARE_CONOBJECT(CoverTool); + + CoverTool(); + + virtual ~CoverTool() {} + + void onActivated(const Gui3DMouseEvent& evt) override; + void onDeactivated() override; + + void on3DMouseDown(const Gui3DMouseEvent& evt) override; + void on3DMouseMove(const Gui3DMouseEvent& evt) override; + void onRender3D() override; + + bool updateGuiInfo() override; +}; + +#endif // !_COVERTOOL_H_ + diff --git a/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp b/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp index a51c614b5..2081a3335 100644 --- a/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp +++ b/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp @@ -44,7 +44,7 @@ void NavMeshSelectTool::on3DMouseDown(const Gui3DMouseEvent& evt) Point3F endPnt = evt.pos + evt.vec * 1000.0f; RayInfo ri; - if (gServerContainer.castRay(startPnt, endPnt, MarkerObjectType, &ri)) + if (gServerContainer.collideBox(startPnt, endPnt, MarkerObjectType, &ri)) { if (!ri.object) return; @@ -53,6 +53,8 @@ void NavMeshSelectTool::on3DMouseDown(const Gui3DMouseEvent& evt) if (selNavMesh) { mCurEditor->selectMesh(selNavMesh); + mSelMesh = selNavMesh; + Con::executef(this, "onNavMeshSelected"); return; } } @@ -68,7 +70,7 @@ void NavMeshSelectTool::on3DMouseMove(const Gui3DMouseEvent& evt) Point3F endPnt = evt.pos + evt.vec * 1000.0f; RayInfo ri; - if (gServerContainer.castRay(startPnt, endPnt, MarkerObjectType, &ri)) + if (gServerContainer.collideBox(startPnt, endPnt, MarkerObjectType, &ri)) { NavMesh* selNavMesh = dynamic_cast(ri.object); if (selNavMesh) @@ -90,6 +92,8 @@ void NavMeshSelectTool::onRender3D() { if (!mCurMesh.isNull()) renderBoxOutline(mCurMesh->getWorldBox(), ColorI::LIGHT); + if (!mSelMesh.isNull()) + renderBoxOutline(mSelMesh->getWorldBox(), ColorI::LIGHT); } bool NavMeshSelectTool::updateGuiInfo() diff --git a/Engine/source/navigation/navMeshTools/navMeshSelectTool.h b/Engine/source/navigation/navMeshTools/navMeshSelectTool.h index 6497d1648..ff2ad8769 100644 --- a/Engine/source/navigation/navMeshTools/navMeshSelectTool.h +++ b/Engine/source/navigation/navMeshTools/navMeshSelectTool.h @@ -11,6 +11,7 @@ class NavMeshSelectTool : public NavMeshTool typedef NavMeshTool Parent; protected: SimObjectPtr mCurMesh; + SimObjectPtr mSelMesh; public: DECLARE_CONOBJECT(NavMeshSelectTool); diff --git a/Templates/BaseGame/game/tools/navEditor/main.tscript b/Templates/BaseGame/game/tools/navEditor/main.tscript index f444bbded..65c107376 100644 --- a/Templates/BaseGame/game/tools/navEditor/main.tscript +++ b/Templates/BaseGame/game/tools/navEditor/main.tscript @@ -73,11 +73,11 @@ function initializeNavEditor() buttonImage = "ToolsModule:nav_link_n_image"; }; - new NavMeshTestTool() + new CoverTool() { - internalName = "TestTool"; - toolTip = "PathFinding Test tool"; - buttonImage = "ToolsModule:3rd_person_camera_n_image"; + internalName = "NavCoverTool"; + toolTip = "Cover Tool"; + buttonImage = "ToolsModule:nav_cover_n_image"; }; new TileTool() @@ -86,6 +86,13 @@ function initializeNavEditor() toolTip = "Tile selection tool"; buttonImage = "ToolsModule:select_bounds_n_image"; }; + + new NavMeshTestTool() + { + internalName = "TestTool"; + toolTip = "PathFinding Test tool"; + buttonImage = "ToolsModule:3rd_person_camera_n_image"; + }; }; // Bind shortcuts for the nav editor. @@ -151,8 +158,8 @@ function EditorGui::SetNavPalletBar() //Adds a button to the pallete stack //Name Icon Click Command Tooltip text Keybind EWToolsPaletteWindow.addButton("EditMode", "ToolsModule:visibility_toggle_n_image", "NavEditorGui.setActiveTool(NavMeshTools->SelectTool);", "", "Edit NavMesh", "1"); - EWToolsPaletteWindow.addButton("LinkMode", "ToolsModule:nav_link_n_image", "NavEditorGui.setActiveTool(NavMeshTools->LinkTool);", "", "Create off-mesh links", "2"); - // EWToolsPaletteWindow.addButton("CoverMode", "ToolsModule:nav_cover_n_image", "NavEditorGui.setMode(\"CoverMode\");", "","Edit cover", "3"); + EWToolsPaletteWindow.addButton("LinkMode", "ToolsModule:nav_link_n_image", "NavEditorGui.setActiveTool(NavMeshTools->LinkTool);", "", "Create off-mesh links", "2"); + EWToolsPaletteWindow.addButton("CoverMode","ToolsModule:nav_cover_n_image", "NavEditorGui.setActiveTool(NavMeshTools->NavCoverTool);", "", "Create Cover Points.", "3"); EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TileTool);" , "", "View and Edit Tiles", "4"); EWToolsPaletteWindow.addButton("TestMode", "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TestTool);", "", "Test pathfinding", "5"); EWToolsPaletteWindow.refresh(); diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 3b3a5a03b..228338e72 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -297,7 +297,7 @@ function NavEditorGui::showSidePanel() } //------------------------------------------------------ -// NAVMESHTESTTOOL +// NAVMESHSELECTTOOL //------------------------------------------------------ function NavMeshSelectTool::onActivated(%this) @@ -317,6 +317,18 @@ function NavMeshSelectTool::onDeactivated(%this) %actions->SelectActions.setVisible(false); } +function NavMeshSeletTool::onNavMeshSelected(%this) +{ + %obj = NavEditorGui.getMesh(); + // we set the naveditorgui navmesh in source so just get it + // and update here. + NavInspector.inspect(NavEditorGui.getMesh()); + + NavTreeView.clearSelection(); + if(isObject(%obj)) + NavTreeView.selectItem(%obj); +} + //------------------------------------------------------ // OffMeshConnectionTool //------------------------------------------------------ @@ -416,6 +428,22 @@ function NavMeshLinkBiDirection::onClick(%this) NavMeshTools->LinkTool.updateLinkFlags(); } +//------------------------------------------------------ +// CoverTool +//------------------------------------------------------ + +function CoverTool::onActivated(%this) +{ + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->CoverActions.setVisible(true); +} + +function CoverTool::onDeactivated(%this) +{ + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->CoverActions.setVisible(false); +} + //------------------------------------------------------ // NAVMESHTESTTOOL //------------------------------------------------------ @@ -573,71 +601,11 @@ function TileTool::onDeactivated(%this) %properties->TileProperties.setVisible(false); } -//------------------------------------------------------ - -function NavEditorGui::onModeSet(%this, %mode) -{ - // Callback when the nav editor changes mode. Set the appropriate dynamic - // GUI contents in the properties/actions boxes. - NavInspector.setVisible(false); - - %actions = NavEditorOptionsWindow->ActionsBox; - %actions->SelectActions.setVisible(false); - %actions->LinkActions.setVisible(false); - %actions->CoverActions.setVisible(false); - %actions->TileActions.setVisible(false); - %actions->TestActions.setVisible(false); - - %properties = NavEditorOptionsWindow->PropertiesBox; - %properties->LinkProperties.setVisible(false); - %properties->TileProperties.setVisible(false); - %properties->TestProperties.setVisible(false); - - switch$(%mode) - { - case "SelectMode": - NavInspector.setVisible(true); - %actions->SelectActions.setVisible(true); - case "LinkMode": - %actions->LinkActions.setVisible(true); - %properties->LinkProperties.setVisible(true); - case "CoverMode": - // - %actions->CoverActions.setVisible(true); - case "TileMode": - %actions->TileActions.setVisible(true); - %properties->TileProperties.setVisible(true); - case "TestMode": - %actions->TestActions.setVisible(true); - %properties->TestProperties.setVisible(true); - } -} - function NavEditorGui::onEscapePressed(%this) { return false; } -function NavEditorGui::selectObject(%this, %obj) -{ - NavTreeView.clearSelection(); - if(isObject(%obj)) - NavTreeView.selectItem(%obj); - %this.onObjectSelected(%obj); -} - -function NavEditorGui::onObjectSelected(%this, %obj) -{ - if(isObject(%this.selectedObject)) - %this.deselect(); - %this.selectedObject = %obj; - if(isObject(%obj)) - { - %this.selectMesh(%obj); - NavInspector.inspect(%obj); - } -} - function NavEditorGui::deleteMesh(%this) { if(isObject(%this.selectedObject)) @@ -732,7 +700,6 @@ function NavTreeView::onInspect(%this, %obj) function NavTreeView::onSelect(%this, %obj) { NavInspector.inspect(%obj); - NavEditorGui.onObjectSelected(%obj); } function NavEditorGui::prepSelectionMode(%this) diff --git a/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui b/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui index b099f5835..499b433d9 100644 --- a/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui +++ b/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui @@ -11,7 +11,6 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { canSave = "1"; Visible = "1"; hovertime = "1000"; - new GuiBitmapButtonCtrl(ENavEditorSelectModeBtn) { canSaveDynamicFields = "1"; class = ENavEditorPaletteButton; @@ -26,9 +25,9 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "NavEditorGui.prepSelectionMode();"; + Command = "NavEditorGui.setActiveTool(NavMeshTools->SelectTool);"; tooltipprofile = "GuiToolTipProfile"; - ToolTip = "View NavMesh (1)."; + ToolTip = "Edit NavMesh (1)."; DetailedDesc = ""; hovertime = "1000"; bitmapAsset = "ToolsModule:visibility_toggle_n_image"; @@ -49,10 +48,10 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "NavEditorGui.setMode(\"LinkMode\");"; + Command = "NavEditorGui.setActiveTool(NavMeshTools->LinkTool);"; tooltipprofile = "GuiToolTipProfile"; - ToolTip = "Create off-mesh links (2)."; - DetailedDesc = "Click to select/add. Shift-click to add multiple end points."; + ToolTip = "Edit Links (2)."; + DetailedDesc = ""; hovertime = "1000"; bitmapAsset = "ToolsModule:nav_link_n_image"; buttonType = "RadioButton"; @@ -72,7 +71,7 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "NavEditorGui.setMode(\"CoverMode\");"; + Command = "NavEditorGui.setActiveTool(NavMeshTools->NavCoverTool);"; tooltipprofile = "GuiToolTipProfile"; ToolTip = "Edit cover (3)."; DetailedDesc = ""; @@ -95,7 +94,7 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "NavEditorGui.setMode(\"TileMode\");"; + Command = "NavEditorGui.setActiveTool(NavMeshTools->TileTool);"; tooltipprofile = "GuiToolTipProfile"; ToolTip = "View tiles (4)."; DetailedDesc = "Click to select."; @@ -118,7 +117,7 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "NavEditorGui.setMode(\"TestMode\");"; + Command = "NavEditorGui.setActiveTool(NavMeshTools->TestTool);"; tooltipprofile = "GuiToolTipProfile"; ToolTip = "Test pathfinding (5)."; DetailedDesc = "Click to select/move character, CTRL-click to spawn, SHIFT-click to deselect."; From 888e72ad28752335140d3b39efa714f28bbcc1ea Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Mon, 28 Jul 2025 08:30:46 +0100 Subject: [PATCH 26/38] Update navEditor.tscript typo --- .../BaseGame/game/tools/navEditor/navEditor.tscript | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 228338e72..3d72f307d 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -317,16 +317,14 @@ function NavMeshSelectTool::onDeactivated(%this) %actions->SelectActions.setVisible(false); } -function NavMeshSeletTool::onNavMeshSelected(%this) +function NavMeshSelectTool::onNavMeshSelected(%this) { - %obj = NavEditorGui.getMesh(); + NavTreeView.clearSelection(); + if(isObject(NavEditorGui.getMesh())) + NavTreeView.selectItem(NavEditorGui.getMesh()); // we set the naveditorgui navmesh in source so just get it // and update here. NavInspector.inspect(NavEditorGui.getMesh()); - - NavTreeView.clearSelection(); - if(isObject(%obj)) - NavTreeView.selectItem(%obj); } //------------------------------------------------------ From 9d98d55b1e860f9605c7409a747c88d4e3872d40 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Mon, 28 Jul 2025 10:02:52 +0100 Subject: [PATCH 27/38] fix tree selection --- Engine/source/navigation/navMeshTools/navMeshSelectTool.h | 1 + Templates/BaseGame/game/tools/navEditor/navEditor.tscript | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Engine/source/navigation/navMeshTools/navMeshSelectTool.h b/Engine/source/navigation/navMeshTools/navMeshSelectTool.h index ff2ad8769..521a23fe8 100644 --- a/Engine/source/navigation/navMeshTools/navMeshSelectTool.h +++ b/Engine/source/navigation/navMeshTools/navMeshSelectTool.h @@ -18,6 +18,7 @@ public: NavMeshSelectTool(); virtual ~NavMeshSelectTool() {} + void setActiveNavMesh(NavMesh* nav_mesh) { mNavMesh = nav_mesh; mSelMesh = nav_mesh; } void onActivated(const Gui3DMouseEvent& evt) override; void onDeactivated() override; diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 3d72f307d..3a501cddd 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -698,6 +698,8 @@ function NavTreeView::onInspect(%this, %obj) function NavTreeView::onSelect(%this, %obj) { NavInspector.inspect(%obj); + + NavEditorGui.selectMesh(%obj); } function NavEditorGui::prepSelectionMode(%this) From e8d56ca9873a96108746a3d86b081298b2fec43d Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Mon, 28 Jul 2025 11:31:06 +0100 Subject: [PATCH 28/38] render the search for the testtool --- Engine/source/navigation/navMesh.cpp | 11 +++++++++-- Engine/source/navigation/navMesh.h | 1 + .../navigation/navMeshTools/navMeshTestTool.cpp | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index f1476e3e3..b9421e3a3 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -1592,8 +1592,6 @@ void NavMesh::renderToDrawer() duDebugDrawNavMeshBVTree(&mDbgDraw, *n->nm); if(m_drawMode == DRAWMODE_NAVMESH_PORTALS) duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm); - if (m_drawMode == DRAWMODE_NAVMESH_NODES) - duDebugDrawNavMeshNodes(&mDbgDraw, *n->mQuery); } mDbgDraw.depthMask(true, false); @@ -1754,6 +1752,15 @@ void NavMesh::renderLinks(duDebugDraw &dd) dd.end(); } +void NavMesh::renderSearch(duDebugDraw& dd) +{ + if (mQuery == NULL) + return; + + if (m_drawMode == DRAWMODE_NAVMESH_NODES) + duDebugDrawNavMeshNodes(&dd, *mQuery); +} + void NavMesh::renderTileData(duDebugDrawTorque &dd, U32 tile) { if (tile > mTiles.size()) diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 61db2a32f..789425ca7 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -266,6 +266,7 @@ public: void prepRenderImage(SceneRenderState *state) override; void render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat); void renderLinks(duDebugDraw &dd); + void renderSearch(duDebugDraw& dd); void renderTileData(duDebugDrawTorque &dd, U32 tile); bool mAlwaysRender; diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp index 90292df84..b2612a841 100644 --- a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp @@ -331,6 +331,8 @@ void NavMeshTestTool::onRender3D() } dd.depthMask(true); + mNavMesh->renderSearch(dd); + dd.immediateRender(); if (!mCurFollowObject.isNull()) From dc74f63d8567b91e5622b890c767d3ca6bc9da91 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Mon, 28 Jul 2025 11:58:22 +0100 Subject: [PATCH 29/38] Update navMeshSelectTool.cpp update gui info --- Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp b/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp index 2081a3335..c80d9fab5 100644 --- a/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp +++ b/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp @@ -105,10 +105,15 @@ bool NavMeshSelectTool::updateGuiInfo() Sim::findObject("EWorldEditorStatusBarSelection", selectionBar); String text; + text = "LMB To select a NavMesh."; if (statusbar) Con::executef(statusbar, "setInfo", text.c_str()); + text = ""; + if(mSelMesh) + text = String::ToString("NavMesh Selected: %d", mSelMesh->getId()); + if (selectionBar) selectionBar->setText(text); From bad9d9b188fe60358b9977848e28b33c02294c6d Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Mon, 28 Jul 2025 20:26:31 +0100 Subject: [PATCH 30/38] final cleanup and fixes move the geo collection into each tile, seems to work better for large levels add true to getnav in aiconver so it overwrites all goals cache the triareas so we can use a tool later to modify them --- Engine/source/T3D/AI/AICover.cpp | 2 +- Engine/source/navigation/navMesh.cpp | 68 ++++++++++++++-------------- Engine/source/navigation/navMesh.h | 7 ++- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/Engine/source/T3D/AI/AICover.cpp b/Engine/source/T3D/AI/AICover.cpp index 97cf82a39..b680a6175 100644 --- a/Engine/source/T3D/AI/AICover.cpp +++ b/Engine/source/T3D/AI/AICover.cpp @@ -80,7 +80,7 @@ bool AIController::findCover(const Point3F& from, F32 radius) if (s.point) { // Calling setPathDestination clears cover... - bool foundPath = getNav()->setPathDestination(s.point->getPosition()); + bool foundPath = getNav()->setPathDestination(s.point->getPosition(), true); setCover(s.point); s.point->setOccupied(true); return foundPath; diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index b9421e3a3..dc4b9c6cc 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -46,7 +46,7 @@ extern bool gEditingMission; IMPLEMENT_CO_NETOBJECT_V1(NavMesh); -const U32 NavMesh::mMaxVertsPerPoly = 3; +const U32 NavMesh::mMaxVertsPerPoly = DT_VERTS_PER_POLYGON; SimObjectPtr NavMesh::smServerSet = NULL; @@ -857,36 +857,6 @@ void NavMesh::buildNextTile() { PROFILE_SCOPE(NavMesh_buildNextTile); - // this is just here so that load regens the mesh, also buildTile needs to regen incase geometry has changed. - if (!m_geo) - { - Box3F worldBox = getWorldBox(); - SceneContainer::CallbackInfo info; - info.context = PLC_Navigation; - info.boundingBox = worldBox; - m_geo = new RecastPolyList; - info.polyList = m_geo; - info.key = this; - getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); - - // Parse water objects into the same list, but remember how much geometry was /not/ water. - mWaterVertStart = m_geo->getVertCount(); - mWaterTriStart = m_geo->getTriCount(); - if (mWaterMethod != Ignore) - { - getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info); - } - - // Check for no geometry. - if (!m_geo->getVertCount()) - { - m_geo->clear(); - return; - } - - m_geo->getChunkyMesh(); - } - if(!mDirtyTiles.empty()) { dtFreeNavMeshQuery(mQuery); @@ -928,6 +898,11 @@ void NavMesh::buildNextTile() tile.dmesh = m_dmesh; m_dmesh = 0; } + if (m_triareas) + { + tile.triareas = m_triareas; + m_triareas = nullptr; + } if(data) { @@ -987,8 +962,6 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, U32 &dataSize) cleanup(); - const rcChunkyTriMesh* chunkyMesh = m_geo->getChunkyMesh(); - // Push out tile boundaries a bit. F32 tileBmin[3], tileBmax[3]; rcVcopy(tileBmin, tile.bmin); @@ -1019,7 +992,33 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, U32 &dataSize) m_cfg.bmax[0] += m_cfg.borderSize * m_cfg.cs; m_cfg.bmax[2] += m_cfg.borderSize * m_cfg.cs; - // Create a heightfield to voxelise our input geometry. + Box3F worldBox = RCtoDTS(m_cfg.bmin, m_cfg.bmax); + SceneContainer::CallbackInfo info; + info.context = PLC_Navigation; + info.boundingBox = worldBox; + m_geo = new RecastPolyList; + info.polyList = m_geo; + info.key = this; + getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); + + // Parse water objects into the same list, but remember how much geometry was /not/ water. + mWaterVertStart = m_geo->getVertCount(); + mWaterTriStart = m_geo->getTriCount(); + if (mWaterMethod != Ignore) + { + getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info); + } + + // Check for no geometry. + if (!m_geo->getVertCount()) + { + m_geo->clear(); + return NULL; + } + + const rcChunkyTriMesh* chunkyMesh = m_geo->getChunkyMesh(); + + // Create a heightfield to voxelize our input geometry. m_solid = rcAllocHeightfield(); if(!m_solid) { @@ -1686,6 +1685,7 @@ void NavMesh::cleanup() m_pmesh = 0; rcFreePolyMeshDetail(m_dmesh); m_dmesh = 0; + SAFE_DELETE(m_geo); } void NavMesh::prepRenderImage(SceneRenderState *state) diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 789425ca7..599ee8878 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -310,13 +310,13 @@ private: /// Recast min and max points. F32 bmin[3], bmax[3]; /// Default constructor. - Tile() : box(Box3F::Invalid), x(0), y(0), chf(0), solid(0), cset(0), pmesh(0), dmesh(0) + Tile() : box(Box3F::Invalid), x(0), y(0), chf(0), solid(0), cset(0), pmesh(0), dmesh(0), triareas(nullptr) { bmin[0] = bmin[1] = bmin[2] = bmax[0] = bmax[1] = bmax[2] = 0.0f; } /// Value constructor. Tile(const Box3F &b, U32 _x, U32 _y, const F32 *min, const F32 *max) - : box(b), x(_x), y(_y), chf(0), solid(0), cset(0), pmesh(0), dmesh(0) + : box(b), x(_x), y(_y), chf(0), solid(0), cset(0), pmesh(0), dmesh(0), triareas(nullptr) { rcVcopy(bmin, min); rcVcopy(bmax, max); @@ -334,8 +334,11 @@ private: delete pmesh; if (dmesh) delete dmesh; + if (triareas) + delete[] triareas; } + unsigned char* triareas; rcCompactHeightfield* chf; rcHeightfield* solid; rcContourSet* cset; From d45c3794a551c00b87ce064f26f0380d855c9c6f Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 29 Jul 2025 10:27:49 +0100 Subject: [PATCH 31/38] Update gfxGLCircularVolatileBuffer.h fix gl volatile buffer --- Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h index cd961a19e..4396709ee 100644 --- a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h +++ b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h @@ -224,6 +224,8 @@ public: if (GFXGL->mCapabilities.bufferStorage) { outPtr = static_cast(mBufferPtr) + mBufferFreePos; + _getBufferData.mOffset = outOffset; + _getBufferData.mSize = size; } else if (GFXGL->glUseMap()) { From 267986a28959ec1b7dbf933437b3f06b9587915c Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 29 Jul 2025 15:20:22 +0100 Subject: [PATCH 32/38] fix volatile buffer change debugdraw to use volatile fix volatile buffer wraparound --- Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h | 4 ++-- Engine/source/navigation/duDebugDrawTorque.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h index 4396709ee..1212bed2e 100644 --- a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h +++ b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h @@ -201,6 +201,8 @@ public: if (mBufferFreePos < mBufferSize) mUsedRanges.push_back(UsedRange(mBufferFreePos, mBufferSize - 1)); + init(); + // Reset free pos mBufferFreePos = 0; @@ -224,8 +226,6 @@ public: if (GFXGL->mCapabilities.bufferStorage) { outPtr = static_cast(mBufferPtr) + mBufferFreePos; - _getBufferData.mOffset = outOffset; - _getBufferData.mSize = size; } else if (GFXGL->glUseMap()) { diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index 570908c8f..1239e6b22 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -245,7 +245,7 @@ void duDebugDrawTorque::end() box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); GFXVertexBufferHandle buffer; - buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + buffer.set(GFX, batchVerts, GFXBufferTypeVolatile); GFXVertexPCT* verts = buffer.lock(); for (U32 i = 0; i < linesThisBatch * vertsPerLine; ++i) @@ -258,7 +258,7 @@ void duDebugDrawTorque::end() // --- Build index buffer GFXPrimitiveBufferHandle pb; - pb.set(GFX, linesThisBatch * 2, linesThisBatch, GFXBufferTypeStatic); + pb.set(GFX, linesThisBatch * 2, linesThisBatch, GFXBufferTypeVolatile); U16* indices = nullptr; pb.lock(&indices); @@ -303,7 +303,7 @@ void duDebugDrawTorque::end() box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); GFXVertexBufferHandle buffer; - buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + buffer.set(GFX, batchVerts, GFXBufferTypeVolatile); GFXVertexPCT* verts = buffer.lock(); for (U32 i = 0; i < trisThisBatch * vertsPerTri; ++i) @@ -317,7 +317,7 @@ void duDebugDrawTorque::end() // --- Build index buffer GFXPrimitiveBufferHandle pb; - pb.set(GFX, trisThisBatch*3, trisThisBatch, GFXBufferTypeStatic); + pb.set(GFX, trisThisBatch*3, trisThisBatch, GFXBufferTypeVolatile); U16* indices = nullptr; pb.lock(&indices); @@ -363,7 +363,7 @@ void duDebugDrawTorque::end() box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); GFXVertexBufferHandle buffer; - buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + buffer.set(GFX, batchVerts, GFXBufferTypeVolatile); GFXVertexPCT* verts = buffer.lock(); U32 outIdx = 0; @@ -383,7 +383,7 @@ void duDebugDrawTorque::end() buffer.unlock(); GFXPrimitiveBufferHandle pb; - pb.set(GFX, batchIndices, quadsThisBatch*2, GFXBufferTypeStatic); + pb.set(GFX, batchIndices, quadsThisBatch*2, GFXBufferTypeVolatile); U16* indices = nullptr; pb.lock(&indices); From 21095f777b22a4e94d1f96d0839ceaae2749c92e Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 29 Jul 2025 16:08:51 +0100 Subject: [PATCH 33/38] Update gfxGLCircularVolatileBuffer.h revert, that broke it more by upping memory --- Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h index 1212bed2e..cd961a19e 100644 --- a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h +++ b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h @@ -201,8 +201,6 @@ public: if (mBufferFreePos < mBufferSize) mUsedRanges.push_back(UsedRange(mBufferFreePos, mBufferSize - 1)); - init(); - // Reset free pos mBufferFreePos = 0; From 1b7768925b61518c6a9474989448afd7e521fb5a Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 31 Jul 2025 08:05:11 +0100 Subject: [PATCH 34/38] Update duDebugDrawTorque.cpp revert back to static because we chache the results of the buffer we cannot use volatile buffers, volatile buffers need to be resubmitted every frame to the ring buffer --- Engine/source/navigation/duDebugDrawTorque.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index 1239e6b22..570908c8f 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -245,7 +245,7 @@ void duDebugDrawTorque::end() box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); GFXVertexBufferHandle buffer; - buffer.set(GFX, batchVerts, GFXBufferTypeVolatile); + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); GFXVertexPCT* verts = buffer.lock(); for (U32 i = 0; i < linesThisBatch * vertsPerLine; ++i) @@ -258,7 +258,7 @@ void duDebugDrawTorque::end() // --- Build index buffer GFXPrimitiveBufferHandle pb; - pb.set(GFX, linesThisBatch * 2, linesThisBatch, GFXBufferTypeVolatile); + pb.set(GFX, linesThisBatch * 2, linesThisBatch, GFXBufferTypeStatic); U16* indices = nullptr; pb.lock(&indices); @@ -303,7 +303,7 @@ void duDebugDrawTorque::end() box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); GFXVertexBufferHandle buffer; - buffer.set(GFX, batchVerts, GFXBufferTypeVolatile); + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); GFXVertexPCT* verts = buffer.lock(); for (U32 i = 0; i < trisThisBatch * vertsPerTri; ++i) @@ -317,7 +317,7 @@ void duDebugDrawTorque::end() // --- Build index buffer GFXPrimitiveBufferHandle pb; - pb.set(GFX, trisThisBatch*3, trisThisBatch, GFXBufferTypeVolatile); + pb.set(GFX, trisThisBatch*3, trisThisBatch, GFXBufferTypeStatic); U16* indices = nullptr; pb.lock(&indices); @@ -363,7 +363,7 @@ void duDebugDrawTorque::end() box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); GFXVertexBufferHandle buffer; - buffer.set(GFX, batchVerts, GFXBufferTypeVolatile); + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); GFXVertexPCT* verts = buffer.lock(); U32 outIdx = 0; @@ -383,7 +383,7 @@ void duDebugDrawTorque::end() buffer.unlock(); GFXPrimitiveBufferHandle pb; - pb.set(GFX, batchIndices, quadsThisBatch*2, GFXBufferTypeVolatile); + pb.set(GFX, batchIndices, quadsThisBatch*2, GFXBufferTypeStatic); U16* indices = nullptr; pb.lock(&indices); From b9193072c1afbce63857fc662a9aa24501845bcd Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 31 Jul 2025 17:55:38 +0100 Subject: [PATCH 35/38] fix for draw cone and draw cylinder these werent rendering correctly and we suspect draw cone was trying to draw more than it was allocating. On a volatle buffer that causes issues. --- Engine/source/gfx/gfxDrawUtil.cpp | 202 +++++++++++++++------------ Engine/source/gfx/gl/gfxGLDevice.cpp | 3 - 2 files changed, 112 insertions(+), 93 deletions(-) diff --git a/Engine/source/gfx/gfxDrawUtil.cpp b/Engine/source/gfx/gfxDrawUtil.cpp index 40e6d15c5..16b886a55 100644 --- a/Engine/source/gfx/gfxDrawUtil.cpp +++ b/Engine/source/gfx/gfxDrawUtil.cpp @@ -1486,69 +1486,73 @@ void GFXDrawUtil::_drawWireCapsule( const GFXStateBlockDesc &desc, const Point3F void GFXDrawUtil::drawCone( const GFXStateBlockDesc &desc, const Point3F &basePnt, const Point3F &tipPnt, F32 baseRadius, const ColorI &color ) { - VectorF uvec = tipPnt - basePnt; - F32 height = uvec.len(); - uvec.normalize(); - MatrixF mat( true ); - MathUtils::getMatrixFromUpVector( uvec, &mat ); - mat.setPosition(basePnt); + VectorF dir = tipPnt - basePnt; + F32 height = dir.len(); + dir.normalize(); - Point3F scale( baseRadius, baseRadius, height ); - mat.scale(scale); + MatrixF mat(true); + MathUtils::getMatrixFromUpVector(dir, &mat); + mat.setPosition(basePnt); + mat.scale(Point3F(baseRadius, baseRadius, height)); GFXTransformSaver saver; - mDevice->pushWorldMatrix(); mDevice->multWorld(mat); - S32 numPoints = sizeof(circlePoints)/sizeof(Point2F); - GFXVertexBufferHandle verts(mDevice, numPoints * 3 + 2, GFXBufferTypeVolatile); + const S32 numPoints = sizeof(circlePoints) / sizeof(Point2F); + + // Vertex index layout + const S32 baseCenterIdx = 0; + const S32 baseStartIdx = 1; + const S32 tipIdx = baseStartIdx + numPoints; + const S32 sideStartIdx = tipIdx + 1; + + const S32 totalVerts = sideStartIdx + numPoints * 3; + + GFXVertexBufferHandle verts(mDevice, totalVerts, GFXBufferTypeVolatile); verts.lock(); - F32 sign = -1.f; - S32 indexDown = 0; //counting down from numPoints - S32 indexUp = 0; //counting up from 0 - S32 index = 0; //circlePoints index for cap - for (S32 i = 0; i < numPoints + 1; i++) + // Base center vertex (at origin in local space) + verts[baseCenterIdx].point = Point3F(0, 0, 0); + verts[baseCenterIdx].color = color; + + // Base circle vertices + for (S32 i = 0; i < numPoints; i++) { - //Top cap - if (i != numPoints) - { - if (sign < 0) - index = indexDown; - else - index = indexUp; + verts[baseStartIdx + i].point = Point3F(circlePoints[i].x, circlePoints[i].y, 0); + verts[baseStartIdx + i].color = color; + } - verts[i].point = Point3F(circlePoints[index].x, circlePoints[index].y, 0); - verts[i].color = color; + // Tip vertex (pointing "up" in local Z) + verts[tipIdx].point = Point3F(0, 0, 1); + verts[tipIdx].color = color; - if (sign < 0) - indexUp += 1; - else - indexDown = numPoints - indexUp; + // Side triangles: one triangle per segment + for (S32 i = 0; i < numPoints; i++) + { + S32 triBase = sideStartIdx + i * 3; - // invert sign - sign *= -1.0f; - } + // Each triangle is (tip, base[i], base[(i+1)%numPoints]) + verts[triBase + 0].point = verts[tipIdx].point; + verts[triBase + 1].point = verts[baseStartIdx + i].point; + verts[triBase + 2].point = verts[baseStartIdx + ((i + 1) % numPoints)].point; - //cone - S32 imod = i % numPoints; - S32 vertindex = 2 * i + numPoints; - verts[vertindex].point = Point3F(circlePoints[imod].x, circlePoints[imod].y, 0); - verts[vertindex].color = color; - verts[vertindex + 1].point = Point3F(0.0f, 0.0f, 1.0f); - verts[vertindex + 1].color = color; + verts[triBase + 0].color = color; + verts[triBase + 1].color = color; + verts[triBase + 2].color = color; } verts.unlock(); - mDevice->setStateBlockByDesc( desc ); - - mDevice->setVertexBuffer( verts ); + mDevice->setStateBlockByDesc(desc); + mDevice->setVertexBuffer(verts); mDevice->setupGenericShaders(); - mDevice->drawPrimitive(GFXTriangleStrip, 0, numPoints - 2); - mDevice->drawPrimitive(GFXTriangleStrip, numPoints, numPoints * 2); + // Draw base cap using triangle fan + mDevice->drawPrimitive(GFXTriangleList, baseCenterIdx, numPoints - 2); + + // Draw sides using triangle list + mDevice->drawPrimitive(GFXTriangleList, sideStartIdx, numPoints); mDevice->popWorldMatrix(); @@ -1556,71 +1560,89 @@ void GFXDrawUtil::drawCone( const GFXStateBlockDesc &desc, const Point3F &basePn void GFXDrawUtil::drawCylinder( const GFXStateBlockDesc &desc, const Point3F &basePnt, const Point3F &tipPnt, F32 radius, const ColorI &color ) { - VectorF uvec = tipPnt - basePnt; - F32 height = uvec.len(); - uvec.normalize(); - MatrixF mat( true ); - MathUtils::getMatrixFromUpVector( uvec, &mat ); + VectorF dir = tipPnt - basePnt; + F32 height = dir.len(); + dir.normalize(); + + MatrixF mat(true); + MathUtils::getMatrixFromUpVector(dir, &mat); mat.setPosition(basePnt); + mat.scale(Point3F(radius, radius, height)); - Point3F scale( radius, radius, height * 2 ); - mat.scale(scale); GFXTransformSaver saver; - mDevice->pushWorldMatrix(); mDevice->multWorld(mat); - S32 numPoints = sizeof(circlePoints) / sizeof(Point2F); - GFXVertexBufferHandle verts(mDevice, numPoints *4 + 2, GFXBufferTypeVolatile); + const S32 numPoints = sizeof(circlePoints) / sizeof(Point2F); + + // Vertex index layout + const S32 baseCenterIdx = 0; + const S32 topCenterIdx = 1; + const S32 baseStartIdx = 2; + const S32 topStartIdx = baseStartIdx + numPoints; + const S32 sideStartIdx = topStartIdx + numPoints; + + const S32 totalVerts = sideStartIdx + numPoints * 6; + + GFXVertexBufferHandle verts(mDevice, totalVerts, GFXBufferTypeVolatile); verts.lock(); - F32 sign = -1.f; - S32 indexDown = 0; //counting down from numPoints - S32 indexUp = 0; //counting up from 0 - S32 index = 0; //circlePoints index for caps - for (S32 i = 0; i < numPoints + 1; i++) + // Base center + verts[baseCenterIdx].point = Point3F(0, 0, 0); + verts[baseCenterIdx].color = color; + + // Top center + verts[topCenterIdx].point = Point3F(0, 0, 1); + verts[topCenterIdx].color = color; + + // Base circle + for (S32 i = 0; i < numPoints; ++i) { - //Top/Bottom cap - if (i != numPoints) - { - if (sign < 0) - index = indexDown; - else - index = indexUp; + verts[baseStartIdx + i].point = Point3F(circlePoints[i].x, circlePoints[i].y, 0); + verts[baseStartIdx + i].color = color; + } - verts[i].point = Point3F(circlePoints[index].x, circlePoints[index].y, 0); - verts[i].color = color; - verts[i + numPoints].point = Point3F(circlePoints[index].x, circlePoints[index].y, 0.5f); - verts[i + numPoints].color = color; + // Top circle + for (S32 i = 0; i < numPoints; ++i) + { + verts[topStartIdx + i].point = Point3F(circlePoints[i].x, circlePoints[i].y, 1.0f); + verts[topStartIdx + i].color = color; + } - if (sign < 0) - indexUp += 1; - else - indexDown = numPoints - indexUp; + // Side triangles + for (S32 i = 0; i < numPoints; ++i) + { + S32 next = (i + 1) % numPoints; + S32 idx = sideStartIdx + i * 6; - // invert sign - sign *= -1.0f; - } + // First triangle (base[i], base[next], top[i]) + verts[idx + 0].point = verts[baseStartIdx + i].point; + verts[idx + 1].point = verts[baseStartIdx + next].point; + verts[idx + 2].point = verts[topStartIdx + i].point; - //cylinder - S32 imod = i % numPoints; - S32 vertindex = 2 * i + (numPoints * 2); - verts[vertindex].point = Point3F(circlePoints[imod].x, circlePoints[imod].y, 0); - verts[vertindex].color = color; - verts[vertindex + 1].point = Point3F(circlePoints[imod].x, circlePoints[imod].y, 0.5f); - verts[vertindex + 1].color = color; + // Second triangle (top[i], base[next], top[next]) + verts[idx + 3].point = verts[topStartIdx + i].point; + verts[idx + 4].point = verts[baseStartIdx + next].point; + verts[idx + 5].point = verts[topStartIdx + next].point; + + for (int j = 0; j < 6; ++j) + verts[idx + j].color = color; } verts.unlock(); - mDevice->setStateBlockByDesc( desc ); - - mDevice->setVertexBuffer( verts ); + mDevice->setStateBlockByDesc(desc); + mDevice->setVertexBuffer(verts); mDevice->setupGenericShaders(); - mDevice->drawPrimitive( GFXTriangleStrip, 0, numPoints-2 ); - mDevice->drawPrimitive( GFXTriangleStrip, numPoints, numPoints - 2); - mDevice->drawPrimitive( GFXTriangleStrip, numPoints*2, numPoints * 2); + // Draw base cap + mDevice->drawPrimitive(GFXTriangleList, baseCenterIdx, numPoints - 2); + + // Draw top cap + mDevice->drawPrimitive(GFXTriangleList, topCenterIdx, numPoints - 2); + + // Draw sides (2 triangles per segment) + mDevice->drawPrimitive(GFXTriangleList, sideStartIdx, numPoints * 2); mDevice->popWorldMatrix(); } diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index e9ba531a9..8019f21bf 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -704,9 +704,6 @@ inline void GFXGLDevice::postDrawPrimitive(U32 primitiveCount) { mDeviceStatistics.mDrawCalls++; mDeviceStatistics.mPolyCount += primitiveCount; - - mVolatileVBs.clear(); - mVolatilePBs.clear(); } void GFXGLDevice::drawPrimitive( GFXPrimitiveType primType, U32 vertexStart, U32 primitiveCount ) From 78a553a74fa1faa7f3247a0f1bcd55907870fb93 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 31 Jul 2025 19:12:26 +0100 Subject: [PATCH 36/38] clamp rendering cover points to 50 units changes from az render cover points --- Engine/source/navigation/coverPoint.cpp | 15 ++++++++++++--- Engine/source/navigation/duDebugDrawTorque.cpp | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Engine/source/navigation/coverPoint.cpp b/Engine/source/navigation/coverPoint.cpp index 29eda49af..c599c1a5a 100644 --- a/Engine/source/navigation/coverPoint.cpp +++ b/Engine/source/navigation/coverPoint.cpp @@ -31,6 +31,7 @@ #include "gfx/gfxDrawUtil.h" #include "renderInstance/renderPassManager.h" #include "console/engineAPI.h" +#include "T3D/gameBase/gameConnection.h" extern bool gEditingMission; @@ -285,14 +286,22 @@ void CoverPoint::prepRenderImage(SceneRenderState *state) void CoverPoint::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat) { - initGFXResources(); + if (!state->isDiffusePass()) return; - if(overrideMat) + GameConnection* conn = GameConnection::getConnectionToServer(); + MatrixF camTrans; + conn->getControlCameraTransform(0, &camTrans); + + if ((getPosition() - camTrans.getPosition()).lenSquared() > 2500) return; //50 unit clamp + + if (overrideMat) return; + initGFXResources(); + if(smVertexBuffer[mSize].isNull()) return; - + PROFILE_SCOPE(CoverPoint_Render); // Set up a GFX debug event (this helps with debugging rendering events in external tools) diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index 570908c8f..8475095f5 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -427,6 +427,8 @@ void duDebugDrawTorque::clearCache() void duDebugDrawTorque::render(SceneRenderState* state) { + if (!state->isDiffusePass()) return; + const Frustum& frustum = state->getCameraFrustum(); for (U32 i = 0; i < mDrawCache.size(); ++i) From c2b347af7cadbcddfb22a1923d4bb0eca38492a8 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 1 Aug 2025 19:42:02 +0100 Subject: [PATCH 37/38] ai test classes protoplayer protocar --- .../managedData/managedDatablocks.tscript | 55 +++++++++++++++++++ .../scripts/server/protoCar.tscript | 47 ++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 Templates/BaseGame/game/data/Prototyping/scripts/server/protoCar.tscript diff --git a/Templates/BaseGame/game/data/Prototyping/scripts/managedData/managedDatablocks.tscript b/Templates/BaseGame/game/data/Prototyping/scripts/managedData/managedDatablocks.tscript index 60dbe9c8a..e73435b9b 100644 --- a/Templates/BaseGame/game/data/Prototyping/scripts/managedData/managedDatablocks.tscript +++ b/Templates/BaseGame/game/data/Prototyping/scripts/managedData/managedDatablocks.tscript @@ -4,3 +4,58 @@ datablock ItemData(PrototypeItemData) ShapeAsset = "Prototyping:TorusPrimitive_shape"; cameraMaxDist = "0.75"; }; + +datablock PlayerData( ProtoPlayer ) { + // Third person shape + ShapeAsset = "Prototyping:Playerbot_shape"; + controlMap = "playerKeyMap"; + AIControllerData = "aiPlayerControl"; +}; + +datablock WheeledVehicleTire(ProtoCarTire) +{ + // Tires act as springs and generate lateral and longitudinal + // forces to move the vehicle. These distortion/spring forces + // are what convert wheel angular velocity into forces that + // act on the rigid body. + shapeAsset = "Prototyping:carwheel_shape"; + + staticFriction = 1; + kineticFriction = 4.2; + + // Spring that generates lateral tire forces + lateralForce = 150000; + lateralDamping = 30000; + lateralRelaxation = 0.1; + + // Spring that generates longitudinal tire forces + longitudinalForce = 600; + longitudinalDamping = 1600; + longitudinalRelaxation = 0.1; +}; + +datablock WheeledVehicleSpring(ProtoCarSpring) +{ + // Wheel suspension properties + length = "0.6"; // Suspension travel + force = 3600; // Spring force + damping = 2800; // Spring damping + antiSwayForce = 300; // Lateral anti-sway force +}; + +datablock WheeledVehicleData(ProtoCar) +{ + category = "Vehicles"; + shapeAsset = "Prototyping:car_shape"; + + collisionMul = 0; + impactMul = 0; + controlMap = "vehicleKeyMap"; + AIControllerData = "aiCarControl"; + cameraMaxDist = "2.81993"; + ShapeFile = "data/Prototyping/shapes/Vehicles/car.dae"; + mass = "1000"; + originalAssetName = "ProtoCar"; + massCenter = "0 0.75 0"; + dragForce = "0.1"; +}; diff --git a/Templates/BaseGame/game/data/Prototyping/scripts/server/protoCar.tscript b/Templates/BaseGame/game/data/Prototyping/scripts/server/protoCar.tscript new file mode 100644 index 000000000..23d59229e --- /dev/null +++ b/Templates/BaseGame/game/data/Prototyping/scripts/server/protoCar.tscript @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- + +// This file contains script methods unique to the WheeledVehicle class. All +// other necessary methods are contained in "../server/scripts/vehicle.cs" in +// which the "generic" Vehicle class methods that are shared by all vehicles, +// (flying, hover, and wheeled) can be found. + +function ProtoCar::onAdd(%this, %obj) +{ + Parent::onAdd(%this, %obj); + + // Setup the car with some tires & springs + for (%i = %obj.getWheelCount() - 1; %i >= 0; %i--) + { + %obj.setWheelTire(%i, ProtoCarTire); + %obj.setWheelSpring(%i, ProtoCarSpring); + %obj.setWheelPowered(%i, false); + } + + // Steer with the front tires + %obj.setWheelSteering(0, 1); + %obj.setWheelSteering(1, 1); + + // Only power the two rear wheels... assuming there are only 4 wheels. + %obj.setWheelPowered(2, true); + %obj.setWheelPowered(3, true); +} \ No newline at end of file From f691188f74ea408be41163968771fe21365a98c4 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 3 Aug 2025 10:18:23 +0100 Subject: [PATCH 38/38] tools build requirements --- Engine/source/navigation/navMeshTool.cpp | 3 ++- Engine/source/navigation/navMeshTool.h | 4 ++-- Tools/CMake/modules/navigation.cmake | 9 ++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Engine/source/navigation/navMeshTool.cpp b/Engine/source/navigation/navMeshTool.cpp index 5fa05b663..758bc3626 100644 --- a/Engine/source/navigation/navMeshTool.cpp +++ b/Engine/source/navigation/navMeshTool.cpp @@ -1,7 +1,7 @@ #include "platform/platform.h" #include "navigation/navMeshTool.h" - +#ifdef TORQUE_TOOLS #include "util/undo.h" #include "math/mMath.h" #include "math/mathUtils.h" @@ -37,3 +37,4 @@ NavMeshTool::NavMeshTool() NavMeshTool::~NavMeshTool() { } +#endif diff --git a/Engine/source/navigation/navMeshTool.h b/Engine/source/navigation/navMeshTool.h index d6ad66aa2..ef6e9ad3c 100644 --- a/Engine/source/navigation/navMeshTool.h +++ b/Engine/source/navigation/navMeshTool.h @@ -1,7 +1,7 @@ #pragma once #ifndef _NAVMESH_TOOL_H_ #define _NAVMESH_TOOL_H_ - +#ifdef TORQUE_TOOLS #ifndef _SIMBASE_H_ #include "console/simBase.h" #endif @@ -54,5 +54,5 @@ public: virtual void onUndoAction() {} }; - +#endif #endif // !_NAVMESH_TOOL_H_ diff --git a/Tools/CMake/modules/navigation.cmake b/Tools/CMake/modules/navigation.cmake index 59a6b7325..a7a58e746 100644 --- a/Tools/CMake/modules/navigation.cmake +++ b/Tools/CMake/modules/navigation.cmake @@ -4,12 +4,15 @@ option(TORQUE_NAVIGATION "Enable Navigation module" ON) if(TORQUE_NAVIGATION) message("Enabling Navigation Module") - file(GLOB_RECURSE TORQUE_NAV_SOURCES "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.h" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/navMeshTools/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/navMeshTools/*.h") - set(TORQUE_SOURCE_FILES ${TORQUE_SOURCE_FILES} ${TORQUE_NAV_SOURCES}) + file(GLOB TORQUE_NAV_SOURCES "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.h") + if(TORQUE_TOOLS) + file(GLOB_RECURSE TORQUE_NAV_TOOLS "${CMAKE_SOURCE_DIR}/Engine/source/navigation/navMeshTools/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/navMeshTools/*.h") + endif() + set(TORQUE_SOURCE_FILES ${TORQUE_SOURCE_FILES} ${TORQUE_NAV_SOURCES} ${TORQUE_NAV_TOOLS}) set(TORQUE_LINK_LIBRARIES ${TORQUE_LINK_LIBRARIES} recast) set(TORQUE_COMPILE_DEFINITIONS ${TORQUE_COMPILE_DEFINITIONS} recast TORQUE_NAVIGATION_ENABLED) # Since recast lives elsewhere we need to ensure it is known to Torque when providing a link to it add_subdirectory("${TORQUE_LIB_ROOT_DIRECTORY}/recast" ${TORQUE_LIB_TARG_DIRECTORY}/recast EXCLUDE_FROM_ALL) - source_group(TREE "${CMAKE_SOURCE_DIR}/Engine/source/navigation/" PREFIX "Modules/NAVIGATION" FILES ${TORQUE_NAV_SOURCES}) + source_group(TREE "${CMAKE_SOURCE_DIR}/Engine/source/navigation/" PREFIX "Modules/NAVIGATION" FILES ${TORQUE_NAV_SOURCES} ${TORQUE_NAV_TOOLS}) endif(TORQUE_NAVIGATION)