Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-25 08:25:03

0001 /*
0002  * \file GenTrapImplementation.h
0003  * \brief Navigation kernels for the Generic Trapezoid (Arb8).
0004  *
0005  * This header implements the runtime kernels (Contains/Inside, Distance, Safety, Normal)
0006  * that operate on the immutable data stored in GenTrapStruct (see GenTrapStruct.h).
0007  *
0008  * Performance notes:
0009  *  - Planar cases can be short-circuited on host via TessellatedSection.
0010  *  - Lateral twisted faces (bilinear patches) use a cheap v(z)-window
0011  *    to cull impossible distance ranges before doing any quadratic/UV work.
0012  *  - For ray/face intersection we adopt a half-open step window [lmin, lmax),
0013  *    so hits at exactly lmax are considered "not in range" and lmin is inclusive
0014  *    up to a small negative tolerance to absorb FP noise.
0015  *
0016  *  Created on: Aug 2, 2014
0017  *      Author: swenzel
0018  *   Review/completion: Nov 4, 2015
0019  *      Author: mgheata
0020  *  Full scalar refactoring: andrei.gheata@cern.ch
0021  */
0022 
0023 #ifndef VECGEOM_VOLUMES_KERNEL_GENTRAPIMPLEMENTATION_H_
0024 #define VECGEOM_VOLUMES_KERNEL_GENTRAPIMPLEMENTATION_H_
0025 
0026 #include "VecGeom/base/Global.h"
0027 #include "VecGeom/base/Vector2D.h"
0028 
0029 #include "VecGeom/volumes/kernel/GenericKernels.h"
0030 #include "VecGeom/volumes/kernel/BoxImplementation.h"
0031 #include "VecGeom/volumes/UnplacedGenTrap.h"
0032 
0033 #include <iostream>
0034 
0035 namespace vecgeom {
0036 
0037 VECGEOM_DEVICE_FORWARD_DECLARE(struct GenTrapImplementation;);
0038 VECGEOM_DEVICE_DECLARE_CONV(struct, GenTrapImplementation);
0039 
0040 inline namespace VECGEOM_IMPL_NAMESPACE {
0041 
0042 class PlacedGenTrap;
0043 class UnplacedGenTrap;
0044 
0045 template <typename T>
0046 struct GenTrapStruct;
0047 
0048 struct GenTrapImplementation {
0049 
0050   using Vertex_t         = Vector3D<Precision>;
0051   using PlacedShape_t    = PlacedGenTrap;
0052   using UnplacedStruct_t = GenTrapStruct<Precision>;
0053   using UnplacedVolume_t = UnplacedGenTrap;
0054 
0055   /**
0056    * \brief Vectorized point containment test (fast path, no surface classification).
0057    *
0058    * Checks whether a point lies inside the generalized trapezoid volume.
0059    * This is typically cheaper than Inside() as it does not distinguish
0060    * between kInside and kSurface — it only returns a boolean.
0061    *
0062    * \tparam Real_v   Scalar or SIMD floating type
0063    * \tparam Bool_v   Corresponding boolean mask type
0064    * \param unplaced  Geometry data structure of the GenTrap
0065    * \param point     Query point in local coordinates
0066    * \param[out] inside  True if the point is not rejected by any face
0067    */
0068   template <typename Real_v, typename Bool_v>
0069   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void Contains(UnplacedStruct_t const &unplaced,
0070                                                                     Vector3D<Real_v> const &point, Bool_v &inside);
0071 
0072   /**
0073    * \brief Classify a point with respect to the solid.
0074    *
0075    * Returns one of EInside::{kInside, kSurface, kOutside}. This may perform
0076    * additional tolerance checks vs. Contains().
0077    *
0078    * \tparam Real_v     Scalar or SIMD floating type
0079    * \tparam Inside_t   Enum or mask convertible to EInside
0080    * \param unplaced    Geometry data structure of the GenTrap
0081    * \param point       Query point in local coordinates
0082    * \param[out] inside Classification result
0083    */
0084   template <typename Real_v, typename Inside_t>
0085   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void Inside(UnplacedStruct_t const &unplaced,
0086                                                                   Vector3D<Real_v> const &point, Inside_t &inside);
0087 
0088   /**
0089    * \brief Shared kernel used by Contains() and Inside().
0090    *
0091    * Evaluates the bilinear/planar surface equations on the four lateral faces
0092    * (and the implicit Z slabs) to determine quick-reject/accept masks.
0093    * When ForInside=true, it additionally computes a conservative
0094    * \c completelyinside mask.
0095    *
0096    * \tparam Real_v   Scalar type (assumed scalar in this implementation)
0097    * \tparam ForInside If true, also compute \c completelyinside; otherwise only outside mask
0098    * \param unplaced   Geometry data structure of the GenTrap
0099    * \param point      Query point
0100    * \param[out] completelyinside True if strictly inside all faces by tolerance
0101    * \param[out] completelyoutside True if outside any face by tolerance
0102    */
0103   template <typename Real_v, bool ForInside>
0104   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void GenericKernelForContainsAndInside(
0105       UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point, vecCore::Mask_v<Real_v> &completelyinside,
0106       vecCore::Mask_v<Real_v> &completelyoutside);
0107 
0108   /**
0109    * \brief Ray entry distance from an external point.
0110    *
0111    * Computes the distance along \p direction from \p point to enter the solid,
0112    * honoring \p stepMax. Returns \c -1 for points that are already inside.
0113    *
0114    * \tparam Real_v Scalar or SIMD floating type
0115    * \param unplaced Geometry data structure of the GenTrap
0116    * \param point    Start point (expected outside)
0117    * \param direction Normalized or non-normalized direction; sign matters
0118    * \param stepMax  Upper bound for the step; intersections beyond are ignored
0119    * \param[out] distance The entry distance, or +inf if no hit, or -1 if already inside
0120    */
0121   template <typename Real_v>
0122   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void DistanceToIn(UnplacedStruct_t const &unplaced,
0123                                                                         Vector3D<Real_v> const &point,
0124                                                                         Vector3D<Real_v> const &direction,
0125                                                                         Real_v const &stepMax, Real_v &distance);
0126 
0127   /**
0128    * \brief Ray exit distance from an internal point.
0129    *
0130    * Computes the distance from \p point along \p direction to leave the solid.
0131    *
0132    * \tparam Real_v Scalar or SIMD floating type
0133    * \param unplaced Geometry data structure of the GenTrap
0134    * \param point    Start point (expected inside)
0135    * \param direction Ray direction
0136    * \param stepMax  Currently unused upper bound (kept for API symmetry)
0137    * \param[out] distance The smallest positive exit distance, or +inf if parallel/no hit
0138    */
0139   template <typename Real_v>
0140   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void DistanceToOut(UnplacedStruct_t const &unplaced,
0141                                                                          Vector3D<Real_v> const &point,
0142                                                                          Vector3D<Real_v> const &direction,
0143                                                                          Real_v const &stepMax, Real_v &distance);
0144 
0145   /**
0146    * \brief Conservative safety from outside to the solid.
0147    *
0148    * Returns an underestimate of the distance one must travel to reach the surface
0149    * from \p point. If \p point is outside the bounding box, the box safety is used.
0150    *
0151    * \tparam Real_v Scalar type (assumed scalar here)
0152    * \param unplaced Geometry data structure of the GenTrap
0153    * \param point    Query point
0154    * \param[out] safety Non-negative lower bound to the closest surface
0155    */
0156   template <typename Real_v>
0157   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SafetyToIn(UnplacedStruct_t const &unplaced,
0158                                                                       Vector3D<Real_v> const &point, Real_v &safety);
0159 
0160   /**
0161    * \brief Conservative safety from inside to the exterior.
0162    *
0163    * Returns an underestimate to the nearest exit surface.
0164    * If the point is found outside along Z, returns -1.
0165    *
0166    * \tparam Real_v Scalar type (assumed scalar here)
0167    * \param unplaced Geometry data structure of the GenTrap
0168    * \param point    Query point
0169    * \param[out] safety Lower bound to the nearest exit; -1 if already outside on Z
0170    */
0171   template <typename Real_v>
0172   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void SafetyToOut(UnplacedStruct_t const &unplaced,
0173                                                                        Vector3D<Real_v> const &point, Real_v &safety);
0174 
0175   /**
0176    * \brief Compute a unit-length outward normal at a surface point.
0177    *
0178    * If \p point is within tolerance of a surface, returns the corresponding
0179    * outward normal and sets \p valid=true. For points not recognized to be on
0180    * any surface, \p valid is set to false and a default (0,0,0) is returned.
0181    *
0182    * \tparam Real_v Scalar type (assumed scalar here)
0183    * \tparam Bool_v Boolean type (mask)
0184    * \param unplaced Geometry data structure of the GenTrap
0185    * \param point    Point on (or near) a surface
0186    * \param[out] normal Unit outward normal (undefined if !valid)
0187    * \param[out] valid  True if a consistent normal could be determined
0188    */
0189   template <typename Real_v, typename Bool_v>
0190   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void NormalKernel(UnplacedStruct_t const &unplaced,
0191                                                                         Vector3D<Real_v> const &point,
0192                                                                         Vector3D<Real_v> &normal, Bool_v &valid);
0193 
0194   /// Utility functions
0195 
0196   /**
0197    * \brief Distance computation to a lateral surface.
0198    *
0199    * Handles both planar and twisted bilinear faces. For planar faces it reduces
0200    * to a plane intersection followed by in-range checks; for twisted faces it
0201    * solves a quadratic derived from the bilinear patch intersection.
0202    *
0203    * \tparam Real_v Precision type
0204    * \param unplaced Geometry data structure of the GenTrap
0205    * \param point Point
0206    * \param dir Direction (need not be unit length)
0207    * \param i Surface index [0,3]
0208    * \param in Inside state of the point (true if the ray starts inside)
0209    * \param lmin Minimum distance limit used to early-reject intersections
0210    * \param lmax Maximum distance limit used to early-reject intersections
0211    * \return Distance to surface (\c +inf if not hit within \p limit)
0212    */
0213   template <typename Real_v>
0214   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static Real_v DistanceToSurf(UnplacedStruct_t const &unplaced,
0215                                                                             Vector3D<Real_v> const &point,
0216                                                                             Vector3D<Real_v> const &dir, int i, bool in,
0217                                                                             Real_v lmin = Real_v(0),
0218                                                                             Real_v lmax = InfinityLength<Real_v>());
0219 
0220   /** \brief Fill vertices of the section at a given Z.
0221    *
0222    * \tparam Real_v Precision type
0223    * \param unplaced Geometry data structure of the GenTrap
0224    * \param z Z coordinate at which to evaluate the section
0225    * \param[out] vertices Out-parameter with the 4 XY vertices of the section
0226    */
0227   template <typename Real_v>
0228   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void FillVerticesAtZ(UnplacedStruct_t const &unplaced, Real_v z,
0229                                                                            Vector2D<Real_v> vertices[4]);
0230 
0231   /**
0232    * \brief Return the index of the edge closest to a given point (projected in XY).
0233    *
0234    * \tparam Real_v Scalar type
0235    * \param point   Query point (only x,y used)
0236    * \param vertxy  The four section vertices in XY
0237    * \param[out] iseg Index of the closest edge in [0,3]
0238    */
0239   template <class Real_v>
0240   VECCORE_ATT_HOST_DEVICE static void GetClosestEdge(Vector3D<Real_v> const &point, Vector2D<Real_v> const vertxy[4],
0241                                                      int &iseg);
0242 
0243   /** \brief Point-in-quad test on a Z section.
0244    *
0245    * Evaluates if the XY of \p point lies inside the quadrilateral defined by
0246    * \p v on the plane z=const of the corresponding section.
0247    *
0248    * \tparam Real_v Scalar type
0249    * \param point Query point (z is ignored except for consistency)
0250    * \param v     The four vertices of the section at the same z
0251    * \return True if inside (within tolerance); false if degenerate or outside
0252    */
0253   template <typename Real_v>
0254   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static bool InsideSection(Vector3D<Real_v> const &point,
0255                                                                          Vector3D<Real_v> const v[4]);
0256 
0257   /**
0258    * \brief Check whether a point on lateral surface \p isurf is within its parametric bounds.
0259    *
0260    * Uses the inverse mapping (XYZ->(u,v)) and tests 0<=u,v<=1 with tolerance.
0261    *
0262    * \tparam Real_v Scalar type
0263    * \param unplaced Geometry data
0264    * \param point    Point assumed on the surface
0265    * \param isurf    Surface index [0,3]
0266    * \return True if the (u,v) parameters lie within [0,1]^2 (with tolerance)
0267    */
0268   template <typename Real_v>
0269   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static bool InSurfLimits(UnplacedStruct_t const &unplaced,
0270                                                                         Vector3D<Real_v> const &point, int isurf);
0271 
0272   /**
0273    * \brief Safety helper across the four lateral faces.
0274    *
0275    * Computes a conservative signed safety by aggregating the maximum of
0276    * signed distances (planar faces) or Lipschitz-continuous bounds (twisted faces).
0277    *
0278    * \tparam Real_v Scalar type
0279    * \param unplaced Geometry data
0280    * \param point    Query point
0281    * \param safmax   Initial safety bound (typically Z safety)
0282    * \param inside   If true, returns negative safety; otherwise positive
0283    * \return Signed conservative safety
0284    */
0285   template <typename Real_v>
0286   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static Real_v SafetyArb4(UnplacedStruct_t const &unplaced,
0287                                                                         Vector3D<Real_v> const &point, Real_v safmax,
0288                                                                         bool inside = true);
0289 
0290   /**
0291    * \brief Normal vector to a bilinear surface (un-normalized).
0292    *
0293    * The surface is parameterized as:
0294    * \f[
0295    * P(u,v) = (1-u)(1-v)P_1 + u(1-v)P_2 + (1-u)vP_3 + uvP_4
0296    * \f]
0297    * whose (non-normalized) normal is \f\vec n = \partial_u P \times \partial_v P\f.
0298    * The user may validate containment in the surface patch if (u,v) parameters lie within [0,1]^2 (with tolerance)
0299    *
0300    * \tparam Real_v Scalar type
0301    * \param unplaced Geometry data
0302    * \param point Point assumed on surface i
0303    * \param i Index of the lateral surface [0,3]
0304    * \param[out] unorm Un-normalized outward normal
0305    * \param[out] u shear coordinate at point.z()
0306    * \param[out] v generator coordinate
0307    */
0308   template <typename Real_v>
0309   VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE static void UNormal(UnplacedStruct_t const &unplaced,
0310                                                                    Vector3D<Real_v> const &point, int i,
0311                                                                    Vector3D<Real_v> &unorm, Real_v &u, Real_v &v);
0312 
0313   /** \brief Inverse mapping from cartesian point on a lateral face to (u,v).
0314    *
0315    * Implements the inverse of the bilinear patch equation restricted to the
0316    * generator pair (A,C) and (B,D) bounding the face.
0317    *
0318    * \tparam Real_v Scalar type
0319    * \param unplaced Geometry data
0320    * \param point Point in cartesian space, assumed on the bilinear surface i
0321    * \param i Index of the surface the point is on in the range [0, 3]
0322    * \return (u,v) coordinates within [0,1]^2 if the point is on the face
0323    */
0324   template <typename Real_v>
0325   VECCORE_ATT_HOST_DEVICE static Vector2D<Real_v> XYZtoUV(UnplacedStruct_t const &unplaced,
0326                                                           Vector3D<Real_v> const &point, int i);
0327 }; // End struct GenTrapImplementation
0328 
0329 //********************************
0330 //**** implementations start here
0331 //********************************/
0332 
0333 //______________________________________________________________________________
0334 // --- Cheap, safe interval culls for twisted faces ---
0335 /**
0336  * \brief Compute the admissible t-interval such that the parametric v(t) stays in [0,1].
0337  *
0338  * The bilinear patch is parameterized with v determined by z:
0339  * \f[
0340  *   v(t) = \frac{z(t) + D_z}{2 D_z} = \frac{p_z + t\,d_z + D_z}{2 D_z}.
0341  * \f]
0342  * We pass \p Dz2 = 1/(2*D_z) to avoid divides. The result interval is intersected
0343  * with the user-provided forward limit \p limit and returned as [tmin,tmax].
0344  *
0345  * \param ptZ   start point z
0346  * \param dirZ  ray z-component
0347  * \param Dz2   precomputed 1/(2*Dz)
0348  * \param limit upper bound on t from the caller (e.g. stepMax)
0349  * \param[out] tmin lower bound of the feasible window
0350  * \param[out] tmax upper bound of the feasible window
0351  * \return false if the feasible window is empty
0352  */
0353 template <typename Real_v>
0354 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool GenTrap_VInterval(Real_v ptZ, Real_v dirZ, Real_v Dz2, Real_v limit,
0355                                                                     Real_v &tmin, Real_v &tmax)
0356 {
0357   // v(t) = (ptZ + Dz + dirZ*t) / (2*Dz) must stay in [0,1]
0358   const Real_v kv = dirZ * Dz2;              // slope of v(t)
0359   const Real_v v0 = ptZ * Dz2 + Real_v(0.5); // v at t=0
0360   Real_v t0 = Real_v(0), t1 = limit;         // user range
0361   if (Abs(kv) < kToleranceDist<Real_v>) {
0362     // v is (almost) constant along the ray
0363     if (v0 < Real_v(0) - kToleranceDist<Real_v> || v0 > Real_v(1) + kToleranceDist<Real_v>) return false;
0364     tmin = t0;
0365     tmax = t1;
0366     return true;
0367   }
0368   const Real_v t_at_0 = (Real_v(0) - v0) / kv;
0369   const Real_v t_at_1 = (Real_v(1) - v0) / kv;
0370   Real_v a = Min(t_at_0, t_at_1), b = Max(t_at_0, t_at_1);
0371   t0 = Max(t0, a);
0372   t1 = Min(t1, b);
0373   if (t1 < t0) return false;
0374   tmin = t0;
0375   tmax = t1;
0376   return true;
0377 }
0378 
0379 //______________________________________________________________________________
0380 template <typename Real_v>
0381 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE Real_v
0382 GenTrapImplementation::DistanceToSurf(UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point,
0383                                       Vector3D<Real_v> const &dir, int i, bool in, Real_v lmin, Real_v lmax)
0384 {
0385   // Return a large sentinel for "no hit"
0386   Real_v big = InfinityLength<Real_v>();
0387   VECGEOM_ASSERT(!unplaced.IsDegenerated(i) && "DistanceToSurf called with degenerated face");
0388 
0389   // NOTE on window semantics:
0390   //   * We use a half-open window [lmin, lmax) to keep composition simple when
0391   //     iterating faces; a hit exactly at lmax is considered "out of range".
0392   //   * We allow a tiny negative slack at lmin to absorb FP error when starting
0393   //     very close to a surface: (t < lmin - tol) is rejected.
0394   //   * For inside->out queries, the first positive root is a valid exit
0395   //     as soon as it passes the window tests, hence early-return is safe.
0396 
0397   if (!unplaced.IsTwisted(i)) {
0398     // -------------------------
0399     // Planar face intersection
0400     // -------------------------
0401 
0402     // Signed plane safety: <0 means inside, >0 outside w.r.t. stored outward normal
0403     const Real_v safety = unplaced.fSurf[i].EvaluatePlanar(point); // n·p + g
0404     const Real_v dn     = unplaced.fSurf[i].DotPlaneNormal(dir);
0405 
0406     // Reject points on the wrong side or going in the wrong direction.
0407     //  - side_ok   : start is consistent with inside/outside state (tolerant)
0408     //  - facing_ok : direction crosses the plane in the correct sense
0409     const bool facing_ok = in ^ (dn /* * graze_limit */ < -kToleranceDist<Real_v>);
0410     const bool side_ok   = (in && safety < kToleranceDist<Real_v>) || (!in && safety > -kToleranceDist<Real_v>);
0411     if (!facing_ok || !side_ok) return big;
0412 
0413     // Distance to plane; can be negative when starting inside (handled by window)
0414     // We check the window before touching the patch bounds to avoid UV work
0415     // for steps that cannot be taken anyway.
0416     Real_v snext = -safety / NonZero(dn);
0417     // window culls before UV work
0418     if ((snext < lmin - kToleranceDist<Real_v>) || (snext >= lmax)) return big;
0419 
0420     // Validate the hit point lies within the finite bilinear bounds of the face
0421     // Clamp tiny negative distances to zero to avoid stepping backwards
0422     return InSurfLimits<Real_v>(unplaced, point + snext * dir, i) ? Max(snext, Real_v(0.)) : big;
0423   } else {
0424     // ---------------------------
0425     // Twisted bilinear intersection
0426     // ---------------------------
0427     // Intersection reduces to a quadratic in t; we test candidate roots against
0428     // the [lmin,lmax) window first, then validate against patch (u,v) bounds.
0429     // The code below uses directly the surface equation, but it is slower:
0430     // Real_v a, b, c;
0431     // unplaced.fSurf[i].RayQuadratic(point, dir, a, b, c);
0432 
0433 #if GENTRAP_USE_HYBRID_COEFFS
0434     const auto pointXY = point.XY();
0435     const auto dirXY   = dir.XY();
0436     const auto z       = point[2];
0437     const auto dz      = dir[2];
0438 
0439     // Cached per-face differences
0440     const auto &dMid = unplaced.fDmid[i];
0441     const auto &dT   = unplaced.fDT[i];
0442 
0443     // Cached scalars (tiny memory footprint)
0444     const Real_v TT = unplaced.fTT[i];
0445     const Real_v MM = unplaced.fMM[i];
0446     const Real_v MT = unplaced.fMT[i];
0447 
0448     // Helper: z-component of 2D cross (kept inlined by the compiler)
0449     auto xz = [](auto const &a, auto const &b) -> Real_v { return a.x() * b.y() - a.y() * b.x(); };
0450 
0451     // Quadratic coefficients (hybrid precompute form)
0452     const Real_v dT_x_dir   = xz(dT, dirXY);
0453     const Real_v dT_x_point = xz(dT, pointXY);
0454     const Real_v dMid_x_dir = xz(dMid, dirXY);
0455     const Real_v dMid_x_pt  = xz(dMid, pointXY);
0456 
0457     Real_v a = dT_x_dir * dz + TT * dz * dz;
0458     Real_v b = dMid_x_dir + z * dT_x_dir + (dT_x_point + (MT + Real_v(2) * z * TT)) * dz;
0459     Real_v c = dMid_x_pt + z * dT_x_point + MM + z * MT + z * z * TT;
0460 #else
0461     int j = (i + 1) % 4;
0462 
0463     // Unpack a few frequently used quantities
0464     auto const pointXY = point.XY();  // project to XY
0465     auto const dirXY   = dir.XY();    // project direction to XY
0466     auto const t       = unplaced.fT; // shear vectors per vertex
0467 
0468     auto const s1 = unplaced.fMidXY[i] + t[i] * point[2]; // generator at z
0469     auto const s2 = unplaced.fMidXY[j] + t[j] * point[2];
0470     auto ds       = s2 - s1;
0471 
0472     // Quadratic coefficients (original on-the-fly derivation)
0473     Real_v a = ((t[j] - t[i]).CrossZ(dirXY) + t[i].CrossZ(t[j]) * dir[2]) * dir[2];
0474     Real_v b = ds.CrossZ(dirXY) + ((t[j] - t[i]).CrossZ(pointXY) + s1.CrossZ(t[j]) - s2.CrossZ(t[i])) * dir[2];
0475     Real_v c = ds.CrossZ(pointXY) + s1.CrossZ(s2);
0476 #endif
0477 
0478     constexpr Real_v tolerance = kToleranceArb4<Real_v>;
0479     Real_v dist[2];
0480     auto nroots = QuadraticSolver(a, b, c, dist);
0481 
0482     // Prevent grazing rays when starting outside: two roots at ~0 within tolerance
0483     if (!in && nroots == 2 && Abs(dist[0]) < tolerance && Abs(dist[1]) < tolerance) return big;
0484 
0485     for (auto k = 0; k < nroots; ++k) {
0486       const Real_v troot = dist[k];
0487 
0488       // step window culls before UV/normal work
0489       if ((troot < lmin - kToleranceDist<Real_v>) || (troot >= lmax)) continue;
0490 
0491       // If the point is inside and not starting from a boundary, this is a minimization problem
0492       if (in && troot > tolerance) return troot; // inside: early minimum positive root is the exit
0493 
0494       // Validate parametric bounds via (u,v), but use implicit gradient for crossing sense
0495       auto hit      = point + troot * dir;
0496       auto uv       = XYZtoUV(unplaced, hit, i);
0497       auto u        = uv[0];
0498       auto v        = uv[1];
0499       bool in_patch = (u >= Real_v(0.)) && (u <= Real_v(1.)) && (v >= Real_v(0.)) && (v <= Real_v(1.));
0500       if (!in_patch) continue;
0501       // Crossing sense from implicit gradient (cheaper than building full geometric normal)
0502       auto grad       = unplaced.fSurf[i].EvaluateGradient(hit);
0503       bool crosses_ok = in ^ (dir.Dot(grad) < Real_v(0.));
0504       if (crosses_ok) return troot;
0505     }
0506   }
0507   return big;
0508 }
0509 
0510 //______________________________________________________________________________
0511 template <typename Real_v, bool ForInside>
0512 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::GenericKernelForContainsAndInside(
0513     UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point, vecCore::Mask_v<Real_v> &completelyinside,
0514     vecCore::Mask_v<Real_v> &completelyoutside)
0515 {
0516   // NOTE: This kernel currently assumes scalar Real_v. We keep the signature
0517   // templated to mirror VecGeom conventions and ease future vectorization.
0518   // First stage: AABB quick reject/accept; second stage: evaluate lateral faces.
0519   // For ForInside=true we also compute a strict "completelyinside" mask
0520   // by testing f_i(point) < -tolerance for all active faces. Points within
0521   // tolerance of the Z slabs are treated specially so we do not incorrectly
0522   // classify them as strictly inside (we then return kSurface in Inside()).
0523 
0524   // The current implementation assumes a scalar Real_v
0525   static_assert(vecCore::VectorSize<Real_v>() == 1);
0526 
0527   // Quick reject/accept using the axis-aligned bounding box (AABB)
0528   Vector3D<Real_v> halfsize(unplaced.fBBdimensions[0], unplaced.fBBdimensions[1], unplaced.fBBdimensions[2]);
0529   BoxImplementation::GenericKernelForContainsAndInside<Real_v, true>(halfsize, point - unplaced.fBBorigin,
0530                                                                      completelyinside, completelyoutside);
0531 
0532   constexpr Real_v tolerance = kToleranceArb4<Real_v>;
0533   if (completelyoutside) return; // outside the AABB => outside the solid
0534 
0535   // If the point lies on the top/bottom faces within tolerance, skip tightening of the inside mask
0536   auto on_surfz = (unplaced.fDz - Abs(point.z())) < Real_v(kTolerance);
0537 
0538   // Evaluate implicit equations for lateral faces; any positive value means "outside that face"
0539   VECGEOM_PRAGMA_UNROLL(4)
0540   for (int i = 0; i < 4; i++) {
0541     if (unplaced.IsDegenerated(i)) continue;
0542     auto eqeval = unplaced.IsTwisted(i) ? unplaced.fSurf[i].Evaluate(point) : unplaced.fSurf[i].EvaluatePlanar(point);
0543     completelyoutside = eqeval > tolerance;
0544     if (completelyoutside) {
0545       completelyinside = false; // single face suffices to reject
0546       return;
0547     }
0548     if (ForInside && !on_surfz) completelyinside &= eqeval < -tolerance; // strictly inside all faces
0549   }
0550 }
0551 
0552 //______________________________________________________________________________
0553 template <typename Real_v, typename Bool_v>
0554 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::Contains(UnplacedStruct_t const &unplaced,
0555                                                                                   Vector3D<Real_v> const &point,
0556                                                                                   Bool_v &inside)
0557 {
0558 #ifndef VECCORE_CUDA
0559   // Use tessellated helper when available (planar case optimization)
0560   // This path handles all lateral faces as triangles/quads on host,
0561   // and is typically faster than evaluating bilinear patch equations
0562   // when the shape is purely planar. Z-slab rejection is kept explicit
0563   // to avoid tessellated work when clearly outside along Z.
0564   if (unplaced.fTslHelper) {
0565     // Check Z range
0566     inside = false;
0567     if (vecCore::math::Abs(point.z()) > unplaced.fDz) return; // rejected by Z slab
0568     inside = unplaced.fTslHelper->Contains(point);
0569     return;
0570   }
0571 #endif
0572   // Generic fallback
0573   Bool_v unused(false);
0574   Bool_v outside(false);
0575   GenericKernelForContainsAndInside<Real_v, false>(unplaced, point, unused, outside);
0576   inside = !outside;
0577 }
0578 
0579 //______________________________________________________________________________
0580 template <typename Real_v, typename Inside_t>
0581 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::Inside(UnplacedStruct_t const &unplaced,
0582                                                                                 Vector3D<Real_v> const &point,
0583                                                                                 Inside_t &inside)
0584 {
0585 #ifndef VECCORE_CUDA
0586   if (unplaced.fTslHelper) {
0587     // Planar-case dispatcher with tessellated helper
0588     inside         = EInside::kOutside;
0589     Precision safZ = vecCore::math::Abs(point.z()) - unplaced.fDz;
0590     if (safZ > kTolerance) return;     // definitely outside along Z
0591     bool insideZ = safZ < -kTolerance; // strictly between the Z planes
0592     inside       = unplaced.fTslHelper->Inside(point);
0593     if (insideZ || inside == EInside::kOutside) return; // kInside is already strict or outside
0594     inside = EInside::kSurface;                         // on the faces within tolerance
0595     return;
0596   }
0597 #endif
0598   bool completelyinside;
0599   bool completelyoutside;
0600   GenericKernelForContainsAndInside<Real_v, true>(unplaced, point, completelyinside, completelyoutside);
0601 
0602   // Map masks to EInside classification
0603   inside = EInside::kSurface;
0604   if (completelyoutside) inside = EInside::kOutside;
0605   if (completelyinside) inside = EInside::kInside;
0606 }
0607 
0608 //______________________________________________________________________________
0609 template <typename Real_v>
0610 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::DistanceToIn(UnplacedStruct_t const &unplaced,
0611                                                                                       Vector3D<Real_v> const &point,
0612                                                                                       Vector3D<Real_v> const &direction,
0613                                                                                       Real_v const &stepMax,
0614                                                                                       Real_v &distance)
0615 {
0616 #ifndef VECCORE_CUDA
0617   // Fast path for planar faces via tessellated helper
0618   if (unplaced.fTslHelper) {
0619     Precision invdirz = 1. / NonZero(direction.z());
0620     distance          = unplaced.fTslHelper->DistanceToIn<false>(point, direction, invdirz, stepMax);
0621     return;
0622   }
0623 #endif
0624   // Summary:
0625   //  1) Handle trivially-inside start (convention distance=-1).
0626   //  2) Intersect the AABB to get an upper bound and early-out if unreachable.
0627   //  3) Try entering through Z slabs (cheap) and return if successful.
0628   //  4) Approact to bounding box distance w/o overshooting into. Improves numerical stability.
0629   //  5) Hoist v-window once, then test lateral faces with [lmin,lmax) window.
0630   bool completelyinside, completelyoutside;
0631   GenericKernelForContainsAndInside<Real_v, true>(unplaced, point, completelyinside, completelyoutside);
0632 
0633   // Already inside -> signal with -1 as convention
0634   if (completelyinside) {
0635     distance = Real_v(-1.);
0636     return;
0637   }
0638 
0639   // Intersect with bounding box first to get an upper bound
0640   Real_v bbdistance = Real_v(kInfLength);
0641   BoxImplementation::DistanceToIn(BoxStruct<Precision>(unplaced.fBBdimensions), point - unplaced.fBBorigin, direction,
0642                                   stepMax, bbdistance);
0643 
0644   // Initialize best distance as +inf; if we cannot reach the AABB we cannot hit the solid
0645   distance = InfinityLength<Real_v>();
0646 
0647   // If we cannot reach the AABB, we cannot reach the solid either
0648   bool done = bbdistance >= InfinityLength<Real_v>();
0649   if (done) return;
0650 
0651   // Potential hit with Z planes? Only if we are outside in Z and heading inward
0652   Real_v zsafety = Abs(point.z()) - unplaced.fDz;
0653   bool canhitz   = (zsafety > -kToleranceDist<Real_v>) && (point.z() * direction.z() < 0);
0654 
0655   if (canhitz) {
0656     // Distance to the relevant Z plane (as in Box algorithm)
0657     Real_v next = zsafety / Abs(direction.z());
0658 
0659     // Check containment on the Z plane polygon (top if dir.z>0, bottom otherwise)
0660     auto const vertices = (direction.z() > 0) ? &unplaced.fVertices[0] : &unplaced.fVertices[4];
0661     auto hitz           = InsideSection(point + next * direction, vertices);
0662     if (hitz) {
0663       distance = next;
0664       return;
0665     }
0666   }
0667 
0668   // ---------- Approach to (just outside) the AABB entry --------------
0669   // We start subsequent work from p0 = point + t_approach*dir, with t_approach = max(0, bbdistance - eps),
0670   Real_v t_approach   = Max(Real_v(0), bbdistance - kToleranceArb4<Real_v>);
0671   Vector3D<Real_v> p0 = point + t_approach * direction;
0672   Real_v step_budget  = stepMax - t_approach;
0673   if (step_budget <= Real_v(0)) {
0674     distance = InfinityLength<Real_v>();
0675     return;
0676   }
0677 
0678   // -------- Hoist v(t) ∈ [0,1] window once (face-independent) --------
0679   // We gate by stepMax here to avoid generating candidates beyond the transport step.
0680   // IMPORTANT: We do not cap tmax by the AABB distance; instead we raise lmin later
0681   // with the (possibly finite) AABB entry so that faces cannot return hits before
0682   // the box is actually entered, while still allowing v-window to rule out ranges.
0683   Real_v tmin_v, tmax_v;
0684   if (!GenTrap_VInterval(p0.z(), direction.z(), unplaced.fDz2, step_budget, tmin_v, tmax_v)) {
0685     // Ray never reaches z within [-Dz,+Dz] in forward t within stepMax
0686     return;
0687   }
0688 
0689   // Do not accept hits before we actually pierce the AABB. From p0 the box entry is ~eps_bb ahead.
0690   Real_v lmin = Max(tmin_v, (t_approach > Real_v(0)) ? kToleranceArb4<Real_v> : Real_v(0));
0691   Real_v lmax = tmax_v;
0692   if (lmin > lmax) return;
0693 
0694   // Keep best local distance measured from p0; add t_approach at the end.
0695   Real_v best_local = InfinityLength<Real_v>();
0696 
0697   // Fall back to lateral surfaces and keep the minimum positive distance
0698   // Separate planar/twisted unrolled loops to reduce divergence on device compare to a mixed loop
0699   VECGEOM_PRAGMA_UNROLL(4)
0700   for (auto i = 0; i < 4; ++i) {
0701     if (!unplaced.IsDegenerated(i) && !unplaced.IsTwisted(i)) {
0702       // Planar faces: necessary front-facing check to avoid useless intersections
0703       Real_v dn = unplaced.fSurf[i].DotPlaneNormal(direction);
0704       if (dn >= -kToleranceDist<Real_v>) continue;
0705       // Cap face work by the z-feasible window upper bound
0706       // (we also pass lmin so the face can reject small distances early, before
0707       // any UV or normal work)
0708       Real_v face_limit = Min(best_local, lmax);
0709       best_local        = Min(best_local, DistanceToSurf(unplaced, p0, direction, i, false, lmin, face_limit));
0710     }
0711   }
0712 
0713   VECGEOM_PRAGMA_UNROLL(4)
0714   for (auto i = 0; i < 4; ++i) {
0715     if (!unplaced.IsDegenerated(i) && unplaced.IsTwisted(i)) {
0716       // Twisted faces: keep coarse direction cull; use the hoisted v-window cap
0717       if (!unplaced.fMesh[i].MayHit(p0, direction, false)) continue;
0718       // As above, we bound by tmax_v and provide lmin=tmin_v to allow
0719       // early rejection prior to UV/normal work.
0720       Real_v face_limit = Min(best_local, lmax);
0721       best_local        = Min(best_local, DistanceToSurf(unplaced, p0, direction, i, false, lmin, face_limit));
0722     }
0723   }
0724 
0725   // Report distance from the original point
0726   distance = (best_local >= InfinityLength<Real_v>()) ? InfinityLength<Real_v>() : (t_approach + best_local);
0727 }
0728 
0729 //______________________________________________________________________________
0730 template <typename Real_v>
0731 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::DistanceToOut(
0732     UnplacedStruct_t const &unplaced, Vector3D<Real_v> const &point, Vector3D<Real_v> const &direction,
0733     Real_v const & /*stepMax*/, Real_v &distance)
0734 {
0735 #ifndef VECCORE_CUDA
0736   if (unplaced.fTslHelper) {
0737     distance = unplaced.fTslHelper->DistanceToOut(point, direction);
0738     return;
0739   }
0740 #endif
0741   // Convention: if the start is already outside, return -1. Otherwise compute
0742   // the minimum positive distance to any exiting surface (Z slabs + laterals).
0743   // Z slabs are attempted first as they are cheaper; lateral faces use the
0744   // same DistanceToSurf() with in=true and a shrinking lmax=distance.
0745   distance = Real_v(-1.); // default if already outside
0746   bool completelyinside, completelyoutside;
0747   // We assume the start point is inside; if not, early-exit
0748   GenericKernelForContainsAndInside<Real_v, true>(unplaced, point, completelyinside, completelyoutside);
0749   if (completelyoutside) return;
0750 
0751   // Candidate intersection with the Z planes (skip if nearly parallel)
0752   Real_v sign  = (direction.z() < 0) ? Real_v(-1.) : Real_v(1.);
0753   Real_v limit = Real_v(1.); // <- This is a first implementation, we should use the real limit
0754   bool skipZ   = Abs(direction.z()) * limit < kToleranceDist<Real_v>;
0755   distance     = skipZ ? InfinityLength<Real_v>() : (sign * unplaced.fDz - point.z()) / NonZero(direction.z());
0756 
0757   // Fall back to lateral surfaces and keep the minimum positive distance
0758   // Separate planar/twisted unrolled loops to reduce divergence on device compare to a mixed loop
0759   VECGEOM_PRAGMA_UNROLL(4)
0760   for (auto i = 0; i < 4; ++i) {
0761     if (!unplaced.IsDegenerated(i) && !unplaced.IsTwisted(i)) {
0762       distance = Min(distance, DistanceToSurf(unplaced, point, direction, i, true, Real_v(0), distance));
0763     }
0764   }
0765 
0766   VECGEOM_PRAGMA_UNROLL(4)
0767   for (auto i = 0; i < 4; ++i) {
0768     if (!unplaced.IsDegenerated(i) && unplaced.IsTwisted(i)) {
0769       if (!unplaced.fMesh[i].MayHit(point, direction, true)) continue;
0770       distance = Min(distance, DistanceToSurf(unplaced, point, direction, i, true, Real_v(0), distance));
0771     }
0772   }
0773 }
0774 
0775 //______________________________________________________________________________
0776 template <typename Real_v>
0777 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::SafetyToIn(UnplacedStruct_t const &unplaced,
0778                                                                                     Vector3D<Real_v> const &point,
0779                                                                                     Real_v &safety)
0780 {
0781 #ifndef VECCORE_CUDA
0782   if (unplaced.fTslHelper) {
0783     safety = unplaced.fTslHelper->SafetyToIn(point);
0784     return;
0785   }
0786 #endif
0787   // Scalar-only path
0788   static_assert(vecCore::VectorSize<Real_v>() == 1);
0789 
0790   // If outside the bounding box, use the box safety (cheap and conservative)
0791   bool inside;
0792   BoxImplementation::Contains(BoxStruct<Precision>(unplaced.fBBdimensions), point - unplaced.fBBorigin, inside);
0793   if (!inside) {
0794     // This is not optimal if top and bottom faces are not on top of each other
0795     BoxImplementation::SafetyToIn(BoxStruct<Precision>(unplaced.fBBdimensions), point - unplaced.fBBorigin, safety);
0796     return;
0797   }
0798 
0799   // Combine Z safety with lateral faces using SafetyArb4
0800   safety = Abs(point[2]) - unplaced.fDz;
0801   safety = SafetyArb4(unplaced, point, safety, false);
0802 }
0803 
0804 //______________________________________________________________________________
0805 template <typename Real_v>
0806 VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::SafetyToOut(UnplacedStruct_t const &unplaced,
0807                                                                 Vector3D<Real_v> const &point, Real_v &safety)
0808 {
0809   // Dispatcher for SafetyToOut
0810 #ifndef VECCORE_CUDA
0811   if (unplaced.fTslHelper) {
0812     safety = unplaced.fTslHelper->SafetyToOut(point);
0813     return;
0814   }
0815 #endif
0816   // Generic implementation for SafetyToOut
0817   // Assume Real_v to be scalar
0818   static_assert(vecCore::VectorSize<Real_v>() == 1);
0819   // Safety convention:
0820   //  - positive value is a *lower bound* to the distance to exit
0821   //  - for Z, we return signed distance to the nearest slab
0822   //  - if already outside along Z (safety >= -tol), return -1 as sentinel
0823 
0824   // Z safety is simple: distance to nearest top/bottom plane (signed)
0825   safety = Abs(point[2]) - unplaced.fDz;
0826 
0827   // Already outside on Z? return -1 to signal this convention
0828   if (safety > -kTolerance) {
0829     safety = Real_v(-1.);
0830     return;
0831   }
0832 
0833   // Aggregate with lateral faces
0834   safety = SafetyArb4(unplaced, point, safety, true);
0835 }
0836 
0837 //______________________________________________________________________________
0838 template <typename Real_v, typename Bool_v>
0839 VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::NormalKernel(UnplacedStruct_t const &unplaced,
0840                                                                  Vector3D<Real_v> const &point,
0841                                                                  Vector3D<Real_v> &normal, Bool_v &valid)
0842 {
0843   // Computes the normal on a surface and returns it as a unit vector
0844   // In case a point is further than tolerance_normal from a surface, set valid=false
0845   // Strategy:
0846   //   * Prefer Z planes when |z|≈Dz.
0847   //   * Otherwise pick the closest projected edge on the section at z to select
0848   //     a candidate lateral face, then compute planar/twisted normal accordingly.
0849   //     For twisted faces we check that (u,v) lies in an expanded [0,1]^2.
0850   valid = Bool_v(true);
0851   normal.Set(0.);
0852 
0853   // First, check proximity to the top/bottom planes
0854   Real_v safz = Abs(unplaced.fDz - Abs(point.z()));
0855   if (safz < kTolerance) {
0856     normal[2] = Sign(point.z());
0857     return;
0858   }
0859 
0860   // For lateral faces, pick the closest edge on the section at z
0861   Vector2D<Real_v> vert[4];
0862   FillVerticesAtZ(unplaced, point.z(), vert);
0863   int iseg;
0864   GetClosestEdge<Real_v>(point, vert, iseg);
0865 
0866   if (!unplaced.IsTwisted(iseg)) {
0867     // Planar face: use precomputed outward normal
0868     normal = unplaced.fSurf[iseg].GetPlaneNormal();
0869     return;
0870   }
0871 
0872   Real_v u, v;
0873   UNormal(unplaced, point, iseg, normal, u, v);
0874   normal.Normalize();
0875 
0876   // Consider points very near boundaries as valid (u,v within tolerance-extended [0,1])
0877   valid = (u > -kToleranceDist<Real_v>) && (u < 1. + kToleranceDist<Real_v>) && (v > -kToleranceDist<Real_v>) &&
0878           (v < 1. + kToleranceDist<Real_v>);
0879 }
0880 
0881 //______________________________________________________________________________
0882 template <typename Real_v>
0883 VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::GetClosestEdge(Vector3D<Real_v> const &point,
0884                                                                    Vector2D<Real_v> const vertxy[4], int &iseg)
0885 {
0886   // Get index of the edge of the quadrilateral represented by vert closest to point.
0887   iseg = 0;
0888   Real_v lsq, dx, dy, dpx, dpy, u;
0889   Real_v safe = InfinityLength<Real_v>();
0890   Real_v ssq  = InfinityLength<Real_v>();
0891 
0892   for (int i = 0; i < 4; ++i) {
0893     int j = (i + 1) % 4;
0894     dx    = vertxy[j].x() - vertxy[i].x();
0895     dy    = vertxy[j].y() - vertxy[i].y();
0896     dpx   = point.x() - vertxy[i].x();
0897     dpy   = point.y() - vertxy[i].y();
0898     lsq   = dx * dx + dy * dy;
0899 
0900     // Skip degenerated segments
0901     if (lsq < kToleranceDistSquared<Real_v>) continue;
0902 
0903     // Projection fraction of P onto segment [i,j]
0904     u = (dpx * dx + dpy * dy);
0905     if (u > lsq) {
0906       // Outside, closer to vertex j
0907       dpx = point.x() - vertxy[j].x();
0908       dpy = point.y() - vertxy[j].y();
0909     } else if (u >= 0) {
0910       // Projection inside the segment
0911       auto invlsq = Real_v(1.) / lsq;
0912       dpx -= u * dx * invlsq;
0913       dpy -= u * dy * invlsq;
0914     }
0915 
0916     ssq = dpx * dpx + dpy * dpy;
0917     if (ssq < safe) {
0918       safe = ssq;
0919       iseg = i;
0920     }
0921   }
0922 }
0923 
0924 //______________________________________________________________________________
0925 template <typename Real_v>
0926 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::UNormal(UnplacedStruct_t const &unplaced,
0927                                                                                  Vector3D<Real_v> const &point, int i,
0928                                                                                  Vector3D<Real_v> &unorm, Real_v &u,
0929                                                                                  Real_v &v)
0930 {
0931   // Get the (u, v) parameters corresponding to the cartesian point
0932   auto uv = XYZtoUV(unplaced, point, i);
0933   u       = uv[0];
0934   v       = uv[1];
0935 
0936   // Tangent vectors of the bilinear patch
0937   // Use extended caches when available
0938 #if GENTRAP_USE_HYBRID_COEFFS
0939   const auto &E0 = unplaced.fE0[i]; // C - A
0940   const auto &E1 = unplaced.fE1[i]; // D - B
0941   const auto &G0 = unplaced.fG0[i]; // B - A
0942   const auto &G1 = unplaced.fG1[i]; // D - C
0943   auto dPdu      = -(Real_v(1.) - v) * E0 - v * E1;
0944   auto dPdv      = (Real_v(1.) - u) * G0 + u * G1;
0945 #else
0946   auto j = (i + 1) % 4;
0947   // Corner notation: face spans A->C on bottom, B->D on top
0948   auto const &A = unplaced.fVertices[i];
0949   auto const &C = unplaced.fVertices[j];
0950   auto const &B = unplaced.fVertices[i + 4];
0951   auto const &D = unplaced.fVertices[j + 4];
0952   auto dPdu     = -(Real_v(1.) - v) * (C - A) - v * (D - B);
0953   auto dPdv     = (Real_v(1.) - u) * (B - A) + u * (D - C);
0954 #endif
0955 
0956   // Un-normalized outward normal
0957   unorm = dPdu.Cross(dPdv);
0958 }
0959 
0960 //______________________________________________________________________________
0961 template <typename Real_v>
0962 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool GenTrapImplementation::InsideSection(Vector3D<Real_v> const &point,
0963                                                                                        Vector3D<Real_v> const v[4])
0964 {
0965   bool inside = true;
0966   bool degen  = true;
0967 
0968   // Half-plane test around the quadrilateral; uses robust cross/length checks
0969   for (int i = 0; i < 4; ++i) {
0970     const auto vij = v[(i + 1) % 4] - v[i];
0971     auto len2      = vij.Mag2();
0972     if (len2 < kToleranceDistSquared<Real_v>) continue; // degenerate edge
0973     degen      = false;
0974     auto cross = (point - v[i]).CrossZ(vij);
0975     inside     = cross * cross >= kToleranceDistSquared<Real_v> * len2 ? (cross >= 0) : false;
0976     if (!inside) break; // early exit if outside any edge
0977   }
0978   if (degen) return false; // degenerate polygon is treated as outside
0979   return inside;
0980 }
0981 
0982 //______________________________________________________________________________
0983 template <typename Real_v>
0984 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE void GenTrapImplementation::FillVerticesAtZ(
0985     UnplacedStruct_t const &unplaced, Real_v z, Vector2D<Real_v> vertices[4])
0986 {
0987   // Interpolate XY vertices along generators to the specified z-plane
0988   for (int i = 0; i < 4; i++)
0989     vertices[i] = unplaced.fMidXY[i] + unplaced.fT[i] * z;
0990 }
0991 
0992 //______________________________________________________________________________
0993 template <typename Real_v>
0994 VECCORE_ATT_HOST_DEVICE Vector2D<Real_v> GenTrapImplementation::XYZtoUV(UnplacedStruct_t const &unplaced,
0995                                                                         Vector3D<Real_v> const &point, int i)
0996 {
0997   // Parametrize v from z: bottom plane z=-Dz -> v=0, top plane z=+Dz -> v=1
0998   Real_v v = unplaced.fDz2 * (point[2] + unplaced.fDz);
0999 
1000   // Points E, F on the generators (A, C) and (B, D) at z = point[2]
1001 #if GENTRAP_USE_HYBRID_COEFFS
1002   const auto &A  = unplaced.fVertices[i];
1003   const auto &G0 = unplaced.fG0[i];                // B - A
1004   const auto &E0 = unplaced.fE0[i];                // C - A
1005   const auto &E1 = unplaced.fE1[i];                // D - B
1006   auto E         = A + v * G0;                     // (1-v)A + vB
1007   auto EF        = (Real_v(1.) - v) * E0 + v * E1; // F - E but stable form
1008 #else
1009   auto j  = (i + 1) % 4;
1010   auto E  = (1. - v) * unplaced.fVertices[i] + v * unplaced.fVertices[i + 4];
1011   auto F  = (1. - v) * unplaced.fVertices[j] + v * unplaced.fVertices[j + 4];
1012   auto EF = F - E;
1013 #endif
1014   auto EP = point - E;
1015 
1016   // Project along EF to obtain u in [0,1]
1017   auto u = EP.Dot(EF) / NonZero(EF.Dot(EF));
1018   return Vector2D<Real_v>(u, v);
1019 }
1020 
1021 //______________________________________________________________________________
1022 template <typename Real_v>
1023 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE bool GenTrapImplementation::InSurfLimits(UnplacedStruct_t const &unplaced,
1024                                                                                       Vector3D<Real_v> const &point,
1025                                                                                       int isurf)
1026 {
1027   auto uv = XYZtoUV(unplaced, point, isurf);
1028   // Tolerant test against the unit square in (u,v). This admits points that
1029   // are numerically on the boundaries (within kToleranceDist) as “in”.
1030   bool inside = (uv[0] > -kToleranceDist<Real_v>) && (uv[0] < Real_v(1.) + kToleranceDist<Real_v>) &&
1031                 (uv[1] > -kToleranceDist<Real_v>) && (uv[1] < Real_v(1.) + kToleranceDist<Real_v>);
1032   return inside;
1033 }
1034 
1035 //______________________________________________________________________________
1036 template <typename Real_v>
1037 VECGEOM_FORCE_INLINE VECCORE_ATT_HOST_DEVICE Real_v GenTrapImplementation::SafetyArb4(UnplacedStruct_t const &unplaced,
1038                                                                                       Vector3D<Real_v> const &point,
1039                                                                                       Real_v safmax, bool inside)
1040 {
1041   // See: https://gitlab.cern.ch/geant4/geant4-dev/-/merge_requests/5261
1042   Real_v safety = safmax;
1043 
1044   // Aggregate per-face conservative signed safeties
1045   VECGEOM_PRAGMA_UNROLL(4)
1046   for (int i = 0; i < 4; i++) {
1047     if (!unplaced.IsDegenerated(i) && !unplaced.IsTwisted(i)) {
1048       // Planar faces: signed distance to plane using stored outward normal
1049       Real_v sface = unplaced.fSurf[i].EvaluatePlanar(point);
1050       safety       = Max(safety, sface);
1051     }
1052   }
1053 
1054   VECGEOM_PRAGMA_UNROLL(4)
1055   // Twisted bilinear faces: use Lipschitz bound of the implicit function
1056   // Note: We could replace this for slightly twisted faces with the much cheaper
1057   //       but less precise: unplaced.fMesh[i].FastSignedSafety(point, inside);
1058   for (int i = 0; i < 4; i++) {
1059     if (!unplaced.IsDegenerated(i) && unplaced.IsTwisted(i)) {
1060       // Outside -> a face can only help if f(point) > 0
1061       if (!inside && unplaced.fSurf[i].Evaluate(point) <= Real_v(0)) continue;
1062       Real_v sface = unplaced.fSurf[i].SafetyLipschitz(point);
1063       safety       = Max(safety, sface);
1064     }
1065   }
1066 
1067   // Return signed conservative safety: negative when inside, positive outside.
1068   return (inside) ? -safety : safety;
1069 }
1070 
1071 } // namespace VECGEOM_IMPL_NAMESPACE
1072 } // namespace vecgeom
1073 
1074 #endif /* GENTRAPIMPLEMENTATION_H_ */