Skip to content

Commit ff64230

Browse files
committed
Improve MPPI tracking precision and make sensor usage flexible
* Implement local path cropping for better lookahead and goal selection. * Fix cost penalties by evaluating exact distance to the reference path. * Remove internal velocity filters to prevent double filtering and fix sluggish response. * Tune angular proportional gain and statistical variance to allow sharp turns. * Enable continuous controller operation by skipping obstacle avoidance if spatial perception data is missing.
1 parent 252576c commit ff64230

2 files changed

Lines changed: 90 additions & 66 deletions

File tree

controllers/easynav_mppi_controller/src/easynav_mppi_controller/MPPIController.cpp

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,28 @@ MPPIController::update_rt(NavState & nav_state)
182182
}
183183

184184
const auto & pose = nav_state.get<nav_msgs::msg::Odometry>("robot_pose").pose.pose;
185-
const auto & perceptions = nav_state.get_no_group<PointPerception>();
185+
186186
const auto & tf_info = RTTFBuffer::getInstance()->get_tf_info();
187-
const auto & filtered = PointPerceptionsOpsView(perceptions)
188-
.filter({-1.0, -1.0, -1.0}, {1.0, 1.0, 1.0})
189-
.fuse(tf_info.map_frame)
190-
.filter({NAN, NAN, 0.1}, {NAN, NAN, NAN})
191-
.collapse({NAN, NAN, 0.1})
192-
.downsample(0.1)
193-
.as_points();
194-
195-
if (filtered.empty()) {
187+
188+
pcl::PointCloud<pcl::PointXYZ> filtered;
189+
190+
if (!nav_state.has("points")) {
196191
RCLCPP_WARN(get_node()->get_logger(),
197-
"No valid points available for MPPI optimization, using the path only.");
192+
"No point sensor configured (\"points\" not in nav_state). Running MPPI without obstacle avoidance.");
193+
} else {
194+
const auto & perceptions = nav_state.get_no_group<PointPerception>();
195+
filtered = PointPerceptionsOpsView(perceptions)
196+
.filter({-1.0, -1.0, -1.0}, {1.0, 1.0, 1.0})
197+
.fuse(tf_info.map_frame)
198+
.filter({NAN, NAN, 0.1}, {NAN, NAN, NAN})
199+
.collapse({NAN, NAN, 0.1})
200+
.downsample(0.1)
201+
.as_points();
202+
203+
if (filtered.empty()) {
204+
RCLCPP_WARN(get_node()->get_logger(),
205+
"No valid points available for MPPI optimization, using the path only.");
206+
}
198207
}
199208

200209
// Compute the control using MPPI with points

