use crate::gfx::skeleton::AnimationClip;
pub(super) struct ClipEntry {
pub clip: AnimationClip,
pub(crate) declared_weight: f32,
pub fade_in_secs: f32,
}
#[derive(Debug)]
pub(super) struct Transition {
pub(crate) source_weights: Vec<f32>,
pub(crate) target_weights: Vec<f32>,
pub(crate) start_secs: f32,
pub duration_secs: f32,
}
#[derive(Default)]
pub(super) struct FlatState {
pub(crate) current_weights: Vec<f32>,
pub transition: Option<Transition>,
}
pub(super) fn advance_weights(state: &mut FlatState, now_secs: f32) {
if let Some(tr) = &state.transition {
let finished = if tr.duration_secs <= 0.0 {
true
} else {
now_secs >= tr.start_secs + tr.duration_secs
};
if finished {
state.current_weights.clone_from(&tr.target_weights);
state.transition = None;
} else {
let progress = ((now_secs - tr.start_secs) / tr.duration_secs).clamp(0.0, 1.0);
for (slot, (src, dst)) in state
.current_weights
.iter_mut()
.zip(tr.source_weights.iter().zip(tr.target_weights.iter()))
{
*slot = src + (dst - src) * progress;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ramp(source: Vec<f32>, target: Vec<f32>, start: f32, duration: f32) -> FlatState {
FlatState {
current_weights: source.clone(),
transition: Some(Transition {
source_weights: source,
target_weights: target,
start_secs: start,
duration_secs: duration,
}),
}
}
#[test]
fn advance_weights_lerps_mid_ramp() {
let mut s = ramp(vec![0.0, 1.0], vec![1.0, 0.0], 0.0, 1.0);
advance_weights(&mut s, 0.25);
assert!((s.current_weights[0] - 0.25).abs() < 1e-6);
assert!((s.current_weights[1] - 0.75).abs() < 1e-6);
assert!(s.transition.is_some(), "ramp still in flight");
}
#[test]
fn advance_weights_snaps_and_clears_at_end() {
let mut s = ramp(vec![0.0], vec![1.0], 0.0, 1.0);
advance_weights(&mut s, 1.0);
assert_eq!(s.current_weights, vec![1.0]);
assert!(s.transition.is_none(), "finished ramp is cleared");
}
#[test]
fn advance_weights_zero_duration_snaps_immediately() {
let mut s = ramp(vec![0.3], vec![0.9], 0.0, 0.0);
advance_weights(&mut s, 0.0);
assert_eq!(s.current_weights, vec![0.9]);
assert!(s.transition.is_none());
}
#[test]
fn advance_weights_without_a_transition_is_a_noop() {
let mut s = FlatState {
current_weights: vec![0.4, 0.6],
transition: None,
};
advance_weights(&mut s, 5.0);
assert_eq!(s.current_weights, vec![0.4, 0.6]);
}
}