Event handling and sensors

Besides the physics simulation itself, applications often need to perform specific actions when some particular events occur inside of the physics world. For example, we might want to generate sounds when two solids collide, or open a door when a player comes close to it, detect when a projectile hits a target, etc. To allow those kinds of actions, it is possible to know when two objects start/stop colliding.

In addition, a special kind of collider, called sensors that do not generate any contacts points, but will detect when they start/stop touching another collider (be it another sensor or not). This is often useful to detect, e.g., that a player entered a specific area.

Finally it is possible to retrieve at any time the set of colliders interacting with another collider using various iterators provided by the contact and proximity graphs.

Sensors

Sensors are a special type of collider that don't generate any contact. Like regular colliders, they have to be added to the ColliderSet, are given a geometric shape, and are attached to a rigid-body. They are commonly used for detecting proximity, e.g., to detect when a player enters a specific area or is close to a door.

The creation of a sensor is identical to the creation of a regular collider except that its sensor flag is set to true:

let sensor = ColliderBuilder::ball(0.5)
.sensor(true)
.build();
// Add to the collider set.
let sensor_handle = collider_set.insert(sensor, parent_body, &mut body_set);

As presented in the next section sensors and regular colliders both generate events of different types to distinguish events due to contacts from events due to sensors.

Interaction handling

I order to handle physics events, it is necessary to collect them using a structure implementing the EventHandler trait. Because Rapier can be paralellized, this event handler must be Send + Sync. One such structure provided by Rapier is the pipeline::ChannelEventCollector. This event collector contains channels from the crossbeam crate. These channels will be populated with events during each called to pipeline.step:

use rapier2d::na::Vector2;
use rapier2d::dynamics::{JointSet, RigidBodySet, IntegrationParameters};
use rapier2d::geometry::{BroadPhase, NarrowPhase, ColliderSet};
use rapier2d::pipeline::{PhysicsPipeline, ChannelEventCollector};
fn main() {
// Here the gravity is -9.81 along the y axis.
let mut pipeline = PhysicsPipeline::new();
let gravity = Vector2::new(0.0, -9.81);
let integration_parameters = IntegrationParameters::default();
let mut broad_phase = BroadPhase::new();
let mut narrow_phase = NarrowPhase::new();
let mut bodies = RigidBodySet::new();
let mut colliders = ColliderSet::new();
let mut joints = JointSet::new();
// Initialize the event collector.
let (contact_send, contact_recv) = crossbeam::channel::unbounded();
let (proximity_send, proximity_recv) = crossbeam::channel::unbounded();
let event_handler = ChannelEventCollector::new(proximity_send, contact_send);
// Run the simulation in the game loop.
loop {
pipeline.step(
&gravity,
&integration_parameters,
&mut broad_phase,
&mut narrow_phase,
&mut bodies,
&mut colliders,
&mut joints,
&event_handler
);
while let Ok(proximity_event) = proximity_recv.try_recv() {
// Handle the proximity event.
}
while let Ok(contact_event) = contact_recv.try_recv() {
// Handle the contact event.
}
}
}
note

Contact and proximity events identify the involved colliders by their handle. It is possible to retrieve the handle of the body a collider is attached to: colliders.get(collider_handle).unwrap().parent().

Proximity events

Proximity events are triggered when two sensors, or one collider and one sensor, transition between two different proximity statuses. There are three possible proximity statuses:

  1. Proximity::Intersecting indicates two sensors (or one sensor and one collider) are touching/penetrating.
  2. Proximity::WithinMargin indicates two sensors (or one sensor and one collider) are not touching but separated by a distance smaller than a small margin given to each collider by the physics pipeline.
  3. Proximity::Disjoint indicates two sensors (or one sensor and one collider) are no longer touching, and are too far apart to be closer than the sum of the linear_prediction given to each collider.

One proximity event has the type ProximityEvent:

FieldDescription
collider1The handle of the first collider/sensor involved in the proximity.
collider2The handle of the second collider/sensor involved in the proximity.
prev_statusThe previous proximity status of the colliders/sensors.
new_statusThe current proximity status of the colliders/sensors.

Only status changes are reported, so prev_status != new_status is guaranteed. On the other hand, keep in mind that the body a collider/sensor is attached to is not required to have a smooth motion. Thus, the transition from, e.g., Proximity::Intersecting to Proximity::Disjoint is possible even if a smooth motion would have necessarily triggered transitions from ::Intersecting to ::WithinMargin and then from ::WithinMargin to ::Disjoint instead.

Contact events

Contact events are triggered when two colliders transition between having zero contact points and at least one. Transitioning between one to more than one contact points is not reported.

An iterator through all contact events may be retrieved by the .contact_events() method of the World. The yielded ContactEvent enum has two variants:

  1. ContactEvent::Started(h1, h2) to indicate the transition between 0 to at least one contact point.
  2. ContactEvent::Stopped(h1, h2) to indicate the transition between at least one contact point to 0.

Here h1 and h2 identify the handles of the colliders involved in the contact.