bevy_transform_interpolation/
extrapolation.rs

1//! [`Transform`] extrapolation, making movement in [`FixedUpdate`] appear smooth
2//! by easing between the current and predicted [`Transform`] in between fixed timesteps.
3//!
4//! See the [`TransformExtrapolationPlugin`] for more information.
5
6use core::marker::PhantomData;
7
8use crate::{
9    NoRotationEasing, NoTranslationEasing, RotationEasingState, TransformEasingPlugin,
10    TransformEasingSet, TranslationEasingState, VelocitySource, VelocitySourceItem,
11};
12use bevy::prelude::*;
13
14/// A plugin for [`Transform`] extrapolation, making movement in [`FixedUpdate`] appear smooth.
15///
16/// Transform extrapolation predicts future positions based on velocity, and applies easing
17/// between the current and predicted [`Transform`] in between fixed timesteps.
18/// This results in movement that looks smooth and feels responsive, but can stutter
19/// when the prediction is incorrect, such as when velocity changes abruptly.
20///
21/// This plugin requires the [`TransformEasingPlugin`] to function. It is automatically added
22/// if not already present in the app.
23///
24/// Note that unlike [`TransformInterpolationPlugin`], this plugin does *not* support scale easing.
25/// However, the [`ScaleInterpolation`] component can still be used even when translation and rotation are extrapolated.
26///
27/// [`TransformInterpolationPlugin`]: crate::interpolation::TransformInterpolationPlugin
28/// [`ScaleInterpolation`]: crate::interpolation::ScaleInterpolation
29///
30/// # Usage
31///
32/// Transform extrapolation requires velocity to predict future positions.
33/// Instead of providing its own velocity components, the [`TransformExtrapolationPlugin`]
34/// lets you specify your own velocity components that you manage yourself.
35///
36/// First, make sure you have components for velocity, and implement the [`VelocitySource`] trait on a [`QueryData`] type:
37///
38/// ```
39/// use bevy::{ecs::query::QueryData, prelude::*};
40/// use bevy_transform_interpolation::VelocitySource;
41///
42/// #[derive(Component, Default)]
43/// struct LinearVelocity(Vec3);
44///
45/// #[derive(Component, Default)]
46/// struct AngularVelocity(Vec3);
47///
48/// #[derive(QueryData)]
49/// struct LinVelSource;
50///
51/// impl VelocitySource for LinVelSource {
52///     // Components storing the previous and current velocities.
53///     // Note: For extrapolation, the `Previous` component is not used, so we can make it the same as `Current`.
54///     type Previous = LinearVelocity;
55///     type Current = LinearVelocity;
56///
57///     fn previous(start: &Self::Previous) -> Vec3 {
58///         start.0
59///     }
60///
61///     fn current(end: &Self::Current) -> Vec3 {
62///         end.0
63///     }
64/// }
65///
66/// #[derive(QueryData)]
67/// struct AngVelSource;
68///
69/// impl VelocitySource for AngVelSource {
70///     type Previous = AngularVelocity;
71///     type Current = AngularVelocity;
72///
73///     fn previous(start: &Self::Previous) -> Vec3 {
74///         start.0
75///     }
76///
77///     fn current(end: &Self::Current) -> Vec3 {
78///         end.0
79///     }
80/// }
81/// ```
82///
83/// Then, add the [`TransformExtrapolationPlugin`] to the app with the velocity sources:
84///
85/// ```no_run
86/// use bevy::{ecs::query::QueryData, prelude::*};
87/// use bevy_transform_interpolation::{prelude::*, VelocitySource};
88/// #
89/// # #[derive(Component, Default)]
90/// # struct LinearVelocity(Vec3);
91/// #
92/// # #[derive(Component, Default)]
93/// # struct AngularVelocity(Vec3);
94/// #
95/// # #[derive(QueryData)]
96/// # struct LinVelSource;
97/// #
98/// # impl VelocitySource for LinVelSource {
99/// #     type Previous = LinearVelocity;
100/// #     type Current = LinearVelocity;
101/// #
102/// #     fn previous(start: &Self::Previous) -> Vec3 {
103/// #         start.0
104/// #     }
105/// #
106/// #     fn current(end: &Self::Current) -> Vec3 {
107/// #         end.0
108/// #     }
109/// # }
110/// #
111/// # #[derive(QueryData)]
112/// # struct AngVelSource;
113/// #
114/// # impl VelocitySource for AngVelSource {
115/// #     type Previous = AngularVelocity;
116/// #     type Current = AngularVelocity;
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/// fn main() {
128///    let mut app = App::new();
129///
130///     app.add_plugins((
131///        TransformInterpolationPlugin::default(),
132///        TransformExtrapolationPlugin::<LinVelSource, AngVelSource>::default(),
133/// #      bevy::time::TimePlugin::default(),
134///    ));
135///
136///    // Optional: Insert velocity components automatically for entities with extrapolation.
137///    app.register_required_components::<TranslationExtrapolation, LinearVelocity>();
138///    app.register_required_components::<RotationExtrapolation, AngularVelocity>();
139///
140///    // ...
141///
142///    app.run();
143/// }
144/// ```
145///
146/// Transform extrapolation can now be enabled for a given entity by adding the [`TransformExtrapolation`] component:
147///
148/// ```
149/// # use bevy::prelude::*;
150/// # use bevy_transform_interpolation::prelude::*;
151/// #
152/// fn setup(mut commands: Commands) {
153///     // Extrapolate translation and rotation.
154///     commands.spawn((
155///         Transform::default(),
156///         TransformExtrapolation,
157///     ));
158/// }
159/// ```
160///
161/// Now, any changes made to the translation or rotation of the entity in [`FixedPreUpdate`], [`FixedUpdate`],
162/// or [`FixedPostUpdate`] will automatically be smoothed in between fixed timesteps.
163///
164/// Transform properties can also be extrapolated individually by adding the [`TranslationExtrapolation`]
165/// and [`RotationExtrapolation`] components.
166///
167/// ```
168/// # use bevy::prelude::*;
169/// # use bevy_transform_interpolation::prelude::*;
170/// #
171/// fn setup(mut commands: Commands) {
172///     // Only extrapolate translation.
173///     commands.spawn((Transform::default(), TranslationExtrapolation));
174///     
175///     // Only extrapolate rotation.
176///     commands.spawn((Transform::default(), RotationExtrapolation));
177/// }
178/// ```
179///
180/// If you want *all* entities with a [`Transform`] to be extrapolated by default, you can use
181/// [`TransformExtrapolationPlugin::extrapolate_all()`], or set the [`extrapolate_translation_all`]
182/// and [`extrapolate_rotation_all`] fields.
183///
184/// ```ignore
185/// # use bevy::prelude::*;
186/// # use bevy_transform_interpolation::prelude::*;
187/// #
188/// fn main() {
189///    App::new()
190///       .add_plugins(TransformExtrapolationPlugin::<LinVelSource, AngVelSource> {
191///           // Extrapolate translation by default, but not rotation.
192///           extrapolate_translation_all: true,
193///           extrapolate_rotation_all: false,
194///       })
195///       // ...
196///       .run();
197/// }
198/// ```
199///
200/// When extrapolation is enabled for all entities by default, you can still opt out of it for individual entities
201/// by adding the [`NoTransformEasing`] component, or the individual [`NoTranslationEasing`] and [`NoRotationEasing`] components.
202///
203/// Note that changing [`Transform`] manually in any schedule that *doesn't* use a fixed timestep is also supported,
204/// but it is equivalent to teleporting, and disables extrapolation for the entity for the remainder of that fixed timestep.
205///
206/// [`QueryData`]: bevy::ecs::query::QueryData
207/// [`TransformExtrapolationPlugin::extrapolate_all()`]: TransformExtrapolationPlugin::extrapolate_all
208/// [`extrapolate_translation_all`]: TransformExtrapolationPlugin::extrapolate_translation_all
209/// [`extrapolate_rotation_all`]: TransformExtrapolationPlugin::extrapolate_rotation_all
210/// [`NoTransformEasing`]: crate::NoTransformEasing
211/// [`NoTranslationEasing`]: crate::NoTranslationEasing
212/// [`NoRotationEasing`]: crate::NoRotationEasing
213///
214/// # Alternatives
215///
216/// For many applications, the stutter caused by mispredictions in extrapolation may be undesirable.
217/// In these cases, the [`TransformInterpolationPlugin`] can be a better alternative.
218///
219/// Transform interpolation eases between the previous and current [`Transform`],
220/// resulting in movement that is always smooth and accurate. The downside is that the rendered
221/// positions can lag slightly behind the true positions, making movement feel delayed.
222///
223/// # Easing Backends
224///
225/// By default, transform extrapolation uses linear interpolation (`lerp`) for easing translation,
226/// and spherical linear interpolation (`slerp`) for easing rotation.
227///
228/// If the previous and current velocities are also available, it is possible to use *Hermite interpolation*
229/// with the [`TransformHermiteEasingPlugin`] to get smoother and more accurate easing. To enable Hermite interpolation
230/// for extrapolation, add the [`TransformHermiteEasing`] component to the entity in addition to the extrapolation components.
231///
232/// [`TransformHermiteEasingPlugin`]: crate::hermite::TransformHermiteEasingPlugin
233/// [`TransformHermiteEasing`]: crate::hermite::TransformHermiteEasing
234#[derive(Debug)]
235pub struct TransformExtrapolationPlugin<LinVel: VelocitySource, AngVel: VelocitySource> {
236    /// If `true`, translation will be extrapolated for all entities with the [`Transform`] component by default.
237    ///
238    /// This can be overridden for individual entities by adding the [`NoTranslationEasing`] or [`NoTransformEasing`] component.
239    ///
240    /// [`NoTransformEasing`]: crate::NoTransformEasing
241    pub extrapolate_translation_all: bool,
242    /// If `true`, rotation will be extrapolated for all entities with the [`Transform`] component by default.
243    ///
244    /// This can be overridden for individual entities by adding the [`NoRotationEasing`] or [`NoTransformEasing`] component.
245    ///
246    /// [`NoTransformEasing`]: crate::NoTransformEasing
247    pub extrapolate_rotation_all: bool,
248    /// Phantom data use the type parameters.
249    #[doc(hidden)]
250    pub _phantom: PhantomData<(LinVel, AngVel)>,
251}
252
253impl<LinVel: VelocitySource, AngVel: VelocitySource> Default
254    for TransformExtrapolationPlugin<LinVel, AngVel>
255{
256    fn default() -> Self {
257        Self {
258            extrapolate_translation_all: false,
259            extrapolate_rotation_all: false,
260            _phantom: PhantomData,
261        }
262    }
263}
264
265impl<LinVel: VelocitySource, AngVel: VelocitySource> TransformExtrapolationPlugin<LinVel, AngVel> {
266    /// Enables extrapolation for translation and rotation for all entities with the [`Transform`] component.
267    ///
268    /// This can be overridden for individual entities by adding the [`NoTransformEasing`] component,
269    /// or the individual [`NoTranslationEasing`] and [`NoRotationEasing`] components.
270    ///
271    /// [`NoTransformEasing`]: crate::NoTransformEasing
272    /// [`NoRotationEasing`]: crate::NoRotationEasing
273    pub fn extrapolate_all() -> Self {
274        Self {
275            extrapolate_translation_all: true,
276            extrapolate_rotation_all: true,
277            _phantom: PhantomData,
278        }
279    }
280}
281
282impl<LinVel: VelocitySource, AngVel: VelocitySource> Plugin
283    for TransformExtrapolationPlugin<LinVel, AngVel>
284{
285    fn build(&self, app: &mut App) {
286        //Register components.
287        app.register_type::<(
288            TransformExtrapolation,
289            TranslationExtrapolation,
290            RotationExtrapolation,
291        )>();
292
293        // Reset the transform to the start of the extrapolation at the beginning of the fixed timestep
294        // to match the true position from the end of the previous fixed tick.
295        app.add_systems(
296            FixedFirst,
297            (
298                reset_translation_extrapolation,
299                reset_rotation_extrapolation,
300            )
301                .before(TransformEasingSet::Reset),
302        );
303
304        // Update the start and end state of the extrapolation at the end of the fixed timestep.
305        app.add_systems(
306            FixedLast,
307            (
308                update_translation_extrapolation_states::<LinVel>,
309                update_rotation_extrapolation_states::<AngVel>,
310            )
311                .in_set(TransformEasingSet::UpdateEnd),
312        );
313
314        // Insert extrapolation components automatically for all entities with a `Transform`
315        // if the corresponding global extrapolation is enabled.
316        if self.extrapolate_translation_all {
317            let _ = app.try_register_required_components::<Transform, TranslationExtrapolation>();
318        }
319        if self.extrapolate_rotation_all {
320            let _ = app.try_register_required_components::<Transform, RotationExtrapolation>();
321        }
322    }
323
324    fn finish(&self, app: &mut App) {
325        // Add the `TransformEasingPlugin` if it hasn't been added yet.
326        // It performs the actual easing based on the start and end states set by the extrapolation.
327        if !app.is_plugin_added::<TransformEasingPlugin>() {
328            app.add_plugins(TransformEasingPlugin);
329        }
330    }
331}
332
333/// Enables [`Transform`] extrapolation for an entity, making changes to translation
334/// and rotation in [`FixedUpdate`] appear smooth.
335///
336/// Extrapolation only works for entities with velocity components.
337/// [`TransformExtrapolationPlugin`] must be added to the app with the appropriate velocity sources.
338///
339/// Unlike [`TransformInterpolation`], this does *not* support scale easing.
340/// However, the [`ScaleInterpolation`] component can still be used even when translation and rotation are extrapolated.
341///
342/// See the [`TransformExtrapolationPlugin`] for more information.
343///
344/// [`TransformInterpolation`]: crate::interpolation::TransformInterpolation
345/// [`ScaleInterpolation`]: crate::interpolation::ScaleInterpolation
346#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
347#[reflect(Component, Debug, Default)]
348#[require(TranslationExtrapolation, RotationExtrapolation)]
349pub struct TransformExtrapolation;
350
351/// Enables translation extrapolation for an entity, making changes to translation
352/// in [`FixedUpdate`] appear smooth.
353///
354/// Extrapolation only works for entities with velocity components.
355/// [`TransformExtrapolationPlugin`] must be added to the app with the appropriate velocity sources.
356///
357/// See the [`TransformExtrapolationPlugin`] for more information.
358#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
359#[reflect(Component, Debug, Default)]
360#[require(TranslationEasingState)]
361pub struct TranslationExtrapolation;
362
363/// Enables rotation extrapolation for an entity, making changes to rotation
364/// in [`FixedUpdate`] appear smooth.
365///
366/// Extrapolation only works for entities with velocity components.
367/// [`TransformExtrapolationPlugin`] must be added to the app with the appropriate velocity sources.
368///
369/// See the [`TransformExtrapolationPlugin`] for more information.
370#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
371#[reflect(Component, Debug, Default)]
372#[require(RotationEasingState)]
373pub struct RotationExtrapolation;
374
375/// Resets the translation to the start of the extrapolation at the beginning of the fixed timestep
376/// to match the true position from the end of the previous fixed tick.
377fn reset_translation_extrapolation(
378    mut query: Query<
379        (&mut Transform, &TranslationEasingState),
380        (With<TranslationExtrapolation>, Without<NoTranslationEasing>),
381    >,
382) {
383    for (mut transform, translation_easing) in &mut query {
384        if let Some(start) = translation_easing.start {
385            transform.translation = start;
386        }
387    }
388}
389
390/// Resets the rotation to the start of the extrapolation at the beginning of the fixed timestep
391/// to match the true position from the end of the previous fixed tick.
392fn reset_rotation_extrapolation(
393    mut query: Query<
394        (&mut Transform, &RotationEasingState),
395        (With<RotationExtrapolation>, Without<NoRotationEasing>),
396    >,
397) {
398    for (mut transform, rotation_easing) in &mut query {
399        if let Some(start) = rotation_easing.start {
400            transform.rotation = start;
401        }
402    }
403}
404
405/// Updates the start and end states of the extrapolation for the next fixed timestep.
406fn update_translation_extrapolation_states<V: VelocitySource>(
407    mut query: Query<
408        (&Transform, &mut TranslationEasingState, &V::Current),
409        (With<TranslationExtrapolation>, Without<NoTranslationEasing>),
410    >,
411    time: Res<Time>,
412) {
413    let delta_secs = time.delta_secs();
414
415    for (transform, mut translation_easing, end_vel) in &mut query {
416        translation_easing.start = Some(transform.translation);
417
418        // Extrapolate the next state based on the current state and velocities.
419        let lin_vel = <V::Item<'static> as VelocitySourceItem<V>>::current(end_vel);
420        translation_easing.end = Some(transform.translation + lin_vel * delta_secs);
421    }
422}
423
424/// Updates the start and end states of the extrapolation for the next fixed timestep.
425fn update_rotation_extrapolation_states<V: VelocitySource>(
426    mut query: Query<
427        (&Transform, &mut RotationEasingState, &V::Current),
428        (With<RotationExtrapolation>, Without<NoRotationEasing>),
429    >,
430    time: Res<Time>,
431) {
432    let delta_secs = time.delta_secs();
433
434    for (transform, mut rotation_easing, end_vel) in &mut query {
435        rotation_easing.start = Some(transform.rotation);
436
437        // Extrapolate the next state based on the current state and velocities.
438        let ang_vel = <V::Item<'static> as VelocitySourceItem<V>>::current(end_vel);
439        let scaled_axis = ang_vel * delta_secs;
440        rotation_easing.end = Some(transform.rotation * Quat::from_scaled_axis(scaled_axis));
441    }
442}