avian3d/dynamics/rigid_body/body_size_metrics.rs
1//! Size metrics associated with a [`RigidBody`] and its colliders.
2//!
3//! See [`BodySizeMetrics`] for more information.
4//!
5//! [`RigidBody`]: crate::dynamics::rigid_body::RigidBody
6
7use core::marker::PhantomData;
8
9use bevy::{ecs::system::StaticSystemParam, prelude::*};
10
11use crate::{
12 collision::collider::{
13 AnyCollider, ColliderContext,
14 collider_hierarchy::{ColliderOf, RigidBodyColliders},
15 collider_transform::ColliderTransform,
16 },
17 dynamics::rigid_body::mass_properties::components::ComputedCenterOfMass,
18 math::ToRealPrecision,
19 schedule::{PhysicsSchedule, PhysicsStepSystems},
20};
21
22/// Size metrics associated with a [`RigidBody`] and its colliders.
23///
24/// These can be used for various purposes, such as determining thresholds
25/// for [Continuous Collision Detection (CCD)][CCD], [sleeping], and contact recycling.
26///
27/// The values are automatically computed and updated by the [`BodySizeMetricsPlugin`].
28///
29/// [`RigidBody`]: crate::dynamics::rigid_body::RigidBody
30/// [CCD]: crate::dynamics::ccd
31/// [sleeping]: crate::dynamics::sleeping
32#[derive(Component, Clone, Copy, Debug, PartialEq, Reflect)]
33pub struct BodySizeMetrics {
34 /// A conservative minimum thickness used by [Continuous Collision Detection (CCD)][CCD]
35 /// to determine how far the body can move in a single timestep before it might start to
36 /// tunnel through geometry.
37 ///
38 /// This is the minimum [`ccd_thickness`] of the colliders attached to the body,
39 /// and typically corresponds to the minimum distance from the centroid
40 /// of any given shape to its surface.
41 ///
42 /// [CCD]: crate::dynamics::ccd
43 /// [`ccd_thickness`]: crate::collision::collider::AnyCollider::ccd_thickness_with_context
44 pub ccd_thickness: f32,
45
46 /// The maximum distance from the center of mass of the body to the surface
47 /// of any of its colliders.
48 ///
49 /// Typically corresponds to the radius of the sphere formed by sweeping
50 /// the body about its center of mass.
51 ///
52 /// This can be useful for determining the maximum velocity that a point
53 /// on the body can have, which can be used for sleeping and contact recycling
54 /// thresholds, for example.
55 pub sweep_radius: f32,
56}
57
58impl Default for BodySizeMetrics {
59 fn default() -> Self {
60 Self {
61 ccd_thickness: f32::INFINITY,
62 sweep_radius: 0.0,
63 }
64 }
65}
66
67/// A plugin for computing and updating [`BodySizeMetrics`] for rigid bodies.
68#[derive(Default)]
69pub struct BodySizeMetricsPlugin<C: AnyCollider> {
70 _phantom: PhantomData<C>,
71}
72
73impl<C: AnyCollider> Plugin for BodySizeMetricsPlugin<C> {
74 fn build(&self, app: &mut App) {
75 // Update body size metrics before they are consumed by the solver and continuous collision
76 // detection. Allowing ambiguities lets multiple collision backends coexist.
77 app.add_systems(
78 PhysicsSchedule,
79 update_body_size_metrics::<C>
80 .before(PhysicsStepSystems::Solver)
81 .ambiguous_with_all(),
82 );
83 }
84}
85
86type BodySizeMetricsData = (
87 &'static mut BodySizeMetrics,
88 &'static RigidBodyColliders,
89 &'static ComputedCenterOfMass,
90);
91
92fn update_body_size_metrics<C: AnyCollider>(
93 // Body size metrics only need updating when the center of mass
94 // or one of the colliders changes.
95 mut bodies: ParamSet<(
96 Query<BodySizeMetricsData, Changed<ComputedCenterOfMass>>,
97 Query<BodySizeMetricsData>,
98 )>,
99 colliders: Query<(Entity, &C, &ColliderTransform)>,
100 changed_colliders: Query<&ColliderOf, Or<(Changed<C>, Changed<ColliderTransform>)>>,
101 context: StaticSystemParam<C::Context>,
102) {
103 let context = context.into_inner();
104
105 // Computes the minimum CCD thickness and maximum sweep radius over a body's colliders.
106 let compute = |rb_colliders: &RigidBodyColliders, com: &ComputedCenterOfMass| {
107 let mut ccd_thickness: f32 = f32::INFINITY;
108 let mut sweep_radius: f32 = 0.0;
109
110 for (entity, collider, collider_transform) in colliders.iter_many(rb_colliders).matched() {
111 // Compute the CCD thickness
112 let ctx = ColliderContext::new(entity, &context);
113 let thickness = collider.ccd_thickness_with_context(ctx);
114 ccd_thickness = ccd_thickness.min(thickness);
115
116 // Compute the sweep radius
117 let ctx = ColliderContext::new(entity, &context);
118 let point = com.0 - collider_transform.translation;
119 let distance_to_com = collider.max_distance_to_point_with_context(point.real(), ctx);
120 sweep_radius = sweep_radius.max(distance_to_com);
121 }
122
123 BodySizeMetrics {
124 ccd_thickness,
125 sweep_radius,
126 }
127 };
128
129 // Bodies whose center of mass changed.
130 for (mut size_metrics, rb_colliders, com) in &mut bodies.p0() {
131 *size_metrics = compute(rb_colliders, com);
132 }
133
134 // Bodies with a collider that changed. Note that bodies may be visited
135 // multiple times, but the end result will be the same.
136 if changed_colliders.is_empty() {
137 return;
138 }
139 let mut all_bodies = bodies.p1();
140 for collider_of in &changed_colliders {
141 if let Ok((mut size_metrics, rb_colliders, com)) = all_bodies.get_mut(collider_of.body) {
142 *size_metrics = compute(rb_colliders, com);
143 }
144 }
145}