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
configStandalone Light OpenGL display display_light Apply smoothing to points Enable Z buffer Highlight links Highlight seeds Highlight sector tracks Highlight final tracks Show individual Show related Highlight grid Show only individual collision Color clusters belonging to track objects Set line Enable drawing TPC data Enable drawing TOF data Enable drawing magnetic field Set field line step count Invert colors configStandalone Heavy OpenGL display display_heavy Show tracks with ITS contribution Show tracks with TOF contribution Enable propagation of loopers Show clusters at nominal x of pad row for early transformed data Split CE tracks when they cross the central Mark clusters Mark adjacent clusters Hide rejected tracks Apply filter on tracks to be displayed Separate increase X coordinate
configStandalone Reconstruction rec_tpc float
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 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)