From 64da8ab168b29ee8b57ab448dd59d9d7cc9ade23 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sat, 19 Jul 2025 11:45:18 -0500 Subject: [PATCH 01/42] non aiturret fixes bump maxheading up to it's proper 180 degree max bump netpipe for the turret rotations to the *controlling client* but not others to 11 bits per axis leave the to-others fidelity slim --- Engine/source/T3D/turret/turretShape.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Engine/source/T3D/turret/turretShape.cpp b/Engine/source/T3D/turret/turretShape.cpp index 08380da6a..ea32f975f 100644 --- a/Engine/source/T3D/turret/turretShape.cpp +++ b/Engine/source/T3D/turret/turretShape.cpp @@ -141,7 +141,7 @@ void TurretShapeData::initPersistFields() "@brief Should the turret allow only z rotations.\n\n" "True indicates that the turret may only be rotated on its z axis, just like the Item class. " "This keeps the turret always upright regardless of the surface it lands on.\n"); - addFieldV("maxHeading", TypeRangedF32, Offset(maxHeading, TurretShapeData), &CommonValidators::PosDegreeRangeQuarter, + addFieldV("maxHeading", TypeRangedF32, Offset(maxHeading, TurretShapeData), &CommonValidators::PosDegreeRangeHalf, "@brief Maximum number of degrees to rotate from center.\n\n" "A value of 180 or more degrees indicates the turret may rotate completely around.\n"); addFieldV("minPitch", TypeRangedF32, Offset(minPitch, TurretShapeData), &CommonValidators::PosDegreeRangeQuarter, @@ -1047,8 +1047,8 @@ void TurretShape::writePacketData(GameConnection *connection, BitStream *stream) // Update client regardless of status flags. Parent::writePacketData(connection, stream); - stream->writeSignedFloat(mRot.x / M_2PI_F, 7); - stream->writeSignedFloat(mRot.z / M_2PI_F, 7); + stream->writeSignedFloat(mRot.x / M_2PI_F, 11); + stream->writeSignedFloat(mRot.z / M_2PI_F, 11); } void TurretShape::readPacketData(GameConnection *connection, BitStream *stream) @@ -1056,8 +1056,8 @@ void TurretShape::readPacketData(GameConnection *connection, BitStream *stream) Parent::readPacketData(connection, stream); Point3F rot(0.0f, 0.0f, 0.0f); - rot.x = stream->readSignedFloat(7) * M_2PI_F; - rot.z = stream->readSignedFloat(7) * M_2PI_F; + rot.x = stream->readSignedFloat(11) * M_2PI_F; + rot.z = stream->readSignedFloat(11) * M_2PI_F; _setRotation(rot); mTurretDelta.rot = rot; From 26ebdd093bdf341bafd6530943fe16c633da2143 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 20 Jul 2025 16:10:27 +0100 Subject: [PATCH 02/42] 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 03/42] 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 04/42] 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 05/42] 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 06/42] 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 07/42] 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 08/42] 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 09/42] 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 10/42] 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 11/42] 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 12/42] 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 13/42] 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 14/42] 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 15/42] 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 16/42] 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 17/42] 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 18/42] 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 19/42] 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 20/42] 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 21/42] 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 22/42] 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 23/42] 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 24/42] 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 25/42] 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 26/42] 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 27/42] 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 28/42] 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 29/42] 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 30/42] 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 31/42] 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 32/42] 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 33/42] 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 34/42] 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 35/42] 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 36/42] 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 37/42] 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 38/42] 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 96134e12adb7a6f673f3b6a9a7f392535dd158da Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sat, 2 Aug 2025 15:04:43 -0500 Subject: [PATCH 39/42] adress odd water values review of watergrid value exposure and transform saver usage --- Engine/source/environment/waterBlock.cpp | 7 ++----- Engine/source/environment/waterBlock.h | 2 +- Engine/source/environment/waterObject.cpp | 3 +++ Engine/source/environment/waterPlane.cpp | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Engine/source/environment/waterBlock.cpp b/Engine/source/environment/waterBlock.cpp index 4ec20ebf9..3ee500494 100644 --- a/Engine/source/environment/waterBlock.cpp +++ b/Engine/source/environment/waterBlock.cpp @@ -531,12 +531,9 @@ void WaterBlock::initPersistFields() docsURL; addGroup( "WaterBlock" ); - addProtectedFieldV("gridSize", TypeRangedS32, Offset(mGridElementSize, WaterBlock), &setGridSizeProperty, &defaultProtectedGetFn, &CommonValidators::NaturalNumber, + addProtectedFieldV("gridSize", TypeRangedF32, Offset(mGridElementSize, WaterBlock), &setGridSizeProperty, &defaultProtectedGetFn, &CommonValidators::PositiveFloat, "Spacing between vertices in the WaterBlock mesh"); - - addProtectedFieldV("gridElementSize", TypeRangedS32, Offset(mGridElementSize, WaterBlock), &setGridSizeProperty, &defaultProtectedGetFn, &CommonValidators::NaturalNumber, - "Duplicate of gridElementSize for backwards compatility"); - + addProtectedFieldV("gridElementSize", TypeRangedF32, Offset(mGridElementSize, WaterBlock), &setGridSizeProperty, &defaultProtectedGetFn, &CommonValidators::PositiveFloat, "Duplicate of gridElementSize for backwards compatility"); Parent::initPersistFields(); } diff --git a/Engine/source/environment/waterBlock.h b/Engine/source/environment/waterBlock.h index fcb115841..555a8a8a6 100644 --- a/Engine/source/environment/waterBlock.h +++ b/Engine/source/environment/waterBlock.h @@ -82,7 +82,7 @@ private: GFXPrimitiveBufferHandle mRadialPrimBuff; // misc - U32 mGridElementSize; + F32 mGridElementSize; U32 mWidth; U32 mHeight; F32 mElapsedTime; diff --git a/Engine/source/environment/waterObject.cpp b/Engine/source/environment/waterObject.cpp index a98861d32..66113f43d 100644 --- a/Engine/source/environment/waterObject.cpp +++ b/Engine/source/environment/waterObject.cpp @@ -45,6 +45,7 @@ #include "T3D/sfx/sfx3DWorld.h" #include "sfx/sfxTypes.h" #include "console/typeValidators.h" +#include "gfx/gfxTransformSaver.h" GFXImplementVertexFormat( GFXWaterVertex ) { @@ -699,6 +700,8 @@ void WaterObject::prepRenderImage( SceneRenderState *state ) if( !state->isDiffusePass() ) return; + GFXTransformSaver saver; + // Setup scene transforms mMatrixSet->setSceneView(GFX->getWorldMatrix()); mMatrixSet->setSceneProjection(GFX->getProjectionMatrix()); diff --git a/Engine/source/environment/waterPlane.cpp b/Engine/source/environment/waterPlane.cpp index 7c9d9ec73..9e6f191a8 100644 --- a/Engine/source/environment/waterPlane.cpp +++ b/Engine/source/environment/waterPlane.cpp @@ -124,9 +124,7 @@ void WaterPlane::initPersistFields() addProtectedFieldV( "gridSize", TypeRangedS32, Offset( mGridSize, WaterPlane ), &protectedSetGridSize, &defaultProtectedGetFn, &CommonValidators::NaturalNumber, "Spacing between vertices in the WaterBlock mesh" ); - - addProtectedFieldV( "gridElementSize", TypeRangedS32, Offset( mGridElementSize, WaterPlane ), &protectedSetGridElementSize, &defaultProtectedGetFn, &CommonValidators::NaturalNumber, - "Duplicate of gridElementSize for backwards compatility"); + addProtectedFieldV("gridElementSize", TypeRangedF32, Offset(mGridElementSize, WaterPlane), &protectedSetGridElementSize, &defaultProtectedGetFn, &CommonValidators::PositiveFloat, "Duplicate of gridElementSize for backwards compatility"); endGroup( "WaterPlane" ); @@ -699,6 +697,8 @@ void WaterPlane::prepRenderImage( SceneRenderState *state ) if( !state->isDiffusePass() ) return; + GFXTransformSaver saver; + mBasicLighting = dStricmp( LIGHTMGR->getId(), "BLM" ) == 0; mUnderwater = isUnderwater( state->getCameraPosition() ); From f691188f74ea408be41163968771fe21365a98c4 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 3 Aug 2025 10:18:23 +0100 Subject: [PATCH 40/42] 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) From f3cad0d77ea075d3b1edfd354f0e08488046fb75 Mon Sep 17 00:00:00 2001 From: JeffR Date: Sun, 3 Aug 2025 12:03:02 -0500 Subject: [PATCH 41/42] Converts the ad-hoc design of the Material Editor to utilize the same inspector interface as most everything else does. - Overhauls the material editor to simplify and streamline the logic behind it since the inspector does most of the work - Tweak a few order positions of materialdefinition fields to work better - Sets AO, Rough and Metal channel fields to use an enum type for human readability - Updates the MaterialPreview gui control to work with assetIds - MatEd now supports setting of parent material to inherit from - Creating a new material now can prompt selecting an existing material to inherit from - Can now edit the mapTo value of a material in the matEd - New standalone Composite Texture Editor window for convering AO, Roughness and Metalness maps in a material to an ORMMap - Can also star the creation of a composite texture via RMB context menu in AB on an image asset - Moved logic of CubemapEditor from MatEd to it's own stuff - Made ImageAsset fields now be more clear when they have nothing assigned, and also have a clear button to empty the field's value so it's consistent across the board - Reorganized the layout of the gui and image files for the MatEd to be easier to navigate - MaterialEditor now overlays the EditorGUI instead of being forcefully embedded in it, allowing easy editing of the MatEd Gui via the Gui editor --- Engine/source/T3D/assets/ImageAsset.cpp | 42 +- .../source/T3D/assets/ImageAssetInspectors.h | 14 +- Engine/source/T3D/guiMaterialPreview.cpp | 47 +- Engine/source/T3D/guiMaterialPreview.h | 9 +- Engine/source/gui/editor/inspector/field.cpp | 9 + Engine/source/gui/editor/inspector/field.h | 2 + Engine/source/gui/editor/inspector/group.cpp | 25 +- .../source/materials/materialDefinition.cpp | 34 +- Engine/source/materials/materialDefinition.h | 10 + .../scripts/assetTypes/image.tscript | 45 + .../scripts/assetTypes/material.tscript | 107 + .../assetBrowser/scripts/editAsset.tscript | 5 + .../game/tools/gui/CubemapEditor.asset.taml | 2 +- .../game/tools/gui/compositeTextureEditor.gui | 437 ++ .../tools/gui/compositeTextureEditor.tscript | 192 + .../BaseGame/game/tools/gui/cubemapEditor.gui | 185 +- .../game/tools/gui/cubemapEditor.tscript | 367 ++ ...aterialEditorGui,EditorGuiGroup.asset.taml | 7 - .../gui/MaterialEditorGui.asset.taml | 5 + .../materialEditor/gui/MaterialEditorGui.gui | 490 ++ .../gui/cubeMapEd_cubePreview.max | Bin 253952 -> 0 bytes .../gui/cubemapEd_spherePreview.max | Bin 253952 -> 0 bytes .../gui/cubematEd_cylinderPreview.max | Bin 258048 -> 0 bytes .../gui/guiMaterialPreviewWindow.ed.gui | 456 -- .../gui/guiMaterialPropertiesWindow.ed.gui | 5290 ----------------- .../materialEditor/gui/matEd_cubePreview.max | Bin 253952 -> 0 bytes .../gui/matEd_pyramidPreview.max | Bin 253952 -> 0 bytes .../gui/matEd_spherePreview.max | Bin 249856 -> 0 bytes .../gui/matEd_torusKnotPreview.max | Bin 217088 -> 0 bytes .../materialEditor/gui/matEd_torusPreview.max | Bin 217088 -> 0 bytes .../{gui => images}/change-material-btn_d.png | Bin .../{gui => images}/change-material-btn_h.png | Bin .../{gui => images}/change-material-btn_n.png | Bin .../change_material_btn_d_image.asset.taml | 0 .../change_material_btn_h_image.asset.taml | 0 .../change_material_btn_n_image.asset.taml | 0 .../{gui => images}/cubeMapEd_previewMat.jpg | Bin .../cubeMapEd_previewMat_image.asset.taml | 0 .../{gui => images}/cube_xNeg.jpg | Bin .../cube_xNeg_image.asset.taml | 0 .../{gui => images}/cube_xPos.jpg | Bin .../cube_xPos_image.asset.taml | 0 .../{gui => images}/cube_yNeg.jpg | Bin .../cube_yNeg_image.asset.taml | 0 .../{gui => images}/cube_yPos.jpg | Bin .../cube_yPos_image.asset.taml | 0 .../{gui => images}/cube_zNeg.jpg | Bin .../cube_zNeg_image.asset.taml | 0 .../{gui => images}/cube_zPos.jpg | Bin .../cube_zPos_image.asset.taml | 0 .../{gui => images}/cubemapBtnBorder_d.png | Bin .../cubemapBtnBorder_d_image.asset.taml | 0 .../{gui => images}/cubemapBtnBorder_h.png | Bin .../cubemapBtnBorder_h_image.asset.taml | 0 .../{gui => images}/cubemapBtnBorder_i.png | Bin .../cubemapBtnBorder_i_image.asset.taml | 0 .../{gui => images}/cubemapBtnBorder_n.png | Bin .../cubemapBtnBorder_n_image.asset.taml | 0 .../{gui => images}/gridTiny2.PNG | Bin .../gui_gridTiny2_image.asset.taml | 0 .../{gui => images}/matEd_cylinderButt_d.jpg | Bin .../matEd_cylinderButt_d_image.asset.taml | 0 .../{gui => images}/matEd_cylinderButt_h.jpg | Bin .../matEd_cylinderButt_h_image.asset.taml | 0 .../{gui => images}/matEd_cylinderButt_n.jpg | Bin .../matEd_cylinderButt_n_image.asset.taml | 0 .../{gui => images}/matEd_cylinderPreview.max | Bin .../{gui => images}/matEd_mappedMat.jpg | Bin .../matEd_mappedMat_image.asset.taml | 0 .../{gui => images}/matEd_sphereButt_d.jpg | Bin .../matEd_sphereButt_d_image.asset.taml | 0 .../{gui => images}/matEd_sphereButt_h.jpg | Bin .../matEd_sphereButt_h_image.asset.taml | 0 .../{gui => images}/matEd_sphereButt_n.jpg | Bin .../matEd_sphereButt_n_image.asset.taml | 0 .../materialSelectorIcon_d.png | Bin .../materialSelectorIcon_d_image.asset.taml | 0 .../materialSelectorIcon_h.png | Bin .../materialSelectorIcon_h_image.asset.taml | 0 .../materialSelectorIcon_n.png | Bin .../materialSelectorIcon_n_image.asset.taml | 0 .../{gui => images}/mesh-selector-btn_d.png | Bin .../{gui => images}/mesh-selector-btn_h.png | Bin .../{gui => images}/mesh-selector-btn_n.png | Bin .../mesh_selector_btn_d_image.asset.taml | 0 .../mesh_selector_btn_h_image.asset.taml | 0 .../mesh_selector_btn_n_image.asset.taml | 0 .../{gui => images}/new-material_d.png | Bin .../{gui => images}/new-material_h.png | Bin .../{gui => images}/new-material_n.png | Bin .../new_material_d_image.asset.taml | 0 .../new_material_h_image.asset.taml | 0 .../new_material_n_image.asset.taml | 0 .../{gui => images}/screenFaded.png | Bin .../screenFaded_image.asset.taml | 0 .../{gui => images}/scrollBox.jpg | Bin .../scrollBox_image.asset.taml | 0 .../{gui => images}/unknownImage.png | Bin .../unknownImage_image.asset.taml | 0 .../{gui => images}/unsavedWarn.png | Bin .../unsavedWarn_image.asset.taml | 0 .../{gui => images}/wav-none_d.png | Bin .../{gui => images}/wav-none_h.png | Bin .../{gui => images}/wav-none_i.png | Bin .../{gui => images}/wav-none_n.png | Bin .../{gui => images}/wav-sine_d.png | Bin .../{gui => images}/wav-sine_h.png | Bin .../{gui => images}/wav-sine_i.png | Bin .../{gui => images}/wav-sine_n.png | Bin .../{gui => images}/wav-square_d.png | Bin .../{gui => images}/wav-square_h.png | Bin .../{gui => images}/wav-square_i.png | Bin .../{gui => images}/wav-square_n.png | Bin .../{gui => images}/wav-triangle_d.png | Bin .../{gui => images}/wav-triangle_h.png | Bin .../{gui => images}/wav-triangle_i.png | Bin .../{gui => images}/wav-triangle_n.png | Bin .../wav_none_d_image.asset.taml | 0 .../wav_none_h_image.asset.taml | 0 .../wav_none_i_image.asset.taml | 0 .../wav_none_n_image.asset.taml | 0 .../wav_sine_d_image.asset.taml | 0 .../wav_sine_h_image.asset.taml | 0 .../wav_sine_i_image.asset.taml | 0 .../wav_sine_n_image.asset.taml | 0 .../wav_square_d_image.asset.taml | 0 .../wav_square_h_image.asset.taml | 0 .../wav_square_i_image.asset.taml | 0 .../wav_square_n_image.asset.taml | 0 .../wav_triangle_d_image.asset.taml | 0 .../wav_triangle_h_image.asset.taml | 0 .../wav_triangle_i_image.asset.taml | 0 .../wav_triangle_n_image.asset.taml | 0 .../game/tools/materialEditor/main.tscript | 32 +- .../scripts/ORMSliderFieldType.tscript | 149 + .../bakeCompositeButtonFieldType.tscript | 31 + .../materialAnimationFieldTypes.tscript | 939 +++ .../scripts/materialEditor.ed.tscript | 1497 ++--- .../scripts/nuMaterialEditor.tscript | 249 + .../cubemaped_cubepreview.asset.taml | 0 .../{gui => shapes}/cubemaped_cubepreview.dts | Bin .../cubemaped_cubepreview.tscript | 0 .../cubemaped_cylinderpreview.asset.taml | 0 .../cubemaped_cylinderpreview.dts | Bin .../cubemaped_cylinderpreview.tscript | 0 .../cubemaped_spherepreview.asset.taml | 0 .../cubemaped_spherepreview.dts | Bin .../cubemaped_spherepreview.tscript | 0 .../{gui => shapes}/cubepreview.asset.taml | 0 .../{gui => shapes}/cubepreview.dts | Bin .../{gui => shapes}/cubepreview.tscript | 0 .../cylinderpreview.asset.taml | 0 .../{gui => shapes}/cylinderpreview.dts | Bin .../{gui => shapes}/cylinderpreview.tscript | 0 .../{gui => shapes}/pyramidpreview.asset.taml | 0 .../{gui => shapes}/pyramidpreview.dts | Bin .../{gui => shapes}/pyramidpreview.tscript | 0 .../{gui => shapes}/spherepreview.asset.taml | 0 .../{gui => shapes}/spherepreview.dts | Bin .../{gui => shapes}/spherepreview.tscript | 0 .../torusknotpreview.asset.taml | 0 .../{gui => shapes}/torusknotpreview.dts | Bin .../{gui => shapes}/torusknotpreview.tscript | 0 .../torusknowpreview.asset.taml | 0 .../{gui => shapes}/torusknowpreview.dts | 0 .../{gui => shapes}/torusknowpreview.tscript | 0 .../{gui => shapes}/toruspreview.asset.taml | 0 .../{gui => shapes}/toruspreview.dts | Bin .../{gui => shapes}/toruspreview.tscript | 0 .../scripts/shapeEditor.ed.tscript | 6 +- .../game/tools/worldEditor/main.tscript | 3 + .../worldEditor/scripts/EditorGui.ed.tscript | 2 +- .../scripts/editors/worldEditor.ed.tscript | 2 +- 173 files changed, 3713 insertions(+), 6977 deletions(-) create mode 100644 Templates/BaseGame/game/tools/gui/compositeTextureEditor.gui create mode 100644 Templates/BaseGame/game/tools/gui/compositeTextureEditor.tscript create mode 100644 Templates/BaseGame/game/tools/gui/cubemapEditor.tscript delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui,EditorGuiGroup.asset.taml create mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui.asset.taml create mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui.gui delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/cubeMapEd_cubePreview.max delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/cubemapEd_spherePreview.max delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/cubematEd_cylinderPreview.max delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/guiMaterialPreviewWindow.ed.gui delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/guiMaterialPropertiesWindow.ed.gui delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/matEd_cubePreview.max delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/matEd_pyramidPreview.max delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/matEd_spherePreview.max delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/matEd_torusKnotPreview.max delete mode 100644 Templates/BaseGame/game/tools/materialEditor/gui/matEd_torusPreview.max rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/change-material-btn_d.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/change-material-btn_h.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/change-material-btn_n.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/change_material_btn_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/change_material_btn_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/change_material_btn_n_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cubeMapEd_previewMat.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cubeMapEd_previewMat_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_xNeg.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_xNeg_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_xPos.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_xPos_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_yNeg.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_yNeg_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_yPos.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_yPos_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_zNeg.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_zNeg_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_zPos.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cube_zPos_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cubemapBtnBorder_d.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cubemapBtnBorder_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cubemapBtnBorder_h.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cubemapBtnBorder_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cubemapBtnBorder_i.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cubemapBtnBorder_i_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cubemapBtnBorder_n.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/cubemapBtnBorder_n_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/gridTiny2.PNG (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/gui_gridTiny2_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_cylinderButt_d.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_cylinderButt_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_cylinderButt_h.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_cylinderButt_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_cylinderButt_n.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_cylinderButt_n_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_cylinderPreview.max (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_mappedMat.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_mappedMat_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_sphereButt_d.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_sphereButt_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_sphereButt_h.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_sphereButt_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_sphereButt_n.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/matEd_sphereButt_n_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/materialSelectorIcon_d.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/materialSelectorIcon_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/materialSelectorIcon_h.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/materialSelectorIcon_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/materialSelectorIcon_n.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/materialSelectorIcon_n_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/mesh-selector-btn_d.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/mesh-selector-btn_h.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/mesh-selector-btn_n.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/mesh_selector_btn_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/mesh_selector_btn_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/mesh_selector_btn_n_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/new-material_d.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/new-material_h.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/new-material_n.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/new_material_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/new_material_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/new_material_n_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/screenFaded.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/screenFaded_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/scrollBox.jpg (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/scrollBox_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/unknownImage.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/unknownImage_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/unsavedWarn.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/unsavedWarn_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-none_d.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-none_h.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-none_i.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-none_n.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-sine_d.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-sine_h.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-sine_i.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-sine_n.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-square_d.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-square_h.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-square_i.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-square_n.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-triangle_d.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-triangle_h.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-triangle_i.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav-triangle_n.png (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_none_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_none_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_none_i_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_none_n_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_sine_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_sine_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_sine_i_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_sine_n_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_square_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_square_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_square_i_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_square_n_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_triangle_d_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_triangle_h_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_triangle_i_image.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => images}/wav_triangle_n_image.asset.taml (100%) create mode 100644 Templates/BaseGame/game/tools/materialEditor/scripts/ORMSliderFieldType.tscript create mode 100644 Templates/BaseGame/game/tools/materialEditor/scripts/bakeCompositeButtonFieldType.tscript create mode 100644 Templates/BaseGame/game/tools/materialEditor/scripts/materialAnimationFieldTypes.tscript create mode 100644 Templates/BaseGame/game/tools/materialEditor/scripts/nuMaterialEditor.tscript rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubemaped_cubepreview.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubemaped_cubepreview.dts (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubemaped_cubepreview.tscript (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubemaped_cylinderpreview.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubemaped_cylinderpreview.dts (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubemaped_cylinderpreview.tscript (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubemaped_spherepreview.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubemaped_spherepreview.dts (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubemaped_spherepreview.tscript (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubepreview.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubepreview.dts (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cubepreview.tscript (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cylinderpreview.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cylinderpreview.dts (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/cylinderpreview.tscript (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/pyramidpreview.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/pyramidpreview.dts (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/pyramidpreview.tscript (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/spherepreview.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/spherepreview.dts (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/spherepreview.tscript (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/torusknotpreview.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/torusknotpreview.dts (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/torusknotpreview.tscript (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/torusknowpreview.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/torusknowpreview.dts (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/torusknowpreview.tscript (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/toruspreview.asset.taml (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/toruspreview.dts (100%) rename Templates/BaseGame/game/tools/materialEditor/{gui => shapes}/toruspreview.tscript (100%) diff --git a/Engine/source/T3D/assets/ImageAsset.cpp b/Engine/source/T3D/assets/ImageAsset.cpp index bc831a2c8..dd50934d5 100644 --- a/Engine/source/T3D/assets/ImageAsset.cpp +++ b/Engine/source/T3D/assets/ImageAsset.cpp @@ -808,6 +808,8 @@ GuiControl* GuiInspectorTypeImageAssetPtr::constructEditControl() if (Sim::findObject("ToolsGuiTextEditProfile", toolEditProfile)) editTextCtrl->setControlProfile(toolEditProfile); + editTextCtrl->setPlaceholderText("(None)"); + GuiControlProfile* toolDefaultProfile = nullptr; Sim::findObject("ToolsGuiDefaultProfile", toolDefaultProfile); @@ -836,21 +838,25 @@ GuiControl* GuiInspectorTypeImageAssetPtr::constructEditControl() // // Create "Open in Editor" button - /*mEditButton = new GuiBitmapButtonCtrl(); + mEditButton = new GuiBitmapButtonCtrl(); + + if (mInspector->getInspectObject() != nullptr) + dSprintf(szBuffer, sizeof(szBuffer), "%d.apply(\"\");", getId()); + else + dSprintf(szBuffer, sizeof(szBuffer), "%s = \"\";", mVariableName); - dSprintf(szBuffer, sizeof(szBuffer), "AssetBrowser.editAsset(%d.getText());", retCtrl->getId()); mEditButton->setField("Command", szBuffer); - mEditButton->setText("Edit"); - mEditButton->setSizing(horizResizeLeft, vertResizeAspectTop); + mEditButton->setBitmap(StringTable->insert("ToolsModule:delete_n_image")); + mEditButton->setSizing(horizResizeRight, vertResizeAspectBottom); mEditButton->setDataField(StringTable->insert("Profile"), NULL, "ToolsGuiButtonProfile"); mEditButton->setDataField(StringTable->insert("tooltipprofile"), NULL, "GuiToolTipProfile"); mEditButton->setDataField(StringTable->insert("hovertime"), NULL, "1000"); - mEditButton->setDataField(StringTable->insert("tooltip"), NULL, "Open this asset in the Image Editor"); + mEditButton->setDataField(StringTable->insert("tooltip"), NULL, "Clear this ImageAsset"); mEditButton->registerObject(); - addObject(mEditButton);*/ + addObject(mEditButton); // mUseHeightOverride = true; @@ -875,9 +881,9 @@ bool GuiInspectorTypeImageAssetPtr::updateRects() mPreviewImage->resize(previewRect.point, previewRect.extent); S32 editPos = previewRect.point.x + previewRect.extent.x + 10; - mEdit->resize(Point2I(editPos, rowSize * 1.5), Point2I(fieldExtent.x - editPos - 5, rowSize)); + mEdit->resize(Point2I(editPos, rowSize * 1.5), Point2I(fieldExtent.x - editPos - 5 - rowSize, rowSize)); - //mEditButton->resize(Point2I(fieldExtent.x - 105, previewRect.point.y + previewRect.extent.y - rowSize), Point2I(100, rowSize)); + mEditButton->resize(Point2I(mEdit->getPosition().x + mEdit->getExtent().x, mEdit->getPosition().y), Point2I(rowSize, rowSize)); mBrowseButton->setHidden(true); @@ -975,7 +981,7 @@ void GuiInspectorTypeImageAssetPtr::updatePreviewImage() //if what we're working with isn't even a valid asset, don't present like we found a good one if (!AssetDatabase.isDeclaredAsset(previewImage)) { - mPreviewImage->_setBitmap(StringTable->EmptyString()); + mPreviewImage->_setBitmap(StringTable->insert("ToolsModule:unknownImage_image")); return; } @@ -1003,7 +1009,7 @@ void GuiInspectorTypeImageAssetPtr::setPreviewImage(StringTableEntry assetId) //if what we're working with isn't even a valid asset, don't present like we found a good one if (!AssetDatabase.isDeclaredAsset(assetId)) { - mPreviewImage->_setBitmap(StringTable->EmptyString()); + mPreviewImage->_setBitmap(StringTable->insert("ToolsModule:unknownImage_image")); return; } @@ -1025,4 +1031,20 @@ void GuiInspectorTypeImageAssetPtr::setPreviewImage(StringTableEntry assetId) if (mPreviewImage->getBitmapAsset().isNull()) mPreviewImage->_setBitmap(StringTable->insert("ToolsModule:genericAssetIcon_image")); } + +void GuiInspectorTypeImageAssetPtr::setCaption(StringTableEntry caption) +{ + mCaption = caption; + mLabel->setText(mCaption); +} + +DefineEngineMethod(GuiInspectorTypeImageAssetPtr, setCaption, void, (String newCaption), , "() - Sets the caption of the field.") +{ + object->setCaption(StringTable->insert(newCaption.c_str())); +} + +DefineEngineMethod(GuiInspectorTypeImageAssetPtr, setIsDeleteBtnVisible, void, (bool isVisible), (false), "() - Sets if the delete/clear button is visible for the field") +{ + object->setIsDeleteBtnVisible(isVisible); +} #endif diff --git a/Engine/source/T3D/assets/ImageAssetInspectors.h b/Engine/source/T3D/assets/ImageAssetInspectors.h index 36d4a9788..b43460293 100644 --- a/Engine/source/T3D/assets/ImageAssetInspectors.h +++ b/Engine/source/T3D/assets/ImageAssetInspectors.h @@ -16,7 +16,9 @@ public: GuiTextCtrl* mLabel = NULL; GuiBitmapButtonCtrl* mPreviewBorderButton = NULL; GuiBitmapCtrl* mPreviewImage = NULL; - GuiButtonCtrl* mEditButton = NULL; + GuiBitmapButtonCtrl* mEditButton = NULL; + + bool mIsDeleteButtonVisible; DECLARE_CONOBJECT(GuiInspectorTypeImageAssetPtr); static void consoleInit(); @@ -29,6 +31,16 @@ public: void updatePreviewImage(); void setPreviewImage(StringTableEntry assetId); + + /// Sets this control's caption text, usually set within setInspectorField, + /// this is exposed in case someone wants to override the normal caption. + void setCaption(StringTableEntry caption) override; + + void setIsDeleteBtnVisible(const bool& isVisible) + { + if (mEditButton) + mEditButton->setVisible(isVisible); + } }; class GuiInspectorTypeImageAssetId : public GuiInspectorTypeImageAssetPtr diff --git a/Engine/source/T3D/guiMaterialPreview.cpp b/Engine/source/T3D/guiMaterialPreview.cpp index a83569f6e..4efda009d 100644 --- a/Engine/source/T3D/guiMaterialPreview.cpp +++ b/Engine/source/T3D/guiMaterialPreview.cpp @@ -40,7 +40,8 @@ // GuiMaterialPreview GuiMaterialPreview::GuiMaterialPreview() : mMouseState(None), - mModel(NULL), + mModelInstance(NULL), + mMountedModelInstance(NULL), runThread(0), lastRenderTime(0), mLastMousePoint(0, 0), @@ -64,13 +65,13 @@ GuiMaterialPreview::GuiMaterialPreview() // By default don't do dynamic reflection // updates for this viewport. mReflectPriority = 0.0f; - mMountedModel = NULL; + mSkinTag = 0; } GuiMaterialPreview::~GuiMaterialPreview() { - SAFE_DELETE(mModel); + SAFE_DELETE(mModelInstance); SAFE_DELETE(mFakeSun); } @@ -258,30 +259,34 @@ void GuiMaterialPreview::onMiddleMouseDragged(const GuiEvent &event) } // This is used to set the model we want to view in the control object. -void GuiMaterialPreview::setObjectModel(const char* modelName) +void GuiMaterialPreview::setObjectModel(StringTableEntry modelName) { deleteModel(); - Resource model = ResourceManager::get().load(modelName); - if (! bool(model)) + _setModel(modelName); + + if (!getModel()) { - Con::warnf(avar("GuiMaterialPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName)); + Con::warnf("GuiMaterialPreview::setObjectModel - Failed to load model '%s'", modelName); return; } - mModel = new TSShapeInstance(model, true); - AssertFatal(mModel, avar("GuiMaterialPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName)); + mModelInstance = new TSShapeInstance(getModel(), true); + mModelInstance->resetMaterialList(); + mModelInstance->cloneMaterialList(); + + AssertFatal(mModelInstance, avar("GuiMaterialPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName)); // Initialize camera values: - mOrbitPos = mModel->getShape()->center; - mMinOrbitDist = mModel->getShape()->mRadius; + mOrbitPos = mModelInstance->getShape()->center; + mMinOrbitDist = mModelInstance->getShape()->mRadius; lastRenderTime = Platform::getVirtualMilliseconds(); } void GuiMaterialPreview::deleteModel() { - SAFE_DELETE(mModel); + SAFE_DELETE(mModelInstance); runThread = 0; } @@ -358,7 +363,7 @@ void GuiMaterialPreview::onMouseLeave(const GuiEvent & event) void GuiMaterialPreview::renderWorld(const RectI &updateRect) { // nothing to render, punt - if ( !mModel && !mMountedModel ) + if ( !mModelInstance && !mMountedModelInstance ) return; S32 time = Platform::getVirtualMilliseconds(); @@ -408,10 +413,10 @@ void GuiMaterialPreview::renderWorld(const RectI &updateRect) LIGHTMGR->unregisterAllLights(); LIGHTMGR->setSpecialLight( LightManager::slSunLightType, mFakeSun ); - if ( mModel ) - mModel->render( rdata ); + if ( mModelInstance ) + mModelInstance->render( rdata ); - if ( mMountedModel ) + if ( mMountedModelInstance ) { // render a weapon /* @@ -441,7 +446,7 @@ void GuiMaterialPreview::renderSunDirection() const { // Render four arrows aiming in the direction of the sun's light ColorI color = LinearColorF(mFakeSun->getColor()).toColorI(); - F32 length = mModel->getShape()->mBounds.len() * 0.8f; + F32 length = mModelInstance->getShape()->mBounds.len() * 0.8f; // Get the sun's vectors Point3F fwd = mFakeSun->getTransform().getForwardVector(); @@ -449,8 +454,8 @@ void GuiMaterialPreview::renderSunDirection() const Point3F right = mFakeSun->getTransform().getRightVector() * length / 8; // Calculate the start and end points of the first arrow (bottom left) - Point3F start = mModel->getShape()->center - fwd * length - up / 2 - right / 2; - Point3F end = mModel->getShape()->center - fwd * length / 3 - up / 2 - right / 2; + Point3F start = mModelInstance->getShape()->center - fwd * length - up / 2 - right / 2; + Point3F end = mModelInstance->getShape()->center - fwd * length / 3 - up / 2 - right / 2; GFXStateBlockDesc desc; desc.setZReadWrite(true, true); @@ -476,7 +481,7 @@ void GuiMaterialPreview::resetViewport() mCameraRot.set( mDegToRad(30.0f), 0, mDegToRad(-30.0f) ); mCameraPos.set(0.0f, 1.75f, 1.25f); mOrbitDist = 5.0f; - mOrbitPos = mModel->getShape()->center; + mOrbitPos = mModelInstance->getShape()->center; // Reset the viewport's lighting. GuiMaterialPreview::mFakeSun->setColor( LinearColorF( 1.0f, 1.0f, 1.0f ) ); @@ -498,7 +503,7 @@ DefineEngineMethod(GuiMaterialPreview, setModel, void, ( const char* shapeName ) "Sets the model to be displayed in this control\n\n" "@param shapeName Name of the model to display.\n") { - object->setObjectModel(shapeName); + object->setObjectModel(StringTable->insert(shapeName)); } DefineEngineMethod(GuiMaterialPreview, deleteModel, void, (),, diff --git a/Engine/source/T3D/guiMaterialPreview.h b/Engine/source/T3D/guiMaterialPreview.h index 3acafeb14..7c5d2ce09 100644 --- a/Engine/source/T3D/guiMaterialPreview.h +++ b/Engine/source/T3D/guiMaterialPreview.h @@ -28,6 +28,8 @@ #ifndef _GUIMATERIALPREVIEW_H_ #define _GUIMATERIALPREVIEW_H_ +#include "assets/ShapeAsset.h" + #include "gui/3d/guiTSControl.h" #include "ts/tsShapeInstance.h" @@ -50,8 +52,11 @@ protected: MouseState mMouseState; - TSShapeInstance* mModel; - TSShapeInstance* mMountedModel; + DECLARE_SHAPEASSET_REFACTOR(GuiMaterialPreview, Model) + DECLARE_SHAPEASSET_REFACTOR(GuiMaterialPreview, MountedModel) + + TSShapeInstance* mModelInstance; + TSShapeInstance* mMountedModelInstance; U32 mSkinTag; // For Camera Panning. diff --git a/Engine/source/gui/editor/inspector/field.cpp b/Engine/source/gui/editor/inspector/field.cpp index 748eddb88..a77c03601 100644 --- a/Engine/source/gui/editor/inspector/field.cpp +++ b/Engine/source/gui/editor/inspector/field.cpp @@ -1050,6 +1050,15 @@ DefineEngineMethod(GuiInspectorField, setCaption, void, (String newCaption),, "( object->setCaption(StringTable->insert(newCaption.c_str())); } +DefineEngineMethod(GuiInspectorField, getFieldName, const char*, (), , "() - Gets the fieldName of the field.") +{ + constexpr U32 bufSize = 128; + char* retBuffer = Con::getReturnBuffer(bufSize); + dSprintf(retBuffer, bufSize, "%s", object->getFieldName()); + + return retBuffer; +} + DefineEngineMethod(GuiInspectorField, setSpecialEditVariableName, void, (String newCaption), , "() - Sets the variable name for special edit fields.") { object->setSpecialEditVariableName(StringTable->insert(newCaption.c_str())); diff --git a/Engine/source/gui/editor/inspector/field.h b/Engine/source/gui/editor/inspector/field.h index a5057254d..30dd7442b 100644 --- a/Engine/source/gui/editor/inspector/field.h +++ b/Engine/source/gui/editor/inspector/field.h @@ -132,6 +132,8 @@ class GuiInspectorField : public GuiControl /// this is exposed in case someone wants to override the normal caption. virtual void setCaption( StringTableEntry caption ) { mCaption = caption; } + virtual StringTableEntry getCaption() { return mCaption; } + void setEditControl(GuiControl* editCtrl); void setHeightOverride(bool useOverride, U32 heightOverride); diff --git a/Engine/source/gui/editor/inspector/group.cpp b/Engine/source/gui/editor/inspector/group.cpp index 13953ead5..cd2a8000b 100644 --- a/Engine/source/gui/editor/inspector/group.cpp +++ b/Engine/source/gui/editor/inspector/group.cpp @@ -211,7 +211,7 @@ GuiInspectorField *GuiInspectorGroup::findField( const char *fieldName ) for( ; i != mChildren.end(); i++ ) { - if( (*i)->getFieldName() != NULL && dStricmp( (*i)->getFieldName(), fieldName ) == 0 ) + if( ((*i)->getFieldName() != NULL && dStricmp( (*i)->getFieldName(), fieldName ) == 0) || ((*i)->getCaption() != StringTable->EmptyString() && dStricmp((*i)->getCaption(), fieldName) == 0) ) return (*i); } @@ -834,3 +834,26 @@ DefineEngineMethod(GuiInspectorGroup, setForcedArrayIndex, void, (S32 arrayIndex { object->setForcedArrayIndex(arrayIndex); } + +DefineEngineMethod(GuiInspectorGroup, findField, S32, (const char* fieldName),, + "Finds an Inspector field in this group of a given name.\n" + "@param fieldName The name of the field to be found.\n" + "@return Field SimObjectId") +{ + if (dStrEqual(fieldName, "")) + return 0; + + GuiInspectorField* field = object->findField(StringTable->insert(fieldName)); + if (field == nullptr) + return 0; + + return field->getId(); +} + +DefineEngineMethod(GuiInspectorGroup, refresh, void, (), , + "Finds an Inspector field in this group of a given name.\n" + "@param fieldName The name of the field to be found.\n" + "@return Field SimObjectId") +{ + object->inspectGroup(); +} diff --git a/Engine/source/materials/materialDefinition.cpp b/Engine/source/materials/materialDefinition.cpp index cd175fd80..4c0e3a61f 100644 --- a/Engine/source/materials/materialDefinition.cpp +++ b/Engine/source/materials/materialDefinition.cpp @@ -101,6 +101,15 @@ ImplementEnumType(MaterialWaveType, { Material::Square, "Square", "Warps the material along a wave which transitions between two oppposite states. As a Square Wave, the transition is quick and sudden." }, EndImplementEnumType; +ImplementEnumType(MaterialSourceChannelType, + "When sampling from ORM Texture maps, dictates what channel to sample from for a given AO, Roughness or Metalness texture.\n" + "@ingroup GFX\n") +{ Material::RedChannel, "Red", "Red Channel" }, +{ Material::GreenChannel, "Green", "Green Channel" }, +{ Material::BlueChannel, "Blue", "Blue Channel" }, +{ Material::AlphaChannel, "Alpha", "Alpha Channel" }, +EndImplementEnumType; + bool Material::sAllowTextureTargetAssignment = false; GFXCubemap* Material::GetNormalizeCube() @@ -254,6 +263,11 @@ void Material::initPersistFields() addGroup("Light Influence Maps"); + addFieldV("roughness", TypeRangedF32, Offset(mRoughness, Material), &CommonValidators::F32_8BitPercent, MAX_STAGES, + "The degree of roughness when not using a ORMConfigMap."); + addFieldV("metalness", TypeRangedF32, Offset(mMetalness, Material), &CommonValidators::F32_8BitPercent, MAX_STAGES, + "The degree of Metalness when not using a ORMConfigMap."); + INITPERSISTFIELD_IMAGEASSET_ARRAY(ORMConfigMap, MAX_STAGES, Material, "AO|Roughness|metalness map"); addField("isSRGb", TypeBool, Offset(mIsSRGb, Material), MAX_STAGES, "Substance Designer Workaround."); @@ -261,21 +275,19 @@ void Material::initPersistFields() "Treat Roughness as Roughness"); INITPERSISTFIELD_IMAGEASSET_ARRAY(AOMap, MAX_STAGES, Material, "AOMap"); - INITPERSISTFIELD_IMAGEASSET_ARRAY(RoughMap, MAX_STAGES, Material, "RoughMap (also needs MetalMap)"); - INITPERSISTFIELD_IMAGEASSET_ARRAY(MetalMap, MAX_STAGES, Material, "MetalMap (also needs RoughMap)"); - INITPERSISTFIELD_IMAGEASSET_ARRAY(GlowMap, MAX_STAGES, Material, "GlowMap (needs Albedo)"); - - addFieldV("AOChan", TypeRangedS32, Offset(mAOChan, Material), &bmpChanRange, MAX_STAGES, + addField("AOChan", TYPEID< SourceChannelType >(), Offset(mAOChan, Material), MAX_STAGES, "The input channel AO maps use."); - addFieldV("roughness", TypeRangedF32, Offset(mRoughness, Material), &CommonValidators::F32_8BitPercent,MAX_STAGES, - "The degree of roughness when not using a ORMConfigMap."); - addFieldV("roughnessChan", TypeRangedS32, Offset(mRoughnessChan, Material), &bmpChanRange, MAX_STAGES, + + INITPERSISTFIELD_IMAGEASSET_ARRAY(RoughMap, MAX_STAGES, Material, "RoughMap (also needs MetalMap)"); + addField("roughnessChan", TYPEID< SourceChannelType >(), Offset(mRoughnessChan, Material), MAX_STAGES, "The input channel roughness maps use."); - addFieldV("metalness", TypeRangedF32, Offset(mMetalness, Material), &CommonValidators::F32_8BitPercent, MAX_STAGES, - "The degree of Metalness when not using a ORMConfigMap."); - addFieldV("metalChan", TypeRangedS32, Offset(mMetalChan, Material), &bmpChanRange, MAX_STAGES, + + INITPERSISTFIELD_IMAGEASSET_ARRAY(MetalMap, MAX_STAGES, Material, "MetalMap (also needs RoughMap)"); + addField("metalChan", TYPEID< SourceChannelType >(), Offset(mMetalChan, Material), MAX_STAGES, "The input channel metalness maps use."); + INITPERSISTFIELD_IMAGEASSET_ARRAY(GlowMap, MAX_STAGES, Material, "GlowMap (needs Albedo)"); + addFieldV("glowMul", TypeRangedF32, Offset(mGlowMul, Material),&glowMulRange, MAX_STAGES, "glow mask multiplier"); endGroup("Light Influence Maps"); diff --git a/Engine/source/materials/materialDefinition.h b/Engine/source/materials/materialDefinition.h index f0045fd10..cecbdc7b3 100644 --- a/Engine/source/materials/materialDefinition.h +++ b/Engine/source/materials/materialDefinition.h @@ -129,6 +129,14 @@ public: Square, }; + enum SourceChannelType + { + RedChannel = 0, + GreenChannel, + BlueChannel, + AlphaChannel + }; + class StageData { protected: @@ -425,9 +433,11 @@ private: typedef Material::AnimType MaterialAnimType; typedef Material::BlendOp MaterialBlendOp; typedef Material::WaveType MaterialWaveType; +typedef Material::SourceChannelType MaterialSourceChannelType; DefineBitfieldType(MaterialAnimType); DefineEnumType(MaterialBlendOp); DefineEnumType(MaterialWaveType); +DefineEnumType(MaterialSourceChannelType); #endif // _MATERIALDEFINITION_H_ diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/image.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/image.tscript index eb29fb365..2b832d0ba 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/image.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/image.tscript @@ -1,5 +1,39 @@ AssetBrowser::registerAssetType("ImageAsset", "Images"); +function ImageAsset::onCreateNew() +{ + %moduleName = $CurrentAssetBrowser.newAssetSettings.moduleName; + %modulePath = "data/" @ %moduleName; + + %assetName = $CurrentAssetBrowser.newAssetSettings.assetName; + + %assetPath = NewAssetTargetAddress.getText() @ "/"; + + %tamlpath = %assetPath @ %assetName @ ".asset.taml"; + %filePath = %assetPath @ %assetName @ ".png"; + + %asset = new ImageAsset() + { + AssetName = %assetName; + versionId = 1; + imageFile = %assetName @ ".png"; + }; + + TamlWrite(%asset, %tamlpath); + + %moduleDef = ModuleDatabase.findModule(%moduleName, 1); + AssetDatabase.addDeclaredAsset(%moduleDef, %tamlpath); + + %file = new FileObject(); + + if(%file.openForWrite(%filePath)) + { + %file.close(); + } + + return %tamlpath; +} + function ImageAsset::buildBrowserElement(%this, %previewData) { //%module = %this.dirHandler.getModuleFromAddress(makeRelativePath(filePath(%assetDef.getImagePath()))); @@ -96,6 +130,17 @@ function ImageAsset::generatePreviewImage(%this, %previewButton, %forceRegenerat function ImageAsset::onShowActionMenu(%this) { GenericAsset::onShowActionMenu(%this); + + %assetId = %this.getAssetId(); + + EditAssetPopup.setItemPosition("Create Composite Texture" TAB "" TAB "CompositeTextureEditor.buildComposite(\"" @ %assetId @ "\");", 4); + + EditAssetPopup.objectData = %assetId; + EditAssetPopup.objectType = AssetDatabase.getAssetType(%assetId); + + EditAssetPopup.reloadItems(); + + EditAssetPopup.showPopup(Canvas); } function ImageAsset::onEditProperties(%this) diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/material.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/material.tscript index 6795387f4..0e9952c7e 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/material.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/material.tscript @@ -1,5 +1,13 @@ AssetBrowser::registerAssetType("MaterialAsset", "Materials"); +function MaterialAsset::setupCreateNew() +{ + NewAssetPropertiesInspector.startGroup("Material"); + NewAssetPropertiesInspector.addField("InheritFrom", "Inherit From", "MaterialInheritList", "What material should this new one inherit from.", "", "", $CurrentAssetBrowser.newAssetSettings); + + NewAssetPropertiesInspector.endGroup(); +} + function MaterialAsset::onCreateNew() { %assetName = $CurrentAssetBrowser.newAssetSettings.assetName; @@ -18,6 +26,7 @@ function MaterialAsset::onCreateNew() materialDefinitionName = %assetName; new Material(%assetName) { + inheritFrom = $CurrentAssetBrowser.newAssetSettings.inheritFrom; mapTo = %assetName; }; }; @@ -227,3 +236,101 @@ function GuiInspectorTypeMaterialAssetPtr::onControlDropped( %this, %payload, %p EWorldEditor.isDirty = true; } + +function GuiInspectorGroup::buildMaterialInheritListField(%this, %fieldName, %fieldLabel, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) +{ + %extent = 18; + + %fieldCtrl = %this.createInspectorField(); + + %extent = %this.stack.getExtent(); + + %width = mRound(%extent/2); + %height = 20; + %inset = 10; + + %editControl = new GuiPopUpMenuCtrlEx() { + class = "materialInheritListField"; + maxPopupHeight = "200"; + sbUsesNAColor = "0"; + reverseTextList = "0"; + bitmapBounds = "16 16"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiPopUpMenuProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + Position = %fieldCtrl.edit.position; + Extent = %fieldCtrl.edit.extent; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + tooltip = ""; //%tooltip; + text = %fieldDefaultVal; + hovertime = "1000"; + ownerObject = %ownerObj; + fieldName = %fieldName; + callbackName = %callbackName; + allowTextSearch = true; + }; + + //set the field value + if(getSubStr(%this.fieldName, 0, 1) $= "$") + { + if(%fieldName $= "") + %editControl.setText(%fieldName); + } + else if(isObject(%ownerObj)) + { + //regular variable + %setCommand = %editControl @ ".setText(" @ %ownerObj @ "." @ %fieldName @ ");"; + eval(%setCommand); + } + + %listCount = getTokenCount(%fieldDataVals, ",;"); + + for(%i=0; %i < materialSet.getCount(); %i++) + { + %matObj = materialSet.getObject(%i); + %matName = %matObj.getName(); + + if(%matName !$= "") + %editControl.add(%matName); + } + + %fieldCtrl.setCaption(%fieldLabel); + %fieldCtrl.setEditControl(%editControl); + + //echo("GuiInspectorListField - " @ %editControl.getID() @ " - " @ %fieldName); + + %this.addInspectorField(%fieldCtrl); +} + +function materialInheritListField::onSelect( %this, %id, %text ) +{ + if(getSubStr(%this.fieldName, 0, 1) $= "$") + { + //ah, a global var, just do it straight, then + %setCommand = %this.fieldName @ " = \"" @ %text @ "\";"; + } + else if(isObject(%this.ownerObject)) + { + //regular variable + %setCommand = %this.ownerObject @ "." @ %this.fieldName @ " = \"" @ %text @ "\";"; + } + else if(%this.callbackName !$= "") + { + %setCommand = %this.callbackName @ "(\"" @ %this.fieldName @ "\",\"" @ %text @"\");"; + } + + echo("materialInheritListField::onSelect() - command: " @ %setCommand); + + eval(%setCommand); +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/editAsset.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/editAsset.tscript index 3ed2b7d2f..a572d06dd 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/editAsset.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/editAsset.tscript @@ -16,6 +16,11 @@ function AssetBrowser::editAsset(%this, %assetDef) } else { + if(AssetDatabase.isDeclaredAsset(%assetDef)) + { + %assetDef = AssetDatabase.acquireAsset(%assetDef); + } + if(isObject(%assetDef)) { %assetType = AssetDatabase.getAssetType(%assetDef.getAssetId()); diff --git a/Templates/BaseGame/game/tools/gui/CubemapEditor.asset.taml b/Templates/BaseGame/game/tools/gui/CubemapEditor.asset.taml index bf47a3b3e..a42dfa462 100644 --- a/Templates/BaseGame/game/tools/gui/CubemapEditor.asset.taml +++ b/Templates/BaseGame/game/tools/gui/CubemapEditor.asset.taml @@ -2,6 +2,6 @@ canSave="true" canSaveDynamicFields="true" AssetName="CubemapEditor" - scriptFile="@assetFile=cubemapEditor.gui" + scriptFile="@assetFile=cubemapEditor.tscript" GUIFile="@assetFile=cubemapEditor.gui" VersionId="1" /> diff --git a/Templates/BaseGame/game/tools/gui/compositeTextureEditor.gui b/Templates/BaseGame/game/tools/gui/compositeTextureEditor.gui new file mode 100644 index 000000000..2d11dfffe --- /dev/null +++ b/Templates/BaseGame/game/tools/gui/compositeTextureEditor.gui @@ -0,0 +1,437 @@ +//--- OBJECT WRITE BEGIN --- +$guiContent = new GuiControl(CompositeTextureEditor) { + extent = "1920 1080"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + isContainer = "1"; + canSaveDynamicFields = "1"; + enabled = "1"; + + new GuiWindowCtrl(CompositeTextureEditorWindow) { + Text = "Composite Texture Editor"; + resizeWidth = "0"; + resizeHeight = "0"; + canMinimize = "0"; + canMaximize = "0"; + closeCommand = "Canvas.popDialog(CompositeTextureEditor);"; + position = "721 416"; + extent = "478 248"; + minExtent = "478 248"; + horizSizing = "center"; + vertSizing = "center"; + profile = "ToolsGuiWindowProfile"; + tooltipProfile = "GuiToolTipProfile"; + + new GuiContainer(CompTextureEd_RedChan) { + position = "11 39"; + extent = "107 174"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + className = "CompositeTextureSlotContainer"; + + new GuiTextCtrl() { + Text = "Red Channel"; + position = "5 -2"; + extent = "100 15"; + horizSizing = "width"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiPopUpMenuCtrl() { + position = "1 20"; + extent = "106 23"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "InputMode"; + className = "CompositeTextureChannelMode"; + }; + new GuiContainer() { + position = "0 41"; + extent = "107 125"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueContainer"; + + new GuiTextEditCtrl() { + position = "0 8"; + extent = "28 20"; + profile = "ToolsGuiTextEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueTxtEdit"; + className = "CompositeTextureRawValueEdit"; + }; + new GuiSliderCtrl() { + position = "38 9"; + extent = "68 19"; + profile = "GuiSliderProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueSlider"; + className = "CompositeTextureRawValueSlider"; + Command = "$thisControl.onDragComplete();"; + range = 0 SPC 1; + ticks = 0.01; + }; + }; + new GuiContainer() { + position = "0 41"; + extent = "107 125"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "TextureContainer"; + + new GuiBitmapCtrl() { + BitmapAsset = "ToolsModule:unknownImage_image"; + BitmapFile = "tools/materialEditor/images/unknownImage.png"; + position = "21 9"; + horizSizing = "center"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "Bitmap"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:cubemapBtnBorder_n_image"; + position = "21 9"; + extent = "64 64"; + horizSizing = "center"; + profile = "ToolsGuiDefaultProfile"; + command = "$CompTexSourceChannel = 0;AssetBrowser.showDialog(\"ImageAsset\", \"CompositeTextureEditor.setSourceTex\");"; + tooltipProfile = "ToolsGuiDefaultProfile"; + ToolTip = "Set the source of the texture for this channel to sample from when baking a composite."; + }; + new GuiTextCtrl() { + Text = "Source Channel"; + position = "5 85"; + extent = "100 10"; + horizSizing = "width"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiPopUpMenuCtrl() { + position = "1 97"; + extent = "106 23"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "sourceChannel"; + className = "CompositeTextureTexSrcChannel"; + }; + }; + }; + new GuiContainer(CompTextureEd_GreenChan) { + position = "129 39"; + extent = "107 174"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + className = "CompositeTextureSlotContainer"; + + new GuiTextCtrl() { + Text = "Green Channel"; + position = "5 -2"; + extent = "100 15"; + horizSizing = "width"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiPopUpMenuCtrl() { + position = "1 20"; + extent = "106 23"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "InputMode"; + className = "CompositeTextureChannelMode"; + }; + new GuiContainer() { + position = "0 41"; + extent = "107 125"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueContainer"; + + new GuiTextEditCtrl() { + position = "0 8"; + extent = "28 20"; + profile = "ToolsGuiTextEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueTxtEdit"; + className = "CompositeTextureRawValueEdit"; + }; + new GuiSliderCtrl() { + position = "38 9"; + extent = "68 19"; + profile = "GuiSliderProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueSlider"; + className = "CompositeTextureRawValueSlider"; + Command = "$thisControl.onDragComplete();"; + range = 0 SPC 1; + ticks = 0.01; + }; + }; + new GuiContainer() { + position = "0 41"; + extent = "107 125"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "TextureContainer"; + + new GuiBitmapCtrl() { + BitmapAsset = "ToolsModule:unknownImage_image"; + BitmapFile = "tools/materialEditor/images/unknownImage.png"; + position = "21 9"; + horizSizing = "center"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "Bitmap"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:cubemapBtnBorder_n_image"; + position = "21 9"; + extent = "64 64"; + horizSizing = "center"; + profile = "ToolsGuiDefaultProfile"; + command = "$CompTexSourceChannel = 1;AssetBrowser.showDialog(\"ImageAsset\", \"CompositeTextureEditor.setSourceTex\");"; + tooltipProfile = "ToolsGuiDefaultProfile"; + ToolTip = "Set the source of the texture for this channel to sample from when baking a composite."; + }; + new GuiTextCtrl() { + Text = "Source Channel"; + position = "5 85"; + extent = "100 10"; + horizSizing = "width"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiPopUpMenuCtrl() { + position = "1 97"; + extent = "106 23"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "sourceChannel"; + className = "CompositeTextureTexSrcChannel"; + }; + }; + }; + new GuiContainer(CompTextureEd_BlueChan) { + position = "247 39"; + extent = "107 174"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + className = "CompositeTextureSlotContainer"; + + new GuiTextCtrl() { + Text = "Blue Channel"; + position = "5 -2"; + extent = "100 15"; + horizSizing = "width"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiPopUpMenuCtrl() { + position = "1 20"; + extent = "106 23"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "InputMode"; + className = "CompositeTextureChannelMode"; + }; + new GuiContainer() { + position = "0 41"; + extent = "107 125"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueContainer"; + + new GuiTextEditCtrl() { + position = "0 8"; + extent = "28 20"; + profile = "ToolsGuiTextEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueTxtEdit"; + className = "CompositeTextureRawValueEdit"; + }; + new GuiSliderCtrl() { + position = "38 9"; + extent = "68 19"; + profile = "GuiSliderProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueSlider"; + className = "CompositeTextureRawValueSlider"; + Command = "$thisControl.onDragComplete();"; + range = 0 SPC 1; + ticks = 0.01; + }; + }; + new GuiContainer() { + position = "0 41"; + extent = "107 125"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "TextureContainer"; + + new GuiBitmapCtrl() { + BitmapAsset = "ToolsModule:unknownImage_image"; + BitmapFile = "tools/materialEditor/images/unknownImage.png"; + position = "21 9"; + horizSizing = "center"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "Bitmap"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:cubemapBtnBorder_n_image"; + position = "21 9"; + extent = "64 64"; + horizSizing = "center"; + profile = "ToolsGuiDefaultProfile"; + command = "$CompTexSourceChannel = 2;AssetBrowser.showDialog(\"ImageAsset\", \"CompositeTextureEditor.setSourceTex\");"; + tooltipProfile = "ToolsGuiDefaultProfile"; + ToolTip = "Set the source of the texture for this channel to sample from when baking a composite."; + }; + new GuiTextCtrl() { + Text = "Source Channel"; + position = "5 85"; + extent = "100 10"; + horizSizing = "width"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiPopUpMenuCtrl() { + position = "1 97"; + extent = "106 23"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "sourceChannel"; + className = "CompositeTextureTexSrcChannel"; + }; + }; + }; + new GuiContainer(CompTextureEd_AlphaChan) { + position = "361 39"; + extent = "107 174"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + className = "CompositeTextureSlotContainer"; + + new GuiTextCtrl() { + Text = "Alpha Channel"; + position = "5 -2"; + extent = "100 15"; + horizSizing = "width"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiPopUpMenuCtrl() { + position = "1 20"; + extent = "106 23"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "InputMode"; + className = "CompositeTextureChannelMode"; + }; + new GuiContainer() { + position = "0 41"; + extent = "107 125"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueContainer"; + + new GuiTextEditCtrl() { + position = "0 8"; + extent = "28 20"; + profile = "ToolsGuiTextEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueTxtEdit"; + className = "CompositeTextureRawValueEdit"; + }; + new GuiSliderCtrl() { + position = "38 9"; + extent = "68 19"; + profile = "GuiSliderProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "RawValueSlider"; + className = "CompositeTextureRawValueSlider"; + Command = "$thisControl.onDragComplete();"; + range = 0 SPC 1; + ticks = 0.01; + }; + }; + new GuiContainer() { + position = "0 41"; + extent = "107 125"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "TextureContainer"; + + new GuiBitmapCtrl() { + BitmapAsset = "ToolsModule:unknownImage_image"; + BitmapFile = "tools/materialEditor/images/unknownImage.png"; + position = "21 9"; + horizSizing = "center"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "Bitmap"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:cubemapBtnBorder_n_image"; + position = "21 9"; + extent = "64 64"; + horizSizing = "center"; + profile = "ToolsGuiDefaultProfile"; + command = "$CompTexSourceChannel = 3;AssetBrowser.showDialog(\"ImageAsset\", \"CompositeTextureEditor.setSourceTex\");"; + tooltipProfile = "ToolsGuiDefaultProfile"; + ToolTip = "Set the source of the texture for this channel to sample from when baking a composite."; + }; + new GuiTextCtrl() { + Text = "Source Channel"; + position = "5 85"; + extent = "100 10"; + horizSizing = "width"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiPopUpMenuCtrl() { + position = "1 97"; + extent = "106 23"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "sourceChannel"; + className = "CompositeTextureTexSrcChannel"; + }; + }; + }; + new GuiButtonCtrl() { + Text = "Save"; + position = "339 216"; + extent = "74 24"; + profile = "ToolsGuiButtonProfile"; + command = "CompositeTextureEditor.saveComposite();"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiButtonCtrl() { + Text = "Cancel"; + position = "417 216"; + extent = "52 24"; + profile = "ToolsGuiButtonProfile"; + command = "Canvas.popDialog(CompositeTextureEditor);"; + tooltipProfile = "GuiToolTipProfile"; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/BaseGame/game/tools/gui/compositeTextureEditor.tscript b/Templates/BaseGame/game/tools/gui/compositeTextureEditor.tscript new file mode 100644 index 000000000..4fd31b5aa --- /dev/null +++ b/Templates/BaseGame/game/tools/gui/compositeTextureEditor.tscript @@ -0,0 +1,192 @@ +function CompositeTextureEditor::buildComposite(%this, %sourceTex0, %sourceTex1, %sourceTex2, %sourceTex3, %sourceChan0, %sourceChan1, %sourceChan2, %sourceChan3, %callback) +{ + Canvas.pushDialog(%this); + + CompTextureEd_RedChan.setChannelData(%sourceTex0, %sourceChan0); + CompTextureEd_GreenChan.setChannelData(%sourceTex1, %sourceChan1); + CompTextureEd_BlueChan.setChannelData(%sourceTex2, %sourceChan2); + CompTextureEd_AlphaChan.setChannelData(%sourceTex3, %sourceChan3); + + %this.callbackFunc = %callback; +} + +function CompositeTextureSlotContainer::onWake(%this) +{ + %this-->InputMode.clear(); + %this-->InputMode.add("Raw Value"); + %this-->InputMode.add("Texture"); + %this-->InputMode.setSelected(1); + %this-->InputMode.active = false; + + %this-->sourceChannel.clear(); + %this-->sourceChannel.add("Red"); + %this-->sourceChannel.add("Green"); + %this-->sourceChannel.add("Blue"); + %this-->sourceChannel.add("Alpha"); +} + +function CompositeTextureSlotContainer::setChannelData(%this, %sourceValue, %sourceChannel) +{ + //for now, hard-force Texture mode + %this-->InputMode.setSelected(1); + + if(AssetDatabase.isDeclaredAsset(%sourceValue)) + { + %this-->InputMode.setSelected(1); + + %this-->bitmap.setBitmap(%sourceValue); + + if(%sourceChannel $= "") + %sourceChannel = "Red"; + + %this-->sourceChannel.setText(%sourceChannel); + } + /*else + { + %this-->InputMode.setSelected(0); + + %value = 0; + if(%sourceValue !$= "" && (isFloat(%sourceValue) || isFloat(%sourceValue))) + { + %value = %sourceValue; + } + + %this-->RawValueTxtEdit.setText(%value); + %this-->RawValueSlider.setValue(%value); + }*/ +} + +function CompositeTextureSlotContainer::getChannelData(%this) +{ + return ""; +} + +function CompositeTextureChannelMode::onSelect(%this, %id, %text) +{ + %isRawValueMode = %text $= "Raw Value"; + %this.getParent()-->RawValueContainer.setVisible(%isRawValueMode); + %this.getParent()-->TextureContainer.setVisible(!%isRawValueMode); +} + +function CompositeTextureEditor::setSourceTex(%this, %assetId) +{ + if($CompTexSourceChannel $= "") + $CompTexSourceChannel = 0; + + %channelContainer = CompositeTextureEditorWindow.getObject($CompTexSourceChannel); + %channelContainer-->bitmap.setBitmap(%assetId); +} + +function CompositeTextureEditor::saveComposite(%this) +{ + %cubemap = CubemapEditor.currentCubemap; + + %aoMap = CompTextureEd_RedChan-->Bitmap.getBitmap(); + %roughMap = CompTextureEd_GreenChan-->Bitmap.getBitmap(); + %metalMap = CompTextureEd_BlueChan-->Bitmap.getBitmap(); + + if(%aoMap $= "ToolsModule:unknownImage_image") + %aoMap = ""; + if(%roughMap $= "ToolsModule:unknownImage_image") + %roughMap = ""; + if(%metalMap $= "ToolsModule:unknownImage_image") + %metalMap = ""; + + if(%aoMap $= "" && %roughMap $= "" && %metalMap $= "") + { + toolsMessageBoxOK("Error", "Saving a composite map requires at least one channel slot to have source data!"); + return; + } + + AssetBrowser.setupCreateNewAsset("ImageAsset", AssetBrowser.selectedModule, "CompositeEditorDoSaveComposite"); +} + +function CompositeEditorDoSaveComposite(%assetId) +{ + %assetDef = AssetDatabase.acquireAsset(%assetId); + if(!isObject(%assetDef)) + { + toolsMessageBoxOK("Error", "We somehow failed to successfully create a new ImageAsset!"); + return; + } + + %targetFilePath = %assetDef.getImagePath(); + + %aoMapAsset = CompTextureEd_RedChan-->Bitmap.getBitmap(); + %roughMapAsset = CompTextureEd_GreenChan-->Bitmap.getBitmap(); + %metalMapAsset = CompTextureEd_BlueChan-->Bitmap.getBitmap(); + + + if(%aoMapAsset $= "ToolsModule:unknownImage_image") + { + %aoMap = ""; + } + else + { + %aoAssetDef = AssetDatabase.acquireAsset(%aoMapAsset); + %aoMap = %aoAssetDef.getImagePath(); + } + if(%roughMapAsset $= "ToolsModule:unknownImage_image") + { + %roughMap = ""; + } + else + { + %roughAssetDef = AssetDatabase.acquireAsset(%roughMapAsset); + %roughMap = %roughAssetDef.getImagePath(); + } + if(%metalMapAsset $= "ToolsModule:unknownImage_image") + { + %metalMap = ""; + } + else + { + %metalAssetDef = AssetDatabase.acquireAsset(%metalMapAsset); + %metalMap = %roughAssetDef.getImagePath(); + } + + if(%aoMap $= "" && %roughMap $= "" && %metalMap $= "") + { + toolsMessageBoxOK("Error", "Attempted to create a composite texture but all three source textures are blank or invalid!"); + return; + } + + %redChanSource = CompTextureEd_RedChan-->sourceChannel; + %greenChanSource = CompTextureEd_GreenChan-->sourceChannel; + %blueChanSource = CompTextureEd_BlueChan-->sourceChannel; + + %aoChan = %redChanSource.findText(%redChanSource.getText()); + %aoChan = %aoChan == -1 ? 0 : %aoChan; + + %roughChan = %greenChanSource.findText(%greenChanSource.getText()); + %roughChan = %roughChan == -1 ? 1 : %roughChan; + + %metalChan = %blueChanSource.findText(%blueChanSource.getText()); + %metalChan = %metalChan == -1 ? 2 : %metalChan; + + %channelKey = %aoChan SPC %roughChan SPC %metalChan SPC 3; + error("Storing: \"" @ %aoMap @"\" \""@ %roughMap @"\" \""@ %metalMap @"\" \""@ %channelKey @"\" \""@ %targetFilePath @"\""); + saveCompositeTexture(%aoMap, %roughMap, %metalMap, "", %channelKey, %targetFilePath); + + %assetDef.refreshAsset(); + + %command = CompositeTextureEditor.callbackFunc @ "(\"" @ %assetId @ "\");"; + if(CompositeTextureEditor.callbackFunc !$= "") + { + eval(%command); + CompositeTextureEditor.callbackFunc = ""; + } + + Canvas.popDialog(CompositeTextureEditor); +} + +function CompositeTextureRawValueEdit::onReturn(%this) +{ + %this.getParent()-->RawValueSlider.setValue(%this.getText()); +} + +function CompositeTextureRawValueSlider::onDragComplete(%this) +{ + %value = %this.getValue(); + %this.getParent()-->RawValueTxtEdit.setText(%value); +} diff --git a/Templates/BaseGame/game/tools/gui/cubemapEditor.gui b/Templates/BaseGame/game/tools/gui/cubemapEditor.gui index d238feae5..b15c3a20b 100644 --- a/Templates/BaseGame/game/tools/gui/cubemapEditor.gui +++ b/Templates/BaseGame/game/tools/gui/cubemapEditor.gui @@ -39,7 +39,7 @@ $guiContent = new GuiControl(CubemapEditor) { canMaximize = "0"; minSize = "50 50"; EdgeSnap = "1"; - closeCommand = "MaterialEditorGui.hideCubemapEditor(true);"; + closeCommand = "CubemapEditor.hideCubemapEditor(true);"; text = "Cubemap Editor"; new GuiTextCtrl(){ @@ -69,23 +69,23 @@ $guiContent = new GuiControl(CubemapEditor) { AnchorRight = "0"; text = "myCubemap 1"; maxLength = "1024"; - AltCommand = "MaterialEditorGui.editCubemapName($ThisControl.getText());"; + AltCommand = "CubemapEditor.editCubemapName($ThisControl.getText());"; }; new GuiButtonCtrl(){ Profile = "ToolsGuiButtonProfile"; position = "339 216"; Extent = "74 24"; text = "Select"; - command = "MaterialEditorGui.selectCubemap();"; // needs hookup use selected cubemap + command = "CubemapEditor.selectCubemap();"; // needs hookup use selected cubemap }; new GuiButtonCtrl(){ Profile = "ToolsGuiButtonProfile"; position = "417 216"; Extent = "52 24"; text = "Cancel"; - command = "MaterialEditorGui.hideCubemapEditor(true);"; // needs hookup Cancel + command = "CubemapEditor.hideCubemapEditor(true);"; // needs hookup Cancel }; - new GuiScrollCtrl(matEd_cubemapEd_availableCubemapScroller) { + new GuiScrollCtrl(cubemapEd_availableCubemapScroller) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "1"; @@ -106,7 +106,7 @@ $guiContent = new GuiControl(CubemapEditor) { constantThumbHeight = "0"; childMargin = "0 0"; - new GuiListBoxCtrl(matEd_cubemapEd_availableCubemapList) { + new GuiListBoxCtrl(cubemapEd_availableCubemapList) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -130,7 +130,7 @@ $guiContent = new GuiControl(CubemapEditor) { text = "Cubemaps"; }; // ------------------------------ Right X Positive ------------------------------------ - new GuiBitmapCtrl(matEd_cubemapEd_XPos) { + new GuiBitmapCtrl(cubemapEd_XPos) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -146,12 +146,12 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:unknownImage_image"; wrap = "0"; }; - new GuiTextCtrl(matEd_cubeMapEd_xPosTxt) { + new GuiTextCtrl(cubeMapEd_xPosTxt) { position = "304 110"; Extent = "57 10"; text = "+ X Right"; }; - new GuiBitmapButtonCtrl(matEd_cubeMapEd_updateXPOSImg) { + new GuiBitmapButtonCtrl(cubeMapEd_updateXPOSImg) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -163,7 +163,7 @@ $guiContent = new GuiControl(CubemapEditor) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "MaterialEditorGui.editCubemapImage(\"0\", $ThisControl.bitmap );"; + Command = "CubemapEditor.editCubemapImage(\"0\", $ThisControl.bitmap );"; tooltipprofile = "ToolsGuiDefaultProfile"; ToolTip = "When using Static Cubemaps, select your CubeMap by clicking here."; hovertime = "1000"; @@ -173,7 +173,7 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:cubemapBtnBorder_n_image"; }; // ------------------------------ X Negitive ------------------------------------ - new GuiBitmapCtrl(matEd_cubemapEd_XNeg) { + new GuiBitmapCtrl(cubemapEd_XNeg) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -189,12 +189,12 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:unknownImage_image"; wrap = "0"; }; - new GuiTextCtrl(matEd_cubeMapEd_xNegTxt) { + new GuiTextCtrl(cubeMapEd_xNegTxt) { position = "171 110"; Extent = "57 10"; text = "- X Left"; }; - new GuiBitmapButtonCtrl(matEd_cubeMapEd_updateXNEGImg) { + new GuiBitmapButtonCtrl(cubeMapEd_updateXNEGImg) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -206,7 +206,7 @@ $guiContent = new GuiControl(CubemapEditor) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "MaterialEditorGui.editCubemapImage(\"1\", $ThisControl.bitmap );"; + Command = "CubemapEditor.editCubemapImage(\"1\", $ThisControl.bitmap );"; tooltipprofile = "ToolsGuiDefaultProfile"; ToolTip = "When using Static Cubemaps, select your CubeMap by clicking here."; hovertime = "1000"; @@ -216,7 +216,7 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:cubemapBtnBorder_n_image"; }; // ------------------------------ Y Positive ------------------------------------ - new GuiBitmapCtrl(matEd_cubemapEd_YPos) { + new GuiBitmapCtrl(cubemapEd_YPos) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -232,12 +232,12 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:unknownImage_image"; wrap = "0"; }; - new GuiTextCtrl(matEd_cubeMapEd_yPosTxt) { + new GuiTextCtrl(cubeMapEd_yPosTxt) { position = "237 175"; Extent = "57 10"; text = "+ Y Front"; }; - new GuiBitmapButtonCtrl(matEd_cubeMapEd_updateYPOSImg) { + new GuiBitmapButtonCtrl(cubeMapEd_updateYPOSImg) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -249,7 +249,7 @@ $guiContent = new GuiControl(CubemapEditor) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "MaterialEditorGui.editCubemapImage(\"3\", $ThisControl.bitmap );"; + Command = "CubemapEditor.editCubemapImage(\"3\", $ThisControl.bitmap );"; tooltipprofile = "ToolsGuiDefaultProfile"; ToolTip = "When using Static Cubemaps, select your CubeMap by clicking here."; hovertime = "1000"; @@ -259,7 +259,7 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:cubemapBtnBorder_n_image"; }; // ------------------------------ Y Negitive ------------------------------------ - new GuiBitmapCtrl(matEd_cubemapEd_YNeG) { + new GuiBitmapCtrl(cubemapEd_YNeG) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -275,12 +275,12 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:unknownImage_image"; wrap = "0"; }; - new GuiTextCtrl(matEd_cubeMapEd_yNegTxt) { + new GuiTextCtrl(cubeMapEd_yNegTxt) { position = "237 44"; Extent = "57 10"; text = "- Y Back"; }; - new GuiBitmapButtonCtrl(matEd_cubeMapEd_updateYNegImg) { + new GuiBitmapButtonCtrl(cubeMapEd_updateYNegImg) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -292,7 +292,7 @@ $guiContent = new GuiControl(CubemapEditor) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "MaterialEditorGui.editCubemapImage(\"2\", $ThisControl.bitmap );"; + Command = "CubemapEditor.editCubemapImage(\"2\", $ThisControl.bitmap );"; tooltipprofile = "ToolsGuiDefaultProfile"; ToolTip = "When using Static Cubemaps, select your CubeMap by clicking here."; hovertime = "1000"; @@ -302,7 +302,7 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:cubemapBtnBorder_n_image"; }; // ------------------------------ Z Positive ------------------------------------ - new GuiBitmapCtrl(matEd_cubemapEd_ZPos) { + new GuiBitmapCtrl(cubemapEd_ZPos) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -318,12 +318,12 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:unknownImage_image"; wrap = "0"; }; - new GuiTextCtrl(matEd_cubeMapEd_zPosTxt) { + new GuiTextCtrl(cubeMapEd_zPosTxt) { position = "237 110"; Extent = "57 10"; text = "+ Z Top"; }; - new GuiBitmapButtonCtrl(matEd_cubeMapEd_updateZPosImg) { + new GuiBitmapButtonCtrl(cubeMapEd_updateZPosImg) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -335,7 +335,7 @@ $guiContent = new GuiControl(CubemapEditor) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "MaterialEditorGui.editCubemapImage(\"4\", $ThisControl.bitmap );"; + Command = "CubemapEditor.editCubemapImage(\"4\", $ThisControl.bitmap );"; tooltipprofile = "ToolsGuiDefaultProfile"; ToolTip = "When using Static Cubemaps, select your CubeMap by clicking here."; hovertime = "1000"; @@ -345,7 +345,7 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:cubemapBtnBorder_n_image"; }; // ------------------------------ Z Negitive ------------------------------------ - new GuiBitmapCtrl(matEd_cubemapEd_ZNeg) { + new GuiBitmapCtrl(cubemapEd_ZNeg) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -361,12 +361,12 @@ $guiContent = new GuiControl(CubemapEditor) { bitmapAsset = "ToolsModule:unknownImage_image"; wrap = "0"; }; - new GuiTextCtrl(matEd_cubeMapEd_zNegTxt) { + new GuiTextCtrl(cubeMapEd_zNegTxt) { position = "369 110"; Extent = "57 10"; text = "- Z Bottom"; }; - new GuiBitmapButtonCtrl(matEd_cubeMapEd_updateZNegImg) { + new GuiBitmapButtonCtrl(cubeMapEd_updateZNegImg) { canSaveDynamicFields = "0"; Enabled = "1"; isContainer = "0"; @@ -378,7 +378,7 @@ $guiContent = new GuiControl(CubemapEditor) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "MaterialEditorGui.editCubemapImage(\"5\", $ThisControl.bitmap );"; + Command = "CubemapEditor.editCubemapImage(\"5\", $ThisControl.bitmap );"; tooltipprofile = "ToolsGuiDefaultProfile"; ToolTip = "When using Static Cubemaps, select your CubeMap by clicking here."; hovertime = "1000"; @@ -401,7 +401,7 @@ $guiContent = new GuiControl(CubemapEditor) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "matEd_addCubemapWindow.setVisible(1);"; // -------------- Needs Hookup Create New Cubemap + Command = "addCubemapWindow.setVisible(1);"; // -------------- Needs Hookup Create New Cubemap hovertime = "1000"; tooltip = "Create New Cubemap"; bitmapAsset = "ToolsModule:new_n_image"; @@ -421,7 +421,7 @@ $guiContent = new GuiControl(CubemapEditor) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "MaterialEditorGui.showDeleteCubemapDialog();"; // -------------- Needs Hookup Delete Cubemap + Command = "CubemapEditor.showDeleteCubemapDialog();"; // -------------- Needs Hookup Delete Cubemap hovertime = "1000"; tooltip = "Delete Cubemap"; bitmapAsset = "ToolsModule:delete_n_image"; @@ -442,7 +442,7 @@ $guiContent = new GuiControl(CubemapEditor) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "MaterialEditorGui.showSaveCubemapDialog();"; // -------------- Needs Hookup Save Cubemap + Command = "CubemapEditor.showSaveCubemapDialog();"; // -------------- Needs Hookup Save Cubemap hovertime = "1000"; tooltip = "Save Cubemap"; bitmapAsset = "ToolsModule:save_icon_n_image"; @@ -451,4 +451,123 @@ $guiContent = new GuiControl(CubemapEditor) { useMouseEvents = "0"; }; }; + + new GuiWindowCtrl(addCubemapWindow) { + canSaveDynamicFields = "0"; + isContainer = "1"; + Profile = "ToolsGuiWindowProfile"; + HorizSizing = "center"; + VertSizing = "center"; + position = "362 333"; + Extent = "300 99"; + MinExtent = "48 92"; + canSave = "1"; + Visible = "0"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + resizeWidth = "1"; + resizeHeight = "1"; + canMove = "1"; + canClose = "0"; + canMinimize = "0"; + canMaximize = "0"; + minSize = "50 50"; + EdgeSnap = "1"; + text = "Create Cubemap"; + + new GuiTextEditCtrl() { + canSaveDynamicFields = "0"; + internalName = "cubemapName"; + isContainer = "0"; + Profile = "ToolsGuiTextEditProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "96 35"; + Extent = "196 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + maxLength = "1024"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + AltCommand = ""; + passwordMask = "*"; + }; + new GuiTextCtrl() { + canSaveDynamicFields = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "12 36"; + Extent = "77 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + maxLength = "1024"; + text = "Cubemap Name"; + }; + new GuiButtonCtrl() { + canSaveDynamicFields = "0"; + isContainer = "0"; + Profile = "ToolsGuiButtonProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "96 68"; + Extent = "126 22"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + groupNum = "-1"; + buttonType = "PushButton"; + useMouseEvents = "0"; + text = "Create"; + Command = "CubemapEditor.addCubemap( addCubemapWindow-->cubemapName.getText() );addCubemapWindow.setVisible(0);"; + }; + new GuiButtonCtrl() { + canSaveDynamicFields = "0"; + isContainer = "0"; + Profile = "ToolsGuiButtonProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "228 68"; + Extent = "64 22"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + groupNum = "-1"; + buttonType = "PushButton"; + useMouseEvents = "0"; + text = "Cancel"; + Command = "addCubemapWindow.setVisible(0);"; + }; + }; }; diff --git a/Templates/BaseGame/game/tools/gui/cubemapEditor.tscript b/Templates/BaseGame/game/tools/gui/cubemapEditor.tscript new file mode 100644 index 000000000..c4e12efc5 --- /dev/null +++ b/Templates/BaseGame/game/tools/gui/cubemapEditor.tscript @@ -0,0 +1,367 @@ +function CubemapEditor::onWake(%this) +{ + cubemapEd_availableCubemapList.clear(); +} + +function CubemapEditor::cancelCubemap(%this) +{ + %cubemap = CubemapEditor.currentCubemap; + + %idx = cubemapEd_availableCubemapList.findItemText( %cubemap.getName() ); + cubemapEd_availableCubemapList.setItemText( %idx, notDirtyCubemap.originalName ); + %cubemap.setName( notDirtyCubemap.originalName ); + + CubemapEditor.copyCubemaps( notDirtyCubemap, %cubemap ); + CubemapEditor.copyCubemaps( notDirtyCubemap, matEdCubeMapPreviewMat); + + %cubemap.updateFaces(); + matEdCubeMapPreviewMat.updateFaces(); +} + +function CubemapEditor::showCubemapEditor(%this) +{ + if (cubemapEditor.isVisible()) + return; + + CubemapEditor.currentCubemap = ""; + + cubemapEditor.setVisible(1); + new PersistenceManager(cubemapEdPerMan); + CubemapEditor.setCubemapNotDirty(); + + for( %i = 0; %i < RootGroup.getCount(); %i++ ) + { + if( RootGroup.getObject(%i).getClassName()!$= "CubemapData" ) + continue; + + for( %k = 0; %k < UnlistedCubemaps.count(); %k++ ) + { + %unlistedFound = 0; + if( UnlistedCubemaps.getValue(%k) $= RootGroup.getObject(%i).name ) + { + %unlistedFound = 1; + break; + } + } + + if( %unlistedFound ) + continue; + + cubemapEd_availableCubemapList.addItem( RootGroup.getObject(%i).name ); + } + + singleton CubemapData(notDirtyCubemap); + + // if there was no cubemap, pick the first, select, and bail, these are going to take + // care of themselves in the selected function + if( !isObject( CubemapEditor.currentMaterial.cubemap ) ) + { + if( cubemapEd_availableCubemapList.getItemCount() > 0 ) + { + cubemapEd_availableCubemapList.setSelected(0, true); + return; + } + else + { + // if there are no cubemaps, then create one, select, and bail + %cubemap = CubemapEditor.createNewCubemap(); + cubemapEd_availableCubemapList.addItem( %cubemap.name ); + cubemapEd_availableCubemapList.setSelected(0, true); + return; + } + } + + // do not directly change activeMat! + CubemapEditor.currentCubemap = CubemapEditor.currentMaterial.cubemap.getId(); + %cubemap = CubemapEditor.currentCubemap; + + notDirtyCubemap.originalName = %cubemap.getName(); + CubemapEditor.copyCubemaps( %cubemap, notDirtyCubemap); + CubemapEditor.copyCubemaps( %cubemap, matEdCubeMapPreviewMat); + CubemapEditor.syncCubemap( %cubemap ); +} + +function CubemapEditor::hideCubemapEditor(%this,%cancel) +{ + if(%cancel) + CubemapEditor.cancelCubemap(); + + cubemapEd_availableCubemapList.clearItems(); + cubemapEdPerMan.delete(); + cubemapEditor.setVisible(0); +} + +// create category and update current material if there is one +function CubemapEditor::addCubemap( %this,%cubemapName ) +{ + if( %cubemapName $= "" ) + { + toolsMessageBoxOK( "Error", "Can not create a cubemap without a valid name."); + return; + } + + for(%i = 0; %i < RootGroup.getCount(); %i++) + { + if( %cubemapName $= RootGroup.getObject(%i).getName() ) + { + toolsMessageBoxOK( "Error", "There is already an object with the same name."); + return; + } + } + + // Create and select a new cubemap + %cubemap = CubemapEditor.createNewCubemap( %cubemapName ); + %idx = cubemapEd_availableCubemapList.addItem( %cubemap.name ); + cubemapEd_availableCubemapList.setSelected( %idx, true ); + + // material category text field to blank + addCubemapWindow-->cubemapName.setText(""); +} + +function CubemapEditor::createNewCubemap( %this, %cubemap ) +{ + if( %cubemap $= "" ) + { + for(%i = 0; ; %i++) + { + %cubemap = "newCubemap_" @ %i; + if( !isObject(%cubemap) ) + break; + } + } + + new CubemapData(%cubemap) + { + cubeMapFaceAsset[0] = "ToolsModule:cube_xNeg_image"; + cubeMapFaceAsset[1] = "ToolsModule:cube_xPos_image"; + cubeMapFaceAsset[2] = "ToolsModule:cube_zNeg_image"; + cubeMapFaceAsset[3] = "ToolsModule:cube_zPos_image"; + cubeMapFaceAsset[4] = "ToolsModule:cube_yNeg_image"; + cubeMapFaceAsset[5] = "ToolsModule:cube_yPos_image"; + + parentGroup = RootGroup; + }; + + cubemapEdPerMan.setDirty( %cubemap, "art/materials." @ $TorqueScriptFileExtension ); + cubemapEdPerMan.saveDirty(); + + return %cubemap; +} + +function CubemapEditor::setCubemapDirty(%this) +{ + %propertyText = "Create Cubemap *"; + cubemapEditor.text = %propertyText; + cubemapEditor.dirty = true; + cubemapEditor-->saveCubemap.setActive(true); + + %cubemap = CubemapEditor.currentCubemap; + + // materials created in the materail selector are given that as its filename, so we run another check + if( CubemapEditor.isMatEditorMaterial( %cubemap ) ) + cubemapEdPerMan.setDirty(%cubemap, "art/materials." @ $TorqueScriptFileExtension); + else + cubemapEdPerMan.setDirty(%cubemap); +} + +function CubemapEditor::setCubemapNotDirty(%this) +{ + %propertyText= strreplace("Create Cubemap" , "*" , ""); + cubemapEditor.text = %propertyText; + cubemapEditor.dirty = false; + cubemapEditor-->saveCubemap.setActive(false); + + %cubemap = CubemapEditor.currentCubemap; + cubemapEdPerMan.removeDirty(%cubemap); +} + +function CubemapEditor::showDeleteCubemapDialog(%this) +{ + %idx = cubemapEd_availableCubemapList.getSelectedItem(); + %cubemap = cubemapEd_availableCubemapList.getItemText( %idx ); + %cubemap = %cubemap.getId(); + + if( %cubemap == -1 || !isObject(%cubemap) ) + return; + + if( isObject( %cubemap ) ) + { + toolsMessageBoxYesNoCancel("Delete Cubemap?", + "Are you sure you want to delete

" @ %cubemap.getName() @ "

Cubemap deletion won't take affect until the engine is quit.", + "CubemapEditor.deleteCubemap( " @ %cubemap @ ", " @ %idx @ " );", + "", + "" ); + } +} + +function CubemapEditor::deleteCubemap( %this, %cubemap, %idx ) +{ + cubemapEd_availableCubemapList.deleteItem( %idx ); + + UnlistedCubemaps.add( "unlistedCubemaps", %cubemap.getName() ); + + if( !CubemapEditor.isMatEditorMaterial( %cubemap ) ) + { + cubemapEdPerMan.removeDirty( %cubemap ); + cubemapEdPerMan.removeObjectFromFile( %cubemap ); + } + + if( cubemapEd_availableCubemapList.getItemCount() > 0 ) + { + cubemapEd_availableCubemapList.setSelected(0, true); + } + else + { + // if there are no cubemaps, then create one, select, and bail + %cubemap = CubemapEditor.createNewCubemap(); + cubemapEd_availableCubemapList.addItem( %cubemap.getName() ); + cubemapEd_availableCubemapList.setSelected(0, true); + } +} + +function cubemapEd_availableCubemapList::onSelect( %this, %id, %cubemap ) +{ + %cubemap = %cubemap.getId(); + if( CubemapEditor.currentCubemap $= %cubemap ) + return; + + if( cubemapEditor.dirty ) + { + %savedCubemap = CubemapEditor.currentCubemap; + toolsMessageBoxYesNoCancel("Save Existing Cubemap?", + "Do you want to save changes to

" @ %savedCubemap.getName(), + "CubemapEditor.saveCubemap(" @ true @ ");", + "CubemapEditor.saveCubemapDialogDontSave(" @ %cubemap @ ");", + "CubemapEditor.saveCubemapDialogCancel();" ); + } + else + CubemapEditor.changeCubemap( %cubemap ); +} + +function CubemapEditor::showSaveCubemapDialog( %this ) +{ + %cubemap = CubemapEditor.currentCubemap; + if( !isObject(%cubemap) ) + return; + + toolsMessageBoxYesNoCancel("Save Cubemap?", + "Do you want to save changes to

" @ %cubemap.getName(), + "CubemapEditor.saveCubemap( " @ %cubemap @ " );", + "", + "" ); +} + +function CubemapEditor::saveCubemap( %this, %cubemap ) +{ + notDirtyCubemap.originalName = %cubemap.getName(); + CubemapEditor.copyCubemaps( %cubemap, notDirtyCubemap ); + CubemapEditor.copyCubemaps( %cubemap, matEdCubeMapPreviewMat); + + cubemapEdPerMan.saveDirty(); + + CubemapEditor.setCubemapNotDirty(); +} + +function CubemapEditor::saveCubemapDialogDontSave( %this, %newCubemap) +{ + //deal with old cubemap first + %oldCubemap = CubemapEditor.currentCubemap; + + %idx = cubemapEd_availableCubemapList.findItemText( %oldCubemap.getName() ); + cubemapEd_availableCubemapList.setItemText( %idx, notDirtyCubemap.originalName ); + %oldCubemap.setName( notDirtyCubemap.originalName ); + + CubemapEditor.copyCubemaps( notDirtyCubemap, %oldCubemap); + CubemapEditor.copyCubemaps( notDirtyCubemap, matEdCubeMapPreviewMat); + CubemapEditor.syncCubemap( %oldCubemap ); + + CubemapEditor.changeCubemap( %newCubemap ); +} + +function CubemapEditor::saveCubemapDialogCancel( %this ) +{ + %cubemap = CubemapEditor.currentCubemap; + %idx = cubemapEd_availableCubemapList.findItemText( %cubemap.getName() ); + cubemapEd_availableCubemapList.clearSelection(); + cubemapEd_availableCubemapList.setSelected( %idx, true ); +} + +function CubemapEditor::changeCubemap( %this, %cubemap ) +{ + CubemapEditor.setCubemapNotDirty(); + CubemapEditor.currentCubemap = %cubemap; + + notDirtyCubemap.originalName = %cubemap.getName(); + CubemapEditor.copyCubemaps( %cubemap, notDirtyCubemap); + CubemapEditor.copyCubemaps( %cubemap, matEdCubeMapPreviewMat); + CubemapEditor.syncCubemap( %cubemap ); +} + +function CubemapEditor::editCubemapImage( %this, %face ) +{ + CubemapEditor.setCubemapDirty(); + + %cubemap = CubemapEditor.currentCubemap; + %bitmap = CubemapEditor.openFile("texture"); + if( %bitmap !$= "" && %bitmap !$= "tools/materialEditor/gui/cubemapBtnBorder" ) + { + %cubemap.cubeFace[%face] = %bitmap; + CubemapEditor.copyCubemaps( %cubemap, matEdCubeMapPreviewMat); + CubemapEditor.syncCubemap( %cubemap ); + } +} + +function CubemapEditor::editCubemapName( %this, %newName ) +{ + CubemapEditor.setCubemapDirty(); + + %cubemap = CubemapEditor.currentCubemap; + + %idx = cubemapEd_availableCubemapList.findItemText( %cubemap.getName() ); + cubemapEd_availableCubemapList.setItemText( %idx, %newName ); + %cubemap.setName(%newName); + + CubemapEditor.syncCubemap( %cubemap ); +} + +function CubemapEditor::syncCubemap( %this, %cubemap ) +{ + %xpos = CubemapEditor.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[0]); + if( %xpos !$= "" ) + cubemapEd_XPos.setBitmap( %xpos ); + + %xneg = CubemapEditor.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[1]); + if( %xneg !$= "" ) + cubemapEd_XNeg.setBitmap( %xneg ); + + %yneg = CubemapEditor.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[2]); + if( %yneg !$= "" ) + cubemapEd_YNeG.setBitmap( %yneg ); + + %ypos = CubemapEditor.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[3]); + if( %ypos !$= "" ) + cubemapEd_YPos.setBitmap( %ypos ); + + %zpos = CubemapEditor.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[4]); + if( %zpos !$= "" ) + cubemapEd_ZPos.setBitmap( %zpos ); + + %zneg = CubemapEditor.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[5]); + if( %zneg !$= "" ) + cubemapEd_ZNeg.setBitmap( %zneg ); + + cubemapEd_activeCubemapNameTxt.setText(%cubemap.getName()); + + %cubemap.updateFaces(); + matEdCubeMapPreviewMat.updateFaces(); +} + +function CubemapEditor::copyCubemaps( %this, %copyFrom, %copyTo) +{ + %copyTo.cubeFace[0] = %copyFrom.cubeFace[0]; + %copyTo.cubeFace[1] = %copyFrom.cubeFace[1]; + %copyTo.cubeFace[2] = %copyFrom.cubeFace[2]; + %copyTo.cubeFace[3] = %copyFrom.cubeFace[3]; + %copyTo.cubeFace[4] = %copyFrom.cubeFace[4]; + %copyTo.cubeFace[5] = %copyFrom.cubeFace[5]; +} diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui,EditorGuiGroup.asset.taml b/Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui,EditorGuiGroup.asset.taml deleted file mode 100644 index bb5d886b7..000000000 --- a/Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui,EditorGuiGroup.asset.taml +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui.asset.taml b/Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui.asset.taml new file mode 100644 index 000000000..9689f07ea --- /dev/null +++ b/Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui.asset.taml @@ -0,0 +1,5 @@ + diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui.gui b/Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui.gui new file mode 100644 index 000000000..d6dc29812 --- /dev/null +++ b/Templates/BaseGame/game/tools/materialEditor/gui/MaterialEditorGui.gui @@ -0,0 +1,490 @@ +//--- OBJECT WRITE BEGIN --- +$guiContent = new GuiControl(MaterialEditorGui) { + extent = "2560 1440"; + profile = "GuiNonModalDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + isContainer = "1"; + canSaveDynamicFields = "1"; + currentLayer = "0"; + currentMaterialAsset = "Prototyping:FloorGray"; + currentMeshMode = "EditorShape"; + currentMode = "Material"; + docked = "1"; + fileSpec = "Torque Material Files (materials.tscript)|materials.tscript|All Files (*.*)|*.*|"; + livePreview = "1"; + materialDirty = "0"; + modelFormats = "DTS Files (*.dts)|*.dts"; + originalAssetName = "MaterialEditorGui"; + originalName = "FloorGray"; + panelHidden = "0"; + preventUndo = "1"; + resizing = "0"; + textureFormats = "Image Files (*.png, *.jpg, *.dds, *.bmp, *.gif, *.jng. *.tga)|*.png;*.jpg;*.dds;*.bmp;*.gif;*.jng;*.tga|All Files (*.*)|*.*|"; + + new GuiWindowCollapseCtrl(MaterialEditorGuiWindow) { + Text = " :: Material Editor"; + canMove = "0"; + canClose = "0"; + canMinimize = "0"; + canMaximize = "0"; + canCollapse = "0"; + margin = "5 5 5 5"; + position = "2168 39"; + extent = "392 1303"; + minExtent = "300 150"; + horizSizing = "windowRelative"; + vertSizing = "windowRelative"; + profile = "ToolsGuiWindowProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "InspectorWindow"; + + new GuiBitmapButtonCtrl(MaterialEditorGui_showBtn) { + BitmapAsset = "ToolsModule:panel_show_n_image"; + position = "4 1"; + extent = "18 18"; + minExtent = "8 8"; + profile = "ToolsGuiButtonProfile"; + visible = "0"; + command = "MaterialEditorGui.showSidePanel();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Show Sidepanel"; + hidden = "1"; + }; + new GuiBitmapButtonCtrl(MaterialEditorGui_UnDockBtn) { + BitmapAsset = "ToolsModule:panel_undock_n_image"; + position = "369 0"; + extent = "18 18"; + minExtent = "8 8"; + horizSizing = "left"; + profile = "ToolsGuiButtonProfile"; + command = "MaterialEditorGui.releaseSidePanel();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Detach Sidepanel"; + }; + new GuiBitmapButtonCtrl(MaterialEditorGui_hideBtn) { + BitmapAsset = "ToolsModule:panel_hide_n_image"; + position = "351 0"; + extent = "18 18"; + minExtent = "8 8"; + horizSizing = "left"; + profile = "ToolsGuiButtonProfile"; + command = "MaterialEditorGui.hideSidePanel();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Hide Sidepanel"; + }; + new GuiBitmapButtonCtrl(MaterialEditorGui_DockBtn) { + BitmapAsset = "ToolsModule:panel_dock_n_image"; + position = "369 0"; + extent = "18 18"; + minExtent = "8 8"; + horizSizing = "left"; + profile = "ToolsGuiButtonProfile"; + visible = "0"; + command = "MaterialEditorGui.dockSidePanel();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Dock Sidepanel"; + hidden = "1"; + }; + new GuiSplitContainer() { + orientation = "Horizontal"; + splitPoint = "182 354"; + fixedSize = "692"; + position = "3 24"; + extent = "387 1269"; + horizSizing = "width"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + + new GuiPanel() { + docking = "Client"; + extent = "387 352"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "Panel1"; + + new GuiContainer(matEd_previewPanel) { + docking = "Client"; + margin = "24 1 3 3"; + position = "3 24"; + extent = "381 327"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + + new GuiSwatchButtonCtrl(matEd_previewBackground) { + color = "0 0 0 0.2"; + position = "-1 -1"; + extent = "383 329"; + horizSizing = "width"; + vertSizing = "height"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiContainer() { + position = "-1 -1"; + extent = "383 329"; + horizSizing = "width"; + vertSizing = "height"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiMaterialPreview(matEd_previewObjectView) { + position = "1 1"; + extent = "380 326"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + isContainer = "0"; + }; + }; + new GuiPopUpMenuCtrl(matEd_quickPreview_Popup) { + Text = "Cube"; + position = "4 0"; + extent = "67 18"; + profile = "ToolsGuiPopUpMenuProfile"; + command = "MaterialEditorGui.updatePreviewObject();"; + tooltipProfile = "GuiToolTipProfile"; + ToolTip = "Changes the Preview Mesh"; + isContainer = "0"; + }; + new GuiSwatchButtonCtrl(matEd_lightColorPicker) { + position = "79 4"; + extent = "14 14"; + command = "getColorF($ThisControl.color, \"MaterialEditorGui.updateLightColor\");"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Change Normal Light Color"; + }; + new GuiSwatchButtonCtrl(matEd_ambientLightColorPicker) { + position = "97 4"; + extent = "14 14"; + command = "getColorF($ThisControl.color, \"MaterialEditorGui.updateAmbientColor\");"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Change Ambient Light Color"; + }; + new GuiSwatchButtonCtrl(MaterialPreviewBackgroundPicker) { + color = "0 0 0 0.2"; + position = "113 0"; + extent = "20 20"; + command = "getColorF($thisControl.color, \"MaterialEditorGui.updatePreviewBackground\");"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Change Background Color (preview)"; + }; + new GuiCheckBoxCtrl() { + Text = "Preview in World"; + position = "266 1"; + extent = "114 18"; + horizSizing = "left"; + profile = "ToolsGuiCheckBoxProfile"; + variable = "MaterialEditorGui.livePreview"; + command = "MaterialEditorGui.updateLivePreview($ThisControl.getValue());"; + tooltipProfile = "GuiToolTipProfile"; + }; + }; + new GuiPanel() { + docking = "Client"; + position = "0 356"; + extent = "387 913"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "panel2"; + + new GuiContainer() { + extent = "388 22"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + + new GuiBitmapButtonCtrl(MatEd_phoBreadcrumb) { + BitmapAsset = "ToolsModule:folderUp_image"; + position = "-1 0"; + extent = "20 19"; + profile = "GuiDefaultProfile"; + visible = "0"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Go back to previous editor"; + hidden = "1"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:delete_n_image"; + position = "368 1"; + extent = "17 17"; + horizSizing = "left"; + profile = "ToolsGuiButtonProfile"; + command = "MaterialEditorGui.deleteMaterial();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Delete Material from File"; + }; + new GuiBitmapButtonCtrl(MatEd_editMaterial) { + BitmapAsset = "ToolsModule:open_file_n_image"; + position = "249 1"; + extent = "16 16"; + horizSizing = "left"; + profile = "GuiDefaultProfile"; + command = "AssetBrowser.showDialog(\"MaterialAsset\", \"MaterialEditorGui.selectMaterialAsset\");"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Open Existing Material"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:new_n_image"; + position = "269 1"; + extent = "16 16"; + horizSizing = "left"; + profile = "ToolsGuiDefaultProfile"; + command = "MaterialEditorGui.createNewMaterial();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Create New Material"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:save_icon_n_image"; + position = "289 1"; + extent = "16 16"; + horizSizing = "left"; + profile = "ToolsGuiDefaultProfile"; + command = "MaterialEditorGui.save();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Save Material (ALT S)"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:visible_n_image"; + position = "309 1"; + extent = "16 16"; + horizSizing = "left"; + profile = "ToolsGuiDefaultProfile"; + command = "MaterialEditorGui.lookupMaterialInstances();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Lookup Material Instances"; + }; + new GuiBitmapCtrl() { + BitmapAsset = "ToolsModule:separator_h_image"; + BitmapFile = "tools/gui/images/separator-h.png"; + position = "330 1"; + extent = "2 16"; + minExtent = "2 16"; + horizSizing = "left"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:reset_icon_n_image"; + position = "334 1"; + extent = "17 17"; + horizSizing = "left"; + profile = "ToolsGuiButtonProfile"; + command = "MaterialEditorGui.refreshMaterial();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Revert Material to Saved"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:clear_icon_n_image"; + position = "351 1"; + extent = "17 17"; + horizSizing = "left"; + profile = "ToolsGuiButtonProfile"; + command = "MaterialEditorGui.clearMaterial();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Clear All Material Properties"; + }; + }; + new GuiContainer() { + position = "0 27"; + extent = "388 88"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + + new GuiContainer(MatEdMaterialMode) { + extent = "392 39"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + + new GuiTextCtrl() { + Text = "Material"; + position = "10 1"; + extent = "50 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiTextEditCtrl() { + position = "80 0"; + extent = "305 20"; + horizSizing = "width"; + profile = "ToolsGuiTextEditProfile"; + altCommand = "MaterialEditorGui.setMaterialDirty();MaterialEditorGui.updateActiveMaterialName($ThisControl.getText());"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "selMaterialName"; + }; + new GuiTextCtrl() { + position = "70 20"; + extent = "317 16"; + horizSizing = "width"; + profile = "GuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + }; + new GuiContainer(MatEdTargetMode) { + extent = "394 20"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + visible = "0"; + tooltipProfile = "GuiToolTipProfile"; + hidden = "1"; + + new GuiTextCtrl() { + Text = "Material Slot"; + position = "4 1"; + extent = "74 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; + ToolTip = "List of Material Slots available for selected object (If applicable)"; + }; + new GuiPopUpMenuCtrlEx(SubMaterialSelector) { + Text = "MaterialAsset"; + position = "96 0"; + extent = "292 17"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuProfile"; + active = "0"; + command = "SubMaterialSelector.onSelect();"; + tooltipProfile = "GuiToolTipProfile"; + ToolTip = "Target Material Slot"; + }; + }; + new GuiContainer() { + position = "0 24"; + extent = "394 62"; + horizSizing = "width"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + + new GuiTextCtrl() { + Text = "MapTo"; + position = "6 2"; + extent = "58 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Name of material slot lookup to associate this material to for shapes."; + }; + new GuiTextEditCtrl() { + position = "80 -2"; + extent = "307 20"; + horizSizing = "width"; + profile = "ToolsGuiTextEditProfile"; + altCommand = "MaterialEditorGui.setMaterialDirty();MaterialEditorGui.updateActiveMaterialMapTo($ThisControl.getText());"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Name of material slot lookup to associate this material to for shapes."; + internalName = "selMaterialMapTo"; + }; + new GuiTextCtrl() { + Text = "Inherit From"; + position = "6 25"; + extent = "69 16"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Other material that this one should inherit fields from as its parent."; + profile = "ToolsGuiTextRightProfile"; + }; + new GuiTextEditCtrl() { + position = "80 21"; + extent = "285 20"; + horizSizing = "width"; + profile = "ToolsGuiTextEditProfile"; + altCommand = "MaterialEditorGui.setMaterialDirty();MaterialEditorGui.updateActiveMaterialInheritFrom($ThisControl.getText());"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "selMaterialInheritFrom"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Other material that this one should inherit fields from as its parent."; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:material_image"; + position = "368 24"; + extent = "16 16"; + horizSizing = "left"; + profile = "GuiDefaultProfile"; + command = "AssetBrowser.showDialog(\"MaterialAsset\", \"MaterialEditorGui.updateActiveMaterialInheritFrom\");"; + tooltipProfile = "ToolsGuiToolTipProfile"; + ToolTip = "Select Material to set as this material to inherit from."; + }; + new GuiTextCtrl() { + Text = "Layer"; + position = "4 46"; + extent = "56 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiPopUpMenuCtrlEx(MaterialEditorLayerSelector) { + Text = "Layer 0"; + position = "80 45"; + extent = "305 17"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuProfile"; + command = "MaterialEditorGui.changeLayer( $ThisControl.getText() );"; + tooltipProfile = "GuiToolTipProfile"; + ToolTip = "Material Layer"; + }; + }; + }; + new GuiContainer() { + docking = "None"; + margin = "0 43 0 5"; + anchorTop = "0"; + anchorLeft = "0"; + position = "0 120"; + extent = "382 793"; + minExtent = "64 64"; + horizSizing = "width"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + isContainer = "0"; + + new GuiTextEditCtrl(MaterialEditorGuiFilter) { + placeholderText = "Filter..."; + validate = "MaterialEditorPropInspector.setSearchText($ThisControl.getText());"; + position = "5 0"; + extent = "383 20"; + horizSizing = "width"; + profile = "ToolsGuiTextEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiBitmapButtonCtrl() { + BitmapAsset = "ToolsModule:clear_icon_n_image"; + position = "371 1"; + extent = "17 17"; + horizSizing = "left"; + profile = "ToolsGuiDefaultProfile"; + command = "MaterialEditorGuiFilter.setText(\"\");MaterialEditorPropInspector.setSearchText(\"\");"; + tooltipProfile = "ToolsGuiToolTipProfile"; + }; + new GuiScrollCtrl() { + hScrollBar = "alwaysOff"; + lockHorizScroll = "1"; + docking = "None"; + anchorTop = "0"; + anchorLeft = "0"; + position = "5 22"; + extent = "382 766"; + minExtent = "8 8"; + horizSizing = "width"; + vertSizing = "height"; + profile = "GuiEditorScrollProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + + new GuiInspector(MaterialEditorPropInspector) { + groupFilters = "-Internal -Ungrouped -Editing -Object -Persistence -Dynamic Fields"; + forcedArrayIndex = "0"; + position = "1 1"; + extent = "367 48"; + minExtent = "8 8"; + horizSizing = "width"; + profile = "GuiInspectorProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + superClass = "EditorInspectorBase"; + collapseState = "1 1 0 0 0 0 0 0"; + }; + }; + }; + }; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubeMapEd_cubePreview.max b/Templates/BaseGame/game/tools/materialEditor/gui/cubeMapEd_cubePreview.max deleted file mode 100644 index fa6249ee95ce08e25c8484cdce6700665d0c86ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253952 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 PcmzBG9s!R)eG&No{V5C~ diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemapEd_spherePreview.max b/Templates/BaseGame/game/tools/materialEditor/gui/cubemapEd_spherePreview.max deleted file mode 100644 index 9425d3b4f3b78a095092957d8174fdece4b1b00c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253952 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

J>%T;?gLrs&r2UI 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 diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubematEd_cylinderPreview.max b/Templates/BaseGame/game/tools/materialEditor/gui/cubematEd_cylinderPreview.max deleted file mode 100644 index 2926befd6517f6af1af23a250bfbbfb50e02c947..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 258048 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 EKgRP0umAu6 diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_pyramidPreview.max b/Templates/BaseGame/game/tools/materialEditor/gui/matEd_pyramidPreview.max deleted file mode 100644 index 06b4b48d110ffd377fb8945686059ca0420d08ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253952 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 diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_spherePreview.max b/Templates/BaseGame/game/tools/materialEditor/gui/matEd_spherePreview.max deleted file mode 100644 index 71485ac8621811e3436d8ca73b259f0b33b83627..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 249856 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 w->L%N$E%c@gzu5~c88OIlYo2#9r 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 diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/change-material-btn_d.png b/Templates/BaseGame/game/tools/materialEditor/images/change-material-btn_d.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/change-material-btn_d.png rename to Templates/BaseGame/game/tools/materialEditor/images/change-material-btn_d.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/change-material-btn_h.png b/Templates/BaseGame/game/tools/materialEditor/images/change-material-btn_h.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/change-material-btn_h.png rename to Templates/BaseGame/game/tools/materialEditor/images/change-material-btn_h.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/change-material-btn_n.png b/Templates/BaseGame/game/tools/materialEditor/images/change-material-btn_n.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/change-material-btn_n.png rename to Templates/BaseGame/game/tools/materialEditor/images/change-material-btn_n.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/change_material_btn_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/change_material_btn_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/change_material_btn_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/change_material_btn_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/change_material_btn_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/change_material_btn_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/change_material_btn_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/change_material_btn_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/change_material_btn_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/change_material_btn_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/change_material_btn_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/change_material_btn_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubeMapEd_previewMat.jpg b/Templates/BaseGame/game/tools/materialEditor/images/cubeMapEd_previewMat.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubeMapEd_previewMat.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/cubeMapEd_previewMat.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubeMapEd_previewMat_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cubeMapEd_previewMat_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubeMapEd_previewMat_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cubeMapEd_previewMat_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_xNeg.jpg b/Templates/BaseGame/game/tools/materialEditor/images/cube_xNeg.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_xNeg.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/cube_xNeg.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_xNeg_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cube_xNeg_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_xNeg_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cube_xNeg_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_xPos.jpg b/Templates/BaseGame/game/tools/materialEditor/images/cube_xPos.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_xPos.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/cube_xPos.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_xPos_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cube_xPos_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_xPos_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cube_xPos_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_yNeg.jpg b/Templates/BaseGame/game/tools/materialEditor/images/cube_yNeg.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_yNeg.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/cube_yNeg.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_yNeg_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cube_yNeg_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_yNeg_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cube_yNeg_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_yPos.jpg b/Templates/BaseGame/game/tools/materialEditor/images/cube_yPos.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_yPos.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/cube_yPos.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_yPos_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cube_yPos_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_yPos_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cube_yPos_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_zNeg.jpg b/Templates/BaseGame/game/tools/materialEditor/images/cube_zNeg.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_zNeg.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/cube_zNeg.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_zNeg_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cube_zNeg_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_zNeg_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cube_zNeg_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_zPos.jpg b/Templates/BaseGame/game/tools/materialEditor/images/cube_zPos.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_zPos.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/cube_zPos.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cube_zPos_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cube_zPos_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cube_zPos_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cube_zPos_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_d.png b/Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_d.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_d.png rename to Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_d.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_h.png b/Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_h.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_h.png rename to Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_h.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_i.png b/Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_i.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_i.png rename to Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_i.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_i_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_i_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_i_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_i_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_n.png b/Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_n.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_n.png rename to Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_n.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemapBtnBorder_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/cubemapBtnBorder_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/gridTiny2.PNG b/Templates/BaseGame/game/tools/materialEditor/images/gridTiny2.PNG similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/gridTiny2.PNG rename to Templates/BaseGame/game/tools/materialEditor/images/gridTiny2.PNG diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/gui_gridTiny2_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/gui_gridTiny2_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/gui_gridTiny2_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/gui_gridTiny2_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_d.jpg b/Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_d.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_d.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_d.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_h.jpg b/Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_h.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_h.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_h.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_n.jpg b/Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_n.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_n.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_n.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderButt_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderButt_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderPreview.max b/Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderPreview.max similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_cylinderPreview.max rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_cylinderPreview.max diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_mappedMat.jpg b/Templates/BaseGame/game/tools/materialEditor/images/matEd_mappedMat.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_mappedMat.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_mappedMat.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_mappedMat_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/matEd_mappedMat_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_mappedMat_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_mappedMat_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_d.jpg b/Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_d.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_d.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_d.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_h.jpg b/Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_h.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_h.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_h.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_n.jpg b/Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_n.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_n.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_n.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/matEd_sphereButt_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/matEd_sphereButt_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_d.png b/Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_d.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_d.png rename to Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_d.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_h.png b/Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_h.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_h.png rename to Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_h.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_n.png b/Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_n.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_n.png rename to Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_n.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/materialSelectorIcon_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/materialSelectorIcon_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/mesh-selector-btn_d.png b/Templates/BaseGame/game/tools/materialEditor/images/mesh-selector-btn_d.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/mesh-selector-btn_d.png rename to Templates/BaseGame/game/tools/materialEditor/images/mesh-selector-btn_d.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/mesh-selector-btn_h.png b/Templates/BaseGame/game/tools/materialEditor/images/mesh-selector-btn_h.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/mesh-selector-btn_h.png rename to Templates/BaseGame/game/tools/materialEditor/images/mesh-selector-btn_h.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/mesh-selector-btn_n.png b/Templates/BaseGame/game/tools/materialEditor/images/mesh-selector-btn_n.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/mesh-selector-btn_n.png rename to Templates/BaseGame/game/tools/materialEditor/images/mesh-selector-btn_n.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/mesh_selector_btn_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/mesh_selector_btn_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/mesh_selector_btn_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/mesh_selector_btn_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/mesh_selector_btn_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/mesh_selector_btn_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/mesh_selector_btn_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/mesh_selector_btn_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/mesh_selector_btn_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/mesh_selector_btn_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/mesh_selector_btn_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/mesh_selector_btn_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/new-material_d.png b/Templates/BaseGame/game/tools/materialEditor/images/new-material_d.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/new-material_d.png rename to Templates/BaseGame/game/tools/materialEditor/images/new-material_d.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/new-material_h.png b/Templates/BaseGame/game/tools/materialEditor/images/new-material_h.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/new-material_h.png rename to Templates/BaseGame/game/tools/materialEditor/images/new-material_h.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/new-material_n.png b/Templates/BaseGame/game/tools/materialEditor/images/new-material_n.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/new-material_n.png rename to Templates/BaseGame/game/tools/materialEditor/images/new-material_n.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/new_material_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/new_material_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/new_material_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/new_material_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/new_material_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/new_material_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/new_material_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/new_material_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/new_material_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/new_material_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/new_material_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/new_material_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/screenFaded.png b/Templates/BaseGame/game/tools/materialEditor/images/screenFaded.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/screenFaded.png rename to Templates/BaseGame/game/tools/materialEditor/images/screenFaded.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/screenFaded_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/screenFaded_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/screenFaded_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/screenFaded_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/scrollBox.jpg b/Templates/BaseGame/game/tools/materialEditor/images/scrollBox.jpg similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/scrollBox.jpg rename to Templates/BaseGame/game/tools/materialEditor/images/scrollBox.jpg diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/scrollBox_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/scrollBox_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/scrollBox_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/scrollBox_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/unknownImage.png b/Templates/BaseGame/game/tools/materialEditor/images/unknownImage.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/unknownImage.png rename to Templates/BaseGame/game/tools/materialEditor/images/unknownImage.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/unknownImage_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/unknownImage_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/unknownImage_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/unknownImage_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/unsavedWarn.png b/Templates/BaseGame/game/tools/materialEditor/images/unsavedWarn.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/unsavedWarn.png rename to Templates/BaseGame/game/tools/materialEditor/images/unsavedWarn.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/unsavedWarn_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/unsavedWarn_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/unsavedWarn_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/unsavedWarn_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-none_d.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-none_d.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-none_d.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-none_d.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-none_h.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-none_h.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-none_h.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-none_h.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-none_i.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-none_i.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-none_i.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-none_i.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-none_n.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-none_n.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-none_n.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-none_n.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-sine_d.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-sine_d.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-sine_d.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-sine_d.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-sine_h.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-sine_h.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-sine_h.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-sine_h.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-sine_i.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-sine_i.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-sine_i.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-sine_i.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-sine_n.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-sine_n.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-sine_n.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-sine_n.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-square_d.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-square_d.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-square_d.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-square_d.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-square_h.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-square_h.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-square_h.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-square_h.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-square_i.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-square_i.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-square_i.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-square_i.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-square_n.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-square_n.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-square_n.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-square_n.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-triangle_d.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-triangle_d.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-triangle_d.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-triangle_d.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-triangle_h.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-triangle_h.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-triangle_h.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-triangle_h.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-triangle_i.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-triangle_i.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-triangle_i.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-triangle_i.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav-triangle_n.png b/Templates/BaseGame/game/tools/materialEditor/images/wav-triangle_n.png similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav-triangle_n.png rename to Templates/BaseGame/game/tools/materialEditor/images/wav-triangle_n.png diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_none_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_none_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_none_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_none_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_none_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_none_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_none_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_none_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_none_i_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_none_i_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_none_i_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_none_i_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_none_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_none_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_none_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_none_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_sine_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_sine_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_sine_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_sine_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_sine_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_sine_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_sine_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_sine_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_sine_i_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_sine_i_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_sine_i_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_sine_i_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_sine_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_sine_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_sine_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_sine_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_square_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_square_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_square_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_square_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_square_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_square_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_square_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_square_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_square_i_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_square_i_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_square_i_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_square_i_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_square_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_square_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_square_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_square_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_triangle_d_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_triangle_d_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_triangle_d_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_triangle_d_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_triangle_h_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_triangle_h_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_triangle_h_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_triangle_h_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_triangle_i_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_triangle_i_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_triangle_i_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_triangle_i_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/wav_triangle_n_image.asset.taml b/Templates/BaseGame/game/tools/materialEditor/images/wav_triangle_n_image.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/wav_triangle_n_image.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/images/wav_triangle_n_image.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/main.tscript b/Templates/BaseGame/game/tools/materialEditor/main.tscript index 9ff894266..dffcee964 100644 --- a/Templates/BaseGame/game/tools/materialEditor/main.tscript +++ b/Templates/BaseGame/game/tools/materialEditor/main.tscript @@ -26,30 +26,19 @@ function initializeMaterialEditor() { echo(" % - Initializing Material Editor"); - // Load Preview Window - exec("~/materialEditor/gui/guiMaterialPreviewWindow.ed.gui"); - - // Load Properties Window - exec("~/materialEditor/gui/guiMaterialPropertiesWindow.ed.gui"); - //Material Instance viewer exec("~/materialEditor/gui/materialInstancesView.ed.gui"); + exec("./gui/MaterialEditorGui.gui"); + // Load Client Scripts. exec("./scripts/materialEditor.ed." @ $TorqueScriptFileExtension); exec("./scripts/materialEditorUndo.ed." @ $TorqueScriptFileExtension); exec("./scripts/materialInstanceView.ed." @ $TorqueScriptFileExtension); - //exec("./gui/profiles.ed." @ $TorqueScriptFileExtension); - MaterialEditorPreviewWindow.setVisible( false ); - //matEd_cubemapEditor.setVisible( false ); - matEd_addCubemapWindow.setVisible( false ); - MaterialEditorPropertiesWindow.setVisible( false ); - - MainSceneTabPanel.add( MaterialEditorPreviewWindow ); - MainSceneTabPanel.add( matEd_cubemapEditor ); - MainSceneTabPanel.add( matEd_addCubemapWindow ); - MainSceneTabPanel.add( MaterialEditorPropertiesWindow ); + exec("./scripts/materialAnimationFieldTypes." @ $TorqueScriptFileExtension); + exec("./scripts/bakeCompositeButtonFieldType." @ $TorqueScriptFileExtension); + exec("./scripts/ORMSliderFieldType." @ $TorqueScriptFileExtension); } function destroyMaterialEditor() @@ -131,8 +120,8 @@ function MaterialEditorPlugin::onActivated( %this ) materialAdvancedPropertiesRollout.Expanded = false; WorldEditorPlugin.onActivated(); - EditorGui-->MatEdPropertiesWindow.setVisible( true ); - EditorGui-->MatEdPreviewWindow.setVisible( true ); + //EditorGui-->MatEdPropertiesWindow.setVisible( true ); + //EditorGui-->MatEdPreviewWindow.setVisible( true ); EditorGui-->WorldEditorToolbar.setVisible( true ); MaterialEditorGui.currentObject = $Tools::materialEditorList; @@ -143,6 +132,8 @@ function MaterialEditorPlugin::onActivated( %this ) // Do resize the windows at start MaterialEditorGui.onWake(); + Canvas.pushDialog(MaterialEditorGui); + Parent::onActivated(%this); } @@ -158,10 +149,11 @@ function MaterialEditorPlugin::onDeactivated( %this ) WorldEditorPlugin.onDeactivated(); + Canvas.popDialog(MaterialEditorGui); MaterialEditorGui.quit(); - EditorGui-->MatEdPropertiesWindow.setVisible( false ); - EditorGui-->MatEdPreviewWindow.setVisible( false ); + //EditorGui-->MatEdPropertiesWindow.setVisible( false ); + //EditorGui-->MatEdPreviewWindow.setVisible( false ); EditorGui-->WorldEditorToolbar.setVisible( false ); %this.map.pop(); diff --git a/Templates/BaseGame/game/tools/materialEditor/scripts/ORMSliderFieldType.tscript b/Templates/BaseGame/game/tools/materialEditor/scripts/ORMSliderFieldType.tscript new file mode 100644 index 000000000..1118dbf28 --- /dev/null +++ b/Templates/BaseGame/game/tools/materialEditor/scripts/ORMSliderFieldType.tscript @@ -0,0 +1,149 @@ +function GuiInspectorGroup::buildMatORMSliderField(%this, %fieldName, %fieldLabel, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) +{ + echo("GuiInspectorGroup::buildMatORMSliderField()"); + %extent = 200; + + %fieldCtrl = %this.createInspectorField(); + + %extent = %this.stack.getExtent(); + + %width = mRound(%extent/2); + %height = 20; + %inset = 10; + + %fieldCtrl.setHeightOverride(true, %height); + + %minRange = getWord(%fieldDataVals,0); + %maxRange = getWord(%fieldDataVals,1); + %tickCount = getWord(%fieldDataVals,2); + %doSnap = getWord(%fieldDataVals,3); + + if(%doSnap $= "") + %doSnap = 0; + + %editControl = new GuiControl() + { + class = "guiMatORMSliderField"; + Position = %fieldCtrl.edit.position; + Extent = %fieldCtrl.edit.extent; + }; + + %editText = new GuiTextEditCtrl() { + class = "guiMatORMSliderFieldText"; + historySize = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + password = "0"; + passwordMask = "*"; + maxLength = "1024"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + Position = "0 0"; + Extent = %fieldCtrl.edit.extent.x * 0.2 SPC %fieldCtrl.edit.extent.y; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextEditProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + text = %fieldDefaultVal; + }; + + %editSlider = new GuiSliderCtrl() { + class = "guiMatORMSliderFieldSlider"; + range = %minRange SPC %maxRange; + ticks = %tickCount; + value = %fieldDefaultVal; + snap = %doSnap; + renderTicks = "true"; + isContainer = "0"; + Profile = "GuiSliderProfile"; + HorizSizing = "width"; + VertSizing = "bottom"; + Position = (%fieldCtrl.edit.extent.x * 0.2) + 5 SPC 0; + Extent = (%fieldCtrl.edit.extent.x * 0.8) - 5 SPC %fieldCtrl.edit.extent.y; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "$thisControl.onDragComplete();"; + tooltipprofile = "GuiToolTipProfile"; + tooltip = ""; //%tooltip; + hovertime = "1000"; + canSaveDynamicFields = "0"; + ownerObject = %ownerObj; + fieldName = %fieldName; + callbackName = %callbackName; + }; + + %editText.sliderControl = %editSlider; + %editSlider.textControl = %editText; + + %editControl.add(%editText); + %editControl.add(%editSlider); + + //set the field value + if(getSubStr(%this.fieldName, 0, 1) $= "$") + { + if(%fieldName $= "") + %editControl.setValue(%fieldName); + } + else if(isObject(%ownerObj)) + { + //regular variable + %setCommand = %editControl @ ".setValue(" @ %ownerObj @ "." @ %fieldName @ ");"; + eval(%setCommand); + } + + %fieldCtrl.setCaption(%fieldLabel); + %fieldCtrl.setEditControl(%editControl); + + %this.addInspectorField(%fieldCtrl); +} + +function guiMatORMSliderFieldField::onResize(%this) +{ +} + +function guiMatORMSliderFieldText::onReturn(%this) +{ + %value = %this.getText(); + %this.sliderControl.setValue(%value); + %this.sliderControl.onDragComplete(); +} + +function guiMatORMSliderFieldSlider::onDragComplete( %this ) +{ + %value = %this.getValue(); + %this.textControl.setText(%value); + + if(getSubStr(%this.fieldName, 0, 1) $= "$") + { + //ah, a global var, just do it straight, then + %setCommand = %this.fieldName @ " = \"" @ %value @ "\";"; + } + else if(isObject(%this.ownerObject)) + { + //regular variable + %setCommand = %this.ownerObject @ "." @ %this.fieldName @ " = \"" @ %value @ "\";"; + } + else if(%this.callbackName !$= "") + { + %setCommand = %this.callbackName @ "(\"" @ %this.fieldName @ "\",\"" @ %value @"\");"; + } + + eval(%setCommand); +} + +function guiMatORMSliderFieldSlider::onMouseDragged(%this) +{ + %this.onDragComplete(); +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/materialEditor/scripts/bakeCompositeButtonFieldType.tscript b/Templates/BaseGame/game/tools/materialEditor/scripts/bakeCompositeButtonFieldType.tscript new file mode 100644 index 000000000..dcb0d24e8 --- /dev/null +++ b/Templates/BaseGame/game/tools/materialEditor/scripts/bakeCompositeButtonFieldType.tscript @@ -0,0 +1,31 @@ +function GuiInspectorGroup::buildBakeCompositeButtonField(%this, %fieldName, %fieldLabel, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) +{ + %fieldCtrl = %this.createInspectorField(); + + %extent = %this.stack.getExtent(); + + %width = mRound(%extent/2); + + %button = new GuiButtonCtrl() { + canSaveDynamicFields = "0"; + Profile = "ToolsGuiButtonProfile"; + HorizSizing = "width"; + VertSizing = "bottom"; + Position = "16 3"; + Extent = %width SPC 18; + MinExtent = "8 2"; + canSave = "0"; + Visible = "1"; + hovertime = "100"; + tooltip = ""; //%tooltip; + tooltipProfile = "EditorToolTipProfile"; + text = "Bake"; + maxLength = "1024"; + command = "MaterialEditorGui.saveCompositeMap();"; + }; + + %fieldCtrl.setCaption(%fieldLabel); + %fieldCtrl.setEditControl(%button); + + %this.addInspectorField(%fieldCtrl); +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/materialEditor/scripts/materialAnimationFieldTypes.tscript b/Templates/BaseGame/game/tools/materialEditor/scripts/materialAnimationFieldTypes.tscript new file mode 100644 index 000000000..80d458ecb --- /dev/null +++ b/Templates/BaseGame/game/tools/materialEditor/scripts/materialAnimationFieldTypes.tscript @@ -0,0 +1,939 @@ +function GuiInspectorGroup::buildMaterialRotAnimationField(%this, %fieldName, %fieldLabel, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) +{ + %container = new GuiContainer() + { + profile="inspectorStyleRolloutInnerProfile"; + isContainer = "1"; + position = "-1 120"; + Extent = 300 SPC 100; + HorizSizing = "width"; + + new GuiCheckboxCtrl() { + canSaveDynamicFields = "0"; + internalName = "RotationAnimation"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiInspectorCheckBoxTitleProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "4 3"; + Extent = "200 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "MaterialEditorGui.updateAnimationFlags();"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + text = " Rotation Animation"; + maxLength = "1024"; + }; + + new GuiControl(){ + class = "AggregateControl"; + position = "0 29"; + Extent = "290 20"; + HorizSizing = "width"; + internalName = "RotAnimAggCtrl"; + + new GuiTextCtrl(){ // u + profile = "ToolsGuiTextProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "11 1"; + Extent = "12 16"; + text = "U"; + }; + + new GuiSliderCtrl() { // u + Profile = "ToolsGuiSliderProfile"; + internalName = "RotationSliderU"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "25 2"; + Extent = "180 15"; + Command = "MaterialEditorGui.updateRotationOffset(true, true);"; + AltCommand = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateRotationOffset(true, false);"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Change U Scroll Direction"; + hovertime = "1000"; + range = "-1 0"; + ticks = "1"; + value = "-0.5"; + }; + new GuiTextEditCtrl(){ // u + internalName = "RotationTextEditU"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "210 0"; + Extent = "34 18"; + text = "0"; + Command = "$ThisControl.getParent().updateFromChild($ThisControl);"; + Profile = "ToolsGuiTextEditProfile"; + }; + }; + + new GuiControl() { + class = "AggregateControl"; + position = "0 50"; + Extent = "290 20"; + HorizSizing = "width"; + + new GuiTextCtrl(){ // v + profile = "ToolsGuiTextProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "11 1"; + Extent = "12 16"; + text = "V"; + }; + + new GuiSliderCtrl() { // v + Profile = "ToolsGuiSliderProfile"; + internalName = "RotationSliderV"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "25 2"; + Extent = "180 15"; + Command = "MaterialEditorGui.updateRotationOffset(true, true);"; + AltCommand = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateRotationOffset(true, false);"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Change V Scroll Direction"; + hovertime = "1000"; + range = "-1 0"; + ticks = "1"; + value = "-0.5"; + }; + + new GuiTextEditCtrl(){ // v + internalName = "RotationTextEditV"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "210 0"; + Extent = "34 18"; + text = "0"; + Profile = "ToolsGuiTextEditProfile"; + Command = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateRotationOffset();"; + }; + }; + new GuiTextCtrl(){ // Pivot Point + HorizSizing = "left"; + VertSizing = "bottom"; + position = "211 12"; + Extent = "34 16"; + text = "Pivot"; + profile = "ToolsGuiTextProfile"; + }; + new GuiBitmapCtrl(){ + HorizSizing = "left"; + VertSizing = "bottom"; + position = "248 20"; + Extent = "48 48"; + isContainer = true; + bitmapAsset=""; + + new GuiBitmapCtrl(){ + HorizSizing = "right"; + VertSizing = "bottom"; + position = "0 0"; + Extent = "48 48"; + bitmapAsset="ToolsModule:cubemapBtnBorder_n_image"; + }; + + new GuiBitmapCtrl(){ //horizontal bar + internalName = "RotationCrosshair"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "20 20"; + Extent = "7 7"; + MinExtent = "0 0"; + bitmapAsset="ToolsModule:crosshair_blue_image"; + }; + }; + + new GuiControl() { + class = "AggregateControl"; + position = "0 75"; + Extent = "300 35"; + HorizSizing = "width"; + + new GuiTextCtrl(){ // Speed + profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "11 0"; + Extent = "40 16"; + text = "Speed"; + }; + + new GuiSliderCtrl() { + canSaveDynamicFields = "0"; + internalName = "RotationSpeedSlider"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiSliderProfile"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "58 3"; + Extent = "148 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "MaterialEditorGui.updateRotationSpeed(true, true);"; + AltCommand = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateRotationSpeed(true, false);"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Scrolling Speed"; + hovertime = "1000"; + range = "-10 10"; + ticks = "1"; + value = "0"; + }; + + new GuiTextEditCtrl() { + canSaveDynamicFields = "0"; + internalName = "RotationSpeedTextEdit"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiTextEditProfile"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "210 1"; + Extent = "34 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateRotationSpeed();"; + hovertime = "1000"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + text = "0"; + maxLength = "1024"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + password = "0"; + passwordMask = "*"; + }; + new GuiTextCtrl(){ // space + profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "0 20"; + Extent = "300 20"; + text = " "; + }; + }; + }; + + %container.resize(0,0,%this-->stack.extent.x, 100); + + %this-->stack.add(%container); +} + +function MaterialEditorGui::updateRotationOffset(%this, %isSlider, %onMouseUp) +{ + %layer = MaterialEditorGui.currentLayer; + %X = MaterialEditorGuiWindow-->RotationTextEditU.getText(); + %Y = MaterialEditorGuiWindow-->RotationTextEditV.getText(); + MaterialEditorPropertiesWindow-->RotationCrosshair.setPosition(45*mAbs(%X)-2, 45*mAbs(%Y)-2); + + MaterialEditorGui.updateActiveMaterial("rotPivotOffset[" @ %layer @ "]", %X SPC %Y,%isSlider,%onMouseUp); +} + +function MaterialEditorGui::updateRotationSpeed(%this, %isSlider, %onMouseUp) +{ + %layer = MaterialEditorGui.currentLayer; + %speed = MaterialEditorGuiWindow-->RotationSpeedTextEdit.getText(); + MaterialEditorGui.updateActiveMaterial("rotSpeed[" @ %layer @ "]",%speed,%isSlider,%onMouseUp); +} + +function GuiInspectorGroup::buildMaterialScrollAnimationField(%this, %fieldName, %fieldLabel, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) +{ + %container = new GuiContainer(){ // Scroll Animation Properties + profile="inspectorStyleRolloutInnerProfile"; + isContainer = "1"; + position = "-1 240"; + Extent = "300 105"; + HorizSizing = "width"; + + new GuiBitmapCtrl(){ + position = "0 5"; + extent ="300 2"; + HorizSizing = "width"; + bitmapAsset ="ToolsModule:separator_v_image"; + }; + + new GuiCheckboxCtrl() { + canSaveDynamicFields = "0"; + internalName = "ScrollAnimation"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiInspectorCheckBoxTitleProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "4 10"; + Extent = "200 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + Command = "MaterialEditorGui.updateAnimationFlags();"; + text = " Scroll Animation"; + maxLength = "1024"; + }; + + new GuiControl(){ + class = "AggregateControl"; + position = "0 29"; + Extent = "290 20"; + HorizSizing = "width"; + + new GuiTextCtrl(){ // u + profile = "ToolsGuiTextProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "11 1"; + Extent = "12 16"; + text = "U"; + }; + + new GuiSliderCtrl() { // u + Profile = "ToolsGuiSliderProfile"; + internalName = "ScrollSliderU"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "25 2"; + Extent = "180 15"; + Command = "MaterialEditorGui.updateScrollOffset(true, true);"; + AltCommand = "$ThisControl.getParent().updateFromChild($ThisControl);MaterialEditorGui.updateScrollOffset(true, false);"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Change U Scroll Direction"; + hovertime = "1000"; + range = "-1 1"; + ticks = "1"; + value = "0"; + }; + new GuiTextEditCtrl(){ // u + internalName = "ScrollTextEditU"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "210 0"; + Extent = "34 18"; + text = "0"; + Profile = "ToolsGuiTextEditProfile"; + Command = "$ThisControl.getParent().updateFromChild($ThisControl);MaterialEditorGui.updateScrollOffset();"; + }; + }; + + new GuiControl() { + class = "AggregateControl"; + position = "0 50"; + Extent = "290 20"; + HorizSizing = "width"; + + new GuiTextCtrl(){ // v + profile = "ToolsGuiTextProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "11 1"; + Extent = "12 16"; + text = "V"; + }; + + new GuiSliderCtrl() { // v + Profile = "ToolsGuiSliderProfile"; + internalName = "ScrollSliderV"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "25 2"; + Extent = "180 15"; + Command = "MaterialEditorGui.updateScrollOffset(true, true);"; + AltCommand = "$ThisControl.getParent().updateFromChild($ThisControl);MaterialEditorGui.updateScrollOffset(true, false);"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Change V Scroll Direction"; + hovertime = "1000"; + range = "-1 1"; + ticks = "1"; + value = "0"; + }; + new GuiTextEditCtrl(){ // v + internalName = "ScrollTextEditV"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "210 0"; + Extent = "34 18"; + text = "0"; + Profile = "ToolsGuiTextEditProfile"; + Command = "$ThisControl.getParent().updateFromChild($ThisControl);MaterialEditorGui.updateScrollOffset();"; + }; + }; + new GuiTextCtrl(){ // Direction Offset + HorizSizing = "left"; + VertSizing = "bottom"; + position = "211 12"; + Extent = "34 16"; + text = "Offset"; + profile = "ToolsGuiTextProfile"; + }; + new GuiBitmapCtrl(){ + HorizSizing = "left"; + VertSizing = "bottom"; + position = "248 20"; + Extent = "48 48"; + isContainer = true; + bitmapAsset=""; + + new GuiBitmapCtrl(){ + HorizSizing = "right"; + VertSizing = "bottom"; + position = "0 0"; + Extent = "48 48"; + bitmapAsset="ToolsModule:cubemapBtnBorder_n_image"; + }; + new GuiBitmapCtrl(){ //vertical bar + HorizSizing = "right"; + VertSizing = "bottom"; + position = "20 20"; + Extent = "7 7"; + MinExtent = "7 7"; + bitmapAsset="ToolsModule:crosshair_image"; + }; + new GuiBitmapCtrl(){ //horizontal bar + internalName = "ScrollCrosshair"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "20 20"; + Extent = "7 7"; + MinExtent = "0 0"; + bitmapAsset="ToolsModule:crosshair_blue_image"; + }; + }; + + new GuiControl() { + class = "AggregateControl"; + position = "0 75"; + Extent = "300 35"; + HorizSizing = "width"; + + new GuiTextCtrl(){ // Speed + profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "11 0"; + Extent = "40 16"; + text = "Speed"; + }; + + new GuiSliderCtrl() { + canSaveDynamicFields = "0"; + internalName = "ScrollSpeedSlider"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiSliderProfile"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "58 3"; + Extent = "148 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "MaterialEditorGui.updateScrollSpeed(true, true);"; + AltCommand = "$ThisControl.getParent().updateFromChild($ThisControl);MaterialEditorGui.updateScrollSpeed(true, false);"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Scrolling Speed"; + hovertime = "1000"; + range = "0 10"; + ticks = "0"; + value = "0"; + }; + + new GuiTextEditCtrl() { + canSaveDynamicFields = "0"; + internalName = "ScrollSpeedTextEdit"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiTextEditProfile"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "210 1"; + Extent = "34 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "$ThisControl.getParent().updateFromChild($ThisControl);MaterialEditorGui.updateScrollSpeed();"; + hovertime = "1000"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + text = "0"; + maxLength = "1024"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + password = "0"; + passwordMask = "*"; + }; + new GuiTextCtrl(){ // space + profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "0 20"; + Extent = "300 20"; + text = " "; + }; + }; + }; + + %this-->stack.add(%container); +} + +function MaterialEditorGui::updateScrollOffset(%this, %isSlider, %onMouseUp) +{ + %layer = MaterialEditorGui.currentLayer; + %X = MaterialEditorGuiWindow-->ScrollTextEditU.getText(); + %Y = MaterialEditorGuiWindow-->ScrollTextEditV.getText(); + MaterialEditorGuiWindow-->ScrollCrosshair.setPosition( -(23 * %X)+20, -(23 * %Y)+20); + + MaterialEditorGui.updateActiveMaterial("scrollDir[" @ %layer @ "]",%X SPC %Y,%isSlider,%onMouseUp); +} + +function MaterialEditorGui::updateScrollSpeed(%this, %isSlider, %onMouseUp) +{ + %layer = MaterialEditorGui.currentLayer; + %speed = MaterialEditorGuiWindow-->ScrollSpeedTextEdit.getText(); + MaterialEditorGui.updateActiveMaterial("scrollSpeed[" @ %layer @ "]",%speed,%isSlider,%onMouseUp); +} + +function GuiInspectorGroup::buildMaterialWaveAnimationField(%this, %fieldName, %fieldLabel, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) +{ + %container = new GuiContainer(){ // Wave Animation Properties + profile="inspectorStyleRolloutInnerProfile"; + isContainer = "1"; + position = "-1 360"; + Extent = "300 85"; + HorizSizing = "width"; + + new GuiBitmapCtrl(){ + position = "0 0"; + extent ="300 2"; + HorizSizing = "width"; + bitmapAsset ="ToolsModule:separator_v_image"; + }; + + new GuiCheckboxCtrl() { + Profile = "ToolsGuiInspectorCheckBoxTitleProfile"; + internalName = "WaveAnimation"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "4 5"; + Extent = "200 16"; + MinExtent = "8 2"; + text = " Wave Animation"; + Command = "MaterialEditorGui.updateAnimationFlags();"; + groupNum = "-1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + }; + + new GuiCheckboxCtrl() { + Profile = "ToolsGuiCheckBoxProfile"; + internalName = "ScaleAnimation"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "180 24"; + Extent = "55 16"; + MinExtent = "8 2"; + text = " Scale"; + Command = "MaterialEditorGui.updateAnimationFlags();"; + groupNum = "-1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + }; + + new GuiTextCtrl() { + profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "1 22"; + Extent = "100 16"; + text = " Wave Type"; + }; + new GuiContainer(){ // Wave Radio Button container + profile = "ToolsGuiDefaultProfile"; + internalName = "WaveButtonContainer"; + position = "120 24"; + Extent = "49 13"; + isContainer = "1"; + + new GuiBitmapButtonCtrl(){ + profile = "ToolsGuiDefaultProfile"; + buttonType = "RadioButton"; + position = "1 0"; + Extent = "13 13"; + bitmapAsset = "ToolsModule:wav_sine_n_image"; + command = "MaterialEditorGui.updateWaveType();"; + tooltip="Sine Wave"; + hovertime = "1000"; + groupNum = "0"; + waveType = "Sin"; + tooltipprofile = "ToolsGuiToolTipProfile"; + }; + new GuiBitmapButtonCtrl(){ + profile = "ToolsGuiDefaultProfile"; + buttonType = "RadioButton"; + position = "17 0"; + Extent = "13 13"; + bitmapAsset = "ToolsModule:wav_triangle_n_image"; + command = "MaterialEditorGui.updateWaveType();"; + tooltip="Triangle Wave"; + hovertime = "1000"; + groupNum = "0"; + waveType = "Triangle"; + tooltipprofile = "ToolsGuiToolTipProfile"; + }; + new GuiBitmapButtonCtrl(){ + profile = "ToolsGuiDefaultProfile"; + buttonType = "RadioButton"; + position = "33 0"; + Extent = "13 13"; + bitmapAsset = "ToolsModule:wav_square_n_image"; + command = "MaterialEditorGui.updateWaveType();"; + tooltip="Square Wave"; + hovertime = "1000"; + groupNum = "0"; + waveType = "Square"; + tooltipprofile = "ToolsGuiToolTipProfile"; + }; + }; + + new GuiControl() { + class = "AggregateControl"; + position = "0 61"; + Extent = "300 20"; + HorizSizing = "width"; + + + new GuiTextCtrl() { + profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "1 2"; + Extent = "100 16"; + text = "Frequency"; + }; + + new GuiTextEditCtrl() { // frequence + canSaveDynamicFields = "0"; + internalName = "WaveTextEditFreq"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiTextEditProfile"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "260 1"; + Extent = "34 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateWaveFreq();"; + hovertime = "1000"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + text = "0"; + maxLength = "1024"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + password = "0"; + passwordMask = "*"; + }; + new GuiSliderCtrl() { // freqency + canSaveDynamicFields = "0"; + internalName = "WaveSliderFreq"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiSliderProfile"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "120 4"; + Extent = "137 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "MaterialEditorGui.updateWaveFreq(true, true);"; + AltCommand = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateWaveFreq(true, false);"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Changes Wave Frequency"; + hovertime = "1000"; + range = "0 10"; + ticks = "9"; + value = "0"; + }; + + }; + + new GuiControl() { + class = "AggregateControl"; + position = "0 40"; + Extent = "300 20"; + HorizSizing = "width"; + + new GuiTextCtrl() { + profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "1 2"; + Extent = "100 16"; + text = "Amplitude"; + }; + + new GuiTextEditCtrl() { // amplitude + Profile = "ToolsGuiTextEditProfile"; + internalName = "WaveTextEditAmp"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "260 1"; + Extent = "34 18"; + Command = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateWaveAmp();"; + hovertime = "1000"; + text = "0"; + }; + new GuiSliderCtrl() { // amplitude + canSaveDynamicFields = "0"; + internalName = "WaveSliderAmp"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiSliderProfile"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "120 4"; + Extent = "137 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "MaterialEditorGui.updateWaveAmp(true, true);"; + AltCommand = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateWaveAmp(true, false);"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Changes Wave Amplitude"; + hovertime = "1000"; + range = "0 1"; + ticks = "1"; + value = "0"; + }; + + }; + }; + + %this-->stack.add(%container); +} + +function MaterialEditorGui::updateWaveType(%this) +{ + for( %radioButton = 0; %radioButton < MaterialEditorGuiWindow-->WaveButtonContainer.getCount(); %radioButton++ ) + { + if( MaterialEditorGuiWindow-->WaveButtonContainer.getObject(%radioButton).getValue() == 1 ) + %type = MaterialEditorGuiWindow-->WaveButtonContainer.getObject(%radioButton).waveType; + } + + %layer = MaterialEditorGui.currentLayer; + MaterialEditorGui.updateActiveMaterial("waveType[" @ %layer @ "]", %type); +} + +function MaterialEditorGui::updateWaveAmp(%this, %isSlider, %onMouseUp) +{ + %layer = MaterialEditorGui.currentLayer; + %amp = MaterialEditorGuiWindow-->WaveTextEditAmp.getText(); + MaterialEditorGui.updateActiveMaterial("waveAmp[" @ %layer @ "]", %amp, %isSlider, %onMouseUp); +} + +function MaterialEditorGui::updateWaveFreq(%this, %isSlider, %onMouseUp) +{ + %layer = MaterialEditorGui.currentLayer; + %freq = MaterialEditorGuiWindow-->WaveTextEditFreq.getText(); + MaterialEditorGui.updateActiveMaterial("waveFreq[" @ %layer @ "]", %freq, %isSlider, %onMouseUp); +} + +function GuiInspectorGroup::buildMaterialSequenceAnimationField(%this, %fieldName, %fieldLabel, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) +{ + %container = new GuiContainer(){ // image Sequence Animation Properties + profile="inspectorStyleRolloutInnerProfile"; + isContainer = "1"; + position = "-1 480"; + Extent = "300 80"; + HorizSizing = "width"; + + new GuiBitmapCtrl(){ + position = "0 5"; + extent ="300 2"; + HorizSizing = "width"; + bitmapAsset ="ToolsModule:separator_v_image"; + }; + + new GuiCheckboxCtrl() { + Profile = "ToolsGuiInspectorCheckBoxTitleProfile"; + internalName = "SequenceAnimation"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "4 10"; + Extent = "200 16"; + MinExtent = "8 2"; + text = " Image Sequence"; + Command = "MaterialEditorGui.updateAnimationFlags();"; + groupNum = "-1"; + }; + + + new GuiControl() { + class = "AggregateControl"; + position = "0 28"; + Extent = "300 20"; + HorizSizing = "width"; + + new GuiTextCtrl() { + profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "1 2"; + Extent = "100 16"; + text = "Frames / Sec"; + }; + + new GuiTextEditCtrl() { + canSaveDynamicFields = "0"; + internalName = "SequenceTextEditFPS"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiTextEditProfile"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "260 1"; + Extent = "34 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateSequenceFPS();"; + hovertime = "1000"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + text = "0"; + maxLength = "1024"; + }; + new GuiSliderCtrl() { + canSaveDynamicFields = "0"; + internalName = "SequenceSliderFPS"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiSliderProfile"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "120 4"; + Extent = "137 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "MaterialEditorGui.updateSequenceFPS(true, true);"; + AltCommand = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateSequenceFPS(true, false);"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "How many frames to display per second."; + hovertime = "1000"; + range = "0 30"; + ticks = "5"; + value = "0"; + }; + }; + + new GuiControl() { + class = "AggregateControl"; + position = "0 49"; + Extent = "300 20"; + HorizSizing = "width"; + + new GuiTextCtrl() { + profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "1 2"; + Extent = "100 16"; + text = "Frames"; + }; + + new GuiTextEditCtrl() { // size + Profile = "ToolsGuiTextEditProfile"; + internalName = "SequenceTextEditSSS"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "260 1"; + Extent = "34 18"; + Command = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateSequenceSSS();"; + hovertime = "1000"; + text = "0"; + }; + new GuiSliderCtrl() { //size + canSaveDynamicFields = "0"; + internalName = "SequenceSliderSSS"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiSliderProfile"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "120 4"; + Extent = "137 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "MaterialEditorGui.updateSequenceSSS(true, true);"; + AltCommand = "$ThisControl.getParent().updateFromChild($ThisControl); MaterialEditorGui.updateSequenceSSS(true, false);"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "How many frames in the sequence."; + hovertime = "1000"; + range = "0 100"; + ticks = "9"; + value = "0"; + }; + }; + }; + + %this-->stack.add(%container); +} + +function MaterialEditorGui::updateSequenceFPS(%this, %isSlider, %onMouseUp) +{ + %layer = MaterialEditorGui.currentLayer; + %fps = MaterialEditorGuiWindow-->SequenceTextEditFPS.getText(); + MaterialEditorGui.updateActiveMaterial("sequenceFramePerSec[" @ %layer @ "]", %fps, %isSlider, %onMouseUp); +} + +function MaterialEditorGui::updateSequenceSSS(%this, %isSlider, %onMouseUp) +{ + %layer = MaterialEditorGui.currentLayer; + %sss = 1 / MaterialEditorGuiWindow-->SequenceTextEditSSS.getText(); + MaterialEditorGui.updateActiveMaterial("sequenceSegmentSize[" @ %layer @ "]", %sss, %isSlider, %onMouseUp); +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/materialEditor/scripts/materialEditor.ed.tscript b/Templates/BaseGame/game/tools/materialEditor/scripts/materialEditor.ed.tscript index 7dca80cba..897e81074 100644 --- a/Templates/BaseGame/game/tools/materialEditor/scripts/materialEditor.ed.tscript +++ b/Templates/BaseGame/game/tools/materialEditor/scripts/materialEditor.ed.tscript @@ -77,52 +77,6 @@ function MaterialEditorGui::open(%this) // These guis are also pushed onto Canvas, which means they shouldn't be parented // by editorgui materialSelector.setVisible(0); - matEdSaveDialog.setVisible(0); - - MaterialEditorPropertiesWindow-->matEd_cubemapEditBtn.setVisible(0); - - //Setup our dropdown menu contents. - //Blending Modes - MaterialEditorPropertiesWindow-->blendingTypePopUp.clear(); - MaterialEditorPropertiesWindow-->blendingTypePopUp.add(None,0); - MaterialEditorPropertiesWindow-->blendingTypePopUp.add(preMul,1); - MaterialEditorPropertiesWindow-->blendingTypePopUp.add(LerpAlpha,2); - MaterialEditorPropertiesWindow-->blendingTypePopUp.add(Mul,3); - MaterialEditorPropertiesWindow-->blendingTypePopUp.add(Add,4); - MaterialEditorPropertiesWindow-->blendingTypePopUp.add(AddAlpha,5); - MaterialEditorPropertiesWindow-->blendingTypePopUp.add(Sub,6); - MaterialEditorPropertiesWindow-->blendingTypePopUp.setSelected( 0, false ); - - //Reflection Types - MaterialEditorPropertiesWindow-->reflectionTypePopUp.clear(); - MaterialEditorPropertiesWindow-->reflectionTypePopUp.add("None",0); - MaterialEditorPropertiesWindow-->reflectionTypePopUp.add("cubemap",1); - MaterialEditorPropertiesWindow-->reflectionTypePopUp.setSelected( 0, false ); - - //Sounds - MaterialEditorPropertiesWindow-->footstepSoundPopup.clear(); - MaterialEditorPropertiesWindow-->impactSoundPopup.clear(); - - %sounds = "" TAB "" TAB "" TAB "" TAB ""; // Default sounds - - %assetQuery = new AssetQuery(); - AssetDatabase.findAssetType(%assetQuery, "SoundAsset"); - - %count = %assetQuery.getCount(); - // Get custom sound assets - for(%i=0; %i < %count; %i++) - { - %assetId = %assetQuery.getAsset(%i); - %sounds = %sounds TAB %assetId; - } - - %count = getFieldCount(%sounds); - for (%i = 0; %i < %count; %i++) - { - %name = getField(%sounds, %i); - MaterialEditorPropertiesWindow-->footstepSoundPopup.add(%name); - MaterialEditorPropertiesWindow-->impactSoundPopup.add(%name); - } //Preview Models matEd_quickPreview_Popup.clear(); @@ -135,15 +89,14 @@ function MaterialEditorGui::open(%this) matEd_quickPreview_Popup.setSelected( 0, false ); matEd_quickPreview_Popup.selected = matEd_quickPreview_Popup.getText(); - MaterialEditorPropertiesWindow-->MaterialLayerCtrl.clear(); - MaterialEditorPropertiesWindow-->MaterialLayerCtrl.add("Layer 0",0); - MaterialEditorPropertiesWindow-->MaterialLayerCtrl.add("Layer 1",1); - MaterialEditorPropertiesWindow-->MaterialLayerCtrl.add("Layer 2",2); - MaterialEditorPropertiesWindow-->MaterialLayerCtrl.add("Layer 3",3); - MaterialEditorPropertiesWindow-->MaterialLayerCtrl.setSelected( 0, false ); + MaterialEditorLayerSelector.clear(); + MaterialEditorLayerSelector.add("Layer 0",0); + MaterialEditorLayerSelector.add("Layer 1",1); + MaterialEditorLayerSelector.add("Layer 2",2); + MaterialEditorLayerSelector.add("Layer 3",3); + MaterialEditorLayerSelector.setSelected( 0, false ); //Sift through the RootGroup and find all loaded material items. - MaterialEditorGui.updateAllFields(); MaterialEditorGui.updatePreviewObject(); // If no selected object; go to material mode. And edit the last selected material @@ -156,6 +109,8 @@ function MaterialEditorGui::open(%this) else MaterialEditorGui.prepareActiveMaterial( "", true ); + MaterialEditorGui.refreshSchedule = MaterialEditorGui.schedule(32, "guiSync", materialEd_previewMaterial); + MaterialEditorGui.preventUndo = false; } @@ -254,8 +209,7 @@ function MaterialEditorGui::selectMaterialAsset(%this, %assetId) function MaterialEditorGui::onWake(%this) { - %fixedWindow = MaterialEditorPreviewWindow; - %fluidWindow = MaterialEditorPropertiesWindow; + %fixedWindow = MaterialEditorGuiWindow; if(EditorSettings.value( "WorldEditor/forceSidebarToSide" ) == 1) { @@ -282,30 +236,14 @@ function MaterialEditorGui::maxSize(%this, %window) // prevent onResize after a resize %this.resizing = false; - %fixedWindow = MaterialEditorPreviewWindow; - %fluidWindow = MaterialEditorPropertiesWindow; + %fluidWindow = MaterialEditorGuiWindow; %offset = -1; // tweak the vertical offset so that it aligns neatly - %top = EditorGuiToolbar.extent.y + %offset; - %bottom = %top + 59; - %maxHeight = Canvas.extent.y - %top - %bottom; - - // --- Fixed window (top) ------------------------------------------------ - // put it back if it moved - %fixedWindow.position.x = Canvas.extent.x - %fixedWindow.extent.x; - %fixedWindow.position.y = %top; - - // don't go beyond the canvas - if(%fixedWindow.extent.y > %maxHeight) - %fixedWindow.extent.y = %maxHeight - %fluidWindow.extent.y; + %toolbarGlobalPos = EditorGuiToolbar.getGlobalPosition(); + %top = %toolbarGlobalPos.y + EditorGuiToolbar.extent.y + %offset; + %bottom = %top + 15; - %position = %fixedWindow.position.x SPC %fixedWindow.position.y; - %extent = %window.extent.x SPC %fixedWindow.extent.y; - %fixedWindow.resize(%position.x, %position.y, %extent.x, %extent.y); - - // --- Fluid window (bottom) --------------------------------------------- - // position is relative to the top window - %position = %fixedWindow.position.x SPC %fixedWindow.extent.y + %top; - %extent = %window.extent.x SPC Canvas.extent.y - %fixedWindow.extent.y - %bottom; + %position = Canvas.extent.x - %fluidWindow.extent.x SPC %top; + %extent = %window.extent.x SPC Canvas.extent.y - EWorldEditorStatusBarInfo.extent.y - %bottom; %fluidWindow.resize(%position.x, %position.y, %extent.x, %extent.y); // --- AssetBrowser window ---------------------------------------------- @@ -334,26 +272,10 @@ function MaterialEditorGui::maxSize(%this, %window) windowConsoleControl.resize(0, %consolePosY, %consoleWidth, %consoleHeight); } } - } + } } -function MaterialEditorPreviewWindow::onMouseDragged(%this) -{ - %parent = MaterialEditorGui; - - if(%parent.panelHidden == true) - { - %parent.showSidePanel(); - } - - if(%parent.resizing == false && %parent.docked == true) - { - %parent.resizing = true; - %parent.maxSize(%this); - } -} - -function MaterialEditorPropertiesWindow::onMouseDragged(%this) +function MaterialEditorGuiWindow::onMouseDragged(%this) { %parent = MaterialEditorGui; @@ -371,8 +293,8 @@ function MaterialEditorPropertiesWindow::onMouseDragged(%this) function MaterialEditorGui::onResize(%this, %newPosition, %newExtent) { - // Window to focus on (mostly the fluid window) - %window = MaterialEditorPreviewWindow; + // Window to focus on + %window = MaterialEditorGuiWindow; if(%window.panelHidden == true) { @@ -383,33 +305,28 @@ function MaterialEditorGui::onResize(%this, %newPosition, %newExtent) { // Only resize once %this.resizing = true; - %this.maxSize(%window); + %this.maxSize(); } } function MaterialEditorGui::dockSidePanel() { %parent = MaterialEditorGui; - %fixedWindow = MaterialEditorPreviewWindow; - %fluidWindow = MaterialEditorPropertiesWindow; + %fixedWindow = MaterialEditorGuiWindow; if(%parent.docked == true) return; // Move and resize the window(s) %parent.resizing = true; - %parent.maxSize(%fluidWindow); + %parent.maxSize(); %parent.docked = true; - %fluidWindow.onMouseDragged(); // Lock the windows in place %fixedWindow.canCollapse = "0"; %fixedWindow.canMove = "0"; - %fluidWindow.canCollapse = "0"; - %fluidWindow.canMove = "0"; - MaterialEditorGui_UnDockBtn.Visible = "1"; MaterialEditorGui_DockBtn.Visible = "0"; @@ -420,8 +337,7 @@ function MaterialEditorGui::dockSidePanel() function MaterialEditorGui::releaseSidePanel() { %parent = MaterialEditorGui; - %fixedWindow = MaterialEditorPreviewWindow; - %fluidWindow = MaterialEditorPropertiesWindow; + %fixedWindow = MaterialEditorGuiWindow; if(%parent.docked == false) return; @@ -430,9 +346,6 @@ function MaterialEditorGui::releaseSidePanel() %fixedWindow.canCollapse = "1"; %fixedWindow.canMove = "1"; - %fluidWindow.canCollapse = "1"; - %fluidWindow.canMove = "1"; - MaterialEditorGui_UnDockBtn.Visible = "0"; MaterialEditorGui_DockBtn.Visible = "1"; @@ -443,10 +356,6 @@ function MaterialEditorGui::releaseSidePanel() %position = %fixedWindow.position.x - 6 SPC %fixedWindow.position.y + 6; %extent = %fixedWindow.extent.x SPC %fixedWindow.extent.y; %fixedWindow.resize(%position.x, %position.y, %extent.x, %extent.y); - - %position = %fluidWindow.position.x - 6 SPC %fluidWindow.position.y + 6; - %extent = %fluidWindow.extent.x SPC %fluidWindow.extent.y - 12; - %fluidWindow.resize(%position.x, %position.y, %extent.x, %extent.y); %parent.docked = false; %parent.resizing = false; @@ -455,31 +364,24 @@ function MaterialEditorGui::releaseSidePanel() function MaterialEditorGui::hideSidePanel() { %parent = MaterialEditorGui; - %fixedWindow = MaterialEditorPreviewWindow; - %fluidWindow = MaterialEditorPropertiesWindow; + %fixedWindow = MaterialEditorGuiWindow; MaterialEditorGui_showBtn.Visible = "1"; MaterialEditorGui_hideBtn.Visible = "0"; // hide the content of the panels %fixedWindow.titleText = %fixedWindow.text; - %fluidWindow.titleText = %fluidWindow.text; %fixedWindow.text = ""; matEd_previewPanel.Visible = "0"; matEd_quickPreview_Popup.Visible = "0"; - %fluidWindow.text = ""; MaterialEditorGuiContent.Visible = "0"; // Let's do a resize so that the panel is collapsed to the side %position = Canvas.extent.x - 24 SPC %fixedWindow.position.y; %extent = %fixedWindow.extent.x SPC %fixedWindow.extent.y; %fixedWindow.resize(%position.x, %position.y, %extent.x, %extent.y); - - %position = Canvas.extent.x - 24 SPC %fluidWindow.position.y; - %extent = %fluidWindow.extent.x SPC %fluidWindow.extent.y; - %fluidWindow.resize(%position.x, %position.y, %extent.x, %extent.y); %parent.panelHidden = true; } @@ -487,8 +389,7 @@ function MaterialEditorGui::hideSidePanel() function MaterialEditorGui::showSidePanel() { %parent = MaterialEditorGui; - %fixedWindow = MaterialEditorPreviewWindow; - %fluidWindow = MaterialEditorPropertiesWindow; + %fixedWindow = MaterialEditorGuiWindow; MaterialEditorGui_showBtn.Visible = "0"; MaterialEditorGui_hideBtn.Visible = "1"; @@ -499,11 +400,10 @@ function MaterialEditorGui::showSidePanel() matEd_previewPanel.Visible = "1"; matEd_quickPreview_Popup.Visible = "1"; - %fluidWindow.text = %fluidWindow.titleText; MaterialEditorGuiContent.Visible = "1"; %parent.resizing = true; - %parent.maxSize(%fluidWindow); + %parent.maxSize(); %parent.panelHidden = false; } @@ -513,6 +413,7 @@ function MaterialEditorGui::showSidePanel() //============================================================================== // SubMaterial(Material Target) -- Supports different ways to grab the +// SubMaterial(Material Target) -- Supports different ways to grab the // material from the dropdown list. We're here either because- // 1. We have switched over from another editor with an object locked in the // $Tools::materialEditorList variable @@ -659,11 +560,6 @@ function MaterialEditorGui::prepareActiveObject( %this, %override ) //============================================================================== // Helper functions to help create categories and manage category lists -function MaterialEditorGui::updateAllFields(%this) -{ - matEd_cubemapEd_availableCubemapList.clear(); -} - function MaterialEditorGui::updatePreviewObject(%this) { %newModel = matEd_quickPreview_Popup.getValue(); @@ -672,32 +568,32 @@ function MaterialEditorGui::updatePreviewObject(%this) { case "sphere": matEd_quickPreview_Popup.selected = %newModel; - matEd_previewObjectView.setModel("tools/materialEditor/gui/spherepreview.dts"); + matEd_previewObjectView.setModel("ToolsModule:spherepreview"); matEd_previewObjectView.setOrbitDistance(4); case "cube": matEd_quickPreview_Popup.selected = %newModel; - matEd_previewObjectView.setModel("tools/materialEditor/gui/cubepreview.dts"); + matEd_previewObjectView.setModel("ToolsModule:cubepreview"); matEd_previewObjectView.setOrbitDistance(5); case "pyramid": matEd_quickPreview_Popup.selected = %newModel; - matEd_previewObjectView.setModel("tools/materialEditor/gui/pyramidpreview.dts"); + matEd_previewObjectView.setModel("ToolsModule:pyramidpreview"); matEd_previewObjectView.setOrbitDistance(5); case "cylinder": matEd_quickPreview_Popup.selected = %newModel; - matEd_previewObjectView.setModel("tools/materialEditor/gui/cylinderpreview.dts"); + matEd_previewObjectView.setModel("ToolsModule:cylinderpreview"); matEd_previewObjectView.setOrbitDistance(4.2); case "torus": matEd_quickPreview_Popup.selected = %newModel; - matEd_previewObjectView.setModel("tools/materialEditor/gui/toruspreview.dts"); + matEd_previewObjectView.setModel("ToolsModule:toruspreview"); matEd_previewObjectView.setOrbitDistance(4.2); case "knot": matEd_quickPreview_Popup.selected = %newModel; - matEd_previewObjectView.setModel("tools/materialEditor/gui/torusknotpreview.dts"); + matEd_previewObjectView.setModel("ToolsModule:torusknotpreview"); } } @@ -765,7 +661,6 @@ function MaterialEditorGui::setActiveMaterial( %this, %material ) // Copy materials over to other references MaterialEditorGui.copyMaterials( MaterialEditorGui.currentMaterial, materialEd_previewMaterial ); MaterialEditorGui.copyMaterials( MaterialEditorGui.currentMaterial, notDirtyMaterial ); - MaterialEditorGui.guiSync( materialEd_previewMaterial ); // Necessary functionality in order to render correctly materialEd_previewMaterial.flush(); @@ -774,6 +669,11 @@ function MaterialEditorGui::setActiveMaterial( %this, %material ) MaterialEditorGui.currentMaterial.reload(); MaterialEditorGui.setMaterialNotDirty(); + + MaterialEditorPropInspector.groupFilters = "-General -Internal -Ungrouped -Editing -Object -Persistence -Dynamic Fields"; + + MaterialEditorPropInspector.inspect(materialEd_previewMaterial); + MaterialEditorGui.refreshSchedule = MaterialEditorGui.schedule(32, "guiSync", materialEd_previewMaterial ); } function MaterialEditorGui::isMatEditorMaterial(%this, %material) @@ -785,10 +685,7 @@ function MaterialEditorGui::isMatEditorMaterial(%this, %material) function MaterialEditorGui::setMaterialNotDirty(%this) { - %propertyText = ":: Material Editor - Properties"; - %previewText = ":: Material Editor - Preview"; - MaterialEditorPropertiesWindow.text = %propertyText; - MaterialEditorPreviewWindow.text = %previewText; + MaterialEditorGuiWindow.text = " :: Material Editor"; MaterialEditorGui.materialDirty = false; matEd_PersistMan.removeDirty(MaterialEditorGui.currentMaterial); @@ -796,10 +693,7 @@ function MaterialEditorGui::setMaterialNotDirty(%this) function MaterialEditorGui::setMaterialDirty(%this) { - %propertyText = ":: Material Editor - Properties *"; - %previewText = ":: Material Editor - Preview *"; - MaterialEditorPropertiesWindow.text = %propertyText; - MaterialEditorPreviewWindow.text = %previewText; + MaterialEditorGuiWindow.text = " :: Material Editor *"; MaterialEditorGui.materialDirty = true; @@ -1015,278 +909,178 @@ function MaterialEditorGui::copyMaterials( %this, %copyFrom, %copyTo) } -function MaterialEditorGui::guiSync( %this, %material ) +function MaterialEditorGui::guiSync( %this ) { %this.preventUndo = true; + //Setup our headers + %this-->selMaterialName.setText(MaterialEditorGui.currentMaterial.name); + %this-->selMaterialMapTo.setText(MaterialEditorGui.currentMaterial.mapTo); + %this-->selMaterialInheritFrom.setText(MaterialEditorGui.currentMaterial.inheritFrom); + if( MaterialEditorGui.currentMode $= "material" ) - { - MatEdMaterialMode-->selMaterialName.setText(MaterialEditorGui.currentMaterial.name); - MatEdMaterialMode-->selMaterialMapTo.setText(MaterialEditorGui.currentMaterial.mapTo); - } + SubMaterialSelector.setActive(false); else + SubMaterialSelector.setActive(true); + + //Streamline fields to our selected layer + MaterialEditorPropInspector.setForcedArrayIndex(MaterialEditorGui.currentLayer); + + // Dynamic elements + //do some presentation adjustments + %ormMapPresent = MaterialEditorGui.currentMaterial.ORMConfigMapAsset[MaterialEditorGui.currentLayer] !$= ""; + + %hasAOMap = MaterialEditorGui.currentMaterial.AOMapAsset[MaterialEditorGui.currentLayer] !$= ""; + %hasRoughMap = MaterialEditorGui.currentMaterial.RoughMapAsset[MaterialEditorGui.currentLayer] !$= ""; + %hasMetalMap = MaterialEditorGui.currentMaterial.MetalMapAsset[MaterialEditorGui.currentLayer] !$= ""; + + %group = MaterialEditorPropInspector.findExistentGroup("Light Influence Maps"); + if(isObject(%group)) { - if( MaterialEditorGui.currentObject.isMethod("getModelFile") ) + if(%ormMapPresent) { - %sourcePath = MaterialEditorGui.currentObject.getModelFile(); - if( %sourcePath !$= "" ) - { - MatEdTargetMode-->selMaterialMapTo.ToolTip = %sourcePath; - %sourceName = fileName(%sourcePath); - MatEdTargetMode-->selMaterialMapTo.setText(%sourceName); - MatEdTargetMode-->selMaterialName.setText(MaterialEditorGui.currentMaterial.name); - } + %group.hideField("isSRGb",false); + %group.hideField("AOMapAsset"); + %group.hideField("aoChan"); + %group.hideField("RoughMapAsset"); + %group.hideField("roughness"); + %group.hideField("roughnessChan"); + %group.hideField("MetalMapAsset"); + %group.hideField("metalness"); + %group.hideField("metalChan"); + %group.hideField("save"); } else { - %info = MaterialEditorGui.currentObject.getClassName(); - MatEdTargetMode-->selMaterialMapTo.ToolTip = %info; - MatEdTargetMode-->selMaterialMapTo.setText(%info); - MatEdTargetMode-->selMaterialName.setText(MaterialEditorGui.currentMaterial.name); + %group.hideField("isSRGb"); + + %hideSliders = (%hasAOMap || %hasRoughMap || %hasMetalMap); + + %group.hideField("aoChan", !%hideSliders); + + %group.hideField("roughness", %hideSliders); + %group.hideField("roughnessChan", !%hideSliders); + + %group.hideField("metalness", %hideSliders); + %group.hideField("metalChan", !%hideSliders); + + %group.hideField("AOMapAsset",false); + %group.hideField("RoughMapAsset",false); + %group.hideField("MetalMapAsset",false); } - } - - MaterialEditorPropertiesWindow-->alphaRefTextEdit.setText((%material).alphaRef); - MaterialEditorPropertiesWindow-->alphaRefSlider.setValue((%material).alphaRef); - MaterialEditorPropertiesWindow-->doubleSidedCheckBox.setValue((%material).doubleSided); - MaterialEditorPropertiesWindow-->transZWriteCheckBox.setValue((%material).translucentZWrite); - MaterialEditorPropertiesWindow-->alphaTestCheckBox.setValue((%material).alphaTest); - MaterialEditorPropertiesWindow-->castShadows.setValue((%material).castShadows); - MaterialEditorPropertiesWindow-->castDynamicShadows.setValue((%material).castDynamicShadows); - MaterialEditorPropertiesWindow-->translucentCheckbox.setValue((%material).translucent); - switch$((%material).translucentBlendOp) - { - case "None": %selectedNum = 0; - case "preMul": %selectedNum = 1; - case "LerpAlpha": %selectedNum = 2; - case "Mul": %selectedNum = 3; - case "": %selectedNum = 4; - case "AddAlpha": %selectedNum = 5; - case "Sub": %selectedNum = 6; - } - MaterialEditorPropertiesWindow-->blendingTypePopUp.setSelected(%selectedNum); - - if((%material).cubemap !$= "") - { - MaterialEditorPropertiesWindow-->matEd_cubemapEditBtn.setVisible(1); - MaterialEditorPropertiesWindow-->reflectionTypePopUp.setSelected(1); - } - else if((%material).dynamiccubemap) - { - MaterialEditorPropertiesWindow-->matEd_cubemapEditBtn.setVisible(0); - MaterialEditorPropertiesWindow-->reflectionTypePopUp.setSelected(2); - } - else if((%material).planarReflection) - { - MaterialEditorPropertiesWindow-->matEd_cubemapEditBtn.setVisible(0); - MaterialEditorPropertiesWindow-->reflectionTypePopUp.setSelected(3); - } - else - { - MaterialEditorPropertiesWindow-->matEd_cubemapEditBtn.setVisible(0); - MaterialEditorPropertiesWindow-->reflectionTypePopUp.setSelected(0); - } - - MaterialEditorPropertiesWindow-->effectColor0Swatch.color = (%material).effectColor[0]; - MaterialEditorPropertiesWindow-->effectColor1Swatch.color = (%material).effectColor[1]; - MaterialEditorPropertiesWindow-->showFootprintsCheckbox.setValue((%material).showFootprints); - MaterialEditorPropertiesWindow-->showDustCheckbox.setValue((%material).showDust); - MaterialEditorGui.updateSoundPopup("Footstep", (%material).footstepSoundId, (%material).customFootstepSound); - MaterialEditorGui.updateSoundPopup("Impact", (%material).impactSoundId, (%material).customImpactSound); - - //layer specific controls are located here - %layer = MaterialEditorGui.currentLayer; - - //Diffuse - %diffuseMap = (%material).getDiffuseMap(%layer); - %diffuseMapText = %diffuseMap !$= "" && %diffuseMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getDiffuseMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->diffuseMapNameText.setText( %diffuseMapText ); - MaterialEditorPropertiesWindow-->diffuseMapDisplayBitmap.setBitmap( getAssetPreviewImage(%diffuseMap) ); - - //Normal - %normalMap = (%material).getNormalMap(%layer); - %normalMapText = %normalMap !$= "" && %normalMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getNormalMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->normalMapNameText.setText( %normalMapText ); - MaterialEditorPropertiesWindow-->normalMapDisplayBitmap.setBitmap( getAssetPreviewImage(%normalMap) ); - - //ORM Config - %ormMap = (%material).getORMConfigMap(%layer); - %hasOrmMap = (%ormMap !$= "" && %ormMap !$=$MaterialEditor::emptyMaterialImage); - %ormMapText = %hasOrmMap ? (%material).getORMConfigMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->ORMConfigMapNameText.setText(%ormMapText ); - MaterialEditorPropertiesWindow-->ORMConfigMapDisplayBitmap.setBitmap( getAssetPreviewImage(%ormMap) ); - - //show or hide depending on if we have a map assigned here - MaterialEditorPropertiesWindow-->RoughnessTextEdit.setVisible(!%hasOrmMap); - MaterialEditorPropertiesWindow-->RoughnessSlider.setVisible(!%hasOrmMap); - MaterialEditorPropertiesWindow-->MetalnessTextEdit.setVisible(!%hasOrmMap); - MaterialEditorPropertiesWindow-->MetalnessSlider.setVisible(!%hasOrmMap); - - if(%hasOrmMap) - { - MaterialEditorPropertiesWindow-->isSRGBCheckbox.setValue((%material).isSRGB[%layer]); - MaterialEditorPropertiesWindow-->invertRoughnessCheckbox.setValue((%material).invertRoughness[%layer]); - } - else - { - MaterialEditorPropertiesWindow-->RoughnessSlider.setValue((%material).roughness, true); - MaterialEditorPropertiesWindow-->MetalnessSlider.setValue((%material).metalness, true); - } - MaterialEditorPropertiesWindow-->isSRGBCheckbox.setVisible(%hasOrmMap); - MaterialEditorPropertiesWindow-->invertRoughnessCheckbox.setVisible(%hasOrmMap); - - //AOMap - %aoMap = (%material).getAOMap(%layer); - %aoMapText = %aoMap !$= "" && %aoMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getAOMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->aoMapNameText.setText( %aoMapText ); - MaterialEditorPropertiesWindow-->aoMapDisplayBitmap.setBitmap( getAssetPreviewImage(%aoMap) ); - - //RoughMap - %roughMap = (%material).getRoughMap(%layer); - %roughMapText = %roughMap !$= "" && %roughMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getRoughMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->roughMapNameText.setText( %roughMapText ); - MaterialEditorPropertiesWindow-->roughMapDisplayBitmap.setBitmap( getAssetPreviewImage(%roughMap) ); - - //MetalMap - %metalMap = (%material).getMetalMap(%layer); - %metalMapText = %metalMap !$= "" && %metalMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getMetalMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->metalMapNameText.setText( %metalMapText ); - MaterialEditorPropertiesWindow-->metalMapDisplayBitmap.setBitmap( getAssetPreviewImage(%metalMap) ); - - //GlowMap - %glowMap = (%material).getGlowMap(%layer); - %glowMapText = %glowMap !$= "" && %glowMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getGlowMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->glowMapNameText.setText( %glowMapText ); - MaterialEditorPropertiesWindow-->glowMapDisplayBitmap.setBitmap( getAssetPreviewImage(%glowMap) ); + %group.refresh(); - //Overlay - %overlayMap = (%material).getOverlayMap(%layer); - %overlayMapText = %overlayMap !$= "" && %overlayMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getOverlayMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->overlayMapNameText.setText( %overlayMapText ); - MaterialEditorPropertiesWindow-->overlayMapDisplayBitmap.setBitmap( getAssetPreviewImage(%overlayMap) ); - - //Detail - %detailMap = (%material).getDetailMap(%layer); - %detailMapText = %detailMap !$= "" && %detailMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getDetailMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->detailMapNameText.setText( %detailMapText ); - MaterialEditorPropertiesWindow-->detailMapDisplayBitmap.setBitmap( getAssetPreviewImage(%detailMap) ); - - //Detail Normal - %detailNormalMap = (%material).getDetailNormalMap(%layer); - %detailNormalMapText = %detailNormalMap !$= "" && %detailNormalMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getDetailNormalMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->detailNormalMapNameText.setText( %detailNormalMapText ); - MaterialEditorPropertiesWindow-->detailNormalMapDisplayBitmap.setBitmap( getAssetPreviewImage(%detailNormalMap) ); - - //Light - %lightMap = (%material).getLightMap(%layer); - %lightMapText = %lightMap !$= "" && %lightMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getLightMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->lightMapNameText.setText( %lightMapText ); - MaterialEditorPropertiesWindow-->lightMapDisplayBitmap.setBitmap( getAssetPreviewImage(%lightMap) ); - - //Tone - %toneMap = (%material).getToneMap(%layer); - %toneMapText = %toneMap !$= "" && %toneMap !$=$MaterialEditor::emptyMaterialImage ? (%material).getToneMapAsset(%layer) : "None"; - MaterialEditorPropertiesWindow-->toneMapNameText.setText( %toneMapText ); - MaterialEditorPropertiesWindow-->toneMapDisplayBitmap.setBitmap( getAssetPreviewImage(%toneMap) ); - - MaterialEditorPropertiesWindow-->accuScaleTextEdit.setText((%material).accuScale[%layer]); - MaterialEditorPropertiesWindow-->accuScaleTextEdit.setText((%material).accuScale[%layer]); - MaterialEditorPropertiesWindow-->accuDirectionTextEdit.setText((%material).accuDirection[%layer]); - MaterialEditorPropertiesWindow-->accuDirectionTextEdit.setText((%material).accuDirection[%layer]); - MaterialEditorPropertiesWindow-->accuStrengthTextEdit.setText((%material).accuStrength[%layer]); - MaterialEditorPropertiesWindow-->accuStrengthTextEdit.setText((%material).accuStrength[%layer]); - MaterialEditorPropertiesWindow-->accuCoverageTextEdit.setText((%material).accuCoverage[%layer]); - MaterialEditorPropertiesWindow-->accuCoverageTextEdit.setText((%material).accuCoverage[%layer]); - MaterialEditorPropertiesWindow-->accuSpecularTextEdit.setText((%material).accuSpecular[%layer]); - MaterialEditorPropertiesWindow-->accuSpecularTextEdit.setText((%material).accuSpecular[%layer]); - - MaterialEditorPropertiesWindow-->detailScaleTextEdit.setText( getWord((%material).detailScale[%layer], 0) ); - MaterialEditorPropertiesWindow-->detailNormalStrengthTextEdit.setText( getWord((%material).detailNormalMapStrength[%layer], 0) ); - - MaterialEditorPropertiesWindow-->colorTintSwatch.color = (%material).diffuseColor[%layer]; - MaterialEditorPropertiesWindow-->specularColorSwatch.color = (%material).specular[%layer]; - - MaterialEditorPropertiesWindow-->glowMulTextEdit.setText((%material).glowMul[%layer]); - MaterialEditorPropertiesWindow-->glowMulSlider.setValue((%material).glowMul[%layer]); - MaterialEditorPropertiesWindow-->glowCheckbox.setValue((%material).glow[%layer]); - MaterialEditorPropertiesWindow-->receiveShadowsCheckbox.setValue((%material).receiveShadows[%layer]); - MaterialEditorPropertiesWindow-->ignoreLightingCheckbox.setValue((%material).ignoreLighting[%layer]); - MaterialEditorPropertiesWindow-->parallaxTextEdit.setText((%material).parallaxScale[%layer]); - MaterialEditorPropertiesWindow-->parallaxSlider.setValue((%material).parallaxScale[%layer]); - - MaterialEditorPropertiesWindow-->useAnisoCheckbox.setValue((%material).useAnisotropic[%layer]); - MaterialEditorPropertiesWindow-->vertLitCheckbox.setValue((%material).vertLit[%layer]); - MaterialEditorPropertiesWindow-->vertColorSwatch.color = (%material).vertColor[%layer]; - MaterialEditorPropertiesWindow-->subSurfaceCheckbox.setValue((%material).subSurface[%layer]); - - // Animation properties - MaterialEditorPropertiesWindow-->RotationAnimation.setValue(0); - MaterialEditorPropertiesWindow-->ScrollAnimation.setValue(0); - MaterialEditorPropertiesWindow-->WaveAnimation.setValue(0); - MaterialEditorPropertiesWindow-->ScaleAnimation.setValue(0); - MaterialEditorPropertiesWindow-->SequenceAnimation.setValue(0); - - %flags = (%material).getAnimFlags(%layer); - %wordCount = getWordCount( %flags ); - for(%i = 0; %i != %wordCount; %i++) - { - switch$(getWord( %flags, %i)) - { - case "$rotate": MaterialEditorPropertiesWindow-->RotationAnimation.setValue(1); - case "$scroll": MaterialEditorPropertiesWindow-->ScrollAnimation.setValue(1); - case "$wave": MaterialEditorPropertiesWindow-->WaveAnimation.setValue(1); - case "$scale": MaterialEditorPropertiesWindow-->ScaleAnimation.setValue(1); - case "$sequence": MaterialEditorPropertiesWindow-->SequenceAnimation.setValue(1); - } + %group.addField("BakeComposite", "BakeCompositeButton", "Performs a bake operation of the ORM maps into a single composite texture asset."); + + %ormMapField = %group.findField("AOMapAsset[0]"); + %bakeBtnField = %group.findField("BakeComposite"); + + %group-->stack.reorderChild(%bakeBtnField, %ormMapField); } - MaterialEditorPropertiesWindow-->RotationTextEditU.setText( getWord((%material).rotPivotOffset[%layer], 0) ); - MaterialEditorPropertiesWindow-->RotationTextEditV.setText( getWord((%material).rotPivotOffset[%layer], 1) ); - MaterialEditorPropertiesWindow-->RotationSpeedTextEdit.setText( (%material).rotSpeed[%layer] ); - MaterialEditorPropertiesWindow-->RotationSliderU.setValue( getWord((%material).rotPivotOffset[%layer], 0) ); - MaterialEditorPropertiesWindow-->RotationSliderV.setValue( getWord((%material).rotPivotOffset[%layer], 1) ); - MaterialEditorPropertiesWindow-->RotationSpeedSlider.setValue( (%material).rotSpeed[%layer] ); - MaterialEditorPropertiesWindow-->RotationCrosshair.setPosition( 45*mAbs(getWord((%material).rotPivotOffset[%layer], 0))-2, 45*mAbs(getWord((%material).rotPivotOffset[%layer], 1))-2 ); - - MaterialEditorPropertiesWindow-->ScrollTextEditU.setText( getWord((%material).scrollDir[%layer], 0) ); - MaterialEditorPropertiesWindow-->ScrollTextEditV.setText( getWord((%material).scrollDir[%layer], 1) ); - MaterialEditorPropertiesWindow-->ScrollSpeedTextEdit.setText( (%material).scrollSpeed[%layer] ); - MaterialEditorPropertiesWindow-->ScrollSliderU.setValue( getWord((%material).scrollDir[%layer], 0) ); - MaterialEditorPropertiesWindow-->ScrollSliderV.setValue( getWord((%material).scrollDir[%layer], 1) ); - MaterialEditorPropertiesWindow-->ScrollSpeedSlider.setValue( (%material).scrollSpeed[%layer] ); - MaterialEditorPropertiesWindow-->ScrollCrosshair.setPosition( -(23 * getWord((%material).scrollDir[%layer], 0))+20, -(23 * getWord((%material).scrollDir[%layer], 1))+20); - - %waveType = (%material).waveType[%layer]; - for( %radioButton = 0; %radioButton < MaterialEditorPropertiesWindow-->WaveButtonContainer.getCount(); %radioButton++ ) + %animflags = MaterialEditorGui.currentMaterial.animFlags[MaterialEditorGui.currentLayer]; + %group = MaterialEditorPropInspector.findExistentGroup("Animation Properties"); + if(isObject(%group)) { - if( %waveType $= MaterialEditorPropertiesWindow-->WaveButtonContainer.getObject(%radioButton).waveType ) - MaterialEditorPropertiesWindow-->WaveButtonContainer.getObject(%radioButton).setStateOn(1); + %hideScroll = true; + //if (strstr(%animflags, "Scroll")>=0) + // %hideScroll = false; + %group.hideField("scrollDir",%hideScroll); + %group.hideField("scrollSpeed",%hideScroll); + + %group.addField("scrollProperties", "MaterialScrollAnimation", "Controls the scroll animation properties of this material"); + + %hideRotate = true; + //if (strstr(%animflags, "Rotate")>=0) + // %hideRotate = false; + %group.hideField("rotSpeed",%hideRotate); + %group.hideField("rotPivotOffset",%hideRotate); + + %group.addField("scrollProperties", "MaterialRotAnimation", "Controls the scroll animation properties of this material"); + + %hideWave = true; + //if (strstr(%animflags, "Wave")>=0) + // %hideWave = false; + %group.hideField("waveType",%hideWave); + %group.hideField("waveFreq",%hideWave); + %group.hideField("waveAmp",%hideWave); + + %group.addField("scrollProperties", "MaterialWaveAnimation", "Controls the scroll animation properties of this material"); + + %showScale = false; + //if (strstr(%animflags, "Scale")>=0) + // %showScale = true; + + %hideSequence = true; + //if (strstr(%animflags, "Sequence")>=0) + // %hideSequence = false; + %group.hideField("sequenceFramePerSec",%hideSequence); + %group.hideField("sequenceSegmentSize",%hideSequence); + + %group.addField("scrollProperties", "MaterialSequenceAnimation", "Controls the scroll animation properties of this material"); } - MaterialEditorPropertiesWindow-->WaveTextEditAmp.setText( (%material).waveAmp[%layer] ); - MaterialEditorPropertiesWindow-->WaveTextEditFreq.setText( (%material).waveFreq[%layer] ); - MaterialEditorPropertiesWindow-->WaveSliderAmp.setValue( (%material).waveAmp[%layer] ); - MaterialEditorPropertiesWindow-->WaveSliderFreq.setValue( (%material).waveFreq[%layer] ); - - %numFrames = mRound( 1 / (%material).sequenceSegmentSize[%layer] ); - - MaterialEditorPropertiesWindow-->SequenceTextEditFPS.setText( (%material).sequenceFramePerSec[%layer] ); - MaterialEditorPropertiesWindow-->SequenceTextEditSSS.setText( %numFrames ); - MaterialEditorPropertiesWindow-->SequenceSliderFPS.setValue( (%material).sequenceFramePerSec[%layer] ); - MaterialEditorPropertiesWindow-->SequenceSliderSSS.setValue( %numFrames ); + MaterialEditorPropInspector.loadCollapseState(); - // Accumulation - MaterialEditorPropertiesWindow-->accuCheckbox.setValue((%material).accuEnabled[%layer]); + cancel(MaterialEditorGui.refreshSchedule); - MaterialEditorPropertiesWindow-->accuCheckbox.setValue((%material).accuEnabled[%layer]); - - %this.getRoughChan((%material).RoughnessChan[%layer]); - %this.getAOChan((%material).AOChan[%layer]); - %this.getMetalChan((%material).metalChan[%layer]); %this.preventUndo = false; } +function MaterialEditorGui::deArrayFieldsInRollout(%this, %rollout, %index) +{ + echo("===================================="); + echo("Processing group for array index: " @ %index); + + %stack = %rollout.getObject(0); + %count = %stack.getCount(); + + if(!isObject(deArrayObjects)) + { + new ArrayObject(deArrayObjects){}; + } + + for(%i=0; %i < %count; %i++) + { + %fieldArrayGroup = %stack.getObject(%i); + %fieldArrayStack = %fieldArrayGroup.getObject(0); + echo("Field Array: " @ %fieldArrayGroup.caption @ ", ID: " @ %fieldArrayGroup @ ", type: " @ %fieldArrayGroup.getClassName()); + + %fieldCaption = %fieldArrayGroup.caption; + %newFieldCaption = getSubStr(%fieldCaption, 0, strLen(%fieldCaption) - 4); + + echo(" New Caption: " @ %newFieldCaption); + + %curLayerField = %fieldArrayStack.getObject(%index); + + echo(" Field Obj for cur layer: " @ %curLayerField); + + %stack.add(%curLayerField); + %curLayerField.setCaption(%newFieldCaption); + + deArrayObjects.add(%stack, %fieldArrayGroup); + + echo(" ---"); + } + + echo("Cleaning up field array groups"); + + for(%i=0; %i < deArrayObjects.count(); %i++) + { + %group = deArrayObjects.getKey(%i); + %obj = deArrayObjects.getValue(%i); + + echo("Removing: " @ %obj @ " from group: " @ %group); + + %group.remove(%obj); + } + + echo("===================================="); + + deArrayObjects.empty(); +} + //======================================= function MaterialEditorGui::getRoughChan(%this, %channel) { @@ -1373,6 +1167,56 @@ function MaterialEditorGui::updateActiveMaterialName(%this, %name) // to find and update all these references so they don't break when we rename the // Material. MaterialEditorGui.updateMaterialReferences( getRootScene(), %action.oldName, %action.newName ); + + //Give a bit of time for the updates to happen before we refresh the GUI + MaterialEditorGui.schedule(500, "syncGUI"); +} + +function MaterialEditorGui::updateActiveMaterialMapTo(%this, %name) +{ + %action = %this.createUndo(ActionUpdateActiveMaterialMapTo, "Update Active Material MapTo"); + %action.material = MaterialEditorGui.currentMaterial; + %action.object = MaterialEditorGui.currentObject; + %action.oldName = MaterialEditorGui.currentMaterial.mapTo; + %action.newName = %name; + MaterialEditorGui.submitUndo( %action ); + + MaterialEditorGui.currentMaterial.mapTo = %name; + + materialEd_previewMaterial.flush(); + materialEd_previewMaterial.reload(); + MaterialEditorGui.currentMaterial.flush(); + MaterialEditorGui.currentMaterial.reload(); + + //Give a bit of time for the updates to happen before we refresh the GUI + MaterialEditorGui.schedule(500, "syncGUI"); +} + +function MaterialEditorGui::updateActiveMaterialInheritFrom(%this, %name) +{ + if(AssetDatabase.isDeclaredAsset(%name)) + { + %assetDef = AssetDatabase.acquireAsset(%name); + if(isObject(%assetDef)) + %name = %assetDef.materialDefinitionName; + } + + %action = %this.createUndo(ActionUpdateActiveMaterialInheritFrom, "Update Active Material InheritFrom"); + %action.material = MaterialEditorGui.currentMaterial; + %action.object = MaterialEditorGui.currentObject; + %action.oldName = MaterialEditorGui.currentMaterial.inheritFrom; + %action.newName = %name; + MaterialEditorGui.submitUndo( %action ); + + MaterialEditorGui.currentMaterial.inheritFrom = %name; + + materialEd_previewMaterial.flush(); + materialEd_previewMaterial.reload(); + MaterialEditorGui.currentMaterial.flush(); + MaterialEditorGui.currentMaterial.reload(); + + //Give a bit of time for the updates to happen before we refresh the GUI + MaterialEditorGui.schedule(500, "syncGUI"); } function MaterialEditorGui::updateMaterialReferences( %this, %obj, %oldName, %newName ) @@ -1432,162 +1276,13 @@ function MaterialEditorGui::updateReflectionType( %this, %type ) } } -// Per-Layer Material Options - -// For update maps -// %action : 1 = change map -// %action : 0 = remove map - -function MaterialEditorGui::updateTextureMap( %this, %type, %action ) -{ - %layer = MaterialEditorGui.currentLayer; - - %this.updatingTextureType = %type; - - %bitmapCtrl = MaterialEditorPropertiesWindow.findObjectByInternalName( %type @ "MapDisplayBitmap", true ); - %textCtrl = MaterialEditorPropertiesWindow.findObjectByInternalName( %type @ "MapNameText", true ); - - if( %action ) - { - AssetBrowser.showDialog("ImageAsset", %this@".doUpdateTextureMap"); - } - else - { - %textCtrl.setText("None"); - %bitmapCtrl.setBitmap($MaterialEditor::emptyMaterialImage); - MaterialEditorGui.updateActiveMaterial(%type @ "Map[" @ %layer @ "]",""); - MaterialEditorGui.updateActiveMaterial(%type @ "MapAsset[" @ %layer @ "]",""); - } - MaterialEditorGui.guiSync( materialEd_previewMaterial ); -} - -function MaterialEditorGui::doUpdateTextureMap( %this, %assetId ) -{ - if(%assetId !$= "") - { - %layer = MaterialEditorGui.currentLayer; - - %type = %this.updatingTextureType; - - %bitmapCtrl = MaterialEditorPropertiesWindow.findObjectByInternalName( %type @ "MapDisplayBitmap", true ); - %textCtrl = MaterialEditorPropertiesWindow.findObjectByInternalName( %type @ "MapNameText", true ); - - %texture = getAssetPreviewImage(%assetId); - - if( %texture !$= "" && %texture !$= $MaterialEditor::emptyMaterialImage) - { - %bitmapCtrl.setBitmap(%texture); - - %bitmap = %bitmapCtrl.getBitmap(); - %bitmap = strreplace(%bitmap,"tools/materialEditor/scripts/",""); - %bitmapCtrl.setBitmap(%bitmap); - %textCtrl.setText(%assetId); - MaterialEditorGui.updateActiveMaterial(%type @ "Map[" @ %layer @ "]",""); - MaterialEditorGui.updateActiveMaterial(%type @ "MapAsset[" @ %layer @ "]", %assetId); - } - } - - %this.updatingTextureType = ""; - MaterialEditorGui.guiSync( materialEd_previewMaterial ); -} - -function MaterialEditorGui::updateDetailScale(%this,%newScale) -{ - %layer = MaterialEditorGui.currentLayer; - - %detailScale = %newScale SPC %newScale; - MaterialEditorGui.updateActiveMaterial("detailScale[" @ %layer @ "]", %detailScale); - } - -function MaterialEditorGui::updateDetailNormalStrength(%this,%newStrength) -{ - %layer = MaterialEditorGui.currentLayer; - - %detailStrength = %newStrength; - MaterialEditorGui.updateActiveMaterial("detailNormalMapStrength[" @ %layer @ "]", %detailStrength); -} - -function MaterialEditorGui::updateRotationOffset(%this, %isSlider, %onMouseUp) -{ - %layer = MaterialEditorGui.currentLayer; - %X = MaterialEditorPropertiesWindow-->RotationTextEditU.getText(); - %Y = MaterialEditorPropertiesWindow-->RotationTextEditV.getText(); - MaterialEditorPropertiesWindow-->RotationCrosshair.setPosition(45*mAbs(%X)-2, 45*mAbs(%Y)-2); - - MaterialEditorGui.updateActiveMaterial("rotPivotOffset[" @ %layer @ "]", %X SPC %Y,%isSlider,%onMouseUp); -} - -function MaterialEditorGui::updateRotationSpeed(%this, %isSlider, %onMouseUp) -{ - %layer = MaterialEditorGui.currentLayer; - %speed = MaterialEditorPropertiesWindow-->RotationSpeedTextEdit.getText(); - MaterialEditorGui.updateActiveMaterial("rotSpeed[" @ %layer @ "]",%speed,%isSlider,%onMouseUp); -} - -function MaterialEditorGui::updateScrollOffset(%this, %isSlider, %onMouseUp) -{ - %layer = MaterialEditorGui.currentLayer; - %X = MaterialEditorPropertiesWindow-->ScrollTextEditU.getText(); - %Y = MaterialEditorPropertiesWindow-->ScrollTextEditV.getText(); - MaterialEditorPropertiesWindow-->ScrollCrosshair.setPosition( -(23 * %X)+20, -(23 * %Y)+20); - - MaterialEditorGui.updateActiveMaterial("scrollDir[" @ %layer @ "]",%X SPC %Y,%isSlider,%onMouseUp); -} - -function MaterialEditorGui::updateScrollSpeed(%this, %isSlider, %onMouseUp) -{ - %layer = MaterialEditorGui.currentLayer; - %speed = MaterialEditorPropertiesWindow-->ScrollSpeedTextEdit.getText(); - MaterialEditorGui.updateActiveMaterial("scrollSpeed[" @ %layer @ "]",%speed,%isSlider,%onMouseUp); -} - -function MaterialEditorGui::updateWaveType(%this) -{ - for( %radioButton = 0; %radioButton < MaterialEditorPropertiesWindow-->WaveButtonContainer.getCount(); %radioButton++ ) - { - if( MaterialEditorPropertiesWindow-->WaveButtonContainer.getObject(%radioButton).getValue() == 1 ) - %type = MaterialEditorPropertiesWindow-->WaveButtonContainer.getObject(%radioButton).waveType; - } - - %layer = MaterialEditorGui.currentLayer; - MaterialEditorGui.updateActiveMaterial("waveType[" @ %layer @ "]", %type); -} - -function MaterialEditorGui::updateWaveAmp(%this, %isSlider, %onMouseUp) -{ - %layer = MaterialEditorGui.currentLayer; - %amp = MaterialEditorPropertiesWindow-->WaveTextEditAmp.getText(); - MaterialEditorGui.updateActiveMaterial("waveAmp[" @ %layer @ "]", %amp, %isSlider, %onMouseUp); -} - -function MaterialEditorGui::updateWaveFreq(%this, %isSlider, %onMouseUp) -{ - %layer = MaterialEditorGui.currentLayer; - %freq = MaterialEditorPropertiesWindow-->WaveTextEditFreq.getText(); - MaterialEditorGui.updateActiveMaterial("waveFreq[" @ %layer @ "]", %freq, %isSlider, %onMouseUp); -} - -function MaterialEditorGui::updateSequenceFPS(%this, %isSlider, %onMouseUp) -{ - %layer = MaterialEditorGui.currentLayer; - %fps = MaterialEditorPropertiesWindow-->SequenceTextEditFPS.getText(); - MaterialEditorGui.updateActiveMaterial("sequenceFramePerSec[" @ %layer @ "]", %fps, %isSlider, %onMouseUp); -} - -function MaterialEditorGui::updateSequenceSSS(%this, %isSlider, %onMouseUp) -{ - %layer = MaterialEditorGui.currentLayer; - %sss = 1 / MaterialEditorPropertiesWindow-->SequenceTextEditSSS.getText(); - MaterialEditorGui.updateActiveMaterial("sequenceSegmentSize[" @ %layer @ "]", %sss, %isSlider, %onMouseUp); -} - function MaterialEditorGui::updateAnimationFlags(%this) { MaterialEditorGui.setMaterialDirty(); %single = true; %flags = ""; - if(MaterialEditorPropertiesWindow-->RotationAnimation.getValue() == true) + if(MaterialEditorGuiWindow-->RotationAnimation.getValue() == true) { if(%single == true) %flags = %flags @ "$Rotate"; @@ -1596,7 +1291,7 @@ function MaterialEditorGui::updateAnimationFlags(%this) %single = false; } - if(MaterialEditorPropertiesWindow-->ScrollAnimation.getValue() == true) + if(MaterialEditorGuiWindow-->ScrollAnimation.getValue() == true) { if(%single == true) %flags = %flags @ "$Scroll"; @@ -1605,7 +1300,7 @@ function MaterialEditorGui::updateAnimationFlags(%this) %single = false; } - if(MaterialEditorPropertiesWindow-->WaveAnimation.getValue() == true) + if(MaterialEditorGuiWindow-->WaveAnimation.getValue() == true) { if(%single == true) %flags = %flags @ "$Wave"; @@ -1614,7 +1309,7 @@ function MaterialEditorGui::updateAnimationFlags(%this) %single = false; } - if(MaterialEditorPropertiesWindow-->ScaleAnimation.getValue() == true) + if(MaterialEditorGuiWindow-->ScaleAnimation.getValue() == true) { if(%single == true) %flags = %flags @ "$Scale"; @@ -1623,7 +1318,7 @@ function MaterialEditorGui::updateAnimationFlags(%this) %single = false; } - if(MaterialEditorPropertiesWindow-->SequenceAnimation.getValue() == true) + if(MaterialEditorGuiWindow-->SequenceAnimation.getValue() == true) { if(%single == true) %flags = %flags @ "$Sequence"; @@ -1682,35 +1377,6 @@ function MaterialEditorGui::syncGuiColor(%this, %guiCtrl, %propname, %color) MaterialEditorGui.updateActiveMaterial(%propName, %color); } -//These two functions are focused on object/layer specific functionality -function MaterialEditorGui::updateColorMultiply(%this,%color) -{ - %propName = "diffuseColor[" @ MaterialEditorGui.currentLayer @ "]"; - %this.syncGuiColor(MaterialEditorPropertiesWindow-->colorTintSwatch, %propName, %color); -} - -function MaterialEditorGui::updateSpecular(%this, %color) -{ - %propName = "specular[" @ MaterialEditorGui.currentLayer @ "]"; - %this.syncGuiColor(MaterialEditorPropertiesWindow-->specularColorSwatch, %propName, %color); -} - -function MaterialEditorGui::updateSubSurfaceColor(%this, %color) -{ - %propName = "subSurfaceColor[" @ MaterialEditorGui.currentLayer @ "]"; - %this.syncGuiColor(MaterialEditorPropertiesWindow-->subSurfaceColorSwatch, %propName, %color); -} - -function MaterialEditorGui::updateEffectColor0(%this, %color) -{ - %this.syncGuiColor(MaterialEditorPropertiesWindow-->effectColor0Swatch, "effectColor[0]", %color); -} - -function MaterialEditorGui::updateEffectColor1(%this, %color) -{ - %this.syncGuiColor(MaterialEditorPropertiesWindow-->effectColor1Swatch, "effectColor[1]", %color); -} - function MaterialEditorGui::updateBehaviorSound(%this, %type, %sound) { %defaultId = -1; @@ -1782,373 +1448,9 @@ function MaterialEditorGui::selectCubemap(%this) return; MaterialEditorGui.updateActiveMaterial( "cubemap", %cubemap.name ); - MaterialEditorGui.hideCubemapEditor(); + CubemapEditor.hideCubemapEditor(); } -function MaterialEditorGui::cancelCubemap(%this) -{ - %cubemap = MaterialEditorGui.currentCubemap; - - %idx = matEd_cubemapEd_availableCubemapList.findItemText( %cubemap.getName() ); - matEd_cubemapEd_availableCubemapList.setItemText( %idx, notDirtyCubemap.originalName ); - %cubemap.setName( notDirtyCubemap.originalName ); - - MaterialEditorGui.copyCubemaps( notDirtyCubemap, %cubemap ); - MaterialEditorGui.copyCubemaps( notDirtyCubemap, matEdCubeMapPreviewMat); - - %cubemap.updateFaces(); - matEdCubeMapPreviewMat.updateFaces(); -} - -function MaterialEditorGui::showCubemapEditor(%this) -{ - if (matEd_cubemapEditor.isVisible()) - return; - - MaterialEditorGui.currentCubemap = ""; - - matEd_cubemapEditor.setVisible(1); - new PersistenceManager(matEd_cubemapEdPerMan); - MaterialEditorGui.setCubemapNotDirty(); - - for( %i = 0; %i < RootGroup.getCount(); %i++ ) - { - if( RootGroup.getObject(%i).getClassName()!$= "CubemapData" ) - continue; - - for( %k = 0; %k < UnlistedCubemaps.count(); %k++ ) - { - %unlistedFound = 0; - if( UnlistedCubemaps.getValue(%k) $= RootGroup.getObject(%i).name ) - { - %unlistedFound = 1; - break; - } - } - - if( %unlistedFound ) - continue; - - matEd_cubemapEd_availableCubemapList.addItem( RootGroup.getObject(%i).name ); - } - - singleton CubemapData(notDirtyCubemap); - - // if there was no cubemap, pick the first, select, and bail, these are going to take - // care of themselves in the selected function - if( !isObject( MaterialEditorGui.currentMaterial.cubemap ) ) - { - if( matEd_cubemapEd_availableCubemapList.getItemCount() > 0 ) - { - matEd_cubemapEd_availableCubemapList.setSelected(0, true); - return; - } - else - { - // if there are no cubemaps, then create one, select, and bail - %cubemap = MaterialEditorGui.createNewCubemap(); - matEd_cubemapEd_availableCubemapList.addItem( %cubemap.name ); - matEd_cubemapEd_availableCubemapList.setSelected(0, true); - return; - } - } - - // do not directly change activeMat! - MaterialEditorGui.currentCubemap = MaterialEditorGui.currentMaterial.cubemap.getId(); - %cubemap = MaterialEditorGui.currentCubemap; - - notDirtyCubemap.originalName = %cubemap.getName(); - MaterialEditorGui.copyCubemaps( %cubemap, notDirtyCubemap); - MaterialEditorGui.copyCubemaps( %cubemap, matEdCubeMapPreviewMat); - MaterialEditorGui.syncCubemap( %cubemap ); -} - -function MaterialEditorGui::hideCubemapEditor(%this,%cancel) -{ - if(%cancel) - MaterialEditorGui.cancelCubemap(); - - matEd_cubemapEd_availableCubemapList.clearItems(); - matEd_cubemapEdPerMan.delete(); - matEd_cubemapEditor.setVisible(0); -} - -// create category and update current material if there is one -function MaterialEditorGui::addCubemap( %this,%cubemapName ) -{ - if( %cubemapName $= "" ) - { - toolsMessageBoxOK( "Error", "Can not create a cubemap without a valid name."); - return; - } - - for(%i = 0; %i < RootGroup.getCount(); %i++) - { - if( %cubemapName $= RootGroup.getObject(%i).getName() ) - { - toolsMessageBoxOK( "Error", "There is already an object with the same name."); - return; - } - } - - // Create and select a new cubemap - %cubemap = MaterialEditorGui.createNewCubemap( %cubemapName ); - %idx = matEd_cubemapEd_availableCubemapList.addItem( %cubemap.name ); - matEd_cubemapEd_availableCubemapList.setSelected( %idx, true ); - - // material category text field to blank - matEd_addCubemapWindow-->cubemapName.setText(""); -} - -function MaterialEditorGui::createNewCubemap( %this, %cubemap ) -{ - if( %cubemap $= "" ) - { - for(%i = 0; ; %i++) - { - %cubemap = "newCubemap_" @ %i; - if( !isObject(%cubemap) ) - break; - } - } - - new CubemapData(%cubemap) - { - cubeMapFaceAsset[0] = "ToolsModule:cube_xNeg_image"; - cubeMapFaceAsset[1] = "ToolsModule:cube_xPos_image"; - cubeMapFaceAsset[2] = "ToolsModule:cube_zNeg_image"; - cubeMapFaceAsset[3] = "ToolsModule:cube_zPos_image"; - cubeMapFaceAsset[4] = "ToolsModule:cube_yNeg_image"; - cubeMapFaceAsset[5] = "ToolsModule:cube_yPos_image"; - - parentGroup = RootGroup; - }; - - matEd_cubemapEdPerMan.setDirty( %cubemap, "art/materials." @ $TorqueScriptFileExtension ); - matEd_cubemapEdPerMan.saveDirty(); - - return %cubemap; -} - -function MaterialEditorGui::setCubemapDirty(%this) -{ - %propertyText = "Create Cubemap *"; - matEd_cubemapEditor.text = %propertyText; - matEd_cubemapEditor.dirty = true; - matEd_cubemapEditor-->saveCubemap.setActive(true); - - %cubemap = MaterialEditorGui.currentCubemap; - - // materials created in the materail selector are given that as its filename, so we run another check - if( MaterialEditorGui.isMatEditorMaterial( %cubemap ) ) - matEd_cubemapEdPerMan.setDirty(%cubemap, "art/materials." @ $TorqueScriptFileExtension); - else - matEd_cubemapEdPerMan.setDirty(%cubemap); -} - -function MaterialEditorGui::setCubemapNotDirty(%this) -{ - %propertyText= strreplace("Create Cubemap" , "*" , ""); - matEd_cubemapEditor.text = %propertyText; - matEd_cubemapEditor.dirty = false; - matEd_cubemapEditor-->saveCubemap.setActive(false); - - %cubemap = MaterialEditorGui.currentCubemap; - matEd_cubemapEdPerMan.removeDirty(%cubemap); -} - -function MaterialEditorGui::showDeleteCubemapDialog(%this) -{ - %idx = matEd_cubemapEd_availableCubemapList.getSelectedItem(); - %cubemap = matEd_cubemapEd_availableCubemapList.getItemText( %idx ); - %cubemap = %cubemap.getId(); - - if( %cubemap == -1 || !isObject(%cubemap) ) - return; - - if( isObject( %cubemap ) ) - { - toolsMessageBoxYesNoCancel("Delete Cubemap?", - "Are you sure you want to delete

" @ %cubemap.getName() @ "

Cubemap deletion won't take affect until the engine is quit.", - "MaterialEditorGui.deleteCubemap( " @ %cubemap @ ", " @ %idx @ " );", - "", - "" ); - } -} - -function MaterialEditorGui::deleteCubemap( %this, %cubemap, %idx ) -{ - matEd_cubemapEd_availableCubemapList.deleteItem( %idx ); - - UnlistedCubemaps.add( "unlistedCubemaps", %cubemap.getName() ); - - if( !MaterialEditorGui.isMatEditorMaterial( %cubemap ) ) - { - matEd_cubemapEdPerMan.removeDirty( %cubemap ); - matEd_cubemapEdPerMan.removeObjectFromFile( %cubemap ); - } - - if( matEd_cubemapEd_availableCubemapList.getItemCount() > 0 ) - { - matEd_cubemapEd_availableCubemapList.setSelected(0, true); - } - else - { - // if there are no cubemaps, then create one, select, and bail - %cubemap = MaterialEditorGui.createNewCubemap(); - matEd_cubemapEd_availableCubemapList.addItem( %cubemap.getName() ); - matEd_cubemapEd_availableCubemapList.setSelected(0, true); - } -} - -function matEd_cubemapEd_availableCubemapList::onSelect( %this, %id, %cubemap ) -{ - %cubemap = %cubemap.getId(); - if( MaterialEditorGui.currentCubemap $= %cubemap ) - return; - - if( matEd_cubemapEditor.dirty ) - { - %savedCubemap = MaterialEditorGui.currentCubemap; - toolsMessageBoxYesNoCancel("Save Existing Cubemap?", - "Do you want to save changes to

" @ %savedCubemap.getName(), - "MaterialEditorGui.saveCubemap(" @ true @ ");", - "MaterialEditorGui.saveCubemapDialogDontSave(" @ %cubemap @ ");", - "MaterialEditorGui.saveCubemapDialogCancel();" ); - } - else - MaterialEditorGui.changeCubemap( %cubemap ); -} - -function MaterialEditorGui::showSaveCubemapDialog( %this ) -{ - %cubemap = MaterialEditorGui.currentCubemap; - if( !isObject(%cubemap) ) - return; - - toolsMessageBoxYesNoCancel("Save Cubemap?", - "Do you want to save changes to

" @ %cubemap.getName(), - "MaterialEditorGui.saveCubemap( " @ %cubemap @ " );", - "", - "" ); -} - -function MaterialEditorGui::saveCubemap( %this, %cubemap ) -{ - notDirtyCubemap.originalName = %cubemap.getName(); - MaterialEditorGui.copyCubemaps( %cubemap, notDirtyCubemap ); - MaterialEditorGui.copyCubemaps( %cubemap, matEdCubeMapPreviewMat); - - matEd_cubemapEdPerMan.saveDirty(); - - MaterialEditorGui.setCubemapNotDirty(); -} - -function MaterialEditorGui::saveCubemapDialogDontSave( %this, %newCubemap) -{ - //deal with old cubemap first - %oldCubemap = MaterialEditorGui.currentCubemap; - - %idx = matEd_cubemapEd_availableCubemapList.findItemText( %oldCubemap.getName() ); - matEd_cubemapEd_availableCubemapList.setItemText( %idx, notDirtyCubemap.originalName ); - %oldCubemap.setName( notDirtyCubemap.originalName ); - - MaterialEditorGui.copyCubemaps( notDirtyCubemap, %oldCubemap); - MaterialEditorGui.copyCubemaps( notDirtyCubemap, matEdCubeMapPreviewMat); - MaterialEditorGui.syncCubemap( %oldCubemap ); - - MaterialEditorGui.changeCubemap( %newCubemap ); -} - -function MaterialEditorGui::saveCubemapDialogCancel( %this ) -{ - %cubemap = MaterialEditorGui.currentCubemap; - %idx = matEd_cubemapEd_availableCubemapList.findItemText( %cubemap.getName() ); - matEd_cubemapEd_availableCubemapList.clearSelection(); - matEd_cubemapEd_availableCubemapList.setSelected( %idx, true ); -} - -function MaterialEditorGui::changeCubemap( %this, %cubemap ) -{ - MaterialEditorGui.setCubemapNotDirty(); - MaterialEditorGui.currentCubemap = %cubemap; - - notDirtyCubemap.originalName = %cubemap.getName(); - MaterialEditorGui.copyCubemaps( %cubemap, notDirtyCubemap); - MaterialEditorGui.copyCubemaps( %cubemap, matEdCubeMapPreviewMat); - MaterialEditorGui.syncCubemap( %cubemap ); -} - -function MaterialEditorGui::editCubemapImage( %this, %face ) -{ - MaterialEditorGui.setCubemapDirty(); - - %cubemap = MaterialEditorGui.currentCubemap; - %bitmap = MaterialEditorGui.openFile("texture"); - if( %bitmap !$= "" && %bitmap !$= "tools/materialEditor/gui/cubemapBtnBorder" ) - { - %cubemap.cubeFace[%face] = %bitmap; - MaterialEditorGui.copyCubemaps( %cubemap, matEdCubeMapPreviewMat); - MaterialEditorGui.syncCubemap( %cubemap ); - } -} - -function MaterialEditorGui::editCubemapName( %this, %newName ) -{ - MaterialEditorGui.setCubemapDirty(); - - %cubemap = MaterialEditorGui.currentCubemap; - - %idx = matEd_cubemapEd_availableCubemapList.findItemText( %cubemap.getName() ); - matEd_cubemapEd_availableCubemapList.setItemText( %idx, %newName ); - %cubemap.setName(%newName); - - MaterialEditorGui.syncCubemap( %cubemap ); -} - -function MaterialEditorGui::syncCubemap( %this, %cubemap ) -{ - %xpos = MaterialEditorGui.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[0]); - if( %xpos !$= "" ) - matEd_cubemapEd_XPos.setBitmap( %xpos ); - - %xneg = MaterialEditorGui.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[1]); - if( %xneg !$= "" ) - matEd_cubemapEd_XNeg.setBitmap( %xneg ); - - %yneg = MaterialEditorGui.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[2]); - if( %yneg !$= "" ) - matEd_cubemapEd_YNeG.setBitmap( %yneg ); - - %ypos = MaterialEditorGui.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[3]); - if( %ypos !$= "" ) - matEd_cubemapEd_YPos.setBitmap( %ypos ); - - %zpos = MaterialEditorGui.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[4]); - if( %zpos !$= "" ) - matEd_cubemapEd_ZPos.setBitmap( %zpos ); - - %zneg = MaterialEditorGui.searchForTexture(%cubemap.getName(), %cubemap.cubeFace[5]); - if( %zneg !$= "" ) - matEd_cubemapEd_ZNeg.setBitmap( %zneg ); - - matEd_cubemapEd_activeCubemapNameTxt.setText(%cubemap.getName()); - - %cubemap.updateFaces(); - matEdCubeMapPreviewMat.updateFaces(); -} - -function MaterialEditorGui::copyCubemaps( %this, %copyFrom, %copyTo) -{ - %copyTo.cubeFace[0] = %copyFrom.cubeFace[0]; - %copyTo.cubeFace[1] = %copyFrom.cubeFace[1]; - %copyTo.cubeFace[2] = %copyFrom.cubeFace[2]; - %copyTo.cubeFace[3] = %copyFrom.cubeFace[3]; - %copyTo.cubeFace[4] = %copyFrom.cubeFace[4]; - %copyTo.cubeFace[5] = %copyFrom.cubeFace[5]; -} - - //============================================================================== // showSaveDialog logic @@ -2296,7 +1598,7 @@ function MaterialEditorGui::onCreateNewMaterialAsset(%this, %newAssetId) //get the new asset definition %assetDef = AssetDatabase.acquireAsset(%newAssetId); if(isObject(%assetDef)) - AssetBrowser.editMaterialAsset(%assetDef); + AssetBrowser.editAsset(%assetDef); } function MaterialEditorGui::deleteMaterial( %this ) @@ -2454,6 +1756,7 @@ function MaterialEditorGui::refreshMaterial(%this) } MaterialEditorGui.setMaterialNotDirty(); + MaterialEditorPropInspector.refreshMaterial(); } //============================================================================== @@ -2595,72 +1898,6 @@ function MaterialEditorGui::changeMaterial(%this, %fromMaterial, %toMaterial) MaterialEditorGui.submitUndo( %action ); } -//============================================================================== -// Image thumbnail right-clicks. - -// not yet functional -function MaterialEditorMapThumbnail::onRightClick( %this ) -{ - if( !isObject( "MaterialEditorMapThumbnailPopup" ) ) - new PopupMenu( MaterialEditorMapThumbnailPopup ) - { - superClass = "MenuBuilder"; - isPopup = true; - - item[ 0 ] = "Open File" TAB "" TAB "openFile( MaterialEditorMapThumbnailPopup.filePath );"; - item[ 1 ] = "Open Folder" TAB "" TAB "openFolder( filePath( MaterialEditorMapThumbnailPopup.filePath ) );"; - - filePath = ""; - }; - - // Find the text control containing the filename. - - %textCtrl = %this.parentGroup.findObjectByInternalName( %this.fileNameTextCtrl, true ); - if( !%textCtrl ) - return; - - %fileName = %textCtrl.getText(); - %fullPath = makeFullPath( %fileName, getMainDotCsDir() ); - - // Construct a full path. - - %isValid = isFile( %fullPath ); - if( !%isValid ) - { - if( isFile( %fileName ) ) - { - %fullPath = %fileName; - %isValid = true; - } - else - { - // Try material-relative path. - - %material = MaterialEditorGui.currentMaterial; - if( isObject( %material ) ) - { - %materialPath = filePath( makeFullPath( %material.getFilename(), getMainDotCsDir() ) ); - %fullPath = makeFullPath( %fileName, %materialPath ); - %isValid = isFile( %fullPath ); - } - } - } - - %popup = MaterialEditorMapThumbnailPopup; - %popup.enableItem( 0, %isValid ); - %popup.enableItem( 1, %isValid ); - %popup.filePath = %fullPath; - - %popup.showPopup( Canvas ); -} - -// Accumulation -function MaterialEditorGui::updateAccuCheckbox(%this, %value) -{ - MaterialEditorGui.updateActiveMaterial("accuEnabled[" @ MaterialEditorGui.currentLayer @ "]", %value); - MaterialEditorGui.guiSync( materialEd_previewMaterial ); -} - // channel in selectors function MaterialEditorGui::setRoughChan(%this, %value) { @@ -2682,41 +1919,64 @@ function MaterialEditorGui::setMetalChan(%this, %value) function MaterialEditorGui::saveCompositeMap(%this) { - %saveAs = ""; - %dlg = new SaveFileDialog() - { - Filters = "PNG File (*.png)|*.png"; - DefaultPath = EditorSettings.value("data/"); - ChangePath = false; - OverwritePrompt = true; - }; - - %ret = %dlg.Execute(); - if(%ret) - { - // Immediately override/set the levelsDirectory - EditorSettings.setValue( "art/shapes/textures", collapseFilename(filePath( %dlg.FileName )) ); - %saveAs = %dlg.FileName; - } - - if( fileExt( %saveAs ) !$= ".png" ) - %saveAs = %saveAs @ ".png"; - - %material = %this.currentMaterial; - %layer = %this.currentLayer; + %source0 = 1; + %sourceChan0 = ""; - %aoMap = %material.getAOMap(%layer); - %roughMap = %material.getRoughMap(%layer); - %metalMap = %material.getMetalMap(%layer); - - %ao = %material.AOChan[%layer]; - %roughness = %material.RoughnessChan[%layer]; - %metal = %material.metalChan[%layer]; - - %channelKey = %ao SPC %roughness SPC %metal SPC 3; - error("Storing: \"" @ %aoMap @"\" \""@ %roughMap @"\" \""@ %metalMap @"\" \""@ %channelKey @"\" \""@ %saveAs @"\""); - saveCompositeTexture(%aoMap,%roughMap,%metalMap,"",%channelKey, %saveAs); - %dlg.delete(); + %source1 = 1; + %sourceChan1 = ""; + + %source2 = 0; + %sourceChan2 = ""; + + %source3 = 1; + %sourceChan3 = ""; + + if(MaterialEditorGui.currentMaterial.AOMapAsset[MaterialEditorGui.currentLayer] !$= "") + { + %source0 = MaterialEditorGui.currentMaterial.AOMapAsset[MaterialEditorGui.currentLayer]; + %sourceChan0 = MaterialEditorGui.currentMaterial.AOChan[MaterialEditorGui.currentLayer]; + } + /*else + { + %source0 = 1; + %sourceChan0 = ""; + }*/ + + if(MaterialEditorGui.currentMaterial.RoughMapAsset[MaterialEditorGui.currentLayer] !$= "") + { + %source1 = MaterialEditorGui.currentMaterial.RoughMapAsset[MaterialEditorGui.currentLayer]; + %sourceChan1 = MaterialEditorGui.currentMaterial.RoughnessChan[MaterialEditorGui.currentLayer]; + } + /*else + { + %source1 = MaterialEditorGui.currentMaterial.roughness[MaterialEditorGui.currentLayer]; + %sourceChan1 = ""; + }*/ + + if(MaterialEditorGui.currentMaterial.MetalMapAsset[MaterialEditorGui.currentLayer] !$= "") + { + %source2 = MaterialEditorGui.currentMaterial.MetalMapAsset[MaterialEditorGui.currentLayer]; + %sourceChan2 = MaterialEditorGui.currentMaterial.MetalChan[MaterialEditorGui.currentLayer]; + } + /*else + { + %source2 = MaterialEditorGui.currentMaterial.metalness[MaterialEditorGui.currentLayer]; + %sourceChan2 = ""; + }*/ + + CompositeTextureEditor.buildComposite(%source0, %source1, %source2, %source3, %sourceChan0, %sourceChan1, %sourceChan2, %sourceChan3, "MaterialEditorGui.finishedSavingCompositeMap"); + + return; +} + +function MaterialEditorGui::finishedSavingCompositeMap(%this, %assetId) +{ + MaterialEditorGui.updateActiveMaterial(ORMConfigMapAsset @ "[" @ MaterialEditorGui.currentLayer @ "]", %assetId); + MaterialEditorGui.updateActiveMaterial(AOMapAsset @ "[" @ MaterialEditorGui.currentLayer @ "]", ""); + MaterialEditorGui.updateActiveMaterial(RoughMapAsset @ "[" @ MaterialEditorGui.currentLayer @ "]", ""); + MaterialEditorGui.updateActiveMaterial(MetalMapAsset @ "[" @ MaterialEditorGui.currentLayer @ "]", ""); + + %this.refreshSchedule = %this.schedule(32, "guiSync"); } function MaterialEditorGui::swapMaterial(%this) @@ -2728,85 +1988,86 @@ function MaterialEditorGui::doSwapMaterial(%this, %materialAsset) MaterialEditorGui.showMaterialChangeSaveDialog(%materialAsset); } -// -// -function matEdDragNDropMapAssignment(%type, %payload) +function MaterialEditorPropInspector::onInspectorFieldModified(%this, %object, %fieldName, %arrayIndex, %oldValue, %newValue) { - %assetType = %payload.assetType; - if(%assetType !$= "ImageAsset") - return; + if(%arrayIndex !$= "" && %arrayIndex !$= "(null)") + MaterialEditorGui.updateActiveMaterial(%fieldName @ "[" @ %arrayIndex @ "]", %newValue); + else + MaterialEditorGui.updateActiveMaterial(%fieldName, %newValue); - %module = %payload.moduleName; - %assetName = %payload.assetName; - %assetId = %module @ ":" @ %assetName; - - MaterialEditorGui.updatingTextureType = %type; - MaterialEditorGui.guiSync( materialEd_previewMaterial ); - - MaterialEditorGui.doUpdateTextureMap( %assetId ); - Canvas.popDialog(EditorDragAndDropLayer); - if(EditorSettings.value("AssetManagement/Assets/closeBrowserOnDragAction", false)) + MaterialEditorGui.refreshSchedule = MaterialEditorGui.schedule(32, "guiSync"); +} + +function MaterialEditorPropInspector::getScrollbar(%this) +{ + %scrollParent = %this; + if (!%scrollParent.isMemberOfClass("GuiScrollCtrl")) + { + while(%scrollParent.getParent() && !%scrollParent.isMemberOfClass("GuiScrollCtrl")) + { + %scrollParent = %scrollParent.getParent(); + } + } + return %scrollParent; +} + +function MaterialEditorPropInspector::onPreInspectObject(%this, %obj) +{ + %this.saveCollapseState(); + %this.saveScrollState(); +} + +function MaterialEditorPropInspector::onPostInspectObject(%this, %obj) +{ + %this.loadCollapseState(); + %this.loadScrollState(); +} + +function MaterialEditorPropInspector::saveScrollState(%this) +{ + %this.scrollPos = %this.getScrollbar().getScrollPosition(); + //echo(%this.getName() @ "::saveScrollState" SPC %this.scrollPos); +} + +function MaterialEditorPropInspector::loadScrollState(%this) +{ + if (%this.scrollPos $= "") + return; + %this.getScrollbar().setScrollPosition(%this.scrollPos.x, %this.scrollPos.y); + //echo(%this.getName() @ "::loadScrollState" SPC %this.scrollPos); +} + + +function MaterialEditorPropInspector::saveCollapseState(%this) +{ + %groupCount = %this.getInspectedGroupCount(); + if (%groupCount == 0) + return; + %this.collapseState = ""; + for(%grp=0; %grp<%groupCount; %grp++) { - AssetBrowser.hideDialog(); + %this.collapseState = %this.collapseState SPC %this.getInspectedGroup(%grp).isExpanded(); + } + %this.collapseState = trim(%this.collapseState); + //echo(%this.getName() @ "::saveCollapsState" SPC %groupCount SPC %this.collapseState); +} + +function MaterialEditorPropInspector::loadCollapseState(%this) +{ + if (%this.collapseState $= "") return; + %groupCount = %this.getInspectedGroupCount(); + //echo(%this.getName() @ "::loadCollapsState" SPC %groupCount SPC %this.collapseState); + for(%grp=0; %grp<%groupCount; %grp++) + { + if (getword(%this.collapseState,%grp)) + %this.getInspectedGroup(%grp).instantExpand(); + else + %this.getInspectedGroup(%grp).instantCollapse(); } } -function materialEditorDiffuseMapContainer::onControlDropped( %this, %payload, %position ) +function MaterialEditorPropInspector::onFieldRightClick(%this, %fieldCtrl) { - matEdDragNDropMapAssignment("Diffuse", %payload); -} - -function materialEditorNormalMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("Normal", %payload); -} - -function materialEditorORMConfigMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("ORMConfig", %payload); -} - -function materialEditorRoughnessMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("Roughness", %payload); -} - -function materialEditorAOMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("AO", %payload); -} - -function materialEditorMetalMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("Metal", %payload); -} - -function materialEditorGlowMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("Glow", %payload); -} - -function materialEditorDetailMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("Detail", %payload); -} - -function materialEditorDetailNormalMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("DetailNormal", %payload); -} - -function materialEditorOverlayMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("Overlay", %payload); -} - -function materialEditorLightMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("Light", %payload); -} - -function materialEditorToneMapContainer::onControlDropped( %this, %payload, %position ) -{ - matEdDragNDropMapAssignment("Tone", %payload); + echo("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + echo(%fieldCtrl.getFieldName()); } \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/materialEditor/scripts/nuMaterialEditor.tscript b/Templates/BaseGame/game/tools/materialEditor/scripts/nuMaterialEditor.tscript new file mode 100644 index 000000000..dcf5979cd --- /dev/null +++ b/Templates/BaseGame/game/tools/materialEditor/scripts/nuMaterialEditor.tscript @@ -0,0 +1,249 @@ +//Generic methods +function GuiInspector::getWindow(%this) +{ + %windowParent = %this; + if (!%windowParent.isMemberOfClass("GuiWindowCtrl")) + { + while(%windowParent.getParent() && !%windowParent.isMemberOfClass("GuiWindowCtrl")) + { + %windowParent = %windowParent.getParent(); + } + } + return %windowParent; +} + +function MaterialEditorPropInspector::getScrollbar(%this) +{ + %scrollParent = %this; + if (!%scrollParent.isMemberOfClass("GuiScrollCtrl")) + { + while(%scrollParent.getParent() && !%scrollParent.isMemberOfClass("GuiScrollCtrl")) + { + %scrollParent = %scrollParent.getParent(); + } + } + return %scrollParent; +} + +function MaterialEditorPropInspector::onPreInspectObject(%this, %obj) +{ + echo("MaterialEditorPropInspector::onPreInspectObject()"); + %this.saveCollapseState(); + %this.saveScrollState(); +} + +function MaterialEditorPropInspector::onPostInspectObject(%this, %obj) +{ + echo("MaterialEditorPropInspector::onPostInspectObject()"); + %this.loadCollapseState(); + %this.loadScrollState(); +} + +function MaterialEditorPropInspector::saveScrollState(%this) +{ + %this.scrollPos = %this.getScrollbar().getScrollPosition(); + //echo(%this.getName() @ "::saveScrollState" SPC %this.scrollPos); +} + +function MaterialEditorPropInspector::loadScrollState(%this) +{ + if (%this.scrollPos $= "") return; + %this.getScrollbar().setScrollPosition(%this.scrollPos.x, %this.scrollPos.y); + //echo(%this.getName() @ "::loadScrollState" SPC %this.scrollPos); +} + + +function MaterialEditorPropInspector::saveCollapseState(%this) +{ + %groupCount = %this.getInspectedGroupCount(); + if (%groupCount == 0) return; + %this.collapseState = ""; + for(%grp=0; %grp<%groupCount; %grp++) + { + %this.collapseState = %this.collapseState SPC %this.getInspectedGroup(%grp).isExpanded(); + } + %this.collapseState = trim(%this.collapseState); + //echo(%this.getName() @ "::saveCollapsState" SPC %groupCount SPC %this.collapseState); +} + +function MaterialEditorPropInspector::loadCollapseState(%this) +{ + if (%this.collapseState $= "") return; + %groupCount = %this.getInspectedGroupCount(); + //echo(%this.getName() @ "::loadCollapsState" SPC %groupCount SPC %this.collapseState); + for(%grp=0; %grp<%groupCount; %grp++) + { + if (getword(%this.collapseState,%grp)) + %this.getInspectedGroup(%grp).instantExpand(); + else + %this.getInspectedGroup(%grp).instantCollapse(); + } +} + +function GuiInspector::renew(%this) +{ + %count = %this.getNumInspectObjects(); + %inspecting = %this.getInspectObject(0); + for(%i=1; %i< %count;%i++) + %inspecting = %inspecting SPC %this.getInspectObject(%i); + + %this.inspect(""); //clear + //readd + for(%i=0; %i<%count;%i++) + %this.addInspect(getword(%inspecting,%i)); +} + +/// Material Editor specific + +function MaterialEditorPlugin::BuildEditorUI(%this) +{ + if(isObject(NuMaterialEditorWindow)) + NuMaterialEditorWindow.delete(); + + $MaterialEditor::currentLayer = 0; + + %matWindow = UIBuilder::Window("Material Editor", MaterialEditorPropertiesWindow.Position.x - MaterialEditorPropertiesWindow.Extent.x SPC MaterialEditorPropertiesWindow.Position.y, MaterialEditorPropertiesWindow.Extent.x SPC MaterialEditorPropertiesWindow.Extent.y); + %matWindow.setName("NuMaterialEditorWindow"); + %matWindow.closeCommand = "EWorldEditor.remove(NuMaterialEditorWindow);"; + UIBuilder::Stack(); + UIBuilder::SameLine(); + %label = UIBuilder::Label("Layer"); + %label.name = "numatedlabel"; + %matLayerListCtrl = UIBuilder::Dropdown("0\t1\t2\t3", "$MaterialEditor::currentLayer", "onMaterialLayerSelected($ThisControl);"); + %matLayerListCtrl.name = "NuMatEdLayerSelector"; + UIBuilder::FitAllOnLine(); + UIBuilder::End(); + %inspector = UIBuilder::Inspector("NuMaterialEdPropInspector"); + UIBuilder::End(); + UIBuilder::End(); + + + + MaterialEditorGui.currentMaterial = materialEd_previewMaterial;//DetailBlue; + + NuMaterialEdPropInspector.refreshMaterial(); + + %this.editorUI = %matWindow; + EWorldEditor.add(%matWindow); +} + +function MaterialEditorPlugin::CloseEditorUI(%this) +{ + EWorldEditor.remove(NuMaterialEditorWindow); +} + +function onMaterialLayerSelected(%this) +{ + $MaterialEditor::currentLayer = %this.getText(); + NuMaterialEdPropInspector.refreshMaterial(); +} + + +function MaterialEditorPropInspector::refreshMaterial(%this) +{ + %this.onPreInspectObject(%this.getInspectObject()); + %this.inspect(MaterialEditorGui.currentMaterial); + %this.setForcedArrayIndex(MaterialEditorGui.currentLayer); + // + //%this.getScrollbar().setExtent(%ext.x, %ext.y); + MaterialEditorGui.guiSync(); + %this.onPostInspectObject(%this.getInspectObject()); +} + +function MaterialEditorPropInspector::onPostInspectorFieldModified(%this, %obj, %inspecting) +{ + MaterialEditorGui.guiSync(); +} + +function NuMaterialEditorWindow::syncGUI(%this) +{ + //do some presentation adjustments + %ormMapPresent = MaterialEditorGui.currentMaterial.ORMConfigMapAsset[$MaterialEditor::currentLayer] !$= ""; + + %hideORMSliders = %ormMapPresent; + if (!%hideORMSliders) + %hideORMSliders = MaterialEditorGui.currentMaterial.AOMapAsset[$MaterialEditor::currentLayer] !$= ""; + if (!%hideORMSliders) + %hideORMSliders = MaterialEditorGui.currentMaterial.RoughMapAsset[$MaterialEditor::currentLayer] !$= ""; + if (!%hideORMSliders) + %hideORMSliders = MaterialEditorGui.currentMaterial.MetalMapAsset[$MaterialEditor::currentLayer] !$= ""; + + %group = NuMaterialEdPropInspector.findExistentGroup("Light Influence Maps"); + if(%ormMapPresent) + { + %group.hideField("isSRGb",false); + %group.hideField("AOMapAsset"); + %group.hideField("aoChan"); + %group.hideField("RoughMapAsset"); + %group.hideField("roughness"); + %group.hideField("roughnessChan"); + %group.hideField("MetalMapAsset"); + %group.hideField("metalness"); + %group.hideField("metalChan"); + %group.hideField("save"); + } + else + { + %group.hideField("isSRGb"); + if (%hideORMSliders) + { + %group.hideField("aoChan",false); + + %group.hideField("roughness"); + %group.hideField("roughnessChan",false); + + %group.hideField("metalness"); + %group.hideField("metalChan",false); + %group.hideField("save",false); + } + else + { + %group.hideField("aoChan"); + + %group.hideField("roughness",false); + %group.hideField("roughnessChan"); + + %group.hideField("metalness",false); + %group.hideField("metalChan"); + %group.hideField("save"); + } + %group.hideField("AOMapAsset",false); + %group.hideField("RoughMapAsset",false); + %group.hideField("MetalMapAsset",false); + } + + %animflags = MaterialEditorGui.currentMaterial.animFlags[$MaterialEditor::currentLayer]; + %group = NuMaterialEdPropInspector.findExistentGroup("Animation Properties"); + + %hideScroll = true; + if (strstr(%animflags, "Scroll")>=0) + %hideScroll = false; + %group.hideField("scrollDir",%hideScroll); + %group.hideField("scrollSpeed",%hideScroll); + + %hideRotate = true; + if (strstr(%animflags, "Rotate")>=0) + %hideRotate = false; + %group.hideField("rotSpeed",%hideRotate); + %group.hideField("rotPivotOffset",%hideRotate); + + %hideWave = true; + if (strstr(%animflags, "Wave")>=0) + %hideWave = false; + %group.hideField("waveType",%hideWave); + %group.hideField("waveFreq",%hideWave); + %group.hideField("waveAmp",%hideWave); + + %showScale = false; + if (strstr(%animflags, "Scale")>=0) + %showScale = true; + + %hideSequence = true; + if (strstr(%animflags, "Sequence")>=0) + %hideSequence = false; + %group.hideField("sequenceFramePerSec",%hideSequence); + %group.hideField("sequenceSegmentSize",%hideSequence); + + cancel(NuMaterialEdPropInspector.refreshing); + NuMaterialEdPropInspector.refreshing = NuMaterialEdPropInspector.schedule(64,"renew"); +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cubepreview.asset.taml b/Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cubepreview.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cubepreview.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cubepreview.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cubepreview.dts b/Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cubepreview.dts similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cubepreview.dts rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cubepreview.dts diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cubepreview.tscript b/Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cubepreview.tscript similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cubepreview.tscript rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cubepreview.tscript diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cylinderpreview.asset.taml b/Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cylinderpreview.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cylinderpreview.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cylinderpreview.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cylinderpreview.dts b/Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cylinderpreview.dts similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cylinderpreview.dts rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cylinderpreview.dts diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cylinderpreview.tscript b/Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cylinderpreview.tscript similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_cylinderpreview.tscript rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_cylinderpreview.tscript diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_spherepreview.asset.taml b/Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_spherepreview.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_spherepreview.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_spherepreview.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_spherepreview.dts b/Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_spherepreview.dts similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_spherepreview.dts rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_spherepreview.dts diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_spherepreview.tscript b/Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_spherepreview.tscript similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubemaped_spherepreview.tscript rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubemaped_spherepreview.tscript diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubepreview.asset.taml b/Templates/BaseGame/game/tools/materialEditor/shapes/cubepreview.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubepreview.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubepreview.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubepreview.dts b/Templates/BaseGame/game/tools/materialEditor/shapes/cubepreview.dts similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubepreview.dts rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubepreview.dts diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cubepreview.tscript b/Templates/BaseGame/game/tools/materialEditor/shapes/cubepreview.tscript similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cubepreview.tscript rename to Templates/BaseGame/game/tools/materialEditor/shapes/cubepreview.tscript diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cylinderpreview.asset.taml b/Templates/BaseGame/game/tools/materialEditor/shapes/cylinderpreview.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cylinderpreview.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/shapes/cylinderpreview.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cylinderpreview.dts b/Templates/BaseGame/game/tools/materialEditor/shapes/cylinderpreview.dts similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cylinderpreview.dts rename to Templates/BaseGame/game/tools/materialEditor/shapes/cylinderpreview.dts diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/cylinderpreview.tscript b/Templates/BaseGame/game/tools/materialEditor/shapes/cylinderpreview.tscript similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/cylinderpreview.tscript rename to Templates/BaseGame/game/tools/materialEditor/shapes/cylinderpreview.tscript diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/pyramidpreview.asset.taml b/Templates/BaseGame/game/tools/materialEditor/shapes/pyramidpreview.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/pyramidpreview.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/shapes/pyramidpreview.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/pyramidpreview.dts b/Templates/BaseGame/game/tools/materialEditor/shapes/pyramidpreview.dts similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/pyramidpreview.dts rename to Templates/BaseGame/game/tools/materialEditor/shapes/pyramidpreview.dts diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/pyramidpreview.tscript b/Templates/BaseGame/game/tools/materialEditor/shapes/pyramidpreview.tscript similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/pyramidpreview.tscript rename to Templates/BaseGame/game/tools/materialEditor/shapes/pyramidpreview.tscript diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/spherepreview.asset.taml b/Templates/BaseGame/game/tools/materialEditor/shapes/spherepreview.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/spherepreview.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/shapes/spherepreview.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/spherepreview.dts b/Templates/BaseGame/game/tools/materialEditor/shapes/spherepreview.dts similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/spherepreview.dts rename to Templates/BaseGame/game/tools/materialEditor/shapes/spherepreview.dts diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/spherepreview.tscript b/Templates/BaseGame/game/tools/materialEditor/shapes/spherepreview.tscript similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/spherepreview.tscript rename to Templates/BaseGame/game/tools/materialEditor/shapes/spherepreview.tscript diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/torusknotpreview.asset.taml b/Templates/BaseGame/game/tools/materialEditor/shapes/torusknotpreview.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/torusknotpreview.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/shapes/torusknotpreview.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/torusknotpreview.dts b/Templates/BaseGame/game/tools/materialEditor/shapes/torusknotpreview.dts similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/torusknotpreview.dts rename to Templates/BaseGame/game/tools/materialEditor/shapes/torusknotpreview.dts diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/torusknotpreview.tscript b/Templates/BaseGame/game/tools/materialEditor/shapes/torusknotpreview.tscript similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/torusknotpreview.tscript rename to Templates/BaseGame/game/tools/materialEditor/shapes/torusknotpreview.tscript diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/torusknowpreview.asset.taml b/Templates/BaseGame/game/tools/materialEditor/shapes/torusknowpreview.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/torusknowpreview.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/shapes/torusknowpreview.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/torusknowpreview.dts b/Templates/BaseGame/game/tools/materialEditor/shapes/torusknowpreview.dts similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/torusknowpreview.dts rename to Templates/BaseGame/game/tools/materialEditor/shapes/torusknowpreview.dts diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/torusknowpreview.tscript b/Templates/BaseGame/game/tools/materialEditor/shapes/torusknowpreview.tscript similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/torusknowpreview.tscript rename to Templates/BaseGame/game/tools/materialEditor/shapes/torusknowpreview.tscript diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/toruspreview.asset.taml b/Templates/BaseGame/game/tools/materialEditor/shapes/toruspreview.asset.taml similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/toruspreview.asset.taml rename to Templates/BaseGame/game/tools/materialEditor/shapes/toruspreview.asset.taml diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/toruspreview.dts b/Templates/BaseGame/game/tools/materialEditor/shapes/toruspreview.dts similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/toruspreview.dts rename to Templates/BaseGame/game/tools/materialEditor/shapes/toruspreview.dts diff --git a/Templates/BaseGame/game/tools/materialEditor/gui/toruspreview.tscript b/Templates/BaseGame/game/tools/materialEditor/shapes/toruspreview.tscript similarity index 100% rename from Templates/BaseGame/game/tools/materialEditor/gui/toruspreview.tscript rename to Templates/BaseGame/game/tools/materialEditor/shapes/toruspreview.tscript diff --git a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript index a7114117f..f16e0c1c5 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript +++ b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript @@ -2179,8 +2179,7 @@ function ShapeEdMaterials::editSelectedMaterial( %this ) ShapeEdSelectWindow.setVisible( false ); ShapeEdPropWindow.setVisible( false ); - EditorGui-->MatEdPropertiesWindow.setVisible( true ); - EditorGui-->MatEdPreviewWindow.setVisible( true ); + Canvas.pushDialog(MaterialEditorGui); MatEd_phoBreadcrumb.setVisible( true ); MatEd_phoBreadcrumb.command = "ShapeEdMaterials.editSelectedMaterialEnd();"; @@ -2204,8 +2203,7 @@ function ShapeEdMaterials::editSelectedMaterialEnd( %this, %closeEditor ) MatEd_phoBreadcrumb.command = ""; MaterialEditorGui.quit(); - EditorGui-->MatEdPropertiesWindow.setVisible( false ); - EditorGui-->MatEdPreviewWindow.setVisible( false ); + Canvas.popDialog(MaterialEditorGui); // Delete the temporary TSStatic %this.tempShape.delete(); diff --git a/Templates/BaseGame/game/tools/worldEditor/main.tscript b/Templates/BaseGame/game/tools/worldEditor/main.tscript index c3a3e88ad..0c5538771 100644 --- a/Templates/BaseGame/game/tools/worldEditor/main.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/main.tscript @@ -47,6 +47,7 @@ function initializeWorldEditor() exec("./gui/probeBakeDlg.gui" ); exec("tools/gui/cubemapEditor.gui" ); + exec("tools/gui/compositeTextureEditor.gui" ); exec("tools/gui/postFxEditor.gui" ); // Load Scripts. @@ -71,6 +72,8 @@ function initializeWorldEditor() exec("./scripts/visibility/probeViz." @ $TorqueScriptFileExtension); exec("./scripts/buttonPalette." @ $TorqueScriptFileExtension); + exec("tools/gui/cubemapEditor." @ $TorqueScriptFileExtension ); + exec("tools/gui/compositeTextureEditor." @ $TorqueScriptFileExtension ); exec("tools/gui/postFxEditor." @ $TorqueScriptFileExtension ); exec("tools/gui/renderTargetVisualizer.ed." @ $TorqueScriptFileExtension); diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript index 714e81b80..7fe337545 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript @@ -671,7 +671,7 @@ function EditorGui::updateSideBar(%this) case "ForestEditorPlugin ": ForestEditorPalleteWindow.onMouseDragged(); case "MaterialEditorPlugin": - MaterialEditorPreviewWindow.onMouseDragged(); + MaterialEditorGuiWindow.onMouseDragged(); case "MeshRoadEditorPlugin": MeshRoadEditorTreeWindow.onMouseDragged(); case "MissionAreaEditorPlugin": diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript index 11f5017bc..6510fe509 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript @@ -43,7 +43,7 @@ function WorldEditor::onSelect( %this, %obj ) // Used to help the Material Editor( the M.E doesn't utilize its own TS control ) // so this dirty extension is used to fake it - if ( MaterialEditorPreviewWindow.isVisible() ) + if ( MaterialEditorGui.isVisible() ) MaterialEditorGui.prepareActiveObject(); // Update the Transform Selection window From bfc1a7e03ce265210e3f5157dd394dbbe8c08a6c Mon Sep 17 00:00:00 2001 From: JeffR Date: Sun, 3 Aug 2025 12:11:44 -0500 Subject: [PATCH 42/42] Removed extra wipwork script that isn't needed now --- .../scripts/nuMaterialEditor.tscript | 249 ------------------ 1 file changed, 249 deletions(-) delete mode 100644 Templates/BaseGame/game/tools/materialEditor/scripts/nuMaterialEditor.tscript diff --git a/Templates/BaseGame/game/tools/materialEditor/scripts/nuMaterialEditor.tscript b/Templates/BaseGame/game/tools/materialEditor/scripts/nuMaterialEditor.tscript deleted file mode 100644 index dcf5979cd..000000000 --- a/Templates/BaseGame/game/tools/materialEditor/scripts/nuMaterialEditor.tscript +++ /dev/null @@ -1,249 +0,0 @@ -//Generic methods -function GuiInspector::getWindow(%this) -{ - %windowParent = %this; - if (!%windowParent.isMemberOfClass("GuiWindowCtrl")) - { - while(%windowParent.getParent() && !%windowParent.isMemberOfClass("GuiWindowCtrl")) - { - %windowParent = %windowParent.getParent(); - } - } - return %windowParent; -} - -function MaterialEditorPropInspector::getScrollbar(%this) -{ - %scrollParent = %this; - if (!%scrollParent.isMemberOfClass("GuiScrollCtrl")) - { - while(%scrollParent.getParent() && !%scrollParent.isMemberOfClass("GuiScrollCtrl")) - { - %scrollParent = %scrollParent.getParent(); - } - } - return %scrollParent; -} - -function MaterialEditorPropInspector::onPreInspectObject(%this, %obj) -{ - echo("MaterialEditorPropInspector::onPreInspectObject()"); - %this.saveCollapseState(); - %this.saveScrollState(); -} - -function MaterialEditorPropInspector::onPostInspectObject(%this, %obj) -{ - echo("MaterialEditorPropInspector::onPostInspectObject()"); - %this.loadCollapseState(); - %this.loadScrollState(); -} - -function MaterialEditorPropInspector::saveScrollState(%this) -{ - %this.scrollPos = %this.getScrollbar().getScrollPosition(); - //echo(%this.getName() @ "::saveScrollState" SPC %this.scrollPos); -} - -function MaterialEditorPropInspector::loadScrollState(%this) -{ - if (%this.scrollPos $= "") return; - %this.getScrollbar().setScrollPosition(%this.scrollPos.x, %this.scrollPos.y); - //echo(%this.getName() @ "::loadScrollState" SPC %this.scrollPos); -} - - -function MaterialEditorPropInspector::saveCollapseState(%this) -{ - %groupCount = %this.getInspectedGroupCount(); - if (%groupCount == 0) return; - %this.collapseState = ""; - for(%grp=0; %grp<%groupCount; %grp++) - { - %this.collapseState = %this.collapseState SPC %this.getInspectedGroup(%grp).isExpanded(); - } - %this.collapseState = trim(%this.collapseState); - //echo(%this.getName() @ "::saveCollapsState" SPC %groupCount SPC %this.collapseState); -} - -function MaterialEditorPropInspector::loadCollapseState(%this) -{ - if (%this.collapseState $= "") return; - %groupCount = %this.getInspectedGroupCount(); - //echo(%this.getName() @ "::loadCollapsState" SPC %groupCount SPC %this.collapseState); - for(%grp=0; %grp<%groupCount; %grp++) - { - if (getword(%this.collapseState,%grp)) - %this.getInspectedGroup(%grp).instantExpand(); - else - %this.getInspectedGroup(%grp).instantCollapse(); - } -} - -function GuiInspector::renew(%this) -{ - %count = %this.getNumInspectObjects(); - %inspecting = %this.getInspectObject(0); - for(%i=1; %i< %count;%i++) - %inspecting = %inspecting SPC %this.getInspectObject(%i); - - %this.inspect(""); //clear - //readd - for(%i=0; %i<%count;%i++) - %this.addInspect(getword(%inspecting,%i)); -} - -/// Material Editor specific - -function MaterialEditorPlugin::BuildEditorUI(%this) -{ - if(isObject(NuMaterialEditorWindow)) - NuMaterialEditorWindow.delete(); - - $MaterialEditor::currentLayer = 0; - - %matWindow = UIBuilder::Window("Material Editor", MaterialEditorPropertiesWindow.Position.x - MaterialEditorPropertiesWindow.Extent.x SPC MaterialEditorPropertiesWindow.Position.y, MaterialEditorPropertiesWindow.Extent.x SPC MaterialEditorPropertiesWindow.Extent.y); - %matWindow.setName("NuMaterialEditorWindow"); - %matWindow.closeCommand = "EWorldEditor.remove(NuMaterialEditorWindow);"; - UIBuilder::Stack(); - UIBuilder::SameLine(); - %label = UIBuilder::Label("Layer"); - %label.name = "numatedlabel"; - %matLayerListCtrl = UIBuilder::Dropdown("0\t1\t2\t3", "$MaterialEditor::currentLayer", "onMaterialLayerSelected($ThisControl);"); - %matLayerListCtrl.name = "NuMatEdLayerSelector"; - UIBuilder::FitAllOnLine(); - UIBuilder::End(); - %inspector = UIBuilder::Inspector("NuMaterialEdPropInspector"); - UIBuilder::End(); - UIBuilder::End(); - - - - MaterialEditorGui.currentMaterial = materialEd_previewMaterial;//DetailBlue; - - NuMaterialEdPropInspector.refreshMaterial(); - - %this.editorUI = %matWindow; - EWorldEditor.add(%matWindow); -} - -function MaterialEditorPlugin::CloseEditorUI(%this) -{ - EWorldEditor.remove(NuMaterialEditorWindow); -} - -function onMaterialLayerSelected(%this) -{ - $MaterialEditor::currentLayer = %this.getText(); - NuMaterialEdPropInspector.refreshMaterial(); -} - - -function MaterialEditorPropInspector::refreshMaterial(%this) -{ - %this.onPreInspectObject(%this.getInspectObject()); - %this.inspect(MaterialEditorGui.currentMaterial); - %this.setForcedArrayIndex(MaterialEditorGui.currentLayer); - // - //%this.getScrollbar().setExtent(%ext.x, %ext.y); - MaterialEditorGui.guiSync(); - %this.onPostInspectObject(%this.getInspectObject()); -} - -function MaterialEditorPropInspector::onPostInspectorFieldModified(%this, %obj, %inspecting) -{ - MaterialEditorGui.guiSync(); -} - -function NuMaterialEditorWindow::syncGUI(%this) -{ - //do some presentation adjustments - %ormMapPresent = MaterialEditorGui.currentMaterial.ORMConfigMapAsset[$MaterialEditor::currentLayer] !$= ""; - - %hideORMSliders = %ormMapPresent; - if (!%hideORMSliders) - %hideORMSliders = MaterialEditorGui.currentMaterial.AOMapAsset[$MaterialEditor::currentLayer] !$= ""; - if (!%hideORMSliders) - %hideORMSliders = MaterialEditorGui.currentMaterial.RoughMapAsset[$MaterialEditor::currentLayer] !$= ""; - if (!%hideORMSliders) - %hideORMSliders = MaterialEditorGui.currentMaterial.MetalMapAsset[$MaterialEditor::currentLayer] !$= ""; - - %group = NuMaterialEdPropInspector.findExistentGroup("Light Influence Maps"); - if(%ormMapPresent) - { - %group.hideField("isSRGb",false); - %group.hideField("AOMapAsset"); - %group.hideField("aoChan"); - %group.hideField("RoughMapAsset"); - %group.hideField("roughness"); - %group.hideField("roughnessChan"); - %group.hideField("MetalMapAsset"); - %group.hideField("metalness"); - %group.hideField("metalChan"); - %group.hideField("save"); - } - else - { - %group.hideField("isSRGb"); - if (%hideORMSliders) - { - %group.hideField("aoChan",false); - - %group.hideField("roughness"); - %group.hideField("roughnessChan",false); - - %group.hideField("metalness"); - %group.hideField("metalChan",false); - %group.hideField("save",false); - } - else - { - %group.hideField("aoChan"); - - %group.hideField("roughness",false); - %group.hideField("roughnessChan"); - - %group.hideField("metalness",false); - %group.hideField("metalChan"); - %group.hideField("save"); - } - %group.hideField("AOMapAsset",false); - %group.hideField("RoughMapAsset",false); - %group.hideField("MetalMapAsset",false); - } - - %animflags = MaterialEditorGui.currentMaterial.animFlags[$MaterialEditor::currentLayer]; - %group = NuMaterialEdPropInspector.findExistentGroup("Animation Properties"); - - %hideScroll = true; - if (strstr(%animflags, "Scroll")>=0) - %hideScroll = false; - %group.hideField("scrollDir",%hideScroll); - %group.hideField("scrollSpeed",%hideScroll); - - %hideRotate = true; - if (strstr(%animflags, "Rotate")>=0) - %hideRotate = false; - %group.hideField("rotSpeed",%hideRotate); - %group.hideField("rotPivotOffset",%hideRotate); - - %hideWave = true; - if (strstr(%animflags, "Wave")>=0) - %hideWave = false; - %group.hideField("waveType",%hideWave); - %group.hideField("waveFreq",%hideWave); - %group.hideField("waveAmp",%hideWave); - - %showScale = false; - if (strstr(%animflags, "Scale")>=0) - %showScale = true; - - %hideSequence = true; - if (strstr(%animflags, "Sequence")>=0) - %hideSequence = false; - %group.hideField("sequenceFramePerSec",%hideSequence); - %group.hideField("sequenceSegmentSize",%hideSequence); - - cancel(NuMaterialEdPropInspector.refreshing); - NuMaterialEdPropInspector.refreshing = NuMaterialEdPropInspector.schedule(64,"renew"); -} \ No newline at end of file