use deke_types::glam::{DAffine3, DQuat, DVec3};
use glam::Vec3;
use squiggle::{Segmented, Spline};
use crate::constraints::PathConditioning;
use crate::error::LinearError;
#[derive(Clone, Debug)]
pub struct CartesianRun {
spline: Spline,
ts: Vec<f32>,
ss: Vec<f32>,
vtx_s: Vec<f64>,
vtx_q: Vec<DQuat>,
length: f64,
}
impl CartesianRun {
pub fn length(&self) -> f64 {
self.length
}
pub fn eval(&self, s: f64) -> DAffine3 {
let s = s.clamp(0.0, self.length);
let t = self.t_at_s(s as f32);
let p = self.spline.point(t);
let pos = DVec3::new(p.x as f64, p.y as f64, p.z as f64);
let rot = self.orientation_at(s);
DAffine3::from_rotation_translation(rot, pos)
}
fn t_at_s(&self, s: f32) -> f32 {
let s = s.clamp(0.0, *self.ss.last().unwrap_or(&0.0));
match self.ss.binary_search_by(|v| v.partial_cmp(&s).unwrap()) {
Ok(i) => self.ts[i],
Err(i) => {
if i == 0 {
return self.ts[0];
}
if i >= self.ss.len() {
return *self.ts.last().unwrap();
}
let (s0, s1) = (self.ss[i - 1], self.ss[i]);
let (t0, t1) = (self.ts[i - 1], self.ts[i]);
let f = if s1 > s0 { (s - s0) / (s1 - s0) } else { 0.0 };
t0 + (t1 - t0) * f
}
}
}
fn orientation_at(&self, s: f64) -> DQuat {
if self.vtx_q.len() == 1 {
return self.vtx_q[0];
}
let mut k = 0usize;
while k + 2 < self.vtx_s.len() && s > self.vtx_s[k + 1] {
k += 1;
}
let (s0, s1) = (self.vtx_s[k], self.vtx_s[k + 1]);
let f = if s1 > s0 {
((s - s0) / (s1 - s0)).clamp(0.0, 1.0)
} else {
0.0
};
self.vtx_q[k].slerp(self.vtx_q[k + 1], f)
}
}
pub fn condition(
poses: &[DAffine3],
cfg: &PathConditioning,
) -> Result<Vec<CartesianRun>, LinearError> {
if poses.len() < 2 {
return Err(LinearError::TooFewPoses(poses.len()));
}
let p: Vec<DVec3> = poses.iter().map(|t| t.translation).collect();
let mut bounds = vec![0usize];
for i in 1..poses.len() - 1 {
if turn_angle(&p, i).is_some_and(|a| a > cfg.sharp_corner_angle) {
bounds.push(i);
}
}
bounds.push(poses.len() - 1);
let mut runs = Vec::with_capacity(bounds.len() - 1);
for (run_idx, w) in bounds.windows(2).enumerate() {
runs.push(build_run(&poses[w[0]..=w[1]], run_idx)?);
}
Ok(runs)
}
fn turn_angle(p: &[DVec3], i: usize) -> Option<f64> {
let a = (p[i] - p[i - 1]).normalize_or_zero();
let b = (p[i + 1] - p[i]).normalize_or_zero();
if a == DVec3::ZERO || b == DVec3::ZERO {
return None;
}
Some(a.dot(b).clamp(-1.0, 1.0).acos())
}
fn build_run(poses: &[DAffine3], run_idx: usize) -> Result<CartesianRun, LinearError> {
let mut keep: Vec<usize> = vec![0];
for i in 1..poses.len() {
let prev = poses[*keep.last().unwrap()].translation;
if poses[i].translation.distance(prev) > 1e-9 {
keep.push(i);
}
}
if keep.len() < 2 {
return Err(LinearError::DegenerateRun { run: run_idx });
}
let knots: Vec<Vec3> = keep
.iter()
.map(|&i| {
let t = poses[i].translation;
Vec3::new(t.x as f32, t.y as f32, t.z as f32)
})
.collect();
let spline = Spline::new(knots.clone());
let mut vtx_q: Vec<DQuat> = keep
.iter()
.map(|&i| DQuat::from_mat3(&poses[i].matrix3).normalize())
.collect();
for k in 1..vtx_q.len() {
if vtx_q[k - 1].dot(vtx_q[k]) < 0.0 {
vtx_q[k] = -vtx_q[k];
}
}
let seg = spline.segment_count().max(1);
let m = (seg * 24).max(64);
let mut ts = Vec::with_capacity(m + 1);
let mut ss = Vec::with_capacity(m + 1);
let mut prev = spline.point(0.0);
let mut acc = 0.0f32;
ts.push(0.0);
ss.push(0.0);
for i in 1..=m {
let t = i as f32 / m as f32;
let pt = spline.point(t);
acc += (pt - prev).length();
prev = pt;
ts.push(t);
ss.push(acc);
}
let length = acc as f64;
if length < 1e-9 {
return Err(LinearError::DegenerateRun { run: run_idx });
}
let mut vtx_s: Vec<f64> = (0..knots.len())
.map(|i| {
let t = i as f32 / seg as f32;
s_at_t(&ts, &ss, t) as f64
})
.collect();
vtx_s[0] = 0.0;
*vtx_s.last_mut().unwrap() = length;
Ok(CartesianRun {
spline,
ts,
ss,
vtx_s,
vtx_q,
length,
})
}
fn s_at_t(ts: &[f32], ss: &[f32], t: f32) -> f32 {
let t = t.clamp(0.0, 1.0);
match ts.binary_search_by(|v| v.partial_cmp(&t).unwrap()) {
Ok(i) => ss[i],
Err(i) => {
if i == 0 {
return ss[0];
}
if i >= ts.len() {
return *ss.last().unwrap();
}
let (t0, t1) = (ts[i - 1], ts[i]);
let (s0, s1) = (ss[i - 1], ss[i]);
let f = if t1 > t0 { (t - t0) / (t1 - t0) } else { 0.0 };
s0 + (s1 - s0) * f
}
}
}