Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Physics Interpolation and Extrapolation #566

Merged
merged 12 commits into from
Dec 7, 2024
Merged

Physics Interpolation and Extrapolation #566

merged 12 commits into from
Dec 7, 2024

Conversation

Jondolf
Copy link
Owner

@Jondolf Jondolf commented Nov 25, 2024

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! 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.

interpolation.mp4

Note: You can see that restitution doesn't work as well for low tick rates; this is expected.

Overview

PhysicsInterpolationPlugin is included in PhysicsPlugins by default, but the actual interpolation/extrapolation is opt-in.

Interpolation and extrapolation can be enabled for individual entities using the TransformInterpolation and TransformExtrapolation components respectively:

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.

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():

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 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.

@Jondolf Jondolf added the C-Enhancement New feature or request label Nov 25, 2024
@Jondolf Jondolf added this to the 0.2 milestone Nov 25, 2024
@Jondolf Jondolf added the A-Transform Relates to transforms or physics positions label Nov 25, 2024
@Mclilzee
Copy link

Mclilzee commented Dec 7, 2024

Is there a reason this isn't enabled by default using the default physics plugin? I was making the switch from rapier to avian, and I had a problem with duplication and ghosting when moving on my 144Hz monitor. I thought it's bug in the physics engine, took me a while into rabbit holes until I found this was the solution.

I'm giving you a perspective of someone who is new to all of this. If there is a reason to not turn it on by default, then it needs to be mentioned at the beginning of the guides, or troubleshooting section.

@Jondolf
Copy link
Owner Author

Jondolf commented Dec 7, 2024

@Mclilzee

I added the PhysicsInterpolationPlugin to PhysicsPlugins now, so people don't need to add the plugin manually. However, the actual interpolation must still be enabled manually for entities by adding the TransformInterpolation component, or by using PhysicsInterpolationPlugin::interpolate_all() to add it automatically for all rigid bodies.

For now, I don't think interpolation should be on by default. Just looking at some prior art, it must be enabled manually in:

  • bevy_rapier
    • You might not have seen problems in bevy_rapier, but that's because it defaults to a variable timestep for some reason, which leads to non-deterministic behavior that is dependent on frame rate. I haven't really seen any other engine do this.
  • Unity
  • Godot

Interpolation does have a small overhead, and you might not want it enabled for absolutely everything. There are also some (small) caveats currently, see the Caveats section at the end of this PR's description.

We can always reconsider the defaults in the future though once we get peoples' experiences with this and see if interpolation causes any issues in practice.

I added an FAQ section to the docs for this though, good call :)

@Jondolf Jondolf merged commit aeeb856 into main Dec 7, 2024
4 checks passed
@Jondolf Jondolf deleted the interpolation branch December 7, 2024 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Transform Relates to transforms or physics positions C-Enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support Transform interpolation
2 participants