trimesh icon indicating copy to clipboard operation
trimesh copied to clipboard

Incorrect flipping of normals during convex hull calculation

Open clemense opened this issue 2 years ago • 1 comments

Hi, I get the following unexpected behavior - a face normal gets flipped that is not supposed to be flipped during convex hull calculation of an already convex mesh.

import trimesh
import numpy as np
import pickle

# helper function to check whether center mass is inside the mesh
# same check is used in trimesh.poses.compute_stable_poses
def center_mass_inside(mesh):
    assert mesh.is_convex
    dots = np.einsum("ij,ij->i", mesh.center_mass - mesh.triangles_center, mesh.face_normals)
    return np.all(dots < 0)

m = pickle.load(open('convex_hull_bug_mesh.pkl', 'rb'))  # File is attached, sorry couldn't export to a mesh format (STL etc) since then the bug can't be reproduced - I assume some processing steps during export / load make it disappear

print(m.is_convex)   # Outputs True - as expected

print(center_mass_inside(m))  # Outputs True - as expected

print(center_mass_inside(m.convex_hull))  # Outputs False - not expected

I am seeing a similar check in https://github.com/mikedh/trimesh/blob/320c9ff7e6788db3feef1ceeca0607f2327e66b2/trimesh/convex.py#L102 but wonder how likely it is that this part of the code fails for certain corner cases?

convex_hull_bug_mesh.zip

clemense avatar Nov 01 '23 18:11 clemense

Thanks for the report! Yeah this one is confusing. I traced it down to convex.fix_normals(multibody=False) which flips the single face to maintain consistent winding. If you comment that out this case no longer happens but then you sometimes have convex hulls without consistant winding.

There is one face that this is happening on looks all good (nonzero area, has normal, etc)

In [19]: (mesh.center_mass - mesh.triangles_center)[broken]
Out[19]: array([[0.07837, 0.04178, 0.04442]])

In [20]: mesh.face_normals[broken]
Out[20]: array([[ 1., -0.,  0.]])

In [21]: mesh.center_mass
Out[21]: array([-0.     ,  0.01133,  0.16   ])

In [22]: mesh.centroid
Out[22]: array([-0.00018,  0.01374,  0.16079])

In [23]: mesh.area_faces[broken]
Out[23]: array([0.00028])

In [24]: mesh.is_winding_consistent
Out[24]: True

In [25]: mesh.is_watertight
Out[25]: True

However in the viewer it does appear to be a back-face which has all the correct topology, but backwards/mirrored?: image

I would usually expect qhull to get rid of these faces since it's only operating on the vertices? Updated debugging:

File Edit Options Buffers Tools Python Help                                                  
import trimesh
import numpy as np
import pickle


# helper function to check whether center mass is inside the mesh                            
# same check is used in trimesh.poses.compute_stable_poses                                   
def center_mass_inside(mesh):
    assert mesh.is_convex
    dots = trimesh.util.diagonal_dot(
        mesh.center_mass - mesh.triangles_center,
        mesh.face_normals)

    return dots < 0


if __name__ == "__main__":
    trimesh.util.attach_to_log()

    old = pickle.load(
        open("convex_hull_bug_mesh.pkl", "rb")
    )  # File is attached, sorry couldn't export to a mesh format (STL etc) since then the b\
ug can't be reproduced - I assume some processing steps during export / load make it disappe\
ar                                                                                           

    m = trimesh.Trimesh(vertices=old.vertices, faces=old.faces, process=False)

    assert np.allclose(m.vertices, old.vertices)

    assert m.is_convex
    assert center_mass_inside(m).all()

    broken = ~center_mass_inside(m.convex_hull)

    m.convex_hull.visual.face_colors[broken] = [255,0,0,255]
    m.show(smooth=False)


    assert center_mass_inside(m.convex_hull)

mikedh avatar Nov 09 '23 19:11 mikedh