AStarPlanner.cpp
Go to the documentation of this file.
1#include "AStarPlanner.h"
2
3#include <algorithm>
4#include <cmath>
5#include <cstddef>
6#include <map>
7#include <memory>
8#include <vector>
9
10#include <boost/geometry.hpp> // IWYU pragma: keep
11#include <boost/geometry/algorithms/detail/intersects/interface.hpp>
12#include <boost/geometry/core/cs.hpp>
13#include <boost/geometry/geometries/box.hpp>
14#include <boost/geometry/geometries/point.hpp>
15
16#include <Eigen/Geometry>
17
18#include <VirtualRobot/BoundingBox.h>
19
23
26
27#include <omp.h>
28#include <range/v3/algorithm/reverse.hpp>
29
30namespace bg = boost::geometry;
31
33{
34 namespace
35 {
36 bool
37 intersects(const VirtualRobot::BoundingBox& bb1, const VirtualRobot::BoundingBox& bb2)
38 {
39 using point_t = bg::model::point<double, 3, bg::cs::cartesian>;
40 using box_t = bg::model::box<point_t>;
41
42 const auto toPoint = [](const Eigen::Vector3f& pt)
43 { return point_t(pt.x(), pt.y(), pt.z()); };
44
45 const box_t box1(toPoint(bb1.getMin()), toPoint(bb1.getMax()));
46 const box_t box2(toPoint(bb2.getMin()), toPoint(bb2.getMax()));
47
48 return bg::intersects(box1, box2);
49 }
50 } // namespace
51
52 AStarPlanner::AStarPlanner(const algorithms::Costmap& costmap) : costmap(costmap)
53 {
54 }
55
56 float
57 AStarPlanner::heuristic(const NodePtr& n1, const NodePtr& n2)
58 {
59 return (1.F - obstacleDistanceWeightFactor) * (n1->position - n2->position).norm();
60 }
61
62 void
63 AStarPlanner::createUniformGrid()
64 {
66
67 const size_t rows = costmap.getGrid().rows();
68 const size_t cols = costmap.getGrid().cols();
69
70 // create the grid with the correct size
71 ARMARX_VERBOSE << "Creating grid";
72 for (size_t r = 0; r < rows; r++)
73 {
74 grid.push_back(std::vector<NodePtr>(cols));
75 }
76
77 // intitializing grid
78 for (size_t r = 0; r < rows; r++)
79 {
80 for (size_t c = 0; c < cols; c++)
81 {
82 const auto pos = costmap.toPositionGlobal({r, c});
83 const float obstacleDistance = costmap.getGrid()(r, c);
84
85 grid[r][c] = std::make_shared<Node>(pos, obstacleDistance);
86 }
87 }
88
89 ARMARX_VERBOSE << "Creating graph";
90
91 // Init successors
92 for (size_t r = 0; r < rows; r++)
93 {
94 for (size_t c = 0; c < cols; c++)
95 {
96 NodePtr candidate = grid[r][c];
97 if (!fulfillsConstraints(candidate))
98 {
99 continue;
100 }
101
102 // Add the valid node as successor to all its neighbors
103 for (int nR = -1; nR <= 1; nR++)
104 {
105 for (int nC = -1; nC <= 1; nC++)
106 {
107 const int neighborIndexC = c + nC;
108 const int neighborIndexR = r + nR;
109 if (neighborIndexC < 0 || neighborIndexR < 0 || (nR == 0 && nC == 0))
110 {
111 continue;
112 }
113
114 if (neighborIndexR >= static_cast<int>(rows) ||
115 neighborIndexC >= static_cast<int>(cols))
116 {
117 continue;
118 }
119
120 grid[neighborIndexR][neighborIndexC]->successors.push_back(candidate);
121 }
122 }
123 }
124 }
125
126 ARMARX_VERBOSE << "Done.";
127 }
128
129 bool
130 AStarPlanner::fulfillsConstraints(const NodePtr& n)
131 {
134
135 return not costmap.isInCollision(n->position);
136 }
137
138 NodePtr
139 AStarPlanner::closestNode(const Eigen::Vector2f& v)
140 {
141 const auto vertex = costmap.toVertex(v);
142
143 const int r = vertex.index.x();
144 const int c = vertex.index.y();
145
148
149 const int rows = static_cast<int>(costmap.getGrid().rows());
150 const int cols = static_cast<int>(costmap.getGrid().cols());
151
152 ARMARX_CHECK_LESS_EQUAL(r, rows - 1);
153 ARMARX_CHECK_LESS_EQUAL(c, cols - 1);
154
155 return grid[static_cast<int>(r)][static_cast<int>(c)];
156 }
157
158 std::vector<Eigen::Vector2f>
159 AStarPlanner::plan(const Eigen::Vector2f& start, const Eigen::Vector2f& goal)
160 {
162 grid.clear();
163 std::vector<Eigen::Vector2f> result;
164
165 createUniformGrid();
166 ARMARX_DEBUG << "Setting start node for position " << start;
167 NodePtr nodeStart = closestNode(start);
168 // FIXME activate ARMARX_CHECK(fulfillsConstraints(nodeStart)) << "Start node in collision!";
169
170 ARMARX_DEBUG << "Setting goal node for position " << goal;
171 NodePtr nodeGoal = closestNode(goal);
172 ARMARX_CHECK(fulfillsConstraints(nodeGoal)) << "Goal node in collision!";
173
174 std::vector<NodePtr> closedSet;
175
176 std::vector<NodePtr> openSet;
177 openSet.push_back(nodeStart);
178
179 std::map<NodePtr, float> gScore;
180 gScore[nodeStart] = 0;
181
182 std::map<NodePtr, float> fScore;
183 fScore[nodeStart] = gScore[nodeStart] + heuristic(nodeStart, nodeGoal);
184
185 while (!openSet.empty())
186 {
187 float lowestScore = fScore[openSet[0]];
188 auto currentIT = openSet.begin();
189 for (auto it = openSet.begin() + 1; it != openSet.end(); it++)
190 {
191 if (fScore[*it] < lowestScore)
192 {
193 lowestScore = fScore[*it];
194 currentIT = it;
195 }
196 }
197 NodePtr currentBest = *currentIT;
198 if (currentBest == nodeGoal)
199 {
200 break;
201 }
202
203 openSet.erase(currentIT);
204 closedSet.push_back(currentBest);
205 for (size_t i = 0; i < currentBest->successors.size(); i++)
206 {
207 NodePtr neighbor = currentBest->successors[i];
208 if (std::find(closedSet.begin(), closedSet.end(), neighbor) != closedSet.end())
209 {
210 continue;
211 }
212
213 float tentativeGScore = gScore[currentBest] + costs(currentBest, neighbor);
214 bool notInOS = std::find(openSet.begin(), openSet.end(), neighbor) == openSet.end();
215 if (notInOS || tentativeGScore < gScore[neighbor])
216 {
217 neighbor->predecessor = currentBest;
218 gScore[neighbor] = tentativeGScore;
219 fScore[neighbor] = tentativeGScore + heuristic(neighbor, nodeGoal);
220 if (notInOS)
221 {
222 openSet.push_back(neighbor);
223 }
224 }
225 }
226 }
227
228 // Found solution, now retrieve path from goal to start
229 if (nodeGoal)
230 {
231 result = nodeGoal->traversePredecessors();
232 }
233
234 // Since the graph was traversed from goal to start, we have to reverse the order
235 ranges::reverse(result);
236
237 return result;
238 }
239
240 float
241 AStarPlanner::costs(const NodePtr& n1, const NodePtr& n2) const
242 {
243 const float euclideanDistance = (n1->position - n2->position).norm();
244
245 // additional costs if
246 const float obstacleDistanceN1 = std::min(n1->obstacleDistance, maxObstacleDistance);
247 const float obstacleDistanceN2 = std::min(n2->obstacleDistance, maxObstacleDistance);
248
249 // const float obstacleProximityChangeCosts = std::max(obstacleDistanceN1 - obstacleDistanceN2, 0.F);
250 const float obstacleProximityChangeCosts = obstacleDistanceN1 - obstacleDistanceN2;
251
252 const float costs =
253 euclideanDistance + obstacleProximityChangeCosts * obstacleDistanceWeightFactor;
254 return std::max(costs, 0.F);
255 }
256
257
258} // namespace armarx::navigation::algorithm::astar
constexpr T c
std::vector< Eigen::Vector2f > plan(const Eigen::Vector2f &start, const Eigen::Vector2f &goal)
EIGEN_MAKE_ALIGNED_OPERATOR_NEW AStarPlanner(const algorithms::Costmap &costmap)
Position toPositionGlobal(const Index &index) const
Definition Costmap.cpp:75
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_LESS_EQUAL(lhs, rhs)
This macro evaluates whether lhs is less or equal (<=) rhs and if it turns out to be false it will th...
#define ARMARX_CHECK_GREATER_EQUAL(lhs, rhs)
This macro evaluates whether lhs is greater or equal (>=) rhs and if it turns out to be false it will...
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
std::shared_ptr< Node > NodePtr
Definition Node.h:11
float euclideanDistance(IteratorType1 first1, IteratorType1 last1, IteratorType2 first2)
Returns the euclidean distance.
Definition Metrics.h:104
constexpr auto n() noexcept
double norm(const Point &a)
Definition point.hpp:102
#define ARMARX_TRACE
Definition trace.h:77