scirs2_spatial/transform/
rotation_spline.rs

1//! RotationSpline for smooth interpolation between multiple rotations
2//!
3//! This module provides a `RotationSpline` class that allows for smooth interpolation
4//! between multiple rotations, creating a continuous curve in rotation space.
5
6use crate::error::{SpatialError, SpatialResult};
7use crate::transform::{Rotation, Slerp};
8use scirs2_core::ndarray::{array, Array1};
9use scirs2_core::numeric::{Float, FromPrimitive};
10
11/// Helper to convert f64 constants to generic Float type
12#[inline(always)]
13fn const_f64<F: Float + FromPrimitive>(value: f64) -> F {
14    F::from(value).expect("Failed to convert constant to target float type")
15}
16
17// Helper function to create an array from values
18#[allow(dead_code)]
19fn euler_array(x: f64, y: f64, z: f64) -> Array1<f64> {
20    array![x, y, z]
21}
22
23// Helper function to create a rotation from Euler angles
24#[allow(dead_code)]
25fn rotation_from_euler(x: f64, y: f64, z: f64, convention: &str) -> SpatialResult<Rotation> {
26    let angles = euler_array(x, y, z);
27    let angles_view = angles.view();
28    Rotation::from_euler(&angles_view, convention)
29}
30
31/// RotationSpline provides smooth interpolation between multiple rotations.
32///
33/// A rotation spline allows for smooth interpolation between a sequence of rotations,
34/// creating a continuous curve in rotation space. It can be used to create smooth
35/// camera paths, character animations, or any other application requiring smooth
36/// rotation transitions.
37///
38/// # Examples
39///
40/// ```
41/// use scirs2_spatial::transform::{Rotation, RotationSpline};
42/// use scirs2_core::ndarray::array;
43/// use std::f64::consts::PI;
44///
45/// // Create some rotations
46/// let rotations = vec![
47///     Rotation::identity(),
48///     Rotation::from_euler(&array![0.0, 0.0, PI/2.0].view(), "xyz").expect("Operation failed"),
49///     Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
50/// ];
51///
52/// // Create times at which these rotations occur
53/// let times = vec![0.0, 0.5, 1.0];
54///
55/// // Create a rotation spline
56/// let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
57///
58/// // Get the interpolated rotation at t=0.25 (between the first two rotations)
59/// let rot_25 = spline.interpolate(0.25);
60///
61/// // Get the interpolated rotation at t=0.75 (between the second two rotations)
62/// let rot_75 = spline.interpolate(0.75);
63/// ```
64#[derive(Clone, Debug)]
65pub struct RotationSpline {
66    /// Sequence of rotations
67    rotations: Vec<Rotation>,
68    /// Times at which these rotations occur
69    times: Vec<f64>,
70    /// Cached velocities for natural cubic spline interpolation
71    velocities: Option<Vec<Array1<f64>>>,
72    /// Type of interpolation to use ("slerp" or "cubic")
73    interpolation_type: String,
74}
75
76impl RotationSpline {
77    /// Create a new rotation spline from a sequence of rotations and times
78    ///
79    /// # Arguments
80    ///
81    /// * `rotations` - A sequence of rotations
82    /// * `times` - The times at which these rotations occur
83    ///
84    /// # Returns
85    ///
86    /// A `SpatialResult` containing the RotationSpline if valid, or an error if invalid
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use scirs2_spatial::transform::{Rotation, RotationSpline};
92    /// use scirs2_core::ndarray::array;
93    /// use std::f64::consts::PI;
94    ///
95    /// let rotations = vec![
96    ///     Rotation::identity(),
97    ///     Rotation::from_euler(&array![0.0, 0.0, PI/2.0].view(), "xyz").expect("Operation failed"),
98    ///     Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
99    /// ];
100    /// let times = vec![0.0, 1.0, 2.0];
101    /// let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
102    /// ```
103    pub fn new(rotations: &[Rotation], times: &[f64]) -> SpatialResult<Self> {
104        if rotations.is_empty() {
105            return Err(SpatialError::ValueError("Rotations cannot be empty".into()));
106        }
107
108        if times.is_empty() {
109            return Err(SpatialError::ValueError("Times cannot be empty".into()));
110        }
111
112        if rotations.len() != times.len() {
113            return Err(SpatialError::ValueError(format!(
114                "Number of _rotations ({}) must match number of times ({})",
115                rotations.len(),
116                times.len()
117            )));
118        }
119
120        // Check if times are strictly increasing
121        for i in 1..times.len() {
122            if times[i] <= times[i - 1] {
123                return Err(SpatialError::ValueError(format!(
124                    "Times must be strictly increasing, but times[{}] = {} <= times[{}] = {}",
125                    i,
126                    times[i],
127                    i - 1,
128                    times[i - 1]
129                )));
130            }
131        }
132
133        // Make a copy of the _rotations and times
134        let rotations = rotations.to_vec();
135        let times = times.to_vec();
136
137        Ok(RotationSpline {
138            rotations,
139            times,
140            velocities: None,
141            interpolation_type: "slerp".to_string(),
142        })
143    }
144
145    /// Set the interpolation type for the rotation spline
146    ///
147    /// # Arguments
148    ///
149    /// * `_interptype` - The interpolation type ("slerp" or "cubic")
150    ///
151    /// # Returns
152    ///
153    /// A `SpatialResult` containing nothing if successful, or an error if the interpolation type is invalid
154    ///
155    /// # Examples
156    ///
157    /// ```
158    /// use scirs2_spatial::transform::{Rotation, RotationSpline};
159    /// use scirs2_core::ndarray::array;
160    /// use std::f64::consts::PI;
161    ///
162    /// let rotations = vec![
163    ///     Rotation::identity(),
164    ///     Rotation::from_euler(&array![0.0, 0.0, PI/2.0].view(), "xyz").expect("Operation failed"),
165    ///     Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
166    /// ];
167    /// let times = vec![0.0, 1.0, 2.0];
168    /// let mut spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
169    ///
170    /// // Set the interpolation type to cubic (natural cubic spline)
171    /// spline.set_interpolation_type("cubic").expect("Test/example failed");
172    /// ```
173    pub fn set_interpolation_type(&mut self, _interptype: &str) -> SpatialResult<()> {
174        match _interptype.to_lowercase().as_str() {
175            "slerp" => {
176                self.interpolation_type = "slerp".to_string();
177                self.velocities = None;
178                Ok(())
179            }
180            "cubic" => {
181                self.interpolation_type = "cubic".to_string();
182                // Compute velocities for cubic interpolation if needed
183                self.compute_velocities();
184                Ok(())
185            }
186            _ => Err(SpatialError::ValueError(format!(
187                "Invalid interpolation _type: {_interptype}. Must be 'slerp' or 'cubic'"
188            ))),
189        }
190    }
191
192    /// Compute velocities for natural cubic spline interpolation
193    fn compute_velocities(&mut self) {
194        if self.velocities.is_some() {
195            return; // Already computed
196        }
197
198        let n = self.times.len();
199        if n <= 2 {
200            // For 2 or fewer points, use zero velocities
201            let mut vels = Vec::with_capacity(n);
202            for _ in 0..n {
203                vels.push(Array1::zeros(3));
204            }
205            self.velocities = Some(vels);
206            return;
207        }
208
209        // Convert rotations to rotation vectors (axis-angle representation)
210        let mut rotvecs = Vec::with_capacity(n);
211        for rot in &self.rotations {
212            rotvecs.push(rot.as_rotvec());
213        }
214
215        // Compute velocities using finite differences and natural boundary conditions
216        let mut vels = Vec::with_capacity(n);
217
218        // For endpoints, we'll use one-sided differences
219        // For internal points, we'll use centered differences
220        for i in 0..n {
221            let vel = if i == 0 {
222                // Forward difference for the first point
223                let dt = self.times[1] - self.times[0];
224                (&rotvecs[1] - &rotvecs[0]) / dt
225            } else if i == n - 1 {
226                // Backward difference for the last point
227                let dt = self.times[n - 1] - self.times[n - 2];
228                (&rotvecs[n - 1] - &rotvecs[n - 2]) / dt
229            } else {
230                // Centered difference for internal points
231                let dt_prev = self.times[i] - self.times[i - 1];
232                let dt_next = self.times[i + 1] - self.times[i];
233
234                // Use weighted average based on time intervals
235                let vel_prev = (&rotvecs[i] - &rotvecs[i - 1]) / dt_prev;
236                let vel_next = (&rotvecs[i + 1] - &rotvecs[i]) / dt_next;
237
238                // Weighted average
239                let weight_prev = dt_next / (dt_prev + dt_next);
240                let weight_next = dt_prev / (dt_prev + dt_next);
241                &vel_prev * weight_prev + &vel_next * weight_next
242            };
243
244            vels.push(vel);
245        }
246
247        self.velocities = Some(vels);
248    }
249
250    /// Compute the second derivatives for natural cubic spline interpolation
251    #[allow(dead_code)]
252    fn compute_natural_spline_second_derivatives(&self, values: &[f64]) -> Vec<f64> {
253        let n = values.len();
254        if n <= 2 {
255            return vec![0.0; n];
256        }
257
258        // Set up the tridiagonal system for natural cubic spline
259        // The system is in the form: A * x = b
260        // where A is a tridiagonal matrix, x is the second derivatives we're solving for,
261        // and b is the right-hand side of the system
262
263        // Allocate arrays for the diagonals of the tridiagonal matrix
264        let mut a = vec![0.0; n - 2]; // Lower diagonal
265        let mut b = vec![0.0; n - 2]; // Main diagonal
266        let mut c = vec![0.0; n - 2]; // Upper diagonal
267        let mut d = vec![0.0; n - 2]; // Right-hand side
268
269        // Set up the tridiagonal system
270        for i in 0..n - 2 {
271            let h_i = self.times[i + 1] - self.times[i];
272            let h_ip1 = self.times[i + 2] - self.times[i + 1];
273
274            a[i] = h_i;
275            b[i] = 2.0 * (h_i + h_ip1);
276            c[i] = h_ip1;
277
278            let fd_i = (values[i + 1] - values[i]) / h_i;
279            let fd_ip1 = (values[i + 2] - values[i + 1]) / h_ip1;
280            d[i] = 6.0 * (fd_ip1 - fd_i);
281        }
282
283        // Solve the tridiagonal system using the Thomas algorithm
284        let mut x = vec![0.0; n - 2];
285        self.solve_tridiagonal(&a, &b, &c, &d, &mut x);
286
287        // The second derivatives at the endpoints are set to zero (natural spline)
288        let mut second_derivs = vec![0.0; n];
289        second_derivs[1..((n - 2) + 1)].copy_from_slice(&x[..(n - 2)]);
290
291        second_derivs
292    }
293
294    /// Solve a tridiagonal system using the Thomas algorithm
295    #[allow(dead_code)]
296    fn solve_tridiagonal(
297        &self,
298        a: &[f64],     // Lower diagonal
299        b: &[f64],     // Main diagonal
300        c: &[f64],     // Upper diagonal
301        d: &[f64],     // Right-hand side
302        x: &mut [f64], // Solution vector
303    ) {
304        let n = x.len();
305        if n == 0 {
306            return;
307        }
308
309        // Forward sweep
310        let mut c_prime = vec![0.0; n];
311        let mut d_prime = vec![0.0; n];
312
313        c_prime[0] = c[0] / b[0];
314        d_prime[0] = d[0] / b[0];
315
316        for i in 1..n {
317            let m = b[i] - a[i - 1] * c_prime[i - 1];
318            c_prime[i] = if i < n - 1 { c[i] / m } else { 0.0 };
319            d_prime[i] = (d[i] - a[i - 1] * d_prime[i - 1]) / m;
320        }
321
322        // Back substitution
323        x[n - 1] = d_prime[n - 1];
324        for i in (0..n - 1).rev() {
325            x[i] = d_prime[i] - c_prime[i] * x[i + 1];
326        }
327    }
328
329    /// Interpolate the rotation spline at a given time
330    ///
331    /// # Arguments
332    ///
333    /// * `t` - The time at which to interpolate
334    ///
335    /// # Returns
336    ///
337    /// The interpolated rotation
338    ///
339    /// # Examples
340    ///
341    /// ```
342    /// use scirs2_spatial::transform::{Rotation, RotationSpline};
343    /// use scirs2_core::ndarray::array;
344    /// use std::f64::consts::PI;
345    ///
346    /// let rotations = vec![
347    ///     Rotation::identity(),
348    ///     Rotation::from_euler(&array![0.0, 0.0, PI/2.0].view(), "xyz").expect("Operation failed"),
349    ///     Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
350    /// ];
351    /// let times = vec![0.0, 1.0, 2.0];
352    /// let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
353    ///
354    /// // Interpolate at t=0.5 (halfway between the first two rotations)
355    /// let rot_half = spline.interpolate(0.5);
356    /// ```
357    pub fn interpolate(&self, t: f64) -> Rotation {
358        let n = self.times.len();
359
360        // Handle boundary cases
361        if t <= self.times[0] {
362            return self.rotations[0].clone();
363        }
364        if t >= self.times[n - 1] {
365            return self.rotations[n - 1].clone();
366        }
367
368        // Find the segment containing t
369        let mut idx = 0;
370        for i in 0..n - 1 {
371            if t >= self.times[i] && t < self.times[i + 1] {
372                idx = i;
373                break;
374            }
375        }
376
377        // Interpolate within the segment based on interpolation type
378        match self.interpolation_type.as_str() {
379            "slerp" => self.interpolate_slerp(t, idx),
380            "cubic" => self.interpolate_cubic(t, idx),
381            _ => self.interpolate_slerp(t, idx), // Default to slerp
382        }
383    }
384
385    /// Interpolate the rotation spline at a given time using Slerp
386    fn interpolate_slerp(&self, t: f64, idx: usize) -> Rotation {
387        let t0 = self.times[idx];
388        let t1 = self.times[idx + 1];
389        let normalized_t = (t - t0) / (t1 - t0);
390
391        // Create a Slerp between the two rotations
392        let slerp = Slerp::new(self.rotations[idx].clone(), self.rotations[idx + 1].clone())
393            .expect("Test/example failed");
394
395        slerp.interpolate(normalized_t)
396    }
397
398    /// Interpolate the rotation spline at a given time using cubic spline
399    fn interpolate_cubic(&self, t: f64, idx: usize) -> Rotation {
400        // Ensure velocities are computed
401        if self.velocities.is_none() {
402            let mut mutable_self = self.clone();
403            mutable_self.compute_velocities();
404            return mutable_self.interpolate_cubic(t, idx);
405        }
406
407        let t0 = self.times[idx];
408        let t1 = self.times[idx + 1];
409        let dt = t1 - t0;
410        let normalized_t = (t - t0) / dt;
411
412        let rot0 = &self.rotations[idx];
413        let rot1 = &self.rotations[idx + 1];
414
415        // Convert rotations to rotation vectors
416        let rotvec0 = rot0.as_rotvec();
417        let rotvec1 = rot1.as_rotvec();
418
419        // Get velocities
420        let velocities = self.velocities.as_ref().expect("Test/example failed");
421        let vel0 = &velocities[idx];
422        let vel1 = &velocities[idx + 1];
423
424        // Use Hermite cubic interpolation formula
425        // h(t) = (2t³ - 3t² + 1)p0 + (t³ - 2t² + t)m0 + (-2t³ + 3t²)p1 + (t³ - t²)m1
426        // where p0, p1 are the start and end values, m0, m1 are the scaled tangents
427        let t2 = normalized_t * normalized_t;
428        let t3 = t2 * normalized_t;
429
430        // Hermite basis functions
431        let h00 = 2.0 * t3 - 3.0 * t2 + 1.0;
432        let h10 = t3 - 2.0 * t2 + normalized_t;
433        let h01 = -2.0 * t3 + 3.0 * t2;
434        let h11 = t3 - t2;
435
436        // Compute the interpolated rotation vector
437        let mut result = rotvec0 * h00;
438        result = &result + &(vel0 * dt * h10);
439        result = &result + &(rotvec1 * h01);
440        result = &result + &(vel1 * dt * h11);
441
442        // Convert back to rotation
443        Rotation::from_rotvec(&result.view()).expect("Operation failed")
444    }
445
446    /// Get the times at which the rotations are defined
447    ///
448    /// # Returns
449    ///
450    /// A reference to the times vector
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// use scirs2_spatial::transform::{Rotation, RotationSpline};
456    /// use scirs2_core::ndarray::array;
457    ///
458    /// let rotations = vec![
459    ///     Rotation::identity(),
460    ///     Rotation::identity(),
461    /// ];
462    /// let times = vec![0.0, 1.0];
463    /// let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
464    ///
465    /// let retrieved_times = spline.times();
466    /// assert_eq!(retrieved_times, &vec![0.0, 1.0]);
467    /// ```
468    pub fn times(&self) -> &Vec<f64> {
469        &self.times
470    }
471
472    /// Get the rotations that define the spline
473    ///
474    /// # Returns
475    ///
476    /// A reference to the rotations vector
477    ///
478    /// # Examples
479    ///
480    /// ```
481    /// use scirs2_spatial::transform::{Rotation, RotationSpline};
482    /// use scirs2_core::ndarray::array;
483    ///
484    /// let rotations = vec![
485    ///     Rotation::identity(),
486    ///     Rotation::identity(),
487    /// ];
488    /// let times = vec![0.0, 1.0];
489    /// let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
490    ///
491    /// let retrieved_rotations = spline.rotations();
492    /// assert_eq!(retrieved_rotations.len(), 2);
493    /// ```
494    pub fn rotations(&self) -> &Vec<Rotation> {
495        &self.rotations
496    }
497
498    /// Generate evenly spaced samples from the rotation spline
499    ///
500    /// # Arguments
501    ///
502    /// * `n` - The number of samples to generate
503    ///
504    /// # Returns
505    ///
506    /// A vector of sampled rotations and the corresponding times
507    ///
508    /// # Examples
509    ///
510    /// ```
511    /// use scirs2_spatial::transform::{Rotation, RotationSpline};
512    /// use scirs2_core::ndarray::array;
513    /// use std::f64::consts::PI;
514    ///
515    /// let rotations = vec![
516    ///     Rotation::identity(),
517    ///     Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
518    /// ];
519    /// let times = vec![0.0, 1.0];
520    /// let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
521    ///
522    /// // Generate 5 samples from the spline
523    /// let (sample_times, sample_rotations) = spline.sample(5);
524    /// assert_eq!(sample_times.len(), 5);
525    /// assert_eq!(sample_rotations.len(), 5);
526    /// ```
527    pub fn sample(&self, n: usize) -> (Vec<f64>, Vec<Rotation>) {
528        if n <= 1 {
529            return (vec![self.times[0]], vec![self.rotations[0].clone()]);
530        }
531
532        let t_min = self.times[0];
533        let t_max = self.times[self.times.len() - 1];
534
535        let mut sampled_times = Vec::with_capacity(n);
536        let mut sampled_rotations = Vec::with_capacity(n);
537
538        for i in 0..n {
539            let t = t_min + (t_max - t_min) * (i as f64 / (n - 1) as f64);
540            sampled_times.push(t);
541            sampled_rotations.push(self.interpolate(t));
542        }
543
544        (sampled_times, sampled_rotations)
545    }
546
547    /// Create a new rotation spline from key rotations at specific times
548    ///
549    /// This is equivalent to the regular constructor but with a more explicit name.
550    ///
551    /// # Arguments
552    ///
553    /// * `key_rots` - The key rotations
554    /// * `keytimes` - The times at which these key rotations occur
555    ///
556    /// # Returns
557    ///
558    /// A `SpatialResult` containing the RotationSpline if valid, or an error if invalid
559    ///
560    /// # Examples
561    ///
562    /// ```
563    /// use scirs2_spatial::transform::{Rotation, RotationSpline};
564    /// use scirs2_core::ndarray::array;
565    /// use std::f64::consts::PI;
566    ///
567    /// let key_rots = vec![
568    ///     Rotation::identity(),
569    ///     Rotation::from_euler(&array![0.0, 0.0, PI/2.0].view(), "xyz").expect("Operation failed"),
570    ///     Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
571    /// ];
572    /// let keytimes = vec![0.0, 1.0, 2.0];
573    ///
574    /// let spline = RotationSpline::from_key_rotations(&key_rots, &keytimes).expect("Test/example failed");
575    /// ```
576    pub fn from_key_rotations(_key_rots: &[Rotation], keytimes: &[f64]) -> SpatialResult<Self> {
577        Self::new(_key_rots, keytimes)
578    }
579
580    /// Get the current interpolation type
581    ///
582    /// # Returns
583    ///
584    /// The current interpolation type ("slerp" or "cubic")
585    ///
586    /// # Examples
587    ///
588    /// ```
589    /// use scirs2_spatial::transform::{Rotation, RotationSpline};
590    /// use scirs2_core::ndarray::array;
591    ///
592    /// let rotations = vec![
593    ///     Rotation::identity(),
594    ///     Rotation::identity(),
595    /// ];
596    /// let times = vec![0.0, 1.0];
597    /// let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
598    ///
599    /// assert_eq!(spline.interpolation_type(), "slerp");
600    /// ```
601    pub fn interpolation_type(&self) -> &'_ str {
602        &self.interpolation_type
603    }
604
605    /// Calculate the angular velocity at a specific time
606    ///
607    /// # Arguments
608    ///
609    /// * `t` - The time at which to calculate the angular velocity
610    ///
611    /// # Returns
612    ///
613    /// The angular velocity as a 3-element array
614    ///
615    /// # Examples
616    ///
617    /// ```
618    /// use scirs2_spatial::transform::{Rotation, RotationSpline};
619    /// use scirs2_core::ndarray::array;
620    /// use std::f64::consts::PI;
621    ///
622    /// let rotations = vec![
623    ///     Rotation::identity(),
624    ///     Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
625    /// ];
626    /// let times = vec![0.0, 1.0];
627    /// let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
628    ///
629    /// // Calculate angular velocity at t=0.5
630    /// let velocity = spline.angular_velocity(0.5);
631    /// // Should be approximately [0, 0, PI]
632    /// ```
633    pub fn angular_velocity(&self, t: f64) -> SpatialResult<Array1<f64>> {
634        let n = self.times.len();
635
636        // Handle boundary cases
637        if t <= self.times[0] || t >= self.times[n - 1] {
638            return Ok(Array1::zeros(3));
639        }
640
641        // Find the segment containing t
642        let mut idx = 0;
643        for i in 0..n - 1 {
644            if t >= self.times[i] && t < self.times[i + 1] {
645                idx = i;
646                break;
647            }
648        }
649
650        // Calculate angular velocity based on interpolation type
651        match self.interpolation_type.as_str() {
652            "slerp" => self.angular_velocity_slerp(t, idx),
653            "cubic" => Ok(self.angular_velocity_cubic(t, idx)),
654            _ => self.angular_velocity_slerp(t, idx), // Default to slerp
655        }
656    }
657
658    /// Calculate angular velocity using Slerp interpolation
659    fn angular_velocity_slerp(&self, t: f64, idx: usize) -> SpatialResult<Array1<f64>> {
660        let t0 = self.times[idx];
661        let t1 = self.times[idx + 1];
662        let dt = t1 - t0;
663        let normalized_t = (t - t0) / dt;
664
665        // Get rotations at the endpoints of the segment
666        let r0 = &self.rotations[idx];
667        let r1 = &self.rotations[idx + 1];
668
669        // Calculate the delta rotation from r0 to r1
670        let delta_rot = r0.inv().compose(r1);
671
672        // Convert to axis-angle representation via rotation vector
673        let rotvec = delta_rot.as_rotvec();
674        let angle = (rotvec.dot(&rotvec)).sqrt();
675        let axis = if angle > 1e-10 {
676            &rotvec / angle
677        } else {
678            Array1::zeros(3)
679        };
680
681        // For slerp, the angular velocity is constant and equals angle/dt along the axis
682        // The angular velocity vector in the current frame is:
683        // ω = (angle / dt) * axis
684
685        // However, we need to transform this to the frame at time t
686        // First interpolate to get the rotation at time t
687        let slerp = Slerp::new(r0.clone(), r1.clone()).expect("Test/example failed");
688        let rot_t = slerp.interpolate(normalized_t);
689
690        // The angular velocity in the global frame is the axis scaled by angular rate
691        let angular_rate = angle / dt;
692        let omega_global = axis * angular_rate;
693
694        // Transform to the body frame at time t
695        // ω_body = R(t)^T * ω_global
696        rot_t.inv().apply(&omega_global.view())
697    }
698
699    /// Calculate angular velocity using cubic spline interpolation
700    fn angular_velocity_cubic(&self, t: f64, idx: usize) -> Array1<f64> {
701        // Ensure velocities are computed
702        if self.velocities.is_none() {
703            let mut mutable_self = self.clone();
704            mutable_self.compute_velocities();
705            return mutable_self.angular_velocity_cubic(t, idx);
706        }
707
708        let t0 = self.times[idx];
709        let t1 = self.times[idx + 1];
710        let dt = t1 - t0;
711        let normalized_t = (t - t0) / dt;
712
713        let rot0 = &self.rotations[idx];
714        let rot1 = &self.rotations[idx + 1];
715
716        // Convert rotations to rotation vectors
717        let rotvec0 = rot0.as_rotvec();
718        let rotvec1 = rot1.as_rotvec();
719
720        // Get velocities
721        let velocities = self.velocities.as_ref().expect("Test/example failed");
722        let vel0 = &velocities[idx];
723        let vel1 = &velocities[idx + 1];
724
725        // Derivatives of Hermite basis functions
726        let dh00_dt = (6.0 * normalized_t.powi(2) - 6.0 * normalized_t) / dt;
727        let dh10_dt = (3.0 * normalized_t.powi(2) - 4.0 * normalized_t + 1.0) / dt;
728        let dh01_dt = (-6.0 * normalized_t.powi(2) + 6.0 * normalized_t) / dt;
729        let dh11_dt = (3.0 * normalized_t.powi(2) - 2.0 * normalized_t) / dt;
730
731        // Compute derivative of rotation vector interpolation
732        let mut d_rotvec_dt = &rotvec0 * dh00_dt;
733        d_rotvec_dt = &d_rotvec_dt + &(vel0 * dt * dh10_dt);
734        d_rotvec_dt = &d_rotvec_dt + &(&rotvec1 * dh01_dt);
735        d_rotvec_dt = &d_rotvec_dt + &(vel1 * dt * dh11_dt);
736
737        // The derivative gives us the angular velocity in the rotation vector space
738        // This is already the angular velocity we want
739        d_rotvec_dt
740    }
741
742    /// Calculate the angular acceleration at a specific time
743    ///
744    /// # Arguments
745    ///
746    /// * `t` - The time at which to calculate the angular acceleration
747    ///
748    /// # Returns
749    ///
750    /// The angular acceleration as a 3-element array
751    ///
752    /// # Examples
753    ///
754    /// ```
755    /// use scirs2_spatial::transform::{Rotation, RotationSpline};
756    /// use scirs2_core::ndarray::array;
757    /// use std::f64::consts::PI;
758    ///
759    /// let rotations = vec![
760    ///     Rotation::identity(),
761    ///     Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
762    ///     Rotation::identity(),
763    /// ];
764    /// let times = vec![0.0, 1.0, 2.0];
765    /// let mut spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
766    ///
767    /// // Set to cubic interpolation for non-zero acceleration
768    /// spline.set_interpolation_type("cubic").expect("Test/example failed");
769    ///
770    /// // Calculate angular acceleration at t=0.5
771    /// let acceleration = spline.angular_acceleration(0.5);
772    /// ```
773    pub fn angular_acceleration(&self, t: f64) -> Array1<f64> {
774        // Cubic interpolation is needed for meaningful acceleration
775        if self.interpolation_type != "cubic" {
776            return Array1::zeros(3); // Slerp has constant velocity, so acceleration is zero
777        }
778
779        let n = self.times.len();
780
781        // Handle boundary cases
782        if t <= self.times[0] || t >= self.times[n - 1] {
783            return Array1::zeros(3);
784        }
785
786        // Find the segment containing t
787        let mut idx = 0;
788        for i in 0..n - 1 {
789            if t >= self.times[i] && t < self.times[i + 1] {
790                idx = i;
791                break;
792            }
793        }
794
795        // Calculate angular acceleration
796        self.angular_acceleration_cubic(t, idx)
797    }
798
799    /// Calculate angular acceleration using cubic spline interpolation
800    fn angular_acceleration_cubic(&self, t: f64, idx: usize) -> Array1<f64> {
801        // Ensure velocities are computed
802        if self.velocities.is_none() {
803            let mut mutable_self = self.clone();
804            mutable_self.compute_velocities();
805            return mutable_self.angular_acceleration_cubic(t, idx);
806        }
807
808        let t0 = self.times[idx];
809        let t1 = self.times[idx + 1];
810        let dt = t1 - t0;
811        let normalized_t = (t - t0) / dt;
812
813        let rot0 = &self.rotations[idx];
814        let rot1 = &self.rotations[idx + 1];
815
816        // Convert rotations to rotation vectors
817        let rotvec0 = rot0.as_rotvec();
818        let rotvec1 = rot1.as_rotvec();
819
820        // Get velocities
821        let velocities = self.velocities.as_ref().expect("Test/example failed");
822        let vel0 = &velocities[idx];
823        let vel1 = &velocities[idx + 1];
824
825        // Second derivatives of Hermite basis functions
826        let d2h00_dt2 = (12.0 * normalized_t - 6.0) / (dt * dt);
827        let d2h10_dt2 = (6.0 * normalized_t - 4.0) / (dt * dt);
828        let d2h01_dt2 = (-12.0 * normalized_t + 6.0) / (dt * dt);
829        let d2h11_dt2 = (6.0 * normalized_t - 2.0) / (dt * dt);
830
831        // Compute second derivative of rotation vector interpolation
832        let mut d2_rotvec_dt2 = &rotvec0 * d2h00_dt2;
833        d2_rotvec_dt2 = &d2_rotvec_dt2 + &(vel0 * dt * d2h10_dt2);
834        d2_rotvec_dt2 = &d2_rotvec_dt2 + &(&rotvec1 * d2h01_dt2);
835        d2_rotvec_dt2 = &d2_rotvec_dt2 + &(vel1 * dt * d2h11_dt2);
836
837        // This gives us the angular acceleration
838        d2_rotvec_dt2
839    }
840}
841
842#[cfg(test)]
843mod tests {
844    use super::*;
845    use approx::assert_relative_eq;
846    use std::f64::consts::PI;
847
848    #[test]
849    fn test_rotation_spline_creation() {
850        let rotations = vec![
851            Rotation::identity(),
852            rotation_from_euler(0.0, 0.0, PI / 2.0, "xyz").expect("Operation failed"),
853            Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
854        ];
855        let times = vec![0.0, 1.0, 2.0];
856
857        let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
858
859        assert_eq!(spline.rotations().len(), 3);
860        assert_eq!(spline.times().len(), 3);
861        assert_eq!(spline.interpolation_type(), "slerp");
862    }
863
864    #[test]
865    fn test_rotation_spline_interpolation_endpoints() {
866        let rotations = vec![
867            Rotation::identity(),
868            rotation_from_euler(0.0, 0.0, PI / 2.0, "xyz").expect("Operation failed"),
869            Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
870        ];
871        let times = vec![0.0, 1.0, 2.0];
872
873        let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
874
875        // Test at endpoints
876        let interp_start = spline.interpolate(0.0);
877        let interp_end = spline.interpolate(2.0);
878
879        // Should match the first and last rotations
880        assert_eq!(interp_start.as_quat(), rotations[0].as_quat());
881        assert_eq!(interp_end.as_quat(), rotations[2].as_quat());
882
883        // Test beyond endpoints (should clamp)
884        let before_start = spline.interpolate(-1.0);
885        let after_end = spline.interpolate(3.0);
886
887        assert_eq!(before_start.as_quat(), rotations[0].as_quat());
888        assert_eq!(after_end.as_quat(), rotations[2].as_quat());
889    }
890
891    #[test]
892    fn test_rotation_spline_interpolation_midpoints() {
893        let rotations = vec![
894            Rotation::identity(),
895            rotation_from_euler(0.0, 0.0, PI / 2.0, "xyz").expect("Operation failed"),
896            Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
897        ];
898        let times = vec![0.0, 1.0, 2.0];
899
900        let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
901
902        // Test at midpoints
903        let interp_mid1 = spline.interpolate(0.5);
904        let interp_mid2 = spline.interpolate(1.5);
905
906        // Apply to a test point
907        let test_point = array![1.0, 0.0, 0.0];
908
909        // Verify interpolation results
910        let rotated_mid1 = interp_mid1
911            .apply(&test_point.view())
912            .expect("Test/example failed");
913        let rotated_mid2 = interp_mid2
914            .apply(&test_point.view())
915            .expect("Test/example failed");
916
917        // At t=0.5 (between identity and 90-degree rotation), should be approximately 45 degrees
918        assert_relative_eq!(rotated_mid1[0], 2.0_f64.sqrt() / 2.0, epsilon = 1e-3);
919        assert_relative_eq!(rotated_mid1[1], 2.0_f64.sqrt() / 2.0, epsilon = 1e-3);
920        assert_relative_eq!(rotated_mid1[2], 0.0, epsilon = 1e-3);
921
922        // At t=1.5 (between 90 and 180 degrees), should be approximately 135 degrees
923        assert_relative_eq!(rotated_mid2[0], -2.0_f64.sqrt() / 2.0, epsilon = 1e-3);
924        assert_relative_eq!(rotated_mid2[1], 2.0_f64.sqrt() / 2.0, epsilon = 1e-3);
925        assert_relative_eq!(rotated_mid2[2], 0.0, epsilon = 1e-3);
926    }
927
928    #[test]
929    fn test_rotation_spline_sampling() {
930        let rotations = vec![
931            Rotation::identity(),
932            Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
933        ];
934        let times = vec![0.0, 1.0];
935
936        let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
937
938        // Sample 5 points
939        let (sample_times, sample_rotations) = spline.sample(5);
940
941        assert_eq!(sample_times.len(), 5);
942        assert_eq!(sample_rotations.len(), 5);
943
944        // Check if times are evenly spaced
945        assert_relative_eq!(sample_times[0], 0.0, epsilon = 1e-10);
946        assert_relative_eq!(sample_times[1], 0.25, epsilon = 1e-10);
947        assert_relative_eq!(sample_times[2], 0.5, epsilon = 1e-10);
948        assert_relative_eq!(sample_times[3], 0.75, epsilon = 1e-10);
949        assert_relative_eq!(sample_times[4], 1.0, epsilon = 1e-10);
950
951        // Check if rotations are correct
952        let point = array![1.0, 0.0, 0.0];
953
954        // At t=0.0, should be identity
955        let rot0 = &sample_rotations[0];
956        let rotated0 = rot0.apply(&point.view()).expect("Test/example failed");
957        assert_relative_eq!(rotated0[0], 1.0, epsilon = 1e-10);
958        assert_relative_eq!(rotated0[1], 0.0, epsilon = 1e-10);
959
960        // At t=0.5, should be 90-degree rotation
961        let rot2 = &sample_rotations[2];
962        let rotated2 = rot2.apply(&point.view()).expect("Test/example failed");
963        assert_relative_eq!(rotated2[0], 0.0, epsilon = 1e-3);
964        assert_relative_eq!(rotated2[1], 1.0, epsilon = 1e-3);
965        assert_relative_eq!(rotated2[2], 0.0, epsilon = 1e-3);
966
967        // At t=1.0, should be 180-degree rotation
968        let rot4 = &sample_rotations[4];
969        let rotated4 = rot4.apply(&point.view()).expect("Test/example failed");
970        assert_relative_eq!(rotated4[0], -1.0, epsilon = 1e-10);
971        assert_relative_eq!(rotated4[1], 0.0, epsilon = 1e-10);
972        assert_relative_eq!(rotated4[2], 0.0, epsilon = 1e-10);
973    }
974
975    #[test]
976    fn test_rotation_spline_errors() {
977        // Empty rotations
978        let result = RotationSpline::new(&[], &[0.0]);
979        assert!(result.is_err());
980
981        // Empty times
982        let rotations = vec![Rotation::identity()];
983        let result = RotationSpline::new(&rotations, &[]);
984        assert!(result.is_err());
985
986        // Mismatched lengths
987        let rotations = vec![Rotation::identity(), Rotation::identity()];
988        let times = vec![0.0];
989        let result = RotationSpline::new(&rotations, &times);
990        assert!(result.is_err());
991
992        // Non-increasing times
993        let rotations = vec![Rotation::identity(), Rotation::identity()];
994        let times = vec![1.0, 0.0];
995        let result = RotationSpline::new(&rotations, &times);
996        assert!(result.is_err());
997
998        // Equal times
999        let rotations = vec![Rotation::identity(), Rotation::identity()];
1000        let times = vec![0.0, 0.0];
1001        let result = RotationSpline::new(&rotations, &times);
1002        assert!(result.is_err());
1003
1004        // Invalid interpolation type
1005        let rotations = vec![Rotation::identity(), Rotation::identity()];
1006        let times = vec![0.0, 1.0];
1007        let mut spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
1008        let result = spline.set_interpolation_type("invalid");
1009        assert!(result.is_err());
1010    }
1011
1012    #[test]
1013    fn test_interpolation_types() {
1014        let rotations = vec![
1015            Rotation::identity(),
1016            rotation_from_euler(0.0, 0.0, PI / 2.0, "xyz").expect("Operation failed"),
1017            Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
1018        ];
1019        let times = vec![0.0, 1.0, 2.0];
1020
1021        let mut spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
1022
1023        // Default should be slerp
1024        assert_eq!(spline.interpolation_type(), "slerp");
1025
1026        // Change to cubic
1027        spline
1028            .set_interpolation_type("cubic")
1029            .expect("Test/example failed");
1030        assert_eq!(spline.interpolation_type(), "cubic");
1031
1032        // Check that velocities are computed
1033        assert!(spline.velocities.is_some());
1034
1035        // Change back to slerp
1036        spline
1037            .set_interpolation_type("slerp")
1038            .expect("Test/example failed");
1039        assert_eq!(spline.interpolation_type(), "slerp");
1040
1041        // Velocities should be cleared
1042        assert!(spline.velocities.is_none());
1043    }
1044
1045    #[test]
1046    fn test_angular_velocity() {
1047        let rotations = vec![
1048            Rotation::identity(),
1049            Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
1050        ];
1051        let times = vec![0.0, 1.0];
1052
1053        let spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
1054
1055        // Angular velocity should be constant for slerp
1056        let velocity = spline.angular_velocity(0.5).expect("Test/example failed");
1057
1058        // For a rotation from identity to 180 degrees around z-axis over 1 second,
1059        // the angular velocity should be approximately [0, 0, π]
1060        assert_relative_eq!(velocity[0], 0.0, epsilon = 1e-3);
1061        assert_relative_eq!(velocity[1], 0.0, epsilon = 1e-3);
1062        assert_relative_eq!(velocity[2], PI, epsilon = 1e-3);
1063
1064        // Velocity should be the same at any point in the segment
1065        let velocity_25 = spline.angular_velocity(0.25).expect("Test/example failed");
1066        let velocity_75 = spline.angular_velocity(0.75).expect("Test/example failed");
1067
1068        assert_relative_eq!(velocity_25[0], velocity[0], epsilon = 1e-10);
1069        assert_relative_eq!(velocity_25[1], velocity[1], epsilon = 1e-10);
1070        assert_relative_eq!(velocity_25[2], velocity[2], epsilon = 1e-10);
1071
1072        assert_relative_eq!(velocity_75[0], velocity[0], epsilon = 1e-10);
1073        assert_relative_eq!(velocity_75[1], velocity[1], epsilon = 1e-10);
1074        assert_relative_eq!(velocity_75[2], velocity[2], epsilon = 1e-10);
1075    }
1076
1077    #[test]
1078    fn test_cubic_interpolation() {
1079        let rotations = vec![
1080            Rotation::identity(),
1081            rotation_from_euler(0.0, 0.0, PI / 2.0, "xyz").expect("Operation failed"),
1082            Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
1083        ];
1084        let times = vec![0.0, 1.0, 2.0];
1085
1086        let mut spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
1087
1088        // Set to cubic interpolation
1089        spline
1090            .set_interpolation_type("cubic")
1091            .expect("Test/example failed");
1092
1093        // Test at endpoints, should match original rotations
1094        let rot_0 = spline.interpolate(0.0);
1095        let rot_1 = spline.interpolate(1.0);
1096        let rot_2 = spline.interpolate(2.0);
1097
1098        let test_point = array![1.0, 0.0, 0.0];
1099
1100        // Check that endpoints match original rotations
1101        let rotated_0 = rot_0
1102            .apply(&test_point.view())
1103            .expect("Test/example failed");
1104        let expected_0 = rotations[0]
1105            .apply(&test_point.view())
1106            .expect("Test/example failed");
1107        assert_relative_eq!(rotated_0[0], expected_0[0], epsilon = 1e-10);
1108        assert_relative_eq!(rotated_0[1], expected_0[1], epsilon = 1e-10);
1109        assert_relative_eq!(rotated_0[2], expected_0[2], epsilon = 1e-10);
1110
1111        let rotated_1 = rot_1
1112            .apply(&test_point.view())
1113            .expect("Test/example failed");
1114        let expected_1 = rotations[1]
1115            .apply(&test_point.view())
1116            .expect("Test/example failed");
1117        assert_relative_eq!(rotated_1[0], expected_1[0], epsilon = 1e-10);
1118        assert_relative_eq!(rotated_1[1], expected_1[1], epsilon = 1e-10);
1119        assert_relative_eq!(rotated_1[2], expected_1[2], epsilon = 1e-10);
1120
1121        let rotated_2 = rot_2
1122            .apply(&test_point.view())
1123            .expect("Test/example failed");
1124        let expected_2 = rotations[2]
1125            .apply(&test_point.view())
1126            .expect("Test/example failed");
1127        assert_relative_eq!(rotated_2[0], expected_2[0], epsilon = 1e-10);
1128        assert_relative_eq!(rotated_2[1], expected_2[1], epsilon = 1e-10);
1129        assert_relative_eq!(rotated_2[2], expected_2[2], epsilon = 1e-10);
1130
1131        // Test midpoints - cubic interpolation should be smoother than slerp
1132        // but still interpolate the key rotations
1133        let rot_05 = spline.interpolate(0.5);
1134        let rot_15 = spline.interpolate(1.5);
1135
1136        // Verify that interpolated rotations are valid
1137        let rotated_05 = rot_05
1138            .apply(&test_point.view())
1139            .expect("Test/example failed");
1140        let rotated_15 = rot_15
1141            .apply(&test_point.view())
1142            .expect("Test/example failed");
1143
1144        // Check that the results are normalized
1145        let norm_05 = (rotated_05.dot(&rotated_05)).sqrt();
1146        let norm_15 = (rotated_15.dot(&rotated_15)).sqrt();
1147        assert_relative_eq!(norm_05, 1.0, epsilon = 1e-10);
1148        assert_relative_eq!(norm_15, 1.0, epsilon = 1e-10);
1149    }
1150
1151    #[test]
1152    fn test_angular_acceleration() {
1153        let rotations = vec![
1154            Rotation::identity(),
1155            rotation_from_euler(0.0, 0.0, PI / 2.0, "xyz").expect("Operation failed"),
1156            Rotation::from_euler(&array![0.0, 0.0, PI].view(), "xyz").expect("Operation failed"),
1157        ];
1158        let times = vec![0.0, 1.0, 2.0];
1159
1160        let mut spline = RotationSpline::new(&rotations, &times).expect("Test/example failed");
1161
1162        // Slerp should have zero acceleration
1163        let accel_slerp = spline.angular_acceleration(0.5);
1164        assert_relative_eq!(accel_slerp[0], 0.0, epsilon = 1e-10);
1165        assert_relative_eq!(accel_slerp[1], 0.0, epsilon = 1e-10);
1166        assert_relative_eq!(accel_slerp[2], 0.0, epsilon = 1e-10);
1167
1168        // Set to cubic interpolation
1169        spline
1170            .set_interpolation_type("cubic")
1171            .expect("Test/example failed");
1172
1173        // Cubic should have non-zero acceleration
1174        let _accel_cubic = spline.angular_acceleration(0.5);
1175
1176        // For linear rotation sequence, acceleration might still be close to zero
1177        // Let's create a more complex rotation sequence
1178        let complex_rotations = vec![
1179            Rotation::identity(),
1180            {
1181                let angles = array![PI / 2.0, 0.0, 0.0];
1182                Rotation::from_euler(&angles.view(), "xyz").expect("Operation failed")
1183            },
1184            {
1185                let angles = array![PI / 2.0, PI / 2.0, 0.0];
1186                Rotation::from_euler(&angles.view(), "xyz").expect("Operation failed")
1187            },
1188        ];
1189        let complex_times = vec![0.0, 1.0, 2.0];
1190
1191        let mut complex_spline =
1192            RotationSpline::new(&complex_rotations, &complex_times).expect("Test/example failed");
1193        complex_spline
1194            .set_interpolation_type("cubic")
1195            .expect("Test/example failed");
1196
1197        let complex_accel = complex_spline.angular_acceleration(0.5);
1198
1199        // For non-linear rotation sequences, acceleration should be non-zero
1200        let magnitude = (complex_accel.dot(&complex_accel)).sqrt();
1201        assert!(magnitude > 1e-6); // Should have meaningful acceleration
1202    }
1203}