Back to home page

EIC code displayed by LXR

 
 

    


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

0001 // SPDX-FileCopyrightText: 2020 CERN
0002 // SPDX-License-Identifier: Apache-2.0
0003 
0004 /**
0005  * @file LoopNavigator.h
0006  * @brief Navigation methods for geometry.
0007  */
0008 
0009 #ifndef LOOP_NAVIGATOR_H_
0010 #define LOOP_NAVIGATOR_H_
0011 
0012 #include <VecGeom/base/Global.h>
0013 #include <VecGeom/base/Vector3D.h>
0014 #include <VecGeom/navigation/NavigationState.h>
0015 #include <VecGeom/volumes/LogicalVolume.h>
0016 
0017 #ifdef VECGEOM_ENABLE_CUDA
0018 #include <VecGeom/backend/cuda/Interface.h>
0019 #endif
0020 
0021 namespace vecgeom {
0022 
0023 class LoopNavigator {
0024 
0025 public:
0026   // using Vector3D<Precision>           = vecgeom::Vector3D<Precision><vecgeom::Precision>;
0027 
0028   static constexpr Precision kBoundaryPush = 10 * vecgeom::kTolerance;
0029 
0030   /**
0031    * @brief Locate the deepest daughter volume that contains a point, starting from a given placed volume.
0032    *
0033    * ## Coordinate frames
0034    * - **Input point** @p point is in the **parent frame of @p vol** (i.e. the same frame expected by
0035    *   `VPlacedVolume::Inside` for @p vol).
0036    * - The NavigationState @p path should point to the **parent frame of @p vol**.
0037    * @param[in]  vol
0038    *   The starting placed volume (the mother at which the descent begins). Must be non-null if @p top is true.
0039    * @param[in]  point
0040    *   Query point **in the parent frame of @p vol**.
0041    * @param[in,out] path
0042    *   Navigation state to be filled. Must start at the  **parent frame of @p vol**.
0043    * @param[in]  top
0044    *   If true, the function first validates that the point is inside @p vol. If false, a nullptr is returned
0045    * @param[in]  exclude
0046    *   Optional placed volume to exclude once during the descent (useful to avoid immediately
0047    *   re-entering a volume you just exited). The exclusion is applied only to the first BVH
0048    *   query and then cleared.
0049    * @return
0050    *   The deepest placed volume that contains the point, or `nullptr` if @p top is true and
0051    *   `vol->Inside(point)` reports outside. When the descent stops at @p vol (i.e. no daughter
0052    *   contains the point), returns @p vol.
0053    */
0054   VECCORE_ATT_HOST_DEVICE
0055   static Daughter LocatePointIn(Daughter vol, Vector3D<Precision> const &point, vecgeom::NavigationState &path,
0056                                 bool top, Daughter exclude = nullptr)
0057   {
0058     if (top) {
0059       VECGEOM_ASSERT(vol != nullptr);
0060       auto inside = vol->Inside(point);
0061       if (inside == kOutside) return nullptr;
0062       // Set the boundary state to the path
0063       if (inside == kSurface) path.SetBoundaryState(true);
0064     }
0065 
0066     Daughter currentvolume = vol;
0067     // transform the point into the reference frame of the `vol`
0068     Vector3D<Precision> currentpoint(vol->GetTransformation()->Transform<Precision>(point));
0069     path.Push(currentvolume);
0070 
0071     bool godeeper;
0072     do {
0073       godeeper = false;
0074       for (auto *daughter : currentvolume->GetDaughters()) {
0075         if (daughter == exclude) {
0076           continue;
0077         }
0078         auto transformedpoint = daughter->GetTransformation()->Transform<Precision>(currentpoint);
0079         auto inside           = daughter->GetUnplacedVolume()->Inside(transformedpoint);
0080         if (inside == EnumInside::kOutside) continue;
0081         if (inside == kSurface) path.SetBoundaryState(true);
0082         // Point inside child
0083         path.Push(daughter);
0084         currentpoint  = transformedpoint;
0085         currentvolume = daughter;
0086         godeeper      = true;
0087         // Skip checking other children
0088         break;
0089       }
0090 
0091       // Only exclude the placed volume once since we could enter it again via a
0092       // different volume history.
0093       exclude = nullptr;
0094     } while (godeeper);
0095 
0096     return currentvolume;
0097   }
0098 
0099   /**
0100    * @brief Find new final NavigationState after a NavigationState has just been left (i.e., after exiting its corresponding top volume)
0101    * The function will climb up the NavigationStates until the point is inside, and then descend again to find the
0102    * deepest volume containing the point. The just exited volume will be skipped
0103    * @param[in]  localpoint   Point in the **local frame of the navigation state (path.Top())**
0104    * @param[in,out] path      Navigation state of the volume that was just left; updated to the new deepest containing path.
0105    * @return The deepest placed volume that contains the point (after re-location), or nullptr if no mother exists.
0106    */
0107   VECCORE_ATT_HOST_DEVICE
0108   static Daughter RelocatePoint(Vector3D<Precision> const &localpoint, vecgeom::NavigationState &path)
0109   {
0110     Daughter currentmother          = path.Top();
0111     Daughter skip                   = nullptr;
0112     Vector3D<Precision> transformed = localpoint;
0113     // continue to climb up the Navigation tree until a volume is found which contains the volume
0114     do {
0115       skip = currentmother;
0116       path.Pop();
0117       transformed   = currentmother->GetTransformation()->InverseTransform(transformed);
0118       currentmother = path.Top();
0119     } while (currentmother && (currentmother->IsAssembly() || !currentmother->UnplacedContains(transformed)));
0120 
0121     // first volume up the hierarchy is found that contains the point. Now descend to find the deepest state that
0122     // contains the point
0123     if (currentmother) {
0124       return LocatePointInNavState(transformed, path, false, skip);
0125     }
0126     return currentmother;
0127   }
0128 
0129   /**
0130    * @brief Update NavState path to the deepest state that contains given point starting from the given path.
0131    * @param[in]  localpoint
0132    *   localpoint **in the reference frame of @p path.**.
0133    * @param[in,out] path
0134    *   Starting NavigationState; output of the final NavigationState
0135    * @param[in]  top
0136    *   If true, the function first validates that the point is inside @p path. If false, a nullptr is returned
0137    * @param[in]  exclude
0138    *   Optional placed volume to exclude once during the descent
0139    * @return
0140    *   The final top-volume of the NavState after finding the deepest NavState that contains the point
0141    */
0142   VECCORE_ATT_HOST_DEVICE
0143   static Daughter LocatePointInNavState(Vector3D<Precision> const &localpoint, vecgeom::NavigationState &path, bool top,
0144                                         vecgeom::VPlacedVolume const *exclude = nullptr)
0145   {
0146     VECGEOM_ASSERT(path.Top() != nullptr);
0147 
0148     auto currentLogical = path.Top()->GetLogicalVolume();
0149     VECGEOM_ASSERT(currentLogical != nullptr);
0150 
0151     // If requested, validate at the logical level (unplaced containment)
0152     if (top) {
0153       auto inside = currentLogical->GetUnplacedVolume()->Inside(localpoint);
0154       if (inside == kOutside) return nullptr;
0155       // Set the boundary state to the path
0156       if (inside == kSurface) path.SetBoundaryState(true);
0157     }
0158 
0159     Vector3D<Precision> currentpoint(localpoint);
0160 
0161     while (currentLogical->GetDaughters().size() > 0) {
0162       bool godeeper = false;
0163 
0164       // Linear scan over placed daughters of the current logical volume
0165       for (auto *daughter : currentLogical->GetDaughters()) {
0166         if (exclude && daughter == exclude) continue;
0167 
0168         // Transform the point from the current logical frame to the daughter's local frame
0169         const auto transformedpoint = daughter->GetTransformation()->Transform<Precision>(currentpoint);
0170         const auto inside           = daughter->GetUnplacedVolume()->Inside(transformedpoint);
0171         if (inside == kOutside) continue;
0172         if (inside == kSurface) path.SetBoundaryState(true);
0173         // Point inside child
0174         path.Push(daughter);
0175         currentpoint   = transformedpoint;
0176         currentLogical = daughter->GetLogicalVolume();
0177         godeeper       = true;
0178         // Skip checking other children
0179         break;
0180       }
0181 
0182       // Only exclude the placed volume once since we could enter it again via a
0183       // different volume history.
0184       exclude = nullptr;
0185       if (!godeeper) break; // no containing daughter at this level
0186     }
0187 
0188     return path.Top();
0189   }
0190 
0191 private:
0192   // Computes a step in the current volume from the localpoint into localdir,
0193   // taking step_limit into account. If a volume is hit, the function calls
0194   // out_state.SetBoundaryState(true) and hitcandidate is set to the hit
0195   // daughter volume, or kept unchanged if the current volume is left.
0196   VECCORE_ATT_HOST_DEVICE
0197   static Precision ComputeStepAndHit(Vector3D<Precision> const &localpoint, Vector3D<Precision> const &localdir,
0198                                      Precision step_limit, vecgeom::NavigationState const &in_state,
0199                                      vecgeom::NavigationState &out_state, Daughter &hitcandidate)
0200   {
0201     if (step_limit <= 0) {
0202       // We don't need to ask any solid, this step is not limited by geometry.
0203       in_state.CopyTo(&out_state);
0204       out_state.SetBoundaryState(false);
0205       return 0;
0206     }
0207 
0208     Precision step = step_limit;
0209     Daughter pvol  = in_state.Top();
0210 
0211     // need to calc DistanceToOut first
0212     step = pvol->DistanceToOut(localpoint, localdir, step_limit);
0213 
0214     if (step < 0) step = 0;
0215     step = Min(step, step_limit);
0216 
0217     for (auto *daughter : pvol->GetDaughters()) {
0218       double ddistance = daughter->DistanceToIn(localpoint, localdir, step);
0219       ddistance        = vecCore::math::Max(ddistance, 0.);
0220       const bool valid = ddistance < step;
0221       hitcandidate     = valid ? daughter : hitcandidate;
0222       step             = valid ? ddistance : step;
0223     }
0224 
0225     // now we have the candidates and we prepare the out_state
0226     in_state.CopyTo(&out_state);
0227     if (step == vecgeom::kInfLength && step_limit > 0.) {
0228       out_state.SetBoundaryState(true);
0229       do {
0230         out_state.Pop();
0231       } while (out_state.Top()->IsAssembly());
0232 
0233       return vecgeom::kTolerance;
0234     }
0235 
0236     // Is geometry further away than physics step?
0237     if (step >= step_limit) {
0238       // Then this is a phyics step and we don't need to do anything.
0239       out_state.SetBoundaryState(false);
0240       return step_limit;
0241     }
0242 
0243     // Otherwise it is a geometry step and we push the point to the boundary.
0244     out_state.SetBoundaryState(true);
0245 
0246     if (step < 0) {
0247       step = 0;
0248     }
0249 
0250     return step;
0251   }
0252 
0253 public:
0254   // Computes the isotropic safety from the globalpoint. The safety must be accurate only below the provided limit.
0255   VECCORE_ATT_HOST_DEVICE
0256   static Precision ComputeSafety(Vector3D<Precision> const &globalpoint, vecgeom::NavigationState const &state,
0257                                  Precision limit = InfinityLength<Precision>())
0258   {
0259     Daughter pvol = state.Top();
0260     if (pvol == nullptr) return kInfLength;
0261     vecgeom::Transformation3D m;
0262     state.TopMatrix(m);
0263     Vector3D<Precision> localpoint = m.Transform(globalpoint);
0264 
0265     Precision safety = pvol->SafetyToOut(localpoint);
0266 
0267     for (auto *daughter : pvol->GetDaughters()) {
0268       double dsafety = daughter->SafetyToIn(localpoint);
0269       safety         = dsafety < safety ? dsafety : safety;
0270     }
0271 
0272     return safety;
0273   }
0274 
0275   // Computes a step from the globalpoint (which must be in the current volume)
0276   // into globaldir, taking step_limit into account. If a volume is hit, the
0277   // function calls out_state.SetBoundaryState(true) and relocates the state to
0278   // the next volume.
0279   VECCORE_ATT_HOST_DEVICE
0280   static Precision ComputeStepAndPropagatedState(Vector3D<Precision> const &globalpoint,
0281                                                  Vector3D<Precision> const &globaldir, Precision step_limit,
0282                                                  vecgeom::NavigationState const &in_state,
0283                                                  vecgeom::NavigationState &out_state, Precision push = 0)
0284   {
0285     if (in_state.Top() == nullptr) return kInfLength;
0286     // If we are on the boundary, push a bit more.
0287     if (in_state.IsOnBoundary()) {
0288       push += kBoundaryPush;
0289     }
0290     if (step_limit < push) {
0291       // Go as far as the step limit says, assuming there is no boundary.
0292       // TODO: Does this make sense?
0293       in_state.CopyTo(&out_state);
0294       out_state.SetBoundaryState(false);
0295       return step_limit;
0296     }
0297     step_limit -= push;
0298 
0299     // calculate local point/dir from global point/dir
0300     Vector3D<Precision> localpoint;
0301     Vector3D<Precision> localdir;
0302     // Impl::DoGlobalToLocalTransformation(in_state, globalpoint, globaldir, localpoint, localdir);
0303     vecgeom::Transformation3D m;
0304     in_state.TopMatrix(m);
0305     localpoint = m.Transform(globalpoint);
0306     localdir   = m.TransformDirection(globaldir);
0307     // The user may want to move point from boundary before computing the step
0308     localpoint += push * localdir;
0309 
0310     Daughter hitcandidate = nullptr;
0311     Precision step        = ComputeStepAndHit(localpoint, localdir, step_limit, in_state, out_state, hitcandidate);
0312     step += push;
0313 
0314     if (out_state.IsOnBoundary()) {
0315       // Relocate the point after the step to refine out_state.
0316       localpoint += (step + kBoundaryPush) * localdir;
0317 
0318       if (!hitcandidate) {
0319         // We didn't hit a daughter but instead we're exiting the current volume.
0320         RelocatePoint(localpoint, out_state);
0321       } else {
0322         // Otherwise check if we're directly entering other daughters transitively.
0323         localpoint = hitcandidate->GetTransformation()->Transform(localpoint);
0324         LocatePointIn(hitcandidate, localpoint, out_state, false);
0325       }
0326 
0327       if (out_state.Top() != nullptr) {
0328         while (out_state.Top()->IsAssembly() || out_state.HasSamePathAsOther(in_state)) {
0329           out_state.Pop();
0330         }
0331         VECGEOM_ASSERT(!out_state.Top()->GetLogicalVolume()->GetUnplacedVolume()->IsAssembly());
0332       }
0333     }
0334 
0335     return step;
0336   }
0337 
0338   // Computes a step from the globalpoint (which must be in the current volume)
0339   // into globaldir, taking step_limit into account. If a volume is hit, the
0340   // function calls out_state.SetBoundaryState(true) and
0341   //  - removes all volumes from out_state if the current volume is left, or
0342   //  - adds the hit daughter volume to out_state if one is hit.
0343   // However the function does _NOT_ relocate the state to the next volume,
0344   // that is entering multiple volumes that share a boundary.
0345   VECCORE_ATT_HOST_DEVICE
0346   static Precision ComputeStepAndNextVolume(Vector3D<Precision> const &globalpoint,
0347                                             Vector3D<Precision> const &globaldir, Precision step_limit,
0348                                             vecgeom::NavigationState const &in_state,
0349                                             vecgeom::NavigationState &out_state, Precision push = 0)
0350   {
0351     if (in_state.Top() == nullptr) return kInfLength;
0352     // If we are on the boundary, push a bit more.
0353     if (in_state.IsOnBoundary()) {
0354       push += kBoundaryPush;
0355     }
0356     if (step_limit < push) {
0357       // Go as far as the step limit says, assuming there is no boundary.
0358       // TODO: Does this make sense?
0359       in_state.CopyTo(&out_state);
0360       if (step_limit > kTolerance) out_state.SetBoundaryState(false);
0361       return step_limit;
0362     }
0363     step_limit -= push;
0364 
0365     // calculate local point/dir from global point/dir
0366     Vector3D<Precision> localpoint;
0367     Vector3D<Precision> localdir;
0368     // Impl::DoGlobalToLocalTransformation(in_state, globalpoint, globaldir, localpoint, localdir);
0369     vecgeom::Transformation3D m;
0370     in_state.TopMatrix(m);
0371     localpoint = m.Transform(globalpoint);
0372     localdir   = m.TransformDirection(globaldir);
0373 
0374     Daughter hitcandidate = nullptr;
0375     // Avoid computing the distance from boundary by pushing the point
0376     Precision step =
0377         ComputeStepAndHit(localpoint + push * localdir, localdir, step_limit, in_state, out_state, hitcandidate);
0378     // step correction with the push distance
0379     step += (step > 0.) * push;
0380 
0381     if (out_state.IsOnBoundary()) {
0382       if (!hitcandidate) {
0383         Daughter currentmother          = out_state.Top();
0384         Vector3D<Precision> transformed = localpoint;
0385         // Push the point inside the next volume.
0386         transformed += (step + kBoundaryPush) * localdir;
0387         do {
0388           // move to deepest parent still containing the point (not on boundary)
0389           out_state.SetLastExited();
0390           out_state.Pop();
0391           transformed   = currentmother->GetTransformation()->InverseTransform(transformed);
0392           currentmother = out_state.Top();
0393         } while (currentmother &&
0394                  (currentmother->IsAssembly() || currentmother->GetUnplacedVolume()->Inside(transformed) != kInside));
0395       } else {
0396         out_state.Push(hitcandidate);
0397       }
0398     }
0399 
0400     return step;
0401   }
0402 
0403   // Relocate a state that was returned from ComputeStepAndNextVolume: It
0404   // recursively locates the pushed point in the containing volume.
0405   VECCORE_ATT_HOST_DEVICE
0406   static void RelocateToNextVolume(Vector3D<Precision> const &globalpoint, Vector3D<Precision> const &globaldir,
0407                                    vecgeom::NavigationState &state)
0408   {
0409     // if already outside, don't do anything
0410     if (state.IsOutside()) return;
0411 
0412     // Push the point inside the next volume.
0413     // A.G. This should not be needed now since LocatePointIn is boundary-aware
0414     Vector3D<Precision> pushed = globalpoint /* + kBoundaryPush * globaldir*/;
0415 
0416     // Calculate local point from global point.
0417     vecgeom::Transformation3D m;
0418     state.TopMatrix(m);
0419     Vector3D<Precision> localpoint = m.Transform(pushed);
0420 
0421     // passing the state to check in + the local point in the reference frame of the state
0422     LocatePointInNavState(localpoint, state, false, state.GetLastExited());
0423 
0424     if (state.Top() != nullptr) {
0425       while (state.Top()->IsAssembly()) {
0426         state.Pop();
0427       }
0428       VECGEOM_ASSERT(!state.Top()->GetLogicalVolume()->GetUnplacedVolume()->IsAssembly());
0429     }
0430   }
0431 };
0432 
0433 } // namespace vecgeom
0434 
0435 #endif // RT_LOOP_NAVIGATOR_H_