From 3fbe657577b4fdfaf80e7a15e3b9f80a08d1186d Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Sun, 7 Jul 2024 17:04:00 +0200 Subject: [PATCH] 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. --- src/naveditor/CrowdTool.cpp | 43 +++++++++- src/naveditor/Editor.cpp | 2 +- src/naveditor/Editor_TileMesh.cpp | 11 ++- src/naveditor/NavMeshTesterTool.cpp | 80 ++++++++++++++++++- src/naveditor/include/CrowdTool.h | 5 ++ src/naveditor/include/Editor.h | 6 +- src/naveditor/include/NavMeshTesterTool.h | 3 + .../recast/DetourCrowd/Include/DetourCrowd.h | 8 +- .../recast/DetourCrowd/Source/DetourCrowd.cpp | 13 ++- 9 files changed, 161 insertions(+), 10 deletions(-) diff --git a/src/naveditor/CrowdTool.cpp b/src/naveditor/CrowdTool.cpp index 2e77c8ea..50905874 100644 --- a/src/naveditor/CrowdTool.cpp +++ b/src/naveditor/CrowdTool.cpp @@ -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; diff --git a/src/naveditor/Editor.cpp b/src/naveditor/Editor.cpp index 7579630f..2511509c 100644 --- a/src/naveditor/Editor.cpp +++ b/src/naveditor/Editor.cpp @@ -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) diff --git a/src/naveditor/Editor_TileMesh.cpp b/src/naveditor/Editor_TileMesh.cpp index 334be984..7c57e16c 100644 --- a/src/naveditor/Editor_TileMesh.cpp +++ b/src/naveditor/Editor_TileMesh.cpp @@ -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."); } diff --git a/src/naveditor/NavMeshTesterTool.cpp b/src/naveditor/NavMeshTesterTool.cpp index f2750a3a..15bb1dcf 100644 --- a/src/naveditor/NavMeshTesterTool.cpp +++ b/src/naveditor/NavMeshTesterTool.cpp @@ -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]; diff --git a/src/naveditor/include/CrowdTool.h b/src/naveditor/include/CrowdTool.h index d2e8c7db..ad00cf06 100644 --- a/src/naveditor/include/CrowdTool.h +++ b/src/naveditor/include/CrowdTool.h @@ -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 diff --git a/src/naveditor/include/Editor.h b/src/naveditor/include/Editor.h index 8a379ea4..52fd93ac 100644 --- a/src/naveditor/include/Editor.h +++ b/src/naveditor/include/Editor.h @@ -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(); diff --git a/src/naveditor/include/NavMeshTesterTool.h b/src/naveditor/include/NavMeshTesterTool.h index 4e6f2efc..da38eed4 100644 --- a/src/naveditor/include/NavMeshTesterTool.h +++ b/src/naveditor/include/NavMeshTesterTool.h @@ -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; diff --git a/src/thirdparty/recast/DetourCrowd/Include/DetourCrowd.h b/src/thirdparty/recast/DetourCrowd/Include/DetourCrowd.h index 0de56111..2f4358f9 100644 --- a/src/thirdparty/recast/DetourCrowd/Include/DetourCrowd.h +++ b/src/thirdparty/recast/DetourCrowd/Include/DetourCrowd.h @@ -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; diff --git a/src/thirdparty/recast/DetourCrowd/Source/DetourCrowd.cpp b/src/thirdparty/recast/DetourCrowd/Source/DetourCrowd.cpp index ee30cc8e..745ce081 100644 --- a/src/thirdparty/recast/DetourCrowd/Source/DetourCrowd.cpp +++ b/src/thirdparty/recast/DetourCrowd/Source/DetourCrowd.cpp @@ -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);