1use crate::*;
2use core::num::NonZeroU16;
3use std::hash::{Hash, Hasher};
4
5type BracketSample = (Option<(Time, Data)>, Option<(Time, Data)>);
7
8#[derive(Clone, Debug, PartialEq, Hash)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14pub enum Value {
15 Uniform(Data),
17 Animated(AnimatedData),
19}
20
21impl Value {
22 pub fn uniform<V: Into<Data>>(value: V) -> Self {
24 Value::Uniform(value.into())
25 }
26
27 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 let data_type = samples_vec[0].1.data_type();
45
46 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 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 value.pad_to_length(expected);
73 }
74 }
75 }
76 }
77 }
78
79 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 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 *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 samples.try_insert(time, value)
344 }
345 }
346 }
347
348 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 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 Some(self.interpolate(time))
367 }
368 }
369 }
370
371 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 Some(self.interpolate(time))
379 }
380 }
381 }
382
383 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 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 let value = self.interpolate(time);
405 result.push((time, value));
406 }
407 }
408 result
409 }
410
411 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 let value = self.interpolate(time);
419 (Some((time, value)), None)
420 }
421 }
422 }
423
424 pub fn is_animated(&self) -> bool {
426 match self {
427 Value::Uniform(_) => false,
428 Value::Animated(samples) => samples.is_animated(),
429 }
430 }
431
432 pub fn sample_count(&self) -> usize {
434 match self {
435 Value::Uniform(_) => 1,
436 Value::Animated(samples) => samples.len(),
437 }
438 }
439
440 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 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 (Value::Uniform(a), Value::Uniform(b)) => Ok(Value::Uniform(combiner(a, b))),
473
474 _ => {
476 let mut all_times = std::collections::BTreeSet::new();
478
479 for t in self.times() {
481 all_times.insert(t);
482 }
483
484 for t in other.times() {
486 all_times.insert(t);
487 }
488
489 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 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 combined_samples.len() == 1 {
507 Ok(Value::Uniform(combined_samples[0].1.clone()))
508 } else {
509 Value::animated(combined_samples)
511 }
512 }
513 }
514 }
515}
516
517impl<V: Into<Data>> From<V> for Value {
519 fn from(value: V) -> Self {
520 Value::uniform(value)
521 }
522}
523
524#[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
539impl 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
568impl Eq for Value {}
571
572impl Value {
573 pub fn hash_with_shutter<H: Hasher>(&self, state: &mut H, shutter: &Shutter) {
580 match self {
581 Value::Uniform(data) => {
582 data.hash(state);
584 }
585 Value::Animated(animated) => {
586 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 let m1 = nalgebra::Matrix3::new(2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 1.0); let m2 = nalgebra::Matrix3::new(1.0, 0.0, 10.0, 0.0, 1.0, 20.0, 0.0, 0.0, 1.0); let v1 = Value::uniform(m1);
605 let v2 = Value::uniform(m2);
606
607 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 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 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); 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 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 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 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}