Project
Loading...
Searching...
No Matches
O2BVHSurfaceSolid.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 third-party BVH headers plus extra kernels, shared with O2Tessellated
19#include "bvh2_third_party.h"
20#include "bvh2_extra_kernels.h"
21
22#include "TBuffer.h"
23#include "TBuffer3D.h"
24#include "TBuffer3DTypes.h"
25
26#include <algorithm>
27#include <cmath>
28#include <iostream>
29#include <limits>
30#include <memory>
31#include <string>
32#include <utility>
33
34using namespace o2::cad;
35using namespace o2::cad::surface;
37
38namespace
39{
40// float BVH types following the O2Tessellated::BuildBVH pattern
41using BVHScalar = float;
42using BVHBBox = bvh::v2::BBox<BVHScalar, 3>;
43using BVHVec3 = bvh::v2::Vec<BVHScalar, 3>;
44using BVHNode = bvh::v2::Node<BVHScalar, 3>;
45using BVH = bvh::v2::Bvh<BVHNode>;
46using BVHRay = bvh::v2::Ray<BVHScalar, 3>;
47
48Vec2 makeVec2(const O2BVHSurfaceSolid::Point2D& point)
49{
50 return {point[0], point[1]};
51}
52
53Vec3 makeVec3(const O2BVHSurfaceSolid::Point3D& point)
54{
55 return {point[0], point[1], point[2]};
56}
57
58Vec3 makeVec3(const Double_t* point)
59{
60 return {point[0], point[1], point[2]};
61}
62
63// The arbitrary skew test direction used for parity-based containment: probes all normals and
64// avoids evident symmetries (same as O2Tessellated), normalized so hit distances are lengths.
65const Vec3 kContainsTestDirection = normalized({1., 1.41421356237, 1.73205080757});
66
68const std::array<Vec3, 5>& reshootDirections()
69{
70 static const std::array<Vec3, 5> directions = [] {
71 std::array<Vec3, 5> spiral{};
72 for (int index = 0; index < 5; ++index) {
73 const double cosTheta = 1. - 2. * (index + 0.5) / 5.;
74 const double sinTheta = std::sqrt(1. - cosTheta * cosTheta);
75 const double phi = 2.399963229728653 * index; // golden angle
76 spiral[index] = normalized({sinTheta * std::cos(phi), sinTheta * std::sin(phi), cosTheta});
77 }
78 return spiral;
79 }();
80 return directions;
81}
82
83// Ray tmax tightening in the BVH distance queries; see O2BVHSurfaceSolid::SetRayTMaxPruning.
84bool gRayTMaxPruning = true;
85// Per-thread diagnostic counter of leaf surface patches visited by the BVH distance queries.
86thread_local long long gRayCandidateCount = 0;
87// ... and by the nearest-patch queries behind Safety and ComputeNormal; see
88// O2BVHSurfaceSolid::ResetSafetyCandidateCounter.
89thread_local long long gSafetyCandidateCount = 0;
90// Deliberately unsound node bound for the nearest-patch traversal; see
91// O2BVHSurfaceSolid::SetSafetyBoundUnsoundForTest. Never true outside a test.
92bool gSafetyBoundUnsound = false;
93
94// Per-thread backing store of SurfaceVisitMarker, one stamp per surface index plus the epoch the
95// live marker stamps with; see the class below.
96thread_local std::vector<unsigned long long> gSurfaceVisitStamps;
97thread_local unsigned long long gSurfaceVisitEpoch = 0;
98
101class SurfaceVisitMarker
102{
103 public:
104 explicit SurfaceVisitMarker(size_t surfaceCount) : mStamps(gSurfaceVisitStamps), mEpoch(++gSurfaceVisitEpoch)
105 {
106 if (mStamps.size() < surfaceCount) {
107 mStamps.resize(surfaceCount, 0);
108 }
109 }
110
112 bool firstVisit(size_t index)
113 {
114 if (mStamps[index] == mEpoch) {
115 return false;
116 }
117 mStamps[index] = mEpoch;
118 return true;
119 }
120
121 private:
123 std::vector<unsigned long long>& mStamps;
124 unsigned long long mEpoch;
125};
126
129inline double boxDistanceSq(const BVHBBox& box, const Vec3& point, bool unsoundBound)
130{
131 const double coordinates[3] = {point.xCoord, point.yCoord, point.zCoord};
132 double distanceSq = 0.;
133 for (int dimension = 0; dimension < 3; ++dimension) {
134 const double lower = static_cast<double>(box.min[dimension]);
135 const double upper = static_cast<double>(box.max[dimension]);
136 const double value = coordinates[dimension];
137 if (value < lower) {
138 distanceSq += (lower - value) * (lower - value);
139 } else if (value > upper) {
140 distanceSq += (value - upper) * (value - upper);
141 }
142 }
143 if (unsoundBound) {
144 // The negative control: the distance to the box centre bounds nothing.
145 double centreDistanceSq = 0.;
146 for (int dimension = 0; dimension < 3; ++dimension) {
147 const double centre =
148 0.5 * (static_cast<double>(box.min[dimension]) + static_cast<double>(box.max[dimension]));
149 const double gap = coordinates[dimension] - centre;
150 centreDistanceSq += gap * gap;
151 }
152 return centreDistanceSq;
153 }
154 return distanceSq * (1. - 1.e-12);
155}
156
158inline BVHScalar truncateRoundUp(double bound)
159{
160 const double clamped = std::min(bound, static_cast<double>(std::numeric_limits<BVHScalar>::max()));
161 const double biased = clamped + std::numeric_limits<BVHScalar>::epsilon() * std::abs(clamped);
162 return static_cast<BVHScalar>(biased);
163}
164
166constexpr double kDistanceRayTolerance = -kRayTolerance;
167
169enum class CrossingSense { Entering,
170 Exiting,
171 Tangential };
172
174inline double clusterMargin(double distance)
175{
176 return 2. * kIntersectionTolerance * std::max(1., std::abs(distance));
177}
178
179inline CrossingSense crossingSense(const RayHit& hit, const Vec3& rayDirection)
180{
181 const double alignment = dot(hit.normal, rayDirection);
182 if (alignment < -kTolerance) {
183 return CrossingSense::Entering;
184 }
185 if (alignment > kTolerance) {
186 return CrossingSense::Exiting;
187 }
188 return CrossingSense::Tangential;
189}
190
192template <typename ClusterVisitor>
193void forEachCrossingCluster(std::vector<RayHit>& hits, const Vec3& rayDirection, ClusterVisitor&& visitor)
194{
195 std::sort(hits.begin(), hits.end(),
196 [](const RayHit& firstHit, const RayHit& secondHit) { return firstHit.distance < secondHit.distance; });
197
198 size_t hitIndex = 0;
199 while (hitIndex < hits.size()) {
200 bool entering = false;
201 bool exiting = false;
202 size_t clusterEnd = hitIndex;
203 // Compared against the cluster's first member, not its predecessor: chaining would merge thin features at large t.
204 while (clusterEnd < hits.size() &&
205 (clusterEnd == hitIndex || sameIntersection(hits[clusterEnd].distance, hits[hitIndex].distance))) {
206 switch (crossingSense(hits[clusterEnd], rayDirection)) {
207 case CrossingSense::Entering:
208 entering = true;
209 break;
210 case CrossingSense::Exiting:
211 exiting = true;
212 break;
213 case CrossingSense::Tangential:
214 break;
215 }
216 ++clusterEnd;
217 }
218 // both, or neither: nothing was crossed
219 const CrossingSense sense = entering == exiting ? CrossingSense::Tangential
220 : (entering ? CrossingSense::Entering : CrossingSense::Exiting);
221 if (!visitor(hitIndex, clusterEnd, sense)) {
222 return;
223 }
224 hitIndex = clusterEnd;
225 }
226}
227
229template <bool wantEntering>
230double nearestCrossingInHits(std::vector<RayHit>& hits, const Vec3& rayDirection, bool& grazedFirst)
231{
232 constexpr CrossingSense wanted = wantEntering ? CrossingSense::Entering : CrossingSense::Exiting;
233 double distance = TGeoShape::Big();
234 grazedFirst = false;
235 forEachCrossingCluster(hits, rayDirection, [&](size_t firstIndex, size_t, CrossingSense sense) {
236 if (sense == CrossingSense::Tangential) {
237 grazedFirst = true;
238 return true;
239 }
240 if (sense != wanted) {
241 return true;
242 }
243 // clusters come in increasing distance, so the first match is the answer; a crossing is never negative
244 distance = std::max(0., hits[firstIndex].distance);
245 return false;
246 });
247 return distance;
248}
249
252
253void fillPoint3(double (&target)[3], const O2BVHSurfaceSolid::Point3D& source)
254{
255 target[0] = source[0];
256 target[1] = source[1];
257 target[2] = source[2];
258}
259
260O2BVHSurfaceSolid::Point3D makePoint3D(const double (&source)[3])
261{
262 return {source[0], source[1], source[2]};
263}
264
268 std::vector<double> scalars, bool innerWall, bool trimmed)
269{
270 BVHSurfaceRecord record;
271 record.kind = kind;
272 fillPoint3(record.origin, origin);
273 fillPoint3(record.axisA, axisA);
274 fillPoint3(record.axisB, axisB);
275 record.scalars = std::move(scalars);
276 record.innerWall = innerWall;
277 record.trimmed = trimmed;
278 return record;
279}
280
282{
284 record.kind = static_cast<int>(curve.kind);
285 record.lineStart[0] = curve.lineStart[0];
286 record.lineStart[1] = curve.lineStart[1];
287 record.lineEnd[0] = curve.lineEnd[0];
288 record.lineEnd[1] = curve.lineEnd[1];
289 record.center[0] = curve.center[0];
290 record.center[1] = curve.center[1];
291 record.radius = curve.radius;
292 record.startAngle = curve.startAngle;
293 record.endAngle = curve.endAngle;
294 record.degree = curve.degree;
295 record.poles.reserve(2 * curve.poles.size());
296 for (const auto& pole : curve.poles) {
297 record.poles.push_back(pole[0]);
298 record.poles.push_back(pole[1]);
299 }
300 record.weights = curve.weights;
301 record.knots = curve.knots;
302 return record;
303}
304
306{
308 curve.kind = static_cast<O2BVHSurfaceSolid::PlanarBoundaryCurve::Kind>(record.kind);
309 curve.lineStart = {record.lineStart[0], record.lineStart[1]};
310 curve.lineEnd = {record.lineEnd[0], record.lineEnd[1]};
311 curve.center = {record.center[0], record.center[1]};
312 curve.radius = record.radius;
313 curve.startAngle = record.startAngle;
314 curve.endAngle = record.endAngle;
315 curve.degree = record.degree;
316 curve.poles.reserve(record.poles.size() / 2);
317 for (size_t index = 0; index + 1 < record.poles.size(); index += 2) {
318 curve.poles.push_back({record.poles[index], record.poles[index + 1]});
319 }
320 curve.weights = record.weights;
321 curve.knots = record.knots;
322 return curve;
323}
324
326void storeCurveWires(BVHSurfaceRecord& record, const std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>& outerWire,
327 const std::vector<std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>>& innerWires)
328{
329 record.wireSizes.push_back(static_cast<int>(outerWire.size()));
330 for (const auto& curve : outerWire) {
331 record.curves.push_back(makeCurveRecord(curve));
332 }
333 for (const auto& innerWire : innerWires) {
334 record.wireSizes.push_back(static_cast<int>(innerWire.size()));
335 for (const auto& curve : innerWire) {
336 record.curves.push_back(makeCurveRecord(curve));
337 }
338 }
339}
340
343bool loadCurveWires(const BVHSurfaceRecord& record, std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>& outerWire,
344 std::vector<std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>>& innerWires)
345{
346 size_t consumed = 0;
347 for (size_t wireIndex = 0; wireIndex < record.wireSizes.size(); ++wireIndex) {
348 const int wireSize = record.wireSizes[wireIndex];
349 if (wireSize < 0 || consumed + static_cast<size_t>(wireSize) > record.curves.size()) {
350 return false;
351 }
352 auto& wire = wireIndex == 0 ? outerWire : innerWires.emplace_back();
353 for (int curveIndex = 0; curveIndex < wireSize; ++curveIndex) {
354 wire.push_back(makeBoundaryCurve(record.curves[consumed + curveIndex]));
355 }
356 consumed += static_cast<size_t>(wireSize);
357 }
358 return consumed == record.curves.size();
359}
360
362void storePolygonWires(BVHSurfaceRecord& record, const std::vector<O2BVHSurfaceSolid::Point2D>& outerWire,
363 const std::vector<std::vector<O2BVHSurfaceSolid::Point2D>>& innerWires)
364{
365 const auto append = [&record](const std::vector<O2BVHSurfaceSolid::Point2D>& wire) {
366 record.wireSizes.push_back(static_cast<int>(wire.size()));
367 for (const auto& vertex : wire) {
368 record.polygonPoints.push_back(vertex[0]);
369 record.polygonPoints.push_back(vertex[1]);
370 }
371 };
372 append(outerWire);
373 for (const auto& innerWire : innerWires) {
374 append(innerWire);
375 }
376}
377
378bool loadPolygonWires(const BVHSurfaceRecord& record, std::vector<O2BVHSurfaceSolid::Point2D>& outerWire,
379 std::vector<std::vector<O2BVHSurfaceSolid::Point2D>>& innerWires)
380{
381 size_t consumed = 0;
382 for (size_t wireIndex = 0; wireIndex < record.wireSizes.size(); ++wireIndex) {
383 const int wireSize = record.wireSizes[wireIndex];
384 if (wireSize < 0 || 2 * (consumed + static_cast<size_t>(wireSize)) > record.polygonPoints.size()) {
385 return false;
386 }
387 auto& wire = wireIndex == 0 ? outerWire : innerWires.emplace_back();
388 for (int vertexIndex = 0; vertexIndex < wireSize; ++vertexIndex) {
389 const size_t offset = 2 * (consumed + vertexIndex);
390 wire.push_back({record.polygonPoints[offset], record.polygonPoints[offset + 1]});
391 }
392 consumed += static_cast<size_t>(wireSize);
393 }
394 return 2 * consumed == record.polygonPoints.size();
395}
397
398// Ray parity of a full intersection list (sorts in place); a mixed-sense cluster is a graze and counts even.
399bool oddCrossingParity(std::vector<RayHit>& hits, const Vec3& rayDirection)
400{
401 int crossings = 0;
402 forEachCrossingCluster(hits, rayDirection, [&](size_t, size_t, CrossingSense sense) {
403 if (sense != CrossingSense::Tangential) {
404 ++crossings;
405 }
406 return true;
407 });
408 return (crossings & 1) != 0;
409}
410
412O2BVHSurfaceSolid::NavigationReliability rimStateToReliability(RimState state)
413{
415 switch (state) {
416 case RimState::Matched:
417 return Reliability::Reliable;
418 case RimState::Reversed:
419 return Reliability::ReversedFaces;
420 case RimState::Boundary:
421 return Reliability::OpenSurfaceSet;
422 case RimState::NonManifold:
423 return Reliability::NonManifold;
424 }
425 return Reliability::Undetermined;
426}
427} // namespace
428
430 std::vector<std::unique_ptr<BoundedSurface>> surfaces;
431 std::vector<Vec3> displayVertices;
432 std::vector<std::array<int, 3>> displayTriangles;
434 std::vector<int> displayTriangleSurface;
438 std::vector<RimReport> rimReports;
439 bool defined = false;
440 std::unique_ptr<BVH> bvh;
442 std::vector<int> leafSurface;
444 bool reliable = false;
446 std::vector<Vec3> safetyAnchors;
447
449 void buildBVH()
450 {
451 bvh.reset();
452 leafSurface.clear();
453 if (surfaces.empty()) {
454 return;
455 }
456
457 std::vector<BVHBBox> primitiveBoxes;
458 std::vector<BVHVec3> primitiveCenters;
459 std::vector<BoundedSurface::CoverBox> coverBoxes;
460 std::vector<int> coverSurface;
461 for (size_t surfaceIndex = 0; surfaceIndex < surfaces.size(); ++surfaceIndex) {
462 coverBoxes.clear();
463 surfaces[surfaceIndex]->appendCoverBoxes(coverBoxes);
464 for (const auto& coverBox : coverBoxes) {
465 BVHBBox primitiveBox;
466 for (int dimension = 0; dimension < 3; ++dimension) {
467 primitiveBox.min[dimension] = std::nextafterf(
468 static_cast<float>(component(coverBox.first, dimension) - kBVHBoxTolerance),
469 -std::numeric_limits<float>::infinity());
470 primitiveBox.max[dimension] = std::nextafterf(
471 static_cast<float>(component(coverBox.second, dimension) + kBVHBoxTolerance),
472 std::numeric_limits<float>::infinity());
473 }
474 primitiveBoxes.push_back(primitiveBox);
475 primitiveCenters.emplace_back(primitiveBox.get_center());
476 coverSurface.push_back(static_cast<int>(surfaceIndex));
477 }
478 }
479
480 typename bvh::v2::DefaultBuilder<BVHNode>::Config config;
481 config.quality = bvh::v2::DefaultBuilder<BVHNode>::Quality::High;
482 // One cover box per leaf: bvh2 enters a leaf without a box test, and a patch intersection costs far more than one.
483 config.max_leaf_size = 1;
484 bvh = std::make_unique<BVH>(bvh::v2::DefaultBuilder<BVHNode>::build(primitiveBoxes, primitiveCenters, config));
485 leafSurface.resize(bvh->prim_ids.size());
486 for (size_t leaf = 0; leaf < bvh->prim_ids.size(); ++leaf) {
487 leafSurface[leaf] = coverSurface[bvh->prim_ids[leaf]];
488 }
489 }
490
492 size_t surfaceOfPrimitive(size_t primitive) const
493 {
494 return static_cast<size_t>(leafSurface[primitive]);
495 }
496
499 {
500 constexpr size_t kAnchorCount = 24;
501 safetyAnchors.clear();
502 if (displayVertices.empty()) {
503 return;
504 }
505 const size_t stride = std::max<size_t>(1, displayVertices.size() / kAnchorCount);
506 for (size_t index = 0; index < displayVertices.size() && safetyAnchors.size() < kAnchorCount; index += stride) {
508 }
509 }
510
513 double anchorSeedDistanceSq(const Vec3& point) const
514 {
515 double bestDistanceSq = std::numeric_limits<double>::infinity();
516 for (const auto& anchor : safetyAnchors) {
517 bestDistanceSq = std::min(bestDistanceSq, normSq(point - anchor));
518 }
519 if (!std::isfinite(bestDistanceSq)) {
520 return bestDistanceSq;
521 }
522 // the relative term dominates the roundings, the absolute one the anchors' on-patch tolerance; both far below kBVHBoxTolerance
523 const double inflated = std::sqrt(bestDistanceSq) * (1. + 1.e-12) + 1.e-10;
524 return inflated * inflated;
525 }
526
529 template <typename SurfaceVisitor>
530 void visitRayCandidates(const Vec3& rayOrigin, const Vec3& rayDirection, SurfaceVisitor&& visitor) const
531 {
532 BVHRay ray(BVHVec3(rayOrigin.xCoord, rayOrigin.yCoord, rayOrigin.zCoord),
533 BVHVec3(rayDirection.xCoord, rayDirection.yCoord, rayDirection.zCoord), 0.f,
534 std::numeric_limits<BVHScalar>::max());
535 static constexpr bool useRobustTraversal = true;
536 static thread_local bvh::v2::GrowingStack<BVH::Index> stack;
537 stack.clear();
538 SurfaceVisitMarker marker(surfaces.size());
539 bvh->intersect<false, useRobustTraversal>(ray, bvh->get_root().index, stack,
540 [&](size_t beginPrimitive, size_t endPrimitive) {
541 for (size_t primitive = beginPrimitive; primitive < endPrimitive;
542 ++primitive) {
543 const size_t surfaceIndex = surfaceOfPrimitive(primitive);
544 if (marker.firstVisit(surfaceIndex)) {
545 visitor(*surfaces[surfaceIndex]);
546 }
547 }
548 return false; // keep traversing
549 });
550 }
551
554 template <bool wantEntering>
555 double nearestCrossing(const Vec3& rayOrigin, const Vec3& rayDirection, double stepmax) const
556 {
557 static thread_local std::vector<RayHit> collectedHits;
558 constexpr CrossingSense wanted = wantEntering ? CrossingSense::Entering : CrossingSense::Exiting;
559
560 // Hits are classified with their neighbours, since a graze crosses nothing. If pruning stopped at a candidate
561 // that turns out to be a graze, redo the query without pruning: both passes must return the same number.
562 long long candidates = 0;
563 for (int attempt = 0; attempt < 2; ++attempt) {
564 const bool pruning = gRayTMaxPruning && attempt == 0;
565 collectedHits.clear();
566
567 double bestCandidate = TGeoShape::Big();
568 BVHRay ray(BVHVec3(rayOrigin.xCoord, rayOrigin.yCoord, rayOrigin.zCoord),
569 BVHVec3(rayDirection.xCoord, rayDirection.yCoord, rayDirection.zCoord), 0.f,
570 truncateRoundUp(stepmax));
571 static constexpr bool useRobustTraversal = true;
572
573 static thread_local bvh::v2::GrowingStack<BVH::Index> stack;
574 stack.clear();
575 SurfaceVisitMarker marker(surfaces.size());
576 // ray is captured by reference on purpose: bvh2 takes it as const Ray&, but the object
577 // itself is ours and mutable, and the traversal reads tmax afresh at every node test.
578 bvh->intersect<false, useRobustTraversal>(
579 ray, bvh->get_root().index, stack, [&](size_t beginPrimitive, size_t endPrimitive) {
580 for (size_t primitive = beginPrimitive; primitive < endPrimitive; ++primitive) {
581 const size_t surfaceIndex = surfaceOfPrimitive(primitive);
582 if (!marker.firstVisit(surfaceIndex)) {
583 continue;
584 }
585 const BoundedSurface& surface = *surfaces[surfaceIndex];
586 ++candidates;
587 // the per-surface bound keeps a margin past the candidate, so its cluster partners are never cut
588 const double bound =
589 pruning ? std::min(stepmax, bestCandidate + clusterMargin(bestCandidate)) : stepmax;
590 const size_t firstNewHit = collectedHits.size();
591 surface.appendIntersections(rayOrigin, rayDirection, kDistanceRayTolerance, bound, collectedHits);
592 for (size_t hitIndex = firstNewHit; hitIndex < collectedHits.size(); ++hitIndex) {
593 const RayHit& hit = collectedHits[hitIndex];
594 if (crossingSense(hit, rayDirection) == wanted && hit.distance < bestCandidate) {
595 bestCandidate = hit.distance;
596 }
597 }
598 }
599 if (pruning && bestCandidate < stepmax) {
600 ray.tmax = std::min(ray.tmax, truncateRoundUp(bestCandidate + kBVHBoxTolerance));
601 }
602 return false; // keep traversing; the shrunk tmax does the pruning
603 });
604
605 bool grazedFirst = false;
606 const double distance = nearestCrossingInHits<wantEntering>(collectedHits, rayDirection, grazedFirst);
607 if (!pruning || !grazedFirst) {
608 gRayCandidateCount += candidates;
609 return distance;
610 }
611 }
612 gRayCandidateCount += candidates;
613 return TGeoShape::Big(); // unreachable: the second attempt never prunes
614 }
615
617 template <bool wantEntering>
618 double nearestCrossingLoop(const Vec3& rayOrigin, const Vec3& rayDirection, double stepmax) const
619 {
620 static thread_local std::vector<RayHit> collectedLoopHits;
621
622 // No pruning at all here: this is the oracle the accelerated query is checked against, so it
623 // trades the shrinking upper bound for having every hit in hand and needing no retry.
624 collectedLoopHits.clear();
625 for (const auto& surface : surfaces) {
626 surface->appendIntersections(rayOrigin, rayDirection, kDistanceRayTolerance, stepmax, collectedLoopHits);
627 }
628 bool grazedFirst = false;
629 return nearestCrossingInHits<wantEntering>(collectedLoopHits, rayDirection, grazedFirst);
630 }
631
633 bool parityAlong(const Vec3& point, const Vec3& direction, bool useBVH, bool* ambiguous = nullptr) const
634 {
635 // reused across calls so containment allocates nothing on the hot path; the capacity is paid
636 // once per thread. Distinct from the distance queries' buffers, which are their own.
637 static thread_local std::vector<RayHit> parityHits;
638 parityHits.clear();
639 if (useBVH) {
640 visitRayCandidates(point, direction, [&](const BoundedSurface& surface) {
641 surface.appendIntersections(point, direction, kRayTolerance, TGeoShape::Big(), parityHits);
642 });
643 } else {
644 for (const auto& surface : surfaces) {
645 surface->appendIntersections(point, direction, kRayTolerance, TGeoShape::Big(), parityHits);
646 }
647 }
648 if (ambiguous != nullptr) {
649 *ambiguous = std::any_of(parityHits.begin(), parityHits.end(),
650 [](const RayHit& hit) { return hit.onTrimBoundary; });
651 }
652 return oddCrossingParity(parityHits, direction);
653 }
654
657 bool containsByVote(const Vec3& point, bool useBVH, bool* allTiedOnBoundary = nullptr) const
658 {
659 constexpr int kMajority = 3; // of the five directions
660 int inside = 0; // shots whose parity rests on no trim-boundary tie-break
661 int outside = 0;
662 int insideOnBoundary = 0; // and shots that do, counted apart
663 int outsideOnBoundary = 0;
664 for (const auto& direction : reshootDirections()) {
665 bool ambiguous = false;
666 const bool answer = parityAlong(point, direction, useBVH, &ambiguous);
667 if (ambiguous) {
668 answer ? ++insideOnBoundary : ++outsideOnBoundary;
669 } else {
670 answer ? ++inside : ++outside;
671 }
672 if (inside >= kMajority || outside >= kMajority) {
673 break;
674 }
675 }
676 if (allTiedOnBoundary != nullptr) {
677 *allTiedOnBoundary = (inside == outside);
678 }
679 // Decide among the shots that rest on the geometry unless they tie; a genuine tie counts all five.
680 if (inside != outside) {
681 return inside > outside;
682 }
683 return (inside + insideOnBoundary) > (outside + outsideOnBoundary);
684 }
685
687 template <typename SurfaceVisitor>
688 bool visitPointCandidates(const Vec3& point, SurfaceVisitor&& visitor) const
689 {
690 const BVHVec3 testPoint(point.xCoord, point.yCoord, point.zCoord);
691 SurfaceVisitMarker marker(surfaces.size());
692 static thread_local std::vector<size_t> nodeStack;
693 nodeStack.clear();
694 nodeStack.push_back(0); // start from the root node
695 while (!nodeStack.empty()) {
696 const auto& node = bvh->nodes[nodeStack.back()];
697 nodeStack.pop_back();
698 if (!bvh::v2::extra::contains(node.get_bbox(), testPoint)) {
699 continue;
700 }
701 if (node.is_leaf()) {
702 const auto beginPrimitive = node.index.first_id();
703 const auto endPrimitive = beginPrimitive + node.index.prim_count();
704 for (auto primitive = beginPrimitive; primitive < endPrimitive; ++primitive) {
705 const size_t surfaceIndex = surfaceOfPrimitive(primitive);
706 if (marker.firstVisit(surfaceIndex) && visitor(*surfaces[surfaceIndex])) {
707 return true;
708 }
709 }
710 } else {
711 const auto firstChild = node.index.first_id();
712 for (size_t child : {firstChild, firstChild + 1}) {
713 if (child < bvh->nodes.size()) {
714 nodeStack.push_back(child);
715 }
716 }
717 }
718 }
719 return false;
720 }
721
723 double nearestPatchDistanceSqLoop(const Vec3& point, size_t* closestIndex) const
724 {
725 double bestDistanceSq = std::numeric_limits<double>::infinity();
726 size_t bestIndex = surfaces.size();
727 for (size_t index = 0; index < surfaces.size(); ++index) {
728 const double patchDistanceSq = surfaces[index]->distanceSqToPatch(point);
729 if (patchDistanceSq < bestDistanceSq) {
730 bestDistanceSq = patchDistanceSq;
731 bestIndex = index;
732 }
733 }
734 if (closestIndex != nullptr) {
735 *closestIndex = bestIndex;
736 }
737 return bestDistanceSq;
738 }
739
742 template <bool TrackIndex>
743 double nearestPatchDistanceSq(const Vec3& point, size_t* closestIndex) const
744 {
745 if (bvh == nullptr) {
746 return nearestPatchDistanceSqLoop(point, closestIndex);
747 }
748
749 struct StackEntry {
750 size_t node;
751 double lowerBoundSq;
752 };
753 // reused across calls so the hot path allocates nothing; capacity is paid once per thread
754 static thread_local std::vector<StackEntry> nodeStack;
755 nodeStack.clear();
756
757 // Seed the running best with the anchor distance; it never displaces the true winner (see anchorSeedDistanceSq).
758 double bestDistanceSq = anchorSeedDistanceSq(point);
759 size_t bestIndex = surfaces.size();
760 SurfaceVisitMarker marker(surfaces.size());
761 const bool unsound = gSafetyBoundUnsound;
762 long long candidates = 0;
763
764 auto pruned = [](double lowerBoundSq, double bestSoFarSq) {
765 return TrackIndex ? lowerBoundSq > bestSoFarSq : lowerBoundSq >= bestSoFarSq;
766 };
767
768 nodeStack.push_back({0, boxDistanceSq(bvh->nodes[0].get_bbox(), point, unsound)});
769 while (!nodeStack.empty()) {
770 const StackEntry entry = nodeStack.back();
771 nodeStack.pop_back();
772 if (pruned(entry.lowerBoundSq, bestDistanceSq)) {
773 continue;
774 }
775 const auto& node = bvh->nodes[entry.node];
776 if (node.is_leaf()) {
777 const auto beginPrimitive = node.index.first_id();
778 const auto endPrimitive = beginPrimitive + node.index.prim_count();
779 for (auto primitive = beginPrimitive; primitive < endPrimitive; ++primitive) {
780 const size_t surfaceIndex = surfaceOfPrimitive(primitive);
781 if (!marker.firstVisit(surfaceIndex)) {
782 continue;
783 }
784 ++candidates;
785 const double patchDistanceSq = surfaces[surfaceIndex]->distanceSqToPatch(point);
786 if (patchDistanceSq < bestDistanceSq) {
787 bestDistanceSq = patchDistanceSq;
788 bestIndex = surfaceIndex;
789 } else if (TrackIndex && patchDistanceSq == bestDistanceSq && surfaceIndex < bestIndex) {
790 bestIndex = surfaceIndex;
791 }
792 }
793 continue;
794 }
795 const size_t firstChild = node.index.first_id();
796 const size_t secondChild = firstChild + 1;
797 if (secondChild >= bvh->nodes.size()) {
798 if (firstChild < bvh->nodes.size()) {
799 nodeStack.push_back({firstChild, boxDistanceSq(bvh->nodes[firstChild].get_bbox(), point, unsound)});
800 }
801 continue;
802 }
803 double nearBound = boxDistanceSq(bvh->nodes[firstChild].get_bbox(), point, unsound);
804 double farBound = boxDistanceSq(bvh->nodes[secondChild].get_bbox(), point, unsound);
805 size_t nearChild = firstChild;
806 size_t farChild = secondChild;
807 if (farBound < nearBound) {
808 std::swap(nearBound, farBound);
809 std::swap(nearChild, farChild);
810 }
811 // farther child first: the stack is LIFO, so the nearer one is popped -- and tightens the
812 // best -- before the farther one is re-tested
813 if (!pruned(farBound, bestDistanceSq)) {
814 nodeStack.push_back({farChild, farBound});
815 }
816 if (!pruned(nearBound, bestDistanceSq)) {
817 nodeStack.push_back({nearChild, nearBound});
818 }
819 }
820
821 gSafetyCandidateCount += candidates;
822 if (closestIndex != nullptr) {
823 *closestIndex = bestIndex;
824 }
825 return bestDistanceSq;
826 }
827
829 bool refuseIfDefined(const O2BVHSurfaceSolid& owner, const char* method) const
830 {
831 if (!defined) {
832 return false;
833 }
834 owner.Error(method, "Shape %s already fully defined. Not adding", owner.GetName());
835 return true;
836 }
837
839 bool commit(std::unique_ptr<BoundedSurface> surface, BVHSurfaceRecord record,
840 std::vector<BVHSurfaceRecord>& records)
841 {
842 records.push_back(std::move(record));
843 surfaces.emplace_back(std::move(surface));
844 return true;
845 }
846};
847
849{
850 switch (recordKind) {
851 case PlanarPolygon:
852 case CurvedPlanar:
853 return 0;
854 case Cylindrical: // radius, heightMin, heightMax, phiStart, phiSweep
855 case Spherical: // radius, thetaMin, thetaMax, phiStart, phiSweep
856 return 5;
857 case Conical: // radiusAtMin, radiusAtMax, heightMin, heightMax, phiStart, phiSweep
858 case Toroidal: // majorRadius, minorRadius, phiStart, phiSweep, tubeStart, tubeSweep
859 return 6;
860 default:
861 return -1;
862 }
863}
864
868
869O2BVHSurfaceSolid::O2BVHSurfaceSolid(const char* name) : TGeoBBox(name, 0., 0., 0.), fImpl(new Impl)
870{
871}
872
874{
875 delete fImpl;
876}
877
878bool O2BVHSurfaceSolid::AddPlanarSurface(const Point3D& origin, const Point3D& axisU, const Point3D& axisV,
879 const std::vector<Point2D>& outerWire,
880 const std::vector<std::vector<Point2D>>& innerWires)
881{
882 if (fImpl->refuseIfDefined(*this, "AddPlanarSurface")) {
883 return false;
884 }
885
886 std::vector<Vec2> convertedOuterWire;
887 convertedOuterWire.reserve(outerWire.size());
888 for (const auto& vertex : outerWire) {
889 convertedOuterWire.push_back(makeVec2(vertex));
890 }
891
892 std::vector<std::vector<Vec2>> convertedInnerWires;
893 convertedInnerWires.reserve(innerWires.size());
894 for (const auto& innerWire : innerWires) {
895 auto& convertedInnerWire = convertedInnerWires.emplace_back();
896 convertedInnerWire.reserve(innerWire.size());
897 for (const auto& vertex : innerWire) {
898 convertedInnerWire.push_back(makeVec2(vertex));
899 }
900 }
901
902 auto surface = std::make_unique<PlanarBoundedSurface>();
903 std::string errorMessage;
904 if (!surface->initialize(makeVec3(origin), makeVec3(axisU), makeVec3(axisV), convertedOuterWire, convertedInnerWires,
905 errorMessage)) {
906 Error("AddPlanarSurface", "%s", errorMessage.c_str());
907 return false;
908 }
909 if (surface->wasReoriented()) {
910 Warning("AddPlanarSurface", "Shape %s: planar surface %d had a wire re-oriented to match its role", GetName(),
911 static_cast<int>(fImpl->surfaces.size()));
912 }
913
914 auto record = makeRecord(BVHSurfaceRecord::PlanarPolygon, origin, axisU, axisV, {}, false, false);
915 storePolygonWires(record, outerWire, innerWires);
916 return fImpl->commit(std::move(surface), std::move(record), fRecords);
917}
918
919namespace
920{
922std::vector<Curve2D> makeCurveWire(const std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>& wire)
923{
924 std::vector<Curve2D> curves;
925 curves.reserve(wire.size());
926 for (const auto& c : wire) {
928 curves.push_back(Curve2D::makeArc({c.center[0], c.center[1]}, c.radius, c.startAngle, c.endAngle));
930 std::vector<Vec2> poles;
931 poles.reserve(c.poles.size());
932 for (const auto& pole : c.poles) {
933 poles.push_back({pole[0], pole[1]});
934 }
935 curves.push_back(Curve2D::makeBSpline(c.degree, std::move(poles), c.weights, c.knots));
936 } else {
937 curves.push_back(Curve2D::makeLine({c.lineStart[0], c.lineStart[1]}, {c.lineEnd[0], c.lineEnd[1]}));
938 }
939 }
940 return curves;
941}
942
944std::vector<std::vector<Curve2D>> makeCurveWires(
945 const std::vector<std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>>& wires)
946{
947 std::vector<std::vector<Curve2D>> loops;
948 loops.reserve(wires.size());
949 for (const auto& wire : wires) {
950 loops.push_back(makeCurveWire(wire));
951 }
952 return loops;
953}
954} // namespace
955
957 const std::vector<PlanarBoundaryCurve>& outerWire,
958 const std::vector<std::vector<PlanarBoundaryCurve>>& innerWires)
959{
960 if (fImpl->refuseIfDefined(*this, "AddCurvedPlanarSurface")) {
961 return false;
962 }
963
964 const std::vector<Curve2D> outerCurves = makeCurveWire(outerWire);
965 const std::vector<std::vector<Curve2D>> innerCurves = makeCurveWires(innerWires);
966
967 auto surface = std::make_unique<CurvedPlanarBoundedSurface>();
968 std::string errorMessage;
969 if (!surface->initialize(makeVec3(origin), makeVec3(axisU), makeVec3(axisV), outerCurves, innerCurves,
970 errorMessage, wireJoinToleranceFor(fModelTolerance))) {
971 Error("AddCurvedPlanarSurface", "%s", errorMessage.c_str());
972 return false;
973 }
974
975 auto record = makeRecord(BVHSurfaceRecord::CurvedPlanar, origin, axisU, axisV, {}, false, false);
976 storeCurveWires(record, outerWire, innerWires);
977 return fImpl->commit(std::move(surface), std::move(record), fRecords);
978}
979
980bool O2BVHSurfaceSolid::AddCylindricalSurface(const Point3D& centerPoint, const Point3D& axis,
981 const Point3D& referenceAxisU, double radius, double heightMin,
982 double heightMax, double phiStart, double phiSweep, bool innerWall)
983{
984 if (fImpl->refuseIfDefined(*this, "AddCylindricalSurface")) {
985 return false;
986 }
987
988 auto surface = std::make_unique<CylindricalBoundedSurface>();
989 std::string errorMessage;
990 if (!surface->initialize(makeVec3(centerPoint), makeVec3(axis), makeVec3(referenceAxisU), radius, heightMin,
991 heightMax, phiStart, phiSweep, innerWall, errorMessage)) {
992 Error("AddCylindricalSurface", "%s", errorMessage.c_str());
993 return false;
994 }
995
996 return fImpl->commit(std::move(surface),
997 makeRecord(BVHSurfaceRecord::Cylindrical, centerPoint, axis, referenceAxisU,
998 {radius, heightMin, heightMax, phiStart, phiSweep}, innerWall, false),
999 fRecords);
1000}
1001
1002bool O2BVHSurfaceSolid::AddCylindricalSurface(const Point3D& centerPoint, const Point3D& axis,
1003 const Point3D& referenceAxisU, double radius, double heightMin,
1004 double heightMax, double phiStart, double phiSweep, bool innerWall,
1005 const std::vector<PlanarBoundaryCurve>& outerTrim,
1006 const std::vector<std::vector<PlanarBoundaryCurve>>& innerTrims)
1007{
1008 if (fImpl->refuseIfDefined(*this, "AddCylindricalSurface")) {
1009 return false;
1010 }
1011
1012 const std::vector<Curve2D> outerCurves = makeCurveWire(outerTrim);
1013 const std::vector<std::vector<Curve2D>> innerCurves = makeCurveWires(innerTrims);
1014
1015 auto surface = std::make_unique<CylindricalBoundedSurface>();
1016 std::string errorMessage;
1017 if (!surface->initialize(makeVec3(centerPoint), makeVec3(axis), makeVec3(referenceAxisU), radius, heightMin,
1018 heightMax, phiStart, phiSweep, innerWall, outerCurves, innerCurves, errorMessage,
1019 wireJoinToleranceFor(fModelTolerance))) {
1020 Error("AddCylindricalSurface", "%s", errorMessage.c_str());
1021 return false;
1022 }
1023
1024 auto record = makeRecord(BVHSurfaceRecord::Cylindrical, centerPoint, axis, referenceAxisU,
1025 {radius, heightMin, heightMax, phiStart, phiSweep}, innerWall, true);
1026 storeCurveWires(record, outerTrim, innerTrims);
1027 return fImpl->commit(std::move(surface), std::move(record), fRecords);
1028}
1029
1031 const Point3D& referenceAxisU, double radius, double thetaMin,
1032 double thetaMax, double phiStart, double phiSweep, bool innerWall)
1033{
1034 if (fImpl->refuseIfDefined(*this, "AddSphericalSurface")) {
1035 return false;
1036 }
1037
1038 auto surface = std::make_unique<SphericalBoundedSurface>();
1039 std::string errorMessage;
1040 if (!surface->initialize(makeVec3(center), makeVec3(polarAxis), makeVec3(referenceAxisU), radius, thetaMin,
1041 thetaMax, phiStart, phiSweep, innerWall, errorMessage)) {
1042 Error("AddSphericalSurface", "%s", errorMessage.c_str());
1043 return false;
1044 }
1045
1046 return fImpl->commit(std::move(surface),
1047 makeRecord(BVHSurfaceRecord::Spherical, center, polarAxis, referenceAxisU,
1048 {radius, thetaMin, thetaMax, phiStart, phiSweep}, innerWall, false),
1049 fRecords);
1050}
1051
1053 const Point3D& referenceAxisU, double radius, double thetaMin,
1054 double thetaMax, double phiStart, double phiSweep, bool innerWall,
1055 const std::vector<PlanarBoundaryCurve>& outerTrim,
1056 const std::vector<std::vector<PlanarBoundaryCurve>>& innerTrims)
1057{
1058 if (fImpl->refuseIfDefined(*this, "AddSphericalSurface")) {
1059 return false;
1060 }
1061
1062 const std::vector<Curve2D> outerCurves = makeCurveWire(outerTrim);
1063 const std::vector<std::vector<Curve2D>> innerCurves = makeCurveWires(innerTrims);
1064
1065 auto surface = std::make_unique<SphericalBoundedSurface>();
1066 std::string errorMessage;
1067 if (!surface->initialize(makeVec3(center), makeVec3(polarAxis), makeVec3(referenceAxisU), radius, thetaMin,
1068 thetaMax, phiStart, phiSweep, innerWall, outerCurves, innerCurves, errorMessage,
1069 wireJoinToleranceFor(fModelTolerance))) {
1070 Error("AddSphericalSurface", "%s", errorMessage.c_str());
1071 return false;
1072 }
1073
1074 auto record = makeRecord(BVHSurfaceRecord::Spherical, center, polarAxis, referenceAxisU,
1075 {radius, thetaMin, thetaMax, phiStart, phiSweep}, innerWall, true);
1076 storeCurveWires(record, outerTrim, innerTrims);
1077 return fImpl->commit(std::move(surface), std::move(record), fRecords);
1078}
1079
1080bool O2BVHSurfaceSolid::AddConicalSurface(const Point3D& centerPoint, const Point3D& axis,
1081 const Point3D& referenceAxisU, double radiusAtMin, double radiusAtMax,
1082 double heightMin, double heightMax, double phiStart, double phiSweep,
1083 bool innerWall)
1084{
1085 if (fImpl->refuseIfDefined(*this, "AddConicalSurface")) {
1086 return false;
1087 }
1088
1089 auto surface = std::make_unique<ConicalBoundedSurface>();
1090 std::string errorMessage;
1091 if (!surface->initialize(makeVec3(centerPoint), makeVec3(axis), makeVec3(referenceAxisU), radiusAtMin,
1092 radiusAtMax, heightMin, heightMax, phiStart, phiSweep, innerWall, errorMessage)) {
1093 Error("AddConicalSurface", "%s", errorMessage.c_str());
1094 return false;
1095 }
1096
1097 return fImpl->commit(std::move(surface),
1098 makeRecord(BVHSurfaceRecord::Conical, centerPoint, axis, referenceAxisU,
1099 {radiusAtMin, radiusAtMax, heightMin, heightMax, phiStart, phiSweep}, innerWall,
1100 false),
1101 fRecords);
1102}
1103
1104bool O2BVHSurfaceSolid::AddConicalSurface(const Point3D& centerPoint, const Point3D& axis,
1105 const Point3D& referenceAxisU, double radiusAtMin, double radiusAtMax,
1106 double heightMin, double heightMax, double phiStart, double phiSweep,
1107 bool innerWall, const std::vector<PlanarBoundaryCurve>& outerTrim,
1108 const std::vector<std::vector<PlanarBoundaryCurve>>& innerTrims)
1109{
1110 if (fImpl->refuseIfDefined(*this, "AddConicalSurface")) {
1111 return false;
1112 }
1113
1114 const std::vector<Curve2D> outerCurves = makeCurveWire(outerTrim);
1115 const std::vector<std::vector<Curve2D>> innerCurves = makeCurveWires(innerTrims);
1116
1117 auto surface = std::make_unique<ConicalBoundedSurface>();
1118 std::string errorMessage;
1119 if (!surface->initialize(makeVec3(centerPoint), makeVec3(axis), makeVec3(referenceAxisU), radiusAtMin,
1120 radiusAtMax, heightMin, heightMax, phiStart, phiSweep, innerWall, outerCurves,
1121 innerCurves, errorMessage, wireJoinToleranceFor(fModelTolerance))) {
1122 Error("AddConicalSurface", "%s", errorMessage.c_str());
1123 return false;
1124 }
1125
1126 auto record = makeRecord(BVHSurfaceRecord::Conical, centerPoint, axis, referenceAxisU,
1127 {radiusAtMin, radiusAtMax, heightMin, heightMax, phiStart, phiSweep}, innerWall, true);
1128 storeCurveWires(record, outerTrim, innerTrims);
1129 return fImpl->commit(std::move(surface), std::move(record), fRecords);
1130}
1131
1132bool O2BVHSurfaceSolid::AddToroidalSurface(const Point3D& centerPoint, const Point3D& axis,
1133 const Point3D& referenceAxisU, double majorRadius, double minorRadius,
1134 double phiStart, double phiSweep, double tubeStart, double tubeSweep,
1135 bool innerWall)
1136{
1137 if (fImpl->refuseIfDefined(*this, "AddToroidalSurface")) {
1138 return false;
1139 }
1140
1141 auto surface = std::make_unique<TorusBoundedSurface>();
1142 std::string errorMessage;
1143 if (!surface->initialize(makeVec3(centerPoint), makeVec3(axis), makeVec3(referenceAxisU), majorRadius, minorRadius,
1144 phiStart, phiSweep, tubeStart, tubeSweep, innerWall, errorMessage)) {
1145 Error("AddToroidalSurface", "%s", errorMessage.c_str());
1146 return false;
1147 }
1148
1149 return fImpl->commit(std::move(surface),
1150 makeRecord(BVHSurfaceRecord::Toroidal, centerPoint, axis, referenceAxisU,
1151 {majorRadius, minorRadius, phiStart, phiSweep, tubeStart, tubeSweep}, innerWall,
1152 false),
1153 fRecords);
1154}
1155
1156bool O2BVHSurfaceSolid::AddToroidalSurface(const Point3D& centerPoint, const Point3D& axis,
1157 const Point3D& referenceAxisU, double majorRadius, double minorRadius,
1158 double phiStart, double phiSweep, double tubeStart, double tubeSweep,
1159 bool innerWall, const std::vector<PlanarBoundaryCurve>& outerTrim,
1160 const std::vector<std::vector<PlanarBoundaryCurve>>& innerTrims)
1161{
1162 if (fImpl->refuseIfDefined(*this, "AddToroidalSurface")) {
1163 return false;
1164 }
1165
1166 const std::vector<Curve2D> outerCurves = makeCurveWire(outerTrim);
1167 const std::vector<std::vector<Curve2D>> innerCurves = makeCurveWires(innerTrims);
1168
1169 auto surface = std::make_unique<TorusBoundedSurface>();
1170 std::string errorMessage;
1171 if (!surface->initialize(makeVec3(centerPoint), makeVec3(axis), makeVec3(referenceAxisU), majorRadius, minorRadius,
1172 phiStart, phiSweep, tubeStart, tubeSweep, innerWall, outerCurves, innerCurves,
1173 errorMessage, wireJoinToleranceFor(fModelTolerance))) {
1174 Error("AddToroidalSurface", "%s", errorMessage.c_str());
1175 return false;
1176 }
1177
1178 auto record = makeRecord(BVHSurfaceRecord::Toroidal, centerPoint, axis, referenceAxisU,
1179 {majorRadius, minorRadius, phiStart, phiSweep, tubeStart, tubeSweep}, innerWall, true);
1180 storeCurveWires(record, outerTrim, innerTrims);
1181 return fImpl->commit(std::move(surface), std::move(record), fRecords);
1182}
1183
1185{
1186 // An empty surface set is unknown, not closed: leave it undefined (Undetermined) and keep the streamed bounding box.
1187 if (fImpl->surfaces.empty()) {
1188 Error("CloseShape", "Shape %s has no bounded surfaces; it stays undefined and reports itself not navigable",
1189 GetName());
1190 return;
1191 }
1192
1193 ComputeBBox();
1194
1195 // the display mesh feeds the safety anchors, so it is assembled before the BVH machinery
1196 fImpl->displayVertices.clear();
1197 fImpl->displayTriangles.clear();
1198 fImpl->displayTriangleSurface.clear();
1199 for (size_t surfaceIndex = 0; surfaceIndex < fImpl->surfaces.size(); ++surfaceIndex) {
1200 fImpl->surfaces[surfaceIndex]->appendDisplayMesh(fImpl->displayVertices, fImpl->displayTriangles);
1201 // resize() only writes the entries it adds, so each surface stamps exactly its own triangles.
1202 fImpl->displayTriangleSurface.resize(fImpl->displayTriangles.size(), static_cast<int>(surfaceIndex));
1203 }
1204 fImpl->collectSafetyAnchors();
1205 fImpl->buildBVH();
1206
1207 fImpl->closure = validateClosure(fImpl->surfaces, fModelTolerance);
1208 fImpl->rimReports.clear();
1209 fImpl->rimReports.reserve(fImpl->closure.rimRecords.size());
1210 for (const RimRecord& record : fImpl->closure.rimRecords) {
1212 report.surface = record.surfaceIndex;
1213 report.rimOnSurface = record.rimIndexOnSurface;
1214 report.closed = record.closed;
1215 report.chords = record.chords;
1216 report.unmatchedChords = record.unmatchedChords;
1217 report.length = record.length;
1218 report.unmatchedLength = record.unmatchedLength;
1219 report.maxIsolation = record.maxIsolation;
1220 report.maxIsolationPoint = {record.maxIsolationPoint.xCoord, record.maxIsolationPoint.yCoord,
1221 record.maxIsolationPoint.zCoord};
1222 report.maxIsolationFace = record.maxIsolationFace;
1223 report.state = rimStateToReliability(record.state);
1224 fImpl->rimReports.push_back(report);
1225 }
1226 fImpl->defined = true;
1228
1229 if (check) {
1230 const auto& closure = fImpl->closure;
1231 // State the consequence, not only the counts.
1232 if (closure.edgeIdentityAvailable && closure.boundaryRims > 0) {
1233 // counted by edge identity, so it says a face is missing
1234 Error("CloseShape",
1235 "Shape %s is NOT a closed surface: %d of its %d source edge(s) have only one face and %d more than two, "
1236 "leaving %d of %d trim loop(s) open; navigation is unreliable, see GetRimReports().",
1237 GetName(), closure.edgeBoundaryCount, closure.edgeIncidences, closure.edgeNonManifoldCount,
1238 closure.boundaryRims, closure.rims);
1239 } else if (closure.boundaryRims > 0) {
1240 Error("CloseShape",
1241 "Shape %s is NOT a closed surface: %d of %d trim loop(s) have no neighbouring face within %g cm, leaving "
1242 "%g cm of %g cm of boundary open (loneliest chord %g cm); navigation is unreliable, see GetRimReports().",
1243 GetName(), closure.boundaryRims, closure.rims, closure.rimEpsilon, closure.unmatchedRimLength,
1244 closure.totalRimLength, closure.maxRimIsolation);
1245 }
1246 if (closure.nonManifoldRims > 0) {
1247 Error("CloseShape",
1248 "Shape %s is NOT a 2-manifold: %d of %d trim loop(s) run along two or more other faces; navigation is "
1249 "unreliable, see GetRimReports().",
1250 GetName(), closure.nonManifoldRims, closure.rims);
1251 }
1252 if (!closure.orientationConsistent) {
1253 Error("CloseShape",
1254 "Shape %s has %d inconsistently oriented (reversed) trim loop(s); navigation is unreliable, see "
1255 "GetRimReports().",
1256 GetName(), closure.reversedRims);
1257 }
1258 if (closure.closed && closure.signedVolume < 0.) {
1259 Warning("CloseShape",
1260 "Shape %s has inward-pointing surface normals (signed volume %g); navigation expects outward normals",
1261 GetName(), closure.signedVolume);
1262 }
1263 }
1264}
1265
1267{
1268 return static_cast<int>(fImpl->surfaces.size());
1269}
1270
1272{
1273 return fImpl->defined;
1274}
1275
1277{
1278 if (!(toleranceCm >= 0.) || !std::isfinite(toleranceCm)) {
1279 Error("SetModelTolerance", "Shape %s: ignoring a non-finite or negative model tolerance %g; it stays %g",
1280 GetName(), toleranceCm, fModelTolerance);
1281 return;
1282 }
1283 fModelTolerance = toleranceCm;
1284}
1285
1287{
1288 return fImpl->bvh != nullptr;
1289}
1290
1292{
1293 if (!HasBVH()) {
1294 return false;
1295 }
1296 const auto rootBox = fImpl->bvh->get_root().get_bbox();
1297 for (int dimension = 0; dimension < 3; ++dimension) {
1298 lower[dimension] = rootBox.min[dimension];
1299 upper[dimension] = rootBox.max[dimension];
1300 }
1301 return true;
1302}
1303
1304int O2BVHSurfaceSolid::CountBVHRayCandidates(const Point3D& point, const Point3D& direction) const
1305{
1306 if (!HasBVH()) {
1307 return -1;
1308 }
1309 int candidates = 0;
1310 fImpl->visitRayCandidates(makeVec3(point), makeVec3(direction), [&](const BoundedSurface&) { ++candidates; });
1311 return candidates;
1312}
1313
1315{
1316 return fImpl->defined && fImpl->closure.closed;
1317}
1318
1320{
1321 return fImpl->defined && fImpl->closure.orientationConsistent;
1322}
1323
1325{
1326 if (!fImpl->defined) {
1328 }
1329 // the worst defect wins; the enum is ordered by severity
1330 const auto& closure = fImpl->closure;
1331 // With edge identities their counts are the verdict, read directly so that faces without rims still report.
1332 if (closure.edgeIdentityAvailable) {
1333 if (closure.edgeNonManifoldCount > 0) {
1335 }
1336 if (closure.edgeBoundaryCount > 0) {
1338 }
1339 if (closure.edgeReversedCount > 0) {
1341 }
1343 }
1344 if (closure.nonManifoldRims > 0) {
1346 }
1347 if (closure.boundaryRims > 0) {
1349 }
1350 if (closure.reversedRims > 0) {
1352 }
1354}
1355
1360
1362{
1363 switch (reliability) {
1365 return "undetermined";
1367 return "reliable";
1369 return "reversed-faces";
1371 return "open-surface-set";
1373 return "non-manifold";
1374 }
1375 return "unknown";
1376}
1377
1379{
1380 return fImpl->closure.boundaryEdges;
1381}
1382
1384{
1385 return fImpl->closure.nonManifoldEdges;
1386}
1387
1389{
1390 return fImpl->closure.reversedEdges;
1391}
1392
1394{
1395 return fImpl->closure.maxRimIsolation;
1396}
1397
1398bool O2BVHSurfaceSolid::SetSurfaceBoundaryEdges(int surfaceIndex, const std::vector<unsigned int>& edgeIds,
1399 const std::vector<unsigned char>& edgeFlags)
1400{
1401 if (surfaceIndex < 0 || surfaceIndex >= static_cast<int>(fImpl->surfaces.size()) ||
1402 surfaceIndex >= static_cast<int>(fRecords.size())) {
1403 Error("SetSurfaceBoundaryEdges", "Shape %s: surface index %d is out of range (%d surface(s))", GetName(),
1404 surfaceIndex, GetNsurfaces());
1405 return false;
1406 }
1407 if (edgeIds.size() != edgeFlags.size()) {
1408 Error("SetSurfaceBoundaryEdges", "Shape %s: surface %d was given %d edge id(s) and %d flag(s)", GetName(),
1409 surfaceIndex, static_cast<int>(edgeIds.size()), static_cast<int>(edgeFlags.size()));
1410 return false;
1411 }
1412 std::vector<BoundedSurface::BoundaryEdgeRef> refs;
1413 refs.reserve(edgeIds.size());
1414 for (size_t index = 0; index < edgeIds.size(); ++index) {
1416 ref.edgeId = edgeIds[index];
1417 ref.reversed = (edgeFlags[index] & kEdgeReversed) != 0;
1418 ref.degenerate = (edgeFlags[index] & kEdgeDegenerate) != 0;
1419 ref.anchored = (edgeFlags[index] & kEdgeAnchored) != 0;
1420 refs.push_back(ref);
1421 }
1422 fImpl->surfaces[static_cast<size_t>(surfaceIndex)]->setBoundaryEdges(std::move(refs));
1423 fRecords[static_cast<size_t>(surfaceIndex)].boundaryEdgeIds = edgeIds;
1424 fRecords[static_cast<size_t>(surfaceIndex)].boundaryEdgeFlags = edgeFlags;
1425 return true;
1426}
1427
1429{
1430 return fImpl->closure.edgeIdentityAvailable;
1431}
1432
1434{
1435 return fImpl->closure.edgeIncidences;
1436}
1437
1439{
1440 return fImpl->closure.edgeSharedCount;
1441}
1442
1447
1452
1457
1462
1467
1472
1477
1479{
1480 return fImpl->closure.rimChordResolution;
1481}
1482
1484{
1485 return fImpl->closure.rimEpsilon;
1486}
1487
1489{
1490 return fImpl->closure.totalRimLength;
1491}
1492
1494{
1495 return fImpl->closure.unmatchedRimLength;
1496}
1497
1499{
1500 return fImpl->closure.rims;
1501}
1502
1504{
1505 return fImpl->closure.matchedRims;
1506}
1507
1509{
1510 return fImpl->closure.boundaryRims;
1511}
1512
1514{
1515 return fImpl->closure.nonManifoldRims;
1516}
1517
1519{
1520 return fImpl->closure.reversedRims;
1521}
1522
1523const std::vector<O2BVHSurfaceSolid::RimReport>& O2BVHSurfaceSolid::GetRimReports() const
1524{
1525 return fImpl->rimReports;
1526}
1527
1528void O2BVHSurfaceSolid::GetSurfaceCapacityContributions(std::vector<double>& contributions) const
1529{
1530 contributions.clear();
1531 contributions.reserve(fImpl->surfaces.size());
1532 for (const auto& surface : fImpl->surfaces) {
1533 contributions.push_back(surface == nullptr ? 0. : surface->capacityContribution());
1534 }
1535}
1536
1538{
1539 if (fImpl->surfaces.empty()) {
1540 fDX = fDY = fDZ = 0.;
1541 fOrigin[0] = fOrigin[1] = fOrigin[2] = 0.;
1542 return;
1543 }
1544
1545 Vec3 lowerCorner{TGeoShape::Big(), TGeoShape::Big(), TGeoShape::Big()};
1546 Vec3 upperCorner{-TGeoShape::Big(), -TGeoShape::Big(), -TGeoShape::Big()};
1547 for (const auto& surface : fImpl->surfaces) {
1548 surface->conservativeBounds(lowerCorner, upperCorner);
1549 }
1550
1551 for (int dimension = 0; dimension < 3; ++dimension) {
1552 const double lowerValue = component(lowerCorner, dimension) - kTolerance;
1553 const double upperValue = component(upperCorner, dimension) + kTolerance;
1554 fOrigin[dimension] = 0.5 * (lowerValue + upperValue);
1555 const double halfLength = 0.5 * (upperValue - lowerValue);
1556 if (dimension == 0) {
1557 fDX = halfLength;
1558 } else if (dimension == 1) {
1559 fDY = halfLength;
1560 } else {
1561 fDZ = halfLength;
1562 }
1563 }
1564}
1565
1566void O2BVHSurfaceSolid::GetMeshNumbers(int& nvert, int& nsegs, int& npols) const
1567{
1568 nvert = GetNmeshVertices();
1569 npols = static_cast<int>(fImpl->displayTriangles.size());
1570 nsegs = 3 * npols;
1571}
1572
1574{
1575 return static_cast<int>(fImpl->displayVertices.size());
1576}
1577
1578namespace
1579{
1581void r2Pair(long long index, double& firstCoordinate, double& secondCoordinate)
1582{
1583 constexpr double kAlpha1 = 0.7548776662466927; // 1 / plastic number
1584 constexpr double kAlpha2 = 0.5698402909980532; // 1 / plastic number^2
1585 const double shifted = static_cast<double>(index + 1);
1586 firstCoordinate = std::fmod(0.5 + kAlpha1 * shifted, 1.);
1587 secondCoordinate = std::fmod(0.5 + kAlpha2 * shifted, 1.);
1588}
1589} // namespace
1590
1592bool O2BVHSurfaceSolid::ProjectOntoPatch(int surfaceIndex, double* point) const
1593{
1594 if (surfaceIndex < 0 || static_cast<size_t>(surfaceIndex) >= fImpl->surfaces.size()) {
1595 return false;
1596 }
1597 const BoundedSurface& surface = *fImpl->surfaces[surfaceIndex];
1598 constexpr double kToleranceSquared = kSurfacePointTolerance * kSurfacePointTolerance;
1599
1600 Vec3 current = makeVec3(point);
1601 double currentDistanceSq = surface.distanceSqToPatch(current);
1602 for (int iteration = 0; iteration < 8 && currentDistanceSq > kToleranceSquared; ++iteration) {
1603 const double distance = std::sqrt(currentDistanceSq);
1604 const Vec3 normal = surface.normalAt(current);
1605 const Vec3 inward{current.xCoord - distance * normal.xCoord, current.yCoord - distance * normal.yCoord,
1606 current.zCoord - distance * normal.zCoord};
1607 const Vec3 outward{current.xCoord + distance * normal.xCoord, current.yCoord + distance * normal.yCoord,
1608 current.zCoord + distance * normal.zCoord};
1609 const double inwardDistanceSq = surface.distanceSqToPatch(inward);
1610 const double outwardDistanceSq = surface.distanceSqToPatch(outward);
1611 const double bestDistanceSq = std::min(inwardDistanceSq, outwardDistanceSq);
1612 // not converging: the nearest patch point lies on the trim wire
1613 if (!(bestDistanceSq < currentDistanceSq)) {
1614 return false;
1615 }
1616 current = (inwardDistanceSq < outwardDistanceSq) ? inward : outward;
1617 currentDistanceSq = bestDistanceSq;
1618 }
1619
1620 if (currentDistanceSq > kToleranceSquared) {
1621 return false;
1622 }
1623 point[0] = current.xCoord;
1624 point[1] = current.yCoord;
1625 point[2] = current.zCoord;
1626 return true;
1627}
1628
1629Bool_t O2BVHSurfaceSolid::GetPointsOnSegments(Int_t npoints, Double_t* array) const
1630{
1631 if (array == nullptr || npoints <= 0 || fImpl->displayVertices.empty()) {
1632 return kFALSE;
1633 }
1634 const int vertexCount = static_cast<int>(fImpl->displayVertices.size());
1635 // Below the mesh size, decline so ROOT uses SetPoints(), whose vertices all lie on patches.
1636 if (npoints < vertexCount) {
1637 return kFALSE;
1638 }
1639
1640 auto writeVertex = [&](int slot, const Vec3& vertex) {
1641 array[3 * slot + 0] = vertex.xCoord;
1642 array[3 * slot + 1] = vertex.yCoord;
1643 array[3 * slot + 2] = vertex.zCoord;
1644 };
1645
1646 for (int vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) {
1647 writeVertex(vertexIndex, fImpl->displayVertices[vertexIndex]);
1648 }
1649
1650 const int extraCount = npoints - vertexCount;
1651 const int triangleCount = static_cast<int>(fImpl->displayTriangles.size());
1652 if (extraCount == 0) {
1653 return kTRUE;
1654 }
1655 if (triangleCount == 0 || fImpl->displayTriangleSurface.size() != fImpl->displayTriangles.size()) {
1656 // No triangles (or a mesh built before the provenance existed): repeat vertices rather than
1657 // leave the tail of the buffer uninitialised, which the caller would read as coordinates.
1658 for (int extraIndex = 0; extraIndex < extraCount; ++extraIndex) {
1659 writeVertex(vertexCount + extraIndex, fImpl->displayVertices[extraIndex % vertexCount]);
1660 }
1661 return kTRUE;
1662 }
1663
1664 for (int extraIndex = 0; extraIndex < extraCount; ++extraIndex) {
1665 // Stride over the triangles rather than walking them in order, so a request that cannot cover
1666 // every triangle still spreads over the whole solid instead of over its first few faces.
1667 const int triangleIndex =
1668 static_cast<int>((static_cast<long long>(extraIndex) * triangleCount) / extraCount) % triangleCount;
1669 const auto& triangle = fImpl->displayTriangles[triangleIndex];
1670 const Vec3& cornerA = fImpl->displayVertices[triangle[0]];
1671 const Vec3& cornerB = fImpl->displayVertices[triangle[1]];
1672 const Vec3& cornerC = fImpl->displayVertices[triangle[2]];
1673
1674 double firstCoordinate = 0.;
1675 double secondCoordinate = 0.;
1676 r2Pair(extraIndex, firstCoordinate, secondCoordinate);
1677 if (firstCoordinate + secondCoordinate > 1.) {
1678 firstCoordinate = 1. - firstCoordinate;
1679 secondCoordinate = 1. - secondCoordinate;
1680 }
1681 const double weightA = 1. - firstCoordinate - secondCoordinate;
1682 double candidate[3] = {weightA * cornerA.xCoord + firstCoordinate * cornerB.xCoord + secondCoordinate * cornerC.xCoord,
1683 weightA * cornerA.yCoord + firstCoordinate * cornerB.yCoord + secondCoordinate * cornerC.yCoord,
1684 weightA * cornerA.zCoord + firstCoordinate * cornerB.zCoord + secondCoordinate * cornerC.zCoord};
1685
1686 if (!ProjectOntoPatch(fImpl->displayTriangleSurface[triangleIndex], candidate)) {
1687 // fall back to a vertex of the sampled triangle, which is on the patch
1688 candidate[0] = cornerA.xCoord;
1689 candidate[1] = cornerA.yCoord;
1690 candidate[2] = cornerA.zCoord;
1691 }
1692 array[3 * (vertexCount + extraIndex) + 0] = candidate[0];
1693 array[3 * (vertexCount + extraIndex) + 1] = candidate[1];
1694 array[3 * (vertexCount + extraIndex) + 2] = candidate[2];
1695 }
1696 return kTRUE;
1697}
1698
1700{
1701 int nvert = 0;
1702 int nsegs = 0;
1703 int npols = 0;
1704 GetMeshNumbers(nvert, nsegs, npols);
1705 auto buff = new TBuffer3D(TBuffer3DTypes::kGeneric, nvert, 3 * nvert, nsegs, 3 * nsegs, npols, 6 * npols);
1706 if (buff != nullptr) {
1707 SetPoints(buff->fPnts);
1708 SetSegsAndPols(*buff);
1709 }
1710 return buff;
1711}
1712
1713void O2BVHSurfaceSolid::Print(Option_t*) const
1714{
1715 std::cout << "=== BVH surface solid " << GetName() << " having " << GetNsurfaces() << " bounded surfaces\n";
1716 const auto reliability = GetNavigationReliability();
1717 std::cout << " navigation: " << GetNavigationReliabilityName(reliability);
1718 if (reliability != NavigationReliability::Reliable && reliability != NavigationReliability::Undetermined) {
1719 std::cout << " (UNRELIABLE; boundary=" << GetBoundaryEdgeCount() << " non-manifold=" << GetNonManifoldEdgeCount()
1720 << " reversed=" << GetReversedEdgeCount() << ")";
1721 }
1722 std::cout << "\n model tolerance: ";
1723 if (fModelTolerance > 0.) {
1724 std::cout << fModelTolerance << " cm (from the source model)";
1725 } else {
1726 std::cout << "not stated";
1727 }
1728 // the identity counts first: when present they are the verdict
1729 if (HasEdgeIdentity()) {
1730 std::cout << "\n edge identity: " << GetSourceEdgeCount() << " source edge(s), shared=" << GetSharedSourceEdgeCount()
1731 << " boundary=" << GetBoundarySourceEdgeCount() << " non-manifold=" << GetNonManifoldSourceEdgeCount()
1732 << " reversed=" << GetReversedSourceEdgeCount() << " degenerate=" << GetDegenerateSourceEdgeCount()
1733 << "\n shared edge deviation: max " << GetMaxSharedEdgeDeviation() << " cm over "
1734 << GetMeasuredSharedEdgeCount() << " measured edge(s)";
1735 if (GetUnmeasuredSharedEdgeCount() > 0) {
1736 std::cout << " (" << GetUnmeasuredSharedEdgeCount() << " not measurable: parametric-rectangle trim)";
1737 }
1738 }
1739 // The isolation, and the resolution that widened the band it was judged in, always together: the
1740 // number is how alone the loneliest chord is, not how far apart two faces are.
1741 if (GetRimCount() > 0) {
1742 std::cout << "\n rim isolation: max " << GetMaxRimIsolation() << " cm (chord resolution "
1743 << GetRimChordResolution() << " cm, declared tolerance " << GetRimMatchTolerance() << " cm)"
1744 << "\n rims: " << GetRimCount() << " (matched=" << GetMatchedRimCount()
1745 << " boundary=" << GetBoundaryRimCount() << " non-manifold=" << GetNonManifoldRimCount()
1746 << " reversed=" << GetReversedRimCount() << "), open " << GetUnmatchedRimLength() << " of "
1747 << GetTotalRimLength() << " cm";
1748 }
1749 std::cout << "\n";
1750}
1751
1752void O2BVHSurfaceSolid::SetPoints(double* points) const
1753{
1754 int coordinateIndex = 0;
1755 for (const auto& vertex : fImpl->displayVertices) {
1756 points[coordinateIndex++] = vertex.xCoord;
1757 points[coordinateIndex++] = vertex.yCoord;
1758 points[coordinateIndex++] = vertex.zCoord;
1759 }
1760}
1761
1763{
1764 int coordinateIndex = 0;
1765 for (const auto& vertex : fImpl->displayVertices) {
1766 points[coordinateIndex++] = vertex.xCoord;
1767 points[coordinateIndex++] = vertex.yCoord;
1768 points[coordinateIndex++] = vertex.zCoord;
1769 }
1770}
1771
1772void O2BVHSurfaceSolid::SetSegsAndPols(TBuffer3D& buff) const
1773{
1774 const int color = GetBasicColor();
1775 int* segs = buff.fSegs;
1776 int* pols = buff.fPols;
1777 int segmentDataIndex = 0;
1778 int polygonDataIndex = 0;
1779 int segmentIndex = 0;
1780 for (const auto& triangle : fImpl->displayTriangles) {
1781 pols[polygonDataIndex++] = color;
1782 pols[polygonDataIndex++] = 3;
1783 for (int triangleEdge = 0; triangleEdge < 3; ++triangleEdge) {
1784 const int nextTriangleEdge = (triangleEdge + 1) % 3;
1785 segs[segmentDataIndex++] = color;
1786 segs[segmentDataIndex++] = triangle[triangleEdge];
1787 segs[segmentDataIndex++] = triangle[nextTriangleEdge];
1788 pols[polygonDataIndex + 2 - triangleEdge] = segmentIndex++;
1789 }
1790 polygonDataIndex += 3;
1791 }
1792}
1793
1794const TBuffer3D& O2BVHSurfaceSolid::GetBuffer3D(int reqSections, Bool_t localFrame) const
1795{
1796 static TBuffer3D buffer(TBuffer3DTypes::kGeneric);
1797
1798 FillBuffer3D(buffer, reqSections, localFrame);
1799
1800 int nvert = 0;
1801 int nsegs = 0;
1802 int npols = 0;
1803 GetMeshNumbers(nvert, nsegs, npols);
1804
1805 if (reqSections & TBuffer3D::kRawSizes) {
1806 if (buffer.SetRawSizes(nvert, 3 * nvert, nsegs, 3 * nsegs, npols, 6 * npols)) {
1807 buffer.SetSectionsValid(TBuffer3D::kRawSizes);
1808 }
1809 }
1810 if ((reqSections & TBuffer3D::kRaw) && buffer.SectionsValid(TBuffer3D::kRawSizes)) {
1811 SetPoints(buffer.fPnts);
1812 if (!buffer.fLocalFrame) {
1813 TransformPoints(buffer.fPnts, buffer.NbPnts());
1814 }
1816 buffer.SetSectionsValid(TBuffer3D::kRaw);
1817 }
1818
1819 return buffer;
1820}
1821
1822bool O2BVHSurfaceSolid::Contains(const Double_t* point) const
1823{
1824 if (fImpl->surfaces.empty()) {
1825 return false;
1826 }
1827
1828 if (fImpl->bvh == nullptr) {
1829 // Before CloseShape there is no BVH and no bounding box, so this fallback must come before the box check.
1830 return Contains_Loop(point);
1831 }
1832
1833 const Vec3 testPoint = makeVec3(point);
1834 if (std::abs(testPoint.xCoord - fOrigin[0]) > fDX + kTolerance ||
1835 std::abs(testPoint.yCoord - fOrigin[1]) > fDY + kTolerance ||
1836 std::abs(testPoint.zCoord - fOrigin[2]) > fDZ + kTolerance) {
1837 return false;
1838 }
1839
1840 // boundary policy: a point within tolerance of any surface patch counts as inside
1841 if (fImpl->visitPointCandidates(
1842 testPoint, [&](const BoundedSurface& surface) { return surface.containsPointOnSurface(testPoint); })) {
1843 return true;
1844 }
1845
1846 return containsByParity(point, true);
1847}
1848
1849bool O2BVHSurfaceSolid::ContainsAlongDirection(const Double_t* point, const Double_t* direction) const
1850{
1851 if (fImpl->surfaces.empty()) {
1852 return false;
1853 }
1854 const Vec3 testPoint = makeVec3(point);
1855 for (const auto& surface : fImpl->surfaces) {
1856 if (surface->containsPointOnSurface(testPoint)) {
1857 return true;
1858 }
1859 }
1860 return fImpl->parityAlong(testPoint, normalized(makeVec3(direction)), fImpl->bvh != nullptr);
1861}
1862
1863bool O2BVHSurfaceSolid::Contains_Loop(const Double_t* point) const
1864{
1865 if (fImpl->surfaces.empty()) {
1866 return false;
1867 }
1868
1869 const Vec3 testPoint = makeVec3(point);
1870 for (const auto& surface : fImpl->surfaces) {
1871 if (surface->containsPointOnSurface(testPoint)) {
1872 return true;
1873 }
1874 }
1875
1876 return containsByParity(point, false);
1877}
1878
1879bool O2BVHSurfaceSolid::containsByParity(const Double_t* point, bool useBVH) const
1880{
1881 // Reliable solid: one parity shot, unless it rests on a trim-band tie-break; otherwise a 5-direction vote.
1882 const Vec3 testPoint = makeVec3(point);
1883 if (fImpl->reliable) {
1884 bool ambiguous = false;
1885 const bool answer = fImpl->parityAlong(testPoint, kContainsTestDirection, useBVH, &ambiguous);
1886 if (!ambiguous) {
1887 return answer;
1888 }
1889 // This shot crossed a patch within its own trim accuracy, so its parity rests on a tie-break
1890 // rather than on the geometry. Re-aim: the sliver belongs to the ray, not to the point.
1891 return fImpl->containsByVote(testPoint, useBVH);
1892 }
1893 return fImpl->containsByVote(testPoint, useBVH);
1894}
1895
1897 std::vector<ContainsCrossing>& bvhCrossings,
1898 std::vector<ContainsCrossing>& loopCrossings) const
1899{
1900 const Point3D direction{kContainsTestDirection.xCoord, kContainsTestDirection.yCoord,
1901 kContainsTestDirection.zCoord};
1902 DescribeContainsCrossings(point, direction, bvhCrossings, loopCrossings);
1903}
1904
1906 std::vector<ContainsCrossing>& bvhCrossings,
1907 std::vector<ContainsCrossing>& loopCrossings) const
1908{
1909 bvhCrossings.clear();
1910 loopCrossings.clear();
1911 if (fImpl->surfaces.empty()) {
1912 return;
1913 }
1914 const Vec3 testPoint = makeVec3(point.data());
1915 const Vec3 testDirection = normalized(makeVec3(direction.data()));
1916
1917 auto collect = [&](std::vector<RayHit>& hits, std::vector<ContainsCrossing>& out) {
1918 std::sort(hits.begin(), hits.end(),
1919 [](const RayHit& first, const RayHit& second) { return first.distance < second.distance; });
1920 out.reserve(hits.size());
1921 for (const auto& hit : hits) {
1922 out.push_back({hit.distance, dot(hit.normal, testDirection), hit.onTrimBoundary});
1923 }
1924 };
1925
1926 std::vector<RayHit> loopHits;
1927 for (const auto& surface : fImpl->surfaces) {
1928 surface->appendIntersections(testPoint, testDirection, kRayTolerance, TGeoShape::Big(), loopHits);
1929 }
1930 collect(loopHits, loopCrossings);
1931
1932 if (fImpl->bvh != nullptr) {
1933 std::vector<RayHit> bvhHits;
1934 fImpl->visitRayCandidates(testPoint, testDirection, [&](const BoundedSurface& surface) {
1935 surface.appendIntersections(testPoint, testDirection, kRayTolerance, TGeoShape::Big(), bvhHits);
1936 });
1937 collect(bvhHits, bvhCrossings);
1938 }
1939}
1940
1941Double_t O2BVHSurfaceSolid::DistFromOutside(const Double_t* point, const Double_t* dir, Int_t iact, Double_t stepmax,
1942 Double_t* safe) const
1943{
1944 if (iact < 3 && safe != nullptr) {
1945 *safe = Safety(point, kFALSE);
1946 if (iact == 0) {
1947 return TGeoShape::Big();
1948 }
1949 if (iact == 1 && stepmax < *safe) {
1950 return TGeoShape::Big();
1951 }
1952 }
1953 if (fImpl->surfaces.empty()) {
1954 return TGeoShape::Big();
1955 }
1956 if (fImpl->bvh == nullptr) {
1957 // before CloseShape there is no acceleration structure yet; stay usable via the plain loop
1958 return DistFromOutside_Loop(point, dir, stepmax);
1959 }
1960
1961 // cheap reject: a per-axis gap to the bounding box beyond stepmax means no reachable crossing
1962 const Double_t halfLengths[3] = {fDX, fDY, fDZ};
1963 for (int dimension = 0; dimension < 3; ++dimension) {
1964 const Double_t lower = fOrigin[dimension] - halfLengths[dimension];
1965 const Double_t upper = fOrigin[dimension] + halfLengths[dimension];
1966 if (lower - point[dimension] > stepmax + kBVHBoxTolerance ||
1967 point[dimension] - upper > stepmax + kBVHBoxTolerance) {
1968 return TGeoShape::Big();
1969 }
1970 }
1971
1972 return fImpl->nearestCrossing<true>(makeVec3(point), makeVec3(dir), stepmax);
1973}
1974
1975Double_t O2BVHSurfaceSolid::DistFromInside(const Double_t* point, const Double_t* dir, Int_t iact, Double_t stepmax,
1976 Double_t* safe) const
1977{
1978 if (iact < 3 && safe != nullptr) {
1979 *safe = Safety(point, kTRUE);
1980 if (iact == 0) {
1981 return TGeoShape::Big();
1982 }
1983 if (iact == 1 && stepmax < *safe) {
1984 return TGeoShape::Big();
1985 }
1986 }
1987 if (fImpl->surfaces.empty()) {
1988 return TGeoShape::Big();
1989 }
1990 if (fImpl->bvh == nullptr) {
1991 return DistFromInside_Loop(point, dir, stepmax);
1992 }
1993 // no bounding-box reject here: the point is inside by contract, so the box is always reachable
1994 return fImpl->nearestCrossing<false>(makeVec3(point), makeVec3(dir), stepmax);
1995}
1996
1997Double_t O2BVHSurfaceSolid::DistFromOutside_Loop(const Double_t* point, const Double_t* dir, Double_t stepmax) const
1998{
1999 if (fImpl->surfaces.empty()) {
2000 return TGeoShape::Big();
2001 }
2002 return fImpl->nearestCrossingLoop<true>(makeVec3(point), makeVec3(dir), stepmax);
2003}
2004
2005Double_t O2BVHSurfaceSolid::DistFromInside_Loop(const Double_t* point, const Double_t* dir, Double_t stepmax) const
2006{
2007 if (fImpl->surfaces.empty()) {
2008 return TGeoShape::Big();
2009 }
2010 return fImpl->nearestCrossingLoop<false>(makeVec3(point), makeVec3(dir), stepmax);
2011}
2012
2014{
2015 gRayTMaxPruning = enable;
2016}
2017
2019{
2020 return gRayTMaxPruning;
2021}
2022
2024{
2025 gRayCandidateCount = 0;
2026}
2027
2029{
2030 return gRayCandidateCount;
2031}
2032
2034{
2035 gSafetyCandidateCount = 0;
2036}
2037
2039{
2040 return gSafetyCandidateCount;
2041}
2042
2044{
2045 gSafetyBoundUnsound = enable;
2046}
2047
2049{
2050 return gSafetyBoundUnsound;
2051}
2052
2054Double_t O2BVHSurfaceSolid::Safety(const Double_t* point, Bool_t) const
2055{
2056 if (fImpl->surfaces.empty()) {
2057 return TGeoShape::Big();
2058 }
2059 const double bestDistanceSq = fImpl->nearestPatchDistanceSq<false>(makeVec3(point), nullptr);
2060 return std::nextafter(std::sqrt(bestDistanceSq), 0.);
2061}
2062
2063Double_t O2BVHSurfaceSolid::Safety_Loop(const Double_t* point, Bool_t) const
2064{
2065 if (fImpl->surfaces.empty()) {
2066 return TGeoShape::Big();
2067 }
2068 const double bestDistanceSq = fImpl->nearestPatchDistanceSqLoop(makeVec3(point), nullptr);
2069 return std::nextafter(std::sqrt(bestDistanceSq), 0.);
2070}
2071
2072void O2BVHSurfaceSolid::ComputeNormal(const Double_t* point, const Double_t* dir, Double_t* norm) const
2073{
2074 computeNormalFrom(point, dir, norm, false);
2075}
2076
2077void O2BVHSurfaceSolid::ComputeNormal_Loop(const Double_t* point, const Double_t* dir, Double_t* norm) const
2078{
2079 computeNormalFrom(point, dir, norm, true);
2080}
2081
2082void O2BVHSurfaceSolid::computeNormalFrom(const Double_t* point, const Double_t* dir, Double_t* norm,
2083 bool useLoop) const
2084{
2085 if (fImpl->surfaces.empty()) {
2086 norm[0] = 1.;
2087 norm[1] = 0.;
2088 norm[2] = 0.;
2089 return;
2090 }
2091
2092 const Vec3 testPoint = makeVec3(point);
2093 size_t closestIndex = fImpl->surfaces.size();
2094 if (useLoop) {
2095 fImpl->nearestPatchDistanceSqLoop(testPoint, &closestIndex);
2096 } else {
2097 fImpl->nearestPatchDistanceSq<true>(testPoint, &closestIndex);
2098 }
2099
2100 if (closestIndex >= fImpl->surfaces.size()) {
2101 norm[0] = 1.;
2102 norm[1] = 0.;
2103 norm[2] = 0.;
2104 return;
2105 }
2106
2107 Vec3 normal = fImpl->surfaces[closestIndex]->normalAt(testPoint);
2108 if (dir != nullptr) {
2109 const Vec3 direction = makeVec3(dir);
2110 if (dot(normal, direction) < 0.) {
2111 normal = normal * -1.;
2112 }
2113 }
2114 norm[0] = normal.xCoord;
2115 norm[1] = normal.yCoord;
2116 norm[2] = normal.zCoord;
2117}
2118
2120{
2121 double capacity = 0.;
2122 for (const auto& surface : fImpl->surfaces) {
2123 capacity += surface->capacityContribution();
2124 }
2125 return std::abs(capacity);
2126}
2127
2128bool O2BVHSurfaceSolid::RebuildFromRecords()
2129{
2130 // Add*Surface refuses to run on a defined shape and re-appends to fRecords as it replays, so
2131 // take the records aside and start from a fresh implementation.
2132 std::vector<BVHSurfaceRecord> records;
2133 records.swap(fRecords);
2134 delete fImpl;
2135 fImpl = new Impl;
2136 // a solid missing a face is a different solid, so a failed record discards the whole shape
2137 const auto discard = [this]() {
2138 fRecords.clear();
2139 delete fImpl;
2140 fImpl = new Impl;
2141 return false;
2142 };
2143
2144 if (records.empty()) {
2145 Error("RebuildFromRecords", "Shape %s carries no surface records, so it stays undefined and not navigable.",
2146 GetName());
2147 return false;
2148 }
2149
2150 for (size_t recordIndex = 0; recordIndex < records.size(); ++recordIndex) {
2151 const auto& record = records[recordIndex];
2152 const int expectedScalars = BVHSurfaceRecord::expectedScalarCount(record.kind);
2153 if (expectedScalars < 0 || record.scalars.size() != static_cast<size_t>(expectedScalars)) {
2154 Error("RebuildFromRecords", "Shape %s: surface record %d has kind %d with %d scalar(s), expected %d", GetName(),
2155 static_cast<int>(recordIndex), record.kind, static_cast<int>(record.scalars.size()), expectedScalars);
2156 return discard();
2157 }
2158
2159 const Point3D origin = makePoint3D(record.origin);
2160 const Point3D axisA = makePoint3D(record.axisA);
2161 const Point3D axisB = makePoint3D(record.axisB);
2162 const auto& s = record.scalars;
2163
2164 std::vector<PlanarBoundaryCurve> outerWire;
2165 std::vector<std::vector<PlanarBoundaryCurve>> innerWires;
2166 std::vector<Point2D> outerPolygon;
2167 std::vector<std::vector<Point2D>> innerPolygons;
2168 const bool wiresLoaded = record.kind == BVHSurfaceRecord::PlanarPolygon
2169 ? loadPolygonWires(record, outerPolygon, innerPolygons)
2170 : loadCurveWires(record, outerWire, innerWires);
2171
2172 bool added = false;
2173 if (!wiresLoaded) {
2174 Error("RebuildFromRecords", "Shape %s: surface record %d has inconsistent wire sizes", GetName(),
2175 static_cast<int>(recordIndex));
2176 } else {
2177 switch (record.kind) {
2179 added = AddPlanarSurface(origin, axisA, axisB, outerPolygon, innerPolygons);
2180 break;
2182 added = AddCurvedPlanarSurface(origin, axisA, axisB, outerWire, innerWires);
2183 break;
2185 added = record.trimmed ? AddCylindricalSurface(origin, axisA, axisB, s[0], s[1], s[2], s[3], s[4],
2186 record.innerWall, outerWire, innerWires)
2187 : AddCylindricalSurface(origin, axisA, axisB, s[0], s[1], s[2], s[3], s[4],
2188 record.innerWall);
2189 break;
2191 added = record.trimmed ? AddSphericalSurface(origin, axisA, axisB, s[0], s[1], s[2], s[3], s[4],
2192 record.innerWall, outerWire, innerWires)
2193 : AddSphericalSurface(origin, axisA, axisB, s[0], s[1], s[2], s[3], s[4],
2194 record.innerWall);
2195 break;
2197 added = record.trimmed ? AddConicalSurface(origin, axisA, axisB, s[0], s[1], s[2], s[3], s[4], s[5],
2198 record.innerWall, outerWire, innerWires)
2199 : AddConicalSurface(origin, axisA, axisB, s[0], s[1], s[2], s[3], s[4], s[5],
2200 record.innerWall);
2201 break;
2203 added = record.trimmed ? AddToroidalSurface(origin, axisA, axisB, s[0], s[1], s[2], s[3], s[4], s[5],
2204 record.innerWall, outerWire, innerWires)
2205 : AddToroidalSurface(origin, axisA, axisB, s[0], s[1], s[2], s[3], s[4], s[5],
2206 record.innerWall);
2207 break;
2208 default:
2209 break;
2210 }
2211 }
2212
2213 if (!added) {
2214 // a solid missing a face is a different solid: discard it rather than return a partial shape
2215 Error("RebuildFromRecords",
2216 "Shape %s: surface record %d (kind %d) did not rebuild, so the shape is discarded and stays undefined.",
2217 GetName(), static_cast<int>(recordIndex), record.kind);
2218 return discard();
2219 }
2220
2221 // the edge identities are part of the record: replay them, or the read-back closure verdict could differ
2222 if (!record.boundaryEdgeIds.empty()) {
2223 SetSurfaceBoundaryEdges(static_cast<int>(recordIndex), record.boundaryEdgeIds, record.boundaryEdgeFlags);
2224 }
2225 }
2226
2227 // check == false: replaying a solid must not re-emit the closure diagnostics that were already
2228 // reported when it was first built. The report itself is recomputed, not trusted.
2229 CloseShape(false);
2230 return true;
2231}
2232
2233void O2BVHSurfaceSolid::Streamer(TBuffer& buffer)
2234{
2235 if (buffer.IsReading()) {
2236 buffer.ReadClassBuffer(O2BVHSurfaceSolid::Class(), this);
2237 RebuildFromRecords();
2238 } else {
2239 buffer.WriteClassBuffer(O2BVHSurfaceSolid::Class(), this);
2240 }
2241}
header::DataOrigin origin
std::vector< o2::soa::IndexRecord > records
Private analytic bounded surfaces, trim wires and closure checks behind O2BVHSurfaceSolid.
std::unique_ptr< expressions::Node > node
uint64_t vertex
Definition RawEventData.h:9
float center
ClassImp(O2BVHSurfaceSolid)
double lower[3]
double upper[3]
uint32_t c
Definition RawData.h:2
uint32_t stack
Definition RawData.h:1
Double_t DistFromOutside_Loop(const Double_t *point, const Double_t *dir, Double_t stepmax=TGeoShape::Big()) const
Non-BVH DistFrom* over all surfaces: the oracles the BVH paths must match exactly.
@ Reliable
closed, consistently oriented 2-manifold: parity is well defined
@ Undetermined
CloseShape() has not run yet: no diagnostics exist.
bool AddCylindricalSurface(const Point3D &centerPoint, const Point3D &axis, const Point3D &referenceAxisU, double radius, double heightMin, double heightMax, double phiStart=0., double phiSweep=6.283185307179586, bool innerWall=false)
Add a cylindrical wall of radius around axis over a height range and a phi sweep; innerWall points th...
double GetRimMatchTolerance() const
The declared rim match tolerance in cm, the model's own or a fallback: the floor of each chord's matc...
void Print(Option_t *option="") const override
static void ResetSafetyCandidateCounter()
Per-thread count of surfaces handed to distanceSqToPatch by Safety and ComputeNormal since the last r...
TBuffer3D * MakeBuffer3D() const override
bool ContainsAlongDirection(const Double_t *point, const Double_t *direction) const
Diagnostic: the parity answer for one explicit direction, bypassing Contains()'s re-shoot policy.
void GetMeshNumbers(int &nvert, int &nsegs, int &npols) const override
bool Contains_Loop(const Double_t *point) const
Double_t DistFromInside_Loop(const Double_t *point, const Double_t *dir, Double_t stepmax=TGeoShape::Big()) const
int GetRimCount() const
Rim counts: total, and split by the same four states as the edge counters above.
const TBuffer3D & GetBuffer3D(int reqSections, Bool_t localFrame) const override
Double_t DistFromOutside(const Double_t *point, const Double_t *dir, Int_t iact=1, Double_t step=TGeoShape::Big(), Double_t *safe=nullptr) const override
static void SetSafetyBoundUnsoundForTest(bool enable)
Test-only sabotage: prune on the distance to the box centre, which bounds nothing,...
void CloseShape(bool check=true)
Finalize the shape: bounding box, display mesh, BVH and closure diagnostics, reported when check is s...
void ComputeNormal_Loop(const Double_t *point, const Double_t *dir, Double_t *norm) const
int CountBVHRayCandidates(const Point3D &point, const Point3D &direction) const
Test hook: distinct surfaces whose cover boxes the ray traverses; -1 without a BVH.
static long long GetSafetyCandidateCount()
void SetPoints(double *points) const override
Double_t Safety_Loop(const Double_t *point, Bool_t in=kTRUE) const
Non-BVH Safety/ComputeNormal over all surfaces: the oracles the BVH traversal must match bit for bit.
Double_t DistFromInside(const Double_t *point, const Double_t *dir, Int_t iact=1, Double_t step=TGeoShape::Big(), Double_t *safe=nullptr) const override
void ComputeNormal(const Double_t *point, const Double_t *dir, Double_t *norm) const override
Bool_t GetPointsOnSegments(Int_t npoints, Double_t *array) const override
Fill array with npoints points on the solid's exact boundary; kFALSE below GetNmeshVertices() so ROOT...
bool Contains(const Double_t *point) const override
void SetSegsAndPols(TBuffer3D &buff) const override
void GetSurfaceCapacityContributions(std::vector< double > &contributions) const
Each face's divergence-theorem contribution to Capacity(), in record order.
Double_t Capacity() const override
std::array< double, 3 > Point3D
bool GetBVHRootBounds(Point3D &lower, Point3D &upper) const
Fill the BVH root-node bounding box; returns false when no BVH has been built.
NavigationReliability GetNavigationReliability() const
The reliability state derived from the last CloseShape(); Undetermined before it has run.
std::array< double, 2 > Point2D
static void SetRayTMaxPruning(bool enable)
Ray tmax tightening in the distance queries, on by default; it never changes an answer....
int GetSourceEdgeCount() const
Distinct source edges and their incidence: shared, boundary, non-manifold, reversed and degenerate.
bool AddCurvedPlanarSurface(const Point3D &origin, const Point3D &axisU, const Point3D &axisV, const std::vector< PlanarBoundaryCurve > &outerWire, const std::vector< std::vector< PlanarBoundaryCurve > > &innerWires={})
Add an exact planar surface bounded by line/arc wires; axisU and axisV are orthonormal and axisU x ax...
static long long GetRayCandidateCount()
Double_t Safety(const Double_t *point, Bool_t in=kTRUE) const override
The distance to the nearest patch, rounded down by one ulp so that Safety is never too large.
int GetNmeshVertices() const override
double GetMaxSharedEdgeDeviation() const
Largest Hausdorff distance between the two faces' realisations of one shared edge,...
static const char * GetNavigationReliabilityName(NavigationReliability reliability)
const std::vector< RimReport > & GetRimReports() const
bool AddConicalSurface(const Point3D &centerPoint, const Point3D &axis, const Point3D &referenceAxisU, double radiusAtMin, double radiusAtMax, double heightMin, double heightMax, double phiStart=0., double phiSweep=6.283185307179586, bool innerWall=false)
Add a conical wall whose radius runs linearly from radiusAtMin to radiusAtMax; one radius may be zero...
static constexpr double kSurfacePointTolerance
void SetModelTolerance(double toleranceCm)
bool SetSurfaceBoundaryEdges(int surfaceIndex, const std::vector< unsigned int > &edgeIds, const std::vector< unsigned char > &edgeFlags)
Attach surface surfaceIndex's edge identities in trim-curve order; false on a bad index or mismatched...
bool AddSphericalSurface(const Point3D &center, const Point3D &polarAxis, const Point3D &referenceAxisU, double radius, double thetaMin=0., double thetaMax=3.141592653589793, double phiStart=0., double phiSweep=6.283185307179586, bool innerWall=false)
Add a spherical surface of radius trimmed to a theta range and a phi sweep; the defaults give a full ...
bool AddToroidalSurface(const Point3D &centerPoint, const Point3D &axis, const Point3D &referenceAxisU, double majorRadius, double minorRadius, double phiStart=0., double phiSweep=6.283185307179586, double tubeStart=0., double tubeSweep=6.283185307179586, bool innerWall=false)
Add a toroidal surface trimmed to a phiRing x phiTube rectangle; the defaults give a full torus,...
double GetTotalRimLength() const
Summed trim-boundary length, and the part with no other face within the match band,...
void DescribeContainsCrossings(const Point3D &point, std::vector< ContainsCrossing > &bvhCrossings, std::vector< ContainsCrossing > &loopCrossings) const
Diagnostic: the parity ray's crossings at point from the BVH and from the loop, sorted by distance.
bool AddPlanarSurface(const Point3D &origin, const Point3D &axisU, const Point3D &axisV, const std::vector< Point2D > &outerWire, const std::vector< std::vector< Point2D > > &innerWires={})
static void ResetRayCandidateCounter()
Per-thread count of surfaces handed to the BVH leaf callback by DistFrom* since the last reset.
@ kEdgeAnchored
entry i is trim curve i of this face, so it can be measured
@ kEdgeReversed
the face runs against the edge's own direction
@ kEdgeDegenerate
cone apex / sphere pole: a point, so it has no second face
bool HasBVH() const
Whether the BVH acceleration structure has been built (after CloseShape).
int GetBoundaryEdgeCount() const
Per-chord closure counts: diagnostics only; GetNavigationReliability() reads the rim counts below.
Abstract analytic surface patch: one support surface plus its trim, with the kernels the navigation n...
virtual void appendIntersections(const Vec3 &rayOrigin, const Vec3 &rayDirection, double minDistance, double maxDistance, std::vector< RayHit > &hits) const =0
Append every hit of the ray with the trimmed patch in [minDistance, maxDistance], with the outward no...
virtual bool containsPointOnSurface(const Vec3 &point) const =0
True if the 3D point lies on the trimmed patch within tolerance.
virtual double capacityContribution() const =0
Signed divergence-theorem contribution to the enclosed volume.
virtual Vec3 normalAt(const Vec3 &point) const =0
Outward-oriented normal at (or nearest to) the given point.
virtual double distanceSqToPatch(const Vec3 &point) const =0
Squared distance from a 3D point to the trimmed patch (used for Safety).
GLuint buffer
Definition glcorearb.h:655
GLuint entry
Definition glcorearb.h:5735
GLuint color
Definition glcorearb.h:1272
GLenum array
Definition glcorearb.h:4274
GLuint index
Definition glcorearb.h:781
GLuint const GLchar * name
Definition glcorearb.h:781
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLsizei GLsizei GLfloat distance
Definition glcorearb.h:5506
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum target
Definition glcorearb.h:1641
GLintptr offset
Definition glcorearb.h:660
GLint GLenum GLboolean normalized
Definition glcorearb.h:867
GLint GLenum GLboolean GLsizei stride
Definition glcorearb.h:867
GLboolean enable
Definition glcorearb.h:3991
const GLchar * marker
Definition glcorearb.h:4051
GLint ref
Definition glcorearb.h:291
GLsizei const GLint * box
Definition glcorearb.h:4697
void report(gsl::span< o2::InteractionTimeRecord > irs, int threshold, bool verbose)
constexpr double wireJoinToleranceFor(double modelTolerance)
The wire-join band for a model with a declared tolerance: that tolerance when looser than kWireJoinTo...
double distanceSq(const Vec2 &firstPoint, const Vec2 &secondPoint)
double dot(const Vec3 &firstVector, const Vec3 &secondVector)
bool sameIntersection(double firstDistance, double secondDistance)
constexpr double kRayTolerance
minimum positive ray parameter t
constexpr double kBVHBoxTolerance
Widening of the BVH leaf boxes before the outward float rounding; it dominates every navigation lengt...
ClosureReport validateClosure(const std::vector< std::unique_ptr< BoundedSurface > > &surfaces, double modelTolerance=0.)
Validate closure and orientation of surfaces by half-edges, measure the rims, and count edge identiti...
double normSq(const Vec3 &vector)
double component(const Vec3 &vector, int dimension)
constexpr double kTolerance
generic length tolerance
constexpr double kIntersectionTolerance
clustering of near-equal intersections
double norm(const Vec3 &vector)
void check(const std::vector< std::string > &arguments, const std::vector< ConfigParamSpec > &workflowOptions, const std::vector< DeviceSpec > &deviceSpecs, CheckMatrix &matrix)
const bool outward
void append(const char *msg, std::string &to)
bool visitPointCandidates(const Vec3 &point, SurfaceVisitor &&visitor) const
Visit every surface whose widened leaf box holds the point, until the visitor returns true.
std::vector< std::unique_ptr< BoundedSurface > > surfaces
std::vector< int > leafSurface
The surface of each BVH leaf primitive, in leaf order.
bool commit(std::unique_ptr< BoundedSurface > surface, BVHSurfaceRecord record, std::vector< BVHSurfaceRecord > &records)
Append a built surface, and to records the record that rebuilds it.
double nearestPatchDistanceSqLoop(const Vec3 &point, size_t *closestIndex) const
The brute-force nearest patch and its index; the lowest index wins an exact tie, which ComputeNormal ...
bool containsByVote(const Vec3 &point, bool useBVH, bool *allTiedOnBoundary=nullptr) const
std::vector< std::array< int, 3 > > displayTriangles
void visitRayCandidates(const Vec3 &rayOrigin, const Vec3 &rayDirection, SurfaceVisitor &&visitor) const
double nearestPatchDistanceSq(const Vec3 &point, size_t *closestIndex) const
bool parityAlong(const Vec3 &point, const Vec3 &direction, bool useBVH, bool *ambiguous=nullptr) const
Parity of the ray's crossings with the surface set, through the BVH or the loop; ambiguous reports a ...
void buildBVH()
Build the BVH over the surfaces' cover boxes, widened by kBVHBoxTolerance and rounded outward to floa...
size_t surfaceOfPrimitive(size_t primitive) const
The surface a BVH leaf primitive belongs to.
double nearestCrossing(const Vec3 &rayOrigin, const Vec3 &rayDirection, double stepmax) const
std::vector< RimReport > rimReports
double anchorSeedDistanceSq(const Vec3 &point) const
double nearestCrossingLoop(const Vec3 &rayOrigin, const Vec3 &rayDirection, double stepmax) const
Same query without the BVH: visit every surface. Oracle and baseline for nearestCrossing.
std::vector< int > displayTriangleSurface
The surface each display triangle came from, parallel to displayTriangles; see GetPointsOnSegments.
bool refuseIfDefined(const O2BVHSurfaceSolid &owner, const char *method) const
True, after reporting it for method of owner, if the shape is defined and takes no more surfaces.
void collectSafetyAnchors()
Subsample the display vertices, which lie on their patches, as safety anchors.
bool reliable
GetNavigationReliability() is Reliable; set by CloseShape.
std::vector< Vec3 > safetyAnchors
A few on-patch display vertices, seeding the nearest-patch traversal's upper bound; see anchorSeedDis...
One boundary curve of a BVHSurfaceRecord in the flat form ROOT streams: a segment,...
std::vector< double > poles
B-spline control points, flattened (u, v) pairs.
std::vector< double > knots
B-spline clamped flat knot vector.
std::vector< double > weights
B-spline weights (empty => non-rational)
int kind
PlanarBoundaryCurve::Kind: 0 = Line, 1 = Arc, 2 = BSpline.
The persistent record of one successful Add*Surface call; reading a solid back replays the records.
std::vector< int > wireSizes
std::vector< BVHSurfaceCurveRecord > curves
std::vector< double > scalars
std::vector< double > polygonPoints
The wires, outer first: PlanarPolygon stores (u, v) pairs in polygonPoints, the others curves; wireSi...
bool trimmed
the wire-trim overload was used (quadrics only)
double axisA[3]
axisU / axis / polarAxis
double axisB[3]
axisV / referenceAxisU
std::vector< unsigned char > boundaryEdgeFlags
static int expectedScalarCount(int recordKind)
How many entries scalars must hold for kind, or -1 for an unknown kind.
std::vector< unsigned int > boundaryEdgeIds
Sidecar v3 boundary edge identities in curve order: an edge-table index and a BoundaryEdgeFlag byte; ...
double origin[3]
origin / centerPoint / center
One boundary curve in the surface's local (u, v) frame: a line segment, a circular arc or a clamped (...
std::vector< double > weights
B-spline weights (empty ⇒ non-rational)
std::vector< double > knots
B-spline clamped flat knot vector.
std::vector< Point2D > poles
B-spline control points.
One trim loop of one face as the closure measurement saw it, naming the rim and its worst chord.
int surface
index into GetSurfaceRecords() of the face owning this rim
Whether a set of bounded surfaces forms a closed, consistently oriented 2-manifold,...
int rims
total number of trim loops over all faces
int reversedEdges
edges shared by two faces in the same direction
int boundaryEdges
edges present on only one face (e.g. a missing face)
bool orientationConsistent
shared edges are traversed in opposite directions
double totalRimLength
summed length in cm of every face's trim boundary
int edgeSharedCount
appearing exactly twice, opposite sense: a properly shared edge
int edgeBoundaryCount
appearing once: a face is missing on the other side
double rimEpsilon
the declared matching tolerance, in cm
std::vector< RimRecord > rimRecords
double maxSharedEdgeDeviation
Largest Hausdorff distance between two faces' realisations of a shared edge, in cm; a measurement,...
int nonManifoldEdges
edges shared by more than two faces
int nonManifoldRims
some chord has two or more other faces within rimEpsilon
bool closed
every boundary edge is shared by exactly two faces
double unmatchedRimLength
how much of it has no other face within the match band, cm
int edgeReversedCount
appearing exactly twice, but with the same sense
int sharedEdgesMeasured
shared edges both of whose faces could be sampled
int edgeNonManifoldCount
appearing three or more times
int edgeIncidences
distinct edge identifiers seen over all faces
static Curve2D makeBSpline(int splineDegree, std::vector< Vec2 > splinePoles, std::vector< double > splineWeights, std::vector< double > splineKnots)
static Curve2D makeLine(const Vec2 &start, const Vec2 &end)
static Curve2D makeArc(const Vec2 &arcCenter, double arcRadius, double arcStartAngle, double arcEndAngle)
One ray/surface intersection: the ray parameter and the outward normal; a quadric patch can give seve...
bool onTrimBoundary
The hit lies within the trim's on-boundary band, so its inside/outside side is a tie-break,...
One trim loop of one face as measureRimClosure saw it, naming the rim and its worst chord.
A 2D point/vector in a surface's parametric (u, v) domain.
A 3D point/vector in the solid's local frame.