7

I'm trying to calculate the centroid of a 3D mesh of triangles.

EDIT : it turns out I was not, I was trying to calculate the center of gravity, which is not the same

My code is made of bits and pieces, mainly :

I compared my results to those provided by Rhino. I calculate the centroid and volume :

  • of the reference NURBS volume with Rhino 7
  • of a 27k triangle mesh with Rhino 7
  • of a simplified 1k triangle mesh with Rhino 7
  • of the same 1k triangle mesh with my code.

enter image description here

As you can see, it works great to calculate the volume, but not for the centroid, and i can't seem to know why. I need the error to be less than 0.01. I checked everything several times, but there must be something obvious.

I'm not great with numerical instability :

  • should I work in milimeters instead of meters ?
  • should I calculate the tetrahedrons signed volume with another point than the origin, as suggested by galinette in the second reference ? I tried and it didn't improve much.

MY CODE

Before calculationg anything, I check that my mesh is correct (code not provided) :

  • closed mesh, no naked edges or any holes ;
  • the vertices of all triangles are ordered consistently, i.e. triangles are correctly oriented towards the outside of the mesh.
using HelixToolkit.Wpf;
using System.Collections.Generic;
using System.Windows.Media.Media3D;

internal static class CentroidHelper
{
    public static Point3D Centroid(this List<MeshGeometry3D> meshes, out double volume)
    {
        Vector3D centroid = new Vector3D();
        volume = 0;

        foreach (var mesh in meshes)
        {
            var c = mesh.Centroid(out double v);
            volume += v;
            centroid += v *c ;
        }

        return (centroid / volume).ToPoint3D();
    }

    public static Vector3D Centroid(this MeshGeometry3D mesh, out double volume)
    {
        Vector3D centroid = new Vector3D();
        double totalArea = 0;
        volume = 0;

        for (int i = 0; i < mesh.TriangleIndices.Count; i += 3)
        {
            var a = mesh.Positions[mesh.TriangleIndices[i + 0]].ToVector3D();
            var b = mesh.Positions[mesh.TriangleIndices[i + 1]].ToVector3D();
            var c = mesh.Positions[mesh.TriangleIndices[i + 2]].ToVector3D();
            var triangleArea = AreaOfTriangle(a, b, c);
            totalArea += triangleArea;
            centroid += triangleArea * (a + b + c) / 3;
                
            volume += SignedVolumeOfTetrahedron(a, b, c);
        }
        return centroid / totalArea;
    }

    private static double SignedVolumeOfTetrahedron(Vector3D a, Vector3D b, Vector3D c)
    {
        return Vector3D.DotProduct(a, Vector3D.CrossProduct(b, c)) / 6.0d;
    }

