Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:14:05

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