From 3e7ca699e02e5a46ae916cb55fb0988136e5da92 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Tue, 13 Aug 2024 01:30:22 +0200 Subject: [PATCH] Recast: properly implement edge raycast offsets The upper pos needs to be offset forward with at least the agent's radius since for jumps, we don't take the gap between the ledge and navmesh into account. --- src/naveditor/Editor.cpp | 86 ++++++++++++++----- .../DebugUtils/Include/DetourDebugDraw.h | 3 +- .../DebugUtils/Source/DetourDebugDraw.cpp | 35 ++++++-- 3 files changed, 96 insertions(+), 28 deletions(-) diff --git a/src/naveditor/Editor.cpp b/src/naveditor/Editor.cpp index d42c7bda..1c279403 100644 --- a/src/naveditor/Editor.cpp +++ b/src/naveditor/Editor.cpp @@ -533,28 +533,62 @@ static bool traverseLinkInPolygon(const dtMeshTile* tile, const float* midPoint) return false; } -static bool traverseLinkInLOS(InputGeom* geom, const float* basePos, const float* landPos, const float heightOffset) +static bool traverseLinkInLOS(InputGeom* geom, const float* lowPos, const float* highPos, const float* edgeDir, const float offsetAmount) { - float rayBasePos[3]; - float rayLandPos[3]; + // We offset the highest point with at least the + // walkable radius, and perform a raycast test + // from the highest point to the lowest. The + // offsetting is necessary to account for the + // gap between the edge of the navmesh and the + // edge of the geometry shown below: + // + // geom upper navmesh + // ^ ^ + // gap | | + // ^ | | + // | | | + // offset <-----> | +++++++++++++... + // / =======================... + // / + // ray <----/ lower navmesh + // / ^ + // geom / | + // ^ / | + // | +++++++++++++++++++++++++++++++... + // ========================================... + // + // We only want the raycast test to fail if the + // ledge is larger than usual, when the low and + // high positions are angled in such way no LOS + // is possible, or when there's an actual object + // between the 2 positions. + float perp[3]; + rdPerpDirEdge2D(edgeDir, false, perp); + + float targetRayPos[3] = { + highPos[0] + perp[0] * offsetAmount, + highPos[1] + perp[1] * offsetAmount, + highPos[2] + }; float hitTime; - rdVcopy(rayBasePos, basePos); - rdVcopy(rayLandPos, landPos); - - // note(amos): offset the ray heights so we don't clip into - // ledges. Realistically, we need at least the height of the - // agent anyways to be able to properly perform a jump - // without clipping into geometry. - rayBasePos[2] += heightOffset; - rayLandPos[2] += heightOffset; - // note(amos): perform 2 raycasts as we have to take the // face normal into account. Path must be clear from both - // directions. - if (geom->raycastMesh(rayBasePos, rayLandPos, hitTime) || - geom->raycastMesh(rayLandPos, rayBasePos, hitTime)) + // directions. We cast from the upper position first as + // an optimization attempt because if there's a ledge, the + // raycast test from the lower pos is more likely to pass + // due to mesh normals, e.g. when the higher mesh is generated + // on a single sided plane, and we have a ledge between our + // lower and higher pos, the test from below will pass while + // the test from above won't. Doing the test from below won't + // matter besides burning CPU time as we will never get here if + // the mesh normals of that plane were flipped as there + // won't be any navmesh on the higher pos in the first place. + // Its still possible there's something blocking on the lower + // pos' side, but this is a lot less likely to happen. + if (geom->raycastMesh(targetRayPos, lowPos, hitTime) || + geom->raycastMesh(lowPos, targetRayPos, hitTime)) return false; return true; @@ -626,14 +660,15 @@ void Editor::connectTileTraverseLinks(dtMeshTile* const baseTile, const bool lin if (distance == 0) continue; + float baseEdgeDir[3], landEdgeDir[3]; + rdVsub(baseEdgeDir, basePolyEpos, basePolySpos); + rdVsub(landEdgeDir, landPolyEpos, landPolySpos); + // todo(amos): use height difference instead of slope angles. const float slopeAngle = rdMathFabsf(rdCalcSlopeAngle(basePolyEdgeMid, landPolyEdgeMid)); if (slopeAngle < TRAVERSE_OVERLAP_SLOPE_THRESHOLD) { - float baseEdgeDir[3], landEdgeDir[3]; - rdVsub(baseEdgeDir, basePolyEpos, basePolySpos); - rdVsub(landEdgeDir, landPolyEpos, landPolySpos); const float dotProduct = rdVdot(baseEdgeDir, landEdgeDir); @@ -672,7 +707,13 @@ void Editor::connectTileTraverseLinks(dtMeshTile* const baseTile, const bool lin continue; } - if (!traverseLinkInLOS(m_geom, basePolyEdgeMid, landPolyEdgeMid, m_agentHeight)) + const bool basePolyHigher = basePolyEdgeMid[2] > landPolyEdgeMid[2]; + float* const lowerEdgeMid = basePolyHigher ? landPolyEdgeMid : basePolyEdgeMid; + float* const higherEdgeMid = basePolyHigher ? basePolyEdgeMid : landPolyEdgeMid; + float* const higherEdgeDir = basePolyHigher ? baseEdgeDir : landEdgeDir; + float walkableRadius = basePolyHigher ? baseTile->header->walkableRadius : landTile->header->walkableRadius; + + if (!traverseLinkInLOS(m_geom, lowerEdgeMid, higherEdgeMid, higherEdgeDir, walkableRadius)) continue; // Need at least 2 links @@ -907,6 +948,11 @@ void Editor::renderDetourDebugMenu() if (ImGui::Checkbox("Transparency", &isEnabled)) toggleNavMeshDrawFlag(DU_DRAWNAVMESH_ALPHA); + isEnabled = (getNavMeshDrawFlags() & DU_DRAWNAVMESH_TRAVERSE_RAY_OFFSET); + + if (ImGui::Checkbox("Traverse Ray Offsets", &isEnabled)) + toggleNavMeshDrawFlag(DU_DRAWNAVMESH_TRAVERSE_RAY_OFFSET); + isEnabled = (getNavMeshDrawFlags() & DU_DRAWNAVMESH_TRAVERSE_LINKS); if (ImGui::Checkbox("Traverse Links", &isEnabled)) diff --git a/src/thirdparty/recast/DebugUtils/Include/DetourDebugDraw.h b/src/thirdparty/recast/DebugUtils/Include/DetourDebugDraw.h index 5093739a..a2b865fc 100644 --- a/src/thirdparty/recast/DebugUtils/Include/DetourDebugDraw.h +++ b/src/thirdparty/recast/DebugUtils/Include/DetourDebugDraw.h @@ -40,7 +40,8 @@ enum DrawNavMeshFlags DU_DRAWNAVMESH_POLY_GROUPS = 1 << 12, // Render poly group by color. DU_DRAWNAVMESH_DEPTH_MASK = 1 << 13, // Use depth mask. DU_DRAWNAVMESH_ALPHA = 1 << 14, // Use transparency. - DU_DRAWNAVMESH_TRAVERSE_LINKS = 1 << 15, // Render traverse links. + DU_DRAWNAVMESH_TRAVERSE_RAY_OFFSET= 1 << 15, // Render traverse link raycast offset. + DU_DRAWNAVMESH_TRAVERSE_LINKS = 1 << 16, // Render traverse links. }; struct duDrawTraverseLinkParams diff --git a/src/thirdparty/recast/DebugUtils/Source/DetourDebugDraw.cpp b/src/thirdparty/recast/DebugUtils/Source/DetourDebugDraw.cpp index a13510ec..a940a96d 100644 --- a/src/thirdparty/recast/DebugUtils/Source/DetourDebugDraw.cpp +++ b/src/thirdparty/recast/DebugUtils/Source/DetourDebugDraw.cpp @@ -46,13 +46,16 @@ static void drawOffMeshConnectionRefPosition(duDebugDraw* dd, const dtOffMeshCon } static void drawPolyBoundaries(duDebugDraw* dd, const dtMeshTile* tile, - const float linew, const float* offset, bool inner) + const float linew, const float* offset, const int flags, bool inner) { - static const float thr = 0.01f*0.01f; - dd->begin(DU_DRAW_LINES, linew, offset); - for (int i = 0; i < tile->header->polyCount; ++i) + const dtMeshHeader* header = tile->header; + const float walkableRadius = header->walkableRadius; + + static const float thr = 0.01f*0.01f; + + for (int i = 0; i < header->polyCount; ++i) { const dtPoly* p = &tile->polys[i]; @@ -92,7 +95,25 @@ static void drawPolyBoundaries(duDebugDraw* dd, const dtMeshTile* tile, const float* v0 = &tile->verts[p->verts[j]*3]; const float* v1 = &tile->verts[p->verts[(j+1) % nj]*3]; - + + if (!inner && flags & DU_DRAWNAVMESH_TRAVERSE_RAY_OFFSET) + { + float perp[3]; + rdPerpDirPtEdge2D(v0, v1, false, perp); + + float mid[3]; + rdVsad(mid, v0, v1, 0.5f); + + float perpEnd[3] = { + mid[0] + perp[0] * walkableRadius, + mid[1] + perp[1] * walkableRadius, + mid[2] + }; + + dd->vertex(mid, c); + dd->vertex(perpEnd, c); + } + // Draw detail mesh edges which align with the actual poly edge. // This is really slow. for (int k = 0; k < pd->triCount; ++k) @@ -278,11 +299,11 @@ static void drawMeshTile(duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMesh // Draw inner poly boundaries if (flags & DU_DRAWNAVMESH_POLY_BOUNDS_INNER) - drawPolyBoundaries(dd, tile, 1.5f, offset, true); + drawPolyBoundaries(dd, tile, 1.5f, offset, flags, true); // Draw outer poly boundaries if (flags & DU_DRAWNAVMESH_POLY_BOUNDS_OUTER) - drawPolyBoundaries(dd, tile, 3.5f, offset, false); + drawPolyBoundaries(dd, tile, 3.5f, offset, flags, false); // Draw poly centers if (flags & DU_DRAWNAVMESH_POLY_CENTERS)