token_value_map/time_data_map/
mod.rs

1use crate::*;
2use enum_dispatch::enum_dispatch;
3
4use std::{
5    collections::BTreeMap,
6    iter::FromIterator,
7    ops::{Add, Mul, Sub},
8};
9
10mod sample;
11pub use sample::*;
12
13/// A mapping from time to data values with interpolation support.
14///
15/// Stores time-value pairs in a [`BTreeMap`] for efficient time-based
16/// queries and supports various interpolation methods.
17///
18/// When the `interpolation` feature is enabled, each value can have an
19/// associated interpolation specification for animation curves.
20///
21/// Without `interpolation` feature: `BTreeMap<Time, T>`.
22///
23/// With `interpolation` feature an entry supports a [`Key`]s:
24/// `BTreeMap<Time, (T, Option<Key<T>>)>`.
25#[derive(Clone, Debug, PartialEq, Hash)]
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27pub struct TimeDataMap<T> {
28    /// The time-value pairs with optional interpolation keys.
29    #[cfg(not(feature = "interpolation"))]
30    pub values: BTreeMap<Time, T>,
31    #[cfg(feature = "interpolation")]
32    pub values: BTreeMap<Time, (T, Option<crate::Key<T>>)>,
33}
34
35// Manual Eq implementation.
36impl<T: Eq> Eq for TimeDataMap<T> {}
37
38// AsRef implementation for backward compatibility.
39#[cfg(not(feature = "interpolation"))]
40impl<T> AsRef<BTreeMap<Time, T>> for TimeDataMap<T> {
41    fn as_ref(&self) -> &BTreeMap<Time, T> {
42        &self.values
43    }
44}
45
46// AIDEV-NOTE: These methods provide backward compatibility for code that
47// previously accessed the BTreeMap directly via .0 field.
48// With interpolation feature, these return a view without interpolation specs.
49impl<T> TimeDataMap<T> {
50    /// Get an iterator over time-value pairs.
51    #[inline]
52    pub fn iter(&self) -> impl Iterator<Item = (&Time, &T)> {
53        #[cfg(not(feature = "interpolation"))]
54        {
55            self.values.iter()
56        }
57        #[cfg(feature = "interpolation")]
58        {
59            self.values.iter().map(|(t, (v, _))| (t, v))
60        }
61    }
62
63    /// Check if empty.
64    #[inline]
65    pub fn is_empty(&self) -> bool {
66        self.values.is_empty()
67    }
68
69    /// Get the number of entries.
70    #[inline]
71    pub fn len(&self) -> usize {
72        self.values.len()
73    }
74}
75
76// Constructor for backward compatibility.
77impl<T> From<BTreeMap<Time, T>> for TimeDataMap<T> {
78    fn from(values: BTreeMap<Time, T>) -> Self {
79        #[cfg(not(feature = "interpolation"))]
80        {
81            Self { values }
82        }
83        #[cfg(feature = "interpolation")]
84        {
85            Self {
86                values: values.into_iter().map(|(k, v)| (k, (v, None))).collect(),
87            }
88        }
89    }
90}
91
92/// Control operations `trait` for time data maps.
93#[enum_dispatch]
94pub trait TimeDataMapControl<T> {
95    /// Returns the number of time samples.
96    fn len(&self) -> usize;
97    /// Returns `true` if there are no time samples.
98    fn is_empty(&self) -> bool;
99    /// Returns `true` if there is more than one time sample.
100    fn is_animated(&self) -> bool;
101}
102
103impl<T> TimeDataMapControl<T> for TimeDataMap<T> {
104    fn len(&self) -> usize {
105        self.values.len()
106    }
107
108    fn is_empty(&self) -> bool {
109        self.values.is_empty()
110    }
111
112    fn is_animated(&self) -> bool {
113        1 < self.values.len()
114    }
115}
116
117impl<T> crate::DataTypeOps for TimeDataMap<T>
118where
119    T: crate::DataTypeOps,
120{
121    fn data_type(&self) -> DataType {
122        // Use the first element to determine the type, or return a default if empty.
123        #[cfg(not(feature = "interpolation"))]
124        {
125            self.values
126                .values()
127                .next()
128                .map(|v| v.data_type())
129                .unwrap_or(DataType::Real)
130        }
131        #[cfg(feature = "interpolation")]
132        {
133            self.values
134                .values()
135                .next()
136                .map(|(v, _)| v.data_type())
137                .unwrap_or(DataType::Real)
138        }
139    }
140
141    fn type_name(&self) -> &'static str {
142        // Use the first element to determine the type name.
143        #[cfg(not(feature = "interpolation"))]
144        {
145            self.values
146                .values()
147                .next()
148                .map(|v| v.type_name())
149                .unwrap_or("unknown")
150        }
151        #[cfg(feature = "interpolation")]
152        {
153            self.values
154                .values()
155                .next()
156                .map(|(v, _)| v.type_name())
157                .unwrap_or("unknown")
158        }
159    }
160}
161
162macro_rules! impl_from_at_time {
163    ($($t:ty),+) => {
164        $(
165            impl From<(Time, $t)> for TimeDataMap<$t> {
166                fn from((time, value): (Time, $t)) -> Self {
167                    TimeDataMap::from(BTreeMap::from([(time, value)]))
168                }
169            }
170        )+
171    };
172}
173
174impl_from_at_time!(
175    Boolean, Real, Integer, String, Color, BooleanVec, RealVec, IntegerVec, StringVec, ColorVec,
176    Data
177);
178
179#[cfg(feature = "vector2")]
180impl_from_at_time!(Vector2);
181#[cfg(feature = "vector3")]
182impl_from_at_time!(Vector3);
183#[cfg(feature = "matrix3")]
184impl_from_at_time!(Matrix3);
185#[cfg(feature = "normal3")]
186impl_from_at_time!(Normal3);
187#[cfg(feature = "point3")]
188impl_from_at_time!(Point3);
189#[cfg(feature = "matrix4")]
190impl_from_at_time!(Matrix4);
191
192#[cfg(all(feature = "vector2", feature = "vec_variants"))]
193impl_from_at_time!(Vector2Vec);
194#[cfg(all(feature = "vector3", feature = "vec_variants"))]
195impl_from_at_time!(Vector3Vec);
196#[cfg(all(feature = "matrix3", feature = "vec_variants"))]
197impl_from_at_time!(Matrix3Vec);
198#[cfg(all(feature = "normal3", feature = "vec_variants"))]
199impl_from_at_time!(Normal3Vec);
200#[cfg(all(feature = "point3", feature = "vec_variants"))]
201impl_from_at_time!(Point3Vec);
202#[cfg(all(feature = "matrix4", feature = "vec_variants"))]
203impl_from_at_time!(Matrix4Vec);
204
205impl<T> TimeDataMap<T> {
206    /// Insert a value at the given time.
207    pub fn insert(&mut self, time: Time, value: T) {
208        #[cfg(not(feature = "interpolation"))]
209        {
210            self.values.insert(time, value);
211        }
212        #[cfg(feature = "interpolation")]
213        {
214            self.values.insert(time, (value, None));
215        }
216    }
217
218    /// Get a value at the exact time.
219    pub fn get(&self, time: &Time) -> Option<&T> {
220        #[cfg(not(feature = "interpolation"))]
221        {
222            self.values.get(time)
223        }
224        #[cfg(feature = "interpolation")]
225        {
226            self.values.get(time).map(|(v, _)| v)
227        }
228    }
229}
230
231impl<T> TimeDataMap<T>
232where
233    T: Clone + Add<Output = T> + Mul<f32, Output = T> + Sub<Output = T>,
234{
235    pub fn interpolate(&self, time: Time) -> T {
236        #[cfg(not(feature = "interpolation"))]
237        {
238            interpolate(&self.values, time)
239        }
240        #[cfg(feature = "interpolation")]
241        {
242            interpolate_with_specs(&self.values, time)
243        }
244    }
245}
246
247// Interpolation-specific methods.
248#[cfg(feature = "interpolation")]
249impl<T> TimeDataMap<T> {
250    /// Insert a value with interpolation specification.
251    ///
252    /// Sets both the value at the given time and how it should interpolate
253    /// to neighboring keyframes.
254    pub fn insert_with_interpolation(&mut self, time: Time, value: T, spec: crate::Key<T>) {
255        self.values.insert(time, (value, Some(spec)));
256    }
257
258    /// Get interpolation spec at a given time.
259    ///
260    /// Returns `None` if no interpolation metadata exists or no spec at this time.
261    pub fn get_interpolation(&self, time: &Time) -> Option<&crate::Key<T>> {
262        self.values.get(time)?.1.as_ref()
263    }
264
265    /// Clear all interpolation metadata.
266    ///
267    /// After calling this, all interpolation reverts to automatic mode.
268    pub fn clear_interpolation(&mut self) {
269        for (_, interp) in self.values.values_mut() {
270            *interp = None;
271        }
272    }
273
274    /// Check if using custom interpolation.
275    ///
276    /// Returns `true` if any interpolation metadata is present.
277    pub fn has_interpolation(&self) -> bool {
278        self.values.values().any(|(_, interp)| interp.is_some())
279    }
280}
281
282impl<T, V> FromIterator<(Time, V)> for TimeDataMap<T>
283where
284    V: Into<T>,
285{
286    fn from_iter<I>(iter: I) -> Self
287    where
288        I: IntoIterator<Item = (Time, V)>,
289    {
290        Self::from(BTreeMap::from_iter(
291            iter.into_iter().map(|(t, v)| (t, v.into())),
292        ))
293    }
294}
295
296impl<T> TimeDataMap<T> {
297    pub fn closest_sample(&self, time: Time) -> &T {
298        #[cfg(not(feature = "interpolation"))]
299        {
300            let mut range = self.values.range(time..);
301            let greater_or_equal = range.next();
302
303            let mut range = self.values.range(..time);
304            let less_than = range.next_back();
305
306            match (less_than, greater_or_equal) {
307                (Some((lower_k, lower_v)), Some((upper_k, upper_v))) => {
308                    if (time.as_ref() - lower_k.as_ref()).abs()
309                        <= (upper_k.as_ref() - time.as_ref()).abs()
310                    {
311                        lower_v
312                    } else {
313                        upper_v
314                    }
315                }
316                (Some(entry), None) | (None, Some(entry)) => entry.1,
317                (None, None) => {
318                    unreachable!("TimeDataMap can never be empty")
319                }
320            }
321        }
322        #[cfg(feature = "interpolation")]
323        {
324            let mut range = self.values.range(time..);
325            let greater_or_equal = range.next();
326
327            let mut range = self.values.range(..time);
328            let less_than = range.next_back();
329
330            match (less_than, greater_or_equal) {
331                (Some((lower_k, (lower_v, _))), Some((upper_k, (upper_v, _)))) => {
332                    if (time.as_ref() - lower_k.as_ref()).abs()
333                        <= (upper_k.as_ref() - time.as_ref()).abs()
334                    {
335                        lower_v
336                    } else {
337                        upper_v
338                    }
339                }
340                (Some((_, (v, _))), None) | (None, Some((_, (v, _)))) => v,
341                (None, None) => {
342                    unreachable!("TimeDataMap can never be empty")
343                }
344            }
345        }
346    }
347
348    /// Sample value at exact time (no interpolation).
349    pub fn sample_at(&self, time: Time) -> Option<&T> {
350        self.get(&time)
351    }
352
353    /// Get the value at or before the given time.
354    pub fn sample_at_or_before(&self, time: Time) -> Option<&T> {
355        #[cfg(not(feature = "interpolation"))]
356        {
357            self.values.range(..=time).next_back().map(|(_, v)| v)
358        }
359        #[cfg(feature = "interpolation")]
360        {
361            self.values.range(..=time).next_back().map(|(_, (v, _))| v)
362        }
363    }
364
365    /// Get the value at or after the given time.
366    pub fn sample_at_or_after(&self, time: Time) -> Option<&T> {
367        #[cfg(not(feature = "interpolation"))]
368        {
369            self.values.range(time..).next().map(|(_, v)| v)
370        }
371        #[cfg(feature = "interpolation")]
372        {
373            self.values.range(time..).next().map(|(_, (v, _))| v)
374        }
375    }
376
377    /// Get surrounding samples for interpolation.
378    ///
379    /// Returns up to `N` samples centered around the given time for
380    /// use in interpolation algorithms.
381    pub fn sample_surrounding<const N: usize>(&self, time: Time) -> SmallVec<[(Time, &T); N]> {
382        // Get samples before the time.
383        let before_count = N / 2;
384
385        #[cfg(not(feature = "interpolation"))]
386        {
387            let mut result = self
388                .values
389                .range(..time)
390                .rev()
391                .take(before_count)
392                .map(|(t, v)| (*t, v))
393                .collect::<SmallVec<[_; N]>>();
394            result.reverse();
395
396            // Get samples at or after the time.
397            let after_count = N - result.len();
398            result.extend(
399                self.values
400                    .range(time..)
401                    .take(after_count)
402                    .map(|(t, v)| (*t, v)),
403            );
404            result
405        }
406
407        #[cfg(feature = "interpolation")]
408        {
409            let mut result = self
410                .values
411                .range(..time)
412                .rev()
413                .take(before_count)
414                .map(|(t, (v, _))| (*t, v))
415                .collect::<SmallVec<[_; N]>>();
416            result.reverse();
417
418            // Get samples at or after the time.
419            let after_count = N - result.len();
420            result.extend(
421                self.values
422                    .range(time..)
423                    .take(after_count)
424                    .map(|(t, (v, _))| (*t, v)),
425            );
426            result
427        }
428    }
429}
430
431#[cfg(feature = "matrix3")]
432#[inline(always)]
433pub(crate) fn interpolate_spherical_linear(
434    map: &BTreeMap<Time, nalgebra::Rotation2<f32>>,
435    time: Time,
436) -> nalgebra::Rotation2<f32> {
437    if map.len() == 1 {
438        return *map.values().next().unwrap();
439    }
440
441    let first = map.iter().next().unwrap();
442    let last = map.iter().next_back().unwrap();
443
444    if time <= *first.0 {
445        return *first.1;
446    }
447
448    if time >= *last.0 {
449        return *last.1;
450    }
451
452    let lower = map.range(..=time).next_back().unwrap();
453    let upper = map.range(time..).next().unwrap();
454
455    let (t0, r0): (f32, _) = (lower.0.into(), lower.1);
456    let (t1, r1): (f32, _) = (upper.0.into(), upper.1);
457
458    // Normalize `t`.
459    let t: f32 = (f32::from(time) - t0) / (t1 - t0);
460
461    r0.slerp(r1, t)
462}
463
464// AIDEV-NOTE: Helper functions for converting BezierHandle variants to slopes and evaluating mixed interpolation modes.
465
466#[cfg(feature = "interpolation")]
467fn bezier_handle_to_slope<V>(
468    handle: &crate::BezierHandle<V>,
469    t1: f32,
470    t2: f32,
471    _v1: &V,
472    _v2: &V,
473) -> Option<V>
474where
475    V: Clone + Add<Output = V> + Sub<Output = V> + Mul<f32, Output = V>,
476{
477    match handle {
478        crate::BezierHandle::SlopePerSecond(s) => Some(s.clone()),
479        crate::BezierHandle::SlopePerFrame(s) => {
480            let frames = (t2 - t1).max(f32::EPSILON);
481            Some(s.clone() * (frames / (t2 - t1)))
482        }
483        crate::BezierHandle::Delta { time, value } => {
484            let time_f32: f32 = (*time).into();
485            if time_f32.abs() <= f32::EPSILON {
486                None
487            } else {
488                Some(value.clone() * (1.0 / time_f32))
489            }
490        }
491        crate::BezierHandle::Angle(_) => None, // angle needs higher-level context; fall back later
492    }
493}
494
495#[cfg(feature = "interpolation")]
496#[allow(clippy::too_many_arguments)]
497fn smooth_tangent<V>(
498    t1: f32,
499    v1: &V,
500    t2: f32,
501    v2: &V,
502    map: &BTreeMap<Time, (V, Option<crate::Key<V>>)>,
503    _time: Time,
504    anchor: Time,
505    incoming: bool,
506) -> V
507where
508    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
509{
510    use smallvec::SmallVec;
511
512    // Minimal Catmull-style tangent: reuse nearby keys via existing helpers.
513    // We reuse interpolate() on a virtual window to let the hermite logic derive slopes.
514    let mut window = SmallVec::<[(Time, V); 2]>::new();
515
516    if incoming {
517        if let Some(prev) = map.range(..anchor).next_back() {
518            window.push((*prev.0, prev.1.0.clone()));
519        }
520        window.push((anchor, v1.clone()));
521    } else {
522        window.push((anchor, v1.clone()));
523        if let Some(next) = map.range(anchor..).nth(1) {
524            window.push((*next.0, next.1.0.clone()));
525        }
526    }
527
528    if window.len() == 2 {
529        let (t0, p0) = &window[0];
530        let (t1, p1) = &window[1];
531        let t0_f: f32 = (*t0).into();
532        let t1_f: f32 = (*t1).into();
533        let dt = (t1_f - t0_f).max(f32::EPSILON);
534        (p1.clone() - p0.clone()) * (1.0 / dt)
535    } else {
536        // Fall back to local linear slope.
537        (v2.clone() - v1.clone()) * (1.0 / (t2 - t1).max(f32::EPSILON))
538    }
539}
540
541#[cfg(feature = "interpolation")]
542fn evaluate_mixed_bezier<V>(
543    time: Time,
544    t1: Time,
545    v1: &V,
546    slope_out: &V,
547    t2: Time,
548    v2: &V,
549    slope_in: &V,
550) -> V
551where
552    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
553{
554    use crate::interpolation::bezier_helpers::*;
555
556    let (p1, p2) = control_points_from_slopes(t1.into(), v1, slope_out, t2.into(), v2, slope_in);
557
558    evaluate_bezier_component_wise(
559        time.into(),
560        (t1.into(), v1),
561        (p1.0, &p1.1),
562        (p2.0, &p2.1),
563        (t2.into(), v2),
564    )
565}
566
567/// Interpolate with respect to interpolation specifications.
568///
569/// This function checks for custom interpolation specs and uses them if available,
570/// otherwise falls back to automatic interpolation.
571#[cfg(feature = "interpolation")]
572#[inline(always)]
573pub(crate) fn interpolate_with_specs<V>(
574    map: &BTreeMap<Time, (V, Option<crate::Key<V>>)>,
575    time: Time,
576) -> V
577where
578    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
579{
580    if map.len() == 1 {
581        return map.values().next().unwrap().0.clone();
582    }
583
584    let first = map.iter().next().unwrap();
585    let last = map.iter().next_back().unwrap();
586
587    if time <= *first.0 {
588        return first.1.0.clone();
589    }
590
591    if time >= *last.0 {
592        return last.1.0.clone();
593    }
594
595    // Find surrounding keyframes.
596    let lower = map.range(..time).next_back().unwrap();
597    let upper = map.range(time..).next().unwrap();
598
599    // If we're exactly on a keyframe, return its value.
600    if lower.0 == upper.0 {
601        return lower.1.0.clone();
602    }
603
604    let (t1, (v1, spec1)) = lower;
605    let (t2, (v2, spec2)) = upper;
606
607    // Check if we have interpolation specs.
608    let interp_out = spec1.as_ref().map(|s| &s.interpolation_out);
609    let interp_in = spec2.as_ref().map(|s| &s.interpolation_in);
610
611    // AIDEV-NOTE: Determine interpolation mode based on specs.
612    // Hold takes precedence over everything else, then explicit Bezier handles,
613    // then Smooth (Catmull-Rom style), then Linear, then fallback to automatic.
614    match (interp_out, interp_in) {
615        // Step/hold beats everything else.
616        (Some(crate::Interpolation::Hold), _) | (_, Some(crate::Interpolation::Hold)) => v1.clone(),
617
618        // Both sides supply explicit handles -- run a cubic Bezier with those tangents.
619        (
620            Some(crate::Interpolation::Bezier(out_handle)),
621            Some(crate::Interpolation::Bezier(in_handle)),
622        ) => {
623            use crate::interpolation::bezier_helpers::*;
624
625            if let (Some(slope_out), Some(slope_in)) = (
626                bezier_handle_to_slope(out_handle, (*t1).into(), (*t2).into(), v1, v2),
627                bezier_handle_to_slope(in_handle, (*t1).into(), (*t2).into(), v1, v2),
628            ) {
629                let (p1, p2) = control_points_from_slopes(
630                    (*t1).into(),
631                    v1,
632                    &slope_out,
633                    (*t2).into(),
634                    v2,
635                    &slope_in,
636                );
637
638                evaluate_bezier_component_wise(
639                    time.into(),
640                    ((*t1).into(), v1),
641                    (p1.0, &p1.1),
642                    (p2.0, &p2.1),
643                    ((*t2).into(), v2),
644                )
645            } else {
646                // Fall back to linear if we can't convert handles to slopes.
647                linear_interp(*t1, *t2, v1, v2, time)
648            }
649        }
650
651        // One side explicit, the other "smooth": derive a Catmull-style tangent for the smooth side.
652        (Some(crate::Interpolation::Bezier(out_handle)), Some(crate::Interpolation::Smooth)) => {
653            if let Some(slope_out) =
654                bezier_handle_to_slope(out_handle, (*t1).into(), (*t2).into(), v1, v2)
655            {
656                let slope_in =
657                    smooth_tangent((*t1).into(), v1, (*t2).into(), v2, map, time, *t1, false);
658
659                evaluate_mixed_bezier(time, *t1, v1, &slope_out, *t2, v2, &slope_in)
660            } else {
661                linear_interp(*t1, *t2, v1, v2, time)
662            }
663        }
664        (Some(crate::Interpolation::Smooth), Some(crate::Interpolation::Bezier(in_handle))) => {
665            if let Some(slope_in) =
666                bezier_handle_to_slope(in_handle, (*t1).into(), (*t2).into(), v1, v2)
667            {
668                let slope_out =
669                    smooth_tangent((*t1).into(), v1, (*t2).into(), v2, map, time, *t1, true);
670
671                evaluate_mixed_bezier(time, *t1, v1, &slope_out, *t2, v2, &slope_in)
672            } else {
673                linear_interp(*t1, *t2, v1, v2, time)
674            }
675        }
676
677        // Symmetric "smooth" -> fall back to the existing automatic interpolation.
678        (Some(crate::Interpolation::Smooth), Some(crate::Interpolation::Smooth)) | (None, None) => {
679            let values_only: BTreeMap<Time, V> =
680                map.iter().map(|(k, (v, _))| (*k, v.clone())).collect();
681            interpolate(&values_only, time)
682        }
683
684        // Linear/linear, linear vs smooth, or any unsupported combination -> straight line.
685        _ => linear_interp(*t1, *t2, v1, v2, time),
686    }
687}
688
689#[inline(always)]
690pub(crate) fn interpolate<V>(map: &BTreeMap<Time, V>, time: Time) -> V
691where
692    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
693{
694    if map.len() == 1 {
695        return map.values().next().unwrap().clone();
696    }
697
698    let first = map.iter().next().unwrap();
699    let last = map.iter().next_back().unwrap();
700
701    if time <= *first.0 {
702        return first.1.clone();
703    }
704
705    if time >= *last.0 {
706        return last.1.clone();
707    }
708
709    let lower = map.range(..time).next_back().unwrap();
710    let upper = map.range(time..).next().unwrap();
711
712    // This is our window for interpolation that holds two to four values.
713    //
714    // The interpolation algorithm is chosen based on how many values are in
715    // the window. See the `match` block below.
716    let mut window = SmallVec::<[(Time, &V); 4]>::new();
717
718    // Extend with up to two values before `lower`.
719    window.extend(map.range(..*lower.0).rev().take(2).map(|(k, v)| (*k, v)));
720
721    // Add `lower` and `upper` (if distinct).
722    window.push((*lower.0, lower.1));
723
724    if lower.0 != upper.0 {
725        window.push((*upper.0, upper.1));
726    }
727
728    // Extend with up to one value after `upper`.
729    window.extend(map.range(*upper.0..).skip(1).take(1).map(|(k, v)| (*k, v)));
730
731    // Ensure chronological order.
732    window.reverse();
733
734    match window.len() {
735        4 => {
736            let (t0, p0) = window[0];
737            let (t1, p1) = window[1];
738            let (t2, p2) = window[2];
739            let (t3, p3) = window[3];
740            hermite_interp(HermiteParams {
741                t0,
742                t1,
743                t2,
744                t3,
745                p0,
746                p1,
747                p2,
748                p3,
749                t: time,
750            })
751        }
752        3 => {
753            let (t0, p0) = window[0];
754            let (t1, p1) = window[1];
755            let (t2, p2) = window[2];
756            quadratic_interp(t0, t1, t2, p0, p1, p2, time)
757        }
758        2 => {
759            let (x0, y0) = window[0];
760            let (x1, y1) = window[1];
761            linear_interp(x0, x1, y0, y1, time)
762        }
763        1 => {
764            // Single keyframe - return its value.
765            window[0].1.clone()
766        }
767        0 => {
768            // This shouldn't happen given the checks above, but be defensive.
769            panic!("Interpolation window is empty - this is a bug in token-value-map")
770        }
771        _ => {
772            // Window has more than 4 elements - shouldn't happen but use first 4.
773            let (t0, p0) = window[0];
774            let (t1, p1) = window[1];
775            let (t2, p2) = window[2];
776            let (t3, p3) = window[3];
777            hermite_interp(HermiteParams {
778                t0,
779                t1,
780                t2,
781                t3,
782                p0,
783                p1,
784                p2,
785                p3,
786                t: time,
787            })
788        }
789    }
790}
791
792#[inline(always)]
793fn linear_interp<V, T>(x0: T, x1: T, y0: &V, y1: &V, x: T) -> V
794where
795    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
796    T: Into<f32>,
797{
798    let x0 = x0.into();
799    let x1 = x1.into();
800    let x = x.into();
801    let alpha = (x - x0) / (x1 - x0);
802    y0.clone() + (y1.clone() - y0.clone()) * alpha
803}
804
805#[inline(always)]
806fn quadratic_interp<V, T>(x0: T, x1: T, x2: T, y0: &V, y1: &V, y2: &V, x: T) -> V
807where
808    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
809    T: Into<f32>,
810{
811    let x0 = x0.into();
812    let x1 = x1.into();
813    let x2 = x2.into();
814    let x = x.into();
815    let a = (x - x1) * (x - x2) / ((x0 - x1) * (x0 - x2));
816    let b = (x - x0) * (x - x2) / ((x1 - x0) * (x1 - x2));
817    let c = (x - x0) * (x - x1) / ((x2 - x0) * (x2 - x1));
818
819    y0.clone() * a + y1.clone() * b + y2.clone() * c
820}
821
822struct HermiteParams<V, T> {
823    t0: T,
824    t1: T,
825    t2: T,
826    t3: T,
827    p0: V,
828    p1: V,
829    p2: V,
830    p3: V,
831    t: T,
832}
833
834#[inline(always)]
835fn hermite_interp<V, T>(params: HermiteParams<&V, T>) -> V
836where
837    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
838    T: Into<f32>,
839{
840    let t0 = params.t0.into();
841    let t1 = params.t1.into();
842    let t2 = params.t2.into();
843    let t3 = params.t3.into();
844    let t = params.t.into();
845
846    // Account for knot positions
847    let tau = if (t2 - t1).abs() < f32::EPSILON {
848        0.0 // Handle degenerate case where t1 == t2
849    } else {
850        (t - t1) / (t2 - t1)
851    };
852
853    // Calculate tension/bias parameters based on the spacing between knots.
854    // This makes the spline respond to actual time intervals rather than
855    // assuming uniform spacing.
856    let tension1 = if (t1 - t0).abs() < f32::EPSILON {
857        1.0
858    } else {
859        (t2 - t1) / (t1 - t0)
860    };
861    let tension2 = if (t3 - t2).abs() < f32::EPSILON {
862        1.0
863    } else {
864        (t2 - t1) / (t3 - t2)
865    };
866
867    // Tangent vectors that respect the actual knot spacing
868    let m1 = (params.p2.clone() - params.p0.clone()) * (0.5 * tension1);
869    let m2 = (params.p3.clone() - params.p1.clone()) * (0.5 * tension2);
870
871    // Hermite basis functions
872    let tau2 = tau * tau;
873    let tau3 = tau2 * tau;
874
875    let h00 = 2.0 * tau3 - 3.0 * tau2 + 1.0;
876    let h10 = tau3 - 2.0 * tau2 + tau;
877    let h01 = -2.0 * tau3 + 3.0 * tau2;
878    let h11 = tau3 - tau2;
879
880    // Hermite interpolation with proper tangent vectors
881    params.p1.clone() * h00 + m1 * h10 + params.p2.clone() * h01 + m2 * h11
882}
883
884#[cfg(feature = "matrix3")]
885#[inline(always)]
886fn decompose_matrix(
887    matrix: &nalgebra::Matrix3<f32>,
888) -> (
889    nalgebra::Vector2<f32>,
890    nalgebra::Rotation2<f32>,
891    nalgebra::Matrix3<f32>,
892) {
893    // Extract translation (assuming the matrix is in homogeneous coordinates).
894    let translation = nalgebra::Vector2::new(matrix[(0, 2)], matrix[(1, 2)]);
895
896    // Extract the linear part (upper-left 2x2 matrix).
897    let linear_part = matrix.fixed_view::<2, 2>(0, 0).into_owned();
898
899    // Perform Singular Value Decomposition (SVD) to separate rotation and
900    // stretch.
901    let svd = nalgebra::SVD::new(linear_part, true, true);
902    let rotation = nalgebra::Rotation2::from_matrix(&svd.u.unwrap().into_owned());
903
904    // Construct the stretch matrix from singular values.
905    let singular_values = svd.singular_values;
906    let stretch = nalgebra::Matrix3::new(
907        singular_values[0],
908        0.0,
909        0.0,
910        0.0,
911        singular_values[1],
912        0.0,
913        0.0,
914        0.0,
915        1.0, // Homogeneous coordinate
916    );
917
918    (translation, rotation, stretch)
919}
920
921#[cfg(feature = "matrix3")]
922#[inline(always)]
923fn recompose_matrix(
924    translation: nalgebra::Vector2<f32>,
925    rotation: nalgebra::Rotation2<f32>,
926    stretch: nalgebra::Matrix3<f32>,
927) -> nalgebra::Matrix3<f32> {
928    let rotation = rotation.to_homogeneous();
929    let mut combined_linear = rotation * stretch;
930
931    combined_linear[(0, 2)] = translation.x;
932    combined_linear[(1, 2)] = translation.y;
933
934    combined_linear
935}