aegir
Phlex-based simulation framework for the SHiP experiment.
Loading...
Searching...
No Matches
scoring_planes.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 CERN for the benefit of the SHiP Collaboration
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
5// scoring_planes.hpp — Scoring planes in a Geant4 parallel world
6//
7// A scoring plane is a thin box or cylindrical shell that records every track
8// entering it — charged or neutral, neutrinos included, whether or not it
9// deposits energy — as a SimHit, and can optionally stop the tracks it
10// records. This is aegir's counterpart of FairShip's exitHadronAbsorber.
11//
12// The planes live in their own parallel world (G4VUserParallelWorld), overlaid
13// on whatever geometry the provider built (builtin, GDML, GeoModel). Geant4
14// tracks particles through both worlds at once, so a plane can be any size,
15// anywhere: it may cut through or extend beyond the detector's own volumes
16// without disturbing their navigation, and without taking on or changing any
17// material (the parallel world is not a layered-mass world). Planes may not
18// overlap one another. They work independently of the sd_mode used for the
19// geometry's own sensitive volumes.
20
21#pragma once
22
23#include <spdlog/spdlog.h>
24
25#include <G4Box.hh>
26#include <G4LogicalVolume.hh>
27#include <G4LogicalVolumeStore.hh>
28#include <G4Navigator.hh>
29#include <G4NistManager.hh>
30#include <G4PVPlacement.hh>
31#include <G4Point3D.hh>
32#include <G4SDManager.hh>
33#include <G4Step.hh>
34#include <G4StepPoint.hh>
35#include <G4String.hh>
36#include <G4ThreeVector.hh>
37#include <G4Track.hh>
38#include <G4Transform3D.hh>
39#include <G4TransportationManager.hh>
40#include <G4Tubs.hh>
41#include <G4VPhysicalVolume.hh>
42#include <G4VSensitiveDetector.hh>
43#include <G4VSolid.hh>
44#include <G4VUserParallelWorld.hh>
45#include <SHiP/QuantityView.hpp>
46#include <SHiP/SimHit.hpp>
47#include <SHiP/Units.hpp>
48#include <algorithm>
49#include <cmath>
50#include <cstddef>
51#include <cstdint>
52#include <limits>
53#include <numbers>
54#include <optional>
55#include <stdexcept>
56#include <string>
57#include <unordered_map>
58#include <unordered_set>
59#include <utility>
60#include <vector>
61
62#include "geant4_sim_core.hpp"
63#include "scoring_plane_id.hpp"
65
66namespace SHiP::g4 {
67
70inline constexpr char const* kScoringWorldName = "aegir_scoring_planes";
71
72enum class ScoringShape : std::uint8_t { box, tube };
73
77 std::string name;
78 std::int32_t id = 0;
80 ship::Length half_x = ship::Length::zero();
81 ship::Length half_y = ship::Length::zero();
82 ship::Length r_min = ship::Length::zero();
83 ship::Length r_max = ship::Length::zero();
84 ship::Length half_z = ship::Length::zero();
85 // Placement — exactly one of:
86 // * `position`: centre in the global frame;
87 // * `after_volume`: downstream of the detector-geometry logical volume
88 // with that exact name. The plane is centred transversely on the
89 // volume's global bounding box and its upstream face sits `gap` behind
90 // the box's downstream end — FairShip's "1 cm after the absorber".
91 std::optional<ship::Vec3<ship::Length>> position;
92 std::string after_volume;
93 ship::Length gap = ship::Length::zero();
95 ship::Energy ke_threshold = ship::Energy::zero();
98 bool kill = false;
99};
100
103 std::int32_t id = 0;
104 double ke_threshold = 0; // Geant4 internal units
105 bool kill = false;
106};
107
108using ScoringPlaneMap = std::unordered_map<G4LogicalVolume*, ScoringPlaneInfo>;
109
110namespace scoring_plane_detail {
111
113inline std::string shape_error(ScoringPlaneConfig const& p) {
114 auto const zero = ship::Length::zero();
115 if (!(p.half_z > zero)) {
116 return "half_z must be positive";
117 }
118 if (p.shape == ScoringShape::box) {
119 if (!(p.half_x > zero) || !(p.half_y > zero)) {
120 return "a box needs positive half_x and half_y";
121 }
122 if (p.r_min != zero || p.r_max != zero) {
123 return "r_min/r_max apply to tubes only";
124 }
125 return {};
126 }
127 if (!(p.r_min >= zero) || !(p.r_max > p.r_min)) {
128 return "a tube needs 0 <= r_min < r_max";
129 }
130 if (p.half_x != zero || p.half_y != zero) {
131 return "half_x/half_y apply to boxes only";
132 }
133 return {};
134}
135
137inline std::string placement_error(ScoringPlaneConfig const& p) {
138 auto const zero = ship::Length::zero();
139 if (p.position.has_value() == !p.after_volume.empty()) {
140 return "set exactly one of 'position' and 'after_volume'";
141 }
142 if (p.after_volume.empty() && p.gap != zero) {
143 return "'gap' applies to 'after_volume' placement only";
144 }
145 if (p.gap < zero) {
146 return "gap must not be negative";
147 }
148 return {};
149}
150
151} // namespace scoring_plane_detail
152
156 std::vector<ScoringPlaneConfig> const& planes) {
157 std::unordered_set<std::string> names;
158 std::unordered_set<std::int32_t> ids;
159 for (auto const& p : planes) {
160 if (p.name.empty()) {
161 throw std::invalid_argument("scoring plane without a name");
162 }
163 std::string error;
164 if (!names.insert(p.name).second) {
165 error = "name used by more than one plane";
166 } else if (!ids.insert(p.id).second) {
167 error = "id " + std::to_string(p.id) + " used by more than one plane";
168 } else if (p.ke_threshold < ship::Energy::zero()) {
169 error = "ke_threshold must not be negative";
170 } else if (error = scoring_plane_detail::shape_error(p); error.empty()) {
172 }
173 if (!error.empty()) {
174 throw std::invalid_argument("scoring plane '" + p.name + "': " + error);
175 }
176 }
177}
178
179namespace scoring_plane_detail {
180
183struct Placement {
184 G4VPhysicalVolume* pv = nullptr;
185 G4VPhysicalVolume* parent = nullptr; // nullptr for the world
186 G4Transform3D to_global = G4Transform3D::Identity;
187};
188
192template <typename Visit>
193void walk(G4VPhysicalVolume* pv, G4VPhysicalVolume* parent,
194 G4Transform3D const& to_global, Visit& visit) {
195 visit(Placement{.pv = pv, .parent = parent, .to_global = to_global});
196 auto* lv = pv->GetLogicalVolume();
197 for (std::size_t i = 0; i < lv->GetNoDaughters(); ++i) {
198 auto* daughter = lv->GetDaughter(static_cast<G4int>(i));
199 if (daughter->IsReplicated()) {
200 continue;
201 }
202 walk(daughter, pv,
203 to_global * G4Transform3D(daughter->GetObjectRotationValue(),
204 daughter->GetObjectTranslation()),
205 visit);
206 }
207}
208
212inline Placement find_unique(G4VPhysicalVolume* world,
213 std::string const& lv_name,
214 std::string const& plane, char const* role) {
215 std::vector<Placement> found;
216 auto visit = [&](Placement const& p) {
217 if (p.pv->GetLogicalVolume()->GetName() == lv_name) {
218 found.push_back(p);
219 }
220 };
221 walk(world, nullptr, G4Transform3D{}, visit);
222 if (found.size() != 1) {
223 throw std::runtime_error(
224 "scoring plane '" + plane + "': " + role + " '" + lv_name +
225 "' must be placed exactly once in the geometry, found " +
226 std::to_string(found.size()) +
227 " placement(s) (volumes inside replicas are not searched)");
228 }
229 return found.front();
230}
231
233inline std::pair<G4ThreeVector, G4ThreeVector> global_bounds(
234 Placement const& p) {
235 G4ThreeVector lo;
236 G4ThreeVector hi;
237 p.pv->GetLogicalVolume()->GetSolid()->BoundingLimits(lo, hi);
238 constexpr double inf = std::numeric_limits<double>::infinity();
239 G4ThreeVector gmin{inf, inf, inf};
240 G4ThreeVector gmax{-inf, -inf, -inf};
241 for (int corner = 0; corner < 8; ++corner) {
242 G4Point3D const local{(corner & 1) != 0 ? hi.x() : lo.x(),
243 (corner & 2) != 0 ? hi.y() : lo.y(),
244 (corner & 4) != 0 ? hi.z() : lo.z()};
245 G4Point3D const global = p.to_global * local;
246 for (int axis = 0; axis < 3; ++axis) {
247 gmin[axis] = std::min(gmin[axis], global[axis]);
248 gmax[axis] = std::max(gmax[axis], global[axis]);
249 }
250 }
251 return {gmin, gmax};
252}
253
254} // namespace scoring_plane_detail
255
256namespace scoring_plane_detail {
257
260inline G4ThreeVector plane_centre(ScoringPlaneConfig const& cfg,
261 G4VPhysicalVolume* mass_world) {
262 namespace cb = aegir::clhep;
263 if (cfg.position) {
264 auto const& pos = *cfg.position;
265 return {cb::g4(pos[0]), cb::g4(pos[1]), cb::g4(pos[2])};
266 }
267 auto const ref =
268 find_unique(mass_world, cfg.after_volume, cfg.name, "after_volume");
269 if (ref.parent == nullptr) {
270 throw std::runtime_error("scoring plane '" + cfg.name +
271 "': after_volume is the world volume");
272 }
273 auto const [lo, hi] = global_bounds(ref);
274 return {0.5 * (lo.x() + hi.x()), 0.5 * (lo.y() + hi.y()),
275 hi.z() + cb::g4(cfg.gap) + cb::g4(cfg.half_z)};
276}
277
279struct Extent {
281 G4ThreeVector centre;
282 double half_x = 0, half_y = 0, r_min = 0, r_max = 0, half_z = 0;
283};
284
285inline Extent extent(ScoringPlaneConfig const& cfg, G4ThreeVector centre) {
286 namespace cb = aegir::clhep;
287 return {.shape = cfg.shape,
288 .centre = centre,
289 .half_x = cb::g4(cfg.half_x),
290 .half_y = cb::g4(cfg.half_y),
291 .r_min = cb::g4(cfg.r_min),
292 .r_max = cb::g4(cfg.r_max),
293 .half_z = cb::g4(cfg.half_z)};
294}
295
298inline std::pair<double, double> distance_range_to_box(double x, double y,
299 Extent const& box) {
300 double const dx = std::abs(x - box.centre.x());
301 double const dy = std::abs(y - box.centre.y());
302 return {
303 std::hypot(std::max(dx - box.half_x, 0.), std::max(dy - box.half_y, 0.)),
304 std::hypot(dx + box.half_x, dy + box.half_y)};
305}
306
309inline std::pair<double, double> distance_range_to_tube(double x, double y,
310 Extent const& tube) {
311 double const d = std::hypot(x - tube.centre.x(), y - tube.centre.y());
312 double const closest = d < tube.r_min ? tube.r_min - d
313 : d <= tube.r_max ? 0.
314 : d - tube.r_max;
315 return {closest, d + tube.r_max};
316}
317
323inline bool overlap(Extent const& a, Extent const& b) {
324 if (std::abs(a.centre.z() - b.centre.z()) >= a.half_z + b.half_z) {
325 return false;
326 }
328 return std::abs(a.centre.x() - b.centre.x()) < a.half_x + b.half_x &&
329 std::abs(a.centre.y() - b.centre.y()) < a.half_y + b.half_y;
330 }
331 auto const& tube = b.shape == ScoringShape::tube ? b : a;
332 auto const& other = b.shape == ScoringShape::tube ? a : b;
333 auto const [closest, farthest] =
334 other.shape == ScoringShape::box
335 ? distance_range_to_box(tube.centre.x(), tube.centre.y(), other)
336 : distance_range_to_tube(tube.centre.x(), tube.centre.y(), other);
337 return closest < tube.r_max && farthest > tube.r_min;
338}
339
342inline void check_inside_world(std::string const& name, Extent const& e,
343 G4VPhysicalVolume* world) {
344 G4ThreeVector lo;
345 G4ThreeVector hi;
346 world->GetLogicalVolume()->GetSolid()->BoundingLimits(lo, hi);
347 lo += world->GetObjectTranslation();
348 hi += world->GetObjectTranslation();
349 double const hx = e.shape == ScoringShape::box ? e.half_x : e.r_max;
350 double const hy = e.shape == ScoringShape::box ? e.half_y : e.r_max;
351 G4ThreeVector const half{hx, hy, e.half_z};
352 for (int axis = 0; axis < 3; ++axis) {
353 if (e.centre[axis] - half[axis] < lo[axis] ||
354 e.centre[axis] + half[axis] > hi[axis]) {
355 throw std::runtime_error("scoring plane '" + name +
356 "' extends beyond the world volume");
357 }
358 }
359}
360
361inline G4VSolid* make_solid(ScoringPlaneConfig const& cfg) {
362 namespace cb = aegir::clhep;
363 if (cfg.shape == ScoringShape::box) {
364 return new G4Box(cfg.name, cb::g4(cfg.half_x), cb::g4(cfg.half_y),
365 cb::g4(cfg.half_z));
366 }
367 return new G4Tubs(cfg.name, cb::g4(cfg.r_min), cb::g4(cfg.r_max),
368 cb::g4(cfg.half_z), 0.,
369 2 * std::numbers::pi); // Geant4 angles are in rad
370}
371
372} // namespace scoring_plane_detail
373
382 G4VPhysicalVolume* mass_world, G4VPhysicalVolume* scoring_world,
383 std::vector<ScoringPlaneConfig> const& planes) {
384 namespace cb = aegir::clhep;
385 namespace detail = scoring_plane_detail;
387
388 ScoringPlaneMap placed;
389 if (planes.empty()) {
390 return placed;
391 }
392
393 // Resolve every plane's extent and check them all first: CheckOverlaps()
394 // only samples surface points, so a small corner overlap between thin
395 // planes could get through it, while these axis-aligned shapes allow an
396 // exact pairwise test.
397 std::vector<detail::Extent> extents;
398 extents.reserve(planes.size());
399 for (auto const& cfg : planes) {
400 if (G4LogicalVolumeStore::GetInstance()->GetVolume(cfg.name, false) !=
401 nullptr) {
402 throw std::runtime_error("scoring plane '" + cfg.name +
403 "': the geometry already has a volume with "
404 "this name");
405 }
406 extents.push_back(
407 detail::extent(cfg, detail::plane_centre(cfg, mass_world)));
408 detail::check_inside_world(cfg.name, extents.back(), scoring_world);
409 }
410 for (std::size_t i = 0; i < planes.size(); ++i) {
411 for (std::size_t j = 0; j < i; ++j) {
412 if (detail::overlap(extents[i], extents[j])) {
413 throw std::runtime_error("scoring planes '" + planes[j].name +
414 "' and '" + planes[i].name + "' overlap");
415 }
416 }
417 }
418
419 auto* vacuum = G4NistManager::Instance()->FindOrBuildMaterial("G4_Galactic");
420 G4Transform3D const world_to_global(scoring_world->GetObjectRotationValue(),
421 scoring_world->GetObjectTranslation());
422 for (std::size_t i = 0; i < planes.size(); ++i) {
423 auto const& cfg = planes[i];
424 G4ThreeVector const centre = extents[i].centre;
425 auto* lv = new G4LogicalVolume(detail::make_solid(cfg), vacuum, cfg.name);
426 // Axis-aligned in the global frame. The parallel world sits where the
427 // detector world does (normally at the origin).
428 auto* pv = new G4PVPlacement(
429 world_to_global.inverse() * G4Translate3D(centre), lv, cfg.name,
430 scoring_world->GetLogicalVolume(), false, 0);
431 // Backstop for worlds that are not boxes; the checks above are exact for
432 // everything else.
433 if (pv->CheckOverlaps(/*res=*/10000)) {
434 throw std::runtime_error("scoring plane '" + cfg.name +
435 "' extends beyond the world volume");
436 }
437
438 placed.emplace(lv,
439 ScoringPlaneInfo{.id = cfg.id,
440 .ke_threshold = cb::g4(cfg.ke_threshold),
441 .kill = cfg.kill});
442 auto const mm = [](double v) {
443 return cb::length(v).numerical_value_in(ship::units::mm);
444 };
445 spdlog::info(
446 "geant4: scoring plane '{}' (id {}) placed, centre ({:.1f}, {:.1f}, "
447 "{:.1f}) mm{}",
448 cfg.name, cfg.id, mm(centre.x()), mm(centre.y()), mm(centre.z()),
449 cfg.kill ? ", kills every track stepping into it" : "");
450 }
451 return placed;
452}
453
461class ScoringPlaneSD : public G4VSensitiveDetector {
462 public:
463 ScoringPlaneSD(G4String const& name, ScoringPlaneMap planes)
464 : G4VSensitiveDetector(name), planes_{std::move(planes)} {}
465
466 G4bool ProcessHits(G4Step* step, G4TouchableHistory*) override {
467 auto const* pre = step->GetPreStepPoint();
468 auto const it =
469 planes_.find(pre->GetTouchable()->GetVolume()->GetLogicalVolume());
470 if (it == planes_.end()) {
471 return false;
472 }
473 auto const& plane = it->second;
474 auto* track = step->GetTrack();
475
476 // A killing plane stops every track that steps into it, as FairShip's
477 // exitHadronAbsorber does: whether or not the track is recorded (it may
478 // be below ke_threshold), and including secondaries born inside the
479 // plane, which never cross its surface. Geant4 applies the kill at the
480 // end of this step.
481 if (plane.kill) {
482 track->SetTrackStatus(fStopAndKill);
483 }
484
485 // Record only tracks entering through the surface (FairShip's
486 // IsTrackEntering()): not later steps inside the plane, and not tracks
487 // created inside it.
488 if (pre->GetStepStatus() != fGeomBoundary ||
489 pre->GetKineticEnergy() < plane.ke_threshold) {
490 return false;
491 }
492
493 auto hit = make_base_hit(step, kScoringPlaneDetectorId);
494 hit.geometryNodeId = plane.id;
495 hit.energyDeposit = 0;
496 // This SD runs inside G4ParallelWorldProcess::PostStepDoIt, before the
497 // stepping manager adds the current step to the track length, so this is
498 // the length from the track's origin to the entry point. (A mass-world SD
499 // such as CrossingSD runs after that and sees the end of the step.)
500 ship::view::setPathLength(hit,
501 aegir::clhep::length(track->GetTrackLength()));
502 tl_hits.push_back(hit);
503 return true;
504 }
505
506 private:
507 ScoringPlaneMap planes_;
508};
509
515class ScoringParallelWorld : public G4VUserParallelWorld {
516 public:
517 explicit ScoringParallelWorld(std::vector<ScoringPlaneConfig> planes)
518 : G4VUserParallelWorld(kScoringWorldName), configs_{std::move(planes)} {}
519
520 void Construct() override {
521 auto* mass_world = G4TransportationManager::GetTransportationManager()
522 ->GetNavigatorForTracking()
523 ->GetWorldVolume();
524 auto* scoring_world = GetWorld();
525 world_lv_ = scoring_world->GetLogicalVolume();
526 planes_ = place_scoring_planes(mass_world, scoring_world, configs_);
527 }
528
529 void ConstructSD() override {
530 if (planes_.empty()) {
531 return;
532 }
533 auto* sd = new ScoringPlaneSD("ScoringPlaneSD", planes_);
534 G4SDManager::GetSDMpointer()->AddNewDetector(sd);
535 for (auto const& [lv, _] : planes_) {
536 lv->SetSensitiveDetector(sd);
537 }
538 }
539
543 [[nodiscard]] bool contains(G4LogicalVolume* lv) const {
544 return lv == world_lv_ || planes_.contains(lv);
545 }
546
547 private:
548 std::vector<ScoringPlaneConfig> configs_;
549 // Written by Construct() on the geometry thread during master
550 // initialisation, read by every thread afterwards.
551 ScoringPlaneMap planes_;
552 G4LogicalVolume* world_lv_ = nullptr;
553};
554
555} // namespace SHiP::g4
The parallel world holding the scoring planes.
Definition scoring_planes.hpp:515
bool contains(G4LogicalVolume *lv) const
Whether lv belongs to this world (a plane or the world volume), so that detector-geometry patterns (s...
Definition scoring_planes.hpp:543
ScoringParallelWorld(std::vector< ScoringPlaneConfig > planes)
Definition scoring_planes.hpp:517
void ConstructSD() override
Definition scoring_planes.hpp:529
void Construct() override
Definition scoring_planes.hpp:520
Records one SimHit per track entering a scoring plane through its surface, regardless of particle typ...
Definition scoring_planes.hpp:461
G4bool ProcessHits(G4Step *step, G4TouchableHistory *) override
Definition scoring_planes.hpp:466
ScoringPlaneSD(G4String const &name, ScoringPlaneMap planes)
Definition scoring_planes.hpp:463
Extent extent(ScoringPlaneConfig const &cfg, G4ThreeVector centre)
Definition scoring_planes.hpp:285
void check_inside_world(std::string const &name, Extent const &e, G4VPhysicalVolume *world)
Throw if e (plane name) is not inside the world's bounding box.
Definition scoring_planes.hpp:342
std::pair< double, double > distance_range_to_box(double x, double y, Extent const &box)
Closest and farthest distance from the point (x, y) to the points of a box's cross-section,...
Definition scoring_planes.hpp:298
void walk(G4VPhysicalVolume *pv, G4VPhysicalVolume *parent, G4Transform3D const &to_global, Visit &visit)
Depth-first visit of every placement below (and including) pv.
Definition scoring_planes.hpp:193
std::string placement_error(ScoringPlaneConfig const &p)
What is wrong with a plane's placement settings, or an empty string.
Definition scoring_planes.hpp:137
Placement find_unique(G4VPhysicalVolume *world, std::string const &lv_name, std::string const &plane, char const *role)
The single placement of the logical volume named lv_name.
Definition scoring_planes.hpp:212
bool overlap(Extent const &a, Extent const &b)
Whether two axis-aligned planes share any volume; planes that only touch do not overlap.
Definition scoring_planes.hpp:323
G4ThreeVector plane_centre(ScoringPlaneConfig const &cfg, G4VPhysicalVolume *mass_world)
A plane's centre in the global frame: its position, or behind its after_volume in the detector geomet...
Definition scoring_planes.hpp:260
std::pair< G4ThreeVector, G4ThreeVector > global_bounds(Placement const &p)
Global axis-aligned bounding box of a placed volume.
Definition scoring_planes.hpp:233
G4VSolid * make_solid(ScoringPlaneConfig const &cfg)
Definition scoring_planes.hpp:361
std::string shape_error(ScoringPlaneConfig const &p)
What is wrong with a plane's shape and size, or an empty string.
Definition scoring_planes.hpp:113
std::pair< double, double > distance_range_to_tube(double x, double y, Extent const &tube)
Closest and farthest distance from the point (x, y) to the points of a tube's cross-section,...
Definition scoring_planes.hpp:309
Definition detector_construction.hpp:33
constexpr std::int32_t kScoringPlaneDetectorId
SimHit::detectorId of every scoring-plane hit.
Definition scoring_plane_id.hpp:21
thread_local std::vector< SimHit > tl_hits
Definition geant4_sim_core.hpp:29
ScoringPlaneMap place_scoring_planes(G4VPhysicalVolume *mass_world, G4VPhysicalVolume *scoring_world, std::vector< ScoringPlaneConfig > const &planes)
Build the configured planes and place them in scoring_world, the parallel world; after_volume referen...
Definition scoring_planes.hpp:381
ScoringShape
Definition scoring_planes.hpp:72
std::unordered_map< G4LogicalVolume *, ScoringPlaneInfo > ScoringPlaneMap
Definition scoring_planes.hpp:108
SimHit make_base_hit(G4Step const *step, int detector_id)
Definition geant4_sim_core.hpp:36
void validate_scoring_planes(std::vector< ScoringPlaneConfig > const &planes)
Check a set of plane configurations for consistency on their own (no geometry needed); throws std::in...
Definition scoring_planes.hpp:155
constexpr char const * kScoringWorldName
Name of the parallel world holding the scoring planes, and of the G4ParallelWorldPhysics constructor ...
Definition scoring_planes.hpp:70
Definition clhep_bridge.hpp:22
ship::Length length(G4double v)
Definition clhep_bridge.hpp:28
One scoring plane, as configured in the geant4 module's scoring_planes.
Definition scoring_planes.hpp:76
ship::Energy ke_threshold
Record only tracks entering with at least this kinetic energy.
Definition scoring_planes.hpp:95
ship::Length gap
Definition scoring_planes.hpp:93
ship::Length r_max
tube only
Definition scoring_planes.hpp:83
ship::Length half_y
box only
Definition scoring_planes.hpp:81
ship::Length half_x
box only
Definition scoring_planes.hpp:80
std::string after_volume
Definition scoring_planes.hpp:92
ship::Length half_z
both shapes
Definition scoring_planes.hpp:84
ScoringShape shape
Definition scoring_planes.hpp:79
ship::Length r_min
tube only
Definition scoring_planes.hpp:82
std::optional< ship::Vec3< ship::Length > > position
Definition scoring_planes.hpp:91
bool kill
Stop and kill every track that steps into the plane, recorded or not (FairShip's StopTrack()).
Definition scoring_planes.hpp:98
std::string name
Volume name; must not exist in the geometry yet.
Definition scoring_planes.hpp:77
What the sensitive detector needs to know about a placed plane.
Definition scoring_planes.hpp:102
double ke_threshold
Definition scoring_planes.hpp:104
std::int32_t id
Definition scoring_planes.hpp:103
bool kill
Definition scoring_planes.hpp:105
A placed plane's extent in the global frame (Geant4 internal units).
Definition scoring_planes.hpp:279
double half_z
Definition scoring_planes.hpp:282
double half_y
Definition scoring_planes.hpp:282
G4ThreeVector centre
Definition scoring_planes.hpp:281
double r_min
Definition scoring_planes.hpp:282
ScoringShape shape
Definition scoring_planes.hpp:280
double r_max
Definition scoring_planes.hpp:282
double half_x
Definition scoring_planes.hpp:282
A physical volume reached from the world, with the transform taking its local frame to the global one...
Definition scoring_planes.hpp:183
G4VPhysicalVolume * parent
Definition scoring_planes.hpp:185
G4VPhysicalVolume * pv
Definition scoring_planes.hpp:184
G4Transform3D to_global
Definition scoring_planes.hpp:186