Skip to main content

token_value_map/
animated_data.rs

1use crate::{
2    macros::{impl_animated_data_insert, impl_data_type_ops, impl_sample_for_animated_data},
3    time_data_map::TimeDataMapControl,
4    *,
5};
6
7use crate::Result;
8use core::num::NonZeroU16;
9use enum_dispatch::enum_dispatch;
10use smallvec::SmallVec;
11use std::hash::Hasher;
12
13/// Time-indexed data with interpolation support.
14///
15/// [`AnimatedData`] `enum` stores a collection of time-value pairs for a
16/// specific data type and provides interpolation between keyframes. Each
17/// variant contains a [`TimeDataMap`] for the corresponding data type.
18#[enum_dispatch(AnimatedDataOps)]
19#[derive(Debug, Clone, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[cfg_attr(feature = "facet", derive(Facet))]
22#[cfg_attr(feature = "facet", facet(opaque))]
23#[cfg_attr(feature = "facet", repr(u8))]
24#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
25pub enum AnimatedData {
26    /// Animated boolean values.
27    Boolean(TimeDataMap<Boolean>),
28    /// Animated integer values.
29    Integer(TimeDataMap<Integer>),
30    /// Animated real values.
31    Real(TimeDataMap<Real>),
32    /// Animated string values.
33    String(TimeDataMap<String>),
34    /// Animated color values.
35    Color(TimeDataMap<Color>),
36    /// Animated 2D vectors.
37    #[cfg(feature = "vector2")]
38    Vector2(TimeDataMap<Vector2>),
39    /// Animated 3D vectors.
40    #[cfg(feature = "vector3")]
41    Vector3(TimeDataMap<Vector3>),
42    /// Animated transformation matrices.
43    #[cfg(feature = "matrix3")]
44    Matrix3(TimeDataMap<Matrix3>),
45    /// Animated 3D normal vectors.
46    #[cfg(feature = "normal3")]
47    Normal3(TimeDataMap<Normal3>),
48    /// Animated 3D points.
49    #[cfg(feature = "point3")]
50    Point3(TimeDataMap<Point3>),
51    /// Animated 4×4 transformation matrices.
52    #[cfg(feature = "matrix4")]
53    Matrix4(TimeDataMap<Matrix4>),
54    /// Animated boolean vectors.
55    BooleanVec(TimeDataMap<BooleanVec>),
56    /// Animated integer vectors.
57    IntegerVec(TimeDataMap<IntegerVec>),
58    /// Animated real vectors.
59    RealVec(TimeDataMap<RealVec>),
60    /// Animated color vectors.
61    ColorVec(TimeDataMap<ColorVec>),
62    /// Animated string vectors.
63    StringVec(TimeDataMap<StringVec>),
64    /// Animated 2D vector arrays.
65    #[cfg(all(feature = "vector2", feature = "vec_variants"))]
66    Vector2Vec(TimeDataMap<Vector2Vec>),
67    /// Animated 3D vector arrays.
68    #[cfg(all(feature = "vector3", feature = "vec_variants"))]
69    Vector3Vec(TimeDataMap<Vector3Vec>),
70    /// Animated matrix arrays.
71    #[cfg(all(feature = "matrix3", feature = "vec_variants"))]
72    Matrix3Vec(TimeDataMap<Matrix3Vec>),
73    /// Animated 3D normal arrays.
74    #[cfg(all(feature = "normal3", feature = "vec_variants"))]
75    Normal3Vec(TimeDataMap<Normal3Vec>),
76    /// Animated 3D point arrays.
77    #[cfg(all(feature = "point3", feature = "vec_variants"))]
78    Point3Vec(TimeDataMap<Point3Vec>),
79    /// Animated 4×4 matrix arrays.
80    #[cfg(all(feature = "matrix4", feature = "vec_variants"))]
81    Matrix4Vec(TimeDataMap<Matrix4Vec>),
82    /// Animated real curve.
83    #[cfg(feature = "curves")]
84    RealCurve(TimeDataMap<RealCurve>),
85    /// Animated color curve.
86    #[cfg(feature = "curves")]
87    ColorCurve(TimeDataMap<ColorCurve>),
88}
89
90/// Common operations `trait` for animated data types.
91#[enum_dispatch]
92pub trait AnimatedDataOps {
93    /// Returns the number of time samples.
94    fn len(&self) -> usize;
95    /// Returns `true` if there are no time samples.
96    fn is_empty(&self) -> bool;
97    /// Returns `true` if there is more than one time sample.
98    fn is_animated(&self) -> bool;
99}
100
101impl<T> AnimatedDataOps for TimeDataMap<T> {
102    fn len(&self) -> usize {
103        self.values.len().get()
104    }
105
106    fn is_empty(&self) -> bool {
107        false
108    }
109
110    fn is_animated(&self) -> bool {
111        self.values.len().get() > 1
112    }
113}
114
115impl_data_type_ops!(AnimatedData);
116
117// Interpolation-specific methods (when feature enabled)
118#[cfg(all(feature = "interpolation", feature = "egui-keyframe"))]
119impl AnimatedData {
120    /// Get bezier handles at a given time.
121    ///
122    /// Returns handles for scalar types (Real, Integer). Other types return None.
123    pub fn bezier_handles(&self, time: &Time) -> Option<egui_keyframe::BezierHandles> {
124        match self {
125            AnimatedData::Real(map) => map.interpolation(time).map(crate::key_to_bezier_handles),
126            AnimatedData::Integer(map) => map.interpolation(time).map(crate::key_to_bezier_handles),
127            // Vector/matrix types don't support scalar bezier handles
128            _ => None,
129        }
130    }
131
132    /// Set bezier handles at a given time.
133    ///
134    /// Works for scalar types (Real, Integer). Other types return an error.
135    pub fn set_bezier_handles(
136        &mut self,
137        time: &Time,
138        handles: egui_keyframe::BezierHandles,
139    ) -> Result<()> {
140        match self {
141            AnimatedData::Real(map) => {
142                map.set_interpolation_at(time, crate::bezier_handles_to_key(&handles))
143            }
144            AnimatedData::Integer(map) => {
145                map.set_interpolation_at(time, crate::bezier_handles_to_key(&handles))
146            }
147            _ => Err(Error::BezierNotSupported {
148                got: self.data_type(),
149            }),
150        }
151    }
152
153    /// Set the interpolation type at a given time.
154    ///
155    /// Works for scalar types (Real, Integer). Other types return an error.
156    pub fn set_keyframe_type(
157        &mut self,
158        time: &Time,
159        keyframe_type: egui_keyframe::KeyframeType,
160    ) -> Result<()> {
161        use crate::interpolation::{Interpolation, Key};
162
163        match keyframe_type {
164            egui_keyframe::KeyframeType::Hold => match self {
165                AnimatedData::Real(map) => map.set_interpolation_at(
166                    time,
167                    Key {
168                        interpolation_in: Interpolation::Hold,
169                        interpolation_out: Interpolation::Hold,
170                    },
171                ),
172                AnimatedData::Integer(map) => map.set_interpolation_at(
173                    time,
174                    Key {
175                        interpolation_in: Interpolation::Hold,
176                        interpolation_out: Interpolation::Hold,
177                    },
178                ),
179                _ => Err(Error::BezierNotSupported {
180                    got: self.data_type(),
181                }),
182            },
183            egui_keyframe::KeyframeType::Linear => match self {
184                AnimatedData::Real(map) => map.set_interpolation_at(
185                    time,
186                    Key {
187                        interpolation_in: Interpolation::Linear,
188                        interpolation_out: Interpolation::Linear,
189                    },
190                ),
191                AnimatedData::Integer(map) => map.set_interpolation_at(
192                    time,
193                    Key {
194                        interpolation_in: Interpolation::Linear,
195                        interpolation_out: Interpolation::Linear,
196                    },
197                ),
198                _ => Err(Error::BezierNotSupported {
199                    got: self.data_type(),
200                }),
201            },
202            egui_keyframe::KeyframeType::Bezier => {
203                // Default bezier handles (smooth curve)
204                self.set_bezier_handles(time, egui_keyframe::BezierHandles::default())
205            }
206        }
207    }
208}
209
210impl AnimatedData {
211    /// Get all time samples from this animated data.
212    pub fn times(&self) -> SmallVec<[Time; 10]> {
213        match self {
214            AnimatedData::Boolean(map) => map.iter().map(|(t, _)| *t).collect(),
215            AnimatedData::Integer(map) => map.iter().map(|(t, _)| *t).collect(),
216            AnimatedData::Real(map) => map.iter().map(|(t, _)| *t).collect(),
217            AnimatedData::String(map) => map.iter().map(|(t, _)| *t).collect(),
218            AnimatedData::Color(map) => map.iter().map(|(t, _)| *t).collect(),
219            #[cfg(feature = "vector2")]
220            AnimatedData::Vector2(map) => map.iter().map(|(t, _)| *t).collect(),
221            #[cfg(feature = "vector3")]
222            AnimatedData::Vector3(map) => map.iter().map(|(t, _)| *t).collect(),
223            #[cfg(feature = "matrix3")]
224            AnimatedData::Matrix3(map) => map.iter().map(|(t, _)| *t).collect(),
225            #[cfg(feature = "normal3")]
226            AnimatedData::Normal3(map) => map.iter().map(|(t, _)| *t).collect(),
227            #[cfg(feature = "point3")]
228            AnimatedData::Point3(map) => map.iter().map(|(t, _)| *t).collect(),
229            #[cfg(feature = "matrix4")]
230            AnimatedData::Matrix4(map) => map.iter().map(|(t, _)| *t).collect(),
231            AnimatedData::BooleanVec(map) => map.iter().map(|(t, _)| *t).collect(),
232            AnimatedData::IntegerVec(map) => map.iter().map(|(t, _)| *t).collect(),
233            AnimatedData::RealVec(map) => map.iter().map(|(t, _)| *t).collect(),
234            AnimatedData::ColorVec(map) => map.iter().map(|(t, _)| *t).collect(),
235            AnimatedData::StringVec(map) => map.iter().map(|(t, _)| *t).collect(),
236            #[cfg(all(feature = "vector2", feature = "vec_variants"))]
237            AnimatedData::Vector2Vec(map) => map.iter().map(|(t, _)| *t).collect(),
238            #[cfg(all(feature = "vector3", feature = "vec_variants"))]
239            AnimatedData::Vector3Vec(map) => map.iter().map(|(t, _)| *t).collect(),
240            #[cfg(all(feature = "matrix3", feature = "vec_variants"))]
241            AnimatedData::Matrix3Vec(map) => map.iter().map(|(t, _)| *t).collect(),
242            #[cfg(all(feature = "normal3", feature = "vec_variants"))]
243            AnimatedData::Normal3Vec(map) => map.iter().map(|(t, _)| *t).collect(),
244            #[cfg(all(feature = "point3", feature = "vec_variants"))]
245            AnimatedData::Point3Vec(map) => map.iter().map(|(t, _)| *t).collect(),
246            #[cfg(all(feature = "matrix4", feature = "vec_variants"))]
247            AnimatedData::Matrix4Vec(map) => map.iter().map(|(t, _)| *t).collect(),
248            #[cfg(feature = "curves")]
249            AnimatedData::RealCurve(map) => map.iter().map(|(t, _)| *t).collect(),
250            #[cfg(feature = "curves")]
251            AnimatedData::ColorCurve(map) => map.iter().map(|(t, _)| *t).collect(),
252        }
253    }
254}
255
256impl From<(Time, Value)> for AnimatedData {
257    fn from((time, value): (Time, Value)) -> Self {
258        match value {
259            Value::Uniform(data) => AnimatedData::from((time, data)),
260            Value::Animated(animated_data) => {
261                // If the value is already animated, we need to insert this time
262                // sample For simplicity, we'll just return the
263                // animated data as-is In a more sophisticated
264                // implementation, we might want to merge or replace samples
265                animated_data
266            }
267        }
268    }
269}
270
271impl From<(Time, Data)> for AnimatedData {
272    fn from((time, data): (Time, Data)) -> Self {
273        match data {
274            Data::Boolean(v) => AnimatedData::Boolean(TimeDataMap::from((time, v))),
275            Data::Integer(v) => AnimatedData::Integer(TimeDataMap::from((time, v))),
276            Data::Real(v) => AnimatedData::Real(TimeDataMap::from((time, v))),
277            Data::String(v) => AnimatedData::String(TimeDataMap::from((time, v))),
278            Data::Color(v) => AnimatedData::Color(TimeDataMap::from((time, v))),
279            #[cfg(feature = "vector2")]
280            Data::Vector2(v) => AnimatedData::Vector2(TimeDataMap::from((time, v))),
281            #[cfg(feature = "vector3")]
282            Data::Vector3(v) => AnimatedData::Vector3(TimeDataMap::from((time, v))),
283            #[cfg(feature = "matrix3")]
284            Data::Matrix3(v) => AnimatedData::Matrix3(TimeDataMap::from((time, v))),
285            #[cfg(feature = "normal3")]
286            Data::Normal3(v) => AnimatedData::Normal3(TimeDataMap::from((time, v))),
287            #[cfg(feature = "point3")]
288            Data::Point3(v) => AnimatedData::Point3(TimeDataMap::from((time, v))),
289            #[cfg(feature = "matrix4")]
290            Data::Matrix4(v) => AnimatedData::Matrix4(TimeDataMap::from((time, v))),
291            Data::BooleanVec(v) => AnimatedData::BooleanVec(TimeDataMap::from((time, v))),
292            Data::IntegerVec(v) => AnimatedData::IntegerVec(TimeDataMap::from((time, v))),
293            Data::RealVec(v) => AnimatedData::RealVec(TimeDataMap::from((time, v))),
294            Data::ColorVec(v) => AnimatedData::ColorVec(TimeDataMap::from((time, v))),
295            Data::StringVec(v) => AnimatedData::StringVec(TimeDataMap::from((time, v))),
296            #[cfg(all(feature = "vector2", feature = "vec_variants"))]
297            Data::Vector2Vec(v) => AnimatedData::Vector2Vec(TimeDataMap::from((time, v))),
298            #[cfg(all(feature = "vector3", feature = "vec_variants"))]
299            Data::Vector3Vec(v) => AnimatedData::Vector3Vec(TimeDataMap::from((time, v))),
300            #[cfg(all(feature = "matrix3", feature = "vec_variants"))]
301            Data::Matrix3Vec(v) => AnimatedData::Matrix3Vec(TimeDataMap::from((time, v))),
302            #[cfg(all(feature = "normal3", feature = "vec_variants"))]
303            Data::Normal3Vec(v) => AnimatedData::Normal3Vec(TimeDataMap::from((time, v))),
304            #[cfg(all(feature = "point3", feature = "vec_variants"))]
305            Data::Point3Vec(v) => AnimatedData::Point3Vec(TimeDataMap::from((time, v))),
306            #[cfg(all(feature = "matrix4", feature = "vec_variants"))]
307            Data::Matrix4Vec(v) => AnimatedData::Matrix4Vec(TimeDataMap::from((time, v))),
308            #[cfg(feature = "curves")]
309            Data::RealCurve(v) => AnimatedData::RealCurve(TimeDataMap::from((time, v))),
310            #[cfg(feature = "curves")]
311            Data::ColorCurve(v) => AnimatedData::ColorCurve(TimeDataMap::from((time, v))),
312        }
313    }
314}
315
316impl_animated_data_insert!(
317    insert_boolean, Boolean, Boolean;
318    insert_integer, Integer, Integer;
319    insert_real, Real, Real;
320    insert_string, String, String;
321    insert_color, Color, Color;
322);
323
324#[cfg(feature = "vector2")]
325impl_animated_data_insert!(
326    insert_vector2, Vector2, Vector2;
327);
328
329#[cfg(feature = "vector3")]
330impl_animated_data_insert!(
331    insert_vector3, Vector3, Vector3;
332);
333
334#[cfg(feature = "matrix3")]
335impl_animated_data_insert!(
336    insert_matrix3, Matrix3, Matrix3;
337);
338
339#[cfg(feature = "normal3")]
340impl_animated_data_insert!(
341    insert_normal3, Normal3, Normal3;
342);
343
344#[cfg(feature = "point3")]
345impl_animated_data_insert!(
346    insert_point3, Point3, Point3;
347);
348
349#[cfg(feature = "matrix4")]
350impl_animated_data_insert!(
351    insert_matrix4, Matrix4, Matrix4;
352);
353
354impl AnimatedData {
355    /// Generic insert method that takes `Data` and matches the type to the
356    /// `AnimatedData` variant.
357    pub fn try_insert(&mut self, time: Time, value: Data) -> Result<()> {
358        match (self, value) {
359            (AnimatedData::Boolean(map), Data::Boolean(v)) => {
360                map.insert(time, v);
361                Ok(())
362            }
363            (AnimatedData::Integer(map), Data::Integer(v)) => {
364                map.insert(time, v);
365                Ok(())
366            }
367            (AnimatedData::Real(map), Data::Real(v)) => {
368                map.insert(time, v);
369                Ok(())
370            }
371            (AnimatedData::String(map), Data::String(v)) => {
372                map.insert(time, v);
373                Ok(())
374            }
375            (AnimatedData::Color(map), Data::Color(v)) => {
376                map.insert(time, v);
377                Ok(())
378            }
379            #[cfg(feature = "vector2")]
380            (AnimatedData::Vector2(map), Data::Vector2(v)) => {
381                map.insert(time, v);
382                Ok(())
383            }
384            #[cfg(feature = "vector3")]
385            (AnimatedData::Vector3(map), Data::Vector3(v)) => {
386                map.insert(time, v);
387                Ok(())
388            }
389            #[cfg(feature = "matrix3")]
390            (AnimatedData::Matrix3(map), Data::Matrix3(v)) => {
391                map.insert(time, v);
392                Ok(())
393            }
394            #[cfg(feature = "normal3")]
395            (AnimatedData::Normal3(map), Data::Normal3(v)) => {
396                map.insert(time, v);
397                Ok(())
398            }
399            #[cfg(feature = "point3")]
400            (AnimatedData::Point3(map), Data::Point3(v)) => {
401                map.insert(time, v);
402                Ok(())
403            }
404            #[cfg(feature = "matrix4")]
405            (AnimatedData::Matrix4(map), Data::Matrix4(v)) => {
406                map.insert(time, v);
407                Ok(())
408            }
409            (AnimatedData::BooleanVec(map), Data::BooleanVec(v)) => {
410                map.insert(time, v);
411                Ok(())
412            }
413            (AnimatedData::IntegerVec(map), Data::IntegerVec(v)) => {
414                map.insert(time, v);
415                Ok(())
416            }
417            (AnimatedData::RealVec(map), Data::RealVec(v)) => {
418                map.insert(time, v);
419                Ok(())
420            }
421            (AnimatedData::ColorVec(map), Data::ColorVec(v)) => {
422                map.insert(time, v);
423                Ok(())
424            }
425            (AnimatedData::StringVec(map), Data::StringVec(v)) => {
426                map.insert(time, v);
427                Ok(())
428            }
429            #[cfg(all(feature = "vector2", feature = "vec_variants"))]
430            (AnimatedData::Vector2Vec(map), Data::Vector2Vec(v)) => {
431                map.insert(time, v);
432                Ok(())
433            }
434            #[cfg(all(feature = "vector3", feature = "vec_variants"))]
435            (AnimatedData::Vector3Vec(map), Data::Vector3Vec(v)) => {
436                map.insert(time, v);
437                Ok(())
438            }
439            #[cfg(all(feature = "matrix3", feature = "vec_variants"))]
440            (AnimatedData::Matrix3Vec(map), Data::Matrix3Vec(v)) => {
441                map.insert(time, v);
442                Ok(())
443            }
444            #[cfg(all(feature = "normal3", feature = "vec_variants"))]
445            (AnimatedData::Normal3Vec(map), Data::Normal3Vec(v)) => {
446                map.insert(time, v);
447                Ok(())
448            }
449            #[cfg(all(feature = "point3", feature = "vec_variants"))]
450            (AnimatedData::Point3Vec(map), Data::Point3Vec(v)) => {
451                map.insert(time, v);
452                Ok(())
453            }
454            #[cfg(all(feature = "matrix4", feature = "vec_variants"))]
455            (AnimatedData::Matrix4Vec(map), Data::Matrix4Vec(v)) => {
456                map.insert(time, v);
457                Ok(())
458            }
459            #[cfg(feature = "curves")]
460            (AnimatedData::RealCurve(map), Data::RealCurve(v)) => {
461                map.insert(time, v);
462                Ok(())
463            }
464            #[cfg(feature = "curves")]
465            (AnimatedData::ColorCurve(map), Data::ColorCurve(v)) => {
466                map.insert(time, v);
467                Ok(())
468            }
469            (s, v) => Err(Error::SampleTypeMismatch {
470                expected: s.data_type(),
471                got: v.data_type(),
472            }),
473        }
474    }
475
476    /// Remove a sample at the given time.
477    ///
478    /// Returns the removed value as [`Data`] if it existed. Returns `None`
479    /// if the key was not found or if it was the last sample (the non-empty
480    /// invariant prevents removing it). Use
481    /// [`Value::remove_at_or_to_uniform`](crate::Value::remove_at_or_to_uniform)
482    /// to degrade to uniform instead.
483    pub fn remove_at(&mut self, time: &Time) -> Option<Data> {
484        // AIDEV-NOTE: map.remove() returns Result<Option<V>>. Err(LastSample)
485        // is flattened to None -- callers use remove_at_or_to_uniform for that case.
486        match self {
487            AnimatedData::Boolean(map) => map.remove(time).ok()?.map(Data::Boolean),
488            AnimatedData::Integer(map) => map.remove(time).ok()?.map(Data::Integer),
489            AnimatedData::Real(map) => map.remove(time).ok()?.map(Data::Real),
490            AnimatedData::String(map) => map.remove(time).ok()?.map(Data::String),
491            AnimatedData::Color(map) => map.remove(time).ok()?.map(Data::Color),
492            #[cfg(feature = "vector2")]
493            AnimatedData::Vector2(map) => map.remove(time).ok()?.map(Data::Vector2),
494            #[cfg(feature = "vector3")]
495            AnimatedData::Vector3(map) => map.remove(time).ok()?.map(Data::Vector3),
496            #[cfg(feature = "matrix3")]
497            AnimatedData::Matrix3(map) => map.remove(time).ok()?.map(Data::Matrix3),
498            #[cfg(feature = "normal3")]
499            AnimatedData::Normal3(map) => map.remove(time).ok()?.map(Data::Normal3),
500            #[cfg(feature = "point3")]
501            AnimatedData::Point3(map) => map.remove(time).ok()?.map(Data::Point3),
502            #[cfg(feature = "matrix4")]
503            AnimatedData::Matrix4(map) => map.remove(time).ok()?.map(Data::Matrix4),
504            AnimatedData::BooleanVec(map) => map.remove(time).ok()?.map(Data::BooleanVec),
505            AnimatedData::IntegerVec(map) => map.remove(time).ok()?.map(Data::IntegerVec),
506            AnimatedData::RealVec(map) => map.remove(time).ok()?.map(Data::RealVec),
507            AnimatedData::ColorVec(map) => map.remove(time).ok()?.map(Data::ColorVec),
508            AnimatedData::StringVec(map) => map.remove(time).ok()?.map(Data::StringVec),
509            #[cfg(all(feature = "vector2", feature = "vec_variants"))]
510            AnimatedData::Vector2Vec(map) => map.remove(time).ok()?.map(Data::Vector2Vec),
511            #[cfg(all(feature = "vector3", feature = "vec_variants"))]
512            AnimatedData::Vector3Vec(map) => map.remove(time).ok()?.map(Data::Vector3Vec),
513            #[cfg(all(feature = "matrix3", feature = "vec_variants"))]
514            AnimatedData::Matrix3Vec(map) => map.remove(time).ok()?.map(Data::Matrix3Vec),
515            #[cfg(all(feature = "normal3", feature = "vec_variants"))]
516            AnimatedData::Normal3Vec(map) => map.remove(time).ok()?.map(Data::Normal3Vec),
517            #[cfg(all(feature = "point3", feature = "vec_variants"))]
518            AnimatedData::Point3Vec(map) => map.remove(time).ok()?.map(Data::Point3Vec),
519            #[cfg(all(feature = "matrix4", feature = "vec_variants"))]
520            AnimatedData::Matrix4Vec(map) => map.remove(time).ok()?.map(Data::Matrix4Vec),
521            #[cfg(feature = "curves")]
522            AnimatedData::RealCurve(map) => map.remove(time).ok()?.map(Data::RealCurve),
523            #[cfg(feature = "curves")]
524            AnimatedData::ColorCurve(map) => map.remove(time).ok()?.map(Data::ColorCurve),
525        }
526    }
527
528    pub fn sample_at(&self, time: Time) -> Option<Data> {
529        match self {
530            AnimatedData::Boolean(map) => map.get(&time).map(|v| Data::Boolean(v.clone())),
531            AnimatedData::Integer(map) => map.get(&time).map(|v| Data::Integer(v.clone())),
532            AnimatedData::Real(map) => map.get(&time).map(|v| Data::Real(v.clone())),
533            AnimatedData::String(map) => map.get(&time).map(|v| Data::String(v.clone())),
534            AnimatedData::Color(map) => map.get(&time).map(|v| Data::Color(*v)),
535            #[cfg(feature = "vector2")]
536            AnimatedData::Vector2(map) => map.get(&time).map(|v| Data::Vector2(v.clone())),
537            #[cfg(feature = "vector3")]
538            AnimatedData::Vector3(map) => map.get(&time).map(|v| Data::Vector3(v.clone())),
539            #[cfg(feature = "matrix3")]
540            AnimatedData::Matrix3(map) => map.get(&time).map(|v| Data::Matrix3(v.clone())),
541            #[cfg(feature = "normal3")]
542            AnimatedData::Normal3(map) => map.get(&time).map(|v| Data::Normal3(v.clone())),
543            #[cfg(feature = "point3")]
544            AnimatedData::Point3(map) => map.get(&time).map(|v| Data::Point3(v.clone())),
545            #[cfg(feature = "matrix4")]
546            AnimatedData::Matrix4(map) => map.get(&time).map(|v| Data::Matrix4(v.clone())),
547            AnimatedData::BooleanVec(map) => map.get(&time).map(|v| Data::BooleanVec(v.clone())),
548            AnimatedData::IntegerVec(map) => map.get(&time).map(|v| Data::IntegerVec(v.clone())),
549            AnimatedData::RealVec(map) => map.get(&time).map(|v| Data::RealVec(v.clone())),
550            AnimatedData::ColorVec(map) => map.get(&time).map(|v| Data::ColorVec(v.clone())),
551            AnimatedData::StringVec(map) => map.get(&time).map(|v| Data::StringVec(v.clone())),
552            #[cfg(all(feature = "vector2", feature = "vec_variants"))]
553            AnimatedData::Vector2Vec(map) => map.get(&time).map(|v| Data::Vector2Vec(v.clone())),
554            #[cfg(all(feature = "vector3", feature = "vec_variants"))]
555            AnimatedData::Vector3Vec(map) => map.get(&time).map(|v| Data::Vector3Vec(v.clone())),
556            #[cfg(all(feature = "matrix3", feature = "vec_variants"))]
557            AnimatedData::Matrix3Vec(map) => map.get(&time).map(|v| Data::Matrix3Vec(v.clone())),
558            #[cfg(all(feature = "normal3", feature = "vec_variants"))]
559            AnimatedData::Normal3Vec(map) => map.get(&time).map(|v| Data::Normal3Vec(v.clone())),
560            #[cfg(all(feature = "point3", feature = "vec_variants"))]
561            AnimatedData::Point3Vec(map) => map.get(&time).map(|v| Data::Point3Vec(v.clone())),
562            #[cfg(all(feature = "matrix4", feature = "vec_variants"))]
563            AnimatedData::Matrix4Vec(map) => map.get(&time).map(|v| Data::Matrix4Vec(v.clone())),
564            #[cfg(feature = "curves")]
565            AnimatedData::RealCurve(map) => map.get(&time).map(|v| Data::RealCurve(v.clone())),
566            #[cfg(feature = "curves")]
567            AnimatedData::ColorCurve(map) => map.get(&time).map(|v| Data::ColorCurve(v.clone())),
568        }
569    }
570
571    pub fn interpolate(&self, time: Time) -> Data {
572        match self {
573            AnimatedData::Boolean(map) => Data::Boolean(map.closest_sample(time).clone()),
574            AnimatedData::Integer(map) => {
575                if TimeDataMapControl::is_animated(map) {
576                    Data::Integer(map.interpolate(time))
577                } else {
578                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
579                    Data::Integer(map.iter().next().unwrap().1.clone())
580                }
581            }
582            AnimatedData::Real(map) => {
583                if TimeDataMapControl::is_animated(map) {
584                    Data::Real(map.interpolate(time))
585                } else {
586                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
587                    Data::Real(map.iter().next().unwrap().1.clone())
588                }
589            }
590            AnimatedData::String(map) => Data::String(map.closest_sample(time).clone()),
591            AnimatedData::Color(map) => {
592                if TimeDataMapControl::is_animated(map) {
593                    Data::Color(map.interpolate(time))
594                } else {
595                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
596                    Data::Color(*map.iter().next().unwrap().1)
597                }
598            }
599            #[cfg(feature = "vector2")]
600            AnimatedData::Vector2(map) => {
601                if TimeDataMapControl::is_animated(map) {
602                    Data::Vector2(map.interpolate(time))
603                } else {
604                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
605                    Data::Vector2(map.iter().next().unwrap().1.clone())
606                }
607            }
608            #[cfg(feature = "vector3")]
609            AnimatedData::Vector3(map) => {
610                if TimeDataMapControl::is_animated(map) {
611                    Data::Vector3(map.interpolate(time))
612                } else {
613                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
614                    Data::Vector3(map.iter().next().unwrap().1.clone())
615                }
616            }
617            #[cfg(feature = "matrix3")]
618            AnimatedData::Matrix3(map) => {
619                if TimeDataMapControl::is_animated(map) {
620                    Data::Matrix3(map.interpolate(time))
621                } else {
622                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
623                    Data::Matrix3(map.iter().next().unwrap().1.clone())
624                }
625            }
626            #[cfg(feature = "normal3")]
627            AnimatedData::Normal3(map) => {
628                if TimeDataMapControl::is_animated(map) {
629                    Data::Normal3(map.interpolate(time))
630                } else {
631                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
632                    Data::Normal3(map.iter().next().unwrap().1.clone())
633                }
634            }
635            #[cfg(feature = "point3")]
636            AnimatedData::Point3(map) => {
637                if TimeDataMapControl::is_animated(map) {
638                    Data::Point3(map.interpolate(time))
639                } else {
640                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
641                    Data::Point3(map.iter().next().unwrap().1.clone())
642                }
643            }
644            #[cfg(feature = "matrix4")]
645            AnimatedData::Matrix4(map) => {
646                if TimeDataMapControl::is_animated(map) {
647                    Data::Matrix4(map.interpolate(time))
648                } else {
649                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
650                    Data::Matrix4(map.iter().next().unwrap().1.clone())
651                }
652            }
653            AnimatedData::BooleanVec(map) => Data::BooleanVec(map.closest_sample(time).clone()),
654            AnimatedData::IntegerVec(map) => {
655                if TimeDataMapControl::is_animated(map) {
656                    Data::IntegerVec(map.interpolate(time))
657                } else {
658                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
659                    Data::IntegerVec(map.iter().next().unwrap().1.clone())
660                }
661            }
662            AnimatedData::RealVec(map) => {
663                if TimeDataMapControl::is_animated(map) {
664                    Data::RealVec(map.interpolate(time))
665                } else {
666                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
667                    Data::RealVec(map.iter().next().unwrap().1.clone())
668                }
669            }
670            AnimatedData::ColorVec(map) => {
671                if TimeDataMapControl::is_animated(map) {
672                    Data::ColorVec(map.interpolate(time))
673                } else {
674                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
675                    Data::ColorVec(map.iter().next().unwrap().1.clone())
676                }
677            }
678            AnimatedData::StringVec(map) => Data::StringVec(map.closest_sample(time).clone()),
679            #[cfg(all(feature = "vector2", feature = "vec_variants"))]
680            AnimatedData::Vector2Vec(map) => {
681                if TimeDataMapControl::is_animated(map) {
682                    Data::Vector2Vec(map.interpolate(time))
683                } else {
684                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
685                    Data::Vector2Vec(map.iter().next().unwrap().1.clone())
686                }
687            }
688            #[cfg(all(feature = "vector3", feature = "vec_variants"))]
689            AnimatedData::Vector3Vec(map) => {
690                if TimeDataMapControl::is_animated(map) {
691                    Data::Vector3Vec(map.interpolate(time))
692                } else {
693                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
694                    Data::Vector3Vec(map.iter().next().unwrap().1.clone())
695                }
696            }
697            #[cfg(all(feature = "matrix3", feature = "vec_variants"))]
698            AnimatedData::Matrix3Vec(map) => {
699                if TimeDataMapControl::is_animated(map) {
700                    Data::Matrix3Vec(map.interpolate(time))
701                } else {
702                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
703                    Data::Matrix3Vec(map.iter().next().unwrap().1.clone())
704                }
705            }
706            #[cfg(all(feature = "normal3", feature = "vec_variants"))]
707            AnimatedData::Normal3Vec(map) => {
708                if TimeDataMapControl::is_animated(map) {
709                    Data::Normal3Vec(map.interpolate(time))
710                } else {
711                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
712                    Data::Normal3Vec(map.iter().next().unwrap().1.clone())
713                }
714            }
715            #[cfg(all(feature = "point3", feature = "vec_variants"))]
716            AnimatedData::Point3Vec(map) => {
717                if TimeDataMapControl::is_animated(map) {
718                    Data::Point3Vec(map.interpolate(time))
719                } else {
720                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
721                    Data::Point3Vec(map.iter().next().unwrap().1.clone())
722                }
723            }
724            #[cfg(all(feature = "matrix4", feature = "vec_variants"))]
725            AnimatedData::Matrix4Vec(map) => {
726                if TimeDataMapControl::is_animated(map) {
727                    Data::Matrix4Vec(map.interpolate(time))
728                } else {
729                    // SAFETY: BTreeMap1 guarantees non-empty; !is_animated() means exactly one entry.
730                    Data::Matrix4Vec(map.iter().next().unwrap().1.clone())
731                }
732            }
733            #[cfg(feature = "curves")]
734            AnimatedData::RealCurve(map) => Data::RealCurve(map.closest_sample(time).clone()),
735            #[cfg(feature = "curves")]
736            AnimatedData::ColorCurve(map) => Data::ColorCurve(map.closest_sample(time).clone()),
737        }
738    }
739}
740
741impl Hash for AnimatedData {
742    fn hash<H: Hasher>(&self, state: &mut H) {
743        std::mem::discriminant(self).hash(state);
744        match self {
745            AnimatedData::Boolean(map) => {
746                map.len().hash(state);
747                #[cfg(not(feature = "interpolation"))]
748                for (time, value) in map.values.as_btree_map() {
749                    time.hash(state);
750                    value.0.hash(state);
751                }
752                #[cfg(feature = "interpolation")]
753                for (time, (value, spec)) in map.values.as_btree_map() {
754                    time.hash(state);
755                    value.0.hash(state);
756                    spec.hash(state);
757                }
758            }
759            AnimatedData::Integer(map) => {
760                map.len().hash(state);
761                #[cfg(not(feature = "interpolation"))]
762                for (time, value) in map.values.as_btree_map() {
763                    time.hash(state);
764                    value.0.hash(state);
765                }
766                #[cfg(feature = "interpolation")]
767                for (time, (value, spec)) in map.values.as_btree_map() {
768                    time.hash(state);
769                    value.0.hash(state);
770                    spec.hash(state);
771                }
772            }
773            AnimatedData::Real(map) => {
774                map.len().hash(state);
775                #[cfg(not(feature = "interpolation"))]
776                for (time, value) in map.values.as_btree_map() {
777                    time.hash(state);
778                    value.0.to_bits().hash(state);
779                }
780                #[cfg(feature = "interpolation")]
781                for (time, (value, spec)) in map.values.as_btree_map() {
782                    time.hash(state);
783                    value.0.to_bits().hash(state);
784                    spec.hash(state);
785                }
786            }
787            AnimatedData::String(map) => {
788                map.len().hash(state);
789                #[cfg(not(feature = "interpolation"))]
790                for (time, value) in map.values.as_btree_map() {
791                    time.hash(state);
792                    value.0.hash(state);
793                }
794                #[cfg(feature = "interpolation")]
795                for (time, (value, spec)) in map.values.as_btree_map() {
796                    time.hash(state);
797                    value.0.hash(state);
798                    spec.hash(state);
799                }
800            }
801            AnimatedData::Color(map) => {
802                map.len().hash(state);
803                #[cfg(not(feature = "interpolation"))]
804                for (time, value) in map.values.as_btree_map() {
805                    time.hash(state);
806                    value.0.iter().for_each(|v| v.to_bits().hash(state));
807                }
808                #[cfg(feature = "interpolation")]
809                for (time, (value, spec)) in map.values.as_btree_map() {
810                    time.hash(state);
811                    value.0.iter().for_each(|v| v.to_bits().hash(state));
812                    spec.hash(state);
813                }
814            }
815            #[cfg(feature = "vector2")]
816            AnimatedData::Vector2(map) => {
817                map.len().hash(state);
818                #[cfg(not(feature = "interpolation"))]
819                for (time, value) in map.values.as_btree_map() {
820                    time.hash(state);
821                    crate::math::vec2_as_slice(&value.0)
822                        .iter()
823                        .for_each(|v| v.to_bits().hash(state));
824                }
825                #[cfg(feature = "interpolation")]
826                for (time, (value, spec)) in map.values.as_btree_map() {
827                    time.hash(state);
828                    crate::math::vec2_as_slice(&value.0)
829                        .iter()
830                        .for_each(|v| v.to_bits().hash(state));
831                    spec.hash(state);
832                }
833            }
834            #[cfg(feature = "vector3")]
835            AnimatedData::Vector3(map) => {
836                map.len().hash(state);
837                #[cfg(not(feature = "interpolation"))]
838                for (time, value) in map.values.as_btree_map() {
839                    time.hash(state);
840                    crate::math::vec3_as_slice(&value.0)
841                        .iter()
842                        .for_each(|v| v.to_bits().hash(state));
843                }
844                #[cfg(feature = "interpolation")]
845                for (time, (value, spec)) in map.values.as_btree_map() {
846                    time.hash(state);
847                    crate::math::vec3_as_slice(&value.0)
848                        .iter()
849                        .for_each(|v| v.to_bits().hash(state));
850                    spec.hash(state);
851                }
852            }
853            #[cfg(feature = "matrix3")]
854            AnimatedData::Matrix3(map) => {
855                map.len().hash(state);
856                #[cfg(not(feature = "interpolation"))]
857                for (time, value) in map.values.as_btree_map() {
858                    time.hash(state);
859                    crate::math::mat3_iter(&value.0).for_each(|v| v.to_bits().hash(state));
860                }
861                #[cfg(feature = "interpolation")]
862                for (time, (value, spec)) in map.values.as_btree_map() {
863                    time.hash(state);
864                    crate::math::mat3_iter(&value.0).for_each(|v| v.to_bits().hash(state));
865                    spec.hash(state);
866                }
867            }
868            #[cfg(feature = "normal3")]
869            AnimatedData::Normal3(map) => {
870                map.len().hash(state);
871                #[cfg(not(feature = "interpolation"))]
872                for (time, value) in map.values.as_btree_map() {
873                    time.hash(state);
874                    crate::math::vec3_as_slice(&value.0)
875                        .iter()
876                        .for_each(|v| v.to_bits().hash(state));
877                }
878                #[cfg(feature = "interpolation")]
879                for (time, (value, spec)) in map.values.as_btree_map() {
880                    time.hash(state);
881                    crate::math::vec3_as_slice(&value.0)
882                        .iter()
883                        .for_each(|v| v.to_bits().hash(state));
884                    spec.hash(state);
885                }
886            }
887            #[cfg(feature = "point3")]
888            AnimatedData::Point3(map) => {
889                map.len().hash(state);
890                #[cfg(not(feature = "interpolation"))]
891                for (time, value) in map.values.as_btree_map() {
892                    time.hash(state);
893                    crate::math::point3_as_slice(&value.0)
894                        .iter()
895                        .for_each(|v| v.to_bits().hash(state));
896                }
897                #[cfg(feature = "interpolation")]
898                for (time, (value, spec)) in map.values.as_btree_map() {
899                    time.hash(state);
900                    crate::math::point3_as_slice(&value.0)
901                        .iter()
902                        .for_each(|v| v.to_bits().hash(state));
903                    spec.hash(state);
904                }
905            }
906            #[cfg(feature = "matrix4")]
907            AnimatedData::Matrix4(map) => {
908                map.len().hash(state);
909                #[cfg(not(feature = "interpolation"))]
910                for (time, value) in map.values.as_btree_map() {
911                    time.hash(state);
912                    crate::math::mat4_iter(&value.0).for_each(|v| v.to_bits().hash(state));
913                }
914                #[cfg(feature = "interpolation")]
915                for (time, (value, spec)) in map.values.as_btree_map() {
916                    time.hash(state);
917                    crate::math::mat4_iter(&value.0).for_each(|v| v.to_bits().hash(state));
918                    spec.hash(state);
919                }
920            }
921            AnimatedData::BooleanVec(map) => {
922                map.len().hash(state);
923                #[cfg(not(feature = "interpolation"))]
924                for (time, value) in map.values.as_btree_map() {
925                    time.hash(state);
926                    value.0.hash(state);
927                }
928                #[cfg(feature = "interpolation")]
929                for (time, (value, spec)) in map.values.as_btree_map() {
930                    time.hash(state);
931                    value.0.hash(state);
932                    spec.hash(state);
933                }
934            }
935            AnimatedData::IntegerVec(map) => {
936                map.len().hash(state);
937                #[cfg(not(feature = "interpolation"))]
938                for (time, value) in map.values.as_btree_map() {
939                    time.hash(state);
940                    value.0.hash(state);
941                }
942                #[cfg(feature = "interpolation")]
943                for (time, (value, spec)) in map.values.as_btree_map() {
944                    time.hash(state);
945                    value.0.hash(state);
946                    spec.hash(state);
947                }
948            }
949            AnimatedData::RealVec(map) => {
950                map.len().hash(state);
951                #[cfg(not(feature = "interpolation"))]
952                for (time, value) in map.values.as_btree_map() {
953                    time.hash(state);
954                    value.0.len().hash(state);
955                    value.0.iter().for_each(|v| v.to_bits().hash(state));
956                }
957                #[cfg(feature = "interpolation")]
958                for (time, (value, spec)) in map.values.as_btree_map() {
959                    time.hash(state);
960                    value.0.len().hash(state);
961                    value.0.iter().for_each(|v| v.to_bits().hash(state));
962                    spec.hash(state);
963                }
964            }
965            AnimatedData::ColorVec(map) => {
966                map.len().hash(state);
967                #[cfg(not(feature = "interpolation"))]
968                for (time, value) in map.values.as_btree_map() {
969                    time.hash(state);
970                    value.0.len().hash(state);
971                    value.0.iter().for_each(|c| {
972                        c.iter().for_each(|v| v.to_bits().hash(state));
973                    });
974                }
975                #[cfg(feature = "interpolation")]
976                for (time, (value, spec)) in map.values.as_btree_map() {
977                    time.hash(state);
978                    value.0.len().hash(state);
979                    value.0.iter().for_each(|c| {
980                        c.iter().for_each(|v| v.to_bits().hash(state));
981                    });
982                    spec.hash(state);
983                }
984            }
985            AnimatedData::StringVec(map) => {
986                map.len().hash(state);
987                #[cfg(not(feature = "interpolation"))]
988                for (time, value) in map.values.as_btree_map() {
989                    time.hash(state);
990                    value.0.hash(state);
991                }
992                #[cfg(feature = "interpolation")]
993                for (time, (value, spec)) in map.values.as_btree_map() {
994                    time.hash(state);
995                    value.0.hash(state);
996                    spec.hash(state);
997                }
998            }
999            #[cfg(all(feature = "vector2", feature = "vec_variants"))]
1000            AnimatedData::Vector2Vec(map) => {
1001                map.len().hash(state);
1002                #[cfg(not(feature = "interpolation"))]
1003                for (time, value) in map.values.as_btree_map() {
1004                    time.hash(state);
1005                    value.0.len().hash(state);
1006                    value.0.iter().for_each(|v| {
1007                        crate::math::vec2_as_slice(v)
1008                            .iter()
1009                            .for_each(|f| f.to_bits().hash(state));
1010                    });
1011                }
1012                #[cfg(feature = "interpolation")]
1013                for (time, (value, spec)) in map.values.as_btree_map() {
1014                    time.hash(state);
1015                    value.0.len().hash(state);
1016                    value.0.iter().for_each(|v| {
1017                        crate::math::vec2_as_slice(v)
1018                            .iter()
1019                            .for_each(|f| f.to_bits().hash(state));
1020                    });
1021                    spec.hash(state);
1022                }
1023            }
1024            #[cfg(all(feature = "vector3", feature = "vec_variants"))]
1025            AnimatedData::Vector3Vec(map) => {
1026                map.len().hash(state);
1027                #[cfg(not(feature = "interpolation"))]
1028                for (time, value) in map.values.as_btree_map() {
1029                    time.hash(state);
1030                    value.0.len().hash(state);
1031                    value.0.iter().for_each(|v| {
1032                        crate::math::vec3_as_slice(v)
1033                            .iter()
1034                            .for_each(|f| f.to_bits().hash(state));
1035                    });
1036                }
1037                #[cfg(feature = "interpolation")]
1038                for (time, (value, spec)) in map.values.as_btree_map() {
1039                    time.hash(state);
1040                    value.0.len().hash(state);
1041                    value.0.iter().for_each(|v| {
1042                        crate::math::vec3_as_slice(v)
1043                            .iter()
1044                            .for_each(|f| f.to_bits().hash(state));
1045                    });
1046                    spec.hash(state);
1047                }
1048            }
1049            #[cfg(all(feature = "matrix3", feature = "vec_variants"))]
1050            AnimatedData::Matrix3Vec(map) => {
1051                map.len().hash(state);
1052                #[cfg(not(feature = "interpolation"))]
1053                for (time, value) in map.values.as_btree_map() {
1054                    time.hash(state);
1055                    value.0.len().hash(state);
1056                    value.0.iter().for_each(|m| {
1057                        crate::math::mat3_iter(m).for_each(|f| f.to_bits().hash(state));
1058                    });
1059                }
1060                #[cfg(feature = "interpolation")]
1061                for (time, (value, spec)) in map.values.as_btree_map() {
1062                    time.hash(state);
1063                    value.0.len().hash(state);
1064                    value.0.iter().for_each(|m| {
1065                        crate::math::mat3_iter(m).for_each(|f| f.to_bits().hash(state));
1066                    });
1067                    spec.hash(state);
1068                }
1069            }
1070            #[cfg(all(feature = "normal3", feature = "vec_variants"))]
1071            AnimatedData::Normal3Vec(map) => {
1072                map.len().hash(state);
1073                #[cfg(not(feature = "interpolation"))]
1074                for (time, value) in map.values.as_btree_map() {
1075                    time.hash(state);
1076                    value.0.len().hash(state);
1077                    value.0.iter().for_each(|v| {
1078                        crate::math::vec3_as_slice(v)
1079                            .iter()
1080                            .for_each(|f| f.to_bits().hash(state));
1081                    });
1082                }
1083                #[cfg(feature = "interpolation")]
1084                for (time, (value, spec)) in map.values.as_btree_map() {
1085                    time.hash(state);
1086                    value.0.len().hash(state);
1087                    value.0.iter().for_each(|v| {
1088                        crate::math::vec3_as_slice(v)
1089                            .iter()
1090                            .for_each(|f| f.to_bits().hash(state));
1091                    });
1092                    spec.hash(state);
1093                }
1094            }
1095            #[cfg(all(feature = "point3", feature = "vec_variants"))]
1096            AnimatedData::Point3Vec(map) => {
1097                map.len().hash(state);
1098                #[cfg(not(feature = "interpolation"))]
1099                for (time, value) in map.values.as_btree_map() {
1100                    time.hash(state);
1101                    value.0.len().hash(state);
1102                    value.0.iter().for_each(|p| {
1103                        crate::math::point3_as_slice(p)
1104                            .iter()
1105                            .for_each(|f| f.to_bits().hash(state));
1106                    });
1107                }
1108                #[cfg(feature = "interpolation")]
1109                for (time, (value, spec)) in map.values.as_btree_map() {
1110                    time.hash(state);
1111                    value.0.len().hash(state);
1112                    value.0.iter().for_each(|p| {
1113                        crate::math::point3_as_slice(p)
1114                            .iter()
1115                            .for_each(|f| f.to_bits().hash(state));
1116                    });
1117                    spec.hash(state);
1118                }
1119            }
1120            #[cfg(all(feature = "matrix4", feature = "vec_variants"))]
1121            AnimatedData::Matrix4Vec(map) => {
1122                map.len().hash(state);
1123                #[cfg(not(feature = "interpolation"))]
1124                for (time, value) in map.values.as_btree_map() {
1125                    time.hash(state);
1126                    value.0.len().hash(state);
1127                    value.0.iter().for_each(|m| {
1128                        crate::math::mat4_iter(m).for_each(|f| f.to_bits().hash(state));
1129                    });
1130                }
1131                #[cfg(feature = "interpolation")]
1132                for (time, (value, spec)) in map.values.as_btree_map() {
1133                    time.hash(state);
1134                    value.0.len().hash(state);
1135                    value.0.iter().for_each(|m| {
1136                        crate::math::mat4_iter(m).for_each(|f| f.to_bits().hash(state));
1137                    });
1138                    spec.hash(state);
1139                }
1140            }
1141            #[cfg(feature = "curves")]
1142            AnimatedData::RealCurve(map) => {
1143                map.len().hash(state);
1144                #[cfg(not(feature = "interpolation"))]
1145                for (time, value) in map.values.as_btree_map() {
1146                    time.hash(state);
1147                    value.hash(state);
1148                }
1149                #[cfg(feature = "interpolation")]
1150                for (time, (value, spec)) in map.values.as_btree_map() {
1151                    time.hash(state);
1152                    value.hash(state);
1153                    spec.hash(state);
1154                }
1155            }
1156            #[cfg(feature = "curves")]
1157            AnimatedData::ColorCurve(map) => {
1158                map.len().hash(state);
1159                #[cfg(not(feature = "interpolation"))]
1160                for (time, value) in map.values.as_btree_map() {
1161                    time.hash(state);
1162                    value.hash(state);
1163                }
1164                #[cfg(feature = "interpolation")]
1165                for (time, (value, spec)) in map.values.as_btree_map() {
1166                    time.hash(state);
1167                    value.hash(state);
1168                    spec.hash(state);
1169                }
1170            }
1171        }
1172    }
1173}
1174
1175// Sample trait implementations for AnimatedData
1176impl_sample_for_animated_data!(
1177    Real, Real;
1178    Integer, Integer;
1179    Color, Color;
1180);
1181
1182impl_sample_for_animated_data!(
1183    Vector2, Vector2, "vector2";
1184    Vector3, Vector3, "vector3";
1185    Matrix3, Matrix3, "matrix3";
1186    Normal3, Normal3, "normal3";
1187    Point3, Point3, "point3";
1188    Matrix4, Matrix4, "matrix4";
1189);
1190
1191impl AnimatedData {
1192    /// Hash the animated data with shutter context for better cache coherency.
1193    ///
1194    /// Samples the animation at standardized points within the shutter range
1195    /// and hashes the interpolated values. If all samples are identical,
1196    /// only one value is hashed for efficiency.
1197    pub fn hash_with_shutter<H: Hasher>(&self, state: &mut H, shutter: &Shutter) {
1198        use smallvec::SmallVec;
1199
1200        // Sample at 5 standardized points within the shutter.
1201        const SAMPLE_POSITIONS: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
1202
1203        // Collect interpolated samples using SmallVec to avoid heap allocation.
1204        let samples: SmallVec<[Data; 5]> = SAMPLE_POSITIONS
1205            .iter()
1206            .map(|&pos| {
1207                let time = shutter.evaluate(pos);
1208                self.interpolate(time)
1209            })
1210            .collect();
1211
1212        // Check if all samples are identical.
1213        let all_same = samples.windows(2).all(|w| w[0] == w[1]);
1214
1215        // Hash the data type discriminant.
1216        std::mem::discriminant(self).hash(state);
1217
1218        if all_same {
1219            // If all samples are the same, just hash one.
1220            1usize.hash(state); // Indicate single sample.
1221            samples[0].hash(state);
1222        } else {
1223            // Hash all samples.
1224            samples.len().hash(state); // Indicate multiple samples.
1225            for sample in &samples {
1226                sample.hash(state);
1227            }
1228        }
1229    }
1230}
1231
1232// Implement AnimatedDataSystem trait for the built-in AnimatedData type.
1233impl crate::traits::AnimatedDataSystem for AnimatedData {
1234    type Data = Data;
1235
1236    fn keyframe_count(&self) -> usize {
1237        AnimatedDataOps::len(self)
1238    }
1239
1240    fn is_keyframes_empty(&self) -> bool {
1241        AnimatedDataOps::is_empty(self)
1242    }
1243
1244    fn has_animation(&self) -> bool {
1245        AnimatedDataOps::is_animated(self)
1246    }
1247
1248    fn times(&self) -> SmallVec<[Time; 10]> {
1249        AnimatedData::times(self)
1250    }
1251
1252    fn interpolate(&self, time: Time) -> Data {
1253        AnimatedData::interpolate(self, time)
1254    }
1255
1256    fn sample_at(&self, time: Time) -> Option<Data> {
1257        AnimatedData::sample_at(self, time)
1258    }
1259
1260    fn try_insert(&mut self, time: Time, value: Data) -> Result<()> {
1261        AnimatedData::try_insert(self, time, value)
1262    }
1263
1264    fn remove_at(&mut self, time: &Time) -> Option<Data> {
1265        AnimatedData::remove_at(self, time)
1266    }
1267
1268    fn discriminant(&self) -> DataType {
1269        DataTypeOps::data_type(self)
1270    }
1271
1272    fn from_single(time: Time, value: Data) -> Self {
1273        AnimatedData::from((time, value))
1274    }
1275
1276    fn variant_name(&self) -> &'static str {
1277        DataTypeOps::type_name(self)
1278    }
1279}