Project
Loading...
Searching...
No Matches
O2OverlapCheck.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
17
20
21#include "TGeoShape.h"
22#include "TGeoBBox.h"
23#include "TGeoMatrix.h"
24#include "TGeoVolume.h"
25#include "TGeoNode.h"
26
27#include <algorithm>
28#include <chrono>
29#include <cmath>
30#include <cstring>
31#include <limits>
32
33namespace o2
34{
35namespace cad
36{
37
39{
40 switch (verdict) {
42 return "disjoint";
44 return "touching";
46 return "INTERPENETRATING";
48 return "CONTAINED";
49 }
50 return "unknown";
51}
52
53namespace
54{
55
58struct MasterBox {
59 double lower[3] = {0., 0., 0.};
60 double upper[3] = {0., 0., 0.};
61 bool valid = false;
62};
63
64MasterBox masterBox(const TGeoShape* shape, const TGeoMatrix* matrix, double pad)
65{
66 MasterBox box;
67 const auto* boundingBox = dynamic_cast<const TGeoBBox*>(shape);
68 if (boundingBox == nullptr) {
69 return box;
70 }
71 const double* origin = boundingBox->GetOrigin();
72 const double halfLengths[3] = {boundingBox->GetDX(), boundingBox->GetDY(), boundingBox->GetDZ()};
73 for (int dimension = 0; dimension < 3; ++dimension) {
74 box.lower[dimension] = std::numeric_limits<double>::max();
75 box.upper[dimension] = -std::numeric_limits<double>::max();
76 }
77 for (int corner = 0; corner < 8; ++corner) {
78 const double local[3] = {origin[0] + ((corner & 1) ? halfLengths[0] : -halfLengths[0]),
79 origin[1] + ((corner & 2) ? halfLengths[1] : -halfLengths[1]),
80 origin[2] + ((corner & 4) ? halfLengths[2] : -halfLengths[2])};
81 double master[3] = {0., 0., 0.};
82 matrix->LocalToMaster(local, master);
83 for (int dimension = 0; dimension < 3; ++dimension) {
84 box.lower[dimension] = std::min(box.lower[dimension], master[dimension]);
85 box.upper[dimension] = std::max(box.upper[dimension], master[dimension]);
86 }
87 }
88 for (int dimension = 0; dimension < 3; ++dimension) {
89 box.lower[dimension] -= pad;
90 box.upper[dimension] += pad;
91 }
92 box.valid = true;
93 return box;
94}
95
96bool boxesOverlap(const MasterBox& first, const MasterBox& second)
97{
98 if (!first.valid || !second.valid) {
99 return true; // no box means no rejection; test the pair
100 }
101 for (int dimension = 0; dimension < 3; ++dimension) {
102 if (first.upper[dimension] < second.lower[dimension] || second.upper[dimension] < first.lower[dimension]) {
103 return false;
104 }
105 }
106 return true;
107}
108
110inline double halton(unsigned int index, unsigned int base)
111{
112 double result = 0.;
113 double fraction = 1.;
114 while (index > 0) {
115 fraction /= base;
116 result += fraction * (index % base);
117 index /= base;
118 }
119 return result;
120}
121
124bool containmentFlips(const TGeoShape* shape, const double* point, double eps)
125{
126 const auto flipsAlong = [&](const double* direction) {
127 double below[3];
128 double above[3];
129 for (int axis = 0; axis < 3; ++axis) {
130 below[axis] = point[axis] - eps * direction[axis];
131 above[axis] = point[axis] + eps * direction[axis];
132 }
133 return shape->Contains(below) != shape->Contains(above);
134 };
135 const double zAxis[3] = {0., 0., 1.};
136 double normal[3] = {0., 0., 0.};
137 shape->ComputeNormal(point, zAxis, normal);
138 const double length = std::sqrt(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]);
139 if (std::isfinite(length) && length > 0.5 && flipsAlong(normal)) {
140 return true;
141 }
142 for (int axis = 0; axis < 3; ++axis) {
143 double direction[3] = {0., 0., 0.};
144 direction[axis] = 1.;
145 if (flipsAlong(direction)) {
146 return true;
147 }
148 }
149 return false;
150}
151
152} // namespace
153
154int SampleBoundaryPoints(const TGeoShape* shape, int npoints, double residualTolerance,
155 std::vector<double>& points, int& rejected, double& worstResidual,
156 bool* usedPointsOnSegments)
157{
158 points.clear();
159 rejected = 0;
160 worstResidual = 0.;
161 if (usedPointsOnSegments != nullptr) {
162 *usedPointsOnSegments = false;
163 }
164 if (shape == nullptr || npoints <= 0) {
165 return 0;
166 }
167
168 int meshVertices = 0;
169 int meshSegments = 0;
170 int meshPolygons = 0;
171 shape->GetMeshNumbers(meshVertices, meshSegments, meshPolygons);
172
173 // TGeoChecker::MakeCheckOverlap's choice: a shape that declines to sample still has display vertices
174 const int capacity = std::max(npoints, meshVertices);
175 std::vector<double> raw(3 * static_cast<size_t>(std::max(capacity, 1)), 0.);
176 int rawCount = 0;
177 if (shape->GetPointsOnSegments(npoints, raw.data())) {
178 rawCount = npoints;
179 if (usedPointsOnSegments != nullptr) {
180 *usedPointsOnSegments = true;
181 }
182 } else {
183 if (meshVertices <= 0) {
184 return 0;
185 }
186 shape->SetPoints(raw.data());
187 rawCount = meshVertices;
188 }
189
190 // O2FlatCSG returns Safety 0 inside undecided boxes, so only its points must also flip containment
191 const bool flatCSG = dynamic_cast<const O2FlatCSG*>(shape) != nullptr;
192 points.reserve(3 * static_cast<size_t>(rawCount));
193 for (int index = 0; index < rawCount; ++index) {
194 const double* candidate = &raw[3 * static_cast<size_t>(index)];
195 // Safety() is a lower bound on the distance to the boundary, so a large value is a proof that
196 // the point is *not* on it. That is the direction this filter needs.
197 const double residual = shape->Safety(candidate, shape->Contains(candidate));
198 if (!(residual <= residualTolerance) || (flatCSG && !containmentFlips(shape, candidate, residualTolerance))) {
199 rejected++;
200 continue;
201 }
202 worstResidual = std::max(worstResidual, residual);
203 points.push_back(candidate[0]);
204 points.push_back(candidate[1]);
205 points.push_back(candidate[2]);
206 }
207 return static_cast<int>(points.size() / 3);
208}
209
210namespace
211{
212
215struct DirectionResult {
216 int contained = 0;
217 int deep = 0;
218 double maxDepth = 0.;
219 double deepestMaster[3] = {0., 0., 0.};
220 double minSeparation = std::numeric_limits<double>::max();
221};
222
223DirectionResult probeDirection(const std::vector<double>& points, const TGeoMatrix* matFrom,
224 const TGeoShape* target, const TGeoMatrix* matTo, double depthTolerance)
225{
226 DirectionResult result;
227 const size_t count = points.size() / 3;
228 for (size_t index = 0; index < count; ++index) {
229 double master[3] = {0., 0., 0.};
230 double local[3] = {0., 0., 0.};
231 matFrom->LocalToMaster(&points[3 * index], master);
232 matTo->MasterToLocal(master, local);
233 if (target->Contains(local)) {
234 result.contained++;
235 const double depth = target->Safety(local, kTRUE);
236 if (depth > depthTolerance) {
237 result.deep++;
238 }
239 if (depth > result.maxDepth) {
240 result.maxDepth = depth;
241 std::memcpy(result.deepestMaster, master, 3 * sizeof(double));
242 }
243 } else {
244 result.minSeparation = std::min(result.minSeparation, target->Safety(local, kFALSE));
245 }
246 }
247 return result;
248}
249
251OverlapPair assemblePair(const std::string& nameA, const std::vector<double>& pointsA, const TGeoShape* shapeA,
252 const TGeoMatrix* matA, const std::string& nameB, const std::vector<double>& pointsB,
253 const TGeoShape* shapeB, const TGeoMatrix* matB, const OverlapOptions& options)
254{
255 OverlapPair pair;
256 pair.nameA = nameA;
257 pair.nameB = nameB;
258 pair.sampledA = static_cast<int>(pointsA.size() / 3);
259 pair.sampledB = static_cast<int>(pointsB.size() / 3);
260
261 const DirectionResult aInB = probeDirection(pointsA, matA, shapeB, matB, options.depthTolerance);
262 const DirectionResult bInA = probeDirection(pointsB, matB, shapeA, matA, options.depthTolerance);
263
264 pair.pointsAInsideB = aInB.contained;
265 pair.pointsBInsideA = bInA.contained;
266 pair.deepPointsAInsideB = aInB.deep;
267 pair.deepPointsBInsideA = bInA.deep;
268
269 if (aInB.maxDepth >= bInA.maxDepth) {
270 pair.depthCm = aInB.maxDepth;
271 std::copy(aInB.deepestMaster, aInB.deepestMaster + 3, pair.deepestPoint.begin());
272 pair.deepestPointFrom = nameA;
273 } else {
274 pair.depthCm = bInA.maxDepth;
275 std::copy(bInA.deepestMaster, bInA.deepestMaster + 3, pair.deepestPoint.begin());
276 pair.deepestPointFrom = nameB;
277 }
278
279 // Containment: every boundary point of one solid is inside the other, and none of them is merely
280 // on its boundary. Legal only as a declared mother/daughter, which a flat conversion never emits.
281 const bool allAInside = pair.sampledA > 0 && aInB.contained == pair.sampledA && aInB.deep == pair.sampledA;
282 const bool allBInside = pair.sampledB > 0 && bInA.contained == pair.sampledB && bInA.deep == pair.sampledB;
283
284 if (allAInside || allBInside) {
285 pair.verdict = OverlapVerdict::Contained;
286 } else if (aInB.deep > 0 || bInA.deep > 0) {
288 } else if (aInB.contained > 0 || bInA.contained > 0) {
289 pair.verdict = OverlapVerdict::Touching;
290 } else {
291 pair.verdict = OverlapVerdict::Disjoint;
292 const double separation = std::min(aInB.minSeparation, bInA.minSeparation);
293 if (separation < std::numeric_limits<double>::max()) {
294 pair.separationCm = separation;
295 }
296 }
297 return pair;
298}
299
301void estimateSharedVolume(const TGeoShape* shapeA, const TGeoMatrix* matA, const TGeoShape* shapeB,
302 const TGeoMatrix* matB, int samples, OverlapPair& pair)
303{
304 const MasterBox boxA = masterBox(shapeA, matA, 0.);
305 const MasterBox boxB = masterBox(shapeB, matB, 0.);
306 if (!boxA.valid || !boxB.valid) {
307 return;
308 }
309 double lower[3];
310 double upper[3];
311 double boxVolume = 1.;
312 for (int dimension = 0; dimension < 3; ++dimension) {
313 lower[dimension] = std::max(boxA.lower[dimension], boxB.lower[dimension]);
314 upper[dimension] = std::min(boxA.upper[dimension], boxB.upper[dimension]);
315 boxVolume *= std::max(0., upper[dimension] - lower[dimension]);
316 }
317 if (!(boxVolume > 0.)) {
318 return;
319 }
320 int hits = 0;
321 for (int sample = 0; sample < samples; ++sample) {
322 const double master[3] = {lower[0] + (upper[0] - lower[0]) * halton(sample + 1, 2),
323 lower[1] + (upper[1] - lower[1]) * halton(sample + 1, 3),
324 lower[2] + (upper[2] - lower[2]) * halton(sample + 1, 5)};
325 double local[3];
326 matA->MasterToLocal(master, local);
327 if (!shapeA->Contains(local)) {
328 continue;
329 }
330 matB->MasterToLocal(master, local);
331 if (shapeB->Contains(local)) {
332 hits++;
333 }
334 }
335 const double fraction = double(hits) / samples;
336 pair.sharedVolumeHits = hits;
337 pair.sharedVolumeCm3 = fraction * boxVolume;
338 pair.sharedVolumeErrCm3 = std::sqrt(std::max(1., double(hits))) / samples * boxVolume;
339}
340
341} // namespace
342
343OverlapPair CheckPairOverlap(const TGeoShape* shapeA, const TGeoMatrix* matA, const std::string& nameA,
344 const TGeoShape* shapeB, const TGeoMatrix* matB, const std::string& nameB,
345 const OverlapOptions& options)
346{
347 OverlapPair pair;
348 pair.nameA = nameA;
349 pair.nameB = nameB;
350 if (shapeA == nullptr || shapeB == nullptr || matA == nullptr || matB == nullptr) {
351 return pair;
352 }
353
354 int rejectedA = 0;
355 int rejectedB = 0;
356 double residualA = 0.;
357 double residualB = 0.;
358 std::vector<double> pointsA;
359 std::vector<double> pointsB;
360 SampleBoundaryPoints(shapeA, options.pointsPerSolid, options.residualTolerance, pointsA, rejectedA, residualA);
361 SampleBoundaryPoints(shapeB, options.pointsPerSolid, options.residualTolerance, pointsB, rejectedB, residualB);
362 pair = assemblePair(nameA, pointsA, shapeA, matA, nameB, pointsB, shapeB, matB, options);
363 if (options.volumeSamples > 0 &&
365 estimateSharedVolume(shapeA, matA, shapeB, matB, options.volumeSamples, pair);
366 }
367 return pair;
368}
369
370OverlapCensus CheckWorldOverlaps(const TGeoVolume* volume, const OverlapOptions& options)
371{
372 const auto startTime = std::chrono::steady_clock::now();
373 OverlapCensus census;
374 if (volume == nullptr) {
375 return census;
376 }
377 const int daughters = volume->GetNdaughters();
378 census.nSolids = daughters;
379 census.nPairsTotal = daughters * (daughters - 1) / 2;
380
381 std::vector<const TGeoShape*> shapes(daughters, nullptr);
382 std::vector<const TGeoMatrix*> matrices(daughters, nullptr);
383 std::vector<std::string> names(daughters);
384 std::vector<MasterBox> boxes(daughters);
385 std::vector<std::vector<double>> points(daughters);
386
387 for (int index = 0; index < daughters; ++index) {
388 TGeoNode* node = volume->GetNode(index);
389 shapes[index] = node->GetVolume()->GetShape();
390 matrices[index] = node->GetMatrix();
391 names[index] = node->GetVolume()->GetName();
392 boxes[index] = masterBox(shapes[index], matrices[index], options.padCm);
393
395 report.name = names[index];
396 report.shapeClass = shapes[index] != nullptr ? shapes[index]->ClassName() : "none";
397 report.requested = options.pointsPerSolid;
398 bool usedSegments = false;
399 report.accepted = SampleBoundaryPoints(shapes[index], options.pointsPerSolid, options.residualTolerance,
400 points[index], report.rejected, report.worstResidualCm, &usedSegments);
401 report.usedPointsOnSegments = usedSegments;
402 census.nPointsRejected += report.rejected;
403 census.worstResidualCm = std::max(census.worstResidualCm, report.worstResidualCm);
404 census.solids.push_back(report);
405 }
406
407 for (int first = 0; first < daughters; ++first) {
408 for (int second = first + 1; second < daughters; ++second) {
409 if (!boxesOverlap(boxes[first], boxes[second])) {
410 continue;
411 }
412 census.nPairsTested++;
413 // Reuse the point sets: sampling is the expensive part and it does not depend on the partner.
414 OverlapPair pair = assemblePair(names[first], points[first], shapes[first], matrices[first], names[second],
415 points[second], shapes[second], matrices[second], options);
416 switch (pair.verdict) {
418 census.nDisjoint++;
419 break;
421 census.nTouching++;
422 break;
424 census.nInterpenetrating++;
425 break;
427 census.nContained++;
428 break;
429 }
430 if (options.volumeSamples > 0 && (pair.verdict == OverlapVerdict::Interpenetrating ||
432 estimateSharedVolume(shapes[first], matrices[first], shapes[second], matrices[second], options.volumeSamples,
433 pair);
434 }
435 census.pairs.push_back(pair);
436 }
437 }
438
439 // extrusion: a daughter's boundary point outside its mother
440 if (options.checkExtrusion && volume->GetShape() != nullptr && !volume->IsAssembly()) {
441 TGeoIdentity identity;
442 for (int index = 0; index < daughters; ++index) {
443 OverlapPair pair;
444 pair.nameA = names[index];
445 pair.nameB = volume->GetName();
446 pair.sampledA = static_cast<int>(points[index].size() / 3);
447 const TGeoShape* mother = volume->GetShape();
448 double worst = 0.;
449 int outside = 0;
450 double worstMaster[3] = {0., 0., 0.};
451 for (size_t point = 0; point < points[index].size() / 3; ++point) {
452 double master[3] = {0., 0., 0.};
453 matrices[index]->LocalToMaster(&points[index][3 * point], master);
454 if (!mother->Contains(master)) {
455 const double depth = mother->Safety(master, kFALSE);
456 if (depth > options.depthTolerance) {
457 outside++;
458 if (depth > worst) {
459 worst = depth;
460 std::memcpy(worstMaster, master, 3 * sizeof(double));
461 }
462 }
463 }
464 }
465 if (outside > 0) {
467 pair.depthCm = worst;
468 pair.deepPointsAInsideB = outside;
469 pair.deepestPointFrom = names[index];
470 std::copy(worstMaster, worstMaster + 3, pair.deepestPoint.begin());
471 census.extrusions.push_back(pair);
472 census.nExtruding++;
473 }
474 }
475 }
476
477 census.elapsedSeconds =
478 std::chrono::duration<double>(std::chrono::steady_clock::now() - startTime).count();
479 return census;
480}
481
482} // namespace cad
483} // namespace o2
header::DataOrigin origin
std::unique_ptr< expressions::Node > node
o2::raw::RawFileWriter * raw
int deep
double lower[3]
double deepestMaster[3]
bool valid
double maxDepth
double minSeparation
int contained
double upper[3]
GLint GLsizei count
Definition glcorearb.h:399
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint index
Definition glcorearb.h:781
GLsizei samples
Definition glcorearb.h:1309
GLint first
Definition glcorearb.h:399
GLenum target
Definition glcorearb.h:1641
GLuint GLsizei GLsizei * length
Definition glcorearb.h:790
GLint GLint GLsizei GLsizei GLsizei depth
Definition glcorearb.h:470
GLsizei const GLint * box
Definition glcorearb.h:4697
void report(gsl::span< o2::InteractionTimeRecord > irs, int threshold, bool verbose)
OverlapCensus CheckWorldOverlaps(const TGeoVolume *volume, const OverlapOptions &options=OverlapOptions())
Census every pair of volume's immediate daughters, and optionally each daughter against volume.
const char * OverlapVerdictName(OverlapVerdict verdict)
OverlapVerdict
Whether two placed solids may legally coexist: disjoint and touching are legal, interpenetrating and ...
@ Contained
every sampled boundary point of the smaller solid is inside the other
@ Disjoint
no sampled boundary point of either solid lies inside the other
@ Interpenetrating
a boundary point of one solid lies strictly inside the other: illegal
@ Touching
boundary points coincide, but none is deeper than the depth tolerance
OverlapPair CheckPairOverlap(const TGeoShape *shapeA, const TGeoMatrix *matA, const std::string &nameA, const TGeoShape *shapeB, const TGeoMatrix *matB, const std::string &nameB, const OverlapOptions &options=OverlapOptions())
Test one placed pair. matA / matB take each shape's local frame to the common frame.
int SampleBoundaryPoints(const TGeoShape *shape, int npoints, double residualTolerance, std::vector< double > &points, int &rejected, double &worstResidual, bool *usedPointsOnSegments=nullptr)
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
std::vector< OverlapSolidReport > solids
int nPairsTotal
N (N - 1) / 2.
std::vector< OverlapPair > extrusions
int nPairsTested
after the bounding-box rejection
std::vector< OverlapPair > pairs
only the pairs that survived the bounding-box rejection
double depthTolerance
A containment shallower than this is a shared boundary, not an overlap. In cm.
double padCm
Bounding-box inflation before the pairwise rejection, in cm; it decides which disjoint pairs get a se...
int volumeSamples
Monte-Carlo samples for the shared volume of an illegal pair; 0, the default, disables the estimate.
One pair of placed solids, and everything measured about it.
OverlapVerdict verdict
int deepPointsAInsideB
... of which deeper than depthTolerance
int sampledA
accepted (on-boundary) sample counts actually used
std::string deepestPointFrom
which solid's boundary the deepest point came from
std::array< double, 3 > deepestPoint
in the master frame
One solid's sampling report; a shape with a poor display mesh shows here as reduced coverage.