bevy_transform_interpolation/
interpolation.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
//! [`Transform`] interpolation, making movement in [`FixedUpdate`] appear smooth
//! by easing between the old and current [`Transform`] in between fixed timesteps.
//!
//! See the [`TransformInterpolationPlugin`] for more information.

#![allow(clippy::type_complexity)]

use crate::{
    prelude::*, RotationEasingState, ScaleEasingState, TransformEasingSet, TranslationEasingState,
};
use bevy::prelude::*;

/// A plugin for [`Transform`] interpolation, making movement in [`FixedUpdate`] appear smooth.
///
/// Transform interpolation applies easing between the old and current [`Transform`]
/// in between fixed timesteps. This results in movement that looks smooth and accurate,
/// at the cost of rendered positions being slightly behind the "true" gameplay positions.
///
/// This plugin requires the [`TransformEasingPlugin`] to function. It is automatically added
/// if not already present in the app.
///
/// # Usage
///
/// Transform interpolation can be enabled for a given entity by adding the [`TransformInterpolation`] component.
///
/// ```
/// use bevy::prelude::*;
/// use bevy_transform_interpolation::prelude::*;
///
/// fn setup(mut commands: Commands) {
///     // Interpolate the entire transform: translation, rotation, and scale.
///     commands.spawn((
///         Transform::default(),
///         TransformInterpolation,
///     ));
/// }
/// ```
///
/// Now, any changes made to the [`Transform`] of the entity in [`FixedPreUpdate`], [`FixedUpdate`],
/// or [`FixedPostUpdate`] will automatically be smoothed in between fixed timesteps.
///
/// Transform properties can also be interpolated individually by adding the [`TranslationInterpolation`],
/// [`RotationInterpolation`], and [`ScaleInterpolation`] components.
///
/// ```
/// # use bevy::prelude::*;
/// # use bevy_transform_interpolation::prelude::*;
/// #
/// fn setup(mut commands: Commands) {
///     // Only interpolate translation.
///     commands.spawn((Transform::default(), TranslationInterpolation));
///     
///     // Only interpolate rotation.
///     commands.spawn((Transform::default(), RotationInterpolation));
///     
///     // Only interpolate scale.
///     commands.spawn((Transform::default(), ScaleInterpolation));
///     
///     // Interpolate translation and rotation, but not scale.
///     commands.spawn((
///         Transform::default(),
///         TranslationInterpolation,
///         RotationInterpolation,
///     ));
/// }
/// ```
///
/// If you want *all* entities with a [`Transform`] to be interpolated by default, you can use
/// [`TransformInterpolationPlugin::interpolate_all()`], or set the [`interpolate_translation_all`],
/// [`interpolate_rotation_all`], and [`interpolate_scale_all`] fields.
///
/// ```no_run
/// # use bevy::prelude::*;
/// # use bevy_transform_interpolation::prelude::*;
/// #
/// fn main() {
///    App::new()
///       .add_plugins(TransformInterpolationPlugin {
///           // Interpolate translation and rotation by default, but not scale.
///           interpolate_translation_all: true,
///           interpolate_rotation_all: true,
///           interpolate_scale_all: false,
///       })
///       // ...
///       .run();
/// }
/// ```
///
/// When interpolation is enabled for all entities by default, you can still opt out of it for individual entities
/// by adding the [`NoTransformEasing`] component, or the individual [`NoTranslationEasing`], [`NoRotationEasing`],
/// and [`NoScaleEasing`] components.
///
/// Note that changing [`Transform`] manually in any schedule that *doesn't* use a fixed timestep is also supported,
/// but it is equivalent to teleporting, and disables interpolation for the entity for the remainder of that fixed timestep.
///
/// [`interpolate_translation_all`]: TransformInterpolationPlugin::interpolate_translation_all
/// [`interpolate_rotation_all`]: TransformInterpolationPlugin::interpolate_rotation_all
/// [`interpolate_scale_all`]: TransformInterpolationPlugin::interpolate_scale_all
///
/// # Alternatives
///
/// For games where low latency is crucial for gameplay, such as in some first-person shooters
/// or racing games, the small delay introduced by interpolation may be undesirable. In those cases,
/// one option is to use the [`TransformExtrapolationPlugin`] instea.
///
/// Transform extrapolation predicts future positions based on velocity, and applies easing between
/// the current and predicted [`Transform`]. This results in movement that looks smooth and feels responsive,
/// but can stutter when the prediction is incorrect, such as when velocity changes abruptly.
///
/// # Easing Backends
///
/// By default, transform interpolation uses linear interpolation (`lerp`) for easing translation and scale,
/// and spherical linear interpolation (`slerp`) for easing rotation.
///
/// If the previous and current velocities are also available, it is possible to use [Hermite interpolation]
/// with the [`TransformHermiteEasingPlugin`] to get smoother and more accurate easing. To enable Hermite interpolation,
/// add the [`TransformHermiteEasing`] component to the entity in addition to the core interpolation components.
#[derive(Debug, Default)]
pub struct TransformInterpolationPlugin {
    /// If `true`, translation will be interpolated for all entities with the [`Transform`] component by default.
    ///
    /// This can be overridden for individual entities by adding the [`NoTranslationEasing`] or [`NoTransformEasing`] component.
    pub interpolate_translation_all: bool,
    /// If `true`, rotation will be interpolated for all entities with the [`Transform`] component by default.
    ///
    /// This can be overridden for individual entities by adding the [`NoRotationEasing`] or [`NoTransformEasing`] component.
    pub interpolate_rotation_all: bool,
    /// If `true`, scale will be interpolated for all entities with the [`Transform`] component by default.
    ///
    /// This can be overridden for individual entities by adding the [`NoScaleEasing`] or [`NoTransformEasing`] component.
    pub interpolate_scale_all: bool,
}

