1use crate::player::{ObjRef, Player};
18use crate::value::{Array, Value};
19use crate::{Error, Result, invalid};
20
21#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct GenericBinding {
24 pub path: u32,
26 pub attribute: u32,
29 pub type_id: i64,
31 pub is_pptr: bool,
33}
34
35impl GenericBinding {
36 pub fn width(&self) -> usize {
38 match (self.type_id, self.attribute) {
39 (4, 1 | 3 | 4) => 3,
40 (4, 2) => 4,
41 _ => 1,
42 }
43 }
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum Storage {
49 Streamed,
51 Dense,
53 Constant,
55}
56
57#[derive(Clone, Copy, Debug, PartialEq)]
60pub struct Key {
61 pub time: f32,
63 pub value: f32,
65 pub in_slope: f32,
67 pub out_slope: f32,
69}
70
71#[derive(Clone, Debug, PartialEq)]
74pub struct Curve {
75 pub storage: Storage,
77 pub keys: Vec<Key>,
79}
80
81impl Curve {
82 pub fn sample(&self, t: f32) -> (f32, f32, f32) {
86 let k = &self.keys;
87 let (Some(first), Some(last)) = (k.first(), k.last()) else {
88 return (0.0, 0.0, 0.0);
89 };
90 if t.is_nan() {
91 return (f32::NAN, 0.0, 0.0);
92 }
93 if t <= first.time {
94 return (
95 first.value,
96 0.0,
97 if t == first.time { first.out_slope } else { 0.0 },
98 );
99 }
100 if t >= last.time {
101 return (last.value, if t == last.time { last.in_slope } else { 0.0 }, 0.0);
102 }
103 let after = k.partition_point(|key| key.time <= t);
105 let (Some(a), Some(b)) = (after.checked_sub(1).and_then(|i| k.get(i)), k.get(after)) else {
106 return (last.value, 0.0, 0.0);
107 };
108 if t == a.time {
109 return (a.value, a.in_slope, a.out_slope);
110 }
111 if !a.out_slope.is_finite() {
112 return (a.value, 0.0, 0.0);
113 }
114 let (dt, s) = (
115 f64::from(b.time - a.time),
116 f64::from(t - a.time) / f64::from(b.time - a.time),
117 );
118 let (p0, p1) = (f64::from(a.value), f64::from(b.value));
119 let (m0, m1) = (f64::from(a.out_slope) * dt, f64::from(b.in_slope) * dt);
120 let (s2, s3) = (s * s, s * s * s);
121 let value = (2.0 * s3 - 3.0 * s2 + 1.0) * p0
122 + (s3 - 2.0 * s2 + s) * m0
123 + (-2.0 * s3 + 3.0 * s2) * p1
124 + (s3 - s2) * m1;
125 let slope = ((6.0 * s2 - 6.0 * s) * p0
126 + (3.0 * s2 - 4.0 * s + 1.0) * m0
127 + (-6.0 * s2 + 6.0 * s) * p1
128 + (3.0 * s2 - 2.0 * s) * m1)
129 / dt;
130 (crate::narrow(value), crate::narrow(slope), crate::narrow(slope))
131 }
132
133 pub fn has_steps(&self) -> bool {
135 self.keys.iter().any(|k| !k.out_slope.is_finite())
136 }
137
138 pub fn is_constant(&self) -> bool {
140 self.keys.len() == 1
141 }
142}
143
144#[derive(Clone, Debug, PartialEq)]
146pub struct Track {
147 pub binding: GenericBinding,
149 pub curves: Vec<Curve>,
151}
152
153#[derive(Clone, Debug, PartialEq)]
155pub struct ClipData {
156 pub name: String,
158 pub duration: f32,
160 pub looping: bool,
162 pub tracks: Vec<Track>,
164 pub events: usize,
166 pub worst_continuity_error: f32,
169}
170
171const KEY_WORDS: usize = 5;
173
174fn int(v: &Value, k: &str) -> Result<i64> {
175 v.get(k)
176 .and_then(Value::as_i64)
177 .ok_or_else(|| invalid(format!("clip: no integer {k}")))
178}
179
180fn float(v: &Value, k: &str) -> Result<f32> {
181 v.get(k)
182 .and_then(Value::as_f32)
183 .ok_or_else(|| invalid(format!("clip: no float {k}")))
184}
185
186fn list<'a>(v: &'a Value, k: &str) -> Result<&'a [Value]> {
187 match v.get(k) {
188 Some(Value::Array(Array::Values(items))) => Ok(items),
189 Some(Value::Array(a)) if a.is_empty() => Ok(&[]),
190 _ => Err(invalid(format!("clip: no list {k}"))),
191 }
192}
193
194fn floats(v: &Value, k: &str) -> Result<Vec<f32>> {
195 match v.get(k) {
196 Some(Value::Array(Array::F32(f))) => Ok(f.clone()),
197 Some(Value::Array(a)) if a.is_empty() => Ok(Vec::new()),
198 _ => Err(invalid(format!("clip: no float list {k}"))),
199 }
200}
201
202fn read_streamed(name: &str, words: &[u32], curve_count: usize) -> Result<(Vec<Curve>, f32)> {
205 let bad = |why: String| invalid(format!("{name}: streamed clip: {why}"));
206 let most = words.len() / KEY_WORDS;
207 if curve_count > most {
208 return Err(bad(format!(
209 "{curve_count} curves, but {} bytes of frames hold at most {most} keys, and every curve has one",
210 words.len().saturating_mul(4)
211 )));
212 }
213 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect();
214 let mut entries: Vec<Vec<(f32, [f32; 4])>> = vec![Vec::new(); curve_count];
216 let mut at = 0;
217 let take = |at: &mut usize, n: usize| -> Result<&[u8]> {
218 let end = at.checked_add(n);
219 let part = end.and_then(|end| bytes.get(*at..end)).ok_or_else(|| {
220 bad(format!(
221 "frames past the data at byte {at} of {}",
222 bytes.len(),
223 at = *at
224 ))
225 })?;
226 *at = end.unwrap_or(*at);
227 Ok(part)
228 };
229 let mut last_time = f32::NEG_INFINITY;
230 let mut ended = false;
231 while at < bytes.len() {
232 let frame_at = at;
233 let time = f32::from_le_bytes(take(&mut at, 4)?.try_into().expect("4 bytes"));
234 let count = i32::from_le_bytes(take(&mut at, 4)?.try_into().expect("4 bytes"));
235 if time.is_nan() || time < last_time {
236 return Err(bad(format!(
237 "frame time {time} after {last_time} (the frame at byte {frame_at}; times must not decrease)"
238 )));
239 }
240 last_time = time;
241 let count = usize::try_from(count).map_err(|_| {
242 bad(format!(
243 "{count} keys in the frame at byte {frame_at} (at least 0)"
244 ))
245 })?;
246 for _ in 0..count {
247 let key_at = at;
248 let index = i32::from_le_bytes(take(&mut at, 4)?.try_into().expect("4 bytes"));
249 let mut c = [0.0f32; 4];
250 for x in &mut c {
251 *x = f32::from_le_bytes(take(&mut at, 4)?.try_into().expect("4 bytes"));
252 }
253 usize::try_from(index)
254 .ok()
255 .and_then(|i| entries.get_mut(i))
256 .ok_or_else(|| {
257 bad(format!(
258 "a key of curve {index} of {curve_count} at byte {key_at}"
259 ))
260 })?
261 .push((time, c));
262 }
263 if time == f32::INFINITY {
264 ended = true;
265 break;
266 }
267 }
268 if !ended || at != bytes.len() {
269 return Err(bad(format!(
270 "the frames do not end with the +inf frame at the end of the data (frames end at byte {at} of {})",
271 bytes.len()
272 )));
273 }
274 let mut worst = 0.0f32;
275 let mut out = Vec::with_capacity(curve_count);
276 for (i, list) in entries.iter().enumerate() {
277 let real: Vec<&(f32, [f32; 4])> = list
279 .iter()
280 .filter(|(t, _)| *t > f32::MIN / 2.0 && t.is_finite())
281 .collect();
282 let mut keys: Vec<Key> = Vec::new();
283 if real.is_empty() {
284 let (_, c) = list.first().ok_or_else(|| bad(format!("curve {i} has no key")))?;
285 keys.push(Key {
286 time: 0.0,
287 value: c[3],
288 in_slope: 0.0,
289 out_slope: 0.0,
290 });
291 }
292 let mut previous: Option<(f32, [f32; 4])> = None;
293 for &&(time, c) in &real {
294 let in_slope = match previous {
295 Some((t0, p)) => {
296 let dt = time - t0;
297 let end = ((p[0] * dt + p[1]) * dt + p[2]) * dt + p[3];
300 if dt >= 1e-3 && end.is_finite() && c[3].is_finite() {
301 worst = worst.max((end - c[3]).abs() / (1.0 + c[3].abs()));
302 }
303 (3.0 * p[0] * dt + 2.0 * p[1]) * dt + p[2]
304 }
305 None => 0.0,
306 };
307 keys.push(Key {
308 time,
309 value: c[3],
310 in_slope,
311 out_slope: c[2],
312 });
313 previous = Some((time, c));
314 }
315 for k in &mut keys {
317 if !k.in_slope.is_finite() {
318 k.in_slope = f32::INFINITY;
319 }
320 }
321 out.push(Curve {
322 storage: Storage::Streamed,
323 keys,
324 });
325 }
326 Ok((out, worst))
327}
328
329fn read_dense(name: &str, dense: &Value) -> Result<Vec<Curve>> {
333 let bad = |why: String| invalid(format!("{name}: dense clip: {why}"));
334 let dense_curves =
335 usize::try_from(int(dense, "m_CurveCount")?).map_err(|_| bad("a negative curve count".into()))?;
336 let frames =
337 usize::try_from(int(dense, "m_FrameCount")?).map_err(|_| bad("a negative frame count".into()))?;
338 let samples = floats(dense, "m_SampleArray")?;
339 if frames.checked_mul(dense_curves) != Some(samples.len()) {
340 return Err(bad(format!(
341 "{} samples for {frames} frames of {dense_curves} curves",
342 samples.len()
343 )));
344 }
345 if frames == 0 && dense_curves > 0 {
346 return Err(bad(format!("{dense_curves} curves of no frames")));
347 }
348 let (rate, begin) = (float(dense, "m_SampleRate")?, float(dense, "m_BeginTime")?);
349 if frames > 1 && !(rate.is_finite() && rate > 0.0) {
350 return Err(bad(format!(
351 "sample rate {rate} for {frames} frames (a positive rate is needed)"
352 )));
353 }
354 let times: Vec<f32> = (0..frames).map(|f| begin + f as f32 / rate).collect();
355 let mut curves = Vec::with_capacity(dense_curves);
356 for c in 0..dense_curves {
357 let values: Vec<f32> = samples.iter().skip(c).step_by(dense_curves).copied().collect();
358 let slope = |a: usize, b: usize| match (values.get(a..=b), times.get(a..=b)) {
359 (Some([v0, .., v1]), Some([t0, .., t1])) => (v1 - v0) / (t1 - t0),
360 _ => 0.0,
361 };
362 curves.push(Curve {
363 storage: Storage::Dense,
364 keys: values
365 .iter()
366 .zip(×)
367 .enumerate()
368 .map(|(f, (&value, &time))| Key {
369 time,
370 value,
371 in_slope: f.checked_sub(1).map_or(0.0, |p| slope(p, f)),
372 out_slope: if f < frames.saturating_sub(1) {
373 slope(f, f.saturating_add(1))
374 } else {
375 0.0
376 },
377 })
378 .collect(),
379 });
380 }
381 Ok(curves)
382}
383
384impl ClipData {
385 pub fn read(player: &Player, r: &ObjRef) -> Result<ClipData> {
387 let c = player.decode(r)?;
388 let name = c.get("m_Name").and_then(Value::as_str).unwrap_or("").to_owned();
389 if c.get("m_Legacy")
390 .is_some_and(|l| matches!(l, Value::Bool(true)) || l.as_i64().is_some_and(|v| v != 0))
391 {
392 return Err(Error::Unsupported(format!("{name}: legacy animation clip")));
393 }
394 let muscle = c
395 .get("m_MuscleClip")
396 .ok_or_else(|| invalid(format!("{name}: no m_MuscleClip")))?;
397 let data = muscle
398 .at(&["m_Clip", "data"])
399 .ok_or_else(|| invalid(format!("{name}: no m_MuscleClip.m_Clip.data")))?;
400 let streamed = data
401 .get("m_StreamedClip")
402 .ok_or_else(|| invalid(format!("{name}: no m_StreamedClip")))?;
403 let dense = data
404 .get("m_DenseClip")
405 .ok_or_else(|| invalid(format!("{name}: no m_DenseClip")))?;
406 let constant = data
407 .get("m_ConstantClip")
408 .ok_or_else(|| invalid(format!("{name}: no m_ConstantClip")))?;
409 let start = float(muscle, "m_StartTime")?;
410 let stop = float(muscle, "m_StopTime")?;
411 if start != 0.0 {
412 return Err(Error::Unsupported(format!(
413 "{name}: a clip starting at {start} s"
414 )));
415 }
416 let streamed_words = match streamed.get("data") {
417 Some(Value::Array(Array::U32(w))) => w.as_slice(),
418 Some(Value::Array(a)) if a.is_empty() => &[],
419 _ => return Err(invalid(format!("{name}: streamed clip data is not 32-bit words"))),
420 };
421 let streamed_count = usize::try_from(int(streamed, "curveCount")?)
422 .map_err(|_| invalid(format!("{name}: negative curve count")))?;
423 let (mut curves, worst) = if streamed_count == 0 && streamed_words.is_empty() {
424 (Vec::new(), 0.0)
425 } else {
426 read_streamed(&name, streamed_words, streamed_count)?
427 };
428 curves.extend(read_dense(&name, dense)?);
429 for v in floats(constant, "data")? {
430 curves.push(Curve {
431 storage: Storage::Constant,
432 keys: vec![Key {
433 time: 0.0,
434 value: v,
435 in_slope: 0.0,
436 out_slope: 0.0,
437 }],
438 });
439 }
440 let mut bindings = Vec::new();
442 let constant_bindings = c
443 .get("m_ClipBindingConstant")
444 .ok_or_else(|| invalid(format!("{name}: no m_ClipBindingConstant")))?;
445 for b in list(constant_bindings, "genericBindings")? {
446 bindings.push(GenericBinding {
447 path: u32::try_from(int(b, "path")?)
448 .map_err(|_| invalid(format!("{name}: a path hash beyond 32 bits")))?,
449 attribute: u32::try_from(int(b, "attribute")?)
450 .map_err(|_| invalid(format!("{name}: an attribute beyond 32 bits")))?,
451 type_id: int(b, "typeID")?,
452 is_pptr: int(b, "isPPtrCurve")? != 0,
453 });
454 }
455 if let Some(b) = bindings.iter().find(|b| b.is_pptr) {
456 return Err(Error::Unsupported(format!(
457 "{name}: a PPtr curve (attribute {})",
458 b.attribute
459 )));
460 }
461 let wanted: usize = bindings.iter().map(GenericBinding::width).sum();
462 if wanted != curves.len() {
463 return Err(invalid(format!(
464 "{name}: the bindings take {wanted} curves, the clip stores {}",
465 curves.len()
466 )));
467 }
468 let mut rest = curves.into_iter();
469 let tracks = bindings
470 .into_iter()
471 .map(|binding| {
472 let curves = rest.by_ref().take(binding.width()).collect();
473 Track { binding, curves }
474 })
475 .collect();
476 let looping = muscle
477 .get("m_LoopTime")
478 .is_some_and(|l| matches!(l, Value::Bool(true)) || l.as_i64().is_some_and(|v| v != 0));
479 Ok(ClipData {
480 name,
481 duration: stop - start,
482 looping,
483 tracks,
484 events: list(&c, "m_Events")?.len(),
485 worst_continuity_error: worst,
486 })
487 }
488}
489
490pub fn path_hash(path: &str) -> u32 {
493 crate::animator::name_hash(path)
494}
495
496pub fn controller_clips(player: &Player, controller: &ObjRef) -> Result<Vec<ObjRef>> {
498 let c = player.decode(controller)?;
499 let mut out = Vec::new();
500 for p in list(&c, "m_AnimationClips")? {
501 if let Some(clip) = player.resolve(&controller.file, p)? {
502 out.push(clip);
503 }
504 }
505 Ok(out)
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 fn key(time: f32, value: f32, in_slope: f32, out_slope: f32) -> Key {
513 Key {
514 time,
515 value,
516 in_slope,
517 out_slope,
518 }
519 }
520
521 fn frame(time: f32, keys: &[(i32, [f32; 4])]) -> Vec<u32> {
522 let mut words = vec![time.to_bits(), keys.len() as u32];
523 for (index, c) in keys {
524 words.push(*index as u32);
525 words.extend(c.iter().map(|x| x.to_bits()));
526 }
527 words
528 }
529
530 #[test]
533 fn a_streamed_curve_decodes_to_keys_with_slopes() {
534 let (c0, c1, c2, c3) = (0.5f32, -1.0, 3.0, 1.0);
536 let end = ((c0 * 2.0 + c1) * 2.0 + c2) * 2.0 + c3;
537 let mut words = frame(-f32::MAX, &[(0, [0.0, 0.0, 0.0, 1.0])]);
538 words.extend(frame(0.0, &[(0, [c0, c1, c2, c3])]));
539 words.extend(frame(2.0, &[(0, [0.0, 0.0, 0.5, end])]));
540 words.extend(frame(f32::INFINITY, &[]));
541 let (curves, worst) = read_streamed("t", &words, 1).unwrap();
542 assert_eq!(worst, 0.0);
543 let k = &curves[0].keys;
544 assert_eq!(k.len(), 2);
545 assert_eq!((k[0].time, k[0].value, k[0].out_slope), (0.0, 1.0, 3.0));
546 assert_eq!(
548 (k[1].time, k[1].value, k[1].in_slope, k[1].out_slope),
549 (2.0, end, 5.0, 0.5)
550 );
551 let (v, s, _) = curves[0].sample(1.0);
553 assert!((v - (0.5 - 1.0 + 3.0 + 1.0)).abs() < 1e-5, "{v}");
554 assert!((s - (1.5 - 2.0 + 3.0)).abs() < 1e-5, "{s}");
555 assert_eq!(curves[0].sample(0.0), (1.0, 0.0, 3.0));
556 assert_eq!(curves[0].sample(2.0), (end, 5.0, 0.0));
557 }
558
559 #[test]
562 fn streamed_clips_refuse_what_is_not_the_format() {
563 let mut words = frame(-f32::MAX, &[(0, [0.0, 0.0, 0.0, 4.0])]);
564 words.extend(frame(f32::INFINITY, &[]));
565 let (curves, _) = read_streamed("t", &words, 1).unwrap();
566 assert_eq!(curves[0].keys, vec![key(0.0, 4.0, 0.0, 0.0)]);
567 let refused = |words: &[u32], n: usize| read_streamed("t", words, n).unwrap_err().to_string();
568 assert!(refused(&words[..words.len() - 2], 1).contains("do not end"));
569 assert!(refused(&words, 0).contains("curve 0 of 0"));
570 let mut back = frame(1.0, &[]);
571 back.extend(frame(0.5, &[]));
572 back.extend(frame(f32::INFINITY, &[]));
573 assert!(refused(&back, 1).contains("after"));
574 }
575
576 #[test]
579 fn a_streamed_curve_count_is_bounded_by_the_data() {
580 let mut words = frame(-f32::MAX, &[(0, [0.0, 0.0, 0.0, 4.0])]);
581 words.extend(frame(f32::INFINITY, &[]));
582 for n in [2, 1 << 40, usize::MAX] {
583 let e = read_streamed("t", &words, n).unwrap_err().to_string();
584 assert!(e.contains("hold at most 1 keys"), "{e}");
585 }
586 let mut words = frame(-f32::MAX, &[(5, [0.0; 4])]);
588 words.extend(frame(f32::INFINITY, &[]));
589 let e = read_streamed("t", &words, 1).unwrap_err().to_string();
590 assert!(e.contains("curve 5 of 1 at byte 8"), "{e}");
591 let mut back = frame(1.0, &[]);
592 back.extend(frame(0.5, &[]));
593 let e = read_streamed("t", &back, 0).unwrap_err().to_string();
594 assert!(e.contains("byte 8"), "{e}");
595 }
596
597 fn dense(curves: i64, frames: i64, samples: Vec<f32>) -> Value {
598 let f = |k: &str, v: Value| (std::sync::Arc::<str>::from(k), v);
599 Value::Struct(vec![
600 f("m_CurveCount", Value::Int(curves)),
601 f("m_FrameCount", Value::Int(frames)),
602 f("m_SampleRate", Value::Float(2.0)),
603 f("m_BeginTime", Value::Float(0.0)),
604 f("m_SampleArray", Value::Array(Array::F32(samples))),
605 ])
606 }
607
608 #[test]
611 fn dense_counts_are_checked_against_the_samples() {
612 for (curves, frames) in [(i64::MAX, 3), (3, i64::MAX), (i64::MAX, i64::MAX), (1 << 40, 0)] {
613 let e = read_dense("t", &dense(curves, frames, vec![]))
614 .unwrap_err()
615 .to_string();
616 assert!(e.contains("dense clip"), "{e}");
617 }
618 let curves = read_dense("t", &dense(2, 2, vec![0.0, 10.0, 1.0, 20.0])).unwrap();
619 assert_eq!(curves.len(), 2);
620 assert_eq!(
621 curves[1].keys,
622 vec![key(0.0, 10.0, 0.0, 20.0), key(0.5, 20.0, 20.0, 0.0)]
623 );
624 assert_eq!(read_dense("t", &dense(0, 0, vec![])).unwrap(), vec![]);
625 }
626
627 #[test]
629 fn sampling_at_nan_does_not_panic() {
630 let curve = Curve {
631 storage: Storage::Constant,
632 keys: vec![key(0.0, 1.0, 0.0, 0.0), key(1.0, 2.0, 0.0, 0.0)],
633 };
634 assert!(curve.sample(f32::NAN).0.is_nan());
635 }
636
637 #[test]
641 fn path_hashes_are_the_crc32_of_the_path() {
642 assert_eq!(path_hash(""), 0);
643 assert_eq!(path_hash("scarf"), 2_702_068_705);
644 assert_eq!(path_hash("root/Lchain1/Lchain2/Lchain3"), 3_634_936_137);
645 assert_eq!(path_hash("root/Rchain1/Rchain2"), 356_844_446);
646 }
647}