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:
RapierContextSimulationcontains the island manager, the broad-phase, the narrow-phase, the CCD solver, the pipelines, and the integration parameters.RapierContextColliderscontains the collider set.RapierRigidBodySetcontains the rigid-body set and the soft-body set.RapierContextJointscontains the impulse joint set and the multibody joint set.RapierConfigurationcontains the gravity and the other options of the simulation described in the configuration section.SimulationToRenderTimecontains the difference between the simulated time and the real time, which is used by theTimestepMode::Interpolatedtimestep 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::SyncBackendcreates, modifies, or removes the Rapier objects according to the components of your entities.PhysicsSet::StepSimulationsteps the simulation of each physics context. The number of timesteps executed at each run of this set, and their length, are selected by theTimestepModeresource described in the integration parameters page.PhysicsSet::Writebackwrites 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.
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:
gravityis the gravity vector applied to the dynamic rigid-bodies. Its default value, along the axis, is multiplied by the length unit of the context.physics_pipeline_activecan be set tofalseto pause the simulation.simulation_modeselects between the full simulation (SimulationMode::Full) and collision-detection only (SimulationMode::CollisionOnly).num_threadsgives the physics context its own thread pool with the given number of threads. IfNone(the default), the simulation runs on the thread pool of the calling thread (usually the global pool ofrayon). This is ignored unless theparallelfeature is enabled (and theunsync-callbacksfeature isn't).scaled_shape_subdivisionis the number of subdivisions used to approximate a shape which cannot be represented exactly once the scale of itsTransformis applied (e.g. a ball with a non-uniform scale becomes a convex polyhedron).force_update_from_transform_changesmakes the plugin accept every change of theTransformof the rigid-bodies, including the changes that may originate from its own writeback.
- Example 2D
- Example 3D
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(())
}
fn modify_configuration(
mut configurations: Query<&mut RapierConfiguration, With<DefaultRapierContext>>,
) -> Result {
let mut configuration = configurations.single_mut()?;
configuration.gravity = Vec3::new(0.0, -9.81, 0.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(())
}