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

Interpolate simulation state at render time #19

Merged
merged 6 commits into from
Oct 18, 2020

Conversation

superdump
Copy link
Contributor

@superdump superdump commented Oct 17, 2020

This commit follows the suggestion from:

https://www.gafferongames.com/post/fix_your_timestep/

The problem is that the simulation is stepped with a fixed time step, as it
should be to maintain simulation stability. However, the time step of each game
loop may vary based on external factors to the physics engine. For example, if
the timestep is 1/60 but the game loop iterates at a faster rate, then the
physics simulation will appear to advance faster than real-time.

The idea for the solution is that the renderer / game loop 'produces' time, and
the simulation consumes it. As such, a new resource has been added to keep
track of the amount of time produced and consumed. It represents how far ahead
of the simulation the rendering is.

The step_world_system has been modified to add Time.delta_seconds to the
SimulationToRenderTime resource, and then if there is more than a time step of
time to simulate, we loop through stepping the simulation and subtracting the
simulation time step from the resource until there is less than a time step of
time remaining to simulate.

As noted in the article, if this was all we did then the motion of objects due
to physics would look jittery. We have to interpolate the positions and
orientations of simulated objects between each simulation step. The
sync_transform_system has been modified to lerp and slerp the position and rotation of
each body based on its previous and current simulation position and rotation.

This commit follows the suggestion from:

https://www.gafferongames.com/post/fix_your_timestep/

The problem is that the simulation is stepped with a fixed time step, as it
should be to maintain simulation stability. However, the time step of each game
loop may vary based on external factors to the physics engine. For example, if
the timestep is 1/60 but the game loop iterates at a faster rate, then the
physics simulation will appear to advance faster than real-time.

The idea for the solution is that the renderer / game loop 'produces' time, and
the simulation consumes it. As such, a new resource has been added to keep
track of the amount of time produced and consumed. It represents how far ahead
of the simulation the rendering is.

The step_world_system has been modified to add Time.delta_seconds to the
SimulationToRenderTime resource, and then if there is more than a time step of
time to simulate, we loop through stepping the simulation and subtracting the
simulation time step from the resource until there is less than a time step of
time remaining to simulate.

As noted in the article, if this was all we did then the motion of objects due
to physics would look jittery. We have to interpolate the positions and
orientations of simulated objects between each simulation step. The
sync_transform_system has been modified to predict the position and rotation of
each body based on its current position and rotation, linear velocity, and
angular velocity. Ideally the linear and angular accelerations would be taken
into account but these are not public.
@@ -60,12 +60,11 @@ pub fn create_joints_system(

/// System responsible for performing one timestep of the physics world.
pub fn step_world_system(
(time, mut sim_to_render_time): (Res<Time>, ResMut<SimulationToRenderTime>),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that these variables had to be made into tuples of resources to work around the limit of 10 parameters to a Bevy system function.

src/physics/systems.rs Outdated Show resolved Hide resolved
rb.position.translation.vector.y,
);
let vel_0 = Vec2::new(rb.linvel.x, rb.linvel.y);
let pos_t = pos_0 + vel_0 * dt;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like extrapolation rather than interpolation, which could show an entity at a position it will never be at in case of sudden turns.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. So then input events between simulation steps need to be taken into account for the rendering.

I was thinking it would be useful to ask the physics engine to predict the position of the body at t seconds ahead of where the simulation is at.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to take acceleration state into account for the extrapolation as well but those fields weren’t accessible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the gaffer on games article again and it seems they suggest interpolating between the previous physics state and the current based on the time remaining in the accumulator. This seems backward in time to me. We just simulated the current state based on input that happened before the current simulation time, whatever remains in the accumulator is time into the future as far as the simulation is concerned, but the article suggests interpolating between two past points in time. It seems it’s suggesting to introduce a time step of lag.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now rewritten to do what the article suggested - store the previous position and rotation, lerp the position, and slerp the rotation. It introduces a physics time step of lag though. :/

Copy link
Member

@sebcrozet sebcrozet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this PR.

The extrapolation part is slightly wrong. The angular velocity of a rigid-body is expressed at its center-of-mass. So the rotation cannot be integrated by just multiplying the angular velocity by dt. In fact it should be taken into account the way we do in Rapier there.

Anyaway, @kabergstrom pointed out, what you are doing here is an extrapolation, which will always yield wrong positions because it ignores all contacts/joints. What it done in the GafferOnGames article is an interpolation between the last state and the current state. So instead of integrating the velocities, I think you should get the new rigid-body position, and interpolate between the previous transform and the new one.

@superdump superdump changed the title Predict simulation state at render time Interpolate simulation state at render time Oct 18, 2020
src/physics/systems.rs Outdated Show resolved Hide resolved
src/physics/systems.rs Outdated Show resolved Hide resolved
@sebcrozet sebcrozet merged commit 65d24d3 into dimforge:master Oct 18, 2020
@sebcrozet
Copy link
Member

All good, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants