Skip to content

Commit

Permalink
Replace time resources with Time<Physics> and Time<Substeps>
Browse files Browse the repository at this point in the history
  • Loading branch information
Jondolf committed Oct 31, 2023
1 parent 55d79ca commit b27370f
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 164 deletions.
6 changes: 5 additions & 1 deletion crates/benches_common_3d/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::time::Duration;

use bevy::{app::PluginsState, prelude::*};
use bevy_xpbd_3d::prelude::*;
use criterion::{measurement::Measurement, BatchSize, Bencher};
Expand All @@ -18,7 +20,9 @@ pub fn bench_app<M: Measurement>(
PhysicsPlugins::default(),
));

app.insert_resource(PhysicsTimestep::FixedOnce(1.0 / 60.0));
app.insert_resource(Time::new_with(Physics::FixedOnce(Duration::from_secs_f64(
1.0 / 64.0,
))));

setup(&mut app);

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_xpbd_2d/examples/move_marbles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn main() {
App::new()
.add_plugins((DefaultPlugins, XpbdExamplePlugin))
.insert_resource(ClearColor(Color::rgb(0.05, 0.05, 0.1)))
.insert_resource(SubstepCount(6))
.insert_resource(SubstepCount(5))
.insert_resource(Gravity(Vector::NEG_Y * 1000.0))
.add_systems(Startup, setup)
.add_systems(Update, movement)
Expand Down
2 changes: 1 addition & 1 deletion src/constraints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
//! ```
//!
//! where `w_i` is the inverse mass of particle `i`, `|▽C_i|` is the length of the gradient vector for particle `i`,
//! `α` is the constraint's compliance (inverse of stiffness) and `h` is the [substep size](SubDeltaTime). Using `α = 0`
//! `α` is the constraint's compliance (inverse of stiffness) and `h` is the substep size. Using `α = 0`
//! corresponds to infinite stiffness.
//!
//! The minus sign is there because the gradients point in the direction in which `C` increases the most,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@
//! solve_velocities(particles and bodies)
//! ```
//!
//! where `h` is the [substep size](SubDeltaTime), `q` is the [rotation](Rotation) as a quaternion,
//! where `h` is the substep size, `q` is the [rotation](Rotation) as a quaternion,
//! `ω` is the [angular velocity](AngularVelocity), `I` is the [angular inertia tensor](`Inertia`) and `τ` is the
//! [external torque](ExternalTorque).
//!
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/collision/broad_phase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ fn update_aabb(
(&Position, Option<&LinearVelocity>, Option<&AngularVelocity>),
With<Children>,
>,
dt: Res<DeltaTime>,
dt: Res<Time>,
) {
// Safety margin multiplier bigger than DELTA_TIME to account for sudden accelerations
let safety_margin_factor = 2.0 * dt.0;
let safety_margin_factor = 2.0 * dt.delta_seconds_adjusted();

for (collider, mut aabb, pos, rot, collider_parent, lin_vel, ang_vel) in &mut colliders {
let (lin_vel, ang_vel) = if let (Some(lin_vel), Some(ang_vel)) = (lin_vel, ang_vel) {
Expand Down
38 changes: 19 additions & 19 deletions src/plugins/integrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ type PosIntegrationComponents = (
fn integrate_pos(
mut bodies: Query<PosIntegrationComponents, Without<Sleeping>>,
gravity: Res<Gravity>,
sub_dt: Res<SubDeltaTime>,
time: Res<Time>,
) {
let delta_secs = time.delta_seconds_adjusted();

for (
rb,
pos,
Expand All @@ -78,7 +80,7 @@ fn integrate_pos(
if rb.is_dynamic() {
// Apply damping
if let Some(damping) = lin_damping {
lin_vel.0 *= 1.0 / (1.0 + sub_dt.0 * damping.0);
lin_vel.0 *= 1.0 / (1.0 + delta_secs * damping.0);
}

let effective_mass = locked_axes.apply_to_vec(Vector::splat(mass.0));
Expand All @@ -88,14 +90,14 @@ fn integrate_pos(
let gravitation_force =
effective_mass * gravity.0 * gravity_scale.map_or(1.0, |scale| scale.0);
let external_forces = gravitation_force + external_force.force();
let delta_lin_vel = sub_dt.0 * external_forces * effective_inv_mass;
let delta_lin_vel = delta_secs * external_forces * effective_inv_mass;
// avoid triggering bevy's change detection unnecessarily
if delta_lin_vel != Vector::ZERO {
lin_vel.0 += delta_lin_vel;
}
}
if lin_vel.0 != Vector::ZERO {
translation.0 += locked_axes.apply_to_vec(sub_dt.0 * lin_vel.0);
translation.0 += locked_axes.apply_to_vec(delta_secs * lin_vel.0);
}
}
}
Expand All @@ -116,10 +118,9 @@ type RotIntegrationComponents = (
/// Explicitly integrates the rotations and angular velocities of bodies taking only external torque into account.
/// This acts as a prediction for the next rotations of the bodies.
#[cfg(feature = "2d")]
fn integrate_rot(
mut bodies: Query<RotIntegrationComponents, Without<Sleeping>>,
sub_dt: Res<SubDeltaTime>,
) {
fn integrate_rot(mut bodies: Query<RotIntegrationComponents, Without<Sleeping>>, time: Res<Time>) {
let delta_secs = time.delta_seconds_adjusted();

for (
rb,
mut rot,
Expand Down Expand Up @@ -147,14 +148,14 @@ fn integrate_rot(
if let Some(damping) = ang_damping {
// avoid triggering bevy's change detection unnecessarily
if ang_vel.0 != 0.0 && damping.0 != 0.0 {
ang_vel.0 *= 1.0 / (1.0 + sub_dt.0 * damping.0);
ang_vel.0 *= 1.0 / (1.0 + delta_secs * damping.0);
}
}

let effective_inv_inertia = locked_axes.apply_to_rotation(inv_inertia.0);

// Apply external torque
let delta_ang_vel = sub_dt.0
let delta_ang_vel = delta_secs
* effective_inv_inertia
* (external_torque.torque() + external_force.torque());
// avoid triggering bevy's change detection unnecessarily
Expand All @@ -163,7 +164,7 @@ fn integrate_rot(
}
}
// avoid triggering bevy's change detection unnecessarily
let delta = locked_axes.apply_to_angular_velocity(sub_dt.0 * ang_vel.0);
let delta = locked_axes.apply_to_angular_velocity(delta_secs * ang_vel.0);
if delta != 0.0 {
*rot += Rotation::from_radians(delta);
}
Expand All @@ -173,10 +174,9 @@ fn integrate_rot(
/// Explicitly integrates the rotations and angular velocities of bodies taking only external torque into account.
/// This acts as a prediction for the next rotations of the bodies.
#[cfg(feature = "3d")]
fn integrate_rot(
mut bodies: Query<RotIntegrationComponents, Without<Sleeping>>,
sub_dt: Res<SubDeltaTime>,
) {
fn integrate_rot(mut bodies: Query<RotIntegrationComponents, Without<Sleeping>>, time: Res<Time>) {
let delta_secs = time.delta_seconds_adjusted();

for (
rb,
mut rot,
Expand Down Expand Up @@ -204,15 +204,15 @@ fn integrate_rot(
if let Some(damping) = ang_damping {
// avoid triggering bevy's change detection unnecessarily
if ang_vel.0 != Vector::ZERO && damping.0 != 0.0 {
ang_vel.0 *= 1.0 / (1.0 + sub_dt.0 * damping.0);
ang_vel.0 *= 1.0 / (1.0 + delta_secs * damping.0);
}
}

let effective_inertia = locked_axes.apply_to_rotation(inertia.rotated(&rot).0);
let effective_inv_inertia = locked_axes.apply_to_rotation(inv_inertia.rotated(&rot).0);

// Apply external torque
let delta_ang_vel = sub_dt.0
let delta_ang_vel = delta_secs
* effective_inv_inertia
* ((external_torque.torque() + external_force.torque())
- ang_vel.0.cross(effective_inertia * ang_vel.0));
Expand All @@ -224,8 +224,8 @@ fn integrate_rot(

let q = Quaternion::from_vec4(ang_vel.0.extend(0.0)) * rot.0;
let effective_dq = locked_axes
.apply_to_angular_velocity(sub_dt.0 * 0.5 * q.xyz())
.extend(sub_dt.0 * 0.5 * q.w);
.apply_to_angular_velocity(delta_secs * 0.5 * q.xyz())
.extend(delta_secs * 0.5 * q.w);
// avoid triggering bevy's change detection unnecessarily
let delta = Quaternion::from_vec4(effective_dq);
if delta != Quaternion::IDENTITY {
Expand Down
8 changes: 4 additions & 4 deletions src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ use bevy::prelude::*;
/// only take affect the next frame.
///
/// To advance the simulation by a certain amount of time instantly, you can set the value of
/// the [`DeltaTime`] resource and manually run the [`PhysicsSchedule`] in an exclusive system:
/// the [`Time<Physics>`] clock and manually run the [`PhysicsSchedule`] in an exclusive system:
///
/// ```
/// use bevy::prelude::*;
Expand All @@ -114,11 +114,11 @@ use bevy::prelude::*;
)]
///
/// fn run_physics(world: &mut World) {
/// // Set the amount of time a step should take
/// world.resource_mut::<DeltaTime>().0 = 1.0 / 120.0;
///
/// // Advance the simulation by 10 steps at 120 Hz
/// for _ in 0..10 {
/// world
/// .resource_mut::<Time<Physics>>()
/// .advance_by(Duration::from_secs_f64(1.0 / 120.0));
/// world.run_schedule(PhysicsSchedule);
/// }
/// }
Expand Down
130 changes: 69 additions & 61 deletions src/plugins/setup.rs → src/plugins/setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
//!
//! See [`PhysicsSetupPlugin`].

mod time;

pub use time::*;

use super::sync::PreviousGlobalTransform;
use crate::prelude::*;
use bevy::{
ecs::schedule::{ExecutorKind, ScheduleBuildSettings},
prelude::*,
transform::TransformSystem,
utils::intern::Interned,
};

use crate::prelude::*;

use super::sync::PreviousGlobalTransform;
use bevy::utils::intern::Interned;
use std::time::Duration;

/// Sets up the physics engine by initializing the necessary schedules, sets and resources.
///
Expand Down Expand Up @@ -57,19 +61,17 @@ impl Default for PhysicsSetupPlugin {
impl Plugin for PhysicsSetupPlugin {
fn build(&self, app: &mut App) {
// Init resources and register component types
app.init_resource::<PhysicsTimestep>()
.init_resource::<PhysicsTimescale>()
.init_resource::<DeltaTime>()
.init_resource::<SubDeltaTime>()
app.init_resource::<PhysicsTimescale>()
.insert_resource(Time::new_with(Physics::default()))
.insert_resource(Time::new_with(Substeps))
.init_resource::<SubstepCount>()
.init_resource::<BroadCollisionPairs>()
.init_resource::<SleepingThreshold>()
.init_resource::<DeactivationTime>()
.init_resource::<Gravity>()
.register_type::<PhysicsTimestep>()
.register_type::<PhysicsTimescale>()
.register_type::<DeltaTime>()
.register_type::<SubDeltaTime>()
.register_type::<Time<Physics>>()
.register_type::<Time<Substeps>>()
.register_type::<SubstepCount>()
.register_type::<BroadCollisionPairs>()
.register_type::<SleepingThreshold>()
Expand Down Expand Up @@ -302,27 +304,20 @@ fn run_physics_schedule(world: &mut World, mut is_first_run: Local<IsFirstRun>)
.expect("no PhysicsLoop resource");

#[cfg(feature = "f32")]
let mut delta_seconds = if physics_loop.fixed_update {
world.resource::<Time<Fixed>>().delta_seconds()
} else {
world.resource::<Time>().delta_seconds()
};

let mut delta_seconds = world.resource::<Time<Virtual>>().delta_seconds();
#[cfg(feature = "f64")]
let mut delta_seconds = if physics_loop.fixed_update {
world.resource::<Time<Fixed>>().delta_seconds_f64()
} else {
world.resource::<Time>().delta_seconds_f64()
};
let mut delta_seconds = world.resource::<Time<Virtual>>().delta_seconds_f64();

let physics_clock = world.resource_mut::<Time<Physics>>();

let time_step = *world.resource::<PhysicsTimestep>();
let time_step = *physics_clock.context();
let time_scale = world.resource::<PhysicsTimescale>().0.max(0.0);

// Update `DeltaTime` according to the `PhysicsTimestep` configuration
// Update `Time` according to the `PhysicsTimestep` configuration
let (raw_dt, accumulate) = match time_step {
PhysicsTimestep::Fixed(fixed_delta_seconds) => (fixed_delta_seconds, true),
PhysicsTimestep::FixedOnce(fixed_delta_seconds) => (fixed_delta_seconds, false),
PhysicsTimestep::Variable { max_dt } => (delta_seconds.min(max_dt), true),
Physics::Fixed(fixed_delta_seconds) => (fixed_delta_seconds.as_secs_adjusted(), true),
Physics::FixedOnce(fixed_delta_seconds) => (fixed_delta_seconds.as_secs_adjusted(), false),
Physics::Variable { max_dt } => (delta_seconds.min(max_dt.as_secs_adjusted()), true),
};

// On the first run of the schedule, `delta_seconds` could be 0.0.
Expand All @@ -334,53 +329,66 @@ fn run_physics_schedule(world: &mut World, mut is_first_run: Local<IsFirstRun>)
is_first_run.0 = false;
}

let dt = raw_dt * time_scale;
world.resource_mut::<DeltaTime>().0 = dt;
let delta_seconds_scaled = raw_dt * time_scale;
let delta_time = Duration::from_secs_adjusted(delta_seconds_scaled);

match accumulate {
false if physics_loop.paused && physics_loop.queued_steps == 0 => {}
false => {
if physics_loop.queued_steps > 0 {
physics_loop.queued_steps -= 1;
}
debug!("running PhysicsSchedule");
world.run_schedule(PhysicsSchedule);
}
true => {
// Add time to the accumulator
if physics_loop.paused {
physics_loop.accumulator += dt * physics_loop.queued_steps as Scalar;
physics_loop.queued_steps = 0;
} else {
physics_loop.accumulator += delta_seconds * time_scale;
}
world.resource_mut::<Time<Physics>>().advance_by(delta_time);

// Step the simulation until the accumulator has been consumed.
// Note that a small remainder may be passed on to the next run of the physics schedule.
while physics_loop.accumulator >= dt && dt > 0.0 {
let _ = world.try_schedule_scope(PhysicsSchedule, |world, schedule| {
match accumulate {
false if physics_loop.paused && physics_loop.queued_steps == 0 => {}
false => {
if physics_loop.queued_steps > 0 {
physics_loop.queued_steps -= 1;
}
debug!("running PhysicsSchedule");
world.run_schedule(PhysicsSchedule);
physics_loop.accumulator -= dt;
*world.resource_mut::<Time>() = world.resource::<Time<Physics>>().as_generic();
schedule.run(world);
}
true => {
// Add time to the accumulator
if physics_loop.paused {
physics_loop.accumulator +=
delta_seconds_scaled * physics_loop.queued_steps as Scalar;
physics_loop.queued_steps = 0;
} else {
physics_loop.accumulator += delta_seconds * time_scale;
}

// Step the simulation until the accumulator has been consumed.
// Note that a small remainder may be passed on to the next run of the physics schedule.
while physics_loop.accumulator >= delta_seconds_scaled && !delta_time.is_zero() {
debug!("running PhysicsSchedule");
*world.resource_mut::<Time>() = world.resource::<Time<Physics>>().as_generic();
schedule.run(world);
physics_loop.accumulator -= delta_seconds_scaled;
}
}
}
}
});

*world.resource_mut::<Time>() = world.resource::<Time<Virtual>>().as_generic();
world.insert_resource(physics_loop);
}

/// Runs the [`SubstepSchedule`].
fn run_substep_schedule(world: &mut World) {
let delta = world.resource::<Time<Physics>>().delta();
let SubstepCount(substeps) = *world.resource::<SubstepCount>();
let dt = world.resource::<DeltaTime>().0;
let sub_delta = delta.div_f64(substeps as f64);

// Update `SubDeltaTime`
let mut sub_delta_time = world.resource_mut::<SubDeltaTime>();
sub_delta_time.0 = dt / substeps as Scalar;
let mut sub_delta_time = world.resource_mut::<Time<Substeps>>();
sub_delta_time.advance_by(sub_delta);

for i in 0..substeps {
debug!("running SubstepSchedule: {i}");
world.run_schedule(SubstepSchedule);
}
let _ = world.try_schedule_scope(SubstepSchedule, |world, schedule| {
for i in 0..substeps {
debug!("running SubstepSchedule: {i}");
*world.resource_mut::<Time>() = world.resource::<Time<Substeps>>().as_generic();
schedule.run(world);
}
});

*world.resource_mut::<Time>() = world.resource::<Time<Virtual>>().as_generic();
}

/// Runs the [`PostProcessCollisions`] schedule.
Expand Down
Loading

0 comments on commit b27370f

Please sign in to comment.