Skip to content

Commit ed0d59f

Browse files
authored
[Graceful Controller] Fix Incorrect Motion Target Heading output by controller (#5530)
* Fix graceful controller lookahead bug Signed-off-by: Sakshay Mahna <sakshum19@gmail.com> * Shorten logic with goal_pose Signed-off-by: Sakshay Mahna <sakshum19@gmail.com> * Add Linear Interpolation Fix Signed-off-by: Sakshay Mahna <sakshum19@gmail.com> * Update const double and comments Signed-off-by: Sakshay Mahna <sakshum19@gmail.com> --------- Signed-off-by: Sakshay Mahna <sakshum19@gmail.com>
1 parent 14660b4 commit ed0d59f

File tree

3 files changed

+186
-27
lines changed

3 files changed

+186
-27
lines changed

nav2_util/include/nav2_util/controller_utils.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ geometry_msgs::msg::Point circleSegmentIntersection(
3737
const geometry_msgs::msg::Point & p2,
3838
double r);
3939

40+
/**
41+
* @brief Find the linear interpolation between two points
42+
* at a given distance starting from first endpoint.
43+
* @param p1 first endpoint of line segment
44+
* @param p2 second endpoint of line segment
45+
* @param target_dist interpolation distance from first endpoint of line segment
46+
* @return point of intersection
47+
*/
48+
geometry_msgs::msg::Point linearInterpolation(
49+
const geometry_msgs::msg::Point & p1,
50+
const geometry_msgs::msg::Point & p2,
51+
const double target_dist);
52+
4053
/**
4154
* @brief Get lookahead point
4255
* @param lookahead_dist Optimal lookahead distance

nav2_util/src/controller_utils.cpp

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ geometry_msgs::msg::Point circleSegmentIntersection(
5454
return p;
5555
}
5656

57+
geometry_msgs::msg::Point linearInterpolation(
58+
const geometry_msgs::msg::Point & p1,
59+
const geometry_msgs::msg::Point & p2,
60+
const double target_dist)
61+
{
62+
geometry_msgs::msg::Point result;
63+
64+
double dx = p2.x - p1.x;
65+
double dy = p2.y - p1.y;
66+
double d_dist = std::hypot(dx, dy);
67+
68+
double target_ratio = target_dist / d_dist;
69+
70+
result.x = p1.x + target_ratio * dx;
71+
result.y = p1.y + target_ratio * dy;
72+
return result;
73+
}
74+
5775
geometry_msgs::msg::PoseStamped getLookAheadPoint(
5876
double & lookahead_dist,
5977
const nav_msgs::msg::Path & transformed_plan,
@@ -63,21 +81,25 @@ geometry_msgs::msg::PoseStamped getLookAheadPoint(
6381
// Using distance along the path
6482
const auto & poses = transformed_plan.poses;
6583
auto goal_pose_it = poses.begin();
66-
double d = 0.0;
84+
double path_dist = 0.0;
85+
double interpolation_dist = 0.0;
6786

6887
bool pose_found = false;
6988
for (size_t i = 1; i < poses.size(); i++) {
7089
const auto & prev_pose = poses[i - 1].pose.position;
7190
const auto & curr_pose = poses[i].pose.position;
7291

73-
d += std::hypot(curr_pose.x - prev_pose.x, curr_pose.y - prev_pose.y);
74-
if (d >= lookahead_dist) {
92+
const double d = std::hypot(curr_pose.x - prev_pose.x, curr_pose.y - prev_pose.y);
93+
if (path_dist + d >= lookahead_dist) {
7594
goal_pose_it = poses.begin() + i;
7695
pose_found = true;
7796
break;
7897
}
98+
99+
path_dist += d;
79100
}
80101

102+
interpolation_dist = lookahead_dist - path_dist;
81103
if (!pose_found) {
82104
goal_pose_it = poses.end();
83105
}
@@ -98,30 +120,30 @@ geometry_msgs::msg::PoseStamped getLookAheadPoint(
98120
projected_position.x += cos(end_path_orientation) * lookahead_dist;
99121
projected_position.y += sin(end_path_orientation) * lookahead_dist;
100122

101-
// Use the circle intersection to find the position at the correct look
123+
// Use the linear interpolation to find the position at the correct look
102124
// ahead distance
103-
const auto interpolated_position = circleSegmentIntersection(
104-
last_pose_it->pose.position, projected_position, lookahead_dist);
125+
const auto interpolated_position = linearInterpolation(
126+
last_pose_it->pose.position, projected_position, interpolation_dist);
105127

106128
geometry_msgs::msg::PoseStamped interpolated_pose;
107129
interpolated_pose.header = last_pose_it->header;
108130
interpolated_pose.pose.position = interpolated_position;
109131

110132
return interpolated_pose;
111133
} else {
112-
lookahead_dist = d; // Updating lookahead distance since using the final point
134+
lookahead_dist = path_dist; // Updating lookahead distance since using the final point
113135
goal_pose_it = std::prev(transformed_plan.poses.end());
114136
}
115137
} else if (goal_pose_it != transformed_plan.poses.begin()) {
116-
// Find the point on the line segment between the two poses
138+
// Find the point on the robot path
117139
// that is exactly the lookahead distance away from the robot pose (the origin)
118-
// This can be found with a closed form for the intersection of a segment and a circle
119-
// Because of the way we did the std::find_if, prev_pose is guaranteed to be inside the circle,
120-
// and goal_pose is guaranteed to be outside the circle.
140+
// This can be found with a linear interpolation between the prev_pose and
141+
// the goal_pose, moving interpolation_dist starting from prev_pose in the
142+
// direction of goal_pose.
121143
auto prev_pose_it = std::prev(goal_pose_it);
122-
auto point = circleSegmentIntersection(
144+
auto point = linearInterpolation(
123145
prev_pose_it->pose.position,
124-
goal_pose_it->pose.position, lookahead_dist);
146+
goal_pose_it->pose.position, interpolation_dist);
125147

126148
// Calculate orientation towards interpolated position
127149
// Convert yaw to quaternion

nav2_util/test/test_controller_utils.cpp

Lines changed: 138 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,130 @@ INSTANTIATE_TEST_SUITE_P(
142142
}
143143
));
144144

145+
using linearInterpolationParam = std::tuple<
146+
std::pair<double, double>,
147+
std::pair<double, double>,
148+
double,
149+
std::pair<double, double>
150+
>;
151+
152+
class linearInterpolationTest
153+
: public ::testing::TestWithParam<linearInterpolationParam>
154+
{};
155+
156+
TEST_P(linearInterpolationTest, linearInterpolation)
157+
{
158+
auto pair1 = std::get<0>(GetParam());
159+
auto pair2 = std::get<1>(GetParam());
160+
auto r = std::get<2>(GetParam());
161+
auto expected_pair = std::get<3>(GetParam());
162+
auto pair_to_point = [](std::pair<double, double> p) -> geometry_msgs::msg::Point {
163+
geometry_msgs::msg::Point point;
164+
point.x = p.first;
165+
point.y = p.second;
166+
point.z = 0.0;
167+
return point;
168+
};
169+
auto p1 = pair_to_point(pair1);
170+
auto p2 = pair_to_point(pair2);
171+
auto actual = nav2_util::linearInterpolation(p1, p2, r);
172+
auto expected_point = pair_to_point(expected_pair);
173+
EXPECT_DOUBLE_EQ(actual.x, expected_point.x);
174+
EXPECT_DOUBLE_EQ(actual.y, expected_point.y);
175+
// Expect that the intersection point is actually r away from the origin
176+
EXPECT_DOUBLE_EQ(r, std::hypot(p1.x - actual.x, p1.y - actual.y));
177+
}
178+
179+
INSTANTIATE_TEST_SUITE_P(
180+
InterpolationTest,
181+
linearInterpolationTest,
182+
testing::Values(
183+
// Origin to the positive X axis
184+
linearInterpolationParam{
185+
{0.0, 0.0},
186+
{2.0, 0.0},
187+
1.0,
188+
{1.0, 0.0}
189+
},
190+
// Origin to the negative X axis
191+
linearInterpolationParam{
192+
{0.0, 0.0},
193+
{-2.0, 0.0},
194+
1.0,
195+
{-1.0, 0.0}
196+
},
197+
// Origin to the positive Y axis
198+
linearInterpolationParam{
199+
{0.0, 0.0},
200+
{0.0, 2.0},
201+
1.0,
202+
{0.0, 1.0}
203+
},
204+
// Origin to the negative Y axis
205+
linearInterpolationParam{
206+
{0.0, 0.0},
207+
{0.0, -2.0},
208+
1.0,
209+
{0.0, -1.0}
210+
},
211+
// non-origin to the X axis with non-unit circle, with the second point inside
212+
linearInterpolationParam{
213+
{4.0, 0.0},
214+
{-1.0, 0.0},
215+
2.0,
216+
{2.0, 0.0}
217+
},
218+
// non-origin to the Y axis with non-unit circle, with the second point inside
219+
linearInterpolationParam{
220+
{0.0, 4.0},
221+
{0.0, -0.5},
222+
2.0,
223+
{0.0, 2.0}
224+
},
225+
// origin to the positive X axis, on the circle
226+
linearInterpolationParam{
227+
{2.0, 0.0},
228+
{0.0, 0.0},
229+
2.0,
230+
{0.0, 0.0}
231+
},
232+
// origin to the positive Y axis, on the circle
233+
linearInterpolationParam{
234+
{0.0, 0.0},
235+
{0.0, 2.0},
236+
2.0,
237+
{0.0, 2.0}
238+
},
239+
// origin to the upper-right quadrant (3-4-5 triangle)
240+
linearInterpolationParam{
241+
{0.0, 0.0},
242+
{6.0, 8.0},
243+
5.0,
244+
{3.0, 4.0}
245+
},
246+
// origin to the lower-left quadrant (3-4-5 triangle)
247+
linearInterpolationParam{
248+
{0.0, 0.0},
249+
{-6.0, -8.0},
250+
5.0,
251+
{-3.0, -4.0}
252+
},
253+
// origin to the upper-left quadrant (3-4-5 triangle)
254+
linearInterpolationParam{
255+
{0.0, 0.0},
256+
{-6.0, 8.0},
257+
5.0,
258+
{-3.0, 4.0}
259+
},
260+
// origin to the lower-right quadrant (3-4-5 triangle)
261+
linearInterpolationParam{
262+
{0.0, 0.0},
263+
{6.0, -8.0},
264+
5.0,
265+
{3.0, -4.0}
266+
}
267+
));
268+
145269
TEST(InterpolationUtils, lookaheadInterpolation)
146270
{
147271
// test Lookahead Point Interpolation
@@ -185,7 +309,7 @@ TEST(InterpolationUtils, lookaheadExtrapolation)
185309
path.poses[0].pose.position.x = 2.0;
186310
path.poses[1].pose.position.x = 3.0;
187311
pt = nav2_util::getLookAheadPoint(lookahead_dist, path, true);
188-
EXPECT_NEAR(pt.pose.position.x, 10.0, EPSILON);
312+
EXPECT_NEAR(pt.pose.position.x, 12.0, EPSILON);
189313
EXPECT_NEAR(pt.pose.position.y, 0.0, EPSILON);
190314

191315
// 2 poses at 45°
@@ -196,8 +320,8 @@ TEST(InterpolationUtils, lookaheadExtrapolation)
196320
path.poses[1].pose.position.x = 3.0;
197321
path.poses[1].pose.position.y = 3.0;
198322
pt = nav2_util::getLookAheadPoint(lookahead_dist, path, true);
199-
EXPECT_NEAR(pt.pose.position.x, cos(45.0 * M_PI / 180) * 10.0, EPSILON);
200-
EXPECT_NEAR(pt.pose.position.y, sin(45.0 * M_PI / 180) * 10.0, EPSILON);
323+
EXPECT_NEAR(pt.pose.position.x, 2.0 + cos(45.0 * M_PI / 180) * 10.0, EPSILON);
324+
EXPECT_NEAR(pt.pose.position.y, 2.0 + sin(45.0 * M_PI / 180) * 10.0, EPSILON);
201325

202326
// 2 poses at 90°
203327
path.poses.clear();
@@ -207,8 +331,8 @@ TEST(InterpolationUtils, lookaheadExtrapolation)
207331
path.poses[1].pose.position.x = 0.0;
208332
path.poses[1].pose.position.y = 3.0;
209333
pt = nav2_util::getLookAheadPoint(lookahead_dist, path, true);
210-
EXPECT_NEAR(pt.pose.position.x, cos(90.0 * M_PI / 180) * 10.0, EPSILON);
211-
EXPECT_NEAR(pt.pose.position.y, sin(90.0 * M_PI / 180) * 10.0, EPSILON);
334+
EXPECT_NEAR(pt.pose.position.x, 0.0 + cos(90.0 * M_PI / 180) * 10.0, EPSILON);
335+
EXPECT_NEAR(pt.pose.position.y, 2.0 + sin(90.0 * M_PI / 180) * 10.0, EPSILON);
212336

213337
// 2 poses at 135°
214338
path.poses.clear();
@@ -218,16 +342,16 @@ TEST(InterpolationUtils, lookaheadExtrapolation)
218342
path.poses[1].pose.position.x = -3.0;
219343
path.poses[1].pose.position.y = 3.0;
220344
pt = nav2_util::getLookAheadPoint(lookahead_dist, path, true);
221-
EXPECT_NEAR(pt.pose.position.x, cos(135.0 * M_PI / 180) * 10.0, EPSILON);
222-
EXPECT_NEAR(pt.pose.position.y, sin(135.0 * M_PI / 180) * 10.0, EPSILON);
345+
EXPECT_NEAR(pt.pose.position.x, -2.0 + cos(135.0 * M_PI / 180) * 10.0, EPSILON);
346+
EXPECT_NEAR(pt.pose.position.y, 2.0 + sin(135.0 * M_PI / 180) * 10.0, EPSILON);
223347

224348
// 2 poses back
225349
path.poses.clear();
226350
path.poses.resize(2);
227351
path.poses[0].pose.position.x = -2.0;
228352
path.poses[1].pose.position.x = -3.0;
229353
pt = nav2_util::getLookAheadPoint(lookahead_dist, path, true);
230-
EXPECT_NEAR(pt.pose.position.x, -10.0, EPSILON);
354+
EXPECT_NEAR(pt.pose.position.x, -12.0, EPSILON);
231355
EXPECT_NEAR(pt.pose.position.y, 0.0, EPSILON);
232356

233357
// 2 poses at -135°
@@ -238,8 +362,8 @@ TEST(InterpolationUtils, lookaheadExtrapolation)
238362
path.poses[1].pose.position.x = -3.0;
239363
path.poses[1].pose.position.y = -3.0;
240364
pt = nav2_util::getLookAheadPoint(lookahead_dist, path, true);
241-
EXPECT_NEAR(pt.pose.position.x, cos(-135.0 * M_PI / 180) * 10.0, EPSILON);
242-
EXPECT_NEAR(pt.pose.position.y, sin(-135.0 * M_PI / 180) * 10.0, EPSILON);
365+
EXPECT_NEAR(pt.pose.position.x, -2.0 + cos(-135.0 * M_PI / 180) * 10.0, EPSILON);
366+
EXPECT_NEAR(pt.pose.position.y, -2.0 + sin(-135.0 * M_PI / 180) * 10.0, EPSILON);
243367

244368
// 2 poses at -90°
245369
path.poses.clear();
@@ -249,8 +373,8 @@ TEST(InterpolationUtils, lookaheadExtrapolation)
249373
path.poses[1].pose.position.x = 0.0;
250374
path.poses[1].pose.position.y = -3.0;
251375
pt = nav2_util::getLookAheadPoint(lookahead_dist, path, true);
252-
EXPECT_NEAR(pt.pose.position.x, cos(-90.0 * M_PI / 180) * 10.0, EPSILON);
253-
EXPECT_NEAR(pt.pose.position.y, sin(-90.0 * M_PI / 180) * 10.0, EPSILON);
376+
EXPECT_NEAR(pt.pose.position.x, 0.0 + cos(-90.0 * M_PI / 180) * 10.0, EPSILON);
377+
EXPECT_NEAR(pt.pose.position.y, -2.0 + sin(-90.0 * M_PI / 180) * 10.0, EPSILON);
254378

255379
// 2 poses at -45°
256380
path.poses.clear();
@@ -260,6 +384,6 @@ TEST(InterpolationUtils, lookaheadExtrapolation)
260384
path.poses[1].pose.position.x = 3.0;
261385
path.poses[1].pose.position.y = -3.0;
262386
pt = nav2_util::getLookAheadPoint(lookahead_dist, path, true);
263-
EXPECT_NEAR(pt.pose.position.x, cos(-45.0 * M_PI / 180) * 10.0, EPSILON);
264-
EXPECT_NEAR(pt.pose.position.y, sin(-45.0 * M_PI / 180) * 10.0, EPSILON);
387+
EXPECT_NEAR(pt.pose.position.x, 2.0 + cos(-45.0 * M_PI / 180) * 10.0, EPSILON);
388+
EXPECT_NEAR(pt.pose.position.y, -2.0 + sin(-45.0 * M_PI / 180) * 10.0, EPSILON);
265389
}

0 commit comments

Comments
 (0)