diff --git a/include/omath/collision/line_tracer.hpp b/include/omath/collision/line_tracer.hpp index 29555a1..3bba86e 100644 --- a/include/omath/collision/line_tracer.hpp +++ b/include/omath/collision/line_tracer.hpp @@ -8,43 +8,96 @@ 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 + constexpr 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/include/omath/collision/simplex.hpp b/include/omath/collision/simplex.hpp index 7820f53..c7c99e7 100644 --- a/include/omath/collision/simplex.hpp +++ b/include/omath/collision/simplex.hpp @@ -130,7 +130,7 @@ namespace omath::collision template [[nodiscard]] - static constexpr bool near_zero(const V& v, const float eps = 1e-7f) + static constexpr bool near_zero(const V& v, const float eps = 1e-7f) noexcept { return v.dot(v) <= eps * eps; } @@ -146,7 +146,7 @@ namespace omath::collision } [[nodiscard]] - constexpr bool handle_line(VectorType& direction) + constexpr bool handle_line(VectorType& direction) noexcept { const auto& a = m_points[0]; const auto& b = m_points[1]; @@ -158,21 +158,11 @@ namespace omath::collision { // ReSharper disable once CppTooWideScopeInitStatement auto n = ab.cross(ao); // Needed to valid handle collision if colliders placed at same origin pos - if (near_zero(n)) - { - // collinear: origin lies on ray AB (often on segment), pick any perp to escape - direction = any_perp(ab); - } - else - { - direction = n.cross(ab); - } - } - else - { - *this = {a}; - direction = ao; + direction = near_zero(n) ? any_perp(ab) : n.cross(ab); + return false; } + *this = {a}; + direction = ao; return false; } diff --git a/source/collision/line_tracer.cpp b/source/collision/line_tracer.cpp deleted file mode 100644 index f03c445..0000000 --- a/source/collision/line_tracer.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// -// Created by Orange on 11/13/2024. -// -#include "omath/collision/line_tracer.hpp" - -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_macho_scanner.cpp b/tests/general/unit_test_macho_scanner.cpp index 0beada0..1e15bdc 100644 --- a/tests/general/unit_test_macho_scanner.cpp +++ b/tests/general/unit_test_macho_scanner.cpp @@ -19,6 +19,8 @@ namespace constexpr std::uint32_t lc_segment = 0x1; constexpr std::uint32_t lc_segment_64 = 0x19; + constexpr std::string_view segment_name = "__TEXT"; + constexpr std::string_view section_name = "__text"; #pragma pack(push, 1) struct MachHeader64 { @@ -117,7 +119,6 @@ namespace constexpr std::size_t segment_size = sizeof(SegmentCommand64); constexpr std::size_t section_size = sizeof(Section64); constexpr std::size_t load_cmd_size = segment_size + section_size; - // Section data will start after headers const std::size_t section_offset = header_size + load_cmd_size; @@ -138,7 +139,7 @@ namespace SegmentCommand64 segment{}; segment.cmd = lc_segment_64; segment.cmdsize = static_cast(load_cmd_size); - std::strncpy(segment.segname, "__TEXT", 16); + std::ranges::copy(segment_name, segment.segname); segment.vmaddr = 0x100000000; segment.vmsize = section_bytes.size(); segment.fileoff = section_offset; @@ -152,8 +153,8 @@ namespace // Create section Section64 section{}; - std::strncpy(section.sectname, "__text", 16); - std::strncpy(section.segname, "__TEXT", 16); + std::ranges::copy(section_name, section.sectname); + std::ranges::copy(segment_name, segment.segname); section.addr = 0x100000000; section.size = section_bytes.size(); section.offset = static_cast(section_offset); @@ -188,7 +189,7 @@ namespace constexpr std::size_t load_cmd_size = segment_size + section_size; // Section data will start after headers - const std::size_t section_offset = header_size + load_cmd_size; + constexpr std::size_t section_offset = header_size + load_cmd_size; // Create Mach-O header MachHeader32 header{}; @@ -206,7 +207,7 @@ namespace SegmentCommand32 segment{}; segment.cmd = lc_segment; segment.cmdsize = static_cast(load_cmd_size); - std::strncpy(segment.segname, "__TEXT", 16); + std::ranges::copy(segment_name, segment.segname); segment.vmaddr = 0x1000; segment.vmsize = static_cast(section_bytes.size()); segment.fileoff = static_cast(section_offset); @@ -220,8 +221,8 @@ namespace // Create section Section32 section{}; - std::strncpy(section.sectname, "__text", 16); - std::strncpy(section.segname, "__TEXT", 16); + std::ranges::copy(section_name, section.sectname); + std::ranges::copy(segment_name, segment.segname); section.addr = 0x1000; section.size = static_cast(section_bytes.size()); section.offset = static_cast(section_offset); 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