avian3d/
interpolation.rs

1//! Physics interpolation and extrapolation for rigid bodies.
2//!
3//! See [`PhysicsInterpolationPlugin`].
4
5use bevy::{ecs::query::QueryData, prelude::*};
6use bevy_transform_interpolation::{prelude::*, VelocitySource};
7
8pub use bevy_transform_interpolation::prelude::{
9    NoRotationEasing, NoScaleEasing, NoTransformEasing, NoTranslationEasing, RotationExtrapolation,
10    RotationHermiteEasing, RotationInterpolation, ScaleInterpolation, TransformExtrapolation,
11    TransformHermiteEasing, TransformInterpolation, TranslationExtrapolation,
12    TranslationHermiteEasing, TranslationInterpolation,
13};
14
15use crate::prelude::*;
16
17/// A plugin for [`Transform`] interpolation and extrapolation for rigid bodies.
18///
19/// # Overview
20///
21/// To make behavior deterministic and independent of frame rate, Avian runs physics at a fixed timestep
22/// in [`FixedPostUpdate`] by default. However, when this timestep doesn't match the display frame rate,
23/// movement can appear choppy, especially on displays with high refresh rates.
24///
25/// The conventional solution is to ease transforms in between physics ticks to smooth out the visual result.
26/// This can be done using either interpolation or extrapolation.
27///
28/// ## Interpolation
29///
30/// [`Transform`] interpolation computes a `Transform` that is somewhere in between the current position
31/// and the position from the previous physics tick. This produces smooth and accurate movement.
32///
33/// The downside of interpolation is that it causes rendering to be slightly behind the physics simulation.
34/// This can make movement feel slightly delayed, but this is rarely noticeable unless using a very small
35/// physics tick rate.
36///
37/// ## Extrapolation
38///
39/// [`Transform`] extrapolation computes a `Transform` that is somewhere in between the current position
40/// and a future position predicted based on velocity. This produces movement that looks smooth and feels
41/// very responsive.
42///
43/// The downside of extrapolation is that it can be less accurate. When the prediction is wrong, the rendered
44/// positions may jump to correct the mispredictions. This can be noticeable when the entity changes direction
45/// or speed rapidly.
46///
47/// Extrapolation is primarily inteded for cases where low latency and high responsiveness are crucial for gameplay,
48/// such as first-person shooters and racing games. For most other games, interpolation is often the better choice.
49///
50/// # Usage
51///
52/// The [`PhysicsInterpolationPlugin`] is included in the [`PhysicsPlugins`] by default,
53/// so most apps don't need to add it manually.
54///
55/// [`Transform`] interpolation and extrapolation can be enabled for individual entities
56/// using the [`TransformInterpolation`] and [`TransformExtrapolation`] components respectively:
57///
58/// ```
59#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
60#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
61/// use bevy::prelude::*;
62///
63/// fn setup(mut commands: Commands) {
64///     // Enable interpolation for this rigid body.
65///     commands.spawn((
66///         RigidBody::Dynamic,
67///         Transform::default(),
68///         TransformInterpolation,
69///     ));
70///
71///     // Enable extrapolation for this rigid body.
72///     commands.spawn((
73///         RigidBody::Dynamic,
74///         Transform::default(),
75///         TransformExtrapolation,
76///     ));
77/// }
78/// ```
79///
80/// Now, any changes made to the [`Transform`] of the entity in [`FixedPreUpdate`], [`FixedUpdate`],
81/// or [`FixedPostUpdate`] will automatically be smoothed in between fixed timesteps.
82///
83/// Transform properties can also be interpolated individually by adding the [`TranslationInterpolation`],
84/// [`RotationInterpolation`], and [`ScaleInterpolation`] components, and similarly for extrapolation.
85///
86/// ```
87#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
88#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
89/// # use bevy::prelude::*;
90/// #
91/// fn setup(mut commands: Commands) {
92///     // Only interpolate translation.
93///     commands.spawn((Transform::default(), TranslationInterpolation));
94///     
95///     // Only interpolate rotation.
96///     commands.spawn((Transform::default(), RotationInterpolation));
97///     
98///     // Only interpolate scale.
99///     commands.spawn((Transform::default(), ScaleInterpolation));
100///     
101///     // Mix and match!
102///     // Extrapolate translation and interpolate rotation.
103///     commands.spawn((
104///         Transform::default(),
105///         TranslationExtrapolation,
106///         RotationInterpolation,
107///     ));
108/// }
109/// ```
110///
111/// If you want *all* rigid bodies to be interpolated or extrapolated by default, you can use
112/// [`PhysicsInterpolationPlugin::interpolate_all()`] or [`PhysicsInterpolationPlugin::extrapolate_all()`]:
113///
114/// ```no_run
115#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
116#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
117/// # use bevy::prelude::*;
118/// #
119/// fn main() {
120///    App::new()
121///       .add_plugins(PhysicsPlugins::default().set(PhysicsInterpolationPlugin::interpolate_all()))
122///       // ...
123///       .run();
124/// }
125/// ```
126///
127/// When interpolation or extrapolation is enabled for all entities by default, you can still opt out of it
128/// for individual entities by adding the [`NoTransformEasing`] component, or the individual
129/// [`NoTranslationEasing`], [`NoRotationEasing`], and [`NoScaleEasing`] components.
130///
131/// Note that changing [`Transform`] manually in any schedule that *doesn't* use a fixed timestep is also supported,
132/// but it is equivalent to teleporting, and disables interpolation for the entity for the remainder of that fixed timestep.
133///
134/// ## Hermite Interpolation
135///
136/// By default, *linear interpolation* (`lerp`) is used for easing translation and scale,
137/// and *spherical linear interpolation* (`slerp`) is used for easing rotation.
138/// This is computationally efficient and works well for most cases.
139///
140/// However, linear interpolation doesn't consider velocity, which can make trajectories look less smooth
141/// at low tick rates. Very high angular velocities (ex: for car wheels or fan blades) can be especially problematic,
142/// as `slerp` always takes the shortest path between two rotations, which can sometimes cause entities to rotate
143/// in the opposite direction.
144///
145/// Unlike linear interpolation, *Hermite interpolation* uses both position and velocity information
146/// to estimate the trajectories of entities, producing smoother results. To enable it for interpolation
147/// or extrapolation, add the [`TransformHermiteEasing`] component or the individual [`TranslationHermiteEasing`]
148/// and [`RotationHermiteEasing`] components:
149///
150/// ```
151#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
152#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
153/// # use bevy::prelude::*;
154/// #
155/// fn setup(mut commands: Commands) {
156///     // Enable Hermite interpolation for this rigid body.
157///     commands.spawn((
158///         RigidBody::Dynamic,
159///         Transform::default(),
160///         TransformInterpolation,
161///         TransformHermiteEasing,
162///     ));
163/// }
164/// ```
165///
166/// Hermite interpolation is more expensive than linear interpolation, so it is generally recommended
167/// to only use it when it produces noticeable benefits. For most cases, linear interpolation should be sufficient.
168///
169/// Note that scale interpolation is always linear, and does not support Hermite interpolation.
170///
171/// # General Interpolation or Extrapolation
172///
173/// Avian uses [`bevy_transform_interpolation`] for interpolation and extrapolation.
174/// It is not limited to physics entities, so it is actually possible to use the components
175/// shown here for interpolating the [`Transform`] of *any* entity!
176///
177/// Refer to the [`bevy_transform_interpolation`] documentation for more information on how to use it.
178#[derive(Debug, Default)]
179pub struct PhysicsInterpolationPlugin {
180    interpolate_translation_all: bool,
181    interpolate_rotation_all: bool,
182    extrapolate_translation_all: bool,
183    extrapolate_rotation_all: bool,
184}
185
186impl PhysicsInterpolationPlugin {
187    /// Enables interpolation of translation and rotation for all rigid bodies.
188    ///
189    /// This can be overridden for individual entities by adding the [`NoTransformEasing`] component,
190    /// or the individual [`NoTranslationEasing`] and [`NoRotationEasing`] components.
191    pub const fn interpolate_all() -> Self {
192        Self {
193            interpolate_translation_all: true,
194            interpolate_rotation_all: true,
195            extrapolate_translation_all: false,
196            extrapolate_rotation_all: false,
197        }
198    }
199
200    /// Enables interpolation of translation for all rigid bodies.
201    ///
202    /// This can be overridden for individual entities by adding the [`NoTranslationEasing`] component.
203    pub const fn interpolate_translation_all() -> Self {
204        Self {
205            interpolate_translation_all: true,
206            interpolate_rotation_all: false,
207            extrapolate_translation_all: false,
208            extrapolate_rotation_all: false,
209        }
210    }
211
212    /// Enables interpolation of rotation for all rigid bodies.
213    ///
214    /// This can be overridden for individual entities by adding the [`NoRotationEasing`] component.
215    pub const fn interpolate_rotation_all() -> Self {
216        Self {
217            interpolate_translation_all: false,
218            interpolate_rotation_all: true,
219            extrapolate_translation_all: false,
220            extrapolate_rotation_all: false,
221        }
222    }
223
224    /// Enables extrapolation of translation and rotation for all rigid bodies.
225    ///
226    /// This can be overridden for individual entities by adding the [`NoTransformEasing`] component,
227    /// or the individual [`NoTranslationEasing`] and [`NoRotationEasing`] components.
228    pub const fn extrapolate_all() -> Self {
229        Self {
230            interpolate_translation_all: false,
231            interpolate_rotation_all: false,
232            extrapolate_translation_all: true,
233            extrapolate_rotation_all: true,
234        }
235    }
236
237    /// Enables extrapolation of translation for all rigid bodies.
238    ///
239    /// This can be overridden for individual entities by adding the [`NoTranslationEasing`] component.
240    pub const fn extrapolate_translation_all() -> Self {
241        Self {
242            interpolate_translation_all: false,
243            interpolate_rotation_all: false,
244            extrapolate_translation_all: true,
245            extrapolate_rotation_all: false,
246        }
247    }
248
249    /// Enables extrapolation of rotation for all rigid bodies.
250    ///
251    /// This can be overridden for individual entities by adding the [`NoRotationEasing`] component.
252    pub const fn extrapolate_rotation_all() -> Self {
253        Self {
254            interpolate_translation_all: false,
255            interpolate_rotation_all: false,
256            extrapolate_translation_all: false,
257            extrapolate_rotation_all: true,
258        }
259    }
260}
261
262impl Plugin for PhysicsInterpolationPlugin {
263    fn build(&self, app: &mut App) {
264        app.add_plugins((
265            TransformInterpolationPlugin::default(),
266            TransformExtrapolationPlugin::<LinVelSource, AngVelSource>::default(),
267            TransformHermiteEasingPlugin::<LinVelSource, AngVelSource>::default(),
268        ));
269
270        // Make the previous velocity components required for Hermite interpolation to insert them automatically.
271        app.register_required_components::<TranslationHermiteEasing, PreviousLinearVelocity>();
272        app.register_required_components::<RotationHermiteEasing, PreviousAngularVelocity>();
273
274        // Enable interpolation for all entities with a rigid body.
275        if self.interpolate_translation_all {
276            let _ = app.try_register_required_components::<RigidBody, TranslationInterpolation>();
277        }
278        if self.interpolate_rotation_all {
279            let _ = app.try_register_required_components::<RigidBody, RotationInterpolation>();
280        }
281
282        // Enable extrapolation for all entities with a rigid body.
283        if self.extrapolate_translation_all {
284            let _ = app.try_register_required_components::<RigidBody, TranslationExtrapolation>();
285        }
286        if self.extrapolate_rotation_all {
287            let _ = app.try_register_required_components::<RigidBody, RotationExtrapolation>();
288        }
289
290        // Update previous velocity components for Hermite interpolation.
291        app.add_systems(
292            PhysicsSchedule,
293            update_previous_velocity.in_set(PhysicsStepSet::First),
294        );
295    }
296}
297
298/// The previous linear velocity of an entity indicating its movement speed and direction during the previous frame.
299#[derive(Component, Default, Deref, DerefMut)]
300struct PreviousLinearVelocity(Vector);
301
302/// The previous angular velocity of an entity indicating its rotation speed during the previous frame.
303#[derive(Component, Default, Deref, DerefMut)]
304struct PreviousAngularVelocity(AngularVelocity);
305
306#[derive(QueryData)]
307struct LinVelSource;
308
309impl VelocitySource for LinVelSource {
310    type Previous = PreviousLinearVelocity;
311    type Current = LinearVelocity;
312
313    fn previous(previous: &Self::Previous) -> Vec3 {
314        #[cfg(feature = "2d")]
315        {
316            previous.f32().extend(0.0)
317        }
318        #[cfg(feature = "3d")]
319        {
320            previous.f32()
321        }
322    }
323
324    fn current(current: &Self::Current) -> Vec3 {
325        #[cfg(feature = "2d")]
326        {
327            current.0.f32().extend(0.0)
328        }
329        #[cfg(feature = "3d")]
330        {
331            current.0.f32()
332        }
333    }
334}
335
336#[derive(QueryData)]
337struct AngVelSource;
338
339#[allow(clippy::unnecessary_cast)]
340impl VelocitySource for AngVelSource {
341    type Previous = PreviousAngularVelocity;
342    type Current = AngularVelocity;
343
344    fn previous(previous: &Self::Previous) -> Vec3 {
345        #[cfg(feature = "2d")]
346        {
347            Vec3::Z * previous.0 .0 as f32
348        }
349        #[cfg(feature = "3d")]
350        {
351            previous.0.f32()
352        }
353    }
354
355    fn current(current: &Self::Current) -> Vec3 {
356        #[cfg(feature = "2d")]
357        {
358            Vec3::Z * current.0 as f32
359        }
360        #[cfg(feature = "3d")]
361        {
362            current.0.f32()
363        }
364    }
365}
366
367fn update_previous_velocity(
368    mut lin_vel_query: Query<(&LinearVelocity, &mut PreviousLinearVelocity)>,
369    mut ang_vel_query: Query<(&AngularVelocity, &mut PreviousAngularVelocity)>,
370) {
371    for (lin_vel, mut prev_lin_vel) in &mut lin_vel_query {
372        prev_lin_vel.0 = lin_vel.0;
373    }
374
375    for (ang_vel, mut prev_ang_vel) in &mut ang_vel_query {
376        prev_ang_vel.0 = *ang_vel;
377    }
378}