Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-22 09:10:20

0001 /// @file TubeImplementation.h
0002 /// @author Georgios Bitzes (georgios.bitzes@cern.ch)
0003 
0004 #ifndef VECGEOM_VOLUMES_KERNEL_TUBEIMPLEMENTATION_H_
0005 #define VECGEOM_VOLUMES_KERNEL_TUBEIMPLEMENTATION_H_
0006 
0007 #include "VecGeom/base/Vector3D.h"
0008 #include "VecGeom/volumes/kernel/GenericKernels.h"
0009 #include "VecGeom/volumes/kernel/shapetypes/TubeTypes.h"
0010 #include "VecGeom/volumes/TubeStruct.h"
0011 #include "VecGeom/volumes/Wedge.h"
0012 #include <cstdio>
0013 
0014 #define TUBE_SAFETY_OLD // use old (and faster) definitions of SafetyToIn() and
0015                         // SafetyToOut()
0016 
0017 namespace vecgeom {
0018 
0019 VECGEOM_DEVICE_DECLARE_CONV_TEMPLATE(struct, TubeImplementation, typename);
0020 
0021 inline namespace VECGEOM_IMPL_NAMESPACE {
0022 
0023 namespace TubeUtilities {
0024 
0025 /**
0026  * Returns whether a point is inside a cylindrical sector, as defined
0027  * by the two vectors that go along the endpoints of the sector
0028  *
0029  * The same could be achieved using atan2 to calculate the angle formed
0030  * by the point, the origin and the X-axes, but this is a lot faster,
0031  * using only multiplications and comparisons
0032  *
0033  * (-x*starty + y*startx) >= 0: calculates whether going from the start vector to the point
0034  * we are traveling in the CCW direction (taking the shortest direction, of course)
0035  *
0036  * (-endx*y + endy*x) >= 0: calculates whether going from the point to the end vector
0037  * we are traveling in the CCW direction (taking the shortest direction, of course)
0038  *
0039  * For a sector smaller than pi, we need that BOTH of them hold true - if going from start, to the
0040  * point, and then to the end we are travelling in CCW, it's obvious the point is inside the
0041  * cylindrical sector.
0042  *
0043  * For a sector bigger than pi, only one of the conditions needs to be true. This is less obvious why.
0044  * Since the sector angle is greater than pi, it can be that one of the two vectors might be
0045  * farther than pi away from the point. In that case, the shortest direction will be CW, so even
0046  * if the point is inside, only one of the two conditions need to hold.
0047  *
0048  * If going from start to point is CCW, then certainly the point is inside as the sector
0049  * is larger than pi.
0050  *
0051  * If going from point to end is CCW, again, the point is certainly inside.
0052  *
0053  * This function is a frankensteinian creature that can determine which of the two cases (smaller vs
0054  * larger than pi) to use either at compile time (if it has enough information, saving an if
0055  * statement) or at runtime.
0056  **/
0057 
0058 template <typename Real_v, typename ShapeType, typename UnplacedVolumeType, bool onSurfaceT, bool includeSurface = true>
0059 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void PointInCyclicalSector(UnplacedVolumeType const &volume,
0060                                                                         Real_v const &x, Real_v const &y,
0061                                                                         typename vecCore::Mask_v<Real_v> &ret)
0062 {
0063   using namespace ::vecgeom::TubeTypes;
0064   // VECGEOM_VALIDATE(SectorType<ShapeType>::value != kNoAngle, << "ShapeType without a
0065   // sector passed to PointInCyclicalSector");
0066 
0067   Real_v startx(volume.fAlongPhi1x);
0068   Real_v starty(volume.fAlongPhi1y);
0069 
0070   Real_v endx(volume.fAlongPhi2x);
0071   Real_v endy(volume.fAlongPhi2y);
0072 
0073   bool smallerthanpi;
0074 
0075   if (SectorType<ShapeType>::value == kUnknownAngle)
0076     smallerthanpi = volume.fDphi <= M_PI;
0077   else
0078     smallerthanpi = SectorType<ShapeType>::value == kOnePi || SectorType<ShapeType>::value == kSmallerThanPi;
0079 
0080   Real_v startCheck = (-x * starty + y * startx);
0081   Real_v endCheck   = (-endx * y + endy * x);
0082 
0083   if (onSurfaceT) {
0084     // in this case, includeSurface is irrelevant
0085     ret = (Abs(startCheck) <= kHalfTolerance) || (Abs(endCheck) <= kHalfTolerance);
0086   } else {
0087     if (smallerthanpi) {
0088       if (includeSurface)
0089         ret = (startCheck >= -kHalfTolerance) & (endCheck >= -kHalfTolerance);
0090       else
0091         ret = (startCheck >= kHalfTolerance) & (endCheck >= kHalfTolerance);
0092     } else {
0093       if (includeSurface)
0094         ret = (startCheck >= -kHalfTolerance) || (endCheck >= -kHalfTolerance);
0095       else
0096         ret = (startCheck >= kHalfTolerance) || (endCheck >= kHalfTolerance);
0097     }
0098   }
0099 }
0100 
0101 template <typename Real_v, typename UnplacedStruct_t, typename TubeType, bool LargestSolution, bool insectorCheck>
0102 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void CircleTrajectoryIntersection(
0103     Real_v const &b, Real_v const &c, UnplacedStruct_t const &tube, Vector3D<Real_v> const &pos,
0104     Vector3D<Real_v> const &dir, Real_v &dist, typename vecCore::Mask_v<Real_v> &ok)
0105 {
0106   using namespace ::vecgeom::TubeTypes;
0107 
0108   using Bool_v = vecCore::Mask_v<Real_v>;
0109 
0110   Real_v delta = b * b - c;
0111   ok           = delta > Real_v(0.);
0112   if (LargestSolution) ok |= delta == Real_v(0.); // this takes care of scratching conventions
0113 
0114   vecCore::MaskedAssign(delta, !ok, Real_v(0.));
0115   delta = Sqrt(delta);
0116   if (!LargestSolution) delta = -delta;
0117 
0118   dist = -b + delta;
0119   // ok &= vecCore::math::Abs(dist) <= kTolerance;
0120   // vecCore::MaskedAssign(dist,ok,Real_v(0.));
0121   // A.G There may be points propagated to Rmax+tolerance which get here and NEED to se a valid negative crossing
0122   // at distance > tolerance, so we need to enlarge the tolerance
0123   ok &= dist >= -2 * kTolerance;
0124   if (vecCore::EarlyReturnAllowed() && vecCore::MaskEmpty(ok)) return;
0125 
0126   if (insectorCheck) {
0127     /* if dist > 100*tube.fRmax, then instead of solving quadratic again,
0128     ** use Newton method to recalculate the root, taking previous distance
0129     ** as initial guess for newton method.
0130     **
0131     ** Commenting the code for root recalculation, coz  that sometimes overfits
0132     ** and ShapeTester starts complaining for TestAccuracyDistanceToIn tests
0133     ** The condition is handled in DistanceToIn itself.
0134     **
0135     ** Still keeping the code in comments for reference.
0136     **
0137     ** Real_v x(0.), y(0.);
0138     ** vecCore::MaskedAssign(x, dist > 100 * tube.fRmax, pos.x() + dist * dir.x());
0139     ** vecCore::MaskedAssign(y, dist > 100 * tube.fRmax, pos.y() + dist * dir.y());
0140     ** vecCore::MaskedAssign(dist, dist > 100 * tube.fRmax,
0141     **      dist - (x * x + y * y - tube.fRmax2) * 0.5 / NonZero(dir.x() * x + dir.y() * y));
0142     */
0143 
0144     Real_v hitz = pos.z() + dist * dir.z();
0145     ok &= (Abs(hitz) <= tube.fZ);
0146     if (vecCore::EarlyReturnAllowed() && vecCore::MaskEmpty(ok)) return;
0147 
0148     if (checkPhiTreatment<TubeType>(tube)) {
0149       Bool_v insector(false);
0150       Real_v hitx = pos.x() + dist * dir.x();
0151       Real_v hity = pos.y() + dist * dir.y();
0152       PointInCyclicalSector<Real_v, TubeType, UnplacedStruct_t, false, true>(tube, hitx, hity, insector);
0153       // insector = tube.fPhiWedge.ContainsWithBoundary<Real_v>(
0154       // Vector3D<Real_v>(hitx, hity, hitz) );
0155       ok &= insector;
0156     }
0157   }
0158 }
0159 
0160 /*
0161  * Input: A point p and a unit vector v.
0162  * Returns the perpendicular distance between
0163  * (the infinite line defined by the vector)
0164  * and (the point)
0165  *
0166  * How does it work? Let phi be the angle formed
0167  * by v and the position vector of the point.
0168  *
0169  * Let proj be the projection vector of point onto that
0170  * line.
0171  *
0172  * We now have a right triangle formed by points
0173  * (0, 0), point and the projected point
0174  *
0175  * For that triangle, it holds that
0176  * sin theta = perpendiculardistance / (magnitude of point vector)
0177  *
0178  * So perpendiculardistance = sin theta * (magnitude of point vector)
0179  *
0180  * But.. the magnitude of the cross product between the point vector
0181  * and the v vector is:
0182  *
0183  * |p x v| = |p| * |v| * sin theta
0184  *
0185  * Since |v| = 1, the magnitude of the cross product is exactly
0186  * what we're looking for, the formula for which is simply
0187  * p.x * v.y - p.y * v.x
0188  *
0189  */
0190 
0191 template <typename Real_v>
0192 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE Real_v PerpDist2D(Real_v const &px, Real_v const &py, Real_v const &vx,
0193                                                                Real_v const &vy)
0194 {
0195   return px * vy - py * vx;
0196 }
0197 
0198 /*
0199  * Find safety distance from a point to the phi plane
0200  */
0201 template <typename Real_v, typename UnplacedStruct_t, typename TubeType, bool inside>
0202 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void PhiPlaneSafety(UnplacedStruct_t const &tube,
0203                                                                  Vector3D<Real_v> const &pos, Real_v &safety)
0204 {
0205   using namespace ::vecgeom::TubeTypes;
0206 
0207   if ((SectorType<TubeType>::value == kUnknownAngle && tube.fDphi > M_PI) ||
0208       (SectorType<TubeType>::value == kBiggerThanPi)) {
0209     safety = Sqrt(pos.x() * pos.x() + pos.y() * pos.y());
0210   } else {
0211     safety = kInfLength;
0212   }
0213 
0214   Real_v phi1 = PerpDist2D<Real_v>(pos.x(), pos.y(), Real_v(tube.fAlongPhi1x), Real_v(tube.fAlongPhi1y));
0215   if (inside) phi1 *= -1;
0216 
0217   if (SectorType<TubeType>::value == kOnePi) {
0218     auto absphi1 = Abs(phi1);
0219     vecCore::MaskedAssign(safety, absphi1 > kHalfTolerance, absphi1);
0220     return;
0221   }
0222 
0223   // make sure point falls on positive part of projection
0224   vecCore::MaskedAssign(safety,
0225                         phi1 > -kHalfTolerance &&
0226                             /*pos.x() * tube.fAlongPhi1x + pos.y() * tube.fAlongPhi1y > 0. &&*/ phi1 < safety,
0227                         phi1);
0228 
0229   Real_v phi2 = PerpDist2D<Real_v>(pos.x(), pos.y(), Real_v(tube.fAlongPhi2x), Real_v(tube.fAlongPhi2y));
0230   if (!inside) phi2 *= -1;
0231 
0232   // make sure point falls on positive part of projection
0233   vecCore::MaskedAssign(safety,
0234                         phi2 > -kHalfTolerance &&
0235                             /*pos.x() * tube.fAlongPhi2x + pos.y() * tube.fAlongPhi2y > 0. &&*/ phi2 < safety,
0236                         phi2);
0237 }
0238 
0239 /*
0240  * Check intersection of the trajectory with a phi-plane
0241  * All points of the along-vector of a phi plane lie on
0242  * s * (alongX, alongY)
0243  * All points of the trajectory of the particle lie on
0244  * (x, y) + t * (vx, vy)
0245  * Thefore, it must hold that s * (alongX, alongY) == (x, y) + t * (vx, vy)
0246  * Solving by t we get t = (alongY*x - alongX*y) / (vy*alongX - vx*alongY)
0247  * s = (x + t*vx) / alongX = (newx) / alongX
0248  *
0249  * If we have two non colinear phi-planes, need to make sure
0250  * point falls on its positive direction <=> dot product between
0251  * along vector and hit-point is positive <=> hitx*alongX + hity*alongY > 0
0252  */
0253 
0254 template <typename Real_v, typename UnplacedStruct_t, typename TubeType, bool PositiveDirectionOfPhiVector,
0255           bool insectorCheck>
0256 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void PhiPlaneTrajectoryIntersection(
0257     Precision alongX, Precision alongY, Precision normalX, Precision normalY, UnplacedStruct_t const &tube,
0258     Vector3D<Real_v> const &pos, Vector3D<Real_v> const &dir, Real_v &dist, typename vecCore::Mask_v<Real_v> &ok)
0259 {
0260 
0261   dist = kInfLength;
0262 
0263   // approaching phi plane from the right side?
0264   // this depends whether we use it for DistanceToIn or DistanceToOut
0265   // Note: wedge normals poing towards the wedge inside, by convention!
0266   Real_v dirDotNorm = dir.x() * normalX + dir.y() * normalY;
0267   if (insectorCheck)
0268     ok = (dirDotNorm > Real_v(0.)); // DistToIn  -- require tracks entering volume
0269   else
0270     ok = (dirDotNorm < Real_v(0.)); // DistToOut -- require tracks leaving volume
0271 
0272   // if( vecCore::EarlyReturnAllowed() && vecCore::MaskEmpty(ok) ) return;
0273 
0274   Real_v dirDotXY = (dir.y() * alongX - dir.x() * alongY);
0275   dist            = (alongY * pos.x() - alongX * pos.y()) / NonZero(dirDotXY);
0276   // A.G to check validity, we have to compare with tolerance the safety rather than the distance to plane
0277   ok &= (dist * Abs(dirDotNorm)) > -kHalfTolerance;
0278   // if( vecCore::EarlyReturnAllowed() && vecCore::MaskEmpty(ok) ) return;
0279 
0280   if (insectorCheck) {
0281     Real_v hitx = pos.x() + dist * dir.x();
0282     Real_v hity = pos.y() + dist * dir.y();
0283     Real_v hitz = pos.z() + dist * dir.z();
0284     Real_v r2   = hitx * hitx + hity * hity;
0285     ok &= Abs(hitz) <= tube.fTolOz && (r2 >= tube.fTolOrmin2) && (r2 <= tube.fTolOrmax2);
0286 
0287     // GL: tested with this if(PosDirPhiVec) around if(insector), so
0288     // if(insector){} requires PosDirPhiVec==true to run
0289     //  --> shapeTester still finishes OK (no mismatches) (some cycles saved...)
0290     if (PositiveDirectionOfPhiVector) {
0291       ok = ok && (hitx * alongX + hity * alongY) > Real_v(0.);
0292     }
0293   } else {
0294     if (PositiveDirectionOfPhiVector) {
0295       Real_v hitx = pos.x() + dist * dir.x();
0296       Real_v hity = pos.y() + dist * dir.y();
0297       ok          = ok && (hitx * alongX + hity * alongY) >= Real_v(0.);
0298     }
0299   }
0300 }
0301 
0302 template <typename Real_v, typename UnplacedStruct_t, bool ForInnerSurface>
0303 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE typename vecCore::Mask_v<Real_v> IsOnTubeSurface(
0304     UnplacedStruct_t const &tube, Vector3D<Real_v> const &point)
0305 {
0306   const Real_v rho = point.Perp2();
0307   if (ForInnerSurface) {
0308     return (rho >= tube.fTolOrmin2) && (rho <= tube.fTolIrmin2) && (Abs(point.z()) < (tube.fZ + kTolerance));
0309   } else {
0310     return (rho >= tube.fTolIrmax2) && (rho <= tube.fTolOrmax2) && (Abs(point.z()) < (tube.fZ + kTolerance));
0311   }
0312 }
0313 
0314 template <typename Real_v, bool ForInnerSurface>
0315 VECCORE_ATT_HOST_DEVICE Vector3D<Real_v> GetNormal(Vector3D<Real_v> const &point)
0316 {
0317   Vector3D<Real_v> norm(0., 0., 0.);
0318   if (ForInnerSurface) {
0319     norm.Set(-point.x(), -point.y(), 0.);
0320   } else {
0321     norm.Set(point.x(), point.y(), 0.);
0322   }
0323   return norm;
0324 }
0325 
0326 template <typename Real_v, typename UnplacedStruct_t, bool ForInnerSurface>
0327 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE typename vecCore::Mask_v<Real_v> IsMovingInsideTubeSurface(
0328     UnplacedStruct_t const &tube, Vector3D<Real_v> const &point, Vector3D<Real_v> const &direction)
0329 {
0330   return IsOnTubeSurface<Real_v, UnplacedStruct_t, ForInnerSurface>(tube, point) &&
0331          (direction.Dot(GetNormal<Real_v, ForInnerSurface>(point)) < -0.5 * int(!ForInnerSurface) * kTolerance);
0332 }
0333 
0334 } // namespace TubeUtilities
0335 
0336 template <typename T>
0337 class SPlacedTube;
0338 template <typename T>
0339 class SUnplacedTube;
0340 template <typename tubeTypeT>
0341 struct TubeImplementation {
0342 
0343   using UnplacedStruct_t = ::vecgeom::TubeStruct<Precision>;
0344   using UnplacedVolume_t = SUnplacedTube<tubeTypeT>;
0345   using PlacedShape_t    = SPlacedTube<UnplacedVolume_t>;
0346 
0347   /////GenericKernel Contains/Inside implementation
0348   template <typename Real_v, bool ForInside>
0349   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void GenericKernelForContainsAndInside(
0350       UnplacedStruct_t const &tube, Vector3D<Real_v> const &point, typename vecCore::Mask_v<Real_v> &completelyinside,
0351       typename vecCore::Mask_v<Real_v> &completelyoutside)
0352   {
0353     using namespace ::vecgeom::TubeTypes;
0354     using Bool_v = vecCore::Mask_v<Real_v>;
0355 
0356     // very fast check on z-height
0357     Real_v absz       = Abs(point[2]);
0358     completelyoutside = absz > MakePlusTolerant<true>(tube.fZ);
0359     if (ForInside) {
0360       completelyinside = absz < MakeMinusTolerant<true>(tube.fZ);
0361     }
0362     if (vecCore::EarlyReturnAllowed()) {
0363       if (vecCore::MaskFull(completelyoutside)) {
0364         return;
0365       }
0366     }
0367 
0368     // check on RMAX
0369     Real_v r2 = point.x() * point.x() + point.y() * point.y();
0370     // calculate cone radius at the z-height of position
0371 
0372     completelyoutside |= r2 > MakePlusTolerantSquare<true>(tube.fRmax);
0373     if (ForInside) {
0374       completelyinside &= r2 < MakeMinusTolerantSquare<true>(tube.fRmax);
0375     }
0376     if (vecCore::EarlyReturnAllowed()) {
0377       if (vecCore::MaskFull(completelyoutside)) {
0378         return;
0379       }
0380     }
0381 
0382     // check on RMIN
0383     if (checkRminTreatment<tubeTypeT>(tube)) {
0384       completelyoutside |= r2 <= MakeMinusTolerantSquare<true>(tube.fRmin);
0385       if (ForInside) {
0386         completelyinside &= r2 > MakePlusTolerantSquare<true>(tube.fRmin);
0387       }
0388       if (vecCore::EarlyReturnAllowed()) {
0389         if (vecCore::MaskFull(completelyoutside)) {
0390           return;
0391         }
0392       }
0393     }
0394 
0395     if (checkPhiTreatment<tubeTypeT>(tube)) {
0396       Bool_v completelyoutsidephi(false);
0397       Bool_v completelyinsidephi(false);
0398       tube.fPhiWedge.GenericKernelForContainsAndInside<Real_v, true>(point, completelyinsidephi, completelyoutsidephi);
0399 
0400       completelyoutside |= completelyoutsidephi;
0401       if (ForInside) completelyinside &= completelyinsidephi;
0402     }
0403   }
0404 
0405   template <typename Real_v>
0406   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void Contains(UnplacedStruct_t const &tube,
0407                                                                     Vector3D<Real_v> const &point,
0408                                                                     typename vecCore::Mask_v<Real_v> &contains)
0409   {
0410     using Bool_v = vecCore::Mask_v<Real_v>;
0411     Bool_v unused, outside;
0412     GenericKernelForContainsAndInside<Real_v, false>(tube, point, unused, outside);
0413     contains = !outside;
0414   }
0415 
0416   template <typename Real_v, typename Inside_t>
0417   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void Inside(UnplacedStruct_t const &tube,
0418                                                                   Vector3D<Real_v> const &point, Inside_t &inside)
0419   {
0420     using Bool_v       = vecCore::Mask_v<Real_v>;
0421     using InsideBool_v = vecCore::Mask_v<Inside_t>;
0422     Bool_v completelyinside, completelyoutside;
0423     GenericKernelForContainsAndInside<Real_v, true>(tube, point, completelyinside, completelyoutside);
0424     inside = EInside::kSurface;
0425     vecCore::MaskedAssign(inside, (InsideBool_v)completelyoutside, Inside_t(EInside::kOutside));
0426     vecCore::MaskedAssign(inside, (InsideBool_v)completelyinside, Inside_t(EInside::kInside));
0427   }
0428 
0429   template <typename Real_v>
0430   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void DistanceToIn(UnplacedStruct_t const &tube,
0431                                                                         Vector3D<Real_v> const &pointt,
0432                                                                         Vector3D<Real_v> const &dir,
0433                                                                         Real_v const &stepMax, Real_v &distance)
0434   {
0435     Vector3D<Real_v> point = pointt;
0436     Real_v ptDist          = point.Mag();
0437     Real_v distToMove(0.);
0438     using Bool_v    = vecCore::Mask_v<Real_v>;
0439     Precision order = 100.;
0440     Bool_v cond     = (ptDist > order * tube.fMaxVal);
0441     /* if the point is at a distance (DIST) of more than 100 times of the maximum dimension
0442      * (of the shape) from the origin of shape, then before calculating distance, first
0443      * manually move the point with distance ( distToMove = DIST-100.*maxDim) along the
0444      * direction, and then calculate DistanceToIn of new moved point using DistanceToInKernel,
0445      *
0446      * The final distance will be (distToMove + DistanceToIn),
0447      *
0448      * This logic no longer requires the recalculation of the roots using Newton method in
0449      * CircleTrajectoryIntersection function, and will also give consistent results with
0450      * ShapeTester
0451      */
0452     vecCore__MaskedAssignFunc(distToMove, cond, (ptDist - Real_v(order * tube.fMaxVal)));
0453     vecCore__MaskedAssignFunc(point, cond, point + distToMove * dir);
0454     DistanceToInKernel<Real_v>(tube, point, dir, stepMax, distance);
0455     distance += distToMove;
0456   }
0457 
0458   template <typename Real_v>
0459   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void DistanceToInKernel(UnplacedStruct_t const &tube,
0460                                                                               Vector3D<Real_v> const &point,
0461                                                                               Vector3D<Real_v> const &dir,
0462                                                                               Real_v const &stepMax, Real_v &distance)
0463   {
0464     (void)stepMax;
0465     using namespace TubeUtilities;
0466     using namespace ::vecgeom::TubeTypes;
0467 
0468     using Bool_v = vecCore::Mask_v<Real_v>;
0469 
0470     Bool_v done(false);
0471 
0472     //=== First, for points outside and moving away --> return infinity
0473     distance = kInfLength;
0474 
0475     // outside of Z range and going away?
0476     Real_v distz = Abs(point.z()) - tube.fZ; // avoid a division for now
0477     done |= distz > kHalfTolerance && point.z() * dir.z() >= 0;
0478 
0479     // // outside of tube and going away?
0480     // done |= Abs(point.x()) > tube.rmax()+kHalfTolerance && point.x()*dir.x()
0481     // >= 0;
0482     // done |= Abs(point.y()) > tube.rmax()+kHalfTolerance && point.y()*dir.y()
0483     // >= 0;
0484     // if(vecCore::EarlyReturnAllowed() && vecCore::MaskFull(done)) return;
0485 
0486     // outside of outer tube and going away?
0487     Real_v rsq   = point.x() * point.x() + point.y() * point.y();
0488     Real_v rdotn = point.x() * dir.x() + point.y() * dir.y();
0489     done |= rsq > tube.fTolIrmax2 && rdotn >= 0;
0490     if (vecCore::EarlyReturnAllowed() && vecCore::MaskFull(done)) return;
0491 
0492     //=== Next, check all dimensions of the tube, whether points are inside -->
0493     // return -1
0494     vecCore__MaskedAssignFunc(distance, !done, Real_v(-1.0));
0495 
0496     // For points inside z-range, return -1
0497     Bool_v inside = distz < -kHalfTolerance;
0498 
0499     inside &= rsq < tube.fTolIrmax2;
0500     if (checkRminTreatment<tubeTypeT>(tube)) {
0501       inside &= rsq > tube.fTolIrmin2;
0502     }
0503     if (checkPhiTreatment<tubeTypeT>(tube) && !vecCore::MaskEmpty(inside)) {
0504       Bool_v insector;
0505       PointInCyclicalSector<Real_v, tubeTypeT, UnplacedStruct_t, false, false>(tube, point.x(), point.y(), insector);
0506       inside &= insector;
0507       // inside &= tube.fPhiWedge.ContainsWithoutBoundary<Real_v>( point );  //
0508       // slower than PointInCyclicalSector()
0509     }
0510     done |= inside;
0511     if (vecCore::EarlyReturnAllowed() && vecCore::MaskFull(done)) return;
0512 
0513     //=== Next step: check if z-plane is the right entry point (both r,phi
0514     // should be valid at z-plane crossing)
0515     vecCore::MaskedAssign(distance, !done, Real_v(kInfLength));
0516 
0517     distz /= NonZeroAbs(dir.z());
0518     // std::cerr << "Dist : " << distz << std::endl;
0519 
0520     Real_v hitx = point.x() + distz * dir.x();
0521     Real_v hity = point.y() + distz * dir.y();
0522     Real_v r2   = hitx * hitx + hity * hity; // radius of intersection with z-plane
0523     Bool_v okz  = distz > -kHalfTolerance && (point.z() * dir.z() < 0);
0524 
0525     okz &= (r2 <= tube.fRmax2);
0526     if (checkRminTreatment<tubeTypeT>(tube)) {
0527       okz &= (tube.fRmin2 <= r2);
0528     }
0529     if (checkPhiTreatment<tubeTypeT>(tube) && !vecCore::MaskEmpty(okz)) {
0530       Bool_v insector;
0531       PointInCyclicalSector<Real_v, tubeTypeT, UnplacedStruct_t, false>(tube, hitx, hity, insector);
0532       okz &= insector;
0533       // okz &= tube.fPhiWedge.ContainsWithBoundary<Real_v>(
0534       // Vector3D<Real_v>(hitx, hity, 0.0) );
0535     }
0536     vecCore::MaskedAssign(distance, !done && okz, distz);
0537     done |= okz;
0538 
0539     // point on outer cyl?
0540     Bool_v isOnSurface   = IsOnTubeSurface<Real_v, UnplacedStruct_t, false>(tube, point);
0541     Bool_v movingInsideR = dir.Dot(GetNormal<Real_v, false>(point)) < -0.5 * kTolerance;
0542     done |= isOnSurface && !movingInsideR;
0543     vecCore::MaskedAssign(distance, isOnSurface && !movingInsideR, kInfLength);
0544     if (vecCore::MaskFull(done)) return;
0545 
0546     Bool_v isOnSurfaceAndMovingInside = isOnSurface && movingInsideR;
0547     if (checkRminTreatment<tubeTypeT>(tube)) {
0548       // point on inner cyl?
0549       isOnSurfaceAndMovingInside |= IsMovingInsideTubeSurface<Real_v, UnplacedStruct_t, true>(tube, point, dir);
0550     }
0551 
0552     if (!checkPhiTreatment<tubeTypeT>(tube)) {
0553       vecCore__MaskedAssignFunc(distance, !done && isOnSurfaceAndMovingInside, Real_v(0.));
0554       done |= isOnSurfaceAndMovingInside;
0555       if (vecCore::MaskFull(done)) return;
0556     } else {
0557       Bool_v insector(false);
0558       PointInCyclicalSector<Real_v, tubeTypeT, UnplacedStruct_t, false>(tube, point.x(), point.y(), insector);
0559       vecCore__MaskedAssignFunc(distance, !done && insector && isOnSurfaceAndMovingInside, Real_v(0.));
0560       done |= (insector && isOnSurfaceAndMovingInside);
0561       if (vecCore::MaskFull(done)) return;
0562     }
0563 
0564     // std::cerr << "distance : " << distance << std::endl;
0565     // if(vecCore::EarlyReturnAllowed() && vecCore::MaskFull(done) ) return;
0566 
0567     //=== Next step: intersection of the trajectories with the two circles
0568 
0569     // Here for values used in both rmin and rmax calculations
0570     Real_v invnsq = Real_v(1.) / NonZero(Real_v(1.) - dir.z() * dir.z());
0571     Real_v b      = invnsq * rdotn;
0572 
0573     /*
0574      * rmax
0575      * If the particle were to hit rmax, it would hit the closest point of the
0576      * two
0577      * --> only consider the smallest solution of the quadratic equation
0578      */
0579     Real_v crmax = invnsq * (rsq - tube.fRmax2);
0580     Real_v dist_rmax;
0581     Bool_v ok_rmax(false);
0582     CircleTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, false, true>(b, crmax, tube, point, dir,
0583                                                                                    dist_rmax, ok_rmax);
0584     ok_rmax &= dist_rmax < distance;
0585     vecCore::MaskedAssign(distance, !done && ok_rmax, dist_rmax);
0586     done |= ok_rmax;
0587     if (vecCore::EarlyReturnAllowed() && vecCore::MaskFull(done)) return;
0588 
0589     /*
0590      * rmin
0591      * If the particle were to hit rmin, it would hit the farthest point of the
0592      * two
0593      * --> only consider the largest solution to the quadratic equation
0594      */
0595     Real_v dist_rmin(-kInfLength);
0596     Bool_v ok_rmin(false);
0597     if (checkRminTreatment<tubeTypeT>(tube)) {
0598       /*
0599        * What happens if both intersections are valid for the same particle?
0600        * This can only happen when particle is outside of the hollow space and
0601        * will certainly hit rmax, not rmin
0602        * So rmax solution always takes priority over rmin, and will overwrite it
0603        * in case both are valid
0604        */
0605       Real_v crmin = invnsq * (rsq - tube.fRmin2);
0606       CircleTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, true, true>(b, crmin, tube, point, dir,
0607                                                                                     dist_rmin, ok_rmin);
0608       ok_rmin &= dist_rmin < distance;
0609       vecCore::MaskedAssign(distance, !done && ok_rmin, dist_rmin);
0610       // done |= ok_rmin; // can't be done here, it's wrong in case
0611       // phi-treatment is needed!
0612     }
0613 
0614     /*
0615      * Calculate intersection between trajectory and the two phi planes
0616      */
0617     if (checkPhiTreatment<tubeTypeT>(tube)) {
0618 
0619       Real_v dist_phi;
0620       Bool_v ok_phi;
0621       auto const &w = tube.fPhiWedge;
0622       PhiPlaneTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, SectorType<tubeTypeT>::value != kOnePi, true>(
0623           tube.fAlongPhi1x, tube.fAlongPhi1y, w.GetNormal1().x(), w.GetNormal1().y(), tube, point, dir, dist_phi,
0624           ok_phi);
0625       ok_phi &= dist_phi < distance;
0626       vecCore::MaskedAssign(distance, !done && ok_phi, dist_phi);
0627       done |= ok_phi;
0628       //      if(vecCore::EarlyReturnAllowed() && vecCore::MaskFull(done))
0629       //      return;
0630 
0631       /*
0632        * If the tube is pi degrees, there's just one phi plane,
0633        * so no need to check again
0634        */
0635 
0636       if (SectorType<tubeTypeT>::value != kOnePi) {
0637         PhiPlaneTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, true, true>(
0638             tube.fAlongPhi2x, tube.fAlongPhi2y, w.GetNormal2().x(), w.GetNormal2().y(), tube, point, dir, dist_phi,
0639             ok_phi);
0640         vecCore::MaskedAssign(distance, ok_phi && dist_phi < distance, dist_phi);
0641       }
0642     }
0643   } // end of DistanceToIn()
0644 
0645   template <typename Real_v>
0646   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void DistanceToOut(UnplacedStruct_t const &tube,
0647                                                                          Vector3D<Real_v> const &point,
0648                                                                          Vector3D<Real_v> const &dir,
0649                                                                          Real_v const &stepMax, Real_v &distance)
0650   {
0651     (void)stepMax;
0652     using namespace ::vecgeom::TubeTypes;
0653     using namespace TubeUtilities;
0654 
0655     using Bool_v = vecCore::Mask_v<Real_v>;
0656 
0657     distance = Real_v(-1.);
0658     Bool_v done(false);
0659 
0660     //=== First we check all dimensions of the tube, whether points are outside
0661     //--> return -1
0662 
0663     // For points outside z-range, return -1
0664     Real_v distz = tube.fZ - Abs(point.z()); // avoid a division for now
0665     done |= distz < -kHalfTolerance;         // distance is already set to -1
0666     if (vecCore::EarlyReturnAllowed() && vecCore::MaskFull(done)) return;
0667 
0668     Real_v rsq   = point.x() * point.x() + point.y() * point.y();
0669     Real_v rdotn = dir.x() * point.x() + dir.y() * point.y();
0670     Real_v crmax = rsq - tube.fRmax2; // avoid a division for now
0671     Real_v crmin = rsq;
0672 
0673     // if outside of Rmax, return -1
0674     done |= crmax > Real_v(2.0 * kTolerance * tube.fRmax);
0675     if (vecCore::EarlyReturnAllowed() && vecCore::MaskFull(done)) return;
0676 
0677     if (checkRminTreatment<tubeTypeT>(tube)) {
0678       // if point is within inner-hole of a hollow tube, it is outside of the
0679       // tube --> return -1
0680       crmin -= tube.fRmin2; // avoid a division for now
0681       done |= crmin < Real_v(-2.0 * kTolerance * tube.fRmin);
0682       if (vecCore::EarlyReturnAllowed() && vecCore::MaskFull(done)) return;
0683     }
0684 
0685     // TODO: add outside check for phi-sections here
0686     if (checkPhiTreatment<tubeTypeT>(tube)) {
0687       Bool_v completelyoutsidephi(false);
0688       Bool_v completelyinsidephi(false);
0689       tube.fPhiWedge.GenericKernelForContainsAndInside<Real_v, true>(point, completelyinsidephi, completelyoutsidephi);
0690 
0691       done |= completelyoutsidephi;
0692       if (vecCore::EarlyReturnAllowed() && vecCore::MaskFull(done)) return;
0693     }
0694     // OK, since we're here, then distance must be non-negative, and the
0695     // smallest of possible intersections
0696     vecCore::MaskedAssign(distance, !done, Real_v(kInfLength));
0697 
0698     Real_v invdirz = Real_v(1.) / NonZero(dir.z());
0699     distz          = (Sign(dir.z()) * tube.fZ - point.z()) * invdirz;
0700     vecCore__MaskedAssignFunc(distz, dir.z() < 0, (-tube.fZ - point.z()) * invdirz);
0701     vecCore::MaskedAssign(distance, !done && Abs(invdirz) < InvdirNearParallel(tube.fRmax) && distz < distance, distz);
0702 
0703     /*
0704      * Find the intersection of the trajectories with the two circles.
0705      * Here I compute values used in both rmin and rmax calculations.
0706      */
0707 
0708     Real_v invnsq = Real_v(1.) / NonZero(Real_v(1.) - dir.z() * dir.z());
0709     Real_v b      = invnsq * rdotn;
0710     // Ignore cylindrical surface crossings for directions near-parallel to Z
0711     // The upper limit matches the direction for which a point on the surface could still hit the cylinder before
0712     // hitting the Z plane
0713     bool checkTube = invnsq < tube.fZ * tube.fZ * kInvTolerance * kInvTolerance;
0714 
0715     /*
0716      * rmin
0717      */
0718 
0719     if (checkTube && checkRminTreatment<tubeTypeT>(tube)) {
0720       Real_v dist_rmin(kInfLength);
0721       Bool_v ok_rmin(false);
0722       Bool_v isOnSurface = IsOnTubeSurface<Real_v, UnplacedStruct_t, true>(tube, point);
0723       if (vecCore::MaskFull(isOnSurface)) {
0724         ok_rmin   = dir.Dot(GetNormal<Real_v, true>(point)) > 0.5 * kTolerance;
0725         dist_rmin = 0.;
0726       } else {
0727         crmin *= invnsq;
0728         CircleTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, false, false>(b, crmin, tube, point, dir,
0729                                                                                         dist_rmin, ok_rmin);
0730       }
0731       vecCore::MaskedAssign(distance, ok_rmin && dist_rmin < distance, dist_rmin);
0732     }
0733 
0734     /*
0735      * rmax
0736      */
0737 
0738     Real_v dist_rmax(kInfLength);
0739     Bool_v ok_rmax(false);
0740     if (checkTube) {
0741       crmax *= invnsq;
0742       CircleTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, true, false>(b, crmax, tube, point, dir,
0743                                                                                      dist_rmax, ok_rmax);
0744       vecCore::MaskedAssign(distance, ok_rmax && dist_rmax < distance, dist_rmax);
0745     }
0746 
0747     /* Phi planes
0748      *
0749      * OK, this is getting weird - the only time I need to
0750      * check if hit-point falls on the positive direction
0751      * of the phi-vector is when angle is bigger than PI.
0752      *
0753      * Otherwise, any distance I get from there is guaranteed to
0754      * be larger - so final result would still be correct and no need to
0755      * check it
0756      */
0757 
0758     if (checkPhiTreatment<tubeTypeT>(tube)) {
0759       Real_v dist_phi(kInfLength);
0760       Bool_v ok_phi(false);
0761 
0762       auto const &w = tube.fPhiWedge;
0763       if (SectorType<tubeTypeT>::value == kSmallerThanPi) {
0764 
0765         Precision normal1X = w.GetNormal1().x();
0766         Precision normal1Y = w.GetNormal1().y();
0767         PhiPlaneTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, false, false>(
0768             tube.fAlongPhi1x, tube.fAlongPhi1y, normal1X, normal1Y, tube, point, dir, dist_phi, ok_phi);
0769         vecCore::MaskedAssign(distance, ok_phi && dist_phi < distance, dist_phi);
0770 
0771         PhiPlaneTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, false, false>(
0772             tube.fAlongPhi2x, tube.fAlongPhi2y, w.GetNormal2().x(), w.GetNormal2().y(), tube, point, dir, dist_phi,
0773             ok_phi);
0774         vecCore::MaskedAssign(distance, ok_phi && dist_phi < distance, dist_phi);
0775       } else if (SectorType<tubeTypeT>::value == kOnePi) {
0776         PhiPlaneTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, false, false>(
0777             tube.fAlongPhi2x, tube.fAlongPhi2y, w.GetNormal2().x(), w.GetNormal2().y(), tube, point, dir, dist_phi,
0778             ok_phi);
0779         vecCore::MaskedAssign(distance, ok_phi && dist_phi < distance, dist_phi);
0780       } else {
0781         // angle bigger than pi or unknown
0782         // need to check that point falls on positive direction of phi-vectors
0783         PhiPlaneTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, true, false>(
0784             tube.fAlongPhi1x, tube.fAlongPhi1y, w.GetNormal1().x(), w.GetNormal1().y(), tube, point, dir, dist_phi,
0785             ok_phi);
0786         vecCore::MaskedAssign(distance, ok_phi && dist_phi < distance, dist_phi);
0787 
0788         PhiPlaneTrajectoryIntersection<Real_v, UnplacedStruct_t, tubeTypeT, true, false>(
0789             tube.fAlongPhi2x, tube.fAlongPhi2y, w.GetNormal2().x(), w.GetNormal2().y(), tube, point, dir, dist_phi,
0790             ok_phi);
0791         vecCore::MaskedAssign(distance, ok_phi && dist_phi < distance, dist_phi);
0792       }
0793     }
0794     return;
0795   }
0796 
0797   /// This function keeps track of both positive (outside) and negative (inside)
0798   /// distances separately
0799   template <typename Real_v>
0800   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SafetyAssign(Real_v safety, Real_v &positiveSafety,
0801                                                                         Real_v &negativeSafety)
0802   {
0803     vecCore::MaskedAssign(positiveSafety, safety >= Real_v(0.) && safety < positiveSafety, safety);
0804     vecCore::MaskedAssign(negativeSafety, safety <= Real_v(0.) && safety > negativeSafety, safety);
0805   }
0806 
0807   /** SafetyKernel finds distances from point to each face of the tube,
0808      returning
0809       largest negative distance (w.r.t. faces which point is inside of ) and
0810       smallest positive distance (w.r.t. faces which point is outside of)
0811    */
0812   template <typename Real_v>
0813   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SafetyKernel(UnplacedStruct_t const &tube,
0814                                                                         Vector3D<Real_v> const &point, Real_v &safePos,
0815                                                                         Real_v &safeNeg)
0816   {
0817 
0818     // TODO: implement caching if input point is not changed
0819     using namespace ::vecgeom::TubeTypes;
0820     using namespace TubeUtilities;
0821 
0822     safePos = kInfLength;
0823     safeNeg = -safePos; // reuse to avoid casting overhead
0824 
0825     Real_v safez = Abs(point.z()) - tube.fZ;
0826     SafetyAssign(safez, safePos, safeNeg);
0827 
0828     Real_v r        = Sqrt(point.x() * point.x() + point.y() * point.y());
0829     Real_v safermax = r - tube.fRmax;
0830     SafetyAssign(safermax, safePos, safeNeg);
0831 
0832     if (checkRminTreatment<tubeTypeT>(tube)) {
0833       Real_v safermin = tube.fRmin - r;
0834       SafetyAssign(safermin, safePos, safeNeg);
0835     }
0836 
0837     if (checkPhiTreatment<tubeTypeT>(tube)) {
0838       Real_v safephi;
0839       PhiPlaneSafety<Real_v, UnplacedStruct_t, tubeTypeT, false>(tube, point, safephi);
0840       SafetyAssign(safephi, safePos, safeNeg);
0841     }
0842   }
0843 
0844   template <typename Real_v>
0845   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SafetyToIn(UnplacedStruct_t const &tube,
0846                                                                       Vector3D<Real_v> const &point, Real_v &safety)
0847   {
0848 
0849 #ifdef TUBE_SAFETY_OLD
0850     SafetyToInOld(tube, point, safety);
0851 #else
0852     Real_v safetyInsidePoint, safetyOutsidePoint;
0853     SafetyKernel(tube, point, safetyOutsidePoint, safetyInsidePoint);
0854 
0855     // Mostly called for points outside --> safetyOutside is finite --> return
0856     // safetyOutside
0857     // If safetyOutside == infinity --> return safetyInside
0858     safety = vecCore::Blend(safetyOutsidePoint == InfinityLength<Real_v>(), safetyInsidePoint, safetyOutsidePoint);
0859 #endif
0860   }
0861 
0862   template <typename Real_v>
0863   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SafetyToOut(UnplacedStruct_t const &tube,
0864                                                                        Vector3D<Real_v> const &point, Real_v &safety)
0865   {
0866 #ifdef TUBE_SAFETY_OLD
0867     SafetyToOutOld(tube, point, safety);
0868 #else
0869     Real_v safetyInsidePoint, safetyOutsidePoint;
0870     SafetyKernel<Real_v>(tube, point, safetyOutsidePoint, safetyInsidePoint);
0871 
0872     // Mostly called for points inside --> safetyOutside==infinity, return
0873     // |safetyInside| (flip sign)
0874     // If called for points outside -- return -safetyOutside
0875     safety = -vecCore::Blend(safetyOutsidePoint == InfinityLength<Real_v>(), safetyInsidePoint, safetyOutsidePoint);
0876 #endif
0877   }
0878 
0879   template <typename Real_v>
0880   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SafetyToInOld(UnplacedStruct_t const &tube,
0881                                                                          Vector3D<Real_v> const &point, Real_v &safety)
0882   {
0883     using namespace ::vecgeom::TubeTypes;
0884     using namespace TubeUtilities;
0885 
0886     using Bool_v = vecCore::Mask_v<Real_v>;
0887 
0888     safety = Abs(point.z()) - tube.fZ;
0889 
0890     Real_v r        = Sqrt(point.x() * point.x() + point.y() * point.y());
0891     Real_v safermax = r - tube.fRmax;
0892     vecCore::MaskedAssign(safety, safermax > safety, safermax);
0893 
0894     if (checkRminTreatment<tubeTypeT>(tube)) {
0895       Real_v safermin = tube.fRmin - r;
0896       vecCore::MaskedAssign(safety, safermin > safety, safermin);
0897     }
0898 
0899     if (checkPhiTreatment<tubeTypeT>(tube)) {
0900       Bool_v insector;
0901       PointInCyclicalSector<Real_v, tubeTypeT, UnplacedStruct_t, false, false>(tube, point.x(), point.y(), insector);
0902       if (vecCore::EarlyReturnAllowed() && vecCore::MaskFull(insector)) return;
0903 
0904       Real_v safephi;
0905       PhiPlaneSafety<Real_v, UnplacedStruct_t, tubeTypeT, false>(tube, point, safephi);
0906       vecCore::MaskedAssign(safety, !insector && safephi < kInfLength && safephi > safety, safephi);
0907     }
0908   }
0909 
0910   template <typename Real_v>
0911   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SafetyToOutOld(UnplacedStruct_t const &tube,
0912                                                                           Vector3D<Real_v> const &point, Real_v &safety)
0913   {
0914     using namespace ::vecgeom::TubeTypes;
0915     using namespace TubeUtilities;
0916 
0917     safety          = tube.fZ - Abs(point.z());
0918     Real_v r        = Sqrt(point.x() * point.x() + point.y() * point.y());
0919     Real_v safermax = tube.fRmax - r;
0920     vecCore::MaskedAssign(safety, safermax < safety, safermax);
0921 
0922     if (checkRminTreatment<tubeTypeT>(tube)) {
0923       Real_v safermin = r - tube.fRmin;
0924       vecCore::MaskedAssign(safety, safermin < safety, safermin);
0925     }
0926 
0927     if (checkPhiTreatment<tubeTypeT>(tube)) {
0928       // Now using Wedge to calculate the SafetyToOut for a sector of a tube
0929       Real_v safephi = tube.fPhiWedge.SafetyToOut<Real_v>(point);
0930       vecCore::MaskedAssign(safety, safephi < safety, safephi);
0931     }
0932   }
0933 
0934   template <typename Real_v>
0935   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static Vector3D<Real_v> ApproxSurfaceNormalKernel(
0936       UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point)
0937   {
0938 
0939     Vector3D<Real_v> norm(0., 0., 0.);
0940     Real_v radius   = point.Perp();
0941     Real_v distRMax = vecCore::math::Abs(radius - unplaced.fRmax);
0942     Real_v distRMin = kInfLength;
0943     vecCore__MaskedAssignFunc(distRMax, distRMax < Real_v(0.), InfinityLength<Real_v>());
0944     if (unplaced.fRmin) {
0945       distRMin = Abs(unplaced.fRmin - radius);
0946       vecCore__MaskedAssignFunc(distRMin, distRMin < Real_v(0.), InfinityLength<Real_v>());
0947     }
0948     Real_v distMin = Min(distRMin, distRMax);
0949 
0950     Real_v distPhi1 = kInfLength, distPhi2 = kInfLength;
0951     if (unplaced.fDphi != vecgeom::kTwoPi) {
0952       distPhi1 = point.x() * unplaced.fPhiWedge.GetNormal1().x() + point.y() * unplaced.fPhiWedge.GetNormal1().y();
0953       distPhi2 = point.x() * unplaced.fPhiWedge.GetNormal2().x() + point.y() * unplaced.fPhiWedge.GetNormal2().y();
0954 
0955       vecCore__MaskedAssignFunc(distPhi1, distPhi1 < Real_v(0.), InfinityLength<Real_v>());
0956       vecCore__MaskedAssignFunc(distPhi2, distPhi2 < Real_v(0.), InfinityLength<Real_v>());
0957       distMin = Min(distMin, Min(distPhi1, distPhi2));
0958     }
0959 
0960     Real_v distZ = kInfLength;
0961     vecCore__MaskedAssignFunc(distZ, point.z() < Real_v(0.), vecCore::math::Abs(point.z() + unplaced.fZ));
0962     vecCore__MaskedAssignFunc(distZ, point.z() >= Real_v(0.), vecCore::math::Abs(point.z() - unplaced.fZ));
0963     distMin = Min(distMin, distZ);
0964 
0965     if (unplaced.fDphi) {
0966       Vector3D<Real_v> normal1 = unplaced.fPhiWedge.GetNormal1();
0967       Vector3D<Real_v> normal2 = unplaced.fPhiWedge.GetNormal2();
0968       vecCore__MaskedAssignFunc(norm, distMin == distPhi1, -normal1);
0969       vecCore__MaskedAssignFunc(norm, distMin == distPhi2, -normal2);
0970     }
0971 
0972     vecCore__MaskedAssignFunc(norm, (distMin == distZ) && (point.z() < Real_v(0.)), Vector3D<Real_v>(0., 0., -1.));
0973     vecCore__MaskedAssignFunc(norm, (distMin == distZ) && (point.z() >= Real_v(0.)), Vector3D<Real_v>(0., 0., 1.));
0974 
0975     if (vecCore::math::Abs(point.z()) < (unplaced.fZ + kTolerance)) {
0976       Vector3D<Real_v> temp = point;
0977       temp.z()              = Real_v(0.);
0978       vecCore__MaskedAssignFunc(norm, distMin == distRMax, temp.Unit());
0979       if (unplaced.fRmin) vecCore__MaskedAssignFunc(norm, distMin == distRMin, -temp.Unit());
0980     }
0981 
0982     return norm;
0983   }
0984 
0985   template <typename Real_v, typename Bool_v>
0986   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void NormalKernel(UnplacedStruct_t const &unplaced,
0987                                                                         Vector3D<Real_v> const &point,
0988                                                                         Vector3D<Real_v> &norm, Bool_v &valid)
0989   {
0990 
0991     valid = Bool_v(false);
0992     Bool_v isPointInside(false), isPointOutside(false);
0993     GenericKernelForContainsAndInside<Real_v, true>(unplaced, point, isPointInside, isPointOutside);
0994     if (isPointInside || isPointOutside) {
0995       norm = ApproxSurfaceNormalKernel<Real_v>(unplaced, point);
0996       return;
0997     }
0998 
0999     int nosurface = 0; // idea from trapezoid;; change nomenclature as confusing
1000 
1001     Precision x2y2 = Sqrt(point.x() * point.x() + point.y() * point.y());
1002     bool inZ = ((point.z() < unplaced.fZ + kTolerance) && (point.z() > -unplaced.fZ - kTolerance)); // in right z range
1003     bool inR = ((x2y2 >= unplaced.fRmin - kTolerance) && (x2y2 <= unplaced.fRmax + kTolerance));    // in right r range
1004     // bool inPhi = fWedge.Contains(point);
1005     // can we combine these two into one??
1006     if (inR && (Abs(point.z() - unplaced.fZ) <= kTolerance)) { // top lid, normal along +Z
1007       norm.Set(0., 0., 1.);
1008       nosurface++;
1009     }
1010     if (inR && (Abs(point.z() + unplaced.fZ) <= kTolerance)) { // bottom base, normal along -Z
1011       if (nosurface > 0) {
1012         // norm exists already; just add to it
1013         norm[2] += Real_v(-1.);
1014       } else {
1015         norm.Set(0., 0., -1.);
1016       }
1017       nosurface++;
1018     }
1019     if (unplaced.fRmin > 0.) {
1020       if (inZ && (Abs(x2y2 - unplaced.fRmin) <= kTolerance)) { // inner tube wall, normal  towards center
1021         Precision invx2y2 = 1. / x2y2;
1022         if (nosurface == 0) {
1023           norm[0] = -point[0] * invx2y2;
1024           norm[1] = -point[1] * invx2y2; // -ve due to inwards
1025           norm[2] = Real_v(0.);
1026         } else {
1027           norm[0] += -point[0] * invx2y2;
1028           norm[1] += -point[1] * invx2y2;
1029         }
1030         nosurface++;
1031       }
1032     }
1033     if (inZ && (Abs(x2y2 - unplaced.fRmax) <= kTolerance)) { // outer tube wall, normal outwards
1034       Precision invx2y2 = 1. / x2y2;
1035       if (nosurface > 0) {
1036         norm[0] += point[0] * invx2y2;
1037         norm[1] += point[1] * invx2y2;
1038       } else {
1039         norm[0] = point[0] * invx2y2;
1040         norm[1] = point[1] * invx2y2;
1041         norm[2] = Real_v(0.);
1042       }
1043       nosurface++;
1044     }
1045 
1046     // otherwise we get a normal from the wedge
1047     if (unplaced.fDphi < vecgeom::kTwoPi) {
1048       if (inR && unplaced.fPhiWedge.IsOnSurface1(point)) {
1049         if (nosurface == 0)
1050           norm = -unplaced.fPhiWedge.GetNormal1();
1051         else
1052           norm += -unplaced.fPhiWedge.GetNormal1();
1053         nosurface++;
1054       }
1055       if (inR && unplaced.fPhiWedge.IsOnSurface2(point)) {
1056         if (nosurface == 0)
1057           norm = -unplaced.fPhiWedge.GetNormal2();
1058         else
1059           norm += -unplaced.fPhiWedge.GetNormal2();
1060         nosurface++;
1061       }
1062     }
1063     if (nosurface > 1) norm = norm / std::sqrt(1. * nosurface);
1064     valid = nosurface != 0; // this is for testing only
1065   }
1066 
1067 }; // End of struct TubeImplementation
1068 
1069 } // namespace VECGEOM_IMPL_NAMESPACE
1070 } // namespace vecgeom
1071 
1072 #endif