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.
This commit is contained in:
2025-09-09 01:31:23 +03:00
parent 07a449b633
commit de61f7a5d8
4 changed files with 54 additions and 16 deletions

View File

@@ -100,6 +100,7 @@ target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
if (OMATH_BUILD_TESTS) if (OMATH_BUILD_TESTS)
add_subdirectory(extlibs) add_subdirectory(extlibs)
add_subdirectory(tests) add_subdirectory(tests)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_BUILD_TESTS)
endif () endif ()
if (OMATH_BUILD_EXAMPLES) if (OMATH_BUILD_EXAMPLES)

View File

@@ -4,13 +4,15 @@
#pragma once #pragma once
#include "omath/projection/error_codes.hpp"
#include "omath/linear_algebra/mat.hpp" #include "omath/linear_algebra/mat.hpp"
#include "omath/linear_algebra/vector3.hpp" #include "omath/linear_algebra/vector3.hpp"
#include "omath/projection/error_codes.hpp"
#include <expected> #include <expected>
#include <omath/angle.hpp> #include <omath/angle.hpp>
#include <type_traits> #include <type_traits>
#ifdef OMATH_BUILD_TESTS
class UnitTestProjection_Projection_Test;
#endif
namespace omath::projection namespace omath::projection
{ {
class ViewPort final class ViewPort final
@@ -45,6 +47,8 @@ namespace omath::projection
requires CameraEngineConcept<TraitClass, Mat4X4Type, ViewAnglesType> requires CameraEngineConcept<TraitClass, Mat4X4Type, ViewAnglesType>
class Camera final class Camera final
{ {
friend UnitTestProjection_Projection_Test;
public: public:
~Camera() = default; ~Camera() = default;
Camera(const Vector3<float>& position, const ViewAnglesType& view_angles, const ViewPort& view_port, Camera(const Vector3<float>& position, const ViewAnglesType& view_angles, const ViewPort& view_port,
@@ -164,6 +168,31 @@ namespace omath::projection
return Vector3<float>{projected.at(0, 0), projected.at(1, 0), projected.at(2, 0)}; return Vector3<float>{projected.at(0, 0), projected.at(1, 0), projected.at(2, 0)};
} }
[[nodiscard]]
std::expected<Vector3<float>, Error> view_port_to_screen(const Vector3<float>& 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<float, Mat4X4Type::get_store_ordering()>(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<float>{inverted_projection.at(0, 0), inverted_projection.at(1, 0),
inverted_projection.at(2, 0)};
}
[[nodiscard]]
std::expected<Vector3<float>, Error> screen_to_world(const Vector3<float>& screen_pos) const noexcept
{
return view_port_to_screen(screen_to_dnc(screen_pos));
}
protected: protected:
ViewPort m_view_port{}; ViewPort m_view_port{};
@@ -186,7 +215,7 @@ namespace omath::projection
[[nodiscard]] Vector3<float> ndc_to_screen_position(const Vector3<float>& ndc) const noexcept [[nodiscard]] Vector3<float> ndc_to_screen_position(const Vector3<float>& ndc) const noexcept
{ {
/* /*
^ ^
| y | y
1 | 1 |
@@ -197,8 +226,14 @@ namespace omath::projection
| |
-1 | -1 |
v 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}; 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<float> screen_to_dnc(const Vector3<float>& 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 } // namespace omath::projection

View File

@@ -10,5 +10,6 @@ namespace omath::projection
enum class Error : uint16_t enum class Error : uint16_t
{ {
WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS, WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS,
INV_VIEW_PROJ_MAT_DET_EQ_ZERO,
}; };
} }

View File

@@ -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, const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
0.01f, 1000.f); 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->x, 960.f, 0.001f);
EXPECT_NEAR(projected->y, 504.f, 0.001f); EXPECT_NEAR(projected->y, 504.f, 0.001f);
EXPECT_NEAR(projected->z, 1.f, 0.001f); EXPECT_NEAR(projected->z, 1.f, 0.001f);