Recast: implement per-traverse anim type disjoint set linking

Previously we handles each table the same. The updated algorithm now has a disjoint set for each traverse anim type, and selectively determines which poly's are reachable based on the link's traverse type connecting them. All left to be done here is making a correct lookup table, which is currently defined as s_traverseAnimTraverseFlags.
This commit is contained in:
Kawe Mazidjatari 2024-08-15 14:47:05 +02:00
parent 91d36cf007
commit c0ff0e6be9
6 changed files with 202 additions and 100 deletions

View File

@ -837,11 +837,11 @@ bool Editor::createTraverseLinks()
return true;
}
bool Editor::createStaticPathingData()
bool Editor::createStaticPathingData(const dtTraverseTableCreateParams* params)
{
if (!m_navMesh) return false;
if (!params->nav) return false;
if (!dtCreateDisjointPolyGroups(m_navMesh, m_djs))
if (!dtCreateDisjointPolyGroups(params))
{
m_ctx->log(RC_LOG_ERROR, "createStaticPathingData: Failed to build disjoint poly groups.");
return false;
@ -856,17 +856,19 @@ bool Editor::createStaticPathingData()
return true;
}
bool Editor::updateStaticPathingData()
bool Editor::updateStaticPathingData(const dtTraverseTableCreateParams* params)
{
if (!m_navMesh) return false;
if (!params->nav) return false;
if (!dtUpdateDisjointPolyGroups(m_navMesh, m_djs))
const int numTraverseTables = NavMesh_GetTraverseTableCountForNavMeshType(m_selectedNavMeshType);
if (!dtUpdateDisjointPolyGroups(params))
{
m_ctx->log(RC_LOG_ERROR, "updateStaticPathingData: Failed to update disjoint poly groups.");
return false;
}
if (!dtCreateTraverseTableData(m_navMesh, m_djs, NavMesh_GetTraverseTableCountForNavMeshType(m_selectedNavMeshType)))
if (!dtCreateTraverseTableData(params))
{
m_ctx->log(RC_LOG_ERROR, "updateStaticPathingData: Failed to build traverse table data.");
return false;
@ -875,10 +877,52 @@ bool Editor::updateStaticPathingData()
return true;
}
// TODO: this lookup table isn't correct, needs to be fixed.
static const int s_traverseAnimTraverseFlags[TraverseAnimType_e::ANIMTYPE_COUNT] = {
0x0000013F, // ANIMTYPE_HUMAN
0x0000013F, // ANIMTYPE_SPECTRE
0x00000000, // ANIMTYPE_STALKER
0x0033FFFF, // ANIMTYPE_FRAG_DRONE
0x00000000, // ANIMTYPE_PILOT
0x00000000, // ANIMTYPE_PROWLER
0x00000000, // ANIMTYPE_SUPER_SPECTRE
0x00000000, // ANIMTYPE_TITAN
0x00000000, // ANIMTYPE_GOLIATH
};
bool animTypeSupportsTraverseLink(const dtTraverseTableCreateParams* params, const dtLink* link, const int tableIndex)
{
// TODO: always link off-mesh connected polygon islands together?
// Research needed.
if (link->reverseLink == DT_NULL_TRAVERSE_REVERSE_LINK)
return true;
const NavMeshType_e navMeshType = (NavMeshType_e)params->navMeshType;
// Only the _small NavMesh has more than 1 table.
const int traverseAnimType = navMeshType == NAVMESH_SMALL
? tableIndex
: NavMesh_GetFirstTraverseAnimTypeForType(navMeshType);
return rdBitCellBit(link->traverseType) & s_traverseAnimTraverseFlags[traverseAnimType];
}
void Editor::createTraverseTableParams(dtTraverseTableCreateParams* params)
{
params->nav = m_navMesh;
params->sets = m_djs;
params->tableCount = NavMesh_GetTraverseTableCountForNavMeshType(m_selectedNavMeshType);
params->navMeshType = m_selectedNavMeshType;
params->canTraverse = animTypeSupportsTraverseLink;
}
void Editor::buildStaticPathingData()
{
createStaticPathingData();
updateStaticPathingData();
dtTraverseTableCreateParams params;
createTraverseTableParams(&params);
createStaticPathingData(&params);
updateStaticPathingData(&params);
}
void Editor::updateToolStates(const float dt)

View File

@ -530,17 +530,17 @@ bool Editor_SoloMesh::handleBuild()
}
}
dtDisjointSet data;
//dtDisjointSet data;
if (!dtCreateDisjointPolyGroups(m_navMesh, data))
{
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Failed to build disjoint poly groups.");
}
//if (!dtCreateDisjointPolyGroups(m_navMesh, data))
//{
// m_ctx->log(RC_LOG_ERROR, "buildNavigation: Failed to build disjoint poly groups.");
//}
if (!dtCreateTraverseTableData(m_navMesh, data, traverseTableCount))
{
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Failed to build traversal table data.");
}
//if (!dtCreateTraverseTableData(m_navMesh, data, traverseTableCount))
//{
// m_ctx->log(RC_LOG_ERROR, "buildNavigation: Failed to build traversal table data.");
//}
m_ctx->stopTimer(RC_TIMER_TOTAL);

