mikedh / trimesh

Python library for loading and using triangular meshes.
https://trimesh.org
MIT License
3.02k stars 583 forks source link

face_adjacency doesn't find all adjacent faces #2186

Open tomas16 opened 8 months ago

tomas16 commented 8 months ago

trimesh version 4.1.4

# Face 2866 contains vertices 1795, 387 and 1792.

face = 2866

vertices_in_face = mesh.faces[face, :]
# TrackedArray([1795,  387, 1792])

mask = np.any(mesh.face_adjacency == face, axis=1)
face_pairs = mesh.face_adjacency[mask, :]
# array([[2744, 2866],
#        [2865, 2866]])

all_adjacent_faces = np.setdiff1d(face_pairs, face)
# array([2744, 2865])

mesh.faces[all_adjacent_faces, :]
# TrackedArray([[1792,  387,  465],
#               [1795,  384,  387]])
# => The only vertex shared by faces 2866, 2744 and 2865 is vertex 387
# => The shared edge between face 2866 and 2744 is the edge containing vertices 387 and 1792
# => The shared edge between face 2866 and 2865 is the edge containing vertices 387 and 1795
# => According to mesh.face_adjacency, there's no adjacent face on the edge containing vertices 1792 and 1795

# Let's double-check this...

faces_with_vertices = np.nonzero(np.any(mesh.faces == 1792, axis=1) & np.any(mesh.faces == 1795, axis=1))[0]
# array([2866, 8222, 8961])

assert face in faces_with_vertices

mesh.faces[faces_with_vertices, :]
# TrackedArray([[1795,  387, 1792],
#               [1795, 1792, 3914],
#               [1795, 4423, 1792]])

# => Our original face 2866 does in fact have 2 additional adjacent faces not found by mesh.face_adjacency !

Before opening this issue, I found https://github.com/mikedh/trimesh/issues/1545

Not sure I follow that one entirely, but does it mean for face_adjacency to detect adjacent faces, the shared edge must have a certain orientation? If that's the case, the "bug" here would be a documentation bug, as it isn't mentioned here.

mikedh commented 8 months ago

Yeah perhaps the docstring should be clearer, is the situation here is "there are N faces sharing an edge where N > 2"? I.e. the mesh is not watertight/manifold/etc? Maybe a more exact definition of what face_adjacency is doing is:

"If an edge occurs exactly twice, mesh.face_adjacency returns the indexes of the two faces that contain that edge."

tomas16 commented 8 months ago

Thanks, that explains the difference. Not what I expected from "face_adjacency" 😉