diff --git a/include/omath/engines/cry_engine/constants.hpp b/include/omath/engines/cry_engine/constants.hpp index 762d040..2917570 100644 --- a/include/omath/engines/cry_engine/constants.hpp +++ b/include/omath/engines/cry_engine/constants.hpp @@ -15,7 +15,7 @@ namespace omath::cry_engine constexpr Vector3 k_abs_forward = {0, 1, 0}; using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; - using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; + using Mat3X3 = Mat<3, 3, float, MatStoreType::ROW_MAJOR>; using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>; using PitchAngle = Angle; using YawAngle = Angle; diff --git a/include/omath/engines/cry_engine/formulas.hpp b/include/omath/engines/cry_engine/formulas.hpp index 555a5f6..b09cb8b 100644 --- a/include/omath/engines/cry_engine/formulas.hpp +++ b/include/omath/engines/cry_engine/formulas.hpp @@ -23,7 +23,7 @@ namespace omath::cry_engine [[nodiscard]] Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far, - NDCDepthRange ndc_depth_range = NDCDepthRange::NEGATIVE_ONE_TO_ONE) noexcept; + NDCDepthRange ndc_depth_range = NDCDepthRange::ZERO_TO_ONE) noexcept; template requires std::is_floating_point_v diff --git a/source/engines/cry_engine/formulas.cpp b/source/engines/cry_engine/formulas.cpp index ac63408..c70b9c0 100644 --- a/source/engines/cry_engine/formulas.cpp +++ b/source/engines/cry_engine/formulas.cpp @@ -46,4 +46,4 @@ namespace omath::cry_engine field_of_view, aspect_ratio, near, far); std::unreachable(); } -} // namespace omath::unity_engine +} // namespace omath::cry_engine diff --git a/source/engines/cry_engine/traits/camera_trait.cpp b/source/engines/cry_engine/traits/camera_trait.cpp index a8ea618..da260f5 100644 --- a/source/engines/cry_engine/traits/camera_trait.cpp +++ b/source/engines/cry_engine/traits/camera_trait.cpp @@ -24,4 +24,4 @@ namespace omath::cry_engine return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far, ndc_depth_range); } -} // namespace omath::unity_engine \ No newline at end of file +} // namespace omath::cry_engine \ No newline at end of file diff --git a/tests/engines/unit_test_cry_engine.cpp b/tests/engines/unit_test_cry_engine.cpp index 2a80c5e..1888c27 100644 --- a/tests/engines/unit_test_cry_engine.cpp +++ b/tests/engines/unit_test_cry_engine.cpp @@ -29,11 +29,11 @@ TEST(unit_test_cry_engine, look_at_right) } TEST(unit_test_cry_engine, look_at_up) { - const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_right); + const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_up); // ReSharper disable once CppTooWideScopeInitStatement const auto dir_vector = cry_engine::forward_vector(angles); - for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), cry_engine::k_abs_right.as_array())) + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), cry_engine::k_abs_up.as_array())) EXPECT_NEAR(result, etalon, 0.0001f); } diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index 5418862..0cc51c3 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -1,6 +1,7 @@ // UnitTestMat.cpp #include "omath/linear_algebra/mat.hpp" #include "omath/linear_algebra/vector3.hpp" +#include "omath/trigonometry/angles.hpp" #include using namespace omath; @@ -306,6 +307,165 @@ TEST(UnitTestMatStandalone, MatPerspectiveNegativeOneToOneRange) EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-3f); } +TEST(UnitTestMatStandalone, MatPerspectiveRightHandedNegOneToOne) +{ + const auto proj = mat_perspective_right_handed_vertical_fov(90.f, 16.f / 9.f, 0.1f, 1000.f); + + // Near plane (negative z for RH) should map to z ~ -1 + auto near_pt = proj * mat_column_from_vector({0, 0, -0.1f}); + near_pt /= near_pt.at(3, 0); + EXPECT_NEAR(near_pt.at(2, 0), -1.0f, 1e-3f); + + // Far plane should map to z ~ 1 + auto far_pt = proj * mat_column_from_vector({0, 0, -1000.f}); + far_pt /= far_pt.at(3, 0); + EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-3f); + + // Mid-range point should be in (-1, 1) + auto mid_pt = proj * mat_column_from_vector({0, 0, -500.f}); + mid_pt /= mid_pt.at(3, 0); + EXPECT_GT(mid_pt.at(2, 0), -1.0f); + EXPECT_LT(mid_pt.at(2, 0), 1.0f); +} + +TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedHorizontalFovZeroToOne) +{ + // hfov=90 deg, aspect=16/9 => tan(hfov/2)=1, so x_axis=1 and y_axis=aspect + const auto proj = mat_perspective_left_handed_horizontal_fov(90.f, 16.f / 9.f, 0.1f, 1000.f); + + // Near plane should map to z ~ 0 + auto near_pt = proj * mat_column_from_vector({0, 0, 0.1f}); + near_pt /= near_pt.at(3, 0); + EXPECT_NEAR(near_pt.at(2, 0), 0.0f, 1e-4f); + + // Far plane should map to z ~ 1 + auto far_pt = proj * mat_column_from_vector({0, 0, 1000.f}); + far_pt /= far_pt.at(3, 0); + EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-4f); + + // Right edge of horizontal frustum at near plane (view_x = tan(hfov/2)*near = 0.1) + auto right_edge = proj * mat_column_from_vector({0.1f, 0, 0.1f}); + right_edge /= right_edge.at(3, 0); + EXPECT_NEAR(right_edge.at(0, 0), 1.0f, 1e-4f); +} + +TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedHorizontalFovNegOneToOne) +{ + const auto proj = mat_perspective_left_handed_horizontal_fov(90.f, 16.f / 9.f, 0.1f, 1000.f); + + auto near_pt = proj * mat_column_from_vector({0, 0, 0.1f}); + near_pt /= near_pt.at(3, 0); + EXPECT_NEAR(near_pt.at(2, 0), -1.0f, 1e-3f); + + auto far_pt = proj * mat_column_from_vector({0, 0, 1000.f}); + far_pt /= far_pt.at(3, 0); + EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-3f); + + auto right_edge = proj * mat_column_from_vector({0.1f, 0, 0.1f}); + right_edge /= right_edge.at(3, 0); + EXPECT_NEAR(right_edge.at(0, 0), 1.0f, 1e-4f); +} + +TEST(UnitTestMatStandalone, MatPerspectiveRightHandedHorizontalFovZeroToOne) +{ + const auto proj = mat_perspective_right_handed_horizontal_fov(90.f, 16.f / 9.f, 0.1f, 1000.f); + + auto near_pt = proj * mat_column_from_vector({0, 0, -0.1f}); + near_pt /= near_pt.at(3, 0); + EXPECT_NEAR(near_pt.at(2, 0), 0.0f, 1e-4f); + + auto far_pt = proj * mat_column_from_vector({0, 0, -1000.f}); + far_pt /= far_pt.at(3, 0); + EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-4f); + + auto right_edge = proj * mat_column_from_vector({0.1f, 0, -0.1f}); + right_edge /= right_edge.at(3, 0); + EXPECT_NEAR(right_edge.at(0, 0), 1.0f, 1e-4f); +} + +TEST(UnitTestMatStandalone, MatPerspectiveRightHandedHorizontalFovNegOneToOne) +{ + const auto proj = mat_perspective_right_handed_horizontal_fov(90.f, 16.f / 9.f, 0.1f, 1000.f); + + auto near_pt = proj * mat_column_from_vector({0, 0, -0.1f}); + near_pt /= near_pt.at(3, 0); + EXPECT_NEAR(near_pt.at(2, 0), -1.0f, 1e-3f); + + auto far_pt = proj * mat_column_from_vector({0, 0, -1000.f}); + far_pt /= far_pt.at(3, 0); + EXPECT_NEAR(far_pt.at(2, 0), 1.0f, 1e-3f); + + auto right_edge = proj * mat_column_from_vector({0.1f, 0, -0.1f}); + right_edge /= right_edge.at(3, 0); + EXPECT_NEAR(right_edge.at(0, 0), 1.0f, 1e-4f); +} + +TEST(UnitTestMatStandalone, MatPerspectiveHorizontalVsVerticalFovEquivalence) +{ + constexpr float hfov_deg = 90.f; + constexpr float aspect = 16.f / 9.f; + const float vfov_deg = angles::horizontal_fov_to_vertical(hfov_deg, aspect); + + const auto proj_h = mat_perspective_left_handed_horizontal_fov(hfov_deg, aspect, 0.1f, 1000.f); + const auto proj_v = mat_perspective_left_handed_vertical_fov(vfov_deg, aspect, 0.1f, 1000.f); + + for (size_t i = 0; i < 4; ++i) + for (size_t j = 0; j < 4; ++j) + EXPECT_NEAR(proj_h.at(i, j), proj_v.at(i, j), 1e-4f); +} + +// Handedness contract: clip_w sign tells front-of-camera vs behind. +// LH: +z view-space is in front (clip_w > 0), -z is behind (clip_w < 0). +// RH: -z view-space is in front (clip_w > 0), +z is behind (clip_w < 0). +TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedVerticalFovHandedness) +{ + const auto proj = mat_perspective_left_handed_vertical_fov(90.f, 16.f / 9.f, 0.1f, 1000.f); + + const auto in_front = proj * mat_column_from_vector({0, 0, 1.f}); + const auto behind = proj * mat_column_from_vector({0, 0, -1.f}); + + EXPECT_GT(in_front.at(3, 0), 0.0f); + EXPECT_LT(behind.at(3, 0), 0.0f); +} + +TEST(UnitTestMatStandalone, MatPerspectiveRightHandedVerticalFovHandedness) +{ + const auto proj = mat_perspective_right_handed_vertical_fov(90.f, 16.f / 9.f, 0.1f, 1000.f); + + const auto in_front = proj * mat_column_from_vector({0, 0, -1.f}); + const auto behind = proj * mat_column_from_vector({0, 0, 1.f}); + + EXPECT_GT(in_front.at(3, 0), 0.0f); + EXPECT_LT(behind.at(3, 0), 0.0f); +} + +TEST(UnitTestMatStandalone, MatPerspectiveLeftHandedHorizontalFovHandedness) +{ + const auto proj = mat_perspective_left_handed_horizontal_fov(90.f, 16.f / 9.f, 0.1f, 1000.f); + + const auto in_front = proj * mat_column_from_vector({0, 0, 1.f}); + const auto behind = proj * mat_column_from_vector({0, 0, -1.f}); + + EXPECT_GT(in_front.at(3, 0), 0.0f); + EXPECT_LT(behind.at(3, 0), 0.0f); +} + +TEST(UnitTestMatStandalone, MatPerspectiveRightHandedHorizontalFovHandedness) +{ + const auto proj = mat_perspective_right_handed_horizontal_fov(90.f, 16.f / 9.f, 0.1f, 1000.f); + + const auto in_front = proj * mat_column_from_vector({0, 0, -1.f}); + const auto behind = proj * mat_column_from_vector({0, 0, 1.f}); + + EXPECT_GT(in_front.at(3, 0), 0.0f); + EXPECT_LT(behind.at(3, 0), 0.0f); +} + TEST(UnitTestMatStandalone, MatPerspectiveZeroToOneEquanity) { // LH and RH should produce same NDC for mirrored z