bevy_heavy/dim2/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
use bevy_math::{DVec2, Isometry2d, Vec2};

use crate::RecipOrZero;

/// [`ComputeMassProperties2d`] implementations for 2D geometric primitives.
mod impls;

/// A trait for computing [`MassProperties2d`] for 2D objects.
///
/// For the 3D equivalent, see [`ComputeMassProperties3d`](crate::ComputeMassProperties3d).
pub trait ComputeMassProperties2d {
    /// Computes the [mass] of the object with a given `density`.
    ///
    /// [mass]: crate#mass
    fn mass(&self, density: f32) -> f32;

    /// Computes the [angular inertia] corresponding to a mass of `1.0`.
    ///
    /// [angular inertia]: crate#angular-inertia
    #[doc(alias = "unit_moment_of_inertia")]
    fn unit_angular_inertia(&self) -> f32;

    /// Computes the [angular inertia] corresponding to the given `mass`.
    ///
    /// Equivalent to `mass * shape.unit_angular_inertia()`.
    ///
    /// [angular inertia]: crate#angular-inertia
    #[inline]
    #[doc(alias = "moment_of_inertia")]
    fn angular_inertia(&self, mass: f32) -> f32 {
        mass * self.unit_angular_inertia()
    }

    /// Computes the local [center of mass] relative to the object's origin.
    ///
    /// [center of mass]: crate#center-of-mass
    fn center_of_mass(&self) -> Vec2;

    /// Computes the [`MassProperties2d`] with a given `density`.
    #[inline]
    fn mass_properties(&self, density: f32) -> MassProperties2d {
        let mass = self.mass(density);
        MassProperties2d::new(mass, self.angular_inertia(mass), self.center_of_mass())
    }
}

/// The [mass], [angular inertia], and local [center of mass] of an object in 2D space.
///
/// [mass]: crate#mass
/// [angular inertia]: crate#angular-inertia
/// [center of mass]: crate#center-of-mass
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct MassProperties2d {
    /// The [mass].
    ///
    /// [mass]: crate#mass
    pub mass: f32,
    /// The angular inertia along the principal axis.
    ///
    /// [angular inertia]: crate#angular-inertia
    pub angular_inertia: f32,
    /// The local [center of mass] relative to the object's origin.
    ///
    /// [center of mass]: crate#center-of-mass
    pub center_of_mass: Vec2,
}

impl Default for MassProperties2d {
    /// Returns the default [`MassProperties2d`], with zero mass and angular inertia.
    fn default() -> Self {
        Self::ZERO
    }
}

impl MassProperties2d {
    /// Zero mass and angular inertia.
    pub const ZERO: Self = Self {
        mass: 0.0,
        angular_inertia: 0.0,
        center_of_mass: Vec2::ZERO,
    };

    /// Creates a new [`MassProperties2d`] from a given mass, principal angular inertia,
    /// and center of mass in local space.
    #[inline]
    pub fn new(mass: f32, angular_inertia: f32, center_of_mass: Vec2) -> Self {
        Self {
            mass,
            angular_inertia,
            center_of_mass,
        }
    }

    /// Computes approximate mass properties from the given set of points representing a shape.
    ///
    /// This can be used to estimate mass properties for arbitrary shapes
    /// by providing a set of sample points from inside the shape.
    ///
    /// The more points there are, and the more uniformly distributed they are,
    /// the more accurate the estimation will be.
    #[inline]
    pub fn from_point_cloud(points: &[Vec2], mass: f32) -> Self {
        let points_recip = 1.0 / points.len() as f64;

        let center_of_mass =
            (points.iter().fold(DVec2::ZERO, |acc, p| acc + p.as_dvec2()) * points_recip).as_vec2();
        let unit_angular_inertia = points.iter().fold(0.0, |acc, p| {
            let r = p.distance_squared(center_of_mass) as f64;
            acc + r * points_recip
        }) as f32;

        Self::new(mass, mass * unit_angular_inertia, center_of_mass)
    }

    /// Returns the center of mass transformed into global space using the given [isometry].
    ///
    /// [isometry]: Isometry2d
    #[inline]
    pub fn global_center_of_mass(&self, isometry: impl Into<Isometry2d>) -> Vec2 {
        let isometry: Isometry2d = isometry.into();
        isometry.transform_point(self.center_of_mass)
    }

    /// Computes the angular inertia corresponding to a mass of `1.0`.
    ///
    /// If the mass is zero, zero is returned.
    #[inline]
    pub fn unit_angular_inertia(&self) -> f32 {
        self.mass.recip_or_zero() * self.angular_inertia
    }

    /// Computes the principal angular inertia at a given `offset`.
    ///
    /// The shifted angular inertia is computed as `angular_inertia + mass * offset.length_squared()`.
    #[inline]
    pub fn shifted_angular_inertia(&self, offset: Vec2) -> f32 {
        self.angular_inertia + offset.length_squared() * self.mass
    }

    /// Returns the mass properties transformed by the given [isometry].
    ///
    /// [isometry]: Isometry2d
    #[inline]
    pub fn transformed_by(mut self, isometry: impl Into<Isometry2d>) -> Self {
        self.transform_by(isometry);
        self
    }

    /// Transforms the mass properties by the given [isometry].
    ///
    /// [isometry]: Isometry2d
    #[inline]
    pub fn transform_by(&mut self, isometry: impl Into<Isometry2d>) {
        self.center_of_mass = self.global_center_of_mass(isometry);
    }

