6#include <BRepAdaptor_Curve.hxx>
7#include <BRepBndLib.hxx>
8#include <BRepExtrema_DistShapeShape.hxx>
9#include <BRepExtrema_SupportType.hxx>
10#include <BRepGProp.hxx>
12#include <BRepLProp_SLProps.hxx>
13#include <BRepMesh_IncrementalMesh.hxx>
14#include <BRepTools.hxx>
15#include <BRepTools_WireExplorer.hxx>
16#include <BRep_Builder.hxx>
17#include <BRep_Tool.hxx>
18#include <BVH_Distance.hxx>
19#include <BVH_Tools.hxx>
21#include <GProp_GProps.hxx>
22#include <GeomAPI_ProjectPointOnSurf.hxx>
23#include <GeomAbs_CurveType.hxx>
24#include <GeomAbs_SurfaceType.hxx>
25#include <Geom_Surface.hxx>
26#include <NCollection_Vector.hxx>
27#include <Poly_Triangulation.hxx>
28#include <Precision.hxx>
29#include <TopAbs_Orientation.hxx>
30#include <TopAbs_ShapeEnum.hxx>
31#include <TopAbs_State.hxx>
32#include <TopExp_Explorer.hxx>
33#include <TopLoc_Location.hxx>
35#include <TopoDS_Edge.hxx>
36#include <TopoDS_Vertex.hxx>
37#include <TopoDS_Wire.hxx>
42#include <gp_Pnt2d.hxx>
45#include <G4Exception.hh>
46#include <G4GeometryTolerance.hh>
47#include <Randomize.hh>
57class PointToMeshDistance
58 :
public BVH_Distance<Standard_Real, 3, BVH_Vec3d, BRepExtrema_TriangleSet> {
60 Standard_Boolean RejectNode(
const BVH_Vec3d& theCornerMin,
const BVH_Vec3d& theCornerMax,
61 Standard_Real& theMetric)
const override {
63 BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(myObject, theCornerMin, theCornerMax);
64 return RejectMetric(theMetric);
67 Standard_Boolean Accept(
const Standard_Integer theIndex,
const Standard_Real&)
override {
69 myBVHSet->GetVertices(theIndex, v0, v1, v2);
70 const Standard_Real sq =
71 BVH_Tools<Standard_Real, 3>::PointTriangleSquareDistance(myObject, v0, v1, v2);
72 if (sq < myDistance) {
74 myBestIndex = theIndex;
77 return Standard_False;
80 Standard_Integer BestIndex()
const {
return myBestIndex; }
83 Standard_Integer myBestIndex{-1};
87 :
public BVH_Traverse<Standard_Real, 3, BRepExtrema_TriangleSet, Standard_Real> {
89 void SetRay(
const BVH_Vec3d& theOrigin,
const BVH_Vec3d& theDir, Standard_Real theTolerance) {
92 myTolerance = theTolerance;
94 myDegenerate = Standard_False;
97 Standard_Boolean RejectNode(
const BVH_Vec3d& theCornerMin,
const BVH_Vec3d& theCornerMax,
98 Standard_Real& theMetric)
const override {
99 Standard_Real tmin = 0.0;
100 Standard_Real tmax = Precision::Infinite();
101 for (
int k = 0; k < 3; ++k) {
102 const Standard_Real dk = (k == 0) ? myDir.x() : (k == 1) ? myDir.y() : myDir.z();
103 const Standard_Real ok = (k == 0) ? myOrigin.x() : (k == 1) ? myOrigin.y() : myOrigin.z();
104 const Standard_Real ck_min = (k == 0) ? theCornerMin.x()
105 : (k == 1) ? theCornerMin.y()
107 const Standard_Real ck_max = (k == 0) ? theCornerMax.x()
108 : (k == 1) ? theCornerMax.y()
110 if (std::abs(dk) < Precision::Confusion()) {
111 if (ok < ck_min - myTolerance || ok > ck_max + myTolerance) {
112 return Standard_True;
115 const Standard_Real t1 = (ck_min - ok) / dk;
116 const Standard_Real t2 = (ck_max - ok) / dk;
123 if (tmin > tmax + myTolerance) {
124 return Standard_True;
129 return Standard_False;
132 Standard_Boolean Accept(
const Standard_Integer theIndex,
const Standard_Real&)
override {
133 BVH_Vec3d v0, v1, v2;
134 myBVHSet->GetVertices(theIndex, v0, v1, v2);
135 const BVH_Vec3d edge1 = v1 - v0;
136 const BVH_Vec3d edge2 = v2 - v0;
137 const BVH_Vec3d h = BVH_Vec3d::Cross(myDir, edge2);
138 const Standard_Real a = edge1.Dot(h);
139 if (std::abs(a) < 1e-12) {
140 return Standard_False;
142 const Standard_Real f = 1.0 / a;
143 const BVH_Vec3d s = myOrigin - v0;
144 const Standard_Real u = f * s.Dot(h);
145 if (u < 0.0 || u > 1.0) {
146 return Standard_False;
148 const BVH_Vec3d q = BVH_Vec3d::Cross(s, edge1);
149 const Standard_Real v = f * myDir.Dot(q);
150 if (v < 0.0 || u + v > 1.0) {
151 return Standard_False;
153 const Standard_Real t = f * edge2.Dot(q);
154 if (t < -myTolerance) {
155 return Standard_False;
158 constexpr Standard_Real kEdgeTol = 1e-6;
159 if (u < kEdgeTol || v < kEdgeTol || (1.0 - u - v) < kEdgeTol) {
160 myDegenerate = Standard_True;
162 return Standard_True;
165 Standard_Boolean RejectMetric(
const Standard_Real&)
const override {
return Standard_False; }
166 Standard_Boolean Stop()
const override {
return Standard_False; }
168 int Crossings()
const {
return myCrossings; }
169 Standard_Boolean Degenerate()
const {
return myDegenerate; }
174 Standard_Real myTolerance{1e-7};
176 Standard_Boolean myDegenerate{Standard_False};
179gp_Pnt ToPoint(
const G4ThreeVector& point) {
return gp_Pnt(point.x(), point.y(), point.z()); }
181G4double IntersectionTolerance() {
182 return 0.5 * G4GeometryTolerance::GetInstance()->GetSurfaceTolerance();
185TopoDS_Vertex MakeVertex(
const G4ThreeVector& point) {
186 BRep_Builder builder;
187 TopoDS_Vertex vertex;
188 builder.MakeVertex(vertex, ToPoint(point), IntersectionTolerance());
193ToPointClassification(
const TopAbs_State state) {
197 return PointClassification::kInside;
199 return PointClassification::kSurface;
202 return PointClassification::kOutside;
206G4ThreeVector FallbackNormal() {
return G4ThreeVector(0.0, 0.0, 1.0); }
208bool PointInPolygon2d(Standard_Real u, Standard_Real v,
const std::vector<gp_Pnt2d>& poly) {
209 const std::size_t n = poly.size();
211 for (std::size_t i = 0; i < n; ++i) {
212 const gp_Pnt2d& a = poly[i];
213 const gp_Pnt2d& b = poly[(i + 1) % n];
214 const Standard_Real av = a.Y();
215 const Standard_Real bv = b.Y();
216 if ((av <= v && bv > v) || (bv <= v && av > v)) {
217 const Standard_Real uCross = a.X() + (v - av) * (b.X() - a.X()) / (bv - av);
223 return (crossings % 2) == 1;
226bool PointOnPolygonBoundary2d(Standard_Real u, Standard_Real v,
const std::vector<gp_Pnt2d>& poly,
228 const Standard_Real tol2 = tol * tol;
229 const std::size_t n = poly.size();
230 for (std::size_t i = 0; i < n; ++i) {
231 const gp_Pnt2d& a = poly[i];
232 const gp_Pnt2d& b = poly[(i + 1) % n];
233 const Standard_Real dx = b.X() - a.X();
234 const Standard_Real dy = b.Y() - a.Y();
235 const Standard_Real len2 = dx * dx + dy * dy;
236 Standard_Real px = 0.0;
237 Standard_Real py = 0.0;
238 if (len2 < 1.0e-20) {
242 const Standard_Real t_seg =
243 std::max(0.0, std::min(1.0, ((u - a.X()) * dx + (v - a.Y()) * dy) / len2));
244 px = a.X() + t_seg * dx;
245 py = a.Y() + t_seg * dy;
247 const Standard_Real dist2 = (u - px) * (u - px) + (v - py) * (v - py);
255std::optional<Standard_Real> RayPlaneFaceHit(
const gp_Lin& ray,
const gp_Pln& plane,
256 const std::vector<gp_Pnt2d>& uvPoly,
257 Standard_Real tMin, Standard_Real tolerance,
258 Standard_Real* u_out =
nullptr,
259 Standard_Real* v_out =
nullptr) {
260 const gp_Dir& lineDir = ray.Direction();
261 const gp_Dir& plnNormal = plane.Axis().Direction();
262 const Standard_Real denom =
263 plnNormal.X() * lineDir.X() + plnNormal.Y() * lineDir.Y() + plnNormal.Z() * lineDir.Z();
264 if (std::abs(denom) < 1.0e-10) {
267 const gp_Pnt& orig = ray.Location();
268 const gp_Pnt& planePt = plane.Location();
269 const Standard_Real numer = plnNormal.X() * (planePt.X() - orig.X()) +
270 plnNormal.Y() * (planePt.Y() - orig.Y()) +
271 plnNormal.Z() * (planePt.Z() - orig.Z());
272 const Standard_Real t = numer / denom;
276 const gp_Pnt hitPt(orig.X() + t * lineDir.X(), orig.Y() + t * lineDir.Y(),
277 orig.Z() + t * lineDir.Z());
278 Standard_Real u = 0.0;
279 Standard_Real v = 0.0;
280 ElSLib::PlaneParameters(plane.Position(), hitPt, u, v);
281 if (!PointInPolygon2d(u, v, uvPoly) && !PointOnPolygonBoundary2d(u, v, uvPoly, tolerance)) {
284 if (u_out !=
nullptr) {
287 if (v_out !=
nullptr) {
293std::optional<G4ThreeVector> TryGetOutwardNormal(
const BRepAdaptor_Surface& surface,
294 const TopoDS_Face& face,
const Standard_Real u,
295 const Standard_Real v) {
296 Standard_Real adjustedU = u;
297 Standard_Real adjustedV = v;
298 const Standard_Real tolerance = IntersectionTolerance();
300 const Standard_Real uFirst = surface.FirstUParameter();
301 const Standard_Real uLast = surface.LastUParameter();
302 const Standard_Real uEpsilon = std::min(
303 std::max(surface.UResolution(tolerance), Precision::PConfusion()), 0.5 * (uLast - uFirst));
304 if (std::abs(adjustedU - uFirst) <= uEpsilon) {
305 adjustedU = std::min(uFirst + uEpsilon, uLast);
306 }
else if (std::abs(adjustedU - uLast) <= uEpsilon) {
307 adjustedU = std::max(uLast - uEpsilon, uFirst);
311 const Standard_Real vFirst = surface.FirstVParameter();
312 const Standard_Real vLast = surface.LastVParameter();
313 const Standard_Real vEpsilon = std::min(
314 std::max(surface.VResolution(tolerance), Precision::PConfusion()), 0.5 * (vLast - vFirst));
315 if (std::abs(adjustedV - vFirst) <= vEpsilon) {
316 adjustedV = std::min(vFirst + vEpsilon, vLast);
317 }
else if (std::abs(adjustedV - vLast) <= vEpsilon) {
318 adjustedV = std::max(vLast - vEpsilon, vFirst);
322 BRepLProp_SLProps props(surface, adjustedU, adjustedV, 1, tolerance);
323 if (!props.IsNormalDefined()) {
324 const Standard_Real vFirst = surface.FirstVParameter();
325 const Standard_Real vLast = surface.LastVParameter();
326 const Standard_Real vMid = 0.5 * (vFirst + vLast);
327 const bool nearVFirst = (adjustedV < vMid);
328 const Standard_Real vRes = std::max(surface.VResolution(tolerance), Precision::PConfusion());
329 Standard_Real finalRetryV = adjustedV;
330 for (
int attempt = 0; attempt < 8 && !props.IsNormalDefined(); ++attempt) {
331 const Standard_Real scale = std::pow(10.0,
static_cast<Standard_Real
>(attempt));
332 const Standard_Real nudge = scale * vRes;
334 nearVFirst ? std::min(adjustedV + nudge, vMid) : std::max(adjustedV - nudge, vMid);
335 props = BRepLProp_SLProps(surface, adjustedU, finalRetryV, 1, tolerance);
337 if (!props.IsNormalDefined()) {
340 constexpr Standard_Real kMaxRetryVDriftFraction = 0.10;
341 if (std::fabs(finalRetryV - adjustedV) > kMaxRetryVDriftFraction * (vLast - vFirst)) {
346 gp_Dir faceNormal = props.Normal();
347 if (face.Orientation() == TopAbs_REVERSED) {
348 faceNormal.Reverse();
351 return G4ThreeVector(faceNormal.X(), faceNormal.Y(), faceNormal.Z());
359 if (fShape.IsNull()) {
360 throw std::invalid_argument(
"G4OCCTSolidKernel: shape must not be null");
366 if (shape.IsNull()) {
367 throw std::invalid_argument(
"G4OCCTSolidKernel::SetShape: shape must not be null");
372 std::unique_lock<std::mutex> lock(fVolumeAreaMutex);
373 fCachedVolume.reset();
374 fCachedSurfaceArea.reset();
377 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
380 fSurfaceCache.reset();
381 fSurfaceCacheGeneration = std::numeric_limits<std::uint64_t>::max();
382 fSurfaceCacheBuilding =
false;
384 fSurfaceCacheCV.notify_all();
386 fShapeGeneration.fetch_add(1, std::memory_order_release);
389void G4OCCTSolidKernel::ComputeBounds() {
392 for (TopExp_Explorer faceEx(fShape, TopAbs_FACE); faceEx.More(); faceEx.Next()) {
393 const TopoDS_Face& face = TopoDS::Face(faceEx.Current());
394 if (BRepAdaptor_Surface(face).GetType() != GeomAbs_Plane) {
397 for (TopExp_Explorer edgeEx(face, TopAbs_EDGE); edgeEx.More(); edgeEx.Next()) {
398 BRepLib::BuildPCurveForEdgeOnPlane(TopoDS::Edge(edgeEx.Current()), face);
403 BRepBndLib::AddOptimal(fShape, boundingBox, Standard_False);
404 if (boundingBox.IsVoid()) {
405 throw std::invalid_argument(
406 "G4OCCTSolidKernel: shape has no computable bounding box (no geometry)");
409 Standard_Real xMin = 0.0;
410 Standard_Real yMin = 0.0;
411 Standard_Real zMin = 0.0;
412 Standard_Real xMax = 0.0;
413 Standard_Real yMax = 0.0;
414 Standard_Real zMax = 0.0;
415 boundingBox.Get(xMin, yMin, zMin, xMax, yMax, zMax);
417 AxisAlignedBounds{G4ThreeVector(xMin, yMin, zMin), G4ThreeVector(xMax, yMax, zMax)};
419 fFaceBoundsCache.clear();
420 G4double maxFaceDiag = 0.0;
421 for (TopExp_Explorer ex(fShape, TopAbs_FACE); ex.More(); ex.Next()) {
423 BRepBndLib::AddOptimal(ex.Current(), faceBox, Standard_False);
424 const TopoDS_Face& currentFace = TopoDS::Face(ex.Current());
425 BRepAdaptor_Surface adaptor(currentFace);
426 std::optional<gp_Pln> maybePlane;
427 std::vector<gp_Pnt2d> uvPolygon;
428 std::optional<G4ThreeVector> outwardNormal;
429 if (adaptor.GetType() == GeomAbs_Plane) {
433 maybePlane = adaptor.Plane();
434 const gp_Ax3& pos = maybePlane->Position();
435 const TopoDS_Wire wire = BRepTools::OuterWire(currentFace);
436 if (!wire.IsNull()) {
437 bool allLinear =
true;
438 std::vector<gp_Pnt2d> poly;
439 for (BRepTools_WireExplorer we(wire, currentFace); we.More(); we.Next()) {
440 const BRepAdaptor_Curve ec(we.Current());
441 if (ec.GetType() != GeomAbs_Line) {
445 const gp_Pnt pt = BRep_Tool::Pnt(we.CurrentVertex());
446 Standard_Real u = 0.0;
447 Standard_Real v = 0.0;
448 ElSLib::PlaneParameters(pos, pt, u, v);
449 poly.emplace_back(u, v);
451 if (allLinear && poly.size() >= 3) {
453 for (TopExp_Explorer wc(currentFace, TopAbs_WIRE); wc.More(); wc.Next()) {
459 if (wireCount == 1) {
460 uvPolygon = std::move(poly);
461 gp_Dir faceNormal = maybePlane->Axis().Direction();
462 if (currentFace.Orientation() == TopAbs_REVERSED) {
463 faceNormal.Reverse();
465 outwardNormal = G4ThreeVector(faceNormal.X(), faceNormal.Y(), faceNormal.Z());
470 fFaceBoundsCache.push_back({currentFace, faceBox, std::move(adaptor), std::move(maybePlane),
471 std::move(uvPolygon), std::move(outwardNormal)});
472 if (!faceBox.IsVoid()) {
473 Standard_Real fx0 = 0.0;
474 Standard_Real fy0 = 0.0;
475 Standard_Real fz0 = 0.0;
476 Standard_Real fx1 = 0.0;
477 Standard_Real fy1 = 0.0;
478 Standard_Real fz1 = 0.0;
479 faceBox.Get(fx0, fy0, fz0, fx1, fy1, fz1);
480 const G4double diag = G4ThreeVector(fx1 - fx0, fy1 - fy0, fz1 - fz0).mag();
481 maxFaceDiag = std::max(maxFaceDiag, diag);
486 fAllFacesPlanar = std::all_of(fFaceBoundsCache.begin(), fFaceBoundsCache.end(),
487 [](
const FaceBounds& fb) { return fb.plane.has_value(); });
494 NCollection_Vector<TopoDS_Shape> faces;
495 for (TopExp_Explorer ex(fShape, TopAbs_FACE); ex.More(); ex.Next()) {
496 faces.Append(ex.Current());
498 if (faces.IsEmpty()) {
499 fTriangleSet.Nullify();
500 fFaceDeflections.clear();
502 fTriangleSet =
new BRepExtrema_TriangleSet(faces);
503 fFaceDeflections.clear();
504 fFaceDeflections.reserve(fFaceBoundsCache.size());
505 for (
const FaceBounds& fb : fFaceBoundsCache) {
506 G4double deflection = fBVHDeflection;
507 if (!fb.box.IsVoid()) {
508 Standard_Real fx0 = 0.0, fy0 = 0.0, fz0 = 0.0;
509 Standard_Real fx1 = 0.0, fy1 = 0.0, fz1 = 0.0;
510 fb.box.Get(fx0, fy0, fz0, fx1, fy1, fz1);
511 const G4double faceDiag = G4ThreeVector(fx1 - fx0, fy1 - fy0, fz1 - fz0).mag();
514 fFaceDeflections.push_back(deflection);
518 ComputeInitialSpheres();
521void G4OCCTSolidKernel::ComputeInitialSpheres() {
522 fInitialSpheres.clear();
524 const G4double tol = IntersectionTolerance();
525 const G4ThreeVector& bmin = fCachedBounds.
min;
526 const G4ThreeVector& bmax = fCachedBounds.
max;
527 const G4ThreeVector centre = 0.5 * (bmin + bmax);
528 const G4ThreeVector halfExt = 0.5 * (bmax - bmin);
530 std::vector<G4ThreeVector> candidates;
531 candidates.reserve(15);
532 candidates.push_back(centre);
533 for (
const G4double s : {-0.5, 0.5}) {
534 candidates.push_back(centre + G4ThreeVector(s * halfExt.x(), 0.0, 0.0));
535 candidates.push_back(centre + G4ThreeVector(0.0, s * halfExt.y(), 0.0));
536 candidates.push_back(centre + G4ThreeVector(0.0, 0.0, s * halfExt.z()));
538 for (
const int sx : {-1, 1}) {
539 for (
const int sy : {-1, 1}) {
540 for (
const int sz : {-1, 1}) {
541 candidates.push_back(centre + G4ThreeVector(0.75 * sx * halfExt.x(),
542 0.75 * sy * halfExt.y(),
543 0.75 * sz * halfExt.z()));
548 BRepClass3d_SolidClassifier localClassifier;
549 localClassifier.Load(fShape);
553 for (
const G4ThreeVector& cand : candidates) {
554 localClassifier.Perform(ToPoint(cand), tol);
555 if (localClassifier.State() != TopAbs_IN) {
558 G4double d = BVHLowerBoundDistance(cand);
560 const auto match = TryFindClosestFace(fFaceBoundsCache, cand);
561 if (!match.has_value() || match->distance <= tol) {
566 fInitialSpheres.push_back({cand, d});
568 std::sort(fInitialSpheres.begin(), fInitialSpheres.end(),
569 [](
const InscribedSphere& a,
const InscribedSphere& b) { return a.radius > b.radius; });
572std::optional<G4OCCTSolidKernel::ClosestFaceMatch>
573G4OCCTSolidKernel::TryFindClosestFace(
const std::vector<FaceBounds>& faceBoundsCache,
574 const G4ThreeVector& point, G4double maxDistance) {
575 if (faceBoundsCache.empty()) {
579 const gp_Pnt queryPoint = ToPoint(point);
580 const TopoDS_Vertex queryVertex = MakeVertex(point);
582 queryBox.Add(queryPoint);
584 std::optional<ClosestFaceMatch> bestMatch;
585 for (std::size_t i = 0; i < faceBoundsCache.size(); ++i) {
586 const FaceBounds& fb = faceBoundsCache[i];
587 const G4double threshold = bestMatch.has_value() ? bestMatch->distance : maxDistance;
592 BRepExtrema_DistShapeShape distance(queryVertex, fb.face);
593 if (!distance.IsDone() || distance.NbSolution() == 0) {
596 const G4double candidateDistance = distance.Value();
598 if (bestMatch.has_value() && candidateDistance >= bestMatch->distance) {
601 ClosestFaceMatch match{.face = fb.face, .distance = candidateDistance, .faceIndex = i};
602 if (distance.NbSolution() > 0 && distance.SupportTypeShape2(1) == BRepExtrema_IsInFace) {
603 Standard_Real u = 0.0;
604 Standard_Real v = 0.0;
605 distance.ParOnFaceS2(1, u, v);
606 match.uv = std::make_pair(u, v);
608 bestMatch = std::move(match);
614BRepClass3d_SolidClassifier&
615G4OCCTSolidKernel::GetOrCreateClassifier(ClassifierCache& cache)
const {
616 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
617 if (cache.generation != currentGen) {
618 cache.classifier.emplace();
619 cache.classifier->Load(fShape);
620 cache.generation = currentGen;
622 return *cache.classifier;
625G4OCCTSolidKernel::IntersectorCache&
626G4OCCTSolidKernel::GetOrCreateIntersector(IntersectorCache& cache)
const {
627 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
628 if (cache.generation != currentGen) {
629 const G4double tol = IntersectionTolerance();
630 cache.faceIntersectors.clear();
631 cache.faceIntersectors.reserve(fFaceBoundsCache.size());
632 cache.expandedBoxes.clear();
633 cache.expandedBoxes.reserve(fFaceBoundsCache.size());
634 for (
const auto& fb : fFaceBoundsCache) {
635 cache.faceIntersectors.push_back(std::make_unique<IntCurvesFace_Intersector>(fb.face, tol));
636 Bnd_Box expanded = fb.box;
637 expanded.Enlarge(tol);
638 cache.expandedBoxes.push_back(std::move(expanded));
640 cache.generation = currentGen;
645G4OCCTSolidKernel::SphereCacheData&
646G4OCCTSolidKernel::GetOrInitSphereCache(SphereCacheData& cache)
const {
647 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
648 if (cache.generation != currentGen) {
649 cache.spheres = fInitialSpheres;
650 cache.generation = currentGen;
655void G4OCCTSolidKernel::TryInsertSphere(SphereCacheData& cache,
const G4ThreeVector& centre,
660 const G4double minRadius = IntersectionTolerance();
661 if (d <= minRadius) {
664 GetOrInitSphereCache(cache);
670 for (
const InscribedSphere& s : cache.spheres) {
672 const G4double gap = s.radius - d;
673 if ((centre - s.centre).mag2() <= gap * gap) {
679 const InscribedSphere newSphere{centre, d};
680 const auto it = std::lower_bound(
681 cache.spheres.begin(), cache.spheres.end(), newSphere,
682 [](
const InscribedSphere& a,
const InscribedSphere& b) { return a.radius > b.radius; });
683 cache.spheres.insert(it, newSphere);
685 cache.spheres.pop_back();
693 const G4double tolerance = IntersectionTolerance();
694 if (p.x() < fCachedBounds.
min.x() - tolerance || p.x() > fCachedBounds.
max.x() + tolerance ||
695 p.y() < fCachedBounds.
min.y() - tolerance || p.y() > fCachedBounds.
max.y() + tolerance ||
696 p.z() < fCachedBounds.
min.z() - tolerance || p.z() > fCachedBounds.
max.z() + tolerance) {
700 const SphereCacheData& localSphereCache = GetOrInitSphereCache(sphereCache);
702 const G4double interiorRadius = s.radius - tolerance;
703 if (interiorRadius > 0.0 && (p - s.centre).mag2() < interiorRadius * interiorRadius) {
708 if (!fTriangleSet.IsNull() && fTriangleSet->Size() > 0) {
712 if (fBVHDeflection > 0.0) {
713 const G4double bvhLB = BVHLowerBoundDistance(p);
714 if (bvhLB < tolerance) {
715 auto&
classifier = GetOrCreateClassifier(classifierCache);
717 return ToPointClassification(
classifier.State());
721 const BVH_Vec3d bvhOrigin(p.x(), p.y(), p.z());
722 const Standard_Real bvhTol =
static_cast<Standard_Real
>(tolerance);
723 TriangleRayCast caster;
724 caster.SetBVHSet(fTriangleSet.get());
725 caster.SetRay(bvhOrigin, BVH_Vec3d(0.0, 0.0, 1.0), bvhTol);
728 if (!caster.Degenerate() && caster.Crossings() > 0) {
737 int outsideVotes = 0;
738 if (!caster.Degenerate()) {
739 if (caster.Crossings() % 2 == 1) {
746 const BVH_Vec3d kExtraRays[2] = {
747 BVH_Vec3d(1.0, 0.0, 0.0),
748 BVH_Vec3d(0.0, 1.0, 0.0),
750 for (
const BVH_Vec3d& dir : kExtraRays) {
751 caster.SetRay(bvhOrigin, dir, bvhTol);
753 if (!caster.Degenerate()) {
754 if (caster.Crossings() % 2 == 1) {
762 if (insideVotes > outsideVotes) {
765 if (outsideVotes > insideVotes) {
769 auto&
classifier = GetOrCreateClassifier(classifierCache);
771 return ToPointClassification(
classifier.State());
775 const gp_Lin ray(ToPoint(p), gp_Dir(0.0, 0.0, 1.0));
777 bool onSurface =
false;
778 bool degenerateRay =
false;
780 for (std::size_t i = 0; i < fFaceBoundsCache.size(); ++i) {
784 const FaceBounds& fb = fFaceBoundsCache[i];
785 if (!fb.uvPolygon.empty()) {
788 Standard_Real u_hit = 0.0;
789 Standard_Real v_hit = 0.0;
791 RayPlaneFaceHit(ray, *fb.plane, fb.uvPolygon, -tolerance, tolerance, &u_hit, &v_hit);
793 const G4double w =
static_cast<G4double
>(*t);
794 if (std::abs(w) <= tolerance) {
796 }
else if (w > tolerance) {
797 if (PointOnPolygonBoundary2d(u_hit, v_hit, fb.uvPolygon, tolerance)) {
798 degenerateRay =
true;
806 fi.Perform(ray, -tolerance, Precision::Infinite());
810 for (Standard_Integer j = 1; j <= fi.NbPnt(); ++j) {
811 const G4double w = fi.WParameter(j);
812 const TopAbs_State state = fi.State(j);
813 if (std::abs(w) <= tolerance && (state == TopAbs_IN || state == TopAbs_ON)) {
815 }
else if (w > tolerance && state == TopAbs_IN) {
817 }
else if (w > tolerance && state == TopAbs_ON) {
818 degenerateRay =
true;
827 if (crossings == 0 || degenerateRay) {
828 auto&
classifier = GetOrCreateClassifier(classifierCache);
830 return ToPointClassification(
classifier.State());
836 if (fAllFacesPlanar) {
837 const gp_Pnt pt = ToPoint(p);
838 const FaceBounds* bestFB =
nullptr;
840 for (
const FaceBounds& fb : fFaceBoundsCache) {
841 if (!fb.plane.has_value() || !fb.outwardNormal.has_value()) {
844 const G4double d = fb.plane->Distance(pt);
851 return *bestFB->outwardNormal;
855 const auto projectAndGetNormalFallback = [&](
const FaceBounds& fb) -> G4ThreeVector {
857 const Handle(Geom_Surface) surface = BRep_Tool::Surface(fb.face, loc);
858 if (surface.IsNull()) {
859 return FallbackNormal();
861 gp_Pnt pLocal = ToPoint(p);
862 if (!loc.IsIdentity()) {
863 pLocal.Transform(loc.Transformation().Inverted());
865 GeomAPI_ProjectPointOnSurf projection(pLocal, surface);
866 if (projection.NbPoints() == 0) {
867 return FallbackNormal();
869 Standard_Real u = 0.0;
870 Standard_Real v = 0.0;
871 projection.LowerDistanceParameters(u, v);
872 return TryGetOutwardNormal(fb.adaptor, fb.face, u, v).value_or(FallbackNormal());
875 const G4double bvhLB = BVHLowerBoundDistance(p);
880 ? bvhLB + 2.0 * fBVHDeflection
882 const auto closestFaceMatch = TryFindClosestFace(fFaceBoundsCache, p, seedDist);
883 if (!closestFaceMatch.has_value()) {
884 return FallbackNormal();
886 const FaceBounds& fb = fFaceBoundsCache[closestFaceMatch->faceIndex];
888 if (fb.outwardNormal.has_value()) {
889 return *fb.outwardNormal;
891 if (closestFaceMatch->uv.has_value()) {
892 const auto [u, v] = *closestFaceMatch->uv;
893 const auto result = TryGetOutwardNormal(fb.adaptor, fb.face, u, v);
894 if (result.has_value()) {
898 return projectAndGetNormalFallback(fb);
903 const G4double tolerance = IntersectionTolerance();
904 const gp_Lin ray(ToPoint(p), gp_Dir(v.x(), v.y(), v.z()));
908 for (std::size_t i = 0; i < fFaceBoundsCache.size(); ++i) {
912 const FaceBounds& fb = fFaceBoundsCache[i];
913 if (!fb.uvPolygon.empty()) {
914 const auto t = RayPlaneFaceHit(ray, *fb.plane, fb.uvPolygon, tolerance, tolerance);
915 if (t && *t < minDistance) {
916 minDistance =
static_cast<G4double
>(*t);
920 fi.Perform(ray, tolerance, Precision::Infinite());
924 for (Standard_Integer j = 1; j <= fi.NbPnt(); ++j) {
925 const G4double w = fi.WParameter(j);
926 if (w > tolerance && w < minDistance) {
937 auto&
classifier = GetOrCreateClassifier(classifierCache);
938 classifier.Perform(ToPoint(p), IntersectionTolerance());
943 const auto match = TryFindClosestFace(fFaceBoundsCache, p);
944 if (!match.has_value()) {
947 return (match->distance <= IntersectionTolerance()) ? 0.0 : match->distance;
950G4double G4OCCTSolidKernel::AABBLowerBound(
const G4ThreeVector& p)
const {
951 const G4ThreeVector& mn = fCachedBounds.
min;
952 const G4ThreeVector& mx = fCachedBounds.
max;
953 const G4double dx = std::max({0.0, mn.x() - p.x(), p.x() - mx.x()});
954 const G4double dy = std::max({0.0, mn.y() - p.y(), p.y() - mx.y()});
955 const G4double dz = std::max({0.0, mn.z() - p.z(), p.z() - mx.z()});
956 return std::sqrt(dx * dx + dy * dy + dz * dz);
959G4double G4OCCTSolidKernel::BVHLowerBoundDistance(
const G4ThreeVector& p)
const {
960 if (fTriangleSet.IsNull() || fTriangleSet->Size() == 0) {
963 PointToMeshDistance solver;
964 solver.SetObject(BVH_Vec3d(p.x(), p.y(), p.z()));
965 solver.SetBVHSet(fTriangleSet.get());
966 const Standard_Real meshDistSq = solver.ComputeDistance();
967 if (!solver.IsDone()) {
970 const G4double meshDist = std::sqrt(
static_cast<G4double
>(meshDistSq));
972 G4double deflection = fBVHDeflection;
973 const Standard_Integer bestIdx = solver.BestIndex();
975 const Standard_Integer faceId = fTriangleSet->GetFaceID(bestIdx);
976 if (faceId >= 0 &&
static_cast<std::size_t
>(faceId) < fFaceDeflections.size()) {
977 deflection = fFaceDeflections[
static_cast<std::size_t
>(faceId)];
984 return std::max(0.0, meshDist - deflection);
987G4double G4OCCTSolidKernel::PlanarFaceLowerBoundDistance(
const G4ThreeVector& p)
const {
988 const gp_Pnt pt = ToPoint(p);
990 for (
const FaceBounds& fb : fFaceBoundsCache) {
991 if (!fb.plane.has_value()) {
994 const G4double d =
static_cast<G4double
>(fb.plane->Distance(pt));
1004 const G4double aabbDist = AABBLowerBound(p);
1005 if (aabbDist > IntersectionTolerance()) {
1012 const G4double bvhDist = BVHLowerBoundDistance(p);
1021 G4bool* validNorm, G4ThreeVector* n)
const {
1022 if (validNorm !=
nullptr) {
1026 const G4double tolerance = IntersectionTolerance();
1027 const gp_Lin ray(ToPoint(p), gp_Dir(v.x(), v.y(), v.z()));
1031 std::size_t minFaceIdx = std::numeric_limits<std::size_t>::max();
1032 G4double minU = 0.0;
1033 G4double minV = 0.0;
1034 bool minIsIn =
false;
1035 bool minIsFastPath =
false;
1037 for (std::size_t i = 0; i < fFaceBoundsCache.size(); ++i) {
1041 const FaceBounds& fb = fFaceBoundsCache[i];
1042 if (!fb.uvPolygon.empty()) {
1043 const auto t = RayPlaneFaceHit(ray, *fb.plane, fb.uvPolygon, tolerance, tolerance);
1044 if (t && *t < minDistance) {
1045 minDistance =
static_cast<G4double
>(*t);
1048 minIsFastPath =
true;
1052 fi.Perform(ray, tolerance, Precision::Infinite());
1056 for (Standard_Integer j = 1; j <= fi.NbPnt(); ++j) {
1057 const G4double w = fi.WParameter(j);
1058 if (w > tolerance && w < minDistance) {
1061 minU = fi.UParameter(j);
1062 minV = fi.VParameter(j);
1063 minIsIn = (fi.State(j) == TopAbs_IN || fi.State(j) == TopAbs_ON);
1064 minIsFastPath =
false;
1070 if (minFaceIdx == std::numeric_limits<std::size_t>::max() ||
1075 if (calcNorm && validNorm !=
nullptr && n !=
nullptr && minIsIn) {
1076 const FaceBounds& fb = fFaceBoundsCache[minFaceIdx];
1077 if (minIsFastPath && fb.outwardNormal.has_value()) {
1078 *n = *fb.outwardNormal;
1081 const auto outNorm = TryGetOutwardNormal(fb.adaptor, fb.face, minU, minV);
1092 const auto match = TryFindClosestFace(fFaceBoundsCache, p);
1093 if (!match.has_value()) {
1096 return (match->distance <= IntersectionTolerance()) ? 0.0 : match->distance;
1102 if (fAllFacesPlanar) {
1105 d = PlanarFaceLowerBoundDistance(p);
1110 const G4double bvhDist = BVHLowerBoundDistance(p);
1115 TryInsertSphere(sphereCache, p, d);
1120 std::unique_lock<std::mutex> lock(fVolumeAreaMutex);
1121 if (!fCachedVolume) {
1123 BRepGProp::VolumeProperties(fShape, props);
1124 fCachedVolume = props.Mass();
1126 return *fCachedVolume;
1130 std::unique_lock<std::mutex> lock(fVolumeAreaMutex);
1131 if (!fCachedSurfaceArea) {
1133 BRepGProp::SurfaceProperties(fShape, props);
1134 fCachedSurfaceArea = props.Mass();
1136 return *fCachedSurfaceArea;
1140 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
1142 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
1143 while (fSurfaceCacheBuilding) {
1144 fSurfaceCacheCV.wait(lock);
1145 if (fSurfaceCache.has_value() && fSurfaceCacheGeneration == currentGen) {
1146 return *fSurfaceCache;
1149 if (fSurfaceCache.has_value() && fSurfaceCacheGeneration == currentGen) {
1150 return *fSurfaceCache;
1154 fSurfaceCacheBuilding =
true;
1162 for (TopExp_Explorer ex(fShape, TopAbs_FACE); ex.More(); ex.Next()) {
1163 const TopoDS_Face& face = TopoDS::Face(ex.Current());
1164 TopLoc_Location loc;
1165 const Handle(Poly_Triangulation) & triangulation = BRep_Tool::Triangulation(face, loc);
1166 if (triangulation.IsNull()) {
1170 const auto faceIndex =
static_cast<std::uint32_t
>(cache.
faces.size());
1171 cache.
faces.push_back(face);
1173 const gp_Trsf& transform = loc.Transformation();
1174 const bool reverseWinding = face.Orientation() == TopAbs_REVERSED;
1176 for (Standard_Integer i = 1; i <= triangulation->NbTriangles(); ++i) {
1177 Standard_Integer idx1 = 0;
1178 Standard_Integer idx2 = 0;
1179 Standard_Integer idx3 = 0;
1180 triangulation->Triangle(i).Get(idx1, idx2, idx3);
1181 if (reverseWinding) {
1182 std::swap(idx2, idx3);
1185 const gp_Pnt q1 = triangulation->Node(idx1).Transformed(transform);
1186 const gp_Pnt q2 = triangulation->Node(idx2).Transformed(transform);
1187 const gp_Pnt q3 = triangulation->Node(idx3).Transformed(transform);
1189 const G4ThreeVector v1(q1.X(), q1.Y(), q1.Z());
1190 const G4ThreeVector v2(q2.X(), q2.Y(), q2.Z());
1191 const G4ThreeVector v3(q3.X(), q3.Y(), q3.Z());
1193 const G4double area = 0.5 * (v2 - v1).cross(v3 - v1).mag();
1199 cache.
triangles.push_back({v1, v2, v3, faceIndex});
1204 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
1205 fSurfaceCacheBuilding =
false;
1207 fSurfaceCacheCV.notify_all();
1211 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
1212 if (!(fSurfaceCache.has_value() && fSurfaceCacheGeneration == currentGen)) {
1213 fSurfaceCache = std::move(cache);
1214 fSurfaceCacheGeneration = currentGen;
1216 fSurfaceCacheBuilding =
false;
1218 fSurfaceCacheCV.notify_all();
1219 return *fSurfaceCache;
1226 G4ExceptionDescription msg;
1227 msg <<
"Tessellation of the OCCT shape";
1228 if (diagnosticName !=
nullptr) {
1229 msg <<
" for solid \"" << diagnosticName <<
"\"";
1231 msg <<
" produced no valid triangles; cannot sample a point on the surface.";
1232 G4Exception(
"G4OCCTSolidKernel::GetPointOnSurface",
"GeomMgt1001", FatalException, msg);
1233 return {0.0, 0.0, 0.0};
1236 const G4double target = G4UniformRand() * cache.
totalArea;
1237 const auto it = std::ranges::lower_bound(cache.
cumulativeAreas, target);
1238 const std::size_t idx = std::min(
static_cast<std::size_t
>(it - cache.
cumulativeAreas.begin()),
1242 G4double r1 = G4UniformRand();
1243 G4double r2 = G4UniformRand();
1244 if (r1 + r2 > 1.0) {
1249 const G4ThreeVector tessPoint =
1250 chosen.
p1 + r1 * (chosen.
p2 - chosen.
p1) + r2 * (chosen.
p3 - chosen.
p1);
1253 TopLoc_Location loc;
1254 const Handle(Geom_Surface) geomSurface = BRep_Tool::Surface(face, loc);
1255 if (!geomSurface.IsNull()) {
1256 gp_Pnt tessPointLocal(tessPoint.x(), tessPoint.y(), tessPoint.z());
1257 if (!loc.IsIdentity()) {
1258 tessPointLocal.Transform(loc.Transformation().Inverted());
1260 GeomAPI_ProjectPointOnSurf projection(tessPointLocal, geomSurface);
1261 if (projection.NbPoints() > 0) {
1262 gp_Pnt projectedPoint = projection.NearestPoint();
1263 if (!loc.IsIdentity()) {
1264 projectedPoint.Transform(loc.Transformation());
1266 return {projectedPoint.X(), projectedPoint.Y(), projectedPoint.Z()};
Shared OCCT-backed solid query kernel for adapter frontends.
G4OCCTSolidKernel::ClassifierCache classifier
G4double DistanceToOut(const G4ThreeVector &p, const G4ThreeVector &v, IntersectorCache &intersectorCache, const G4bool calcNorm=false, G4bool *validNorm=nullptr, G4ThreeVector *n=nullptr) const
Exact ray distance from an interior point to the first exit intersection.
void SetShape(const TopoDS_Shape &shape)
G4OCCTSolidKernel(const TopoDS_Shape &shape)
G4double GetCubicVolume()
Compute and cache the solid volume for the current shape generation.
G4double GetSurfaceArea()
Compute and cache the solid surface area for the current shape generation.
static constexpr std::size_t kMaxInscribedSpheres
Maximum number of inscribed spheres retained in a per-thread sphere cache.
G4ThreeVector GetPointOnSurface(const char *diagnosticName=nullptr) const
G4double DistanceToIn(const G4ThreeVector &p, const G4ThreeVector &v, IntersectorCache &intersectorCache) const
Exact ray distance from an exterior point to the first entry intersection.
PointClassification
Classification result for point-in-solid queries.
G4ThreeVector SurfaceNormal(const G4ThreeVector &p) const
Return the outward surface normal at the face nearest point p.
static G4double Infinity()
Return the Geant4 navigation infinity sentinel used by the kernel.
G4double ExactDistanceToOut(const G4ThreeVector &p) const
Exact shortest distance from a point to the surface.
PointClassification ClassifyPoint(const G4ThreeVector &p, ClassifierCache &classifierCache, IntersectorCache &intersectorCache, SphereCacheData &sphereCache) const
Classify a point as inside, on, or outside the solid.
G4double ExactDistanceToIn(const G4ThreeVector &p, ClassifierCache &classifierCache) const
Exact shortest distance from an exterior point to the surface.
const SurfaceSamplingCache & GetOrBuildSurfaceCache() const
Build or return the shared surface-sampling cache for the current shape.
constexpr Standard_Real kOCCTRelativeDeflection
Cached inscribed sphere used to accelerate deep-interior classifications.
std::vector< std::unique_ptr< IntCurvesFace_Intersector > > faceIntersectors
std::vector< Bnd_Box > expandedBoxes
std::vector< InscribedSphere > spheres
std::vector< TopoDS_Face > faces
std::vector< G4double > cumulativeAreas
std::vector< SurfaceTriangle > triangles
Tessellated triangle entry used for random surface-point sampling.