From 18a12c2afe514eba97378b5af5aefcc72a74a4b6 Mon Sep 17 00:00:00 2001 From: Valaphee <32491319+valaphee@users.noreply.github.com> Date: Wed, 24 Jan 2024 00:32:59 +0100 Subject: [PATCH 1/3] Abstract away animation interpolation and reduce code redundancy, solve todo for cubic_spline_interpolation --- crates/bevy_animation/src/lib.rs | 267 +++++++++++++------------------ 1 file changed, 111 insertions(+), 156 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 02f8139a697d5..d15186f752898 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -2,7 +2,7 @@ #![warn(missing_docs)] -use std::ops::Deref; +use std::ops::{Add, Deref, Mul}; use std::time::Duration; use bevy_app::{App, Plugin, PostUpdate}; @@ -126,7 +126,7 @@ impl VariableCurve { } /// Interpolation method to use between keyframes. -#[derive(Reflect, Clone, Debug)] +#[derive(Reflect, Copy, Clone, Debug)] pub enum Interpolation { /// Linear interpolation between the two closest keyframes. Linear, @@ -137,6 +137,66 @@ pub enum Interpolation { CubicSpline, } +impl Interpolation { + fn interpolate( + self, + lerp: f32, + duration: f32, + keyframes: &[T], + stride: usize, + ) -> T { + match self { + Interpolation::Step => keyframes[stride * 0], + Interpolation::Linear => { + let value_start = keyframes[stride * 0]; + let value_end = keyframes[stride * 1]; + value_start._lerp(value_end, lerp) + } + Interpolation::CubicSpline => { + println!("b"); + + let value_start = keyframes[stride * 1]; + let tangent_out_start = keyframes[stride * 2]; + let tangent_in_end = keyframes[stride * 3]; + let value_end = keyframes[stride * 4]; + (value_start * (2.0 * lerp.powi(3) - 3.0 * lerp.powi(2) + 1.0)) + + (tangent_out_start * (duration * (lerp.powi(3) - 2.0 * lerp.powi(2) + lerp))) + + (value_end * (-2.0 * lerp.powi(3) + 3.0 * lerp.powi(2))) + + (tangent_in_end * (duration * (lerp.powi(3) - lerp.powi(2)))) + } + } + } + + fn step_size(self) -> usize { + match self { + Interpolation::Step | Interpolation::Linear => 1, + Interpolation::CubicSpline => 3, + } + } +} + +trait Interpolatable: Sized + Copy + Mul + Add { + fn _lerp(self, rhs: Self, t: f32) -> Self; +} + +impl Interpolatable for Vec3 { + fn _lerp(self, rhs: Self, t: f32) -> Self { + self.lerp(rhs, t) + } +} + +impl Interpolatable for f32 { + fn _lerp(self, rhs: Self, t: f32) -> Self { + self.lerp(rhs, t) + } +} + +impl Interpolatable for Quat { + fn _lerp(self, rhs: Self, t: f32) -> Self { + self.slerp(rhs, t) + } +} + /// Path to an entity, with [`Name`]s. Each entity in a path must have a name. #[derive(Reflect, Clone, Debug, Hash, PartialEq, Eq, Default)] pub struct EntityPath { @@ -648,42 +708,6 @@ fn run_animation_player( } } -/// Update `weights` based on weights in `keyframe` with a linear interpolation -/// on `key_lerp`. -fn lerp_morph_weights(weights: &mut [f32], keyframe: impl Iterator, key_lerp: f32) { - let zipped = weights.iter_mut().zip(keyframe); - for (morph_weight, keyframe) in zipped { - *morph_weight = morph_weight.lerp(keyframe, key_lerp); - } -} - -/// Extract a keyframe from a list of keyframes by index. -/// -/// # Panics -/// -/// When `key_index * target_count` is larger than `keyframes` -/// -/// This happens when `keyframes` is not formatted as described in -/// [`Keyframes::Weights`]. A possible cause is [`AnimationClip`] not being -/// meant to be used for the [`MorphWeights`] of the entity it's being applied to. -fn get_keyframe(target_count: usize, keyframes: &[f32], key_index: usize) -> &[f32] { - let start = target_count * key_index; - let end = target_count * (key_index + 1); - &keyframes[start..end] -} - -// Helper macro for cubic spline interpolation -// it needs to work on `f32`, `Vec3` and `Quat` -// TODO: replace by a function if the proper trait bounds can be figured out -macro_rules! cubic_spline_interpolation { - ($value_start: expr, $tangent_out_start: expr, $tangent_in_end: expr, $value_end: expr, $lerp: expr, $step_duration: expr,) => { - $value_start * (2.0 * $lerp.powi(3) - 3.0 * $lerp.powi(2) + 1.0) - + $tangent_out_start * ($step_duration) * ($lerp.powi(3) - 2.0 * $lerp.powi(2) + $lerp) - + $value_end * (-2.0 * $lerp.powi(3) + 3.0 * $lerp.powi(2)) - + $tangent_in_end * ($step_duration) * ($lerp.powi(3) - $lerp.powi(2)) - }; -} - #[allow(clippy::too_many_arguments)] fn apply_animation( weight: f32, @@ -760,11 +784,13 @@ fn apply_animation( Keyframes::Weights(keyframes) => { if let Some(morphs) = &mut morphs { let target_count = morphs.weights().len(); - lerp_morph_weights( - morphs.weights_mut(), - get_keyframe(target_count, keyframes, 0).iter().copied(), - weight, - ); + for (x, y) in morphs + .weights_mut() + .iter_mut() + .zip(&keyframes[..target_count]) + { + *x = x.lerp(*y, weight) + } } } } @@ -808,128 +834,57 @@ fn apply_keyframe( transform: &mut Mut, morphs: &mut Option>, ) { - match (&curve.interpolation, &curve.keyframes) { - (Interpolation::Step, Keyframes::Rotation(keyframes)) => { - transform.rotation = transform.rotation.slerp(keyframes[step_start], weight); - } - (Interpolation::Linear, Keyframes::Rotation(keyframes)) => { - let rot_start = keyframes[step_start]; - let mut rot_end = keyframes[step_start + 1]; - // Choose the smallest angle for the rotation - if rot_end.dot(rot_start) < 0.0 { - rot_end = -rot_end; - } - // Rotations are using a spherical linear interpolation - let rot = rot_start.normalize().slerp(rot_end.normalize(), lerp); - transform.rotation = transform.rotation.slerp(rot, weight); - } - (Interpolation::CubicSpline, Keyframes::Rotation(keyframes)) => { - let value_start = keyframes[step_start * 3 + 1]; - let tangent_out_start = keyframes[step_start * 3 + 2]; - let tangent_in_end = keyframes[(step_start + 1) * 3]; - let value_end = keyframes[(step_start + 1) * 3 + 1]; - let result = cubic_spline_interpolation!( - value_start, - tangent_out_start, - tangent_in_end, - value_end, - lerp, - duration, + match &curve.keyframes { + Keyframes::Rotation(keyframes) => { + transform.rotation = transform.rotation.slerp( + curve.interpolation.interpolate( + lerp, + duration, + &keyframes[step_start * curve.interpolation.step_size()..], + 1, + ), + weight, ); - transform.rotation = transform.rotation.slerp(result.normalize(), weight); } - (Interpolation::Step, Keyframes::Translation(keyframes)) => { - transform.translation = transform.translation.lerp(keyframes[step_start], weight); - } - (Interpolation::Linear, Keyframes::Translation(keyframes)) => { - let translation_start = keyframes[step_start]; - let translation_end = keyframes[step_start + 1]; - let result = translation_start.lerp(translation_end, lerp); - transform.translation = transform.translation.lerp(result, weight); - } - (Interpolation::CubicSpline, Keyframes::Translation(keyframes)) => { - let value_start = keyframes[step_start * 3 + 1]; - let tangent_out_start = keyframes[step_start * 3 + 2]; - let tangent_in_end = keyframes[(step_start + 1) * 3]; - let value_end = keyframes[(step_start + 1) * 3 + 1]; - let result = cubic_spline_interpolation!( - value_start, - tangent_out_start, - tangent_in_end, - value_end, - lerp, - duration, + Keyframes::Translation(keyframes) => { + transform.translation = transform.translation.lerp( + curve.interpolation.interpolate( + lerp, + duration, + &keyframes[step_start * curve.interpolation.step_size()..], + 1, + ), + weight, ); - transform.translation = transform.translation.lerp(result, weight); - } - (Interpolation::Step, Keyframes::Scale(keyframes)) => { - transform.scale = transform.scale.lerp(keyframes[step_start], weight); } - (Interpolation::Linear, Keyframes::Scale(keyframes)) => { - let scale_start = keyframes[step_start]; - let scale_end = keyframes[step_start + 1]; - let result = scale_start.lerp(scale_end, lerp); - transform.scale = transform.scale.lerp(result, weight); - } - (Interpolation::CubicSpline, Keyframes::Scale(keyframes)) => { - let value_start = keyframes[step_start * 3 + 1]; - let tangent_out_start = keyframes[step_start * 3 + 2]; - let tangent_in_end = keyframes[(step_start + 1) * 3]; - let value_end = keyframes[(step_start + 1) * 3 + 1]; - let result = cubic_spline_interpolation!( - value_start, - tangent_out_start, - tangent_in_end, - value_end, - lerp, - duration, + Keyframes::Scale(keyframes) => { + transform.scale = transform.scale.lerp( + curve.interpolation.interpolate( + lerp, + duration, + &keyframes[step_start * curve.interpolation.step_size()..], + 1, + ), + weight, ); - transform.scale = transform.scale.lerp(result, weight); - } - (Interpolation::Step, Keyframes::Weights(keyframes)) => { - if let Some(morphs) = morphs { - let target_count = morphs.weights().len(); - let morph_start = get_keyframe(target_count, keyframes, step_start); - lerp_morph_weights(morphs.weights_mut(), morph_start.iter().copied(), weight); - } } - (Interpolation::Linear, Keyframes::Weights(keyframes)) => { + Keyframes::Weights(keyframes) => { if let Some(morphs) = morphs { let target_count = morphs.weights().len(); - let morph_start = get_keyframe(target_count, keyframes, step_start); - let morph_end = get_keyframe(target_count, keyframes, step_start + 1); - let result = morph_start - .iter() - .zip(morph_end) - .map(|(a, b)| a.lerp(*b, lerp)); - lerp_morph_weights(morphs.weights_mut(), result, weight); - } - } - (Interpolation::CubicSpline, Keyframes::Weights(keyframes)) => { - if let Some(morphs) = morphs { - let target_count = morphs.weights().len(); - let morph_start = get_keyframe(target_count, keyframes, step_start * 3 + 1); - let tangents_out_start = get_keyframe(target_count, keyframes, step_start * 3 + 2); - let tangents_in_end = get_keyframe(target_count, keyframes, (step_start + 1) * 3); - let morph_end = get_keyframe(target_count, keyframes, (step_start + 1) * 3 + 1); - let result = morph_start - .iter() - .zip(tangents_out_start) - .zip(tangents_in_end) - .zip(morph_end) - .map( - |(((value_start, tangent_out_start), tangent_in_end), value_end)| { - cubic_spline_interpolation!( - value_start, - tangent_out_start, - tangent_in_end, - value_end, - lerp, - duration, - ) - }, + for (morph, morph_weight) in morphs.weights_mut().iter_mut().enumerate() { + *morph_weight = morph_weight.lerp( + curve.interpolation.interpolate( + lerp, + duration, + &keyframes[step_start + * curve.interpolation.step_size() + * target_count + + morph..], + target_count, + ), + weight, ); - lerp_morph_weights(morphs.weights_mut(), result, weight); + } } } } From 1ea80cea3a186fd3a29894b7dab47fc170462ae2 Mon Sep 17 00:00:00 2001 From: Valaphee <32491319+valaphee@users.noreply.github.com> Date: Wed, 24 Jan 2024 00:39:05 +0100 Subject: [PATCH 2/3] Remove debug print, order interpolation types --- crates/bevy_animation/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index d15186f752898..90545ecaa465b 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -146,15 +146,13 @@ impl Interpolation { stride: usize, ) -> T { match self { - Interpolation::Step => keyframes[stride * 0], Interpolation::Linear => { let value_start = keyframes[stride * 0]; let value_end = keyframes[stride * 1]; value_start._lerp(value_end, lerp) } + Interpolation::Step => keyframes[stride * 0], Interpolation::CubicSpline => { - println!("b"); - let value_start = keyframes[stride * 1]; let tangent_out_start = keyframes[stride * 2]; let tangent_in_end = keyframes[stride * 3]; @@ -169,7 +167,7 @@ impl Interpolation { fn step_size(self) -> usize { match self { - Interpolation::Step | Interpolation::Linear => 1, + Interpolation::Linear | Interpolation::Step => 1, Interpolation::CubicSpline => 3, } } From 561124efcf1bdbf12628c042f2d039cd786ab1d9 Mon Sep 17 00:00:00 2001 From: Valaphee <32491319+valaphee@users.noreply.github.com> Date: Wed, 24 Jan 2024 00:46:39 +0100 Subject: [PATCH 3/3] Add inline hints --- crates/bevy_animation/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 90545ecaa465b..a389da5176d32 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -138,6 +138,7 @@ pub enum Interpolation { } impl Interpolation { + #[inline(always)] fn interpolate( self, lerp: f32, @@ -165,6 +166,7 @@ impl Interpolation { } } + #[inline(always)] fn step_size(self) -> usize { match self { Interpolation::Linear | Interpolation::Step => 1, @@ -177,19 +179,22 @@ trait Interpolatable: Sized + Copy + Mul + Add Self; } -impl Interpolatable for Vec3 { +impl Interpolatable for f32 { + #[inline(always)] fn _lerp(self, rhs: Self, t: f32) -> Self { self.lerp(rhs, t) } } -impl Interpolatable for f32 { +impl Interpolatable for Vec3 { + #[inline(always)] fn _lerp(self, rhs: Self, t: f32) -> Self { self.lerp(rhs, t) } } impl Interpolatable for Quat { + #[inline(always)] fn _lerp(self, rhs: Self, t: f32) -> Self { self.slerp(rhs, t) }