1use crate::math::{Real, Vect};
2use bevy::ecs::message::Message;
3use bevy::prelude::Entity;
4use rapier::dynamics::{RigidBodySet, SoftBodySet, SoftBodyTearEvent as RapierSoftBodyTearEvent};
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::{
15 ActiveEvents, ContactForceEventThreshold, RapierContext, RapierContextMut,
16 RapierContextSimulation, SoftBodyAttachments, SoftBodyExternalForce, SoftBodyExternalImpulse,
17 SoftBodyKinematicTargets, SoftBodyPinnedParticles,
18};
19
20#[derive(Message, Copy, Clone, Debug, PartialEq, Eq)]
34pub enum CollisionEvent {
35 Started(Entity, Entity, CollisionEventFlags),
37 Stopped(Entity, Entity, CollisionEventFlags),
39}
40
41#[derive(Message, Clone, Debug, PartialEq, Eq)]
62pub struct PhysicsQuarantineEvent {
63 pub context: Entity,
65 pub bodies: Vec<Entity>,
67 pub colliders: Vec<Entity>,
70 pub soft_bodies: Vec<Entity>,
73}
74
75#[derive(Message, Copy, Clone, Debug, PartialEq)]
81pub struct ContactForceEvent {
82 pub collider1: Entity,
84 pub collider2: Entity,
86 pub total_force: Vect,
88 pub total_force_magnitude: Real,
94 pub max_force_direction: Vect,
96 pub max_force_magnitude: Real,
98 pub started: bool,
105}
106
107#[derive(Message, Clone, Debug)]
123pub struct SoftBodyTearEvent {
124 pub context: Entity,
126 pub soft_body: Entity,
128 pub pieces: Vec<SoftBodyTearPiece>,
134 pub cluster_splits: Vec<SoftBodyClusterSplit>,
136 pub moved_joints: Vec<SoftBodyJointMove>,
138 pub raw: RapierSoftBodyTearEvent,
141}
142
143#[derive(Clone, Debug)]
156pub struct SoftBodyTearResult {
157 pub pieces: Vec<Entity>,
161 pub raw: RapierSoftBodyTearEvent,
163}
164
165#[derive(Clone, Debug, PartialEq, Eq)]
167pub struct SoftBodyTearPiece {
168 pub soft_body: Entity,
170 pub particles: Vec<u32>,
173}
174
175#[derive(Copy, Clone, Debug, PartialEq, Eq)]
177pub struct SoftBodyClusterSplit {
178 pub source_cluster: u32,
180 pub soft_body: Entity,
182 pub cluster: u32,
184 pub proxy: Option<Entity>,
190 pub keeps_proxy: bool,
192}
193
194#[derive(Copy, Clone, Debug, PartialEq, Eq)]
200pub struct SoftBodyJointMove {
201 pub joint: Option<Entity>,
203 pub from: Option<Entity>,
205 pub to: Option<Entity>,
207}
208
209pub(crate) struct EventHandlerFanOut<'a> {
212 pub queue: Option<&'a EventQueue<'a>>,
213 pub user: Option<&'a (dyn EventHandler + Send + Sync)>,
214 pub soft_body_tears: &'a RwLock<Vec<RapierSoftBodyTearEvent>>,
216}
217
218impl EventHandlerFanOut<'_> {
219 fn handlers(&self) -> impl Iterator<Item = &dyn EventHandler> {
220 self.queue
221 .map(|q| q as &dyn EventHandler)
222 .into_iter()
223 .chain(self.user.map(|h| h as &dyn EventHandler))
224 }
225}
226
227impl EventHandler for EventHandlerFanOut<'_> {
228 fn handle_collision_event(
229 &self,
230 bodies: &RigidBodySet,
231 colliders: &ColliderSet,
232 event: RapierCollisionEvent,
233 contact_pair: Option<&ContactPair>,
234 ) {
235 for handler in self.handlers() {
236 handler.handle_collision_event(bodies, colliders, event, contact_pair);
237 }
238 }
239
240 fn handle_contact_force_event(
241 &self,
242 dt: Real,
243 bodies: &RigidBodySet,
244 colliders: &ColliderSet,
245 contact_pair: &ContactPair,
246 total_force_magnitude: Real,
247 ) {
248 for handler in self.handlers() {
249 handler.handle_contact_force_event(
250 dt,
251 bodies,
252 colliders,
253 contact_pair,
254 total_force_magnitude,
255 );
256 }
257 }
258
259 fn handle_soft_body_tear_event(
260 &self,
261 soft_bodies: &SoftBodySet,
262 event: &RapierSoftBodyTearEvent,
263 ) {
264 if let Ok(mut tears) = self.soft_body_tears.write() {
265 tears.push(event.clone());
266 }
267 for handler in self.handlers() {
268 handler.handle_soft_body_tear_event(soft_bodies, event);
269 }
270 }
271}
272
273pub(crate) struct EventQueue<'a> {
279 pub deleted_colliders: &'a HashMap<ColliderHandle, Entity>,
282 pub collision_events: RwLock<Vec<CollisionEvent>>,
283 pub contact_force_events: RwLock<Vec<ContactForceEvent>>,
284}
285
286impl EventQueue<'_> {
287 fn collider2entity(&self, colliders: &ColliderSet, handle: ColliderHandle) -> Option<Entity> {
288 let entity = colliders
289 .get(handle)
290 .and_then(|co| Entity::try_from_bits(co.user_data as u64))
291 .or_else(|| self.deleted_colliders.get(&handle).copied());
292 if entity.is_none() {
293 log::debug!("No entity found for collider {handle:?}; its event is dropped.");
296 }
297 entity
298 }
299}
300
301impl EventHandler for EventQueue<'_> {
302 fn handle_collision_event(
303 &self,
304 _bodies: &RigidBodySet,
305 colliders: &ColliderSet,
306 event: RapierCollisionEvent,
307 _: Option<&ContactPair>,
308 ) {
309 let event = match event {
310 RapierCollisionEvent::Started(h1, h2, flags) => {
311 let (Some(e1), Some(e2)) = (
312 self.collider2entity(colliders, h1),
313 self.collider2entity(colliders, h2),
314 ) else {
315 return;
316 };
317 CollisionEvent::Started(e1, e2, flags)
318 }
319 RapierCollisionEvent::Stopped(h1, h2, flags) => {
320 let (Some(e1), Some(e2)) = (
321 self.collider2entity(colliders, h1),
322 self.collider2entity(colliders, h2),
323 ) else {
324 return;
325 };
326 CollisionEvent::Stopped(e1, e2, flags)
327 }
328 };
329
330 if let Ok(mut events) = self.collision_events.write() {
331 events.push(event);
332 }
333 }
334
335 fn handle_contact_force_event(
336 &self,
337 dt: Real,
338 _bodies: &RigidBodySet,
339 colliders: &ColliderSet,
340 contact_pair: &ContactPair,
341 total_force_magnitude: Real,
342 ) {
343 let rapier_event =
344 RapierContactForceEvent::from_contact_pair(dt, contact_pair, total_force_magnitude);
345 let (Some(collider1), Some(collider2)) = (
346 self.collider2entity(colliders, rapier_event.collider1),
347 self.collider2entity(colliders, rapier_event.collider2),
348 ) else {
349 return;
350 };
351 let event = ContactForceEvent {
352 collider1,
353 collider2,
354 total_force: rapier_event.total_force,
355 total_force_magnitude: rapier_event.total_force_magnitude,
356 max_force_direction: rapier_event.max_force_direction,
357 max_force_magnitude: rapier_event.max_force_magnitude,
358 started: rapier_event.started,
359 };
360
361 if let Ok(mut events) = self.contact_force_events.write() {
362 events.push(event);
363 }
364 }
365 fn handle_soft_body_tear_event(
368 &self,
369 _soft_bodies: &SoftBodySet,
370 _event: &RapierSoftBodyTearEvent,
371 ) {
372 }
373}
374
375#[cfg(test)]
376mod test {
377 use bevy::{
378 app::{App, Startup, Update},
379 prelude::{Commands, Component, Entity, Query, With},
380 time::{TimePlugin, TimeUpdateStrategy},
381 transform::{components::Transform, TransformPlugin},
382 MinimalPlugins,
383 };
384
385 use crate::{plugin::*, prelude::*};
386
387 #[cfg(feature = "dim3")]
388 fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
389 Collider::cuboid(hx, hy, hz)
390 }
391 #[cfg(feature = "dim2")]
392 fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
393 Collider::cuboid(hx, hy)
394 }
395
396 #[test]
397 pub fn events_received() {
398 return main();
399
400 use bevy::prelude::*;
401
402 #[derive(Resource, Reflect)]
403 pub struct EventsSaver<E: Message> {
404 pub events: Vec<E>,
405 }
406 impl<E: Message> Default for EventsSaver<E> {
407 fn default() -> Self {
408 Self {
409 events: Default::default(),
410 }
411 }
412 }
413 pub fn save_events<E: Message + Clone>(
414 mut events: MessageReader<E>,
415 mut saver: ResMut<EventsSaver<E>>,
416 ) {
417 for event in events.read() {
418 saver.events.push(event.clone());
419 }
420 }
421 fn run_test(app: &mut App) {
422 app.add_systems(PostUpdate, save_events::<CollisionEvent>)
423 .add_systems(PostUpdate, save_events::<ContactForceEvent>)
424 .init_resource::<EventsSaver<CollisionEvent>>()
425 .init_resource::<EventsSaver<ContactForceEvent>>();
426
427 app.insert_resource(TimeUpdateStrategy::ManualDuration(
429 std::time::Duration::from_secs_f32(1f32 / 60f32),
430 ));
431 app.finish();
432 for _ in 0..120 {
435 app.update();
436 }
437 let saved_collisions = app
438 .world()
439 .get_resource::<EventsSaver<CollisionEvent>>()
440 .unwrap();
441 assert!(!saved_collisions.events.is_empty());
442 let saved_contact_forces = app
443 .world()
444 .get_resource::<EventsSaver<CollisionEvent>>()
445 .unwrap();
446 assert!(!saved_contact_forces.events.is_empty());
447 }
448
449 fn main() {
451 let mut app = App::new();
452 app.add_plugins((
453 TransformPlugin,
454 TimePlugin,
455 RapierPhysicsPlugin::<NoUserData>::default(),
456 ))
457 .add_systems(Startup, setup_physics);
458 run_test(&mut app);
459 }
460
461 pub fn setup_physics(mut commands: Commands) {
462 commands.spawn((Transform::from_xyz(0.0, -1.2, 0.0), cuboid(4.0, 1.0, 1.0)));
466
467 commands.spawn((
468 Transform::from_xyz(0.0, 5.0, 0.0),
469 cuboid(4.0, 1.5, 1.0),
470 Sensor,
471 ));
472
473 commands.spawn((
474 Transform::from_xyz(0.0, 13.0, 0.0),
475 RigidBody::Dynamic,
476 cuboid(0.5, 0.5, 0.5),
477 ActiveEvents::COLLISION_EVENTS,
478 ContactForceEventThreshold(30.0),
479 ));
480 }
481 }
482
483 fn falling_box_app() -> App {
484 let mut app = App::new();
485 app.add_plugins((
486 TransformPlugin,
487 TimePlugin,
488 RapierPhysicsPlugin::<NoUserData>::default(),
489 ))
490 .insert_resource(TimeUpdateStrategy::ManualDuration(
491 std::time::Duration::from_secs_f32(1f32 / 60f32),
492 ))
493 .add_systems(Startup, |mut commands: Commands| {
494 commands.spawn((Transform::from_xyz(0.0, -1.0, 0.0), cuboid(4.0, 1.0, 4.0)));
495 commands.spawn((
496 Transform::from_xyz(0.0, 0.6, 0.0),
497 RigidBody::Dynamic,
498 cuboid(0.5, 0.5, 0.5),
499 ActiveEvents::COLLISION_EVENTS | ActiveEvents::CONTACT_FORCE_EVENTS,
500 ContactForceEventThreshold(0.0),
501 ));
502 });
503 app
504 }
505
506 #[test]
507 pub fn contact_force_event_started_flag() {
508 use bevy::prelude::*;
509
510 #[derive(Resource, Default)]
511 struct Saved(Vec<ContactForceEvent>);
512
513 let mut app = falling_box_app();
514 app.init_resource::<Saved>().add_systems(
515 PostUpdate,
516 |mut events: MessageReader<ContactForceEvent>, mut saved: ResMut<Saved>| {
517 saved.0.extend(events.read().copied());
518 },
519 );
520 app.finish();
521 for _ in 0..120 {
522 app.update();
523 }
524
525 let events = &app.world().resource::<Saved>().0;
526 assert!(events.len() > 2);
527 assert!(events[0].started);
529 assert!(!events.last().unwrap().started);
531 assert!(events.iter().filter(|e| e.started).count() < events.len());
532 }
533
534 #[test]
535 pub fn custom_event_handler_receives_events() {
536 use bevy::prelude::*;
537 use rapier::dynamics::{RigidBodySet, SoftBodySet, SoftBodyTearEvent};
538 use rapier::geometry::{ColliderSet, CollisionEvent as RapierCollisionEvent, ContactPair};
539 use rapier::pipeline::EventHandler;
540 use std::sync::{
541 atomic::{AtomicUsize, Ordering},
542 Arc,
543 };
544
545 #[derive(Default, Clone)]
546 struct Counters {
547 collisions: Arc<AtomicUsize>,
548 collisions_with_pair: Arc<AtomicUsize>,
549 forces: Arc<AtomicUsize>,
550 }
551
552 impl EventHandler for Counters {
553 fn handle_collision_event(
554 &self,
555 _bodies: &RigidBodySet,
556 _colliders: &ColliderSet,
557 _event: RapierCollisionEvent,
558 contact_pair: Option<&ContactPair>,
559 ) {
560 self.collisions.fetch_add(1, Ordering::SeqCst);
561 if contact_pair.is_some() {
562 self.collisions_with_pair.fetch_add(1, Ordering::SeqCst);
563 }
564 }
565
566 fn handle_contact_force_event(
567 &self,
568 _dt: crate::math::Real,
569 _bodies: &RigidBodySet,
570 _colliders: &ColliderSet,
571 _contact_pair: &ContactPair,
572 _total_force_magnitude: crate::math::Real,
573 ) {
574 self.forces.fetch_add(1, Ordering::SeqCst);
575 }
576
577 fn handle_soft_body_tear_event(
578 &self,
579 _soft_bodies: &SoftBodySet,
580 _event: &SoftBodyTearEvent,
581 ) {
582 }
583 }
584
585 #[derive(Resource, Default)]
586 struct BevyCounts {
587 collisions: usize,
588 forces: usize,
589 }
590
591 let counters = Counters::default();
592 let handler = counters.clone();
593 let mut app = falling_box_app();
594 app.init_resource::<BevyCounts>()
595 .add_systems(
596 Startup,
597 move |mut contexts: Query<&mut RapierContextSimulation>| {
598 for mut context in contexts.iter_mut() {
599 context.set_event_handler(handler.clone());
600 }
601 },
602 )
603 .add_systems(
604 PostUpdate,
605 |mut collisions: MessageReader<CollisionEvent>,
606 mut forces: MessageReader<ContactForceEvent>,
607 mut counts: ResMut<BevyCounts>| {
608 counts.collisions += collisions.read().count();
609 counts.forces += forces.read().count();
610 },
611 );
612 app.finish();
613 for _ in 0..60 {
614 app.update();
615 }
616
617 let bevy_counts = app.world().resource::<BevyCounts>();
618 let collisions = counters.collisions.load(Ordering::SeqCst);
619 let forces = counters.forces.load(Ordering::SeqCst);
620 assert!(collisions > 0);
621 assert!(counters.collisions_with_pair.load(Ordering::SeqCst) > 0);
622 assert!(forces > 0);
623 assert_eq!(bevy_counts.collisions, collisions);
625 assert_eq!(bevy_counts.forces, forces);
626 }
627
628 #[test]
629 pub fn spam_remove_rapier_entity_interpolated() {
630 let mut app = App::new();
631 app.add_plugins((
632 MinimalPlugins,
633 TransformPlugin,
634 RapierPhysicsPlugin::<NoUserData>::default(),
635 ))
636 .insert_resource(TimestepMode::Interpolated {
637 dt: 1.0 / 30.0,
638 time_scale: 1.0,
639 substeps: 2,
640 })
641 .add_systems(Startup, setup_physics)
642 .add_systems(Update, remove_collider);
643 app.insert_resource(TimeUpdateStrategy::ManualDuration(
645 std::time::Duration::from_secs_f32(1f32 / 60f32),
646 ));
647
648 app.finish();
649
650 for _ in 0..100 {
651 app.update();
652 }
653 return;
654
655 #[derive(Component)]
656 pub struct ToRemove;
657
658 #[cfg(feature = "dim3")]
659 fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
660 Collider::cuboid(hx, hy, hz)
661 }
662 #[cfg(feature = "dim2")]
663 fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
664 Collider::cuboid(hx, hy)
665 }
666 pub fn setup_physics(mut commands: Commands) {
667 for _i in 0..100 {
668 commands.spawn((
669 Transform::from_xyz(0.0, 0.0, 0.0),
670 RigidBody::Dynamic,
671 cuboid(0.5, 0.5, 0.5),
672 ActiveEvents::all(),
673 ToRemove,
674 ));
675 }
676 let ground_size = 5.1;
680 let ground_height = 0.1;
681 let starting_y = -0.5 - ground_height;
682
683 commands.spawn((
684 Transform::from_xyz(0.0, starting_y, 0.0),
685 cuboid(ground_size, ground_height, ground_size),
686 ));
687 }
688
689 fn remove_collider(mut commands: Commands, query: Query<Entity, With<ToRemove>>) {
690 let Some(entity) = query.iter().next() else {
691 return;
692 };
693 commands.entity(entity).despawn();
694 }
695 }
696}