bevy_heavy/dim2/
mod.rs

1use alloc::vec::Vec;
2
3use bevy_math::{DVec2, Isometry2d, Vec2};
4#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
5use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
6
7use crate::RecipOrZero;
8
9/// [`ComputeMassProperties2d`] implementations for 2D geometric primitives.
10mod impls;
11
12pub use impls::*;
13
14/// A trait for computing [`MassProperties2d`] for 2D objects.
15///
16/// For the 3D equivalent, see [`ComputeMassProperties3d`](crate::ComputeMassProperties3d).
17pub trait ComputeMassProperties2d {
18    /// Computes the [mass] of the object with a given `density`.
19    ///
20    /// [mass]: crate#mass
21    fn mass(&self, density: f32) -> f32;
22
23    /// Computes the [angular inertia] corresponding to a mass of `1.0`.
24    ///
25    /// [angular inertia]: crate#angular-inertia
26    #[doc(alias = "unit_moment_of_inertia")]
27    fn unit_angular_inertia(&self) -> f32;
28
29    /// Computes the [angular inertia] corresponding to the given `mass`.
30    ///
31    /// Equivalent to `mass * shape.unit_angular_inertia()`.
32    ///
33    /// [angular inertia]: crate#angular-inertia
34    #[inline]
35    #[doc(alias = "moment_of_inertia")]
36    fn angular_inertia(&self, mass: f32) -> f32 {
37        mass * self.unit_angular_inertia()
38    }
39
40    /// Computes the local [center of mass] relative to the object's origin.
41    ///
42    /// [center of mass]: crate#center-of-mass
43    fn center_of_mass(&self) -> Vec2;
44
45    /// Computes the [`MassProperties2d`] with a given `density`.
46    #[inline]
47    fn mass_properties(&self, density: f32) -> MassProperties2d {
48        let mass = self.mass(density);
49        MassProperties2d::new(mass, self.angular_inertia(mass), self.center_of_mass())
50    }
51}
52
53/// The [mass], [angular inertia], and local [center of mass] of an object in 2D space.
54///
55/// [mass]: crate#mass
56/// [angular inertia]: crate#angular-inertia
57/// [center of mass]: crate#center-of-mass
58#[derive(Clone, Copy, Debug, PartialEq)]
59#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
60#[cfg_attr(feature = "bevy_reflect", reflect(Debug, PartialEq))]
61#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
62#[cfg_attr(
63    all(feature = "bevy_reflect", feature = "serialize"),
64    reflect(Serialize, Deserialize)
65)]
66pub struct MassProperties2d {
67    /// The [mass].
68    ///
69    /// [mass]: crate#mass
70    pub mass: f32,
71    /// The angular inertia along the principal axis.
72    ///
73    /// [angular inertia]: crate#angular-inertia
74    pub angular_inertia: f32,
75    /// The local [center of mass] relative to the object's origin.
76    ///
77    /// [center of mass]: crate#center-of-mass
78    pub center_of_mass: Vec2,
79}
80
81impl Default for MassProperties2d {
82    /// Returns the default [`MassProperties2d`], with zero mass and angular inertia.
83    fn default() -> Self {
84        Self::ZERO
85    }
86}
87
88impl MassProperties2d {
89    /// Zero mass and angular inertia.
90    pub const ZERO: Self = Self {
91        mass: 0.0,
92        angular_inertia: 0.0,
93        center_of_mass: Vec2::ZERO,
94    };
95
96    /// Creates a new [`MassProperties2d`] from a given mass, principal angular inertia,
97    /// and center of mass in local space.
98    #[inline]
99    pub fn new(mass: f32, angular_inertia: f32, center_of_mass: Vec2) -> Self {
100        Self {
101            mass,
102            angular_inertia,
103            center_of_mass,
104        }
105    }
106
107    /// Computes approximate mass properties from the given set of points representing a shape.
108    ///
109    /// This can be used to estimate mass properties for arbitrary shapes
110    /// by providing a set of sample points from inside the shape.
111    ///
112    /// The more points there are, and the more uniformly distributed they are,
113    /// the more accurate the estimation will be.
114    #[inline]
115    pub fn from_point_cloud(points: &[Vec2], mass: f32) -> Self {
116        let points_recip = 1.0 / points.len() as f64;
117
118        let center_of_mass =
119            (points.iter().fold(DVec2::ZERO, |acc, p| acc + p.as_dvec2()) * points_recip).as_vec2();
120        let unit_angular_inertia = points.iter().fold(0.0, |acc, p| {
121            let r = p.distance_squared(center_of_mass) as f64;
122            acc + r * points_recip
123        }) as f32;
124
125        Self::new(mass, mass * unit_angular_inertia, center_of_mass)
126    }
127
128    /// Returns the center of mass transformed into global space using the given [isometry].
129    ///
130    /// [isometry]: Isometry2d
131    #[inline]
132    pub fn global_center_of_mass(&self, isometry: impl Into<Isometry2d>) -> Vec2 {
133        let isometry: Isometry2d = isometry.into();
134        isometry.transform_point(self.center_of_mass)
135    }
136
137    /// Computes the angular inertia corresponding to a mass of `1.0`.
138    ///
139    /// If the mass is zero, zero is returned.
140    #[inline]
141    pub fn unit_angular_inertia(&self) -> f32 {
142        self.mass.recip_or_zero() * self.angular_inertia
143    }
144
145    /// Computes the principal angular inertia at a given `offset`.
146    ///
147    /// The shifted angular inertia is computed as `angular_inertia + mass * offset.length_squared()`.
148    #[inline]
149    pub fn shifted_angular_inertia(&self, offset: Vec2) -> f32 {
150        self.angular_inertia + offset.length_squared() * self.mass
151    }
152
153    /// Returns the mass properties transformed by the given [isometry].
154    ///
155    /// [isometry]: Isometry2d
156    #[inline]
157    pub fn transformed_by(mut self, isometry: impl Into<Isometry2d>) -> Self {
158        self.transform_by(isometry);
159        self
160    }
161
162    /// Transforms the mass properties by the given [isometry].
163    ///
164    /// [isometry]: Isometry2d
165    #[inline]
166    pub fn transform_by(&mut self, isometry: impl Into<Isometry2d>) {
167        self.center_of_mass = self.global_center_of_mass(isometry);
168    }
169
170    /// Returns the mass propeorties with the inverse of mass and angular inertia.
171    ///
172    /// The center of mass is left unchanged.
173    #[inline]
174    pub fn inverse(&self) -> Self {
175        Self {
176            mass: self.mass.recip_or_zero(),
177            angular_inertia: self.angular_inertia.recip_or_zero(),
178            center_of_mass: self.center_of_mass,
179        }
180    }
181
182    /// Sets the mass to the given `new_mass`.
183    ///
184    /// If `update_angular_inertia` is `true`, the angular inertia will be scaled accordingly.
185    #[inline]
186    pub fn set_mass(&mut self, new_mass: f32, update_angular_inertia: bool) {
187        if update_angular_inertia {
188            // Adjust angular inertia to match the new mass.
189            self.angular_inertia *= new_mass * self.mass.recip_or_zero();
190        }
191        self.mass = new_mass;
192    }
193}
194
195impl core::ops::Add for MassProperties2d {
196    type Output = Self;
197
198    #[inline]
199    fn add(self, other: Self) -> Self::Output {
200        if self == Self::ZERO {
201            return other;
202        } else if other == Self::ZERO {
203            return self;
204        }
205
206        let mass1 = self.mass;
207        let mass2 = other.mass;
208        let new_mass = mass1 + mass2;
209
210        // The new center of mass is the weighted average of the centers of masses of `self` and `other`.
211        let new_center_of_mass =
212            (self.center_of_mass * mass1 + other.center_of_mass * mass2) * new_mass.recip_or_zero();
213
214        // Compute the new principal angular inertia, taking the new center of mass into account.
215        let i1 = self.shifted_angular_inertia(new_center_of_mass - self.center_of_mass);
216        let i2 = other.shifted_angular_inertia(new_center_of_mass - other.center_of_mass);
217        let new_angular_inertia = i1 + i2;
218
219        Self {
220            mass: new_mass,
221            angular_inertia: new_angular_inertia,
222            center_of_mass: new_center_of_mass,
223        }
224    }
225}
226
227impl core::ops::AddAssign for MassProperties2d {
228    #[inline]
229    fn add_assign(&mut self, other: Self) {
230        *self = *self + other;
231    }
232}
233
234impl core::ops::Sub for MassProperties2d {
235    type Output = Self;
236
237    #[inline]
238    fn sub(self, other: Self) -> Self::Output {
239        if self == Self::ZERO || other == Self::ZERO {
240            return self;
241        }
242
243        let mass1 = self.mass;
244        let mass2 = other.mass;
245
246        if mass1 <= mass2 {
247            // The result would have non-positive mass.
248            return Self {
249                center_of_mass: self.center_of_mass,
250                ..Self::ZERO
251            };
252        }
253
254        let new_mass = mass1 - mass2;
255
256        // The new center of mass is the negated weighted average of the centers of masses of `self` and `other`.
257        let new_center_of_mass =
258            (self.center_of_mass * mass1 - other.center_of_mass * mass2) * new_mass.recip_or_zero();
259
260        // Compute the new principal angular inertia, taking the new center of mass into account.
261        let i1 = self.shifted_angular_inertia(new_center_of_mass - self.center_of_mass);
262        let i2 = other.shifted_angular_inertia(new_center_of_mass - other.center_of_mass);
263        let new_angular_inertia = (i1 - i2).max(0.0);
264
265        Self {
266            mass: new_mass,
267            angular_inertia: new_angular_inertia,
268            center_of_mass: new_center_of_mass,
269        }
270    }
271}
272
273impl core::ops::SubAssign for MassProperties2d {
274    #[inline]
275    fn sub_assign(&mut self, other: Self) {
276        *self = *self - other;
277    }
278}
279
280impl core::iter::Sum for MassProperties2d {
281    #[inline]
282    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
283        let mut total_mass = 0.0;
284        let mut total_angular_inertia = 0.0;
285        let mut total_center_of_mass = Vec2::ZERO;
286
287        // TODO: Avoid this allocation if possible. This is currently needed because we iterate twice.
288        let mut all_properties = Vec::with_capacity(iter.size_hint().1.unwrap_or_default());
289
290        for props in iter {
291            total_mass += props.mass;
292            total_center_of_mass += props.center_of_mass * props.mass;
293            all_properties.push(props);
294        }
295
296        if total_mass > 0.0 {
297            total_center_of_mass *= total_mass.recip_or_zero();
298        }
299
300        for props in all_properties {
301            total_angular_inertia +=
302                props.shifted_angular_inertia(total_center_of_mass - props.center_of_mass);
303        }
304
305        if !total_center_of_mass.is_finite() {
306            // The center of mass can be non-finite if the mass is non-finite.
307            total_center_of_mass = Vec2::ZERO;
308        }
309
310        Self::new(total_mass, total_angular_inertia, total_center_of_mass)
311    }
312}
313
314#[cfg(any(feature = "approx", test))]
315impl approx::AbsDiffEq for MassProperties2d {
316    type Epsilon = f32;
317    fn default_epsilon() -> f32 {
318        f32::EPSILON
319    }
320    fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
321        self.mass.abs_diff_eq(&other.mass, epsilon)
322            && self
323                .angular_inertia
324                .abs_diff_eq(&other.angular_inertia, epsilon)
325            && self
326                .center_of_mass
327                .abs_diff_eq(other.center_of_mass, epsilon)
328    }
329}
330
331#[cfg(any(feature = "approx", test))]
332impl approx::RelativeEq for MassProperties2d {
333    fn default_max_relative() -> f32 {
334        f32::EPSILON
335    }
336    fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
337        self.mass.relative_eq(&other.mass, epsilon, max_relative)
338            && self
339                .angular_inertia
340                .relative_eq(&other.angular_inertia, epsilon, max_relative)
341            && self
342                .center_of_mass
343                .relative_eq(&other.center_of_mass, epsilon, max_relative)
344    }
345}
346
347#[cfg(any(feature = "approx", test))]
348impl approx::UlpsEq for MassProperties2d {
349    fn default_max_ulps() -> u32 {
350        4
351    }
352    fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
353        self.mass.ulps_eq(&other.mass, epsilon, max_ulps)
354            && self
355                .angular_inertia
356                .ulps_eq(&other.angular_inertia, epsilon, max_ulps)
357            && self
358                .center_of_mass
359                .ulps_eq(&other.center_of_mass, epsilon, max_ulps)
360    }
361}
362
363// TODO: Tests