Skip to content

Trace rays against a BVH instead of the nearest face centroids - #84

Draft
akaszynski wants to merge 2 commits into
mainfrom
feat/bvh-ray-trace
Draft

Trace rays against a BVH instead of the nearest face centroids#84
akaszynski wants to merge 2 commits into
mainfrom
feat/bvh-ray-trace

Conversation

@akaszynski

@akaszynski akaszynski commented Aug 16, 2026

Copy link
Copy Markdown
Member

create_mesh(moveclus=True) projects each cluster centroid onto the surface along its normal. It did that by taking the 1000 faces whose centroids were nearest the cluster centroid and stopping at the first one the ray hit. Both halves are approximations: the face a ray meets first need not be one whose centroid is near it, and beyond 1000 faces it is not a candidate at all. This builds a BVH over the surface, ported from femorph, and takes the true nearest intersection.

The second commit is the bigger one. The Möller-Trumbore determinant is the sine of the angle between the ray and the plane of the face times twice the area of the face, so rejecting on a fixed 1e-6 threw out small faces regardless of angle: 99.7% of the faces of the bunny subdivided once, whose centroids moveclus then could not move at all. Dividing the area back out makes the test scale free, and takes the bunny at 5000 clusters from 32 of its points moving to 4968, and its worst distance from the surface from 5.5e-4 to 1.6e-17. The airplane goes from 1.09 off a model 2000 across to 2.8e-14.

Also 5.9-15x faster on the projection and 4.5-10x on create_mesh overall, since a tree walk replaces a k=1000 kd-tree query.

ray_trace loses its neigh, no_inf, num_threads and out_of_bounds_idx arguments and returns the index of the face hit rather than an index into a candidate list. MAX_THREADS goes with it: it fed a num_threads parameter the extension never read.

Tests check the tree against a brute-force reference over every face on four meshes in both directions, and pin each case that was wrong: the nearest centroid not being the nearest face, a hit beyond the thousandth candidate, mesh scale, and a face of no area. The airplane test fails on main at 1.09.

Groundwork for #83, which needs the hit face to tell a legitimate projection from a runaway.

Changes drafted by Claude Opus 5 but fully understood by me.

The projection in create_mesh cast each cluster normal at the 1000 faces
whose centroids were nearest the centroid, and stopped at the first of
them it hit. Neither half holds: the face a ray meets first need not have
a near centroid, and past 1000 faces it is not a candidate at all.

Build a BVH over the surface and take the true nearest intersection.
Exact rather than approximate, and 5.6-15x faster on the projection since
it replaces a k=1000 kd-tree query with a tree walk.
The Moller-Trumbore determinant is the sine of the angle between the ray
and the plane of the face, times twice the area of the face. Rejecting on
a fixed 1e-6 therefore threw out small faces whatever their angle: 99.7%
of the faces of the bunny subdivided once, whose centroids moveclus then
could not move at all.

Divide the area back out at build time so the test is on the angle alone,
and reject a face of no area outright rather than dividing by zero and
relying on NaN comparisons to fall through.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant