Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-10-27 09:51:31

0001 /// \file PolyhedronImplementation.h
0002 /// \brief Polyhedron kernel helpers and public implementation entry points.
0003 /// \author Johannes de Fine Licht (johannes.definelicht@cern.ch)
0004 
0005 #ifndef VECGEOM_VOLUMES_KERNEL_POLYHEDRONIMPLEMENTATION_H_
0006 #define VECGEOM_VOLUMES_KERNEL_POLYHEDRONIMPLEMENTATION_H_
0007 
0008 #include <cstdio>
0009 
0010 #include "VecGeom/base/Vector3D.h"
0011 #include "VecGeom/volumes/kernel/GenericKernels.h"
0012 #include "VecGeom/volumes/kernel/TubeImplementation.h"
0013 #include "VecGeom/volumes/Quadrilaterals.h"
0014 #include "VecGeom/volumes/PolyhedronStruct.h"
0015 #include "VecGeom/volumes/SurfaceHitView.h"
0016 
0017 namespace vecgeom {
0018 
0019 // VECGEOM_DEVICE_FORWARD_DECLARE(struct PolyhedronImplementation;);
0020 
0021 VECGEOM_DEVICE_DECLARE_CONV_TEMPLATE_2v(struct, PolyhedronImplementation, Polyhedron::EInnerRadii,
0022                                         Polyhedron::EInnerRadii::kGeneric, Polyhedron::EPhiCutout,
0023                                         Polyhedron::EPhiCutout::kGeneric);
0024 
0025 inline namespace VECGEOM_IMPL_NAMESPACE {
0026 
0027 class PlacedPolyhedron;
0028 class UnplacedPolyhedron;
0029 
0030 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0031 struct PolyhedronImplementation {
0032 
0033   using PlacedShape_t    = PlacedPolyhedron;
0034   using UnplacedStruct_t = PolyhedronStruct<Precision>;
0035   using UnplacedVolume_t = UnplacedPolyhedron;
0036 
0037   /// @brief Boundary family selected by a `DistanceToOut` query.
0038   enum class ExitSurface { kNone, kInner, kPhi, kOuter, kZMin, kZMax };
0039 
0040   /// @brief Selected boundary family and quadrilateral index.
0041   struct ExitHit {
0042     ExitSurface surface{ExitSurface::kNone};
0043     int face{-1};
0044   };
0045 
0046   /// @brief Locate the z-segment owning a point.
0047   /// @param unplaced Polyhedron storage.
0048   /// @param pointZ Z-coordinate of the query point.
0049   /// @return Index of the owning z-segment. Returns `-1` below the first
0050   ///         z-plane and `N` above the last z-plane, where `N` is the segment
0051   ///         count.
0052   template <typename Real_v>
0053   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static int FindZSegment(UnplacedStruct_t const &unplaced,
0054                                                                        Real_v const &pointZ);
0055 
0056   /// @brief Locate the phi-segment owning a point.
0057   /// @param unplaced Polyhedron storage.
0058   /// @param point Query point.
0059   /// @return Index of the owning phi-segment, or `-1` if no segment matches.
0060   template <typename Real_v>
0061   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static int FindPhiSegment(UnplacedStruct_t const &unplaced,
0062                                                                          Vector3D<Real_v> const &point);
0063 
0064   /// @brief Compute the first valid entry distance against one z-segment shell.
0065   /// @param unplaced Polyhedron storage.
0066   /// @param segmentIndex Z-segment index to test.
0067   /// @param point Ray origin.
0068   /// @param direction Ray direction.
0069   /// @return Distance to the closest accepted quadrilateral hit.
0070   template <typename Real_v>
0071   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static Real_v DistanceToInZSegment(UnplacedStruct_t const &unplaced,
0072                                                                                   int segmentIndex,
0073                                                                                   Vector3D<Real_v> const &point,
0074                                                                                   Vector3D<Real_v> const &direction);
0075 
0076   /// @brief Compute the first valid exit distance against one z-segment shell.
0077   /// @param unplaced Polyhedron storage.
0078   /// @param segmentIndex Z-segment index to test.
0079   /// @param zMin Lower z bound of the owning segment.
0080   /// @param zMax Upper z bound of the owning segment.
0081   /// @param point Ray origin.
0082   /// @param direction Ray direction.
0083   /// @param hit Optional scalar output identifying the accepted face.
0084   /// @return Distance to the closest accepted quadrilateral hit.
0085   template <typename Real_v>
0086   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static Real_v DistanceToOutZSegment(
0087       UnplacedStruct_t const &unplaced, int segmentIndex, Precision zMin, Precision zMax, Vector3D<Real_v> const &point,
0088       Vector3D<Real_v> const &direction, ExitHit *hit = nullptr);
0089 
0090   /// @brief Compute the exact squared safety to one z-segment shell.
0091   /// @param unplaced Polyhedron storage.
0092   /// @param segmentIndex Z-segment index to test.
0093   /// @param phiIndex Input/output phi-segment index, updated for the winning
0094   ///        feature.
0095   /// @param point Query point.
0096   /// @param pt_inside Whether the public caller classified the point as inside.
0097   /// @param iSurf Output identifier of the closest feature family.
0098   /// @return Exact squared distance to the closest quadrilateral feature of the
0099   ///         selected z-segment.
0100   VECCORE_ATT_HOST_DEVICE
0101   VECGEOM_FORCE_INLINE
0102   static Precision SafetyToZSegmentSquared(UnplacedStruct_t const &unplaced, int segmentIndex, int &phiIndex,
0103                                            Vector3D<Precision> const &point, bool pt_inside, int &iSurf);
0104 
0105   /// @brief Minimize a distance-to-boundary candidate with the endcap distance.
0106   /// @param unplaced Polyhedron storage.
0107   /// @param goingRight Whether the ray advances towards increasing z.
0108   /// @param point Ray origin.
0109   /// @param direction Ray direction.
0110   /// @param distance Input/output minimum distance.
0111   /// @return `true` when the endcap replaced the input candidate.
0112   template <bool pointInsideT>
0113   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static bool DistanceToEndcaps(UnplacedStruct_t const &unplaced,
0114                                                                              bool goingRight,
0115                                                                              Vector3D<Precision> const &point,
0116                                                                              Vector3D<Precision> const &direction,
0117                                                                              Precision &distance);
0118 
0119   /// @brief Populate a supporting normal for a selected exit face.
0120   /// @param unplaced Polyhedron storage.
0121   /// @param segmentIndex Owning z-segment, or `-1` for a global endcap.
0122   /// @param hit Selected boundary family and quadrilateral index.
0123   /// @param hitInfo Exit-normal destination and `leftBehind` result.
0124   template <typename Real_v>
0125   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SetExitHit(UnplacedStruct_t const &unplaced,
0126                                                                       int segmentIndex, ExitHit const &hit,
0127                                                                       SurfaceHitView<Real_v> *hitInfo);
0128 
0129   /// @brief Minimize a squared safety candidate with the closest endcap.
0130   /// @param unplaced Polyhedron storage.
0131   /// @param point Query point.
0132   /// @param distance Input/output squared safety estimate.
0133   /// @param iz Output normal direction for the winning endcap.
0134   VECCORE_ATT_HOST_DEVICE
0135   VECGEOM_FORCE_INLINE
0136   static void SafetyToEndcapsSquared(UnplacedStruct_t const &unplaced, Vector3D<Precision> const &point,
0137                                      Precision &distance, int &iz);
0138 
0139   /// @brief Squared distance to finite faces for a known angular classification.
0140   /// @param unplaced Polyhedron storage.
0141   /// @param point Query point in local coordinates.
0142   /// @param phiIndex Bracketing facet, or -1 for a point in the cutout.
0143   /// @param pointInside Whether the query is inside; includes both phi faces.
0144   /// @return Squared boundary distance, including finite cap and same-z edges.
0145   /// @details Used when the projected outline cannot provide the required
0146   ///          safety quality. The caller handles wrong-side sentinels.
0147   VECCORE_ATT_HOST_DEVICE
0148   VECGEOM_FORCE_INLINE
0149   static Precision SafetyToFiniteFacesSquared(UnplacedStruct_t const &unplaced, Vector3D<Precision> const &point,
0150                                               int phiIndex, bool pointInside);
0151 
0152   /// @brief Conservative squared safety from the full-phi RZ outline.
0153   /// The maximum facet projection is a non-expansive map into (r,z), so
0154   /// distance to the mapped boundary cannot exceed the 3D safety. Axis edges
0155   /// close the outline for parity but are not physical boundary surfaces.
0156   /// At the query's phi, lifting an outline boundary point back to the solid
0157   /// stretches only r, by at most sec(halfSideAngle). The public safeties limit that
0158   /// factor to sqrt(2), using finite faces for larger facet spans.
0159   /// @param unplaced Polyhedron storage with cached outline geometry.
0160   /// @param point Query point in local coordinates.
0161   /// @param normSign +1 for SafetyToIn, -1 for SafetyToOut.
0162   /// @param wrongSide Output: parity places the point on the wrong side,
0163   ///        excluding points within the surface tolerance.
0164   /// @return Nonnegative squared safety; the caller applies the -1 sentinel.
0165   VECCORE_ATT_HOST_DEVICE
0166   VECGEOM_FORCE_INLINE
0167   static Precision SafetyToRZOutlineSquared(UnplacedStruct_t const &unplaced, Vector3D<Precision> const &point,
0168                                             Precision normSign, bool &wrongSide);
0169 
0170   /// @brief Check whether a point lies inside the infinite phi cutout wedge.
0171   /// @param segment Segment providing the phi planes.
0172   /// @param largePhiCutout Whether the excluded wedge is larger than pi.
0173   /// @param point Query point.
0174   /// @return `true` when the point is inside the infinite wedge.
0175   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static bool InPhiCutoutWedge(ZSegment const &segment,
0176                                                                             bool largePhiCutout,
0177                                                                             Vector3D<Precision> const &point);
0178 
0179   /// @brief Inside kernel for repeated-z border segments.
0180   VECCORE_ATT_HOST_DEVICE
0181   VECGEOM_FORCE_INLINE
0182   static Inside_t InsideSegBorder(UnplacedStruct_t const &unplaced, Vector3D<Precision> const &point, int zIndex);
0183 
0184   /// @brief Surface normal kernel.
0185   VECCORE_ATT_HOST_DEVICE
0186   VECGEOM_FORCE_INLINE
0187   static bool NormalKernel(UnplacedStruct_t const &unplaced, Vector3D<Precision> const &point,
0188                            Vector3D<Precision> &normal);
0189 
0190   /// @brief Public contains entry point for unplaced helpers.
0191   template <typename Real_v>
0192   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void UnplacedContains(UnplacedStruct_t const &unplaced,
0193                                                                             Vector3D<Real_v> const &point,
0194                                                                             bool &inside);
0195 
0196   /// @brief Public contains entry point.
0197   template <typename Real_v>
0198   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void Contains(UnplacedStruct_t const &unplaced,
0199                                                                     Vector3D<Real_v> const &point, bool &inside);
0200 
0201   /// @brief Public inside entry point.
0202   template <typename Real_v>
0203   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void Inside(UnplacedStruct_t const &unplaced,
0204                                                                   Vector3D<Real_v> const &point, Inside_t &inside);
0205 
0206   /// @brief Public `DistanceToIn` entry point.
0207   template <typename Real_v>
0208   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void DistanceToIn(UnplacedStruct_t const &unplaced,
0209                                                                         Vector3D<Real_v> const &point,
0210                                                                         Vector3D<Real_v> const &direction,
0211                                                                         Real_v const &stepMax, Real_v &distance);
0212 
0213   /// @brief Public `DistanceToOut` entry point.
0214   /// @details Always reports the exit-surface normal; `leftBehind` is set only
0215   /// for whole-solid supporting faces.
0216   /// @param unplaced Polyhedron storage and cached supporting-plane classification.
0217   /// @param point Polyhedron-local ray origin.
0218   /// @param direction Polyhedron-local ray direction.
0219   /// @param stepMax Distance limit, retaining the existing distance-query semantics.
0220   /// @param distance Output exit distance or the existing wrong-side result.
0221   /// @param hitInfo Optional exit-normal destination and `leftBehind` result.
0222   template <typename Real_v>
0223   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void DistanceToOut(UnplacedStruct_t const &unplaced,
0224                                                                          Vector3D<Real_v> const &point,
0225                                                                          Vector3D<Real_v> const &direction,
0226                                                                          Real_v const &stepMax, Real_v &distance,
0227                                                                          SurfaceHitView<Real_v> *hitInfo = nullptr);
0228 
0229   /// @brief Compile-time-selected `DistanceToOut` implementation.
0230   template <typename Real_v, bool RecordHitInfo>
0231   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void DistanceToOutImpl(UnplacedStruct_t const &unplaced,
0232                                                                              Vector3D<Real_v> const &point,
0233                                                                              Vector3D<Real_v> const &direction,
0234                                                                              Real_v const &stepMax, Real_v &distance,
0235                                                                              SurfaceHitView<Real_v> *hitInfo);
0236 
0237   /// @brief Public `SafetyToIn` entry point.
0238   /// @details Outside the tolerance band, entry safety is at least the true
0239   ///          boundary distance divided by sqrt(2). Cutout queries and facets
0240   ///          spanning more than pi/2 use finite-face distances.
0241   template <typename Real_v>
0242   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SafetyToIn(UnplacedStruct_t const &unplaced,
0243                                                                       Vector3D<Real_v> const &point, Real_v &safety);
0244 
0245   /// @brief Public `SafetyToOut` entry point.
0246   /// @details Uses the same sqrt(2) quality bound as SafetyToIn. Wider facets
0247   ///          use finite-face distances after wrong-side rejection.
0248   template <typename Real_v>
0249   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SafetyToOut(UnplacedStruct_t const &unplaced,
0250                                                                        Vector3D<Real_v> const &point, Real_v &safety);
0251 
0252 }; // End struct PolyhedronImplementation
0253 
0254 namespace {
0255 
0256 /// @brief Select the bounding-tube helper used by polyhedron bounds checks.
0257 /// @details The current implementation keeps `UniversalTube` for both cases so
0258 ///          phi-limited hollow polyhedra preserve the historical behavior.
0259 
0260 // SW (19.6.2015): switching to UniversalTube as Phi section was not
0261 // correctly treated with a hollow tube
0262 // TODO: this could be CORRECTLY put back for optimization
0263 template <Polyhedron::EInnerRadii innerRadiiT>
0264 struct HasInnerRadiiTraits {
0265   /// @brief Tube helper type used when inner radii are present.
0266   typedef TubeImplementation<TubeTypes::UniversalTube> TubeKernels;
0267 };
0268 
0269 template <>
0270 struct HasInnerRadiiTraits<Polyhedron::EInnerRadii::kFalse> {
0271   /// @brief Tube helper type used when inner radii are absent.
0272   typedef TubeImplementation<TubeTypes::UniversalTube> TubeKernels;
0273 };
0274 
0275 /// @brief Decide whether inner radii participate in the current query.
0276 /// @param hasInnerRadius Runtime segment flag.
0277 /// @return `true` when inner radii must be tested.
0278 template <Polyhedron::EInnerRadii innerRadiiT>
0279 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool TreatInner(bool hasInnerRadius)
0280 {
0281   return hasInnerRadius;
0282 }
0283 
0284 /// @brief Specialized `TreatInner` for solids without inner radii.
0285 template <>
0286 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool TreatInner<Polyhedron::EInnerRadii::kFalse>(bool /*hasInnerRadius*/)
0287 {
0288   return false;
0289 }
0290 
0291 /// @brief Decide whether phi-cutout handling participates in the current query.
0292 /// @param hasPhiCutout Runtime solid flag.
0293 /// @return `true` when phi-cutout checks must be evaluated.
0294 template <Polyhedron::EPhiCutout phiCutoutT>
0295 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool TreatPhi(bool /*hasPhiCutout*/)
0296 {
0297   return true;
0298 }
0299 
0300 /// @brief Specialized `TreatPhi` for full-phi polyhedra.
0301 template <>
0302 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool TreatPhi<Polyhedron::EPhiCutout::kFalse>(bool /*hasPhiCutout*/)
0303 {
0304   return false;
0305 }
0306 
0307 /// @brief Specialized `TreatPhi` for runtime-selected phi cutouts.
0308 template <>
0309 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool TreatPhi<Polyhedron::EPhiCutout::kGeneric>(bool hasPhiCutout)
0310 {
0311   return hasPhiCutout;
0312 }
0313 
0314 /// @brief Decide whether the excluded phi wedge is larger than pi.
0315 /// @param largePhiCutout Runtime solid flag.
0316 /// @return `true` when the cutout exceeds pi.
0317 template <Polyhedron::EPhiCutout phiCutoutT>
0318 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool LargePhiCutout(bool largePhiCutout)
0319 {
0320   return largePhiCutout;
0321 }
0322 
0323 /// @brief Specialized large-cutout selector for templates known to be small.
0324 template <>
0325 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool LargePhiCutout<Polyhedron::EPhiCutout::kTrue>(bool /*largePhiCutout*/)
0326 {
0327   return false;
0328 }
0329 
0330 /// @brief Specialized large-cutout selector for templates known to exceed pi.
0331 template <>
0332 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool LargePhiCutout<Polyhedron::EPhiCutout::kLarge>(
0333     bool /*largePhiCutout*/)
0334 {
0335   return true;
0336 }
0337 
0338 } // End anonymous namespace
0339 
0340 namespace {
0341 
0342 /// @brief Scalar helper locating a z-segment from the raw z-plane array.
0343 /// @param pointZ Query z coordinate.
0344 /// @param begin Pointer to the first z plane.
0345 /// @param size Number of z planes.
0346 /// @return Segment index convention used by `FindZSegment`.
0347 template <typename Real_v>
0348 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE int FindZSegmentKernel(Real_v const &pointZ, Precision const *begin,
0349                                                                     size_t size);
0350 template <>
0351 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE int FindZSegmentKernel<Precision>(Precision const &pointZ,
0352                                                                                Precision const *begin, size_t size)
0353 {
0354   int index            = size - 1;
0355   Precision const *end = begin + index;
0356   // Modified algorithm to select the first section the position is close to
0357   // within boundary tolerance. This is important for degenerated Z polyhedra
0358   while (begin <= end && pointZ < *end + kTolerance) {
0359     --index;
0360     --end;
0361   }
0362   if ((size_t(index + 2) < size) && (pointZ > *(end + 1) - kTolerance)) return (index + 1);
0363   return index;
0364 }
0365 } // End anonymous namespace
0366 
0367 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0368 template <typename Real_v>
0369 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE int PolyhedronImplementation<innerRadiiT, phiCutoutT>::FindZSegment(
0370     UnplacedStruct_t const &unplaced, Real_v const &pointZ)
0371 {
0372   return FindZSegmentKernel<Real_v>(pointZ, &unplaced.fZPlanes[0], unplaced.fZPlanes.size());
0373 }
0374 
0375 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0376 template <typename Real_v>
0377 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE int PolyhedronImplementation<innerRadiiT, phiCutoutT>::FindPhiSegment(
0378     UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point)
0379 {
0380 
0381   // Bounds between phi sections are represented as planes through the origin,
0382   // with the normal pointing along the phi direction.
0383   // To find the correct section, the point is projected onto each plane. If the
0384   // point is in front of a plane, but behind the subsequent plane, it must be
0385   // between them.
0386 
0387   int index                           = -1;
0388   SOA3D<Precision> const &phiSections = unplaced.fPhiSections;
0389   Real_v projectionFirst, projectionSecond;
0390   projectionFirst = point[0] * phiSections.x(0) + point[1] * phiSections.y(0) + point[2] * phiSections.z(0);
0391   for (int i = 1, iMax = unplaced.fSideCount + 1; i < iMax; ++i) {
0392     projectionSecond = point[0] * phiSections.x(i) + point[1] * phiSections.y(i) + point[2] * phiSections.z(i);
0393     if (projectionFirst > -kTolerance && projectionSecond < kTolerance) {
0394       index = i - 1;
0395       break;
0396     }
0397     projectionFirst = projectionSecond;
0398   }
0399 
0400   return index;
0401 }
0402 
0403 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0404 template <typename Real_v>
0405 VECCORE_ATT_HOST_DEVICE Real_v PolyhedronImplementation<innerRadiiT, phiCutoutT>::DistanceToInZSegment(
0406     UnplacedStruct_t const &unplaced, int segmentIndex, Vector3D<Real_v> const &point,
0407     Vector3D<Real_v> const &direction)
0408 {
0409 
0410   Real_v distance;
0411   bool done = false;
0412 
0413   ZSegment const &segment = unplaced.fZSegments[segmentIndex];
0414 
0415   // If the outer shell is hit, this will always be the correct result, except
0416   // for a zero crossing at a knife edge (rmin = rmax) that leaves an incident
0417   // inner plane into the hole: it does not enter the solid.
0418   distance = segment.outer.DistanceToIn<Real_v, false>(point, direction);
0419   if (TreatInner<innerRadiiT>(segment.hasInnerRadius()) && distance < kTolerance) {
0420     for (int i = 0; i < segment.inner.size(); ++i) {
0421       auto const normal = segment.inner.GetNormal(i);
0422       if (Abs(point.Dot(normal) + segment.inner.GetDistance(i)) < kTolerance && direction.Dot(normal) < -kTolerance) {
0423         distance = InfinityLength<Real_v>();
0424         break;
0425       }
0426     }
0427   }
0428   done = distance < InfinityLength<Real_v>();
0429   if (done) return distance;
0430 
0431   // If the outer shell is not hit and the phi cutout sides are hit, this will
0432   // always be the correct result
0433   if (TreatPhi<phiCutoutT>(unplaced.fHasPhiCutout)) {
0434     if (!done) distance = segment.phi.DistanceToIn<Real_v, false>(point, direction);
0435     if (unplaced.fHasLargePhiCutout) {
0436       // NOTE: The statement above is NOT always true: if fHasLargePhiCutout is false there can be a first hit of the
0437       // inner surface coming from the endcap holes
0438       done |= distance < InfinityLength<Real_v>();
0439       if (done) return distance;
0440     }
0441   }
0442 
0443   // Finally treat inner shell
0444   if (TreatInner<innerRadiiT>(segment.hasInnerRadius())) {
0445     Real_v distrmin = segment.inner.DistanceToIn<Real_v, true>(point, direction);
0446     // A zero inner crossing on an edge shared with an outer plane that the ray
0447     // leaves does not enter the solid (e.g. a knife edge where rmin = rmax).
0448     if (distrmin < kTolerance) {
0449       for (int i = 0; i < segment.outer.size(); ++i) {
0450         auto const normal = segment.outer.GetNormal(i);
0451         if (Abs(point.Dot(normal) + segment.outer.GetDistance(i)) < kTolerance && direction.Dot(normal) > kTolerance) {
0452           distrmin = InfinityLength<Real_v>();
0453           break;
0454         }
0455       }
0456     }
0457     if (!done && distance > distrmin) distance = distrmin;
0458   }
0459 
0460   return distance;
0461 }
0462 
0463 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0464 template <typename Real_v>
0465 VECCORE_ATT_HOST_DEVICE Real_v PolyhedronImplementation<innerRadiiT, phiCutoutT>::DistanceToOutZSegment(
0466     UnplacedStruct_t const &unplaced, int segmentIndex, Precision zMin, Precision zMax, Vector3D<Real_v> const &point,
0467     Vector3D<Real_v> const &direction, ExitHit *hit)
0468 {
0469 
0470   bool done       = false;
0471   Real_v distance = InfinityLength<Real_v>();
0472 
0473   ZSegment const &segment = unplaced.fZSegments[segmentIndex];
0474 
0475   // Check inner shell first, as it would always be the correct result
0476   if (TreatInner<innerRadiiT>(segment.hasInnerRadius())) {
0477     int face = -1;
0478     distance = segment.inner.DistanceToIn<Real_v, false, true>(point, direction, &face);
0479     if (distance < kTolerance) {
0480       const int count    = segment.inner.size();
0481       const int previous = face ? face - 1 : count - 1;
0482       const int next     = face + 1 == count ? 0 : face + 1;
0483       // Smooth-face starts need no scan. At an axial tip all planes are
0484       // incident, so the neighboring-plane test retains the full check.
0485       const bool nearEdge =
0486           Abs(point.Dot(segment.inner.GetNormal(previous)) + segment.inner.GetDistance(previous)) < kTolerance ||
0487           Abs(point.Dot(segment.inner.GetNormal(next)) + segment.inner.GetDistance(next)) < kTolerance;
0488       // Entering one inner facet at a shared edge need not enter the hole.
0489       // Reject a zero crossing if another incident hole plane is moving out.
0490       for (int i = 0; nearEdge && i < count; ++i) {
0491         auto const normal = segment.inner.GetNormal(i);
0492         if (Abs(point.Dot(normal) + segment.inner.GetDistance(i)) < kTolerance && direction.Dot(normal) > kTolerance) {
0493           distance = InfinityLength<Real_v>();
0494           break;
0495         }
0496       }
0497     }
0498     if (hit && distance < InfinityLength<Real_v>()) *hit = {ExitSurface::kInner, face};
0499     // Even if an inner surface is hit, there may be a phi hit before if there is no large phi cut
0500     if (unplaced.fHasLargePhiCutout) {
0501       done = distance < InfinityLength<Real_v>();
0502       if (done) return distance;
0503     }
0504   }
0505 
0506   // Check phi cutout if necessary. It is also possible to return here if a
0507   // result is found
0508   if (TreatPhi<phiCutoutT>(unplaced.fHasPhiCutout)) {
0509     int face       = -1;
0510     Real_v distphi = segment.phi.DistanceToIn<Real_v, true, true>(point, direction, hit ? &face : nullptr);
0511     if (distance > distphi) {
0512       distance = distphi;
0513       if (hit) *hit = {ExitSurface::kPhi, face};
0514     }
0515   }
0516 
0517   done = distance > -kTolerance && distance < InfinityLength<Real_v>();
0518   if (done) return distance;
0519 
0520   // Finally check outer shell
0521   int face       = -1;
0522   Real_v distout = segment.outer.DistanceToOut<Real_v>(point, direction, zMin, zMax, hit ? &face : nullptr);
0523   if (!done) {
0524     distance = distout;
0525     if (hit && distout < InfinityLength<Real_v>()) *hit = {ExitSurface::kOuter, face};
0526   }
0527 
0528   return distance;
0529 }
0530 
0531 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0532 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE Precision
0533 PolyhedronImplementation<innerRadiiT, phiCutoutT>::SafetyToZSegmentSquared(UnplacedStruct_t const &unplaced,
0534                                                                            int segmentIndex, int &phiIndex,
0535                                                                            Vector3D<Precision> const &point,
0536                                                                            bool pt_inside, int &iSurf)
0537 {
0538 
0539   ZSegment const &segment = unplaced.fZSegments[segmentIndex];
0540   bool in_cutout          = phiIndex < 0;
0541 
0542   Precision safetySquared = InfinityLength<Precision>();
0543   if (TreatPhi<phiCutoutT>(unplaced.fHasPhiCutout) && segment.phi.size() == 2) {
0544     //  Check if points is in the cutout wedge first.
0545     if (pt_inside || in_cutout) {
0546       // If point is in the cutout or if the call comes from SafetyToOut we need to check both phi planes
0547       iSurf         = 0;
0548       safetySquared = segment.phi.ScalarDistanceSquared(0, point);
0549       Precision saf = segment.phi.ScalarDistanceSquared(1, point);
0550       if (saf < safetySquared) {
0551         safetySquared = saf;
0552         iSurf         = 1;
0553       }
0554       // If the point is within the phi cutout wedge, we still need to check the
0555       // inner part
0556       if (in_cutout) {
0557         if (TreatInner<innerRadiiT>(segment.hasInnerRadius())) {
0558           if (segment.inner.size() > 0) {
0559             Precision safetySquaredInner = segment.inner.ScalarDistanceSquared(0, point);
0560             if (safetySquaredInner < safetySquared) {
0561               iSurf         = 2;
0562               phiIndex      = 0;
0563               safetySquared = safetySquaredInner;
0564             }
0565             if (segment.inner.size() > 1) {
0566               safetySquaredInner = segment.inner.ScalarDistanceSquared(segment.inner.size() - 1, point);
0567               if (safetySquaredInner < safetySquared) {
0568                 iSurf         = 2;
0569                 phiIndex      = segment.inner.size() - 1;
0570                 safetySquared = safetySquaredInner;
0571               }
0572             }
0573           }
0574         }
0575         return safetySquared;
0576       }
0577     }
0578   }
0579 
0580   if (in_cutout && segmentIndex > 0 && segmentIndex < unplaced.fZSegments.size() - 1 &&
0581       unplaced.fZPlanes[segmentIndex] == unplaced.fZPlanes[segmentIndex + 1]) {
0582     // We are checking a segment at same Z. We have to check the inner and outer
0583     // quadrilaterals for first and last phi
0584     Precision safetySquaredOuter = InfinityLength<Precision>();
0585     if (segment.outer.size() > 0) {
0586       safetySquaredOuter = segment.outer.ScalarDistanceSquared(0, point);
0587       if (safetySquaredOuter < safetySquared) {
0588         iSurf         = 3;
0589         phiIndex      = 0;
0590         safetySquared = safetySquaredOuter;
0591       }
0592       if (segment.outer.size() > 1) {
0593         safetySquaredOuter = segment.outer.ScalarDistanceSquared(segment.outer.size() - 1, point);
0594         if (safetySquaredOuter < safetySquared) {
0595           iSurf         = 3;
0596           phiIndex      = segment.outer.size() - 1;
0597           safetySquared = safetySquaredOuter;
0598         }
0599       }
0600     }
0601     Precision safetySquaredInner = InfinityLength<Precision>();
0602     if (TreatInner<innerRadiiT>(segment.hasInnerRadius())) {
0603       if (segment.inner.size() > 0) {
0604         safetySquaredInner = segment.inner.ScalarDistanceSquared(0, point);
0605         if (safetySquaredInner < safetySquared) {
0606           iSurf         = 2;
0607           phiIndex      = 0;
0608           safetySquared = safetySquaredInner;
0609         }
0610         if (segment.inner.size() > 1) {
0611           safetySquaredInner = segment.inner.ScalarDistanceSquared(segment.inner.size() - 1, point);
0612           if (safetySquaredInner < safetySquared) {
0613             iSurf         = 2;
0614             phiIndex      = segment.inner.size() - 1;
0615             safetySquared = safetySquaredInner;
0616           }
0617         }
0618       }
0619     }
0620     return safetySquared;
0621   }
0622 
0623   // Otherwise check the outer shell
0624   // TODO: we need to check segment.outer.size() > 0
0625   Precision safetySquaredOuter = InfinityLength<Precision>();
0626   if (segment.outer.size() > 0) safetySquaredOuter = segment.outer.ScalarDistanceSquared(phiIndex, point);
0627 
0628   // And finally the inner
0629   Precision safetySquaredInner = InfinityLength<Precision>();
0630   if (TreatInner<innerRadiiT>(segment.hasInnerRadius())) {
0631     if (segment.inner.size() > 0) safetySquaredInner = segment.inner.ScalarDistanceSquared(phiIndex, point);
0632   }
0633   if (safetySquaredInner < safetySquared) {
0634     iSurf         = 2;
0635     safetySquared = safetySquaredInner;
0636   }
0637   if (safetySquaredOuter < safetySquared) {
0638     iSurf         = 3;
0639     safetySquared = safetySquaredOuter;
0640   }
0641   return safetySquared;
0642 }
0643 
0644 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0645 template <bool pointInsideT>
0646 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool PolyhedronImplementation<innerRadiiT, phiCutoutT>::DistanceToEndcaps(
0647     UnplacedStruct_t const &unplaced, bool /*goingRight*/, Vector3D<Precision> const &point,
0648     Vector3D<Precision> const &direction, Precision &distance)
0649 {
0650 
0651   ZSegment const *segment;
0652   Precision zPlane;
0653 
0654   // Determine whether to use first segment/first endcap or last segment/second
0655   // endcap
0656   // NOTE: might make this more elegant
0657   if (pointInsideT) // inside version
0658   {
0659     if (direction[2] < 0) {
0660       segment = &unplaced.fZSegments[0];
0661       zPlane  = unplaced.fZPlanes[0];
0662     } else {
0663       segment = &unplaced.fZSegments[unplaced.fZSegments.size() - 1];
0664       zPlane  = unplaced.fZPlanes[unplaced.fZSegments.size()];
0665     }
0666   } else // outside version
0667   {
0668     if (direction[2] < 0) {
0669       segment = &unplaced.fZSegments[unplaced.fZSegments.size() - 1];
0670       zPlane  = unplaced.fZPlanes[unplaced.fZSegments.size()];
0671     } else {
0672       segment = &unplaced.fZSegments[0];
0673       zPlane  = unplaced.fZPlanes[0];
0674     }
0675   }
0676 
0677   Precision distanceTest = (zPlane - point[2]) / NonZero(direction[2]);
0678   // If the distance is not better there's no reason to check for validity
0679   if (distanceTest < -kTolerance || distanceTest >= distance) return false;
0680 
0681   Vector3D<Precision> intersection = point + distanceTest * direction;
0682   // Intersection point must be inside outer shell and outside inner shell
0683   if (!segment->outer.Contains<Precision>(intersection)) return false;
0684   if (TreatInner<innerRadiiT>(segment->hasInnerRadius())) {
0685     if (segment->inner.Contains<Precision>(intersection)) return false;
0686   }
0687   // Intersection point must not be in phi cutout wedge
0688   if (TreatPhi<phiCutoutT>(unplaced.fHasPhiCutout)) {
0689     if (InPhiCutoutWedge(*segment, unplaced.fHasLargePhiCutout, intersection)) {
0690       return false;
0691     }
0692   }
0693 
0694   distance = distanceTest;
0695   return true;
0696 }
0697 
0698 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0699 template <typename Real_v>
0700 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void PolyhedronImplementation<innerRadiiT, phiCutoutT>::SetExitHit(
0701     UnplacedStruct_t const &unplaced, int segmentIndex, ExitHit const &hit, SurfaceHitView<Real_v> *hitInfo)
0702 {
0703   bool leftBehind = false;
0704   Vector3D<Precision> normal;
0705 
0706   if (hit.surface == ExitSurface::kZMin || hit.surface == ExitSurface::kZMax) {
0707     leftBehind = true;
0708     normal.Set(0., 0., hit.surface == ExitSurface::kZMin ? -1. : 1.);
0709   } else if (segmentIndex >= 0 && segmentIndex < unplaced.fZSegments.size() && hit.face >= 0) {
0710     ZSegment const &segment = unplaced.fZSegments[segmentIndex];
0711     if (hit.surface == ExitSurface::kOuter) {
0712       leftBehind = segment.outerSupports;
0713       normal     = segment.outer.GetNormal(hit.face);
0714     } else if (hit.surface == ExitSurface::kPhi) {
0715       leftBehind = unplaced.fHasLargePhiCutout;
0716       normal     = segment.phi.GetNormal(hit.face);
0717     } else if (hit.surface == ExitSurface::kInner) {
0718       // Inner-shell planes face away from the axis; the solid's outward normal is the opposite.
0719       normal = -segment.inner.GetNormal(hit.face);
0720     }
0721   }
0722 
0723   hitInfo->SetLeftBehind(leftBehind);
0724   if (hitInfo->WantsNormal() && normal.Mag2() > 0.) hitInfo->SetNormal(Vector3D<Real_v>(normal));
0725 }
0726 
0727 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0728 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void PolyhedronImplementation<
0729     innerRadiiT, phiCutoutT>::SafetyToEndcapsSquared(UnplacedStruct_t const &unplaced, Vector3D<Precision> const &point,
0730                                                      Precision &distanceSquared, int &iz)
0731 {
0732 
0733   // Compute both distances (simple subtractions) to determine which is closer
0734   Precision firstDistance = unplaced.fZPlanes[0] - point[2];
0735   Precision lastDistance  = unplaced.fZPlanes[unplaced.fZSegments.size()] - point[2];
0736 
0737   // Only treat the closest endcap
0738   bool isFirst            = Abs(firstDistance) < Abs(lastDistance);
0739   iz                      = 0;
0740   ZSegment const &segment = isFirst ? unplaced.fZSegments[0] : unplaced.fZSegments[unplaced.fZSegments.size() - 1];
0741 
0742   Precision distanceTest        = isFirst ? firstDistance : lastDistance;
0743   Precision distanceTestSquared = distanceTest * distanceTest;
0744   // No need to investigate further if distance is larger anyway
0745   if (distanceTestSquared >= distanceSquared) return;
0746 
0747   // Check if projection is within the endcap bounds
0748   Vector3D<Precision> intersection(point[0], point[1], point[2] + distanceTest);
0749   if (!segment.outer.Contains<Precision>(intersection)) return;
0750   if (TreatInner<innerRadiiT>(segment.hasInnerRadius())) {
0751     if (segment.inner.Contains<Precision>(intersection)) return;
0752   }
0753   if (TreatPhi<phiCutoutT>(unplaced.fHasPhiCutout)) {
0754     if (InPhiCutoutWedge(segment, unplaced.fHasLargePhiCutout, intersection)) {
0755       return;
0756     }
0757   }
0758 
0759   iz              = (isFirst) ? -1 : 1;
0760   distanceSquared = distanceTestSquared;
0761 }
0762 
0763 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0764 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool PolyhedronImplementation<innerRadiiT, phiCutoutT>::InPhiCutoutWedge(
0765     ZSegment const &segment, bool largePhiCutout, Vector3D<Precision> const &point)
0766 {
0767   bool pointSeg0 = point.Dot(segment.phi.GetNormal(0)) + segment.phi.GetDistance(0) >= kTolerance;
0768   bool pointSeg1 = point.Dot(segment.phi.GetNormal(1)) + segment.phi.GetDistance(1) >= kTolerance;
0769   // For a cutout larger than 180 degrees, the point is in the wedge if it is
0770   // in front of at least one plane.
0771   if (LargePhiCutout<phiCutoutT>(largePhiCutout)) {
0772     return pointSeg0 || pointSeg1;
0773   }
0774   // Otherwise it should be in front of both planes
0775   return pointSeg0 && pointSeg1;
0776 }
0777 
0778 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0779 VECCORE_ATT_HOST_DEVICE Inside_t PolyhedronImplementation<innerRadiiT, phiCutoutT>::InsideSegBorder(
0780     UnplacedStruct_t const &unplaced, Vector3D<Precision> const &point, int zIndex)
0781 {
0782   // Check Inside if the point is in between two non-continuous "border-like"
0783   // segments. The zIndex corresponds to the lesser index of the 2 planes having the same Z.
0784   // The Quadrilaterals algorithm for Inside in this case does not work.
0785 
0786   ZSegment const &segment = unplaced.fZSegments[zIndex];
0787   // Identify phi index
0788   int phiIndex = FindPhiSegment<Precision>(unplaced, point);
0789   if (phiIndex < 0) return EInside::kOutside;
0790   // Get the vector perpendicular to the rmax edge of the outer quadrilateral
0791   Vector3D<Precision> const &vout = (segment.outer.size()) ? segment.outer.GetSideVectors()[0].GetNormals()[phiIndex]
0792                                                            : segment.inner.GetSideVectors()[0].GetNormals()[phiIndex];
0793   // Compute the projection of the point vectoron the vout vector. This
0794   // corresponds to a "radius" or the point.
0795   Precision rdotvout = Abs<Precision>(point.Dot(vout));
0796   // Now compare the point radius with the ranges corresponding to the lower
0797   // and upper segments
0798   bool in1 = (rdotvout > unplaced.fRMin[zIndex] - kTolerance) && (rdotvout < unplaced.fRMax[zIndex] + kTolerance);
0799   bool in2 =
0800       (rdotvout > unplaced.fRMin[zIndex + 1] - kTolerance) && (rdotvout < unplaced.fRMax[zIndex + 1] + kTolerance);
0801   if (in1 && in2) {
0802     if ((rdotvout < unplaced.fRMin[zIndex] + kTolerance) || (rdotvout > unplaced.fRMax[zIndex] - kTolerance) ||
0803         (rdotvout < unplaced.fRMin[zIndex + 1] + kTolerance) || (rdotvout > unplaced.fRMax[zIndex + 1] - kTolerance))
0804       return EInside::kSurface;
0805     // Need to check phi surface
0806     if (TreatPhi<phiCutoutT>(unplaced.fHasPhiCutout)) {
0807       Inside_t insidePhi = unplaced.fPhiWedge.Inside<Precision>(point);
0808       return insidePhi;
0809     }
0810     return EInside::kInside;
0811   }
0812   if (!in1 && !in2) return EInside::kOutside;
0813   return EInside::kSurface;
0814 }
0815 
0816 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0817 VECCORE_ATT_HOST_DEVICE bool PolyhedronImplementation<innerRadiiT, phiCutoutT>::NormalKernel(
0818     UnplacedStruct_t const &unplaced, Vector3D<Precision> const &point, Vector3D<Precision> &normal)
0819 {
0820 
0821   Precision safety = InfinityLength<Precision>();
0822   const int zMax   = unplaced.fZSegments.size();
0823   int zIndex       = FindZSegment<Precision>(unplaced, point[2]);
0824   if (zIndex < 0) {
0825     normal = Vector3D<Precision>(0, 0, -1);
0826     return true;
0827   }
0828 
0829   if (zIndex >= zMax) {
0830     normal = Vector3D<Precision>(0, 0, 1);
0831     return true;
0832   }
0833 
0834   int iSeg = zIndex;
0835   Precision dz;
0836   int iSurf    = -1;
0837   int iz       = 0;
0838   int phiIndex = FindPhiSegment<Precision>(unplaced, point);
0839 
0840   if (zMax == 1 && phiIndex >= 0 && !TreatPhi<phiCutoutT>(unplaced.fHasPhiCutout) &&
0841       !TreatInner<innerRadiiT>(unplaced.fZSegments[0].hasInnerRadius()) && unplaced.fRMax[0] == unplaced.fRMax[1] &&
0842       point[2] >= unplaced.fZPlanes[0] && point[2] <= unplaced.fZPlanes[1]) {
0843     const auto &outer                    = unplaced.fZSegments[0].outer;
0844     const auto faceNormal                = outer.GetNormal(phiIndex);
0845     const Precision planeDistance        = point.Dot(faceNormal) + outer.GetDistance(phiIndex);
0846     const Vector3D<Precision> projection = point - planeDistance * faceNormal;
0847     const auto &sides                    = outer.GetSideVectors();
0848     // A prism's radial projection preserves z; only its two vertical sides need checking.
0849     if (faceNormal.z() == 0. && projection.Dot(sides[1].GetNormal(phiIndex)) + sides[1].GetDistance(phiIndex) >= 0. &&
0850         projection.Dot(sides[3].GetNormal(phiIndex)) + sides[3].GetDistance(phiIndex) >= 0.) {
0851       safety = planeDistance * planeDistance;
0852       SafetyToEndcapsSquared(unplaced, point, safety, iz);
0853       normal = iz ? Vector3D<Precision>(0., 0., iz) : faceNormal;
0854       return true;
0855     }
0856   }
0857 
0858   // Right
0859   for (int z = zIndex; z < zMax;) {
0860     int iSurfCrt        = -1;
0861     Precision safetySeg = SafetyToZSegmentSquared(unplaced, z, phiIndex, point, true, iSurfCrt);
0862     if (safetySeg < safety) {
0863       safety = safetySeg;
0864       iSeg   = z;
0865       iSurf  = iSurfCrt;
0866     }
0867     ++z;
0868     dz = unplaced.fZPlanes[z] - point[2];
0869     if (dz * dz > safety) break;
0870   }
0871   // Left
0872   for (int z = zIndex - 1; z >= 0; --z) {
0873     int iSurfCrt        = -1;
0874     Precision safetySeg = SafetyToZSegmentSquared(unplaced, z, phiIndex, point, true, iSurfCrt);
0875     if (safetySeg < safety) {
0876       safety = safetySeg;
0877       iSeg   = z;
0878       iSurf  = iSurfCrt;
0879     }
0880     dz = point[2] - unplaced.fZPlanes[z];
0881     if (dz * dz > safety) break;
0882   }
0883 
0884   // Endcap
0885   SafetyToEndcapsSquared(unplaced, point, safety, iz);
0886   if (iz != 0) {
0887     normal = Vector3D<Precision>(0, 0, iz);
0888     return true;
0889   }
0890 
0891   // Retrieve the segment the point is closest to.
0892   ZSegment const &segment = unplaced.fZSegments[iSeg];
0893   if (iSurf >= 0 && iSurf < 2) {
0894     normal = segment.phi.GetNormal(iSurf);
0895   } else {
0896     if (iSurf == 2)
0897       normal = -1. * segment.inner.GetNormal(phiIndex);
0898     else
0899       normal = segment.outer.GetNormal(phiIndex);
0900   }
0901   return true;
0902 }
0903 
0904 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0905 template <typename Real_v>
0906 VECCORE_ATT_HOST_DEVICE void PolyhedronImplementation<innerRadiiT, phiCutoutT>::UnplacedContains(
0907     UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point, bool &inside)
0908 {
0909   Contains(unplaced, point, inside);
0910 }
0911 
0912 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0913 template <typename Real_v>
0914 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void PolyhedronImplementation<innerRadiiT, phiCutoutT>::Contains(
0915     UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point, bool &inside)
0916 {
0917   Vector3D<Precision> localPoint(point[0], point[1], point[2]);
0918 
0919   // First check if in bounding tube.
0920   {
0921     bool inBounds;
0922     HasInnerRadiiTraits<innerRadiiT>::TubeKernels::template Contains<>(
0923         unplaced.fBoundingTube,
0924         Vector3D<Precision>(localPoint[0], localPoint[1], localPoint[2] - unplaced.fBoundingTubeOffset), inBounds);
0925     if (!inBounds) {
0926       inside = false;
0927       return;
0928     }
0929   }
0930 
0931   // Find correct segment by checking Z-bounds.
0932   int zIndex = FindZSegment<Precision>(unplaced, localPoint[2]);
0933   if (!(zIndex >= 0 && zIndex < unplaced.fZSegments.size())) {
0934     inside = false;
0935     return;
0936   }
0937 
0938   ZSegment const &segment = unplaced.fZSegments[zIndex];
0939 
0940   // In case the point lies at the same Z as 2 consecutive planes, the lesser
0941   // index is selected. The Quadrilaterals algorithm for Contains in this case
0942   // does not work.
0943   if (unplaced.fSameZ[zIndex]) {
0944     int phiIndex = FindPhiSegment<Precision>(unplaced, localPoint);
0945     if (phiIndex < 0) {
0946       inside = false;
0947       return;
0948     }
0949     Vector3D<Precision> const &vout = (segment.outer.size()) ? segment.outer.GetSideVectors()[0].GetNormals()[phiIndex]
0950                                                              : segment.inner.GetSideVectors()[0].GetNormals()[phiIndex];
0951     Precision rdotvout              = Abs<Precision>(localPoint.Dot(vout));
0952     bool in1                        = (rdotvout >= unplaced.fRMin[zIndex]) && (rdotvout <= unplaced.fRMax[zIndex]);
0953     bool in2 = (rdotvout >= unplaced.fRMin[zIndex + 1]) && (rdotvout <= unplaced.fRMax[zIndex + 1]);
0954     inside   = (in1 | in2);
0955     return;
0956   }
0957 
0958   if (!segment.outer.Contains<Precision>(localPoint)) {
0959     inside = false;
0960     return;
0961   }
0962 
0963   if (TreatInner<innerRadiiT>(segment.hasInnerRadius()) && segment.inner.Contains<Precision>(localPoint)) {
0964     inside = false;
0965     return;
0966   }
0967 
0968   // The bounding tube already handles phi in principle, but the dedicated
0969   // polyhedron cutout check still matters because it uses different tolerances.
0970   if (TreatPhi<phiCutoutT>(unplaced.fHasPhiCutout) && !segment.phi.Contains<Precision>(localPoint)) {
0971     inside = false;
0972     return;
0973   }
0974 
0975   inside = true;
0976 }
0977 
0978 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
0979 template <typename Real_v>
0980 VECCORE_ATT_HOST_DEVICE void PolyhedronImplementation<innerRadiiT, phiCutoutT>::Inside(UnplacedStruct_t const &unplaced,
0981                                                                                        Vector3D<Real_v> const &point,
0982                                                                                        Inside_t &inside)
0983 {
0984   Vector3D<Precision> localPoint(point[0], point[1], point[2]);
0985 
0986   {
0987     bool inBounds;
0988     // The bounding tube is intentionally slightly enlarged to keep the fast
0989     // reject compatible with the tolerance-sensitive inside checks below. The
0990     // old ideal tube was too tight for early rejection because this path must
0991     // preserve the same tolerance envelope as the detailed segment checks.
0992     HasInnerRadiiTraits<innerRadiiT>::TubeKernels::template Contains<>(
0993         unplaced.fBoundingTube,
0994         Vector3D<Precision>(localPoint[0], localPoint[1], localPoint[2] - unplaced.fBoundingTubeOffset), inBounds);
0995     if (!inBounds) {
0996       inside = EInside::kOutside;
0997       return;
0998     }
0999   }
1000 
1001   // For two consecutive planes with identical Z, FindZSegment must prefer the
1002   // first segment so the degenerate border segment is checked before its
1003   // neighbor. The enlarged bounding tube can still accept points outside the
1004   // actual Z range, so the explicit segment-range rejection remains necessary.
1005   int zIndex = FindZSegment<Precision>(unplaced, localPoint[2]);
1006   if (zIndex < 0 || zIndex > (unplaced.fZSegments.size() - 1)) {
1007     inside = EInside::kOutside;
1008     return;
1009   }
1010 
1011   ZSegment const &segment = unplaced.fZSegments[zIndex];
1012   if (unplaced.fSameZ[zIndex]) {
1013     inside = InsideSegBorder(unplaced, localPoint, zIndex);
1014     return;
1015   }
1016 
1017   {
1018     Inside_t insideOuter = segment.outer.Inside<Precision, Inside_t>(localPoint);
1019     if (insideOuter != EInside::kInside) {
1020       inside = insideOuter;
1021       return;
1022     }
1023   }
1024 
1025   if (TreatInner<innerRadiiT>(segment.hasInnerRadius())) {
1026     Inside_t insideInner = segment.inner.Inside<Precision, Inside_t>(localPoint);
1027     if (insideInner == EInside::kInside) {
1028       inside = EInside::kOutside;
1029       return;
1030     }
1031     if (insideInner == EInside::kSurface) {
1032       inside = EInside::kSurface;
1033       return;
1034     }
1035   }
1036 
1037   if (TreatPhi<phiCutoutT>(unplaced.fHasPhiCutout)) {
1038     Inside_t insidePhi = segment.phi.Inside<Precision, Inside_t>(localPoint);
1039     if (insidePhi != EInside::kInside) {
1040       inside = insidePhi;
1041       return;
1042     }
1043   }
1044 
1045   // After the radial and phi checks, the point can still be on the global Z
1046   // endcap boundary. Classify that tolerance band as surface rather than inside.
1047   Precision dz = Abs(Abs(localPoint[2] - unplaced.fBoundingTubeOffset) -
1048                      0.5 * (unplaced.fZPlanes[unplaced.fZSegments.size()] - unplaced.fZPlanes[0]));
1049   inside       = dz < kTolerance ? EInside::kSurface : EInside::kInside;
1050 }
1051 
1052 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
1053 template <typename Real_v>
1054 VECCORE_ATT_HOST_DEVICE void PolyhedronImplementation<innerRadiiT, phiCutoutT>::DistanceToIn(
1055     UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point, Vector3D<Real_v> const &direction,
1056     Real_v const &stepMax, Real_v &distance)
1057 {
1058   Vector3D<Precision> localPoint(point[0], point[1], point[2]);
1059   Vector3D<Precision> localDirection(direction[0], direction[1], direction[2]);
1060   Precision localStepMax = stepMax;
1061 
1062   if ((localPoint[2] < unplaced.fZPlanes[0] + kTolerance) && localDirection[2] <= 0) {
1063     distance = InfinityLength<Real_v>();
1064     return;
1065   }
1066   if ((localPoint[2] > unplaced.fZPlanes[unplaced.fZSegments.size()] - kTolerance) && localDirection[2] >= 0) {
1067     distance = InfinityLength<Real_v>();
1068     return;
1069   }
1070 
1071   // Explicitly detect wrong-side points. Without this guard, a point already
1072   // inside the solid could be reported as having a normal entry distance.
1073   Inside_t insideState;
1074   Inside(unplaced, localPoint, insideState);
1075   if (insideState == kInside) {
1076     distance = Real_v(-1.);
1077     return;
1078   }
1079 
1080   bool inBounds;
1081   Precision tubeDistance = 0.;
1082   {
1083     Vector3D<Precision> boundsPoint(localPoint[0], localPoint[1], localPoint[2] - unplaced.fBoundingTubeOffset);
1084     HasInnerRadiiTraits<innerRadiiT>::TubeKernels::template Contains<>(unplaced.fBoundingTube, boundsPoint, inBounds);
1085     // If the point is inside the bounding tube, its DistanceToIn value is not
1086     // reliable for rejecting the ray; the tube can report a forward boundary
1087     // even though the polyhedron entry must be decided by the detailed segments.
1088     if (!inBounds) {
1089       // When the point is outside the bounding tube, a missed tube entry is a
1090       // valid early rejection for the whole polyhedron.
1091       HasInnerRadiiTraits<innerRadiiT>::TubeKernels::template DistanceToIn<>(
1092           unplaced.fBoundingTube, boundsPoint, localDirection, localStepMax, tubeDistance);
1093       if (tubeDistance == InfinityLength<Precision>()) {
1094         distance = InfinityLength<Real_v>();
1095         return;
1096       }
1097     }
1098   }
1099 
1100   int zIndex     = FindZSegment<Precision>(unplaced, localPoint[2]);
1101   const int zMax = unplaced.fZSegments.size();
1102   // Clamp only after FindZSegment: the first or last Z segment still has to be
1103   // checked even when the start point is outside the nominal Z range.
1104   zIndex = zIndex < 0 ? 0 : (zIndex >= zMax ? zMax - 1 : zIndex);
1105 
1106   bool goingRight                     = localDirection[2] >= 0;
1107   constexpr Precision boundsTolerance = 1E-6;
1108   if (zMax > 2 && tubeDistance > boundsTolerance) {
1109     // No accepted hit precedes the bounding-tube entry beyond the final check's tolerance.
1110     // Keep the neighboring segment as well to preserve section-boundary ownership.
1111     const Precision boundsZ = localPoint[2] + (tubeDistance - boundsTolerance) * localDirection[2];
1112     const int initialIndex  = zIndex;
1113     if (goingRight) {
1114       while (zIndex + 1 < zMax && unplaced.fZPlanes[zIndex + 1] < boundsZ - kTolerance)
1115         ++zIndex;
1116       if (zIndex > initialIndex) --zIndex;
1117     } else {
1118       while (zIndex > 0 && unplaced.fZPlanes[zIndex] > boundsZ + kTolerance)
1119         --zIndex;
1120       if (zIndex < initialIndex) ++zIndex;
1121     }
1122   }
1123 
1124   Precision result = InfinityLength<Precision>();
1125   if (goingRight) {
1126     for (int zSegCount = unplaced.fZSegments.size(); zIndex < zSegCount; ++zIndex) {
1127       result = DistanceToInZSegment<Precision>(unplaced, zIndex, localPoint, localDirection);
1128       // Once a valid segment hit is found, farther Z segments cannot provide a
1129       // shorter entry; only the endcaps still need to be minimized.
1130       if (result >= 0 && result < InfinityLength<Precision>()) break;
1131     }
1132   } else {
1133     for (; zIndex >= 0; --zIndex) {
1134       result = DistanceToInZSegment<Precision>(unplaced, zIndex, localPoint, localDirection);
1135       // Once a valid segment hit is found, farther Z segments cannot provide a
1136       // shorter entry; only the endcaps still need to be minimized.
1137       if (result >= 0 && result < InfinityLength<Precision>()) break;
1138     }
1139   }
1140 
1141   // Endcaps are not part of the Z-segment side traversal, so minimize them
1142   // explicitly. The final tube comparison is only a sanity check against the
1143   // coarse bounding volume, not the geometry algorithm that finds the entry.
1144   DistanceToEndcaps<false>(unplaced, goingRight, localPoint, localDirection, result);
1145   result   = (result >= tubeDistance - boundsTolerance) ? result : vecgeom::InfinityLength<Precision>();
1146   distance = result;
1147 }
1148 
1149 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
1150 template <typename Real_v, bool RecordHitInfo>
1151 VECCORE_ATT_HOST_DEVICE void PolyhedronImplementation<innerRadiiT, phiCutoutT>::DistanceToOutImpl(
1152     UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point, Vector3D<Real_v> const &direction,
1153     Real_v const &stepMax, Real_v &distance, SurfaceHitView<Real_v> *hitInfo)
1154 {
1155   if constexpr (RecordHitInfo) hitInfo->Clear();
1156 
1157   // Keep stepMax ignored until the finite-limit DistanceToOut convention is
1158   // revised; clamping an error-like infinity to stepMax would hide missed exits.
1159   (void)stepMax;
1160   Vector3D<Precision> localPoint(point[0], point[1], point[2]);
1161   Vector3D<Precision> localDirection(direction[0], direction[1], direction[2]);
1162 
1163   const int zMax = unplaced.fZSegments.size();
1164   if ((localPoint[2] < unplaced.fZPlanes[0] - kTolerance) || (localPoint[2] > unplaced.fZPlanes[zMax] + kTolerance)) {
1165     distance = Real_v(-1.);
1166     return;
1167   }
1168 
1169   Inside_t insideState;
1170   Inside(unplaced, localPoint, insideState);
1171   if (insideState == kOutside) {
1172     distance = Real_v(-1.);
1173     return;
1174   }
1175 
1176   int zIndex = FindZSegment<Precision>(unplaced, localPoint[2]);
1177   zIndex     = zIndex < 0 ? 0 : (zIndex >= zMax ? zMax - 1 : zIndex);
1178 
1179   bool goingRight = localDirection[2] >= 0;
1180 
1181   Precision result = InfinityLength<Precision>();
1182   ExitHit finalHit;
1183   int finalSegment = -1;
1184   if (goingRight) {
1185     for (; zIndex < zMax; ++zIndex) {
1186       ExitHit segmentHit;
1187       result =
1188           DistanceToOutZSegment<Precision>(unplaced, zIndex, unplaced.fZPlanes[zIndex], unplaced.fZPlanes[zIndex + 1],
1189                                            localPoint, localDirection, RecordHitInfo ? &segmentHit : nullptr);
1190       if (result >= 0 && result < InfinityLength<Precision>()) {
1191         if constexpr (RecordHitInfo) {
1192           finalHit     = segmentHit;
1193           finalSegment = zIndex;
1194         }
1195         break;
1196       }
1197       if (unplaced.fZPlanes[zIndex] - localPoint[2] > result) break;
1198     }
1199   } else {
1200     for (; zIndex >= 0; --zIndex) {
1201       ExitHit segmentHit;
1202       result =
1203           DistanceToOutZSegment<Precision>(unplaced, zIndex, unplaced.fZPlanes[zIndex], unplaced.fZPlanes[zIndex + 1],
1204                                            localPoint, localDirection, RecordHitInfo ? &segmentHit : nullptr);
1205       if (result >= 0 && result < InfinityLength<Precision>()) {
1206         if constexpr (RecordHitInfo) {
1207           finalHit     = segmentHit;
1208           finalSegment = zIndex;
1209         }
1210         break;
1211       }
1212       if (localPoint[2] - unplaced.fZPlanes[zIndex] > result) break;
1213     }
1214   }
1215 
1216   if (Abs(localDirection[2]) > kTolerance) {
1217     bool const endcapHit = DistanceToEndcaps<true>(unplaced, goingRight, localPoint, localDirection, result);
1218     if constexpr (RecordHitInfo) {
1219       if (endcapHit) {
1220         finalHit.surface = localDirection[2] < 0. ? ExitSurface::kZMin : ExitSurface::kZMax;
1221         finalHit.face    = -1;
1222         finalSegment     = -1;
1223       }
1224     }
1225   }
1226 
1227   if (result >= InfinityLength<Precision>()) result = 0.;
1228   distance = result;
1229   if constexpr (RecordHitInfo) {
1230     if (finalHit.surface != ExitSurface::kNone) SetExitHit(unplaced, finalSegment, finalHit, hitInfo);
1231   }
1232 }
1233 
1234 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
1235 template <typename Real_v>
1236 VECCORE_ATT_HOST_DEVICE void PolyhedronImplementation<innerRadiiT, phiCutoutT>::DistanceToOut(
1237     UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point, Vector3D<Real_v> const &direction,
1238     Real_v const &stepMax, Real_v &distance, SurfaceHitView<Real_v> *hitInfo)
1239 {
1240   if (hitInfo)
1241     DistanceToOutImpl<Real_v, true>(unplaced, point, direction, stepMax, distance, hitInfo);
1242   else
1243     DistanceToOutImpl<Real_v, false>(unplaced, point, direction, stepMax, distance, nullptr);
1244 }
1245 
1246 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
1247 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE Precision
1248 PolyhedronImplementation<innerRadiiT, phiCutoutT>::SafetyToRZOutlineSquared(UnplacedStruct_t const &unplaced,
1249                                                                             Vector3D<Precision> const &point,
1250                                                                             Precision normSign, bool &wrongSide)
1251 {
1252   // The largest panel-normal projection selects the nearest facet without
1253   // unpredictable per-point branches. For long facet lists, the bracketing
1254   // search can stop early; both give the same radius inside the phi region.
1255   const int phiIndex = unplaced.fSideCount > 64 ? FindPhiSegment<Precision>(unplaced, point) : -1;
1256   Precision rzRadius = 0.;
1257   if (phiIndex >= 0) {
1258     const PhiMidTrig trig = unplaced.fPhiMid[phiIndex];
1259     rzRadius              = Max(Precision(0.), point.x() * trig.cosMid + point.y() * trig.sinMid);
1260   } else {
1261     for (int i = 0; i < unplaced.fSideCount; ++i) {
1262       const PhiMidTrig trig = unplaced.fPhiMid[i];
1263       rzRadius              = Max(rzRadius, point.x() * trig.cosMid + point.y() * trig.sinMid);
1264     }
1265   }
1266   const Precision z = point.z();
1267 
1268   // Outline corners and edge length/tangent are precomputed in
1269   // PolyhedronStruct::Initialize and colocated per entry (one memory access
1270   // per corner instead of five); this loop only looks them up. All points
1271   // walk the same corner sequence, so entry1 of this iteration becomes
1272   // entry0 of the next -- each entry is read once per pass.
1273   const int numCorners = int(unplaced.fRZOutline.size());
1274 
1275   Precision best = InfinityLength<Precision>();
1276   // Exact point-in-polygon parity (ray-casting), folded into the same loop.
1277   // Needed because the per-edge normSign reject below only reflects the
1278   // nearest edge, not a reliable containment test for a non-convex outline.
1279   bool contains         = false;
1280   RZOutlineEntry entry0 = unplaced.fRZOutline[0];
1281   for (int k = 0; k < numCorners; ++k) {
1282     const int k1                = (k + 1 == numCorners) ? 0 : k + 1;
1283     const RZOutlineEntry entry1 = unplaced.fRZOutline[k1];
1284     const Precision r0 = entry0.r, z0 = entry0.z;
1285     const Precision r1 = entry1.r, z1 = entry1.z;
1286 
1287     if ((z0 > z) != (z1 > z)) {
1288       const Precision rIntercept = r0 + (z - z0) / (z1 - z0) * (r1 - r0);
1289       if (rzRadius < rIntercept) contains = !contains;
1290     }
1291 
1292     // Cheap reject before the distance work below: an edge entirely above
1293     // or below the point by more than the current best safety cannot
1294     // improve it, matching native's dzBound pre-filter.
1295     const Precision zEdgeMin = Min(z0, z1), zEdgeMax = Max(z0, z1);
1296     Precision dzBound = Precision(0.);
1297     if (z < zEdgeMin) {
1298       dzBound = zEdgeMin - z;
1299     } else if (z > zEdgeMax) {
1300       dzBound = z - zEdgeMax;
1301     }
1302     if (dzBound * dzBound > best) {
1303       entry0 = entry1;
1304       continue;
1305     }
1306 
1307     const Precision length = entry0.edgeLength;
1308     // The r=0 closure is not a surface of a full-phi solid.
1309     if (length > Precision(0.) && (r0 > Precision(0.) || r1 > Precision(0.))) {
1310       const Precision rS = entry0.edgeRS, zS = entry0.edgeZS;
1311       const Precision rNorm = zS, zNorm = -rS;
1312       const Precision drp = rzRadius - r0, dzp = z - z0;
1313       const Precision norm = drp * rNorm + dzp * zNorm;
1314       if (normSign * norm > -Precision(0.5) * kTolerance) {
1315         const Precision q  = drp * rS + dzp * zS;
1316         Precision distOut2 = Precision(0.);
1317         if (q < Precision(0.)) {
1318           distOut2 = q * q;
1319         } else if (q > length) {
1320           distOut2 = (q - length) * (q - length);
1321         }
1322         const Precision d2 = norm * norm + distOut2;
1323         if (d2 < best) best = d2;
1324       }
1325     }
1326     entry0 = entry1;
1327   }
1328   // normSign>0 (SafetyToIn): contained is wrong-side. normSign<0
1329   // (SafetyToOut): not-contained is wrong-side. Near-surface points (best
1330   // ~0) are excluded so their correct ~0 safety isn't overridden.
1331   wrongSide = ((normSign > Precision(0.)) ? contains : !contains) && best > kTolerance * kTolerance;
1332   return best;
1333 }
1334 
1335 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
1336 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE Precision
1337 PolyhedronImplementation<innerRadiiT, phiCutoutT>::SafetyToFiniteFacesSquared(UnplacedStruct_t const &unplaced,
1338                                                                               Vector3D<Precision> const &point,
1339                                                                               int phiIndex, bool pointInside)
1340 {
1341   // In the cutout, independent radial/phi bounds can vanish far from the
1342   // finite solid. Use the cut faces and their neighboring inner facets.
1343   // For a wide facet, use its finite inner/outer faces instead. The helper
1344   // also includes same-z edges; cap interiors matter only outside the cutout.
1345   Precision result = InfinityLength<Precision>();
1346   const int nz     = unplaced.fZSegments.size();
1347   const int start  = Max(0, Min(nz - 1, FindZSegment(unplaced, point.z())));
1348   // Visit the nearest z segment first, then stop each sweep when its axial
1349   // gap exceeds the best finite-face distance already found.
1350   for (int iz = start; iz < nz; ++iz) {
1351     // The helper writes the closest facet index. Each new z segment must
1352     // retain the original angular classification, including cutout queries.
1353     int segmentPhi     = phiIndex, surface;
1354     result             = Min(result, SafetyToZSegmentSquared(unplaced, iz, segmentPhi, point, pointInside, surface));
1355     const Precision dz = unplaced.fZPlanes[iz + 1] - point.z();
1356     if (dz * dz > result) break;
1357   }
1358   for (int iz = start - 1; iz >= 0; --iz) {
1359     int segmentPhi     = phiIndex, surface;
1360     result             = Min(result, SafetyToZSegmentSquared(unplaced, iz, segmentPhi, point, pointInside, surface));
1361     const Precision dz = point.z() - unplaced.fZPlanes[iz];
1362     if (dz * dz > result) break;
1363   }
1364   if (phiIndex >= 0) {
1365     int cap;
1366     SafetyToEndcapsSquared(unplaced, point, result, cap);
1367   }
1368   return result;
1369 }
1370 
1371 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
1372 template <typename Real_v>
1373 VECCORE_ATT_HOST_DEVICE void PolyhedronImplementation<innerRadiiT, phiCutoutT>::SafetyToIn(
1374     UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point, Real_v &safety)
1375 {
1376   Vector3D<Precision> localPoint(point[0], point[1], point[2]);
1377 
1378   // At fixed phi, lifting an RZ distance back to the solid stretches its
1379   // radial component by at most sec(halfSideAngle). Limit that factor to
1380   // sqrt(2); wider facets use finite-face distances below.
1381   const bool useOutline = unplaced.fPhiDelta <= Precision(unplaced.fSideCount) * kHalfPi;
1382   if (!unplaced.fHasPhiCutout && useOutline) {
1383     bool wrongSide;
1384     const Precision result = SafetyToRZOutlineSquared(unplaced, localPoint, Precision(1.), wrongSide);
1385     safety                 = wrongSide ? Real_v(-1.) : Sqrt(result);
1386     return;
1387   }
1388 
1389   const int phiIndex = FindPhiSegment<Precision>(unplaced, localPoint);
1390   if (phiIndex >= 0) {
1391     // Phi is inside the wedge: the same-phi RZ-outline projection is a
1392     // valid conservative bound here too (see SafetyToRZOutlineSquared's
1393     // own doc comment) -- exact away from a facet's own angular edge, and
1394     // never an overestimate near one, confirmed both analytically and by
1395     // the same large-scale differential testing as the no-cutout path.
1396     bool wrongSide;
1397     const Precision result = SafetyToRZOutlineSquared(unplaced, localPoint, Precision(1.), wrongSide);
1398     if (wrongSide || useOutline) {
1399       safety = wrongSide ? Real_v(-1.) : Sqrt(result);
1400       return;
1401     }
1402   }
1403 
1404   safety = Sqrt(SafetyToFiniteFacesSquared(unplaced, localPoint, phiIndex, false));
1405 }
1406 
1407 template <Polyhedron::EInnerRadii innerRadiiT, Polyhedron::EPhiCutout phiCutoutT>
1408 template <typename Real_v>
1409 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void PolyhedronImplementation<innerRadiiT, phiCutoutT>::SafetyToOut(
1410     UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point, Real_v &safety)
1411 {
1412   Vector3D<Precision> localPoint(point[0], point[1], point[2]);
1413 
1414   const bool useOutline = unplaced.fPhiDelta <= Precision(unplaced.fSideCount) * kHalfPi;
1415   if (!unplaced.fHasPhiCutout) {
1416     bool wrongSide;
1417     Precision result = SafetyToRZOutlineSquared(unplaced, localPoint, Precision(-1.), wrongSide);
1418     if (!wrongSide && !useOutline)
1419       result = SafetyToFiniteFacesSquared(unplaced, localPoint, FindPhiSegment(unplaced, localPoint), true);
1420     safety = wrongSide ? Real_v(-1.) : Sqrt(result);
1421     return;
1422   }
1423 
1424   // C = F (the phi-closed profile) intersected with the phi wedge W. A point
1425   // actually inside C always has phi inside W, so leaving C happens at the
1426   // first moment of leaving either F or W: SafetyToOut(C,p) exactly equals
1427   // min(SafetyToOut(F,p), SafetyToOut(W,p)). The F term uses the same
1428   // same-phi RZ-outline projection as SafetyToIn above -- a valid
1429   // conservative bound (never an overestimate here, i.e. never reports more
1430   // than the true remaining distance to the boundary), confirmed by the
1431   // same large-scale differential testing as the no-cutout path.
1432   //
1433   // Signed wedge safety also rejects the wrong phi region. The outline
1434   // projection below needs no facet index; only the finite-face fallback does.
1435   const Precision phiSafety = unplaced.fPhiWedge.SafetyToOut(localPoint);
1436   if (phiSafety < Precision(0.)) {
1437     safety = Real_v(-1.);
1438     return;
1439   }
1440   bool rzWrongSide;
1441   const Precision rzResult = SafetyToRZOutlineSquared(unplaced, localPoint, Precision(-1.), rzWrongSide);
1442   if (rzWrongSide) {
1443     safety = Real_v(-1.);
1444     return;
1445   }
1446   if (!useOutline) {
1447     const int phiIndex = FindPhiSegment<Precision>(unplaced, localPoint);
1448     if (phiIndex < 0) {
1449       safety = Real_v(-1.);
1450       return;
1451     }
1452     safety = Sqrt(SafetyToFiniteFacesSquared(unplaced, localPoint, phiIndex, true));
1453     return;
1454   }
1455   Precision phiSquared = phiSafety * phiSafety;
1456   // A reflex wedge has rays, not complete planes, as its boundary. Behind
1457   // both rays the closest boundary is the axis; plane distances can vanish
1458   // there as the excluded angle shrinks, despite a finite radial distance.
1459   if (unplaced.fPhiDelta > kPi && localPoint.Dot(unplaced.fPhiWedge.GetAlong1()) < 0. &&
1460       localPoint.Dot(unplaced.fPhiWedge.GetAlong2()) < 0.)
1461     phiSquared = localPoint.Perp2();
1462   safety = Sqrt(Min(rzResult, phiSquared));
1463 }
1464 
1465 } // namespace VECGEOM_IMPL_NAMESPACE
1466 } // namespace vecgeom
1467 
1468 #endif // VECGEOM_VOLUMES_KERNEL_POLYHEDRONIMPLEMENTATION_H_