bevy_transform_interpolation/hermite.rs
1//! Hermite interpolation for [`Transform`] easing.
2
3use core::{f32::consts::TAU, marker::PhantomData};
4
5use bevy::prelude::*;
6use ops::FloatPow;
7
8use crate::{
9 NoRotationEasing, NoTranslationEasing, NonlinearRotationEasing, NonlinearTranslationEasing,
10 RotationEasingState, TransformEasingSet, TranslationEasingState, VelocitySource,
11 VelocitySourceItem,
12};
13
14/// A Hermite interpolation plugin for [`Transform`] easing.
15///
16/// By default, [`TransformInterpolationPlugin`] and [`TransformExtrapolationPlugin`]
17/// use *linear interpolation* (`lerp`) for easing translation and scale,
18/// and *spherical linear interpolation* (`slerp`) for easing rotation.
19/// This is computationally efficient and works well for most cases.
20///
21/// However, for more accurate and reliable easing that works at arbitrary velocities,
22/// it may be preferable to use *Hermite interpolation*. It uses both position and velocity information
23/// to estimate the trajectories of entities, producing smoother results.
24///
25/// This plugin should be used alongside the [`TransformInterpolationPlugin`] and/or [`TransformExtrapolationPlugin`].
26/// The [`TransformEasingPlugin`] is also required, and it is automatically added if not already present in the app.
27///
28/// [`TransformInterpolationPlugin`]: crate::interpolation::TransformInterpolationPlugin
29/// [`TransformExtrapolationPlugin`]: crate::extrapolation::TransformExtrapolationPlugin
30/// [`TransformEasingPlugin`]: crate::TransformEasingPlugin
31///
32/// # Usage
33///
34/// Hermite interpolation requires velocity to produce accurate curves.
35/// Instead of providing its own velocity components, the [`TransformHermiteEasingPlugin`]
36/// lets you specify your own velocity components that you manage yourself.
37///
38/// First, make sure you have components for the previous and current velocity, and implement
39/// the [`VelocitySource`] trait on a [`QueryData`] type:
40///
41/// ```
42/// use bevy::{ecs::query::QueryData, prelude::*};
43/// use bevy_transform_interpolation::VelocitySource;
44///
45/// #[derive(Component, Default)]
46/// struct PreviousLinearVelocity(Vec3);
47///
48/// #[derive(Component, Default)]
49/// struct PreviousAngularVelocity(Vec3);
50///
51/// #[derive(Component, Default)]
52/// struct LinearVelocity(Vec3);
53///
54/// #[derive(Component, Default)]
55/// struct AngularVelocity(Vec3);
56///
57/// #[derive(QueryData)]
58/// struct LinVelSource;
59///
60/// impl VelocitySource for LinVelSource {
61/// // Components storing the previous and current velocities.
62/// type Previous = PreviousLinearVelocity;
63/// type Current = LinearVelocity;
64///
65/// fn previous(start: &Self::Previous) -> Vec3 {
66/// start.0
67/// }
68///
69/// fn current(end: &Self::Current) -> Vec3 {
70/// end.0
71/// }
72/// }
73///
74/// #[derive(QueryData)]
75/// struct AngVelSource;
76///
77/// impl VelocitySource for AngVelSource {
78/// type Previous = PreviousAngularVelocity;
79/// type Current = AngularVelocity;
80///
81/// fn previous(start: &Self::Previous) -> Vec3 {
82/// start.0
83/// }
84///
85/// fn current(end: &Self::Current) -> Vec3 {
86/// end.0
87/// }
88/// }
89/// ```
90///
91/// Then, add the [`TransformHermiteEasingPlugin`] to the app with the velocity sources,
92/// along with the [`TransformInterpolationPlugin`] and/or [`TransformExtrapolationPlugin`]:
93///
94/// ```no_run
95/// use bevy::{ecs::query::QueryData, prelude::*};
96/// use bevy_transform_interpolation::{prelude::*, VelocitySource};
97/// #
98/// # #[derive(Component, Default)]
99/// # struct PreviousLinearVelocity(Vec3);
100/// #
101/// # #[derive(Component, Default)]
102/// # struct PreviousAngularVelocity(Vec3);
103/// #
104/// # #[derive(Component, Default)]
105/// # struct LinearVelocity(Vec3);
106/// #
107/// # #[derive(Component, Default)]
108/// # struct AngularVelocity(Vec3);
109/// #
110/// # #[derive(QueryData)]
111/// # struct LinVelSource;
112/// #
113/// # impl VelocitySource for LinVelSource {
114/// # // Components storing the previous and current velocities.
115/// # type Previous = PreviousLinearVelocity;
116/// # type Current = LinearVelocity;
117/// #
118/// # fn previous(start: &Self::Previous) -> Vec3 {
119/// # start.0
120/// # }
121/// #
122/// # fn current(end: &Self::Current) -> Vec3 {
123/// # end.0
124/// # }
125/// # }
126/// #
127/// # #[derive(QueryData)]
128/// # struct AngVelSource;
129/// #
130/// # impl VelocitySource for AngVelSource {
131/// # type Previous = PreviousAngularVelocity;
132/// # type Current = AngularVelocity;
133/// #
134/// # fn previous(start: &Self::Previous) -> Vec3 {
135/// # start.0
136/// # }
137/// #
138/// # fn current(end: &Self::Current) -> Vec3 {
139/// # end.0
140/// # }
141/// # }
142///
143/// fn main() {
144/// let mut app = App::new();
145///
146/// app.add_plugins((
147/// TransformInterpolationPlugin::default(),
148/// TransformHermiteEasingPlugin::<LinVelSource, AngVelSource>::default(),
149/// # bevy::time::TimePlugin::default(),
150/// ));
151///
152/// // Optional: Insert velocity components automatically for entities with Hermite interpolation.
153/// app.register_required_components::<TranslationHermiteEasing, LinearVelocity>();
154/// app.register_required_components::<TranslationHermiteEasing, PreviousLinearVelocity>();
155/// app.register_required_components::<RotationHermiteEasing, AngularVelocity>();
156/// app.register_required_components::<RotationHermiteEasing, PreviousAngularVelocity>();
157///
158/// // ...
159///
160/// app.run();
161/// }
162/// ```
163///
164/// Hermite interpolation can now be used for any interpolated or extrapolated entity
165/// that has the velocity components by adding the [`TransformHermiteEasing`] component:
166///
167/// ```
168/// # use bevy::prelude::*;
169/// # use bevy_transform_interpolation::prelude::*;
170/// #
171/// fn setup(mut commands: Commands) {
172/// // Use Hermite interpolation for interpolating translation and rotation.
173/// commands.spawn((
174/// Transform::default(),
175/// TransformInterpolation,
176/// TransformHermiteEasing,
177/// ));
178/// }
179/// ```
180///
181/// Hermite interpolation can also be used for translation and rotation separately:
182///
183/// ```
184/// # use bevy::prelude::*;
185/// # use bevy_transform_interpolation::prelude::*;
186/// #
187/// fn setup(mut commands: Commands) {
188/// // Use Hermite interpolation for interpolating translation.
189/// commands.spawn((
190/// Transform::default(),
191/// TranslationInterpolation,
192/// TranslationHermiteEasing,
193/// ));
194///
195/// // Use Hermite interpolation for interpolating rotation.
196/// commands.spawn((
197/// Transform::default(),
198/// RotationInterpolation,
199/// RotationHermiteEasing,
200/// ));
201/// }
202/// ```
203///
204/// [`QueryData`]: bevy::ecs::query::QueryData
205#[derive(Debug)]
206pub struct TransformHermiteEasingPlugin<LinVel: VelocitySource, AngVel: VelocitySource>(
207 PhantomData<LinVel>,
208 PhantomData<AngVel>,
209);
210
211impl<LinVel: VelocitySource, AngVel: VelocitySource> Default
212 for TransformHermiteEasingPlugin<LinVel, AngVel>
213{
214 fn default() -> Self {
215 Self(PhantomData, PhantomData)
216 }
217}
218
219impl<LinVel: VelocitySource, AngVel: VelocitySource> Plugin
220 for TransformHermiteEasingPlugin<LinVel, AngVel>
221{
222 fn build(&self, app: &mut App) {
223 // Register components.
224 app.register_type::<(
225 TransformHermiteEasing,
226 TranslationHermiteEasing,
227 RotationHermiteEasing,
228 )>();
229
230 // Mark entities with Hermite interpolation as having nonlinear easing to disable linear easing.
231 let _ = app
232 .try_register_required_components::<TranslationHermiteEasing, NonlinearTranslationEasing>();
233 let _ = app
234 .try_register_required_components::<RotationHermiteEasing, NonlinearRotationEasing>();
235
236 // Perform easing.
237 app.add_systems(
238 RunFixedMainLoop,
239 (
240 ease_translation_hermite::<LinVel>,
241 ease_rotation_hermite::<AngVel>,
242 )
243 .in_set(TransformEasingSet::Ease),
244 );
245 }
246}
247
248/// Enables [Hermite interpolation](TransformHermiteEasingPlugin) for the easing of the [`Transform`] of an entity.
249/// Must be used together with either [`TransformInterpolation`] or [`TransformExtrapolation`].
250///
251/// For the interpolation to work, the entity must have velocity components that are updated every frame,
252/// and the app must have a [`TransformHermiteEasingPlugin`] with the appropriate velocity sources added.
253///
254/// See the [`TransformHermiteEasingPlugin`] for more information.
255///
256/// [`TransformInterpolation`]: crate::interpolation::TransformInterpolation
257/// [`TransformExtrapolation`]: crate::extrapolation::TransformExtrapolation
258#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
259#[reflect(Component, Debug, Default)]
260#[require(TranslationHermiteEasing, RotationHermiteEasing)]
261pub struct TransformHermiteEasing;
262
263/// Enables [Hermite interpolation](TransformHermiteEasingPlugin) for the easing of the translation of an entity.
264/// Must be used together with [`TranslationInterpolation`] or [`TranslationExtrapolation`].
265///
266/// For the interpolation to work, the entity must have a linear velocity component that is updated every frame,
267/// and the app must have a [`TransformHermiteEasingPlugin`] with the appropriate velocity source added.
268///
269/// See the [`TransformHermiteEasingPlugin`] for more information.
270///
271/// [`TranslationInterpolation`]: crate::interpolation::TranslationInterpolation
272/// [`TranslationExtrapolation`]: crate::extrapolation::TranslationExtrapolation
273#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
274#[reflect(Component, Debug, Default)]
275pub struct TranslationHermiteEasing;
276
277/// Enables [Hermite interpolation](TransformHermiteEasingPlugin) for the easing of the rotation of an entity.
278/// Must be used together with [`RotationInterpolation`] or [`RotationExtrapolation`].
279///
280/// For the interpolation to work, the entity must have an angular velocity component that is updated every frame,
281/// and the app must have a [`TransformHermiteEasingPlugin`] with the appropriate velocity source added.
282///
283/// See the [`TransformHermiteEasingPlugin`] for more information.
284///
285/// [`RotationInterpolation`]: crate::interpolation::RotationInterpolation
286/// [`RotationExtrapolation`]: crate::extrapolation::RotationExtrapolation
287#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
288#[reflect(Component, Debug, Default)]
289pub struct RotationHermiteEasing;
290
291/// Eases the translations of entities with Hermite interpolation.
292fn ease_translation_hermite<V: VelocitySource>(
293 mut query: Query<
294 (
295 &mut Transform,
296 &TranslationEasingState,
297 &V::Previous,
298 &V::Current,
299 ),
300 Without<NoTranslationEasing>,
301 >,
302 time: Res<Time<Fixed>>,
303) {
304 let overstep = time.overstep_fraction();
305 let delta_secs = time.delta_secs();
306
307 query
308 .par_iter_mut()
309 .for_each(|(mut transform, interpolation, start_vel, end_vel)| {
310 if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) {
311 let vel0 = <V::Item<'static> as VelocitySourceItem<V>>::previous(start_vel);
312 let vel1 = <V::Item<'static> as VelocitySourceItem<V>>::current(end_vel);
313 transform.translation =
314 hermite_vec3(start, end, delta_secs * vel0, delta_secs * vel1, overstep);
315 }
316 });
317}
318
319/// Eases the rotations of entities with Hermite interpolation.
320fn ease_rotation_hermite<V: VelocitySource>(
321 mut query: Query<
322 (
323 &mut Transform,
324 &RotationEasingState,
325 &V::Previous,
326 &V::Current,
327 ),
328 Without<NoRotationEasing>,
329 >,
330 time: Res<Time<Fixed>>,
331) {
332 let overstep = time.overstep_fraction();
333 let delta_secs = time.delta_secs();
334
335 query
336 .par_iter_mut()
337 .for_each(|(mut transform, interpolation, start_vel, end_vel)| {
338 if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) {
339 let vel0 = <V::Item<'static> as VelocitySourceItem<V>>::previous(start_vel);
340 let vel1 = <V::Item<'static> as VelocitySourceItem<V>>::current(end_vel);
341 transform.rotation = hermite_quat(
342 start,
343 end,
344 delta_secs * vel0,
345 delta_secs * vel1,
346 overstep,
347 true,
348 );
349 }
350 });
351}
352
353/// Performs a cubic Hermite interpolation between two vectors `p0` and `p1` with velocities `v0` and `v1`
354/// based on the value at `t`.
355///
356/// When `t` is `0.0`, the result will be equal to `p0`. When `t` is `1.0`, the result will be equal to `p1`.
357pub fn hermite_vec3(p0: Vec3, p1: Vec3, v0: Vec3, v1: Vec3, t: f32) -> Vec3 {
358 // Reference:
359 //
360 // Holden, D. "Cubic Interpolation of Quaternions"
361 // https://theorangeduck.com/page/cubic-interpolation-quaternions
362 //
363 // The article is mostly about quaternions, but also describes Hermite interpolation for vectors.
364 // For quaternions, we use a different approach. See `hermite_quat`.
365
366 let t2 = t * t;
367 let t3 = t2 * t;
368
369 // Polynomial coefficients
370 let b0 = 2.0 * t3 - 3.0 * t2 + 1.0;
371 let b1 = 3.0 * t2 - 2.0 * t3;
372 let b2 = t3 - 2.0 * t2 + t;
373 let b3 = t3 - t2;
374
375 b0 * p0 + b1 * p1 + b2 * v0 + b3 * v1
376}
377
378/// Performs a cubic Hermite interpolation between quaternions `q0` and `q1`
379/// with angular velocities `w0` and `w1` based on the value at `t`.
380///
381/// Both quaternions and angular velocities should be in the global frame.
382/// The angular velocities should be normalized such that they represent the angle of rotation
383/// over the time step.
384///
385/// When `t` is `0.0`, the result will be equal to `q0`. When `t` is `1.0`, the result will be equal to `q1`.
386///
387/// If `unwrap` is `true`, the interpolation will work for arbitrarily large velocities
388/// and handle multiple full revolutions correctly. This is a bit more expensive,
389/// but can be important for high angular velocities.
390pub fn hermite_quat(qa: Quat, qb: Quat, w0: Vec3, w1: Vec3, t: f32, unwrap: bool) -> Quat {
391 // Reference:
392 //
393 // Kim M.-J. et al. "A General Construction Scheme for Unit Quaternion Curves with Simple High Order Derivatives".
394 // http://graphics.cs.cmu.edu/nsp/course/15-464/Fall05/papers/kimKimShin.pdf
395 //
396 // Note that the paper's angular velocities are defined in the local frame, but our values
397 // are in the global frame, so the order of multiplication for quaternions is reversed.
398
399 let t2 = t * t;
400 let t3 = t * t2;
401
402 // Cumulative Bernstein basis polynomials
403 let b1 = 1.0 - (1.0 - t).cubed();
404 let b2 = 3.0 * t2 - 2.0 * t3;
405 let b3 = t3;
406
407 let w0_div_3 = w0 / 3.0;
408 let w1_div_3 = w1 / 3.0;
409
410 // Advance by a third from initial rotation, with initial velocity.
411 let q1 = Quat::from_scaled_axis(w0_div_3) * qa;
412
413 // Back off by a third from final rotation, with final velocity.
414 let q2 = Quat::from_scaled_axis(-w1_div_3) * qb;
415
416 // Calculate fractional rotation needed to go from q0 to q1.
417 // q1 = q0 * Quat(w01 / 3)
418 let mut w01_div_3 = (q2 * q1.inverse()).to_scaled_axis();
419
420 // Add multiples of 2π to the magnitude of w01 / 3 to minimize
421 // its distance to the average of w0 / 3 and w1 / 3.
422 if unwrap {
423 let average_w_div_3 = w0_div_3.midpoint(w1_div_3);
424 let w01_direction = w01_div_3.normalize_or_zero();
425
426 // Closest point along unit vector n from starting point a to target point p, where l is the distance:
427 //
428 // argmin(l) length(a + l n - p)^2
429 //
430 // 0 = d/dl length(a + l n - p)^2 = dot([a + l n - p], n)
431 //
432 // l dot(n, n) = l = dot(p - a, n)
433
434 let extra_angle = w01_direction.dot(average_w_div_3 - w01_div_3);
435 w01_div_3 += ops::round(extra_angle / TAU) * TAU * w01_direction;
436 }
437
438 // Rotate by b1 * dt / 3 at initial velocity, then by b2 * dt / 3 at w01, then by b3 * dt / 3 at final velocity.
439 Quat::from_scaled_axis(b3 * w1_div_3)
440 * Quat::from_scaled_axis(b2 * w01_div_3)
441 * Quat::from_scaled_axis(b1 * w0_div_3)
442 * qa
443}