Recast: implement static pathing logic in editor

The editor now takes the static pathing data into account when creating paths/testing the navmesh using the NavMeshTesterTool or CrowdTool. An option is made allowing you to select which traverse anim type you want to use for pathing (each of them uses a different traversal table, thus giving them different options as to which links and jumps they can take).

This allows us to test AI withing the editor itself, thus saving a lot of time shuffling navmesh files around and reloading them in-game.
This commit is contained in:
Kawe Mazidjatari 2024-07-07 17:04:00 +02:00
parent 86bdbce7b1
commit 3fbe657577
9 changed files with 161 additions and 10 deletions

View File

@ -101,6 +101,7 @@ CrowdToolState::CrowdToolState() :
m_toolParams.m_showPerfGraph = false;
m_toolParams.m_showDetailAll = false;
m_toolParams.m_expandOptions = true;
m_toolParams.m_expandTraversalOptions = false;
m_toolParams.m_anticipateTurns = true;
m_toolParams.m_optimizeVis = true;
m_toolParams.m_optimizeTopo = true;
@ -110,6 +111,7 @@ CrowdToolState::CrowdToolState() :
m_toolParams.m_separationWeight = 20.0f;
m_toolParams.m_maxAcceleration = 800.f;
m_toolParams.m_maxSpeed = 200.f;
m_toolParams.m_traverseAnimType = ANIMTYPE_NONE;
memset(m_trails, 0, sizeof(m_trails));
@ -135,6 +137,8 @@ void CrowdToolState::init(class Editor* editor)
dtNavMesh* nav = m_editor->getNavMesh();
dtCrowd* crowd = m_editor->getCrowd();
m_toolParams.m_traverseAnimType = NavMesh_GetFirstTraverseAnimTypeForType(m_editor->getLoadedNavMeshType());
if (nav && crowd && (m_nav != nav || m_crowd != crowd))
{
@ -562,7 +566,12 @@ void CrowdToolState::handleRenderOverlay(double* proj, double* model, int* view)
if (gluProject((GLdouble)pos[0], (GLdouble)pos[1], (GLdouble)pos[2]+h,
model, proj, view, &x, &y, &z))
{
snprintf(label, 32, "%d", i);
const TraverseAnimType_e animType = ag->params.traverseAnimType;
const char* animTypeName = animType == ANIMTYPE_NONE
? "none"
: g_traverseAnimTypeNames[animType];
snprintf(label, 32, "%s (%d)", animTypeName, i);
imguiDrawText((int)x, (int)y+15, IMGUI_ALIGN_CENTER, label, imguiRGBA(0,0,0,220));
}
}
@ -649,6 +658,7 @@ void CrowdToolState::addAgent(const float* p)
ap.updateFlags |= DT_CROWD_SEPARATION;
ap.obstacleAvoidanceType = (unsigned char)m_toolParams.m_obstacleAvoidanceType;
ap.separationWeight = m_toolParams.m_separationWeight;
ap.traverseAnimType = m_toolParams.m_traverseAnimType;
int idx = crowd->addAgent(p, &ap);
if (idx != -1)
@ -944,6 +954,37 @@ void CrowdTool::handleMenu()
imguiUnindent();
}
if (imguiCollapse("Traverse Animation Type", 0, params->m_expandTraversalOptions))
params->m_expandTraversalOptions = !params->m_expandTraversalOptions;
const NavMeshType_e loadedNavMeshType = m_editor->getLoadedNavMeshType();
// TODO: perhaps clamp with m_nav->m_params.traversalTableCount? Technically a navmesh should
// contain all the traversal tables it supports, so if we crash the navmesh is technically corrupt.
const int traverseTableCount = NavMesh_GetTraversalTableCountForNavMeshType(loadedNavMeshType);
const TraverseAnimType_e baseType = NavMesh_GetFirstTraverseAnimTypeForType(loadedNavMeshType);
if (params->m_expandTraversalOptions)
{
imguiIndent();
for (int i = ANIMTYPE_NONE; i < traverseTableCount; i++)
{
const bool noAnimtype = i == ANIMTYPE_NONE;
const TraverseAnimType_e animTypeIndex = noAnimtype ? ANIMTYPE_NONE : TraverseAnimType_e((int)baseType + i);
const char* animtypeName = noAnimtype ? "none" : g_traverseAnimTypeNames[animTypeIndex];
if (imguiCheck(animtypeName, params->m_traverseAnimType == animTypeIndex))
{
params->m_traverseAnimType = animTypeIndex;
m_state->updateAgentParams();
}
}
imguiUnindent();
}
if (imguiCollapse("Selected Debug Draw", 0, params->m_expandSelectedDebugDraw))
params->m_expandSelectedDebugDraw = !params->m_expandSelectedDebugDraw;

