Skip to content

Commit 219e70b

Browse files
authored
Support mesh optimization params in mesh inertial calculator (#2770)
Signed-off-by: Ian Chen <ichen@openrobotics.org>
1 parent 54830b9 commit 219e70b

File tree

7 files changed

+256
-42
lines changed

7 files changed

+256
-42
lines changed

src/MeshInertiaCalculator.cc

+39-37
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@
2525

2626
#include <gz/sim/Util.hh>
2727

28-
#include <gz/common/graphics.hh>
29-
#include <gz/common/Mesh.hh>
3028
#include <gz/common/MeshManager.hh>
29+
#include <gz/common/SubMesh.hh>
3130

3231
#include <gz/math/Vector3.hh>
3332
#include <gz/math/Pose3.hh>
@@ -42,15 +41,15 @@ using namespace sim;
4241
void MeshInertiaCalculator::GetMeshTriangles(
4342
std::vector<Triangle> &_triangles,
4443
const gz::math::Vector3d &_meshScale,
45-
const gz::common::Mesh* _mesh)
44+
const gz::common::SubMesh* _subMesh)
4645
{
4746
// Get the vertices & indices of the mesh
4847
double* vertArray = nullptr;
4948
int* indArray = nullptr;
50-
_mesh->FillArrays(&vertArray, &indArray);
49+
_subMesh->FillArrays(&vertArray, &indArray);
5150

5251
// Add check to see if size of the ind array is divisible by 3
53-
for (unsigned int i = 0; i < _mesh->IndexCount(); i += 3)
52+
for (unsigned int i = 0; i < _subMesh->IndexCount(); i += 3)
5453
{
5554
Triangle triangle;
5655
triangle.v0.X() = vertArray[static_cast<ptrdiff_t>(3 * indArray[i])];
@@ -187,62 +186,65 @@ void MeshInertiaCalculator::CalculateMassProperties(
187186

188187
//////////////////////////////////////////////////
189188
std::optional<gz::math::Inertiald> MeshInertiaCalculator::operator()
190-
(sdf::Errors& _errors,
189+
(sdf::Errors &_errors,
191190
const sdf::CustomInertiaCalcProperties& _calculatorParams)
192191
{
193-
const gz::common::Mesh *mesh = nullptr;
194192
const double density = _calculatorParams.Density();
195193

196194
auto sdfMesh = _calculatorParams.Mesh();
197195

198196
if (sdfMesh == std::nullopt)
199197
{
200198
gzerr << "Could not calculate inertia for mesh "
201-
"as it std::nullopt" << std::endl;
199+
"as mesh SDF is std::nullopt" << std::endl;
202200
_errors.push_back({sdf::ErrorCode::FATAL_ERROR,
203-
"Could not calculate mesh inertia as mesh object is"
201+
"Could not calculate mesh inertia as mesh SDF is"
204202
"std::nullopt"});
205203
return std::nullopt;
206204
}
207205

208-
auto fullPath = asFullPath(sdfMesh->Uri(), sdfMesh->FilePath());
209-
210-
if (fullPath.empty())
206+
const common::Mesh *mesh = loadMesh(*sdfMesh);
207+
if (!mesh)
211208
{
212-
gzerr << "Mesh geometry missing uri" << std::endl;
213-
_errors.push_back({sdf::ErrorCode::URI_INVALID,
214-
"Attempting to load the mesh but the URI seems to be incorrect"});
209+
gzerr << "Failed to load mesh: " << sdfMesh->Uri() << std::endl;
210+
_errors.push_back({sdf::ErrorCode::FATAL_ERROR,
211+
"Could not calculate mesh inertia as mesh is not loaded."});
215212
return std::nullopt;
216213
}
217214

218-
// Load the Mesh
219-
gz::common::MeshManager *meshManager = gz::common::MeshManager::Instance();
220-
mesh = meshManager->Load(fullPath);
221-
if (!mesh)
215+
// Compute inertia for each submesh then sum up to get the final inertia
216+
// values.
217+
gz::math::Inertiald meshInertial;
218+
for (unsigned int i = 0; i < mesh->SubMeshCount(); ++i)
222219
{
223-
gzerr << "Failed to load mesh: " << fullPath << std::endl;
224-
return std::nullopt;
225-
}
226-
std::vector<Triangle> meshTriangles;
227-
gz::math::MassMatrix3d meshMassMatrix;
228-
gz::math::Pose3d centreOfMass;
220+
std::vector<Triangle> meshTriangles;
221+
gz::math::MassMatrix3d meshMassMatrix;
222+
gz::math::Pose3d centreOfMass;
229223

230-
// Create a list of Triangle objects from the mesh vertices and indices
231-
this->GetMeshTriangles(meshTriangles, sdfMesh->Scale(), mesh);
224+
// Create a list of Triangle objects from the mesh vertices and indices
225+
auto submesh = mesh->SubMeshByIndex(i).lock();
226+
this->GetMeshTriangles(meshTriangles, sdfMesh->Scale(), submesh.get());
232227

233-
// Calculate mesh mass properties
234-
this->CalculateMassProperties(meshTriangles, density,
235-
meshMassMatrix, centreOfMass);
228+
// Calculate mesh mass properties
229+
this->CalculateMassProperties(meshTriangles, density,
230+
meshMassMatrix, centreOfMass);
236231

237-
gz::math::Inertiald meshInertial;
232+
if (meshMassMatrix.IsValid())
233+
meshInertial += gz::math::Inertiald(meshMassMatrix, centreOfMass);
234+
}
238235

239-
if (!meshInertial.SetMassMatrix(meshMassMatrix))
236+
if (meshInertial.MassMatrix().Mass() <= 0.0 ||
237+
!meshInertial.MassMatrix().IsValid())
240238
{
239+
gzerr << "Failed to computed valid inertia in MeshInertiaCalculator. "
240+
<< "Ensure that the mesh is water tight, or try optimizing the mesh "
241+
<< "by setting the //mesh/@optimization attribute in SDF to "
242+
<< "`convex_hull` or `convex_decomposition`."
243+
<< std::endl;
244+
_errors.push_back({sdf::ErrorCode::WARNING,
245+
"Could not calculate valid mesh inertia"});
241246
return std::nullopt;
242247
}
243-
else
244-
{
245-
meshInertial.SetPose(centreOfMass);
246-
return meshInertial;
247-
}
248+
249+
return meshInertial;
248250
}

src/MeshInertiaCalculator.hh

+2-4
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@
2828
#include <gz/sim/Util.hh>
2929
#include <gz/sim/config.hh>
3030

31-
#include <gz/common/graphics.hh>
32-
#include <gz/common/Mesh.hh>
33-
#include <gz/common/MeshManager.hh>
31+
#include <gz/common/SubMesh.hh>
3432

3533
#include <gz/math/Vector3.hh>
3634
#include <gz/math/Pose3.hh>
@@ -88,7 +86,7 @@ namespace gz
8886
public: void GetMeshTriangles(
8987
std::vector<Triangle> &_triangles,
9088
const gz::math::Vector3d &_meshScale,
91-
const gz::common::Mesh* _mesh);
89+
const gz::common::SubMesh* _mesh);
9290

9391
/// \brief Function that calculates the mass, mass matrix & centre of
9492
/// mass of a mesh using a vector of Triangles of the mesh

test/integration/mesh_inertia_calculation.cc

+87
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
#include <gtest/gtest.h>
19+
#include <cstddef>
1920
#include <optional>
2021
#include <string>
2122

@@ -207,3 +208,89 @@ TEST(MeshInertiaCalculationTest,
207208
EXPECT_EQ(link.WorldInertialPose(*ecm).value(),
208209
gz::math::Pose3d(0, 0, 1, 0, 0, 0));
209210
}
211+
212+
TEST(MeshInertiaCalculationTest, CylinderColladaOptimizedMeshInertiaCalculation)
213+
{
214+
size_t kIter = 100u;
215+
216+
// Start server and run.
217+
gz::sim::ServerConfig serverConfig;
218+
serverConfig.SetSdfFile(common::joinPaths(
219+
PROJECT_SOURCE_PATH, "test", "worlds", "mesh_inertia_calculation.sdf"));
220+
221+
common::setenv(
222+
"GZ_SIM_RESOURCE_PATH",
223+
common::joinPaths(PROJECT_SOURCE_PATH, "test", "worlds", "models"));
224+
225+
gz::sim::Server server(serverConfig);
226+
227+
// Create a system just to get the ECM
228+
EntityComponentManager *ecm;
229+
test::Relay testSystem;
230+
testSystem.OnPreUpdate(
231+
[&](const UpdateInfo &, EntityComponentManager &_ecm)
232+
{
233+
ecm = &_ecm;
234+
}
235+
);
236+
server.AddSystem(testSystem.systemPtr);
237+
238+
ASSERT_FALSE(server.Running());
239+
ASSERT_FALSE(*server.Running(0));
240+
ASSERT_TRUE(server.Run(true, kIter, false));
241+
ASSERT_NE(nullptr, ecm);
242+
243+
// Get link of collada cylinder
244+
gz::sim::Entity modelEntity = ecm->EntityByComponents(
245+
gz::sim::components::Name("cylinder_dae_convex_decomposition"),
246+
gz::sim::components::Model()
247+
);
248+
249+
gz::sim::Model model = gz::sim::Model(modelEntity);
250+
ASSERT_TRUE(model.Valid(*ecm));
251+
252+
gz::sim::Entity linkEntity =
253+
model.LinkByName(*ecm, "cylinder_dae_convex_decomposition");
254+
gz::sim::Link link = gz::sim::Link(linkEntity);
255+
ASSERT_TRUE(link.Valid(*ecm));
256+
257+
// Enable checks for pose values
258+
link.EnableVelocityChecks(*ecm);
259+
260+
ASSERT_NE(link.WorldInertiaMatrix(*ecm), std::nullopt);
261+
ASSERT_NE(link.WorldInertialPose(*ecm), std::nullopt);
262+
ASSERT_NE(link.WorldPose(*ecm), std::nullopt);
263+
264+
// The cylinder has a radius of 1m, length of 2m, and density of 1240 kg/m³.
265+
// Volume: πr²h = 2π ≈ 6.283
266+
// Mass: ρV = (1240.0) * 2π ≈ 7791.1497
267+
// Ix = Iy : 1/12 * m(3r² + h²) = m/12 * (3 + 4) ≈ 4544.83
268+
// Iz : ½mr² ≈ 3895.57
269+
gz::math::Inertiald meshInertial;
270+
meshInertial.SetMassMatrix(gz::math::MassMatrix3d(
271+
7791.1497,
272+
gz::math::Vector3d(4544.83, 4544.83, 3895.57),
273+
gz::math::Vector3d::Zero
274+
));
275+
meshInertial.SetPose(gz::math::Pose3d::Zero);
276+
277+
// Check the Inertia Matrix within a larger tolerance since we are
278+
// comparing a mesh cylinder made of convex hulls with an ideal cylinder.
279+
// For values more closer to the ideal, a higher number convex decomposition
280+
// paramers would be required in the mesh sdf.
281+
double ixxyyzzTol = meshInertial.MassMatrix().DiagonalMoments().Max() * 0.1;
282+
gz::math::Vector3d actualIxxyyzz(link.WorldInertiaMatrix(*ecm).value()(0, 0),
283+
link.WorldInertiaMatrix(*ecm).value()(1, 1),
284+
link.WorldInertiaMatrix(*ecm).value()(2, 2));
285+
gz::math::Vector3d actualIxyxzyz(link.WorldInertiaMatrix(*ecm).value()(0, 1),
286+
link.WorldInertiaMatrix(*ecm).value()(0, 2),
287+
link.WorldInertiaMatrix(*ecm).value()(1, 2));
288+
EXPECT_TRUE(actualIxxyyzz.Equal(meshInertial.MassMatrix().DiagonalMoments(),
289+
ixxyyzzTol));
290+
EXPECT_TRUE(actualIxyxzyz.Equal(
291+
meshInertial.MassMatrix().OffDiagonalMoments(), 3.5));
292+
// Check the Inertial Pose and Link Pose
293+
EXPECT_EQ(link.WorldPose(*ecm).value(), gz::math::Pose3d::Zero);
294+
EXPECT_TRUE(link.WorldInertialPose(*ecm).value().Equal(
295+
gz::math::Pose3d::Zero, 1e-2));
296+
}

test/worlds/mesh_inertia_calculation.sdf

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
<real_time_factor>1.0</real_time_factor>
77
</physics>
88

9-
109
<include>
1110
<uri>cylinder_dae</uri>
1211
<name>cylinder_dae</name>
@@ -18,5 +17,11 @@
1817
<name>cylinder_dae_bottom_origin</name>
1918
<pose>4 0 0 0 0 0</pose>
2019
</include>
20+
21+
<include>
22+
<uri>cylinder_dae_convex_decomposition</uri>
23+
<name>cylinder_dae_convex_decomposition</name>
24+
<pose>8 0 0 0 0 0</pose>
25+
</include>
2126
</world>
2227
</sdf>

test/worlds/models/cylinder_dae_convex_decomposition/meshes/cylinder.dae

+69
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0"?>
2+
3+
<model>
4+
<name>cylinder_dae_convex_decomposition</name>
5+
<version>1.0</version>
6+
<sdf version="1.11">model.sdf</sdf>
7+
8+
<author>
9+
<name>Ian Chen</name>
10+
<email></email>
11+
</author>
12+
13+
<description>
14+
A model of a cylinder collada mesh with convex decomposition
15+
</description>
16+
</model>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" ?>
2+
<sdf version="1.11">
3+
<model name="cylinder_dae_convex_decomposition">
4+
<link name="cylinder_dae_convex_decomposition">
5+
<pose>0 0 0 0 0 0</pose>
6+
<inertial auto="true" />
7+
<collision name="cylinder_collision">
8+
<density>1240.0</density>
9+
<auto_inertia_params>
10+
<gz:voxel_size>0.01</gz:voxel_size>
11+
</auto_inertia_params>
12+
<geometry>
13+
<mesh optimization="convex_decomposition">
14+
<convex_decomposition>
15+
<max_convex_hulls>16</max_convex_hulls>
16+
<voxel_resolution>200000</voxel_resolution>
17+
</convex_decomposition>
18+
<uri>meshes/cylinder.dae</uri>
19+
</mesh>
20+
</geometry>
21+
</collision>
22+
<visual name="cylinder_visual">
23+
<pose>0 0 0 0 0 0</pose>
24+
<geometry>
25+
<mesh>
26+
<uri>meshes/cylinder.dae</uri>
27+
</mesh>
28+
</geometry>
29+
<material>
30+
<diffuse>0.7 0.7 0.7 1.0</diffuse>
31+
<ambient>0.7 0.7 0.7 1.0</ambient>
32+
<specular>0.7 0.7 0.7 1.0</specular>
33+
</material>
34+
</visual>
35+
</link>
36+
</model>
37+
</sdf>

0 commit comments

Comments
 (0)