avian3d/
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
//! Physics interpolation and extrapolation for rigid bodies.
//!
//! See [`PhysicsInterpolationPlugin`].

use bevy::{ecs::query::QueryData, prelude::*};
use bevy_transform_interpolation::{prelude::*, VelocitySource};

pub use bevy_transform_interpolation::prelude::{
    NoRotationEasing, NoScaleEasing, NoTransformEasing, NoTranslationEasing, RotationExtrapolation,
    RotationHermiteEasing, RotationInterpolation, ScaleInterpolation, TransformExtrapolation,
    TransformHermiteEasing, TransformInterpolation, TranslationExtrapolation,
    TranslationHermiteEasing, TranslationInterpolation,
};

use crate::prelude::*;

/// A plugin for [`Transform`] interpolation and extrapolation for rigid bodies.
///
/// # Overview
///
/// To make behavior deterministic and independent of frame rate, Avian runs physics at a fixed timestep
/// in [`FixedPostUpdate`] by default. However, when this timestep doesn't match the display frame rate,
/// movement can appear choppy, especially on displays with high refresh rates.
///
/// The conventional solution is to ease transforms in between physics ticks to smooth out the visual result.
/// This can be done using either interpolation or extrapolation.
///
/// ## Interpolation
///
/// [`Transform`] interpolation computes a `Transform` that is somewhere in between the current position
/// and the position from the previous physics tick. This produces smooth and accurate movement.
///
/// The downside of interpolation is that it causes rendering to be slightly behind the physics simulation.
/// This can make movement feel slightly delayed, but this is rarely noticeable unless using a very small
/// physics tick rate.
///
/// ## Extrapolation
///
/// [`Transform`] extrapolation computes a `Transform` that is somewhere in between the current position
/// and a future position predicted based on velocity. This produces movement that looks smooth and feels
/// very responsive.
///
/// The downside of extrapolation is that it can be less accurate. When the prediction is wrong, the rendered
/// positions may jump to correct the mispredictions. This can be noticeable when the entity changes direction
/// or speed rapidly.
///
/// Extrapolation is primarily inteded for cases where low latency and high responsiveness are crucial for gameplay,
/// such as first-person shooters and racing games. For most other games, interpolation is often the better choice.
///
/// # Usage
///
/// The [`PhysicsInterpolationPlugin`] is included in the [`PhysicsPlugins`] by default,
/// so most apps don't need to add it manually.
///
/// [`Transform`] interpolation and extrapolation can be enabled for individual entities
/// using the [`TransformInterpolation`] and [`TransformExtrapolation`] components respectively:
///
/// ```
#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
/// use bevy::prelude::*;
///
/// fn setup(mut commands: Commands) {
///     // Enable interpolation for this rigid body.
///     commands.spawn((
///         RigidBody::Dynamic,
///         Transform::default(),
///         TransformInterpolation,
///     ));
///
///     // Enable extrapolation for this rigid body.
///     commands.spawn((
///         RigidBody::Dynamic,
///         Transform::default(),
///         TransformExtrapolation,
///     ));
/// }
/// ```
///
/// 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, and similarly for extrapolation.
///
/// ```
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
/// # use bevy::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));
///     
///     // Mix and match!
///     // Extrapolate translation and interpolate rotation.
///     commands.spawn((
///         Transform::default(),
///         TranslationExtrapolation,
///         RotationInterpolation,
///     ));
/// }
/// ```
///
/// If you want *all* rigid bodies to be interpolated or extrapolated by default, you can use
/// [`PhysicsInterpolationPlugin::interpolate_all()`] or [`PhysicsInterpolationPlugin::extrapolate_all()`]:
///
/// ```no_run
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
/// # use bevy::prelude::*;
/// #
/// fn main() {
///    App::new()
///       .add_plugins(PhysicsPlugins::default().set(PhysicsInterpolationPlugin::interpolate_all()))
///       // ...
///       .run();
/// }
/// ```
///
/// When interpolation or extrapolation 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.
///
/// ## Hermite Interpolation
///
/// By default, *linear interpolation* (`lerp`) is used for easing translation and scale,
/// and *spherical linear interpolation* (`slerp`) is used for easing rotation.
/// This is computationally efficient and works well for most cases.
///
/// However, linear interpolation doesn't consider velocity, which can make trajectories look less smooth
/// at low tick rates. Very high angular velocities (ex: for car wheels or fan blades) can be especially problematic,
/// as `slerp` always takes the shortest path between two rotations, which can sometimes cause entities to rotate
/// in the opposite direction.
///
/// Unlike linear interpolation, *Hermite interpolation* uses both position and velocity information
/// to estimate the trajectories of entities, producing smoother results. To enable it for interpolation
/// or extrapolation, add the [`TransformHermiteEasing`] component or the individual [`TranslationHermiteEasing`]
/// and [`RotationHermiteEasing`] components:
///
/// ```
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
/// # use bevy::prelude::*;
/// #
/// fn setup(mut commands: Commands) {
///     // Enable Hermite interpolation for this rigid body.
///     commands.spawn((
///         RigidBody::Dynamic,
///         Transform::default(),
///         TransformInterpolation,
///         TransformHermiteEasing,
///     ));
/// }
/// ```
///
/// Hermite interpolation is more expensive than linear interpolation, so it is generally recommended
/// to only use it when it produces noticeable benefits. For most cases, linear interpolation should be sufficient.
///
/// Note that scale interpolation is always linear, and does not support Hermite interpolation.
///
/// # General Interpolation or Extrapolation
///
/// Avian uses [`bevy_transform_interpolation`] for interpolation and extrapolation.
/// It is not limited to physics entities, so it is actually possible to use the components
/// shown here for interpolating the [`Transform`] of *any* entity!
///
/// Refer to the [`bevy_transform_interpolation`] documentation for more information on how to use it.
#[derive(Debug, Default)]
pub struct PhysicsInterpolationPlugin {
    interpolate_translation_all: bool,
    interpolate_rotation_all: bool,
    extrapolate_translation_all: bool,
    extrapolate_rotation_all: bool,
}