View File

@ -222,7 +222,11 @@ void NavMeshPruneTool::handleMenu()
if (ImGui::Button("Prune Unselected"))
{
disableUnvisitedPolys(nav, m_flags);
m_editor->updateStaticPathingData();
dtTraverseTableCreateParams params;
m_editor->createTraverseTableParams(&params);
m_editor->updateStaticPathingData(&params);
delete m_flags;
m_flags = 0;
}

View File

@ -173,7 +173,7 @@ protected:
EditorToolState* m_toolStates[MAX_TOOLS];
BuildContext* m_ctx;
dtDisjointSet m_djs;
dtDisjointSet m_djs[DT_MAX_TRAVERSE_TABLES];
EditorDebugDraw m_dd;
unsigned int m_navMeshDrawFlags;
@ -250,10 +250,13 @@ public:
void connectTileTraverseLinks(dtMeshTile* const baseTile, const bool linkToNeighbor); // Make private.
bool createTraverseLinks();
void createTraverseTableParams(dtTraverseTableCreateParams* params);
void buildStaticPathingData();
bool createStaticPathingData();
bool updateStaticPathingData();
bool createStaticPathingData(const dtTraverseTableCreateParams* params);
bool updateStaticPathingData(const dtTraverseTableCreateParams* params);
private:
// Explicitly disabled copy constructor and copy assignment operator.

View File

@ -120,6 +120,19 @@ public:
init(size);
}
void copy(dtDisjointSet& other)
{
other.rank.resize(rank.size());
for (int i = 0; i < other.rank.size(); i++)
other.rank[i] = rank[i];
other.parent.resize(parent.size());
for (int i = 0; i < other.parent.size(); i++)
other.parent[i] = parent[i];
}
void init(const int size)
{
rank.resize(size);
@ -175,26 +188,39 @@ private:
mutable rdIntArray parent;
};
/// Builds navigation mesh disjoint poly groups from the provided navmesh.
struct dtLink;
/// Parameters used to build traverse links.
/// @ingroup detour
/// @param[in] nav The navigation mesh to use.
/// @param[Out] disjoint The disjoint set data.
struct dtTraverseTableCreateParams
{
dtNavMesh* nav; ///< The navmesh.
dtDisjointSet* sets; ///< The disjoint polygroup sets.
int tableCount; ///< The number of traverse tables this navmesh should contain.
int navMeshType; ///< The navmesh type [_small, _extra_large].
///< The user installed callback which is used to determine if an animType
/// an use this traverse link.
bool (*canTraverse)(const dtTraverseTableCreateParams* params, const dtLink* link, const int tableIndex);
};
/// Builds 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 created.
bool dtCreateDisjointPolyGroups(dtNavMesh* nav, dtDisjointSet& disjoint);
bool dtCreateDisjointPolyGroups(const dtTraverseTableCreateParams* params);
/// Updates navigation mesh disjoint poly groups from the provided navmesh.
/// Updates navigation mesh disjoint poly groups from the provided parameters.
/// @ingroup detour
/// @param[in] nav The navigation mesh to use.
/// @param[Out] disjoint The disjoint set data.
/// @param[in] params The build parameters.
/// @return True if the disjoint set data was successfully updated.
bool dtUpdateDisjointPolyGroups(dtNavMesh* nav, dtDisjointSet& disjoint);
bool dtUpdateDisjointPolyGroups(const dtTraverseTableCreateParams* params);
/// Builds navigation mesh static traverse table from the provided navmesh.
/// Builds navigation mesh static traverse table from the provided parameters.
/// @ingroup detour
/// @param[in] nav The navigation mesh to use.
/// @param[in] disjoint The disjoint set data.
/// @param[in] params The build parameters.
/// @return True if the static traverse table was successfully created.
bool dtCreateTraverseTableData(dtNavMesh* nav, const dtDisjointSet& disjoint, const int tableCount);
bool dtCreateTraverseTableData(const dtTraverseTableCreateParams* params);
/// Builds navigation mesh tile data from the provided tile creation data.
/// @ingroup detour

View File

