mikedh / trimesh

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

Trimesh.contains returning random results #181

Closed ngoldbaum closed 6 years ago

ngoldbaum commented 6 years ago

Take a look at the following script:

import trimesh
import numpy as np

mesh = trimesh.load_mesh('trianglified.obj', 'obj')
mesh_components = mesh.split()
positions = np.array([[0.50609589, 0.4977951, 0.50026703],
                      [0.50611115, 0.4977951, 0.50026703],
                      [0.50611115, 0.49781036, 0.50025177],
                      [0.50611115, 0.49781036, 0.50026703],
                      [0.5061264, 0.49777985, 0.50026703],
                      [0.5061264, 0.4977951, 0.50026703],
                      [0.5061264, 0.49781036, 0.50025177]])

clump123 = mesh_components[123]
print(clump123.contains(positions))

This was reduced from a more complicated case. I've uploaded the obj file here: http://use.yt/upload/b67ea165. You can download it with curl -JO http://use.yt/upload/b67ea165, it should create a file named trianglified.obj in the directory you run curl from.

If you run this script repeatedly you'll find that random points in the list of points getting passed in are found to be inside the mesh.

I'm using trimesh 2.30.55 installed from conda following the installation instructions in the docs. The issue happens both with and without passing use_embree to load_mesh.

mikedh commented 6 years ago

Thanks for the reproducible report!

This looks a lot like a scale/numerical tolerance issue: the overall scale of that mesh is tiny (1.5e-2) and the component is even smaller (8e-5).

If you scale the mesh so the component is to closer to a unit cube the results are a bit more reasonable:

    scale = 5e3 / mesh.scale
    positions *= scale
    mesh.apply_scale(scale)
    mesh_components = mesh.split()

One point looks to be on the surface and is reported as 'not contained' and the rest are.

With meshes like your input (bunch of super tiny potatoes) contains is probably always going to be a bit squirrely, you might try looking at each components bounding sphere:

    from sklearn.neighbors import BallTree

    spheres = [i.bounding_sphere for i in mesh_components]
    radius = np.array([i.primitive.radius for i in spheres])
    origin = np.array([i.primitive.center for i in spheres])

    tree = BallTree(positions)
    hits = tree.query_radius(origin, radius)
ngoldbaum commented 6 years ago

Thank you for the excellent suggestions. It would be great if trimesh could warn somehow when in the limit where contains might be squirrely (as you put it :smile:).

Other than that please feel free to close this issue. I've copied your comment to the student I have working with trimesh who originally ran into this issue.

mikedh commented 6 years ago

Sounds good- I also might add a QbB option (like qhull has) to ray tests, which automatically scales all input to a unit cube before doing queries, which should help with scale issues.