Recast: implement primitive shape volume editor

Allows for selecting already created volumes and modifying or deleting them.
This commit is contained in:
Kawe Mazidjatari 2024-09-22 12:27:45 +02:00
parent da66a7b9c1
commit 9e46c8be75
5 changed files with 241 additions and 75 deletions

View File

@ -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

View File

@ -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;

View File

@ -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<<i);
}
ImGui::Unindent();
handleVolumeFlags(m_polyFlags, "ShapeVolumeCreate");
}
ImGui::Unindent();
ImGui::Separator();
if (ImGui::Button("Clear Shape"))
InputGeom* geom = m_editor->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
{

View File

@ -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,

View File

@ -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();