File indexing completed on 2026-11-13 11:00:02
0001
0002
0003
0004
0005
0006
0007 #ifndef VECGEOM_BASE_INDEXEDBVH_H_
0008 #define VECGEOM_BASE_INDEXEDBVH_H_
0009
0010 #include "VecGeom/base/AABB.h"
0011 #include "VecGeom/base/Assert.h"
0012 #include "VecGeom/base/Config.h"
0013 #include "VecGeom/base/Cuda.h"
0014
0015 #include <algorithm>
0016 #include <cstddef>
0017 #include <cmath>
0018 #include <cstring>
0019 #include <limits>
0020 #include <new>
0021 #include <numeric>
0022 #include <type_traits>
0023 #include <utility>
0024 #include <vector>
0025
0026 namespace vecgeom {
0027 namespace cuda {
0028 template <typename Real_t>
0029 class IndexedBVH;
0030 template <typename Real_t>
0031 struct IndexedBVHNode;
0032 }
0033 VECGEOM_DEVICE_DECLARE_CONV_TEMPLATE(class, IndexedBVH, typename);
0034 VECGEOM_DEVICE_DECLARE_CONV_TEMPLATE(struct, IndexedBVHNode, typename);
0035 inline namespace VECGEOM_IMPL_NAMESPACE {
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046 template <typename Real_t>
0047 struct IndexedBVHNode {
0048 Real_t bounds[6];
0049 int first{0};
0050 int count{0};
0051
0052
0053 VECCORE_ATT_HOST_DEVICE
0054 bool IsLeaf() const { return count > 0; }
0055
0056
0057 VECCORE_ATT_HOST_DEVICE
0058 AABB<Real_t> Bounds() const
0059 {
0060 return AABB<Real_t>(Vector3D<Real_t>(bounds[0], bounds[2], bounds[4]),
0061 Vector3D<Real_t>(bounds[1], bounds[3], bounds[5]));
0062 }
0063
0064
0065 VECCORE_ATT_HOST
0066 void SetBounds(const AABB<Real_t> &b)
0067 {
0068 const auto lo = b.Min();
0069 const auto hi = b.Max();
0070 const auto inf = std::numeric_limits<Real_t>::infinity();
0071
0072 bounds[0] = std::nextafter(lo[0], -inf);
0073 bounds[1] = std::nextafter(hi[0], inf);
0074 bounds[2] = std::nextafter(lo[1], -inf);
0075 bounds[3] = std::nextafter(hi[1], inf);
0076 bounds[4] = std::nextafter(lo[2], -inf);
0077 bounds[5] = std::nextafter(hi[2], inf);
0078 }
0079 };
0080
0081
0082
0083
0084
0085
0086
0087
0088
0089
0090
0091
0092
0093
0094
0095
0096
0097
0098
0099
0100
0101
0102
0103
0104
0105
0106
0107
0108
0109
0110
0111
0112
0113
0114
0115
0116
0117
0118
0119
0120
0121
0122 template <typename Real_t>
0123 class IndexedBVH {
0124 public:
0125
0126
0127
0128
0129
0130 static constexpr int BVH_MAX_DEPTH = 32;
0131
0132 static constexpr int kTraversalStackSize = BVH_MAX_DEPTH + 1;
0133
0134
0135 using Node = IndexedBVHNode<Real_t>;
0136
0137 IndexedBVH() = default;
0138
0139
0140 VECCORE_ATT_HOST_DEVICE int GetRootId() const { return fRootId; }
0141
0142 VECCORE_ATT_HOST_DEVICE int GetRootNChild() const { return fNPrim; }
0143
0144 VECCORE_ATT_HOST_DEVICE int GetRootNDaughter() const { return fNDaughter; }
0145
0146 VECCORE_ATT_HOST_DEVICE int GetNNodes() const { return fNNodes; }
0147
0148 VECCORE_ATT_HOST_DEVICE const Node *GetNodes() const { return fNodes; }
0149
0150 VECCORE_ATT_HOST_DEVICE const int *GetPrimId() const { return fPrimId; }
0151
0152 VECCORE_ATT_HOST_DEVICE const AABB<Real_t> *GetAABBs() const { return fAABBs; }
0153
0154 VECCORE_ATT_HOST_DEVICE const int *GetBoxToDaughter() const { return fBoxToDaughter; }
0155
0156 VECCORE_ATT_HOST_DEVICE bool HasDaughterMap() const { return fBoxToDaughter != nullptr; }
0157
0158 VECCORE_ATT_HOST_DEVICE int GetDaughterId(int boxId) const { return fBoxToDaughter ? fBoxToDaughter[boxId] : boxId; }
0159
0160
0161
0162
0163
0164
0165
0166
0167
0168
0169
0170
0171
0172
0173
0174
0175
0176 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool IntersectNode(unsigned int nodeId, Vector3D<Real_t> const &point,
0177 Vector3D<Real_t> const &inverseDirection,
0178 unsigned int octant, Real_t step,
0179 Real_t &approach) const
0180 {
0181 return IntersectOctant(fNodes[nodeId].bounds, point, inverseDirection, octant, step, approach);
0182 }
0183
0184
0185
0186
0187
0188
0189
0190
0191
0192
0193 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool IntersectBox(int boxId, Vector3D<Real_t> const &point,
0194 Vector3D<Real_t> const &inverseDirection, Real_t step,
0195 Real_t &approach) const
0196 {
0197 return fAABBs[boxId].IntersectInvDirApproach(point, inverseDirection, step, approach);
0198 }
0199
0200
0201
0202
0203
0204
0205
0206 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE Real_t NodeSafetySqr(unsigned int nodeId,
0207 Vector3D<Real_t> const &point) const
0208 {
0209 return ConservativeSafetySqr(fNodes[nodeId], point);
0210 }
0211
0212
0213
0214
0215
0216
0217 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static Precision FinalizeSafetySqr(Real_t safetySqr)
0218 {
0219 return static_cast<Precision>(kSafetyShrink * safetySqr);
0220 }
0221
0222
0223
0224
0225
0226
0227
0228 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE Precision BoxSafety(int boxId, Vector3D<Precision> const &point) const
0229 {
0230 return fAABBs[boxId].Safety(point);
0231 }
0232
0233
0234
0235
0236
0237
0238
0239 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool NodeContains(unsigned int nodeId,
0240 Vector3D<Real_t> const &point) const
0241 {
0242 return fNodes[nodeId].Bounds().Contains(point);
0243 }
0244
0245
0246
0247
0248
0249
0250
0251 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool BoxContains(int boxId, Vector3D<Real_t> const &point) const
0252 {
0253 return fAABBs[boxId].Contains(point);
0254 }
0255
0256
0257
0258
0259
0260
0261
0262 #ifdef VECGEOM_ENABLE_CUDA
0263
0264 struct DeviceImageLayout {
0265 int rootId{0};
0266 int nPrim{0};
0267 int nDaughter{0};
0268 int nNodes{0};
0269 std::size_t nodesOffset{0};
0270 std::size_t primIdOffset{0};
0271 std::size_t aabbsOffset{0};
0272 std::size_t boxToDaughterOffset{0};
0273 };
0274 static_assert(std::is_trivially_copyable<DeviceImageLayout>::value,
0275 "IndexedBVH device-image layout must remain byte-copyable");
0276
0277
0278
0279
0280
0281
0282
0283
0284
0285
0286 VECCORE_ATT_HOST_DEVICE
0287 IndexedBVH(int rootId, int nPrim, int nNodes, Node *nodes, int *primId, AABB<Real_t> *aabbs)
0288 : IndexedBVH(rootId, nPrim, 0, nNodes, nodes, primId, aabbs, nullptr)
0289 {
0290 }
0291
0292
0293
0294
0295
0296
0297
0298
0299
0300
0301
0302
0303
0304
0305
0306
0307 VECCORE_ATT_HOST_DEVICE
0308 IndexedBVH(int rootId, int nPrim, int nDaughter, int nNodes, Node *nodes, int *primId, AABB<Real_t> *aabbs,
0309 int *boxToDaughter)
0310 : fRootId(rootId), fNPrim(nPrim), fNDaughter(nDaughter), fNNodes(nNodes), fNodeCapacity(nNodes), fNodes(nodes),
0311 fPrimId(primId), fAABBs(aabbs), fBoxToDaughter(boxToDaughter)
0312 {
0313 }
0314
0315
0316
0317
0318
0319
0320
0321 VECCORE_ATT_HOST_DEVICE
0322 static void MaterializeDeviceImage(char *arena, std::size_t objectOffset, IndexedBVH<Real_t> *target = nullptr)
0323 {
0324 DeviceImageLayout layout;
0325 memcpy(&layout, arena + objectOffset, sizeof(layout));
0326
0327 auto *bvh = target ? target : reinterpret_cast<IndexedBVH<Real_t> *>(arena + objectOffset);
0328 if (layout.nPrim == 0) {
0329 new (bvh) IndexedBVH<Real_t>();
0330 return;
0331 }
0332
0333 auto *nodes = reinterpret_cast<Node *>(arena + layout.nodesOffset);
0334 auto *primId = reinterpret_cast<int *>(arena + layout.primIdOffset);
0335 auto *aabbs = reinterpret_cast<AABB<Real_t> *>(arena + layout.aabbsOffset);
0336 auto *boxToDaughter =
0337 layout.boxToDaughterOffset ? reinterpret_cast<int *>(arena + layout.boxToDaughterOffset) : nullptr;
0338
0339 new (bvh) IndexedBVH<Real_t>(layout.rootId, layout.nPrim, layout.nDaughter, layout.nNodes, nodes, primId, aabbs,
0340 boxToDaughter);
0341 }
0342 #endif
0343
0344 #ifdef VECGEOM_CUDA_INTERFACE
0345
0346
0347
0348
0349
0350 DeviceImageLayout MakeDeviceImageLayout(std::size_t &nextOffset) const;
0351
0352
0353
0354
0355
0356
0357
0358
0359 void PackDeviceImage(char *arena, std::size_t arenaSize, std::size_t objectOffset,
0360 DeviceImageLayout const &layout) const;
0361
0362
0363
0364
0365
0366
0367
0368
0369
0370
0371 DevicePtr<cuda::IndexedBVH<Real_t>> CopyToGpu(void *addr) const;
0372 #endif
0373
0374
0375
0376
0377
0378
0379
0380
0381 static constexpr Real_t kCostTraversal = Real_t(1.0);
0382 static constexpr Real_t kCostIntersect = Real_t(3.0);
0383 static constexpr int kMaxLeafSize = 4;
0384
0385
0386 struct BuildConfig {
0387
0388 double traversalCost{double(kCostTraversal)};
0389
0390 double primitiveCost{double(kCostIntersect)};
0391
0392 int maxLeafSize{kMaxLeafSize};
0393 };
0394
0395
0396 static constexpr int kVolumeFineTreeLevels = 1;
0397
0398 static constexpr int kVolumeWideLeafThreshold = kMaxLeafSize * (1 << kVolumeFineTreeLevels);
0399
0400 static constexpr int kVolumeWideLeafFactor = 3;
0401
0402
0403
0404
0405
0406
0407
0408
0409
0410
0411
0412
0413 static constexpr BuildConfig VolumeNavigationBuildConfig(int primitiveCount)
0414 {
0415 return primitiveCount <= kVolumeWideLeafThreshold ? BuildConfig{1.0, 2.0, kMaxLeafSize}
0416 : BuildConfig{2.0, 1.0, kVolumeWideLeafFactor * kMaxLeafSize};
0417 }
0418
0419
0420
0421
0422
0423
0424
0425
0426 template <typename InputReal_t>
0427 VECCORE_ATT_HOST IndexedBVH(int rootId, Vector3D<InputReal_t> const *ptrAABB, int nChild)
0428 : IndexedBVH(rootId, ptrAABB, nChild, BuildConfig{})
0429 {
0430 }
0431
0432
0433
0434
0435
0436
0437
0438
0439
0440 template <typename InputReal_t>
0441 VECCORE_ATT_HOST IndexedBVH(int rootId, Vector3D<InputReal_t> const *ptrAABB, int nChild, BuildConfig const &config)
0442 : IndexedBVH(rootId, ptrAABB, nChild, nullptr, 0, config)
0443 {
0444 }
0445
0446
0447
0448
0449
0450
0451
0452
0453
0454
0455
0456
0457
0458
0459
0460
0461 template <typename InputReal_t>
0462 VECCORE_ATT_HOST IndexedBVH(int rootId, Vector3D<InputReal_t> const *ptrAABB, int nChild, int const *boxToDaughter,
0463 int nDaughter = 0, BuildConfig const &config = BuildConfig{})
0464 : fRootId(rootId), fNPrim(nChild)
0465 {
0466 VECGEOM_VALIDATE(nChild > 0, << "Cannot construct IndexedBVH for a volume with no primitives!");
0467 VECGEOM_VALIDATE(config.traversalCost > 0. && config.primitiveCost > 0. && config.maxLeafSize > 0,
0468 << "IndexedBVH build costs and maximum leaf size must be positive!");
0469
0470 if (boxToDaughter) {
0471 VECGEOM_VALIDATE(nDaughter <= 0 || nDaughter <= nChild,
0472 << "Cannot construct IndexedBVH with more mapped items than primitive boxes!");
0473 int mappedDaughters = 0;
0474 for (int boxId = 0; boxId < nChild; ++boxId) {
0475 int const daughterId = boxToDaughter[boxId];
0476 VECGEOM_VALIDATE(daughterId >= 0, << "Cannot construct IndexedBVH with a negative mapped item id!");
0477 VECGEOM_VALIDATE(daughterId < nChild, << "Cannot construct IndexedBVH with a sparse mapped-item id!");
0478 VECGEOM_VALIDATE(nDaughter <= 0 || daughterId < nDaughter,
0479 << "Cannot construct IndexedBVH with an item id outside the configured range!");
0480 mappedDaughters = std::max(mappedDaughters, daughterId + 1);
0481 }
0482 if (nDaughter <= 0) nDaughter = mappedDaughters;
0483
0484 std::vector<bool> seen(nDaughter, false);
0485 for (int boxId = 0; boxId < nChild; ++boxId)
0486 seen[boxToDaughter[boxId]] = true;
0487 VECGEOM_VALIDATE(std::all_of(seen.begin(), seen.end(), [](bool value) { return value; }),
0488 << "Cannot construct IndexedBVH when a mapped item has no primitive box!");
0489
0490 fNDaughter = nDaughter;
0491 fBoxToDaughter = new int[nChild];
0492 std::copy(boxToDaughter, boxToDaughter + nChild, fBoxToDaughter);
0493 }
0494
0495 fPrimId = new int[nChild];
0496 std::iota(fPrimId, fPrimId + nChild, 0);
0497
0498
0499
0500
0501 fAABBs = new AABB<Real_t>[nChild];
0502 for (int i = 0; i < nChild; ++i) {
0503 Vector3D<Real_t> lo(RoundOutward<true>(ptrAABB[2 * i].x()), RoundOutward<true>(ptrAABB[2 * i].y()),
0504 RoundOutward<true>(ptrAABB[2 * i].z()));
0505 Vector3D<Real_t> hi(RoundOutward<false>(ptrAABB[2 * i + 1].x()), RoundOutward<false>(ptrAABB[2 * i + 1].y()),
0506 RoundOutward<false>(ptrAABB[2 * i + 1].z()));
0507 const auto inf = std::numeric_limits<Real_t>::infinity();
0508 for (int axis = 0; axis < 3; ++axis) {
0509 lo[axis] = std::nextafter(lo[axis], -inf);
0510 hi[axis] = std::nextafter(hi[axis], inf);
0511 }
0512 fAABBs[i] = AABB<Real_t>(lo, hi);
0513 }
0514
0515
0516 fNodeCapacity = std::max(1, 2 * nChild - 1);
0517 fNodes = new Node[fNodeCapacity];
0518 fNNodes = 1;
0519 BuildNode(0, 0, fAABBs, fPrimId, fPrimId + nChild, config);
0520
0521
0522
0523
0524 if (fNNodes < fNodeCapacity) {
0525 auto *compactNodes = new Node[fNNodes];
0526 std::copy(fNodes, fNodes + fNNodes, compactNodes);
0527 delete[] fNodes;
0528 fNodes = compactNodes;
0529 fNodeCapacity = fNNodes;
0530 }
0531 }
0532
0533 IndexedBVH(IndexedBVH const &) = delete;
0534 IndexedBVH &operator=(IndexedBVH const &) = delete;
0535
0536
0537 VECCORE_ATT_HOST
0538 IndexedBVH(IndexedBVH &&other) noexcept { MoveFrom(other); }
0539
0540
0541 VECCORE_ATT_HOST
0542 IndexedBVH &operator=(IndexedBVH &&other) noexcept
0543 {
0544 if (this != &other) {
0545 Clear();
0546 MoveFrom(other);
0547 }
0548 return *this;
0549 }
0550
0551
0552 std::size_t GetAllocatedSize() const
0553 {
0554 std::size_t size = static_cast<std::size_t>(fNodeCapacity) * sizeof(Node) +
0555 static_cast<std::size_t>(fNPrim) * (sizeof(int) + sizeof(AABB<Real_t>));
0556 if (fBoxToDaughter) size += static_cast<std::size_t>(fNPrim) * sizeof(int);
0557 return size;
0558 }
0559
0560
0561 std::size_t GetDeviceAllocatedSize() const
0562 {
0563 std::size_t size = static_cast<std::size_t>(fNNodes) * sizeof(Node) +
0564 static_cast<std::size_t>(fNPrim) * (sizeof(int) + sizeof(AABB<Real_t>));
0565 if (fBoxToDaughter) size += static_cast<std::size_t>(fNPrim) * sizeof(int);
0566 return size;
0567 }
0568
0569 ~IndexedBVH() { Clear(); }
0570
0571
0572 VECCORE_ATT_HOST
0573 void Clear()
0574 {
0575 #ifndef VECCORE_CUDA_DEVICE_COMPILATION
0576 delete[] fNodes;
0577 fNodes = nullptr;
0578 delete[] fPrimId;
0579 fPrimId = nullptr;
0580 delete[] fAABBs;
0581 fAABBs = nullptr;
0582 delete[] fBoxToDaughter;
0583 fBoxToDaughter = nullptr;
0584 fRootId = 0;
0585 fNPrim = 0;
0586 fNDaughter = 0;
0587 fNNodes = 0;
0588 fNodeCapacity = 0;
0589 #endif
0590 }
0591
0592
0593
0594
0595
0596
0597
0598
0599
0600
0601
0602
0603
0604 VECCORE_ATT_HOST void EstimateTraversalWork(double &nodeVisits, double &primitiveBoxTests,
0605 double &candidateCalls) const
0606 {
0607 nodeVisits = primitiveBoxTests = candidateCalls = 0.;
0608 if (!fNodes || !fPrimId || !fAABBs) return;
0609
0610 const double rootArea = static_cast<double>(fNodes[0].Bounds().SurfaceArea());
0611 if (!(rootArea > 0.)) return;
0612
0613 unsigned int stack[kTraversalStackSize];
0614 int sp = 1;
0615 stack[0] = 0;
0616 while (sp > 0) {
0617 const unsigned int id = stack[--sp];
0618 const double probability = id == 0 ? 1. : static_cast<double>(fNodes[id].Bounds().SurfaceArea()) / rootArea;
0619 nodeVisits += probability;
0620 if (fNodes[id].IsLeaf()) {
0621 primitiveBoxTests += probability * fNodes[id].count;
0622 for (int child = 0; child < fNodes[id].count; ++child) {
0623 const int boxId = fPrimId[fNodes[id].first + child];
0624 candidateCalls += static_cast<double>(fAABBs[boxId].SurfaceArea()) / rootArea;
0625 }
0626 } else {
0627 stack[sp++] = fNodes[id].first;
0628 stack[sp++] = fNodes[id].first + 1;
0629 }
0630 }
0631 }
0632
0633
0634
0635
0636
0637
0638
0639
0640
0641 VECCORE_ATT_HOST void EstimateSafetyWork(double &nodeVisits, double &primitiveBoxTests, double &candidateCalls) const
0642 {
0643 nodeVisits = primitiveBoxTests = candidateCalls = 0.;
0644 if (!fNodes || !fPrimId || !fAABBs) return;
0645
0646 const auto volume = [](AABB<Real_t> const &box) {
0647 const auto extent = box.Size();
0648 return static_cast<double>(extent[0]) * static_cast<double>(extent[1]) * static_cast<double>(extent[2]);
0649 };
0650 const double rootArea = static_cast<double>(fNodes[0].Bounds().SurfaceArea());
0651 const double rootVolume = volume(fNodes[0].Bounds());
0652 if (!(rootArea > 0.) || !(rootVolume > 0.)) return;
0653
0654 unsigned int stack[kTraversalStackSize];
0655 int sp = 1;
0656 stack[0] = 0;
0657 while (sp > 0) {
0658 const unsigned int id = stack[--sp];
0659 nodeVisits += id == 0 ? 1. : static_cast<double>(fNodes[id].Bounds().SurfaceArea()) / rootArea;
0660 if (fNodes[id].IsLeaf()) {
0661 const double occupancy = id == 0 ? 1. : volume(fNodes[id].Bounds()) / rootVolume;
0662 primitiveBoxTests += occupancy * fNodes[id].count;
0663 for (int child = 0; child < fNodes[id].count; ++child) {
0664 const int boxId = fPrimId[fNodes[id].first + child];
0665 candidateCalls += volume(fAABBs[boxId]) / rootVolume;
0666 }
0667 } else {
0668 stack[sp++] = fNodes[id].first;
0669 stack[sp++] = fNodes[id].first + 1;
0670 }
0671 }
0672 }
0673
0674
0675
0676 private:
0677
0678
0679 int fRootId{0};
0680 int fNPrim{0};
0681 int fNDaughter{0};
0682 int fNNodes{0};
0683 int fNodeCapacity{0};
0684 Node *fNodes{nullptr};
0685 int *fPrimId{nullptr};
0686 AABB<Real_t> *fAABBs{nullptr};
0687 int *fBoxToDaughter{nullptr};
0688
0689
0690
0691
0692 static constexpr Real_t kSafetyShrink = Real_t(1.) - Real_t(8.) * vecgeom::kEpsilonT<Real_t>;
0693
0694
0695
0696
0697
0698
0699 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static Real_t ConservativeSafetySqr(const Node &node,
0700 const Vector3D<Real_t> &point)
0701 {
0702 const Real_t dx = vecgeom::Max(vecgeom::Max(node.bounds[0] - point[0], Real_t(0.)), point[0] - node.bounds[1]);
0703 const Real_t dy = vecgeom::Max(vecgeom::Max(node.bounds[2] - point[1], Real_t(0.)), point[1] - node.bounds[3]);
0704 const Real_t dz = vecgeom::Max(vecgeom::Max(node.bounds[4] - point[2], Real_t(0.)), point[2] - node.bounds[5]);
0705 return kSafetyShrink * (dx * dx + dy * dy + dz * dz);
0706 }
0707
0708
0709
0710
0711
0712
0713
0714
0715
0716
0717
0718
0719
0720
0721
0722
0723 VECCORE_ATT_HOST_DEVICE
0724 static bool IntersectOctant(const Real_t (&bounds)[6], const Vector3D<Real_t> &point, const Vector3D<Real_t> &invdir,
0725 unsigned int octant, Real_t step, Real_t &tmin)
0726 {
0727 #ifdef VECCORE_CUDA_DEVICE_COMPILATION
0728
0729 const unsigned int bit0 = octant & 1u;
0730 const unsigned int bit1 = (octant >> 1) & 1u;
0731 const unsigned int bit2 = (octant >> 2) & 1u;
0732
0733 const Real_t t0x = (bounds[bit0] - point[0]) * invdir[0];
0734 const Real_t t1x = (bounds[1 - bit0] - point[0]) * invdir[0];
0735 const Real_t t0y = (bounds[2 + bit1] - point[1]) * invdir[1];
0736 const Real_t t1y = (bounds[3 - bit1] - point[1]) * invdir[1];
0737 const Real_t t0z = (bounds[4 + bit2] - point[2]) * invdir[2];
0738 const Real_t t1z = (bounds[5 - bit2] - point[2]) * invdir[2];
0739
0740 using vecCore::math::Max;
0741 using vecCore::math::Min;
0742 tmin = Max(Max(Max(t0x, t0y), t0z), Real_t(0.));
0743
0744 const Real_t tmax = Min(Min(Min(t1x, t1y), t1z) * (Real_t(1.) + vecgeom::kToleranceDist<Real_t>), step);
0745 return tmin <= tmax;
0746 #else
0747
0748
0749 (void)octant;
0750 const Real_t t0x = (bounds[0] - point[0]) * invdir[0];
0751 const Real_t t1x = (bounds[1] - point[0]) * invdir[0];
0752 const Real_t t0y = (bounds[2] - point[1]) * invdir[1];
0753 const Real_t t1y = (bounds[3] - point[1]) * invdir[1];
0754 const Real_t t0z = (bounds[4] - point[2]) * invdir[2];
0755 const Real_t t1z = (bounds[5] - point[2]) * invdir[2];
0756
0757 using vecCore::math::Max;
0758 using vecCore::math::Min;
0759 tmin = Max(Max(Max(Min(t0x, t1x), Min(t0y, t1y)), Min(t0z, t1z)), Real_t(0.));
0760 const Real_t tmax = Min(
0761 Min(Min(Max(t0x, t1x), Max(t0y, t1y)), Max(t0z, t1z)) * (Real_t(1.) + vecgeom::kToleranceDist<Real_t>), step);
0762 return tmin <= tmax;
0763 #endif
0764 }
0765
0766
0767
0768 VECCORE_ATT_HOST
0769 void MoveFrom(IndexedBVH &other) noexcept
0770 {
0771 fRootId = std::exchange(other.fRootId, 0);
0772 fNPrim = std::exchange(other.fNPrim, 0);
0773 fNDaughter = std::exchange(other.fNDaughter, 0);
0774 fNNodes = std::exchange(other.fNNodes, 0);
0775 fNodeCapacity = std::exchange(other.fNodeCapacity, 0);
0776 fNodes = std::exchange(other.fNodes, nullptr);
0777 fPrimId = std::exchange(other.fPrimId, nullptr);
0778 fAABBs = std::exchange(other.fAABBs, nullptr);
0779 fBoxToDaughter = std::exchange(other.fBoxToDaughter, nullptr);
0780 }
0781
0782
0783
0784
0785
0786
0787
0788
0789
0790
0791 template <bool isMin, typename From>
0792 VECCORE_ATT_HOST static Real_t RoundOutward(From v)
0793 {
0794 Real_t r = static_cast<Real_t>(v);
0795 if (isMin ? (static_cast<From>(r) > v) : (static_cast<From>(r) < v)) {
0796 r = isMin ? std::nextafter(r, -std::numeric_limits<Real_t>::infinity())
0797 : std::nextafter(r, std::numeric_limits<Real_t>::infinity());
0798 }
0799 return r;
0800 }
0801
0802
0803
0804
0805
0806
0807
0808
0809
0810
0811 template <typename T>
0812 VECCORE_ATT_HOST static bool less3D(const T &left, const T &right, int sortAxis)
0813 {
0814 return left[sortAxis] < right[sortAxis] ||
0815 (left[sortAxis] == right[sortAxis] && (left[(sortAxis + 1) % 3] < right[(sortAxis + 1) % 3] ||
0816 (left[(sortAxis + 1) % 3] == right[(sortAxis + 1) % 3] &&
0817 left[(sortAxis + 2) % 3] < right[(sortAxis + 2) % 3])));
0818 }
0819
0820
0821
0822
0823
0824
0825
0826
0827
0828
0829
0830
0831
0832 VECCORE_ATT_HOST
0833 static std::vector<std::pair<double, double>> SweepSurfaceArea(AABB<Real_t> const *boxes, int const *begin,
0834 int const *end)
0835 {
0836 if (begin >= end) return {};
0837
0838 std::vector<std::pair<double, double>> areas(std::distance(begin, end), {0., 0.});
0839
0840 AABB<Real_t> box{boxes[*begin]};
0841 for (auto it = begin + 1; it < end; ++it) {
0842 areas[it - begin].first = box.SurfaceArea();
0843 box = AABB<Real_t>::Union(box, boxes[*it]);
0844 }
0845
0846 AABB<Real_t> box2{boxes[*(end - 1)]};
0847 for (auto it = end; it != begin;) {
0848 --it;
0849 box2 = AABB<Real_t>::Union(box2, boxes[*it]);
0850 areas[it - begin].second = box2.SurfaceArea();
0851 }
0852
0853 return areas;
0854 }
0855
0856
0857
0858
0859
0860
0861
0862
0863
0864
0865
0866
0867
0868
0869
0870 VECCORE_ATT_HOST
0871 static int *SweepSAHSplit(AABB<Real_t> const *boxes, int *first, int *last, Real_t nodeArea,
0872 BuildConfig const &config, bool &improves)
0873 {
0874 const auto n = std::distance(first, last);
0875 double bestCost = double(n) * config.primitiveCost;
0876 int bestSplitAxis = -1;
0877 int bestSplitOffset = -1;
0878 const double invArea = 1.0 / double(nodeArea);
0879
0880 int currentSortAxis = 0;
0881 auto sorter = [boxes, ¤tSortAxis](int a, int b) {
0882 const auto centroidA = boxes[a].Center();
0883 const auto centroidB = boxes[b].Center();
0884 constexpr double shift = 0.01;
0885 return less3D(centroidA + shift * (centroidA - boxes[a].Min()), centroidB + shift * (centroidB - boxes[b].Min()),
0886 currentSortAxis);
0887 };
0888
0889 for (int axis = 0; axis <= 2; ++axis) {
0890 currentSortAxis = axis;
0891 std::sort(first, last, sorter);
0892
0893 const std::vector<std::pair<double, double>> sweep = SweepSurfaceArea(boxes, first, last);
0894
0895
0896 for (int *splitObject = first + 1; splitObject < last; ++splitObject) {
0897 const auto k = std::distance(first, splitObject);
0898 const double cost = config.traversalCost +
0899 config.primitiveCost * (sweep[k].first * invArea * k + sweep[k].second * invArea * (n - k));
0900
0901 if (cost < bestCost) {
0902 bestCost = cost;
0903 bestSplitAxis = axis;
0904 bestSplitOffset = static_cast<int>(k);
0905 }
0906 }
0907 }
0908
0909 improves = (bestSplitAxis != -1);
0910 if (!improves) return last;
0911
0912 currentSortAxis = bestSplitAxis;
0913 std::sort(first, last, sorter);
0914 return first + bestSplitOffset;
0915 }
0916
0917
0918
0919
0920
0921
0922
0923
0924
0925
0926
0927
0928 VECCORE_ATT_HOST
0929 static int *MedianSplitLongestAxis(AABB<Real_t> const *boxes, int *first, int *last, AABB<Real_t> const &bounds)
0930 {
0931 const auto size = bounds.Size();
0932 const int axis = (size[0] > size[2]) ? (size[0] > size[1] ? 0 : 1) : (size[1] > size[2] ? 1 : 2);
0933 int *mid = first + std::distance(first, last) / 2;
0934 std::nth_element(first, mid, last,
0935 [boxes, axis](int a, int b) { return less3D(boxes[a].Center(), boxes[b].Center(), axis); });
0936 return mid;
0937 }
0938
0939
0940
0941
0942
0943
0944
0945
0946
0947
0948
0949
0950
0951
0952
0953 VECCORE_ATT_HOST
0954 void BuildNode(int id, int depth, AABB<Real_t> const *boxes, int *first, int *last, BuildConfig const &config)
0955 {
0956 Node &node = fNodes[id];
0957 const int n = static_cast<int>(std::distance(first, last));
0958
0959 AABB<Real_t> bbox = boxes[*first];
0960 for (int *it = first + 1; it != last; ++it)
0961 bbox = AABB<Real_t>::Union(bbox, boxes[*it]);
0962 node.SetBounds(bbox);
0963
0964 auto makeLeaf = [&] {
0965 node.first = static_cast<int>(first - fPrimId);
0966 node.count = n;
0967 };
0968
0969 if (n == 1 || depth >= BVH_MAX_DEPTH) return makeLeaf();
0970
0971 const Real_t nodeArea = bbox.SurfaceArea();
0972 bool improves = false;
0973 int *pivot = (nodeArea > Real_t(0.)) ? SweepSAHSplit(boxes, first, last, nodeArea, config, improves) : last;
0974
0975 if (!improves) {
0976 if (n <= config.maxLeafSize) return makeLeaf();
0977
0978 pivot = MedianSplitLongestAxis(boxes, first, last, bbox);
0979
0980 if (pivot == first || pivot == last) return makeLeaf();
0981 }
0982
0983 const int childL = fNNodes++;
0984 const int childR = fNNodes++;
0985 node.first = childL;
0986 node.count = 0;
0987
0988 BuildNode(childL, depth + 1, boxes, first, pivot, config);
0989 BuildNode(childR, depth + 1, boxes, pivot, last, config);
0990 }
0991 };
0992
0993 }
0994
0995 #ifdef VECGEOM_ENABLE_CUDA
0996
0997
0998
0999
1000
1001
1002 template <typename Real_t>
1003 cuda::IndexedBVH<Real_t> *CopyManyIndexedBVHToGpu(void const *hostBVHs, std::size_t count);
1004 #endif
1005
1006 }
1007
1008 #endif