Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
dynamic_window_pure_pursuit_functions.hpp
1 // Copyright (c) 2025 Fumiya Ohnishi
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.
14 
15 #ifndef NAV2_REGULATED_PURE_PURSUIT_CONTROLLER__DYNAMIC_WINDOW_PURE_PURSUIT_FUNCTIONS_HPP_
16 #define NAV2_REGULATED_PURE_PURSUIT_CONTROLLER__DYNAMIC_WINDOW_PURE_PURSUIT_FUNCTIONS_HPP_
17 
18 #include <string>
19 #include <vector>
20 #include <algorithm>
21 #include <tuple>
22 #include <utility>
23 #include <limits>
24 
25 #include "rclcpp/rclcpp.hpp"
26 #include "geometry_msgs/msg/twist.hpp"
27 
28 namespace nav2_regulated_pure_pursuit_controller
29 {
30 
31 namespace dynamic_window_pure_pursuit
32 {
33 
35 {
36  double max_linear_vel;
37  double min_linear_vel;
38  double max_angular_vel;
39  double min_angular_vel;
40 };
41 
57 inline DynamicWindowBounds computeDynamicWindow(
58  const geometry_msgs::msg::Twist & current_speed,
59  const double & max_linear_vel,
60  const double & min_linear_vel,
61  const double & max_angular_vel,
62  const double & min_angular_vel,
63  const double & max_linear_accel,
64  const double & max_linear_decel,
65  const double & max_angular_accel,
66  const double & max_angular_decel,
67  const double & dt
68 )
69 {
70  DynamicWindowBounds dynamic_window;
71  constexpr double Eps = 1e-3;
72 
73  // function to compute dynamic window for a single dimension
74  auto compute_window = [&](const double & current_vel, const double & max_vel,
75  const double & min_vel, const double & max_accel, const double & max_decel)
76  {
77  double candidate_max_vel = 0.0;
78  double candidate_min_vel = 0.0;
79 
80  if (current_vel > Eps) {
81  // if the current velocity is positive, acceleration means an increase in speed
82  candidate_max_vel = current_vel + max_accel * dt;
83  candidate_min_vel = current_vel + max_decel * dt;
84  } else if (current_vel < -Eps) {
85  // if the current velocity is negative, acceleration means a decrease in speed
86  candidate_max_vel = current_vel - max_decel * dt;
87  candidate_min_vel = current_vel - max_accel * dt;
88  } else {
89  // if the current velocity is zero, allow acceleration in both directions.
90  candidate_max_vel = current_vel + max_accel * dt;
91  candidate_min_vel = current_vel - max_accel * dt;
92  }
93 
94  // clip to max/min velocity limits
95  double dynamic_window_max_vel = std::min(candidate_max_vel, max_vel);
96  double dynamic_window_min_vel = std::max(candidate_min_vel, min_vel);
97  return std::make_tuple(dynamic_window_max_vel, dynamic_window_min_vel);
98  };
99 
100  // linear velocity
101  std::tie(dynamic_window.max_linear_vel, dynamic_window.min_linear_vel) =
102  compute_window(current_speed.linear.x, max_linear_vel, min_linear_vel,
103  max_linear_accel, max_linear_decel);
104 
105  // angular velocity
106  std::tie(dynamic_window.max_angular_vel, dynamic_window.min_angular_vel) =
107  compute_window(current_speed.angular.z, max_angular_vel, min_angular_vel,
108  max_angular_accel, max_angular_decel);
109 
110  return dynamic_window;
111 }
112 
118 inline void applyRegulationToDynamicWindow(
119  const double & regulated_linear_vel,
120  DynamicWindowBounds & dynamic_window)
121 {
122  // Create regulated bounds [0, v_reg] or [v_reg, 0]
123  double v_reg_min = std::min(0.0, regulated_linear_vel);
124  double v_reg_max = std::max(0.0, regulated_linear_vel);
125 
126  // Intersect the dynamic window with the regulated bounds
127  dynamic_window.min_linear_vel = std::max(dynamic_window.min_linear_vel, v_reg_min);
128  dynamic_window.max_linear_vel = std::min(dynamic_window.max_linear_vel, v_reg_max);
129 
130  // If min > max, collapse to the nearest boundary
131  if (dynamic_window.min_linear_vel > dynamic_window.max_linear_vel) {
132  if (dynamic_window.min_linear_vel > v_reg_max) {
133  dynamic_window.max_linear_vel = dynamic_window.min_linear_vel;
134  } else {
135  dynamic_window.min_linear_vel = dynamic_window.max_linear_vel;
136  }
137  }
138 }
139 
147 inline std::tuple<double, double> computeOptimalVelocityWithinDynamicWindow(
148  const DynamicWindowBounds & dynamic_window,
149  const double & curvature,
150  const double & sign
151 )
152 {
153  double optimal_linear_vel;
154  double optimal_angular_vel;
155 
156  // consider linear_vel - angular_vel space (horizontal and vertical axes respectively)
157  // Select the closest point to the line
158  // angular_vel = curvature * linear_vel within the dynamic window.
159  // If multiple points are equally close, select the one with the largest linear_vel.
160 
161  // When curvature == 0, the line is angular_vel = 0
162  if (abs(curvature) < 1e-3) {
163  // linear velocity
164  if (sign >= 0.0) {
165  // If moving forward, select the max linear vel
166  optimal_linear_vel = dynamic_window.max_linear_vel;
167  } else {
168  // If moving backward, select the min linear vel
169  optimal_linear_vel = dynamic_window.min_linear_vel;
170  }
171 
172  // angular velocity
173  // If the line angular_vel = 0 intersects the dynamic window,angular_vel = 0.0
174  if (dynamic_window.min_angular_vel <= 0.0 && 0.0 <= dynamic_window.max_angular_vel) {
175  optimal_angular_vel = 0.0;
176  } else {
177  // If not, select angular vel within dynamic window closest to 0
178  if (std::abs(dynamic_window.min_angular_vel) <= std::abs(dynamic_window.max_angular_vel)) {
179  optimal_angular_vel = dynamic_window.min_angular_vel;
180  } else {
181  optimal_angular_vel = dynamic_window.max_angular_vel;
182  }
183  }
184  return std::make_tuple(optimal_linear_vel, optimal_angular_vel);
185  }
186 
187  // When the dynamic window and the line angular_vel = curvature * linear_vel intersect,
188  // select the intersection point that yields the highest linear velocity.
189 
190  // List the four candidate intersection points
191  std::pair<double, double> candidates[] = {
192  {dynamic_window.min_linear_vel, curvature * dynamic_window.min_linear_vel},
193  {dynamic_window.max_linear_vel, curvature * dynamic_window.max_linear_vel},
194  {dynamic_window.min_angular_vel / curvature, dynamic_window.min_angular_vel},
195  {dynamic_window.max_angular_vel / curvature, dynamic_window.max_angular_vel}
196  };
197 
198  double best_linear_vel = -std::numeric_limits<double>::max() * sign;
199  double best_angular_vel = 0.0;
200 
201  for (auto [linear_vel, angular_vel] : candidates) {
202  // Check whether the candidate lies within the dynamic window
203  if (linear_vel >= dynamic_window.min_linear_vel &&
204  linear_vel <= dynamic_window.max_linear_vel &&
205  angular_vel >= dynamic_window.min_angular_vel &&
206  angular_vel <= dynamic_window.max_angular_vel)
207  {
208  // Select the candidate with the largest linear velocity (considering moving direction)
209  if (linear_vel * sign > best_linear_vel * sign) {
210  best_linear_vel = linear_vel;
211  best_angular_vel = angular_vel;
212  }
213  }
214  }
215 
216  // If best_linear_vel was updated, it means that a valid intersection exists
217  if (best_linear_vel != -std::numeric_limits<double>::max() * sign) {
218  optimal_linear_vel = best_linear_vel;
219  optimal_angular_vel = best_angular_vel;
220  return std::make_tuple(optimal_linear_vel, optimal_angular_vel);
221  }
222 
223  // When the dynamic window and the line angular_vel = curvature * linear_vel have no intersection,
224  // select the point within the dynamic window that is closest to the line.
225 
226  // Because the dynamic window is a convex region,
227  // the closest point must be one of its four corners.
228  const std::array<std::array<double, 2>, 4> corners = {{
229  {dynamic_window.min_linear_vel, dynamic_window.min_angular_vel},
230  {dynamic_window.min_linear_vel, dynamic_window.max_angular_vel},
231  {dynamic_window.max_linear_vel, dynamic_window.min_angular_vel},
232  {dynamic_window.max_linear_vel, dynamic_window.max_angular_vel}
233  }};
234 
235  // Compute the distance from a point (linear_vel, angular_vel)
236  // to the line angular_vel = curvature * linear_vel
237  const double denom = std::sqrt(curvature * curvature + 1.0);
238  auto compute_dist = [&](const std::array<double, 2> & corner) -> double {
239  return std::abs(curvature * corner[0] - corner[1]) / denom;
240  };
241 
242  double closest_dist = std::numeric_limits<double>::max();
243  best_linear_vel = -std::numeric_limits<double>::max() * sign;
244  best_angular_vel = 0.0;
245 
246  for (const auto & corner : corners) {
247  const double dist = compute_dist(corner);
248  // Update if this corner is closer to the line,
249  // or equally close but has a larger linear velocity (considering moving direction)
250  if (dist < closest_dist ||
251  (std::abs(dist - closest_dist) <= 1e-3 && corner[0] * sign > best_linear_vel * sign))
252  {
253  closest_dist = dist;
254  best_linear_vel = corner[0];
255  best_angular_vel = corner[1];
256  }
257  }
258 
259  optimal_linear_vel = best_linear_vel;
260  optimal_angular_vel = best_angular_vel;
261 
262  return std::make_tuple(optimal_linear_vel, optimal_angular_vel);
263 }
264 
282 inline std::tuple<double, double> computeDynamicWindowVelocities(
283  const geometry_msgs::msg::Twist & current_speed,
284  const double & max_linear_vel,
285  const double & min_linear_vel,
286  const double & max_angular_vel,
287  const double & min_angular_vel,
288  const double & max_linear_accel,
289  const double & max_linear_decel,
290  const double & max_angular_accel,
291  const double & max_angular_decel,
292  const double & regulated_linear_vel,
293  const double & curvature,
294  const double & sign,
295  const double & dt
296 )
297 {
298  // compute Dynamic Window
299  DynamicWindowBounds dynamic_window = computeDynamicWindow(
300  current_speed, max_linear_vel, min_linear_vel, max_angular_vel, min_angular_vel,
301  max_linear_accel, max_linear_decel, max_angular_accel, max_angular_decel, dt);
302 
303  // apply regulation to Dynamic Window
304  applyRegulationToDynamicWindow(regulated_linear_vel, dynamic_window);
305 
306  // compute optimal velocity within Dynamic Window
307  auto [linear_vel, angular_vel] = computeOptimalVelocityWithinDynamicWindow(
308  dynamic_window, curvature, sign);
309 
310  return std::make_tuple(linear_vel, angular_vel);
311 }
312 
313 
314 } // namespace dynamic_window_pure_pursuit
315 
316 } // namespace nav2_regulated_pure_pursuit_controller
317 
318 #endif // NAV2_REGULATED_PURE_PURSUIT_CONTROLLER__DYNAMIC_WINDOW_PURE_PURSUIT_FUNCTIONS_HPP_