Project
Loading...
Searching...
No Matches
O2BVHAssembly.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
13
15
16#include "TGeoBBox.h"
17#include "TGeoManager.h"
18#include "TGeoMatrix.h"
19#include "TGeoNode.h"
20#include "TGeoVolume.h"
21
22// the same third-party BVH2 entry point O2Tessellated and O2BVHSurfaceSolid use
23#include "bvh2_third_party.h"
24#include "bvh2_extra_kernels.h"
25
26#include <algorithm>
27#include <cmath>
28#include <limits>
29#include <vector>
30
31using namespace o2::cad;
33
34namespace
35{
36// float BVH types, following the O2Tessellated::BuildBVH pattern
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>;
43
45constexpr double kBoxTolerance = 1.e-3;
46
48inline float roundOutward(double value, bool up)
49{
50 return std::nextafterf(static_cast<float>(value),
51 up ? std::numeric_limits<float>::infinity() : -std::numeric_limits<float>::infinity());
52}
53
55inline float truncateRoundUp(double value)
56{
57 const float rounded = static_cast<float>(value);
58 return rounded < value ? std::nextafterf(rounded, std::numeric_limits<float>::infinity()) : rounded;
59}
60
62inline double boxDistanceSq(const BVHBBox& box, const double* point)
63{
64 double distanceSq = 0.;
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;
71 distanceSq += gap * gap;
72 } else if (coordinate > upper) {
73 const double gap = coordinate - upper;
74 distanceSq += gap * gap;
75 }
76 }
77 return distanceSq * (1. - 1.e-12);
78}
79
82constexpr double kSafetyBoundShare = 1. / 3.;
83
84inline bool boxContains(const BVHBBox& box, const double* point)
85{
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]);
89}
90
92struct SafetyEntry {
93 double distanceSq;
94 size_t node;
95};
96
98constexpr unsigned kSmallStackCapacity = 64;
99
101template <typename T, typename Traverse>
102auto withTraversalStack(int treeDepth, Traverse&& traverse)
103{
104 if (treeDepth + 2 <= static_cast<int>(kSmallStackCapacity)) {
105 bvh::v2::SmallStack<T, kSmallStackCapacity> stack;
106 return traverse(stack);
107 }
108 bvh::v2::GrowingStack<T> stack;
109 return traverse(stack);
110}
111} // namespace
112
114
115O2BVHAssembly::O2BVHAssembly(TGeoVolumeAssembly* volume) : TGeoShapeAssembly(volume)
116{
117 if (volume != nullptr) {
118 BuildBVH();
119 }
120}
121
123{
124 delete static_cast<BVH*>(fBVH);
125 fBVH = nullptr;
126}
127
129{
130 const auto* bvh = static_cast<const BVH*>(fBVH);
131 if (bvh == nullptr) {
132 return 0;
133 }
134 return bvh->nodes.size() * sizeof(BVHNode) + bvh->prim_ids.size() * sizeof(size_t);
135}
136
139
141{
142 delete static_cast<BVH*>(fBVH);
143 fBVH = nullptr;
144 fNbuilt = -1;
145 fTreeDepth = 0;
146 if (fVolume == nullptr) {
147 return;
148 }
149 ComputeBBox();
150 const int nDaughters = fVolume->GetNdaughters();
151 fNbuilt = nDaughters;
152 if (nDaughters == 0) {
153 return;
154 }
155
156 std::vector<BVHBBox> boxes;
157 std::vector<BVHVec3> centers;
158 boxes.reserve(nDaughters);
159 centers.reserve(nDaughters);
160
161 double corners[24];
162 double master[3];
163 for (int index = 0; index < nDaughters; ++index) {
164 TGeoNode* node = fVolume->GetNode(index);
165 TGeoShape* shape = node->GetVolume()->GetShape();
166 // an assembly daughter, or one whose box was never computed, has to produce it first --
167 // the same guard TGeoShapeAssembly::RecomputeBoxLast uses
168 if (node->GetVolume()->IsAssembly() || TGeoShape::IsSameWithinTolerance(((TGeoBBox*)shape)->GetDX(), 0.)) {
169 shape->ComputeBBox();
170 }
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]);
179 }
180 }
181 BVHBBox box;
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);
185 }
186 boxes.push_back(box);
187 centers.emplace_back(box.get_center());
188 }
189
190 typename bvh::v2::DefaultBuilder<BVHNode>::Config config;
191 config.quality = bvh::v2::DefaultBuilder<BVHNode>::Quality::High;
192 // One daughter per leaf: bvh2 enters a leaf without a box test, and a daughter query costs far more than one.
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);
196
197 // tree depth, which decides whether a traversal fits the fixed-size stack
198 std::vector<std::pair<size_t, int>> pending{{0, 1}};
199 while (!pending.empty()) {
200 const auto [index, level] = pending.back();
201 pending.pop_back();
202 fTreeDepth = std::max(fTreeDepth, level);
203 const auto& node = built->nodes[index];
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});
209 }
210 }
211 }
212 }
213}
214
215void O2BVHAssembly::EnsureBuilt() const
216{
217 const int nDaughters = fVolume != nullptr ? fVolume->GetNdaughters() : 0;
218 if (fNbuilt == nDaughters && (fBVH != nullptr || nDaughters == 0)) {
219 return;
220 }
221 const_cast<O2BVHAssembly*>(this)->BuildBVH();
222}
223
226
227Bool_t O2BVHAssembly::Contains(const Double_t* point) const
228{
229 EnsureBuilt();
230 if (!fBBoxOK) {
231 const_cast<O2BVHAssembly*>(this)->ComputeBBox();
232 }
233 if (!TGeoBBox::Contains(point)) {
234 return kFALSE;
235 }
236 const auto* bvh = static_cast<const BVH*>(fBVH);
237 if (bvh == nullptr) {
238 return kFALSE;
239 }
240
241 int best = -1;
242 withTraversalStack<size_t>(fTreeDepth, [&](auto& stack) {
243 double local[3];
244 stack.push(0); // the bvh2 root node
245 while (!stack.is_empty()) {
246 const auto& node = bvh->nodes[stack.pop()];
247 if (!boxContains(node.get_bbox(), point)) {
248 continue;
249 }
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]);
255 // the loop twin takes the lowest-indexed daughter that contains the point, so a candidate
256 // that cannot beat the standing answer need not be resolved at all
257 if (best >= 0 && daughter > best) {
258 continue;
259 }
260 TGeoNode* geoNode = fVolume->GetNode(daughter);
261 geoNode->MasterToLocal(point, local);
262 if (geoNode->GetVolume()->GetShape()->Contains(local)) {
263 best = daughter;
264 }
265 }
266 } else {
267 const auto firstChild = node.index.first_id();
268 for (size_t child : {firstChild, firstChild + 1}) {
269 if (child < bvh->nodes.size()) {
270 stack.push(child);
271 }
272 }
273 }
274 }
275 });
276
277 if (best < 0) {
278 return kFALSE;
279 }
280 // this is how the daughter identity reaches TGeoNavigator, and through it the hit
281 fVolume->SetCurrentNodeIndex(best);
282 fVolume->SetNextNodeIndex(best);
283 return kTRUE;
284}
285
286Bool_t O2BVHAssembly::Contains_Loop(const Double_t* point) const
287{
288 if (!fBBoxOK) {
289 const_cast<O2BVHAssembly*>(this)->ComputeBBox();
290 }
291 if (!TGeoBBox::Contains(point)) {
292 return kFALSE;
293 }
294 double local[3];
295 const int nDaughters = fVolume != nullptr ? fVolume->GetNdaughters() : 0;
296 for (int index = 0; index < nDaughters; ++index) {
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);
302 return kTRUE;
303 }
304 }
305 return kFALSE;
306}
307
310
311Double_t O2BVHAssembly::DistFromOutside(const Double_t* point, const Double_t* dir, Int_t iact, Double_t step,
312 Double_t* safe) const
313{
314 EnsureBuilt();
315 if (!fBBoxOK) {
316 const_cast<O2BVHAssembly*>(this)->ComputeBBox();
317 }
318 if (iact < 3 && safe != nullptr) {
319 *safe = Safety(point, kFALSE);
320 if (iact == 0) {
321 return TGeoShape::Big();
322 }
323 if (iact == 1 && step <= *safe) {
324 return TGeoShape::Big();
325 }
326 }
327 const auto* bvh = static_cast<const BVH*>(fBVH);
328 if (bvh == nullptr) {
329 return TGeoShape::Big();
330 }
331
332 double best = TGeoShape::Big();
333 int bestIndex = -1;
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) {
342 double local[3];
343 double localDir[3];
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);
350 if (distance < best) {
351 best = distance;
352 bestIndex = daughter;
353 } else if (distance == best && daughter < bestIndex) {
354 bestIndex = daughter;
355 }
356 }
357 // A daughter whose box the ray only meets beyond best + kBoxTolerance cannot cross nearer,
358 // and cannot tie either: its true crossing is at least its box entry distance.
359 if (bestIndex >= 0) {
360 ray.tmax = std::min(ray.tmax, truncateRoundUp(best + kBoxTolerance));
361 }
362 return false; // keep traversing
363 });
364 });
365
366 if (bestIndex < 0 || best >= step) {
367 return TGeoShape::Big();
368 }
369 volume->SetNextNodeIndex(bestIndex);
370 return best;
371}
372
373Double_t O2BVHAssembly::DistFromOutside_Loop(const Double_t* point, const Double_t* dir, Double_t step) const
374{
375 if (!fBBoxOK) {
376 const_cast<O2BVHAssembly*>(this)->ComputeBBox();
377 }
378 double best = TGeoShape::Big();
379 int bestIndex = -1;
380 double local[3];
381 double localDir[3];
382 const int nDaughters = fVolume != nullptr ? fVolume->GetNdaughters() : 0;
383 for (int index = 0; index < nDaughters; ++index) {
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);
388 if (distance < best) {
389 best = distance;
390 bestIndex = index;
391 }
392 }
393 if (bestIndex < 0 || best >= step) {
394 return TGeoShape::Big();
395 }
396 fVolume->SetNextNodeIndex(bestIndex);
397 return best;
398}
399
402
403Double_t O2BVHAssembly::Safety(const Double_t* point, Bool_t in) const
404{
405 if (in) {
406 return TGeoShapeAssembly::Safety(point, in);
407 }
408 EnsureBuilt();
409 if (!fBBoxOK) {
410 const_cast<O2BVHAssembly*>(this)->ComputeBBox();
411 }
412 const auto* bvh = static_cast<const BVH*>(fBVH);
413 if (bvh == nullptr) {
414 return TGeoShape::Big();
415 }
416
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()) {
421 const SafetyEntry entry = stack.pop();
422 if (entry.distanceSq * kSafetyBoundShare >= best * best) {
423 continue;
424 }
425 const auto& node = bvh->nodes[entry.node];
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);
432 if (safety <= 0.) {
433 return 0.;
434 }
435 best = std::min(best, safety);
436 }
437 } else {
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()};
441 for (int side = 0; side < 2; ++side) {
442 if (children[side] < bvh->nodes.size()) {
443 distancesSq[side] = boxDistanceSq(bvh->nodes[children[side]].get_bbox(), point);
444 }
445 }
446 // push the farther child first so the nearer one is popped, and prunes, first
447 const bool leftIsFarther = distancesSq[0] >= distancesSq[1];
448 const int order[2] = {leftIsFarther ? 0 : 1, leftIsFarther ? 1 : 0};
449 for (int side = 0; side < 2; ++side) {
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]});
453 }
454 }
455 }
456 }
457 return best;
458 });
459}
460
461Double_t O2BVHAssembly::Safety_Loop(const Double_t* point, Bool_t in) const
462{
463 if (in) {
464 return TGeoShapeAssembly::Safety(point, in);
465 }
466 double best = TGeoShape::Big();
467 const int nDaughters = fVolume != nullptr ? fVolume->GetNdaughters() : 0;
468 for (int index = 0; index < nDaughters; ++index) {
469 const double safety = fVolume->GetNode(index)->Safety(point, kFALSE);
470 if (safety <= 0.) {
471 return 0.;
472 }
473 best = std::min(best, safety);
474 }
475 return best;
476}
477
480
482{
483 if (volume == nullptr) {
484 return nullptr;
485 }
486 auto* shape = new O2BVHAssembly(volume);
487 volume->SetShape(shape);
488 return shape;
489}
std::unique_ptr< expressions::Node > node
ClassImp(O2BVHAssembly)
double lower[3]
double upper[3]
uint32_t side
Definition RawData.h:0
uint32_t stack
Definition RawData.h:1
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.
GLuint entry
Definition glcorearb.h:5735
GLuint index
Definition glcorearb.h:781
GLsizei GLsizei GLfloat distance
Definition glcorearb.h:5506
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLint level
Definition glcorearb.h:275
GLsizei const GLint * box
Definition glcorearb.h:4697
double distanceSq(const Vec2 &firstPoint, const Vec2 &secondPoint)