17#include "TGeoManager.h"
18#include "TGeoMatrix.h"
20#include "TGeoVolume.h"
37using BVHScalar = float;
38using BVHBBox = bvh::v2::BBox<BVHScalar, 3>;
39using BVHVec3 = bvh::v2::Vec<BVHScalar, 3>;
40using BVHNode = bvh::v2::Node<BVHScalar, 3>;
41using BVH = bvh::v2::Bvh<BVHNode>;
42using BVHRay = bvh::v2::Ray<BVHScalar, 3>;
45constexpr double kBoxTolerance = 1.e-3;
48inline float roundOutward(
double value,
bool up)
50 return std::nextafterf(
static_cast<float>(
value),
51 up ? std::numeric_limits<float>::infinity() : -
std::numeric_limits<float>::infinity());
55inline float truncateRoundUp(
double value)
57 const float rounded =
static_cast<float>(
value);
58 return rounded <
value ? std::nextafterf(rounded, std::numeric_limits<float>::infinity()) : rounded;
62inline double boxDistanceSq(
const BVHBBox&
box,
const double* point)
65 for (
int dimension = 0; dimension < 3; ++dimension) {
66 const double lower =
static_cast<double>(
box.min[dimension]);
67 const double upper =
static_cast<double>(
box.max[dimension]);
68 const double coordinate = point[dimension];
69 if (coordinate <
lower) {
70 const double gap =
lower - coordinate;
72 }
else if (coordinate >
upper) {
73 const double gap = coordinate -
upper;
82constexpr double kSafetyBoundShare = 1. / 3.;
84inline bool boxContains(
const BVHBBox&
box,
const double* point)
86 return point[0] >=
static_cast<double>(
box.min[0]) && point[0] <=
static_cast<double>(
box.max[0]) &&
87 point[1] >=
static_cast<double>(
box.min[1]) && point[1] <=
static_cast<double>(
box.max[1]) &&
88 point[2] >=
static_cast<double>(
box.min[2]) && point[2] <=
static_cast<double>(
box.max[2]);
98constexpr unsigned kSmallStackCapacity = 64;
101template <
typename T,
typename Traverse>
102auto withTraversalStack(
int treeDepth, Traverse&& traverse)
104 if (treeDepth + 2 <=
static_cast<int>(kSmallStackCapacity)) {
105 bvh::v2::SmallStack<T, kSmallStackCapacity>
stack;
106 return traverse(
stack);
108 bvh::v2::GrowingStack<T>
stack;
109 return traverse(
stack);
117 if (volume !=
nullptr) {
124 delete static_cast<BVH*
>(fBVH);
130 const auto*
bvh =
static_cast<const BVH*
>(fBVH);
131 if (
bvh ==
nullptr) {
134 return bvh->nodes.size() *
sizeof(BVHNode) +
bvh->prim_ids.size() *
sizeof(size_t);
142 delete static_cast<BVH*
>(fBVH);
146 if (fVolume ==
nullptr) {
150 const int nDaughters = fVolume->GetNdaughters();
151 fNbuilt = nDaughters;
152 if (nDaughters == 0) {
156 std::vector<BVHBBox> boxes;
157 std::vector<BVHVec3> centers;
158 boxes.reserve(nDaughters);
159 centers.reserve(nDaughters);
164 TGeoNode*
node = fVolume->GetNode(
index);
165 TGeoShape* shape =
node->GetVolume()->GetShape();
168 if (
node->GetVolume()->IsAssembly() || TGeoShape::IsSameWithinTolerance(((
TGeoBBox*)shape)->GetDX(), 0.)) {
169 shape->ComputeBBox();
171 ((
TGeoBBox*)shape)->SetBoxPoints(corners);
172 double lower[3] = {TGeoShape::Big(), TGeoShape::Big(), TGeoShape::Big()};
173 double upper[3] = {-TGeoShape::Big(), -TGeoShape::Big(), -TGeoShape::Big()};
174 for (
int corner = 0; corner < 8; ++corner) {
175 node->LocalToMaster(&corners[3 * corner], master);
176 for (
int dimension = 0; dimension < 3; ++dimension) {
177 lower[dimension] = std::min(
lower[dimension], master[dimension]);
178 upper[dimension] = std::max(
upper[dimension], master[dimension]);
182 for (
int dimension = 0; dimension < 3; ++dimension) {
183 box.min[dimension] = roundOutward(
lower[dimension] - kBoxTolerance,
false);
184 box.max[dimension] = roundOutward(
upper[dimension] + kBoxTolerance,
true);
186 boxes.push_back(
box);
187 centers.emplace_back(
box.get_center());
190 typename bvh::v2::DefaultBuilder<BVHNode>::Config config;
191 config.quality = bvh::v2::DefaultBuilder<BVHNode>::Quality::High;
193 config.max_leaf_size = 1;
194 auto* built =
new BVH(bvh::v2::DefaultBuilder<BVHNode>::build(boxes, centers, config));
195 fBVH =
static_cast<void*
>(built);
198 std::vector<std::pair<size_t, int>> pending{{0, 1}};
199 while (!pending.empty()) {
202 fTreeDepth = std::max(fTreeDepth,
level);
204 if (!
node.is_leaf()) {
205 const size_t firstChild =
node.index.first_id();
206 for (
size_t child : {firstChild, firstChild + 1}) {
207 if (child < built->
nodes.size()) {
208 pending.push_back({child,
level + 1});
215void O2BVHAssembly::EnsureBuilt()
const
217 const int nDaughters = fVolume !=
nullptr ? fVolume->GetNdaughters() : 0;
218 if (fNbuilt == nDaughters && (fBVH !=
nullptr || nDaughters == 0)) {
233 if (!TGeoBBox::Contains(point)) {
236 const auto*
bvh =
static_cast<const BVH*
>(fBVH);
237 if (
bvh ==
nullptr) {
242 withTraversalStack<size_t>(fTreeDepth, [&](
auto&
stack) {
245 while (!
stack.is_empty()) {
246 const auto& node = bvh->nodes[stack.pop()];
247 if (!boxContains(node.get_bbox(), point)) {
250 if (
node.is_leaf()) {
251 const auto beginPrimitive = node.index.first_id();
252 const auto endPrimitive = beginPrimitive + node.index.prim_count();
253 for (auto primitive = beginPrimitive; primitive < endPrimitive; ++primitive) {
254 const int daughter = static_cast<int>(bvh->prim_ids[primitive]);
257 if (best >= 0 && daughter > best) {
260 TGeoNode* geoNode = fVolume->GetNode(daughter);
261 geoNode->MasterToLocal(point, local);
262 if (geoNode->GetVolume()->GetShape()->Contains(local)) {
267 const auto firstChild =
node.index.first_id();
268 for (
size_t child : {firstChild, firstChild + 1}) {
269 if (child < bvh->
nodes.size()) {
281 fVolume->SetCurrentNodeIndex(best);
282 fVolume->SetNextNodeIndex(best);
291 if (!TGeoBBox::Contains(point)) {
295 const int nDaughters = fVolume !=
nullptr ? fVolume->GetNdaughters() : 0;
297 TGeoNode* geoNode = fVolume->GetNode(
index);
298 geoNode->MasterToLocal(point, local);
299 if (geoNode->GetVolume()->GetShape()->Contains(local)) {
300 fVolume->SetCurrentNodeIndex(
index);
301 fVolume->SetNextNodeIndex(
index);
312 Double_t* safe)
const
318 if (iact < 3 && safe !=
nullptr) {
319 *safe =
Safety(point, kFALSE);
321 return TGeoShape::Big();
323 if (iact == 1 && step <= *safe) {
324 return TGeoShape::Big();
327 const auto*
bvh =
static_cast<const BVH*
>(fBVH);
328 if (
bvh ==
nullptr) {
329 return TGeoShape::Big();
332 double best = TGeoShape::Big();
334 BVHRay ray(BVHVec3(
static_cast<float>(point[0]),
static_cast<float>(point[1]),
static_cast<float>(point[2])),
335 BVHVec3(
static_cast<float>(dir[0]),
static_cast<float>(dir[1]),
static_cast<float>(dir[2])), 0.f,
336 truncateRoundUp(step + kBoxTolerance));
337 static constexpr bool useRobustTraversal =
true;
338 auto* volume = fVolume;
339 withTraversalStack<BVH::Index>(fTreeDepth, [&](
auto&
stack) {
340 bvh->intersect<
false, useRobustTraversal>(
341 ray,
bvh->get_root().
index,
stack, [&](
size_t beginPrimitive,
size_t endPrimitive) {
344 for (
size_t primitive = beginPrimitive; primitive < endPrimitive; ++primitive) {
345 const int daughter =
static_cast<int>(
bvh->prim_ids[primitive]);
346 TGeoNode* geoNode = volume->GetNode(daughter);
347 geoNode->MasterToLocal(point, local);
348 geoNode->MasterToLocalVect(dir, localDir);
349 const double distance = geoNode->GetVolume()->GetShape()->DistFromOutside(local, localDir, 3, step);
352 bestIndex = daughter;
353 }
else if (
distance == best && daughter < bestIndex) {
354 bestIndex = daughter;
359 if (bestIndex >= 0) {
360 ray.tmax = std::min(ray.tmax, truncateRoundUp(best + kBoxTolerance));
366 if (bestIndex < 0 || best >= step) {
367 return TGeoShape::Big();
369 volume->SetNextNodeIndex(bestIndex);
378 double best = TGeoShape::Big();
382 const int nDaughters = fVolume !=
nullptr ? fVolume->GetNdaughters() : 0;
384 TGeoNode* geoNode = fVolume->GetNode(
index);
385 geoNode->MasterToLocal(point, local);
386 geoNode->MasterToLocalVect(dir, localDir);
387 const double distance = geoNode->GetVolume()->GetShape()->DistFromOutside(local, localDir, 3, step);
393 if (bestIndex < 0 || best >= step) {
394 return TGeoShape::Big();
396 fVolume->SetNextNodeIndex(bestIndex);
406 return TGeoShapeAssembly::Safety(point, in);
412 const auto*
bvh =
static_cast<const BVH*
>(fBVH);
413 if (
bvh ==
nullptr) {
414 return TGeoShape::Big();
417 return withTraversalStack<SafetyEntry>(fTreeDepth, [&](
auto&
stack) {
418 double best = TGeoShape::Big();
419 stack.push({boxDistanceSq(
bvh->nodes[0].get_bbox(), point), size_t(0)});
420 while (!
stack.is_empty()) {
422 if (
entry.distanceSq * kSafetyBoundShare >= best * best) {
426 if (
node.is_leaf()) {
427 const auto beginPrimitive =
node.index.first_id();
428 const auto endPrimitive = beginPrimitive +
node.index.prim_count();
429 for (
auto primitive = beginPrimitive; primitive < endPrimitive; ++primitive) {
430 const int daughter =
static_cast<int>(
bvh->prim_ids[primitive]);
431 const double safety = fVolume->GetNode(daughter)->Safety(point, kFALSE);
435 best = std::min(best, safety);
438 const auto firstChild =
node.index.first_id();
439 const size_t children[2] = {firstChild, firstChild + 1};
440 double distancesSq[2] = {TGeoShape::Big(), TGeoShape::Big()};
442 if (children[
side] <
bvh->nodes.size()) {
443 distancesSq[
side] = boxDistanceSq(
bvh->nodes[children[
side]].get_bbox(), point);
447 const bool leftIsFarther = distancesSq[0] >= distancesSq[1];
448 const int order[2] = {leftIsFarther ? 0 : 1, leftIsFarther ? 1 : 0};
450 const int which = order[
side];
451 if (children[which] <
bvh->nodes.size() && distancesSq[which] * kSafetyBoundShare < best * best) {
452 stack.push({distancesSq[which], children[which]});
464 return TGeoShapeAssembly::Safety(point, in);
466 double best = TGeoShape::Big();
467 const int nDaughters = fVolume !=
nullptr ? fVolume->GetNdaughters() : 0;
469 const double safety = fVolume->GetNode(
index)->Safety(point, kFALSE);
473 best = std::min(best, safety);
483 if (volume ==
nullptr) {
487 volume->SetShape(shape);
std::unique_ptr< expressions::Node > node
static O2BVHAssembly * MakeBVHAssembly(TGeoVolumeAssembly *volume)
Replace volume's shape by an O2BVHAssembly and return it.
Double_t DistFromOutside_Loop(const Double_t *point, const Double_t *dir, Double_t step=TGeoShape::Big()) const
Double_t DistFromOutside(const Double_t *point, const Double_t *dir, Int_t iact=1, Double_t step=TGeoShape::Big(), Double_t *safe=nullptr) const override
DistFromOutside – daughters are queried with the fixed query bound, so the answer is visit-order inde...
void BuildBVH()
(Re)build the acceleration structure from the volume's current daughter list.
size_t GetBVHMemory() const
Bytes held by the BVH nodes and the primitive-index permutation.
Double_t Safety(const Double_t *point, Bool_t in=kTRUE) const override
Safety – from inside as ROOT; from outside a nearest-daughter descent of the BVH.
Double_t Safety_Loop(const Double_t *point, Bool_t in=kTRUE) const
Bool_t Contains_Loop(const Double_t *point) const
Bool_t Contains(const Double_t *point) const override
Contains.
~O2BVHAssembly() override
GLsizei GLsizei GLfloat distance
GLsizei const GLfloat * value
GLsizei const GLint * box
double distanceSq(const Vec2 &firstPoint, const Vec2 &secondPoint)