use crate::gfx::skeleton::AnimationClip;
use crate::math::vec3::{add, scale};
use super::{CompiledGraph, CompiledState, GraphCursor};
pub fn cursor_root_delta<'a>(
graph: &CompiledGraph,
before: &GraphCursor,
after: &GraphCursor,
params: &[f32],
clip_at: &impl Fn(usize) -> &'a AnimationClip,
) -> [f32; 3] {
if before.state != after.state {
return [0.0; 3];
}
let state = &graph.states[after.state];
let delta = state_root_delta(state, before.clock, after.clock, params, clip_at);
if let (Some(fade_before), Some(fade_after)) = (&before.fade, &after.fade)
&& fade_before.from_state == fade_after.from_state
{
let from = &graph.states[fade_after.from_state];
let from_delta = state_root_delta(
from,
fade_before.from_clock,
fade_after.from_clock,
params,
clip_at,
);
let progress = fade_after.progress();
return add(scale(from_delta, 1.0 - progress), scale(delta, progress));
}
delta
}
pub(crate) fn state_root_delta<'a>(
state: &CompiledState,
clock0: f32,
clock1: f32,
params: &[f32],
clip_at: &impl Fn(usize) -> &'a AnimationClip,
) -> [f32; 3] {
let weights = state.play.weights(params);
let sync = state.play.sync();
let mut delta = [0.0f32; 3];
for (member, &w) in state.play.members().iter().zip(&weights) {
if w <= 0.0 {
continue;
}
let Some(root) = clip_at(member.clip).root.as_ref() else {
continue;
};
let (t0, t1) = if sync {
(clock0 * member.duration_secs, clock1 * member.duration_secs)
} else {
(clock0, clock1)
};
let d = root.delta(t0, t1, member.duration_secs, state.looping);
delta = add(delta, scale(d, w));
}
delta
}
#[cfg(test)]
mod tests {
use super::super::{
Blend1D, ClipPlay, CompiledGraph, CompiledState, CompiledTransition, GraphCursor, StatePlay,
};
use super::*;
use crate::gfx::root_motion::{RootKey, RootTrack};
use alloc::vec;
use alloc::vec::Vec;
fn walker(duration: f32, travel: f32) -> AnimationClip {
AnimationClip {
duration,
looping: true,
tracks: Vec::new(),
morph_keys: Vec::new(),
root: Some(RootTrack {
keys: vec![
RootKey {
time: 0.0,
translation: [0.0; 3],
},
RootKey {
time: duration,
translation: [travel, 0.0, 0.0],
},
],
}),
}
}
fn in_place(duration: f32) -> AnimationClip {
AnimationClip {
duration,
looping: true,
tracks: Vec::new(),
morph_keys: Vec::new(),
root: None,
}
}
fn clip_state(name: &str, clip: usize, duration: f32) -> CompiledState {
CompiledState {
name: name.into(),
rate: 1.0,
looping: true,
play: StatePlay::Clip(ClipPlay {
clip,
duration_secs: duration,
}),
transitions: Vec::new(),
}
}
#[test]
fn clip_state_delta_tracks_the_clock() {
let clips = [walker(1.0, 2.0)];
let state = clip_state("walk", 0, 1.0);
let d = state_root_delta(&state, 0.25, 0.75, &[], &|i| &clips[i]);
assert!((d[0] - 1.0).abs() < 1e-5, "{d:?}");
let d = state_root_delta(&state, 0.75, 1.25, &[], &|i| &clips[i]);
assert!((d[0] - 1.0).abs() < 1e-5, "{d:?}");
}
#[test]
fn blend_scales_displacement_by_member_weight() {
let clips = [in_place(1.0), walker(1.0, 2.0)];
let state = CompiledState {
name: "locomotion".into(),
rate: 1.0,
looping: true,
play: StatePlay::Blend1D(Blend1D {
param: 0,
thresholds: vec![0.0, 1.0],
plays: vec![
ClipPlay {
clip: 0,
duration_secs: 1.0,
},
ClipPlay {
clip: 1,
duration_secs: 1.0,
},
],
sync: true,
}),
transitions: Vec::new(),
};
let d = state_root_delta(&state, 0.0, 0.5, &[0.5], &|i| &clips[i]);
assert!((d[0] - 0.5).abs() < 1e-5, "half of half the cycle: {d:?}");
let d = state_root_delta(&state, 0.0, 0.5, &[1.0], &|i| &clips[i]);
assert!((d[0] - 1.0).abs() < 1e-5, "{d:?}");
}
#[test]
fn cursor_delta_skips_transition_frames_and_blends_fades() {
let clips = [walker(1.0, 2.0), walker(1.0, 4.0)];
let mut graph = CompiledGraph {
params: Vec::new(),
states: vec![clip_state("a", 0, 1.0), clip_state("b", 1, 1.0)],
initial: 0,
};
graph.states[0].transitions = vec![CompiledTransition {
to: 1,
duration_secs: 0.4,
exit_time: None,
conditions: Vec::new(),
}];
let before = GraphCursor::start(&graph);
let mut cursor = before.clone();
cursor.advance(&graph, &[], 0.1);
assert_eq!(cursor.state, 1);
let d = cursor_root_delta(&graph, &before, &cursor, &[], &|i| &clips[i]);
assert_eq!(d, [0.0; 3]);
let before = cursor.clone();
cursor.advance(&graph, &[], 0.2);
let d = cursor_root_delta(&graph, &before, &cursor, &[], &|i| &clips[i]);
assert!((d[0] - 0.6).abs() < 1e-5, "{d:?}");
let mut cursor2 = cursor.clone();
cursor2.fade = None;
let before = cursor2.clone();
cursor2.advance(&graph, &[], 0.25);
let d = cursor_root_delta(&graph, &before, &cursor2, &[], &|i| &clips[i]);
assert!((d[0] - 1.0).abs() < 1e-5, "{d:?}");
}
}