Emit collision stopped events after a collider is removed.

This commit is contained in:
Sébastien Crozet
2022-03-20 12:13:32 +01:00
committed by Sébastien Crozet
parent 063c638ec5
commit d38740369c
6 changed files with 146 additions and 71 deletions

View File

@@ -14,6 +14,7 @@
to simplify the users event handling. to simplify the users event handling.
- The `ActiveEvents::CONTACT_EVENTS` and `ActiveEvents::INTERSECTION_EVENTS` flags have been replaced by a single - The `ActiveEvents::CONTACT_EVENTS` and `ActiveEvents::INTERSECTION_EVENTS` flags have been replaced by a single
flag `ActiveEvents::COLLISION_EVENTS`. flag `ActiveEvents::COLLISION_EVENTS`.
- Events `CollisionEvent::Stopped` are now generated after a collider is removed.
## v0.12.0-alpha.0 (2 Jan. 2022) ## v0.12.0-alpha.0 (2 Jan. 2022)
### Fixed ### Fixed

View File

@@ -616,8 +616,8 @@ impl CCDSolver {
.contains(ActiveEvents::COLLISION_EVENTS) .contains(ActiveEvents::COLLISION_EVENTS)
{ {
// Emit one intersection-started and one intersection-stopped event. // Emit one intersection-started and one intersection-stopped event.
events.handle_intersection_event(CollisionEvent::Started(toi.c1, toi.c2)); events.handle_collision_event(CollisionEvent::Started(toi.c1, toi.c2), None);
events.handle_intersection_event(CollisionEvent::Stopped(toi.c1, toi.c2)); events.handle_collision_event(CollisionEvent::Stopped(toi.c1, toi.c2, false), None);
} }
} }

View File

