use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct ClothoidState<S: ControlScalar> {
pub x: S,
pub y: S,
pub theta: S,
pub kappa: S,
}
#[derive(Debug, Clone, Copy)]
pub struct ClothoidSegment<S: ControlScalar> {
pub curvature_rate: S,
pub length: S,
}
impl<S: ControlScalar> ClothoidSegment<S> {
pub fn new(curvature_rate: S, length: S) -> Self {
Self {
curvature_rate,
length,
}
}
pub fn evaluate(&self, s: S) -> ClothoidState<S> {
let s = s.clamp_val(S::ZERO, self.length);
let theta = self.curvature_rate * s * s * S::HALF;
let kappa = self.curvature_rate * s;
if self.curvature_rate.abs() < S::EPSILON {
return ClothoidState {
x: s,
y: S::ZERO,
theta: S::ZERO,
kappa: S::ZERO,
};
}
let pi = S::PI;
let a = self.curvature_rate.abs();
let scale = (pi / a).sqrt(); let t = s / scale;
let cx = Self::fresnel_c(t);
let sx = Self::fresnel_s(t);
let sign = if self.curvature_rate >= S::ZERO {
S::ONE
} else {
-S::ONE
};
ClothoidState {
x: scale * cx,
y: scale * sx * sign,
theta,
kappa,
}
}
fn fresnel_c(t: S) -> S {
let pi = S::PI;
let half_pi = pi * S::HALF;
let t2 = t * t;
let t4 = t2 * t2;
let hp2 = half_pi * half_pi;
let term0 = t;
let term1 = -hp2 * t4 * t / S::from_f64(10.0);
let term2 = hp2 * hp2 * t4 * t4 * t / S::from_f64(216.0);
let term3 = -hp2 * hp2 * hp2 * t4 * t4 * t4 * t / S::from_f64(9360.0);
let t17 = t4 * t4 * t4 * t4 * t;
let term4 = hp2 * hp2 * hp2 * hp2 * t17 / S::from_f64(685_440.0);
let t21 = t17 * t4;
let term5 = -hp2 * hp2 * hp2 * hp2 * hp2 * t21 / S::from_f64(76_204_800.0);
let t25 = t21 * t4;
let hp12 = hp2 * hp2 * hp2 * hp2 * hp2 * hp2;
let term6 = hp12 * t25 / S::from_f64(11_975_040_000.0);
term0 + term1 + term2 + term3 + term4 + term5 + term6
}
fn fresnel_s(t: S) -> S {
let pi = S::PI;
let half_pi = pi * S::HALF;
let t2 = t * t;
let t3 = t2 * t;
let t4 = t2 * t2;
let hp2 = half_pi * half_pi;
let term0 = half_pi * t3 / S::from_f64(3.0);
let term1 = -half_pi * hp2 * t4 * t3 / S::from_f64(42.0);
let term2 = half_pi * hp2 * hp2 * t4 * t4 * t3 / S::from_f64(1320.0);
let t15 = t4 * t4 * t4 * t3;
let term3 = -half_pi * hp2 * hp2 * hp2 * t15 / S::from_f64(75_600.0);
let t19 = t15 * t4;
let term4 = half_pi * hp2 * hp2 * hp2 * hp2 * t19 / S::from_f64(6_894_720.0);
let t23 = t19 * t4;
let hp11 = half_pi * hp2 * hp2 * hp2 * hp2 * hp2;
let term5 = -hp11 * t23 / S::from_f64(916_215_040.0);
let t27 = t23 * t4;
let hp13 = hp11 * hp2;
let term6 = hp13 * t27 / S::from_f64(168_129_561_600.0);
term0 + term1 + term2 + term3 + term4 + term5 + term6
}
pub fn end_state(&self) -> ClothoidState<S> {
self.evaluate(self.length)
}
pub fn kappa(&self, s: S) -> S {
self.curvature_rate * s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_curvature_rate_is_straight_line() {
let seg = ClothoidSegment::new(0.0_f64, 5.0);
let state = seg.evaluate(3.0);
assert!((state.x - 3.0).abs() < 1e-6, "x={}", state.x);
assert!(state.y.abs() < 1e-6, "y={}", state.y);
assert!(state.theta.abs() < 1e-6);
}
#[test]
fn curvature_increases_linearly() {
let a = 0.5_f64;
let seg = ClothoidSegment::new(a, 4.0);
assert!((seg.kappa(2.0) - 1.0).abs() < 1e-10);
assert!((seg.kappa(4.0) - 2.0).abs() < 1e-10);
}
#[test]
fn heading_matches_integral_of_curvature() {
let a = 0.4_f64;
let seg = ClothoidSegment::new(a, 3.0);
let state = seg.evaluate(3.0);
let expected_theta = a * 9.0 / 2.0; assert!(
(state.theta - expected_theta).abs() < 1e-10,
"theta={}",
state.theta
);
}
#[test]
fn fresnel_small_t_accuracy() {
let seg = ClothoidSegment::new(1.0_f64, 0.1);
let state = seg.evaluate(0.1);
assert!((state.x - 0.1).abs() < 1e-4, "x={}", state.x);
assert!(state.y.abs() < 1e-3, "y={}", state.y);
}
}