125 lines
4.5 KiB
C#
125 lines
4.5 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Roy_T.AStar.Collections;
|
|
using Roy_T.AStar.Graphs;
|
|
using Roy_T.AStar.Grids;
|
|
using Roy_T.AStar.Primitives;
|
|
|
|
namespace Roy_T.AStar.Paths
|
|
{
|
|
public sealed class PathFinder
|
|
{
|
|
private readonly MinHeap<PathFinderNode> Interesting;
|
|
private readonly Dictionary<INode, PathFinderNode> Nodes;
|
|
private readonly PathReconstructor PathReconstructor;
|
|
|
|
private PathFinderNode NodeClosestToGoal;
|
|
|
|
public PathFinder()
|
|
{
|
|
this.Interesting = new MinHeap<PathFinderNode>();
|
|
this.Nodes = new Dictionary<INode, PathFinderNode>();
|
|
this.PathReconstructor = new PathReconstructor();
|
|
}
|
|
|
|
public Path FindPath(GridPosition start, GridPosition end, Grid grid)
|
|
{
|
|
var startNode = grid.GetNode(start);
|
|
var endNode = grid.GetNode(end);
|
|
|
|
var maximumVelocity = grid.GetAllNodes().SelectMany(n => n.Outgoing).Select(e => e.TraversalVelocity).Max();
|
|
|
|
return this.FindPath(startNode, endNode, maximumVelocity);
|
|
}
|
|
|
|
public Path FindPath(GridPosition start, GridPosition end, Grid grid, Velocity maximumVelocity)
|
|
{
|
|
var startNode = grid.GetNode(start);
|
|
var endNode = grid.GetNode(end);
|
|
|
|
return this.FindPath(startNode, endNode, maximumVelocity);
|
|
}
|
|
|
|
public Path FindPath(INode start, INode goal, Velocity maximumVelocity)
|
|
{
|
|
this.ResetState();
|
|
this.AddFirstNode(start, goal, maximumVelocity);
|
|
|
|
while (this.Interesting.Count > 0)
|
|
{
|
|
var current = this.Interesting.Extract();
|
|
if (GoalReached(goal, current))
|
|
{
|
|
return this.PathReconstructor.ConstructPathTo(current.Node, goal);
|
|
}
|
|
|
|
this.UpdateNodeClosestToGoal(current);
|
|
|
|
foreach (var edge in current.Node.Outgoing)
|
|
{
|
|
var oppositeNode = edge.End;
|
|
var costSoFar = current.DurationSoFar + edge.TraversalDuration;
|
|
|
|
if (this.Nodes.TryGetValue(oppositeNode, out var node))
|
|
{
|
|
this.UpdateExistingNode(goal, maximumVelocity, current, edge, oppositeNode, costSoFar, node);
|
|
}
|
|
else
|
|
{
|
|
this.InsertNode(oppositeNode, edge, goal, costSoFar, maximumVelocity);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.PathReconstructor.ConstructPathTo(this.NodeClosestToGoal.Node, goal);
|
|
}
|
|
|
|
private void ResetState()
|
|
{
|
|
this.Interesting.Clear();
|
|
this.Nodes.Clear();
|
|
this.PathReconstructor.Clear();
|
|
this.NodeClosestToGoal = null;
|
|
}
|
|
|
|
private void AddFirstNode(INode start, INode goal, Velocity maximumVelocity)
|
|
{
|
|
var head = new PathFinderNode(start, Duration.Zero, ExpectedDuration(start, goal, maximumVelocity));
|
|
this.Interesting.Insert(head);
|
|
this.Nodes.Add(head.Node, head);
|
|
this.NodeClosestToGoal = head;
|
|
}
|
|
|
|
private static bool GoalReached(INode goal, PathFinderNode current) => current.Node == goal;
|
|
|
|
private void UpdateNodeClosestToGoal(PathFinderNode current)
|
|
{
|
|
if (current.ExpectedRemainingTime < this.NodeClosestToGoal.ExpectedRemainingTime)
|
|
{
|
|
this.NodeClosestToGoal = current;
|
|
}
|
|
}
|
|
|
|
private void UpdateExistingNode(INode goal, Velocity maximumVelocity, PathFinderNode current, IEdge edge, INode oppositeNode, Duration costSoFar, PathFinderNode node)
|
|
{
|
|
if (node.DurationSoFar > costSoFar)
|
|
{
|
|
this.Interesting.Remove(node);
|
|
this.InsertNode(oppositeNode, edge, goal, costSoFar, maximumVelocity);
|
|
}
|
|
}
|
|
|
|
private void InsertNode(INode current, IEdge via, INode goal, Duration costSoFar, Velocity maximumVelocity)
|
|
{
|
|
this.PathReconstructor.SetCameFrom(current, via);
|
|
|
|
var node = new PathFinderNode(current, costSoFar, ExpectedDuration(current, goal, maximumVelocity));
|
|
this.Interesting.Insert(node);
|
|
this.Nodes[current] = node;
|
|
}
|
|
|
|
public static Duration ExpectedDuration(INode a, INode b, Velocity maximumVelocity)
|
|
=> Distance.BeweenPositions(a.Position, b.Position) / maximumVelocity;
|
|
}
|
|
}
|