@@ -1,8 +1,11 @@
use crate::dynamics::RigidBodyHandle; use crate::dynamics::RigidBodyHandle;
use crate::geometry::{ColliderHandle, Contact, ContactManifold}; use crate::geometry::{ColliderHandle, Contact, ContactManifold};
use crate::math::{Point, Real, Vector}; use crate::math::{Point, Real, Vector};
use crate::pipeline::EventHandler;
use parry::query::ContactManifoldsWorkspace; use parry::query::ContactManifoldsWorkspace;
use super::CollisionEvent;
bitflags::bitflags! { bitflags::bitflags! {
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
/// Flags affecting the behavior of the constraints solver for a given contact manifold. /// Flags affecting the behavior of the constraints solver for a given contact manifold.
@@ -46,6 +49,45 @@ impl Default for ContactData {
} }
} }
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug)]
/// The description of all the contacts between a pair of colliders.
pub struct IntersectionPair {
/// Are the colliders intersecting?
pub intersecting: bool,
/// Was a `CollisionEvent::Started` emitted for this collider?
pub(crate) start_event_emited: bool,
}
impl IntersectionPair {
pub(crate) fn new() -> Self {
Self {
intersecting: false,
start_event_emited: false,
}
}
pub(crate) fn emit_start_event(
&mut self,
collider1: ColliderHandle,
collider2: ColliderHandle,
events: &dyn EventHandler,
) {
self.start_event_emited = true;
events.handle_collision_event(CollisionEvent::new(collider1, collider2, true), None);
}
pub(crate) fn emit_stop_event(
&mut self,
collider1: ColliderHandle,
collider2: ColliderHandle,
events: &dyn EventHandler,
) {
self.start_event_emited = false;
events.handle_collision_event(CollisionEvent::new(collider1, collider2, false), None);
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Clone)] #[derive(Clone)]
/// The description of all the contacts between a pair of colliders. /// The description of all the contacts between a pair of colliders.
@@ -60,6 +102,8 @@ pub struct ContactPair {
pub manifolds: Vec<ContactManifold>, pub manifolds: Vec<ContactManifold>,
/// Is there any active contact in this contact pair? /// Is there any active contact in this contact pair?
pub has_any_active_contact: bool, pub has_any_active_contact: bool,
/// Was a `CollisionEvent::Started` emitted for this collider?
pub(crate) start_event_emited: bool,
pub(crate) workspace: Option<ContactManifoldsWorkspace>, pub(crate) workspace: Option<ContactManifoldsWorkspace>,
} }
@@ -70,6 +114,7 @@ impl ContactPair {
collider2, collider2,
has_any_active_contact: false, has_any_active_contact: false,
manifolds: Vec::new(), manifolds: Vec::new(),
start_event_emited: false,
workspace: None, workspace: None,
} }
} }
@@ -109,6 +154,24 @@ impl ContactPair {
deepest deepest
} }
pub(crate) fn emit_start_event(&mut self, events: &dyn EventHandler) {
self.start_event_emited = true;
events.handle_collision_event(
CollisionEvent::new(self.collider1, self.collider2, true),
Some(self),
);
}
pub(crate) fn emit_stop_event(&mut self, events: &dyn EventHandler) {
self.start_event_emited = false;
events.handle_collision_event(
CollisionEvent::new(self.collider1, self.collider2, false),
Some(self),
);
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@@ -2,8 +2,9 @@
pub use self::broad_phase_multi_sap::{BroadPhase, BroadPhasePairEvent, ColliderPair}; pub use self::broad_phase_multi_sap::{BroadPhase, BroadPhasePairEvent, ColliderPair};
pub use self::collider_components::*; pub use self::collider_components::*;
pub use self::contact_pair::{ContactData, ContactManifoldData}; pub use self::contact_pair::{
pub use self::contact_pair::{ContactPair, SolverContact, SolverFlags}; ContactData, ContactManifoldData, ContactPair, IntersectionPair, SolverContact, SolverFlags,
};
pub use self::interaction_graph::{ pub use self::interaction_graph::{
ColliderGraphIndex, InteractionGraph, RigidBodyGraphIndex, TemporaryInteractionIndex, ColliderGraphIndex, InteractionGraph, RigidBodyGraphIndex, TemporaryInteractionIndex,
}; };
@@ -56,8 +57,11 @@ pub use parry::shape::SharedShape;
pub enum CollisionEvent { pub enum CollisionEvent {
/// Event occurring when two colliders start being in contact (or intersecting) /// Event occurring when two colliders start being in contact (or intersecting)
Started(ColliderHandle, ColliderHandle), Started(ColliderHandle, ColliderHandle),
/// Event occurring when two colliders stop being in contact (or intersecting) /// Event occurring when two colliders stop being in contact (or intersecting).
Stopped(ColliderHandle, ColliderHandle), ///
/// The boolean is set to `true` of this event originates from at least one of
/// the colliders being removed from the `ColliderSet`.
Stopped(ColliderHandle, ColliderHandle, bool),
} }
impl CollisionEvent { impl CollisionEvent {
@@ -65,31 +69,31 @@ impl CollisionEvent {
if start { if start {
Self::Started(h1, h2) Self::Started(h1, h2)
} else { } else {
Self::Stopped(h1, h2) Self::Stopped(h1, h2, false)
} }
} }
/// Is this a `Started` collision event? /// Is this a `Started` collision event?
pub fn started(self) -> bool { pub fn started(self) -> bool {
matches!(self, CollisionEvent::Started(_, _)) matches!(self, CollisionEvent::Started(..))
} }
/// Is this a `Stopped` collision event? /// Is this a `Stopped` collision event?
pub fn stopped(self) -> bool { pub fn stopped(self) -> bool {
matches!(self, CollisionEvent::Stopped(_, _)) matches!(self, CollisionEvent::Stopped(..))
} }
/// The handle of the first collider involved in this collision event. /// The handle of the first collider involved in this collision event.
pub fn collider1(self) -> ColliderHandle { pub fn collider1(self) -> ColliderHandle {
match self { match self {
Self::Started(h, _) | Self::Stopped(h, _) => h, Self::Started(h, _) | Self::Stopped(h, _, _) => h,
} }
} }
/// The handle of the second collider involved in this collision event. /// The handle of the second collider involved in this collision event.
pub fn collider2(self) -> ColliderHandle { pub fn collider2(self) -> ColliderHandle {
match self { match self {
Self::Started(_, h) | Self::Stopped(_, h) => h, Self::Started(_, h) | Self::Stopped(_, h, _) => h,
} }
} }
} }

