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