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
9mod impls;
11
12pub trait ComputeMassProperties2d {
16 fn mass(&self, density: f32) -> f32;
20
21 #[doc(alias = "unit_moment_of_inertia")]
25 fn unit_angular_inertia(&self) -> f32;
26
27 #[inline]
33 #[doc(alias = "moment_of_inertia")]
34 fn angular_inertia(&self, mass: f32) -> f32 {
35 mass * self.unit_angular_inertia()
36 }
37
38 fn center_of_mass(&self) -> Vec2;
42
43 #[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#[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 pub mass: f32,
69 pub angular_inertia: f32,
73 pub center_of_mass: Vec2,
77}
78
79impl Default for MassProperties2d {
80 fn default() -> Self {
82 Self::ZERO
83 }
84}
85
86impl MassProperties2d {
87 pub const ZERO: Self = Self {
89 mass: 0.0,
90 angular_inertia: 0.0,
91 center_of_mass: Vec2::ZERO,
92 };
93
94 #[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 #[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 #[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 #[inline]
139 pub fn unit_angular_inertia(&self) -> f32 {
140 self.mass.recip_or_zero() * self.angular_inertia
141 }
142
143 #[inline]
147 pub fn shifted_angular_inertia(&self, offset: Vec2) -> f32 {
148 self.angular_inertia + offset.length_squared() * self.mass
149 }
150
151 #[inline]
155 pub fn transformed_by(mut self, isometry: impl Into<Isometry2d>) -> Self {
156 self.transform_by(isometry);
157 self
158 }
159
160 #[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 #[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 #[inline]
184 pub fn set_mass(&mut self, new_mass: f32, update_angular_inertia: bool) {
185 if update_angular_inertia {
186 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 let new_center_of_mass =
210 (self.center_of_mass * mass1 + other.center_of_mass * mass2) * new_mass.recip_or_zero();
211
212 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 return Self {
247 center_of_mass: self.center_of_mass,
248 ..Self::ZERO
249 };
250 }
251
252 let new_mass = mass1 - mass2;
253
254 let new_center_of_mass =
256 (self.center_of_mass * mass1 - other.center_of_mass * mass2) * new_mass.recip_or_zero();
257
258 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 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