Nav2 Navigation Stack - jazzy  jazzy
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 <algorithm>
20 #include <chrono>
21 #include <string>
22 #include <limits>
23 #include <memory>
24 #include <vector>
25 
26 // xtensor creates warnings that needs to be ignored as we are building with -Werror
27 #pragma GCC diagnostic push
28 #pragma GCC diagnostic ignored "-Warray-bounds"
29 #pragma GCC diagnostic ignored "-Wstringop-overflow"
30 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
31 #include <xtensor/xarray.hpp>
32 #include <xtensor/xnorm.hpp>
33 #include <xtensor/xmath.hpp>
34 #include <xtensor/xview.hpp>
35 #pragma GCC diagnostic pop
36 
37 #include "angles/angles.h"
38 
39 #include "tf2/utils.h"
40 #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
41 
42 #include "geometry_msgs/msg/twist_stamped.hpp"
43 #include "nav_msgs/msg/path.hpp"
44 #include "visualization_msgs/msg/marker_array.hpp"
45 
46 #include "rclcpp/rclcpp.hpp"
47 #include "rclcpp_lifecycle/lifecycle_node.hpp"
48 
49 #include "nav2_util/node_utils.hpp"
50 #include "nav2_core/goal_checker.hpp"
51 
52 #include "nav2_mppi_controller/models/optimizer_settings.hpp"
53 #include "nav2_mppi_controller/models/control_sequence.hpp"
54 #include "nav2_mppi_controller/models/path.hpp"
55 #include "builtin_interfaces/msg/time.hpp"
56 #include "nav2_mppi_controller/critic_data.hpp"
57 
58 #define M_PIF 3.141592653589793238462643383279502884e+00F
59 #define M_PIF_2 1.5707963267948966e+00F
60 
61 namespace mppi::utils
62 {
63 using xt::evaluation_strategy::immediate;
64 
72 inline geometry_msgs::msg::Pose createPose(double x, double y, double z)
73 {
74  geometry_msgs::msg::Pose pose;
75  pose.position.x = x;
76  pose.position.y = y;
77  pose.position.z = z;
78  pose.orientation.w = 1;
79  pose.orientation.x = 0;
80  pose.orientation.y = 0;
81  pose.orientation.z = 0;
82  return pose;
83 }
84 
92 inline geometry_msgs::msg::Vector3 createScale(double x, double y, double z)
93 {
94  geometry_msgs::msg::Vector3 scale;
95  scale.x = x;
96  scale.y = y;
97  scale.z = z;
98  return scale;
99 }
100 
109 inline std_msgs::msg::ColorRGBA createColor(float r, float g, float b, float a)
110 {
111  std_msgs::msg::ColorRGBA color;
112  color.r = r;
113  color.g = g;
114  color.b = b;
115  color.a = a;
116  return color;
117 }
118 
128 inline visualization_msgs::msg::Marker createMarker(
129  int id, const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Vector3 & scale,
130  const std_msgs::msg::ColorRGBA & color, const std::string & frame_id, const std::string & ns)
131 {
132  using visualization_msgs::msg::Marker;
133  Marker marker;
134  marker.header.frame_id = frame_id;
135  marker.header.stamp = rclcpp::Time(0, 0);
136  marker.ns = ns;
137  marker.id = id;
138  marker.type = Marker::SPHERE;
139  marker.action = Marker::ADD;
140 
141  marker.pose = pose;
142  marker.scale = scale;
143  marker.color = color;
144  return marker;
145 }
146 
154 inline geometry_msgs::msg::TwistStamped toTwistStamped(
155  float vx, float wz, const builtin_interfaces::msg::Time & stamp, const std::string & frame)
156 {
157  geometry_msgs::msg::TwistStamped twist;
158  twist.header.frame_id = frame;
159  twist.header.stamp = stamp;
160  twist.twist.linear.x = vx;
161  twist.twist.angular.z = wz;
162 
163  return twist;
164 }
165 
174 inline geometry_msgs::msg::TwistStamped toTwistStamped(
175  float vx, float vy, float wz, const builtin_interfaces::msg::Time & stamp,
176  const std::string & frame)
177 {
178  auto twist = toTwistStamped(vx, wz, stamp, frame);
179  twist.twist.linear.y = vy;
180 
181  return twist;
182 }
183 
189 inline models::Path toTensor(const nav_msgs::msg::Path & path)
190 {
191  auto result = models::Path{};
192  result.reset(path.poses.size());
193 
194  for (size_t i = 0; i < path.poses.size(); ++i) {
195  result.x(i) = path.poses[i].pose.position.x;
196  result.y(i) = path.poses[i].pose.position.y;
197  result.yaws(i) = tf2::getYaw(path.poses[i].pose.orientation);
198  }
199 
200  return result;
201 }
202 
210 inline bool withinPositionGoalTolerance(
211  nav2_core::GoalChecker * goal_checker,
212  const geometry_msgs::msg::Pose & robot,
213  const geometry_msgs::msg::Pose & goal)
214 {
215  if (goal_checker) {
216  geometry_msgs::msg::Pose pose_tolerance;
217  geometry_msgs::msg::Twist velocity_tolerance;
218  goal_checker->getTolerances(pose_tolerance, velocity_tolerance);
219 
220  const auto pose_tolerance_sq = pose_tolerance.position.x * pose_tolerance.position.x;
221 
222  auto dx = robot.position.x - goal.position.x;
223  auto dy = robot.position.y - goal.position.y;
224 
225  auto dist_sq = dx * dx + dy * dy;
226 
227  if (dist_sq < pose_tolerance_sq) {
228  return true;
229  }
230  }
231 
232  return false;
233 }
234 
242 inline bool withinPositionGoalTolerance(
243  float pose_tolerance,
244  const geometry_msgs::msg::Pose & robot,
245  const geometry_msgs::msg::Pose & goal)
246 {
247  const double & dist_sq =
248  std::pow(goal.position.x - robot.position.x, 2) +
249  std::pow(goal.position.y - robot.position.y, 2);
250 
251  const float pose_tolerance_sq = pose_tolerance * pose_tolerance;
252 
253  if (dist_sq < pose_tolerance_sq) {
254  return true;
255  }
256 
257  return false;
258 }
259 
267 template<typename T>
268 auto normalize_angles(const T & angles)
269 {
270  auto theta = xt::eval(xt::fmod(angles + M_PIF, 2.0f * M_PIF));
271  return xt::eval(xt::where(theta < 0.0f, theta + M_PIF, theta - M_PIF));
272 }
273 
287 template<typename F, typename T>
288 auto shortest_angular_distance(
289  const F & from,
290  const T & to)
291 {
292  return normalize_angles(to - from);
293 }
294 
301 inline size_t findPathFurthestReachedPoint(const CriticData & data)
302 {
303  const auto traj_x = xt::view(data.trajectories.x, xt::all(), -1, xt::newaxis());
304  const auto traj_y = xt::view(data.trajectories.y, xt::all(), -1, xt::newaxis());
305 
306  const auto dx = data.path.x - traj_x;
307  const auto dy = data.path.y - traj_y;
308 
309  const auto dists = dx * dx + dy * dy;
310 
311  size_t max_id_by_trajectories = 0, min_id_by_path = 0;
312  float min_distance_by_path = std::numeric_limits<float>::max();
313 
314  for (size_t i = 0; i < dists.shape(0); i++) {
315  min_id_by_path = 0;
316  min_distance_by_path = std::numeric_limits<float>::max();
317  for (size_t j = max_id_by_trajectories; j < dists.shape(1); j++) {
318  const float cur_dist = dists(i, j);
319  if (cur_dist < min_distance_by_path) {
320  min_distance_by_path = cur_dist;
321  min_id_by_path = j;
322  }
323  }
324  max_id_by_trajectories = std::max(max_id_by_trajectories, min_id_by_path);
325  }
326  return max_id_by_trajectories;
327 }
328 
333 inline void setPathFurthestPointIfNotSet(CriticData & data)
334 {
335  if (!data.furthest_reached_path_point) {
336  data.furthest_reached_path_point = findPathFurthestReachedPoint(data);
337  }
338 }
339 
344 inline void findPathCosts(
345  CriticData & data,
346  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
347 {
348  auto * costmap = costmap_ros->getCostmap();
349  unsigned int map_x, map_y;
350  const size_t path_segments_count = data.path.x.shape(0) - 1;
351  data.path_pts_valid = std::vector<bool>(path_segments_count, false);
352  const bool tracking_unknown = costmap_ros->getLayeredCostmap()->isTrackingUnknown();
353  for (unsigned int idx = 0; idx < path_segments_count; idx++) {
354  if (!costmap->worldToMap(data.path.x(idx), data.path.y(idx), map_x, map_y)) {
355  (*data.path_pts_valid)[idx] = false;
356  continue;
357  }
358 
359  switch (costmap->getCost(map_x, map_y)) {
360  case (nav2_costmap_2d::LETHAL_OBSTACLE):
361  (*data.path_pts_valid)[idx] = false;
362  continue;
363  case (nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE):
364  (*data.path_pts_valid)[idx] = false;
365  continue;
366  case (nav2_costmap_2d::NO_INFORMATION):
367  (*data.path_pts_valid)[idx] = tracking_unknown ? true : false;
368  continue;
369  }
370 
371  (*data.path_pts_valid)[idx] = true;
372  }
373 }
374 
379 inline void setPathCostsIfNotSet(
380  CriticData & data,
381  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
382 {
383  if (!data.path_pts_valid) {
384  findPathCosts(data, costmap_ros);
385  }
386 }
387 
396 inline float posePointAngle(
397  const geometry_msgs::msg::Pose & pose, double point_x, double point_y, bool forward_preference)
398 {
399  float pose_x = pose.position.x;
400  float pose_y = pose.position.y;
401  float pose_yaw = tf2::getYaw(pose.orientation);
402 
403  float yaw = atan2f(point_y - pose_y, point_x - pose_x);
404 
405  // If no preference for forward, return smallest angle either in heading or 180 of heading
406  if (!forward_preference) {
407  return std::min(
408  fabs(angles::shortest_angular_distance(yaw, pose_yaw)),
409  fabs(angles::shortest_angular_distance(yaw, angles::normalize_angle(pose_yaw + M_PIF))));
410  }
411 
412  return fabs(angles::shortest_angular_distance(yaw, pose_yaw));
413 }
414 
423 inline float posePointAngle(
424  const geometry_msgs::msg::Pose & pose,
425  double point_x, double point_y, double point_yaw)
426 {
427  float pose_x = static_cast<float>(pose.position.x);
428  float pose_y = static_cast<float>(pose.position.y);
429  float pose_yaw = static_cast<float>(tf2::getYaw(pose.orientation));
430 
431  float yaw = atan2f(static_cast<float>(point_y) - pose_y, static_cast<float>(point_x) - pose_x);
432 
433  if (fabs(angles::shortest_angular_distance(yaw, static_cast<float>(point_yaw))) > M_PIF_2) {
434  yaw = angles::normalize_angle(yaw + M_PIF);
435  }
436 
437  return fabs(angles::shortest_angular_distance(yaw, pose_yaw));
438 }
439 
446 inline void savitskyGolayFilter(
447  models::ControlSequence & control_sequence,
448  std::array<mppi::models::Control, 4> & control_history,
449  const models::OptimizerSettings & settings)
450 {
451  // Savitzky-Golay Quadratic, 9-point Coefficients
452  xt::xarray<float> filter = {-21.0, 14.0, 39.0, 54.0, 59.0, 54.0, 39.0, 14.0, -21.0};
453  filter /= 231.0;
454 
455  // Too short to smooth meaningfully
456  const unsigned int num_sequences = control_sequence.vx.shape(0) - 1;
457  if (num_sequences < 20) {
458  return;
459  }
460 
461  auto applyFilter = [&](const xt::xarray<float> & data) -> float {
462  return xt::sum(data * filter, {0}, immediate)();
463  };
464 
465  auto applyFilterOverAxis =
466  [&](xt::xtensor<float, 1> & sequence, const xt::xtensor<float, 1> & initial_sequence,
467  const float hist_0, const float hist_1, const float hist_2, const float hist_3) -> void
468  {
469  float pt_m4 = hist_0;
470  float pt_m3 = hist_1;
471  float pt_m2 = hist_2;
472  float pt_m1 = hist_3;
473  float pt = initial_sequence(0);
474  float pt_p1 = initial_sequence(1);
475  float pt_p2 = initial_sequence(2);
476  float pt_p3 = initial_sequence(3);
477  float pt_p4 = initial_sequence(4);
478 
479  for (unsigned int idx = 0; idx != num_sequences; idx++) {
480  sequence(idx) = applyFilter({pt_m4, pt_m3, pt_m2, pt_m1, pt, pt_p1, pt_p2, pt_p3, pt_p4});
481  pt_m4 = pt_m3;
482  pt_m3 = pt_m2;
483  pt_m2 = pt_m1;
484  pt_m1 = pt;
485  pt = pt_p1;
486  pt_p1 = pt_p2;
487  pt_p2 = pt_p3;
488  pt_p3 = pt_p4;
489 
490  if (idx + 5 < num_sequences) {
491  pt_p4 = initial_sequence(idx + 5);
492  } else {
493  // Return the last point
494  pt_p4 = initial_sequence(num_sequences);
495  }
496  }
497  };
498 
499  // Filter trajectories
500  const models::ControlSequence initial_control_sequence = control_sequence;
501  applyFilterOverAxis(
502  control_sequence.vx, initial_control_sequence.vx, control_history[0].vx,
503  control_history[1].vx, control_history[2].vx, control_history[3].vx);
504  applyFilterOverAxis(
505  control_sequence.vy, initial_control_sequence.vy, control_history[0].vy,
506  control_history[1].vy, control_history[2].vy, control_history[3].vy);
507  applyFilterOverAxis(
508  control_sequence.wz, initial_control_sequence.wz, control_history[0].wz,
509  control_history[1].wz, control_history[2].wz, control_history[3].wz);
510 
511  // Update control history
512  unsigned int offset = settings.shift_control_sequence ? 1 : 0;
513  control_history[0] = control_history[1];
514  control_history[1] = control_history[2];
515  control_history[2] = control_history[3];
516  control_history[3] = {
517  control_sequence.vx(offset),
518  control_sequence.vy(offset),
519  control_sequence.wz(offset)};
520 }
521 
527 inline unsigned int findFirstPathInversion(nav_msgs::msg::Path & path)
528 {
529  // At least 3 poses for a possible inversion
530  if (path.poses.size() < 3) {
531  return path.poses.size();
532  }
533 
534  // Iterating through the path to determine the position of the path inversion
535  for (unsigned int idx = 1; idx < path.poses.size() - 1; ++idx) {
536  // We have two vectors for the dot product OA and AB. Determining the vectors.
537  float oa_x = path.poses[idx].pose.position.x -
538  path.poses[idx - 1].pose.position.x;
539  float oa_y = path.poses[idx].pose.position.y -
540  path.poses[idx - 1].pose.position.y;
541  float ab_x = path.poses[idx + 1].pose.position.x -
542  path.poses[idx].pose.position.x;
543  float ab_y = path.poses[idx + 1].pose.position.y -
544  path.poses[idx].pose.position.y;
545 
546  // Checking for the existance of cusp, in the path, using the dot product.
547  float dot_product = (oa_x * ab_x) + (oa_y * ab_y);
548  if (dot_product < 0.0f) {
549  return idx + 1;
550  }
551  }
552 
553  return path.poses.size();
554 }
555 
561 inline unsigned int removePosesAfterFirstInversion(nav_msgs::msg::Path & path)
562 {
563  nav_msgs::msg::Path cropped_path = path;
564  const unsigned int first_after_inversion = findFirstPathInversion(cropped_path);
565  if (first_after_inversion == path.poses.size()) {
566  return 0u;
567  }
568 
569  cropped_path.poses.erase(
570  cropped_path.poses.begin() + first_after_inversion, cropped_path.poses.end());
571  path = cropped_path;
572  return first_after_inversion;
573 }
574 
581 inline unsigned int findClosestPathPt(
582  const std::vector<float> & vec, const float dist, const unsigned int init = 0u)
583 {
584  float distim1 = init != 0u ? vec[init] : 0.0f; // First is 0, no accumulated distance yet
585  float disti = 0.0f;
586  const unsigned int size = vec.size();
587  for (unsigned int i = init + 1; i != size; i++) {
588  disti = vec[i];
589  if (disti > dist) {
590  if (i > 0 && dist - distim1 < disti - dist) {
591  return i - 1;
592  }
593  return i;
594  }
595  distim1 = disti;
596  }
597  return size - 1;
598 }
599 
600 // A struct to hold pose data in floating point resolution
601 struct Pose2D
602 {
603  float x, y, theta;
604 };
605 
606 } // namespace mppi::utils
607 
608 #endif // NAV2_MPPI_CONTROLLER__TOOLS__UTILS_HPP_
Function-object for checking whether a goal has been reached.
virtual bool getTolerances(geometry_msgs::msg::Pose &pose_tolerance, geometry_msgs::msg::Twist &vel_tolerance)=0
Get the maximum possible tolerances used for goal checking in the major types. Any field without a va...
void reset(unsigned int size)
Reset path data.
Definition: path.hpp:41