diff --git a/include/omath/engines/cry_engine/camera.hpp b/include/omath/engines/cry_engine/camera.hpp index d02ec6a..df5c03a 100644 --- a/include/omath/engines/cry_engine/camera.hpp +++ b/include/omath/engines/cry_engine/camera.hpp @@ -9,5 +9,5 @@ namespace omath::cry_engine { - using Camera = projection::Camera; + using Camera = projection::Camera; } // namespace omath::cry_engine \ No newline at end of file diff --git a/include/omath/engines/frostbite_engine/camera.hpp b/include/omath/engines/frostbite_engine/camera.hpp index 977dc42..8c02cf1 100644 --- a/include/omath/engines/frostbite_engine/camera.hpp +++ b/include/omath/engines/frostbite_engine/camera.hpp @@ -9,5 +9,5 @@ namespace omath::frostbite_engine { - using Camera = projection::Camera; + using Camera = projection::Camera; } // namespace omath::unity_engine \ No newline at end of file diff --git a/include/omath/engines/iw_engine/camera.hpp b/include/omath/engines/iw_engine/camera.hpp index b7213ad..52247d9 100644 --- a/include/omath/engines/iw_engine/camera.hpp +++ b/include/omath/engines/iw_engine/camera.hpp @@ -9,5 +9,5 @@ namespace omath::iw_engine { - using Camera = projection::Camera; + using Camera = projection::Camera; } // namespace omath::iw_engine \ No newline at end of file diff --git a/include/omath/engines/opengl_engine/camera.hpp b/include/omath/engines/opengl_engine/camera.hpp index de01bd5..a1ef66e 100644 --- a/include/omath/engines/opengl_engine/camera.hpp +++ b/include/omath/engines/opengl_engine/camera.hpp @@ -8,5 +8,5 @@ namespace omath::opengl_engine { - using Camera = projection::Camera; + using Camera = projection::Camera; } // namespace omath::opengl_engine \ No newline at end of file diff --git a/include/omath/engines/source_engine/camera.hpp b/include/omath/engines/source_engine/camera.hpp index 68871a9..2936f5c 100644 --- a/include/omath/engines/source_engine/camera.hpp +++ b/include/omath/engines/source_engine/camera.hpp @@ -7,5 +7,5 @@ #include "traits/camera_trait.hpp" namespace omath::source_engine { - using Camera = projection::Camera; + using Camera = projection::Camera; } // namespace omath::source_engine \ No newline at end of file diff --git a/include/omath/engines/unity_engine/camera.hpp b/include/omath/engines/unity_engine/camera.hpp index 7f818b4..5bccf0d 100644 --- a/include/omath/engines/unity_engine/camera.hpp +++ b/include/omath/engines/unity_engine/camera.hpp @@ -9,5 +9,5 @@ namespace omath::unity_engine { - using Camera = projection::Camera; + using Camera = projection::Camera; } // namespace omath::unity_engine \ No newline at end of file diff --git a/include/omath/engines/unreal_engine/camera.hpp b/include/omath/engines/unreal_engine/camera.hpp index 6565e13..4b5283e 100644 --- a/include/omath/engines/unreal_engine/camera.hpp +++ b/include/omath/engines/unreal_engine/camera.hpp @@ -9,5 +9,5 @@ namespace omath::unreal_engine { - using Camera = projection::Camera; + using Camera = projection::Camera; } // namespace omath::unreal_engine \ No newline at end of file diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index d73111c..a3d285e 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -42,6 +42,12 @@ namespace omath::projection AUTO, MANUAL, }; + struct CameraAxes + { + bool inverted_forward = false; + bool inverted_right = false; + }; + template concept CameraEngineConcept = requires(const Vector3& cam_origin, const Vector3& look_at, const ViewAnglesType& angles, @@ -58,8 +64,9 @@ namespace omath::projection requires noexcept(T::calc_projection_matrix(fov, viewport, znear, zfar, ndc_depth_range)); }; - template + template requires CameraEngineConcept class Camera final { @@ -87,7 +94,7 @@ namespace omath::projection static ViewAnglesType calc_view_angles_from_view_matrix(const Mat4X4Type& view_matrix) noexcept { Vector3 forward_vector = {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]}; - if constexpr (inverted_z) + if constexpr (axes.inverted_forward) forward_vector = -forward_vector; return TraitClass::calc_look_at_angle({}, forward_vector); } @@ -121,7 +128,7 @@ namespace omath::projection { const auto& view_matrix = get_view_matrix(); - if constexpr (inverted_z) + if constexpr (axes.inverted_forward) return -Vector3{view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]}; return {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]}; } @@ -130,6 +137,8 @@ namespace omath::projection Vector3 get_right() const noexcept { const auto& view_matrix = get_view_matrix(); + if constexpr (axes.inverted_right) + return -Vector3{view_matrix[0, 0], view_matrix[0, 1], view_matrix[0, 2]}; return {view_matrix[0, 0], view_matrix[0, 1], view_matrix[0, 2]}; } diff --git a/tests/general/unit_test_projection.cpp b/tests/general/unit_test_projection.cpp index 2b0bf31..1c46e59 100644 --- a/tests/general/unit_test_projection.cpp +++ b/tests/general/unit_test_projection.cpp @@ -5,8 +5,12 @@ #include #include #include +#include +#include +#include #include #include +#include #include #include #include @@ -781,4 +785,159 @@ TEST(UnitTestProjection, Unity_CalcOriginFromViewMatrix_WithRotation) EXPECT_NEAR(origin.x, 300.f, k_eps); EXPECT_NEAR(origin.y, -100.f, k_eps); EXPECT_NEAR(origin.z, 75.f, k_eps); -} \ No newline at end of file +} +// ---- Camera basis vectors at zero angles ---- + +TEST(UnitTestProjection, SourceEngine_ZeroAngles_BasisVectors) +{ + constexpr float k_eps = 1e-5f; + const auto cam = omath::source_engine::Camera({}, {}, {1920.f, 1080.f}, + omath::projection::FieldOfView::from_degrees(90.f), 0.01f, 1000.f); + const auto fwd = cam.get_forward(); + const auto right = cam.get_right(); + const auto up = cam.get_up(); + + EXPECT_NEAR(fwd.x, omath::source_engine::k_abs_forward.x, k_eps); + EXPECT_NEAR(fwd.y, omath::source_engine::k_abs_forward.y, k_eps); + EXPECT_NEAR(fwd.z, omath::source_engine::k_abs_forward.z, k_eps); + + EXPECT_NEAR(right.x, omath::source_engine::k_abs_right.x, k_eps); + EXPECT_NEAR(right.y, omath::source_engine::k_abs_right.y, k_eps); + EXPECT_NEAR(right.z, omath::source_engine::k_abs_right.z, k_eps); + + EXPECT_NEAR(up.x, omath::source_engine::k_abs_up.x, k_eps); + EXPECT_NEAR(up.y, omath::source_engine::k_abs_up.y, k_eps); + EXPECT_NEAR(up.z, omath::source_engine::k_abs_up.z, k_eps); +} + +TEST(UnitTestProjection, UnityEngine_ZeroAngles_BasisVectors) +{ + constexpr float k_eps = 1e-5f; + const auto cam = omath::unity_engine::Camera({}, {}, {1280.f, 720.f}, + omath::projection::FieldOfView::from_degrees(60.f), 0.03f, 1000.f); + const auto fwd = cam.get_forward(); + const auto right = cam.get_right(); + const auto up = cam.get_up(); + + EXPECT_NEAR(fwd.x, omath::unity_engine::k_abs_forward.x, k_eps); + EXPECT_NEAR(fwd.y, omath::unity_engine::k_abs_forward.y, k_eps); + EXPECT_NEAR(fwd.z, omath::unity_engine::k_abs_forward.z, k_eps); + + EXPECT_NEAR(right.x, omath::unity_engine::k_abs_right.x, k_eps); + EXPECT_NEAR(right.y, omath::unity_engine::k_abs_right.y, k_eps); + EXPECT_NEAR(right.z, omath::unity_engine::k_abs_right.z, k_eps); + + EXPECT_NEAR(up.x, omath::unity_engine::k_abs_up.x, k_eps); + EXPECT_NEAR(up.y, omath::unity_engine::k_abs_up.y, k_eps); + EXPECT_NEAR(up.z, omath::unity_engine::k_abs_up.z, k_eps); +} + +TEST(UnitTestProjection, OpenGLEngine_ZeroAngles_BasisVectors) +{ + constexpr float k_eps = 1e-5f; + const auto cam = omath::opengl_engine::Camera({}, {}, {1920.f, 1080.f}, + omath::projection::FieldOfView::from_degrees(90.f), 0.01f, 1000.f); + const auto fwd = cam.get_forward(); + const auto right = cam.get_right(); + const auto up = cam.get_up(); + + EXPECT_NEAR(fwd.x, omath::opengl_engine::k_abs_forward.x, k_eps); + EXPECT_NEAR(fwd.y, omath::opengl_engine::k_abs_forward.y, k_eps); + EXPECT_NEAR(fwd.z, omath::opengl_engine::k_abs_forward.z, k_eps); + + EXPECT_NEAR(right.x, omath::opengl_engine::k_abs_right.x, k_eps); + EXPECT_NEAR(right.y, omath::opengl_engine::k_abs_right.y, k_eps); + EXPECT_NEAR(right.z, omath::opengl_engine::k_abs_right.z, k_eps); + + EXPECT_NEAR(up.x, omath::opengl_engine::k_abs_up.x, k_eps); + EXPECT_NEAR(up.y, omath::opengl_engine::k_abs_up.y, k_eps); + EXPECT_NEAR(up.z, omath::opengl_engine::k_abs_up.z, k_eps); +} + +TEST(UnitTestProjection, UnrealEngine_ZeroAngles_BasisVectors) +{ + constexpr float k_eps = 1e-5f; + const auto cam = omath::unreal_engine::Camera({}, {}, {1920.f, 1080.f}, + omath::projection::FieldOfView::from_degrees(90.f), 0.01f, 1000.f); + const auto fwd = cam.get_forward(); + const auto right = cam.get_right(); + const auto up = cam.get_up(); + + EXPECT_NEAR(fwd.x, omath::unreal_engine::k_abs_forward.x, k_eps); + EXPECT_NEAR(fwd.y, omath::unreal_engine::k_abs_forward.y, k_eps); + EXPECT_NEAR(fwd.z, omath::unreal_engine::k_abs_forward.z, k_eps); + + EXPECT_NEAR(right.x, omath::unreal_engine::k_abs_right.x, k_eps); + EXPECT_NEAR(right.y, omath::unreal_engine::k_abs_right.y, k_eps); + EXPECT_NEAR(right.z, omath::unreal_engine::k_abs_right.z, k_eps); + + EXPECT_NEAR(up.x, omath::unreal_engine::k_abs_up.x, k_eps); + EXPECT_NEAR(up.y, omath::unreal_engine::k_abs_up.y, k_eps); + EXPECT_NEAR(up.z, omath::unreal_engine::k_abs_up.z, k_eps); +} + +TEST(UnitTestProjection, FrostbiteEngine_ZeroAngles_BasisVectors) +{ + constexpr float k_eps = 1e-5f; + const auto cam = omath::frostbite_engine::Camera({}, {}, {1920.f, 1080.f}, + omath::projection::FieldOfView::from_degrees(90.f), 0.01f, 1000.f); + const auto fwd = cam.get_forward(); + const auto right = cam.get_right(); + const auto up = cam.get_up(); + + EXPECT_NEAR(fwd.x, omath::frostbite_engine::k_abs_forward.x, k_eps); + EXPECT_NEAR(fwd.y, omath::frostbite_engine::k_abs_forward.y, k_eps); + EXPECT_NEAR(fwd.z, omath::frostbite_engine::k_abs_forward.z, k_eps); + + EXPECT_NEAR(right.x, omath::frostbite_engine::k_abs_right.x, k_eps); + EXPECT_NEAR(right.y, omath::frostbite_engine::k_abs_right.y, k_eps); + EXPECT_NEAR(right.z, omath::frostbite_engine::k_abs_right.z, k_eps); + + EXPECT_NEAR(up.x, omath::frostbite_engine::k_abs_up.x, k_eps); + EXPECT_NEAR(up.y, omath::frostbite_engine::k_abs_up.y, k_eps); + EXPECT_NEAR(up.z, omath::frostbite_engine::k_abs_up.z, k_eps); +} + +TEST(UnitTestProjection, CryEngine_ZeroAngles_BasisVectors) +{ + constexpr float k_eps = 1e-5f; + const auto cam = omath::cry_engine::Camera({}, {}, {1920.f, 1080.f}, + omath::projection::FieldOfView::from_degrees(90.f), 0.01f, 1000.f); + const auto fwd = cam.get_forward(); + const auto right = cam.get_right(); + const auto up = cam.get_up(); + + EXPECT_NEAR(fwd.x, omath::cry_engine::k_abs_forward.x, k_eps); + EXPECT_NEAR(fwd.y, omath::cry_engine::k_abs_forward.y, k_eps); + EXPECT_NEAR(fwd.z, omath::cry_engine::k_abs_forward.z, k_eps); + + EXPECT_NEAR(right.x, omath::cry_engine::k_abs_right.x, k_eps); + EXPECT_NEAR(right.y, omath::cry_engine::k_abs_right.y, k_eps); + EXPECT_NEAR(right.z, omath::cry_engine::k_abs_right.z, k_eps); + + EXPECT_NEAR(up.x, omath::cry_engine::k_abs_up.x, k_eps); + EXPECT_NEAR(up.y, omath::cry_engine::k_abs_up.y, k_eps); + EXPECT_NEAR(up.z, omath::cry_engine::k_abs_up.z, k_eps); +} + +TEST(UnitTestProjection, IWEngine_ZeroAngles_BasisVectors) +{ + constexpr float k_eps = 1e-5f; + const auto cam = omath::iw_engine::Camera({}, {}, {1920.f, 1080.f}, + omath::projection::FieldOfView::from_degrees(90.f), 0.01f, 1000.f); + const auto fwd = cam.get_forward(); + const auto right = cam.get_right(); + const auto up = cam.get_up(); + + EXPECT_NEAR(fwd.x, omath::iw_engine::k_abs_forward.x, k_eps); + EXPECT_NEAR(fwd.y, omath::iw_engine::k_abs_forward.y, k_eps); + EXPECT_NEAR(fwd.z, omath::iw_engine::k_abs_forward.z, k_eps); + + EXPECT_NEAR(right.x, omath::iw_engine::k_abs_right.x, k_eps); + EXPECT_NEAR(right.y, omath::iw_engine::k_abs_right.y, k_eps); + EXPECT_NEAR(right.z, omath::iw_engine::k_abs_right.z, k_eps); + + EXPECT_NEAR(up.x, omath::iw_engine::k_abs_up.x, k_eps); + EXPECT_NEAR(up.y, omath::iw_engine::k_abs_up.y, k_eps); + EXPECT_NEAR(up.z, omath::iw_engine::k_abs_up.z, k_eps); +}