Skip to main content

rapier2d/pipeline/
event_handler.rs

1#[cfg(feature = "alloc")]
2use crate::dynamics::RigidBodySet;
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///
54/// Implement this trait to get notified when:
55/// - Two colliders start or stop touching ([`handle_collision_event`](Self::handle_collision_event))
56/// - Contact forces exceed a threshold ([`handle_contact_force_event`](Self::handle_contact_force_event))
57///
58/// # Common use cases
59/// - Playing sound effects when objects collide
60/// - Triggering game events (damage, pickups, checkpoints)
61/// - Monitoring structural stress
62/// - Detecting when specific objects touch
63///
64/// # Built-in implementation
65/// Use [`ChannelEventCollector`] to collect events into channels for processing after the physics step.
66///
67/// # Example
68/// ```
69/// # use rapier3d::prelude::*;
70/// # use rapier3d::geometry::ContactPair;
71/// struct MyEventHandler;
72///
73/// impl EventHandler for MyEventHandler {
74///     fn handle_collision_event(
75///         &self,
76///         bodies: &RigidBodySet,
77///         colliders: &ColliderSet,
78///         event: CollisionEvent,
79///         contact_pair: Option<&ContactPair>,
80///     ) {
81///         match event {
82///             CollisionEvent::Started(h1, h2, _) => {
83///                 println!("Collision started between {:?} and {:?}", h1, h2);
84///             }
85///             CollisionEvent::Stopped(h1, h2, _) => {
86///                 println!("Collision ended between {:?} and {:?}", h1, h2);
87///             }
88///         }
89///     }
90/// #   fn handle_contact_force_event(&self, _dt: Real, _bodies: &RigidBodySet, _colliders: &ColliderSet, _contact_pair: &ContactPair, _total_force_magnitude: Real) {}
91/// }
92/// ```
93#[cfg(feature = "alloc")]
94pub trait EventHandler: crate::utils::MaybeSync {
95    /// Called when two colliders start or stop touching each other.
96    ///
97    /// Collision events are triggered when intersection state changes (Started/Stopped).
98    /// At least one collider must have [`ActiveEvents::COLLISION_EVENTS`] enabled.
99    ///
100    /// # Parameters
101    /// * `event` - Either `Started(h1, h2, flags)` or `Stopped(h1, h2, flags)`
102    /// * `bodies` - All rigid bodies (to look up body info)
103    /// * `colliders` - All colliders (to look up collider info)
104    /// * `contact_pair` - Detailed contact info (`None` for sensors, since they don't compute contacts)
105    ///
106    /// # Use cases
107    /// - Play collision sound effects
108    /// - Apply damage when objects hit
109    /// - Trigger game events (entering zones, picking up items)
110    /// - Track what's touching what
111    fn handle_collision_event(
112        &self,
113        bodies: &RigidBodySet,
114        colliders: &ColliderSet,
115        event: CollisionEvent,
116        contact_pair: Option<&ContactPair>,
117    );
118
119    /// Called when contact forces exceed a threshold.
120    ///
121    /// Triggered when the total force magnitude between two colliders exceeds the
122    /// [`Collider::contact_force_event_threshold`](crate::geometry::Collider::set_contact_force_event_threshold).
123    /// At least one collider must have [`ActiveEvents::CONTACT_FORCE_EVENTS`] enabled.
124    ///
125    /// # Use cases
126    /// - Detect hard impacts (for damage, breaking objects)
127    /// - Monitor structural stress
128    /// - Trigger effects at certain force levels (sparks, cracks)
129    ///
130    /// # Parameters
131    /// * `total_force_magnitude` - Sum of magnitudes of all contact forces (not vector sum!)
132    ///   Example: Two forces `[0, 100, 0]` and `[0, -100, 0]` → magnitude = 200 (not 0)
133    fn handle_contact_force_event(
134        &self,
135        dt: Real,
136        bodies: &RigidBodySet,
137        colliders: &ColliderSet,
138        contact_pair: &ContactPair,
139        total_force_magnitude: Real,
140    );
141}
142
143#[cfg(feature = "alloc")]
144impl EventHandler for () {
145    fn handle_collision_event(
146        &self,
147        _bodies: &RigidBodySet,
148        _colliders: &ColliderSet,
149        _event: CollisionEvent,
150        _contact_pair: Option<&ContactPair>,
151    ) {
152    }
153
154    fn handle_contact_force_event(
155        &self,
156        _dt: Real,
157        _bodies: &RigidBodySet,
158        _colliders: &ColliderSet,
159        _contact_pair: &ContactPair,
160        _total_force_magnitude: Real,
161    ) {
162    }
163}
164
165/// A ready-to-use event handler that collects events into channels for later processing.
166///
167/// Instead of processing events immediately during physics step, this collector sends them
168/// to channels that you can poll from your game loop. This is the recommended approach.
169///
170/// # Example
171/// ```
172/// # use rapier3d::prelude::*;
173/// use std::sync::mpsc::channel;
174///
175/// let (collision_send, collision_recv) = channel();
176/// let (contact_force_send, contact_force_recv) = channel();
177/// let event_handler = ChannelEventCollector::new(collision_send, contact_force_send);
178///
179/// // After physics step:
180/// while let Ok(collision_event) = collision_recv.try_recv() {
181///     match collision_event {
182///         CollisionEvent::Started(h1, h2, _) => println!("Collision!"),
183///         CollisionEvent::Stopped(h1, h2, _) => println!("Separated"),
184///     }
185/// }
186/// ```
187#[cfg(feature = "std")]
188pub struct ChannelEventCollector {
189    collision_event_sender: std::sync::mpsc::Sender<CollisionEvent>,
190    contact_force_event_sender: std::sync::mpsc::Sender<ContactForceEvent>,
191}
192
193#[cfg(feature = "std")]
194impl ChannelEventCollector {
195    /// Initialize a new collision event handler from channel senders.
196    pub fn new(
197        collision_event_sender: std::sync::mpsc::Sender<CollisionEvent>,
198        contact_force_event_sender: std::sync::mpsc::Sender<ContactForceEvent>,
199    ) -> Self {
200        Self {
201            collision_event_sender,
202            contact_force_event_sender,
203        }
204    }
205}
206
207#[cfg(feature = "std")]
208impl EventHandler for ChannelEventCollector {
209    fn handle_collision_event(
210        &self,
211        _bodies: &RigidBodySet,
212        _colliders: &ColliderSet,
213        event: CollisionEvent,
214        _: Option<&ContactPair>,
215    ) {
216        let _ = self.collision_event_sender.send(event);
217    }
218
219    fn handle_contact_force_event(
220        &self,
221        dt: Real,
222        _bodies: &RigidBodySet,
223        _colliders: &ColliderSet,
224        contact_pair: &ContactPair,
225        total_force_magnitude: Real,
226    ) {
227        let result = ContactForceEvent::from_contact_pair(dt, contact_pair, total_force_magnitude);
228        let _ = self.contact_force_event_sender.send(result);
229    }
230}