Skip to content

Commit e1a97fc

Browse files
committed
util functions shaped, styled
Signed-off-by: silanus23 <berkantali23@outlook.com>
1 parent 36106a1 commit e1a97fc

3 files changed

Lines changed: 100 additions & 96 deletions

File tree

nav2_mppi_controller/include/nav2_mppi_controller/critics/path_align_critic.hpp

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,45 @@ class PathAlignCritic : public CriticFunction
7272
float computeMinDistanceToPath(float px, float py, Eigen::Index & closest_seg_idx);
7373

7474
/**
75-
* @brief Compute squared distance from point to a specific path segment
76-
* @param px Point x coordinate
77-
* @param py Point y coordinate
78-
* @param seg_idx Segment index
79-
* @return Squared distance to segment
75+
* @brief Compute squared distance from a point to a line segment
76+
*
77+
* Projects the point onto the segment (clamped to segment bounds) and returns
78+
* the squared distance. Uses precomputed segment geometry for efficiency.
79+
*
80+
* @param px X-coordinate of the query point
81+
* @param py Y-coordinate of the query point
82+
* @param seg_idx Index of the path segment
83+
* @return Squared distance from point to segment
84+
*
85+
* @note Returns squared distance to avoid sqrt() computation
86+
* @note Handles degenerate (zero-length) segments
8087
*/
81-
float distSqToSegment(float px, float py, Eigen::Index seg_idx);
88+
inline float distSqToSegment(float px, float py, Eigen::Index seg_idx)
89+
{
90+
const float x1 = path_x_cache_(seg_idx);
91+
const float y1 = path_y_cache_(seg_idx);
92+
const float dx = segment_dx_(seg_idx);
93+
const float dy = segment_dy_(seg_idx);
94+
const float inv_len_sq = segment_inv_len_sq_(seg_idx);
95+
96+
if (inv_len_sq < 1e-6f) {
97+
// Degenerate segment, return distance to point
98+
const float dpx = px - x1;
99+
const float dpy = py - y1;
100+
return dpx * dpx + dpy * dpy;
101+
}
102+
103+
// Project point onto segment, clamped to [0,1]
104+
const float t = std::max(0.0f, std::min(1.0f, ((px - x1) * dx + (py - y1) * dy) * inv_len_sq));
105+
106+
const float closest_x = x1 + t * dx;
107+
const float closest_y = y1 + t * dy;
108+
109+
const float dist_x = px - closest_x;
110+
const float dist_y = py - closest_y;
111+
112+
return dist_x * dist_x + dist_y * dist_y;
113+
}
82114

83115
size_t offset_from_furthest_{0};
84116
int trajectory_point_step_{0};
@@ -100,11 +132,12 @@ class PathAlignCritic : public CriticFunction
100132
Eigen::ArrayXf segment_dx_;
101133
Eigen::ArrayXf segment_dy_;
102134
Eigen::ArrayXf segment_len_sq_;
135+
Eigen::ArrayXf segment_inv_len_sq_;
103136
Eigen::ArrayXf segment_lengths_;
104137
Eigen::ArrayXf cumulative_distances_;
105138
Eigen::Array<Eigen::Index, Eigen::Dynamic, 1> closest_indices_;
106139
};
107140

108141
} // namespace mppi::critics
109142

110-
#endif // NAV2_MPPI_CONTROLLER__CRITICS__PATH_ALIGN_CRITIC_HPP_
143+
#endif // NAV2_MPPI_CONTROLLER__CRITICS__PATH_ALIGN_CRITIC_HPP_

