Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-25 08:16:59

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