Skip to content

Commit 587d3aa

Browse files
committed
Add rotation tolerance checks and path rotation handling in PathHandler
Signed-off-by: Tony Najjar <[email protected]>
1 parent 47327a5 commit 587d3aa

File tree

3 files changed

+158
-2
lines changed

3 files changed

+158
-2
lines changed

nav2_mppi_controller/include/nav2_mppi_controller/tools/path_handler.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,22 +135,36 @@ class PathHandler
135135
*/
136136
bool isWithinInversionTolerances(const geometry_msgs::msg::PoseStamped & robot_pose);
137137

138+
/**
139+
* @brief Check if the robot pose is within the set rotation tolerances
140+
* @param robot_pose Robot's current pose to check
141+
* @return bool If the robot pose is within the set rotation tolerances
142+
*/
143+
bool isWithinRotationTolerances(const geometry_msgs::msg::PoseStamped & robot_pose);
144+
138145
std::string name_;
139146
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_;
140147
std::shared_ptr<tf2_ros::Buffer> tf_buffer_;
141148
ParametersHandler * parameters_handler_;
142149

143150
nav_msgs::msg::Path global_plan_;
144151
nav_msgs::msg::Path global_plan_up_to_inversion_;
152+
nav_msgs::msg::Path global_plan_up_to_rotation_;
145153
rclcpp::Logger logger_{rclcpp::get_logger("MPPIController")};
146154

147155
double max_robot_pose_search_dist_{0};
148156
double prune_distance_{0};
149157
double transform_tolerance_{0};
150158
float inversion_xy_tolerance_{0.2};
151159
float inversion_yaw_tolerance{0.4};
160+
float rotation_xy_tolerance_{0.2};
161+
float rotation_yaw_tolerance_{0.4};
162+
float rotation_translation_threshold_{0.02};
163+
float rotation_rotation_threshold_{0.785};
152164
bool enforce_path_inversion_{false};
165+
bool enforce_path_rotation_{false};
153166
unsigned int inversion_locale_{0u};
167+
unsigned int rotation_locale_{0u};
154168
};
155169
} // namespace mppi
156170

nav2_mppi_controller/include/nav2_mppi_controller/tools/utils.hpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,8 +510,71 @@ inline void savitskyGolayFilter(
510510
control_sequence.wz(offset)};
511511
}
512512

513+
/**
514+
* @brief Find the first pose at which there is an in-place rotation in the path
515+
* @param path Path to check
516+
* @param translation_threshold Maximum translation per pose transition
517+
* @param rotation_threshold Minimum total rotation to consider significant
518+
* @return Index after the rotation sequence if found, path size if no rotation detected
519+
*/
520+
inline unsigned int findFirstPathRotation(
521+
const nav_msgs::msg::Path & path,
522+
float translation_threshold,
523+
float rotation_threshold)
524+
{
525+
if (path.poses.size() < 2) {
526+
return path.poses.size();
527+
}
528+
529+
// Iterate through path to find first rotation sequence
530+
for (unsigned int start_idx = 0; start_idx < path.poses.size() - 1; ++start_idx) {
531+
// Check initial translation
532+
float ab_x = path.poses[start_idx + 1].pose.position.x -
533+
path.poses[start_idx].pose.position.x;
534+
float ab_y = path.poses[start_idx + 1].pose.position.y -
535+
path.poses[start_idx].pose.position.y;
536+
float initial_translation = sqrtf(ab_x * ab_x + ab_y * ab_y);
537+
538+
if (initial_translation >= translation_threshold) {
539+
continue; // Not a rotation sequence, try next pose
540+
}
541+
542+
// Start of potential rotation sequence
543+
float start_yaw = tf2::getYaw(path.poses[start_idx].pose.orientation);
544+
unsigned int j = start_idx + 1;
545+
unsigned int last_valid_j = j; // Track the last pose in the rotation sequence
546+
547+
// Keep accumulating poses while translation remains small
548+
while (j < path.poses.size() - 1) {
549+
float next_x = path.poses[j + 1].pose.position.x - path.poses[j].pose.position.x;
550+
float next_y = path.poses[j + 1].pose.position.y - path.poses[j].pose.position.y;
551+
float next_translation = sqrtf(next_x * next_x + next_y * next_y);
552+
553+
// If individual translation is above threshold, end of rotation sequence
554+
if (next_translation >= translation_threshold) {
555+
break;
556+
}
557+
558+
j++;
559+
last_valid_j = j; // Update the last valid pose in the sequence
560+
}
561+
562+
// Check if we have accumulated enough rotation
563+
float end_yaw = tf2::getYaw(path.poses[last_valid_j].pose.orientation);
564+
float total_rotation = fabs(angles::shortest_angular_distance(start_yaw, end_yaw));
565+
566+
if (total_rotation > rotation_threshold) {
567+
// Return the index after the last pose in the rotation sequence
568+
return last_valid_j + 1;
569+
}
570+
}
571+
572+
return path.poses.size(); // No significant rotation detected
573+
}
574+
513575
/**
514576
* @brief Find the iterator of the first pose at which there is an inversion on the path,
577+
* or where the robot rotates in place
515578
* @param path to check for inversion
516579
* @return the first point after the inversion found in the path
517580
*/
@@ -563,6 +626,38 @@ inline unsigned int removePosesAfterFirstInversion(nav_msgs::msg::Path & path)
563626
return first_after_inversion;
564627
}
565628

