hivemind 1.0.0
Loading...
Searching...
No Matches
routemaker.cpp
Go to the documentation of this file.
2
4
5#include <algorithm>
6#include <array>
7#include <cassert>
8#include <cmath>
9
10// Temporary: When 3D, drone height should vary
11#define DRONE_FLIGHT_HEIGHT 175
12
13namespace Routemaker
14{
16 : m_MapWidth(size),
17 m_HeightMap(std::make_unique<HeightManagement::HeightManager>())
18 {
19 UpdateOrigin(origin, size);
20 }
21
22 // Relational data that forms paths need to be reset before running A*
23 // again. This method serves as a simple way to make sure all of these
24 // values are reset.
25 void
27 {
28 for (uint32_t y{}; y < m_RoutemakerWidth; ++y) {
29 for (uint32_t x{}; x < m_RoutemakerWidth; ++x) {
30 NodePtr node{ GetNode(x, y) };
31 node->Parent = std::weak_ptr<Node<Cell2D>>();
32 node->GlobalGoal = std::numeric_limits<double>::infinity();
33 node->LocalGoal = std::numeric_limits<double>::infinity();
34 node->Visited = false;
35 }
36 }
37 }
38
39 // In order to improve efficiency, the resolution of routemaker is adjusted
40 // based on the size of the scenario. If you are working on a 2km scale, you
41 // probably don't need 1 meter fidelity. This should maybe be adjustable as
42 // part of the scenario settings in the future.
43 void
45 {
46 if (m_MapWidth < 250) {
47 m_RoutemakerRes = 1;
48 } else if (m_MapWidth < 500) {
49 m_RoutemakerRes = 2;
50 } else if (m_MapWidth < 1000) {
51 m_RoutemakerRes = 3;
52 } else if (m_MapWidth < 2000) {
53 m_RoutemakerRes = 4;
54 } else {
55 m_RoutemakerRes = 5;
56 }
57
58 m_RoutemakerWidth = m_MapWidth / m_RoutemakerRes;
59 }
60
61 // Whenever the scenario's origin or size is updated, we need to create a
62 // new graph with the proper height data
63 void
65 {
66 m_HeightMap->UpdateOrigin(utmOrigin, size);
67 m_MapWidth = size;
68 UpdateResolution();
69 m_Nodes = std::vector<NodePtr>(m_RoutemakerWidth * m_RoutemakerWidth);
70
71 for (uint32_t y{ 0 }; y < m_RoutemakerWidth; ++y) {
72 for (uint32_t x{ 0 }; x < m_RoutemakerWidth; ++x) {
73 uint32_t xRel{ x * m_RoutemakerRes };
74 uint32_t yRel{ y * m_RoutemakerRes };
75 float height{ 0.0f };
76 bool occupied{ false };
77 for (int j{ 0 }; j < m_RoutemakerRes; ++j) {
78 for (int i{ 0 }; i < m_RoutemakerRes; ++i) {
79 float heightCandidate;
80 if (m_HeightMap->GetHeight(static_cast<int>(xRel) + i,
81 static_cast<int>(yRel) + j,
82 heightCandidate)) {
83 height = std::max(height, heightCandidate);
84 }
85 // Set occupied to true if any of the heights are larger
86 // tha DRONE_FLIGHT_HEIGHT
87 occupied = (occupied || (height > DRONE_FLIGHT_HEIGHT));
88 }
89 }
90 Node<Cell2D> node{};
91 node.Data = { x, y, occupied };
92 m_Nodes[x + y * m_RoutemakerWidth] =
93 std::make_shared<Node<Cell2D>>(node);
94 }
95 }
96 }
97
98 // Not a pretty method, but does the job. Currently, the routemaker is
99 // considering a 2D space, keeping the drones at the same altitude. In the
100 // future, we need to consider 3D, meaning the GetNeighbors method needs to
101 // consider neighbors above and below the current node as well.
102 std::vector<Routemaker::NodePtr>
104 {
105 std::vector<NodePtr> neighbors;
106
107 uint32_t x{ node->Data.X };
108 uint32_t y{ node->Data.Y };
109
110 if (x > 0) {
111 NodePtr neighbor{ GetNode(x - 1, y) };
112 if (!neighbor->Data.Occupied) {
113 neighbors.push_back(neighbor);
114 }
115 }
116 if (x < m_RoutemakerWidth - 1) {
117 NodePtr neighbor{ GetNode(x + 1, y) };
118 if (!neighbor->Data.Occupied) {
119 neighbors.push_back(neighbor);
120 }
121 }
122 if (y > 0) {
123 NodePtr neighbor{ GetNode(x, y - 1) };
124 if (!neighbor->Data.Occupied) {
125 neighbors.push_back(neighbor);
126 }
127 }
128
129 if (y < m_RoutemakerWidth - 1) {
130 NodePtr neighbor{ GetNode(x, y + 1) };
131 if (!neighbor->Data.Occupied) {
132 neighbors.push_back(neighbor);
133 }
134 }
135
136 if ((x > 0) && (y > 0)) {
137 NodePtr neighbor{ GetNode(x - 1, y - 1) };
138 if (!neighbor->Data.Occupied) {
139 neighbors.push_back(neighbor);
140 }
141 }
142
143 if ((x < (m_RoutemakerWidth - 1)) && (y > 0)) {
144 NodePtr neighbor{ GetNode(x + 1, y - 1) };
145 if (!neighbor->Data.Occupied) {
146 neighbors.push_back(neighbor);
147 }
148 }
149
150 if ((x < (m_RoutemakerWidth - 1)) && (y < (m_RoutemakerWidth - 1))) {
151 NodePtr neighbor{ GetNode(x + 1, y + 1) };
152 if (!neighbor->Data.Occupied) {
153 neighbors.push_back(neighbor);
154 }
155 }
156
157 if ((x > 0) && (y < (m_RoutemakerWidth - 1))) {
158 NodePtr neighbor{ GetNode(x - 1, y + 1) };
159 if (!neighbor->Data.Occupied) {
160 neighbors.push_back(neighbor);
161 }
162 }
163
164 return neighbors;
165 }
166
167 double
168 Routemaker::GetCost(NodePtr a, NodePtr b)
169 {
170 double x1{ static_cast<double>(a->Data.X) * m_RoutemakerRes };
171 double y1{ static_cast<double>(a->Data.Y) * m_RoutemakerRes };
172 double x2{ static_cast<double>(b->Data.X) * m_RoutemakerRes };
173 double y2{ static_cast<double>(b->Data.Y) * m_RoutemakerRes };
174
175 // We are using a standard cartesian grid, with each cell being one
176 // node. As such, Euclidean distance is a nice measure of cost.
177 return std::sqrt(std::pow(x2 - x1, 2) + std::pow(y2 - y1, 2));
178 }
179
180 // Bresenham's algorithm is a fine starting point for detecting line of
181 // sight. However, for better accuracy and more scalability for 3D,
182 // Ray-casting should probably be considered in the future.
183 bool
184 Routemaker::HasLineOfSight(NodePtr a, NodePtr b)
185 {
186 // Assume we have a line of sight
187 bool hasLineOfSight{ true };
188
189 std::list<NodePtr> nodes{ BresenhamLine(a, b) };
190 std::for_each(nodes.begin(), nodes.end(),
191 [&hasLineOfSight](const NodePtr& n) {
192 if (n->Data.Occupied) {
193 // If any nodes are occupied, there is no line of
194 // sight.
195 hasLineOfSight = false;
196 }
197 });
198
199 return hasLineOfSight;
200 }
201
202 std::list<Routemaker::NodePtr>
203 Routemaker::BresenhamLine(const NodePtr& a, const NodePtr& b) const
204 {
205 std::list<NodePtr> list;
206
207 auto x1{ static_cast<int32_t>(a->Data.X) };
208 auto y1{ static_cast<int32_t>(a->Data.Y) };
209 auto x2{ static_cast<int32_t>(b->Data.X) };
210 auto y2{ static_cast<int32_t>(b->Data.Y) };
211
212 bool isSteep{ std::abs(y2 - y1) > std::abs(x2 - x1) };
213 if (isSteep) {
214 std::swap(x1, y1);
215 std::swap(x2, y2);
216 }
217 if (x1 > x2) {
218 std::swap(x1, x2);
219 std::swap(y1, y2);
220 }
221
222 int32_t deltaX{ x2 - x1 };
223 int32_t deltaY{ std::abs(y2 - y1) };
224 int32_t error{}, yStep, y{ y1 };
225
226 if (y1 < y2) {
227 yStep = 1;
228 } else {
229 yStep = -1;
230 }
231
232 for (int32_t x{ x1 }; x <= x2; ++x) {
233 if (isSteep) {
234 list.push_back(GetNode(y, x));
235 } else {
236 list.push_back(GetNode(x, y));
237 }
238
239 error += deltaY;
240 if (2 * error >= deltaX) {
241 y += yStep;
242 error -= deltaX;
243 }
244 }
245
246 return list;
247 }
248
250 Routemaker::GetNode(uint32_t x, uint32_t y) const
251 {
252 uint32_t index{ x + y * m_RoutemakerWidth };
253 return m_Nodes[index];
254 }
255
256 std::vector<Core::CartesianCoordinate>
257 Routemaker::MakeRoute(const Core::Keyframe& a, const Core::Keyframe& b)
258 {
259 // Scenario class should have sorted the keyframes already, but just to
260 // make sure:
261 assert(b.TimeStamp > a.TimeStamp && a.AgentId == b.AgentId);
262
263 // Keyframes store positions in a symmetric space. Let's make them
264 // symmetric to fit our grid.
265 Core::CartesianCoordinate asymmetricAPosition{
267 };
268 Core::CartesianCoordinate asymmetricBPosition{
270 };
271
272 // Let's also divide by our resolution to find the cells each position
273 // fits in.
274 asymmetricAPosition.X /= m_RoutemakerRes;
275 asymmetricAPosition.Y /= m_RoutemakerRes;
276 asymmetricAPosition.Z /= m_RoutemakerRes;
277
278 asymmetricBPosition.X /= m_RoutemakerRes;
279 asymmetricBPosition.Y /= m_RoutemakerRes;
280 asymmetricBPosition.Z /= m_RoutemakerRes;
281
282 NodePtr start{ GetNode(static_cast<uint32_t>(asymmetricAPosition.X),
283 static_cast<uint32_t>(asymmetricAPosition.Y)) };
284 NodePtr goal{ GetNode(static_cast<uint32_t>(asymmetricBPosition.X),
285 static_cast<uint32_t>(asymmetricBPosition.Y)) };
286
287 SolveAStar(start, goal); // We find the path
288 PostSmooth(start, goal); // We make it smooth(er)
289
290 // After the path has been generated, we start at the goal, and
291 // recursively travel to the start, collecting all positions that make
292 // up the route
293 std::vector<Core::CartesianCoordinate> route;
294 NodePtr current{ goal };
295 NodePtr parent{ goal->Parent.lock() };
296 while (current != start && parent != nullptr) {
297 // We scale up by resolution again
299 (float)current->Data.X * m_RoutemakerRes,
300 (float)current->Data.Y * m_RoutemakerRes, DRONE_FLIGHT_HEIGHT
301 };
302
303 // Scenario likes symmetric positions, so let's transform back
304 Core::CartesianCoordinate positionSymmetric{
306 };
307 route.push_back(positionSymmetric);
308 current = parent;
309 parent = current->Parent.lock();
310 }
311 Core::CartesianCoordinate positionSymmetric{
313 { (float)start->Data.X * m_RoutemakerRes,
314 (float)start->Data.Y * m_RoutemakerRes, DRONE_FLIGHT_HEIGHT })
315 };
316 route.push_back(positionSymmetric);
317
318 // Finally, let's reverse the path, so it goes from start to goal,
319 // rather than from goal to start.
320 std::reverse(route.begin(), route.end());
321
322 return route;
323 }
324
325} // namespace Routemaker
static Core::CartesianCoordinate SymmetricToAsymmetric(Core::CartesianCoordinate symmetric)
Function used to convert a coordinate in a symmetric coordinate system to a coordinate in an asymmetr...
static Core::CartesianCoordinate AsymmetricToSymmetric(Core::CartesianCoordinate asymmetric)
Function used to convert a coordinate in an asymmetric cooridnate system to a coordinate in a symmetr...
std::shared_ptr< Node< Cell2D > > NodePtr
Helper alias to make code more readable.
Definition: graph.h:77
Main class responsible for handling creation of routes between keyframes.
Definition: routemaker.h:23
void UpdateOrigin(Core::UTMCoordinate UTMOrigin, int size)
Updates the origin coordinate and the size of the map.
Definition: routemaker.cpp:64
double GetCost(NodePtr a, NodePtr b) override
Returns the cost between a and b.
Definition: routemaker.cpp:168
std::vector< NodePtr > GetNeighbors(NodePtr node) override
Collects all neighbor nodes of node.
Definition: routemaker.cpp:103
void ResetNodes() override
Resets all local and global goals and parent relationships of all nodes.
Definition: routemaker.cpp:26
bool HasLineOfSight(NodePtr a, NodePtr b) override
Determines if there is a direct line of sight between node a and node b.
Definition: routemaker.cpp:184
#define DRONE_FLIGHT_HEIGHT
Definition: routemaker.cpp:11
A structure that represents a cartesian coordinate.
Definition: types.h:12
A structure representing an agent's position in cartesian space at a given point in time.
Definition: types.h:69
CartesianCoordinate Position
Definition: types.h:78
int AgentId
Definition: types.h:76
float TimeStamp
Definition: types.h:77
\ A structure that represents a coordinate in the Universal Transverse Mercator coordinate system
Definition: types.h:46
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