#[derive(Debug)]
pub struct KeyFrames<S: AnimateStateSetter> {
pub state: S,
frames: Box<[KeyFrame<S::Value>]>,
}
#[derive(Debug, Clone)]
pub struct KeyFrame<S> {
pub rate: f32,
pub state_value: S,
}
impl<S: AnimateStateSetter> KeyFrames<S> {
pub fn new(state: S, mut keyframes: Vec<KeyFrame<S::Value>>) -> Self {
assert!(!keyframes.is_empty(), "KeyFrames must have at least one frame");
keyframes.sort_by(|a, b| a.rate.total_cmp(&b.rate));
Self { state, frames: keyframes.into_boxed_slice() }
}
#[allow(clippy::type_complexity)]
pub fn into_lerp_fn_state(
self,
) -> LerpFnState<S, impl FnMut(&S::Value, &S::Value, f32) -> S::Value>
where
S::Value: Lerp,
{
let Self { state, frames } = self;
LerpFnState::new(state, move |from, to, rate| {
let idx = frames
.binary_search_by(|f| f.rate.total_cmp(&rate))
.unwrap_or_else(|idx| idx);
if idx == 0 {
let rate = if rate > 0. { rate / frames[0].rate } else { 1. };
from.lerp(&frames[0].state_value, rate)
} else if idx == frames.len() {
let pre_rate = frames[idx - 1].rate;
if pre_rate == 1. {
to.clone()
} else {
let rate = (rate - pre_rate) / (1. - pre_rate);
frames[idx - 1].state_value.lerp(to, rate)
}
} else {
let f2 = &frames[idx];
let f1 = &frames[idx - 1];
if f2.rate == f1.rate {
f2.state_value.clone()
} else {
let rate = (rate - f1.rate) / (f2.rate - f1.rate);
f1.state_value.lerp(&f2.state_value, rate)
}
}
})
}
}
#[macro_export]
macro_rules! keyframes {
(state: $state:expr, frames: [ $($f:expr),*] $(,)?) => {
$crate::animation::KeyFrames::new($state, vec![ $($f),*]).into_lerp_fn_state()
};
(state: $state:expr, frames: [ $($f:expr),* ], $l: literal% => $v:expr $(, $($rest:tt)*)?) => {
$crate::keyframes!(
state: $state,
frames: [
$($f,)*
KeyFrame { rate: $l as f32 / 100., state_value: $v }
]
$(, $($rest)*)?
)
};
(state: $state:expr, frames: [$($f:expr),*], $rate: expr => $v:expr $(, $($rest:tt)*)?) => {
$crate::keyframes!(
state: $state,
frames: [
$($f,)*
KeyFrame { rate: $rate, state_value: $v }
]
$(, $($rest)*)?
)
};
(state: $state:expr, $($rest: tt)+) => {
$crate::keyframes!(state: $state, frames: [], $($rest)*)
};
}
pub use keyframes;
use super::{AnimateStateSetter, Lerp, LerpFnState};
#[cfg(test)]
mod tests {
use super::*;
use crate::{animation::animate_state::AnimateState, reset_test_env, state::Stateful};
#[test]
fn smoke() {
reset_test_env!();
let state = Stateful::new(1.);
let mut keyframes = keyframes! {
state: state,
0.1 => 0.4,
50% => 2.5,
1. => 1.
};
let p5 = keyframes.calc_lerp_value(&0., &1., 0.05);
assert!(0. < p5 && p5 < 0.4);
let p10 = keyframes.calc_lerp_value(&0., &1., 0.1);
assert_eq!(p10, 0.4);
let p25 = keyframes.calc_lerp_value(&0., &1., 0.25);
assert!(0.4 < p25 && p25 < 2.5);
let p50 = keyframes.calc_lerp_value(&0., &1., 0.5);
assert_eq!(p50, 2.5);
let p100 = keyframes.calc_lerp_value(&0., &1., 1.);
assert_eq!(p100, 1.);
}
}