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}