impl PhysicsInterpolationPlugin {
    /// Enables interpolation of translation and rotation for all rigid bodies.
    ///
    /// This can be overridden for individual entities by adding the [`NoTransformEasing`] component,
    /// or the individual [`NoTranslationEasing`] and [`NoRotationEasing`] components.
    pub const fn interpolate_all() -> Self {
        Self {
            interpolate_translation_all: true,
            interpolate_rotation_all: true,
            extrapolate_translation_all: false,
            extrapolate_rotation_all: false,
        }
    }

    /// Enables interpolation of translation for all rigid bodies.
    ///
    /// This can be overridden for individual entities by adding the [`NoTranslationEasing`] component.
    pub const fn interpolate_translation_all() -> Self {
        Self {
            interpolate_translation_all: true,
            interpolate_rotation_all: false,
            extrapolate_translation_all: false,
            extrapolate_rotation_all: false,
        }
    }

    /// Enables interpolation of rotation for all rigid bodies.
    ///
    /// This can be overridden for individual entities by adding the [`NoRotationEasing`] component.
    pub const fn interpolate_rotation_all() -> Self {
        Self {
            interpolate_translation_all: false,
            interpolate_rotation_all: true,
            extrapolate_translation_all: false,
            extrapolate_rotation_all: false,
        }
    }

    /// Enables extrapolation of translation and rotation for all rigid bodies.
    ///
    /// This can be overridden for individual entities by adding the [`NoTransformEasing`] component,
    /// or the individual [`NoTranslationEasing`] and [`NoRotationEasing`] components.
    pub const fn extrapolate_all() -> Self {
        Self {
            interpolate_translation_all: false,
            interpolate_rotation_all: false,
            extrapolate_translation_all: true,
            extrapolate_rotation_all: true,
        }
    }

    /// Enables extrapolation of translation for all rigid bodies.
    ///
    /// This can be overridden for individual entities by adding the [`NoTranslationEasing`] component.
    pub const fn extrapolate_translation_all() -> Self {
        Self {
            interpolate_translation_all: false,
            interpolate_rotation_all: false,
            extrapolate_translation_all: true,
            extrapolate_rotation_all: false,
        }
    }

