1

I want to calculate the mass, center of mass, and inertia tensor of player created objects. Some of the objects will be hollow instead of solid. I am creating a closed triangle mesh for their object. Note that this is for a physics building game so I want the values to be as accurate as possible (within reason).

Based on the references listed below, I can get the properties for a solid object by creating a tetrahedron from each triangle of the mesh and calculating the signed volume which gives the mass (volume * density) and the center of mass. I even got the inertia tensor calculation working.

How do I do the same for a hollow object?

  1. For the mass and center of mass, I am iterating through the triangles of the mesh and totaling their area and calculating the area-weighted average of their positions, then multiplying the surface area by a "density" value to get the mass.

     public static (float, Vector3) CalculateSurfaceArea(Mesh mesh)
     {
         Vector3[] vertices = mesh.vertices;
         int[] triangles = mesh.triangles;
    
         float totalArea = 0f;
         Vector3 temp = Vector3.zero;
    
         for (int i = 0; i <= triangles.Length - 3; i += 3)
         {
             Vector3 v1 = vertices[triangles[i]];
             Vector3 v2 = vertices[triangles[i + 1]];
             Vector3 v3 = vertices[triangles[i + 2]];
             Vector3 edge21 = v2 - v1;
             Vector3 edge31 = v3 - v1;
    
             float area = Vector3.Cross(edge21, edge31).magnitude / 2f; // area of the triangle
             totalArea += area;
    
             Vector3 triCenter = (v1 + v2 + v3) / 3f; // center of the triangle
             temp += triCenter * area;
         }
    
         Vector3 areaCenter = temp / totalArea;
    
         return (totalArea, areaCenter);
     }
    
  2. For the inertia tensor, I am trying a similar approach where I iterate through all the triangles and total up their moments of inertia and using the parallel-axis theorem to account for their positions but (a) I am not sure this is correct and (b) how do I calculate the products of inertia (Ixy, Ixz, Iyz)?

     public static (Vector3, Quaternion) CalculateHollowInertiaTensor(Mesh mesh)
     {
         Vector3[] vertices = mesh.vertices;
         int[] triangles = mesh.triangles;
    
         double Ixx = 0f;
         double Iyy = 0f;
         double Izz = 0f;
         double Ixy = 0f;
         double Ixz = 0f;
         double Iyz = 0f;
    
         for (int i = 0; i <= triangles.Length - 3; i += 3)
         {
             Vector3 v1 = vertices[triangles[i]];
             Vector3 v2 = vertices[triangles[i + 1]];
             Vector3 v3 = vertices[triangles[i + 2]];
             Vector3 edge21 = v2 - v1;
             Vector3 edge31 = v3 - v1;
             Vector3 center = (v1 + v2 + v3) / 3f; // center of the triangle
             Vector3 offset = center - v1;
    
             float area = Vector3.Cross(edge21, edge31).magnitude / 2f; // area of the triangle
    
             // Moment of inertia of triangle rotating around the first vertex
             // https://en.wikipedia.org/wiki/List_of_moments_of_inertia
             // I = (1/6)m(P.P + P.Q + Q.Q)
             float triIxx = (edge21.y * edge21.y + edge21.z * edge21.z + edge21.y * edge31.y + edge21.z * edge31.z + edge31.y * edge31.y + edge31.z * edge31.z) / 6f;
             float triIyy = (edge21.x * edge21.x + edge21.z * edge21.z + edge21.x * edge31.x + edge21.z * edge31.z + edge31.x * edge31.x + edge31.z * edge31.z) / 6f;
             float triIzz = (edge21.x * edge21.x + edge21.y * edge21.y + edge21.x * edge31.x + edge21.y * edge31.y + edge31.x * edge31.x + edge31.y * edge31.y) / 6f;
    
             // Shift to the center of the triangle
             triIxx -= offset.y * offset.y + offset.z * offset.z;
             triIyy -= offset.x * offset.x + offset.z * offset.z;
             triIzz -= offset.x * offset.x + offset.y * offset.y;
    
             // Shift to the origin (using parallel-axis theorem)
             triIxx += center.y * center.y + center.z * center.z;
             triIyy += center.x * center.x + center.z * center.z;
             triIzz += center.x * center.x + center.y * center.y;
    
             Ixx += triIxx * area;
             Iyy += triIyy * area;
             Izz += triIzz * area;
             //Ixy += area * center.x * center.y;
             //Ixz += area * center.x * center.z;
             //Iyz += area * center.y * center.z;
         }
    
         Matrix<double> inertiaTensor = Matrix<double>.Build.Dense(3, 3);
         inertiaTensor[0, 0] = Ixx;
         inertiaTensor[1, 1] = Iyy;
         inertiaTensor[2, 2] = Izz;
         inertiaTensor[0, 1] = inertiaTensor[1, 0] = -Ixy;
         inertiaTensor[0, 2] = inertiaTensor[2, 0] = -Ixz;
         inertiaTensor[1, 2] = inertiaTensor[2, 1] = -Iyz;
    
         Debug.Log(inertiaTensor);
    
         //Matrix<double> inertiaTensorInverse = inertiaTensor.Inverse();
    
         // Find the principal axes and simplified inertia tensor
         Evd<double> evd = inertiaTensor.Evd();
         Vector3 inertiaTensorDiagonal = MakeVector3(evd.EigenValues.Real());
         Quaternion inertiaTensorRotation = Quaternion.LookRotation(
             MakeVector3(evd.EigenVectors.Column(2)),
             MakeVector3(evd.EigenVectors.Column(1))
             );
    
         return (inertiaTensorDiagonal, inertiaTensorRotation);
     }
    

