1extern crate nalgebra as na;
16use math::{is_near_zero, vec3, Isometry3, Mat3, Mat3Ext, Real, UnitQuat, Vec3};
17pub use phys_geom::shape;
18use phys_geom::shape::{Capsule, Cuboid, Cylinder, InfinitePlane, Sphere, Tetrahedron};
19use transformer::InertiaTensorTransformer;
20
21use crate::math::UnitQuatExt;
22
23pub mod math;
24pub mod transformer;
25
26#[derive(Clone)]
28pub struct Inertia {
29 pub diag_inertia: Vec3,
31 pub inertia_rotation: Option<UnitQuat>,
33 pub center_of_mass: Option<Vec3>,
35}
36
37impl Default for Inertia {
38 fn default() -> Self {
39 Inertia {
40 diag_inertia: Vec3::repeat(1.0),
41 inertia_rotation: None,
42 center_of_mass: None,
43 }
44 }
45}
46
47impl Inertia {
48 pub const ZERO: Inertia = Inertia {
50 diag_inertia: Vec3::new(0.0, 0.0, 0.0),
51 inertia_rotation: None,
52 center_of_mass: None,
53 };
54}
55
56pub trait ComputeInertia {
58 fn compute_inertia(&self) -> Inertia;
60}
61
62impl ComputeInertia for Sphere {
63 #[inline]
64 fn compute_inertia(&self) -> Inertia {
65 Inertia {
66 diag_inertia: sphere(self.radius()),
67 ..Default::default()
68 }
69 }
70}
71
72impl ComputeInertia for Cuboid {
73 #[inline]
74 fn compute_inertia(&self) -> Inertia {
75 Inertia {
76 diag_inertia: cube(self.length()),
77 ..Default::default()
78 }
79 }
80}
81
82impl ComputeInertia for Cylinder {
83 #[inline]
84 fn compute_inertia(&self) -> Inertia {
85 Inertia {
86 diag_inertia: cylinder(self.radius(), self.height()),
87 ..Default::default()
88 }
89 }
90}
91
92impl ComputeInertia for Capsule {
93 #[inline]
94 fn compute_inertia(&self) -> Inertia {
95 Inertia {
96 diag_inertia: capsule(self.radius(), self.height()),
97 ..Default::default()
98 }
99 }
100}
101
102impl ComputeInertia for InfinitePlane {
103 #[inline]
104 fn compute_inertia(&self) -> Inertia {
105 Inertia::ZERO
106 }
107}
108
109#[inline]
111pub fn cube(size: impl Into<[Real; 3]>) -> Vec3 {
112 let [a, b, c] = size.into();
113 const M: Real = 1.0 / 12.0;
114 let (a2, b2, c2) = (a * a, b * b, c * c);
115 vec3(b2 + c2, a2 + c2, a2 + b2) * M
116}
117
118#[inline]
120pub fn sphere(radius: Real) -> Vec3 {
121 let a = 2.0 / 5.0 * radius * radius;
122 vec3(a, a, a)
123}
124
125#[inline]
129pub fn cylinder(radius: Real, height: Real) -> Vec3 {
130 const SA: Real = 1.0 / 12.0;
131 const SB: Real = 1.0 / 2.0;
132
133 let a = SA * (3.0 * radius * radius + height * height);
134 let b = SB * radius * radius;
135 vec3(a, b, a)
136}
137
138#[inline]
142pub fn capsule(radius: Real, height: Real) -> Vec3 {
143 const A: Real = 13.0 / 20.0;
144 const B: Real = 7.0 / 12.0;
145 const C: Real = 3.0 / 8.0;
146 const D: Real = 9.0 / 10.0;
147
148 let radius_sq = radius * radius;
149 let height_sq = height * height;
150 let a = A * radius_sq + B * height_sq + C * radius * height;
151 let b = D * radius_sq;
152 vec3(a, b, a)
153}
154
155pub fn tetrahedron(points: [Vec3; 4]) -> Mat3 {
157 let [p1, p2, p3, p4] = points;
158
159 let x1 = p1[0];
161 let y1 = p1[1];
162 let z1 = p1[2];
163 let x2 = p2[0];
164 let y2 = p2[1];
165 let z2 = p2[2];
166 let x3 = p3[0];
167 let y3 = p3[1];
168 let z3 = p3[2];
169 let x4 = p4[0];
170 let y4 = p4[1];
171 let z4 = p4[2];
172
173 let diag_x = x1 * x1
174 + x1 * x2
175 + x2 * x2
176 + x1 * x3
177 + x2 * x3
178 + x3 * x3
179 + x1 * x4
180 + x2 * x4
181 + x3 * x4
182 + x4 * x4;
183 let diag_y = y1 * y1
184 + y1 * y2
185 + y2 * y2
186 + y1 * y3
187 + y2 * y3
188 + y3 * y3
189 + y1 * y4
190 + y2 * y4
191 + y3 * y4
192 + y4 * y4;
193 let diag_z = z1 * z1
194 + z1 * z2
195 + z2 * z2
196 + z1 * z3
197 + z2 * z3
198 + z3 * z3
199 + z1 * z4
200 + z2 * z4
201 + z3 * z4
202 + z4 * z4;
203
204 let a0 = (diag_y + diag_z) * 0.1;
205 let b0 = (diag_z + diag_x) * 0.1;
206 let c0 = (diag_x + diag_y) * 0.1;
207
208 let a1 = (y1 * z1 * 2.0
209 + y2 * z1
210 + y3 * z1
211 + y4 * z1
212 + y1 * z2
213 + y2 * z2 * 2.0
214 + y3 * z2
215 + y4 * z2
216 + y1 * z3
217 + y2 * z3
218 + y3 * z3 * 2.0
219 + y4 * z3
220 + y1 * z4
221 + y2 * z4
222 + y3 * z4
223 + y4 * z4 * 2.0)
224 * 0.05;
225 let b1 = (x1 * z1 * 2.0
226 + x2 * z1
227 + x3 * z1
228 + x4 * z1
229 + x1 * z2
230 + x2 * z2 * 2.0
231 + x3 * z2
232 + x4 * z2
233 + x1 * z3
234 + x2 * z3
235 + x3 * z3 * 2.0
236 + x4 * z3
237 + x1 * z4
238 + x2 * z4
239 + x3 * z4
240 + x4 * z4 * 2.0)
241 * 0.05;
242 let c1 = (x1 * y1 * 2.0
243 + x2 * y1
244 + x3 * y1
245 + x4 * y1
246 + x1 * y2
247 + x2 * y2 * 2.0
248 + x3 * y2
249 + x4 * y2
250 + x1 * y3
251 + x2 * y3
252 + x3 * y3 * 2.0
253 + x4 * y3
254 + x1 * y4
255 + x2 * y4
256 + x3 * y4
257 + x4 * y4 * 2.0)
258 * 0.05;
259
260 Mat3::from_column_slice(&[a0, -b1, -c1, -b1, b0, -a1, -c1, -a1, c0])
261}
262
263pub fn mesh(triangles: impl Iterator<Item = [Vec3; 3]>) -> Inertia {
265 let mut vol = 0.0;
266 let mut itot = Mat3::zeros();
267 let mut com = Vec3::zeros();
268 for triangle in triangles {
269 let a = Vec3::zeros();
270 let [b, c, d] = triangle;
271 let tet = Tetrahedron {
272 a: a.into(),
273 b: b.into(),
274 c: c.into(),
275 d: d.into(),
276 };
277 let volume = tet.signed_volume();
278 let ipart = tetrahedron([a, b, c, d]);
279
280 com += Vec3::from(tet.center()) * volume;
281 itot += ipart * volume;
282 vol += volume;
283 }
284
285 if vol == 0.0 {
286 return Inertia::ZERO;
287 }
288 let inertia = itot * vol.recip();
289 com /= vol;
290
291 let (diag_inertia, inertia_rotation) = inertia.diagonalize();
292 Inertia {
293 diag_inertia,
294 inertia_rotation: if inertia_rotation.is_near_identity() {
295 None
296 } else {
297 inertia_rotation.into()
298 },
299 center_of_mass: if is_near_zero(com, 1e-5) {
300 None
301 } else {
302 com.into()
303 },
304 }
305}
306
307pub struct CompoundUnit<S> {
308 pub object: S,
309 pub pose: Isometry3,
310 pub mass: Real,
311}
312pub fn compound<S>(
328 iter: impl Iterator<Item = CompoundUnit<S>>,
329 zero_mass_as_infinite: bool,
330) -> (Inertia, Real)
331where
332 S: ComputeInertia,
333{
334 let mut compound_computer = InertiaTensorTransformer::new(Vec3::zeros(), Mat3::zeros(), 0.0);
335
336 let mut has_shape = false;
337 for CompoundUnit {
338 object: entity,
339 pose,
340 mass,
341 } in iter
342 {
343 has_shape = true;
344 if mass == 0.0 {
345 if zero_mass_as_infinite {
346 return (Inertia::ZERO, 0.0);
347 } else {
348 continue;
349 }
350 }
351 let inertia = entity.compute_inertia();
352
353 let inertia_matrix = Mat3::from_diagonal(&(inertia.diag_inertia * mass));
355 let mut shape_computer = InertiaTensorTransformer::new(Vec3::zeros(), inertia_matrix, mass);
356 if let Some(rotation) = inertia.inertia_rotation {
357 shape_computer.rotate(rotation);
358 }
359
360 if let Some(translation) = inertia.center_of_mass {
361 shape_computer.translate(translation);
362 }
363
364 shape_computer.transform(&pose);
365 compound_computer.add(&shape_computer);
366 }
367 if !has_shape {
368 return (
369 Inertia {
370 diag_inertia: sphere(0.5),
371 ..Default::default()
372 },
373 1.0,
374 );
375 }
376
377 let compound_com = compound_computer.center_of_mass();
378 compound_computer.center();
379 let (diag, rot) = compound_computer.inertia().diagonalize();
380 let mass = compound_computer.mass();
381 (
382 Inertia {
383 diag_inertia: diag / compound_computer.mass(),
384 inertia_rotation: if rot.is_near_identity() {
385 None
386 } else {
387 Some(rot)
388 },
389 center_of_mass: if is_near_zero(compound_com, 1e-6) {
390 None
391 } else {
392 Some(compound_com)
393 },
394 },
395 mass,
396 )
397}
398
399#[inline]
406pub fn diagonalize_inertia(inertia_matrix: [math::Real; 6]) -> (Vec3, UnitQuat) {
407 let inertia = Mat3::from_column_slice(&[
408 inertia_matrix[0],
409 inertia_matrix[1],
410 inertia_matrix[2],
411 inertia_matrix[1],
412 inertia_matrix[3],
413 inertia_matrix[4],
414 inertia_matrix[2],
415 inertia_matrix[4],
416 inertia_matrix[5],
417 ]);
418 inertia.diagonalize()
419}
420
421#[cfg(test)]
422mod tests {
423 use approx::assert_relative_eq;
424
425 use super::*;
426
427 #[test]
428 fn test_cylinder() {
429 let cylinder = Cylinder::new(1.0, 1.0);
430 let inertia = cylinder.compute_inertia();
431
432 assert_relative_eq!(inertia.diag_inertia, Vec3::new(7.0 / 12.0, 0.5, 7.0 / 12.0));
433 assert_eq!(inertia.center_of_mass, None);
434 assert_eq!(inertia.inertia_rotation, None);
435 }
436
437 #[test]
438 fn test_capsule() {
439 let capsule = Capsule::new(0.5, 0.5);
440 let inertia = capsule.compute_inertia();
441
442 assert_relative_eq!(inertia.diag_inertia, Vec3::new(0.9333333, 0.225, 0.9333333));
443 assert_eq!(inertia.center_of_mass, None);
444 assert_eq!(inertia.inertia_rotation, None);
445 }
446
447 #[test]
448 fn test_cuboid() {
449 let cuboid = Cuboid::new([1.0, 1.0, 1.0]);
450 let inertia = cuboid.compute_inertia();
451
452 assert_relative_eq!(
453 inertia.diag_inertia,
454 Vec3::new(1.0 / 6.0, 1.0 / 6.0, 1.0 / 6.0)
455 );
456 assert_eq!(inertia.center_of_mass, None);
457 assert_eq!(inertia.inertia_rotation, None);
458 }
459
460 #[test]
461 fn test_sphere() {
462 let sphere = Sphere::new(1.0);
463 let inertia = sphere.compute_inertia();
464
465 assert_relative_eq!(
466 inertia.diag_inertia,
467 Vec3::new(2.0 / 5.0, 2.0 / 5.0, 2.0 / 5.0)
468 );
469 assert_eq!(inertia.center_of_mass, None);
470 assert_eq!(inertia.inertia_rotation, None);
471 }
472
473 #[test]
474 fn test_infinite_plane() {
475 let plane = InfinitePlane::default();
476 let inertia = plane.compute_inertia();
477 assert_relative_eq!(inertia.diag_inertia, Inertia::ZERO.diag_inertia);
478 assert_eq!(inertia.center_of_mass, None);
479 assert_eq!(inertia.inertia_rotation, None);
480 }
481
482 #[test]
483 fn test_tetrahedron() {
484 let inertia = super::tetrahedron([
485 vec3(0.0, 0.0, 0.0),
486 vec3(1.0, 0.0, 0.0),
487 vec3(0.0, 1.0, 0.0),
488 vec3(0.0, 0.0, 1.0),
489 ]);
490 assert_relative_eq!(
491 inertia,
492 Mat3::from_column_slice(&[0.2, -0.05, -0.05, -0.05, 0.2, -0.05, -0.05, -0.05, 0.2])
493 );
494
495 let inertia = super::tetrahedron([
496 vec3(1.0, 1.0, 1.0),
497 vec3(-1.0, -1.0, 1.0),
498 vec3(-1.0, 1.0, -1.0),
499 vec3(1.0, -1.0, -1.0),
500 ]);
501
502 assert_relative_eq!(inertia, Mat3::from_diagonal(&vec3(0.4, 0.4, 0.4)));
503 }
504
505 #[test]
506 fn test_mesh() {
507 let com = vec3(0.5, 0.5, 0.5);
508 let cube_corners = [
518 vec3(0.0, 0.0, 0.0) - com,
519 vec3(1.0, 0.0, 0.0) - com,
520 vec3(1.0, 1.0, 0.0) - com,
521 vec3(0.0, 1.0, 0.0) - com,
522 vec3(0.0, 0.0, 1.0) - com,
523 vec3(1.0, 0.0, 1.0) - com,
524 vec3(1.0, 1.0, 1.0) - com,
525 vec3(0.0, 1.0, 1.0) - com,
526 ];
527 let indices = [
528 [0, 1, 2],
529 [0, 2, 3],
530 [4, 6, 5],
531 [4, 7, 6],
532 [0, 4, 1],
533 [1, 4, 5],
534 [2, 6, 3],
535 [3, 6, 7],
536 [0, 3, 7],
537 [0, 7, 4],
538 [5, 6, 2],
539 [5, 2, 1],
540 ];
541 let triangles = indices
542 .iter()
543 .map(|[a, b, c]| [cube_corners[*a], cube_corners[*b], cube_corners[*c]]);
544
545 let inertia = super::mesh(triangles);
546
547 let expected = Cuboid::UNIT.compute_inertia();
548
549 assert_relative_eq!(inertia.diag_inertia, expected.diag_inertia);
550 assert_eq!(inertia.center_of_mass, None);
551 assert_eq!(inertia.inertia_rotation, None);
552 }
553
554 #[test]
555 fn test_zero_volume_mesh() {
556 let triangles = vec![[
557 vec3(0.0, 0.0, 0.0),
558 vec3(1.0, 0.0, 0.0),
559 vec3(0.0, 1.0, 0.0),
560 ]];
561 let inertia = super::mesh(triangles.into_iter());
562 assert_relative_eq!(inertia.diag_inertia, Vec3::zeros());
563 }
564
565 mod compounds {
566 use approx::assert_relative_eq;
567 use phys_geom::shape::{Cuboid, Sphere};
568
569 use super::Real;
570 use crate::math::{vec3, Isometry3, UnitQuat, Vec3};
571 use crate::{CompoundUnit, ComputeInertia, Inertia};
572
573 struct TransformedShapeRefs<'a, S> {
574 shapes: &'a [S],
575 poses: &'a [Isometry3],
576 masses: &'a [Real],
577 }
578
579 impl<'a, S: Clone> TransformedShapeRefs<'a, S> {
580 #[inline]
581 fn iter(&self) -> impl Iterator<Item = CompoundUnit<S>> + '_ {
582 let len = self.shapes.len();
583 (0..len).map(|index| CompoundUnit {
584 object: self.shapes[index].clone(),
585 pose: self.poses[index],
586 mass: self.masses[index],
587 })
588 }
589 }
590
591 pub(crate) fn compound<S: ComputeInertia + Clone>(
592 shapes: &[S],
593 poses: &[Isometry3],
594 masses: &[Real],
595 zero_mass_as_infinite: bool,
596 ) -> (Inertia, Real) {
597 let shapes = TransformedShapeRefs {
598 shapes,
599 poses,
600 masses,
601 };
602 crate::compound(shapes.iter(), zero_mass_as_infinite)
603 }
604
605 #[test]
606 fn test_compound_one_cube() {
607 const EPS: Real = 1e-5;
608
609 let shapes = &[Cuboid::new(Vec3::repeat(1.0))];
610 let poses: &[Isometry3; 1] = &[Isometry3::identity()];
611 let masses = &[1.0];
612 let (
613 Inertia {
614 diag_inertia,
615 center_of_mass,
616 inertia_rotation,
617 },
618 mass,
619 ) = compound(shapes, poses, masses, true);
620
621 assert_eq!(mass, 1.0);
622 assert_eq!(inertia_rotation, None);
623 assert_eq!(center_of_mass, None);
624 assert_relative_eq!(
625 diag_inertia,
626 vec3(0.166_666_67, 0.166_666_67, 0.166_666_67),
627 epsilon = EPS
628 );
629 }
630
631 #[test]
632 fn test_compound_two_cubes_symmetric() {
633 let shapes = &[
636 Cuboid::new_xyz(0.5, 1.0, 1.0),
637 Cuboid::new_xyz(0.5, 1.0, 1.0),
638 ];
639 let poses = &[
640 Isometry3::from_parts(vec3(-0.25, 0., 0.).into(), UnitQuat::identity()),
641 Isometry3::from_parts(vec3(0.25, 0., 0.).into(), UnitQuat::identity()),
642 ];
643 let masses = &[1.0, 1.0];
644 let (
645 Inertia {
646 diag_inertia,
647 inertia_rotation,
648 center_of_mass,
649 },
650 mass,
651 ) = compound(shapes, poses, masses, true);
652
653 assert_eq!(mass, 2.0);
654 assert_relative_eq!(
655 diag_inertia,
656 crate::cube(vec3(1.0, 1.0, 1.0)),
657 epsilon = 1e-5
658 );
659 assert_eq!(inertia_rotation, None);
660 assert_eq!(center_of_mass, None);
661 }
662
663 #[test]
664 fn test_compound_zero_volume_shape() {
665 #[derive(Clone)]
666 struct MockInertia {
667 pub inertia: Inertia,
668 }
669 impl ComputeInertia for MockInertia {
670 fn compute_inertia(&self) -> Inertia {
671 self.inertia.clone()
672 }
673 }
674
675 let inertias = &[
676 MockInertia {
677 inertia: Cuboid::UNIT.compute_inertia(),
678 },
679 MockInertia {
680 inertia: Inertia::ZERO,
681 },
682 ];
683 let poses = &[
684 Isometry3::from_parts(vec3(-1., 1., 0.).into(), UnitQuat::identity()),
685 Isometry3::from_parts(vec3(2., 1., 0.).into(), UnitQuat::identity()),
686 ];
687 let masses = &[1.0, 0.0];
688 let (
689 Inertia {
690 diag_inertia,
691 center_of_mass,
692 inertia_rotation,
693 },
694 mass,
695 ) = compound(inertias, poses, masses, true);
696 assert_eq!(mass, 0.0);
697 assert_eq!(diag_inertia, Vec3::zeros());
698 assert_eq!(center_of_mass, None);
699 assert_eq!(inertia_rotation, None);
700
701 let (Inertia { diag_inertia, .. }, mass) = compound(inertias, poses, masses, false);
702 assert_eq!(mass, 1.0);
703 assert_relative_eq!(
704 diag_inertia,
705 inertias[0].inertia.diag_inertia,
706 epsilon = 1e-5
707 );
708 }
709
710 #[test]
711 fn test_compound_empty_shape() {
712 let shapes: &[Sphere; 0] = &[];
713 let poses = &[];
714 let masses = &[];
715 let (
716 Inertia {
717 diag_inertia,
718 inertia_rotation,
719 center_of_mass,
720 },
721 mass,
722 ) = compound(shapes, poses, masses, false);
723 assert_eq!(mass, 1.0);
724 assert_eq!(diag_inertia, crate::sphere(0.5));
725 assert_eq!(inertia_rotation, None);
726 assert_eq!(center_of_mass, None);
727 }
728 }
729
730 #[test]
731 fn test_diagonalize_inertia() {
732 let inertia = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
733 let (diag, rot) = diagonalize_inertia(inertia);
734 let inertia_matrix = Mat3::from_column_slice(&[
735 inertia[0], inertia[1], inertia[2], inertia[1], inertia[3], inertia[4], inertia[2],
736 inertia[4], inertia[5],
737 ]);
738
739 let diag_matrix = Mat3::from_diagonal(&diag);
740 let rot_matrix = Mat3::from(rot);
741 let result = rot_matrix * diag_matrix * rot_matrix.transpose();
742 assert_relative_eq!(inertia_matrix, result, epsilon = 1e-5);
743 }
744}