Recast: implement off-mesh connection editor

Allows the user to modify core attributes of any off-mesh connection in the world.
This commit is contained in:
Kawe Mazidjatari 2024-10-22 12:05:44 +02:00
parent 1bb531c36b
commit d7f2b7d319
7 changed files with 215 additions and 47 deletions

View File

@ -266,6 +266,13 @@ bool InputGeom::loadGeomSet(rcContext* ctx, const std::string& filepath)
&yaw,
&bidir, &jump, &order, &area, &flags);
if (std::isnan(yaw))
{
const float offset[3] = { 0.0f,0.0f,rad };
yaw = dtCalcOffMeshRefYaw(&verts[0], &verts[3]);
dtCalcOffMeshRefPos(verts, yaw, offset, refs);
}
m_offMeshConRads[m_offMeshConCount] = rad;
m_offMeshConRefYaws[m_offMeshConCount] = yaw;
m_offMeshConDirs[m_offMeshConCount] = (unsigned char)bidir;
@ -613,11 +620,11 @@ bool InputGeom::raycastMesh(const float* src, const float* dst, const unsigned i
return hit;
}
void InputGeom::addOffMeshConnection(const float* spos, const float* epos, const float rad,
int InputGeom::addOffMeshConnection(const float* spos, const float* epos, const float rad,
unsigned char bidir, unsigned char jump, unsigned char order,
unsigned char area, unsigned short flags)
{
if (m_offMeshConCount >= MAX_OFFMESH_CONNECTIONS) return;
if (m_offMeshConCount >= MAX_OFFMESH_CONNECTIONS) return -1;
rdAssert(jump < DT_MAX_TRAVERSE_TYPES);
float* verts = &m_offMeshConVerts[m_offMeshConCount*3*2];
@ -626,7 +633,8 @@ void InputGeom::addOffMeshConnection(const float* spos, const float* epos, const
float* refs = &m_offMeshConRefPos[m_offMeshConCount*3];
const float yaw = dtCalcOffMeshRefYaw(spos, epos);
dtCalcOffMeshRefPos(spos, yaw, DT_OFFMESH_CON_REFPOS_OFFSET, refs);
const float offset[3] = { 0.0f,0.0f,rad };
dtCalcOffMeshRefPos(spos, yaw, offset, refs);
m_offMeshConRads[m_offMeshConCount] = rad;
m_offMeshConRefYaws[m_offMeshConCount] = yaw;
@ -636,7 +644,8 @@ void InputGeom::addOffMeshConnection(const float* spos, const float* epos, const
m_offMeshConAreas[m_offMeshConCount] = area;
m_offMeshConFlags[m_offMeshConCount] = flags;
m_offMeshConId[m_offMeshConCount] = 1000 + m_offMeshConCount;
m_offMeshConCount++;
return m_offMeshConCount++;
}
void InputGeom::deleteOffMeshConnection(int i)
@ -661,10 +670,9 @@ void InputGeom::deleteOffMeshConnection(int i)
m_offMeshConFlags[i] = m_offMeshConFlags[m_offMeshConCount];
}
void InputGeom::drawOffMeshConnections(duDebugDraw* dd, const float* offset, bool hilight)
void InputGeom::drawOffMeshConnections(duDebugDraw* dd, const float* offset, const int hilightIdx)
{
unsigned int conColor = duRGBA(192,0,128,192);
unsigned int baseColor = duRGBA(0,0,0,64);
const unsigned int baseColor = duRGBA(0,0,0,64);
dd->depthMask(false);
dd->begin(DU_DRAW_LINES, 2.0f, offset);
@ -681,16 +689,16 @@ void InputGeom::drawOffMeshConnections(duDebugDraw* dd, const float* offset, boo
duAppendCircle(dd, v[0],v[1],v[2]+5.0f, m_offMeshConRads[i], baseColor);
duAppendCircle(dd, v[3],v[4],v[5]+5.0f, m_offMeshConRads[i], baseColor);
if (hilight)
{
duAppendArc(dd, v[0],v[1],v[2], v[3],v[4],v[5], 0.25f,
(m_offMeshConDirs[i]&DT_OFFMESH_CON_BIDIR) ? 30.0f : 0.0f, 30.0f, conColor);
}
const unsigned int conColor = hilightIdx == i ? duRGBA(192,0,128,192) : duRGBA(152,0,78,192);
duAppendArc(dd, v[0], v[1], v[2], v[3], v[4], v[5], 0.25f,
(m_offMeshConDirs[i] & DT_OFFMESH_CON_BIDIR) ? 30.0f : 0.0f, 30.0f, conColor);
float* r = &m_offMeshConRefPos[i*3];
float refPosDir[3];
dtCalcOffMeshRefPos(r, m_offMeshConRefYaws[i], DT_OFFMESH_CON_REFPOS_OFFSET, refPosDir);
const float arrowLength[3] = { 35.f, 35.f, 0.f };
dtCalcOffMeshRefPos(r, m_offMeshConRefYaws[i], arrowLength, refPosDir);
duAppendArrow(dd, r[0],r[1],r[2], refPosDir[0],refPosDir[1],refPosDir[2], 0.f, 10.f, duRGBA(255,255,0,255));
}

View File

@ -35,9 +35,13 @@ OffMeshConnectionTool::OffMeshConnectionTool() :
m_bidir(true),
m_invertVertexLookupOrder(false),
m_traverseType(0),
m_oldFlags(0)
m_oldFlags(0),
m_selectedOffMeshIndex(-1),
m_copiedOffMeshIndex(-1)
{
rdVset(m_hitPos, 0.0f,0.0f,0.0f);
rdVset(m_refOffset, 0.0f,0.0f,0.0f);
memset(&m_copyOffMeshInstance, 0, sizeof(OffMeshConnection));
}
OffMeshConnectionTool::~OffMeshConnectionTool()
@ -63,6 +67,8 @@ void OffMeshConnectionTool::init(Editor* editor)
const float agentRadius = m_editor->getAgentRadius();
m_radius = agentRadius;
m_lastSelectedAgentRadius = agentRadius;
rdVset(m_refOffset, 0.0f,0.0f, agentRadius);
}
}
@ -74,18 +80,149 @@ void OffMeshConnectionTool::reset()
m_hitPosSet = false;
}
void OffMeshConnectionTool::handleMenu()
#define VALUE_ADJUST_WINDOW 200
void OffMeshConnectionTool::renderModifyMenu()
{
InputGeom* geom = m_editor->getInputGeom();
if (!geom) return;
ImGui::Separator();
ImGui::Text("Modify Off-Mesh Connection");
ImGui::SliderInt("Selected##OffMeshConnectionModify", &m_selectedOffMeshIndex, -1, geom->getOffMeshConnectionCount()-1);
if (m_selectedOffMeshIndex == -1)
return;
float* verts = &geom->getOffMeshConnectionVerts()[m_selectedOffMeshIndex*6];
float* refs = &geom->getOffMeshConnectionRefPos()[m_selectedOffMeshIndex*3];
float& rad = geom->getOffMeshConnectionRads()[m_selectedOffMeshIndex];
float& yaw = geom->getOffMeshConnectionRefYaws()[m_selectedOffMeshIndex];
unsigned char& dir = geom->getOffMeshConnectionDirs()[m_selectedOffMeshIndex];
unsigned char& jump = geom->getOffMeshConnectionJumps()[m_selectedOffMeshIndex];
unsigned char& order = geom->getOffMeshConnectionOrders()[m_selectedOffMeshIndex];
unsigned char& area = geom->getOffMeshConnectionAreas()[m_selectedOffMeshIndex];
unsigned short& flags = geom->getOffMeshConnectionFlags()[m_selectedOffMeshIndex];
if (m_copiedOffMeshIndex != m_selectedOffMeshIndex)
{
rdVcopy(&m_copyOffMeshInstance.pos[0], &verts[0]);
rdVcopy(&m_copyOffMeshInstance.pos[3], &verts[3]);
rdVcopy(m_copyOffMeshInstance.refPos, refs);
rdVset(m_refOffset, 0.f,0.f,rad);
m_copyOffMeshInstance.rad = rad;
m_copyOffMeshInstance.refYaw = yaw;
m_copyOffMeshInstance.dir = dir;
m_copyOffMeshInstance.jump = jump;
m_copyOffMeshInstance.order = order;
m_copyOffMeshInstance.area = area;
m_copyOffMeshInstance.flags = flags;
m_copiedOffMeshIndex = m_selectedOffMeshIndex;
}
ImGui::PushItemWidth(60);
ImGui::SliderFloat("##OffMeshConnectionModifyStartX", &verts[0], m_copyOffMeshInstance.pos[0]-VALUE_ADJUST_WINDOW, m_copyOffMeshInstance.pos[0]+VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::SliderFloat("##OffMeshConnectionModifyStartY", &verts[1], m_copyOffMeshInstance.pos[1]-VALUE_ADJUST_WINDOW, m_copyOffMeshInstance.pos[1]+VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::SliderFloat("##OffMeshConnectionModifyStartZ", &verts[2], m_copyOffMeshInstance.pos[2]-VALUE_ADJUST_WINDOW, m_copyOffMeshInstance.pos[2]+VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::Text("Start");
ImGui::SliderFloat("##OffMeshConnectionModifyEndX", &verts[3], m_copyOffMeshInstance.pos[3]-VALUE_ADJUST_WINDOW, m_copyOffMeshInstance.pos[3]+VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::SliderFloat("##OffMeshConnectionModifyEndY", &verts[4], m_copyOffMeshInstance.pos[4]-VALUE_ADJUST_WINDOW, m_copyOffMeshInstance.pos[4]+VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::SliderFloat("##OffMeshConnectionModifyEndZ", &verts[5], m_copyOffMeshInstance.pos[5]-VALUE_ADJUST_WINDOW, m_copyOffMeshInstance.pos[5]+VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::Text("End");
ImGui::SliderFloat("##OffMeshConnectionModifyRefX", &refs[0], m_copyOffMeshInstance.refPos[0]-VALUE_ADJUST_WINDOW, m_copyOffMeshInstance.refPos[0]+VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::SliderFloat("##OffMeshConnectionModifyRefY", &refs[1], m_copyOffMeshInstance.refPos[1]-VALUE_ADJUST_WINDOW, m_copyOffMeshInstance.refPos[1]+VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::SliderFloat("##OffMeshConnectionModifyRefZ", &refs[2], m_copyOffMeshInstance.refPos[2]-VALUE_ADJUST_WINDOW, m_copyOffMeshInstance.refPos[2]+VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::Text("Ref");
ImGui::PopItemWidth();
// On newer navmesh sets, off-mesh links are always bidirectional.
#if DT_NAVMESH_SET_VERSION < 7
ImGui::Checkbox("Bidirectional", &m_bidir);
ImGui::Checkbox("Bidirectional##OffMeshConnectionModify", (bool*)&dir);
#endif
ImGui::Checkbox("Invert Lookup Order", &m_invertVertexLookupOrder);
ImGui::SliderFloat("Radius##OffMeshConnectionModify", &rad, 0, 512);
ImGui::SliderFloat("Yaw##OffMeshConnectionModify", &yaw, -180, 180);
int traverseType = jump;
ImGui::SliderInt("Jump##OffMeshConnectionModify", &traverseType, 0, DT_MAX_TRAVERSE_TYPES-1, "%d", ImGuiSliderFlags_NoInput);
if (traverseType != jump)
jump = (unsigned char)traverseType;
ImGui::PushItemWidth(60);
ImGui::SliderFloat("##OffMeshConnectionModifyRefOffsetX", &m_refOffset[0], -VALUE_ADJUST_WINDOW, VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::SliderFloat("##OffMeshConnectionModifyRefOffsetY", &m_refOffset[1], -VALUE_ADJUST_WINDOW, VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::SliderFloat("##OffMeshConnectionModifyRefOffsetZ", &m_refOffset[2], -VALUE_ADJUST_WINDOW, VALUE_ADJUST_WINDOW);
ImGui::SameLine();
ImGui::Text("Ref Offset");
ImGui::PopItemWidth();
if (ImGui::Button("Recalculate Reference##OffMeshConnectionModify"))
{
yaw = dtCalcOffMeshRefYaw(&verts[0], &verts[3]);
dtCalcOffMeshRefPos(verts, yaw, m_refOffset, refs);
}
if (ImGui::Button("Reset Connection##OffMeshConnectionModify"))
{
rdVcopy(&verts[0], &m_copyOffMeshInstance.pos[0]);
rdVcopy(&verts[3], &m_copyOffMeshInstance.pos[3]);
rdVcopy(refs, m_copyOffMeshInstance.refPos);
rad = m_copyOffMeshInstance.rad;
yaw = m_copyOffMeshInstance.refYaw;
dir = m_copyOffMeshInstance.dir;
jump = m_copyOffMeshInstance.jump;
order = m_copyOffMeshInstance.order;
area = m_copyOffMeshInstance.area;
flags = m_copyOffMeshInstance.flags;
}
if (ImGui::Button("Delete Connection##OffMeshConnectionModify"))
{
geom->deleteOffMeshConnection(m_selectedOffMeshIndex);
m_selectedOffMeshIndex = -1;
m_copiedOffMeshIndex = -1;
return;
}
}
void OffMeshConnectionTool::handleMenu()
{
ImGui::Text("Create Off-Mesh Connection");
// On newer navmesh sets, off-mesh links are always bidirectional.
#if DT_NAVMESH_SET_VERSION < 7
ImGui::Checkbox("Bidirectional##OffMeshConnectionCreate", &m_bidir);
#endif
ImGui::Checkbox("Invert Lookup Order##OffMeshConnectionCreate", &m_invertVertexLookupOrder);
ImGui::PushItemWidth(140);
ImGui::SliderInt("Traverse Type##OffMeshConnectionTool", &m_traverseType, 0, DT_MAX_TRAVERSE_TYPES-1, "%d", ImGuiSliderFlags_NoInput);
ImGui::SliderFloat("Radius##OffMeshConnectionTool", &m_radius, 0, 512);
ImGui::SliderInt("Jump##OffMeshConnectionCreate", &m_traverseType, 0, DT_MAX_TRAVERSE_TYPES-1, "%d", ImGuiSliderFlags_NoInput);
ImGui::SliderFloat("Radius##OffMeshConnectionCreate", &m_radius, 0, 512);
ImGui::PopItemWidth();
renderModifyMenu();
}
void OffMeshConnectionTool::handleClick(const float* /*s*/, const float* p, const int /*v*/, bool shift)
@ -96,7 +233,7 @@ void OffMeshConnectionTool::handleClick(const float* /*s*/, const float* p, cons
if (shift)
{
// Delete
// Select
// Find nearest link end-point
float nearestDist = FLT_MAX;
int nearestIndex = -1;
@ -104,18 +241,18 @@ void OffMeshConnectionTool::handleClick(const float* /*s*/, const float* p, cons
for (int i = 0; i < geom->getOffMeshConnectionCount()*2; ++i)
{
const float* v = &verts[i*3];
float d = rdVdistSqr(p, v);
float d = rdVdist2DSqr(p, v);
if (d < nearestDist)
{
nearestDist = d;
nearestIndex = i/2; // Each link has two vertices.
}
}
// If end point close enough, delete it.
// If end point close enough, select it it.
if (nearestIndex != -1 &&
sqrtf(nearestDist) < m_radius)
rdMathSqrtf(nearestDist) < m_radius)
{
geom->deleteOffMeshConnection(nearestIndex);
m_selectedOffMeshIndex = nearestIndex;
}
}
else
@ -135,7 +272,7 @@ void OffMeshConnectionTool::handleClick(const float* /*s*/, const float* p, cons
#else
;
#endif;
geom->addOffMeshConnection(m_hitPos, p, m_radius, m_bidir ? 1 : 0,
m_selectedOffMeshIndex = geom->addOffMeshConnection(m_hitPos, p, m_radius, m_bidir ? 1 : 0,
(unsigned char)m_traverseType, m_invertVertexLookupOrder ? 1 : 0, area, flags);
m_hitPosSet = false;
}
@ -171,7 +308,7 @@ void OffMeshConnectionTool::handleRender()
InputGeom* geom = m_editor->getInputGeom();
if (geom)
geom->drawOffMeshConnections(&dd, m_editor->getRecastDrawOffset(), true);
geom->drawOffMeshConnections(&dd, m_editor->getRecastDrawOffset(), m_selectedOffMeshIndex);
}
void OffMeshConnectionTool::handleRenderOverlay(double* proj, double* model, int* view)

View File

@ -174,28 +174,28 @@ public:
/// @name Off-Mesh connections.
///@{
int getOffMeshConnectionCount() const { return m_offMeshConCount; }
const float* getOffMeshConnectionVerts() const { return m_offMeshConVerts; }
const float* getOffMeshConnectionRads() const { return m_offMeshConRads; }
const unsigned char* getOffMeshConnectionDirs() const { return m_offMeshConDirs; }
const unsigned char* getOffMeshConnectionJumps() const { return m_offMeshConJumps; }
const unsigned char* getOffMeshConnectionOrders() const { return m_offMeshConOrders; }
const unsigned char* getOffMeshConnectionAreas() const { return m_offMeshConAreas; }
const unsigned short* getOffMeshConnectionFlags() const { return m_offMeshConFlags; }
const unsigned short* getOffMeshConnectionId() const { return m_offMeshConId; }
const float* getOffMeshConnectionRefPos() const { return m_offMeshConRefPos; }
const float* getOffMeshConnectionRefYaws() const { return m_offMeshConRefYaws; }
void addOffMeshConnection(const float* spos, const float* epos, const float rad,
int getOffMeshConnectionCount() { return m_offMeshConCount; }
float* getOffMeshConnectionVerts() { return m_offMeshConVerts; }
float* getOffMeshConnectionRads() { return m_offMeshConRads; }
unsigned char* getOffMeshConnectionDirs() { return m_offMeshConDirs; }
unsigned char* getOffMeshConnectionJumps() { return m_offMeshConJumps; }
unsigned char* getOffMeshConnectionOrders() { return m_offMeshConOrders; }
unsigned char* getOffMeshConnectionAreas() { return m_offMeshConAreas; }
unsigned short* getOffMeshConnectionFlags() { return m_offMeshConFlags; }
unsigned short* getOffMeshConnectionId() { return m_offMeshConId; }
float* getOffMeshConnectionRefPos() { return m_offMeshConRefPos; }
float* getOffMeshConnectionRefYaws() { return m_offMeshConRefYaws; }
int addOffMeshConnection(const float* spos, const float* epos, const float rad,
unsigned char bidir, unsigned char jump, unsigned char order,
unsigned char area, unsigned short flags);
void deleteOffMeshConnection(int i);
void drawOffMeshConnections(struct duDebugDraw* dd, const float* offset, bool hilight = false);
void drawOffMeshConnections(struct duDebugDraw* dd, const float* offset, const int hilightIdx = -1);
///@}
/// @name Shape Volumes.
///@{
int getShapeVolumeCount() const { return m_volumeCount; } // todo(amos): rename to 'getShapeVolumeCount'
ShapeVolume* getShapeVolumes() { return m_volumes; } // todo(amos): rename to 'getShapeVolumes'
int getShapeVolumeCount() const { return m_volumeCount; }
ShapeVolume* getShapeVolumes() { return m_volumes; }
int addBoxVolume(const float* bmin, const float* bmax,
unsigned short flags, unsigned char area);
int addCylinderVolume(const float* pos, const float radius,

View File

@ -23,10 +23,25 @@
// Tool to create off-mesh connection for InputGeom
// TODO: eventually this needs to be the type for off-mesh connections in InputGeom!
struct OffMeshConnection
{
float pos[6];
float refPos[3];
float rad;
float refYaw;
unsigned char dir;
unsigned char jump;
unsigned char order;
unsigned char area;
unsigned short flags;
};
class OffMeshConnectionTool : public EditorTool
{
Editor* m_editor;
float m_hitPos[3];
float m_refOffset[3];
float m_lastSelectedAgentRadius;
float m_radius;
bool m_hitPosSet;
@ -34,10 +49,17 @@ class OffMeshConnectionTool : public EditorTool
bool m_invertVertexLookupOrder;
int m_traverseType;
unsigned int m_oldFlags;
int m_selectedOffMeshIndex;
int m_copiedOffMeshIndex;
OffMeshConnection m_copyOffMeshInstance;
public:
OffMeshConnectionTool();
~OffMeshConnectionTool();
void renderModifyMenu();
virtual int type() { return TOOL_OFFMESH_CONNECTION; }
virtual void init(Editor* editor);

View File

@ -413,7 +413,8 @@ static void drawTileBounds(duDebugDraw* dd, const dtMeshTile* tile, const float*
static void drawOffMeshConnectionRefPosition(duDebugDraw* dd, const dtOffMeshConnection* con)
{
float refPosDir[3];
dtCalcOffMeshRefPos(con->refPos, con->refYaw, DT_OFFMESH_CON_REFPOS_OFFSET, refPosDir);
const float arrowLength[3] = { 35.f, 35.f, 0.f };
dtCalcOffMeshRefPos(con->refPos, con->refYaw, arrowLength, refPosDir);
duAppendArrow(dd, con->refPos[0], con->refPos[1], con->refPos[2],
refPosDir[0], refPosDir[1], refPosDir[2], 0.f, 10.f, duRGBA(255,255,0,255));

View File

@ -530,7 +530,7 @@ extern float dtCalcOffMeshRefYaw(const float* spos, const float* epos);
/// @param yawRad[in] The yaw angle of the off-mesh connection in radians.
/// @param offset[in] The desired offset from the start position.
/// @param res[in] The output ref position.
extern void dtCalcOffMeshRefPos(const float* spos, float yawRad, float offset, float* res);
extern void dtCalcOffMeshRefPos(const float* spos, const float yawDeg, const float* offset, float* res);
/// Provides high level information related to a dtMeshTile object.
/// @ingroup detour

View File

@ -2206,16 +2206,16 @@ float dtCalcOffMeshRefYaw(const float* spos, const float* epos)
return rdRadToDeg(yawRad);
}
void dtCalcOffMeshRefPos(const float* spos, float yawDeg, float offset, float* res)
void dtCalcOffMeshRefPos(const float* spos, const float yawDeg, const float* offset, float* res)
{
const float yawRad = rdDegToRad(yawDeg);
const float dx = offset*rdMathCosf(yawRad);
const float dy = offset*rdMathSinf(yawRad);
const float dx = offset[0]*rdMathCosf(yawRad);
const float dy = offset[1]*rdMathSinf(yawRad);
res[0] = spos[0]+dx;
res[1] = spos[1]+dy;
res[2] = spos[2];
res[2] = spos[2]+offset[2];
}
int dtGetNavMeshVersionForSet(const int setVersion)