Skip to content

Commit b723b6a

Browse files
committed
Add centroid function
Add test and switch to padded instead of packed Update test to include non-uniform case
1 parent 37bd280 commit b723b6a

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

pytorch3d/ops/watertight.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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

tests/test_meshes.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,25 @@ def test_assigned_normals(self):
12981298
yes_normals.offset_verts_(torch.FloatTensor([1, 2, 3]).expand(12, 3))
12991299
self.assertFalse(torch.allclose(yes_normals.verts_normals_padded(), verts))
13001300

1301+
def test_centroid(self):
1302+
meshes = init_simple_mesh()
1303+
# Check that it returns a valid value for multiple meshes with an inconsistent number
1304+
# of vertices
1305+
meshes.volume_centroid()
1306+
1307+
cube = init_cube_meshes()
1308+
self.assertClose(
1309+
cube.volume_centroid(),
1310+
torch.tensor(
1311+
[
1312+
[0.5] * 3,
1313+
[1.5] * 3,
1314+
[2.5] * 3,
1315+
[3.5] * 3,
1316+
]
1317+
),
1318+
)
1319+
13011320
def test_submeshes(self):
13021321
empty_mesh = Meshes([], [])
13031322
# Four cubes with offsets [0, 1, 2, 3].

0 commit comments

Comments
 (0)