    /// Enables extrapolation of rotation for all rigid bodies.
    ///
    /// This can be overridden for individual entities by adding the [`NoRotationEasing`] component.
    pub const fn extrapolate_rotation_all() -> Self {
        Self {
            interpolate_translation_all: false,
            interpolate_rotation_all: false,
            extrapolate_translation_all: false,
            extrapolate_rotation_all: true,
        }
    }
}

impl Plugin for PhysicsInterpolationPlugin {
    fn build(&self, app: &mut App) {
        app.add_plugins((
            TransformInterpolationPlugin::default(),
            TransformExtrapolationPlugin::<LinVelSource, AngVelSource>::default(),
            TransformHermiteEasingPlugin::<LinVelSource, AngVelSource>::default(),
        ));

        // Make the previous velocity components required for Hermite interpolation to insert them automatically.
        app.register_required_components::<TranslationHermiteEasing, PreviousLinearVelocity>();
        app.register_required_components::<RotationHermiteEasing, PreviousAngularVelocity>();

        // Enable interpolation for all entities with a rigid body.
        if self.interpolate_translation_all {
            let _ = app.try_register_required_components::<RigidBody, TranslationInterpolation>();
        }
        if self.interpolate_rotation_all {
            let _ = app.try_register_required_components::<RigidBody, RotationInterpolation>();
        }

        // Enable extrapolation for all entities with a rigid body.
        if self.extrapolate_translation_all {
            let _ = app.try_register_required_components::<RigidBody, TranslationExtrapolation>();
        }
        if self.extrapolate_rotation_all {
            let _ = app.try_register_required_components::<RigidBody, RotationExtrapolation>();
        }

        // Update previous velocity components for Hermite interpolation.
        app.add_systems(
            PhysicsSchedule,
            update_previous_velocity.in_set(PhysicsStepSet::First),
        );
    }
}

/// The previous linear velocity of an entity indicating its movement speed and direction during the previous frame.
#[derive(Component, Default, Deref, DerefMut)]
struct PreviousLinearVelocity(Vector);

/// The previous angular velocity of an entity indicating its rotation speed during the previous frame.
#[derive(Component, Default, Deref, DerefMut)]
struct PreviousAngularVelocity(AngularVelocity);

#[derive(QueryData)]
struct LinVelSource;

impl VelocitySource for LinVelSource {
    type Previous = PreviousLinearVelocity;
    type Current = LinearVelocity;

    fn previous(previous: &Self::Previous) -> Vec3 {
        #[cfg(feature = "2d")]
        {
            previous.f32().extend(0.0)
        }
        #[cfg(feature = "3d")]
        {
            previous.f32()
        }
    }

    fn current(current: &Self::Current) -> Vec3 {
        #[cfg(feature = "2d")]
        {
            current.0.f32().extend(0.0)
        }
        #[cfg(feature = "3d")]
        {
            current.0.f32()
        }
    }
}

#[derive(QueryData)]
struct AngVelSource;

#[allow(clippy::unnecessary_cast)]
impl VelocitySource for AngVelSource {
    type Previous = PreviousAngularVelocity;
    type Current = AngularVelocity;

    fn previous(previous: &Self::Previous) -> Vec3 {
        #[cfg(feature = "2d")]
        {
            Vec3::Z * previous.0 .0 as f32
        }
        #[cfg(feature = "3d")]
        {
            previous.0.f32()
        }
    }

    fn current(current: &Self::Current) -> Vec3 {
        #[cfg(feature = "2d")]
        {
            Vec3::Z * current.0 as f32
        }
        #[cfg(feature = "3d")]
        {
            current.0.f32()
        }
    }
}

fn update_previous_velocity(
    mut lin_vel_query: Query<(&LinearVelocity, &mut PreviousLinearVelocity)>,
    mut ang_vel_query: Query<(&AngularVelocity, &mut PreviousAngularVelocity)>,
) {
    for (lin_vel, mut prev_lin_vel) in &mut lin_vel_query {
        prev_lin_vel.0 = lin_vel.0;
    }

    for (ang_vel, mut prev_ang_vel) in &mut ang_vel_query {
        prev_ang_vel.0 = *ang_vel;
    }
}