16 #ifndef NAV2_MPPI_CONTROLLER__TOOLS__UTILS_HPP_
17 #define NAV2_MPPI_CONTROLLER__TOOLS__UTILS_HPP_
19 #include <Eigen/Dense>
28 #include "angles/angles.h"
30 #include "tf2/utils.hpp"
31 #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
33 #include "geometry_msgs/msg/twist_stamped.hpp"
34 #include "nav_msgs/msg/path.hpp"
35 #include "nav2_msgs/msg/trajectory.hpp"
36 #include "visualization_msgs/msg/marker_array.hpp"
38 #include "rclcpp/rclcpp.hpp"
39 #include "rclcpp_lifecycle/lifecycle_node.hpp"
41 #include "nav2_util/node_utils.hpp"
42 #include "nav2_core/goal_checker.hpp"
44 #include "nav2_mppi_controller/models/optimizer_settings.hpp"
45 #include "nav2_mppi_controller/models/control_sequence.hpp"
46 #include "nav2_mppi_controller/models/path.hpp"
47 #include "builtin_interfaces/msg/time.hpp"
48 #include "nav2_mppi_controller/critic_data.hpp"
50 #define M_PIF 3.141592653589793238462643383279502884e+00F
51 #define M_PIF_2 1.5707963267948966e+00F
62 inline geometry_msgs::msg::Pose createPose(
double x,
double y,
double z)
64 geometry_msgs::msg::Pose pose;
68 pose.orientation.w = 1;
69 pose.orientation.x = 0;
70 pose.orientation.y = 0;
71 pose.orientation.z = 0;
82 inline geometry_msgs::msg::Vector3 createScale(
double x,
double y,
double z)
84 geometry_msgs::msg::Vector3 scale;
99 inline std_msgs::msg::ColorRGBA createColor(
float r,
float g,
float b,
float a)
101 std_msgs::msg::ColorRGBA color;
118 inline visualization_msgs::msg::Marker createMarker(
119 int id,
const geometry_msgs::msg::Pose & pose,
const geometry_msgs::msg::Vector3 & scale,
120 const std_msgs::msg::ColorRGBA & color,
const std::string & frame_id,
const std::string & ns)
122 using visualization_msgs::msg::Marker;
124 marker.header.frame_id = frame_id;
125 marker.header.stamp = rclcpp::Time(0, 0);
128 marker.type = Marker::SPHERE;
129 marker.action = Marker::ADD;
132 marker.scale = scale;
133 marker.color = color;
144 inline geometry_msgs::msg::TwistStamped toTwistStamped(
145 float vx,
float wz,
const builtin_interfaces::msg::Time & stamp,
const std::string & frame)
147 geometry_msgs::msg::TwistStamped twist;
148 twist.header.frame_id = frame;
149 twist.header.stamp = stamp;
150 twist.twist.linear.x = vx;
151 twist.twist.angular.z = wz;
164 inline geometry_msgs::msg::TwistStamped toTwistStamped(
165 float vx,
float vy,
float wz,
const builtin_interfaces::msg::Time & stamp,
166 const std::string & frame)
168 auto twist = toTwistStamped(vx, wz, stamp, frame);
169 twist.twist.linear.y = vy;
174 inline std::unique_ptr<nav2_msgs::msg::Trajectory> toTrajectoryMsg(
175 const Eigen::ArrayXXf & trajectory,
176 const models::ControlSequence & control_sequence,
177 const double & model_dt,
178 const std_msgs::msg::Header & header)
180 auto trajectory_msg = std::make_unique<nav2_msgs::msg::Trajectory>();
181 trajectory_msg->header = header;
182 trajectory_msg->points.resize(trajectory.rows());
184 for (
int i = 0; i < trajectory.rows(); ++i) {
185 auto & curr_pt = trajectory_msg->points[i];
186 curr_pt.time_from_start = rclcpp::Duration::from_seconds(i * model_dt);
187 curr_pt.pose.position.x = trajectory(i, 0);
188 curr_pt.pose.position.y = trajectory(i, 1);
189 tf2::Quaternion quat;
190 quat.setRPY(0.0, 0.0, trajectory(i, 2));
191 curr_pt.pose.orientation = tf2::toMsg(quat);
192 curr_pt.velocity.linear.x = control_sequence.vx(i);
193 curr_pt.velocity.angular.z = control_sequence.wz(i);
194 if (control_sequence.vy.size() > 0) {
195 curr_pt.velocity.linear.y = control_sequence.vy(i);
199 return trajectory_msg;
207 inline models::Path toTensor(
const nav_msgs::msg::Path & path)
209 auto result = models::Path{};
210 result.
reset(path.poses.size());
212 for (
size_t i = 0; i < path.poses.size(); ++i) {
213 result.x(i) = path.poses[i].pose.position.x;
214 result.y(i) = path.poses[i].pose.position.y;
215 result.yaws(i) = tf2::getYaw(path.poses[i].pose.orientation);
226 inline geometry_msgs::msg::Pose getLastPathPose(
const models::Path & path)
228 const unsigned int path_last_idx = path.x.size() - 1;
230 auto last_orientation = path.yaws(path_last_idx);
232 tf2::Quaternion pose_orientation;
233 pose_orientation.setRPY(0.0, 0.0, last_orientation);
235 geometry_msgs::msg::Pose pathPose;
236 pathPose.position.x = path.x(path_last_idx);
237 pathPose.position.y = path.y(path_last_idx);
238 pathPose.orientation.x = pose_orientation.x();
239 pathPose.orientation.y = pose_orientation.y();
240 pathPose.orientation.z = pose_orientation.z();
241 pathPose.orientation.w = pose_orientation.w();
253 inline geometry_msgs::msg::Pose getCriticGoal(
254 const CriticData & data,
255 bool enforce_path_inversion)
257 if (enforce_path_inversion) {
258 return getLastPathPose(data.path);
271 inline bool withinPositionGoalTolerance(
273 const geometry_msgs::msg::Pose & robot,
274 const geometry_msgs::msg::Pose & goal)
277 geometry_msgs::msg::Pose pose_tolerance;
278 geometry_msgs::msg::Twist velocity_tolerance;
279 goal_checker->
getTolerances(pose_tolerance, velocity_tolerance);
281 const auto pose_tolerance_sq = pose_tolerance.position.x * pose_tolerance.position.x;
283 auto dx = robot.position.x - goal.position.x;
284 auto dy = robot.position.y - goal.position.y;
286 auto dist_sq = dx * dx + dy * dy;
288 if (dist_sq < pose_tolerance_sq) {
303 inline bool withinPositionGoalTolerance(
304 float pose_tolerance,
305 const geometry_msgs::msg::Pose & robot,
306 const geometry_msgs::msg::Pose & goal)
308 const double & dist_sq =
309 std::pow(goal.position.x - robot.position.x, 2) +
310 std::pow(goal.position.y - robot.position.y, 2);
312 const float pose_tolerance_sq = pose_tolerance * pose_tolerance;
314 if (dist_sq < pose_tolerance_sq) {
329 auto normalize_angles(
const T & angles)
331 return (angles + M_PIF).unaryExpr([&](
const float x) {
332 float remainder = std::fmod(x, 2.0f * M_PIF);
333 return remainder < 0.0f ? remainder + M_PIF : remainder - M_PIF;
350 template<
typename F,
typename T>
351 auto shortest_angular_distance(
355 return normalize_angles(to - from);
364 inline size_t findPathFurthestReachedPoint(
const CriticData & data)
366 int traj_cols = data.trajectories.x.cols();
367 const auto traj_x = data.trajectories.x.col(traj_cols - 1);
368 const auto traj_y = data.trajectories.y.col(traj_cols - 1);
370 const auto dx = (data.path.x.transpose()).replicate(traj_x.rows(), 1).colwise() - traj_x;
371 const auto dy = (data.path.y.transpose()).replicate(traj_y.rows(), 1).colwise() - traj_y;
373 const auto dists = dx * dx + dy * dy;
375 int max_id_by_trajectories = 0, min_id_by_path = 0;
376 float min_distance_by_path = std::numeric_limits<float>::max();
377 size_t n_rows = dists.rows();
378 size_t n_cols = dists.cols();
379 for (
size_t i = 0; i != n_rows; i++) {
381 min_distance_by_path = std::numeric_limits<float>::max();
382 for (
size_t j = max_id_by_trajectories; j != n_cols; j++) {
383 const float cur_dist = dists(i, j);
384 if (cur_dist < min_distance_by_path) {
385 min_distance_by_path = cur_dist;
389 max_id_by_trajectories = std::max(max_id_by_trajectories, min_id_by_path);
391 return max_id_by_trajectories;
398 inline void setPathFurthestPointIfNotSet(CriticData & data)
400 if (!data.furthest_reached_path_point) {
401 data.furthest_reached_path_point = findPathFurthestReachedPoint(data);
409 inline void findPathCosts(
411 std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
413 auto * costmap = costmap_ros->getCostmap();
414 unsigned int map_x, map_y;
415 const size_t path_segments_count = data.path.x.size() - 1;
416 data.path_pts_valid = std::vector<bool>(path_segments_count,
false);
417 const bool tracking_unknown = costmap_ros->getLayeredCostmap()->isTrackingUnknown();
418 for (
unsigned int idx = 0; idx < path_segments_count; idx++) {
419 if (!costmap->worldToMap(data.path.x(idx), data.path.y(idx), map_x, map_y)) {
420 (*data.path_pts_valid)[idx] =
false;
424 switch (costmap->getCost(map_x, map_y)) {
425 case (nav2_costmap_2d::LETHAL_OBSTACLE):
426 (*data.path_pts_valid)[idx] =
false;
428 case (nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE):
429 (*data.path_pts_valid)[idx] =
false;
431 case (nav2_costmap_2d::NO_INFORMATION):
432 (*data.path_pts_valid)[idx] = tracking_unknown ?
true :
false;
436 (*data.path_pts_valid)[idx] =
true;
444 inline void setPathCostsIfNotSet(
446 std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
448 if (!data.path_pts_valid) {
449 findPathCosts(data, costmap_ros);
461 inline float posePointAngle(
462 const geometry_msgs::msg::Pose & pose,
double point_x,
double point_y,
bool forward_preference)
464 float pose_x = pose.position.x;
465 float pose_y = pose.position.y;
466 float pose_yaw = tf2::getYaw(pose.orientation);
468 float yaw = atan2f(point_y - pose_y, point_x - pose_x);
471 if (!forward_preference) {
473 fabs(angles::shortest_angular_distance(yaw, pose_yaw)),
474 fabs(angles::shortest_angular_distance(yaw, angles::normalize_angle(pose_yaw + M_PIF))));
477 return fabs(angles::shortest_angular_distance(yaw, pose_yaw));
488 inline float posePointAngle(
489 const geometry_msgs::msg::Pose & pose,
490 double point_x,
double point_y,
double point_yaw)
492 float pose_x =
static_cast<float>(pose.position.x);
493 float pose_y =
static_cast<float>(pose.position.y);
494 float pose_yaw =
static_cast<float>(tf2::getYaw(pose.orientation));
496 float yaw = atan2f(
static_cast<float>(point_y) - pose_y,
static_cast<float>(point_x) - pose_x);
498 if (fabs(angles::shortest_angular_distance(yaw,
static_cast<float>(point_yaw))) > M_PIF_2) {
499 yaw = angles::normalize_angle(yaw + M_PIF);
502 return fabs(angles::shortest_angular_distance(yaw, pose_yaw));
511 inline void savitskyGolayFilter(
512 models::ControlSequence & control_sequence,
513 std::array<mppi::models::Control, 4> & control_history,
514 const models::OptimizerSettings & settings)
517 Eigen::Array<float, 9, 1> filter = {-21.0f, 14.0f, 39.0f, 54.0f, 59.0f, 54.0f, 39.0f, 14.0f,
522 const unsigned int num_sequences = control_sequence.vx.size() - 1;
523 if (num_sequences < 20) {
527 auto applyFilter = [&](
const Eigen::Array<float, 9, 1> & data) ->
float {
528 return (data * filter).eval().sum();
531 auto applyFilterOverAxis =
532 [&](Eigen::ArrayXf & sequence,
const Eigen::ArrayXf & initial_sequence,
533 const float hist_0,
const float hist_1,
const float hist_2,
const float hist_3) ->
void
535 float pt_m4 = hist_0;
536 float pt_m3 = hist_1;
537 float pt_m2 = hist_2;
538 float pt_m1 = hist_3;
539 float pt = initial_sequence(0);
540 float pt_p1 = initial_sequence(1);
541 float pt_p2 = initial_sequence(2);
542 float pt_p3 = initial_sequence(3);
543 float pt_p4 = initial_sequence(4);
545 for (
unsigned int idx = 0; idx != num_sequences; idx++) {
546 sequence(idx) = applyFilter({pt_m4, pt_m3, pt_m2, pt_m1, pt, pt_p1, pt_p2, pt_p3, pt_p4});
556 if (idx + 5 < num_sequences) {
557 pt_p4 = initial_sequence(idx + 5);
560 pt_p4 = initial_sequence(num_sequences);
566 const models::ControlSequence initial_control_sequence = control_sequence;
568 control_sequence.vx, initial_control_sequence.vx, control_history[0].vx,
569 control_history[1].vx, control_history[2].vx, control_history[3].vx);
571 control_sequence.vy, initial_control_sequence.vy, control_history[0].vy,
572 control_history[1].vy, control_history[2].vy, control_history[3].vy);
574 control_sequence.wz, initial_control_sequence.wz, control_history[0].wz,
575 control_history[1].wz, control_history[2].wz, control_history[3].wz);
578 unsigned int offset = settings.shift_control_sequence ? 1 : 0;
579 control_history[0] = control_history[1];
580 control_history[1] = control_history[2];
581 control_history[2] = control_history[3];
582 control_history[3] = {
583 control_sequence.vx(offset),
584 control_sequence.vy(offset),
585 control_sequence.wz(offset)};
593 inline unsigned int findFirstPathInversion(nav_msgs::msg::Path & path)
596 if (path.poses.size() < 3) {
597 return path.poses.size();
601 for (
unsigned int idx = 1; idx < path.poses.size() - 1; ++idx) {
603 float oa_x = path.poses[idx].pose.position.x -
604 path.poses[idx - 1].pose.position.x;
605 float oa_y = path.poses[idx].pose.position.y -
606 path.poses[idx - 1].pose.position.y;
607 float ab_x = path.poses[idx + 1].pose.position.x -
608 path.poses[idx].pose.position.x;
609 float ab_y = path.poses[idx + 1].pose.position.y -
610 path.poses[idx].pose.position.y;
613 float dot_product = (oa_x * ab_x) + (oa_y * ab_y);
614 if (dot_product < 0.0f) {
619 return path.poses.size();
627 inline unsigned int removePosesAfterFirstInversion(nav_msgs::msg::Path & path)
629 nav_msgs::msg::Path cropped_path = path;
630 const unsigned int first_after_inversion = findFirstPathInversion(cropped_path);
631 if (first_after_inversion == path.poses.size()) {
635 cropped_path.poses.erase(
636 cropped_path.poses.begin() + first_after_inversion, cropped_path.poses.end());
638 return first_after_inversion;
647 inline unsigned int findClosestPathPt(
648 const std::vector<float> & vec,
const float dist,
const unsigned int init = 0u)
650 float distim1 = init != 0u ? vec[init] : 0.0f;
652 const unsigned int size = vec.size();
653 for (
unsigned int i = init + 1; i != size; i++) {
656 if (i > 0 && dist - distim1 < disti - dist) {
679 inline void shiftColumnsByOnePlace(Eigen::Ref<Eigen::ArrayXXf> e,
int direction)
682 if(size == 1) {
return;}
683 if(abs(direction) != 1) {
684 throw std::logic_error(
"Invalid direction, only 1 and -1 are valid values.");
687 if((e.cols() == 1 || e.rows() == 1) && size > 1) {
688 auto start_ptr = direction == 1 ? e.data() + size - 2 : e.data() + 1;
689 auto end_ptr = direction == 1 ? e.data() : e.data() + size - 1;
690 while(start_ptr != end_ptr) {
691 *(start_ptr + direction) = *start_ptr;
692 start_ptr -= direction;
694 *(start_ptr + direction) = *start_ptr;
696 auto start_ptr = direction == 1 ? e.data() + size - 2 * e.rows() : e.data() + e.rows();
697 auto end_ptr = direction == 1 ? e.data() : e.data() + size - e.rows();
698 auto span = e.rows();
699 while(start_ptr != end_ptr) {
700 std::copy(start_ptr, start_ptr + span, start_ptr + direction * span);
701 start_ptr -= (direction * span);
703 std::copy(start_ptr, start_ptr + span, start_ptr + direction * span);
714 inline auto normalize_yaws_between_points(
715 const Eigen::Ref<const Eigen::ArrayXf> & last_yaws,
716 const Eigen::Ref<const Eigen::ArrayXf> & yaw_between_points)
718 Eigen::ArrayXf yaws = utils::shortest_angular_distance(
719 last_yaws, yaw_between_points).abs();
720 int size = yaws.size();
721 Eigen::ArrayXf yaws_between_points_corrected(size);
722 for(
int i = 0; i != size; i++) {
723 const float & yaw_between_point = yaw_between_points[i];
724 yaws_between_points_corrected[i] = yaws[i] < M_PIF_2 ?
725 yaw_between_point : angles::normalize_angle(yaw_between_point + M_PIF);
727 return yaws_between_points_corrected;
736 inline auto normalize_yaws_between_points(
737 const float goal_yaw,
const Eigen::Ref<const Eigen::ArrayXf> & yaw_between_points)
739 int size = yaw_between_points.size();
740 Eigen::ArrayXf yaws_between_points_corrected(size);
741 for(
int i = 0; i != size; i++) {
742 const float & yaw_between_point = yaw_between_points[i];
743 yaws_between_points_corrected[i] = fabs(
744 angles::normalize_angle(yaw_between_point - goal_yaw)) < M_PIF_2 ?
745 yaw_between_point : angles::normalize_angle(yaw_between_point + M_PIF);
747 return yaws_between_points_corrected;
757 const float lower_bound,
const float upper_bound,
const float input)
759 return std::min(upper_bound, std::max(input, lower_bound));
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.