sevenx_engine 0.2.11

Engine de jogos 2D/3D completa com suporte Android, física, áudio, partículas, tilemap, UI, eventos e sistema 3D avançado com PBR.
Documentation
// Sistema de Animação 2D/3D com Tweening
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();
        }

        // Encontra keyframes antes e depois
        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()
                }
            }
        }
    }
}

// Builder para facilitar criação de animações
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()
    }
}