Simulation structures
This page describes all the data structures needed for stepping the simulation, i.e., the structures owned by the physics world.
Physics world
Wiring every structure of this page by hand gives a complete control over the simulation, but this is rarely what an
application needs. The PhysicsWorld is a façade owning all of them (the sets, the pipelines, the gravity, and the
integration parameters), so a simulation can be created, stepped, and queried with a less verbose API.
Every structure it owns remains accessible as a public field, therefore switching to the PhysicsWorld doesn't give
up any feature (for example in case where borrowing the entire PhysicsWorld would cause borrowing issues):
- Example 2D
- Example 3D
// The world owns every structure needed by the simulation.
let mut world = PhysicsWorld::new();
world.gravity = Vector::new(0.0, -9.81);
world.integration_parameters.dt = 1.0 / 60.0;
/* Create the ground: a collider without any parent rigid-body. */
world.insert_collider(ColliderBuilder::cuboid(100.0, 0.1), None);
/* Create the bouncing ball: the rigid-body and its collider are inserted at once. */
let (ball_handle, _ball_collider) = world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(0.0, 10.0)),
ColliderBuilder::ball(0.5).restitution(0.7),
);
/* Run the game loop, stepping the simulation once per frame. */
for _ in 0..200 {
world.step();
println!("Ball altitude: {}", world.bodies[ball_handle].translation().y);
}
// The world owns every structure needed by the simulation.
let mut world = PhysicsWorld::new();
world.gravity = Vector::new(0.0, -9.81, 0.0);
world.integration_parameters.dt = 1.0 / 60.0;
/* Create the ground: a collider without any parent rigid-body. */
world.insert_collider(ColliderBuilder::cuboid(100.0, 0.1, 100.0), None);
/* Create the bouncing ball: the rigid-body and its collider are inserted at once. */
let (ball_handle, _ball_collider) = world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(0.0, 10.0, 0.0)),
ColliderBuilder::ball(0.5).restitution(0.7),
);
/* Run the game loop, stepping the simulation once per frame. */
for _ in 0..200 {
world.step();
println!("Ball altitude: {}", world.bodies[ball_handle].translation().y);
}
The world also forwards the scene queries and the most common insertions and removals, which saves passing the sets they need to each other:
- Example 2D
- Example 3D
// The scene queries are available on the world directly.
let ray = Ray::new(Vector::new(0.0, 10.0), Vector::new(0.0, -1.0));
if let Some((handle, distance)) = world.cast_ray(&ray, 100.0, true, QueryFilter::default()) {
println!("Collider {:?} hit at distance {}", handle, distance);
}
// Every set remains reachable as a public field of the world.
let num_pairs = world.narrow_phase.contact_pairs().count();
println!("{} contact pairs", num_pairs);
// The scene queries are available on the world directly.
let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
if let Some((handle, distance)) = world.cast_ray(&ray, 100.0, true, QueryFilter::default()) {
println!("Collider {:?} hit at distance {}", handle, distance);
}
// Every set remains reachable as a public field of the world.
let num_pairs = world.narrow_phase.contact_pairs().count();
println!("{} contact pairs", num_pairs);
The rest of this page describes the structures owned by the PhysicsWorld.
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 PhysicsWorld::gravity, or by passing a different vector to
PhysicsPipeline::step).
Learn more about per-rigid-body gravity modification in the dedicated section.
Integration parameters
The IntegrationParameters 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 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:
// Iter on each rigid-bodies that moved (dynamic and kinematic).
for rigid_body_handle in world.islands.active_bodies() {
let rigid_body = &world.bodies[rigid_body_handle];
println!(
"Rigid body {:?} has a new position: {:?}",
rigid_body_handle,
rigid_body.position()
);
}
Learn more about sleeping rigid-bodies in the dedicated section.
Physics pipeline
The PhysicsPipeline 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.
PhysicsPipeline::step executes one timestep.
Its usage is illustrated in the basic simulation example.
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.
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 a temporary object that is initialized from the broad-phase, collider set, and rigid-body set.
It reuses the acceleration data-structure (BVH) from the broad-phase which is automatically updated by the physics
stepping function. This is what PhysicsWorld::query_pipeline does:
// A temporary query pipeline borrowing the broad-phase, the narrow-phase, and the sets. This
// is what `PhysicsWorld::query_pipeline` does.
let query_pipeline = world.broad_phase.as_query_pipeline(
world.narrow_phase.query_dispatcher(),
&world.bodies,
&world.colliders,
QueryFilter::default(),
);
Learn more about scene queries with the QueryPipeline in the dedicated page.
Rigid-body set
The RigidBodySet contains all the rigid-bodies that needs to be simulated. This set is represented as a
generational-arena, i.e., a vector where each element is indexed using a handle that combines
an u32 index and an u32 generation number. This ensures that every rigid-body is given a unique handle.
Learn more about rigid-bodies in the dedicated page.
Collider set
The ColliderSet contains all the colliders that needs to be simulated. This set is represented as a
generational-arena, i.e., a vector where each element is indexed using a handle that combines
an u32 index and an u32 generation number. This ensures that every collider is
given a unique handle.
Learn more about colliders in the dedicated page.
Joint set
The ImpulseJointSet contains all the impulse-based joints that needs to be simulated. This set is represented as a
generational-arena, i.e., a vector where each element is indexed using a handle that combines
an u32 index and an u32 generation number. This ensures that every joint is
given a unique handle.
Learn more about joints in the dedicated page.
Soft-body set
The SoftBodySet contains all the soft-bodies that needs to be simulated, as well as the particles and the
elements they are made of. Like the other sets, it is a generational-arena giving a unique handle to each soft-body.
Note that this set must be given to the pipeline even if the simulation doesn't have any soft-body.
Learn more about soft-bodies in the dedicated page.
CCD solver
The CCD solver is responsible for the resolution of Continuous-Collision-Detection. By itself, this structure
doesn't expose any useful feature. So it should simply be passed to the PhysicsPipeline::step method.
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.
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. Learn more about collision events in
the dedicated section.
Stepping the structures by hand
Everything described in this page can be created and stepped without the physics world, which is exactly what the world does internally. This is useful when the structures must be owned by different parts of your application, e.g., by the resources of an ECS. Note that every one of them is then given to the pipeline at each timestep:
- Example 2D
- Example 3D
fn manual_stepping() {
let mut rigid_body_set = RigidBodySet::new();
let mut collider_set = ColliderSet::new();
/* Create the ground. */
let collider = ColliderBuilder::cuboid(100.0, 0.1).build();
collider_set.insert(collider);
/* Create the bouncing ball. */
let rigid_body = RigidBodyBuilder::dynamic()
.translation(Vector::new(0.0, 10.0))
.build();
let collider = ColliderBuilder::ball(0.5).restitution(0.7).build();
let ball_body_handle = rigid_body_set.insert(rigid_body);
collider_set.insert_with_parent(collider, ball_body_handle, &mut rigid_body_set);
/* Create other structures necessary for the simulation. */
let gravity = Vector::new(0.0, -9.81);
let integration_parameters = IntegrationParameters::default();
let mut physics_pipeline = PhysicsPipeline::new();
let mut island_manager = IslandManager::new();
let mut broad_phase = DefaultBroadPhase::new();
let mut narrow_phase = NarrowPhase::new();
let mut impulse_joint_set = ImpulseJointSet::new();
let mut multibody_joint_set = MultibodyJointSet::new();
let mut soft_body_set = SoftBodySet::new();
let mut ccd_solver = CCDSolver::new();
let physics_hooks = ();
let event_handler = ();
/* Run the game loop, stepping the simulation once per frame. */
for _ in 0..200 {
physics_pipeline.step(
gravity,
&integration_parameters,
&mut island_manager,
&mut broad_phase,
&mut narrow_phase,
&mut rigid_body_set,
&mut collider_set,
&mut impulse_joint_set,
&mut multibody_joint_set,
&mut soft_body_set,
&mut ccd_solver,
&physics_hooks,
&event_handler,
);
let ball_body = &rigid_body_set[ball_body_handle];
println!("Ball altitude: {}", ball_body.translation().y);
}
}
fn manual_stepping() {
let mut rigid_body_set = RigidBodySet::new();
let mut collider_set = ColliderSet::new();
/* Create the ground. */
let collider = ColliderBuilder::cuboid(100.0, 0.1, 100.0).build();
collider_set.insert(collider);
/* Create the bounding ball. */
let rigid_body = RigidBodyBuilder::dynamic()
.translation(Vector::new(0.0, 10.0, 0.0))
.build();
let collider = ColliderBuilder::ball(0.5).restitution(0.7).build();
let ball_body_handle = rigid_body_set.insert(rigid_body);
collider_set.insert_with_parent(collider, ball_body_handle, &mut rigid_body_set);
/* Create other structures necessary for the simulation. */
let gravity = Vector::new(0.0, -9.81, 0.0);
let integration_parameters = IntegrationParameters::default();
let mut physics_pipeline = PhysicsPipeline::new();
let mut island_manager = IslandManager::new();
let mut broad_phase = DefaultBroadPhase::new();
let mut narrow_phase = NarrowPhase::new();
let mut impulse_joint_set = ImpulseJointSet::new();
let mut multibody_joint_set = MultibodyJointSet::new();
let mut soft_body_set = SoftBodySet::new();
let mut ccd_solver = CCDSolver::new();
let physics_hooks = ();
let event_handler = ();
/* Run the game loop, stepping the simulation once per frame. */
for _ in 0..200 {
physics_pipeline.step(
gravity,
&integration_parameters,
&mut island_manager,
&mut broad_phase,
&mut narrow_phase,
&mut rigid_body_set,
&mut collider_set,
&mut impulse_joint_set,
&mut multibody_joint_set,
&mut soft_body_set,
&mut ccd_solver,
&physics_hooks,
&event_handler,
);
let ball_body = &rigid_body_set[ball_body_handle];
println!("Ball altitude: {}", ball_body.translation().y);
}
}