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
30pub 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
48pub 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
89pub 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 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 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
332fn 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
379fn 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
396pub 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 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 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 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 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#[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#[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(); 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
637pub 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 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 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 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 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 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}