Recast: support traverse links connecting to external tiles + major performance improvement

Now connect to neighbor tiles (still work in progress). Also moved poly overlap and raycast logic to separate functions. The raycast checks are now performed as a very last step as previously we would raycast, and then determine we can't establish the link because the traverse type was NULL or the distance was too great, which both could be checked before the raycast and are also a lot cheaper to check. Changing the order of checks significantly improved the performance.

Also fixed a bug in dtCalcLinkDistance, if the number exceeds 2550.f, it would overflow and cause links to be a lot larger. If the value exceeds the number now, it would return 0 and no link will be established.
This commit is contained in:
Kawe Mazidjatari 2024-08-12 11:09:48 +02:00
parent 4d2210d5e9
commit a99984b0f3
4 changed files with 135 additions and 99 deletions

View File

@ -510,15 +510,62 @@ bool CanOverlapPoly(const TraverseType_e traverseType)
return s_traverseTypes[traverseType].minSlope > 5.0f; return s_traverseTypes[traverseType].minSlope > 5.0f;
} }
static bool traverseLinkInPolygon(const dtMeshTile* tile, const float* midPoint)
{
const int polyCount = tile->header->polyCount;
for (int i = 0; i < polyCount; i++)
{
const dtPoly* poly = &tile->polys[i];
float verts[DT_VERTS_PER_POLYGON*3];
const int nverts = poly->vertCount;
for (int j = 0; j < nverts; ++j)
rdVcopy(&verts[j*3], &tile->verts[poly->verts[j]*3]);
if (rdPointInPolygon(midPoint, verts, nverts))
return true;
}
return false;
}
static bool traverseLinkInLOS(InputGeom* geom, const float* startPos, const float* endPoint, const float heightOffset)
{
float rayStartPos[3];
float rayEndPos[3];
float hitTime;
rdVcopy(rayStartPos, startPos);
rdVcopy(rayEndPos, endPoint);
// note(amos): offset the ray heights so we don't clip into
// ledges. Realistically, we need at least the height of the
// agent anyways to be able to properly perform a jump
// without clipping into geometry.
rayStartPos[2] += heightOffset;
rayEndPos[2] += heightOffset;
// note(amos): perform 2 raycasts as we have to take the
// face normal into account. Path must be clear from both
// directions.
if (geom->raycastMesh(rayStartPos, rayEndPos, hitTime) ||
geom->raycastMesh(rayEndPos, rayStartPos, hitTime))
return false;
return true;
}
// TODO: create lookup table and look for distance + slope to determine the // TODO: create lookup table and look for distance + slope to determine the
// correct jumpType. // correct jumpType.
// TODO: make sure we don't generate duplicate pairs of jump types between // TODO: make sure we don't generate duplicate pairs of jump types between
// 2 polygons. // 2 polygons.
void Editor::connectTileTraverseLinks(dtMeshTile* const tile) void Editor::connectTileTraverseLinks(dtMeshTile* const startTile, dtMeshTile* const endTile)
{ {
for (int i = 0; i < tile->header->polyCount; ++i) for (int i = 0; i < startTile->header->polyCount; ++i)
{ {
dtPoly* const startPoly = &tile->polys[i]; dtPoly* const startPoly = &startTile->polys[i];
for (int j = 0; j < startPoly->vertCount; ++j) for (int j = 0; j < startPoly->vertCount; ++j)
{ {
@ -527,8 +574,8 @@ void Editor::connectTileTraverseLinks(dtMeshTile* const tile)
continue; continue;
// Polygon 1 edge // Polygon 1 edge
const float* const startPolySpos = &tile->verts[startPoly->verts[j] * 3]; const float* const startPolySpos = &startTile->verts[startPoly->verts[j] * 3];
const float* const startPolyEpos = &tile->verts[startPoly->verts[(j + 1) % startPoly->vertCount] * 3]; const float* const startPolyEpos = &startTile->verts[startPoly->verts[(j + 1) % startPoly->vertCount] * 3];
float startPolyEdgeMid[3]; float startPolyEdgeMid[3];
rdVsad(startPolyEdgeMid, startPolySpos, startPolyEpos, 0.5f); rdVsad(startPolyEdgeMid, startPolySpos, startPolyEpos, 0.5f);
@ -536,11 +583,12 @@ void Editor::connectTileTraverseLinks(dtMeshTile* const tile)
float startEdgeDir[3]; float startEdgeDir[3];
rdVsub(startEdgeDir, startPolyEpos, startPolySpos); rdVsub(startEdgeDir, startPolyEpos, startPolySpos);
for (int k = 0; k < tile->header->polyCount; ++k) for (int k = 0; k < endTile->header->polyCount; ++k)
{ {
if (i == k) continue; // Skip self const bool external = startTile != endTile;
if (!external && i == k) continue; // Skip self
dtPoly* const endPoly = &tile->polys[k]; dtPoly* const endPoly = &endTile->polys[k];
for (int m = 0; m < endPoly->vertCount; ++m) for (int m = 0; m < endPoly->vertCount; ++m)
{ {
@ -549,12 +597,17 @@ void Editor::connectTileTraverseLinks(dtMeshTile* const tile)
continue; continue;
// Polygon 2 edge // Polygon 2 edge
const float* const endPolySpos = &tile->verts[endPoly->verts[m] * 3]; const float* const endPolySpos = &endTile->verts[endPoly->verts[m] * 3];
const float* const endPolyEpos = &tile->verts[endPoly->verts[(m + 1) % endPoly->vertCount] * 3]; const float* const endPolyEpos = &endTile->verts[endPoly->verts[(m + 1) % endPoly->vertCount] * 3];
float endPolyEdgeMid[3]; float endPolyEdgeMid[3];
rdVsad(endPolyEdgeMid, endPolySpos, endPolyEpos, 0.5f); rdVsad(endPolyEdgeMid, endPolySpos, endPolyEpos, 0.5f);
const unsigned char distance = dtCalcLinkDistance(startPolyEdgeMid, endPolyEdgeMid);
if (distance == 0)
continue;
float endEdgeDir[3]; float endEdgeDir[3];
rdVsub(endEdgeDir, endPolyEpos, endPolySpos); rdVsub(endEdgeDir, endPolyEpos, endPolySpos);
@ -574,80 +627,46 @@ void Editor::connectTileTraverseLinks(dtMeshTile* const tile)
if (rdIntersectSegSeg2D(startPolySpos, startPolyEpos, endPolySpos, endPolyEpos, t, s)) if (rdIntersectSegSeg2D(startPolySpos, startPolyEpos, endPolySpos, endPolyEpos, t, s))
continue; continue;
float raycastStartPos[3];
float raycastEndPos[3];
float hitTime;
rdVcopy(raycastStartPos, startPolyEdgeMid);
rdVcopy(raycastEndPos, endPolyEdgeMid);
// note(amos): offset the ray heights so we don't clip into
// ledges. Realistically, we need at least the height of the
// agent anyways to be able to properly perform a jump
// without clipping into geometry.
raycastStartPos[2] += m_agentHeight;
raycastEndPos[2] += m_agentHeight;
// note(amos): perform 2 raycasts as we have to take the
// face normal into account. Path must be clear from both
// directions.
if (m_geom->raycastMesh(raycastStartPos, raycastEndPos, hitTime) ||
m_geom->raycastMesh(raycastEndPos, raycastStartPos, hitTime))
continue;
const unsigned char distance = dtCalcLinkDistance(startPolyEdgeMid, endPolyEdgeMid);
const float slopeAngle = rdMathFabsf(rdCalcSlopeAngle(startPolyEdgeMid, endPolyEdgeMid)); const float slopeAngle = rdMathFabsf(rdCalcSlopeAngle(startPolyEdgeMid, endPolyEdgeMid));
const bool samePolyGroup = startPoly->groupId == endPoly->groupId; const bool samePolyGroup = startPoly->groupId == endPoly->groupId;
const TraverseType_e traverseType = GetBestTraverseType(slopeAngle, distance, samePolyGroup); const TraverseType_e traverseType = GetBestTraverseType(slopeAngle, distance, samePolyGroup);
if (traverseType == DT_NULL_TRAVERSE_TYPE)
continue;
if (!CanOverlapPoly(traverseType)) if (!CanOverlapPoly(traverseType))
{ {
bool overlaps = false;
float linkMidPoint[3]; float linkMidPoint[3];
rdVsad(linkMidPoint, startPolyEdgeMid, endPolyEdgeMid, 0.5f); rdVsad(linkMidPoint, startPolyEdgeMid, endPolyEdgeMid, 0.5f);
for (int e = 0; e < tile->header->polyCount; e++) if (traverseLinkInPolygon(startTile, linkMidPoint) || traverseLinkInPolygon(endTile, linkMidPoint))
{
const dtPoly* posTestPoly = &tile->polys[e];
float polyVerts[DT_VERTS_PER_POLYGON * 3];
const int nverts = posTestPoly->vertCount;
for (int o = 0; o < nverts; ++o)
rdVcopy(&polyVerts[o * 3], &tile->verts[posTestPoly->verts[o] * 3]);
if (rdPointInPolygon(linkMidPoint, polyVerts, nverts))
{
overlaps = true;
break;
}
}
if (overlaps)
continue; continue;
} }
if (traverseType != DT_NULL_TRAVERSE_TYPE) if (!traverseLinkInLOS(m_geom, startPolyEdgeMid, endPolyEdgeMid, m_agentHeight))
{ continue;
// Need at least 2 links // Need at least 2 links
const unsigned int forwardIdx = tile->allocLink(); // todo(amos): perhaps optimize this so we check this before raycasting
// etc.. must also check if the tile isn't external because if so, we need
// space for 2 links in the same tile.
const unsigned int forwardIdx = startTile->allocLink();
if (forwardIdx == DT_NULL_LINK) // TODO: should move on to next tile. if (forwardIdx == DT_NULL_LINK) // TODO: should move on to next tile.
continue; continue;
const unsigned int reverseIdx = tile->allocLink(); const unsigned int reverseIdx = endTile->allocLink();
if (reverseIdx == DT_NULL_LINK) // TODO: should move on to next tile. if (reverseIdx == DT_NULL_LINK) // TODO: should move on to next tile.
{ {
tile->freeLink(forwardIdx); startTile->freeLink(forwardIdx);
continue; continue;
} }
dtLink* const forwardLink = &tile->links[forwardIdx]; dtLink* const forwardLink = &startTile->links[forwardIdx];
forwardLink->ref = m_navMesh->getPolyRefBase(tile) | (dtPolyRef)k; forwardLink->ref = m_navMesh->getPolyRefBase(endTile) | (dtPolyRef)k;
forwardLink->edge = (unsigned char)j; forwardLink->edge = (unsigned char)j;
forwardLink->side = 0xff; forwardLink->side = 0xff;
forwardLink->bmin = 0; forwardLink->bmin = 0;
@ -658,9 +677,9 @@ void Editor::connectTileTraverseLinks(dtMeshTile* const tile)
forwardLink->traverseDist = distance; forwardLink->traverseDist = distance;
forwardLink->reverseLink = (unsigned short)reverseIdx; forwardLink->reverseLink = (unsigned short)reverseIdx;
dtLink* const reverseLink = &tile->links[reverseIdx]; dtLink* const reverseLink = &endTile->links[reverseIdx];
reverseLink->ref = m_navMesh->getPolyRefBase(tile) | (dtPolyRef)i; reverseLink->ref = m_navMesh->getPolyRefBase(startTile) | (dtPolyRef)i;
reverseLink->edge = (unsigned char)m; reverseLink->edge = (unsigned char)m;
reverseLink->side = 0xff; reverseLink->side = 0xff;
reverseLink->bmin = 0; reverseLink->bmin = 0;
@ -674,19 +693,30 @@ void Editor::connectTileTraverseLinks(dtMeshTile* const tile)
} }
} }
} }
}
} }
bool Editor::createTraverseLinks() bool Editor::createTraverseLinks()
{ {
rdAssert(m_navMesh); rdAssert(m_navMesh);
const int maxTiles = m_navMesh->getMaxTiles();
const int MAX_NEIS = 32; // Max neighbors
for (int i = 0; i < m_navMesh->getMaxTiles(); i++) for (int i = 0; i < maxTiles; i++)
{ {
dtMeshTile* tile = m_navMesh->getTile(i); dtMeshTile* startTile = m_navMesh->getTile(i);
if (!tile->header) continue; if (!startTile || !startTile->header)
continue;
connectTileTraverseLinks(tile); for (int dir = 0; dir < 8; ++dir)
{
dtMeshTile* edgeNeis[MAX_NEIS];
int numEdgeNeis = m_navMesh->getNeighbourTilesAt(startTile->header->x, startTile->header->y, dir, edgeNeis, MAX_NEIS);
for (int j = 0; j < numEdgeNeis; ++j)
{
connectTileTraverseLinks(startTile, edgeNeis[j]);
}
}
} }
return true; return true;

View File

@ -245,7 +245,7 @@ public:
void resetCommonSettings(); void resetCommonSettings();
void handleCommonSettings(); void handleCommonSettings();
void connectTileTraverseLinks(dtMeshTile* const tile); // Make private. void connectTileTraverseLinks(dtMeshTile* const startTile, dtMeshTile* const endTile); // Make private.
bool createTraverseLinks(); bool createTraverseLinks();
void buildStaticPathingData(); void buildStaticPathingData();

View File

@ -97,6 +97,9 @@ static const unsigned char DT_NULL_TRAVERSE_TYPE = 0xff;
/// A value that indicates the link doesn't contain a reverse traverse link. /// A value that indicates the link doesn't contain a reverse traverse link.
static const unsigned short DT_NULL_TRAVERSE_REVERSE_LINK = 0xffff; static const unsigned short DT_NULL_TRAVERSE_REVERSE_LINK = 0xffff;
/// The maximum traverse distance for a traverse link. (Quantized value should not overflow #dtLink::traverseDist.)
static const float DT_TRAVERSE_DIST_MAX = 2550.0f;
/// The cached traverse link distance quantization factor. /// The cached traverse link distance quantization factor.
static const float DT_TRAVERSE_DIST_QUANT_FACTOR = 0.1f; static const float DT_TRAVERSE_DIST_QUANT_FACTOR = 0.1f;
@ -809,7 +812,7 @@ private:
dtNavMesh& operator=(const dtNavMesh&); dtNavMesh& operator=(const dtNavMesh&);
public:
/// Returns neighbour tile based on side. /// Returns neighbour tile based on side.
int getTilesAt(const int x, const int y, int getTilesAt(const int x, const int y,
dtMeshTile** tiles, const int maxTiles) const; dtMeshTile** tiles, const int maxTiles) const;
@ -818,6 +821,7 @@ private:
int getNeighbourTilesAt(const int x, const int y, const int side, int getNeighbourTilesAt(const int x, const int y, const int side,
dtMeshTile** tiles, const int maxTiles) const; dtMeshTile** tiles, const int maxTiles) const;
private:
/// Returns all polygons in neighbour tile based on portal defined by the segment. /// Returns all polygons in neighbour tile based on portal defined by the segment.
int findConnectingPolys(const float* va, const float* vb, int findConnectingPolys(const float* va, const float* vb,
const dtMeshTile* tile, int side, const dtMeshTile* tile, int side,

View File

@ -1750,7 +1750,9 @@ dtStatus dtNavMesh::getPolyArea(dtPolyRef ref, unsigned char* resultArea) const
unsigned char dtCalcLinkDistance(const float* spos, const float* epos) unsigned char dtCalcLinkDistance(const float* spos, const float* epos)
{ {
return (unsigned char)rdMathFloorf(rdVdist(spos, epos) * DT_TRAVERSE_DIST_QUANT_FACTOR); const float distance = rdMathFabsf(rdVdist(spos, epos));
if (distance > DT_TRAVERSE_DIST_MAX) return (unsigned char)0;
return (unsigned char)(distance * DT_TRAVERSE_DIST_QUANT_FACTOR);
} }
float dtCalcPolySurfaceArea(const dtPoly* poly, const float* verts) float dtCalcPolySurfaceArea(const dtPoly* poly, const float* verts)