1use crate::dynamics::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::{DefaultRapierContext, 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::*;
17use rapier::dynamics::RigidBodyHandle;
18use rapier::geometry::ColliderBuilder;
19#[cfg(all(feature = "dim3", feature = "async-collider"))]
20use {
21 crate::prelude::{AsyncCollider, AsyncSceneCollider},
22 bevy::scene::SceneInstance,
23};
24
25#[cfg(feature = "dim2")]
26use bevy::math::Vec3Swizzles;
27
28pub type ColliderComponents<'a> = (
30 (Entity, Option<&'a RapierContextEntityLink>),
31 &'a Collider,
32 Option<&'a Sensor>,
33 Option<&'a ColliderMassProperties>,
34 Option<&'a ActiveEvents>,
35 Option<&'a ActiveHooks>,
36 Option<&'a ActiveCollisionTypes>,
37 Option<&'a Friction>,
38 Option<&'a Restitution>,
39 Option<&'a ContactSkin>,
40 Option<&'a CollisionGroups>,
41 Option<&'a SolverGroups>,
42 Option<&'a ContactForceEventThreshold>,
43 Option<&'a ColliderDisabled>,
44);
45
46pub fn apply_scale(
49 config: Query<&RapierConfiguration>,
50 mut changed_collider_scales: Query<
51 (
52 &mut Collider,
53 &RapierContextEntityLink,
54 &GlobalTransform,
55 Option<&ColliderScale>,
56 ),
57 Or<(
58 Changed<Collider>,
59 Changed<GlobalTransform>,
60 Changed<ColliderScale>,
61 )>,
62 >,
63) {
64 for (mut shape, link, transform, custom_scale) in changed_collider_scales.iter_mut() {
65 let config = config.get(link.0).unwrap();
66 #[cfg(feature = "dim2")]
67 let effective_scale = match custom_scale {
68 Some(ColliderScale::Absolute(scale)) => *scale,
69 Some(ColliderScale::Relative(scale)) => {
70 *scale * transform.compute_transform().scale.xy()
71 }
72 None => transform.compute_transform().scale.xy(),
73 };
74 #[cfg(feature = "dim3")]
75 let effective_scale = match custom_scale {
76 Some(ColliderScale::Absolute(scale)) => *scale,
77 Some(ColliderScale::Relative(scale)) => *scale * transform.compute_transform().scale,
78 None => transform.compute_transform().scale,
79 };
80
81 if shape.scale != crate::geometry::get_snapped_scale(effective_scale) {
82 shape.set_scale(effective_scale, config.scaled_shape_subdivision);
83 }
84 }
85}
86
87pub fn apply_collider_user_changes(
89 mut context: Query<(&RapierRigidBodySet, &mut RapierContextColliders)>,
90 config: Query<&RapierConfiguration>,
91 (changed_collider_transforms, child_of_query, transform_query): (
92 Query<
93 (RapierEntity, &RapierColliderHandle, &GlobalTransform),
94 (Without<RapierRigidBodyHandle>, Changed<GlobalTransform>),
95 >,
96 Query<&ChildOf>,
97 Query<&Transform>,
98 ),
99
100 changed_shapes: Query<(RapierEntity, &RapierColliderHandle, &Collider), Changed<Collider>>,
101 changed_active_events: Query<
102 (RapierEntity, &RapierColliderHandle, &ActiveEvents),
103 Changed<ActiveEvents>,
104 >,
105 changed_active_hooks: Query<
106 (RapierEntity, &RapierColliderHandle, &ActiveHooks),
107 Changed<ActiveHooks>,
108 >,
109 changed_active_collision_types: Query<
110 (RapierEntity, &RapierColliderHandle, &ActiveCollisionTypes),
111 Changed<ActiveCollisionTypes>,
112 >,
113 (changed_friction, changed_restitution, changed_contact_skin): (
114 Query<(RapierEntity, &RapierColliderHandle, &Friction), Changed<Friction>>,
115 Query<(RapierEntity, &RapierColliderHandle, &Restitution), Changed<Restitution>>,
116 Query<(RapierEntity, &RapierColliderHandle, &ContactSkin), Changed<ContactSkin>>,
117 ),
118 changed_collision_groups: Query<
119 (RapierEntity, &RapierColliderHandle, &CollisionGroups),
120 Changed<CollisionGroups>,
121 >,
122 changed_solver_groups: Query<
123 (RapierEntity, &RapierColliderHandle, &SolverGroups),
124 Changed<SolverGroups>,
125 >,
126 changed_sensors: Query<(RapierEntity, &RapierColliderHandle, &Sensor), Changed<Sensor>>,
127 changed_disabled: Query<
128 (RapierEntity, &RapierColliderHandle, &ColliderDisabled),
129 Changed<ColliderDisabled>,
130 >,
131 changed_contact_force_threshold: Query<
132 (
133 RapierEntity,
134 &RapierColliderHandle,
135 &ContactForceEventThreshold,
136 ),
137 Changed<ContactForceEventThreshold>,
138 >,
139 changed_collider_mass_props: Query<
140 (RapierEntity, &RapierColliderHandle, &ColliderMassProperties),
141 Changed<ColliderMassProperties>,
142 >,
143
144 mut mass_modified: MessageWriter<MassModifiedEvent>,
145) {
146 for (rapier_entity, handle, transform) in changed_collider_transforms.iter() {
147 let (rigidbody_set, mut context_colliders) = context
148 .get_mut(rapier_entity.rapier_context_link.0)
149 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
150 if context_colliders
151 .collider_parent(rigidbody_set, rapier_entity.entity)
152 .is_some()
153 {
154 let (_, collider_position) = collider_offset(
155 rapier_entity.entity,
156 rigidbody_set,
157 &child_of_query,
158 &transform_query,
159 );
160
161 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
162 let new_pos = utils::transform_to_iso(&collider_position);
163
164 if co
165 .position_wrt_parent()
166 .map(|pos| *pos != new_pos)
167 .unwrap_or(true)
168 {
169 co.set_position_wrt_parent(new_pos);
170 }
171 }
172 } else if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
173 let new_pos = utils::transform_to_iso(&transform.compute_transform());
174
175 if *co.position() != new_pos {
176 co.set_position(utils::transform_to_iso(&transform.compute_transform()));
177 }
178 }
179 }
180
181 for (rapier_entity, handle, shape) in changed_shapes.iter() {
182 let (rigidbody_set, mut context_colliders) = context
183 .get_mut(rapier_entity.rapier_context_link.0)
184 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
185 let config = config.get(rapier_entity.rapier_context_link.0).unwrap();
186 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
187 let mut scaled_shape = shape.clone();
188 scaled_shape.set_scale(shape.scale, config.scaled_shape_subdivision);
189 co.set_shape(scaled_shape.raw.clone());
190
191 if let Some(body) = co.parent() {
192 if let Some(body_entity) = rigidbody_set.rigid_body_entity(body) {
193 mass_modified.write(body_entity.into());
194 }
195 }
196 }
197 }
198
199 for (rapier_entity, handle, active_events) in changed_active_events.iter() {
200 let (_, mut context_colliders) = context
201 .get_mut(rapier_entity.rapier_context_link.0)
202 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
203 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
204 co.set_active_events((*active_events).into())
205 }
206 }
207
208 for (rapier_entity, handle, active_hooks) in changed_active_hooks.iter() {
209 let (_, mut context_colliders) = context
210 .get_mut(rapier_entity.rapier_context_link.0)
211 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
212 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
213 co.set_active_hooks((*active_hooks).into())
214 }
215 }
216
217 for (rapier_entity, handle, active_collision_types) in changed_active_collision_types.iter() {
218 let (_, mut context_colliders) = context
219 .get_mut(rapier_entity.rapier_context_link.0)
220 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
221 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
222 co.set_active_collision_types((*active_collision_types).into())
223 }
224 }
225
226 for (rapier_entity, handle, friction) in changed_friction.iter() {
227 let (_, mut context_colliders) = context
228 .get_mut(rapier_entity.rapier_context_link.0)
229 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
230 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
231 co.set_friction(friction.coefficient);
232 co.set_friction_combine_rule(friction.combine_rule.into());
233 }
234 }
235
236 for (rapier_entity, handle, restitution) in changed_restitution.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_restitution(restitution.coefficient);
242 co.set_restitution_combine_rule(restitution.combine_rule.into());
243 }
244 }
245
246 for (rapier_entity, handle, contact_skin) in changed_contact_skin.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_contact_skin(contact_skin.0);
252 }
253 }
254
255 for (rapier_entity, handle, collision_groups) in changed_collision_groups.iter() {
256 let (_, mut context_colliders) = context
257 .get_mut(rapier_entity.rapier_context_link.0)
258 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
259 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
260 co.set_collision_groups((*collision_groups).into());
261 }
262 }
263
264 for (rapier_entity, handle, solver_groups) in changed_solver_groups.iter() {
265 let (_, mut context_colliders) = context
266 .get_mut(rapier_entity.rapier_context_link.0)
267 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
268 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
269 co.set_solver_groups((*solver_groups).into());
270 }
271 }
272
273 for (rapier_entity, handle, _) in changed_sensors.iter() {
274 let (_, mut context_colliders) = context
275 .get_mut(rapier_entity.rapier_context_link.0)
276 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
277 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
278 co.set_sensor(true);
279 }
280 }
281
282 for (rapier_entity, handle, _) in changed_disabled.iter() {
283 let (_, mut context_colliders) = context
284 .get_mut(rapier_entity.rapier_context_link.0)
285 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
286 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
287 co.set_enabled(false);
288 }
289 }
290
291 for (rapier_entity, handle, threshold) in changed_contact_force_threshold.iter() {
292 let (_, mut context_colliders) = context
293 .get_mut(rapier_entity.rapier_context_link.0)
294 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
295 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
296 co.set_contact_force_event_threshold(threshold.0);
297 }
298 }
299
300 for (rapier_entity, handle, mprops) in changed_collider_mass_props.iter() {
301 let (rigidbody_set, mut context_colliders) = context
302 .get_mut(rapier_entity.rapier_context_link.0)
303 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
304 if let Some(co) = context_colliders.colliders.get_mut(handle.0) {
305 match mprops {
306 ColliderMassProperties::Density(density) => co.set_density(*density),
307 ColliderMassProperties::Mass(mass) => co.set_mass(*mass),
308 ColliderMassProperties::MassProperties(mprops) => {
309 co.set_mass_properties(mprops.into_rapier())
310 }
311 }
312
313 if let Some(body) = co.parent() {
314 if let Some(body_entity) = rigidbody_set.rigid_body_entity(body) {
315 mass_modified.write(body_entity.into());
316 }
317 }
318 }
319 }
320}
321
322pub(crate) fn collider_offset(
323 entity: Entity,
324 rigidbody_set: &RapierRigidBodySet,
325 child_of_query: &Query<&ChildOf>,
326 transform_query: &Query<&Transform>,
327) -> (Option<RigidBodyHandle>, Transform) {
328 let mut body_entity = entity;
329 let mut body_handle = rigidbody_set.entity2body.get(&body_entity).copied();
330 let mut child_transform = Transform::default();
331 while body_handle.is_none() {
332 if let Ok(child_of) = child_of_query.get(body_entity) {
333 if let Ok(transform) = transform_query.get(body_entity) {
334 child_transform = *transform * child_transform;
335 }
336 body_entity = child_of.parent();
337 } else {
338 break;
339 }
340
341 body_handle = rigidbody_set.entity2body.get(&body_entity).copied();
342 }
343
344 if body_handle.is_some() {
345 if let Ok(transform) = transform_query.get(body_entity) {
346 let scale_transform = Transform {
347 scale: transform.scale,
348 ..default()
349 };
350
351 child_transform = scale_transform * child_transform;
352 }
353 }
354
355 (body_handle, child_transform)
356}
357
358pub fn init_colliders(
360 mut commands: Commands,
361 config: Query<&RapierConfiguration>,
362 mut context_access: Query<(&mut RapierRigidBodySet, &mut RapierContextColliders)>,
363 default_context_access: Query<Entity, With<DefaultRapierContext>>,
364 colliders: Query<(ColliderComponents, Option<&GlobalTransform>), Without<RapierColliderHandle>>,
365 mut rigid_body_mprops: Query<&mut ReadMassProperties>,
366 child_of_query: Query<&ChildOf>,
367 transform_query: Query<&Transform>,
368) {
369 for (
370 (
371 (entity, entity_context_link),
372 shape,
373 sensor,
374 mprops,
375 active_events,
376 active_hooks,
377 active_collision_types,
378 friction,
379 restitution,
380 contact_skin,
381 collision_groups,
382 solver_groups,
383 contact_force_event_threshold,
384 disabled,
385 ),
386 global_transform,
387 ) in colliders.iter()
388 {
389 let context_entity = entity_context_link.map_or_else(
391 || {
392 let context_entity = default_context_access.single().ok()?;
393 commands
394 .entity(entity)
395 .insert(RapierContextEntityLink(context_entity));
396 Some(context_entity)
397 },
398 |link| Some(link.0),
399 );
400 let Some(context_entity) = context_entity else {
401 continue;
402 };
403
404 let config = config.get(context_entity).unwrap_or_else(|_| {
405 panic!("Failed to retrieve `RapierConfiguration` on entity {context_entity}.")
406 });
407
408 let Some(mut rigidbody_set_collider_set) = context_access.get_mut(context_entity).ok()
409 else {
410 log::error!("Could not find entity {context_entity} with rapier context while initializing {entity}");
411 continue;
412 };
413 let context_colliders = &mut *rigidbody_set_collider_set.1;
414 let rigidbody_set = &mut *rigidbody_set_collider_set.0;
415 let mut scaled_shape = shape.clone();
416 scaled_shape.set_scale(shape.scale, config.scaled_shape_subdivision);
417 let mut builder = ColliderBuilder::new(scaled_shape.raw.clone());
418
419 builder = builder.sensor(sensor.is_some());
420 builder = builder.enabled(disabled.is_none());
421
422 if let Some(mprops) = mprops {
423 builder = match mprops {
424 ColliderMassProperties::Density(density) => builder.density(*density),
425 ColliderMassProperties::Mass(mass) => builder.mass(*mass),
426 ColliderMassProperties::MassProperties(mprops) => {
427 builder.mass_properties(mprops.into_rapier())
428 }
429 };
430 }
431
432 if let Some(active_events) = active_events {
433 builder = builder.active_events((*active_events).into());
434 }
435
436 if let Some(active_hooks) = active_hooks {
437 builder = builder.active_hooks((*active_hooks).into());
438 }
439
440 if let Some(active_collision_types) = active_collision_types {
441 builder = builder.active_collision_types((*active_collision_types).into());
442 }
443
444 if let Some(friction) = friction {
445 builder = builder
446 .friction(friction.coefficient)
447 .friction_combine_rule(friction.combine_rule.into());
448 }
449
450 if let Some(restitution) = restitution {
451 builder = builder
452 .restitution(restitution.coefficient)
453 .restitution_combine_rule(restitution.combine_rule.into());
454 }
455
456 if let Some(contact_skin) = contact_skin {
457 builder = builder.contact_skin(contact_skin.0);
458 }
459
460 if let Some(collision_groups) = collision_groups {
461 builder = builder.collision_groups((*collision_groups).into());
462 }
463
464 if let Some(solver_groups) = solver_groups {
465 builder = builder.solver_groups((*solver_groups).into());
466 }
467
468 if let Some(threshold) = contact_force_event_threshold {
469 builder = builder.contact_force_event_threshold(threshold.0);
470 }
471 let body_entity = entity;
472 let (body_handle, child_transform) =
473 collider_offset(entity, rigidbody_set, &child_of_query, &transform_query);
474
475 builder = builder.user_data(entity.to_bits() as u128);
476
477 let handle = if let Some(body_handle) = body_handle {
478 builder = builder.position(utils::transform_to_iso(&child_transform));
479 let handle = context_colliders.colliders.insert_with_parent(
480 builder,
481 body_handle,
482 &mut rigidbody_set.bodies,
483 );
484 if let Ok(mut mprops) = rigid_body_mprops.get_mut(body_entity) {
485 if let Some(parent_body) = rigidbody_set.bodies.get(body_handle) {
488 mprops.set(MassProperties::from_rapier(
489 parent_body.mass_properties().local_mprops,
490 ));
491 }
492 }
493 handle
494 } else {
495 let global_transform = global_transform.cloned().unwrap_or_default();
496 builder = builder.position(utils::transform_to_iso(
497 &global_transform.compute_transform(),
498 ));
499 context_colliders.colliders.insert(builder)
500 };
501
502 commands.entity(entity).insert(RapierColliderHandle(handle));
503 context_colliders.entity2collider.insert(entity, handle);
504 }
505}
506
507#[cfg(all(feature = "dim3", feature = "async-collider"))]
510pub fn init_async_colliders(
511 mut commands: Commands,
512 meshes: Res<Assets<Mesh>>,
513 async_colliders: Query<(Entity, &Mesh3d, &AsyncCollider)>,
514) {
515 for (entity, mesh_handle, async_collider) in async_colliders.iter() {
516 if let Some(mesh) = meshes.get(mesh_handle) {
517 match Collider::from_bevy_mesh(mesh, &async_collider.0) {
518 Some(collider) => {
519 commands
520 .entity(entity)
521 .insert(collider)
522 .remove::<AsyncCollider>();
523 }
524 None => log::error!("Unable to generate collider from mesh {mesh:?}"),
525 }
526 }
527 }
528}
529
530#[cfg(all(feature = "dim3", feature = "async-collider"))]
533pub fn init_async_scene_colliders(
534 mut commands: Commands,
535 meshes: Res<Assets<Mesh>>,
536 scene_spawner: Res<SceneSpawner>,
537 async_colliders: Query<(Entity, &SceneInstance, &AsyncSceneCollider)>,
538 children: Query<&Children>,
539 mesh_handles: Query<(&Name, &Mesh3d)>,
540) {
541 for (scene_entity, scene_instance, async_collider) in async_colliders.iter() {
542 if scene_spawner.instance_is_ready(**scene_instance) {
543 for child_entity in children.iter_descendants(scene_entity) {
544 if let Ok((name, handle)) = mesh_handles.get(child_entity) {
545 let shape = async_collider
546 .named_shapes
547 .get(name.as_str())
548 .unwrap_or(&async_collider.shape);
549 if let Some(shape) = shape {
550 let mesh = meshes.get(handle).unwrap(); match Collider::from_bevy_mesh(mesh, shape) {
552 Some(collider) => {
553 commands.entity(child_entity).insert(collider);
554 }
555 None => log::error!(
556 "Unable to generate collider from mesh {mesh:?} with name {name}"
557 ),
558 }
559 }
560 }
561 }
562
563 commands.entity(scene_entity).remove::<AsyncSceneCollider>();
564 }
565 }
566}
567
568pub fn update_colliding_entities(
571 mut collision_events: MessageReader<CollisionEvent>,
572 mut colliding_entities: Query<&mut CollidingEntities>,
573) {
574 for event in collision_events.read() {
575 match event.to_owned() {
576 CollisionEvent::Started(entity1, entity2, _) => {
577 if let Ok(mut entities) = colliding_entities.get_mut(entity1) {
578 entities.0.insert(entity2);
579 }
580 if let Ok(mut entities) = colliding_entities.get_mut(entity2) {
581 entities.0.insert(entity1);
582 }
583 }
584 CollisionEvent::Stopped(entity1, entity2, _) => {
585 if let Ok(mut entities) = colliding_entities.get_mut(entity1) {
586 entities.0.remove(&entity2);
587 }
588 if let Ok(mut entities) = colliding_entities.get_mut(entity2) {
589 entities.0.remove(&entity1);
590 }
591 }
592 }
593 }
594}
595
596#[cfg(test)]
597#[allow(missing_docs)]
598pub mod test {
599 #[test]
600 #[cfg(all(feature = "dim3", feature = "async-collider"))]
601 fn async_collider_initializes() {
602 use super::*;
603 use bevy::{mesh::MeshPlugin, scene::ScenePlugin};
604
605 let mut app = App::new();
606 app.add_plugins((AssetPlugin::default(), MeshPlugin, ScenePlugin));
607 app.add_systems(Update, init_async_colliders);
608
609 app.finish();
610
611 let mut meshes = app.world_mut().resource_mut::<Assets<Mesh>>();
612 let cube = meshes.add(Cuboid::default());
613
614 let entity = app
615 .world_mut()
616 .spawn((Mesh3d(cube), AsyncCollider::default()))
617 .id();
618
619 app.update();
620
621 let entity = app.world().entity(entity);
622 assert!(
623 entity.get::<Collider>().is_some(),
624 "Collider component should be added"
625 );
626 assert!(
627 entity.get::<AsyncCollider>().is_none(),
628 "AsyncCollider component should be removed after Collider component creation"
629 );
630 }
631
632 #[test]
633 #[cfg(all(feature = "dim3", feature = "async-collider"))]
634 fn async_scene_collider_initializes() {
635 use super::*;
636 use bevy::{mesh::MeshPlugin, scene::ScenePlugin};
637
638 let mut app = App::new();
639 app.add_plugins((AssetPlugin::default(), MeshPlugin, ScenePlugin));
640 app.add_systems(PostUpdate, init_async_scene_colliders);
641
642 let mut meshes = app.world_mut().resource_mut::<Assets<Mesh>>();
643 let cube_handle = meshes.add(Cuboid::default());
644 let capsule_handle = meshes.add(Capsule3d::default());
645 let cube = app
646 .world_mut()
647 .spawn((Name::new("Cube"), Mesh3d(cube_handle)))
648 .id();
649 let capsule = app
650 .world_mut()
651 .spawn((Name::new("Capsule"), Mesh3d(capsule_handle)))
652 .id();
653
654 let mut scenes = app.world_mut().resource_mut::<Assets<Scene>>();
655 let scene = scenes.add(Scene::new(World::new()));
656
657 let mut named_shapes = bevy::platform::collections::HashMap::default();
658 named_shapes.insert("Capsule".to_string(), None);
659 let parent = app
660 .world_mut()
661 .spawn((
662 SceneRoot(scene),
663 AsyncSceneCollider {
664 named_shapes,
665 ..Default::default()
666 },
667 ))
668 .add_children(&[cube, capsule])
669 .id();
670
671 app.update();
672
673 assert!(
674 app.world().entity(cube).get::<Collider>().is_some(),
675 "Collider component should be added for cube"
676 );
677 assert!(
678 app.world().entity(capsule).get::<Collider>().is_none(),
679 "Collider component shouldn't be added for capsule"
680 );
681 assert!(
682 app.world().entity(parent).get::<AsyncCollider>().is_none(),
683 "AsyncSceneCollider component should be removed after Collider components creation"
684 );
685 }
686}