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 myOnSurface = Standard_False;
95 myDegenerate = Standard_False;
98 Standard_Boolean RejectNode(
const BVH_Vec3d& theCornerMin,
const BVH_Vec3d& theCornerMax,
99 Standard_Real& theMetric)
const override {
100 Standard_Real tmin = 0.0;
101 Standard_Real tmax = Precision::Infinite();
102 for (
int k = 0; k < 3; ++k) {
103 const Standard_Real dk = (k == 0) ? myDir.x() : (k == 1) ? myDir.y() : myDir.z();
104 const Standard_Real ok = (k == 0) ? myOrigin.x() : (k == 1) ? myOrigin.y() : myOrigin.z();
105 const Standard_Real ck_min = (k == 0) ? theCornerMin.x()
106 : (k == 1) ? theCornerMin.y()
108 const Standard_Real ck_max = (k == 0) ? theCornerMax.x()
109 : (k == 1) ? theCornerMax.y()
111 if (std::abs(dk) < Precision::Confusion()) {
112 if (ok < ck_min - myTolerance || ok > ck_max + myTolerance) {
113 return Standard_True;
116 const Standard_Real t1 = (ck_min - ok) / dk;
117 const Standard_Real t2 = (ck_max - ok) / dk;
124 if (tmin > tmax + myTolerance) {
125 return Standard_True;
130 return Standard_False;
133 Standard_Boolean Accept(
const Standard_Integer theIndex,
const Standard_Real&)
override {
134 BVH_Vec3d v0, v1, v2;
135 myBVHSet->GetVertices(theIndex, v0, v1, v2);
136 const BVH_Vec3d edge1 = v1 - v0;
137 const BVH_Vec3d edge2 = v2 - v0;
138 const BVH_Vec3d h = BVH_Vec3d::Cross(myDir, edge2);
139 const Standard_Real a = edge1.Dot(h);
140 if (std::abs(a) < 1e-12) {
141 return Standard_False;
143 const Standard_Real f = 1.0 / a;
144 const BVH_Vec3d s = myOrigin - v0;
145 const Standard_Real u = f * s.Dot(h);
146 if (u < 0.0 || u > 1.0) {
147 return Standard_False;
149 const BVH_Vec3d q = BVH_Vec3d::Cross(s, edge1);
150 const Standard_Real v = f * myDir.Dot(q);
151 if (v < 0.0 || u + v > 1.0) {
152 return Standard_False;
154 const Standard_Real t = f * edge2.Dot(q);
155 if (std::abs(t) <= myTolerance) {
156 myOnSurface = Standard_True;
157 return Standard_True;
159 if (t < -myTolerance) {
160 return Standard_False;
163 constexpr Standard_Real kEdgeTol = 1e-6;
164 if (u < kEdgeTol || v < kEdgeTol || (1.0 - u - v) < kEdgeTol) {
165 myDegenerate = Standard_True;
167 return Standard_True;
170 Standard_Boolean RejectMetric(
const Standard_Real&)
const override {
return Standard_False; }
171 Standard_Boolean Stop()
const override {
return Standard_False; }
173 int Crossings()
const {
return myCrossings; }
174 Standard_Boolean OnSurface()
const {
return myOnSurface; }
175 Standard_Boolean Degenerate()
const {
return myDegenerate; }
180 Standard_Real myTolerance{1e-7};
182 Standard_Boolean myOnSurface{Standard_False};
183 Standard_Boolean myDegenerate{Standard_False};
186gp_Pnt ToPoint(
const G4ThreeVector& point) {
return gp_Pnt(point.x(), point.y(), point.z()); }
188G4double IntersectionTolerance() {
189 return 0.5 * G4GeometryTolerance::GetInstance()->GetSurfaceTolerance();
192TopoDS_Vertex MakeVertex(
const G4ThreeVector& point) {
193 BRep_Builder builder;
194 TopoDS_Vertex vertex;
195 builder.MakeVertex(vertex, ToPoint(point), IntersectionTolerance());
200ToPointClassification(
const TopAbs_State state) {
204 return PointClassification::kInside;
206 return PointClassification::kSurface;
209 return PointClassification::kOutside;
213G4ThreeVector FallbackNormal() {
return G4ThreeVector(0.0, 0.0, 1.0); }
215bool PointInPolygon2d(Standard_Real u, Standard_Real v,
const std::vector<gp_Pnt2d>& poly) {
216 const std::size_t n = poly.size();
218 for (std::size_t i = 0; i < n; ++i) {
219 const gp_Pnt2d& a = poly[i];
220 const gp_Pnt2d& b = poly[(i + 1) % n];
221 const Standard_Real av = a.Y();
222 const Standard_Real bv = b.Y();
223 if ((av <= v && bv > v) || (bv <= v && av > v)) {
224 const Standard_Real uCross = a.X() + (v - av) * (b.X() - a.X()) / (bv - av);
230 return (crossings % 2) == 1;
233bool PointOnPolygonBoundary2d(Standard_Real u, Standard_Real v,
const std::vector<gp_Pnt2d>& poly,
235 const Standard_Real tol2 = tol * tol;
236 const std::size_t n = poly.size();
237 for (std::size_t i = 0; i < n; ++i) {
238 const gp_Pnt2d& a = poly[i];
239 const gp_Pnt2d& b = poly[(i + 1) % n];
240 const Standard_Real dx = b.X() - a.X();
241 const Standard_Real dy = b.Y() - a.Y();
242 const Standard_Real len2 = dx * dx + dy * dy;
243 Standard_Real px = 0.0;
244 Standard_Real py = 0.0;
245 if (len2 < 1.0e-20) {
249 const Standard_Real t_seg =
250 std::max(0.0, std::min(1.0, ((u - a.X()) * dx + (v - a.Y()) * dy) / len2));
251 px = a.X() + t_seg * dx;
252 py = a.Y() + t_seg * dy;
254 const Standard_Real dist2 = (u - px) * (u - px) + (v - py) * (v - py);
262std::optional<Standard_Real> RayPlaneFaceHit(
const gp_Lin& ray,
const gp_Pln& plane,
263 const std::vector<gp_Pnt2d>& uvPoly,
264 Standard_Real tMin, Standard_Real tolerance,
265 Standard_Real* u_out =
nullptr,
266 Standard_Real* v_out =
nullptr) {
267 const gp_Dir& lineDir = ray.Direction();
268 const gp_Dir& plnNormal = plane.Axis().Direction();
269 const Standard_Real denom =
270 plnNormal.X() * lineDir.X() + plnNormal.Y() * lineDir.Y() + plnNormal.Z() * lineDir.Z();
271 if (std::abs(denom) < 1.0e-10) {
274 const gp_Pnt& orig = ray.Location();
275 const gp_Pnt& planePt = plane.Location();
276 const Standard_Real numer = plnNormal.X() * (planePt.X() - orig.X()) +
277 plnNormal.Y() * (planePt.Y() - orig.Y()) +
278 plnNormal.Z() * (planePt.Z() - orig.Z());
279 const Standard_Real t = numer / denom;
283 const gp_Pnt hitPt(orig.X() + t * lineDir.X(), orig.Y() + t * lineDir.Y(),
284 orig.Z() + t * lineDir.Z());
285 Standard_Real u = 0.0;
286 Standard_Real v = 0.0;
287 ElSLib::PlaneParameters(plane.Position(), hitPt, u, v);
288 if (!PointInPolygon2d(u, v, uvPoly) && !PointOnPolygonBoundary2d(u, v, uvPoly, tolerance)) {
291 if (u_out !=
nullptr) {
294 if (v_out !=
nullptr) {
300std::optional<G4ThreeVector> TryGetOutwardNormal(
const BRepAdaptor_Surface& surface,
301 const TopoDS_Face& face,
const Standard_Real u,
302 const Standard_Real v) {
303 Standard_Real adjustedU = u;
304 Standard_Real adjustedV = v;
305 const Standard_Real tolerance = IntersectionTolerance();
307 const Standard_Real uFirst = surface.FirstUParameter();
308 const Standard_Real uLast = surface.LastUParameter();
309 const Standard_Real uEpsilon = std::min(
310 std::max(surface.UResolution(tolerance), Precision::PConfusion()), 0.5 * (uLast - uFirst));
311 if (std::abs(adjustedU - uFirst) <= uEpsilon) {
312 adjustedU = std::min(uFirst + uEpsilon, uLast);
313 }
else if (std::abs(adjustedU - uLast) <= uEpsilon) {
314 adjustedU = std::max(uLast - uEpsilon, uFirst);
318 const Standard_Real vFirst = surface.FirstVParameter();
319 const Standard_Real vLast = surface.LastVParameter();
320 const Standard_Real vEpsilon = std::min(
321 std::max(surface.VResolution(tolerance), Precision::PConfusion()), 0.5 * (vLast - vFirst));
322 if (std::abs(adjustedV - vFirst) <= vEpsilon) {
323 adjustedV = std::min(vFirst + vEpsilon, vLast);
324 }
else if (std::abs(adjustedV - vLast) <= vEpsilon) {
325 adjustedV = std::max(vLast - vEpsilon, vFirst);
329 BRepLProp_SLProps props(surface, adjustedU, adjustedV, 1, tolerance);
330 if (!props.IsNormalDefined()) {
331 const Standard_Real vFirst = surface.FirstVParameter();
332 const Standard_Real vLast = surface.LastVParameter();
333 const Standard_Real vMid = 0.5 * (vFirst + vLast);
334 const bool nearVFirst = (adjustedV < vMid);
335 const Standard_Real vRes = std::max(surface.VResolution(tolerance), Precision::PConfusion());
336 Standard_Real finalRetryV = adjustedV;
337 for (
int attempt = 0; attempt < 8 && !props.IsNormalDefined(); ++attempt) {
338 const Standard_Real scale = std::pow(10.0,
static_cast<Standard_Real
>(attempt));
339 const Standard_Real nudge = scale * vRes;
341 nearVFirst ? std::min(adjustedV + nudge, vMid) : std::max(adjustedV - nudge, vMid);
342 props = BRepLProp_SLProps(surface, adjustedU, finalRetryV, 1, tolerance);
344 if (!props.IsNormalDefined()) {
347 constexpr Standard_Real kMaxRetryVDriftFraction = 0.10;
348 if (std::fabs(finalRetryV - adjustedV) > kMaxRetryVDriftFraction * (vLast - vFirst)) {
353 gp_Dir faceNormal = props.Normal();
354 if (face.Orientation() == TopAbs_REVERSED) {
355 faceNormal.Reverse();
358 return G4ThreeVector(faceNormal.X(), faceNormal.Y(), faceNormal.Z());
366 if (fShape.IsNull()) {
367 throw std::invalid_argument(
"G4OCCTSolidKernel: shape must not be null");
373 if (shape.IsNull()) {
374 throw std::invalid_argument(
"G4OCCTSolidKernel::SetShape: shape must not be null");
379 std::unique_lock<std::mutex> lock(fVolumeAreaMutex);
380 fCachedVolume.reset();
381 fCachedSurfaceArea.reset();
384 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
387 fSurfaceCache.reset();
388 fSurfaceCacheGeneration = std::numeric_limits<std::uint64_t>::max();
389 fSurfaceCacheBuilding =
false;
391 fSurfaceCacheCV.notify_all();
393 fShapeGeneration.fetch_add(1, std::memory_order_release);
396void G4OCCTSolidKernel::ComputeBounds() {
399 for (TopExp_Explorer faceEx(fShape, TopAbs_FACE); faceEx.More(); faceEx.Next()) {
400 const TopoDS_Face& face = TopoDS::Face(faceEx.Current());
401 if (BRepAdaptor_Surface(face).GetType() != GeomAbs_Plane) {
404 for (TopExp_Explorer edgeEx(face, TopAbs_EDGE); edgeEx.More(); edgeEx.Next()) {
405 BRepLib::BuildPCurveForEdgeOnPlane(TopoDS::Edge(edgeEx.Current()), face);
410 BRepBndLib::AddOptimal(fShape, boundingBox, Standard_False);
411 if (boundingBox.IsVoid()) {
412 throw std::invalid_argument(
413 "G4OCCTSolidKernel: shape has no computable bounding box (no geometry)");
416 Standard_Real xMin = 0.0;
417 Standard_Real yMin = 0.0;
418 Standard_Real zMin = 0.0;
419 Standard_Real xMax = 0.0;
420 Standard_Real yMax = 0.0;
421 Standard_Real zMax = 0.0;
422 boundingBox.Get(xMin, yMin, zMin, xMax, yMax, zMax);
424 AxisAlignedBounds{G4ThreeVector(xMin, yMin, zMin), G4ThreeVector(xMax, yMax, zMax)};
426 fFaceBoundsCache.clear();
427 G4double maxFaceDiag = 0.0;
428 for (TopExp_Explorer ex(fShape, TopAbs_FACE); ex.More(); ex.Next()) {
430 BRepBndLib::AddOptimal(ex.Current(), faceBox, Standard_False);
431 const TopoDS_Face& currentFace = TopoDS::Face(ex.Current());
432 BRepAdaptor_Surface adaptor(currentFace);
433 std::optional<gp_Pln> maybePlane;
434 std::vector<gp_Pnt2d> uvPolygon;
435 std::optional<G4ThreeVector> outwardNormal;
436 if (adaptor.GetType() == GeomAbs_Plane) {
440 maybePlane = adaptor.Plane();
441 const gp_Ax3& pos = maybePlane->Position();
442 const TopoDS_Wire wire = BRepTools::OuterWire(currentFace);
443 if (!wire.IsNull()) {
444 bool allLinear =
true;
445 std::vector<gp_Pnt2d> poly;
446 for (BRepTools_WireExplorer we(wire, currentFace); we.More(); we.Next()) {
447 const BRepAdaptor_Curve ec(we.Current());
448 if (ec.GetType() != GeomAbs_Line) {
452 const gp_Pnt pt = BRep_Tool::Pnt(we.CurrentVertex());
453 Standard_Real u = 0.0;
454 Standard_Real v = 0.0;
455 ElSLib::PlaneParameters(pos, pt, u, v);
456 poly.emplace_back(u, v);
458 if (allLinear && poly.size() >= 3) {
460 for (TopExp_Explorer wc(currentFace, TopAbs_WIRE); wc.More(); wc.Next()) {
466 if (wireCount == 1) {
467 uvPolygon = std::move(poly);
468 gp_Dir faceNormal = maybePlane->Axis().Direction();
469 if (currentFace.Orientation() == TopAbs_REVERSED) {
470 faceNormal.Reverse();
472 outwardNormal = G4ThreeVector(faceNormal.X(), faceNormal.Y(), faceNormal.Z());
477 fFaceBoundsCache.push_back({currentFace, faceBox, std::move(adaptor), std::move(maybePlane),
478 std::move(uvPolygon), std::move(outwardNormal)});
479 if (!faceBox.IsVoid()) {
480 Standard_Real fx0 = 0.0;
481 Standard_Real fy0 = 0.0;
482 Standard_Real fz0 = 0.0;
483 Standard_Real fx1 = 0.0;
484 Standard_Real fy1 = 0.0;
485 Standard_Real fz1 = 0.0;
486 faceBox.Get(fx0, fy0, fz0, fx1, fy1, fz1);
487 const G4double diag = G4ThreeVector(fx1 - fx0, fy1 - fy0, fz1 - fz0).mag();
488 maxFaceDiag = std::max(maxFaceDiag, diag);
493 fAllFacesPlanar = std::all_of(fFaceBoundsCache.begin(), fFaceBoundsCache.end(),
494 [](
const FaceBounds& fb) { return fb.plane.has_value(); });
501 NCollection_Vector<TopoDS_Shape> faces;
502 for (TopExp_Explorer ex(fShape, TopAbs_FACE); ex.More(); ex.Next()) {
503 faces.Append(ex.Current());
505 if (faces.IsEmpty()) {
506 fTriangleSet.Nullify();
507 fFaceDeflections.clear();
509 fTriangleSet =
new BRepExtrema_TriangleSet(faces);
510 fFaceDeflections.clear();
511 fFaceDeflections.reserve(fFaceBoundsCache.size());
512 for (
const FaceBounds& fb : fFaceBoundsCache) {
513 G4double deflection = fBVHDeflection;
514 if (!fb.box.IsVoid()) {
515 Standard_Real fx0 = 0.0, fy0 = 0.0, fz0 = 0.0;
516 Standard_Real fx1 = 0.0, fy1 = 0.0, fz1 = 0.0;
517 fb.box.Get(fx0, fy0, fz0, fx1, fy1, fz1);
518 const G4double faceDiag = G4ThreeVector(fx1 - fx0, fy1 - fy0, fz1 - fz0).mag();
521 fFaceDeflections.push_back(deflection);
525 ComputeInitialSpheres();
528void G4OCCTSolidKernel::ComputeInitialSpheres() {
529 fInitialSpheres.clear();
531 const G4double tol = IntersectionTolerance();
532 const G4ThreeVector& bmin = fCachedBounds.
min;
533 const G4ThreeVector& bmax = fCachedBounds.
max;
534 const G4ThreeVector centre = 0.5 * (bmin + bmax);
535 const G4ThreeVector halfExt = 0.5 * (bmax - bmin);
537 std::vector<G4ThreeVector> candidates;
538 candidates.reserve(15);
539 candidates.push_back(centre);
540 for (
const G4double s : {-0.5, 0.5}) {
541 candidates.push_back(centre + G4ThreeVector(s * halfExt.x(), 0.0, 0.0));
542 candidates.push_back(centre + G4ThreeVector(0.0, s * halfExt.y(), 0.0));
543 candidates.push_back(centre + G4ThreeVector(0.0, 0.0, s * halfExt.z()));
545 for (
const int sx : {-1, 1}) {
546 for (
const int sy : {-1, 1}) {
547 for (
const int sz : {-1, 1}) {
548 candidates.push_back(centre + G4ThreeVector(0.75 * sx * halfExt.x(),
549 0.75 * sy * halfExt.y(),
550 0.75 * sz * halfExt.z()));
555 BRepClass3d_SolidClassifier localClassifier;
556 localClassifier.Load(fShape);
560 for (
const G4ThreeVector& cand : candidates) {
561 localClassifier.Perform(ToPoint(cand), tol);
562 if (localClassifier.State() != TopAbs_IN) {
565 G4double d = BVHLowerBoundDistance(cand);
567 const auto match = TryFindClosestFace(fFaceBoundsCache, cand);
568 if (!match.has_value() || match->distance <= tol) {
573 fInitialSpheres.push_back({cand, d});
575 std::sort(fInitialSpheres.begin(), fInitialSpheres.end(),
576 [](
const InscribedSphere& a,
const InscribedSphere& b) { return a.radius > b.radius; });
579std::optional<G4OCCTSolidKernel::ClosestFaceMatch>
580G4OCCTSolidKernel::TryFindClosestFace(
const std::vector<FaceBounds>& faceBoundsCache,
581 const G4ThreeVector& point, G4double maxDistance) {
582 if (faceBoundsCache.empty()) {
586 const gp_Pnt queryPoint = ToPoint(point);
587 const TopoDS_Vertex queryVertex = MakeVertex(point);
589 queryBox.Add(queryPoint);
591 std::optional<ClosestFaceMatch> bestMatch;
592 for (std::size_t i = 0; i < faceBoundsCache.size(); ++i) {
593 const FaceBounds& fb = faceBoundsCache[i];
594 const G4double threshold = bestMatch.has_value() ? bestMatch->distance : maxDistance;
599 BRepExtrema_DistShapeShape distance(queryVertex, fb.face);
600 if (!distance.IsDone() || distance.NbSolution() == 0) {
603 const G4double candidateDistance = distance.Value();
605 if (bestMatch.has_value() && candidateDistance >= bestMatch->distance) {
608 ClosestFaceMatch match{.face = fb.face, .distance = candidateDistance, .faceIndex = i};
609 if (distance.NbSolution() > 0 && distance.SupportTypeShape2(1) == BRepExtrema_IsInFace) {
610 Standard_Real u = 0.0;
611 Standard_Real v = 0.0;
612 distance.ParOnFaceS2(1, u, v);
613 match.uv = std::make_pair(u, v);
615 bestMatch = std::move(match);
621BRepClass3d_SolidClassifier&
622G4OCCTSolidKernel::GetOrCreateClassifier(ClassifierCache& cache)
const {
623 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
624 if (cache.generation != currentGen) {
625 cache.classifier.emplace();
626 cache.classifier->Load(fShape);
627 cache.generation = currentGen;
629 return *cache.classifier;
632G4OCCTSolidKernel::IntersectorCache&
633G4OCCTSolidKernel::GetOrCreateIntersector(IntersectorCache& cache)
const {
634 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
635 if (cache.generation != currentGen) {
636 const G4double tol = IntersectionTolerance();
637 cache.faceIntersectors.clear();
638 cache.faceIntersectors.reserve(fFaceBoundsCache.size());
639 cache.expandedBoxes.clear();
640 cache.expandedBoxes.reserve(fFaceBoundsCache.size());
641 for (
const auto& fb : fFaceBoundsCache) {
642 cache.faceIntersectors.push_back(std::make_unique<IntCurvesFace_Intersector>(fb.face, tol));
643 Bnd_Box expanded = fb.box;
644 expanded.Enlarge(tol);
645 cache.expandedBoxes.push_back(std::move(expanded));
647 cache.generation = currentGen;
652G4OCCTSolidKernel::SphereCacheData&
653G4OCCTSolidKernel::GetOrInitSphereCache(SphereCacheData& cache)
const {
654 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
655 if (cache.generation != currentGen) {
656 cache.spheres = fInitialSpheres;
657 cache.generation = currentGen;
662void G4OCCTSolidKernel::TryInsertSphere(SphereCacheData& cache,
const G4ThreeVector& centre,
667 const G4double minRadius = IntersectionTolerance();
668 if (d <= minRadius) {
671 GetOrInitSphereCache(cache);
677 for (
const InscribedSphere& s : cache.spheres) {
679 const G4double gap = s.radius - d;
680 if ((centre - s.centre).mag2() <= gap * gap) {
686 const InscribedSphere newSphere{centre, d};
687 const auto it = std::lower_bound(
688 cache.spheres.begin(), cache.spheres.end(), newSphere,
689 [](
const InscribedSphere& a,
const InscribedSphere& b) { return a.radius > b.radius; });
690 cache.spheres.insert(it, newSphere);
692 cache.spheres.pop_back();
700 const G4double tolerance = IntersectionTolerance();
701 if (p.x() < fCachedBounds.
min.x() - tolerance || p.x() > fCachedBounds.
max.x() + tolerance ||
702 p.y() < fCachedBounds.
min.y() - tolerance || p.y() > fCachedBounds.
max.y() + tolerance ||
703 p.z() < fCachedBounds.
min.z() - tolerance || p.z() > fCachedBounds.
max.z() + tolerance) {
707 const SphereCacheData& localSphereCache = GetOrInitSphereCache(sphereCache);
709 const G4double interiorRadius = s.radius - tolerance;
710 if (interiorRadius > 0.0 && (p - s.centre).mag2() < interiorRadius * interiorRadius) {
715 if (!fTriangleSet.IsNull() && fTriangleSet->Size() > 0) {
719 if (fBVHDeflection > 0.0) {
720 const G4double bvhLB = BVHLowerBoundDistance(p);
721 if (bvhLB < tolerance) {
722 auto&
classifier = GetOrCreateClassifier(classifierCache);
724 return ToPointClassification(
classifier.State());
728 const BVH_Vec3d bvhOrigin(p.x(), p.y(), p.z());
729 const Standard_Real bvhTol =
static_cast<Standard_Real
>(tolerance);
730 TriangleRayCast caster;
731 caster.SetBVHSet(fTriangleSet.get());
732 caster.SetRay(bvhOrigin, BVH_Vec3d(0.0, 0.0, 1.0), bvhTol);
735 if (caster.OnSurface()) {
738 if (!caster.Degenerate() && caster.Crossings() > 0) {
747 int outsideVotes = 0;
748 if (!caster.Degenerate()) {
749 if (caster.Crossings() % 2 == 1) {
756 const BVH_Vec3d kExtraRays[2] = {
757 BVH_Vec3d(1.0, 0.0, 0.0),
758 BVH_Vec3d(0.0, 1.0, 0.0),
760 for (
const BVH_Vec3d& dir : kExtraRays) {
761 caster.SetRay(bvhOrigin, dir, bvhTol);
763 if (caster.OnSurface()) {
766 if (!caster.Degenerate()) {
767 if (caster.Crossings() % 2 == 1) {
775 if (insideVotes > outsideVotes) {
778 if (outsideVotes > insideVotes) {
782 auto&
classifier = GetOrCreateClassifier(classifierCache);
784 return ToPointClassification(
classifier.State());
788 const gp_Lin ray(ToPoint(p), gp_Dir(0.0, 0.0, 1.0));
790 bool onSurface =
false;
791 bool degenerateRay =
false;
793 for (std::size_t i = 0; i < fFaceBoundsCache.size(); ++i) {
797 const FaceBounds& fb = fFaceBoundsCache[i];
798 if (!fb.uvPolygon.empty()) {
801 Standard_Real u_hit = 0.0;
802 Standard_Real v_hit = 0.0;
804 RayPlaneFaceHit(ray, *fb.plane, fb.uvPolygon, -tolerance, tolerance, &u_hit, &v_hit);
806 const G4double w =
static_cast<G4double
>(*t);
807 if (std::abs(w) <= tolerance) {
809 }
else if (w > tolerance) {
810 if (PointOnPolygonBoundary2d(u_hit, v_hit, fb.uvPolygon, tolerance)) {
811 degenerateRay =
true;
819 fi.Perform(ray, -tolerance, Precision::Infinite());
823 for (Standard_Integer j = 1; j <= fi.NbPnt(); ++j) {
824 const G4double w = fi.WParameter(j);
825 const TopAbs_State state = fi.State(j);
826 if (std::abs(w) <= tolerance && (state == TopAbs_IN || state == TopAbs_ON)) {
828 }
else if (w > tolerance && state == TopAbs_IN) {
830 }
else if (w > tolerance && state == TopAbs_ON) {
831 degenerateRay =
true;
840 if (crossings == 0 || degenerateRay) {
841 auto&
classifier = GetOrCreateClassifier(classifierCache);
843 return ToPointClassification(
classifier.State());
849 if (fAllFacesPlanar) {
850 const gp_Pnt pt = ToPoint(p);
851 const FaceBounds* bestFB =
nullptr;
853 for (
const FaceBounds& fb : fFaceBoundsCache) {
854 if (!fb.plane.has_value() || !fb.outwardNormal.has_value()) {
857 const G4double d = fb.plane->Distance(pt);
864 return *bestFB->outwardNormal;
868 const auto projectAndGetNormalFallback = [&](
const FaceBounds& fb) -> G4ThreeVector {
870 const Handle(Geom_Surface) surface = BRep_Tool::Surface(fb.face, loc);
871 if (surface.IsNull()) {
872 return FallbackNormal();
874 gp_Pnt pLocal = ToPoint(p);
875 if (!loc.IsIdentity()) {
876 pLocal.Transform(loc.Transformation().Inverted());
878 GeomAPI_ProjectPointOnSurf projection(pLocal, surface);
879 if (projection.NbPoints() == 0) {
880 return FallbackNormal();
882 Standard_Real u = 0.0;
883 Standard_Real v = 0.0;
884 projection.LowerDistanceParameters(u, v);
885 return TryGetOutwardNormal(fb.adaptor, fb.face, u, v).value_or(FallbackNormal());
888 const G4double bvhLB = BVHLowerBoundDistance(p);
893 ? bvhLB + 2.0 * fBVHDeflection
895 const auto closestFaceMatch = TryFindClosestFace(fFaceBoundsCache, p, seedDist);
896 if (!closestFaceMatch.has_value()) {
897 return FallbackNormal();
899 const FaceBounds& fb = fFaceBoundsCache[closestFaceMatch->faceIndex];
901 if (fb.outwardNormal.has_value()) {
902 return *fb.outwardNormal;
904 if (closestFaceMatch->uv.has_value()) {
905 const auto [u, v] = *closestFaceMatch->uv;
906 const auto result = TryGetOutwardNormal(fb.adaptor, fb.face, u, v);
907 if (result.has_value()) {
911 return projectAndGetNormalFallback(fb);
916 const G4double tolerance = IntersectionTolerance();
917 const gp_Lin ray(ToPoint(p), gp_Dir(v.x(), v.y(), v.z()));
921 for (std::size_t i = 0; i < fFaceBoundsCache.size(); ++i) {
925 const FaceBounds& fb = fFaceBoundsCache[i];
926 if (!fb.uvPolygon.empty()) {
927 const auto t = RayPlaneFaceHit(ray, *fb.plane, fb.uvPolygon, tolerance, tolerance);
928 if (t && *t < minDistance) {
929 minDistance =
static_cast<G4double
>(*t);
933 fi.Perform(ray, tolerance, Precision::Infinite());
937 for (Standard_Integer j = 1; j <= fi.NbPnt(); ++j) {
938 const G4double w = fi.WParameter(j);
939 if (w > tolerance && w < minDistance) {
950 auto&
classifier = GetOrCreateClassifier(classifierCache);
951 classifier.Perform(ToPoint(p), IntersectionTolerance());
956 const auto match = TryFindClosestFace(fFaceBoundsCache, p);
957 if (!match.has_value()) {
960 return (match->distance <= IntersectionTolerance()) ? 0.0 : match->distance;
963G4double G4OCCTSolidKernel::AABBLowerBound(
const G4ThreeVector& p)
const {
964 const G4ThreeVector& mn = fCachedBounds.
min;
965 const G4ThreeVector& mx = fCachedBounds.
max;
966 const G4double dx = std::max({0.0, mn.x() - p.x(), p.x() - mx.x()});
967 const G4double dy = std::max({0.0, mn.y() - p.y(), p.y() - mx.y()});
968 const G4double dz = std::max({0.0, mn.z() - p.z(), p.z() - mx.z()});
969 return std::sqrt(dx * dx + dy * dy + dz * dz);
972G4double G4OCCTSolidKernel::BVHLowerBoundDistance(
const G4ThreeVector& p)
const {
973 if (fTriangleSet.IsNull() || fTriangleSet->Size() == 0) {
976 PointToMeshDistance solver;
977 solver.SetObject(BVH_Vec3d(p.x(), p.y(), p.z()));
978 solver.SetBVHSet(fTriangleSet.get());
979 const Standard_Real meshDistSq = solver.ComputeDistance();
980 if (!solver.IsDone()) {
983 const G4double meshDist = std::sqrt(
static_cast<G4double
>(meshDistSq));
985 G4double deflection = fBVHDeflection;
986 const Standard_Integer bestIdx = solver.BestIndex();
988 const Standard_Integer faceId = fTriangleSet->GetFaceID(bestIdx);
989 if (faceId >= 0 &&
static_cast<std::size_t
>(faceId) < fFaceDeflections.size()) {
990 deflection = fFaceDeflections[
static_cast<std::size_t
>(faceId)];
997 return std::max(0.0, meshDist - deflection);
1000G4double G4OCCTSolidKernel::PlanarFaceLowerBoundDistance(
const G4ThreeVector& p)
const {
1001 const gp_Pnt pt = ToPoint(p);
1003 for (
const FaceBounds& fb : fFaceBoundsCache) {
1004 if (!fb.plane.has_value()) {
1007 const G4double d =
static_cast<G4double
>(fb.plane->Distance(pt));
1017 const G4double aabbDist = AABBLowerBound(p);
1018 if (aabbDist > IntersectionTolerance()) {
1025 const G4double bvhDist = BVHLowerBoundDistance(p);
1034 G4bool* validNorm, G4ThreeVector* n)
const {
1035 if (validNorm !=
nullptr) {
1039 const G4double tolerance = IntersectionTolerance();
1040 const gp_Lin ray(ToPoint(p), gp_Dir(v.x(), v.y(), v.z()));
1044 std::size_t minFaceIdx = std::numeric_limits<std::size_t>::max();
1045 G4double minU = 0.0;
1046 G4double minV = 0.0;
1047 bool minIsIn =
false;
1048 bool minIsFastPath =
false;
1050 for (std::size_t i = 0; i < fFaceBoundsCache.size(); ++i) {
1054 const FaceBounds& fb = fFaceBoundsCache[i];
1055 if (!fb.uvPolygon.empty()) {
1056 const auto t = RayPlaneFaceHit(ray, *fb.plane, fb.uvPolygon, tolerance, tolerance);
1057 if (t && *t < minDistance) {
1058 minDistance =
static_cast<G4double
>(*t);
1061 minIsFastPath =
true;
1065 fi.Perform(ray, tolerance, Precision::Infinite());
1069 for (Standard_Integer j = 1; j <= fi.NbPnt(); ++j) {
1070 const G4double w = fi.WParameter(j);
1071 if (w > tolerance && w < minDistance) {
1074 minU = fi.UParameter(j);
1075 minV = fi.VParameter(j);
1076 minIsIn = (fi.State(j) == TopAbs_IN || fi.State(j) == TopAbs_ON);
1077 minIsFastPath =
false;
1083 if (minFaceIdx == std::numeric_limits<std::size_t>::max() ||
1088 if (calcNorm && validNorm !=
nullptr && n !=
nullptr && minIsIn) {
1089 const FaceBounds& fb = fFaceBoundsCache[minFaceIdx];
1090 if (minIsFastPath && fb.outwardNormal.has_value()) {
1091 *n = *fb.outwardNormal;
1094 const auto outNorm = TryGetOutwardNormal(fb.adaptor, fb.face, minU, minV);
1105 const auto match = TryFindClosestFace(fFaceBoundsCache, p);
1106 if (!match.has_value()) {
1109 return (match->distance <= IntersectionTolerance()) ? 0.0 : match->distance;
1115 if (fAllFacesPlanar) {
1118 d = PlanarFaceLowerBoundDistance(p);
1123 const G4double bvhDist = BVHLowerBoundDistance(p);
1128 TryInsertSphere(sphereCache, p, d);
1133 std::unique_lock<std::mutex> lock(fVolumeAreaMutex);
1134 if (!fCachedVolume) {
1136 BRepGProp::VolumeProperties(fShape, props);
1137 fCachedVolume = props.Mass();
1139 return *fCachedVolume;
1143 std::unique_lock<std::mutex> lock(fVolumeAreaMutex);
1144 if (!fCachedSurfaceArea) {
1146 BRepGProp::SurfaceProperties(fShape, props);
1147 fCachedSurfaceArea = props.Mass();
1149 return *fCachedSurfaceArea;
1153 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
1155 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
1156 while (fSurfaceCacheBuilding) {
1157 fSurfaceCacheCV.wait(lock);
1158 if (fSurfaceCache.has_value() && fSurfaceCacheGeneration == currentGen) {
1159 return *fSurfaceCache;
1162 if (fSurfaceCache.has_value() && fSurfaceCacheGeneration == currentGen) {
1163 return *fSurfaceCache;
1167 fSurfaceCacheBuilding =
true;
1175 for (TopExp_Explorer ex(fShape, TopAbs_FACE); ex.More(); ex.Next()) {
1176 const TopoDS_Face& face = TopoDS::Face(ex.Current());
1177 TopLoc_Location loc;
1178 const Handle(Poly_Triangulation) & triangulation = BRep_Tool::Triangulation(face, loc);
1179 if (triangulation.IsNull()) {
1183 const auto faceIndex =
static_cast<std::uint32_t
>(cache.
faces.size());
1184 cache.
faces.push_back(face);
1186 const gp_Trsf& transform = loc.Transformation();
1187 const bool reverseWinding = face.Orientation() == TopAbs_REVERSED;
1189 for (Standard_Integer i = 1; i <= triangulation->NbTriangles(); ++i) {
1190 Standard_Integer idx1 = 0;
1191 Standard_Integer idx2 = 0;
1192 Standard_Integer idx3 = 0;
1193 triangulation->Triangle(i).Get(idx1, idx2, idx3);
1194 if (reverseWinding) {
1195 std::swap(idx2, idx3);
1198 const gp_Pnt q1 = triangulation->Node(idx1).Transformed(transform);
1199 const gp_Pnt q2 = triangulation->Node(idx2).Transformed(transform);
1200 const gp_Pnt q3 = triangulation->Node(idx3).Transformed(transform);
1202 const G4ThreeVector v1(q1.X(), q1.Y(), q1.Z());
1203 const G4ThreeVector v2(q2.X(), q2.Y(), q2.Z());
1204 const G4ThreeVector v3(q3.X(), q3.Y(), q3.Z());
1206 const G4double area = 0.5 * (v2 - v1).cross(v3 - v1).mag();
1212 cache.
triangles.push_back({v1, v2, v3, faceIndex});
1217 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
1218 fSurfaceCacheBuilding =
false;
1220 fSurfaceCacheCV.notify_all();
1224 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
1225 if (!(fSurfaceCache.has_value() && fSurfaceCacheGeneration == currentGen)) {
1226 fSurfaceCache = std::move(cache);
1227 fSurfaceCacheGeneration = currentGen;
1229 fSurfaceCacheBuilding =
false;
1231 fSurfaceCacheCV.notify_all();
1232 return *fSurfaceCache;
1239 G4ExceptionDescription msg;
1240 msg <<
"Tessellation of the OCCT shape";
1241 if (diagnosticName !=
nullptr) {
1242 msg <<
" for solid \"" << diagnosticName <<
"\"";
1244 msg <<
" produced no valid triangles; cannot sample a point on the surface.";
1245 G4Exception(
"G4OCCTSolidKernel::GetPointOnSurface",
"GeomMgt1001", FatalException, msg);
1246 return {0.0, 0.0, 0.0};
1249 const G4double target = G4UniformRand() * cache.
totalArea;
1250 const auto it = std::ranges::lower_bound(cache.
cumulativeAreas, target);
1251 const std::size_t idx = std::min(
static_cast<std::size_t
>(it - cache.
cumulativeAreas.begin()),
1255 G4double r1 = G4UniformRand();
1256 G4double r2 = G4UniformRand();
1257 if (r1 + r2 > 1.0) {
1262 const G4ThreeVector tessPoint =
1263 chosen.
p1 + r1 * (chosen.
p2 - chosen.
p1) + r2 * (chosen.
p3 - chosen.
p1);
1266 TopLoc_Location loc;
1267 const Handle(Geom_Surface) geomSurface = BRep_Tool::Surface(face, loc);
1268 if (!geomSurface.IsNull()) {
1269 gp_Pnt tessPointLocal(tessPoint.x(), tessPoint.y(), tessPoint.z());
1270 if (!loc.IsIdentity()) {
1271 tessPointLocal.Transform(loc.Transformation().Inverted());
1273 GeomAPI_ProjectPointOnSurf projection(tessPointLocal, geomSurface);
1274 if (projection.NbPoints() > 0) {
1275 gp_Pnt projectedPoint = projection.NearestPoint();
1276 if (!loc.IsIdentity()) {
1277 projectedPoint.Transform(loc.Transformation());
1279 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.