BVHTree

The BVHTree implements a bounding volume hierarchy tree. This data structure recursively subdivides a rectilinear region of interest into a “tree” of subregions, stopping when a subregion contains less than some number of objects or when the tree reaches a specified height. Similar to UniformGrid, subregions are also called bins.

The BVHTree is well-suited for particle-mesh or ray-mesh intersection tests. It is also well-suited to data sets where the contents are unevenly distributed, since the bins are subdivided based on their contents. The figure below shows several 2D triangles and their bounding box, which serves as the root bin in the tree.

Diagram showing triangles in a bounding box

The BVHTree::build() method recurses into each bin, creating up to two child bins depending on how many objects are located there and how they are distributed.

Diagram showing first division of a BVHTree
Diagram showing second division of a BVHTree

Unlike the UniformGrid, BVHTree bins can overlap.

Diagram showing third division of a BVHTree

The following code example shows how a BVHTree can be used to accelerate a point-mesh intersection algorithm. First, we insert all triangles into the index and call BVHTree::build().

#include "axom/spin/BVHTree.hpp"
// the BVHTree is in 2D, storing an index to 2D triangles
using BVHTree2DType = axom::spin::BVHTree<int, in2D>;
// supporting classes
using BoundingBox2DType = axom::primal::BoundingBox<double, in2D>;
using Point2DType = axom::primal::Point<double, in2D>;
using Triangle2DType = axom::primal::Triangle<double, in2D>;
BoundingBox2DType findBbox(Triangle2DType& tri);

BVHTree2DType* buildBVHTree(std::vector<Triangle2DType>& tris)
{
  // Initialize BVHTree with the triangles
  const int MaxBinFill = 1;
  const int MaxLevels = 4;
  int tricount = static_cast<int>(tris.size());
  BVHTree2DType* tree = new BVHTree2DType(tricount, MaxLevels);

  for(int i = 0; i < tricount; ++i)
  {
    tree->insert(findBbox(tris[i]), i);
  }

  // Build bounding volume hierarchy
  tree->build(MaxBinFill);

  return tree;
}

After the structure is built, make a list of triangles that are candidate neighbors to the query point. Call BVHTree::find() to get the list of bins that the query point intersects. The key idea of find() is that testing for probe intersection with a bin (bounding box) is cheap. If a bin intersection test fails (misses), the contents of the bin are cheaply pruned out of the search. If the probe does intersect a bin, the next level of bins is tested for probe intersection. Without the acceleration data structure, each probe point must be tested against each triangle.

void findCandidateBVHTreeBins(BVHTree2DType* tree,
                              Point2DType ppoint,
                              std::vector<int>& candidates)
{
  // Which triangles does the probe point intersect?
  // Get the candidate bins
  std::vector<int> bins;
  tree->find(ppoint, bins);
  size_t nbins = bins.size();

  // for each candidate bin,
  for(size_t curb = 0; curb < nbins; ++curb)
  {
    // get its size and object array
    int bcount = tree->getBucketNumObjects(bins[curb]);
    const int* ary = tree->getBucketObjectArray(bins[curb]);

    // For each object in the current bin,
    for(int j = 0; j < bcount; ++j)
    {
      // find the tree's internal object ID
      int treeObjID = ary[j];
      // and use it to retrieve the triangle's ID.
      int triID = tree->getObjectData(treeObjID);

      // Then store the ID in the candidates list.
      candidates.push_back(triID);
    }
  }

  // Sort the candidate triangles, and throw out duplicates.
  // This is not strictly necessary but saves some calls to checkInTriangle().
  std::sort(candidates.begin(), candidates.end());
  std::vector<int>::iterator jend =
    std::unique(candidates.begin(), candidates.end());
  candidates.erase(jend, candidates.end());
}

Finally, test the point against all candidate neighbor triangles.

void findIntersectionsWithCandidates(std::vector<Triangle2DType>& tris,
                                     std::vector<int>& candidates,
                                     Point2DType ppoint,
                                     std::vector<int>& intersections)
{
  // Test if ppoint lands in any of its neighbor triangles.
  int csize = static_cast<int>(candidates.size());
  for(int i = 0; i < csize; ++i)
  {
    Triangle2DType& t = tris[candidates[i]];
    if(t.checkInTriangle(ppoint))
    {
      intersections.push_back(candidates[i]);
    }
  }
}