@ -248,14 +248,17 @@ static void setPolyGroupsTraversalReachability(int* const tableData, const int n
tableData[index] &= ~value;
}
bool dtCreateDisjointPolyGroups(dtNavMesh* nav, dtDisjointSet& disjoint)
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.
disjoint.init(DT_FIRST_USABLE_POLY_GROUP);
set.init(DT_FIRST_USABLE_POLY_GROUP);
// Clear all labels.
for (int i = 0; i < nav->getMaxTiles(); ++i)
@ -317,14 +320,14 @@ bool dtCreateDisjointPolyGroups(dtNavMesh* nav, dtDisjointSet& disjoint)
const bool noLinkedGroups = linkedGroups.empty();
if (noLinkedGroups)
poly.groupId = (unsigned short)disjoint.insertNew();
poly.groupId = (unsigned short)set.insertNew();
else
{
const unsigned short rootGroup = *linkedGroups.begin();
poly.groupId = rootGroup;
for (const int linkedGroup : linkedGroups)
disjoint.setUnion(rootGroup, linkedGroup);
set.setUnion(rootGroup, linkedGroup);
}
if (!noLinkedGroups)
@ -343,66 +346,20 @@ bool dtCreateDisjointPolyGroups(dtNavMesh* nav, dtDisjointSet& disjoint)
dtPoly& poly = tile->polys[j];
if (poly.groupId != DT_UNLINKED_POLY_GROUP)
{
const int id = disjoint.find(poly.groupId);
const int id = set.find(poly.groupId);
poly.groupId = (unsigned short)id;
}
}
}
nav->setPolyGroupcount(disjoint.getSetCount());
nav->setPolyGroupcount(set.getSetCount());
return true;
}
bool dtUpdateDisjointPolyGroups(dtNavMesh* nav, dtDisjointSet& disjoint)
static void unionTraverseLinkedPolyGroups(const dtTraverseTableCreateParams* params, const int tableIndex)
{
// 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;
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;
}
}
// Gather all unique polygroups and map them to a contiguous range.
std::map<unsigned short, unsigned short> groupMap;
disjoint.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)disjoint.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];
}
}
dtNavMesh* nav = params->nav;
dtDisjointSet& set = params->sets[tableIndex];
// Fifth pass to handle off-mesh connections.
// note(amos): this has to happen after the first and second pass as these
@ -431,7 +388,7 @@ bool dtUpdateDisjointPolyGroups(dtNavMesh* nav, dtDisjointSet& disjoint)
while (plink != DT_NULL_LINK)
{
const dtLink l = tile->links[plink];
const dtLink& l = tile->links[plink];
const dtMeshTile* t;
const dtPoly* p;
nav->getTileAndPolyByRefUnsafe(l.ref, &t, &p);
@ -440,8 +397,8 @@ bool dtUpdateDisjointPolyGroups(dtNavMesh* nav, dtDisjointSet& disjoint)
{
if (firstGroupId == DT_NULL_POLY_GROUP)
firstGroupId = p->groupId;
else
disjoint.setUnion(firstGroupId, p->groupId);
else if (params->canTraverse(params, &l, tableIndex))
set.setUnion(firstGroupId, p->groupId);
}
plink = l.next;
@ -486,21 +443,89 @@ bool dtUpdateDisjointPolyGroups(dtNavMesh* nav, dtDisjointSet& disjoint)
rdAssert(landPoly->getType() != DT_POLYTYPE_OFFMESH_CONNECTION);
rdAssert(landPoly->groupId != DT_UNLINKED_POLY_GROUP);
if (poly.groupId != landPoly->groupId)
disjoint.setUnion(poly.groupId, landPoly->groupId);
if (poly.groupId != landPoly->groupId && params->canTraverse(params, link, tableIndex))
set.setUnion(poly.groupId, landPoly->groupId);
}
}
}
}
nav->setPolyGroupcount(disjoint.getSetCount());
bool dtUpdateDisjointPolyGroups(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;
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;
}
}
// 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++)
{
dtDisjointSet& targetSet = params->sets[i];
if (i > 0) // Don't copy the base into itself.
set.copy(targetSet);
unionTraverseLinkedPolyGroups(params, i);
}
nav->setPolyGroupcount(set.getSetCount());
return true;
}
// todo(amos): remove param 'tableCount' and make struct 'dtTraverseTableCreateParams'
bool dtCreateTraverseTableData(dtNavMesh* nav, const dtDisjointSet& disjoint, const int tableCount)
bool dtCreateTraverseTableData(const dtTraverseTableCreateParams* params)
{
dtNavMesh* nav = params->nav;
const int polyGroupCount = nav->getPolyGroupCount();
const int tableSize = dtCalcTraverseTableSize(polyGroupCount);
const int tableCount = params->tableCount;
nav->freeTraverseTables();
@ -510,8 +535,6 @@ bool dtCreateTraverseTableData(dtNavMesh* nav, const dtDisjointSet& disjoint, co
nav->setTraverseTableSize(tableSize);
nav->setTraverseTableCount(tableCount);
// TODO: figure out which jump type belongs to which traverse anim type
// and determine reachability from here...
for (int i = 0; i < tableCount; i++)
{
int* const traverseTable = (int*)rdAlloc(sizeof(int)*tableSize, RD_ALLOC_PERM);
@ -522,12 +545,14 @@ bool dtCreateTraverseTableData(dtNavMesh* nav, const dtDisjointSet& disjoint, co
nav->setTraverseTable(i, traverseTable);
memset(traverseTable, 0, sizeof(int)*tableSize);
const dtDisjointSet& set = params->sets[i];
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!
const bool isReachable = j == k || disjoint.find(j) == disjoint.find(k);
const bool isReachable = j == k || set.find(j) == set.find(k);
setPolyGroupsTraversalReachability(traverseTable, polyGroupCount, j, k, isReachable);
}
}