View File

@ -62,7 +62,7 @@ Editor::Editor() :
m_filterLowHangingObstacles(true),
m_filterLedgeSpans(true),
m_filterWalkableLowHeightSpans(true),
m_navMeshType(NAVMESH_SMALL),
m_selectedNavMeshType(NAVMESH_SMALL),
m_navmeshName(NavMesh_GetNameForType(NAVMESH_SMALL)),
m_tool(0),
m_ctx(0)

View File

@ -232,7 +232,7 @@ void Editor_TileMesh::selectNavMeshType(const NavMeshType_e navMeshType)
m_navmeshName = h.name;
m_tileSize = h.tileSize;
m_navMeshType = navMeshType;
m_selectedNavMeshType = navMeshType;
}
void Editor_TileMesh::handleSettings()
@ -299,6 +299,9 @@ void Editor_TileMesh::handleSettings()
rdFreeNavMesh(m_navMesh);
m_navMesh = Editor::loadAll(m_modelName.c_str());
m_navQuery->init(m_navMesh, 2048);
m_loadedNavMeshType = m_selectedNavMeshType;
initToolStates(this);
}
if (imguiButton("Save"))
@ -679,6 +682,8 @@ bool Editor_TileMesh::handleBuild()
return false;
}
m_loadedNavMeshType = m_selectedNavMeshType;
dtNavMeshParams params;
rcVcopy(params.orig, m_geom->getNavMeshBoundsMin());
@ -690,7 +695,7 @@ bool Editor_TileMesh::handleBuild()
params.maxPolys = m_maxPolysPerTile;
params.polyGroupCount = 0;
params.traversalTableSize = 0;
params.traversalTableCount = NavMesh_GetTraversalTableCountForNavMeshType(m_navMeshType);
params.traversalTableCount = NavMesh_GetTraversalTableCountForNavMeshType(m_selectedNavMeshType);
params.magicDataCount = 0;
dtStatus status;
@ -842,7 +847,7 @@ void Editor_TileMesh::buildAllTiles()
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Failed to build disjoint poly groups.");
}
if (!dtCreateTraversalTableData(m_navMesh, data, NavMesh_GetTraversalTableCountForNavMeshType(m_navMeshType)))
if (!dtCreateTraversalTableData(m_navMesh, data, NavMesh_GetTraversalTableCountForNavMeshType(m_selectedNavMeshType)))
{
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Failed to build traversal table data.");
}

View File