    private static double AreaOfTriangle(Vector3D a, Vector3D b, Vector3D c)
    {
        return 0.5d * Vector3D.CrossProduct(b - a, c - a).Length;
    }
}    
geriwald
  • 190
  • 1
  • 4
  • 17
  • I may have found the answer to that question : it seems that the method I used doesn't work. I still have to check but i think it only works for convex solids. I should post the question on a maths forum. I have still to do more checks, but it seems that calculating the average of the centroids of the unit tetragons weighted by their algebraic mass works. I will explain it in more details once I have checked my results. – geriwald Apr 08 '21 at 17:06
  • [See also this post of mine](https://stackoverflow.com/a/58286695/380384) on how to calculate mass properties of an STL file (triangle mesh). – John Alexiou Apr 10 '21 at 23:02

4 Answers4

4

It turns out the commonly accepted answer in stack overflow and all over the internet is wrong :

The geometric centroid DOES NOT always coincide with the center of mass, although it is very close.

It is only true for some volumes, as n-dimensional simplexes and platonic solids.

I have checked this by comparing my results to Rhino's and to reality by calculating a ship's hull position at equilibrium.

After thinking about it a lot, it seems quite obvious to me now, but you are very welcome to check and criticise my method.

METHOD

I use the principles of the method described in Zhang & Chen's paper, with the center of mass.

The center of mass of a tetrahedron does coincide with its centroid (i.e. the isobarycenter of its vertices).

The barycenter of the centroids weighted by the signed volume of the tetrahedrons is the centroid of the mesh.

Unit tetrahedron, from 3

CODE

internal static class CentroidHelper
{
    public static Point3D Centroid(this List<MeshGeometry3D> meshes, out double volume)
    {
        Vector3D centroid = new Vector3D();
        volume = 0;

        foreach (var mesh in meshes)
        {
            var c = mesh.Centroid(out double v);
            volume += v;
            centroid += v * c;
        }
        return (centroid / volume).ToPoint3D();
    }

    public static Vector3D Centroid(this MeshGeometry3D mesh, out double volume)
    {
        Vector3D centroid = new Vector3D();
        volume = 0;

        for (int i = 0; i < mesh.TriangleIndices.Count; i += 3)
        {
            var a = mesh.Positions[mesh.TriangleIndices[i + 0]].ToVector3D();
            var b = mesh.Positions[mesh.TriangleIndices[i + 1]].ToVector3D();
            var c = mesh.Positions[mesh.TriangleIndices[i + 2]].ToVector3D();
            var tetrahedronVolume = SignedVolumeOfTetrahedron(a, b, c);
            centroid += tetrahedronVolume * (a + b + c) / 4.0d;
            volume += tetrahedronVolume;
        }
        return centroid / volume;
    }

    private static double SignedVolumeOfTetrahedron(Vector3D a, Vector3D b, Vector3D c)
    {
        return Vector3D.DotProduct(a, Vector3D.CrossProduct(b, c)) / 6.0d;
    }
}
geriwald
  • 190
  • 1
  • 4
  • 17
  • 1
    I offered all the reputation I had as a bounty on this question, so i can't comment on the other questions about this subject yet. I will do that when i get some reputation back. – geriwald Apr 09 '21 at 08:09
  • From [Wikipedia Center of Mass](https://en.wikipedia.org/wiki/Center_of_mass) "If a continuous mass distribution has uniform density, which means ρ is constant, then the center of mass is the same as the centroid of the volume." Note here the centroid is calculated for the whole volume rather than just the vertices. – Salix alba Apr 09 '21 at 20:16
2

The problem is in centroid += triangleArea * (a + b + c) / 3

This gives the centroid of a triangle, but for center of mass and volume calculations, you need the volume centroid of the triangular pyramid with base (a,b,c) and tip the origin.

but to understand look at properties of the following volume element defined for each triangle.

trig

What is marked as CG above is the volume centroid of the pyramid and together with the volume of the pyramid its weighted average defines the center of mass of the entire shape.

Given vectors A, B, and C you have the weighted average as

for all triangles
  ΔV = A · (B × C)/6
  V += ΔV
  CG += ΔV * (A + B + C)/4
next

CG = CG/V

where · is the vector dot product, and × the vector cross product.

The key here is that you need

centroid += triangleVolume * (a + b + c) / 4

since the centroid of a pyramid is 1/4 the distance from the base. Here is the relevant Wikipedia paragraph

wiki

John Alexiou
  • 28,472
  • 11
  • 77
  • 133
  • Yes, it’s what I do in the accepted answer – geriwald Apr 10 '21 at 23:26
  • 1
    Although your result is correct, your demonstration is not. You should take a look at the definition of a pyramid. What you drew is a tetrahedron. It’s another wikipedia page. – geriwald Apr 10 '21 at 23:29
  • @geriwald it is a [triangular pyramid](https://mathworld.wolfram.com/TriangularPyramid.html#:~:text=A%20triangular%20pyramid%20is%20a,gonal%20pyramid%20with%20%2C%20given%20by) – John Alexiou Apr 11 '21 at 13:50
  • Oh right I read your answer too fast. I was thrown off by your first line : in my first version i was not trying to calculate the centroid of the tetraedron, but of the triangle, as suggested in the accepted answers i cite in the beginning of my question. I didn't explain that, that's true. – geriwald Apr 14 '21 at 15:37
1

I'll start by calculating the volume and move onto the centroid once the basic idea is established.

This answer is going to sound a little like black magic. We can use the Divergence_theorem here. This theorem translates problems of integration over a volume with calculation over the surface of the object.

To understand it you really need some calculus. If you know a little integration then we can calculate the integral just by evaluating the end points (the boundary of the interval).

enter image description here

The divergence theorem works much the same, and we can calculate the integral of some quantity by evaluating a related quantity over the boundary, but now the boundary is the surface itself.

enter image description here

Here F is some vector valued quantity, V stands for the volume, S the surface, n the normal and ∇ the divergence operator.

For our case we would want the bit inside the LHS integral to just be 1. So we calculate the integral of 1 over the volume. There are a few candidates for this and the simplest is just F(x,y,z) = (x,0,0). ( ∇ . F = df/dx + df/dy + df/dz = 1 + 0 + 0 = 0).

For the RHS we now integrate F(x,y,z).N over the surface. As our surface is a triangulated mesh this means that we take the sum

sum_over_all_triangles (integral of F.N for that triangle).

So the task boils down to finding the integral of the function over the triangle. This is a lot simpler than it seems. For a vertex A=(x,y,z), F(A) = (x,0,0) and if the unit outwards normal is (nx, ny, nz) then F(A) . N = (x,0,0) . (nx,ny,nz) = x * nx, call this quantity fA. Calculate the quantities fB and fC as well.

For points inside the triangle the quantity can be calculated as a linear conbination of the values at the coordinates. A long-winded integrations yields the simple formula

  1/3 area ( fA + fB + fC)

where area is the area of the triangle, and we are done.

So to put all the above in psudocode

total = 0
for each triangle:
    let A = (ax,ay,az), B, C be three verticies
    let N = (nx,ny,nz) be the unit outward normal
    let area be the triangle area
    fA = ax * nx
    fB = bx * nx
    fC = cx * nx
    total += 1/3 * area * (fA + fB + fC)

Told you its black magic.


Now for the centroid we apply the same technique but use three different functions for F. The x coordinate of the centroid is given by

enter image description here

A candidate for F is then F(x,y,z) = (1/2 x^2, 0, 0) as ∇ . F = x.

Things work similarly but not our function is non-linear, so the calculation for the integral of each triangle will be more complex.

We can use Barycentric coordinate system to make the integral over the triangle easier.

integral over triangle

Calculating this for

f(x,y,z) = F(x,y,z) . (nx,ny,nz) = 1/2 x^2 * nx

gives the integration over a triangle as

1/12 area * nx (ax^2 + bx^2 + cx^2 + ax bx + ax cx + bx cx)

I used wolfram alpha to do the integration. The pseudocode for calculation volume and centroid is then

volume = 0
Cx = 0
Cy = 0
Cz = 0
for each triangle:
    let A = (ax,ay,az), B, C be three verticies
    let N = (nx,ny,nz) be the unit outward normal
    let area be the triangle area

    volume += 1/3 * area * (ax + bx + cx) * nx
    Cx += 1/12 area * nx (ax^2 + bx^2 + cx^2 + ax bx + ax cx + bx cx)
    Cy += 1/12 area * ny (ay^2 + by^2 + cy^2 + ay by + ay cy + by cy)
    Cz += 1/12 area * nz (az^2 + bz^2 + cz^2 + az bz + az cz + bz cz)

Cx /= volume
Cy /= volume
Cz /= volume

I've incorporated to code in my own program and checked volumes and centroids results with various objects with known volumes, such as elipsoids, SingSurf volume calculator.


If we choose F(x,y,z) = 1/3 (x,y,z), we can see that the formula for volumes works out to be identical to that found by summing signed volumes of tetrahedrons.

V= int 1/6 A . B x C

Using the divergence theorem, with r = (x,y,z)

enter image description here

For a linear function, g(r), the integral over the triangle is the area times the mean of the function evaluated at the vertices.

int over triangle g = 1/3 area * (g(A)+g(B)+g(C))

Here

enter image description here

Now a non-normalised normal can be calculated as n = (B-A) x (C-A). The area = 1/2 | n | and the unit normal n-hat = n / |n| so area * n-hat = 1/2 n.

enter image description here

As required.

Salix alba
  • 7,536
  • 2
  • 32
  • 38
  • The factor of `1/3` is incorrect here. The correct factor is `1/4` for a pyramid volume element. – John Alexiou Apr 10 '21 at 23:19
  • I don’t understand why your result favors the x coordinate. This seems suspicious. – geriwald Apr 11 '21 at 08:55
  • @JohnAlexiou I'm not calculating volumes of pyramidal elements. I'm integrating a quantity over a triangle. The thing about this algorithm is we don't need to split the volume into 3-simplexes, just triangulate the surface. – Salix alba Apr 11 '21 at 09:37
  • @geriwald Any direction would do. I would get the same result using the y or z direction. Indeed we could choose any vector field so that ∇. F = 1. I could even pick F=(1/3 x,1/3 y, 1/3 z). – Salix alba Apr 11 '21 at 09:44
  • Note that the (not unit) normal of the _ABC_ triangle is `n=(a × b + b × c + c × d)` and the area of the triangle is `A = 1/2 |(a × b + b × c + c × d)|` – John Alexiou Apr 11 '21 at 13:54
  • Ok, in your version of the message when I put my comment, it wasn't clear (to me) that you were calculating the X coordinate of the centroid. – geriwald Apr 14 '21 at 15:40
  • 1
    I haven't been on SO for a few days as i was testing my software (btw my solution works great, giving a relative difference less than 1e-9 compared to Rhino). – geriwald Apr 14 '21 at 15:42
  • I will test your solution when i have more time. I'm giving you the rep for all your efforts and the nice maths formulas. – geriwald Apr 14 '21 at 15:43
  • I'm not sure my method really offers any advantage. It is a long mathematical route to get to what is essentially the same formula. In terms of computational complexity, there is little advantage, unless unit normals and areas of faces are known. – Salix alba Apr 15 '21 at 04:39
0

I think you're making it a bit more complicated than it needs to be. All you need to do is work out the average x,y,z of all the points and that is your center. You can do each individually, so the pseudocode would be:

xSum=0;
ySum=0;
zSum=0;
pointCount=0;
for each triangle in meshArray {
 for each point in triangle {
   xSum += point.x;
   ySum += point.y;
   zSum += point.z;
   pointCount++;
 }
}
centerPointX = xSum / pointCount;
centerPointY = ySum / pointCount;
centerPointZ = zSum / pointCount;

- EDIT -

For smoothing out an unbalanced mesh, you could process the meshArray into equally sized voxels (3D area cubes) with a granularity of your choosing depending on the accuracy required.

So for 100x100x100 voxel point cloud smoothing:

for (x=0;x<=100;x++){
 for (y=0;y<=100;y++){
  for (z=0;z<=100;z++){
   hasPoint = false;
   for each triangle in meshArray {
    for each point in triangle {
      if point.x,y,z contained with x,y,z of this voxel {hasPoint=true;}
    }
   }
   addPoint at voxel (x,y,z) center to lowDensityPointCloud
  }
 }
}

Then you take your lowDensityPointCloud and find the center point of that with the previous method.

James
  • 620
  • 3
  • 13
  • 1
    Well, I don’t think this works. Imagine a cube with 4 vertices on one side and the opposite side a very dense mesh of small triangles. Or even one more vertice than the first side. – geriwald Apr 08 '21 at 19:03
  • You're right. Then I would suggest processing the volume in voxels to get a smoothed center value for each voxel, then you can process the voxel results and take an average of that. Make sense? – James Apr 08 '21 at 19:10
  • I found the solution, I will post it tomorrow, it’s a bit complicated to explain in a comment. – geriwald Apr 08 '21 at 19:28
  • I need a very good accuracy. This would not be computationally efficient. – geriwald Apr 08 '21 at 23:52