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