| | | 1 | | using FixedMathSharp; |
| | | 2 | | using GridForge.Grids; |
| | | 3 | | using System; |
| | | 4 | | using System.Diagnostics.CodeAnalysis; |
| | | 5 | | using Trailblazer.Pathing; |
| | | 6 | | |
| | | 7 | | namespace Trailblazer.Navigation; |
| | | 8 | | |
| | | 9 | | /// <summary> |
| | | 10 | | /// Plans a bounded volume-first handoff into chart-backed traversal for navigators. |
| | | 11 | | /// </summary> |
| | | 12 | | internal static class GuidedVolumeExitPlanner |
| | | 13 | | { |
| | | 14 | | /// <summary> |
| | | 15 | | /// Attempts to plan a volume exit path from the origin to a target position, followed by a chart-backed leg to the |
| | | 16 | | /// </summary> |
| | | 17 | | /// <param name="context">The world context that owns the request and transition registry.</param> |
| | | 18 | | /// <param name="origin"></param> |
| | | 19 | | /// <param name="targetPosition"></param> |
| | | 20 | | /// <param name="unitSize"></param> |
| | | 21 | | /// <param name="medium"></param> |
| | | 22 | | /// <param name="chartPathMode"></param> |
| | | 23 | | /// <param name="allowUnwalkableEndpoints"></param> |
| | | 24 | | /// <param name="allowTraversalTransitions"></param> |
| | | 25 | | /// <param name="maxClimbHeight"></param> |
| | | 26 | | /// <param name="aStarHeuristic"></param> |
| | | 27 | | /// <param name="flowFieldExtraFloodRange"></param> |
| | | 28 | | /// <param name="request"></param> |
| | | 29 | | /// <param name="handoff"></param> |
| | | 30 | | /// <param name="totalPathCost"></param> |
| | | 31 | | /// <returns>True if a valid path was found; otherwise, false.</returns> |
| | | 32 | | public static bool TryPlan( |
| | | 33 | | TrailblazerWorldContext context, |
| | | 34 | | Vector3d origin, |
| | | 35 | | Vector3d targetPosition, |
| | | 36 | | Fixed64 unitSize, |
| | | 37 | | TraversalMedium medium, |
| | | 38 | | SolidPathAlgorithm chartPathMode, |
| | | 39 | | bool allowUnwalkableEndpoints, |
| | | 40 | | bool allowTraversalTransitions, |
| | | 41 | | Fixed64 maxClimbHeight, |
| | | 42 | | HeuristicMethod aStarHeuristic, |
| | | 43 | | int flowFieldExtraFloodRange, |
| | | 44 | | [NotNullWhen(true)] out VolumePathRequest? request, |
| | | 45 | | out GuidedVolumeExitHandoff? handoff, |
| | | 46 | | out int totalPathCost) |
| | | 47 | | { |
| | 33 | 48 | | PathRequestContextResolver.ThrowIfUnusable(context); |
| | 33 | 49 | | request = null; |
| | 33 | 50 | | handoff = null; |
| | 33 | 51 | | totalPathCost = 0; |
| | | 52 | | |
| | 33 | 53 | | if (chartPathMode != SolidPathAlgorithm.AStar |
| | 33 | 54 | | && chartPathMode != SolidPathAlgorithm.FlowField) |
| | | 55 | | { |
| | 1 | 56 | | return false; |
| | | 57 | | } |
| | | 58 | | |
| | 32 | 59 | | TraversalTransition bestTransition = default; |
| | 32 | 60 | | VolumePathRequest? bestRequest = null; |
| | 32 | 61 | | int bestTotalCost = int.MaxValue; |
| | | 62 | | |
| | 32 | 63 | | if (!TryPlanWithTransitions( |
| | 32 | 64 | | GetLocalDirectedTransitions(context, targetPosition, medium), |
| | 32 | 65 | | context, |
| | 32 | 66 | | origin, |
| | 32 | 67 | | targetPosition, |
| | 32 | 68 | | unitSize, |
| | 32 | 69 | | chartPathMode, |
| | 32 | 70 | | allowUnwalkableEndpoints, |
| | 32 | 71 | | allowTraversalTransitions, |
| | 32 | 72 | | maxClimbHeight, |
| | 32 | 73 | | aStarHeuristic, |
| | 32 | 74 | | flowFieldExtraFloodRange, |
| | 32 | 75 | | ref bestTransition, |
| | 32 | 76 | | ref bestRequest, |
| | 32 | 77 | | ref bestTotalCost) |
| | 32 | 78 | | && !TryPlanWithTransitions( |
| | 32 | 79 | | context.Transitions.GetDirectedTransitions(medium, TraversalMedium.Solid), |
| | 32 | 80 | | context, |
| | 32 | 81 | | origin, |
| | 32 | 82 | | targetPosition, |
| | 32 | 83 | | unitSize, |
| | 32 | 84 | | chartPathMode, |
| | 32 | 85 | | allowUnwalkableEndpoints, |
| | 32 | 86 | | allowTraversalTransitions, |
| | 32 | 87 | | maxClimbHeight, |
| | 32 | 88 | | aStarHeuristic, |
| | 32 | 89 | | flowFieldExtraFloodRange, |
| | 32 | 90 | | ref bestTransition, |
| | 32 | 91 | | ref bestRequest, |
| | 32 | 92 | | ref bestTotalCost)) |
| | | 93 | | { |
| | 5 | 94 | | return false; |
| | | 95 | | } |
| | | 96 | | |
| | 27 | 97 | | if (bestRequest == null) |
| | 0 | 98 | | return false; |
| | | 99 | | |
| | 27 | 100 | | request = bestRequest; |
| | 27 | 101 | | totalPathCost = bestTotalCost; |
| | 27 | 102 | | handoff = CreateHandoff( |
| | 27 | 103 | | bestTransition, |
| | 27 | 104 | | context, |
| | 27 | 105 | | targetPosition, |
| | 27 | 106 | | chartPathMode, |
| | 27 | 107 | | allowUnwalkableEndpoints, |
| | 27 | 108 | | allowTraversalTransitions, |
| | 27 | 109 | | maxClimbHeight, |
| | 27 | 110 | | aStarHeuristic, |
| | 27 | 111 | | flowFieldExtraFloodRange); |
| | 27 | 112 | | return true; |
| | | 113 | | } |
| | | 114 | | |
| | | 115 | | |
| | | 116 | | private static bool TryPlanWithTransitions( |
| | | 117 | | TraversalTransition[] transitions, |
| | | 118 | | TrailblazerWorldContext context, |
| | | 119 | | Vector3d origin, |
| | | 120 | | Vector3d targetPosition, |
| | | 121 | | Fixed64 unitSize, |
| | | 122 | | SolidPathAlgorithm chartPathMode, |
| | | 123 | | bool allowUnwalkableEndpoints, |
| | | 124 | | bool allowTraversalTransitions, |
| | | 125 | | Fixed64 maxClimbHeight, |
| | | 126 | | HeuristicMethod aStarHeuristic, |
| | | 127 | | int flowFieldExtraFloodRange, |
| | | 128 | | ref TraversalTransition bestTransition, |
| | | 129 | | ref VolumePathRequest? bestRequest, |
| | | 130 | | ref int bestTotalCost) |
| | | 131 | | { |
| | 37 | 132 | | if (transitions == null || transitions.Length == 0) |
| | 5 | 133 | | return false; |
| | | 134 | | |
| | 32 | 135 | | bool foundPlan = false; |
| | | 136 | | |
| | 128 | 137 | | for (int i = 0; i < transitions.Length; i++) |
| | | 138 | | { |
| | 32 | 139 | | TraversalTransition transition = transitions[i]; |
| | 32 | 140 | | VolumePathRequest? volumeRequest = VolumePathRequest.Create( |
| | 32 | 141 | | context, |
| | 32 | 142 | | origin, |
| | 32 | 143 | | transition.Source.Position, |
| | 32 | 144 | | unitSize, |
| | 32 | 145 | | aStarHeuristic, |
| | 32 | 146 | | allowUnwalkableEndpoints, |
| | 32 | 147 | | transition.Source.Medium); |
| | 32 | 148 | | if (volumeRequest == null) |
| | | 149 | | continue; |
| | | 150 | | |
| | 32 | 151 | | int volumeCost = 0; |
| | 32 | 152 | | if (!volumeRequest.HasZeroDisplacement) |
| | | 153 | | { |
| | 31 | 154 | | VolumeSurveyResult volumeResult = context.Pathing.State.GuideState.VolumeSurveyor.FindPath(volumeRequest |
| | 31 | 155 | | if (!volumeResult.HasPath) |
| | | 156 | | continue; |
| | | 157 | | |
| | 31 | 158 | | volumeCost = volumeResult.Waypoints![^1].PathCost; |
| | | 159 | | } |
| | | 160 | | |
| | 32 | 161 | | if (!TryGetChartLegCost( |
| | 32 | 162 | | context, |
| | 32 | 163 | | transition.Destination.Position, |
| | 32 | 164 | | targetPosition, |
| | 32 | 165 | | unitSize, |
| | 32 | 166 | | chartPathMode, |
| | 32 | 167 | | allowUnwalkableEndpoints, |
| | 32 | 168 | | allowTraversalTransitions, |
| | 32 | 169 | | maxClimbHeight, |
| | 32 | 170 | | aStarHeuristic, |
| | 32 | 171 | | flowFieldExtraFloodRange, |
| | 32 | 172 | | out int chartCost)) |
| | | 173 | | { |
| | | 174 | | continue; |
| | | 175 | | } |
| | | 176 | | |
| | 27 | 177 | | int totalCost = volumeCost + transition.PathCostModifier + chartCost; |
| | 27 | 178 | | if (totalCost >= bestTotalCost) |
| | | 179 | | continue; |
| | | 180 | | |
| | 27 | 181 | | bestTotalCost = totalCost; |
| | 27 | 182 | | bestTransition = transition; |
| | 27 | 183 | | bestRequest = volumeRequest; |
| | 27 | 184 | | foundPlan = true; |
| | | 185 | | } |
| | | 186 | | |
| | 32 | 187 | | return foundPlan; |
| | | 188 | | } |
| | | 189 | | |
| | | 190 | | private static TraversalTransition[] GetLocalDirectedTransitions( |
| | | 191 | | TrailblazerWorldContext context, |
| | | 192 | | Vector3d targetPosition, |
| | | 193 | | TraversalMedium medium) |
| | | 194 | | { |
| | 32 | 195 | | if (!context.World.TryGetVoxel(targetPosition, out Voxel? targetVoxel) |
| | 32 | 196 | | || targetVoxel == null) |
| | 1 | 197 | | return Array.Empty<TraversalTransition>(); |
| | | 198 | | |
| | 31 | 199 | | return context.Transitions.GetDirectedTransitionsToDestinationGrid( |
| | 31 | 200 | | targetVoxel.GridIndex, |
| | 31 | 201 | | medium, |
| | 31 | 202 | | TraversalMedium.Solid); |
| | | 203 | | } |
| | | 204 | | |
| | | 205 | | private static GuidedVolumeExitHandoff CreateHandoff( |
| | | 206 | | TraversalTransition transition, |
| | | 207 | | TrailblazerWorldContext context, |
| | | 208 | | Vector3d targetPosition, |
| | | 209 | | SolidPathAlgorithm chartPathMode, |
| | | 210 | | bool allowUnwalkableEndpoints, |
| | | 211 | | bool allowTraversalTransitions, |
| | | 212 | | Fixed64 maxClimbHeight, |
| | | 213 | | HeuristicMethod aStarHeuristic, |
| | | 214 | | int flowFieldExtraFloodRange) |
| | | 215 | | { |
| | 27 | 216 | | return new GuidedVolumeExitHandoff |
| | 27 | 217 | | { |
| | 27 | 218 | | TransitionId = transition.Id, |
| | 27 | 219 | | Context = context, |
| | 27 | 220 | | ChartOriginPosition = transition.Destination.Position, |
| | 27 | 221 | | TargetPosition = targetPosition, |
| | 27 | 222 | | ChartPathMode = chartPathMode, |
| | 27 | 223 | | AllowUnwalkableEndpoints = allowUnwalkableEndpoints, |
| | 27 | 224 | | AllowTraversalTransitions = allowTraversalTransitions, |
| | 27 | 225 | | MaxClimbHeight = maxClimbHeight, |
| | 27 | 226 | | AStarHeuristic = aStarHeuristic, |
| | 27 | 227 | | FlowFieldExtraFloodRange = flowFieldExtraFloodRange, |
| | 27 | 228 | | IsRequestingClimb = transition.PreserveClimbIntentOnFollowup |
| | 27 | 229 | | }; |
| | | 230 | | } |
| | | 231 | | |
| | | 232 | | private static bool TryGetChartLegCost( |
| | | 233 | | TrailblazerWorldContext context, |
| | | 234 | | Vector3d origin, |
| | | 235 | | Vector3d targetPosition, |
| | | 236 | | Fixed64 unitSize, |
| | | 237 | | SolidPathAlgorithm chartPathMode, |
| | | 238 | | bool allowUnwalkableEndpoints, |
| | | 239 | | bool allowTraversalTransitions, |
| | | 240 | | Fixed64 maxClimbHeight, |
| | | 241 | | HeuristicMethod aStarHeuristic, |
| | | 242 | | int flowFieldExtraFloodRange, |
| | | 243 | | out int chartCost) |
| | | 244 | | { |
| | 32 | 245 | | chartCost = 0; |
| | | 246 | | |
| | | 247 | | switch (chartPathMode) |
| | | 248 | | { |
| | | 249 | | case SolidPathAlgorithm.FlowField: |
| | 12 | 250 | | FlowFieldPathRequest? flowFieldRequest = FlowFieldPathRequest.Create( |
| | 12 | 251 | | context, |
| | 12 | 252 | | origin, |
| | 12 | 253 | | targetPosition, |
| | 12 | 254 | | unitSize, |
| | 12 | 255 | | allowUnwalkableEndpoints, |
| | 12 | 256 | | allowTraversalTransitions); |
| | 12 | 257 | | if (flowFieldRequest == null) |
| | 0 | 258 | | return false; |
| | | 259 | | |
| | 12 | 260 | | flowFieldRequest.MaxClimbHeight = maxClimbHeight; |
| | 12 | 261 | | flowFieldRequest.ExtraFloodRange = flowFieldExtraFloodRange; |
| | 12 | 262 | | if (flowFieldRequest.HasZeroDisplacement) |
| | 1 | 263 | | return true; |
| | | 264 | | |
| | 11 | 265 | | if (TryGetDirectFlowFieldCost(flowFieldRequest, out chartCost)) |
| | 8 | 266 | | return true; |
| | | 267 | | |
| | 3 | 268 | | if (!allowTraversalTransitions) |
| | 2 | 269 | | return false; |
| | | 270 | | |
| | 1 | 271 | | return TryGetTransitionAwareChartCost(flowFieldRequest, out chartCost); |
| | | 272 | | |
| | | 273 | | case SolidPathAlgorithm.AStar: |
| | | 274 | | default: |
| | 20 | 275 | | AStarPathRequest? aStarRequest = AStarPathRequest.Create( |
| | 20 | 276 | | context, |
| | 20 | 277 | | origin, |
| | 20 | 278 | | targetPosition, |
| | 20 | 279 | | unitSize, |
| | 20 | 280 | | aStarHeuristic, |
| | 20 | 281 | | allowUnwalkableEndpoints, |
| | 20 | 282 | | allowTraversalTransitions); |
| | 20 | 283 | | if (aStarRequest == null) |
| | 1 | 284 | | return false; |
| | | 285 | | |
| | 19 | 286 | | aStarRequest.MaxClimbHeight = maxClimbHeight; |
| | 19 | 287 | | if (aStarRequest.HasZeroDisplacement) |
| | 4 | 288 | | return true; |
| | | 289 | | |
| | 15 | 290 | | if (TryGetDirectAStarCost(aStarRequest, out chartCost)) |
| | 1 | 291 | | return true; |
| | | 292 | | |
| | 14 | 293 | | if (!allowTraversalTransitions) |
| | 2 | 294 | | return false; |
| | | 295 | | |
| | 12 | 296 | | return TryGetTransitionAwareChartCost(aStarRequest, out chartCost); |
| | | 297 | | } |
| | | 298 | | } |
| | | 299 | | |
| | | 300 | | private static bool TryGetDirectAStarCost( |
| | | 301 | | AStarPathRequest request, |
| | | 302 | | out int chartCost) |
| | | 303 | | { |
| | 15 | 304 | | chartCost = 0; |
| | | 305 | | |
| | 15 | 306 | | AStarSurveyResult aStarResult = request.Context.Pathing.State.GuideState.AStarSurveyor.FindPath(request); |
| | 15 | 307 | | return aStarResult.HasPath |
| | 15 | 308 | | && TryAssignChartCost(aStarResult.Waypoints[^1].PathCost, out chartCost); |
| | | 309 | | } |
| | | 310 | | |
| | | 311 | | private static bool TryGetDirectFlowFieldCost( |
| | | 312 | | FlowFieldPathRequest request, |
| | | 313 | | out int chartCost) |
| | | 314 | | { |
| | 11 | 315 | | chartCost = 0; |
| | | 316 | | |
| | 11 | 317 | | FlowFieldSurveyResult flowFieldResult = request.Context.Pathing.State.GuideState.FlowFieldSurveyor.FindPath(requ |
| | 11 | 318 | | return flowFieldResult.HasPath |
| | 11 | 319 | | && flowFieldResult.Fields != null |
| | 11 | 320 | | && request.StartNode != null |
| | 11 | 321 | | && flowFieldResult.Fields.TryGetValue(request.StartNode.WorldIndex, out FlowField startField) |
| | 11 | 322 | | && TryAssignChartCost(startField.PathCost, out chartCost); |
| | | 323 | | } |
| | | 324 | | |
| | | 325 | | private static bool TryGetTransitionAwareChartCost( |
| | | 326 | | AStarPathRequest request, |
| | | 327 | | out int chartCost) |
| | | 328 | | { |
| | 12 | 329 | | return TryGetTransitionAwareChartCost(HybridPathRequest.CreateFromAStar(request), out chartCost); |
| | | 330 | | } |
| | | 331 | | |
| | | 332 | | private static bool TryGetTransitionAwareChartCost( |
| | | 333 | | FlowFieldPathRequest request, |
| | | 334 | | out int chartCost) |
| | | 335 | | { |
| | 1 | 336 | | return TryGetTransitionAwareChartCost(HybridPathRequest.CreateFromFlowField(request), out chartCost); |
| | | 337 | | } |
| | | 338 | | |
| | | 339 | | internal static bool TryGetTransitionAwareChartCost( |
| | | 340 | | HybridPathRequest? hybridRequest, |
| | | 341 | | out int chartCost) |
| | | 342 | | { |
| | 16 | 343 | | chartCost = 0; |
| | | 344 | | |
| | 16 | 345 | | HybridRoutePlan? routePlan = hybridRequest?.RoutePlan; |
| | 16 | 346 | | return routePlan != null |
| | 16 | 347 | | && routePlan.DirectedTransitions.Length > 0 |
| | 16 | 348 | | && TryAssignChartCost(routePlan.TotalPathCost, out chartCost); |
| | | 349 | | } |
| | | 350 | | |
| | | 351 | | private static bool TryAssignChartCost(int cost, out int chartCost) |
| | | 352 | | { |
| | 23 | 353 | | chartCost = cost; |
| | 23 | 354 | | return true; |
| | | 355 | | } |
| | | 356 | | } |