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#[derive(Message, Copy, Clone, Debug, PartialEq, Eq)]
21pub enum CollisionEvent {
22 Started(Entity, Entity, CollisionEventFlags),
24 Stopped(Entity, Entity, CollisionEventFlags),
26}
27
28#[derive(Message, Copy, Clone, Debug, PartialEq)]
34pub struct ContactForceEvent {
35 pub collider1: Entity,
37 pub collider2: Entity,
39 pub total_force: Vect,
41 pub total_force_magnitude: Real,
47 pub max_force_direction: Vect,
49 pub max_force_magnitude: Real,
51}
52
53pub(crate) struct EventQueue<'a> {
59 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 app.insert_resource(TimeUpdateStrategy::ManualDuration(
181 std::time::Duration::from_secs_f32(1f32 / 60f32),
182 ));
183 app.finish();
184 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 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 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 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 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}