1use deke_types::glam::{DAffine3, DQuat, DVec3};
13use glam::Vec3;
14use squiggle::{Segmented, Spline};
15
16use crate::constraints::PathConditioning;
17use crate::error::LinearError;
18
19#[derive(Clone, Debug)]
21pub struct CartesianRun {
22 spline: Spline,
23 ts: Vec<f32>,
24 ss: Vec<f32>,
25 vtx_s: Vec<f64>,
26 vtx_q: Vec<DQuat>,
27 length: f64,
28}
29
30impl CartesianRun {
31 pub fn length(&self) -> f64 {
33 self.length
34 }
35
36 pub fn eval(&self, s: f64) -> DAffine3 {
38 let s = s.clamp(0.0, self.length);
39 let t = self.t_at_s(s as f32);
40 let p = self.spline.point(t);
41 let pos = DVec3::new(p.x as f64, p.y as f64, p.z as f64);
42 let rot = self.orientation_at(s);
43 DAffine3::from_rotation_translation(rot, pos)
44 }
45
46 fn t_at_s(&self, s: f32) -> f32 {
47 let s = s.clamp(0.0, *self.ss.last().unwrap_or(&0.0));
48 match self.ss.binary_search_by(|v| v.partial_cmp(&s).unwrap()) {
49 Ok(i) => self.ts[i],
50 Err(i) => {
51 if i == 0 {
52 return self.ts[0];
53 }
54 if i >= self.ss.len() {
55 return *self.ts.last().unwrap();
56 }
57 let (s0, s1) = (self.ss[i - 1], self.ss[i]);
58 let (t0, t1) = (self.ts[i - 1], self.ts[i]);
59 let f = if s1 > s0 { (s - s0) / (s1 - s0) } else { 0.0 };
60 t0 + (t1 - t0) * f
61 }
62 }
63 }
64
65 fn orientation_at(&self, s: f64) -> DQuat {
66 if self.vtx_q.len() == 1 {
67 return self.vtx_q[0];
68 }
69 let mut k = 0usize;
70 while k + 2 < self.vtx_s.len() && s > self.vtx_s[k + 1] {
71 k += 1;
72 }
73 let (s0, s1) = (self.vtx_s[k], self.vtx_s[k + 1]);
74 let f = if s1 > s0 {
75 ((s - s0) / (s1 - s0)).clamp(0.0, 1.0)
76 } else {
77 0.0
78 };
79 self.vtx_q[k].slerp(self.vtx_q[k + 1], f)
80 }
81}
82
83pub fn condition(
85 poses: &[DAffine3],
86 cfg: &PathConditioning,
87) -> Result<Vec<CartesianRun>, LinearError> {
88 if poses.len() < 2 {
89 return Err(LinearError::TooFewPoses(poses.len()));
90 }
91
92 let p: Vec<DVec3> = poses.iter().map(|t| t.translation).collect();
93
94 let mut bounds = vec![0usize];
95 for i in 1..poses.len() - 1 {
96 if turn_angle(&p, i).is_some_and(|a| a > cfg.sharp_corner_angle) {
97 bounds.push(i);
98 }
99 }
100 bounds.push(poses.len() - 1);
101
102 let mut runs = Vec::with_capacity(bounds.len() - 1);
103 for (run_idx, w) in bounds.windows(2).enumerate() {
104 runs.push(build_run(&poses[w[0]..=w[1]], run_idx)?);
105 }
106 Ok(runs)
107}
108
109fn turn_angle(p: &[DVec3], i: usize) -> Option<f64> {
110 let a = (p[i] - p[i - 1]).normalize_or_zero();
111 let b = (p[i + 1] - p[i]).normalize_or_zero();
112 if a == DVec3::ZERO || b == DVec3::ZERO {
113 return None;
114 }
115 Some(a.dot(b).clamp(-1.0, 1.0).acos())
116}
117
118fn build_run(poses: &[DAffine3], run_idx: usize) -> Result<CartesianRun, LinearError> {
119 let mut keep: Vec<usize> = vec![0];
121 for i in 1..poses.len() {
122 let prev = poses[*keep.last().unwrap()].translation;
123 if poses[i].translation.distance(prev) > 1e-9 {
124 keep.push(i);
125 }
126 }
127 if keep.len() < 2 {
128 return Err(LinearError::DegenerateRun { run: run_idx });
129 }
130
131 let knots: Vec<Vec3> = keep
132 .iter()
133 .map(|&i| {
134 let t = poses[i].translation;
135 Vec3::new(t.x as f32, t.y as f32, t.z as f32)
136 })
137 .collect();
138 let spline = Spline::new(knots.clone());
139
140 let mut vtx_q: Vec<DQuat> = keep
141 .iter()
142 .map(|&i| DQuat::from_mat3(&poses[i].matrix3).normalize())
143 .collect();
144 for k in 1..vtx_q.len() {
146 if vtx_q[k - 1].dot(vtx_q[k]) < 0.0 {
147 vtx_q[k] = -vtx_q[k];
148 }
149 }
150
151 let seg = spline.segment_count().max(1);
152 let m = (seg * 24).max(64);
153 let mut ts = Vec::with_capacity(m + 1);
154 let mut ss = Vec::with_capacity(m + 1);
155 let mut prev = spline.point(0.0);
156 let mut acc = 0.0f32;
157 ts.push(0.0);
158 ss.push(0.0);
159 for i in 1..=m {
160 let t = i as f32 / m as f32;
161 let pt = spline.point(t);
162 acc += (pt - prev).length();
163 prev = pt;
164 ts.push(t);
165 ss.push(acc);
166 }
167 let length = acc as f64;
168 if length < 1e-9 {
169 return Err(LinearError::DegenerateRun { run: run_idx });
170 }
171
172 let mut vtx_s: Vec<f64> = (0..knots.len())
174 .map(|i| {
175 let t = i as f32 / seg as f32;
176 s_at_t(&ts, &ss, t) as f64
177 })
178 .collect();
179 vtx_s[0] = 0.0;
180 *vtx_s.last_mut().unwrap() = length;
181
182 Ok(CartesianRun {
183 spline,
184 ts,
185 ss,
186 vtx_s,
187 vtx_q,
188 length,
189 })
190}
191
192fn s_at_t(ts: &[f32], ss: &[f32], t: f32) -> f32 {
193 let t = t.clamp(0.0, 1.0);
194 match ts.binary_search_by(|v| v.partial_cmp(&t).unwrap()) {
195 Ok(i) => ss[i],
196 Err(i) => {
197 if i == 0 {
198 return ss[0];
199 }
200 if i >= ts.len() {
201 return *ss.last().unwrap();
202 }
203 let (t0, t1) = (ts[i - 1], ts[i]);
204 let (s0, s1) = (ss[i - 1], ss[i]);
205 let f = if t1 > t0 { (t - t0) / (t1 - t0) } else { 0.0 };
206 s0 + (s1 - s0) * f
207 }
208 }
209}