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