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