mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
Recast: API improvement; move traverse link algorithm to navmesh class
The algorithm has been moved to the dtNavMesh class, and now features a parameter based design allowing code that utilizes the algorithm to define engine-specific tests such as the raycasting, traverse masking, etc. The code can now be hooked up to any level editor without changing a line of code inside the Recast & Detour library. All parameters have been documented thoroughly. The code now also properly returns errors if out of memory, or if invalid parameters were provided. This commit does not affect the results of the traverse link generation, the navmesh remains identical to the one generated on the previous design.
This commit is contained in:
parent
1e92548624
commit
0814efe613
@ -460,7 +460,29 @@ void Editor::handleUpdate(const float dt)
|
||||
updateToolStates(dt);
|
||||
}
|
||||
|
||||
TraverseType_e GetBestTraverseType(const float traverseDist, const float elevation, const float slope, const bool baseOverlaps, const bool landOverlaps)
|
||||
bool traverseTypeSupported(void* userData, const unsigned char traverseType)
|
||||
{
|
||||
const Editor* editor = (const Editor*)userData;
|
||||
const NavMeshType_e navMeshType = editor->getSelectedNavMeshType();
|
||||
|
||||
if (navMeshType == NavMeshType_e::NAVMESH_SMALL)
|
||||
{
|
||||
const int tableCount = NavMesh_GetTraverseTableCountForNavMeshType(navMeshType);
|
||||
|
||||
for (int t = 0; t < tableCount; t++)
|
||||
{
|
||||
if (rdBitCellBit(traverseType) & s_traverseAnimTraverseFlags[t])
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const int traverseTableIndex = NavMesh_GetFirstTraverseAnimTypeForType(navMeshType);
|
||||
return rdBitCellBit(traverseType) & s_traverseAnimTraverseFlags[traverseTableIndex];
|
||||
}
|
||||
|
||||
unsigned char GetBestTraverseType(void* userData, const float traverseDist, const float elevation, const float slope, const bool baseOverlaps, const bool landOverlaps)
|
||||
{
|
||||
TraverseType_e bestTraverseType = INVALID_TRAVERSE_TYPE;
|
||||
float smallestDiff = FLT_MAX;
|
||||
@ -521,27 +543,10 @@ TraverseType_e GetBestTraverseType(const float traverseDist, const float elevati
|
||||
}
|
||||
}
|
||||
|
||||
return bestTraverseType;
|
||||
}
|
||||
if (!traverseTypeSupported(userData, (unsigned char)bestTraverseType))
|
||||
return DT_NULL_TRAVERSE_TYPE;
|
||||
|
||||
float calcEdgeOverlap(const float* edge1Start, const float* edge1End, const float* edge2Start, const float* edge2End, const float* targetEdgeVec)
|
||||
{
|
||||
float min1 = rdVproj2D(edge1Start, targetEdgeVec);
|
||||
float max1 = rdVproj2D(edge1End, targetEdgeVec);
|
||||
|
||||
if (min1 > max1)
|
||||
rdSwap(min1, max1);
|
||||
|
||||
float min2 = rdVproj2D(edge2Start, targetEdgeVec);
|
||||
float max2 = rdVproj2D(edge2End, targetEdgeVec);
|
||||
|
||||
if (min2 > max2)
|
||||
rdSwap(min2, max2);
|
||||
|
||||
const float start = rdMax(min1, min2);
|
||||
const float end = rdMin(max1, max2);
|
||||
|
||||
return rdMax(0.0f, end - start);
|
||||
return (unsigned char)bestTraverseType;
|
||||
}
|
||||
|
||||
static bool polyEdgeFaceAgainst(const float* v1, const float* v2, const float* n1, const float* n2)
|
||||
@ -579,8 +584,26 @@ static bool traverseLinkOffsetIntersectsGeom(const InputGeom* geom, const float*
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool traverseLinkInLOS(const InputGeom* geom, const float* lowPos, const float* highPos, const float* lowDir, const float* highDir, const float offsetAmount)
|
||||
static bool traverseLinkInLOS(void* userData, const float* lowPos, const float* highPos, const float* lowDir,
|
||||
const float* highDir, const float walkableRadius, const float slopeAngle)
|
||||
{
|
||||
Editor* editor = (Editor*)userData;
|
||||
InputGeom* geom = editor->getInputGeom();
|
||||
|
||||
const float extraOffset = editor->getTraverseRayExtraOffset();
|
||||
const float cellHeight = editor->getCellHeight();
|
||||
float offsetAmount;
|
||||
|
||||
if (editor->useDynamicTraverseRayOffset())
|
||||
{
|
||||
const float totLedgeSpan = walkableRadius + extraOffset;
|
||||
const float maxAngle = rdCalcMaxLOSAngle(totLedgeSpan, cellHeight);
|
||||
|
||||
offsetAmount = rdCalcLedgeSpanOffsetAmount(totLedgeSpan, slopeAngle, maxAngle);
|
||||
}
|
||||
else
|
||||
offsetAmount = walkableRadius + extraOffset;
|
||||
|
||||
float lowNormal[3];
|
||||
rdCalcEdgeNormal2D(lowDir, lowNormal);
|
||||
|
||||
@ -678,342 +701,47 @@ static bool traverseLinkInLOS(const InputGeom* geom, const float* lowPos, const
|
||||
return true;
|
||||
}
|
||||
|
||||
void Editor::connectTileTraverseLinks(dtMeshTile* const baseTile, const bool linkToNeighbor)
|
||||
static unsigned int* findFromPolyMap(void* userData, const dtPolyRef basePolyRef, const dtPolyRef landPolyRef)
|
||||
{
|
||||
const dtMeshHeader* baseHeader = baseTile->header;
|
||||
Editor* editor = (Editor*)userData;
|
||||
auto it = editor->getTraverseLinkPolyMap().find(TraverseLinkPolyPair(basePolyRef, landPolyRef));
|
||||
|
||||
if (!baseHeader->detailMeshCount)
|
||||
return; // Detail meshes are required for traverse links.
|
||||
if (it == editor->getTraverseLinkPolyMap().end())
|
||||
return nullptr;
|
||||
|
||||
// If we link to the same tile, we need at least 2 links.
|
||||
if (!baseTile->linkCountAvailable(linkToNeighbor ? 1 : 2))
|
||||
return;
|
||||
|
||||
static const float detailEdgeAlignThresh = 0.01f*0.01f;
|
||||
|
||||
const dtPolyRef basePolyRefBase = m_navMesh->getPolyRefBase(baseTile);
|
||||
bool firstBaseTileLinkUsed = false;
|
||||
|
||||
for (int i = 0; i < baseHeader->polyCount; ++i)
|
||||
{
|
||||
dtPoly* const basePoly = &baseTile->polys[i];
|
||||
|
||||
if (basePoly->groupId == DT_UNLINKED_POLY_GROUP)
|
||||
continue;
|
||||
|
||||
if (basePoly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
|
||||
continue;
|
||||
|
||||
dtPolyDetail* const baseDetail = &baseTile->detailMeshes[i];
|
||||
|
||||
for (int j = 0; j < basePoly->vertCount; ++j)
|
||||
{
|
||||
// Hard edges only!
|
||||
if (basePoly->neis[j] != 0)
|
||||
continue;
|
||||
|
||||
// Polygon 1 edge
|
||||
const float* const basePolySpos = &baseTile->verts[basePoly->verts[j]*3];
|
||||
const float* const basePolyEpos = &baseTile->verts[basePoly->verts[(j+1)%basePoly->vertCount]*3];
|
||||
|
||||
for (int k = 0; k < baseDetail->triCount; ++k)
|
||||
{
|
||||
const unsigned char* baseTri = &baseTile->detailTris[(baseDetail->triBase+k)*4];
|
||||
const float* baseTriVerts[3];
|
||||
for (int l = 0; l < 3; ++l)
|
||||
{
|
||||
if (baseTri[l] < basePoly->vertCount)
|
||||
baseTriVerts[l] = &baseTile->verts[basePoly->verts[baseTri[l]]*3];
|
||||
else
|
||||
baseTriVerts[l] = &baseTile->detailVerts[(baseDetail->vertBase+(baseTri[l]-basePoly->vertCount))*3];
|
||||
}
|
||||
for (int l = 0, m = 2; l < 3; m = l++)
|
||||
{
|
||||
if ((dtGetDetailTriEdgeFlags(baseTri[3], m) & DT_DETAIL_EDGE_BOUNDARY) == 0)
|
||||
continue;
|
||||
|
||||
if (rdDistancePtLine2D(baseTriVerts[m], basePolySpos, basePolyEpos) >= detailEdgeAlignThresh ||
|
||||
rdDistancePtLine2D(baseTriVerts[l], basePolySpos, basePolyEpos) >= detailEdgeAlignThresh)
|
||||
continue;
|
||||
|
||||
const float* baseDetailPolyEdgeSpos = baseTriVerts[m];
|
||||
const float* baseDetailPolyEdgeEpos = baseTriVerts[l];
|
||||
|
||||
float baseTmin;
|
||||
float baseTmax;
|
||||
if (!rdCalcSubEdgeArea2D(basePolySpos, basePolyEpos, baseDetailPolyEdgeSpos, baseDetailPolyEdgeEpos, baseTmin, baseTmax))
|
||||
continue;
|
||||
|
||||
float baseEdgeDir[3];
|
||||
rdVsub(baseEdgeDir, baseDetailPolyEdgeEpos, baseDetailPolyEdgeSpos);
|
||||
|
||||
unsigned char baseSide = rdClassifyDirection(baseEdgeDir, baseHeader->bmin, baseHeader->bmax);
|
||||
|
||||
const int MAX_NEIS = 32; // Max neighbors
|
||||
dtMeshTile* neis[MAX_NEIS];
|
||||
|
||||
int nneis = 0;
|
||||
|
||||
if (linkToNeighbor) // Retrieve the neighboring tiles on the side of our base poly edge.
|
||||
{
|
||||
nneis = m_navMesh->getNeighbourTilesAt(baseHeader->x, baseHeader->y, baseSide, neis, MAX_NEIS);
|
||||
|
||||
// No neighbors, nothing to link to on this side.
|
||||
if (!nneis)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Internal links.
|
||||
nneis = 1;
|
||||
neis[0] = baseTile;
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
float basePolyEdgeMid[3];
|
||||
if (nneis)
|
||||
rdVsad(basePolyEdgeMid, baseDetailPolyEdgeSpos, baseDetailPolyEdgeEpos, 0.5f);
|
||||
|
||||
for (int n = nneis - 1; n >= 0; --n)
|
||||
static int addToPolyMap(void* userData, const dtPolyRef basePolyRef, const dtPolyRef landPolyRef, const unsigned int traverseTypeBit)
|
||||
{
|
||||
dtMeshTile* landTile = neis[n];
|
||||
const bool sameTile = baseTile == landTile;
|
||||
Editor* editor = (Editor*)userData;
|
||||
|
||||
// Don't connect to same tile edges yet, leave that for the second pass.
|
||||
if (linkToNeighbor && sameTile)
|
||||
continue;
|
||||
|
||||
const dtMeshHeader* landHeader = landTile->header;
|
||||
|
||||
if (!landHeader->detailMeshCount)
|
||||
continue; // Detail meshes are required for traverse links.
|
||||
|
||||
// Skip same polygon.
|
||||
if (sameTile && i == n)
|
||||
continue;
|
||||
|
||||
if (!landTile->linkCountAvailable(1))
|
||||
continue;
|
||||
|
||||
const dtPolyRef landPolyRefBase = m_navMesh->getPolyRefBase(landTile);
|
||||
bool firstLandTileLinkUsed = false;
|
||||
|
||||
for (int o = 0; o < landHeader->polyCount; ++o)
|
||||
try
|
||||
{
|
||||
dtPoly* const landPoly = &landTile->polys[o];
|
||||
|
||||
if (landPoly->groupId == DT_UNLINKED_POLY_GROUP)
|
||||
continue;
|
||||
|
||||
if (landPoly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
|
||||
continue;
|
||||
|
||||
dtPolyDetail* const landDetail = &landTile->detailMeshes[o];
|
||||
|
||||
for (int p = 0; p < landPoly->vertCount; ++p)
|
||||
auto ret = editor->getTraverseLinkPolyMap().emplace(TraverseLinkPolyPair(basePolyRef, landPolyRef), traverseTypeBit);
|
||||
if (!ret.second)
|
||||
{
|
||||
if (landPoly->neis[p] != 0)
|
||||
continue;
|
||||
|
||||
// Polygon 2 edge
|
||||
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)
|
||||
rdAssert(ret.second); // Called 'addToPolyMap' while poly link already exists.
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
catch (const std::bad_alloc& /*e*/)
|
||||
{
|
||||
const unsigned char* landTri = &landTile->detailTris[(landDetail->triBase+q)*4];
|
||||
const float* landTriVerts[3];
|
||||
for (int r = 0; r < 3; ++r)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Editor::createTraverseLinkParams(dtTraverseLinkConnectParams& params)
|
||||
{
|
||||
if (landTri[r] < landPoly->vertCount)
|
||||
landTriVerts[r] = &landTile->verts[landPoly->verts[landTri[r]]*3];
|
||||
else
|
||||
landTriVerts[r] = &landTile->detailVerts[(landDetail->vertBase+(landTri[r]-landPoly->vertCount))*3];
|
||||
}
|
||||
for (int r = 0, s = 2; r < 3; s = r++)
|
||||
{
|
||||
// We need at least 2 links available, figure out if
|
||||
// we link to the same tile or another one.
|
||||
if (linkToNeighbor)
|
||||
{
|
||||
if (firstLandTileLinkUsed && !landTile->linkCountAvailable(1))
|
||||
continue;
|
||||
params.getTraverseType = &GetBestTraverseType;
|
||||
params.traverseLinkInLOS = &traverseLinkInLOS;
|
||||
params.findPolyLink = &findFromPolyMap;
|
||||
params.addPolyLink = &addToPolyMap;
|
||||
|
||||
else if (firstBaseTileLinkUsed && !baseTile->linkCountAvailable(1))
|
||||
return;
|
||||
}
|
||||
else if (firstBaseTileLinkUsed && !baseTile->linkCountAvailable(2))
|
||||
return;
|
||||
|
||||
if ((dtGetDetailTriEdgeFlags(landTri[3], s) & DT_DETAIL_EDGE_BOUNDARY) == 0)
|
||||
continue;
|
||||
|
||||
if (rdDistancePtLine2D(landTriVerts[s], landPolySpos, landPolyEpos) >= detailEdgeAlignThresh ||
|
||||
rdDistancePtLine2D(landTriVerts[r], landPolySpos, landPolyEpos) >= detailEdgeAlignThresh)
|
||||
continue;
|
||||
|
||||
const float* landDetailPolyEdgeSpos = landTriVerts[s];
|
||||
const float* landDetailPolyEdgeEpos = landTriVerts[r];
|
||||
|
||||
float landTmin;
|
||||
float landTmax;
|
||||
if (!rdCalcSubEdgeArea2D(landPolySpos, landPolyEpos, landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, landTmin, landTmax))
|
||||
continue;
|
||||
|
||||
float landPolyEdgeMid[3];
|
||||
rdVsad(landPolyEdgeMid, landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, 0.5f);
|
||||
|
||||
const float dist = dtCalcLinkDistance(basePolyEdgeMid, landPolyEdgeMid);
|
||||
const unsigned char quantDist = dtQuantLinkDistance(dist);
|
||||
|
||||
if (quantDist == 0)
|
||||
continue; // Link distance is greater than maximum supported.
|
||||
|
||||
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 = calcEdgeOverlap(baseDetailPolyEdgeSpos, baseDetailPolyEdgeEpos, landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, baseEdgeDir) > m_traverseEdgeMinOverlap;
|
||||
const bool landOverlaps = calcEdgeOverlap(landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, baseDetailPolyEdgeSpos, baseDetailPolyEdgeEpos, landEdgeDir) > m_traverseEdgeMinOverlap;
|
||||
|
||||
const TraverseType_e traverseType = GetBestTraverseType(dist, elevation, slopeAngle, baseOverlaps, landOverlaps);
|
||||
|
||||
if (traverseType == DT_NULL_TRAVERSE_TYPE)
|
||||
continue;
|
||||
|
||||
if (m_selectedNavMeshType == NavMeshType_e::NAVMESH_SMALL)
|
||||
{
|
||||
const int tableCount = NavMesh_GetTraverseTableCountForNavMeshType(m_selectedNavMeshType);
|
||||
bool traverseTypeSupported = false;
|
||||
|
||||
for (int t = 0; t < tableCount; t++)
|
||||
{
|
||||
if (rdBitCellBit(traverseType) & s_traverseAnimTraverseFlags[t])
|
||||
{
|
||||
traverseTypeSupported = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!traverseTypeSupported)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
const int traverseTableIndex = NavMesh_GetFirstTraverseAnimTypeForType(m_selectedNavMeshType);
|
||||
const bool traverseTypeSupported = rdBitCellBit(traverseType) & s_traverseAnimTraverseFlags[traverseTableIndex];
|
||||
|
||||
if (!traverseTypeSupported)
|
||||
continue;
|
||||
}
|
||||
|
||||
const dtPolyRef basePolyRef = basePolyRefBase | i;
|
||||
const dtPolyRef landPolyRef = landPolyRefBase | o;
|
||||
|
||||
const TraverseLinkPolyPair linkedPolyPair(basePolyRef, landPolyRef);
|
||||
auto linkedIt = m_traverseLinkPolyMap.find(linkedPolyPair);
|
||||
|
||||
bool traverseLinkFound = false;
|
||||
|
||||
if (linkedIt != m_traverseLinkPolyMap.end())
|
||||
traverseLinkFound = true;
|
||||
|
||||
// These 2 polygons are already linked with the same traverse type.
|
||||
if (traverseLinkFound && (rdBitCellBit(traverseType) & linkedIt->second))
|
||||
continue;
|
||||
|
||||
const bool basePolyHigher = basePolyEdgeMid[2] > landPolyEdgeMid[2];
|
||||
float* const lowerEdgeMid = basePolyHigher ? landPolyEdgeMid : basePolyEdgeMid;
|
||||
float* const higherEdgeMid = basePolyHigher ? basePolyEdgeMid : landPolyEdgeMid;
|
||||
float* const lowerEdgeDir = basePolyHigher ? landEdgeDir : baseEdgeDir;
|
||||
float* const higherEdgeDir = basePolyHigher ? baseEdgeDir : landEdgeDir;
|
||||
|
||||
const float walkableRadius = basePolyHigher ? baseHeader->walkableRadius : landHeader->walkableRadius;
|
||||
float offsetAmount;
|
||||
|
||||
if (m_traverseRayDynamicOffset)
|
||||
{
|
||||
const float totLedgeSpan = walkableRadius + m_traverseRayExtraOffset;
|
||||
const float maxAngle = rdCalcMaxLOSAngle(totLedgeSpan, m_cellHeight);
|
||||
|
||||
offsetAmount = rdCalcLedgeSpanOffsetAmount(totLedgeSpan, slopeAngle, maxAngle);
|
||||
}
|
||||
else
|
||||
offsetAmount = walkableRadius + m_traverseRayExtraOffset;
|
||||
|
||||
if (!traverseLinkInLOS(m_geom, lowerEdgeMid, higherEdgeMid, lowerEdgeDir, higherEdgeDir, offsetAmount))
|
||||
continue;
|
||||
|
||||
const unsigned char landSide = linkToNeighbor
|
||||
? rdClassifyPointOutsideBounds(landPolyEdgeMid, landHeader->bmin, landHeader->bmax)
|
||||
: rdClassifyPointInsideBounds(landPolyEdgeMid, landHeader->bmin, landHeader->bmax);
|
||||
|
||||
const unsigned int forwardIdx = baseTile->allocLink();
|
||||
const unsigned int reverseIdx = landTile->allocLink();
|
||||
|
||||
// Allocated 2 new links, need to check for enough space on subsequent runs.
|
||||
// This optimization saves a lot of time generating navmeshes for larger or
|
||||
// more complicated geometry.
|
||||
firstBaseTileLinkUsed = true;
|
||||
firstLandTileLinkUsed = true;
|
||||
|
||||
// Calculate the portal limits.
|
||||
|
||||
dtLink* const forwardLink = &baseTile->links[forwardIdx];
|
||||
|
||||
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->next = basePoly->firstLink;
|
||||
basePoly->firstLink = forwardIdx;
|
||||
forwardLink->traverseType = (unsigned char)traverseType;
|
||||
forwardLink->traverseDist = quantDist;
|
||||
forwardLink->reverseLink = (unsigned short)reverseIdx;
|
||||
|
||||
dtLink* const reverseLink = &landTile->links[reverseIdx];
|
||||
|
||||
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->next = landPoly->firstLink;
|
||||
landPoly->firstLink = reverseIdx;
|
||||
reverseLink->traverseType = (unsigned char)traverseType;
|
||||
reverseLink->traverseDist = quantDist;
|
||||
reverseLink->reverseLink = (unsigned short)forwardIdx;
|
||||
|
||||
if (traverseLinkFound)
|
||||
linkedIt->second |= 1 << traverseType;
|
||||
else
|
||||
m_traverseLinkPolyMap.emplace(linkedPolyPair, 1 << traverseType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
params.userData = this;
|
||||
params.minEdgeOverlap = m_traverseEdgeMinOverlap;
|
||||
}
|
||||
|
||||
bool Editor::createTraverseLinks()
|
||||
@ -1021,6 +749,9 @@ bool Editor::createTraverseLinks()
|
||||
rdAssert(m_navMesh);
|
||||
m_traverseLinkPolyMap.clear();
|
||||
|
||||
dtTraverseLinkConnectParams params;
|
||||
createTraverseLinkParams(params);
|
||||
|
||||
const int maxTiles = m_navMesh->getMaxTiles();
|
||||
|
||||
for (int i = 0; i < maxTiles; i++)
|
||||
@ -1029,8 +760,12 @@ bool Editor::createTraverseLinks()
|
||||
if (!baseTile || !baseTile->header)
|
||||
continue;
|
||||
|
||||
connectTileTraverseLinks(baseTile, false);
|
||||
connectTileTraverseLinks(baseTile, true);
|
||||
const dtTileRef baseTileRef = m_navMesh->getTileRef(baseTile);
|
||||
|
||||
params.linkToNeighbor = false;
|
||||
m_navMesh->connectTraverseLinks(baseTileRef, params);
|
||||
params.linkToNeighbor = true;
|
||||
m_navMesh->connectTraverseLinks(baseTileRef, params);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -735,11 +735,14 @@ void Editor_TileMesh::buildTile(const float* pos)
|
||||
}
|
||||
}
|
||||
|
||||
dtMeshTile* tile = (dtMeshTile*)m_navMesh->getTileByRef(tileRef);
|
||||
|
||||
// Reconnect the traverse links.
|
||||
connectTileTraverseLinks(tile, false);
|
||||
connectTileTraverseLinks(tile, true);
|
||||
dtTraverseLinkConnectParams params;
|
||||
createTraverseLinkParams(params);
|
||||
|
||||
params.linkToNeighbor = false;
|
||||
m_navMesh->connectTraverseLinks(tileRef, params);
|
||||
params.linkToNeighbor = true;
|
||||
m_navMesh->connectTraverseLinks(tileRef, params);
|
||||
|
||||
buildStaticPathingData();
|
||||
}
|
||||
|
@ -359,6 +359,8 @@ public:
|
||||
virtual float getAgentHeight() { return m_agentHeight; }
|
||||
virtual float getAgentClimb() { return m_agentMaxClimb; }
|
||||
|
||||
inline float getCellHeight() const { return m_cellHeight; }
|
||||
|
||||
inline unsigned int getNavMeshDrawFlags() const { return m_navMeshDrawFlags; }
|
||||
inline void setNavMeshDrawFlags(unsigned int flags) { m_navMeshDrawFlags = flags; }
|
||||
|
||||
@ -367,6 +369,11 @@ public:
|
||||
inline NavMeshType_e getSelectedNavMeshType() const { return m_selectedNavMeshType; }
|
||||
inline NavMeshType_e getLoadedNavMeshType() const { return m_loadedNavMeshType; }
|
||||
|
||||
inline bool useDynamicTraverseRayOffset() const { return m_traverseRayDynamicOffset; }
|
||||
inline float getTraverseRayExtraOffset() const { return m_traverseRayExtraOffset; }
|
||||
|
||||
inline std::map<TraverseLinkPolyPair, unsigned int>& getTraverseLinkPolyMap() { return m_traverseLinkPolyMap; }
|
||||
|
||||
inline const char* getModelName() const { return m_modelName.c_str(); }
|
||||
|
||||
void updateToolStates(const float dt);
|
||||
@ -388,6 +395,8 @@ public:
|
||||
void connectTileTraverseLinks(dtMeshTile* const baseTile, const bool linkToNeighbor); // Make private.
|
||||
bool createTraverseLinks();
|
||||
|
||||
void createTraverseLinkParams(dtTraverseLinkConnectParams& params);
|
||||
|
||||
void createTraverseTableParams(dtTraverseTableCreateParams* params);
|
||||
|
||||
void connectOffMeshLinks();
|
||||
|
@ -496,6 +496,59 @@ private:
|
||||
dtMeshTile& operator=(const dtMeshTile&);
|
||||
};
|
||||
|
||||
/// Configuration parameters used to create traverse links between polygon edges.
|
||||
/// @ingroup detour
|
||||
struct dtTraverseLinkConnectParams
|
||||
{
|
||||
/// User defined callback that returns the desired traverse type based on
|
||||
/// the provided spatial and logical characteristics of this potential link.
|
||||
/// @param[in] userData Pointer to user defined data.
|
||||
/// @param[in] traverseDist The total distance in length of the traverse link. [Unit: wu]
|
||||
/// @param[in] elevation The elevation difference between base and land position. [Unit: wu]
|
||||
/// @param[in] slopeAngle The slope angle from base to land position. [Unit: Degrees]
|
||||
/// @param[in] baseOverlaps Whether the projection of the base edge overlaps with the land edge.
|
||||
/// @param[in] landOverlaps Whether the projection of the land edge overlaps with the base edge.
|
||||
/// @return The desired traverse type for provided spatial and logical characteristics.
|
||||
unsigned char(*getTraverseType)(void* userData, const float traverseDist, const float elevation,
|
||||
const float slopeAngle, const bool baseOverlaps, const bool landOverlaps);
|
||||
|
||||
/// User defined callback that returns whether a traverse link based on
|
||||
/// provided spatial characteristics is clear in terms of line-of-sight.
|
||||
/// @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] 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.
|
||||
bool(*traverseLinkInLOS)(void* userData, const float* lowerEdgeMid, const float* higherEdgeMid,
|
||||
const float* lowerEdgeDir, const float* higherEdgeDir, const float walkableRadius, const float slopeAngle);
|
||||
|
||||
/// User defined callback that looks if a link between these 2 polygons
|
||||
/// have already been established. A traverse type can only be used once
|
||||
/// between 2 polygons, but the 2 polygons can have more than one link.
|
||||
/// @param[in] userData Pointer to user defined data.
|
||||
/// @param[in] basePolyRef The reference of the polygon on the base tile.
|
||||
/// @param[in] landPolyRef The reference of the polygon on the land tile.
|
||||
/// @return Pointer to the bit cell, null if no link was found.
|
||||
unsigned int*(*findPolyLink)(void* userData, const dtPolyRef basePolyRef, const dtPolyRef landPolyRef);
|
||||
|
||||
/// User defined callback that adds a new polygon pair to the list. On
|
||||
/// subsequent lookups, the bit cell of this pair should be returned and
|
||||
/// used instead, see #findPolyLink.
|
||||
/// @param[in] userData Pointer to user defined data.
|
||||
/// @param[in] basePolyRef The reference of the polygon on the base tile.
|
||||
/// @param[in] landPolyRef The reference of the polygon on the land tile.
|
||||
/// @param[in] traverseTypeBit The traverse type bit index to be stored initially in the bit cell.
|
||||
/// @return -1 if out-of-memory, 1 if link was already present, 0 on success.
|
||||
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.
|
||||
bool linkToNeighbor; ///< Whether to link to polygons in neighboring tiles. Limits linkage to internal polygons if false.
|
||||
};
|
||||
|
||||
/// Configuration parameters used to define multi-tile navigation meshes.
|
||||
/// The values are used to allocate space during the initialization of a navigation mesh.
|
||||
/// @see dtNavMesh::init()
|
||||
@ -858,6 +911,8 @@ public:
|
||||
int getNeighbourTilesAt(const int x, const int y, const int side,
|
||||
dtMeshTile** tiles, const int maxTiles) const;
|
||||
|
||||
/// Builds external polygon links for a tile.
|
||||
dtStatus connectTraverseLinks(const dtTileRef tileRef, const dtTraverseLinkConnectParams& params);
|
||||
/// Builds external polygon links for a tile.
|
||||
dtStatus connectExtOffMeshLinks(const dtTileRef tileRef);
|
||||
/// Builds internal polygons links for a tile.
|
||||
|
@ -735,6 +735,316 @@ dtStatus dtNavMesh::baseOffMeshLinks(const dtTileRef tileRef)
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
dtStatus dtNavMesh::connectTraverseLinks(const dtTileRef tileRef, const dtTraverseLinkConnectParams& params)
|
||||
{
|
||||
const int tileIndex = (int)decodePolyIdTile((dtPolyRef)tileRef);
|
||||
if (tileIndex >= m_maxTiles)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
|
||||
dtMeshTile* baseTile = &m_tiles[tileIndex];
|
||||
const dtMeshHeader* baseHeader = baseTile->header;
|
||||
|
||||
if (!baseHeader)
|
||||
return DT_FAILURE | DT_INVALID_PARAM; // Invalid tile.
|
||||
|
||||
if (!baseHeader->detailMeshCount)
|
||||
return DT_FAILURE | DT_INVALID_PARAM; // Detail meshes are required for traverse links.
|
||||
|
||||
// If we link to the same tile, we need at least 2 links.
|
||||
if (!baseTile->linkCountAvailable(params.linkToNeighbor ? 1 : 2))
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
|
||||
static const float detailEdgeAlignThresh = 0.01f*0.01f;
|
||||
|
||||
const dtPolyRef basePolyRefBase = getPolyRefBase(baseTile);
|
||||
bool firstBaseTileLinkUsed = false;
|
||||
|
||||
for (int i = 0; i < baseHeader->polyCount; ++i)
|
||||
{
|
||||
dtPoly* const basePoly = &baseTile->polys[i];
|
||||
|
||||
if (basePoly->groupId == DT_UNLINKED_POLY_GROUP)
|
||||
continue;
|
||||
|
||||
if (basePoly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
|
||||
continue;
|
||||
|
||||
dtPolyDetail* const baseDetail = &baseTile->detailMeshes[i];
|
||||
|
||||
for (int j = 0; j < basePoly->vertCount; ++j)
|
||||
{
|
||||
// Hard edges only!
|
||||
if (basePoly->neis[j] != 0)
|
||||
continue;
|
||||
|
||||
// Polygon 1 edge
|
||||
const float* const basePolySpos = &baseTile->verts[basePoly->verts[j]*3];
|
||||
const float* const basePolyEpos = &baseTile->verts[basePoly->verts[(j+1)%basePoly->vertCount]*3];
|
||||
|
||||
for (int k = 0; k < baseDetail->triCount; ++k)
|
||||
{
|
||||
const unsigned char* baseTri = &baseTile->detailTris[(baseDetail->triBase+k)*4];
|
||||
const float* baseTriVerts[3];
|
||||
for (int l = 0; l < 3; ++l)
|
||||
{
|
||||
if (baseTri[l] < basePoly->vertCount)
|
||||
baseTriVerts[l] = &baseTile->verts[basePoly->verts[baseTri[l]]*3];
|
||||
else
|
||||
baseTriVerts[l] = &baseTile->detailVerts[(baseDetail->vertBase+(baseTri[l]-basePoly->vertCount))*3];
|
||||
}
|
||||
for (int l = 0, m = 2; l < 3; m = l++)
|
||||
{
|
||||
if ((dtGetDetailTriEdgeFlags(baseTri[3], m) & DT_DETAIL_EDGE_BOUNDARY) == 0)
|
||||
continue;
|
||||
|
||||
if (rdDistancePtLine2D(baseTriVerts[m], basePolySpos, basePolyEpos) >= detailEdgeAlignThresh ||
|
||||
rdDistancePtLine2D(baseTriVerts[l], basePolySpos, basePolyEpos) >= detailEdgeAlignThresh)
|
||||
continue;
|
||||
|
||||
const float* baseDetailPolyEdgeSpos = baseTriVerts[m];
|
||||
const float* baseDetailPolyEdgeEpos = baseTriVerts[l];
|
||||
|
||||
float baseTmin;
|
||||
float baseTmax;
|
||||
if (!rdCalcSubEdgeArea2D(basePolySpos, basePolyEpos, baseDetailPolyEdgeSpos, baseDetailPolyEdgeEpos, baseTmin, baseTmax))
|
||||
continue;
|
||||
|
||||
float baseEdgeDir[3];
|
||||
rdVsub(baseEdgeDir, baseDetailPolyEdgeEpos, baseDetailPolyEdgeSpos);
|
||||
|
||||
unsigned char baseSide = rdClassifyDirection(baseEdgeDir, baseHeader->bmin, baseHeader->bmax);
|
||||
|
||||
const int MAX_NEIS = 32; // Max neighbors
|
||||
dtMeshTile* neis[MAX_NEIS];
|
||||
|
||||
int nneis = 0;
|
||||
|
||||
if (params.linkToNeighbor) // Retrieve the neighboring tiles on the side of our base poly edge.
|
||||
{
|
||||
nneis = getNeighbourTilesAt(baseHeader->x, baseHeader->y, baseSide, neis, MAX_NEIS);
|
||||
|
||||
// No neighbors, nothing to link to on this side.
|
||||
if (!nneis)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Internal links.
|
||||
nneis = 1;
|
||||
neis[0] = baseTile;
|
||||
}
|
||||
|
||||
float basePolyEdgeMid[3];
|
||||
if (nneis)
|
||||
rdVsad(basePolyEdgeMid, baseDetailPolyEdgeSpos, baseDetailPolyEdgeEpos, 0.5f);
|
||||
|
||||
for (int n = nneis - 1; n >= 0; --n)
|
||||
{
|
||||
dtMeshTile* landTile = neis[n];
|
||||
const bool sameTile = baseTile == landTile;
|
||||
|
||||
// Don't connect to same tile edges yet, leave that for the second pass.
|
||||
if (params.linkToNeighbor && sameTile)
|
||||
continue;
|
||||
|
||||
const dtMeshHeader* landHeader = landTile->header;
|
||||
|
||||
if (!landHeader->detailMeshCount)
|
||||
continue; // Detail meshes are required for traverse links.
|
||||
|
||||
// Skip same polygon.
|
||||
if (sameTile && i == n)
|
||||
continue;
|
||||
|
||||
if (!landTile->linkCountAvailable(1))
|
||||
continue;
|
||||
|
||||
const dtPolyRef landPolyRefBase = getPolyRefBase(landTile);
|
||||
bool firstLandTileLinkUsed = false;
|
||||
|
||||
for (int o = 0; o < landHeader->polyCount; ++o)
|
||||
{
|
||||
dtPoly* const landPoly = &landTile->polys[o];
|
||||
|
||||
if (landPoly->groupId == DT_UNLINKED_POLY_GROUP)
|
||||
continue;
|
||||
|
||||
if (landPoly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
|
||||
continue;
|
||||
|
||||
dtPolyDetail* const landDetail = &landTile->detailMeshes[o];
|
||||
|
||||
for (int p = 0; p < landPoly->vertCount; ++p)
|
||||
{
|
||||
if (landPoly->neis[p] != 0)
|
||||
continue;
|
||||
|
||||
// Polygon 2 edge
|
||||
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)
|
||||
{
|
||||
const unsigned char* landTri = &landTile->detailTris[(landDetail->triBase+q)*4];
|
||||
const float* landTriVerts[3];
|
||||
for (int r = 0; r < 3; ++r)
|
||||
{
|
||||
if (landTri[r] < landPoly->vertCount)
|
||||
landTriVerts[r] = &landTile->verts[landPoly->verts[landTri[r]]*3];
|
||||
else
|
||||
landTriVerts[r] = &landTile->detailVerts[(landDetail->vertBase+(landTri[r]-landPoly->vertCount))*3];
|
||||
}
|
||||
for (int r = 0, s = 2; r < 3; s = r++)
|
||||
{
|
||||
// We need at least 2 links available, figure out if
|
||||
// we link to the same tile or another one.
|
||||
if (params.linkToNeighbor)
|
||||
{
|
||||
if (firstLandTileLinkUsed && !landTile->linkCountAvailable(1))
|
||||
continue;
|
||||
|
||||
else if (firstBaseTileLinkUsed && !baseTile->linkCountAvailable(1))
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
}
|
||||
else if (firstBaseTileLinkUsed && !baseTile->linkCountAvailable(2))
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
|
||||
if ((dtGetDetailTriEdgeFlags(landTri[3], s) & DT_DETAIL_EDGE_BOUNDARY) == 0)
|
||||
continue;
|
||||
|
||||
if (rdDistancePtLine2D(landTriVerts[s], landPolySpos, landPolyEpos) >= detailEdgeAlignThresh ||
|
||||
rdDistancePtLine2D(landTriVerts[r], landPolySpos, landPolyEpos) >= detailEdgeAlignThresh)
|
||||
continue;
|
||||
|
||||
const float* landDetailPolyEdgeSpos = landTriVerts[s];
|
||||
const float* landDetailPolyEdgeEpos = landTriVerts[r];
|
||||
|
||||
float landTmin;
|
||||
float landTmax;
|
||||
if (!rdCalcSubEdgeArea2D(landPolySpos, landPolyEpos, landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, landTmin, landTmax))
|
||||
continue;
|
||||
|
||||
float landPolyEdgeMid[3];
|
||||
rdVsad(landPolyEdgeMid, landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, 0.5f);
|
||||
|
||||
const float dist = dtCalcLinkDistance(basePolyEdgeMid, landPolyEdgeMid);
|
||||
const unsigned char quantDist = dtQuantLinkDistance(dist);
|
||||
|
||||
if (quantDist == 0)
|
||||
continue; // Link distance is greater than maximum supported.
|
||||
|
||||
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;
|
||||
const bool landOverlaps = rdCalcEdgeOverlap2D(landDetailPolyEdgeSpos, landDetailPolyEdgeEpos, baseDetailPolyEdgeSpos, baseDetailPolyEdgeEpos, landEdgeDir) > params.minEdgeOverlap;
|
||||
|
||||
const unsigned char traverseType = params.getTraverseType(params.userData, dist, elevation, slopeAngle, baseOverlaps, landOverlaps);
|
||||
|
||||
if (traverseType == DT_NULL_TRAVERSE_TYPE)
|
||||
continue;
|
||||
|
||||
const dtPolyRef basePolyRef = basePolyRefBase | i;
|
||||
const dtPolyRef landPolyRef = landPolyRefBase | o;
|
||||
|
||||
unsigned int* linkedTraverseType = params.findPolyLink(params.userData, basePolyRef, landPolyRef);
|
||||
|
||||
// These 2 polygons are already linked with the same traverse type.
|
||||
if (linkedTraverseType && (rdBitCellBit(traverseType) & *linkedTraverseType))
|
||||
continue;
|
||||
|
||||
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 walkableRadius = basePolyHigher ? baseHeader->walkableRadius : landHeader->walkableRadius;
|
||||
|
||||
if (!params.traverseLinkInLOS(params.userData, lowerEdgeMid, higherEdgeMid, lowerEdgeDir, higherEdgeDir, walkableRadius, slopeAngle))
|
||||
continue;
|
||||
|
||||
const unsigned char landSide = params.linkToNeighbor
|
||||
? rdClassifyPointOutsideBounds(landPolyEdgeMid, landHeader->bmin, landHeader->bmax)
|
||||
: rdClassifyPointInsideBounds(landPolyEdgeMid, landHeader->bmin, landHeader->bmax);
|
||||
|
||||
const unsigned int forwardIdx = baseTile->allocLink();
|
||||
const unsigned int reverseIdx = landTile->allocLink();
|
||||
|
||||
// Allocated 2 new links, need to check for enough space on subsequent runs.
|
||||
// This optimization saves a lot of time generating navmeshes for larger or
|
||||
// more complicated geometry.
|
||||
firstBaseTileLinkUsed = true;
|
||||
firstLandTileLinkUsed = true;
|
||||
|
||||
dtLink* const forwardLink = &baseTile->links[forwardIdx];
|
||||
|
||||
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->next = basePoly->firstLink;
|
||||
basePoly->firstLink = forwardIdx;
|
||||
forwardLink->traverseType = (unsigned char)traverseType;
|
||||
forwardLink->traverseDist = quantDist;
|
||||
forwardLink->reverseLink = (unsigned short)reverseIdx;
|
||||
|
||||
dtLink* const reverseLink = &landTile->links[reverseIdx];
|
||||
|
||||
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->next = landPoly->firstLink;
|
||||
landPoly->firstLink = reverseIdx;
|
||||
reverseLink->traverseType = (unsigned char)traverseType;
|
||||
reverseLink->traverseDist = quantDist;
|
||||
reverseLink->reverseLink = (unsigned short)forwardIdx;
|
||||
|
||||
if (linkedTraverseType)
|
||||
*linkedTraverseType |= 1<<traverseType;
|
||||
else
|
||||
{
|
||||
const int ret = params.addPolyLink(params.userData, basePolyRef, landPolyRef, 1<<traverseType);
|
||||
|
||||
if (ret < 0)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
if (ret > 0)
|
||||
return DT_FAILURE | DT_INVALID_PARAM;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template<bool onlyBoundary>
|
||||
|
@ -487,15 +487,27 @@ void rdCalcEdgeNormal2D(const float* dir, float* out);
|
||||
/// @param[out] out The resulting normal. [(x, y)]
|
||||
void rdCalcEdgeNormalPt2D(const float* v1, const float* v2, float* out);
|
||||
|
||||
/// Derives the sub-edge area of an edge.
|
||||
/// @param[in] edgeStart First vert of the polygon edge. [(x, y, z)]
|
||||
/// @param[in] edgeEnd Second vert of the polygon edge. [(x, y, z)]
|
||||
/// @param[in] subEdgeStart First vert of the detail edge. [(x, y, z)]
|
||||
/// @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,
|
||||
const float* subEdgeEnd, float& tmin, float& tmax);
|
||||
|
||||
/// Derives the overlap between 2 edges.
|
||||
/// @param[in] edge1Start Start vert of the first edge. [(x, y, z)]
|
||||
/// @param[in] edge1End End vert of the first edge. [(x, y, z)]
|
||||
/// @param[in] edge2Start Start vert of the second edge. [(x, y, z)]
|
||||
/// @param[in] edge2End End vert of the second edge. [(x, y, z)]
|
||||
/// @param[in] targetEdgeVec The projection direction. [(x, y, z)]
|
||||
/// @return The length of the overlap.
|
||||
float rdCalcEdgeOverlap2D(const float* edge1Start, const float* edge1End,
|
||||
const float* edge2Start, const float* edge2End, const float* targetEdgeVec);
|
||||
|
||||
/// Derives the maximum angle in which an object on an elevated surface can be seen from below.
|
||||
/// @param[in] ledgeSpan The distance between the edge of the object and the edge of the ledge.
|
||||
/// @param[in] objectHeight The height of the object.
|
||||
|
@ -445,6 +445,27 @@ bool rdCalcSubEdgeArea2D(const float* edgeStart, const float* edgeEnd, const flo
|
||||
return true;
|
||||
}
|
||||
|
||||
float rdCalcEdgeOverlap2D(const float* edge1Start, const float* edge1End,
|
||||
const float* edge2Start, const float* edge2End, const float* targetEdgeVec)
|
||||
{
|
||||
float min1 = rdVproj2D(edge1Start, targetEdgeVec);
|
||||
float max1 = rdVproj2D(edge1End, targetEdgeVec);
|
||||
|
||||
if (min1 > max1)
|
||||
rdSwap(min1, max1);
|
||||
|
||||
float min2 = rdVproj2D(edge2Start, targetEdgeVec);
|
||||
float max2 = rdVproj2D(edge2End, targetEdgeVec);
|
||||
|
||||
if (min2 > max2)
|
||||
rdSwap(min2, max2);
|
||||
|
||||
const float start = rdMax(min1, min2);
|
||||
const float end = rdMin(max1, max2);
|
||||
|
||||
return rdMax(0.0f, end - start);
|
||||
}
|
||||
|
||||
float rdCalcMaxLOSAngle(const float ledgeSpan, const float objectHeight)
|
||||
{
|
||||
const float angleRad = rdMathAtan2f(objectHeight, ledgeSpan);
|
||||
|
Loading…
x
Reference in New Issue
Block a user