-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Animatable trait for interpolation and blending (#4482)
# Objective Allow animation of types other than translation, scale, and rotation on `Transforms`. ## Solution Add a base trait for all values that can be animated by the animation system. This provides the basic operations for sampling and blending animation values for more than just translation, rotation, and scale. This implements part of bevyengine/rfcs#51, but is missing the implementations for `Range<T>` and `Color`. This also does not fully integrate with the existing `AnimationPlayer` yet, just setting up the trait. --------- Co-authored-by: Kirillov Kirill <kirusfg@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: irate <JustTheCoolDude@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecil@gmail.com>
- Loading branch information
1 parent
c859eac
commit 602515d
Showing
3 changed files
with
177 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<T> { | ||
/// 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<Item = BlendInput<Self>>) -> 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<Item = BlendInput<Self>>) -> 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<Item = BlendInput<Self>>) -> 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<Item = BlendInput<Self>>) -> 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<Item = BlendInput<Self>>) -> 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: <http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/> | ||
#[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<Item = BlendInput<Self>>) -> Self { | ||
let mut value = Self::IDENTITY; | ||
for input in inputs { | ||
value = Self::interpolate(&value, &input.value, input.weight); | ||
} | ||
value | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<T>(a: T, b: T, t: f32) -> T { | ||
if t < 1.0 { | ||
a | ||
} else { | ||
b | ||
} | ||
} |