1use core::marker::PhantomData;
6
7#[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
8use crate::collision::collider::cache::ColliderCache;
9use crate::{
10 collision::broad_phase::BroadPhaseSystems,
11 physics_transform::{PhysicsTransformConfig, PhysicsTransformSystems, init_physics_transform},
12 prelude::*,
13};
14#[cfg(all(feature = "bevy_scene", feature = "default-collider"))]
15use bevy::scene::SceneInstance;
16use bevy::{
17 ecs::{intern::Interned, schedule::ScheduleLabel, system::StaticSystemParam},
18 prelude::*,
19};
20use mass_properties::{MassPropertySystems, components::RecomputeMassProperties};
21
22#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
41#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
42pub struct ColliderBackendPlugin<C: ScalableCollider> {
67 schedule: Interned<dyn ScheduleLabel>,
68 _phantom: PhantomData<C>,
69}
70
71impl<C: ScalableCollider> ColliderBackendPlugin<C> {
72 pub fn new(schedule: impl ScheduleLabel) -> Self {
76 Self {
77 schedule: schedule.intern(),
78 _phantom: PhantomData,
79 }
80 }
81}
82
83impl<C: ScalableCollider> Default for ColliderBackendPlugin<C> {
84 fn default() -> Self {
85 Self {
86 schedule: FixedPostUpdate.intern(),
87 _phantom: PhantomData,
88 }
89 }
90}
91
92impl<C: ScalableCollider> Plugin for ColliderBackendPlugin<C> {
93 fn build(&self, app: &mut App) {
94 let _ = app.try_register_required_components_with::<C, Position>(|| Position::PLACEHOLDER);
96 let _ = app.try_register_required_components_with::<C, Rotation>(|| Rotation::PLACEHOLDER);
97 let _ = app.try_register_required_components::<C, ColliderMarker>();
98 let _ = app.try_register_required_components::<C, ColliderAabb>();
99 let _ = app.try_register_required_components::<C, CollisionLayers>();
100 let _ = app.try_register_required_components::<C, ColliderDensity>();
101 let _ = app.try_register_required_components::<C, ColliderMassProperties>();
102
103 app.init_resource::<PhysicsTransformConfig>();
105 app.init_resource::<NarrowPhaseConfig>();
106 app.init_resource::<PhysicsLengthUnit>();
107
108 let hooks = app.world_mut().register_component_hooks::<C>();
109
110 hooks.on_add(|mut world, ctx| {
112 init_physics_transform(&mut world, &ctx);
114
115 let scale = world
116 .entity(ctx.entity)
117 .get::<GlobalTransform>()
118 .map(|gt| gt.scale())
119 .unwrap_or_default();
120 #[cfg(feature = "2d")]
121 let scale = scale.xy();
122
123 let mut entity_mut = world.entity_mut(ctx.entity);
124
125 entity_mut
129 .get_mut::<C>()
130 .unwrap()
131 .set_scale(scale.adjust_precision(), 10);
132
133 let collider = entity_mut.get::<C>().unwrap();
134
135 let density = entity_mut
136 .get::<ColliderDensity>()
137 .copied()
138 .unwrap_or_default();
139
140 let mass_properties = if entity_mut.get::<Sensor>().is_some() {
141 MassProperties::ZERO
142 } else {
143 collider.mass_properties(density.0)
144 };
145
146 if let Some(mut collider_mass_properties) =
147 entity_mut.get_mut::<ColliderMassProperties>()
148 {
149 *collider_mass_properties = ColliderMassProperties::from(mass_properties);
150 }
151 });
152
153 app.world_mut()
156 .register_component_hooks::<C>()
157 .on_remove(|mut world, ctx| {
158 world
162 .commands()
163 .entity(ctx.entity)
164 .try_remove::<ColliderMarker>();
165
166 let entity_ref = world.entity_mut(ctx.entity);
167
168 let Some(ColliderOf { body }) = entity_ref.get::<ColliderOf>().copied() else {
170 return;
171 };
172
173 world
175 .commands()
176 .entity(body)
177 .try_insert(RecomputeMassProperties);
178 });
179
180 app.add_observer(
182 |trigger: On<Add, C>,
183 mut query: Query<(
184 &C,
185 &Position,
186 &Rotation,
187 Option<&CollisionMargin>,
188 &mut ColliderAabb,
189 )>,
190 narrow_phase_config: Res<NarrowPhaseConfig>,
191 length_unit: Res<PhysicsLengthUnit>,
192 collider_context: StaticSystemParam<C::Context>| {
193 let contact_tolerance = length_unit.0 * narrow_phase_config.contact_tolerance;
194 let aabb_context = AabbContext::new(trigger.entity, &*collider_context);
195
196 if let Ok((collider, pos, rot, collision_margin, mut aabb)) =
197 query.get_mut(trigger.entity)
198 {
199 let collision_margin = collision_margin.map_or(0.0, |m| m.0);
200 *aabb = collider
201 .aabb_with_context(pos.0, *rot, aabb_context)
202 .grow(Vector::splat(contact_tolerance + collision_margin));
203 }
204 },
205 );
206
207 app.add_observer(
209 |trigger: On<Add, Sensor>,
210 mut commands: Commands,
211 query: Query<(&ColliderMassProperties, &ColliderOf)>| {
212 if let Ok((collider_mass_properties, &ColliderOf { body })) =
213 query.get(trigger.entity)
214 {
215 if *collider_mass_properties == ColliderMassProperties::ZERO {
217 return;
218 }
219
220 if let Ok(mut entity_commands) = commands.get_entity(body) {
222 entity_commands.insert(RecomputeMassProperties);
223 }
224 }
225 },
226 );
227
228 app.add_observer(
230 |trigger: On<Remove, Sensor>,
231 mut collider_query: Query<(
232 Ref<C>,
233 &ColliderDensity,
234 &mut ColliderMassProperties,
235 )>| {
236 if let Ok((collider, density, mut collider_mass_properties)) =
237 collider_query.get_mut(trigger.entity)
238 {
239 *collider_mass_properties =
241 ColliderMassProperties::from(collider.mass_properties(density.0));
242 }
243 },
244 );
245
246 app.add_systems(
247 self.schedule,
248 (
249 update_collider_scale::<C>
250 .in_set(PhysicsSystems::Prepare)
251 .after(PhysicsTransformSystems::TransformToPosition),
252 update_collider_mass_properties::<C>
253 .in_set(MassPropertySystems::UpdateColliderMassProperties),
254 )
255 .chain(),
256 );
257
258 let physics_schedule = app
259 .get_schedule_mut(PhysicsSchedule)
260 .expect("add PhysicsSchedule first");
261
262 physics_schedule.add_systems(
265 update_aabb::<C>
266 .in_set(PhysicsStepSystems::BroadPhase)
267 .after(BroadPhaseSystems::First)
268 .before(BroadPhaseSystems::UpdateStructures)
269 .ambiguous_with_all(),
270 );
271
272 #[cfg(feature = "default-collider")]
273 app.add_systems(
274 Update,
275 (
276 init_collider_constructors,
277 init_collider_constructor_hierarchies,
278 ),
279 );
280 }
281}
282
283#[derive(Reflect, Component, Clone, Copy, Debug, Default)]
287#[reflect(Component, Debug, Default)]
288pub struct ColliderMarker;
289
290#[cfg(feature = "default-collider")]
299fn init_collider_constructors(
300 mut commands: Commands,
301 #[cfg(feature = "collider-from-mesh")] meshes: Res<Assets<Mesh>>,
302 #[cfg(feature = "collider-from-mesh")] mesh_handles: Query<&Mesh3d>,
303 #[cfg(feature = "collider-from-mesh")] mut collider_cache: Option<ResMut<ColliderCache>>,
304 constructors: Query<(
305 Entity,
306 Option<&Collider>,
307 Option<&Name>,
308 &ColliderConstructor,
309 )>,
310) {
311 for (entity, existing_collider, name, constructor) in constructors.iter() {
312 let name = pretty_name(name, entity);
313 if existing_collider.is_some() {
314 warn!(
315 "Tried to add a collider to entity {name} via {constructor:#?}, \
316 but that entity already holds a collider. Skipping.",
317 );
318 commands.entity(entity).remove::<ColliderConstructor>();
319 continue;
320 }
321 #[cfg(feature = "collider-from-mesh")]
322 let collider = if constructor.requires_mesh() {
323 let mesh_handle = mesh_handles.get(entity).unwrap_or_else(|_| panic!(
324 "Tried to add a collider to entity {name} via {constructor:#?} that requires a mesh, \
325 but no mesh handle was found"));
326 let Some(mesh) = meshes.get(mesh_handle) else {
327 continue;
329 };
330 collider_cache
331 .as_mut()
332 .map(|cache| cache.get_or_insert(mesh_handle, mesh, constructor.clone()))
333 .unwrap_or_else(|| Collider::try_from_constructor(constructor.clone(), Some(mesh)))
334 } else {
335 Collider::try_from_constructor(constructor.clone(), None)
336 };
337
338 #[cfg(not(feature = "collider-from-mesh"))]
339 let collider = Collider::try_from_constructor(constructor.clone());
340
341 if let Some(collider) = collider {
342 commands.entity(entity).insert(collider);
343 commands.trigger(ColliderConstructorReady { entity })
344 } else {
345 error!(
346 "Tried to add a collider to entity {name} via {constructor:#?}, \
347 but the collider could not be generated. Skipping.",
348 );
349 }
350 commands.entity(entity).remove::<ColliderConstructor>();
351 }
352}
353
354#[cfg(feature = "default-collider")]
358fn init_collider_constructor_hierarchies(
359 mut commands: Commands,
360 #[cfg(feature = "collider-from-mesh")] meshes: Res<Assets<Mesh>>,
361 #[cfg(feature = "collider-from-mesh")] mesh_handles: Query<&Mesh3d>,
362 #[cfg(feature = "collider-from-mesh")] mut collider_cache: Option<ResMut<ColliderCache>>,
363 #[cfg(feature = "bevy_scene")] scene_spawner: Res<SceneSpawner>,
364 #[cfg(feature = "bevy_scene")] scenes: Query<&SceneRoot>,
365 #[cfg(feature = "bevy_scene")] scene_instances: Query<&SceneInstance>,
366 collider_constructors: Query<(Entity, &ColliderConstructorHierarchy)>,
367 children: Query<&Children>,
368 child_query: Query<(Option<&Name>, Option<&Collider>)>,
369) {
370 use super::ColliderConstructorHierarchyConfig;
371
372 for (scene_entity, collider_constructor_hierarchy) in collider_constructors.iter() {
373 #[cfg(feature = "bevy_scene")]
374 {
375 if scenes.contains(scene_entity) {
376 if let Ok(scene_instance) = scene_instances.get(scene_entity) {
377 if !scene_spawner.instance_is_ready(**scene_instance) {
378 continue;
380 }
381 } else {
382 continue;
384 }
385 }
386 }
387
388 for child_entity in children.iter_descendants(scene_entity) {
389 let Ok((name, existing_collider)) = child_query.get(child_entity) else {
390 continue;
391 };
392
393 let pretty_name = pretty_name(name, child_entity);
394
395 let default_collider = || {
396 Some(ColliderConstructorHierarchyConfig {
397 constructor: collider_constructor_hierarchy.default_constructor.clone(),
398 ..default()
399 })
400 };
401
402 let collider_data = if let Some(name) = name {
403 collider_constructor_hierarchy
404 .config
405 .get(name.as_str())
406 .cloned()
407 .unwrap_or_else(default_collider)
408 } else if existing_collider.is_some() {
409 warn!(
410 "Tried to add a collider to entity {pretty_name} via {collider_constructor_hierarchy:#?}, \
411 but that entity already holds a collider. Skipping. \
412 If this was intentional, add the name of the collider to overwrite to `ColliderConstructorHierarchy.config`."
413 );
414 continue;
415 } else {
416 default_collider()
417 };
418
419 let Some(collider_data) = collider_data else {
421 continue;
422 };
423
424 let Some(constructor) = collider_data
427 .constructor
428 .or_else(|| collider_constructor_hierarchy.default_constructor.clone())
429 else {
430 continue;
431 };
432
433 #[cfg(feature = "collider-from-mesh")]
434 let collider = if constructor.requires_mesh() {
435 let Ok(mesh_handle) = mesh_handles.get(child_entity) else {
436 continue;
438 };
439 let Some(mesh) = meshes.get(mesh_handle) else {
440 continue;
442 };
443 collider_cache
444 .as_mut()
445 .map(|cache| cache.get_or_insert(mesh_handle, mesh, constructor.clone()))
446 .unwrap_or_else(|| {
447 Collider::try_from_constructor(constructor.clone(), Some(mesh))
448 })
449 } else {
450 Collider::try_from_constructor(constructor.clone(), None)
451 };
452
453 #[cfg(not(feature = "collider-from-mesh"))]
454 let collider = Collider::try_from_constructor(constructor);
455
456 if let Some(collider) = collider {
457 commands.entity(child_entity).insert((
458 collider,
459 collider_data
460 .layers
461 .unwrap_or(collider_constructor_hierarchy.default_layers),
462 collider_data
463 .density
464 .unwrap_or(collider_constructor_hierarchy.default_density),
465 ));
466 } else {
467 error!(
468 "Tried to add a collider to entity {pretty_name} via {collider_constructor_hierarchy:#?}, \
469 but the collider could not be generated. Skipping.",
470 );
471 }
472 }
473
474 commands
475 .entity(scene_entity)
476 .remove::<ColliderConstructorHierarchy>();
477
478 commands.trigger(ColliderConstructorHierarchyReady {
479 entity: scene_entity,
480 })
481 }
482}
483
484#[cfg(feature = "default-collider")]
485fn pretty_name(name: Option<&Name>, entity: Entity) -> String {
486 name.map(|n| n.to_string())
487 .unwrap_or_else(|| format!("<unnamed entity {}>", entity.index()))
488}
489
490#[allow(clippy::type_complexity)]
492fn update_aabb<C: AnyCollider>(
493 mut colliders: Query<
494 (
495 Entity,
496 &C,
497 &mut ColliderAabb,
498 &Position,
499 &Rotation,
500 Option<&ColliderOf>,
501 Option<&CollisionMargin>,
502 Option<&SpeculativeMargin>,
503 Has<SweptCcd>,
504 Option<&LinearVelocity>,
505 Option<&AngularVelocity>,
506 ),
507 Or<(
508 Changed<Position>,
509 Changed<Rotation>,
510 Changed<LinearVelocity>,
511 Changed<AngularVelocity>,
512 Changed<C>,
513 )>,
514 >,
515 rb_velocities: Query<
516 (
517 &Position,
518 &Rotation,
519 &ComputedCenterOfMass,
520 &LinearVelocity,
521 &AngularVelocity,
522 ),
523 With<Children>,
524 >,
525 narrow_phase_config: Res<NarrowPhaseConfig>,
526 length_unit: Res<PhysicsLengthUnit>,
527 time: Res<Time>,
528 collider_context: StaticSystemParam<C::Context>,
529) {
530 let delta_secs = time.delta_seconds_adjusted();
531 let default_speculative_margin = length_unit.0 * narrow_phase_config.default_speculative_margin;
532 let contact_tolerance = length_unit.0 * narrow_phase_config.contact_tolerance;
533
534 for (
535 entity,
536 collider,
537 mut aabb,
538 pos,
539 rot,
540 collider_of,
541 collision_margin,
542 speculative_margin,
543 has_swept_ccd,
544 lin_vel,
545 ang_vel,
546 ) in &mut colliders
547 {
548 let collision_margin = collision_margin.map_or(0.0, |margin| margin.0);
549 let speculative_margin = if has_swept_ccd {
550 Scalar::MAX
551 } else {
552 speculative_margin.map_or(default_speculative_margin, |margin| margin.0)
553 };
554
555 let context = AabbContext::new(entity, &*collider_context);
556
557 if speculative_margin <= 0.0 {
558 *aabb = collider
559 .aabb_with_context(pos.0, *rot, context)
560 .grow(Vector::splat(contact_tolerance + collision_margin));
561 continue;
562 }
563
564 let (lin_vel, ang_vel) = if let (Some(lin_vel), Some(ang_vel)) = (lin_vel, ang_vel) {
566 (*lin_vel, *ang_vel)
567 } else if let Some(Ok((rb_pos, rb_rot, center_of_mass, lin_vel, ang_vel))) =
568 collider_of.map(|&ColliderOf { body }| rb_velocities.get(body))
569 {
570 let offset = pos.0 - rb_pos.0 - rb_rot * center_of_mass.0;
577 #[cfg(feature = "2d")]
578 let vel_at_offset =
579 lin_vel.0 + Vector::new(-ang_vel.0 * offset.y, ang_vel.0 * offset.x) * 1.0;
580 #[cfg(feature = "3d")]
581 let vel_at_offset = lin_vel.0 + ang_vel.cross(offset);
582 (LinearVelocity(vel_at_offset), *ang_vel)
583 } else {
584 (LinearVelocity::ZERO, AngularVelocity::ZERO)
585 };
586
587 let (start_pos, start_rot) = (*pos, *rot);
589 let (end_pos, end_rot) = {
590 #[cfg(feature = "2d")]
591 {
592 (
593 pos.0
594 + (lin_vel.0 * delta_secs)
595 .clamp_length_max(speculative_margin.max(contact_tolerance)),
596 *rot * Rotation::radians(ang_vel.0 * delta_secs),
597 )
598 }
599 #[cfg(feature = "3d")]
600 {
601 let end_rot =
602 Rotation(Quaternion::from_scaled_axis(ang_vel.0 * delta_secs) * rot.0)
603 .fast_renormalize();
604 (
605 pos.0
606 + (lin_vel.0 * delta_secs)
607 .clamp_length_max(speculative_margin.max(contact_tolerance)),
608 end_rot,
609 )
610 }
611 };
612 *aabb = collider
615 .swept_aabb_with_context(start_pos.0, start_rot, end_pos, end_rot, context)
616 .grow(Vector::splat(contact_tolerance + collision_margin));
617 }
618}
619
620#[allow(clippy::type_complexity)]
622pub fn update_collider_scale<C: ScalableCollider>(
623 mut colliders: ParamSet<(
624 Query<(&Transform, &mut C), (Without<ChildOf>, Or<(Changed<Transform>, Changed<C>)>)>,
626 Query<
628 (&ColliderTransform, &mut C),
629 (With<ChildOf>, Or<(Changed<ColliderTransform>, Changed<C>)>),
630 >,
631 )>,
632 config: Res<PhysicsTransformConfig>,
633) {
634 if config.transform_to_collider_scale {
635 for (transform, mut collider) in &mut colliders.p0() {
637 #[cfg(feature = "2d")]
638 let scale = transform.scale.truncate().adjust_precision();
639 #[cfg(feature = "3d")]
640 let scale = transform.scale.adjust_precision();
641 if scale != collider.scale() {
642 collider.set_scale(scale, 10);
645 }
646 }
647 }
648 for (collider_transform, mut collider) in &mut colliders.p1() {
650 if collider_transform.scale != collider.scale() {
651 collider.set_scale(collider_transform.scale, 10);
654 }
655 }
656}
657
658#[allow(clippy::type_complexity)]
660pub(crate) fn update_collider_mass_properties<C: AnyCollider>(
661 mut query: Query<
662 (Ref<C>, &ColliderDensity, &mut ColliderMassProperties),
663 (Or<(Changed<C>, Changed<ColliderDensity>)>, Without<Sensor>),
664 >,
665) {
666 for (collider, density, mut collider_mass_properties) in &mut query {
667 *collider_mass_properties =
669 ColliderMassProperties::from(collider.mass_properties(density.0));
670 }
671}
672
673#[cfg(test)]
674mod tests {
675 #![allow(clippy::unnecessary_cast)]
676
677 #[cfg(feature = "default-collider")]
678 use super::*;
679
680 #[test]
681 #[cfg(feature = "default-collider")]
682 fn sensor_mass_properties() {
683 let mut app = App::new();
684
685 app.init_schedule(PhysicsSchedule)
686 .init_schedule(SubstepSchedule);
687
688 app.add_plugins((
689 MassPropertyPlugin::new(FixedPostUpdate),
690 ColliderHierarchyPlugin,
691 ColliderTransformPlugin::default(),
692 ColliderBackendPlugin::<Collider>::new(FixedPostUpdate),
693 ));
694
695 let collider = Collider::capsule(0.5, 2.0);
696 let mass_properties = MassPropertiesBundle::from_shape(&collider, 1.0);
697
698 let parent = app
699 .world_mut()
700 .spawn((
701 RigidBody::Dynamic,
702 mass_properties.clone(),
703 Transform::default(),
704 ))
705 .id();
706
707 let child = app
708 .world_mut()
709 .spawn((
710 collider,
711 Transform::from_xyz(1.0, 0.0, 0.0),
712 ChildOf(parent),
713 ))
714 .id();
715
716 app.world_mut().run_schedule(FixedPostUpdate);
717
718 assert_eq!(
719 app.world()
720 .entity(parent)
721 .get::<ComputedMass>()
722 .expect("rigid body should have mass")
723 .value() as f32,
724 2.0 * mass_properties.mass.0,
725 );
726 assert!(
727 app.world()
728 .entity(parent)
729 .get::<ComputedCenterOfMass>()
730 .expect("rigid body should have a center of mass")
731 .x
732 > 0.0,
733 );
734
735 let mut entity_mut = app.world_mut().entity_mut(child);
737 entity_mut.insert(Sensor);
738
739 app.world_mut().run_schedule(FixedPostUpdate);
740
741 assert_eq!(
742 app.world()
743 .entity(parent)
744 .get::<ComputedMass>()
745 .expect("rigid body should have mass")
746 .value() as f32,
747 mass_properties.mass.0,
748 );
749 assert!(
750 app.world()
751 .entity(parent)
752 .get::<ComputedCenterOfMass>()
753 .expect("rigid body should have a center of mass")
754 .x
755 == 0.0,
756 );
757
758 let mut entity_mut = app.world_mut().entity_mut(child);
760 entity_mut.remove::<Sensor>();
761
762 app.world_mut().run_schedule(FixedPostUpdate);
763
764 assert_eq!(
765 app.world()
766 .entity(parent)
767 .get::<ComputedMass>()
768 .expect("rigid body should have mass")
769 .value() as f32,
770 2.0 * mass_properties.mass.0,
771 );
772 assert!(
773 app.world()
774 .entity(parent)
775 .get::<ComputedCenterOfMass>()
776 .expect("rigid body should have a center of mass")
777 .x
778 > 0.0,
779 );
780 }
781}