Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-10-23 08:22:51

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