|
| 1 | +import torch |
| 2 | + |
| 3 | + |
| 4 | +def volume_centroid(mesh): |
| 5 | + """ |
| 6 | + Compute the volumetric centroid of this mesh, which is distinct from the center of mass. |
| 7 | + The center of mass (average of all vertices) will be closer to where there are a |
| 8 | + higher density of points in a mesh are, but the centroid, which is based on volume, |
| 9 | + will be closer to a perceived center of the mesh, as opposed to based on the density |
| 10 | + of vertices. This function assumes that the mesh is watertight, and that the faces are |
| 11 | + all oriented in the same direction. |
| 12 | + Returns: |
| 13 | + The position of the centroid as a tensor of shape (3). |
| 14 | + """ |
| 15 | + v_idxs = mesh.faces_padded().split([1, 1, 1], dim=-1) |
| 16 | + verts = mesh.verts_padded() |
| 17 | + valid = (mesh.faces_padded() != -1).all(dim=-1, keepdim=True) |
| 18 | + |
| 19 | + v0, v1, v2 = [ |
| 20 | + torch.gather( |
| 21 | + verts, |
| 22 | + 1, |
| 23 | + idx.where(valid, torch.zeros_like(idx)).expand(-1, -1, 3), |
| 24 | + ).where(valid, torch.zeros_like(idx, dtype=verts.dtype)) |
| 25 | + for idx in v_idxs |
| 26 | + ] |
| 27 | + |
| 28 | + tetra_center = (v0 + v1 + v2) / 4 |
| 29 | + signed_tetra_vol = (v0 * torch.cross(v1, v2, dim=-1)).sum(dim=-1, keepdim=True) / 6 |
| 30 | + denom = signed_tetra_vol.sum(dim=-2) |
| 31 | + # clamp the denominator to prevent instability for degenerate meshes. |
| 32 | + denom = torch.where(denom < 0, denom.clamp(max=-1e-5), denom.clamp(min=1e-5)) |
| 33 | + return (tetra_center * signed_tetra_vol).sum(dim=-2) / denom |
0 commit comments