diff --git a/crates/bevy_animation/src/animatable.rs b/crates/bevy_animation/src/animatable.rs new file mode 100644 index 00000000000000..24b90316faa35d --- /dev/null +++ b/crates/bevy_animation/src/animatable.rs @@ -0,0 +1,162 @@ +use crate::util; +use bevy_ecs::world::World; +use bevy_math::*; +use bevy_reflect::Reflect; +use bevy_transform::prelude::Transform; +use bevy_utils::FloatOrd; + +/// An individual input for [`Animatable::blend`]. +pub struct BlendInput { + /// The individual item's weight. This may not be bound to the range `[0.0, 1.0]`. + pub weight: f32, + /// The input value to be blended. + pub value: T, + /// Whether or not to additively blend this input into the final result. + pub additive: bool, +} + +/// An animatable value type. +pub trait Animatable: Reflect + Sized + Send + Sync + 'static { + /// Interpolates between `a` and `b` with a interpolation factor of `time`. + /// + /// The `time` parameter here may not be clamped to the range `[0.0, 1.0]`. + fn interpolate(a: &Self, b: &Self, time: f32) -> Self; + + /// Blends one or more values together. + /// + /// Implementors should return a default value when no inputs are provided here. + fn blend(inputs: impl Iterator>) -> Self; + + /// Post-processes the value using resources in the [`World`]. + /// Most animatable types do not need to implement this. + fn post_process(&mut self, _world: &World) {} +} + +macro_rules! impl_float_animatable { + ($ty: ty, $base: ty) => { + impl Animatable for $ty { + #[inline] + fn interpolate(a: &Self, b: &Self, t: f32) -> Self { + let t = <$base>::from(t); + (*a) * (1.0 - t) + (*b) * t + } + + #[inline] + fn blend(inputs: impl Iterator>) -> Self { + let mut value = Default::default(); + for input in inputs { + if input.additive { + value += <$base>::from(input.weight) * input.value; + } else { + value = Self::interpolate(&value, &input.value, input.weight); + } + } + value + } + } + }; +} + +impl_float_animatable!(f32, f32); +impl_float_animatable!(Vec2, f32); +impl_float_animatable!(Vec3A, f32); +impl_float_animatable!(Vec4, f32); + +impl_float_animatable!(f64, f64); +impl_float_animatable!(DVec2, f64); +impl_float_animatable!(DVec3, f64); +impl_float_animatable!(DVec4, f64); + +// Vec3 is special cased to use Vec3A internally for blending +impl Animatable for Vec3 { + #[inline] + fn interpolate(a: &Self, b: &Self, t: f32) -> Self { + (*a) * (1.0 - t) + (*b) * t + } + + #[inline] + fn blend(inputs: impl Iterator>) -> Self { + let mut value = Vec3A::ZERO; + for input in inputs { + if input.additive { + value += input.weight * Vec3A::from(input.value); + } else { + value = Vec3A::interpolate(&value, &Vec3A::from(input.value), input.weight); + } + } + Self::from(value) + } +} + +impl Animatable for bool { + #[inline] + fn interpolate(a: &Self, b: &Self, t: f32) -> Self { + util::step_unclamped(*a, *b, t) + } + + #[inline] + fn blend(inputs: impl Iterator>) -> Self { + inputs + .max_by(|a, b| FloatOrd(a.weight).cmp(&FloatOrd(b.weight))) + .map(|input| input.value) + .unwrap_or(false) + } +} + +impl Animatable for Transform { + fn interpolate(a: &Self, b: &Self, t: f32) -> Self { + Self { + translation: Vec3::interpolate(&a.translation, &b.translation, t), + rotation: Quat::interpolate(&a.rotation, &b.rotation, t), + scale: Vec3::interpolate(&a.scale, &b.scale, t), + } + } + + fn blend(inputs: impl Iterator>) -> Self { + let mut translation = Vec3A::ZERO; + let mut scale = Vec3A::ZERO; + let mut rotation = Quat::IDENTITY; + + for input in inputs { + if input.additive { + translation += input.weight * Vec3A::from(input.value.translation); + scale += input.weight * Vec3A::from(input.value.scale); + rotation = rotation.slerp(input.value.rotation, input.weight); + } else { + translation = Vec3A::interpolate( + &translation, + &Vec3A::from(input.value.translation), + input.weight, + ); + scale = Vec3A::interpolate(&scale, &Vec3A::from(input.value.scale), input.weight); + rotation = Quat::interpolate(&rotation, &input.value.rotation, input.weight); + } + } + + Self { + translation: Vec3::from(translation), + rotation, + scale: Vec3::from(scale), + } + } +} + +impl Animatable for Quat { + /// Performs an nlerp, because it's cheaper and easier to combine with other animations, + /// reference: + #[inline] + fn interpolate(a: &Self, b: &Self, t: f32) -> Self { + // We want to smoothly interpolate between the two quaternions by default, + // rather than using a quicker but less correct linear interpolation. + a.slerp(*b, t) + } + + #[inline] + fn blend(inputs: impl Iterator>) -> Self { + let mut value = Self::IDENTITY; + for input in inputs { + value = Self::interpolate(&value, &input.value, input.weight); + } + value + } +} diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 05e6921e0afcd0..43d66fd2d81bbb 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -2,6 +2,9 @@ #![warn(missing_docs)] +mod animatable; +mod util; + use std::ops::{Add, Deref, Mul}; use std::time::Duration; @@ -21,8 +24,8 @@ use bevy_utils::{tracing::warn, HashMap}; pub mod prelude { #[doc(hidden)] pub use crate::{ - AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Interpolation, Keyframes, - VariableCurve, + animatable::*, AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Interpolation, + Keyframes, VariableCurve, }; } diff --git a/crates/bevy_animation/src/util.rs b/crates/bevy_animation/src/util.rs new file mode 100644 index 00000000000000..67aaf8116e365c --- /dev/null +++ b/crates/bevy_animation/src/util.rs @@ -0,0 +1,10 @@ +/// Steps between two different discrete values of any type. +/// Returns `a` if `t < 1.0`, otherwise returns `b`. +#[inline] +pub(crate) fn step_unclamped(a: T, b: T, t: f32) -> T { + if t < 1.0 { + a + } else { + b + } +}