Skip to content

Commit

Permalink
feat: Variable timestep mode for PhysicsSteps (jcornaz#123)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `PhysicsSteps::duration` now returns a `PhysicsDuration` instead of `Duration`
  • Loading branch information
zicklag authored Jul 10, 2021
1 parent 3699075 commit 4a646d7
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 13 deletions.
2 changes: 1 addition & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub use events::{CollisionData, CollisionEvent};
pub use gravity::Gravity;
pub use layers::{CollisionLayers, PhysicsLayer};
pub use physics_time::PhysicsTime;
pub use step::PhysicsSteps;
pub use step::{PhysicsStepDuration, PhysicsSteps};
pub use velocity::{Acceleration, AxisAngle, Velocity};

mod constraints;
Expand Down
72 changes: 64 additions & 8 deletions core/src/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,35 @@ use bevy::prelude::*;
pub struct PhysicsSteps(Mode);

enum Mode {
MaxDeltaTime(Duration),
EveryFrame(Duration),
Timer(Timer),
}

impl Default for PhysicsSteps {
fn default() -> Self {
Self::from_steps_per_seconds(58.0)
Self::from_max_delta_time(Duration::from_secs_f32(0.2) /* 50 FPS */)
}
}

/// The duration of time that this physics step should advance the simulation time
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PhysicsStepDuration {
/// The simulation time should be advanced by the provided exact duration
Exact(Duration),
/// The simulation time should be advanced by this frame's delta-time or the max delta time if
/// the delta time is greater than the max
MaxDeltaTime(Duration),
}

impl PhysicsStepDuration {
/// Get the exact duration of this physics step, provided the delta-time
#[must_use]
pub fn exact(&self, delta_time: Duration) -> Duration {
match self {
PhysicsStepDuration::Exact(duration) => *duration,
PhysicsStepDuration::MaxDeltaTime(max) => delta_time.min(*max),
}
}
}

Expand Down Expand Up @@ -63,9 +85,8 @@ impl PhysicsSteps {
Self(Mode::Timer(Timer::new(duration, true)))
}

/// Configure the physics systems to run at each and every frame. Regardless of the current FPS.
///
/// It takes a duration which is "haw much" the physics simulation should advance at each frame.
/// Configure the physics systems to run at each and every frame, advancing the simulation the
/// same amount of time each frame.
///
/// Should NOT be used in production. It is mostly useful for testing purposes.
///
Expand All @@ -78,21 +99,56 @@ impl PhysicsSteps {
Self(Mode::EveryFrame(duration))
}

/// Step the physics simulation every frame, advancing the simulation according to the frame
/// delta time, as long as the delta time is not above a provided maximum.
///
/// This is the default setting of [`PhysicsSteps`] with a max duration set to 20 ms ( 50 FPS ).
///
/// Because it runs the physics step every frame, this physics step mode is precise, but will
/// slow down if the frame delta time is higher than the provided `max` duration.
///
/// The purpose of setting the max duration is to prevent objects from going through walls, etc.
/// in the case that the frame rate drops significantly.
///
/// By setting the max duration to `Duration::MAX`, the simulation speed will not slow down,
/// regardless of the frame rate, but if the frame rate gets too low, objects may begin to pass
/// through each-other because they may travel completely to the other side of a collision
/// object in a single frame, depending on their velocity.
///
/// # Example
///
/// ```no_run
/// # use bevy::prelude::*;
/// # use heron_core::PhysicsSteps;
/// # use std::time::Duration;
/// App::build()
/// // Runs physics step every frame.
/// // If the frame rate drops bellow 30 FPS, then the physics simulation will slow down.
/// .insert_resource(PhysicsSteps::from_max_delta_time(Duration::from_secs_f64(1.0 / 30.0)))
/// // ...
/// .run();
/// ```
#[must_use]
pub fn from_max_delta_time(max: Duration) -> Self {
Self(Mode::MaxDeltaTime(max))
}

/// Returns true only if the current frame is a frame that execute a physics simulation step
#[must_use]
pub fn is_step_frame(&self) -> bool {
match &self.0 {
Mode::EveryFrame(_) => true,
Mode::EveryFrame(_) | Mode::MaxDeltaTime(_) => true,
Mode::Timer(timer) => timer.just_finished(),
}
}

/// Time that elapses between each physics step
#[must_use]
pub fn duration(&self) -> Duration {
pub fn duration(&self) -> PhysicsStepDuration {
match &self.0 {
Mode::EveryFrame(duration) => *duration,
Mode::Timer(timer) => timer.duration(),
Mode::EveryFrame(duration) => PhysicsStepDuration::Exact(*duration),
Mode::Timer(timer) => PhysicsStepDuration::Exact(timer.duration()),
Mode::MaxDeltaTime(max) => PhysicsStepDuration::MaxDeltaTime(*max),
}
}

Expand Down
18 changes: 15 additions & 3 deletions rapier/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use bevy::log::prelude::*;
use bevy::math::Vec3;
use crossbeam::channel::{Receiver, Sender};

use heron_core::{CollisionData, CollisionEvent, Gravity, PhysicsSteps, PhysicsTime};
use heron_core::{
CollisionData, CollisionEvent, Gravity, PhysicsStepDuration, PhysicsSteps, PhysicsTime,
};

use crate::convert::{IntoBevy, IntoRapier};
use crate::rapier::dynamics::{
Expand All @@ -18,10 +20,20 @@ use crate::rapier::pipeline::{EventHandler, PhysicsPipeline};
pub(crate) fn update_integration_parameters(
physics_steps: Res<'_, PhysicsSteps>,
physics_time: Res<'_, PhysicsTime>,
bevy_time: Res<'_, bevy::core::Time>,
mut integration_parameters: ResMut<'_, IntegrationParameters>,
) {
if physics_steps.is_changed() || physics_time.is_changed() {
integration_parameters.dt = physics_steps.duration().as_secs_f32() * physics_time.scale();
if matches!(
physics_steps.duration(),
PhysicsStepDuration::MaxDeltaTime(_)
) || physics_steps.is_changed()
|| physics_time.is_changed()
{
integration_parameters.dt = physics_steps
.duration()
.exact(bevy_time.delta())
.as_secs_f32()
* physics_time.scale();
}
}

Expand Down
6 changes: 5 additions & 1 deletion rapier/src/velocity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ pub(crate) fn update_rapier_velocity(
pub(crate) fn apply_velocity_to_kinematic_bodies(
mut bodies: ResMut<'_, RigidBodySet>,
physics_step: Res<'_, PhysicsSteps>,
bevy_time: Res<'_, bevy::core::Time>,
query: Query<'_, (&RigidBodyHandle, &RigidBody, &Velocity)>,
) {
let delta_time = physics_step.duration().as_secs_f32();
let delta_time = physics_step
.duration()
.exact(bevy_time.delta())
.as_secs_f32();
let kinematic_bodies = query
.iter()
.filter(|(_, body_type, _)| matches!(body_type, RigidBody::KinematicVelocityBased));
Expand Down

0 comments on commit 4a646d7

Please sign in to comment.