Skip to content

Commit bab2a86

Browse files
Calculate next path point using lookahead for Graceful Controller (#5003)
* Add controller utils Signed-off-by: Sakshay Mahna <[email protected]> * Fix linting issues Signed-off-by: Sakshay Mahna <[email protected]> * Update regulated pure pursuit Signed-off-by: Sakshay Mahna <[email protected]> * Update graceful controller Signed-off-by: Sakshay Mahna <[email protected]> * Remove interpolate after goal & Use orientationAroundZAxis Signed-off-by: Sakshay Mahna <[email protected]> * Update nav2_util/src/controller_utils.cpp Signed-off-by: Steve Macenski <[email protected]> * Update nav2_util/src/controller_utils.cpp Signed-off-by: Steve Macenski <[email protected]> * Update nav2_util/src/controller_utils.cpp Signed-off-by: Steve Macenski <[email protected]> * Remove interpolate_after_goal parameter from test Signed-off-by: Sakshay Mahna <[email protected]> --------- Signed-off-by: Sakshay Mahna <[email protected]> Signed-off-by: Steve Macenski <[email protected]> Co-authored-by: Steve Macenski <[email protected]>
1 parent 2a947bf commit bab2a86

File tree

10 files changed

+556
-429
lines changed

10 files changed

+556
-429
lines changed

nav2_graceful_controller/include/nav2_graceful_controller/graceful_controller.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@ class GracefulController : public nav2_core::Controller
107107
void setSpeedLimit(const double & speed_limit, const bool & percentage) override;
108108

109109
protected:
110+
/**
111+
* @brief Validate a given target pose for calculating command velocity
112+
* @param target_pose Target pose to validate
113+
* @param dist_to_target Distance to target pose
114+
* @param dist_to_goal Distance to navigation goal
115+
* @param trajectory Trajectory to validate in simulation
116+
* @param costmap_transform Transform between global and local costmap
117+
* @param cmd_vel Initial command velocity to validate in simulation
118+
* @return true if target pose is valid, false otherwise
119+
*/
120+
bool validateTargetPose(
121+
geometry_msgs::msg::PoseStamped & target_pose,
122+
double dist_to_target,
123+
double dist_to_goal,
124+
nav_msgs::msg::Path & trajectory,
125+
geometry_msgs::msg::TransformStamped & costmap_transform,
126+
geometry_msgs::msg::TwistStamped & cmd_vel);
127+
110128
/**
111129
* @brief Simulate trajectory calculating in every step the new velocity command based on
112130
* a new curvature value and checking for collisions.

nav2_graceful_controller/src/graceful_controller.cpp

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "angles/angles.h"
1919
#include "nav2_core/controller_exceptions.hpp"
2020
#include "nav2_util/geometry_utils.hpp"
21+
#include "nav2_util/controller_utils.hpp"
2122
#include "nav2_graceful_controller/graceful_controller.hpp"
2223
#include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
2324

@@ -195,46 +196,38 @@ geometry_msgs::msg::TwistStamped GracefulController::computeVelocityCommands(
195196
// Else, fall through and see if we should follow control law longer
196197
}
197198

198-
// Precompute distance to candidate poses
199+
// Find a valid target pose and its trajectory
200+
nav_msgs::msg::Path local_plan;
201+
geometry_msgs::msg::PoseStamped target_pose;
202+
203+
double dist_to_target;
199204
std::vector<double> target_distances;
200205
computeDistanceAlongPath(transformed_plan.poses, target_distances);
201206

202-
// Work back from the end of plan to find valid target pose
207+
bool is_first_iteration = true;
203208
for (int i = transformed_plan.poses.size() - 1; i >= 0; --i) {
204-
// Underlying control law needs a single target pose, which should:
205-
// * Be as far away as possible from the robot (for smoothness)
206-
// * But no further than the max_lookahed_ distance
207-
// * Be feasible to reach in a collision free manner
208-
geometry_msgs::msg::PoseStamped target_pose = transformed_plan.poses[i];
209-
double dist_to_target = target_distances[i];
210-
211-
// Continue if target_pose is too far away from robot
212-
if (dist_to_target > params_->max_lookahead) {continue;}
213-
214-
if (dist_to_goal < params_->max_lookahead) {
215-
if (params_->prefer_final_rotation) {
216-
// Avoid instability and big sweeping turns at the end of paths by
217-
// ignoring final heading
218-
double yaw = std::atan2(target_pose.pose.position.y, target_pose.pose.position.x);
219-
target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(yaw);
220-
}
221-
} else if (dist_to_target < params_->min_lookahead) {
222-
// Make sure target is far enough away to avoid instability
223-
break;
224-
}
225-
226-
// Flip the orientation of the motion target if the robot is moving backwards
227-
bool reversing = false;
228-
if (params_->allow_backward && target_pose.pose.position.x < 0.0) {
229-
reversing = true;
230-
target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
231-
tf2::getYaw(target_pose.pose.orientation) + M_PI);
209+
if (is_first_iteration) {
210+
// Calculate target pose through lookahead interpolation to get most accurate
211+
// lookahead point, if possible
212+
dist_to_target = params_->max_lookahead;
213+
// Interpolate after goal false for graceful controller
214+
// Requires interpolating the orientation which is not yet implemented
215+
// Updates dist_to_target for target_pose returned if using the point on the path
216+
target_pose = nav2_util::getLookAheadPoint(dist_to_target, transformed_plan, false);
217+
is_first_iteration = false;
218+
} else {
219+
// Underlying control law needs a single target pose, which should:
220+
// * Be as far away as possible from the robot (for smoothness)
221+
// * But no further than the max_lookahed_ distance
222+
// * Be feasible to reach in a collision free manner
223+
dist_to_target = target_distances[i];
224+
target_pose = transformed_plan.poses[i];
232225
}
233226

234-
// Actually simulate our path
235-
nav_msgs::msg::Path local_plan;
236-
if (simulateTrajectory(target_pose, costmap_transform, local_plan, cmd_vel, reversing)) {
237-
// Successfully simulated to target_pose - compute velocity at this moment
227+
// Compute velocity at this moment if valid target pose is found
228+
if (validateTargetPose(
229+
target_pose, dist_to_target, dist_to_goal, local_plan, costmap_transform, cmd_vel))
230+
{
238231
// Publish the selected target_pose
239232
motion_target_pub_->publish(target_pose);
240233
// Publish marker for slowdown radius around motion target for debugging / visualization
@@ -283,6 +276,49 @@ void GracefulController::setSpeedLimit(
283276
}
284277
}
285278

279+
bool GracefulController::validateTargetPose(
280+
geometry_msgs::msg::PoseStamped & target_pose,
281+
double dist_to_target,
282+
double dist_to_goal,
283+
nav_msgs::msg::Path & trajectory,
284+
geometry_msgs::msg::TransformStamped & costmap_transform,
285+
geometry_msgs::msg::TwistStamped & cmd_vel)
286+
{
287+
// Continue if target_pose is too far away from robot
288+
if (dist_to_target > params_->max_lookahead) {
289+
return false;
290+
}
291+
292+
if (dist_to_goal < params_->max_lookahead) {
293+
if (params_->prefer_final_rotation) {
294+
// Avoid instability and big sweeping turns at the end of paths by
295+
// ignoring final heading
296+
double yaw = std::atan2(target_pose.pose.position.y, target_pose.pose.position.x);
297+
target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(yaw);
298+
}
299+
} else if (dist_to_target < params_->min_lookahead) {
300+
// Make sure target is far enough away to avoid instability
301+
return false;
302+
}
303+
304+
// Flip the orientation of the motion target if the robot is moving backwards
305+
bool reversing = false;
306+
if (params_->allow_backward && target_pose.pose.position.x < 0.0) {
307+
reversing = true;
308+
target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
309+
tf2::getYaw(target_pose.pose.orientation) + M_PI);
310+
}
311+
312+
// Actually simulate the path
313+
if (simulateTrajectory(target_pose, costmap_transform, trajectory, cmd_vel, reversing)) {
314+
// Successfully simulated to target_pose
315+
return true;
316+
}
317+
318+
// Validation not successful
319+
return false;
320+
}
321+
286322
bool GracefulController::simulateTrajectory(
287323
const geometry_msgs::msg::PoseStamped & motion_target,
288324
const geometry_msgs::msg::TransformStamped & costmap_transform,

nav2_regulated_pure_pursuit_controller/include/nav2_regulated_pure_pursuit_controller/regulated_pure_pursuit_controller.hpp

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -174,32 +174,6 @@ class RegulatedPurePursuitController : public nav2_core::Controller
174174
const double & pose_cost, const nav_msgs::msg::Path & path,
175175
double & linear_vel, double & sign);
176176

177-
/**
178-
* @brief Find the intersection a circle and a line segment.
179-
* This assumes the circle is centered at the origin.
180-
* If no intersection is found, a floating point error will occur.
181-
* @param p1 first endpoint of line segment
182-
* @param p2 second endpoint of line segment
183-
* @param r radius of circle
184-
* @return point of intersection
185-
*/
186-
static geometry_msgs::msg::Point circleSegmentIntersection(
187-
const geometry_msgs::msg::Point & p1,
188-
const geometry_msgs::msg::Point & p2,
189-
double r);
190-
191-
/**
192-
* @brief Get lookahead point
193-
* @param lookahead_dist Optimal lookahead distance
194-
* @param path Current global path
195-
* @param interpolate_after_goal If true, interpolate the lookahead point after the goal based
196-
* on the orientation given by the position of the last two pose of the path
197-
* @return Lookahead point
198-
*/
199-
geometry_msgs::msg::PoseStamped getLookAheadPoint(
200-
const double &, const nav_msgs::msg::Path &,
201-
bool interpolate_after_goal = false);
202-
203177
/**
204178
* @brief checks for the cusp position
205179
* @param pose Pose input to determine the cusp position

nav2_regulated_pure_pursuit_controller/src/regulated_pure_pursuit_controller.cpp

Lines changed: 3 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "nav2_core/controller_exceptions.hpp"
2626
#include "nav2_ros_common/node_utils.hpp"
2727
#include "nav2_util/geometry_utils.hpp"
28+
#include "nav2_util/controller_utils.hpp"
2829
#include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
2930

3031
using std::hypot;
@@ -204,7 +205,7 @@ geometry_msgs::msg::TwistStamped RegulatedPurePursuitController::computeVelocity
204205
}
205206

206207
// Get the particular point on the path at the lookahead distance
207-
auto carrot_pose = getLookAheadPoint(lookahead_dist, transformed_plan);
208+
auto carrot_pose = nav2_util::getLookAheadPoint(lookahead_dist, transformed_plan);
208209
auto rotate_to_path_carrot_pose = carrot_pose;
209210
carrot_pub_->publish(createCarrotMsg(carrot_pose));
210211

@@ -214,7 +215,7 @@ geometry_msgs::msg::TwistStamped RegulatedPurePursuitController::computeVelocity
214215

215216
double regulation_curvature = lookahead_curvature;
216217
if (params_->use_fixed_curvature_lookahead) {
217-
auto curvature_lookahead_pose = getLookAheadPoint(
218+
auto curvature_lookahead_pose = nav2_util::getLookAheadPoint(
218219
curv_lookahead_dist,
219220
transformed_plan, params_->interpolate_curvature_after_goal);
220221
rotate_to_path_carrot_pose = curvature_lookahead_pose;
@@ -358,100 +359,6 @@ void RegulatedPurePursuitController::rotateToHeading(
358359
}
359360
}
360361

361-
geometry_msgs::msg::Point RegulatedPurePursuitController::circleSegmentIntersection(
362-
const geometry_msgs::msg::Point & p1,
363-
const geometry_msgs::msg::Point & p2,
364-
double r)
365-
{
366-
// Formula for intersection of a line with a circle centered at the origin,
367-
// modified to always return the point that is on the segment between the two points.
368-
// https://mathworld.wolfram.com/Circle-LineIntersection.html
369-
// This works because the poses are transformed into the robot frame.
370-
// This can be derived from solving the system of equations of a line and a circle
371-
// which results in something that is just a reformulation of the quadratic formula.
372-
// Interactive illustration in doc/circle-segment-intersection.ipynb as well as at
373-
// https://www.desmos.com/calculator/td5cwbuocd
374-
double x1 = p1.x;
375-
double x2 = p2.x;
376-
double y1 = p1.y;
377-
double y2 = p2.y;
378-
379-
double dx = x2 - x1;
380-
double dy = y2 - y1;
381-
double dr2 = dx * dx + dy * dy;
382-
double D = x1 * y2 - x2 * y1;
383-
384-
// Augmentation to only return point within segment
385-
double d1 = x1 * x1 + y1 * y1;
386-
double d2 = x2 * x2 + y2 * y2;
387-
double dd = d2 - d1;
388-
389-
geometry_msgs::msg::Point p;
390-
double sqrt_term = std::sqrt(r * r * dr2 - D * D);
391-
p.x = (D * dy + std::copysign(1.0, dd) * dx * sqrt_term) / dr2;
392-
p.y = (-D * dx + std::copysign(1.0, dd) * dy * sqrt_term) / dr2;
393-
return p;
394-
}
395-
396-
geometry_msgs::msg::PoseStamped RegulatedPurePursuitController::getLookAheadPoint(
397-
const double & lookahead_dist,
398-
const nav_msgs::msg::Path & transformed_plan,
399-
bool interpolate_after_goal)
400-
{
401-
// Find the first pose which is at a distance greater than the lookahead distance
402-
auto goal_pose_it = std::find_if(
403-
transformed_plan.poses.begin(), transformed_plan.poses.end(), [&](const auto & ps) {
404-
return hypot(ps.pose.position.x, ps.pose.position.y) >= lookahead_dist;
405-
});
406-
407-
// If the no pose is not far enough, take the last pose
408-
if (goal_pose_it == transformed_plan.poses.end()) {
409-
if (interpolate_after_goal) {
410-
auto last_pose_it = std::prev(transformed_plan.poses.end());
411-
auto prev_last_pose_it = std::prev(last_pose_it);
412-
413-
double end_path_orientation = atan2(
414-
last_pose_it->pose.position.y - prev_last_pose_it->pose.position.y,
415-
last_pose_it->pose.position.x - prev_last_pose_it->pose.position.x);
416-
417-
// Project the last segment out to guarantee it is beyond the look ahead
418-
// distance
419-
auto projected_position = last_pose_it->pose.position;
420-
projected_position.x += cos(end_path_orientation) * lookahead_dist;
421-
projected_position.y += sin(end_path_orientation) * lookahead_dist;
422-
423-
// Use the circle intersection to find the position at the correct look
424-
// ahead distance
425-
const auto interpolated_position = circleSegmentIntersection(
426-
last_pose_it->pose.position, projected_position, lookahead_dist);
427-
428-
geometry_msgs::msg::PoseStamped interpolated_pose;
429-
interpolated_pose.header = last_pose_it->header;
430-
interpolated_pose.pose.position = interpolated_position;
431-
return interpolated_pose;
432-
} else {
433-
goal_pose_it = std::prev(transformed_plan.poses.end());
434-
}
435-
} else if (goal_pose_it != transformed_plan.poses.begin()) {
436-
// Find the point on the line segment between the two poses
437-
// that is exactly the lookahead distance away from the robot pose (the origin)
438-
// This can be found with a closed form for the intersection of a segment and a circle
439-
// Because of the way we did the std::find_if, prev_pose is guaranteed to be inside the circle,
440-
// and goal_pose is guaranteed to be outside the circle.
441-
auto prev_pose_it = std::prev(goal_pose_it);
442-
auto point = circleSegmentIntersection(
443-
prev_pose_it->pose.position,
444-
goal_pose_it->pose.position, lookahead_dist);
445-
geometry_msgs::msg::PoseStamped pose;
446-
pose.header.frame_id = prev_pose_it->header.frame_id;
447-
pose.header.stamp = goal_pose_it->header.stamp;
448-
pose.pose.position = point;
449-
return pose;
450-
}
451-
452-
return *goal_pose_it;
453-
}
454-
455362
void RegulatedPurePursuitController::applyConstraints(
456363
const double & curvature, const geometry_msgs::msg::Twist & /*curr_speed*/,
457364
const double & pose_cost, const nav_msgs::msg::Path & path, double & linear_vel, double & sign)

0 commit comments

Comments
 (0)