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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

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

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

  • 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.
  • The extrapolation currently doesn't integrate velocity for the prediction, so it won't account for gravity or external forces.

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.

@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
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
1 participant