File indexing completed on 2026-05-17 07:34:43
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Geometry/Blueprint.hpp"
0010
0011 #include "Acts/Geometry/CuboidPortalShell.hpp"
0012 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0013 #include "Acts/Geometry/CylinderPortalShell.hpp"
0014 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0015 #include "Acts/Geometry/Extent.hpp"
0016 #include "Acts/Geometry/GeometryIdentifier.hpp"
0017 #include "Acts/Geometry/PortalShell.hpp"
0018 #include "Acts/Geometry/VolumeBounds.hpp"
0019 #include "Acts/Geometry/detail/AlignablePortalVisitor.hpp"
0020 #include "Acts/Geometry/detail/BoundDeduplicator.hpp"
0021 #include "Acts/Navigation/INavigationPolicy.hpp"
0022 #include "Acts/Navigation/TryAllNavigationPolicy.hpp"
0023 #include "Acts/Utilities/GraphViz.hpp"
0024 #include "Acts/Utilities/Logger.hpp"
0025
0026 #include <algorithm>
0027
0028 namespace {
0029 const std::string s_rootName = "Root";
0030 }
0031
0032 namespace Acts::Experimental {
0033
0034
0035
0036 class BlueprintVisitor : public TrackingGeometryMutableVisitor {
0037 public:
0038 explicit BlueprintVisitor(
0039 const Logger &logger,
0040 std::array<const TrackingVolume *, GeometryIdentifier::getMaxVolume()>
0041 &volumesById)
0042 : TrackingGeometryMutableVisitor(true),
0043 m_volumesById(volumesById),
0044 m_logger(logger) {}
0045
0046 void visitVolume(TrackingVolume &volume) override {
0047 GeometryIdentifier::Value iportal = 0;
0048 GeometryIdentifier::Value isensitive = 0;
0049
0050 auto id = volume.geometryId();
0051
0052 if (id == GeometryIdentifier{}) {
0053 auto it = std::ranges::find(m_volumesById, nullptr);
0054 if (it == m_volumesById.end()) {
0055 ACTS_ERROR("No free volume IDs left, all " << m_volumesById.size()
0056 << " are used");
0057
0058 throw std::logic_error("No free volume IDs left");
0059 }
0060
0061 id = GeometryIdentifier().withVolume(
0062 std::distance(m_volumesById.begin(), it) + 1);
0063
0064 ACTS_VERBOSE("Assigning volume ID " << id << " for "
0065 << volume.volumeName());
0066 volume.assignGeometryId(id);
0067 *it = &volume;
0068 }
0069
0070 for (auto &portal : volume.portals()) {
0071 if (portal.surface().geometryId() != GeometryIdentifier{}) {
0072 continue;
0073 }
0074 iportal += 1;
0075 auto portalId = id.withBoundary(iportal);
0076 ACTS_VERBOSE("Assigning portal ID: " << portalId);
0077 portal.surface().assignGeometryId(portalId);
0078 }
0079 for (auto &surface : volume.surfaces()) {
0080 if (surface.geometryId() != GeometryIdentifier{}) {
0081 continue;
0082 }
0083 isensitive += 1;
0084 auto surfaceId = id.withSensitive(isensitive);
0085 ACTS_VERBOSE("Assigning surface ID: " << surfaceId);
0086 surface.assignGeometryId(surfaceId);
0087 }
0088 }
0089
0090 private:
0091 std::array<const TrackingVolume *, GeometryIdentifier::getMaxVolume()>
0092 &m_volumesById;
0093 const Logger &m_logger;
0094 const Acts::Logger &logger() const { return m_logger; }
0095 };
0096
0097 Blueprint::Blueprint(const Config &config) : m_cfg(config) {}
0098
0099 const std::string &Blueprint::name() const {
0100 return s_rootName;
0101 }
0102
0103 Volume &Blueprint::build(const BlueprintOptions & ,
0104 const GeometryContext & ,
0105 const Logger & ) {
0106 throw std::logic_error("Root node cannot be built");
0107 }
0108
0109 PortalShellBase &Blueprint::connect(const BlueprintOptions & ,
0110 const GeometryContext & ,
0111 const Logger & ) {
0112 throw std::logic_error("Root node cannot be connected");
0113 }
0114
0115 void Blueprint::finalize(const BlueprintOptions & ,
0116 const GeometryContext & ,
0117 TrackingVolume & ,
0118 const Logger & ) {
0119 throw std::logic_error("Root node cannot be finalized");
0120 }
0121
0122 void Blueprint::addToGraphviz(std::ostream &os) const {
0123 GraphViz::Node node{
0124 .id = name(), .label = "World", .shape = GraphViz::Shape::House};
0125
0126 os << node;
0127 BlueprintNode::addToGraphviz(os);
0128 }
0129
0130 std::unique_ptr<TrackingGeometry> Blueprint::construct(
0131 const BlueprintOptions &options, const GeometryContext &gctx,
0132 const Logger &logger) {
0133 using enum AxisDirection;
0134
0135 ACTS_INFO(prefix() << "Building tracking geometry from blueprint tree");
0136
0137 options.validate();
0138
0139 if (m_cfg.envelope == ExtentEnvelope::Zero()) {
0140 ACTS_WARNING(prefix() << "Root node is configured with zero envelope. This "
0141 "might lead to navigation issues");
0142 }
0143
0144 if (children().size() != 1) {
0145 ACTS_ERROR(prefix() << "Root node must have exactly one child");
0146 throw std::logic_error("Root node must have exactly one child");
0147 }
0148
0149 auto &child = children().at(0);
0150
0151 ACTS_DEBUG(prefix() << "Executing building on tree");
0152 Volume &topVolume = child.build(options, gctx, logger);
0153 const auto &bounds = topVolume.volumeBounds();
0154
0155 std::stringstream ss;
0156 bounds.toStream(ss);
0157 ACTS_DEBUG(prefix() << "have top volume: " << ss.str() << "\n"
0158 << topVolume.localToGlobalTransform(gctx).matrix());
0159
0160 std::unique_ptr<TrackingVolume> world;
0161 static const std::string worldName = "World";
0162
0163 if (const auto *cyl = dynamic_cast<const CylinderVolumeBounds *>(&bounds);
0164 cyl != nullptr) {
0165 ACTS_VERBOSE(prefix() << "Expanding cylinder bounds");
0166 using enum CylinderVolumeBounds::BoundValues;
0167
0168
0169 auto newBounds = std::make_shared<CylinderVolumeBounds>(*cyl);
0170
0171 const auto &zEnv = m_cfg.envelope[AxisZ];
0172 if (zEnv[0] != zEnv[1]) {
0173 ACTS_ERROR(
0174 prefix() << "Root node cylinder envelope for z must be symmetric");
0175 throw std::logic_error(
0176 "Root node cylinder envelope for z must be "
0177 "symmetric");
0178 }
0179
0180 const auto &rEnv = m_cfg.envelope[AxisR];
0181
0182 newBounds->set({
0183 {eHalfLengthZ, newBounds->get(eHalfLengthZ) + zEnv[0]},
0184 {eMinR, std::max(0.0, newBounds->get(eMinR) - rEnv[0])},
0185 {eMaxR, newBounds->get(eMaxR) + rEnv[1]},
0186 });
0187
0188 ACTS_DEBUG(prefix() << "Applied envelope to cylinder: Z=" << zEnv[0]
0189 << ", Rmin=" << rEnv[0] << ", Rmax=" << rEnv[1]);
0190
0191 world =
0192 std::make_unique<TrackingVolume>(topVolume.localToGlobalTransform(gctx),
0193 std::move(newBounds), worldName);
0194
0195
0196 SingleCylinderPortalShell worldShell{gctx, *world};
0197 worldShell.applyToVolume();
0198
0199 } else if (const auto *box =
0200 dynamic_cast<const CuboidVolumeBounds *>(&bounds);
0201 box != nullptr) {
0202 ACTS_VERBOSE(prefix() << "Expanding cuboid bounds");
0203
0204 auto newBounds = std::make_shared<CuboidVolumeBounds>(*box);
0205
0206
0207 double halfX = newBounds->get(CuboidVolumeBounds::eHalfLengthX);
0208 double halfY = newBounds->get(CuboidVolumeBounds::eHalfLengthY);
0209 double halfZ = newBounds->get(CuboidVolumeBounds::eHalfLengthZ);
0210
0211
0212 const auto &xEnv = m_cfg.envelope[AxisX];
0213 const auto &yEnv = m_cfg.envelope[AxisY];
0214 const auto &zEnv = m_cfg.envelope[AxisZ];
0215
0216
0217 if (xEnv[0] != xEnv[1]) {
0218 ACTS_ERROR(
0219 prefix() << "Root node cuboid envelope for X must be symmetric");
0220 throw std::logic_error(
0221 "Root node cuboid envelope for X must be symmetric");
0222 }
0223
0224 if (yEnv[0] != yEnv[1]) {
0225 ACTS_ERROR(
0226 prefix() << "Root node cuboid envelope for Y must be symmetric");
0227 throw std::logic_error(
0228 "Root node cuboid envelope for Y must be symmetric");
0229 }
0230
0231 if (zEnv[0] != zEnv[1]) {
0232 ACTS_ERROR(
0233 prefix() << "Root node cuboid envelope for Z must be symmetric");
0234 throw std::logic_error(
0235 "Root node cuboid envelope for Z must be symmetric");
0236 }
0237
0238 newBounds->set({
0239 {CuboidVolumeBounds::eHalfLengthX, halfX + xEnv[0]},
0240 {CuboidVolumeBounds::eHalfLengthY, halfY + yEnv[0]},
0241 {CuboidVolumeBounds::eHalfLengthZ, halfZ + zEnv[0]},
0242 });
0243
0244 ACTS_DEBUG(prefix() << "Applied envelope to cuboid: X=" << xEnv[0]
0245 << ", Y=" << yEnv[0] << ", Z=" << zEnv[0]);
0246
0247 world =
0248 std::make_unique<TrackingVolume>(topVolume.localToGlobalTransform(gctx),
0249 std::move(newBounds), worldName);
0250
0251
0252 SingleCuboidPortalShell worldShell{gctx, *world};
0253 worldShell.applyToVolume();
0254
0255 } else {
0256 throw std::logic_error{"Unsupported volume bounds type"};
0257 }
0258
0259 ACTS_DEBUG(prefix() << "New root volume bounds are: "
0260 << world->volumeBounds());
0261
0262 world->setNavigationPolicy(std::make_unique<Acts::TryAllNavigationPolicy>(
0263 gctx, *world, logger, Acts::TryAllNavigationPolicy::Config{}));
0264
0265 auto &shell = child.connect(options, gctx, logger);
0266
0267
0268
0269
0270 shell.fill(*world);
0271
0272 if (m_cfg.boundDeduplication) {
0273 ACTS_DEBUG("Deduplicate equivalent bounds");
0274 detail::BoundDeduplicator deduplicator{};
0275 world->apply(deduplicator);
0276 }
0277 child.finalize(options, gctx, *world, logger);
0278
0279 std::set<std::string, std::less<>> volumeNames;
0280 std::array<const TrackingVolume *, GeometryIdentifier::getMaxVolume()>
0281 volumesById{};
0282 volumesById.fill(nullptr);
0283
0284
0285
0286 world->apply([&, this](TrackingVolume &volume) {
0287 if (volumeNames.contains(volume.volumeName())) {
0288 ACTS_ERROR(prefix() << "Duplicate volume name: " << volume.volumeName());
0289 throw std::logic_error("Duplicate volume name");
0290 }
0291 volumeNames.insert(volume.volumeName());
0292
0293 if (volume.geometryId() != GeometryIdentifier{}) {
0294
0295
0296 if (volumesById.at(volume.geometryId().volume() - 1) == nullptr) {
0297 volumesById.at(volume.geometryId().volume() - 1) = &volume;
0298 }
0299 }
0300
0301
0302 volume.clearBoundarySurfaces();
0303 });
0304
0305 std::size_t unusedVolumeIds = std::ranges::count(volumesById, nullptr);
0306 ACTS_DEBUG(prefix() << "Number of unused volume IDs: " << unusedVolumeIds);
0307
0308 ACTS_DEBUG(prefix() << "Assigning volume IDs for remaining volumes");
0309
0310 BlueprintVisitor visitor{logger, volumesById};
0311 world->apply(visitor);
0312
0313 Acts::detail::AlignablePortalVisitor alignPortals{gctx, logger};
0314 world->apply(alignPortals);
0315
0316 return std::make_unique<TrackingGeometry>(
0317 std::move(world), nullptr, GeometryIdentifierHook{}, logger, false);
0318 }
0319
0320 }