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 closest_sample(&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
529// AIDEV-NOTE: Internal types for backend-agnostic Matrix3 SVD decomposition.
530// These implement the arithmetic traits needed by the generic `interpolate()`
531// function, avoiding any dependency on a specific math backend for SVD compute.
532// The analytical 2×2 SVD replaces the previous nalgebra-dependent implementation.
533
534/// Internal 2D translation extracted from a 3×3 homogeneous matrix.
535#[cfg(feature = "matrix3")]
536#[derive(Clone)]
537struct Trans2(f32, f32);
538
539#[cfg(feature = "matrix3")]
540impl Add for Trans2 {
541    type Output = Self;
542    fn add(self, rhs: Self) -> Self {
543        Trans2(self.0 + rhs.0, self.1 + rhs.1)
544    }
545}
546
547#[cfg(feature = "matrix3")]
548impl Sub for Trans2 {
549    type Output = Self;
550    fn sub(self, rhs: Self) -> Self {
551        Trans2(self.0 - rhs.0, self.1 - rhs.1)
552    }
553}
554
555#[cfg(feature = "matrix3")]
556impl Mul<f32> for Trans2 {
557    type Output = Self;
558    fn mul(self, s: f32) -> Self {
559        Trans2(self.0 * s, self.1 * s)
560    }
561}
562
563/// Internal diagonal stretch (singular values) for decomposed 3×3 matrices.
564#[cfg(feature = "matrix3")]
565#[derive(Clone)]
566struct DiagStretch(f32, f32);
567
568#[cfg(feature = "matrix3")]
569impl Add for DiagStretch {
570    type Output = Self;
571    fn add(self, rhs: Self) -> Self {
572        DiagStretch(self.0 + rhs.0, self.1 + rhs.1)
573    }
574}
575
576#[cfg(feature = "matrix3")]
577impl Sub for DiagStretch {
578    type Output = Self;
579    fn sub(self, rhs: Self) -> Self {
580        DiagStretch(self.0 - rhs.0, self.1 - rhs.1)
581    }
582}
583
584#[cfg(feature = "matrix3")]
585impl Mul<f32> for DiagStretch {
586    type Output = Self;
587    fn mul(self, s: f32) -> Self {
588        DiagStretch(self.0 * s, self.1 * s)
589    }
590}
591
592/// Interpolate 2D rotation angles via shortest-path slerp across a [`BTreeMap`].
593#[cfg(feature = "matrix3")]
594#[inline(always)]
595fn interpolate_rotation(map: &BTreeMap<Time, f32>, time: Time) -> f32 {
596    if map.len() == 1 {
597        // SAFETY: Guarded by len == 1 check above.
598        return *map.values().next().unwrap();
599    }
600
601    // SAFETY: Past the len == 1 early return, map has >= 2 entries.
602    let first = map.iter().next().unwrap();
603    let last = map.iter().next_back().unwrap();
604
605    if time <= *first.0 {
606        return *first.1;
607    }
608
609    if time >= *last.0 {
610        return *last.1;
611    }
612
613    // SAFETY: key is strictly between first and last keys (boundary checks above).
614    let lower = map.range(..=time).next_back().unwrap();
615    let upper = map.range(time..).next().unwrap();
616
617    let t0 = f32::from(*lower.0);
618    let t1 = f32::from(*upper.0);
619    let a0 = *lower.1;
620    let a1 = *upper.1;
621
622    // Normalize `t` to [0, 1].
623    let t: f32 = (f32::from(time) - t0) / (t1 - t0);
624
625    // Shortest-path angle interpolation.
626    use std::f32::consts::{PI, TAU};
627    let mut diff = a1 - a0;
628    if diff > PI {
629        diff -= TAU;
630    } else if diff < -PI {
631        diff += TAU;
632    }
633    a0 + diff * t
634}
635
636// AIDEV-NOTE: Helper functions for converting BezierHandle variants to slopes and evaluating mixed interpolation modes.
637
638#[cfg(feature = "interpolation")]
639fn bezier_handle_to_slope<V>(
640    handle: &crate::BezierHandle<V>,
641    t1: f32,
642    t2: f32,
643    _v1: &V,
644    _v2: &V,
645) -> Option<V>
646where
647    V: Clone + Add<Output = V> + Sub<Output = V> + Mul<f32, Output = V>,
648{
649    match handle {
650        crate::BezierHandle::SlopePerSecond(s) => Some(s.clone()),
651        crate::BezierHandle::SlopePerFrame(s) => {
652            let frames = (t2 - t1).max(f32::EPSILON);
653            Some(s.clone() * (frames / (t2 - t1)))
654        }
655        crate::BezierHandle::Delta { time, value } => {
656            let time_f32: f32 = (*time).into();
657            if time_f32.abs() <= f32::EPSILON {
658                None
659            } else {
660                Some(value.clone() * (1.0 / time_f32))
661            }
662        }
663        crate::BezierHandle::Angle(_) => None, // angle needs higher-level context; fall back later
664    }
665}
666
667#[cfg(feature = "interpolation")]
668#[allow(clippy::too_many_arguments)]
669fn smooth_tangent<K, V>(
670    t1: f32,
671    v1: &V,
672    t2: f32,
673    v2: &V,
674    map: &BTreeMap<K, (V, Option<crate::Key<V>>)>,
675    _key: K,
676    anchor: K,
677    incoming: bool,
678) -> V
679where
680    K: Ord + Copy + Into<f32>,
681    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
682{
683    use smallvec::SmallVec;
684
685    // Minimal Catmull-style tangent: reuse nearby keys via existing helpers.
686    let mut window = SmallVec::<[(K, V); 2]>::new();
687
688    if incoming {
689        if let Some(prev) = map.range(..anchor).next_back() {
690            window.push((*prev.0, prev.1.0.clone()));
691        }
692        window.push((anchor, v1.clone()));
693    } else {
694        window.push((anchor, v1.clone()));
695        if let Some(next) = map.range(anchor..).nth(1) {
696            window.push((*next.0, next.1.0.clone()));
697        }
698    }
699
700    if window.len() == 2 {
701        let (k0, p0) = &window[0];
702        let (k1, p1) = &window[1];
703        let k0_f: f32 = (*k0).into();
704        let k1_f: f32 = (*k1).into();
705        let dt = (k1_f - k0_f).max(f32::EPSILON);
706        (p1.clone() - p0.clone()) * (1.0 / dt)
707    } else {
708        // Fall back to local linear slope.
709        (v2.clone() - v1.clone()) * (1.0 / (t2 - t1).max(f32::EPSILON))
710    }
711}
712
713#[cfg(feature = "interpolation")]
714fn evaluate_mixed_bezier<K, V>(
715    key: K,
716    k1: K,
717    v1: &V,
718    slope_out: &V,
719    k2: K,
720    v2: &V,
721    slope_in: &V,
722) -> V
723where
724    K: Copy + Into<f32>,
725    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
726{
727    use crate::interpolation::bezier_helpers::*;
728
729    let (p1, p2) = control_points_from_slopes(k1.into(), v1, slope_out, k2.into(), v2, slope_in);
730
731    evaluate_bezier_component_wise(
732        key.into(),
733        (k1.into(), v1),
734        (p1.0, &p1.1),
735        (p2.0, &p2.1),
736        (k2.into(), v2),
737    )
738}
739
740/// Interpolate with respect to interpolation specifications.
741///
742/// This function checks for custom interpolation specs and uses them if available,
743/// otherwise falls back to automatic interpolation.
744#[cfg(feature = "interpolation")]
745#[inline(always)]
746pub(crate) fn interpolate_with_specs<K, V>(
747    map: &BTreeMap<K, (V, Option<crate::Key<V>>)>,
748    key: K,
749) -> V
750where
751    K: Ord + Copy + Into<f32>,
752    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
753{
754    if map.len() == 1 {
755        // SAFETY: Guarded by len == 1 check above.
756        return map.values().next().unwrap().0.clone();
757    }
758
759    // SAFETY: Past the len == 1 early return, map has >= 2 entries.
760    let first = map.iter().next().unwrap();
761    let last = map.iter().next_back().unwrap();
762
763    if key <= *first.0 {
764        return first.1.0.clone();
765    }
766
767    if key >= *last.0 {
768        return last.1.0.clone();
769    }
770
771    // SAFETY: key is strictly between first and last keys (boundary checks above).
772    let lower = map.range(..key).next_back().unwrap();
773    let upper = map.range(key..).next().unwrap();
774
775    // If we're exactly on a keyframe, return its value.
776    if lower.0 == upper.0 {
777        return lower.1.0.clone();
778    }
779
780    let (k1, (v1, spec1)) = lower;
781    let (k2, (v2, spec2)) = upper;
782
783    // Check if we have interpolation specs.
784    let interp_out = spec1.as_ref().map(|s| &s.interpolation_out);
785    let interp_in = spec2.as_ref().map(|s| &s.interpolation_in);
786
787    match (interp_out, interp_in) {
788        // Step/hold beats everything else.
789        (Some(crate::Interpolation::Hold), _) | (_, Some(crate::Interpolation::Hold)) => v1.clone(),
790
791        // Both sides supply explicit handles -- run a cubic Bezier with those tangents.
792        (
793            Some(crate::Interpolation::Bezier(out_handle)),
794            Some(crate::Interpolation::Bezier(in_handle)),
795        ) => {
796            use crate::interpolation::bezier_helpers::*;
797
798            if let (Some(slope_out), Some(slope_in)) = (
799                bezier_handle_to_slope(out_handle, (*k1).into(), (*k2).into(), v1, v2),
800                bezier_handle_to_slope(in_handle, (*k1).into(), (*k2).into(), v1, v2),
801            ) {
802                let (p1, p2) = control_points_from_slopes(
803                    (*k1).into(),
804                    v1,
805                    &slope_out,
806                    (*k2).into(),
807                    v2,
808                    &slope_in,
809                );
810
811                evaluate_bezier_component_wise(
812                    key.into(),
813                    ((*k1).into(), v1),
814                    (p1.0, &p1.1),
815                    (p2.0, &p2.1),
816                    ((*k2).into(), v2),
817                )
818            } else {
819                // Fall back to linear if we can't convert handles to slopes.
820                linear_interp(*k1, *k2, v1, v2, key)
821            }
822        }
823
824        // One side explicit, the other "smooth": derive a Catmull-style tangent for the smooth side.
825        (Some(crate::Interpolation::Bezier(out_handle)), Some(crate::Interpolation::Smooth)) => {
826            if let Some(slope_out) =
827                bezier_handle_to_slope(out_handle, (*k1).into(), (*k2).into(), v1, v2)
828            {
829                let slope_in =
830                    smooth_tangent((*k1).into(), v1, (*k2).into(), v2, map, key, *k1, false);
831
832                evaluate_mixed_bezier(key, *k1, v1, &slope_out, *k2, v2, &slope_in)
833            } else {
834                linear_interp(*k1, *k2, v1, v2, key)
835            }
836        }
837        (Some(crate::Interpolation::Smooth), Some(crate::Interpolation::Bezier(in_handle))) => {
838            if let Some(slope_in) =
839                bezier_handle_to_slope(in_handle, (*k1).into(), (*k2).into(), v1, v2)
840            {
841                let slope_out =
842                    smooth_tangent((*k1).into(), v1, (*k2).into(), v2, map, key, *k1, true);
843
844                evaluate_mixed_bezier(key, *k1, v1, &slope_out, *k2, v2, &slope_in)
845            } else {
846                linear_interp(*k1, *k2, v1, v2, key)
847            }
848        }
849
850        // Symmetric "smooth" -> fall back to the existing automatic interpolation.
851        (Some(crate::Interpolation::Smooth), Some(crate::Interpolation::Smooth)) | (None, None) => {
852            let values_only: BTreeMap<K, V> =
853                map.iter().map(|(k, (v, _))| (*k, v.clone())).collect();
854            interpolate(&values_only, key)
855        }
856
857        // Linear/linear, linear vs smooth, or any unsupported combination -> straight line.
858        _ => linear_interp(*k1, *k2, v1, v2, key),
859    }
860}
861
862#[inline(always)]
863pub(crate) fn interpolate<K, V>(map: &BTreeMap<K, V>, key: K) -> V
864where
865    K: Ord + Copy + Into<f32>,
866    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
867{
868    if map.len() == 1 {
869        // SAFETY: Guarded by len == 1 check above.
870        return map.values().next().unwrap().clone();
871    }
872
873    // SAFETY: Past the len == 1 early return, map has >= 2 entries.
874    let first = map.iter().next().unwrap();
875    let last = map.iter().next_back().unwrap();
876
877    if key <= *first.0 {
878        return first.1.clone();
879    }
880
881    if key >= *last.0 {
882        return last.1.clone();
883    }
884
885    // SAFETY: key is strictly between first and last keys (boundary checks above).
886    let lower = map.range(..key).next_back().unwrap();
887    let upper = map.range(key..).next().unwrap();
888
889    // This is our window for interpolation that holds two to four values.
890    //
891    // The interpolation algorithm is chosen based on how many values are in
892    // the window. See the `match` block below.
893    let mut window = SmallVec::<[(K, &V); 4]>::new();
894
895    // Extend with up to two values before `lower`.
896    window.extend(map.range(..*lower.0).rev().take(2).map(|(k, v)| (*k, v)));
897
898    // Add `lower` and `upper` (if distinct).
899    window.push((*lower.0, lower.1));
900
901    if lower.0 != upper.0 {
902        window.push((*upper.0, upper.1));
903    }
904
905    // Extend with up to one value after `upper`.
906    window.extend(map.range(*upper.0..).skip(1).take(1).map(|(k, v)| (*k, v)));
907
908    // Ensure chronological order.
909    window.reverse();
910
911    match window.len() {
912        4 => {
913            let (t0, p0) = window[0];
914            let (t1, p1) = window[1];
915            let (t2, p2) = window[2];
916            let (t3, p3) = window[3];
917            hermite_interp(HermiteParams {
918                t0,
919                t1,
920                t2,
921                t3,
922                p0,
923                p1,
924                p2,
925                p3,
926                t: key,
927            })
928        }
929        3 => {
930            let (t0, p0) = window[0];
931            let (t1, p1) = window[1];
932            let (t2, p2) = window[2];
933            quadratic_interp(t0, t1, t2, p0, p1, p2, key)
934        }
935        2 => {
936            let (x0, y0) = window[0];
937            let (x1, y1) = window[1];
938            linear_interp(x0, x1, y0, y1, key)
939        }
940        1 => {
941            // Single keyframe - return its value.
942            window[0].1.clone()
943        }
944        0 => {
945            // This shouldn't happen given the checks above, but be defensive.
946            panic!("Interpolation window is empty - this is a bug in token-value-map")
947        }
948        _ => {
949            // Window has more than 4 elements - shouldn't happen but use first 4.
950            let (t0, p0) = window[0];
951            let (t1, p1) = window[1];
952            let (t2, p2) = window[2];
953            let (t3, p3) = window[3];
954            hermite_interp(HermiteParams {
955                t0,
956                t1,
957                t2,
958                t3,
959                p0,
960                p1,
961                p2,
962                p3,
963                t: key,
964            })
965        }
966    }
967}
968
969#[inline(always)]
970fn linear_interp<V, T>(x0: T, x1: T, y0: &V, y1: &V, x: T) -> V
971where
972    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
973    T: Into<f32>,
974{
975    let x0 = x0.into();
976    let x1 = x1.into();
977    let x = x.into();
978    let alpha = (x - x0) / (x1 - x0);
979    y0.clone() + (y1.clone() - y0.clone()) * alpha
980}
981
982#[inline(always)]
983fn quadratic_interp<V, T>(x0: T, x1: T, x2: T, y0: &V, y1: &V, y2: &V, x: T) -> V
984where
985    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
986    T: Into<f32>,
987{
988    let x0 = x0.into();
989    let x1 = x1.into();
990    let x2 = x2.into();
991    let x = x.into();
992    let a = (x - x1) * (x - x2) / ((x0 - x1) * (x0 - x2));
993    let b = (x - x0) * (x - x2) / ((x1 - x0) * (x1 - x2));
994    let c = (x - x0) * (x - x1) / ((x2 - x0) * (x2 - x1));
995
996    y0.clone() * a + y1.clone() * b + y2.clone() * c
997}
998
999struct HermiteParams<V, T> {
1000    t0: T,
1001    t1: T,
1002    t2: T,
1003    t3: T,
1004    p0: V,
1005    p1: V,
1006    p2: V,
1007    p3: V,
1008    t: T,
1009}
1010
1011#[inline(always)]
1012fn hermite_interp<V, T>(params: HermiteParams<&V, T>) -> V
1013where
1014    V: Clone + Add<Output = V> + Mul<f32, Output = V> + Sub<Output = V>,
1015    T: Into<f32>,
1016{
1017    let t0 = params.t0.into();
1018    let t1 = params.t1.into();
1019    let t2 = params.t2.into();
1020    let t3 = params.t3.into();
1021    let t = params.t.into();
1022
1023    // Account for knot positions
1024    let tau = if (t2 - t1).abs() < f32::EPSILON {
1025        0.0 // Handle degenerate case where t1 == t2
1026    } else {
1027        (t - t1) / (t2 - t1)
1028    };
1029
1030    // Calculate tension/bias parameters based on the spacing between knots.
1031    // This makes the spline respond to actual time intervals rather than
1032    // assuming uniform spacing.
1033    let tension1 = if (t1 - t0).abs() < f32::EPSILON {
1034        1.0
1035    } else {
1036        (t2 - t1) / (t1 - t0)
1037    };
1038    let tension2 = if (t3 - t2).abs() < f32::EPSILON {
1039        1.0
1040    } else {
1041        (t2 - t1) / (t3 - t2)
1042    };
1043
1044    // Tangent vectors that respect the actual knot spacing
1045    let m1 = (params.p2.clone() - params.p0.clone()) * (0.5 * tension1);
1046    let m2 = (params.p3.clone() - params.p1.clone()) * (0.5 * tension2);
1047
1048    // Hermite basis functions
1049    let tau2 = tau * tau;
1050    let tau3 = tau2 * tau;
1051
1052    let h00 = 2.0 * tau3 - 3.0 * tau2 + 1.0;
1053    let h10 = tau3 - 2.0 * tau2 + tau;
1054    let h01 = -2.0 * tau3 + 3.0 * tau2;
1055    let h11 = tau3 - tau2;
1056
1057    // Hermite interpolation with proper tangent vectors
1058    params.p1.clone() * h00 + m1 * h10 + params.p2.clone() * h01 + m2 * h11
1059}
1060
1061/// Analytical 2×2 SVD decomposition of a 3×3 homogeneous matrix.
1062///
1063/// Extracts translation, rotation angle (radians), and diagonal stretch
1064/// (singular values) without depending on any specific math backend.
1065#[cfg(feature = "matrix3")]
1066#[inline(always)]
1067fn decompose_matrix(matrix: &crate::math::Mat3Impl) -> (Trans2, f32, DiagStretch) {
1068    use crate::math::mat3;
1069
1070    // Extract translation from the last column.
1071    let tx = mat3(matrix, 0, 2);
1072    let ty = mat3(matrix, 1, 2);
1073
1074    // Upper-left 2×2 linear block.
1075    let a = mat3(matrix, 0, 0);
1076    let b = mat3(matrix, 0, 1);
1077    let c = mat3(matrix, 1, 0);
1078    let d = mat3(matrix, 1, 1);
1079
1080    // Analytical 2×2 SVD: M = Rot(θ) × diag(σ₁, σ₂) × Rot(-φ).
1081    let e = (a + d) * 0.5;
1082    let f = (a - d) * 0.5;
1083    let g = (c + b) * 0.5;
1084    let h = (c - b) * 0.5;
1085
1086    let q = (e * e + h * h).sqrt();
1087    let r = (f * f + g * g).sqrt();
1088
1089    let s1 = q + r;
1090    let s2 = q - r;
1091
1092    let theta1 = g.atan2(f);
1093    let theta2 = h.atan2(e);
1094
1095    // U rotation angle.
1096    let rotation_angle = (theta2 + theta1) * 0.5;
1097
1098    (Trans2(tx, ty), rotation_angle, DiagStretch(s1, s2))
1099}
1100
1101/// Recompose a 3×3 homogeneous matrix from its decomposed parts.
1102///
1103/// Constructs `Rot(angle) × diag(sx, sy, 1)` with translation in the last column.
1104#[cfg(feature = "matrix3")]
1105#[inline(always)]
1106fn recompose_matrix(
1107    translation: Trans2,
1108    rotation_angle: f32,
1109    stretch: DiagStretch,
1110) -> crate::math::Mat3Impl {
1111    let cos = rotation_angle.cos();
1112    let sin = rotation_angle.sin();
1113
1114    // Rot(θ) × diag(sx, sy, 1) in column-major layout.
1115    crate::math::mat3_from_column_slice(&[
1116        stretch.0 * cos,
1117        stretch.0 * sin,
1118        0.0,
1119        -stretch.1 * sin,
1120        stretch.1 * cos,
1121        0.0,
1122        translation.0,
1123        translation.1,
1124        1.0,
1125    ])
1126}