From 00e7c564fd5c72d44a6db887c2faa3e00e2ba8e3 Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 11 Jun 2026 16:18:42 +0300 Subject: [PATCH 01/10] added gcem --- CMakeLists.txt | 10 +++++++++ CMakePresets.json | 2 +- .../internal/optional_constexpr_math.hpp | 12 ++++++++++ include/omath/trigonometry/angle.hpp | 22 ++++++++++++++++--- vcpkg.json | 7 ++++++ 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 include/omath/internal/optional_constexpr_math.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fd24fcd6..8da5a639 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ option(OMATH_ENABLE_FORCE_INLINE option(OMATH_ENABLE_LUA "omath bindings for lua" OFF) option(OMATH_ENABLE_HOOKING "omath will HooksManager that can hook DirectX/OpenGL automatically" OFF) +option(OMATH_USE_GCEM "omath will use gcem library to make more functions/methods constexpr" OFF) if(VCPKG_MANIFEST_FEATURES) foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES) @@ -50,6 +51,8 @@ if(VCPKG_MANIFEST_FEATURES) set(OMATH_ENABLE_LUA ON) elseif(omath_feature STREQUAL "hooking") set(OMATH_ENABLE_HOOKING ON) + elseif (omath_feature STREQUAL "gcem") + set(OMATH_USE_GCEM ON) endif() endforeach() @@ -80,6 +83,7 @@ if(${PROJECT_IS_TOP_LEVEL}) message(STATUS "[${PROJECT_NAME}]: Coverage feature status ${OMATH_ENABLE_COVERAGE}") message(STATUS "[${PROJECT_NAME}]: Valgrind feature status ${OMATH_ENABLE_VALGRIND}") message(STATUS "[${PROJECT_NAME}]: Lua feature status ${OMATH_ENABLE_LUA}") + message(STATUS "[${PROJECT_NAME}]: Gcem feature status ${OMATH_USE_GCEM}") endif() if(OMATH_STATIC_MSVC_RUNTIME_LIBRARY) @@ -120,6 +124,12 @@ if (OMATH_ENABLE_HOOKING) endif () endif () + +if (OMATH_USE_GCEM) + target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_USE_GCEM) + find_package(gcem CONFIG REQUIRED) + target_link_libraries(${PROJECT_NAME} PUBLIC gcem) +endif () add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}") diff --git a/CMakePresets.json b/CMakePresets.json index 2ed29f2f..f067ed39 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -57,7 +57,7 @@ "inherits": ["windows-base", "vcpkg-base"], "cacheVariables": { "VCPKG_TARGET_TRIPLET": "x64-windows-static", - "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples;hooking", + "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples;hooking;gcem", "OMATH_STATIC_MSVC_RUNTIME_LIBRARY": "ON" } }, diff --git a/include/omath/internal/optional_constexpr_math.hpp b/include/omath/internal/optional_constexpr_math.hpp new file mode 100644 index 00000000..ecc49dd0 --- /dev/null +++ b/include/omath/internal/optional_constexpr_math.hpp @@ -0,0 +1,12 @@ +// +// Created by orange on 6/11/2026. +// +#pragma once + + +#ifdef OMATH_USE_GCEM +#include +#define OMATH_CONSTEXPR constexpr +#else +#define OMATH_CONSTEXPR +#endif \ No newline at end of file diff --git a/include/omath/trigonometry/angle.hpp b/include/omath/trigonometry/angle.hpp index 4506e0c0..7b687ee4 100644 --- a/include/omath/trigonometry/angle.hpp +++ b/include/omath/trigonometry/angle.hpp @@ -7,6 +7,7 @@ #include #include #include +#include "omath/internal/optional_constexpr_math.hpp" namespace omath { @@ -70,21 +71,36 @@ namespace omath } [[nodiscard]] - Type sin() const noexcept + OMATH_CONSTEXPR Type sin() const noexcept { +#ifdef OMATH_USE_GCEM + return gcem::sin(as_radians()); +#else return std::sin(as_radians()); + +#endif } [[nodiscard]] - Type cos() const noexcept + OMATH_CONSTEXPR Type cos() const noexcept { +#ifdef OMATH_USE_GCEM + return gcem::cos(as_radians()); +#else return std::cos(as_radians()); + +#endif } [[nodiscard]] - Type tan() const noexcept + OMATH_CONSTEXPR Type tan() const noexcept { +#ifdef OMATH_USE_GCEM + return gcem::tan(as_radians()); +#else return std::tan(as_radians()); + +#endif } [[nodiscard]] diff --git a/vcpkg.json b/vcpkg.json index 27e3bef9..5b68917f 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -82,6 +82,13 @@ "lua", "sol2" ] + }, + "gcem": + { + "description": "Improve constexpr compatibility using gcem", + "dependencies": [ + "gcem" + ] } } } From a2be99be50ed9a5223ef3610a49b5858f2dcce03 Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 11 Jun 2026 23:20:18 +0300 Subject: [PATCH 02/10] added more gcem to angle vec2,3 --- CMakePresets.json | 2 +- include/omath/linear_algebra/vector2.hpp | 27 ++++++++++---- include/omath/linear_algebra/vector3.hpp | 46 +++++++++++++++--------- include/omath/trigonometry/angle.hpp | 11 ++++-- tests/general/unit_test_vector2.cpp | 9 +++-- tests/general/unit_test_vector3.cpp | 43 +++++++++++++--------- 6 files changed, 92 insertions(+), 46 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index f067ed39..8780fa24 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -239,7 +239,7 @@ "hidden": true, "inherits": ["darwin-base", "vcpkg-base"], "cacheVariables": { - "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples;lua" + "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples;lua;gcem" } }, { diff --git a/include/omath/linear_algebra/vector2.hpp b/include/omath/linear_algebra/vector2.hpp index e283d510..74ef70bf 100644 --- a/include/omath/linear_algebra/vector2.hpp +++ b/include/omath/linear_algebra/vector2.hpp @@ -3,6 +3,7 @@ // #pragma once +#include "omath/internal/optional_constexpr_math.hpp" #include #include #include @@ -116,9 +117,13 @@ namespace omath // Basic vector operations [[nodiscard("You must use distance")]] - Type distance_to(const Vector2& other) const noexcept + OMATH_CONSTEXPR Type distance_to(const Vector2& other) const noexcept { +#ifdef OMATH_USE_GCEM + return gcem::sqrt(distance_to_sqr(other)); +#else return std::sqrt(distance_to_sqr(other)); +#endif } [[nodiscard("You must use squared distance")]] @@ -136,7 +141,11 @@ namespace omath #ifndef _MSC_VER [[nodiscard("You must use length")]] constexpr Type length() const noexcept { +#ifdef OMATH_USE_GCEM + return gcem::hypot(this->x, this->y); +#else return std::hypot(this->x, this->y); +#endif } [[nodiscard("You must use normalized vector")]] constexpr Vector2 normalized() const noexcept @@ -146,13 +155,17 @@ namespace omath } #else [[nodiscard("You must use length")]] - Type length() const noexcept + OMATH_CONSTEXPR Type length() const noexcept { +#ifdef OMATH_USE_GCEM + return gcem::hypot(x, y); +#else return std::hypot(x, y); +#endif } [[nodiscard("You must use normalized vector")]] - Vector2 normalized() const noexcept + OMATH_CONSTEXPR Vector2 normalized() const noexcept { const Type len = length(); return len > static_cast(0) ? *this / len : *this; @@ -216,24 +229,24 @@ namespace omath } [[nodiscard("You must use comparison result")]] - bool operator<(const Vector2& other) const noexcept + OMATH_CONSTEXPR bool operator<(const Vector2& other) const noexcept { return length() < other.length(); } [[nodiscard("You must use comparison result")]] - bool operator>(const Vector2& other) const noexcept + OMATH_CONSTEXPR bool operator>(const Vector2& other) const noexcept { return length() > other.length(); } [[nodiscard("You must use comparison result")]] - bool operator<=(const Vector2& other) const noexcept + OMATH_CONSTEXPR bool operator<=(const Vector2& other) const noexcept { return length() <= other.length(); } [[nodiscard("You must use comparison result")]] - bool operator>=(const Vector2& other) const noexcept + OMATH_CONSTEXPR bool operator>=(const Vector2& other) const noexcept { return length() >= other.length(); } diff --git a/include/omath/linear_algebra/vector3.hpp b/include/omath/linear_algebra/vector3.hpp index 6dac4171..405c8f3b 100644 --- a/include/omath/linear_algebra/vector3.hpp +++ b/include/omath/linear_algebra/vector3.hpp @@ -140,20 +140,24 @@ namespace omath } #ifndef _MSC_VER - [[nodiscard("You must use length")]] constexpr Type length() const + [[nodiscard("You must use length")]] constexpr Type length() const noexcept { +#ifdef OMATH_USE_GCEM + return gcem::sqrt(this->x * this->x + this->y * this->y + z * z); +#else return std::hypot(this->x, this->y, z); +#endif } - [[nodiscard("You must use 2D length")]] constexpr Type length_2d() const + [[nodiscard("You must use 2D length")]] constexpr Type length_2d() const noexcept { return Vector2::length(); } - [[nodiscard("You must use distance")]] Type distance_to(const Vector3& other) const + [[nodiscard("You must use distance")]] OMATH_CONSTEXPR Type distance_to(const Vector3& other) const noexcept { return (*this - other).length(); } - [[nodiscard("You must use normalized vector")]] constexpr Vector3 normalized() const + [[nodiscard("You must use normalized vector")]] constexpr Vector3 normalized() const noexcept { const Type length_value = this->length(); @@ -161,13 +165,17 @@ namespace omath } #else [[nodiscard("You must use length")]] - Type length() const noexcept + OMATH_CONSTEXPR Type length() const noexcept { +#ifdef OMATH_USE_GCEM + return gcem::sqrt(this->x * this->x + this->y * this->y + this->z * this->z); +#else return std::hypot(this->x, this->y, z); +#endif } [[nodiscard("You must use normalized vector")]] - Vector3 normalized() const noexcept + OMATH_CONSTEXPR Vector3 normalized() const noexcept { const Type len = this->length(); @@ -175,13 +183,13 @@ namespace omath } [[nodiscard("You must use 2D length")]] - Type length_2d() const noexcept + OMATH_CONSTEXPR Type length_2d() const noexcept { return Vector2::length(); } [[nodiscard("You must use distance")]] - Type distance_to(const Vector3& v_other) const noexcept + OMATH_CONSTEXPR Type distance_to(const Vector3& v_other) const noexcept { return (*this - v_other).length(); } @@ -249,24 +257,28 @@ namespace omath } [[nodiscard("You must use direction check result")]] - bool point_to_same_direction(const Vector3& other) const + OMATH_CONSTEXPR bool point_to_same_direction(const Vector3& other) const { return dot(other) > static_cast(0); } [[nodiscard("You must use angle between vectors")]] - std::expected, Vector3Error> + OMATH_CONSTEXPR std::expected, Vector3Error> angle_between(const Vector3& other) const noexcept { const auto bottom = length() * other.length(); if (bottom == static_cast(0)) return std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE); - +#ifdef OMATH_USE_GCEM + return Angle::from_radians(gcem::acos(dot(other) / bottom)); +#else return Angle::from_radians(std::acos(dot(other) / bottom)); +#endif } [[nodiscard("You must use perpendicularity check result")]] - bool is_perpendicular(const Vector3& other, Type epsilon = static_cast(0.0001)) const noexcept + OMATH_CONSTEXPR 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; @@ -287,25 +299,25 @@ namespace omath } [[nodiscard("You must use comparison result")]] - bool operator<(const Vector3& other) const noexcept + OMATH_CONSTEXPR bool operator<(const Vector3& other) const noexcept { return length() < other.length(); } [[nodiscard("You must use comparison result")]] - bool operator>(const Vector3& other) const noexcept + OMATH_CONSTEXPR bool operator>(const Vector3& other) const noexcept { return length() > other.length(); } [[nodiscard("You must use comparison result")]] - bool operator<=(const Vector3& other) const noexcept + OMATH_CONSTEXPR bool operator<=(const Vector3& other) const noexcept { return length() <= other.length(); } [[nodiscard("You must use comparison result")]] - bool operator>=(const Vector3& other) const noexcept + OMATH_CONSTEXPR bool operator>=(const Vector3& other) const noexcept { return length() >= other.length(); } @@ -321,7 +333,7 @@ namespace omath template<> struct std::hash> { [[nodiscard("You must use hash value")]] - std::size_t operator()(const omath::Vector3& vec) const noexcept + constexpr std::size_t operator()(const omath::Vector3& vec) const noexcept { std::size_t hash = 0; constexpr std::hash hasher; diff --git a/include/omath/trigonometry/angle.hpp b/include/omath/trigonometry/angle.hpp index 7b687ee4..146a3093 100644 --- a/include/omath/trigonometry/angle.hpp +++ b/include/omath/trigonometry/angle.hpp @@ -3,11 +3,11 @@ // #pragma once +#include "omath/internal/optional_constexpr_math.hpp" #include "omath/trigonometry/angles.hpp" #include #include #include -#include "omath/internal/optional_constexpr_math.hpp" namespace omath { @@ -104,13 +104,18 @@ namespace omath } [[nodiscard]] - Type atan() const noexcept + OMATH_CONSTEXPR Type atan() const noexcept { +#ifdef OMATH_USE_GCEM + return gcem::atan(as_radians()); +#else return std::atan(as_radians()); + +#endif } [[nodiscard]] - Type cot() const noexcept + OMATH_CONSTEXPR Type cot() const noexcept { return cos() / sin(); } diff --git a/tests/general/unit_test_vector2.cpp b/tests/general/unit_test_vector2.cpp index 91d9e619..52f606c0 100644 --- a/tests/general/unit_test_vector2.cpp +++ b/tests/general/unit_test_vector2.cpp @@ -2,10 +2,10 @@ // Created by Vlad on 02.09.2024. // -#include #include // For FLT_MAX and FLT_MIN #include // For std::isinf and std::isnan #include +#include using namespace omath; @@ -399,7 +399,6 @@ TEST_F(UnitTestVector2, GreaterEqualOperator) EXPECT_TRUE(omath::Vector2(1.f, 1.f) >= omath::Vector2{}); } - // ── Cast operator tests ────────────────────────────────────────────────────── TEST(Vector2Cast, FloatToDouble) @@ -463,3 +462,9 @@ static_assert(Vector2(1.0f, 2.0f).length_sqr() == 5.0f, "LengthSqr should be 5") static_assert(Vector2(1.0f, 2.0f).dot(Vector2(4.0f, 5.0f)) == 14.0f, "Dot product should be 14"); static_assert(Vector2(4.0f, 5.0f).distance_to_sqr(Vector2(1.0f, 2.0f)) == 18.0f, "DistToSqr should be 18"); static_assert(Vector2(-1.0f, -2.0f).abs() == Vector2(1.0f, 2.0f), "Abs should convert negative values to positive"); + +#ifdef OMATH_USE_GCEM +static_assert(Vector2(3.0f, 4.0f).length() == 5.0f, "Length should be constexpr with gcem"); +static_assert(Vector2(0.0f, 0.0f).distance_to(Vector2(3.0f, 4.0f)) == 5.0f, "Distance should be constexpr with gcem"); +static_assert(Vector2(1.0f, 1.0f) < Vector2(3.0f, 4.0f), "Comparison should be constexpr with gcem"); +#endif diff --git a/tests/general/unit_test_vector3.cpp b/tests/general/unit_test_vector3.cpp index 158f1c0b..e9c2b758 100644 --- a/tests/general/unit_test_vector3.cpp +++ b/tests/general/unit_test_vector3.cpp @@ -2,11 +2,11 @@ // Created by Vlad on 01.09.2024. // -#include #include // For FLT_MAX, FLT_MIN #include #include #include // For std::numeric_limits +#include using namespace omath; @@ -31,35 +31,33 @@ TEST(Vector3More, ArithmeticAndDotCross) constexpr Vector3 a{1.f, 0.f, 0.f}; constexpr Vector3 b{0.f, 1.f, 0.f}; const auto c = a + b; - constexpr Vector3 expect_c{1.f,1.f,0.f}; + constexpr Vector3 expect_c{1.f, 1.f, 0.f}; EXPECT_EQ(c, expect_c); const auto d = a - b; - constexpr Vector3 expect_d{1.f,-1.f,0.f}; + constexpr Vector3 expect_d{1.f, -1.f, 0.f}; EXPECT_EQ(d, expect_d); const auto e = a * 2.f; - constexpr Vector3 expect_e{2.f,0.f,0.f}; + constexpr Vector3 expect_e{2.f, 0.f, 0.f}; EXPECT_EQ(e, expect_e); EXPECT_FLOAT_EQ(a.dot(b), 0.f); // manual cross product check - const auto cr = Vector3{ a.y * b.z - a.z * b.y, - a.z * b.x - a.x * b.z, - a.x * b.y - a.y * b.x }; - constexpr Vector3 expect_cr{0.f,0.f,1.f}; + const auto cr = Vector3{a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; + constexpr Vector3 expect_cr{0.f, 0.f, 1.f}; EXPECT_EQ(cr, expect_cr); } TEST(Vector3More, NormalizationEdgeCases) { - constexpr Vector3 z{0.0,0.0,0.0}; + constexpr Vector3 z{0.0, 0.0, 0.0}; const auto zn = z.normalized(); EXPECT_DOUBLE_EQ(zn.x, 0.0); EXPECT_DOUBLE_EQ(zn.y, 0.0); EXPECT_DOUBLE_EQ(zn.z, 0.0); - constexpr Vector3 v{3.0,4.0,0.0}; + constexpr Vector3 v{3.0, 4.0, 0.0}; const auto vn = v.normalized(); EXPECT_NEAR(vn.x, 0.6, 1e-12); EXPECT_NEAR(vn.y, 0.8, 1e-12); @@ -481,16 +479,14 @@ TEST_F(UnitTestVector3, AsTuple) // Test AsTuple method TEST_F(UnitTestVector3, AngleBeatween) { - EXPECT_NEAR(Vector3(0.0f, 0.0f, 1.0f).angle_between({1, 0, 0}).value().as_degrees(), - 90.0f, 0.001f); - EXPECT_NEAR(Vector3(0.0f, 0.0f, 1.0f).angle_between({0.0f, 0.0f, 1.0f}).value().as_degrees(), - 0.0f, 0.001f); + EXPECT_NEAR(Vector3(0.0f, 0.0f, 1.0f).angle_between({1, 0, 0}).value().as_degrees(), 90.0f, 0.001f); + EXPECT_NEAR(Vector3(0.0f, 0.0f, 1.0f).angle_between({0.0f, 0.0f, 1.0f}).value().as_degrees(), 0.0f, 0.001f); EXPECT_FALSE(Vector3(0.0f, 0.0f, 0.0f).angle_between({0.0f, 0.0f, 1.0f}).has_value()); } TEST_F(UnitTestVector3, IsPerpendicular) { - EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).is_perpendicular({1, 0 ,0}), true); + EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).is_perpendicular({1, 0, 0}), true); EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).is_perpendicular({0.0f, 0.0f, 1.0f}), false); EXPECT_FALSE(Vector3(0.0f, 0.0f, 0.0f).is_perpendicular({0.0f, 0.0f, 1.0f})); } @@ -585,4 +581,19 @@ TEST(Vector3Cast, SameTypeRoundtrip) static_assert(Vector3(1.0f, 2.0f, 3.0f).length_sqr() == 14.0f, "LengthSqr should be 14"); static_assert(Vector3(1.0f, 2.0f, 3.0f).dot(Vector3(4.0f, 5.0f, 6.0f)) == 32.0f, "Dot product should be 32"); static_assert(Vector3(4.0f, 5.0f, 6.0f).distance_to_sqr(Vector3(1.0f, 2.0f, 3.0f)) == 27.0f, "DistToSqr should be 27"); -static_assert(Vector3(-1.0f, -2.0f, -3.0f).abs() == Vector3(1.0f, 2.0f, 3.0f), "Abs should convert negative values to positive"); +static_assert(Vector3(-1.0f, -2.0f, -3.0f).abs() == Vector3(1.0f, 2.0f, 3.0f), + "Abs should convert negative values to positive"); + +#ifdef OMATH_USE_GCEM +static_assert(Vector3(1.0f, 2.0f, 2.0f).length() == 3.0f, "Length should be constexpr with gcem"); +static_assert(Vector3(0.0f, 0.0f, 0.0f).distance_to(Vector3(1.0f, 2.0f, 2.0f)) == 3.0f, + "Distance should be constexpr with gcem"); +static_assert(Vector3(1.0f, 1.0f, 1.0f) < Vector3(3.0f, 4.0f, 5.0f), "Comparison should be constexpr with gcem"); +static_assert( + [] + { + constexpr auto angle = Vector3(1.0f, 0.0f, 0.0f).angle_between(Vector3(0.0f, 1.0f, 0.0f)); + return angle.has_value() && angle->as_degrees() > 89.999f && angle->as_degrees() < 90.001f; + }(), + "Angle between should be constexpr with gcem"); +#endif From a741fc14852e58f8a4c57a0c0df8e3bb64e65a9a Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 11 Jun 2026 23:41:07 +0300 Subject: [PATCH 03/10] now add constexpr gcem tests for triangle and mat classes --- include/omath/linear_algebra/mat.hpp | 21 +++++++++++++++++++-- include/omath/linear_algebra/triangle.hpp | 6 +++--- tests/general/unit_test_angle.cpp | 18 +++++++++++++++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/include/omath/linear_algebra/mat.hpp b/include/omath/linear_algebra/mat.hpp index 2cbf0788..a3ee0eea 100644 --- a/include/omath/linear_algebra/mat.hpp +++ b/include/omath/linear_algebra/mat.hpp @@ -622,10 +622,14 @@ namespace omath template [[nodiscard("You must use extracted scale")]] - Vector3 mat_extract_scale(const Mat<4, 4, Type, St>& mat) noexcept + OMATH_CONSTEXPR Vector3 mat_extract_scale(const Mat<4, 4, Type, St>& mat) noexcept { auto column_length = [](const Type x, const Type y, const Type z) { +#ifdef OMATH_CONSTEXPR + return static_cast(gcem::sqrt(x * x + y * y + z * z)); +#else return static_cast(std::sqrt(x * x + y * y + z * z)); +#endif }; const auto scale_x = column_length(mat.at(0, 0), mat.at(1, 0), mat.at(2, 0)); @@ -635,9 +639,16 @@ namespace omath constexpr auto epsilon = std::numeric_limits::epsilon(); return { +#ifdef OMATH_CONSTEXPR + gcem::abs(scale_x) < epsilon ? Type{1} : scale_x, + gcem::abs(scale_y) < epsilon ? Type{1} : scale_y, + gcem::abs(scale_z) < epsilon ? Type{1} : scale_z, +#else std::abs(scale_x) < epsilon ? Type{1} : scale_x, std::abs(scale_y) < epsilon ? Type{1} : scale_y, std::abs(scale_z) < epsilon ? Type{1} : scale_z, +#endif + }; } @@ -654,9 +665,15 @@ namespace omath const auto m22 = mat.at(2, 2) / scale.z; return { - angles::radians_to_degrees(std::atan2(m21, m22)), +#ifdef OMATH_CONSTEXPR + angles::radians_to_degrees(gcem::atan2(m21, m22)), + angles::radians_to_degrees(gcem::asin(std::clamp(-m20, Type{-1}, Type{1}))), + angles::radians_to_degrees(gcem::atan2(m10, m00)), +#else + angles::radians_to_degrees(gc::atan2(m21, m22)), angles::radians_to_degrees(std::asin(std::clamp(-m20, Type{-1}, Type{1}))), angles::radians_to_degrees(std::atan2(m10, m00)), +#endif }; } diff --git a/include/omath/linear_algebra/triangle.hpp b/include/omath/linear_algebra/triangle.hpp index 1d8e68c4..a62f8887 100644 --- a/include/omath/linear_algebra/triangle.hpp +++ b/include/omath/linear_algebra/triangle.hpp @@ -3,7 +3,7 @@ // #pragma once #include "vector3.hpp" - +#include "omath/internal/optional_constexpr_math.hpp" namespace omath { /* @@ -40,13 +40,13 @@ namespace omath } [[nodiscard]] - Vector::ContainedType side_a_length() const + OMATH_CONSTEXPR Vector::ContainedType side_a_length() const { return m_vertex1.distance_to(m_vertex2); } [[nodiscard]] - Vector::ContainedType side_b_length() const + OMATH_CONSTEXPR Vector::ContainedType side_b_length() const { return m_vertex3.distance_to(m_vertex2); } diff --git a/tests/general/unit_test_angle.cpp b/tests/general/unit_test_angle.cpp index d1fc410f..ad17e655 100644 --- a/tests/general/unit_test_angle.cpp +++ b/tests/general/unit_test_angle.cpp @@ -2,10 +2,10 @@ // Created by Orange on 11/30/2024. // -#include #include #include #include +#include using namespace omath; @@ -19,6 +19,14 @@ namespace constexpr float k_eps = 1e-5f; +#ifdef OMATH_USE_GCEM + constexpr bool close_to(const float actual, const float expected, const float epsilon) + { + const float diff = actual - expected; + return (diff < 0.0f ? -diff : diff) <= epsilon; + } +#endif + } // namespace // ---------- Construction / factories ---------- @@ -190,3 +198,11 @@ TEST(UnitTestAngle, BinaryMinus_ReturnsWrappedDiff) const Deg c = a - b; // expect 340° EXPECT_FLOAT_EQ(c.as_degrees(), 340.0f); } + +#ifdef OMATH_USE_GCEM +static_assert(close_to(Pitch::from_degrees(0.0f).sin(), 0.0f, k_eps), "Sin should be constexpr with gcem"); +static_assert(close_to(Pitch::from_degrees(0.0f).cos(), 1.0f, k_eps), "Cos should be constexpr with gcem"); +static_assert(close_to(Pitch::from_degrees(45.0f).tan(), 1.0f, 1e-4f), "Tan should be constexpr with gcem"); +static_assert(close_to(Pitch::from_degrees(45.0f).cot(), 1.0f, 1e-4f), "Cot should be constexpr with gcem"); +static_assert(close_to(Pitch::from_degrees(45.0f).atan(), 0.66577375f, 1e-6f), "Atan should be constexpr with gcem"); +#endif From 854b50f317c5cb10bf62890e0a1ae4acdb6d8a06 Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 11 Jun 2026 23:49:03 +0300 Subject: [PATCH 04/10] added mat tests and triangle tests --- include/omath/linear_algebra/mat.hpp | 44 +++++++++++------ include/omath/linear_algebra/triangle.hpp | 6 ++- tests/general/unit_test_mat.cpp | 60 ++++++++++++++++++++++- tests/general/unit_test_triangle.cpp | 28 +++++++++++ 4 files changed, 122 insertions(+), 16 deletions(-) diff --git a/include/omath/linear_algebra/mat.hpp b/include/omath/linear_algebra/mat.hpp index a3ee0eea..d8e3cb9b 100644 --- a/include/omath/linear_algebra/mat.hpp +++ b/include/omath/linear_algebra/mat.hpp @@ -679,7 +679,7 @@ namespace omath template [[nodiscard("You must use rotation matrix")]] - Mat<4, 4, Type, St> mat_rotation_axis_x(const Angle& angle) noexcept + OMATH_CONSTEXPR Mat<4, 4, Type, St> mat_rotation_axis_x(const Angle& angle) noexcept { return { @@ -692,7 +692,7 @@ namespace omath template [[nodiscard("You must use rotation matrix")]] - Mat<4, 4, Type, St> mat_rotation_axis_y(const Angle& angle) noexcept + OMATH_CONSTEXPR Mat<4, 4, Type, St> mat_rotation_axis_y(const Angle& angle) noexcept { return { @@ -705,7 +705,7 @@ namespace omath template [[nodiscard("You must use rotation matrix")]] - Mat<4, 4, Type, St> mat_rotation_axis_z(const Angle& angle) noexcept + OMATH_CONSTEXPR Mat<4, 4, Type, St> mat_rotation_axis_z(const Angle& angle) noexcept { return { @@ -718,7 +718,7 @@ namespace omath template [[nodiscard("You must use camera view matrix")]] - static Mat<4, 4, Type, St> mat_camera_view(const Vector3& forward, const Vector3& right, + OMATH_CONSTEXPR static Mat<4, 4, Type, St> mat_camera_view(const Vector3& forward, const Vector3& right, const Vector3& up, const Vector3& camera_origin) noexcept { return Mat<4, 4, Type, St> @@ -732,12 +732,15 @@ namespace omath template - [[nodiscard("You must use perspective matrix")]] + [[nodiscard("You must use perspective matrix")]] OMATH_CONSTEXPR Mat<4, 4, Type, St> mat_perspective_left_handed_vertical_fov(const Type field_of_view, const Type aspect_ratio, const Type near, const Type far) noexcept { - const auto fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / Type{2}); - +#ifdef OMATH_USE_GCEM + const auto fov_half_tan = gcem::tan(angles::degrees_to_radians(field_of_view) / Type{2}); +#else + const auto fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / Type{2}) +#endif if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE) return {{Type{1} / (aspect_ratio * fov_half_tan), Type{0}, Type{0}, Type{0}}, {Type{0}, Type{1} / fov_half_tan, Type{0}, Type{0}}, @@ -754,11 +757,15 @@ namespace omath template - [[nodiscard("You must use perspective matrix")]] + [[nodiscard("You must use perspective matrix")]] OMATH_CONSTEXPR Mat<4, 4, Type, St> mat_perspective_right_handed_vertical_fov(const Type field_of_view, const Type aspect_ratio, const Type near, const Type far) noexcept { +#ifdef OMATH_USE_GCEM + const auto fov_half_tan = gcem::tan(angles::degrees_to_radians(field_of_view) / Type{2}); +#else const auto fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / Type{2}); +#endif if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE) return {{Type{1} / (aspect_ratio * fov_half_tan), Type{0}, Type{0}, Type{0}}, @@ -779,12 +786,16 @@ namespace omath // X and Y scales derived as: X = 1 / tan(hfov/2), Y = aspect / tan(hfov/2). template - [[nodiscard("You must use perspective matrix")]] + [[nodiscard("You must use perspective matrix")]] OMATH_CONSTEXPR Mat<4, 4, Type, St> mat_perspective_left_handed_horizontal_fov(const Type horizontal_fov, const Type aspect_ratio, const Type near, const Type far) noexcept { +#ifdef OMATH_USE_GCEM + const auto inv_tan_half_hfov = Type{1} / gcem::tan(angles::degrees_to_radians(horizontal_fov) / Type{2}); +#else const auto inv_tan_half_hfov = Type{1} / std::tan(angles::degrees_to_radians(horizontal_fov) / Type{2}); +#endif const auto x_axis = inv_tan_half_hfov; const auto y_axis = inv_tan_half_hfov * aspect_ratio; @@ -804,12 +815,17 @@ namespace omath template - [[nodiscard("You must use perspective matrix")]] + [[nodiscard("You must use perspective matrix")]] OMATH_CONSTEXPR Mat<4, 4, Type, St> mat_perspective_right_handed_horizontal_fov(const Type horizontal_fov, const Type aspect_ratio, const Type near, const Type far) noexcept { +#ifdef OMATH_USE_GCEM + const auto inv_tan_half_hfov = Type{1} / gcem::tan(angles::degrees_to_radians(horizontal_fov) / Type{2}); +#else const auto inv_tan_half_hfov = Type{1} / std::tan(angles::degrees_to_radians(horizontal_fov) / Type{2}); +#endif + const auto x_axis = inv_tan_half_hfov; const auto y_axis = inv_tan_half_hfov * aspect_ratio; @@ -828,7 +844,7 @@ namespace omath } template - [[nodiscard("You must use ortho matrix")]] + [[nodiscard("You must use ortho matrix")]] OMATH_CONSTEXPR Mat<4, 4, Type, St> mat_ortho_left_handed(const Type left, const Type right, const Type bottom, const Type top, const Type near, const Type far) noexcept { @@ -853,7 +869,7 @@ namespace omath } template - [[nodiscard("You must use ortho matrix")]] + [[nodiscard("You must use ortho matrix")]] OMATH_CONSTEXPR Mat<4, 4, Type, St> mat_ortho_right_handed(const Type left, const Type right, const Type bottom, const Type top, const Type near, const Type far) noexcept { @@ -877,7 +893,7 @@ namespace omath std::unreachable(); } template - Mat<4, 4, T, St> mat_look_at_left_handed(const Vector3& eye, const Vector3& center, const Vector3& up) + OMATH_CONSTEXPR Mat<4, 4, T, St> mat_look_at_left_handed(const Vector3& eye, const Vector3& center, const Vector3& up) { const Vector3 f = (center - eye).normalized(); const Vector3 s = f.cross(up).normalized(); @@ -886,7 +902,7 @@ namespace omath } template - Mat<4, 4, T, St>mat_look_at_right_handed(const Vector3& eye, const Vector3& center, const Vector3& up) + OMATH_CONSTEXPR Mat<4, 4, T, St> mat_look_at_right_handed(const Vector3& eye, const Vector3& center, const Vector3& up) { const Vector3 f = (center - eye).normalized(); const Vector3 s = f.cross(up).normalized(); diff --git a/include/omath/linear_algebra/triangle.hpp b/include/omath/linear_algebra/triangle.hpp index a62f8887..8e051be1 100644 --- a/include/omath/linear_algebra/triangle.hpp +++ b/include/omath/linear_algebra/triangle.hpp @@ -2,8 +2,8 @@ // Created by Orange on 11/13/2024. // #pragma once -#include "vector3.hpp" #include "omath/internal/optional_constexpr_math.hpp" +#include "vector3.hpp" namespace omath { /* @@ -69,7 +69,11 @@ namespace omath const auto side_b = side_b_length(); const auto hypot_value = hypot(); +#ifdef OMATH_USE_GCEM + return gcem::abs(side_a * side_a + side_b * side_b - hypot_value * hypot_value) <= 0.0001f; +#else return std::abs(side_a * side_a + side_b * side_b - hypot_value * hypot_value) <= 0.0001f; +#endif } [[nodiscard]] constexpr Vector side_b_vector() const diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index 0cc51c35..63bfd643 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -1,11 +1,25 @@ // UnitTestMat.cpp #include "omath/linear_algebra/mat.hpp" #include "omath/linear_algebra/vector3.hpp" +#include "omath/trigonometry/angle.hpp" #include "omath/trigonometry/angles.hpp" #include using namespace omath; +#ifdef OMATH_USE_GCEM +namespace +{ + using Pitch = Angle(-90), static_cast(90), AngleFlags::Clamped>; + + constexpr bool close_to(const float actual, const float expected, const float epsilon) + { + const float diff = actual - expected; + return (diff < 0.0f ? -diff : diff) <= epsilon; + } +} // namespace +#endif + class UnitTestMat : public ::testing::Test { protected: @@ -522,4 +536,48 @@ TEST(UnitTestMatStandalone, MatOrthoNegativeOneToOneDefault) NDCDepthRange::NEGATIVE_ONE_TO_ONE>(-1.f, 1.f, -1.f, 1.f, 0.1f, 100.f); EXPECT_EQ(ortho_default, ortho_explicit); -} \ No newline at end of file +} + +#ifdef OMATH_USE_GCEM +static_assert( + [] + { + constexpr auto scale = mat_extract_scale(mat_scale(Vector3{2.0f, 3.0f, 4.0f})); + return close_to(scale.x, 2.0f, 1e-5f) && close_to(scale.y, 3.0f, 1e-5f) && close_to(scale.z, 4.0f, 1e-5f); + }(), + "Mat scale extraction should be constexpr with gcem"); + +static_assert( + [] + { + constexpr auto rotation = mat_rotation_axis_z(Pitch::from_degrees(90.0f)); + return close_to(rotation.at(0, 0), 0.0f, 1e-5f) && close_to(rotation.at(0, 1), -1.0f, 1e-5f) + && close_to(rotation.at(1, 0), 1.0f, 1e-5f) && close_to(rotation.at(1, 1), 0.0f, 1e-5f) + && close_to(rotation.at(2, 2), 1.0f, 1e-5f) && close_to(rotation.at(3, 3), 1.0f, 1e-5f); + }(), + "Mat rotation should be constexpr with gcem"); + +static_assert( + [] + { + constexpr auto projection = + mat_perspective_left_handed_vertical_fov(90.0f, 1.0f, 1.0f, 11.0f); + return close_to(projection.at(0, 0), 1.0f, 1e-5f) && close_to(projection.at(1, 1), 1.0f, 1e-5f) + && close_to(projection.at(2, 2), 1.1f, 1e-5f) && close_to(projection.at(2, 3), -1.1f, 1e-5f) + && close_to(projection.at(3, 2), 1.0f, 1e-5f); + }(), + "Mat vertical-FOV perspective should be constexpr with gcem"); + +static_assert( + [] + { + constexpr auto projection = + mat_perspective_right_handed_horizontal_fov(90.0f, 2.0f, 1.0f, 11.0f); + return close_to(projection.at(0, 0), 1.0f, 1e-5f) && close_to(projection.at(1, 1), 2.0f, 1e-5f) + && close_to(projection.at(2, 2), -1.1f, 1e-5f) && close_to(projection.at(2, 3), -1.1f, 1e-5f) + && close_to(projection.at(3, 2), -1.0f, 1e-5f); + }(), + "Mat horizontal-FOV perspective should be constexpr with gcem"); +#endif diff --git a/tests/general/unit_test_triangle.cpp b/tests/general/unit_test_triangle.cpp index f972a8a5..e3276894 100644 --- a/tests/general/unit_test_triangle.cpp +++ b/tests/general/unit_test_triangle.cpp @@ -8,6 +8,17 @@ using namespace omath; +#ifdef OMATH_USE_GCEM +namespace +{ + constexpr bool close_to(const float actual, const float expected, const float epsilon) + { + const float diff = actual - expected; + return (diff < 0.0f ? -diff : diff) <= epsilon; + } +} // namespace +#endif + class UnitTestTriangle : public ::testing::Test { protected: @@ -129,3 +140,20 @@ TEST_F(UnitTestTriangle, MidPoint) EXPECT_FLOAT_EQ(mid2.y, (2.0f + 5.0f + 8.0f) / 3.0f); EXPECT_FLOAT_EQ(mid2.z, (3.0f + 6.0f + 9.0f) / 3.0f); } + +#ifdef OMATH_USE_GCEM +static_assert( + [] + { + constexpr Triangle> triangle{{3.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 4.0f, 0.0f}}; + constexpr auto normal = triangle.calculate_normal(); + constexpr auto mid_point = triangle.mid_point(); + + return close_to(triangle.side_a_length(), 3.0f, 1e-5f) && close_to(triangle.side_b_length(), 4.0f, 1e-5f) + && close_to(triangle.hypot(), 5.0f, 1e-5f) && triangle.is_rectangular() + && close_to(normal.length(), 1.0f, 1e-5f) && close_to(normal.z, -1.0f, 1e-5f) + && close_to(mid_point.x, 1.0f, 1e-5f) && close_to(mid_point.y, 4.0f / 3.0f, 1e-5f) + && close_to(mid_point.z, 0.0f, 1e-5f); + }(), + "Triangle helpers should be constexpr with gcem"); +#endif From 589f440e70e99c5257c978c6d1be2f5562a9f297 Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 11 Jun 2026 23:54:53 +0300 Subject: [PATCH 05/10] added gcem to pipelines --- .github/workflows/cmake-multi-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index a989ffd7..1866dd6a 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -107,7 +107,7 @@ jobs: -DOMATH_BUILD_TESTS=ON \ -DOMATH_BUILD_BENCHMARK=OFF \ -DOMATH_ENABLE_COVERAGE=${{ matrix.coverage == true && 'ON' || 'OFF' }} \ - -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;lua" + -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;lua;gcem" - name: Build shell: bash From d9acf5eea82333e31f3d3bf6331f45885ea2f209 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 12 Jun 2026 00:04:12 +0300 Subject: [PATCH 06/10] fix --- include/omath/linear_algebra/mat.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/omath/linear_algebra/mat.hpp b/include/omath/linear_algebra/mat.hpp index d8e3cb9b..7a0dece3 100644 --- a/include/omath/linear_algebra/mat.hpp +++ b/include/omath/linear_algebra/mat.hpp @@ -625,7 +625,7 @@ namespace omath OMATH_CONSTEXPR Vector3 mat_extract_scale(const Mat<4, 4, Type, St>& mat) noexcept { auto column_length = [](const Type x, const Type y, const Type z) { -#ifdef OMATH_CONSTEXPR +#ifdef OMATH_USE_GCEM return static_cast(gcem::sqrt(x * x + y * y + z * z)); #else return static_cast(std::sqrt(x * x + y * y + z * z)); @@ -639,7 +639,7 @@ namespace omath constexpr auto epsilon = std::numeric_limits::epsilon(); return { -#ifdef OMATH_CONSTEXPR +#ifdef OMATH_USE_GCEM gcem::abs(scale_x) < epsilon ? Type{1} : scale_x, gcem::abs(scale_y) < epsilon ? Type{1} : scale_y, gcem::abs(scale_z) < epsilon ? Type{1} : scale_z, @@ -665,12 +665,12 @@ namespace omath const auto m22 = mat.at(2, 2) / scale.z; return { -#ifdef OMATH_CONSTEXPR +#ifdef OMATH_USE_GCEM angles::radians_to_degrees(gcem::atan2(m21, m22)), angles::radians_to_degrees(gcem::asin(std::clamp(-m20, Type{-1}, Type{1}))), angles::radians_to_degrees(gcem::atan2(m10, m00)), #else - angles::radians_to_degrees(gc::atan2(m21, m22)), + angles::radians_to_degrees(std::atan2(m21, m22)), angles::radians_to_degrees(std::asin(std::clamp(-m20, Type{-1}, Type{1}))), angles::radians_to_degrees(std::atan2(m10, m00)), #endif @@ -739,7 +739,7 @@ namespace omath #ifdef OMATH_USE_GCEM const auto fov_half_tan = gcem::tan(angles::degrees_to_radians(field_of_view) / Type{2}); #else - const auto fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / Type{2}) + const auto fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / Type{2}); #endif if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE) return {{Type{1} / (aspect_ratio * fov_half_tan), Type{0}, Type{0}, Type{0}}, From 4a9125549f3ace32743474a3c20071aa84a6816a Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 12 Jun 2026 00:09:14 +0300 Subject: [PATCH 07/10] patch --- include/omath/linear_algebra/mat.hpp | 1 - include/omath/linear_algebra/vector3.hpp | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/omath/linear_algebra/mat.hpp b/include/omath/linear_algebra/mat.hpp index 7a0dece3..fce2acb9 100644 --- a/include/omath/linear_algebra/mat.hpp +++ b/include/omath/linear_algebra/mat.hpp @@ -648,7 +648,6 @@ namespace omath std::abs(scale_y) < epsilon ? Type{1} : scale_y, std::abs(scale_z) < epsilon ? Type{1} : scale_z, #endif - }; } diff --git a/include/omath/linear_algebra/vector3.hpp b/include/omath/linear_algebra/vector3.hpp index 405c8f3b..c36deb13 100644 --- a/include/omath/linear_algebra/vector3.hpp +++ b/include/omath/linear_algebra/vector3.hpp @@ -332,8 +332,9 @@ namespace omath template<> struct std::hash> { + // NOTE: Cannot be constexpr because of MSVC [[nodiscard("You must use hash value")]] - constexpr std::size_t operator()(const omath::Vector3& vec) const noexcept + std::size_t operator()(const omath::Vector3& vec) const noexcept { std::size_t hash = 0; constexpr std::hash hasher; From 5f14510e5b5d2c097ac3d705c3764fd7c03fcb53 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 12 Jun 2026 00:52:22 +0300 Subject: [PATCH 08/10] made constexpr cryengine camera & fromulas --- include/omath/engines/cry_engine/formulas.hpp | 71 +++++++++-- .../cry_engine/traits/camera_trait.hpp | 32 ++++- include/omath/trigonometry/angles.hpp | 7 +- source/engines/cry_engine/formulas.cpp | 70 ----------- .../cry_engine/traits/camera_trait.cpp | 27 ---- tests/engines/unit_test_cry_engine.cpp | 118 +++++++++++++++--- 6 files changed, 195 insertions(+), 130 deletions(-) delete mode 100644 source/engines/cry_engine/formulas.cpp delete mode 100644 source/engines/cry_engine/traits/camera_trait.cpp diff --git a/include/omath/engines/cry_engine/formulas.hpp b/include/omath/engines/cry_engine/formulas.hpp index 4014b403..3739b677 100644 --- a/include/omath/engines/cry_engine/formulas.hpp +++ b/include/omath/engines/cry_engine/formulas.hpp @@ -8,31 +8,82 @@ namespace omath::cry_engine { [[nodiscard]] - Vector3 forward_vector(const ViewAngles& angles) noexcept; + OMATH_CONSTEXPR 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); + } [[nodiscard]] - Vector3 right_vector(const ViewAngles& angles) noexcept; + OMATH_CONSTEXPR 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)}; + } [[nodiscard]] - Vector3 up_vector(const ViewAngles& angles) noexcept; + OMATH_CONSTEXPR Vector3 right_vector(const ViewAngles& angles) noexcept + { + const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right); - [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; + } [[nodiscard]] - Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; + OMATH_CONSTEXPR 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)}; + } [[nodiscard]] - Vector3 extract_origin(const Mat4X4& mat) noexcept; + OMATH_CONSTEXPR 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); + } [[nodiscard]] - Vector3 extract_scale(const Mat4X4& mat) noexcept; + OMATH_CONSTEXPR Vector3 extract_origin(const Mat4X4& mat) noexcept + { + return mat_extract_origin(mat); + } [[nodiscard]] - ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept; + OMATH_CONSTEXPR Vector3 extract_scale(const Mat4X4& mat) noexcept + { + return mat_extract_scale(mat); + } [[nodiscard]] - Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far, - NDCDepthRange ndc_depth_range = NDCDepthRange::ZERO_TO_ONE) noexcept; + OMATH_CONSTEXPR ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept + { + const auto angles = mat_extract_rotation_zyx(mat); + return { + PitchAngle::from_degrees(angles.x), + YawAngle::from_degrees(angles.z), + RollAngle::from_degrees(angles.y), + }; + } + [[nodiscard]] + OMATH_CONSTEXPR Mat4X4 + calc_perspective_projection_matrix( + const float field_of_view, const float aspect_ratio, const float near, const float far, + const NDCDepthRange ndc_depth_range = NDCDepthRange::ZERO_TO_ONE) noexcept + { + if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE) + return mat_perspective_left_handed_vertical_fov( + field_of_view, aspect_ratio, near, far); + + if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE) + return mat_perspective_left_handed_vertical_fov( + field_of_view, aspect_ratio, near, far); + std::unreachable(); + } template requires std::is_floating_point_v diff --git a/include/omath/engines/cry_engine/traits/camera_trait.hpp b/include/omath/engines/cry_engine/traits/camera_trait.hpp index b7770af2..0aee1397 100644 --- a/include/omath/engines/cry_engine/traits/camera_trait.hpp +++ b/include/omath/engines/cry_engine/traits/camera_trait.hpp @@ -4,21 +4,41 @@ #pragma once #include "omath/engines/cry_engine/formulas.hpp" +#include "omath/internal/optional_constexpr_math.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; + OMATH_CONSTEXPR static ViewAngles calc_look_at_angle(const Vector3& cam_origin, + const Vector3& look_at) noexcept + { + const auto direction = (look_at - cam_origin).normalized(); +#ifdef OMATH_USE_GCEM + return {PitchAngle::from_radians(gcem::asin(direction.z)), + YawAngle::from_radians(-gcem::atan2(direction.x, direction.y)), RollAngle::from_radians(0.f)}; +#else + return {PitchAngle::from_radians(std::asin(direction.z)), + YawAngle::from_radians(-std::atan2(direction.x, direction.y)), RollAngle::from_radians(0.f)}; +#endif + } [[nodiscard]] - static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept; + OMATH_CONSTEXPR static Mat4X4 calc_view_matrix(const ViewAngles& angles, + const Vector3& cam_origin) noexcept + { + return cry_engine::calc_view_matrix(angles, cam_origin); + } [[nodiscard]] - static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port, - float near, float far, NDCDepthRange ndc_depth_range) noexcept; + OMATH_CONSTEXPR static Mat4X4 + calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port, + const float near, const float far, const NDCDepthRange ndc_depth_range) noexcept + { + return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far, + ndc_depth_range); + } }; -} // namespace omath::cry_engine \ No newline at end of file +} // namespace omath::cry_engine diff --git a/include/omath/trigonometry/angles.hpp b/include/omath/trigonometry/angles.hpp index ce07dd83..10c27895 100644 --- a/include/omath/trigonometry/angles.hpp +++ b/include/omath/trigonometry/angles.hpp @@ -3,6 +3,7 @@ // #pragma once +#include "omath/internal/optional_constexpr_math.hpp" #include #include @@ -47,14 +48,18 @@ namespace omath::angles template requires std::is_arithmetic_v - [[nodiscard]] Type wrap_angle(const Type& angle, const Type& min, const Type& max) noexcept + [[nodiscard]] OMATH_CONSTEXPR Type wrap_angle(const Type& angle, const Type& min, const Type& max) noexcept { if (angle <= max && angle >= min) return angle; const Type range = max - min; +#ifdef OMATH_USE_GCEM + Type wrapped_angle = gcem::fmod(angle - min, range); +#else Type wrapped_angle = std::fmod(angle - min, range); +#endif if (wrapped_angle < 0) wrapped_angle += range; diff --git a/source/engines/cry_engine/formulas.cpp b/source/engines/cry_engine/formulas.cpp deleted file mode 100644 index bc7d6ce2..00000000 --- a/source/engines/cry_engine/formulas.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// -// 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); - } - - Vector3 extract_origin(const Mat4X4& mat) noexcept - { - return mat_extract_origin(mat); - } - - Vector3 extract_scale(const Mat4X4& mat) noexcept - { - return mat_extract_scale(mat); - } - - ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept - { - const auto angles = mat_extract_rotation_zyx(mat); - return { - PitchAngle::from_degrees(angles.x), - YawAngle::from_degrees(angles.z), - RollAngle::from_degrees(angles.y), - }; - } - - Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, - const float far, const NDCDepthRange ndc_depth_range) noexcept - { - if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE) - return mat_perspective_left_handed_vertical_fov( - field_of_view, aspect_ratio, near, far); - - if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE) - return mat_perspective_left_handed_vertical_fov( - field_of_view, aspect_ratio, near, far); - std::unreachable(); - } -} // namespace omath::cry_engine diff --git a/source/engines/cry_engine/traits/camera_trait.cpp b/source/engines/cry_engine/traits/camera_trait.cpp deleted file mode 100644 index da260f5b..00000000 --- a/source/engines/cry_engine/traits/camera_trait.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// -// 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, const NDCDepthRange ndc_depth_range) noexcept - { - return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far, - ndc_depth_range); - } -} // namespace omath::cry_engine \ No newline at end of file diff --git a/tests/engines/unit_test_cry_engine.cpp b/tests/engines/unit_test_cry_engine.cpp index 1888c278..33927725 100644 --- a/tests/engines/unit_test_cry_engine.cpp +++ b/tests/engines/unit_test_cry_engine.cpp @@ -9,6 +9,98 @@ #include using namespace omath; + +#ifdef OMATH_USE_GCEM +namespace +{ + constexpr bool close_to(const float actual, const float expected, const float epsilon) + { + const float diff = actual - expected; + return (diff < 0.0f ? -diff : diff) <= epsilon; + } + + constexpr bool vec_close_to(const Vector3& actual, const Vector3& expected, const float epsilon) + { + return close_to(actual.x, expected.x, epsilon) && close_to(actual.y, expected.y, epsilon) + && close_to(actual.z, expected.z, epsilon); + } +} // namespace + +static_assert( + [] + { + constexpr omath::cry_engine::ViewAngles angles{}; + constexpr auto forward = omath::cry_engine::forward_vector(angles); + constexpr auto right = omath::cry_engine::right_vector(angles); + constexpr auto up = omath::cry_engine::up_vector(angles); + constexpr auto rotation = omath::cry_engine::rotation_matrix(angles); + + return vec_close_to(forward, omath::cry_engine::k_abs_forward, 1e-5f) + && vec_close_to(right, omath::cry_engine::k_abs_right, 1e-5f) + && vec_close_to(up, omath::cry_engine::k_abs_up, 1e-5f) && close_to(rotation.at(0, 0), 1.0f, 1e-5f) + && close_to(rotation.at(1, 1), 1.0f, 1e-5f) && close_to(rotation.at(2, 2), 1.0f, 1e-5f) + && close_to(rotation.at(3, 3), 1.0f, 1e-5f); + }(), + "CryEngine basis vectors should be constexpr with gcem"); + +static_assert( + [] + { + constexpr auto view = omath::cry_engine::calc_view_matrix({}, {}); + return close_to(view.at(0, 0), 1.0f, 1e-5f) && close_to(view.at(1, 2), 1.0f, 1e-5f) + && close_to(view.at(2, 1), 1.0f, 1e-5f) && close_to(view.at(3, 3), 1.0f, 1e-5f); + }(), + "CryEngine view matrix should be constexpr with gcem"); + +static_assert( + [] + { + constexpr auto transform = mat_translation({1.0f, 2.0f, 3.0f}) + * mat_scale({2.0f, 3.0f, 4.0f}); + constexpr auto origin = omath::cry_engine::extract_origin(transform); + constexpr auto scale = omath::cry_engine::extract_scale(transform); + + return vec_close_to(origin, {1.0f, 2.0f, 3.0f}, 1e-5f) && vec_close_to(scale, {2.0f, 3.0f, 4.0f}, 1e-5f); + }(), + "CryEngine transform extraction should be constexpr with gcem"); + +static_assert( + [] + { + constexpr auto projection = omath::cry_engine::calc_perspective_projection_matrix( + 90.0f, 1.0f, 1.0f, 11.0f, NDCDepthRange::ZERO_TO_ONE); + return close_to(projection.at(0, 0), 1.0f, 1e-5f) && close_to(projection.at(1, 1), 1.0f, 1e-5f) + && close_to(projection.at(2, 2), 1.1f, 1e-5f) && close_to(projection.at(2, 3), -1.1f, 1e-5f) + && close_to(projection.at(3, 2), 1.0f, 1e-5f); + }(), + "CryEngine projection matrix should be constexpr with gcem"); + +static_assert( + [] + { + constexpr auto angles = + omath::cry_engine::CameraTrait::calc_look_at_angle({}, omath::cry_engine::k_abs_forward); + constexpr auto view = omath::cry_engine::CameraTrait::calc_view_matrix(angles, {}); + constexpr auto projection = omath::cry_engine::CameraTrait::calc_projection_matrix( + projection::FieldOfView::from_degrees(90.0f), {1.0f, 1.0f}, 1.0f, 11.0f, + NDCDepthRange::ZERO_TO_ONE); + + return close_to(angles.pitch.as_degrees(), 0.0f, 1e-5f) && close_to(angles.yaw.as_degrees(), 0.0f, 1e-5f) + && close_to(angles.roll.as_degrees(), 0.0f, 1e-5f) && close_to(view.at(0, 0), 1.0f, 1e-5f) + && close_to(view.at(2, 1), 1.0f, 1e-5f) && close_to(projection.at(0, 0), 1.0f, 1e-5f) + && close_to(projection.at(1, 1), 1.0f, 1e-5f) && close_to(projection.at(2, 2), 1.1f, 1e-5f) + && close_to(projection.at(2, 3), -1.1f, 1e-5f) && close_to(projection.at(3, 2), 1.0f, 1e-5f); + }(), + "CryEngine CameraTrait should be constexpr with gcem"); + +static_assert(omath::cry_engine::units_to_centimeters(100.0f) == 1.0f); +static_assert(omath::cry_engine::units_to_meters(2.0f) == 2.0f); +static_assert(omath::cry_engine::units_to_kilometers(2000.0f) == 2.0f); +static_assert(omath::cry_engine::centimeters_to_units(1.0f) == 100.0f); +static_assert(omath::cry_engine::meters_to_units(2.0f) == 2.0f); +static_assert(omath::cry_engine::kilometers_to_units(2.0f) == 2000.0f); +#endif + TEST(unit_test_cry_engine, look_at_forward) { const auto angles = cry_engine::CameraTrait::calc_look_at_angle({}, cry_engine::k_abs_forward); @@ -251,11 +343,9 @@ TEST(unit_test_cry_engine, ViewAnglesAsVector3Zero) TEST(unit_test_cry_engine, ViewAnglesAsVector3Values) { - const omath::cry_engine::ViewAngles angles{ - omath::cry_engine::PitchAngle::from_degrees(45.f), - omath::cry_engine::YawAngle::from_degrees(-90.f), - omath::cry_engine::RollAngle::from_degrees(30.f) - }; + const omath::cry_engine::ViewAngles angles{omath::cry_engine::PitchAngle::from_degrees(45.f), + omath::cry_engine::YawAngle::from_degrees(-90.f), + omath::cry_engine::RollAngle::from_degrees(30.f)}; const auto vec = angles.as_vector3(); EXPECT_FLOAT_EQ(vec.x, 45.f); @@ -266,11 +356,9 @@ TEST(unit_test_cry_engine, ViewAnglesAsVector3Values) TEST(unit_test_cry_engine, ViewAnglesAsVector3ClampedPitch) { // Pitch is clamped to [-90, 90] - const omath::cry_engine::ViewAngles angles{ - omath::cry_engine::PitchAngle::from_degrees(120.f), - omath::cry_engine::YawAngle::from_degrees(0.f), - omath::cry_engine::RollAngle::from_degrees(0.f) - }; + const omath::cry_engine::ViewAngles angles{omath::cry_engine::PitchAngle::from_degrees(120.f), + omath::cry_engine::YawAngle::from_degrees(0.f), + omath::cry_engine::RollAngle::from_degrees(0.f)}; const auto vec = angles.as_vector3(); EXPECT_FLOAT_EQ(vec.x, 90.f); @@ -279,12 +367,10 @@ TEST(unit_test_cry_engine, ViewAnglesAsVector3ClampedPitch) TEST(unit_test_cry_engine, ViewAnglesAsVector3NormalizedYaw) { // Yaw is normalized to [-180, 180], 270 wraps to -90 - const omath::cry_engine::ViewAngles angles{ - omath::cry_engine::PitchAngle::from_degrees(0.f), - omath::cry_engine::YawAngle::from_degrees(270.f), - omath::cry_engine::RollAngle::from_degrees(0.f) - }; + const omath::cry_engine::ViewAngles angles{omath::cry_engine::PitchAngle::from_degrees(0.f), + omath::cry_engine::YawAngle::from_degrees(270.f), + omath::cry_engine::RollAngle::from_degrees(0.f)}; const auto vec = angles.as_vector3(); EXPECT_NEAR(vec.y, -90.f, 0.01f); -} \ No newline at end of file +} From 2d38387da92909613b4cf0ecd63c1c8c8ea6f5b0 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 12 Jun 2026 01:05:14 +0300 Subject: [PATCH 09/10] fix --- include/omath/linear_algebra/mat.hpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/include/omath/linear_algebra/mat.hpp b/include/omath/linear_algebra/mat.hpp index fce2acb9..f0f8bb40 100644 --- a/include/omath/linear_algebra/mat.hpp +++ b/include/omath/linear_algebra/mat.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #ifdef OMATH_USE_AVX2 @@ -149,7 +150,8 @@ namespace omath } } - [[nodiscard("You must use element reference")]] constexpr Type& at(const size_t row_index, const size_t column_index) + [[nodiscard("You must use element reference")]] constexpr Type& at(const size_t row_index, + const size_t column_index) { return const_cast(std::as_const(*this).at(row_index, column_index)); } @@ -176,6 +178,13 @@ namespace omath operator*(const Mat& other) const { #ifdef OMATH_USE_AVX2 + if (std::is_constant_evaluated()) + { + if constexpr (StoreType == MatStoreType::ROW_MAJOR) + return cache_friendly_multiply_row_major(other); + else if constexpr (StoreType == MatStoreType::COLUMN_MAJOR) + return cache_friendly_multiply_col_major(other); + } if constexpr (StoreType == MatStoreType::ROW_MAJOR) return avx_multiply_row_major(other); else if constexpr (StoreType == MatStoreType::COLUMN_MAJOR) From 792fb1385145a43152e90cfa0511dfe185cfb0c6 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 12 Jun 2026 01:12:13 +0300 Subject: [PATCH 10/10] patch --- include/omath/engines/cry_engine/formulas.hpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/include/omath/engines/cry_engine/formulas.hpp b/include/omath/engines/cry_engine/formulas.hpp index 3739b677..03382605 100644 --- a/include/omath/engines/cry_engine/formulas.hpp +++ b/include/omath/engines/cry_engine/formulas.hpp @@ -8,7 +8,7 @@ namespace omath::cry_engine { [[nodiscard]] - OMATH_CONSTEXPR Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept + inline OMATH_CONSTEXPR Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept { return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.roll) @@ -16,7 +16,7 @@ namespace omath::cry_engine } [[nodiscard]] - OMATH_CONSTEXPR Vector3 forward_vector(const ViewAngles& angles) noexcept + inline OMATH_CONSTEXPR Vector3 forward_vector(const ViewAngles& angles) noexcept { const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_forward); @@ -24,7 +24,7 @@ namespace omath::cry_engine } [[nodiscard]] - OMATH_CONSTEXPR Vector3 right_vector(const ViewAngles& angles) noexcept + inline OMATH_CONSTEXPR Vector3 right_vector(const ViewAngles& angles) noexcept { const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right); @@ -32,7 +32,7 @@ namespace omath::cry_engine } [[nodiscard]] - OMATH_CONSTEXPR Vector3 up_vector(const ViewAngles& angles) noexcept + inline OMATH_CONSTEXPR Vector3 up_vector(const ViewAngles& angles) noexcept { const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_up); @@ -40,26 +40,26 @@ namespace omath::cry_engine } [[nodiscard]] - OMATH_CONSTEXPR Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept + inline OMATH_CONSTEXPR 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); } [[nodiscard]] - OMATH_CONSTEXPR Vector3 extract_origin(const Mat4X4& mat) noexcept + inline OMATH_CONSTEXPR Vector3 extract_origin(const Mat4X4& mat) noexcept { return mat_extract_origin(mat); } [[nodiscard]] - OMATH_CONSTEXPR Vector3 extract_scale(const Mat4X4& mat) noexcept + inline OMATH_CONSTEXPR Vector3 extract_scale(const Mat4X4& mat) noexcept { return mat_extract_scale(mat); } [[nodiscard]] - OMATH_CONSTEXPR ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept + inline OMATH_CONSTEXPR ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept { const auto angles = mat_extract_rotation_zyx(mat); return { @@ -69,8 +69,7 @@ namespace omath::cry_engine }; } [[nodiscard]] - OMATH_CONSTEXPR Mat4X4 - calc_perspective_projection_matrix( + inline OMATH_CONSTEXPR Mat4X4 calc_perspective_projection_matrix( const float field_of_view, const float aspect_ratio, const float near, const float far, const NDCDepthRange ndc_depth_range = NDCDepthRange::ZERO_TO_ONE) noexcept {