File indexing completed on 2026-07-17 07:51:38
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/data/test_case.hpp>
0010 #include <boost/test/detail/log_level.hpp>
0011 #include <boost/test/tools/context.hpp>
0012 #include <boost/test/tools/old/interface.hpp>
0013 #include <boost/test/unit_test.hpp>
0014 #include <boost/test/unit_test_log.hpp>
0015 #include <boost/test/unit_test_parameters.hpp>
0016 #include <boost/test/unit_test_suite.hpp>
0017
0018 #include "Acts/Definitions/Algebra.hpp"
0019 #include "Acts/Definitions/Tolerance.hpp"
0020 #include "Acts/Definitions/Units.hpp"
0021 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0022 #include "Acts/Geometry/CuboidVolumeStack.hpp"
0023 #include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
0024 #include "Acts/Geometry/VolumeResizeStrategy.hpp"
0025 #include "Acts/Utilities/AxisDefinitions.hpp"
0026 #include "Acts/Utilities/Logger.hpp"
0027 #include "Acts/Utilities/ThrowAssert.hpp"
0028 #include "Acts/Utilities/Zip.hpp"
0029 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0030
0031 #include <cassert>
0032 #include <initializer_list>
0033 #include <stdexcept>
0034
0035 using namespace Acts;
0036 using namespace Acts::UnitLiterals;
0037
0038 namespace ActsTests {
0039
0040 auto logger = getDefaultLogger("UnitTests", Logging::VERBOSE);
0041
0042 const auto gctx = GeometryContext::dangerouslyDefaultConstruct();
0043
0044 struct Fixture {
0045 Logging::Level m_level;
0046 Fixture() {
0047 m_level = Logging::getFailureThreshold();
0048 Logging::setFailureThreshold(Logging::FATAL);
0049 }
0050
0051 ~Fixture() { Logging::setFailureThreshold(m_level); }
0052 };
0053
0054 BOOST_FIXTURE_TEST_SUITE(GeometrySuite, Fixture)
0055
0056 static const std::vector<VolumeAttachmentStrategy> strategies = {
0057 VolumeAttachmentStrategy::Gap,
0058 VolumeAttachmentStrategy::First,
0059 VolumeAttachmentStrategy::Second,
0060 VolumeAttachmentStrategy::Midpoint,
0061 };
0062
0063 static const std::vector<VolumeResizeStrategy> resizeStrategies = {
0064 VolumeResizeStrategy::Expand,
0065 VolumeResizeStrategy::Gap,
0066 };
0067
0068 BOOST_AUTO_TEST_SUITE(CuboidVolumeStackTest)
0069
0070 BOOST_DATA_TEST_CASE(BaselineLocal,
0071 (boost::unit_test::data::xrange(-135, 180, 45) *
0072 boost::unit_test::data::xrange(0, 2, 1) *
0073 boost::unit_test::data::make(0.8, 1.0, 1.2) *
0074 boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0075 Vector3{20_mm, 0_mm, 0_mm},
0076 Vector3{0_mm, 20_mm, 0_mm},
0077 Vector3{20_mm, 20_mm, 0_mm},
0078 Vector3{0_mm, 0_mm, 20_mm}) *
0079 boost::unit_test::data::make(strategies) *
0080 boost::unit_test::data::make(AxisDirection::AxisX,
0081 AxisDirection::AxisY,
0082 AxisDirection::AxisZ)),
0083 angle, rotate, shift, offset, strategy, dir) {
0084 double halfDir = 400_mm;
0085
0086 auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0087
0088 auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0089 auto dirOrth1Idx = CuboidVolumeStack::axisToIndex(dirOrth1);
0090
0091 auto boundDir = CuboidVolumeBounds::boundsFromAxisDirection(dir);
0092 auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
0093 auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth2);
0094
0095 auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0096 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0097 {boundDir, halfDir},
0098 {boundDirOrth1, 100_mm},
0099 {boundDirOrth2, 400_mm}});
0100
0101 auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0102 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0103 {boundDir, halfDir},
0104 {boundDirOrth1, 200_mm},
0105 {boundDirOrth2, 600_mm}});
0106
0107 auto bounds3 = std::make_shared<CuboidVolumeBounds>(
0108 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0109 {boundDir, halfDir},
0110 {boundDirOrth1, 300_mm},
0111 {boundDirOrth2, 500_mm}});
0112
0113 Transform3 base = AngleAxis3(angle * 1_degree, Vector3::Unit(dirOrth1Idx)) *
0114 Translation3(offset);
0115
0116 Translation3 translation1(Vector3::Unit(dirIdx) * (-2 * halfDir * shift));
0117 Transform3 transform1 = base * translation1;
0118 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0119
0120 Transform3 transform2 = base;
0121 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0122
0123 Translation3 translation3(Vector3::Unit(dirIdx) * (2 * halfDir * shift));
0124 Transform3 transform3 = base * translation3;
0125 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0126
0127 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
0128
0129 std::rotate(volumes.begin(), volumes.begin() + rotate, volumes.end());
0130
0131 auto origVolumes = volumes;
0132
0133 std::vector<CuboidVolumeBounds> originalBounds;
0134 std::transform(volumes.begin(), volumes.end(),
0135 std::back_inserter(originalBounds), [](const auto& vol) {
0136 const auto* res =
0137 dynamic_cast<CuboidVolumeBounds*>(&vol->volumeBounds());
0138 throw_assert(res != nullptr, "");
0139 return *res;
0140 });
0141
0142 if (shift < 1.0) {
0143 BOOST_CHECK_THROW(CuboidVolumeStack(gctx, volumes, dir, strategy,
0144 VolumeResizeStrategy::Gap, *logger),
0145 std::invalid_argument);
0146 return;
0147 }
0148 CuboidVolumeStack stack(gctx, volumes, dir, strategy,
0149 VolumeResizeStrategy::Gap, *logger);
0150
0151 auto stackBounds =
0152 dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0153 BOOST_REQUIRE(stackBounds != nullptr);
0154
0155 BOOST_CHECK_CLOSE(stackBounds->get(boundDirOrth1), 300_mm, 1e-6);
0156 BOOST_CHECK_CLOSE(stackBounds->get(boundDirOrth2), 600_mm, 1e-6);
0157 BOOST_CHECK_CLOSE(stackBounds->get(boundDir), halfDir + 2 * halfDir * shift,
0158 1e-6);
0159 CHECK_CLOSE_OR_SMALL(stack.localToGlobalTransform(gctx).matrix(),
0160 base.matrix(), 1e-10, 1e-12);
0161
0162
0163
0164 for (const auto& volume : volumes) {
0165 const auto* cuboidBounds =
0166 dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0167 BOOST_REQUIRE(cuboidBounds != nullptr);
0168 BOOST_CHECK_CLOSE(cuboidBounds->get(boundDirOrth1), 300_mm, 1e-6);
0169 BOOST_CHECK_CLOSE(cuboidBounds->get(boundDirOrth2), 600_mm, 1e-6);
0170 }
0171
0172
0173 for (std::size_t i = 0; i < volumes.size() - 1; ++i) {
0174 const auto& a = volumes.at(i);
0175 const auto& b = volumes.at(i + 1);
0176
0177 BOOST_CHECK_LT((base.inverse() * a->center(gctx))[dirIdx],
0178 (base.inverse() * b->center(gctx))[dirIdx]);
0179 }
0180
0181 if (shift <= 1.0) {
0182
0183 BOOST_CHECK_EQUAL(volumes.size(), 3);
0184
0185
0186 BOOST_CHECK_EQUAL(vol1->localToGlobalTransform(gctx).matrix(),
0187 transform1.matrix());
0188 BOOST_CHECK_EQUAL(vol2->localToGlobalTransform(gctx).matrix(),
0189 transform2.matrix());
0190 BOOST_CHECK_EQUAL(vol3->localToGlobalTransform(gctx).matrix(),
0191 transform3.matrix());
0192
0193 for (const auto& [volume, bounds] : zip(origVolumes, originalBounds)) {
0194 const auto* newBounds =
0195 dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0196 BOOST_CHECK_CLOSE(newBounds->get(boundDir), bounds.get(boundDir), 1e-6);
0197 }
0198 } else {
0199 if (strategy == VolumeAttachmentStrategy::Gap) {
0200
0201 BOOST_CHECK_EQUAL(volumes.size(), 5);
0202 auto gap1 = volumes.at(1);
0203 auto gap2 = volumes.at(3);
0204
0205 BOOST_TEST_MESSAGE(
0206 "Gap 1: " << gap1->localToGlobalTransform(gctx).matrix());
0207 BOOST_TEST_MESSAGE(
0208 "Gap 2: " << gap2->localToGlobalTransform(gctx).matrix());
0209
0210 const auto* gapBounds1 =
0211 dynamic_cast<const CuboidVolumeBounds*>(&gap1->volumeBounds());
0212 const auto* gapBounds2 =
0213 dynamic_cast<const CuboidVolumeBounds*>(&gap2->volumeBounds());
0214
0215 double gapHlDir = (shift - 1.0) * halfDir;
0216
0217 BOOST_CHECK(std::abs(gapBounds1->get(boundDir) - gapHlDir) < 1e-12);
0218 BOOST_CHECK(std::abs(gapBounds2->get(boundDir) - gapHlDir) < 1e-12);
0219
0220 double gap1Dir = (-2 * halfDir * shift) + halfDir + gapHlDir;
0221 double gap2Dir = (2 * halfDir * shift) - halfDir - gapHlDir;
0222
0223 Translation3 gap1Translation(Vector3::Unit(dirIdx) * gap1Dir);
0224 Translation3 gap2Translation(Vector3::Unit(dirIdx) * gap2Dir);
0225
0226 Transform3 gap1Transform = base * gap1Translation;
0227 Transform3 gap2Transform = base * gap2Translation;
0228
0229 CHECK_CLOSE_OR_SMALL(gap1->localToGlobalTransform(gctx).matrix(),
0230 gap1Transform.matrix(), 1e-10, 1e-12);
0231 CHECK_CLOSE_OR_SMALL(gap2->localToGlobalTransform(gctx).matrix(),
0232 gap2Transform.matrix(), 1e-10, 1e-12);
0233
0234
0235 for (const auto& [volume, bounds] : zip(origVolumes, originalBounds)) {
0236 const auto* newBounds =
0237 dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0238 BOOST_CHECK_CLOSE(newBounds->get(boundDir), bounds.get(boundDir), 1e-6);
0239 }
0240
0241
0242 BOOST_CHECK_EQUAL(vol1->localToGlobalTransform(gctx).matrix(),
0243 transform1.matrix());
0244 BOOST_CHECK_EQUAL(vol2->localToGlobalTransform(gctx).matrix(),
0245 transform2.matrix());
0246 BOOST_CHECK_EQUAL(vol3->localToGlobalTransform(gctx).matrix(),
0247 transform3.matrix());
0248 } else if (strategy == VolumeAttachmentStrategy::First) {
0249
0250 BOOST_CHECK_EQUAL(volumes.size(), 3);
0251
0252 double wGap = (shift - 1.0) * halfDir * 2;
0253
0254
0255 auto newBounds1 =
0256 dynamic_cast<const CuboidVolumeBounds*>(&vol1->volumeBounds());
0257 BOOST_CHECK_CLOSE(newBounds1->get(boundDir), halfDir + wGap / 2.0, 1e-6);
0258 double pDir1 = -2 * halfDir * shift + wGap / 2.0;
0259 Translation3 expectedTranslation1(Vector3::Unit(dirIdx) * pDir1);
0260 Transform3 expectedTransform1 = base * expectedTranslation1;
0261 CHECK_CLOSE_OR_SMALL(vol1->localToGlobalTransform(gctx).matrix(),
0262 expectedTransform1.matrix(), 1e-10, 1e-12);
0263
0264
0265 auto newBounds2 =
0266 dynamic_cast<const CuboidVolumeBounds*>(&vol2->volumeBounds());
0267 BOOST_CHECK_CLOSE(newBounds2->get(boundDir), halfDir + wGap / 2.0, 1e-6);
0268 double pDir2 = wGap / 2.0;
0269 Translation3 expectedTranslation2(Vector3::Unit(dirIdx) * pDir2);
0270 Transform3 expectedTransform2 = base * expectedTranslation2;
0271 CHECK_CLOSE_OR_SMALL(vol2->localToGlobalTransform(gctx).matrix(),
0272 expectedTransform2.matrix(), 1e-10, 1e-12);
0273
0274
0275 auto newBounds3 =
0276 dynamic_cast<const CuboidVolumeBounds*>(&vol3->volumeBounds());
0277 BOOST_CHECK_CLOSE(newBounds3->get(boundDir), halfDir, 1e-6);
0278 double pDir3 = 2 * halfDir * shift;
0279 Translation3 expectedTranslation3(Vector3::Unit(dirIdx) * pDir3);
0280 Transform3 expectedTransform3 = base * expectedTranslation3;
0281 CHECK_CLOSE_OR_SMALL(vol3->localToGlobalTransform(gctx).matrix(),
0282 expectedTransform3.matrix(), 1e-10, 1e-12);
0283 } else if (strategy == VolumeAttachmentStrategy::Second) {
0284
0285 BOOST_CHECK_EQUAL(volumes.size(), 3);
0286
0287 double wGap = (shift - 1.0) * halfDir * 2;
0288
0289
0290 auto newBounds1 =
0291 dynamic_cast<const CuboidVolumeBounds*>(&vol1->volumeBounds());
0292 BOOST_CHECK_CLOSE(newBounds1->get(boundDir), halfDir, 1e-6);
0293 double pDir1 = -2 * halfDir * shift;
0294 Translation3 expectedTranslation1(Vector3::Unit(dirIdx) * pDir1);
0295 Transform3 expectedTransform1 = base * expectedTranslation1;
0296 CHECK_CLOSE_OR_SMALL(vol1->localToGlobalTransform(gctx).matrix(),
0297 expectedTransform1.matrix(), 1e-10, 1e-12);
0298
0299
0300 auto newBounds2 =
0301 dynamic_cast<const CuboidVolumeBounds*>(&vol2->volumeBounds());
0302 BOOST_CHECK_CLOSE(newBounds2->get(boundDir), halfDir + wGap / 2.0, 1e-6);
0303 double pDir2 = -wGap / 2.0;
0304 Translation3 expectedTranslation2(Vector3::Unit(dirIdx) * pDir2);
0305 Transform3 expectedTransform2 = base * expectedTranslation2;
0306 CHECK_CLOSE_OR_SMALL(vol2->localToGlobalTransform(gctx).matrix(),
0307 expectedTransform2.matrix(), 1e-10, 1e-12);
0308
0309
0310 auto newBounds3 =
0311 dynamic_cast<const CuboidVolumeBounds*>(&vol3->volumeBounds());
0312 BOOST_CHECK_CLOSE(newBounds3->get(boundDir), halfDir + wGap / 2.0, 1e-6);
0313 double pDir3 = 2 * halfDir * shift - wGap / 2.0;
0314 Translation3 expectedTranslation3(Vector3::Unit(dirIdx) * pDir3);
0315 Transform3 expectedTransform3 = base * expectedTranslation3;
0316 CHECK_CLOSE_OR_SMALL(vol3->localToGlobalTransform(gctx).matrix(),
0317 expectedTransform3.matrix(), 1e-10, 1e-12);
0318 } else if (strategy == VolumeAttachmentStrategy::Midpoint) {
0319
0320 BOOST_CHECK_EQUAL(volumes.size(), 3);
0321
0322 double wGap = (shift - 1.0) * halfDir * 2;
0323
0324
0325 auto newBounds1 =
0326 dynamic_cast<const CuboidVolumeBounds*>(&vol1->volumeBounds());
0327 BOOST_CHECK_CLOSE(newBounds1->get(boundDir), halfDir + wGap / 4.0, 1e-6);
0328 double pDir1 = -2 * halfDir * shift + wGap / 4.0;
0329 Translation3 expectedTranslation1(Vector3::Unit(dirIdx) * pDir1);
0330 Transform3 expectedTransform1 = base * expectedTranslation1;
0331 CHECK_CLOSE_OR_SMALL(vol1->localToGlobalTransform(gctx).matrix(),
0332 expectedTransform1.matrix(), 1e-10, 1e-12);
0333
0334
0335 auto newBounds2 =
0336 dynamic_cast<const CuboidVolumeBounds*>(&vol2->volumeBounds());
0337 BOOST_CHECK_CLOSE(newBounds2->get(boundDir), halfDir + wGap / 2.0, 1e-6);
0338 CHECK_CLOSE_OR_SMALL(vol2->localToGlobalTransform(gctx).matrix(),
0339 base.matrix(), 1e-10, 1e-12);
0340
0341
0342 auto newBounds3 =
0343 dynamic_cast<const CuboidVolumeBounds*>(&vol3->volumeBounds());
0344 BOOST_CHECK_CLOSE(newBounds3->get(boundDir), halfDir + wGap / 4.0, 1e-6);
0345 double pDir3 = 2 * halfDir * shift - wGap / 4.0;
0346 Translation3 expectedTranslation3(Vector3::Unit(dirIdx) * pDir3);
0347 Transform3 expectedTransform3 = base * expectedTranslation3;
0348 CHECK_CLOSE_OR_SMALL(vol3->localToGlobalTransform(gctx).matrix(),
0349 expectedTransform3.matrix(), 1e-10, 1e-12);
0350 }
0351 }
0352 }
0353
0354 BOOST_DATA_TEST_CASE(Asymmetric,
0355 boost::unit_test::data::make(AxisDirection::AxisX,
0356 AxisDirection::AxisY,
0357 AxisDirection::AxisZ),
0358 dir) {
0359 double halfDir1 = 200_mm;
0360 double pDir1 = -1100_mm;
0361 double halfDir2 = 600_mm;
0362 double pDir2 = -200_mm;
0363 double halfDir3 = 400_mm;
0364 double pDir3 = 850_mm;
0365
0366 auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0367
0368 auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0369
0370 auto boundDir = CuboidVolumeBounds::boundsFromAxisDirection(dir);
0371 auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
0372 auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth2);
0373
0374 auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0375 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0376 {boundDir, halfDir1},
0377 {boundDirOrth1, 100_mm},
0378 {boundDirOrth2, 400_mm}});
0379
0380 auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0381 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0382 {boundDir, halfDir2},
0383 {boundDirOrth1, 200_mm},
0384 {boundDirOrth2, 600_mm}});
0385
0386 auto bounds3 = std::make_shared<CuboidVolumeBounds>(
0387 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0388 {boundDir, halfDir3},
0389 {boundDirOrth1, 300_mm},
0390 {boundDirOrth2, 500_mm}});
0391
0392 Translation3 translation1(Vector3::Unit(dirIdx) * pDir1);
0393 Transform3 transform1(translation1);
0394 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0395
0396 Translation3 translation2(Vector3::Unit(dirIdx) * pDir2);
0397 Transform3 transform2(translation2);
0398 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0399
0400 Translation3 translation3(Vector3::Unit(dirIdx) * pDir3);
0401 Transform3 transform3(translation3);
0402 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0403
0404 std::vector<Volume*> volumes = {vol2.get(), vol1.get(), vol3.get()};
0405
0406 CuboidVolumeStack stack(gctx, volumes, dir, VolumeAttachmentStrategy::Gap,
0407 VolumeResizeStrategy::Gap, *logger);
0408 BOOST_CHECK_EQUAL(volumes.size(), 5);
0409
0410 auto stackBounds =
0411 dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0412 BOOST_REQUIRE(stackBounds != nullptr);
0413
0414 BOOST_CHECK_CLOSE(stackBounds->get(boundDirOrth1), 300_mm, 1e-6);
0415 BOOST_CHECK_CLOSE(stackBounds->get(boundDirOrth2), 600_mm, 1e-6);
0416 BOOST_CHECK_CLOSE(stackBounds->get(boundDir),
0417 (std::abs(pDir1 - halfDir1) + pDir3 + halfDir3) / 2.0,
0418 1e-6);
0419
0420 double midDir = (pDir1 - halfDir1 + pDir3 + halfDir3) / 2.0;
0421 Translation3 expectedTranslation(Vector3::Unit(dirIdx) * midDir);
0422 Transform3 expectedTransform = Transform3::Identity() * expectedTranslation;
0423 CHECK_CLOSE_OR_SMALL(stack.localToGlobalTransform(gctx).matrix(),
0424 expectedTransform.matrix(), 1e-10, 1e-12);
0425 }
0426
0427 BOOST_DATA_TEST_CASE(UpdateStack,
0428 (boost::unit_test::data::xrange(-135, 180, 45) *
0429 boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0430 Vector3{20_mm, 0_mm, 0_mm},
0431 Vector3{0_mm, 20_mm, 0_mm},
0432 Vector3{20_mm, 20_mm, 0_mm},
0433 Vector3{0_mm, 0_mm, 20_mm}) *
0434 boost::unit_test::data::make(-100_mm, 0_mm, 100_mm) *
0435 boost::unit_test::data::make(resizeStrategies) *
0436 boost::unit_test::data::make(AxisDirection::AxisX,
0437 AxisDirection::AxisY,
0438 AxisDirection::AxisZ)),
0439 angle, offset, zshift, strategy, dir) {
0440 double halfDir = 400_mm;
0441
0442 auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0443
0444 auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0445 auto dirOrth1Idx = CuboidVolumeStack::axisToIndex(dirOrth1);
0446
0447 auto boundDir = CuboidVolumeBounds::boundsFromAxisDirection(dir);
0448 auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
0449 auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth2);
0450
0451 auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0452 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0453 {boundDir, halfDir},
0454 {boundDirOrth1, 100_mm},
0455 {boundDirOrth2, 600_mm}});
0456
0457 auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0458 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0459 {boundDir, halfDir},
0460 {boundDirOrth1, 100_mm},
0461 {boundDirOrth2, 600_mm}});
0462
0463 auto bounds3 = std::make_shared<CuboidVolumeBounds>(
0464 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0465 {boundDir, halfDir},
0466 {boundDirOrth1, 100_mm},
0467 {boundDirOrth2, 600_mm}});
0468
0469 Vector3 shift = Vector3::Unit(dirIdx) * zshift;
0470 Transform3 base = AngleAxis3(angle * 1_degree, Vector3::Unit(dirOrth1Idx)) *
0471 Translation3(offset + shift);
0472
0473 Translation3 translation1(Vector3::Unit(dirIdx) * -2 * halfDir);
0474 Transform3 transform1 = base * translation1;
0475 auto vol1 = std::make_shared<Volume>(transform1, bounds1);
0476
0477 Transform3 transform2 = base;
0478 auto vol2 = std::make_shared<Volume>(transform2, bounds2);
0479
0480 Translation3 translation3(Vector3::Unit(dirIdx) * 2 * halfDir);
0481 Transform3 transform3 = base * translation3;
0482 auto vol3 = std::make_shared<Volume>(transform3, bounds3);
0483
0484 std::vector<Volume*> volumes = {vol1.get(), vol2.get(), vol3.get()};
0485 std::vector<Volume*> originalVolumes = volumes;
0486
0487 std::vector<Transform3> originalTransforms = {transform1, transform2,
0488 transform3};
0489
0490 CuboidVolumeStack stack(gctx, volumes, dir,
0491 VolumeAttachmentStrategy::Gap,
0492
0493 strategy, *logger);
0494
0495 const auto* originalBounds =
0496 dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0497
0498 auto assertOriginalBounds = [&]() {
0499 const auto* bounds =
0500 dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0501 BOOST_REQUIRE(bounds != nullptr);
0502 BOOST_CHECK_EQUAL(bounds, originalBounds);
0503 BOOST_CHECK_CLOSE(bounds->get(boundDirOrth1), 100_mm, 1e-6);
0504 BOOST_CHECK_CLOSE(bounds->get(boundDirOrth2), 600_mm, 1e-6);
0505 BOOST_CHECK_CLOSE(bounds->get(boundDir), 3 * halfDir, 1e-6);
0506 };
0507
0508 assertOriginalBounds();
0509
0510 {
0511
0512 auto bounds = std::make_shared<CuboidVolumeBounds>(
0513 dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0514 stack.update(gctx, bounds, std::nullopt, *logger);
0515 assertOriginalBounds();
0516 }
0517
0518 {
0519
0520 auto bounds = std::make_shared<CuboidVolumeBounds>(
0521 dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0522 bounds->set(boundDirOrth1, 20_mm);
0523 BOOST_CHECK_THROW(stack.update(gctx, bounds, std::nullopt, *logger),
0524 std::invalid_argument);
0525 assertOriginalBounds();
0526 }
0527
0528 {
0529
0530 auto bounds = std::make_shared<CuboidVolumeBounds>(
0531 dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0532 bounds->set(boundDirOrth2, 200_mm);
0533 BOOST_CHECK_THROW(stack.update(gctx, bounds, std::nullopt, *logger),
0534 std::invalid_argument);
0535 assertOriginalBounds();
0536 }
0537
0538 {
0539
0540 auto bounds = std::make_shared<CuboidVolumeBounds>(
0541 dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0542 bounds->set(boundDir, 2 * halfDir);
0543 BOOST_CHECK_THROW(stack.update(gctx, bounds, std::nullopt, *logger),
0544 std::invalid_argument);
0545 assertOriginalBounds();
0546 }
0547
0548 {
0549
0550 auto bounds = std::make_shared<CuboidVolumeBounds>(
0551 dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0552 bounds->set(boundDirOrth1, 700_mm);
0553 stack.update(gctx, bounds, std::nullopt, *logger);
0554 const auto* updatedBounds =
0555 dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0556 BOOST_REQUIRE(updatedBounds != nullptr);
0557 BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth1), 700_mm, 1e-6);
0558 BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth2), 600_mm, 1e-6);
0559 BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 3 * halfDir, 1e-6);
0560
0561
0562 BOOST_CHECK_EQUAL(volumes.size(), 3);
0563
0564
0565 for (const auto& [volume, origTransform] :
0566 zip(volumes, originalTransforms)) {
0567 const auto* newBounds =
0568 dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0569 BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth1), 700_mm, 1e-6);
0570 BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth2), 600_mm, 1e-6);
0571 BOOST_CHECK_CLOSE(newBounds->get(boundDir), halfDir, 1e-6);
0572
0573
0574 BOOST_CHECK_EQUAL(volume->localToGlobalTransform(gctx).matrix(),
0575 origTransform.matrix());
0576 }
0577 }
0578 {
0579
0580 auto bounds = std::make_shared<CuboidVolumeBounds>(
0581 dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0582 bounds->set(boundDirOrth2, 700_mm);
0583 stack.update(gctx, bounds, std::nullopt, *logger);
0584 const auto* updatedBounds =
0585 dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0586 BOOST_REQUIRE(updatedBounds != nullptr);
0587 BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth1), 700_mm, 1e-6);
0588 BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth2), 700_mm, 1e-6);
0589 BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 3 * halfDir, 1e-6);
0590
0591
0592 BOOST_CHECK_EQUAL(volumes.size(), 3);
0593
0594
0595 for (const auto& [volume, origTransform] :
0596 zip(volumes, originalTransforms)) {
0597 const auto* newBounds =
0598 dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0599 BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth1), 700_mm, 1e-6);
0600 BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth2), 700_mm, 1e-6);
0601 BOOST_CHECK_CLOSE(newBounds->get(boundDir), halfDir, 1e-6);
0602
0603
0604 BOOST_CHECK_EQUAL(volume->localToGlobalTransform(gctx).matrix(),
0605 origTransform.matrix());
0606 }
0607 }
0608
0609 {
0610
0611 auto bounds = std::make_shared<CuboidVolumeBounds>(
0612 dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0613 bounds->set(boundDir, 4 * halfDir);
0614 stack.update(gctx, bounds, std::nullopt, *logger);
0615 const auto* updatedBounds =
0616 dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0617 BOOST_REQUIRE(updatedBounds != nullptr);
0618 BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 4 * halfDir, 1e-6);
0619 BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth1), 700_mm, 1e-6);
0620 BOOST_CHECK_CLOSE(updatedBounds->get(boundDirOrth2), 700_mm, 1e-6);
0621
0622 if (strategy == VolumeResizeStrategy::Expand) {
0623
0624 BOOST_CHECK_EQUAL(volumes.size(), 3);
0625
0626
0627 auto newBounds1 =
0628 dynamic_cast<const CuboidVolumeBounds*>(&vol1->volumeBounds());
0629 BOOST_CHECK_CLOSE(newBounds1->get(boundDir), halfDir + halfDir / 2.0,
0630 1e-6);
0631 auto expectedTranslation1 =
0632 Translation3(Vector3::Unit(dirIdx) * (-2 * halfDir - halfDir / 2.0));
0633 Transform3 expectedTransform1 = base * expectedTranslation1;
0634 CHECK_CLOSE_OR_SMALL(vol1->localToGlobalTransform(gctx).matrix(),
0635 expectedTransform1.matrix(), 1e-10, 1e-12);
0636
0637
0638 auto newBounds2 =
0639 dynamic_cast<const CuboidVolumeBounds*>(&vol2->volumeBounds());
0640 BOOST_CHECK_CLOSE(newBounds2->get(boundDir), halfDir, 1e-6);
0641 CHECK_CLOSE_OR_SMALL(vol2->localToGlobalTransform(gctx).matrix(),
0642 transform2.matrix(), 1e-10, 1e-12);
0643
0644
0645 auto newBounds3 =
0646 dynamic_cast<const CuboidVolumeBounds*>(&vol3->volumeBounds());
0647 BOOST_CHECK_CLOSE(newBounds3->get(boundDir), halfDir + halfDir / 2.0,
0648 1e-6);
0649 auto expectedTranslation3 =
0650 Translation3(Vector3::Unit(dirIdx) * (2 * halfDir + halfDir / 2.0));
0651 Transform3 expectedTransform3 = base * expectedTranslation3;
0652 CHECK_CLOSE_OR_SMALL(vol3->localToGlobalTransform(gctx).matrix(),
0653 expectedTransform3.matrix(), 1e-10, 1e-12);
0654 } else if (strategy == VolumeResizeStrategy::Gap) {
0655
0656 BOOST_CHECK_EQUAL(volumes.size(), 5);
0657
0658 for (const auto& [volume, origTransform] :
0659 zip(originalVolumes, originalTransforms)) {
0660 const auto* newBounds =
0661 dynamic_cast<const CuboidVolumeBounds*>(&volume->volumeBounds());
0662 BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth1), 700_mm, 1e-6);
0663 BOOST_CHECK_CLOSE(newBounds->get(boundDirOrth2), 700_mm, 1e-6);
0664 BOOST_CHECK_CLOSE(newBounds->get(boundDir), halfDir, 1e-6);
0665
0666 CHECK_CLOSE_OR_SMALL(volume->localToGlobalTransform(gctx).matrix(),
0667 origTransform.matrix(), 1e-10, 1e-12);
0668 }
0669
0670 auto gap1 = volumes.front();
0671 auto gap2 = volumes.back();
0672
0673 const auto* gapBounds1 =
0674 dynamic_cast<const CuboidVolumeBounds*>(&gap1->volumeBounds());
0675 const auto* gapBounds2 =
0676 dynamic_cast<const CuboidVolumeBounds*>(&gap2->volumeBounds());
0677
0678 BOOST_CHECK_CLOSE(gapBounds1->get(boundDir), halfDir / 2.0, 1e-6);
0679 BOOST_CHECK_CLOSE(gapBounds2->get(boundDir), halfDir / 2.0, 1e-6);
0680 auto gap1Translation =
0681 Translation3(Vector3::Unit(dirIdx) * (-3 * halfDir - halfDir / 2.0));
0682 Transform3 gap1Transform = base * gap1Translation;
0683
0684 auto gap2Translation =
0685 Translation3(Vector3::Unit(dirIdx) * (3 * halfDir + halfDir / 2.0));
0686 Transform3 gap2Transform = base * gap2Translation;
0687 CHECK_CLOSE_OR_SMALL(gap1->localToGlobalTransform(gctx).matrix(),
0688 gap1Transform.matrix(), 1e-10, 1e-12);
0689 CHECK_CLOSE_OR_SMALL(gap2->localToGlobalTransform(gctx).matrix(),
0690 gap2Transform.matrix(), 1e-10, 1e-12);
0691 }
0692 }
0693 }
0694
0695 BOOST_DATA_TEST_CASE(
0696 UpdateStackOneSided,
0697 ((boost::unit_test::data::make(-1.0, 1.0) ^
0698 boost::unit_test::data::make(VolumeResizeStrategy::Gap,
0699 VolumeResizeStrategy::Expand)) *
0700 boost::unit_test::data::make(AxisDirection::AxisX, AxisDirection::AxisY,
0701 AxisDirection::AxisZ)),
0702 f, strategy, dir) {
0703 auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0704
0705 auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0706 auto dirOrth1Idx = CuboidVolumeStack::axisToIndex(dirOrth1);
0707 auto dirOrth2Idx = CuboidVolumeStack::axisToIndex(dirOrth2);
0708
0709 auto boundDir = CuboidVolumeBounds::boundsFromAxisDirection(dir);
0710 auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
0711 auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth2);
0712
0713 auto bounds1 = std::make_shared<CuboidVolumeBounds>(
0714 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0715 {boundDir, 400_mm},
0716 {boundDirOrth1, 100_mm},
0717 {boundDirOrth2, 300_mm}});
0718
0719 auto bounds2 = std::make_shared<CuboidVolumeBounds>(
0720 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0721 {boundDir, 400_mm},
0722 {boundDirOrth1, 100_mm},
0723 {boundDirOrth2, 300_mm}});
0724
0725 auto trf = Transform3::Identity();
0726
0727 auto translation1 = Translation3(Vector3::Unit(dirIdx) * -500_mm);
0728 auto trf1 = trf * translation1;
0729 auto vol1 = std::make_shared<Volume>(trf1, bounds1);
0730
0731 auto translation2 = Translation3(Vector3::Unit(dirIdx) * 500_mm);
0732 auto trf2 = trf * translation2;
0733 auto vol2 = std::make_shared<Volume>(trf2, bounds2);
0734
0735 std::vector<Volume*> volumes = {vol1.get(), vol2.get()};
0736
0737 CuboidVolumeStack stack{gctx, volumes, dir, VolumeAttachmentStrategy::Gap,
0738 strategy, *logger};
0739 const auto* originalBounds =
0740 dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0741
0742
0743 auto newBounds = std::make_shared<CuboidVolumeBounds>(
0744 dynamic_cast<const CuboidVolumeBounds&>(stack.volumeBounds()));
0745 newBounds->set(boundDir, 950_mm);
0746
0747 auto delta = Translation3(Vector3::Unit(dirIdx) * f * 50_mm);
0748 trf *= delta;
0749
0750
0751 auto checkUnchanged = [&]() {
0752 const auto* bounds =
0753 dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0754 BOOST_REQUIRE(bounds != nullptr);
0755 BOOST_CHECK_EQUAL(*bounds, *originalBounds);
0756 };
0757
0758
0759 BOOST_CHECK_THROW(
0760 auto errDelta = Translation3(Vector3::Unit(dirIdx) * f * 20_mm);
0761 stack.update(gctx, newBounds, trf * errDelta, *logger),
0762 std::invalid_argument);
0763 checkUnchanged();
0764
0765
0766 BOOST_CHECK_THROW(
0767 auto errDelta = Translation3(Vector3::Unit(dirOrth1Idx) * 10_mm);
0768 stack.update(gctx, newBounds, trf * errDelta, *logger),
0769 std::invalid_argument);
0770 checkUnchanged();
0771
0772
0773 BOOST_CHECK_THROW(
0774 auto errDelta = Translation3(Vector3::Unit(dirOrth2Idx) * 10_mm);
0775 stack.update(gctx, newBounds, trf * errDelta, *logger),
0776 std::invalid_argument);
0777 checkUnchanged();
0778
0779
0780 BOOST_CHECK_THROW(
0781 stack.update(gctx, newBounds,
0782 trf * AngleAxis3{10_degree, Vector3::Unit(dirOrth1Idx)},
0783 *logger),
0784 std::invalid_argument);
0785 checkUnchanged();
0786
0787 stack.update(gctx, newBounds, trf, *logger);
0788
0789 CHECK_CLOSE_OR_SMALL(stack.localToGlobalTransform(gctx).matrix(),
0790 trf.matrix(), 1e-10, 1e-12);
0791 const auto* bounds =
0792 dynamic_cast<const CuboidVolumeBounds*>(&stack.volumeBounds());
0793 BOOST_REQUIRE(bounds != nullptr);
0794 BOOST_CHECK_CLOSE(bounds->get(boundDir), 950_mm, 1e-6);
0795
0796
0797 for (const auto* vol : volumes) {
0798 const auto* volBounds =
0799 dynamic_cast<const CuboidVolumeBounds*>(&vol->volumeBounds());
0800 BOOST_REQUIRE(volBounds != nullptr);
0801 BOOST_CHECK_CLOSE(volBounds->get(boundDirOrth1), 100_mm, 1e-6);
0802 BOOST_CHECK_CLOSE(volBounds->get(boundDirOrth2), 300_mm, 1e-6);
0803 }
0804
0805 if (strategy == VolumeResizeStrategy::Expand) {
0806
0807 BOOST_CHECK_EQUAL(volumes.size(), 3);
0808 const Volume* vol = nullptr;
0809 if (f < 0.0) {
0810
0811 vol = volumes.front();
0812 } else {
0813
0814 vol = volumes.back();
0815 }
0816
0817 const auto* volBounds =
0818 dynamic_cast<const CuboidVolumeBounds*>(&vol->volumeBounds());
0819 BOOST_REQUIRE(volBounds != nullptr);
0820 BOOST_CHECK_CLOSE(volBounds->get(boundDir), 450_mm, 1e-6);
0821 BOOST_CHECK_EQUAL(vol->center(gctx)[dirIdx], f * 550_mm);
0822 } else if (strategy == VolumeResizeStrategy::Gap) {
0823
0824 BOOST_CHECK_EQUAL(volumes.size(), 4);
0825
0826 const Volume* gap = nullptr;
0827 if (f < 0.0) {
0828 gap = volumes.front();
0829 } else {
0830 gap = volumes.back();
0831 }
0832 const auto* gapBounds =
0833 dynamic_cast<const CuboidVolumeBounds*>(&gap->volumeBounds());
0834 BOOST_REQUIRE(gapBounds != nullptr);
0835
0836 BOOST_CHECK_CLOSE(gapBounds->get(boundDir), 50_mm, 1e-6);
0837 BOOST_CHECK_EQUAL(gap->center(gctx)[dirIdx], f * 950_mm);
0838 }
0839 }
0840
0841
0842
0843
0844
0845
0846
0847
0848
0849
0850
0851
0852
0853
0854
0855
0856
0857
0858
0859
0860
0861
0862
0863
0864
0865
0866
0867
0868
0869 BOOST_DATA_TEST_CASE(ResizeGapMultiple,
0870 boost::unit_test::data::make(AxisDirection::AxisX,
0871 AxisDirection::AxisY,
0872 AxisDirection::AxisZ),
0873 dir) {
0874 auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
0875
0876 auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
0877
0878 auto boundDir = CuboidVolumeBounds::boundsFromAxisDirection(dir);
0879 auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
0880 auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth2);
0881
0882 auto bounds = std::make_shared<CuboidVolumeBounds>(
0883 std::initializer_list<std::pair<CuboidVolumeBounds::BoundValues, double>>{
0884 {boundDir, 100}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0885 Transform3 trf = Transform3::Identity();
0886 Volume vol{trf, bounds};
0887
0888 BOOST_TEST_CONTEXT("Positive") {
0889 std::vector<Volume*> volumes = {&vol};
0890 CuboidVolumeStack stack(gctx, volumes, dir, VolumeAttachmentStrategy::Gap,
0891 VolumeResizeStrategy::Gap, *logger);
0892
0893 BOOST_CHECK_EQUAL(volumes.size(), 1);
0894 BOOST_CHECK(stack.gaps().empty());
0895
0896 auto newBounds1 = std::make_shared<CuboidVolumeBounds>(
0897 std::initializer_list<
0898 std::pair<CuboidVolumeBounds::BoundValues, double>>{
0899 {boundDir, 200}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0900 stack.update(gctx, newBounds1,
0901 trf * Translation3{Vector3::Unit(dirIdx) * 100}, *logger);
0902 BOOST_CHECK_EQUAL(volumes.size(), 2);
0903 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0904
0905 BOOST_CHECK_EQUAL(stack.gaps().front()->center(gctx)[dirIdx], 200.0);
0906 const auto* updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
0907 &stack.gaps().front()->volumeBounds());
0908 BOOST_REQUIRE_NE(updatedBounds, nullptr);
0909 BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 100.0, 1e-6);
0910
0911 auto newBounds2 = std::make_shared<CuboidVolumeBounds>(
0912 std::initializer_list<
0913 std::pair<CuboidVolumeBounds::BoundValues, double>>{
0914 {boundDir, 300}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0915 stack.update(gctx, newBounds2,
0916 trf * Translation3{Vector3::Unit(dirIdx) * 200}, *logger);
0917
0918 BOOST_CHECK_EQUAL(volumes.size(), 2);
0919
0920 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0921
0922 BOOST_CHECK_EQUAL(stack.gaps().front()->center(gctx)[dirIdx], 300.0);
0923 updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
0924 &stack.gaps().front()->volumeBounds());
0925 BOOST_REQUIRE_NE(updatedBounds, nullptr);
0926 BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 200.0, 1e-6);
0927 }
0928
0929 BOOST_TEST_CONTEXT("Negative") {
0930 std::vector<Volume*> volumes = {&vol};
0931 CuboidVolumeStack stack(gctx, volumes, dir, VolumeAttachmentStrategy::Gap,
0932 VolumeResizeStrategy::Gap, *logger);
0933
0934 BOOST_CHECK_EQUAL(volumes.size(), 1);
0935 BOOST_CHECK(stack.gaps().empty());
0936
0937 auto newBounds1 = std::make_shared<CuboidVolumeBounds>(
0938 std::initializer_list<
0939 std::pair<CuboidVolumeBounds::BoundValues, double>>{
0940 {boundDir, 200}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0941 stack.update(gctx, newBounds1,
0942 trf * Translation3{Vector3::Unit(dirIdx) * -100}, *logger);
0943 BOOST_CHECK_EQUAL(volumes.size(), 2);
0944 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0945
0946 BOOST_CHECK_EQUAL(stack.gaps().front()->center(gctx)[dirIdx], -200.0);
0947 const auto* updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
0948 &stack.gaps().front()->volumeBounds());
0949 BOOST_REQUIRE_NE(updatedBounds, nullptr);
0950 BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 100.0, 1e-6);
0951
0952 auto newBounds2 = std::make_shared<CuboidVolumeBounds>(
0953 std::initializer_list<
0954 std::pair<CuboidVolumeBounds::BoundValues, double>>{
0955 {boundDir, 300}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
0956 stack.update(gctx, newBounds2,
0957 trf * Translation3{Vector3::Unit(dirIdx) * -200}, *logger);
0958
0959 BOOST_CHECK_EQUAL(volumes.size(), 2);
0960
0961 BOOST_CHECK_EQUAL(stack.gaps().size(), 1);
0962
0963 BOOST_CHECK_EQUAL(stack.gaps().front()->center(gctx)[dirIdx], -300.0);
0964 updatedBounds = dynamic_cast<const CuboidVolumeBounds*>(
0965 &stack.gaps().front()->volumeBounds());
0966 BOOST_REQUIRE_NE(updatedBounds, nullptr);
0967 BOOST_CHECK_CLOSE(updatedBounds->get(boundDir), 200.0, 1e-6);
0968 }
0969 }
0970
0971 BOOST_DATA_TEST_CASE(InvalidDirection, boost::unit_test::data::make(strategies),
0972 strategy) {
0973 std::vector<Volume*> volumes;
0974 auto vol1 = std::make_shared<Volume>(
0975 Transform3::Identity(),
0976 std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
0977 volumes.push_back(vol1.get());
0978
0979
0980 BOOST_CHECK_THROW(
0981 CuboidVolumeStack(gctx, volumes, AxisDirection::AxisR, strategy),
0982 std::invalid_argument);
0983
0984 auto vol2 = std::make_shared<Volume>(
0985 Transform3::Identity(),
0986 std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
0987 volumes.push_back(vol2.get());
0988
0989 BOOST_CHECK_THROW(
0990 CuboidVolumeStack(gctx, volumes, AxisDirection::AxisR, strategy),
0991 std::invalid_argument);
0992 }
0993
0994 BOOST_DATA_TEST_CASE(InvalidInput,
0995 (boost::unit_test::data::make(strategies) *
0996 boost::unit_test::data::make(AxisDirection::AxisX,
0997 AxisDirection::AxisY,
0998 AxisDirection::AxisZ)),
0999 strategy, direction) {
1000 BOOST_TEST_CONTEXT("Empty Volume") {
1001 std::vector<Volume*> volumes;
1002 BOOST_CHECK_THROW(CuboidVolumeStack(gctx, volumes, direction, strategy),
1003 std::invalid_argument);
1004 }
1005
1006 BOOST_TEST_CONTEXT("Volumes rotated relative to each other") {
1007
1008
1009 for (const Vector3 axis : {Vector3::UnitX(), Vector3::UnitY()}) {
1010 std::vector<Volume*> volumes;
1011 auto vol1 = std::make_shared<Volume>(
1012 Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1013 std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1014 volumes.push_back(vol1.get());
1015
1016 BOOST_TEST_MESSAGE("Axis: " << axis);
1017 auto vol2 = std::make_shared<Volume>(
1018 Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm}} *
1019 AngleAxis3(1_degree, axis)},
1020 std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1021 volumes.push_back(vol2.get());
1022
1023 BOOST_CHECK_THROW(CuboidVolumeStack(gctx, volumes, direction, strategy,
1024 VolumeResizeStrategy::Gap, *logger),
1025 std::invalid_argument);
1026 }
1027 }
1028
1029 BOOST_TEST_CONTEXT(
1030 "Volumes shifted in the orthogonal plane relative to each other") {
1031 for (const Vector3& shift :
1032 {Vector3{5_mm, 0, 0}, Vector3{0, -5_mm, 0}, Vector3{2_mm, -2_mm, 0}}) {
1033 std::vector<Volume*> volumes;
1034 auto vol1 = std::make_shared<Volume>(
1035 Transform3{Translation3{Vector3{0_mm, 0_mm, -500_mm}}},
1036 std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1037 volumes.push_back(vol1.get());
1038
1039 auto vol2 = std::make_shared<Volume>(
1040 Transform3{Translation3{Vector3{0_mm, 0_mm, 500_mm} + shift}},
1041 std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1042 volumes.push_back(vol2.get());
1043
1044 BOOST_CHECK_THROW(CuboidVolumeStack(gctx, volumes, direction, strategy,
1045 VolumeResizeStrategy::Gap, *logger),
1046 std::invalid_argument);
1047 }
1048 }
1049 }
1050
1051 BOOST_DATA_TEST_CASE(JoinCuboidVolumeSingle,
1052 (boost::unit_test::data::make(AxisDirection::AxisX,
1053 AxisDirection::AxisY,
1054 AxisDirection::AxisZ) *
1055 boost::unit_test::data::make(strategies)),
1056 direction, strategy) {
1057 auto vol = std::make_shared<Volume>(
1058 Transform3::Identity() * Translation3{14_mm, 24_mm, 0_mm} *
1059 AngleAxis3(73_degree, Vector3::UnitX()),
1060 std::make_shared<CuboidVolumeBounds>(100_mm, 400_mm, 400_mm));
1061
1062 std::vector<Volume*> volumes{vol.get()};
1063
1064 CuboidVolumeStack stack(gctx, volumes, direction, strategy,
1065 VolumeResizeStrategy::Gap, *logger);
1066
1067
1068
1069 BOOST_CHECK_EQUAL(volumes.size(), 1);
1070 BOOST_CHECK_EQUAL(volumes.at(0), vol.get());
1071 BOOST_CHECK_EQUAL(vol->localToGlobalTransform(gctx).matrix(),
1072 stack.localToGlobalTransform(gctx).matrix());
1073 BOOST_CHECK_EQUAL(vol->volumeBounds(), stack.volumeBounds());
1074 }
1075
1076
1077
1078
1079
1080
1081
1082 BOOST_DATA_TEST_CASE(GapCreationTolerance,
1083 boost::unit_test::data::make(AxisDirection::AxisX,
1084 AxisDirection::AxisY,
1085 AxisDirection::AxisZ),
1086 dir) {
1087 auto boundDir = CuboidVolumeBounds::boundsFromAxisDirection(dir);
1088 auto [dirOrth1, dirOrth2] = CuboidVolumeStack::getOrthogonalAxes(dir);
1089 auto boundDirOrth1 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth1);
1090 auto boundDirOrth2 = CuboidVolumeBounds::boundsFromAxisDirection(dirOrth2);
1091 auto dirIdx = CuboidVolumeStack::axisToIndex(dir);
1092
1093 auto makeBounds = [&](double hl) {
1094 return std::make_shared<CuboidVolumeBounds>(
1095 std::initializer_list<
1096 std::pair<CuboidVolumeBounds::BoundValues, double>>{
1097 {boundDir, hl}, {boundDirOrth1, 70}, {boundDirOrth2, 100}});
1098 };
1099
1100
1101 const double eps = s_onSurfaceTolerance / 2.0;
1102
1103 for (const double sign : {+1.0, -1.0}) {
1104 BOOST_TEST_CONTEXT("sign=" << sign) {
1105 Volume vol{Transform3::Identity(), makeBounds(100)};
1106 std::vector<Volume*> volumes = {&vol};
1107 CuboidVolumeStack stack(gctx, volumes, dir, VolumeAttachmentStrategy::Gap,
1108 VolumeResizeStrategy::Gap, *logger);
1109
1110 BOOST_CHECK(stack.gaps().empty());
1111
1112
1113 stack.update(
1114 gctx, makeBounds(100 + eps / 2.0),
1115 Transform3{Translation3{Vector3::Unit(dirIdx) * sign * eps / 2.0}},
1116 *logger);
1117
1118
1119 BOOST_CHECK(stack.gaps().empty());
1120 BOOST_CHECK_EQUAL(volumes.size(), 1);
1121 }
1122 }
1123 }
1124
1125 BOOST_AUTO_TEST_SUITE_END()
1126 BOOST_AUTO_TEST_SUITE_END()
1127
1128 }