diff --git a/include/omath/collision/line_tracer.hpp b/include/omath/collision/line_tracer.hpp index 29555a1..074c5d2 100644 --- a/include/omath/collision/line_tracer.hpp +++ b/include/omath/collision/line_tracer.hpp @@ -8,43 +8,95 @@ namespace omath::collision { - class Ray + template> + class Ray final { public: - Vector3 start; - Vector3 end; + using VectorType = T; + VectorType start; + VectorType end; bool infinite_length = false; [[nodiscard]] - Vector3 direction_vector() const noexcept; + constexpr VectorType direction_vector() const noexcept + { + return end - start; + } [[nodiscard]] - Vector3 direction_vector_normalized() const noexcept; + constexpr VectorType direction_vector_normalized() const noexcept + { + return direction_vector().normalized(); + } }; - class LineTracer + + template> + class LineTracer final { + using TriangleType = Triangle; public: LineTracer() = delete; [[nodiscard]] - static bool can_trace_line(const Ray& ray, const Triangle>& triangle) noexcept; + constexpr static bool can_trace_line(const RayType& ray, const TriangleType& triangle) noexcept + { + return get_ray_hit_point(ray, triangle) == ray.end; + } // Realization of Möller–Trumbore intersection algorithm // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm [[nodiscard]] - static Vector3 get_ray_hit_point(const Ray& ray, const Triangle>& triangle) noexcept; + constexpr static auto get_ray_hit_point(const RayType& ray, const TriangleType& triangle) noexcept + { + constexpr float k_epsilon = std::numeric_limits::epsilon(); + + const auto side_a = triangle.side_a_vector(); + const auto side_b = triangle.side_b_vector(); + + const auto ray_dir = ray.direction_vector(); + + const auto p = ray_dir.cross(side_b); + const auto det = side_a.dot(p); + + if (std::abs(det) < k_epsilon) + return ray.end; + + const auto inv_det = 1 / det; + const auto t = ray.start - triangle.m_vertex2; + const auto u = t.dot(p) * inv_det; + + if ((u < 0 && std::abs(u) > k_epsilon) || (u > 1 && std::abs(u - 1) > k_epsilon)) + return ray.end; + + const auto q = t.cross(side_a); + // ReSharper disable once CppTooWideScopeInitStatement + const auto v = ray_dir.dot(q) * inv_det; + + if ((v < 0 && std::abs(v) > k_epsilon) || (u + v > 1 && std::abs(u + v - 1) > k_epsilon)) + return ray.end; + + const auto t_hit = side_b.dot(q) * inv_det; + + if (ray.infinite_length && t_hit <= k_epsilon) + return ray.end; + + if (t_hit <= k_epsilon || t_hit > 1 - k_epsilon) + return ray.end; + + return ray.start + ray_dir * t_hit; + } template [[nodiscard]] - static Vector3 get_ray_hit_point(const Ray& ray, const MeshType& mesh) noexcept + static auto get_ray_hit_point(const RayType& ray, const MeshType& mesh) noexcept { - Vector3 mesh_hit = ray.end; + auto mesh_hit = ray.end; - auto begin = mesh.m_element_buffer_object.cbegin(); - auto end = mesh.m_element_buffer_object.cend(); + const auto begin = mesh.m_element_buffer_object.cbegin(); + const auto end = mesh.m_element_buffer_object.cend(); for (auto current = begin; current < end; current = std::next(current)) { - auto face = mesh.make_face_in_world_space(current); + const auto face = mesh.make_face_in_world_space(current); auto ray_stop_point = get_ray_hit_point(ray, face); if (ray_stop_point.distance_to(ray.start) < mesh_hit.distance_to(ray.start)) diff --git a/source/collision/line_tracer.cpp b/source/collision/line_tracer.cpp index f03c445..0ccb57c 100644 --- a/source/collision/line_tracer.cpp +++ b/source/collision/line_tracer.cpp @@ -5,57 +5,4 @@ namespace omath::collision { - bool LineTracer::can_trace_line(const Ray& ray, const Triangle>& triangle) noexcept - { - return get_ray_hit_point(ray, triangle) == ray.end; - } - Vector3 Ray::direction_vector() const noexcept - { - return end - start; - } - - Vector3 Ray::direction_vector_normalized() const noexcept - { - return direction_vector().normalized(); - } - - Vector3 LineTracer::get_ray_hit_point(const Ray& ray, const Triangle>& triangle) noexcept - { - constexpr float k_epsilon = std::numeric_limits::epsilon(); - - const auto side_a = triangle.side_a_vector(); - const auto side_b = triangle.side_b_vector(); - - const auto ray_dir = ray.direction_vector(); - - const auto p = ray_dir.cross(side_b); - const auto det = side_a.dot(p); - - if (std::abs(det) < k_epsilon) - return ray.end; - - const auto inv_det = 1.0f / det; - const auto t = ray.start - triangle.m_vertex2; - const auto u = t.dot(p) * inv_det; - - if ((u < 0 && std::abs(u) > k_epsilon) || (u > 1 && std::abs(u - 1) > k_epsilon)) - return ray.end; - - const auto q = t.cross(side_a); - // ReSharper disable once CppTooWideScopeInitStatement - const auto v = ray_dir.dot(q) * inv_det; - - if ((v < 0 && std::abs(v) > k_epsilon) || (u + v > 1 && std::abs(u + v - 1) > k_epsilon)) - return ray.end; - - const auto t_hit = side_b.dot(q) * inv_det; - - if (ray.infinite_length && t_hit <= k_epsilon) - return ray.end; - - if (t_hit <= k_epsilon || t_hit > 1.0f - k_epsilon) - return ray.end; - - return ray.start + ray_dir * t_hit; - } } // namespace omath::collision diff --git a/tests/general/unit_test_line_trace.cpp b/tests/general/unit_test_line_trace.cpp index 35229ed..4aa6507 100644 --- a/tests/general/unit_test_line_trace.cpp +++ b/tests/general/unit_test_line_trace.cpp @@ -47,7 +47,7 @@ namespace // ----------------------------------------------------------------------------- struct TraceCase { - Ray ray; + Ray<> ray; bool expected_clear; // true => segment does NOT hit the triangle friend std::ostream& operator<<(std::ostream& os, const TraceCase& tc) { @@ -66,7 +66,7 @@ namespace TEST_P(CanTraceLineParam, VariousRays) { const auto& [ray, expected_clear] = GetParam(); - EXPECT_EQ(LineTracer::can_trace_line(ray, triangle), expected_clear); + EXPECT_EQ(LineTracer<>::can_trace_line(ray, triangle), expected_clear); } INSTANTIATE_TEST_SUITE_P( @@ -91,7 +91,7 @@ namespace constexpr Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}}; constexpr Vec3 expected{0.3f, 0.3f, 0.f}; - const Vec3 hit = LineTracer::get_ray_hit_point(ray, triangle); + const Vec3 hit = LineTracer<>::get_ray_hit_point(ray, triangle); ASSERT_FALSE(vec_equal(hit, ray.end)); EXPECT_TRUE(vec_equal(hit, expected)); } @@ -106,7 +106,7 @@ namespace {1001.f, 1000.f, 1000.f}, {1000.f, 1001.f, 1000.f}}; - EXPECT_TRUE(LineTracer::can_trace_line(short_ray, distant)); + EXPECT_TRUE(LineTracer<>::can_trace_line(short_ray, distant)); } TEST(unit_test_unity_engine, CantHit) @@ -115,13 +115,13 @@ namespace constexpr Ray ray{{}, {1.0, 0, 0}, false}; - EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, triangle)); + EXPECT_TRUE(omath::collision::LineTracer<>::can_trace_line(ray, triangle)); } TEST(unit_test_unity_engine, CanHit) { constexpr omath::Triangle> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}}; constexpr Ray ray{{}, {2.1, 0, 0}, false}; - EXPECT_FALSE(omath::collision::LineTracer::can_trace_line(ray, triangle)); + EXPECT_FALSE(omath::collision::LineTracer<>::can_trace_line(ray, triangle)); } } // namespace diff --git a/tests/general/unit_test_line_tracer.cpp b/tests/general/unit_test_line_tracer.cpp index fccc167..ac56a7b 100644 --- a/tests/general/unit_test_line_tracer.cpp +++ b/tests/general/unit_test_line_tracer.cpp @@ -15,9 +15,9 @@ TEST(LineTracerTests, ParallelRayReturnsEnd) ray.end = Vector3{1.f,1.f,1.f}; // For a ray parallel to the triangle plane the algorithm should return ray.end - const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); + const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_TRUE(hit == ray.end); - EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, tri)); + EXPECT_TRUE(omath::collision::LineTracer<>::can_trace_line(ray, tri)); } TEST(LineTracerTests, MissesTriangleReturnsEnd) @@ -27,7 +27,7 @@ TEST(LineTracerTests, MissesTriangleReturnsEnd) ray.start = Vector3{2.f,2.f,-1.f}; ray.end = Vector3{2.f,2.f,1.f}; // passes above the triangle area - const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); + const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_TRUE(hit == ray.end); } @@ -38,7 +38,7 @@ TEST(LineTracerTests, HitTriangleReturnsPointInsideSegment) ray.start = Vector3{0.25f,0.25f,-1.f}; ray.end = Vector3{0.25f,0.25f,1.f}; - const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); + const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri); // Should return a point between start and end (z approximately 0) EXPECT_NE(hit, ray.end); EXPECT_NEAR(hit.z, 0.f, 1e-4f); @@ -60,6 +60,6 @@ TEST(LineTracerTests, InfiniteLengthEarlyOut) // If t_hit <= epsilon the algorithm should return ray.end when infinite_length is true. // Using start on the triangle plane should produce t_hit <= epsilon. - const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); + const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_TRUE(hit == ray.end); } diff --git a/tests/general/unit_test_line_tracer_extra.cpp b/tests/general/unit_test_line_tracer_extra.cpp index 6eb184c..dd95749 100644 --- a/tests/general/unit_test_line_tracer_extra.cpp +++ b/tests/general/unit_test_line_tracer_extra.cpp @@ -10,7 +10,7 @@ TEST(LineTracerExtra, MissParallel) { constexpr Triangle> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Ray ray{ {0.3f,0.3f,1.f}, {0.3f,0.3f,2.f}, false }; // parallel above triangle - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } @@ -18,7 +18,7 @@ TEST(LineTracerExtra, HitCenter) { constexpr Triangle> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Ray ray{ {0.3f,0.3f,-1.f}, {0.3f,0.3f,1.f}, false }; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); ASSERT_FALSE(hit == ray.end); EXPECT_NEAR(hit.x, 0.3f, 1e-6f); EXPECT_NEAR(hit.y, 0.3f, 1e-6f); @@ -30,7 +30,7 @@ TEST(LineTracerExtra, HitOnEdge) constexpr Triangle> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Ray ray{ {0.0f,0.0f,1.f}, {0.0f,0.0f,0.f}, false }; // hitting exact vertex/edge may be considered miss; ensure function handles without crash - if (const auto hit = LineTracer::get_ray_hit_point(ray, tri); hit != ray.end) + if (const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); hit != ray.end) { EXPECT_NEAR(hit.x, 0.0f, 1e-6f); EXPECT_NEAR(hit.y, 0.0f, 1e-6f); @@ -42,6 +42,6 @@ TEST(LineTracerExtra, InfiniteRayIgnoredIfBehind) constexpr Triangle> tri({0,0,0},{1,0,0},{0,1,0}); // Ray pointing away but infinite_length true should be ignored constexpr Ray ray{ {0.5f,0.5f,-1.f}, {0.5f,0.5f,-2.f}, true }; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } diff --git a/tests/general/unit_test_line_tracer_more.cpp b/tests/general/unit_test_line_tracer_more.cpp index 81c711b..1373209 100644 --- a/tests/general/unit_test_line_tracer_more.cpp +++ b/tests/general/unit_test_line_tracer_more.cpp @@ -14,7 +14,7 @@ TEST(LineTracerMore, ParallelRayReturnsEnd) constexpr Triangle3 tri(Vector3{0.f,0.f,0.f}, Vector3{1.f,0.f,0.f}, Vector3{0.f,1.f,0.f}); Ray ray; ray.start = {0.f,0.f,1.f}; ray.end = {1.f,0.f,1.f}; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } @@ -24,7 +24,7 @@ TEST(LineTracerMore, UOutOfRangeReturnsEnd) constexpr Triangle3 tri(Vector3{0.f,0.f,0.f}, Vector3{1.f,0.f,0.f}, Vector3{0.f,1.f,0.f}); Ray ray; ray.start = {-1.f,-1.f,-1.f}; ray.end = {-0.5f,-1.f,1.f}; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } @@ -34,7 +34,7 @@ TEST(LineTracerMore, VOutOfRangeReturnsEnd) constexpr Triangle3 tri(Vector3{0.f,0.f,0.f}, Vector3{1.f,0.f,0.f}, Vector3{0.f,1.f,0.f}); Ray ray; ray.start = {2.f,2.f,-1.f}; ray.end = {2.f,2.f,1.f}; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } @@ -43,7 +43,7 @@ TEST(LineTracerMore, THitTooSmallReturnsEnd) constexpr Triangle3 tri(Vector3{0.f,0.f,0.f}, Vector3{1.f,0.f,0.f}, Vector3{0.f,1.f,0.f}); Ray ray; ray.start = {0.f,0.f,0.0000000001f}; ray.end = {0.f,0.f,1.f}; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } @@ -53,7 +53,7 @@ TEST(LineTracerMore, THitGreaterThanOneReturnsEnd) // Choose a ray and compute t_hit locally to assert consistency Ray ray; ray.start = {0.f,0.f,-1.f}; ray.end = {0.f,0.f,-0.5f}; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); constexpr float k_epsilon = std::numeric_limits::epsilon(); constexpr auto side_a = tri.side_a_vector(); @@ -87,7 +87,7 @@ TEST(LineTracerMore, InfiniteLengthWithSmallTHitReturnsEnd) // Create triangle slightly behind so t_hit <= eps tri = tri2; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } @@ -96,7 +96,7 @@ TEST(LineTracerMore, SuccessfulHitReturnsPoint) constexpr Triangle3 tri(Vector3{0.f,0.f,0.f}, Vector3{1.f,0.f,0.f}, Vector3{0.f,1.f,0.f}); Ray ray; ray.start = {0.1f,0.1f,-1.f}; ray.end = {0.1f,0.1f,1.f}; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_NE(hit, ray.end); // Hit should be on plane z=0 and near x=0.1,y=0.1 EXPECT_NEAR(hit.z, 0.f, 1e-6f); diff --git a/tests/general/unit_test_line_tracer_more2.cpp b/tests/general/unit_test_line_tracer_more2.cpp index 716453f..7b762b7 100644 --- a/tests/general/unit_test_line_tracer_more2.cpp +++ b/tests/general/unit_test_line_tracer_more2.cpp @@ -14,7 +14,7 @@ TEST(LineTracerMore2, UGreaterThanOneReturnsEnd) // choose ray so barycentric u > 1 Ray ray; ray.start = {2.f, -1.f, -1.f}; ray.end = {2.f, -1.f, 1.f}; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } @@ -24,7 +24,7 @@ TEST(LineTracerMore2, VGreaterThanOneReturnsEnd) // choose ray so barycentric v > 1 Ray ray; ray.start = {-1.f, 2.f, -1.f}; ray.end = {-1.f, 2.f, 1.f}; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } @@ -34,7 +34,7 @@ TEST(LineTracerMore2, UPlusVGreaterThanOneReturnsEnd) // Ray aimed so u+v > 1 (outside triangle region) Ray ray; ray.start = {1.f, 1.f, -1.f}; ray.end = {1.f, 1.f, 1.f}; - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } @@ -52,6 +52,6 @@ TEST(LineTracerMore2, ZeroLengthRayHandled) Ray ray; ray.start = {0.f,0.f,0.f}; ray.end = {0.f,0.f,0.f}; // Zero-length ray: direction length == 0; algorithm should handle without crash - const auto hit = LineTracer::get_ray_hit_point(ray, tri); + const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); EXPECT_EQ(hit, ray.end); } diff --git a/tests/general/unit_test_primitive_box.cpp b/tests/general/unit_test_primitive_box.cpp index 779099e..c4a9a0d 100644 --- a/tests/general/unit_test_primitive_box.cpp +++ b/tests/general/unit_test_primitive_box.cpp @@ -12,5 +12,5 @@ TEST(test, test) {0.f, 30.f, 0.f}, {}, omath::opengl_engine::k_abs_forward, omath::opengl_engine::k_abs_right); omath::collision::Ray ray{.start = {0, 0, 0}, .end = {-100, 0, 0}}; - std::ignore = omath::collision::LineTracer::get_ray_hit_point(ray, result); + std::ignore = omath::collision::LineTracer<>::get_ray_hit_point(ray, result); } \ No newline at end of file