diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index 4b71f17..bce63f1 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -48,7 +48,7 @@ namespace omath::projection bool inverted_right = false; }; - template + 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, @@ -57,7 +57,7 @@ namespace omath::projection { 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, ndc_depth_range) } -> std::same_as; - + requires std::is_floating_point_v; // 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)); @@ -66,8 +66,8 @@ namespace omath::projection template - requires CameraEngineConcept + CameraAxes axes = {}, class NumericType = float> + requires CameraEngineConcept class Camera final { #ifdef OMATH_BUILD_TESTS @@ -84,7 +84,7 @@ namespace omath::projection ~Camera() = default; Camera(const Vector3& position, const ViewAnglesType& view_angles, const ViewPort& view_port, - const FieldOfView& fov, const float near, const float far) noexcept + const FieldOfView& fov, const NumericType near, const NumericType 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) { @@ -104,7 +104,7 @@ namespace omath::projection static ProjectionParams extract_projection_params(const Mat4X4Type& proj_matrix) noexcept { // m[1,1] == 1 / tan(fov/2) => fov = 2 * atan(1 / m[1,1]) - const float f = proj_matrix.at(1, 1); + const auto f = proj_matrix.at(1, 1); // m[0,0] == m[1,1] / aspect_ratio => aspect = m[1,1] / m[0,0] return {FieldOfView::from_radians(2.f * std::atan(1.f / f)), f / proj_matrix.at(0, 0)}; } @@ -112,14 +112,14 @@ namespace omath::projection [[nodiscard]] static ViewAnglesType calc_view_angles_from_view_matrix(const Mat4X4Type& view_matrix) noexcept { - Vector3 forward_vector = {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]}; + Vector3 forward_vector = {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]}; if constexpr (axes.inverted_forward) forward_vector = -forward_vector; return TraitClass::calc_look_at_angle({}, forward_vector); } [[nodiscard]] - static Vector3 calc_origin_from_view_matrix(const Mat4X4Type& view_matrix) noexcept + static Vector3 calc_origin_from_view_matrix(const Mat4X4Type& view_matrix) noexcept { // The view matrix is R * T(-origin), so the last column stores t = -R * origin. // Recovering origin: origin = -R^T * t @@ -130,40 +130,40 @@ namespace omath::projection }; } - void look_at(const Vector3& target) + void look_at(const Vector3& target) { m_view_angles = TraitClass::calc_look_at_angle(m_origin, target); m_view_projection_matrix = std::nullopt; m_view_matrix = std::nullopt; } [[nodiscard]] - ViewAnglesType calc_look_at_angles(const Vector3& look_to) const + ViewAnglesType calc_look_at_angles(const Vector3& look_to) const { return TraitClass::calc_look_at_angle(m_origin, look_to); } [[nodiscard]] - Vector3 get_forward() const noexcept + Vector3 get_forward() const noexcept { const auto& view_matrix = get_view_matrix(); return {view_matrix[2, 0], view_matrix[2, 1], view_matrix[2, 2]}; } [[nodiscard]] - Vector3 get_right() const noexcept + Vector3 get_right() const noexcept { const auto& view_matrix = get_view_matrix(); return {view_matrix[0, 0], view_matrix[0, 1], view_matrix[0, 2]}; } [[nodiscard]] - Vector3 get_up() const noexcept + Vector3 get_up() const noexcept { const auto& view_matrix = get_view_matrix(); return {view_matrix[1, 0], view_matrix[1, 1], view_matrix[1, 2]}; } [[nodiscard]] - Vector3 get_abs_forward() const noexcept + Vector3 get_abs_forward() const noexcept { if constexpr (axes.inverted_forward) return -get_forward(); @@ -171,7 +171,7 @@ namespace omath::projection } [[nodiscard]] - Vector3 get_abs_right() const noexcept + Vector3 get_abs_right() const noexcept { if constexpr (axes.inverted_right) return -get_right(); @@ -179,7 +179,7 @@ namespace omath::projection } [[nodiscard]] - Vector3 get_abs_up() const noexcept + Vector3 get_abs_up() const noexcept { return get_up(); } @@ -216,14 +216,14 @@ namespace omath::projection m_projection_matrix = std::nullopt; } - void set_near_plane(const float near_plane) noexcept + void set_near_plane(const NumericType near_plane) noexcept { m_near_plane_distance = near_plane; m_view_projection_matrix = std::nullopt; m_projection_matrix = std::nullopt; } - void set_far_plane(const float far_plane) noexcept + void set_far_plane(const NumericType far_plane) noexcept { m_far_plane_distance = far_plane; m_view_projection_matrix = std::nullopt; @@ -237,7 +237,7 @@ namespace omath::projection m_view_matrix = std::nullopt; } - void set_origin(const Vector3& origin) noexcept + void set_origin(const Vector3& origin) noexcept { m_origin = origin; m_view_projection_matrix = std::nullopt; @@ -255,12 +255,12 @@ namespace omath::projection return m_field_of_view; } - [[nodiscard]] const float& get_near_plane() const noexcept + [[nodiscard]] const NumericType& get_near_plane() const noexcept { return m_near_plane_distance; } - [[nodiscard]] const float& get_far_plane() const noexcept + [[nodiscard]] const NumericType& get_far_plane() const noexcept { return m_far_plane_distance; } @@ -270,14 +270,14 @@ namespace omath::projection return m_view_angles; } - [[nodiscard]] const Vector3& get_origin() const noexcept + [[nodiscard]] const Vector3& get_origin() const noexcept { return m_origin; } template - [[nodiscard]] std::expected, Error> - world_to_screen(const Vector3& world_position) const noexcept + [[nodiscard]] std::expected, Error> + world_to_screen(const Vector3& world_position) const noexcept { const auto normalized_cords = world_to_view_port(world_position); @@ -292,7 +292,7 @@ namespace omath::projection std::unreachable(); } template - [[nodiscard]] std::expected, Error> + [[nodiscard]] std::expected, Error> world_to_screen_unclipped(const Vector3& world_position) const noexcept { const auto normalized_cords = world_to_view_port(world_position, ViewPortClipping::MANUAL); @@ -308,14 +308,14 @@ namespace omath::projection std::unreachable(); } - [[nodiscard]] bool is_culled_by_frustum(const Triangle>& triangle) const noexcept + [[nodiscard]] bool is_culled_by_frustum(const Triangle>& triangle) const noexcept { // Transform to clip space (before perspective divide) - auto to_clip = [this](const Vector3& point) + auto to_clip = [this](const Vector3& point) { auto clip = get_view_projection_matrix() - * mat_column_from_vector(point); - return std::array{ + * mat_column_from_vector(point); + return std::array{ clip.at(0, 0), // x clip.at(1, 0), // y clip.at(2, 0), // z @@ -332,8 +332,8 @@ namespace omath::projection return true; // Helper: all three vertices outside the same clip plane - auto all_outside_plane = [](const int axis, const std::array& a, const std::array& b, - const std::array& c, const bool positive_side) + auto all_outside_plane = [](const int axis, const std::array& a, const std::array& b, + const std::array& c, const bool positive_side) { if (positive_side) return a[axis] > a[3] && b[axis] > b[3] && c[axis] > c[3]; @@ -374,7 +374,7 @@ namespace omath::projection return false; } - [[nodiscard]] bool is_aabb_culled_by_frustum(const primitives::Aabb& aabb) const noexcept + [[nodiscard]] bool is_aabb_culled_by_frustum(const primitives::Aabb& aabb) const noexcept { const auto& m = get_view_projection_matrix(); @@ -420,9 +420,9 @@ namespace omath::projection // (the "positive vertex"). If it's outside, the entire AABB is outside. for (const auto& [a, b, c, d] : planes) { - const float px = a >= 0.f ? aabb.max.x : aabb.min.x; - const float py = b >= 0.f ? aabb.max.y : aabb.min.y; - const float pz = c >= 0.f ? aabb.max.z : aabb.min.z; + const auto px = a >= 0.f ? aabb.max.x : aabb.min.x; + const auto py = b >= 0.f ? aabb.max.y : aabb.min.y; + const auto pz = c >= 0.f ? aabb.max.z : aabb.min.z; if (a * px + b * py + c * pz + d < 0.f) return true; @@ -431,15 +431,15 @@ namespace omath::projection return false; } - [[nodiscard]] std::expected, Error> - world_to_view_port(const Vector3& world_position, + [[nodiscard]] std::expected, Error> + world_to_view_port(const Vector3& world_position, const ViewPortClipping& clipping = ViewPortClipping::AUTO) const noexcept { auto projected = get_view_projection_matrix() - * mat_column_from_vector(world_position); + * mat_column_from_vector(world_position); const auto& w = projected.at(3, 0); - constexpr auto eps = std::numeric_limits::epsilon(); + constexpr auto eps = std::numeric_limits::epsilon(); if (w <= eps) return std::unexpected(Error::PERSPECTIVE_DIVIDER_LESS_EQ_ZERO); @@ -460,7 +460,7 @@ namespace omath::projection return Vector3{projected.at(0, 0), projected.at(1, 0), projected.at(2, 0)}; } [[nodiscard]] - std::expected, Error> view_port_to_world(const Vector3& ndc) const noexcept + std::expected, Error> view_port_to_world(const Vector3& ndc) const noexcept { const auto inv_view_proj = get_view_projection_matrix().inverted(); @@ -468,29 +468,29 @@ namespace omath::projection return std::unexpected(Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO); auto inverted_projection = - inv_view_proj.value() * mat_column_from_vector(ndc); + inv_view_proj.value() * mat_column_from_vector(ndc); const auto& w = inverted_projection.at(3, 0); - if (std::abs(w) < std::numeric_limits::epsilon()) + if (std::abs(w) < std::numeric_limits::epsilon()) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); inverted_projection /= w; - return Vector3{inverted_projection.at(0, 0), inverted_projection.at(1, 0), + return Vector3{inverted_projection.at(0, 0), inverted_projection.at(1, 0), inverted_projection.at(2, 0)}; } template [[nodiscard]] - std::expected, Error> screen_to_world(const Vector3& screen_pos) const noexcept + std::expected, Error> screen_to_world(const Vector3& screen_pos) const noexcept { return view_port_to_world(screen_to_ndc(screen_pos)); } template [[nodiscard]] - std::expected, Error> screen_to_world(const Vector2& screen_pos) const noexcept + 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}); @@ -498,7 +498,7 @@ namespace omath::projection protected: ViewPort m_view_port{}; - Angle m_field_of_view; + Angle m_field_of_view; mutable std::optional m_view_projection_matrix; mutable std::optional m_projection_matrix; @@ -507,13 +507,13 @@ namespace omath::projection float m_near_plane_distance; ViewAnglesType m_view_angles; - Vector3 m_origin; + Vector3 m_origin; private: template [[nodiscard]] constexpr static bool is_ndc_out_of_bounds(const Type& ndc) noexcept { - constexpr auto eps = std::numeric_limits::epsilon(); + constexpr auto eps = std::numeric_limits::epsilon(); const auto& data = ndc.raw_array(); // x and y are always in [-1, 1]