Skip to main content

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}