1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
//! Generic value type that works with any [`DataSystem`].
use crate::{Result, Shutter, Time, traits::*};
use smallvec::SmallVec;
use std::hash::{Hash, Hasher};
#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// A value that can be either uniform or animated over time.
///
/// This is the generic version of [`Value`](crate::Value) that works with any
/// [`DataSystem`]. Use this when you have a custom data type system.
///
/// For the built-in types, use [`Value`](crate::Value) which is an alias for
/// `GenericValue<Data>`.
#[derive(Clone, Debug, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(
serialize = "D: Serialize, D::Animated: Serialize",
deserialize = "D: Deserialize<'de>, D::Animated: Deserialize<'de>"
))
)]
#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
pub enum GenericValue<D: DataSystem> {
/// A constant value that does not change over time.
Uniform(D),
/// A value that changes over time with keyframe interpolation.
Animated(D::Animated),
}
impl<D: DataSystem> GenericValue<D> {
/// Creates a uniform value that does not change over time.
pub fn uniform(value: D) -> Self {
GenericValue::Uniform(value)
}
/// Creates an animated value from time-value pairs.
///
/// All samples must have the same data type. Vector samples are padded
/// to match the length of the longest vector in the set.
pub fn animated<I>(samples: I) -> Result<Self>
where
I: IntoIterator<Item = (Time, D)>,
{
let mut samples_vec: Vec<(Time, D)> = samples.into_iter().collect();
if samples_vec.is_empty() {
return Err(crate::Error::EmptySamples);
}
// Get the data type from the first sample.
let data_type = samples_vec[0].1.discriminant();
let expected_name = samples_vec[0].1.variant_name();
// Check all samples have the same type and handle length consistency.
let mut expected_len: Option<usize> = None;
for (time, value) in &mut samples_vec {
if value.discriminant() != data_type {
return Err(crate::Error::GenericTypeMismatch {
expected: expected_name,
got: value.variant_name(),
});
}
// Check vector length consistency.
if let Some(vec_len) = value.try_len() {
match expected_len {
None => expected_len = Some(vec_len),
Some(expected) => {
if vec_len > expected {
return Err(crate::Error::VectorLengthExceeded {
actual: vec_len,
expected,
time: *time,
});
} else if vec_len < expected {
value.pad_to_length(expected);
}
}
}
}
}
// Create animated data from the first sample, then insert the rest.
let (first_time, first_value) = samples_vec.remove(0);
let mut animated = D::Animated::from_single(first_time, first_value);
for (time, value) in samples_vec {
animated.try_insert(time, value)?;
}
Ok(GenericValue::Animated(animated))
}
/// Adds a sample at a specific time.
///
/// If the value is uniform, it becomes animated with the new sample.
pub fn add_sample(&mut self, time: Time, value: D) -> Result<()> {
match self {
GenericValue::Uniform(_) => {
*self = GenericValue::animated(vec![(time, value)])?;
Ok(())
}
GenericValue::Animated(samples) => {
if value.discriminant() != samples.discriminant() {
return Err(crate::Error::GenericTypeMismatch {
expected: samples.variant_name(),
got: value.variant_name(),
});
}
samples.try_insert(time, value)
}
}
}
/// Removes a sample at a specific time.
///
/// Returns the removed value if it existed. For uniform values, this is a
/// no-op and returns `None`. The last sample in an animated value cannot
/// be removed (returns `None`). Use [`remove_at_or_to_uniform`](Self::remove_at_or_to_uniform)
/// to degrade to uniform instead.
pub fn remove_at(&mut self, time: &Time) -> Option<D> {
match self {
GenericValue::Uniform(_) => None,
GenericValue::Animated(samples) => samples.remove_at(time),
}
}
/// Deprecated alias for [`remove_at`](Self::remove_at).
#[deprecated(since = "0.2.2", note = "renamed to `remove_at`")]
pub fn remove_sample(&mut self, time: &Time) -> Option<D> {
self.remove_at(time)
}
/// Remove a sample, converting to uniform if it was the last keyframe.
///
/// If the value is uniform, returns `None`. If the removed sample was
/// the last keyframe, the value degrades from [`GenericValue::Animated`]
/// to [`GenericValue::Uniform`] with that keyframe's value and returns
/// `None` (the data is preserved, not lost). Otherwise returns the
/// removed value.
pub fn remove_at_or_to_uniform(&mut self, time: &Time) -> Option<D> {
match self {
GenericValue::Uniform(_) => None,
GenericValue::Animated(samples) => {
let removed = samples.remove_at(time);
if removed.is_some() {
return removed;
}
// remove_at returned None -- either key not found or last sample.
// Check if it's the last sample at this time.
if samples.sample_at(*time).is_some() {
// It's the last sample -- degrade to uniform.
let data = samples.interpolate(Time::default());
*self = GenericValue::Uniform(data);
}
None
}
}
}
/// Samples the value at an exact time without interpolation.
///
/// Returns the exact value if it exists at the given time, or `None` if
/// no sample exists at that time for animated values.
pub fn sample_at(&self, time: Time) -> Option<D> {
match self {
GenericValue::Uniform(v) => Some(v.clone()),
GenericValue::Animated(samples) => samples.sample_at(time),
}
}
/// Interpolates the value at the given time.
///
/// For uniform values, returns the constant value. For animated values,
/// interpolates between surrounding keyframes.
pub fn interpolate(&self, time: Time) -> D {
match self {
GenericValue::Uniform(v) => v.clone(),
GenericValue::Animated(samples) => samples.interpolate(time),
}
}
/// Returns `true` if the value is animated (has multiple keyframes).
pub fn is_animated(&self) -> bool {
match self {
GenericValue::Uniform(_) => false,
GenericValue::Animated(samples) => samples.has_animation(),
}
}
/// Returns the number of time samples.
pub fn sample_count(&self) -> usize {
match self {
GenericValue::Uniform(_) => 1,
GenericValue::Animated(samples) => samples.keyframe_count(),
}
}
/// Returns all keyframe times.
pub fn times(&self) -> SmallVec<[Time; 10]> {
match self {
GenericValue::Uniform(_) => SmallVec::new_const(),
GenericValue::Animated(samples) => samples.times(),
}
}
/// Returns the data type discriminant for this value.
pub fn discriminant(&self) -> D::DataType {
match self {
GenericValue::Uniform(data) => data.discriminant(),
GenericValue::Animated(animated) => animated.discriminant(),
}
}
/// Returns a human-readable type name for this value.
pub fn variant_name(&self) -> &'static str {
match self {
GenericValue::Uniform(data) => data.variant_name(),
GenericValue::Animated(animated) => animated.variant_name(),
}
}
/// Merges this value with another using a combiner function.
///
/// For uniform values, applies the combiner once. For animated values,
/// samples both at the union of all keyframe times and applies the
/// combiner at each time.
pub fn merge_with<F>(&self, other: &GenericValue<D>, combiner: F) -> Result<GenericValue<D>>
where
F: Fn(&D, &D) -> D,
{
match (self, other) {
(GenericValue::Uniform(a), GenericValue::Uniform(b)) => {
Ok(GenericValue::Uniform(combiner(a, b)))
}
_ => {
let mut all_times = std::collections::BTreeSet::new();
for t in self.times() {
all_times.insert(t);
}
for t in other.times() {
all_times.insert(t);
}
if all_times.is_empty() {
let a = self.interpolate(Time::default());
let b = other.interpolate(Time::default());
return Ok(GenericValue::Uniform(combiner(&a, &b)));
}
let combined_samples: Vec<(Time, D)> = all_times
.into_iter()
.map(|time| {
let a = self.interpolate(time);
let b = other.interpolate(time);
(time, combiner(&a, &b))
})
.collect();
if combined_samples.len() == 1 {
Ok(GenericValue::Uniform(
// SAFETY: Guarded by combined_samples.len() == 1 check above.
combined_samples.into_iter().next().unwrap().1,
))
} else {
GenericValue::animated(combined_samples)
}
}
}
}
/// Hashes the value with shutter context for animation-aware caching.
///
/// For animated values, samples at standardized points within the shutter
/// range and hashes the interpolated values.
pub fn hash_with_shutter<H: Hasher>(&self, state: &mut H, shutter: &Shutter) {
match self {
GenericValue::Uniform(data) => {
data.hash(state);
}
GenericValue::Animated(animated) => {
// Sample at 5 standardized points within the shutter.
const SAMPLE_POSITIONS: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
let samples: SmallVec<[D; 5]> = SAMPLE_POSITIONS
.iter()
.map(|&pos| {
let time = shutter.evaluate(pos);
animated.interpolate(time)
})
.collect();
let all_same = samples.windows(2).all(|w| w[0] == w[1]);
std::mem::discriminant(self).hash(state);
if all_same {
1usize.hash(state);
samples[0].hash(state);
} else {
samples.len().hash(state);
for sample in &samples {
sample.hash(state);
}
}
}
}
}
}
impl<D: DataSystem> From<D> for GenericValue<D> {
fn from(value: D) -> Self {
GenericValue::uniform(value)
}
}
impl<D: DataSystem> Eq for GenericValue<D> {}