Skip to main content

token_value_map/time_data_map/
mod.rs

1use crate::*;
2use enum_dispatch::enum_dispatch;
3use mitsein::btree_map1::BTreeMap1;
4
5use std::{
6    collections::BTreeMap,
7    iter::FromIterator,
8    ops::{Add, Mul, Sub},
9};
10
11mod sample;
12pub use sample::*;
13
14/// A generic key-value data map with interpolation support.
15///
16/// Stores key-value pairs in a [`BTreeMap1`] for efficient ordered
17/// queries and supports various interpolation methods. The map is
18/// guaranteed to be non-empty at the type level.
19///
20/// When the `interpolation` feature is enabled, each value can have an
21/// associated interpolation specification for animation curves.
22///
23/// Use the type alias [`TimeDataMap<V>`] for time-keyed maps (the common case),
24/// or use `KeyDataMap<Position, V>` for curve-domain maps.
25// AIDEV-NOTE: BTreeMap1 enforces the non-empty invariant at the type level.
26// This eliminates the class of panics from unwrap() on empty-map iteration.
27#[derive(Clone, Debug, PartialEq, Hash)]
28#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
29#[cfg_attr(
30    feature = "serde",
31    serde(bound(
32        serialize = "K: Serialize + Ord, V: Serialize",
33        deserialize = "K: Deserialize<'de> + Ord, V: Deserialize<'de>",
34    ))
35)]
36#[cfg_attr(feature = "facet", derive(Facet))]
37#[cfg_attr(feature = "facet", facet(opaque))]
38pub struct KeyDataMap<K, V> {
39    /// The key-value pairs with optional interpolation keys.
40    ///
41    /// Guaranteed non-empty by [`BTreeMap1`].
42    #[cfg(not(feature = "interpolation"))]
43    pub values: BTreeMap1<K, V>,
44    #[cfg(feature = "interpolation")]
45    pub values: BTreeMap1<K, (V, Option<crate::Key<V>>)>,
46}
47
48/// A time-keyed data map. Alias for `KeyDataMap<Time, V>`.
49pub type TimeDataMap<V> = KeyDataMap<Time, V>;
50
51// Manual Eq implementation.
52impl<K: Eq, V: Eq> Eq for KeyDataMap<K, V> {}
53
54// AsRef implementation for backward compatibility.
55#[cfg(not(feature = "interpolation"))]
56impl<K, V> AsRef<BTreeMap<K, V>> for KeyDataMap<K, V> {
57    fn as_ref(&self) -> &BTreeMap<K, V> {
58        self.values.as_btree_map()
59    }
60}
61
62impl<K: Ord, V> KeyDataMap<K, V> {
63    /// Get an iterator over key-value pairs.
64    #[inline]
65    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
66        #[cfg(not(feature = "interpolation"))]
67        {
68            self.values.as_btree_map().iter()
69        }
70        #[cfg(feature = "interpolation")]
71        {
72            self.values.as_btree_map().iter().map(|(k, (v, _))| (k, v))
73        }
74    }
75
76    /// Always returns `false` -- [`BTreeMap1`] guarantees at least one entry.
77    #[inline]
78    pub fn is_empty(&self) -> bool {
79        false
80    }
81
82    /// Get the number of entries.
83    #[inline]
84    pub fn len(&self) -> usize {
85        self.values.len().get()
86    }
87
88    /// Remove a sample at the given key.
89    ///
90    /// Returns `Ok(Some(value))` if the key existed and was removed,
91    /// `Ok(None)` if the key was not found, or `Err(LastSample)` if
92    /// removing the key would empty the map.
93    #[inline]
94    pub fn remove(&mut self, key: &K) -> crate::Result<Option<V>> {
95        if !self.values.contains_key(key) {
96            return Ok(None);
97        }
98        if self.values.len().get() == 1 {
99            return Err(crate::Error::LastSample);
100        }
101        // SAFETY: len >= 2 and key exists, so removing won't empty the map.
102        #[cfg(not(feature = "interpolation"))]
103        {
104            Ok(unsafe { self.values.as_mut_btree_map() }.remove(key))
105        }
106        #[cfg(feature = "interpolation")]
107        {
108            Ok(unsafe { self.values.as_mut_btree_map() }
109                .remove(key)
110                .map(|(v, _)| v))
111        }
112    }
113}
114
115// Fallible constructor from BTreeMap.
116impl<K: Ord, V> TryFrom<BTreeMap<K, V>> for KeyDataMap<K, V> {
117    type Error = crate::Error;
118
119    fn try_from(values: BTreeMap<K, V>) -> crate::Result<Self> {
120        #[cfg(not(feature = "interpolation"))]
121        {
122            let values = BTreeMap1::try_from(values).map_err(|_| crate::Error::EmptySamples)?;
123            Ok(Self { values })
124        }
125        #[cfg(feature = "interpolation")]
126        {
127            let interp_values: BTreeMap<K, (V, Option<crate::Key<V>>)> =
128                values.into_iter().map(|(k, v)| (k, (v, None))).collect();
129            let values =
130                BTreeMap1::try_from(interp_values).map_err(|_| crate::Error::EmptySamples)?;
131            Ok(Self { values })
132        }
133    }
134}
135
136// Direct constructor from BTreeMap1 (already validated non-empty).
137#[cfg(not(feature = "interpolation"))]
138impl<K: Ord, V> From<BTreeMap1<K, V>> for KeyDataMap<K, V> {
139    fn from(values: BTreeMap1<K, V>) -> Self {
140        Self { values }
141    }
142}
143
144impl<K: Ord, V> KeyDataMap<K, V> {
145    /// Create a map with a single key-value pair.
146    pub fn from_single(key: K, value: V) -> Self {
147        #[cfg(not(feature = "interpolation"))]
148        {
149            Self {
150                values: BTreeMap1::from_one((key, value)),
151            }
152        }
153        #[cfg(feature = "interpolation")]
154        {
155            Self {
156                values: BTreeMap1::from_one((key, (value, None))),
157            }
158        }
159    }
160}
161
162/// Control operations `trait` for time data maps.
163#[enum_dispatch]
164pub trait TimeDataMapControl<T> {
165    /// Returns the number of time samples.
166    fn len(&self) -> usize;
167    /// Returns `true` if there are no time samples.
168    fn is_empty(&self) -> bool;
169    /// Returns `true` if there is more than one time sample.
170    fn is_animated(&self) -> bool;
171}
172
173impl<V> TimeDataMapControl<V> for KeyDataMap<Time, V> {
174    fn len(&self) -> usize {
175        self.values.len().get()
176    }
177
178    fn is_empty(&self) -> bool {
179        false
180    }
181
182    fn is_animated(&self) -> bool {
183        self.values.len().get() > 1
184    }
185}
186
187#[cfg(feature = "builtin-types")]
188impl<K: Ord, V> crate::DataTypeOps for KeyDataMap<K, V>
189where
190    V: crate::DataTypeOps,
191{
192    fn data_type(&self) -> crate::DataType {
193        // SAFETY: BTreeMap1 guarantees at least one entry.
194        #[cfg(not(feature = "interpolation"))]
195        {
196            self.values.first_key_value().1.data_type()
197        }
198        #[cfg(feature = "interpolation")]
199        {
200            self.values.first_key_value().1.0.data_type()
201        }
202    }
203
204    fn type_name(&self) -> &'static str {
205        // SAFETY: BTreeMap1 guarantees at least one entry.
206        #[cfg(not(feature = "interpolation"))]
207        {
208            self.values.first_key_value().1.type_name()
209        }
210        #[cfg(feature = "interpolation")]
211        {
212            self.values.first_key_value().1.0.type_name()
213        }
214    }
215}
216
217// AIDEV-NOTE: These From impls are only available with builtin-types feature.
218// They delegate to KeyDataMap::from_single which uses BTreeMap1::from_one.
219#[cfg(feature = "builtin-types")]
220macro_rules! impl_from_at_time {
221    ($($t:ty),+) => {
222        $(
223            impl From<(Time, $t)> for TimeDataMap<$t> {
224                fn from((time, value): (Time, $t)) -> Self {
225                    KeyDataMap::from_single(time, value)
226                }
227            }
228        )+
229    };
230}
231
232#[cfg(feature = "builtin-types")]
233impl_from_at_time!(
234    Boolean, Real, Integer, String, Color, BooleanVec, RealVec, IntegerVec, StringVec, ColorVec,
235    Data
236);
237
238#[cfg(all(feature = "builtin-types", feature = "curves"))]
239impl_from_at_time!(RealCurve, ColorCurve);
240
241#[cfg(all(feature = "builtin-types", feature = "vector2"))]
242impl_from_at_time!(Vector2);
243#[cfg(all(feature = "builtin-types", feature = "vector3"))]
244impl_from_at_time!(Vector3);
245#[cfg(all(feature = "builtin-types", feature = "matrix3"))]
246impl_from_at_time!(Matrix3);
247#[cfg(all(feature = "builtin-types", feature = "normal3"))]
248impl_from_at_time!(Normal3);
249#[cfg(all(feature = "builtin-types", feature = "point3"))]
250impl_from_at_time!(Point3);
251#[cfg(all(feature = "builtin-types", feature = "matrix4"))]
252impl_from_at_time!(Matrix4);
253
254#[cfg(all(
255    feature = "builtin-types",
256    feature = "vector2",
257    feature = "vec_variants"
258))]
259impl_from_at_time!(Vector2Vec);
260#[cfg(all(
261    feature = "builtin-types",
262    feature = "vector3",
263    feature = "vec_variants"
264))]
265impl_from_at_time!(Vector3Vec);
266#[cfg(all(
267    feature = "builtin-types",
268    feature = "matrix3",
269    feature = "vec_variants"
270))]
271impl_from_at_time!(Matrix3Vec);
272#[cfg(all(
273    feature = "builtin-types",
274    feature = "normal3",
275    feature = "vec_variants"
276))]
277impl_from_at_time!(Normal3Vec);
278#[cfg(all(
279    feature = "builtin-types",
280    feature = "point3",
281    feature = "vec_variants"
282))]
283impl_from_at_time!(Point3Vec);
284#[cfg(all(
285    feature = "builtin-types",
286    feature = "matrix4",
287    feature = "vec_variants"
288))]
289impl_from_at_time!(Matrix4Vec);
290
291impl<K: Ord, V> KeyDataMap<K, V> {
292    /// Insert a value at the given key.
293    pub fn insert(&mut self, key: K, value: V) {
294        #[cfg(not(feature = "interpolation"))]
295        {
296            self.values.insert(key, value);
297        }
298        #[cfg(feature = "interpolation")]
299        {
300            self.values.insert(key, (value, None));
301        }
302    }
303
304    /// Get a value at the exact key.
305    pub fn get(&self, key: &K) -> Option<&V> {
306        #[cfg(not(feature = "interpolation"))]
307        {
308            self.values.get(key)
309        }
310        #[cfg(feature = "interpolation")]
311        {
312            self.values.get(key).map(|(v, _)| v)
313        }
314    }
315}
316
317impl<K, V> KeyDataMap<K, V>
318where
319    K: Ord + Copy + Into<f32>,
320    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
321{
322    pub fn interpolate(&self, key: K) -> V {
323        #[cfg(not(feature = "interpolation"))]
324        {
325            interpolate(self.values.as_btree_map(), key)
326        }
327        #[cfg(feature = "interpolation")]
328        {
329            interpolate_with_specs(self.values.as_btree_map(), key)
330        }
331    }
332}
333
334// Interpolation-specific methods.
335#[cfg(feature = "interpolation")]
336impl<K: Ord, V> KeyDataMap<K, V> {
337    /// Insert a value with interpolation specification.
338    ///
339    /// Sets both the value at the given key and how it should interpolate
340    /// to neighboring keyframes.
341    pub fn insert_with_interpolation(&mut self, key: K, value: V, spec: crate::Key<V>) {
342        self.values.insert(key, (value, Some(spec)));
343    }
344
345    /// Get interpolation spec at a given key.
346    ///
347    /// Returns `None` if no interpolation metadata exists or no spec at this key.
348    pub fn interpolation(&self, key: &K) -> Option<&crate::Key<V>> {
349        self.values.get(key)?.1.as_ref()
350    }
351
352    /// Set or update the interpolation spec at a given key.
353    ///
354    /// Returns `Ok(())` if the key existed and was updated, or an error if no
355    /// sample exists at that key.
356    pub fn set_interpolation_at(&mut self, key: &K, spec: crate::Key<V>) -> crate::Result<()> {
357        if let Some(entry) = self.values.get_mut(key) {
358            entry.1 = Some(spec);
359            Ok(())
360        } else {
361            Err(crate::Error::KeyNotFound)
362        }
363    }
364
365    /// Clear all interpolation metadata.
366    ///
367    /// After calling this, all interpolation reverts to automatic mode.
368    pub fn clear_interpolation(&mut self) {
369        // SAFETY: Only modifying values in-place, not removing entries.
370        for (_, interp) in unsafe { self.values.as_mut_btree_map() }.values_mut() {
371            *interp = None;
372        }
373    }
374
375    /// Check if using custom interpolation.
376    ///
377    /// Returns `true` if any interpolation metadata is present.
378    pub fn has_interpolation(&self) -> bool {
379        self.values
380            .as_btree_map()
381            .values()
382            .any(|(_, interp)| interp.is_some())
383    }
384}
385
386impl<K: Ord, V, U> FromIterator<(K, U)> for KeyDataMap<K, V>
387where
388    U: Into<V>,
389{
390    fn from_iter<I>(iter: I) -> Self
391    where
392        I: IntoIterator<Item = (K, U)>,
393    {
394        let btree: BTreeMap<K, V> = iter.into_iter().map(|(k, v)| (k, v.into())).collect();
395        // SAFETY: All callers (Value::animated, GenericValue::animated) validate non-empty
396        // before calling from_iter. This is enforced by EmptySamples checks upstream.
397        Self::try_from(btree).expect("KeyDataMap::from_iter called with empty iterator")
398    }
399}
400
401impl<K: Ord + Copy + Into<f32>, V> KeyDataMap<K, V> {
402    pub fn sample_closest_at(&self, key: K) -> &V {
403        let k_f32: f32 = key.into();
404        let map = self.values.as_btree_map();
405        #[cfg(not(feature = "interpolation"))]
406        {
407            let greater_or_equal = map.range(key..).next();
408            let less_than = map.range(..key).next_back();
409
410            match (less_than, greater_or_equal) {
411                (Some((lower_k, lower_v)), Some((upper_k, upper_v))) => {
412                    let lo: f32 = (*lower_k).into();
413                    let hi: f32 = (*upper_k).into();
414                    if (k_f32 - lo).abs() <= (hi - k_f32).abs() {
415                        lower_v
416                    } else {
417                        upper_v
418                    }
419                }
420                (Some(entry), None) | (None, Some(entry)) => entry.1,
421                // SAFETY: BTreeMap1 guarantees at least one entry exists.
422                (None, None) => {
423                    unreachable!("BTreeMap1 guarantees KeyDataMap is never empty")
424                }
425            }
426        }
427        #[cfg(feature = "interpolation")]
428        {
429            let greater_or_equal = map.range(key..).next();
430            let less_than = map.range(..key).next_back();
431
432            match (less_than, greater_or_equal) {
433                (Some((lower_k, (lower_v, _))), Some((upper_k, (upper_v, _)))) => {
434                    let lo: f32 = (*lower_k).into();
435                    let hi: f32 = (*upper_k).into();
436                    if (k_f32 - lo).abs() <= (hi - k_f32).abs() {
437                        lower_v
438                    } else {
439                        upper_v
440                    }
441                }
442                (Some((_, (v, _))), None) | (None, Some((_, (v, _)))) => v,
443                // SAFETY: BTreeMap1 guarantees at least one entry exists.
444                (None, None) => {
445                    unreachable!("BTreeMap1 guarantees KeyDataMap is never empty")
446                }
447            }
448        }
449    }
450
451    /// Sample value at exact key (no interpolation).
452    pub fn sample_at(&self, key: K) -> Option<&V> {
453        self.get(&key)
454    }
455
456    /// Get the value at or before the given key.
457    pub fn sample_at_or_before(&self, key: K) -> Option<&V> {
458        let map = self.values.as_btree_map();
459        #[cfg(not(feature = "interpolation"))]
460        {
461            map.range(..=key).next_back().map(|(_, v)| v)
462        }
463        #[cfg(feature = "interpolation")]
464        {
465            map.range(..=key).next_back().map(|(_, (v, _))| v)
466        }
467    }
468
469    /// Get the value at or after the given key.
470    pub fn sample_at_or_after(&self, key: K) -> Option<&V> {
471        let map = self.values.as_btree_map();
472        #[cfg(not(feature = "interpolation"))]
473        {
474            map.range(key..).next().map(|(_, v)| v)
475        }
476        #[cfg(feature = "interpolation")]
477        {
478            map.range(key..).next().map(|(_, (v, _))| v)
479        }
480    }
481
482    /// Get surrounding samples for interpolation.
483    ///
484    /// Returns up to `N` samples centered around the given key for
485    /// use in interpolation algorithms.
486    pub fn sample_surrounding<const N: usize>(&self, key: K) -> SmallVec<[(K, &V); N]> {
487        let map = self.values.as_btree_map();
488        // Get samples before the key.
489        let before_count = N / 2;
490
491        #[cfg(not(feature = "interpolation"))]
492        {
493            let mut result = map
494                .range(..key)
495                .rev()
496                .take(before_count)
497                .map(|(k, v)| (*k, v))
498                .collect::<SmallVec<[_; N]>>();
499            result.reverse();
500
501            // Get samples at or after the key.
502            let after_count = N - result.len();
503            result.extend(map.range(key..).take(after_count).map(|(k, v)| (*k, v)));
504            result
505        }
506
507        #[cfg(feature = "interpolation")]
508        {
509            let mut result = map
510                .range(..key)
511                .rev()
512                .take(before_count)
513                .map(|(k, (v, _))| (*k, v))
514                .collect::<SmallVec<[_; N]>>();
515            result.reverse();
516
517            // Get samples at or after the key.
518            let after_count = N - result.len();
519            result.extend(
520                map.range(key..)
521                    .take(after_count)
522                    .map(|(k, (v, _))| (*k, v)),
523            );
524            result
525        }
526    }
527
528    /// Deprecated alias for [`sample_closest_at`](Self::sample_closest_at).
529    #[deprecated(since = "0.2.3", note = "renamed to `sample_closest_at`")]
530    pub fn closest_sample(&self, key: K) -> &V {
531        self.sample_closest_at(key)
532    }
533}
534
535// AIDEV-NOTE: Internal types for backend-agnostic Matrix3 SVD decomposition.
536// These implement the arithmetic traits needed by the generic `interpolate()`
537// function, avoiding any dependency on a specific math backend for SVD compute.
538// The analytical 2×2 SVD replaces the previous nalgebra-dependent implementation.
539
540/// Internal 2D translation extracted from a 3×3 homogeneous matrix.
541#[cfg(feature = "matrix3")]
542#[derive(Clone)]
543struct Trans2(f32, f32);
544
545#[cfg(feature = "matrix3")]
546impl Add for Trans2 {
547    type Output = Self;
548    fn add(self, rhs: Self) -> Self {
549        Trans2(self.0 + rhs.0, self.1 + rhs.1)
550    }
551}
552
553#[cfg(feature = "matrix3")]
554impl Sub for Trans2 {
555    type Output = Self;
556    fn sub(self, rhs: Self) -> Self {
557        Trans2(self.0 - rhs.0, self.1 - rhs.1)
558    }
559}
560
561#[cfg(feature = "matrix3")]
562impl Mul<f32> for Trans2 {
563    type Output = Self;
564    fn mul(self, s: f32) -> Self {
565        Trans2(self.0 * s, self.1 * s)
566    }
567}
568
569/// Internal diagonal stretch (singular values) for decomposed 3×3 matrices.
570#[cfg(feature = "matrix3")]
571#[derive(Clone)]
572struct DiagStretch(f32, f32);
573
574#[cfg(feature = "matrix3")]
575impl Add for DiagStretch {
576    type Output = Self;
577    fn add(self, rhs: Self) -> Self {
578        DiagStretch(self.0 + rhs.0, self.1 + rhs.1)
579    }
580}
581
582#[cfg(feature = "matrix3")]
583impl Sub for DiagStretch {
584    type Output = Self;
585    fn sub(self, rhs: Self) -> Self {
586        DiagStretch(self.0 - rhs.0, self.1 - rhs.1)
587    }
588}
589
590#[cfg(feature = "matrix3")]
591impl Mul<f32> for DiagStretch {
592    type Output = Self;
593    fn mul(self, s: f32) -> Self {
594        DiagStretch(self.0 * s, self.1 * s)
595    }
596}
597
598/// Interpolate 2D rotation angles via shortest-path slerp across a [`BTreeMap`].
599#[cfg(feature = "matrix3")]
600#[inline(always)]
601fn interpolate_rotation(map: &BTreeMap<Time, f32>, time: Time) -> f32 {
602    if map.len() == 1 {
603        // SAFETY: Guarded by len == 1 check above.
604        return *map.values().next().unwrap();
605    }
606
607    // SAFETY: Past the len == 1 early return, map has >= 2 entries.
608    let first = map.iter().next().unwrap();
609    let last = map.iter().next_back().unwrap();
610
611    if time <= *first.0 {
612        return *first.1;
613    }
614
615    if time >= *last.0 {
616        return *last.1;
617    }
618
619    // SAFETY: key is strictly between first and last keys (boundary checks above).
620    let lower = map.range(..=time).next_back().unwrap();
621    let upper = map.range(time..).next().unwrap();
622
623    let t0 = f32::from(*lower.0);
624    let t1 = f32::from(*upper.0);
625    let a0 = *lower.1;
626    let a1 = *upper.1;
627
628    // Normalize `t` to [0, 1].
629    let t: f32 = (f32::from(time) - t0) / (t1 - t0);
630
631    // Shortest-path angle interpolation.
632    use std::f32::consts::{PI, TAU};
633    let mut diff = a1 - a0;
634    if diff > PI {
635        diff -= TAU;
636    } else if diff < -PI {
637        diff += TAU;
638    }
639    a0 + diff * t
640}
641
642// AIDEV-NOTE: Helper functions for converting BezierHandle variants to slopes and evaluating mixed interpolation modes.
643
644#[cfg(feature = "interpolation")]
645fn bezier_handle_to_slope<V>(
646    handle: &crate::BezierHandle<V>,
647    t1: f32,
648    t2: f32,
649    _v1: &V,
650    _v2: &V,
651) -> Option<V>
652where
653    V: Clone + Add<Output = V> + Sub<Output = V> + Mul<f32, Output = V>,
654{
655    match handle {
656        crate::BezierHandle::SlopePerSecond(s) => Some(s.clone()),
657        crate::BezierHandle::SlopePerFrame(s) => {
658            let frames = (t2 - t1).max(f32::EPSILON);
659            Some(s.clone() * (frames / (t2 - t1)))
660        }
661        crate::BezierHandle::Delta { time, value } => {
662            let time_f32: f32 = (*time).into();
663            if time_f32.abs() <= f32::EPSILON {
664                None
665            } else {
666                Some(value.clone() * (1.0 / time_f32))
667            }
668        }
669        crate::BezierHandle::Angle(_) => None, // angle needs higher-level context; fall back later
670    }
671}
672
673#[cfg(feature = "interpolation")]
674#[allow(clippy::too_many_arguments)]
675fn smooth_tangent<K, V>(
676    t1: f32,
677    v1: &V,
678    t2: f32,
679    v2: &V,
680    map: &BTreeMap<K, (V, Option<crate::Key<V>>)>,
681    _key: K,
682    anchor: K,
683    incoming: bool,
684) -> V
685where
686    K: Ord + Copy + Into<f32>,
687    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
688{
689    use smallvec::SmallVec;
690
691    // Minimal Catmull-style tangent: reuse nearby keys via existing helpers.
692    let mut window = SmallVec::<[(K, V); 2]>::new();
693
694    if incoming {
695        if let Some(prev) = map.range(..anchor).next_back() {
696            window.push((*prev.0, prev.1.0.clone()));
697        }
698        window.push((anchor, v1.clone()));
699    } else {
700        window.push((anchor, v1.clone()));
701        if let Some(next) = map.range(anchor..).nth(1) {
702            window.push((*next.0, next.1.0.clone()));
703        }
704    }
705
706    if window.len() == 2 {
707        let (k0, p0) = &window[0];
708        let (k1, p1) = &window[1];
709        let k0_f: f32 = (*k0).into();
710        let k1_f: f32 = (*k1).into();
711        let dt = (k1_f - k0_f).max(f32::EPSILON);
712        (p1.clone() - p0.clone()) * (1.0 / dt)
713    } else {
714        // Fall back to local linear slope.
715        (v2.clone() - v1.clone()) * (1.0 / (t2 - t1).max(f32::EPSILON))
716    }
717}
718
719#[cfg(feature = "interpolation")]
720fn evaluate_mixed_bezier<K, V>(
721    key: K,
722    k1: K,
723    v1: &V,
724    slope_out: &V,
725    k2: K,
726    v2: &V,
727    slope_in: &V,
728) -> V
729where
730    K: Copy + Into<f32>,
731    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
732{
733    use crate::interpolation::bezier_helpers::*;
734
735    let (p1, p2) = control_points_from_slopes(k1.into(), v1, slope_out, k2.into(), v2, slope_in);
736
737    evaluate_bezier_component_wise(
738        key.into(),
739        (k1.into(), v1),
740        (p1.0, &p1.1),
741        (p2.0, &p2.1),
742        (k2.into(), v2),
743    )
744}
745
746/// Interpolate with respect to interpolation specifications.
747///
748/// This function checks for custom interpolation specs and uses them if available,
749/// otherwise falls back to automatic interpolation.
750#[cfg(feature = "interpolation")]
751#[inline(always)]
752pub(crate) fn interpolate_with_specs<K, V>(
753    map: &BTreeMap<K, (V, Option<crate::Key<V>>)>,
754    key: K,
755) -> V
756where
757    K: Ord + Copy + Into<f32>,
758    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
759{
760    if map.len() == 1 {
761        // SAFETY: Guarded by len == 1 check above.
762        return map.values().next().unwrap().0.clone();
763    }
764
765    // SAFETY: Past the len == 1 early return, map has >= 2 entries.
766    let first = map.iter().next().unwrap();
767    let last = map.iter().next_back().unwrap();
768
769    if key <= *first.0 {
770        return first.1.0.clone();
771    }
772
773    if key >= *last.0 {
774        return last.1.0.clone();
775    }
776
777    // SAFETY: key is strictly between first and last keys (boundary checks above).
778    let lower = map.range(..key).next_back().unwrap();
779    let upper = map.range(key..).next().unwrap();
780
781    // If we're exactly on a keyframe, return its value.
782    if lower.0 == upper.0 {
783        return lower.1.0.clone();
784    }
785
786    let (k1, (v1, spec1)) = lower;
787    let (k2, (v2, spec2)) = upper;
788
789    // Check if we have interpolation specs.
790    let interp_out = spec1.as_ref().map(|s| &s.interpolation_out);
791    let interp_in = spec2.as_ref().map(|s| &s.interpolation_in);
792
793    match (interp_out, interp_in) {
794        // Step/hold beats everything else.
795        (Some(crate::Interpolation::Hold), _) | (_, Some(crate::Interpolation::Hold)) => v1.clone(),
796
797        // Both sides supply explicit handles -- run a cubic Bezier with those tangents.
798        (
799            Some(crate::Interpolation::Bezier(out_handle)),
800            Some(crate::Interpolation::Bezier(in_handle)),
801        ) => {
802            use crate::interpolation::bezier_helpers::*;
803
804            if let (Some(slope_out), Some(slope_in)) = (
805                bezier_handle_to_slope(out_handle, (*k1).into(), (*k2).into(), v1, v2),
806                bezier_handle_to_slope(in_handle, (*k1).into(), (*k2).into(), v1, v2),
807            ) {
808                let (p1, p2) = control_points_from_slopes(
809                    (*k1).into(),
810                    v1,
811                    &slope_out,
812                    (*k2).into(),
813                    v2,
814                    &slope_in,
815                );
816
817                evaluate_bezier_component_wise(
818                    key.into(),
819                    ((*k1).into(), v1),
820                    (p1.0, &p1.1),
821                    (p2.0, &p2.1),
822                    ((*k2).into(), v2),
823                )
824            } else {
825                // Fall back to linear if we can't convert handles to slopes.
826                linear_interp(*k1, *k2, v1, v2, key)
827            }
828        }
829
830        // One side explicit, the other "smooth": derive a Catmull-style tangent for the smooth side.
831        (Some(crate::Interpolation::Bezier(out_handle)), Some(crate::Interpolation::Smooth)) => {
832            if let Some(slope_out) =
833                bezier_handle_to_slope(out_handle, (*k1).into(), (*k2).into(), v1, v2)
834            {
835                let slope_in =
836                    smooth_tangent((*k1).into(), v1, (*k2).into(), v2, map, key, *k1, false);
837
838                evaluate_mixed_bezier(key, *k1, v1, &slope_out, *k2, v2, &slope_in)
839            } else {
840                linear_interp(*k1, *k2, v1, v2, key)
841            }
842        }
843        (Some(crate::Interpolation::Smooth), Some(crate::Interpolation::Bezier(in_handle))) => {
844            if let Some(slope_in) =
845                bezier_handle_to_slope(in_handle, (*k1).into(), (*k2).into(), v1, v2)
846            {
847                let slope_out =
848                    smooth_tangent((*k1).into(), v1, (*k2).into(), v2, map, key, *k1, true);
849
850                evaluate_mixed_bezier(key, *k1, v1, &slope_out, *k2, v2, &slope_in)
851            } else {
852                linear_interp(*k1, *k2, v1, v2, key)
853            }
854        }
855
856        // Symmetric "smooth" -> fall back to the existing automatic interpolation.
857        (Some(crate::Interpolation::Smooth), Some(crate::Interpolation::Smooth)) | (None, None) => {
858            let values_only: BTreeMap<K, V> =
859                map.iter().map(|(k, (v, _))| (*k, v.clone())).collect();
860            interpolate(&values_only, key)
861        }
862
863        // Linear/linear, linear vs smooth, or any unsupported combination -> straight line.
864        _ => linear_interp(*k1, *k2, v1, v2, key),
865    }
866}
867
868#[inline(always)]
869pub(crate) fn interpolate<K, V>(map: &BTreeMap<K, V>, key: K) -> V
870where
871    K: Ord + Copy + Into<f32>,
872    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
873{
874    if map.len() == 1 {
875        // SAFETY: Guarded by len == 1 check above.
876        return map.values().next().unwrap().clone();
877    }
878
879    // SAFETY: Past the len == 1 early return, map has >= 2 entries.
880    let first = map.iter().next().unwrap();
881    let last = map.iter().next_back().unwrap();
882
883    if key <= *first.0 {
884        return first.1.clone();
885    }
886
887    if key >= *last.0 {
888        return last.1.clone();
889    }
890
891    // SAFETY: key is strictly between first and last keys (boundary checks above).
892    let lower = map.range(..key).next_back().unwrap();
893    let upper = map.range(key..).next().unwrap();
894
895    // This is our window for interpolation that holds two to four values.
896    //
897    // The interpolation algorithm is chosen based on how many values are in
898    // the window. See the `match` block below.
899    let mut window = SmallVec::<[(K, &V); 4]>::new();
900
901    // Extend with up to two values before `lower`.
902    window.extend(map.range(..*lower.0).rev().take(2).map(|(k, v)| (*k, v)));
903
904    // Add `lower` and `upper` (if distinct).
905    window.push((*lower.0, lower.1));
906
907    if lower.0 != upper.0 {
908        window.push((*upper.0, upper.1));
909    }
910
911    // Extend with up to one value after `upper`.
912    window.extend(map.range(*upper.0..).skip(1).take(1).map(|(k, v)| (*k, v)));
913
914    // Ensure chronological order.
915    window.reverse();
916
917    match window.len() {
918        4 => {
919            let (t0, p0) = window[0];
920            let (t1, p1) = window[1];
921            let (t2, p2) = window[2];
922            let (t3, p3) = window[3];
923            hermite_interp(HermiteParams {
924                t0,
925                t1,
926                t2,
927                t3,
928                p0,
929                p1,
930                p2,
931                p3,
932                t: key,
933            })
934        }
935        3 => {
936            let (t0, p0) = window[0];
937            let (t1, p1) = window[1];
938            let (t2, p2) = window[2];
939            quadratic_interp(t0, t1, t2, p0, p1, p2, key)
940        }
941        2 => {
942            let (x0, y0) = window[0];
943            let (x1, y1) = window[1];
944            linear_interp(x0, x1, y0, y1, key)
945        }
946        1 => {
947            // Single keyframe - return its value.
948            window[0].1.clone()
949        }
950        0 => {
951            // This shouldn't happen given the checks above, but be defensive.
952            panic!("Interpolation window is empty - this is a bug in token-value-map")
953        }
954        _ => {
955            // Window has more than 4 elements - shouldn't happen but use first 4.
956            let (t0, p0) = window[0];
957            let (t1, p1) = window[1];
958            let (t2, p2) = window[2];
959            let (t3, p3) = window[3];
960            hermite_interp(HermiteParams {
961                t0,
962                t1,
963                t2,
964                t3,
965                p0,
966                p1,
967                p2,
968                p3,
969                t: key,
970            })
971        }
972    }
973}
974
975#[inline(always)]
976fn linear_interp<V, T>(x0: T, x1: T, y0: &V, y1: &V, x: T) -> V
977where
978    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
979    T: Into<f32>,
980{
981    let x0 = x0.into();
982    let x1 = x1.into();
983    let x = x.into();
984    let alpha = (x - x0) / (x1 - x0);
985    y0.clone() + (y1.clone() - y0.clone()) * alpha
986}
987
988#[inline(always)]
989fn quadratic_interp<V, T>(x0: T, x1: T, x2: T, y0: &V, y1: &V, y2: &V, x: T) -> V
990where
991    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
992    T: Into<f32>,
993{
994    let x0 = x0.into();
995    let x1 = x1.into();
996    let x2 = x2.into();
997    let x = x.into();
998    let a = (x - x1) * (x - x2) / ((x0 - x1) * (x0 - x2));
999    let b = (x - x0) * (x - x2) / ((x1 - x0) * (x1 - x2));
1000    let c = (x - x0) * (x - x1) / ((x2 - x0) * (x2 - x1));
1001
1002    y0.clone() * a + y1.clone() * b + y2.clone() * c
1003}
1004
1005struct HermiteParams<V, T> {
1006    t0: T,
1007    t1: T,
1008    t2: T,
1009    t3: T,
1010    p0: V,
1011    p1: V,
1012    p2: V,
1013    p3: V,
1014    t: T,
1015}
1016
1017#[inline(always)]
1018fn hermite_interp<V, T>(params: HermiteParams<&V, T>) -> V
1019where
1020    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
1021    T: Into<f32>,
1022{
1023    let t0 = params.t0.into();
1024    let t1 = params.t1.into();
1025    let t2 = params.t2.into();
1026    let t3 = params.t3.into();
1027    let t = params.t.into();
1028
1029    // Account for knot positions
1030    let tau = if (t2 - t1).abs() < f32::EPSILON {
1031        0.0 // Handle degenerate case where t1 == t2
1032    } else {
1033        (t - t1) / (t2 - t1)
1034    };
1035
1036    // Calculate tension/bias parameters based on the spacing between knots.
1037    // This makes the spline respond to actual time intervals rather than
1038    // assuming uniform spacing.
1039    let tension1 = if (t1 - t0).abs() < f32::EPSILON {
1040        1.0
1041    } else {
1042        (t2 - t1) / (t1 - t0)
1043    };
1044    let tension2 = if (t3 - t2).abs() < f32::EPSILON {
1045        1.0
1046    } else {
1047        (t2 - t1) / (t3 - t2)
1048    };
1049
1050    // Tangent vectors that respect the actual knot spacing
1051    let m1 = (params.p2.clone() - params.p0.clone()) * (0.5 * tension1);
1052    let m2 = (params.p3.clone() - params.p1.clone()) * (0.5 * tension2);
1053
1054    // Hermite basis functions
1055    let tau2 = tau * tau;
1056    let tau3 = tau2 * tau;
1057
1058    let h00 = 2.0 * tau3 - 3.0 * tau2 + 1.0;
1059    let h10 = tau3 - 2.0 * tau2 + tau;
1060    let h01 = -2.0 * tau3 + 3.0 * tau2;
1061    let h11 = tau3 - tau2;
1062
1063    // Hermite interpolation with proper tangent vectors
1064    params.p1.clone() * h00 + m1 * h10 + params.p2.clone() * h01 + m2 * h11
1065}
1066
1067/// Analytical 2×2 SVD decomposition of a 3×3 homogeneous matrix.
1068///
1069/// Extracts translation, rotation angle (radians), and diagonal stretch
1070/// (singular values) without depending on any specific math backend.
1071#[cfg(feature = "matrix3")]
1072#[inline(always)]
1073fn decompose_matrix(matrix: &crate::math::Mat3Impl) -> (Trans2, f32, DiagStretch) {
1074    use crate::math::mat3;
1075
1076    // Extract translation from the last column.
1077    let tx = mat3(matrix, 0, 2);
1078    let ty = mat3(matrix, 1, 2);
1079
1080    // Upper-left 2×2 linear block.
1081    let a = mat3(matrix, 0, 0);
1082    let b = mat3(matrix, 0, 1);
1083    let c = mat3(matrix, 1, 0);
1084    let d = mat3(matrix, 1, 1);
1085
1086    // Analytical 2×2 SVD: M = Rot(θ) × diag(σ₁, σ₂) × Rot(-φ).
1087    let e = (a + d) * 0.5;
1088    let f = (a - d) * 0.5;
1089    let g = (c + b) * 0.5;
1090    let h = (c - b) * 0.5;
1091
1092    let q = (e * e + h * h).sqrt();
1093    let r = (f * f + g * g).sqrt();
1094
1095    let s1 = q + r;
1096    let s2 = q - r;
1097
1098    let theta1 = g.atan2(f);
1099    let theta2 = h.atan2(e);
1100
1101    // U rotation angle.
1102    let rotation_angle = (theta2 + theta1) * 0.5;
1103
1104    (Trans2(tx, ty), rotation_angle, DiagStretch(s1, s2))
1105}
1106
1107/// Recompose a 3×3 homogeneous matrix from its decomposed parts.
1108///
1109/// Constructs `Rot(angle) × diag(sx, sy, 1)` with translation in the last column.
1110#[cfg(feature = "matrix3")]
1111#[inline(always)]
1112fn recompose_matrix(
1113    translation: Trans2,
1114    rotation_angle: f32,
1115    stretch: DiagStretch,
1116) -> crate::math::Mat3Impl {
1117    let cos = rotation_angle.cos();
1118    let sin = rotation_angle.sin();
1119
1120    // Rot(θ) × diag(sx, sy, 1) in column-major layout.
1121    crate::math::mat3_from_column_slice(&[
1122        stretch.0 * cos,
1123        stretch.0 * sin,
1124        0.0,
1125        -stretch.1 * sin,
1126        stretch.1 * cos,
1127        0.0,
1128        translation.0,
1129        translation.1,
1130        1.0,
1131    ])
1132}