avian3d/dynamics/rigid_body/mass_properties/
system_param.rs

1use crate::prelude::*;
2use bevy::{
3    ecs::system::{
4        lifetimeless::{Read, Write},
5        SystemParam,
6    },
7    prelude::*,
8};
9
10/// A [`SystemParam`] that provides helper methods for computing and updating [mass properties].
11///
12/// [mass properties]: crate::dynamics::rigid_body::mass_properties
13#[derive(SystemParam)]
14pub struct MassPropertyHelper<'w, 's> {
15    query: Query<
16        'w,
17        's,
18        (
19            Option<Read<Mass>>,
20            Option<Read<AngularInertia>>,
21            Option<Read<CenterOfMass>>,
22            Option<Read<ColliderMassProperties>>,
23            Option<Read<ColliderTransform>>,
24            Has<Sensor>,
25        ),
26        Or<(
27            With<Mass>,
28            With<AngularInertia>,
29            With<CenterOfMass>,
30            (
31                With<ColliderMassProperties>,
32                With<ColliderTransform>,
33                Without<Sensor>,
34            ),
35        )>,
36    >,
37    computed_mass_properties_query: Query<
38        'w,
39        's,
40        (
41            Write<ComputedMass>,
42            Write<ComputedAngularInertia>,
43            Write<ComputedCenterOfMass>,
44            Option<Read<Mass>>,
45            Option<Read<AngularInertia>>,
46            Option<Read<CenterOfMass>>,
47            Has<NoAutoMass>,
48            Has<NoAutoAngularInertia>,
49            Has<NoAutoCenterOfMass>,
50        ),
51    >,
52    children: Query<'w, 's, Read<Children>>,
53}
54
55impl MassPropertyHelper<'_, '_> {
56    /// Updates the [`ComputedMass`], [`ComputedAngularInertia`], and [`ComputedCenterOfMass`] of the given entity.
57    ///
58    /// This takes into account the mass properties of descendants, unless the given entity has the [`NoAutoMass`],
59    /// [`NoAutoAngularInertia`], or [`NoAutoCenterOfMass`] marker components.
60    pub fn update_mass_properties(&mut self, entity: Entity) {
61        // Compute the total mass properties of the entity and its descendants.
62        let mut mass_props = self.total_mass_properties(entity);
63
64        let Ok((
65            mut computed_mass,
66            mut computed_inertia,
67            mut computed_com,
68            mass,
69            angular_inertia,
70            center_of_mass,
71            no_auto_mass,
72            no_auto_inertia,
73            no_auto_com,
74        )) = self.computed_mass_properties_query.get_mut(entity)
75        else {
76            return;
77        };
78
79        // If automatic computation of mass properties is disabled, set them to the local `Mass`, `AngularInertia`, and `CenterOfMass`.
80        // Otherwise, use the computed total mass properties.
81
82        if no_auto_mass {
83            if let Some(mass) = mass {
84                mass_props.set_mass(mass.0, !no_auto_inertia);
85                computed_mass.set(mass_props.mass as Scalar);
86            } else if !no_auto_inertia {
87                // Make sure the angular inertia is scaled to match the existing computed mass.
88                #[allow(clippy::unnecessary_cast)]
89                mass_props.set_mass(computed_mass.value() as f32, true);
90            }
91        } else {
92            computed_mass.set(mass_props.mass as Scalar);
93        }
94
95        if no_auto_inertia {
96            if let Some(angular_inertia) = angular_inertia {
97                #[cfg(feature = "2d")]
98                {
99                    mass_props.angular_inertia = angular_inertia.0;
100                    computed_inertia.set(mass_props.angular_inertia as Scalar);
101                }
102                #[cfg(feature = "3d")]
103                {
104                    mass_props.principal_angular_inertia = angular_inertia.principal;
105                    mass_props.local_inertial_frame = angular_inertia.local_frame;
106                    computed_inertia.set(
107                        mass_props
108                            .angular_inertia_tensor()
109                            .as_mat3()
110                            .adjust_precision(),
111                    );
112                }
113            }
114        } else {
115            #[cfg(feature = "2d")]
116            {
117                computed_inertia.set(mass_props.angular_inertia as Scalar);
118            }
119            #[cfg(feature = "3d")]
120            {
121                computed_inertia.set(
122                    mass_props
123                        .angular_inertia_tensor()
124                        .as_mat3()
125                        .adjust_precision(),
126                );
127            }
128        }
129
130        if no_auto_com {
131            if let Some(center_of_mass) = center_of_mass {
132                mass_props.center_of_mass = center_of_mass.0;
133                computed_com.0 = mass_props.center_of_mass.adjust_precision();
134            }
135        } else {
136            computed_com.0 = mass_props.center_of_mass.adjust_precision();
137        }
138    }
139
140    /// Computes the total mass properties of the given entity,
141    /// taking into account the mass properties of descendants.
142    ///
143    /// This ignores the [`NoAutoMass`], [`NoAutoAngularInertia`], and [`NoAutoCenterOfMass`] marker components.
144    pub fn total_mass_properties(&self, entity: Entity) -> MassProperties {
145        core::iter::once(self.local_mass_properties(entity))
146            .chain(
147                self.children
148                    .iter_descendants(entity)
149                    .map(|child| self.local_mass_properties(child)),
150            )
151            .flatten()
152            .sum()
153    }
154
155    /// Computes the total mass properties of the descendants of the given entity.
156    ///
157    /// This ignores the [`NoAutoMass`], [`NoAutoAngularInertia`], and [`NoAutoCenterOfMass`] marker components.
158    pub fn descendants_mass_properties(&self, entity: Entity) -> MassProperties {
159        self.children
160            .iter_descendants(entity)
161            .filter_map(|child| self.local_mass_properties(child))
162            .sum()
163    }
164
165    /// Computes the local mass properties of the given entity.
166    ///
167    /// This only considers the entity's own [`Mass`], [`AngularInertia`], [`CenterOfMass`],
168    /// and/or [`ColliderMassProperties`] if present, not those of its children.
169    ///
170    /// If the entity has no mass properties or the entity does not exist, `None` is returned.
171    pub fn local_mass_properties(&self, entity: Entity) -> Option<MassProperties> {
172        let (mass, angular_inertia, center_of_mass, collider_mass, collider_transform, is_sensor) =
173            self.query.get(entity).ok()?;
174
175        // Initialize the mass properties with the collider's mass properties or zero.
176        let mut mass_props = collider_mass
177            .filter(|_| !is_sensor)
178            .map_or(MassProperties::ZERO, |m| **m);
179
180        // Set the mass if the `Mass` component is present.
181        if let Some(mass) = mass {
182            // TODO: This needs to consider `NoAutoMass`.
183            // Only adjust the angular inertia if it is not exlicitly set with `AngularInertia`.
184            let update_angular_inertia = angular_inertia.is_none();
185            mass_props.set_mass(mass.0, update_angular_inertia);
186        }
187
188        // Set the angular inertia if the `AngularInertia` component is present.
189        if let Some(angular_inertia) = angular_inertia {
190            #[cfg(feature = "2d")]
191            {
192                mass_props.angular_inertia = angular_inertia.0;
193            }
194            #[cfg(feature = "3d")]
195            {
196                mass_props.principal_angular_inertia = angular_inertia.principal;
197                mass_props.local_inertial_frame = angular_inertia.local_frame;
198            }
199        }
200
201        // Set the center of mass if the `CenterOfMass` component is present.
202        if let Some(center_of_mass) = center_of_mass {
203            mass_props.center_of_mass = center_of_mass.0;
204        }
205
206        if let Some(collider_transform) = collider_mass.and(collider_transform) {
207            #[cfg(feature = "2d")]
208            {
209                mass_props.transform_by(Isometry2d::new(
210                    collider_transform.translation.f32(),
211                    Rot2::from(collider_transform.rotation),
212                ));
213            }
214            #[cfg(feature = "3d")]
215            {
216                mass_props.transform_by(Isometry3d::new(
217                    collider_transform.translation.f32(),
218                    collider_transform.rotation.f32(),
219                ));
220            }
221        }
222
223        Some(mass_props)
224    }
225}