File indexing completed on 2026-11-13 11:00:28
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021 #pragma once
0022
0023 #include "VecGeom/base/Vector3D.h"
0024 #include "VecGeom/base/Transformation3D.h"
0025 #include "VecGeom/management/BVHSubdivisionOptions.h"
0026 #include "VecGeom/volumes/PlacedVolume.h"
0027 #include "VecGeom/volumes/LogicalVolume.h"
0028 #include "VecGeom/volumes/UnplacedVolume.h"
0029 #include "VecGeom/volumes/utilities/ClippedExtent.h"
0030
0031 #include <algorithm>
0032 #include <cassert>
0033 #include <cmath>
0034 #include <limits>
0035 #include <utility>
0036 #include <vector>
0037
0038 namespace vecgeom {
0039 inline namespace cxx {
0040
0041
0042 struct SlabBox {
0043 Vector3D<Precision> min{kInfLength, kInfLength, kInfLength};
0044 Vector3D<Precision> max{-kInfLength, -kInfLength, -kInfLength};
0045 int slabIndex{0};
0046
0047 bool Valid() const { return min[0] <= max[0] && min[1] <= max[1] && min[2] <= max[2]; }
0048 Precision SurfaceArea() const
0049 {
0050 if (!Valid()) return Precision(0);
0051 auto d = max - min;
0052 return Precision(2) * (d[0] * d[1] + d[1] * d[2] + d[2] * d[0]);
0053 }
0054 Vector3D<Precision> HalfExtents() const { return (max - min) * Precision(0.5); }
0055 Vector3D<Precision> Center() const { return (max + min) * Precision(0.5); }
0056 };
0057
0058
0059
0060
0061
0062
0063 struct AABBSubdivisionResult {
0064 std::vector<SlabBox> slabs;
0065 int chosenM{1};
0066 int chosenAxis{0};
0067 Precision globalSA{0};
0068 Precision finalSA{0};
0069 bool usedSingleAxisSearch{false};
0070
0071 std::vector<Precision> saHistory;
0072 };
0073
0074 class AABBSubdivider {
0075 public:
0076 static AABBSubdivider &Instance()
0077 {
0078 static AABBSubdivider inst;
0079 return inst;
0080 }
0081
0082
0083
0084
0085
0086
0087
0088
0089
0090 std::vector<SlabBox> Subdivide(VPlacedVolume const *placed, int M, AABBSubdividerConfig const &cfg = {}) const;
0091
0092
0093
0094
0095
0096
0097
0098
0099
0100
0101
0102
0103
0104 std::vector<SlabBox> SubdivideAlongAxis(VPlacedVolume const *placed, int axisIdx, int M) const;
0105
0106
0107
0108
0109
0110
0111
0112
0113
0114
0115
0116
0117
0118 std::vector<SlabBox> SubdivideAlongAxis(VPlacedVolume const *placed, int axisIdx, int M,
0119 SlabBox const &globalBox) const;
0120
0121
0122
0123
0124
0125
0126
0127
0128
0129
0130
0131
0132
0133 AABBSubdivisionResult SubdivideAuto(VPlacedVolume const *placed, AABBSubdividerConfig const &cfg = {}) const;
0134
0135 private:
0136
0137 static SlabBox ComputeGlobalAABB(VUnplacedVolume const *unplaced, Transformation3D const *tr);
0138
0139
0140
0141
0142
0143 static SlabBox ComputeOneSlabAABB(VUnplacedVolume const *unplaced, Transformation3D const *tr, int axisIdx,
0144 Precision lo, Precision hi, SlabBox const &globalBox, int slabIndex);
0145
0146
0147 static std::vector<SlabBox> ComputeSlabs(VUnplacedVolume const *unplaced, Transformation3D const *tr, int axisIdx,
0148 int M, SlabBox const &globalBox);
0149
0150
0151 static std::vector<SlabBox> BestAxisSlabs(VUnplacedVolume const *unplaced, Transformation3D const *tr, int M,
0152 SlabBox const &globalBox, AABBSubdividerConfig const &cfg, int &outBestAxis,
0153 Precision &outSA);
0154
0155 static Precision TotalSurfaceArea(std::vector<SlabBox> const &slabs);
0156 static Precision ExtentSurfaceArea(VUnplacedVolume const *unplaced);
0157 static Precision AABBInflationRatio(VUnplacedVolume const *unplaced, SlabBox const &globalBox);
0158 static Precision CandidateObjective(Precision totalSA, std::size_t nSlabs, Precision globalSA,
0159 AABBSubdividerConfig const &cfg);
0160 static Precision ModeMinReduction(AABBSubdividerConfig const &cfg);
0161 static int ModeMaxM(AABBSubdividerConfig const &cfg);
0162 static int LongestAxis(SlabBox const &box);
0163 static int EstimateFastMaxM(SlabBox const &box, int maxM);
0164 };
0165
0166 inline SlabBox AABBSubdivider::ComputeGlobalAABB(VUnplacedVolume const *unplaced, Transformation3D const *tr)
0167 {
0168 Vector3D<Precision> localMin, localMax;
0169 unplaced->Extent(localMin, localMax);
0170 const ClippedBox box = TransformLocalAABBToMother(localMin, localMax, tr);
0171
0172 SlabBox result;
0173 result.min = box.min;
0174 result.max = box.max;
0175 result.slabIndex = 0;
0176 return result;
0177 }
0178
0179 inline SlabBox AABBSubdivider::ComputeOneSlabAABB(VUnplacedVolume const *unplaced, Transformation3D const *tr,
0180 int axisIdx, Precision lo, Precision hi, SlabBox const &globalBox,
0181 int slabIndex)
0182 {
0183 SlabBox result;
0184 result.slabIndex = slabIndex;
0185
0186 VoxelLimits limits;
0187 limits.AddLimit(AxisFromIndex(axisIdx), lo, hi);
0188
0189 bool axisResult[3] = {false, false, false};
0190 Precision minValue[3] = {0., 0., 0.};
0191 Precision maxValue[3] = {0., 0., 0.};
0192 int nExisting = 0;
0193 for (int axis = 0; axis < 3; ++axis) {
0194 axisResult[axis] =
0195 unplaced->CalculateClippedExtent(AxisFromIndex(axis), limits, *tr, minValue[axis], maxValue[axis]);
0196 if (axisResult[axis]) ++nExisting;
0197 }
0198
0199 if (nExisting == 0) return result;
0200
0201 if (nExisting == 3) {
0202 result.min = Vector3D<Precision>(minValue[0], minValue[1], minValue[2]);
0203 result.max = Vector3D<Precision>(maxValue[0], maxValue[1], maxValue[2]);
0204 return result;
0205 }
0206
0207
0208
0209
0210 ClippedBox fallback(globalBox.min, globalBox.max);
0211 if (!IntersectBoxWithLimits(fallback, limits)) return result;
0212 result.min = fallback.min;
0213 result.max = fallback.max;
0214 return result;
0215 }
0216
0217 inline std::vector<SlabBox> AABBSubdivider::ComputeSlabs(VUnplacedVolume const *unplaced, Transformation3D const *tr,
0218 int axisIdx, int M, SlabBox const &globalBox)
0219 {
0220 const Precision lo0 = globalBox.min[axisIdx];
0221 const Precision hi0 = globalBox.max[axisIdx];
0222 if (!(hi0 > lo0) || M < 1 || M > kMaximumAABBSubdivisionM) return {};
0223
0224 const Precision slabW = (hi0 - lo0) / Precision(M);
0225 std::vector<SlabBox> slabs;
0226 slabs.reserve(M);
0227 for (int k = 0; k < M; ++k) {
0228 auto slab = ComputeOneSlabAABB(unplaced, tr, axisIdx, lo0 + k * slabW, lo0 + (k + 1) * slabW, globalBox, k);
0229 if (slab.Valid()) slabs.push_back(slab);
0230 }
0231 return slabs;
0232 }
0233
0234 inline Precision AABBSubdivider::TotalSurfaceArea(std::vector<SlabBox> const &slabs)
0235 {
0236 Precision total = 0.;
0237 for (auto const &slab : slabs)
0238 total += slab.SurfaceArea();
0239 return total;
0240 }
0241
0242 inline Precision AABBSubdivider::ExtentSurfaceArea(VUnplacedVolume const *unplaced)
0243 {
0244 Vector3D<Precision> localMin, localMax;
0245 unplaced->Extent(localMin, localMax);
0246 SlabBox localBox;
0247 localBox.min = localMin;
0248 localBox.max = localMax;
0249 return localBox.SurfaceArea();
0250 }
0251
0252 inline Precision AABBSubdivider::AABBInflationRatio(VUnplacedVolume const *unplaced, SlabBox const &globalBox)
0253 {
0254 const Precision localSA = ExtentSurfaceArea(unplaced);
0255 return localSA > 0. ? globalBox.SurfaceArea() / localSA : Precision(0);
0256 }
0257
0258 inline Precision AABBSubdivider::CandidateObjective(Precision totalSA, std::size_t nSlabs, Precision globalSA,
0259 AABBSubdividerConfig const &cfg)
0260 {
0261 if (globalSA <= 0.) return std::numeric_limits<Precision>::max();
0262 Precision penalty = cfg.fastBoxPenalty;
0263 if (cfg.mode == AABBSubdivisionMode::kBalanced) penalty = cfg.balancedBoxPenalty;
0264 if (cfg.mode == AABBSubdivisionMode::kNavigation) penalty = cfg.navigationBoxPenalty;
0265 return totalSA / globalSA + penalty * Precision(nSlabs > 0 ? nSlabs - 1 : 0);
0266 }
0267
0268 inline Precision AABBSubdivider::ModeMinReduction(AABBSubdividerConfig const &cfg)
0269 {
0270 if (cfg.mode == AABBSubdivisionMode::kNavigation) return cfg.navigationMinReduction;
0271 if (cfg.mode == AABBSubdivisionMode::kBalanced) return cfg.balancedMinReduction;
0272 return cfg.fastMinReduction;
0273 }
0274
0275 inline int AABBSubdivider::ModeMaxM(AABBSubdividerConfig const &cfg)
0276 {
0277 if (cfg.mode == AABBSubdivisionMode::kNavigation) return std::clamp(cfg.autoMaxM, 1, kMaximumAABBSubdivisionM);
0278 if (cfg.mode == AABBSubdivisionMode::kBalanced) return std::clamp(cfg.balancedMaxM, 1, kMaximumAABBSubdivisionM);
0279 if (cfg.mode == AABBSubdivisionMode::kFast) return std::clamp(cfg.fastMaxM, 1, kMaximumAABBSubdivisionM);
0280 return 1;
0281 }
0282
0283 inline int AABBSubdivider::LongestAxis(SlabBox const &box)
0284 {
0285 const auto extent = box.max - box.min;
0286 int axis = 0;
0287 if (extent[1] > extent[axis]) axis = 1;
0288 if (extent[2] > extent[axis]) axis = 2;
0289 return axis;
0290 }
0291
0292 inline int AABBSubdivider::EstimateFastMaxM(SlabBox const &box, int maxM)
0293 {
0294 if (maxM <= 1) return 1;
0295
0296
0297
0298
0299 Precision extent[3] = {box.max[0] - box.min[0], box.max[1] - box.min[1], box.max[2] - box.min[2]};
0300 std::sort(extent, extent + 3, [](Precision a, Precision b) { return a > b; });
0301 if (!(extent[1] > 0.)) return 1;
0302
0303 const int aspectM = static_cast<int>(std::ceil(extent[0] / extent[1]));
0304 return std::max(2, std::min(maxM, aspectM));
0305 }
0306
0307 inline std::vector<SlabBox> AABBSubdivider::BestAxisSlabs(VUnplacedVolume const *unplaced, Transformation3D const *tr,
0308 int M, SlabBox const &globalBox,
0309 AABBSubdividerConfig const &cfg, int &outBestAxis,
0310 Precision &outSA)
0311 {
0312 std::vector<SlabBox> bestSlabs;
0313 Precision bestObjective = std::numeric_limits<Precision>::max();
0314 outBestAxis = 0;
0315 outSA = std::numeric_limits<Precision>::max();
0316
0317 const Precision globalSA = globalBox.SurfaceArea();
0318 for (int axis = 0; axis < 3; ++axis) {
0319 if (globalBox.max[axis] - globalBox.min[axis] < Precision(1e-10)) continue;
0320
0321 auto slabs = ComputeSlabs(unplaced, tr, axis, M, globalBox);
0322 const Precision totalSA = TotalSurfaceArea(slabs);
0323 const Precision objective = CandidateObjective(totalSA, slabs.size(), globalSA, cfg);
0324 const bool betterObjective = objective < bestObjective;
0325 const bool tiedButTighterSA = objective == bestObjective && totalSA < outSA;
0326 if (betterObjective || tiedButTighterSA) {
0327 bestObjective = objective;
0328 outBestAxis = axis;
0329 outSA = totalSA;
0330 bestSlabs = std::move(slabs);
0331 }
0332 }
0333 return bestSlabs;
0334 }
0335
0336 inline std::vector<SlabBox> AABBSubdivider::Subdivide(VPlacedVolume const *placed, int M,
0337 AABBSubdividerConfig const &cfg) const
0338 {
0339 assert(placed && "AABBSubdivider::Subdivide: null placed volume");
0340 assert(M >= 1 && "AABBSubdivider::Subdivide: M must be >= 1");
0341
0342 VUnplacedVolume const *unplaced = placed->GetLogicalVolume()->GetUnplacedVolume();
0343 Transformation3D const *tr = placed->GetTransformation();
0344 SlabBox globalBox = ComputeGlobalAABB(unplaced, tr);
0345 if (M == 1) return {globalBox};
0346
0347 int bestAxis = 0;
0348 Precision totalSA = 0.;
0349 auto slabs = BestAxisSlabs(unplaced, tr, M, globalBox, cfg, bestAxis, totalSA);
0350 return slabs.empty() ? std::vector<SlabBox>{globalBox} : slabs;
0351 }
0352
0353 inline std::vector<SlabBox> AABBSubdivider::SubdivideAlongAxis(VPlacedVolume const *placed, int axisIdx, int M) const
0354 {
0355 assert(placed && "AABBSubdivider::SubdivideAlongAxis: null placed volume");
0356 assert(axisIdx >= 0 && axisIdx < 3 && "AABBSubdivider::SubdivideAlongAxis: invalid axis");
0357 assert(M >= 1 && "AABBSubdivider::SubdivideAlongAxis: M must be >= 1");
0358
0359 VUnplacedVolume const *unplaced = placed->GetLogicalVolume()->GetUnplacedVolume();
0360 Transformation3D const *tr = placed->GetTransformation();
0361 SlabBox globalBox = ComputeGlobalAABB(unplaced, tr);
0362
0363 return SubdivideAlongAxis(placed, axisIdx, M, globalBox);
0364 }
0365
0366 inline std::vector<SlabBox> AABBSubdivider::SubdivideAlongAxis(VPlacedVolume const *placed, int axisIdx, int M,
0367 SlabBox const &globalBox) const
0368 {
0369 assert(placed && "AABBSubdivider::SubdivideAlongAxis: null placed volume");
0370 assert(axisIdx >= 0 && axisIdx < 3 && "AABBSubdivider::SubdivideAlongAxis: invalid axis");
0371 assert(M >= 1 && "AABBSubdivider::SubdivideAlongAxis: M must be >= 1");
0372 assert(globalBox.Valid() && "AABBSubdivider::SubdivideAlongAxis: invalid baseline AABB");
0373
0374 if (M == 1) return {globalBox};
0375
0376 VUnplacedVolume const *unplaced = placed->GetLogicalVolume()->GetUnplacedVolume();
0377 Transformation3D const *tr = placed->GetTransformation();
0378 return ComputeSlabs(unplaced, tr, axisIdx, M, globalBox);
0379 }
0380
0381 inline AABBSubdivisionResult AABBSubdivider::SubdivideAuto(VPlacedVolume const *placed,
0382 AABBSubdividerConfig const &cfg) const
0383 {
0384 assert(placed && "AABBSubdivider::SubdivideAuto: null placed volume");
0385
0386 VUnplacedVolume const *unplaced = placed->GetLogicalVolume()->GetUnplacedVolume();
0387 Transformation3D const *tr = placed->GetTransformation();
0388 SlabBox globalBox = ComputeGlobalAABB(unplaced, tr);
0389
0390 AABBSubdivisionResult res;
0391 res.slabs = {globalBox};
0392 res.chosenM = 1;
0393 res.chosenAxis = 0;
0394 res.globalSA = globalBox.SurfaceArea();
0395 res.finalSA = res.globalSA;
0396 res.saHistory.push_back(res.globalSA);
0397
0398 if (res.globalSA < std::numeric_limits<Precision>::epsilon() || cfg.mode == AABBSubdivisionMode::kOff) return res;
0399
0400 std::vector<SlabBox> bestSlabs = res.slabs;
0401 Precision bestSA = res.globalSA;
0402 Precision bestObjective = CandidateObjective(bestSA, bestSlabs.size(), res.globalSA, cfg);
0403 int bestAxis = 0;
0404 int nonImproving = 0;
0405 const int maxM = ModeMaxM(cfg);
0406 const bool fastMode = cfg.mode == AABBSubdivisionMode::kFast;
0407
0408
0409
0410 const bool balancedSingleAxis = cfg.mode == AABBSubdivisionMode::kBalanced &&
0411 AABBInflationRatio(unplaced, globalBox) <= cfg.balancedFullSearchAABBInflation;
0412 const bool singleAxisMode = fastMode || balancedSingleAxis;
0413 res.usedSingleAxisSearch = singleAxisMode;
0414 const int fastAxis = singleAxisMode ? LongestAxis(globalBox) : 0;
0415 const int scanMaxM = singleAxisMode ? EstimateFastMaxM(globalBox, maxM) : maxM;
0416
0417 for (int M = 2; M <= scanMaxM; ++M) {
0418 int candidateAxis = 0;
0419 Precision candidateSA = std::numeric_limits<Precision>::max();
0420
0421
0422 auto candidateSlabs = singleAxisMode ? ComputeSlabs(unplaced, tr, fastAxis, M, globalBox)
0423 : BestAxisSlabs(unplaced, tr, M, globalBox, cfg, candidateAxis, candidateSA);
0424 if (singleAxisMode) {
0425 candidateAxis = fastAxis;
0426 candidateSA = TotalSurfaceArea(candidateSlabs);
0427 }
0428 res.saHistory.push_back(candidateSA);
0429 if (candidateSlabs.empty()) {
0430 ++nonImproving;
0431 } else {
0432 const Precision objective = CandidateObjective(candidateSA, candidateSlabs.size(), res.globalSA, cfg);
0433 if (objective < bestObjective) {
0434 bestSlabs = std::move(candidateSlabs);
0435 bestSA = candidateSA;
0436 bestObjective = objective;
0437 bestAxis = candidateAxis;
0438 res.chosenM = M;
0439 nonImproving = 0;
0440 } else {
0441 ++nonImproving;
0442 }
0443 }
0444
0445 if (!fastMode && nonImproving >= cfg.nonImprovingStopCount) break;
0446 }
0447
0448 const Precision rawReduction = (res.globalSA - bestSA) / res.globalSA;
0449 if (res.chosenM <= 1 || rawReduction < ModeMinReduction(cfg)) return res;
0450
0451 res.slabs = std::move(bestSlabs);
0452 res.chosenAxis = bestAxis;
0453 res.finalSA = bestSA;
0454 return res;
0455 }
0456
0457 }
0458 }