Compare commits

..

5 Commits

Author SHA1 Message Date
c364879b5f Merge pull request #68 from orange-cpp/feature/screen_to_world
Feature/screen to world
2025-09-09 02:58:32 +03:00
418b7c0e7e Fixes float type conversion in world_to_screen
Fixes a potential type conversion issue by explicitly casting the x-coordinate to float in the world_to_screen test. This prevents possible compiler warnings and ensures the intended behavior.
2025-09-09 02:13:45 +03:00
69f46abce1 Adds projection test for world-to-screen consistency
Adds a test to verify the consistency of world-to-screen and
screen-to-world projections. This ensures that projecting a point
from world to screen and back results in the same point, thereby
validating the correctness of the camera projection transformations.
2025-09-09 01:37:38 +03:00
de61f7a5d8 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.
2025-09-09 01:31:23 +03:00
07a449b633 Simplifies raycast early exit condition
Combines the infinite length raycast hit check into a single condition.

This clarifies the logic and avoids redundant checks for early exit
in the ray-triangle intersection test, improving performance.
2025-09-08 23:52:35 +03:00
5 changed files with 63 additions and 20 deletions

View File

@@ -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)

View File

@@ -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 <expected>
#include <omath/angle.hpp>
#include <type_traits>
#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<TraitClass, Mat4X4Type, ViewAnglesType>
class Camera final
{
#ifdef OMATH_BUILD_TESTS
friend UnitTestProjection_Projection_Test;
#endif
public:
~Camera() = default;
Camera(const Vector3<float>& position, const ViewAnglesType& view_angles, const ViewPort& view_port,
@@ -164,6 +172,31 @@ namespace omath::projection
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:
ViewPort m_view_port{};
@@ -186,19 +219,25 @@ namespace omath::projection
[[nodiscard]] Vector3<float> ndc_to_screen_position(const Vector3<float>& 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<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

View File

@@ -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,
};
}

View File

@@ -50,12 +50,10 @@ namespace omath::collision
const auto t_hit = side_b.dot(q) * inv_det;
if (ray.infinite_length)
{
if (t_hit <= k_epsilon)
return ray.end;
}
else if (t_hit <= k_epsilon || t_hit > 1.0f - k_epsilon)
if (ray.infinite_length && t_hit <= k_epsilon)
return ray.end;
if (t_hit <= k_epsilon || t_hit > 1.0f - k_epsilon)
return ray.end;
return ray.start + ray_dir * t_hit;

View File

@@ -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<omath::Vector2<float>>(projected.value()),
static_cast<omath::Vector2<float>>(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);