hivemind 1.0.0
Loading...
Searching...
No Matches
graph.h
Go to the documentation of this file.
1#pragma once
2
3#include <functional>
4#include <iostream>
5#include <memory>
6#include <queue>
7#include <vector>
8
9namespace Routemaker
10{
11
12 /// \brief Represents a node in a Graph data structured made for
13 /// path-finding
14 ///
15 /// \tparam T Type of data to store inside the node
16 template<typename T>
17 struct Node
18 {
19 /// \brief Data stored in the the node.
20 ///
21 /// Stores data not needed by the A\* path-finding algorithm. This is
22 /// what the user actually wants to store in the \ref Graph.
24
25 /// \brief A non-owner pointer to the parent of the node.
26 ///
27 /// Should not be set by user. The A\* path-finding algorithm sets the
28 /// value for this member when traversing the \ref Graph. It used to
29 /// find the way back to the start after the goal is found.
30 std::weak_ptr<Node<T>> Parent;
31
32 /// \brief Specifies if a given node has been visited during
33 /// path-finding.
34 ///
35 /// Should not be set by user. Is generally only used internally by the
36 /// A\* path-finding algorithm when traversing the \ref Graph. May be
37 /// used in debug views to visualize which nodes are visited during
38 /// path-finding.
39 bool Visited;
40
41 /// \brief Represents the assumed cost from the start to the goal node
42 /// through this node.
43 ///
44 /// Should not be set by the user.
45 /// The A\* path-finding algorithm uses cost to find the shortest path
46 /// in a reasonable amount of time. This member contains the sum of the
47 /// cost to get to this node from the start node, represented in \ref
48 /// LocalGoal, plus the assumed cost to get from this node to the goal
49 /// node. The A\* path-finding algorithm uses this value during \ref
50 /// Graph traversal to sort a priority queue in order to explore the
51 /// assumed shortest paths first.
52 double GlobalGoal;
53
54 /// \brief Represents the cost from the start node to this node.
55 ///
56 /// Should not be set by the user.
57 /// The A\* path-finding algorithm uses cost to find the shortest path
58 /// in a reasonable amount of time. This member contains the sum of the
59 /// cost to get to this node from the start node. While traversing the
60 /// \ref Graph, the A\* path-finding algorithm updates and uses this
61 /// member to check for shorter paths.
62 double LocalGoal;
63 };
64
65 /// \brief Abstract graph interface optimized for path-finding.
66 ///
67 /// \tparam T Type of user data to store in each node
68 ///
69 /// This interface is designed to be flexible and scalable. The sub-classes
70 /// are required to implement a few methods, such as \ref Graph.GetNeighbors
71 /// and \ref Graph.GetCost for the A\* path-finding algorithm to work.
72 template<typename T>
73 class Graph
74 {
75 public:
76 using NodePtr = std::shared_ptr<Node<T>>; ///< Helper alias to make code
77 ///< more readable.
78
79 public:
80 /// \brief Collects all neighbor nodes of \p node.
81 ///
82 /// Implemented by sub-classes of Graph.
83 /// The neighbor relationship between nodes define the edges of the
84 /// graph. It is up to the subclass to define these relationships. For a
85 /// 2D grid, the neighbors would simply be the nodes directly to the
86 /// north, south, east and west, in addition to the corners between
87 /// them. For a road network, the relationships may be more complex.
88 ///
89 /// \param node A pointer to the node from which to collect all
90 /// neighbors \return A vector of pointers to all the neighbors of \p
91 /// node
92 virtual std::vector<NodePtr> GetNeighbors(NodePtr node) = 0;
93
94 /// \brief Returns the cost between \p a and \p b.
95 ///
96 /// Implemented by sub-classes of Graph.
97 /// The a\* path-finding algorithm uses cost to efficiently find the
98 /// best path between two nodes. In order to do this, it requires some
99 /// method of calculating the cost of moving between any two nodes. It
100 /// is up to the sub-class to define how this is calulated. An example
101 /// of this cost may be the euclidean distance between two nodes.
102 ///
103 /// \param a Pointer to the first \ref Node
104 /// \param b Pointer to the second \ref Node
105 /// \return Cost between node \p a and node \p b.
106 virtual double GetCost(NodePtr a, NodePtr b) = 0;
107
108 /// \brief Determines if there is a direct line of sight between node \p
109 /// a and node \p b.
110 ///
111 /// Implemented by sub-classes of Graph.
112 /// The \ref Graph.PostSmooth method traverses the already found path
113 /// through the A\* path-finding algorithm and simplifies it by using
114 /// this method. In a graph representing a 2D grid, a Bresenham
115 /// implementation or ray-casting can be used to determine line of
116 /// sight.
117 ///
118 /// \param a Pointer to the first \ref Node
119 /// \param b Pointer to the second \ref Node
120 /// \return bool specifying whether or not there is a direct line of
121 /// sight
122 virtual bool HasLineOfSight(NodePtr a, NodePtr b) = 0;
123
124 /// \brief Resets all local and global goals and parent relationships of
125 /// all nodes.
126 ///
127 /// Implemented by sub-classes of Graph.
128 /// In order to be able to re-use the same graph for several A\*
129 /// searches, the \ref Graph.SolveAStar method needs to be able to reset
130 /// all the nodes. As this interface does not contain the actual
131 /// collection of nodes, this needs to be implemented in the
132 /// sub-classes.
133 virtual void ResetNodes(void) = 0;
134
135 /// \brief Finds *cheapest* path from \p start to \p goal.
136 ///
137 /// \param start Pointer to the node to start the path from
138 /// \param goal Pointer to the node to find the path to
139 ///
140 /// Using the A\* algorithm, this method explores the graph's nodes and
141 /// updates their local and global goals, their visited flags, as well
142 /// as their parent relationships.
143 ///
144 /// When the algorithm finishes, given that a path exists between the
145 /// nodes, the cheapest path between them is defined by the parent
146 /// relationships. The path can be *extracted* by starting at the \p
147 /// goal and following the \ref Node.Parent pointers until \p start is
148 /// reached, saving each node in a list and reversing it at the end.
149 void SolveAStar(NodePtr start, NodePtr goal);
150
151 /// \brief Simplifies the path from \p start to \p goal.
152 ///
153 /// \param start Pointer to the start node of the path
154 /// \param goal Pointer to the end node of the path
155 ///
156 /// Should be run on the same nodes as \ref Graph.SolveAStar, and should
157 /// only be called after \ref Graph.SolveAStar has finished.
158 ///
159 ///
160 void PostSmooth(NodePtr start, NodePtr goal);
161 };
162
163 template<typename T>
164 void
166 {
167 ResetNodes(); // Make sure all relational values are reset
168
169 NodePtr current = start;
170 current->LocalGoal = 0.0;
171 current->GlobalGoal = GetCost(current, goal);
172
173 // Create a priority queue that compares nodes' global goal value
174 std::priority_queue<NodePtr, std::vector<NodePtr>,
175 std::function<bool(NodePtr, NodePtr)>>
176 notTested([](NodePtr a, NodePtr b) {
177 return a->GlobalGoal > b->GlobalGoal;
178 });
179
180 notTested.push(start);
181
182 // Let's go for all long as we have untested discovered nodes
183 while (!notTested.empty()) {
184 // But in case we have already discovered nodes in our list, let's
185 // remove them
186 while (!notTested.empty() && notTested.top()->Visited) {
187 notTested.pop();
188 }
189
190 // If we ended up removing some nodes, and we are now out of
191 // untested nodes, let's break from the loop
192 if (notTested.empty()) {
193 break;
194 }
195
196 current = notTested.top();
197 current->Visited = true;
198
199 for (auto neighbor : GetNeighbors(current)) {
200 // We only want to explore unoccupied cells.
201 if (!neighbor->Visited && !neighbor->Data.Occupied) {
202 notTested.push(neighbor);
203 }
204
205 // Let's calculate the cost of the travel to this node so far +
206 // the cost to get from here to the neighbor, and update the
207 // neighbors relational values if it is a new record for the
208 // neighbor.
209 double candidateGoal =
210 current->LocalGoal + GetCost(current, neighbor);
211 if (candidateGoal < neighbor->LocalGoal) {
212 neighbor->Parent = current;
213 neighbor->LocalGoal = candidateGoal;
214 neighbor->GlobalGoal =
215 neighbor->LocalGoal + GetCost(neighbor, goal);
216 }
217 }
218 }
219 }
220
221 // Quite a simple little algorithm to simplify and smooth out a path found
222 // through A*: We just start at the goal, and check if we have a direct line
223 // of sight to our grandparent. If we do, then we can remove the middle man,
224 // our parent, from the equation and make our grandparent our parent
225 // instead. Then check again for our new grandparent. If we do not have a
226 // direct line of sight to our grandparent, we move on to our parent and
227 // check its grandparent. We do this recursively until we reach the start
228 // node.
229 template<typename T>
230 void
232 {
233 NodePtr current = goal;
234 NodePtr parent = current->Parent.lock();
235 while (current && parent && (current != start)) {
236 NodePtr grandParent = parent->Parent.lock();
237 if (!grandParent) {
238 break;
239 }
240 if (HasLineOfSight(current, grandParent)) {
241 current->Parent = grandParent;
242 parent = grandParent;
243 continue;
244 }
245 current = parent;
246 parent = current->Parent.lock();
247 }
248 }
249
250} // namespace Routemaker
Abstract graph interface optimized for path-finding.
Definition: graph.h:74
void PostSmooth(NodePtr start, NodePtr goal)
Simplifies the path from start to goal.
Definition: graph.h:231
virtual void ResetNodes(void)=0
Resets all local and global goals and parent relationships of all nodes.
virtual std::vector< NodePtr > GetNeighbors(NodePtr node)=0
Collects all neighbor nodes of node.
virtual bool HasLineOfSight(NodePtr a, NodePtr b)=0
Determines if there is a direct line of sight between node a and node b.
void SolveAStar(NodePtr start, NodePtr goal)
Finds cheapest path from start to goal.
Definition: graph.h:165
std::shared_ptr< Node< T > > NodePtr
Helper alias to make code more readable.
Definition: graph.h:77
virtual double GetCost(NodePtr a, NodePtr b)=0
Returns the cost between a and b.
Represents a node in a Graph data structured made for path-finding.
Definition: graph.h:18
T Data
Data stored in the the node.
Definition: graph.h:23
std::weak_ptr< Node< T > > Parent
A non-owner pointer to the parent of the node.
Definition: graph.h:30
bool Visited
Specifies if a given node has been visited during path-finding.
Definition: graph.h:39
double LocalGoal
Represents the cost from the start node to this node.
Definition: graph.h:62
double GlobalGoal
Represents the assumed cost from the start to the goal node through this node.
Definition: graph.h:52