1use crate::mass_properties::MassProperties;
2use crate::math::{Matrix, Point, Real, DIM};
3use crate::shape::Tetrahedron;
4use num::Zero;
5
6impl MassProperties {
7 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
37pub 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 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
154pub 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 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 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, mesh_mprops.local_com,
284 epsilon = 1.0e-3
285 );
286 }
287 }
288
289 #[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 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 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 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 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}