Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Physics Interpolation and Extrapolation (#566)
# Objective Closes #444. To produce frame rate independent behavior and deterministic results, Avian runs at a fixed timestep in `FixedPostUpdate` by default. However, this can often lead to visual stutter when the fixed timestep does not match the display refresh rate, especially at low physics tick rates. Avian should support `Transform` interpolation to visually smooth out movement in between fixed timesteps. ## Solution Add a `PhysicsInterpolationPlugin` powered by my new crate [`bevy_transform_interpolation`](https://github.com/Jondolf/bevy_transform_interpolation)! It supports: - Transform interpolation and extrapolation - Granularly interpolating only specific transform properties - Optional Hermite interpolation to produce more accurate easing and fix visual interpolation artifacts caused by very large angular velocities (ex: for car wheels or fan blades) - Custom easing backends A new `interpolation` example has been added to demonstrate the new interpolation and extrapolation functionality. https://github.com/user-attachments/assets/0eac03ac-f8b3-4b82-b828-d36c0976a7cc Note: You can see that restitution doesn't work as well for low tick rates; this is expected. ### Overview To enable interpolation/extrapolation functionality, add the `PhysicsInterpolationPlugin`: ```rust fn main() { App::new() .add_plugins(( DefaultPlugins, PhysicsPlugins::default(), PhysicsInterpolationPlugin::default(), )) // ...other plugins, resources, and systems .run(); } ``` Interpolation and extrapolation can be enabled for individual entities using the `TransformInterpolation` and `TransformExtrapolation` components respectively: ```rust 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. ```rust 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()`: ```rust fn main() { App::build() .add_plugins(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. ## Caveats - [`big_space`](https://github.com/aevyrie/big_space) should sort of work with `bevy_transform_interpolation`, but transitions between grid cells aren't eased correctly. Avian itself doesn't support `big_space` yet either, but it's something to keep in mind. This should be fixable on the `bevy_transform_interpolation` side. - `bevy_transform_interpolation` technically stores duplicate position data, since we could use the existing `Position` and `Rotation` components for the current "gameplay transform". However, these physics components are in global space while `Transform` isn't, which could complicate hierarchies. For now, I chose to accept this small amount of duplication; if it is an issue, we could make `bevy_transform_interpolation` accept arbitrary "position sources" similar to the "velocity sources" it already has. - There are instances where you want to "teleport" entities without interpolation. For now, I chose to make transform changes in non-fixed schedules teleport, but we might want to consider alternative approaches too, like calling some command or adding a marker component to skip interpolation for one fixed tick. - The extrapolation currently doesn't integrate velocity for the prediction, so it won't account for gravity or external forces.
- Loading branch information