Skip to content

Commit

Permalink
ray test fallback to fix signed distance bug
Browse files Browse the repository at this point in the history
  • Loading branch information
mikedh committed Aug 24, 2017
1 parent c83b9a7 commit c89ddd6
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 57 deletions.
Binary file added models/origin_inside.STL
Binary file not shown.
5 changes: 4 additions & 1 deletion tests/test_permutate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# minimum number of faces to test
# permutations on
MIN_FACES = 25
MIN_FACES = 50


class PermutateTest(g.unittest.TestCase):
Expand All @@ -19,6 +19,8 @@ def make_assertions(mesh, test, rigid=False):
if (close(test.face_adjacency,
mesh.face_adjacency) and
len(mesh.faces) > MIN_FACES):
print(mesh.metadata)
mesh.show()
raise ValueError('face adjacency of %s the same after permutation!',
mesh.metadata['file_name'])

Expand Down Expand Up @@ -62,6 +64,7 @@ def make_assertions(mesh, test, rigid=False):

transform = g.trimesh.permutate.transform(mesh)
tesselate = g.trimesh.permutate.tesselation(mesh)

make_assertions(mesh, noise, rigid=False)
make_assertions(mesh, no_noise, rigid=True)
make_assertions(mesh, transform, rigid=True)
Expand Down
22 changes: 22 additions & 0 deletions tests/test_proximity.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,28 @@ def points_on_circle(count):
return result, result_distance


def test_coplanar_signed_distance(self):
mesh = g.trimesh.primitives.Box()

# should be well outside the box but coplanar with a face
# so the signed distance should be negative
distance = mesh.nearest.signed_distance([mesh.bounds[0] + [100,0,0]])

assert distance[0] < 0.0

# constructed so origin is inside but also coplanar with
# the nearest face
mesh = g.get_mesh('origin_inside.STL')

# origin should be inside, so distance should be positive
distance = mesh.nearest.signed_distance([[0,0,0]])

assert distance[0] > 0.0





if __name__ == '__main__':
g.trimesh.util.attach_to_log()
g.unittest.main()
59 changes: 9 additions & 50 deletions tests/test_ray.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,60 +47,19 @@ def test_rps(self):
use_embree)

def test_contains(self):
mesh = g.get_mesh('unit_cube.STL')
scale = 1.5
for use_embree in [True, False]:
mesh = g.get_mesh('unit_cube.STL', use_embree=use_embree)
g.log.info('Contains test ray engine: ' + str(mesh.ray.__class__))

test_on = mesh.ray.contains_points(mesh.vertices)
test_in = mesh.ray.contains_points(mesh.vertices * (1.0 / scale))
test_out = mesh.ray.contains_points(mesh.vertices * scale)

test_on = mesh.contains(mesh.vertices)
test_in = mesh.contains(mesh.vertices * (1.0 / scale))
test_out = mesh.contains(mesh.vertices * scale)

#assert test_on.all()
self.assertTrue(test_in.all())
self.assertFalse(test_out.any())
assert test_in.all()
assert not test_out.any()


if __name__ == '__main__':
g.trimesh.util.attach_to_log()
g.unittest.main()

'''
# sandbox to generate ray_data
file_names = ['octagonal_pocket.ply',
'featuretype.STL',
'soup.stl',
'ballA.off']
kwargs = [{'file_name' : f,
'use_embree' : e} for f,e in g.itertools.product(file_names,
[True, False])]
meshes = [g.get_mesh(**k) for k in kwargs]
names = [i.metadata['file_name'] for i in meshes]
rays = dict()
# number or frays
rl = 300
# number of random vectors per origin
nr = 3
for m,name in zip(meshes, names):
name = m.metadata['file_name']
origins = g.trimesh.sample.volume_rectangular(
extents=m.bounding_box.primitive.extents*3,
count=rl*2,
transform=m.bounding_box.primitive.transform)
origins = origins[m.nearest.signed_distance(origins) < -.05][:rl]
directions = g.np.column_stack((m.centroid - origins,
g.np.random.random((len(origins),3*nr)))).reshape((-1,3))
directions = g.trimesh.unitize(directions)
forigins = g.np.tile(origins, nr+1).reshape((-1,3))
rays[name] = {'ray_origins' : forigins.tolist(),
'ray_directions' : directions.tolist()}
'''
4 changes: 3 additions & 1 deletion trimesh/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1562,7 +1562,9 @@ def copy(self):
copied._data.data = copy.deepcopy(self._data.data)
# copy visual information
copied.visual._data.data = copy.deepcopy(self.visual._data.data)

