PhysicsBasedAnimationToolkit 0.0.10
Cross-platform C++20 library of algorithms and data structures commonly used in computer graphics research on physically-based simulation.
Loading...
Searching...
No Matches
BoundingVolumeHierarchy.h
Go to the documentation of this file.
1
10
11#ifndef PBAT_GEOMETRY_BOUNDINGVOLUMEHIERARCHY_H
12#define PBAT_GEOMETRY_BOUNDINGVOLUMEHIERARCHY_H
13
14#include "KdTree.h"
15
16#include <pbat/Aliases.h>
17#include <pbat/common/Eigen.h>
18#include <queue>
19#include <stack>
20#include <tbb/parallel_for.h>
21#include <utility>
22#include <vector>
23
24namespace pbat {
25namespace geometry {
26
35template <class TDerived, class TBoundingVolume, class TPrimitive, int Dims>
36class BoundingVolumeHierarchy
37{
38 public:
39 using DerivedType = TDerived;
40 using BoundingVolumeType = TBoundingVolume;
41 using PrimitiveType = TPrimitive;
42 static auto constexpr kDims = Dims;
43
44 template <class TDerived2, class TBoundingVolume2, class TPrimitive2, int Dims2>
45 friend class BoundingVolumeHierarchy;
46
47 BoundingVolumeHierarchy() = default;
48
54 void Construct(Index nPrimitives, Index maxPointsInLeaf = 10);
59 auto BoundingVolumes() const -> std::vector<BoundingVolumeType> const&
60 {
61 return mBoundingVolumes;
62 }
63
79 template <class FIntersectsBoundingVolume, class FIntersectsPrimitive>
80 std::vector<Index> PrimitivesIntersecting(
81 FIntersectsBoundingVolume&& ibv,
82 FIntersectsPrimitive&& ip,
83 std::size_t reserve = 50ULL) const;
95 template <class FDistanceToBoundingVolume, class FDistanceToPrimitive>
96 auto
97 NearestPrimitivesTo(FDistanceToBoundingVolume&& db, FDistanceToPrimitive&& dp, std::size_t K)
98 const -> std::pair<std::vector<Index>, std::vector<Scalar>>;
99
103 void Update();
104
105 // Static virtual functions (CRTP)
106
114 {
115 return static_cast<TDerived const*>(this)->Primitive(p);
116 }
117
123 auto PrimitiveLocation(PrimitiveType const& primitive) const
124 {
125 return static_cast<TDerived const*>(this)->PrimitiveLocation(primitive);
126 }
127
134 template <class RPrimitiveIndices>
135 BoundingVolumeType BoundingVolumeOf(RPrimitiveIndices&& primitiveIndexRange) const
136 {
137 return static_cast<TDerived const*>(this)->BoundingVolumeOf(primitiveIndexRange);
138 }
139
140 protected:
163 template <
164 class TDerived2,
165 class TBoundingVolume2,
166 class TPrimitive2,
167 int Dims2,
168 class FBoundingVolumesOverlap,
169 class FPrimitivesOverlap,
170 class FPrimitivesAreAdjacent>
172 BoundingVolumeHierarchy<TDerived2, TBoundingVolume2, TPrimitive2, Dims2> const& other,
173 FBoundingVolumesOverlap&& bvo,
174 FPrimitivesOverlap&& po,
175 FPrimitivesAreAdjacent&& PrimitivesAreAdjacent =
176 [](PrimitiveType const& p1, TPrimitive2 const& p2) -> bool { return false; },
177 std::size_t reserve = 50ULL) const;
178
179 std::vector<BoundingVolumeType> mBoundingVolumes;
181};
182
183template <class TDerived, class TBoundingVolume, class TPrimitive, int Dims>
185 Index nPrimitives,
186 Index maxPointsInLeaf)
187{
188 Matrix<Dims, Eigen::Dynamic> P(Dims, nPrimitives);
189 for (auto p = 0; p < P.cols(); ++p)
190 {
191 P.col(p) = PrimitiveLocation(Primitive(p));
192 }
193 mKdTree.Construct(P, maxPointsInLeaf);
194 std::size_t const nBoundingVolumes = mKdTree.Nodes().size();
195 mBoundingVolumes.resize(nBoundingVolumes);
196 tbb::parallel_for(std::size_t{0ULL}, nBoundingVolumes, [this](std::size_t b) {
197 mBoundingVolumes[b] = BoundingVolumeOf(mKdTree.PointsInNode(static_cast<Index>(b)));
198 });
199}
200
201template <class TDerived, class TBoundingVolume, class TPrimitive, int Dims>
202inline auto
204 Index bvIdx) const
205{
206 std::size_t const bvIdxStl = static_cast<std::size_t>(bvIdx);
207 return mKdTree.PointsInNode(mKdTree.Nodes()[bvIdxStl]);
208}
209
210template <class TDerived, class TBoundingVolume, class TPrimitive, int Dims>
212{
213 auto const& nodes = mKdTree.Nodes();
214 tbb::parallel_for(std::size_t{0ULL}, nodes.size(), [&](std::size_t bvIdx) {
215 KdTreeNode const& node = nodes[bvIdx];
216 mBoundingVolumes[bvIdx] = BoundingVolumeOf(mKdTree.PointsInNode(node));
217 });
218}
219
220template <class TDerived, class TBoundingVolume, class TPrimitive, int Dims>
221template <class FIntersectsBoundingVolume, class FIntersectsPrimitive>
222inline std::vector<Index>
224 FIntersectsBoundingVolume&& ibv,
225 FIntersectsPrimitive&& ip,
226 std::size_t reserve) const
227{
228 std::vector<Index> intersectingPrimitives{};
229 intersectingPrimitives.reserve(reserve);
230
231 mKdTree.DepthFirstSearch([&](Index bvIdx, KdTreeNode const& node) -> bool {
232 if (node.IsLeaf())
233 {
234 for (auto const idx : mKdTree.PointsInNode(node))
235 {
236 bool const bIntersects = ip(Primitive(idx));
237 if (bIntersects)
238 {
239 intersectingPrimitives.push_back(idx);
240 }
241 }
242 return false; // Cannot visit deeper than a leaf node
243 }
244 else
245 {
246 auto const bvIdxStl = static_cast<std::size_t>(bvIdx);
247 BoundingVolumeType const& bv = mBoundingVolumes[bvIdxStl];
248 return ibv(bv); // Visit deeper if this bounding volume overlaps with the
249 // queried shape
250 }
251 });
252
253 return intersectingPrimitives;
254}
255
256template <class TDerived, class TBoundingVolume, class TPrimitive, int Dims>
257template <class FDistanceToBoundingVolume, class FDistanceToPrimitive>
258inline auto
260 FDistanceToBoundingVolume&& db,
261 FDistanceToPrimitive&& dp,
262 std::size_t K) const -> std::pair<std::vector<Index>, std::vector<Scalar>>
263{
264 std::vector<Index> neighbours{};
265 std::vector<Scalar> distances{};
266 neighbours.reserve(K);
267 distances.reserve(K);
268
269 enum class EQueueItem { Volume, Primitive };
270 struct QueueItem
271 {
272 EQueueItem type; // Indicates if this QueueItem holds a primitive or a volume
273 Index idx; // Index of the primitive, if this QueueItem holds a primitive, or index
274 // of the node, if this QueueItem holds a volume (recall that node_idx =
275 // bv_idx + 1)
276 Scalar d; // Distance from this QueueItem to p
277 };
278 auto const MakeVolumeQueueItem = [&](Index bvIdx) {
279 auto const bvIdxStl = static_cast<std::size_t>(bvIdx);
280 BoundingVolumeType const& bv = mBoundingVolumes[bvIdxStl];
281 Scalar const d = db(bv);
282 QueueItem const q{EQueueItem::Volume, bvIdx, d};
283 return q;
284 };
285 auto const MakePrimitiveQueueItem = [&](Index pIdx) {
286 PrimitiveType const& p = Primitive(pIdx);
287 Scalar const d = dp(p);
288 QueueItem const q{EQueueItem::Primitive, pIdx, d};
289 return q;
290 };
291
292 auto const Greater = [](QueueItem const& q1, QueueItem const& q2) {
293 return q1.d > q2.d;
294 };
295 using PriorityQueue = std::priority_queue<QueueItem, std::vector<QueueItem>, decltype(Greater)>;
296 PriorityQueue queue{Greater};
297 queue.push(MakeVolumeQueueItem(0));
298 auto const& nodes = mKdTree.Nodes();
299 while (!queue.empty())
300 {
301 QueueItem const q = queue.top();
302 queue.pop();
303 if (q.type == EQueueItem::Volume)
304 {
305 auto const qIdxStl = static_cast<std::size_t>(q.idx);
306 KdTreeNode const& node = nodes[qIdxStl];
307 if (node.IsLeaf())
308 {
309 for (auto const pIdx : mKdTree.PointsInNode(node))
310 {
311 queue.push(MakePrimitiveQueueItem(pIdx));
312 }
313 }
314 else
315 {
316 queue.push(MakeVolumeQueueItem(node.Left()));
317 queue.push(MakeVolumeQueueItem(node.Right()));
318 }
319 }
320 else
321 {
322 // If the queue item at the beginning of the priority queue is a primitive,
323 // it means that primitive is closer to the point p than any other primitive or
324 // bounding volume in the queue, thus it is the current closest primitive to p.
325 neighbours.push_back(q.idx);
326 distances.push_back(q.d);
327 }
328
329 if (neighbours.size() == K)
330 break;
331 }
332 return {neighbours, distances};
333}
334
335template <class TDerived, class TBoundingVolume, class TPrimitive, int Dims>
336template <
337 class TDerived2,
338 class TBoundingVolume2,
339 class TPrimitive2,
340 int Dims2,
341 class FBoundingVolumesOverlap,
342 class FPrimitivesOverlap,
343 class FPrimitivesAreAdjacent>
344inline IndexMatrixX
346 BoundingVolumeHierarchy<TDerived2, TBoundingVolume2, TPrimitive2, Dims2> const& other,
347 FBoundingVolumesOverlap&& bvo,
348 FPrimitivesOverlap&& po,
349 FPrimitivesAreAdjacent&& pa,
350 std::size_t reserve) const
351{
352 using BoundingVolumeType2 = TBoundingVolume2;
353 std::vector<Index> overlaps{};
354 overlaps.reserve(reserve * 2);
355 auto const& nodes1 = mKdTree.Nodes();
356 auto const& nodes2 = other.mKdTree.Nodes();
357
358 using PrimitivePairType = std::pair<Index, Index>;
359 std::stack<PrimitivePairType> stack{};
360 stack.push({0, 0}); // Root bounding volumes of *this and other
361 while (!stack.empty())
362 {
363 auto const [n1, n2] = stack.top();
364 auto const n1Stl = static_cast<std::size_t>(n1);
365 auto const n2Stl = static_cast<std::size_t>(n2);
366 stack.pop();
367 BoundingVolumeType const& bv1 = mBoundingVolumes[n1Stl];
368 BoundingVolumeType2 const& bv2 = other.mBoundingVolumes[n2Stl];
369 if (!bvo(bv1, bv2))
370 continue;
371
372 KdTreeNode const& node1 = nodes1[n1Stl];
373 KdTreeNode const& node2 = nodes2[n2Stl];
374 bool const bIsNode1Leaf = node1.IsLeaf();
375 bool const bIsNode2Leaf = node2.IsLeaf();
376 if (bIsNode1Leaf and bIsNode2Leaf)
377 {
378 for (auto const p1 : mKdTree.PointsInNode(node1))
379 {
380 for (auto const p2 : other.mKdTree.PointsInNode(node2))
381 {
382 // For self collision, prevent expensive narrow phase testing when mesh
383 // primitives are adjacent, since this gives false positives
384 if (pa(Primitive(p1), other.Primitive(p2)))
385 continue;
386
387 if (po(Primitive(p1), other.Primitive(p2)))
388 {
389 overlaps.push_back(p1);
390 overlaps.push_back(p2);
391 }
392 }
393 }
394 }
395 else if (bIsNode1Leaf and not bIsNode2Leaf)
396 {
397 stack.push({n1, node2.Left()});
398 stack.push({n1, node2.Right()});
399 }
400 else if (not bIsNode1Leaf and bIsNode2Leaf)
401 {
402 stack.push({node1.Left(), n2});
403 stack.push({node1.Right(), n2});
404 }
405 else
406 {
407 auto const n1n = node1.n;
408 auto const n2n = node2.n;
409 if (n1n > n2n)
410 {
411 stack.push({node1.Left(), n2});
412 stack.push({node1.Right(), n2});
413 }
414 else
415 {
416 stack.push({n1, node2.Left()});
417 stack.push({n1, node2.Right()});
418 }
419 }
420 }
421 return common::ToEigen(overlaps).reshaped(2, overlaps.size() / 2);
422}
423
424} // namespace geometry
425} // namespace pbat
426
427#endif // PBAT_GEOMETRY_BOUNDINGVOLUMEHIERARCHY_H
This file contains the KdTree class.
void Construct(Index nPrimitives, Index maxPointsInLeaf=10)
Construct the BVH from a set of primitives.
Definition BoundingVolumeHierarchy.h:184
TPrimitive PrimitiveType
Type of primitives.
Definition BoundingVolumeHierarchy.h:41
auto BoundingVolumes() const -> std::vector< BoundingVolumeType > const &
Returns the bounding volumes of this BVH.
Definition BoundingVolumeHierarchy.h:59
auto PrimitivesInBoundingVolume(Index bvIdx) const
Returns the indices of the primitives contained in the bounding volume bvIdx.
Definition BoundingVolumeHierarchy.h:203
auto PrimitiveLocation(PrimitiveType const &primitive) const
Returns the location of the primitive.
Definition BoundingVolumeHierarchy.h:123
BoundingVolumeType BoundingVolumeOf(RPrimitiveIndices &&primitiveIndexRange) const
Returns the bounding volume of the primitives in the range [first, last)
Definition BoundingVolumeHierarchy.h:135
std::vector< Index > PrimitivesIntersecting(FIntersectsBoundingVolume &&ibv, FIntersectsPrimitive &&ip, std::size_t reserve=50ULL) const
Returns the indices of the primitives intersecting the bounding volume bv.
Definition BoundingVolumeHierarchy.h:223
TBoundingVolume BoundingVolumeType
Type of bounding volumes.
Definition BoundingVolumeHierarchy.h:40
auto NearestPrimitivesTo(FDistanceToBoundingVolume &&db, FDistanceToPrimitive &&dp, std::size_t K) const -> std::pair< std::vector< Index >, std::vector< Scalar > >
Obtains the k nearest neighbours (primitives of this BVH)
Definition BoundingVolumeHierarchy.h:259
PrimitiveType Primitive(Index p) const
Returns the primitive at index p.
Definition BoundingVolumeHierarchy.h:113
void Update()
Update the bounding volumes of this BVH.
Definition BoundingVolumeHierarchy.h:211
IndexMatrixX OverlappingPrimitivesImpl(BoundingVolumeHierarchy< TDerived2, TBoundingVolume2, TPrimitive2, Dims2 > const &other, FBoundingVolumesOverlap &&bvo, FPrimitivesOverlap &&po, FPrimitivesAreAdjacent &&PrimitivesAreAdjacent=[](PrimitiveType const &p1, TPrimitive2 const &p2) -> bool { return false;}, std::size_t reserve=50ULL) const
Returns the indices of the primitives overlapping between this BVH and another BVH.
Definition BoundingVolumeHierarchy.h:345
std::vector< BoundingVolumeType > mBoundingVolumes
Definition BoundingVolumeHierarchy.h:179
TDerived DerivedType
Actual type.
Definition BoundingVolumeHierarchy.h:39
KDTree class.
Definition KdTree.h:66
std::vector< KdTreeNode > const & Nodes() const
Returns the nodes of the k-D tree.
Definition KdTree.h:120
auto PointsInNode(Index const nodeIdx) const
Returns the points in a node.
Definition KdTree.h:256
Eigen adaptors for ranges.
auto ToEigen(R &&r) -> Eigen::Map< Eigen::Vector< std::ranges::range_value_t< R >, Eigen::Dynamic > const >
Map a range of scalars to an eigen vector of such scalars.
Definition Eigen.h:28
Geometric queries, quantities and data structures.
Definition AabbKdTreeHierarchy.h:23
The main namespace of the library.
Definition Aliases.h:15
std::ptrdiff_t Index
Index type.
Definition Aliases.h:17
Eigen::Matrix< Index, Eigen::Dynamic, Eigen::Dynamic > IndexMatrixX
Dynamic-size index matrix type.
Definition Aliases.h:50
double Scalar
Scalar type.
Definition Aliases.h:18
Eigen::Matrix< Scalar, Rows, Cols > Matrix
Fixed-size matrix type.
Definition Aliases.h:31
Node of a KDTree.
Definition KdTree.h:31
bool IsLeaf() const
Returns true if this node is a leaf node, false otherwise.
Definition KdTree.h:42
Index n
Definition KdTree.h:34
auto Right() const
Returns right child node.
Definition KdTree.h:57
auto Left() const
Returns left child node.
Definition KdTree.h:52