Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
obstacle_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 "nav2_smac_planner/obstacle_heuristic.hpp"
16 
17 namespace nav2_smac_planner
18 {
19 
21  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros_i,
22  const unsigned int & start_x, const unsigned int & start_y,
23  const unsigned int & goal_x, const unsigned int & goal_y,
24  const bool downsample_obstacle_heuristic)
25 {
26  // Downsample costmap 2x to compute a sparse obstacle heuristic. This speeds up
27  // the planner considerably to search through 75% less cells with no detectable
28  // erosion of path quality after even modest smoothing. The error would be no more
29  // than 0.05 * normalized cost. Since this is just a search prior, there's no loss in generality
30  costmap_ros = costmap_ros_i;
31  auto costmap = costmap_ros->getCostmap();
32 
33  // Clear lookup table
34  unsigned int size = 0u;
35  unsigned int size_x = 0u;
36  if (downsample_obstacle_heuristic) {
37  size_x = ceil(static_cast<float>(costmap->getSizeInCellsX()) / 2.0f);
38  size = size_x *
39  ceil(static_cast<float>(costmap->getSizeInCellsY()) / 2.0f);
40  } else {
41  size_x = costmap->getSizeInCellsX();
42  size = size_x * costmap->getSizeInCellsY();
43  }
44 
45  if (obstacle_heuristic_lookup_table_.size() == size) {
46  // must reset all values
47  std::fill(
48  obstacle_heuristic_lookup_table_.begin(),
49  obstacle_heuristic_lookup_table_.end(), 0.0f);
50  } else {
51  unsigned int obstacle_size = obstacle_heuristic_lookup_table_.size();
52  obstacle_heuristic_lookup_table_.resize(size, 0.0f);
53  // must reset values for non-constructed indices
54  std::fill_n(
55  obstacle_heuristic_lookup_table_.begin(), obstacle_size, 0.0f);
56  }
57 
58  obstacle_heuristic_queue_.clear();
59  obstacle_heuristic_queue_.reserve(size);
60 
61  // Set initial goal point to queue from. Divided by 2 due to downsampled costmap.
62  unsigned int goal_index;
63  if (downsample_obstacle_heuristic) {
64  goal_index = floor(goal_y / 2.0f) * size_x + floor(goal_x / 2.0f);
65  } else {
66  goal_index = floor(goal_y) * size_x + floor(goal_x);
67  }
68 
69  obstacle_heuristic_queue_.emplace_back(
70  distanceHeuristic2D(goal_index, size_x, start_x, start_y), goal_index);
71 
72  // initialize goal cell with a very small value to differentiate it from 0.0 (~uninitialized)
73  // the negative value means the cell is in the open set
74  obstacle_heuristic_lookup_table_[goal_index] = -0.00001f;
75 }
76 
78  const Coordinates & node_coords,
79  const float & cost_penalty,
80  const bool use_quadratic_cost_penalty,
81  const bool downsample_obstacle_heuristic)
82 {
83  // If already expanded, return the cost
84  auto costmap = costmap_ros->getCostmap();
85  unsigned int size_x = 0u;
86  unsigned int size_y = 0u;
87  if (downsample_obstacle_heuristic) {
88  size_x = ceil(static_cast<float>(costmap->getSizeInCellsX()) / 2.0f);
89  size_y = ceil(static_cast<float>(costmap->getSizeInCellsY()) / 2.0f);
90  } else {
91  size_x = costmap->getSizeInCellsX();
92  size_y = costmap->getSizeInCellsY();
93  }
94 
95  // Divided by 2 due to downsampled costmap.
96  unsigned int start_y, start_x;
97  if (downsample_obstacle_heuristic) {
98  start_y = floor(node_coords.y / 2.0f);
99  start_x = floor(node_coords.x / 2.0f);
100  } else {
101  start_y = floor(node_coords.y);
102  start_x = floor(node_coords.x);
103  }
104 
105  const unsigned int start_index = start_y * size_x + start_x;
106  const float & requested_node_cost = obstacle_heuristic_lookup_table_[start_index];
107  if (requested_node_cost > 0.0f) {
108  // costs are doubled due to downsampling
109  return downsample_obstacle_heuristic ? 2.0f * requested_node_cost : requested_node_cost;
110  }
111 
112  // If not, expand until it is included. This dynamic programming ensures that
113  // we only expand the MINIMUM spanning set of the costmap per planning request.
114  // Rather than naively expanding the entire (potentially massive) map for a limited
115  // path, we only expand to the extent required for the furthest expansion in the
116  // search-planning request that dynamically updates during search as needed.
117 
118  // start_x and start_y have changed since last call
119  // we need to recompute 2D distance heuristic and reprioritize queue
120  for (auto & n : obstacle_heuristic_queue_) {
121  n.first = -obstacle_heuristic_lookup_table_[n.second] +
122  distanceHeuristic2D(n.second, size_x, start_x, start_y);
123  }
124  std::make_heap(
125  obstacle_heuristic_queue_.begin(), obstacle_heuristic_queue_.end(),
127 
128  const int size_x_int = static_cast<int>(size_x);
129  const float sqrt2 = sqrtf(2.0f);
130  float c_cost, cost, travel_cost, new_cost, existing_cost;
131  unsigned int mx, my;
132  unsigned int idx, new_idx = 0;
133 
134  const std::vector<int> neighborhood = {1, -1, // left right
135  size_x_int, -size_x_int, // up down
136  size_x_int + 1, size_x_int - 1, // upper diagonals
137  -size_x_int + 1, -size_x_int - 1}; // lower diagonals
138 
139  while (!obstacle_heuristic_queue_.empty()) {
140  idx = obstacle_heuristic_queue_.front().second;
141  std::pop_heap(
142  obstacle_heuristic_queue_.begin(), obstacle_heuristic_queue_.end(),
144  obstacle_heuristic_queue_.pop_back();
145  c_cost = obstacle_heuristic_lookup_table_[idx];
146  if (c_cost > 0.0f) {
147  // cell has been processed and closed, no further cost improvements
148  // are mathematically possible thanks to euclidean distance heuristic consistency
149  continue;
150  }
151  c_cost = -c_cost;
152  obstacle_heuristic_lookup_table_[idx] = c_cost; // set a positive value to close the cell
153 
154  // find neighbors
155  for (unsigned int i = 0; i != neighborhood.size(); i++) {
156  new_idx = static_cast<unsigned int>(static_cast<int>(idx) + neighborhood[i]);
157 
158  // if neighbor path is better and non-lethal, set new cost and add to queue
159  if (new_idx < size_x * size_y) {
160  if (downsample_obstacle_heuristic) {
161  // Get costmap values as if downsampled
162  unsigned int y_offset = (new_idx / size_x) * 2;
163  unsigned int x_offset = (new_idx - ((new_idx / size_x) * size_x)) * 2;
164  cost = costmap->getCost(x_offset, y_offset);
165  for (unsigned int k = 0; k < 2u; ++k) {
166  unsigned int mxd = x_offset + k;
167  if (mxd >= costmap->getSizeInCellsX()) {
168  continue;
169  }
170  for (unsigned int j = 0; j < 2u; ++j) {
171  unsigned int myd = y_offset + j;
172  if (myd >= costmap->getSizeInCellsY()) {
173  continue;
174  }
175  if (k == 0 && j == 0) {
176  continue;
177  }
178  cost = std::min(cost, static_cast<float>(costmap->getCost(mxd, myd)));
179  }
180  }
181  } else {
182  cost = static_cast<float>(costmap->getCost(new_idx));
183  }
184 
185  if (cost >= INSCRIBED_COST) {
186  continue;
187  }
188 
189  my = new_idx / size_x;
190  mx = new_idx - (my * size_x);
191 
192  if (mx >= size_x - 3 || mx <= 3) {
193  continue;
194  }
195  if (my >= size_y - 3 || my <= 3) {
196  continue;
197  }
198 
199  existing_cost = obstacle_heuristic_lookup_table_[new_idx];
200  if (existing_cost <= 0.0f) {
201  if (use_quadratic_cost_penalty) {
202  travel_cost =
203  (i <= 3 ? 1.0f : sqrt2) * (1.0f + (cost_penalty * cost * cost / 63504.0f)); // 252^2
204  } else {
205  travel_cost =
206  ((i <= 3) ? 1.0f : sqrt2) * (1.0f + (cost_penalty * cost / 252.0f));
207  }
208 
209  new_cost = c_cost + travel_cost;
210  if (existing_cost == 0.0f || -existing_cost > new_cost) {
211  // the negative value means the cell is in the open set
212  obstacle_heuristic_lookup_table_[new_idx] = -new_cost;
213  obstacle_heuristic_queue_.emplace_back(
214  new_cost + distanceHeuristic2D(new_idx, size_x, start_x, start_y), new_idx);
215  std::push_heap(
216  obstacle_heuristic_queue_.begin(), obstacle_heuristic_queue_.end(),
218  }
219  }
220  }
221  }
222 
223  if (idx == start_index) {
224  break;
225  }
226  }
227  return downsample_obstacle_heuristic ? 2.0f * requested_node_cost : requested_node_cost;
228 }
229 
230 } // namespace nav2_smac_planner
float getObstacleHeuristic(const Coordinates &node_coords, const float &cost_penalty, const bool use_quadratic_cost_penalty, const bool downsample_obstacle_heuristic)
Compute the Obstacle heuristic.
void resetObstacleHeuristic(std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros_i, const unsigned int &start_x, const unsigned int &start_y, const unsigned int &goal_x, const unsigned int &goal_y, const bool downsample_obstacle_heuristic)
Compute the wavefront heuristic.
Implementation of coordinate2d structure.
Definition: types.hpp:224