References I have found so far:

===== EDIT =====

Based on John Alexiou's answer, I reimplemented the algorithm for hollow objects:

    public static (float, Vector3, Vector3, Quaternion) CalculateHollowMassCenterInertiaTensor(Mesh mesh)
    {
        Vector3[] vertices = mesh.vertices;
        int[] triangles = mesh.triangles;

        double Ixx = 0f;
        double Iyy = 0f;
        double Izz = 0f;
        double Ixy = 0f;
        double Ixz = 0f;
        double Iyz = 0f;

        float totalMass = 0f;
        Vector3 temp = Vector3.zero;

        for (int i = 0; i <= triangles.Length - 3; i += 3)
        {
            Vector3 v1 = vertices[triangles[i]];
            Vector3 v2 = vertices[triangles[i + 1]];
            Vector3 v3 = vertices[triangles[i + 2]];

            Vector3 center = (v1 + v2 + v3) / 3f; // center of the triangle
            float area = (Vector3.Cross(v1, v2) + Vector3.Cross(v2, v3) + Vector3.Cross(v3, v1)).magnitude / 2f; // area of the triangle

            totalMass += area;
            temp += center * area;

            Ixx += area * (v1.y * v1.y + v1.z * v1.z + v2.y * v2.y + v2.z * v2.z + v3.y * v3.y + v3.z * v3.z) / 3f;
            Iyy += area * (v1.x * v1.x + v1.z * v1.z + v2.x * v2.x + v2.z * v2.z + v3.x * v3.x + v3.z * v3.z) / 3f;
            Izz += area * (v1.x * v1.x + v1.y * v1.y + v2.x * v2.x + v2.y * v2.y + v3.x * v3.x + v3.y * v3.y) / 3f;
            Ixy += area * (v1.x * v1.y + v2.x * v2.y + v3.x * v3.y) / 3f;
            Ixz += area * (v1.x * v1.z + v2.x * v2.z + v3.x * v3.z) / 3f;
            Iyz += area * (v1.y * v1.z + v2.y * v2.z + v3.y * v3.z) / 3f;
        }

        Vector3 centerOfMass = temp / totalMass;

        Matrix<double> inertiaTensor = Matrix<double>.Build.Dense(3, 3);
        inertiaTensor[0, 0] = Ixx;
        inertiaTensor[1, 1] = Iyy;
        inertiaTensor[2, 2] = Izz;
        inertiaTensor[0, 1] = inertiaTensor[1, 0] = -Ixy;
        inertiaTensor[0, 2] = inertiaTensor[2, 0] = -Ixz;
        inertiaTensor[1, 2] = inertiaTensor[2, 1] = -Iyz;

        Debug.Log(inertiaTensor);

        // Find the principal axes and simplified inertia tensor
        Evd<double> evd = inertiaTensor.Evd();
        Vector3 inertiaTensorDiagonal = MakeVector3(evd.EigenValues.Real());
        Quaternion inertiaTensorRotation = Quaternion.LookRotation(
                MakeVector3(evd.EigenVectors.Column(2)),
                MakeVector3(evd.EigenVectors.Column(1))
            );

        return (totalMass, centerOfMass, inertiaTensorDiagonal, inertiaTensorRotation);
    }

It is giving me the wrong values for the inertia tensor though.