# get metadata
copied.metadata = copy.deepcopy(self.metadata)

# make sure cache is set from here
copied._cache.id_set()

Expand Down
10 changes: 9 additions & 1 deletion trimesh/proximity.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def signed_distance(mesh, points):
Find the signed distance from a mesh to a list of points.
* Points OUTSIDE the mesh will have NEGATIVE distance
* Points within tol.zero of the surface have POSITIVE distance
* Points on the surface will have very small distances with arbitrary sign
* Points INSIDE the mesh will have POSITIVE distance
Parameters
Expand Down Expand Up @@ -197,6 +197,14 @@ def signed_distance(mesh, points):
# sign of projection of vector onto normal
sign = np.sign(util.diagonal_dot(normal, vector))

# if the sign of the projection is zero, it means that
# the point is coplanar with the nearest triangle
# this means the point is either on the surface, or
parallel = sign==0
if parallel.any():
inside = mesh.ray.contains_points(points[parallel])
sign[parallel] = (inside.astype(int) * 2) - 1

# apply sign to previously computed distance
distance[nonzero] *= sign

Expand Down
18 changes: 18 additions & 0 deletions trimesh/ray/ray_pyembree.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from pyembree import rtcore_scene
from pyembree.mesh_construction import TriangleMesh

from .ray_util import contains_points

from .. import util
from .. import intersections

Expand Down Expand Up @@ -219,3 +221,19 @@ def intersects_any(self,
ray_directions=ray_directions)
hit = first != -1
return hit

def contains_points(self, points):
'''
Check if a mesh contains a list of points, using ray tests.
If the point is on the surface of the mesh, behavior is undefined.
Parameters
---------
points: (n,3) points in space
Returns
---------
contains: (n) boolean array, whether point is inside mesh or not
'''
return contains_points(self, points)
20 changes: 20 additions & 0 deletions trimesh/ray/ray_triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
'''
import numpy as np


from .ray_util import contains_points

from ..constants import tol
from ..grouping import unique_rows

Expand Down Expand Up @@ -116,6 +119,23 @@ def intersects_any(self,
hit_any[hit_idx] = True
return hit_any

def contains_points(self, points):
'''
Check if a mesh contains a list of points, using ray tests.
If the point is on the surface of the mesh, behavior is undefined.
Parameters
---------
points: (n,3) points in space
Returns
---------
contains: (n) boolean array, whether point is inside mesh or not
'''

return contains_points(self, points)


def ray_triangle_id(triangles,
ray_origins,
Expand Down
8 changes: 5 additions & 3 deletions trimesh/ray/ray_util.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import numpy as np


def contains_points(mesh, points):
def contains_points(intersector, points):
'''
Check if a mesh contains a set of points, using ray tests.
Expand All @@ -19,8 +19,10 @@ def contains_points(mesh, points):
ray_origins = np.asanyarray(points)
# rays are all going in arbitrary direction
ray_directions = np.tile([0, 0, 1.0], (len(points), 1))
locations, index_ray = mesh.ray.intersects_location(
ray_origins, ray_directions)
(locations,
index_ray,
index_tri) = intersector.intersects_location(ray_origins,
ray_directions)

if len(locations) == 0:
return np.zeros(len(points), dtype=np.bool)
Expand Down
2 changes: 1 addition & 1 deletion trimesh/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '2.14.12'
__version__ = '2.14.13'

0 comments on commit c89ddd6

Please sign in to comment.