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}