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}