25 #include "ompl/base/ScopedState.h"
26 #include "ompl/base/spaces/DubinsStateSpace.h"
27 #include "ompl/base/spaces/ReedsSheppStateSpace.h"
29 #include "nav2_smac_planner/node_hybrid.hpp"
31 using namespace std::chrono;
33 namespace nav2_smac_planner
37 LookupTable NodeHybrid::obstacle_heuristic_lookup_table;
38 double NodeHybrid::travel_distance_cost = sqrt(2);
39 HybridMotionTable NodeHybrid::motion_table;
40 float NodeHybrid::size_lookup = 25;
41 LookupTable NodeHybrid::dist_heuristic_lookup_table;
43 CostmapDownsampler NodeHybrid::downsampler;
44 ObstacleHeuristicQueue NodeHybrid::obstacle_heuristic_queue;
55 void HybridMotionTable::initDubin(
56 unsigned int & size_x_in,
58 unsigned int & num_angle_quantization_in,
62 change_penalty = search_info.change_penalty;
63 non_straight_penalty = search_info.non_straight_penalty;
64 cost_penalty = search_info.cost_penalty;
65 reverse_penalty = search_info.reverse_penalty;
66 travel_distance_reward = 1.0f - search_info.retrospective_penalty;
69 if (num_angle_quantization_in == num_angle_quantization &&
70 min_turning_radius == search_info.minimum_turning_radius &&
71 motion_model == MotionModel::DUBIN)
76 num_angle_quantization = num_angle_quantization_in;
77 num_angle_quantization_float =
static_cast<float>(num_angle_quantization);
78 min_turning_radius = search_info.minimum_turning_radius;
79 motion_model = MotionModel::DUBIN;
91 float angle = 2.0 * asin(sqrt(2.0) / (2 * min_turning_radius));
95 2.0f *
static_cast<float>(M_PI) /
static_cast<float>(num_angle_quantization);
97 if (angle < bin_size) {
102 increments = ceil(angle / bin_size);
104 angle = increments * bin_size;
109 float delta_x = min_turning_radius * sin(angle);
112 float delta_y = min_turning_radius - (min_turning_radius * cos(angle));
115 projections.reserve(3);
116 projections.emplace_back(hypotf(delta_x, delta_y), 0.0, 0.0);
117 projections.emplace_back(delta_x, delta_y, increments);
118 projections.emplace_back(delta_x, -delta_y, -increments);
121 state_space = std::make_unique<ompl::base::DubinsStateSpace>(min_turning_radius);
124 delta_xs.resize(projections.size());
125 delta_ys.resize(projections.size());
126 trig_values.resize(num_angle_quantization);
128 for (
unsigned int i = 0; i != projections.size(); i++) {
129 delta_xs[i].resize(num_angle_quantization);
130 delta_ys[i].resize(num_angle_quantization);
132 for (
unsigned int j = 0; j != num_angle_quantization; j++) {
133 double cos_theta = cos(bin_size * j);
134 double sin_theta = sin(bin_size * j);
137 trig_values[j] = {cos_theta, sin_theta};
139 delta_xs[i][j] = projections[i]._x * cos_theta - projections[i]._y * sin_theta;
140 delta_ys[i][j] = projections[i]._x * sin_theta + projections[i]._y * cos_theta;
148 void HybridMotionTable::initReedsShepp(
149 unsigned int & size_x_in,
151 unsigned int & num_angle_quantization_in,
155 change_penalty = search_info.change_penalty;
156 non_straight_penalty = search_info.non_straight_penalty;
157 cost_penalty = search_info.cost_penalty;
158 reverse_penalty = search_info.reverse_penalty;
159 travel_distance_reward = 1.0f - search_info.retrospective_penalty;
162 if (num_angle_quantization_in == num_angle_quantization &&
163 min_turning_radius == search_info.minimum_turning_radius &&
164 motion_model == MotionModel::REEDS_SHEPP)
169 num_angle_quantization = num_angle_quantization_in;
170 num_angle_quantization_float =
static_cast<float>(num_angle_quantization);
171 min_turning_radius = search_info.minimum_turning_radius;
172 motion_model = MotionModel::REEDS_SHEPP;
174 float angle = 2.0 * asin(sqrt(2.0) / (2 * min_turning_radius));
176 2.0f *
static_cast<float>(M_PI) /
static_cast<float>(num_angle_quantization);
178 if (angle < bin_size) {
181 increments = ceil(angle / bin_size);
183 angle = increments * bin_size;
185 float delta_x = min_turning_radius * sin(angle);
186 float delta_y = min_turning_radius - (min_turning_radius * cos(angle));
189 projections.reserve(6);
190 projections.emplace_back(hypotf(delta_x, delta_y), 0.0, 0.0);
191 projections.emplace_back(delta_x, delta_y, increments);
192 projections.emplace_back(delta_x, -delta_y, -increments);
193 projections.emplace_back(-hypotf(delta_x, delta_y), 0.0, 0.0);
194 projections.emplace_back(-delta_x, delta_y, -increments);
195 projections.emplace_back(-delta_x, -delta_y, increments);
198 state_space = std::make_unique<ompl::base::ReedsSheppStateSpace>(min_turning_radius);
201 delta_xs.resize(projections.size());
202 delta_ys.resize(projections.size());
203 trig_values.resize(num_angle_quantization);
205 for (
unsigned int i = 0; i != projections.size(); i++) {
206 delta_xs[i].resize(num_angle_quantization);
207 delta_ys[i].resize(num_angle_quantization);
209 for (
unsigned int j = 0; j != num_angle_quantization; j++) {
210 double cos_theta = cos(bin_size * j);
211 double sin_theta = sin(bin_size * j);
214 trig_values[j] = {cos_theta, sin_theta};
216 delta_xs[i][j] = projections[i]._x * cos_theta - projections[i]._y * sin_theta;
217 delta_ys[i][j] = projections[i]._x * sin_theta + projections[i]._y * cos_theta;
222 MotionPoses HybridMotionTable::getProjections(
const NodeHybrid * node)
224 MotionPoses projection_list;
225 projection_list.reserve(projections.size());
227 for (
unsigned int i = 0; i != projections.size(); i++) {
228 const MotionPose & motion_model = projections[i];
231 const float & node_heading = node->pose.theta;
232 float new_heading = node_heading + motion_model._theta;
234 if (new_heading < 0.0) {
235 new_heading += num_angle_quantization_float;
238 if (new_heading >= num_angle_quantization_float) {
239 new_heading -= num_angle_quantization_float;
242 projection_list.emplace_back(
243 delta_xs[i][node_heading] + node->pose.x,
244 delta_ys[i][node_heading] + node->pose.y,
248 return projection_list;
251 unsigned int HybridMotionTable::getClosestAngularBin(
const double & theta)
253 auto bin =
static_cast<unsigned int>(round(
static_cast<float>(theta) / bin_size));
254 return bin < num_angle_quantization ? bin : 0u;
257 float HybridMotionTable::getAngleFromBin(
const unsigned int & bin_idx)
259 return bin_idx * bin_size;
262 NodeHybrid::NodeHybrid(
const unsigned int index)
264 pose(0.0f, 0.0f, 0.0f),
265 _cell_cost(std::numeric_limits<float>::quiet_NaN()),
266 _accumulated_cost(std::numeric_limits<float>::max()),
269 _motion_primitive_index(std::numeric_limits<unsigned int>::max())
281 _cell_cost = std::numeric_limits<float>::quiet_NaN();
282 _accumulated_cost = std::numeric_limits<float>::max();
283 _was_visited =
false;
284 _motion_primitive_index = std::numeric_limits<unsigned int>::max();
291 const bool & traverse_unknown,
295 this->pose.x, this->pose.y, this->pose.theta , traverse_unknown))
300 _cell_cost = collision_checker->
getCost();
306 const float normalized_cost = child->
getCost() / 252.0;
307 if (std::isnan(normalized_cost)) {
308 throw std::runtime_error(
309 "Node attempted to get traversal "
310 "cost without a known SE2 collision cost!");
315 return NodeHybrid::travel_distance_cost;
318 float travel_cost = 0.0;
319 float travel_cost_raw =
320 NodeHybrid::travel_distance_cost *
321 (motion_table.travel_distance_reward + motion_table.cost_penalty * normalized_cost);
325 travel_cost = travel_cost_raw;
329 travel_cost = travel_cost_raw * motion_table.non_straight_penalty;
332 travel_cost = travel_cost_raw *
333 (motion_table.non_straight_penalty + motion_table.change_penalty);
339 travel_cost *= motion_table.reverse_penalty;
350 const float obstacle_heuristic =
352 const float dist_heuristic =
getDistanceHeuristic(node_coords, goal_coords, obstacle_heuristic);
353 return std::max(obstacle_heuristic, dist_heuristic);
357 const MotionModel & motion_model,
358 unsigned int & size_x,
359 unsigned int & size_y,
360 unsigned int & num_angle_quantization,
364 switch (motion_model) {
365 case MotionModel::DUBIN:
366 motion_table.
initDubin(size_x, size_y, num_angle_quantization, search_info);
368 case MotionModel::REEDS_SHEPP:
369 motion_table.
initReedsShepp(size_x, size_y, num_angle_quantization, search_info);
372 throw std::runtime_error(
373 "Invalid motion model for Hybrid A*. Please select between"
374 " Dubin (Ackermann forward only),"
375 " Reeds-Shepp (Ackermann forward and back).");
378 travel_distance_cost = motion_table.projections[0]._x;
381 inline float distanceHeuristic2D(
382 const unsigned int idx,
const unsigned int size_x,
383 const unsigned int target_x,
const unsigned int target_y)
385 int dx =
static_cast<int>(idx % size_x) -
static_cast<int>(target_x);
386 int dy =
static_cast<int>(idx / size_x) -
static_cast<int>(target_y);
387 return std::sqrt(dx * dx + dy * dy);
392 const unsigned int & start_x,
const unsigned int & start_y,
393 const unsigned int & goal_x,
const unsigned int & goal_y)
399 std::weak_ptr<nav2_util::LifecycleNode> ptr;
400 downsampler.
on_configure(ptr,
"fake_frame",
"fake_topic", costmap, 2.0,
true);
402 sampled_costmap = downsampler.
downsample(2.0);
406 if (obstacle_heuristic_lookup_table.size() == size) {
409 obstacle_heuristic_lookup_table.begin(),
410 obstacle_heuristic_lookup_table.end(), 0.0);
412 unsigned int obstacle_size = obstacle_heuristic_lookup_table.size();
413 obstacle_heuristic_lookup_table.resize(size, 0.0);
416 obstacle_heuristic_lookup_table.begin(), obstacle_size, 0.0);
419 obstacle_heuristic_queue.clear();
420 obstacle_heuristic_queue.reserve(
425 const unsigned int goal_index = floor(goal_y / 2.0) * size_x + floor(goal_x / 2.0);
426 obstacle_heuristic_queue.emplace_back(
427 distanceHeuristic2D(goal_index, size_x, start_x, start_y), goal_index);
431 obstacle_heuristic_lookup_table[goal_index] = -0.00001f;
437 const double & cost_penalty)
442 const unsigned int start_y = floor(node_coords.y / 2.0);
443 const unsigned int start_x = floor(node_coords.x / 2.0);
444 const unsigned int start_index = start_y * size_x + start_x;
445 const float & requested_node_cost = obstacle_heuristic_lookup_table[start_index];
446 if (requested_node_cost > 0.0f) {
448 return 2.0 * requested_node_cost;
459 for (
auto & n : obstacle_heuristic_queue) {
460 n.first = -obstacle_heuristic_lookup_table[n.second] +
461 distanceHeuristic2D(n.second, size_x, start_x, start_y);
464 obstacle_heuristic_queue.begin(), obstacle_heuristic_queue.end(),
467 const int size_x_int =
static_cast<int>(size_x);
469 const float sqrt_2 = sqrt(2);
470 float c_cost, cost, travel_cost, new_cost, existing_cost;
471 unsigned int idx, mx, my, mx_idx, my_idx;
472 unsigned int new_idx = 0;
474 const std::vector<int> neighborhood = {1, -1,
475 size_x_int, -size_x_int,
476 size_x_int + 1, size_x_int - 1,
477 -size_x_int + 1, -size_x_int - 1};
479 while (!obstacle_heuristic_queue.empty()) {
480 idx = obstacle_heuristic_queue.front().second;
482 obstacle_heuristic_queue.begin(), obstacle_heuristic_queue.end(),
484 obstacle_heuristic_queue.pop_back();
485 c_cost = obstacle_heuristic_lookup_table[idx];
492 obstacle_heuristic_lookup_table[idx] = c_cost;
494 my_idx = idx / size_x;
495 mx_idx = idx - (my_idx * size_x);
498 for (
unsigned int i = 0; i != neighborhood.size(); i++) {
499 new_idx =
static_cast<unsigned int>(
static_cast<int>(idx) + neighborhood[i]);
502 if (new_idx < size_x * size_y) {
503 cost =
static_cast<float>(sampled_costmap->
getCost(new_idx));
504 if (cost >= INSCRIBED) {
508 my = new_idx / size_x;
509 mx = new_idx - (my * size_x);
511 if (mx == 0 && mx_idx >= size_x - 1 || mx >= size_x - 1 && mx_idx == 0) {
514 if (my == 0 && my_idx >= size_y - 1 || my >= size_y - 1 && my_idx == 0) {
518 existing_cost = obstacle_heuristic_lookup_table[new_idx];
519 if (existing_cost <= 0.0f) {
521 ((i <= 3) ? 1.0f : sqrt_2) * (1.0f + (cost_penalty * cost / 252.0f));
522 new_cost = c_cost + travel_cost;
523 if (existing_cost == 0.0f || -existing_cost > new_cost) {
525 obstacle_heuristic_lookup_table[new_idx] = -new_cost;
526 obstacle_heuristic_queue.emplace_back(
527 new_cost + distanceHeuristic2D(new_idx, size_x, start_x, start_y), new_idx);
529 obstacle_heuristic_queue.begin(), obstacle_heuristic_queue.end(),
536 if (idx == start_index) {
543 return 2.0 * requested_node_cost;
549 const float & obstacle_heuristic)
558 const TrigValues & trig_vals = motion_table.trig_values[goal_coords.theta];
559 const float cos_th = trig_vals.first;
560 const float sin_th = -trig_vals.second;
561 const float dx = node_coords.x - goal_coords.x;
562 const float dy = node_coords.y - goal_coords.y;
564 double dtheta_bin = node_coords.theta - goal_coords.theta;
565 if (dtheta_bin < 0) {
566 dtheta_bin += motion_table.num_angle_quantization;
568 if (dtheta_bin > motion_table.num_angle_quantization) {
569 dtheta_bin -= motion_table.num_angle_quantization;
573 round(dx * cos_th - dy * sin_th),
574 round(dx * sin_th + dy * cos_th),
580 float motion_heuristic = 0.0;
581 const int floored_size = floor(size_lookup / 2.0);
582 const int ceiling_size = ceil(size_lookup / 2.0);
583 const float mirrored_relative_y = abs(node_coords_relative.y);
584 if (abs(node_coords_relative.x) < floored_size && mirrored_relative_y < floored_size) {
587 if (node_coords_relative.y < 0.0) {
588 theta_pos = motion_table.num_angle_quantization - node_coords_relative.theta;
590 theta_pos = node_coords_relative.theta;
592 const int x_pos = node_coords_relative.x + floored_size;
593 const int y_pos =
static_cast<int>(mirrored_relative_y);
595 x_pos * ceiling_size * motion_table.num_angle_quantization +
596 y_pos * motion_table.num_angle_quantization +
598 motion_heuristic = dist_heuristic_lookup_table[index];
599 }
else if (obstacle_heuristic <= 0.0) {
602 static ompl::base::ScopedState<> from(motion_table.state_space), to(motion_table.state_space);
603 to[0] = goal_coords.x;
604 to[1] = goal_coords.y;
605 to[2] = goal_coords.theta * motion_table.num_angle_quantization;
606 from[0] = node_coords.x;
607 from[1] = node_coords.y;
608 from[2] = node_coords.theta * motion_table.num_angle_quantization;
609 motion_heuristic = motion_table.state_space->distance(from(), to());
612 return motion_heuristic;
616 const float & lookup_table_dim,
617 const MotionModel & motion_model,
618 const unsigned int & dim_3_size,
622 if (motion_model == MotionModel::DUBIN) {
623 motion_table.state_space = std::make_unique<ompl::base::DubinsStateSpace>(
624 search_info.minimum_turning_radius);
625 }
else if (motion_model == MotionModel::REEDS_SHEPP) {
626 motion_table.state_space = std::make_unique<ompl::base::ReedsSheppStateSpace>(
627 search_info.minimum_turning_radius);
629 throw std::runtime_error(
630 "Node attempted to precompute distance heuristics "
631 "with invalid motion model!");
634 ompl::base::ScopedState<> from(motion_table.state_space), to(motion_table.state_space);
638 size_lookup = lookup_table_dim;
639 float motion_heuristic = 0.0;
640 unsigned int index = 0;
641 int dim_3_size_int =
static_cast<int>(dim_3_size);
642 float angular_bin_size = 2 * M_PI /
static_cast<float>(dim_3_size);
649 dist_heuristic_lookup_table.resize(size_lookup * ceil(size_lookup / 2.0) * dim_3_size_int);
650 for (
float x = ceil(-size_lookup / 2.0); x <= floor(size_lookup / 2.0); x += 1.0) {
651 for (
float y = 0.0; y <= floor(size_lookup / 2.0); y += 1.0) {
652 for (
int heading = 0; heading != dim_3_size_int; heading++) {
655 from[2] = heading * angular_bin_size;
656 motion_heuristic = motion_table.state_space->distance(from(), to());
657 dist_heuristic_lookup_table[index] = motion_heuristic;
667 const bool & traverse_unknown,
668 NodeVector & neighbors)
670 unsigned int index = 0;
673 const MotionPoses motion_projections = motion_table.
getProjections(
this);
675 for (
unsigned int i = 0; i != motion_projections.size(); i++) {
677 static_cast<unsigned int>(motion_projections[i]._x),
678 static_cast<unsigned int>(motion_projections[i]._y),
679 static_cast<unsigned int>(motion_projections[i]._theta),
680 motion_table.size_x, motion_table.num_angle_quantization);
682 if (NeighborGetter(index, neighbor) && !neighbor->
wasVisited()) {
685 initial_node_coords = neighbor->pose;
688 motion_projections[i]._x,
689 motion_projections[i]._y,
690 motion_projections[i]._theta));
691 if (neighbor->
isNodeValid(traverse_unknown, collision_checker)) {
693 neighbors.push_back(neighbor);
695 neighbor->
setPose(initial_node_coords);
709 while (current_node->parent) {
710 path.push_back(current_node->pose);
712 path.back().theta = NodeHybrid::motion_table.getAngleFromBin(path.back().theta);
713 current_node = current_node->parent;
717 path.push_back(current_node->pose);
719 path.back().theta = NodeHybrid::motion_table.getAngleFromBin(path.back().theta);
A 2D costmap provides a mapping between points in the world and their associated "costs".
unsigned char getCost(unsigned int mx, unsigned int my) const
Get the cost of a cell in the costmap.
unsigned int getSizeInCellsX() const
Accessor for the x size of the costmap in cells.
unsigned int getSizeInCellsY() const
Accessor for the y size of the costmap in cells.
void on_activate()
Activate the publisher of the downsampled costmap.
void on_configure(const nav2_util::LifecycleNode::WeakPtr &node, const std::string &global_frame, const std::string &topic_name, nav2_costmap_2d::Costmap2D *const costmap, const unsigned int &downsampling_factor, const bool &use_min_cost_neighbor=false)
Configure the downsampled costmap object and the ROS publisher.
nav2_costmap_2d::Costmap2D * downsample(const unsigned int &downsampling_factor)
Downsample the given costmap by the downsampling factor, and publish the downsampled costmap.
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*.
static float getHeuristicCost(const Coordinates &node_coords, const Coordinates &goal_coordinates, const nav2_costmap_2d::Costmap2D *costmap)
Get cost of heuristic of node.
static void precomputeDistanceHeuristic(const float &lookup_table_dim, const MotionModel &motion_model, const unsigned int &dim_3_size, const SearchInfo &search_info)
Compute the SE2 distance heuristic.
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.
static void resetObstacleHeuristic(nav2_costmap_2d::Costmap2D *costmap, const unsigned int &start_x, const unsigned int &start_y, const unsigned int &goal_x, const unsigned int &goal_y)
reset the obstacle heuristic state
void setPose(const Coordinates &pose_in)
setting continuous coordinate search poses (in partial-cells)
~NodeHybrid()
A destructor for nav2_smac_planner::NodeHybrid.
void setMotionPrimitiveIndex(const unsigned int &idx)
Sets the motion primitive index used to achieve node in search.
static void initMotionModel(const MotionModel &motion_model, unsigned int &size_x, unsigned int &size_y, unsigned int &angle_quantization, SearchInfo &search_info)
Initialize motion models.
void reset()
Reset method for new search.
unsigned int & getMotionPrimitiveIndex()
Gets the motion primitive index used to achieve node in search.
static float getObstacleHeuristic(const Coordinates &node_coords, const Coordinates &goal_coords, const double &cost_penalty)
Compute the Obstacle heuristic.
bool & wasVisited()
Gets if cell has been visited in search.
void getNeighbors(std::function< bool(const unsigned int &, nav2_smac_planner::NodeHybrid *&)> &validity_checker, GridCollisionChecker *collision_checker, const bool &traverse_unknown, NodeVector &neighbors)
Retrieve all valid neighbors of a node.
static float getDistanceHeuristic(const Coordinates &node_coords, const Coordinates &goal_coords, const float &obstacle_heuristic)
Compute the Distance heuristic.
unsigned int & getIndex()
Gets cell index.
bool backtracePath(CoordinateVector &path)
Set the starting pose for planning, as a node index.
float & getCost()
Gets the costmap cost at this node.
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.
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.
NodeHybrid implementation of coordinate structure.
Search properties and penalties.