@ -159,6 +159,7 @@ NavMeshTesterTool::NavMeshTesterTool() :
m_navQuery(0),
m_pathFindStatus(DT_FAILURE),
m_toolMode(TOOLMODE_PATHFIND_FOLLOW),
m_traverseAnimType(ANIMTYPE_NONE),
m_straightPathOptions(0),
m_startRef(0),
m_endRef(0),
@ -401,7 +402,33 @@ void NavMeshTesterTool::handleMenu()
}
imguiUnindent();
imguiSeparator();
imguiSeparator();
imguiLabel("Traverse Anim Type");
imguiIndent();
const NavMeshType_e loadedNavMeshType = m_editor->getLoadedNavMeshType();
// TODO: perhaps clamp with m_nav->m_params.traversalTableCount? Technically a navmesh should
// contain all the traversal tables it supports, so if we crash the navmesh is technically corrupt.
const int traverseTableCount = NavMesh_GetTraversalTableCountForNavMeshType(loadedNavMeshType);
const TraverseAnimType_e baseType = NavMesh_GetFirstTraverseAnimTypeForType(loadedNavMeshType);
for (int i = ANIMTYPE_NONE; i < traverseTableCount; i++)
{
const bool noAnimtype = i == ANIMTYPE_NONE;
const TraverseAnimType_e animTypeIndex = noAnimtype ? ANIMTYPE_NONE : TraverseAnimType_e((int)baseType + i);
const char* animtypeName = noAnimtype ? "none" : g_traverseAnimTypeNames[animTypeIndex];
if (imguiCheck(animtypeName, m_traverseAnimType == animTypeIndex))
{
m_traverseAnimType = animTypeIndex;
}
}
imguiUnindent();
imguiSeparator();
}
void NavMeshTesterTool::handleClick(const float* /*s*/, const float* p, bool shift)
@ -431,7 +458,19 @@ void NavMeshTesterTool::handleToggle()
if (!m_sposSet || !m_eposSet || !m_startRef || !m_endRef)
return;
const bool hasAnimType = m_traverseAnimType != ANIMTYPE_NONE;
const int traversalTableIndex = hasAnimType
? NavMesh_GetTraversalTableIndexForAnimType(m_traverseAnimType)
: NULL;
if (!m_navMesh->isGoalPolyReachable(m_startRef, m_endRef, !hasAnimType, traversalTableIndex))
{
printf("%s: end poly '%d' is unreachable from start poly '%d'\n", "m_navMesh->isGoalPolyReachable", m_startRef, m_endRef);
reset();
return;
}
static const float STEP_SIZE = 10.0f;
static const float SLOP = 2.0f;
@ -632,6 +671,18 @@ void NavMeshTesterTool::recalc()
m_endRef = 0;
m_pathFindStatus = DT_FAILURE;
const bool hasAnimType = m_traverseAnimType != ANIMTYPE_NONE;
const int traversalTableIndex = hasAnimType
? NavMesh_GetTraversalTableIndexForAnimType(m_traverseAnimType)
: NULL;
if (!m_navMesh->isGoalPolyReachable(m_startRef, m_endRef, !hasAnimType, traversalTableIndex))
{
printf("%s: end poly '%d' is unreachable from start poly '%d'\n", "m_navMesh->isGoalPolyReachable", m_startRef, m_endRef);
reset();
return;
}
if (m_toolMode == TOOLMODE_PATHFIND_FOLLOW)
{
@ -1317,6 +1368,31 @@ void NavMeshTesterTool::handleRenderOverlay(double* proj, double* model, int* vi
{
imguiDrawText((int)x, (int)(y-25), IMGUI_ALIGN_CENTER, "End", imguiRGBA(0,0,0,220));
}
// Useful utility to draw all polygroup id's at the center of the polygons.
// The code has been commented as this is very expensive, and we need to add
// an option to only render a range of group id's.
//if (m_navMesh)
//{
// for (int i = 0; i < m_navMesh->getMaxTiles(); i++)
// {
// const dtMeshTile* tile = m_navMesh->getTile(i);
// if (!tile->header) continue;
// for (int j = 0; j < tile->header->polyCount; j++)
// {
// const dtPoly* poly = &tile->polys[j];
// if (gluProject((GLdouble)poly->center[0], (GLdouble)poly->center[1], (GLdouble)poly->center[2] + 30,
// model, proj, view, &x, &y, &z))
// {
// char label[6];
// snprintf(label, sizeof(label), "%hu", poly->groupId);
// imguiDrawText((int)x, (int)y, IMGUI_ALIGN_CENTER, label, imguiRGBA(0, 0, 0, 220));
// }
// }
// }
//}
// Tool help
const int h = view[3];

View File

@ -25,6 +25,8 @@
#include "NavEditor/Include/ValueHistory.h"
#include "NavEditor/Include/Editor.h"
#include "game/server/ai_navmesh.h"
// Tool to create crowds.
struct CrowdToolParams
@ -45,6 +47,7 @@ struct CrowdToolParams
bool m_showDetailAll;
bool m_expandOptions;
bool m_expandTraversalOptions;
bool m_anticipateTurns;
bool m_optimizeVis;
bool m_optimizeTopo;
@ -55,6 +58,8 @@ struct CrowdToolParams
float m_maxAcceleration;
float m_maxSpeed;
TraverseAnimType_e m_traverseAnimType;
};
class CrowdToolState : public EditorToolState

View File

@ -135,7 +135,8 @@ protected:
float m_detailSampleMaxError;
int m_partitionType;
NavMeshType_e m_navMeshType;
NavMeshType_e m_selectedNavMeshType;
NavMeshType_e m_loadedNavMeshType;
const char* m_navmeshName;
EditorTool* m_tool;
@ -188,6 +189,9 @@ public:
inline void toggleNavMeshDrawFlag(unsigned int flag) { m_navMeshDrawFlags ^= flag; }
inline NavMeshType_e getSelectedNavMeshType() const { return m_selectedNavMeshType; }
inline NavMeshType_e getLoadedNavMeshType() const { return m_loadedNavMeshType; }
void updateToolStates(const float dt);
void initToolStates(Editor* editor);
void resetToolStates();

View File

@ -23,6 +23,8 @@
#include "Detour/Include/DetourNavMeshQuery.h"
#include "NavEditor/Include/Editor.h"
#include "game/server/ai_navmesh.h"
class NavMeshTesterTool : public EditorTool
{
Editor* m_editor;
@ -47,6 +49,7 @@ class NavMeshTesterTool : public EditorTool
};
ToolMode m_toolMode;
TraverseAnimType_e m_traverseAnimType;
int m_straightPathOptions;

View File

@ -26,6 +26,8 @@
#include "DetourProximityGrid.h"
#include "DetourPathQueue.h"
#include "game/server/ai_navmesh.h"
/// The maximum number of neighbors that a crowd agent can take into account
/// for steering decisions.
/// @ingroup crowd
@ -83,9 +85,13 @@ struct dtCrowdAgentParams
float pathOptimizationRange; ///< The path visibility optimization range. [Limit: > 0]
/// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
/// How aggressive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
float separationWeight;
/// The traversal animation type, which is used to determine which traversal table this agent will use.
/// [Limit: ANIMTYPE_NONE >= value <= #dtNavMeshParams::traversalTableCount]
TraverseAnimType_e traverseAnimType;
/// Flags that impact steering behavior. (See: #UpdateFlags)
unsigned char updateFlags;

View File

@ -459,9 +459,20 @@ void dtCrowd::updateMoveRequest(const float /*dt*/)
if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY)
continue;
const dtPolyRef* path = ag->corridor.getPath();
const TraverseAnimType_e animType = ag->params.traverseAnimType;
const bool hasAnimType = animType != ANIMTYPE_NONE;
const int traversalTableIndex = hasAnimType
? NavMesh_GetTraversalTableIndexForAnimType(animType)
: NULL;
// Don't fire off the request if the goal is unreachable.
if (!m_navquery->isGoalPolyReachable(path[0], ag->targetRef, !hasAnimType, traversalTableIndex))
continue;
if (ag->targetState == DT_CROWDAGENT_TARGET_REQUESTING)
{
const dtPolyRef* path = ag->corridor.getPath();
const int npath = ag->corridor.getPathCount();
rdAssert(npath);