Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
utils.hpp
1 // Copyright (c) 2022 Samsung Research America, @artofnothingness Alexey Budyakov
2 // Copyright (c) 2023 Open Navigation LLC
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #ifndef NAV2_MPPI_CONTROLLER__TOOLS__UTILS_HPP_
17 #define NAV2_MPPI_CONTROLLER__TOOLS__UTILS_HPP_
18 
19 #include <Eigen/Dense>
20 
21 #include <algorithm>
22 #include <chrono>
23 #include <string>
24 #include <limits>
25 #include <memory>
26 #include <vector>
27 
28 #include "angles/angles.h"
29 
30 #include "tf2/utils.hpp"
31 #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
32 
33 #include "geometry_msgs/msg/twist_stamped.hpp"
34 #include "nav_msgs/msg/path.hpp"
35 #include "nav_msgs/msg/trajectory.hpp"
36 #include "visualization_msgs/msg/marker_array.hpp"
37 
38 #include "rclcpp/duration.hpp"
39 #include "rclcpp/time.hpp"
40 #include "rclcpp_lifecycle/lifecycle_node.hpp"
41 
42 #include "nav2_ros_common/node_utils.hpp"
43 #include "nav2_core/goal_checker.hpp"
44 
45 #include "nav2_mppi_controller/models/optimizer_settings.hpp"
46 #include "nav2_mppi_controller/models/control_sequence.hpp"
47 #include "nav2_mppi_controller/models/path.hpp"
48 #include "builtin_interfaces/msg/time.hpp"
49 #include "nav2_mppi_controller/critic_data.hpp"
50 
51 #define M_PIF 3.141592653589793238462643383279502884e+00F
52 #define M_PIF_2 1.5707963267948966e+00F
53 
54 namespace mppi::utils
55 {
63 inline geometry_msgs::msg::Pose createPose(double x, double y, double z)
64 {
65  geometry_msgs::msg::Pose pose;
66  pose.position.x = x;
67  pose.position.y = y;
68  pose.position.z = z;
69  pose.orientation.w = 1;
70  pose.orientation.x = 0;
71  pose.orientation.y = 0;
72  pose.orientation.z = 0;
73  return pose;
74 }
75 
83 inline geometry_msgs::msg::Vector3 createScale(double x, double y, double z)
84 {
85  geometry_msgs::msg::Vector3 scale;
86  scale.x = x;
87  scale.y = y;
88  scale.z = z;
89  return scale;
90 }
91 
100 inline std_msgs::msg::ColorRGBA createColor(float r, float g, float b, float a)
101 {
102  std_msgs::msg::ColorRGBA color;
103  color.r = r;
104  color.g = g;
105  color.b = b;
106  color.a = a;
107  return color;
108 }
109 
119 inline visualization_msgs::msg::Marker createMarker(
120  int id, const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Vector3 & scale,
121  const std_msgs::msg::ColorRGBA & color, const std::string & frame_id, const std::string & ns)
122 {
123  using visualization_msgs::msg::Marker;
124  Marker marker;
125  marker.header.frame_id = frame_id;
126  marker.header.stamp = rclcpp::Time(0, 0);
127  marker.ns = ns;
128  marker.id = id;
129  marker.type = Marker::SPHERE;
130  marker.action = Marker::ADD;
131 
132  marker.pose = pose;
133  marker.scale = scale;
134  marker.color = color;
135  return marker;
136 }
137 
145 inline geometry_msgs::msg::TwistStamped toTwistStamped(
146  float vx, float wz, const builtin_interfaces::msg::Time & stamp, const std::string & frame)
147 {
148  geometry_msgs::msg::TwistStamped twist;
149  twist.header.frame_id = frame;
150  twist.header.stamp = stamp;
151  twist.twist.linear.x = vx;
152  twist.twist.angular.z = wz;
153 
154  return twist;
155 }
156 
165 inline geometry_msgs::msg::TwistStamped toTwistStamped(
166  float vx, float vy, float wz, const builtin_interfaces::msg::Time & stamp,
167  const std::string & frame)
168 {
169  auto twist = toTwistStamped(vx, wz, stamp, frame);
170  twist.twist.linear.y = vy;
171 
172  return twist;
173 }
174 
175 inline std::unique_ptr<nav_msgs::msg::Trajectory> toTrajectoryMsg(
176  const Eigen::ArrayXXf & trajectory,
177  const models::ControlSequence & control_sequence,
178  const double & model_dt,
179  const std_msgs::msg::Header & header)
180 {
181  auto trajectory_msg = std::make_unique<nav_msgs::msg::Trajectory>();
182  trajectory_msg->header = header;
183  trajectory_msg->points.resize(trajectory.rows());
184 
185  for (int i = 0; i < trajectory.rows(); ++i) {
186  auto & curr_pt = trajectory_msg->points[i];
187  curr_pt.header.frame_id = header.frame_id;
188  curr_pt.header.stamp = header.stamp + rclcpp::Duration::from_seconds(i * model_dt);
189  curr_pt.pose.position.x = trajectory(i, 0);
190  curr_pt.pose.position.y = trajectory(i, 1);
191  tf2::Quaternion quat;
192  quat.setRPY(0.0, 0.0, trajectory(i, 2));
193  curr_pt.pose.orientation = tf2::toMsg(quat);
194  curr_pt.velocity.linear.x = control_sequence.vx(i);
195  curr_pt.velocity.angular.z = control_sequence.wz(i);
196  if (control_sequence.vy.size() > 0) {
197  curr_pt.velocity.linear.y = control_sequence.vy(i);
198  }
199  }
200 
201  return trajectory_msg;
202 }
203 
209 inline models::Path toTensor(const nav_msgs::msg::Path & path)
210 {
211  auto result = models::Path{};
212  result.reset(path.poses.size());
213 
214  for (size_t i = 0; i < path.poses.size(); ++i) {
215  result.x(i) = path.poses[i].pose.position.x;
216  result.y(i) = path.poses[i].pose.position.y;
217  result.yaws(i) = tf2::getYaw(path.poses[i].pose.orientation);
218  }
219 
220  return result;
221 }
222 
228 inline geometry_msgs::msg::Pose getLastPathPose(const models::Path & path)
229 {
230  const unsigned int path_last_idx = path.x.size() - 1;
231 
232  auto last_orientation = path.yaws(path_last_idx);
233 
234  tf2::Quaternion pose_orientation;
235  pose_orientation.setRPY(0.0, 0.0, last_orientation);
236 
237  geometry_msgs::msg::Pose pathPose;
238  pathPose.position.x = path.x(path_last_idx);
239  pathPose.position.y = path.y(path_last_idx);
240  pathPose.orientation.x = pose_orientation.x();
241  pathPose.orientation.y = pose_orientation.y();
242  pathPose.orientation.z = pose_orientation.z();
243  pathPose.orientation.w = pose_orientation.w();
244 
245  return pathPose;
246 }
247 
255 template<typename T>
256 auto normalize_angles(const T & angles)
257 {
258  return (angles + M_PIF).unaryExpr(
259  [&](const float x) {
260  float remainder = std::fmod(x, 2.0f * M_PIF);
261  return remainder < 0.0f ? remainder + M_PIF : remainder - M_PIF;
262  });
263 }
264 
278 template<typename F, typename T>
279 auto shortest_angular_distance(
280  const F & from,
281  const T & to)
282 {
283  return normalize_angles(to - from);
284 }
285 
293 inline size_t findPathFurthestReachedPoint(const CriticData & data)
294 {
295  const int traj_cols = data.trajectories.x.cols();
296  const int n_rows = static_cast<int>(data.trajectories.x.rows());
297  const int n_cols = static_cast<int>(data.path.x.size());
298 
299  // Cumulative arc-lengths along the reference path
300  std::vector<float> path_integrated_dists(n_cols, 0.0f);
301  for (int i = 1; i < n_cols; ++i) {
302  const float dx = data.path.x(i) - data.path.x(i - 1);
303  const float dy = data.path.y(i) - data.path.y(i - 1);
304  path_integrated_dists[i] = path_integrated_dists[i - 1] + sqrtf(dx * dx + dy * dy);
305  }
306 
307  // Arc-length of all candidate trajectories
308  const Eigen::ArrayXf traj_integrated_dists =
309  ((data.trajectories.x.rightCols(traj_cols - 1) -
310  data.trajectories.x.leftCols(traj_cols - 1)).square() +
311  (data.trajectories.y.rightCols(traj_cols - 1) -
312  data.trajectories.y.leftCols(traj_cols - 1)).square())
313  .sqrt().rowwise().sum();
314 
315  const auto & traj_x_end = data.trajectories.x.col(traj_cols - 1);
316  const auto & traj_y_end = data.trajectories.y.col(traj_cols - 1);
317 
318  int max_idx = 0;
319  for (int i = 0; i < n_rows; ++i) {
320  int max_reachable_idx = static_cast<int>(
321  std::lower_bound(
322  path_integrated_dists.begin(), path_integrated_dists.end(),
323  traj_integrated_dists(i)) - path_integrated_dists.begin());
324  max_reachable_idx = std::min(max_reachable_idx, n_cols - 1);
325 
326  if (max_reachable_idx < 0) {continue;}
327 
328  // Bounded Euclidean search: closest path point within [0..max_reachable_idx]
329  Eigen::Index eucl_idx;
330  ((data.path.x.head(max_reachable_idx + 1) - traj_x_end(i)).square() +
331  (data.path.y.head(max_reachable_idx + 1) - traj_y_end(i)).square()).minCoeff(&eucl_idx);
332 
333  max_idx = std::max(max_idx, static_cast<int>(eucl_idx));
334 
335  // Early exit if we've already reached the end of the path
336  if (max_idx == n_cols - 1) {
337  break;
338  }
339  }
340 
341  return static_cast<size_t>(max_idx);
342 }
343 
348 inline void setPathFurthestPointIfNotSet(CriticData & data)
349 {
350  if (!data.furthest_reached_path_point) {
351  data.furthest_reached_path_point = findPathFurthestReachedPoint(data);
352  }
353 }
354 
359 inline void findPathCosts(
360  CriticData & data,
361  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
362 {
363  auto * costmap = costmap_ros->getCostmap();
364  unsigned int map_x, map_y;
365  const size_t path_segments_count = data.path.x.size() - 1;
366  data.path_pts_valid = std::vector<bool>(path_segments_count, false);
367  const bool tracking_unknown = costmap_ros->getLayeredCostmap()->isTrackingUnknown();
368  for (unsigned int idx = 0; idx < path_segments_count; idx++) {
369  if (!costmap->worldToMap(data.path.x(idx), data.path.y(idx), map_x, map_y)) {
370  (*data.path_pts_valid)[idx] = false;
371  continue;
372  }
373 
374  switch (costmap->getCost(map_x, map_y)) {
375  case (nav2_costmap_2d::LETHAL_OBSTACLE):
376  (*data.path_pts_valid)[idx] = false;
377  continue;
378  case (nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE):
379  (*data.path_pts_valid)[idx] = false;
380  continue;
381  case (nav2_costmap_2d::NO_INFORMATION):
382  (*data.path_pts_valid)[idx] = tracking_unknown ? true : false;
383  continue;
384  }
385 
386  (*data.path_pts_valid)[idx] = true;
387  }
388 }
389 
394 inline void setPathCostsIfNotSet(
395  CriticData & data,
396  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
397 {
398  if (!data.path_pts_valid) {
399  findPathCosts(data, costmap_ros);
400  }
401 }
402 
411 inline float posePointAngle(
412  const geometry_msgs::msg::Pose & pose, double point_x, double point_y, bool forward_preference)
413 {
414  float pose_x = pose.position.x;
415  float pose_y = pose.position.y;
416  float pose_yaw = tf2::getYaw(pose.orientation);
417 
418  float yaw = atan2f(point_y - pose_y, point_x - pose_x);
419 
420  // If no preference for forward, return smallest angle either in heading or 180 of heading
421  if (!forward_preference) {
422  return std::min(
423  fabs(angles::shortest_angular_distance(yaw, pose_yaw)),
424  fabs(angles::shortest_angular_distance(yaw, angles::normalize_angle(pose_yaw + M_PIF))));
425  }
426 
427  return fabs(angles::shortest_angular_distance(yaw, pose_yaw));
428 }
429 
438 inline float posePointAngle(
439  const geometry_msgs::msg::Pose & pose,
440  double point_x, double point_y, double point_yaw)
441 {
442  float pose_x = static_cast<float>(pose.position.x);
443  float pose_y = static_cast<float>(pose.position.y);
444  float pose_yaw = static_cast<float>(tf2::getYaw(pose.orientation));
445 
446  float yaw = atan2f(static_cast<float>(point_y) - pose_y, static_cast<float>(point_x) - pose_x);
447 
448  if (fabs(angles::shortest_angular_distance(yaw, static_cast<float>(point_yaw))) > M_PIF_2) {
449  yaw = angles::normalize_angle(yaw + M_PIF);
450  }
451 
452  return fabs(angles::shortest_angular_distance(yaw, pose_yaw));
453 }
454 
461 inline void savitskyGolayFilter(
462  models::ControlSequence & control_sequence,
463  std::array<mppi::models::Control, 4> & control_history,
464  const models::OptimizerSettings & settings)
465 {
466  // Savitzky-Golay filter coefficients, 9-point window
467  Eigen::Array<float, 9, 1> filter;
468  if (settings.sgf_order == 1) {
469  // Degree-1 (linear): uniform moving average with more aggressive smoothing
470  filter = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
471  filter /= 9.0f;
472  } else {
473  // Degree-2 (quadratic): standard 9-point SG coefficients
474  filter = {-21.0f, 14.0f, 39.0f, 54.0f, 59.0f, 54.0f, 39.0f, 14.0f, -21.0f};
475  filter /= 231.0f;
476  }
477 
478  // Too short to smooth meaningfully
479  const unsigned int num_sequences = control_sequence.vx.size() - 1;
480  if (num_sequences < 20) {
481  return;
482  }
483 
484  auto applyFilter = [&](const Eigen::Array<float, 9, 1> & data) -> float {
485  return (data * filter).eval().sum();
486  };
487 
488  auto applyFilterOverAxis =
489  [&](Eigen::ArrayXf & sequence, const Eigen::ArrayXf & initial_sequence,
490  const float hist_0, const float hist_1, const float hist_2, const float hist_3) -> void
491  {
492  float pt_m4 = hist_0;
493  float pt_m3 = hist_1;
494  float pt_m2 = hist_2;
495  float pt_m1 = hist_3;
496  float pt = initial_sequence(0);
497  float pt_p1 = initial_sequence(1);
498  float pt_p2 = initial_sequence(2);
499  float pt_p3 = initial_sequence(3);
500  float pt_p4 = initial_sequence(4);
501 
502  for (unsigned int idx = 0; idx != num_sequences; idx++) {
503  sequence(idx) = applyFilter({pt_m4, pt_m3, pt_m2, pt_m1, pt, pt_p1, pt_p2, pt_p3, pt_p4});
504  pt_m4 = pt_m3;
505  pt_m3 = pt_m2;
506  pt_m2 = pt_m1;
507  pt_m1 = pt;
508  pt = pt_p1;
509  pt_p1 = pt_p2;
510  pt_p2 = pt_p3;
511  pt_p3 = pt_p4;
512 
513  if (idx + 5 < num_sequences) {
514  pt_p4 = initial_sequence(idx + 5);
515  } else {
516  // Return the last point
517  pt_p4 = initial_sequence(num_sequences);
518  }
519  }
520  };
521 
522  // Filter trajectories
523  const models::ControlSequence initial_control_sequence = control_sequence;
524  applyFilterOverAxis(
525  control_sequence.vx, initial_control_sequence.vx, control_history[0].vx,
526  control_history[1].vx, control_history[2].vx, control_history[3].vx);
527  applyFilterOverAxis(
528  control_sequence.vy, initial_control_sequence.vy, control_history[0].vy,
529  control_history[1].vy, control_history[2].vy, control_history[3].vy);
530  applyFilterOverAxis(
531  control_sequence.wz, initial_control_sequence.wz, control_history[0].wz,
532  control_history[1].wz, control_history[2].wz, control_history[3].wz);
533 
534  // Update control history
535  unsigned int offset = settings.shift_control_sequence ? 1 : 0;
536  control_history[0] = control_history[1];
537  control_history[1] = control_history[2];
538  control_history[2] = control_history[3];
539  control_history[3] = {
540  control_sequence.vx(offset),
541  control_sequence.vy(offset),
542  control_sequence.wz(offset)};
543 }
544 
551 inline unsigned int findClosestPathPt(
552  const std::vector<float> & vec, const float dist, const unsigned int init = 0u)
553 {
554  float distim1 = init != 0u ? vec[init] : 0.0f; // First is 0, no accumulated distance yet
555  float disti = 0.0f;
556  const unsigned int size = vec.size();
557  for (unsigned int i = init + 1; i != size; i++) {
558  disti = vec[i];
559  if (disti > dist) {
560  if (i > 0 && dist - distim1 < disti - dist) {
561  return i - 1;
562  }
563  return i;
564  }
565  distim1 = disti;
566  }
567  return size - 1;
568 }
569 
570 // A struct to hold pose data in floating point resolution
571 struct Pose2D
572 {
573  float x, y, theta;
574 };
575 
583 inline void shiftColumnsByOnePlace(Eigen::Ref<Eigen::ArrayXXf> e, int direction)
584 {
585  int size = e.size();
586  if (size == 1) {return;}
587  if (abs(direction) != 1) {
588  throw std::logic_error("Invalid direction, only 1 and -1 are valid values.");
589  }
590 
591  if ((e.cols() == 1 || e.rows() == 1) && size > 1) {
592  auto start_ptr = direction == 1 ? e.data() + size - 2 : e.data() + 1;
593  auto end_ptr = direction == 1 ? e.data() : e.data() + size - 1;
594  while (start_ptr != end_ptr) {
595  *(start_ptr + direction) = *start_ptr;
596  start_ptr -= direction;
597  }
598  *(start_ptr + direction) = *start_ptr;
599  } else {
600  auto start_ptr = direction == 1 ? e.data() + size - 2 * e.rows() : e.data() + e.rows();
601  auto end_ptr = direction == 1 ? e.data() : e.data() + size - e.rows();
602  auto span = e.rows();
603  while (start_ptr != end_ptr) {
604  std::copy(start_ptr, start_ptr + span, start_ptr + direction * span);
605  start_ptr -= (direction * span);
606  }
607  std::copy(start_ptr, start_ptr + span, start_ptr + direction * span);
608  }
609 }
610 
618 inline auto normalize_yaws_between_points(
619  const Eigen::Ref<const Eigen::ArrayXf> & last_yaws,
620  const Eigen::Ref<const Eigen::ArrayXf> & yaw_between_points)
621 {
622  Eigen::ArrayXf yaws = utils::shortest_angular_distance(
623  last_yaws, yaw_between_points).abs();
624  int size = yaws.size();
625  Eigen::ArrayXf yaws_between_points_corrected(size);
626  for (int i = 0; i != size; i++) {
627  const float & yaw_between_point = yaw_between_points[i];
628  yaws_between_points_corrected[i] = yaws[i] < M_PIF_2 ?
629  yaw_between_point : angles::normalize_angle(yaw_between_point + M_PIF);
630  }
631  return yaws_between_points_corrected;
632 }
633 
640 inline auto normalize_yaws_between_points(
641  const float goal_yaw, const Eigen::Ref<const Eigen::ArrayXf> & yaw_between_points)
642 {
643  int size = yaw_between_points.size();
644  Eigen::ArrayXf yaws_between_points_corrected(size);
645  for (int i = 0; i != size; i++) {
646  const float & yaw_between_point = yaw_between_points[i];
647  yaws_between_points_corrected[i] = fabs(
648  angles::normalize_angle(yaw_between_point - goal_yaw)) < M_PIF_2 ?
649  yaw_between_point : angles::normalize_angle(yaw_between_point + M_PIF);
650  }
651  return yaws_between_points_corrected;
652 }
653 
660 inline float clamp(
661  const float lower_bound, const float upper_bound, const float input)
662 {
663  return std::min(upper_bound, std::max(input, lower_bound));
664 }
665 
675 inline float clampVelocityByAccel(
676  const float last_vel, const float curr_vel,
677  const float min_delta, const float max_delta)
678 {
679  if (last_vel >= 0) {
680  return clamp(last_vel + min_delta, last_vel + max_delta, curr_vel);
681  }
682  return clamp(last_vel - max_delta, last_vel - min_delta, curr_vel);
683 }
684 
685 } // namespace mppi::utils
686 
687 #endif // NAV2_MPPI_CONTROLLER__TOOLS__UTILS_HPP_
void reset(unsigned int size)
Reset path data.
Definition: path.hpp:36