Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-17 08:02:20

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #include "Acts/Geometry/TrackingVolume.hpp"
0010 
0011 #include "Acts/Definitions/Direction.hpp"
0012 #include "Acts/Geometry/GeometryIdentifier.hpp"
0013 #include "Acts/Geometry/GlueVolumesDescriptor.hpp"
0014 #include "Acts/Geometry/Portal.hpp"
0015 #include "Acts/Geometry/TrackingGeometryVisitor.hpp"
0016 #include "Acts/Geometry/VolumeBounds.hpp"
0017 #include "Acts/Material/IMaterialDecorator.hpp"
0018 #include "Acts/Material/IVolumeMaterial.hpp"
0019 #include "Acts/Navigation/INavigationPolicy.hpp"
0020 #include "Acts/Navigation/NavigationStream.hpp"
0021 #include "Acts/Propagator/Navigator.hpp"
0022 #include "Acts/Surfaces/RegularSurface.hpp"
0023 #include "Acts/Surfaces/Surface.hpp"
0024 #include "Acts/Surfaces/SurfaceArray.hpp"
0025 #include "Acts/Utilities/Intersection.hpp"
0026 
0027 #include <algorithm>
0028 #include <memory>
0029 #include <ostream>
0030 #include <string>
0031 #include <utility>
0032 
0033 #include <boost/container/small_vector.hpp>
0034 
0035 namespace Acts {
0036 
0037 TrackingVolume::TrackingVolume(
0038     const Transform3& transform, std::shared_ptr<VolumeBounds> volumeBounds,
0039     std::shared_ptr<const IVolumeMaterial> volumeMaterial,
0040     std::unique_ptr<const LayerArray> staticLayerArray,
0041     std::shared_ptr<const TrackingVolumeArray> containedVolumeArray,
0042     MutableTrackingVolumeVector denseVolumeVector,
0043     const std::string& volumeName)
0044     : Volume(transform, std::move(volumeBounds)),
0045       m_confinedLayers(std::move(staticLayerArray)),
0046       m_confinedVolumes(std::move(containedVolumeArray)),
0047       m_confinedDenseVolumes({}),
0048       m_volumeMaterial(std::move(volumeMaterial)),
0049       m_name(volumeName) {
0050   createBoundarySurfaces();
0051   interlinkLayers();
0052   connectDenseBoundarySurfaces(denseVolumeVector);
0053 
0054   m_navigationDelegate.connect<&INavigationPolicy::noopInitializeCandidates>();
0055 }
0056 
0057 TrackingVolume::TrackingVolume(Volume& volume, const std::string& volumeName)
0058     : TrackingVolume(volume.transform(), volume.volumeBoundsPtr(), nullptr,
0059                      nullptr, nullptr, MutableTrackingVolumeVector{},
0060                      volumeName) {}
0061 
0062 TrackingVolume::TrackingVolume(const Transform3& transform,
0063                                std::shared_ptr<VolumeBounds> volbounds,
0064                                const std::string& volumeName)
0065     : TrackingVolume(transform, std::move(volbounds), nullptr, nullptr, nullptr,
0066                      {}, volumeName) {}
0067 
0068 TrackingVolume::~TrackingVolume() = default;
0069 TrackingVolume::TrackingVolume(TrackingVolume&&) noexcept = default;
0070 TrackingVolume& TrackingVolume::operator=(TrackingVolume&&) noexcept = default;
0071 
0072 const TrackingVolume* TrackingVolume::lowestTrackingVolume(
0073     const GeometryContext& gctx, const Vector3& position,
0074     const double tol) const {
0075   if (!inside(position, tol)) {
0076     return nullptr;
0077   }
0078 
0079   // confined static volumes - highest hierarchy
0080   if (m_confinedVolumes) {
0081     const TrackingVolume* volume = m_confinedVolumes->object(position).get();
0082     if (volume != nullptr) {
0083       return volume->lowestTrackingVolume(gctx, position, tol);
0084     }
0085   }
0086 
0087   // search for dense volumes
0088   if (!m_confinedDenseVolumes.empty()) {
0089     for (auto& denseVolume : m_confinedDenseVolumes) {
0090       if (denseVolume->inside(position, tol)) {
0091         return denseVolume.get();
0092       }
0093     }
0094   }
0095 
0096   // @TODO: Abstract this into an accelerateable structure
0097   for (const auto& volume : volumes()) {
0098     if (volume.inside(position, tol)) {
0099       return volume.lowestTrackingVolume(gctx, position, tol);
0100     }
0101   }
0102 
0103   // there is no lower sub structure
0104   return this;
0105 }
0106 
0107 const TrackingVolumeBoundaries& TrackingVolume::boundarySurfaces() const {
0108   return m_boundarySurfaces;
0109 }
0110 
0111 void TrackingVolume::connectDenseBoundarySurfaces(
0112     MutableTrackingVolumeVector& confinedDenseVolumes) {
0113   if (!confinedDenseVolumes.empty()) {
0114     Direction dir = Direction::Positive();
0115     // Walk over each dense volume
0116     for (auto& confDenseVol : confinedDenseVolumes) {
0117       // Walk over each boundary surface of the volume
0118       auto& boundSur = confDenseVol->boundarySurfaces();
0119       for (std::size_t i = 0; i < boundSur.size(); i++) {
0120         // Skip empty entries since we do not know the shape of the dense volume
0121         // and therewith the used indices
0122         if (boundSur.at(i) == nullptr) {
0123           continue;
0124         }
0125 
0126         // Use mother volume as the opposite direction of the already used
0127         // direction
0128         auto mutableBs =
0129             std::const_pointer_cast<BoundarySurfaceT<TrackingVolume>>(
0130                 boundSur.at(i));
0131         if (mutableBs->m_oppositeVolume != nullptr &&
0132             mutableBs->m_alongVolume == nullptr) {
0133           dir = Direction::Positive();
0134           mutableBs->attachVolume(this, dir);
0135         } else {
0136           if (mutableBs->m_oppositeVolume == nullptr &&
0137               mutableBs->m_alongVolume != nullptr) {
0138             dir = Direction::Negative();
0139             mutableBs->attachVolume(this, dir);
0140           }
0141         }
0142 
0143         // Update the boundary
0144         confDenseVol->updateBoundarySurface(static_cast<BoundarySurfaceFace>(i),
0145                                             mutableBs);
0146       }
0147       // Store the volume
0148       m_confinedDenseVolumes.push_back(std::move(confDenseVol));
0149     }
0150   }
0151 }
0152 
0153 void TrackingVolume::createBoundarySurfaces() {
0154   using Boundary = BoundarySurfaceT<TrackingVolume>;
0155 
0156   // Transform Surfaces To BoundarySurfaces
0157   auto orientedSurfaces = Volume::volumeBounds().orientedSurfaces(m_transform);
0158 
0159   m_boundarySurfaces.reserve(orientedSurfaces.size());
0160   for (auto& osf : orientedSurfaces) {
0161     TrackingVolume* opposite = nullptr;
0162     TrackingVolume* along = nullptr;
0163     if (osf.direction == Direction::OppositeNormal()) {
0164       opposite = this;
0165     } else {
0166       along = this;
0167     }
0168     m_boundarySurfaces.push_back(std::make_shared<const Boundary>(
0169         std::move(osf.surface), opposite, along));
0170   }
0171 }
0172 
0173 void TrackingVolume::clearBoundarySurfaces() {
0174   m_boundarySurfaces.clear();
0175 }
0176 
0177 void TrackingVolume::glueTrackingVolume(const GeometryContext& gctx,
0178                                         BoundarySurfaceFace bsfMine,
0179                                         TrackingVolume* neighbor,
0180                                         BoundarySurfaceFace bsfNeighbor) {
0181   // Find the connection of the two tracking volumes: AxisDirection::AxisR
0182   // returns the center except for cylindrical volumes
0183   Vector3 bPosition(referencePosition(gctx, AxisDirection::AxisR));
0184   Vector3 distance = Vector3(
0185       neighbor->referencePosition(gctx, AxisDirection::AxisR) - bPosition);
0186   // glue to the face
0187   std::shared_ptr<const BoundarySurfaceT<TrackingVolume>> bSurfaceMine =
0188       boundarySurfaces().at(bsfMine);
0189   // @todo - complex glueing could be possible with actual intersection for the
0190   // normal vector
0191   // Coerce the arbitrary position bPosition to be on the surface repr so we can
0192   // get a normal
0193   Vector3 nvector =
0194       bSurfaceMine->surfaceRepresentation().normal(gctx, bPosition);
0195   // estimate the orientation
0196   Direction dir = Direction::fromScalar(nvector.dot(distance));
0197   // The easy case :
0198   // - no glue volume descriptors on either side
0199   if ((m_glueVolumeDescriptor == nullptr) ||
0200       m_glueVolumeDescriptor->glueVolumes(bsfMine) == nullptr) {
0201     // the boundary orientation
0202     auto mutableBSurfaceMine =
0203         std::const_pointer_cast<BoundarySurfaceT<TrackingVolume>>(bSurfaceMine);
0204     mutableBSurfaceMine->attachVolume(neighbor, dir);
0205     // Make sure you keep the boundary material if there
0206     const Surface& neighborSurface =
0207         neighbor->m_boundarySurfaces.at(bsfNeighbor)->surfaceRepresentation();
0208     auto neighborMaterial = neighborSurface.surfaceMaterialSharedPtr();
0209     const Surface& mySurface = bSurfaceMine->surfaceRepresentation();
0210     auto myMaterial = mySurface.surfaceMaterialSharedPtr();
0211     // Keep the neighbor material
0212     if (myMaterial == nullptr && neighborMaterial != nullptr) {
0213       Surface* myMutbableSurface = const_cast<Surface*>(&mySurface);
0214       myMutbableSurface->assignSurfaceMaterial(neighborMaterial);
0215     }
0216     // Now set it to the neighbor volume
0217     (neighbor->m_boundarySurfaces).at(bsfNeighbor) = bSurfaceMine;
0218   }
0219 }
0220 
0221 void TrackingVolume::glueTrackingVolumes(
0222     const GeometryContext& gctx, BoundarySurfaceFace bsfMine,
0223     const std::shared_ptr<TrackingVolumeArray>& neighbors,
0224     BoundarySurfaceFace bsfNeighbor) {
0225   // find the connection of the two tracking volumes : AxisDirection::AxisR
0226   // returns the center except for cylindrical volumes
0227   std::shared_ptr<const TrackingVolume> nRefVolume =
0228       neighbors->arrayObjects().at(0);
0229   // get the distance
0230   Vector3 bPosition(referencePosition(gctx, AxisDirection::AxisR));
0231   Vector3 distance(nRefVolume->referencePosition(gctx, AxisDirection::AxisR) -
0232                    bPosition);
0233   // take the normal at the binning positio
0234   std::shared_ptr<const BoundarySurfaceT<TrackingVolume>> bSurfaceMine =
0235       boundarySurfaces().at(bsfMine);
0236   // @todo - complex glueing could be possible with actual intersection for the
0237   // normal vector
0238   // Coerce the arbitrary position bPosition to be on the surface repr so we can
0239   // get a normal
0240   Vector3 nvector =
0241       bSurfaceMine->surfaceRepresentation().normal(gctx, bPosition);
0242   // estimate the orientation
0243   Direction dir = Direction::fromScalar(nvector.dot(distance));
0244   // the easy case :
0245   // - no glue volume descriptors on either side
0246   if ((m_glueVolumeDescriptor == nullptr) ||
0247       !m_glueVolumeDescriptor->glueVolumes(bsfMine)) {
0248     // the boundary orientation
0249     auto mutableBSurfaceMine =
0250         std::const_pointer_cast<BoundarySurfaceT<TrackingVolume>>(bSurfaceMine);
0251     mutableBSurfaceMine->attachVolumeArray(neighbors, dir);
0252     // now set it to the neighbor volumes - the optised way
0253     for (auto& nVolume : neighbors->arrayObjects()) {
0254       auto mutableNVolume = std::const_pointer_cast<TrackingVolume>(nVolume);
0255       (mutableNVolume->m_boundarySurfaces).at(bsfNeighbor) = bSurfaceMine;
0256     }
0257   }
0258 }
0259 
0260 void TrackingVolume::assignBoundaryMaterial(
0261     std::shared_ptr<const ISurfaceMaterial> surfaceMaterial,
0262     BoundarySurfaceFace bsFace) {
0263   auto bSurface = m_boundarySurfaces.at(bsFace);
0264   RegularSurface* surface =
0265       const_cast<RegularSurface*>(&bSurface->surfaceRepresentation());
0266   surface->assignSurfaceMaterial(std::move(surfaceMaterial));
0267 }
0268 
0269 void TrackingVolume::updateBoundarySurface(
0270     BoundarySurfaceFace bsf,
0271     std::shared_ptr<const BoundarySurfaceT<TrackingVolume>> bs,
0272     bool checkmaterial) {
0273   if (checkmaterial) {
0274     auto cMaterialPtr = m_boundarySurfaces.at(bsf)
0275                             ->surfaceRepresentation()
0276                             .surfaceMaterialSharedPtr();
0277     auto bsMaterial = bs->surfaceRepresentation().surfaceMaterial();
0278     if (cMaterialPtr != nullptr && bsMaterial == nullptr) {
0279       RegularSurface* surface =
0280           const_cast<RegularSurface*>(&bs->surfaceRepresentation());
0281       surface->assignSurfaceMaterial(cMaterialPtr);
0282     }
0283   }
0284   m_boundarySurfaces.at(bsf) = std::move(bs);
0285 }
0286 
0287 void TrackingVolume::registerGlueVolumeDescriptor(
0288     std::unique_ptr<GlueVolumesDescriptor> gvd) {
0289   m_glueVolumeDescriptor = std::move(gvd);
0290 }
0291 
0292 GlueVolumesDescriptor& TrackingVolume::glueVolumesDescriptor() {
0293   if (m_glueVolumeDescriptor == nullptr) {
0294     m_glueVolumeDescriptor = std::make_unique<GlueVolumesDescriptor>();
0295   }
0296   return *m_glueVolumeDescriptor;
0297 }
0298 
0299 void TrackingVolume::synchronizeLayers(double envelope) const {
0300   // case a : Layers exist
0301   // msgstream << MSG::VERBOSE << "  -> synchronizing Layer dimensions of
0302   // TrackingVolume '" << volumeName() << "'." << endreq;
0303 
0304   if (m_confinedLayers) {
0305     // msgstream << MSG::VERBOSE << "  ---> working on " <<
0306     // m_confinedLayers->arrayObjects().size() << " (material+navigation)
0307     // layers." << endreq;
0308     for (auto& clayIter : m_confinedLayers->arrayObjects()) {
0309       if (clayIter) {
0310         // @todo implement synchronize layer
0311         //  if (clayIter->surfaceRepresentation().type() == Surface::Cylinder &&
0312         //  !(center().isApprox(clayIter->surfaceRepresentation().center())) )
0313         //      clayIter->resizeAndRepositionLayer(volumeBounds(),center(),envelope);
0314         //  else
0315         //      clayIter->resizeLayer(volumeBounds(),envelope);
0316       }  // else
0317       // msgstream << MSG::WARNING << "  ---> found 0 pointer to layer,
0318       // indicates problem." << endreq;
0319     }
0320   }
0321 
0322   // case b : container volume -> step down
0323   if (m_confinedVolumes) {
0324     // msgstream << MSG::VERBOSE << "  ---> no confined layers, working on " <<
0325     // m_confinedVolumes->arrayObjects().size() << " confined volumes." <<
0326     // endreq;
0327     for (auto& cVolumesIter : m_confinedVolumes->arrayObjects()) {
0328       cVolumesIter->synchronizeLayers(envelope);
0329     }
0330   }
0331 }
0332 
0333 void TrackingVolume::interlinkLayers() {
0334   if (m_confinedLayers) {
0335     auto& layers = m_confinedLayers->arrayObjects();
0336 
0337     // forward register the last one as the previous one
0338     //  first <- | -> second, first <- | -> second, first <- | -> second
0339     const Layer* lastLayer = nullptr;
0340     for (auto& layerPtr : layers) {
0341       // we'll need to mutate our confined layers to perform this operation
0342       Layer& mutableLayer = *(std::const_pointer_cast<Layer>(layerPtr));
0343       // register the layers
0344       mutableLayer.m_nextLayerUtility = m_confinedLayers->binUtility();
0345       mutableLayer.m_nextLayers.first = lastLayer;
0346       // register the volume
0347       mutableLayer.encloseTrackingVolume(*this);
0348       // remember the last layer
0349       lastLayer = &mutableLayer;
0350     }
0351     // backward loop
0352     lastLayer = nullptr;
0353     for (auto layerIter = layers.rbegin(); layerIter != layers.rend();
0354          ++layerIter) {
0355       // set the other next volume
0356       Layer& mutableLayer = *(std::const_pointer_cast<Layer>(*layerIter));
0357       mutableLayer.m_nextLayers.second = lastLayer;
0358       lastLayer = &mutableLayer;
0359     }
0360   }
0361 }
0362 
0363 // Returns the boundary surfaces ordered in probability to hit them based on
0364 boost::container::small_vector<BoundaryIntersection, 4>
0365 TrackingVolume::compatibleBoundaries(const GeometryContext& gctx,
0366                                      const Vector3& position,
0367                                      const Vector3& direction,
0368                                      const NavigationOptions<Surface>& options,
0369                                      const Logger& logger) const {
0370   ACTS_VERBOSE("Finding compatibleBoundaries");
0371 
0372   boost::container::small_vector<BoundaryIntersection, 4> intersections;
0373 
0374   // The limits for this navigation step
0375   double nearLimit = options.nearLimit;
0376   double farLimit = options.farLimit;
0377 
0378   // Helper function to test intersection
0379   auto checkIntersection =
0380       [&](SurfaceMultiIntersection& candidates,
0381           const BoundarySurface* boundary) -> BoundaryIntersection {
0382     for (const auto& intersection : candidates.split()) {
0383       if (!intersection.isValid()) {
0384         continue;
0385       }
0386 
0387       ACTS_VERBOSE("Check intersection with surface "
0388                    << boundary->surfaceRepresentation().geometryId());
0389       if (detail::checkPathLength(intersection.pathLength(), nearLimit,
0390                                   farLimit, logger)) {
0391         return BoundaryIntersection(intersection, boundary, nullptr);
0392       }
0393     }
0394 
0395     ACTS_VERBOSE("No intersection accepted");
0396     return BoundaryIntersection(SurfaceIntersection::invalid(), nullptr,
0397                                 nullptr);
0398   };
0399 
0400   /// Helper function to process boundary surfaces
0401   auto processBoundaries =
0402       [&](const TrackingVolumeBoundaries& boundaries) -> void {
0403     // Loop over the boundary surfaces
0404     for (auto& boundary : boundaries) {
0405       // Get the boundary surface pointer
0406       const auto& surface = boundary->surfaceRepresentation();
0407       ACTS_VERBOSE("Consider boundary surface " << surface.geometryId());
0408 
0409       // Exclude the boundary where you are on
0410       // TODO this is not optimal as we might exit via the same boundary (e.g.
0411       // cylinder)
0412       if (&surface == options.startObject) {
0413         ACTS_VERBOSE(" - Surface is excluded surface");
0414         continue;
0415       }
0416 
0417       auto candidates = surface.intersect(gctx, position, direction,
0418                                           options.boundaryTolerance);
0419       // Intersect and continue
0420       auto intersection = checkIntersection(candidates, boundary.get());
0421       if (intersection.intersection.isValid()) {
0422         ACTS_VERBOSE(" - Proceed with surface");
0423         intersections.push_back(intersection);
0424       } else {
0425         ACTS_VERBOSE(" - Surface intersecion invalid");
0426       }
0427     }
0428   };
0429 
0430   // Process the boundaries of the current volume
0431   const auto& surfaces = boundarySurfaces();
0432   ACTS_VERBOSE("Volume reports " << surfaces.size() << " boundary surfaces");
0433   processBoundaries(surfaces);
0434 
0435   // Process potential boundaries of contained volumes
0436   auto confinedDenseVolumes = denseVolumes();
0437   ACTS_VERBOSE("Volume reports " << confinedDenseVolumes.size()
0438                                  << " confined dense volumes");
0439   for (const auto& volume : confinedDenseVolumes) {
0440     const auto& surfacesConfined = volume->boundarySurfaces();
0441     ACTS_VERBOSE(" -> " << surfacesConfined.size() << " boundary surfaces");
0442     processBoundaries(surfacesConfined);
0443   }
0444 
0445   return intersections;
0446 }
0447 
0448 boost::container::small_vector<LayerIntersection, 10>
0449 TrackingVolume::compatibleLayers(
0450     const GeometryContext& gctx, const Vector3& position,
0451     const Vector3& direction, const NavigationOptions<Layer>& options) const {
0452   // the layer intersections which are valid
0453   boost::container::small_vector<LayerIntersection, 10> lIntersections;
0454 
0455   // the confinedLayers
0456   if (m_confinedLayers == nullptr) {
0457     return {};
0458   }
0459 
0460   // start layer given or not - test layer
0461   const Layer* tLayer = options.startObject != nullptr
0462                             ? static_cast<const Layer*>(options.startObject)
0463                             : associatedLayer(gctx, position);
0464   while (tLayer != nullptr) {
0465     // check if the layer needs resolving
0466     // - resolveSensitive -> always take layer if it has a surface array
0467     // - resolveMaterial -> always take layer if it has material
0468     // - resolvePassive -> always take, unless it's a navigation layer
0469     // skip the start object
0470     if (tLayer != options.startObject && tLayer->resolve(options)) {
0471       // if it's a resolveable start layer, you are by definition on it
0472       // layer on approach intersection
0473       auto atIntersection =
0474           tLayer->surfaceOnApproach(gctx, position, direction, options);
0475       // Intersection is ok - take it (move to surface on approach)
0476       if (atIntersection.isValid()) {
0477         // create a layer intersection
0478         lIntersections.push_back(LayerIntersection(atIntersection, tLayer));
0479       }
0480     }
0481     // move to next one or break because you reached the end layer
0482     tLayer = (tLayer == options.endObject)
0483                  ? nullptr
0484                  : tLayer->nextLayer(gctx, position, direction);
0485   }
0486 
0487   // In case of cylindrical layers we might resolve far intersection solutions
0488   // which are not valid for navigation. These are discarded here by checking
0489   // against the minimum path length.
0490   auto min = std::min_element(
0491       lIntersections.begin(), lIntersections.end(),
0492       [](const LayerIntersection& a, const LayerIntersection& b) {
0493         return a.first.pathLength() < b.first.pathLength();
0494       });
0495   std::rotate(lIntersections.begin(), min, lIntersections.end());
0496   lIntersections.resize(std::distance(min, lIntersections.end()),
0497                         {SurfaceIntersection::invalid(), nullptr});
0498 
0499   return lIntersections;
0500 }
0501 
0502 const std::string& TrackingVolume::volumeName() const {
0503   return m_name;
0504 }
0505 
0506 void TrackingVolume::setVolumeName(const std::string& volumeName) {
0507   m_name = volumeName;
0508 }
0509 
0510 const IVolumeMaterial* TrackingVolume::volumeMaterial() const {
0511   return m_volumeMaterial.get();
0512 }
0513 
0514 const std::shared_ptr<const IVolumeMaterial>&
0515 TrackingVolume::volumeMaterialPtr() const {
0516   return m_volumeMaterial;
0517 }
0518 
0519 void TrackingVolume::assignVolumeMaterial(
0520     std::shared_ptr<const IVolumeMaterial> material) {
0521   m_volumeMaterial = std::move(material);
0522 }
0523 
0524 const LayerArray* TrackingVolume::confinedLayers() const {
0525   return m_confinedLayers.get();
0526 }
0527 
0528 const MutableTrackingVolumeVector TrackingVolume::denseVolumes() const {
0529   return m_confinedDenseVolumes;
0530 }
0531 
0532 std::shared_ptr<const TrackingVolumeArray> TrackingVolume::confinedVolumes()
0533     const {
0534   return m_confinedVolumes;
0535 }
0536 
0537 const TrackingVolume* TrackingVolume::motherVolume() const {
0538   return m_motherVolume;
0539 }
0540 
0541 TrackingVolume* TrackingVolume::motherVolume() {
0542   return m_motherVolume;
0543 }
0544 
0545 void TrackingVolume::setMotherVolume(TrackingVolume* mvol) {
0546   m_motherVolume = mvol;
0547 }
0548 
0549 const Layer* TrackingVolume::associatedLayer(const GeometryContext& /*gctx*/,
0550                                              const Vector3& position) const {
0551   // confined static layers - highest hierarchy
0552   if (m_confinedLayers != nullptr) {
0553     return (m_confinedLayers->object(position).get());
0554   }
0555 
0556   // return the null pointer
0557   return nullptr;
0558 }
0559 
0560 TrackingVolume::VolumeRange TrackingVolume::volumes() const {
0561   return VolumeRange{m_volumes};
0562 }
0563 
0564 TrackingVolume::MutableVolumeRange TrackingVolume::volumes() {
0565   return MutableVolumeRange{m_volumes};
0566 }
0567 
0568 TrackingVolume& TrackingVolume::addVolume(
0569     std::unique_ptr<TrackingVolume> volume) {
0570   if (volume->motherVolume() != nullptr) {
0571     throw std::invalid_argument("Volume already has a mother volume");
0572   }
0573 
0574   volume->setMotherVolume(this);
0575   m_volumes.push_back(std::move(volume));
0576   return *m_volumes.back();
0577 }
0578 
0579 TrackingVolume::PortalRange TrackingVolume::portals() const {
0580   return PortalRange{m_portals};
0581 }
0582 
0583 TrackingVolume::MutablePortalRange TrackingVolume::portals() {
0584   return MutablePortalRange{m_portals};
0585 }
0586 
0587 void TrackingVolume::addPortal(std::shared_ptr<Portal> portal) {
0588   if (portal == nullptr) {
0589     throw std::invalid_argument("Portal is nullptr");
0590   }
0591   m_portals.push_back(std::move(portal));
0592 }
0593 
0594 TrackingVolume::SurfaceRange TrackingVolume::surfaces() const {
0595   return SurfaceRange{m_surfaces};
0596 }
0597 
0598 TrackingVolume::MutableSurfaceRange TrackingVolume::surfaces() {
0599   return MutableSurfaceRange{m_surfaces};
0600 }
0601 
0602 void TrackingVolume::addSurface(std::shared_ptr<Surface> surface) {
0603   if (surface == nullptr) {
0604     throw std::invalid_argument("Surface is nullptr");
0605   }
0606   m_surfaces.push_back(std::move(surface));
0607 }
0608 
0609 void TrackingVolume::visualize(IVisualization3D& helper,
0610                                const GeometryContext& gctx,
0611                                const ViewConfig& viewConfig,
0612                                const ViewConfig& portalViewConfig,
0613                                const ViewConfig& sensitiveViewConfig) const {
0614   helper.object(volumeName());
0615   if (viewConfig.visible) {
0616     Volume::visualize(helper, gctx, viewConfig);
0617   }
0618 
0619   if (sensitiveViewConfig.visible) {
0620     if (!surfaces().empty()) {
0621       helper.object(volumeName() + "_sensitives");
0622       for (const auto& surface : surfaces()) {
0623         surface.visualize(helper, gctx, sensitiveViewConfig);
0624       }
0625     }
0626   }
0627 
0628   if (portalViewConfig.visible) {
0629     helper.object(volumeName() + "_portals");
0630     for (const auto& portal : portals()) {
0631       portal.surface().visualize(helper, gctx, portalViewConfig);
0632     }
0633   }
0634 
0635   for (const auto& child : volumes()) {
0636     child.visualize(helper, gctx, viewConfig, portalViewConfig,
0637                     sensitiveViewConfig);
0638   }
0639 }
0640 
0641 const INavigationPolicy* TrackingVolume::navigationPolicy() const {
0642   return m_navigationPolicy.get();
0643 }
0644 
0645 INavigationPolicy* TrackingVolume::navigationPolicy() {
0646   return m_navigationPolicy.get();
0647 }
0648 
0649 void TrackingVolume::setNavigationPolicy(
0650     std::unique_ptr<INavigationPolicy> policy) {
0651   if (policy == nullptr) {
0652     throw std::invalid_argument("Navigation policy is nullptr");
0653   }
0654 
0655   m_navigationPolicy = std::move(policy);
0656   m_navigationPolicy->connect(m_navigationDelegate);
0657 }
0658 
0659 void TrackingVolume::initializeNavigationCandidates(
0660     const NavigationArguments& args, AppendOnlyNavigationStream& stream,
0661     const Logger& logger) const {
0662   m_navigationDelegate(args, stream, logger);
0663 }
0664 
0665 namespace {
0666 
0667 void visitLayer(const Layer& layer, TrackingGeometryVisitor& visitor) {
0668   visitor.visitLayer(layer);
0669   // Surfaces contained in the surface array
0670   if (layer.surfaceArray() != nullptr) {
0671     for (const auto& srf : layer.surfaceArray()->surfaces()) {
0672       visitor.visitSurface(*srf);
0673     }
0674   }
0675   visitor.visitSurface(layer.surfaceRepresentation());
0676   if (layer.approachDescriptor() != nullptr) {
0677     for (const auto& srf : layer.approachDescriptor()->containedSurfaces()) {
0678       visitor.visitSurface(*srf);
0679     }
0680   }
0681 }
0682 
0683 }  // namespace
0684 
0685 // @TODO: Unify once Gen1 is removed: should share most code between mutable and const
0686 void TrackingVolume::apply(TrackingGeometryVisitor& visitor) const {
0687   visitor.visitVolume(*this);
0688 
0689   // Visit the boundary surfaces
0690   for (const auto& bs : m_boundarySurfaces) {
0691     visitor.visitBoundarySurface(*bs);
0692   }
0693 
0694   for (const auto& portal : portals()) {
0695     visitor.visitPortal(portal);
0696   }
0697 
0698   // Internal structure
0699   if (m_confinedLayers != nullptr) {
0700     std::ranges::for_each(
0701         m_confinedLayers->arrayObjects(),
0702         [&](const auto& layer) { visitLayer(*layer, visitor); });
0703   }
0704 
0705   if (m_confinedVolumes != nullptr) {
0706     // contains sub volumes
0707     for (const auto& volume : m_confinedVolumes->arrayObjects()) {
0708       volume->apply(visitor);
0709     }
0710   }
0711 
0712   for (const auto& surface : surfaces()) {
0713     visitor.visitSurface(surface);
0714   }
0715 
0716   for (const auto& volume : volumes()) {
0717     volume.apply(visitor);
0718   }
0719 }
0720 
0721 void TrackingVolume::apply(TrackingGeometryMutableVisitor& visitor) {
0722   // if the visitor is configured for inner--->outer volume visiting we visit
0723   // the children first
0724   if (visitor.visitDepthFirst()) {
0725     for (auto& volume : volumes()) {
0726       volume.apply(visitor);
0727     }
0728   }
0729 
0730   visitor.visitVolume(*this);
0731 
0732   // Visit the boundary surfaces
0733   // This does const casts because Gen1 substructure does not have transitive
0734   // const-ness
0735 
0736   // @TODO: Remove this when Gen1 is remoeved
0737 
0738   for (const auto& bs : m_boundarySurfaces) {
0739     visitor.visitBoundarySurface(
0740         const_cast<BoundarySurfaceT<TrackingVolume>&>(*bs));
0741     visitor.visitSurface(
0742         const_cast<RegularSurface&>(bs->surfaceRepresentation()));
0743   }
0744 
0745   for (auto& portal : portals()) {
0746     visitor.visitPortal(portal);
0747     visitor.visitSurface(portal.surface());
0748   }
0749 
0750   // Internal structure
0751   // This does const casts because Gen1 substructure does not have transitive
0752   // const-ness
0753   // @TODO: Remove this when Gen1 is remoeved
0754   if (m_confinedVolumes == nullptr) {
0755     // no sub volumes => loop over the confined layers
0756     if (m_confinedLayers != nullptr) {
0757       for (const auto& layer : m_confinedLayers->arrayObjects()) {
0758         visitor.visitLayer(const_cast<Layer&>(*layer));
0759         // Surfaces contained in the surface array
0760         if (layer->surfaceArray() != nullptr) {
0761           for (const auto& srf : layer->surfaceArray()->surfaces()) {
0762             visitor.visitSurface(const_cast<Surface&>(*srf));
0763           }
0764         }
0765         // Surfaces of the layer
0766         visitor.visitSurface(
0767             const_cast<Surface&>(layer->surfaceRepresentation()));
0768         // Approach surfaces of the layer
0769         if (layer->approachDescriptor() != nullptr) {
0770           for (const auto& srf :
0771                layer->approachDescriptor()->containedSurfaces()) {
0772             visitor.visitSurface(const_cast<Surface&>(*srf));
0773           }
0774         }
0775       }
0776     }
0777   } else {
0778     // contains sub volumes
0779     for (const auto& volume : m_confinedVolumes->arrayObjects()) {
0780       const_cast<TrackingVolume&>(*volume).apply(visitor);
0781     }
0782   }
0783 
0784   for (auto& surface : surfaces()) {
0785     visitor.visitSurface(surface);
0786   }
0787 
0788   if (!visitor.visitDepthFirst()) {
0789     // if the visitor is configured for outer--->inner volume visiting we visit
0790     // the children last
0791     for (auto& volume : volumes()) {
0792       volume.apply(visitor);
0793     }
0794   }
0795 }
0796 
0797 }  // namespace Acts