Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: Fixed Centre of Mass and Inertia Matrix Calculation Bug MeshInertiaCalculator::CalculateMassProperties() function #2182

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 18 additions & 59 deletions src/MeshInertiaCalculator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <gz/math/Pose3.hh>
#include <gz/math/MassMatrix3.hh>
#include <gz/math/Inertial.hh>
#include <gz/math/Quaternion.hh>

using namespace gz;
using namespace sim;
Expand Down Expand Up @@ -72,41 +73,12 @@ void MeshInertiaCalculator::GetMeshTriangles(
}
}

//////////////////////////////////////////////////
void MeshInertiaCalculator::CalculateMeshCentroid(
gz::math::Pose3d &_centreOfMass,
std::vector<Triangle> &_triangles)
{
gz::math::Vector3d centroid = gz::math::Vector3d::Zero;
gz::math::Vector3d triangleCross = gz::math::Vector3d::Zero;
double totalMeshArea = 0.0;
double triangleArea = 0.0;

for (Triangle &triangle : _triangles)
{
// Calculate the area of the triangle using half of
// cross product magnitude
triangleCross =
(triangle.v1 - triangle.v0).Cross(triangle.v2 - triangle.v0);
triangleArea = triangleCross.Length() / 2;

centroid = centroid + (triangle.centroid * triangleArea);
totalMeshArea = totalMeshArea + triangleArea;
}

centroid = centroid / totalMeshArea;

_centreOfMass.SetX(centroid.X());
_centreOfMass.SetY(centroid.Y());
_centreOfMass.SetZ(centroid.Z());
}

