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