From de61f7a5d8b8f4d1950849ea0347c84a5409af76 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 9 Sep 2025 01:31:23 +0300 Subject: [PATCH] Adds screen to world space conversion Adds functionality to convert screen coordinates to world space, including handling for cases where the inverse view projection matrix is singular or when the world position is out of screen bounds. Also exposes Camera class to unit tests. --- CMakeLists.txt | 1 + include/omath/projection/camera.hpp | 63 ++++++++++++++++++------ include/omath/projection/error_codes.hpp | 1 + tests/general/unit_test_projection.cpp | 5 +- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad86253..e98cb97 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 be44e8e..b2c2515 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -4,13 +4,15 @@ #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 +class UnitTestProjection_Projection_Test; +#endif namespace omath::projection { class ViewPort final @@ -45,6 +47,8 @@ namespace omath::projection requires CameraEngineConcept class Camera final { + friend UnitTestProjection_Projection_Test; + public: ~Camera() = default; Camera(const Vector3& position, const ViewAnglesType& view_angles, const ViewPort& view_port, @@ -164,6 +168,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 +215,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 ae29fc0..0129af2 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 a5ca9a7..8d55d1a 100644 --- a/tests/general/unit_test_projection.cpp +++ b/tests/general/unit_test_projection.cpp @@ -13,8 +13,9 @@ 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, 0, 50.f}); + const auto result = cam.screen_to_world(projected.value()); + const auto result2 = cam.world_to_screen(result.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);