Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-27 07:24:21

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 // Project include(s)
0010 #include "detray/geometry/surface.hpp"
0011 
0012 #include "detray/definitions/indexing.hpp"
0013 #include "detray/utils/geometry_utils.hpp"  //< cos incidence angle
0014 
0015 // Detray test include(s)
0016 #include "detray/test/common/build_toy_detector.hpp"
0017 #include "detray/test/common/build_wire_chamber.hpp"
0018 #include "detray/test/framework/types.hpp"
0019 
0020 // Vecmem include(s)
0021 #include <vecmem/memory/host_memory_resource.hpp>
0022 
0023 // GTest include(s)
0024 #include <gtest/gtest.h>
0025 
0026 namespace {
0027 
0028 /// Define mask types
0029 enum class mask_id : unsigned int {
0030   e_unmasked = 0u,
0031 };
0032 
0033 std::ostream& operator<<(std::ostream& os, mask_id /*mid*/) {
0034   os << "e_unmasked";
0035   return os;
0036 }
0037 
0038 /// Define material types
0039 enum class material_id : unsigned int {
0040   e_material_slab = 0u,
0041 };
0042 
0043 std::ostream& operator<<(std::ostream& os, material_id /*mid*/) {
0044   os << "e_material_slab";
0045   return os;
0046 }
0047 
0048 constexpr detray::test::scalar tol{5e-5f};
0049 
0050 detray::dvector3D<detray::test::algebra> test_dir{0.f, 0.f, 1.f};
0051 
0052 }  // anonymous namespace
0053 
0054 // This tests the construction of a surface descriptor object
0055 GTEST_TEST(detray_geometry, surface_descriptor) {
0056   using namespace detray;
0057 
0058   using mask_link_t = dtyped_index<mask_id, dindex>;
0059   using material_link_t = dtyped_index<material_id, dindex>;
0060 
0061   mask_link_t mask_id{mask_id::e_unmasked, 0u};
0062   material_link_t material_id{material_id::e_material_slab, 0u};
0063 
0064   surface_descriptor<mask_link_t, material_link_t> desc(
0065       1u, mask_id, material_id, 2u, surface_id::e_sensitive);
0066 
0067   static_assert(sizeof(decltype(desc)) == 16);
0068 
0069   // Test access
0070   ASSERT_EQ(desc.transform(), 1u);
0071   ASSERT_EQ(desc.volume(), 2u);
0072   ASSERT_EQ(desc.id(), surface_id::e_sensitive);
0073   ASSERT_FALSE(desc.is_portal());
0074   ASSERT_FALSE(desc.is_passive());
0075   ASSERT_TRUE(desc.is_sensitive());
0076   ASSERT_EQ(desc.mask(), mask_id);
0077   ASSERT_EQ(desc.material(), material_id);
0078 
0079   // Test setters
0080   desc.set_volume(5u);
0081   desc.set_id(surface_id::e_portal);
0082   desc.set_index(6u);
0083   desc.update_transform(7u);
0084   desc.update_mask(7u);
0085   desc.update_material(7u);
0086 
0087   ASSERT_EQ(desc.transform(), 8u);
0088   ASSERT_EQ(desc.volume(), 5u);
0089   ASSERT_EQ(desc.id(), surface_id::e_portal);
0090   ASSERT_EQ(desc.index(), 6u);
0091   ASSERT_TRUE(desc.is_portal());
0092   ASSERT_FALSE(desc.is_passive());
0093   ASSERT_FALSE(desc.is_sensitive());
0094   ASSERT_EQ(desc.mask().id(), mask_id::e_unmasked);
0095   ASSERT_EQ(desc.mask().index(), 7u);
0096   ASSERT_EQ(desc.material().id(), material_id::e_material_slab);
0097   ASSERT_EQ(desc.material().index(), 7u);
0098 }
0099 
0100 /// This tests the functionality of a tracking surface interface for the toy
0101 /// detector
0102 GTEST_TEST(detray_geometry, surface_toy_detector) {
0103   using namespace detray;
0104 
0105   using detector_t = detector<test::toy_metadata>;
0106 
0107   using test_algebra = detector_t::algebra_type;
0108   using scalar = geometry::surface<detector_t>::scalar_type;
0109   using point2 = geometry::surface<detector_t>::point2_type;
0110   using point3 = geometry::surface<detector_t>::point3_type;
0111   using vector3 = geometry::surface<detector_t>::vector3_type;
0112 
0113   vecmem::host_memory_resource host_mr;
0114   toy_det_config<test::scalar> toy_cfg{};
0115   toy_cfg.use_material_maps(true).do_check(true);
0116   const auto [toy_det, names] =
0117       build_toy_detector<test_algebra>(host_mr, toy_cfg);
0118 
0119   auto ctx = typename detector_t::geometry_context{};
0120 
0121   //
0122   // Disc
0123   //
0124   const auto disc_descr = toy_det.surfaces()[1u];
0125   const auto disc = geometry::surface{toy_det, disc_descr};
0126 
0127   // IDs
0128   ASSERT_EQ(disc.identifier(), disc_descr.identifier());
0129   ASSERT_EQ(disc.volume(), 0u);
0130   ASSERT_EQ(disc.index(), 1u);
0131   ASSERT_EQ(disc.id(), surface_id::e_portal);
0132   ASSERT_EQ(disc.shape_id(), detector_t::masks::id::e_ring2D);
0133   ASSERT_EQ(disc.shape_name(), "ring2D");
0134   ASSERT_FALSE(disc.is_sensitive());
0135   ASSERT_FALSE(disc.is_passive());
0136   ASSERT_TRUE(disc.is_portal());
0137 
0138   // Transformation matrix
0139   const auto disc_translation =
0140       disc.transform(ctx).translation();  // beampipe portal
0141   ASSERT_NEAR(disc_translation[0], 0.f, tol);
0142   ASSERT_NEAR(disc_translation[1], 0.f, tol);
0143   ASSERT_NEAR(disc_translation[2], -827.5f, tol);
0144   auto center = disc.center(ctx);
0145   ASSERT_NEAR(center[0], 0.f, tol);
0146   ASSERT_NEAR(center[1], 0.f, tol);
0147   ASSERT_NEAR(center[2], -827.5f, tol);
0148 
0149   // Surface normal
0150   const auto z_axis = vector3{0.f, 0.f, 1.f};
0151   const vector3 normal_3D = disc.normal(ctx, point3{0.f, 0.f, 0.f});
0152   const vector3 normal_2D = disc.normal(ctx, point2{0.f, 0.f});
0153   // trigger all code paths
0154   ASSERT_NEAR(normal_3D[0], z_axis[0], tol);
0155   ASSERT_NEAR(normal_3D[1], z_axis[1], tol);
0156   ASSERT_NEAR(normal_3D[2], z_axis[2], tol);
0157   ASSERT_NEAR(normal_2D[0], z_axis[0], tol);
0158   ASSERT_NEAR(normal_2D[1], z_axis[1], tol);
0159   ASSERT_NEAR(normal_2D[2], z_axis[2], tol);
0160 
0161   // Cos incidence angle
0162   vector3 dir = vector::normalize(vector3{1.f, 0.f, 1.f});
0163   ASSERT_NEAR(cos_angle(ctx, disc, dir, point3{0.f, 0.f, 0.f}),
0164               constant<scalar>::inv_sqrt2, tol);
0165   ASSERT_NEAR(cos_angle(ctx, disc, dir, point2{0.f, 0.f}),
0166               constant<scalar>::inv_sqrt2, tol);
0167 
0168   dir = vector::normalize(vector3{1.f, 1.f, 0.f});
0169   ASSERT_NEAR(cos_angle(ctx, disc, dir, point3{1.f, 0.5f, 0.f}), 0.f, tol);
0170   ASSERT_NEAR(cos_angle(ctx, disc, dir, point2{1.f, 0.5f}), 0.f, tol);
0171 
0172   dir = vector::normalize(vector3{static_cast<scalar>(1.f),
0173                                   static_cast<scalar>(0.f),
0174                                   constant<scalar>::pi});
0175   scalar cos_inc_angle{constant<scalar>::pi /
0176                        std::sqrt(1.f + std::pow(constant<scalar>::pi, 2.f))};
0177   ASSERT_NEAR(cos_angle(ctx, disc, dir, point3{2.f, 1.f, 0.f}), cos_inc_angle,
0178               tol);
0179   ASSERT_NEAR(cos_angle(ctx, disc, dir, point2{2.f, 1.f}), cos_inc_angle, tol);
0180 
0181   // Coordinate transformations
0182   point3 glob_pos = {4.f, 7.f, 4.f};
0183   point3 local = disc.global_to_local(ctx, glob_pos, test_dir);
0184   point2 bound = disc.global_to_bound(ctx, glob_pos, test_dir);
0185 
0186   ASSERT_NEAR(local[0], std::sqrt(65.f), tol);
0187   ASSERT_NEAR(local[1], std::atan2(7.f, 4.f), tol);
0188   ASSERT_NEAR(bound[0], local[0], tol);
0189   ASSERT_NEAR(bound[1], local[1], tol);
0190 
0191   // Roundtrip
0192   point3 global = disc.local_to_global(ctx, local, test_dir);
0193   point3 global2 = disc.local_to_global(ctx, bound, test_dir);
0194 
0195   ASSERT_NEAR(glob_pos[0], global[0], tol);
0196   ASSERT_NEAR(glob_pos[1], global[1], tol);
0197   ASSERT_NEAR(glob_pos[2], global[2], tol);
0198 
0199   ASSERT_NEAR(global2[0], glob_pos[0], tol);
0200   ASSERT_NEAR(global2[1], glob_pos[1], tol);
0201   // The bound transform assumes the point is on surface
0202   ASSERT_NEAR(global2[2], disc_translation[2], tol);
0203 
0204   // Test the material
0205   ASSERT_TRUE(disc.has_material());
0206   const auto* mat_param = disc.material_parameters({0.f, 0.f});
0207   ASSERT_TRUE(mat_param);
0208   ASSERT_EQ(*mat_param, toy_cfg.mapped_material());
0209 
0210   //
0211   // Rectangle
0212   //
0213   const auto rec_descr = toy_det.surfaces()[586u];
0214   const auto rec = geometry::surface{toy_det, rec_descr};
0215 
0216   // IDs
0217   ASSERT_EQ(rec.identifier(), rec_descr.identifier());
0218   ASSERT_EQ(rec.volume(), 9u);
0219   ASSERT_EQ(rec.index(), 586u);
0220   ASSERT_EQ(rec.id(), surface_id::e_sensitive);
0221   ASSERT_EQ(rec.shape_id(), detector_t::masks::id::e_rectangle2D);
0222   ASSERT_TRUE(rec.is_sensitive());
0223   ASSERT_FALSE(rec.is_passive());
0224   ASSERT_FALSE(rec.is_portal());
0225 
0226   // Transformation matrix
0227   const auto& rec_translation = rec.transform(ctx).translation();
0228   ASSERT_NEAR(rec_translation[0], -71.902099f, tol);
0229   ASSERT_NEAR(rec_translation[1], -7.081735f, tol);
0230   ASSERT_NEAR(rec_translation[2], -455.f, tol);
0231   center = rec.center(ctx);
0232   ASSERT_NEAR(center[0], -71.902099f, tol);
0233   ASSERT_NEAR(center[1], -7.081735f, tol);
0234   ASSERT_NEAR(center[2], -455.f, tol);
0235 
0236   // Surface normal
0237   // trigger all code paths
0238   global = rec.transform(ctx).vector_to_global(z_axis);
0239   ASSERT_EQ(rec.normal(ctx, point3{0.f, 0.f, 0.f}), global);
0240   ASSERT_EQ(rec.normal(ctx, point2{0.f, 0.f}), global);
0241 
0242   // Incidence angle
0243   dir = vector::normalize(global);
0244   ASSERT_NEAR(cos_angle(ctx, rec, dir, point3{0.f, 0.f, 0.f}), 1.f, tol);
0245   ASSERT_NEAR(cos_angle(ctx, rec, dir, point2{0.f, 0.f}), 1.f, tol);
0246 
0247   dir = vector::normalize(
0248       vector3{static_cast<scalar>(0.f), -global[2], global[1]});
0249   ASSERT_NEAR(cos_angle(ctx, rec, dir, point3{1.f, 0.5f, 0.f}), 0.f, tol);
0250   ASSERT_NEAR(cos_angle(ctx, rec, dir, point2{1.f, 0.5f}), 0.f, tol);
0251 
0252   dir = vector::normalize(vector3{static_cast<scalar>(1.f),
0253                                   static_cast<scalar>(0.f),
0254                                   constant<scalar>::pi});
0255   cos_inc_angle = std::fabs(vector::dot(dir, global));
0256   ASSERT_NEAR(cos_angle(ctx, rec, dir, point3{2.f, 1.f, 0.f}), cos_inc_angle,
0257               tol);
0258   ASSERT_NEAR(cos_angle(ctx, rec, dir, point2{2.f, 1.f}), cos_inc_angle, tol);
0259 
0260   // Coordinate transformation roundtrip
0261   glob_pos = {4.f, 7.f, 4.f};
0262 
0263   local = rec.global_to_local(ctx, glob_pos, test_dir);
0264   global = rec.local_to_global(ctx, local, test_dir);
0265   ASSERT_NEAR(glob_pos[0], global[0], tol);
0266   ASSERT_NEAR(glob_pos[1], global[1], tol);
0267   ASSERT_NEAR(glob_pos[2], global[2], tol);
0268 
0269   glob_pos = {-71.902099f, -7.081735f, -460.f};
0270 
0271   local = rec.global_to_local(ctx, glob_pos, test_dir);
0272   global = rec.local_to_global(ctx, local, test_dir);
0273   ASSERT_NEAR(glob_pos[0], global[0], tol);
0274   ASSERT_NEAR(glob_pos[1], global[1], tol);
0275   ASSERT_NEAR(glob_pos[2], global[2], tol);
0276 
0277   bound = rec.global_to_bound(ctx, glob_pos, test_dir);
0278   global = rec.local_to_global(ctx, bound, test_dir);
0279   ASSERT_NEAR(global[0], glob_pos[0], tol);
0280   ASSERT_NEAR(global[1], glob_pos[1], tol);
0281   ASSERT_NEAR(global[2], glob_pos[2], tol);
0282 
0283   // Test the material (no material on sensitive surfaces)
0284   ASSERT_FALSE(rec.has_material());
0285 
0286   //
0287   // Concentric Cylinder
0288   //
0289   const auto cyl_descr = toy_det.surfaces()[3u];
0290   const auto cyl = geometry::surface{toy_det, cyl_descr};
0291 
0292   // IDs
0293   ASSERT_EQ(cyl.identifier(), cyl_descr.identifier());
0294   ASSERT_EQ(cyl.volume(), 0u);
0295   ASSERT_EQ(cyl.index(), 3u);
0296   ASSERT_EQ(cyl.id(), surface_id::e_portal);
0297   ASSERT_EQ(cyl.shape_id(), detector_t::masks::id::e_concentric_cylinder2D);
0298   ASSERT_FALSE(cyl.is_sensitive());
0299   ASSERT_FALSE(cyl.is_passive());
0300   ASSERT_TRUE(cyl.is_portal());
0301 
0302   // Transformation matrix
0303   const auto& cyl_translation = cyl.transform(ctx).translation();
0304   ASSERT_NEAR(cyl_translation[0], 0.f, tol);
0305   ASSERT_NEAR(cyl_translation[1], 0.f, tol);
0306   ASSERT_NEAR(cyl_translation[2], 0.f, tol);
0307   center = cyl.center(ctx);
0308   ASSERT_NEAR(center[0], 0.f, tol);
0309   ASSERT_NEAR(center[1], 0.f, tol);
0310   ASSERT_NEAR(center[2], 0.f, tol);
0311 
0312   // Surface normal
0313   // trigger all code paths
0314   constexpr scalar r{25.f * unit<scalar>::mm};
0315   const vector3 x_axis{1.f, 0.f, 0.f};
0316   ASSERT_NEAR(
0317       vector::norm(cyl.normal(ctx, point3{static_cast<scalar>(0.f),
0318                                           static_cast<scalar>(0.f), r}) -
0319                    x_axis),
0320       0.f, tol);
0321   ASSERT_NEAR(vector::norm(cyl.normal(ctx, point2{0.f, 0.f}) - x_axis),
0322               static_cast<scalar>(0.f), tol);
0323   ASSERT_NEAR(
0324       vector::norm(cyl.normal(ctx, point3{r * constant<scalar>::pi,
0325                                           static_cast<scalar>(0.f), r}) +
0326                    x_axis),
0327       0.f, tol);
0328   ASSERT_NEAR(vector::norm(cyl.normal(ctx, point2{r * constant<scalar>::pi,
0329                                                   static_cast<scalar>(0.f)}) +
0330                            x_axis),
0331               0.f, tol);
0332 
0333   const vector3 y_axis{0.f, 1.f, 0.f};
0334   ASSERT_NEAR(
0335       vector::norm(cyl.normal(ctx, point3{r * constant<scalar>::pi_2,
0336                                           static_cast<scalar>(0.f), r}) -
0337                    y_axis),
0338       0.f, tol);
0339   ASSERT_NEAR(vector::norm(cyl.normal(ctx, point2{r * constant<scalar>::pi_2,
0340                                                   static_cast<scalar>(0.f)}) -
0341                            y_axis),
0342               0.f, tol);
0343 
0344   // Incidence angle
0345   ASSERT_NEAR(
0346       cos_angle(ctx, cyl, x_axis,
0347                 point3{r * constant<scalar>::pi, static_cast<scalar>(0.f), r}),
0348       1.f, tol);
0349   ASSERT_NEAR(
0350       cos_angle(ctx, cyl, x_axis,
0351                 point2{r * constant<scalar>::pi, static_cast<scalar>(0.f)}),
0352       1.f, tol);
0353 
0354   ASSERT_NEAR(cos_angle(ctx, cyl, z_axis,
0355                         point3{r * constant<scalar>::pi_2,
0356                                static_cast<scalar>(0.f), r}),
0357               0.f, tol);
0358   ASSERT_NEAR(
0359       cos_angle(ctx, cyl, z_axis,
0360                 point2{r * constant<scalar>::pi_2, static_cast<scalar>(0.f)}),
0361       0.f, tol);
0362 
0363   dir = vector::normalize(vector3{1.f, 1.f, 0.f});
0364   ASSERT_NEAR(
0365       cos_angle(ctx, cyl, dir,
0366                 point3{static_cast<scalar>(0.f), static_cast<scalar>(1.f), r}),
0367       constant<scalar>::inv_sqrt2, tol);
0368   ASSERT_NEAR(cos_angle(ctx, cyl, dir, point2{0.f, 1.f}),
0369               constant<scalar>::inv_sqrt2, tol);
0370 
0371   // Coordinate transformation roundtrip
0372   glob_pos = {4.f, 7.f, 4.f};
0373 
0374   local = cyl.global_to_local(ctx, glob_pos, test_dir);
0375   global = cyl.local_to_global(ctx, local, test_dir);
0376   ASSERT_NEAR(glob_pos[0], global[0], tol);
0377   ASSERT_NEAR(glob_pos[1], global[1], tol);
0378   ASSERT_NEAR(glob_pos[2], global[2], tol);
0379 
0380   glob_pos = {constant<scalar>::inv_sqrt2 * r, constant<scalar>::inv_sqrt2 * r,
0381               static_cast<scalar>(2.f)};
0382 
0383   local = cyl.global_to_local(ctx, glob_pos, test_dir);
0384   global = cyl.local_to_global(ctx, local, test_dir);
0385   ASSERT_NEAR(glob_pos[0], global[0], tol);
0386   ASSERT_NEAR(glob_pos[1], global[1], tol);
0387   ASSERT_NEAR(glob_pos[2], global[2], tol);
0388 
0389   bound = cyl.global_to_bound(ctx, glob_pos, test_dir);
0390   global = cyl.local_to_global(ctx, bound, test_dir);
0391   ASSERT_NEAR(global[0], glob_pos[0], tol);
0392   ASSERT_NEAR(global[1], glob_pos[1], tol);
0393   ASSERT_NEAR(global[2], glob_pos[2], tol);
0394 
0395   // Test the material
0396   ASSERT_TRUE(cyl.has_material());
0397   mat_param = cyl.material_parameters({0.f, 0.f});
0398   ASSERT_TRUE(mat_param);
0399   ASSERT_EQ(*mat_param, toy_cfg.mapped_material());
0400 }
0401 
0402 /// This tests the functionality of a tracking surface interface for a wire
0403 /// chamber detector
0404 GTEST_TEST(detray_geometry, surface_wire_chamber) {
0405   using namespace detray;
0406 
0407   using metadata_t = test::wire_chamber_metadata;
0408   using detector_t = detector<metadata_t>;
0409 
0410   using test_algebra = metadata_t::algebra_type;
0411   using scalar = geometry::surface<detector_t>::scalar_type;
0412   using point2 = geometry::surface<detector_t>::point2_type;
0413   using point3 = geometry::surface<detector_t>::point3_type;
0414   using vector3 = geometry::surface<detector_t>::vector3_type;
0415 
0416   vecmem::host_memory_resource host_mr;
0417   wire_chamber_config<scalar> cfg{};
0418   const auto [wire_chmbr, names] =
0419       build_wire_chamber<test_algebra>(host_mr, cfg);
0420 
0421   auto ctx = typename detector_t::geometry_context{};
0422 
0423   //
0424   // Line
0425   //
0426   const auto line_descr = wire_chmbr.surfaces()[23u];
0427   const auto line = geometry::surface{wire_chmbr, line_descr};
0428 
0429   // IDs
0430   ASSERT_EQ(line.identifier(), line_descr.identifier());
0431   ASSERT_EQ(line.volume(), 1u);
0432   ASSERT_EQ(line.index(), 23u);
0433   ASSERT_EQ(line.id(), surface_id::e_sensitive);
0434   ASSERT_EQ(line.shape_id(), detector_t::masks::id::e_drift_cell);
0435   ASSERT_TRUE(line.is_sensitive());
0436   ASSERT_FALSE(line.is_passive());
0437   ASSERT_FALSE(line.is_portal());
0438 
0439   // Transformation matrix
0440   const auto line_translation = line.transform(ctx).translation();
0441   ASSERT_NEAR(line_translation[0], 412.858582f, tol);
0442   ASSERT_NEAR(line_translation[1], 299.412414f, tol);
0443   ASSERT_NEAR(line_translation[2], 0.f, tol);
0444   auto center = line.center(ctx);
0445   ASSERT_NEAR(center[0], 412.858582f, tol);
0446   ASSERT_NEAR(center[1], 299.412414f, tol);
0447   ASSERT_NEAR(center[2], 0.f, tol);
0448 
0449   // Surface normal
0450   const auto z_axis = vector3{0.f, 0.f, 1.f};
0451   point3 global = line.transform(ctx).vector_to_global(z_axis);
0452   const vector3 normal_3D = line.normal(ctx, point3{1.f, 0.f, 0.f});
0453   const vector3 normal_2D = line.normal(ctx, point2{1.f, 0.f});
0454   const vector3 neg_normal_3D = line.normal(ctx, point3{-1.f, 0.f, 0.f});
0455   const vector3 neg_normal_2D = line.normal(ctx, point2{-1.f, 0.f});
0456   // trigger all code paths
0457   ASSERT_NEAR(normal_3D[0], global[0], tol);
0458   ASSERT_NEAR(normal_3D[1], global[1], tol);
0459   ASSERT_NEAR(normal_3D[2], global[2], tol);
0460   ASSERT_NEAR(normal_2D[0], global[0], tol);
0461   ASSERT_NEAR(normal_2D[1], global[1], tol);
0462   ASSERT_NEAR(normal_2D[2], global[2], tol);
0463   ASSERT_NEAR(neg_normal_3D[0], global[0], tol);
0464   ASSERT_NEAR(neg_normal_3D[1], global[1], tol);
0465   ASSERT_NEAR(neg_normal_3D[2], global[2], tol);
0466   ASSERT_NEAR(neg_normal_2D[0], global[0], tol);
0467   ASSERT_NEAR(neg_normal_2D[1], global[1], tol);
0468   ASSERT_NEAR(neg_normal_2D[2], global[2], tol);
0469 
0470   // Cos incidence angle
0471   vector3 dir = vector::normalize(global);
0472   ASSERT_NEAR(cos_angle(ctx, line, dir, point3{1.f, 0.f, 0.f}), 1.f, tol);
0473   ASSERT_NEAR(cos_angle(ctx, line, dir, point2{1.f, 0.f}), 1.f, tol);
0474 
0475   dir = vector::normalize(
0476       vector3{static_cast<scalar>(0.f), -global[2], global[1]});
0477   ASSERT_NEAR(cos_angle(ctx, line, dir, point3{1.f, 100.f, 0.f}), 0.f, tol);
0478   ASSERT_NEAR(cos_angle(ctx, line, dir, point2{1.f, 100.f}), 0.f, tol);
0479 
0480   dir = vector3{-0.685475f, -0.0404595f, 0.726971f};
0481   ASSERT_NEAR(cos_angle(ctx, line, dir, point3{2.f, 1.f, 0.f}),
0482               constant<scalar>::inv_sqrt2, 0.0005);
0483   ASSERT_NEAR(cos_angle(ctx, line, dir, point2{2.f, 1.f}),
0484               constant<scalar>::inv_sqrt2, 0.0005);
0485 
0486   // Coordinate transformation roundtrip
0487   point3 glob_pos = {4.f, 7.f, 4.f};
0488 
0489   point3 local = line.global_to_local(ctx, glob_pos, test_dir);
0490   global = line.local_to_global(ctx, local, dir);
0491 
0492   // @TODO: Needs a reduced tolerance, why?
0493   scalar red_tol{0.00015f};
0494   ASSERT_NEAR(glob_pos[0], global[0], red_tol);
0495   ASSERT_NEAR(glob_pos[1], global[1], red_tol);
0496   ASSERT_NEAR(glob_pos[2], global[2], red_tol);
0497 
0498   glob_pos = center;
0499 
0500   local = line.global_to_local(ctx, glob_pos, test_dir);
0501   global = line.local_to_global(ctx, local, test_dir);
0502   red_tol = 7.f * 1e-5f;
0503   ASSERT_NEAR(glob_pos[0], global[0], red_tol);
0504   ASSERT_NEAR(glob_pos[1], global[1], red_tol);
0505   ASSERT_NEAR(glob_pos[2], global[2], red_tol);
0506 
0507   point2 bound = line.global_to_bound(ctx, glob_pos, test_dir);
0508   global = line.local_to_global(ctx, bound, test_dir);
0509   ASSERT_NEAR(global[0], glob_pos[0], red_tol);
0510   ASSERT_NEAR(global[1], glob_pos[1], red_tol);
0511   ASSERT_NEAR(global[2], glob_pos[2], red_tol);
0512 
0513   // Test the material
0514   ASSERT_TRUE(line.has_material());
0515   const auto* mat_param = line.material_parameters({0.f, 0.f});
0516   ASSERT_TRUE(mat_param);
0517   ASSERT_EQ(*mat_param, tungsten<scalar>());
0518 }