Skip to main content

deke_types/
traj.rs

1use std::time::Duration;
2
3use num_traits::Float;
4
5use crate::{DekeError, DekeResult, RobotPath, SRobotPath, SRobotQ};
6
7/// Dynamically-sized robot trajectory: a dense [`RobotPath`] sampled uniformly at `dt`.
8#[derive(Debug, Clone)]
9pub struct RobotTraj<T: Float = f32> {
10    dt: Duration,
11    path: RobotPath<T>,
12}
13
14impl<T: Float> RobotTraj<T> {
15    pub fn new(dt: Duration, path: RobotPath<T>) -> Self {
16        Self { dt, path }
17    }
18
19    pub fn dt(&self) -> Duration {
20        self.dt
21    }
22
23    pub fn path(&self) -> &RobotPath<T> {
24        &self.path
25    }
26
27    pub fn path_mut(&mut self) -> &mut RobotPath<T> {
28        &mut self.path
29    }
30
31    pub fn into_path(self) -> RobotPath<T> {
32        self.path
33    }
34
35    pub fn len(&self) -> usize {
36        self.path.nrows()
37    }
38
39    pub fn is_empty(&self) -> bool {
40        self.path.nrows() == 0
41    }
42
43    pub fn dof(&self) -> usize {
44        self.path.ncols()
45    }
46
47    pub fn duration(&self) -> Duration {
48        self.dt
49            .saturating_mul(self.len().saturating_sub(1) as u32)
50    }
51
52    pub fn time_at(&self, index: usize) -> Duration {
53        self.dt.saturating_mul(index as u32)
54    }
55}
56
57/// Statically-sized robot trajectory: an [`SRobotPath`] sampled uniformly at `dt`.
58#[derive(Debug, Clone)]
59pub struct SRobotTraj<const N: usize, T: Float = f32> {
60    dt: Duration,
61    path: SRobotPath<N, T>,
62}
63
64impl<const N: usize, T: Float> SRobotTraj<N, T> {
65    pub fn new(dt: Duration, path: SRobotPath<N, T>) -> Self {
66        Self { dt, path }
67    }
68
69    pub fn try_from_waypoints(dt: Duration, waypoints: Vec<SRobotQ<N, T>>) -> DekeResult<Self> {
70        Ok(Self {
71            dt,
72            path: SRobotPath::try_new(waypoints)?,
73        })
74    }
75
76    pub fn dt(&self) -> Duration {
77        self.dt
78    }
79
80    pub fn set_dt(&mut self, dt: Duration) {
81        self.dt = dt;
82    }
83
84    pub fn path(&self) -> &SRobotPath<N, T> {
85        &self.path
86    }
87
88    pub fn path_mut(&mut self) -> &mut SRobotPath<N, T> {
89        &mut self.path
90    }
91
92    pub fn into_path(self) -> SRobotPath<N, T> {
93        self.path
94    }
95
96    pub fn len(&self) -> usize {
97        self.path.len()
98    }
99
100    pub fn is_empty(&self) -> bool {
101        self.len() == 0
102    }
103
104    pub fn dof(&self) -> usize {
105        N
106    }
107
108    pub fn first(&self) -> &SRobotQ<N, T> {
109        self.path.first()
110    }
111
112    pub fn last(&self) -> &SRobotQ<N, T> {
113        self.path.last()
114    }
115
116    pub fn get(&self, index: usize) -> Option<&SRobotQ<N, T>> {
117        self.path.get(index)
118    }
119
120    pub fn duration(&self) -> Duration {
121        self.dt
122            .saturating_mul(self.len().saturating_sub(1) as u32)
123    }
124
125    pub fn time_at(&self, index: usize) -> Duration {
126        self.dt.saturating_mul(index as u32)
127    }
128
129    pub fn iter(&self) -> std::slice::Iter<'_, SRobotQ<N, T>> {
130        self.path.iter()
131    }
132
133    pub fn iter_timed(&self) -> impl Iterator<Item = (Duration, &SRobotQ<N, T>)> + '_ {
134        let dt = self.dt;
135        self.path
136            .iter()
137            .enumerate()
138            .map(move |(i, q)| (dt.saturating_mul(i as u32), q))
139    }
140
141    /// Samples the trajectory at a given wall-clock time via linear interpolation between
142    /// adjacent waypoints. Times outside `[0, duration()]` are clamped.
143    pub fn sample_at_time(&self, t: Duration) -> Option<SRobotQ<N, T>> {
144        let n = self.len();
145        if n == 0 {
146            return None;
147        }
148        if n == 1 {
149            return Some(*self.first());
150        }
151
152        let dt_secs = self.dt.as_secs_f64();
153        if dt_secs <= 0.0 {
154            return Some(*self.first());
155        }
156
157        let t_secs = t.as_secs_f64();
158        let max_secs = dt_secs * (n - 1) as f64;
159        let clamped = t_secs.clamp(0.0, max_secs);
160
161        let f = clamped / dt_secs;
162        let i = (f.floor() as usize).min(n - 2);
163        let local = T::from(f - i as f64).unwrap_or_else(T::zero);
164
165        let a = self.path.get(i)?;
166        let b = self.path.get(i + 1)?;
167        Some(a.interpolate(b, local))
168    }
169
170    /// Finite-difference velocity at sample index `i`, in joint-units per second.
171    /// Uses forward difference at the start, backward at the end, central elsewhere.
172    pub fn velocity_at(&self, i: usize) -> Option<SRobotQ<N, T>> {
173        let n = self.len();
174        if n < 2 || i >= n {
175            return None;
176        }
177        let dt_secs = T::from(self.dt.as_secs_f64()).unwrap_or_else(T::zero);
178        if dt_secs == T::zero() {
179            return Some(SRobotQ::zeros());
180        }
181
182        let (a, b, span) = if i == 0 {
183            (*self.path.get(0)?, *self.path.get(1)?, T::one())
184        } else if i == n - 1 {
185            (*self.path.get(n - 2)?, *self.path.get(n - 1)?, T::one())
186        } else {
187            (
188                *self.path.get(i - 1)?,
189                *self.path.get(i + 1)?,
190                T::from(2.0).unwrap_or_else(T::one),
191            )
192        };
193
194        Some((b - a) * (T::one() / (span * dt_secs)))
195    }
196
197    /// Finite-difference acceleration at sample index `i`, in joint-units per second^2.
198    pub fn acceleration_at(&self, i: usize) -> Option<SRobotQ<N, T>> {
199        let n = self.len();
200        if n < 3 || i >= n {
201            return None;
202        }
203        let dt_secs = T::from(self.dt.as_secs_f64()).unwrap_or_else(T::zero);
204        if dt_secs == T::zero() {
205            return Some(SRobotQ::zeros());
206        }
207
208        let idx = i.clamp(1, n - 2);
209        let a = *self.path.get(idx - 1)?;
210        let b = *self.path.get(idx)?;
211        let c = *self.path.get(idx + 1)?;
212        let inv_dt_sq = T::one() / (dt_secs * dt_secs);
213        Some((a - b * T::from(2.0).unwrap_or_else(T::one) + c) * inv_dt_sq)
214    }
215
216    pub fn max_joint_velocity(&self) -> T {
217        let mut peak = T::zero();
218        for i in 0..self.len() {
219            if let Some(v) = self.velocity_at(i) {
220                peak = peak.max(v.linf_norm());
221            }
222        }
223        peak
224    }
225
226    pub fn max_joint_acceleration(&self) -> T {
227        let mut peak = T::zero();
228        for i in 0..self.len() {
229            if let Some(a) = self.acceleration_at(i) {
230                peak = peak.max(a.linf_norm());
231            }
232        }
233        peak
234    }
235
236    /// Reverses the trajectory in place (swaps start and end, preserves `dt`).
237    pub fn reverse(&mut self) {
238        self.path.reverse();
239    }
240
241    pub fn reversed(&self) -> Self {
242        Self {
243            dt: self.dt,
244            path: self.path.reversed(),
245        }
246    }
247
248    /// Rescales the trajectory by changing `dt`. `scale > 1.0` slows it down; `< 1.0` speeds it up.
249    pub fn rescale_time(&mut self, scale: f64) {
250        let new_secs = self.dt.as_secs_f64() * scale.max(0.0);
251        self.dt = Duration::from_secs_f64(new_secs);
252    }
253
254    pub fn to_robot_traj(&self) -> RobotTraj<T> {
255        RobotTraj {
256            dt: self.dt,
257            path: self.path.to_robot_path(),
258        }
259    }
260}
261
262impl<const N: usize, T: Float> std::ops::Index<usize> for SRobotTraj<N, T> {
263    type Output = SRobotQ<N, T>;
264    #[inline]
265    fn index(&self, i: usize) -> &SRobotQ<N, T> {
266        &self.path[i]
267    }
268}
269
270impl<'a, const N: usize, T: Float> IntoIterator for &'a SRobotTraj<N, T> {
271    type Item = &'a SRobotQ<N, T>;
272    type IntoIter = std::slice::Iter<'a, SRobotQ<N, T>>;
273
274    fn into_iter(self) -> Self::IntoIter {
275        self.path.iter()
276    }
277}
278
279impl<const N: usize, T: Float> AsRef<SRobotPath<N, T>> for SRobotTraj<N, T> {
280    fn as_ref(&self) -> &SRobotPath<N, T> {
281        &self.path
282    }
283}
284
285impl<const N: usize, T: Float> From<SRobotTraj<N, T>> for SRobotPath<N, T> {
286    fn from(traj: SRobotTraj<N, T>) -> Self {
287        traj.path
288    }
289}
290
291impl<const N: usize, T: Float> From<SRobotTraj<N, T>> for RobotTraj<T> {
292    fn from(traj: SRobotTraj<N, T>) -> Self {
293        traj.to_robot_traj()
294    }
295}
296
297impl<const N: usize, T: Float> TryFrom<RobotTraj<T>> for SRobotTraj<N, T> {
298    type Error = DekeError;
299
300    fn try_from(traj: RobotTraj<T>) -> Result<Self, Self::Error> {
301        Ok(Self {
302            dt: traj.dt,
303            path: SRobotPath::<N, T>::try_from(traj.path)?,
304        })
305    }
306}