use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnimationSystem {
pub animations: HashMap<String, Animation>,
pub active_animations: Vec<ActiveAnimation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Animation {
pub name: String,
pub duration: f32,
pub keyframes: Vec<Keyframe>,
pub loop_mode: LoopMode,
pub easing: EasingFunction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Keyframe {
pub time: f32,
pub value: AnimationValue,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AnimationValue {
Float(f32),
Vec2(f32, f32),
Vec3(f32, f32, f32),
Color([u8; 4]),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LoopMode {
Once,
Loop,
PingPong,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EasingFunction {
Linear,
EaseIn,
EaseOut,
EaseInOut,
Bounce,
Elastic,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActiveAnimation {
pub name: String,
pub current_time: f32,
pub playing: bool,
pub speed: f32,
}
impl AnimationSystem {
pub fn new() -> Self {
Self {
animations: HashMap::new(),
active_animations: Vec::new(),
}
}
pub fn add_animation(&mut self, animation: Animation) {
self.animations.insert(animation.name.clone(), animation);
}
pub fn play(&mut self, name: &str) {
if self.animations.contains_key(name) {
self.active_animations.push(ActiveAnimation {
name: name.to_string(),
current_time: 0.0,
playing: true,
speed: 1.0,
});
}
}
pub fn stop(&mut self, name: &str) {
self.active_animations.retain(|a| a.name != name);
}
pub fn update(&mut self, dt: f32) {
for active in &mut self.active_animations {
if !active.playing {
continue;
}
active.current_time += dt * active.speed;
if let Some(anim) = self.animations.get(&active.name) {
match anim.loop_mode {
LoopMode::Once => {
if active.current_time >= anim.duration {
active.playing = false;
}
}
LoopMode::Loop => {
if active.current_time >= anim.duration {
active.current_time = 0.0;
}
}
LoopMode::PingPong => {
if active.current_time >= anim.duration {
active.speed *= -1.0;
active.current_time = anim.duration;
} else if active.current_time <= 0.0 {
active.speed *= -1.0;
active.current_time = 0.0;
}
}
}
}
}
self.active_animations.retain(|a| a.playing);
}
pub fn get_value(&self, name: &str) -> Option<AnimationValue> {
let active = self.active_animations.iter().find(|a| a.name == name)?;
let anim = self.animations.get(name)?;
Some(self.interpolate_value(anim, active.current_time))
}
fn interpolate_value(&self, anim: &Animation, time: f32) -> AnimationValue {
if anim.keyframes.is_empty() {
return AnimationValue::Float(0.0);
}
if anim.keyframes.len() == 1 {
return anim.keyframes[0].value.clone();
}
let mut prev_idx = 0;
let mut next_idx = 0;
for (i, kf) in anim.keyframes.iter().enumerate() {
if kf.time <= time {
prev_idx = i;
}
if kf.time >= time {
next_idx = i;
break;
}
}
if prev_idx == next_idx {
return anim.keyframes[prev_idx].value.clone();
}
let prev = &anim.keyframes[prev_idx];
let next = &anim.keyframes[next_idx];
let t = (time - prev.time) / (next.time - prev.time);
let eased_t = self.apply_easing(t, anim.easing);
self.lerp_value(&prev.value, &next.value, eased_t)
}
fn lerp_value(&self, a: &AnimationValue, b: &AnimationValue, t: f32) -> AnimationValue {
match (a, b) {
(AnimationValue::Float(a), AnimationValue::Float(b)) => {
AnimationValue::Float(a + (b - a) * t)
}
(AnimationValue::Vec2(ax, ay), AnimationValue::Vec2(bx, by)) => {
AnimationValue::Vec2(ax + (bx - ax) * t, ay + (by - ay) * t)
}
(AnimationValue::Vec3(ax, ay, az), AnimationValue::Vec3(bx, by, bz)) => {
AnimationValue::Vec3(
ax + (bx - ax) * t,
ay + (by - ay) * t,
az + (bz - az) * t,
)
}
(AnimationValue::Color(a), AnimationValue::Color(b)) => AnimationValue::Color([
(a[0] as f32 + (b[0] as f32 - a[0] as f32) * t) as u8,
(a[1] as f32 + (b[1] as f32 - a[1] as f32) * t) as u8,
(a[2] as f32 + (b[2] as f32 - a[2] as f32) * t) as u8,
(a[3] as f32 + (b[3] as f32 - a[3] as f32) * t) as u8,
]),
_ => a.clone(),
}
}
fn apply_easing(&self, t: f32, easing: EasingFunction) -> f32 {
match easing {
EasingFunction::Linear => t,
EasingFunction::EaseIn => t * t,
EasingFunction::EaseOut => t * (2.0 - t),
EasingFunction::EaseInOut => {
if t < 0.5 {
2.0 * t * t
} else {
-1.0 + (4.0 - 2.0 * t) * t
}
}
EasingFunction::Bounce => {
if t < 0.5 {
8.0 * t * t * t * t
} else {
1.0 - 8.0 * (1.0 - t).powi(4)
}
}
EasingFunction::Elastic => {
if t == 0.0 || t == 1.0 {
t
} else {
-(2.0_f32.powf(10.0 * (t - 1.0)))
* ((t - 1.1) * 5.0 * std::f32::consts::PI).sin()
}
}
}
}
}
pub struct AnimationBuilder {
name: String,
duration: f32,
keyframes: Vec<Keyframe>,
loop_mode: LoopMode,
easing: EasingFunction,
}
impl AnimationBuilder {
pub fn new(name: &str, duration: f32) -> Self {
Self {
name: name.to_string(),
duration,
keyframes: Vec::new(),
loop_mode: LoopMode::Once,
easing: EasingFunction::Linear,
}
}
pub fn add_keyframe(mut self, time: f32, value: AnimationValue) -> Self {
self.keyframes.push(Keyframe { time, value });
self
}
pub fn loop_mode(mut self, mode: LoopMode) -> Self {
self.loop_mode = mode;
self
}
pub fn easing(mut self, easing: EasingFunction) -> Self {
self.easing = easing;
self
}
pub fn build(mut self) -> Animation {
self.keyframes.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
Animation {
name: self.name,
duration: self.duration,
keyframes: self.keyframes,
loop_mode: self.loop_mode,
easing: self.easing,
}
}
}
impl Default for AnimationSystem {
fn default() -> Self {
Self::new()
}
}