From a8b302f165be3f968f8a1a49c5c7d66c5b2aaea7 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Wed, 16 Oct 2024 19:55:37 +0200 Subject: [PATCH] Recast: rewrite polygon island grouping algorithm Flood-fill assign instead to make sure all polygon islands are mapped to a contiguous range of group id's on the first pass. This is required as we will otherwise reach the DT_MAX_POLY_GROUP_COUNT limit on complex and large geometry before the prune pass while we don't have DT_MAX_POLY_GROUP_COUNT polygon islands inside the navmesh. Also implemented poly group overflow protection and simplified the API. --- src/naveditor/Editor.cpp | 73 ++-- src/naveditor/Editor_TileMesh.cpp | 8 +- src/naveditor/NavMeshPruneTool.cpp | 18 +- src/naveditor/include/Editor.h | 5 +- src/naveditor/include/NavMeshPruneTool.h | 1 - .../recast/Detour/Include/DetourNavMesh.h | 20 +- .../Detour/Include/DetourNavMeshBuilder.h | 23 +- .../Detour/Source/DetourNavMeshBuilder.cpp | 410 +++++++++--------- 8 files changed, 260 insertions(+), 298 deletions(-) diff --git a/src/naveditor/Editor.cpp b/src/naveditor/Editor.cpp index f331fa16..e2201a3a 100644 --- a/src/naveditor/Editor.cpp +++ b/src/naveditor/Editor.cpp @@ -131,7 +131,7 @@ Editor::Editor() : m_filterLedgeSpans(true), m_filterWalkableLowHeightSpans(true), m_traverseRayDynamicOffset(true), - m_collapseLinkedPolyGroups(true), + m_collapseLinkedPolyGroups(false), m_buildBvTree(true), m_selectedNavMeshType(NAVMESH_SMALL), m_loadedNavMeshType(NAVMESH_SMALL), @@ -430,6 +430,10 @@ void Editor::handleCommonSettings() m_traverseLinkDrawParams.extraOffset = m_traverseRayExtraOffset; ImGui::SliderFloat("Min Overlap", &m_traverseEdgeMinOverlap, 0.0f, m_tileSize*m_cellSize, "%g"); + + if (ImGui::Button("Rebuild Static Pathing Data")) + createStaticPathingData(); + ImGui::Separator(); } @@ -779,38 +783,6 @@ bool Editor::createTraverseLinks() return true; } -bool Editor::createStaticPathingData(const dtTraverseTableCreateParams* params) -{ - if (!params->nav) return false; - - if (!dtCreateDisjointPolyGroups(params)) - { - m_ctx->log(RC_LOG_ERROR, "createStaticPathingData: Failed to build disjoint poly groups."); - return false; - } - - return true; -} - -bool Editor::updateStaticPathingData(const dtTraverseTableCreateParams* params) -{ - if (!params->nav) return false; - - if (!dtUpdateDisjointPolyGroups(params)) - { - m_ctx->log(RC_LOG_ERROR, "updateStaticPathingData: Failed to update disjoint poly groups."); - return false; - } - - if (!dtCreateTraverseTableData(params)) - { - m_ctx->log(RC_LOG_ERROR, "updateStaticPathingData: Failed to build traverse table data."); - return false; - } - - return true; -} - static bool animTypeSupportsTraverseLink(const dtTraverseTableCreateParams* params, const dtLink* link, const int tableIndex) { const NavMeshType_e navMeshType = (NavMeshType_e)params->navMeshType; @@ -823,23 +795,32 @@ static bool animTypeSupportsTraverseLink(const dtTraverseTableCreateParams* para return rdBitCellBit(link->traverseType) & s_traverseAnimTraverseFlags[traverseAnimType]; } -void Editor::createTraverseTableParams(dtTraverseTableCreateParams* params) +bool Editor::createStaticPathingData() { - params->nav = m_navMesh; - params->sets = m_djs; - params->tableCount = NavMesh_GetTraverseTableCountForNavMeshType(m_selectedNavMeshType); - params->navMeshType = m_selectedNavMeshType; - params->canTraverse = animTypeSupportsTraverseLink; - params->collapseGroups = m_collapseLinkedPolyGroups; -} + if (!m_navMesh) + return false; -void Editor::buildStaticPathingData() -{ dtTraverseTableCreateParams params; - createTraverseTableParams(¶ms); + params.nav = m_navMesh; + params.sets = m_djs; + params.tableCount = NavMesh_GetTraverseTableCountForNavMeshType(m_selectedNavMeshType); + params.navMeshType = m_selectedNavMeshType; + params.canTraverse = animTypeSupportsTraverseLink; + params.collapseGroups = m_collapseLinkedPolyGroups; - createStaticPathingData(¶ms); - updateStaticPathingData(¶ms); + if (!dtCreateDisjointPolyGroups(¶ms)) + { + m_ctx->log(RC_LOG_ERROR, "createStaticPathingData: Failed to build disjoint poly groups."); + return false; + } + + if (!dtCreateTraverseTableData(¶ms)) + { + m_ctx->log(RC_LOG_ERROR, "createStaticPathingData: Failed to build traverse table data."); + return false; + } + + return true; } void Editor::connectOffMeshLinks() diff --git a/src/naveditor/Editor_TileMesh.cpp b/src/naveditor/Editor_TileMesh.cpp index ab5e9bf9..4d197eae 100644 --- a/src/naveditor/Editor_TileMesh.cpp +++ b/src/naveditor/Editor_TileMesh.cpp @@ -739,7 +739,7 @@ void Editor_TileMesh::buildTile(const float* pos) params.linkToNeighbor = true; m_navMesh->connectTraverseLinks(tileRef, params); - buildStaticPathingData(); + createStaticPathingData(); } } @@ -803,7 +803,7 @@ void Editor_TileMesh::removeTile(const float* pos) ++it; } - buildStaticPathingData(); + createStaticPathingData(); } } @@ -850,7 +850,7 @@ void Editor_TileMesh::buildAllTiles() connectOffMeshLinks(); createTraverseLinks(); - buildStaticPathingData(); + createStaticPathingData(); // Start the build process. m_ctx->stopTimer(RC_TIMER_TEMP); @@ -877,7 +877,7 @@ void Editor_TileMesh::removeAllTiles() m_navMesh->removeTile(m_navMesh->getTileRefAt(x,y,0),0,0); m_traverseLinkPolyMap.clear(); - buildStaticPathingData(); + createStaticPathingData(); } void Editor_TileMesh::buildAllHulls() diff --git a/src/naveditor/NavMeshPruneTool.cpp b/src/naveditor/NavMeshPruneTool.cpp index 951a6cef..ea275d12 100644 --- a/src/naveditor/NavMeshPruneTool.cpp +++ b/src/naveditor/NavMeshPruneTool.cpp @@ -120,7 +120,7 @@ public: }; -static void floodNavmesh(dtNavMesh* nav, NavmeshFlags* flags, dtPolyRef start, unsigned char flag) +static void floodNavMesh(dtNavMesh* nav, NavmeshFlags* flags, dtPolyRef start, unsigned char flag) { // If already visited, skip. if (flags->getFlags(start)) @@ -203,8 +203,7 @@ void NavMeshPruneTool::pruneUnvisitedTilesAndPolys(dtNavMesh* nav, NavmeshFlags* NavMeshPruneTool::NavMeshPruneTool() : m_editor(0), m_flags(0), - m_hitPosSet(false), - m_ranPruneTool(false) + m_hitPosSet(false) { m_hitPos[0] = 0.0f; m_hitPos[1] = 0.0f; @@ -226,7 +225,6 @@ void NavMeshPruneTool::init(Editor* editor) void NavMeshPruneTool::reset() { m_hitPosSet = false; - m_ranPruneTool = false; if (m_flags) { @@ -240,25 +238,23 @@ void NavMeshPruneTool::handleMenu() dtNavMesh* nav = m_editor->getNavMesh(); if (!nav) return; - if (!m_flags) return; + if (!m_hitPosSet) return; if (ImGui::Button("Clear Selection")) { m_flags->clearAllFlags(); + m_hitPosSet = false; } if (ImGui::Button("Prune Unselected")) { pruneUnvisitedTilesAndPolys(nav, m_flags); - dtTraverseTableCreateParams params; - - m_editor->createTraverseTableParams(¶ms); - m_editor->updateStaticPathingData(¶ms); + m_editor->createStaticPathingData(); delete m_flags; m_flags = 0; - m_ranPruneTool = true; + m_hitPosSet = false; } } @@ -290,7 +286,7 @@ void NavMeshPruneTool::handleClick(const float* s, const float* p, const int /*v m_flags->init(nav); } - floodNavmesh(nav, m_flags, ref, 1); + floodNavMesh(nav, m_flags, ref, 1); } } diff --git a/src/naveditor/include/Editor.h b/src/naveditor/include/Editor.h index c7a02cc5..8d76b59b 100644 --- a/src/naveditor/include/Editor.h +++ b/src/naveditor/include/Editor.h @@ -359,14 +359,11 @@ public: void handleCommonSettings(); void createTraverseLinkParams(dtTraverseLinkConnectParams& params); - void createTraverseTableParams(dtTraverseTableCreateParams* params); bool createTraverseLinks(); void connectOffMeshLinks(); - void buildStaticPathingData(); - bool createStaticPathingData(const dtTraverseTableCreateParams* params); - bool updateStaticPathingData(const dtTraverseTableCreateParams* params); + bool createStaticPathingData(); private: // Explicitly disabled copy constructor and copy assignment operator. diff --git a/src/naveditor/include/NavMeshPruneTool.h b/src/naveditor/include/NavMeshPruneTool.h index 1dc88cbd..d618a27f 100644 --- a/src/naveditor/include/NavMeshPruneTool.h +++ b/src/naveditor/include/NavMeshPruneTool.h @@ -31,7 +31,6 @@ class NavMeshPruneTool : public EditorTool float m_hitPos[3]; bool m_hitPosSet; - bool m_ranPruneTool; public: NavMeshPruneTool(); diff --git a/src/thirdparty/recast/Detour/Include/DetourNavMesh.h b/src/thirdparty/recast/Detour/Include/DetourNavMesh.h index b45c73cf..5a804561 100644 --- a/src/thirdparty/recast/Detour/Include/DetourNavMesh.h +++ b/src/thirdparty/recast/Detour/Include/DetourNavMesh.h @@ -76,21 +76,23 @@ static const int DT_SEMI_UNLINKED_TILE_USER_ID = 2; /// A value that indicates that this poly hasn't been assigned to a group yet. static const unsigned short DT_NULL_POLY_GROUP = 0; -/// A poly group that holds all unconnected polys (not linked to anything). -/// These are considered 'trash' by the game engine; see [r5apex_ds + CA88B2]. +/// A polygon group that holds all unconnected polys (not linked to anything). +/// These are considered 'trash' by the game engine; see [r5apex_ds + CA88B2]. /// For reference, Titanfall 2 single player NavMeshes also marked everything unconnected as '1'. static const unsigned short DT_UNLINKED_POLY_GROUP = 1; /// The first non-reserved poly group; #DT_UNLINKED_POLY_GROUP and below are reserved. static const unsigned short DT_FIRST_USABLE_POLY_GROUP = 2; -/// The minimum required number of poly groups for static pathing logic to work. -/// (E.g. if we have 2 poly groups, group id 1 (#DT_UNLINKED_POLY_GROUP), and -/// group id 2, then 1 is never reachable as its considered 'trash' by design, -/// and 2 is always reachable as that's the only group id. If group id 3 is -/// involved then code can use the static patching logic to quickly query if we -/// are even on the same (or connected) poly island before trying to compute a path). -static const int DT_MIN_POLY_GROUP_COUNT = 3; +/// The minimum number of polygon groups (islands) before an actual table has +/// to be built and allocated. Static pathing can work without a table if we +/// only have 2 polygon groups, as all linked polygons are tagged under the +/// group #DT_FIRST_USABLE_POLY_GROUP, while the dead polygons are tagged under +/// the group #DT_UNLINKED_POLY_GROUP. +static const unsigned short DT_MIN_POLY_GROUP_COUNT = 3; + +/// The maximum number of polygon groups (islands) per navmesh. +static const unsigned short DT_MAX_POLY_GROUP_COUNT = 65535; /// The maximum number of traversal tables per navmesh that will be used for static pathing. static const int DT_MAX_TRAVERSE_TABLES = 5; diff --git a/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h b/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h index 649e9f4c..b3bf050d 100644 --- a/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h +++ b/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h @@ -119,9 +119,9 @@ class dtDisjointSet { public: dtDisjointSet() = default; - dtDisjointSet(const int size) + dtDisjointSet(const int size, const int max = -1) { - init(size); + init(size, max); } void copy(dtDisjointSet& other) @@ -137,19 +137,25 @@ public: other.parent[i] = parent[i]; } - void init(const int size) + void init(const int size, const int max = -1) { + rdAssert(size <= max); + rank.resize(size); parent.resize(size); + limit = max; for (int i = 0; i < parent.size(); i++) parent[i] = i; } int insertNew() { - rank.push(0); - const int newId = parent.size(); + + if (limit > 0 && newId > limit) + return -1; // Limit has been reached. + + rank.push(0); parent.push(newId); return newId; @@ -190,6 +196,7 @@ public: private: rdIntArray rank; mutable rdIntArray parent; + int limit; }; struct dtLink; @@ -218,12 +225,6 @@ struct dtTraverseTableCreateParams /// @return True if the disjoint set data was successfully created. bool dtCreateDisjointPolyGroups(const dtTraverseTableCreateParams* params); -/// Updates navigation mesh disjoint poly groups from the provided parameters. -/// @ingroup detour -/// @param[in] params The build parameters. -/// @return True if the disjoint set data was successfully updated. -bool dtUpdateDisjointPolyGroups(const dtTraverseTableCreateParams* params); - /// Builds navigation mesh static traverse table from the provided parameters. /// @ingroup detour /// @param[in] params The build parameters. diff --git a/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp b/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp index 196e8c74..2a572450 100644 --- a/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp +++ b/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp @@ -322,150 +322,27 @@ static void setPolyGroupsTraversalReachability(int* const tableData, const int n tableData[index] &= ~value; } -bool dtCreateDisjointPolyGroups(const dtTraverseTableCreateParams* params) -{ - dtNavMesh* nav = params->nav; - dtDisjointSet& set = params->sets[0]; - - rdAssert(nav); - - // Reserve the first poly groups - // 0 = DT_NULL_POLY_GROUP. - // 1 = DT_UNLINKED_POLY_GROUP. - set.init(DT_FIRST_USABLE_POLY_GROUP); - - // Clear all labels. - for (int i = 0; i < nav->getMaxTiles(); ++i) - { - dtMeshTile* tile = nav->getTile(i); - if (!tile || !tile->header || !tile->dataSize) continue; - int pcount = tile->header->polyCount; - for (int j = 0; j < pcount; j++) - { - dtPoly& poly = tile->polys[j]; - - poly.groupId = DT_NULL_POLY_GROUP; -#if DT_NAVMESH_SET_VERSION >= 7 - // NOTE: these fields are unknown and need to be reversed. - // It is possible these are used internally only. - poly.unk1 = (unsigned short)-1; - poly.unk2 = (unsigned short)-1; -#endif - } - } - - // First pass to group poly islands. - std::set linkedGroups; - for (int i = 0; i < nav->getMaxTiles(); ++i) - { - dtMeshTile* tile = nav->getTile(i); - if (!tile || !tile->header || !tile->dataSize) continue; - const int pcount = tile->header->polyCount; - for (int j = 0; j < pcount; j++) - { - dtPoly& poly = tile->polys[j]; - unsigned int plink = poly.firstLink; - - if (params->collapseGroups) - { - if (plink != DT_NULL_LINK) - poly.groupId = DT_FIRST_USABLE_POLY_GROUP; - - continue; - } - - // Off-mesh connections need their own ID's, skip the assignment - // here since else we will be marking 2 (or more) poly islands - // under the same group id. - // NOTE: when we implement jump links, we will have to check on - // these here as well! They also shouldn't merge 2 islands together. - // Ultimately, the jump links should only be used during traverse - // table building to mark linked islands as reachable. - if (poly.getType() != DT_POLYTYPE_OFFMESH_CONNECTION) - { - while (plink != DT_NULL_LINK) - { - const dtLink l = tile->links[plink]; - - // Polygons linked with traverse links are not necessarily on - // the same group, these should be skipped. - if (l.traverseType != DT_NULL_TRAVERSE_TYPE) - { - plink = l.next; - continue; - } - - const dtMeshTile* t; - const dtPoly* p; - nav->getTileAndPolyByRefUnsafe(l.ref, &t, &p); - - if (p->groupId != DT_NULL_POLY_GROUP) - linkedGroups.insert(p->groupId); - - plink = l.next; - } - } - - const bool noLinkedGroups = linkedGroups.empty(); - - if (noLinkedGroups) - poly.groupId = (unsigned short)set.insertNew(); - else - { - const unsigned short rootGroup = *linkedGroups.begin(); - poly.groupId = rootGroup; - - for (const int linkedGroup : linkedGroups) - set.setUnion(rootGroup, linkedGroup); - } - - if (!noLinkedGroups) - linkedGroups.clear(); - } - } - - // Second pass to ensure all poly's have their root disjoint set ID. - for (int i = 0; i < nav->getMaxTiles(); ++i) - { - dtMeshTile* tile = nav->getTile(i); - if (!tile || !tile->header || !tile->dataSize) continue; - const int pcount = tile->header->polyCount; - for (int j = 0; j < pcount; j++) - { - dtPoly& poly = tile->polys[j]; - if (poly.groupId != DT_UNLINKED_POLY_GROUP) - { - const int id = params->collapseGroups - ? DT_FIRST_USABLE_POLY_GROUP - : set.find(poly.groupId); - - poly.groupId = (unsigned short)id; - } - } - } - - nav->setPolyGroupCount(set.getSetCount()); - return true; -} - static void unionTraverseLinkedPolyGroups(const dtTraverseTableCreateParams* params, const int tableIndex) { - if (params->collapseGroups) - return; - + rdAssert(!params->collapseGroups); dtDisjointSet& set = params->sets[tableIndex]; if (!set.getSetCount()) return; dtNavMesh* nav = params->nav; + const int maxTiles = nav->getMaxTiles(); - // Sixth pass to handle traverse linked poly's. - for (int i = 0; i < nav->getMaxTiles(); ++i) + // Handle traverse linked poly's. + for (int i = 0; i < maxTiles; ++i) { dtMeshTile* tile = nav->getTile(i); - if (!tile || !tile->header || !tile->dataSize) continue; - const int pcount = tile->header->polyCount; + const dtMeshHeader* header = tile->header; + + if (!header) + continue; + + const int pcount = header->polyCount; for (int j = 0; j < pcount; j++) { dtPoly& poly = tile->polys[j]; @@ -487,77 +364,77 @@ static void unionTraverseLinkedPolyGroups(const dtTraverseTableCreateParams* par } } -bool dtUpdateDisjointPolyGroups(const dtTraverseTableCreateParams* params) +static bool floodPolygonIsland(dtNavMesh* nav, dtDisjointSet& set, const dtPolyRef startRef) +{ + std::set visitedPolys; + rdPermVector openList; + + openList.push_back(startRef); + + while (!openList.empty()) + { + dtPolyRef polyRef = openList.back(); + openList.pop_back(); + + // Skip already visited polygons. + if (visitedPolys.find(polyRef) != visitedPolys.end()) + continue; + + visitedPolys.insert(polyRef); + unsigned int salt, it, ip; + + nav->decodePolyId(polyRef, salt, it, ip); + + dtMeshTile* currentTile = nav->getTile(it); + dtPoly* poly = ¤tTile->polys[ip]; + + if (poly->groupId == DT_NULL_POLY_GROUP) + { + const int newGroup = set.insertNew(); + + // Overflow, too many polygon islands. + if (newGroup == -1) + return false; + + poly->groupId = (unsigned short)newGroup; + } + + for (unsigned int i = poly->firstLink; i != DT_NULL_LINK; i = currentTile->links[i].next) + { + const dtLink& link = currentTile->links[i]; + + // Skip traverse links as these can join separate islands together. + if (link.traverseType != DT_NULL_TRAVERSE_TYPE) + continue; + + const dtPolyRef neiRef = link.ref; + + if (visitedPolys.find(neiRef) == visitedPolys.end()) + { + nav->decodePolyId(neiRef, salt, it, ip); + + dtMeshTile* neiTile = nav->getTile(it); + dtPoly* neiPoly = &neiTile->polys[ip]; + + if (neiPoly->groupId != DT_NULL_POLY_GROUP) + continue; + + if (neiPoly->getType() != DT_POLYTYPE_OFFMESH_CONNECTION) + { + neiPoly->groupId = poly->groupId; + openList.push_back(neiRef); + } + } + } + } + + return true; +} + +static void copyBaseDisjointSets(const dtTraverseTableCreateParams* params) { - dtNavMesh* nav = params->nav; dtDisjointSet& set = params->sets[0]; - // Third pass to mark all unlinked poly's. - for (int i = 0; i < nav->getMaxTiles(); ++i) - { - dtMeshTile* tile = nav->getTile(i); - if (!tile || !tile->header || !tile->dataSize) continue; - const int pcount = tile->header->polyCount; - int numUnlinkedPolys = 0; - for (int j = 0; j < pcount; j++) - { - dtPoly& poly = tile->polys[j]; - - // This poly isn't connected to anything, mark it so the game - // won't consider this poly in path generation. - if (poly.firstLink == DT_NULL_LINK) - { - poly.groupId = DT_UNLINKED_POLY_GROUP; - numUnlinkedPolys++; - } - } - - if (numUnlinkedPolys) - { - tile->header->userId = (numUnlinkedPolys == tile->header->polyCount) - ? DT_FULL_UNLINKED_TILE_USER_ID - : DT_SEMI_UNLINKED_TILE_USER_ID; - } - else if (tile->header->userId == DT_FULL_UNLINKED_TILE_USER_ID - || tile->header->userId == DT_SEMI_UNLINKED_TILE_USER_ID) - tile->header->userId = 0; - } - - if (!params->collapseGroups) - { - // Gather all unique polygroups and map them to a contiguous range. - std::map groupMap; - set.init(DT_FIRST_USABLE_POLY_GROUP); - - for (int i = 0; i < nav->getMaxTiles(); ++i) - { - dtMeshTile* tile = nav->getTile(i); - if (!tile || !tile->header || !tile->dataSize) continue; - const int pcount = tile->header->polyCount; - for (int j = 0; j < pcount; j++) - { - dtPoly& poly = tile->polys[j]; - unsigned short oldId = poly.groupId; - if (oldId != DT_UNLINKED_POLY_GROUP && groupMap.find(oldId) == groupMap.end()) - groupMap[oldId] = (unsigned short)set.insertNew(); - } - } - - // Fourth pass to apply the new mapping to all polys. - for (int i = 0; i < nav->getMaxTiles(); ++i) - { - dtMeshTile* tile = nav->getTile(i); - if (!tile || !tile->header || !tile->dataSize) continue; - const int pcount = tile->header->polyCount; - for (int j = 0; j < pcount; j++) - { - dtPoly& poly = tile->polys[j]; - if (poly.groupId != DT_UNLINKED_POLY_GROUP) - poly.groupId = groupMap[poly.groupId]; - } - } - } - // Copy base disjoint set results to sets for each traverse table. for (int i = 0; i < params->tableCount; i++) { @@ -568,53 +445,162 @@ bool dtUpdateDisjointPolyGroups(const dtTraverseTableCreateParams* params) unionTraverseLinkedPolyGroups(params, i); } +} - nav->setPolyGroupCount(set.getSetCount()); - return true; +bool dtCreateDisjointPolyGroups(const dtTraverseTableCreateParams* params) +{ + dtNavMesh* nav = params->nav; + dtDisjointSet& set = params->sets[0]; + + rdAssert(nav); + + // Reserve the first poly groups + // 0 = DT_NULL_POLY_GROUP. + // 1 = DT_UNLINKED_POLY_GROUP. + set.init(DT_FIRST_USABLE_POLY_GROUP, DT_MAX_POLY_GROUP_COUNT); + + const int maxTiles = nav->getMaxTiles(); + + // Clear all labels. + for (int i = 0; i < maxTiles; ++i) + { + dtMeshTile* tile = nav->getTile(i); + const dtMeshHeader* header = tile->header; + if (!header) + continue; + + const int pcount = header->polyCount; + for (int j = 0; j < pcount; j++) + { + dtPoly& poly = tile->polys[j]; + + poly.groupId = DT_NULL_POLY_GROUP; +#if DT_NAVMESH_SET_VERSION >= 7 + // NOTE: these fields are unknown and need to be reversed. + // It is possible these are used internally only. + poly.unk1 = (unsigned short)-1; + poly.unk2 = (unsigned short)-1; +#endif + } + } + + // True if we have more than DT_MAX_POLY_GROUPS polygon islands. + bool failure = false; + + // Mark polygon islands and unlinked polygons. + for (int i = 0; i < maxTiles; ++i) + { + dtMeshTile* tile = nav->getTile(i); + const dtMeshHeader* header = tile->header; + + if (!header) + continue; + + const int pcount = header->polyCount; + for (int j = 0; j < pcount; ++j) + { + dtPoly& poly = tile->polys[j]; + + // Skip if the polygon is already part of an island. + if (poly.groupId != DT_NULL_POLY_GROUP) + continue; + + if (poly.firstLink == DT_NULL_LINK) + { + poly.groupId = DT_UNLINKED_POLY_GROUP; + continue; + } + + if (failure) + continue; + + if (params->collapseGroups) + { + poly.groupId = DT_FIRST_USABLE_POLY_GROUP; + continue; + } + + // Off-mesh connections need their own ID's, skip the assignment + // here since else we will be marking 2 (or more) poly islands + // under the same group id. + if (poly.getType() == DT_POLYTYPE_OFFMESH_CONNECTION) + { + const int newId = set.insertNew(); + + if (newId == -1) + { + failure = true; + continue; + } + + poly.groupId = (unsigned short)newId; + continue; + } + + const dtPolyRef polyRefBase = nav->getPolyRefBase(tile); + + if (!floodPolygonIsland(nav, set, polyRefBase | j)) + failure = true; + } + } + + return !failure; } bool dtCreateTraverseTableData(const dtTraverseTableCreateParams* params) { + const dtDisjointSet& baseSet = params->sets[0]; + const int polyGroupCount = baseSet.getSetCount(); + dtNavMesh* nav = params->nav; - const int polyGroupCount = nav->getPolyGroupCount(); - const int tableSize = dtCalcTraverseTableSize(polyGroupCount); - const int tableCount = params->tableCount; + if (polyGroupCount < DT_MIN_POLY_GROUP_COUNT) + { + nav->setTraverseTableCount(0); + nav->setTraverseTableSize(0); + nav->setPolyGroupCount(polyGroupCount); + + return true; + } nav->freeTraverseTables(); + const int tableCount = params->tableCount; if (!nav->allocTraverseTables(tableCount)) return false; - nav->setTraverseTableSize(tableSize); nav->setTraverseTableCount(tableCount); + copyBaseDisjointSets(params); - if (!tableSize) - return true; + const int tableSize = dtCalcTraverseTableSize(polyGroupCount); + nav->setTraverseTableSize(tableSize); for (int i = 0; i < tableCount; i++) { - int* const traverseTable = (int*)rdAlloc(sizeof(int)*tableSize, RD_ALLOC_PERM); + const rdSizeType bufferSize = sizeof(int)*tableSize; + int* const traverseTable = (int*)rdAlloc(bufferSize, RD_ALLOC_PERM); if (!traverseTable) return false; + memset(traverseTable, 0, bufferSize); nav->setTraverseTable(i, traverseTable); - memset(traverseTable, 0, sizeof(int)*tableSize); const dtDisjointSet& set = params->sets[i]; + rdAssert(set.getSetCount() >= DT_MIN_POLY_GROUP_COUNT); for (unsigned short j = 0; j < polyGroupCount; j++) { for (unsigned short k = 0; k < polyGroupCount; k++) { - // Only reachable if its the same polygroup or if they are linked! + // Only reachable if its the same poly group or if they are linked! const bool isReachable = j == k || set.find(j) == set.find(k); setPolyGroupsTraversalReachability(traverseTable, polyGroupCount, j, k, isReachable); } } } + nav->setPolyGroupCount(baseSet.getSetCount()); return true; }