//////////////////////////////////////////////////
void MeshInertiaCalculator::CalculateMassProperties(
const std::vector<Triangle>& _triangles,
double _density,
gz::math::MassMatrix3d& _massMatrix,
gz::math::Pose3d& _inertiaOrigin)
gz::math::Pose3d& _centreOfMass)
{
// Some coefficients for the calculation of integral terms
const double coefficients[10] = {1.0 / 6, 1.0 / 24, 1.0 / 24, 1.0 / 24,
Expand Down Expand Up @@ -185,27 +157,27 @@ void MeshInertiaCalculator::CalculateMassProperties(
// Accumulate the result and add it to MassMatrix object of gz::math
double volume = integral[0];
double mass = volume * _density;
_inertiaOrigin.SetX(integral[1] / mass);
_inertiaOrigin.SetY(integral[2] / mass);
_inertiaOrigin.SetZ(integral[3] / mass);
_centreOfMass.SetX(integral[1] / volume);
_centreOfMass.SetY(integral[2] / volume);
_centreOfMass.SetZ(integral[3] / volume);
gz::math::Vector3d ixxyyzz = gz::math::Vector3d();
gz::math::Vector3d ixyxzyz = gz::math::Vector3d();

// Diagonal Elements of the Mass Matrix
ixxyyzz.X() = (integral[5] + integral[6] - mass *
(_inertiaOrigin.Y() * _inertiaOrigin.Y() +
_inertiaOrigin.Z() * _inertiaOrigin.Z()));
ixxyyzz.Y() = (integral[4] + integral[6] - mass *
(_inertiaOrigin.Z() * _inertiaOrigin.Z() +
_inertiaOrigin.X() * _inertiaOrigin.X()));
ixxyyzz.Z() = integral[4] + integral[5] - mass *
(_inertiaOrigin.X() * _inertiaOrigin.X() +
_inertiaOrigin.Y() * _inertiaOrigin.Y());
ixxyyzz.X() = (integral[5] + integral[6] - volume *
(_centreOfMass.Y() * _centreOfMass.Y() +
_centreOfMass.Z() * _centreOfMass.Z()));
ixxyyzz.Y() = (integral[4] + integral[6] - volume *
(_centreOfMass.Z() * _centreOfMass.Z() +
_centreOfMass.X() * _centreOfMass.X()));
ixxyyzz.Z() = integral[4] + integral[5] - volume *
(_centreOfMass.X() * _centreOfMass.X() +
_centreOfMass.Y() * _centreOfMass.Y());

// Off Diagonal Elements of the Mass Matrix
ixyxzyz.X() = -(integral[7] - mass * _inertiaOrigin.X() * _inertiaOrigin.Y());
ixyxzyz.Y() = -(integral[8] - mass * _inertiaOrigin.Y() * _inertiaOrigin.Z());
ixyxzyz.Z() = -(integral[9] - mass * _inertiaOrigin.X() * _inertiaOrigin.Z());
ixyxzyz.X() = -(integral[7] - volume * _centreOfMass.X() * _centreOfMass.Y());
ixyxzyz.Y() = -(integral[9] - volume * _centreOfMass.X() * _centreOfMass.Z());
ixyxzyz.Z() = -(integral[8] - volume * _centreOfMass.Y() * _centreOfMass.Z());

// Set the values in the MassMatrix object
_massMatrix.SetMass(mass);
Expand Down Expand Up @@ -249,26 +221,13 @@ std::optional<gz::math::Inertiald> MeshInertiaCalculator::operator()
std::vector<Triangle> meshTriangles;
gz::math::MassMatrix3d meshMassMatrix;
gz::math::Pose3d centreOfMass;
gz::math::Pose3d inertiaOrigin;

// Create a list of Triangle objects from the mesh vertices and indices
this->GetMeshTriangles(meshTriangles, sdfMesh->Scale(), mesh);

// Calculate the mesh centroid and set is as centre of mass
this->CalculateMeshCentroid(centreOfMass, meshTriangles);

// Calculate mesh mass properties
this->CalculateMassProperties(meshTriangles, density,
meshMassMatrix, inertiaOrigin);

if (inertiaOrigin != centreOfMass)
{
// TODO(jasmeet0915): tranform the calculated inertia matrix
// from inertia origin to centre of mass
gzwarn << "Calculated centroid does not match the mesh origin. "
"Inertia Tensor values won't be correct. Use mesh with origin at "
"geometric center." << std::endl;
}
meshMassMatrix, centreOfMass);

gz::math::Inertiald meshInertial;

Expand Down
13 changes: 0 additions & 13 deletions src/MeshInertiaCalculator.hh
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,6 @@ namespace gz
const gz::math::Vector3d &_meshScale,
const gz::common::Mesh* _mesh);

/// \brief Function to calculate the centroid of the mesh. Since
/// uniform density is considered for the mesh, the centroid value
/// is used as the centre of mass.
/// The mesh centroid is calculated by the average of the
/// centroid of mesh triangle weighted by the area of the
/// respective triangle
/// \param[out] _centreOfMass A gz::math::Pose3d object to hold
/// calculated centroid (centre of mass) of the mesh
/// \param[in] _triangles A vector with all the triangles of the
/// mesh represented as instances of the Triangle struct
public: void CalculateMeshCentroid(gz::math::Pose3d &_centreOfMass,
std::vector<Triangle> &_triangles);

/// \brief Function that calculates the mass, mass matrix & centre of
/// mass of a mesh using a vector of Triangles of the mesh
/// \param[in] _triangles A vector of all the Triangles of the mesh
Expand Down
84 changes: 82 additions & 2 deletions test/integration/mesh_inertia_calculation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ TEST(MeshInertiaCalculationTest, CylinderColladaMeshInertiaCalculation)
{
size_t kIter = 100u;

gz::math::Pose3d linkPose(4, 4, 1, 0, 0, 0);
gz::math::Inertiald linkInertial;
// Start server and run.
gz::sim::ServerConfig serverConfig;
serverConfig.SetSdfFile(common::joinPaths(
Expand Down Expand Up @@ -127,3 +125,85 @@ TEST(MeshInertiaCalculationTest, CylinderColladaMeshInertiaCalculation)
EXPECT_EQ(link.WorldPose(*ecm).value(), gz::math::Pose3d::Zero);
EXPECT_EQ(link.WorldInertialPose(*ecm).value(), gz::math::Pose3d::Zero);
}

TEST(MeshInertiaCalculationTest,
CylinderColladaMeshWithNonCenterOriginInertiaCalculation)
{
size_t kIter = 100u;

// Start server and run.
gz::sim::ServerConfig serverConfig;
serverConfig.SetSdfFile(common::joinPaths(
PROJECT_SOURCE_PATH, "test", "worlds", "mesh_inertia_calculation.sdf"));

common::setenv(
"GZ_SIM_RESOURCE_PATH",
common::joinPaths(PROJECT_SOURCE_PATH, "test", "worlds", "models"));

gz::sim::Server server(serverConfig);

// Create a system just to get the ECM
EntityComponentManager *ecm;
test::Relay testSystem;
testSystem.OnPreUpdate(
[&](const UpdateInfo &, EntityComponentManager &_ecm)
{
ecm = &_ecm;
}
);
server.AddSystem(testSystem.systemPtr);

ASSERT_FALSE(server.Running());
ASSERT_FALSE(*server.Running(0));
ASSERT_TRUE(server.Run(true, kIter, false));
ASSERT_NE(nullptr, ecm);

// Get link of collada cylinder
gz::sim::Entity modelEntity = ecm->EntityByComponents(
gz::sim::components::Name("cylinder_dae_bottom_origin"),
gz::sim::components::Model()
);

gz::sim::Model model = gz::sim::Model(modelEntity);
ASSERT_TRUE(model.Valid(*ecm));

gz::sim::Entity linkEntity = model.LinkByName(*ecm,
"cylinder_dae_bottom_origin");
gz::sim::Link link = gz::sim::Link(linkEntity);
ASSERT_TRUE(link.Valid(*ecm));

// Enable checks for pose values
link.EnableVelocityChecks(*ecm);

ASSERT_NE(link.WorldInertiaMatrix(*ecm), std::nullopt);
ASSERT_NE(link.WorldInertialPose(*ecm), std::nullopt);
ASSERT_NE(link.WorldPose(*ecm), std::nullopt);

// The cylinder has a radius of 1m, length of 2m, and density of 1240 kg/m³.
// Volume: πr²h = 2π ≈ 6.283
// Mass: ρV = (1240.0) * 2π ≈ 7791.1497
// Ix = Iy : 1/12 * m(3r² + h²) = m/12 * (3 + 4) ≈ 4544.83
// Iz : ½mr² ≈ 3895.57
gz::math::Inertiald meshInertial;
meshInertial.SetMassMatrix(gz::math::MassMatrix3d(
7791.1497,
gz::math::Vector3d(4544.83, 4544.83, 3895.57),
gz::math::Vector3d::Zero
));
meshInertial.SetPose(gz::math::Pose3d::Zero);
gz::math::Matrix3 inertiaMatrix = meshInertial.Moi();

// Check the Inertia Matrix within a tolerance of 0.005 since we are
// comparing a mesh cylinder with an ideal cylinder. For values more closer
// to the ideal, a higher number of vertices would be required in mesh
EXPECT_TRUE(
link.WorldInertiaMatrix(*ecm).value().Equal(inertiaMatrix, 0.005));

// Check the Inertial Pose and Link Pose
EXPECT_EQ(link.WorldPose(*ecm).value(), gz::math::Pose3d::Zero);

// Since the height of cylinder is 2m and origin is at center of bottom face
// the center of mass (inertial pose) will be 1m above the ground
EXPECT_EQ(link.WorldInertialPose(*ecm).value(),
gz::math::Pose3d(0, 0, 1, 0, 0, 0));
}
6 changes: 6 additions & 0 deletions test/worlds/mesh_inertia_calculation.sdf
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,11 @@
<name>cylinder_dae</name>
<pose>0 0 0 0 0 0</pose>
</include>

<include>
<uri>cylinder_dae_bottom_origin</uri>
<name>cylinder_dae_bottom_origin</name>
<pose>4 0 0 0 0 0</pose>
</include>
</world>
</sdf>

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions test/worlds/models/cylinder_dae_bottom_origin/model.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0"?>

<model>
<name>cylinder_dae_bottom_origin</name>
<version>1.0</version>
<sdf version="1.11">model.sdf</sdf>

<author>
<name>Jasmeet Singh</name>
<email>[email protected]</email>
</author>

<description>
A model of a Cylinder collada with mesh origin at the center of the bottom face
</description>
</model>
33 changes: 33 additions & 0 deletions test/worlds/models/cylinder_dae_bottom_origin/model.sdf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" ?>
<sdf version="1.11">
<model name="cylinder_dae_bottom_origin">
<link name="cylinder_dae_bottom_origin">
<pose>0 0 0 0 0 0</pose>
<inertial auto="true" />
<collision name="cylinder_collision">
<density>1240.0</density>
<auto_inertia_params>
<gz:voxel_size>0.01</gz:voxel_size>
</auto_inertia_params>
<geometry>
<mesh>
<uri>meshes/cylinder_bottom_origin.dae</uri>
</mesh>
</geometry>
</collision>
<visual name="cylinder_visual">
<pose>0 0 0 0 0 0</pose>
<geometry>
<mesh>
<uri>meshes/cylinder_bottom_origin.dae</uri>
</mesh>
</geometry>
<material>
<diffuse>0.7 0.7 0.7 1.0</diffuse>
<ambient>0.7 0.7 0.7 1.0</ambient>
<specular>0.7 0.7 0.7 1.0</specular>
</material>
</visual>
</link>
</model>
</sdf>
5 changes: 0 additions & 5 deletions tutorials/auto_inertia_calculation.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,6 @@ the center of mass in this case).

Here are some key points to consider when using automatic inertia calculation with 3D Meshes:
* Water-tight triangle meshes are required for the Mesh Inertia Calculator.
* Currently, the mesh inertia is calculated about the mesh origin. Since the link
inertia value needs to be about the Center of Mass, the mesh origin needs to be set
at the Center of Mass (Centroid). Functions to transform the inertia matrix to the mesh
centroid in case the mesh origin is set elsewhere are under development. Therefore, this
should hopefully be fixed in the future.
* Since the vertex data is used for inertia calculations, high vertex count would be
needed for near ideal values. However, it is recommended to use basic shapes with the
geometry tag (Box, Capsule, Cylinder, Ellipsoid and Sphere) as collision geometries get
Expand Down