Recast: fix several bugs in traverse link algorithm

- rdCalcSubEdgeArea2D could return false when tmin > tmax, which prevents the link from being established, but tmin can be larger than tmax when the detail edge equals the main edge. The issue we wanted to prevent where the triangle had an inverted winding order has been fixed in commit 1e48d8abd9e604a384e65cf633b62f4e11737d35.
- In dtNavMesh::connectTraverseLinks, the extraneous dot product check has been removed; this is now handled by the user installed dtTraverseLinkConnectParams::getTraverseType and dtTraverseLinkConnectParams::getTraverseType.
- In dtNavMesh::connectTraverseLinks, when we ran out of links in the land tile, we would still continue the traversal of all its detail edges and polygons. We now break out of all loops to move on to the next tile as we can not establish any traverse link when there are no free ones available.
This commit is contained in:
Kawe Mazidjatari 2024-10-18 12:13:16 +02:00
parent 2ed41d67ae
commit 4501581c6a
3 changed files with 17 additions and 38 deletions

View File

@ -806,8 +806,7 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
float baseTmin;
float baseTmax;
if (!rdCalcSubEdgeArea2D(basePolySpos, basePolyEpos, baseDetailPolyEdgeSpos, baseDetailPolyEdgeEpos, baseTmin, baseTmax))
continue;
rdCalcSubEdgeArea2D(basePolySpos, basePolyEpos, baseDetailPolyEdgeSpos, baseDetailPolyEdgeEpos, baseTmin, baseTmax);
float baseEdgeDir[3];
rdVsub(baseEdgeDir, baseDetailPolyEdgeEpos, baseDetailPolyEdgeSpos);
@ -862,7 +861,9 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
const dtPolyRef landPolyRefBase = getPolyRefBase(landTile);
bool firstLandTileLinkUsed = false;
for (int o = 0; o < landHeader->polyCount; ++o)
bool moveToNextTile = false;
for (int o = 0; (o < landHeader->polyCount) && !moveToNextTile; ++o)
{
dtPoly* const landPoly = &landTile->polys[o];
@ -874,7 +875,7 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
dtPolyDetail* const landDetail = &landTile->detailMeshes[o];
for (int p = 0; p < landPoly->vertCount; ++p)
for (int p = 0; (p < landPoly->vertCount) && !moveToNextTile; ++p)
{
if (landPoly->neis[p] != 0)
continue;
@ -883,7 +884,7 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
const float* const landPolySpos = &landTile->verts[landPoly->verts[p]*3];
const float* const landPolyEpos = &landTile->verts[landPoly->verts[(p+1)%landPoly->vertCount]*3];
for (int q = 0; q < landDetail->triCount; ++q)
for (int q = 0; (q < landDetail->triCount) && !moveToNextTile; ++q)
{
const unsigned char* landTri = &landTile->detailTris[(landDetail->triBase+q)*4];
const float* landTriVerts[3];
@ -901,7 +902,10 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
if (params.linkToNeighbor)
{
if (firstLandTileLinkUsed && !landTile->linkCountAvailable(1))
continue;
{
moveToNextTile = true;
break;
}
else if (firstBaseTileLinkUsed && !baseTile->linkCountAvailable(1))
return DT_FAILURE | DT_OUT_OF_MEMORY;
@ -921,8 +925,7 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
float landTmin;
float landTmax;
if (!rdCalcSubEdgeArea2D(landPolySpos, landPolyEpos, landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, landTmin, landTmax))
continue;
rdCalcSubEdgeArea2D(landPolySpos, landPolyEpos, landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, landTmin, landTmax);
float landPolyEdgeMid[3];
rdVsad(landPolyEdgeMid, landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, 0.5f);
@ -936,22 +939,6 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
float landEdgeDir[3];
rdVsub(landEdgeDir, landDetailPolyEdgeEpos, landDetailPolyEdgeSpos);
const float dotProduct = rdVdot(baseEdgeDir, landEdgeDir);
// Edges facing the same direction should not be linked.
// Doing so causes links to go through from underneath
// geometry. E.g. we have an HVAC on a roof, and we try
// to link our roof poly edge facing north to the edge
// of the poly on the HVAC also facing north, the link
// will go through the HVAC and thus cause the NPC to
// jump through it.
// Another case where this is necessary is when having
// a land edge that connects with the base edge, this
// prevents the algorithm from establishing a parallel
// traverse link.
if (dotProduct > 0)
continue;
const float elevation = rdMathFabsf(basePolyEdgeMid[2] - landPolyEdgeMid[2]);
const float slopeAngle = rdMathFabsf(rdCalcSlopeAngle(basePolyEdgeMid, landPolyEdgeMid));
const bool baseOverlaps = rdCalcEdgeOverlap2D(baseDetailPolyEdgeSpos, baseDetailPolyEdgeEpos, landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, baseEdgeDir) > params.minEdgeOverlap;

View File

@ -514,8 +514,7 @@ void rdCalcEdgeNormalPt2D(const float* v1, const float* v2, float* out);
/// @param[in] subEdgeEnd Second vert of the detail edge. [(x, y, z)]
/// @param[out] tmin The normalized distance ratio from polygon edge start to detail edge start.
/// @param[out] tmax The normalized distance ratio from polygon edge start to detail edge end.
/// @return False if tmin and tmax don't correspond to the winding order of the edge.
bool rdCalcSubEdgeArea2D(const float* edgeStart, const float* edgeEnd, const float* subEdgeStart,
void rdCalcSubEdgeArea2D(const float* edgeStart, const float* edgeEnd, const float* subEdgeStart,
const float* subEdgeEnd, float& tmin, float& tmax);
/// Derives the overlap between 2 edges.

View File

@ -597,26 +597,19 @@ void rdCalcEdgeNormalPt2D(const float* v1, const float* v2, float* out)
rdCalcEdgeNormal2D(dir, out);
}
bool rdCalcSubEdgeArea2D(const float* edgeStart, const float* edgeEnd, const float* subEdgeStart,
void rdCalcSubEdgeArea2D(const float* edgeStart, const float* edgeEnd, const float* subEdgeStart,
const float* subEdgeEnd, float& tmin, float& tmax)
{
const float edgeLen = rdVdist2D(edgeStart, edgeEnd);
const float subEdgeStartDist = rdVdist2D(edgeStart, subEdgeStart);
const float subEdgeEndDist = rdVdist2D(edgeStart, subEdgeEnd);
tmin = subEdgeStartDist / edgeLen;
tmax = subEdgeEndDist / edgeLen;
tmax = subEdgeStartDist / edgeLen;
tmin = subEdgeEndDist / edgeLen;
// note(amos): If the min is larger than the max, we most likely have a
// malformed detail polygon, e.g. a triangle that is flipped causing its
// boundary edge's start vert to be closer to the end vert of the polygon
// when comparing the distances in the same winding order. This can happen
// on more complex geometry or when the error tollerance is raised. Either
// way return false to notify caller that the calculation has failed.
// Can happen when the sub edge equals the main edge.
if (tmin > tmax)
return false;
return true;
rdSwap(tmin, tmax);
}
float rdCalcEdgeOverlap2D(const float* edge1Start, const float* edge1End,