Skip to main content

Simulation structures

This page describes the components making up a physics context (spawned automatically by the RapierPhysicsPlugin), and the options controlling how it is simulated.

Physics context​

The structures described in this page are stored in the components of a physics context entity. The RapierPhysicsPlugin spawns one automatically during the PreStartup schedule, marked with the DefaultRapierContext component, so most applications don't need to create one themselves (see the multiple physics contexts page otherwise). A physics context is made of the following components:

  • RapierContextSimulation contains the island manager, the broad-phase, the narrow-phase, the CCD solver, the pipelines, and the integration parameters.
  • RapierContextColliders contains the collider set.
  • RapierRigidBodySet contains the rigid-body set and the soft-body set.
  • RapierContextJoints contains the impulse joint set and the multibody joint set.
  • RapierConfiguration contains the gravity and the other options of the simulation described in the configuration section.
  • SimulationToRenderTime contains the difference between the simulated time and the real time, which is used by the TimestepMode::Interpolated timestep mode.

The Rapier objects created from your entities store the bits of their entity in their user-data, and these components also map each entity to the handle of its Rapier object, e.g., with RapierRigidBodySet::rigid_body_entity or RapierContextColliders::collider_entity. Keep in mind that these objects are managed by the plugin: they should be modified through the components of their entities (rigid-bodies, colliders, joints, etc.) rather than through the sets directly.

The ReadRapierContext and WriteRapierContext system parameters give access to all the components of the default physics context at once, through the RapierContext and RapierContextMut structures (another context can be selected with the query filter given as their type parameter). This is what most of the scene queries rely on. When only one of these components is needed, it can be queried directly like any other component, e.g., with Query<&RapierContextSimulation, With<DefaultRapierContext>>.

Gravity​

Gravity is represented as a vector. It affects every dynamic rigid-body taking part of the simulation. The gravity can be altered at each timestep (by modifying the component field RapierConfiguration::gravity). Learn more about per-rigid-body gravity modification in the dedicated section.

Integration parameters​

The IntegrationParameters (the RapierContextSimulation::integration_parameters field) controls various aspects of the physics simulation, including the timestep length, number of solver iterations, number of CCD substeps, etc. The default integration parameters are set to achieve a good balance between performance and accuracy for games. They can be changed to make the simulation more accurate at the expense of a bit of performance. Learn more about each integration parameter in the API docs.

Island manager​

The IslandManager (the RapierContextSimulation::islands field) is responsible for tracking the set of dynamic rigid-bodies that are still moving and these that are no longer moving (and can ignored by subsequent timesteps to avoid useless computations). The island manager is automatically updated by PhysicsPipeline::step and can be queried to retrieve the list of all the rigid-bodies modified by the physics engine during the last timestep. This can be useful to update the rendering of only the rigid-bodies that moved:

fn print_active_bodies(context: ReadRapierContext, transforms: Query<&Transform>) -> Result {
let context = context.single()?;
// Iter on each rigid-body that moved (dynamic and kinematic).
for handle in context.simulation.islands.active_bodies() {
let Some(entity) = context.rigidbody_set.rigid_body_entity(handle) else {
continue;
};
if let Ok(transform) = transforms.get(entity) {
println!("Rigid body {entity} has a new position: {}", transform.translation);
}
}
Ok(())
}

Learn more about sleeping rigid-bodies in the dedicated section.

Physics pipeline​

The PhysicsPipeline (the RapierContextSimulation::pipeline field) is responsible for tying everything together in order to run the physics simulation. It will take care of updating every data-structures mentioned in this page (except the other pipelines), running the collision-detection, running the force computation and integration, and running CCD resolution.

The pipeline is run by the systems of the RapierPhysicsPlugin, which are organized in three system sets executed in this order (in the PostUpdate schedule by default, before the propagation of the transforms):

  • PhysicsSet::SyncBackend creates, modifies, or removes the Rapier objects according to the components of your entities.
  • PhysicsSet::StepSimulation steps the simulation of each physics context. The number of timesteps executed at each run of this set, and their length, are selected by the TimestepMode resource described in the integration parameters page.
  • PhysicsSet::Writeback writes the results of the simulation back into the components (Transform, Velocity, etc.), and sends the collision events.