impl TransformInterpolationPlugin {
    /// Enables interpolation for translation, rotation, and scale for all entities with the [`Transform`] component.
    ///
    /// This can be overridden for individual entities by adding the [`NoTransformEasing`] component,
    /// or the individual [`NoTranslationEasing`], [`NoRotationEasing`], and [`NoScaleEasing`] components.
    pub const fn interpolate_all() -> Self {
        Self {
            interpolate_translation_all: true,
            interpolate_rotation_all: true,
            interpolate_scale_all: true,
        }
    }
}

impl Plugin for TransformInterpolationPlugin {
    fn build(&self, app: &mut App) {
        // Register components.
        app.register_type::<(
            TranslationInterpolation,
            RotationInterpolation,
            ScaleInterpolation,
        )>();

        app.add_systems(
            FixedFirst,
            (
                complete_translation_easing,
                complete_rotation_easing,
                complete_scale_easing,
            )
                .chain()
                .before(TransformEasingSet::Reset),
        );

        // Update the start state of the interpolation at the start of the fixed timestep.
        app.add_systems(
            FixedFirst,
            (
                update_translation_interpolation_start,
                update_rotation_interpolation_start,
                update_scale_interpolation_start,
            )
                .chain()
                .in_set(TransformEasingSet::UpdateStart),
        );

        // Update the end state of the interpolation at the end of the fixed timestep.
        app.add_systems(
            FixedLast,
            (
                update_translation_interpolation_end,
                update_rotation_interpolation_end,
                update_scale_interpolation_end,
            )
                .chain()
                .in_set(TransformEasingSet::UpdateEnd),
        );

        // Insert interpolation components automatically for all entities with a `Transform`
        // if the corresponding global interpolation is enabled.
        if self.interpolate_translation_all {
            let _ = app.try_register_required_components::<Transform, TranslationInterpolation>();
        }
        if self.interpolate_rotation_all {
            let _ = app.try_register_required_components::<Transform, RotationInterpolation>();
        }
        if self.interpolate_scale_all {
            let _ = app.try_register_required_components::<Transform, ScaleInterpolation>();
        }
    }

    fn finish(&self, app: &mut App) {
        // Add the `TransformEasingPlugin` if it hasn't been added yet.
        if !app.is_plugin_added::<TransformEasingPlugin>() {
            app.add_plugins(TransformEasingPlugin);
        }
    }
}

