Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
distance_heuristic.cpp
1 // Copyright (c) 2026, Open Navigation LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License. Reserved.
14 
15 #include <type_traits>
16 
17 #include "ompl/base/ScopedState.h"
18 #include "ompl/base/spaces/DubinsStateSpace.h"
19 #include "ompl/base/spaces/ReedsSheppStateSpace.h"
20 #include "ompl/base/spaces/SE2StateSpace.h"
21 #include "nav2_smac_planner/distance_heuristic.hpp"
22 #include "nav2_smac_planner/node_hybrid.hpp"
23 #include "nav2_smac_planner/node_lattice.hpp"
24 
25 namespace nav2_smac_planner
26 {
27 
28 template<>
29 template<typename MotionTableT>
31  const float & lookup_table_dim,
32  const MotionModel & motion_model,
33  const unsigned int & dim_3_size,
34  const SearchInfo & search_info,
35  MotionTableT & motion_table)
36 {
37  // Dubin or Reeds-Shepp shortest distances
38  if (motion_model == MotionModel::DUBIN) {
39  motion_table.state_space = std::make_shared<ompl::base::DubinsStateSpace>(
40  search_info.minimum_turning_radius);
41  } else if (motion_model == MotionModel::REEDS_SHEPP) {
42  motion_table.state_space = std::make_shared<ompl::base::ReedsSheppStateSpace>(
43  search_info.minimum_turning_radius);
44  } else {
45  throw std::runtime_error(
46  "Node attempted to precompute distance heuristics "
47  "with invalid motion model!");
48  }
49 
50  ompl::base::ScopedState<> from(motion_table.state_space), to(motion_table.state_space);
51  to[0] = 0.0;
52  to[1] = 0.0;
53  to[2] = 0.0;
54  size_lookup_ = lookup_table_dim;
55  float motion_heuristic = 0.0;
56  unsigned int index = 0;
57  int dim_3_size_int = static_cast<int>(dim_3_size);
58  float angular_bin_size = 2 * M_PI / static_cast<float>(dim_3_size);
59 
60  // Create a lookup table of Dubin/Reeds-Shepp distances in a window around the goal
61  // to help drive the search towards admissible approaches. Deu to symmetries in the
62  // Heuristic space, we need to only store 2 of the 4 quadrants and simply mirror
63  // around the X axis any relative node lookup. This reduces memory overhead and increases
64  // the size of a window a platform can store in memory.
65  dist_heuristic_lookup_table_.resize(size_lookup_ * ceil(size_lookup_ / 2.0) * dim_3_size_int);
66  for (float x = ceil(-size_lookup_ / 2.0); x <= floor(size_lookup_ / 2.0); x += 1.0) {
67  for (float y = 0.0; y <= floor(size_lookup_ / 2.0); y += 1.0) {
68  for (int heading = 0; heading != dim_3_size_int; heading++) {
69  from[0] = x;
70  from[1] = y;
71  from[2] = heading * angular_bin_size;
72  motion_heuristic = motion_table.state_space->distance(from(), to());
73  dist_heuristic_lookup_table_[index] = motion_heuristic;
74  index++;
75  }
76  }
77  }
78 }
79 
80 template<>
81 template<typename MotionTableT>
83  const float & lookup_table_dim,
84  const MotionModel & /*motion_model*/,
85  const unsigned int & dim_3_size,
86  const SearchInfo & search_info,
87  MotionTableT & motion_table)
88 {
89  motion_table.lattice_metadata =
90  LatticeMotionTable::getLatticeMetadata(search_info.lattice_filepath);
91 
92  // Select state space based on motion model from lattice file
93  if (motion_table.lattice_metadata.motion_model == "omni") {
94  // Holonomic robots: Euclidean distance heuristic
95  motion_table.state_space = std::make_shared<ompl::base::SE2StateSpace>();
96  motion_table.motion_model = MotionModel::OMNI;
97  } else if (!search_info.allow_reverse_expansion) {
98  motion_table.state_space = std::make_shared<ompl::base::DubinsStateSpace>(
99  search_info.minimum_turning_radius);
100  motion_table.motion_model = MotionModel::DUBIN;
101  } else {
102  motion_table.state_space = std::make_shared<ompl::base::ReedsSheppStateSpace>(
103  search_info.minimum_turning_radius);
104  motion_table.motion_model = MotionModel::REEDS_SHEPP;
105  }
106 
107  ompl::base::ScopedState<> from(motion_table.state_space), to(motion_table.state_space);
108  to[0] = 0.0;
109  to[1] = 0.0;
110  to[2] = 0.0;
111  size_lookup_ = lookup_table_dim;
112  float motion_heuristic = 0.0;
113  unsigned int index = 0;
114  int dim_3_size_int = static_cast<int>(dim_3_size);
115 
116  // Create a lookup table of Dubin/Reeds-Shepp distances in a window around the goal
117  // to help drive the search towards admissible approaches. Due to symmetries in the
118  // Heuristic space, we need to only store 2 of the 4 quadrants and simply mirror
119  // around the X axis any relative node lookup. This reduces memory overhead and increases
120  // the size of a window a platform can store in memory.
121  dist_heuristic_lookup_table_.resize(size_lookup_ * ceil(size_lookup_ / 2.0) * dim_3_size_int);
122  for (float x = ceil(-size_lookup_ / 2.0); x <= floor(size_lookup_ / 2.0); x += 1.0) {
123  for (float y = 0.0; y <= floor(size_lookup_ / 2.0); y += 1.0) {
124  for (int heading = 0; heading != dim_3_size_int; heading++) {
125  from[0] = x;
126  from[1] = y;
127  from[2] = motion_table.getAngleFromBin(heading);
128  motion_heuristic = motion_table.state_space->distance(from(), to());
129  dist_heuristic_lookup_table_[index] = motion_heuristic;
130  index++;
131  }
132  }
133  }
134 }
135 
136 template<typename NodeT>
137 template<typename MotionTableT>
139  const Coordinates & node_coords,
140  const Coordinates & goal_coords,
141  const float & obstacle_heuristic,
142  MotionTableT & motion_table)
143 {
144  // rotate and translate node_coords such that goal_coords relative is (0,0,0)
145  // Due to the rounding involved in exact cell increments for caching,
146  // this is not an exact replica of a live heuristic, but has bounded error.
147  // (Usually less than 1 cell length)
148 
149  // This angle is negative since we are de-rotating the current node
150  // by the goal angle; cos(-th) = cos(th) & sin(-th) = -sin(th)
151  const TrigValues & trig_vals = motion_table.trig_values[goal_coords.theta];
152  const float cos_th = trig_vals.first;
153  const float sin_th = -trig_vals.second;
154  const float dx = node_coords.x - goal_coords.x;
155  const float dy = node_coords.y - goal_coords.y;
156 
157  double dtheta_bin = node_coords.theta - goal_coords.theta;
158  if (dtheta_bin < 0) {
159  dtheta_bin += motion_table.num_angle_quantization;
160  }
161  if (dtheta_bin > motion_table.num_angle_quantization) {
162  dtheta_bin -= motion_table.num_angle_quantization;
163  }
164 
165  Coordinates node_coords_relative(
166  round(dx * cos_th - dy * sin_th),
167  round(dx * sin_th + dy * cos_th),
168  round(dtheta_bin));
169 
170  // Check if the relative node coordinate is within the localized window around the goal
171  // to apply the distance heuristic. Since the lookup table is contains only the positive
172  // X axis, we mirror the Y and theta values across the X axis to find the heuristic values.
173  float motion_heuristic = 0.0;
174  const int floored_size = floor(size_lookup_ / 2.0);
175  const int ceiling_size = ceil(size_lookup_ / 2.0);
176  const float mirrored_relative_y = abs(node_coords_relative.y);
177  if (abs(node_coords_relative.x) < floored_size && mirrored_relative_y < floored_size) {
178  // Need to mirror angle if Y coordinate was mirrored
179  int theta_pos;
180  if (node_coords_relative.y < 0.0) {
181  theta_pos = motion_table.num_angle_quantization - node_coords_relative.theta;
182  } else {
183  theta_pos = node_coords_relative.theta;
184  }
185  const int x_pos = node_coords_relative.x + floored_size;
186  const int y_pos = static_cast<int>(mirrored_relative_y);
187  const int index =
188  x_pos * ceiling_size * motion_table.num_angle_quantization +
189  y_pos * motion_table.num_angle_quantization +
190  theta_pos;
191  motion_heuristic = dist_heuristic_lookup_table_[index];
192  } else if (obstacle_heuristic <= 0.0) {
193  ompl::base::ScopedState<> from(motion_table.state_space), to(motion_table.state_space);
194  to[0] = goal_coords.x;
195  to[1] = goal_coords.y;
196  from[0] = node_coords.x;
197  from[1] = node_coords.y;
198  if constexpr (std::is_base_of_v<NodeHybrid, NodeT>) {
199  to[2] = goal_coords.theta * motion_table.num_angle_quantization;
200  from[2] = node_coords.theta * motion_table.num_angle_quantization;
201  } else {
202  to[2] = motion_table.getAngleFromBin(goal_coords.theta);
203  from[2] = motion_table.getAngleFromBin(node_coords.theta);
204  }
205  motion_heuristic = motion_table.state_space->distance(from(), to());
206  }
207 
208  return motion_heuristic;
209 }
210 
211 // Instantiate algorithm for the supported template types
213  const float &, const MotionModel &, const unsigned int &, const SearchInfo &,
216  const float &, const MotionModel &, const unsigned int &, const SearchInfo &,
219  const Coordinates &, const Coordinates &, const float &, HybridMotionTable &);
221  const Coordinates &, const Coordinates &, const float &, LatticeMotionTable &);
222 } // namespace nav2_smac_planner
Distance Heuristic implementation for graph, Hybrid-A*.
void precomputeDistanceHeuristic(const float &lookup_table_dim, const MotionModel &motion_model, const unsigned int &dim_3_size, const SearchInfo &search_info, MotionTableT &motion_table)
Compute the SE2 distance heuristic.
float getDistanceHeuristic(const Coordinates &node_coords, const Coordinates &goal_coords, const float &obstacle_heuristic, MotionTableT &motion_table)
Compute the Distance heuristic.
Implementation of coordinate2d structure.
Definition: types.hpp:224
A table of motion primitives and related functions.
Definition: node_hybrid.hpp:45
A table of motion primitives and related functions.
static LatticeMetadata getLatticeMetadata(const std::string &lattice_filepath)
Get file metadata needed.
Search properties and penalties.
Definition: types.hpp:38