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 :
- This nice paper by Cha Zhang and Tsuhan Chen
- SO : How to calculate the volume of a 3D mesh object the surface of which is made up triangles
- SO : How to compute the centroid of a mesh with triangular faces?
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.
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;
}
}