From 9e46c8be755f1d6966edd00d93aa7c04fa2ac9fa Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Sun, 22 Sep 2024 12:27:45 +0200 Subject: [PATCH] Recast: implement primitive shape volume editor Allows for selecting already created volumes and modifying or deleting them. --- src/naveditor/Editor_Common.cpp | 14 +- src/naveditor/InputGeom.cpp | 15 +- src/naveditor/ShapeVolumeTool.cpp | 259 ++++++++++++++++++------ src/naveditor/include/InputGeom.h | 19 +- src/naveditor/include/ShapeVolumeTool.h | 9 + 5 files changed, 241 insertions(+), 75 deletions(-) diff --git a/src/naveditor/Editor_Common.cpp b/src/naveditor/Editor_Common.cpp index 6d3e2698..a80978ec 100644 --- a/src/naveditor/Editor_Common.cpp +++ b/src/naveditor/Editor_Common.cpp @@ -23,6 +23,7 @@ #include "DebugUtils/Include/DetourDebugDraw.h" #include "Include/InputGeom.h" #include "DetourTileCache/Include/DetourTileCache.h" +#include "include/ShapeVolumeTool.h" static void EditorCommon_DrawInputGeometry(duDebugDraw* const dd, const InputGeom* const geom, const float maxSlope, const float textureScale) @@ -380,10 +381,17 @@ void Editor_StaticTileMeshCommon::renderTileMeshData() glDepthMask(GL_TRUE); } + int selectedVolumeIndex = -1; + if (m_tool->type() == TOOL_SHAPE_VOLUME) + { + const ShapeVolumeTool* volTool = (const ShapeVolumeTool*)m_tool; + selectedVolumeIndex = volTool->getSelectedVolumeIndex(); + } + // TODO: also add flags for this - m_geom->drawBoxVolumes(&m_dd, recastDrawOffset); - m_geom->drawCylinderVolumes(&m_dd, recastDrawOffset); - m_geom->drawConvexVolumes(&m_dd, recastDrawOffset); + m_geom->drawBoxVolumes(&m_dd, recastDrawOffset, selectedVolumeIndex); + m_geom->drawCylinderVolumes(&m_dd, recastDrawOffset, selectedVolumeIndex); + m_geom->drawConvexVolumes(&m_dd, recastDrawOffset, selectedVolumeIndex); // NOTE: commented out because this already gets rendered when the off-mesh // connection tool is activated. And if we generated an off-mesh link, this diff --git a/src/naveditor/InputGeom.cpp b/src/naveditor/InputGeom.cpp index 2d2f60a4..9fcdb01d 100644 --- a/src/naveditor/InputGeom.cpp +++ b/src/naveditor/InputGeom.cpp @@ -317,6 +317,9 @@ bool InputGeom::loadGeomSet(rcContext* ctx, const std::string& filepath) &vol->verts[0], &vol->verts[1], &vol->verts[2], &vol->verts[3], &vol->verts[4], &vol->verts[5]); + vol->hmin = 0.0f; + vol->hmax = 0.0f; + vol->nverts = 6; vol->type = VOLUME_BOX; } } @@ -331,6 +334,9 @@ bool InputGeom::loadGeomSet(rcContext* ctx, const std::string& filepath) &vol->verts[0], &vol->verts[1], &vol->verts[2], &vol->verts[3], &vol->verts[4]); + vol->hmin = 0.0f; + vol->hmax = 0.0f; + vol->nverts = 5; vol->type = VOLUME_CYLINDER; } } @@ -715,9 +721,11 @@ void InputGeom::addBoxVolume(const float* bmin, const float* bmax, { if (m_volumeCount >= MAX_VOLUMES) return; ShapeVolume* vol = &m_volumes[m_volumeCount++]; - memset(vol, 0, sizeof(ShapeVolume)); rdVcopy(&vol->verts[0], bmin); rdVcopy(&vol->verts[3], bmax); + vol->hmin = 0.0f; + vol->hmax = 0.0f; + vol->nverts = 6; vol->flags = flags; vol->area = area; vol->type = VOLUME_BOX; @@ -728,10 +736,12 @@ void InputGeom::addCylinderVolume(const float* pos, const float radius, { if (m_volumeCount >= MAX_VOLUMES) return; ShapeVolume* vol = &m_volumes[m_volumeCount++]; - memset(vol, 0, sizeof(ShapeVolume)); rdVcopy(vol->verts, pos); vol->verts[3] = radius; vol->verts[4] = height; + vol->hmin = 0.0f; + vol->hmax = 0.0f; + vol->nverts = 5; vol->flags = flags; vol->area = area; vol->type = VOLUME_CYLINDER; @@ -742,7 +752,6 @@ void InputGeom::addConvexVolume(const float* verts, const int nverts, { if (m_volumeCount >= MAX_VOLUMES) return; ShapeVolume* vol = &m_volumes[m_volumeCount++]; - memset(vol, 0, sizeof(ShapeVolume)); memcpy(vol->verts, verts, sizeof(float)*3*nverts); vol->hmin = minh; vol->hmax = maxh; diff --git a/src/naveditor/ShapeVolumeTool.cpp b/src/naveditor/ShapeVolumeTool.cpp index 133321cd..ce44427c 100644 --- a/src/naveditor/ShapeVolumeTool.cpp +++ b/src/naveditor/ShapeVolumeTool.cpp @@ -74,8 +74,30 @@ static int convexhull(const float* pts, int npts, int* out) return i; } +static bool isValidVolumeIndex(const int index, const InputGeom* geom) +{ + return index > -1 && index < geom->getConvexVolumeCount(); +} + +void handleVolumeFlags(int& flags, const char* buttonId) +{ + ImGui::Text("Poly Flags"); + ImGui::Indent(); + + const int numPolyFlags = V_ARRAYSIZE(g_navMeshPolyFlagNames); + char buttonName[64]; + + for (int i = 0; i < numPolyFlags; i++) + { + snprintf(buttonName, sizeof(buttonName), "%s##%s", g_navMeshPolyFlagNames[i], buttonId); + ImGui::CheckboxFlags(buttonName, &flags, i == (numPolyFlags - 1) ? DT_POLYFLAGS_ALL : 1 << i); + } + + ImGui::Unindent(); +} ShapeVolumeTool::ShapeVolumeTool() : + m_selectedVolumeIndex(-1), m_editor(0), m_selectedPrimitive(VOLUME_CONVEX), m_areaType(RC_NULL_AREA), @@ -88,10 +110,54 @@ ShapeVolumeTool::ShapeVolumeTool() : m_convexHeight(650.0f), m_convexDescent(150.0f), m_npts(0), - m_nhull(0) + m_nhull(0), + m_copiedShapeIndex(-1) { } +int ShapeVolumeTool::getVolumeAtPos(const float* p) +{ + InputGeom* geom = m_editor->getInputGeom(); + rdAssert(geom); + + // Delete + int nearestIndex = -1; + const ShapeVolume* vols = geom->getConvexVolumes(); + + for (int i = 0; i < geom->getConvexVolumeCount(); ++i) + { + const ShapeVolume& vol = vols[i]; + + if (vol.type == VOLUME_BOX) + { + if (rdPointInAABB(p, &vol.verts[0], &vol.verts[3])) + { + nearestIndex = i; + } + } + else if (vol.type == VOLUME_CYLINDER) + { + if (rdPointInCylinder(p, &vol.verts[0], vol.verts[3], vol.verts[4])) + { + nearestIndex = i; + } + } + else if (vol.type == VOLUME_CONVEX) + { + if (rdPointInPolygon(p, vol.verts, vol.nverts) && + p[2] >= vol.hmin && p[2] <= vol.hmax) + { + nearestIndex = i; + } + } + + if (nearestIndex != -1) + break; + } + + return nearestIndex; +} + void ShapeVolumeTool::init(Editor* editor) { m_editor = editor; @@ -111,7 +177,9 @@ static const char* s_primitiveNames[] = { void ShapeVolumeTool::handleMenu() { - if (ImGui::BeginCombo("Primitive", s_primitiveNames[m_selectedPrimitive])) + ImGui::Text("Create Shape"); + + if (ImGui::BeginCombo("Primitive##ShapeVolumeCreate", s_primitiveNames[m_selectedPrimitive])) { for (int i = 0; i < V_ARRAYSIZE(s_primitiveNames); i++) { @@ -132,22 +200,28 @@ void ShapeVolumeTool::handleMenu() switch (m_selectedPrimitive) { case VOLUME_BOX: - ImGui::SliderFloat("Box Descent", &m_boxDescent, 0.1f, 4000); - ImGui::SliderFloat("Box Ascent", &m_boxAscent, 0.1f, 4000); + ImGui::SliderFloat("Descent##ShapeVolumeCreate", &m_boxDescent, 0.1f, 4000); + ImGui::SliderFloat("Ascent##ShapeVolumeCreate", &m_boxAscent, 0.1f, 4000); break; case VOLUME_CYLINDER: - ImGui::SliderFloat("Cylinder Radius", &m_cylinderRadius, 0.1f, 4000); - ImGui::SliderFloat("Cylinder Height", &m_cylinderHeight, 0.1f, 4000); + ImGui::SliderFloat("Radius##ShapeVolumeCreate", &m_cylinderRadius, 0.1f, 4000); + ImGui::SliderFloat("Height##ShapeVolumeCreate", &m_cylinderHeight, 0.1f, 4000); break; case VOLUME_CONVEX: - ImGui::SliderFloat("Convex Height", &m_convexHeight, 0.1f, 4000); - ImGui::SliderFloat("Convex Descent", &m_convexDescent, 0.1f, 4000); - ImGui::SliderFloat("Convex Offset", &m_convexOffset, 0.0f, 2000); + ImGui::SliderFloat("Height##ShapeVolumeCreate", &m_convexHeight, 0.1f, 4000); + ImGui::SliderFloat("Descent##ShapeVolumeCreate", &m_convexDescent, 0.1f, 4000); + ImGui::SliderFloat("Offset##ShapeVolumeCreate", &m_convexOffset, 0.0f, 2000); break; } ImGui::PopItemWidth(); + if ((m_npts || m_nhull) && ImGui::Button("Clear Shape##ShapeVolumeCreate")) + { + m_npts = 0; + m_nhull = 0; + } + ImGui::Separator(); ImGui::Text("Brushes"); @@ -155,37 +229,126 @@ void ShapeVolumeTool::handleMenu() bool isEnabled = m_areaType == RC_NULL_AREA; - if (ImGui::Checkbox("Clip", &isEnabled)) + if (ImGui::Checkbox("Clip##ShapeVolumeCreate", &isEnabled)) m_areaType = RC_NULL_AREA; isEnabled = m_areaType == DT_POLYAREA_TRIGGER; - if (ImGui::Checkbox("Trigger", &isEnabled)) - m_areaType = DT_POLYAREA_TRIGGER; // todo(amos): also allow setting flags and store this in .gset. + if (ImGui::Checkbox("Trigger##ShapeVolumeCreate", &isEnabled)) + m_areaType = DT_POLYAREA_TRIGGER; if (m_areaType == DT_POLYAREA_TRIGGER) { - ImGui::Text("Poly Flags"); - ImGui::Indent(); - - const int numPolyFlags = V_ARRAYSIZE(g_navMeshPolyFlagNames); - - for (int i = 0; i < numPolyFlags; i++) - { - const char* flagName = g_navMeshPolyFlagNames[i]; - ImGui::CheckboxFlags(flagName, &m_polyFlags, i == (numPolyFlags-1) ? DT_POLYFLAGS_ALL : 1<getInputGeom(); + + if (!geom || !isValidVolumeIndex(m_selectedVolumeIndex, geom)) + return; + + ImGui::Separator(); + ImGui::Text("Modify Shape"); + + ShapeVolume& vol = geom->getConvexVolumes()[m_selectedVolumeIndex]; + + if (m_selectedVolumeIndex != m_copiedShapeIndex) { - m_npts = 0; - m_nhull = 0; + m_shapeCopy = vol; + m_copiedShapeIndex = m_selectedVolumeIndex; } + + if (vol.area == DT_POLYAREA_TRIGGER) + { + int flags = vol.flags; + handleVolumeFlags(flags, "ShapeVolumeModify"); + + if (flags != vol.flags) + vol.flags = (unsigned short)flags; + } + + ImGui::PushItemWidth(120.f); + + switch (vol.type) + { + case VOLUME_BOX: + + ImGui::PushItemWidth(60); + ImGui::SliderFloat("##ShapeBoxMinsX", &vol.verts[0], m_shapeCopy.verts[0]-4000, vol.verts[3]); + ImGui::SameLine(); + ImGui::SliderFloat("##ShapeBoxMinsY", &vol.verts[1], m_shapeCopy.verts[1]-4000, vol.verts[4]); + ImGui::SameLine(); + ImGui::SliderFloat("##ShapeBoxMinsZ", &vol.verts[2], m_shapeCopy.verts[2]-4000, vol.verts[5]); + ImGui::SameLine(); + ImGui::Text("Mins"); + + ImGui::SliderFloat("##ShapeBoxMaxsX", &vol.verts[3], vol.verts[0], m_shapeCopy.verts[3]+4000); + ImGui::SameLine(); + ImGui::SliderFloat("##ShapeBoxMaxsY", &vol.verts[4], vol.verts[1], m_shapeCopy.verts[4]+4000); + ImGui::SameLine(); + ImGui::SliderFloat("##ShapeBoxMaxsZ", &vol.verts[5], vol.verts[2], m_shapeCopy.verts[5]+4000); + ImGui::SameLine(); + ImGui::Text("Maxs"); + ImGui::PopItemWidth(); + break; + case VOLUME_CYLINDER: + + ImGui::PushItemWidth(55); + ImGui::SliderFloat("##ShapeCylinderX", &vol.verts[0], m_shapeCopy.verts[0]-4000, m_shapeCopy.verts[0]+4000); + ImGui::SameLine(); + ImGui::SliderFloat("##ShapeCylinderY", &vol.verts[1], m_shapeCopy.verts[1]-4000, m_shapeCopy.verts[1]+4000); + ImGui::SameLine(); + ImGui::SliderFloat("##ShapeCylinderZ", &vol.verts[2], m_shapeCopy.verts[2]-4000, m_shapeCopy.verts[2]+4000); + ImGui::SameLine(); + ImGui::Text("Position"); + ImGui::PopItemWidth(); + + ImGui::SliderFloat("Radius##ShapeVolumeModify", &vol.verts[3], 0.1f, 4000); + ImGui::SliderFloat("Height##ShapeVolumeModify", &vol.verts[4], 0.1f, 4000); + break; + case VOLUME_CONVEX: + ImGui::SliderFloat("Descent##ShapeVolumeModify", &vol.hmin, m_shapeCopy.hmin-4000, m_shapeCopy.hmin+4000); + ImGui::SliderFloat("Ascent##ShapeVolumeModify", &vol.hmax, m_shapeCopy.hmax-4000, m_shapeCopy.hmax+4000); + + char sliderId[64]; + + for (int i = 0; i < vol.nverts; i++) + { + const int len = snprintf(sliderId, sizeof(sliderId), "##(%d)ShapeConvexX", i); + if (len < 0) + { + rdAssert(0); + break; + } + + ImGui::PushItemWidth(55); + ImGui::SliderFloat(sliderId, &vol.verts[(i*3)+0], m_shapeCopy.verts[(i*3)+0]-4000, m_shapeCopy.verts[(i*3)+0]+4000); + ImGui::SameLine(); + sliderId[len] = 'Y'; + ImGui::SliderFloat(sliderId, &vol.verts[(i*3)+1], m_shapeCopy.verts[(i*3)+1]-4000, m_shapeCopy.verts[(i*3)+1]+4000); + ImGui::SameLine(); + sliderId[len] = 'Z'; + ImGui::SliderFloat(sliderId, &vol.verts[(i*3)+2], m_shapeCopy.verts[(i*3)+2]-4000, m_shapeCopy.verts[(i*3)+2]+4000); + ImGui::SameLine(); + + snprintf(sliderId, sizeof(sliderId), "Vert #%d", i); + + ImGui::Text(sliderId); + ImGui::PopItemWidth(); + } + break; + } + + if (ImGui::Button("Delete Shape##ShapeVolumeModify")) + { + geom->deleteConvexVolume(m_selectedVolumeIndex); + m_selectedVolumeIndex = -1; + + return; + } + + ImGui::PopItemWidth(); } void ShapeVolumeTool::handleClick(const float* /*s*/, const float* p, bool shift) @@ -196,45 +359,7 @@ void ShapeVolumeTool::handleClick(const float* /*s*/, const float* p, bool shift if (shift) { - // Delete - int nearestIndex = -1; - const ShapeVolume* vols = geom->getConvexVolumes(); - - for (int i = 0; i < geom->getConvexVolumeCount(); ++i) - { - const ShapeVolume& vol = vols[i]; - - if (vol.type == VOLUME_BOX) - { - if (rdPointInAABB(p, &vol.verts[0], &vol.verts[3])) - { - nearestIndex = i; - } - } - else if (vol.type == VOLUME_CYLINDER) - { - if (rdPointInCylinder(p, &vol.verts[0], vol.verts[3], vol.verts[4])) - { - nearestIndex = i; - } - } - else if (vol.type == VOLUME_CONVEX) - { - if (rdPointInPolygon(p, vol.verts, vol.nverts) && - p[2] >= vol.hmin && p[2] <= vol.hmax) - { - nearestIndex = i; - } - } - - if (nearestIndex != -1) - break; - } - // If end point close enough, delete it. - if (nearestIndex != -1) - { - geom->deleteConvexVolume(nearestIndex); - } + m_selectedVolumeIndex = getVolumeAtPos(p); } else // Create { diff --git a/src/naveditor/include/InputGeom.h b/src/naveditor/include/InputGeom.h index 79942772..69174a6f 100644 --- a/src/naveditor/include/InputGeom.h +++ b/src/naveditor/include/InputGeom.h @@ -24,7 +24,8 @@ enum VolumeType : unsigned char { - VOLUME_BOX, + VOLUME_INVALID = 0xff, + VOLUME_BOX = 0, VOLUME_CYLINDER, VOLUME_CONVEX }; @@ -40,6 +41,20 @@ enum TraceMask : unsigned int static const int MAX_SHAPEVOL_PTS = 12; struct ShapeVolume { + ShapeVolume() + { + for (int i = 0; i < MAX_SHAPEVOL_PTS; i++) + { + rdVset(&verts[i*3], 0.f,0.f,0.f); + } + hmin = 0.f; + hmax = 0.f; + nverts = 0; + flags = 0; + area = 0; + type = VOLUME_INVALID; + } + float verts[MAX_SHAPEVOL_PTS*3]; float hmin, hmax; int nverts; @@ -175,7 +190,7 @@ public: /// @name Shape Volumes. ///@{ int getConvexVolumeCount() const { return m_volumeCount; } // todo(amos): rename to 'getShapeVolumeCount' - const ShapeVolume* getConvexVolumes() const { return m_volumes; } // todo(amos): rename to 'getShapeVolumes' + ShapeVolume* getConvexVolumes() { return m_volumes; } // todo(amos): rename to 'getShapeVolumes' void addBoxVolume(const float* bmin, const float* bmax, unsigned short flags, unsigned char area); void addCylinderVolume(const float* pos, const float radius, diff --git a/src/naveditor/include/ShapeVolumeTool.h b/src/naveditor/include/ShapeVolumeTool.h index 1edd94ad..ad465bb2 100644 --- a/src/naveditor/include/ShapeVolumeTool.h +++ b/src/naveditor/include/ShapeVolumeTool.h @@ -27,7 +27,10 @@ class ShapeVolumeTool : public EditorTool { Editor* m_editor; + + int m_selectedVolumeIndex; int m_selectedPrimitive; + int m_areaType; int m_polyFlags; @@ -45,10 +48,16 @@ class ShapeVolumeTool : public EditorTool int m_npts; int m_hull[MAX_SHAPEVOL_PTS]; int m_nhull; + + ShapeVolume m_shapeCopy; + int m_copiedShapeIndex; public: ShapeVolumeTool(); + int getVolumeAtPos(const float* p); + inline int getSelectedVolumeIndex() const { return m_selectedVolumeIndex; }; + virtual int type() { return TOOL_SHAPE_VOLUME; } virtual void init(Editor* editor); virtual void reset();