From a0ca9afadb6e0c128f2d23b932c32a170b49ea7d Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Thu, 21 Jul 2022 14:28:05 +0200 Subject: [PATCH 01/13] First draft of an animation blending implementation --- crates/bevy_animation/src/lib.rs | 254 +++++++++++++++++++++++------ examples/animation/animated_fox.rs | 9 + 2 files changed, 209 insertions(+), 54 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index ee70d801ef952..a115c38fe13a2 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -17,10 +17,10 @@ use bevy_ecs::{ }; use bevy_hierarchy::Children; use bevy_math::{Quat, Vec3}; -use bevy_reflect::{Reflect, TypeUuid}; +use bevy_reflect::{erased_serde::private::serde::Serialize, Reflect, TypeUuid}; use bevy_time::Time; use bevy_transform::{prelude::Transform, TransformSystem}; -use bevy_utils::{tracing::warn, HashMap}; +use bevy_utils::{prelude::default, tracing::warn, HashMap}; #[allow(missing_docs)] pub mod prelude { @@ -90,6 +90,54 @@ impl AnimationClip { } } +#[derive(Clone, Debug, Reflect)] +pub struct AnimationTransition { + next_animation_clip: Handle, + repeat: bool, + speed: f32, + clip_elapsed: f32, + transition_time: f32, + transition_elapsed: f32, +} + +impl Default for AnimationTransition { + fn default() -> Self { + Self { + next_animation_clip: Default::default(), + repeat: false, + speed: 1.0, + clip_elapsed: 0.0, + transition_time: 0.0, + transition_elapsed: 0.0, + } + } +} + +impl AnimationTransition { + /// Set the next animation to repeat + pub fn repeat(&mut self) -> &mut Self { + self.repeat = true; + self + } + + /// Speed of the animation playback for the next animation + pub fn speed(&self) -> f32 { + self.speed + } + + /// Set the speed of the next animation playback + pub fn set_speed(&mut self, speed: f32) -> &mut Self { + self.speed = speed; + self + } + + /// Seek to a specific time in the next animation + pub fn set_elapsed(&mut self, elapsed: f32) -> &mut Self { + self.clip_elapsed = elapsed; + self + } +} + /// Animation controls #[derive(Component, Reflect)] #[reflect(Component)] @@ -99,6 +147,7 @@ pub struct AnimationPlayer { speed: f32, elapsed: f32, animation_clip: Handle, + transition: AnimationTransition, } impl Default for AnimationPlayer { @@ -109,6 +158,7 @@ impl Default for AnimationPlayer { speed: 1.0, elapsed: 0.0, animation_clip: Default::default(), + transition: AnimationTransition::default(), } } } @@ -123,6 +173,21 @@ impl AnimationPlayer { self } + /// Crosfade from the current to the next animation + pub fn cross_fade( + &mut self, + handle: Handle, + transition_time: f32, + ) -> &mut AnimationTransition { + self.resume(); + self.transition = AnimationTransition { + next_animation_clip: handle, + transition_time, + ..Default::default() + }; + &mut self.transition + } + /// Set the animation to repeat pub fn repeat(&mut self) -> &mut Self { self.repeat = true; @@ -185,21 +250,54 @@ pub fn animation_player( ) { for (entity, mut player) in &mut animation_players { if let Some(animation_clip) = animations.get(&player.animation_clip) { + // If next_clip is a valid animation the player is in transition + let next_clip = animations.get(&player.transition.next_animation_clip); + let in_transition = next_clip.is_some(); + // Continue if paused unless the `AnimationPlayer` was changed // This allow the animation to still be updated if the player.elapsed field was manually updated in pause if player.paused && !player.is_changed() { continue; } if !player.paused { - player.elapsed += time.delta_seconds() * player.speed; + if in_transition { + player.transition.transition_elapsed += time.delta_seconds(); + player.transition.clip_elapsed += + time.delta_seconds() * player.transition.speed; + player.elapsed += time.delta_seconds() * player.speed; + } else { + player.elapsed += time.delta_seconds() * player.speed; + } + } + + let mut fade_factor = 0.0; + if in_transition { + fade_factor = + player.transition.transition_elapsed / player.transition.transition_time; } - let mut elapsed = player.elapsed; + + if fade_factor >= 1.0 { + fade_factor = 1.0; // set to exactly one so the last step of the interpolation is exact + } + + let mut current_elapsed = player.elapsed; if player.repeat { - elapsed %= animation_clip.duration; + current_elapsed %= animation_clip.duration; } - if elapsed < 0.0 { - elapsed += animation_clip.duration; + if current_elapsed < 0.0 { + current_elapsed += animation_clip.duration; } + + let mut next_elapsed = player.transition.clip_elapsed; + if let Some(next_clip) = next_clip { + if player.transition.repeat { + next_elapsed %= next_clip.duration; + } + if next_elapsed < 0.0 { + next_elapsed += next_clip.duration; + } + } + 'entity: for (path, curves) in &animation_clip.curves { // PERF: finding the target entity can be optimised let mut current_entity = entity; @@ -224,61 +322,109 @@ pub fn animation_player( } } if let Ok(mut transform) = transforms.get_mut(current_entity) { - for curve in curves { - // Some curves have only one keyframe used to set a transform - if curve.keyframe_timestamps.len() == 1 { - match &curve.keyframes { - Keyframes::Rotation(keyframes) => transform.rotation = keyframes[0], - Keyframes::Translation(keyframes) => { - transform.translation = keyframes[0]; - } - Keyframes::Scale(keyframes) => transform.scale = keyframes[0], - } - continue; + let mut clip_curves = vec![curves]; + let mut updated_transforms = vec![transform.clone()]; + + // If in transition also push the target clip to the array + if let Some(next_clip) = next_clip { + if let Some(next_curves) = next_clip.curves.get(path) { + clip_curves.push(next_curves); + updated_transforms.push(transform.clone()); } + } - // Find the current keyframe - // PERF: finding the current keyframe can be optimised - let step_start = match curve - .keyframe_timestamps - .binary_search_by(|probe| probe.partial_cmp(&elapsed).unwrap()) - { - Ok(i) => i, - Err(0) => continue, // this curve isn't started yet - Err(n) if n > curve.keyframe_timestamps.len() - 1 => continue, // this curve is finished - Err(i) => i - 1, + for (index, curves) in clip_curves.iter().enumerate() { + let local_elapsed = if index == 0 { + current_elapsed + } else { + next_elapsed }; - let ts_start = curve.keyframe_timestamps[step_start]; - let ts_end = curve.keyframe_timestamps[step_start + 1]; - let lerp = (elapsed - ts_start) / (ts_end - ts_start); - - // Apply the keyframe - match &curve.keyframes { - 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; + for curve in *curves { + // Some curves have only one keyframe used to set a transform + if curve.keyframe_timestamps.len() == 1 { + match &curve.keyframes { + Keyframes::Rotation(keyframes) => { + updated_transforms[index].rotation = keyframes[0] + } + Keyframes::Translation(keyframes) => { + updated_transforms[index].translation = keyframes[0]; + } + Keyframes::Scale(keyframes) => { + updated_transforms[index].scale = keyframes[0] + } } - // Rotations are using a spherical linear interpolation - transform.rotation = - rot_start.normalize().slerp(rot_end.normalize(), lerp); + continue; } - 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 = result; - } - 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 = result; + + // Find the current keyframe + // PERF: finding the current keyframe can be optimised + let step_start = + match curve.keyframe_timestamps.binary_search_by(|probe| { + probe.partial_cmp(&local_elapsed).unwrap() + }) { + Ok(i) => i, + Err(0) => continue, // this curve isn't started yet + Err(n) if n > curve.keyframe_timestamps.len() - 1 => continue, // this curve is finished + Err(i) => i - 1, + }; + let ts_start = curve.keyframe_timestamps[step_start]; + let ts_end = curve.keyframe_timestamps[step_start + 1]; + let lerp = (local_elapsed - ts_start) / (ts_end - ts_start); + + // Apply the keyframe + match &curve.keyframes { + 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 result = + rot_start.normalize().slerp(rot_end.normalize(), lerp); + updated_transforms[index].rotation = result; + } + Keyframes::Translation(keyframes) => { + let translation_start = keyframes[step_start]; + let translation_end = keyframes[step_start + 1]; + let result = translation_start.lerp(translation_end, lerp); + updated_transforms[index].translation = result; + } + Keyframes::Scale(keyframes) => { + let scale_start = keyframes[step_start]; + let scale_end = keyframes[step_start + 1]; + let result = scale_start.lerp(scale_end, lerp); + updated_transforms[index].scale = result; + } } } } + + if updated_transforms.len() == 1 { + *transform = updated_transforms[0]; + } else if updated_transforms.len() == 2 { + let from = updated_transforms[0]; + let to = updated_transforms[1]; + transform.rotation = from.rotation.slerp(to.rotation, fade_factor); + transform.translation = from.translation.lerp(to.translation, fade_factor); + transform.scale = from.scale.lerp(to.scale, fade_factor); + } + } + } + + // Transition to next clip has finished + if fade_factor == 1.0 { + let next_clip = player.transition.next_animation_clip.clone_weak(); + let next_speed = player.transition.speed; + let repeat = player.transition.repeat; + player + .play(next_clip) + .set_elapsed(next_elapsed) + .set_speed(next_speed); + if repeat { + player.repeat(); } } } diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index 533fd1677c562..ad3edf2d19ea3 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -123,6 +123,15 @@ fn keyboard_animation_control( if keyboard_input.just_pressed(KeyCode::Return) { *current_animation = (*current_animation + 1) % animations.0.len(); + + player + .cross_fade(animations.0[*current_animation].clone_weak(), 1.5) + .repeat(); + } + + if keyboard_input.just_pressed(KeyCode::P) { + *current_animation = (*current_animation + 1) % animations.0.len(); + player .play(animations.0[*current_animation].clone_weak()) .repeat(); From df0a36d7ab05f867084fc11a0dc1ef34a29f515a Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Thu, 21 Jul 2022 15:26:00 +0200 Subject: [PATCH 02/13] rename fade_factor to transition_lerp --- crates/bevy_animation/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index a115c38fe13a2..344f569812724 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -270,14 +270,14 @@ pub fn animation_player( } } - let mut fade_factor = 0.0; + let mut transition_lerp = 0.0; if in_transition { - fade_factor = + transition_lerp = player.transition.transition_elapsed / player.transition.transition_time; } - if fade_factor >= 1.0 { - fade_factor = 1.0; // set to exactly one so the last step of the interpolation is exact + if transition_lerp >= 1.0 { + transition_lerp = 1.0; // set to exactly one so the last step of the interpolation is exact } let mut current_elapsed = player.elapsed; @@ -407,15 +407,15 @@ pub fn animation_player( } else if updated_transforms.len() == 2 { let from = updated_transforms[0]; let to = updated_transforms[1]; - transform.rotation = from.rotation.slerp(to.rotation, fade_factor); - transform.translation = from.translation.lerp(to.translation, fade_factor); - transform.scale = from.scale.lerp(to.scale, fade_factor); + transform.rotation = from.rotation.slerp(to.rotation, transition_lerp); + transform.translation = from.translation.lerp(to.translation, transition_lerp); + transform.scale = from.scale.lerp(to.scale, transition_lerp); } } } // Transition to next clip has finished - if fade_factor == 1.0 { + if transition_lerp == 1.0 { let next_clip = player.transition.next_animation_clip.clone_weak(); let next_speed = player.transition.speed; let repeat = player.transition.repeat; From 60880be65d8ab3526db8fe4c485c2e05a1fe3d0a Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Thu, 21 Jul 2022 15:27:41 +0200 Subject: [PATCH 03/13] formatting and example println --- crates/bevy_animation/src/lib.rs | 3 ++- examples/animation/animated_fox.rs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 344f569812724..f4035a3039e0d 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -408,7 +408,8 @@ pub fn animation_player( let from = updated_transforms[0]; let to = updated_transforms[1]; transform.rotation = from.rotation.slerp(to.rotation, transition_lerp); - transform.translation = from.translation.lerp(to.translation, transition_lerp); + transform.translation = + from.translation.lerp(to.translation, transition_lerp); transform.scale = from.scale.lerp(to.scale, transition_lerp); } } diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index ad3edf2d19ea3..daa2ea6cdb64a 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -69,7 +69,8 @@ fn setup( println!(" - spacebar: play / pause"); println!(" - arrow up / down: speed up / slow down animation playback"); println!(" - arrow left / right: seek backward / forward"); - println!(" - return: change animation"); + println!(" - return: change animation using crossfade()"); + println!(" - p: change animation using play()"); } // Once the scene is loaded, start the animation @@ -128,7 +129,7 @@ fn keyboard_animation_control( .cross_fade(animations.0[*current_animation].clone_weak(), 1.5) .repeat(); } - + if keyboard_input.just_pressed(KeyCode::P) { *current_animation = (*current_animation + 1) % animations.0.len(); From abdb553806ee109f94a47cfc7df1a7accbbc1c09 Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Thu, 21 Jul 2022 15:42:13 +0200 Subject: [PATCH 04/13] add some documentation --- crates/bevy_animation/src/lib.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index f4035a3039e0d..dd002a5e8440b 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -17,10 +17,10 @@ use bevy_ecs::{ }; use bevy_hierarchy::Children; use bevy_math::{Quat, Vec3}; -use bevy_reflect::{erased_serde::private::serde::Serialize, Reflect, TypeUuid}; +use bevy_reflect::{Reflect, TypeUuid}; use bevy_time::Time; use bevy_transform::{prelude::Transform, TransformSystem}; -use bevy_utils::{prelude::default, tracing::warn, HashMap}; +use bevy_utils::{tracing::warn, HashMap}; #[allow(missing_docs)] pub mod prelude { @@ -90,14 +90,18 @@ impl AnimationClip { } } +/// A representation of the data needed transition to a new animation #[derive(Clone, Debug, Reflect)] pub struct AnimationTransition { next_animation_clip: Handle, repeat: bool, speed: f32, + /// The elapsed time of the target clip (is scaled by speed and reset on clip end) clip_elapsed: f32, - transition_time: f32, + /// The actual time that the transition is running in seconds transition_elapsed: f32, + /// The desired duration of the transition + transition_time: f32, } impl Default for AnimationTransition { @@ -174,6 +178,9 @@ impl AnimationPlayer { } /// Crosfade from the current to the next animation + /// + /// - [`handle`] a handle to the target animation clip + /// - [`transition_time`] determines the duration of the transition in seconds pub fn cross_fade( &mut self, handle: Handle, From d271651e6c51fa767061631fa82d68b1eaeb4a1d Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Thu, 21 Jul 2022 15:52:37 +0200 Subject: [PATCH 05/13] some clean-up and comments --- crates/bevy_animation/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index dd002a5e8440b..dc2ce66f78dfc 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -284,7 +284,8 @@ pub fn animation_player( } if transition_lerp >= 1.0 { - transition_lerp = 1.0; // set to exactly one so the last step of the interpolation is exact + // set to exactly one so the last step of the interpolation is exact + transition_lerp = 1.0; } let mut current_elapsed = player.elapsed; @@ -330,13 +331,13 @@ pub fn animation_player( } if let Ok(mut transform) = transforms.get_mut(current_entity) { let mut clip_curves = vec![curves]; - let mut updated_transforms = vec![transform.clone()]; + let mut updated_transforms = vec![*transform]; // If in transition also push the target clip to the array if let Some(next_clip) = next_clip { if let Some(next_curves) = next_clip.curves.get(path) { clip_curves.push(next_curves); - updated_transforms.push(transform.clone()); + updated_transforms.push(*transform); } } @@ -409,6 +410,8 @@ pub fn animation_player( } } + // if updated_transforms has length 2 the animation is in transition and we use the computed transforms + // from both the current and the target curve and interpolate between them using the transition_lerp factor if updated_transforms.len() == 1 { *transform = updated_transforms[0]; } else if updated_transforms.len() == 2 { From 66fde82563318b2d16306efabc7b851ab3ee6f21 Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Thu, 21 Jul 2022 15:53:50 +0200 Subject: [PATCH 06/13] fix fmt --- crates/bevy_animation/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index dc2ce66f78dfc..a2a24f1f9de5c 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -285,7 +285,7 @@ pub fn animation_player( if transition_lerp >= 1.0 { // set to exactly one so the last step of the interpolation is exact - transition_lerp = 1.0; + transition_lerp = 1.0; } let mut current_elapsed = player.elapsed; From 1b212ad211cf4000dbfd42af1224bb3d9cb7f91b Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Thu, 21 Jul 2022 16:17:54 +0200 Subject: [PATCH 07/13] add missing semiclolons --- crates/bevy_animation/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index a2a24f1f9de5c..498663df6307c 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -352,13 +352,13 @@ pub fn animation_player( if curve.keyframe_timestamps.len() == 1 { match &curve.keyframes { Keyframes::Rotation(keyframes) => { - updated_transforms[index].rotation = keyframes[0] + updated_transforms[index].rotation = keyframes[0]; } Keyframes::Translation(keyframes) => { updated_transforms[index].translation = keyframes[0]; } Keyframes::Scale(keyframes) => { - updated_transforms[index].scale = keyframes[0] + updated_transforms[index].scale = keyframes[0]; } } continue; From e599a7683266e1f256e6327f487246736025d5ca Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Thu, 21 Jul 2022 16:27:29 +0200 Subject: [PATCH 08/13] fix docs --- crates/bevy_animation/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 498663df6307c..9b76ea4273d6d 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -179,8 +179,8 @@ impl AnimationPlayer { /// Crosfade from the current to the next animation /// - /// - [`handle`] a handle to the target animation clip - /// - [`transition_time`] determines the duration of the transition in seconds + /// - `handle` a handle to the target animation clip + /// - `transition_time` determines the duration of the transition in seconds pub fn cross_fade( &mut self, handle: Handle, From 7f368b55f6fe786773bf65dea7713082f44a67f5 Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Thu, 21 Jul 2022 17:52:30 +0200 Subject: [PATCH 09/13] fixed review remarks --- crates/bevy_animation/src/lib.rs | 71 +++++++++++++++++------------ examples/animation/animated_fox.rs | 10 ++-- examples/stress_tests/many_foxes.rs | 6 ++- examples/tools/scene_viewer.rs | 3 +- 4 files changed, 55 insertions(+), 35 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 9b76ea4273d6d..a99b4cf05120e 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -20,7 +20,7 @@ use bevy_math::{Quat, Vec3}; use bevy_reflect::{Reflect, TypeUuid}; use bevy_time::Time; use bevy_transform::{prelude::Transform, TransformSystem}; -use bevy_utils::{tracing::warn, HashMap}; +use bevy_utils::{tracing::warn, Duration, HashMap}; #[allow(missing_docs)] pub mod prelude { @@ -90,18 +90,20 @@ impl AnimationClip { } } -/// A representation of the data needed transition to a new animation +/// The data needed to transition to a new animation +/// +/// Stored in an [`AnimationPlayer`] #[derive(Clone, Debug, Reflect)] pub struct AnimationTransition { next_animation_clip: Handle, repeat: bool, speed: f32, /// The elapsed time of the target clip (is scaled by speed and reset on clip end) - clip_elapsed: f32, + clip_elapsed: Duration, /// The actual time that the transition is running in seconds - transition_elapsed: f32, + transition_elapsed: Duration, /// The desired duration of the transition - transition_time: f32, + transition_time: Duration, } impl Default for AnimationTransition { @@ -110,9 +112,9 @@ impl Default for AnimationTransition { next_animation_clip: Default::default(), repeat: false, speed: 1.0, - clip_elapsed: 0.0, - transition_time: 0.0, - transition_elapsed: 0.0, + clip_elapsed: Duration::ZERO, + transition_time: Duration::ZERO, + transition_elapsed: Duration::ZERO, } } } @@ -136,7 +138,7 @@ impl AnimationTransition { } /// Seek to a specific time in the next animation - pub fn set_elapsed(&mut self, elapsed: f32) -> &mut Self { + pub fn set_elapsed(&mut self, elapsed: Duration) -> &mut Self { self.clip_elapsed = elapsed; self } @@ -149,9 +151,10 @@ pub struct AnimationPlayer { paused: bool, repeat: bool, speed: f32, - elapsed: f32, + elapsed: Duration, animation_clip: Handle, transition: AnimationTransition, + in_transition: bool, } impl Default for AnimationPlayer { @@ -160,9 +163,10 @@ impl Default for AnimationPlayer { paused: false, repeat: false, speed: 1.0, - elapsed: 0.0, + elapsed: Duration::ZERO, animation_clip: Default::default(), transition: AnimationTransition::default(), + in_transition: false, } } } @@ -184,7 +188,7 @@ impl AnimationPlayer { pub fn cross_fade( &mut self, handle: Handle, - transition_time: f32, + transition_time: Duration, ) -> &mut AnimationTransition { self.resume(); self.transition = AnimationTransition { @@ -192,6 +196,7 @@ impl AnimationPlayer { transition_time, ..Default::default() }; + self.in_transition = true; &mut self.transition } @@ -234,15 +239,20 @@ impl AnimationPlayer { } /// Time elapsed playing the animation - pub fn elapsed(&self) -> f32 { + pub fn elapsed(&self) -> Duration { self.elapsed } /// Seek to a specific time in the animation - pub fn set_elapsed(&mut self, elapsed: f32) -> &mut Self { + pub fn set_elapsed(&mut self, elapsed: Duration) -> &mut Self { self.elapsed = elapsed; self } + + /// Is the animation in transition + pub fn is_in_transition(&self) -> bool { + self.in_transition + } } /// System that will play all animations, using any entity with a [`AnimationPlayer`] @@ -257,9 +267,10 @@ pub fn animation_player( ) { for (entity, mut player) in &mut animation_players { if let Some(animation_clip) = animations.get(&player.animation_clip) { - // If next_clip is a valid animation the player is in transition + // Try to fetch the next clip in case the animation is in transition let next_clip = animations.get(&player.transition.next_animation_clip); - let in_transition = next_clip.is_some(); + + let mut transition_just_finished = false; // Continue if paused unless the `AnimationPlayer` was changed // This allow the animation to still be updated if the player.elapsed field was manually updated in pause @@ -267,28 +278,30 @@ pub fn animation_player( continue; } if !player.paused { - if in_transition { - player.transition.transition_elapsed += time.delta_seconds(); + let delta_seconds = time.delta_seconds(); + if player.in_transition { + player.transition.transition_elapsed += Duration::from_secs_f32(delta_seconds); + let t_speed = player.transition.speed; player.transition.clip_elapsed += - time.delta_seconds() * player.transition.speed; - player.elapsed += time.delta_seconds() * player.speed; - } else { - player.elapsed += time.delta_seconds() * player.speed; + Duration::from_secs_f32(delta_seconds * t_speed); } + let p_speed = player.speed; + player.elapsed += Duration::from_secs_f32(delta_seconds * p_speed); } let mut transition_lerp = 0.0; - if in_transition { - transition_lerp = - player.transition.transition_elapsed / player.transition.transition_time; + if player.in_transition { + transition_lerp = player.transition.transition_elapsed.as_secs_f32() + / player.transition.transition_time.as_secs_f32(); } if transition_lerp >= 1.0 { // set to exactly one so the last step of the interpolation is exact transition_lerp = 1.0; + transition_just_finished = true; } - let mut current_elapsed = player.elapsed; + let mut current_elapsed = player.elapsed.as_secs_f32(); if player.repeat { current_elapsed %= animation_clip.duration; } @@ -296,7 +309,7 @@ pub fn animation_player( current_elapsed += animation_clip.duration; } - let mut next_elapsed = player.transition.clip_elapsed; + let mut next_elapsed = player.transition.clip_elapsed.as_secs_f32(); if let Some(next_clip) = next_clip { if player.transition.repeat { next_elapsed %= next_clip.duration; @@ -426,13 +439,13 @@ pub fn animation_player( } // Transition to next clip has finished - if transition_lerp == 1.0 { + if transition_just_finished { let next_clip = player.transition.next_animation_clip.clone_weak(); let next_speed = player.transition.speed; let repeat = player.transition.repeat; player .play(next_clip) - .set_elapsed(next_elapsed) + .set_elapsed(Duration::from_secs_f32(next_elapsed)) .set_speed(next_speed); if repeat { player.repeat(); diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index daa2ea6cdb64a..de6e5bc0ef62f 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -1,6 +1,7 @@ //! Plays animations from a skinned glTF. use bevy::prelude::*; +use bevy::utils::Duration; fn main() { App::new() @@ -114,19 +115,22 @@ fn keyboard_animation_control( if keyboard_input.just_pressed(KeyCode::Left) { let elapsed = player.elapsed(); - player.set_elapsed(elapsed - 0.1); + player.set_elapsed(elapsed - Duration::from_secs_f32(0.1)); } if keyboard_input.just_pressed(KeyCode::Right) { let elapsed = player.elapsed(); - player.set_elapsed(elapsed + 0.1); + player.set_elapsed(elapsed + Duration::from_secs_f32(0.1)); } if keyboard_input.just_pressed(KeyCode::Return) { *current_animation = (*current_animation + 1) % animations.0.len(); player - .cross_fade(animations.0[*current_animation].clone_weak(), 1.5) + .cross_fade( + animations.0[*current_animation].clone_weak(), + Duration::from_secs_f32(1.5), + ) .repeat(); } diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index f4589efe344c9..723b9df25ebf5 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -1,6 +1,8 @@ //! Loads animations from a skinned glTF, spawns many of them, and plays the //! animation to stress test skinned meshes. +use std::time::Duration; + use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, @@ -258,12 +260,12 @@ fn keyboard_animation_control( if keyboard_input.just_pressed(KeyCode::Left) { let elapsed = player.elapsed(); - player.set_elapsed(elapsed - 0.1); + player.set_elapsed(elapsed - Duration::from_secs_f32(0.1)); } if keyboard_input.just_pressed(KeyCode::Right) { let elapsed = player.elapsed(); - player.set_elapsed(elapsed + 0.1); + player.set_elapsed(elapsed + Duration::from_secs_f32(0.1)); } if keyboard_input.just_pressed(KeyCode::Return) { diff --git a/examples/tools/scene_viewer.rs b/examples/tools/scene_viewer.rs index 0bb91b3573712..dd582a89eeb3f 100644 --- a/examples/tools/scene_viewer.rs +++ b/examples/tools/scene_viewer.rs @@ -12,6 +12,7 @@ use bevy::{ prelude::*, render::primitives::{Aabb, Sphere}, scene::InstanceId, + utils::Duration, }; use std::f32::consts::TAU; @@ -199,7 +200,7 @@ fn keyboard_animation_control( // delay the animation change for one frame *changing = true; // set the current animation to its start and pause it to reset to its starting state - player.set_elapsed(0.0).pause(); + player.set_elapsed(Duration::ZERO).pause(); } } } From b6b1c8de62db0b86009d60d93b6ce5ed0ed60d95 Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Sun, 31 Jul 2022 17:49:52 +0200 Subject: [PATCH 10/13] performance improvements --- crates/bevy_animation/src/lib.rs | 188 +++++++++++++++---------------- 1 file changed, 91 insertions(+), 97 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index a99b4cf05120e..cc60a97250fd3 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -267,8 +267,11 @@ pub fn animation_player( ) { for (entity, mut player) in &mut animation_players { if let Some(animation_clip) = animations.get(&player.animation_clip) { - // Try to fetch the next clip in case the animation is in transition - let next_clip = animations.get(&player.transition.next_animation_clip); + let next_clip = if player.in_transition { + animations.get(&player.transition.next_animation_clip) + } else { + None + }; let mut transition_just_finished = false; @@ -309,13 +312,16 @@ pub fn animation_player( current_elapsed += animation_clip.duration; } - let mut next_elapsed = player.transition.clip_elapsed.as_secs_f32(); - if let Some(next_clip) = next_clip { - if player.transition.repeat { - next_elapsed %= next_clip.duration; - } - if next_elapsed < 0.0 { - next_elapsed += next_clip.duration; + let mut next_elapsed = 0.0; + if player.in_transition { + if let Some(next_clip) = next_clip { + next_elapsed = player.transition.clip_elapsed.as_secs_f32(); + if player.transition.repeat { + next_elapsed %= next_clip.duration; + } + if next_elapsed < 0.0 { + next_elapsed += next_clip.duration; + } } } @@ -343,102 +349,26 @@ pub fn animation_player( } } if let Ok(mut transform) = transforms.get_mut(current_entity) { - let mut clip_curves = vec![curves]; - let mut updated_transforms = vec![*transform]; - - // If in transition also push the target clip to the array - if let Some(next_clip) = next_clip { + if !player.in_transition { + update_transforms(curves, current_elapsed, &mut transform); + } else if let Some(next_clip) = next_clip { if let Some(next_curves) = next_clip.curves.get(path) { - clip_curves.push(next_curves); - updated_transforms.push(*transform); - } - } + let mut from = *transform; + let mut to = *transform; - for (index, curves) in clip_curves.iter().enumerate() { - let local_elapsed = if index == 0 { - current_elapsed - } else { - next_elapsed - }; - for curve in *curves { - // Some curves have only one keyframe used to set a transform - if curve.keyframe_timestamps.len() == 1 { - match &curve.keyframes { - Keyframes::Rotation(keyframes) => { - updated_transforms[index].rotation = keyframes[0]; - } - Keyframes::Translation(keyframes) => { - updated_transforms[index].translation = keyframes[0]; - } - Keyframes::Scale(keyframes) => { - updated_transforms[index].scale = keyframes[0]; - } - } - continue; - } + update_transforms(curves, current_elapsed, &mut from); + update_transforms(next_curves, next_elapsed, &mut to); - // Find the current keyframe - // PERF: finding the current keyframe can be optimised - let step_start = - match curve.keyframe_timestamps.binary_search_by(|probe| { - probe.partial_cmp(&local_elapsed).unwrap() - }) { - Ok(i) => i, - Err(0) => continue, // this curve isn't started yet - Err(n) if n > curve.keyframe_timestamps.len() - 1 => continue, // this curve is finished - Err(i) => i - 1, - }; - let ts_start = curve.keyframe_timestamps[step_start]; - let ts_end = curve.keyframe_timestamps[step_start + 1]; - let lerp = (local_elapsed - ts_start) / (ts_end - ts_start); - - // Apply the keyframe - match &curve.keyframes { - 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 result = - rot_start.normalize().slerp(rot_end.normalize(), lerp); - updated_transforms[index].rotation = result; - } - Keyframes::Translation(keyframes) => { - let translation_start = keyframes[step_start]; - let translation_end = keyframes[step_start + 1]; - let result = translation_start.lerp(translation_end, lerp); - updated_transforms[index].translation = result; - } - Keyframes::Scale(keyframes) => { - let scale_start = keyframes[step_start]; - let scale_end = keyframes[step_start + 1]; - let result = scale_start.lerp(scale_end, lerp); - updated_transforms[index].scale = result; - } - } + transform.rotation = from.rotation.slerp(to.rotation, transition_lerp); + transform.translation = + from.translation.lerp(to.translation, transition_lerp); + transform.scale = from.scale.lerp(to.scale, transition_lerp); } } - - // if updated_transforms has length 2 the animation is in transition and we use the computed transforms - // from both the current and the target curve and interpolate between them using the transition_lerp factor - if updated_transforms.len() == 1 { - *transform = updated_transforms[0]; - } else if updated_transforms.len() == 2 { - let from = updated_transforms[0]; - let to = updated_transforms[1]; - transform.rotation = from.rotation.slerp(to.rotation, transition_lerp); - transform.translation = - from.translation.lerp(to.translation, transition_lerp); - transform.scale = from.scale.lerp(to.scale, transition_lerp); - } } } - // Transition to next clip has finished + // Execute if transition to next clip has finished if transition_just_finished { let next_clip = player.transition.next_animation_clip.clone_weak(); let next_speed = player.transition.speed; @@ -455,6 +385,70 @@ pub fn animation_player( } } +#[inline(always)] +fn update_transforms(curves: &Vec, elapsed: f32, mut transform: &mut Transform) { + for curve in curves { + // Some curves have only one keyframe used to set a transform + if curve.keyframe_timestamps.len() == 1 { + match &curve.keyframes { + Keyframes::Rotation(keyframes) => { + transform.rotation = keyframes[0]; + } + Keyframes::Translation(keyframes) => { + transform.translation = keyframes[0]; + } + Keyframes::Scale(keyframes) => { + transform.scale = keyframes[0]; + } + } + continue; + } + + // Find the current keyframe + // PERF: finding the current keyframe can be optimised + let step_start = match curve + .keyframe_timestamps + .binary_search_by(|probe| probe.partial_cmp(&elapsed).unwrap()) + { + Ok(i) => i, + Err(0) => continue, // this curve isn't started yet + Err(n) if n > curve.keyframe_timestamps.len() - 1 => continue, // this curve is finished + Err(i) => i - 1, + }; + let ts_start = curve.keyframe_timestamps[step_start]; + let ts_end = curve.keyframe_timestamps[step_start + 1]; + let lerp = (elapsed - ts_start) / (ts_end - ts_start); + + // Apply the keyframe + match &curve.keyframes { + 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 result = rot_start.normalize().slerp(rot_end.normalize(), lerp); + transform.rotation = result; + } + 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 = result; + } + 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 = result; + } + } + } +} + /// Adds animation support to an app #[derive(Default)] pub struct AnimationPlugin {} From a0db664f46c52410aea435fa8a5e97949ee3f145 Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Sun, 31 Jul 2022 18:06:51 +0200 Subject: [PATCH 11/13] naming --- crates/bevy_animation/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index cc60a97250fd3..2f401d5c1dfb7 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -350,14 +350,14 @@ pub fn animation_player( } if let Ok(mut transform) = transforms.get_mut(current_entity) { if !player.in_transition { - update_transforms(curves, current_elapsed, &mut transform); + update_transform(curves, current_elapsed, &mut transform); } else if let Some(next_clip) = next_clip { if let Some(next_curves) = next_clip.curves.get(path) { let mut from = *transform; let mut to = *transform; - update_transforms(curves, current_elapsed, &mut from); - update_transforms(next_curves, next_elapsed, &mut to); + update_transform(curves, current_elapsed, &mut from); + update_transform(next_curves, next_elapsed, &mut to); transform.rotation = from.rotation.slerp(to.rotation, transition_lerp); transform.translation = @@ -386,7 +386,7 @@ pub fn animation_player( } #[inline(always)] -fn update_transforms(curves: &Vec, elapsed: f32, mut transform: &mut Transform) { +fn update_transform(curves: &Vec, elapsed: f32, mut transform: &mut Transform) { for curve in curves { // Some curves have only one keyframe used to set a transform if curve.keyframe_timestamps.len() == 1 { From 1bdc745c2985884b9ede462392b1a94c333ef9ca Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Sun, 31 Jul 2022 18:08:03 +0200 Subject: [PATCH 12/13] Revert "naming" This reverts commit 2a1218fefd0d3fbf142897d2c6aaf8abf0c68084. --- crates/bevy_animation/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 2f401d5c1dfb7..cc60a97250fd3 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -350,14 +350,14 @@ pub fn animation_player( } if let Ok(mut transform) = transforms.get_mut(current_entity) { if !player.in_transition { - update_transform(curves, current_elapsed, &mut transform); + update_transforms(curves, current_elapsed, &mut transform); } else if let Some(next_clip) = next_clip { if let Some(next_curves) = next_clip.curves.get(path) { let mut from = *transform; let mut to = *transform; - update_transform(curves, current_elapsed, &mut from); - update_transform(next_curves, next_elapsed, &mut to); + update_transforms(curves, current_elapsed, &mut from); + update_transforms(next_curves, next_elapsed, &mut to); transform.rotation = from.rotation.slerp(to.rotation, transition_lerp); transform.translation = @@ -386,7 +386,7 @@ pub fn animation_player( } #[inline(always)] -fn update_transform(curves: &Vec, elapsed: f32, mut transform: &mut Transform) { +fn update_transforms(curves: &Vec, elapsed: f32, mut transform: &mut Transform) { for curve in curves { // Some curves have only one keyframe used to set a transform if curve.keyframe_timestamps.len() == 1 { From e045e087fdded27ef00fb0750e6632a71f0a4e0e Mon Sep 17 00:00:00 2001 From: Ben Dzaebel Date: Sun, 31 Jul 2022 18:09:19 +0200 Subject: [PATCH 13/13] naming --- crates/bevy_animation/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index cc60a97250fd3..2f401d5c1dfb7 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -350,14 +350,14 @@ pub fn animation_player( } if let Ok(mut transform) = transforms.get_mut(current_entity) { if !player.in_transition { - update_transforms(curves, current_elapsed, &mut transform); + update_transform(curves, current_elapsed, &mut transform); } else if let Some(next_clip) = next_clip { if let Some(next_curves) = next_clip.curves.get(path) { let mut from = *transform; let mut to = *transform; - update_transforms(curves, current_elapsed, &mut from); - update_transforms(next_curves, next_elapsed, &mut to); + update_transform(curves, current_elapsed, &mut from); + update_transform(next_curves, next_elapsed, &mut to); transform.rotation = from.rotation.slerp(to.rotation, transition_lerp); transform.translation = @@ -386,7 +386,7 @@ pub fn animation_player( } #[inline(always)] -fn update_transforms(curves: &Vec, elapsed: f32, mut transform: &mut Transform) { +fn update_transform(curves: &Vec, elapsed: f32, mut transform: &mut Transform) { for curve in curves { // Some curves have only one keyframe used to set a transform if curve.keyframe_timestamps.len() == 1 {