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.
This commit is contained in:
Kawe Mazidjatari 2024-10-16 19:55:37 +02:00
parent d027621028
commit a8b302f165
8 changed files with 260 additions and 298 deletions

View File

@ -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(&params);
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(&params);
updateStaticPathingData(&params);
if (!dtCreateDisjointPolyGroups(&params))
{
m_ctx->log(RC_LOG_ERROR, "createStaticPathingData: Failed to build disjoint poly groups.");
return false;
}
if (!dtCreateTraverseTableData(&params))
{
m_ctx->log(RC_LOG_ERROR, "createStaticPathingData: Failed to build traverse table data.");
return false;
}
return true;
}
void Editor::connectOffMeshLinks()

View File

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

View File

@ -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(&params);
m_editor->updateStaticPathingData(&params);
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);
}
}

View File

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

View File

@ -31,7 +31,6 @@ class NavMeshPruneTool : public EditorTool
float m_hitPos[3];
bool m_hitPosSet;
bool m_ranPruneTool;
public:
NavMeshPruneTool();

View File

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

View File

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

View File

@ -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<unsigned short> 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<dtPolyRef> visitedPolys;
rdPermVector<dtPolyRef> 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 = &currentTile->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<unsigned short, unsigned short> 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;
}