diff --git a/src/naveditor/Editor.cpp b/src/naveditor/Editor.cpp index fd1f411d..5796af9a 100644 --- a/src/naveditor/Editor.cpp +++ b/src/naveditor/Editor.cpp @@ -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; + return &it->second; +} - static const float detailEdgeAlignThresh = 0.01f*0.01f; +static int addToPolyMap(void* userData, const dtPolyRef basePolyRef, const dtPolyRef landPolyRef, const unsigned int traverseTypeBit) +{ + Editor* editor = (Editor*)userData; - const dtPolyRef basePolyRefBase = m_navMesh->getPolyRefBase(baseTile); - bool firstBaseTileLinkUsed = false; - - for (int i = 0; i < baseHeader->polyCount; ++i) + try { - 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) + auto ret = editor->getTraverseLinkPolyMap().emplace(TraverseLinkPolyPair(basePolyRef, landPolyRef), traverseTypeBit); + if (!ret.second) { - // 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; - } - - 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 (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) - { - 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 (linkToNeighbor) - { - if (firstLandTileLinkUsed && !landTile->linkCountAvailable(1)) - continue; - - 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); - } - } - } - } - } - } - } + rdAssert(ret.second); // Called 'addToPolyMap' while poly link already exists. + return 1; } } + catch (const std::bad_alloc& /*e*/) + { + return -1; + } + + return 0; +} + +void Editor::createTraverseLinkParams(dtTraverseLinkConnectParams& params) +{ + params.getTraverseType = &GetBestTraverseType; + params.traverseLinkInLOS = &traverseLinkInLOS; + params.findPolyLink = &findFromPolyMap; + params.addPolyLink = &addToPolyMap; + + 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; diff --git a/src/naveditor/Editor_TileMesh.cpp b/src/naveditor/Editor_TileMesh.cpp index 8b1bed74..8c02bc6f 100644 --- a/src/naveditor/Editor_TileMesh.cpp +++ b/src/naveditor/Editor_TileMesh.cpp @@ -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(); } diff --git a/src/naveditor/include/Editor.h b/src/naveditor/include/Editor.h index e76c8a81..444f963e 100644 --- a/src/naveditor/include/Editor.h +++ b/src/naveditor/include/Editor.h @@ -358,6 +358,8 @@ public: virtual float getAgentRadius() { return m_agentRadius; } 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& 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(); diff --git a/src/thirdparty/recast/Detour/Include/DetourNavMesh.h b/src/thirdparty/recast/Detour/Include/DetourNavMesh.h index 856aabbd..be86bbc7 100644 --- a/src/thirdparty/recast/Detour/Include/DetourNavMesh.h +++ b/src/thirdparty/recast/Detour/Include/DetourNavMesh.h @@ -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. diff --git a/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp b/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp index 75bd742d..f93f6d86 100644 --- a/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp +++ b/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp @@ -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< 0) + return DT_FAILURE | DT_INVALID_PARAM; + } + } + } + } + } + } + } + } + } + } + + return DT_SUCCESS; +} + namespace { template diff --git a/src/thirdparty/recast/Shared/Include/SharedCommon.h b/src/thirdparty/recast/Shared/Include/SharedCommon.h index 15ee3e4f..d581e108 100644 --- a/src/thirdparty/recast/Shared/Include/SharedCommon.h +++ b/src/thirdparty/recast/Shared/Include/SharedCommon.h @@ -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. diff --git a/src/thirdparty/recast/Shared/Source/SharedCommon.cpp b/src/thirdparty/recast/Shared/Source/SharedCommon.cpp index c761ddc2..5b18f0f3 100644 --- a/src/thirdparty/recast/Shared/Source/SharedCommon.cpp +++ b/src/thirdparty/recast/Shared/Source/SharedCommon.cpp @@ -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);