Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-13 11:00:10

0001 //------------------------------- -*- C++ -*- -------------------------------//
0002 // Copyright VecGeom contributors: see top-level LICENSE file for details
0003 // SPDX-License-Identifier: Apache-2.0
0004 //---------------------------------------------------------------------------//
0005 //! \file VecGeom/navigation/NavView.h
0006 //---------------------------------------------------------------------------//
0007 #pragma once
0008 
0009 #include <cmath>
0010 
0011 #include "VecGeom/base/Assert.h"
0012 #include "VecGeom/base/Math.h"
0013 #include "VecGeom/base/Macros.h"
0014 #include "VecGeom/base/MemSpace.h"
0015 #include "VecGeom/base/Vector3D.h"
0016 #include "VecGeom/navigation/BVHNavigator.h"
0017 #include "VecGeom/volumes/PlacedVolume.h"
0018 #include "NavFindResult.h"
0019 #include "NavPath.h"
0020 #include "NavParams.h"
0021 
0022 //---------------------------------------------------------------------------//
0023 /*! For testing and development (and required by unit tests, always check navigation inputs/outputs.
0024  * When switching to production use, these will be moved to a wrapper class.
0025  * For now, define a VECGEOM_NAV_DEBUG macro that can be disabled by downstream code.
0026  */
0027 #ifndef VECGEOM_NAV_DEBUG
0028 #if VECGEOM_DEVICE_COMPILE
0029 // Never use runtime assertions in CUDA code
0030 #define VECGEOM_NAV_DEBUG 0
0031 #else
0032 // Default assertions to on
0033 #define VECGEOM_NAV_DEBUG 1
0034 #endif
0035 #endif
0036 
0037 #if VECGEOM_NAV_DEBUG
0038 #define VECGEOM_NAV_VALIDATE(COND, MSG) VECGEOM_VALIDATE(COND, MSG)
0039 #else
0040 #define VECGEOM_NAV_VALIDATE(COND, MSG) VECGEOM_DISCARD(COND)
0041 #endif
0042 
0043 namespace vecgeom {
0044 
0045 //---------------------------------------------------------------------------//
0046 
0047 // Note that vectors are different types on host and device...
0048 VECGEOM_HOST_FORWARD_DECLARE(template <class T> class Vector3D;);
0049 VECGEOM_DEVICE_FORWARD_DECLARE(template <class T> class Vector3D;);
0050 template <class T, MemSpace M>
0051 using Vector3 = MemSpaceCond_t<M, vecgeom::cxx::Vector3D<T>, vecgeom::cuda::Vector3D<T>>;
0052 
0053 using Real3 = Vector3<Precision, MemSpace::native>;
0054 
0055 //---------------------------------------------------------------------------//
0056 
0057 /*!
0058  * Fundamental VecGeom navigation interface for a single thread.
0059  *
0060  * \c NavView is an ephemeral wrapper around externally owned VecGeom
0061  * navigation state.  It provides direct access to VecGeom navigator
0062  * primitives regardless of storage, track-slot management, execution
0063  * architecture, or implementation navigator/state.
0064  *
0065  * The view holds non-owning references to:
0066  *  - Shared \c NavParams: stores world volume and possibly other
0067  *    thread-independent navigation data. (Currently the NavParams::world member
0068  *    is only required for initializing from \c UnknownPath and may be left as nullptr.)
0069  *  - Current \c NavState: encodes the current + previous navigation path
0070  *    and surface-crossing flag.
0071  *  - Next \c NavState: scratch buffer used for half-relocation.
0072  *  - Global position as a \c Span<Real,3>.
0073  *  - Global direction as a \c Span<Real,3>.
0074  *
0075  * The caller is responsible for keeping track of the on-boundary flag and the
0076  * straight-line distance between operations.
0077  *
0078  * The \c OpaquePath is a low-level, POD-compatible representation of a
0079  * navigation path that is selected at
0080  * build time:
0081  *  - \c NavIndex   when \c VECGEOM_USE_NAVINDEX is set.
0082  *  - \c NavTuple   when \c VECGEOM_USE_NAVTUPLE  is set.
0083  * G4VG converts between Geant4 touchable paths and \c OpaquePath.
0084  *
0085  * \par Initialization
0086  *
0087  * Initializing the state wrapped by a view is done in one of two ways:
0088  * - <em>Directly from a full navigation path.</em> This use case is for
0089  *   converting directly from a Geant4 "touchable history" or for cloning a
0090  *   navigation state.
0091  * - <em>By performing a full point-in-volume search based on a starting
0092  *   position.</em> This is needed to initialize "primary" particles which have
0093  *   no geometric metadata.
0094  *
0095  * \par Typical straight-tracking usage
0096  * \code
0097  *   NavView view(setup, cur_state, nxt_state, pos_span, dir_span);
0098  *   view.Initialize(unknown_path, pos_span, dir_span);
0099  *   NavFindResult nfr = view.FindNextBoundary(max_dist);
0100  *   if (nfr.GetKind() == NavFindResultKind::hit) {
0101  *     view.MoveToBoundary(nfr.GetDistance());
0102  *     auto normal = view.CalcNextNormal(); // retain before crossing if needed
0103  *     view.CrossBoundary();
0104  *   elseif (nfr.GetKind() == NavFindResultKind::reentrant) {
0105  *     auto normal = view.CalcNextNormal(); // may be needed, or may use previous retained
0106  *     view.CrossBoundary();
0107  *   } else if (nfr.GetKind() == NavFindResultKind::miss) {
0108  *     view.MoveInternal(max_dist);
0109  *   } else {
0110  *     // Handle an invalid boundary result
0111  *   }
0112  * \endcode
0113  *
0114  * \note See \c navigation.md for additional high-level documentation.
0115  *
0116  * \note All points are in the "global" (world) coordinate system unless otherwise specified.
0117  */
0118 class NavView {
0119 public:
0120   //!@{
0121   //! \name Type aliases
0122   using Real            = ::vecgeom::Precision;
0123   using Real3           = ::vecgeom::Real3;
0124   using SpanReal3       = Real3 &;
0125   using SpanConstReal3  = Real3 const &;
0126   using NavState        = ::vecgeom::NavigationState;
0127   using LogicalVolumeId = unsigned int;
0128   using PlacedVolumeId  = unsigned int;
0129   using OpaquePath      = OpaqueNavPath;
0130   using NavSetupT       = NavParams<MemSpace::native>;
0131   //!@}
0132 
0133   // Construct a view to externally owned navigation state and position/direction
0134   inline VECGEOM_FUNCTION NavView(NavSetupT const &setup, NavState &cur_state, NavState &nxt_state, SpanReal3 pos,
0135                                   SpanReal3 dir);
0136 
0137   //// STATE-INDEPENDENT FUNCTIONS ////
0138 
0139   //! Opaque path indicating "outside"
0140   static VECGEOM_FUNCTION OpaquePath GetOutsidePath() { return NavIndex_t{0}; }
0141 
0142   //! Opaque path indicating "world"
0143   static VECGEOM_FUNCTION OpaquePath GetWorldPath() { return NavIndex_t{1}; }
0144 
0145   //// INITIALIZATION ////
0146 
0147   // Initialize navigation from an existing opaque path and boundary state
0148   inline VECGEOM_FUNCTION void Initialize(FullPath fp, SpanConstReal3 pos, SpanConstReal3 dir);
0149 
0150   // Initialize navigation from position and direction within the world
0151   inline VECGEOM_FUNCTION void Initialize(UnknownPath, SpanConstReal3 pos, SpanConstReal3 dir);
0152 
0153   //// OPERATIONS ////
0154 
0155   // Compute a conservative isotropic distance to the nearest boundary
0156   inline VECGEOM_FUNCTION Real FindSafety(Real max);
0157 
0158   // Find the straight-line distance to the next boundary
0159   inline VECGEOM_FUNCTION NavFindResult FindNextBoundary(Real max);
0160 
0161   // Change the current track direction
0162   inline VECGEOM_FUNCTION void ChangeDirection(SpanConstReal3 dir);
0163 
0164   // Move to an arbitrary position within the current safety sphere
0165   inline VECGEOM_FUNCTION void MoveInternal(SpanConstReal3 pos);
0166 
0167   // Move along the current direction without reaching a boundary
0168   inline VECGEOM_FUNCTION void MoveInternal(Real step);
0169 
0170   // Advance to the boundary at the exact distance returned b
0171   inline VECGEOM_FUNCTION void MoveToBoundary(Real step);
0172 
0173   // Compute the global boundary normal pointing from current to next state
0174   inline VECGEOM_FUNCTION Real3 CalcNextNormal() const;
0175 
0176   // Cross the current boundary and update the navigation state
0177   inline VECGEOM_FUNCTION void CrossBoundary();
0178 
0179   //// ACCESSORS ////
0180 
0181   // Whether the navigation state is outside the world or uninitialized
0182   inline VECGEOM_FUNCTION bool IsOutside() const;
0183 
0184   // Whether the navigation state is on the boundary of a volume
0185   inline VECGEOM_FUNCTION bool IsOnBoundary() const;
0186 
0187   // VecGeom logical volume ID of the current volume (NOT outside)
0188   inline VECGEOM_FUNCTION LogicalVolumeId GetLogicalVolumeId() const;
0189 
0190   // VecGeom placed volume ID of the current volume instance (NOT outside)
0191   inline VECGEOM_FUNCTION PlacedVolumeId GetPlacedVolumeId() const;
0192 
0193   // Access the current navigation path as an opaque value
0194   inline VECGEOM_FUNCTION OpaquePath const &GetOpaquePath() const;
0195 
0196   //! Current global position.
0197   VECGEOM_FUNCTION SpanConstReal3 GetPosition() const { return pos_; }
0198 
0199   //! Current global direction (unit vector).
0200   VECGEOM_FUNCTION SpanConstReal3 GetDirection() const { return dir_; }
0201 
0202 private:
0203   //// TYPES ////
0204 
0205   using Navigator = ::vecgeom::BVHNavigator;
0206 
0207   //// DATA ////
0208 
0209   NavSetupT const &setup_; //!< Navigation setup
0210   NavState &cur_state_;    //!< Current navigation path
0211   NavState &nxt_state_;    //!< Scratch "next" navigation path
0212   SpanReal3 pos_;          //!< Global position [cm]
0213   SpanReal3 dir_;          //!< Global direction (unit vector)
0214 
0215   //// HELPER FUNCTIONS ////
0216 
0217   // Move a straight-line distance without error checking
0218   inline VECGEOM_FUNCTION void MoveInternalImpl(Real distance);
0219 
0220   //! Copy data from a const span to a span: this will change based on the definition of Span3
0221   static VECGEOM_FUNCTION void CopyFrom(SpanReal3 dst, SpanConstReal3 src) { dst = src; }
0222 
0223   // Validate normalized directions: will be replaced by corecel::ArraySoftUnit
0224   inline static VECGEOM_FUNCTION bool IsUnitNormal(SpanConstReal3 v);
0225 };
0226 
0227 //---------------------------------------------------------------------------//
0228 // CONSTRUCTOR
0229 //---------------------------------------------------------------------------//
0230 
0231 /*!
0232  * Construct from externally owned navigation state and position/direction.
0233  *
0234  * All arguments may be borrowed references; the caller must ensure they
0235  * outlive this view.
0236  *
0237  * \param setup      Global (shared) data about the VecGeom navigation definition
0238  * \param cur_state  Current navigation state (encodes path + boundary
0239  *                   flag).
0240  * \param nxt_state  Scratch navigation state used as a "next" buffer for
0241  *                   half-relocation.
0242  * \param pos        Mutable pointer to the global position
0243  * \param dir        Mutable pointer to the global direction
0244  */
0245 VECGEOM_FUNCTION NavView::NavView(NavSetupT const &setup, NavState &cur_state, NavState &nxt_state, SpanReal3 pos,
0246                                   SpanReal3 dir)
0247     : setup_{setup}, cur_state_{cur_state}, nxt_state_{nxt_state}, pos_{pos}, dir_{dir}
0248 {
0249   VECGEOM_NAV_VALIDATE(&cur_state_ != &nxt_state_, << "current and next must point to different objects");
0250 }
0251 
0252 //---------------------------------------------------------------------------//
0253 // INITIALIZATION
0254 //---------------------------------------------------------------------------//
0255 
0256 /*!
0257  * Initialize navigation from an existing opaque path (CPU only).
0258  *
0259  * Intended for ADePT-style use where a Geant4 touchable path has already
0260  * been converted to an \c OpaquePath by G4VG.
0261  *
0262  * \pre \c pos must lie inside or on the boundary of the volume encoded by
0263  *      \c path, and must not belong to a child volume of it.
0264  * \pre \c dir must be normalised to unit length within machine precision.
0265  */
0266 VECGEOM_FUNCTION void NavView::Initialize(FullPath fp, SpanConstReal3 pos, SpanConstReal3 dir)
0267 {
0268   VECGEOM_NAV_VALIDATE(IsUnitNormal(dir), << "direction " << dir << " is not a unit vector");
0269 
0270   // Copy position and direction into the externally owned storage
0271   CopyFrom(pos_, pos);
0272   CopyFrom(dir_, dir);
0273 
0274   // Clear current and next states
0275   cur_state_.Clear();
0276   nxt_state_.Clear();
0277 
0278   // NOTE: SetNavIndex can actually take a tuple when VECGEOM_USE_NAVTUPLE
0279   cur_state_.SetNavIndex(fp.path);
0280   cur_state_.SetBoundaryState(fp.boundary);
0281 }
0282 
0283 /*!
0284  * Initialize navigation by searching for the given position and direction.
0285  *
0286  * Performs a full geometry location to find the enclosing volume.  Works
0287  * on both host and device, and is the preferred initialisation path for
0288  * generating primary particles from spatial distributions (e.g., uniform
0289  * box) on device. It is allowed for the position to be outside or on the
0290  * boundary of the world.
0291  *
0292  * \pre \c dir must be normalised to unit length within machine precision.
0293  */
0294 VECGEOM_FUNCTION void NavView::Initialize(UnknownPath, SpanConstReal3 pos, SpanConstReal3 dir)
0295 {
0296   VECGEOM_NAV_VALIDATE(IsUnitNormal(dir), << "direction " << dir << " is not a unit vector");
0297   // Copy position and direction into the externally owned storage
0298   CopyFrom(pos_, pos);
0299   CopyFrom(dir_, dir);
0300 
0301   // Clear current and nexdt states
0302   cur_state_.Clear();
0303   nxt_state_.Clear();
0304 
0305   auto const *world = setup_.world;
0306   VECGEOM_NAV_VALIDATE(world != nullptr, << "world is required for unknown-path initialization");
0307   constexpr bool check_top = true;
0308 
0309   auto *top_pv = Navigator::LocatePointIn(world, pos_, cur_state_, check_top);
0310   VECGEOM_NAV_VALIDATE((top_pv == nullptr) == this->IsOutside(), << "inconsistent initialization result");
0311 }
0312 
0313 //---------------------------------------------------------------------------//
0314 // OPERATIONS
0315 //---------------------------------------------------------------------------//
0316 
0317 /*!
0318  * Compute a conservative isotropic distance to the nearest boundary.
0319  *
0320  * Returns a safety radius \em s satisfying the following constraints for
0321  * the maximum input distance \em d and the true inscribed-sphere radius
0322  * \em t:
0323  *  - \f$ s > 0 \f$
0324  *  - \f$ s \le d \f$.
0325  *  - \f$ s \le t \f$ (conservative upper bound on the inscribed sphere).
0326  *  - Ideally \f$ s \ge C\,t \f$ for a geometry-independent constant \em C
0327  *    (bounded relative error), though this cannot always be guaranteed.
0328  *
0329  * \pre The track is not on a boundary.
0330  * \pre \c max is strictly positive.
0331  *
0332  * \param max Maximum search distance.
0333  * \return Conservative safety radius.
0334  */
0335 VECGEOM_FUNCTION auto NavView::FindSafety(Real max_dist) -> Real
0336 {
0337   VECGEOM_NAV_VALIDATE(!this->IsOnBoundary() && !this->IsOutside(),
0338                        << "cannot search for safety when on a boundary or outside");
0339   VECGEOM_NAV_VALIDATE(max_dist > 0, << "safety max " << max_dist << " is nonpositive");
0340 
0341   Real result = Navigator::ComputeSafety(pos_, cur_state_, max_dist);
0342   // TODO: some shapes return a limit greater than max_dist; fix for each one
0343   result = Min(max_dist, result);
0344   // TODO: when on the wrong side of the boundary, a *negative* distance is returned
0345   result = Max(Real(0), result);
0346 
0347   VECGEOM_NAV_VALIDATE(0 <= result && result <= max_dist,
0348                        << "returned safety " << result << " is out of bounds (0, " << max_dist << "]");
0349   return result;
0350 }
0351 
0352 /*!
0353  * Find the straight-line distance to the next boundary.
0354  *
0355  * The returned \c NavFindResultKind enumeration informs potential next actions:
0356  *  - \c hit: The next state is cached internally.
0357  *    Call \c MoveToBoundary .
0358  *  - \c miss: No boundary exists along the current direction up to \c
0359  *    max_dist. Call \c MoveInternal.
0360  *  - \c reentrant: The track is already on a boundary headed outward from the
0361  *    current volume (indicating a reentrant volume change).
0362  *    This likely corresponds to the call sequence <code>{MoveToBoundary,
0363  *    ChangeDirection, CrossBoundary}</code> where the new direction points
0364  *    back into the previous volume.
0365  *    This can be handled as a degenerate step (\c MoveInternal away from the
0366  *    boundary) \em or crossing back into the previous boundary with \c
0367  *    CrossBoundary .
0368  *  - \c error: a negative solid distance indicates a geometry overlap.
0369  *
0370  * \pre The track position is inside the current volume or on a boundary.
0371  * \pre \c max_dist is strictly positive.
0372  *
0373  * \param max_dist Maximum search distance.
0374  * \return \c NavFindResult containing distance and boundary flag.
0375  *
0376  * \post Internal next state is cached (current implementation).
0377  * \post The next distance is \em not cached; the caller must store it if
0378  *       \c MoveToBoundary will need it.
0379  */
0380 VECGEOM_FUNCTION auto NavView::FindNextBoundary(Real max_dist) -> NavFindResult
0381 {
0382   VECGEOM_NAV_VALIDATE(!this->IsOutside(), << "cannot search while outside");
0383   VECGEOM_NAV_VALIDATE(max_dist > 0, << "max_dist=" << max_dist << " must be positive");
0384 
0385   // Note: search distance may need to be bumped slightly to capture boundaries at maximum distance
0386   auto next_distance = Navigator::ComputeStepAndNextVolume(pos_, dir_, max_dist, cur_state_, nxt_state_);
0387   // Next distance should only be non-positive if a boundary is hit
0388   VECGEOM_ASSERT(nxt_state_.IsOnBoundary() || next_distance > 0);
0389 
0390   VECGEOM_NAV_VALIDATE(next_distance <= max_dist,
0391                        << "returned distance " << next_distance << " is beyond maximum " << max_dist);
0392 
0393   if (!nxt_state_.IsOnBoundary())
0394     return NavFindResult::Miss();
0395   else if (next_distance > 0)
0396     return NavFindResult{next_distance}; // Hit
0397   else if (next_distance == 0)
0398     return NavFindResult::Reentrant(); // Already on a boundary and exiting
0399   else
0400     return NavFindResult::Error(); // Backward or NaN (error)
0401 }
0402 
0403 /*!
0404  * Change the current track direction.
0405  *
0406  * May be called while on a boundary (e.g., before EM boundary crossing or
0407  * after optical reflection).
0408  *
0409  * \pre \c dir is normalised to unit length within machine epsilon.
0410  *
0411  * \param dir New direction unit vector.
0412  *
0413  * \post If in the interior of a volume, the next state is no longer valid, and
0414  * \c FindNextBoundary \em must be called before further movement.
0415  * If on a boundary, the next state \em may be invalid: if the new direction
0416  * points toward the interior, then \c FindNextBoundary \em should be called to
0417  * avoid two unnecessary crossings.
0418  *
0419  * \note When on a boundary, changing direction will only invalidate the next
0420  * state if the sign of \f$ \vec \Omega \cdot \vec n \f$ changes (i.e., if the
0421  * direction was previously outward and with the new direction is inward). In
0422  * the spirit of the zero-overhead principle, we do not do this for
0423  * production code.
0424  */
0425 VECGEOM_FUNCTION void NavView::ChangeDirection(SpanConstReal3 dir)
0426 {
0427   VECGEOM_NAV_VALIDATE(IsUnitNormal(dir), << "direction " << dir << " is not a unit vector");
0428   dir_ = dir;
0429   if (!cur_state_.IsOnBoundary()) {
0430     // NOTE: this is here for error checking purposes and should be removed for production use
0431     nxt_state_.Clear();
0432   }
0433 }
0434 
0435 /*!
0436  * Move to an arbitrary position within the current safety sphere.
0437  *
0438  * Used for charged tracks whose position is displaced transversely inside
0439  * the safety bubble (e.g.\ by magnetic-field or multiple-scattering
0440  * correction), where the displacement is guaranteed not to cross a
0441  * boundary.
0442  *
0443  * \pre \c pos lies within the current safety sphere and is not on a boundary.
0444  *
0445  * \param pos New global position.
0446  *
0447  * \post Boundary flag is false.
0448  */
0449 VECGEOM_FUNCTION void NavView::MoveInternal(SpanConstReal3 pos)
0450 {
0451   // TODO: check against safety?
0452   VECGEOM_NAV_VALIDATE(!this->IsOutside(), << "cannot move while outside");
0453   pos_ = pos;
0454   cur_state_.SetBoundaryState(false);
0455   nxt_state_.SetBoundaryState(false);
0456   // TODO: validate the volume hasn't changed
0457 }
0458 
0459 /*!
0460  * Move along the current direction without reaching a boundary.
0461  *
0462  * Used for neutral tracks (photons) and for charged tracks near a
0463  * boundary where the safety is less than the sagitta or miss distance.
0464  * Unlike Geant4, no re-location call is needed after this move.
0465  *
0466  * \pre \c step is strictly positive.
0467  * \pre \c step is strictly less than the distance to the next boundary.
0468  *
0469  * \param step Distance to advance along the current direction.
0470  *
0471  * \post Boundary flag is false.
0472  */
0473 VECGEOM_FUNCTION void NavView::MoveInternal(Real step)
0474 {
0475   VECGEOM_NAV_VALIDATE(!this->IsOutside(), << "cannot move while outside");
0476   this->MoveInternalImpl(step);
0477   cur_state_.SetBoundaryState(false);
0478 }
0479 
0480 /*!
0481  * Advance to the boundary at the exact distance returned by
0482  * \c FindNextBoundary.
0483  *
0484  * \pre \c FindNextBoundary returned \c hit or \c reentrant.
0485  * \pre \c ChangeDirection has \em not been called since the last
0486  *      \c FindNextBoundary.
0487  *
0488  * \param step Exact distance returned by the preceding \c FindNextBoundary
0489  *             call.
0490  *
0491  * \post The track is on the boundary (distance from true surface less than
0492  *       a small tolerance, but potentially more than machine epsilon).
0493  * \post Boundary state is true.
0494  */
0495 VECGEOM_FUNCTION void NavView::MoveToBoundary(Real step)
0496 {
0497   VECGEOM_NAV_VALIDATE(step > 0, << "boundary must be a positive distance away");
0498   // TODO: validate input step is equal (within tolerance) to the last
0499   // NavFindResult that returned a hit (before direction change)
0500   // TODO: validate that a direction change on the boundary has not occurred
0501   VECGEOM_NAV_VALIDATE(nxt_state_.IsOnBoundary(), << "next state is not on a boundary");
0502   this->MoveInternalImpl(step);
0503   cur_state_.SetBoundaryState(true);
0504   // TODO: verify endpoint is on boundary of current or next volume?
0505 }
0506 
0507 /*!
0508  * @brief Compute the global boundary normal pointing from current to next state.
0509  *
0510  * "Next" is the state prepared by FindNextBoundary, not the particle direction.
0511  * Changing direction at the boundary preserves that state and this normal.
0512  * Leaving the current solid uses its outward normal; entering a daughter uses
0513  * the negated daughter normal. BVHNavigator::ComputeNextNormal performs the
0514  * calculation, including conversion to global coordinates.
0515  *
0516  * Retain the returned value in the caller if needed after CrossBoundary,
0517  * which adopts next as current and clears next. No normal is cached by the view.
0518  * Edges and corners follow the selected solid's Normal convention.
0519  *
0520  * @pre FindNextBoundary returned hit or reentrant; MoveToBoundary placed the
0521  * track at that boundary. Current and next represent distinct paths.
0522  * @pre CrossBoundary has not yet been called for this transition.
0523  *
0524  * @return Unit global normal from current toward next.
0525  * @throws std::runtime_error On an invalid workflow or unavailable solid normal
0526  * (e.g. an unresolved assembly); asserts instead on device.
0527  */
0528 VECGEOM_FUNCTION auto NavView::CalcNextNormal() const -> Real3
0529 {
0530   VECGEOM_NAV_VALIDATE(nxt_state_.IsOnBoundary(), << "FindNextBoundary not yet called");
0531   VECGEOM_NAV_VALIDATE(cur_state_.IsOnBoundary(), << "MoveToBoundary not yet called");
0532   VECGEOM_NAV_VALIDATE(!cur_state_.IsOutside(), << "cannot query a normal while outside");
0533   VECGEOM_NAV_VALIDATE(!cur_state_.HasSamePathAsOther(nxt_state_), << "current and next paths must differ");
0534   Real3 normal;
0535   VECGEOM_NAV_VALIDATE(Navigator::ComputeNextNormal(pos_, cur_state_, nxt_state_, normal),
0536                        << "boundary normal is unavailable");
0537   return normal;
0538 }
0539 
0540 /*!
0541  * Cross the current boundary and update the navigation state.
0542  *
0543  * Performs relocation: the "next" half-cooked state becomes the fully
0544  * correct current state.  The "next" state is then invalidated, so
0545  * \c FindNextBoundary must be called again before the next
0546  * \c MoveToBoundary or \c CrossBoundary.
0547  *
0548  * \pre The track is on a boundary (i.e.\ \c FindNextBoundary and
0549  *      \c MoveToBoundary have been called in order).
0550  *
0551  * \post The current navigation path reflects the new volume; calls to
0552  *       \c GetLogicalVolumeId and \c GetPlacedVolumeId will return updated
0553  *       values.
0554  * \post The internal next state is invalidated.
0555  *
0556  * \todo Should we \em swap current and next state so that we can move
0557  * back and forth cheaply, and then how would that affect FindNextBoundary?
0558  * What about a method ReenterBoundary()?
0559  */
0560 VECGEOM_FUNCTION void NavView::CrossBoundary()
0561 {
0562   VECGEOM_NAV_VALIDATE(nxt_state_.IsOnBoundary(), << "FindNextBoundary not yet called");
0563   VECGEOM_NAV_VALIDATE(cur_state_.IsOnBoundary(), << "MoveToBoundary not yet called");
0564 
0565   cur_state_ = nxt_state_;
0566   if (!cur_state_.IsOutside()) {
0567     Navigator::RelocateToNextVolume(pos_, dir_, cur_state_);
0568   }
0569   VECGEOM_ASSERT(cur_state_.IsOnBoundary());
0570 
0571   nxt_state_.Clear();
0572 }
0573 
0574 //---------------------------------------------------------------------------//
0575 // ACCESSORS
0576 //---------------------------------------------------------------------------//
0577 
0578 /*!
0579  * Whether the navigation state is outside.
0580  *
0581  * This should be "true" for uninitialized states as well.
0582  */
0583 VECGEOM_FUNCTION bool NavView::IsOutside() const { return cur_state_.IsOutside(); }
0584 
0585 /*!
0586  * Whether the navigation state is on the boundary of a volume.
0587  *
0588  * This should always be \c false if outside.
0589  */
0590 VECGEOM_FUNCTION bool NavView::IsOnBoundary() const { return cur_state_.IsOnBoundary(); }
0591 
0592 /*!
0593  * VecGeom logical volume ID of the current volume.
0594  *
0595  * On host this can be mapped to \c vecgeom::LogicalVolume* and from there
0596  * to Adept/Celeritas materials, sensitive-detector regions, or Geant4 logical
0597  * volume pointers.
0598  *
0599  * \pre \c IsOutside must be \c false
0600  */
0601 VECGEOM_FUNCTION auto NavView::GetLogicalVolumeId() const -> LogicalVolumeId
0602 {
0603   VECGEOM_NAV_VALIDATE(!cur_state_.IsOutside(), << "cannot query ID while outside");
0604   return cur_state_.GetLogicalId();
0605 }
0606 
0607 /*!
0608  * VecGeom placed volume ID of the current volume instance.
0609  *
0610  * On host this can be mapped to \c vecgeom::VPlacedVolume* and from there
0611  * to a \c G4VPhysicalVolume*.
0612  */
0613 VECGEOM_FUNCTION auto NavView::GetPlacedVolumeId() const -> PlacedVolumeId
0614 {
0615   VECGEOM_NAV_VALIDATE(!cur_state_.IsOutside(), << "cannot query ID while outside");
0616   auto *pv = cur_state_.Top();
0617   VECGEOM_ASSERT(pv);
0618   return pv->id();
0619 }
0620 
0621 /*!
0622  * Access the current navigation path as an opaque value.
0623  *
0624  * The path encodes the full touchable hierarchy and is only meaningful in
0625  * the context of the VecGeom geometry from which this view was
0626  * constructed.
0627  */
0628 VECGEOM_FUNCTION auto NavView::GetOpaquePath() const -> OpaquePath const & { return cur_state_.GetState(); }
0629 
0630 /*!
0631  * Move a straight-line distance without error checking.
0632  *
0633  * This is optimized to avoid aliasing issues and to use fused multiply-add for higher precision.
0634  */
0635 VECGEOM_FUNCTION void NavView::MoveInternalImpl(Real distance)
0636 {
0637   Real3 newpos;
0638   for (int i = 0; i < 3; ++i) {
0639     newpos[i] = std::fma(distance, dir_[i], pos_[i]);
0640   }
0641   pos_ = newpos;
0642 }
0643 /*!
0644  * Validate directions have unit normal.
0645  *
0646  * This will be replaced by corecel::ArraySoftUnit : see
0647  * https://celeritas-project.github.io/celeritas/user/implementation/corecel/numerics.html#_CPPv4I0EN9celeritas13ArraySoftUnitE
0648  * .
0649  */
0650 VECGEOM_FUNCTION bool NavView::IsUnitNormal(SpanConstReal3 v)
0651 {
0652   constexpr Real epsilon{1e-14};
0653   Real length_sq{0};
0654   for (int i = 0; i < 3; ++i) {
0655     length_sq = std::fma(v[i], v[i], length_sq);
0656   }
0657   // Factor of 3 is a loose bound of triangle inequality
0658   return Abs(length_sq - 1) < 3 * epsilon;
0659 }
0660 
0661 //---------------------------------------------------------------------------//
0662 } // namespace vecgeom