1use super::{ComputeMassProperties2d, MassProperties2d};
2use bevy_math::{
3 ops,
4 primitives::{
5 Annulus, Arc2d, BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, CircularSector,
6 CircularSegment, Ellipse, Line2d, Measured2d, Plane2d, Polygon, Polyline2d, Rectangle,
7 RegularPolygon, Rhombus, Segment2d, Triangle2d,
8 },
9 FloatPow, Vec2,
10};
11
12impl ComputeMassProperties2d for Circle {
13 #[inline]
14 fn mass(&self, density: f32) -> f32 {
15 self.area() * density
16 }
17
18 #[inline]
19 fn unit_angular_inertia(&self) -> f32 {
20 self.radius.squared() / 2.0
21 }
22
23 #[inline]
24 fn center_of_mass(&self) -> Vec2 {
25 Vec2::ZERO
26 }
27}
28
29impl ComputeMassProperties2d for CircularSector {
30 #[inline]
31 fn mass(&self, density: f32) -> f32 {
32 self.area() * density
33 }
34
35 #[inline]
36 fn unit_angular_inertia(&self) -> f32 {
37 0.5 * ops::powf(self.radius(), 4.0) * self.angle()
38 }
39
40 #[inline]
41 fn center_of_mass(&self) -> Vec2 {
42 let angle = self.angle();
43 let y = 2.0 * self.radius() * ops::sin(angle) / (3.0 * angle);
44 Vec2::new(0.0, y)
45 }
46}
47
48impl ComputeMassProperties2d for CircularSegment {
49 #[inline]
50 fn mass(&self, density: f32) -> f32 {
51 self.area() * density
52 }
53
54 #[inline]
55 fn unit_angular_inertia(&self) -> f32 {
56 let angle = self.angle();
57 let (sin, cos) = ops::sin_cos(angle);
58 ops::powf(self.radius(), 4.0) / 4.0 * (angle - sin + 2.0 / 3.0 * sin * (1.0 - cos) / 2.0)
59 }
60
61 #[inline]
62 fn center_of_mass(&self) -> Vec2 {
63 let y = self.radius() * ops::sin(self.half_angle()).cubed()
64 / (6.0 * self.half_angle() - ops::sin(self.angle()));
65 Vec2::new(0.0, y)
66 }
67}
68
69impl ComputeMassProperties2d for Ellipse {
70 #[inline]
71 fn mass(&self, density: f32) -> f32 {
72 self.area() * density
73 }
74
75 #[inline]
76 fn unit_angular_inertia(&self) -> f32 {
77 self.half_size.length_squared() / 4.0
78 }
79
80 #[inline]
81 fn center_of_mass(&self) -> Vec2 {
82 Vec2::ZERO
83 }
84}
85
86impl ComputeMassProperties2d for Annulus {
87 #[inline]
88 fn mass(&self, density: f32) -> f32 {
89 self.area() * density
90 }
91
92 #[inline]
93 fn unit_angular_inertia(&self) -> f32 {
94 0.5 * (self.outer_circle.radius.squared() + self.inner_circle.radius.squared())
95 }
96
97 #[inline]
98 fn center_of_mass(&self) -> Vec2 {
99 Vec2::ZERO
100 }
101}
102
103impl ComputeMassProperties2d for Triangle2d {
104 #[inline]
105 fn mass(&self, density: f32) -> f32 {
106 self.area() * density
107 }
108
109 #[inline]
110 fn unit_angular_inertia(&self) -> f32 {
111 let center_of_mass = self.center_of_mass();
115 let com_a = self.vertices[1] - center_of_mass;
116 let com_c = self.vertices[2] - center_of_mass;
117
118 (com_a.length_squared() + com_a.dot(com_c) + com_c.length_squared()) / 6.0
119 }
120
121 #[inline]
122 fn center_of_mass(&self) -> Vec2 {
123 (self.vertices[0] + self.vertices[1] + self.vertices[2]) / 3.0
124 }
125
126 #[inline]
127 fn mass_properties(&self, density: f32) -> MassProperties2d {
128 let area = self.area();
129 let center_of_mass = self.center_of_mass();
130
131 if area < f32::EPSILON {
132 return MassProperties2d::new(0.0, 0.0, center_of_mass);
133 }
134
135 let mass = area * density;
136
137 MassProperties2d::new(mass, self.angular_inertia(mass), center_of_mass)
138 }
139}
140
141impl ComputeMassProperties2d for Rectangle {
142 #[inline]
143 fn mass(&self, density: f32) -> f32 {
144 self.area() * density
145 }
146
147 #[inline]
148 fn unit_angular_inertia(&self) -> f32 {
149 self.half_size.length_squared() / 3.0
150 }
151
152 #[inline]
153 fn center_of_mass(&self) -> Vec2 {
154 Vec2::ZERO
155 }
156}
157
158impl ComputeMassProperties2d for Rhombus {
159 #[inline]
160 fn mass(&self, density: f32) -> f32 {
161 self.area() * density
162 }
163
164 #[inline]
165 fn unit_angular_inertia(&self) -> f32 {
166 self.half_diagonals.length_squared() / 12.0
167 }
168
169 #[inline]
170 fn center_of_mass(&self) -> Vec2 {
171 Vec2::ZERO
172 }
173}
174
175impl ComputeMassProperties2d for RegularPolygon {
176 #[inline]
177 fn mass(&self, density: f32) -> f32 {
178 self.area() * density
179 }
180
181 #[inline]
182 fn unit_angular_inertia(&self) -> f32 {
183 let half_external_angle = core::f32::consts::PI / self.sides as f32;
184 self.circumradius().squared() / 6.0 * (1.0 + 2.0 * ops::cos(half_external_angle).squared())
185 }
186
187 #[inline]
188 fn center_of_mass(&self) -> Vec2 {
189 Vec2::ZERO
190 }
191}
192
193impl ComputeMassProperties2d for Capsule2d {
194 #[inline]
195 fn mass(&self, density: f32) -> f32 {
196 let area = self.radius * (core::f32::consts::PI * self.radius + 4.0 * self.half_length);
197 area * density
198 }
199
200 #[inline]
201 fn unit_angular_inertia(&self) -> f32 {
202 let rectangle = Rectangle {
204 half_size: Vec2::new(self.radius, self.half_length),
205 };
206 let rectangle_height = rectangle.half_size.y * 2.0;
207 let circle = Circle::new(self.radius);
208
209 let rectangle_area = rectangle.area();
211 let circle_area = circle.area();
212
213 let density = 1.0 / (rectangle_area + circle_area);
215 let rectangle_mass = rectangle_area * density;
216 let circle_mass = circle_area * density;
217
218 let rectangle_inertia = rectangle.angular_inertia(rectangle_mass);
220 let circle_inertia = circle.angular_inertia(circle_mass);
221
222 let mut capsule_inertia = rectangle_inertia + circle_inertia;
224
225 capsule_inertia += (rectangle_height.squared() * 0.25
227 + rectangle_height * self.radius * 3.0 / 8.0)
228 * circle_mass;
229
230 capsule_inertia
231 }
232
233 #[inline]
234 fn center_of_mass(&self) -> Vec2 {
235 Vec2::ZERO
236 }
237
238 #[inline]
239 fn mass_properties(&self, density: f32) -> MassProperties2d {
240 let rectangle = Rectangle {
242 half_size: Vec2::new(self.radius, self.half_length),
243 };
244 let rectangle_height = rectangle.half_size.y * 2.0;
245 let circle = Circle::new(self.radius);
246
247 let rectangle_area = rectangle.area();
249 let circle_area = circle.area();
250
251 let rectangle_mass = rectangle_area * density;
253 let circle_mass = circle_area * density;
254
255 let rectangle_inertia = rectangle.angular_inertia(rectangle_mass);
257 let circle_inertia = circle.angular_inertia(circle_mass);
258
259 let mut capsule_inertia = rectangle_inertia + circle_inertia;
261
262 capsule_inertia += (rectangle_height.squared() * 0.25
264 + rectangle_height * self.radius * 3.0 / 8.0)
265 * circle_mass;
266
267 MassProperties2d::new(rectangle_mass + circle_mass, capsule_inertia, Vec2::ZERO)
268 }
269}
270
271impl<const N: usize> ComputeMassProperties2d for Polygon<N> {
272 #[inline]
273 fn mass(&self, density: f32) -> f32 {
274 let area = convex_polygon_area(&self.vertices);
276 area * density
277 }
278
279 #[inline]
280 fn unit_angular_inertia(&self) -> f32 {
281 convex_polygon_unit_angular_inertia(&self.vertices)
282 }
283
284 #[inline]
285 fn center_of_mass(&self) -> Vec2 {
286 convex_polygon_area_and_com(&self.vertices).1
287 }
288
289 #[inline]
290 fn mass_properties(&self, density: f32) -> MassProperties2d {
291 convex_polygon_mass_properties(&self.vertices, density)
292 }
293}
294
295impl ComputeMassProperties2d for BoxedPolygon {
296 #[inline]
297 fn mass(&self, density: f32) -> f32 {
298 let area = convex_polygon_area(&self.vertices);
300 area * density
301 }
302
303 #[inline]
304 fn unit_angular_inertia(&self) -> f32 {
305 convex_polygon_unit_angular_inertia(&self.vertices)
306 }
307
308 #[inline]
309 fn center_of_mass(&self) -> Vec2 {
310 convex_polygon_area_and_com(&self.vertices).1
311 }
312
313 #[inline]
314 fn mass_properties(&self, density: f32) -> MassProperties2d {
315 convex_polygon_mass_properties(&self.vertices, density)
316 }
317}
318
319#[inline]
320fn convex_polygon_mass_properties(vertices: &[Vec2], density: f32) -> MassProperties2d {
321 let (area, center_of_mass) = convex_polygon_area_and_com(vertices);
323
324 if area < f32::EPSILON {
325 return MassProperties2d::new(0.0, 0.0, center_of_mass);
326 }
327
328 let mut inertia = 0.0;
330
331 let mut iter = vertices.iter().peekable();
333 let first = **iter.peek().unwrap();
334
335 while let Some(vertex) = iter.next() {
338 let triangle = Triangle2d::new(
339 *vertex,
340 iter.peek().copied().copied().unwrap_or(first),
341 center_of_mass,
342 );
343 inertia += triangle.unit_angular_inertia() * triangle.area();
344 }
345
346 MassProperties2d::new(area * density, inertia * density, center_of_mass)
347}
348
349#[inline]
350fn convex_polygon_unit_angular_inertia(vertices: &[Vec2]) -> f32 {
351 let (area, center_of_mass) = convex_polygon_area_and_com(vertices);
353
354 if area < f32::EPSILON {
355 return 0.0;
356 }
357
358 let mut inertia = 0.0;
360
361 let mut iter = vertices.iter().peekable();
363 let first = **iter.peek().unwrap();
364
365 while let Some(vertex) = iter.next() {
368 let triangle = Triangle2d::new(
369 *vertex,
370 iter.peek().copied().copied().unwrap_or(first),
371 center_of_mass,
372 );
373 inertia += triangle.unit_angular_inertia() * triangle.area();
374 }
375
376 inertia / area
377}
378
379#[inline]
380fn convex_polygon_area(vertices: &[Vec2]) -> f32 {
381 let geometric_center =
382 vertices.iter().fold(Vec2::ZERO, |acc, vtx| acc + *vtx) / vertices.len() as f32;
383
384 let mut area = 0.0;
386
387 let mut iter = vertices.iter().peekable();
389 let Some(first) = iter.peek().copied().copied() else {
390 return 0.0;
391 };
392
393 while let Some(vertex) = iter.next() {
396 let (a, b, c) = (
397 *vertex,
398 iter.peek().copied().copied().unwrap_or(first),
399 geometric_center,
400 );
401 let tri_area = Triangle2d::new(a, b, c).area();
402
403 area += tri_area;
404 }
405
406 area
407}
408
409#[inline]
410fn convex_polygon_area_and_com(vertices: &[Vec2]) -> (f32, Vec2) {
411 let geometric_center =
412 vertices.iter().fold(Vec2::ZERO, |acc, vtx| acc + *vtx) / vertices.len() as f32;
413
414 let mut area = 0.0;
416 let mut center = Vec2::ZERO;
417
418 let mut iter = vertices.iter().peekable();
420 let Some(first) = iter.peek().copied().copied() else {
421 return (0.0, Vec2::ZERO);
422 };
423
424 while let Some(vertex) = iter.next() {
427 let (a, b, c) = (
428 *vertex,
429 iter.peek().copied().copied().unwrap_or(first),
430 geometric_center,
431 );
432 let tri_area = Triangle2d::new(a, b, c).area();
433 let tri_center = (a + b + c) / 3.0;
434
435 area += tri_area;
436 center += tri_center * tri_area;
437 }
438
439 if area < f32::EPSILON {
440 (area, geometric_center)
441 } else {
442 (area, center / area)
443 }
444}
445
446macro_rules! impl_zero_mass_properties_2d {
447 ($($shape:ty),*) => {
448 $(
449 impl ComputeMassProperties2d for $shape {
450 #[inline]
451 fn mass(&self, _density: f32) -> f32 {
452 0.0
453 }
454
455 #[inline]
456 fn unit_angular_inertia(&self) -> f32 {
457 0.0
458 }
459
460 #[inline]
461 fn angular_inertia(&self, _mass: f32) -> f32 {
462 0.0
463 }
464
465 #[inline]
466 fn center_of_mass(&self) -> Vec2 {
467 Vec2::ZERO
468 }
469
470 #[inline]
471 fn mass_properties(&self, _density: f32) -> MassProperties2d {
472 MassProperties2d::ZERO
473 }
474 }
475 )*
476 };
477}
478
479impl_zero_mass_properties_2d!(Arc2d);
480impl_zero_mass_properties_2d!(Plane2d);
481impl_zero_mass_properties_2d!(Line2d);
482impl_zero_mass_properties_2d!(Segment2d);
483impl_zero_mass_properties_2d!(BoxedPolyline2d);
484
485impl<const N: usize> ComputeMassProperties2d for Polyline2d<N> {
486 #[inline]
487 fn mass(&self, _density: f32) -> f32 {
488 0.0
489 }
490
491 #[inline]
492 fn unit_angular_inertia(&self) -> f32 {
493 0.0
494 }
495
496 #[inline]
497 fn angular_inertia(&self, _mass: f32) -> f32 {
498 0.0
499 }
500
501 #[inline]
502 fn center_of_mass(&self) -> Vec2 {
503 Vec2::ZERO
504 }
505
506 #[inline]
507 fn mass_properties(&self, _density: f32) -> MassProperties2d {
508 MassProperties2d::ZERO
509 }
510}
511
512#[cfg(test)]
513mod tests {
514 use alloc::vec::Vec;
515
516 use approx::assert_relative_eq;
517 use bevy_math::ShapeSample;
518 use rand::SeedableRng;
519
520 use super::*;
521
522 macro_rules! test_shape {
523 ($test_name:tt, $shape:expr) => {
524 #[test]
525 fn $test_name() {
526 let shape = $shape;
527
528 let mut rng = rand_chacha::ChaCha8Rng::from_seed(Default::default());
530 let points = (0..1_000_000)
531 .map(|_| shape.sample_interior(&mut rng))
532 .collect::<Vec<_>>();
533
534 let density = 2.0;
536 let mass = shape.mass(density);
537 let angular_inertia = shape.angular_inertia(mass);
538 let center_of_mass = shape.center_of_mass();
539
540 let mass_props = shape.mass_properties(density);
542 assert_relative_eq!(mass, mass_props.mass);
543 assert_relative_eq!(angular_inertia, mass_props.angular_inertia);
544 assert_relative_eq!(center_of_mass, mass_props.center_of_mass);
545
546 let expected = MassProperties2d::from_point_cloud(&points, mass);
550
551 assert_relative_eq!(mass, expected.mass);
552 assert_relative_eq!(angular_inertia, expected.angular_inertia, epsilon = 0.1);
553 assert_relative_eq!(center_of_mass, expected.center_of_mass, epsilon = 0.01);
554 }
555 };
556 }
557
558 test_shape!(circle, Circle::new(2.0));
561 test_shape!(annulus, Annulus::new(1.0, 2.0));
565 test_shape!(
566 triangle,
567 Triangle2d::new(
568 Vec2::new(8.0, 6.0),
569 Vec2::new(2.0, 0.0),
570 Vec2::new(6.0, 2.0)
571 )
572 );
573 test_shape!(rectangle, Rectangle::new(2.0, 1.0));
574 test_shape!(capsule, Capsule2d::new(1.0, 0.25));
578}