Skip to main content

bevy_animation/
animatable.rs

1//! Traits and type for interpolating between values.
2
3use crate::util;
4use bevy_color::{Laba, LinearRgba, Oklaba, Srgba, Xyza};
5use bevy_math::*;
6use bevy_reflect::Reflect;
7use bevy_transform::prelude::Transform;
8
9/// An individual input for [`Animatable::blend`].
10pub struct BlendInput<T> {
11    /// The individual item's weight. This may not be bound to the range `[0.0, 1.0]`.
12    pub weight: f32,
13    /// The input value to be blended.
14    pub value: T,
15    /// Whether or not to additively blend this input into the final result.
16    pub additive: bool,
17}
18
19/// An animatable value type.
20pub trait Animatable: Reflect + Sized + Send + Sync + 'static {
21    /// Interpolates between `a` and `b` with an interpolation factor of `time`.
22    ///
23    /// The `time` parameter here may not be clamped to the range `[0.0, 1.0]`.
24    fn interpolate(a: &Self, b: &Self, time: f32) -> Self;
25
26    /// Blends one or more values together.
27    ///
28    /// Implementors should return a default value when no inputs are provided here.
29    fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self;
30}
31
32macro_rules! impl_float_animatable {
33    ($ty: ty, $base: ty) => {
34        impl Animatable for $ty {
35            #[inline]
36            fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
37                let t = <$base>::from(t);
38                (*a) * (1.0 - t) + (*b) * t
39            }
40
41            #[inline]
42            fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
43                let mut value = Default::default();
44                for input in inputs {
45                    if input.additive {
46                        value += <$base>::from(input.weight) * input.value;
47                    } else {
48                        value = Self::interpolate(&value, &input.value, input.weight);
49                    }
50                }
51                value
52            }
53        }
54    };
55}
56
57macro_rules! impl_color_animatable {
58    ($ty: ident) => {
59        impl Animatable for $ty {
60            #[inline]
61            fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
62                let value = *a * (1. - t) + *b * t;
63                value
64            }
65
66            #[inline]
67            fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
68                let mut value = Default::default();
69                for input in inputs {
70                    if input.additive {
71                        value += input.weight * input.value;
72                    } else {
73                        value = Self::interpolate(&value, &input.value, input.weight);
74                    }
75                }
76                value
77            }
78        }
79    };
80}
81
82impl_float_animatable!(f32, f32);
83impl_float_animatable!(Vec2, f32);
84impl_float_animatable!(Vec3A, f32);
85impl_float_animatable!(Vec4, f32);
86
87impl_float_animatable!(f64, f64);
88impl_float_animatable!(DVec2, f64);
89impl_float_animatable!(DVec3, f64);
90impl_float_animatable!(DVec4, f64);
91
92impl_color_animatable!(LinearRgba);
93impl_color_animatable!(Laba);
94impl_color_animatable!(Oklaba);
95impl_color_animatable!(Srgba);
96impl_color_animatable!(Xyza);
97
98// Vec3 is special cased to use Vec3A internally for blending
99impl Animatable for Vec3 {
100    #[inline]
101    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
102        (*a) * (1.0 - t) + (*b) * t
103    }
104
105    #[inline]
106    fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
107        let mut value = Vec3A::ZERO;
108        for input in inputs {
109            if input.additive {
110                value += input.weight * Vec3A::from(input.value);
111            } else {
112                value = Vec3A::interpolate(&value, &Vec3A::from(input.value), input.weight);
113            }
114        }
115        Self::from(value)
116    }
117}
118
119impl Animatable for bool {
120    #[inline]
121    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
122        util::step_unclamped(*a, *b, t)
123    }
124
125    #[inline]
126    fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
127        inputs
128            .max_by_key(|x| FloatOrd(x.weight))
129            .is_some_and(|input| input.value)
130    }
131}
132
133impl Animatable for Transform {
134    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
135        Self {
136            translation: Vec3::interpolate(&a.translation, &b.translation, t),
137            rotation: Quat::interpolate(&a.rotation, &b.rotation, t),
138            scale: Vec3::interpolate(&a.scale, &b.scale, t),
139        }
140    }
141
142    fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
143        let mut translation = Vec3A::ZERO;
144        let mut scale = Vec3A::ZERO;
145        let mut rotation = Quat::IDENTITY;
146
147        for input in inputs {
148            if input.additive {
149                translation += input.weight * Vec3A::from(input.value.translation);
150                scale += input.weight * Vec3A::from(input.value.scale);
151                rotation =
152                    Quat::slerp(Quat::IDENTITY, input.value.rotation, input.weight) * rotation;
153            } else {
154                translation = Vec3A::interpolate(
155                    &translation,
156                    &Vec3A::from(input.value.translation),
157                    input.weight,
158                );
159                scale = Vec3A::interpolate(&scale, &Vec3A::from(input.value.scale), input.weight);
160                rotation = Quat::interpolate(&rotation, &input.value.rotation, input.weight);
161            }
162        }
163
164        Self {
165            translation: Vec3::from(translation),
166            rotation,
167            scale: Vec3::from(scale),
168        }
169    }
170}
171
172impl Animatable for Quat {
173    /// Performs a slerp to smoothly interpolate between quaternions.
174    #[inline]
175    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
176        // We want to smoothly interpolate between the two quaternions by default,
177        // rather than using a quicker but less correct linear interpolation.
178        a.slerp(*b, t)
179    }
180
181    #[inline]
182    fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
183        let mut value = Self::IDENTITY;
184        for BlendInput {
185            weight,
186            value: incoming_value,
187            additive,
188        } in inputs
189        {
190            if additive {
191                value = Self::slerp(Self::IDENTITY, incoming_value, weight) * value;
192            } else {
193                value = Self::interpolate(&value, &incoming_value, weight);
194            }
195        }
196        value
197    }
198}
199
200/// Evaluates a cubic Bézier curve at a value `t`, given two endpoints and the
201/// derivatives at those endpoints.
202///
203/// The derivatives are linearly scaled by `duration`.
204pub fn interpolate_with_cubic_bezier<T>(p0: &T, d0: &T, d3: &T, p3: &T, t: f32, duration: f32) -> T
205where
206    T: Animatable + Clone,
207{
208    // We're given two endpoints, along with the derivatives at those endpoints,
209    // and have to evaluate the cubic Bézier curve at time t using only
210    // (additive) blending and linear interpolation.
211    //
212    // Evaluating a Bézier curve via repeated linear interpolation when the
213    // control points are known is straightforward via [de Casteljau
214    // subdivision]. So the only remaining problem is to get the two off-curve
215    // control points. The [derivative of the cubic Bézier curve] is:
216    //
217    //      B′(t) = 3(1 - t)²(P₁ - P₀) + 6(1 - t)t(P₂ - P₁) + 3t²(P₃ - P₂)
218    //
219    // Setting t = 0 and t = 1 and solving gives us:
220    //
221    //      P₁ = P₀ + B′(0) / 3
222    //      P₂ = P₃ - B′(1) / 3
223    //
224    // These P₁ and P₂ formulas can be expressed as additive blends.
225    //
226    // So, to sum up, first we calculate the off-curve control points via
227    // additive blending, and then we use repeated linear interpolation to
228    // evaluate the curve.
229    //
230    // [de Casteljau subdivision]: https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
231    // [derivative of the cubic Bézier curve]: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B%C3%A9zier_curves
232
233    // Compute control points from derivatives.
234    let p1 = T::blend(
235        [
236            BlendInput {
237                weight: duration / 3.0,
238                value: (*d0).clone(),
239                additive: true,
240            },
241            BlendInput {
242                weight: 1.0,
243                value: (*p0).clone(),
244                additive: true,
245            },
246        ]
247        .into_iter(),
248    );
249    let p2 = T::blend(
250        [
251            BlendInput {
252                weight: duration / -3.0,
253                value: (*d3).clone(),
254                additive: true,
255            },
256            BlendInput {
257                weight: 1.0,
258                value: (*p3).clone(),
259                additive: true,
260            },
261        ]
262        .into_iter(),
263    );
264
265    // Use de Casteljau subdivision to evaluate.
266    let p0p1 = T::interpolate(p0, &p1, t);
267    let p1p2 = T::interpolate(&p1, &p2, t);
268    let p2p3 = T::interpolate(&p2, p3, t);
269    let p0p1p2 = T::interpolate(&p0p1, &p1p2, t);
270    let p1p2p3 = T::interpolate(&p1p2, &p2p3, t);
271    T::interpolate(&p0p1p2, &p1p2p3, t)
272}