BVH-Impl.hpp
Go to the documentation of this file.
1 // This code is based on Jet framework.
2 // Copyright (c) 2018 Doyub Kim
3 // CubbyFlow is voxel-based fluid simulation engine for computer games.
4 // Copyright (c) 2020 CubbyFlow Team
5 // Core Part: Chris Ohk, Junwoo Hwang, Jihong Sin, Seungwoo Yoo
6 // AI Part: Dongheon Cho, Minseo Kim
7 // We are making my contributions/submissions to this project solely in our
8 // personal capacity and are not conveying any rights to any intellectual
9 // property of any third parties.
10 
11 #ifndef CUBBYFLOW_BVH_IMPL_HPP
12 #define CUBBYFLOW_BVH_IMPL_HPP
13 
14 #include <numeric>
15 
16 namespace CubbyFlow
17 {
18 template <typename T, size_t N>
19 BVH<T, N>::Node::Node() : flags(0)
20 {
21  child = std::numeric_limits<size_t>::max();
22 }
23 
24 template <typename T, size_t N>
25 void BVH<T, N>::Node::InitLeaf(size_t it, const BoundingBox<double, N>& b)
26 {
27  flags = static_cast<char>(N);
28  item = it;
29  bound = b;
30 }
31 
32 template <typename T, size_t N>
33 void BVH<T, N>::Node::InitInternal(uint8_t axis, size_t c,
34  const BoundingBox<double, N>& b)
35 {
36  flags = axis;
37  child = c;
38  bound = b;
39 }
40 
41 template <typename T, size_t N>
42 bool BVH<T, N>::Node::IsLeaf() const
43 {
44  return flags == static_cast<char>(N);
45 }
46 
47 template <typename T, size_t N>
49  const ConstArrayView1<T>& items,
50  const ConstArrayView1<BoundingBox<double, N>>& itemsBounds)
51 {
52  m_items = items;
53  m_itemBounds = itemsBounds;
54 
55  if (m_items.IsEmpty())
56  {
57  return;
58  }
59 
60  m_nodes.Clear();
61  m_bound = BoundingBox<double, N>{};
62 
63  for (size_t i = 0; i < m_items.Length(); ++i)
64  {
65  m_bound.Merge(m_itemBounds[i]);
66  }
67 
68  Array1<size_t> itemIndices(m_items.Length());
69  std::iota(std::begin(itemIndices), std::end(itemIndices), 0);
70 
71  Build(0, itemIndices.data(), m_items.Length(), 0);
72 }
73 
74 template <typename T, size_t N>
76 {
77  m_bound = BoundingBox<double, N>{};
78  m_items.Clear();
79  m_itemBounds.Clear();
80  m_nodes.Clear();
81 }
82 
83 template <typename T, size_t N>
85  const Vector<double, N>& pt,
86  const NearestNeighborDistanceFunc<T, N>& distanceFunc) const
87 {
89  best.distance = std::numeric_limits<double>::max();
90  best.item = nullptr;
91 
92  // Prepare to traverse BVH
93  static const int kMaxTreeDepth = 8 * sizeof(size_t);
94  const Node* todo[kMaxTreeDepth];
95  size_t todoPos = 0;
96 
97  // Traverse BVH nodes
98  const Node* node = m_nodes.data();
99  while (node != nullptr)
100  {
101  if (node->IsLeaf())
102  {
103  if (double dist = distanceFunc(m_items[node->item], pt);
104  dist < best.distance)
105  {
106  best.distance = dist;
107  best.item = &m_items[node->item];
108  }
109 
110  // Grab next node to process from todo stack
111  if (todoPos > 0)
112  {
113  // Dequeue
114  --todoPos;
115  node = todo[todoPos];
116  }
117  else
118  {
119  break;
120  }
121  }
122  else
123  {
124  const double bestDistSqr = best.distance * best.distance;
125 
126  const Node* left = node + 1;
127  const Node* right = &m_nodes[node->child];
128 
129  // If pt is inside the box, then the closestLeft and Right will be
130  // identical to pt. This will make distMinLeftSqr and
131  // distMinRightSqr zero, meaning that such a box will have higher
132  // priority.
133  Vector<double, N> closestLeft = left->bound.Clamp(pt);
134  Vector<double, N> closestRight = right->bound.Clamp(pt);
135 
136  const double distMinLeftSqr = closestLeft.DistanceSquaredTo(pt);
137  const double distMinRightSqr = closestRight.DistanceSquaredTo(pt);
138 
139  const bool shouldVisitLeft = distMinLeftSqr < bestDistSqr;
140  const bool shouldVisitRight = distMinRightSqr < bestDistSqr;
141 
142  if (shouldVisitLeft && shouldVisitRight)
143  {
144  const Node* secondChild;
145  const Node* firstChild;
146 
147  if (distMinLeftSqr < distMinRightSqr)
148  {
149  firstChild = left;
150  secondChild = right;
151  }
152  else
153  {
154  firstChild = right;
155  secondChild = left;
156  }
157 
158  // Enqueue secondChild in todo stack
159  todo[todoPos] = secondChild;
160  ++todoPos;
161  node = firstChild;
162  }
163  else if (shouldVisitLeft)
164  {
165  node = left;
166  }
167  else if (shouldVisitRight)
168  {
169  node = right;
170  }
171  else
172  {
173  if (todoPos > 0)
174  {
175  // Dequeue
176  --todoPos;
177  node = todo[todoPos];
178  }
179  else
180  {
181  break;
182  }
183  }
184  }
185  }
186 
187  return best;
188 }
189 
190 template <typename T, size_t N>
192  const BoxIntersectionTestFunc<T, N>& testFunc) const
193 {
194  if (!m_bound.Overlaps(box))
195  {
196  return false;
197  }
198 
199  // prepare to traverse BVH for box
200  static const int MAX_TREE_DEPTH = 8 * sizeof(size_t);
201  const Node* todo[MAX_TREE_DEPTH];
202  size_t todoPos = 0;
203 
204  // traverse BVH nodes for box
205  const Node* node = m_nodes.data();
206 
207  while (node != nullptr)
208  {
209  if (node->IsLeaf())
210  {
211  if (testFunc(m_items[node->item], box))
212  {
213  return true;
214  }
215 
216  // grab next node to process from todo stack
217  if (todoPos > 0)
218  {
219  // Dequeue
220  --todoPos;
221  node = todo[todoPos];
222  }
223  else
224  {
225  break;
226  }
227  }
228  else
229  {
230  // get node children pointers for box
231  const Node* firstChild = node + 1;
232  const Node* secondChild = const_cast<Node*>(&m_nodes[node->child]);
233 
234  // advance to next child node, possibly enqueue other child
235  if (!firstChild->bound.Overlaps(box))
236  {
237  node = secondChild;
238  }
239  else if (!secondChild->bound.Overlaps(box))
240  {
241  node = firstChild;
242  }
243  else
244  {
245  // enqueue secondChild in todo stack
246  todo[todoPos] = secondChild;
247  ++todoPos;
248  node = firstChild;
249  }
250  }
251  }
252 
253  return false;
254 }
255 
256 template <typename T, size_t N>
258  const RayIntersectionTestFunc<T, N>& testFunc) const
259 {
260  if (!m_bound.Intersects(ray))
261  {
262  return false;
263  }
264 
265  // prepare to traverse BVH for ray
266  static const int kMaxTreeDepth = 8 * sizeof(size_t);
267  const Node* todo[kMaxTreeDepth];
268  size_t todoPos = 0;
269 
270  // traverse BVH nodes for ray
271  const Node* node = m_nodes.data();
272 
273  while (node != nullptr)
274  {
275  if (node->IsLeaf())
276  {
277  if (testFunc(m_items[node->item], ray))
278  {
279  return true;
280  }
281 
282  // grab next node to process from todo stack
283  if (todoPos > 0)
284  {
285  // Dequeue
286  --todoPos;
287  node = todo[todoPos];
288  }
289  else
290  {
291  break;
292  }
293  }
294  else
295  {
296  // get node children pointers for ray
297  const Node* firstChild;
298  const Node* secondChild;
299 
300  if (ray.direction[node->flags] > 0.0)
301  {
302  firstChild = node + 1;
303  secondChild = const_cast<Node*>(&m_nodes[node->child]);
304  }
305  else
306  {
307  firstChild = const_cast<Node*>(&m_nodes[node->child]);
308  secondChild = node + 1;
309  }
310 
311  // advance to next child node, possibly enqueue other child
312  if (!firstChild->bound.Intersects(ray))
313  {
314  node = secondChild;
315  }
316  else if (!secondChild->bound.Intersects(ray))
317  {
318  node = firstChild;
319  }
320  else
321  {
322  // enqueue secondChild in todo stack
323  todo[todoPos] = secondChild;
324  ++todoPos;
325  node = firstChild;
326  }
327  }
328  }
329 
330  return false;
331 }
332 
333 template <typename T, size_t N>
335  const BoundingBox<double, N>& box,
336  const BoxIntersectionTestFunc<T, N>& testFunc,
337  const IntersectionVisitorFunc<T>& visitorFunc) const
338 {
339  if (!m_bound.Overlaps(box))
340  {
341  return;
342  }
343 
344  // prepare to traverse BVH for box
345  static const int MAX_TREE_DEPTH = 8 * sizeof(size_t);
346  const Node* todo[MAX_TREE_DEPTH];
347  size_t todoPos = 0;
348 
349  // traverse BVH nodes for box
350  const Node* node = m_nodes.data();
351 
352  while (node != nullptr)
353  {
354  if (node->IsLeaf())
355  {
356  if (testFunc(m_items[node->item], box))
357  {
358  visitorFunc(m_items[node->item]);
359  }
360 
361  // grab next node to process from todo stack
362  if (todoPos > 0)
363  {
364  // Dequeue
365  --todoPos;
366  node = todo[todoPos];
367  }
368  else
369  {
370  break;
371  }
372  }
373  else
374  {
375  // get node children pointers for box
376  const Node* firstChild = node + 1;
377  const Node* secondChild = const_cast<Node*>(&m_nodes[node->child]);
378 
379  // advance to next child node, possibly enqueue other child
380  if (!firstChild->bound.Overlaps(box))
381  {
382  node = secondChild;
383  }
384  else if (!secondChild->bound.Overlaps(box))
385  {
386  node = firstChild;
387  }
388  else
389  {
390  // enqueue secondChild in todo stack
391  todo[todoPos] = secondChild;
392  ++todoPos;
393  node = firstChild;
394  }
395  }
396  }
397 }
398 
399 template <typename T, size_t N>
401  const Ray<double, N>& ray, const RayIntersectionTestFunc<T, N>& testFunc,
402  const IntersectionVisitorFunc<T>& visitorFunc) const
403 {
404  if (!m_bound.Intersects(ray))
405  {
406  return;
407  }
408 
409  // prepare to traverse BVH for ray
410  static const int MAX_TREE_DEPTH = 8 * sizeof(size_t);
411  const Node* todo[MAX_TREE_DEPTH];
412  size_t todoPos = 0;
413 
414  // traverse BVH nodes for ray
415  const Node* node = m_nodes.data();
416 
417  while (node != nullptr)
418  {
419  if (node->IsLeaf())
420  {
421  if (testFunc(m_items[node->item], ray))
422  {
423  visitorFunc(m_items[node->item]);
424  }
425 
426  // grab next node to process from todo stack
427  if (todoPos > 0)
428  {
429  // Dequeue
430  --todoPos;
431  node = todo[todoPos];
432  }
433  else
434  {
435  break;
436  }
437  }
438  else
439  {
440  // get node children pointers for ray
441  const Node* firstChild;
442  const Node* secondChild;
443 
444  if (ray.direction[node->flags] > 0.0)
445  {
446  firstChild = node + 1;
447  secondChild = const_cast<Node*>(&m_nodes[node->child]);
448  }
449  else
450  {
451  firstChild = const_cast<Node*>(&m_nodes[node->child]);
452  secondChild = node + 1;
453  }
454 
455  // advance to next child node, possibly enqueue other child
456  if (!firstChild->bound.Intersects(ray))
457  {
458  node = secondChild;
459  }
460  else if (!secondChild->bound.Intersects(ray))
461  {
462  node = firstChild;
463  }
464  else
465  {
466  // enqueue secondChild in todo stack
467  todo[todoPos] = secondChild;
468  ++todoPos;
469  node = firstChild;
470  }
471  }
472  }
473 }
474 
475 template <typename T, size_t N>
477  const Ray<double, N>& ray,
478  const GetRayIntersectionFunc<T, N>& testFunc) const
479 {
481  best.distance = std::numeric_limits<double>::max();
482  best.item = nullptr;
483 
484  if (!m_bound.Intersects(ray))
485  {
486  return best;
487  }
488 
489  // prepare to traverse BVH for ray
490  static const int MAX_TREE_DEPTH = 8 * sizeof(size_t);
491  const Node* todo[MAX_TREE_DEPTH];
492  size_t todoPos = 0;
493 
494  // traverse BVH nodes for ray
495  const Node* node = m_nodes.data();
496 
497  while (node != nullptr)
498  {
499  if (node->IsLeaf())
500  {
501  if (double dist = testFunc(m_items[node->item], ray);
502  dist < best.distance)
503  {
504  best.distance = dist;
505  best.item = m_items.data() + node->item;
506  }
507 
508  // grab next node to process from todo stack
509  if (todoPos > 0)
510  {
511  // Dequeue
512  --todoPos;
513  node = todo[todoPos];
514  }
515  else
516  {
517  break;
518  }
519  }
520  else
521  {
522  // get node children pointers for ray
523  const Node* firstChild;
524  const Node* secondChild;
525 
526  if (ray.direction[node->flags] > 0.0)
527  {
528  firstChild = node + 1;
529  secondChild = const_cast<Node*>(&m_nodes[node->child]);
530  }
531  else
532  {
533  firstChild = const_cast<Node*>(&m_nodes[node->child]);
534  secondChild = node + 1;
535  }
536 
537  // advance to next child node, possibly enqueue other child
538  if (!firstChild->bound.Intersects(ray))
539  {
540  node = secondChild;
541  }
542  else if (!secondChild->bound.Intersects(ray))
543  {
544  node = firstChild;
545  }
546  else
547  {
548  // enqueue secondChild in todo stack
549  todo[todoPos] = secondChild;
550  ++todoPos;
551  node = firstChild;
552  }
553  }
554  }
555 
556  return best;
557 }
558 
559 template <typename T, size_t N>
561 {
562  return m_bound;
563 }
564 
565 template <typename T, size_t N>
567 {
568  return m_items.begin();
569 }
570 
571 template <typename T, size_t N>
573 {
574  return m_items.end();
575 }
576 
577 template <typename T, size_t N>
579 {
580  return m_items.begin();
581 }
582 
583 template <typename T, size_t N>
585 {
586  return m_items.end();
587 }
588 
589 template <typename T, size_t N>
591 {
592  return m_items.Length();
593 }
594 
595 template <typename T, size_t N>
596 const T& BVH<T, N>::Item(size_t i) const
597 {
598  return m_items[i];
599 }
600 
601 template <typename T, size_t N>
603 {
604  return m_nodes.Length();
605 }
606 
607 template <typename T, size_t N>
608 std::pair<size_t, size_t> BVH<T, N>::Children(size_t i) const
609 {
610  if (IsLeaf(i))
611  {
612  return std::make_pair(std::numeric_limits<size_t>::max(),
613  std::numeric_limits<size_t>::max());
614  }
615 
616  return std::make_pair(i + 1, m_nodes[i].child);
617 }
618 
619 template <typename T, size_t N>
620 bool BVH<T, N>::IsLeaf(size_t i) const
621 {
622  return m_nodes[i].IsLeaf();
623 }
624 
625 template <typename T, size_t N>
627 {
628  return m_nodes[i].bound;
629 }
630 
631 template <typename T, size_t N>
633 {
634  if (IsLeaf(i))
635  {
636  return m_nodes[i].item + begin();
637  }
638 
639  return end();
640 }
641 
642 template <typename T, size_t N>
644 {
645  if (IsLeaf(i))
646  {
647  return m_nodes[i].item + begin();
648  }
649 
650  return end();
651 }
652 
653 template <typename T, size_t N>
654 size_t BVH<T, N>::Build(size_t nodeIndex, size_t* itemIndices, size_t nItems,
655  size_t currentDepth)
656 {
657  // add a node
658  m_nodes.Append(Node{});
659 
660  // initialize leaf node if termination criteria met
661  if (nItems == 1)
662  {
663  m_nodes[nodeIndex].InitLeaf(itemIndices[0],
664  m_itemBounds[itemIndices[0]]);
665  return currentDepth + 1;
666  }
667 
668  // find the mid-point of the bounding box to use as a qsplit pivot
669  BoundingBox<double, N> nodeBound;
670  for (size_t i = 0; i < nItems; ++i)
671  {
672  nodeBound.Merge(m_itemBounds[itemIndices[i]]);
673  }
674 
675  Vector<double, N> d = nodeBound.upperCorner - nodeBound.lowerCorner;
676 
677  // choose which axis to split along
678  auto axis = static_cast<uint8_t>(d.DominantAxis());
679 
680  const double pivot =
681  0.5 * (nodeBound.upperCorner[axis] + nodeBound.lowerCorner[axis]);
682 
683  // classify primitives with respect to split
684  const size_t midPoint = QSplit(itemIndices, nItems, pivot, axis);
685 
686  // recursively initialize children _nodes
687  const size_t d0 =
688  Build(nodeIndex + 1, itemIndices, midPoint, currentDepth + 1);
689  m_nodes[nodeIndex].InitInternal(axis, m_nodes.Length(), nodeBound);
690  const size_t d1 = Build(m_nodes[nodeIndex].child, itemIndices + midPoint,
691  nItems - midPoint, currentDepth + 1);
692 
693  return std::max(d0, d1);
694 }
695 
696 template <typename T, size_t N>
697 size_t BVH<T, N>::QSplit(size_t* itemIndices, size_t numItems, double pivot,
698  uint8_t axis)
699 {
700  size_t ret = 0;
701 
702  for (size_t i = 0; i < numItems; ++i)
703  {
704  BoundingBox<double, N> b = m_itemBounds[itemIndices[i]];
705 
706  if (const double centroid =
707  0.5f * (b.lowerCorner[axis] + b.upperCorner[axis]);
708  centroid < pivot)
709  {
710  std::swap(itemIndices[i], itemIndices[ret]);
711  ret++;
712  }
713  }
714 
715  if (ret == 0 || ret == numItems)
716  {
717  ret = numItems >> 1;
718  }
719 
720  return ret;
721 }
722 } // namespace CubbyFlow
723 
724 #endif
ValueType DistanceSquaredTo(const MatrixExpression< T, R, C, E > &other) const
Returns the squared distance to the other vector.
VectorType upperCorner
Upper corner of the bounding box.
Definition: BoundingBox.hpp:148
N-D nearest neighbor query result.
Definition: NearestNeighborQueryEngine.hpp:23
Class for N-D ray.
Definition: Ray.hpp:25
Definition: Matrix.hpp:27
N-D closest intersection query result.
Definition: IntersectionQueryEngine.hpp:25
Definition: pybind11Utils.hpp:20
size_t DominantAxis() const
Definition: MatrixExpression-Impl.hpp:206
Definition: Array-Impl.hpp:19
Iterator begin()
Returns the begin Iterator of the item.
Definition: BVH-Impl.hpp:566
const T * item
Definition: NearestNeighborQueryEngine.hpp:25
std::function< bool(const T &, const BoundingBox< double, N > &)> BoxIntersectionTestFunc
N-D box-item intersection test function.
Definition: IntersectionQueryEngine.hpp:55
double distance
Definition: IntersectionQueryEngine.hpp:28
Iterator end()
Returns the end Iterator of the item.
Definition: BVH-Impl.hpp:572
typename ContainerType::Iterator Iterator
Definition: BVH.hpp:35
VectorType lowerCorner
Lower corner of the bounding box.
Definition: BoundingBox.hpp:145
void Merge(const VectorType &point)
Merges this and other point.
Definition: BoundingBox-Impl.hpp:219
VectorType direction
The direction of the ray.
Definition: Ray.hpp:38
Generic N-dimensional array class interface.
Definition: Array.hpp:32
typename ContainerType::ConstIterator ConstIterator
Definition: BVH.hpp:36
const T * item
Definition: IntersectionQueryEngine.hpp:27
std::function< double(const T &, const Vector< double, N > &)> NearestNeighborDistanceFunc
N-D nearest neighbor distance measure function.
Definition: NearestNeighborQueryEngine.hpp:40
std::function< bool(const T &, const Ray< double, N > &)> RayIntersectionTestFunc
N-D ray-item intersection test function.
Definition: IntersectionQueryEngine.hpp:68
Bounding Volume Hierarchy (BVH) in N-D.
Definition: BVH.hpp:30
std::function< double(const T &, const Ray< double, N > &)> GetRayIntersectionFunc
N-D ray-item closest intersection evaluation function.
Definition: IntersectionQueryEngine.hpp:81
double distance
Definition: NearestNeighborQueryEngine.hpp:26
std::function< void(const T &)> IntersectionVisitorFunc
Visitor function which is invoked for each intersecting item.
Definition: IntersectionQueryEngine.hpp:93
bool IsLeaf(size_t i) const
Returns true if i-th node is a leaf node.
Definition: BVH-Impl.hpp:620