Interaction 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 by polling physics events.

In addition, a special kind of collider, called sensors allow the addition of geometric shapes (that may be attached optionally to a body part) to the world that do not generate any collisions, but will still 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 geometrical world.

Sensors§

Sensors are a special type of collider that don’t generate any contact. Like regular colliders, they have to be added to the world, are given a geometric shape, and are attached to a body part. 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 sensor demo shows a ball-shaped sensor attached to a cube. All colliders (except the ground) that intersect with this ball are colored in yellow.

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

let shape = ShapeHandle::new(Ball::new(1.5));
let sensor = ColliderDesc::new(shape)
    .sensor(true)
    .build();
// Add to the collider set.
let sensor_handle = collider_set.insert(sensor);

Note that sensors don’t have margins like regular colliders.

Since sensors are just special kinds of colliders, they can be removed from the collider set using collider_set.remove(sensor_handle) and retrieved with collider_set.get(sensor_handle). 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.

Note

Because sensors are just special cases of colliders, they can be given a specific collision group so they generate events only with a subset of sensors and colliders. The procedure for setting up collision groups is the same as for regular colliders as described in that section.

Interaction handling§

Events generated by nphysics actually come from the ncollide2d or the ncollide3d crate which is responsible for all the collision detection. Therefore, the Event Handling section on ncollide’s user guide is directly applicable here. We distinguish two kinds of events: proximity events generated by sensors and contact events generated when two non-sensor colliders touch. Events are generated by the physics world at each timestep. It is possible to iterate through all the events generated by the last call to mechanical_world.step(...). Those events can be retrieved from the geometrical world as:

for proximity in geometrical_world.proximity_events() {
    // Handle proximity events.
}

for contact in geometrialc_world.contact_events() {
    // Handle contact events.
}

Warning

Iterating through all events will not clear the internal event pool. Therefore, it is possible to write several loops like above, each of which will yield the same events in the same order. However, keep in mind all events that occurred before the last call to mechanical_world.step(...) are no longer retrievable.

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 with collider_set.get(collider_handle).unwrap().body().

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 the sum of the linear_prediction given to each collider.
  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.

An iterator through all proximity events may be retrieved using the .proximity_events() method of the MechanicalWorld. This yields ProximityEvent structures:

Field Description
collider1 The handle of the first collider/sensor involved in the proximity.
collider2 The handle of the second collider/sensor involved in the proximity.
prev_status The previous proximity status of the colliders/sensors.
new_status The 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 parts a collider/sensor is attached to are 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(handle1, handle2) to indicate the transition between 0 to at least one contact point.
  2. ContactEvent::Stopped(handle1, handle2) to indicate the transition between at least one contact point to 0.

In the case of starting contacts, it is possible to retrieve the contacts by obtaining a reference to the contact manifold with:

if let Some(collider1, collider2, algorithm, manifold) = geometrical_world.contact_pair(handle1, handle2) {
   // ...
}

Interaction iterators§

Besides polling events, it is possible to retrieve at any time the complete list of colliders interacting with another one using the GeometricalWorld (or its type alias DefaultGeometricalWorld).

The is_effective flag

In the following table, the is_effective parameter is something you likely want to always set to true.

Setting it to false will yield results that may be misleading. Indeed, setting this to false will return all the pairs created by the narrow-phase of the collision detection pipeline. Therefore, it will include all pairs that are close enough to require the narrow phase to deal with them but that may not actually be in contact or proximity, depending on the linear_prediction and angular_prediction parameters set during the collider construction. Setting is_effective to true will filter-out those spurious pairs for you.

Method Description
.contact_pairs(is_effective) Iterator through all the contact pairs. If is_effective is true, only pairs involving actual contacts (i.e. contacts within the shapes margin or penetrating) are yielded.
.proximity_pairs(is_effective) Iterator through all the proximity pairs. If is_effective is true, only pairs with a proximity equal to Proximity::Intersecting are yielded.
.interaction_pairs(is_effective) Combination of .contact_pairs and .proximity_pairs into a single iterator.
.contact_pair(h1, h2, is_effective) The contact pair, if any, between the colliders identified by h1 and h2. If is_effective is true, only a pair involving actual contacts (i.e. contacts within the shapes margin or penetrating) is returned.
.proximity_pair(h1, h2, is_effective) The proximity pair, if any, between the colliders identified by h1 and h2. If is_effective is true, only a pair involving a proximity equal to Proximity::Intersecting is returned.
.interaction_pair(h1, h2, is_effective) Combination of .contact_pair and .proximity_pair in a single iterator.
.contacts_with(h, is_effective) Iterator through all the proximity algorithms and colliders in contact with the one identified by h. If is_effective is true, only pairs involving actual contacts (i.e. contacts within the shapes margin or penetrating) are yielded.
.proximities_with(h, is_effective) Iterator through all the proximity algorithms and colliders in proximity of the one identified by h. If is_effective is true, only pairs with a proximity equal to Proximity::Intersecting are yielded.
.interactions_with(h, is_effective) Combination of .contacts_with and .proximities_with on a single iterator.
.colliders_in_contact_with(h) Iterator through all the colliders in contact with the one identified by h, i.e., non-sensor colliders with shapes (margin included) that intersect the shape (margin included) of the one identified by h
.colliders_in_proximity_of(h) Iterator through all the colliders in proximity Proximity::Intersecting of the one identified by h. The collider identified by h and/or one of the intersecting colliders will be a sensor.
.colliders_interacting_with(h) Combination of .collider_in_contact_with and .colliders_in_proximity_of on a single iterator.

Proximity events

Recall that a proximity can only occur between a sensor collider and another collider (sensor or not). On the other hand, contacts only occur between two non-sensor colliders.


Contact models Continuous collision detection