Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-17 08:35:37

0001 #ifndef VECGEOM_SURFACE_NAVIGATOR_H_
0002 #define VECGEOM_SURFACE_NAVIGATOR_H_
0003 
0004 #include <VecGeom/management/Logger.h>
0005 #include <VecGeom/surfaces/SurfData.h>
0006 #include <VecGeom/surfaces/Model.h>
0007 #include <VecGeom/surfaces/LogicEvaluator.h>
0008 #include <VecGeom/volumes/VolumeTree.h>
0009 #include <VecGeom/navigation/NavigationState.h>
0010 #include <VecGeom/base/Algorithms.h>
0011 
0012 #include <VecGeom/volumes/utilities/VolumeUtilities.h>
0013 #include <VecGeom/base/BVH.h>
0014 
0015 #include <iomanip>
0016 
0017 namespace vgbrep {
0018 namespace protonav {
0019 
0020 /// @brief Check the Inside for the VolumeShell object in local coordinates
0021 /// @param point Point in local volume coordinates
0022 /// @param volId Logical volume id
0023 /// @param surfdata Surface data storage
0024 /// @param logic_id Logical id entering/exiting surface for which the logic is known
0025 /// @param is_inside whether the known entering/exiting surface is inside or not
0026 /// @return Boolean value representing if the point is inside the VolumeShell
0027 template <typename Real_t>
0028 VECCORE_ATT_HOST_DEVICE VECGEOM_FORCE_INLINE bool LogicInsideLocal(vecgeom::Vector3D<Real_t> const &localpoint,
0029                                                                    int volId, SurfData<Real_t> const &surfdata,
0030                                                                    const int logic_id   = vecgeom::kMaximumInt,
0031                                                                    const bool is_inside = 0)
0032 {
0033   auto const &logic = surfdata.fShells[volId].fLogic;
0034   // Evaluate volume shell logic
0035   auto inside = EvaluateInside(localpoint, volId, logic, surfdata, logic_id, is_inside);
0036   return inside;
0037 }
0038 
0039 /// @brief Check the Inside for the VolumeShell object associated with a touchable
0040 /// @param point Point in global coordinates
0041 /// @param in_state Navigation state associated with the touchable
0042 /// @param logic_id Logical id of entering/exiting surface for which the logic is known
0043 /// @param is_inside whether the known entering/exiting surface is inside or not
0044 /// @return Boolean value representing if the point is inside the VolumeShell
0045 template <typename Real_t>
0046 VECCORE_ATT_HOST_DEVICE VECGEOM_FORCE_INLINE bool LogicInside(vecgeom::Vector3D<Real_t> const &point,
0047                                                               vecgeom::NavigationState const &in_state,
0048                                                               SurfData<Real_t> const &surfdata,
0049                                                               const int logic_id   = vecgeom::kMaximumInt,
0050                                                               const bool is_inside = 0)
0051 {
0052   // Convert point in local VolumeShell coordinates
0053   Vector3D<Real_t> localpoint;
0054   vecgeom::Transformation3DMP<Real_t> trans;
0055   in_state.TopMatrix(trans);
0056   trans.Transform(point, localpoint);
0057   auto volId  = in_state.GetLogicalId();
0058   auto inside = LogicInsideLocal(localpoint, volId, surfdata, logic_id, is_inside);
0059   return inside;
0060 }
0061 
0062 /// @brief Function checking is a given framed surface is exited at a distance from a global point
0063 /// @param point Global point
0064 /// @param direction Global direction
0065 /// @param distance Crossing distance
0066 /// @param onsurf Propagated point on surface in the surface coordinate frame
0067 /// @param exited_state State beeing exited
0068 /// @param framedsurf Framed surface to be checked
0069 /// @param last_bool_state Set as last Boolean state checked if this is a Boolean surface
0070 /// @param surfdata Surface data storage
0071 /// @return Exiting the frame
0072 template <typename Real_t>
0073 VECCORE_ATT_HOST_DEVICE bool IsExitingFrame(
0074     Vector3D<Real_t> const &point, Vector3D<Real_t> const &direction, Real_t distance, Vector3D<Real_t> const &onsurf,
0075     vecgeom::NavigationState const &exited_state,
0076     FramedSurface<Real_t, vecgeom::Transformation2DMP<Real_t>> const &framedsurf, NavIndex_t &last_bool_state,
0077     SurfData<Real_t> const &surfdata)
0078 {
0079   constexpr Real_t kPushDistance = 1000 * vecgeom::kToleranceDist<Real_t>;
0080   bool inframe                   = framedsurf.fNeverCheck ? true : framedsurf.InsideFrame(onsurf, surfdata);
0081   if (inframe && framedsurf.fLogicId) {
0082     last_bool_state = framedsurf.fState;
0083     // Frame cross does not guarantee a real surface cross in case of Booleans
0084     // For a real exiting, the post-crossing point must be outside the Boolean
0085     auto pushedPoint = std::is_same<Real_t, double>::value
0086                            ? point + (distance + kPushDistance) * direction
0087                            : point + (distance + vecgeom::kRelTolerance<Real_t>(distance + point.Mag())) * direction;
0088     // The logic for the frame that is exited can be set to false without a numerical check
0089     auto inside = LogicInside(pushedPoint, exited_state, surfdata, framedsurf.fLogicId, false);
0090     if (inside) inframe = false;
0091   }
0092   return inframe;
0093 }
0094 
0095 /// @brief Find the topmost exited frame on the common surface pointed by crossed_surf
0096 /// @param crossed_surf Locator of the first frame to be checked
0097 /// @param frame_hit The first checked frame is known to be hit
0098 /// @param point Global point
0099 /// @param direction Global direction
0100 /// @param distance Distance from global point to the surface
0101 /// @param onsurf Point on the CS, in the surface reference frame
0102 /// @param surfdata Surface data storage
0103 /// @param exiting_scene Input: A scene is exited, false if not known
0104 /// @param exiting_scene Output: The exited frame has a TOP_SCENE state
0105 /// @param surf_index Output: Surface index of the topmost exited frame
0106 /// @param crossed_surf Output: Locator of the crossed framed surface, including the state after exiting
0107 /// @param traversal Output: traversal frame index on the other side of the crossed surface
0108 /// @return A frame was exited
0109 template <typename Real_t>
0110 VECCORE_ATT_HOST_DEVICE bool CheckFramesExiting(FSlocator &crossed_surf, bool frame_hit, Vector3D<Real_t> const &point,
0111                                                 Vector3D<Real_t> const &direction, Real_t distance,
0112                                                 Vector3D<Real_t> const &onsurf, SurfData<Real_t> const &surfdata,
0113                                                 bool &exiting_scene, int &surf_index, int &traversal)
0114 {
0115   constexpr NavIndex_t kInvalidState = NavIndex_t(-1);
0116 
0117   traversal                 = -3; // not known
0118   int isurf                 = crossed_surf.GetCSindex();
0119   auto const &surf          = surfdata.fCommonSurfaces[isurf];
0120   bool left_side            = crossed_surf.IsLeftSide();
0121   bool is_scene_surface     = surf.IsSceneSurface();
0122   auto const state          = crossed_surf.state;
0123   auto in_navind            = state.GetNavIndex();
0124   NavIndex_t top_exit_state = NavIndex_t(-1);
0125   // We need to check frame intersection
0126   // This is an exiting surface for in_state
0127   // First check the frame of the current state on this surface
0128   auto const &exit_side = left_side ? surf.fLeftSide : surf.fRightSide;
0129   // Get the index of the first framed surface on the exit side.
0130   int frameind_start = crossed_surf.frame_id;
0131   // If the current touchable is exited on this surface, it MUST be through the inside of the
0132   // corresponding frames. Loop all frames coming from the same touchable
0133   bool inframe       = frame_hit;
0134   int parent_ind     = -1;
0135   bool embedded      = true;
0136   bool traversal_set = false;
0137   if (frame_hit) {
0138     traversal     = exit_side.GetSurface(frameind_start, surfdata).fTraversal;
0139     traversal_set = true;
0140   }
0141 
0142   NavIndex_t last_bool_state = kInvalidState; // last checked Boolean state exited
0143   auto exited_state          = state;
0144 
0145   auto onsurf_crt = onsurf;
0146   if (exiting_scene) {
0147     // Moving to a parent scene, we need to recompute the local point on surface
0148     vecgeom::Transformation3DMP<Real_t> scene_trans;
0149     state.SceneMatrix(scene_trans);
0150     auto local_scene = scene_trans.Transform(point + distance * direction);
0151     onsurf_crt       = surf.fTrans.Transform(local_scene);
0152   }
0153 
0154   // Adjust the state to reflect the topmost exited frame
0155   auto setTopExited = [&](FramedSurface<Real_t, vecgeom::Transformation2DMP<Real_t>> const &framed_surf, int ind) {
0156     surf_index = framed_surf.fSurfIndex;
0157     parent_ind = framed_surf.fParent;
0158     framed_surf.GetParentState(top_exit_state);
0159     exiting_scene = is_scene_surface && (framed_surf.fState == 0);
0160     embedded      = framed_surf.fEmbedded;
0161     crossed_surf.Set(isurf, ind, left_side);
0162     if (!traversal_set) {
0163       traversal     = framed_surf.fTraversal;
0164       traversal_set = true;
0165     }
0166   };
0167 
0168   for (SideIterator it(exit_side, onsurf_crt, surfdata, 1, frameind_start); !it.Done(); ++it) {
0169     auto ind               = it();
0170     auto const &framedsurf = exit_side.GetSurface(ind, surfdata);
0171     // The deepest exited frame surface must belong to the input state
0172     if (!exiting_scene && !inframe && framedsurf.fState != in_navind) continue;
0173     // Skip if this is an already checked Boolean
0174     if (framedsurf.fState == last_bool_state) continue;
0175     // Check if the frame mask is crossed
0176     bool inframe_tmp = (ind == frameind_start) && inframe;
0177     if (!inframe_tmp)
0178       inframe_tmp = IsExitingFrame<Real_t>(point, direction, distance, onsurf_crt, exited_state, framedsurf,
0179                                            last_bool_state, surfdata);
0180     if (inframe_tmp) {
0181       // The frame is exited
0182       inframe = true;
0183       // Cache the top exited state and local surface index for the exited surface
0184       setTopExited(framedsurf, ind);
0185       // If this frame has no parent this is the topmost exited one
0186       if (parent_ind < 0) break;
0187       // loop over parent frames
0188       while (parent_ind > 0) {
0189         // next parent to be checked in case the frame is not embedded is parent_ind
0190         it.SetNextIndex(parent_ind);
0191         // Not embedded frames must be checked thoroughly
0192         // Note: frames could be non-embedded because they are extruding overlaps. This can be confirmed if the parent
0193         // surface is embedding. In that case, the parent is exited as well. For these extruding overlaps the
0194         // crossed_surf must be set to the highest exited frame for the overlap detection to work
0195         auto const &parent_framedsurf = exit_side.GetSurface(parent_ind, surfdata);
0196         if (!embedded && !parent_framedsurf.fEmbedding) break;
0197         // The frame is embedded in the parent, so the parent is also exited
0198         setTopExited(parent_framedsurf, parent_ind);
0199         if (parent_framedsurf.fState)
0200           exited_state.SetNavIndex(parent_framedsurf.fState);
0201         else
0202           exited_state.PopScene();
0203       }
0204       if (embedded) break;
0205     }
0206   }
0207   if (inframe) {
0208     // backup exited state
0209     crossed_surf.state = state;
0210     crossed_surf.state.SetLastExited();
0211     // set navigation state to top exit state which is either the surf.fDefaultState or a boolean in case an exiting
0212     // frame was found that does not exit through the default state but stays within the boolean solid
0213     crossed_surf.state.SetNavIndex(top_exit_state);
0214     crossed_surf.state.SetBoundaryState(true);
0215   }
0216   return inframe;
0217 }
0218 
0219 /// @brief Find the deepest hit child frame knowing that the parent is also hit
0220 /// @param side Side to search for hit frames
0221 /// @param in_state Initial state of the track
0222 /// @param in_navind Scene navigation index corresponding to the initial state
0223 /// @param is_scene The initial state is in a scene volume
0224 /// @param is_scene_surface The CS is a scene surface
0225 /// @param onsurf_local Point propagated on the CS the side belongs to, in local CS coordinates
0226 /// @param pushed_point Pushed propagated point in global coordinated, for logical frame checks
0227 /// @param surfdata Surface data storage
0228 /// @return Index of frame being hit
0229 template <typename Real_t>
0230 VECCORE_ATT_HOST_DEVICE int FindFrameOnEnteringSide(Side const &side, vecgeom::NavigationState const &in_state,
0231                                                     NavIndex_t in_navind, bool is_scene, bool is_scene_surface,
0232                                                     Vector3D<Real_t> const &onsurf_local,
0233                                                     Vector3D<Real_t> const &pushed_point,
0234                                                     SurfData<Real_t> const &surfdata, int &ihit)
0235 {
0236   // Cached last checked Boolean volume and the inside on the pushed point
0237   bool found      = ihit >= 0;
0238   int ifound      = ihit;
0239   bool lastinside = false;
0240   int lastbool    = -1;
0241   // Lambda for getting the first frame with same state as the provided one
0242   auto getFirstParent = [&](int iframe) {
0243     int ifirst    = iframe;
0244     auto refState = side.GetSurface(iframe, surfdata).fState;
0245     for (auto ind = iframe - 1; ind >= 0; --ind) {
0246       if (side.GetSurface(ind, surfdata).fState == refState)
0247         ifirst = ind;
0248       else
0249         return ifirst;
0250     }
0251     return ifirst;
0252   };
0253 
0254   // Lambda for checking if the pushed point is in the Boolean volume
0255   auto insideBoolean = [&](FramedSurface<Real_t, vecgeom::Transformation2DMP<Real_t>> const &surf) {
0256     auto checked_state = in_state;
0257     if (surf.fState) {
0258       if (is_scene_surface && checked_state.GetNavIndex() > 0)
0259         checked_state.PushScene(surf.fState);
0260       else
0261         checked_state.SetNavIndex(surf.fState);
0262     }
0263     int ivol = checked_state.GetLogicalId();
0264     if (lastbool == ivol) return lastinside;
0265     // The logic for the frame that is entered can be set to true without a numerical check
0266     lastbool   = ivol;
0267     lastinside = LogicInside(pushed_point, checked_state, surfdata, surf.fLogicId, true);
0268     return lastinside;
0269   };
0270 
0271   // loop frames, parents first, starting from the hit parent
0272   int iparent = ifound;
0273   if (found) iparent = getFirstParent(ifound);
0274   int indstart = (iparent >= 0) ? iparent - 1 : side.fNsurf - 1;
0275   for (SideIterator it(side, onsurf_local, surfdata, -1, indstart); !it.Done(); --it) {
0276     auto ind               = it();
0277     auto const &framedsurf = side.GetSurface(ind, surfdata);
0278     // NeverCheck frames are always hit
0279     if (framedsurf.fNeverCheck) {
0280       ihit = ind;
0281       return ind;
0282     }
0283     // Check only direct children if a frame was already found
0284     if (found && framedsurf.fParent != iparent) continue;
0285     // Skip same state frames unless this is a side of a scene surface
0286     if (!is_scene_surface && framedsurf.fState == in_navind) continue;
0287     // Hitting a topscene frame starting from the same scene must be discarded
0288     if (is_scene && framedsurf.fState == 0) continue;
0289     // Skip embedded children if a parent was not yet found,
0290     // unless the parent is virtual, then we need to check the children since the parent will not be found
0291     if (!found && framedsurf.fParent >= 0)
0292       if ((framedsurf.fEmbedded && !(framedsurf.fVirtualParent)) ||
0293           side.GetSurface(framedsurf.fParent, surfdata).fEmbedding)
0294         continue;
0295 
0296     //=== Do the real check ===//
0297     auto inframe = framedsurf.InsideFrame(onsurf_local, surfdata);
0298     // For Booleans make sure we don't cross a virtual frame
0299     if (inframe && framedsurf.fLogicId) inframe = insideBoolean(framedsurf);
0300     if (inframe) {
0301       found   = true;
0302       ifound  = ind;
0303       iparent = getFirstParent(ifound);
0304       // Set index of the first frame being hit
0305       if (ihit < 0) ihit = ind;
0306     }
0307   }
0308   return ifound;
0309 }
0310 
0311 /// @brief Computes isotropic safety for the logic expression of a volume
0312 /// @param localpoint Point in local volume coordinates
0313 /// @param lvol_id LogicalVolume id
0314 /// @param surfdata
0315 /// @return isotropic safety value
0316 template <typename Real_t>
0317 VECCORE_ATT_HOST_DEVICE VECGEOM_FORCE_INLINE Real_t LocalLogicSafety(vecgeom::Vector3D<Real_t> const &localpoint,
0318                                                                      bool exiting, int lvol_id,
0319                                                                      SurfData<Real_t> const &surfdata,
0320                                                                      Real_t safe_max) // Temporarily non default
0321 {
0322   auto const &logic = surfdata.fShells[lvol_id].fLogic;
0323   Real_t safety     = EvaluateSafety(localpoint, lvol_id, exiting, logic, surfdata, safe_max);
0324   return safety;
0325 }
0326 
0327 /// @brief Computes isotropic safety for the logic expression of a volume
0328 /// @param point Point in global coordinates
0329 /// @param in_state
0330 /// @param surfdata
0331 /// @return isotropic safety value
0332 template <typename Real_t>
0333 VECCORE_ATT_HOST_DEVICE VECGEOM_FORCE_INLINE Real_t LogicSafety(vecgeom::Vector3D<Real_t> const &point, bool exiting,
0334                                                                 vecgeom::NavigationState const &in_state,
0335                                                                 SurfData<Real_t> const &surfdata,
0336                                                                 Real_t safe_max = vecgeom::InfinityLength<Real_t>())
0337 {
0338   // Convert point in local VolumeShell coordinates
0339   Vector3D<Real_t> localpoint;
0340   vecgeom::Transformation3DMP<Real_t> trans;
0341   in_state.TopMatrix(trans);
0342   trans.Transform(point, localpoint);
0343   auto volId        = in_state.GetLogicalId();
0344   auto const &logic = surfdata.fShells[volId].fLogic;
0345   Real_t safety     = EvaluateSafety(localpoint, volId, exiting, logic, surfdata, safe_max);
0346   return safety;
0347 }
0348 
0349 /// @brief Locate a point in a volume sub-hierarchy
0350 /// @tparam Real_t Floating point type
0351 /// @param iplaced Index of the placed Volume to start checking from
0352 /// @param point Point in local volume coordinates
0353 /// @param path Path pointing to the top volume to check
0354 /// @param surfdata Surface data storage
0355 /// @param top The top volume must be checked also
0356 /// @param exclude Index of the placed volume to exclude from checking
0357 /// @return Placed volume pointer containing the point
0358 template <typename Real_i, typename Real_t>
0359 VECCORE_ATT_HOST_DEVICE int LocatePointIn(int iplaced, vecgeom::Vector3D<Real_i> const &point_i,
0360                                           vecgeom::NavigationState &path, bool top, int exclude = -1)
0361 {
0362   using NavigationState = vecgeom::NavigationState;
0363   auto const &surfdata  = SurfData<Real_t>::Instance();
0364   vecgeom::Vector3D<Real_t> point(point_i);
0365 
0366   if (top) {
0367     VECGEOM_ASSERT(iplaced >= 0);
0368     auto ivol   = NavigationState::ToPlacedId(iplaced).fVolume.fId;
0369     auto inside = LogicInsideLocal(point, ivol, surfdata);
0370     if (!inside) return -1;
0371   }
0372 
0373   auto currentvolume = iplaced;
0374   path.Push(currentvolume);
0375 
0376   bool godeeper;
0377   do {
0378     godeeper         = false;
0379     auto const &lvol = NavigationState::ToPlacedId(currentvolume).fVolume;
0380     for (auto i = 0; i < lvol.fNplaced; ++i) {
0381       auto daughter = lvol.fChildren[i].fId;
0382       if (daughter == exclude) continue;
0383       path.PushDaughter(i);
0384       auto inside = LogicInside(point, path, surfdata);
0385 
0386       if (inside) {
0387         currentvolume = daughter;
0388         godeeper      = true;
0389         break;
0390       } else {
0391         path.Pop();
0392       }
0393     }
0394     // Only exclude the placed volume once since we could enter it again via a
0395     // different volume history.
0396     exclude = -1;
0397   } while (godeeper);
0398 
0399   return currentvolume;
0400 }
0401 
0402 /// @brief Check whether
0403 /// @tparam Real_t Floating point type
0404 /// @param path Path pointing to the top volume to check
0405 /// @param crossed_surf FSLocator holding the highest exited framed surface
0406 /// @return Placed volume pointer containing the point
0407 template <typename Real_t>
0408 VECCORE_ATT_HOST_DEVICE bool VolumeHasCommonSurface(vecgeom::NavigationState &path, FSlocator const &crossed_surf)
0409 {
0410 
0411   // always included in outside
0412   if (path.GetNavIndex() == 0) return false;
0413   // for extruding overlaps no common surface needs to be excluded
0414   if (crossed_surf.GetFSindex() == -1) return false;
0415 
0416   auto const &surfdata  = SurfData<Real_t>::Instance();
0417   auto const &surf      = surfdata.fCommonSurfaces[crossed_surf.GetCSindex()];
0418   auto const &exit_side = crossed_surf.IsLeftSide() ? surf.fLeftSide : surf.fRightSide;
0419 
0420   auto state_id = path.GetNavIndex();
0421 
0422   // non-embedded surfaces cannot be excluded based on this approach, since this could exclude the correct parent that
0423   // is entered through the non-embedded part of the frame.
0424   if (!surfdata.IsFramedSurfaceEmbedded(crossed_surf) && surfdata.FramedSurfaceParentInd(crossed_surf) >= 0)
0425     return false;
0426 
0427   // loop over all framed surfaces to see if booleans are on the common surface
0428   // booleans can have the same state while having a different framed surface, so they need a full inside check
0429   // for (int isurf = 0; isurf < exit_side.fNsurf; isurf++) {
0430   //   if (surfdata.fFramedSurf[exit_side.fSurfaces[isurf]].fLogicId) return false;
0431   // }
0432 
0433   // loop over all states of the exited common surface and exclude volumes of that state
0434   // NOTE: this might not be correct for booleans
0435   for (int isurf = 0; isurf < exit_side.fNsurf; isurf++) {
0436     if (surfdata.fFramedSurf[exit_side.fSurfaces[isurf]].fState == state_id) return true;
0437   }
0438   return false;
0439 }
0440 
0441 /// @brief Locate a point in a volume sub-hierarchy
0442 /// @tparam Real_t Floating point type
0443 /// @param starting_path initial path before the ComputeStepAndHit
0444 /// @param point Point in local volume coordinates
0445 /// @param direction Direction in local volume coordinates
0446 /// @param path Path pointing to the top volume to check
0447 /// @param crossed_surf FSLocator holding the highest exited framed surface
0448 /// @param distance distance of the last step
0449 /// @return Placed volume pointer containing the point
0450 template <typename Real_i, typename Real_t>
0451 VECCORE_ATT_HOST_DEVICE int ReLocatePointIn(vecgeom::NavigationState &starting_path,
0452                                             vecgeom::Vector3D<Real_i> const &point_i,
0453                                             vecgeom::Vector3D<Real_i> const &direction_i,
0454                                             vecgeom::NavigationState &path, FSlocator const &crossed_surf,
0455                                             Real_i distance_i)
0456 {
0457   using PlacedId        = vecgeom::PlacedId;
0458   using NavigationState = vecgeom::NavigationState;
0459   using ESolidType      = vecgeom::ESolidType;
0460   auto const &surfdata  = SurfData<Real_t>::Instance();
0461 
0462   vecgeom::Vector3D<Real_t> point(point_i);
0463   vecgeom::Vector3D<Real_t> direction(direction_i);
0464   Real_t distance = static_cast<Real_t>(distance_i);
0465 
0466   // set path to be starting path to check for daughters
0467   path               = starting_path;
0468   auto currentvolume = starting_path.TopId();
0469   auto placedId      = [](int iplaced) -> PlacedId const      &{ return NavigationState::ToPlacedId(iplaced); };
0470   auto prev_volume   = path.GetLastIdExited();
0471 
0472   constexpr Real_t kPushDistance = 1000 * vecgeom::kToleranceDist<Real_t>;
0473   bool is_boolean                = false;
0474   bool is_self_entering = false; // this flag is intended for booleans that we start in and don't exit because we cross
0475                                  // a virtual surface within the boolean
0476   // in case of 0 steps, a push is needed to exclude the previously exited volume
0477   bool is_zero_step = distance < 10000 * vecgeom::kToleranceDist<Real_t>;
0478   auto final_point  = is_zero_step ? point + kPushDistance * direction : point;
0479 
0480   int logic_id_exit = vecgeom::kMaximumInt;
0481 
0482   // first, check daughter volumes of the current path to find if the point lies within any of the daughters
0483   bool godeeper;
0484   bool inside_daughter = false;
0485   if (crossed_surf.GetFSindex() != -1) { // if not exiting surface was found, we don't need to check for children
0486     do {
0487       godeeper = false;
0488       for (auto i = 0; i < placedId(currentvolume).fVolume.fNplaced; ++i) {
0489         auto const &daughter = placedId(currentvolume).fVolume.fChildren[i];
0490         is_boolean           = daughter.fVolume.fSolidType == ESolidType::boolean;
0491         // if (daughter.fId == prev_volume && !is_boolean) {
0492         //   // Only exclude the placed volume once since we could enter it again via a
0493         //   // different volume history.
0494         //   prev_volume = -1;
0495         //   continue;
0496         // }
0497         if (is_boolean) {
0498           final_point = point + kPushDistance * direction;
0499           // logic_id_exit = surfdata.FramedSurfaceLogicId(crossed_surf);
0500         }
0501         path.PushDaughter(i);
0502         bool inside = LogicInside(final_point, path, surfdata); //, logic_id_exit, false);
0503         final_point = is_zero_step ? point + kPushDistance * direction : point;
0504         //        logic_id_exit = -1;
0505 
0506         if (inside) {
0507           inside_daughter = true;
0508           currentvolume   = daughter.fId;
0509           godeeper        = true;
0510           break;
0511         } else {
0512           path.Pop();
0513         }
0514       }
0515     } while (godeeper);
0516   }
0517 
0518   // if point was located in daughter and this is not the previous starting path, return
0519   if (inside_daughter && !path.HasSamePathAsOther(starting_path)) return currentvolume;
0520 
0521   if (crossed_surf.GetFSindex() != -1 && crossed_surf.GetCSindex() != 0) {
0522 
0523     // reset to highest exited state
0524     path = crossed_surf.state;
0525     // set to the state of the exited surface itself
0526     path.SetNavIndex(surfdata.FramedSurfaceState(crossed_surf));
0527   } else {
0528     path = starting_path;
0529   }
0530 
0531   // we need to check if the exiting framed surface belongs to a boolean.
0532   // in case of a boolean, an exiting surface does not necessarily mean that the full boolean is exited,
0533   // thus, in that case we should not go up in the path before searching in the boolean itself
0534   // if (surfdata.FramedSurfaceLogicId(crossed_surf)) {
0535   //   auto pushedPoint = point + kPushDistance * direction;
0536   //   vecgeom::NavigationState boolean_state;
0537   //   boolean_state = path;
0538   //   in_boolean    = LogicInside(pushedPoint, boolean_state, surfdata, 0, false);
0539   // }
0540 
0541   currentvolume = path.TopId();
0542   VECGEOM_VALIDATE(
0543       currentvolume >= 0, << " currentvolume is nullptr in overlap detection! Most likely due to incorrect path!");
0544   is_boolean = placedId(currentvolume).fVolume.fSolidType == ESolidType::boolean;
0545 
0546   if (!is_boolean) {
0547     // exclude volume of the highest parent of the exited framed surface
0548     if (path.GetNavIndex() > 1) path.SetLastExited();
0549     prev_volume = path.GetLastIdExited();
0550     // navigate one level higher to search for inside
0551     if (path.GetNavIndex() > 1) path.Pop(); // go one level higher, unless we are in the top volume
0552   } else {
0553     prev_volume      = starting_path.TopId();
0554     is_self_entering = true;
0555   }
0556 
0557   currentvolume = path.TopId();
0558   VECGEOM_VALIDATE(
0559       currentvolume >= 0, << " currentvolume is nullptr in overlap detection! This might due to incorrect path "
0560                              "or due to a surface previously being falsely flagged as overlapping!");
0561   is_boolean = placedId(currentvolume).fVolume.fSolidType == ESolidType::boolean;
0562 
0563   // check whether the point is in the parent volume, otherwise go higher until it is found
0564   bool gohigher = false;
0565 
0566   bool inside;
0567   do {
0568     gohigher     = false;
0569     auto same_cs = VolumeHasCommonSurface<Real_t>(path, crossed_surf);
0570     if (same_cs && !is_boolean) {
0571       inside = false;
0572     } else {
0573 
0574       // if (vecgeom::BooleanHelper::GetBooleanStruct(currentvolume->GetUnplacedVolume())) {
0575       if (placedId(currentvolume).fVolume.fSolidType == ESolidType::boolean) {
0576         final_point   = point + kPushDistance * direction;
0577         logic_id_exit = surfdata.FramedSurfaceLogicId(crossed_surf);
0578       }
0579 
0580       inside = LogicInside(final_point, path, surfdata, logic_id_exit, false);
0581       // in the case of surfaces that are closer than the push distance together, it can happen that a boolean is exited
0582       // into another boolean. however, the other boolean extents only with a very small distance into the next one, so
0583       // a push would mark this as false, leading to an incorrect overlap therefore, we need to check whether the point
0584       // is with and without a push inside the next boolean. however, we should not do this check when we check whether
0585       // we are staying within the same boolean because without the push this would always return true, although we are
0586       // exiting the boolean
0587       if (placedId(currentvolume).fVolume.fSolidType == ESolidType::boolean && !is_self_entering)
0588         inside |= LogicInside(point, path, surfdata, logic_id_exit, false);
0589       final_point   = is_zero_step ? point + kPushDistance * direction : point;
0590       logic_id_exit = vecgeom::kMaximumInt;
0591     }
0592 
0593     if (inside == false) {
0594       prev_volume = currentvolume;
0595       path.Pop();
0596       currentvolume = path.TopId();
0597       VECGEOM_ASSERT(
0598           currentvolume >= 0 &&
0599           " currentvolume is nullptr in overlap detection! That means some inside call failed or was falsely excluded");
0600       gohigher = true;
0601     }
0602   } while (gohigher);
0603 
0604   do {
0605     godeeper = false;
0606     for (auto i = 0; i < placedId(currentvolume).fVolume.fNplaced; ++i) {
0607       auto const &daughter = placedId(currentvolume).fVolume.fChildren[i];
0608       is_boolean           = daughter.fVolume.fSolidType == ESolidType::boolean;
0609       if (daughter.fId == prev_volume && !is_boolean) {
0610         // Only exclude the placed volume once since we could enter it again via a
0611         // different volume history.
0612         prev_volume = -1;
0613         continue;
0614       }
0615       path.PushDaughter(i);
0616       auto same_cs = VolumeHasCommonSurface<Real_t>(path, crossed_surf);
0617       if (same_cs && !is_boolean) {
0618         inside = false;
0619       } else {
0620 
0621         if (is_boolean) {
0622           final_point = point + kPushDistance * direction;
0623           if (daughter.fId == prev_volume) {
0624             logic_id_exit = surfdata.FramedSurfaceLogicId(crossed_surf);
0625           }
0626         }
0627 
0628         inside        = LogicInside(final_point, path, surfdata, logic_id_exit, false);
0629         final_point   = is_zero_step ? point + kPushDistance * direction : point;
0630         logic_id_exit = -1;
0631       }
0632       if (inside) {
0633         currentvolume = daughter.fId;
0634         godeeper      = true;
0635         break;
0636       } else {
0637         path.Pop();
0638       }
0639     }
0640   } while (godeeper);
0641 
0642   return currentvolume;
0643 }
0644 
0645 /// @brief Find the deepest hit frame being entered on a CS side
0646 /// @param hit_frame Must contain the input state, candidate CS index and side, initial hit frame if any
0647 /// @param point Point in global coordinates
0648 /// @param direction Direction in global coordinates
0649 /// @param hit_dist Hit distance to the unplaced surface
0650 /// @param onsurf_local Point propagated on the CS the side belongs to, in local CS coordinates
0651 /// @param out_frame Locator for the final hit frame
0652 /// @return A frame was hit flag. Fills hit_frame.frame_id and out_frame, pointing to the final state crossed into
0653 template <typename Real_t>
0654 VECCORE_ATT_HOST_DEVICE bool EnterCS(FSlocator &hit_frame, Vector3D<Real_t> const &point,
0655                                      Vector3D<Real_t> const &direction, Real_t hit_dist,
0656                                      Vector3D<Real_t> const &onsurf_local, FSlocator &out_frame, int traversal)
0657 {
0658   constexpr Real_t kPushDistance = 1000 * vecgeom::kTolerance;
0659   auto const &surfdata           = SurfData<Real_t>::Instance();
0660   auto const &in_state           = hit_frame.state;
0661   unsigned short scene_id = 0, newscene_id = 0;
0662   bool is_scene            = in_state.GetSceneId(scene_id, newscene_id);
0663   auto in_navind           = in_state.GetNavIndex();
0664   auto pushed_point        = std::is_same<Real_t, double>::value
0665                                  ? point + (hit_dist + kPushDistance) * direction
0666                                  : point + (hit_dist + vecgeom::kRelTolerance<Real_t>(hit_dist + point.Mag())) * direction;
0667   auto const &surf_crossed = surfdata.GetCommonSurface(hit_frame);
0668   auto const &enter_side   = surfdata.GetSide(hit_frame);
0669   // The task now is either to find the deepest frame knowing the hit frame, or to fully relocate on the entering side
0670   bool relocate = hit_frame.GetFSindex() < 0;
0671   int iframe    = -1;
0672 // #define SURF_DEBUG_TRAVERSALS
0673 #ifndef SURF_DEBUG_TRAVERSALS
0674   if (relocate && traversal > -2) {
0675     iframe = traversal;
0676   } else
0677 #endif
0678     iframe = FindFrameOnEnteringSide(enter_side, in_state, in_navind, is_scene, surf_crossed.IsSceneSurface(),
0679                                      onsurf_local, pushed_point, surfdata, hit_frame.frame_id);
0680   out_frame.Set(hit_frame.GetCSindex(), iframe, hit_frame.IsLeftSide());
0681   out_frame.state = in_state;
0682 #ifdef SURF_DEBUG_TRAVERSALS
0683   if (relocate) {
0684     if (iframe < 0) {
0685       if (traversal > -1)
0686         printf("Error on CS=%d leftside=%d frame=%d transition=%d   -> no hit\n", hit_frame.GetCSindex(),
0687                hit_frame.IsLeftSide(), hit_frame.GetFSindex(), traversal);
0688       VECGEOM_ASSERT(traversal <= -1);
0689     }
0690     if ((traversal == -1 && iframe > -1) || (traversal > -1 && iframe != traversal)) {
0691       printf("Error on CS=%d leftside=%d frame=%d transition=%d   ->   CS=%d leftside=%d found frame %d\n",
0692              hit_frame.GetCSindex(), !hit_frame.IsLeftSide(), hit_frame.GetFSindex(), traversal, out_frame.GetCSindex(),
0693              out_frame.IsLeftSide(), iframe);
0694       VECGEOM_ASSERT((traversal == -1 && iframe == -1) || (traversal > -1 && iframe == traversal));
0695     }
0696   }
0697 #endif
0698   if (iframe < 0) return false;
0699   while (iframe >= 0) {
0700     auto const &framedsurf = surfdata.GetFramedSurface(out_frame);
0701     // Update out_frame to point to the deppest frame being hit
0702     out_frame.state.SetLastExited();
0703     out_frame.state.SetBoundaryState(true);
0704     if (is_scene && out_frame.state.GetNavIndex() > 0)
0705       // Entering from a scene volume must push the scene (if state not already on the scene)
0706       out_frame.state.PushScene(framedsurf.fState);
0707     else
0708       // Normal entering within the scene
0709       out_frame.state.SetNavIndex(framedsurf.fState);
0710 
0711     iframe = -1;
0712     // If entering a scene volume, we need to scan the entered portal CS
0713     if (framedsurf.fSceneCS > 0) {
0714       // A top frame was hit on the portal
0715       out_frame.Set(framedsurf.fSceneCS, vecCore::math::Abs(framedsurf.fSceneCSind) - 1, framedsurf.fSceneCSind > 0);
0716       auto const &portal      = surfdata.GetCommonSurface(out_frame);
0717       auto const &portal_side = surfdata.GetSide(out_frame);
0718       // if the portal has no children, we are already in the final state, entering a scene volume
0719       if (!portal_side.HasChildren()) return true;
0720       // We need to do relocation on the portal, after recomputing onsurf
0721       vecgeom::Transformation3DMP<Real_t> scene_trans;
0722 
0723       // Is this a new scene
0724       is_scene = out_frame.state.GetSceneId(scene_id, newscene_id);
0725       if (is_scene) {
0726         out_frame.state.TopMatrix(scene_trans);
0727       } else {
0728         out_frame.state.SceneMatrix(scene_trans);
0729       }
0730 
0731       auto local_scene = scene_trans.Transform(point + hit_dist * direction);
0732       auto onsurf      = portal.fTrans.Transform(local_scene);
0733       iframe           = FindFrameOnEnteringSide(portal_side, out_frame.state, out_frame.state.GetNavIndex(), is_scene,
0734                                                  portal.IsSceneSurface(), onsurf, pushed_point, surfdata, out_frame.frame_id);
0735       if (iframe == out_frame.frame_id) return true;
0736       // A daughter frame was found on the portal, so continue the loop
0737       out_frame.frame_id = iframe;
0738     }
0739   }
0740   return true;
0741 }
0742 
0743 /// @brief Find the deepest hit frame being entered on a CS side
0744 /// @param hit_frame Must contain the input state, candidate CS index and side, initial hit frame if any
0745 /// @param point Point in global coordinates
0746 /// @param direction Direction in global coordinates
0747 /// @param hit_dist Hit distance to the unplaced surface
0748 /// @param onsurf_local Point propagated on the CS the side belongs to, in local CS coordinates
0749 /// @param out_frame Locator for the final hit frame
0750 /// @return Fills hit_frame.frame_id and out_frame, pointing to the final state crossed into
0751 template <typename Real_t>
0752 VECCORE_ATT_HOST_DEVICE bool ExitCS(FSlocator &hit_frame, bool is_hit, Vector3D<Real_t> const &point,
0753                                     Vector3D<Real_t> const &direction, Real_t hit_dist,
0754                                     Vector3D<Real_t> const &onsurf_local, FSlocator &hit_FS, FSlocator &out_frame)
0755 {
0756   auto const &surfdata = SurfData<Real_t>::Instance();
0757   int surf_index       = 0;
0758   int traversal        = -2;
0759   auto exiting_scene   = false;
0760   auto inframe         = CheckFramesExiting(hit_frame, is_hit, point, direction, hit_dist, onsurf_local, surfdata,
0761                                             exiting_scene, surf_index, traversal);
0762   if (!inframe) return false;
0763   // the current state is correctly exited, so there is a transition on this surface
0764   auto &out_state          = out_frame.state;
0765   hit_FS                   = hit_frame;
0766   out_state                = hit_FS.state;
0767   auto isurfcross          = hit_frame.GetCSindex();
0768   auto relocated_left_side = !hit_frame.IsLeftSide(); // opposite to exiting side
0769   auto onsurf              = onsurf_local;
0770   // Check if a scene surface was crossed
0771   while (exiting_scene) {
0772     out_state.PopScene();
0773     // Convert point in scene coordinates
0774     vecgeom::Transformation3DMP<Real_t> scene_trans;
0775     out_state.SceneMatrix(scene_trans);
0776     // Get the exit frame locator in the parent scene
0777     surfdata.SceneToTouchableLocator(out_state, surf_index, hit_FS);
0778     // Find the topmost exit frame on the portal
0779     hit_FS.state          = out_state;
0780     auto local_propagated = scene_trans.Transform(point + hit_dist * direction);
0781     inframe               = CheckFramesExiting(hit_FS, /*frame_hit=*/true, point, direction, hit_dist, onsurf, surfdata,
0782                                                exiting_scene, surf_index, traversal);
0783     isurfcross            = hit_FS.GetCSindex();
0784     relocated_left_side   = !hit_FS.IsLeftSide();
0785     out_state             = hit_FS.state;
0786     // Moving to a parent scene, we need to recompute the local point on surface
0787     auto const &surf_crossed = surfdata.GetCommonSurface(hit_FS);
0788     onsurf                   = surf_crossed.fTrans.Transform(local_propagated);
0789   }
0790   // Relocate on the other side of the CS
0791   hit_frame.Set(isurfcross, -1, relocated_left_side);
0792   // If nothing on the other side no need to relocate
0793   if (surfdata.GetSide(hit_frame).fNsurf == 0) return true;
0794   hit_frame.state = out_state;
0795   // Seek and cross entering frames
0796   EnterCS(hit_frame, point, direction, hit_dist, onsurf, out_frame, traversal);
0797   return true;
0798 }
0799 
0800 /// @brief Compute the distance to a local framed surface in case the frame is hit
0801 /// @tparam Real_t Precision type
0802 /// @param point_volume Point in volume coordinates
0803 /// @param direction_volume Direction in volume coordinates
0804 /// @param volId Id of the volume to which the framed surface belongs
0805 /// @param surfdata Surface data
0806 /// @param framedsurf Framed surface to be checked
0807 /// @param exiting The surface should be an exiting one
0808 /// @param surfhit Returned validity of the crossing
0809 /// @return Distance to unplaced surface
0810 template <typename Real_t>
0811 VECCORE_ATT_HOST_DEVICE Real_t DistanceToLocalFS(vecgeom::Vector3D<Real_t> const &point_volume,
0812                                                  vecgeom::Vector3D<Real_t> const &direction_volume, int volId,
0813                                                  SurfData<Real_t> const &surfdata,
0814                                                  FramedSurface<Real_t, TransformationMP<Real_t>> const &framedsurf,
0815                                                  bool exiting, bool &surfhit, Real_t &safety)
0816 {
0817   bool two_solutions             = false;
0818   constexpr Real_t kPushDistance = 1000 * vecgeom::kToleranceDist<Real_t>;
0819   // Convert point and direction to surface frame
0820   auto const &trans         = framedsurf.fTrans;
0821   Vector3D<Real_t> local    = trans.Transform(point_volume);
0822   Vector3D<Real_t> localdir = trans.TransformDirection(direction_volume);
0823   // Check if the surface is a flipped one
0824   bool flipped_bool = framedsurf.fLogicId < 0;
0825   bool visibility   = exiting ^ flipped_bool;
0826   // Compute distance taking into account the surface visibility (normal orientation)
0827   Real_t dist = vecgeom::InfinityLength<Real_t>();
0828   surfhit     = framedsurf.fSurface.Intersect(local, localdir, visibility, surfdata, dist, two_solutions, safety);
0829   // This is a protection against rays going parallel through a surface:
0830   // if an exact corner of two boxes is hit with a parallel ray, the ray could jump infinitely between two boxes with 0
0831   // steps
0832   if (surfhit && Abs(dist) < vecgeom::kTolerance && Abs(localdir[2]) < vecgeom::kToleranceDist<Real_t> && visibility) {
0833     surfhit = false;
0834     dist    = vecgeom::InfinityLength<Real_t>();
0835   }
0836   if (!surfhit) return dist;
0837   // Do the frame intersection using the propagated point on surface
0838   auto onsurf = local + localdir * dist;
0839   if (!framedsurf.fNeverCheck) surfhit = framedsurf.fFrame.Inside(onsurf, surfdata);
0840   if (!surfhit) return dist;
0841   // If the frame belongs to a Boolean, we need to check if the solid is really exited/entered
0842   if (framedsurf.fLogicId) {
0843     onsurf = std::is_same<Real_t, double>::value
0844                  ? point_volume + (dist + kPushDistance) * direction_volume
0845                  : point_volume + (dist + vecgeom::kRelTolerance<Real_t>(dist + point_volume.Mag())) * direction_volume;
0846     // The logic for the frame that is entered can be set to true without a numerical check
0847     auto inside = LogicInsideLocal(onsurf, volId, surfdata, framedsurf.fLogicId, !exiting);
0848     surfhit     = inside ^ exiting;
0849   }
0850 
0851   return dist;
0852 }
0853 
0854 /// @brief Compute the distance to the unplaced surface of a common surface
0855 /// @tparam Real_t Precision type
0856 /// @param point Point in scene coordinates
0857 /// @param direction Direction in scene coordinates
0858 /// @param surfdata Surface data
0859 /// @param exiting Is the surface an exiting candidate
0860 /// @param isurf Common surface index
0861 /// @param sides Visible sides of the common surface
0862 /// @param surfhit Returned validity of the crossing
0863 /// @param onsurf Point on surface in the CS frame
0864 /// @param dist2 distance to the possible second solution
0865 /// @param local point in common surface coordinate
0866 /// @param localdir direction in common surface coordinate
0867 /// @return Distance to unplaced surface
0868 template <typename Real_t>
0869 VECCORE_ATT_HOST_DEVICE Real_t DistanceToUnplaced(vecgeom::Vector3D<Real_t> const &point,
0870                                                   vecgeom::Vector3D<Real_t> const &direction,
0871                                                   SurfData<Real_t> const &surfdata, int isurf, char sides, bool exiting,
0872                                                   bool &left_side, bool &surfhit, vecgeom::Vector3D<Real_t> &onsurf,
0873                                                   Real_t &dist2, vecgeom::Vector3D<Real_t> &local,
0874                                                   vecgeom::Vector3D<Real_t> &localdir, Real_t &safety)
0875 {
0876   constexpr char kLside = 1;
0877   constexpr char kRside = 2;
0878   auto const &surf      = surfdata.fCommonSurfaces[isurf];
0879 
0880   // Convert point and direction to surface frame
0881   auto const &trans = surf.fTrans;
0882   local             = trans.Transform(point);
0883   localdir          = trans.TransformDirection(direction);
0884 
0885   // Compute distance to surface
0886   Real_t dist   = -vecgeom::InfinityLength<Real_t>();
0887   bool flipped  = false;
0888   auto unplaced = surfdata.GetUnplaced(isurf, flipped);
0889 
0890   left_side             = (sides & kLside) > 0;
0891   bool check_both_sides = left_side && (sides & kRside) > 0;
0892   bool visibility       = !exiting ^ left_side ^ flipped;
0893   bool two_solutions    = false;
0894   surfhit               = unplaced.Intersect(local, localdir, visibility, surfdata, dist, two_solutions, safety);
0895   if ((!surfhit && check_both_sides) || (two_solutions && check_both_sides)) {
0896     // Left side already checked, now check right side
0897     // Note: only one side can have a valid exiting
0898     left_side          = false;
0899     visibility         = !exiting ^ flipped;
0900     bool surfhit_right = unplaced.Intersect(local, localdir, visibility, surfdata, dist2, two_solutions, safety);
0901     if (surfhit_right) {
0902       if (surfhit) {
0903         // both sides have valid hits, we need to chose the one with the smaller distance
0904         // such that two valid solutions are returned with dist being the closer one and dist2 the one that is further
0905         // away if the solution on the right side is closer, use it and store the left side solution as dist2
0906         if (dist2 < dist) {
0907           Real_t tmp_dist = dist;
0908           dist            = dist2;
0909           dist2           = tmp_dist;
0910         } else {
0911           // set left_side back to true if left_side solution is closer
0912           left_side = true;
0913         }
0914       } else {
0915         // surfhit is false so only the right side hit was valid. Thus, we return only one valid distance dist
0916         dist  = dist2;
0917         dist2 = -vecgeom::InfinityLength<Real_t>();
0918       }
0919       // since surfhit_right is true, we definitively have one valid hit
0920       surfhit = surfhit_right;
0921     } else {
0922       // surfhit_right is false, therefore the original surfhit, dist and dist2 = -infinity is returned
0923       left_side = true;
0924     }
0925   }
0926   if (surfhit) onsurf = local + dist * localdir;
0927   return dist;
0928 }
0929 
0930 /// @brief Method computing the distance to the next surface and state after crossing it
0931 /// @tparam Real_t Floating point type for the interface and data storage
0932 /// @param point Global point
0933 /// @param direction Global direction
0934 /// @param in_state Input navigation state before crossing
0935 /// @param out_state Output navigation state after crossing
0936 /// @param surfdata Surface data storage
0937 /// @param exit_FS CrossedSurface storing common surface id, framed surface id, and side of highest exited framed surface and of last hit framed surface (entered or exited)
0938 /// @param stepmax maximum step
0939 /// @return Distance to next surface.
0940 template <typename Real_i, typename Real_t>
0941 VECCORE_ATT_HOST_DEVICE Real_t ComputeStepAndHit(vecgeom::Vector3D<Real_t> const &point_i,
0942                                                  vecgeom::Vector3D<Real_t> const &direction_i,
0943                                                  vecgeom::NavigationState const &in_state,
0944                                                  vecgeom::NavigationState &out_state, CrossedSurface &exit_FS,
0945                                                  Real_i stepmax = vecgeom::InfinityLength<Real_t>())
0946 {
0947   // Get the list of candidate surfaces for in_state
0948   auto in_navind = in_state.GetNavIndex();
0949   if (in_navind == 0) return vecgeom::InfinityLength<Real_i>();
0950   auto const &surfdata = SurfData<Real_t>::Instance();
0951 
0952   // cast to Real_t
0953   vecgeom::Vector3D<Real_t> point(point_i);
0954   vecgeom::Vector3D<Real_t> direction(direction_i);
0955 
0956   Real_i distance         = stepmax;
0957   unsigned short scene_id = 0, newscene_id = 0;
0958   bool is_scene        = in_state.GetSceneId(scene_id, newscene_id);
0959   auto const &cand     = surfdata.GetCandidates(scene_id, in_state.GetId());
0960   auto const &new_cand = is_scene ? surfdata.GetCandidates(newscene_id, 0) : cand;
0961   bool found           = false;
0962   FSlocator tmp_hit_FS;
0963 
0964   Vector3D<Real_t> onsurf;
0965   out_state = in_state;
0966   out_state.SetBoundaryState(false);
0967   auto skip_surf = exit_FS.hit_surf.GetCSindex();
0968 
0969   // Convert the point and direction to the scene coordinate system
0970   vecgeom::Transformation3DMP<Real_t> scene_trans;
0971   in_state.SceneMatrix(scene_trans);
0972   Vector3D<Real_t> local_scene    = scene_trans.Transform(point);
0973   Vector3D<Real_t> localdir_scene = scene_trans.TransformDirection(direction);
0974   Vector3D<Real_t> local;
0975   Vector3D<Real_t> localdir;
0976 
0977   // First check Exiting candidates
0978   for (auto icand = 0; icand < cand.fNExiting; ++icand) {
0979     int isurf  = cand.fCandidates[icand];
0980     char sides = cand.fSides[icand];
0981     if (isurf == 0) continue;
0982     bool left_side, surfhit;
0983     Vector3D<Real_t> onsurf_crt;
0984     Real_t safety;
0985     Real_t dist2 = -vecgeom::InfinityLength<Real_t>(); // possible second solution
0986     // Compute distance to the unplaced surface
0987     auto dist = DistanceToUnplaced(local_scene, localdir_scene, surfdata, isurf, sides, /*exiting=*/true, left_side,
0988                                    surfhit, onsurf_crt, dist2, local, localdir, safety);
0989     if (!surfhit || (dist < -vecgeom::kToleranceStrict<Real_t> && !(Abs(safety) < vecgeom::kToleranceStrict<Real_t>)) ||
0990         dist >= distance)
0991       continue;
0992     // if (dist < 0) dist = Real_t(0.);
0993 
0994     tmp_hit_FS.Set(isurf, cand.fFrameInd[icand], left_side);
0995     tmp_hit_FS.state = in_state;
0996 
0997     FSlocator out_frame;
0998     auto inframe =
0999         ExitCS(tmp_hit_FS, /*is_hit=*/false, point, direction, dist, onsurf_crt, exit_FS.hit_surf, out_frame);
1000     exit_FS.exit_surf = exit_FS.hit_surf; // store exiting information
1001     // if no frame was hit and no second solution exists, continue
1002     if (!inframe && dist2 < -vecgeom::kToleranceStrict<Real_t>) continue;
1003     // if no frame was hit, try second solution
1004     if (!inframe && dist2 > -vecgeom::kToleranceStrict<Real_t>) {
1005       tmp_hit_FS.Set(isurf, cand.fFrameInd[icand], !left_side);
1006       onsurf_crt = local + dist2 * localdir;
1007       inframe = ExitCS(tmp_hit_FS, /*is_hit=*/false, point, direction, dist2, onsurf_crt, exit_FS.hit_surf, out_frame);
1008       exit_FS.exit_surf = exit_FS.hit_surf;
1009       // store exiting information
1010       if (inframe) dist = dist2;
1011     }
1012     if (!inframe) continue;
1013     found     = true;
1014     distance  = Real_i(dist);
1015     out_state = out_frame.state;
1016     continue; // there may be closer surfaces being crossed
1017   }
1018   // If there is no physics step limitation, an exiting surface must be found
1019   if (!found && stepmax == vecgeom::InfinityLength<Real_t>()) {
1020     // frame_id = -1 indicates extruding overlap
1021     exit_FS.Set(0, -1, 0);
1022 
1023     // This can happen if the exit point is outside the mother volume (extrusion)
1024     // To recover, one can return the mother state as output and a zero distance
1025     return stepmax;
1026   }
1027 
1028   // Now check entering candidates
1029   if (is_scene) {
1030     // If we are on a scene we need to work in the volume reference
1031     scene_trans.Clear();
1032     in_state.TopMatrix(scene_trans);
1033     local_scene    = scene_trans.Transform(point);
1034     localdir_scene = scene_trans.TransformDirection(direction);
1035   }
1036 
1037   for (auto icand = new_cand.fNExiting; icand < new_cand.fNcand; ++icand) {
1038     int isurf          = vecCore::math::Abs(new_cand[icand]);
1039     bool self_entering = new_cand[icand] < 0;
1040     if (isurf == skip_surf) continue;
1041     char sides = new_cand.fSides[icand];
1042 
1043     bool left_side, surfhit;
1044     Vector3D<Real_t> onsurf_crt;
1045     Real_t safety;
1046     Real_t dist2 = -vecgeom::InfinityLength<Real_t>(); // possible second solution
1047     // Compute distance to the unplaced surface
1048     auto dist = DistanceToUnplaced(local_scene, localdir_scene, surfdata, isurf, sides, /*exiting=*/false, left_side,
1049                                    surfhit, onsurf_crt, dist2, local, localdir, safety);
1050     if (!surfhit || (dist < -vecgeom::kToleranceStrict<Real_t> && !(Abs(safety) < vecgeom::kToleranceStrict<Real_t>)) ||
1051         dist >= distance)
1052       continue;
1053     // if (dist < 0) dist = Real_t(0.);
1054 
1055     // Temporary ugly solution to avoid self-entering the volume at 0 distance on the same surface
1056     if (self_entering && vecCore::math::Abs(dist) < vecgeom::kToleranceStrict<Real_t>) continue;
1057 
1058     FSlocator out_frame;
1059     auto EnterFrameCheck = [&](auto left_side, auto &onsurf, auto dist) -> int {
1060       // Seek and cross entering frames
1061       // Bootstrap the temporary frame locator with the hit CS side
1062       tmp_hit_FS.Set(isurf, -1, left_side);
1063       tmp_hit_FS.state = in_state;
1064       EnterCS(tmp_hit_FS, point, direction, dist, onsurf_crt, out_frame, -2);
1065       return tmp_hit_FS.frame_id;
1066     };
1067     auto iframe = EnterFrameCheck(left_side, onsurf_crt, dist);
1068 
1069     // if frame is not hit and not second solution was found, continue
1070     if (iframe < 0 && dist2 < -vecgeom::kToleranceStrict<Real_t>) continue;
1071 
1072     // if no frame is hit and second solution is valid, check second solution
1073     if (iframe < 0 && dist2 > -vecgeom::kToleranceStrict<Real_t> && dist2 < distance) {
1074       onsurf_crt = local + dist2 * localdir;
1075       iframe     = EnterFrameCheck(!left_side, onsurf_crt, dist2);
1076       if (iframe >= 0) dist = dist2;
1077     }
1078 
1079     if (iframe < 0) continue;
1080     // We do have the final hit frame as out_frame now
1081     exit_FS.hit_surf = tmp_hit_FS; // store the entering surface information in hit_FS. Needed for correctly marking
1082                                    // surfaces as overlapping
1083     // This surface is certainly hit because the parent frame is hit
1084     distance = Real_i(dist);
1085     // compute exited state
1086     out_state = out_frame.state;
1087   }
1088 
1089   // Fix the out_state if pointing to a 0 scene
1090   if (out_state.GetSceneLevel() > 0 && out_state.GetNavIndex() == 0) out_state.PopScene();
1091   return distance;
1092 }
1093 
1094 /// @brief Method computing the distance to the next surface and state after crossing it
1095 /// @tparam Real_t Floating point type for the interface and data storage
1096 /// @param point Global point
1097 /// @param direction Global direction
1098 /// @param in_state Input navigation state before crossing
1099 /// @param out_state Output navigation state after crossing
1100 /// @param surfdata Surface data storage
1101 /// @param exit_surf Input: surface to be skipped, output: crossed surface index
1102 /// @return Distance to next surface
1103 template <typename Real_i, typename Real_t>
1104 VECCORE_ATT_HOST_DEVICE Real_t ComputeSafety(vecgeom::Vector3D<Real_i> const &point_i,
1105                                              vecgeom::NavigationState const &in_state, int &closest_surf,
1106                                              Real_i limit = vecgeom::InfinityLength<Real_i>())
1107 {
1108   constexpr char kLside = 0x01;
1109   constexpr char kRside = 0x02;
1110   using vecgeom::NavigationState;
1111   Real_t safety_far = vecgeom::InfinityLength<Real_t>(); // safety to a bbox found farther than the search limit
1112   bool found{false}, found_far{false};
1113 
1114   auto const &surfdata = SurfData<Real_t>::Instance();
1115   vecgeom::Vector3D<Real_t> point(point_i);
1116   closest_surf         = 0;
1117   int last_logic_volid = -1;
1118   Real_t safety        = vecgeom::InfinityLength<Real_t>();
1119   if (in_state.GetNavIndex() == 0) return safety;
1120   Vector3D<Real_t> onsurf;
1121   unsigned short scene_id = 0, newscene_id = 0;
1122   // Get the list of visible candidate surfaces for in_state
1123   in_state.GetSceneId(scene_id, newscene_id);
1124   auto const &cand = surfdata.GetCandidates(scene_id, in_state.GetId());
1125 
1126   // Convert the point to the scene coordinate system
1127   vecgeom::Transformation3DMP<Real_t> scene_trans;
1128   in_state.SceneMatrix(scene_trans);
1129   Vector3D<Real_t> local_scene = scene_trans.Transform(point);
1130 
1131   // First check Exiting candidates
1132   for (auto icand = 0; icand < cand.fNExiting; ++icand) {
1133     bool validSafety = true;
1134     int isurf        = cand[icand];
1135     if (isurf == 0) continue;
1136     auto const &surf = surfdata.fCommonSurfaces[isurf];
1137 
1138     // Convert point to surface frame
1139     auto const &trans      = surf.fTrans;
1140     Vector3D<Real_t> local = trans.Transform(local_scene);
1141     Vector3D<Real_t> onsurf_crt;
1142     Real_t safety_surf = vecgeom::InfinityLength<Real_t>();
1143     bool flipped       = false;
1144     auto unplaced      = surfdata.GetUnplaced(isurf, flipped);
1145 
1146     // left_side is the side which defines the exit normal
1147     char sides            = cand.fSides[icand];
1148     bool left_side        = (sides & kLside) > 0;
1149     bool right_side       = (sides & kRside) > 0;
1150     bool check_both_sides = left_side && right_side;
1151     bool visibility       = left_side ^ flipped;
1152 
1153     // Compute signed closest distance to surface. The closest projected point on surface is computed, except for:
1154     // - negative safety (coming from the wrong side)
1155     // - exiting framed surfaces for which fUseSurfSafety is true
1156     // To test if on GPU is better to compute the projection systematically
1157     // auto const &check_side = left_side ? surf.fLeftSide : surf.fRightSide;
1158     bool can_compute = unplaced.Safety(local, visibility, surfdata, safety_surf, onsurf_crt);
1159     if (!can_compute && check_both_sides) {
1160       // Left side already checked, now check right side
1161       // Note: only one side can have a valid safety
1162       left_side   = false;
1163       visibility  = flipped;
1164       safety_surf = -safety_surf;
1165       can_compute = true;
1166     }
1167 
1168     if (!can_compute || safety_surf < -vecgeom::kToleranceDist<Real_t> || safety_surf >= safety) continue;
1169     if (safety_surf > limit) {
1170       found_far  = true;
1171       safety_far = vecCore::math::Min(safety_surf, safety_far);
1172       continue;
1173     }
1174 
1175     // This is an exiting surface for in_state
1176     // Only check the frame of the current state on this surface
1177 
1178     // Get the index of the framed surface on the exit side. This assumes a SINGLE frame inprint
1179     // coming from a touchable on any common surface.
1180     auto const &exit_side  = left_side ? surf.fLeftSide : surf.fRightSide;
1181     int frameind           = cand.fFrameInd[icand]; // index of framed surface on the side
1182     auto const &framedsurf = exit_side.GetSurface(frameind, surfdata);
1183     // Skip already checked logic surfaces.
1184     // if (framedsurf.fLogicId && last_logic_volid == framedsurf.VolumeId()) continue;
1185     // Check if the exited frame safety is needed at all
1186     auto safetyFrame = safety_surf;
1187     // We need to compute also the safety of the projection of the point on surface to the frame
1188     if (!framedsurf.fNeverCheck) safetyFrame = framedsurf.SafetyFrame(onsurf_crt, safety_surf, surfdata, validSafety);
1189     if (validSafety) {
1190       // The distance to the unplaced is valid, so mark as found
1191       found = true;
1192       if (safetyFrame < safety) {
1193         closest_surf = isurf;
1194         safety       = safetyFrame;
1195       }
1196     }
1197   }
1198 
1199   // Now check the entering candidates
1200   for (auto icand = cand.fNExiting; icand < cand.fNcand; ++icand) {
1201     int isurf        = vecCore::math::Abs(cand[icand]);
1202     auto const &surf = surfdata.fCommonSurfaces[isurf];
1203 
1204     // Convert point to surface frame
1205     auto const &trans      = surf.fTrans;
1206     Vector3D<Real_t> local = trans.Transform(local_scene);
1207     Vector3D<Real_t> onsurf_crt;
1208     Real_t safety_surf = vecgeom::InfinityLength<Real_t>();
1209     bool flipped       = false;
1210     auto unplaced      = surfdata.GetUnplaced(isurf, flipped);
1211 
1212     // left_side is the side which defines the exit normal
1213     // left_side is the side which defines the exit normal
1214     char sides            = cand.fSides[icand];
1215     bool left_side        = (sides & kLside) > 0;
1216     bool right_side       = (sides & kRside) > 0;
1217     bool check_both_sides = left_side && right_side;
1218     bool visibility       = !left_side ^ flipped;
1219     bool can_compute      = unplaced.Safety(local, visibility, surfdata, safety_surf, onsurf_crt);
1220 
1221     if (!can_compute && check_both_sides) {
1222       // Left side already checked, now check right side
1223       // Note: only one side can have a valid safety
1224       left_side   = false;
1225       visibility  = !flipped;
1226       safety_surf = -safety_surf;
1227       can_compute = true;
1228       // can_compute = unplaced.Safety(local, visibility, surfdata, safety_surf, onsurf_crt);
1229     }
1230     if (!can_compute || safety_surf < -vecgeom::kToleranceDist<Real_t> || safety_surf >= safety) continue;
1231     if (safety_surf > limit) {
1232       found_far  = true;
1233       safety_far = vecCore::math::Min(safety_surf, safety_far);
1234       continue;
1235     }
1236 
1237     // Entering side. We only check the parent frames on the side
1238     auto const &entry_side = left_side ? surf.fLeftSide : surf.fRightSide;
1239     const int num_parents  = entry_side.fNumParents;
1240     int iparent            = 0;
1241     // Parent frames are last in the list
1242     for (auto ind = entry_side.fNsurf - 1; ind >= 0; --ind) {
1243       bool validSafety       = true;
1244       auto const &framedsurf = entry_side.GetSurface(ind, surfdata);
1245       if (framedsurf.fParent >= 0) continue; // skip children
1246       // Skip already checked logic surfaces.
1247       if (framedsurf.fLogicId && last_logic_volid == framedsurf.VolumeId()) continue;
1248       iparent++;
1249       auto safetyFrame = safety_surf;
1250       if (!framedsurf.fNeverCheck) safetyFrame = framedsurf.SafetyFrame(onsurf_crt, safety_surf, surfdata, validSafety);
1251       if (validSafety && safetyFrame < safety) {
1252         // The distance to the unplaced is valid, so mark as found
1253         safety       = safetyFrame;
1254         found        = true;
1255         closest_surf = isurf;
1256       }
1257       if (iparent == num_parents) break;
1258     }
1259   }
1260   if (!found && found_far) return safety_far;
1261   safety *= found;
1262   return safety;
1263 }
1264 
1265 } // namespace protonav
1266 } // namespace vgbrep
1267 #endif