1#![allow(dead_code, clippy::too_many_arguments, clippy::type_complexity)]
2
3fn binomial_coefficient(n: usize, k: usize) -> f64 {
9 if k > n {
10 return 0.0;
11 }
12 if k == 0 || k == n {
13 return 1.0;
14 }
15
16 let k = if k > n - k { n - k } else { k };
17
18 let mut result = 1.0;
19 for i in 0..k {
20 result *= (n - i) as f64;
21 result /= (i + 1) as f64;
22 }
23 result
24}
25
26fn bernstein_poly(n: usize, i: usize, t: f64) -> f64 {
28 binomial_coefficient(n, i) * t.powi(i as i32) * (1.0 - t).powi((n - i) as i32)
29}
30
31fn bezier(t: f64, control_points: &[(f64, f64)]) -> (f64, f64) {
33 let n = control_points.len() - 1;
34 let mut x = 0.0;
35 let mut y = 0.0;
36
37 for (i, &(px, py)) in control_points.iter().enumerate() {
38 let basis = bernstein_poly(n, i, t);
39 x += basis * px;
40 y += basis * py;
41 }
42
43 (x, y)
44}
45
46pub fn calc_bezier_path(control_points: &[(f64, f64)], n_points: usize) -> Vec<(f64, f64)> {
48 let mut trajectory = Vec::new();
49
50 for i in 0..n_points {
51 let t = i as f64 / (n_points - 1) as f64;
52 trajectory.push(bezier(t, control_points));
53 }
54
55 trajectory
56}
57
58pub fn calc_4points_bezier_path(
60 sx: f64,
61 sy: f64,
62 syaw: f64,
63 ex: f64,
64 ey: f64,
65 eyaw: f64,
66 offset: f64,
67) -> (Vec<(f64, f64)>, Vec<(f64, f64)>) {
68 let dist = ((sx - ex).powi(2) + (sy - ey).powi(2)).sqrt() / offset;
69
70 let control_points = vec![
71 (sx, sy),
72 (sx + dist * syaw.cos(), sy + dist * syaw.sin()),
73 (ex - dist * eyaw.cos(), ey - dist * eyaw.sin()),
74 (ex, ey),
75 ];
76
77 let path = calc_bezier_path(&control_points, 100);
78
79 (path, control_points)
80}
81
82pub fn bezier_derivatives_control_points(
84 control_points: &[(f64, f64)],
85 n_derivatives: usize,
86) -> Vec<Vec<(f64, f64)>> {
87 let mut derivatives = vec![control_points.to_vec()];
88
89 for i in 0..n_derivatives {
90 let current = &derivatives[i];
91 let n = current.len();
92
93 if n <= 1 {
94 break;
95 }
96
97 let mut next_derivative = Vec::new();
98 for j in 0..n - 1 {
99 let dx = (n - 1) as f64 * (current[j + 1].0 - current[j].0);
100 let dy = (n - 1) as f64 * (current[j + 1].1 - current[j].1);
101 next_derivative.push((dx, dy));
102 }
103 derivatives.push(next_derivative);
104 }
105
106 derivatives
107}
108
109pub fn calc_curvature(control_points: &[(f64, f64)], t: f64) -> f64 {
111 let derivatives = bezier_derivatives_control_points(control_points, 2);
112
113 if derivatives.len() < 3 {
114 return 0.0;
115 }
116
117 let first_deriv = bezier(t, &derivatives[1]);
118 let second_deriv = bezier(t, &derivatives[2]);
119
120 let dx = first_deriv.0;
121 let dy = first_deriv.1;
122 let ddx = second_deriv.0;
123 let ddy = second_deriv.1;
124
125 let numerator = dx * ddy - dy * ddx;
126 let denominator = (dx * dx + dy * dy).powf(1.5);
127
128 if denominator.abs() < 1e-10 {
129 0.0
130 } else {
131 numerator / denominator
132 }
133}
134
135pub struct BezierPathPlanner {
136 pub path: Vec<(f64, f64)>,
137 pub control_points: Vec<(f64, f64)>,
138 pub curvature: Vec<f64>,
139}
140
141impl BezierPathPlanner {
142 pub fn new() -> Self {
143 BezierPathPlanner {
144 path: Vec::new(),
145 control_points: Vec::new(),
146 curvature: Vec::new(),
147 }
148 }
149
150 pub fn planning(
151 &mut self,
152 sx: f64,
153 sy: f64,
154 syaw: f64,
155 ex: f64,
156 ey: f64,
157 eyaw: f64,
158 offset: f64,
159 ) -> bool {
160 let (path, control_points) = calc_4points_bezier_path(sx, sy, syaw, ex, ey, eyaw, offset);
161
162 let mut curvature = Vec::new();
163 for i in 0..path.len() {
164 let t = i as f64 / (path.len() - 1) as f64;
165 curvature.push(calc_curvature(&control_points, t));
166 }
167
168 self.path = path;
169 self.control_points = control_points;
170 self.curvature = curvature;
171
172 true
173 }
174
175 pub fn planning_with_control_points(
176 &mut self,
177 control_points: Vec<(f64, f64)>,
178 n_points: usize,
179 ) -> bool {
180 if control_points.len() < 2 {
181 return false;
182 }
183
184 let path = calc_bezier_path(&control_points, n_points);
185
186 let mut curvature = Vec::new();
187 for i in 0..path.len() {
188 let t = i as f64 / (path.len() - 1) as f64;
189 curvature.push(calc_curvature(&control_points, t));
190 }
191
192 self.path = path;
193 self.control_points = control_points;
194 self.curvature = curvature;
195
196 true
197 }
198}
199
200impl Default for BezierPathPlanner {
201 fn default() -> Self {
202 Self::new()
203 }
204}
205
206#[cfg(test)]
207#[allow(clippy::excessive_precision)]
208mod tests {
209 use super::*;
210
211 type PathFingerprint = (f64, f64, f64, f64);
212 type CurvatureFingerprint = (f64, f64, f64);
213
214 fn assert_point_close(actual: (f64, f64), expected: (f64, f64), tolerance: f64) {
215 assert!(
216 (actual.0 - expected.0).abs() < tolerance,
217 "x mismatch: actual={actual:?}, expected={expected:?}"
218 );
219 assert!(
220 (actual.1 - expected.1).abs() < tolerance,
221 "y mismatch: actual={actual:?}, expected={expected:?}"
222 );
223 }
224
225 fn path_fingerprint(path: &[(f64, f64)]) -> PathFingerprint {
226 let sum_x = path.iter().map(|point| point.0).sum();
227 let sum_y = path.iter().map(|point| point.1).sum();
228 let weighted_sum_x = path
229 .iter()
230 .enumerate()
231 .map(|(index, point)| (index + 1) as f64 * point.0)
232 .sum();
233 let weighted_sum_y = path
234 .iter()
235 .enumerate()
236 .map(|(index, point)| (index + 1) as f64 * point.1)
237 .sum();
238 (sum_x, sum_y, weighted_sum_x, weighted_sum_y)
239 }
240
241 fn curvature_fingerprint(curvature: &[f64]) -> CurvatureFingerprint {
242 let sum = curvature.iter().copied().sum();
243 let weighted_sum = curvature
244 .iter()
245 .enumerate()
246 .map(|(index, value)| (index + 1) as f64 * value)
247 .sum();
248 let sum_sq = curvature.iter().map(|value| value * value).sum();
249 (sum, weighted_sum, sum_sq)
250 }
251
252 fn assert_path_fingerprint_close(
253 actual: PathFingerprint,
254 expected: PathFingerprint,
255 tolerance: f64,
256 ) {
257 assert!(
258 (actual.0 - expected.0).abs() < tolerance,
259 "sum_x mismatch: actual={actual:?}, expected={expected:?}"
260 );
261 assert!(
262 (actual.1 - expected.1).abs() < tolerance,
263 "sum_y mismatch: actual={actual:?}, expected={expected:?}"
264 );
265 assert!(
266 (actual.2 - expected.2).abs() < tolerance,
267 "weighted_sum_x mismatch: actual={actual:?}, expected={expected:?}"
268 );
269 assert!(
270 (actual.3 - expected.3).abs() < tolerance,
271 "weighted_sum_y mismatch: actual={actual:?}, expected={expected:?}"
272 );
273 }
274
275 fn assert_curvature_fingerprint_close(
276 actual: CurvatureFingerprint,
277 expected: CurvatureFingerprint,
278 tolerance: f64,
279 ) {
280 assert!(
281 (actual.0 - expected.0).abs() < tolerance,
282 "sum mismatch: actual={actual:?}, expected={expected:?}"
283 );
284 assert!(
285 (actual.1 - expected.1).abs() < tolerance,
286 "weighted_sum mismatch: actual={actual:?}, expected={expected:?}"
287 );
288 assert!(
289 (actual.2 - expected.2).abs() < tolerance,
290 "sum_sq mismatch: actual={actual:?}, expected={expected:?}"
291 );
292 }
293
294 #[test]
295 fn test_bezier_path_planning() {
296 let mut planner = BezierPathPlanner::new();
297 let result = planner.planning(
298 10.0,
299 1.0,
300 180.0_f64.to_radians(),
301 0.0,
302 -3.0,
303 (-45.0_f64).to_radians(),
304 3.0,
305 );
306 assert!(result);
307 assert!(!planner.path.is_empty());
308 assert!(!planner.curvature.is_empty());
309 }
310
311 #[test]
312 fn test_bezier_with_control_points() {
313 let mut planner = BezierPathPlanner::new();
314 let cp = vec![(-1.0, 0.0), (3.0, -3.0), (4.0, 1.0), (2.0, 1.0), (1.0, 3.0)];
315 let result = planner.planning_with_control_points(cp, 100);
316 assert!(result);
317 assert!(!planner.path.is_empty());
318 }
319
320 #[test]
321 fn test_planning_matches_upstream_main_example() {
322 let mut planner = BezierPathPlanner::new();
323 planner.planning(
324 10.0,
325 1.0,
326 180.0_f64.to_radians(),
327 0.0,
328 -3.0,
329 (-45.0_f64).to_radians(),
330 3.0,
331 );
332
333 let expected_control_points = [
334 (10.0, 1.0),
335 (6.409_890_128_576_997, 1.0),
336 (-2.538_591_035_287_97, -0.461_408_964_712_031),
337 (0.0, -3.0),
338 ];
339 let expected_samples = [
340 (0usize, 10.0, 1.0),
341 (25, 6.526_392_761_056_504, 0.726_609_535_974_175),
342 (50, 2.630_199_272_944_397, -0.068_812_597_489_713),
343 (75, -0.060_978_738_129_225, -1.349_142_512_471_283),
344 (99, 0.0, -3.0),
345 ];
346
347 assert_eq!(planner.path.len(), 100);
348 for (actual, expected) in planner
349 .control_points
350 .iter()
351 .copied()
352 .zip(expected_control_points.iter().copied())
353 {
354 assert_point_close(actual, expected, 1e-12);
355 }
356 for (index, x, y) in expected_samples {
357 assert_point_close(planner.path[index], (x, y), 1e-12);
358 }
359 }
360
361 #[test]
362 fn test_calc_curvature_matches_upstream_main_example() {
363 let control_points = [
364 (10.0, 1.0),
365 (6.409_890_128_576_997, 1.0),
366 (-2.538_591_035_287_97, -0.461_408_964_712_031),
367 (0.0, -3.0),
368 ];
369
370 let curvature = calc_curvature(&control_points, 0.86);
371 assert!((curvature - 1.203_883_311_167_986).abs() < 1e-12);
372 }
373
374 #[test]
375 fn test_planning_with_control_points_matches_upstream_reference() {
376 let mut planner = BezierPathPlanner::new();
377 planner.planning_with_control_points(
378 vec![(-1.0, 0.0), (3.0, -3.0), (4.0, 1.0), (2.0, 1.0), (1.0, 3.0)],
379 100,
380 );
381
382 let expected_path_samples = [
383 (0usize, -1.0, 0.0),
384 (25, 1.908_827_926_528_655, -0.991_419_119_052_972),
385 (50, 2.749_694_941_997_521, 0.090_314_761_977_827),
386 (75, 2.108_174_996_479_529, 1.482_624_053_372_864),
387 (99, 1.0, 3.0),
388 ];
389 let expected_curvature_samples = [
390 (0usize, 0.114),
391 (25, 0.686_973_069_873_466),
392 (50, 0.778_736_996_781_883),
393 (75, 0.123_309_580_949_893),
394 (99, -0.268_328_157_299_975),
395 ];
396
397 assert_eq!(planner.path.len(), 100);
398 assert_eq!(planner.curvature.len(), 100);
399 for (index, x, y) in expected_path_samples {
400 assert_point_close(planner.path[index], (x, y), 1e-12);
401 }
402 for (index, expected) in expected_curvature_samples {
403 assert!(
404 (planner.curvature[index] - expected).abs() < 1e-12,
405 "curvature mismatch at {index}: actual={}, expected={expected}",
406 planner.curvature[index]
407 );
408 }
409 assert_path_fingerprint_close(
410 path_fingerprint(&planner.path),
411 (
412 178.183_164_845_750_3,
413 41.116_834_432_822_614,
414 10_028.516_464_168_946,
415 5_417.733_506_201_01,
416 ),
417 1e-9,
418 );
419 assert_curvature_fingerprint_close(
420 curvature_fingerprint(&planner.curvature),
421 (
422 39.037_977_305_450_994,
423 1_413.299_260_366_335_1,
424 30.142_637_421_645_173,
425 ),
426 1e-9,
427 );
428 }
429}