// // Created by Vlad on 27.08.2024. // #pragma once #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 { public: float m_width; float m_height; [[nodiscard]] constexpr float aspect_ratio() const { return m_width / m_height; } }; using FieldOfView = Angle; template concept CameraEngineConcept = requires(const Vector3& cam_origin, const Vector3& look_at, const ViewAnglesType& angles, const FieldOfView& fov, const ViewPort& viewport, float znear, float zfar) { // Presence + return types { T::calc_look_at_angle(cam_origin, look_at) } -> std::same_as; { T::calc_view_matrix(angles, cam_origin) } -> std::same_as; { T::calc_projection_matrix(fov, viewport, znear, zfar) } -> std::same_as; // Enforce noexcept as in the trait declaration requires noexcept(T::calc_look_at_angle(cam_origin, look_at)); requires noexcept(T::calc_view_matrix(angles, cam_origin)); requires noexcept(T::calc_projection_matrix(fov, viewport, znear, zfar)); }; template 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, const FieldOfView& fov, const float near, const float far) noexcept : m_view_port(view_port), m_field_of_view(fov), m_far_plane_distance(far), m_near_plane_distance(near), m_view_angles(view_angles), m_origin(position) { } void look_at(const Vector3& target) { m_view_angles = TraitClass::calc_look_at_angle(m_origin, target); m_view_projection_matrix = std::nullopt; } protected: [[nodiscard]] Mat4X4Type calc_view_projection_matrix() const noexcept { return TraitClass::calc_projection_matrix(m_field_of_view, m_view_port, m_near_plane_distance, m_far_plane_distance) * TraitClass::calc_view_matrix(m_view_angles, m_origin); } public: [[nodiscard]] const Mat4X4Type& get_view_projection_matrix() const noexcept { if (!m_view_projection_matrix.has_value()) m_view_projection_matrix = calc_view_projection_matrix(); return m_view_projection_matrix.value(); } void set_field_of_view(const FieldOfView& fov) noexcept { m_field_of_view = fov; m_view_projection_matrix = std::nullopt; } void set_near_plane(const float near) noexcept { m_near_plane_distance = near; m_view_projection_matrix = std::nullopt; } void set_far_plane(const float far) noexcept { m_far_plane_distance = far; m_view_projection_matrix = std::nullopt; } void set_view_angles(const ViewAnglesType& view_angles) noexcept { m_view_angles = view_angles; m_view_projection_matrix = std::nullopt; } void set_origin(const Vector3& origin) noexcept { m_origin = origin; m_view_projection_matrix = std::nullopt; } void set_view_port(const ViewPort& view_port) noexcept { m_view_port = view_port; m_view_projection_matrix = std::nullopt; } [[nodiscard]] const FieldOfView& get_field_of_view() const noexcept { return m_field_of_view; } [[nodiscard]] const float& get_near_plane() const noexcept { return m_near_plane_distance; } [[nodiscard]] const float& get_far_plane() const noexcept { return m_far_plane_distance; } [[nodiscard]] const ViewAnglesType& get_view_angles() const noexcept { return m_view_angles; } [[nodiscard]] const Vector3& get_origin() const noexcept { return m_origin; } [[nodiscard]] std::expected, Error> world_to_screen(const Vector3& world_position) const noexcept { auto normalized_cords = world_to_view_port(world_position); if (!normalized_cords.has_value()) return std::unexpected{normalized_cords.error()}; return ndc_to_screen_position(*normalized_cords); } [[nodiscard]] std::expected, Error> world_to_view_port(const Vector3& world_position) const noexcept { auto projected = get_view_projection_matrix() * mat_column_from_vector(world_position); if (projected.at(3, 0) == 0.0f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); projected /= projected.at(3, 0); if (is_ndc_out_of_bounds(projected)) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); 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_ndc(screen_pos)); } [[nodiscard]] std::expected, Error> screen_to_world(const Vector2& screen_pos) const noexcept { const auto& [x, y] = screen_pos; return screen_to_world({x, y, 1.f}); } protected: ViewPort m_view_port{}; Angle m_field_of_view; mutable std::optional m_view_projection_matrix; float m_far_plane_distance; float m_near_plane_distance; ViewAnglesType m_view_angles; Vector3 m_origin; private: template [[nodiscard]] constexpr static bool is_ndc_out_of_bounds(const Type& ndc) noexcept { return std::ranges::any_of(ndc.raw_array(), [](const auto& val) { return val < -1 || val > 1; }); } [[nodiscard]] Vector3 ndc_to_screen_position(const Vector3& ndc) const noexcept { /* ^ | y 1 | | | -1 ---------0--------- 1 --> x | | -1 | v */ return {(ndc.x + 1.f) / 2.f * m_view_port.m_width, (ndc.y / 2.f + 0.5f) * m_view_port.m_height, ndc.z}; } [[nodiscard]] Vector3 screen_to_ndc(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