diff --git a/CMakeLists.txt b/CMakeLists.txt index ad862534..e98cb975 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) if (OMATH_BUILD_TESTS) add_subdirectory(extlibs) add_subdirectory(tests) + target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_BUILD_TESTS) endif () if (OMATH_BUILD_EXAMPLES) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index be44e8e8..6d84cb62 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -4,13 +4,18 @@ #pragma once -#include "omath/projection/error_codes.hpp" #include "omath/linear_algebra/mat.hpp" #include "omath/linear_algebra/vector3.hpp" +#include "omath/projection/error_codes.hpp" #include #include #include +#ifdef OMATH_BUILD_TESTS +// ReSharper disable once CppInconsistentNaming +class UnitTestProjection_Projection_Test; +#endif + namespace omath::projection { class ViewPort final @@ -45,6 +50,9 @@ namespace omath::projection requires CameraEngineConcept class Camera final { +#ifdef OMATH_BUILD_TESTS + friend UnitTestProjection_Projection_Test; +#endif public: ~Camera() = default; Camera(const Vector3& position, const ViewAnglesType& view_angles, const ViewPort& view_port, @@ -164,6 +172,31 @@ namespace omath::projection return Vector3{projected.at(0, 0), projected.at(1, 0), projected.at(2, 0)}; } + [[nodiscard]] + std::expected, Error> view_port_to_screen(const Vector3& ndc) const noexcept + { + const auto inv_view_proj = get_view_projection_matrix().inverted(); + + if (!inv_view_proj) + return std::unexpected(Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO); + + auto inverted_projection = + inv_view_proj.value() * mat_column_from_vector(ndc); + + if (!inverted_projection.at(3, 0)) + return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); + + inverted_projection /= inverted_projection.at(3, 0); + + return Vector3{inverted_projection.at(0, 0), inverted_projection.at(1, 0), + inverted_projection.at(2, 0)}; + } + + [[nodiscard]] + std::expected, Error> screen_to_world(const Vector3& screen_pos) const noexcept + { + return view_port_to_screen(screen_to_dnc(screen_pos)); + } protected: ViewPort m_view_port{}; @@ -186,19 +219,25 @@ namespace omath::projection [[nodiscard]] Vector3 ndc_to_screen_position(const Vector3& ndc) const noexcept { -/* - ^ - | y - 1 | - | - | - -1 ---------0--------- 1 --> x - | - | - -1 | - v -*/ + /* + ^ + | y + 1 | + | + | + -1 ---------0--------- 1 --> x + | + | + -1 | + v + */ return {(ndc.x + 1.f) / 2.f * m_view_port.m_width, (1.f - ndc.y) / 2.f * m_view_port.m_height, ndc.z}; } + + [[nodiscard]] Vector3 screen_to_dnc(const Vector3& screen_pos) const noexcept + { + return {screen_pos.x / m_view_port.m_width * 2.f - 1.f, 1.f - screen_pos.y / m_view_port.m_height * 2.f, + screen_pos.z}; + } }; } // namespace omath::projection diff --git a/include/omath/projection/error_codes.hpp b/include/omath/projection/error_codes.hpp index ae29fc07..0129af2c 100644 --- a/include/omath/projection/error_codes.hpp +++ b/include/omath/projection/error_codes.hpp @@ -10,5 +10,6 @@ namespace omath::projection enum class Error : uint16_t { WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS, + INV_VIEW_PROJ_MAT_DET_EQ_ZERO, }; } \ No newline at end of file diff --git a/tests/general/unit_test_projection.cpp b/tests/general/unit_test_projection.cpp index a5ca9a74..b2ba6d7f 100644 --- a/tests/general/unit_test_projection.cpp +++ b/tests/general/unit_test_projection.cpp @@ -13,8 +13,12 @@ TEST(UnitTestProjection, Projection) const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); - const auto projected = cam.world_to_screen({1000, 0, 50}); + const auto projected = cam.world_to_screen({1000.f, 0, 50.f}); + const auto result = cam.screen_to_world(projected.value()); + const auto result2 = cam.world_to_screen(result.value()); + EXPECT_EQ(static_cast>(projected.value()), + static_cast>(result2.value())); EXPECT_NEAR(projected->x, 960.f, 0.001f); EXPECT_NEAR(projected->y, 504.f, 0.001f); EXPECT_NEAR(projected->z, 1.f, 0.001f);