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
17pub 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
42pub struct CubicBSplineImpl<
44 S: IsSingleScalar<DM, DN>,
45 const DIMS: usize,
46 const DM: usize,
47 const DN: usize,
48> {
49 pub control_points: alloc::vec::Vec<S::SingleVector<DIMS>>,
51 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 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 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 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#[derive(Clone, Debug, Copy)]
155pub struct IndexAndU<S: IsSingleScalar<DM, DN>, const DM: usize, const DN: usize> {
156 pub segment_idx: usize,
158 pub u: S,
160}
161
162pub struct CubicBSpline<
164 S: IsSingleScalar<DM, DN>,
165 const DIMS: usize,
166 const DM: usize,
167 const DN: usize,
168> {
169 pub spline_impl: CubicBSplineImpl<S, DIMS, DM, DN>,
171 pub t0: S,
173}
174
175#[derive(Clone, Debug, Copy)]
177pub struct CubicBSplineParams<S: IsSingleScalar<DM, DN> + 'static, const DM: usize, const DN: usize>
178{
179 pub delta_t: S,
181 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 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 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 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 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 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 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 pub fn normalized_t(&self, t: S) -> S {
258 (t - self.t0) / self.spline_impl.delta_t
259 }
260
261 pub fn num_segments(&self) -> usize {
263 self.spline_impl.num_segments()
264 }
265
266 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}