    /// Returns the mass propeorties with the inverse of mass and angular inertia.
    ///
    /// The center of mass is left unchanged.
    #[inline]
    pub fn inverse(&self) -> Self {
        Self {
            mass: self.mass.recip_or_zero(),
            angular_inertia: self.angular_inertia.recip_or_zero(),
            center_of_mass: self.center_of_mass,
        }
    }

    /// Sets the mass to the given `new_mass`.
    ///
    /// If `update_angular_inertia` is `true`, the angular inertia will be scaled accordingly.
    #[inline]
    pub fn set_mass(&mut self, new_mass: f32, update_angular_inertia: bool) {
        if update_angular_inertia {
            // Adjust angular inertia to match the new mass.
            self.angular_inertia *= new_mass * self.mass.recip_or_zero();
        }
        self.mass = new_mass;
    }
}

impl std::ops::Add for MassProperties2d {
    type Output = Self;

    #[inline]
    fn add(self, other: Self) -> Self::Output {
        if self == Self::ZERO {
            return other;
        } else if other == Self::ZERO {
            return self;
        }

        let mass1 = self.mass;
        let mass2 = other.mass;
        let new_mass = mass1 + mass2;

        // The new center of mass is the weighted average of the centers of masses of `self` and `other`.
        let new_center_of_mass =
            (self.center_of_mass * mass1 + other.center_of_mass * mass2) * new_mass.recip_or_zero();

        // Compute the new principal angular inertia, taking the new center of mass into account.
        let i1 = self.shifted_angular_inertia(new_center_of_mass - self.center_of_mass);
        let i2 = other.shifted_angular_inertia(new_center_of_mass - other.center_of_mass);
        let new_angular_inertia = i1 + i2;

        Self {
            mass: new_mass,
            angular_inertia: new_angular_inertia,
            center_of_mass: new_center_of_mass,
        }
    }
}

impl std::ops::AddAssign for MassProperties2d {
    #[inline]
    fn add_assign(&mut self, other: Self) {
        *self = *self + other;
    }
}

impl std::ops::Sub for MassProperties2d {
    type Output = Self;

    #[inline]
    fn sub(self, other: Self) -> Self::Output {
        if self == Self::ZERO || other == Self::ZERO {
            return self;
        }

        let mass1 = self.mass;
        let mass2 = other.mass;

        if mass1 <= mass2 {
            // The result would have non-positive mass.
            return Self {
                center_of_mass: self.center_of_mass,
                ..Self::ZERO
            };
        }

        let new_mass = mass1 - mass2;

        // The new center of mass is the negated weighted average of the centers of masses of `self` and `other`.
        let new_center_of_mass =
            (self.center_of_mass * mass1 - other.center_of_mass * mass2) * new_mass.recip_or_zero();

        // Compute the new principal angular inertia, taking the new center of mass into account.
        let i1 = self.shifted_angular_inertia(new_center_of_mass - self.center_of_mass);
        let i2 = other.shifted_angular_inertia(new_center_of_mass - other.center_of_mass);
        let new_angular_inertia = (i1 - i2).max(0.0);

        Self {
            mass: new_mass,
            angular_inertia: new_angular_inertia,
            center_of_mass: new_center_of_mass,
        }
    }
}

impl std::ops::SubAssign for MassProperties2d {
    #[inline]
    fn sub_assign(&mut self, other: Self) {
        *self = *self - other;
    }
}

impl std::iter::Sum for MassProperties2d {
    #[inline]
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        let mut total_mass = 0.0;
        let mut total_angular_inertia = 0.0;
        let mut total_center_of_mass = Vec2::ZERO;

        // TODO: Avoid this allocation if possible. This is currently needed because we iterate twice.
        let mut all_properties = Vec::with_capacity(iter.size_hint().1.unwrap_or_default());

        for props in iter {
            total_mass += props.mass;
            total_center_of_mass += props.center_of_mass * props.mass;
            all_properties.push(props);
        }

        if total_mass > 0.0 {
            total_center_of_mass /= total_mass;
        }

        for props in all_properties {
            total_angular_inertia +=
                props.shifted_angular_inertia(total_center_of_mass - props.center_of_mass);
        }

        Self::new(total_mass, total_angular_inertia, total_center_of_mass)
    }
}

#[cfg(any(feature = "approx", test))]
impl approx::AbsDiffEq for MassProperties2d {
    type Epsilon = f32;
    fn default_epsilon() -> f32 {
        f32::EPSILON
    }
    fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
        self.mass.abs_diff_eq(&other.mass, epsilon)
            && self
                .angular_inertia
                .abs_diff_eq(&other.angular_inertia, epsilon)
            && self
                .center_of_mass
                .abs_diff_eq(other.center_of_mass, epsilon)
    }
}

#[cfg(any(feature = "approx", test))]
impl approx::RelativeEq for MassProperties2d {
    fn default_max_relative() -> f32 {
        f32::EPSILON
    }
    fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
        self.mass.relative_eq(&other.mass, epsilon, max_relative)
            && self
                .angular_inertia
                .relative_eq(&other.angular_inertia, epsilon, max_relative)
            && self
                .center_of_mass
                .relative_eq(&other.center_of_mass, epsilon, max_relative)
    }
}

#[cfg(any(feature = "approx", test))]
impl approx::UlpsEq for MassProperties2d {
    fn default_max_ulps() -> u32 {
        4
    }
    fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
        self.mass.ulps_eq(&other.mass, epsilon, max_ulps)
            && self
                .angular_inertia
                .ulps_eq(&other.angular_inertia, epsilon, max_ulps)
            && self
                .center_of_mass
                .ulps_eq(&other.center_of_mass, epsilon, max_ulps)
    }
}

// TODO: Tests