pub mod errors;
mod parser;
#[derive(Clone, Debug, PartialEq)]
pub struct PropertiesAnimation {
pub version: (u16, u16, u16),
pub fps: f32,
pub animations: Vec<Animation>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Animation {
pub fps: f32,
pub object_name: String,
pub property_name: String,
pub frame_start: u32,
pub frame_end: u32,
pub frame_values: Vec<f32>,
}
type Result = std::result::Result<PropertiesAnimation, errors::Error>;
impl PropertiesAnimation {
pub fn from_bytes(bytes: &[u8]) -> Result {
let props_animation = parser::props_animation(bytes)?;
Ok(props_animation.into())
}
pub fn from_file(path: &str) -> Result {
let bytes = std::fs::read(path)?;
Self::from_bytes(&bytes)
}
}
impl Animation {
#[inline]
pub fn get_animation_value_at_time(&self, elapsed_time: f32) -> f32 {
self.get_interpolated_value_at_frame(self.fps * elapsed_time)
}
pub fn get_value_at_exact_frame(&self, frame: u32) -> f32 {
if frame <= self.frame_start {
return self.frame_values[0];
} else if frame >= self.frame_end {
return self.frame_values[self.frame_values.len() - 1];
}
let index = (frame - self.frame_start) as usize;
self.frame_values[index]
}
pub fn get_interpolated_value_at_frame(&self, frame: f32) -> f32 {
let lower_frame = frame.floor();
let fraction = frame - lower_frame;
let lower_frame = lower_frame as u32;
let upper_frame = lower_frame + 1;
let lower_value = self.get_value_at_exact_frame(lower_frame);
let upper_value = self.get_value_at_exact_frame(upper_frame);
lower_value + (upper_value - lower_value) * fraction
}
}
impl<'a> From<parser::PropsAnimation<'a>> for PropertiesAnimation {
fn from(props_animation: parser::PropsAnimation<'a>) -> Self {
PropertiesAnimation {
version: props_animation.semver(),
fps: props_animation.fps,
animations: props_animation
.animations
.into_iter()
.map(|animation| {
let mut anim: Animation = animation.into();
anim.fps = props_animation.fps;
anim
})
.collect(),
}
}
}
impl<'a> From<parser::Animation<'a>> for Animation {
fn from(animation: parser::Animation<'a>) -> Self {
Animation {
fps: 0.0,
object_name: animation.header.object_name.to_string(),
property_name: animation.header.property_name.to_string(),
frame_start: animation.header.frame_start,
frame_end: animation.header.frame_end,
frame_values: animation.values.0,
}
}
}
#[cfg(test)]
mod tests {
use crate::parser::tests::{
first_anim, first_anim_header, first_anim_values, single_props_anim,
};
#[test]
fn it_works() {
let result = crate::PropertiesAnimation::from_file("assets/single_anim.panim").unwrap();
assert_eq!(result, single_props_anim!().into());
let animation = &result.animations[0];
assert_eq!(animation.get_value_at_exact_frame(10), 0.0);
assert_eq!(animation.get_value_at_exact_frame(200), 1.0);
assert_eq!(animation.get_value_at_exact_frame(90), 0.5);
assert_eq!(animation.get_interpolated_value_at_frame(85.5), 0.18612504);
assert_eq!(
animation.get_animation_value_at_time(85.4 / 24.0),
0.18015012
);
}
}