parry3d/mass_properties/
mass_properties_trimesh3d.rs

1use crate::mass_properties::MassProperties;
2use crate::math::{Matrix, Point, Real, DIM};
3use crate::shape::Tetrahedron;
4use num::Zero;
5
6impl MassProperties {
7    /// Computes the mass properties of a triangle mesh.
8    pub fn from_trimesh(
9        density: Real,
10        vertices: &[Point<Real>],
11        indices: &[[u32; DIM]],
12    ) -> MassProperties {
13        let (volume, com) = trimesh_signed_volume_and_center_of_mass(vertices, indices);
14
15        if volume.is_zero() {
16            return MassProperties::zero();
17        }
18
19        let mut itot = Matrix::zeros();
20
21        for t in indices {
22            let p2 = &vertices[t[0] as usize];
23            let p3 = &vertices[t[1] as usize];
24            let p4 = &vertices[t[2] as usize];
25
26            let vol = Tetrahedron::new(com, *p2, *p3, *p4).signed_volume();
27            let ipart = tetrahedron_unit_inertia_tensor_wrt_point(&com, &com, p2, p3, p4);
28
29            itot += ipart * vol;
30        }
31
32        let sign = volume.signum();
33        Self::with_inertia_matrix(com, volume * density * sign, itot * density * sign)
34    }
35}
36
37/// Computes the unit inertia tensor of a tetrahedron, with regard to the given `point`.
38pub fn tetrahedron_unit_inertia_tensor_wrt_point(
39    point: &Point<Real>,
40    p1: &Point<Real>,
41    p2: &Point<Real>,
42    p3: &Point<Real>,
43    p4: &Point<Real>,
44) -> Matrix<Real> {
45    let p1 = p1 - point;
46    let p2 = p2 - point;
47    let p3 = p3 - point;
48    let p4 = p4 - point;
49
50    // Just for readability.
51    let x1 = p1[0];
52    let y1 = p1[1];
53    let z1 = p1[2];
54    let x2 = p2[0];
55    let y2 = p2[1];
56    let z2 = p2[2];
57    let x3 = p3[0];
58    let y3 = p3[1];
59    let z3 = p3[2];
60    let x4 = p4[0];
61    let y4 = p4[1];
62    let z4 = p4[2];
63
64    let diag_x = x1 * x1
65        + x1 * x2
66        + x2 * x2
67        + x1 * x3
68        + x2 * x3
69        + x3 * x3
70        + x1 * x4
71        + x2 * x4
72        + x3 * x4
73        + x4 * x4;
74    let diag_y = y1 * y1
75        + y1 * y2
76        + y2 * y2
77        + y1 * y3
78        + y2 * y3
79        + y3 * y3
80        + y1 * y4
81        + y2 * y4
82        + y3 * y4
83        + y4 * y4;
84    let diag_z = z1 * z1
85        + z1 * z2
86        + z2 * z2
87        + z1 * z3
88        + z2 * z3
89        + z3 * z3
90        + z1 * z4
91        + z2 * z4
92        + z3 * z4
93        + z4 * z4;
94
95    let a0 = (diag_y + diag_z) * 0.1;
96    let b0 = (diag_z + diag_x) * 0.1;
97    let c0 = (diag_x + diag_y) * 0.1;
98
99    let a1 = (y1 * z1 * 2.0
100        + y2 * z1
101        + y3 * z1
102        + y4 * z1
103        + y1 * z2
104        + y2 * z2 * 2.0
105        + y3 * z2
106        + y4 * z2
107        + y1 * z3
108        + y2 * z3
109        + y3 * z3 * 2.0
110        + y4 * z3
111        + y1 * z4
112        + y2 * z4
113        + y3 * z4
114        + y4 * z4 * 2.0)
115        * 0.05;
116    let b1 = (x1 * z1 * 2.0
117        + x2 * z1
118        + x3 * z1
119        + x4 * z1
120        + x1 * z2
121        + x2 * z2 * 2.0
122        + x3 * z2
123        + x4 * z2
124        + x1 * z3
125        + x2 * z3
126        + x3 * z3 * 2.0
127        + x4 * z3
128        + x1 * z4
129        + x2 * z4
130        + x3 * z4
131        + x4 * z4 * 2.0)
132        * 0.05;
133    let c1 = (x1 * y1 * 2.0
134        + x2 * y1
135        + x3 * y1
136        + x4 * y1
137        + x1 * y2
138        + x2 * y2 * 2.0
139        + x3 * y2
140        + x4 * y2
141        + x1 * y3
142        + x2 * y3
143        + x3 * y3 * 2.0
144        + x4 * y3
145        + x1 * y4
146        + x2 * y4
147        + x3 * y4
148        + x4 * y4 * 2.0)
149        * 0.05;
150
151    Matrix::new(a0, -c1, -b1, -c1, b0, -a1, -b1, -a1, c0)
152}
153
154/// Computes the volume and center-of-mass of a mesh.
155pub fn trimesh_signed_volume_and_center_of_mass(
156    vertices: &[Point<Real>],
157    indices: &[[u32; DIM]],
158) -> (Real, Point<Real>) {
159    let geometric_center = crate::utils::center(vertices);
160
161    let mut res = Point::origin();
162    let mut vol = 0.0;
163
164    for t in indices {
165        let p2 = vertices[t[0] as usize];
166        let p3 = vertices[t[1] as usize];
167        let p4 = vertices[t[2] as usize];
168
169        let volume = Tetrahedron::new(geometric_center, p2, p3, p4).signed_volume();
170        let center = Tetrahedron::new(geometric_center, p2, p3, p4).center();
171
172        res += center.coords * volume;
173        vol += volume;
174    }
175
176    if vol.is_zero() {
177        (vol, geometric_center)
178    } else {
179        (vol, res / vol)
180    }
181}
182
183#[cfg(test)]
184mod test {
185    use crate::math::{Isometry, Vector};
186    use crate::{
187        mass_properties::MassProperties,
188        shape::{Ball, Capsule, Cone, Cuboid, Cylinder, Shape},
189    };
190    use na::UnitQuaternion;
191    use std::dbg;
192
193    fn assert_same_principal_inertias(mprops1: &MassProperties, mprops2: &MassProperties) {
194        for k in 0..3 {
195            let i1 = mprops1.principal_inertia_local_frame
196                * mprops1.principal_inertia().component_mul(
197                    &(mprops1.principal_inertia_local_frame.inverse() * Vector::ith(k, 1.0)),
198                );
199            let i2 = mprops2.principal_inertia_local_frame
200                * mprops2.principal_inertia().component_mul(
201                    &(mprops2.principal_inertia_local_frame.inverse() * Vector::ith(k, 1.0)),
202                );
203            assert_relative_eq!(i1, i2, epsilon = 0.5)
204        }
205    }
206
207    #[test]
208    fn cuboid_as_trimesh_mprops() {
209        let cuboid = Cuboid::new(Vector::new(1.0, 2.0, 3.0));
210
211        use crate::shape::Shape;
212        let orig_mprops = cuboid.mass_properties(1.0);
213        dbg!(orig_mprops.principal_inertia());
214
215        let mut trimesh = cuboid.to_trimesh();
216        let mprops = MassProperties::from_trimesh(1.0, &trimesh.0, &trimesh.1);
217        assert_relative_eq!(mprops.mass(), 48.0, epsilon = 1.0e-4);
218        assert_relative_eq!(
219            (mprops.principal_inertia_local_frame * mprops.principal_inertia()).abs(),
220            Vector::new(208.0, 160.0, 80.0),
221            epsilon = 1.0e-4
222        );
223
224        // Check after shifting the trimesh off the origin.
225        trimesh
226            .0
227            .iter_mut()
228            .for_each(|pt| *pt += Vector::new(30.0, 20.0, 10.0));
229        let mprops = MassProperties::from_trimesh(1.0, &trimesh.0, &trimesh.1);
230        assert_relative_eq!(mprops.mass(), 48.0, epsilon = 1.0e-4);
231        assert_relative_eq!(
232            (mprops.principal_inertia_local_frame * mprops.principal_inertia()).abs(),
233            Vector::new(208.0, 160.0, 80.0),
234            epsilon = 1.0e-4
235        );
236    }
237
238    #[test]
239    fn primitives_as_trimesh_mprops() {
240        let primitives = (
241            Cuboid::new(Vector::new(1.0, 2.0, 3.0)),
242            Capsule::new_y(2.0, 1.0),
243            Cone::new(2.0, 1.0),
244            Cylinder::new(2.0, 1.0),
245            Ball::new(2.0),
246        );
247        let mut meshes = [
248            primitives.0.to_trimesh(),
249            primitives.1.to_trimesh(100, 100),
250            primitives.2.to_trimesh(100),
251            primitives.3.to_trimesh(100),
252            primitives.4.to_trimesh(100, 100),
253        ];
254        let shapes = [
255            &primitives.0 as &dyn Shape,
256            &primitives.1 as &dyn Shape,
257            &primitives.2 as &dyn Shape,
258            &primitives.3 as &dyn Shape,
259            &primitives.4 as &dyn Shape,
260        ];
261
262        for (shape, mesh) in shapes.iter().zip(meshes.iter_mut()) {
263            let shape_mprops = shape.mass_properties(2.0);
264            let mesh_mprops = MassProperties::from_trimesh(2.0, &mesh.0, &mesh.1);
265            assert_relative_eq!(shape_mprops.mass(), mesh_mprops.mass(), epsilon = 1.0e-1);
266            assert_same_principal_inertias(&shape_mprops, &mesh_mprops);
267            assert_relative_eq!(
268                shape_mprops.local_com,
269                mesh_mprops.local_com,
270                epsilon = 1.0e-3
271            );
272
273            // Now try with a shifted mesh.
274            let shift = Vector::new(33.0, 22.0, 11.0);
275            mesh.0.iter_mut().for_each(|pt| *pt += shift);
276
277            let mesh_mprops = MassProperties::from_trimesh(2.0, &mesh.0, &mesh.1);
278
279            assert_relative_eq!(shape_mprops.mass(), mesh_mprops.mass(), epsilon = 1.0e-1);
280            assert_same_principal_inertias(&shape_mprops, &mesh_mprops);
281            assert_relative_eq!(
282                shape_mprops.local_com + shift, // The mesh is shifted, so its center-of-mass is shifted too.
283                mesh_mprops.local_com,
284                epsilon = 1.0e-3
285            );
286        }
287    }
288
289    // Regression test for https://github.com/dimforge/parry/pull/331
290    //
291    // We compare the angular inertia tensor of a rotated & translated Cuboid, with the
292    // inertia tensor of the same cuboid represented as a `TriMesh` with rotated & translated
293    // vertices.
294    #[test]
295    fn rotated_inertia_tensor() {
296        let cuboid = Cuboid::new(Vector::new(1.0, 2.0, 3.0));
297        let density = 1.0;
298
299        // Compute mass properties with a translated and rotated cuboid.
300        let pose = Isometry::new(Vector::new(5.0, 2.0, 3.0), Vector::new(0.6, 0.7, 0.8));
301        let mprops = cuboid.mass_properties(density);
302
303        // Compute mass properties with a manually transformed cuboid.
304        let mut vertices = cuboid.to_trimesh().0;
305        let trimesh_origin_mprops =
306            MassProperties::from_trimesh(density, &vertices, &cuboid.to_trimesh().1);
307        vertices.iter_mut().for_each(|v| *v = pose * *v);
308        let trimesh_transformed_mprops =
309            MassProperties::from_trimesh(density, &vertices, &cuboid.to_trimesh().1);
310
311        // Compare the mass properties.
312        assert_relative_eq!(
313            mprops.mass(),
314            trimesh_origin_mprops.mass(),
315            epsilon = 1.0e-4
316        );
317        assert_relative_eq!(
318            mprops.mass(),
319            trimesh_transformed_mprops.mass(),
320            epsilon = 1.0e-4
321        );
322        // compare local_com
323        assert_relative_eq!(
324            pose * mprops.local_com,
325            trimesh_transformed_mprops.local_com,
326            epsilon = 1.0e-4
327        );
328        assert_relative_eq!(
329            pose * trimesh_origin_mprops.local_com,
330            trimesh_transformed_mprops.local_com,
331            epsilon = 1.0e-4
332        );
333        assert_relative_eq!(
334            trimesh_origin_mprops.principal_inertia(),
335            mprops.principal_inertia(),
336            epsilon = 1.0e-4
337        );
338        let w1 = trimesh_origin_mprops.world_inv_inertia_sqrt(&pose.rotation);
339        let w2 = trimesh_transformed_mprops.world_inv_inertia_sqrt(&UnitQuaternion::identity());
340        assert_relative_eq!(w1.m11, w2.m11, epsilon = 1.0e-7);
341        assert_relative_eq!(w1.m12, w2.m12, epsilon = 1.0e-7);
342        assert_relative_eq!(w1.m13, w2.m13, epsilon = 1.0e-7);
343        assert_relative_eq!(w1.m22, w2.m22, epsilon = 1.0e-7);
344        assert_relative_eq!(w1.m23, w2.m23, epsilon = 1.0e-7);
345        assert_relative_eq!(w1.m33, w2.m33, epsilon = 1.0e-7);
346    }
347}