-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAStarPathfinder.cs
172 lines (148 loc) · 5.9 KB
/
AStarPathfinder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
using System;
using System.Collections.Generic;
using MathTools;
using Debugging;
namespace Pathfinding
{
/// <summary>
/// calculates paths given an IAstarGraph and start/goal positions
/// </summary>
public class AStarPathfinder
{
public int numExpansions;
protected int sessionID = 1;
private PriorityQueue<DefaultPqNode> openSet;
protected NodeInfo[] nodeInfos;
protected const int NoParent = -1;
protected struct NodeInfo
{
public int parent;
public float gCost, hCost;
// sessionID - in open set
// sessionID + 1 - in closed set
public int visited;
public NodeInfo(int parent, float gCost, float hCost, int sessionID)
{
this.parent = parent;
this.gCost = gCost;
// Cache hCost
this.hCost = hCost;
this.visited = sessionID;
}
public void Update(int parent, float gCost)
{
this.parent = parent;
this.gCost = gCost;
}
public void Update(int visited)
{
this.visited = visited;
}
}
public AStarPathfinder(int graphSize)
{
nodeInfos = new NodeInfo[graphSize];
openSet = new PriorityQueue<DefaultPqNode>(graphSize, new DefaultPqNodeComparer());
}
/// <summary>
/// Gets a path from start to goal if possible. If no path is found null is returned.
/// </summary>
public bool Search(IAstarGraph graph, int startID, int goalID, List<int> path)
{
numExpansions = 0;
path.Clear();
bool foundPath = false;
float startHCost = graph.Heuristic(startID, goalID);
openSet.Enqueue(new DefaultPqNode(startID, startHCost, startHCost));
nodeInfos[startID] = new NodeInfo(-1, 0f, startHCost, sessionID);
while (openSet.Count > 0)
{
numExpansions++;
var current = openSet.Dequeue();
nodeInfos[current.id].visited = sessionID + 1;
SetVertex(graph, current.id, goalID);
if (current.id == goalID)
{
foundPath = true;
break;
}
float currentGCost = nodeInfos[current.id].gCost;
// Obsolete enqueued value (multiple nodes of same id on queue)
if (current.priority > (currentGCost + nodeInfos[current.id].hCost + MathExt.Epsilon))
continue;
graph.BeginIterEdges(current.id);
while (graph.MoveNext())
{
var edge = graph.CurrentEdge;
float newGCost = currentGCost + edge.cost;
UpdateVertex(graph, sourceID: current.id, neighborID: edge.vertexId, newGCost: newGCost, goalID: goalID);
}
}
openSet.Reset();
// Make sure we are reset
if (sessionID >= int.MaxValue - 1)
{
for (int i = 0; i < nodeInfos.Length; i++)
nodeInfos[i].visited = 0;
sessionID = 0;
}
sessionID += 2;
if (foundPath)
RecontructPath(startID, goalID, path);
return foundPath;
}
protected virtual void SetVertex(IAstarGraph graph, int sourceID, int goalID)
{ }
// If node has not been visited (neither open nor closed) or newGCost < nodeInfo.gCost, enqueue it, otherwise do nothing
// this will result in duplicates in priority queue with same id, check this when dequeueing if priority is up to date
// now we do not need to decrease priority, so no need to find a node in the queue
protected virtual void UpdateVertex(IAstarGraph graph, int sourceID, int neighborID, float newGCost, int goalID)
{
var nodeInfo = nodeInfos[neighborID];
// Node has not been visited, so add it to the queue (explore)
if (nodeInfo.visited < sessionID)
{
float hCost = graph.Heuristic(neighborID, goalID);
openSet.Enqueue(new DefaultPqNode(neighborID, newGCost + hCost, hCost));
nodeInfos[neighborID] = new NodeInfo(sourceID, newGCost, hCost, sessionID);
}
// Node has been visited, but a shorter path has been found to it, so update cost
else if (newGCost < nodeInfo.gCost)
{
openSet.Enqueue(new DefaultPqNode(neighborID, newGCost + nodeInfo.hCost, nodeInfo.hCost));
nodeInfos[neighborID] = new NodeInfo(sourceID, newGCost, nodeInfo.hCost, sessionID);
}
}
/// <summary>
/// Reconstructs a path from the parents
/// </summary>
private List<int> RecontructPath(int startID, int goalID, List<int> path)
{
int currentID = goalID;
path.Add(goalID);
while (currentID != startID)
{
currentID = nodeInfos[currentID].parent;
path.Add(currentID);
/*if (path.Count > 1000)
{
Debug.Log(currentID);
throw new OutOfMemoryException();
}*/
}
path.Reverse();
return path;
}
}
public struct Edge
{
// ID of vertex endpoint
public int vertexId;
public float cost;
public Edge(int vertexId, float cost)
{
this.vertexId = vertexId;
this.cost = cost;
}
}
}