Jondolf / avian

ECS-driven 2D and 3D physics engine for the Bevy game engine.
https://crates.io/crates/avian3d
Apache License 2.0
1.56k stars 120 forks source link

Physics Interpolation and Extrapolation #566

Open Jondolf opened 4 days ago

Jondolf commented 4 days ago

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:

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:

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:

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

Note

This PR should probably not be merged yet, as bevy_transform_interpolation hasn't been released on crates.io yet, and this PR is also depending on a branch that is blocked on either Bevy 0.15 or a new RC releasing. I will merge this for the upcoming Avian release though.