use std::collections::HashMap;
use std::sync::Arc;
use crate::data::{AnimationId, SkeletonData};
#[derive(Debug, Clone)]
pub struct AnimationStateData {
data: Arc<SkeletonData>,
default_mix: f32,
mixes: HashMap<(AnimationId, AnimationId), f32>,
}
impl AnimationStateData {
#[must_use]
pub fn new(data: Arc<SkeletonData>) -> Self {
Self {
data,
default_mix: 0.0,
mixes: HashMap::new(),
}
}
#[must_use]
pub fn data(&self) -> &Arc<SkeletonData> {
&self.data
}
#[must_use]
pub fn default_mix(&self) -> f32 {
self.default_mix
}
pub fn set_default_mix(&mut self, value: f32) {
self.default_mix = value;
}
pub fn set_mix(&mut self, from: AnimationId, to: AnimationId, duration: f32) {
self.mixes.insert((from, to), duration);
}
pub fn set_mix_by_name(
&mut self,
from_name: &str,
to_name: &str,
duration: f32,
) -> Result<(), MixAnimationNotFound> {
let from = self.find(from_name)?;
let to = self.find(to_name)?;
self.set_mix(from, to, duration);
Ok(())
}
#[must_use]
pub fn mix(&self, from: AnimationId, to: AnimationId) -> f32 {
*self.mixes.get(&(from, to)).unwrap_or(&self.default_mix)
}
pub fn clear(&mut self) {
self.mixes.clear();
self.default_mix = 0.0;
}
fn find(&self, name: &str) -> Result<AnimationId, MixAnimationNotFound> {
self.data
.animations
.iter()
.position(|a| a.name == name)
.map(|i| AnimationId(i as u16))
.ok_or_else(|| MixAnimationNotFound(name.to_string()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("no animation named `{0}`")]
pub struct MixAnimationNotFound(pub String);
#[cfg(test)]
#[allow(clippy::float_cmp)] mod tests {
use super::*;
use crate::data::Animation;
fn with_animations(names: &[&str]) -> Arc<SkeletonData> {
let mut sd = SkeletonData::default();
for name in names {
sd.animations.push(Animation::new(*name, 1.0));
}
Arc::new(sd)
}
#[test]
fn default_mix_is_zero() {
let sd = with_animations(&["a", "b"]);
let data = AnimationStateData::new(sd);
assert_eq!(data.default_mix(), 0.0);
assert_eq!(data.mix(AnimationId(0), AnimationId(1)), 0.0);
}
#[test]
fn default_mix_overridable() {
let sd = with_animations(&["a", "b"]);
let mut data = AnimationStateData::new(sd);
data.set_default_mix(0.25);
assert_eq!(data.mix(AnimationId(0), AnimationId(1)), 0.25);
}
#[test]
fn per_pair_override_beats_default() {
let sd = with_animations(&["walk", "idle", "run"]);
let mut data = AnimationStateData::new(sd);
data.set_default_mix(0.2);
data.set_mix_by_name("walk", "idle", 0.5).unwrap();
assert_eq!(data.mix(AnimationId(0), AnimationId(1)), 0.5);
assert_eq!(data.mix(AnimationId(2), AnimationId(1)), 0.2);
}
#[test]
fn set_mix_by_name_errors_on_missing() {
let sd = with_animations(&["a"]);
let mut data = AnimationStateData::new(sd);
assert_eq!(
data.set_mix_by_name("a", "nope", 0.1).unwrap_err(),
MixAnimationNotFound("nope".into())
);
}
#[test]
fn clear_resets_default_and_overrides() {
let sd = with_animations(&["a", "b"]);
let mut data = AnimationStateData::new(sd);
data.set_default_mix(0.3);
data.set_mix(AnimationId(0), AnimationId(1), 0.9);
data.clear();
assert_eq!(data.default_mix(), 0.0);
assert_eq!(data.mix(AnimationId(0), AnimationId(1)), 0.0);
}
}