mass Expected Inertial Tensor Actual Inertial Tensor
Cube 6.00 1.6667 1.6667 1.6667 3.0000 3.0000 3.0000
Sphere 3.11 0.5236 0.5236 0.5236 0.5151 0.5162 0.5163
Cylinder 7.80 4.5488 1.7671 4.5488 8.7134 1.8217 8.7134

Cube has 1m sides, Sphere has 0.5m radius, Cylinder has 0.5m radius and 2m height. All are centered around the origin.

Note that I am using the total surface area as the mass. Assume that the thickness * density = 1 so mass = area * thickness * density becomes mass = area * 1. I will be picking better values for those later because I assume that will not have that big of an effect on the algorithm.

Also note that some rounding is to be expected because the shapes are being approximated by a triangular mesh.

Sirius 5
  • 103
  • 9

1 Answers1

1

For a shell body to have mass and mass moment of inertia, the sides must have some thickness ε>0.

This defines the mass of a triangle defined by the vectors A, B and C and thickness ε and density ρ as

area = 1/2*Math.Abs( Vector3.Cross(A,B) + Vector3.Cross(B,C) + Vector3.Cross(C,A) )
mass = ρ*area*ε

or the above can be used to find the density from the total mass of an object, once the total surface area is calculated.

To find the mass moment of inertia you need to define a function returning the MMOI matrix of a unit mass particle from the location vector r=(x,y,z).

Matrix3 Mmoi(Vector3 r)
{
    //     | y^2+z^2    -x y      -x z   |
    // I = |  -x y     x^2+z^2    -y z   |
    //     |  -x z      -y z     x^2+y^2 |
    return new Matrix3(
        r.y*r.y + r.z*r.z, -r.x*r.y, -r.x*r.y,
        -r.x*r.y, r.x*r.x + r.z*r.y, -r.y*r.z,
        -r.x*r.z, -r.y*r.z, r.x*r.x + r.y*r.y);
}

and then calculate the MMOI of a triangle from the vertices and the mass

Matrix3 Mmoi(double m, Vector3 A, Vector3 B, Vector3 C)
{
    return (m/3)*(Mmoi(A)+Mmoi(B)+Mmoi(C));
}

The above is derived from the surface integral over the triangle, and since [SO] does not support math formatting I am omitting the details here.

Yes the above is true, the MMOI of a surface triangle is that of the average MMOI of the three vertices.

Update

In correlating the above with CAD I realized you have to integrate over both front and back surfaces of the triangle. The result that matches CAD mass properties is

Matrix3 Mmoi(double m, Vector3 A, Vector3 B, Vector3 C)
{
    return 2*(m/3)*(Mmoi(A)+Mmoi(B)+Mmoi(C));
}
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
  • "the MMOI of a surface triangle is that of the average MMOI of the three vertices" really? That seems too simple... – Sirius 5 Apr 14 '21 at 00:33
  • @Sirius5 - so the math works out, but when I did an example in CAD it got only 1/2 of the CAD properties from the above. Don't know why, but try `(2*m/3)*(Mmoi(A)+Mmoi(B)+Mmoi(C))` instead. I think for a solid you integrate the surface twice, front and back, and above I did not do that. – John Alexiou Apr 14 '21 at 01:20
  • [[this was meant to be before your last comment]] That got me closer, thanks, but the returned MMOI is too high: 3 instead of the 5/3 that I was expecting. From what I've been able to find, the MMOI of a hollow cube is I=5/3*m*s^2 where m is the mass of each face (using 1 kg), and s is the side length (using 1 m). – Sirius 5 Apr 14 '21 at 01:23
  • https://physics.stackexchange.com/a/105234 is one of the places that I got the formula from. Which coincidentally, is a question that you also answered. – Sirius 5 Apr 14 '21 at 01:29
  • I am not sure what is going on now. [CAD results](https://i.imgur.com/ViVDAfO.png) from a hollow cube of 1kg, with 1m side has **I=5/18**. – John Alexiou Apr 14 '21 at 13:18
  • 1
    That's because your cube has 1 kg mass, whereas mine is 1 kg *per face* for a total of 6 kg. Using your numbers: I = 5/3*(1/6)*(1)^2 = 5/18 – Sirius 5 Apr 14 '21 at 14:12
  • That still does not explain why the computer gives me the wrong value though. But it does give the right number for a sphere (0.5m radius). But not for a cylinder either (0.5m radius, 2m height) (Ix = Iz = 8.71 & Iy = 1.82 vs 4.55 & 1.77). – Sirius 5 Apr 14 '21 at 14:16
  • I edited the question to add the algorithm as I have it now. – Sirius 5 Apr 14 '21 at 14:43