View File

@@ -10,7 +10,7 @@ use crate::geometry::{
BroadPhasePairEvent, ColliderChanges, ColliderGraphIndex, ColliderHandle, ColliderMaterial, BroadPhasePairEvent, ColliderChanges, ColliderGraphIndex, ColliderHandle, ColliderMaterial,
ColliderPair, ColliderParent, ColliderPosition, ColliderShape, ColliderType, CollisionEvent, ColliderPair, ColliderParent, ColliderPosition, ColliderShape, ColliderType, CollisionEvent,
ContactData, ContactManifold, ContactManifoldData, ContactPair, InteractionGraph, ContactData, ContactManifold, ContactManifoldData, ContactPair, InteractionGraph,
SolverContact, SolverFlags, IntersectionPair, SolverContact, SolverFlags,
}; };
use crate::math::{Real, Vector}; use crate::math::{Real, Vector};
use crate::pipeline::{ use crate::pipeline::{
@@ -56,7 +56,7 @@ pub struct NarrowPhase {
)] )]
query_dispatcher: Arc<dyn PersistentQueryDispatcher<ContactManifoldData, ContactData>>, query_dispatcher: Arc<dyn PersistentQueryDispatcher<ContactManifoldData, ContactData>>,
contact_graph: InteractionGraph<ColliderHandle, ContactPair>, contact_graph: InteractionGraph<ColliderHandle, ContactPair>,
intersection_graph: InteractionGraph<ColliderHandle, bool>, intersection_graph: InteractionGraph<ColliderHandle, IntersectionPair>,
graph_indices: Coarena<ColliderGraphIndices>, graph_indices: Coarena<ColliderGraphIndices>,
} }
@@ -101,7 +101,7 @@ impl NarrowPhase {
} }
/// The intersection graph containing all intersection pairs and their intersection information. /// The intersection graph containing all intersection pairs and their intersection information.
pub fn intersection_graph(&self) -> &InteractionGraph<ColliderHandle, bool> { pub fn intersection_graph(&self) -> &InteractionGraph<ColliderHandle, IntersectionPair> {
&self.intersection_graph &self.intersection_graph
} }
@@ -146,7 +146,7 @@ impl NarrowPhase {
.flat_map(move |id| { .flat_map(move |id| {
self.intersection_graph self.intersection_graph
.interactions_with(id) .interactions_with(id)
.map(|e| (e.0, e.1, *e.2)) .map(|e| (e.0, e.1, e.2.intersecting))
}) })
} }
@@ -162,7 +162,7 @@ impl NarrowPhase {
.flat_map(move |id| { .flat_map(move |id| {
self.intersection_graph self.intersection_graph
.interactions_with(id) .interactions_with(id)
.map(|e| (e.0, e.1, *e.2)) .map(|e| (e.0, e.1, e.2.intersecting))
}) })
} }
@@ -211,7 +211,7 @@ impl NarrowPhase {
let id2 = self.graph_indices.get_unknown_gen(collider2)?; let id2 = self.graph_indices.get_unknown_gen(collider2)?;
self.intersection_graph self.intersection_graph
.interaction_pair(id1.intersection_graph_index, id2.intersection_graph_index) .interaction_pair(id1.intersection_graph_index, id2.intersection_graph_index)
.map(|c| *c.2) .map(|c| c.2.intersecting)
} }
/// The intersection pair involving two specific colliders. /// The intersection pair involving two specific colliders.
@@ -227,7 +227,7 @@ impl NarrowPhase {
let id2 = self.graph_indices.get(collider2.0)?; let id2 = self.graph_indices.get(collider2.0)?;
self.intersection_graph self.intersection_graph
.interaction_pair(id1.intersection_graph_index, id2.intersection_graph_index) .interaction_pair(id1.intersection_graph_index, id2.intersection_graph_index)
.map(|c| *c.2) .map(|c| c.2.intersecting)
} }
/// All the contact pairs maintained by this narrow-phase. /// All the contact pairs maintained by this narrow-phase.
@@ -241,7 +241,7 @@ impl NarrowPhase {
) -> impl Iterator<Item = (ColliderHandle, ColliderHandle, bool)> + '_ { ) -> impl Iterator<Item = (ColliderHandle, ColliderHandle, bool)> + '_ {
self.intersection_graph self.intersection_graph
.interactions_with_endpoints() .interactions_with_endpoints()
.map(|e| (e.0, e.1, *e.2)) .map(|e| (e.0, e.1, e.2.intersecting))
} }
// #[cfg(feature = "parallel")] // #[cfg(feature = "parallel")]
@@ -297,6 +297,7 @@ impl NarrowPhase {
bodies, bodies,
&mut prox_id_remap, &mut prox_id_remap,
&mut contact_id_remap, &mut contact_id_remap,
events,
); );
} }
} }
@@ -308,20 +309,21 @@ impl NarrowPhase {
&mut self, &mut self,
intersection_graph_id: ColliderGraphIndex, intersection_graph_id: ColliderGraphIndex,
contact_graph_id: ColliderGraphIndex, contact_graph_id: ColliderGraphIndex,
islands: Option<&mut IslandManager>, mut islands: Option<&mut IslandManager>,
colliders: &mut Colliders, colliders: &mut Colliders,
bodies: &mut Bodies, bodies: &mut Bodies,
prox_id_remap: &mut HashMap<ColliderHandle, ColliderGraphIndex>, prox_id_remap: &mut HashMap<ColliderHandle, ColliderGraphIndex>,
contact_id_remap: &mut HashMap<ColliderHandle, ColliderGraphIndex>, contact_id_remap: &mut HashMap<ColliderHandle, ColliderGraphIndex>,
events: &dyn EventHandler,
) where ) where
Bodies: ComponentSetMut<RigidBodyActivation> Bodies: ComponentSetMut<RigidBodyActivation>
+ ComponentSet<RigidBodyType> + ComponentSet<RigidBodyType>
+ ComponentSetMut<RigidBodyIds>, + ComponentSetMut<RigidBodyIds>,
Colliders: ComponentSetOption<ColliderParent>, Colliders: ComponentSetOption<ColliderParent>,
{ {
// Wake up every body in contact with the deleted collider. // Wake up every body in contact with the deleted collider and generate Stopped collision events.
if let Some(islands) = islands { if let Some(islands) = islands.as_deref_mut() {
for (a, b, _) in self.contact_graph.interactions_with(contact_graph_id) { for (a, b, pair) in self.contact_graph.interactions_with(contact_graph_id) {
if let Some(parent) = colliders.get(a.0).map(|c| c.handle) { if let Some(parent) = colliders.get(a.0).map(|c| c.handle) {
islands.wake_up(bodies, parent, true) islands.wake_up(bodies, parent, true)
} }
@@ -329,6 +331,24 @@ impl NarrowPhase {
if let Some(parent) = colliders.get(b.0).map(|c| c.handle) { if let Some(parent) = colliders.get(b.0).map(|c| c.handle) {
islands.wake_up(bodies, parent, true) islands.wake_up(bodies, parent, true)
} }
if pair.start_event_emited {
events.handle_collision_event(CollisionEvent::Stopped(a, b, true), Some(pair));
}
}
} else {
// If there is no island, dont wake-up bodies, but do send the Stopped collision event.
for (a, b, pair) in self.contact_graph.interactions_with(contact_graph_id) {
if pair.start_event_emited {
events.handle_collision_event(CollisionEvent::Stopped(a, b, true), Some(pair));
}
}
}
// Generate Stopped collision events for intersections.
for (a, b, pair) in self.intersection_graph.interactions_with(contact_graph_id) {
if pair.start_event_emited {
events.handle_collision_event(CollisionEvent::Stopped(a, b, true), None);
} }
} }
@@ -506,21 +526,21 @@ impl NarrowPhase {
|| (mode == PairRemovalMode::Auto || (mode == PairRemovalMode::Auto
&& (co_type1.is_sensor() || co_type2.is_sensor())) && (co_type1.is_sensor() || co_type2.is_sensor()))
{ {
let was_intersecting = self let intersection = self
.intersection_graph .intersection_graph
.remove_edge(gid1.intersection_graph_index, gid2.intersection_graph_index); .remove_edge(gid1.intersection_graph_index, gid2.intersection_graph_index);
// Emit an intersection lost event if we had an intersection before removing the edge. // Emit an intersection lost event if we had an intersection before removing the edge.
if Some(true) == was_intersecting { if let Some(mut intersection) = intersection {
let co_flag1: &ColliderFlags = colliders.index(pair.collider1.0); if intersection.intersecting {
let co_flag2: &ColliderFlags = colliders.index(pair.collider2.0); let co_flag1: &ColliderFlags = colliders.index(pair.collider1.0);
let co_flag2: &ColliderFlags = colliders.index(pair.collider2.0);
if (co_flag1.active_events | co_flag2.active_events) if (co_flag1.active_events | co_flag2.active_events)
.contains(ActiveEvents::COLLISION_EVENTS) .contains(ActiveEvents::COLLISION_EVENTS)
{ {
let prox_event = intersection.emit_stop_event(pair.collider1, pair.collider2, events)
CollisionEvent::Stopped(pair.collider1, pair.collider2); }
events.handle_intersection_event(prox_event)
} }
} }
} else { } else {
@@ -530,7 +550,7 @@ impl NarrowPhase {
// Emit a contact stopped event if we had a contact before removing the edge. // Emit a contact stopped event if we had a contact before removing the edge.
// Also wake up the dynamic bodies that were in contact. // Also wake up the dynamic bodies that were in contact.
if let Some(ctct) = contact_pair { if let Some(mut ctct) = contact_pair {
if ctct.has_any_active_contact { if ctct.has_any_active_contact {
let co_parent1: Option<&ColliderParent> = let co_parent1: Option<&ColliderParent> =
colliders.get(pair.collider1.0); colliders.get(pair.collider1.0);
@@ -553,10 +573,7 @@ impl NarrowPhase {
if (co_flag1.active_events | co_flag2.active_events) if (co_flag1.active_events | co_flag2.active_events)
.contains(ActiveEvents::COLLISION_EVENTS) .contains(ActiveEvents::COLLISION_EVENTS)
{ {
events.handle_contact_event( ctct.emit_stop_event(events);
CollisionEvent::Stopped(pair.collider1, pair.collider2),
&ctct,
)
} }
} }
} }
@@ -615,7 +632,7 @@ impl NarrowPhase {
let _ = self.intersection_graph.add_edge( let _ = self.intersection_graph.add_edge(
gid1.intersection_graph_index, gid1.intersection_graph_index,
gid2.intersection_graph_index, gid2.intersection_graph_index,
false, IntersectionPair::new(),
); );
} }
} else { } else {
@@ -712,7 +729,7 @@ impl NarrowPhase {
par_iter_mut!(&mut self.intersection_graph.graph.edges).for_each(|edge| { par_iter_mut!(&mut self.intersection_graph.graph.edges).for_each(|edge| {
let handle1 = nodes[edge.source().index()].weight; let handle1 = nodes[edge.source().index()].weight;
let handle2 = nodes[edge.target().index()].weight; let handle2 = nodes[edge.target().index()].weight;
let had_intersection = edge.weight; let had_intersection = edge.weight.intersecting;
// TODO: remove the `loop` once labels on blocks is stabilized. // TODO: remove the `loop` once labels on blocks is stabilized.
'emit_events: loop { 'emit_events: loop {
@@ -755,13 +772,13 @@ impl NarrowPhase {
if !co_flags1.active_collision_types.test(rb_type1, rb_type2) if !co_flags1.active_collision_types.test(rb_type1, rb_type2)
&& !co_flags2.active_collision_types.test(rb_type1, rb_type2) && !co_flags2.active_collision_types.test(rb_type1, rb_type2)
{ {
edge.weight = false; edge.weight.intersecting = false;
break 'emit_events; break 'emit_events;
} }
// Filter based on collision groups. // Filter based on collision groups.
if !co_flags1.collision_groups.test(co_flags2.collision_groups) { if !co_flags1.collision_groups.test(co_flags2.collision_groups) {
edge.weight = false; edge.weight.intersecting = false;
break 'emit_events; break 'emit_events;
} }
@@ -779,13 +796,13 @@ impl NarrowPhase {
if !hooks.filter_intersection_pair(&context) { if !hooks.filter_intersection_pair(&context) {
// No intersection allowed. // No intersection allowed.
edge.weight = false; edge.weight.intersecting = false;
break 'emit_events; break 'emit_events;
} }
} }
let pos12 = co_pos1.inv_mul(co_pos2); let pos12 = co_pos1.inv_mul(co_pos2);
edge.weight = query_dispatcher edge.weight.intersecting = query_dispatcher
.intersection_test(&pos12, &**co_shape1, &**co_shape2) .intersection_test(&pos12, &**co_shape1, &**co_shape2)
.unwrap_or(false); .unwrap_or(false);
break 'emit_events; break 'emit_events;
@@ -796,13 +813,13 @@ impl NarrowPhase {
let active_events = co_flags1.active_events | co_flags2.active_events; let active_events = co_flags1.active_events | co_flags2.active_events;
if active_events.contains(ActiveEvents::COLLISION_EVENTS) if active_events.contains(ActiveEvents::COLLISION_EVENTS)
&& had_intersection != edge.weight && had_intersection != edge.weight.intersecting
{ {
events.handle_intersection_event(CollisionEvent::new( if edge.weight.intersecting {
handle1, edge.weight.emit_start_event(handle1, handle2, events);
handle2, } else {
edge.weight, edge.weight.emit_stop_event(handle1, handle2, events);
)); }
} }
}); });
} }
@@ -1029,15 +1046,9 @@ impl NarrowPhase {
if pair.has_any_active_contact != had_any_active_contact { if pair.has_any_active_contact != had_any_active_contact {
if active_events.contains(ActiveEvents::COLLISION_EVENTS) { if active_events.contains(ActiveEvents::COLLISION_EVENTS) {
if pair.has_any_active_contact { if pair.has_any_active_contact {
events.handle_contact_event( pair.emit_start_event(events);
CollisionEvent::Started(pair.collider1, pair.collider2),
pair,
);
} else { } else {
events.handle_contact_event( pair.emit_stop_event(events);
CollisionEvent::Stopped(pair.collider1, pair.collider2),
pair,
);
} }
} }
} }

View File

@@ -23,18 +23,18 @@ impl Default for ActiveEvents {
pub trait EventHandler: Send + Sync { pub trait EventHandler: Send + Sync {
/// Handle a collision event. /// Handle a collision event.
/// ///
/// A intersection event is emitted when the state of intersection between two colliders changes. /// A collision event is emitted when the state of intersection between two colliders changes.
fn handle_intersection_event(&self, event: CollisionEvent);
/// Handle a contact event.
/// ///
/// A contact event is emitted when two collider start or stop touching, independently from the /// # Parameters
/// number of contact points involved. /// * `event` - The collision event.
fn handle_contact_event(&self, event: CollisionEvent, contact_pair: &ContactPair); /// * `contact_pair` - The current state of contacts between the two colliders. This is set ot `None`
/// if at least one of the collider is a sensor (in which case no contact information
/// is ever computed).
fn handle_collision_event(&self, event: CollisionEvent, contact_pair: Option<&ContactPair>);
} }
impl EventHandler for () { impl EventHandler for () {
fn handle_intersection_event(&self, _event: CollisionEvent) {} fn handle_collision_event(&self, _event: CollisionEvent, _contact_pair: Option<&ContactPair>) {}
fn handle_contact_event(&self, _event: CollisionEvent, _contact_pair: &ContactPair) {}
} }
/// A collision event handler that collects events into a crossbeam channel. /// A collision event handler that collects events into a crossbeam channel.
@@ -50,11 +50,7 @@ impl ChannelEventCollector {
} }
impl EventHandler for ChannelEventCollector { impl EventHandler for ChannelEventCollector {
fn handle_intersection_event(&self, event: CollisionEvent) { fn handle_collision_event(&self, event: CollisionEvent, _: Option<&ContactPair>) {
let _ = self.event_sender.send(event);
}
fn handle_contact_event(&self, event: CollisionEvent, _: &ContactPair) {
let _ = self.event_sender.send(event); let _ = self.event_sender.send(event);
} }
} }