Project
Loading...
Searching...
No Matches
O2FlatCSG.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 "BoundedSurface.h"
17
18// the same third-party BVH2 entry point O2Tessellated, O2BVHSurfaceSolid and O2BVHAssembly use
19#include "bvh2_third_party.h"
20#include "bvh2_extra_kernels.h"
21
22#include "TGeoShape.h"
23
24#include <algorithm>
25#include <cassert>
26#include <cmath>
27#include <cstddef>
28#include <limits>
29#include <numeric>
30#include <vector>
31
33
34namespace o2
35{
36namespace cad
37{
38
39namespace
40{
42constexpr int kMaxRootsPerHalfspace = 4;
43
45constexpr int kMaxCubifySplits = 10;
46
48int maxPairsForCell(int halfspaceCount)
49{
50 return 2 + kMaxRootsPerHalfspace * halfspaceCount;
51}
52
53// float BVH types: the BVH only nominates boxes, and roundOutward makes each node box a superset of its boxes.
54using BVHScalar = float;
55using BVHBBox = bvh::v2::BBox<BVHScalar, 3>;
56using BVHVec3 = bvh::v2::Vec<BVHScalar, 3>;
57using BVHNode = bvh::v2::Node<BVHScalar, 3>;
58using BVH = bvh::v2::Bvh<BVHNode>;
59
61thread_local long long gUnprunedRetryCount = 0;
62
64inline float roundOutward(double value, bool up)
65{
66 return std::nextafterf(static_cast<float>(value), up ? std::numeric_limits<float>::infinity()
67 : -std::numeric_limits<float>::infinity());
68}
69
72bool slabWindow(const double* boxMin, const double* boxMax, const double* origin, const double* dir,
73 double& tlo, double& thi)
74{
75 for (int index = 0; index < 3; ++index) {
76 if (std::abs(dir[index]) < 1.e-300) {
77 // parallel to this pair of faces: the ray is either inside the slab for every t or outside
78 // it for every t
79 if (origin[index] < boxMin[index] || origin[index] > boxMax[index]) {
80 return false;
81 }
82 continue;
83 }
84 double low = (boxMin[index] - origin[index]) / dir[index];
85 double high = (boxMax[index] - origin[index]) / dir[index];
86 if (low > high) {
87 std::swap(low, high);
88 }
89 tlo = std::max(tlo, low);
90 thi = std::min(thi, high);
91 if (tlo > thi) {
92 return false;
93 }
94 }
95 return true;
96}
97
99inline bool nodeWindow(const BVHBBox& box, const double* origin, const double* dir, double& tlo,
100 double& thi)
101{
102 const double lo[3] = {box.min[0], box.min[1], box.min[2]};
103 const double hi[3] = {box.max[0], box.max[1], box.max[2]};
104 return slabWindow(lo, hi, origin, dir, tlo, thi);
105}
106
108inline bool boxHoldsPoint(const FlatCSGBox& box, const double* point)
109{
110 return point[0] >= box.min[0] && point[0] <= box.max[0] && point[1] >= box.min[1] &&
111 point[1] <= box.max[1] && point[2] >= box.min[2] && point[2] <= box.max[2];
112}
113
115void halfspaceGradient(const FlatCSGHalfspace& halfspace, const double* point, double grad[3])
116{
117 if (halfspace.kind == FlatCSGHalfspace::kTorus) {
118 const double* c = halfspace.c;
119 const double axis[3] = {c[3], c[4], c[5]};
120 const double major = c[6];
121 const double offset[3] = {point[0] - c[0], point[1] - c[1], point[2] - c[2]};
122 const double along = offset[0] * axis[0] + offset[1] * axis[1] + offset[2] * axis[2];
123 double radial[3];
124 for (int index = 0; index < 3; ++index) {
125 radial[index] = offset[index] - along * axis[index];
126 }
127 const double rho = std::sqrt(radial[0] * radial[0] + radial[1] * radial[1] + radial[2] * radial[2]);
128 const double u = rho - major;
129 const double s = std::hypot(u, along);
130 if (s < 1.e-300 || rho < 1.e-300) {
131 // degenerate: on the revolution axis or the kissing point; leave it zero for the caller's fallback
132 grad[0] = grad[1] = grad[2] = 0.;
133 return;
134 }
135 const double du = u / s;
136 const double dv = along / s;
137 for (int index = 0; index < 3; ++index) {
138 grad[index] = halfspace.sign * (du * (radial[index] / rho) + dv * axis[index]);
139 }
140 return;
141 }
142 const double* c = halfspace.c;
143 const double a[3][3] = {{c[0], c[1], c[2]}, {c[1], c[3], c[4]}, {c[2], c[4], c[5]}};
144 const double b[3] = {c[6], c[7], c[8]};
145 for (int row = 0; row < 3; ++row) {
146 double value = b[row];
147 for (int column = 0; column < 3; ++column) {
148 value += a[row][column] * point[column];
149 }
150 grad[row] = halfspace.sign * 2. * value;
151 }
152}
153
157template <typename Visit>
158void traverseRay(const BVH& bvh, const double* origin, const double* dir, double cap, const double& tmax,
159 bool nearFirst, double* culled, Visit&& visit)
160{
161 struct Entry {
162 size_t node;
163 double tlo;
164 };
165 // thread_local rather than a member or a fresh vector per call: TGeo shares one shape object
166 // across every navigator under TGeoManager::SetMaxThreads, and this is not re-entered
167 thread_local std::vector<Entry> stack;
168 stack.clear();
169 const auto entersWithin = [&](size_t index, double& tlo) {
170 tlo = 0.;
171 double thi = cap;
172 return nodeWindow(bvh.nodes[index].get_bbox(), origin, dir, tlo, thi);
173 };
174 // a skipped node's own entry is a lower bound on every piece under it
175 const auto skip = [&](double tlo) {
176 if (culled != nullptr && tlo < *culled) {
177 *culled = tlo;
178 }
179 };
180 double rootTlo = 0.;
181 if (entersWithin(0, rootTlo)) {
182 stack.push_back({0, rootTlo}); // the bvh2 root node
183 }
184 while (!stack.empty()) {
185 const Entry entry = stack.back();
186 stack.pop_back();
187 if (entry.tlo > tmax) {
188 skip(entry.tlo); // the visitor lowered tmax past this node
189 continue;
190 }
191 const auto& node = bvh.nodes[entry.node];
192 if (node.is_leaf()) {
193 const auto beginPrimitive = node.index.first_id();
194 const auto endPrimitive = beginPrimitive + node.index.prim_count();
195 for (auto primitive = beginPrimitive; primitive < endPrimitive; ++primitive) {
196 visit(static_cast<int>(bvh.prim_ids[primitive]));
197 }
198 } else {
199 const auto firstChild = node.index.first_id();
200 Entry children[2];
201 int count = 0;
202 for (size_t child : {firstChild, firstChild + 1}) {
203 double tlo = 0.;
204 if (child < bvh.nodes.size() && entersWithin(child, tlo)) {
205 if (tlo > tmax) {
206 skip(tlo);
207 } else {
208 children[count++] = {child, tlo};
209 }
210 }
211 }
212 // LIFO: the farther child is pushed first
213 if (nearFirst && count == 2 && children[0].tlo < children[1].tlo) {
214 std::swap(children[0], children[1]);
215 }
216 for (int index = 0; index < count; ++index) {
217 stack.push_back(children[index]);
218 }
219 }
220 }
221}
224template <typename Visit>
225bool traversePoint(const BVH& bvh, const double* point, Visit&& visit)
226{
227 const BVHVec3 query(static_cast<float>(point[0]), static_cast<float>(point[1]),
228 static_cast<float>(point[2]));
229 thread_local std::vector<size_t> stack;
230 stack.clear();
231 stack.push_back(0); // the bvh2 root node
232 while (!stack.empty()) {
233 const size_t current = stack.back();
234 stack.pop_back();
235 const auto& node = bvh.nodes[current];
236 if (!bvh::v2::extra::contains(node.get_bbox(), query)) {
237 continue;
238 }
239 if (node.is_leaf()) {
240 const auto beginPrimitive = node.index.first_id();
241 const auto endPrimitive = beginPrimitive + node.index.prim_count();
242 for (auto primitive = beginPrimitive; primitive < endPrimitive; ++primitive) {
243 if (visit(static_cast<int>(bvh.prim_ids[primitive]))) {
244 return true;
245 }
246 }
247 } else {
248 const auto firstChild = node.index.first_id();
249 for (size_t child : {firstChild, firstChild + 1}) {
250 if (child < bvh.nodes.size()) {
251 stack.push_back(child);
252 }
253 }
254 }
255 }
256 return false;
257}
258
260inline double boxDistanceSquared(const FlatCSGBox& box, const double* point)
261{
262 double squared = 0.;
263 for (int index = 0; index < 3; ++index) {
264 const double value = point[index];
265 if (value < box.min[index]) {
266 squared += (box.min[index] - value) * (box.min[index] - value);
267 } else if (value > box.max[index]) {
268 squared += (value - box.max[index]) * (value - box.max[index]);
269 }
270 }
271 return squared;
272}
273
275inline double distanceToFaces(const FlatCSGBox& box, const double* point)
276{
277 double toFace = TGeoShape::Big();
278 for (int index = 0; index < 3; ++index) {
279 toFace = std::min(toFace, std::min(point[index] - box.min[index], box.max[index] - point[index]));
280 }
281 return toFace;
282}
283} // namespace
284
286
287O2FlatCSG::O2FlatCSG(const char* name) : TGeoBBox(name, 0., 0., 0.) {}
288
290{
291 delete static_cast<BVH*>(fBVH);
292 fBVH = nullptr;
293}
294
296{
297 const auto* bvh = static_cast<const BVH*>(fBVH);
298 if (bvh == nullptr) {
299 return 0;
300 }
301 return bvh->nodes.size() * sizeof(BVHNode) + bvh->prim_ids.size() * sizeof(size_t);
302}
303
304int O2FlatCSG::AddQuadric(double sign, const double coeff[10])
305{
306 FlatCSGHalfspace halfspace;
308 halfspace.sign = sign < 0. ? -1. : 1.;
309 for (int index = 0; index < 10; ++index) {
310 halfspace.c[index] = coeff[index];
311 }
312 fHalfspaces.push_back(halfspace);
313 return static_cast<int>(fHalfspaces.size()) - 1;
314}
315
316int O2FlatCSG::AddTorus(double sign, const double* centre, const double* axis, double major,
317 double minor)
318{
319 FlatCSGHalfspace halfspace;
320 halfspace.kind = FlatCSGHalfspace::kTorus;
321 halfspace.sign = sign < 0. ? -1. : 1.;
322 // normalise the axis once here; a zero axis is a caller bug and asserts
323 const double axisNorm = std::sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
324 assert(axisNorm > 0. && "O2FlatCSG::AddTorus: axis must not be the zero vector");
325 for (int index = 0; index < 3; ++index) {
326 halfspace.c[index] = centre[index];
327 halfspace.c[3 + index] = axis[index] / axisNorm;
328 }
329 halfspace.c[6] = major;
330 halfspace.c[7] = minor;
331 fHalfspaces.push_back(halfspace);
332 return static_cast<int>(fHalfspaces.size()) - 1;
333}
334
335int O2FlatCSG::AddCell(int first, int count, double volume)
336{
337 FlatCSGCell cell;
338 cell.first = first;
339 cell.count = count;
340 cell.volume = volume;
341 fCells.push_back(cell);
342 return static_cast<int>(fCells.size()) - 1;
343}
344
346{
347 if (static_cast<int>(fCellBBoxSet.size()) < GetNcells()) {
348 fCellLo.resize(3 * GetNcells(), 0.);
349 fCellHi.resize(3 * GetNcells(), 0.);
350 fCellBBoxSet.resize(GetNcells(), false);
351 }
352}
353
354void O2FlatCSG::SetCellBBox(int cell, const double* lo, const double* hi)
355{
356 if (cell < 0 || cell >= GetNcells()) {
357 // a cell index before its AddCell would write past the end of fCellLo/fCellHi
358 Error("SetCellBBox", "Shape %s: cell %d is out of range (%d cell(s) so far); ignoring",
359 GetName(), cell, GetNcells());
360 return;
361 }
363 for (int index = 0; index < 3; ++index) {
364 fCellLo[3 * cell + index] = lo[index];
365 fCellHi[3 * cell + index] = hi[index];
366 }
367 fCellBBoxSet[cell] = true;
368}
369
370void O2FlatCSG::GetCellBBox(int cell, double* lo, double* hi) const
371{
372 const bool set = cell >= 0 && cell < GetNcells() && static_cast<size_t>(cell) < fCellBBoxSet.size() &&
373 fCellBBoxSet[cell];
374 for (int index = 0; index < 3; ++index) {
375 lo[index] = set ? fCellLo[3 * cell + index] : 0.;
376 hi[index] = set ? fCellHi[3 * cell + index] : 0.;
377 }
378}
379
380double O2FlatCSG::EvalHalfspace(const FlatCSGHalfspace& halfspace, const double* point)
381{
382 if (halfspace.kind == FlatCSGHalfspace::kTorus) {
383 const double* c = halfspace.c;
384 const double offset[3] = {point[0] - c[0], point[1] - c[1], point[2] - c[2]};
385 const double along = offset[0] * c[3] + offset[1] * c[4] + offset[2] * c[5];
386 const double radial[3] = {offset[0] - along * c[3], offset[1] - along * c[4],
387 offset[2] - along * c[5]};
388 const double rho = std::sqrt(radial[0] * radial[0] + radial[1] * radial[1] +
389 radial[2] * radial[2]);
390 // the exact signed distance, which is 1-Lipschitz
391 return halfspace.sign * (std::hypot(rho - c[6], along) - c[7]);
392 }
393 const double* c = halfspace.c;
394 const double x = point[0];
395 const double y = point[1];
396 const double z = point[2];
397 const double quadratic = c[0] * x * x + c[3] * y * y + c[5] * z * z +
398 2. * (c[1] * x * y + c[2] * x * z + c[4] * y * z);
399 const double linear = 2. * (c[6] * x + c[7] * y + c[8] * z);
400 return halfspace.sign * (quadratic + linear + c[9]);
401}
402
403void O2FlatCSG::HalfspaceRange(const FlatCSGHalfspace& halfspace, const double* lo,
404 const double* hi, double& rangeLo, double& rangeHi)
405{
406 // preconditions (see the header): non-negative half-extents and finite bounds
407 assert(std::isfinite(lo[0]) && std::isfinite(lo[1]) && std::isfinite(lo[2]) &&
408 std::isfinite(hi[0]) && std::isfinite(hi[1]) && std::isfinite(hi[2]) &&
409 lo[0] <= hi[0] && lo[1] <= hi[1] && lo[2] <= hi[2] &&
410 "O2FlatCSG::HalfspaceRange: lo/hi must be finite and lo[i] <= hi[i] on every axis");
411
412 double centre[3];
413 double half[3];
414 for (int index = 0; index < 3; ++index) {
415 centre[index] = 0.5 * (lo[index] + hi[index]);
416 half[index] = 0.5 * (hi[index] - lo[index]);
417 }
418 const double middle = EvalHalfspace(halfspace, centre);
419
420 // Pad by 64 eps times the summed term magnitudes, not |middle|, which cancels on a straddling box.
421 constexpr double kPadFactor = 64. * std::numeric_limits<double>::epsilon();
422
423 double halfWidth;
424 double mag;
425 if (halfspace.kind == FlatCSGHalfspace::kTorus) {
426 // the torus's signed distance is 1-Lipschitz, so over the box it deviates by at most |h|
427 halfWidth = std::sqrt(half[0] * half[0] + half[1] * half[1] + half[2] * half[2]);
428 const double* c = halfspace.c;
429 const double offset[3] = {centre[0] - c[0], centre[1] - c[1], centre[2] - c[2]};
430 const double along = offset[0] * c[3] + offset[1] * c[4] + offset[2] * c[5];
431 const double radial[3] = {offset[0] - along * c[3], offset[1] - along * c[4],
432 offset[2] - along * c[5]};
433 const double rho = std::sqrt(radial[0] * radial[0] + radial[1] * radial[1] +
434 radial[2] * radial[2]);
435 // mag needs no term for the centre's scale: near the core circle offset is exact by Sterbenz's lemma
436 mag = rho + std::abs(c[6]) + std::abs(along) + std::abs(c[7]);
437 } else {
438 const double* c = halfspace.c;
439 const double a[3][3] = {{c[0], c[1], c[2]}, {c[1], c[3], c[4]}, {c[2], c[4], c[5]}};
440 const double b[3] = {c[6], c[7], c[8]};
441 double slack = 0.;
442 mag = std::abs(c[9]);
443 for (int row = 0; row < 3; ++row) {
444 double gradient = b[row];
445 mag += 2. * std::abs(b[row] * centre[row]);
446 for (int column = 0; column < 3; ++column) {
447 gradient += a[row][column] * centre[column];
448 // sum |A_ij| h_i h_j over-estimates the cross-term deviation only for non-negative half-extents
449 slack += std::abs(a[row][column]) * half[row] * half[column];
450 mag += std::abs(a[row][column] * centre[row] * centre[column]);
451 }
452 slack += 2. * std::abs(gradient) * half[row];
453 }
454 // |sign| == 1, so the unsigned slack bounds the signed deviation too
455 halfWidth = slack;
456 }
457 // widen by the pad: the drop tests treat the bound as exact and nActive == 0 is trusted
458 halfWidth += kPadFactor * mag;
459 rangeLo = middle - halfWidth;
460 rangeHi = middle + halfWidth;
461}
462
463bool O2FlatCSG::CellContains(int index, const double* point) const
464{
465 const FlatCSGCell& cell = fCells[index];
466 for (int offset = 0; offset < cell.count; ++offset) {
467 if (EvalHalfspace(fHalfspaces[cell.first + offset], point) > 0.) {
468 return false;
469 }
470 }
471 return true;
472}
473
474void O2FlatCSG::SplitBox(int cell, const double* lo, const double* hi,
475 const std::vector<int>& active, int depth, double minSize,
476 int cubifyBudget)
477{
478 std::vector<int> stillActive;
479 stillActive.reserve(active.size());
480 for (int halfspace : active) {
481 double rangeLo = 0.;
482 double rangeHi = 0.;
483 HalfspaceRange(fHalfspaces[halfspace], lo, hi, rangeLo, rangeHi);
484 if (rangeLo > 0.) {
485 return; // the box is wholly outside this halfspace, hence wholly outside the cell
486 }
487 if (rangeHi > 0.) {
488 stillActive.push_back(halfspace); // undecided; it stays
489 }
490 // rangeHi <= 0: the halfspace holds everywhere in the box, so it is dropped
491 }
492
493 double longest = 0.;
494 double shortest = TGeoShape::Big();
495 int axis = 0;
496 for (int index = 0; index < 3; ++index) {
497 const double extent = hi[index] - lo[index];
498 if (extent > longest) {
499 longest = extent;
500 axis = index;
501 }
502 shortest = std::min(shortest, extent);
503 }
504 // a split out of a far-from-cubic box draws on cubifyBudget, not on depth
505 // `shortest` is floored at minSize so a flat cell does not burn the whole cubifyBudget
506 const bool farFromCubic = longest > 2. * std::max(shortest, minSize);
507 const bool keep = stillActive.empty() || depth <= 0 || longest <= minSize ||
508 (farFromCubic && cubifyBudget <= 0);
509 if (keep) {
511 for (int index = 0; index < 3; ++index) {
512 box.min[index] = lo[index];
513 box.max[index] = hi[index];
514 }
515 box.cell = cell;
516 box.firstActive = static_cast<int>(fActive.size());
517 box.nActive = static_cast<int>(stillActive.size());
518 fActive.insert(fActive.end(), stillActive.begin(), stillActive.end());
519 fBoxes.push_back(box);
520 return;
521 }
522
523 const int childDepth = farFromCubic ? depth : depth - 1;
524 const int childCubifyBudget = farFromCubic ? cubifyBudget - 1 : cubifyBudget;
525 const double middle = 0.5 * (lo[axis] + hi[axis]);
526 double childLo[3] = {lo[0], lo[1], lo[2]};
527 double childHi[3] = {hi[0], hi[1], hi[2]};
528 childHi[axis] = middle;
529 SplitBox(cell, childLo, childHi, stillActive, childDepth, minSize, childCubifyBudget);
530 childHi[axis] = hi[axis];
531 childLo[axis] = middle;
532 SplitBox(cell, childLo, childHi, stillActive, childDepth, minSize, childCubifyBudget);
533}
534
536{
537 fBoxes.clear();
538 fActive.clear();
539 fClosed = false;
540 // dropped before the validation below can return: a BVH left over from an earlier CloseShape
541 // would describe boxes that no longer exist, and the queries key off `fBVH != nullptr`
542 delete static_cast<BVH*>(fBVH);
543 fBVH = nullptr;
544
546 // Refuse the whole shape when a cell's bbox is missing, inverted or non-finite: a cell without a box would vanish.
547 bool anyProblem = false;
548 for (int cell = 0; cell < GetNcells(); ++cell) {
549 if (!fCellBBoxSet[cell]) {
550 Error("CloseShape",
551 "Shape %s cell %d has no bounding box (SetCellBBox was never called for it); it would "
552 "silently vanish from the solid. Not building any boxes -- IsClosed() stays false.",
553 GetName(), cell);
554 anyProblem = true;
555 continue;
556 }
557 for (int index = 0; index < 3; ++index) {
558 const double loValue = fCellLo[3 * cell + index];
559 const double hiValue = fCellHi[3 * cell + index];
560 if (!std::isfinite(loValue) || !std::isfinite(hiValue)) {
561 Error("CloseShape",
562 "Shape %s cell %d has a non-finite bounding box on axis %d (lo %g, hi %g). Not "
563 "building any boxes -- IsClosed() stays false.",
564 GetName(), cell, index, loValue, hiValue);
565 anyProblem = true;
566 continue;
567 }
568 if (hiValue < loValue) {
569 Error("CloseShape",
570 "Shape %s cell %d has an inverted bounding box on axis %d (lo %g > hi %g); "
571 "SetCellBBox's arguments look swapped. Not building any boxes -- IsClosed() stays "
572 "false.",
573 GetName(), cell, index, loValue, hiValue);
574 anyProblem = true;
575 }
576 }
577 }
578 if (anyProblem) {
579 return;
580 }
581
582 double partLo[3] = {TGeoShape::Big(), TGeoShape::Big(), TGeoShape::Big()};
583 double partHi[3] = {-TGeoShape::Big(), -TGeoShape::Big(), -TGeoShape::Big()};
584 for (int cell = 0; cell < GetNcells(); ++cell) {
585 for (int index = 0; index < 3; ++index) {
586 partLo[index] = std::min(partLo[index], fCellLo[3 * cell + index]);
587 partHi[index] = std::max(partHi[index], fCellHi[3 * cell + index]);
588 }
589 }
590 const double diagonal = std::sqrt((partHi[0] - partLo[0]) * (partHi[0] - partLo[0]) +
591 (partHi[1] - partLo[1]) * (partHi[1] - partLo[1]) +
592 (partHi[2] - partLo[2]) * (partHi[2] - partLo[2]));
593 const double minSize = fMinBoxFraction * diagonal;
594
595#ifndef NDEBUG
596 // A cell must lie inside its bbox; one that spills out makes the accelerated queries and the twins disagree.
597 {
598 const double reach = 1.e-6 * (diagonal > 0. ? diagonal : 1.);
599 for (int cell = 0; cell < GetNcells(); ++cell) {
600 const double* cellLo = &fCellLo[3 * cell];
601 const double* cellHi = &fCellHi[3 * cell];
602 for (int axis = 0; axis < 3; ++axis) {
603 const int first = (axis + 1) % 3;
604 const int second = (axis + 2) % 3;
605 for (int side = 0; side < 2; ++side) {
606 for (int step1 = 0; step1 <= 4; ++step1) {
607 for (int step2 = 0; step2 <= 4; ++step2) {
608 double probe[3];
609 probe[axis] = side == 0 ? cellLo[axis] - reach : cellHi[axis] + reach;
610 probe[first] = cellLo[first] + 0.25 * step1 * (cellHi[first] - cellLo[first]);
611 probe[second] = cellLo[second] + 0.25 * step2 * (cellHi[second] - cellLo[second]);
612 assert(!CellContains(cell, probe) &&
613 "O2FlatCSG::CloseShape: a cell reaches past the bounding box SetCellBBox was "
614 "given, so this shape and its own _Loop twins answer differently out there. "
615 "The converter's box is the CAD piece's own bbox, so the cell is larger than "
616 "the part: close the cell's halfspaces or refuse the part -- do NOT widen "
617 "the box, which would ship the phantom material");
618 }
619 }
620 }
621 }
622 }
623 }
624#endif
625
626 for (int cell = 0; cell < GetNcells(); ++cell) {
627 std::vector<int> active;
628 active.reserve(fCells[cell].count);
629 for (int offset = 0; offset < fCells[cell].count; ++offset) {
630 active.push_back(fCells[cell].first + offset);
631 }
632 SplitBox(cell, &fCellLo[3 * cell], &fCellHi[3 * cell], active, fSplitDepth, minSize,
633 kMaxCubifySplits);
634 }
635
636 if (!fBoxes.empty()) {
637 std::vector<BVHBBox> boxes;
638 std::vector<BVHVec3> centers;
639 boxes.reserve(fBoxes.size());
640 centers.reserve(fBoxes.size());
641 for (const auto& box : fBoxes) {
642 BVHBBox bounds;
643 for (int index = 0; index < 3; ++index) {
644 // outward, so a float node box is a superset of the double box it stands for and the
645 // traversal can only ever nominate too many candidates -- never drop one
646 bounds.min[index] = roundOutward(box.min[index], false);
647 bounds.max[index] = roundOutward(box.max[index], true);
648 }
649 boxes.push_back(bounds);
650 centers.emplace_back(bounds.get_center());
651 }
652 typename bvh::v2::DefaultBuilder<BVHNode>::Config config;
653 config.quality = bvh::v2::DefaultBuilder<BVHNode>::Quality::High;
654 // One box per leaf: bvh2 enters a leaf without a box test, and each box is visited at most once per traversal.
655 config.max_leaf_size = 1;
656 fBVH = static_cast<void*>(
657 new BVH(bvh::v2::DefaultBuilder<BVHNode>::build(boxes, centers, config)));
658 }
659
660 fClosed = true;
661 ComputeBBox();
662}
663
664Bool_t O2FlatCSG::Contains_Loop(const Double_t* point) const
665{
666 for (int index = 0; index < GetNcells(); ++index) {
667 if (CellContains(index, point)) {
668 return kTRUE;
669 }
670 }
671 return kFALSE;
672}
673
676
677Bool_t O2FlatCSG::GetPointsOnSegments(Int_t npoints, Double_t* array) const
678{
679 if (array == nullptr || npoints <= 0 || !fClosed) {
680 return kFALSE;
681 }
682 // the boxes that carry boundary: those with a non-empty active list
683 std::vector<int> boundaryBoxes;
684 for (int index = 0; index < static_cast<int>(fBoxes.size()); ++index) {
685 if (fBoxes[index].nActive > 0) {
686 boundaryBoxes.push_back(index);
687 }
688 }
689 if (boundaryBoxes.empty()) {
690 return kFALSE;
691 }
692 // the R2 low-discrepancy pair O2Tessellated uses, mapped to directions on the unit sphere
693 constexpr double kAlpha1 = 0.7548776662466927;
694 constexpr double kAlpha2 = 0.5698402909980532;
695 constexpr double kFlipProbe = 1.e-6;
696 const double zAxis[3] = {0., 0., 1.};
697 std::vector<double> pairs;
698 int produced = 0;
699 const long long maxAttempts = 64LL * npoints;
700 for (long long attempt = 0; attempt < maxAttempts && produced < npoints; ++attempt) {
701 const FlatCSGBox& box = fBoxes[boundaryBoxes[attempt % static_cast<long long>(boundaryBoxes.size())]];
702 const double u = std::fmod(0.5 + kAlpha1 * static_cast<double>(attempt + 1), 1.);
703 const double v = std::fmod(0.5 + kAlpha2 * static_cast<double>(attempt + 1), 1.);
704 const double cosTheta = 1. - 2. * u;
705 const double sinTheta = std::sqrt(std::max(0., 1. - cosTheta * cosTheta));
706 const double phi = o2::cad::surface::kTwoPi * v;
707 const double dir[3] = {sinTheta * std::cos(phi), sinTheta * std::sin(phi), cosTheta};
708 const double centre[3] = {0.5 * (box.min[0] + box.max[0]), 0.5 * (box.min[1] + box.max[1]),
709 0.5 * (box.min[2] + box.max[2])};
710 double tlo = 0.;
711 double thi = TGeoShape::Big();
712 if (!slabWindow(box.min, box.max, centre, dir, tlo, thi)) {
713 continue;
714 }
715 const int capacity = maxPairsForCell(box.nActive);
716 pairs.resize(2 * static_cast<size_t>(capacity));
717 const int found = CellIntervals(box.cell, fActive.data() + box.firstActive, box.nActive, centre, dir, tlo, thi,
718 pairs.data(), capacity);
719 // the first crossing of the cell's surface inside the box; a window end is a box face, not surface
720 double crossing = -1.;
721 for (int pair = 0; pair < found && crossing < 0.; ++pair) {
722 if (pairs[2 * pair] > tlo) {
723 crossing = pairs[2 * pair];
724 } else if (pairs[2 * pair + 1] < thi) {
725 crossing = pairs[2 * pair + 1];
726 }
727 }
728 if (crossing < 0.) {
729 continue;
730 }
731 double* slot = &array[3 * static_cast<size_t>(produced)];
732 for (int axis = 0; axis < 3; ++axis) {
733 slot[axis] = centre[axis] + crossing * dir[axis];
734 }
735 // a face between two cells is not boundary of the union: keep only points where containment flips
736 double normal[3] = {0., 0., 0.};
737 ComputeNormal(slot, zAxis, normal);
738 double below[3];
739 double above[3];
740 for (int axis = 0; axis < 3; ++axis) {
741 below[axis] = slot[axis] - kFlipProbe * normal[axis];
742 above[axis] = slot[axis] + kFlipProbe * normal[axis];
743 }
744 if (Contains(below) != Contains(above)) {
745 ++produced;
746 }
747 }
748 return produced == npoints ? kTRUE : kFALSE;
749}
750
751Bool_t O2FlatCSG::Contains(const Double_t* point) const
752{
753 if (!fClosed || fBVH == nullptr) {
754 // no boxes to walk: answer from the twin rather than report no material
755 return Contains_Loop(point);
756 }
757 const bool inside = traversePoint(*static_cast<const BVH*>(fBVH), point, [&](int index) {
758 const FlatCSGBox& box = fBoxes[index];
759 if (!boxHoldsPoint(box, point)) {
760 return false;
761 }
762 if (box.nActive == 0) {
763 return true; // wholly inside its cell: nothing left to test
764 }
765 bool inCell = true;
766 for (int slot = 0; slot < box.nActive && inCell; ++slot) {
767 inCell = EvalHalfspace(fHalfspaces[fActive[box.firstActive + slot]], point) <= 0.;
768 }
769 return inCell;
770 });
771 return inside ? kTRUE : kFALSE;
772}
773
774int O2FlatCSG::HalfspaceRoots(const FlatCSGHalfspace& halfspace, const double* origin,
775 const double* dir, double* roots)
776{
777 if (halfspace.kind == FlatCSGHalfspace::kTorus) {
778 // the quartic derivation below takes the leading coefficient a4 = |dir|^4 to be exactly 1;
779 // a non-unit direction silently returns wrong roots instead of failing, so catch it here
780 assert(std::abs(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2] - 1.) < 1.e-9 &&
781 "O2FlatCSG::HalfspaceRoots: torus branch requires a unit direction");
782 const double* c = halfspace.c;
783 const double axis[3] = {c[3], c[4], c[5]};
784 const double major = c[6];
785 const double minor = c[7];
786 const double offset[3] = {origin[0] - c[0], origin[1] - c[1], origin[2] - c[2]};
787 // components along the axis, and the perpendicular parts
788 const double pz = offset[0] * axis[0] + offset[1] * axis[1] + offset[2] * axis[2];
789 const double dz = dir[0] * axis[0] + dir[1] * axis[1] + dir[2] * axis[2];
790 double pPerp[3];
791 double dPerp[3];
792 for (int index = 0; index < 3; ++index) {
793 pPerp[index] = offset[index] - pz * axis[index];
794 dPerp[index] = dir[index] - dz * axis[index];
795 }
796 const double pp = pPerp[0] * pPerp[0] + pPerp[1] * pPerp[1] + pPerp[2] * pPerp[2];
797 const double dd = dPerp[0] * dPerp[0] + dPerp[1] * dPerp[1] + dPerp[2] * dPerp[2];
798 const double pd = pPerp[0] * dPerp[0] + pPerp[1] * dPerp[1] + pPerp[2] * dPerp[2];
799 // (|X|^2 + R^2 - r^2)^2 - 4 R^2 (X_perp . X_perp) = 0 with X = P + tD, |D| = 1
800 const double e = pp + pz * pz + major * major - minor * minor;
801 const double f = pd + pz * dz;
802 const double a4 = 1.;
803 const double a3 = 4. * f;
804 const double a2 = 2. * e + 4. * f * f - 4. * major * major * dd;
805 const double a1 = 4. * e * f - 8. * major * major * pd;
806 const double a0 = e * e - 4. * major * major * pp;
807 // solveQuarticReal is scale-normalised, so the torus needs no degeneracy guard
808 const auto found = o2::cad::surface::solveQuarticReal(a4, a3, a2, a1, a0);
809 int count = 0;
810 for (double value : found) {
811 if (count < kMaxRootsPerHalfspace) {
812 roots[count++] = value;
813 }
814 }
815 return count;
816 }
817 const double* c = halfspace.c;
818 // A d
819 const double ad[3] = {c[0] * dir[0] + c[1] * dir[1] + c[2] * dir[2],
820 c[1] * dir[0] + c[3] * dir[1] + c[4] * dir[2],
821 c[2] * dir[0] + c[4] * dir[1] + c[5] * dir[2]};
822 // A o + b
823 const double aob[3] = {c[0] * origin[0] + c[1] * origin[1] + c[2] * origin[2] + c[6],
824 c[1] * origin[0] + c[3] * origin[1] + c[4] * origin[2] + c[7],
825 c[2] * origin[0] + c[4] * origin[1] + c[5] * origin[2] + c[8]};
826 const double alpha = dir[0] * ad[0] + dir[1] * ad[1] + dir[2] * ad[2];
827 const double beta = dir[0] * aob[0] + dir[1] * aob[1] + dir[2] * aob[2];
828 const double gamma = EvalHalfspace(halfspace, origin) * halfspace.sign; // sign*sign==1: the unsigned Q(o)
829
830 // a plane has alpha exactly 0 and an axis-parallel ray nearly so: both are linear equations
831 // 1e-14 is cm-dependent: a root it discards lies at |t| >= ~1e6 cm, outside any ALICE geometry
832 const double reference = std::abs(beta) + std::abs(gamma) + 1.e-300;
833 if (std::abs(alpha) <= 1.e-14 * reference) {
834 if (std::abs(beta) <= 1.e-300) {
835 return 0;
836 }
837 roots[0] = -0.5 * gamma / beta;
838 return 1;
839 }
840 const double disc = beta * beta - alpha * gamma;
841 if (disc < 0.) {
842 return 0;
843 }
844 const double root = std::sqrt(disc);
845 // the numerically stable pair, so a grazing ray does not lose the near root to cancellation
846 const double q = -(beta + (beta >= 0. ? root : -root));
847 if (q == 0.) {
848 // q == 0 only when beta == gamma == 0: one double root at t = 0, without the 0/0 of the general formula
849 roots[0] = 0.;
850 return 1;
851 }
852 roots[0] = q / alpha;
853 roots[1] = gamma / q;
854 return 2;
855}
856
857int O2FlatCSG::CellIntervals(int cell, const int* active, int nActive, const double* origin,
858 const double* dir, double tlo, double thi, double* out,
859 int maxOut) const
860{
861 const FlatCSGCell& description = fCells[cell];
862 const int count = nActive < 0 ? description.count : nActive;
863 if (thi <= tlo) {
864 return 0;
865 }
866
867 // every root of every active halfspace in the window; thread_local, sized from the cell's halfspace count
868 thread_local std::vector<double> breakBuffer;
869 const std::size_t needed = 2 + static_cast<std::size_t>(kMaxRootsPerHalfspace) * static_cast<std::size_t>(count);
870 if (breakBuffer.size() < needed) {
871 breakBuffer.resize(needed);
872 }
873 double* breaks = breakBuffer.data();
874 int nBreaks = 0;
875 breaks[nBreaks++] = tlo;
876 breaks[nBreaks++] = thi;
877 for (int slot = 0; slot < count; ++slot) {
878 const int index = active != nullptr ? active[slot] : description.first + slot;
879 double roots[kMaxRootsPerHalfspace];
880 const int found = HalfspaceRoots(fHalfspaces[index], origin, dir, roots);
881 for (int root = 0; root < found; ++root) {
882 if (roots[root] > tlo && roots[root] < thi) {
883 breaks[nBreaks++] = roots[root];
884 }
885 }
886 }
887 std::sort(breaks, breaks + nBreaks);
888
889 // classify the midpoint of each sub-interval and merge the runs that are inside
890 int pairs = 0;
891 bool open = false;
892 bool overflow = false;
893 for (int index = 0; index + 1 < nBreaks; ++index) {
894 const double lo = breaks[index];
895 const double hi = breaks[index + 1];
896 if (hi <= lo) {
897 continue;
898 }
899 const double middle = 0.5 * (lo + hi);
900 double probe[3] = {origin[0] + middle * dir[0], origin[1] + middle * dir[1],
901 origin[2] + middle * dir[2]};
902 bool inside = true;
903 for (int slot = 0; slot < count && inside; ++slot) {
904 const int halfspace = active != nullptr ? active[slot] : description.first + slot;
905 inside = EvalHalfspace(fHalfspaces[halfspace], probe) <= 0.;
906 }
907 if (inside) {
908 if (open) {
909 out[2 * (pairs - 1) + 1] = hi;
910 } else if (pairs < maxOut) {
911 out[2 * pairs] = lo;
912 out[2 * pairs + 1] = hi;
913 ++pairs;
914 open = true;
915 } else {
916 // maxOut was too small for this cell along this ray: fail loudly (a negative count)
917 // rather than hand the caller a silently truncated list that reads as a valid answer
918 overflow = true;
919 open = false;
920 }
921 } else {
922 open = false;
923 }
924 }
925 return overflow ? -1 : pairs;
926}
927
928namespace
929{
931int mergeIntervals(double* pairs, int count, double glue)
932{
933 if (count < 2) {
934 return count;
935 }
936 // sort by entry
937 for (int outer = 1; outer < count; ++outer) {
938 const double lo = pairs[2 * outer];
939 const double hi = pairs[2 * outer + 1];
940 int inner = outer - 1;
941 while (inner >= 0 && pairs[2 * inner] > lo) {
942 pairs[2 * (inner + 1)] = pairs[2 * inner];
943 pairs[2 * (inner + 1) + 1] = pairs[2 * inner + 1];
944 --inner;
945 }
946 pairs[2 * (inner + 1)] = lo;
947 pairs[2 * (inner + 1) + 1] = hi;
948 }
949 int kept = 1;
950 for (int index = 1; index < count; ++index) {
951 if (pairs[2 * index] <= pairs[2 * (kept - 1) + 1] + glue) {
952 pairs[2 * (kept - 1) + 1] = std::max(pairs[2 * (kept - 1) + 1], pairs[2 * index + 1]);
953 } else {
954 pairs[2 * kept] = pairs[2 * index];
955 pairs[2 * kept + 1] = pairs[2 * index + 1];
956 ++kept;
957 }
958 }
959 return kept;
960}
961} // namespace
962
963Double_t O2FlatCSG::DistFromOutside_Loop(const Double_t* point, const Double_t* dir,
964 Double_t step) const
965{
966 // thread_local: see the comment on the scratch-buffer members it replaced in the header
967 thread_local std::vector<double> pairBuffer;
968 double best = TGeoShape::Big();
969 for (int cell = 0; cell < GetNcells(); ++cell) {
970 // sized from this cell's own halfspace count, so a busy cell's intervals are never truncated
971 const int capacity = maxPairsForCell(fCells[cell].count);
972 if (static_cast<int>(pairBuffer.size()) < 2 * capacity) {
973 pairBuffer.resize(2 * capacity);
974 }
975 const int found = CellIntervals(cell, nullptr, -1, point, dir, 0., step,
976 pairBuffer.data(), capacity);
977 // capacity is provably sufficient (maxPairsForCell), so CellIntervals cannot overflow here;
978 // a negative found would mean that bound itself is wrong, which is a bug, not live data
979 for (int pair = 0; pair < found; ++pair) {
980 // a point exactly on the boundary is already inside; only a real entry counts
981 if (pairBuffer[2 * pair + 1] > TGeoShape::Tolerance() && pairBuffer[2 * pair] < best) {
982 best = std::max(pairBuffer[2 * pair], 0.);
983 }
984 }
985 }
986 return best;
987}
988
989Double_t O2FlatCSG::DistFromInside_Loop(const Double_t* point, const Double_t* dir,
990 Double_t step) const
991{
992 // the union's occupancy; the buffer fits every cell's worst case at once
993 thread_local std::vector<double> pairBuffer;
994 int totalCapacity = 0;
995 for (int cell = 0; cell < GetNcells(); ++cell) {
996 totalCapacity += maxPairsForCell(fCells[cell].count);
997 }
998 if (static_cast<int>(pairBuffer.size()) < 2 * totalCapacity) {
999 pairBuffer.resize(2 * totalCapacity);
1000 }
1001 int count = 0;
1002 for (int cell = 0; cell < GetNcells(); ++cell) {
1003 // not expected to overflow, but a negative count must never reach the pointer arithmetic
1004 const int found = CellIntervals(cell, nullptr, -1, point, dir, 0., step,
1005 pairBuffer.data() + 2 * count, totalCapacity - count);
1006 if (found < 0) {
1007 Error("DistFromInside_Loop",
1008 "CellIntervals overflowed for cell %d: the maxPairsForCell bound no longer holds",
1009 cell);
1010 return TGeoShape::Big();
1011 }
1012 count += found;
1013 }
1014 count = mergeIntervals(pairBuffer.data(), count, TGeoShape::Tolerance());
1015 for (int pair = 0; pair < count; ++pair) {
1016 if (pairBuffer[2 * pair] <= TGeoShape::Tolerance()) {
1017 return pairBuffer[2 * pair + 1];
1018 }
1019 }
1020 return 0.;
1021}
1022
1025
1026bool O2FlatCSG::GatherRayPieces(const Double_t* point, const Double_t* dir, Double_t step,
1027 std::vector<double>& pairs, std::vector<int>& cells, RayBound bound,
1028 double& smallestPruned) const
1029{
1030 pairs.clear();
1031 cells.clear();
1032 smallestPruned = TGeoShape::Big();
1033 const BVH& bvh = *static_cast<const BVH*>(fBVH);
1034
1035 // one box's intervals; thread_local for the reason the header's scratch-buffer comment gives
1036 thread_local std::vector<double> boxPairs;
1037 bool overflowed = false;
1038 // the running bound: with kEntry an upper bound on DistFromOutside's answer, with kExit the far
1039 // end of the interval holding t = 0; a box entered past it cannot change the answer
1040 double limit = step;
1041 double reach = -1.; // kExit's chain end, negative until a piece holds t = 0
1042 // only kExit can prune a box that later turns out to matter, so only it needs the record
1043 double* culled = bound == RayBound::kExit ? &smallestPruned : nullptr;
1044 traverseRay(bvh, point, dir, step, limit, bound != RayBound::kNone, culled, [&](int index) {
1045 const FlatCSGBox& box = fBoxes[index];
1046 double tlo = 0.;
1047 double thi = step;
1048 if (!slabWindow(box.min, box.max, point, dir, tlo, thi) || thi <= tlo) {
1049 return;
1050 }
1051 if (tlo > limit) {
1052 if (culled != nullptr && tlo < smallestPruned) {
1053 smallestPruned = tlo;
1054 }
1055 return;
1056 }
1057 // sized from THIS box's active-list length, which is the count CellIntervals will walk, so
1058 // the bound it is asked to respect is the one it was given
1059 const int capacity = maxPairsForCell(box.nActive);
1060 if (static_cast<int>(boxPairs.size()) < 2 * capacity) {
1061 boxPairs.resize(2 * capacity);
1062 }
1063 // nActive == 0 means the box is wholly inside its cell; CellIntervals then has no halfspace
1064 // to break on and returns the whole window, which is exactly the right answer
1065 const int* active = box.nActive > 0 ? fActive.data() + box.firstActive : nullptr;
1066 const int found = CellIntervals(box.cell, active, box.nActive, point, dir, tlo, thi,
1067 boxPairs.data(), capacity);
1068 if (found < 0) {
1069 overflowed = true;
1070 return;
1071 }
1072 for (int pair = 0; pair < found; ++pair) {
1073 const double enter = boxPairs[2 * pair];
1074 const double exit = boxPairs[2 * pair + 1];
1075 pairs.push_back(enter);
1076 pairs.push_back(exit);
1077 cells.push_back(box.cell);
1078 if (bound == RayBound::kEntry && exit > TGeoShape::Tolerance()) {
1079 limit = std::min(limit, std::max({enter, 0., TGeoShape::Tolerance()}));
1080 } else if (bound == RayBound::kExit &&
1081 (reach < 0. ? enter <= TGeoShape::Tolerance() : enter <= reach + TGeoShape::Tolerance())) {
1082 // the chain of pieces holding t = 0, joined with DistFromInside's own merge glue
1083 reach = std::max(reach, exit);
1084 limit = std::min(step, reach + TGeoShape::Tolerance());
1085 }
1086 }
1087 });
1088 return !overflowed;
1089}
1090
1093
1094Double_t O2FlatCSG::DistFromOutsideBVH(const Double_t* point, const Double_t* dir,
1095 Double_t step) const
1096{
1097 thread_local std::vector<double> pairs;
1098 thread_local std::vector<int> cells;
1099 double smallestPruned = TGeoShape::Big();
1100 if (!GatherRayPieces(point, dir, step, pairs, cells, RayBound::kEntry, smallestPruned)) {
1101 Error("DistFromOutside",
1102 "Shape %s: CellIntervals overflowed a per-box buffer sized from that box's own active "
1103 "list; the maxPairsForCell bound no longer holds. Answering from the loop twin.",
1104 GetName());
1105 return DistFromOutside_Loop(point, dir, step);
1106 }
1107
1108 // sort the pieces by (cell, entry) through a permutation, so the run merge below sees each
1109 // cell's pieces contiguously and in order
1110 const int count = static_cast<int>(cells.size());
1111 thread_local std::vector<int> order;
1112 order.resize(count);
1113 std::iota(order.begin(), order.end(), 0);
1114 std::sort(order.begin(), order.end(), [&](int left, int right) {
1115 if (cells[left] != cells[right]) {
1116 return cells[left] < cells[right];
1117 }
1118 return pairs[2 * left] < pairs[2 * right];
1119 });
1120
1121 double best = TGeoShape::Big();
1122 int index = 0;
1123 while (index < count) {
1124 const int cell = cells[order[index]];
1125 const double enter = pairs[2 * order[index]];
1126 double exit = pairs[2 * order[index] + 1];
1127 ++index;
1128 // join what is only one interval of this cell, cut into pieces by the boxes that tile it
1129 while (index < count && cells[order[index]] == cell && pairs[2 * order[index]] <= exit) {
1130 exit = std::max(exit, pairs[2 * order[index] + 1]);
1131 ++index;
1132 }
1133 // DistFromOutside_Loop's rule, unchanged: a point exactly on the boundary is already inside,
1134 // so only an interval that really extends past the tolerance counts as an entry
1135 if (exit > TGeoShape::Tolerance() && enter < best) {
1136 best = std::max(enter, 0.);
1137 }
1138 }
1139 return best;
1140}
1141
1144
1145Double_t O2FlatCSG::DistFromInsideBVH(const Double_t* point, const Double_t* dir,
1146 Double_t step) const
1147{
1148 thread_local std::vector<double> pairs;
1149 thread_local std::vector<int> cells;
1150 for (int attempt = 0; attempt < 2; ++attempt) {
1151 // the bound grows as pieces merge, so a box skipped against an earlier, smaller one might have
1152 // mattered after all; the second attempt does not prune and is the definition of the answer
1153 const RayBound bound = attempt == 0 ? RayBound::kExit : RayBound::kNone;
1154 double smallestPruned = TGeoShape::Big();
1155 if (!GatherRayPieces(point, dir, step, pairs, cells, bound, smallestPruned)) {
1156 Error("DistFromInside",
1157 "Shape %s: CellIntervals overflowed a per-box buffer sized from that box's own active "
1158 "list; the maxPairsForCell bound no longer holds. Answering from the loop twin.",
1159 GetName());
1160 return DistFromInside_Loop(point, dir, step);
1161 }
1162 const int count = mergeIntervals(pairs.data(), static_cast<int>(cells.size()),
1163 TGeoShape::Tolerance());
1164 double answer = 0.;
1165 for (int pair = 0; pair < count; ++pair) {
1166 if (pairs[2 * pair] <= TGeoShape::Tolerance()) {
1167 answer = pairs[2 * pair + 1];
1168 break;
1169 }
1170 }
1171 if (attempt == 1 || smallestPruned > answer + TGeoShape::Tolerance()) {
1172 return answer;
1173 }
1174 ++gUnprunedRetryCount;
1175 }
1176 return 0.; // unreachable: the second attempt never prunes
1177}
1178
1179void O2FlatCSG::ResetUnprunedRetryCounter()
1180{
1181 gUnprunedRetryCount = 0;
1182}
1183
1184long long O2FlatCSG::GetUnprunedRetryCount()
1185{
1186 return gUnprunedRetryCount;
1187}
1188
1189Double_t O2FlatCSG::DistFromOutside(const Double_t* point, const Double_t* dir, Int_t iact,
1190 Double_t step, Double_t* safe) const
1191{
1192 if (iact < 3 && safe != nullptr) {
1193 *safe = Safety(point, kFALSE);
1194 if (iact == 0) {
1195 return TGeoShape::Big();
1196 }
1197 if (iact == 1 && step < *safe) {
1198 return TGeoShape::Big();
1199 }
1200 }
1201 if (!fClosed || fBVH == nullptr) {
1202 // no boxes to walk: see the note on Contains. The twin is the definition of the answer, and
1203 // an empty box array in the accelerated path would silently report empty space.
1204 return DistFromOutside_Loop(point, dir, step);
1205 }
1206 return DistFromOutsideBVH(point, dir, step);
1207}
1208
1209Double_t O2FlatCSG::DistFromInside(const Double_t* point, const Double_t* dir, Int_t iact,
1210 Double_t step, Double_t* safe) const
1211{
1212 if (iact < 3 && safe != nullptr) {
1213 *safe = Safety(point, kTRUE);
1214 if (iact == 0) {
1215 return TGeoShape::Big();
1216 }
1217 if (iact == 1 && step < *safe) {
1218 return TGeoShape::Big();
1219 }
1220 }
1221 if (!fClosed || fBVH == nullptr) {
1222 return DistFromInside_Loop(point, dir, step);
1223 }
1224 return DistFromInsideBVH(point, dir, step);
1225}
1226
1229
1230Double_t O2FlatCSG::Safety_Loop(const Double_t* point, Bool_t in) const
1231{
1232 if (!in) {
1233 double best = TGeoShape::Big();
1234 for (const auto& box : fBoxes) {
1235 best = std::min(best, boxDistanceSquared(box, point));
1236 }
1237 return best >= TGeoShape::Big() ? 0. : std::sqrt(best);
1238 }
1239
1240 double best = 0.;
1241 for (const auto& box : fBoxes) {
1242 if (boxHoldsPoint(box, point) && box.nActive == 0) {
1243 best = std::max(best, distanceToFaces(box, point));
1244 }
1245 }
1246 return std::max(best, 0.);
1247}
1248
1251
1252Double_t O2FlatCSG::Safety(const Double_t* point, Bool_t in) const
1253{
1254 if (!fClosed || fBVH == nullptr) {
1255 // no boxes to walk: see the note on Contains -- the twin is the definition of the answer.
1256 return Safety_Loop(point, in);
1257 }
1258 const BVH& bvh = *static_cast<const BVH*>(fBVH);
1259
1260 if (!in) {
1261 // node boxes are read back as double and measured against the double point: a float query could prune the nearest box
1262 using DVec3 = bvh::v2::Vec<double, 3>;
1263 using DBBox = bvh::v2::BBox<double, 3>;
1264 const DVec3 dpoint(point[0], point[1], point[2]);
1265 const auto nodeDistanceSquared = [&bvh, &dpoint](size_t index) {
1266 const auto& fbox = bvh.nodes[index].get_bbox();
1267 const DBBox dbox(DVec3(static_cast<double>(fbox.min[0]), static_cast<double>(fbox.min[1]),
1268 static_cast<double>(fbox.min[2])),
1269 DVec3(static_cast<double>(fbox.max[0]), static_cast<double>(fbox.max[1]),
1270 static_cast<double>(fbox.max[2])));
1271 return bvh::v2::extra::SafetySqToNode(dbox, dpoint);
1272 };
1273 struct NodeEntry {
1274 size_t node;
1275 double squared;
1276 };
1277 thread_local std::vector<NodeEntry> nearStack;
1278 nearStack.clear();
1279 nearStack.push_back({0, nodeDistanceSquared(0)}); // the bvh2 root node
1280 double best = TGeoShape::Big();
1281 while (!nearStack.empty()) {
1282 const NodeEntry entry = nearStack.back();
1283 nearStack.pop_back();
1284 const auto& node = bvh.nodes[entry.node];
1285 if (entry.squared >= best) {
1286 continue; // this subtree cannot hold anything nearer than what is already found
1287 }
1288 if (node.is_leaf()) {
1289 const auto beginPrimitive = node.index.first_id();
1290 const auto endPrimitive = beginPrimitive + node.index.prim_count();
1291 for (auto primitive = beginPrimitive; primitive < endPrimitive; ++primitive) {
1292 best = std::min(best, boxDistanceSquared(fBoxes[bvh.prim_ids[primitive]], point));
1293 }
1294 } else {
1295 // nearer child first, pruning on the way in; the same min in another order
1296 const auto firstChild = node.index.first_id();
1297 size_t children[2] = {firstChild, firstChild + 1};
1298 double childSquared[2] = {TGeoShape::Big(), TGeoShape::Big()};
1299 for (int index = 0; index < 2; ++index) {
1300 if (children[index] < bvh.nodes.size()) {
1301 childSquared[index] = nodeDistanceSquared(children[index]);
1302 }
1303 }
1304 const int nearer = childSquared[0] <= childSquared[1] ? 0 : 1;
1305 const int farther = 1 - nearer;
1306 // LIFO, so the farther child is pushed first and popped last.
1307 if (children[farther] < bvh.nodes.size() && childSquared[farther] < best) {
1308 nearStack.push_back({children[farther], childSquared[farther]});
1309 }
1310 if (children[nearer] < bvh.nodes.size() && childSquared[nearer] < best) {
1311 nearStack.push_back({children[nearer], childSquared[nearer]});
1312 }
1313 }
1314 }
1315 return best >= TGeoShape::Big() ? 0. : std::sqrt(best);
1316 }
1317
1318 double best = 0.;
1319 traversePoint(bvh, point, [&](int index) {
1320 const FlatCSGBox& box = fBoxes[index];
1321 if (boxHoldsPoint(box, point) && box.nActive == 0) {
1322 best = std::max(best, distanceToFaces(box, point));
1323 }
1324 return false;
1325 });
1326 return std::max(best, 0.);
1327}
1328
1329Double_t O2FlatCSG::Capacity() const
1330{
1331 // the cells of a decomposition are disjoint by construction, so their own volumes just sum
1332 return std::accumulate(fCells.begin(), fCells.end(), 0.,
1333 [](double sum, const FlatCSGCell& cell) { return sum + cell.volume; });
1334}
1335
1339
1340void O2FlatCSG::ComputeNormal(const Double_t* point, const Double_t* dir, Double_t* norm) const
1341{
1342 norm[0] = norm[1] = norm[2] = 0.;
1343 if (fHalfspaces.empty()) {
1344 return;
1345 }
1346
1347 // the candidates: the active list of the box that holds the point; for a box wholly inside its
1348 // cell, that cell's halfspace run; and when no box holds the point, every halfspace
1349 const int* activeList = nullptr;
1350 int rangeFirst = 0;
1351 int nCandidates = GetNhalfspaces();
1352 if (fClosed && fBVH != nullptr) {
1353 traversePoint(*static_cast<const BVH*>(fBVH), point, [&](int index) {
1354 const FlatCSGBox& box = fBoxes[index];
1355 if (!boxHoldsPoint(box, point)) {
1356 return false;
1357 }
1358 if (box.nActive > 0) {
1359 activeList = fActive.data() + box.firstActive;
1360 nCandidates = box.nActive;
1361 } else {
1362 rangeFirst = fCells[box.cell].first;
1363 nCandidates = fCells[box.cell].count;
1364 }
1365 return true; // cells are disjoint; the first box that holds the point is the answer
1366 });
1367 }
1368 const auto indexAt = [&](int slot) { return activeList != nullptr ? activeList[slot] : rangeFirst + slot; };
1369
1370 int best = -1;
1371 double bestValue = std::numeric_limits<double>::infinity();
1372 double bestGrad[3] = {0., 0., 0.};
1373 for (int slot = 0; slot < nCandidates; ++slot) {
1374 const int candidate = indexAt(slot);
1375 const FlatCSGHalfspace& halfspace = fHalfspaces[candidate];
1376 const double f = EvalHalfspace(halfspace, point);
1377 double grad[3];
1378 halfspaceGradient(halfspace, point, grad);
1379 const double gradLength = std::sqrt(grad[0] * grad[0] + grad[1] * grad[1] + grad[2] * grad[2]);
1380 if (gradLength < 1.e-300) {
1381 continue; // degenerate gradient (see halfspaceGradient); this halfspace cannot win
1382 }
1383 const double value = std::abs(f) / gradLength; // the first-order distance to this surface
1384 if (value < bestValue) {
1385 bestValue = value;
1386 best = candidate;
1387 bestGrad[0] = grad[0] / gradLength;
1388 bestGrad[1] = grad[1] / gradLength;
1389 bestGrad[2] = grad[2] / gradLength;
1390 }
1391 }
1392
1393 if (best < 0) {
1394 // every candidate's gradient was degenerate (a torus axis or core circle): fall back to the travel direction
1395 const double dirLength = std::sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
1396 if (dirLength > 1.e-300) {
1397 for (int index = 0; index < 3; ++index) {
1398 norm[index] = dir[index] / dirLength;
1399 }
1400 }
1401 return;
1402 }
1403
1404 for (int index = 0; index < 3; ++index) {
1405 norm[index] = bestGrad[index];
1406 }
1407 const double dot = norm[0] * dir[0] + norm[1] * dir[1] + norm[2] * dir[2];
1408 if (dot < 0.) {
1409 for (int index = 0; index < 3; ++index) {
1410 norm[index] = -norm[index];
1411 }
1412 }
1413}
1414
1415void O2FlatCSG::ComputeBBox()
1416{
1417 // the union of the retained sub-cell boxes, tighter than the union of the cell AABBs
1418 if (fBoxes.empty()) {
1419 return;
1420 }
1421 double lo[3] = {TGeoShape::Big(), TGeoShape::Big(), TGeoShape::Big()};
1422 double hi[3] = {-TGeoShape::Big(), -TGeoShape::Big(), -TGeoShape::Big()};
1423 for (const FlatCSGBox& box : fBoxes) {
1424 for (int index = 0; index < 3; ++index) {
1425 lo[index] = std::min(lo[index], box.min[index]);
1426 hi[index] = std::max(hi[index], box.max[index]);
1427 }
1428 }
1429 for (int index = 0; index < 3; ++index) {
1430 fOrigin[index] = 0.5 * (lo[index] + hi[index]);
1431 }
1432 fDX = 0.5 * (hi[0] - lo[0]);
1433 fDY = 0.5 * (hi[1] - lo[1]);
1434 fDZ = 0.5 * (hi[2] - lo[2]);
1435}
1436
1437} // namespace cad
1438} // namespace o2
header::DataOrigin origin
header::DataDescription description
Private analytic bounded surfaces, trim wires and closure checks behind O2BVHSurfaceSolid.
size_t minSize
std::unique_ptr< expressions::Node > node
std::vector< double > sum
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 h configStandalone Camera window renderer OpenGL display display_camera Orient the camera such that the y axis is always upwards Full Screen MultiSample Anti Downsampling Anti Enable Vertical Sync Target frame or if the backend cannot limit to the refresh Use OpenGL indirect draws to reduce number of draw calls Display h configStandalone Vulkan display display_vulkan Have uniform buffers in host accessible device memory h configStandalone Internal display display_internal Number of updates the Qt GUI may send ahead of the last one the display has before it waits for the display Opacity of the console background Number of console lines kept
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error s
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char e
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char pp
configStandalone Reconstruction rec_tpc float
bool overflow
ClassImp(o2::cad::O2FlatCSG)
uint32_t side
Definition RawData.h:0
uint32_t minor
Definition RawData.h:6
uint32_t c
Definition RawData.h:2
uint32_t stack
Definition RawData.h:1
uint32_t major
Definition RawData.h:7
void SplitBox(int cell, const double *lo, const double *hi, const std::vector< int > &active, int depth, double minSize, int cubifyBudget)
std::vector< int > fActive
Definition O2FlatCSG.h:194
void * fBVH
The BVH over fBoxes, rebuilt by CloseShape; not streamed.
Definition O2FlatCSG.h:208
int AddCell(int first, int count, double volume)
Append a cell over [first, first + count) of the halfspace array; returns its index.
Double_t DistFromOutside_Loop(const Double_t *point, const Double_t *dir, Double_t step=TGeoShape::Big()) const
std::vector< double > fCellLo
each cell's AABB low corner, 3 doubles per cell
Definition O2FlatCSG.h:195
int AddQuadric(double sign, const double coeff[10])
Append a quadric halfspace; returns its index. sign is +1 or -1, inside is sign*Q <= 0.
int CellIntervals(int cell, const int *active, int nActive, const double *origin, const double *dir, double tlo, double thi, double *out, int maxOut) const
static int HalfspaceRoots(const FlatCSGHalfspace &halfspace, const double *origin, const double *dir, double *roots)
Real roots of sign * f(origin + t*dir) = 0, unsorted, at most four; returns the count.
bool fClosed
Set by a successful CloseShape; not streamed. The #pragma read rule closes every shape ROOT reads bac...
Definition O2FlatCSG.h:201
Bool_t GetPointsOnSegments(Int_t npoints, Double_t *array) const override
Points on the solid's own boundary, for the overlap checkers; kFALSE if fewer than npoints were found...
bool GatherRayPieces(const Double_t *point, const Double_t *dir, Double_t step, std::vector< double > &pairs, std::vector< int > &cells, RayBound bound, double &smallestPruned) const
GatherRayPieces – each box's window is its own slab intersected with [0, step], never pooled across b...
void EnsureCellBBoxStorage()
Grow the per-cell bounding-box storage to the cell count.
std::vector< bool > fCellBBoxSet
Definition O2FlatCSG.h:199
static double EvalHalfspace(const FlatCSGHalfspace &halfspace, const double *point)
sign * f(point); the halfspace contains the point when this is <= 0.
void CloseShape()
Build the sub-cell boxes and their BVH. Call once, after the last AddCell.
Bool_t Contains_Loop(const Double_t *point) const
static void HalfspaceRange(const FlatCSGHalfspace &halfspace, const double *lo, const double *hi, double &rangeLo, double &rangeHi)
void ComputeBBox() override
The union of the retained sub-cell boxes, tighter than the union of the cell AABBs.
std::vector< double > fCellHi
Definition O2FlatCSG.h:196
Double_t DistFromInside_Loop(const Double_t *point, const Double_t *dir, Double_t step=TGeoShape::Big()) const
int GetNcells() const
Definition O2FlatCSG.h:76
int AddTorus(double sign, const double *centre, const double *axis, double major, double minor)
Append a torus halfspace, inside sign * (sqrt((rho - major)^2 + z^2) - minor) <= 0 about unit axis; r...
void ComputeNormal(const Double_t *point, const Double_t *dir, Double_t *norm) const override
The normal of the halfspace nearest to equality at point, oriented along dir.
~O2FlatCSG() override
Bool_t Contains(const Double_t *point) const override
void GetCellBBox(int cell, double *lo, double *hi) const
std::vector< FlatCSGCell > fCells
the DNF's cells, indexing into it
Definition O2FlatCSG.h:188
size_t GetBVHMemory() const
Bytes held by the BVH nodes and the primitive-index permutation.
bool CellContains(int index, const double *point) const
True when every halfspace of cell index contains point.
Double_t DistFromOutsideBVH(const Double_t *point, const Double_t *dir, Double_t step) const
The accelerated DistFromOutside/DistFromInside bodies; each clips the ray to a box before using its a...
std::vector< FlatCSGBox > fBoxes
The sub-cell boxes, rebuilt by CloseShape; not streamed.
Definition O2FlatCSG.h:191
std::vector< FlatCSGHalfspace > fHalfspaces
the flat halfspace array
Definition O2FlatCSG.h:187
void SetCellBBox(int cell, const double *lo, const double *hi)
GLfloat GLfloat GLfloat alpha
Definition glcorearb.h:279
GLint GLenum GLint x
Definition glcorearb.h:403
GLint GLsizei count
Definition glcorearb.h:399
const GLdouble * v
Definition glcorearb.h:832
GLuint index
Definition glcorearb.h:781
GLuint const GLchar * name
Definition glcorearb.h:781
GLdouble GLdouble right
Definition glcorearb.h:4077
GLint first
Definition glcorearb.h:399
GLdouble f
Definition glcorearb.h:310
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint y
Definition glcorearb.h:270
GLint reference
Definition glcorearb.h:5487
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLint left
Definition glcorearb.h:1979
GLintptr offset
Definition glcorearb.h:660
GLint GLint GLsizei GLsizei GLsizei depth
Definition glcorearb.h:470
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLsizei const GLint * box
Definition glcorearb.h:4697
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
QuarticRoots solveQuarticReal(double a4, double a3, double a2, double a1, double a0, QuarticBranch *takenBranch=nullptr)
constexpr double kTwoPi
const bool const bool const int FollowDirection BestTrial TrackITSInternal< NLayers > & best
value_T rho
Definition TrackUtils.h:36
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
One DNF cell: [first, first + count) of the halfspace array, intersected; volume is its own volume.
Definition O2FlatCSG.h:37
std::vector< Cell > cells
std::vector< int > row