Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_animation/src/animatable.rs
6595 views
1
//! Traits and type for interpolating between values.
2
3
use crate::util;
4
use bevy_color::{Laba, LinearRgba, Oklaba, Srgba, Xyza};
5
use bevy_math::*;
6
use bevy_reflect::Reflect;
7
use bevy_transform::prelude::Transform;
8
9
/// An individual input for [`Animatable::blend`].
10
pub 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.
20
pub 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
32
macro_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
57
macro_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
82
impl_float_animatable!(f32, f32);
83
impl_float_animatable!(Vec2, f32);
84
impl_float_animatable!(Vec3A, f32);
85
impl_float_animatable!(Vec4, f32);
86
87
impl_float_animatable!(f64, f64);
88
impl_float_animatable!(DVec2, f64);
89
impl_float_animatable!(DVec3, f64);
90
impl_float_animatable!(DVec4, f64);
91
92
impl_color_animatable!(LinearRgba);
93
impl_color_animatable!(Laba);
94
impl_color_animatable!(Oklaba);
95
impl_color_animatable!(Srgba);
96
impl_color_animatable!(Xyza);
97
98
// Vec3 is special cased to use Vec3A internally for blending
99
impl 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
119
impl 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
133
impl 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
172
impl 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`.
204
pub fn interpolate_with_cubic_bezier<T>(p0: &T, d0: &T, d3: &T, p3: &T, t: f32, duration: f32) -> T
205
where
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
}
273
274