use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum Interpolation {
Step,
#[default]
Linear,
CubicSpline,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Keyframe {
pub time: f32,
pub value: Vec<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnimationTrack {
pub node_index: usize,
pub property: AnimatedProperty,
pub interpolation: Interpolation,
pub keyframes: Vec<Keyframe>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AnimatedProperty {
Translation,
Rotation,
Scale,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportedAnimation {
pub name: String,
pub duration: f32,
pub tracks: Vec<AnimationTrack>,
}
impl ImportedAnimation {
pub fn track_count(&self) -> usize {
self.tracks.len()
}
pub fn total_keyframes(&self) -> usize {
self.tracks.iter().map(|t| t.keyframes.len()).sum()
}
pub fn validate(&self) -> Result<(), String> {
if self.duration < 0.0 || !self.duration.is_finite() {
return Err(format!("content_animation_invalid_duration:{}", self.duration));
}
for (i, track) in self.tracks.iter().enumerate() {
for w in track.keyframes.windows(2) {
if w[1].time < w[0].time {
return Err(format!(
"content_animation_unsorted_keyframes:track[{i}] time {} > {}",
w[0].time, w[1].time
));
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_animation() -> ImportedAnimation {
ImportedAnimation {
name: "Walk".into(),
duration: 1.0,
tracks: vec![AnimationTrack {
node_index: 0,
property: AnimatedProperty::Translation,
interpolation: Interpolation::Linear,
keyframes: vec![
Keyframe {
time: 0.0,
value: vec![0.0, 0.0, 0.0],
},
Keyframe {
time: 0.5,
value: vec![1.0, 0.0, 0.0],
},
Keyframe {
time: 1.0,
value: vec![2.0, 0.0, 0.0],
},
],
}],
}
}
#[test]
fn valid_animation() {
let anim = sample_animation();
assert!(anim.validate().is_ok());
assert_eq!(anim.track_count(), 1);
assert_eq!(anim.total_keyframes(), 3);
}
#[test]
fn negative_duration() {
let mut anim = sample_animation();
anim.duration = -1.0;
assert!(anim.validate().unwrap_err().contains("invalid_duration"));
}
#[test]
fn unsorted_keyframes() {
let mut anim = sample_animation();
anim.tracks[0].keyframes[1].time = 2.0; assert!(anim.validate().unwrap_err().contains("unsorted"));
}
#[test]
fn interpolation_default_linear() {
assert_eq!(Interpolation::default(), Interpolation::Linear);
}
}