use crate::motion::trajectory::{Trajectory, TrajectoryPoint};
use crate::odom::pose::Pose;
use crate::util::si::Vector2;
#[derive(Debug, Clone)]
pub struct PurePursuit {
trajectory: Trajectory,
lookahead: f64,
}
impl PurePursuit {
#[inline]
pub fn new(trajectory: Trajectory, lookahead: f64) -> Self {
Self {
trajectory,
lookahead,
}
}
#[inline]
pub const fn lookahead(&self) -> f64 {
self.lookahead
}
#[inline]
pub fn set_lookahead(&mut self, lookahead: f64) {
self.lookahead = lookahead;
}
#[inline]
pub const fn trajectory(&self) -> &Trajectory {
&self.trajectory
}
pub fn intersect(&self, pose: Pose) -> Option<TrajectoryPoint> {
let points = self.trajectory.points();
if points.len() < 2 {
return points.first().copied();
}
let center = pose.position();
let radius = self.lookahead;
let mut best: Option<(usize, f64, Vector2<f64>)> = None;
for (index, window) in points.windows(2).enumerate() {
let a_pose = window[0].pose;
let b_pose = window[1].pose;
let a = a_pose.position();
let b = b_pose.position();
let candidates = segment_circle_intersections(a, b, center, radius);
for (t, point) in candidates {
let should_replace = match best {
None => true,
Some((best_index, best_t, _)) => {
index > best_index || (index == best_index && t > best_t)
}
};
if should_replace {
best = Some((index, t, point));
}
}
}
let (index, t, point) = best?;
let segment = &points[index..=index + 1];
let a = segment[0];
let b = segment[1];
Some(TrajectoryPoint::new(
Pose::new(point, a.pose.heading()),
lerp(a.linear_velocity, b.linear_velocity, t),
lerp(a.angular_velocity, b.angular_velocity, t),
a.time + (b.time - a.time) * t,
))
}
pub fn curvature(&self, pose: Pose, target: Vector2<f64>) -> f64 {
let coords = pose.position();
let dx = target.x - coords.x;
let dy = target.y - coords.y;
let heading = pose.heading();
let cos_h = heading.cos();
let sin_h = heading.sin();
let y_r = -sin_h * dx + cos_h * dy;
if self.lookahead == 0.0 {
0.0
} else {
2.0 * y_r / (self.lookahead * self.lookahead)
}
}
}
fn segment_circle_intersections(
a: Vector2<f64>,
b: Vector2<f64>,
center: Vector2<f64>,
radius: f64,
) -> Vec<(f64, Vector2<f64>)> {
let d = b - a;
let f = a - center;
let a_coef = d.dot(d);
let b_coef = 2.0 * f.dot(d);
let c_coef = f.dot(f) - radius * radius;
let discriminant = b_coef * b_coef - 4.0 * a_coef * c_coef;
if discriminant < 0.0 || a_coef == 0.0 {
return Vec::new();
}
let sqrt_disc = libm::sqrt(discriminant);
let t1 = (-b_coef - sqrt_disc) / (2.0 * a_coef);
let t2 = (-b_coef + sqrt_disc) / (2.0 * a_coef);
let mut results = Vec::new();
if (0.0..=1.0).contains(&t1) {
results.push((t1, a + d * t1));
}
if (0.0..=1.0).contains(&t2) && t2 != t1 {
results.push((t2, a + d * t2));
}
results
}
fn lerp(a: f64, b: f64, t: f64) -> f64 {
a + (b - a) * t
}