1use crate::error::{SpatialError, SpatialResult};
29use std::f64::consts::PI;
30
31pub use super::dubins::Pose2D;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum Motion {
37 Forward,
39 Backward,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum Turn {
46 Left,
48 Right,
50 Straight,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq)]
56pub struct ReedsSheppSegment {
57 pub motion: Motion,
59 pub turn: Turn,
61 pub length: f64,
63}
64
65impl ReedsSheppSegment {
66 pub fn new(motion: Motion, turn: Turn, length: f64) -> Self {
68 Self {
69 motion,
70 turn,
71 length,
72 }
73 }
74
75 pub fn signed_length(&self) -> f64 {
77 match self.motion {
78 Motion::Forward => self.length,
79 Motion::Backward => -self.length,
80 }
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum ReedsSheppPathType {
87 CCC,
89 CSC,
91 CCSCC,
93}
94
95#[derive(Debug, Clone)]
97pub struct ReedsSheppPath {
98 start: Pose2D,
100 goal: Pose2D,
102 turning_radius: f64,
104 path_type: ReedsSheppPathType,
106 segments: Vec<ReedsSheppSegment>,
108 length: f64,
110}
111
112impl ReedsSheppPath {
113 fn new(
115 start: Pose2D,
116 goal: Pose2D,
117 turning_radius: f64,
118 path_type: ReedsSheppPathType,
119 segments: Vec<ReedsSheppSegment>,
120 ) -> Self {
121 let length = segments.iter().map(|s| s.length).sum();
122 Self {
123 start,
124 goal,
125 turning_radius,
126 path_type,
127 segments,
128 length,
129 }
130 }
131
132 pub fn length(&self) -> f64 {
134 self.length
135 }
136
137 pub fn path_type(&self) -> ReedsSheppPathType {
139 self.path_type
140 }
141
142 pub fn segments(&self) -> &[ReedsSheppSegment] {
144 &self.segments
145 }
146
147 pub fn start(&self) -> &Pose2D {
149 &self.start
150 }
151
152 pub fn goal(&self) -> &Pose2D {
154 &self.goal
155 }
156
157 pub fn turning_radius(&self) -> f64 {
159 self.turning_radius
160 }
161
162 pub fn sample(&self, t: f64) -> SpatialResult<Pose2D> {
172 if !(0.0..=1.0).contains(&t) {
173 return Err(SpatialError::ValueError(
174 "Parameter t must be in [0, 1]".to_string(),
175 ));
176 }
177
178 let target_distance = t * self.length;
179 let mut current_distance = 0.0;
180 let mut current_pose = self.start;
181
182 for segment in &self.segments {
183 if current_distance + segment.length >= target_distance {
184 let segment_distance = target_distance - current_distance;
186 let segment_t = if segment.length > 0.0 {
187 segment_distance / segment.length
188 } else {
189 0.0
190 };
191
192 return self.sample_segment(¤t_pose, segment, segment_t);
193 }
194
195 current_pose = self.sample_segment(¤t_pose, segment, 1.0)?;
197 current_distance += segment.length;
198 }
199
200 Ok(self.goal)
201 }
202
203 fn sample_segment(
205 &self,
206 start_pose: &Pose2D,
207 segment: &ReedsSheppSegment,
208 t: f64,
209 ) -> SpatialResult<Pose2D> {
210 let distance = t * segment.signed_length();
211
212 match segment.turn {
213 Turn::Straight => {
214 let new_x = start_pose.x + distance * start_pose.theta.cos();
215 let new_y = start_pose.y + distance * start_pose.theta.sin();
216 Ok(Pose2D::new(new_x, new_y, start_pose.theta))
217 }
218 Turn::Left => {
219 let sign = match segment.motion {
220 Motion::Forward => 1.0,
221 Motion::Backward => -1.0,
222 };
223 let angle = distance / self.turning_radius;
224 let center_x = start_pose.x - sign * self.turning_radius * start_pose.theta.sin();
225 let center_y = start_pose.y + sign * self.turning_radius * start_pose.theta.cos();
226 let new_theta = start_pose.theta + angle;
227 let new_x = center_x + sign * self.turning_radius * new_theta.sin();
228 let new_y = center_y - sign * self.turning_radius * new_theta.cos();
229 Ok(Pose2D::new(new_x, new_y, new_theta))
230 }
231 Turn::Right => {
232 let sign = match segment.motion {
233 Motion::Forward => 1.0,
234 Motion::Backward => -1.0,
235 };
236 let angle = distance / self.turning_radius;
237 let center_x = start_pose.x + sign * self.turning_radius * start_pose.theta.sin();
238 let center_y = start_pose.y - sign * self.turning_radius * start_pose.theta.cos();
239 let new_theta = start_pose.theta - angle;
240 let new_x = center_x - sign * self.turning_radius * new_theta.sin();
241 let new_y = center_y + sign * self.turning_radius * new_theta.cos();
242 Ok(Pose2D::new(new_x, new_y, new_theta))
243 }
244 }
245 }
246}
247
248pub struct ReedsSheppPlanner {
250 turning_radius: f64,
252}
253
254impl ReedsSheppPlanner {
255 pub fn new(_turningradius: f64) -> Self {
265 Self {
266 turning_radius: _turningradius,
267 }
268 }
269
270 pub fn plan(&self, start: &Pose2D, goal: &Pose2D) -> SpatialResult<ReedsSheppPath> {
281 if self.turning_radius <= 0.0 {
282 return Err(SpatialError::ValueError(
283 "Turning radius must be positive".to_string(),
284 ));
285 }
286
287 let _start = start.normalize_angle();
289 let goal = goal.normalize_angle();
290
291 let dx = goal.x - start.x;
293 let dy = goal.y - start.y;
294 let dtheta = goal.theta - start.theta;
295
296 let cos_theta = start.theta.cos();
298 let sin_theta = start.theta.sin();
299 let x = dx * cos_theta + dy * sin_theta;
300 let y = -dx * sin_theta + dy * cos_theta;
301 let phi = Self::normalize_angle(dtheta);
302
303 let x_scaled = x / self.turning_radius;
305 let y_scaled = y / self.turning_radius;
306
307 let mut best_path = None;
309 let mut best_length = f64::INFINITY;
310
311 let path_functions = [
313 Self::csc_path,
314 Self::ccc_path,
315 Self::cccc_path,
316 Self::ccsc_path,
317 Self::ccscc_path,
318 ];
319
320 for path_fn in &path_functions {
321 if let Ok(segments) = path_fn(self, x_scaled, y_scaled, phi) {
322 let path_length: f64 = segments.iter().map(|s| s.length).sum();
323 if path_length < best_length {
324 best_length = path_length;
325 let path_type = ReedsSheppPlanner::determine_path_type(&segments);
326 best_path = Some(ReedsSheppPath::new(
327 *start,
328 goal,
329 self.turning_radius,
330 path_type,
331 segments,
332 ));
333 }
334 }
335 }
336
337 best_path.ok_or_else(|| {
338 SpatialError::ComputationError(
339 "Failed to compute any valid Reeds-Shepp path".to_string(),
340 )
341 })
342 }
343
344 fn determine_path_type(segments: &[ReedsSheppSegment]) -> ReedsSheppPathType {
346 match segments.len() {
347 3 => {
348 if segments.iter().all(|s| s.turn != Turn::Straight) {
349 ReedsSheppPathType::CCC
350 } else {
351 ReedsSheppPathType::CSC
352 }
353 }
354 _ => ReedsSheppPathType::CSC, }
356 }
357
358 fn csc_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
360 let d = (x * x + y * y).sqrt();
364 if d < 2.0 {
365 return Err(SpatialError::ComputationError(
366 "Distance too small for CSC path".to_string(),
367 ));
368 }
369
370 let theta = y.atan2(x);
372 let alpha = Self::normalize_angle(-theta);
373 let beta = Self::normalize_angle(phi - theta);
374
375 let tmp0 = d + alpha.sin() - beta.sin();
377 let p_squared =
378 2.0 + d * d - 2.0 * (alpha - beta).cos() + 2.0 * d * (alpha.sin() - beta.sin());
379
380 if p_squared >= 0.0 {
381 let tmp1 = (beta - alpha).atan2(tmp0);
382 let t = Self::normalize_angle(-alpha + tmp1);
383 let p = p_squared.sqrt();
384 let q = Self::normalize_angle(beta - tmp1);
385
386 if t >= 0.0 && p >= 0.0 && q >= 0.0 {
387 return Ok(vec![
388 ReedsSheppSegment::new(
389 Motion::Forward,
390 Turn::Left,
391 t.abs() * self.turning_radius,
392 ),
393 ReedsSheppSegment::new(
394 Motion::Forward,
395 Turn::Straight,
396 p.abs() * self.turning_radius,
397 ),
398 ReedsSheppSegment::new(
399 Motion::Forward,
400 Turn::Left,
401 q.abs() * self.turning_radius,
402 ),
403 ]);
404 }
405 }
406
407 Err(SpatialError::ComputationError(
408 "No valid CSC path found".to_string(),
409 ))
410 }
411
412 fn ccc_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
414 let xi = x - phi.sin();
416 let eta = y - 1.0 + phi.cos();
417 let rho = 0.25 * (2.0 + (xi * xi + eta * eta).sqrt());
418
419 if rho <= 1.0 {
420 let u = (4.0 * rho * rho - 1.0).sqrt().acos();
421 if u >= 0.0 {
422 let t = Self::normalize_angle(u + xi.atan2(eta));
423 let v = Self::normalize_angle(t - u - phi);
424
425 if t >= 0.0 && v <= 0.0 {
426 return Ok(vec![
427 ReedsSheppSegment::new(
428 Motion::Forward,
429 Turn::Left,
430 t.abs() * self.turning_radius,
431 ),
432 ReedsSheppSegment::new(
433 Motion::Backward,
434 Turn::Right,
435 u.abs() * self.turning_radius,
436 ),
437 ReedsSheppSegment::new(
438 Motion::Forward,
439 Turn::Left,
440 v.abs() * self.turning_radius,
441 ),
442 ]);
443 }
444 }
445 }
446
447 Err(SpatialError::ComputationError(
448 "No valid CCC path found".to_string(),
449 ))
450 }
451
452 fn lrlr_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
454 let xi = x + phi.sin();
455 let eta = y - 1.0 + phi.cos();
456 let rho = 0.25 * (xi * xi + eta * eta);
457
458 if rho <= 1.0 {
459 let u = (4.0 - rho * rho).sqrt().acos();
460 if u.is_finite() && u >= 0.0 {
461 let v = eta.atan2(xi);
462 let t = Self::normalize_angle(v + u + PI);
463 let s = Self::normalize_angle(phi - t + 2.0 * u);
464
465 if t >= 0.0 && s >= 0.0 {
466 return Ok(vec![
467 ReedsSheppSegment::new(
468 Motion::Forward,
469 Turn::Left,
470 t * self.turning_radius,
471 ),
472 ReedsSheppSegment::new(
473 Motion::Backward,
474 Turn::Right,
475 u * self.turning_radius,
476 ),
477 ReedsSheppSegment::new(
478 Motion::Forward,
479 Turn::Left,
480 u * self.turning_radius,
481 ),
482 ReedsSheppSegment::new(
483 Motion::Backward,
484 Turn::Right,
485 s * self.turning_radius,
486 ),
487 ]);
488 }
489 }
490 }
491
492 Err(SpatialError::ComputationError(
493 "No valid LRLR path found".to_string(),
494 ))
495 }
496
497 fn rlrl_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
499 let xi = x - phi.sin();
500 let eta = y - 1.0 - phi.cos();
501 let rho = 0.25 * (xi * xi + eta * eta);
502
503 if rho <= 1.0 {
504 let u = (4.0 - rho * rho).sqrt().acos();
505 if u.is_finite() && u >= 0.0 {
506 let v = eta.atan2(xi);
507 let t = Self::normalize_angle(v - u - PI);
508 let s = Self::normalize_angle(t - phi + 2.0 * u);
509
510 if t <= 0.0 && s <= 0.0 {
511 return Ok(vec![
512 ReedsSheppSegment::new(
513 Motion::Forward,
514 Turn::Right,
515 t.abs() * self.turning_radius,
516 ),
517 ReedsSheppSegment::new(
518 Motion::Backward,
519 Turn::Left,
520 u * self.turning_radius,
521 ),
522 ReedsSheppSegment::new(
523 Motion::Forward,
524 Turn::Right,
525 u * self.turning_radius,
526 ),
527 ReedsSheppSegment::new(
528 Motion::Backward,
529 Turn::Left,
530 s.abs() * self.turning_radius,
531 ),
532 ]);
533 }
534 }
535 }
536
537 Err(SpatialError::ComputationError(
538 "No valid RLRL path found".to_string(),
539 ))
540 }
541
542 fn cccc_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
544 if let Ok(lrlr_path) = self.lrlr_path(x, y, phi) {
549 return Ok(lrlr_path);
550 }
551
552 if let Ok(rlrl_path) = self.rlrl_path(x, y, phi) {
554 return Ok(rlrl_path);
555 }
556
557 Err(SpatialError::ComputationError(
558 "No valid CCCC path found".to_string(),
559 ))
560 }
561
562 fn lrsl_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
564 let xi = x - phi.sin();
565 let eta = y - 1.0 + phi.cos();
566 let rho_squared = xi * xi + eta * eta;
567
568 if rho_squared >= 4.0 {
569 let rho = rho_squared.sqrt();
570 let u = (rho - 2.0).acos();
571 if u.is_finite() {
572 let v = eta.atan2(xi);
573 let t = Self::normalize_angle(v - u);
574 let s = Self::normalize_angle(t - phi + u);
575
576 if t >= 0.0 && s >= 0.0 {
577 let p = rho - 2.0;
578 return Ok(vec![
579 ReedsSheppSegment::new(
580 Motion::Forward,
581 Turn::Left,
582 t * self.turning_radius,
583 ),
584 ReedsSheppSegment::new(
585 Motion::Backward,
586 Turn::Right,
587 u * self.turning_radius,
588 ),
589 ReedsSheppSegment::new(
590 Motion::Forward,
591 Turn::Straight,
592 p * self.turning_radius,
593 ),
594 ReedsSheppSegment::new(
595 Motion::Forward,
596 Turn::Left,
597 s * self.turning_radius,
598 ),
599 ]);
600 }
601 }
602 }
603
604 Err(SpatialError::ComputationError(
605 "No valid LRSL path found".to_string(),
606 ))
607 }
608
609 fn lrsr_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
611 let xi = x + phi.sin();
612 let eta = y - 1.0 - phi.cos();
613 let rho_squared = xi * xi + eta * eta;
614
615 if rho_squared >= 4.0 {
616 let rho = rho_squared.sqrt();
617 let u = (rho - 2.0).acos();
618 if u.is_finite() {
619 let v = eta.atan2(xi);
620 let t = Self::normalize_angle(v + u);
621 let s = Self::normalize_angle(phi - t + u);
622
623 if t >= 0.0 && s >= 0.0 {
624 let p = rho - 2.0;
625 return Ok(vec![
626 ReedsSheppSegment::new(
627 Motion::Forward,
628 Turn::Left,
629 t * self.turning_radius,
630 ),
631 ReedsSheppSegment::new(
632 Motion::Backward,
633 Turn::Right,
634 u * self.turning_radius,
635 ),
636 ReedsSheppSegment::new(
637 Motion::Forward,
638 Turn::Straight,
639 p * self.turning_radius,
640 ),
641 ReedsSheppSegment::new(
642 Motion::Backward,
643 Turn::Right,
644 s * self.turning_radius,
645 ),
646 ]);
647 }
648 }
649 }
650
651 Err(SpatialError::ComputationError(
652 "No valid LRSR path found".to_string(),
653 ))
654 }
655
656 fn rlsl_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
658 let xi = x + phi.sin();
659 let eta = y - 1.0 - phi.cos();
660 let rho_squared = xi * xi + eta * eta;
661
662 if rho_squared >= 4.0 {
663 let rho = rho_squared.sqrt();
664 let u = (rho - 2.0).acos();
665 if u.is_finite() {
666 let v = eta.atan2(xi);
667 let t = Self::normalize_angle(v - u);
668 let s = Self::normalize_angle(phi - t + u);
669
670 if t <= 0.0 && s <= 0.0 {
671 let p = rho - 2.0;
672 return Ok(vec![
673 ReedsSheppSegment::new(
674 Motion::Forward,
675 Turn::Right,
676 t.abs() * self.turning_radius,
677 ),
678 ReedsSheppSegment::new(
679 Motion::Backward,
680 Turn::Left,
681 u * self.turning_radius,
682 ),
683 ReedsSheppSegment::new(
684 Motion::Forward,
685 Turn::Straight,
686 p * self.turning_radius,
687 ),
688 ReedsSheppSegment::new(
689 Motion::Forward,
690 Turn::Left,
691 s.abs() * self.turning_radius,
692 ),
693 ]);
694 }
695 }
696 }
697
698 Err(SpatialError::ComputationError(
699 "No valid RLSL path found".to_string(),
700 ))
701 }
702
703 fn rlsr_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
705 let xi = x - phi.sin();
706 let eta = y - 1.0 + phi.cos();
707 let rho_squared = xi * xi + eta * eta;
708
709 if rho_squared >= 4.0 {
710 let rho = rho_squared.sqrt();
711 let u = (rho - 2.0).acos();
712 if u.is_finite() {
713 let v = eta.atan2(xi);
714 let t = Self::normalize_angle(v + u);
715 let s = Self::normalize_angle(t - phi + u);
716
717 if t <= 0.0 && s <= 0.0 {
718 let p = rho - 2.0;
719 return Ok(vec![
720 ReedsSheppSegment::new(
721 Motion::Forward,
722 Turn::Right,
723 t.abs() * self.turning_radius,
724 ),
725 ReedsSheppSegment::new(
726 Motion::Backward,
727 Turn::Left,
728 u * self.turning_radius,
729 ),
730 ReedsSheppSegment::new(
731 Motion::Forward,
732 Turn::Straight,
733 p * self.turning_radius,
734 ),
735 ReedsSheppSegment::new(
736 Motion::Backward,
737 Turn::Right,
738 s.abs() * self.turning_radius,
739 ),
740 ]);
741 }
742 }
743 }
744
745 Err(SpatialError::ComputationError(
746 "No valid RLSR path found".to_string(),
747 ))
748 }
749
750 fn ccsc_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
752 if let Ok(lrsl_path) = self.lrsl_path(x, y, phi) {
757 return Ok(lrsl_path);
758 }
759
760 if let Ok(lrsr_path) = self.lrsr_path(x, y, phi) {
762 return Ok(lrsr_path);
763 }
764
765 if let Ok(rlsl_path) = self.rlsl_path(x, y, phi) {
767 return Ok(rlsl_path);
768 }
769
770 if let Ok(rlsr_path) = self.rlsr_path(x, y, phi) {
772 return Ok(rlsr_path);
773 }
774
775 Err(SpatialError::ComputationError(
776 "No valid CCSC path found".to_string(),
777 ))
778 }
779
780 fn lrlsl_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
782 let xi = x + phi.sin();
783 let eta = y - 1.0 + phi.cos();
784 let rho = 0.25 * (xi * xi + eta * eta);
785
786 if (1.0..=4.0).contains(&rho) {
787 let u1 = (rho - 1.0).sqrt().acos();
788 let u2 = (4.0 - rho).sqrt().acos();
789
790 if u1.is_finite() && u2.is_finite() {
791 let v = eta.atan2(xi);
792 let t = Self::normalize_angle(v + u1 + u2 + PI);
793 let s = Self::normalize_angle(phi - t + u1 + u2);
794
795 if t >= 0.0 && s >= 0.0 {
796 let p = (rho - 1.0).sqrt();
797 return Ok(vec![
798 ReedsSheppSegment::new(
799 Motion::Forward,
800 Turn::Left,
801 t * self.turning_radius,
802 ),
803 ReedsSheppSegment::new(
804 Motion::Backward,
805 Turn::Right,
806 u1 * self.turning_radius,
807 ),
808 ReedsSheppSegment::new(
809 Motion::Forward,
810 Turn::Left,
811 u2 * self.turning_radius,
812 ),
813 ReedsSheppSegment::new(
814 Motion::Forward,
815 Turn::Straight,
816 p * self.turning_radius,
817 ),
818 ReedsSheppSegment::new(
819 Motion::Forward,
820 Turn::Left,
821 s * self.turning_radius,
822 ),
823 ]);
824 }
825 }
826 }
827
828 Err(SpatialError::ComputationError(
829 "No valid LRLSL path found".to_string(),
830 ))
831 }
832
833 fn rlrlr_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
835 let xi = x - phi.sin();
836 let eta = y - 1.0 - phi.cos();
837 let rho = 0.25 * (xi * xi + eta * eta);
838
839 if (1.0..=4.0).contains(&rho) {
840 let u1 = (rho - 1.0).sqrt().acos();
841 let u2 = (4.0 - rho).sqrt().acos();
842
843 if u1.is_finite() && u2.is_finite() {
844 let v = eta.atan2(xi);
845 let t = Self::normalize_angle(v - u1 - u2 - PI);
846 let s = Self::normalize_angle(t - phi + u1 + u2);
847
848 if t <= 0.0 && s <= 0.0 {
849 let _p = (rho - 1.0).sqrt(); return Ok(vec![
851 ReedsSheppSegment::new(
852 Motion::Forward,
853 Turn::Right,
854 t.abs() * self.turning_radius,
855 ),
856 ReedsSheppSegment::new(
857 Motion::Backward,
858 Turn::Left,
859 u1 * self.turning_radius,
860 ),
861 ReedsSheppSegment::new(
862 Motion::Forward,
863 Turn::Right,
864 u2 * self.turning_radius,
865 ),
866 ReedsSheppSegment::new(
867 Motion::Forward,
868 Turn::Left,
869 u1 * self.turning_radius,
870 ),
871 ReedsSheppSegment::new(
872 Motion::Backward,
873 Turn::Right,
874 s.abs() * self.turning_radius,
875 ),
876 ]);
877 }
878 }
879 }
880
881 Err(SpatialError::ComputationError(
882 "No valid RLRLR path found".to_string(),
883 ))
884 }
885
886 fn ccscc_path(&self, x: f64, y: f64, phi: f64) -> SpatialResult<Vec<ReedsSheppSegment>> {
888 if let Ok(lrlsl_path) = self.lrlsl_path(x, y, phi) {
893 return Ok(lrlsl_path);
894 }
895
896 if let Ok(rlrlr_path) = self.rlrlr_path(x, y, phi) {
898 return Ok(rlrlr_path);
899 }
900
901 Err(SpatialError::ComputationError(
902 "No valid CCSCC path found".to_string(),
903 ))
904 }
905
906 fn normalize_angle(angle: f64) -> f64 {
908 let mut normalized = angle % (2.0 * PI);
909 if normalized > PI {
910 normalized -= 2.0 * PI;
911 } else if normalized < -PI {
912 normalized += 2.0 * PI;
913 }
914 normalized
915 }
916}
917
918#[cfg(test)]
919mod tests {
920 use super::*;
921 use approx::assert_relative_eq;
922
923 #[test]
924 fn test_reedshepp_segment_basic() {
925 let segment = ReedsSheppSegment::new(Motion::Forward, Turn::Left, 2.0);
926 assert_eq!(segment.motion, Motion::Forward);
927 assert_eq!(segment.turn, Turn::Left);
928 assert_eq!(segment.length, 2.0);
929 assert_eq!(segment.signed_length(), 2.0);
930
931 let backward_segment = ReedsSheppSegment::new(Motion::Backward, Turn::Right, 3.0);
932 assert_eq!(backward_segment.signed_length(), -3.0);
933 }
934
935 #[test]
936 fn test_reedshepp_planner_creation() {
937 let planner = ReedsSheppPlanner::new(1.0);
938 assert_eq!(planner.turning_radius, 1.0);
939 }
940
941 #[test]
942 fn test_reedshepp_simple_path() {
943 let start = Pose2D::new(0.0, 0.0, 0.0);
944 let goal = Pose2D::new(5.0, 0.0, 0.0);
945 let planner = ReedsSheppPlanner::new(1.0);
946
947 let path = planner.plan(&start, &goal);
948 if let Ok(path) = path {
949 assert!(path.length() > 0.0);
950 assert!(!path.segments().is_empty());
951 }
952 }
954
955 #[test]
956 fn test_reedshepp_path_sampling() {
957 let start = Pose2D::new(0.0, 0.0, 0.0);
958 let goal = Pose2D::new(3.0, 0.0, 0.0);
959 let planner = ReedsSheppPlanner::new(1.0);
960
961 if let Ok(path) = planner.plan(&start, &goal) {
962 let start_sample = path.sample(0.0).expect("Operation failed");
964 assert_relative_eq!(start_sample.x, start.x, epsilon = 1e-2);
965 assert_relative_eq!(start_sample.y, start.y, epsilon = 1e-2);
966
967 assert!(path.sample(-0.1).is_err());
969 assert!(path.sample(1.1).is_err());
970 }
971 }
972
973 #[test]
974 fn test_reedshepp_backward_capability() {
975 let start = Pose2D::new(0.0, 0.0, 0.0);
977 let goal = Pose2D::new(-1.0, 0.0, PI);
978 let planner = ReedsSheppPlanner::new(2.0);
979
980 let path = planner.plan(&start, &goal);
981 if let Ok(path) = path {
982 let _has_backward = path.segments().iter().any(|s| s.motion == Motion::Backward);
984 assert!(path.length() > 0.0);
987 }
988 }
989
990 #[test]
991 fn test_normalize_angle() {
992 assert_relative_eq!(
993 ReedsSheppPlanner::normalize_angle(0.0),
994 0.0,
995 epsilon = 1e-10
996 );
997 assert_relative_eq!(ReedsSheppPlanner::normalize_angle(PI), PI, epsilon = 1e-10);
998 assert_relative_eq!(
999 ReedsSheppPlanner::normalize_angle(-PI),
1000 -PI,
1001 epsilon = 1e-10
1002 );
1003
1004 let normalized_3pi = ReedsSheppPlanner::normalize_angle(3.0 * PI);
1006 assert!(
1007 (normalized_3pi - PI).abs() < 1e-10 || (normalized_3pi - (-PI)).abs() < 1e-10,
1008 "Expected ±π, got {normalized_3pi}"
1009 );
1010 }
1011
1012 #[test]
1013 fn test_reedshepp_invalid_turning_radius() {
1014 let start = Pose2D::new(0.0, 0.0, 0.0);
1015 let goal = Pose2D::new(1.0, 0.0, 0.0);
1016 let planner = ReedsSheppPlanner::new(-1.0);
1017
1018 let result = planner.plan(&start, &goal);
1019 assert!(result.is_err());
1020 }
1021
1022 #[test]
1023 fn test_reedshepp_cccc_paths() {
1024 let start = Pose2D::new(0.0, 0.0, 0.0);
1026 let goal = Pose2D::new(0.0, 0.0, 2.0 * PI); let planner = ReedsSheppPlanner::new(0.5);
1028
1029 let path = planner.plan(&start, &goal);
1030 if let Ok(path) = path {
1031 assert!(path.length() >= 0.0);
1034 assert!(!path.segments().is_empty());
1035
1036 assert!(!path.segments().is_empty());
1038 }
1039 }
1040
1041 #[test]
1042 fn test_reedshepp_ccsc_paths() {
1043 let start = Pose2D::new(0.0, 0.0, 0.0);
1045 let goal = Pose2D::new(5.0, 2.0, PI / 2.0); let planner = ReedsSheppPlanner::new(1.0);
1047
1048 let path = planner.plan(&start, &goal);
1049 if let Ok(path) = path {
1050 assert!(path.length() > 0.0);
1051 assert!(!path.segments().is_empty());
1052
1053 assert!(path.length() < 20.0 * planner.turning_radius); }
1056 }
1057
1058 #[test]
1059 fn test_reedshepp_ccscc_paths() {
1060 let start = Pose2D::new(0.0, 0.0, 0.0);
1062 let goal = Pose2D::new(-2.0, 1.0, -PI / 4.0); let planner = ReedsSheppPlanner::new(0.8);
1064
1065 let path = planner.plan(&start, &goal);
1066 if let Ok(path) = path {
1067 assert!(path.length() > 0.0);
1068 assert!(!path.segments().is_empty());
1069
1070 assert!(path.segments().len() >= 3);
1072 assert!(path.segments().len() <= 5);
1073 }
1074 }
1075
1076 #[test]
1077 fn test_reedshepp_path_types() {
1078 let start = Pose2D::new(0.0, 0.0, 0.0);
1079 let goal = Pose2D::new(2.0, 1.0, PI / 3.0);
1080 let planner = ReedsSheppPlanner::new(1.0);
1081
1082 let path = planner.plan(&start, &goal);
1083 if let Ok(path) = path {
1084 let path_type = path.path_type();
1086 assert!(matches!(
1087 path_type,
1088 ReedsSheppPathType::CCC | ReedsSheppPathType::CSC | ReedsSheppPathType::CCSCC
1089 ));
1090
1091 for segment in path.segments() {
1093 assert!(segment.length >= 0.0);
1094 assert!(segment.length.is_finite());
1095
1096 assert!(matches!(segment.motion, Motion::Forward | Motion::Backward));
1098
1099 assert!(matches!(
1101 segment.turn,
1102 Turn::Left | Turn::Right | Turn::Straight
1103 ));
1104 }
1105 }
1106 }
1107
1108 #[test]
1109 fn test_reedshepp_segments_validation() {
1110 let start = Pose2D::new(0.0, 0.0, 0.0);
1111 let goal = Pose2D::new(1.0, 1.0, PI / 2.0);
1112 let planner = ReedsSheppPlanner::new(1.0);
1113
1114 let path = planner.plan(&start, &goal);
1115 if let Ok(path) = path {
1116 for segment in path.segments() {
1118 assert!(
1120 segment.length >= 0.0,
1121 "Segment length should be non-negative, got: {}",
1122 segment.length
1123 );
1124 assert!(segment.length.is_finite());
1125
1126 let signed_length = segment.signed_length();
1128 match segment.motion {
1129 Motion::Forward => {
1130 assert_relative_eq!(signed_length, segment.length, epsilon = 1e-10)
1131 }
1132 Motion::Backward => {
1133 assert_relative_eq!(signed_length, -segment.length, epsilon = 1e-10)
1134 }
1135 }
1136 }
1137
1138 assert!(path.length() > 0.0);
1140 }
1141 }
1142
1143 #[test]
1144 fn test_reedshepp_different_turning_radii() {
1145 let start = Pose2D::new(0.0, 0.0, 0.0);
1146 let goal = Pose2D::new(2.0, 2.0, PI);
1147
1148 let radii = [0.5, 1.0, 2.0, 5.0];
1149 let mut path_lengths = Vec::new();
1150
1151 for &radius in &radii {
1152 let planner = ReedsSheppPlanner::new(radius);
1153 if let Ok(path) = planner.plan(&start, &goal) {
1154 path_lengths.push(path.length());
1155 }
1156 }
1157
1158 assert!(
1161 !path_lengths.is_empty(),
1162 "Should find valid paths for some turning radii"
1163 );
1164
1165 for &length in &path_lengths {
1166 assert!(length > 0.0);
1167 assert!(length.is_finite());
1168 }
1169 }
1170
1171 #[test]
1172 fn test_reedshepp_path_continuity() {
1173 let start = Pose2D::new(1.0, 2.0, PI / 4.0);
1175 let goal = Pose2D::new(3.0, 1.0, -PI / 6.0);
1176 let planner = ReedsSheppPlanner::new(1.5);
1177
1178 let path = planner.plan(&start, &goal);
1179 if let Ok(path) = path {
1180 let start_sample = path.sample(0.0).expect("Operation failed");
1182 let end_sample = path.sample(1.0).expect("Operation failed");
1183
1184 assert_relative_eq!(start_sample.x, start.x, epsilon = 1e-2);
1186 assert_relative_eq!(start_sample.y, start.y, epsilon = 1e-2);
1187 assert_relative_eq!(end_sample.x, goal.x, epsilon = 1e-2);
1188 assert_relative_eq!(end_sample.y, goal.y, epsilon = 1e-2);
1189 }
1190 }
1191
1192 #[test]
1193 fn test_reedshepp_extreme_cases() {
1194 let planner = ReedsSheppPlanner::new(1.0);
1195
1196 let close_start = Pose2D::new(0.0, 0.0, 0.0);
1198 let close_goal = Pose2D::new(0.01, 0.01, 0.1);
1199
1200 if let Ok(path) = planner.plan(&close_start, &close_goal) {
1201 assert!(path.length() > 0.0);
1202 assert!(path.length() < 1.0); }
1204
1205 let complex_start = Pose2D::new(0.0, 0.0, 0.0);
1207 let complex_goal = Pose2D::new(-1.0, -1.0, PI);
1208
1209 if let Ok(path) = planner.plan(&complex_start, &complex_goal) {
1210 assert!(path.length() > 2.0); }
1212 }
1213}