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