Skip to content

Commit 602515d

Browse files
james7132kirusfgmockersftim-blackbirdalice-i-cecile
authored
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 <[email protected]> Co-authored-by: François <[email protected]> Co-authored-by: irate <[email protected]> Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Alice Cecile <[email protected]>
1 parent c859eac commit 602515d

File tree

3 files changed

+177
-2
lines changed

3 files changed

+177
-2
lines changed
+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
use crate::util;
2+
use bevy_ecs::world::World;
3+
use bevy_math::*;
4+
use bevy_reflect::Reflect;
5+
use bevy_transform::prelude::Transform;
6+
use bevy_utils::FloatOrd;
7+
8+
/// An individual input for [`Animatable::blend`].
9+
pub struct BlendInput<T> {
10+
/// The individual item's weight. This may not be bound to the range `[0.0, 1.0]`.
11+
pub weight: f32,
12+
/// The input value to be blended.
13+
pub value: T,
14+
/// Whether or not to additively blend this input into the final result.
15+
pub additive: bool,
16+
}
17+
18+
/// An animatable value type.
19+
pub trait Animatable: Reflect + Sized + Send + Sync + 'static {
20+
/// Interpolates between `a` and `b` with a interpolation factor of `time`.
21+
///
22+
/// The `time` parameter here may not be clamped to the range `[0.0, 1.0]`.
23+
fn interpolate(a: &Self, b: &Self, time: f32) -> Self;
24+
25+
/// Blends one or more values together.
26+
///
27+
/// Implementors should return a default value when no inputs are provided here.
28+
fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self;
29+
30+
/// Post-processes the value using resources in the [`World`].
31+
/// Most animatable types do not need to implement this.
32+
fn post_process(&mut self, _world: &World) {}
33+
}
34+
35+
macro_rules! impl_float_animatable {
36+
($ty: ty, $base: ty) => {
37+
impl Animatable for $ty {
38+
#[inline]
39+
fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
40+
let t = <$base>::from(t);
41+
(*a) * (1.0 - t) + (*b) * t
42+
}
43+
44+
#[inline]
45+
fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
46+
let mut value = Default::default();
47+
for input in inputs {
48+
if input.additive {
49+
value += <$base>::from(input.weight) * input.value;
50+
} else {
51+
value = Self::interpolate(&value, &input.value, input.weight);
52+
}
53+
}
54+
value
55+
}
56+
}
57+
};
58+
}
59+
60+
impl_float_animatable!(f32, f32);
61+
impl_float_animatable!(Vec2, f32);
62+
impl_float_animatable!(Vec3A, f32);
63+
impl_float_animatable!(Vec4, f32);
64+
65+
impl_float_animatable!(f64, f64);
66+
impl_float_animatable!(DVec2, f64);
67+
impl_float_animatable!(DVec3, f64);
68+
impl_float_animatable!(DVec4, f64);
69+
70+
// Vec3 is special cased to use Vec3A internally for blending
71+
impl Animatable for Vec3 {
72+
#[inline]
73+
fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
74+
(*a) * (1.0 - t) + (*b) * t
75+
}
76+
77+
#[inline]
78+
fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
79+
let mut value = Vec3A::ZERO;
80+
for input in inputs {
81+
if input.additive {
82+
value += input.weight * Vec3A::from(input.value);
83+
} else {
84+
value = Vec3A::interpolate(&value, &Vec3A::from(input.value), input.weight);
85+
}
86+
}
87+
Self::from(value)
88+
}
89+
}
90+
91+
impl Animatable for bool {
92+
#[inline]
93+
fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
94+
util::step_unclamped(*a, *b, t)
95+
}
96+
97+
#[inline]
98+
fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
99+
inputs
100+
.max_by(|a, b| FloatOrd(a.weight).cmp(&FloatOrd(b.weight)))
101+
.map(|input| input.value)
102+
.unwrap_or(false)
103+
}
104+
}
105+
106+
impl Animatable for Transform {
107+
fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
108+
Self {
109+
translation: Vec3::interpolate(&a.translation, &b.translation, t),
110+
rotation: Quat::interpolate(&a.rotation, &b.rotation, t),
111+
scale: Vec3::interpolate(&a.scale, &b.scale, t),
112+
}
113+
}
114+
115+
fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
116+
let mut translation = Vec3A::ZERO;
117+
let mut scale = Vec3A::ZERO;
118+
let mut rotation = Quat::IDENTITY;
119+
120+
for input in inputs {
121+
if input.additive {
122+
translation += input.weight * Vec3A::from(input.value.translation);
123+
scale += input.weight * Vec3A::from(input.value.scale);
124+
rotation = rotation.slerp(input.value.rotation, input.weight);
125+
} else {
126+
translation = Vec3A::interpolate(
127+
&translation,
128+
&Vec3A::from(input.value.translation),
129+
input.weight,
130+
);
131+
scale = Vec3A::interpolate(&scale, &Vec3A::from(input.value.scale), input.weight);
132+
rotation = Quat::interpolate(&rotation, &input.value.rotation, input.weight);
133+
}
134+
}
135+
136+
Self {
137+
translation: Vec3::from(translation),
138+
rotation,
139+
scale: Vec3::from(scale),
140+
}
141+
}
142+
}
143+
144+
impl Animatable for Quat {
145+
/// Performs an nlerp, because it's cheaper and easier to combine with other animations,
146+
/// reference: <http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/>
147+
#[inline]
148+
fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
149+
// We want to smoothly interpolate between the two quaternions by default,
150+
// rather than using a quicker but less correct linear interpolation.
151+
a.slerp(*b, t)
152+
}
153+
154+
#[inline]
155+
fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
156+
let mut value = Self::IDENTITY;
157+
for input in inputs {
158+
value = Self::interpolate(&value, &input.value, input.weight);
159+
}
160+
value
161+
}
162+
}

crates/bevy_animation/src/lib.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
33
#![warn(missing_docs)]
44

5+
mod animatable;
6+
mod util;
7+
58
use std::ops::{Add, Deref, Mul};
69
use std::time::Duration;
710

@@ -21,8 +24,8 @@ use bevy_utils::{tracing::warn, HashMap};
2124
pub mod prelude {
2225
#[doc(hidden)]
2326
pub use crate::{
24-
AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Interpolation, Keyframes,
25-
VariableCurve,
27+
animatable::*, AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Interpolation,
28+
Keyframes, VariableCurve,
2629
};
2730
}
2831

crates/bevy_animation/src/util.rs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// Steps between two different discrete values of any type.
2+
/// Returns `a` if `t < 1.0`, otherwise returns `b`.
3+
#[inline]
4+
pub(crate) fn step_unclamped<T>(a: T, b: T, t: f32) -> T {
5+
if t < 1.0 {
6+
a
7+
} else {
8+
b
9+
}
10+
}

0 commit comments

Comments
 (0)