From 85b199841284ba9042a39b5547341bd3b6db0036 Mon Sep 17 00:00:00 2001 From: Mateo Carreras Date: Mon, 21 Feb 2022 20:28:10 -0800 Subject: [PATCH 1/3] generic key frame values --- graphics-core/src/animated_space.rs | 6 +- graphics-core/src/animation.rs | 62 ++++++++++++------- .../src/animation/key_frame_value.rs | 47 ++++++++++++++ .../src/animation/key_frame_value/tests.rs | 11 ++++ graphics-core/src/animation/tests.rs | 26 ++------ 5 files changed, 108 insertions(+), 44 deletions(-) create mode 100644 graphics-core/src/animation/key_frame_value.rs create mode 100644 graphics-core/src/animation/key_frame_value/tests.rs diff --git a/graphics-core/src/animated_space.rs b/graphics-core/src/animated_space.rs index c8c0227..f561ae1 100644 --- a/graphics-core/src/animated_space.rs +++ b/graphics-core/src/animated_space.rs @@ -1,7 +1,7 @@ #[cfg(test)] pub mod tests; -use crate::animation::AnimatedValue; +use crate::animation::{AnimatedFloat, AnimatedValue}; use crate::space::Point; /// An alias for `AnimatedPoint` because it can be more semantically correct @@ -11,7 +11,7 @@ pub type AnimatedVector = AnimatedPoint; /// A basic point in 3D space that has animated values. #[derive(Debug, Clone)] pub struct AnimatedPoint { - values: [AnimatedValue; 3] + values: [AnimatedFloat; 3] } #[derive(Debug, Clone)] @@ -22,7 +22,7 @@ pub struct AnimatedTriangle { impl AnimatedPoint { /// Create a new point from 3 already constructed animated values. - pub fn new(values: [AnimatedValue; 3]) -> Self { + pub fn new(values: [AnimatedFloat; 3]) -> Self { Self { values } } diff --git a/graphics-core/src/animation.rs b/graphics-core/src/animation.rs index 50a2229..e5c9b63 100644 --- a/graphics-core/src/animation.rs +++ b/graphics-core/src/animation.rs @@ -1,22 +1,31 @@ #[cfg(test)] mod tests; +mod key_frame_value; + +pub use key_frame_value::*; use std::cmp::Ordering; /// A simple value mapped to a frame/time in the scene. The time stored is an /// integer because it should be mapped to a frame number. Frame numbers can /// be negative. +/// +/// The type parameter `T` is the type of the value. Typically this is a +/// numeric type, but it can be any type. #[derive(Clone, Debug, Copy)] -pub struct KeyFrame { +pub struct KeyFrame { /// The frame number that this node on the timeline. pub time: i64, /// The value at said KeyFrame - pub value: f64 + pub value: T } -impl KeyFrame { +pub type IntKeyFrame = KeyFrame; +pub type FloatKeyFrame = KeyFrame; + +impl KeyFrame { /// Construct a new key frame at the specified time and place. - pub fn new(time: i64, value: f64) -> Self { + pub fn new(time: i64, value: T) -> Self { Self { time, value } } @@ -35,21 +44,21 @@ impl KeyFrame { } } -impl Eq for KeyFrame {} +impl Eq for KeyFrame {} -impl PartialEq for KeyFrame { +impl PartialEq for KeyFrame { fn eq(&self, other: &Self) -> bool { self.time == other.time } } -impl PartialOrd for KeyFrame { +impl PartialOrd for KeyFrame { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for KeyFrame { +impl Ord for KeyFrame { fn cmp(&self, other: &Self) -> Ordering { self.time.cmp(&other.time) } @@ -57,9 +66,9 @@ impl Ord for KeyFrame { /// A value that is animated by keyframes. #[derive(Clone, Debug)] -pub struct AnimatedValue { +pub struct AnimatedValue { /// Essentially a map of time to value. - frames: Vec, + frames: Vec>, /// Equation that determines the intermediate value between to frames. /// The input is the percentage between the two frames, and is always /// between 0 and 1. The output should be also be between 0 and 1, and is @@ -67,6 +76,9 @@ pub struct AnimatedValue { equation: fn(f64) -> f64 } +pub type AnimatedInt = AnimatedValue; +pub type AnimatedFloat = AnimatedValue; + /// Just returns the input, because the physical percentage through should /// match the time percentage through. pub fn linear_animation_equation(percentage: f64) -> f64 { @@ -94,9 +106,9 @@ pub fn ease_in_out_animation_equation(percentage: f64) -> f64 { } } -impl AnimatedValue { +impl AnimatedValue { /// Just a constant value that will not change with time. - pub fn constant(value: f64) -> AnimatedValue { + pub fn constant(value: T) -> AnimatedValue { AnimatedValue { frames: vec![KeyFrame::new(0, value)], equation: linear_animation_equation @@ -104,7 +116,7 @@ impl AnimatedValue { } /// Linearly animated value. Frames should be sorted by time. - pub fn linear(mut frames: Vec) -> AnimatedValue { + pub fn linear(mut frames: Vec>) -> AnimatedValue { frames.sort_unstable(); AnimatedValue { frames, @@ -114,7 +126,7 @@ impl AnimatedValue { /// An animated value that is eased in and out. Frames should be sorted by /// time. - pub fn ease_in_out(mut frames: Vec) -> AnimatedValue { + pub fn ease_in_out(mut frames: Vec>) -> AnimatedValue { frames.sort_unstable(); AnimatedValue { frames, @@ -122,28 +134,32 @@ impl AnimatedValue { } } - pub fn insert_frame(&mut self, time: i64, value: f64) { + pub fn insert_frame(&mut self, time: i64, value: T) { self.frames.push(KeyFrame::new(time, value)); self.frames.sort_unstable(); } /// Returns the value at the given time. - pub fn get_value(&self, time: f64) -> f64 { + /// + /// # Panics + /// This function **will panic** if there are no frames in the animation. + pub fn get_value(&self, time: f64) -> T { // Check for not having any frames. if self.frames.is_empty() { - return 0.0; + panic!("No frames in AnimatedValue"); } // Check for only having one frame or time is before the first frame. if self.frames.len() == 1 || time < self.frames[0].timef() { - return self.frames[0].value; + return self.frames[0].value.clone(); } // Check for being after the last frame. if time > self.frames[self.frames.len() - 1].timef() { - return self.frames[self.frames.len() - 1].value; + return self.frames[self.frames.len() - 1].value.clone(); } + // TODO: Just use unwrap if unreachable is used // Find the two frames that are before and after the time. let (before, after) = match self.find_frames_before_and_after(time) { Some(frames) => frames, @@ -152,7 +168,7 @@ impl AnimatedValue { }; if before == after { // If the two frames are the same, return the value of the frame. - return self.frames[before].value; + return self.frames[before].value.clone(); } let before = &self.frames[before]; @@ -161,7 +177,11 @@ impl AnimatedValue { let frame_time_difference = (after.time - before.time) as f64; let time_since_before = time - before.time as f64; - (self.equation)(time_since_before / frame_time_difference) + // Calculate the percentage through the animation. + let percentage = (self.equation)(time_since_before / frame_time_difference); + + // Interpolate between the two frames. + before.value.interpolate(&after.value, percentage) } /// Finds the indices of the two frames that are before and after the time. diff --git a/graphics-core/src/animation/key_frame_value.rs b/graphics-core/src/animation/key_frame_value.rs new file mode 100644 index 0000000..e3d369e --- /dev/null +++ b/graphics-core/src/animation/key_frame_value.rs @@ -0,0 +1,47 @@ +#[cfg(test)] +mod tests; + +use std::fmt::Debug; + +/// A value that can be animated. +pub trait KeyFrameValue: Debug + Clone { + fn interpolate(&self, other: &Self, t: f64) -> Self; +} + +macro_rules! impl_key_frame_value { + ($t:ty) => { + impl KeyFrameValue for $t { + fn interpolate(&self, other: &Self, t: f64) -> Self { + let result = (*self as f64) * (1.0 - t) + (*other as f64) * t; + + result as $t + } + } + }; +} + +macro_rules! impl_key_frame_value_int { + ($t:ty) => { + impl KeyFrameValue for $t { + fn interpolate(&self, other: &Self, t: f64) -> Self { + let result = (*self as f64) * (1.0 - t) + (*other as f64) * t; + + result.round() as $t + } + } + }; +} + +impl_key_frame_value!(f32); +impl_key_frame_value!(f64); +impl_key_frame_value_int!(i8); +impl_key_frame_value_int!(i16); +impl_key_frame_value_int!(i32); +impl_key_frame_value_int!(i64); +impl_key_frame_value_int!(i128); +impl_key_frame_value_int!(u8); +impl_key_frame_value_int!(u16); +impl_key_frame_value_int!(u32); +impl_key_frame_value_int!(u64); +impl_key_frame_value_int!(u128); +impl_key_frame_value_int!(usize); \ No newline at end of file diff --git a/graphics-core/src/animation/key_frame_value/tests.rs b/graphics-core/src/animation/key_frame_value/tests.rs new file mode 100644 index 0000000..8f41832 --- /dev/null +++ b/graphics-core/src/animation/key_frame_value/tests.rs @@ -0,0 +1,11 @@ +use super::*; + +#[test] +fn test_integer_interpolation() { + assert_eq!(1_i32.interpolate(&2_i32, 0.5), 2_i32); +} + +#[test] +fn test_float_interpolation() { + assert_eq!(1_f32.interpolate(&2_f32, 0.5), 1.5_f32); +} diff --git a/graphics-core/src/animation/tests.rs b/graphics-core/src/animation/tests.rs index 530f642..ed67642 100644 --- a/graphics-core/src/animation/tests.rs +++ b/graphics-core/src/animation/tests.rs @@ -8,23 +8,9 @@ fn key_frame_timef_should_be_straight_conversion() { assert_eq!(key_frame.timef(), 5_f64); } -#[test] -fn animated_value_with_no_frames() { - let anim_val = AnimatedValue::linear(Vec::new()); - - let test_results = vec![20.14, 0.0, PI, -PI] - .iter() - .map(|val| anim_val.get_value(*val).round() as i64) - .collect::>(); - - for tr in test_results { - assert_eq!(0, tr); - } -} - #[test] fn constant_animated_value() { - let anim_val = AnimatedValue::constant(10.0); + let anim_val: AnimatedFloat = AnimatedValue::constant(10.0); let test_results = vec![20.14, 0.0, PI, -PI] .iter() @@ -57,7 +43,7 @@ fn linear_animated_value() { #[test] fn find_frames_ba_when_zero_frames_should_be_none() { - let anim_val = AnimatedValue::linear(Vec::new()); + let anim_val: AnimatedFloat = AnimatedValue::linear(Vec::new()); let result = anim_val.find_frames_before_and_after(0.0); @@ -128,20 +114,20 @@ fn find_frames_ba_when_several_frames() { #[test] fn test_is_constant_function() { - let no_keyframes = AnimatedValue { + let no_keyframes: AnimatedFloat = AnimatedValue { frames: vec![], equation: linear_animation_equation }; - let one_keyframe = AnimatedValue { + let one_keyframe: AnimatedFloat = AnimatedValue { frames: vec![KeyFrame::new(5, 5.0)], equation: linear_animation_equation }; - let two_keyframes_different_value = AnimatedValue { + let two_keyframes_different_value: AnimatedFloat = AnimatedValue { frames: vec![KeyFrame::new(0, 1.0), KeyFrame::new(24, 40.0)], equation: linear_animation_equation }; // Should still not be considered a constant - let two_keyframes_same_value = AnimatedValue { + let two_keyframes_same_value: AnimatedFloat = AnimatedValue { frames: vec![KeyFrame::new(0, 1.0), KeyFrame::new(24, 1.0)], equation: linear_animation_equation }; From 040fc60ef1b38b1800d0dcb486929bb5ea2b8ca5 Mon Sep 17 00:00:00 2001 From: Mateo Carreras Date: Mon, 21 Feb 2022 20:29:39 -0800 Subject: [PATCH 2/3] stats --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53f14d4..a85372c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![gpl 3 badge](https://img.shields.io/badge/license-GPL%203.0-blue) ![repo size](https://img.shields.io/github/repo-size/PokeyOne/yapre) -![lines of code](https://img.shields.io/badge/lines%20of%20rust-2114-informational) +![lines of code](https://img.shields.io/badge/lines%20of%20rust-2286-informational) I think this is the 5th time (?) that I have started working on a ray-tracing rendering engine. Hopefully this time with more follow through as Rust makes From 01384e4056c725229e3ba69fe19a0dce25e240e6 Mon Sep 17 00:00:00 2001 From: Mateo Carreras Date: Mon, 21 Feb 2022 20:43:49 -0800 Subject: [PATCH 3/3] documentation of KeyFrameValue --- .../src/animation/key_frame_value.rs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/graphics-core/src/animation/key_frame_value.rs b/graphics-core/src/animation/key_frame_value.rs index e3d369e..ac808db 100644 --- a/graphics-core/src/animation/key_frame_value.rs +++ b/graphics-core/src/animation/key_frame_value.rs @@ -4,7 +4,58 @@ mod tests; use std::fmt::Debug; /// A value that can be animated. +/// +/// Essentially allows for the definition of a interpolation formula between +/// two values. This is already implemented for +/// - `f32` +/// - `f64` +/// - `i8` +/// - `i16` +/// - `i32` +/// - `i64` +/// - `i128` +/// - `u8` +/// - `u16` +/// - `u32` +/// - `u64` +/// - `u128` +/// - `usize` +/// +/// The general formula for numbers would be: +/// ```text +/// a * (1.0 - t) + b * t +/// ``` +/// Where `a` and `b` are the start and end values, and `t` is the interpolation +/// factor (i.e. a number between 0 and 1). +/// +/// It also defines the other traits that are required for the animation system. pub trait KeyFrameValue: Debug + Clone { + /// Get a mixture of this value and the supplied value based on the + /// interpolation factor. + /// + /// The interpolation factor is usually a number between 0 and 1, but this + /// function should not assume that. The implementations for the primitive + /// types, for example, just does the math without any clamping. + /// + /// # Parameters + /// + /// - `other`: The value to mix with. + /// - `t`: The interpolation factor. + /// + /// # Returns + /// + /// The mixed value. + /// + /// # Examples + /// + /// ``` + /// use yapre_graphics_core::animation::KeyFrameValue; + /// + /// let a: f64 = 1.0; + /// let b: f64 = 5.0; + /// let t: f64 = 0.5; + /// assert_eq!(a.interpolate(&b, t), 3.0); + /// ``` fn interpolate(&self, other: &Self, t: f64) -> Self; }