use concinnity_core::math::vec3::{add, scale};
use super::flat::ClipEntry;
pub(super) fn flat_root_delta(clips: &[ClipEntry], weights: &[f32], t0: f32, t1: f32) -> [f32; 3] {
match clips {
[] => [0.0; 3],
[single] => single
.clip
.root
.as_ref()
.map(|root| root.delta(t0, t1, single.clip.duration, single.clip.looping))
.unwrap_or([0.0; 3]),
many => {
let total: f32 = weights.iter().map(|w| w.max(0.0)).sum();
if total <= 1e-6 {
return [0.0; 3];
}
let mut out = [0.0; 3];
for (entry, &w) in many.iter().zip(weights) {
if w <= 0.0 {
continue;
}
if let Some(root) = &entry.clip.root {
let d = root.delta(t0, t1, entry.clip.duration, entry.clip.looping);
out = add(out, scale(d, w / total));
}
}
out
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gfx::root_motion::{RootKey, RootTrack};
use crate::gfx::skeleton::AnimationClip;
fn moving_clip(duration: f32, looping: bool, per: f32) -> ClipEntry {
ClipEntry {
clip: AnimationClip {
morph_keys: Vec::new(),
duration,
looping,
tracks: Vec::new(),
root: Some(RootTrack {
keys: vec![
RootKey {
time: 0.0,
translation: [0.0, 0.0, 0.0],
},
RootKey {
time: duration,
translation: [per, 0.0, 0.0],
},
],
}),
},
declared_weight: 1.0,
fade_in_secs: 0.0,
}
}
fn rootless_clip(duration: f32) -> ClipEntry {
ClipEntry {
clip: AnimationClip {
morph_keys: Vec::new(),
duration,
looping: false,
tracks: Vec::new(),
root: None,
},
declared_weight: 1.0,
fade_in_secs: 0.0,
}
}
#[test]
fn empty_bucket_has_no_displacement() {
assert_eq!(flat_root_delta(&[], &[], 0.0, 1.0), [0.0; 3]);
}
#[test]
fn single_clip_reports_its_root_delta() {
let clips = [moving_clip(1.0, false, 4.0)];
let d = flat_root_delta(&clips, &[0.0], 0.0, 0.5);
assert!((d[0] - 2.0).abs() < 1e-5, "{d:?}");
assert_eq!([d[1], d[2]], [0.0, 0.0]);
}
#[test]
fn single_clip_without_a_root_track_is_silent() {
let clips = [rootless_clip(1.0)];
assert_eq!(flat_root_delta(&clips, &[1.0], 0.0, 1.0), [0.0; 3]);
}
#[test]
fn many_clips_blend_by_normalized_weight() {
let clips = [moving_clip(1.0, false, 4.0), moving_clip(1.0, false, 4.0)];
let d = flat_root_delta(&clips, &[1.0, 3.0], 0.0, 1.0);
assert!((d[0] - 4.0).abs() < 1e-5, "{d:?}");
}
#[test]
fn many_clips_skip_zero_weight_members() {
let clips = [moving_clip(1.0, false, 99.0), moving_clip(1.0, false, 6.0)];
let d = flat_root_delta(&clips, &[0.0, 2.0], 0.0, 1.0);
assert!((d[0] - 6.0).abs() < 1e-5, "{d:?}");
}
#[test]
fn many_clips_with_zero_total_weight_are_silent() {
let clips = [moving_clip(1.0, false, 4.0), moving_clip(1.0, false, 4.0)];
assert_eq!(flat_root_delta(&clips, &[0.0, 0.0], 0.0, 1.0), [0.0; 3]);
}
}