bevy_transform_interpolation/interpolation.rs
1//! [`Transform`] interpolation, making movement in [`FixedUpdate`] appear smooth
2//! by easing between the old and current [`Transform`] in between fixed timesteps.
3//!
4//! See the [`TransformInterpolationPlugin`] for more information.
5
6#![allow(clippy::type_complexity)]
7
8use crate::{
9    prelude::*, RotationEasingState, ScaleEasingState, TransformEasingSystems,
10    TranslationEasingState,
11};
12use bevy::prelude::*;
13
14/// A plugin for [`Transform`] interpolation, making movement in [`FixedUpdate`] appear smooth.
15///
16/// Transform interpolation applies easing between the old and current [`Transform`]
17/// in between fixed timesteps. This results in movement that looks smooth and accurate,
18/// at the cost of rendered positions being slightly behind the "true" gameplay positions.
19///
20/// This plugin requires the [`TransformEasingPlugin`] to function. It is automatically added
21/// if not already present in the app.
22///
23/// # Usage
24///
25/// Transform interpolation can be enabled for a given entity by adding the [`TransformInterpolation`] component.
26///
27/// ```
28/// use bevy::prelude::*;
29/// use bevy_transform_interpolation::prelude::*;
30///
31/// fn setup(mut commands: Commands) {
32///     // Interpolate the entire transform: translation, rotation, and scale.
33///     commands.spawn((
34///         Transform::default(),
35///         TransformInterpolation,
36///     ));
37/// }
38/// ```
39///
40/// Now, any changes made to the [`Transform`] of the entity in [`FixedPreUpdate`], [`FixedUpdate`],
41/// or [`FixedPostUpdate`] will automatically be smoothed in between fixed timesteps.
42///
43/// Transform properties can also be interpolated individually by adding the [`TranslationInterpolation`],
44/// [`RotationInterpolation`], and [`ScaleInterpolation`] components.
45///
46/// ```
47/// # use bevy::prelude::*;
48/// # use bevy_transform_interpolation::prelude::*;
49/// #
50/// fn setup(mut commands: Commands) {
51///     // Only interpolate translation.
52///     commands.spawn((Transform::default(), TranslationInterpolation));
53///     
54///     // Only interpolate rotation.
55///     commands.spawn((Transform::default(), RotationInterpolation));
56///     
57///     // Only interpolate scale.
58///     commands.spawn((Transform::default(), ScaleInterpolation));
59///     
60///     // Interpolate translation and rotation, but not scale.
61///     commands.spawn((
62///         Transform::default(),
63///         TranslationInterpolation,
64///         RotationInterpolation,
65///     ));
66/// }
67/// ```
68///
69/// If you want *all* entities with a [`Transform`] to be interpolated by default, you can use
70/// [`TransformInterpolationPlugin::interpolate_all()`], or set the [`interpolate_translation_all`],
71/// [`interpolate_rotation_all`], and [`interpolate_scale_all`] fields.
72///
73/// ```no_run
74/// # use bevy::prelude::*;
75/// # use bevy_transform_interpolation::prelude::*;
76/// #
77/// fn main() {
78///    App::new()
79///       .add_plugins(TransformInterpolationPlugin {
80///           // Interpolate translation and rotation by default, but not scale.
81///           interpolate_translation_all: true,
82///           interpolate_rotation_all: true,
83///           interpolate_scale_all: false,
84///       })
85///       // ...
86///       .run();
87/// }
88/// ```
89///
90/// When interpolation is enabled for all entities by default, you can still opt out of it for individual entities
91/// by adding the [`NoTransformEasing`] component, or the individual [`NoTranslationEasing`], [`NoRotationEasing`],
92/// and [`NoScaleEasing`] components.
93///
94/// Note that changing [`Transform`] manually in any schedule that *doesn't* use a fixed timestep is also supported,
95/// but it is equivalent to teleporting, and disables interpolation for the entity for the remainder of that fixed timestep.
96///
97/// [`interpolate_translation_all`]: TransformInterpolationPlugin::interpolate_translation_all
98/// [`interpolate_rotation_all`]: TransformInterpolationPlugin::interpolate_rotation_all
99/// [`interpolate_scale_all`]: TransformInterpolationPlugin::interpolate_scale_all
100///
101/// # Alternatives
102///
103/// For games where low latency is crucial for gameplay, such as in some first-person shooters
104/// or racing games, the small delay introduced by interpolation may be undesirable. In those cases,
105/// one option is to use the [`TransformExtrapolationPlugin`] instea.
106///
107/// Transform extrapolation predicts future positions based on velocity, and applies easing between
108/// the current and predicted [`Transform`]. This results in movement that looks smooth and feels responsive,
109/// but can stutter when the prediction is incorrect, such as when velocity changes abruptly.
110///
111/// # Easing Backends
112///
113/// By default, transform interpolation uses linear interpolation (`lerp`) for easing translation and scale,
114/// and spherical linear interpolation (`slerp`) for easing rotation.
115///
116/// If the previous and current velocities are also available, it is possible to use [Hermite interpolation]
117/// with the [`TransformHermiteEasingPlugin`] to get smoother and more accurate easing. To enable Hermite interpolation,
118/// add the [`TransformHermiteEasing`] component to the entity in addition to the core interpolation components.
119#[derive(Debug, Default)]
120pub struct TransformInterpolationPlugin {
121    /// If `true`, translation will be interpolated for all entities with the [`Transform`] component by default.
122    ///
123    /// This can be overridden for individual entities by adding the [`NoTranslationEasing`] or [`NoTransformEasing`] component.
124    pub interpolate_translation_all: bool,
125    /// If `true`, rotation will be interpolated for all entities with the [`Transform`] component by default.
126    ///
127    /// This can be overridden for individual entities by adding the [`NoRotationEasing`] or [`NoTransformEasing`] component.
128    pub interpolate_rotation_all: bool,
129    /// If `true`, scale will be interpolated for all entities with the [`Transform`] component by default.
130    ///
131    /// This can be overridden for individual entities by adding the [`NoScaleEasing`] or [`NoTransformEasing`] component.
132    pub interpolate_scale_all: bool,
133}
134
135impl TransformInterpolationPlugin {
136    /// Enables interpolation for translation, rotation, and scale for all entities with the [`Transform`] component.
137    ///
138    /// This can be overridden for individual entities by adding the [`NoTransformEasing`] component,
139    /// or the individual [`NoTranslationEasing`], [`NoRotationEasing`], and [`NoScaleEasing`] components.
140    pub const fn interpolate_all() -> Self {
141        Self {
142            interpolate_translation_all: true,
143            interpolate_rotation_all: true,
144            interpolate_scale_all: true,
145        }
146    }
147}
148
149impl Plugin for TransformInterpolationPlugin {
150    fn build(&self, app: &mut App) {
151        // Register components.
152        app.register_type::<(
153            TranslationInterpolation,
154            RotationInterpolation,
155            ScaleInterpolation,
156        )>();
157
158        app.add_systems(
159            FixedFirst,
160            (
161                complete_translation_easing,
162                complete_rotation_easing,
163                complete_scale_easing,
164            )
165                .chain()
166                .before(TransformEasingSystems::Reset),
167        );
168
169        // Update the start state of the interpolation at the start of the fixed timestep.
170        app.add_systems(
171            FixedFirst,
172            (
173                update_translation_interpolation_start,
174                update_rotation_interpolation_start,
175                update_scale_interpolation_start,
176            )
177                .chain()
178                .in_set(TransformEasingSystems::UpdateStart),
179        );
180
181        // Update the end state of the interpolation at the end of the fixed timestep.
182        app.add_systems(
183            FixedLast,
184            (
185                update_translation_interpolation_end,
186                update_rotation_interpolation_end,
187                update_scale_interpolation_end,
188            )
189                .chain()
190                .in_set(TransformEasingSystems::UpdateEnd),
191        );
192
193        // Insert interpolation components automatically for all entities with a `Transform`
194        // if the corresponding global interpolation is enabled.
195        if self.interpolate_translation_all {
196            let _ = app.try_register_required_components::<Transform, TranslationInterpolation>();
197        }
198        if self.interpolate_rotation_all {
199            let _ = app.try_register_required_components::<Transform, RotationInterpolation>();
200        }
201        if self.interpolate_scale_all {
202            let _ = app.try_register_required_components::<Transform, ScaleInterpolation>();
203        }
204    }
205
206    fn finish(&self, app: &mut App) {
207        // Add the `TransformEasingPlugin` if it hasn't been added yet.
208        if !app.is_plugin_added::<TransformEasingPlugin>() {
209            app.add_plugins(TransformEasingPlugin);
210        }
211    }
212}
213
214/// Enables full [`Transform`] interpolation for an entity, making changes to translation,
215/// rotation, and scale in [`FixedUpdate`] appear smooth.
216///
217/// See the [`TransformInterpolationPlugin`] for more information.
218#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
219#[reflect(Component, Debug, Default)]
220#[require(TranslationInterpolation, RotationInterpolation, ScaleInterpolation)]
221pub struct TransformInterpolation;
222
223/// Enables translation interpolation for an entity, making changes to translation
224/// in [`FixedUpdate`] appear smooth.
225///
226/// See the [`TransformInterpolationPlugin`] for more information.
227#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
228#[reflect(Component, Debug, Default)]
229#[require(TranslationEasingState)]
230pub struct TranslationInterpolation;
231
232/// Enables rotation interpolation for an entity, making changes to rotation
233/// in [`FixedUpdate`] appear smooth.
234///
235/// See the [`TransformInterpolationPlugin`] for more information.
236#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
237#[reflect(Component, Debug, Default)]
238#[require(RotationEasingState)]
239pub struct RotationInterpolation;
240
241/// Enables scale interpolation for an entity, making changes to scale
242/// in [`FixedUpdate`] appear smooth.
243///
244/// See the [`TransformInterpolationPlugin`] for more information.
245#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
246#[reflect(Component, Debug, Default)]
247#[require(ScaleEasingState)]
248pub struct ScaleInterpolation;
249
250/// Makes sure the previous translation easing is fully applied before the next easing starts.
251fn complete_translation_easing(
252    mut query: Query<
253        (&mut Transform, &TranslationEasingState),
254        (With<TranslationInterpolation>, Without<NoTranslationEasing>),
255    >,
256) {
257    for (mut transform, easing) in &mut query {
258        // Make sure the previous easing is fully applied.
259        if let Some(end) = easing.end {
260            transform.translation = end;
261        }
262    }
263}
264
265/// Makes sure the previous rotation easing is fully applied before the next easing starts.
266fn complete_rotation_easing(
267    mut query: Query<
268        (&mut Transform, &RotationEasingState),
269        (With<RotationInterpolation>, Without<NoRotationEasing>),
270    >,
271) {
272    for (mut transform, easing) in &mut query {
273        // Make sure the previous easing is fully applied.
274        if let Some(end) = easing.end {
275            transform.rotation = end;
276        }
277    }
278}
279
280/// Makes sure the previous scale easing is fully applied before the next easing starts.
281fn complete_scale_easing(
282    mut query: Query<
283        (&mut Transform, &ScaleEasingState),
284        (With<ScaleInterpolation>, Without<NoScaleEasing>),
285    >,
286) {
287    for (mut transform, easing) in &mut query {
288        // Make sure the previous easing is fully applied.
289        if let Some(end) = easing.end {
290            transform.scale = end;
291        }
292    }
293}
294
295fn update_translation_interpolation_start(
296    mut query: Query<
297        (&Transform, &mut TranslationEasingState),
298        (With<TranslationInterpolation>, Without<NoTranslationEasing>),
299    >,
300) {
301    for (transform, mut easing) in &mut query {
302        easing.start = Some(transform.translation);
303    }
304}
305
306fn update_translation_interpolation_end(
307    mut query: Query<
308        (&Transform, &mut TranslationEasingState),
309        (With<TranslationInterpolation>, Without<NoTranslationEasing>),
310    >,
311) {
312    for (transform, mut easing) in &mut query {
313        easing.end = Some(transform.translation);
314    }
315}
316
317fn update_rotation_interpolation_start(
318    mut query: Query<
319        (&Transform, &mut RotationEasingState),
320        (With<RotationInterpolation>, Without<NoRotationEasing>),
321    >,
322) {
323    for (transform, mut easing) in &mut query {
324        easing.start = Some(transform.rotation);
325    }
326}
327
328fn update_rotation_interpolation_end(
329    mut query: Query<
330        (&Transform, &mut RotationEasingState),
331        (With<RotationInterpolation>, Without<NoRotationEasing>),
332    >,
333) {
334    for (transform, mut easing) in &mut query {
335        easing.end = Some(transform.rotation);
336    }
337}
338
339fn update_scale_interpolation_start(
340    mut query: Query<
341        (&Transform, &mut ScaleEasingState),
342        (With<ScaleInterpolation>, Without<NoScaleEasing>),
343    >,
344) {
345    for (transform, mut easing) in &mut query {
346        easing.start = Some(transform.scale);
347    }
348}
349
350fn update_scale_interpolation_end(
351    mut query: Query<
352        (&Transform, &mut ScaleEasingState),
353        (With<ScaleInterpolation>, Without<NoScaleEasing>),
354    >,
355) {
356    for (transform, mut easing) in &mut query {
357        easing.end = Some(transform.scale);
358    }
359}