BVHTraversal
Walking a BVH with a search of your own, through the general PackedBVH::traverse() (see PackedBVH::traverse()). The scene is 20,000 small balls, a primitive type the program defines itself, and the program asks three questions: which ball surface is nearest to a point, which ball a ray hits first, and whether the segment between two points is blocked. The library supplies the walk and the query types; the program supplies the primitive, the state, the leaf test and the bound.
The source is at Examples/BVHTraversal/main.cpp. See Building for how to compile
it with CMake, GNU Make, or a direct compiler invocation.
cd Examples/BVHTraversal
./BVHTraversal.ex
The code that runs on either side
A search is four things: a query type (what is tracked through the tree), a state (what is
remembered between leaves), a leaf callback (what happens at a leaf) and a bound (how near a
node must be to be entered). The two searches that also run on a GPU are written as functors with an
EBGEOMETRY_HOST_DEVICE call operator, holding the BVH by value:
NearestBallwalks with aBVH::PointQuery, whose key for a box is the squared distance from the point to it. The state is the nearest surface distance so far and its ball; the leaf callback measures the distance to each ball’s surface; the bound is the state’s distance, squared to match the key, so a box farther than the nearest surface found is skipped.FirstHitwalks with aBVH::RayQuery, whose key for a box is the distance along the ray at which it enters it; a box the ray misses is never entered. The state is the nearest hit so far; the leaf callback is a ray–sphere test the program writes itself; the bound is the nearest hit, so a box the ray enters beyond it is skipped. The default order, nearest first, finds a near hit early. It also passes a visit callback, which the walk calls for every node it enters, and which could refuse a node; here it only counts them.
m_bvh.traverse(EBGeometry::BVH::RayQuery<T>(a_origin, m_direction), hit, leaf, bound, visit);
Building the BVH
A PackedBVH is built from (primitive, bounding box) pairs into a pool, which must outlive it.
The primitive can be any trivially copyable type; Ball has no member functions at all:
EBGeometry::Pool pool(EBGeometry::hostMemoryResource());
const BVH bvh = BVH::buildBottomUp(pool, balls, std::size_t(4));
Using it on the host
The functors are called like functions. The ray’s search reports how many of the BVH’s nodes it
entered, a small fraction of them. blocked() asks whether anything lies between two points: a ray
along the segment, whose bound starts at the segment’s length (nothing beyond the far end counts) and
drops to \(-\infty\) at the first ball hit, which ends the walk. Since any hit will do, it visits
children in the order they are stored (AsStored), without sorting:
a_bvh.traverse(EBGeometry::BVH::RayQuery<T>(a_from, direction), reach, leaf, bound,
EBGeometry::BVH::DescendAll{}, EBGeometry::BVH::AsStored{});
The same queries on a GPU
The pool is frozen and mirrored to GPU::memoryResource(), the BVH is rebased onto the copy, and
GPU::transform() runs NearestBall and FirstHit once per input in a kernel, one query per
thread. In a plain C++ build the same lines run on the host; a GPU build without a visible GPU skips
them.