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, TransformEasingSystems, 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(TransformEasingSystems::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 =
312                    <V::Item<'static, 'static> as VelocitySourceItem<V>>::previous(start_vel);
313                let vel1 = <V::Item<'static, 'static> as VelocitySourceItem<V>>::current(end_vel);
314                transform.translation =
315                    hermite_vec3(start, end, delta_secs * vel0, delta_secs * vel1, overstep);
316            }
317        });
318}
319
320/// Eases the rotations of entities with Hermite interpolation.
321fn ease_rotation_hermite<V: VelocitySource>(
322    mut query: Query<
323        (
324            &mut Transform,
325            &RotationEasingState,
326            &V::Previous,
327            &V::Current,
328        ),
329        Without<NoRotationEasing>,
330    >,
331    time: Res<Time<Fixed>>,
332) {
333    let overstep = time.overstep_fraction();
334    let delta_secs = time.delta_secs();
335
336    query
337        .par_iter_mut()
338        .for_each(|(mut transform, interpolation, start_vel, end_vel)| {
339            if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) {
340                let vel0 =
341                    <V::Item<'static, 'static> as VelocitySourceItem<V>>::previous(start_vel);
342                let vel1 = <V::Item<'static, 'static> as VelocitySourceItem<V>>::current(end_vel);
343                transform.rotation = hermite_quat(
344                    start,
345                    end,
346                    delta_secs * vel0,
347                    delta_secs * vel1,
348                    overstep,
349                    true,
350                );
351            }
352        });
353}
354
355/// Performs a cubic Hermite interpolation between two vectors `p0` and `p1` with velocities `v0` and `v1`
356/// based on the value at `t`.
357///
358/// When `t` is `0.0`, the result will be equal to `p0`. When `t` is `1.0`, the result will be equal to `p1`.
359pub fn hermite_vec3(p0: Vec3, p1: Vec3, v0: Vec3, v1: Vec3, t: f32) -> Vec3 {
360    // Reference:
361    //
362    // Holden, D. "Cubic Interpolation of Quaternions"
363    // https://theorangeduck.com/page/cubic-interpolation-quaternions
364    //
365    // The article is mostly about quaternions, but also describes Hermite interpolation for vectors.
366    // For quaternions, we use a different approach. See `hermite_quat`.
367
368    let t2 = t * t;
369    let t3 = t2 * t;
370
371    // Polynomial coefficients
372    let b0 = 2.0 * t3 - 3.0 * t2 + 1.0;
373    let b1 = 3.0 * t2 - 2.0 * t3;
374    let b2 = t3 - 2.0 * t2 + t;
375    let b3 = t3 - t2;
376
377    b0 * p0 + b1 * p1 + b2 * v0 + b3 * v1
378}
379
380/// Performs a cubic Hermite interpolation between quaternions `q0` and `q1`
381/// with angular velocities `w0` and `w1` based on the value at `t`.
382///
383/// Both quaternions and angular velocities should be in the global frame.
384/// The angular velocities should be normalized such that they represent the angle of rotation
385/// over the time step.
386///
387/// When `t` is `0.0`, the result will be equal to `q0`. When `t` is `1.0`, the result will be equal to `q1`.
388///
389/// If `unwrap` is `true`, the interpolation will work for arbitrarily large velocities
390/// and handle multiple full revolutions correctly. This is a bit more expensive,
391/// but can be important for high angular velocities.
392pub fn hermite_quat(qa: Quat, qb: Quat, w0: Vec3, w1: Vec3, t: f32, unwrap: bool) -> Quat {
393    // Reference:
394    //
395    // Kim M.-J. et al. "A General Construction Scheme for Unit Quaternion Curves with Simple High Order Derivatives".
396    // http://graphics.cs.cmu.edu/nsp/course/15-464/Fall05/papers/kimKimShin.pdf
397    //
398    // Note that the paper's angular velocities are defined in the local frame, but our values
399    // are in the global frame, so the order of multiplication for quaternions is reversed.
400
401    let t2 = t * t;
402    let t3 = t * t2;
403
404    // Cumulative Bernstein basis polynomials
405    let b1 = 1.0 - (1.0 - t).cubed();
406    let b2 = 3.0 * t2 - 2.0 * t3;
407    let b3 = t3;
408
409    let w0_div_3 = w0 / 3.0;
410    let w1_div_3 = w1 / 3.0;
411
412    // Advance by a third from initial rotation, with initial velocity.
413    let q1 = Quat::from_scaled_axis(w0_div_3) * qa;
414
415    // Back off by a third from final rotation, with final velocity.
416    let q2 = Quat::from_scaled_axis(-w1_div_3) * qb;
417
418    // Calculate fractional rotation needed to go from q0 to q1.
419    // q1 = q0 * Quat(w01 / 3)
420    let mut w01_div_3 = (q2 * q1.inverse()).to_scaled_axis();
421
422    // Add multiples of 2π to the magnitude of w01 / 3 to minimize
423    // its distance to the average of w0 / 3 and w1 / 3.
424    if unwrap {
425        let average_w_div_3 = w0_div_3.midpoint(w1_div_3);
426        let w01_direction = w01_div_3.normalize_or_zero();
427
428        // Closest point along unit vector n from starting point a to target point p, where l is the distance:
429        //
430        // argmin(l) length(a + l n - p)^2
431        //
432        // 0 = d/dl length(a + l n - p)^2 = dot([a + l n - p], n)
433        //
434        // l dot(n, n) = l = dot(p - a, n)
435
436        let extra_angle = w01_direction.dot(average_w_div_3 - w01_div_3);
437        w01_div_3 += ops::round(extra_angle / TAU) * TAU * w01_direction;
438    }
439
440    // Rotate by b1 * dt / 3 at initial velocity, then by b2 * dt / 3 at w01, then by b3 * dt / 3 at final velocity.
441    Quat::from_scaled_axis(b3 * w1_div_3)
442        * Quat::from_scaled_axis(b2 * w01_div_3)
443        * Quat::from_scaled_axis(b1 * w0_div_3)
444        * qa
445}