Skip to content

Commit 8e8c3e7

Browse files
committed
Update test to include non-uniform case
1 parent f124dc4 commit 8e8c3e7

File tree

3 files changed

+50
-36
lines changed

3 files changed

+50
-36
lines changed

pytorch3d/ops/watertight.py

+33
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

pytorch3d/structures/meshes.py

-30
Original file line numberDiff line numberDiff line change
@@ -1545,36 +1545,6 @@ def sample_textures(self, fragments):
15451545
else:
15461546
raise ValueError("Meshes does not have textures")
15471547

1548-
def volume_centroid(self):
1549-
"""
1550-
Compute the volumetric centroid of this mesh, which is distinct from the center of mass.
1551-
The center of mass (average of all vertices) will be closer to where there are a
1552-
higher density of points in a mesh are, but the centroid, which is based on volume,
1553-
will be closer to a perceived center of the mesh, as opposed to based on the density
1554-
of vertices. This function assumes that the mesh is watertight, and that the faces are
1555-
all oriented in the same direction.
1556-
1557-
Returns:
1558-
The position of the centroid as a tensor of shape (3).
1559-
"""
1560-
v_idxs = self.faces_padded().split([1, 1, 1], dim=-1)
1561-
verts = self.verts_padded()
1562-
1563-
v0, v1, v2 = [torch.gather(verts, 1, idx.expand(-1, -1, 3)) for idx in v_idxs]
1564-
1565-
tetra_center = (v0 + v1 + v2) / 4
1566-
signed_tetra_vol = (v0 * torch.cross(v1, v2, dim=-1)).sum(
1567-
dim=-1, keepdim=True
1568-
) / 6
1569-
denom = signed_tetra_vol.sum(dim=-2)
1570-
# clamp the denominator to prevent instability for degenerate meshes.
1571-
denom = torch.where(
1572-
denom < 0,
1573-
denom.clamp(max=-1e-5),
1574-
denom.clamp(min=1e-5)
1575-
)
1576-
return (tetra_center * signed_tetra_vol).sum(dim=-2) / denom
1577-
15781548
def submeshes(
15791549
self,
15801550
face_indices: Union[

tests/test_meshes.py

+17-6
Original file line numberDiff line numberDiff line change
@@ -1299,13 +1299,24 @@ def test_assigned_normals(self):
12991299
self.assertFalse(torch.allclose(yes_normals.verts_normals_padded(), verts))
13001300

13011301
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+
13021307
cube = init_cube_meshes()
1303-
self.assertClose(cube.volume_centroid(), torch.tensor([
1304-
[0.5] * 3,
1305-
[1.5] * 3,
1306-
[2.5] * 3,
1307-
[3.5] * 3,
1308-
]))
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+
13091320
def test_submeshes(self):
13101321
empty_mesh = Meshes([], [])
13111322
# Four cubes with offsets [0, 1, 2, 3].

0 commit comments

Comments
 (0)