nav2_mppi_controller/include/nav2_mppi_controller/tools/utils.hpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -602,29 +602,28 @@ inline unsigned int findClosestPathPtBinary(
602602
{
603603
if (size == 0) {return 0u;}
604604
if (size == 1) {return 0u;}
605-
605+
606606
// Use binary search since cumulative_distances is monotonically increasing
607607
// std::lower_bound returns iterator to first element >= dist
608608
const float * begin = vec;
609609
const float * end = vec + size;
610610
const float * it = std::lower_bound(begin, end, dist);
611-
612-
// Handle edge cases
611+
613612
if (it == end) {
614613
// dist is beyond last element, return last index
615614
return static_cast<unsigned int>(size - 1);
616615
}
617-
616+
618617
if (it == begin) {
619618
// dist is before or at first element
620619
return 0u;
621620
}
622-
621+
623622
// Check which neighbor is closer: the element found or the one before it
624623
const unsigned int idx = static_cast<unsigned int>(it - begin);
625624
const float dist_curr = *it;
626625
const float dist_prev = *(it - 1);
627-
626+
628627
if (dist - dist_prev < dist_curr - dist) {
629628
return idx - 1;
630629
}

nav2_mppi_controller/src/critics/path_align_critic.cpp

Lines changed: 55 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ void PathAlignCritic::initialize()
2929
getParam(threshold_to_consider_, "threshold_to_consider", 0.5f);
3030
getParam(use_path_orientations_, "use_path_orientations", false);
3131
getParam(use_geometric_alignment_, "use_geometric_alignment", false);
32-
getParam(search_window_, "search_window", 2.0);
32+
getParam(search_window_, "search_window", 0.5);
3333
getParam(score_arc_length_, "score_arc_length", true);
34-
getParam(lookahead_distance_, "lookahead_distance", 5.0f);
34+
getParam(lookahead_distance_, "lookahead_distance", 1.0f);
3535

3636
if (!score_arc_length_ && !use_geometric_alignment_) {
3737
RCLCPP_WARN(logger_,
@@ -110,63 +110,65 @@ void PathAlignCritic::scoreArcLength(CriticData & data, std::vector<bool> & path
110110
unsigned int path_pt = 0u;
111111
float traj_integrated_distance = 0.0f;
112112

113-
const Eigen::Index traj_length = data.trajectories.x.cols();
114-
const int effective_stride = std::max(1, std::min(trajectory_point_step_,
115-
static_cast<int>(traj_length)));
116-
const int num_sample_points = (traj_length + effective_stride - 1) / effective_stride;
113+
int strided_traj_rows = data.trajectories.x.rows();
114+
int strided_traj_cols = floor((data.trajectories.x.cols() - 1) / trajectory_point_step_) + 1;
115+
int outer_stride = strided_traj_rows * trajectory_point_step_;
116+
// Get strided trajectory information
117+
const auto T_x = Eigen::Map<const Eigen::ArrayXXf, 0,
118+
Eigen::Stride<-1, -1>>(
119+
data.trajectories.x.data(),
120+
strided_traj_rows, strided_traj_cols, Eigen::Stride<-1, -1>(outer_stride, 1));
121+
const auto T_y = Eigen::Map<const Eigen::ArrayXXf, 0,
122+
Eigen::Stride<-1, -1>>(
123+
data.trajectories.y.data(),
124+
strided_traj_rows, strided_traj_cols, Eigen::Stride<-1, -1>(outer_stride, 1));
125+
const auto T_yaw = Eigen::Map<const Eigen::ArrayXXf, 0,
126+
Eigen::Stride<-1, -1>>(
127+
data.trajectories.yaws.data(), strided_traj_rows, strided_traj_cols,
128+
Eigen::Stride<-1, -1>(outer_stride, 1));
129+
const auto traj_sampled_size = T_x.cols();
117130

118-
// Score each trajectory in the batch
119131
for (size_t t = 0; t < batch_size; ++t) {
120132
summed_path_dist = 0.0f;
121133
num_samples = 0u;
122134
traj_integrated_distance = 0.0f;
123135
path_pt = 0u;
124-
125-
// Start from first trajectory point (close to current pose, one time step ahead)
126-
float prev_x = data.trajectories.x(t, 0);
127-
float prev_y = data.trajectories.y(t, 0);
128-
129-
// Sample trajectory points at regular intervals
130-
for (int sample_idx = 1; sample_idx < num_sample_points; ++sample_idx) {
131-
const Eigen::Index traj_col = sample_idx * effective_stride;
132-
if (traj_col >= traj_length) {break;}
133-
134-
const float curr_x = data.trajectories.x(t, traj_col);
135-
const float curr_y = data.trajectories.y(t, traj_col);
136-
137-
// Accumulate distance traveled along trajectory
138-
dx = curr_x - prev_x;
139-
dy = curr_y - prev_y;
136+
float Tx_m1 = T_x(t, 0);
137+
float Ty_m1 = T_y(t, 0);
138+
for (int p = 1; p < traj_sampled_size; p++) {
139+
const float Tx = T_x(t, p);
140+
const float Ty = T_y(t, p);
141+
dx = Tx - Tx_m1;
142+
dy = Ty - Ty_m1;
143+
Tx_m1 = Tx;
144+
Ty_m1 = Ty;
140145
traj_integrated_distance += sqrtf(dx * dx + dy * dy);
141-
142-
// Find corresponding path point at same arc length distance
143146
path_pt = utils::findClosestPathPtBinary(
144147
cumulative_distances_.data(), path_segments_count,
145148
traj_integrated_distance);
146149

147-
// Calculate distance from trajectory point to corresponding path point
150+
// The nearest path point to align to needs to be not in collision, else
151+
// let the obstacle critic take over in this region due to dynamic obstacles
148152
if (path_pts_valid[path_pt]) {
149153
const auto & pose = path[path_pt];
150-
dx = pose.x - curr_x;
151-
dy = pose.y - curr_y;
154+
dx = pose.x - Tx;
155+
dy = pose.y - Ty;
152156
num_samples++;
153157
if (use_path_orientations_) {
154-
dyaw = angles::shortest_angular_distance(pose.theta, data.trajectories.yaws(t, traj_col));
158+
dyaw = angles::shortest_angular_distance(pose.theta, T_yaw(t, p));
155159
summed_path_dist += sqrtf(dx * dx + dy * dy + dyaw * dyaw);
156160
} else {
157161
summed_path_dist += sqrtf(dx * dx + dy * dy);
158162
}
159163
}
160-
161-
prev_x = curr_x;
162-
prev_y = curr_y;
163164
}
164-
165-
// Average distance across all sampled points
166-
cost(t) = (num_samples > 0u) ? summed_path_dist / static_cast<float>(num_samples) : 0.0f;
165+
if (num_samples > 0u) {
166+
cost(t) = summed_path_dist / static_cast<float>(num_samples);
167+
} else {
168+
cost(t) = 0.0f;
169+
}
167170
}
168171

169-
// Apply weight and power, then add to total costs
170172
if (power_ > 1u) {
171173
data.costs += (cost * weight_).pow(power_);
172174
} else {
@@ -200,19 +202,22 @@ void PathAlignCritic::scoreGeometric(CriticData & data, std::vector<bool> & path
200202
const float robot_y = data.state.pose.pose.position.y;
201203
const size_t path_segments_count = *data.furthest_reached_path_point;
202204
const Eigen::Index num_segments = static_cast<Eigen::Index>(path_size_cache_) - 1;
203-
205+
204206
Eigen::Index current_closest_seg = 0;
205207
float min_dist_sq = std::numeric_limits<float>::max();
206-
208+
207209
// Find initial hint by locating robot position on path
208-
for (Eigen::Index i = 0; i < num_segments && i < static_cast<Eigen::Index>(path_segments_count); ++i) {
210+
for (Eigen::Index i = 0;
211+
i < num_segments && i < static_cast<Eigen::Index>(path_segments_count);
212+
++i)
213+
{
209214
const float dist_sq = distSqToSegment(robot_x, robot_y, i);
210215
if (dist_sq < min_dist_sq) {
211216
min_dist_sq = dist_sq;
212217
current_closest_seg = i;
213218
}
214219
}
215-
220+
216221
closest_indices_.setConstant(current_closest_seg);
217222

218223
const int effective_stride = std::max(1, std::min(trajectory_point_step_,
@@ -221,13 +226,13 @@ void PathAlignCritic::scoreGeometric(CriticData & data, std::vector<bool> & path
221226
// Score each trajectory in the batch
222227
for (Eigen::Index traj_idx = 0; traj_idx < batch_size; ++traj_idx) {
223228
float accumulated_distance = 0.0f;
224-
229+
225230
// Start from first trajectory point
226231
float prev_x = traj_x(traj_idx, 0);
227232
float prev_y = traj_y(traj_idx, 0);
228-
233+
229234
int sample_idx = 0;
230-
235+
231236
// Sample trajectory points at regular intervals
232237
while (true) {
233238
const Eigen::Index traj_col = sample_idx * effective_stride;
@@ -241,7 +246,7 @@ void PathAlignCritic::scoreGeometric(CriticData & data, std::vector<bool> & path
241246
const float dx = px - prev_x;
242247
const float dy = py - prev_y;
243248
accumulated_distance += std::sqrt(dx * dx + dy * dy);
244-
249+
245250
// Stop scoring beyond lookahead distance
246251
if (accumulated_distance > lookahead_distance_) {break;}
247252
}
@@ -257,10 +262,8 @@ void PathAlignCritic::scoreGeometric(CriticData & data, std::vector<bool> & path
257262
cost_array(traj_idx) += min_dist;
258263
valid_sample_count(traj_idx)++;
259264
}
260-
261265
// Update hint for next sample
262266
closest_indices_(traj_idx) = closest_seg;
263-
264267
prev_x = px;
265268
prev_y = py;
266269
sample_idx++;
@@ -295,7 +298,7 @@ void PathAlignCritic::updatePathCache(const models::Path & path, size_t path_seg
295298
const size_t path_points_count = path_segments_count + 1;
296299

297300
// Only update cache if path changed
298-
// TODO: Optimize path change detection. Current element-wise comparison is expensive.
301+
// TODO(@silanus23): Optimize path change detection. Current element-wise comparison is expensive.
299302
// Possible approaches:
300303
// 1. Add hash field to models::Path (cleanest, needs controller-level change)
301304
// 2. Compute local hash here (self-contained, small overhead)
@@ -331,6 +334,7 @@ void PathAlignCritic::updatePathCache(const models::Path & path, size_t path_seg
331334
segment_dx_.resize(num_segments);
332335
segment_dy_.resize(num_segments);
333336
segment_len_sq_.resize(num_segments);
337+
segment_inv_len_sq_.resize(num_segments);
334338
}
335339

336340
// Precompute segment geometry for efficient distance calculations
@@ -341,45 +345,13 @@ void PathAlignCritic::updatePathCache(const models::Path & path, size_t path_seg
341345
segment_len_sq_(i) = segment_dx_(i) * segment_dx_(i) + segment_dy_(i) * segment_dy_(i);
342346
segment_lengths_(i) = std::sqrt(segment_len_sq_(i));
343347
cumulative_distances_(i + 1) = cumulative_distances_(i) + segment_lengths_(i);
348+
segment_inv_len_sq_(i) = (segment_len_sq_(i) > 1e-6f) ? (1.0f / segment_len_sq_(i)) : 0.0f;
344349
}
345350
}
346-
// TODO: Even I have tried other ways and spend effort maybe there can still be
347-
// room for improvement here
348-
float PathAlignCritic::distSqToSegment(float px, float py, Eigen::Index seg_idx)
349-
{
350-
const float x1 = path_x_cache_(seg_idx);
351-
const float y1 = path_y_cache_(seg_idx);
352-
const float x2 = path_x_cache_(seg_idx + 1);
353-
const float y2 = path_y_cache_(seg_idx + 1);
354-
355-
const float dx = x2 - x1;
356-
const float dy = y2 - y1;
357-
const float len_sq = dx * dx + dy * dy;
358-
359-
if (len_sq < 1e-6f) {
360-
// Degenerate segment, return distance to point
361-
const float dpx = px - x1;
362-
const float dpy = py - y1;
363-
return dpx * dpx + dpy * dpy;
364-
}
365-
366-
// Project point onto line, clamped to segment
367-
const float t = std::max(0.0f, std::min(1.0f, ((px - x1) * dx + (py - y1) * dy) / len_sq));
368-
369-
const float closest_x = x1 + t * dx;
370-
const float closest_y = y1 + t * dy;
371-
372-
const float dist_x = px - closest_x;
373-
const float dist_y = py - closest_y;
374-
375-
return dist_x * dist_x + dist_y * dist_y;
376-
}
377-
378351

352+
// TODO(@silanus23): Not sure if this should be in util too
379353
float PathAlignCritic::computeMinDistanceToPath(float px, float py, Eigen::Index & closest_seg_idx)
380354
{
381-
static constexpr float MIN_SEGMENT_LENGTH_SQ = 1e-6f;
382-
383355
const Eigen::Index num_segments = static_cast<Eigen::Index>(path_size_cache_) - 1;
384356
if (num_segments < 1) {return std::numeric_limits<float>::max();}
385357

@@ -401,10 +373,10 @@ float PathAlignCritic::computeMinDistanceToPath(float px, float py, Eigen::Index
401373
float min_dist_sq = std::numeric_limits<float>::max();
402374
Eigen::Index best_seg = closest_seg_idx;
403375

404-
// Search only within window for efficiency (O(window_size) instead of O(path_length))
376+
// Search only within window for efficiency
405377
for (Eigen::Index seg_idx = start_idx; seg_idx < num_segments; ++seg_idx) {
406378
if (cumulative_distances_(seg_idx) > end_distance) {break;}
407-
if (segment_len_sq_(seg_idx) < MIN_SEGMENT_LENGTH_SQ) {continue;} // Skip degenerate segments
379+
if (segment_len_sq_(seg_idx) < 1e-6f) {continue;} // Skip degenerate segments
408380

409381
const float dist_sq = distSqToSegment(px, py, seg_idx);
410382

0 commit comments

Comments
 (0)