use crate::animation::AnimationClip;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct AnimationState {
pub name: String,
pub clip_index: usize,
pub looped: bool,
pub speed: f32,
}
#[derive(Clone, Debug)]
pub struct AnimationTransition {
pub from: String,
pub to: String,
pub blend_duration: f32,
pub trigger: Option<String>,
pub has_exit_time: bool,
}
#[derive(Clone, Debug)]
pub struct ActiveBlend {
pub from_clip: usize,
pub to_clip: usize,
pub from_time: f32,
pub to_time: f32,
pub elapsed: f32,
pub duration: f32,
pub to_state: String,
pub to_looped: bool,
pub to_speed: f32,
}
impl ActiveBlend {
#[inline]
pub fn alpha(&self) -> f32 {
if self.duration <= 0.0 {
1.0
} else {
(self.elapsed / self.duration).clamp(0.0, 1.0)
}
}
}
#[derive(Clone)]
pub struct AnimationStateMachine {
pub clips: Arc<[AnimationClip]>,
pub states: Vec<AnimationState>,
pub transitions: Vec<AnimationTransition>,
pub current_state: String,
pub current_time: f32,
pub active_blend: Option<ActiveBlend>,
pending_triggers: Vec<String>,
}
impl AnimationStateMachine {
pub fn new(
initial_state: &str,
clips: Arc<[AnimationClip]>,
states: Vec<AnimationState>,
transitions: Vec<AnimationTransition>,
) -> Self {
Self {
clips,
states,
transitions,
current_state: initial_state.to_string(),
current_time: 0.0,
active_blend: None,
pending_triggers: Vec::new(),
}
}
pub fn trigger(&mut self, name: &str) {
self.pending_triggers.push(name.to_string());
}
pub fn drain_triggers(&mut self) -> Vec<String> {
self.pending_triggers.drain(..).collect()
}
pub fn find_state(&self, name: &str) -> Option<&AnimationState> {
self.states.iter().find(|s| s.name == name)
}
pub fn current_clip_index(&self) -> Option<usize> {
self.find_state(&self.current_state).map(|s| s.clip_index)
}
pub fn current_clip_duration(&self) -> f32 {
self.current_clip_index()
.and_then(|i| self.clips.get(i))
.map(|c| c.duration)
.unwrap_or(1.0)
}
pub fn current_speed(&self) -> f32 {
self.find_state(&self.current_state)
.map(|s| s.speed)
.unwrap_or(1.0)
}
pub fn is_current_looped(&self) -> bool {
self.find_state(&self.current_state)
.map(|s| s.looped)
.unwrap_or(true)
}
pub fn find_transition(
&self,
from: &str,
trigger: Option<&str>,
clip_finished: bool,
) -> Option<&AnimationTransition> {
self.transitions.iter().find(|tr| {
let from_matches = tr.from == from || tr.from == "*";
if !from_matches {
return false;
}
if let Some(ref req) = tr.trigger {
if let Some(t) = trigger {
return t == req;
}
return false;
}
if tr.has_exit_time {
clip_finished
} else {
false
}
})
}
}