629+
/**
630+
* @brief Find and remove poses after the first in-place rotation in the path
631+
* @param path to check for rotation
632+
* @param translation_threshold Maximum translation per pose transition
633+
* @param rotation_threshold Minimum total rotation to consider significant
634+
* @return The location of the rotation, return 0 if none exist
635+
*/
636+
inline unsigned int removePosesAfterFirstRotation(
637+
nav_msgs::msg::Path & path,
638+
float translation_threshold,
639+
float rotation_threshold)
640+
{
641+
if (path.poses.size() < 2) {
642+
return 0u;
643+
}
644+
645+
nav_msgs::msg::Path cropped_path = path;
646+
// Find first in-place rotation
647+
const unsigned int first_after_rotation = findFirstPathRotation(
648+
cropped_path, translation_threshold, rotation_threshold);
649+
650+
if (first_after_rotation == path.poses.size()) {
651+
return 0u; // No rotation found
652+
}
653+
654+
// Remove poses after the rotation
655+
cropped_path.poses.erase(
656+
cropped_path.poses.begin() + first_after_rotation, cropped_path.poses.end());
657+
path = cropped_path;
658+
return first_after_rotation;
659+
}
660+
566661
/**
567662
* @brief Compare to trajectory points to find closest path point along integrated distances
568663
* @param vec Vect to check

nav2_mppi_controller/src/path_handler.cpp

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ void PathHandler::initialize(
4444
getParam(inversion_yaw_tolerance, "inversion_yaw_tolerance", 0.4);
4545
inversion_locale_ = 0u;
4646
}
47+
getParam(enforce_path_rotation_, "enforce_path_rotation", false);
48+
if (enforce_path_rotation_) {
49+
getParam(rotation_xy_tolerance_, "rotation_xy_tolerance", 0.2);
50+
getParam(rotation_yaw_tolerance_, "rotation_yaw_tolerance", 0.4);
51+
getParam(rotation_translation_threshold_, "rotation_translation_threshold", 0.02);
52+
getParam(rotation_rotation_threshold_, "rotation_rotation_threshold", 0.785);
53+
rotation_locale_ = 0u;
54+
}
4755
}
4856

4957
std::pair<nav_msgs::msg::Path, PathIterator>
@@ -139,6 +147,15 @@ nav_msgs::msg::Path PathHandler::transformPath(
139147
}
140148
}
141149

150+
if (enforce_path_rotation_ && rotation_locale_ != 0u) {
151+
if (isWithinRotationTolerances(global_pose)) {
152+
// Robot has completed the rotation, unlock the rest of the path
153+
global_plan_up_to_rotation_ = global_plan_;
154+
global_plan_up_to_inversion_ = global_plan_;
155+
rotation_locale_ = 0u; // Reset since rotation is complete
156+
}
157+
}
158+
142159
if (transformed_plan.poses.empty()) {
143160
throw nav2_core::InvalidPath("Resulting plan has 0 poses in it.");
144161
}
@@ -156,10 +173,26 @@ double PathHandler::getMaxCostmapDist()
156173
void PathHandler::setPath(const nav_msgs::msg::Path & plan)
157174
{
158175
global_plan_ = plan;
159-
global_plan_up_to_inversion_ = global_plan_;
176+
177+
// Start with the full plan and progressively apply restrictions
178+
nav_msgs::msg::Path most_restrictive_plan = global_plan_;
179+
180+
// Check for rotation restrictions first
181+
if (enforce_path_rotation_) {
182+
rotation_locale_ = utils::removePosesAfterFirstRotation(
183+
most_restrictive_plan,
184+
rotation_translation_threshold_,
185+
rotation_rotation_threshold_);
186+
}
187+
188+
// Check for inversion restrictions (may further restrict the path)
160189
if (enforce_path_inversion_) {
161-
inversion_locale_ = utils::removePosesAfterFirstInversion(global_plan_up_to_inversion_);
190+
inversion_locale_ = utils::removePosesAfterFirstInversion(most_restrictive_plan);
162191
}
192+
193+
// Set the working paths to the most restrictive version
194+
global_plan_up_to_rotation_ = most_restrictive_plan;
195+
global_plan_up_to_inversion_ = most_restrictive_plan;
163196
}
164197

165198
nav_msgs::msg::Path & PathHandler::getPath() {return global_plan_;}
@@ -202,4 +235,18 @@ bool PathHandler::isWithinInversionTolerances(const geometry_msgs::msg::PoseStam
202235
return distance <= inversion_xy_tolerance_ && fabs(angle_distance) <= inversion_yaw_tolerance;
203236
}
204237

238+
bool PathHandler::isWithinRotationTolerances(const geometry_msgs::msg::PoseStamped & robot_pose)
239+
{
240+
// Keep full path if we are within tolerance of the rotation pose
241+
const auto last_pose = global_plan_up_to_rotation_.poses.back();
242+
float distance = hypotf(
243+
robot_pose.pose.position.x - last_pose.pose.position.x,
244+
robot_pose.pose.position.y - last_pose.pose.position.y);
245+
246+
float angle_distance = angles::shortest_angular_distance(
247+
tf2::getYaw(robot_pose.pose.orientation),
248+
tf2::getYaw(last_pose.pose.orientation));
249+
return distance <= rotation_xy_tolerance_ && fabs(angle_distance) <= rotation_yaw_tolerance_;
250+
}
251+
205252
} // namespace mppi

0 commit comments

Comments
 (0)