diff --git a/extern/typed-geometry b/extern/typed-geometry
index 693adea14919b8e60828ed92f8d0bc8fe6206735..3a9f8048463c4ce8197fae53a12d2c4e0f151454 160000
--- a/extern/typed-geometry
+++ b/extern/typed-geometry
@@ -1 +1 @@
-Subproject commit 693adea14919b8e60828ed92f8d0bc8fe6206735
+Subproject commit 3a9f8048463c4ce8197fae53a12d2c4e0f151454
diff --git a/tests/feature/intersections/intersection.cc b/tests/feature/intersections/intersection.cc
index 07e1e82cbf29b52e457ac28a68c9820e631629a3..8cdff59ab73199c391f78d7bb02f8507e8cba9d8 100644
--- a/tests/feature/intersections/intersection.cc
+++ b/tests/feature/intersections/intersection.cc
@@ -7,7 +7,7 @@ TG_FUZZ_TEST(TypedGeometry, IntersectionRay3Sphere3)
     auto box1 = tg::aabb1(tg::pos1(-1.0f), tg::pos1(1.0f));
     auto box3 = tg::aabb3(tg::pos3(-1.0f), tg::pos3(1.0f));
     // random sphere
-    auto s = tg::sphere3(uniform(rng, box3) * 10.0f, tg::abs(uniform(rng, box1).x));
+    auto s = tg::sphere_boundary<3, float>(uniform(rng, box3) * 10.0f, tg::abs(uniform(rng, box1).x));
 
     {
         // ray from sphere origin to random direction
@@ -357,19 +357,19 @@ TG_FUZZ_TEST(Triangle, Intersection)
     auto ip4 = tg::intersection(ray, t4);
     auto ip5 = tg::intersection(ray, t5);
 
-    CHECK(ip0.has_value());
-    CHECK(ip1.has_value());
-    CHECK(ip2.has_value());
-    CHECK(ip3.has_value());
-    CHECK(ip4.has_value());
-    CHECK(ip5.has_value());
-
-    CHECK(ip0.value() == approx(p, 0.01f));
-    CHECK(ip1.value() == approx(p, 0.01f));
-    CHECK(ip2.value() == approx(p, 0.01f));
-    CHECK(ip3.value() == approx(p, 0.01f));
-    CHECK(ip4.value() == approx(p, 0.01f));
-    CHECK(ip5.value() == approx(p, 0.01f));
+    CHECK(ip0.any());
+    CHECK(ip1.any());
+    CHECK(ip2.any());
+    CHECK(ip3.any());
+    CHECK(ip4.any());
+    CHECK(ip5.any());
+
+    CHECK(ip0.first() == approx(p, 0.01f));
+    CHECK(ip1.first() == approx(p, 0.01f));
+    CHECK(ip2.first() == approx(p, 0.01f));
+    CHECK(ip3.first() == approx(p, 0.01f));
+    CHECK(ip4.first() == approx(p, 0.01f));
+    CHECK(ip5.first() == approx(p, 0.01f));
 
     auto a = uniform(rng, -2.f, 2.f);
     auto b = uniform(rng, -2.f, 2.f);
diff --git a/tests/feature/intersections/ray-intersect.cc b/tests/feature/intersections/ray-intersect.cc
index df4316f7e1065043825287b569987e98c21a3c80..874512b4dd75c17b5479fd74e6e53273a90fdad3 100644
--- a/tests/feature/intersections/ray-intersect.cc
+++ b/tests/feature/intersections/ray-intersect.cc
@@ -2,66 +2,180 @@
 
 TG_FUZZ_TEST(Ray, Intersect)
 {
-    auto bounds = tg::aabb3(-10, 10);
-
-    // ray - plane
-    {
-        auto const r = tg::ray3(uniform(rng, bounds), tg::uniform<tg::dir3>(rng));
-        auto const p = tg::plane(tg::uniform<tg::dir3>(rng), uniform(rng, bounds));
-
-        auto t = intersection_parameter(r, p);
-
+    const auto tolerance = 0.002f;
+    const auto test_obj = [tolerance](const auto& ray, const auto& obj) {
+        const auto ts = tg::intersection_parameter(ray, obj);
+        for (const auto& t : ts)
+        {
+            const auto ip = ray[t];
+            CHECK(contains(obj, ip, tolerance));
+        }
+        const auto t = tg::closest_intersection_parameter(ray, obj);
         if (t.has_value())
         {
-            auto const ip = r[t.value()];
-
-            CHECK(distance(ip, p) == approx(0).epsilon(1e-2f));
+            CHECK(t.value() == ts.first());
+            CHECK(closest_intersection(ray, obj) == ray[t.value()]);
+            CHECK(intersects(ray, obj));
         }
-    }
-
-    // ray - tube
-    {
-        auto const r = tg::ray3(uniform(rng, bounds), tg::uniform<tg::dir3>(rng));
-        auto const t = tg::cylinder_boundary_no_caps<3, float>(uniform(rng, bounds), uniform(rng, bounds), uniform(rng, 0.5f, 10.0f));
+        else
+            CHECK(!intersects(ray, obj));
+    };
 
-        auto is = intersection_parameter(r, t);
-        for (auto i : is)
+    const auto test_solid_obj = [tolerance, &rng](const auto& ray, const auto& obj) {
+        const auto ts = tg::intersection_parameter(ray, obj);
+        if (ts.has_value())
         {
-            auto ip = r[i];
-
-            CHECK(distance(ip, t) == approx(0).epsilon(1e-2f));
+            const auto interval = ts.value();
+            const auto ip1 = ray[interval.start];
+            const auto ip2 = ray[uniform(rng, interval.start, interval.end)];
+            const auto ip3 = ray[interval.end];
+            CHECK(contains(obj, ip1, tolerance));
+            CHECK(contains(obj, ip2, tolerance));
+            CHECK(contains(obj, ip3, tolerance));
+
+            const auto t = tg::closest_intersection_parameter(ray, obj);
+            CHECK(t.has_value());
+            CHECK(t.value() == ts.value().start);
+            CHECK(closest_intersection(ray, obj) == ip1);
+            CHECK(intersects(ray, obj));
         }
-    }
-
-    // ray - disk
-    {
-        auto const r = tg::ray3(uniform(rng, bounds), tg::uniform<tg::dir3>(rng));
-        auto const d = tg::sphere2in3(uniform(rng, bounds), uniform(rng, 0.5f, 10.0f), tg::uniform<tg::dir3>(rng));
-
-        auto ip = intersection(r, d);
-
-        if (ip.has_value())
-            CHECK(distance(ip.value(), d) == approx(0).epsilon(1e-2f));
-    }
-
-    // ray - cylinder
-    {
-        auto const r = tg::ray3(uniform(rng, bounds), tg::uniform<tg::dir3>(rng));
-        auto const c = tg::cylinder3(uniform(rng, bounds), uniform(rng, bounds), uniform(rng, 0.5f, 10.0f));
-        auto const t = tg::cylinder_boundary_no_caps<3, float>(c.axis, c.radius);
-
-        auto it = closest_intersection(r, t);
-        if (it.has_value())
+        else
+            CHECK(!intersects(ray, obj));
+    };
+
+    const auto test_obj_and_boundary = [&](const auto& ray, const auto& obj) {
+        const auto bounds = boundary_of(obj);
+        test_solid_obj(ray, obj);
+        test_obj(ray, bounds);
+
+        const auto iObj = tg::closest_intersection_parameter(ray, obj);
+        const auto iBounds = tg::closest_intersection_parameter(ray, bounds);
+        if (iBounds.has_value())
         {
-            CHECK(distance(it.value(), t) == approx(0).epsilon(1e-2f));
-            CHECK(distance(it.value(), c) == approx(0).epsilon(1e-2f));
+            CHECK(iObj.has_value());
+            CHECK(iBounds.value() >= iObj.value());
+            CHECK(contains(obj, ray[iBounds.value()], tolerance));
         }
-
-        auto ip = closest_intersection(r, c);
-
-        if (ip.has_value())
-            CHECK(distance(ip.value(), c) == approx(0).epsilon(1e-2f));
-    }
+    };
+
+    const auto test_obj_and_boundary_no_caps = [&](const auto& ray, const auto& obj) {
+        const auto bounds = boundary_of(obj);
+        const auto boundsNoCaps = boundary_no_caps_of(obj);
+        test_solid_obj(ray, obj);
+        test_obj(ray, bounds);
+        test_obj(ray, boundsNoCaps);
+
+        const auto iObj = tg::closest_intersection_parameter(ray, obj);
+        const auto iBounds = tg::closest_intersection_parameter(ray, bounds);
+        const auto iBoundsNoCaps = tg::closest_intersection_parameter(ray, boundsNoCaps);
+        if (iBoundsNoCaps.has_value())
+        {
+            CHECK(iBounds.has_value());
+            CHECK(iBoundsNoCaps.value() >= iBounds.value());
+            CHECK(contains(bounds, ray[iBoundsNoCaps.value()], tolerance));
+        }
+        if (iBounds.has_value())
+        {
+            CHECK(iObj.has_value());
+            CHECK(iBounds.value() >= iObj.value());
+            CHECK(contains(obj, ray[iBounds.value()], tolerance));
+        }
+    };
+    (void)test_obj_and_boundary_no_caps; // TODO: Remove
+
+    const auto r = uniform(rng, 0.0f, 10.0f);
+    const auto h = uniform(rng, 0.0f, 10.0f);
+    const auto n1 = tg::uniform<tg::dir1>(rng);
+    const auto n2 = tg::uniform<tg::dir2>(rng);
+    const auto n3 = tg::uniform<tg::dir3>(rng);
+    const auto n4 = tg::uniform<tg::dir4>(rng);
+
+    const auto range1 = tg::aabb1(tg::pos1(-10), tg::pos1(10));
+    const auto range2 = tg::aabb2(tg::pos2(-10), tg::pos2(10));
+    const auto range3 = tg::aabb3(tg::pos3(-10), tg::pos3(10));
+    const auto range4 = tg::aabb4(tg::pos4(-10), tg::pos4(10));
+
+    const auto pos10 = uniform(rng, range1);
+    const auto pos11 = uniform(rng, range1);
+
+    const auto pos20 = uniform(rng, range2);
+    const auto pos21 = uniform(rng, range2);
+    const auto pos22 = uniform(rng, range2);
+
+    const auto pos30 = uniform(rng, range3);
+    const auto pos31 = uniform(rng, range3);
+    const auto pos32 = uniform(rng, range3);
+
+    const auto pos40 = uniform(rng, range4);
+    const auto pos41 = uniform(rng, range4);
+
+    const auto axis0 = tg::segment3(pos30, pos31);
+
+    const auto d1 = tg::uniform<tg::dir1>(rng);
+    auto m1 = tg::mat1();
+    m1[0] = d1 * uniform(rng, 1.0f, 3.0f);
+
+    const auto d20 = tg::uniform<tg::dir2>(rng);
+    const auto d21 = perpendicular(d20);
+    auto m2 = tg::mat2();
+    m2[0] = d20 * uniform(rng, 1.0f, 3.0f);
+    m2[1] = d21 * uniform(rng, 1.0f, 3.0f);
+
+    const auto d30 = tg::uniform<tg::dir3>(rng);
+    const auto d31 = any_normal(d30);
+    const auto d32 = normalize(cross(d30, d31));
+    auto m3 = tg::mat3();
+    m3[0] = d30 * uniform(rng, 1.0f, 3.0f);
+    m3[1] = d31 * uniform(rng, 1.0f, 3.0f);
+    m3[2] = d32 * uniform(rng, 1.0f, 3.0f);
+
+    auto m23 = tg::mat2x3();
+    m23[0] = d30 * uniform(rng, 1.0f, 3.0f);
+    m23[1] = d31 * uniform(rng, 1.0f, 3.0f);
+
+
+    const auto ray1 = tg::ray1(uniform(rng, range1), tg::uniform<tg::dir1>(rng));
+    const auto ray2 = tg::ray2(uniform(rng, range2), tg::uniform<tg::dir2>(rng));
+    const auto ray3 = tg::ray3(uniform(rng, range3), tg::uniform<tg::dir3>(rng));
+    const auto ray4 = tg::ray4(uniform(rng, range4), tg::uniform<tg::dir4>(rng));
+
+    // aabb
+    test_obj_and_boundary(ray1, aabb_of(pos10, pos11));
+    test_obj_and_boundary(ray2, aabb_of(pos20, pos21));
+    test_obj_and_boundary(ray3, aabb_of(pos30, pos31));
+    test_obj_and_boundary(ray4, aabb_of(pos40, pos41));
+    // box
+    test_obj_and_boundary(ray1, tg::box1(pos10, m1));
+    test_obj_and_boundary(ray2, tg::box2(pos20, m2));
+    test_obj_and_boundary(ray3, tg::box3(pos30, m3));
+    // cylinder
+//    test_obj(ray3, tg::cylinder_boundary<3, float>(axis0, r));
+    test_obj(ray3, tg::cylinder_boundary_no_caps<3, float>(axis0, r));
+    // halfspace
+    test_solid_obj(ray1, tg::halfspace1(n1, h));
+    test_solid_obj(ray2, tg::halfspace2(n2, h));
+    test_solid_obj(ray3, tg::halfspace3(n3, h));
+    test_solid_obj(ray4, tg::halfspace4(n4, h));
+    // line
+    test_obj(ray2, tg::line2(pos20, n2));
+    // plane
+    test_obj(ray1, tg::plane1(n1, h));
+    test_obj(ray2, tg::plane2(n2, h));
+    test_obj(ray3, tg::plane3(n3, h));
+    test_obj(ray4, tg::plane4(n4, h));
+    // ray
+    test_obj(ray2, tg::ray2(pos20, n2));
+    // segment
+    test_obj(ray2, tg::segment2(pos20, pos21));
+    // sphere
+    test_obj_and_boundary(ray1, tg::sphere1(pos10, r));
+    test_obj_and_boundary(ray2, tg::sphere2(pos20, r));
+    test_obj_and_boundary(ray3, tg::sphere3(pos30, r));
+    test_obj_and_boundary(ray4, tg::sphere4(pos40, r));
+    test_obj(ray3, tg::sphere2in3(pos30, r, n3));
+    // triangle
+    test_solid_obj(ray2, tg::triangle2(pos20, pos21, pos22));
+    test_obj(ray3, tg::triangle3(pos30, pos31, pos32));
 }
 
 TG_FUZZ_TEST(Intersect, LineLine2)
diff --git a/tests/feature/objects/aabb.cc b/tests/feature/objects/aabb.cc
index ab7152f560550f943407528936d459bd48b341a3..9d1e8481df5d33a21c621b11680657073a71e88a 100644
--- a/tests/feature/objects/aabb.cc
+++ b/tests/feature/objects/aabb.cc
@@ -24,7 +24,7 @@ TG_FUZZ_TEST(TypedGeometry, AABB)
 
 TG_FUZZ_TEST(TypedGeometry, ObjectAABB)
 {;
-    auto test_obj = [&](auto obj) {
+    const auto test_obj = [&](auto obj) {
         auto bb = aabb_of(obj);
         auto p = uniform(rng, obj);
         CHECK(contains(bb, p));
diff --git a/tests/feature/objects/any_point.cc b/tests/feature/objects/any_point.cc
index dbe7da1a92d38bd9d580a4036724061fef0fa221..f1e26aa4f1eae4089511eb6fd6500a8c1ea88ee2 100644
--- a/tests/feature/objects/any_point.cc
+++ b/tests/feature/objects/any_point.cc
@@ -3,17 +3,17 @@
 TG_FUZZ_TEST(TypedGeometry, AnyPoint)
 {
     const auto tolerance = 0.001f;
-    auto const test_obj = [tolerance](const auto& o) {
+    const auto test_obj = [tolerance](const auto& o) {
         auto p = any_point(o);
         CHECK(contains(o, p, tolerance));
     };
 
-    auto const test_obj_and_boundary = [&test_obj](const auto& o) {
+    const auto test_obj_and_boundary = [&test_obj](const auto& o) {
         test_obj(o);
         test_obj(boundary_of(o));
     };
 
-    auto const test_obj_and_boundary_no_caps = [&test_obj](const auto& o) {
+    const auto test_obj_and_boundary_no_caps = [&test_obj](const auto& o) {
         test_obj(o);
         test_obj(boundary_of(o));
         test_obj(boundary_no_caps_of(o));
diff --git a/tests/feature/objects/centroid.cc b/tests/feature/objects/centroid.cc
index 290c219921e0689e50f636fd2338180d517c7e8f..901734fc972b8447bc4384353c25e718ac50be81 100644
--- a/tests/feature/objects/centroid.cc
+++ b/tests/feature/objects/centroid.cc
@@ -71,7 +71,7 @@ TG_FUZZ_TEST(TypedGeometry, CentroidByUniform)
     const auto tolerance = 0.25f;
     const auto numSamples = 100000;
 
-    auto const test_obj = [&rng, numSamples, tolerance](auto const& o) {
+    const auto test_obj = [&rng, numSamples, tolerance](const auto& o) {
         auto center = uniform(rng, o);
         for (auto i = 1; i < numSamples; ++i)
             center += uniform(rng, o);
@@ -82,12 +82,12 @@ TG_FUZZ_TEST(TypedGeometry, CentroidByUniform)
         CHECK(approxEqual);
     };
 
-    auto const test_obj_and_boundary = [&test_obj](auto const& o) {
+    const auto test_obj_and_boundary = [&test_obj](const auto& o) {
         test_obj(o);
         test_obj(boundary_of(o));
     };
 
-    auto const test_obj_and_boundary_no_caps = [&test_obj](auto const& o) {
+    const auto test_obj_and_boundary_no_caps = [&test_obj](const auto& o) {
         test_obj(o);
         test_obj(boundary_of(o));
         test_obj(boundary_no_caps_of(o));
diff --git a/tests/feature/objects/project.cc b/tests/feature/objects/project.cc
index e496d308afeca40933f100210788f87ee90cf804..99ade853dbce947adfc90746c008a5ae66e0af4f 100644
--- a/tests/feature/objects/project.cc
+++ b/tests/feature/objects/project.cc
@@ -4,7 +4,7 @@
 
 TG_FUZZ_TEST_MAX_ITS_MAX_CYCLES(TypedGeometry, Project, 25, 100'000'000'000)
 {
-    auto const test_obj = [&rng](auto p, auto o) {
+    const auto test_obj = [&rng](const auto& p, const auto& o) {
         auto proj = project(p, o);
 
         // Projected point lies in the object
@@ -29,12 +29,12 @@ TG_FUZZ_TEST_MAX_ITS_MAX_CYCLES(TypedGeometry, Project, 25, 100'000'000'000)
         CHECK(dist == approx(0.0f));
     };
 
-    auto const test_obj_and_boundary = [&test_obj](auto p, auto o) {
+    const auto test_obj_and_boundary = [&test_obj](const auto& p, const auto& o) {
         test_obj(p, o);
         test_obj(p, boundary_of(o));
     };
 
-    auto const test_obj_and_boundary_no_caps = [&test_obj](auto p, auto o) {
+    const auto test_obj_and_boundary_no_caps = [&test_obj](const auto& p, const auto& o) {
         test_obj(p, o);
         test_obj(p, boundary_of(o));
         test_obj(p, boundary_no_caps_of(o));