Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-08 09:28:17

0001 /*
0002  * PolyhedronStruct.h
0003  *
0004  *  Created on: 09.12.2016
0005  *      Author: mgheata
0006  */
0007 #ifndef VECGEOM_POLYHEDRONSTRUCT_H_
0008 #define VECGEOM_POLYHEDRONSTRUCT_H_
0009 
0010 #include <ostream>
0011 
0012 #include <VecCore/VecCore>
0013 #include "VecGeom/base/Vector3D.h"
0014 #include "VecGeom/volumes/Quadrilaterals.h"
0015 #include "VecGeom/volumes/Wedge_Evolution.h"
0016 #include "VecGeom/base/Array.h"
0017 #include "VecGeom/base/SOA3D.h"
0018 #include "VecGeom/volumes/TubeStruct.h"
0019 
0020 // These enums should be in the scope vecgeom::Polyhedron, but when used in the
0021 // shape implementation helper instantiations, nvcc gets confused:
0022 
0023 enum struct EInnerRadii { kFalse = -1, kGeneric = 0, kTrue = 1 };
0024 enum struct EPhiCutout { kFalse = -1, kGeneric = 0, kTrue = 1, kLarge = 2 };
0025 
0026 namespace vecgeom {
0027 
0028 VECGEOM_DEVICE_FORWARD_DECLARE(struct ZSegment;);
0029 VECGEOM_DEVICE_DECLARE_CONV(struct, ZSegment);
0030 
0031 // Declare types shared by cxx and cuda.
0032 namespace Polyhedron {
0033 using ::EInnerRadii;
0034 using ::EPhiCutout;
0035 } // namespace Polyhedron
0036 
0037 inline namespace VECGEOM_IMPL_NAMESPACE {
0038 
0039 /// Represents one segment along the Z-axis, containing one or more sets of
0040 /// quadrilaterals that represent the outer, inner and phi shells.
0041 struct ZSegment {
0042   Quadrilaterals outer; ///< Should always be non-empty.
0043   Quadrilaterals phi;   ///< Is empty if fHasPhiCutout is false.
0044   Quadrilaterals inner; ///< Is empty hasInnerRadius is false.
0045 
0046   VECCORE_ATT_HOST_DEVICE
0047   bool hasInnerRadius() const { return inner.size() > 0; }
0048 
0049   VECCORE_ATT_HOST_DEVICE
0050   size_t aligned_sizeof_data(size_t nOuter, size_t nInner, size_t nPhi)
0051   {
0052     return Quadrilaterals::aligned_sizeof_data(nOuter) + Quadrilaterals::aligned_sizeof_data(nInner) +
0053            Quadrilaterals::aligned_sizeof_data(nPhi);
0054   }
0055 
0056   ZSegment() = default;
0057 
0058   VECCORE_ATT_HOST_DEVICE
0059   ZSegment(size_t nOuter, size_t nInner, size_t nPhi, AlignedAllocator &a, bool convex = true)
0060       : outer(nOuter, a), phi(nPhi, a, convex), inner(nInner, a)
0061   {
0062   }
0063 };
0064 
0065 // a plain and lightweight struct to encapsulate data members of a polyhedron
0066 template <typename T = double>
0067 struct PolyhedronStruct {
0068   size_t fSize{0};                ///< Size of the buffer to hold the object, including alignment
0069   int fSideCount{0};              ///< Number of segments along phi.
0070   bool fHasInnerRadii{false};     ///< Has any Z-segments with an inner radius != 0.
0071   bool fHasPhiCutout{false};      ///< Has a cutout angle along phi.
0072   bool fHasLargePhiCutout{false}; ///< Phi cutout is larger than pi.
0073   T fPhiStart{0.};                ///< Phi start in radians (input to constructor)
0074   T fPhiDelta{0.};                ///< Phi delta in radians (input to constructor)
0075   evolution::Wedge fPhiWedge;     ///< Phi wedge
0076   Array<ZSegment> fZSegments;     ///< AOS'esque collections of quadrilaterals
0077   Array<T> fZPlanes;              ///< Z-coordinate of each plane separating segments
0078   Array<T> fRMin;                 ///< Inner radii as specified in constructor.
0079   Array<T> fRMax;                 ///< Outer radii as specified in constructor.
0080   Array<bool> fSameZ;             ///< Array of flags marking that the following plane is at same Z
0081   SOA3D<T> fPhiSections;          ///< Unit vectors marking the bounds between
0082                                   ///  phi segments, represented by planes
0083                                   ///  through the origin with the normal
0084                                   ///  point along the positive phi direction.
0085   TubeStruct<T> fBoundingTube;    ///< Tube enclosing the outer bounds of the
0086                                   ///  polyhedron. Used in Contains, Inside and
0087                                   ///  DistanceToIn.
0088   T fBoundingTubeOffset{0.};      ///< Offset in Z of the center of the bounding
0089                                   ///  tube. Used as a quick substitution for
0090                                   ///  running a full transformation.
0091 
0092   /// Internal structure to cache component surface areas per Z segment
0093   struct AreaStruct {
0094     Precision area        = 0.;      ///< Cached total surface area
0095     Precision top_area    = 0.;      ///< Area of top surface
0096     Precision bottom_area = 0.;      ///< Area of top surface
0097     Precision *outer      = nullptr; ///< Array of surface areas for the auter part
0098     Precision *inner      = nullptr; ///< Array of surface areas for the inner part
0099     Precision *phi        = nullptr; ///< Array of surface areas for the phi part
0100 
0101     AreaStruct(int nseg)
0102     {
0103       inner = new Precision[nseg];
0104       outer = new Precision[nseg];
0105       phi   = new Precision[nseg];
0106     }
0107 
0108     VECCORE_ATT_HOST_DEVICE
0109     ~AreaStruct()
0110     {
0111       delete[] inner;
0112       delete[] outer;
0113       delete[] phi;
0114     }
0115   };
0116 
0117   mutable AreaStruct *fAreaStruct = nullptr; ///< Cached surface area values
0118   mutable Precision fCapacity     = 0.;      ///< Stored Capacity
0119 
0120   // These data member and member functions are added for convexity detection
0121   bool fContinuousInSlope;
0122   bool fConvexityPossible;
0123   bool fEqualRmax;
0124 
0125   VECGEOM_FORCE_INLINE
0126   VECCORE_ATT_HOST_DEVICE
0127   static bool ApproxEqual(const Precision &x, const Precision &y) { return vecCore::math::Abs(x - y) < kTolerance; }
0128 
0129   PolyhedronStruct() = default;
0130 
0131   VECCORE_ATT_HOST_DEVICE
0132   PolyhedronStruct(Precision phiStart, Precision phiDelta, const int sideCount, const int zPlaneCount,
0133                    Precision const zPlanes[], Precision const rMin[], Precision const rMax[])
0134       : fSideCount(sideCount), fHasInnerRadii(false), fHasPhiCutout(phiDelta < kTwoPi),
0135         fHasLargePhiCutout(phiDelta < kPi), fPhiStart(NormalizeAngle<kScalar>(phiStart)),
0136         fPhiDelta((phiDelta > kTwoPi) ? kTwoPi : phiDelta), fPhiWedge(fPhiDelta, fPhiStart),
0137         fZSegments(zPlaneCount - 1), fZPlanes(zPlaneCount), fRMin(zPlaneCount), fRMax(zPlaneCount), fSameZ(zPlaneCount),
0138         fPhiSections(sideCount + 1), fBoundingTube(0, 1, 1, fPhiStart, fPhiDelta), fContinuousInSlope(true),
0139         fConvexityPossible(true), fEqualRmax(true)
0140   {
0141     // initialize polyhedron internals
0142     Initialize(phiStart, phiDelta, sideCount, zPlaneCount, zPlanes, rMin, rMax);
0143   }
0144 
0145   VECCORE_ATT_HOST_DEVICE
0146   PolyhedronStruct(Precision phiStart, Precision phiDelta, const int sideCount, const int zPlaneCount,
0147                    Precision const zPlanes[], Precision const rMin[], Precision const rMax[], AlignedAllocator &a)
0148       : fSideCount(sideCount), fHasInnerRadii(false), fHasPhiCutout(phiDelta < kTwoPi),
0149         fHasLargePhiCutout(phiDelta < kPi), fPhiStart(NormalizeAngle<kScalar>(phiStart)),
0150         fPhiDelta((phiDelta > kTwoPi) ? kTwoPi : phiDelta), fPhiWedge(fPhiDelta, fPhiStart),
0151         fZSegments(zPlaneCount - 1, a), fZPlanes(zPlaneCount, a), fRMin(zPlaneCount, a), fRMax(zPlaneCount, a),
0152         fSameZ(zPlaneCount, a), fPhiSections(sideCount + 1), fBoundingTube(0, 1, 1, fPhiStart, fPhiDelta),
0153         fContinuousInSlope(true), fConvexityPossible(true), fEqualRmax(true)
0154   {
0155     // initialize polyhedron internals
0156     Initialize(phiStart, phiDelta, sideCount, zPlaneCount, zPlanes, rMin, rMax, a);
0157   }
0158 
0159   PolyhedronStruct(Precision phiStart, Precision phiDelta, const int sideCount, const int verticesCount,
0160                    Precision const r[], Precision const z[])
0161       : fSideCount(sideCount), fHasInnerRadii(false), fHasPhiCutout(phiDelta < kTwoPi),
0162         fHasLargePhiCutout(phiDelta < kPi), fPhiStart(NormalizeAngle<kScalar>(phiStart)),
0163         fPhiDelta((phiDelta > kTwoPi) ? kTwoPi : phiDelta), fPhiWedge(fPhiDelta, fPhiStart), fZSegments(), fZPlanes(),
0164         fRMin(), fRMax(), fSameZ(), fPhiSections(sideCount + 1), fBoundingTube(0, 1, 1, fPhiStart, fPhiDelta),
0165         fContinuousInSlope(true), fConvexityPossible(true), fEqualRmax(true)
0166   {
0167     if (verticesCount < 3) throw std::runtime_error("A Polyhedron needs at least 3 (rz) vertices");
0168 
0169     // Geant4-like construction (n = verticesCount). The rz section is described
0170     // as a sequence of connected vertices (r[i], z[i]). We have to associate
0171     // the vertices with (rmin, rmax, z) plane representation.
0172 
0173     // detect if vertices are defined clockwise
0174     Precision area = 0;
0175     for (int i = 0; i < verticesCount; ++i) {
0176       int j = (i + 1) % verticesCount;
0177       area += r[i] * z[j] - r[j] * z[i];
0178     }
0179 
0180     bool cw      = (area < 0);
0181     int inc      = cw ? -1 : 1;
0182     Precision zt = z[0];
0183     Precision zb = z[0];
0184     // Find min/max on Z
0185     for (int i = 0; i < verticesCount; ++i) {
0186       if (z[i] > zt) zt = z[i];
0187       if (z[i] < zb) zb = z[i];
0188     }
0189 
0190     // Add implicit vertices
0191     Precision *rnew    = new Precision[2 * verticesCount];
0192     Precision *znew    = new Precision[2 * verticesCount];
0193     int verticesCount1 = 0;
0194     for (int i0 = 0; i0 < verticesCount; ++i0) {
0195       rnew[verticesCount1]   = r[i0];
0196       znew[verticesCount1++] = z[i0];
0197       // Check if top/bottom vertex is singular
0198       if (vecCore::math::Abs(z[i0] - zt) < kTolerance || vecCore::math::Abs(z[i0] - zb) < kTolerance) {
0199         if (vecCore::math::Abs(z[i0] - z[(i0 + verticesCount - 1) % verticesCount]) > kTolerance &&
0200             vecCore::math::Abs(z[i0] - z[(i0 + 1) % verticesCount]) > kTolerance) {
0201           rnew[verticesCount1]   = r[i0];
0202           znew[verticesCount1++] = z[i0];
0203         }
0204       }
0205       int i1       = (i0 + 1) % verticesCount;
0206       Precision dz = z[i1] - z[i0];
0207       if (vecCore::math::Abs(dz) < kTolerance) continue;
0208       Precision zmin = vecCore::math::Min(z[i0], z[i1]);
0209       Precision zmax = vecCore::math::Max(z[i0], z[i1]);
0210       for (int j = 0; j < verticesCount - 2; ++j) {
0211         // go backward
0212         int k = (i0 - 1 - j + verticesCount) % verticesCount;
0213         if (z[k] > zmin + kTolerance && z[k] < zmax - kTolerance) {
0214           // Project the vertex on current segment to get a new vertex
0215           Precision rp = r[i0] + (r[i1] - r[i0]) * (z[k] - z[i0]) / dz;
0216           VECGEOM_ASSERT(rp >= 0);
0217           // We need to insert point (rp, z[k]) after i1
0218           rnew[verticesCount1]   = rp;
0219           znew[verticesCount1++] = z[k];
0220         }
0221       }
0222     }
0223 
0224     // detect index of outer vertex with minimum Z
0225     int i0 = -1;
0226     for (int i = 0; i < verticesCount1; ++i) {
0227       if (znew[i] == zb) {
0228         i0 = i;
0229         break;
0230       }
0231     }
0232     if (vecCore::math::Abs(zb - znew[(i0 + inc) % verticesCount1]) < kTolerance) i0 = (i0 + inc) % verticesCount1;
0233 
0234     if (phiDelta <= 0 || phiDelta > kTwoPi - kAngTolerance) phiDelta = kTwoPi;
0235     Precision sidePhi         = phiDelta / sideCount;
0236     Precision cosHalfDeltaPhi = cos(0.5 * sidePhi);
0237 
0238     // We count vertices starting from imin, making sure we move counter-clockwise
0239 
0240     int Nz          = verticesCount1 / 2;
0241     Precision *rMin = new Precision[Nz];
0242     Precision *rMax = new Precision[Nz];
0243     Precision *zArg = new Precision[Nz];
0244 
0245     for (int i = 0; i < Nz; ++i) {
0246       // Current vertex index going always ccw from (rmin,zmin)
0247       int j    = (i0 + verticesCount1 + inc * i) % verticesCount1;
0248       int jsim = (i0 + verticesCount1 + inc * (verticesCount1 - 1 - i)) % verticesCount1;
0249       VECGEOM_ASSERT(znew[j] == znew[jsim]);
0250       zArg[i] = znew[j];
0251       rMax[i] = rnew[j] * cosHalfDeltaPhi;
0252       rMin[i] = rnew[jsim] * cosHalfDeltaPhi;
0253       VECGEOM_ASSERT(rMax[i] >= rMin[i] &&
0254                      "UnplPolycone ERROR: r[] provided has problems of the Rmax < Rmin type, please check!\n");
0255     }
0256 
0257     // Allocate arrays
0258     fZSegments.Allocate(Nz - 1);
0259     fZPlanes.Allocate(Nz);
0260     fRMin.Allocate(Nz);
0261     fRMax.Allocate(Nz);
0262     fSameZ.Allocate(Nz);
0263 
0264     // Delegate to full constructor
0265     Initialize(phiStart, phiDelta, sideCount, Nz, zArg, rMin, rMax);
0266     delete[] rnew;
0267     delete[] znew;
0268     delete[] rMin;
0269     delete[] rMax;
0270     delete[] zArg;
0271   }
0272 
0273   VECCORE_ATT_HOST_DEVICE
0274   ~PolyhedronStruct() { delete fAreaStruct; }
0275 
0276   VECCORE_ATT_HOST_DEVICE
0277   VECGEOM_FORCE_INLINE
0278   static size_t aligned_sizeof_data(Precision /*phiStart*/, Precision phiDelta, const int sideCount,
0279                                     const int zPlaneCount, Precision const zPlanes[], Precision const rMin[],
0280                                     Precision const rMax[])
0281   {
0282     const bool hasPhiCutout = phiDelta < 2 * kPi;
0283     size_t aligned_size     = (zPlaneCount - 1) * sizeof(ZSegment);
0284     // Alignment of all Array data members, which are AlignedBase types
0285     aligned_size += 5 * kAlignmentBoundary;
0286     // fZsegments content
0287     for (int i = 0; i < zPlaneCount - 1; ++i) {
0288       // Z-planes must be monotonically increasing
0289       VECGEOM_ASSERT(zPlanes[i] <= zPlanes[i + 1]);
0290       bool hasInnerRadius = rMin[i] > kTolerance || rMin[i + 1] > kTolerance;
0291       int multiplier      = (ApproxEqual(zPlanes[i], zPlanes[i + 1]) && ApproxEqual(rMax[i], rMax[i + 1])) ? 0 : 1;
0292       aligned_size += Quadrilaterals::aligned_sizeof_data(sideCount * multiplier);
0293       // no phi segment here if degenerate z;
0294       if (hasPhiCutout) {
0295         multiplier = (zPlanes[i] == zPlanes[i + 1]) ? 0 : 1;
0296         aligned_size += Quadrilaterals::aligned_sizeof_data(2 * multiplier);
0297       }
0298       multiplier = (zPlanes[i] == zPlanes[i + 1] && rMin[i] == rMin[i + 1]) ? 0 : 1;
0299       if (hasInnerRadius && multiplier > 0) {
0300         aligned_size += Quadrilaterals::aligned_sizeof_data(sideCount * multiplier);
0301       }
0302     }
0303     // fZplanes, fRmin, fRmax
0304     aligned_size += 3 * Array<T>::aligned_sizeof_data(zPlaneCount);
0305     // fSameZ
0306     aligned_size += Array<bool>::aligned_sizeof_data(zPlaneCount);
0307     // fPhiSections
0308     aligned_size += SOA3D<T>::aligned_sizeof_data(sideCount + 1);
0309     return aligned_size;
0310   }
0311 
0312   VECCORE_ATT_HOST_DEVICE
0313   bool CheckContinuityInSlope(const Precision rOuter[], const Precision zPlane[], const unsigned int nz)
0314   {
0315     Precision prevSlope = kInfLength;
0316     for (unsigned int j = 0; j < nz - 1; ++j) {
0317       if (ApproxEqual(zPlane[j + 1], zPlane[j])) {
0318         if (!ApproxEqual(rOuter[j + 1], rOuter[j])) return false;
0319       } else {
0320         Precision currentSlope = (rOuter[j + 1] - rOuter[j]) / (zPlane[j + 1] - zPlane[j]);
0321         if (currentSlope > prevSlope) return false;
0322         prevSlope = currentSlope;
0323       }
0324     }
0325     return true;
0326   }
0327 
0328   VECCORE_ATT_HOST_DEVICE
0329   void Dump()
0330   {
0331     auto dump_planes = [](Planes const &planes) {
0332       auto const &normals   = planes.GetNormals();
0333       auto const &distances = planes.GetDistances();
0334       printf("[%d]: convex=%d normals[%p] distances[%p]", planes.size(), planes.IsConvex(), (void *)&normals,
0335              (void *)&distances);
0336       if (planes.size() == 0) printf(" : empty");
0337       printf("\n");
0338       for (size_t i = 0; i < planes.size(); ++i) {
0339         auto const &normal = normals[i];
0340         auto distance      = distances[i];
0341         printf("         [%lu]: fNormal { %g, %g, %g } fDistance %g\n", i, normal[0], normal[1], normal[2], distance);
0342       }
0343     };
0344     printf("== PolyhedronStruct at: %p", (void *)this);
0345     printf("   side count: %d  z_plane_count: %u phiStart: %g phiDelta: %g\n", fSideCount, fZPlanes.size(), fPhiStart,
0346            fPhiDelta);
0347     printf("   hasInnerRadii: %d hasPhiCutout: %d fHasLargePhiCutout: %d\n", fHasInnerRadii, fHasPhiCutout,
0348            fHasLargePhiCutout);
0349     for (size_t i = 0; i < fZSegments.size(); ++i) {
0350       printf("   ZSegments[%lu]:\n", i);
0351       auto const &zseg = fZSegments[i];
0352       printf("     outer\n");
0353       printf("       planes");
0354       dump_planes(zseg.outer.GetPlanes());
0355       for (size_t j = 0; j < 4; ++j) {
0356         printf("       side vectors[%lu]", j);
0357         dump_planes(zseg.outer.GetSideVectors()[j]);
0358         auto const &corners = zseg.outer.GetCorners()[j];
0359         if (corners.size()) printf("       corners[%lu]", j);
0360         for (size_t k = 0; k < corners.size(); ++k)
0361           printf(" %lu:{%g, %g, %g}", k, corners[k].x(), corners[k].y(), corners[k].z());
0362         if (corners.size()) printf("\n");
0363       }
0364       printf("     inner\n");
0365       printf("       planes");
0366       dump_planes(zseg.inner.GetPlanes());
0367       for (size_t j = 0; j < 4; ++j) {
0368         printf("       side vectors[%lu]", j);
0369         dump_planes(zseg.inner.GetSideVectors()[j]);
0370         auto const &corners = zseg.inner.GetCorners()[j];
0371         if (corners.size()) printf("       corners[%lu]", j);
0372         for (size_t k = 0; k < corners.size(); ++k)
0373           printf(" %lu:{%g, %g, %g}", k, corners[k].x(), corners[k].y(), corners[k].z());
0374         if (corners.size()) printf("\n");
0375       }
0376       printf("     phi\n");
0377       printf("       planes");
0378       dump_planes(zseg.phi.GetPlanes());
0379       for (size_t j = 0; j < 4; ++j) {
0380         printf("       side vectors[%lu]", j);
0381         dump_planes(zseg.phi.GetSideVectors()[j]);
0382         auto const &corners = zseg.phi.GetCorners()[j];
0383         if (corners.size()) printf("       corners[%lu]", j);
0384         for (size_t k = 0; k < corners.size(); ++k)
0385           printf(" %lu:{%g, %g, %g}", k, corners[k].x(), corners[k].y(), corners[k].z());
0386         if (corners.size()) printf("\n");
0387       }
0388     }
0389     printf("\n   fZPlanes: ");
0390     for (size_t i = 0; i < fZPlanes.size(); ++i)
0391       printf(" %lu: %g", i, fZPlanes[i]);
0392     printf("\n   fRMin: ");
0393     for (size_t i = 0; i < fRMin.size(); ++i)
0394       printf(" %lu: %g", i, fRMin[i]);
0395     printf("\n   fRMax: ");
0396     for (size_t i = 0; i < fRMax.size(); ++i)
0397       printf(" %lu: %g", i, fRMax[i]);
0398     printf("\n   fSameZ: ");
0399     for (size_t i = 0; i < fSameZ.size(); ++i)
0400       printf(" %lu: %d", i, fSameZ[i]);
0401     printf("\n   fPhiSections: ");
0402     for (size_t i = 0; i < fPhiSections.size(); ++i)
0403       printf(" %lu: {%g, %g, %g}", i, fPhiSections[i].x(), fPhiSections[i].y(), fPhiSections[i].z());
0404     printf("\n");
0405   }
0406 
0407   // This method does the proper construction of planes and segments.
0408   // Used by multiple constructors.
0409   VECCORE_ATT_HOST_DEVICE
0410   void Initialize(Precision phiStart, Precision phiDelta, const int sideCount, const int zPlaneCount,
0411                   Precision const zPlanes[], Precision const rMin[], Precision const rMax[])
0412   {
0413     typedef Vector3D<Precision> Vec_t;
0414 
0415     // Sanity check of input parameters
0416     VECGEOM_ASSERT(zPlaneCount > 1);
0417     VECGEOM_ASSERT(fSideCount > 0);
0418     fSize = PolyhedronStruct<T>::aligned_sizeof_data(phiStart, phiDelta, sideCount, zPlaneCount, zPlanes, rMin, rMax);
0419 
0420     for (auto i = 0; i < zPlaneCount; ++i) {
0421       fZPlanes[i] = 0.;
0422       fRMin[i]    = 0.;
0423       fRMax[i]    = 0.;
0424       fSameZ[i]   = false;
0425     }
0426     copy(zPlanes, zPlanes + zPlaneCount, &fZPlanes[0]);
0427     copy(rMin, rMin + zPlaneCount, &fRMin[0]);
0428     copy(rMax, rMax + zPlaneCount, &fRMax[0]);
0429 
0430     Precision startRmax = rMax[0];
0431     for (int i = 0; i < zPlaneCount; i++) {
0432       fConvexityPossible &= (rMin[i] < kTolerance);
0433       fEqualRmax &= (ApproxEqual(startRmax, rMax[i]));
0434       if (i > 0 && i < zPlaneCount - 2) {
0435         if (ApproxEqual(fZPlanes[i], fZPlanes[i + 1])) fSameZ[i] = true;
0436       }
0437     }
0438     fContinuousInSlope = CheckContinuityInSlope(rMax, zPlanes, zPlaneCount);
0439 
0440     // Initialize segments
0441     // sometimes there will be no quadrilaterals: for instance when
0442     // rmin jumps at some z and rmax remains continouus
0443     for (int i = 0; i < zPlaneCount - 1; ++i) {
0444       // Z-planes must be monotonically increasing
0445       VECGEOM_ASSERT(zPlanes[i] <= zPlanes[i + 1]);
0446 
0447       bool hasInnerRadius = rMin[i] > kTolerance || rMin[i + 1] > kTolerance;
0448 
0449       int multiplier = (ApproxEqual(zPlanes[i], zPlanes[i + 1]) && ApproxEqual(rMax[i], rMax[i + 1])) ? 0 : 1;
0450 
0451       // create quadrilaterals in a predefined place with placement new
0452       new (&fZSegments[i].outer) Quadrilaterals(sideCount * multiplier);
0453 
0454       // no phi segment here if degenerate z;
0455       if (fHasPhiCutout) {
0456         multiplier = (zPlanes[i] == zPlanes[i + 1]) ? 0 : 1;
0457         new (&fZSegments[i].phi) Quadrilaterals(2 * multiplier, phiDelta <= kPi);
0458       } else {
0459         new (&fZSegments[i].phi) Quadrilaterals(0);
0460       }
0461 
0462       multiplier = (zPlanes[i] == zPlanes[i + 1] && rMin[i] == rMin[i + 1]) ? 0 : 1;
0463 
0464       if (hasInnerRadius && multiplier > 0) {
0465         new (&fZSegments[i].inner) Quadrilaterals(sideCount * multiplier);
0466         fHasInnerRadii = true;
0467       } else {
0468         new (&fZSegments[i].inner) Quadrilaterals(0);
0469       }
0470     }
0471 
0472     // Compute the cylindrical coordinate phi along which the corners are placed
0473     if (phiDelta <= 0 || phiDelta > kTwoPi - kAngTolerance) phiDelta = kTwoPi;
0474     phiStart = NormalizeAngle<kScalar>(phiStart);
0475     if (phiDelta > kTwoPi) phiDelta = kTwoPi;
0476     Precision sidePhi = phiDelta / sideCount;
0477 
0478     auto getPhi = [&](int side) {
0479       if (!fHasPhiCutout && side == sideCount) {
0480         side = 0;
0481       }
0482       return NormalizeAngle<kScalar>(phiStart + side * sidePhi);
0483     };
0484 
0485     for (int i = 0, iMax = sideCount + 1; i < iMax; ++i) {
0486       Vector3D<Precision> cornerVector = Vec_t::FromCylindrical(1., getPhi(i), 0).Normalized().FixZeroes();
0487       fPhiSections.set(i, cornerVector.Normalized().Cross(Vector3D<Precision>(0, 0, -1)));
0488     }
0489 
0490     // Specified radii are to the sides, not to the corners. Change these values,
0491     // as corners and not sides are used to build the structure
0492     Precision cosHalfDeltaPhi = cos(0.5 * sidePhi);
0493     Precision innerRadius = kInfLength, outerRadius = -kInfLength;
0494     for (int i = 0; i < zPlaneCount; ++i) {
0495       // Use distance to side for minimizing inner radius of bounding tube
0496       if (rMin[i] < innerRadius) innerRadius = rMin[i];
0497       VECGEOM_ASSERT(rMin[i] >= 0 && rMax[i] >= 0);
0498       // Use distance to corner for minimizing outer radius of bounding tube
0499       if (rMax[i] > outerRadius) outerRadius = rMax[i];
0500     }
0501     // need to convert from distance to planes to real radius in case of outerradius
0502     // the inner radius of the bounding tube is given by min(rMin[])
0503     outerRadius /= cosHalfDeltaPhi;
0504 
0505     // Create bounding tube with biggest outer radius and smallest inner radius
0506     Precision boundingTubeZ = 0.5 * (zPlanes[zPlaneCount - 1] - zPlanes[0]) + kTolerance;
0507     // Make bounding tube phi range a bit larger to contain all points on phi boundaries
0508     const Precision kPhiTolerance = 100 * kTolerance;
0509     // The increase in the angle has to be large enough to contain most of
0510     // kSurface points. There will be some points close to the Z axis which will
0511     // not be contained. The value is empirical to satisfy ShapeTester
0512     Precision boundsPhiStart = !fHasPhiCutout ? 0 : phiStart - kPhiTolerance;
0513     Precision boundsPhiDelta = !fHasPhiCutout ? kTwoPi : phiDelta + 2 * kPhiTolerance;
0514 
0515     fBoundingTube = TubeStruct<Precision>(innerRadius - kHalfTolerance, outerRadius + kHalfTolerance, boundingTubeZ,
0516                                           boundsPhiStart, boundsPhiDelta);
0517 
0518     // The offset has to match the middle of the polyhedron
0519     fBoundingTubeOffset = 0.5 * (zPlanes[0] + zPlanes[zPlaneCount - 1]);
0520 
0521     auto getVertexImpl = [&](Precision const r[], int i, int j) {
0522       if (!fHasPhiCutout && j == sideCount) {
0523         j = 0;
0524       }
0525       return Vec_t::FromCylindrical(r[i] / cosHalfDeltaPhi, getPhi(j), zPlanes[i]).FixZeroes();
0526     };
0527 
0528     auto getInnerVertex = [&](int i, int j) { return getVertexImpl(rMin, i, j); };
0529     auto getOuterVertex = [&](int i, int j) { return getVertexImpl(rMax, i, j); };
0530 
0531     // Build segments by drawing quadrilaterals between vertices
0532     for (int iPlane = 0; iPlane < zPlaneCount - 1; ++iPlane) {
0533 
0534       auto WrongNormal = [](Vector3D<Precision> const &normal, Vector3D<Precision> const &corner) {
0535         return normal[0] * corner[0] + normal[1] * corner[1] < 0;
0536       };
0537 
0538       // Draw the regular quadrilaterals along phi
0539       for (int iSide = 0; iSide < fZSegments[iPlane].outer.size(); ++iSide) {
0540         fZSegments[iPlane].outer.Set(iSide, getOuterVertex(iPlane, iSide), getOuterVertex(iPlane, iSide + 1),
0541                                      getOuterVertex(iPlane + 1, iSide + 1), getOuterVertex(iPlane + 1, iSide));
0542         // Normal has to point away from Z-axis
0543         if (WrongNormal(fZSegments[iPlane].outer.GetNormal(iSide), getOuterVertex(iPlane, iSide))) {
0544           fZSegments[iPlane].outer.FlipSign(iSide);
0545         }
0546       }
0547       for (int iSide = 0; iSide < fZSegments[iPlane].inner.size(); ++iSide) {
0548         fZSegments[iPlane].inner.Set(iSide, getInnerVertex(iPlane, iSide), getInnerVertex(iPlane, iSide + 1),
0549                                      getInnerVertex(iPlane + 1, iSide + 1), getInnerVertex(iPlane + 1, iSide));
0550         // Normal has to point away from Z-axis
0551         if (WrongNormal(fZSegments[iPlane].inner.GetNormal(iSide), getInnerVertex(iPlane, iSide))) {
0552           fZSegments[iPlane].inner.FlipSign(iSide);
0553         }
0554       }
0555 
0556       if (fHasPhiCutout && fZSegments[iPlane].phi.size() == 2) {
0557         // If there's a phi cutout, draw two quadrilaterals connecting the four
0558         // corners (two inner, two outer) of the first and last phi coordinate,
0559         // respectively
0560         fZSegments[iPlane].phi.Set(0, getInnerVertex(iPlane, 0), getInnerVertex(iPlane + 1, 0),
0561                                    getOuterVertex(iPlane + 1, 0), getOuterVertex(iPlane, 0));
0562         // Make sure normal points backwards along phi
0563         if (fZSegments[iPlane].phi.GetNormal(0).Dot(fPhiSections[0]) > 0) {
0564           fZSegments[iPlane].phi.FlipSign(0);
0565         }
0566         fZSegments[iPlane].phi.Set(1, getOuterVertex(iPlane, sideCount), getOuterVertex(iPlane + 1, sideCount),
0567                                    getInnerVertex(iPlane + 1, sideCount), getInnerVertex(iPlane, sideCount));
0568         // Make sure normal points forwards along phi
0569         if (fZSegments[iPlane].phi.GetNormal(1).Dot(fPhiSections[fSideCount]) < 0) {
0570           fZSegments[iPlane].phi.FlipSign(1);
0571         }
0572       }
0573 
0574     } // End loop over segments
0575   }
0576 
0577   // This method does the proper construction of planes and segments.
0578   // Used by multiple constructors.
0579   VECCORE_ATT_HOST_DEVICE
0580   void Initialize(Precision phiStart, Precision phiDelta, const int sideCount, const int zPlaneCount,
0581                   Precision const zPlanes[], Precision const rMin[], Precision const rMax[], AlignedAllocator &a)
0582   {
0583     typedef Vector3D<Precision> Vec_t;
0584 
0585     // Sanity check of input parameters
0586     VECGEOM_ASSERT(zPlaneCount > 1);
0587     VECGEOM_ASSERT(fSideCount > 0);
0588 
0589     for (auto i = 0; i < zPlaneCount; ++i) {
0590       fZPlanes[i] = 0.;
0591       fRMin[i]    = 0.;
0592       fRMax[i]    = 0.;
0593       fSameZ[i]   = false;
0594     }
0595     copy(zPlanes, zPlanes + zPlaneCount, &fZPlanes[0]);
0596     copy(rMin, rMin + zPlaneCount, &fRMin[0]);
0597     copy(rMax, rMax + zPlaneCount, &fRMax[0]);
0598 
0599     Precision startRmax = rMax[0];
0600     for (int i = 0; i < zPlaneCount; i++) {
0601       fConvexityPossible &= (rMin[i] < kTolerance);
0602       fEqualRmax &= (ApproxEqual(startRmax, rMax[i]));
0603       if (i > 0 && i < zPlaneCount - 2) {
0604         if (ApproxEqual(fZPlanes[i], fZPlanes[i + 1])) fSameZ[i] = true;
0605       }
0606     }
0607     fContinuousInSlope = CheckContinuityInSlope(rMax, zPlanes, zPlaneCount);
0608 
0609     // Initialize segments
0610     // sometimes there will be no quadrilaterals: for instance when
0611     // rmin jumps at some z and rmax remains continouus
0612     for (int i = 0; i < zPlaneCount - 1; ++i) {
0613       // Z-planes must be monotonically increasing
0614       VECGEOM_ASSERT(zPlanes[i] <= zPlanes[i + 1]);
0615 
0616       bool hasInnerRadius = rMin[i] > kTolerance || rMin[i + 1] > kTolerance;
0617       bool convex         = phiDelta <= kPi;
0618 
0619       int multiplier = (ApproxEqual(zPlanes[i], zPlanes[i + 1]) && ApproxEqual(rMax[i], rMax[i + 1])) ? 0 : 1;
0620       size_t nouter  = sideCount * multiplier;
0621 
0622       // no phi segment here if degenerate z;
0623       size_t nphi = 0;
0624       if (fHasPhiCutout) {
0625         multiplier = (zPlanes[i] == zPlanes[i + 1]) ? 0 : 1;
0626         nphi       = 2 * multiplier;
0627       }
0628 
0629       multiplier    = (zPlanes[i] == zPlanes[i + 1] && rMin[i] == rMin[i + 1]) ? 0 : 1;
0630       size_t ninner = 0;
0631 
0632       if (hasInnerRadius && multiplier > 0) {
0633         ninner         = sideCount * multiplier;
0634         fHasInnerRadii = true;
0635       }
0636 
0637       // Create section
0638       new (&fZSegments[i]) ZSegment(nouter, ninner, nphi, a, convex);
0639     }
0640 
0641     // Compute the cylindrical coordinate phi along which the corners are placed
0642     if (phiDelta <= 0 || phiDelta > kTwoPi - kAngTolerance) phiDelta = kTwoPi;
0643     phiStart = NormalizeAngle<kScalar>(phiStart);
0644     if (phiDelta > kTwoPi) phiDelta = kTwoPi;
0645     Precision sidePhi = phiDelta / sideCount;
0646 
0647     auto getPhi = [&](int side) {
0648       if (!fHasPhiCutout && side == sideCount) {
0649         side = 0;
0650       }
0651       return NormalizeAngle<kScalar>(phiStart + side * sidePhi);
0652     };
0653 
0654     for (int i = 0, iMax = sideCount + 1; i < iMax; ++i) {
0655       Vector3D<Precision> cornerVector = Vec_t::FromCylindrical(1., getPhi(i), 0).Normalized().FixZeroes();
0656       fPhiSections.set(i, cornerVector.Normalized().Cross(Vector3D<Precision>(0, 0, -1)));
0657     }
0658 
0659     // Specified radii are to the sides, not to the corners. Change these values,
0660     // as corners and not sides are used to build the structure
0661     Precision cosHalfDeltaPhi = cos(0.5 * sidePhi);
0662     Precision innerRadius = kInfLength, outerRadius = -kInfLength;
0663     for (int i = 0; i < zPlaneCount; ++i) {
0664       // Use distance to side for minimizing inner radius of bounding tube
0665       if (rMin[i] < innerRadius) innerRadius = rMin[i];
0666       VECGEOM_ASSERT(rMin[i] >= 0 && rMax[i] >= 0);
0667       // Use distance to corner for minimizing outer radius of bounding tube
0668       if (rMax[i] > outerRadius) outerRadius = rMax[i];
0669     }
0670     // need to convert from distance to planes to real radius in case of outerradius
0671     // the inner radius of the bounding tube is given by min(rMin[])
0672     outerRadius /= cosHalfDeltaPhi;
0673 
0674     // Create bounding tube with biggest outer radius and smallest inner radius
0675     Precision boundingTubeZ = 0.5 * (zPlanes[zPlaneCount - 1] - zPlanes[0]) + kTolerance;
0676     // Make bounding tube phi range a bit larger to contain all points on phi boundaries
0677     const Precision kPhiTolerance = 100 * kTolerance;
0678     // The increase in the angle has to be large enough to contain most of
0679     // kSurface points. There will be some points close to the Z axis which will
0680     // not be contained. The value is empirical to satisfy ShapeTester
0681     Precision boundsPhiStart = !fHasPhiCutout ? 0 : phiStart - kPhiTolerance;
0682     Precision boundsPhiDelta = !fHasPhiCutout ? kTwoPi : phiDelta + 2 * kPhiTolerance;
0683 
0684     fBoundingTube = TubeStruct<Precision>(innerRadius - kHalfTolerance, outerRadius + kHalfTolerance, boundingTubeZ,
0685                                           boundsPhiStart, boundsPhiDelta);
0686 
0687     // The offset has to match the middle of the polyhedron
0688     fBoundingTubeOffset = 0.5 * (zPlanes[0] + zPlanes[zPlaneCount - 1]);
0689 
0690     auto getVertexImpl = [&](Precision const r[], int i, int j) {
0691       if (!fHasPhiCutout && j == sideCount) {
0692         j = 0;
0693       }
0694       return Vec_t::FromCylindrical(r[i] / cosHalfDeltaPhi, getPhi(j), zPlanes[i]).FixZeroes();
0695     };
0696 
0697     auto getInnerVertex = [&](int i, int j) { return getVertexImpl(rMin, i, j); };
0698     auto getOuterVertex = [&](int i, int j) { return getVertexImpl(rMax, i, j); };
0699 
0700     // Build segments by drawing quadrilaterals between vertices
0701     for (int iPlane = 0; iPlane < zPlaneCount - 1; ++iPlane) {
0702 
0703       auto WrongNormal = [](Vector3D<Precision> const &normal, Vector3D<Precision> const &corner) {
0704         return normal[0] * corner[0] + normal[1] * corner[1] < 0;
0705       };
0706 
0707       // Draw the regular quadrilaterals along phi
0708       for (int iSide = 0; iSide < fZSegments[iPlane].outer.size(); ++iSide) {
0709         fZSegments[iPlane].outer.Set(iSide, getOuterVertex(iPlane, iSide), getOuterVertex(iPlane, iSide + 1),
0710                                      getOuterVertex(iPlane + 1, iSide + 1), getOuterVertex(iPlane + 1, iSide));
0711         // Normal has to point away from Z-axis
0712         if (WrongNormal(fZSegments[iPlane].outer.GetNormal(iSide), getOuterVertex(iPlane, iSide))) {
0713           fZSegments[iPlane].outer.FlipSign(iSide);
0714         }
0715       }
0716       for (int iSide = 0; iSide < fZSegments[iPlane].inner.size(); ++iSide) {
0717         fZSegments[iPlane].inner.Set(iSide, getInnerVertex(iPlane, iSide), getInnerVertex(iPlane, iSide + 1),
0718                                      getInnerVertex(iPlane + 1, iSide + 1), getInnerVertex(iPlane + 1, iSide));
0719         // Normal has to point away from Z-axis
0720         if (WrongNormal(fZSegments[iPlane].inner.GetNormal(iSide), getInnerVertex(iPlane, iSide))) {
0721           fZSegments[iPlane].inner.FlipSign(iSide);
0722         }
0723       }
0724 
0725       if (fHasPhiCutout && fZSegments[iPlane].phi.size() == 2) {
0726         // If there's a phi cutout, draw two quadrilaterals connecting the four
0727         // corners (two inner, two outer) of the first and last phi coordinate,
0728         // respectively
0729         fZSegments[iPlane].phi.Set(0, getInnerVertex(iPlane, 0), getInnerVertex(iPlane + 1, 0),
0730                                    getOuterVertex(iPlane + 1, 0), getOuterVertex(iPlane, 0));
0731         // Make sure normal points backwards along phi
0732         if (fZSegments[iPlane].phi.GetNormal(0).Dot(fPhiSections[0]) > 0) {
0733           fZSegments[iPlane].phi.FlipSign(0);
0734         }
0735         fZSegments[iPlane].phi.Set(1, getOuterVertex(iPlane, sideCount), getOuterVertex(iPlane + 1, sideCount),
0736                                    getInnerVertex(iPlane + 1, sideCount), getInnerVertex(iPlane, sideCount));
0737         // Make sure normal points forwards along phi
0738         if (fZSegments[iPlane].phi.GetNormal(1).Dot(fPhiSections[fSideCount]) < 0) {
0739           fZSegments[iPlane].phi.FlipSign(1);
0740         }
0741       }
0742 
0743     } // End loop over segments
0744   }
0745 };
0746 } // namespace VECGEOM_IMPL_NAMESPACE
0747 } // namespace vecgeom
0748 
0749 #endif