From 7a25b8e5b6e62916a54e1d8e4921020a1ce6ba32 Mon Sep 17 00:00:00 2001 From: mintlu8 Date: Mon, 10 Jun 2024 03:05:35 +0800 Subject: [PATCH 1/3] Added ability to sample transform. --- crates/bevy_animation/src/lib.rs | 118 ++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 0023532a18eef..b6520231e8667 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -18,12 +18,15 @@ use std::hash::{Hash, Hasher}; use std::iter; use std::ops::{Add, Mul}; +use animatable::Animatable; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::{Asset, AssetApp, Assets, Handle}; use bevy_core::Name; use bevy_ecs::entity::MapEntities; use bevy_ecs::prelude::*; use bevy_ecs::reflect::ReflectMapEntities; +use bevy_ecs::system::QueryLens; +use bevy_hierarchy::Parent; use bevy_math::{FloatExt, Quat, Vec3}; use bevy_reflect::Reflect; use bevy_render::mesh::morph::MorphWeights; @@ -155,10 +158,76 @@ impl VariableCurve { Some(step_start) } + + /// Write the output of this [`VariableCurve`] at `seek_time` to a [`Transform`] component, with influence, `influence`. + pub fn write_to_transform(&self, seek_time: f32, influence: f32, transform: &mut Transform) { + let Some((idx, fac, step_duration)) = (match self.find_current_keyframe(seek_time) { + Some(idx) => (|| { + let l = *self.keyframe_timestamps.get(idx)?; + let r = *self.keyframe_timestamps.get(idx)?; + let fac = (seek_time - l) / (r - l); + Some((idx, fac, r - l)) + })(), + None => self + .keyframe_timestamps + .len() + .checked_sub(1) + .map(|x| (x, 0.0, 0.0)), + }) else { + return; + }; + match &self.keyframes { + Keyframes::Rotation(r) => { + let rot = sample(r, self.interpolation, idx, fac, step_duration); + transform.rotation = Quat::slerp(transform.rotation, rot, influence); + } + Keyframes::Translation(t) => { + let translation = sample(t, self.interpolation, idx, fac, step_duration); + transform.translation = Vec3::lerp(transform.translation, translation, influence); + } + Keyframes::Scale(s) => { + let scale = sample(s, self.interpolation, idx, fac, step_duration); + transform.scale = Vec3::lerp(transform.scale, scale, influence); + } + Keyframes::Weights(_) => (), + } + } +} + +/// Sample a [`Keyframes`] buffer. +fn sample + Add + Default + Copy>( + buffer: &[V], + interpolation: Interpolation, + idx: usize, + fac: f32, + step_duration: f32, +) -> V { + if idx * interpolation.points_per_keyframe() + >= buffer.len() - interpolation.points_per_keyframe() + { + return match interpolation { + Interpolation::Linear | Interpolation::Step => buffer.last(), + Interpolation::CubicSpline => buffer.get(buffer.len().wrapping_sub(2)), + } + .copied() + .unwrap_or_default(); + } + match interpolation { + Interpolation::Linear => buffer[idx], + Interpolation::Step => buffer[idx] * (1.0 - fac) + buffer[idx + 1] * fac, + Interpolation::CubicSpline => cubic_spline_interpolation( + buffer[idx * 3 + 1], + buffer[idx * 3 + 2], + buffer[idx * 3 + 3], + buffer[idx * 3 + 4], + fac, + step_duration, + ), + } } /// Interpolation method to use between keyframes. -#[derive(Reflect, Clone, Debug)] +#[derive(Reflect, Clone, Copy, Debug)] pub enum Interpolation { /// Linear interpolation between the two closest keyframes. Linear, @@ -169,6 +238,15 @@ pub enum Interpolation { CubicSpline, } +impl Interpolation { + fn points_per_keyframe(&self) -> usize { + match self { + Interpolation::Linear | Interpolation::Step => 1, + Interpolation::CubicSpline => 3, + } + } +} + /// A list of [`VariableCurve`]s and the [`AnimationTargetId`]s to which they /// apply. /// @@ -308,6 +386,44 @@ impl AnimationClip { .max(*curve.keyframe_timestamps.last().unwrap_or(&0.0)); self.curves.entry(target_id).or_default().push(curve); } + + /// Obtain the transform of a bone at a specific time relative to the position of the [`AnimationPlayer`] + /// if under the sole influence of this [`AnimationClip`]. + /// + /// Returns None if the [`Entity`] does not exist, + /// no ancestor [`AnimationPlayer`] found or have a broken transform hierarchy. + /// + /// # Limitations + /// + /// The existing [`Transform`] is used if the clip is not responsible for animating an ancestor bone. + /// Which means this realistically only works if the animation has control over all moving bones leading back to the + /// armature root. + pub fn sample_transform_at( + &mut self, + entity: Entity, + time: f32, + query: &Query<( + &Transform, + Option<&AnimationTarget>, + Option<&Parent>, + Has, + )>, + ) -> Option { + let (transform, target, parent, is_root) = query.get(entity).ok()?; + let mut transform = *transform; + let parent_transform = if is_root { + Transform::IDENTITY + } else { + self.sample_transform_at(parent?.get(), time, query)? + }; + // If not animated by this, use the existing `Transform` component. + if let Some(curves) = target.and_then(|t| self.curves.get(&t.id)) { + for curve in curves { + curve.write_to_transform(time, 1.0, &mut transform); + } + } + Some(parent_transform.mul_transform(transform)) + } } /// Repetition behavior of an animation. From 87dff8012423e919b30b3fa7133a07e68f573044 Mon Sep 17 00:00:00 2001 From: mintlu8 Date: Mon, 10 Jun 2024 03:07:59 +0800 Subject: [PATCH 2/3] Update lib.rs --- crates/bevy_animation/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index b6520231e8667..7b61345bc4b60 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -18,14 +18,12 @@ use std::hash::{Hash, Hasher}; use std::iter; use std::ops::{Add, Mul}; -use animatable::Animatable; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::{Asset, AssetApp, Assets, Handle}; use bevy_core::Name; use bevy_ecs::entity::MapEntities; use bevy_ecs::prelude::*; use bevy_ecs::reflect::ReflectMapEntities; -use bevy_ecs::system::QueryLens; use bevy_hierarchy::Parent; use bevy_math::{FloatExt, Quat, Vec3}; use bevy_reflect::Reflect; From d565150f54596659bc2faa9e1b7d0c15d4b35c64 Mon Sep 17 00:00:00 2001 From: mintlu8 Date: Mon, 10 Jun 2024 03:10:33 +0800 Subject: [PATCH 3/3] Update lib.rs --- 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 7b61345bc4b60..f2c2106139cbe 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -157,7 +157,7 @@ impl VariableCurve { Some(step_start) } - /// Write the output of this [`VariableCurve`] at `seek_time` to a [`Transform`] component, with influence, `influence`. + /// Write the output of this [`VariableCurve`] at `seek_time` to a [`Transform`] component with influence `influence`. pub fn write_to_transform(&self, seek_time: f32, influence: f32, transform: &mut Transform) { let Some((idx, fac, step_duration)) = (match self.find_current_keyframe(seek_time) { Some(idx) => (|| {