/// Enables full [`Transform`] interpolation for an entity, making changes to translation,
/// rotation, and scale in [`FixedUpdate`] appear smooth.
///
/// See the [`TransformInterpolationPlugin`] for more information.
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Default)]
#[require(TranslationInterpolation, RotationInterpolation, ScaleInterpolation)]
pub struct TransformInterpolation;

/// Enables translation interpolation for an entity, making changes to translation
/// in [`FixedUpdate`] appear smooth.
///
/// See the [`TransformInterpolationPlugin`] for more information.
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Default)]
#[require(TranslationEasingState)]
pub struct TranslationInterpolation;

/// Enables rotation interpolation for an entity, making changes to rotation
/// in [`FixedUpdate`] appear smooth.
///
/// See the [`TransformInterpolationPlugin`] for more information.
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Default)]
#[require(RotationEasingState)]
pub struct RotationInterpolation;

/// Enables scale interpolation for an entity, making changes to scale
/// in [`FixedUpdate`] appear smooth.
///
/// See the [`TransformInterpolationPlugin`] for more information.
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Default)]
#[require(ScaleEasingState)]
pub struct ScaleInterpolation;

/// Makes sure the previous translation easing is fully applied before the next easing starts.
fn complete_translation_easing(
    mut query: Query<
        (&mut Transform, &TranslationEasingState),
        (With<TranslationInterpolation>, Without<NoTranslationEasing>),
    >,
) {
    for (mut transform, easing) in &mut query {
        // Make sure the previous easing is fully applied.
        if let Some(end) = easing.end {
            transform.translation = end;
        }
    }
}

/// Makes sure the previous rotation easing is fully applied before the next easing starts.
fn complete_rotation_easing(
    mut query: Query<
        (&mut Transform, &RotationEasingState),
        (With<RotationInterpolation>, Without<NoRotationEasing>),
    >,
) {
    for (mut transform, easing) in &mut query {
        // Make sure the previous easing is fully applied.
        if let Some(end) = easing.end {
            transform.rotation = end;
        }
    }
}

/// Makes sure the previous scale easing is fully applied before the next easing starts.
fn complete_scale_easing(
    mut query: Query<
        (&mut Transform, &ScaleEasingState),
        (With<ScaleInterpolation>, Without<NoScaleEasing>),
    >,
) {
    for (mut transform, easing) in &mut query {
        // Make sure the previous easing is fully applied.
        if let Some(end) = easing.end {
            transform.scale = end;
        }
    }
}

fn update_translation_interpolation_start(
    mut query: Query<
        (&Transform, &mut TranslationEasingState),
        (With<TranslationInterpolation>, Without<NoTranslationEasing>),
    >,
) {
    for (transform, mut easing) in &mut query {
        easing.start = Some(transform.translation);
    }
}

fn update_translation_interpolation_end(
    mut query: Query<
        (&Transform, &mut TranslationEasingState),
        (With<TranslationInterpolation>, Without<NoTranslationEasing>),
    >,
) {
    for (transform, mut easing) in &mut query {
        easing.end = Some(transform.translation);
    }
}

fn update_rotation_interpolation_start(
    mut query: Query<
        (&Transform, &mut RotationEasingState),
        (With<RotationInterpolation>, Without<NoRotationEasing>),
    >,
) {
    for (transform, mut easing) in &mut query {
        easing.start = Some(transform.rotation);
    }
}

fn update_rotation_interpolation_end(
    mut query: Query<
        (&Transform, &mut RotationEasingState),
        (With<RotationInterpolation>, Without<NoRotationEasing>),
    >,
) {
    for (transform, mut easing) in &mut query {
        easing.end = Some(transform.rotation);
    }
}

fn update_scale_interpolation_start(
    mut query: Query<
        (&Transform, &mut ScaleEasingState),
        (With<ScaleInterpolation>, Without<NoScaleEasing>),
    >,
) {
    for (transform, mut easing) in &mut query {
        easing.start = Some(transform.scale);
    }
}

fn update_scale_interpolation_end(
    mut query: Query<
        (&Transform, &mut ScaleEasingState),
        (With<ScaleInterpolation>, Without<NoScaleEasing>),
    >,
) {
    for (transform, mut easing) in &mut query {
        easing.end = Some(transform.scale);
    }
}