token_value_map/
value.rs

1use crate::*;
2use core::num::NonZeroU16;
3use std::hash::{Hash, Hasher};
4
5/// Type alias for bracket sampling return type.
6type BracketSample = (Option<(Time, Data)>, Option<(Time, Data)>);
7
8/// A value that can be either uniform or animated over time.
9///
10/// A [`Value`] contains either a single [`Data`] value that remains constant
11/// (uniform) or [`AnimatedData`] that changes over time with interpolation.
12#[derive(Clone, Debug, PartialEq, Hash)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14pub enum Value {
15    /// A constant value that does not change over time.
16    Uniform(Data),
17    /// A value that changes over time with keyframe interpolation.
18    Animated(AnimatedData),
19}
20
21impl Value {
22    /// Create a uniform value that does not change over time.
23    pub fn uniform<V: Into<Data>>(value: V) -> Self {
24        Value::Uniform(value.into())
25    }
26
27    /// Create an animated value from time-value pairs.
28    ///
29    /// All samples must have the same data type. Vector samples are padded
30    /// to match the length of the longest vector in the set.
31    pub fn animated<I, V>(samples: I) -> Result<Self>
32    where
33        I: IntoIterator<Item = (Time, V)>,
34        V: Into<Data>,
35    {
36        let mut samples_vec: Vec<(Time, Data)> =
37            samples.into_iter().map(|(t, v)| (t, v.into())).collect();
38
39        if samples_vec.is_empty() {
40            return Err(anyhow!("Cannot create animated value with no samples"));
41        }
42
43        // Get the data type from the first sample
44        let data_type = samples_vec[0].1.data_type();
45
46        // Check all samples have the same type and handle length consistency
47        let mut expected_len: Option<usize> = None;
48        for (time, value) in &mut samples_vec {
49            if value.data_type() != data_type {
50                return Err(anyhow!(
51                    "All animated samples must have the same type. Expected {:?}, found {:?} at time {}",
52                    data_type,
53                    value.data_type(),
54                    time
55                ));
56            }
57
58            // Check vector length consistency
59            if let Some(vec_len) = value.try_len() {
60                match expected_len {
61                    None => expected_len = Some(vec_len),
62                    Some(expected) => {
63                        if vec_len > expected {
64                            return Err(anyhow!(
65                                "Vector length {} exceeds expected length {} at time {}",
66                                vec_len,
67                                expected,
68                                time
69                            ));
70                        } else if vec_len < expected {
71                            // Pad to expected length
72                            value.pad_to_length(expected);
73                        }
74                    }
75                }
76            }
77        }
78
79        // Create the appropriate AnimatedData variant by extracting the
80        // specific data type
81
82        let animated_data = match data_type {
83            DataType::Boolean => {
84                let typed_samples: Vec<(Time, Boolean)> = samples_vec
85                    .into_iter()
86                    .map(|(t, data)| match data {
87                        Data::Boolean(v) => (t, v),
88                        _ => unreachable!("Type validation should have caught this"),
89                    })
90                    .collect();
91                AnimatedData::Boolean(TimeDataMap::from_iter(typed_samples))
92            }
93            DataType::Integer => {
94                let typed_samples: Vec<(Time, Integer)> = samples_vec
95                    .into_iter()
96                    .map(|(t, data)| match data {
97                        Data::Integer(v) => (t, v),
98                        _ => unreachable!("Type validation should have caught this"),
99                    })
100                    .collect();
101                AnimatedData::Integer(TimeDataMap::from_iter(typed_samples))
102            }
103            DataType::Real => {
104                let typed_samples: Vec<(Time, Real)> = samples_vec
105                    .into_iter()
106                    .map(|(t, data)| match data {
107                        Data::Real(v) => (t, v),
108                        _ => unreachable!("Type validation should have caught this"),
109                    })
110                    .collect();
111                AnimatedData::Real(TimeDataMap::from_iter(typed_samples))
112            }
113            DataType::String => {
114                let typed_samples: Vec<(Time, String)> = samples_vec
115                    .into_iter()
116                    .map(|(t, data)| match data {
117                        Data::String(v) => (t, v),
118                        _ => unreachable!("Type validation should have caught this"),
119                    })
120                    .collect();
121                AnimatedData::String(TimeDataMap::from_iter(typed_samples))
122            }
123            DataType::Color => {
124                let typed_samples: Vec<(Time, Color)> = samples_vec
125                    .into_iter()
126                    .map(|(t, data)| match data {
127                        Data::Color(v) => (t, v),
128                        _ => unreachable!("Type validation should have caught this"),
129                    })
130                    .collect();
131                AnimatedData::Color(TimeDataMap::from_iter(typed_samples))
132            }
133            #[cfg(feature = "vector2")]
134            DataType::Vector2 => {
135                let typed_samples: Vec<(Time, Vector2)> = samples_vec
136                    .into_iter()
137                    .map(|(t, data)| match data {
138                        Data::Vector2(v) => (t, v),
139                        _ => unreachable!("Type validation should have caught this"),
140                    })
141                    .collect();
142                AnimatedData::Vector2(TimeDataMap::from_iter(typed_samples))
143            }
144            #[cfg(feature = "vector3")]
145            DataType::Vector3 => {
146                let typed_samples: Vec<(Time, Vector3)> = samples_vec
147                    .into_iter()
148                    .map(|(t, data)| match data {
149                        Data::Vector3(v) => (t, v),
150                        _ => unreachable!("Type validation should have caught this"),
151                    })
152                    .collect();
153                AnimatedData::Vector3(TimeDataMap::from_iter(typed_samples))
154            }
155            #[cfg(feature = "matrix3")]
156            DataType::Matrix3 => {
157                let typed_samples: Vec<(Time, Matrix3)> = samples_vec
158                    .into_iter()
159                    .map(|(t, data)| match data {
160                        Data::Matrix3(v) => (t, v),
161                        _ => unreachable!("Type validation should have caught this"),
162                    })
163                    .collect();
164                AnimatedData::Matrix3(TimeDataMap::from_iter(typed_samples))
165            }
166            #[cfg(feature = "normal3")]
167            DataType::Normal3 => {
168                let typed_samples: Vec<(Time, Normal3)> = samples_vec
169                    .into_iter()
170                    .map(|(t, data)| match data {
171                        Data::Normal3(v) => (t, v),
172                        _ => unreachable!("Type validation should have caught this"),
173                    })
174                    .collect();
175                AnimatedData::Normal3(TimeDataMap::from_iter(typed_samples))
176            }
177            #[cfg(feature = "point3")]
178            DataType::Point3 => {
179                let typed_samples: Vec<(Time, Point3)> = samples_vec
180                    .into_iter()
181                    .map(|(t, data)| match data {
182                        Data::Point3(v) => (t, v),
183                        _ => unreachable!("Type validation should have caught this"),
184                    })
185                    .collect();
186                AnimatedData::Point3(TimeDataMap::from_iter(typed_samples))
187            }
188            #[cfg(feature = "matrix4")]
189            DataType::Matrix4 => {
190                let typed_samples: Vec<(Time, Matrix4)> = samples_vec
191                    .into_iter()
192                    .map(|(t, data)| match data {
193                        Data::Matrix4(v) => (t, v),
194                        _ => unreachable!("Type validation should have caught this"),
195                    })
196                    .collect();
197                AnimatedData::Matrix4(TimeDataMap::from_iter(typed_samples))
198            }
199            DataType::BooleanVec => {
200                let typed_samples: Vec<(Time, BooleanVec)> = samples_vec
201                    .into_iter()
202                    .map(|(t, data)| match data {
203                        Data::BooleanVec(v) => (t, v),
204                        _ => unreachable!("Type validation should have caught this"),
205                    })
206                    .collect();
207                AnimatedData::BooleanVec(TimeDataMap::from_iter(typed_samples))
208            }
209            DataType::IntegerVec => {
210                let typed_samples: Vec<(Time, IntegerVec)> = samples_vec
211                    .into_iter()
212                    .map(|(t, data)| match data {
213                        Data::IntegerVec(v) => (t, v),
214                        _ => unreachable!("Type validation should have caught this"),
215                    })
216                    .collect();
217                AnimatedData::IntegerVec(TimeDataMap::from_iter(typed_samples))
218            }
219            DataType::RealVec => {
220                let typed_samples: Vec<(Time, RealVec)> = samples_vec
221                    .into_iter()
222                    .map(|(t, data)| match data {
223                        Data::RealVec(v) => (t, v),
224                        _ => unreachable!("Type validation should have caught this"),
225                    })
226                    .collect();
227                AnimatedData::RealVec(TimeDataMap::from_iter(typed_samples))
228            }
229            DataType::ColorVec => {
230                let typed_samples: Vec<(Time, ColorVec)> = samples_vec
231                    .into_iter()
232                    .map(|(t, data)| match data {
233                        Data::ColorVec(v) => (t, v),
234                        _ => unreachable!("Type validation should have caught this"),
235                    })
236                    .collect();
237                AnimatedData::ColorVec(TimeDataMap::from_iter(typed_samples))
238            }
239            DataType::StringVec => {
240                let typed_samples: Vec<(Time, StringVec)> = samples_vec
241                    .into_iter()
242                    .map(|(t, data)| match data {
243                        Data::StringVec(v) => (t, v),
244                        _ => unreachable!("Type validation should have caught this"),
245                    })
246                    .collect();
247                AnimatedData::StringVec(TimeDataMap::from_iter(typed_samples))
248            }
249            #[cfg(all(feature = "vector2", feature = "vec_variants"))]
250            DataType::Vector2Vec => {
251                let typed_samples: Vec<(Time, Vector2Vec)> = samples_vec
252                    .into_iter()
253                    .map(|(t, data)| match data {
254                        Data::Vector2Vec(v) => (t, v),
255                        _ => unreachable!("Type validation should have caught this"),
256                    })
257                    .collect();
258                AnimatedData::Vector2Vec(TimeDataMap::from_iter(typed_samples))
259            }
260            #[cfg(all(feature = "vector3", feature = "vec_variants"))]
261            DataType::Vector3Vec => {
262                let typed_samples: Vec<(Time, Vector3Vec)> = samples_vec
263                    .into_iter()
264                    .map(|(t, data)| match data {
265                        Data::Vector3Vec(v) => (t, v),
266                        _ => unreachable!("Type validation should have caught this"),
267                    })
268                    .collect();
269                AnimatedData::Vector3Vec(TimeDataMap::from_iter(typed_samples))
270            }
271            #[cfg(all(feature = "matrix3", feature = "vec_variants"))]
272            DataType::Matrix3Vec => {
273                let typed_samples: Vec<(Time, Matrix3Vec)> = samples_vec
274                    .into_iter()
275                    .map(|(t, data)| match data {
276                        Data::Matrix3Vec(v) => (t, v),
277                        _ => unreachable!("Type validation should have caught this"),
278                    })
279                    .collect();
280                AnimatedData::Matrix3Vec(TimeDataMap::from_iter(typed_samples))
281            }
282            #[cfg(all(feature = "normal3", feature = "vec_variants"))]
283            DataType::Normal3Vec => {
284                let typed_samples: Vec<(Time, Normal3Vec)> = samples_vec
285                    .into_iter()
286                    .map(|(t, data)| match data {
287                        Data::Normal3Vec(v) => (t, v),
288                        _ => unreachable!("Type validation should have caught this"),
289                    })
290                    .collect();
291                AnimatedData::Normal3Vec(TimeDataMap::from_iter(typed_samples))
292            }
293            #[cfg(all(feature = "point3", feature = "vec_variants"))]
294            DataType::Point3Vec => {
295                let typed_samples: Vec<(Time, Point3Vec)> = samples_vec
296                    .into_iter()
297                    .map(|(t, data)| match data {
298                        Data::Point3Vec(v) => (t, v),
299                        _ => unreachable!("Type validation should have caught this"),
300                    })
301                    .collect();
302                AnimatedData::Point3Vec(TimeDataMap::from_iter(typed_samples))
303            }
304            #[cfg(all(feature = "matrix4", feature = "vec_variants"))]
305            DataType::Matrix4Vec => {
306                let typed_samples: Vec<(Time, Matrix4Vec)> = samples_vec
307                    .into_iter()
308                    .map(|(t, data)| match data {
309                        Data::Matrix4Vec(v) => (t, v),
310                        _ => unreachable!("Type validation should have caught this"),
311                    })
312                    .collect();
313                AnimatedData::Matrix4Vec(TimeDataMap::from_iter(typed_samples))
314            }
315        };
316
317        Ok(Value::Animated(animated_data))
318    }
319
320    /// Add a sample at a specific time, checking length constraints
321    pub fn add_sample<V: Into<Data>>(&mut self, time: Time, val: V) -> Result<()> {
322        let value = val.into();
323
324        match self {
325            Value::Uniform(_uniform_value) => {
326                // Switch to animated and drop/ignore the existing uniform
327                // content Create a new animated value with only
328                // the new sample
329                *self = Value::animated(vec![(time, value)])?;
330                Ok(())
331            }
332            Value::Animated(samples) => {
333                let data_type = samples.data_type();
334                if value.data_type() != data_type {
335                    return Err(anyhow!(
336                        "Type mismatch: cannot add {:?} to animated {:?}",
337                        value.data_type(),
338                        data_type
339                    ));
340                }
341
342                // Insert the value using the generic insert method
343                samples.try_insert(time, value)
344            }
345        }
346    }
347
348    /// Sample value at exact time without interpolation.
349    ///
350    /// Returns the exact value if it exists at the given time, or `None` if
351    /// no sample exists at that time for animated values.
352    pub fn sample_at(&self, time: Time) -> Option<Data> {
353        match self {
354            Value::Uniform(v) => Some(v.clone()),
355            Value::Animated(samples) => samples.sample_at(time),
356        }
357    }
358
359    /// Get the value at or before the given time
360    pub fn sample_at_or_before(&self, time: Time) -> Option<Data> {
361        match self {
362            Value::Uniform(v) => Some(v.clone()),
363            Value::Animated(_samples) => {
364                // For now, use interpolation at the exact time
365                // TODO: Implement proper at-or-before sampling in AnimatedData
366                Some(self.interpolate(time))
367            }
368        }
369    }
370
371    /// Get the value at or after the given time
372    pub fn sample_at_or_after(&self, time: Time) -> Option<Data> {
373        match self {
374            Value::Uniform(v) => Some(v.clone()),
375            Value::Animated(_samples) => {
376                // For now, use interpolation at the exact time
377                // TODO: Implement proper at-or-after sampling in AnimatedData
378                Some(self.interpolate(time))
379            }
380        }
381    }
382
383    /// Interpolate value at the given time.
384    ///
385    /// For uniform values, returns the constant value. For animated values,
386    /// interpolates between surrounding keyframes using appropriate
387    /// interpolation methods (linear, quadratic, or hermite).
388    pub fn interpolate(&self, time: Time) -> Data {
389        match self {
390            Value::Uniform(v) => v.clone(),
391            Value::Animated(samples) => samples.interpolate(time),
392        }
393    }
394
395    /// Get surrounding samples for interpolation.
396    pub fn sample_surrounding<const N: usize>(&self, time: Time) -> SmallVec<[(Time, Data); N]> {
397        let mut result = SmallVec::<[(Time, Data); N]>::new_const();
398        match self {
399            Value::Uniform(v) => result.push((time, v.clone())),
400            Value::Animated(_samples) => {
401                // TODO: Implement proper surrounding sample collection for
402                // AnimatedData For now, just return the
403                // interpolated value at the given time
404                let value = self.interpolate(time);
405                result.push((time, value));
406            }
407        }
408        result
409    }
410
411    /// Get the two samples surrounding a time for linear interpolation
412    pub fn sample_bracket(&self, time: Time) -> BracketSample {
413        match self {
414            Value::Uniform(v) => (Some((time, v.clone())), None),
415            Value::Animated(_samples) => {
416                // TODO: Implement proper bracketing for AnimatedData
417                // For now, just return the interpolated value at the given time
418                let value = self.interpolate(time);
419                (Some((time, value)), None)
420            }
421        }
422    }
423
424    /// Check if the value is animated.
425    pub fn is_animated(&self) -> bool {
426        match self {
427            Value::Uniform(_) => false,
428            Value::Animated(samples) => samples.is_animated(),
429        }
430    }
431
432    /// Get the number of time samples.
433    pub fn sample_count(&self) -> usize {
434        match self {
435            Value::Uniform(_) => 1,
436            Value::Animated(samples) => samples.len(),
437        }
438    }
439
440    /// Get all time samples.
441    pub fn times(&self) -> SmallVec<[Time; 10]> {
442        match self {
443            Value::Uniform(_) => SmallVec::<[Time; 10]>::new_const(),
444            Value::Animated(samples) => samples.times(),
445        }
446    }
447
448    /// Merge this value with another using a combiner function.
449    ///
450    /// For uniform values, applies the combiner once.
451    /// For animated values, samples both at the union of all keyframe times
452    /// and applies the combiner at each time.
453    ///
454    /// # Example
455    /// ```ignore
456    /// // Multiply two matrices
457    /// let result = matrix1.merge_with(&matrix2, |a, b| {
458    ///     match (a, b) {
459    ///         (Data::Matrix3(m1), Data::Matrix3(m2)) => {
460    ///             Data::Matrix3(Matrix3(m1.0 * m2.0))
461    ///         }
462    ///         _ => a, // fallback
463    ///     }
464    /// })?;
465    /// ```
466    pub fn merge_with<F>(&self, other: &Value, combiner: F) -> Result<Value>
467    where
468        F: Fn(&Data, &Data) -> Data,
469    {
470        match (self, other) {
471            // Both uniform: simple case
472            (Value::Uniform(a), Value::Uniform(b)) => Ok(Value::Uniform(combiner(a, b))),
473
474            // One or both animated: need to sample at union of times
475            _ => {
476                // Collect all unique times from both values
477                let mut all_times = std::collections::BTreeSet::new();
478
479                // Add times from self
480                for t in self.times() {
481                    all_times.insert(t);
482                }
483
484                // Add times from other
485                for t in other.times() {
486                    all_times.insert(t);
487                }
488
489                // If no times found (both were uniform with no times), sample at default
490                if all_times.is_empty() {
491                    let a = self.interpolate(Time::default());
492                    let b = other.interpolate(Time::default());
493                    return Ok(Value::Uniform(combiner(&a, &b)));
494                }
495
496                // Sample both values at all times and combine
497                let mut combined_samples = Vec::new();
498                for time in all_times {
499                    let a = self.interpolate(time);
500                    let b = other.interpolate(time);
501                    let combined = combiner(&a, &b);
502                    combined_samples.push((time, combined));
503                }
504
505                // If only one sample, return as uniform
506                if combined_samples.len() == 1 {
507                    Ok(Value::Uniform(combined_samples[0].1.clone()))
508                } else {
509                    // Create animated value from combined samples
510                    Value::animated(combined_samples)
511                }
512            }
513        }
514    }
515}
516
517// From implementations for Value
518impl<V: Into<Data>> From<V> for Value {
519    fn from(value: V) -> Self {
520        Value::uniform(value)
521    }
522}
523
524// Sample trait implementations for Value using macro
525#[cfg(feature = "vector2")]
526impl_sample_for_value!(Vector2, Vector2);
527#[cfg(feature = "vector3")]
528impl_sample_for_value!(Vector3, Vector3);
529impl_sample_for_value!(Color, Color);
530#[cfg(feature = "matrix3")]
531impl_sample_for_value!(Matrix3, Matrix3);
532#[cfg(feature = "normal3")]
533impl_sample_for_value!(Normal3, Normal3);
534#[cfg(feature = "point3")]
535impl_sample_for_value!(Point3, Point3);
536#[cfg(feature = "matrix4")]
537impl_sample_for_value!(Matrix4, Matrix4);
538
539// Special implementations for Real and Integer that handle type conversion
540impl Sample<Real> for Value {
541    fn sample(&self, shutter: &Shutter, samples: NonZeroU16) -> Result<Vec<(Real, SampleWeight)>> {
542        match self {
543            Value::Uniform(data) => {
544                let value = Real(data.to_f32()? as f64);
545                Ok(vec![(value, 1.0)])
546            }
547            Value::Animated(animated_data) => animated_data.sample(shutter, samples),
548        }
549    }
550}
551
552impl Sample<Integer> for Value {
553    fn sample(
554        &self,
555        shutter: &Shutter,
556        samples: NonZeroU16,
557    ) -> Result<Vec<(Integer, SampleWeight)>> {
558        match self {
559            Value::Uniform(data) => {
560                let value = Integer(data.to_i64()?);
561                Ok(vec![(value, 1.0)])
562            }
563            Value::Animated(animated_data) => animated_data.sample(shutter, samples),
564        }
565    }
566}
567
568// Manual Eq implementation for Value
569// This is safe because we handle floating point comparison deterministically
570impl Eq for Value {}
571
572impl Value {
573    /// Hash the value with shutter context for animation-aware caching.
574    ///
575    /// For animated values, this samples at standardized points within the shutter
576    /// range and hashes the interpolated values rather than raw keyframes.
577    /// This provides better cache coherency for animations with different absolute
578    /// times but identical interpolated values.
579    pub fn hash_with_shutter<H: Hasher>(&self, state: &mut H, shutter: &Shutter) {
580        match self {
581            Value::Uniform(data) => {
582                // For uniform values, just use regular hashing.
583                data.hash(state);
584            }
585            Value::Animated(animated) => {
586                // For animated values, sample at standardized points.
587                animated.hash_with_shutter(state, shutter);
588            }
589        }
590    }
591}
592
593#[cfg(test)]
594mod tests {
595    use super::*;
596
597    #[cfg(feature = "matrix3")]
598    #[test]
599    fn test_matrix_merge_uniform() {
600        // Create two uniform matrices
601        let m1 = nalgebra::Matrix3::new(2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 1.0); // Scale by 2
602        let m2 = nalgebra::Matrix3::new(1.0, 0.0, 10.0, 0.0, 1.0, 20.0, 0.0, 0.0, 1.0); // Translate by (10, 20)
603
604        let v1 = Value::uniform(m1);
605        let v2 = Value::uniform(m2);
606
607        // Merge them with multiplication
608        let result = v1
609            .merge_with(&v2, |a, b| match (a, b) {
610                (Data::Matrix3(ma), Data::Matrix3(mb)) => Data::Matrix3(ma.clone() * mb.clone()),
611                _ => a.clone(),
612            })
613            .unwrap();
614
615        // Check result is uniform
616        if let Value::Uniform(Data::Matrix3(result_matrix)) = result {
617            let expected = m1 * m2;
618            assert_eq!(result_matrix.0, expected);
619        } else {
620            panic!("Expected uniform result");
621        }
622    }
623
624    #[cfg(feature = "matrix3")]
625    #[test]
626    fn test_matrix_merge_animated() {
627        use frame_tick::Tick;
628
629        // Create first animated matrix (rotation)
630        let m1_t0 = nalgebra::Matrix3::identity();
631        let m1_t10 = nalgebra::Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0); // 90 degree rotation
632
633        let v1 = Value::animated([
634            (Tick::from_secs(0.0), m1_t0),
635            (Tick::from_secs(10.0), m1_t10),
636        ])
637        .unwrap();
638
639        // Create second animated matrix (scale)
640        let m2_t5 = nalgebra::Matrix3::new(2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 1.0);
641        let m2_t15 = nalgebra::Matrix3::new(3.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 1.0);
642
643        let v2 = Value::animated([
644            (Tick::from_secs(5.0), m2_t5),
645            (Tick::from_secs(15.0), m2_t15),
646        ])
647        .unwrap();
648
649        // Merge them
650        let result = v1
651            .merge_with(&v2, |a, b| match (a, b) {
652                (Data::Matrix3(ma), Data::Matrix3(mb)) => Data::Matrix3(ma.clone() * mb.clone()),
653                _ => a.clone(),
654            })
655            .unwrap();
656
657        // Check that result is animated with samples at t=0, 5, 10, 15
658        if let Value::Animated(animated) = result {
659            let times = animated.times();
660            assert_eq!(times.len(), 4);
661            assert!(times.contains(&Tick::from_secs(0.0)));
662            assert!(times.contains(&Tick::from_secs(5.0)));
663            assert!(times.contains(&Tick::from_secs(10.0)));
664            assert!(times.contains(&Tick::from_secs(15.0)));
665        } else {
666            panic!("Expected animated result");
667        }
668    }
669}