File indexing completed on 2026-04-17 08:35:32
0001
0002
0003
0004 #ifndef VECGEOM_SURFACES_BVHCREATOR_H_
0005 #define VECGEOM_SURFACES_BVHCREATOR_H_
0006
0007 #include <fstream>
0008 #include <VecGeom/management/ABBoxManager.h>
0009 #include <VecGeom/surfaces/Model.h>
0010 #include <VecGeom/base/BVH.h>
0011
0012 namespace vgbrep {
0013 namespace bvh {
0014
0015 enum class ConstructionAlgorithm : unsigned int {
0016 SplitLongestAxis = 0,
0017 LargestDistanceAlongAxis = 1,
0018 SurfaceAreaHeuristic = 2,
0019 };
0020
0021 namespace {
0022 using namespace vecgeom;
0023 template <typename Real_b>
0024 int *splitAlongLongestAxis(const vecgeom::AABB<Real_b> *primitiveBoxes, int *begin, int *end,
0025 const vecgeom::AABB<Real_b> ¤tBVHNode)
0026 {
0027 const Vector3D<Precision> basis[] = {{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}};
0028 auto closestAxis = [](Vector3D<Precision> v) {
0029 v = v.Abs();
0030 return v[0] > v[2] ? (v[0] > v[1] ? 0 : 1) : (v[1] > v[2] ? 1 : 2);
0031 };
0032 Vector3D<Precision> p = currentBVHNode.Center();
0033 Vector3D<Precision> v = basis[closestAxis(currentBVHNode.Size())];
0034
0035 return std::partition(begin, end, [&](size_t i) {
0036 Vector3D<Precision> center = static_cast<Vector3D<Precision>>(primitiveBoxes[i].Center());
0037 return Vector3D<Precision>::Dot(center - p, v) < 0.0;
0038 });
0039 }
0040
0041 template <typename Real_b>
0042 int *largestDistanceAlongAxis(const vecgeom::AABB<Real_b> *primitiveBoxes, int *begin, int *end,
0043 const vecgeom::AABB<Real_b> & )
0044 {
0045
0046 float extension[3][2] = {{0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}};
0047 for (int axis = 0; axis <= 2; ++axis) {
0048 auto minMaxIt = std::minmax_element(
0049 begin, end, [=](size_t a, size_t b) { return primitiveBoxes[a].Min()[axis] < primitiveBoxes[b].Min()[axis]; });
0050 extension[axis][0] = primitiveBoxes[*minMaxIt.first].Min()[axis];
0051 extension[axis][1] = primitiveBoxes[*minMaxIt.second].Min()[axis];
0052 }
0053
0054 const int splitAxis = std::distance(extension, std::max_element(extension, extension + 3, [](float a[], float b[]) {
0055 return a[1] - a[0] < b[1] - b[0];
0056 }));
0057 const float middlePoint = (extension[splitAxis][1] + extension[splitAxis][0]) / 2.f;
0058 return std::partition(begin, end, [=](size_t i) { return primitiveBoxes[i].Min()[splitAxis] < middlePoint; });
0059 }
0060
0061
0062
0063
0064
0065
0066
0067 template <typename T>
0068 bool less3D(const T &left, const T &right, const int sortAxis)
0069 {
0070 return left[sortAxis] < right[sortAxis] ||
0071 (left[sortAxis] == right[sortAxis] && (left[(sortAxis + 1) % 3] < right[(sortAxis + 1) % 3] ||
0072 (left[(sortAxis + 1) % 3] == right[(sortAxis + 1) % 3] &&
0073 left[(sortAxis + 2) % 3] < right[(sortAxis + 2) % 3])));
0074 }
0075
0076
0077
0078
0079
0080
0081
0082
0083
0084
0085
0086
0087
0088
0089
0090
0091 template <typename Real_b>
0092 std::vector<std::pair<double, double>> sweepSurfaceArea(const vecgeom::AABB<Real_b> *primitiveBoxes, int const *begin,
0093 int const *end)
0094 {
0095 if (begin >= end) return {};
0096
0097 std::vector<std::pair<double, double>> areas(std::distance(begin, end), {0., 0.});
0098
0099 vecgeom::AABB<Real_b> box{primitiveBoxes[*begin]};
0100 for (auto it = begin + 1; it < end; ++it) {
0101 areas[it - begin].first = box.SurfaceArea();
0102 box = vecgeom::AABB<Real_b>::Union(box, primitiveBoxes[*it]);
0103 }
0104
0105 vecgeom::AABB<Real_b> box2{primitiveBoxes[*(end - 1)]};
0106 for (auto it = end - 1; it >= begin; --it) {
0107 box2 = vecgeom::AABB<Real_b>::Union(box2, primitiveBoxes[*(it)]);
0108 areas[it - begin].second = box2.SurfaceArea();
0109 }
0110
0111 return areas;
0112 }
0113
0114
0115
0116
0117
0118
0119
0120
0121
0122
0123
0124
0125
0126
0127 template <typename Real_b>
0128 int *surfaceAreaHeuristic(const vecgeom::AABB<Real_b> *primitiveBoxes, int *begin, int *end,
0129 const vecgeom::AABB<Real_b> & )
0130 {
0131 int bestSplitAxis = -1;
0132 double bestTraversalMetric = std::distance(begin, end);
0133 int bestSplitObject = -1;
0134 const auto nObj = std::distance(begin, end);
0135
0136 int currentSortAxis = 0;
0137 auto sorter = [primitiveBoxes, ¤tSortAxis](int a, int b) {
0138 const auto centroidA = primitiveBoxes[a].Center();
0139 const auto centroidB = primitiveBoxes[b].Center();
0140 constexpr double shift = 0.01;
0141 return less3D(centroidA + shift * (centroidA - primitiveBoxes[a].Min()),
0142 centroidB + shift * (centroidB - primitiveBoxes[b].Min()), currentSortAxis);
0143 };
0144
0145 for (int axis = 0; axis <= 2; ++axis) {
0146
0147 currentSortAxis = axis;
0148 std::sort(begin, end, sorter);
0149
0150
0151 const std::vector<std::pair<double, double>> surfaceSweep = sweepSurfaceArea(primitiveBoxes, begin, end);
0152 const auto totSurfArea = surfaceSweep.front().second;
0153
0154 for (int *splitObject = begin; splitObject < end; ++splitObject) {
0155 const auto left = surfaceSweep[splitObject - begin].first / NonZero(totSurfArea);
0156 const auto right = surfaceSweep[splitObject - begin].second / NonZero(totSurfArea);
0157 VECGEOM_ASSERT(left <= 1. && right <= 1.);
0158
0159
0160 const auto splitMetric = left * std::distance(begin, splitObject) + right * std::distance(splitObject, end) +
0161 0.1 * abs(nObj / 2 - std::distance(begin, splitObject) / nObj);
0162
0163 if (splitMetric < bestTraversalMetric) {
0164 bestTraversalMetric = splitMetric;
0165 bestSplitAxis = axis;
0166 bestSplitObject = *splitObject;
0167 }
0168 }
0169 }
0170
0171 if (bestSplitAxis == -1) return end;
0172
0173 currentSortAxis = bestSplitAxis;
0174 auto result = std::partition(begin, end, [sorter, bestSplitObject](size_t i) { return sorter(i, bestSplitObject); });
0175
0176 return result;
0177 }
0178
0179
0180
0181
0182
0183 template <typename Real_b>
0184 int *(*splittingFunction[])(const vecgeom::AABB<Real_b> * , int * ,
0185 int * , const vecgeom::AABB<Real_b> & ) = {
0186 &splitAlongLongestAxis,
0187 &largestDistanceAlongAxis,
0188 &surfaceAreaHeuristic,
0189 };
0190
0191 }
0192
0193
0194
0195
0196
0197
0198
0199
0200
0201
0202
0203
0204
0205
0206
0207 template <typename Real_b>
0208 void ComputeNodes(unsigned int id, int *first, int *last, unsigned int nodes, int *aPrimId, int *aNChild, int *aOffset,
0209 vecgeom::AABB<Real_b> *aNodes, vecgeom::AABB<Real_b> *aAABBs,
0210 ConstructionAlgorithm constructionAlgorithm)
0211 {
0212 if (id >= nodes) return;
0213
0214 aNChild[id] = std::distance(first, last);
0215 aOffset[id] = std::distance(aPrimId, first);
0216
0217
0218 if (first == last) return;
0219
0220 aNodes[id] = aAABBs[*first];
0221 for (auto it = std::next(first); it != last; ++it)
0222 aNodes[id] = vecgeom::AABB<Real_b>::Union(aNodes[id], aAABBs[*it]);
0223
0224
0225 if (std::next(first) == last) return;
0226
0227 const auto algo = static_cast<unsigned int>(constructionAlgorithm);
0228
0229
0230 int *pivot = splittingFunction<Real_b>[algo](aAABBs, first, last, aNodes[id]);
0231 VECGEOM_ASSERT(first <= pivot && pivot <= last);
0232
0233 ComputeNodes(2 * id + 1, first, pivot, nodes, aPrimId, aNChild, aOffset, aNodes, aAABBs, constructionAlgorithm);
0234 ComputeNodes(2 * id + 2, pivot, last, nodes, aPrimId, aNChild, aOffset, aNodes, aAABBs, constructionAlgorithm);
0235 }
0236
0237
0238
0239
0240
0241
0242
0243
0244 template <typename Real_t>
0245 static void InitBVH(int ivol, BVH<typename vgbrep::SurfData<Real_t>::Real_b> &bvh,
0246 vgbrep::CPUsurfData<Precision> const &cpudata,
0247 Vector3D<typename vgbrep::SurfData<Real_t>::Real_b> *boxes, int nBoxes, int depth = 0)
0248 {
0249 using Real_b = typename vgbrep::SurfData<Real_t>::Real_b;
0250 uint aRootId = ivol;
0251
0252 if (nBoxes <= 0) throw std::logic_error("Cannot construct BVH for volume with no surfaces!");
0253
0254 auto aRootNChild = nBoxes;
0255
0256 auto aAABBs = new vecgeom::AABB<Real_b>[nBoxes];
0257 for (auto i = 0; i < nBoxes; ++i)
0258 aAABBs[i] = vecgeom::AABB(boxes[2 * i], boxes[2 * i + 1]);
0259
0260
0261 auto aPrimId = new int[nBoxes];
0262 std::iota(aPrimId, aPrimId + nBoxes, 0);
0263
0264
0265
0266
0267
0268
0269
0270 int aDepth = std::min(depth ? depth : std::max(0, (int)std::log2(nBoxes / 2)), BVH<Real_b>::BVH_MAX_DEPTH);
0271
0272 unsigned int nodes = (2 << aDepth) - 1;
0273
0274 auto aNChild = new int[nodes];
0275 auto aOffset = new int[nodes];
0276 auto aNodes = new vecgeom::AABB<Real_b>[nodes];
0277 std::fill(aNChild, aNChild + nodes, 0);
0278 std::fill(aOffset, aOffset + nodes, -1);
0279
0280
0281 ComputeNodes(0, aPrimId, aPrimId + nBoxes, nodes, aPrimId, aNChild, aOffset, aNodes, aAABBs,
0282 ConstructionAlgorithm::SurfaceAreaHeuristic);
0283
0284
0285 for (unsigned int id = 0; id < nodes / 2; ++id)
0286 if (aNChild[id] > 8 && (aNChild[id] == aNChild[2 * id + 1] + aNChild[2 * id + 2])) aNChild[id] = -1;
0287
0288
0289 bvh.Set(aRootId, aRootNChild, aDepth, aPrimId, aAABBs, aOffset, aNChild, aNodes);
0290 }
0291
0292
0293 template <typename Real_b>
0294 static void DumpBVH(const BVH<Real_b> &bvh, const char *filename)
0295 {
0296 auto fillAABBbuffer = [](const vecgeom::AABB<Real_b> *aabbs, int n, double *buffer) {
0297 for (auto i = 0; i < n; ++i) {
0298 buffer[6 * i] = aabbs[i].Min()[0];
0299 buffer[6 * i + 1] = aabbs[i].Min()[1];
0300 buffer[6 * i + 2] = aabbs[i].Min()[2];
0301 buffer[6 * i + 3] = aabbs[i].Max()[0];
0302 buffer[6 * i + 4] = aabbs[i].Max()[1];
0303 buffer[6 * i + 5] = aabbs[i].Max()[2];
0304 }
0305 };
0306
0307 std::ofstream stm(filename, std::ios::binary);
0308 if (!stm.is_open()) {
0309 std::cout << "bad file " << filename << "\n";
0310 return;
0311 }
0312 auto aRootId = bvh.GetRootId();
0313 stm.write(reinterpret_cast<const char *>(&aRootId), sizeof(aRootId));
0314 auto aRootNChild = bvh.GetRootNChild();
0315 stm.write(reinterpret_cast<const char *>(&aRootNChild), sizeof(aRootNChild));
0316 auto aDepth = bvh.GetDepth();
0317 stm.write(reinterpret_cast<const char *>(&aDepth), sizeof(aDepth));
0318 unsigned int nodes = (2 << aDepth) - 1;
0319 auto aPrimId = bvh.GetPrimId();
0320 stm.write(reinterpret_cast<const char *>(aPrimId), aRootNChild * sizeof(int));
0321 auto aOffset = bvh.GetOffset();
0322 stm.write(reinterpret_cast<const char *>(aOffset), nodes * sizeof(int));
0323 auto aNChild = bvh.GetNChild();
0324 stm.write(reinterpret_cast<const char *>(aNChild), nodes * sizeof(int));
0325 double *buffer = new double[6 * (nodes + aRootNChild)];
0326 fillAABBbuffer(bvh.GetNodes(), nodes, buffer);
0327 stm.write(reinterpret_cast<const char *>(buffer), 6 * nodes * sizeof(double));
0328 fillAABBbuffer(bvh.GetAABBs(), aRootNChild, buffer + 6 * nodes);
0329 stm.write(reinterpret_cast<const char *>(buffer + 6 * nodes), 6 * aRootNChild * sizeof(double));
0330 stm.close();
0331 delete[] buffer;
0332 }
0333
0334 }
0335 }
0336
0337 #endif