| | | 1 | | using FixedMathSharp; |
| | | 2 | | |
| | | 3 | | namespace Trailblazer.Pathing; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// Provides steering direction based on a sequence of waypoints generated from an A* pathfinding survey. |
| | | 7 | | /// Suitable for direct point-to-point navigation along a computed path. |
| | | 8 | | /// </summary> |
| | | 9 | | public class AStarGuide : IWaypointGuide |
| | | 10 | | { |
| | | 11 | | /// <summary> |
| | | 12 | | /// The result of the A* survey, containing the waypoints and path information needed to guide an agent along the pa |
| | | 13 | | /// </summary> |
| | | 14 | | public AStarSurveyResult TrailMap { get; private set; } = AStarSurveyResult.Empty; |
| | | 15 | | |
| | | 16 | | /// <summary> |
| | | 17 | | /// Cached smoothed waypoints generated from the original TrailMap waypoints. |
| | | 18 | | /// This allows for optional smoothing (e.g. Catmull-Rom interpolation) without modifying the original path data. |
| | | 19 | | /// </summary> |
| | | 20 | | private AStarWaypoint[]? _smoothedWaypoints; |
| | | 21 | | |
| | | 22 | | /// <inheritdoc/> |
| | | 23 | | public int CurrentWaypointIndex { get; private set; } |
| | | 24 | | |
| | | 25 | | /// <summary> |
| | | 26 | | /// Indicates whether a smoothing algorithm like spline interpolation should be applied to the final path. |
| | | 27 | | /// </summary> |
| | | 28 | | public bool UseSplineSmoothing { get; set; } |
| | | 29 | | |
| | | 30 | | /// <summary> |
| | | 31 | | /// Tracks the last waypoint index that was used to provide a fallback direction. |
| | | 32 | | /// This helps ensure that fallback directions are provided in a forward progression along the path, |
| | | 33 | | /// rather than repeatedly returning the same fallback when the agent is stuck. |
| | | 34 | | /// </summary> |
| | | 35 | | private int _lastTriedIndex; |
| | | 36 | | |
| | | 37 | | /// <summary> |
| | | 38 | | /// Initializes the guide with the given A* survey result. |
| | | 39 | | /// </summary> |
| | | 40 | | /// <param name="surveyResult">The result of the A* survey containing the waypoints and path information.</param> |
| | | 41 | | /// <returns>True if the guide is successfully initialized with a valid path; otherwise, false.</returns> |
| | | 42 | | public bool Initialize(AStarSurveyResult surveyResult) |
| | | 43 | | { |
| | 590 | 44 | | if (!surveyResult.HasPath) |
| | 1 | 45 | | return false; |
| | | 46 | | |
| | 589 | 47 | | TrailMap = surveyResult; |
| | 589 | 48 | | CurrentWaypointIndex = 0; |
| | | 49 | | |
| | 589 | 50 | | return true; |
| | | 51 | | } |
| | | 52 | | |
| | | 53 | | /// <summary> |
| | | 54 | | /// Gets the active waypoints for this guide, applying optional smoothing if enabled. |
| | | 55 | | /// </summary> |
| | | 56 | | public AStarWaypoint[] ActiveWaypoints |
| | | 57 | | { |
| | | 58 | | get |
| | | 59 | | { |
| | 181 | 60 | | if (UseSplineSmoothing) |
| | | 61 | | { |
| | 10 | 62 | | if (_smoothedWaypoints == null && TrailMap.Waypoints.Length >= 4) |
| | 4 | 63 | | _smoothedWaypoints = AStarSurveyor.CatmullSmooth(TrailMap.Waypoints); |
| | 10 | 64 | | return _smoothedWaypoints ?? TrailMap.Waypoints; |
| | | 65 | | } |
| | | 66 | | |
| | 171 | 67 | | return TrailMap.Waypoints; |
| | | 68 | | } |
| | | 69 | | } |
| | | 70 | | |
| | | 71 | | /// <summary> |
| | | 72 | | /// Determines whether the guide has reached the final waypoint. |
| | | 73 | | /// </summary> |
| | | 74 | | /// <returns>True if the guide has arrived at the final waypoint; otherwise, false.</returns> |
| | | 75 | | public bool HasArrived() |
| | | 76 | | { |
| | 5 | 77 | | return TrailMap.HasPath && CurrentWaypointIndex == ActiveWaypoints.Length - 1; |
| | | 78 | | } |
| | | 79 | | |
| | | 80 | | /// <inheritdoc/> |
| | | 81 | | public int GetIndex(Vector3d from) |
| | | 82 | | { |
| | 2 | 83 | | Fixed64 minDistSq = Fixed64.MAX_VALUE; |
| | 2 | 84 | | int bestIndex = -1; |
| | 12 | 85 | | for (int i = 0; i < ActiveWaypoints.Length; i++) |
| | | 86 | | { |
| | 5 | 87 | | Fixed64 distSq = (from - ActiveWaypoints[i].Position).SqrMagnitude; |
| | 5 | 88 | | if (distSq < minDistSq) |
| | | 89 | | { |
| | 5 | 90 | | minDistSq = distSq; |
| | 5 | 91 | | bestIndex = i; |
| | | 92 | | } |
| | | 93 | | |
| | 5 | 94 | | if (minDistSq <= Fixed64.Epsilon) |
| | | 95 | | break; |
| | | 96 | | } |
| | | 97 | | |
| | 2 | 98 | | return bestIndex; |
| | | 99 | | } |
| | | 100 | | |
| | | 101 | | /// <inheritdoc/> |
| | 29 | 102 | | public void AdvanceWaypoint() => CurrentWaypointIndex++; |
| | | 103 | | |
| | | 104 | | /// <inheritdoc/> |
| | | 105 | | public bool TryGetMovementDirection(Vector3d origin, out Vector3d direction) |
| | | 106 | | { |
| | 2 | 107 | | direction = Vector3d.Zero; |
| | | 108 | | |
| | 2 | 109 | | if (!TrailMap.HasPath) |
| | 1 | 110 | | return false; |
| | | 111 | | |
| | 1 | 112 | | int closestIndex = GetIndex(origin); |
| | 1 | 113 | | direction = (ActiveWaypoints[closestIndex].Position - origin).Normalize(); |
| | 1 | 114 | | return true; |
| | | 115 | | } |
| | | 116 | | |
| | | 117 | | /// <inheritdoc/> |
| | | 118 | | public Vector3d GetCurrentWaypointDirection(Vector3d origin) |
| | | 119 | | { |
| | 34 | 120 | | if (!TrailMap.HasPath || CurrentWaypointIndex < 0 || CurrentWaypointIndex >= ActiveWaypoints.Length) |
| | 1 | 121 | | return Vector3d.Zero; |
| | | 122 | | |
| | 33 | 123 | | Vector3d movementDirection = ActiveWaypoints[CurrentWaypointIndex].Position; |
| | 33 | 124 | | if (movementDirection == Vector3d.Zero) |
| | 18 | 125 | | return Vector3d.Zero; |
| | | 126 | | |
| | 15 | 127 | | return (movementDirection - origin).Normal; |
| | | 128 | | } |
| | | 129 | | |
| | | 130 | | /// <inheritdoc/> |
| | | 131 | | public bool TryGetFallbackDirection(Vector3d from, out Vector3d fallbackDirection) |
| | | 132 | | { |
| | 3 | 133 | | fallbackDirection = Vector3d.Zero; |
| | | 134 | | |
| | 3 | 135 | | if (ActiveWaypoints.Length == 0) |
| | 1 | 136 | | return false; |
| | | 137 | | |
| | | 138 | | // Start from CurrentIndex + 1 and search forward |
| | 2 | 139 | | int searchStart = FixedMath.Clamp(_lastTriedIndex, 0, ActiveWaypoints.Length - 1); |
| | | 140 | | |
| | 2 | 141 | | Fixed64 minDistSq = Fixed64.MAX_VALUE; |
| | 2 | 142 | | int bestIndex = searchStart; |
| | | 143 | | |
| | 16 | 144 | | for (int i = searchStart; i < ActiveWaypoints.Length; i++) |
| | | 145 | | { |
| | 6 | 146 | | Fixed64 distSq = (from - ActiveWaypoints[i].Position).SqrMagnitude; |
| | 6 | 147 | | if (distSq < minDistSq) |
| | | 148 | | { |
| | 4 | 149 | | minDistSq = distSq; |
| | 4 | 150 | | bestIndex = i; |
| | | 151 | | } |
| | | 152 | | } |
| | | 153 | | |
| | 2 | 154 | | fallbackDirection = (ActiveWaypoints[bestIndex].Position - from).Normal; |
| | 2 | 155 | | _lastTriedIndex = bestIndex; |
| | 2 | 156 | | return true; |
| | | 157 | | } |
| | | 158 | | |
| | | 159 | | /// <summary> |
| | | 160 | | /// Attempts to get the waypoint at the specified index. |
| | | 161 | | /// </summary> |
| | | 162 | | /// <param name="index">The index of the waypoint to retrieve.</param> |
| | | 163 | | /// <param name="waypoint">The waypoint at the specified index, if found.</param> |
| | | 164 | | /// <returns>True if the waypoint was successfully retrieved; otherwise, false.</returns> |
| | | 165 | | public bool TryGetWaypointAt(int index, out AStarWaypoint waypoint) |
| | | 166 | | { |
| | 15 | 167 | | if (!TrailMap.HasPath || index < 0 || index >= ActiveWaypoints.Length) |
| | | 168 | | { |
| | 3 | 169 | | waypoint = default; |
| | 3 | 170 | | return false; |
| | | 171 | | } |
| | | 172 | | |
| | 12 | 173 | | waypoint = ActiveWaypoints[index]; |
| | 12 | 174 | | return true; |
| | | 175 | | } |
| | | 176 | | |
| | | 177 | | internal void ResetForReuse() |
| | | 178 | | { |
| | 559 | 179 | | TrailMap = AStarSurveyResult.Empty; |
| | 559 | 180 | | _smoothedWaypoints = null; |
| | 559 | 181 | | CurrentWaypointIndex = 0; |
| | 559 | 182 | | UseSplineSmoothing = false; |
| | 559 | 183 | | _lastTriedIndex = 0; |
| | 559 | 184 | | } |
| | | 185 | | } |