diff --git a/include/omath/engines/cry_engine/camera.hpp b/include/omath/engines/cry_engine/camera.hpp new file mode 100644 index 00000000..0db847fe --- /dev/null +++ b/include/omath/engines/cry_engine/camera.hpp @@ -0,0 +1,13 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once +#include "omath/engines/cry_engine/constants.hpp" +#include "omath/projection/camera.hpp" +#include "traits/camera_trait.hpp" + +namespace omath::cry_engine +{ + using Camera = projection::Camera; +} // namespace omath::cry_engine \ No newline at end of file diff --git a/include/omath/engines/cry_engine/constants.hpp b/include/omath/engines/cry_engine/constants.hpp new file mode 100644 index 00000000..762d0404 --- /dev/null +++ b/include/omath/engines/cry_engine/constants.hpp @@ -0,0 +1,25 @@ +// +// Created by Vlad on 10/21/2025. +// + +#pragma once +#include "omath/linear_algebra/mat.hpp" +#include "omath/linear_algebra/vector3.hpp" +#include +#include + +namespace omath::cry_engine +{ + constexpr Vector3 k_abs_up = {0, 0, 1}; + constexpr Vector3 k_abs_right = {1, 0, 0}; + constexpr Vector3 k_abs_forward = {0, 1, 0}; + + using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; + using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; + using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>; + using PitchAngle = Angle; + using YawAngle = Angle; + using RollAngle = Angle; + + using ViewAngles = omath::ViewAngles; +} // namespace omath::cry_engine \ No newline at end of file diff --git a/include/omath/engines/cry_engine/formulas.hpp b/include/omath/engines/cry_engine/formulas.hpp new file mode 100644 index 00000000..7a7c8152 --- /dev/null +++ b/include/omath/engines/cry_engine/formulas.hpp @@ -0,0 +1,74 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once +#include "omath/engines/cry_engine/constants.hpp" + +namespace omath::cry_engine +{ + [[nodiscard]] + Vector3 forward_vector(const ViewAngles& angles) noexcept; + + [[nodiscard]] + Vector3 right_vector(const ViewAngles& angles) noexcept; + + [[nodiscard]] + Vector3 up_vector(const ViewAngles& angles) noexcept; + + [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept; + + [[nodiscard]] + Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; + + [[nodiscard]] + Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; + + template + requires std::is_floating_point_v + [[nodiscard]] + constexpr FloatingType units_to_centimeters(const FloatingType& units) + { + return units / static_cast(100); + } + + template + requires std::is_floating_point_v + [[nodiscard]] + constexpr FloatingType units_to_meters(const FloatingType& units) + { + return units; + } + + template + requires std::is_floating_point_v + [[nodiscard]] + constexpr FloatingType units_to_kilometers(const FloatingType& units) + { + return units_to_meters(units) / static_cast(1000); + } + + template + requires std::is_floating_point_v + [[nodiscard]] + constexpr FloatingType centimeters_to_units(const FloatingType& centimeters) + { + return centimeters * static_cast(100); + } + + template + requires std::is_floating_point_v + [[nodiscard]] + constexpr FloatingType meters_to_units(const FloatingType& meters) + { + return meters; + } + + template + requires std::is_floating_point_v + [[nodiscard]] + constexpr FloatingType kilometers_to_units(const FloatingType& kilometers) + { + return meters_to_units(kilometers * static_cast(1000)); + } +} // namespace omath::cry_engine diff --git a/include/omath/engines/cry_engine/mesh.hpp b/include/omath/engines/cry_engine/mesh.hpp new file mode 100644 index 00000000..9695f0c7 --- /dev/null +++ b/include/omath/engines/cry_engine/mesh.hpp @@ -0,0 +1,12 @@ +// +// Created by Vladislav on 09.11.2025. +// +#pragma once +#include "constants.hpp" +#include "omath/3d_primitives/mesh.hpp" +#include "traits/mesh_trait.hpp" + +namespace omath::cry_engine +{ + using Mesh = primitives::Mesh; +} \ No newline at end of file diff --git a/include/omath/engines/cry_engine/traits/camera_trait.hpp b/include/omath/engines/cry_engine/traits/camera_trait.hpp new file mode 100644 index 00000000..e59f2a9c --- /dev/null +++ b/include/omath/engines/cry_engine/traits/camera_trait.hpp @@ -0,0 +1,24 @@ +// +// Created by Vlad on 8/10/2025. +// + +#pragma once +#include "omath/engines/cry_engine/formulas.hpp" +#include "omath/projection/camera.hpp" + +namespace omath::cry_engine +{ + class CameraTrait final + { + public: + [[nodiscard]] + static ViewAngles calc_look_at_angle(const Vector3& cam_origin, const Vector3& look_at) noexcept; + + [[nodiscard]] + static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept; + [[nodiscard]] + static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port, + float near, float far) noexcept; + }; + +} // namespace omath::cry_engine \ No newline at end of file diff --git a/include/omath/engines/cry_engine/traits/mesh_trait.hpp b/include/omath/engines/cry_engine/traits/mesh_trait.hpp new file mode 100644 index 00000000..200c0687 --- /dev/null +++ b/include/omath/engines/cry_engine/traits/mesh_trait.hpp @@ -0,0 +1,19 @@ +// +// Created by Vladislav on 09.11.2025. +// +#pragma once +#include +#include + +namespace omath::cry_engine +{ + class MeshTrait final + { + public: + [[nodiscard]] + static Mat4X4 rotation_matrix(const ViewAngles& rotation) + { + return cry_engine::rotation_matrix(rotation); + } + }; +} // namespace omath::cry_engine \ No newline at end of file diff --git a/include/omath/engines/cry_engine/traits/pred_engine_trait.hpp b/include/omath/engines/cry_engine/traits/pred_engine_trait.hpp new file mode 100644 index 00000000..1e3621fb --- /dev/null +++ b/include/omath/engines/cry_engine/traits/pred_engine_trait.hpp @@ -0,0 +1,76 @@ +// +// Created by Vlad on 8/6/2025. +// +#pragma once +#include "omath/engines/cry_engine/formulas.hpp" +#include "omath/projectile_prediction/projectile.hpp" +#include "omath/projectile_prediction/target.hpp" +#include + +namespace omath::cry_engine +{ + class PredEngineTrait final + { + public: + constexpr static Vector3 predict_projectile_position(const projectile_prediction::Projectile& projectile, + const float pitch, const float yaw, + const float time, const float gravity) noexcept + { + auto current_pos = projectile.m_origin + + forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw), + RollAngle::from_degrees(0)}) + * projectile.m_launch_speed * time; + current_pos.z -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f; + + return current_pos; + } + [[nodiscard]] + static constexpr Vector3 predict_target_position(const projectile_prediction::Target& target, + const float time, const float gravity) noexcept + { + auto predicted = target.m_origin + target.m_velocity * time; + + if (target.m_is_airborne) + predicted.z -= gravity * (time * time) * 0.5f; + + return predicted; + } + [[nodiscard]] + static float calc_vector_2d_distance(const Vector3& delta) noexcept + { + return std::sqrt(delta.x * delta.x + delta.y * delta.y); + } + + [[nodiscard]] + constexpr static float get_vector_height_coordinate(const Vector3& vec) noexcept + { + return vec.z; + } + + [[nodiscard]] + static Vector3 calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile, + Vector3 predicted_target_position, + const std::optional projectile_pitch) noexcept + { + const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin); + const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value())); + + return {predicted_target_position.x, predicted_target_position.y, projectile.m_origin.z + height}; + } + // Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be: + // 89 look up, -89 look down + [[nodiscard]] + static float calc_direct_pitch_angle(const Vector3& origin, const Vector3& view_to) noexcept + { + const auto direction = (view_to - origin).normalized(); + return angles::radians_to_degrees(std::asin(direction.z)); + } + [[nodiscard]] + static float calc_direct_yaw_angle(const Vector3& origin, const Vector3& view_to) noexcept + { + const auto direction = (view_to - origin).normalized(); + + return angles::radians_to_degrees(-std::atan2(direction.x, direction.y)); + }; + }; +} // namespace omath::cry_engine diff --git a/include/omath/linear_algebra/vector2.hpp b/include/omath/linear_algebra/vector2.hpp index 2ee37503..51d30791 100644 --- a/include/omath/linear_algebra/vector2.hpp +++ b/include/omath/linear_algebra/vector2.hpp @@ -220,6 +220,12 @@ namespace omath { return std::make_tuple(x, y); } + + [[nodiscard]] + constexpr std::array as_array() const noexcept + { + return {x, y}; + } #ifdef OMATH_IMGUI_INTEGRATION [[nodiscard]] constexpr ImVec2 to_im_vec2() const noexcept diff --git a/include/omath/linear_algebra/vector3.hpp b/include/omath/linear_algebra/vector3.hpp index ab481f2c..2845ff77 100644 --- a/include/omath/linear_algebra/vector3.hpp +++ b/include/omath/linear_algebra/vector3.hpp @@ -4,8 +4,8 @@ #pragma once -#include "omath/trigonometry/angle.hpp" #include "omath/linear_algebra/vector2.hpp" +#include "omath/trigonometry/angle.hpp" #include #include #include @@ -233,7 +233,8 @@ namespace omath return Angle::from_radians(std::acos(dot(other) / bottom)); } - [[nodiscard]] bool is_perpendicular(const Vector3& other, Type epsilon = static_cast(0.0001)) const noexcept + [[nodiscard]] bool is_perpendicular(const Vector3& other, + Type epsilon = static_cast(0.0001)) const noexcept { if (const auto angle = angle_between(other)) return std::abs(angle->as_degrees() - static_cast(90)) <= epsilon; @@ -274,6 +275,12 @@ namespace omath { return length() >= other.length(); } + + [[nodiscard]] + constexpr std::array as_array() const noexcept + { + return {this->x, this->y, z}; + } }; } // namespace omath diff --git a/include/omath/linear_algebra/vector4.hpp b/include/omath/linear_algebra/vector4.hpp index f045df9a..14e20678 100644 --- a/include/omath/linear_algebra/vector4.hpp +++ b/include/omath/linear_algebra/vector4.hpp @@ -3,8 +3,8 @@ // #pragma once -#include #include "omath/linear_algebra/vector3.hpp" +#include namespace omath { @@ -183,6 +183,12 @@ namespace omath return length() >= other.length(); } + [[nodiscard]] + constexpr std::array as_array() const noexcept + { + return {this->x, this->y, this->z, w}; + } + #ifdef OMATH_IMGUI_INTEGRATION [[nodiscard]] constexpr ImVec4 to_im_vec4() const noexcept @@ -200,7 +206,7 @@ namespace omath return {static_cast(other.x), static_cast(other.y), static_cast(other.z)}; } #endif -}; + }; } // namespace omath template<> struct std::hash> diff --git a/source/engines/cry_engine/camera_trait.cpp b/source/engines/cry_engine/camera_trait.cpp new file mode 100644 index 00000000..ac912f8a --- /dev/null +++ b/source/engines/cry_engine/camera_trait.cpp @@ -0,0 +1,26 @@ +// +// Created by Vlad on 8/11/2025. +// +#include "omath/engines/cry_engine/traits/camera_trait.hpp" + +namespace omath::cry_engine +{ + + ViewAngles CameraTrait::calc_look_at_angle(const Vector3& cam_origin, const Vector3& look_at) noexcept + { + const auto direction = (look_at - cam_origin).normalized(); + + return {PitchAngle::from_radians(std::asin(direction.z)), + YawAngle::from_radians(-std::atan2(direction.x, direction.y)), RollAngle::from_radians(0.f)}; + } + Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept + { + return cry_engine::calc_view_matrix(angles, cam_origin); + } + Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov, + const projection::ViewPort& view_port, const float near, + const float far) noexcept + { + return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far); + } +} // namespace omath::unity_engine \ No newline at end of file diff --git a/source/engines/cry_engine/formulas.cpp b/source/engines/cry_engine/formulas.cpp new file mode 100644 index 00000000..605b3d03 --- /dev/null +++ b/source/engines/cry_engine/formulas.cpp @@ -0,0 +1,42 @@ +// +// Created by Vlad on 3/22/2025. +// +#include "omath/engines/cry_engine/formulas.hpp" + +namespace omath::cry_engine +{ + Vector3 forward_vector(const ViewAngles& angles) noexcept + { + const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_forward); + + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; + } + Vector3 right_vector(const ViewAngles& angles) noexcept + { + const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right); + + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; + } + Vector3 up_vector(const ViewAngles& angles) noexcept + { + const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_up); + + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; + } + Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept + { + return mat_camera_view(forward_vector(angles), right_vector(angles), + up_vector(angles), cam_origin); + } + Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept + { + return mat_rotation_axis_z(angles.yaw) + * mat_rotation_axis_y(angles.roll) + * mat_rotation_axis_x(angles.pitch); + } + Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, + const float far) noexcept + { + return mat_perspective_left_handed(field_of_view, aspect_ratio, near, far); + } +} // namespace omath::unity_engine diff --git a/tests/engines/unit_test_cry_engine.cpp b/tests/engines/unit_test_cry_engine.cpp new file mode 100644 index 00000000..a811fe87 --- /dev/null +++ b/tests/engines/unit_test_cry_engine.cpp @@ -0,0 +1,240 @@ +// +// Created by Vladislav on 19.02.2026. +// +#include +#include +#include +#include +#include +#include + +using namespace omath; +TEST(unit_test_cry_engine, look_at_forward) +{ + const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = cry_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), cry_engine::k_abs_forward.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_cry_engine, look_at_right) +{ + const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = cry_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), cry_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_cry_engine, look_at_up) +{ + const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = cry_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), cry_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} + +TEST(unit_test_cry_engine, look_at_back) +{ + const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, -cry_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = cry_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-cry_engine::k_abs_forward).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_cry_engine, look_at_left) +{ + const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, -cry_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = cry_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-cry_engine::k_abs_right).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_cry_engine, look_at_down) +{ + const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, -cry_engine::k_abs_up); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = cry_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-cry_engine::k_abs_up).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} + +TEST(unit_test_cry_engine, RightVector) +{ + const auto right = omath::cry_engine::right_vector({}); + + EXPECT_EQ(right, omath::cry_engine::k_abs_right); +} + +TEST(unit_test_cry_engine, UpVector) +{ + const auto up = omath::cry_engine::up_vector({}); + EXPECT_EQ(up, omath::cry_engine::k_abs_up); +} + +TEST(unit_test_cry_engine, ProjectTargetMovedFromCamera) +{ + constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f); + const auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.01f, 1000.f); + + for (float distance = 0.02f; distance < 100.f; distance += 0.01f) + { + const auto projected = cam.world_to_screen({0, distance, 0}); + + EXPECT_TRUE(projected.has_value()); + + if (!projected.has_value()) + continue; + + EXPECT_NEAR(projected->x, 640, 0.00001f); + EXPECT_NEAR(projected->y, 360, 0.00001f); + } +} + +TEST(unit_test_cry_engine, CameraSetAndGetFov) +{ + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); + auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 90.f); + cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(50.f)); + + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); +} + +TEST(unit_test_cry_engine, CameraSetAndGetOrigin) +{ + auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); + + EXPECT_EQ(cam.get_origin(), omath::Vector3{}); + cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(50.f)); + + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); +} +TEST(unit_test_cry_engine, loook_at_random_all_axis) +{ + std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source + std::uniform_real_distribution dist(-1000.f, 1000.f); + + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); + auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.001f, 10000.f); + + std::size_t failed_points = 0; + + for (int i = 0; i < 1000; i++) + { + const auto position_to_look = omath::Vector3{dist(gen), dist(gen), dist(gen)}; + + if (cam.get_origin().distance_to(position_to_look) < 10) + continue; + + cam.look_at(position_to_look); + + auto projected_pos = cam.world_to_view_port(position_to_look); + + EXPECT_TRUE(projected_pos.has_value()); + + if (!projected_pos) + continue; + + if (std::abs(projected_pos->x - 0.f) >= 0.0001f || std::abs(projected_pos->y - 0.f) >= 0.0001f) + failed_points++; + } + EXPECT_LE(failed_points, 100); +} + +TEST(unit_test_cry_engine, loook_at_random_x_axis) +{ + std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source + std::uniform_real_distribution dist(-1000.f, 1000.f); + + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); + auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.001f, 10000.f); + + std::size_t failed_points = 0; + for (int i = 0; i < 1000; i++) + { + const auto position_to_look = omath::Vector3{dist(gen), 0.f, 0.f}; + if (cam.get_origin().distance_to(position_to_look) < 10) + continue; + + cam.look_at(position_to_look); + + auto projected_pos = cam.world_to_view_port(position_to_look); + + EXPECT_TRUE(projected_pos.has_value()); + + if (!projected_pos) + continue; + + if (std::abs(projected_pos->x - 0.f) >= 0.001f || std::abs(projected_pos->y - 0.f) >= 0.001f) + failed_points++; + } + EXPECT_LE(failed_points, 100); +} + +TEST(unit_test_cry_engine, loook_at_random_y_axis) +{ + std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source + std::uniform_real_distribution dist(-1000.f, 1000.f); + + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); + auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.001f, 10000.f); + + std::size_t failed_points = 0; + for (int i = 0; i < 1000; i++) + { + const auto position_to_look = omath::Vector3{0.f, dist(gen), 0.f}; + if (cam.get_origin().distance_to(position_to_look) < 10) + continue; + + cam.look_at(position_to_look); + + auto projected_pos = cam.world_to_view_port(position_to_look); + + EXPECT_TRUE(projected_pos.has_value()); + + if (!projected_pos) + continue; + + if (std::abs(projected_pos->x - 0.f) >= 0.01f || std::abs(projected_pos->y - 0.f) >= 0.01f) + failed_points++; + } + EXPECT_LE(failed_points, 100); +} + +TEST(unit_test_cry_engine, loook_at_random_z_axis) +{ + std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source + std::uniform_real_distribution dist(-1000.f, 1000.f); + + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); + auto cam = omath::cry_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.001f, 10000.f); + + std::size_t failed_points = 0; + for (int i = 0; i < 1000; i++) + { + const auto position_to_look = omath::Vector3{0.f, 0.f, dist(gen)}; + if (cam.get_origin().distance_to(position_to_look) < 10) + continue; + + cam.look_at(position_to_look); + + auto projected_pos = cam.world_to_view_port(position_to_look); + + EXPECT_TRUE(projected_pos.has_value()); + + if (!projected_pos) + continue; + + if (std::abs(projected_pos->x - 0.f) >= 0.01f || std::abs(projected_pos->y - 0.f) >= 0.01f) + failed_points++; + } + EXPECT_LE(failed_points, 100); +} \ No newline at end of file diff --git a/tests/engines/unit_test_frostbite_engine.cpp b/tests/engines/unit_test_frostbite_engine.cpp index 7f6a6f3b..5a2ee167 100644 --- a/tests/engines/unit_test_frostbite_engine.cpp +++ b/tests/engines/unit_test_frostbite_engine.cpp @@ -7,6 +7,7 @@ #include #include #include +#include TEST(unit_test_frostbite_engine, UnitsToCentimeters_BasicValues) { @@ -352,4 +353,55 @@ TEST(unit_test_frostbite_engine, loook_at_random_z_axis) failed_points++; } EXPECT_LE(failed_points, 100); -} \ No newline at end of file +} +TEST(unit_test_frostbite_engine, look_at_right) +{ + const auto angles = omath::frostbite_engine::CameraTrait::calc_look_at_angle({}, omath::frostbite_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::frostbite_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), omath::frostbite_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_frostbite_engine, look_at_up) +{ + const auto angles = omath::frostbite_engine::CameraTrait::calc_look_at_angle({}, omath::frostbite_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::frostbite_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), omath::frostbite_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} + +TEST(unit_test_frostbite_engine, look_at_back) +{ + const auto angles = omath::frostbite_engine::CameraTrait::calc_look_at_angle({}, -omath::frostbite_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::frostbite_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), (-omath::frostbite_engine::k_abs_forward).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_frostbite_engine, look_at_left) +{ + const auto angles = omath::frostbite_engine::CameraTrait::calc_look_at_angle({}, -omath::frostbite_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::frostbite_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), (-omath::frostbite_engine::k_abs_right).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_frostbite_engine, look_at_down) +{ + const auto angles = omath::frostbite_engine::CameraTrait::calc_look_at_angle({}, -omath::frostbite_engine::k_abs_up); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::frostbite_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), (-omath::frostbite_engine::k_abs_up).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} diff --git a/tests/engines/unit_test_iw_engine.cpp b/tests/engines/unit_test_iw_engine.cpp index ed274578..44f2d381 100644 --- a/tests/engines/unit_test_iw_engine.cpp +++ b/tests/engines/unit_test_iw_engine.cpp @@ -6,6 +6,7 @@ #include #include #include +#include TEST(unit_test_iw_engine, ForwardVector) { @@ -223,4 +224,60 @@ TEST(unit_test_iw_engine, loook_at_random_z_axis) failed_points++; } EXPECT_LE(failed_points, 100); +} +TEST(unit_test_iw_engine, look_at_forward) +{ + const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, omath::iw_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::iw_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::iw_engine::k_abs_forward.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_iw_engine, look_at_right) +{ + const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, omath::iw_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::iw_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::iw_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_iw_engine, look_at_up) +{ + const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, omath::iw_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::iw_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::iw_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} + +TEST(unit_test_iw_engine, look_at_back) +{ + const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, -omath::iw_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::iw_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::iw_engine::k_abs_forward).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_iw_engine, look_at_left) +{ + const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, -omath::iw_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::iw_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::iw_engine::k_abs_right).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_iw_engine, look_at_down) +{ + const auto angles = omath::iw_engine::CameraTrait::calc_look_at_angle({}, -omath::iw_engine::k_abs_up); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::iw_engine::forward_vector(angles); + EXPECT_NEAR(dir_vector.z, -0.99984f, 0.0001f); + EXPECT_NEAR(dir_vector.x,- 0.017f, 0.01f); + EXPECT_NEAR(dir_vector.y, 0.f, 0.001f); } \ No newline at end of file diff --git a/tests/engines/unit_test_open_gl.cpp b/tests/engines/unit_test_open_gl.cpp index eb8a8f94..f784e345 100644 --- a/tests/engines/unit_test_open_gl.cpp +++ b/tests/engines/unit_test_open_gl.cpp @@ -6,6 +6,7 @@ #include #include #include +#include TEST(unit_test_opengl, UnitsToCentimeters_BasicValues) { @@ -337,4 +338,60 @@ TEST(unit_test_opengl_engine, loook_at_random_z_axis) failed_points++; } EXPECT_LE(failed_points, 100); +} + +TEST(unit_test_opengl_engine, look_at_forward) +{ + const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, omath::opengl_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::opengl_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::opengl_engine::k_abs_forward.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_opengl_engine, look_at_right) +{ + const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, omath::opengl_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::opengl_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::opengl_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_opengl_engine, look_at_up) +{ + const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, omath::opengl_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::opengl_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::opengl_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} + +TEST(unit_test_opengl_engine, look_at_back) +{ + const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, -omath::opengl_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::opengl_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::opengl_engine::k_abs_forward).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_opengl_engine, look_at_left) +{ + const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, -omath::opengl_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::opengl_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::opengl_engine::k_abs_right).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_opengl_engine, look_at_down) +{ + const auto angles = omath::opengl_engine::CameraTrait::calc_look_at_angle({}, -omath::opengl_engine::k_abs_up); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::opengl_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::opengl_engine::k_abs_up).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); } \ No newline at end of file diff --git a/tests/engines/unit_test_source_engine.cpp b/tests/engines/unit_test_source_engine.cpp index 4e3510fa..f311185c 100644 --- a/tests/engines/unit_test_source_engine.cpp +++ b/tests/engines/unit_test_source_engine.cpp @@ -6,6 +6,7 @@ #include #include #include +#include TEST(unit_test_source_engine_units, HammerUnitsToCentimeters_BasicValues) { @@ -365,4 +366,60 @@ TEST(unit_test_source_engine, loook_at_random_z_axis) failed_points++; } EXPECT_LE(failed_points, 100); +} +TEST(unit_test_source_engine, look_at_forward) +{ + const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, omath::source_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::source_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::source_engine::k_abs_forward.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_source_engine, look_at_right) +{ + const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, omath::source_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::source_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::source_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_source_engine, look_at_up) +{ + const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, omath::source_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::source_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::source_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} + +TEST(unit_test_source_engine, look_at_back) +{ + const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, -omath::source_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::source_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::source_engine::k_abs_forward).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_source_engine, look_at_left) +{ + const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, -omath::source_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::source_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::source_engine::k_abs_right).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_source_engine, look_at_down) +{ + const auto angles = omath::source_engine::CameraTrait::calc_look_at_angle({}, -omath::source_engine::k_abs_up); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::source_engine::forward_vector(angles); + EXPECT_NEAR(dir_vector.z, -0.99984f, 0.0001f); + EXPECT_NEAR(dir_vector.x,- 0.017f, 0.01f); + EXPECT_NEAR(dir_vector.y, 0.f, 0.001f); } \ No newline at end of file diff --git a/tests/engines/unit_test_unity_engine.cpp b/tests/engines/unit_test_unity_engine.cpp index ac577653..6621349f 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -7,6 +7,7 @@ #include #include #include +#include TEST(unit_test_unity_engine, UnitsToCentimeters_BasicValues) { @@ -207,7 +208,8 @@ TEST(unit_test_unity_engine, Project) constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f); const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 1000.f); - const auto proj = cam.world_to_screen({10.f, 3, 10.f}); + const auto proj = + cam.world_to_screen({10.f, 3, 10.f}); EXPECT_NEAR(proj->x, 1263.538, 0.001f); EXPECT_NEAR(proj->y, 547.061f, 0.001f); @@ -353,4 +355,65 @@ TEST(unit_test_unity_engine, loook_at_random_z_axis) failed_points++; } EXPECT_LE(failed_points, 100); -} \ No newline at end of file +} +TEST(unit_test_unity_engine, look_at_forward) +{ + const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, omath::unity_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unity_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), omath::unity_engine::k_abs_forward.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_unity_engine, look_at_right) +{ + const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, omath::unity_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unity_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), omath::unity_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_unity_engine, look_at_up) +{ + const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, omath::unity_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unity_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), omath::unity_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} + +TEST(unit_test_unity_engine, look_at_back) +{ + const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, -omath::unity_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unity_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), (-omath::unity_engine::k_abs_forward).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_unity_engine, look_at_left) +{ + const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, -omath::unity_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unity_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), (-omath::unity_engine::k_abs_right).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_unity_engine, look_at_down) +{ + const auto angles = omath::unity_engine::CameraTrait::calc_look_at_angle({}, -omath::unity_engine::k_abs_up); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unity_engine::forward_vector(angles); + for (const auto& [result, etalon] : + std::views::zip(dir_vector.as_array(), (-omath::unity_engine::k_abs_up).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} diff --git a/tests/engines/unit_test_unreal_engine.cpp b/tests/engines/unit_test_unreal_engine.cpp index 0a4441a1..e2679c81 100644 --- a/tests/engines/unit_test_unreal_engine.cpp +++ b/tests/engines/unit_test_unreal_engine.cpp @@ -7,6 +7,7 @@ #include #include #include +#include TEST(unit_test_unreal_engine, ForwardVector) { @@ -361,4 +362,59 @@ TEST(unit_test_unreal_engine, ConstexprConversions) static_assert(cm == 100000.0, "units_to_centimeters constexpr failed"); static_assert(m == 1000.0, "units_to_meters constexpr failed"); static_assert(km == 1.0, "units_to_kilometers constexpr failed"); +} +TEST(unit_test_unreal_engine, look_at_forward) +{ + const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, omath::unreal_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unreal_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::unreal_engine::k_abs_forward.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_unreal_engine, look_at_right) +{ + const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, omath::unreal_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unreal_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::unreal_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_unreal_engine, look_at_up) +{ + const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, omath::unreal_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unreal_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), omath::unreal_engine::k_abs_right.as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} + +TEST(unit_test_unreal_engine, look_at_back) +{ + const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, -omath::unreal_engine::k_abs_forward); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unreal_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::unreal_engine::k_abs_forward).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_unreal_engine, look_at_left) +{ + const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, -omath::unreal_engine::k_abs_right); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unreal_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::unreal_engine::k_abs_right).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); +} +TEST(unit_test_unreal_engine, look_at_down) +{ + const auto angles = omath::unreal_engine::CameraTrait::calc_look_at_angle({}, -omath::unreal_engine::k_abs_up); + + // ReSharper disable once CppTooWideScopeInitStatement + const auto dir_vector = omath::unreal_engine::forward_vector(angles); + for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::unreal_engine::k_abs_up).as_array())) + EXPECT_NEAR(result, etalon, 0.0001f); } \ No newline at end of file