sophus_spline/
lib.rs

1#![cfg_attr(feature = "simd", feature(portable_simd))]
2#![deny(missing_docs)]
3#![no_std]
4#![allow(clippy::needless_range_loop)]
5#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
6#![cfg_attr(nightly, feature(doc_auto_cfg))]
7
8#[doc = include_str!(concat!("../",  core::env!("CARGO_PKG_README")))]
9#[cfg(doctest)]
10pub struct ReadmeDoctests;
11
12#[cfg(feature = "std")]
13extern crate std;
14
15mod spline_segment;
16
17/// sophus_spline prelude.
18///
19/// It is recommended to import this prelude when working with `sophus_spline types:
20///
21/// ```
22/// use sophus_spline::prelude::*;
23/// ```
24///
25/// or
26///
27/// ```ignore
28/// use sophus::prelude::*;
29/// ```
30///
31/// to import all preludes when using the `sophus` umbrella crate.
32pub mod prelude {
33    pub use sophus_autodiff::prelude::*;
34}
35
36use log::debug;
37use sophus_autodiff::prelude::*;
38pub use spline_segment::*;
39
40extern crate alloc;
41
42/// Cubic B-Spline implementation
43pub struct CubicBSplineImpl<
44    S: IsSingleScalar<DM, DN>,
45    const DIMS: usize,
46    const DM: usize,
47    const DN: usize,
48> {
49    /// Control points
50    pub control_points: alloc::vec::Vec<S::SingleVector<DIMS>>,
51    /// delta between control points
52    pub delta_t: S,
53}
54
55impl<S: IsSingleScalar<DM, DN>, const DIMS: usize, const DM: usize, const DN: usize>
56    CubicBSplineImpl<S, DIMS, DM, DN>
57{
58    /// indices involved
59    pub fn idx_involved(&self, segment_idx: usize) -> alloc::vec::Vec<usize> {
60        let num = self.num_segments();
61        assert!(segment_idx < num);
62
63        let idx_prev = if segment_idx == 0 { 0 } else { segment_idx - 1 };
64        let idx_0 = segment_idx;
65        let idx_1 = segment_idx + 1;
66        let idx_2 = (segment_idx + 2).min(self.control_points.len() - 1);
67        alloc::vec![idx_prev, idx_0, idx_1, idx_2]
68    }
69
70    /// Interpolate
71    pub fn interpolate(&self, segment_idx: usize, u: S) -> S::SingleVector<DIMS> {
72        let num = self.num_segments();
73        assert!(segment_idx < num);
74
75        let case = if segment_idx == 0 {
76            SegmentCase::First
77        } else if segment_idx == num - 1 {
78            SegmentCase::Last
79        } else {
80            SegmentCase::Normal
81        };
82
83        let idx_prev = if segment_idx == 0 { 0 } else { segment_idx - 1 };
84        let idx_0 = segment_idx;
85        let idx_1 = segment_idx + 1;
86        let idx_2 = (segment_idx + 2).min(self.control_points.len() - 1);
87        CubicBSplineSegment::<S, DIMS, DM, DN> {
88            case,
89            control_points: [
90                self.control_points[idx_prev],
91                self.control_points[idx_0],
92                self.control_points[idx_1],
93                self.control_points[idx_2],
94            ],
95        }
96        .interpolate(u)
97    }
98
99    fn num_segments(&self) -> usize {
100        assert!(!self.control_points.is_empty());
101        self.control_points.len() - 1
102    }
103
104    /// derivative of the interpolation
105    pub fn dxi_interpolate(
106        &self,
107        segment_idx: usize,
108        u: S,
109        control_point_idx: usize,
110    ) -> S::SingleMatrix<DIMS, DIMS> {
111        let num = self.num_segments();
112        assert!(segment_idx < num);
113
114        let case = if segment_idx == 0 {
115            SegmentCase::First
116        } else if segment_idx == num - 1 {
117            SegmentCase::Last
118        } else {
119            SegmentCase::Normal
120        };
121
122        let idx_prev = if segment_idx == 0 { 0 } else { segment_idx - 1 };
123        let idx_0 = segment_idx;
124        let idx_1 = segment_idx + 1;
125        let idx_2 = (segment_idx + 2).min(self.control_points.len() - 1);
126        let spline_segment = CubicBSplineSegment::<S, DIMS, DM, DN> {
127            case,
128            control_points: [
129                self.control_points[idx_prev],
130                self.control_points[idx_0],
131                self.control_points[idx_1],
132                self.control_points[idx_2],
133            ],
134        };
135
136        let mut dxi: S::SingleMatrix<DIMS, DIMS> = S::SingleMatrix::<DIMS, DIMS>::zeros();
137        if idx_prev == control_point_idx {
138            dxi = dxi + spline_segment.dxi_interpolate(u, 0);
139        }
140        if idx_0 == control_point_idx {
141            dxi = dxi + spline_segment.dxi_interpolate(u, 1);
142        }
143        if idx_1 == control_point_idx {
144            dxi = dxi + spline_segment.dxi_interpolate(u, 2);
145        }
146        if idx_2 == control_point_idx {
147            dxi = dxi + spline_segment.dxi_interpolate(u, 3);
148        }
149        dxi
150    }
151}
152
153/// Index and u
154#[derive(Clone, Debug, Copy)]
155pub struct IndexAndU<S: IsSingleScalar<DM, DN>, const DM: usize, const DN: usize> {
156    /// segment index
157    pub segment_idx: usize,
158    /// u
159    pub u: S,
160}
161
162/// Cubic B-Spline
163pub struct CubicBSpline<
164    S: IsSingleScalar<DM, DN>,
165    const DIMS: usize,
166    const DM: usize,
167    const DN: usize,
168> {
169    /// Cubic B-Spline implementation
170    pub spline_impl: CubicBSplineImpl<S, DIMS, DM, DN>,
171    /// start time t0
172    pub t0: S,
173}
174
175/// Cubic B-Spline parameters
176#[derive(Clone, Debug, Copy)]
177pub struct CubicBSplineParams<S: IsSingleScalar<DM, DN> + 'static, const DM: usize, const DN: usize>
178{
179    /// delta between control points
180    pub delta_t: S,
181    /// start time t0
182    pub t0: S,
183}
184
185impl<S: IsSingleScalar<DM, DN> + 'static, const DIMS: usize, const DM: usize, const DN: usize>
186    CubicBSpline<S, DIMS, DM, DN>
187{
188    /// create a new cubic B-Spline
189    pub fn new(
190        control_points: alloc::vec::Vec<S::SingleVector<DIMS>>,
191        params: CubicBSplineParams<S, DM, DN>,
192    ) -> Self {
193        Self {
194            spline_impl: CubicBSplineImpl {
195                control_points,
196                delta_t: params.delta_t,
197            },
198            t0: params.t0,
199        }
200    }
201
202    /// interpolate
203    pub fn interpolate(&self, t: S) -> S::SingleVector<DIMS> {
204        let index_and_u = self.index_and_u(t);
205        self.spline_impl
206            .interpolate(index_and_u.segment_idx, index_and_u.u)
207    }
208
209    /// derivative of the interpolation
210    pub fn dxi_interpolate(&self, t: S, control_point_idx: usize) -> S::SingleMatrix<DIMS, DIMS> {
211        let index_and_u = self.index_and_u(t);
212        self.spline_impl
213            .dxi_interpolate(index_and_u.segment_idx, index_and_u.u, control_point_idx)
214    }
215
216    /// indices involved
217    pub fn idx_involved(&self, t: S) -> alloc::vec::Vec<usize> {
218        let index_and_u = self.index_and_u(t);
219        self.spline_impl.idx_involved(index_and_u.segment_idx)
220    }
221
222    /// index and u
223    pub fn index_and_u(&self, t: S) -> IndexAndU<S, DM, DN> {
224        assert!(t.greater_equal(&self.t0).all());
225        assert!(t.less_equal(&self.t_max()).all());
226
227        let normalized_t: S = self.normalized_t(t);
228
229        let mut idx_and_u = IndexAndU::<S, DM, DN> {
230            segment_idx: normalized_t.i64_floor() as usize,
231            u: normalized_t.clone().fract(),
232        };
233        debug!("{idx_and_u:?}");
234
235        let eps = 0.00001;
236
237        if idx_and_u.u.single_real_scalar() > eps {
238            debug!("case A");
239            return idx_and_u;
240        }
241
242        // i < N/2
243        if idx_and_u.segment_idx < self.num_segments() / 2 {
244            debug!("case B");
245            return idx_and_u;
246        }
247
248        debug!("case C");
249
250        idx_and_u.segment_idx -= 1;
251        idx_and_u.u += S::from_f64(1.0);
252
253        idx_and_u
254    }
255
256    /// normalized between [0, N]
257    pub fn normalized_t(&self, t: S) -> S {
258        (t - self.t0) / self.spline_impl.delta_t
259    }
260
261    /// number of segments
262    pub fn num_segments(&self) -> usize {
263        self.spline_impl.num_segments()
264    }
265
266    /// t_max
267    pub fn t_max(&self) -> S {
268        self.t0 + S::from_f64(self.num_segments() as f64) * self.spline_impl.delta_t
269    }
270}
271
272#[test]
273fn test_pline() {
274    use log::info;
275    use sophus_autodiff::points::example_points;
276
277    let points = example_points::<f64, 2, 1, 0, 0>();
278    for (t0, delta_t) in [(0.0, 1.0)] {
279        let params = CubicBSplineParams { delta_t, t0 };
280
281        let mut points = points.clone();
282        let spline = CubicBSpline::new(points.clone(), params);
283        points.reverse();
284        let rspline = CubicBSpline::new(points, params);
285
286        info!("tmax: {}", spline.t_max());
287
288        let mut t = t0;
289
290        loop {
291            if t >= spline.t_max() {
292                break;
293            }
294
295            info!("t {t}");
296            info!("{:?}", spline.idx_involved(t));
297
298            info!("i: {}", spline.interpolate(t));
299            info!("i': {}", rspline.interpolate(spline.t_max() - t));
300
301            for i in 0..spline.num_segments() {
302                info!("dx: {} {}", i, spline.dxi_interpolate(t, i));
303                info!(
304                    "dx': {} {}",
305                    i,
306                    rspline.dxi_interpolate(spline.t_max() - t, spline.num_segments() - i)
307                );
308            }
309            t += 0.1;
310        }
311
312        info!("{:?}", spline.idx_involved(1.01));
313    }
314}