use blinc_core::{Color, Vec3};
pub trait Interpolate: Clone {
fn lerp(&self, other: &Self, t: f32) -> Self;
fn approx_eq(&self, other: &Self, epsilon: f32) -> bool;
}
pub trait SphericalInterpolate: Clone {
fn slerp(&self, other: &Self, t: f32) -> Self;
fn approx_eq(&self, other: &Self, epsilon: f32) -> bool;
}
impl Interpolate for f32 {
fn lerp(&self, other: &Self, t: f32) -> Self {
self + (other - self) * t
}
fn approx_eq(&self, other: &Self, epsilon: f32) -> bool {
(self - other).abs() < epsilon
}
}
impl Interpolate for Vec3 {
fn lerp(&self, other: &Self, t: f32) -> Self {
Vec3::new(
self.x + (other.x - self.x) * t,
self.y + (other.y - self.y) * t,
self.z + (other.z - self.z) * t,
)
}
fn approx_eq(&self, other: &Self, epsilon: f32) -> bool {
(self.x - other.x).abs() < epsilon
&& (self.y - other.y).abs() < epsilon
&& (self.z - other.z).abs() < epsilon
}
}
impl Interpolate for Color {
fn lerp(&self, other: &Self, t: f32) -> Self {
Color::lerp(self, other, t)
}
fn approx_eq(&self, other: &Self, epsilon: f32) -> bool {
(self.r - other.r).abs() < epsilon
&& (self.g - other.g).abs() < epsilon
&& (self.b - other.b).abs() < epsilon
&& (self.a - other.a).abs() < epsilon
}
}
use crate::easing::Easing;
#[derive(Clone, Debug)]
pub struct TypedKeyframe<T: Interpolate> {
pub time: f32,
pub value: T,
pub easing: Easing,
}
impl<T: Interpolate> TypedKeyframe<T> {
pub fn new(time: f32, value: T, easing: Easing) -> Self {
Self {
time,
value,
easing,
}
}
pub fn linear(time: f32, value: T) -> Self {
Self::new(time, value, Easing::Linear)
}
}
#[derive(Clone, Debug)]
pub struct TypedKeyframeAnimation<T: Interpolate> {
duration_ms: u32,
keyframes: Vec<TypedKeyframe<T>>,
current_time: f32,
playing: bool,
looping: bool,
}
impl<T: Interpolate> TypedKeyframeAnimation<T> {
pub fn new(duration_ms: u32) -> Self {
Self {
duration_ms,
keyframes: Vec::new(),
current_time: 0.0,
playing: false,
looping: false,
}
}
pub fn keyframe(mut self, time: f32, value: T, easing: Easing) -> Self {
self.keyframes.push(TypedKeyframe::new(time, value, easing));
self.keyframes
.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
self
}
pub fn at(mut self, time: f32, value: T) -> Self {
self.keyframes.push(TypedKeyframe::linear(time, value));
self.keyframes
.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
self
}
pub fn looping(mut self, looping: bool) -> Self {
self.looping = looping;
self
}
pub fn start(&mut self) {
self.current_time = 0.0;
self.playing = true;
}
pub fn stop(&mut self) {
self.playing = false;
}
pub fn is_playing(&self) -> bool {
self.playing
}
pub fn progress(&self) -> f32 {
if self.duration_ms == 0 {
return 1.0;
}
(self.current_time / self.duration_ms as f32).clamp(0.0, 1.0)
}
pub fn value(&self) -> Option<T> {
if self.keyframes.is_empty() {
return None;
}
let progress = self.progress();
let mut prev_kf = &self.keyframes[0];
let mut next_kf = &self.keyframes[0];
for kf in &self.keyframes {
if kf.time <= progress {
prev_kf = kf;
}
if kf.time >= progress {
next_kf = kf;
break;
}
}
if (prev_kf.time - next_kf.time).abs() < f32::EPSILON {
return Some(prev_kf.value.clone());
}
let local_progress = (progress - prev_kf.time) / (next_kf.time - prev_kf.time);
let eased = next_kf.easing.apply(local_progress);
Some(prev_kf.value.lerp(&next_kf.value, eased))
}
pub fn tick(&mut self, dt_ms: f32) {
if !self.playing {
return;
}
self.current_time += dt_ms;
if self.current_time >= self.duration_ms as f32 {
if self.looping {
self.current_time %= self.duration_ms as f32;
} else {
self.current_time = self.duration_ms as f32;
self.playing = false;
}
}
}
pub fn sample_at(&self, progress: f32) -> Option<T> {
if self.keyframes.is_empty() {
return None;
}
let progress = progress.clamp(0.0, 1.0);
let mut prev_kf = &self.keyframes[0];
let mut next_kf = &self.keyframes[0];
for kf in &self.keyframes {
if kf.time <= progress {
prev_kf = kf;
}
if kf.time >= progress {
next_kf = kf;
break;
}
}
if (prev_kf.time - next_kf.time).abs() < f32::EPSILON {
return Some(prev_kf.value.clone());
}
let local_progress = (progress - prev_kf.time) / (next_kf.time - prev_kf.time);
let eased = next_kf.easing.apply(local_progress);
Some(prev_kf.value.lerp(&next_kf.value, eased))
}
}
pub type FloatAnimation = TypedKeyframeAnimation<f32>;
pub type Vec3Animation = TypedKeyframeAnimation<Vec3>;
pub type ColorAnimation = TypedKeyframeAnimation<Color>;
pub type FloatKeyframe = TypedKeyframe<f32>;
pub type Vec3Keyframe = TypedKeyframe<Vec3>;
pub type ColorKeyframe = TypedKeyframe<Color>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_float_interpolation() {
assert!((0.0_f32.lerp(&1.0, 0.5) - 0.5).abs() < 1e-6);
assert!((10.0_f32.lerp(&20.0, 0.25) - 12.5).abs() < 1e-6);
}
#[test]
fn test_vec3_interpolation() {
let a = Vec3::new(0.0, 0.0, 0.0);
let b = Vec3::new(10.0, 20.0, 30.0);
let mid = a.lerp(&b, 0.5);
assert!((mid.x - 5.0).abs() < 1e-6);
assert!((mid.y - 10.0).abs() < 1e-6);
assert!((mid.z - 15.0).abs() < 1e-6);
}
#[test]
fn test_typed_keyframe_animation() {
let mut anim = Vec3Animation::new(1000)
.at(0.0, Vec3::new(0.0, 0.0, 0.0))
.at(1.0, Vec3::new(100.0, 0.0, 0.0));
anim.start();
let v = anim.value().unwrap();
assert!((v.x - 0.0).abs() < 1e-4);
anim.tick(500.0);
let v = anim.value().unwrap();
assert!((v.x - 50.0).abs() < 1e-4);
anim.tick(500.0);
let v = anim.value().unwrap();
assert!((v.x - 100.0).abs() < 1e-4);
}
}