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)