1use crate::mass_properties::MassProperties;
2use crate::math::{Matrix, Real, Vector, DIM};
3use crate::shape::Tetrahedron;
4use num::Zero;
5
6impl MassProperties {
7 pub fn from_trimesh(
9 density: Real,
10 vertices: &[Vector],
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::ZERO;
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: Vector,
40 p1: Vector,
41 p2: Vector,
42 p3: Vector,
43 p4: Vector,
44) -> Matrix {
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::from_cols_array(&[a0, -c1, -b1, -c1, b0, -a1, -b1, -a1, c0])
152}
153
154pub fn trimesh_signed_volume_and_center_of_mass(
156 vertices: &[Vector],
157 indices: &[[u32; DIM]],
158) -> (Real, Vector) {
159 let geometric_center = crate::utils::center(vertices);
160
161 let mut res = Vector::ZERO;
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 * 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::{Pose, Rotation, Vector, VectorExt};
186 use crate::{
187 mass_properties::MassProperties,
188 shape::{Ball, Capsule, Cone, Cuboid, Cylinder, Shape},
189 };
190 use std::dbg;
191
192 fn assert_same_principal_inertias(mprops1: &MassProperties, mprops2: &MassProperties) {
193 for k in 0..3 {
194 let i1 = mprops1.principal_inertia_local_frame
195 * (mprops1.principal_inertia()
196 * (mprops1.principal_inertia_local_frame.inverse() * Vector::ith(k, 1.0)));
197 let i2 = mprops2.principal_inertia_local_frame
198 * (mprops2.principal_inertia()
199 * (mprops2.principal_inertia_local_frame.inverse() * Vector::ith(k, 1.0)));
200 assert!(i1.abs_diff_eq(i2, 0.5))
201 }
202 }
203
204 #[test]
205 fn cuboid_as_trimesh_mprops() {
206 let cuboid = Cuboid::new(Vector::new(1.0, 2.0, 3.0));
207
208 use crate::shape::Shape;
209 let orig_mprops = cuboid.mass_properties(1.0);
210 dbg!(orig_mprops.principal_inertia());
211
212 let mut trimesh = cuboid.to_trimesh();
213 let mprops = MassProperties::from_trimesh(1.0, &trimesh.0, &trimesh.1);
214 assert_relative_eq!(mprops.mass(), 48.0, epsilon = 1.0e-4);
215 assert!(
216 (mprops.principal_inertia_local_frame * mprops.principal_inertia())
217 .abs()
218 .abs_diff_eq(Vector::new(208.0, 160.0, 80.0), 1.0e-4)
219 );
220
221 trimesh
223 .0
224 .iter_mut()
225 .for_each(|pt| *pt += Vector::new(30.0, 20.0, 10.0));
226 let mprops = MassProperties::from_trimesh(1.0, &trimesh.0, &trimesh.1);
227 assert_relative_eq!(mprops.mass(), 48.0, epsilon = 1.0e-4);
228 assert!(
229 (mprops.principal_inertia_local_frame * mprops.principal_inertia())
230 .abs()
231 .abs_diff_eq(Vector::new(208.0, 160.0, 80.0), 1.0e-4)
232 );
233 }
234
235 #[test]
236 fn primitives_as_trimesh_mprops() {
237 let primitives = (
238 Cuboid::new(Vector::new(1.0, 2.0, 3.0)),
239 Capsule::new_y(2.0, 1.0),
240 Cone::new(2.0, 1.0),
241 Cylinder::new(2.0, 1.0),
242 Ball::new(2.0),
243 );
244 let mut meshes = [
245 primitives.0.to_trimesh(),
246 primitives.1.to_trimesh(100, 100),
247 primitives.2.to_trimesh(100),
248 primitives.3.to_trimesh(100),
249 primitives.4.to_trimesh(100, 100),
250 ];
251 let shapes = [
252 &primitives.0 as &dyn Shape,
253 &primitives.1 as &dyn Shape,
254 &primitives.2 as &dyn Shape,
255 &primitives.3 as &dyn Shape,
256 &primitives.4 as &dyn Shape,
257 ];
258
259 for (shape, mesh) in shapes.iter().zip(meshes.iter_mut()) {
260 let shape_mprops = shape.mass_properties(2.0);
261 let mesh_mprops = MassProperties::from_trimesh(2.0, &mesh.0, &mesh.1);
262 assert_relative_eq!(shape_mprops.mass(), mesh_mprops.mass(), epsilon = 1.0e-1);
263 assert_same_principal_inertias(&shape_mprops, &mesh_mprops);
264 assert_relative_eq!(
265 shape_mprops.local_com,
266 mesh_mprops.local_com,
267 epsilon = 1.0e-3
268 );
269
270 let shift = Vector::new(33.0, 22.0, 11.0);
272 mesh.0.iter_mut().for_each(|pt| *pt += shift);
273
274 let mesh_mprops = MassProperties::from_trimesh(2.0, &mesh.0, &mesh.1);
275
276 assert_relative_eq!(shape_mprops.mass(), mesh_mprops.mass(), epsilon = 1.0e-1);
277 assert_same_principal_inertias(&shape_mprops, &mesh_mprops);
278 assert_relative_eq!(
279 shape_mprops.local_com + shift, mesh_mprops.local_com,
281 epsilon = 1.0e-3
282 );
283 }
284 }
285
286 #[test]
292 fn rotated_inertia_tensor() {
293 let cuboid = Cuboid::new(Vector::new(3.0, 2.0, 1.0));
294 let density = 1.0;
295
296 let pose = Pose::new(Vector::new(5.0, 2.0, 3.0), Vector::new(0.6, 0.7, 0.8));
298 let mprops = cuboid.mass_properties(density);
299
300 let mut vertices = cuboid.to_trimesh().0;
302 let trimesh_origin_mprops =
303 MassProperties::from_trimesh(density, &vertices, &cuboid.to_trimesh().1);
304 vertices.iter_mut().for_each(|v| *v = pose * *v);
305 let trimesh_transformed_mprops =
306 MassProperties::from_trimesh(density, &vertices, &cuboid.to_trimesh().1);
307
308 assert_relative_eq!(
310 mprops.mass(),
311 trimesh_origin_mprops.mass(),
312 epsilon = 1.0e-4
313 );
314 assert_relative_eq!(
315 mprops.mass(),
316 trimesh_transformed_mprops.mass(),
317 epsilon = 1.0e-4
318 );
319 assert_relative_eq!(
321 pose * mprops.local_com,
322 trimesh_transformed_mprops.local_com,
323 epsilon = 1.0e-4
324 );
325 assert_relative_eq!(
326 pose * trimesh_origin_mprops.local_com,
327 trimesh_transformed_mprops.local_com,
328 epsilon = 1.0e-4
329 );
330 assert!(trimesh_origin_mprops
331 .principal_inertia()
332 .abs_diff_eq(mprops.principal_inertia(), 1.0e-4));
333 let w1 = trimesh_origin_mprops.world_inv_inertia(&pose.rotation);
334 let w2 = trimesh_transformed_mprops.world_inv_inertia(&Rotation::IDENTITY);
335 assert_relative_eq!(w1.m11, w2.m11, epsilon = 1.0e-7);
336 assert_relative_eq!(w1.m12, w2.m12, epsilon = 1.0e-7);
337 assert_relative_eq!(w1.m13, w2.m13, epsilon = 1.0e-7);
338 assert_relative_eq!(w1.m22, w2.m22, epsilon = 1.0e-7);
339 assert_relative_eq!(w1.m23, w2.m23, epsilon = 1.0e-7);
340 assert_relative_eq!(w1.m33, w2.m33, epsilon = 1.0e-7);
341 }
342}