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