Therefore, your systems reading the results of the simulation should run after PhysicsSet::Writeback (or in a later schedule), and those modifying the components before PhysicsSet::SyncBackend. The simulation of a physics context can be paused by setting its RapierConfiguration::physics_pipeline_active to false:

fn toggle_pause(mut configurations: Query<&mut RapierConfiguration>) {
for mut configuration in configurations.iter_mut() {
configuration.physics_pipeline_active = !configuration.physics_pipeline_active;
}
}

Collision pipeline​

The CollisionPipeline is similar to the PhysicsPipeline except that it will only run collision-detection. It won't perform any dynamics (force computation, integration, CCD, etc.) It is generally used instead of the PhysicsPipeline when one only needs collision-detection.

info

Running both the CollisionPipeline and the PhysicsPipeline is useless because the PhysicsPipeline already does collision-detection.

A physics context runs its CollisionPipeline (the RapierContextSimulation::collision_pipeline field) instead of its PhysicsPipeline if its RapierConfiguration::simulation_mode is set to SimulationMode::CollisionOnly (the default is SimulationMode::Full). The collision events, the contact and intersection pairs, the physics hooks, and the scene queries keep working in this mode, but no forces, joints, or contact responses are applied: the rigid-bodies only move when their Transform is modified (the kinematic position-based rigid-bodies are teleported to their next kinematic position). Note that the contact force events are never emitted in this mode.

Query pipeline​

The QueryPipeline is responsible for efficiently running scene queries, e.g., ray-casting, shape-casting (sweep tests), intersection tests, on all the colliders of the scene.

The QueryPipeline is created from the physics context each time a scene query is run through the RapierContext (see its RapierContext::with_query_pipeline method).

Learn more about scene queries with the QueryPipeline in the dedicated page.

CCD solver​

The CCD solver (the RapierContextSimulation::ccd_solver field) is responsible for the resolution of Continuous-Collision-Detection. By itself, this structure doesn't expose any useful feature. It is given to the pipeline automatically by the bevy_rapier plugin. Learn more about CCD in the dedicated section.

Physics hooks​

The physics hooks are trait-objects implementing the PhysicsHooks trait. They can be used to apply arbitrary rules to ignore collision detection between some pairs of colliders. They can also be used to modify the contacts processed by the constraints solver for computing forces.

The physics hooks are a system parameter implementing the BevyPhysicsHooks trait, given as the type parameter of the RapierPhysicsPlugin (NoUserData if there are none).

Learn more about physics hooks in the dedicated section.

Event handler​

The event handlers are trait-objects implementing the EventHandler trait. They can be used to get notified when two non-sensor colliders start/stop having contacts, and when one sensor collider and one other collider start/stop intersecting. These events are sent as the CollisionEvent and ContactForceEvent Bevy messages, and your own EventHandler can be installed in addition with RapierContextSimulation::set_event_handler. Learn more about collision events in the dedicated section.

Configuration​

