Skip to main content

bevy_rapier3d/plugin/systems/
collider.rs

1use crate::dynamics::{DeformableCollider, DeformableColliderError, ReadMassProperties};
2use crate::geometry::Collider;
3use crate::plugin::context::systemparams::{RapierEntity, RAPIER_CONTEXT_EXPECT_ERROR};
4use crate::plugin::context::RapierContextEntityLink;
5use crate::plugin::{
6    context::{RapierContextColliders, RapierRigidBodySet},
7    RapierConfiguration,
8};
9use crate::prelude::{
10    ActiveCollisionTypes, ActiveEvents, ActiveHooks, ColliderDisabled, ColliderMassProperties,
11    ColliderScale, CollidingEntities, CollisionEvent, CollisionGroups, ContactForceEventThreshold,
12    ContactSkin, Friction, MassModifiedEvent, MassProperties, RapierColliderHandle,
13    RapierRigidBodyHandle, Restitution, Sensor, SolverGroups,
14};
15use crate::utils;
16use bevy::prelude::*;
17
18use super::RapierContextLinkResolver;
19use rapier::dynamics::RigidBodyHandle;
20use rapier::geometry::ColliderBuilder;
21#[cfg(all(feature = "dim3", feature = "async-collider"))]
22use {
23    crate::prelude::{AsyncCollider, AsyncSceneCollider},
24    bevy::world_serialization::WorldInstance,
25};
26
27#[cfg(feature = "dim2")]
28use bevy::math::Vec3Swizzles;
29
30/// Components related to colliders.
31pub type ColliderComponents<'a> = (
32    (Entity, Option<&'a RapierContextEntityLink>),
33    &'a Collider,
34    Option<&'a Sensor>,
35    Option<&'a ColliderMassProperties>,
36    Option<&'a ActiveEvents>,
37    Option<&'a ActiveHooks>,
38    Option<&'a ActiveCollisionTypes>,
39    Option<&'a Friction>,
40    Option<&'a Restitution>,
41    Option<&'a ContactSkin>,
42    Option<&'a CollisionGroups>,
43    Option<&'a SolverGroups>,
44    Option<&'a ContactForceEventThreshold>,
45    Option<&'a ColliderDisabled>,
46);
47
48/// System responsible for applying [`GlobalTransform`] scale and/or [`ColliderScale`] to
49/// colliders.
50pub fn apply_scale(
51    config: Query<&RapierConfiguration>,
52    mut changed_collider_scales: Query<
53        (
54            &mut Collider,
55            &RapierContextEntityLink,
56            &GlobalTransform,
57            Option<&ColliderScale>,
58        ),
59        Or<(
60            Changed<Collider>,
61            Changed<GlobalTransform>,
62            Changed<ColliderScale>,
63        )>,
64    >,
65) {
66    for (mut shape, link, transform, custom_scale) in changed_collider_scales.iter_mut() {
67        let config = config.get(link.0).unwrap();
68        #[cfg(feature = "dim2")]
69        let effective_scale = match custom_scale {
70            Some(ColliderScale::Absolute(scale)) => *scale,
71            Some(ColliderScale::Relative(scale)) => {
72                *scale * transform.compute_transform().scale.xy()
73            }
74            None => transform.compute_transform().scale.xy(),
75        };
76        #[cfg(feature = "dim3")]
77        let effective_scale = match custom_scale {
78            Some(ColliderScale::Absolute(scale)) => *scale,
79            Some(ColliderScale::Relative(scale)) => *scale * transform.compute_transform().scale,
80            None => transform.compute_transform().scale,
81        };
82
83        if shape.scale != crate::geometry::get_snapped_scale(effective_scale) {
84            shape.set_scale(effective_scale, config.scaled_shape_subdivision);
85        }
86    }
87}
88
89/// System responsible for applying changes the user made to a collider-related component.
90pub fn apply_collider_user_changes(
91    mut context: Query<(&RapierRigidBodySet, &mut RapierContextColliders)>,
92    config: Query<&RapierConfiguration>,
93    (changed_collider_transforms, child_of_query, transform_query): (
94        Query<
95            (RapierEntity, &RapierColliderHandle, &GlobalTransform),
96            (Without<RapierRigidBodyHandle>, Changed<GlobalTransform>),
97        >,
98        Query<&ChildOf>,
99        Query<&Transform>,
100    ),
101
102    changed_shapes: Query<(RapierEntity, &RapierColliderHandle, &Collider), Changed<Collider>>,
103    changed_active_events: Query<
104        (RapierEntity, &RapierColliderHandle, &ActiveEvents),
105        Changed<ActiveEvents>,
106    >,
107    changed_active_hooks: Query<
108        (RapierEntity, &RapierColliderHandle, &ActiveHooks),
109        Changed<ActiveHooks>,
110    >,
111    changed_active_collision_types: Query<
112        (RapierEntity, &RapierColliderHandle, &ActiveCollisionTypes),
113        Changed<ActiveCollisionTypes>,
114    >,
115    (changed_friction, changed_restitution, changed_contact_skin): (
116        Query<(RapierEntity, &RapierColliderHandle, &Friction), Changed<Friction>>,
117        Query<(RapierEntity, &RapierColliderHandle, &Restitution), Changed<Restitution>>,
118        Query<(RapierEntity, &RapierColliderHandle, &ContactSkin), Changed<ContactSkin>>,
119    ),
120    changed_collision_groups: Query<
121        (RapierEntity, &RapierColliderHandle, &CollisionGroups),
122        Changed<CollisionGroups>,
123    >,
124    changed_solver_groups: Query<
125        (RapierEntity, &RapierColliderHandle, &SolverGroups),
126        Changed<SolverGroups>,
127    >,
128    changed_sensors: Query<(RapierEntity, &RapierColliderHandle, &Sensor), Changed<Sensor>>,
129    changed_disabled: Query<
130        (RapierEntity, &RapierColliderHandle, &ColliderDisabled),
131        Changed<ColliderDisabled>,
132    >,
133    changed_contact_force_threshold: Query<
134        (
135            RapierEntity,
136            &RapierColliderHandle,
137            &ContactForceEventThreshold,
138        ),
139        Changed<ContactForceEventThreshold>,
140    >,
141    changed_collider_mass_props: Query<
142        (RapierEntity, &RapierColliderHandle, &ColliderMassProperties),
143        Changed<ColliderMassProperties>,
144    >,
145
146    mut mass_modified: MessageWriter<MassModifiedEvent>,
147) {
148    for (rapier_entity, handle, transform) in changed_collider_transforms.iter() {
149        let (rigidbody_set, mut context_colliders) = context
150            .get_mut(rapier_entity.rapier_context_link.0)
151            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
152        if is_deformable(&context_colliders, handle) {
153            // A deformable collider follows the particles of its soft body.
154            continue;
155        }
156        if context_colliders
157            .collider_parent(rigidbody_set, rapier_entity.entity)
158            .is_some()
159        {
160            let (_, collider_position) = collider_offset(
161                rapier_entity.entity,
162                rigidbody_set,
163                &child_of_query,
164                &transform_query,
165            );
166
167            if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
168                let new_pos = utils::transform_to_iso(&collider_position);
169
170                if co
171                    .position_wrt_parent()
172                    .map(|pos| *pos != new_pos)
173                    .unwrap_or(true)
174                {
175                    co.set_position_wrt_parent(new_pos);
176                }
177            }
178        } else if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
179            let new_pos = utils::transform_to_iso(&transform.compute_transform());
180
181            if *co.position() != new_pos {
182                co.set_position(utils::transform_to_iso(&transform.compute_transform()));
183            }
184        }
185    }
186
187    for (rapier_entity, handle, shape) in changed_shapes.iter() {
188        let (rigidbody_set, mut context_colliders) = context
189            .get_mut(rapier_entity.rapier_context_link.0)
190            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
191        let config = config.get(rapier_entity.rapier_context_link.0).unwrap();
192        if is_deformable(&context_colliders, handle) {
193            // The shape of a deformable collider is managed by its soft body.
194            continue;
195        }
196        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
197            let mut scaled_shape = shape.clone();
198            scaled_shape.set_scale(shape.scale, config.scaled_shape_subdivision);
199            co.set_shape(scaled_shape.raw.clone());
200
201            if let Some(body) = co.parent() {
202                if let Some(body_entity) = rigidbody_set.rigid_body_entity(body) {
203                    mass_modified.write(body_entity.into());
204                }
205            }
206        }
207    }
208
209    for (rapier_entity, handle, active_events) in changed_active_events.iter() {
210        let (_, mut context_colliders) = context
211            .get_mut(rapier_entity.rapier_context_link.0)
212            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
213        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
214            co.set_active_events((*active_events).into())
215        }
216    }
217
218    for (rapier_entity, handle, active_hooks) in changed_active_hooks.iter() {
219        let (_, mut context_colliders) = context
220            .get_mut(rapier_entity.rapier_context_link.0)
221            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
222        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
223            co.set_active_hooks((*active_hooks).into())
224        }
225    }
226
227    for (rapier_entity, handle, active_collision_types) in changed_active_collision_types.iter() {
228        let (_, mut context_colliders) = context
229            .get_mut(rapier_entity.rapier_context_link.0)
230            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
231        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
232            co.set_active_collision_types((*active_collision_types).into())
233        }
234    }
235
236    for (rapier_entity, handle, friction) in changed_friction.iter() {
237        let (_, mut context_colliders) = context
238            .get_mut(rapier_entity.rapier_context_link.0)
239            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
240        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
241            co.set_friction(friction.coefficient);
242            co.set_friction_combine_rule(friction.combine_rule.into());
243        }
244    }
245
246    for (rapier_entity, handle, restitution) in changed_restitution.iter() {
247        let (_, mut context_colliders) = context
248            .get_mut(rapier_entity.rapier_context_link.0)
249            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
250        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
251            co.set_restitution(restitution.coefficient);
252            co.set_restitution_combine_rule(restitution.combine_rule.into());
253        }
254    }
255
256    for (rapier_entity, handle, contact_skin) in changed_contact_skin.iter() {
257        let (_, mut context_colliders) = context
258            .get_mut(rapier_entity.rapier_context_link.0)
259            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
260        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
261            co.set_contact_skin(contact_skin.0);
262        }
263    }
264
265    for (rapier_entity, handle, collision_groups) in changed_collision_groups.iter() {
266        let (_, mut context_colliders) = context
267            .get_mut(rapier_entity.rapier_context_link.0)
268            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
269        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
270            co.set_collision_groups((*collision_groups).into());
271        }
272    }
273
274    for (rapier_entity, handle, solver_groups) in changed_solver_groups.iter() {
275        let (_, mut context_colliders) = context
276            .get_mut(rapier_entity.rapier_context_link.0)
277            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
278        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
279            co.set_solver_groups((*solver_groups).into());
280        }
281    }
282
283    for (rapier_entity, handle, _) in changed_sensors.iter() {
284        let (_, mut context_colliders) = context
285            .get_mut(rapier_entity.rapier_context_link.0)
286            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
287        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
288            co.set_sensor(true);
289        }
290    }
291
292    for (rapier_entity, handle, _) in changed_disabled.iter() {
293        let (_, mut context_colliders) = context
294            .get_mut(rapier_entity.rapier_context_link.0)
295            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
296        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
297            co.set_enabled(false);
298        }
299    }
300
301    for (rapier_entity, handle, threshold) in changed_contact_force_threshold.iter() {
302        let (_, mut context_colliders) = context
303            .get_mut(rapier_entity.rapier_context_link.0)
304            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
305        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
306            co.set_contact_force_event_threshold(threshold.0);
307        }
308    }
309
310    for (rapier_entity, handle, mprops) in changed_collider_mass_props.iter() {
311        let (rigidbody_set, mut context_colliders) = context
312            .get_mut(rapier_entity.rapier_context_link.0)
313            .expect(RAPIER_CONTEXT_EXPECT_ERROR);
314        if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
315            match mprops {
316                ColliderMassProperties::Density(density) => co.set_density(*density),
317                ColliderMassProperties::Mass(mass) => co.set_mass(*mass),
318                ColliderMassProperties::MassProperties(mprops) => {
319                    co.set_mass_properties(mprops.into_rapier())
320                }
321            }
322
323            if let Some(body) = co.parent() {
324                if let Some(body_entity) = rigidbody_set.rigid_body_entity(body) {
325                    mass_modified.write(body_entity.into());
326                }
327            }
328        }
329    }
330}
331
332/// Whether the collider of `handle` is bound to a soft body.
333fn is_deformable(
334    context_colliders: &RapierContextColliders,
335    handle: &RapierColliderHandle,
336) -> bool {
337    context_colliders
338        .colliders
339        .get(handle.0)
340        .is_some_and(|co| co.deformable_mesh_ref().is_some())
341}
342
343pub(crate) fn collider_offset(
344    entity: Entity,
345    rigidbody_set: &RapierRigidBodySet,
346    child_of_query: &Query<&ChildOf>,
347    transform_query: &Query<&Transform>,
348) -> (Option<RigidBodyHandle>, Transform) {
349    let mut body_entity = entity;
350    let mut body_handle = rigidbody_set.entity2body.get(&body_entity).copied();
351    let mut child_transform = Transform::default();
352    while body_handle.is_none() {
353        if let Ok(child_of) = child_of_query.get(body_entity) {
354            if let Ok(transform) = transform_query.get(body_entity) {
355                child_transform = *transform * child_transform;
356            }
357            body_entity = child_of.parent();
358        } else {
359            break;
360        }
361
362        body_handle = rigidbody_set.entity2body.get(&body_entity).copied();
363    }
364
365    if body_handle.is_some() {
366        if let Ok(transform) = transform_query.get(body_entity) {
367            let scale_transform = Transform {
368                scale: transform.scale,
369                ..default()
370            };
371
372            child_transform = scale_transform * child_transform;
373        }
374    }
375
376    (body_handle, child_transform)
377}
378
379/// The proxy rigid-body a [`DeformableCollider`] targeting `target` is bound to: the whole-body
380/// cluster's proxy of a soft body entity, or the proxy of a soft-body cluster entity.
381fn deformable_collider_parent(
382    rigidbody_set: &RapierRigidBodySet,
383    target: Entity,
384) -> Option<RigidBodyHandle> {
385    if let Some(sb) = rigidbody_set.soft_body(target) {
386        return Some(sb.root_body());
387    }
388    let handle = *rigidbody_set.entity2body.get(&target)?;
389    rigidbody_set
390        .bodies
391        .get(handle)?
392        .is_soft_frame()
393        .then_some(handle)
394}
395
396/// System responsible for creating new Rapier colliders from the related `bevy_rapier` components.
397pub fn init_colliders(
398    mut commands: Commands,
399    config: Query<&RapierConfiguration>,
400    mut context_access: Query<(&mut RapierRigidBodySet, &mut RapierContextColliders)>,
401    context_links: RapierContextLinkResolver,
402    colliders: Query<
403        (ColliderComponents, Option<&GlobalTransform>),
404        (
405            Without<RapierColliderHandle>,
406            Without<DeformableColliderError>,
407        ),
408    >,
409    mut rigid_body_mprops: Query<&mut ReadMassProperties>,
410    child_of_query: Query<&ChildOf>,
411    transform_query: Query<&Transform>,
412    deformables: Query<&DeformableCollider>,
413) {
414    for (
415        (
416            (entity, entity_context_link),
417            shape,
418            sensor,
419            mprops,
420            active_events,
421            active_hooks,
422            active_collision_types,
423            friction,
424            restitution,
425            contact_skin,
426            collision_groups,
427            solver_groups,
428            contact_force_event_threshold,
429            disabled,
430        ),
431        global_transform,
432    ) in colliders.iter()
433    {
434        // Use the RapierContextEntityLink, or insert the context of an ancestor or the default one.
435        let context_entity = context_links.resolve(entity, entity_context_link, &mut commands);
436        let Some(context_entity) = context_entity else {
437            continue;
438        };
439
440        let config = config.get(context_entity).unwrap_or_else(|_| {
441            panic!("Failed to retrieve `RapierConfiguration` on entity {context_entity}.")
442        });
443
444        let Some(mut rigidbody_set_collider_set) = context_access.get_mut(context_entity).ok()
445        else {
446            log::error!("Could not find entity {context_entity} with rapier context while initializing {entity}");
447            continue;
448        };
449        let context_colliders = &mut *rigidbody_set_collider_set.1;
450        let rigidbody_set = &mut *rigidbody_set_collider_set.0;
451        let mut scaled_shape = shape.clone();
452        scaled_shape.set_scale(shape.scale, config.scaled_shape_subdivision);
453        let mut builder = ColliderBuilder::new(scaled_shape.raw.clone());
454
455        builder = builder.sensor(sensor.is_some());
456        builder = builder.enabled(disabled.is_none());
457
458        if let Some(mprops) = mprops {
459            builder = match mprops {
460                ColliderMassProperties::Density(density) => builder.density(*density),
461                ColliderMassProperties::Mass(mass) => builder.mass(*mass),
462                ColliderMassProperties::MassProperties(mprops) => {
463                    builder.mass_properties(mprops.into_rapier())
464                }
465            };
466        }
467
468        if let Some(active_events) = active_events {
469            builder = builder.active_events((*active_events).into());
470        }
471
472        if let Some(active_hooks) = active_hooks {
473            builder = builder.active_hooks((*active_hooks).into());
474        }
475
476        if let Some(active_collision_types) = active_collision_types {
477            builder = builder.active_collision_types((*active_collision_types).into());
478        }
479
480        if let Some(friction) = friction {
481            builder = builder
482                .friction(friction.coefficient)
483                .friction_combine_rule(friction.combine_rule.into());
484        }
485
486        if let Some(restitution) = restitution {
487            builder = builder
488                .restitution(restitution.coefficient)
489                .restitution_combine_rule(restitution.combine_rule.into());
490        }
491
492        if let Some(contact_skin) = contact_skin {
493            builder = builder.contact_skin(contact_skin.0);
494        }
495
496        if let Some(collision_groups) = collision_groups {
497            builder = builder.collision_groups((*collision_groups).into());
498        }
499
500        if let Some(solver_groups) = solver_groups {
501            builder = builder.solver_groups((*solver_groups).into());
502        }
503
504        if let Some(threshold) = contact_force_event_threshold {
505            builder = builder.contact_force_event_threshold(threshold.0);
506        }
507        builder = builder.user_data(entity.to_bits() as u128);
508
509        if let Ok(deformable) = deformables.get(entity) {
510            // The soft body (or cluster) may not be created yet: try again next frame.
511            let Some(proxy) = deformable_collider_parent(rigidbody_set, deformable.target) else {
512                continue;
513            };
514            let proxy_pose = rigidbody_set
515                .bodies
516                .get(proxy)
517                .map(|rb| *rb.position())
518                .unwrap_or_default();
519            let global_transform = global_transform.cloned().unwrap_or_default();
520            let pose = utils::transform_to_iso(&global_transform.compute_transform());
521            // The vertices are placed by the entity's global transform.
522            builder = builder.position(proxy_pose.inverse() * pose);
523            match context_colliders.colliders.insert_deformable(
524                builder,
525                deformable.binding.clone(),
526                proxy,
527                &mut rigidbody_set.bodies,
528                &mut rigidbody_set.soft_bodies,
529            ) {
530                Ok(handle) => {
531                    commands.entity(entity).insert(RapierColliderHandle(handle));
532                    context_colliders.entity2collider.insert(entity, handle);
533                }
534                Err(err) => {
535                    log::error!("Failed to create the deformable collider of {entity}: {err}");
536                    commands.entity(entity).insert(DeformableColliderError(err));
537                }
538            }
539            continue;
540        }
541
542        let body_entity = entity;
543        let (body_handle, child_transform) =
544            collider_offset(entity, rigidbody_set, &child_of_query, &transform_query);
545
546        let handle = if let Some(body_handle) = body_handle {
547            builder = builder.position(utils::transform_to_iso(&child_transform));
548            let handle = context_colliders.colliders.insert_with_parent(
549                builder,
550                body_handle,
551                &mut rigidbody_set.bodies,
552            );
553            if let Ok(mut mprops) = rigid_body_mprops.get_mut(body_entity) {
554                // Inserting the collider changed the rigid-body’s mass properties.
555                // Read them back from the engine.
556                if let Some(parent_body) = rigidbody_set.bodies.get(body_handle) {
557                    mprops.set(MassProperties::from_rapier(
558                        parent_body.mass_properties().local_mprops,
559                    ));
560                }
561            }
562            handle
563        } else {
564            let global_transform = global_transform.cloned().unwrap_or_default();
565            builder = builder.position(utils::transform_to_iso(
566                &global_transform.compute_transform(),
567            ));
568            context_colliders.colliders.insert(builder)
569        };
570
571        commands.entity(entity).insert(RapierColliderHandle(handle));
572        context_colliders.entity2collider.insert(entity, handle);
573    }
574}
575
576/// System responsible for creating `Collider` components from `AsyncCollider` components if the
577/// corresponding mesh has become available.
578#[cfg(all(feature = "dim3", feature = "async-collider"))]
579pub fn init_async_colliders(
580    mut commands: Commands,
581    meshes: Res<Assets<Mesh>>,
582    async_colliders: Query<(Entity, &Mesh3d, &AsyncCollider)>,
583) {
584    for (entity, mesh_handle, async_collider) in async_colliders.iter() {
585        if let Some(mesh) = meshes.get(mesh_handle) {
586            match Collider::from_bevy_mesh(mesh, &async_collider.0) {
587                Some(collider) => {
588                    commands
589                        .entity(entity)
590                        .insert(collider)
591                        .remove::<AsyncCollider>();
592                }
593                None => log::error!("Unable to generate collider from mesh {mesh:?}"),
594            }
595        }
596    }
597}
598
599/// System responsible for creating `Collider` components from `AsyncSceneCollider` components if the
600/// corresponding scene has become available.
601#[cfg(all(feature = "dim3", feature = "async-collider"))]
602pub fn init_async_scene_colliders(
603    mut commands: Commands,
604    meshes: Res<Assets<Mesh>>,
605    scene_spawner: If<Res<WorldInstanceSpawner>>,
606    async_colliders: Query<(Entity, &WorldInstance, &AsyncSceneCollider)>,
607    children: Query<&Children>,
608    mesh_handles: Query<(&Name, &Mesh3d)>,
609) {
610    for (scene_entity, scene_instance, async_collider) in async_colliders.iter() {
611        if scene_spawner.instance_is_ready(**scene_instance) {
612            for child_entity in children.iter_descendants(scene_entity) {
613                if let Ok((name, handle)) = mesh_handles.get(child_entity) {
614                    let shape = async_collider
615                        .named_shapes
616                        .get(name.as_str())
617                        .unwrap_or(&async_collider.shape);
618                    if let Some(shape) = shape {
619                        let mesh = meshes.get(handle).unwrap(); // NOTE: Mesh is already loaded
620                        match Collider::from_bevy_mesh(mesh, shape) {
621                            Some(collider) => {
622                                commands.entity(child_entity).insert(collider);
623                            }
624                            None => log::error!(
625                                "Unable to generate collider from mesh {mesh:?} with name {name}"
626                            ),
627                        }
628                    }
629                }
630            }
631
632            commands.entity(scene_entity).remove::<AsyncSceneCollider>();
633        }
634    }
635}
636
637/// Adds entity to [`CollidingEntities`] on starting collision and removes from it when the
638/// collision ends.
639pub fn update_colliding_entities(
640    mut collision_events: MessageReader<CollisionEvent>,
641    mut colliding_entities: Query<&mut CollidingEntities>,
642) {
643    for event in collision_events.read() {
644        match event.to_owned() {
645            CollisionEvent::Started(entity1, entity2, _) => {
646                if let Ok(mut entities) = colliding_entities.get_mut(entity1) {
647                    entities.0.insert(entity2);
648                }
649                if let Ok(mut entities) = colliding_entities.get_mut(entity2) {
650                    entities.0.insert(entity1);
651                }
652            }
653            CollisionEvent::Stopped(entity1, entity2, _) => {
654                if let Ok(mut entities) = colliding_entities.get_mut(entity1) {
655                    entities.0.remove(&entity2);
656                }
657                if let Ok(mut entities) = colliding_entities.get_mut(entity2) {
658                    entities.0.remove(&entity1);
659                }
660            }
661        }
662    }
663}
664
665#[cfg(test)]
666#[allow(missing_docs)]
667pub mod test {
668    #[test]
669    #[cfg(all(feature = "dim3", feature = "async-collider"))]
670    fn async_collider_initializes() {
671        use super::*;
672        use bevy::{mesh::MeshPlugin, world_serialization::WorldSerializationPlugin};
673
674        let mut app = App::new();
675        app.add_plugins((AssetPlugin::default(), MeshPlugin, WorldSerializationPlugin));
676        app.add_systems(Update, init_async_colliders);
677
678        app.finish();
679
680        let mut meshes = app.world_mut().resource_mut::<Assets<Mesh>>();
681        let cube = meshes.add(Cuboid::default());
682
683        let entity = app
684            .world_mut()
685            .spawn((Mesh3d(cube), AsyncCollider::default()))
686            .id();
687
688        app.update();
689
690        let entity = app.world().entity(entity);
691        assert!(
692            entity.get::<Collider>().is_some(),
693            "Collider component should be added"
694        );
695        assert!(
696            entity.get::<AsyncCollider>().is_none(),
697            "AsyncCollider component should be removed after Collider component creation"
698        );
699    }
700
701    #[test]
702    #[cfg(all(feature = "dim3", feature = "async-collider"))]
703    fn async_collider_voxels_and_converted_initialize() {
704        use super::*;
705        use crate::geometry::{ComputedColliderShape, FillMode, MeshConverter};
706        use bevy::{mesh::MeshPlugin, world_serialization::WorldSerializationPlugin};
707
708        let mut app = App::new();
709        app.add_plugins((AssetPlugin::default(), MeshPlugin, WorldSerializationPlugin));
710        app.add_systems(Update, init_async_colliders);
711
712        app.finish();
713
714        let mut meshes = app.world_mut().resource_mut::<Assets<Mesh>>();
715        let cube = meshes.add(Cuboid::default());
716
717        let voxels = app
718            .world_mut()
719            .spawn((
720                Mesh3d(cube.clone()),
721                AsyncCollider(ComputedColliderShape::Voxels {
722                    voxel_size: 0.25,
723                    fill_mode: FillMode::default(),
724                }),
725            ))
726            .id();
727        let aabb = app
728            .world_mut()
729            .spawn((
730                Mesh3d(cube),
731                AsyncCollider(ComputedColliderShape::Converted(MeshConverter::Aabb)),
732            ))
733            .id();
734
735        app.update();
736
737        let voxels = app.world().entity(voxels).get::<Collider>().unwrap();
738        assert!(voxels.as_voxels().is_some());
739        let aabb = app.world().entity(aabb).get::<Collider>().unwrap();
740        assert!(aabb.as_cuboid().is_some());
741    }
742
743    #[test]
744    #[cfg(all(feature = "dim3", feature = "async-collider"))]
745    fn async_scene_collider_initializes() {
746        use super::*;
747        use bevy::{mesh::MeshPlugin, world_serialization::WorldSerializationPlugin};
748
749        let mut app = App::new();
750        app.add_plugins((AssetPlugin::default(), MeshPlugin, WorldSerializationPlugin));
751        app.add_systems(PostUpdate, init_async_scene_colliders);
752
753        let mut meshes = app.world_mut().resource_mut::<Assets<Mesh>>();
754        let cube_handle = meshes.add(Cuboid::default());
755        let capsule_handle = meshes.add(Capsule3d::default());
756        let cube = app
757            .world_mut()
758            .spawn((Name::new("Cube"), Mesh3d(cube_handle)))
759            .id();
760        let capsule = app
761            .world_mut()
762            .spawn((Name::new("Capsule"), Mesh3d(capsule_handle)))
763            .id();
764
765        let mut scenes = app.world_mut().resource_mut::<Assets<WorldAsset>>();
766        let scene = scenes.add(WorldAsset::new(World::new()));
767
768        let mut named_shapes = bevy::platform::collections::HashMap::default();
769        named_shapes.insert("Capsule".to_string(), None);
770        let parent = app
771            .world_mut()
772            .spawn((
773                WorldAssetRoot(scene),
774                AsyncSceneCollider {
775                    named_shapes,
776                    ..Default::default()
777                },
778            ))
779            .add_children(&[cube, capsule])
780            .id();
781
782        app.update();
783
784        assert!(
785            app.world().entity(cube).get::<Collider>().is_some(),
786            "Collider component should be added for cube"
787        );
788        assert!(
789            app.world().entity(capsule).get::<Collider>().is_none(),
790            "Collider component shouldn't be added for capsule"
791        );
792        assert!(
793            app.world().entity(parent).get::<AsyncCollider>().is_none(),
794            "AsyncSceneCollider component should be removed after Collider components creation"
795        );
796    }
797}
798
799#[cfg(test)]
800mod tests {
801    use super::*;
802    use crate::dynamics::{CoefficientCombineRule, GravityScale, RigidBody};
803    use crate::geometry::{Group, InteractionTestMode, ReadColliderMassProperties};
804    use crate::math::Vect;
805    use crate::plugin::context::systemparams::ReadRapierContext;
806    use crate::plugin::context::{RapierContextSimulation, RapierRigidBodySet};
807    use crate::plugin::{NoUserData, RapierPhysicsPlugin};
808    use crate::prelude::QueryFilter;
809    use bevy::ecs::system::RunSystemOnce;
810    use bevy::time::{TimePlugin, TimeUpdateStrategy};
811    use rapier::geometry::ColliderBuilder;
812
813    fn test_app() -> App {
814        let mut app = App::new();
815        app.add_plugins((
816            TransformPlugin,
817            TimePlugin,
818            RapierPhysicsPlugin::<NoUserData>::default(),
819        ))
820        .insert_resource(TimeUpdateStrategy::ManualDuration(
821            std::time::Duration::from_secs_f32(1.0 / 60.0),
822        ));
823        app.finish();
824        app
825    }
826
827    fn with_collider<R>(
828        app: &mut App,
829        entity: Entity,
830        f: impl FnOnce(&RapierContextColliders, &rapier::geometry::Collider) -> R,
831    ) -> R {
832        let world = app.world_mut();
833        let set = world
834            .query::<&RapierContextColliders>()
835            .single(world)
836            .unwrap();
837        let co = set.colliders.get(set.entity2collider()[&entity]).unwrap();
838        f(set, co)
839    }
840
841    #[test]
842    fn collider_components_are_reset_on_removal() {
843        let mut app = test_app();
844        app.update();
845
846        let entity = app
847            .world_mut()
848            .spawn((
849                Transform::default(),
850                Collider::ball(0.5),
851                Friction {
852                    coefficient: 1.0,
853                    combine_rule: CoefficientCombineRule::GeometricMean,
854                },
855                Restitution {
856                    coefficient: 0.8,
857                    combine_rule: CoefficientCombineRule::ClampedSum,
858                },
859                ContactSkin(0.1),
860                CollisionGroups::new(Group::GROUP_2, Group::GROUP_3)
861                    .with_test_mode(InteractionTestMode::Or),
862                SolverGroups::new(Group::GROUP_4, Group::GROUP_5),
863                (
864                    ActiveEvents::all(),
865                    ActiveHooks::all(),
866                    ActiveCollisionTypes::all(),
867                    ContactForceEventThreshold(10.0),
868                    ColliderMassProperties::Density(3.0),
869                ),
870            ))
871            .id();
872        app.update();
873
874        with_collider(&mut app, entity, |_, co| {
875            assert_eq!(co.friction(), 1.0);
876            assert_eq!(
877                co.friction_combine_rule(),
878                rapier::dynamics::CoefficientCombineRule::GeometricMean
879            );
880            assert_eq!(co.density(), 3.0);
881            assert_eq!(
882                co.collision_groups().test_mode,
883                rapier::geometry::InteractionTestMode::Or
884            );
885        });
886
887        app.world_mut().entity_mut(entity).remove::<(
888            Friction,
889            Restitution,
890            ContactSkin,
891            CollisionGroups,
892            SolverGroups,
893            ActiveEvents,
894            ActiveHooks,
895            ActiveCollisionTypes,
896            ContactForceEventThreshold,
897            ColliderMassProperties,
898        )>();
899        app.update();
900
901        let defaults = ColliderBuilder::default();
902        with_collider(&mut app, entity, |_, co| {
903            assert_eq!(co.friction(), defaults.friction);
904            assert_eq!(co.friction_combine_rule(), defaults.friction_combine_rule);
905            assert_eq!(co.restitution(), defaults.restitution);
906            assert_eq!(
907                co.restitution_combine_rule(),
908                defaults.restitution_combine_rule
909            );
910            assert_eq!(co.contact_skin(), defaults.contact_skin);
911            assert_eq!(co.collision_groups(), defaults.collision_groups);
912            assert_eq!(co.solver_groups(), defaults.solver_groups);
913            assert_eq!(co.active_events(), defaults.active_events);
914            assert_eq!(co.active_hooks(), defaults.active_hooks);
915            assert_eq!(co.active_collision_types(), defaults.active_collision_types);
916            assert_eq!(
917                co.contact_force_event_threshold(),
918                defaults.contact_force_event_threshold
919            );
920            assert_eq!(co.density(), ColliderBuilder::default_density());
921        });
922    }
923
924    #[test]
925    fn collider_mass_properties_are_read_back() {
926        let mut app = test_app();
927        app.update();
928
929        let entity = app
930            .world_mut()
931            .spawn((
932                Transform::default(),
933                RigidBody::Dynamic,
934                GravityScale(0.0),
935                Collider::ball(0.5),
936                ColliderMassProperties::Density(2.0),
937                ReadColliderMassProperties::default(),
938            ))
939            .id();
940        app.update();
941
942        #[cfg(feature = "dim2")]
943        let volume = |r: f32| std::f32::consts::PI * r * r;
944        #[cfg(feature = "dim3")]
945        let volume = |r: f32| 4.0 / 3.0 * std::f32::consts::PI * r * r * r;
946
947        let props = *app
948            .world()
949            .get::<ReadColliderMassProperties>(entity)
950            .unwrap();
951        approx::assert_relative_eq!(props.volume, volume(0.5), epsilon = 1.0e-5);
952        assert_eq!(props.density, 2.0);
953        approx::assert_relative_eq!(props.mass, 2.0 * volume(0.5), epsilon = 1.0e-5);
954        assert_eq!(props.local_mass_properties.mass, props.mass);
955
956        with_collider(&mut app, entity, |set, _| {
957            assert_eq!(set.volume(entity), Some(props.volume));
958            assert_eq!(set.mass(entity), Some(props.mass));
959            assert_eq!(set.density(entity), Some(2.0));
960            assert_eq!(set.volume(Entity::PLACEHOLDER), None);
961
962            let aabb = set.compute_aabb(entity).unwrap();
963            approx::assert_relative_eq!(Vect::from(aabb.min), Vect::splat(-0.5));
964            approx::assert_relative_eq!(Vect::from(aabb.max), Vect::splat(0.5));
965
966            let swept = set
967                .compute_swept_aabb(entity, Transform::from_xyz(2.0, 0.0, 0.0))
968                .unwrap();
969            approx::assert_relative_eq!(Vect::from(swept.min), Vect::splat(-0.5));
970            approx::assert_relative_eq!(swept.max.x, 2.5);
971        });
972
973        // Shape changes are read back.
974        app.world_mut()
975            .entity_mut(entity)
976            .insert(Collider::ball(1.0));
977        app.update();
978        let props = *app
979            .world()
980            .get::<ReadColliderMassProperties>(entity)
981            .unwrap();
982        approx::assert_relative_eq!(props.volume, volume(1.0), epsilon = 1.0e-5);
983        approx::assert_relative_eq!(props.mass, 2.0 * volume(1.0), epsilon = 1.0e-5);
984
985        // Mass-properties changes are read back.
986        app.world_mut()
987            .entity_mut(entity)
988            .insert(ColliderMassProperties::Mass(5.0));
989        app.update();
990        let props = *app
991            .world()
992            .get::<ReadColliderMassProperties>(entity)
993            .unwrap();
994        assert_eq!(props.mass, 5.0);
995        approx::assert_relative_eq!(props.density, 5.0 / volume(1.0), epsilon = 1.0e-5);
996
997        // Removing the custom mass-properties falls back to the default density.
998        app.world_mut()
999            .entity_mut(entity)
1000            .remove::<ColliderMassProperties>();
1001        app.update();
1002        let props = *app
1003            .world()
1004            .get::<ReadColliderMassProperties>(entity)
1005            .unwrap();
1006        assert_eq!(props.density, 1.0);
1007        approx::assert_relative_eq!(props.mass, volume(1.0), epsilon = 1.0e-5);
1008
1009        // The rigid-body mass accounts for the reset collider mass.
1010        let world = app.world_mut();
1011        let set = world.query::<&RapierRigidBodySet>().single(world).unwrap();
1012        approx::assert_relative_eq!(set.mass(entity).unwrap(), volume(1.0), epsilon = 1.0e-5);
1013    }
1014
1015    #[test]
1016    fn collision_groups_or_mode_enables_contacts() {
1017        let mut app = test_app();
1018        app.update();
1019
1020        // The membership of `b` matches the filter of `a`, but not the other way around.
1021        let groups_a = CollisionGroups::new(Group::GROUP_1, Group::GROUP_2);
1022        let groups_b = CollisionGroups::new(Group::GROUP_2, Group::GROUP_3);
1023
1024        let mut spawn_pair = |x: f32, mode: InteractionTestMode| {
1025            let a = app
1026                .world_mut()
1027                .spawn((
1028                    Transform::from_xyz(x, 0.0, 0.0),
1029                    RigidBody::Dynamic,
1030                    GravityScale(0.0),
1031                    Collider::ball(0.5),
1032                    groups_a.with_test_mode(mode),
1033                ))
1034                .id();
1035            let b = app
1036                .world_mut()
1037                .spawn((
1038                    Transform::from_xyz(x + 0.5, 0.0, 0.0),
1039                    Collider::ball(0.5),
1040                    groups_b.with_test_mode(mode),
1041                ))
1042                .id();
1043            (a, b)
1044        };
1045        let and_pair = spawn_pair(0.0, InteractionTestMode::And);
1046        let or_pair = spawn_pair(10.0, InteractionTestMode::Or);
1047
1048        app.update();
1049        app.update();
1050
1051        let world = app.world_mut();
1052        let (simulation, colliders, bodies) = world
1053            .query::<(
1054                &RapierContextSimulation,
1055                &RapierContextColliders,
1056                &RapierRigidBodySet,
1057            )>()
1058            .single(world)
1059            .unwrap();
1060        let in_contact = |(a, b): (Entity, Entity)| {
1061            simulation
1062                .contact_pair(colliders, bodies, a, b)
1063                .is_some_and(|pair| pair.has_any_active_contact())
1064        };
1065        assert!(!in_contact(and_pair));
1066        assert!(in_contact(or_pair));
1067    }
1068
1069    #[test]
1070    fn query_filter_groups_honor_test_mode() {
1071        let mut app = test_app();
1072        app.update();
1073
1074        let groups = CollisionGroups::new(Group::GROUP_1, Group::GROUP_2)
1075            .with_test_mode(InteractionTestMode::Or);
1076        let entity = app
1077            .world_mut()
1078            .spawn((Transform::default(), Collider::ball(0.5), groups))
1079            .id();
1080        app.update();
1081        app.update();
1082
1083        let query_groups = CollisionGroups::new(Group::GROUP_2, Group::GROUP_3);
1084        let hit = |mode: InteractionTestMode| {
1085            move |context: ReadRapierContext| {
1086                let context = context.single().unwrap();
1087                let filter = QueryFilter::new().groups(query_groups.with_test_mode(mode));
1088                let mut hits = vec![];
1089                context.intersect_point(Vect::ZERO, filter, |e, _| {
1090                    hits.push(e);
1091                    true
1092                });
1093                hits
1094            }
1095        };
1096
1097        let and_hits = app
1098            .world_mut()
1099            .run_system_once(hit(InteractionTestMode::And))
1100            .unwrap();
1101        assert!(and_hits.is_empty());
1102        let or_hits = app
1103            .world_mut()
1104            .run_system_once(hit(InteractionTestMode::Or))
1105            .unwrap();
1106        assert_eq!(or_hits, vec![entity]);
1107    }
1108}