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
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)
float sum(float s, o2::dcs::DataPointValue v)
Definition dcs-ccdb.cxx:39
GLfloat GLfloat GLfloat alpha
Definition glcorearb.h:279
GLint GLenum GLint x
Definition glcorearb.h:403
GLint GLsizei count
Definition glcorearb.h:399
GLuint entry
Definition glcorearb.h:5735
const GLdouble * v
Definition glcorearb.h:832
GLenum array
Definition glcorearb.h:4274
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
GLsizei const GLfloat * value
Definition glcorearb.h:819
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