Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
node_hybrid.cpp
1 // Copyright (c) 2020, Samsung Research America
2 // Copyright (c) 2020, Applied Electric Vehicles Pty Ltd
3 // Copyright (c) 2023, Open Navigation LLC
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License. Reserved.
16 
17 #include <math.h>
18 #include <chrono>
19 #include <vector>
20 #include <memory>
21 #include <algorithm>
22 #include <queue>
23 #include <limits>
24 #include <utility>
25 
26 #include "ompl/base/ScopedState.h"
27 #include "ompl/base/spaces/DubinsStateSpace.h"
28 #include "ompl/base/spaces/ReedsSheppStateSpace.h"
29 
30 #include "nav2_smac_planner/node_hybrid.hpp"
31 
32 using namespace std::chrono; // NOLINT
33 
34 namespace nav2_smac_planner
35 {
36 
37 // Each of these tables are the projected motion models through
38 // time and space applied to the search on the current node in
39 // continuous map-coordinates (e.g. not meters but partial map cells)
40 // Currently, these are set to project *at minimum* into a neighboring
41 // cell. Though this could be later modified to project a certain
42 // amount of time or particular distance forward.
43 
44 // http://planning.cs.uiuc.edu/planning/node821.html
45 // Model for ackermann style vehicle with minimum radius restriction
46 void HybridMotionTable::initDubin(
47  unsigned int & size_x_in,
48  unsigned int & /*size_y_in*/,
49  unsigned int & num_angle_quantization_in,
50  SearchInfo & search_info)
51 {
52  size_x = size_x_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;
60 
61  // if nothing changed, no need to re-compute primitives
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)
66  {
67  return;
68  }
69 
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;
75 
76  // angle must meet 3 requirements:
77  // 1) be increment of quantized bin size
78  // 2) chord length must be greater than sqrt(2) to leave current cell
79  // 3) maximum curvature must be respected, represented by minimum turning angle
80  // Thusly:
81  // On circle of radius minimum turning angle, we need select motion primitives
82  // with chord length > sqrt(2) and be an increment of our bin size
83  //
84  // chord >= sqrt(2) >= 2 * R * sin (angle / 2); where angle / N = quantized bin size
85  // Thusly: angle <= 2.0 * asin(sqrt(2) / (2 * R))
86  float angle = 2.0 * asin(sqrt(2.0) / (2 * min_turning_radius));
87  // Now make sure angle is an increment of the quantized bin size
88  // And since its based on the minimum chord, we need to make sure its always larger
89  bin_size =
90  2.0f * static_cast<float>(M_PI) / static_cast<float>(num_angle_quantization);
91  float increments;
92  if (angle < bin_size) {
93  increments = 1.0f;
94  } else {
95  // Search dimensions are clean multiples of quantization - this prevents
96  // paths with loops in them
97  increments = ceil(angle / bin_size);
98  }
99  angle = increments * bin_size;
100 
101  // find deflections
102  // If we make a right triangle out of the chord in circle of radius
103  // min turning angle, we can see that delta X = R * sin (angle)
104  const float delta_x = min_turning_radius * sin(angle);
105  // Using that same right triangle, we can see that the complement
106  // to delta Y is R * cos (angle). If we subtract R, we get the actual value
107  const float delta_y = min_turning_radius - (min_turning_radius * cos(angle));
108  const float delta_dist = hypotf(delta_x, delta_y);
109 
110  projections.clear();
111  projections.reserve(3);
112  projections.emplace_back(delta_dist, 0.0, 0.0, TurnDirection::FORWARD); // Forward
113  projections.emplace_back(delta_x, delta_y, increments, TurnDirection::LEFT); // Left
114  projections.emplace_back(delta_x, -delta_y, -increments, TurnDirection::RIGHT); // Right
115 
116  if (search_info.allow_primitive_interpolation && increments > 1.0f) {
117  // Create primitives that are +/- N to fill in search space to use all set angular quantizations
118  // Allows us to create N many primitives so that each search iteration can expand into any angle
119  // bin possible with the minimum turning radius constraint, not just the most extreme turns.
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); // Left
128  projections.emplace_back(
129  delta_x_n, -delta_y_n, -static_cast<float>(i), TurnDirection::RIGHT); // Right
130  }
131  }
132 
133  // Create the correct OMPL state space
134  state_space = std::make_shared<ompl::base::DubinsStateSpace>(min_turning_radius);
135 
136  // Precompute projection deltas
137  delta_xs.resize(projections.size());
138  delta_ys.resize(projections.size());
139  trig_values.resize(num_angle_quantization);
140 
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);
144 
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);
148  if (i == 0) {
149  // if first iteration, cache the trig values for later
150  trig_values[j] = {cos_theta, sin_theta};
151  }
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;
154  }
155  }
156 
157  // Precompute travel costs for each motion primitive
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) {
162  // Turning, so length is the arc length
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;
166  } else {
167  travel_costs[i] = delta_dist;
168  }
169  }
170 }
171 
172 // http://planning.cs.uiuc.edu/planning/node822.html
173 // Same as Dubin model but now reverse is valid
174 // See notes in Dubin for explanation
175 void HybridMotionTable::initReedsShepp(
176  unsigned int & size_x_in,
177  unsigned int & /*size_y_in*/,
178  unsigned int & num_angle_quantization_in,
179  SearchInfo & search_info)
180 {
181  size_x = size_x_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;
189 
190  // if nothing changed, no need to re-compute primitives
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)
195  {
196  return;
197  }
198 
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;
204 
205  float angle = 2.0 * asin(sqrt(2.0) / (2 * min_turning_radius));
206  bin_size =
207  2.0f * static_cast<float>(M_PI) / static_cast<float>(num_angle_quantization);
208  float increments;
209  if (angle < bin_size) {
210  increments = 1.0f;
211  } else {
212  increments = ceil(angle / bin_size);
213  }
214  angle = increments * bin_size;
215 
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);
219 
220  projections.clear();
221  projections.reserve(6);
222  projections.emplace_back(delta_dist, 0.0, 0.0, TurnDirection::FORWARD); // Forward
223  projections.emplace_back(
224  delta_x, delta_y, increments, TurnDirection::LEFT); // Forward + Left
225  projections.emplace_back(
226  delta_x, -delta_y, -increments, TurnDirection::RIGHT); // Forward + Right
227  projections.emplace_back(-delta_dist, 0.0, 0.0, TurnDirection::REVERSE); // Backward
228  projections.emplace_back(
229  -delta_x, delta_y, -increments, TurnDirection::REV_LEFT); // Backward + Left
230  projections.emplace_back(
231  -delta_x, -delta_y, increments, TurnDirection::REV_RIGHT); // Backward + Right
232 
233  if (search_info.allow_primitive_interpolation && increments > 1.0f) {
234  // Create primitives that are +/- N to fill in search space to use all set angular quantizations
235  // Allows us to create N many primitives so that each search iteration can expand into any angle
236  // bin possible with the minimum turning radius constraint, not just the most extreme turns.
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); // Forward + Left
245  projections.emplace_back(
246  delta_x_n, -delta_y_n, -static_cast<float>(i), TurnDirection::RIGHT); // Forward + Right
247  projections.emplace_back(
248  -delta_x_n, delta_y_n, -static_cast<float>(i),
249  TurnDirection::REV_LEFT); // Backward + Left
250  projections.emplace_back(
251  -delta_x_n, -delta_y_n, static_cast<float>(i),
252  TurnDirection::REV_RIGHT); // Backward + Right
253  }
254  }
255 
256  // Create the correct OMPL state space
257  state_space = std::make_shared<ompl::base::ReedsSheppStateSpace>(min_turning_radius);
258 
259  // Precompute projection deltas
260  delta_xs.resize(projections.size());
261  delta_ys.resize(projections.size());
262  trig_values.resize(num_angle_quantization);
263 
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);
267 
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);
271  if (i == 0) {
272  // if first iteration, cache the trig values for later
273  trig_values[j] = {cos_theta, sin_theta};
274  }
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;
277  }
278  }
279 
280  // Precompute travel costs for each motion primitive
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) {
285  // Turning, so length is the arc length
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;
289  } else {
290  travel_costs[i] = delta_dist;
291  }
292  }
293 }
294 
295 MotionPoses HybridMotionTable::getProjections(const NodeHybrid * node)
296 {
297  MotionPoses projection_list;
298  projection_list.reserve(projections.size());
299 
300  for (unsigned int i = 0; i != projections.size(); i++) {
301  const MotionPose & proj_motion_model = projections[i];
302 
303  // normalize theta, I know its overkill, but I've been burned before...
304  const float & node_heading = node->pose.theta;
305  float new_heading = node_heading + proj_motion_model._theta;
306 
307  if (new_heading < 0.0) {
308  new_heading += num_angle_quantization_float;
309  }
310 
311  if (new_heading >= num_angle_quantization_float) {
312  new_heading -= num_angle_quantization_float;
313  }
314 
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);
319  }
320 
321  return projection_list;
322 }
323 
324 unsigned int HybridMotionTable::getClosestAngularBin(const double & theta)
325 {
326  auto bin = static_cast<unsigned int>(round(static_cast<float>(theta) / bin_size));
327  return bin < num_angle_quantization ? bin : 0u;
328 }
329 
330 float HybridMotionTable::getAngleFromBin(const unsigned int & bin_idx)
331 {
332  return bin_idx * bin_size;
333 }
334 
335 double HybridMotionTable::getAngle(const double & theta)
336 {
337  return theta / bin_size;
338 }
339 
340 NodeHybrid::NodeHybrid(const uint64_t index, NodeContext * ctx)
341 : parent(nullptr),
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()),
345  _index(index),
346  _was_visited(false),
347  _motion_primitive_index(std::numeric_limits<unsigned int>::max()),
348  _is_node_valid(false),
349  _ctx(ctx)
350 {
351 }
352 
354 {
355  parent = nullptr;
356 }
357 
359 {
360  parent = nullptr;
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();
365  pose.x = 0.0f;
366  pose.y = 0.0f;
367  pose.theta = 0.0f;
368  _is_node_valid = false;
369 }
370 
372  const bool & traverse_unknown,
373  GridCollisionChecker * collision_checker)
374 {
375  // Already found, we can return the result
376  if (!std::isnan(_cell_cost)) {
377  return _is_node_valid;
378  }
379 
380  _is_node_valid = !collision_checker->inCollision(
381  this->pose.x, this->pose.y, this->pose.theta /*bin number*/, traverse_unknown);
382  _cell_cost = collision_checker->getCost();
383  return _is_node_valid;
384 }
385 
387 {
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!");
393  }
394 
395  const TurnDirection & child_turn_dir = child->getTurnDirection();
396  float travel_cost_raw = _ctx->motion_table.travel_costs[child->getMotionPrimitiveIndex()];
397  float travel_cost = 0.0;
398 
399  if (_ctx->motion_table.use_quadratic_cost_penalty) {
400  travel_cost_raw *=
401  (_ctx->motion_table.travel_distance_reward +
402  (_ctx->motion_table.cost_penalty * normalized_cost * normalized_cost));
403  } else {
404  travel_cost_raw *=
405  (_ctx->motion_table.travel_distance_reward + _ctx->motion_table.cost_penalty *
406  normalized_cost);
407  }
408 
409  if (child_turn_dir == TurnDirection::FORWARD || child_turn_dir == TurnDirection::REVERSE ||
410  getMotionPrimitiveIndex() == std::numeric_limits<unsigned int>::max())
411  {
412  // New motion is a straight motion, no additional costs to be applied
413  travel_cost = travel_cost_raw;
414  } else {
415  if (getTurnDirection() == child_turn_dir) {
416  // Turning motion but keeps in same direction: encourages to commit to turning if starting it
417  travel_cost = travel_cost_raw * _ctx->motion_table.non_straight_penalty;
418  } else {
419  // Turning motion and changing direction: penalizes wiggling
420  travel_cost = travel_cost_raw *
421  (_ctx->motion_table.non_straight_penalty + _ctx->motion_table.change_penalty);
422  }
423  }
424 
425  if (child_turn_dir == TurnDirection::REV_RIGHT ||
426  child_turn_dir == TurnDirection::REV_LEFT ||
427  child_turn_dir == TurnDirection::REVERSE)
428  {
429  // reverse direction
430  travel_cost *= _ctx->motion_table.reverse_penalty;
431  }
432 
433  return travel_cost;
434 }
435 
437  const Coordinates & node_coords,
438  const CoordinateVector & goals_coords)
439 {
440  // obstacle heuristic does not depend on goal heading
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(
448  distance_heuristic,
449  _ctx->distance_heuristic->getDistanceHeuristic(node_coords, goals_coords[i],
450  obstacle_heuristic, _ctx->motion_table));
451  }
452  return std::max(obstacle_heuristic, distance_heuristic);
453 }
454 
456  NodeContext * ctx,
457  const MotionModel & motion_model,
458  unsigned int & size_x,
459  unsigned int & size_y,
460  unsigned int & num_angle_quantization,
461  SearchInfo & search_info)
462 {
463  // find the motion model selected
464  switch (motion_model) {
465  case MotionModel::DUBIN:
466  ctx->motion_table.initDubin(size_x, size_y, num_angle_quantization, search_info);
467  break;
468  case MotionModel::REEDS_SHEPP:
469  ctx->motion_table.initReedsShepp(size_x, size_y, num_angle_quantization, search_info);
470  break;
471  default:
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).");
476  }
477 }
478 
480  std::function<bool(const uint64_t &,
481  nav2_smac_planner::NodeHybrid * &)> & NeighborGetter,
482  GridCollisionChecker * collision_checker,
483  const bool & traverse_unknown,
484  NodeVector & neighbors)
485 {
486  uint64_t index = 0;
487  NodePtr neighbor = nullptr;
488  Coordinates initial_node_coords;
489  const MotionPoses motion_projections = _ctx->motion_table.getProjections(this);
490 
491  for (unsigned int i = 0; i != motion_projections.size(); i++) {
492  index = NodeHybrid::getIndex(
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);
497 
498  if (NeighborGetter(index, neighbor) && !neighbor->wasVisited()) {
499  // Cache the initial pose in case it was visited but valid
500  // don't want to disrupt continuous coordinate expansion
501  initial_node_coords = neighbor->pose;
502  neighbor->setPose(
503  Coordinates(
504  motion_projections[i]._x,
505  motion_projections[i]._y,
506  motion_projections[i]._theta));
507  if (neighbor->isNodeValid(traverse_unknown, collision_checker)) {
508  neighbor->setMotionPrimitiveIndex(i, motion_projections[i]._turn_dir);
509  neighbors.push_back(neighbor);
510  } else {
511  neighbor->setPose(initial_node_coords);
512  }
513  }
514  }
515 }
516 
517 bool NodeHybrid::backtracePath(CoordinateVector & path)
518 {
519  if (!this->parent) {
520  return false;
521  }
522 
523  NodePtr current_node = this;
524 
525  while (current_node->parent) {
526  path.push_back(current_node->pose);
527  // Convert angle to radians
528  path.back().theta = _ctx->motion_table.getAngleFromBin(path.back().theta);
529  current_node = current_node->parent;
530  }
531 
532  // add the start pose
533  path.push_back(current_node->pose);
534  // Convert angle to radians
535  path.back().theta = _ctx->motion_table.getAngleFromBin(path.back().theta);
536 
537  return true;
538 }
539 
540 } // namespace nav2_smac_planner
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.
Definition: types.hpp:224
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.
Definition: node_hybrid.cpp:46
A struct for poses in motion primitives.
Definition: types.hpp:119
Search properties and penalties.
Definition: types.hpp:38