Recast: implement traverse portal alignment

Shift portal mins and maxs together to try and align the portals. The system is dynamic and will take portal span into consideration. Also slightly changed the API to implement an optimization by switching the directional vector of the edges to their normals as we only need the normals in traverseLinkInLOS, this avoids having to recalculate them.
This commit is contained in:
Kawe Mazidjatari 2024-11-01 13:57:38 +01:00
parent 8d5bc23cf6
commit bb140317c7
4 changed files with 76 additions and 21 deletions

View File

@ -295,6 +295,7 @@ void Editor::resetCommonSettings()
// lower slope to clip into geometry unless this is being set very high (>40.0).
m_traverseRayExtraOffset = 20.0f;
m_traverseEdgeMinOverlap = RD_EPS;
m_traversePortalMaxAlign = 0.5f;
m_regionMinSize = 4;
m_regionMergeSize = 20;
@ -458,6 +459,8 @@ void Editor::handleCommonSettings()
ImGui::SliderFloat("Min Overlap", &m_traverseEdgeMinOverlap, 0.0f, m_tileSize*m_cellSize, "%g");
ImGui::SliderFloat("Max Align", &m_traversePortalMaxAlign, 0.0f, 0.5f, "%g", ImGuiSliderFlags_AlwaysClamp);
if (ImGui::Button("Rebuild Static Pathing Data"))
createStaticPathingData();
@ -629,8 +632,8 @@ static bool traverseLinkOffsetIntersectsGeom(const InputGeom* geom, const float*
return false;
}
static bool traverseLinkInLOS(void* userData, const float* lowPos, const float* highPos, const float* lowDir,
const float* highDir, const float walkableRadius, const float slopeAngle)
static bool traverseLinkInLOS(void* userData, const float* lowPos, const float* highPos, const float* lowNorm,
const float* highNorm, const float walkableRadius, const float slopeAngle)
{
Editor* editor = (Editor*)userData;
InputGeom* geom = editor->getInputGeom();
@ -649,12 +652,6 @@ static bool traverseLinkInLOS(void* userData, const float* lowPos, const float*
else
offsetAmount = walkableRadius + extraOffset;
float lowNormal[3];
rdCalcEdgeNormal2D(lowDir, lowNormal);
float highNormal[3];
rdCalcEdgeNormal2D(highDir, highNormal);
// Detect overhangs to avoid links like these:
//
// geom upper navmesh
@ -678,7 +675,7 @@ static bool traverseLinkInLOS(void* userData, const float* lowPos, const float*
// The AI would otherwise attempt to initiate
// the jump from the lower navmesh, causing it
// to clip through geometry.
if (!polyEdgeFaceAgainst(lowPos, highPos, lowNormal, highNormal))
if (!polyEdgeFaceAgainst(lowPos, highPos, lowNorm, highNorm))
return false;
const float* targetRayPos = highPos;
@ -715,8 +712,8 @@ static bool traverseLinkInLOS(void* userData, const float* lowPos, const float*
if (hasOffset)
{
offsetRayPos[0] = highPos[0] + highNormal[0] * offsetAmount;
offsetRayPos[1] = highPos[1] + highNormal[1] * offsetAmount;
offsetRayPos[0] = highPos[0] + highNorm[0] * offsetAmount;
offsetRayPos[1] = highPos[1] + highNorm[1] * offsetAmount;
offsetRayPos[2] = highPos[2];
if (traverseLinkOffsetIntersectsGeom(geom, highPos, offsetRayPos))
@ -787,6 +784,7 @@ void Editor::createTraverseLinkParams(dtTraverseLinkConnectParams& params)
params.userData = this;
params.minEdgeOverlap = m_traverseEdgeMinOverlap;
params.maxPortalAlign = m_traversePortalMaxAlign;
}
bool Editor::createTraverseLinks()

View File

@ -255,6 +255,7 @@ protected:
float m_agentMaxSlope;
float m_traverseRayExtraOffset;
float m_traverseEdgeMinOverlap;
float m_traversePortalMaxAlign;
int m_regionMinSize;
int m_regionMergeSize;
int m_edgeMaxLen;

View File

@ -647,8 +647,8 @@ struct dtTraverseLinkConnectParams
/// @param[in] userData Pointer to user defined data.
/// @param[in] lowerEdgeMid The mid point of the lower edge from which the link starts. [(x, y, z)] [Unit: wu]
/// @param[in] higherEdgeMid The mid point of the higher edge to which the link ends. [(x, y, z)] [Unit: wu]
/// @param[in] lowerEdgeDir The vector direction of the lower edge. [(x, y, z)] [Unit: wu]
/// @param[in] higherEdgeDir The vector direction of the higher edge. [(x, y, z)] [Unit: wu]
/// @param[in] lowerEdgeNorm The edge normal of the lower edge. [(x, y, z)] [Unit: wu]
/// @param[in] higherEdgeNorm The edge normal of the higher edge. [(x, y, z)] [Unit: wu]
/// @param[in] walkableRadius The walkable radius defined by the tile hosting the link. [Unit: wu]
/// @param[in] slopeAngle The slope angle from lower to higher edge mid points. [Unit: Degrees]
/// @return True if the link between the lower and higher edge mid points don't collide with anything.
@ -675,7 +675,8 @@ struct dtTraverseLinkConnectParams
int(*addPolyLink)(void* userData, const dtPolyRef basePolyRef, const dtPolyRef landPolyRef, const unsigned int traverseTypeBit);
void* userData; ///< The user defined data that will be provided to all callbacks, for example: your editor's class instance.
float minEdgeOverlap; ///< The minimum amount of projection overlap required between the 2 edges before they are considered overlapping.
float minEdgeOverlap; ///< The minimum amount of projection overlap required between the 2 edges before they are considered overlapping. [Unit: wu]
float maxPortalAlign; ///< The maximum amount of portal alignment the system will apply. [Limit: 0 >= align < 0.5]
bool linkToNeighbor; ///< Whether to link to polygons in neighboring tiles. Limits linkage to internal polygons if false.
};

View File

@ -107,6 +107,47 @@ static void calcSlabEndPoints(const float* va, const float* vb, float* bmin, flo
}
}
static void alignPortalLimits(const float* portal1Pos, const float* portal1Norm, const float* portal2Pos,
const float portalTmin, const float portalTmax, float& outPortalTmin, float& outPortalTmax, const float maxAlign)
{
// note(amos): if we take an extreme scenario, where:
// - span = portalTmax(1.0f) - portalTmin(0.99f)
// - shiftAmount = maxAlign(1.0f) * abs(crossZ(1.0f)) * span(0.1f)
// the portal will collapse as we will shift portalTmin(0.99f) with
// shiftAmount(0.1f)! Also, any value above 0.5 will cause too much
// shifting; to avoid rounding mins into maxs or the other way around
// during quantization, maxAlign should never be greater than 0.5.
rdAssert(maxAlign <= 0.5f);
float delta[3];
delta[0] = portal2Pos[0]-portal1Pos[0];
delta[1] = portal2Pos[1]-portal1Pos[1];
delta[2] = 0.0f;
rdVnormalize2D(delta);
float cross[3];
rdVcross(cross, delta, portal1Norm);
const float span = portalTmax-portalTmin;
const float shiftAmount = maxAlign * rdMathFabsf(cross[2]) * span;
if (cross[2] < 0)
{
outPortalTmin = rdMin(portalTmax, portalTmin+shiftAmount);
outPortalTmax = rdMin(1.0f, portalTmax+shiftAmount);
}
else if (cross[2] > 0)
{
outPortalTmin = rdMax(0.0f, portalTmin-shiftAmount);
outPortalTmax = rdMax(portalTmin, portalTmax-shiftAmount);
}
else
{
outPortalTmin = portalTmin;
outPortalTmax = portalTmax;
}
}
inline int computeTileHash(int x, int y, const int mask)
{
const unsigned int h1 = 0x8da6b343; // Large multiplicative constants;
@ -824,6 +865,9 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
float baseEdgeDir[3];
rdVsub(baseEdgeDir, baseDetailPolyEdgeEpos, baseDetailPolyEdgeSpos);
float baseEdgeNorm[3];
rdCalcEdgeNormal2D(baseEdgeDir, baseEdgeNorm);
const unsigned char baseSide = rdClassifyDirection(baseEdgeDir, baseHeader->bmin, baseHeader->bmax);
const int MAX_NEIS = 32; // Max neighbors
@ -955,6 +999,9 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
float landEdgeDir[3];
rdVsub(landEdgeDir, landDetailPolyEdgeEpos, landDetailPolyEdgeSpos);
float landEdgeNorm[3];
rdCalcEdgeNormal2D(landEdgeDir, landEdgeNorm);
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;
@ -977,18 +1024,26 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
const bool basePolyHigher = basePolyEdgeMid[2] > landPolyEdgeMid[2];
const float* const lowerEdgeMid = basePolyHigher ? landPolyEdgeMid : basePolyEdgeMid;
const float* const higherEdgeMid = basePolyHigher ? basePolyEdgeMid : landPolyEdgeMid;
const float* const lowerEdgeDir = basePolyHigher ? landEdgeDir : baseEdgeDir;
const float* const higherEdgeDir = basePolyHigher ? baseEdgeDir : landEdgeDir;
const float* const lowerEdgeNorm = basePolyHigher ? landEdgeNorm : baseEdgeNorm;
const float* const higherEdgeNorm = basePolyHigher ? baseEdgeNorm : landEdgeNorm;
const float walkableRadius = basePolyHigher ? baseHeader->walkableRadius : landHeader->walkableRadius;
if (!params.traverseLinkInLOS(params.userData, lowerEdgeMid, higherEdgeMid, lowerEdgeDir, higherEdgeDir, walkableRadius, slopeAngle))
if (!params.traverseLinkInLOS(params.userData, lowerEdgeMid, higherEdgeMid, lowerEdgeNorm, higherEdgeNorm, walkableRadius, slopeAngle))
continue;
const unsigned char landSide = params.linkToNeighbor
? rdClassifyPointOutsideBounds(landPolyEdgeMid, landHeader->bmin, landHeader->bmax)
: rdClassifyPointInsideBounds(landPolyEdgeMid, landHeader->bmin, landHeader->bmax);
float newBaseTmin;
float newBaseTmax;
alignPortalLimits(basePolyEdgeMid, baseEdgeNorm, landPolyEdgeMid, baseTmin, baseTmax, newBaseTmin, newBaseTmax, params.maxPortalAlign);
float newLandTmin;
float newLandTmax;
alignPortalLimits(landPolyEdgeMid, landEdgeNorm, basePolyEdgeMid, landTmin, landTmax, newLandTmin, newLandTmax, params.maxPortalAlign);
const unsigned int forwardIdx = baseTile->allocLink();
const unsigned int reverseIdx = landTile->allocLink();
@ -1003,8 +1058,8 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
forwardLink->ref = landPolyRef;
forwardLink->edge = (unsigned char)j;
forwardLink->side = landSide;
forwardLink->bmin = (unsigned char)rdMathRoundf(baseTmin*255.f);
forwardLink->bmax = (unsigned char)rdMathRoundf(baseTmax*255.f);
forwardLink->bmin = (unsigned char)rdMathRoundf(newBaseTmin*255.f);
forwardLink->bmax = (unsigned char)rdMathRoundf(newBaseTmax*255.f);
forwardLink->next = basePoly->firstLink;
basePoly->firstLink = forwardIdx;
forwardLink->traverseType = (unsigned char)traverseType;
@ -1016,8 +1071,8 @@ dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraver
reverseLink->ref = basePolyRef;
reverseLink->edge = (unsigned char)p;
reverseLink->side = baseSide;
reverseLink->bmin = (unsigned char)rdMathRoundf(landTmin*255.f);
reverseLink->bmax = (unsigned char)rdMathRoundf(landTmax*255.f);
reverseLink->bmin = (unsigned char)rdMathRoundf(newLandTmin*255.f);
reverseLink->bmax = (unsigned char)rdMathRoundf(newLandTmax*255.f);
reverseLink->next = landPoly->firstLink;
landPoly->firstLink = reverseIdx;
reverseLink->traverseType = (unsigned char)traverseType;