bevy_rapier3d/pipeline/
events.rs

1use crate::math::{Real, Vect};
2use bevy::ecs::message::Message;
3use bevy::prelude::Entity;
4use rapier::dynamics::RigidBodySet;
5use rapier::geometry::{
6    ColliderHandle, ColliderSet, CollisionEvent as RapierCollisionEvent, CollisionEventFlags,
7    ContactForceEvent as RapierContactForceEvent, ContactPair,
8};
9use rapier::pipeline::EventHandler;
10use std::collections::HashMap;
11use std::sync::RwLock;
12
13#[cfg(doc)]
14use crate::prelude::{ActiveEvents, ContactForceEventThreshold};
15
16/// Events occurring when two colliders start or stop colliding
17///
18/// This will only get triggered if the entity has the
19/// [`ActiveEvents::COLLISION_EVENTS`] flag enabled.
20#[derive(Message, Copy, Clone, Debug, PartialEq, Eq)]
21pub enum CollisionEvent {
22    /// Event occurring when two colliders start colliding
23    Started(Entity, Entity, CollisionEventFlags),
24    /// Event occurring when two colliders stop colliding
25    Stopped(Entity, Entity, CollisionEventFlags),
26}
27
28/// Event occurring when the sum of the magnitudes of the contact forces
29/// between two colliders exceed a threshold ([`ContactForceEventThreshold`]).
30///
31/// This will only get triggered if the entity has the
32/// [`ActiveEvents::CONTACT_FORCE_EVENTS`] flag enabled.
33#[derive(Message, Copy, Clone, Debug, PartialEq)]
34pub struct ContactForceEvent {
35    /// The first collider involved in the contact.
36    pub collider1: Entity,
37    /// The second collider involved in the contact.
38    pub collider2: Entity,
39    /// The sum of all the forces between the two colliders.
40    pub total_force: Vect,
41    /// The sum of the magnitudes of each force between the two colliders.
42    ///
43    /// Note that this is **not** the same as the magnitude of `self.total_force`.
44    /// Here we are summing the magnitude of all the forces, instead of taking
45    /// the magnitude of their sum.
46    pub total_force_magnitude: Real,
47    /// The world-space (unit) direction of the force with strongest magnitude.
48    pub max_force_direction: Vect,
49    /// The magnitude of the largest force at a contact point of this contact pair.
50    pub max_force_magnitude: Real,
51}
52
53// TODO: it may be more efficient to use crossbeam channel.
54// However crossbeam channels cause a Segfault (I have not
55// investigated how to reproduce this exactly to open an
56// issue).
57/// A set of queues collecting events emitted by the physics engine.
58pub(crate) struct EventQueue<'a> {
59    // Used to retrieve the entity of colliders that have been removed from the simulation
60    // since the last physics step.
61    pub deleted_colliders: &'a HashMap<ColliderHandle, Entity>,
62    pub collision_events: RwLock<Vec<CollisionEvent>>,
63    pub contact_force_events: RwLock<Vec<ContactForceEvent>>,
64}
65
66impl EventQueue<'_> {
67    fn collider2entity(&self, colliders: &ColliderSet, handle: ColliderHandle) -> Entity {
68        colliders
69            .get(handle)
70            .map(|co| Entity::from_bits(co.user_data as u64))
71            .or_else(|| self.deleted_colliders.get(&handle).copied())
72            .expect("Internal error: entity not found for collision event.")
73    }
74}
75
76impl EventHandler for EventQueue<'_> {
77    fn handle_collision_event(
78        &self,
79        _bodies: &RigidBodySet,
80        colliders: &ColliderSet,
81        event: RapierCollisionEvent,
82        _: Option<&ContactPair>,
83    ) {
84        let event = match event {
85            RapierCollisionEvent::Started(h1, h2, flags) => {
86                let e1 = self.collider2entity(colliders, h1);
87                let e2 = self.collider2entity(colliders, h2);
88                CollisionEvent::Started(e1, e2, flags)
89            }
90            RapierCollisionEvent::Stopped(h1, h2, flags) => {
91                let e1 = self.collider2entity(colliders, h1);
92                let e2 = self.collider2entity(colliders, h2);
93                CollisionEvent::Stopped(e1, e2, flags)
94            }
95        };
96
97        if let Ok(mut events) = self.collision_events.write() {
98            events.push(event);
99        }
100    }
101
102    fn handle_contact_force_event(
103        &self,
104        dt: Real,
105        _bodies: &RigidBodySet,
106        colliders: &ColliderSet,
107        contact_pair: &ContactPair,
108        total_force_magnitude: Real,
109    ) {
110        let rapier_event =
111            RapierContactForceEvent::from_contact_pair(dt, contact_pair, total_force_magnitude);
112        let event = ContactForceEvent {
113            collider1: self.collider2entity(colliders, rapier_event.collider1),
114            collider2: self.collider2entity(colliders, rapier_event.collider2),
115            total_force: rapier_event.total_force.into(),
116            total_force_magnitude: rapier_event.total_force_magnitude,
117            max_force_direction: rapier_event.max_force_direction.into(),
118            max_force_magnitude: rapier_event.max_force_magnitude,
119        };
120
121        if let Ok(mut events) = self.contact_force_events.write() {
122            events.push(event);
123        }
124    }
125}
126
127#[cfg(test)]
128mod test {
129    use bevy::{
130        app::{App, Startup, Update},
131        prelude::{Commands, Component, Entity, Query, With},
132        time::{TimePlugin, TimeUpdateStrategy},
133        transform::{components::Transform, TransformPlugin},
134        MinimalPlugins,
135    };
136
137    use crate::{plugin::*, prelude::*};
138
139    #[cfg(feature = "dim3")]
140    fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
141        Collider::cuboid(hx, hy, hz)
142    }
143    #[cfg(feature = "dim2")]
144    fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
145        Collider::cuboid(hx, hy)
146    }
147
148    #[test]
149    pub fn events_received() {
150        return main();
151
152        use bevy::prelude::*;
153
154        #[derive(Resource, Reflect)]
155        pub struct EventsSaver<E: Message> {
156            pub events: Vec<E>,
157        }
158        impl<E: Message> Default for EventsSaver<E> {
159            fn default() -> Self {
160                Self {
161                    events: Default::default(),
162                }
163            }
164        }
165        pub fn save_events<E: Message + Clone>(
166            mut events: MessageReader<E>,
167            mut saver: ResMut<EventsSaver<E>>,
168        ) {
169            for event in events.read() {
170                saver.events.push(event.clone());
171            }
172        }
173        fn run_test(app: &mut App) {
174            app.add_systems(PostUpdate, save_events::<CollisionEvent>)
175                .add_systems(PostUpdate, save_events::<ContactForceEvent>)
176                .init_resource::<EventsSaver<CollisionEvent>>()
177                .init_resource::<EventsSaver<ContactForceEvent>>();
178
179            // Simulates 60 updates per seconds
180            app.insert_resource(TimeUpdateStrategy::ManualDuration(
181                std::time::Duration::from_secs_f32(1f32 / 60f32),
182            ));
183            app.finish();
184            // 2 seconds should be plenty of time for the cube to fall on the
185            // lowest collider.
186            for _ in 0..120 {
187                app.update();
188            }
189            let saved_collisions = app
190                .world()
191                .get_resource::<EventsSaver<CollisionEvent>>()
192                .unwrap();
193            assert!(!saved_collisions.events.is_empty());
194            let saved_contact_forces = app
195                .world()
196                .get_resource::<EventsSaver<CollisionEvent>>()
197                .unwrap();
198            assert!(!saved_contact_forces.events.is_empty());
199        }
200
201        /// Adapted from events example
202        fn main() {
203            let mut app = App::new();
204            app.add_plugins((
205                TransformPlugin,
206                TimePlugin,
207                RapierPhysicsPlugin::<NoUserData>::default(),
208            ))
209            .add_systems(Startup, setup_physics);
210            run_test(&mut app);
211        }
212
213        pub fn setup_physics(mut commands: Commands) {
214            /*
215             * Ground
216             */
217            commands.spawn((Transform::from_xyz(0.0, -1.2, 0.0), cuboid(4.0, 1.0, 1.0)));
218
219            commands.spawn((
220                Transform::from_xyz(0.0, 5.0, 0.0),
221                cuboid(4.0, 1.5, 1.0),
222                Sensor,
223            ));
224
225            commands.spawn((
226                Transform::from_xyz(0.0, 13.0, 0.0),
227                RigidBody::Dynamic,
228                cuboid(0.5, 0.5, 0.5),
229                ActiveEvents::COLLISION_EVENTS,
230                ContactForceEventThreshold(30.0),
231            ));
232        }
233    }
234
235    #[test]
236    pub fn spam_remove_rapier_entity_interpolated() {
237        let mut app = App::new();
238        app.add_plugins((
239            MinimalPlugins,
240            TransformPlugin,
241            RapierPhysicsPlugin::<NoUserData>::default(),
242        ))
243        .insert_resource(TimestepMode::Interpolated {
244            dt: 1.0 / 30.0,
245            time_scale: 1.0,
246            substeps: 2,
247        })
248        .add_systems(Startup, setup_physics)
249        .add_systems(Update, remove_collider);
250        // Simulates 60 updates per seconds
251        app.insert_resource(TimeUpdateStrategy::ManualDuration(
252            std::time::Duration::from_secs_f32(1f32 / 60f32),
253        ));
254
255        app.finish();
256
257        for _ in 0..100 {
258            app.update();
259        }
260        return;
261
262        #[derive(Component)]
263        pub struct ToRemove;
264
265        #[cfg(feature = "dim3")]
266        fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
267            Collider::cuboid(hx, hy, hz)
268        }
269        #[cfg(feature = "dim2")]
270        fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
271            Collider::cuboid(hx, hy)
272        }
273        pub fn setup_physics(mut commands: Commands) {
274            for _i in 0..100 {
275                commands.spawn((
276                    Transform::from_xyz(0.0, 0.0, 0.0),
277                    RigidBody::Dynamic,
278                    cuboid(0.5, 0.5, 0.5),
279                    ActiveEvents::all(),
280                    ToRemove,
281                ));
282            }
283            /*
284             * Ground
285             */
286            let ground_size = 5.1;
287            let ground_height = 0.1;
288            let starting_y = -0.5 - ground_height;
289
290            commands.spawn((
291                Transform::from_xyz(0.0, starting_y, 0.0),
292                cuboid(ground_size, ground_height, ground_size),
293            ));
294        }
295
296        fn remove_collider(mut commands: Commands, query: Query<Entity, With<ToRemove>>) {
297            let Some(entity) = query.iter().next() else {
298                return;
299            };
300            commands.entity(entity).despawn();
301        }
302    }
303}