26 #include "ompl/base/ScopedState.h"
27 #include "ompl/base/spaces/DubinsStateSpace.h"
28 #include "ompl/base/spaces/ReedsSheppStateSpace.h"
30 #include "nav2_smac_planner/node_hybrid.hpp"
32 using namespace std::chrono;
34 namespace nav2_smac_planner
46 void HybridMotionTable::initDubin(
47 unsigned int & size_x_in,
49 unsigned int & num_angle_quantization_in,
53 change_penalty = search_info.change_penalty;
54 non_straight_penalty = search_info.non_straight_penalty;
55 cost_penalty = search_info.cost_penalty;
56 reverse_penalty = search_info.reverse_penalty;
57 travel_distance_reward = 1.0f - search_info.retrospective_penalty;
58 downsample_obstacle_heuristic = search_info.downsample_obstacle_heuristic;
59 use_quadratic_cost_penalty = search_info.use_quadratic_cost_penalty;
62 if (num_angle_quantization_in == num_angle_quantization &&
63 min_turning_radius == search_info.minimum_turning_radius &&
64 allow_primitive_interpolation == search_info.allow_primitive_interpolation &&
65 motion_model == MotionModel::DUBIN)
70 num_angle_quantization = num_angle_quantization_in;
71 num_angle_quantization_float =
static_cast<float>(num_angle_quantization);
72 min_turning_radius = search_info.minimum_turning_radius;
73 allow_primitive_interpolation = search_info.allow_primitive_interpolation;
74 motion_model = MotionModel::DUBIN;
86 float angle = 2.0 * asin(sqrt(2.0) / (2 * min_turning_radius));
90 2.0f *
static_cast<float>(M_PI) /
static_cast<float>(num_angle_quantization);
92 if (angle < bin_size) {
97 increments = ceil(angle / bin_size);
99 angle = increments * bin_size;
104 const float delta_x = min_turning_radius * sin(angle);
107 const float delta_y = min_turning_radius - (min_turning_radius * cos(angle));
108 const float delta_dist = hypotf(delta_x, delta_y);
111 projections.reserve(3);
112 projections.emplace_back(delta_dist, 0.0, 0.0, TurnDirection::FORWARD);
113 projections.emplace_back(delta_x, delta_y, increments, TurnDirection::LEFT);
114 projections.emplace_back(delta_x, -delta_y, -increments, TurnDirection::RIGHT);
116 if (search_info.allow_primitive_interpolation && increments > 1.0f) {
120 projections.reserve(3 + (2 * (increments - 1)));
121 for (
unsigned int i = 1; i < static_cast<unsigned int>(increments); i++) {
122 const float angle_n =
static_cast<float>(i) * bin_size;
123 const float turning_rad_n = delta_dist / (2.0f * sin(angle_n / 2.0f));
124 const float delta_x_n = turning_rad_n * sin(angle_n);
125 const float delta_y_n = turning_rad_n - (turning_rad_n * cos(angle_n));
126 projections.emplace_back(
127 delta_x_n, delta_y_n,
static_cast<float>(i), TurnDirection::LEFT);
128 projections.emplace_back(
129 delta_x_n, -delta_y_n, -
static_cast<float>(i), TurnDirection::RIGHT);
134 state_space = std::make_shared<ompl::base::DubinsStateSpace>(min_turning_radius);
137 delta_xs.resize(projections.size());
138 delta_ys.resize(projections.size());
139 trig_values.resize(num_angle_quantization);
141 for (
unsigned int i = 0; i != projections.size(); i++) {
142 delta_xs[i].resize(num_angle_quantization);
143 delta_ys[i].resize(num_angle_quantization);
145 for (
unsigned int j = 0; j != num_angle_quantization; j++) {
146 double cos_theta = cos(bin_size * j);
147 double sin_theta = sin(bin_size * j);
150 trig_values[j] = {cos_theta, sin_theta};
152 delta_xs[i][j] = projections[i]._x * cos_theta - projections[i]._y * sin_theta;
153 delta_ys[i][j] = projections[i]._x * sin_theta + projections[i]._y * cos_theta;
158 travel_costs.resize(projections.size());
159 for (
unsigned int i = 0; i != projections.size(); i++) {
160 const TurnDirection turn_dir = projections[i]._turn_dir;
161 if (turn_dir != TurnDirection::FORWARD && turn_dir != TurnDirection::REVERSE) {
163 const float arc_angle = projections[i]._theta * bin_size;
164 const float turning_rad = delta_dist / (2.0f * sin(arc_angle / 2.0f));
165 travel_costs[i] = turning_rad * arc_angle;
167 travel_costs[i] = delta_dist;
175 void HybridMotionTable::initReedsShepp(
176 unsigned int & size_x_in,
178 unsigned int & num_angle_quantization_in,
182 change_penalty = search_info.change_penalty;
183 non_straight_penalty = search_info.non_straight_penalty;
184 cost_penalty = search_info.cost_penalty;
185 reverse_penalty = search_info.reverse_penalty;
186 travel_distance_reward = 1.0f - search_info.retrospective_penalty;
187 downsample_obstacle_heuristic = search_info.downsample_obstacle_heuristic;
188 use_quadratic_cost_penalty = search_info.use_quadratic_cost_penalty;
191 if (num_angle_quantization_in == num_angle_quantization &&
192 min_turning_radius == search_info.minimum_turning_radius &&
193 allow_primitive_interpolation == search_info.allow_primitive_interpolation &&
194 motion_model == MotionModel::REEDS_SHEPP)
199 num_angle_quantization = num_angle_quantization_in;
200 num_angle_quantization_float =
static_cast<float>(num_angle_quantization);
201 min_turning_radius = search_info.minimum_turning_radius;
202 allow_primitive_interpolation = search_info.allow_primitive_interpolation;
203 motion_model = MotionModel::REEDS_SHEPP;
205 float angle = 2.0 * asin(sqrt(2.0) / (2 * min_turning_radius));
207 2.0f *
static_cast<float>(M_PI) /
static_cast<float>(num_angle_quantization);
209 if (angle < bin_size) {
212 increments = ceil(angle / bin_size);
214 angle = increments * bin_size;
216 const float delta_x = min_turning_radius * sin(angle);
217 const float delta_y = min_turning_radius - (min_turning_radius * cos(angle));
218 const float delta_dist = hypotf(delta_x, delta_y);
221 projections.reserve(6);
222 projections.emplace_back(delta_dist, 0.0, 0.0, TurnDirection::FORWARD);
223 projections.emplace_back(
224 delta_x, delta_y, increments, TurnDirection::LEFT);
225 projections.emplace_back(
226 delta_x, -delta_y, -increments, TurnDirection::RIGHT);
227 projections.emplace_back(-delta_dist, 0.0, 0.0, TurnDirection::REVERSE);
228 projections.emplace_back(
229 -delta_x, delta_y, -increments, TurnDirection::REV_LEFT);
230 projections.emplace_back(
231 -delta_x, -delta_y, increments, TurnDirection::REV_RIGHT);
233 if (search_info.allow_primitive_interpolation && increments > 1.0f) {
237 projections.reserve(6 + (4 * (increments - 1)));
238 for (
unsigned int i = 1; i < static_cast<unsigned int>(increments); i++) {
239 const float angle_n =
static_cast<float>(i) * bin_size;
240 const float turning_rad_n = delta_dist / (2.0f * sin(angle_n / 2.0f));
241 const float delta_x_n = turning_rad_n * sin(angle_n);
242 const float delta_y_n = turning_rad_n - (turning_rad_n * cos(angle_n));
243 projections.emplace_back(
244 delta_x_n, delta_y_n,
static_cast<float>(i), TurnDirection::LEFT);
245 projections.emplace_back(
246 delta_x_n, -delta_y_n, -
static_cast<float>(i), TurnDirection::RIGHT);
247 projections.emplace_back(
248 -delta_x_n, delta_y_n, -
static_cast<float>(i),
249 TurnDirection::REV_LEFT);
250 projections.emplace_back(
251 -delta_x_n, -delta_y_n,
static_cast<float>(i),
252 TurnDirection::REV_RIGHT);
257 state_space = std::make_shared<ompl::base::ReedsSheppStateSpace>(min_turning_radius);
260 delta_xs.resize(projections.size());
261 delta_ys.resize(projections.size());
262 trig_values.resize(num_angle_quantization);
264 for (
unsigned int i = 0; i != projections.size(); i++) {
265 delta_xs[i].resize(num_angle_quantization);
266 delta_ys[i].resize(num_angle_quantization);
268 for (
unsigned int j = 0; j != num_angle_quantization; j++) {
269 double cos_theta = cos(bin_size * j);
270 double sin_theta = sin(bin_size * j);
273 trig_values[j] = {cos_theta, sin_theta};
275 delta_xs[i][j] = projections[i]._x * cos_theta - projections[i]._y * sin_theta;
276 delta_ys[i][j] = projections[i]._x * sin_theta + projections[i]._y * cos_theta;
281 travel_costs.resize(projections.size());
282 for (
unsigned int i = 0; i != projections.size(); i++) {
283 const TurnDirection turn_dir = projections[i]._turn_dir;
284 if (turn_dir != TurnDirection::FORWARD && turn_dir != TurnDirection::REVERSE) {
286 const float arc_angle = projections[i]._theta * bin_size;
287 const float turning_rad = delta_dist / (2.0f * sin(arc_angle / 2.0f));
288 travel_costs[i] = turning_rad * arc_angle;
290 travel_costs[i] = delta_dist;
295 MotionPoses HybridMotionTable::getProjections(
const NodeHybrid * node)
297 MotionPoses projection_list;
298 projection_list.reserve(projections.size());
300 for (
unsigned int i = 0; i != projections.size(); i++) {
301 const MotionPose & proj_motion_model = projections[i];
304 const float & node_heading = node->pose.theta;
305 float new_heading = node_heading + proj_motion_model._theta;
307 if (new_heading < 0.0) {
308 new_heading += num_angle_quantization_float;
311 if (new_heading >= num_angle_quantization_float) {
312 new_heading -= num_angle_quantization_float;
315 projection_list.emplace_back(
316 delta_xs[i][node_heading] + node->pose.x,
317 delta_ys[i][node_heading] + node->pose.y,
318 new_heading, proj_motion_model._turn_dir);
321 return projection_list;
324 unsigned int HybridMotionTable::getClosestAngularBin(
const double & theta)
326 auto bin =
static_cast<unsigned int>(round(
static_cast<float>(theta) / bin_size));
327 return bin < num_angle_quantization ? bin : 0u;
330 float HybridMotionTable::getAngleFromBin(
const unsigned int & bin_idx)
332 return bin_idx * bin_size;
335 double HybridMotionTable::getAngle(
const double & theta)
337 return theta / bin_size;
342 pose(0.0f, 0.0f, 0.0f),
343 _cell_cost(std::numeric_limits<float>::quiet_NaN()),
344 _accumulated_cost(std::numeric_limits<float>::max()),
347 _motion_primitive_index(std::numeric_limits<unsigned int>::max()),
348 _is_node_valid(false),
361 _cell_cost = std::numeric_limits<float>::quiet_NaN();
362 _accumulated_cost = std::numeric_limits<float>::max();
363 _was_visited =
false;
364 _motion_primitive_index = std::numeric_limits<unsigned int>::max();
368 _is_node_valid =
false;
372 const bool & traverse_unknown,
376 if (!std::isnan(_cell_cost)) {
377 return _is_node_valid;
381 this->pose.x, this->pose.y, this->pose.theta , traverse_unknown);
382 _cell_cost = collision_checker->
getCost();
383 return _is_node_valid;
388 const float normalized_cost = child->
getCost() / 252.0f;
389 if (std::isnan(normalized_cost)) {
390 throw std::runtime_error(
391 "Node attempted to get traversal "
392 "cost without a known SE2 collision cost!");
397 float travel_cost = 0.0;
399 if (_ctx->motion_table.use_quadratic_cost_penalty) {
401 (_ctx->motion_table.travel_distance_reward +
402 (_ctx->motion_table.cost_penalty * normalized_cost * normalized_cost));
405 (_ctx->motion_table.travel_distance_reward + _ctx->motion_table.cost_penalty *
409 if (child_turn_dir == TurnDirection::FORWARD || child_turn_dir == TurnDirection::REVERSE ||
413 travel_cost = travel_cost_raw;
417 travel_cost = travel_cost_raw * _ctx->motion_table.non_straight_penalty;
420 travel_cost = travel_cost_raw *
421 (_ctx->motion_table.non_straight_penalty + _ctx->motion_table.change_penalty);
425 if (child_turn_dir == TurnDirection::REV_RIGHT ||
426 child_turn_dir == TurnDirection::REV_LEFT ||
427 child_turn_dir == TurnDirection::REVERSE)
430 travel_cost *= _ctx->motion_table.reverse_penalty;
438 const CoordinateVector & goals_coords)
441 const float obstacle_heuristic =
442 _ctx->obstacle_heuristic->getObstacleHeuristic(node_coords, _ctx->motion_table.cost_penalty,
443 _ctx->motion_table.use_quadratic_cost_penalty,
444 _ctx->motion_table.downsample_obstacle_heuristic);
445 float distance_heuristic = std::numeric_limits<float>::max();
446 for (
unsigned int i = 0; i < goals_coords.size(); i++) {
447 distance_heuristic = std::min(
449 _ctx->distance_heuristic->getDistanceHeuristic(node_coords, goals_coords[i],
450 obstacle_heuristic, _ctx->motion_table));
452 return std::max(obstacle_heuristic, distance_heuristic);
457 const MotionModel & motion_model,
458 unsigned int & size_x,
459 unsigned int & size_y,
460 unsigned int & num_angle_quantization,
464 switch (motion_model) {
465 case MotionModel::DUBIN:
466 ctx->motion_table.
initDubin(size_x, size_y, num_angle_quantization, search_info);
468 case MotionModel::REEDS_SHEPP:
469 ctx->motion_table.
initReedsShepp(size_x, size_y, num_angle_quantization, search_info);
472 throw std::runtime_error(
473 "Invalid motion model for Hybrid A*. Please select between"
474 " Dubin (Ackermann forward only),"
475 " Reeds-Shepp (Ackermann forward and back).");
480 std::function<
bool(
const uint64_t &,
483 const bool & traverse_unknown,
484 NodeVector & neighbors)
489 const MotionPoses motion_projections = _ctx->motion_table.
getProjections(
this);
491 for (
unsigned int i = 0; i != motion_projections.size(); i++) {
493 static_cast<unsigned int>(motion_projections[i]._x),
494 static_cast<unsigned int>(motion_projections[i]._y),
495 static_cast<unsigned int>(motion_projections[i]._theta),
496 _ctx->motion_table.size_x, _ctx->motion_table.num_angle_quantization);
498 if (NeighborGetter(index, neighbor) && !neighbor->
wasVisited()) {
501 initial_node_coords = neighbor->pose;
504 motion_projections[i]._x,
505 motion_projections[i]._y,
506 motion_projections[i]._theta));
507 if (neighbor->
isNodeValid(traverse_unknown, collision_checker)) {
509 neighbors.push_back(neighbor);
511 neighbor->
setPose(initial_node_coords);
525 while (current_node->parent) {
526 path.push_back(current_node->pose);
528 path.back().theta = _ctx->motion_table.
getAngleFromBin(path.back().theta);
529 current_node = current_node->parent;
533 path.push_back(current_node->pose);
535 path.back().theta = _ctx->motion_table.
getAngleFromBin(path.back().theta);
A costmap grid collision checker.
bool inCollision(const float &x, const float &y, const float &theta, const bool &traverse_unknown)
Check if in collision with costmap and footprint at pose.
float getCost()
Get cost at footprint pose in costmap.
NodeHybrid implementation for graph, Hybrid-A*.
uint64_t getIndex()
Gets cell index.
bool isNodeValid(const bool &traverse_unknown, GridCollisionChecker *collision_checker)
Check if this node is valid.
float getTraversalCost(const NodePtr &child)
Get traversal cost of parent node to child node.
void setPose(const Coordinates &pose_in)
setting continuous coordinate search poses (in partial-cells)
~NodeHybrid()
A destructor for nav2_smac_planner::NodeHybrid.
void getNeighbors(std::function< bool(const uint64_t &, nav2_smac_planner::NodeHybrid *&)> &validity_checker, GridCollisionChecker *collision_checker, const bool &traverse_unknown, NodeVector &neighbors)
Retrieve all valid neighbors of a node.
float getCost()
Gets the costmap cost at this node.
void reset()
Reset method for new search.
void setMotionPrimitiveIndex(const unsigned int &idx, const TurnDirection &turn_dir)
Sets the motion primitive index used to achieve node in search.
unsigned int & getMotionPrimitiveIndex()
Gets the motion primitive index used to achieve node in search.
static void initMotionModel(NodeContext *ctx, const MotionModel &motion_model, unsigned int &size_x, unsigned int &size_y, unsigned int &angle_quantization, SearchInfo &search_info)
Initialize motion models.
bool wasVisited()
Gets if cell has been visited in search.
float getHeuristicCost(const Coordinates &node_coords, const CoordinateVector &goals_coords)
Get cost of heuristic of node.
TurnDirection & getTurnDirection()
Gets the motion primitive turning direction used to achieve node in search.
bool backtracePath(CoordinateVector &path)
Set the starting pose for planning, as a node index.
Implementation of coordinate2d structure.
void initReedsShepp(unsigned int &size_x_in, unsigned int &size_y_in, unsigned int &angle_quantization_in, SearchInfo &search_info)
Initializing using Reeds-Shepp model.
MotionPoses getProjections(const NodeHybrid *node)
Get projections of motion models.
float getAngleFromBin(const unsigned int &bin_idx)
Get the raw orientation from an angular bin.
void initDubin(unsigned int &size_x_in, unsigned int &size_y_in, unsigned int &angle_quantization_in, SearchInfo &search_info)
Initializing using Dubin model.
A struct for poses in motion primitives.
Search properties and penalties.