using System; using System.Collections.Generic; using DotRecast.Core; using DotRecast.Core.Numerics; using DotRecast.Detour; namespace DotRecast.Recast.Toolset.Tools { public class RcTestNavMeshTool : IRcToolable { public const int MAX_POLYS = 256; public const int MAX_SMOOTH = 2048; public RcTestNavMeshTool() { } public string GetName() { return "Test Navmesh"; } public DtStatus FindFollowPath(DtNavMesh navMesh, DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPt, RcVec3f endPt, IDtQueryFilter filter, bool enableRaycast, ref List pathIterPolys, ref List smoothPath) { if (startRef == 0 || endRef == 0) { pathIterPolys?.Clear(); smoothPath?.Clear(); return DtStatus.DT_FAILURE; } pathIterPolys ??= new List(); smoothPath ??= new List(); pathIterPolys.Clear(); smoothPath.Clear(); var opt = new DtFindPathOption(enableRaycast ? DtFindPathOptions.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue); navQuery.FindPath(startRef, endRef, startPt, endPt, filter, ref pathIterPolys, opt); if (0 >= pathIterPolys.Count) return DtStatus.DT_FAILURE; // Iterate over the path to find smooth path on the detail mesh surface. navQuery.ClosestPointOnPoly(startRef, startPt, out var iterPos, out var _); navQuery.ClosestPointOnPoly(pathIterPolys[pathIterPolys.Count - 1], endPt, out var targetPos, out var _); float STEP_SIZE = 0.5f; float SLOP = 0.01f; smoothPath.Clear(); smoothPath.Add(iterPos); var visited = new List(); // Move towards target a small advancement at a time until target reached or // when ran out of memory to store the path. while (0 < pathIterPolys.Count && smoothPath.Count < MAX_SMOOTH) { // Find location to steer towards. if (!DtPathUtils.GetSteerTarget(navQuery, iterPos, targetPos, SLOP, pathIterPolys, out var steerPos, out var steerPosFlag, out var steerPosRef)) { break; } bool endOfPath = (steerPosFlag & DtStraightPathFlags.DT_STRAIGHTPATH_END) != 0 ? true : false; bool offMeshConnection = (steerPosFlag & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0 ? true : false; // Find movement delta. RcVec3f delta = RcVec3f.Subtract(steerPos, iterPos); float len = MathF.Sqrt(RcVec3f.Dot(delta, delta)); // If the steer target is end of path or off-mesh link, do not move past the location. if ((endOfPath || offMeshConnection) && len < STEP_SIZE) { len = 1; } else { len = STEP_SIZE / len; } RcVec3f moveTgt = RcVecUtils.Mad(iterPos, delta, len); // Move navQuery.MoveAlongSurface(pathIterPolys[0], iterPos, moveTgt, filter, out var result, ref visited); iterPos = result; pathIterPolys = DtPathUtils.MergeCorridorStartMoved(pathIterPolys, MAX_POLYS, visited); pathIterPolys = DtPathUtils.FixupShortcuts(pathIterPolys, navQuery); var status = navQuery.GetPolyHeight(pathIterPolys[0], result, out var h); if (status.Succeeded()) { iterPos.Y = h; } // Handle end of path and off-mesh links when close enough. if (endOfPath && DtPathUtils.InRange(iterPos, steerPos, SLOP, 1.0f)) { // Reached end of path. iterPos = targetPos; if (smoothPath.Count < MAX_SMOOTH) { smoothPath.Add(iterPos); } break; } else if (offMeshConnection && DtPathUtils.InRange(iterPos, steerPos, SLOP, 1.0f)) { // Reached off-mesh connection. RcVec3f startPos = RcVec3f.Zero; RcVec3f endPos = RcVec3f.Zero; // Advance the path up to and over the off-mesh connection. long prevRef = 0; long polyRef = pathIterPolys[0]; int npos = 0; while (npos < pathIterPolys.Count && polyRef != steerPosRef) { prevRef = polyRef; polyRef = pathIterPolys[npos]; npos++; } pathIterPolys = pathIterPolys.GetRange(npos, pathIterPolys.Count - npos); // Handle the connection. var status2 = navMesh.GetOffMeshConnectionPolyEndPoints(prevRef, polyRef, ref startPos, ref endPos); if (status2.Succeeded()) { if (smoothPath.Count < MAX_SMOOTH) { smoothPath.Add(startPos); // Hack to make the dotted path not visible during off-mesh connection. if ((smoothPath.Count & 1) != 0) { smoothPath.Add(startPos); } } // Move position at the other side of the off-mesh link. iterPos = endPos; navQuery.GetPolyHeight(pathIterPolys[0], iterPos, out var eh); iterPos.Y = eh; } } // Store results. if (smoothPath.Count < MAX_SMOOTH) { smoothPath.Add(iterPos); } } return DtStatus.DT_SUCCESS; } public DtStatus FindStraightPath(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPt, RcVec3f endPt, IDtQueryFilter filter, bool enableRaycast, ref List polys, ref List straightPath, int straightPathOptions) { if (startRef == 0 || endRef == 0) { return DtStatus.DT_FAILURE; } polys ??= new List(); straightPath ??= new List(); polys.Clear(); straightPath.Clear(); var opt = new DtFindPathOption(enableRaycast ? DtFindPathOptions.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue); navQuery.FindPath(startRef, endRef, startPt, endPt, filter, ref polys, opt); if (0 >= polys.Count) return DtStatus.DT_FAILURE; // In case of partial path, make sure the end point is clamped to the last polygon. var epos = new RcVec3f(endPt.X, endPt.Y, endPt.Z); if (polys[polys.Count - 1] != endRef) { var result = navQuery.ClosestPointOnPoly(polys[polys.Count - 1], endPt, out var closest, out var _); if (result.Succeeded()) { epos = closest; } } navQuery.FindStraightPath(startPt, epos, polys, ref straightPath, MAX_POLYS, straightPathOptions); return DtStatus.DT_SUCCESS; } public DtStatus InitSlicedFindPath(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, bool enableRaycast) { if (startRef == 0 || endRef == 0) { return DtStatus.DT_FAILURE; } return navQuery.InitSlicedFindPath(startRef, endRef, startPos, endPos, filter, enableRaycast ? DtFindPathOptions.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue ); } public DtStatus UpdateSlicedFindPath(DtNavMeshQuery navQuery, int maxIter, long endRef, RcVec3f startPos, RcVec3f endPos, ref List path, ref List straightPath) { var status = navQuery.UpdateSlicedFindPath(maxIter, out _); if (!status.Succeeded()) { return status; } navQuery.FinalizeSlicedFindPath(ref path); straightPath?.Clear(); if (path != null) { // In case of partial path, make sure the end point is clamped to the last polygon. RcVec3f epos = endPos; if (path[path.Count - 1] != endRef) { var result = navQuery.ClosestPointOnPoly(path[path.Count - 1], endPos, out var closest, out var _); if (result.Succeeded()) { epos = closest; } } straightPath = new List(MAX_POLYS); navQuery.FindStraightPath(startPos, epos, path, ref straightPath, MAX_POLYS, DtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS); } return DtStatus.DT_SUCCESS; } public DtStatus Raycast(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, ref List polys, ref List straightPath, ref RcVec3f hitPos, ref RcVec3f hitNormal, ref bool hitResult) { if (startRef == 0 || endRef == 0) { polys?.Clear(); straightPath?.Clear(); return DtStatus.DT_FAILURE; } var path = new List(); var status = navQuery.Raycast(startRef, startPos, endPos, filter, out var t, out var hitNormal2, ref path); if (!status.Succeeded()) { return status; } // results ... polys = path; if (t > 1) { // No hit hitPos = endPos; hitResult = false; } else { // Hit hitPos = RcVec3f.Lerp(startPos, endPos, t); hitNormal = hitNormal2; hitResult = true; } // Adjust height. if (path.Count > 0) { var result = navQuery.GetPolyHeight(path[path.Count - 1], hitPos, out var h); if (result.Succeeded()) { hitPos.Y = h; } } straightPath ??= new List(); straightPath.Clear(); straightPath.Add(new DtStraightPath(startPos, 0, 0)); straightPath.Add(new DtStraightPath(hitPos, 0, 0)); return status; } public DtStatus FindDistanceToWall(DtNavMeshQuery navQuery, long startRef, RcVec3f spos, float maxRadius, IDtQueryFilter filter, ref float hitDist, ref RcVec3f hitPos, ref RcVec3f hitNormal) { if (0 == startRef) { return DtStatus.DT_FAILURE; } var status = navQuery.FindDistanceToWall(startRef, spos, maxRadius, filter, out var tempHitDist, out var tempHitPos, out var tempHitNormal); if (status.Succeeded()) { hitDist = tempHitDist; hitPos = tempHitPos; hitNormal = tempHitNormal; } return status; } public DtStatus FindPolysAroundCircle(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f spos, RcVec3f epos, IDtQueryFilter filter, ref List resultRef, ref List resultParent) { if (startRef == 0 || endRef == 0) { return DtStatus.DT_FAILURE; } float dx = epos.X - spos.X; float dz = epos.Z - spos.Z; float dist = MathF.Sqrt(dx * dx + dz * dz); List tempResultRefs = new List(); List tempParentRefs = new List(); List tempCosts = new List(); var status = navQuery.FindPolysAroundCircle(startRef, spos, dist, filter, ref tempResultRefs, ref tempParentRefs, ref tempCosts); if (status.Succeeded()) { resultRef = tempResultRefs; resultParent = tempParentRefs; } return status; } public DtStatus FindLocalNeighbourhood(DtNavMeshQuery navQuery, long startRef, RcVec3f spos, float radius, IDtQueryFilter filter, ref List resultRef, ref List resultParent) { if (startRef == 0) { resultRef?.Clear(); resultParent?.Clear(); return DtStatus.DT_FAILURE; } resultRef ??= new List(); resultParent ??= new List(); resultRef.Clear(); resultParent.Clear(); var status = navQuery.FindLocalNeighbourhood(startRef, spos, radius, filter, ref resultRef, ref resultParent); return status; } public DtStatus FindPolysAroundShape(DtNavMeshQuery navQuery, float agentHeight, long startRef, long endRef, RcVec3f spos, RcVec3f epos, IDtQueryFilter filter, ref List resultRefs, ref List resultParents, ref RcVec3f[] queryPoly) { if (startRef == 0 || endRef == 0) { return DtStatus.DT_FAILURE; } float nx = (epos.Z - spos.Z) * 0.25f; float nz = -(epos.X - spos.X) * 0.25f; var tempQueryPoly = new RcVec3f[4]; tempQueryPoly[0].X = spos.X + nx * 1.2f; tempQueryPoly[0].Y = spos.Y + agentHeight / 2; tempQueryPoly[0].Z = spos.Z + nz * 1.2f; tempQueryPoly[1].X = spos.X - nx * 1.3f; tempQueryPoly[1].Y = spos.Y + agentHeight / 2; tempQueryPoly[1].Z = spos.Z - nz * 1.3f; tempQueryPoly[2].X = epos.X - nx * 0.8f; tempQueryPoly[2].Y = epos.Y + agentHeight / 2; tempQueryPoly[2].Z = epos.Z - nz * 0.8f; tempQueryPoly[3].X = epos.X + nx; tempQueryPoly[3].Y = epos.Y + agentHeight / 2; tempQueryPoly[3].Z = epos.Z + nz; var tempResultRefs = new List(); var tempResultParents = new List(); var tempCosts = new List(); var status = navQuery.FindPolysAroundShape(startRef, tempQueryPoly, filter, ref tempResultRefs, ref tempResultParents, ref tempCosts); if (status.Succeeded()) { resultRefs = tempResultRefs; resultParents = tempResultParents; queryPoly = tempQueryPoly; } return status; } public DtStatus FindRandomPointAroundCircle(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f spos, RcVec3f epos, IDtQueryFilter filter, bool constrainByCircle, int count, ref List points) { if (startRef == 0 || endRef == 0) { return DtStatus.DT_FAILURE; } float dx = epos.X - spos.X; float dz = epos.Z - spos.Z; float dist = MathF.Sqrt(dx * dx + dz * dz); IDtPolygonByCircleConstraint constraint = constrainByCircle ? DtStrictDtPolygonByCircleConstraint.Shared : DtNoOpDtPolygonByCircleConstraint.Shared; var frand = new RcRand(); int prevCnt = points.Count; points = new List(); while (0 < count && points.Count < prevCnt + count) { var status = navQuery.FindRandomPointAroundCircle(startRef, spos, dist, filter, frand, constraint, out var randomRef, out var randomPt); if (status.Succeeded()) { points.Add(randomPt); } } return DtStatus.DT_SUCCESS; } } }