diff --git a/include/omath/collision/line_tracer.hpp b/include/omath/collision/line_tracer.hpp index e8c0b15..8834136 100644 --- a/include/omath/collision/line_tracer.hpp +++ b/include/omath/collision/line_tracer.hpp @@ -3,8 +3,8 @@ // #pragma once -#include "omath/Vector3.hpp" -#include "omath/Triangle.hpp" +#include "omath/triangle.hpp" +#include "omath/vector3.hpp" namespace omath::collision { diff --git a/include/omath/engines/unity_engine/camera.hpp b/include/omath/engines/unity_engine/camera.hpp new file mode 100644 index 0000000..e3a7f4e --- /dev/null +++ b/include/omath/engines/unity_engine/camera.hpp @@ -0,0 +1,21 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once +#include "omath/engines/unity_engine/constants.hpp" +#include "omath/projection/camera.hpp" + +namespace omath::unity_engine +{ + class Camera final : public projection::Camera + { + public: + Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + const Angle& fov, float near, float far); + void LookAt(const Vector3& target) override; + protected: + [[nodiscard]] Mat4x4 CalcViewMatrix() const override; + [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; + }; +} \ No newline at end of file diff --git a/include/omath/engines/unity_engine/constants.hpp b/include/omath/engines/unity_engine/constants.hpp new file mode 100644 index 0000000..dae1775 --- /dev/null +++ b/include/omath/engines/unity_engine/constants.hpp @@ -0,0 +1,26 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once + +#include +#include +#include +#include + +namespace omath::unity_engine +{ + constexpr Vector3 kAbsUp = {0, 1, 0}; + constexpr Vector3 kAbsRight = {1, 0, 0}; + constexpr Vector3 kAbsForward = {0, 0, 1}; + + using Mat4x4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; + using Mat3x3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; + using Mat1x3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>; + using PitchAngle = Angle; + using YawAngle = Angle; + using RollAngle = Angle; + + using ViewAngles = omath::ViewAngles; +} // namespace omath::source diff --git a/include/omath/engines/unity_engine/formulas.hpp b/include/omath/engines/unity_engine/formulas.hpp new file mode 100644 index 0000000..6352cd1 --- /dev/null +++ b/include/omath/engines/unity_engine/formulas.hpp @@ -0,0 +1,24 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once +#include "omath/engines/unity_engine/constants.hpp" + +namespace omath::unity_engine +{ + [[nodiscard]] + Vector3 ForwardVector(const ViewAngles& angles); + + [[nodiscard]] + Vector3 RightVector(const ViewAngles& angles); + + [[nodiscard]] + Vector3 UpVector(const ViewAngles& angles); + + [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); + + + [[nodiscard]] + Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); +} // namespace omath::source diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 674612a..09252cd 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -430,4 +430,30 @@ namespace omath { return MatRotationAxisZ(angles.yaw) * MatRotationAxisY(angles.pitch) * MatRotationAxisX(angles.roll); } + + template + [[nodiscard]] + Mat<4, 4, Type, St> MatPerspectiveLeftHanded(const float fieldOfView, const float aspectRatio, const float near, + const float far) noexcept + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + return {{1.f / (aspectRatio * fovHalfTan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fovHalfTan, 0.f, 0.f}, + {0.f, 0.f, (far + near) / (far - near), -(2.f * near * far) / (far - near)}, + {0.f, 0.f, 1.f, 0.f}}; + } + + template + [[nodiscard]] + Mat<4, 4, Type, St> MatPerspectiveRightHanded(const float fieldOfView, const float aspectRatio, const float near, + const float far) noexcept + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + return {{1.f / (aspectRatio * fovHalfTan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fovHalfTan, 0.f, 0.f}, + {0.f, 0.f, -(far + near) / (far - near), -(2.f * near * far) / (far - near)}, + {0.f, 0.f, -1.f, 0.f}}; + } } // namespace omath diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index add81ce..7c3473b 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -5,8 +5,8 @@ #pragma once #include -#include -#include +#include +#include #include #include #include "omath/projection/error_codes.hpp" @@ -36,8 +36,8 @@ namespace omath::projection m_viewPort(viewPort), m_fieldOfView(fov), m_farPlaneDistance(far), m_nearPlaneDistance(near), m_viewAngles(viewAngles), m_origin(position) { - } + protected: virtual void LookAt(const Vector3& target) = 0; @@ -49,8 +49,8 @@ namespace omath::projection { return CalcProjectionMatrix() * CalcViewMatrix(); } - public: + public: [[nodiscard]] const Mat4x4Type& GetViewProjectionMatrix() const { if (!m_viewProjectionMatrix.has_value()) @@ -116,9 +116,19 @@ namespace omath::projection [[nodiscard]] std::expected, Error> WorldToScreen(const Vector3& worldPosition) const { - const auto& viewProjMatrix = GetViewProjectionMatrix(); + auto normalizedCords = WorldToViewPort(worldPosition); - auto projected = viewProjMatrix * MatColumnFromVector(worldPosition); + if (!normalizedCords.has_value()) + return std::unexpected{normalizedCords.error()}; + + + return NdcToScreenPosition(*normalizedCords); + } + + [[nodiscard]] std::expected, Error> WorldToViewPort(const Vector3& worldPosition) const + { + auto projected = GetViewProjectionMatrix() * + MatColumnFromVector(worldPosition); if (projected.At(3, 0) == 0.0f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); @@ -128,10 +138,7 @@ namespace omath::projection if (IsNdcOutOfBounds(projected)) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); - const auto screenPositionX = (projected.At(0,0)+1.f) / 2.f * m_viewPort.m_width; - const auto screenPositionY = (-projected.At(1,0)+1) / 2.f * m_viewPort.m_height; - - return Vector3{screenPositionX, screenPositionY, projected.At(2,0)}; + return Vector3{projected.At(0, 0), projected.At(1, 0), projected.At(2, 0)}; } protected: @@ -152,7 +159,17 @@ namespace omath::projection [[nodiscard]] constexpr static bool IsNdcOutOfBounds(const Type& ndc) { - return std::ranges::any_of( ndc.RawArray(), [](const auto& val) { return val < -1 || val > 1; }); + return std::ranges::any_of(ndc.RawArray(), [](const auto& val) { return val < -1 || val > 1; }); + } + + [[nodiscard]] Vector3 NdcToScreenPosition(const Vector3& ndc) const + { + return + { + (ndc.x + 1.f) / 2.f * m_viewPort.m_width, + (1.f - ndc.y) / 2.f * m_viewPort.m_height, + ndc.z + }; } }; } // namespace omath::projection diff --git a/source/engines/CMakeLists.txt b/source/engines/CMakeLists.txt index eb223b0..aea648a 100644 --- a/source/engines/CMakeLists.txt +++ b/source/engines/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(source_engine) add_subdirectory(opengl_engine) add_subdirectory(iw_engine) +add_subdirectory(unity_engine) \ No newline at end of file diff --git a/source/engines/iw_engine/formulas.cpp b/source/engines/iw_engine/formulas.cpp index 3bc47dc..f4fe076 100644 --- a/source/engines/iw_engine/formulas.cpp +++ b/source/engines/iw_engine/formulas.cpp @@ -26,22 +26,25 @@ namespace omath::iw_engine return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } + Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); } + Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { - // NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation + // NOTE: Need magic number to fix fov calculation, since IW engine inherit Quake proj matrix calculation constexpr auto kMultiplyFactor = 0.75f; + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; return { {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, 1.f / fovHalfTan, 0, 0}, + {0, 1.f / (fovHalfTan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, 1, 0}, }; - } + }; } // namespace omath::iw_engine diff --git a/source/engines/opengl_engine/formulas.cpp b/source/engines/opengl_engine/formulas.cpp index be1a638..8162bdb 100644 --- a/source/engines/opengl_engine/formulas.cpp +++ b/source/engines/opengl_engine/formulas.cpp @@ -40,7 +40,6 @@ namespace omath::opengl_engine {0, 1.f / (fovHalfTan), 0, 0}, {0, 0, -(far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, -1, 0}, - }; } } // namespace omath::opengl_engine diff --git a/source/engines/source_engine/formulas.cpp b/source/engines/source_engine/formulas.cpp index 1051138..f037cad 100644 --- a/source/engines/source_engine/formulas.cpp +++ b/source/engines/source_engine/formulas.cpp @@ -30,6 +30,7 @@ namespace omath::source_engine { return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); } + Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { @@ -43,7 +44,6 @@ namespace omath::source_engine {0, 1.f / (fovHalfTan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, 1, 0}, - }; } } // namespace omath::source_engine diff --git a/source/engines/unity_engine/CMakeLists.txt b/source/engines/unity_engine/CMakeLists.txt new file mode 100644 index 0000000..5da7def --- /dev/null +++ b/source/engines/unity_engine/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(omath PRIVATE formulas.cpp camera.cpp) \ No newline at end of file diff --git a/source/engines/unity_engine/camera.cpp b/source/engines/unity_engine/camera.cpp new file mode 100644 index 0000000..5270f5a --- /dev/null +++ b/source/engines/unity_engine/camera.cpp @@ -0,0 +1,28 @@ +// +// Created by Vlad on 3/22/2025. +// +#include +#include + + +namespace omath::unity_engine +{ + Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + const projection::FieldOfView& fov, const float near, const float far) : + projection::Camera(position, viewAngles, viewPort, fov, near, far) + { + } + void Camera::LookAt([[maybe_unused]] const Vector3& target) + { + throw std::runtime_error("Not implemented"); + } + Mat4x4 Camera::CalcViewMatrix() const + { + return unity_engine::CalcViewMatrix(m_viewAngles, m_origin); + } + Mat4x4 Camera::CalcProjectionMatrix() const + { + return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, + m_farPlaneDistance); + } +} // namespace omath::unity_engine diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp new file mode 100644 index 0000000..cccc16c --- /dev/null +++ b/source/engines/unity_engine/formulas.cpp @@ -0,0 +1,45 @@ +// +// Created by Vlad on 3/22/2025. +// +#include "omath/engines/unity_engine/formulas.hpp" + + + +namespace omath::unity_engine +{ + Vector3 unity_engine::ForwardVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Vector3 RightVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Vector3 UpVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + { + return MatCameraView(ForwardVector(angles), RightVector(angles), + UpVector(angles), cam_origin); + } + Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, + const float far) + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + return { + {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, 1.f / (fovHalfTan), 0, 0}, + {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, + {0, 0, -1.f, 0}, + }; + } +} // namespace omath::unity_engine diff --git a/tests/engines/unit_test_iw_engine.cpp b/tests/engines/unit_test_iw_engine.cpp index cd42ee4..679af03 100644 --- a/tests/engines/unit_test_iw_engine.cpp +++ b/tests/engines/unit_test_iw_engine.cpp @@ -7,30 +7,30 @@ #include -TEST(UnitTestEwEngine, ForwardVector) +TEST(UnitTestIwEngine, ForwardVector) { - const auto forward = omath::source_engine::ForwardVector({}); + const auto forward = omath::iw_engine::ForwardVector({}); - EXPECT_EQ(forward, omath::source_engine::kAbsForward); + EXPECT_EQ(forward, omath::iw_engine::kAbsForward); } -TEST(UnitTestEwEngine, RightVector) +TEST(UnitTestIwEngine, RightVector) { - const auto right = omath::source_engine::RightVector({}); + const auto right = omath::iw_engine::RightVector({}); - EXPECT_EQ(right, omath::source_engine::kAbsRight); + EXPECT_EQ(right, omath::iw_engine::kAbsRight); } -TEST(UnitTestEwEngine, UpVector) +TEST(UnitTestIwEngine, UpVector) { - const auto up = omath::source_engine::UpVector({}); - EXPECT_EQ(up, omath::source_engine::kAbsUp); + const auto up = omath::iw_engine::UpVector({}); + EXPECT_EQ(up, omath::iw_engine::kAbsUp); } -TEST(UnitTestEwEngine, ProjectTargetMovedFromCamera) +TEST(UnitTestIwEngine, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); - const auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + const auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); for (float distance = 0.02f; distance < 1000.f; distance += 0.01f) @@ -47,10 +47,10 @@ TEST(UnitTestEwEngine, ProjectTargetMovedFromCamera) } } -TEST(UnitTestEwEngine, CameraSetAndGetFov) +TEST(UnitTestIwEngine, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); - auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); @@ -58,9 +58,9 @@ TEST(UnitTestEwEngine, CameraSetAndGetFov) EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); } -TEST(UnitTestEwEngine, CameraSetAndGetOrigin) +TEST(UnitTestIwEngine, CameraSetAndGetOrigin) { - auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); + auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); diff --git a/tests/engines/unit_test_source_engine.cpp b/tests/engines/unit_test_source_engine.cpp index 1b9592c..c129249 100644 --- a/tests/engines/unit_test_source_engine.cpp +++ b/tests/engines/unit_test_source_engine.cpp @@ -47,6 +47,26 @@ TEST(UnitTestSourceEngine, ProjectTargetMovedFromCamera) } } +TEST(UnitTestSourceEngine, ProjectTargetMovedUp) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + const auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + + auto prev = 1080.f; + for (float distance = 0.0f; distance < 10.f; distance += 1.f) + { + const auto projected = cam.WorldToScreen({100.f, 0, distance}); + EXPECT_TRUE(projected.has_value()); + + if (!projected.has_value()) + continue; + + EXPECT_TRUE(projected->y < prev); + + prev = projected->y; + } +} + TEST(UnitTestSourceEngine, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); diff --git a/tests/engines/unit_test_unity_engine.cpp b/tests/engines/unit_test_unity_engine.cpp index 4f6b72b..48b8b46 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -1,3 +1,76 @@ // // Created by Orange on 11/27/2024. // +#include +#include +#include +#include + + +TEST(UnitTestUnityEngine, ForwardVector) +{ + const auto forward = omath::unity_engine::ForwardVector({}); + + EXPECT_EQ(forward, omath::unity_engine::kAbsForward); +} + +TEST(UnitTestUnityEngine, RightVector) +{ + const auto right = omath::unity_engine::RightVector({}); + + EXPECT_EQ(right, omath::unity_engine::kAbsRight); +} + +TEST(UnitTestUnityEngine, UpVector) +{ + const auto up = omath::unity_engine::UpVector({}); + EXPECT_EQ(up, omath::unity_engine::kAbsUp); +} + +TEST(UnitTestUnityEngine, ProjectTargetMovedFromCamera) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(60.f); + const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.01f, 1000.f); + + + for (float distance = 0.02f; distance < 100.f; distance += 0.01f) + { + const auto projected = cam.WorldToScreen({0, 0, distance}); + + EXPECT_TRUE(projected.has_value()); + + if (!projected.has_value()) + continue; + + EXPECT_NEAR(projected->x, 640, 0.00001f); + EXPECT_NEAR(projected->y, 360, 0.00001f); + } +} +TEST(UnitTestUnityEngine, Project) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(60.f); + + const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 1000.f); + const auto proj = cam.WorldToScreen({0.f, 2.f, 10.f}); +} + +TEST(UnitTestUnityEngine, CameraSetAndGetFov) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); + cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); +} + +TEST(UnitTestUnityEngine, CameraSetAndGetOrigin) +{ + auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); + + EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); + cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); +} \ No newline at end of file