Skip to main content

deke_types/
path.rs

1use crate::q::RobotQ;
2use crate::{DekeError, DekeResult};
3
4/// An ordered sequence of joint configurations representing a robot path.
5#[derive(Debug, Clone)]
6pub struct RobotPath {
7    waypoints: Vec<RobotQ>,
8}
9
10impl RobotPath {
11    pub fn new(waypoints: Vec<RobotQ>) -> DekeResult<Self> {
12        if let Some(first) = waypoints.first() {
13            let n = first.len();
14            for q in waypoints.iter().skip(1) {
15                if q.len() != n {
16                    return Err(DekeError::ShapeMismatch {
17                        expected: n,
18                        found: q.len(),
19                    });
20                }
21            }
22        }
23        Ok(Self { waypoints })
24    }
25
26    pub fn empty() -> Self {
27        Self {
28            waypoints: Vec::new(),
29        }
30    }
31
32    pub fn with_capacity(cap: usize) -> Self {
33        Self {
34            waypoints: Vec::with_capacity(cap),
35        }
36    }
37
38    pub fn len(&self) -> usize {
39        self.waypoints.len()
40    }
41
42    pub fn is_empty(&self) -> bool {
43        self.waypoints.is_empty()
44    }
45
46    /// Number of joints per waypoint, or 0 if the path is empty.
47    pub fn ndof(&self) -> usize {
48        self.waypoints.first().map_or(0, |q| q.len())
49    }
50
51    pub fn get(&self, index: usize) -> Option<&RobotQ> {
52        self.waypoints.get(index)
53    }
54
55    pub fn first(&self) -> Option<&RobotQ> {
56        self.waypoints.first()
57    }
58
59    pub fn last(&self) -> Option<&RobotQ> {
60        self.waypoints.last()
61    }
62
63    pub fn iter(&self) -> std::slice::Iter<'_, RobotQ> {
64        self.waypoints.iter()
65    }
66
67    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, RobotQ> {
68        self.waypoints.iter_mut()
69    }
70
71    /// Iterator over consecutive pairs of waypoints.
72    pub fn segments(&self) -> impl Iterator<Item = (&RobotQ, &RobotQ)> {
73        self.waypoints.windows(2).map(|w| (&w[0], &w[1]))
74    }
75
76    pub fn push(&mut self, q: RobotQ) -> DekeResult<()> {
77        if let Some(first) = self.waypoints.first() {
78            if q.len() != first.len() {
79                return Err(DekeError::ShapeMismatch {
80                    expected: first.len(),
81                    found: q.len(),
82                });
83            }
84        }
85        self.waypoints.push(q);
86        Ok(())
87    }
88
89    pub fn pop(&mut self) -> Option<RobotQ> {
90        self.waypoints.pop()
91    }
92
93    pub fn clear(&mut self) {
94        self.waypoints.clear();
95    }
96
97    pub fn truncate(&mut self, len: usize) {
98        self.waypoints.truncate(len);
99    }
100
101    pub fn reverse(&mut self) {
102        self.waypoints.reverse();
103    }
104
105    pub fn reversed(&self) -> Self {
106        let mut wps = self.waypoints.clone();
107        wps.reverse();
108        Self { waypoints: wps }
109    }
110
111    /// Total arc length (sum of Euclidean distances between consecutive waypoints).
112    pub fn arc_length(&self) -> f32 {
113        self.segments().map(|(a, b)| q_distance(a, b)).sum()
114    }
115
116    /// Euclidean distance of each segment.
117    pub fn segment_lengths(&self) -> Vec<f32> {
118        self.segments().map(|(a, b)| q_distance(a, b)).collect()
119    }
120
121    /// Cumulative arc length at each waypoint, starting at 0.0.
122    pub fn cumulative_lengths(&self) -> Vec<f32> {
123        let mut cum = Vec::with_capacity(self.len());
124        let mut total = 0.0;
125        cum.push(0.0);
126        for (a, b) in self.segments() {
127            total += q_distance(a, b);
128            cum.push(total);
129        }
130        cum
131    }
132
133    /// Maximum Euclidean distance between any two consecutive waypoints.
134    pub fn max_segment_length(&self) -> f32 {
135        self.segments()
136            .map(|(a, b)| q_distance(a, b))
137            .fold(0.0, f32::max)
138    }
139
140    /// Maximum per-joint deviation (L-inf norm) across any segment.
141    pub fn max_joint_step(&self) -> f32 {
142        self.segments()
143            .map(|(a, b)| q_linf_distance(a, b))
144            .fold(0.0, f32::max)
145    }
146
147    /// Linearly interpolate along the path by normalized parameter `t` in `[0, 1]`.
148    /// Returns `None` if the path has fewer than 2 waypoints.
149    pub fn sample(&self, t: f32) -> Option<RobotQ> {
150        let n = self.len();
151        if n < 2 {
152            return if n == 1 {
153                Some(self.waypoints[0].clone())
154            } else {
155                None
156            };
157        }
158
159        let t = t.clamp(0.0, 1.0);
160        let total = self.arc_length();
161        if total == 0.0 {
162            return Some(self.waypoints[0].clone());
163        }
164
165        let target = t * total;
166        let mut accumulated = 0.0;
167
168        for (a, b) in self.segments() {
169            let seg_len = q_distance(a, b);
170            if accumulated + seg_len >= target {
171                let local_t = if seg_len > 0.0 {
172                    (target - accumulated) / seg_len
173                } else {
174                    0.0
175                };
176                return Some(a + &((b - a) * local_t));
177            }
178            accumulated += seg_len;
179        }
180
181        Some(self.waypoints.last().unwrap().clone())
182    }
183
184    /// Subdivide every segment so that no segment exceeds `max_dist`.
185    pub fn densify(&self, max_dist: f32) -> Self {
186        if self.len() < 2 {
187            return self.clone();
188        }
189
190        let mut out = Vec::new();
191        out.push(self.waypoints[0].clone());
192
193        for (a, b) in self.segments() {
194            let d = q_distance(a, b);
195            let steps = (d / max_dist).ceil() as usize;
196            let steps = steps.max(1);
197            for i in 1..=steps {
198                let t = i as f32 / steps as f32;
199                out.push(a + &((b - a) * t));
200            }
201        }
202
203        Self { waypoints: out }
204    }
205
206    /// Remove waypoints that are within `tol` of the line between their neighbors
207    /// (Ramer-Douglas-Peucker in joint space).
208    pub fn simplify(&self, tol: f32) -> Self {
209        if self.len() <= 2 {
210            return self.clone();
211        }
212
213        let mut keep = vec![true; self.len()];
214        rdp_mark(&self.waypoints, 0, self.len() - 1, tol, &mut keep);
215
216        let waypoints = self
217            .waypoints
218            .iter()
219            .enumerate()
220            .filter(|(i, _)| keep[*i])
221            .map(|(_, q)| q.clone())
222            .collect();
223
224        Self { waypoints }
225    }
226
227    /// Apply a function to every waypoint.
228    pub fn map_waypoints(&self, f: impl Fn(&RobotQ) -> RobotQ) -> Self {
229        Self {
230            waypoints: self.waypoints.iter().map(f).collect(),
231        }
232    }
233}
234
235impl std::ops::Index<usize> for RobotPath {
236    type Output = RobotQ;
237    #[inline]
238    fn index(&self, i: usize) -> &RobotQ {
239        &self.waypoints[i]
240    }
241}
242
243impl std::ops::IndexMut<usize> for RobotPath {
244    #[inline]
245    fn index_mut(&mut self, i: usize) -> &mut RobotQ {
246        &mut self.waypoints[i]
247    }
248}
249
250impl FromIterator<RobotQ> for RobotPath {
251    fn from_iter<I: IntoIterator<Item = RobotQ>>(iter: I) -> Self {
252        Self {
253            waypoints: iter.into_iter().collect(),
254        }
255    }
256}
257
258impl IntoIterator for RobotPath {
259    type Item = RobotQ;
260    type IntoIter = std::vec::IntoIter<RobotQ>;
261
262    fn into_iter(self) -> Self::IntoIter {
263        self.waypoints.into_iter()
264    }
265}
266
267impl<'a> IntoIterator for &'a RobotPath {
268    type Item = &'a RobotQ;
269    type IntoIter = std::slice::Iter<'a, RobotQ>;
270
271    fn into_iter(self) -> Self::IntoIter {
272        self.waypoints.iter()
273    }
274}
275
276fn q_distance(a: &RobotQ, b: &RobotQ) -> f32 {
277    let diff = b - a;
278    diff.mapv(|x| x * x).sum().sqrt()
279}
280
281fn q_linf_distance(a: &RobotQ, b: &RobotQ) -> f32 {
282    let diff = b - a;
283    diff.mapv(f32::abs).into_iter().fold(0.0, f32::max)
284}
285
286fn rdp_mark(pts: &[RobotQ], start: usize, end: usize, tol: f32, keep: &mut [bool]) {
287    if end <= start + 1 {
288        return;
289    }
290
291    let seg = &pts[end] - &pts[start];
292    let seg_len_sq: f32 = seg.mapv(|x| x * x).sum();
293
294    let mut max_dist = 0.0f32;
295    let mut max_idx = start;
296
297    for i in (start + 1)..end {
298        let v = &pts[i] - &pts[start];
299        let dist = if seg_len_sq < 1e-30 {
300            v.mapv(|x| x * x).sum().sqrt()
301        } else {
302            let t = (v.dot(&seg) / seg_len_sq).clamp(0.0, 1.0);
303            let proj = &v - &(&seg * t);
304            proj.mapv(|x| x * x).sum().sqrt()
305        };
306        if dist > max_dist {
307            max_dist = dist;
308            max_idx = i;
309        }
310    }
311
312    if max_dist > tol {
313        keep[max_idx] = true;
314        rdp_mark(pts, start, max_idx, tol, keep);
315        rdp_mark(pts, max_idx, end, tol, keep);
316    } else {
317        for k in (start + 1)..end {
318            keep[k] = false;
319        }
320    }
321}