The RapierConfiguration component of a physics context contains the options of its simulation that are not part of the integration parameters:

  • gravity is the gravity vector applied to the dynamic rigid-bodies. Its default value, −9.81-9.81 along the yy axis, is multiplied by the length unit of the context.
  • physics_pipeline_active can be set to false to pause the simulation.
  • simulation_mode selects between the full simulation (SimulationMode::Full) and collision-detection only (SimulationMode::CollisionOnly).
  • num_threads gives the physics context its own thread pool with the given number of threads. If None (the default), the simulation runs on the thread pool of the calling thread (usually the global pool of rayon). This is ignored unless the parallel feature is enabled (and the unsync-callbacks feature isn't).
  • scaled_shape_subdivision is the number of subdivisions used to approximate a shape which cannot be represented exactly once the scale of its Transform is applied (e.g. a ball with a non-uniform scale becomes a convex polyhedron).
  • force_update_from_transform_changes makes the plugin accept every change of the Transform of the rigid-bodies, including the changes that may originate from its own writeback.
fn modify_configuration(
mut configurations: Query<&mut RapierConfiguration, With<DefaultRapierContext>>,
) -> Result {
let mut configuration = configurations.single_mut()?;
configuration.gravity = Vec2::new(0.0, -981.0);
// Only detect collisions: no forces, joints, or contact responses.
configuration.simulation_mode = SimulationMode::CollisionOnly;
// Run the simulation of this context on its own pool of 4 threads (needs the
// `parallel` feature).
configuration.num_threads = Some(4);
Ok(())
}

The initial configuration of the default physics context can also be given to the RapierPhysicsPlugin, as shown in the integration parameters page.

Diagnostics​

The RapierDiagnosticsPlugin registers Bevy diagnostics measuring the simulation of every physics context, so they can be displayed with any tool reading Bevy's diagnostics, e.g., with the LogDiagnosticsPlugin of Bevy:

App::new()
.add_plugins((
DefaultPlugins,
RapierPhysicsPlugin::<NoUserData>::default(),
// Measures the simulation of every context after each physics update (the
// measurements are summed over the contexts), and of each context separately.
RapierDiagnosticsPlugin::default().with_per_context_diagnostics(true),
// Prints every registered diagnostic once per second.
LogDiagnosticsPlugin::default(),
))

The diagnostics are identified by the constants RapierDiagnosticsPlugin::STEP_TIME (the total time spent in the simulation steps of a physics update), RapierDiagnosticsPlugin::STEPS, RapierDiagnosticsPlugin::RIGID_BODIES, RapierDiagnosticsPlugin::ACTIVE_BODIES, RapierDiagnosticsPlugin::COLLIDERS, RapierDiagnosticsPlugin::CONTACT_PAIRS, RapierDiagnosticsPlugin::SOLVER_CONSTRAINTS, etc. The time spent by each stage of the pipeline (broad-phase, narrow-phase, solver, CCD, etc.) is only measured if the profiler feature is enabled. Note that the plugin must run in the same schedule as the physics: if the RapierPhysicsPlugin runs in FixedUpdate, use RapierDiagnosticsPlugin::default().in_schedule(FixedUpdate).

The diagnostics at these paths are the sums of the measurements of all the physics contexts (counts and timings alike), so they measure the whole physics workload of the application. If RapierDiagnosticsPlugin::with_per_context_diagnostics(true) is given, as in the example above, each context is also measured separately, at the paths given by RapierDiagnosticsPlugin::context_path (e.g. rapier/12v0/step_time for the step time of the context with the entity 12v0). These are registered when the context is measured for the first time:

fn print_context_step_times(
diagnostics: Res<DiagnosticsStore>,
contexts: Query<Entity, With<RapierContextSimulation>>,
) {
for context in &contexts {
// The path of the step time of this context only.
let path =
RapierDiagnosticsPlugin::context_path(&RapierDiagnosticsPlugin::STEP_TIME, context);
if let Some(step_time) = diagnostics.get(&path).and_then(|d| d.smoothed()) {
println!("The context {context} spends {step_time:.3}ms per physics update.");
}
}
}

The same measurements can be read without Bevy's diagnostics from RapierContextSimulation::step_stats, which describes the timesteps executed by the last physics update. Only the number of steps and their total time are measured unless the counters of the PhysicsPipeline are enabled (which the RapierDiagnosticsPlugin does for every physics context):

fn print_step_stats(
contexts: Query<&RapierContextSimulation, With<DefaultRapierContext>>,
) -> Result {
let stats = contexts.single()?.step_stats();
if stats.num_steps > 0 {
println!(
"{} steps in {:.3}ms, {} contact pairs",
stats.num_steps, stats.step_time_ms, stats.num_contact_pairs
);
}
Ok(())
}