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