Skip to main content

rapier2d/pipeline/
event_handler.rs

1#[cfg(feature = "alloc")]
2use crate::dynamics::{RigidBodySet, SoftBodySet, SoftBodyTearEvent};
3#[cfg(all(feature = "std", feature = "alloc"))]
4use crate::geometry::ContactForceEvent;
5#[cfg(feature = "alloc")]
6use crate::geometry::{ColliderSet, CollisionEvent, ContactPair};
7#[cfg(feature = "alloc")]
8use crate::math::Real;
9
10bitflags::bitflags! {
11    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
12    #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
13    /// Flags that control which physics events are generated for a collider.
14    ///
15    /// By default, colliders don't generate events (for performance). Enable specific events
16    /// per-collider using these flags.
17    ///
18    /// # Example
19    /// ```
20    /// # use rapier3d::prelude::*;
21    /// // Enable collision start/stop events for a trigger zone
22    /// let trigger = ColliderBuilder::cuboid(5.0, 5.0, 5.0)
23    ///     .sensor(true)
24    ///     .active_events(ActiveEvents::COLLISION_EVENTS)
25    ///     .build();
26    ///
27    /// // Enable force events for breakable glass
28    /// let glass = ColliderBuilder::cuboid(1.0, 2.0, 0.1)
29    ///     .active_events(ActiveEvents::CONTACT_FORCE_EVENTS)
30    ///     .contact_force_event_threshold(1000.0)
31    ///     .build();
32    /// ```
33    pub struct ActiveEvents: u32 {
34        /// Enables `Started`/`Stopped` collision events for this collider.
35        ///
36        /// You'll receive events when this collider starts or stops touching others.
37        const COLLISION_EVENTS = 0b0001;
38
39        /// Enables contact force events when forces exceed a threshold.
40        ///
41        /// You'll receive events when contact forces surpass `contact_force_event_threshold`.
42        const CONTACT_FORCE_EVENTS = 0b0010;
43    }
44}
45
46impl Default for ActiveEvents {
47    fn default() -> Self {
48        ActiveEvents::empty()
49    }
50}
51
52/// A callback interface for receiving physics events (collisions starting/stopping, contact forces,
53/// soft-body tears).
54///
55/// Implement this trait to get notified when:
56/// - Two colliders start or stop touching ([`handle_collision_event`](Self::handle_collision_event))
57/// - Contact forces exceed a threshold ([`handle_contact_force_event`](Self::handle_contact_force_event))
58/// - A soft body tears ([`handle_soft_body_tear_event`](Self::handle_soft_body_tear_event))
59///
60/// # Common use cases
61/// - Playing sound effects when objects collide
62/// - Triggering game events (damage, pickups, checkpoints)
63/// - Monitoring structural stress
64/// - Detecting when specific objects touch
65///
66/// # Built-in implementation
67/// Use [`ChannelEventCollector`] to collect events into channels for processing after the physics step.
68///
69/// # Example
70/// ```
71/// # use rapier3d::prelude::*;
72/// # use rapier3d::geometry::ContactPair;
73/// struct MyEventHandler;
74///
75/// impl EventHandler for MyEventHandler {
76///     fn handle_collision_event(
77///         &self,
78///         bodies: &RigidBodySet,
79///         colliders: &ColliderSet,
80///         event: CollisionEvent,
81///         contact_pair: Option<&ContactPair>,
82///     ) {
83///         match event {
84///             CollisionEvent::Started(h1, h2, _) => {
85///                 println!("Collision started between {:?} and {:?}", h1, h2);
86///             }
87///             CollisionEvent::Stopped(h1, h2, _) => {
88///                 println!("Collision ended between {:?} and {:?}", h1, h2);
89///             }
90///         }
91///     }
92/// #   fn handle_contact_force_event(&self, _dt: Real, _bodies: &RigidBodySet, _colliders: &ColliderSet, _contact_pair: &ContactPair, _total_force_magnitude: Real) {}
93/// #   fn handle_soft_body_tear_event(&self, _soft_bodies: &SoftBodySet, _event: &SoftBodyTearEvent) {}
94/// }
95/// ```
96#[cfg(feature = "alloc")]
97pub trait EventHandler: crate::utils::MaybeSync {
98    /// Called when two colliders start or stop touching each other.
99    ///
100    /// Collision events are triggered when intersection state changes (Started/Stopped).
101    /// At least one collider must have [`ActiveEvents::COLLISION_EVENTS`] enabled.
102    ///
103    /// # Parameters
104    /// * `event` - Either `Started(h1, h2, flags)` or `Stopped(h1, h2, flags)`
105    /// * `bodies` - All rigid bodies (to look up body info)
106    /// * `colliders` - All colliders (to look up collider info)
107    /// * `contact_pair` - Detailed contact info (`None` for sensors, since they don't compute contacts)
108    ///
109    /// # Use cases
110    /// - Play collision sound effects
111    /// - Apply damage when objects hit
112    /// - Trigger game events (entering zones, picking up items)
113    /// - Track what's touching what
114    fn handle_collision_event(
115        &self,
116        bodies: &RigidBodySet,
117        colliders: &ColliderSet,
118        event: CollisionEvent,
119        contact_pair: Option<&ContactPair>,
120    );
121
122    /// Called when contact forces exceed a threshold.
123    ///
124    /// Triggered when the total force magnitude between two colliders exceeds the
125    /// [`Collider::contact_force_event_threshold`](crate::geometry::Collider::set_contact_force_event_threshold).
126    /// At least one collider must have [`ActiveEvents::CONTACT_FORCE_EVENTS`] enabled.
127    ///
128    /// # Use cases
129    /// - Detect hard impacts (for damage, breaking objects)
130    /// - Monitor structural stress
131    /// - Trigger effects at certain force levels (sparks, cracks)
132    ///
133    /// # Parameters
134    /// * `total_force_magnitude` - Sum of magnitudes of all contact forces (not vector sum!)
135    ///   Example: Two forces `[0, 100, 0]` and `[0, -100, 0]` → magnitude = 200 (not 0)
136    fn handle_contact_force_event(
137        &self,
138        dt: Real,
139        bodies: &RigidBodySet,
140        colliders: &ColliderSet,
141        contact_pair: &ContactPair,
142        total_force_magnitude: Real,
143    );
144
145    /// Called at the end of a step for every soft body that tore during it (an element past its
146    /// tear threshold, or a `SoftBody::tear_edge`/`tear_cell` request), once the topology change is
147    /// applied ([`SoftBodyTearEvent`]); immediate `SoftBodySet::tear`/`cut` return theirs instead.
148    fn handle_soft_body_tear_event(&self, soft_bodies: &SoftBodySet, event: &SoftBodyTearEvent);
149}
150
151#[cfg(feature = "alloc")]
152impl EventHandler for () {
153    fn handle_collision_event(
154        &self,
155        _bodies: &RigidBodySet,
156        _colliders: &ColliderSet,
157        _event: CollisionEvent,
158        _contact_pair: Option<&ContactPair>,
159    ) {
160    }
161
162    fn handle_contact_force_event(
163        &self,
164        _dt: Real,
165        _bodies: &RigidBodySet,
166        _colliders: &ColliderSet,
167        _contact_pair: &ContactPair,
168        _total_force_magnitude: Real,
169    ) {
170    }
171
172    fn handle_soft_body_tear_event(&self, _soft_bodies: &SoftBodySet, _event: &SoftBodyTearEvent) {}
173}
174
175/// A ready-to-use event handler that collects events into channels for later processing.
176///
177/// Instead of processing events immediately during physics step, this collector sends them
178/// to channels that you can poll from your game loop. This is the recommended approach.
179///
180/// # Example
181/// ```
182/// # use rapier3d::prelude::*;
183/// use std::sync::mpsc::channel;
184///
185/// let (collision_send, collision_recv) = channel();
186/// let (contact_force_send, contact_force_recv) = channel();
187/// let (soft_body_tear_send, soft_body_tear_recv) = channel();
188/// let event_handler =
189///     ChannelEventCollector::new(collision_send, contact_force_send, soft_body_tear_send);
190///
191/// // After physics step:
192/// while let Ok(collision_event) = collision_recv.try_recv() {
193///     match collision_event {
194///         CollisionEvent::Started(h1, h2, _) => println!("Collision!"),
195///         CollisionEvent::Stopped(h1, h2, _) => println!("Separated"),
196///     }
197/// }
198/// while let Ok(tear_event) = soft_body_tear_recv.try_recv() {
199///     println!("Soft body {:?} tore", tear_event.soft_body);
200/// }
201/// ```
202#[cfg(feature = "std")]
203pub struct ChannelEventCollector {
204    collision_event_sender: std::sync::mpsc::Sender<CollisionEvent>,
205    contact_force_event_sender: std::sync::mpsc::Sender<ContactForceEvent>,
206    soft_body_tear_event_sender: std::sync::mpsc::Sender<SoftBodyTearEvent>,
207}
208
209#[cfg(feature = "std")]
210impl ChannelEventCollector {
211    /// Initialize a new event collector from channel senders, one per event kind.
212    ///
213    /// A sender whose receiver was dropped just discards the events of its kind.
214    pub fn new(
215        collision_event_sender: std::sync::mpsc::Sender<CollisionEvent>,
216        contact_force_event_sender: std::sync::mpsc::Sender<ContactForceEvent>,
217        soft_body_tear_event_sender: std::sync::mpsc::Sender<SoftBodyTearEvent>,
218    ) -> Self {
219        Self {
220            collision_event_sender,
221            contact_force_event_sender,
222            soft_body_tear_event_sender,
223        }
224    }
225}
226
227#[cfg(feature = "std")]
228impl EventHandler for ChannelEventCollector {
229    fn handle_collision_event(
230        &self,
231        _bodies: &RigidBodySet,
232        _colliders: &ColliderSet,
233        event: CollisionEvent,
234        _: Option<&ContactPair>,
235    ) {
236        let _ = self.collision_event_sender.send(event);
237    }
238
239    fn handle_contact_force_event(
240        &self,
241        dt: Real,
242        _bodies: &RigidBodySet,
243        _colliders: &ColliderSet,
244        contact_pair: &ContactPair,
245        total_force_magnitude: Real,
246    ) {
247        let result = ContactForceEvent::from_contact_pair(dt, contact_pair, total_force_magnitude);
248        let _ = self.contact_force_event_sender.send(result);
249    }
250
251    fn handle_soft_body_tear_event(&self, _soft_bodies: &SoftBodySet, event: &SoftBodyTearEvent) {
252        let _ = self.soft_body_tear_event_sender.send(event.clone());
253    }
254}