diff --git a/include/omath/Triangle.hpp b/include/omath/Triangle.hpp new file mode 100644 index 0000000..e9e3665 --- /dev/null +++ b/include/omath/Triangle.hpp @@ -0,0 +1,74 @@ +// +// Created by Orange on 11/13/2024. +// +#pragma once +#include "omath/Vector3.hpp" + +namespace omath +{ + template + class Triangle final + { + public: + constexpr Triangle() = default; + constexpr Triangle(const Vector& vertex1, const Vector& vertex2, const Vector& vertex3) + : m_vertex1(vertex1), m_vertex2(vertex2), m_vertex3(vertex3) + { + } + + Vector3 m_vertex1; + Vector3 m_vertex2; + Vector3 m_vertex3; + + [[nodiscard]] + constexpr Vector3 CalculateNormal() const + { + const auto b = SideBVector(); + const auto a = SideAVector(); + return b.Cross(a).Normalized(); + } + + [[nodiscard]] + float SideALength() const + { + return m_vertex1.DistTo(m_vertex2); + } + + [[nodiscard]] + float SideBLength() const + { + return m_vertex3.DistTo(m_vertex2); + } + + [[nodiscard]] + constexpr Vector3 SideAVector() const + { + return m_vertex1 - m_vertex2; + } + + [[nodiscard]] + constexpr float Hypot() const + { + return m_vertex1.DistTo(m_vertex3); + } + [[nodiscard]] + constexpr bool IsRectangular() const + { + const auto sideA = SideALength(); + const auto sideB = SideBLength(); + const auto hypot = Hypot(); + + return std::abs(sideA*sideA + sideB*sideB - hypot*hypot) <= 0.0001f; + } + [[nodiscard]] + constexpr Vector3 SideBVector() const + { + return m_vertex3 - m_vertex2; + } + [[nodiscard]] + constexpr Vector3 MidPoint() const + { + return (m_vertex1 + m_vertex2 + m_vertex3) / 3; + } + }; +} // namespace omath diff --git a/include/omath/Triangle3d.hpp b/include/omath/Triangle3d.hpp deleted file mode 100644 index de71a80..0000000 --- a/include/omath/Triangle3d.hpp +++ /dev/null @@ -1,36 +0,0 @@ -// -// Created by Orange on 11/13/2024. -// -#pragma once -#include "omath/Vector3.hpp" - -namespace omath -{ - class Triangle3d final - { - public: - Triangle3d(const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3); - - Vector3 m_vertex1; - Vector3 m_vertex2; - Vector3 m_vertex3; - - [[nodiscard]] - Vector3 CalculateNormal() const; - - [[nodiscard]] - float SideALength() const; - - [[nodiscard]] - float SideBLength() const; - - [[nodiscard]] - Vector3 SideAVector() const; - - [[nodiscard]] - Vector3 SideBVector() const; - - [[nodiscard]] - Vector3 MidPoint() const; - }; -} diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/LineTracer.hpp index e86f1dd..33b81a2 100644 --- a/include/omath/collision/LineTracer.hpp +++ b/include/omath/collision/LineTracer.hpp @@ -4,7 +4,7 @@ #pragma once #include "omath/Vector3.hpp" -#include "omath/Triangle3d.hpp" +#include "omath/Triangle.hpp" namespace omath::collision { @@ -27,12 +27,12 @@ namespace omath::collision [[nodiscard]] - static bool CanTraceLine(const Ray& ray, const Triangle3d& triangle); + static bool CanTraceLine(const Ray& ray, const Triangle& triangle); // Realization of Möller–Trumbore intersection algorithm // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm [[nodiscard]] - static Vector3 GetRayHitPoint(const Ray& ray, const Triangle3d& triangle); + static Vector3 GetRayHitPoint(const Ray& ray, const Triangle& triangle); }; } diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 0e0ea74..fe42cba 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -4,7 +4,6 @@ target_sources(omath PRIVATE color.cpp Vector4.cpp Vector2.cpp - Triangle3d.cpp ) add_subdirectory(prediction) diff --git a/source/Triangle3d.cpp b/source/Triangle3d.cpp deleted file mode 100644 index aacabc8..0000000 --- a/source/Triangle3d.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "omath/Triangle3d.hpp" - - -namespace omath -{ - Triangle3d::Triangle3d(const Vector3 &vertex1, const Vector3 &vertex2, const Vector3 &vertex3) - : m_vertex1(vertex1), m_vertex2(vertex2), m_vertex3(vertex3) - { - - } - - Vector3 Triangle3d::CalculateNormal() const - { - return (m_vertex1 - m_vertex2).Cross(m_vertex3 - m_vertex1).Normalized(); - } - - float Triangle3d::SideALength() const - { - return m_vertex1.DistTo(m_vertex2); - } - - float Triangle3d::SideBLength() const - { - return m_vertex3.DistTo(m_vertex2); - } - - Vector3 Triangle3d::SideAVector() const - { - return m_vertex1 - m_vertex2; - } - - Vector3 Triangle3d::SideBVector() const - { - return m_vertex3 - m_vertex2; - } - Vector3 Triangle3d::MidPoint() const - { - return (m_vertex1 + m_vertex2 + m_vertex3) / 3; - } -} // namespace omath diff --git a/source/collision/LineTracer.cpp b/source/collision/LineTracer.cpp index 83bcdd2..5f8c5b6 100644 --- a/source/collision/LineTracer.cpp +++ b/source/collision/LineTracer.cpp @@ -5,7 +5,7 @@ namespace omath::collision { - bool LineTracer::CanTraceLine(const Ray &ray, const Triangle3d &triangle) + bool LineTracer::CanTraceLine(const Ray& ray, const Triangle& triangle) { return GetRayHitPoint(ray, triangle) == ray.end; } @@ -19,7 +19,7 @@ namespace omath::collision return DirectionVector().Normalized(); } - Vector3 LineTracer::GetRayHitPoint(const Ray &ray, const Triangle3d &triangle) + Vector3 LineTracer::GetRayHitPoint(const Ray& ray, const Triangle& triangle) { constexpr float kEpsilon = std::numeric_limits::epsilon(); @@ -41,7 +41,7 @@ namespace omath::collision const auto u = t.Dot(p) * invDet; - if ((u < 0 && std::abs(u) > kEpsilon) || (u > 1 && std::abs(u-1) > kEpsilon)) + if ((u < 0 && std::abs(u) > kEpsilon) || (u > 1 && std::abs(u - 1) > kEpsilon)) return ray.end; const auto q = t.Cross(sideA); @@ -59,4 +59,4 @@ namespace omath::collision return ray.start + rayDir * tHit; } -} +} // namespace omath::collision diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 77c9508..aa377b8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(unit-tests general/UnitTestAngles.cpp general/UnitTestViewAngles.cpp general/UnitTestAngle.cpp + general/UnitTestTriangle.cpp engines/UnitTestOpenGL.cpp engines/UnitTestUnityEngine.cpp diff --git a/tests/general/UnitTestLineTrace.cpp b/tests/general/UnitTestLineTrace.cpp index 67884d9..4e37916 100644 --- a/tests/general/UnitTestLineTrace.cpp +++ b/tests/general/UnitTestLineTrace.cpp @@ -1,6 +1,6 @@ #include "gtest/gtest.h" #include "omath/collision/LineTracer.hpp" -#include "omath/Triangle3d.hpp" +#include "omath/Triangle.hpp" #include "omath/Vector3.hpp" using namespace omath; @@ -13,7 +13,7 @@ class LineTracerTest : public ::testing::Test Vector3 vertex1{0.0f, 0.0f, 0.0f}; Vector3 vertex2{1.0f, 0.0f, 0.0f}; Vector3 vertex3{0.0f, 1.0f, 0.0f}; - Triangle3d triangle{vertex1, vertex2, vertex3}; + Triangle triangle{vertex1, vertex2, vertex3}; }; // Test that a ray intersecting the triangle returns false for CanTraceLine @@ -71,7 +71,7 @@ TEST_F(LineTracerTest, TriangleFarBeyondRayEndPoint) constexpr Ray ray{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}; // Define a triangle far beyond the ray's endpoint - const Triangle3d distantTriangle{ + constexpr Triangle distantTriangle{ {1000.0f, 1000.0f, 1000.0f}, {1001.0f, 1000.0f, 1000.0f}, {1000.0f, 1001.0f, 1000.0f} }; diff --git a/tests/general/UnitTestTriangle.cpp b/tests/general/UnitTestTriangle.cpp new file mode 100644 index 0000000..258cb97 --- /dev/null +++ b/tests/general/UnitTestTriangle.cpp @@ -0,0 +1,132 @@ +// +// Created by Orange on 1/6/2025. +// +#include "omath/Triangle.hpp" +#include +#include +#include // For std::sqrt, std::isinf, std::isnan + + +using namespace omath; + +class UnitTestTriangle : public ::testing::Test +{ +protected: + // Define some Triangles to use in tests + Triangle t1; + Triangle t2; + Triangle t3; + + constexpr void SetUp() override + { + // Triangle with vertices (0, 0, 0), (1, 0, 0), (0, 1, 0) + t1 = Triangle( + Vector3(0.0f, 0.0f, 0.0f), + Vector3(1.0f, 0.0f, 0.0f), + Vector3(0.0f, 1.0f, 0.0f) + ); + + // Triangle with vertices (1, 2, 3), (4, 5, 6), (7, 8, 9) + t2 = Triangle( + Vector3(1.0f, 2.0f, 3.0f), + Vector3(4.0f, 5.0f, 6.0f), + Vector3(7.0f, 8.0f, 9.0f) + ); + + // An isosceles right triangle + t3 = Triangle( + Vector3(0.0f, 0.0f, 0.0f), + Vector3(2.0f, 0.0f, 0.0f), + Vector3(0.0f, 2.0f, 0.0f) + ); + } +}; + +// Test constructor and vertices +TEST_F(UnitTestTriangle, Constructor) +{ + constexpr Triangle t( + Vector3(1.0f, 2.0f, 3.0f), + Vector3(4.0f, 5.0f, 6.0f), + Vector3(7.0f, 8.0f, 9.0f) + ); + + EXPECT_FLOAT_EQ(t.m_vertex1.x, 1.0f); + EXPECT_FLOAT_EQ(t.m_vertex1.y, 2.0f); + EXPECT_FLOAT_EQ(t.m_vertex1.z, 3.0f); + + EXPECT_FLOAT_EQ(t.m_vertex2.x, 4.0f); + EXPECT_FLOAT_EQ(t.m_vertex2.y, 5.0f); + EXPECT_FLOAT_EQ(t.m_vertex2.z, 6.0f); + + EXPECT_FLOAT_EQ(t.m_vertex3.x, 7.0f); + EXPECT_FLOAT_EQ(t.m_vertex3.y, 8.0f); + EXPECT_FLOAT_EQ(t.m_vertex3.z, 9.0f); +} + +// Test CalculateNormal +TEST_F(UnitTestTriangle, CalculateNormal) +{ + // For t1, the normal should point in the +Z direction (0, 0, 1) or (0, 0, -1) + const Vector3 normal_t1 = t1.CalculateNormal(); + // Check if it's normalized and pointed along Z (sign can differ, so use absolute check) + EXPECT_NEAR(std::fabs(normal_t1.z), 1.0f, 1e-5f); + EXPECT_NEAR(normal_t1.Length(), 1.0f, 1e-5f); + + + // For t3, we expect the normal to be along +Z as well + const Vector3 normal_t3 = t3.CalculateNormal(); + EXPECT_NEAR(std::fabs(normal_t3.z), 1.0f, 1e-5f); +} + +// Test side lengths +TEST_F(UnitTestTriangle, SideLengths) +{ + // For t1 side lengths + EXPECT_FLOAT_EQ(t1.SideALength(), std::sqrt(1.0f)); // distance between (0,0,0) and (1,0,0) + EXPECT_FLOAT_EQ(t1.SideBLength(), std::sqrt(1.0f + 1.0f)); // distance between (4,5,6) & (7,8,9)... but we are testing t1, so let's be accurate: + // Actually, for t1: vertex2=(1,0,0), vertex3=(0,1,0) + // Dist between (0,1,0) and (1,0,0) = sqrt((1-0)^2 + (0-1)^2) = sqrt(1 + 1) = sqrt(2) + EXPECT_FLOAT_EQ(t1.SideBLength(), std::sqrt(2.0f)); + + // For t3, side a = distance between vertex1=(0,0,0) and vertex2=(2,0,0), which is 2 + // side b = distance between vertex3=(0,2,0) and vertex2=(2,0,0), which is sqrt(2^2 + (-2)^2)= sqrt(8)= 2.828... + // We'll just check side a first: + EXPECT_FLOAT_EQ(t3.SideALength(), 2.0f); + // Then side b: + EXPECT_FLOAT_EQ(t3.SideBLength(), std::sqrt(8.0f)); +} + +// Test side vectors +TEST_F(UnitTestTriangle, SideVectors) +{ + const Vector3 sideA_t1 = t1.SideAVector(); // m_vertex1 - m_vertex2 + EXPECT_FLOAT_EQ(sideA_t1.x, 0.0f - 1.0f); + EXPECT_FLOAT_EQ(sideA_t1.y, 0.0f - 0.0f); + EXPECT_FLOAT_EQ(sideA_t1.z, 0.0f - 0.0f); + + const Vector3 sideB_t1 = t1.SideBVector(); // m_vertex3 - m_vertex2 + EXPECT_FLOAT_EQ(sideB_t1.x, 0.0f - 1.0f); + EXPECT_FLOAT_EQ(sideB_t1.y, 1.0f - 0.0f); + EXPECT_FLOAT_EQ(sideB_t1.z, 0.0f - 0.0f); +} + +TEST_F(UnitTestTriangle, IsRectangular) +{ + EXPECT_TRUE(Triangle({2,0,0}, {}, {0,2,0}).IsRectangular()); +} +// Test midpoint +TEST_F(UnitTestTriangle, MidPoint) +{ + // For t1, midpoint of (0,0,0), (1,0,0), (0,1,0) + const Vector3 mid1 = t1.MidPoint(); + EXPECT_FLOAT_EQ(mid1.x, (0.0f + 1.0f + 0.0f) / 3.0f); + EXPECT_FLOAT_EQ(mid1.y, (0.0f + 0.0f + 1.0f) / 3.0f); + EXPECT_FLOAT_EQ(mid1.z, 0.0f); + + // For t2, midpoint of (1,2,3), (4,5,6), (7,8,9) + const Vector3 mid2 = t2.MidPoint(); + EXPECT_FLOAT_EQ(mid2.x, (1.0f + 4.0f + 7.0f) / 3.0f); + EXPECT_FLOAT_EQ(mid2.y, (2.0f + 5.0f + 8.0f) / 3.0f); + EXPECT_FLOAT_EQ(mid2.z, (3.0f + 6.0f + 9.0f) / 3.0f); +}