controllers/easynav_mppi_controller/src/easynav_mppi_controller/MPPIOptimizer.cpp

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,36 +23,17 @@ MPPIOptimizer::MPPIOptimizer(
2323
std::vector<std::pair<double, double>>
2424
MPPIOptimizer::simulate_trajectory(
2525
double x, double y, double yaw, double v, double w,
26-
const nav_msgs::msg::Path & path, int steps)
26+
const nav_msgs::msg::Path & /*path*/, int steps)
2727
{
2828
size_t horizon_steps = static_cast<size_t>(steps);
2929
std::vector<std::pair<double, double>> trajectory;
3030
trajectory.reserve(horizon_steps);
3131

32-
bool has_path = !path.poses.empty();
33-
3432
for (size_t i = 0; i < horizon_steps; ++i) {
35-
// MPPI noise sampling
36-
double v_sample = v + normal_(rng_);
37-
double w_sample = w + normal_(rng_);
38-
3933
// Differential drive kinematics
40-
x += v_sample * std::cos(yaw) * dt_;
41-
y += v_sample * std::sin(yaw) * dt_;
42-
yaw += w_sample * dt_;
43-
44-
if (has_path) {
45-
// Calculate the index in the path based on the current step
46-
size_t path_idx = std::min(static_cast<size_t>((i * path.poses.size()) / horizon_steps),
47-
path.poses.size() - 1);
48-
double target_x = path.poses[path_idx].pose.position.x;
49-
double target_y = path.poses[path_idx].pose.position.y;
50-
51-
// Smooth motion towards the target
52-
double alpha = 0.2;
53-
x += alpha * (target_x - x);
54-
y += alpha * (target_y - y);
55-
}
34+
x += v * std::cos(yaw) * dt_;
35+
y += v * std::sin(yaw) * dt_;
36+
yaw += w * dt_;
5637

5738
trajectory.emplace_back(x, y);
5839
}
@@ -87,21 +68,30 @@ double MPPIOptimizer::compute_cost(
8768
double cost = 0.0;
8869
size_t path_size = path.poses.size();
8970

71+
if (path_size == 0) return std::numeric_limits<double>::max();
72+
9073
// --- Path-Tracking Penalties ---
9174
for (size_t i = 0; i < trajectory.size(); ++i) {
9275
const auto &[x, y] = trajectory[i];
9376

94-
// Cost is distributed along the trajectory
95-
double path_alpha = static_cast<double>(i) / trajectory.size();
96-
size_t idx = std::min(static_cast<size_t>(path_alpha * path_size), path_size - 1);
97-
98-
double dx = path.poses[idx].pose.position.x - x;
99-
double dy = path.poses[idx].pose.position.y - y;
100-
double dist = std::hypot(dx, dy);
77+
// Find closest point on path (path is already cropped to local window)
78+
double min_dist2 = std::numeric_limits<double>::max();
79+
size_t closest_idx = 0;
80+
81+
for (size_t j = 0; j < path_size; ++j) {
82+
double dx = path.poses[j].pose.position.x - x;
83+
double dy = path.poses[j].pose.position.y - y;
84+
double dist2 = dx*dx + dy*dy;
85+
if (dist2 < min_dist2) {
86+
min_dist2 = dist2;
87+
closest_idx = j;
88+
}
89+
}
90+
double dist = std::sqrt(min_dist2);
10191

10292
// Heading error is calculated based on the initial yaw
103-
double heading_penalty = heading_error(initial_yaw, path.poses[idx].pose.position.x,
104-
path.poses[idx].pose.position.y, x, y);
93+
double heading_penalty = heading_error(initial_yaw, path.poses[closest_idx].pose.position.x,
94+
path.poses[closest_idx].pose.position.y, x, y);
10595

10696
// FOV penalty: discourage trajectories outside robot's view
10797
double angle_to_goal = heading_error(initial_yaw, trajectory.back().first,
@@ -187,10 +177,39 @@ MPPIResult MPPIOptimizer::compute_control(
187177
double y = current_pose.position.y;
188178
double yaw = tf2::getYaw(current_pose.orientation);
189179

190-
// Select goal pose (within horizon, or last path point)
191-
int last_pose = std::min(static_cast<size_t>(horizon_steps_), path.poses.size() - 1);
192-
int sim_steps = std::max(1, last_pose);
193-
const auto & path_pose = path.poses[last_pose].pose;
180+
// Find closest point on global path
181+
size_t closest_idx = 0;
182+
double min_dist_to_robot = std::numeric_limits<double>::max();
183+
for (size_t i = 0; i < path.poses.size(); ++i) {
184+
double dist = std::hypot(path.poses[i].pose.position.x - x, path.poses[i].pose.position.y - y);
185+
if (dist < min_dist_to_robot) {
186+
min_dist_to_robot = dist;
187+
closest_idx = i;
188+
}
189+
}
190+
191+
// Crop the path up to a reasonable lookahead distance
192+
nav_msgs::msg::Path local_path;
193+
local_path.header = path.header;
194+
195+
double max_lookahead_dist = max_lin_vel_ * horizon_steps_ * dt_ * 1.5;
196+
if (max_lookahead_dist < 2.0) max_lookahead_dist = 2.0;
197+
198+
double path_accum_dist = 0.0;
199+
local_path.poses.push_back(path.poses[closest_idx]);
200+
for (size_t i = closest_idx + 1; i < path.poses.size(); ++i) {
201+
local_path.poses.push_back(path.poses[i]);
202+
double dx = path.poses[i].pose.position.x - path.poses[i-1].pose.position.x;
203+
double dy = path.poses[i].pose.position.y - path.poses[i-1].pose.position.y;
204+
path_accum_dist += std::hypot(dx, dy);
205+
if (path_accum_dist > max_lookahead_dist) {
206+
break;
207+
}
208+
}
209+
210+
// Select local goal pose from the cropped path
211+
int sim_steps = horizon_steps_;
212+
const auto & path_pose = local_path.poses.back().pose;
194213
double px = path_pose.position.x;
195214
double py = path_pose.position.y;
196215
double dist_to_goal = std::hypot(px - x, py - y);
@@ -222,19 +241,22 @@ MPPIResult MPPIOptimizer::compute_control(
222241
double base_v = max_lin_vel_ * std::min(dist_to_goal, 1.0) * v_scale;
223242
base_v = std::clamp(base_v, 0.0, max_lin_vel_);
224243

225-
// Angular velocity is proportional to the heading error: turn faster if more misaligned
226-
double w_scale = std::min(1.0, 2.0 * angle_mag / M_PI);
227-
double base_w = std::clamp(w_scale * angle_error, -max_ang_vel_, max_ang_vel_);
244+
// Apply a stronger proportional gain for angular tracking to allow sharp turns
245+
double base_w = std::clamp(2.5 * angle_error, -max_ang_vel_, max_ang_vel_);
246+
247+
// Use proper spread variance to allow MPPI to explore turns
248+
std::normal_distribution<double> v_dist(0.0, max_lin_vel_ * 0.2);
249+
std::normal_distribution<double> w_dist(0.0, max_ang_vel_ * 0.4);
228250

229251
// Generate candidate trajectories
230252
for (int i = 0; i < num_samples_; ++i) {
231253
// Sample noise for velocities with Gaussian distribution and clamp
232-
double v = std::clamp(base_v + v_noise_(rng_), -max_lin_vel_, max_lin_vel_);
233-
double w = std::clamp(base_w + w_noise_(rng_), -max_ang_vel_, max_ang_vel_);
254+
double v = std::clamp(base_v + v_dist(rng_), -max_lin_vel_, max_lin_vel_);
255+
double w = std::clamp(base_w + w_dist(rng_), -max_ang_vel_, max_ang_vel_);
234256

235257
// Simulate trajectory and compute its cost
236-
auto traj = simulate_trajectory(x, y, yaw, v, w, path, sim_steps);
237-
double cost = compute_cost(traj, path, v, w, yaw, points);
258+
auto traj = simulate_trajectory(x, y, yaw, v, w, local_path, sim_steps);
259+
double cost = compute_cost(traj, local_path, v, w, yaw, points);
238260
all_trajs.push_back(traj);
239261

240262
// Store the sample with its cost
@@ -273,18 +295,11 @@ MPPIResult MPPIOptimizer::compute_control(
273295
vrot += sample.w * sample.cost / denom;
274296
}
275297

276-
// Calculate the maximum change in velocities based on acceleration limits
277-
double max_v_delta = max_lin_acc_ * dt_;
278-
double max_w_delta = max_ang_acc_ * dt_;
279-
280-
// Limit velocity changes (slew rate)
281-
vlin = last_v_ + std::clamp(vlin - last_v_, -max_v_delta, max_v_delta);
282-
vrot = last_w_ + std::clamp(vrot - last_w_, -max_w_delta, max_w_delta);
283-
284-
// Smooth velocities
285-
double alpha_smooth = 0.2; // lower is more smooth
286-
last_v_ = alpha_smooth * vlin + (1.0 - alpha_smooth) * last_v_;
287-
last_w_ = alpha_smooth * vrot + (1.0 - alpha_smooth) * last_w_;
298+
// We remove the internal slew rate and low-pass filters here because MPPIController
299+
// already rigorously applies kinematic constraints (accelerations). Double filtering
300+
// causes severe sluggishness and path divergence.
301+
last_v_ = vlin;
302+
last_w_ = vrot;
288303

289304
// Return final control command and trajectories
290305
return MPPIResult{last_v_, last_w_, all_trajs, best_traj};

0 commit comments

Comments
 (0)