|
|
|||
File indexing completed on 2026-05-10 08:39:32
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // Copyright (C) 2026 G4OCCT Contributors 0003 0004 /// @file G4OCCTSolid.hh 0005 /// @brief Declaration of G4OCCTSolid. 0006 0007 #ifndef G4OCCT_G4OCCTSolid_hh 0008 #define G4OCCT_G4OCCTSolid_hh 0009 0010 #include <G4Cache.hh> 0011 #include <G4Polyhedron.hh> 0012 #include <G4ThreeVector.hh> 0013 #include <G4VSolid.hh> 0014 0015 // OCCT shape representation 0016 #include <BRepAdaptor_Surface.hxx> 0017 #include <BRepClass3d_SolidClassifier.hxx> 0018 #include <BRepExtrema_TriangleSet.hxx> 0019 #include <Bnd_Box.hxx> 0020 #include <IntCurvesFace_Intersector.hxx> 0021 #include <TopoDS_Face.hxx> 0022 #include <TopoDS_Shape.hxx> 0023 #include <TopoDS_TShape.hxx> 0024 #include <gp_Pln.hxx> 0025 #include <gp_Pnt2d.hxx> 0026 0027 #include <atomic> 0028 #include <condition_variable> 0029 #include <cstdint> 0030 #include <limits> 0031 #include <memory> 0032 #include <mutex> 0033 #include <optional> 0034 #include <stdexcept> 0035 #include <string> 0036 #include <unordered_map> 0037 #include <utility> 0038 #include <vector> 0039 0040 /** 0041 * @brief Geant4 solid wrapping an Open CASCADE Technology (OCCT) TopoDS_Shape. 0042 * 0043 * Wraps an Open CASCADE Technology (OCCT) TopoDS_Shape as a Geant4 solid 0044 * (G4VSolid). The OCCT shape is stored by value and is queried directly for 0045 * Geant4 navigation, extent, and visualisation requests. 0046 * 0047 * In OCCT the closest analogue to G4VSolid is TopoDS_Shape, which is the root 0048 * of the Boundary-Representation topology hierarchy and can describe any shape 0049 * from a simple box to a complex multi-face shell. The mapping is discussed in 0050 * detail in docs/geometry_mapping.md. 0051 * 0052 * ## Thread safety and caching 0053 * 0054 * `G4OCCTSolid` is shared read-only across all Geant4 worker threads once 0055 * the geometry is constructed. OCCT algorithm objects such as 0056 * `BRepClass3d_SolidClassifier` and `IntCurvesFace_Intersector` hold 0057 * mutable internal state and must not be shared between threads. Per-thread 0058 * caches of both algorithm objects are maintained via `G4Cache` so that the 0059 * one-time O(N_faces) initialisation cost is paid only once per thread rather 0060 * than on every navigation call. 0061 * 0062 * A monotonically increasing `fShapeGeneration` counter is incremented by 0063 * `SetOCCTShape()`. Each per-thread cache entry records the generation at 0064 * which it was built; `GetOrCreateClassifier()` and `GetOrCreateIntersector()` 0065 * rebuild the entry whenever the stored generation is stale. This ensures that 0066 * all worker threads automatically pick up a new shape on their next navigation 0067 * call, even if `SetOCCTShape()` is invoked after some threads have already 0068 * initialised their caches. 0069 */ 0070 class G4OCCTSolid : public G4VSolid { 0071 public: 0072 /** 0073 * Construct with a Geant4 solid name and an OCCT shape. 0074 * 0075 * @param name Name registered with the Geant4 solid store. 0076 * @param shape OCCT boundary-representation shape to wrap. 0077 * @throws std::invalid_argument if @p shape is null or has no computable bounding box. 0078 */ 0079 G4OCCTSolid(const G4String& name, const TopoDS_Shape& shape); 0080 0081 ~G4OCCTSolid() override; 0082 0083 /** 0084 * Load a STEP file and construct a G4OCCTSolid from the first shape found. 0085 * 0086 * Convenience factory that combines STEP file reading with solid construction. 0087 * Equivalent to constructing a `G4OCCTSolid` from the shape returned by a 0088 * `STEPControl_Reader`. 0089 * 0090 * @param name Name registered with the Geant4 solid store. 0091 * @param path Filesystem path to the STEP file. 0092 * @return Pointer to a newly heap-allocated G4OCCTSolid (owned by the caller). 0093 * @throws std::runtime_error if the file cannot be read or yields a null shape. 0094 */ 0095 static G4OCCTSolid* FromSTEP(const G4String& name, const std::string& path); 0096 0097 // ── G4VSolid pure-virtual interface ─────────────────────────────────────── 0098 0099 /// Return kInside, kSurface, or kOutside for point @p p. 0100 EInside Inside(const G4ThreeVector& p) const override; 0101 0102 /// Return the outward unit normal at surface point @p p. 0103 G4ThreeVector SurfaceNormal(const G4ThreeVector& p) const override; 0104 0105 /// Distance from external point @p p along direction @p v to solid surface. 0106 /// @param p Point outside (or on) the solid surface. 0107 /// @param v Non-zero unit direction vector (precondition required by the G4VSolid contract). 0108 G4double DistanceToIn(const G4ThreeVector& p, const G4ThreeVector& v) const override; 0109 0110 /// A lower bound on the shortest distance from external point @p p to the solid surface. 0111 /// 0112 /// Uses a two-stage acceleration pipeline: 0113 /// 0114 /// **Tier 0 (O(1)):** If @p p is outside the cached axis-aligned bounding box by more than 0115 /// `IntersectionTolerance()`, the AABB distance is returned immediately. This is always a 0116 /// valid conservative lower bound and avoids any OCCT call for distant exterior points. 0117 /// 0118 /// **Fallback:** Points within `IntersectionTolerance()` of the AABB fall through to 0119 /// `ExactDistanceToIn(p)`. 0120 /// 0121 /// For the exact shortest distance, use ExactDistanceToIn(p). 0122 G4double DistanceToIn(const G4ThreeVector& p) const override; 0123 0124 /// Distance from internal point @p p along direction @p v to solid surface. 0125 /// @param p Point inside (or on) the solid surface. 0126 /// @param v Non-zero unit direction vector (precondition required by the G4VSolid contract). 0127 G4double DistanceToOut(const G4ThreeVector& p, const G4ThreeVector& v, 0128 const G4bool calcNorm = false, G4bool* validNorm = nullptr, 0129 G4ThreeVector* n = nullptr) const override; 0130 0131 /// A lower bound on the shortest distance from internal point @p p to the solid surface. 0132 /// For the exact shortest distance, use ExactDistanceToOut(p). 0133 G4double DistanceToOut(const G4ThreeVector& p) const override; 0134 0135 /// Return a point sampled uniformly at random from the solid's surface. 0136 /// 0137 /// The OCCT shape is tessellated with a relative chord deflection of 1 % and 0138 /// each mesh triangle is selected with probability proportional to its 0139 /// (planar) area. A random point within the selected triangle is then 0140 /// generated using standard barycentric-coordinate sampling and projected 0141 /// back to the nearest point on the triangle's originating analytical OCCT 0142 /// face surface via `GeomAPI_ProjectPointOnSurf`. This projection step 0143 /// places the returned point on the analytical surface for curved faces 0144 /// (spheres, cylinders, cones, tori, etc.), rather than inside the solid at 0145 /// a chord midpoint of the tessellation mesh. If projection fails (e.g. for 0146 /// a degenerate or null surface), the raw tessellation point is returned as a 0147 /// fallback; such a point may lie slightly inside the solid. 0148 /// 0149 /// The distribution is area-weighted over the triangulated surface, which 0150 /// approximates the uniform distribution over the true analytic surface; the 0151 /// bias is proportional to the curvature and the 1 % chord-height error. 0152 /// The tessellation is cached per shape generation so repeated calls only 0153 /// require an O(log N_triangles) area selection plus one surface projection. 0154 /// 0155 /// Emits a @c JustWarning G4Exception and returns the origin if the 0156 /// tessellation produces no valid triangles. 0157 G4ThreeVector GetPointOnSurface() const override; 0158 0159 // ── G4OCCTSolid distance functions ──────────────────────────────────────── 0160 0161 /// Exact shortest distance from external point @p p to the solid surface. 0162 /// Returns 0 if @p p is on or inside the surface, or kInfinity if the 0163 /// calculation fails. 0164 G4double ExactDistanceToIn(const G4ThreeVector& p) const; 0165 0166 /// Exact shortest distance from internal point @p p to the solid surface. 0167 /// Returns 0 if @p p is within IntersectionTolerance() of the surface, or if 0168 /// the calculation fails. For points outside the solid, 0169 /// returns the positive distance to the nearest surface. 0170 G4double ExactDistanceToOut(const G4ThreeVector& p) const; 0171 0172 /// Compute and return the cubic volume of the solid. 0173 /// The result is cached; repeated calls return the cached value. 0174 G4double GetCubicVolume() override; 0175 0176 /// Compute and return the surface area of the solid. 0177 /// The result is cached; repeated calls return the cached value. 0178 G4double GetSurfaceArea() override; 0179 0180 /// Return a string identifying the entity type. 0181 G4GeometryType GetEntityType() const override; 0182 0183 /// Return the axis-aligned bounding box extent. 0184 G4VisExtent GetExtent() const override; 0185 0186 /// Return the axis-aligned bounding box limits. 0187 void BoundingLimits(G4ThreeVector& pMin, G4ThreeVector& pMax) const override; 0188 0189 /// Calculate the extent of the solid in the given axis. 0190 G4bool CalculateExtent(const EAxis pAxis, const G4VoxelLimits& pVoxelLimit, 0191 const G4AffineTransform& pTransform, G4double& pMin, 0192 G4double& pMax) const override; 0193 0194 /// Describe the solid to the graphics scene. 0195 void DescribeYourselfTo(G4VGraphicsScene& scene) const override; 0196 0197 /// Create a polyhedron representation for visualisation. 0198 G4Polyhedron* CreatePolyhedron() const override; 0199 0200 /// Stream a human-readable description. 0201 std::ostream& StreamInfo(std::ostream& os) const override; 0202 0203 // ── G4OCCTSolid-specific interface ──────────────────────────────────────── 0204 0205 /// Read access to the underlying OCCT shape. 0206 const TopoDS_Shape& GetOCCTShape() const { return fShape; } 0207 0208 /// Replace the underlying OCCT shape. 0209 /// @note Increments an internal generation counter so that every worker 0210 /// thread automatically reloads its per-thread classifier and 0211 /// intersector on its next navigation call. The shape update itself 0212 /// is not atomic with respect to ongoing navigation; avoid calling 0213 /// this while a simulation run is in progress. 0214 /// @throws std::invalid_argument if @p shape is null or has no computable bounding box. 0215 void SetOCCTShape(const TopoDS_Shape& shape) { 0216 if (shape.IsNull()) { 0217 throw std::invalid_argument("G4OCCTSolid::SetOCCTShape: shape must not be null"); 0218 } 0219 fShape = shape; 0220 ComputeBounds(); 0221 { 0222 std::unique_lock<std::mutex> lock(fPolyhedronMutex); 0223 fCachedPolyhedron.reset(); 0224 } 0225 { 0226 std::unique_lock<std::mutex> lock(fVolumeAreaMutex); 0227 fCachedVolume.reset(); 0228 fCachedSurfaceArea.reset(); 0229 } 0230 { 0231 std::unique_lock<std::mutex> lock(fSurfaceCacheMutex); 0232 fSurfaceCache.reset(); 0233 } 0234 fShapeGeneration.fetch_add(1, std::memory_order_release); 0235 } 0236 0237 private: 0238 /// Per-face bounding box entry used to prefilter the closest-face search in 0239 /// `SurfaceNormal`. 0240 struct FaceBounds { 0241 TopoDS_Face face; 0242 Bnd_Box box; 0243 BRepAdaptor_Surface adaptor; 0244 std::optional<gp_Pln> plane; ///< precomputed plane equation for planar faces 0245 /// Outer wire boundary vertices in the plane's (u,v) parameter space. 0246 /// Non-empty only when the face is planar and every edge of the outer wire 0247 /// is a straight line segment, which allows exact point-in-polygon testing 0248 /// without invoking the full OCCT topology machinery at navigation time. 0249 std::vector<gp_Pnt2d> uvPolygon; 0250 /// Precomputed constant outward normal for planar faces. Non-empty whenever 0251 /// @c uvPolygon is non-empty. Used by DistanceToOut(p,v) to skip the 0252 /// BRepLProp_SLProps evaluation on the hot normal-computation path. 0253 std::optional<G4ThreeVector> outwardNormal; 0254 }; 0255 0256 /// Result of the closest-face search. 0257 struct ClosestFaceMatch { 0258 TopoDS_Face face; 0259 G4double distance{kInfinity}; 0260 /// Index into the face-bounds cache vector (e.g. the @c faceBoundsCache 0261 /// argument passed to the search) used to retrieve the cached 0262 /// @c FaceBounds::adaptor without reconstructing it on the fly. 0263 std::size_t faceIndex{0}; 0264 /// UV parameters at the closest point on the face, available when 0265 /// BRepExtrema_DistShapeShape found an interior solution 0266 /// (BRepExtrema_IsInFace). Not set for edge or vertex solutions. 0267 std::optional<std::pair<Standard_Real, Standard_Real>> uv; 0268 }; 0269 0270 /// Axis-aligned bounding box: minimum and maximum corners. 0271 struct AxisAlignedBounds { 0272 G4ThreeVector min; 0273 G4ThreeVector max; 0274 }; 0275 0276 /// Per-thread classifier cache entry: generation stamp + lazily-built classifier. 0277 /// 0278 /// The `generation` field is compared against `fShapeGeneration` on every 0279 /// call to `GetOrCreateClassifier()`; a mismatch triggers a reload. 0280 /// Initialised to the maximum uint64 value, which can never match generation 0, 0281 /// so the first call from each thread always builds the classifier. 0282 struct ClassifierCache { 0283 std::uint64_t generation{std::numeric_limits<std::uint64_t>::max()}; 0284 std::optional<BRepClass3d_SolidClassifier> classifier; 0285 }; 0286 0287 /// Per-thread intersector cache entry: generation stamp + per-face intersectors. 0288 /// 0289 /// The `generation` field is compared against `fShapeGeneration` on every 0290 /// call to `GetOrCreateIntersector()`; a mismatch triggers a reload. 0291 /// Initialised to the maximum uint64 value, which can never match generation 0, 0292 /// so the first call from each thread always builds the intersector. 0293 /// 0294 /// `faceIntersectors` holds one `IntCurvesFace_Intersector` per entry in 0295 /// `fFaceBoundsCache`, in the same order. Each entry is heap-allocated 0296 /// (via `std::unique_ptr`) to avoid requiring movability of 0297 /// `IntCurvesFace_Intersector` during vector reallocation. The per-face 0298 /// intersectors are used by `DistanceToIn(p,v)` and `DistanceToOut(p,v,...)` 0299 /// in a bbox-prefiltered loop that avoids `NCollection_Sequence` heap 0300 /// allocation. 0301 /// 0302 /// `expandedBoxes` holds a copy of each entry's `FaceBounds::box`, enlarged 0303 /// by `IntersectionTolerance()`. Planar faces have zero-thickness bounding 0304 /// boxes in their normal direction; `Bnd_Box::IsOut(gp_Lin)` can return a 0305 /// floating-point false positive for such degenerate boxes when the line 0306 /// grazes the bounding plane. Enlarging by the tolerance ensures every box 0307 /// has finite extent in all directions and makes the `IsOut` test robust. 0308 struct IntersectorCache { 0309 std::uint64_t generation{std::numeric_limits<std::uint64_t>::max()}; 0310 std::vector<std::unique_ptr<IntCurvesFace_Intersector>> faceIntersectors; 0311 std::vector<Bnd_Box> expandedBoxes; ///< per-face boxes enlarged by `IntersectionTolerance()` 0312 }; 0313 0314 /// A proven inscribed sphere: every point within @c radius of @c centre is 0315 /// inside the solid. The guarantee comes from @c DistanceToOut(centre) ≥ radius. 0316 struct InscribedSphere { 0317 G4ThreeVector centre; 0318 G4double radius; 0319 }; 0320 0321 /// Per-thread inscribed-sphere cache. 0322 /// 0323 /// Populated from `fInitialSpheres` on first access per thread and grown by 0324 /// `TryInsertSphere()` on each `DistanceToOut(p)` call. Kept sorted in 0325 /// descending order of radius so `Inside()` checks the largest (most useful) 0326 /// spheres first and benefits from early exit. 0327 /// 0328 /// The `generation` field mirrors the classifier-cache pattern: a mismatch 0329 /// with `fShapeGeneration` causes the cache to be re-seeded from 0330 /// `fInitialSpheres`, discarding any runtime-accumulated spheres for the old 0331 /// shape. 0332 struct SphereCacheData { 0333 std::vector<InscribedSphere> spheres; 0334 std::uint64_t generation{std::numeric_limits<std::uint64_t>::max()}; 0335 }; 0336 0337 /// Maximum number of inscribed spheres kept in the per-thread cache. 0338 static constexpr std::size_t kMaxInscribedSpheres = 64; 0339 0340 /// Single mesh triangle used by the surface-sampling cache. 0341 /// 0342 /// Each triangle carries only the three vertex positions and a compact index 0343 /// into `SurfaceSamplingCache::faces`. Using an index instead of a full 0344 /// `TopoDS_Face` value avoids duplicating the face handle across every 0345 /// triangle on that face, which can be thousands of entries for a 0346 /// finely-tessellated shape. 0347 struct SurfaceTriangle { 0348 G4ThreeVector p1, p2, p3; 0349 std::uint32_t 0350 faceIndex; ///< Index into SurfaceSamplingCache::faces (valid range: [0, faces.size())). 0351 }; 0352 0353 /// Cached data for area-weighted surface-point sampling via `GetPointOnSurface()`. 0354 /// 0355 /// Stores the flat list of triangles extracted from the OCCT triangulation 0356 /// together with a cumulative-area array that enables O(log N) area-weighted 0357 /// selection, and a deduplicated list of the OCCT faces from which the 0358 /// triangles were collected (used for surface projection in 0359 /// `GetPointOnSurface()`). Built lazily on the first `GetPointOnSurface()` 0360 /// call after each shape update; keyed by `fShapeGeneration`. 0361 struct SurfaceSamplingCache { 0362 std::vector<TopoDS_Face> faces; ///< Unique OCCT faces, one entry per shape face. 0363 std::vector<SurfaceTriangle> triangles; 0364 std::vector<G4double> cumulativeAreas; 0365 G4double totalArea{0.0}; 0366 }; 0367 0368 /// The underlying OCCT shape. Guaranteed non-null: the constructor and 0369 /// SetOCCTShape() both throw std::invalid_argument when given a null shape. 0370 TopoDS_Shape fShape; 0371 0372 /// Cached axis-aligned bounding box; computed eagerly in the constructor and 0373 /// recomputed by `ComputeBounds()` whenever `SetOCCTShape()` is called. 0374 /// Always valid after construction: the constructor and `SetOCCTShape()` throw 0375 /// `std::invalid_argument` if the shape has no computable bounding box. 0376 AxisAlignedBounds fCachedBounds; 0377 0378 /// Per-face bounding boxes, populated alongside `fCachedBounds` in `ComputeBounds()`. 0379 /// Used by `SurfaceNormal` as a lower-bound prefilter to avoid calling 0380 /// `BRepExtrema_DistShapeShape` on faces that are provably farther than the 0381 /// current best candidate. Written once at construction / `SetOCCTShape()`; 0382 /// read-only during navigation, so no additional synchronisation is required. 0383 std::vector<FaceBounds> fFaceBoundsCache; 0384 0385 /// Index from a face's underlying `TShape` pointer to the list of 0386 /// `fFaceBoundsCache` entry indices that share that `TShape`. Populated 0387 /// alongside `fFaceBoundsCache` in `ComputeBounds()` and used by 0388 /// `DistanceToOut(p,v,...)` to avoid an O(N) linear scan when looking up 0389 /// the cached `BRepAdaptor_Surface` for the hit face returned by the 0390 /// intersector. 0391 /// 0392 /// Hash lookup narrows candidates to the (almost always singleton) set of 0393 /// faces sharing the same `TShape`; `IsPartner()` (TShape + Location) is 0394 /// then used within that set to select the correct located entry, handling 0395 /// instanced sub-shapes where the same `TShape` appears at several locations. 0396 /// 0397 /// Written once at construction / `SetOCCTShape()`; read-only during 0398 /// navigation. 0399 std::unordered_map<const TopoDS_TShape*, std::vector<std::size_t>> fFaceAdaptorIndex; 0400 0401 /// Inscribed spheres seeded at construction time by `ComputeInitialSpheres()`. 0402 /// 0403 /// Evaluated once per shape at up to 15 interior candidate points (AABB centre, 0404 /// 6 axis-offset interior points each placed at half the distance from the AABB 0405 /// centre to the nearest face along that axis, and 8 octant centres) using 0406 /// `BVHLowerBoundDistance`. 0407 /// Written only in `ComputeBounds()` → `ComputeInitialSpheres()`; thereafter 0408 /// read-only, so no additional synchronisation is needed. 0409 /// Copied into each thread's `SphereCacheData` on first cache access. 0410 std::vector<InscribedSphere> fInitialSpheres; 0411 0412 /// BVH-accelerated triangle set over the tessellated surface of @c fShape. 0413 /// 0414 /// Built once in `ComputeBounds()` after `BRepMesh_IncrementalMesh` tessellates 0415 /// the shape. Used by `BVHLowerBoundDistance()` to compute O(log T) lower bounds 0416 /// on the point-to-surface distance. Null when the shape has no faces. 0417 /// Written only in `ComputeBounds()`; read-only (const BVH traversal) during 0418 /// navigation — no additional synchronisation is required beyond the construction 0419 /// ordering already guaranteed by the geometry-build phase. 0420 Handle(BRepExtrema_TriangleSet) fTriangleSet; 0421 0422 /// Conservative upper bound on the Hausdorff distance between the analytical 0423 /// surface of @c fShape and its tessellation stored in @c fTriangleSet. 0424 /// 0425 /// Set in `ComputeBounds()` to `kRelativeDeflection × max_face_bbox_diagonal`, 0426 /// which bounds the chord-height error for a relative linear deflection of 0427 /// `kRelativeDeflection`. Used in `BVHLowerBoundDistance()` as the correction 0428 /// term `δ` in `s = max(0, mesh_dist − δ)` to guarantee a valid lower bound. 0429 /// Also retained for contexts where a conservative global bound is needed (e.g. 0430 /// the `SurfaceNormal` seed distance calculation). 0431 G4double fBVHDeflection{0.0}; 0432 0433 /// Per-face Hausdorff deflection bound: `kRelativeDeflection × face_bbox_diagonal` for 0434 /// each face in @c fFaceBoundsCache (same ordering). In `BVHLowerBoundDistance()`, 0435 /// `BRepExtrema_TriangleSet::GetFaceID()` maps the nearest triangle back to its face 0436 /// so the tightest per-face correction can be used instead of the global 0437 /// @c fBVHDeflection. For queries near small faces this yields a tighter lower bound 0438 /// and avoids unnecessary fall-through to the expensive exact solver. 0439 /// 0440 /// Populated alongside @c fTriangleSet in `ComputeBounds()`. Empty when 0441 /// @c fTriangleSet is null (no faces). 0442 std::vector<G4double> fFaceDeflections; 0443 0444 /// True if every face in @c fFaceBoundsCache has a precomputed plane (i.e. all 0445 /// faces are planar). Set by `ComputeBounds()`. When true, `DistanceToOut(p)` 0446 /// can bypass the BVH triangle-mesh traversal entirely. 0447 bool fAllFacesPlanar{false}; 0448 0449 /// Monotonically increasing counter; incremented by each `SetOCCTShape()` call. 0450 /// Read (acquire) in `GetOrCreateClassifier()` and `GetOrCreateIntersector()` const; 0451 /// written (release) in `SetOCCTShape()`. 0452 std::atomic<std::uint64_t> fShapeGeneration{0}; 0453 0454 /// Per-thread cache of the BRepClass3d_SolidClassifier for this solid. 0455 /// 0456 /// Initialised lazily on the first `Inside` or `DistanceToIn(p)` call on 0457 /// each thread. Stale entries (generation mismatch) are rebuilt automatically. 0458 mutable G4Cache<ClassifierCache> fClassifierCache; 0459 0460 /// Per-thread cache of per-face `IntCurvesFace_Intersector` objects for this solid. 0461 /// 0462 /// Initialised lazily on the first `DistanceToIn(p, v)` or `DistanceToOut(p, v)` 0463 /// call on each thread. Stale entries (generation mismatch) are rebuilt automatically. 0464 mutable G4Cache<IntersectorCache> fIntersectorCache; 0465 0466 /// Per-thread inscribed-sphere cache, grown online from `DistanceToOut(p)` calls. 0467 /// 0468 /// Seeded from `fInitialSpheres` on first access per thread; grown by 0469 /// `TryInsertSphere()`. Stale entries (generation mismatch) are re-seeded from 0470 /// `fInitialSpheres`, discarding runtime-accumulated spheres for the old shape. 0471 mutable G4Cache<SphereCacheData> fSphereCache; 0472 0473 /// Cached polyhedron built by the first `CreatePolyhedron()` call after each 0474 /// shape update. Subsequent calls return a copy of this object, avoiding a 0475 /// repeated (and expensive) `BRepMesh_IncrementalMesh` tessellation. 0476 /// 0477 /// Access to this field, `fPolyhedronGeneration`, and `fPolyhedronBuilding` is 0478 /// serialised by `fPolyhedronMutex`. `nullptr` indicates that no valid cache 0479 /// entry exists for the current shape generation. 0480 mutable std::unique_ptr<G4Polyhedron> fCachedPolyhedron; 0481 0482 /// Shape-generation stamp at which `fCachedPolyhedron` was last built. 0483 /// Initialised to `std::numeric_limits<std::uint64_t>::max()` so that it 0484 /// can never match the initial `fShapeGeneration` value of 0, forcing a 0485 /// build on the first call to `CreatePolyhedron()`. 0486 /// Protected by `fPolyhedronMutex`. 0487 mutable std::uint64_t fPolyhedronGeneration{std::numeric_limits<std::uint64_t>::max()}; 0488 0489 /// True while one thread is performing the expensive tessellation. 0490 /// Other threads that also miss the cache wait on `fPolyhedronCV` instead 0491 /// of running duplicate tessellations. 0492 /// Protected by `fPolyhedronMutex`. 0493 mutable bool fPolyhedronBuilding{false}; 0494 0495 /// Mutex serialising concurrent access to `fCachedPolyhedron`, 0496 /// `fPolyhedronGeneration`, and `fPolyhedronBuilding` in `CreatePolyhedron()`. 0497 mutable std::mutex fPolyhedronMutex; 0498 0499 /// Notified when `fPolyhedronBuilding` transitions to false so that threads 0500 /// waiting for a concurrent build can re-evaluate the cache. 0501 mutable std::condition_variable fPolyhedronCV; 0502 0503 /// Cached cubic volume in mm³; `std::nullopt` until first `GetCubicVolume()` call 0504 /// or after each `SetOCCTShape()` call. Protected by `fVolumeAreaMutex`. 0505 mutable std::optional<G4double> fCachedVolume; 0506 0507 /// Cached surface area in mm²; `std::nullopt` until first `GetSurfaceArea()` call 0508 /// or after each `SetOCCTShape()` call. Protected by `fVolumeAreaMutex`. 0509 mutable std::optional<G4double> fCachedSurfaceArea; 0510 0511 /// Mutex serialising concurrent access to `fCachedVolume` and `fCachedSurfaceArea`. 0512 mutable std::mutex fVolumeAreaMutex; 0513 0514 /// Cached surface-sampling data built by the first `GetPointOnSurface()` call 0515 /// after each shape update. `std::nullopt` indicates no valid entry for the 0516 /// current shape generation. Protected by `fSurfaceCacheMutex`. 0517 mutable std::optional<SurfaceSamplingCache> fSurfaceCache; 0518 0519 /// Shape-generation stamp at which `fSurfaceCache` was built. 0520 /// Initialised to `std::numeric_limits<std::uint64_t>::max()` so that it can 0521 /// never match the initial `fShapeGeneration` value of 0, forcing a build on 0522 /// the first `GetPointOnSurface()` call. Protected by `fSurfaceCacheMutex`. 0523 mutable std::uint64_t fSurfaceCacheGeneration{std::numeric_limits<std::uint64_t>::max()}; 0524 0525 /// Mutex serialising concurrent access to `fSurfaceCache` and 0526 /// `fSurfaceCacheGeneration` in `GetOrBuildSurfaceCache()`. 0527 mutable std::mutex fSurfaceCacheMutex; 0528 0529 /// Return a reference to the per-thread classifier, (re-)initialising it from 0530 /// @c fShape whenever the cached generation does not match @c fShapeGeneration. 0531 BRepClass3d_SolidClassifier& GetOrCreateClassifier() const; 0532 0533 /// Return a reference to the per-thread intersector cache, (re-)initialising 0534 /// it from @c fShape whenever the cached generation does not match 0535 /// @c fShapeGeneration. The cache holds one `IntCurvesFace_Intersector` per 0536 /// face in `fFaceBoundsCache`, in the same order. 0537 IntersectorCache& GetOrCreateIntersector() const; 0538 0539 /// Return a reference to the per-thread inscribed-sphere cache, seeding it from 0540 /// @c fInitialSpheres if the cached generation does not match @c fShapeGeneration. 0541 SphereCacheData& GetOrInitSphereCache() const; 0542 0543 /// Attempt to insert an inscribed sphere with centre @p centre and radius @p d into 0544 /// the per-thread sphere cache. The sphere is accepted only if it is not already 0545 /// dominated by an existing cache entry and passes the minimum-radius filter. 0546 /// Maintains the cache sorted descending by radius and bounded to @c kMaxInscribedSpheres. 0547 void TryInsertSphere(const G4ThreeVector& centre, G4double d) const; 0548 0549 /// Compute the axis-aligned bounding box of @c fShape and store it in 0550 /// @c fCachedBounds, and populate @c fFaceBoundsCache with per-face bounding 0551 /// boxes. Throws `std::invalid_argument` when the shape has no computable 0552 /// bounding box (e.g. an empty compound). Called from the constructor and 0553 /// @c SetOCCTShape(). 0554 void ComputeBounds(); 0555 0556 /// Seed @c fInitialSpheres with inscribed spheres at up to 15 interior candidate 0557 /// points (AABB centre, 6 face midpoints, 8 octant centres). Called at the end 0558 /// of @c ComputeBounds() after the BVH mesh is available. Each candidate that 0559 /// classifies as @c TopAbs_IN and has a positive BVH lower-bound distance is 0560 /// stored as a seed sphere. Thread-safe: written only during geometry construction, 0561 /// read-only during navigation. 0562 void ComputeInitialSpheres(); 0563 0564 /// Compute the distance from @p p to the cached axis-aligned bounding box. 0565 /// Returns 0 when @p p is on or inside the AABB. 0566 G4double AABBLowerBound(const G4ThreeVector& p) const; 0567 0568 /// Compute a conservative lower bound on the distance from @p p to the solid surface 0569 /// using the BVH-accelerated triangle set @c fTriangleSet. 0570 /// 0571 /// The returned value satisfies @c 0 ≤ s ≤ true_distance, making it a valid 0572 /// Geant4 safety distance. Returns @c kInfinity if @c fTriangleSet is not 0573 /// available (null or empty). 0574 G4double BVHLowerBoundDistance(const G4ThreeVector& p) const; 0575 0576 /// Returns the minimum perpendicular distance from @p p to any planar face's 0577 /// infinite supporting plane. 0578 /// 0579 /// When the solid boundary is composed entirely of planar faces, the returned 0580 /// value is a conservative safety distance satisfying 0581 /// @c 0 ≤ s ≤ true_distance; for interior points of convex all-planar solids 0582 /// it is exact. For solids that include any curved faces (mixed-topology 0583 /// solids), this quantity is only a heuristic accelerator and is not 0584 /// guaranteed to be a strict lower bound on the true distance to the surface. 0585 /// 0586 /// Returns @c kInfinity if no planar faces exist. 0587 G4double PlanarFaceLowerBoundDistance(const G4ThreeVector& p) const; 0588 0589 /// Find the closest trimmed face to @p point using pre-computed per-face bounding 0590 /// boxes as a lower-bound prefilter. A face whose AABB distance from @p point 0591 /// exceeds @p maxDistance (default: @c kInfinity) or the current best candidate 0592 /// distance is skipped before the more expensive BRepExtrema_DistShapeShape call 0593 /// is made. Passing a BVH-derived upper bound as @p maxDistance lets callers 0594 /// skip faces that are provably farther than the nearest tessellated surface point 0595 /// plus twice the BVH deflection, reducing the number of BRepExtrema calls. 0596 static std::optional<ClosestFaceMatch> 0597 TryFindClosestFace(const std::vector<FaceBounds>& faceBoundsCache, const G4ThreeVector& point, 0598 G4double maxDistance = kInfinity); 0599 0600 /// Build and cache (or return the already-cached) surface-sampling data for 0601 /// the current shape generation. The OCCT shape is tessellated first 0602 /// (idempotent if a triangulation already exists), then all mesh triangles 0603 /// are collected with their areas. The result is protected by 0604 /// @c fSurfaceCacheMutex and keyed by @c fShapeGeneration. 0605 const SurfaceSamplingCache& GetOrBuildSurfaceCache() const; 0606 }; 0607 0608 #endif // G4OCCT_G4OCCTSolid_hh
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|