Skip to content

Commit 8596fca

Browse files
Krzysztof Chalupkafacebook-github-bot
Krzysztof Chalupka
authored andcommitted
Submesh 2/n: to_sorted
Summary: Sort a mesh's vertices in alphabetical order, and resort the face coords accordingly. Textured meshes are not supported yet, but will be added down the stack. This, togehter with mesh equality, can be used to compare two meshes in a way invariant to vertex permutations, as shown in the unit tests. We do not want the submeshing mechanism to guarantee any particular vertex order, leaving that up to the implementation, so we need this function for testing. Reviewed By: bottler Differential Revision: D35440656 fbshipit-source-id: 5a4dd921fdb00625a33da08b5fea79e20ac6402c
1 parent 7f097b0 commit 8596fca

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed

tests/test_meshes.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,73 @@ def mesh_structures_equal(mesh1, mesh2) -> bool:
166166
return True
167167

168168

169+
def to_sorted(mesh: Meshes) -> "Meshes":
170+
"""
171+
Create a new Meshes object, where each sub-mesh's vertices are sorted
172+
alphabetically.
173+
174+
Returns:
175+
A Meshes object with the same topology as this mesh, with vertices sorted
176+
alphabetically.
177+
178+
Example:
179+
180+
For a mesh with verts [[2.3, .2, .4], [.0, .1, .2], [.0, .0, .1]] and a single
181+
face [[0, 1, 2]], to_sorted will create a new mesh with verts [[.0, .0, .1],
182+
[.0, .1, .2], [2.3, .2, .4]] and a single face [[2, 1, 0]]. This is useful to
183+
create a semi-canonical representation of the mesh that is invariant to vertex
184+
permutations, but not invariant to coordinate frame changes.
185+
"""
186+
if mesh.textures is not None:
187+
raise NotImplementedError(
188+
"to_sorted is not implemented for meshes with "
189+
f"{type(mesh.textures).__name__} textures."
190+
)
191+
192+
verts_list = mesh.verts_list()
193+
faces_list = mesh.faces_list()
194+
verts_sorted_list = []
195+
faces_sorted_list = []
196+
197+
for verts, faces in zip(verts_list, faces_list):
198+
# Argsort the vertices alphabetically: sort_ids[k] corresponds to the id of
199+
# the vertex in the non-sorted mesh that should sit at index k in the sorted mesh.
200+
sort_ids = torch.tensor(
201+
[
202+
idx_and_val[0]
203+
for idx_and_val in sorted(
204+
enumerate(verts.tolist()),
205+
key=lambda idx_and_val: idx_and_val[1],
206+
)
207+
],
208+
device=mesh.device,
209+
)
210+
211+
# Resort the vertices. index_select allocates new memory.
212+
verts_sorted = verts[sort_ids]
213+
verts_sorted_list.append(verts_sorted)
214+
215+
# The `faces` tensor contains vertex ids. Substitute old vertex ids for the
216+
# new ones. new_vertex_ids is the inverse of sort_ids: new_vertex_ids[k]
217+
# corresponds to the id of the vertex in the sorted mesh that is the same as
218+
# vertex k in the non-sorted mesh.
219+
new_vertex_ids = torch.argsort(sort_ids)
220+
faces_sorted = (
221+
torch.gather(new_vertex_ids, 0, faces.flatten())
222+
.reshape(faces.shape)
223+
.clone()
224+
)
225+
faces_sorted_list.append(faces_sorted)
226+
227+
other = mesh.__class__(verts=verts_sorted_list, faces=faces_sorted_list)
228+
for k in mesh._INTERNAL_TENSORS:
229+
v = getattr(mesh, k)
230+
if torch.is_tensor(v):
231+
setattr(other, k, v.clone())
232+
233+
return other
234+
235+
169236
class TestMeshes(TestCaseMixin, unittest.TestCase):
170237
def setUp(self) -> None:
171238
np.random.seed(42)
@@ -1223,6 +1290,57 @@ def test_equality(self):
12231290
self.assertFalse(mesh_structures_equal(meshes1, meshes2))
12241291
self.assertFalse(mesh_structures_equal(meshes1, meshes3))
12251292

1293+
def test_to_sorted(self):
1294+
mesh = init_simple_mesh()
1295+
sorted_mesh = to_sorted(mesh)
1296+
1297+
expected_verts = [
1298+
torch.tensor(
1299+
[[0.1, 0.3, 0.5], [0.5, 0.2, 0.1], [0.6, 0.8, 0.7]],
1300+
dtype=torch.float32,
1301+
),
1302+
torch.tensor(
1303+
# Vertex permutation: 0->0, 1->3, 2->2, 3->1
1304+
[[0.1, 0.3, 0.3], [0.1, 0.5, 0.3], [0.2, 0.3, 0.4], [0.6, 0.7, 0.8]],
1305+
dtype=torch.float32,
1306+
),
1307+
torch.tensor(
1308+
# Vertex permutation: 0->2, 1->1, 2->4, 3->0, 4->3
1309+
[
1310+
[0.2, 0.3, 0.4],
1311+
[0.2, 0.4, 0.8],
1312+
[0.7, 0.3, 0.6],
1313+
[0.9, 0.3, 0.8],
1314+
[0.9, 0.5, 0.2],
1315+
],
1316+
dtype=torch.float32,
1317+
),
1318+
]
1319+
1320+
expected_faces = [
1321+
torch.tensor([[0, 1, 2]], dtype=torch.int64),
1322+
torch.tensor([[0, 3, 2], [3, 2, 1]], dtype=torch.int64),
1323+
torch.tensor(
1324+
[
1325+
[1, 4, 2],
1326+
[2, 1, 0],
1327+
[4, 0, 1],
1328+
[3, 0, 4],
1329+
[3, 2, 1],
1330+
[3, 0, 1],
1331+
[3, 4, 1],
1332+
],
1333+
dtype=torch.int64,
1334+
),
1335+
]
1336+
1337+
self.assertFalse(mesh_structures_equal(mesh, sorted_mesh))
1338+
self.assertTrue(
1339+
mesh_structures_equal(
1340+
Meshes(verts=expected_verts, faces=expected_faces), sorted_mesh
1341+
)
1342+
)
1343+
12261344
@staticmethod
12271345
def compute_packed_with_init(
12281346
num_meshes: int = 10, max_v: int = 100, max_f: int = 300, device: str = "cpu"

0 commit comments

Comments
 (0)