panim_loader/
lib.rs

1//! This is a loader for the PANIM format, which is a simple animation format for custom properties in Blender.
2//! This is meant to be used in conjunction with the [PANIM Blender Exporter](https://github.com/Synphonyte/blender-panim-exporter).
3//!
4//! Please also check there to see the details of the binary file format.
5//!
6//! The primary use case for this file type is to export more animation data on top of what can be stored in GLTF files.
7//!
8//! # Usage
9//!
10//! ```
11//! use panim_loader::PropertiesAnimation;
12//!
13//! let anims = PropertiesAnimation::from_file("assets/single_anim.panim").unwrap();
14//! let value = &anims.animations[0].get_animation_value_at_time(10.0);
15//! ```
16
17pub mod errors;
18mod parser;
19
20/// A Properties Animation file containing all the animations for all exported properties of all objects in the scene.
21#[derive(Clone, Debug, PartialEq)]
22pub struct PropertiesAnimation {
23    /// Version of the file format as a tuple of semver (major, minor, patch).
24    pub version: (u16, u16, u16),
25
26    /// The number of frames per second.
27    pub fps: f32,
28
29    /// The list of animations.
30    pub animations: Vec<Animation>,
31}
32
33/// An animation for a single property of a single object.
34#[derive(Clone, Debug, PartialEq)]
35pub struct Animation {
36    /// The number of frames per second. Same as [PropertiesAnimation::fps]
37    pub fps: f32,
38
39    /// The unique name of the object.
40    pub object_name: String,
41
42    /// The name of the property.
43    pub property_name: String,
44
45    /// The first frame of the animation.
46    pub frame_start: u32,
47
48    /// The last frame of the animation (including).
49    pub frame_end: u32,
50
51    /// The list of values for each frame starting with `frame_start` and ending with `frame_end`.
52    pub frame_values: Vec<f32>,
53}
54
55/// The result type for parsing a Properties Animation file.
56type Result = std::result::Result<PropertiesAnimation, errors::Error>;
57
58impl PropertiesAnimation {
59    /// Parses a Properties Animation file from a byte slice.
60    pub fn from_bytes(bytes: &[u8]) -> Result {
61        let props_animation = parser::props_animation(bytes)?;
62        Ok(props_animation.into())
63    }
64
65    /// Parses a Properties Animation file from a file path.
66    pub fn from_file(path: &str) -> Result {
67        let bytes = std::fs::read(path)?;
68        Self::from_bytes(&bytes)
69    }
70}
71
72impl Animation {
73    /// Returns the value of the animation at the given time (in seconds).
74    #[inline]
75    pub fn get_animation_value_at_time(&self, elapsed_time: f32) -> f32 {
76        self.get_interpolated_value_at_frame(self.fps * elapsed_time)
77    }
78
79    /// Returns the value of the animation at the given frame.
80    /// If a frame is before the start of the animation, the first value is returned.
81    /// If a frame is after the end of the animation, the last value is returned.
82    pub fn get_value_at_exact_frame(&self, frame: u32) -> f32 {
83        if frame <= self.frame_start {
84            return self.frame_values[0];
85        } else if frame >= self.frame_end {
86            return self.frame_values[self.frame_values.len() - 1];
87        }
88
89        let index = (frame - self.frame_start) as usize;
90        self.frame_values[index]
91    }
92
93    /// Returns the value of the animation at the given frame.
94    /// If a frame is before the start of the animation, the first value is returned.
95    /// If a frame is after the end of the animation, the last value is returned.
96    /// If a frame is between two frames (i.e. is not an integer), the value is linearly interpolated.
97    pub fn get_interpolated_value_at_frame(&self, frame: f32) -> f32 {
98        let lower_frame = frame.floor();
99
100        let fraction = frame - lower_frame;
101
102        let lower_frame = lower_frame as u32;
103        let upper_frame = lower_frame + 1;
104
105        let lower_value = self.get_value_at_exact_frame(lower_frame);
106        let upper_value = self.get_value_at_exact_frame(upper_frame);
107
108        lower_value + (upper_value - lower_value) * fraction
109    }
110}
111
112impl<'a> From<parser::PropsAnimation<'a>> for PropertiesAnimation {
113    fn from(props_animation: parser::PropsAnimation<'a>) -> Self {
114        PropertiesAnimation {
115            version: props_animation.semver(),
116            fps: props_animation.fps,
117            animations: props_animation
118                .animations
119                .into_iter()
120                .map(|animation| {
121                    let mut anim: Animation = animation.into();
122                    anim.fps = props_animation.fps;
123                    anim
124                })
125                .collect(),
126        }
127    }
128}
129
130impl<'a> From<parser::Animation<'a>> for Animation {
131    fn from(animation: parser::Animation<'a>) -> Self {
132        Animation {
133            fps: 0.0,
134            object_name: animation.header.object_name.to_string(),
135            property_name: animation.header.property_name.to_string(),
136            frame_start: animation.header.frame_start,
137            frame_end: animation.header.frame_end,
138            frame_values: animation.values.0,
139        }
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use crate::parser::tests::{
146        first_anim, first_anim_header, first_anim_values, single_props_anim,
147    };
148
149    #[test]
150    fn it_works() {
151        let result = crate::PropertiesAnimation::from_file("assets/single_anim.panim").unwrap();
152        assert_eq!(result, single_props_anim!().into());
153
154        let animation = &result.animations[0];
155        assert_eq!(animation.get_value_at_exact_frame(10), 0.0);
156        assert_eq!(animation.get_value_at_exact_frame(200), 1.0);
157        assert_eq!(animation.get_value_at_exact_frame(90), 0.5);
158        assert_eq!(animation.get_interpolated_value_at_frame(85.5), 0.18612504);
159
160        assert_eq!(
161            animation.get_animation_value_at_time(85.4 / 24.0),
162            0.18015012
163        );
164    }
165}