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 use impls::*;
13
14pub trait ComputeMassProperties2d {
18 fn mass(&self, density: f32) -> f32;
22
23 #[doc(alias = "unit_moment_of_inertia")]
27 fn unit_angular_inertia(&self) -> f32;
28
29 #[inline]
35 #[doc(alias = "moment_of_inertia")]
36 fn angular_inertia(&self, mass: f32) -> f32 {
37 mass * self.unit_angular_inertia()
38 }
39
40 fn center_of_mass(&self) -> Vec2;
44
45 #[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#[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 pub mass: f32,
71 pub angular_inertia: f32,
75 pub center_of_mass: Vec2,
79}
80
81impl Default for MassProperties2d {
82 fn default() -> Self {
84 Self::ZERO
85 }
86}
87
88impl MassProperties2d {
89 pub const ZERO: Self = Self {
91 mass: 0.0,
92 angular_inertia: 0.0,
93 center_of_mass: Vec2::ZERO,
94 };
95
96 #[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 #[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 #[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 #[inline]
141 pub fn unit_angular_inertia(&self) -> f32 {
142 self.mass.recip_or_zero() * self.angular_inertia
143 }
144
145 #[inline]
149 pub fn shifted_angular_inertia(&self, offset: Vec2) -> f32 {
150 self.angular_inertia + offset.length_squared() * self.mass
151 }
152
153 #[inline]
157 pub fn transformed_by(mut self, isometry: impl Into<Isometry2d>) -> Self {
158 self.transform_by(isometry);
159 self
160 }
161
162 #[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 #[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 #[inline]
186 pub fn set_mass(&mut self, new_mass: f32, update_angular_inertia: bool) {
187 if update_angular_inertia {
188 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 let new_center_of_mass =
212 (self.center_of_mass * mass1 + other.center_of_mass * mass2) * new_mass.recip_or_zero();
213
214 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 return Self {
249 center_of_mass: self.center_of_mass,
250 ..Self::ZERO
251 };
252 }
253
254 let new_mass = mass1 - mass2;
255
256 let new_center_of_mass =
258 (self.center_of_mass * mass1 - other.center_of_mass * mass2) * new_mass.recip_or_zero();
259
260 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 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 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