|
| 1 | +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. |
| 2 | +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. |
| 3 | +// All rights not expressly granted are reserved. |
| 4 | +// |
| 5 | +// This software is distributed under the terms of the GNU General Public |
| 6 | +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". |
| 7 | +// |
| 8 | +// In applying this license CERN does not waive the privileges and immunities |
| 9 | +// granted to it by virtue of its status as an Intergovernmental Organization |
| 10 | +// or submit itself to any jurisdiction. |
| 11 | +/// \author Sandro Wenzel <sandro.wenzel@cern.ch> |
| 12 | +/// \since 2026-08 |
| 13 | + |
| 14 | +#ifndef ALICEO2_CADSUPPORT_O2FLATCSG_ |
| 15 | +#define ALICEO2_CADSUPPORT_O2FLATCSG_ |
| 16 | + |
| 17 | +#include "TGeoBBox.h" |
| 18 | + |
| 19 | +#include <vector> |
| 20 | + |
| 21 | +namespace o2 |
| 22 | +{ |
| 23 | +namespace cad |
| 24 | +{ |
| 25 | + |
| 26 | +/// One signed implicit halfspace, the region `sign * f(x) <= 0`: kQuadric stores `x^T A x + 2 b^T x + c` as |
| 27 | +/// (a00, a01, a02, a11, a12, a22, b0, b1, b2, c); kTorus stores (px, py, pz, dx, dy, dz, R, r) in the first eight. |
| 28 | +struct FlatCSGHalfspace { |
| 29 | + enum Kind : int { kQuadric = 0, |
| 30 | + kTorus = 1 }; |
| 31 | + int kind = kQuadric; |
| 32 | + double sign = 1.; |
| 33 | + double c[11] = {}; |
| 34 | +}; |
| 35 | + |
| 36 | +/// One DNF cell: `[first, first + count)` of the halfspace array, intersected; `volume` is its own volume. |
| 37 | +struct FlatCSGCell { |
| 38 | + int first = 0; |
| 39 | + int count = 0; |
| 40 | + double volume = 0.; |
| 41 | +}; |
| 42 | + |
| 43 | +/// One box of the sub-cell subdivision; `nActive == 0` means it is wholly inside its cell. |
| 44 | +/// An active list describes its cell only inside its box, so every ray query clips to the box first. |
| 45 | +struct FlatCSGBox { |
| 46 | + double min[3] = {}; |
| 47 | + double max[3] = {}; |
| 48 | + int cell = -1; |
| 49 | + int firstActive = 0; |
| 50 | + int nActive = 0; |
| 51 | +}; |
| 52 | + |
| 53 | +/// A solid stored as a union of intersection cells over signed implicit halfspaces, the flat DNF of a decomposed part. |
| 54 | +/// Every accelerated query has a bit-identical `_Loop` twin over all cells and halfspaces. |
| 55 | +class O2FlatCSG : public TGeoBBox |
| 56 | +{ |
| 57 | + public: |
| 58 | + O2FlatCSG(); |
| 59 | + explicit O2FlatCSG(const char* name); |
| 60 | + ~O2FlatCSG() override; |
| 61 | + |
| 62 | + // The shape owns a raw `bvh::v2::Bvh` behind `fBVH`, so a compiler-written copy would hand two |
| 63 | + // shapes the same BVH and then free it twice; same treatment as O2BVHAssembly. |
| 64 | + O2FlatCSG(const O2FlatCSG&) = delete; |
| 65 | + O2FlatCSG& operator=(const O2FlatCSG&) = delete; |
| 66 | + |
| 67 | + // ---- building ------------------------------------------------------------------------- |
| 68 | + /// Append a quadric halfspace; returns its index. `sign` is +1 or -1, inside is `sign*Q <= 0`. |
| 69 | + int AddQuadric(double sign, const double coeff[10]); |
| 70 | + /// Append a torus halfspace, inside `sign * (sqrt((rho - major)^2 + z^2) - minor) <= 0` about unit \a axis; returns its index. |
| 71 | + int AddTorus(double sign, const double* centre, const double* axis, double major, double minor); |
| 72 | + /// Append a cell over `[first, first + count)` of the halfspace array; returns its index. |
| 73 | + int AddCell(int first, int count, double volume); |
| 74 | + |
| 75 | + int GetNhalfspaces() const { return static_cast<int>(fHalfspaces.size()); } |
| 76 | + int GetNcells() const { return static_cast<int>(fCells.size()); } |
| 77 | + const FlatCSGHalfspace& GetHalfspace(int index) const { return fHalfspaces[index]; } |
| 78 | + const FlatCSGCell& GetCell(int index) const { return fCells[index]; } |
| 79 | + |
| 80 | + /// The AABB of cell \a cell. The halfspaces alone do not bound a cell -- an intersection of |
| 81 | + /// halfspaces can be unbounded -- so the converter supplies the box the decomposition measured. |
| 82 | + void SetCellBBox(int cell, const double* lo, const double* hi); |
| 83 | + /// The AABB `SetCellBBox` recorded for cell \a cell, for the sidecar writer. Reads back zeros |
| 84 | + /// for a cell whose box was never set. |
| 85 | + void GetCellBBox(int cell, double* lo, double* hi) const; |
| 86 | + |
| 87 | + /// Build the sub-cell boxes and their BVH. Call once, after the last AddCell. |
| 88 | + void CloseShape(); |
| 89 | + bool IsClosed() const { return fClosed; } |
| 90 | + |
| 91 | + /// Bytes held by the BVH nodes and the primitive-index permutation. |
| 92 | + size_t GetBVHMemory() const; |
| 93 | + |
| 94 | + int GetNboxes() const { return static_cast<int>(fBoxes.size()); } |
| 95 | + const FlatCSGBox& GetBox(int index) const { return fBoxes[index]; } |
| 96 | + /// For the tests: the box structure is the thing being proved sound, so it has to be readable. |
| 97 | + int GetActive(int index) const { return fActive[index]; } |
| 98 | + /// True when every halfspace of cell `index` contains `point`. |
| 99 | + bool CellContains(int index, const double* point) const; |
| 100 | + |
| 101 | + /// Subdivision depth cap. See `fSplitDepth` for where the default comes from. |
| 102 | + void SetSplitDepth(int depth) { fSplitDepth = depth; } |
| 103 | + /// Stop splitting a box narrower than this fraction of the part's bounding-box diagonal. |
| 104 | + /// See `fMinBoxFraction` for where the default comes from. |
| 105 | + void SetMinBoxFraction(double fraction) { fMinBoxFraction = fraction; } |
| 106 | + |
| 107 | + /// `sign * f(point)`; the halfspace contains the point when this is `<= 0`. |
| 108 | + static double EvalHalfspace(const FlatCSGHalfspace& halfspace, const double* point); |
| 109 | + |
| 110 | + /// A rigorous enclosure `[rangeLo, rangeHi]` of `sign * f` over the box `[lo, hi]`, padded outward. |
| 111 | + /// Requires `lo[i] <= hi[i]` and finite bounds, which CloseShape enforces; it does not check them. |
| 112 | + static void HalfspaceRange(const FlatCSGHalfspace& halfspace, const double* lo, const double* hi, |
| 113 | + double& rangeLo, double& rangeHi); |
| 114 | + |
| 115 | + /// Real roots of `sign * f(origin + t*dir) = 0`, unsorted, at most four; returns the count. |
| 116 | + static int HalfspaceRoots(const FlatCSGHalfspace& halfspace, const double* origin, |
| 117 | + const double* dir, double* roots); |
| 118 | + |
| 119 | + /// The occupancy of cell \a cell along the ray within `[tlo, thi]`, as `[enter, exit]` pairs in \a out; a null \a active uses every halfspace. |
| 120 | + /// Returns the pair count, or a negative value when \a maxOut is too small. |
| 121 | + int CellIntervals(int cell, const int* active, int nActive, const double* origin, |
| 122 | + const double* dir, double tlo, double thi, double* out, int maxOut) const; |
| 123 | + |
| 124 | + // ---- the TGeoShape contract; the accelerated queries use their `_Loop` twin until CloseShape succeeds ---- |
| 125 | + Bool_t Contains(const Double_t* point) const override; |
| 126 | + |
| 127 | + Double_t DistFromOutside(const Double_t* point, const Double_t* dir, Int_t iact = 1, |
| 128 | + Double_t step = TGeoShape::Big(), Double_t* safe = nullptr) const override; |
| 129 | + Double_t DistFromInside(const Double_t* point, const Double_t* dir, Int_t iact = 1, |
| 130 | + Double_t step = TGeoShape::Big(), Double_t* safe = nullptr) const override; |
| 131 | + |
| 132 | + /// A lower bound on the distance to the boundary from the box structure: outside the nearest box, inside the faces of a solid box, else 0. |
| 133 | + Double_t Safety(const Double_t* point, Bool_t in = kTRUE) const override; |
| 134 | + |
| 135 | + /// Per-thread count of DistFromInside queries whose pruned traversal had to be redone unpruned. |
| 136 | + static void ResetUnprunedRetryCounter(); |
| 137 | + static long long GetUnprunedRetryCount(); |
| 138 | + |
| 139 | + /// The union of the retained sub-cell boxes, tighter than the union of the cell AABBs. |
| 140 | + void ComputeBBox() override; |
| 141 | + |
| 142 | + /// The sum of the cells' own volumes. The cells of a decomposition are disjoint by construction |
| 143 | + /// (`decompose`'s volume guard checks it), so there is no inclusion-exclusion to do. |
| 144 | + Double_t Capacity() const override; |
| 145 | + |
| 146 | + /// The normal of the halfspace nearest to equality at `point`, oriented along `dir`. |
| 147 | + void ComputeNormal(const Double_t* point, const Double_t* dir, Double_t* norm) const override; |
| 148 | + /// Points on the solid's own boundary, for the overlap checkers; kFALSE if fewer than \a npoints were found. |
| 149 | + Bool_t GetPointsOnSegments(Int_t npoints, Double_t* array) const override; |
| 150 | + |
| 151 | + // ---- the reference twins --------------------------------------------------------------- |
| 152 | + Bool_t Contains_Loop(const Double_t* point) const; |
| 153 | + |
| 154 | + Double_t DistFromOutside_Loop(const Double_t* point, const Double_t* dir, |
| 155 | + Double_t step = TGeoShape::Big()) const; |
| 156 | + Double_t DistFromInside_Loop(const Double_t* point, const Double_t* dir, |
| 157 | + Double_t step = TGeoShape::Big()) const; |
| 158 | + /// `Safety`'s twin over all boxes: it must equal `Safety` and be a sound bound. |
| 159 | + Double_t Safety_Loop(const Double_t* point, Bool_t in = kTRUE) const; |
| 160 | + |
| 161 | + protected: |
| 162 | + /// Grow the per-cell bounding-box storage to the cell count. |
| 163 | + void EnsureCellBBoxStorage(); |
| 164 | + |
| 165 | + /// The accelerated DistFromOutside/DistFromInside bodies; each clips the ray to a box before using its active list. |
| 166 | + Double_t DistFromOutsideBVH(const Double_t* point, const Double_t* dir, Double_t step) const; |
| 167 | + Double_t DistFromInsideBVH(const Double_t* point, const Double_t* dir, Double_t step) const; |
| 168 | + |
| 169 | + /// What the running bound prunes against: nothing, the nearest entry so far (DistFromOutside) or |
| 170 | + /// the far end of the interval holding t = 0 so far (DistFromInside). |
| 171 | + enum class RayBound { kNone, |
| 172 | + kEntry, |
| 173 | + kExit }; |
| 174 | + |
| 175 | + /// Each box's own occupancy pieces along the ray within `[0, step]`: `[enter, exit]` in \a pairs and its cell in \a cells, unmerged. |
| 176 | + /// False when a `CellIntervals` call overflowed. \a smallestPruned reports the nearest entry the bound skipped, Big if it skipped nothing. |
| 177 | + bool GatherRayPieces(const Double_t* point, const Double_t* dir, Double_t step, |
| 178 | + std::vector<double>& pairs, std::vector<int>& cells, RayBound bound, |
| 179 | + double& smallestPruned) const; |
| 180 | + |
| 181 | + /// Recursively split `[lo, hi]` for `cell`, dropping the halfspaces the range bound decides and the boxes it proves outside. |
| 182 | + /// A split of a far-from-cubic box draws on `cubifyBudget`, any other on `depth`. |
| 183 | + void SplitBox(int cell, const double* lo, const double* hi, const std::vector<int>& active, |
| 184 | + int depth, double minSize, int cubifyBudget); |
| 185 | + |
| 186 | + std::vector<FlatCSGHalfspace> fHalfspaces; ///< the flat halfspace array |
| 187 | + std::vector<FlatCSGCell> fCells; ///< the DNF's cells, indexing into it |
| 188 | + |
| 189 | + /// The sub-cell boxes, rebuilt by `CloseShape`; not streamed. |
| 190 | + std::vector<FlatCSGBox> fBoxes; //! |
| 191 | + /// The boxes' active-halfspace lists, concatenated. Derived alongside `fBoxes`; not streamed |
| 192 | + /// for the same reason. |
| 193 | + std::vector<int> fActive; //! |
| 194 | + std::vector<double> fCellLo; ///< each cell's AABB low corner, 3 doubles per cell |
| 195 | + std::vector<double> fCellHi; ///< each cell's AABB high corner, 3 doubles per cell |
| 196 | + /// Whether `SetCellBBox` was ever called for a given cell; `CloseShape` refuses to build a |
| 197 | + /// solid missing one rather than silently drop that cell -- see `CloseShape`'s implementation. |
| 198 | + std::vector<bool> fCellBBoxSet; |
| 199 | + /// Set by a successful `CloseShape`; not streamed. The `#pragma read` rule closes every shape ROOT reads back. |
| 200 | + bool fClosed = false; //! |
| 201 | + /// Subdivision depth cap, and the minimum box size as a fraction of the part's bounding-box |
| 202 | + /// diagonal, chosen for query cost on the shipped parts. |
| 203 | + int fSplitDepth = 4; |
| 204 | + double fMinBoxFraction = 0.05; |
| 205 | + |
| 206 | + /// The BVH over `fBoxes`, rebuilt by `CloseShape`; not streamed. |
| 207 | + void* fBVH = nullptr; //! bvh::v2::Bvh over the sub-cell boxes |
| 208 | + |
| 209 | + // Scratch buffers are thread_local statics in the .cxx, never members: shapes are shared by all navigator threads. |
| 210 | + |
| 211 | + ClassDefOverride(O2FlatCSG, 1) // flat-DNF halfspace shape class |
| 212 | +}; |
| 213 | + |
| 214 | +} // namespace cad |
| 215 | +} // namespace o2 |
| 216 | + |
| 217 | +#endif |
0 commit comments