Skip to content

Commit 47b3590

Browse files
authored
Release: Mesh.contains fix (#2535)
`mesh.contains` with multiple hits wasn't tested, and the embree 2 -> 4 move introduced a bug. - test and fix #2534 - restores the scale-aware offset for multiple hits.
2 parents 89a7d38 + 700b245 commit 47b3590

4 files changed

Lines changed: 29 additions & 10 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ requires = ["setuptools >= 61.0", "wheel"]
55
[project]
66
name = "trimesh"
77
requires-python = ">=3.8"
8-
version = "4.12.1"
8+
version = "4.12.2"
99
authors = [{name = "Michael Dawson-Haggerty", email = "mikedh@kerfed.com"}]
1010
license = {file = "LICENSE.md"}
1111
description = "Import, export, process, analyze and view triangular meshes."

tests/test_ray.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,25 @@ def test_contains():
113113
assert test_centroid.all()
114114

115115

116+
def test_contains_cavity():
117+
# https://github.com/mikedh/trimesh/issues/2534
118+
from trimesh import ray as ray_mod
119+
120+
mesh = g.trimesh.boolean.difference(
121+
[
122+
g.trimesh.creation.box(extents=[1, 1, 1]),
123+
g.trimesh.creation.box(extents=[0.1, 0.1, 0.1]),
124+
]
125+
)
126+
engines = [ray_mod.ray_triangle.RayMeshIntersector]
127+
if ray_mod.has_embree:
128+
engines.append(ray_mod.ray_pyembree.RayMeshIntersector)
129+
for engine in engines:
130+
mesh.ray = engine(mesh)
131+
# origin sits inside the subtracted cavity
132+
assert not mesh.contains([[0, 0, 0]])[0]
133+
134+
116135
def test_on_vertex():
117136
for use_embree in [False]:
118137
m = g.trimesh.creation.box(use_embree=use_embree)
@@ -135,7 +154,7 @@ def test_on_edge():
135154
for use_embree in [True, False]:
136155
m = g.get_mesh("7_8ths_cube.stl", use_embree=use_embree)
137156

138-
points = [[4.5, 0, -23], [4.5, 0, -2], [0, -1e-6, 0.0], [0, 0, -1]]
157+
points = [[4.5, 0, -23], [4.5, 0, -2], [0, -1e-4, 0.0], [0, 0, -1]]
139158
truth = [False, True, True, True]
140159
result = g.trimesh.ray.ray_util.contains_points(m.ray, points)
141160

trimesh/ray/ray_pyembree.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@
1717
# embree operates on float32 values
1818
_embree_dtype = np.float32
1919

20-
# when calculating multiple hits we offset the hit to
21-
# advance the ray past the plane of the triangle we hit
22-
# hardcode offset for rays larger than our resolution:
23-
# np.finfo(_embree_dtype).resolution * 10
24-
_ray_offset = 1e-5
20+
# scale-aware base ray offset to step past hit triangles above f32 ULP
21+
_ray_offset_factor = 1e-6
22+
_ray_offset_floor = 1e-8
2523

2624

2725
class RayMeshIntersector:
@@ -150,7 +148,8 @@ def intersects_id(
150148

151149
if multiple_hits or return_locations:
152150
# how much to offset ray to transport to the other side of face
153-
ray_offsets = ray_directions * _ray_offset
151+
base_offset = max(_ray_offset_floor, self.mesh.scale * _ray_offset_factor)
152+
ray_offsets = ray_directions * base_offset
154153

155154
# grab the planes from triangles
156155
plane_origins = self.mesh.triangles[:, 0, :]
@@ -214,7 +213,7 @@ def intersects_id(
214213

215214
# clean hits step onto the new face with a fresh base offset
216215
ray_origins[ok_rays] = new_origins + ray_offsets[ok_rays]
217-
ray_offsets[ok_rays] = ray_directions[ok_rays] * _ray_offset
216+
ray_offsets[ok_rays] = ray_directions[ok_rays] * base_offset
218217
# stuck rays double their offset and step further to try to clear
219218
ray_offsets[dupe_rays] *= 2.0
220219
ray_origins[dupe_rays] += ray_offsets[dupe_rays]

trimesh/ray/ray_util.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,11 @@ def contains_points(
5454
)
5555

5656
# cast a ray both forwards and backwards
57+
# need multiple_hits=True so the parity test counts cavity walls
5758
_location, index_ray, _c = intersector.intersects_location(
5859
np.vstack((points[inside_aabb], points[inside_aabb])),
5960
np.vstack((ray_directions, -ray_directions)),
60-
multiple_hits=False,
61+
multiple_hits=True,
6162
)
6263

6364
# if we hit nothing in either direction just return with no hits

0 commit comments

Comments
 (0)