GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 86.1% 793 / 0 / 921
Functions: 100.0% 51 / 0 / 51
Branches: 57.1% 625 / 0 / 1094

src/G4OCCTSolidKernel.cc
Line Branch Exec Source
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 // Copyright (C) 2026 G4OCCT Contributors
3
4 #include "G4OCCTSolidKernel.hh"
5
6 #include <BRepAdaptor_Curve.hxx>
7 #include <BRepBndLib.hxx>
8 #include <BRepExtrema_DistShapeShape.hxx>
9 #include <BRepExtrema_SupportType.hxx>
10 #include <BRepGProp.hxx>
11 #include <BRepLib.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>
20 #include <ElSLib.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>
34 #include <TopoDS.hxx>
35 #include <TopoDS_Edge.hxx>
36 #include <TopoDS_Vertex.hxx>
37 #include <TopoDS_Wire.hxx>
38 #include <gp_Dir.hxx>
39 #include <gp_Lin.hxx>
40 #include <gp_Pln.hxx>
41 #include <gp_Pnt.hxx>
42 #include <gp_Pnt2d.hxx>
43 #include <gp_Vec.hxx>
44
45 #include <G4Exception.hh>
46 #include <G4GeometryTolerance.hh>
47 #include <Randomize.hh>
48
49 #include <algorithm>
50 #include <cmath>
51 #include <optional>
52 #include <ranges>
53 #include <stdexcept>
54
55 namespace {
56
57 1061106 class PointToMeshDistance
58 : public BVH_Distance<Standard_Real, 3, BVH_Vec3d, BRepExtrema_TriangleSet> {
59 public:
60 126514032 Standard_Boolean RejectNode(const BVH_Vec3d& theCornerMin, const BVH_Vec3d& theCornerMax,
61 Standard_Real& theMetric) const override {
62 126514032 theMetric =
63 126514032 BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(myObject, theCornerMin, theCornerMax);
64 126514032 return RejectMetric(theMetric);
65 }
66
67 75165751 Standard_Boolean Accept(const Standard_Integer theIndex, const Standard_Real&) override {
68 75165751 BVH_Vec3d v0, v1, v2;
69 75165751 myBVHSet->GetVertices(theIndex, v0, v1, v2);
70 75165751 const Standard_Real sq =
71 75165751 BVH_Tools<Standard_Real, 3>::PointTriangleSquareDistance(myObject, v0, v1, v2);
72
2/2
✓ Branch 0 taken 3987732 times.
✓ Branch 1 taken 71178019 times.
75165751 if (sq < myDistance) {
73 3987732 myDistance = sq;
74 3987732 myBestIndex = theIndex;
75 3987732 return Standard_True;
76 }
77 71178019 return Standard_False;
78 75165751 }
79
80 1061106 Standard_Integer BestIndex() const { return myBestIndex; }
81
82 private:
83 1061106 Standard_Integer myBestIndex{-1};
84 };
85
86
1/2
✓ Branch 0 taken 661838 times.
✗ Branch 1 not taken.
661838 class TriangleRayCast
87 : public BVH_Traverse<Standard_Real, 3, BRepExtrema_TriangleSet, Standard_Real> {
88 public:
89 1555354 void SetRay(const BVH_Vec3d& theOrigin, const BVH_Vec3d& theDir, Standard_Real theTolerance) {
90 1555354 myOrigin = theOrigin;
91 1555354 myDir = theDir;
92 1555354 myTolerance = theTolerance;
93 1555354 myCrossings = 0;
94 1555354 myDegenerate = Standard_False;
95 1555354 }
96
97 65033096 Standard_Boolean RejectNode(const BVH_Vec3d& theCornerMin, const BVH_Vec3d& theCornerMax,
98 Standard_Real& theMetric) const override {
99 65033096 Standard_Real tmin = 0.0;
100 65033096 Standard_Real tmax = Precision::Infinite();
101
2/2
✓ Branch 0 taken 34749661 times.
✓ Branch 1 taken 158822939 times.
193572600 for (int k = 0; k < 3; ++k) {
102
4/4
✓ Branch 0 taken 93789843 times.
✓ Branch 1 taken 65033096 times.
✓ Branch 2 taken 51806538 times.
✓ Branch 3 taken 41983305 times.
158822939 const Standard_Real dk = (k == 0) ? myDir.x() : (k == 1) ? myDir.y() : myDir.z();
103
4/4
✓ Branch 0 taken 93789843 times.
✓ Branch 1 taken 65033096 times.
✓ Branch 2 taken 51806538 times.
✓ Branch 3 taken 41983305 times.
158822939 const Standard_Real ok = (k == 0) ? myOrigin.x() : (k == 1) ? myOrigin.y() : myOrigin.z();
104
2/2
✓ Branch 0 taken 93789843 times.
✓ Branch 1 taken 65033096 times.
252612782 const Standard_Real ck_min = (k == 0) ? theCornerMin.x()
105
2/2
✓ Branch 0 taken 51806538 times.
✓ Branch 1 taken 41983305 times.
93789843 : (k == 1) ? theCornerMin.y()
106 41983305 : theCornerMin.z();
107
2/2
✓ Branch 0 taken 93789843 times.
✓ Branch 1 taken 65033096 times.
252612782 const Standard_Real ck_max = (k == 0) ? theCornerMax.x()
108
2/2
✓ Branch 0 taken 51806538 times.
✓ Branch 1 taken 41983305 times.
93789843 : (k == 1) ? theCornerMax.y()
109 41983305 : theCornerMax.z();
110
2/2
✓ Branch 0 taken 111017370 times.
✓ Branch 1 taken 47805569 times.
158822939 if (std::abs(dk) < Precision::Confusion()) {
111
4/4
✓ Branch 0 taken 97116048 times.
✓ Branch 1 taken 13901322 times.
✓ Branch 2 taken 12832953 times.
✓ Branch 3 taken 84283095 times.
111017370 if (ok < ck_min - myTolerance || ok > ck_max + myTolerance) {
112 26734275 return Standard_True;
113 }
114 84283095 } else {
115 47805569 const Standard_Real t1 = (ck_min - ok) / dk;
116 47805569 const Standard_Real t2 = (ck_max - ok) / dk;
117
2/2
✓ Branch 0 taken 26443326 times.
✓ Branch 1 taken 21362243 times.
47805569 if (t1 > tmin) {
118 21362243 tmin = t1;
119 21362243 }
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 47805569 times.
47805569 if (t2 < tmax) {
121 47805569 tmax = t2;
122 47805569 }
123
2/2
✓ Branch 0 taken 44256409 times.
✓ Branch 1 taken 3549160 times.
47805569 if (tmin > tmax + myTolerance) {
124 3549160 return Standard_True;
125 }
126 }
127 128539504 }
128 34749661 theMetric = tmin;
129 34749661 return Standard_False;
130 65033096 }
131
132 12576058 Standard_Boolean Accept(const Standard_Integer theIndex, const Standard_Real&) override {
133 12576058 BVH_Vec3d v0, v1, v2;
134 12576058 myBVHSet->GetVertices(theIndex, v0, v1, v2);
135 12576058 const BVH_Vec3d edge1 = v1 - v0;
136 12576058 const BVH_Vec3d edge2 = v2 - v0;
137 12576058 const BVH_Vec3d h = BVH_Vec3d::Cross(myDir, edge2);
138 12576058 const Standard_Real a = edge1.Dot(h);
139
2/2
✓ Branch 0 taken 1279600 times.
✓ Branch 1 taken 11296458 times.
12576058 if (std::abs(a) < 1e-12) {
140 1279600 return Standard_False;
141 }
142 11296458 const Standard_Real f = 1.0 / a;
143 11296458 const BVH_Vec3d s = myOrigin - v0;
144 11296458 const Standard_Real u = f * s.Dot(h);
145
4/4
✓ Branch 0 taken 7281318 times.
✓ Branch 1 taken 4015140 times.
✓ Branch 2 taken 2126182 times.
✓ Branch 3 taken 5155136 times.
11296458 if (u < 0.0 || u > 1.0) {
146 6141322 return Standard_False;
147 }
148 5155136 const BVH_Vec3d q = BVH_Vec3d::Cross(s, edge1);
149 5155136 const Standard_Real v = f * myDir.Dot(q);
150
4/4
✓ Branch 0 taken 4060874 times.
✓ Branch 1 taken 1094262 times.
✓ Branch 2 taken 1592381 times.
✓ Branch 3 taken 2468493 times.
5155136 if (v < 0.0 || u + v > 1.0) {
151 2686643 return Standard_False;
152 }
153 2468493 const Standard_Real t = f * edge2.Dot(q);
154
2/2
✓ Branch 0 taken 232868 times.
✓ Branch 1 taken 2235625 times.
2468493 if (t < -myTolerance) {
155 232868 return Standard_False;
156 }
157 2235625 ++myCrossings;
158 2235625 constexpr Standard_Real kEdgeTol = 1e-6;
159
6/6
✓ Branch 0 taken 1774804 times.
✓ Branch 1 taken 460821 times.
✓ Branch 2 taken 1083576 times.
✓ Branch 3 taken 691228 times.
✓ Branch 4 taken 57607 times.
✓ Branch 5 taken 1025969 times.
2235625 if (u < kEdgeTol || v < kEdgeTol || (1.0 - u - v) < kEdgeTol) {
160 1209656 myDegenerate = Standard_True;
161 1209656 }
162 2235625 return Standard_True;
163 12576058 }
164
165 8256916 Standard_Boolean RejectMetric(const Standard_Real&) const override { return Standard_False; }
166 77609154 Standard_Boolean Stop() const override { return Standard_False; }
167
168 1756364 int Crossings() const { return myCrossings; }
169 2002112 Standard_Boolean Degenerate() const { return myDegenerate; }
170
171 private:
172 BVH_Vec3d myOrigin;
173 BVH_Vec3d myDir;
174 661838 Standard_Real myTolerance{1e-7};
175 661838 int myCrossings{0};
176 661838 Standard_Boolean myDegenerate{Standard_False};
177 };
178
179 3803259 gp_Pnt ToPoint(const G4ThreeVector& point) { return gp_Pnt(point.x(), point.y(), point.z()); }
180
181 11099016 G4double IntersectionTolerance() {
182 11099016 return 0.5 * G4GeometryTolerance::GetInstance()->GetSurfaceTolerance();
183 }
184
185 339042 TopoDS_Vertex MakeVertex(const G4ThreeVector& point) {
186 BRep_Builder builder;
187 339042 TopoDS_Vertex vertex;
188
3/6
✓ Branch 0 taken 339042 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 339042 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 339042 times.
✗ Branch 5 not taken.
339042 builder.MakeVertex(vertex, ToPoint(point), IntersectionTolerance());
189 339042 return vertex;
190 339042 }
191
192 g4occt::detail::G4OCCTSolidKernel::PointClassification
193 139398 ToPointClassification(const TopAbs_State state) {
194 using PointClassification = g4occt::detail::G4OCCTSolidKernel::PointClassification;
195
3/4
✓ Branch 0 taken 235 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17639 times.
✓ Branch 3 taken 121524 times.
139398 switch (state) {
196 case TopAbs_IN:
197 17639 return PointClassification::kInside;
198 case TopAbs_ON:
199 235 return PointClassification::kSurface;
200 121524 case TopAbs_OUT:
201 default:
202 121524 return PointClassification::kOutside;
203 }
204 139398 }
205
206 34 G4ThreeVector FallbackNormal() { return G4ThreeVector(0.0, 0.0, 1.0); }
207
208 673170 bool PointInPolygon2d(Standard_Real u, Standard_Real v, const std::vector<gp_Pnt2d>& poly) {
209 673170 const std::size_t n = poly.size();
210 673170 int crossings = 0;
211
2/2
✓ Branch 0 taken 2827510 times.
✓ Branch 1 taken 490622 times.
3318132 for (std::size_t i = 0; i < n; ++i) {
212 2827510 const gp_Pnt2d& a = poly[i];
213 2827510 const gp_Pnt2d& b = poly[(i + 1) % n];
214 2827510 const Standard_Real av = a.Y();
215 2827510 const Standard_Real bv = b.Y();
216
6/6
✓ Branch 0 taken 1365902 times.
✓ Branch 1 taken 1461608 times.
✓ Branch 2 taken 91274 times.
✓ Branch 3 taken 1274628 times.
✓ Branch 4 taken 1365902 times.
✓ Branch 5 taken 1370334 times.
2827510 if ((av <= v && bv > v) || (bv <= v && av > v)) {
217 1457176 const Standard_Real uCross = a.X() + (v - av) * (b.X() - a.X()) / (bv - av);
218
2/2
✓ Branch 0 taken 671247 times.
✓ Branch 1 taken 603381 times.
1457176 if (u < uCross) {
219 603381 ++crossings;
220 603381 }
221 1274628 }
222 2644962 }
223 490622 return (crossings % 2) == 1;
224 }
225
226 133211 bool PointOnPolygonBoundary2d(Standard_Real u, Standard_Real v, const std::vector<gp_Pnt2d>& poly,
227 Standard_Real tol) {
228 133211 const Standard_Real tol2 = tol * tol;
229 133211 const std::size_t n = poly.size();
230
2/2
✓ Branch 0 taken 490814 times.
✓ Branch 1 taken 133192 times.
624006 for (std::size_t i = 0; i < n; ++i) {
231 490814 const gp_Pnt2d& a = poly[i];
232 490814 const gp_Pnt2d& b = poly[(i + 1) % n];
233 490814 const Standard_Real dx = b.X() - a.X();
234 490814 const Standard_Real dy = b.Y() - a.Y();
235 490814 const Standard_Real len2 = dx * dx + dy * dy;
236 490814 Standard_Real px = 0.0;
237 490814 Standard_Real py = 0.0;
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 490814 times.
490814 if (len2 < 1.0e-20) {
239 ✗ px = a.X();
240 ✗ py = a.Y();
241 ✗ } else {
242 490814 const Standard_Real t_seg =
243 490814 std::max(0.0, std::min(1.0, ((u - a.X()) * dx + (v - a.Y()) * dy) / len2));
244 490814 px = a.X() + t_seg * dx;
245 490814 py = a.Y() + t_seg * dy;
246 }
247 490814 const Standard_Real dist2 = (u - px) * (u - px) + (v - py) * (v - py);
248
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 490795 times.
490814 if (dist2 <= tol2) {
249 19 return true;
250 }
251 490795 }
252 133192 return false;
253 133211 }
254
255 738860 std::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 738860 const gp_Dir& lineDir = ray.Direction();
261 738860 const gp_Dir& plnNormal = plane.Axis().Direction();
262 738860 const Standard_Real denom =
263 738860 plnNormal.X() * lineDir.X() + plnNormal.Y() * lineDir.Y() + plnNormal.Z() * lineDir.Z();
264
2/2
✓ Branch 0 taken 5020 times.
✓ Branch 1 taken 733840 times.
738860 if (std::abs(denom) < 1.0e-10) {
265 5020 return std::nullopt;
266 }
267 733840 const gp_Pnt& orig = ray.Location();
268 733840 const gp_Pnt& planePt = plane.Location();
269 2201520 const Standard_Real numer = plnNormal.X() * (planePt.X() - orig.X()) +
270 1467680 plnNormal.Y() * (planePt.Y() - orig.Y()) +
271 733840 plnNormal.Z() * (planePt.Z() - orig.Z());
272 733840 const Standard_Real t = numer / denom;
273
2/2
✓ Branch 0 taken 60670 times.
✓ Branch 1 taken 673170 times.
733840 if (t < tMin) {
274 60670 return std::nullopt;
275 }
276 1346340 const gp_Pnt hitPt(orig.X() + t * lineDir.X(), orig.Y() + t * lineDir.Y(),
277 673170 orig.Z() + t * lineDir.Z());
278 673170 Standard_Real u = 0.0;
279 673170 Standard_Real v = 0.0;
280 673170 ElSLib::PlaneParameters(plane.Position(), hitPt, u, v);
281
4/4
✓ Branch 0 taken 133211 times.
✓ Branch 1 taken 539959 times.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 133192 times.
673170 if (!PointInPolygon2d(u, v, uvPoly) && !PointOnPolygonBoundary2d(u, v, uvPoly, tolerance)) {
282 133192 return std::nullopt;
283 }
284
1/2
✓ Branch 0 taken 539978 times.
✗ Branch 1 not taken.
539978 if (u_out != nullptr) {
285 ✗ *u_out = u;
286 ✗ }
287
1/2
✓ Branch 0 taken 539978 times.
✗ Branch 1 not taken.
539978 if (v_out != nullptr) {
288 ✗ *v_out = v;
289 ✗ }
290 539978 return t;
291 738860 }
292
293 297435 std::optional<G4ThreeVector> TryGetOutwardNormal(const BRepAdaptor_Surface& surface,
294 const TopoDS_Face& face, const Standard_Real u,
295 const Standard_Real v) {
296 297435 Standard_Real adjustedU = u;
297 297435 Standard_Real adjustedV = v;
298 297435 const Standard_Real tolerance = IntersectionTolerance();
299 {
300 297435 const Standard_Real uFirst = surface.FirstUParameter();
301 297435 const Standard_Real uLast = surface.LastUParameter();
302 297435 const Standard_Real uEpsilon = std::min(
303 297435 std::max(surface.UResolution(tolerance), Precision::PConfusion()), 0.5 * (uLast - uFirst));
304
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 297395 times.
297435 if (std::abs(adjustedU - uFirst) <= uEpsilon) {
305 40 adjustedU = std::min(uFirst + uEpsilon, uLast);
306
1/2
✓ Branch 0 taken 297395 times.
✗ Branch 1 not taken.
297435 } else if (std::abs(adjustedU - uLast) <= uEpsilon) {
307 ✗ adjustedU = std::max(uLast - uEpsilon, uFirst);
308 ✗ }
309 }
310 {
311 297435 const Standard_Real vFirst = surface.FirstVParameter();
312 297435 const Standard_Real vLast = surface.LastVParameter();
313 297435 const Standard_Real vEpsilon = std::min(
314 297435 std::max(surface.VResolution(tolerance), Precision::PConfusion()), 0.5 * (vLast - vFirst));
315
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 297426 times.
297435 if (std::abs(adjustedV - vFirst) <= vEpsilon) {
316 9 adjustedV = std::min(vFirst + vEpsilon, vLast);
317
2/2
✓ Branch 0 taken 297416 times.
✓ Branch 1 taken 10 times.
297435 } else if (std::abs(adjustedV - vLast) <= vEpsilon) {
318 10 adjustedV = std::max(vLast - vEpsilon, vFirst);
319 10 }
320 }
321
322 297435 BRepLProp_SLProps props(surface, adjustedU, adjustedV, 1, tolerance);
323
2/4
✓ Branch 0 taken 297435 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 297435 times.
297435 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;
333 ✗ finalRetryV =
334 ✗ nearVFirst ? std::min(adjustedV + nudge, vMid) : std::max(adjustedV - nudge, vMid);
335 ✗ props = BRepLProp_SLProps(surface, adjustedU, finalRetryV, 1, tolerance);
336 ✗ }
337 ✗ if (!props.IsNormalDefined()) {
338 ✗ return std::nullopt;
339 }
340 ✗ constexpr Standard_Real kMaxRetryVDriftFraction = 0.10;
341 ✗ if (std::fabs(finalRetryV - adjustedV) > kMaxRetryVDriftFraction * (vLast - vFirst)) {
342 ✗ return std::nullopt;
343 }
344 ✗ }
345
346
1/2
✓ Branch 0 taken 297435 times.
✗ Branch 1 not taken.
297435 gp_Dir faceNormal = props.Normal();
347
2/2
✓ Branch 0 taken 21681 times.
✓ Branch 1 taken 275754 times.
297435 if (face.Orientation() == TopAbs_REVERSED) {
348
1/2
✓ Branch 0 taken 21681 times.
✗ Branch 1 not taken.
21681 faceNormal.Reverse();
349 21681 }
350
351
4/8
✓ Branch 0 taken 297435 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 297435 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 297435 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 297435 times.
✗ Branch 7 not taken.
297435 return G4ThreeVector(faceNormal.X(), faceNormal.Y(), faceNormal.Z());
352 297435 }
353
354 } // namespace
355
356 namespace g4occt::detail {
357
358
2/4
✓ Branch 0 taken 263 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 263 times.
✗ Branch 3 not taken.
263 G4OCCTSolidKernel::G4OCCTSolidKernel(const TopoDS_Shape& shape) : fShape(shape) {
359
3/4
✓ Branch 0 taken 263 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 262 times.
✓ Branch 3 taken 1 time.
263 if (fShape.IsNull()) {
360
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
1 throw std::invalid_argument("G4OCCTSolidKernel: shape must not be null");
361 }
362
2/2
✓ Branch 0 taken 261 times.
✓ Branch 1 taken 1 time.
262 ComputeBounds();
363 2 }
364
365 6 void G4OCCTSolidKernel::SetShape(const TopoDS_Shape& shape) {
366
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
6 if (shape.IsNull()) {
367
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 throw std::invalid_argument("G4OCCTSolidKernel::SetShape: shape must not be null");
368 }
369 5 fShape = shape;
370 5 ComputeBounds();
371 {
372 5 std::unique_lock<std::mutex> lock(fVolumeAreaMutex);
373 5 fCachedVolume.reset();
374 5 fCachedSurfaceArea.reset();
375 5 }
376 {
377 5 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
378 // Reset both the cache payload and its bookkeeping together so waiting
379 // threads never observe "no cache" with stale generation/build metadata.
380 5 fSurfaceCache.reset();
381 5 fSurfaceCacheGeneration = std::numeric_limits<std::uint64_t>::max();
382 5 fSurfaceCacheBuilding = false;
383
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 lock.unlock();
384 5 fSurfaceCacheCV.notify_all();
385 5 }
386 5 fShapeGeneration.fetch_add(1, std::memory_order_release);
387 5 }
388
389 267 void G4OCCTSolidKernel::ComputeBounds() {
390 // Build missing PCurves for planar faces up front so the later polygon fast
391 // paths can derive stable (u, v) polygons from the OCCT face wiring.
392
4/6
✓ Branch 0 taken 7682 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✓ Branch 3 taken 267 times.
✓ Branch 4 taken 7415 times.
✗ Branch 5 not taken.
7682 for (TopExp_Explorer faceEx(fShape, TopAbs_FACE); faceEx.More(); faceEx.Next()) {
393
2/4
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✗ Branch 3 not taken.
7415 const TopoDS_Face& face = TopoDS::Face(faceEx.Current());
394
4/6
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7415 times.
✓ Branch 4 taken 2649 times.
✓ Branch 5 taken 4766 times.
7415 if (BRepAdaptor_Surface(face).GetType() != GeomAbs_Plane) {
395 4766 continue;
396 }
397
5/8
✓ Branch 0 taken 2649 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18249 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15600 times.
✓ Branch 5 taken 2649 times.
✓ Branch 6 taken 15600 times.
✗ Branch 7 not taken.
18249 for (TopExp_Explorer edgeEx(face, TopAbs_EDGE); edgeEx.More(); edgeEx.Next()) {
398
3/6
✓ Branch 0 taken 15600 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15600 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15600 times.
✗ Branch 5 not taken.
15600 BRepLib::BuildPCurveForEdgeOnPlane(TopoDS::Edge(edgeEx.Current()), face);
399 15600 }
400 2649 }
401
402 267 Bnd_Box boundingBox;
403 267 BRepBndLib::AddOptimal(fShape, boundingBox, /*useTriangulation=*/Standard_False);
404
2/2
✓ Branch 0 taken 266 times.
✓ Branch 1 taken 1 time.
267 if (boundingBox.IsVoid()) {
405
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 throw std::invalid_argument(
406 "G4OCCTSolidKernel: shape has no computable bounding box (no geometry)");
407 }
408
409 266 Standard_Real xMin = 0.0;
410 266 Standard_Real yMin = 0.0;
411 266 Standard_Real zMin = 0.0;
412 266 Standard_Real xMax = 0.0;
413 266 Standard_Real yMax = 0.0;
414 266 Standard_Real zMax = 0.0;
415 266 boundingBox.Get(xMin, yMin, zMin, xMax, yMax, zMax);
416 266 fCachedBounds =
417
1/2
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
266 AxisAlignedBounds{G4ThreeVector(xMin, yMin, zMin), G4ThreeVector(xMax, yMax, zMax)};
418
419 266 fFaceBoundsCache.clear();
420 266 G4double maxFaceDiag = 0.0;
421
4/6
✓ Branch 0 taken 7681 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✓ Branch 3 taken 266 times.
✓ Branch 4 taken 7415 times.
✗ Branch 5 not taken.
7681 for (TopExp_Explorer ex(fShape, TopAbs_FACE); ex.More(); ex.Next()) {
422
1/2
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
7415 Bnd_Box faceBox;
423
2/4
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✗ Branch 3 not taken.
7415 BRepBndLib::AddOptimal(ex.Current(), faceBox, /*useTriangulation=*/Standard_False);
424
2/4
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✗ Branch 3 not taken.
7415 const TopoDS_Face& currentFace = TopoDS::Face(ex.Current());
425
1/2
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
7415 BRepAdaptor_Surface adaptor(currentFace);
426 7415 std::optional<gp_Pln> maybePlane;
427 7415 std::vector<gp_Pnt2d> uvPolygon;
428 7415 std::optional<G4ThreeVector> outwardNormal;
429
3/4
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2649 times.
✓ Branch 3 taken 4766 times.
7415 if (adaptor.GetType() == GeomAbs_Plane) {
430 // For single-wire planar faces we cache a 2D polygon in face coordinates
431 // and the analytical outward normal. That lets Inside/Distance* use a
432 // cheap ray-plane + polygon test instead of the heavier OCCT intersector.
433
1/2
✓ Branch 0 taken 2649 times.
✗ Branch 1 not taken.
2649 maybePlane = adaptor.Plane();
434
1/2
✓ Branch 0 taken 2649 times.
✗ Branch 1 not taken.
2649 const gp_Ax3& pos = maybePlane->Position();
435
1/2
✓ Branch 0 taken 2649 times.
✗ Branch 1 not taken.
2649 const TopoDS_Wire wire = BRepTools::OuterWire(currentFace);
436
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2649 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2649 times.
2649 if (!wire.IsNull()) {
437 2649 bool allLinear = true;
438 2649 std::vector<gp_Pnt2d> poly;
439
5/8
✓ Branch 0 taken 2649 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11959 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2115 times.
✓ Branch 5 taken 9844 times.
✓ Branch 6 taken 9310 times.
✗ Branch 7 not taken.
12493 for (BRepTools_WireExplorer we(wire, currentFace); we.More(); we.Next()) {
440
2/4
✓ Branch 0 taken 9844 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9844 times.
✗ Branch 3 not taken.
9844 const BRepAdaptor_Curve ec(we.Current());
441
3/4
✓ Branch 0 taken 9844 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9310 times.
✓ Branch 3 taken 534 times.
9844 if (ec.GetType() != GeomAbs_Line) {
442 534 allLinear = false;
443 534 break;
444 }
445
2/4
✓ Branch 0 taken 9310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9310 times.
✗ Branch 3 not taken.
9310 const gp_Pnt pt = BRep_Tool::Pnt(we.CurrentVertex());
446 9310 Standard_Real u = 0.0;
447 9310 Standard_Real v = 0.0;
448
1/2
✓ Branch 0 taken 9310 times.
✗ Branch 1 not taken.
9310 ElSLib::PlaneParameters(pos, pt, u, v);
449
1/2
✓ Branch 0 taken 9310 times.
✗ Branch 1 not taken.
9310 poly.emplace_back(u, v);
450 9844 }
451
3/4
✓ Branch 0 taken 2115 times.
✓ Branch 1 taken 534 times.
✓ Branch 2 taken 2115 times.
✗ Branch 3 not taken.
2649 if (allLinear && poly.size() >= 3) {
452 2115 int wireCount = 0;
453
5/8
✓ Branch 0 taken 2115 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4230 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2035 times.
✓ Branch 5 taken 2195 times.
✓ Branch 6 taken 2115 times.
✗ Branch 7 not taken.
4310 for (TopExp_Explorer wc(currentFace, TopAbs_WIRE); wc.More(); wc.Next()) {
454 2195 ++wireCount;
455
2/2
✓ Branch 0 taken 2115 times.
✓ Branch 1 taken 80 times.
2195 if (wireCount > 1) {
456 80 break;
457 }
458 2115 }
459
2/2
✓ Branch 0 taken 2035 times.
✓ Branch 1 taken 80 times.
2115 if (wireCount == 1) {
460 2035 uvPolygon = std::move(poly);
461
2/4
✓ Branch 0 taken 2035 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2035 times.
✗ Branch 3 not taken.
2035 gp_Dir faceNormal = maybePlane->Axis().Direction();
462
3/4
✓ Branch 0 taken 2035 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1034 times.
✓ Branch 3 taken 1001 times.
2035 if (currentFace.Orientation() == TopAbs_REVERSED) {
463
1/2
✓ Branch 0 taken 1034 times.
✗ Branch 1 not taken.
1034 faceNormal.Reverse();
464 1034 }
465
4/8
✓ Branch 0 taken 2035 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2035 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2035 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2035 times.
✗ Branch 7 not taken.
2035 outwardNormal = G4ThreeVector(faceNormal.X(), faceNormal.Y(), faceNormal.Z());
466 2035 }
467 2115 }
468 2649 }
469 2649 }
470
5/10
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7415 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7415 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 7415 times.
✗ Branch 9 not taken.
7415 fFaceBoundsCache.push_back({currentFace, faceBox, std::move(adaptor), std::move(maybePlane),
471 14830 std::move(uvPolygon), std::move(outwardNormal)});
472
2/4
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✗ Branch 3 not taken.
7415 if (!faceBox.IsVoid()) {
473 7415 Standard_Real fx0 = 0.0;
474 7415 Standard_Real fy0 = 0.0;
475 7415 Standard_Real fz0 = 0.0;
476 7415 Standard_Real fx1 = 0.0;
477 7415 Standard_Real fy1 = 0.0;
478 7415 Standard_Real fz1 = 0.0;
479
1/2
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
7415 faceBox.Get(fx0, fy0, fz0, fx1, fy1, fz1);
480
2/4
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✗ Branch 3 not taken.
7415 const G4double diag = G4ThreeVector(fx1 - fx0, fy1 - fy0, fz1 - fz0).mag();
481
1/2
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
7415 maxFaceDiag = std::max(maxFaceDiag, diag);
482 7415 }
483 7415 }
484
485 266 fBVHDeflection = kOCCTRelativeDeflection * maxFaceDiag;
486 266 fAllFacesPlanar = std::all_of(fFaceBoundsCache.begin(), fFaceBoundsCache.end(),
487 1148 [](const FaceBounds& fb) { return fb.plane.has_value(); });
488
489 {
490 266 [[maybe_unused]] const BRepMesh_IncrementalMesh mesher(fShape, kOCCTRelativeDeflection,
491 /*isRelative=*/Standard_True);
492 266 }
493
494 266 NCollection_Vector<TopoDS_Shape> faces;
495
5/8
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7681 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7415 times.
✓ Branch 5 taken 266 times.
✓ Branch 6 taken 7415 times.
✗ Branch 7 not taken.
7681 for (TopExp_Explorer ex(fShape, TopAbs_FACE); ex.More(); ex.Next()) {
496
2/4
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✗ Branch 3 not taken.
7415 faces.Append(ex.Current());
497 7415 }
498
2/4
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 266 times.
266 if (faces.IsEmpty()) {
499 ✗ fTriangleSet.Nullify();
500 ✗ fFaceDeflections.clear();
501 ✗ } else {
502
3/6
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 266 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 266 times.
✗ Branch 5 not taken.
266 fTriangleSet = new BRepExtrema_TriangleSet(faces);
503 266 fFaceDeflections.clear();
504
1/2
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
266 fFaceDeflections.reserve(fFaceBoundsCache.size());
505
2/2
✓ Branch 0 taken 7415 times.
✓ Branch 1 taken 266 times.
7681 for (const FaceBounds& fb : fFaceBoundsCache) {
506 7415 G4double deflection = fBVHDeflection;
507
2/4
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✗ Branch 3 not taken.
7415 if (!fb.box.IsVoid()) {
508 7415 Standard_Real fx0 = 0.0, fy0 = 0.0, fz0 = 0.0;
509 7415 Standard_Real fx1 = 0.0, fy1 = 0.0, fz1 = 0.0;
510
1/2
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
7415 fb.box.Get(fx0, fy0, fz0, fx1, fy1, fz1);
511
2/4
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7415 times.
✗ Branch 3 not taken.
7415 const G4double faceDiag = G4ThreeVector(fx1 - fx0, fy1 - fy0, fz1 - fz0).mag();
512 7415 deflection = kOCCTRelativeDeflection * faceDiag;
513 7415 }
514
1/2
✓ Branch 0 taken 7415 times.
✗ Branch 1 not taken.
7415 fFaceDeflections.push_back(deflection);
515 }
516 }
517
518
1/2
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
266 ComputeInitialSpheres();
519 266 }
520
521 266 void G4OCCTSolidKernel::ComputeInitialSpheres() {
522 266 fInitialSpheres.clear();
523
524 266 const G4double tol = IntersectionTolerance();
525 266 const G4ThreeVector& bmin = fCachedBounds.min;
526 266 const G4ThreeVector& bmax = fCachedBounds.max;
527
1/2
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
266 const G4ThreeVector centre = 0.5 * (bmin + bmax);
528
2/4
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 266 times.
✗ Branch 3 not taken.
266 const G4ThreeVector halfExt = 0.5 * (bmax - bmin);
529
530 266 std::vector<G4ThreeVector> candidates;
531
1/2
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
266 candidates.reserve(15);
532
1/2
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
266 candidates.push_back(centre);
533
2/2
✓ Branch 0 taken 532 times.
✓ Branch 1 taken 266 times.
798 for (const G4double s : {-0.5, 0.5}) {
534
4/8
✓ Branch 0 taken 532 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 532 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 532 times.
✓ Branch 6 taken 532 times.
✗ Branch 7 not taken.
532 candidates.push_back(centre + G4ThreeVector(s * halfExt.x(), 0.0, 0.0));
535
4/8
✓ Branch 0 taken 532 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 532 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 532 times.
✓ Branch 6 taken 532 times.
✗ Branch 7 not taken.
532 candidates.push_back(centre + G4ThreeVector(0.0, s * halfExt.y(), 0.0));
536
4/8
✓ Branch 0 taken 532 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 532 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 532 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 532 times.
532 candidates.push_back(centre + G4ThreeVector(0.0, 0.0, s * halfExt.z()));
537 }
538
2/2
✓ Branch 0 taken 266 times.
✓ Branch 1 taken 532 times.
798 for (const int sx : {-1, 1}) {
539
2/2
✓ Branch 0 taken 532 times.
✓ Branch 1 taken 1064 times.
1596 for (const int sy : {-1, 1}) {
540
2/2
✓ Branch 0 taken 2128 times.
✓ Branch 1 taken 1064 times.
3192 for (const int sz : {-1, 1}) {
541
4/8
✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2128 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2128 times.
✓ Branch 6 taken 2128 times.
✗ Branch 7 not taken.
4256 candidates.push_back(centre + G4ThreeVector(0.75 * sx * halfExt.x(),
542
1/2
✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
2128 0.75 * sy * halfExt.y(),
543
1/2
✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
2128 0.75 * sz * halfExt.z()));
544 }
545 }
546 }
547
548
1/2
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
266 BRepClass3d_SolidClassifier localClassifier;
549
1/2
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
266 localClassifier.Load(fShape);
550
551 // Seed the per-thread sphere caches from a small set of AABB-derived sample
552 // points so deep interior queries can often terminate without touching OCCT.
553
2/2
✓ Branch 0 taken 266 times.
✓ Branch 1 taken 3990 times.
4256 for (const G4ThreeVector& cand : candidates) {
554
2/4
✓ Branch 0 taken 3990 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3990 times.
✗ Branch 3 not taken.
3990 localClassifier.Perform(ToPoint(cand), tol);
555
3/4
✓ Branch 0 taken 3990 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2720 times.
✓ Branch 3 taken 1270 times.
3990 if (localClassifier.State() != TopAbs_IN) {
556 1270 continue;
557 }
558
1/2
✓ Branch 0 taken 2720 times.
✗ Branch 1 not taken.
2720 G4double d = BVHLowerBoundDistance(cand);
559
2/4
✓ Branch 0 taken 2720 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2720 times.
✗ Branch 3 not taken.
2720 if (d >= G4OCCTSolidKernel::Infinity() || d <= tol) {
560
3/4
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 2667 times.
✓ Branch 2 taken 53 times.
✗ Branch 3 not taken.
2720 const auto match = TryFindClosestFace(fFaceBoundsCache, cand);
561
2/4
✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 53 times.
53 if (!match.has_value() || match->distance <= tol) {
562 ✗ continue;
563 }
564 53 d = match->distance;
565 53 }
566
3/4
✓ Branch 0 taken 2720 times.
✓ Branch 1 taken 2667 times.
✓ Branch 2 taken 2720 times.
✗ Branch 3 not taken.
53 fInitialSpheres.push_back({cand, d});
567 }
568
2/2
✓ Branch 0 taken 5068 times.
✓ Branch 1 taken 5334 times.
266 std::sort(fInitialSpheres.begin(), fInitialSpheres.end(),
569 6466 [](const InscribedSphere& a, const InscribedSphere& b) { return a.radius > b.radius; });
570 15736 }
571
572 std::optional<G4OCCTSolidKernel::ClosestFaceMatch>
573 339042 G4OCCTSolidKernel::TryFindClosestFace(const std::vector<FaceBounds>& faceBoundsCache,
574 const G4ThreeVector& point, G4double maxDistance) {
575
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 339042 times.
339042 if (faceBoundsCache.empty()) {
576 ✗ return std::nullopt;
577 }
578
579 339042 const gp_Pnt queryPoint = ToPoint(point);
580 339042 const TopoDS_Vertex queryVertex = MakeVertex(point);
581
1/2
✓ Branch 0 taken 339042 times.
✗ Branch 1 not taken.
339042 Bnd_Box queryBox;
582
1/2
✓ Branch 0 taken 339042 times.
✗ Branch 1 not taken.
339042 queryBox.Add(queryPoint);
583
584 339042 std::optional<ClosestFaceMatch> bestMatch;
585
2/2
✓ Branch 0 taken 25487449 times.
✓ Branch 1 taken 339042 times.
25826491 for (std::size_t i = 0; i < faceBoundsCache.size(); ++i) {
586 25487449 const FaceBounds& fb = faceBoundsCache[i];
587
2/2
✓ Branch 0 taken 17813442 times.
✓ Branch 1 taken 7674007 times.
25487449 const G4double threshold = bestMatch.has_value() ? bestMatch->distance : maxDistance;
588
6/8
✓ Branch 0 taken 25487449 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25419470 times.
✓ Branch 3 taken 67979 times.
✓ Branch 4 taken 25419470 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 631822 times.
✓ Branch 7 taken 24787648 times.
25487449 if (threshold < G4OCCTSolidKernel::Infinity() && fb.box.Distance(queryBox) > threshold) {
589 24787648 continue;
590 }
591
592
2/4
✓ Branch 0 taken 699801 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 699801 times.
699801 BRepExtrema_DistShapeShape distance(queryVertex, fb.face);
593
4/8
✓ Branch 0 taken 699801 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 699801 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 699801 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 699801 times.
✗ Branch 7 not taken.
699801 if (!distance.IsDone() || distance.NbSolution() == 0) {
594 ✗ continue;
595 }
596
1/2
✓ Branch 0 taken 699801 times.
✗ Branch 1 not taken.
699801 const G4double candidateDistance = distance.Value();
597
598
4/4
✓ Branch 0 taken 360759 times.
✓ Branch 1 taken 339042 times.
✓ Branch 2 taken 264197 times.
✓ Branch 3 taken 96562 times.
699801 if (bestMatch.has_value() && candidateDistance >= bestMatch->distance) {
599 96562 continue;
600 }
601
1/2
✓ Branch 0 taken 603239 times.
✗ Branch 1 not taken.
603239 ClosestFaceMatch match{.face = fb.face, .distance = candidateDistance, .faceIndex = i};
602
5/8
✓ Branch 0 taken 603239 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 603239 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 603239 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 408720 times.
✓ Branch 7 taken 194519 times.
603239 if (distance.NbSolution() > 0 && distance.SupportTypeShape2(1) == BRepExtrema_IsInFace) {
603 408720 Standard_Real u = 0.0;
604 408720 Standard_Real v = 0.0;
605
1/2
✓ Branch 0 taken 408720 times.
✗ Branch 1 not taken.
408720 distance.ParOnFaceS2(1, u, v);
606
1/2
✓ Branch 0 taken 408720 times.
✗ Branch 1 not taken.
408720 match.uv = std::make_pair(u, v);
607 408720 }
608 603239 bestMatch = std::move(match);
609 699801 }
610
611 339042 return bestMatch;
612 339042 }
613
614 BRepClass3d_SolidClassifier&
615 167213 G4OCCTSolidKernel::GetOrCreateClassifier(ClassifierCache& cache) const {
616 167213 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
617
2/2
✓ Branch 0 taken 167150 times.
✓ Branch 1 taken 63 times.
167213 if (cache.generation != currentGen) {
618 63 cache.classifier.emplace();
619 63 cache.classifier->Load(fShape);
620 63 cache.generation = currentGen;
621 63 }
622 167213 return *cache.classifier;
623 }
624
625 G4OCCTSolidKernel::IntersectorCache&
626 2738970 G4OCCTSolidKernel::GetOrCreateIntersector(IntersectorCache& cache) const {
627 2738970 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
628
2/2
✓ Branch 0 taken 2738879 times.
✓ Branch 1 taken 91 times.
2738970 if (cache.generation != currentGen) {
629 91 const G4double tol = IntersectionTolerance();
630 91 cache.faceIntersectors.clear();
631 91 cache.faceIntersectors.reserve(fFaceBoundsCache.size());
632 91 cache.expandedBoxes.clear();
633 91 cache.expandedBoxes.reserve(fFaceBoundsCache.size());
634
2/2
✓ Branch 0 taken 91 times.
✓ Branch 1 taken 3657 times.
3748 for (const auto& fb : fFaceBoundsCache) {
635
1/2
✓ Branch 0 taken 3657 times.
✗ Branch 1 not taken.
3657 cache.faceIntersectors.push_back(std::make_unique<IntCurvesFace_Intersector>(fb.face, tol));
636 3657 Bnd_Box expanded = fb.box;
637 3657 expanded.Enlarge(tol);
638 3657 cache.expandedBoxes.push_back(std::move(expanded));
639 }
640 91 cache.generation = currentGen;
641 91 }
642 2738970 return cache;
643 ✗ }
644
645 G4OCCTSolidKernel::SphereCacheData&
646 2624704 G4OCCTSolidKernel::GetOrInitSphereCache(SphereCacheData& cache) const {
647 2624704 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
648
2/2
✓ Branch 0 taken 2624555 times.
✓ Branch 1 taken 149 times.
2624704 if (cache.generation != currentGen) {
649 149 cache.spheres = fInitialSpheres;
650 149 cache.generation = currentGen;
651 149 }
652 2624704 return cache;
653 }
654
655 40176 void G4OCCTSolidKernel::TryInsertSphere(SphereCacheData& cache, const G4ThreeVector& centre,
656 G4double d) const {
657
2/2
✓ Branch 0 taken 2611 times.
✓ Branch 1 taken 37565 times.
40176 if (d <= 0.0) {
658 2611 return;
659 }
660 37565 const G4double minRadius = IntersectionTolerance();
661
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 37565 times.
37565 if (d <= minRadius) {
662 ✗ return;
663 }
664 37565 GetOrInitSphereCache(cache);
665
666
4/4
✓ Branch 0 taken 35719 times.
✓ Branch 1 taken 1846 times.
✓ Branch 2 taken 30224 times.
✓ Branch 3 taken 5495 times.
37565 if (cache.spheres.size() >= kMaxInscribedSpheres && d <= cache.spheres.back().radius) {
667 30224 return;
668 }
669
670
2/2
✓ Branch 0 taken 416923 times.
✓ Branch 1 taken 7330 times.
424253 for (const InscribedSphere& s : cache.spheres) {
671
2/2
✓ Branch 0 taken 203873 times.
✓ Branch 1 taken 213050 times.
416923 if (s.radius >= d) {
672 213050 const G4double gap = s.radius - d;
673
3/4
✓ Branch 0 taken 213050 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 213039 times.
213050 if ((centre - s.centre).mag2() <= gap * gap) {
674 11 return;
675 }
676 213039 }
677 }
678
679 7330 const InscribedSphere newSphere{centre, d};
680
1/2
✓ Branch 0 taken 7330 times.
✗ Branch 1 not taken.
7330 const auto it = std::lower_bound(
681 7330 cache.spheres.begin(), cache.spheres.end(), newSphere,
682 42311 [](const InscribedSphere& a, const InscribedSphere& b) { return a.radius > b.radius; });
683
1/2
✓ Branch 0 taken 7330 times.
✗ Branch 1 not taken.
7330 cache.spheres.insert(it, newSphere);
684
2/2
✓ Branch 0 taken 1836 times.
✓ Branch 1 taken 5494 times.
7330 if (cache.spheres.size() > kMaxInscribedSpheres) {
685 5494 cache.spheres.pop_back();
686 5494 }
687 40176 }
688
689 G4OCCTSolidKernel::PointClassification
690 5063958 G4OCCTSolidKernel::ClassifyPoint(const G4ThreeVector& p, ClassifierCache& classifierCache,
691 IntersectorCache& intersectorCache,
692 SphereCacheData& sphereCache) const {
693 5063958 const G4double tolerance = IntersectionTolerance();
694
4/4
✓ Branch 0 taken 5063955 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 4257543 times.
✓ Branch 3 taken 806412 times.
5063958 if (p.x() < fCachedBounds.min.x() - tolerance || p.x() > fCachedBounds.max.x() + tolerance ||
695
3/4
✓ Branch 0 taken 4257543 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3393540 times.
✓ Branch 3 taken 864003 times.
4257543 p.y() < fCachedBounds.min.y() - tolerance || p.y() > fCachedBounds.max.y() + tolerance ||
696
3/4
✓ Branch 0 taken 3393540 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 806401 times.
✓ Branch 3 taken 2587139 times.
3393540 p.z() < fCachedBounds.min.z() - tolerance || p.z() > fCachedBounds.max.z() + tolerance) {
697 2476819 return PointClassification::kOutside;
698 }
699
700 2587139 const SphereCacheData& localSphereCache = GetOrInitSphereCache(sphereCache);
701
2/2
✓ Branch 0 taken 4962399 times.
✓ Branch 1 taken 743633 times.
5706032 for (const InscribedSphere& s : localSphereCache.spheres) {
702 4962399 const G4double interiorRadius = s.radius - tolerance;
703
5/10
✗ Branch 0 not taken.
✓ Branch 1 taken 4962399 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4962399 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4962399 times.
✓ Branch 6 taken 3118893 times.
✓ Branch 7 taken 1843506 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
4962399 if (interiorRadius > 0.0 && (p - s.centre).mag2() < interiorRadius * interiorRadius) {
704 1843506 return PointClassification::kInside;
705 }
706 }
707
708
2/4
✓ Branch 0 taken 743633 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 743633 times.
743633 if (!fTriangleSet.IsNull() && fTriangleSet->Size() > 0) {
709 // Near the tessellated surface we deliberately fall back to the exact OCCT
710 // classifier; the BVH lower bound is only used to prove that we are safely
711 // away from the true analytical boundary by more than the mesh deflection.
712
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 743633 times.
743633 if (fBVHDeflection > 0.0) {
713 743633 const G4double bvhLB = BVHLowerBoundDistance(p);
714
2/2
✓ Branch 0 taken 81795 times.
✓ Branch 1 taken 661838 times.
743633 if (bvhLB < tolerance) {
715 81795 auto& classifier = GetOrCreateClassifier(classifierCache);
716 81795 classifier.Perform(ToPoint(p), tolerance);
717 81795 return ToPointClassification(classifier.State());
718 }
719 661838 }
720
721 661838 const BVH_Vec3d bvhOrigin(p.x(), p.y(), p.z());
722 661838 const Standard_Real bvhTol = static_cast<Standard_Real>(tolerance);
723 661838 TriangleRayCast caster;
724
2/4
✓ Branch 0 taken 661838 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 661838 times.
✗ Branch 3 not taken.
661838 caster.SetBVHSet(fTriangleSet.get());
725
2/4
✓ Branch 0 taken 661838 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 661838 times.
✗ Branch 3 not taken.
661838 caster.SetRay(bvhOrigin, BVH_Vec3d(0.0, 0.0, 1.0), bvhTol);
726
1/2
✓ Branch 0 taken 661838 times.
✗ Branch 1 not taken.
661838 caster.Select();
727
728
6/8
✓ Branch 0 taken 661838 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 604232 times.
✓ Branch 3 taken 57606 times.
✓ Branch 4 taken 604232 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 215080 times.
✓ Branch 7 taken 389152 times.
661838 if (!caster.Degenerate() && caster.Crossings() > 0) {
729
1/2
✓ Branch 0 taken 215080 times.
✗ Branch 1 not taken.
215080 return (caster.Crossings() % 2 == 1) ? PointClassification::kInside
730 : PointClassification::kOutside;
731 }
732
733 // Edge/vertex hits can make one parity ray ambiguous, so we cast two
734 // additional orthogonal rays and let non-degenerate rays vote before
735 // paying for the exact classifier fallback.
736 446758 int insideVotes = 0;
737 446758 int outsideVotes = 0;
738
3/4
✓ Branch 0 taken 446758 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 389152 times.
✓ Branch 3 taken 57606 times.
446758 if (!caster.Degenerate()) {
739
2/4
✓ Branch 0 taken 389152 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 389152 times.
389152 if (caster.Crossings() % 2 == 1) {
740 ✗ ++insideVotes;
741 ✗ } else {
742 389152 ++outsideVotes;
743 }
744 389152 }
745
746 446758 const BVH_Vec3d kExtraRays[2] = {
747
1/2
✓ Branch 0 taken 446758 times.
✗ Branch 1 not taken.
446758 BVH_Vec3d(1.0, 0.0, 0.0),
748
1/2
✓ Branch 0 taken 446758 times.
✗ Branch 1 not taken.
446758 BVH_Vec3d(0.0, 1.0, 0.0),
749 };
750
2/2
✓ Branch 0 taken 893516 times.
✓ Branch 1 taken 446758 times.
1340274 for (const BVH_Vec3d& dir : kExtraRays) {
751
1/2
✓ Branch 0 taken 893516 times.
✗ Branch 1 not taken.
893516 caster.SetRay(bvhOrigin, dir, bvhTol);
752
1/2
✓ Branch 0 taken 893516 times.
✗ Branch 1 not taken.
893516 caster.Select();
753
3/4
✓ Branch 0 taken 893516 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 547900 times.
✓ Branch 3 taken 345616 times.
893516 if (!caster.Degenerate()) {
754
3/4
✓ Branch 0 taken 547900 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 57722 times.
✓ Branch 3 taken 490178 times.
547900 if (caster.Crossings() % 2 == 1) {
755 57722 ++insideVotes;
756 57722 } else {
757 490178 ++outsideVotes;
758 }
759 547900 }
760 }
761
762
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 446756 times.
446758 if (insideVotes > outsideVotes) {
763 2 return PointClassification::kInside;
764 }
765
2/2
✓ Branch 0 taken 57603 times.
✓ Branch 1 taken 389153 times.
446756 if (outsideVotes > insideVotes) {
766 389153 return PointClassification::kOutside;
767 }
768
769
1/2
✓ Branch 0 taken 57603 times.
✗ Branch 1 not taken.
57603 auto& classifier = GetOrCreateClassifier(classifierCache);
770
2/4
✓ Branch 0 taken 57603 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 57603 times.
✗ Branch 3 not taken.
57603 classifier.Perform(ToPoint(p), tolerance);
771
2/4
✓ Branch 0 taken 57603 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 57603 times.
✗ Branch 3 not taken.
57603 return ToPointClassification(classifier.State());
772 661838 }
773
774 ✗ IntersectorCache& cache = GetOrCreateIntersector(intersectorCache);
775 ✗ const gp_Lin ray(ToPoint(p), gp_Dir(0.0, 0.0, 1.0));
776 ✗ int crossings = 0;
777 ✗ bool onSurface = false;
778 ✗ bool degenerateRay = false;
779
780 ✗ for (std::size_t i = 0; i < fFaceBoundsCache.size(); ++i) {
781 ✗ if (cache.expandedBoxes[i].IsOut(ray)) {
782 ✗ continue;
783 }
784 ✗ const FaceBounds& fb = fFaceBoundsCache[i];
785 ✗ if (!fb.uvPolygon.empty()) {
786 // Fast path for single-wire planar faces: intersect the analytical plane
787 // and then test the hit against the cached 2D polygon in face space.
788 ✗ Standard_Real u_hit = 0.0;
789 ✗ Standard_Real v_hit = 0.0;
790 const auto t =
791 ✗ RayPlaneFaceHit(ray, *fb.plane, fb.uvPolygon, -tolerance, tolerance, &u_hit, &v_hit);
792 ✗ if (t) {
793 ✗ const G4double w = static_cast<G4double>(*t);
794 ✗ if (std::abs(w) <= tolerance) {
795 ✗ onSurface = true;
796 ✗ } else if (w > tolerance) {
797 ✗ if (PointOnPolygonBoundary2d(u_hit, v_hit, fb.uvPolygon, tolerance)) {
798 ✗ degenerateRay = true;
799 ✗ } else {
800 ✗ ++crossings;
801 }
802 ✗ }
803 ✗ }
804 ✗ } else {
805 ✗ IntCurvesFace_Intersector& fi = *cache.faceIntersectors[i];
806 ✗ fi.Perform(ray, -tolerance, Precision::Infinite());
807 ✗ if (!fi.IsDone()) {
808 ✗ continue;
809 }
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)) {
814 ✗ onSurface = true;
815 ✗ } else if (w > tolerance && state == TopAbs_IN) {
816 ✗ ++crossings;
817 ✗ } else if (w > tolerance && state == TopAbs_ON) {
818 ✗ degenerateRay = true;
819 ✗ }
820 ✗ }
821 }
822 ✗ }
823
824 ✗ if (onSurface) {
825 ✗ return PointClassification::kSurface;
826 }
827 ✗ if (crossings == 0 || degenerateRay) {
828 ✗ auto& classifier = GetOrCreateClassifier(classifierCache);
829 ✗ classifier.Perform(ToPoint(p), tolerance);
830 ✗ return ToPointClassification(classifier.State());
831 }
832 ✗ return (crossings % 2 == 1) ? PointClassification::kInside : PointClassification::kOutside;
833 5063958 }
834
835 465852 G4ThreeVector G4OCCTSolidKernel::SurfaceNormal(const G4ThreeVector& p) const {
836
2/2
✓ Branch 0 taken 271063 times.
✓ Branch 1 taken 194789 times.
465852 if (fAllFacesPlanar) {
837 194789 const gp_Pnt pt = ToPoint(p);
838 194789 const FaceBounds* bestFB = nullptr;
839 194789 G4double bestDist = G4OCCTSolidKernel::Infinity();
840
2/2
✓ Branch 0 taken 1674300 times.
✓ Branch 1 taken 194789 times.
1869089 for (const FaceBounds& fb : fFaceBoundsCache) {
841
2/4
✓ Branch 0 taken 1674300 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1674300 times.
1674300 if (!fb.plane.has_value() || !fb.outwardNormal.has_value()) {
842 ✗ continue;
843 }
844 1674300 const G4double d = fb.plane->Distance(pt);
845
2/2
✓ Branch 0 taken 1107125 times.
✓ Branch 1 taken 567175 times.
1674300 if (d < bestDist) {
846 567175 bestDist = d;
847 567175 bestFB = &fb;
848 567175 }
849 }
850
1/2
✓ Branch 0 taken 194789 times.
✗ Branch 1 not taken.
194789 if (bestFB) {
851 194789 return *bestFB->outwardNormal;
852 }
853 ✗ }
854
855 271097 const auto projectAndGetNormalFallback = [&](const FaceBounds& fb) -> G4ThreeVector {
856 34 TopLoc_Location loc;
857
2/4
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
34 const Handle(Geom_Surface) surface = BRep_Tool::Surface(fb.face, loc);
858
2/4
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 34 times.
34 if (surface.IsNull()) {
859 ✗ return FallbackNormal();
860 }
861
1/2
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
34 gp_Pnt pLocal = ToPoint(p);
862
2/4
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 34 times.
34 if (!loc.IsIdentity()) {
863 ✗ pLocal.Transform(loc.Transformation().Inverted());
864 ✗ }
865
1/2
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
34 GeomAPI_ProjectPointOnSurf projection(pLocal, surface);
866
2/4
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 34 times.
34 if (projection.NbPoints() == 0) {
867 ✗ return FallbackNormal();
868 }
869 34 Standard_Real u = 0.0;
870 34 Standard_Real v = 0.0;
871
1/2
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
34 projection.LowerDistanceParameters(u, v);
872
3/6
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 34 times.
✗ Branch 5 not taken.
34 return TryGetOutwardNormal(fb.adaptor, fb.face, u, v).value_or(FallbackNormal());
873 34 };
874
875 271063 const G4double bvhLB = BVHLowerBoundDistance(p);
876 // The BVH gives a lower bound to the true surface distance. Adding
877 // 2×deflection turns that into a conservative face-search radius that still
878 // prunes obviously distant faces before exact OCCT distance checks.
879
2/4
✓ Branch 0 taken 271063 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 271063 times.
271063 const G4double seedDist = (fBVHDeflection > 0.0 && bvhLB < G4OCCTSolidKernel::Infinity())
880 271063 ? bvhLB + 2.0 * fBVHDeflection
881 ✗ : G4OCCTSolidKernel::Infinity();
882 271063 const auto closestFaceMatch = TryFindClosestFace(fFaceBoundsCache, p, seedDist);
883
1/2
✓ Branch 0 taken 271063 times.
✗ Branch 1 not taken.
271063 if (!closestFaceMatch.has_value()) {
884 ✗ return FallbackNormal();
885 }
886 271063 const FaceBounds& fb = fFaceBoundsCache[closestFaceMatch->faceIndex];
887
888
2/2
✓ Branch 0 taken 8225 times.
✓ Branch 1 taken 262838 times.
271063 if (fb.outwardNormal.has_value()) {
889
1/2
✓ Branch 0 taken 8225 times.
✗ Branch 1 not taken.
8225 return *fb.outwardNormal;
890 }
891
2/2
✓ Branch 0 taken 262804 times.
✓ Branch 1 taken 34 times.
262838 if (closestFaceMatch->uv.has_value()) {
892 788412 const auto [u, v] = *closestFaceMatch->uv;
893
3/6
✓ Branch 0 taken 262804 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 262804 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 262804 times.
✗ Branch 5 not taken.
788412 const auto result = TryGetOutwardNormal(fb.adaptor, fb.face, u, v);
894
1/2
✓ Branch 0 taken 262804 times.
✗ Branch 1 not taken.
262804 if (result.has_value()) {
895
1/2
✓ Branch 0 taken 262804 times.
✗ Branch 1 not taken.
262804 return *result;
896 }
897 262804 }
898
1/2
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
34 return projectAndGetNormalFallback(fb);
899 465852 }
900
901 2679561 G4double G4OCCTSolidKernel::DistanceToIn(const G4ThreeVector& p, const G4ThreeVector& v,
902 IntersectorCache& intersectorCache) const {
903 2679561 const G4double tolerance = IntersectionTolerance();
904 2679561 const gp_Lin ray(ToPoint(p), gp_Dir(v.x(), v.y(), v.z()));
905 2679561 IntersectorCache& cache = GetOrCreateIntersector(intersectorCache);
906
907 2679561 G4double minDistance = G4OCCTSolidKernel::Infinity();
908
2/2
✓ Branch 0 taken 184694833 times.
✓ Branch 1 taken 2679561 times.
187374394 for (std::size_t i = 0; i < fFaceBoundsCache.size(); ++i) {
909
2/2
✓ Branch 0 taken 182800041 times.
✓ Branch 1 taken 1894792 times.
184694833 if (cache.expandedBoxes[i].IsOut(ray)) {
910 182800041 continue;
911 }
912 1894792 const FaceBounds& fb = fFaceBoundsCache[i];
913
2/2
✓ Branch 0 taken 1212395 times.
✓ Branch 1 taken 682397 times.
1894792 if (!fb.uvPolygon.empty()) {
914 682397 const auto t = RayPlaneFaceHit(ray, *fb.plane, fb.uvPolygon, tolerance, tolerance);
915
4/4
✓ Branch 0 taken 515163 times.
✓ Branch 1 taken 167234 times.
✓ Branch 2 taken 137022 times.
✓ Branch 3 taken 378141 times.
682397 if (t && *t < minDistance) {
916 378141 minDistance = static_cast<G4double>(*t);
917 378141 }
918 682397 } else {
919 1212395 IntCurvesFace_Intersector& fi = *cache.faceIntersectors[i];
920 1212395 fi.Perform(ray, tolerance, Precision::Infinite());
921
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1212395 times.
1212395 if (!fi.IsDone()) {
922 ✗ continue;
923 }
924
2/2
✓ Branch 0 taken 686513 times.
✓ Branch 1 taken 1212395 times.
1898908 for (Standard_Integer j = 1; j <= fi.NbPnt(); ++j) {
925 686513 const G4double w = fi.WParameter(j);
926
3/4
✓ Branch 0 taken 686513 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259497 times.
✓ Branch 3 taken 427016 times.
686513 if (w > tolerance && w < minDistance) {
927 427016 minDistance = w;
928 427016 }
929 686513 }
930 }
931 1894792 }
932 2679561 return minDistance;
933 }
934
935 27815 G4double G4OCCTSolidKernel::ExactDistanceToIn(const G4ThreeVector& p,
936 ClassifierCache& classifierCache) const {
937 27815 auto& classifier = GetOrCreateClassifier(classifierCache);
938 27815 classifier.Perform(ToPoint(p), IntersectionTolerance());
939
4/4
✓ Branch 0 taken 27813 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 27809 times.
27815 if (classifier.State() == TopAbs_IN || classifier.State() == TopAbs_ON) {
940 6 return 0.0;
941 }
942
943 27809 const auto match = TryFindClosestFace(fFaceBoundsCache, p);
944
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27809 times.
27809 if (!match.has_value()) {
945 ✗ return G4OCCTSolidKernel::Infinity();
946 }
947
2/4
✓ Branch 0 taken 27809 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 27809 times.
27809 return (match->distance <= IntersectionTolerance()) ? 0.0 : match->distance;
948 27815 }
949
950 2502255 G4double G4OCCTSolidKernel::AABBLowerBound(const G4ThreeVector& p) const {
951 2502255 const G4ThreeVector& mn = fCachedBounds.min;
952 2502255 const G4ThreeVector& mx = fCachedBounds.max;
953 2502255 const G4double dx = std::max({0.0, mn.x() - p.x(), p.x() - mx.x()});
954 2502255 const G4double dy = std::max({0.0, mn.y() - p.y(), p.y() - mx.y()});
955 2502255 const G4double dz = std::max({0.0, mn.z() - p.z(), p.z() - mx.z()});
956 2502255 return std::sqrt(dx * dx + dy * dy + dz * dz);
957 }
958
959 1061106 G4double G4OCCTSolidKernel::BVHLowerBoundDistance(const G4ThreeVector& p) const {
960
2/4
✓ Branch 0 taken 1061106 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1061106 times.
1061106 if (fTriangleSet.IsNull() || fTriangleSet->Size() == 0) {
961 ✗ return G4OCCTSolidKernel::Infinity();
962 }
963 1061106 PointToMeshDistance solver;
964
5/10
✓ Branch 0 taken 1061106 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1061106 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1061106 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1061106 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1061106 times.
✗ Branch 9 not taken.
1061106 solver.SetObject(BVH_Vec3d(p.x(), p.y(), p.z()));
965
2/4
✓ Branch 0 taken 1061106 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1061106 times.
✗ Branch 3 not taken.
1061106 solver.SetBVHSet(fTriangleSet.get());
966
1/2
✓ Branch 0 taken 1061106 times.
✗ Branch 1 not taken.
1061106 const Standard_Real meshDistSq = solver.ComputeDistance();
967
2/4
✓ Branch 0 taken 1061106 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1061106 times.
1061106 if (!solver.IsDone()) {
968 ✗ return G4OCCTSolidKernel::Infinity();
969 }
970 1061106 const G4double meshDist = std::sqrt(static_cast<G4double>(meshDistSq));
971
972 1061106 G4double deflection = fBVHDeflection;
973
1/2
✓ Branch 0 taken 1061106 times.
✗ Branch 1 not taken.
1061106 const Standard_Integer bestIdx = solver.BestIndex();
974
1/2
✓ Branch 0 taken 1061106 times.
✗ Branch 1 not taken.
1061106 if (bestIdx >= 0) {
975
2/4
✓ Branch 0 taken 1061106 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1061106 times.
✗ Branch 3 not taken.
1061106 const Standard_Integer faceId = fTriangleSet->GetFaceID(bestIdx);
976
2/4
✓ Branch 0 taken 1061106 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1061106 times.
1061106 if (faceId >= 0 && static_cast<std::size_t>(faceId) < fFaceDeflections.size()) {
977 1061106 deflection = fFaceDeflections[static_cast<std::size_t>(faceId)];
978 1061106 }
979 1061106 }
980
981 // The tessellated distance can overestimate how far we are from the
982 // analytical surface by up to the local mesh deflection, so subtract it and
983 // clamp at zero to preserve a conservative lower bound.
984
1/2
✓ Branch 0 taken 1061106 times.
✗ Branch 1 not taken.
1061106 return std::max(0.0, meshDist - deflection);
985 1061106 }
986
987 20179 G4double G4OCCTSolidKernel::PlanarFaceLowerBoundDistance(const G4ThreeVector& p) const {
988 20179 const gp_Pnt pt = ToPoint(p);
989 20179 G4double minDist = G4OCCTSolidKernel::Infinity();
990
2/2
✓ Branch 0 taken 184423 times.
✓ Branch 1 taken 20179 times.
204602 for (const FaceBounds& fb : fFaceBoundsCache) {
991
1/2
✓ Branch 0 taken 184423 times.
✗ Branch 1 not taken.
184423 if (!fb.plane.has_value()) {
992 ✗ continue;
993 }
994 184423 const G4double d = static_cast<G4double>(fb.plane->Distance(pt));
995
2/2
✓ Branch 0 taken 132064 times.
✓ Branch 1 taken 52359 times.
184423 if (d < minDist) {
996 52359 minDist = d;
997 52359 }
998 }
999 20179 return minDist;
1000 }
1001
1002 2502255 G4double G4OCCTSolidKernel::DistanceToIn(const G4ThreeVector& p,
1003 ClassifierCache& classifierCache) const {
1004 2502255 const G4double aabbDist = AABBLowerBound(p);
1005
2/2
✓ Branch 0 taken 2478562 times.
✓ Branch 1 taken 23693 times.
2502255 if (aabbDist > IntersectionTolerance()) {
1006 2478562 return aabbDist;
1007 }
1008
1009 // Once the point is inside or very near the AABB, the BVH lower bound is the
1010 // next cheap filter. Only near-surface cases fall through to the exact OCCT
1011 // distance query.
1012 23693 const G4double bvhDist = BVHLowerBoundDistance(p);
1013
3/4
✓ Branch 0 taken 23693 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2368 times.
✓ Branch 3 taken 21325 times.
23693 if (bvhDist < G4OCCTSolidKernel::Infinity() && bvhDist > IntersectionTolerance()) {
1014 21325 return bvhDist;
1015 }
1016 2368 return ExactDistanceToIn(p, classifierCache);
1017 2502255 }
1018
1019 59409 G4double G4OCCTSolidKernel::DistanceToOut(const G4ThreeVector& p, const G4ThreeVector& v,
1020 IntersectorCache& intersectorCache, const G4bool calcNorm,
1021 G4bool* validNorm, G4ThreeVector* n) const {
1022
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 59407 times.
59409 if (validNorm != nullptr) {
1023 59407 *validNorm = false;
1024 59407 }
1025
1026 59409 const G4double tolerance = IntersectionTolerance();
1027 59409 const gp_Lin ray(ToPoint(p), gp_Dir(v.x(), v.y(), v.z()));
1028 59409 IntersectorCache& cache = GetOrCreateIntersector(intersectorCache);
1029
1030 59409 G4double minDistance = G4OCCTSolidKernel::Infinity();
1031 59409 std::size_t minFaceIdx = std::numeric_limits<std::size_t>::max();
1032 59409 G4double minU = 0.0;
1033 59409 G4double minV = 0.0;
1034 59409 bool minIsIn = false;
1035 59409 bool minIsFastPath = false;
1036
1037
2/2
✓ Branch 0 taken 317526 times.
✓ Branch 1 taken 59409 times.
376935 for (std::size_t i = 0; i < fFaceBoundsCache.size(); ++i) {
1038
2/2
✓ Branch 0 taken 198870 times.
✓ Branch 1 taken 118656 times.
317526 if (cache.expandedBoxes[i].IsOut(ray)) {
1039 198870 continue;
1040 }
1041 118656 const FaceBounds& fb = fFaceBoundsCache[i];
1042
2/2
✓ Branch 0 taken 62193 times.
✓ Branch 1 taken 56463 times.
118656 if (!fb.uvPolygon.empty()) {
1043 56463 const auto t = RayPlaneFaceHit(ray, *fb.plane, fb.uvPolygon, tolerance, tolerance);
1044
4/4
✓ Branch 0 taken 24815 times.
✓ Branch 1 taken 31648 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 24813 times.
56463 if (t && *t < minDistance) {
1045 24813 minDistance = static_cast<G4double>(*t);
1046 24813 minFaceIdx = i;
1047 24813 minIsIn = true;
1048 24813 minIsFastPath = true;
1049 24813 }
1050 56463 } else {
1051 62193 IntCurvesFace_Intersector& fi = *cache.faceIntersectors[i];
1052 62193 fi.Perform(ray, tolerance, Precision::Infinite());
1053
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62193 times.
62193 if (!fi.IsDone()) {
1054 ✗ continue;
1055 }
1056
2/2
✓ Branch 0 taken 35871 times.
✓ Branch 1 taken 62193 times.
98064 for (Standard_Integer j = 1; j <= fi.NbPnt(); ++j) {
1057 35871 const G4double w = fi.WParameter(j);
1058
3/4
✓ Branch 0 taken 35871 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1274 times.
✓ Branch 3 taken 34597 times.
35871 if (w > tolerance && w < minDistance) {
1059 34597 minDistance = w;
1060 34597 minFaceIdx = i;
1061 34597 minU = fi.UParameter(j);
1062 34597 minV = fi.VParameter(j);
1063
2/2
✓ Branch 0 taken 34574 times.
✓ Branch 1 taken 23 times.
34597 minIsIn = (fi.State(j) == TopAbs_IN || fi.State(j) == TopAbs_ON);
1064 34597 minIsFastPath = false;
1065 34597 }
1066 35871 }
1067 }
1068 118656 }
1069
1070
2/4
✓ Branch 0 taken 59409 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 59409 times.
59409 if (minFaceIdx == std::numeric_limits<std::size_t>::max() ||
1071 59409 minDistance == G4OCCTSolidKernel::Infinity()) {
1072 ✗ return 0.0;
1073 }
1074
1075
7/8
✓ Branch 0 taken 59405 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 59404 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 59403 times.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 7 taken 59403 times.
59409 if (calcNorm && validNorm != nullptr && n != nullptr && minIsIn) {
1076 59403 const FaceBounds& fb = fFaceBoundsCache[minFaceIdx];
1077
3/4
✓ Branch 0 taken 24806 times.
✓ Branch 1 taken 34597 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24806 times.
59403 if (minIsFastPath && fb.outwardNormal.has_value()) {
1078 24806 *n = *fb.outwardNormal;
1079 24806 *validNorm = true;
1080 24806 } else {
1081 34597 const auto outNorm = TryGetOutwardNormal(fb.adaptor, fb.face, minU, minV);
1082
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34597 times.
34597 if (outNorm) {
1083
1/2
✓ Branch 0 taken 34597 times.
✗ Branch 1 not taken.
34597 *n = *outNorm;
1084 34597 *validNorm = true;
1085 34597 }
1086 34597 }
1087 59403 }
1088 59409 return minDistance;
1089 59409 }
1090
1091 40117 G4double G4OCCTSolidKernel::ExactDistanceToOut(const G4ThreeVector& p) const {
1092 40117 const auto match = TryFindClosestFace(fFaceBoundsCache, p);
1093
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40117 times.
40117 if (!match.has_value()) {
1094 ✗ return 0.0;
1095 }
1096
2/4
✓ Branch 0 taken 40117 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 40117 times.
40117 return (match->distance <= IntersectionTolerance()) ? 0.0 : match->distance;
1097 40117 }
1098
1099 40176 G4double G4OCCTSolidKernel::DistanceToOut(const G4ThreeVector& p,
1100 SphereCacheData& sphereCache) const {
1101 G4double d;
1102
2/2
✓ Branch 0 taken 20179 times.
✓ Branch 1 taken 19997 times.
40176 if (fAllFacesPlanar) {
1103 // For all-planar solids the analytical plane distances are already exact
1104 // lower bounds, so they avoid BVH traversal entirely.
1105 20179 d = PlanarFaceLowerBoundDistance(p);
1106
1/2
✓ Branch 0 taken 20179 times.
✗ Branch 1 not taken.
20179 if (d == G4OCCTSolidKernel::Infinity()) {
1107 ✗ d = ExactDistanceToOut(p);
1108 ✗ }
1109 20179 } else {
1110 19997 const G4double bvhDist = BVHLowerBoundDistance(p);
1111
1/2
✓ Branch 0 taken 19997 times.
✗ Branch 1 not taken.
19997 d = (bvhDist < G4OCCTSolidKernel::Infinity()) ? bvhDist : ExactDistanceToOut(p);
1112 }
1113 // Feed the just-computed lower bound back into the adaptive sphere cache so
1114 // future interior queries can terminate earlier.
1115 40176 TryInsertSphere(sphereCache, p, d);
1116 40176 return d;
1117 }
1118
1119 6 G4double G4OCCTSolidKernel::GetCubicVolume() {
1120 6 std::unique_lock<std::mutex> lock(fVolumeAreaMutex);
1121
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 if (!fCachedVolume) {
1122
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 GProp_GProps props;
1123
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 BRepGProp::VolumeProperties(fShape, props);
1124
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 fCachedVolume = props.Mass();
1125 5 }
1126 6 return *fCachedVolume;
1127 6 }
1128
1129 3 G4double G4OCCTSolidKernel::GetSurfaceArea() {
1130 3 std::unique_lock<std::mutex> lock(fVolumeAreaMutex);
1131
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 if (!fCachedSurfaceArea) {
1132
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 GProp_GProps props;
1133
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 BRepGProp::SurfaceProperties(fShape, props);
1134
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 fCachedSurfaceArea = props.Mass();
1135 2 }
1136 3 return *fCachedSurfaceArea;
1137 3 }
1138
1139 203 const G4OCCTSolidKernel::SurfaceSamplingCache& G4OCCTSolidKernel::GetOrBuildSurfaceCache() const {
1140 203 const std::uint64_t currentGen = fShapeGeneration.load(std::memory_order_acquire);
1141 {
1142 203 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
1143
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 203 times.
203 while (fSurfaceCacheBuilding) {
1144 ✗ fSurfaceCacheCV.wait(lock);
1145 ✗ if (fSurfaceCache.has_value() && fSurfaceCacheGeneration == currentGen) {
1146 ✗ return *fSurfaceCache;
1147 }
1148 }
1149
3/4
✓ Branch 0 taken 198 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 198 times.
203 if (fSurfaceCache.has_value() && fSurfaceCacheGeneration == currentGen) {
1150 198 return *fSurfaceCache;
1151 }
1152 // Claim the build so only one thread pays for meshing/tessellation work on
1153 // a cold cache miss; waiters sleep on the condition variable above.
1154 5 fSurfaceCacheBuilding = true;
1155 203 }
1156
1157 5 SurfaceSamplingCache cache;
1158 try {
1159
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 BRepMesh_IncrementalMesh mesher(fShape, kOCCTRelativeDeflection, /*isRelative=*/Standard_True);
1160 (void)mesher;
1161
1162
5/8
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 25 times.
✗ Branch 7 not taken.
30 for (TopExp_Explorer ex(fShape, TopAbs_FACE); ex.More(); ex.Next()) {
1163
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
25 const TopoDS_Face& face = TopoDS::Face(ex.Current());
1164
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 TopLoc_Location loc;
1165
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 const Handle(Poly_Triangulation) & triangulation = BRep_Tool::Triangulation(face, loc);
1166
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
25 if (triangulation.IsNull()) {
1167 ✗ continue;
1168 }
1169
1170 25 const auto faceIndex = static_cast<std::uint32_t>(cache.faces.size());
1171
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 cache.faces.push_back(face);
1172
1173
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 const gp_Trsf& transform = loc.Transformation();
1174
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 const bool reverseWinding = face.Orientation() == TopAbs_REVERSED;
1175
1176
4/6
✓ Branch 0 taken 1051 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1051 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1026 times.
✓ Branch 5 taken 25 times.
1051 for (Standard_Integer i = 1; i <= triangulation->NbTriangles(); ++i) {
1177 1026 Standard_Integer idx1 = 0;
1178 1026 Standard_Integer idx2 = 0;
1179 1026 Standard_Integer idx3 = 0;
1180
3/6
✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1026 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1026 times.
✗ Branch 5 not taken.
1026 triangulation->Triangle(i).Get(idx1, idx2, idx3);
1181
2/2
✓ Branch 0 taken 1002 times.
✓ Branch 1 taken 24 times.
1026 if (reverseWinding) {
1182 24 std::swap(idx2, idx3);
1183 24 }
1184
1185
3/6
✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1026 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1026 times.
✗ Branch 5 not taken.
1026 const gp_Pnt q1 = triangulation->Node(idx1).Transformed(transform);
1186
3/6
✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1026 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1026 times.
✗ Branch 5 not taken.
1026 const gp_Pnt q2 = triangulation->Node(idx2).Transformed(transform);
1187
3/6
✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1026 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1026 times.
✗ Branch 5 not taken.
1026 const gp_Pnt q3 = triangulation->Node(idx3).Transformed(transform);
1188
1189
4/8
✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1026 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1026 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1026 times.
✗ Branch 7 not taken.
1026 const G4ThreeVector v1(q1.X(), q1.Y(), q1.Z());
1190
4/8
✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1026 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1026 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1026 times.
✗ Branch 7 not taken.
1026 const G4ThreeVector v2(q2.X(), q2.Y(), q2.Z());
1191
4/8
✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1026 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1026 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1026 times.
✗ Branch 7 not taken.
1026 const G4ThreeVector v3(q3.X(), q3.Y(), q3.Z());
1192
1193
4/8
✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1026 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1026 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1026 times.
✗ Branch 7 not taken.
1026 const G4double area = 0.5 * (v2 - v1).cross(v3 - v1).mag();
1194
2/2
✓ Branch 0 taken 1024 times.
✓ Branch 1 taken 2 times.
1026 if (area > 0.0) {
1195 // Prefix sums let GetPointOnSurface() perform an area-weighted random
1196 // triangle selection with one lower_bound on cumulativeAreas.
1197 1024 cache.totalArea += area;
1198
1/2
✓ Branch 0 taken 1024 times.
✗ Branch 1 not taken.
1024 cache.cumulativeAreas.push_back(cache.totalArea);
1199
4/8
✓ Branch 0 taken 1024 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1024 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1024 times.
✓ Branch 6 taken 1024 times.
✗ Branch 7 not taken.
1024 cache.triangles.push_back({v1, v2, v3, faceIndex});
1200 1024 }
1201 1026 }
1202 25 }
1203 5 } catch (...) {
1204 ✗ std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
1205 ✗ fSurfaceCacheBuilding = false;
1206 ✗ lock.unlock();
1207 ✗ fSurfaceCacheCV.notify_all();
1208 ✗ throw;
1209 ✗ }
1210
1211
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex);
1212
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 if (!(fSurfaceCache.has_value() && fSurfaceCacheGeneration == currentGen)) {
1213 5 fSurfaceCache = std::move(cache);
1214 5 fSurfaceCacheGeneration = currentGen;
1215 5 }
1216 5 fSurfaceCacheBuilding = false;
1217
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 lock.unlock();
1218 5 fSurfaceCacheCV.notify_all();
1219 5 return *fSurfaceCache;
1220 203 }
1221
1222 200 G4ThreeVector G4OCCTSolidKernel::GetPointOnSurface(const char* diagnosticName) const {
1223 200 const SurfaceSamplingCache& cache = GetOrBuildSurfaceCache();
1224
1225
2/4
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 200 times.
200 if (cache.triangles.empty() || cache.totalArea == 0.0) {
1226 ✗ G4ExceptionDescription msg;
1227 ✗ msg << "Tessellation of the OCCT shape";
1228 ✗ if (diagnosticName != nullptr) {
1229 ✗ msg << " for solid \"" << diagnosticName << "\"";
1230 ✗ }
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};
1234 ✗ }
1235
1236 200 const G4double target = G4UniformRand() * cache.totalArea;
1237 200 const auto it = std::ranges::lower_bound(cache.cumulativeAreas, target);
1238 400 const std::size_t idx = std::min(static_cast<std::size_t>(it - cache.cumulativeAreas.begin()),
1239 200 cache.triangles.size() - 1);
1240 200 const SurfaceTriangle& chosen = cache.triangles[idx];
1241
1242 200 G4double r1 = G4UniformRand();
1243 200 G4double r2 = G4UniformRand();
1244
2/2
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 110 times.
200 if (r1 + r2 > 1.0) {
1245 110 r1 = 1.0 - r1;
1246 110 r2 = 1.0 - r2;
1247 110 }
1248
1249 const G4ThreeVector tessPoint =
1250
5/10
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 200 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 200 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 200 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 200 times.
✗ Branch 9 not taken.
200 chosen.p1 + r1 * (chosen.p2 - chosen.p1) + r2 * (chosen.p3 - chosen.p1);
1251
1252 200 const TopoDS_Face& face = cache.faces[chosen.faceIndex];
1253
1/2
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
200 TopLoc_Location loc;
1254
2/4
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 200 times.
✗ Branch 3 not taken.
200 const Handle(Geom_Surface) geomSurface = BRep_Tool::Surface(face, loc);
1255
2/4
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 200 times.
✗ Branch 3 not taken.
200 if (!geomSurface.IsNull()) {
1256
4/8
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 200 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 200 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 200 times.
✗ Branch 7 not taken.
200 gp_Pnt tessPointLocal(tessPoint.x(), tessPoint.y(), tessPoint.z());
1257
2/4
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 200 times.
200 if (!loc.IsIdentity()) {
1258 ✗ tessPointLocal.Transform(loc.Transformation().Inverted());
1259 ✗ }
1260
1/2
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
200 GeomAPI_ProjectPointOnSurf projection(tessPointLocal, geomSurface);
1261
2/4
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 200 times.
✗ Branch 3 not taken.
200 if (projection.NbPoints() > 0) {
1262
1/2
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
200 gp_Pnt projectedPoint = projection.NearestPoint();
1263
2/4
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 200 times.
200 if (!loc.IsIdentity()) {
1264 ✗ projectedPoint.Transform(loc.Transformation());
1265 ✗ }
1266
4/8
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 200 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 200 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 200 times.
✗ Branch 7 not taken.
200 return {projectedPoint.X(), projectedPoint.Y(), projectedPoint.Z()};
1267 }
1268 200 }
1269
1270 ✗ return tessPoint;
1271 200 }
1272
1273 } // namespace g4occt::detail
1274