File indexing completed on 2026-09-16 08:19:35
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Geometry/CuboidPortalShell.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Geometry/BoundarySurfaceFace.hpp"
0013 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0014 #include "Acts/Geometry/Portal.hpp"
0015 #include "Acts/Geometry/PortalLinkBase.hpp"
0016 #include "Acts/Geometry/TrackingVolume.hpp"
0017 #include "Acts/Utilities/AxisDefinitions.hpp"
0018
0019 #include <algorithm>
0020 #include <array>
0021 #include <cstddef>
0022 #include <numeric>
0023 #include <sstream>
0024 #include <stdexcept>
0025 #include <unordered_map>
0026
0027 namespace Acts {
0028
0029 void CuboidPortalShell::fill(TrackingVolume& volume) {
0030 using enum CuboidVolumeBounds::Face;
0031 for (Face face : {NegativeZFace, PositiveZFace, NegativeXFace, PositiveXFace,
0032 NegativeYFace, PositiveYFace}) {
0033 const auto& portalAtFace = portal(face);
0034 if (portalAtFace != nullptr) {
0035 portalAtFace->fill(volume);
0036 volume.addPortal(portalAtFace);
0037 }
0038 }
0039 }
0040
0041 SingleCuboidPortalShell::SingleCuboidPortalShell(const GeometryContext& gctx,
0042 TrackingVolume& volume)
0043 : m_volume{&volume} {
0044 using enum CuboidVolumeBounds::Face;
0045 if (m_volume->volumeBounds().type() != VolumeBounds::BoundsType::eCuboid) {
0046 throw std::invalid_argument(
0047 "CuboidPortalShell: Invalid volume bounds type");
0048 }
0049
0050 const auto& bounds =
0051 dynamic_cast<const CuboidVolumeBounds&>(m_volume->volumeBounds());
0052
0053 std::vector<OrientedSurface> orientedSurfaces =
0054 bounds.orientedSurfaces(m_volume->localToGlobalTransform(gctx));
0055
0056 auto handle = [&](Face face, std::size_t from) {
0057 const auto& source = orientedSurfaces.at(from);
0058 m_portals.at(toUnderlying(face)) =
0059 std::make_shared<Portal>(source.direction, source.surface, *m_volume);
0060 };
0061
0062 handle(NegativeZFace, negativeFaceXY);
0063 handle(PositiveZFace, positiveFaceXY);
0064 handle(NegativeXFace, negativeFaceYZ);
0065 handle(PositiveXFace, positiveFaceYZ);
0066 handle(NegativeYFace, negativeFaceZX);
0067 handle(PositiveYFace, positiveFaceZX);
0068 }
0069
0070 std::shared_ptr<Portal> SingleCuboidPortalShell::portal(Face face) {
0071 return m_portals.at(toUnderlying(face));
0072 }
0073
0074 void SingleCuboidPortalShell::setPortal(std::shared_ptr<Portal> portal,
0075 Face face) {
0076 assert(portal != nullptr);
0077 assert(portal->isValid());
0078 m_portals.at(toUnderlying(face)) = std::move(portal);
0079 }
0080
0081 std::size_t SingleCuboidPortalShell::size() const {
0082 std::size_t count = 0;
0083 std::ranges::for_each(
0084 m_portals, [&count](const auto& portal) { count += portal ? 1 : 0; });
0085 return count;
0086 }
0087
0088 void SingleCuboidPortalShell::applyToVolume() {
0089 for (std::size_t i = 0; i < m_portals.size(); i++) {
0090 const auto& portal = m_portals.at(i);
0091 if (portal != nullptr) {
0092 if (!portal->isValid()) {
0093 std::stringstream ss;
0094 ss << static_cast<Face>(i);
0095 throw std::runtime_error{"Invalid portal found in shell at " +
0096 ss.str()};
0097 }
0098 m_volume->addPortal(portal);
0099 }
0100 }
0101 }
0102
0103 bool SingleCuboidPortalShell::isValid() const {
0104 return std::ranges::all_of(m_portals, [](const auto& portal) {
0105 return portal == nullptr || portal->isValid();
0106 });
0107 };
0108
0109 std::string SingleCuboidPortalShell::label() const {
0110 std::stringstream ss;
0111 ss << "CuboidShell(vol=" << m_volume->volumeName() << ")";
0112 return ss.str();
0113 }
0114
0115 CuboidStackPortalShell::CuboidStackPortalShell(
0116 const GeometryContext& gctx, std::vector<CuboidPortalShell*> shells,
0117 AxisDirection direction, const Logger& logger,
0118 PortalMaterialMergePolicy materialPolicy)
0119 : m_direction(direction), m_shells{std::move(shells)} {
0120 using enum CuboidVolumeBounds::Face;
0121 using enum AxisDirection;
0122 std::tie(m_frontFace, m_backFace, m_sideFaces) =
0123 CuboidVolumeBounds::facesFromAxisDirection(m_direction);
0124
0125 std::unordered_map<Face, AxisDirection> onSurfaceDirs;
0126 switch (m_direction) {
0127 case AxisX:
0128 onSurfaceDirs = {{NegativeZFace, AxisX},
0129 {PositiveZFace, AxisX},
0130 {NegativeYFace, AxisY},
0131 {PositiveYFace, AxisY}};
0132 break;
0133 case AxisY:
0134 onSurfaceDirs = {{NegativeZFace, AxisY},
0135 {PositiveZFace, AxisY},
0136 {NegativeXFace, AxisX},
0137 {PositiveXFace, AxisX}};
0138 break;
0139 case AxisZ:
0140 onSurfaceDirs = {{NegativeXFace, AxisY},
0141 {PositiveXFace, AxisY},
0142 {NegativeYFace, AxisX},
0143 {PositiveYFace, AxisX}};
0144 break;
0145 default:
0146 throw std::invalid_argument("CuboidPortalShell: Invalid axis direction");
0147 }
0148
0149 ACTS_VERBOSE("Making cuboid stack shell in " << m_direction << " direction");
0150 if (std::ranges::any_of(m_shells,
0151 [](const auto* shell) { return shell == nullptr; })) {
0152 ACTS_ERROR("Invalid shell pointer");
0153 throw std::invalid_argument("Invalid shell pointer");
0154 }
0155
0156 ACTS_VERBOSE(" ~> " << label());
0157
0158 if (!std::ranges::all_of(
0159 m_shells, [](const auto* shell) { return shell->isValid(); })) {
0160 ACTS_ERROR("Invalid shell");
0161 throw std::invalid_argument("Invalid shell");
0162 }
0163
0164 std::ranges::sort(
0165 m_shells, [*this, &gctx](const auto& shellA, const auto& shellB) {
0166 switch (m_direction) {
0167 case AxisX:
0168 return (shellA->localToGlobalTransform(gctx).translation().x() <
0169 shellB->localToGlobalTransform(gctx).translation().x());
0170 case AxisY:
0171 return (shellA->localToGlobalTransform(gctx).translation().y() <
0172 shellB->localToGlobalTransform(gctx).translation().y());
0173 case AxisZ:
0174 return (shellA->localToGlobalTransform(gctx).translation().z() <
0175 shellB->localToGlobalTransform(gctx).translation().z());
0176 default:
0177 throw std::invalid_argument(
0178 "CuboidPortalShell: Invalid axis direction");
0179 }
0180 });
0181
0182 auto merge = [&](Face face) {
0183 std::vector<std::shared_ptr<Portal>> portals;
0184 std::ranges::transform(m_shells, std::back_inserter(portals),
0185 [face](auto* shell) { return shell->portal(face); });
0186
0187 std::shared_ptr<Portal> merged;
0188 try {
0189 merged = std::accumulate(
0190 std::next(portals.begin()), portals.end(), portals.front(),
0191 [&](const auto& aPortal,
0192 const auto& bPortal) -> std::shared_ptr<Portal> {
0193 assert(aPortal != nullptr);
0194 assert(bPortal != nullptr);
0195
0196 AxisDirection onSurfaceAxis = onSurfaceDirs.at(face);
0197
0198 return std::make_shared<Portal>(
0199 Portal::merge(gctx, *aPortal, *bPortal, onSurfaceAxis, logger,
0200 materialPolicy));
0201 });
0202 } catch (const PortalMergingException& e) {
0203 std::stringstream ss;
0204 ss << "Failed to merge portals at face " << face << " while building "
0205 << label() << " (stacking in " << m_direction << "). Shells involved:";
0206 for (const auto* shell : m_shells) {
0207 ss << "\n - " << shell->label();
0208 }
0209 ss << "\nUnderlying error: " << e.what();
0210 ACTS_ERROR(ss.str());
0211 throw PortalMergingException{ss.str()};
0212 }
0213
0214 assert(merged != nullptr);
0215 assert(merged->isValid());
0216
0217
0218 for (auto& shell : m_shells) {
0219 shell->setPortal(merged, face);
0220 }
0221 };
0222
0223 auto fuse = [&](Face faceA, Face faceB) {
0224 for (std::size_t i = 1; i < m_shells.size(); i++) {
0225 auto& shellA = m_shells.at(i - 1);
0226 auto& shellB = m_shells.at(i);
0227 ACTS_VERBOSE("Fusing " << shellA->label() << " and " << shellB->label());
0228
0229 auto fused = std::make_shared<Portal>(Portal::fuse(
0230 gctx, *shellA->portal(faceA), *shellB->portal(faceB), logger));
0231
0232 assert(fused != nullptr && "Invalid fused portal");
0233 assert(fused->isValid() && "Fused portal is invalid");
0234
0235 shellA->setPortal(fused, faceA);
0236 shellB->setPortal(fused, faceB);
0237
0238 assert(shellA->isValid() && "Shell A is not valid after fusing");
0239 assert(shellB->isValid() && "Shell B is not valid after fusing");
0240 }
0241 };
0242
0243 for (const auto& face : m_sideFaces) {
0244 merge(face);
0245 }
0246 fuse(m_backFace, m_frontFace);
0247 assert(isValid() && "Shell is not valid after construction");
0248 }
0249
0250 std::size_t CuboidStackPortalShell::size() const {
0251 return 6;
0252 }
0253
0254 std::vector<CuboidStackPortalShell::Face> CuboidStackPortalShell::mergedFaces(
0255 AxisDirection direction) {
0256
0257
0258 const auto& [frontFace, backFace, sideFaces] =
0259 CuboidVolumeBounds::facesFromAxisDirection(direction);
0260 return {sideFaces.begin(), sideFaces.end()};
0261 }
0262
0263 std::shared_ptr<Portal> CuboidStackPortalShell::portal(Face face) {
0264 if (face == m_backFace) {
0265 return m_shells.back()->portal(face);
0266 } else {
0267 return m_shells.front()->portal(face);
0268 }
0269 }
0270
0271 void CuboidStackPortalShell::setPortal(std::shared_ptr<Portal> portal,
0272 Face face) {
0273 assert(portal != nullptr);
0274
0275 if (face == m_backFace) {
0276 m_shells.back()->setPortal(std::move(portal), face);
0277 } else if (face == m_frontFace) {
0278 m_shells.front()->setPortal(std::move(portal), face);
0279 } else {
0280 for (auto& shell : m_shells) {
0281 shell->setPortal(portal, face);
0282 }
0283 }
0284 }
0285
0286 bool CuboidStackPortalShell::isValid() const {
0287 return std::ranges::all_of(m_shells, [](const auto* shell) {
0288 assert(shell != nullptr);
0289 return shell->isValid();
0290 });
0291 }
0292
0293 const Transform3& CuboidStackPortalShell::localToGlobalTransform(
0294 const GeometryContext& gctx) const {
0295 return m_shells.front()->localToGlobalTransform(gctx);
0296 }
0297
0298 std::string CuboidStackPortalShell::label() const {
0299
0300
0301
0302
0303 std::stringstream ss;
0304 ss << "CuboidStackShell(dir=" << m_direction << ", " << m_shells.size()
0305 << " children)";
0306 return ss.str();
0307 }
0308
0309 std::ostream& operator<<(std::ostream& os, CuboidPortalShell::Face face) {
0310 switch (face) {
0311 using enum CuboidVolumeBounds::Face;
0312 case PositiveZFace:
0313 return os << "PositiveZFace";
0314 case NegativeZFace:
0315 return os << "NegativeZFace";
0316 case PositiveXFace:
0317 return os << "PositiveXFace";
0318 case NegativeXFace:
0319 return os << "NegativeXFace";
0320 case PositiveYFace:
0321 return os << "PositiveYFace";
0322 case NegativeYFace:
0323 return os << "NegativeYFace";
0324 default:
0325 return os << "Invalid face";
0326 }
0327 }
0328
0329 }