From 71b2737f6dfcf1a3b234b9dadad0adc96929bd02 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 8 May 2024 18:15:01 +0300 Subject: [PATCH 001/795] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 3e6a8658..966e07b8 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,7 @@ The Universal Math Library (UML) is a comprehensive, open-source library aimed a ### Installation 1. Clone the repository: ``` - git clone https://github.com/VladislavAlpatov/uml.git + git clone https://github.com/orange-cpp/uml.git ``` 2. Navigate to the project directory: ``` From 53bd4510727c76f4bb43023ce45870c081a288f3 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 13 May 2024 21:25:38 +0300 Subject: [PATCH 002/795] update --- source/ProjectilePredictor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ProjectilePredictor.cpp b/source/ProjectilePredictor.cpp index c2fe2b61..e94e1bec 100644 --- a/source/ProjectilePredictor.cpp +++ b/source/ProjectilePredictor.cpp @@ -97,7 +97,7 @@ namespace uml::prediction auto currentPos = projectile.m_origin + velocity * time; currentPos.z -= m_gravity * projectile.m_gravityMultiplier * std::pow(time, 2.f) * 0.5f; - if (currentPos.DistTo(end) <= 25.f) + if (currentPos.DistTo(end) <= 10.f) return time; } From 4e01f3ee0985508dfb3ef3582f545ca05d76492b Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 18 May 2024 13:28:04 +0300 Subject: [PATCH 003/795] added to_string method to matrix --- include/uml/matrix.h | 42 ++++++++++++++++++++++++++++++---------- source/matrix.cpp | 32 +++++++++++++++++++++++++----- tests/CMakeLists.txt | 2 +- tests/UnitTestMatrix.cpp | 15 ++++++++++++++ 4 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 tests/UnitTestMatrix.cpp diff --git a/include/uml/matrix.h b/include/uml/matrix.h index 25a13050..afb09f6b 100644 --- a/include/uml/matrix.h +++ b/include/uml/matrix.h @@ -1,6 +1,8 @@ #pragma once #include #include +#include + namespace uml { @@ -13,7 +15,8 @@ namespace uml explicit matrix(const std::vector> &rows); - [[nodiscard]] static matrix to_screen_matrix(float screenWidth, float screenHeight); + [[nodiscard]] + static matrix to_screen_matrix(float screenWidth, float screenHeight); matrix(const matrix &other); @@ -21,21 +24,30 @@ namespace uml matrix(matrix &&other) noexcept; - [[nodiscard]] size_t get_rows_count() const noexcept; + [[nodiscard]] + size_t get_rows_count() const noexcept; - [[nodiscard]] size_t get_columns_count() const noexcept; + [[nodiscard]] + size_t get_columns_count() const noexcept; - [[nodiscard]] std::pair get_size() const noexcept; + [[nodiscard]] + std::pair get_size() const noexcept; + [[nodiscard]] float &at(size_t iRow, size_t iCol); + [[nodiscard]] float get_sum(); + void set_from_raw(const float* pRawMatrix); + + [[nodiscard]] matrix transpose(); void set(float val); - [[nodiscard]] const float &at(size_t iRow, size_t iCol) const; + [[nodiscard]] + const float &at(size_t iRow, size_t iCol) const; matrix operator*(const matrix &other) const; @@ -49,20 +61,30 @@ namespace uml void clear(); - [[nodiscard]] matrix strip(size_t row, size_t column) const; + [[nodiscard]] + matrix strip(size_t row, size_t column) const; - [[nodiscard]] float minor(size_t i, size_t j) const; + [[nodiscard]] + float minor(size_t i, size_t j) const; - [[nodiscard]] float alg_complement(size_t i, size_t j) const; + [[nodiscard]] + float alg_complement(size_t i, size_t j) const; + + [[nodiscard]] + float det() const; + + [[nodiscard]] + const float* raw() const; - [[nodiscard]] float det() const; - [[nodiscard]] const float* raw() const; matrix &operator=(const matrix &other); matrix &operator=(matrix &&other) noexcept; matrix operator/(float f) const; + [[nodiscard]] + std::string to_string() const; + ~matrix(); private: diff --git a/source/matrix.cpp b/source/matrix.cpp index bb511ffc..b606beb0 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -4,6 +4,9 @@ */ #include "uml/matrix.h" + +#include + #include "uml/Vector3.h" #include #include @@ -204,6 +207,25 @@ namespace uml return out; } + std::string matrix::to_string() const + { + std::string str; + + for (size_t i = 0; i < m_rows; i++) + { + for (size_t j = 0; j < m_columns; ++j) + { + str += std::format("{:.1f}",at(i, j)); + + if (j == m_columns-1) + str += '\n'; + else + str += ' '; + } + } + return str; + } + float matrix::det() const { if (m_rows + m_columns == 2) @@ -279,11 +301,11 @@ namespace uml matrix matrix::to_screen_matrix(float screenWidth, float screenHeight) { return matrix({ - {screenWidth / 2.f, 0.f, 0.f, 0.f}, - {0.f, -screenHeight / 2.f, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {screenWidth / 2.f, screenHeight / 2.f, 0.f, 1.f}, - }); + {screenWidth / 2.f, 0.f, 0.f, 0.f}, + {0.f, -screenHeight / 2.f, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {screenWidth / 2.f, screenHeight / 2.f, 0.f, 1.f}, + }); } const float * matrix::raw() const diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aaefac72..12feaf59 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,6 @@ project(unit-tests) file(GLOB TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include(GoogleTest) -add_executable(unit-tests UnitTestColor.cpp) +add_executable(unit-tests UnitTestColor.cpp UnitTestMatrix.cpp) target_link_libraries(unit-tests PRIVATE gtest gtest_main uml) \ No newline at end of file diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp new file mode 100644 index 00000000..cad8f5d5 --- /dev/null +++ b/tests/UnitTestMatrix.cpp @@ -0,0 +1,15 @@ +// +// Created by vlad on 5/18/2024. +// +#include +#include +#include +TEST(UnitTestMatrix, ToString) +{ + uml::matrix matrix(2, 2); + matrix.set(1.1); + const auto str = matrix.to_string(); + + std::cout << str; + +} \ No newline at end of file From 7ea5cecff0ef942eb2dca563bde3d7857a1761b7 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 18 May 2024 13:40:29 +0300 Subject: [PATCH 004/795] added some nodiscards --- .idea/vcs.xml | 1 + include/uml/ProjectilePredictor.h | 7 +++---- include/uml/Vector4.h | 24 ++++++++++++++++++++++-- include/uml/color.h | 2 +- source/Vector4.cpp | 25 ++++++++++++++++++------- source/color.cpp | 10 ++++++---- tests/UnitTestMatrix.cpp | 2 ++ 7 files changed, 53 insertions(+), 18 deletions(-) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1ddf..adc159a8 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/include/uml/ProjectilePredictor.h b/include/uml/ProjectilePredictor.h index 1a18b347..ad7dc470 100644 --- a/include/uml/ProjectilePredictor.h +++ b/include/uml/ProjectilePredictor.h @@ -4,7 +4,6 @@ #pragma once #include -#include #include @@ -35,7 +34,6 @@ namespace uml::prediction const Projectile& projectile) const; private: - const float m_gravity; const float m_maxTravelTime; const float m_timeStepSize; @@ -49,8 +47,9 @@ namespace uml::prediction const Vector3& targetPosition) const; [[nodiscard]] - std::optional ProjectileTravelTime(const Vector3& end, const Projectile& projectile, - const float angle) const; + std::optional ProjectileTravelTime(const Vector3& end, + const Projectile& projectile, + float angle) const; }; }; diff --git a/include/uml/Vector4.h b/include/uml/Vector4.h index 6d928f4b..a09a9847 100644 --- a/include/uml/Vector4.h +++ b/include/uml/Vector4.h @@ -12,10 +12,13 @@ namespace uml public: float w = 0.f; - Vector4(float x = 0.f, float y = 0.f, float z = 0.f, float w = 0.f) : Vector3(x, y, z), w(w) {} + Vector4(const float x = 0.f, const float y = 0.f, const float z = 0.f, const float w = 0.f) : Vector3(x, y, z), w(w) {} Vector4() = default; + [[nodiscard]] bool operator==(const Vector4& src) const; + + [[nodiscard]] bool operator!=(const Vector4& src) const; Vector4& operator+=(const Vector4& v); @@ -28,15 +31,32 @@ namespace uml [[nodiscard]] float Length() const; [[nodiscard]] float LengthSqr() const; [[nodiscard]] float Dot(const Vector4& vOther) const; + Vector4& Abs(); + Vector4& Clamp(float min, float max); + + [[nodiscard]] Vector4 operator-() const; + + [[nodiscard]] Vector4 operator+(const Vector4& v) const; + + [[nodiscard]] Vector4 operator-(const Vector4& v) const; + + [[nodiscard]] Vector4 operator*(float scalar) const; + + [[nodiscard]] Vector4 operator*(const Vector4& v) const; + + [[nodiscard]] Vector4 operator/(float scalar) const; + + [[nodiscard]] Vector4 operator/(const Vector4& v) const; - [[nodiscard]] float Sum() const; + [[nodiscard]] + float Sum() const; }; } diff --git a/include/uml/color.h b/include/uml/color.h index f811395a..d5c05ffc 100644 --- a/include/uml/color.h +++ b/include/uml/color.h @@ -19,7 +19,7 @@ namespace uml::color public: Color(float r, float g, float b, float a); static Color FromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); - explicit Color(const Vector4& vec); + explicit Color(Vector4 vec); [[nodiscard]] Color Blend(const Color& other, float ratio) const; [[nodiscard]] static Color Red() {return {1.f, 0.f, 0.f, 1.f};} diff --git a/source/Vector4.cpp b/source/Vector4.cpp index fadd1778..62a834b3 100644 --- a/source/Vector4.cpp +++ b/source/Vector4.cpp @@ -3,6 +3,8 @@ // #include "uml/Vector4.h" + +#include #include namespace uml @@ -81,39 +83,48 @@ namespace uml return *this; } + Vector4& Vector4::Clamp(const float min, const float max) + { + x = std::clamp(x, min, max); + y = std::clamp(y, min, max); + z = std::clamp(z, min, max); + + return *this; + } + Vector4 Vector4::operator-() const { - return Vector4(-x, -y, -z, -w); + return {-x, -y, -z, -w}; } Vector4 Vector4::operator+(const Vector4& v) const { - return Vector4(x + v.x, y + v.y, z + v.z, w + v.w); + return {x + v.x, y + v.y, z + v.z, w + v.w}; } Vector4 Vector4::operator-(const Vector4& v) const { - return Vector4(x - v.x, y - v.y, z - v.z, w - v.w); + return {x - v.x, y - v.y, z - v.z, w - v.w}; } Vector4 Vector4::operator*(float scalar) const { - return Vector4(x * scalar, y * scalar, z * scalar, w * scalar); + return {x * scalar, y * scalar, z * scalar, w * scalar}; } Vector4 Vector4::operator*(const Vector4& v) const { - return Vector4(x * v.x, y * v.y, z * v.z, w * v.w); + return {x * v.x, y * v.y, z * v.z, w * v.w}; } Vector4 Vector4::operator/(float scalar) const { - return Vector4(x / scalar, y / scalar, z / scalar, w / scalar); + return {x / scalar, y / scalar, z / scalar, w / scalar}; } Vector4 Vector4::operator/(const Vector4& v) const { - return Vector4(x / v.x, y / v.y, z / v.z, w / v.w); + return {x / v.x, y / v.y, z / v.z, w / v.w}; } float Vector4::Sum() const diff --git a/source/color.cpp b/source/color.cpp index 876d0566..d056008d 100644 --- a/source/color.cpp +++ b/source/color.cpp @@ -17,20 +17,22 @@ namespace uml::color return Color( (*this * (1.f - ratio)) + (other * ratio) ); } - Color::Color(float r, float g, float b, float a) : Vector4(r,g,b,a) + Color::Color(const float r, const float g, const float b, const float a) + : Vector4(std::clamp(r, 0.f, 1.f), + std::clamp(g, 0.f, 1.f), + std::clamp(b, 0.f, 1.f), + std::clamp(a, 0.f, 1.f)) { } - Color::Color(const Vector4 &vec) : Vector4(vec) + Color::Color(Vector4 vec) : Vector4(vec.Clamp(0.f, 1.f)) { } Color Color::FromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) { - return Color{Vector4(r, g, b, a) / 255.f}; - } } \ No newline at end of file diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index cad8f5d5..ecf06933 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -4,6 +4,8 @@ #include #include #include + + TEST(UnitTestMatrix, ToString) { uml::matrix matrix(2, 2); From d4356257573244fd754133e16da05755909766b9 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 18 May 2024 13:58:54 +0300 Subject: [PATCH 005/795] added hsv stuff --- include/uml/color.h | 20 ++++++++++++++- source/color.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/include/uml/color.h b/include/uml/color.h index d5c05ffc..f3f37953 100644 --- a/include/uml/color.h +++ b/include/uml/color.h @@ -11,6 +11,13 @@ namespace uml::color { + struct HSV + { + float m_hue{}; + float m_saturation{}; + float m_value{}; + }; + [[nodiscard]] Vector3 Blend(const Vector3& first, const Vector3& second, float ratio); @@ -18,9 +25,20 @@ namespace uml::color { public: Color(float r, float g, float b, float a); + + [[nodiscard]] static Color FromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + + [[nodiscard]] + static Color FromHSV(float hue, float saturation, float value); + + [[nodiscard]] + HSV ToHSV() const; + explicit Color(Vector4 vec); - [[nodiscard]] Color Blend(const Color& other, float ratio) const; + + [[nodiscard]] + Color Blend(const Color& other, float ratio) const; [[nodiscard]] static Color Red() {return {1.f, 0.f, 0.f, 1.f};} [[nodiscard]] static Color Green() {return {0.f, 1.f, 0.f, 1.f};} diff --git a/source/color.cpp b/source/color.cpp index d056008d..0d093196 100644 --- a/source/color.cpp +++ b/source/color.cpp @@ -4,6 +4,8 @@ #include "uml/color.h" #include +#include + namespace uml::color { @@ -35,4 +37,62 @@ namespace uml::color { return Color{Vector4(r, g, b, a) / 255.f}; } -} \ No newline at end of file + + Color Color::FromHSV(float hue, const float saturation, const float value) { + float r{}, g{}, b{}; + + hue = std::clamp(hue, 0.f, 1.f); + + const int i = static_cast(hue * 6.f); + const float f = hue * 6 - i; + const float p = value * (1 - saturation); + const float q = value * (1 - f * saturation); + const float t = value * (1 - (1 - f) * saturation); + + switch (i % 6) + { + case 0: r = value, g = t, b = p; break; + case 1: r = q, g = value, b = p; break; + case 2: r = p, g = value, b = t; break; + case 3: r = p, g = q, b = value; break; + case 4: r = t, g = p, b = value; break; + case 5: r = value, g = p, b = q; break; + + default: return {0.f, 0.f, 0.f, 0.f}; + } + + return {r, g, b, 1.f}; + } + + HSV Color::ToHSV() const { + HSV hsvData; + + const float& red = x; + const float& green = y; + const float& blue = z; + + const float max = std::max({red, green, blue}); + const float min = std::min({red, green, blue}); + const float delta = max - min; + + + if (delta == 0.f) + hsvData.m_hue = 0.f; + + else if (max == red) + hsvData.m_hue = 60.f * (std::fmodf(((green - blue) / delta), 6.f)); + else if (max == green) + hsvData.m_hue = 60.f * (((blue - red) / delta) + 2.f); + else if (max == blue) + hsvData.m_hue = 60.f * (((red - green) / delta) + 4.f); + + if (hsvData.m_hue < 0.f) + hsvData.m_hue += 360.f; + + hsvData.m_hue /= 360.f; + hsvData.m_saturation = max == 0.f ? 0.f : delta / max; + hsvData.m_value = max; + + return hsvData; + } +} From c600d53b20867c0ee4207594f229318473b937f2 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 26 May 2024 08:01:05 +0300 Subject: [PATCH 006/795] updated projectile prediction --- include/uml/ProjectilePredictor.h | 3 +++ source/ProjectilePredictor.cpp | 20 ++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/include/uml/ProjectilePredictor.h b/include/uml/ProjectilePredictor.h index ad7dc470..20a0a75b 100644 --- a/include/uml/ProjectilePredictor.h +++ b/include/uml/ProjectilePredictor.h @@ -50,6 +50,9 @@ namespace uml::prediction std::optional ProjectileTravelTime(const Vector3& end, const Projectile& projectile, float angle) const; + + [[nodiscard]] + bool IsTargetWasHit(const Vector3& end, const Projectile& projectile, float angle, float time) const; }; }; diff --git a/source/ProjectilePredictor.cpp b/source/ProjectilePredictor.cpp index e94e1bec..71454523 100644 --- a/source/ProjectilePredictor.cpp +++ b/source/ProjectilePredictor.cpp @@ -32,10 +32,7 @@ namespace uml::prediction if (!projectilePitch.has_value()) [[unlikely]] return std::nullopt; - const auto timeToHit = ProjectileTravelTime(predictedTargetPosition, - projectile, - projectilePitch.value()); - if (!timeToHit.has_value() || timeToHit.value() > time) + if (!IsTargetWasHit(predictedTargetPosition, projectile, projectilePitch.value(), time)) continue; const auto delta2d = (predictedTargetPosition - projectile.m_origin).Length2D(); @@ -103,4 +100,19 @@ namespace uml::prediction return std::nullopt; } + + bool ProjectilePredictor::IsTargetWasHit(const Vector3 &end, const Projectile &projectile, const float angle, + const float time) const + { + auto launchAngles = projectile.m_origin.ViewAngleTo(end); + launchAngles.x = angle; + + const auto velocity = Vector3::CreateVelocity(launchAngles, projectile.m_velocity); + + auto currentPos = projectile.m_origin + velocity * time; + currentPos.z -= m_gravity * projectile.m_gravityMultiplier * std::pow(time, 2.f) * 0.5f; + + + return currentPos.DistTo(end) <= 10.f; + } } From 07360f4e915473420b5e88c327a64615dd414870 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 9 Jun 2024 21:14:33 +0300 Subject: [PATCH 007/795] refactored projectile prediction --- CMakeLists.txt | 3 +- include/uml/ProjectilePredictor.h | 58 -------------- include/uml/Vector3.h | 2 +- include/uml/angles.h | 4 +- include/uml/prediction/Engine.h | 37 +++++++++ include/uml/prediction/Projectile.h | 25 ++++++ include/uml/prediction/Target.h | 22 ++++++ source/CMakeLists.txt | 5 +- source/ProjectilePredictor.cpp | 118 ---------------------------- source/Vector3.cpp | 12 +-- source/angles.cpp | 8 +- source/prediction/CMakeLists.txt | 1 + source/prediction/Engine.cpp | 71 +++++++++++++++++ source/prediction/Projectile.cpp | 23 ++++++ source/prediction/Target.cpp | 20 +++++ tests/UnitTestColor.cpp | 10 +-- 16 files changed, 222 insertions(+), 197 deletions(-) delete mode 100644 include/uml/ProjectilePredictor.h create mode 100644 include/uml/prediction/Engine.h create mode 100644 include/uml/prediction/Projectile.h create mode 100644 include/uml/prediction/Target.h delete mode 100644 source/ProjectilePredictor.cpp create mode 100644 source/prediction/CMakeLists.txt create mode 100644 source/prediction/Engine.cpp create mode 100644 source/prediction/Projectile.cpp create mode 100644 source/prediction/Target.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dfef162..87ccf280 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,8 @@ project(uml) set(CMAKE_CXX_STANDARD 26) option(BUILD_TESTS "Build test programs" ON) -add_library(uml STATIC source/Vector3.cpp) +add_library(uml STATIC source/Vector3.cpp + include/uml/prediction/engine.h) add_subdirectory(source) add_subdirectory(extlibs) diff --git a/include/uml/ProjectilePredictor.h b/include/uml/ProjectilePredictor.h deleted file mode 100644 index 20a0a75b..00000000 --- a/include/uml/ProjectilePredictor.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// Created by vlad on 11/6/23. -// - -#pragma once -#include -#include - - -namespace uml::prediction -{ - struct Projectile - { - Vector3 m_origin; - float m_velocity{}; - float m_gravityMultiplier = 1.f; - }; - struct Target - { - Vector3 m_origin; - Vector3 m_vecVelocity; - bool m_IsAirborne; - }; - class ProjectilePredictor - { - public: - explicit ProjectilePredictor(float gravityValue, - float maxTimeToTravel = 3.f, - float timeStep = 0.1f); - - - [[nodiscard]] - std::optional PredictPointToAim(const Target& target, - const Projectile& projectile) const; - - private: - const float m_gravity; - const float m_maxTravelTime; - const float m_timeStepSize; - - [[nodiscard]] - Vector3 LinearPrediction(const Target& target, float time) const; - - [[nodiscard]] - std::optional - MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, - const Vector3& targetPosition) const; - - [[nodiscard]] - std::optional ProjectileTravelTime(const Vector3& end, - const Projectile& projectile, - float angle) const; - - [[nodiscard]] - bool IsTargetWasHit(const Vector3& end, const Projectile& projectile, float angle, float time) const; - }; - -}; diff --git a/include/uml/Vector3.h b/include/uml/Vector3.h index 06f6507d..ea4348ab 100644 --- a/include/uml/Vector3.h +++ b/include/uml/Vector3.h @@ -57,7 +57,7 @@ namespace uml { return *reinterpret_cast(this); } - [[nodiscard]] static Vector3 CreateVelocity(const Vector3& angles, float length); + [[nodiscard]] static Vector3 CreateVelocity(float pitch, float yaw, float speed); [[nodiscard]] float Sum() const; [[nodiscard]] float Sum2D() const; [[nodiscard]] Vector3 ViewAngleTo(const Vector3& other) const; diff --git a/include/uml/angles.h b/include/uml/angles.h index 67268967..39b9a07b 100644 --- a/include/uml/angles.h +++ b/include/uml/angles.h @@ -6,6 +6,6 @@ namespace uml::angles { - [[nodiscard]] float RadToDeg(float rads); - [[nodiscard]] float DegToRad(float degrees); + [[nodiscard]] float RadiansToDegrees(float rads); + [[nodiscard]] float DegreesToRadians(float degrees); } \ No newline at end of file diff --git a/include/uml/prediction/Engine.h b/include/uml/prediction/Engine.h new file mode 100644 index 00000000..99712719 --- /dev/null +++ b/include/uml/prediction/Engine.h @@ -0,0 +1,37 @@ +// +// Created by Vlad on 6/9/2024. +// + +#pragma once + +#include +#include "uml/Vector3.h" +#include "uml/prediction/Projectile.h" +#include "uml/prediction/Target.h" + + +namespace uml::prediction +{ + class Engine + { + public: + explicit Engine(float gravityConstant, float simulationTimeStep, float maximumSimulationTime); + + [[nodiscard]] + std::optional MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const; + + private: + const float m_gravityConstant; + const float m_simulationTimeStep; + const float m_maximumSimulationTime; + + [[nodiscard]] + std::optional MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, + const Vector3& targetPosition) const; + + + [[nodiscard]] + bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, float time) const; + + }; +} \ No newline at end of file diff --git a/include/uml/prediction/Projectile.h b/include/uml/prediction/Projectile.h new file mode 100644 index 00000000..58603bd9 --- /dev/null +++ b/include/uml/prediction/Projectile.h @@ -0,0 +1,25 @@ +// +// Created by Vlad on 6/9/2024. +// + +#pragma once +#include "uml/Vector3.h" + + +namespace uml::prediction +{ + class Projectile final + { + public: + + [[nodiscard]] + Vector3 CalculateVelocity(float pitch, float yaw) const; + + [[nodiscard]] + Vector3 PredictPosition(float pitch, float yaw, float time, float gravity) const; + + Vector3 m_origin; + float m_launchSpeed{}; + float m_gravityScale{}; + }; +} \ No newline at end of file diff --git a/include/uml/prediction/Target.h b/include/uml/prediction/Target.h new file mode 100644 index 00000000..db5eb465 --- /dev/null +++ b/include/uml/prediction/Target.h @@ -0,0 +1,22 @@ +// +// Created by Vlad on 6/9/2024. +// + +#pragma once +#include "uml/Vector3.h" + + +namespace uml::prediction +{ + class Target final + { + public: + + [[nodiscard]] + Vector3 PredictPosition(float time, float gravity) const; + + Vector3 m_origin; + Vector3 m_velocity; + bool m_isAirborne{}; + }; +} \ No newline at end of file diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 80bd6363..c8faebe0 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -2,6 +2,7 @@ target_sources(uml PRIVATE Vector3.cpp matrix.cpp angles.cpp - ProjectilePredictor.cpp color.cpp - Vector4.cpp) \ No newline at end of file + Vector4.cpp) + +add_subdirectory(prediction) \ No newline at end of file diff --git a/source/ProjectilePredictor.cpp b/source/ProjectilePredictor.cpp deleted file mode 100644 index 71454523..00000000 --- a/source/ProjectilePredictor.cpp +++ /dev/null @@ -1,118 +0,0 @@ -// -// Created by vlad on 11/6/23. -// - -#include "uml/ProjectilePredictor.h" -#include "uml/Vector3.h" -#include "uml/angles.h" - -#include -#include - -namespace uml::prediction -{ - ProjectilePredictor::ProjectilePredictor(float gravityValue, - float maxTimeToTravel, - float timeStep) - : m_gravity(gravityValue), m_maxTravelTime(maxTimeToTravel), m_timeStepSize(timeStep) - { - - } - - std::optional ProjectilePredictor::PredictPointToAim( - const Target &target, const Projectile &projectile) const - { - for (float time = 0.0f; time <= m_maxTravelTime; time += m_timeStepSize) - { - auto predictedTargetPosition = LinearPrediction(target, time); - - const auto projectilePitch = - MaybeCalculateProjectileLaunchPitchAngle(projectile, predictedTargetPosition); - - if (!projectilePitch.has_value()) [[unlikely]] - return std::nullopt; - - if (!IsTargetWasHit(predictedTargetPosition, projectile, projectilePitch.value(), time)) - continue; - - const auto delta2d = (predictedTargetPosition - projectile.m_origin).Length2D(); - const auto height = delta2d * std::tan(angles::DegToRad(projectilePitch.value())); - - predictedTargetPosition.z = projectile.m_origin.z + height; - - return predictedTargetPosition; - } - - - return std::nullopt; - } - - Vector3 ProjectilePredictor::LinearPrediction(const Target &target, float time) const - { - auto predicted = target.m_origin + target.m_vecVelocity * time; - - if (target.m_IsAirborne) - predicted.z -= m_gravity * std::pow(time, 2.f) * 0.5f; - - return predicted; - } - - std::optional - ProjectilePredictor::MaybeCalculateProjectileLaunchPitchAngle(const Projectile &projectile, - const Vector3 &targetPosition) - const - { - const auto bulletGravity = m_gravity * projectile.m_gravityMultiplier; - const auto delta = targetPosition - projectile.m_origin;; - - const auto distance2d = delta.Length2D(); - - - float root = std::pow(projectile.m_velocity, 4.f) - bulletGravity * (bulletGravity * - std::pow(distance2d, 2.f) + 2.0f * delta.z * std::pow(projectile.m_velocity, 2.f)); - - if (root < 0.0f) [[unlikely]] - return std::nullopt; - - root = std::sqrt(root); - const float angle = std::atan((std::pow(projectile.m_velocity, 2.f) - root) / (bulletGravity * distance2d)); - - return angles::RadToDeg(angle); - } - - std::optional ProjectilePredictor::ProjectileTravelTime(const Vector3 &end, - const Projectile &projectile, - const float angle) const - { - auto launchAngles = projectile.m_origin.ViewAngleTo(end); - launchAngles.x = angle; - - const auto velocity = Vector3::CreateVelocity(launchAngles, projectile.m_velocity); - - for (float time = 0.0f; time <= m_maxTravelTime; time += m_timeStepSize) - { - auto currentPos = projectile.m_origin + velocity * time; - currentPos.z -= m_gravity * projectile.m_gravityMultiplier * std::pow(time, 2.f) * 0.5f; - - if (currentPos.DistTo(end) <= 10.f) - return time; - } - - return std::nullopt; - } - - bool ProjectilePredictor::IsTargetWasHit(const Vector3 &end, const Projectile &projectile, const float angle, - const float time) const - { - auto launchAngles = projectile.m_origin.ViewAngleTo(end); - launchAngles.x = angle; - - const auto velocity = Vector3::CreateVelocity(launchAngles, projectile.m_velocity); - - auto currentPos = projectile.m_origin + velocity * time; - currentPos.z -= m_gravity * projectile.m_gravityMultiplier * std::pow(time, 2.f) * 0.5f; - - - return currentPos.DistTo(end) <= 10.f; - } -} diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 47698891..7fb27f7e 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -177,13 +177,13 @@ namespace uml return {x / v.x, y / v.y, z / v.z}; } - Vector3 Vector3::CreateVelocity(const Vector3 &angles, const float length) + Vector3 Vector3::CreateVelocity(const float pitch, const float yaw, const float speed) { return { - std::cos(angles::DegToRad(angles.x)) * std::cos(angles::DegToRad(angles.y)) * length, - std::cos(angles::DegToRad(angles.x)) * std::sin(angles::DegToRad(angles.y)) * length, - std::sin(angles::DegToRad(angles.x)) * length, + std::cos(angles::DegreesToRadians(pitch)) * std::cos(angles::DegreesToRadians(yaw)) * speed, + std::cos(angles::DegreesToRadians(pitch)) * std::sin(angles::DegreesToRadians(yaw)) * speed, + std::sin(angles::DegreesToRadians(pitch)) * speed, }; } @@ -205,8 +205,8 @@ namespace uml // Make x negative since -89 is top and 89 is bottom return { - -angles::RadToDeg(asinf(delta.z / distance)), - angles::RadToDeg(atan2f(delta.y, delta.x)), + -angles::RadiansToDegrees(asinf(delta.z / distance)), + angles::RadiansToDegrees(atan2f(delta.y, delta.x)), 0.f }; } diff --git a/source/angles.cpp b/source/angles.cpp index 01950901..26b9f0fe 100644 --- a/source/angles.cpp +++ b/source/angles.cpp @@ -8,13 +8,13 @@ namespace uml::angles { - float RadToDeg(float rads) + float RadiansToDegrees(const float radiands) { - return rads * 180.f / std::numbers::pi_v; + return radiands * (180.f / std::numbers::pi_v); } - float DegToRad(float degrees) + float DegreesToRadians(const float degrees) { - return degrees * std::numbers::pi_v / 180.f; + return degrees * (std::numbers::pi_v / 180.f); } } diff --git a/source/prediction/CMakeLists.txt b/source/prediction/CMakeLists.txt new file mode 100644 index 00000000..6964df71 --- /dev/null +++ b/source/prediction/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(uml PRIVATE Engine.cpp Projectile.cpp Target.cpp) \ No newline at end of file diff --git a/source/prediction/Engine.cpp b/source/prediction/Engine.cpp new file mode 100644 index 00000000..2205fce7 --- /dev/null +++ b/source/prediction/Engine.cpp @@ -0,0 +1,71 @@ +// +// Created by Vlad on 6/9/2024. +// + + +#include "uml/prediction/Engine.h" +#include +#include + + +namespace uml::prediction +{ + Engine::Engine(const float gravityConstant, const float simulationTimeStep, const float maximumSimulationTime) + : m_gravityConstant(gravityConstant), + m_simulationTimeStep(simulationTimeStep), + m_maximumSimulationTime(maximumSimulationTime) + { + } + + std::optional Engine::MaybeCalculateAimPoint(const Projectile &projectile, const Target &target) const + { + for (float time = 0.f; time < m_maximumSimulationTime; time += m_simulationTimeStep) + { + const auto predictedTargetPosition = target.PredictPosition(time, m_gravityConstant); + + const auto projectilePitch = MaybeCalculateProjectileLaunchPitchAngle(projectile, predictedTargetPosition); + + if (!projectilePitch.has_value()) [[unlikely]] + continue; + + if (!IsProjectileReachedTarget(predictedTargetPosition, projectile, projectilePitch.value(), time)) + continue; + + const auto delta2d = (predictedTargetPosition - projectile.m_origin).Length2D(); + const auto height = delta2d * std::tan(angles::DegreesToRadians(projectilePitch.value())); + + return Vector3(predictedTargetPosition.x, predictedTargetPosition.y, projectile.m_origin.z + height); + } + return std::nullopt; + } + + std::optional Engine::MaybeCalculateProjectileLaunchPitchAngle(const Projectile &projectile, + const Vector3 &targetPosition) const + { + const auto bulletGravity = m_gravityConstant * projectile.m_gravityScale; + const auto delta = targetPosition - projectile.m_origin;; + + const auto distance2d = delta.Length2D(); + + + float root = std::pow(projectile.m_launchSpeed, 4.f) - bulletGravity * (bulletGravity * + std::pow(distance2d, 2.f) + 2.0f * delta.z * std::pow(projectile.m_launchSpeed, 2.f)); + + if (root < 0.0f) [[unlikely]] + return std::nullopt; + + root = std::sqrt(root); + const float angle = std::atan((std::pow(projectile.m_launchSpeed, 2.f) - root) / (bulletGravity * distance2d)); + + return angles::RadiansToDegrees(angle); + } + + bool Engine::IsProjectileReachedTarget(const Vector3 &targetPosition, const Projectile &projectile, + const float pitch, const float time) const + { + const auto yaw = projectile.m_origin.ViewAngleTo(targetPosition).y; + const auto projectilePosition = projectile.PredictPosition(pitch, yaw, time, m_gravityConstant); + + return projectilePosition.DistTo(targetPosition) <= 10.f; + } +} diff --git a/source/prediction/Projectile.cpp b/source/prediction/Projectile.cpp new file mode 100644 index 00000000..e65144d2 --- /dev/null +++ b/source/prediction/Projectile.cpp @@ -0,0 +1,23 @@ +// +// Created by Vlad on 6/9/2024. +// + +#include "uml/prediction/Projectile.h" +#include + + +namespace uml::prediction +{ + Vector3 Projectile::CalculateVelocity(const float pitch, const float yaw) const + { + return Vector3::CreateVelocity(pitch, yaw, m_launchSpeed); + } + + Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const + { + auto currentPos = m_origin + Vector3::CreateVelocity(pitch, yaw, m_launchSpeed) * time; + currentPos.z -= (gravity * m_gravityScale) * std::pow(time, 2.f) * 0.5f; + + return currentPos; + } +} diff --git a/source/prediction/Target.cpp b/source/prediction/Target.cpp new file mode 100644 index 00000000..9783897c --- /dev/null +++ b/source/prediction/Target.cpp @@ -0,0 +1,20 @@ +// +// Created by Vlad on 6/9/2024. +// + +#include "uml/prediction/Target.h" +#include + + +namespace uml::prediction +{ + Vector3 Target::PredictPosition(const float time, const float gravity) const + { + auto predicted = m_origin + m_velocity * time; + + if (m_isAirborne) + predicted.z -= gravity * std::pow(time, 2.f) * 0.5f; + + return predicted; + } +} diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp index 3e45514b..02700e5a 100644 --- a/tests/UnitTestColor.cpp +++ b/tests/UnitTestColor.cpp @@ -1,13 +1,13 @@ #include -#include +#include TEST(x,x) { - uml::prediction::Target target{.m_origin = {100, 0, 60}, .m_vecVelocity = {0,0, 0}, .m_IsAirborne = false}; - uml::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_velocity = 400, .m_gravityMultiplier= 0.4}; - auto vel = uml::prediction::ProjectilePredictor(400).PredictPointToAim(target, proj); + uml::prediction::Target target{.m_origin = {100, 0, 60}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; + uml::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 400, .m_gravityScale= 0.4}; + auto vel = uml::prediction::Engine(400, 1.f / 10000.f, 50).MaybeCalculateAimPoint(proj, target); auto pitch = proj.m_origin.ViewAngleTo(vel.value()).x; - // printf("pitch: %f", pitch); + printf("pitch: %f", pitch); } \ No newline at end of file From dd77a5ef8f9c0c4cfed34a05038b6823cb0e76cd Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 9 Jun 2024 21:19:10 +0300 Subject: [PATCH 008/795] added new param --- include/uml/prediction/Engine.h | 4 +++- source/prediction/Engine.cpp | 8 +++++--- tests/UnitTestColor.cpp | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/include/uml/prediction/Engine.h b/include/uml/prediction/Engine.h index 99712719..8a944889 100644 --- a/include/uml/prediction/Engine.h +++ b/include/uml/prediction/Engine.h @@ -15,7 +15,8 @@ namespace uml::prediction class Engine { public: - explicit Engine(float gravityConstant, float simulationTimeStep, float maximumSimulationTime); + explicit Engine(float gravityConstant, float simulationTimeStep, + float maximumSimulationTime, float distanceTolerance); [[nodiscard]] std::optional MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const; @@ -24,6 +25,7 @@ namespace uml::prediction const float m_gravityConstant; const float m_simulationTimeStep; const float m_maximumSimulationTime; + const float m_distanceTolerance; [[nodiscard]] std::optional MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, diff --git a/source/prediction/Engine.cpp b/source/prediction/Engine.cpp index 2205fce7..09a58772 100644 --- a/source/prediction/Engine.cpp +++ b/source/prediction/Engine.cpp @@ -10,10 +10,12 @@ namespace uml::prediction { - Engine::Engine(const float gravityConstant, const float simulationTimeStep, const float maximumSimulationTime) + Engine::Engine(const float gravityConstant, const float simulationTimeStep, + const float maximumSimulationTime, float distanceTolerance) : m_gravityConstant(gravityConstant), m_simulationTimeStep(simulationTimeStep), - m_maximumSimulationTime(maximumSimulationTime) + m_maximumSimulationTime(maximumSimulationTime), + m_distanceTolerance(distanceTolerance) { } @@ -66,6 +68,6 @@ namespace uml::prediction const auto yaw = projectile.m_origin.ViewAngleTo(targetPosition).y; const auto projectilePosition = projectile.PredictPosition(pitch, yaw, time, m_gravityConstant); - return projectilePosition.DistTo(targetPosition) <= 10.f; + return projectilePosition.DistTo(targetPosition) <= m_distanceTolerance; } } diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp index 02700e5a..3eb3ea02 100644 --- a/tests/UnitTestColor.cpp +++ b/tests/UnitTestColor.cpp @@ -4,8 +4,8 @@ TEST(x,x) { uml::prediction::Target target{.m_origin = {100, 0, 60}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; - uml::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 400, .m_gravityScale= 0.4}; - auto vel = uml::prediction::Engine(400, 1.f / 10000.f, 50).MaybeCalculateAimPoint(proj, target); + uml::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 5000, .m_gravityScale= 0.4}; + auto vel = uml::prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); auto pitch = proj.m_origin.ViewAngleTo(vel.value()).x; From aec4ef9a287ba8dac96224aade5cafd77ab89d8f Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 10 Jun 2024 00:39:43 +0300 Subject: [PATCH 009/795] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 966e07b8..c958a6de 100644 --- a/readme.md +++ b/readme.md @@ -55,4 +55,4 @@ Contributions to UML are welcome! Please read `CONTRIBUTING.md` for details on o This project is licensed under the GPL V3 - see the `LICENSE` file for details. ## Acknowledgments -- Vladislav Alpatov | [Telegram](https://t.me/nullifiedvlad) +- Orange | [Telegram](https://t.me/nullifiedvlad) From 403546fd89b9f631ee5f7a4ceb1b31674d417ea0 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 10 Jun 2024 00:40:00 +0300 Subject: [PATCH 010/795] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c958a6de..b4f201ef 100644 --- a/readme.md +++ b/readme.md @@ -55,4 +55,4 @@ Contributions to UML are welcome! Please read `CONTRIBUTING.md` for details on o This project is licensed under the GPL V3 - see the `LICENSE` file for details. ## Acknowledgments -- Orange | [Telegram](https://t.me/nullifiedvlad) +- Orange | [Telegram](https://t.me/orange-cpp) From 183cbf35e083433e7e253fffd5cd793804098672 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 10 Jun 2024 00:40:29 +0300 Subject: [PATCH 011/795] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b4f201ef..8beb299f 100644 --- a/readme.md +++ b/readme.md @@ -55,4 +55,4 @@ Contributions to UML are welcome! Please read `CONTRIBUTING.md` for details on o This project is licensed under the GPL V3 - see the `LICENSE` file for details. ## Acknowledgments -- Orange | [Telegram](https://t.me/orange-cpp) +- Orange | [Telegram](https://t.me/orange_cpp) From d697cc517fa9bce20a5956378954a4850724166d Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 12 Jun 2024 22:32:08 +0300 Subject: [PATCH 012/795] fixed cmake --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 87ccf280..2dfef162 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,7 @@ project(uml) set(CMAKE_CXX_STANDARD 26) option(BUILD_TESTS "Build test programs" ON) -add_library(uml STATIC source/Vector3.cpp - include/uml/prediction/engine.h) +add_library(uml STATIC source/Vector3.cpp) add_subdirectory(source) add_subdirectory(extlibs) From 528b8a0ca9be8dfcbd16dc849c53066c6c14456f Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 17 Jun 2024 01:35:16 +0300 Subject: [PATCH 013/795] added 2 methods --- include/uml/Vector3.h | 4 ++++ source/Vector3.cpp | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/uml/Vector3.h b/include/uml/Vector3.h index ea4348ab..947bbc82 100644 --- a/include/uml/Vector3.h +++ b/include/uml/Vector3.h @@ -57,9 +57,13 @@ namespace uml { return *reinterpret_cast(this); } + [[nodiscard]] Vector3 Cross(const Vector3 &v) const; [[nodiscard]] static Vector3 CreateVelocity(float pitch, float yaw, float speed); [[nodiscard]] float Sum() const; [[nodiscard]] float Sum2D() const; [[nodiscard]] Vector3 ViewAngleTo(const Vector3& other) const; + + [[nodiscard]] + Vector3 Normalized() const; }; } diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 7fb27f7e..8f0f8429 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -210,4 +210,23 @@ namespace uml 0.f }; } + + Vector3 Vector3::Cross(const Vector3 &v) const + { + return { + y * v.z - z * v.y, + z * v.x - x * v.z, + x * v.y - y * v.x + }; + } + + Vector3 Vector3::Normalized() const + { + float length = this->Length(); + if (length != 0) + { + return *this / length; + } + return *this; + } } \ No newline at end of file From 13e7adc8f680a5c113125c02962ccd6e2c06ca8e Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 9 Jul 2024 19:29:22 +0300 Subject: [PATCH 014/795] rebranding moment --- CMakeLists.txt | 6 ++-- include/{uml => omath}/Vector3.h | 8 ++++- include/{uml => omath}/Vector4.h | 4 +-- include/omath/angles.h | 11 ++++++ include/{uml => omath}/color.h | 6 ++-- include/{uml => omath}/matrix.h | 2 +- include/{uml => omath}/prediction/Engine.h | 8 ++--- .../{uml => omath}/prediction/Projectile.h | 4 +-- include/{uml => omath}/prediction/Target.h | 4 +-- include/uml/angles.h | 11 ------ readme.md | 20 +++++------ source/CMakeLists.txt | 2 +- source/Vector3.cpp | 35 +++++++++++++++++-- source/Vector4.cpp | 4 +-- source/angles.cpp | 8 ++--- source/color.cpp | 4 +-- source/matrix.cpp | 6 ++-- source/prediction/CMakeLists.txt | 2 +- source/prediction/Engine.cpp | 8 ++--- source/prediction/Projectile.cpp | 4 +-- source/prediction/Target.cpp | 4 +-- tests/CMakeLists.txt | 2 +- tests/UnitTestColor.cpp | 8 ++--- tests/UnitTestMatrix.cpp | 4 +-- 24 files changed, 105 insertions(+), 70 deletions(-) rename include/{uml => omath}/Vector3.h (88%) rename include/{uml => omath}/Vector4.h (97%) create mode 100644 include/omath/angles.h rename include/{uml => omath}/color.h (92%) rename include/{uml => omath}/matrix.h (99%) rename include/{uml => omath}/prediction/Engine.h (88%) rename include/{uml => omath}/prediction/Projectile.h (87%) rename include/{uml => omath}/prediction/Target.h (83%) delete mode 100644 include/uml/angles.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dfef162..909d3587 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,12 @@ cmake_minimum_required(VERSION 3.26) -project(uml) +project(omath) set(CMAKE_CXX_STANDARD 26) option(BUILD_TESTS "Build test programs" ON) -add_library(uml STATIC source/Vector3.cpp) +add_library(omath STATIC source/Vector3.cpp) add_subdirectory(source) add_subdirectory(extlibs) add_subdirectory(tests) -target_include_directories(uml PUBLIC include) \ No newline at end of file +target_include_directories(omath PUBLIC include) \ No newline at end of file diff --git a/include/uml/Vector3.h b/include/omath/Vector3.h similarity index 88% rename from include/uml/Vector3.h rename to include/omath/Vector3.h index 947bbc82..88d5d56a 100644 --- a/include/uml/Vector3.h +++ b/include/omath/Vector3.h @@ -4,7 +4,7 @@ #pragma once -namespace uml +namespace omath { class Vector3 { public: @@ -57,12 +57,18 @@ namespace uml { return *reinterpret_cast(this); } + [[nodiscard]] Vector3 Cross(const Vector3 &v) const; [[nodiscard]] static Vector3 CreateVelocity(float pitch, float yaw, float speed); [[nodiscard]] float Sum() const; [[nodiscard]] float Sum2D() const; [[nodiscard]] Vector3 ViewAngleTo(const Vector3& other) const; + [[nodiscard]] static Vector3 ForwardVector(float pitch, float yaw); + [[nodiscard]] static Vector3 RightVector(float pitch, float yaw, float roll); + [[nodiscard]] static Vector3 UpVector(float pitch, float yaw, float roll); + + [[nodiscard]] Vector3 Normalized() const; }; diff --git a/include/uml/Vector4.h b/include/omath/Vector4.h similarity index 97% rename from include/uml/Vector4.h rename to include/omath/Vector4.h index a09a9847..18ca171d 100644 --- a/include/uml/Vector4.h +++ b/include/omath/Vector4.h @@ -3,9 +3,9 @@ // #pragma once -#include +#include -namespace uml +namespace omath { class Vector4 : public Vector3 { diff --git a/include/omath/angles.h b/include/omath/angles.h new file mode 100644 index 00000000..01400921 --- /dev/null +++ b/include/omath/angles.h @@ -0,0 +1,11 @@ +// +// Created by vlad on 11/6/23. +// + +#pragma once + +namespace omath::angles +{ + [[nodiscard]] constexpr float RadiansToDegrees(float rads); + [[nodiscard]] constexpr float DegreesToRadians(float degrees); +} \ No newline at end of file diff --git a/include/uml/color.h b/include/omath/color.h similarity index 92% rename from include/uml/color.h rename to include/omath/color.h index f3f37953..856833f0 100644 --- a/include/uml/color.h +++ b/include/omath/color.h @@ -4,12 +4,12 @@ #pragma once -#include "uml/Vector3.h" +#include "omath/Vector3.h" #include -#include "uml/Vector4.h" +#include "omath/Vector4.h" -namespace uml::color +namespace omath::color { struct HSV { diff --git a/include/uml/matrix.h b/include/omath/matrix.h similarity index 99% rename from include/uml/matrix.h rename to include/omath/matrix.h index afb09f6b..6ac80034 100644 --- a/include/uml/matrix.h +++ b/include/omath/matrix.h @@ -4,7 +4,7 @@ #include -namespace uml +namespace omath { class Vector3; diff --git a/include/uml/prediction/Engine.h b/include/omath/prediction/Engine.h similarity index 88% rename from include/uml/prediction/Engine.h rename to include/omath/prediction/Engine.h index 8a944889..3a32f920 100644 --- a/include/uml/prediction/Engine.h +++ b/include/omath/prediction/Engine.h @@ -5,12 +5,12 @@ #pragma once #include -#include "uml/Vector3.h" -#include "uml/prediction/Projectile.h" -#include "uml/prediction/Target.h" +#include "omath/Vector3.h" +#include "omath/prediction/Projectile.h" +#include "omath/prediction/Target.h" -namespace uml::prediction +namespace omath::prediction { class Engine { diff --git a/include/uml/prediction/Projectile.h b/include/omath/prediction/Projectile.h similarity index 87% rename from include/uml/prediction/Projectile.h rename to include/omath/prediction/Projectile.h index 58603bd9..48038f81 100644 --- a/include/uml/prediction/Projectile.h +++ b/include/omath/prediction/Projectile.h @@ -3,10 +3,10 @@ // #pragma once -#include "uml/Vector3.h" +#include "omath/Vector3.h" -namespace uml::prediction +namespace omath::prediction { class Projectile final { diff --git a/include/uml/prediction/Target.h b/include/omath/prediction/Target.h similarity index 83% rename from include/uml/prediction/Target.h rename to include/omath/prediction/Target.h index db5eb465..bb21fb07 100644 --- a/include/uml/prediction/Target.h +++ b/include/omath/prediction/Target.h @@ -3,10 +3,10 @@ // #pragma once -#include "uml/Vector3.h" +#include "omath/Vector3.h" -namespace uml::prediction +namespace omath::prediction { class Target final { diff --git a/include/uml/angles.h b/include/uml/angles.h deleted file mode 100644 index 39b9a07b..00000000 --- a/include/uml/angles.h +++ /dev/null @@ -1,11 +0,0 @@ -// -// Created by vlad on 11/6/23. -// - -#pragma once - -namespace uml::angles -{ - [[nodiscard]] float RadiansToDegrees(float rads); - [[nodiscard]] float DegreesToRadians(float degrees); -} \ No newline at end of file diff --git a/readme.md b/readme.md index 8beb299f..2e2f2c40 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ -# Universal Math Library (UML) +# Oranges's Math Library (omath) ## Overview -The Universal Math Library (UML) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. +Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. ## Features - **Efficiency**: Optimized for performance, ensuring quick computations. @@ -31,25 +31,25 @@ The Universal Math Library (UML) is a comprehensive, open-source library aimed a ## Usage Simple world to screen function ```c++ -std::optional WorldToScreen(uml::Vector3 worldPosition, float width, float height) +std::optional WorldToScreen(omath::Vector3 worldPosition, float width, float height) { auto projected = (GetViewProjectionMatrix() * worldPosition).transpose(); projected /= projected.at(0, 3); - const auto out = projected * uml::matrix::to_screen_matrix(width, - height); + const auto out = projected * omath::matrix::to_screen_matrix(width, + height); - if (out.at(0,2) <= 0.f) + if (out.at(0, 2) <= 0.f) return std::nullopt; - auto final = uml::Vector3(out.at(0,0), - out.at(0, 1), - out.at(0,2)); + auto final = omath::Vector3(out.at(0, 0), + out.at(0, 1), + out.at(0, 3)); return {final}; } ``` ## Contributing -Contributions to UML are welcome! Please read `CONTRIBUTING.md` for details on our code of conduct and the process for submitting pull requests. +Contributions to `omath` are welcome! Please read `CONTRIBUTING.md` for details on our code of conduct and the process for submitting pull requests. ## License This project is licensed under the GPL V3 - see the `LICENSE` file for details. diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index c8faebe0..7886760b 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(uml PRIVATE +target_sources(omath PRIVATE Vector3.cpp matrix.cpp angles.cpp diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 8f0f8429..76447945 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -2,11 +2,11 @@ // Created by vlad on 10/28/23. // -#include +#include #include -#include +#include -namespace uml +namespace omath { bool Vector3::operator==(const Vector3 &src) const { @@ -211,6 +211,35 @@ namespace uml }; } + Vector3 Vector3::ForwardVector(const float pitch, const float yaw) + { + const auto cosPitch = std::cos(angles::DegreesToRadians(pitch)); + const auto sinPitch = std::sin(angles::DegreesToRadians(pitch)); + + const auto cosYaw = std::cos(angles::DegreesToRadians(yaw)); + const auto sinYaw = std::sin(angles::DegreesToRadians(yaw)); + + + return {cosPitch*cosYaw, cosPitch*sinYaw, sinPitch}; + } + + Vector3 Vector3::RightVector(const float pitch, const float yaw, const float roll) + { + const auto cosPitch = std::cos(angles::DegreesToRadians(pitch)); + const auto sinPitch = std::sin(angles::DegreesToRadians(pitch)); + + const auto cosYaw = std::cos(angles::DegreesToRadians(yaw)); + const auto sinYaw = std::sin(angles::DegreesToRadians(yaw)); + + const auto cosRoll = std::cos(angles::DegreesToRadians(yaw)); + const auto sinRoll = std::sin(angles::DegreesToRadians(yaw)); + + + return {-sinRoll*sinPitch*cosYaw + -cosRoll*-sinYaw, + -sinRoll*sinPitch*sinYaw + -cosRoll*cosYaw, + sinRoll*cosPitch}; + } + Vector3 Vector3::Cross(const Vector3 &v) const { return { diff --git a/source/Vector4.cpp b/source/Vector4.cpp index 62a834b3..526a9d17 100644 --- a/source/Vector4.cpp +++ b/source/Vector4.cpp @@ -2,12 +2,12 @@ // Vector4.cpp // -#include "uml/Vector4.h" +#include "omath/Vector4.h" #include #include -namespace uml +namespace omath { bool Vector4::operator==(const Vector4& src) const { diff --git a/source/angles.cpp b/source/angles.cpp index 26b9f0fe..14d17224 100644 --- a/source/angles.cpp +++ b/source/angles.cpp @@ -2,18 +2,18 @@ // Created by vlad on 11/6/23. // -#include "uml/angles.h" +#include "omath/angles.h" #include -namespace uml::angles +namespace omath::angles { - float RadiansToDegrees(const float radiands) + constexpr float RadiansToDegrees(const float radiands) { return radiands * (180.f / std::numbers::pi_v); } - float DegreesToRadians(const float degrees) + constexpr float DegreesToRadians(const float degrees) { return degrees * (std::numbers::pi_v / 180.f); } diff --git a/source/color.cpp b/source/color.cpp index 0d093196..880c431b 100644 --- a/source/color.cpp +++ b/source/color.cpp @@ -2,12 +2,12 @@ // Created by vlad on 2/4/24. // -#include "uml/color.h" +#include "omath/color.h" #include #include -namespace uml::color +namespace omath::color { Vector3 Blend(const Vector3 &first, const Vector3 &second, float ratio) { diff --git a/source/matrix.cpp b/source/matrix.cpp index b606beb0..2298d9b7 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -3,17 +3,17 @@ * Created by Alpatov Softworks with love in Russia. */ -#include "uml/matrix.h" +#include "omath/matrix.h" #include -#include "uml/Vector3.h" +#include "omath/Vector3.h" #include #include #include -namespace uml +namespace omath { matrix::matrix(const size_t rows, const size_t columns) { diff --git a/source/prediction/CMakeLists.txt b/source/prediction/CMakeLists.txt index 6964df71..da6b5de1 100644 --- a/source/prediction/CMakeLists.txt +++ b/source/prediction/CMakeLists.txt @@ -1 +1 @@ -target_sources(uml PRIVATE Engine.cpp Projectile.cpp Target.cpp) \ No newline at end of file +target_sources(omath PRIVATE Engine.cpp Projectile.cpp Target.cpp) \ No newline at end of file diff --git a/source/prediction/Engine.cpp b/source/prediction/Engine.cpp index 09a58772..80cf4470 100644 --- a/source/prediction/Engine.cpp +++ b/source/prediction/Engine.cpp @@ -3,15 +3,15 @@ // -#include "uml/prediction/Engine.h" +#include "omath/prediction/Engine.h" #include -#include +#include -namespace uml::prediction +namespace omath::prediction { Engine::Engine(const float gravityConstant, const float simulationTimeStep, - const float maximumSimulationTime, float distanceTolerance) + const float maximumSimulationTime, const float distanceTolerance) : m_gravityConstant(gravityConstant), m_simulationTimeStep(simulationTimeStep), m_maximumSimulationTime(maximumSimulationTime), diff --git a/source/prediction/Projectile.cpp b/source/prediction/Projectile.cpp index e65144d2..3ce51643 100644 --- a/source/prediction/Projectile.cpp +++ b/source/prediction/Projectile.cpp @@ -2,11 +2,11 @@ // Created by Vlad on 6/9/2024. // -#include "uml/prediction/Projectile.h" +#include "omath/prediction/Projectile.h" #include -namespace uml::prediction +namespace omath::prediction { Vector3 Projectile::CalculateVelocity(const float pitch, const float yaw) const { diff --git a/source/prediction/Target.cpp b/source/prediction/Target.cpp index 9783897c..64c58a14 100644 --- a/source/prediction/Target.cpp +++ b/source/prediction/Target.cpp @@ -2,11 +2,11 @@ // Created by Vlad on 6/9/2024. // -#include "uml/prediction/Target.h" +#include "omath/prediction/Target.h" #include -namespace uml::prediction +namespace omath::prediction { Vector3 Target::PredictPosition(const float time, const float gravity) const { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 12feaf59..55d4f36f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,4 +6,4 @@ file(GLOB TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include(GoogleTest) add_executable(unit-tests UnitTestColor.cpp UnitTestMatrix.cpp) -target_link_libraries(unit-tests PRIVATE gtest gtest_main uml) \ No newline at end of file +target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) \ No newline at end of file diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp index 3eb3ea02..5ced2126 100644 --- a/tests/UnitTestColor.cpp +++ b/tests/UnitTestColor.cpp @@ -1,11 +1,11 @@ #include -#include +#include TEST(x,x) { - uml::prediction::Target target{.m_origin = {100, 0, 60}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; - uml::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 5000, .m_gravityScale= 0.4}; - auto vel = uml::prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); + omath::prediction::Target target{.m_origin = {100, 0, 60}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; + omath::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 5000, .m_gravityScale= 0.4}; + auto vel = omath::prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); auto pitch = proj.m_origin.ViewAngleTo(vel.value()).x; diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index ecf06933..4b98613f 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -2,13 +2,13 @@ // Created by vlad on 5/18/2024. // #include -#include +#include #include TEST(UnitTestMatrix, ToString) { - uml::matrix matrix(2, 2); + omath::matrix matrix(2, 2); matrix.set(1.1); const auto str = matrix.to_string(); From 49935437b120db6aac6cee932f8f19243c7a9400 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Thu, 11 Jul 2024 22:39:25 +0300 Subject: [PATCH 015/795] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 2e2f2c40..eb534115 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,7 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at ### Installation 1. Clone the repository: ``` - git clone https://github.com/orange-cpp/uml.git + git clone https://github.com/orange-cpp/omath.git ``` 2. Navigate to the project directory: ``` From 3241b07a3bb02cbc45cb0fb83efe34d511cd4368 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Thu, 11 Jul 2024 22:39:43 +0300 Subject: [PATCH 016/795] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index eb534115..fe3eb259 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,7 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at ``` 2. Navigate to the project directory: ``` - cd uml + cd omath ``` 3. Build the project using CMake: ``` From 6d52bedffb0c41cd1e21632985804a32cb47b18a Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 11 Jul 2024 22:42:40 +0300 Subject: [PATCH 017/795] removed constexpr --- include/omath/angles.h | 4 ++-- source/angles.cpp | 4 ++-- source/matrix.cpp | 6 ------ tests/CMakeLists.txt | 5 ++++- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/include/omath/angles.h b/include/omath/angles.h index 01400921..03eccd3a 100644 --- a/include/omath/angles.h +++ b/include/omath/angles.h @@ -6,6 +6,6 @@ namespace omath::angles { - [[nodiscard]] constexpr float RadiansToDegrees(float rads); - [[nodiscard]] constexpr float DegreesToRadians(float degrees); + [[nodiscard]] float RadiansToDegrees(float rads); + [[nodiscard]] float DegreesToRadians(float degrees); } \ No newline at end of file diff --git a/source/angles.cpp b/source/angles.cpp index 14d17224..3b9b0865 100644 --- a/source/angles.cpp +++ b/source/angles.cpp @@ -8,12 +8,12 @@ namespace omath::angles { - constexpr float RadiansToDegrees(const float radiands) + float RadiansToDegrees(const float radiands) { return radiands * (180.f / std::numbers::pi_v); } - constexpr float DegreesToRadians(const float degrees) + float DegreesToRadians(const float degrees) { return degrees * (std::numbers::pi_v / 180.f); } diff --git a/source/matrix.cpp b/source/matrix.cpp index 2298d9b7..e5201295 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -1,12 +1,6 @@ -/* - * Copyright (c) 2022. - * Created by Alpatov Softworks with love in Russia. - */ - #include "omath/matrix.h" #include - #include "omath/Vector3.h" #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 55d4f36f..43c591f0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,10 @@ enable_testing() project(unit-tests) file(GLOB TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + include(GoogleTest) add_executable(unit-tests UnitTestColor.cpp UnitTestMatrix.cpp) -target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) \ No newline at end of file +target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) + +gtest_discover_tests(unit-tests) \ No newline at end of file From c0b4a644c6c045b8172c7a838739031b0554da46 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 12 Jul 2024 02:41:36 +0300 Subject: [PATCH 018/795] updated unit test --- source/Vector3.cpp | 5 ++--- tests/CMakeLists.txt | 2 +- tests/UnitTestColor.cpp | 13 ------------- tests/UnitTestPrediction.cpp | 14 ++++++++++++++ 4 files changed, 17 insertions(+), 17 deletions(-) delete mode 100644 tests/UnitTestColor.cpp create mode 100644 tests/UnitTestPrediction.cpp diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 76447945..5abdd04c 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -202,10 +202,9 @@ namespace omath const float distance = DistTo(other); const auto delta = other - *this; - // Make x negative since -89 is top and 89 is bottom return { - -angles::RadiansToDegrees(asinf(delta.z / distance)), + angles::RadiansToDegrees(asinf(delta.z / distance)), angles::RadiansToDegrees(atan2f(delta.y, delta.x)), 0.f }; @@ -251,7 +250,7 @@ namespace omath Vector3 Vector3::Normalized() const { - float length = this->Length(); + const float length = this->Length(); if (length != 0) { return *this / length; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 43c591f0..a73124c7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,7 +5,7 @@ project(unit-tests) file(GLOB TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include(GoogleTest) -add_executable(unit-tests UnitTestColor.cpp UnitTestMatrix.cpp) +add_executable(unit-tests UnitTestPrediction.cpp UnitTestMatrix.cpp) target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp deleted file mode 100644 index 5ced2126..00000000 --- a/tests/UnitTestColor.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include - -TEST(x,x) -{ - omath::prediction::Target target{.m_origin = {100, 0, 60}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; - omath::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 5000, .m_gravityScale= 0.4}; - auto vel = omath::prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); - - auto pitch = proj.m_origin.ViewAngleTo(vel.value()).x; - - printf("pitch: %f", pitch); -} \ No newline at end of file diff --git a/tests/UnitTestPrediction.cpp b/tests/UnitTestPrediction.cpp new file mode 100644 index 00000000..d3ce5c2d --- /dev/null +++ b/tests/UnitTestPrediction.cpp @@ -0,0 +1,14 @@ +#include +#include + +TEST(UnitTestPrediction, PredictionTest) +{ + const omath::prediction::Target target{.m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; + const omath::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 5000, .m_gravityScale= 0.4}; + const auto viewPoint = omath::prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); + + const auto [pitch, yaw, _] = proj.m_origin.ViewAngleTo(viewPoint.value()); + + EXPECT_NEAR(42.547142, pitch, 0.0001f); + EXPECT_NEAR(-1.181189, yaw, 0.0001f); +} \ No newline at end of file From 9b77e2e0be39130a68c98f40d9ac4495d8905221 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 12 Jul 2024 02:44:37 +0300 Subject: [PATCH 019/795] fix --- CMakeLists.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 909d3587..b2cab68a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,19 @@ cmake_minimum_required(VERSION 3.26) + + project(omath) set(CMAKE_CXX_STANDARD 26) -option(BUILD_TESTS "Build test programs" ON) + +option(BUILD_TESTS "Build unit tests" ON) + add_library(omath STATIC source/Vector3.cpp) add_subdirectory(source) add_subdirectory(extlibs) -add_subdirectory(tests) + +if(BUILD_TESTS) + add_subdirectory(tests) +endif () target_include_directories(omath PUBLIC include) \ No newline at end of file From 632d6a45b1bef2c939313f433af38d0c7f91d8a0 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 28 Jul 2024 17:06:55 +0300 Subject: [PATCH 020/795] minor improvements --- .gitignore | 3 ++- source/Vector3.cpp | 14 ++++++-------- tests/CMakeLists.txt | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 28c6c4bc..98568e70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /cmake-build/ -/.idea \ No newline at end of file +/.idea +/out diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 5abdd04c..e0a68f14 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -204,8 +204,8 @@ namespace omath return { - angles::RadiansToDegrees(asinf(delta.z / distance)), - angles::RadiansToDegrees(atan2f(delta.y, delta.x)), + angles::RadiansToDegrees(std::asin(delta.z / distance)), + angles::RadiansToDegrees(std::atan2(delta.y, delta.x)), 0.f }; } @@ -241,7 +241,8 @@ namespace omath Vector3 Vector3::Cross(const Vector3 &v) const { - return { + return + { y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x @@ -251,10 +252,7 @@ namespace omath Vector3 Vector3::Normalized() const { const float length = this->Length(); - if (length != 0) - { - return *this / length; - } - return *this; + + return length != 0 ? *this / length : *this; } } \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a73124c7..3c09cbb1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,7 @@ enable_testing() project(unit-tests) - +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") file(GLOB TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include(GoogleTest) From 157d57811a6fa15ced934abaa0e2297b92712f8e Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 18 Aug 2024 08:49:39 +0300 Subject: [PATCH 021/795] made some functions constexpr --- include/omath/angles.h | 13 ++++++++++--- source/CMakeLists.txt | 1 - source/angles.cpp | 20 -------------------- 3 files changed, 10 insertions(+), 24 deletions(-) delete mode 100644 source/angles.cpp diff --git a/include/omath/angles.h b/include/omath/angles.h index 03eccd3a..9e557fd6 100644 --- a/include/omath/angles.h +++ b/include/omath/angles.h @@ -3,9 +3,16 @@ // #pragma once +#include namespace omath::angles { - [[nodiscard]] float RadiansToDegrees(float rads); - [[nodiscard]] float DegreesToRadians(float degrees); -} \ No newline at end of file + [[nodiscard]] constexpr float RadiansToDegrees(const float radiands) + { + return radiands * (180.f / std::numbers::pi_v); + } + [[nodiscard]] constexpr float DegreesToRadians(const float degrees) + { + return degrees * (std::numbers::pi_v / 180.f); + } +} diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 7886760b..26a3d6a2 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,7 +1,6 @@ target_sources(omath PRIVATE Vector3.cpp matrix.cpp - angles.cpp color.cpp Vector4.cpp) diff --git a/source/angles.cpp b/source/angles.cpp deleted file mode 100644 index 3b9b0865..00000000 --- a/source/angles.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// -// Created by vlad on 11/6/23. -// - -#include "omath/angles.h" -#include - - -namespace omath::angles -{ - float RadiansToDegrees(const float radiands) - { - return radiands * (180.f / std::numbers::pi_v); - } - - float DegreesToRadians(const float degrees) - { - return degrees * (std::numbers::pi_v / 180.f); - } -} From 67e28a5a2ec0e33764165608ee38385e00d1a339 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 18 Aug 2024 08:52:16 +0300 Subject: [PATCH 022/795] updated matrix name --- include/omath/matrix.h | 36 ++++++++++---------- source/matrix.cpp | 72 ++++++++++++++++++++-------------------- tests/UnitTestMatrix.cpp | 2 +- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/include/omath/matrix.h b/include/omath/matrix.h index 6ac80034..920f94c3 100644 --- a/include/omath/matrix.h +++ b/include/omath/matrix.h @@ -8,21 +8,21 @@ namespace omath { class Vector3; - class matrix + class Matrix { public: - matrix(size_t rows, size_t columns); + Matrix(size_t rows, size_t columns); - explicit matrix(const std::vector> &rows); + explicit Matrix(const std::vector> &rows); [[nodiscard]] - static matrix to_screen_matrix(float screenWidth, float screenHeight); + static Matrix to_screen_matrix(float screenWidth, float screenHeight); - matrix(const matrix &other); + Matrix(const Matrix &other); - matrix(size_t rows, size_t columns, const float *pRaw); + Matrix(size_t rows, size_t columns, const float *pRaw); - matrix(matrix &&other) noexcept; + Matrix(Matrix &&other) noexcept; [[nodiscard]] size_t get_rows_count() const noexcept; @@ -42,27 +42,27 @@ namespace omath void set_from_raw(const float* pRawMatrix); [[nodiscard]] - matrix transpose(); + Matrix transpose(); void set(float val); [[nodiscard]] const float &at(size_t iRow, size_t iCol) const; - matrix operator*(const matrix &other) const; + Matrix operator*(const Matrix &other) const; - matrix operator*(float f) const; + Matrix operator*(float f) const; - matrix operator*(const Vector3 &vec3) const; + Matrix operator*(const Vector3 &vec3) const; - matrix &operator*=(float f); + Matrix &operator*=(float f); - matrix &operator/=(float f); + Matrix &operator/=(float f); void clear(); [[nodiscard]] - matrix strip(size_t row, size_t column) const; + Matrix strip(size_t row, size_t column) const; [[nodiscard]] float minor(size_t i, size_t j) const; @@ -76,16 +76,16 @@ namespace omath [[nodiscard]] const float* raw() const; - matrix &operator=(const matrix &other); + Matrix &operator=(const Matrix &other); - matrix &operator=(matrix &&other) noexcept; + Matrix &operator=(Matrix &&other) noexcept; - matrix operator/(float f) const; + Matrix operator/(float f) const; [[nodiscard]] std::string to_string() const; - ~matrix(); + ~Matrix(); private: size_t m_rows = 0; diff --git a/source/matrix.cpp b/source/matrix.cpp index e5201295..493da780 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -9,7 +9,7 @@ namespace omath { - matrix::matrix(const size_t rows, const size_t columns) + Matrix::Matrix(const size_t rows, const size_t columns) { if (rows == 0 and columns == 0) throw std::runtime_error("Matrix cannot be 0x0"); @@ -22,7 +22,7 @@ namespace omath set(0.f); } - matrix::matrix(const std::vector> &rows) + Matrix::Matrix(const std::vector> &rows) { m_rows = rows.size(); m_columns = rows[0].size(); @@ -35,7 +35,7 @@ namespace omath at(i,j) = rows[i][j]; } - matrix::matrix(const matrix &other) + Matrix::Matrix(const Matrix &other) { m_rows = other.m_rows; m_columns = other.m_columns; @@ -47,7 +47,7 @@ namespace omath at(i, j) = other.at(i, j); } - matrix::matrix(const size_t rows, const size_t columns, const float *pRaw) + Matrix::Matrix(const size_t rows, const size_t columns, const float *pRaw) { m_rows = rows; m_columns = columns; @@ -60,12 +60,12 @@ namespace omath } - size_t matrix::get_rows_count() const noexcept + size_t Matrix::get_rows_count() const noexcept { return m_rows; } - matrix::matrix(matrix &&other) noexcept + Matrix::Matrix(Matrix &&other) noexcept { m_rows = other.m_rows; m_columns = other.m_columns; @@ -73,22 +73,22 @@ namespace omath } - size_t matrix::get_columns_count() const noexcept + size_t Matrix::get_columns_count() const noexcept { return m_columns; } - std::pair matrix::get_size() const noexcept + std::pair Matrix::get_size() const noexcept { return {get_rows_count(), get_columns_count()}; } - float &matrix::at(const size_t iRow, const size_t iCol) + float &Matrix::at(const size_t iRow, const size_t iCol) { return const_cast(std::as_const(*this).at(iRow, iCol)); } - float matrix::get_sum() + float Matrix::get_sum() { float sum = 0; @@ -99,17 +99,17 @@ namespace omath return sum; } - const float &matrix::at(const size_t iRow, const size_t iCol) const + const float &Matrix::at(const size_t iRow, const size_t iCol) const { return m_pData[iRow * m_columns + iCol]; } - matrix matrix::operator*(const matrix &other) const + Matrix Matrix::operator*(const Matrix &other) const { if (m_columns != other.m_rows) throw std::runtime_error("n != m"); - auto outMat = matrix(m_rows, other.m_columns); + auto outMat = Matrix(m_rows, other.m_columns); for (size_t d = 0; d < m_rows; ++d) for (size_t i = 0; i < other.m_columns; ++i) @@ -120,7 +120,7 @@ namespace omath return outMat; } - matrix matrix::operator*(const float f) const + Matrix Matrix::operator*(const float f) const { auto out = *this; for (size_t i = 0; i < m_rows; ++i) @@ -130,7 +130,7 @@ namespace omath return out; } - matrix &matrix::operator*=(const float f) + Matrix &Matrix::operator*=(const float f) { for (size_t i = 0; i < get_rows_count(); i++) for (size_t j = 0; j < get_columns_count(); j++) @@ -138,14 +138,14 @@ namespace omath return *this; } - void matrix::clear() + void Matrix::clear() { set(0.f); } - matrix matrix::operator*(const Vector3 &vec3) const + Matrix Matrix::operator*(const Vector3 &vec3) const { - auto vecmatrix = matrix(m_rows, 1); + auto vecmatrix = Matrix(m_rows, 1); vecmatrix.set(1.f); vecmatrix.at(0, 0) = vec3.x; vecmatrix.at(1, 0) = vec3.y; @@ -156,7 +156,7 @@ namespace omath } - matrix &matrix::operator=(const matrix &other) + Matrix &Matrix::operator=(const Matrix &other) { if (this == &other) return *this; @@ -169,7 +169,7 @@ namespace omath } - matrix &matrix::operator=(matrix &&other) noexcept + Matrix &Matrix::operator=(Matrix &&other) noexcept { if (this == &other) return *this; @@ -182,7 +182,7 @@ namespace omath } - matrix &matrix::operator/=(const float f) + Matrix &Matrix::operator/=(const float f) { for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) @@ -191,7 +191,7 @@ namespace omath return *this; } - matrix matrix::operator/(const float f) const + Matrix Matrix::operator/(const float f) const { auto out = *this; for (size_t i = 0; i < m_rows; ++i) @@ -201,7 +201,7 @@ namespace omath return out; } - std::string matrix::to_string() const + std::string Matrix::to_string() const { std::string str; @@ -220,7 +220,7 @@ namespace omath return str; } - float matrix::det() const + float Matrix::det() const { if (m_rows + m_columns == 2) return at(0, 0); @@ -235,15 +235,15 @@ namespace omath return fDet; } - float matrix::alg_complement(const size_t i, const size_t j) const + float Matrix::alg_complement(const size_t i, const size_t j) const { const auto tmp = minor(i, j); return ((i + j + 2) % 2 == 0) ? tmp : -tmp; } - matrix matrix::transpose() + Matrix Matrix::transpose() { - matrix transposed = {m_columns, m_rows}; + Matrix transposed = {m_columns, m_rows}; for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) @@ -252,18 +252,18 @@ namespace omath return transposed; } - matrix::~matrix() = default; + Matrix::~Matrix() = default; - void matrix::set(const float val) + void Matrix::set(const float val) { for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) at(i, j) = val; } - matrix matrix::strip(const size_t row, const size_t column) const + Matrix Matrix::strip(const size_t row, const size_t column) const { - matrix stripped = {m_rows - 1, m_columns - 1}; + Matrix stripped = {m_rows - 1, m_columns - 1}; size_t iStripRowIndex = 0; for (size_t i = 0; i < m_rows; i++) @@ -287,14 +287,14 @@ namespace omath return stripped; } - float matrix::minor(const size_t i, const size_t j) const + float Matrix::minor(const size_t i, const size_t j) const { return strip(i, j).det(); } - matrix matrix::to_screen_matrix(float screenWidth, float screenHeight) + Matrix Matrix::to_screen_matrix(float screenWidth, float screenHeight) { - return matrix({ + return Matrix({ {screenWidth / 2.f, 0.f, 0.f, 0.f}, {0.f, -screenHeight / 2.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, @@ -302,12 +302,12 @@ namespace omath }); } - const float * matrix::raw() const + const float * Matrix::raw() const { return m_pData.get(); } - void matrix::set_from_raw(const float *pRawMatrix) + void Matrix::set_from_raw(const float *pRawMatrix) { for (size_t i = 0; i < m_columns*m_rows; ++i) at(i / m_rows, i % m_columns) = pRawMatrix[i]; diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index 4b98613f..f7fc1122 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -8,7 +8,7 @@ TEST(UnitTestMatrix, ToString) { - omath::matrix matrix(2, 2); + omath::Matrix matrix(2, 2); matrix.set(1.1); const auto str = matrix.to_string(); From 62f471e04a9247e933aed87de69e8774fac102cc Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 18 Aug 2024 08:55:28 +0300 Subject: [PATCH 023/795] fixed matrix naming --- include/omath/matrix.h | 38 ++++++------- source/matrix.cpp | 114 +++++++++++++++++++-------------------- tests/UnitTestMatrix.cpp | 4 +- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/include/omath/matrix.h b/include/omath/matrix.h index 920f94c3..79655cc5 100644 --- a/include/omath/matrix.h +++ b/include/omath/matrix.h @@ -8,7 +8,7 @@ namespace omath { class Vector3; - class Matrix + class Matrix final { public: Matrix(size_t rows, size_t columns); @@ -16,7 +16,7 @@ namespace omath explicit Matrix(const std::vector> &rows); [[nodiscard]] - static Matrix to_screen_matrix(float screenWidth, float screenHeight); + static Matrix ToScreenMatrix(float screenWidth, float screenHeight); Matrix(const Matrix &other); @@ -25,29 +25,29 @@ namespace omath Matrix(Matrix &&other) noexcept; [[nodiscard]] - size_t get_rows_count() const noexcept; + size_t RowCount() const noexcept; [[nodiscard]] - size_t get_columns_count() const noexcept; + size_t ColumnsCount() const noexcept; [[nodiscard]] - std::pair get_size() const noexcept; + std::pair Size() const noexcept; [[nodiscard]] - float &at(size_t iRow, size_t iCol); + float &At(size_t iRow, size_t iCol); [[nodiscard]] - float get_sum(); + float Sum(); - void set_from_raw(const float* pRawMatrix); + void SetDataFromRaw(const float* pRawMatrix); [[nodiscard]] - Matrix transpose(); + Matrix Transpose(); - void set(float val); + void Set(float val); [[nodiscard]] - const float &at(size_t iRow, size_t iCol) const; + const float &At(size_t iRow, size_t iCol) const; Matrix operator*(const Matrix &other) const; @@ -59,22 +59,22 @@ namespace omath Matrix &operator/=(float f); - void clear(); + void Clear(); [[nodiscard]] - Matrix strip(size_t row, size_t column) const; + Matrix Strip(size_t row, size_t column) const; [[nodiscard]] - float minor(size_t i, size_t j) const; + float Minor(size_t i, size_t j) const; [[nodiscard]] - float alg_complement(size_t i, size_t j) const; + float AlgComplement(size_t i, size_t j) const; [[nodiscard]] - float det() const; + float Determinant() const; [[nodiscard]] - const float* raw() const; + const float* Raw() const; Matrix &operator=(const Matrix &other); @@ -83,13 +83,13 @@ namespace omath Matrix operator/(float f) const; [[nodiscard]] - std::string to_string() const; + std::string ToSrtring() const; ~Matrix(); private: size_t m_rows = 0; size_t m_columns = 0; - std::unique_ptr m_pData = nullptr; + std::unique_ptr m_data = nullptr; }; } \ No newline at end of file diff --git a/source/matrix.cpp b/source/matrix.cpp index 493da780..7ca93098 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -17,9 +17,9 @@ namespace omath m_rows = rows; m_columns = columns; - m_pData = std::make_unique(m_rows * m_columns); + m_data = std::make_unique(m_rows * m_columns); - set(0.f); + Set(0.f); } Matrix::Matrix(const std::vector> &rows) @@ -28,11 +28,11 @@ namespace omath m_columns = rows[0].size(); - m_pData = std::make_unique(m_rows * m_columns); + m_data = std::make_unique(m_rows * m_columns); for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - at(i,j) = rows[i][j]; + At(i,j) = rows[i][j]; } Matrix::Matrix(const Matrix &other) @@ -40,11 +40,11 @@ namespace omath m_rows = other.m_rows; m_columns = other.m_columns; - m_pData = std::make_unique(m_rows * m_columns); + m_data = std::make_unique(m_rows * m_columns); for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - at(i, j) = other.at(i, j); + At(i, j) = other.At(i, j); } Matrix::Matrix(const size_t rows, const size_t columns, const float *pRaw) @@ -53,14 +53,14 @@ namespace omath m_columns = columns; - m_pData = std::make_unique(m_rows * m_columns); + m_data = std::make_unique(m_rows * m_columns); for (size_t i = 0; i < rows*columns; ++i) - at(i / rows, i % columns) = pRaw[i]; + At(i / rows, i % columns) = pRaw[i]; } - size_t Matrix::get_rows_count() const noexcept + size_t Matrix::RowCount() const noexcept { return m_rows; } @@ -69,39 +69,39 @@ namespace omath { m_rows = other.m_rows; m_columns = other.m_columns; - m_pData = std::move(other.m_pData); + m_data = std::move(other.m_data); } - size_t Matrix::get_columns_count() const noexcept + size_t Matrix::ColumnsCount() const noexcept { return m_columns; } - std::pair Matrix::get_size() const noexcept + std::pair Matrix::Size() const noexcept { - return {get_rows_count(), get_columns_count()}; + return {RowCount(), ColumnsCount()}; } - float &Matrix::at(const size_t iRow, const size_t iCol) + float &Matrix::At(const size_t iRow, const size_t iCol) { - return const_cast(std::as_const(*this).at(iRow, iCol)); + return const_cast(std::as_const(*this).At(iRow, iCol)); } - float Matrix::get_sum() + float Matrix::Sum() { float sum = 0; - for (size_t i = 0; i < get_rows_count(); i++) - for (size_t j = 0; j < get_columns_count(); j++) - sum += at(i, j); + for (size_t i = 0; i < RowCount(); i++) + for (size_t j = 0; j < ColumnsCount(); j++) + sum += At(i, j); return sum; } - const float &Matrix::at(const size_t iRow, const size_t iCol) const + const float &Matrix::At(const size_t iRow, const size_t iCol) const { - return m_pData[iRow * m_columns + iCol]; + return m_data[iRow * m_columns + iCol]; } Matrix Matrix::operator*(const Matrix &other) const @@ -114,7 +114,7 @@ namespace omath for (size_t d = 0; d < m_rows; ++d) for (size_t i = 0; i < other.m_columns; ++i) for (size_t j = 0; j < other.m_rows; ++j) - outMat.at(d, i) += at(d, j) * other.at(j, i); + outMat.At(d, i) += At(d, j) * other.At(j, i); return outMat; @@ -125,31 +125,31 @@ namespace omath auto out = *this; for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - out.at(i, j) *= f; + out.At(i, j) *= f; return out; } Matrix &Matrix::operator*=(const float f) { - for (size_t i = 0; i < get_rows_count(); i++) - for (size_t j = 0; j < get_columns_count(); j++) - at(i, j) *= f; + for (size_t i = 0; i < RowCount(); i++) + for (size_t j = 0; j < ColumnsCount(); j++) + At(i, j) *= f; return *this; } - void Matrix::clear() + void Matrix::Clear() { - set(0.f); + Set(0.f); } Matrix Matrix::operator*(const Vector3 &vec3) const { auto vecmatrix = Matrix(m_rows, 1); - vecmatrix.set(1.f); - vecmatrix.at(0, 0) = vec3.x; - vecmatrix.at(1, 0) = vec3.y; - vecmatrix.at(2, 0) = vec3.z; + vecmatrix.Set(1.f); + vecmatrix.At(0, 0) = vec3.x; + vecmatrix.At(1, 0) = vec3.y; + vecmatrix.At(2, 0) = vec3.z; return *this * vecmatrix; @@ -163,7 +163,7 @@ namespace omath for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - at(i, j) = other.at(i, j); + At(i, j) = other.At(i, j); return *this; @@ -176,7 +176,7 @@ namespace omath m_rows = other.m_rows; m_columns = other.m_columns; - m_pData = std::move(other.m_pData); + m_data = std::move(other.m_data); return *this; @@ -186,7 +186,7 @@ namespace omath { for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - at(i, j) /= f; + At(i, j) /= f; return *this; } @@ -196,12 +196,12 @@ namespace omath auto out = *this; for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - out.at(i, j) /= f; + out.At(i, j) /= f; return out; } - std::string Matrix::to_string() const + std::string Matrix::ToSrtring() const { std::string str; @@ -209,7 +209,7 @@ namespace omath { for (size_t j = 0; j < m_columns; ++j) { - str += std::format("{:.1f}",at(i, j)); + str += std::format("{:.1f}",At(i, j)); if (j == m_columns-1) str += '\n'; @@ -220,48 +220,48 @@ namespace omath return str; } - float Matrix::det() const + float Matrix::Determinant() const { if (m_rows + m_columns == 2) - return at(0, 0); + return At(0, 0); if (m_rows == 2 and m_columns == 2) - return at(0, 0) * at(1, 1) - at(0, 1) * at(1, 0); + return At(0, 0) * At(1, 1) - At(0, 1) * At(1, 0); float fDet = 0; for (size_t i = 0; i < m_columns; i++) - fDet += alg_complement(0, i) * at(0, i); + fDet += AlgComplement(0, i) * At(0, i); return fDet; } - float Matrix::alg_complement(const size_t i, const size_t j) const + float Matrix::AlgComplement(const size_t i, const size_t j) const { - const auto tmp = minor(i, j); + const auto tmp = Minor(i, j); return ((i + j + 2) % 2 == 0) ? tmp : -tmp; } - Matrix Matrix::transpose() + Matrix Matrix::Transpose() { Matrix transposed = {m_columns, m_rows}; for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - transposed.at(j, i) = at(i, j); + transposed.At(j, i) = At(i, j); return transposed; } Matrix::~Matrix() = default; - void Matrix::set(const float val) + void Matrix::Set(const float val) { for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - at(i, j) = val; + At(i, j) = val; } - Matrix Matrix::strip(const size_t row, const size_t column) const + Matrix Matrix::Strip(const size_t row, const size_t column) const { Matrix stripped = {m_rows - 1, m_columns - 1}; size_t iStripRowIndex = 0; @@ -277,7 +277,7 @@ namespace omath if (j == column) continue; - stripped.at(iStripRowIndex, iStripColumnIndex) = at(i, j); + stripped.At(iStripRowIndex, iStripColumnIndex) = At(i, j); iStripColumnIndex++; } @@ -287,12 +287,12 @@ namespace omath return stripped; } - float Matrix::minor(const size_t i, const size_t j) const + float Matrix::Minor(const size_t i, const size_t j) const { - return strip(i, j).det(); + return Strip(i, j).Determinant(); } - Matrix Matrix::to_screen_matrix(float screenWidth, float screenHeight) + Matrix Matrix::ToScreenMatrix(float screenWidth, float screenHeight) { return Matrix({ {screenWidth / 2.f, 0.f, 0.f, 0.f}, @@ -302,14 +302,14 @@ namespace omath }); } - const float * Matrix::raw() const + const float * Matrix::Raw() const { - return m_pData.get(); + return m_data.get(); } - void Matrix::set_from_raw(const float *pRawMatrix) + void Matrix::SetDataFromRaw(const float *pRawMatrix) { for (size_t i = 0; i < m_columns*m_rows; ++i) - at(i / m_rows, i % m_columns) = pRawMatrix[i]; + At(i / m_rows, i % m_columns) = pRawMatrix[i]; } } \ No newline at end of file diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index f7fc1122..dceb2641 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -9,8 +9,8 @@ TEST(UnitTestMatrix, ToString) { omath::Matrix matrix(2, 2); - matrix.set(1.1); - const auto str = matrix.to_string(); + matrix.Set(1.1); + const auto str = matrix.ToSrtring(); std::cout << str; From cd08f8c742adde68a2711f4564d8afb6dc4749d5 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 28 Jul 2024 18:06:00 +0300 Subject: [PATCH 024/795] added files, added hash function --- include/omath/Vector3.h | 22 +++++++++++++ include/omath/pathfinding/Astar.h | 20 ++++++++++++ include/omath/pathfinding/NavigationMesh.h | 33 ++++++++++++++++++++ source/CMakeLists.txt | 3 +- source/pathfinding/Astar.cpp | 36 ++++++++++++++++++++++ source/pathfinding/CMakeLists.txt | 1 + source/pathfinding/NavigationMesh.cpp | 4 +++ 7 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 include/omath/pathfinding/Astar.h create mode 100644 include/omath/pathfinding/NavigationMesh.h create mode 100644 source/pathfinding/Astar.cpp create mode 100644 source/pathfinding/CMakeLists.txt create mode 100644 source/pathfinding/NavigationMesh.cpp diff --git a/include/omath/Vector3.h b/include/omath/Vector3.h index 88d5d56a..1bb88077 100644 --- a/include/omath/Vector3.h +++ b/include/omath/Vector3.h @@ -4,6 +4,9 @@ #pragma once +#include +#include + namespace omath { class Vector3 { @@ -73,3 +76,22 @@ namespace omath Vector3 Normalized() const; }; } +// ReSharper disable once CppRedundantNamespaceDefinition +namespace std +{ + template<> + struct hash + { + std::size_t operator()(const omath::Vector3& vec) const noexcept + { + std::size_t hash = 0; + constexpr std::hash hasher; + + hash ^= hasher(vec.x) + 0x9e3779b9 + (hash<<6) + (hash>>2); + hash ^= hasher(vec.y) + 0x9e3779b9 + (hash<<6) + (hash>>2); + hash ^= hasher(vec.z) + 0x9e3779b9 + (hash<<6) + (hash>>2); + + return hash; + } + }; +} diff --git a/include/omath/pathfinding/Astar.h b/include/omath/pathfinding/Astar.h new file mode 100644 index 00000000..05256790 --- /dev/null +++ b/include/omath/pathfinding/Astar.h @@ -0,0 +1,20 @@ +// +// Created by Vlad on 28.07.2024. +// + +#pragma once +#include +#include "NavigationMesh.h" +#include "omath/Vector3.h" + + +namespace omath::pathfinding +{ + class Astar + { + public: + [[nodiscard]] + static std::vector FindPath(const Vector3& start, const Vector3& end, const NavigationMesh& navMesh); + + }; +} \ No newline at end of file diff --git a/include/omath/pathfinding/NavigationMesh.h b/include/omath/pathfinding/NavigationMesh.h new file mode 100644 index 00000000..1af4b06b --- /dev/null +++ b/include/omath/pathfinding/NavigationMesh.h @@ -0,0 +1,33 @@ +// +// Created by Vlad on 28.07.2024. +// + +#pragma once + +#include "omath/Vector3.h" +#include +#include +#include + + +namespace omath::pathfinding +{ + struct NavigationVertex + { + Vector3 origin; + std::vector connections; + }; + + + class NavigationMesh final + { + public: + + [[nodiscard]] + std::expected GetClossestVertex(const Vector3& point) const; + + private: + + + }; +} \ No newline at end of file diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 26a3d6a2..499567c0 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -4,4 +4,5 @@ target_sources(omath PRIVATE color.cpp Vector4.cpp) -add_subdirectory(prediction) \ No newline at end of file +add_subdirectory(prediction) +add_subdirectory(pathfinding) \ No newline at end of file diff --git a/source/pathfinding/Astar.cpp b/source/pathfinding/Astar.cpp new file mode 100644 index 00000000..f211de73 --- /dev/null +++ b/source/pathfinding/Astar.cpp @@ -0,0 +1,36 @@ +// +// Created by Vlad on 28.07.2024. +// +#include "omath/pathfinding/Astar.h" +#include + + + +namespace omath::pathfinding +{ + struct PathNode final + { + PathNode* cameFrom; + const NavigationVertex* navVertex; + float gCost = 0.f; + }; + + + std::vector Astar::FindPath(const Vector3 &start, const Vector3 &end, const NavigationMesh &navMesh) + { + std::unordered_map closedList; + std::unordered_map openList; + + const auto& startVertex = navMesh.GetClossestVertex(start).value(); + const auto& endVertex = navMesh.GetClossestVertex(end).value(); + + openList.emplace(startVertex.origin, PathNode{nullptr, &startVertex, 0.f}); + + while (!openList.empty()) + { + + } + + return {}; + } +} diff --git a/source/pathfinding/CMakeLists.txt b/source/pathfinding/CMakeLists.txt new file mode 100644 index 00000000..d1da67f1 --- /dev/null +++ b/source/pathfinding/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(omath PRIVATE NavigationMesh.cpp Astar.cpp) \ No newline at end of file diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/NavigationMesh.cpp new file mode 100644 index 00000000..5ff1b3f4 --- /dev/null +++ b/source/pathfinding/NavigationMesh.cpp @@ -0,0 +1,4 @@ +// +// Created by Vlad on 28.07.2024. +// +#include "omath/pathfinding/NavigationMesh.h" \ No newline at end of file From f4c988247c9b1bcd73f6f59be79cffe32865622d Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 1 Aug 2024 21:55:56 +0300 Subject: [PATCH 025/795] changed stuff --- include/omath/pathfinding/NavigationMesh.h | 2 +- source/pathfinding/Astar.cpp | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/include/omath/pathfinding/NavigationMesh.h b/include/omath/pathfinding/NavigationMesh.h index 1af4b06b..c37da96f 100644 --- a/include/omath/pathfinding/NavigationMesh.h +++ b/include/omath/pathfinding/NavigationMesh.h @@ -15,7 +15,7 @@ namespace omath::pathfinding struct NavigationVertex { Vector3 origin; - std::vector connections; + std::vector connections; }; diff --git a/source/pathfinding/Astar.cpp b/source/pathfinding/Astar.cpp index f211de73..12c3b55c 100644 --- a/source/pathfinding/Astar.cpp +++ b/source/pathfinding/Astar.cpp @@ -3,15 +3,15 @@ // #include "omath/pathfinding/Astar.h" #include - +#include namespace omath::pathfinding { struct PathNode final { - PathNode* cameFrom; - const NavigationVertex* navVertex; + Vector3 cameFrom; + NavigationVertex const* navVertex; float gCost = 0.f; }; @@ -24,11 +24,23 @@ namespace omath::pathfinding const auto& startVertex = navMesh.GetClossestVertex(start).value(); const auto& endVertex = navMesh.GetClossestVertex(end).value(); - openList.emplace(startVertex.origin, PathNode{nullptr, &startVertex, 0.f}); + openList.emplace(startVertex.origin, PathNode{startVertex.origin, &startVertex, 0.f}); while (!openList.empty()) { - + const auto [cord, node] = *std::ranges::min_element(openList, + [&endVertex](const auto& a, const auto& b) -> bool + { + const auto aCost = a.second.gCost + a.second.navVertex->origin.DistTo(endVertex.origin); + const auto bCost = b.second.gCost + b.second.navVertex->origin.DistTo(endVertex.origin); + return aCost < bCost; + }); + + openList.erase(cord); + + for (const auto& neighbor : node.navVertex->connections) + if (!closedList.contains(neighbor->origin)) + closedList.emplace(neighbor->origin, PathNode{cord, neighbor, neighbor->origin.DistTo(cord) + node.gCost}); } return {}; From 945fd7daa213260e13b64ccb061c529e204cb219 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 18 Aug 2024 10:33:01 +0300 Subject: [PATCH 026/795] improved code added unit test --- include/omath/pathfinding/NavigationMesh.h | 8 +++-- source/pathfinding/Astar.cpp | 37 +++++++++++++++------- source/pathfinding/NavigationMesh.cpp | 24 +++++++++++++- tests/CMakeLists.txt | 2 +- tests/UnitTestAstar.cpp | 17 ++++++++++ 5 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 tests/UnitTestAstar.cpp diff --git a/include/omath/pathfinding/NavigationMesh.h b/include/omath/pathfinding/NavigationMesh.h index c37da96f..f786501e 100644 --- a/include/omath/pathfinding/NavigationMesh.h +++ b/include/omath/pathfinding/NavigationMesh.h @@ -24,10 +24,14 @@ namespace omath::pathfinding public: [[nodiscard]] - std::expected GetClossestVertex(const Vector3& point) const; + std::expected GetClossestVertex(const Vector3& point) const; - private: + [[nodiscard]] + const std::vector& GetNeighbors(const Vector3& vertex) const; + + std::list m_vertexes; + std::unordered_map> m_verTextMap; }; } \ No newline at end of file diff --git a/source/pathfinding/Astar.cpp b/source/pathfinding/Astar.cpp index 12c3b55c..06f20907 100644 --- a/source/pathfinding/Astar.cpp +++ b/source/pathfinding/Astar.cpp @@ -2,6 +2,8 @@ // Created by Vlad on 28.07.2024. // #include "omath/pathfinding/Astar.h" + +#include #include #include @@ -10,8 +12,7 @@ namespace omath::pathfinding { struct PathNode final { - Vector3 cameFrom; - NavigationVertex const* navVertex; + std::optional cameFrom; float gCost = 0.f; }; @@ -21,26 +22,38 @@ namespace omath::pathfinding std::unordered_map closedList; std::unordered_map openList; - const auto& startVertex = navMesh.GetClossestVertex(start).value(); - const auto& endVertex = navMesh.GetClossestVertex(end).value(); + const auto startVertex = navMesh.GetClossestVertex(start).value(); + const auto endVertex = navMesh.GetClossestVertex(end).value(); - openList.emplace(startVertex.origin, PathNode{startVertex.origin, &startVertex, 0.f}); + openList.emplace(startVertex, PathNode{std::nullopt, 0.f}); while (!openList.empty()) { - const auto [cord, node] = *std::ranges::min_element(openList, + const auto perfectVertex = *std::ranges::min_element(openList, [&endVertex](const auto& a, const auto& b) -> bool { - const auto aCost = a.second.gCost + a.second.navVertex->origin.DistTo(endVertex.origin); - const auto bCost = b.second.gCost + b.second.navVertex->origin.DistTo(endVertex.origin); + const auto aCost = a.second.gCost + a.first.DistTo(endVertex); + const auto bCost = b.second.gCost + b.first.DistTo(endVertex); return aCost < bCost; }); - openList.erase(cord); + closedList.emplace(perfectVertex); + openList.erase(perfectVertex.first); + + for (const auto& neighbor : navMesh.GetNeighbors(perfectVertex.first)) + if (!closedList.contains(neighbor)) + openList.emplace(neighbor, PathNode{perfectVertex.first, neighbor.DistTo(perfectVertex.first) + perfectVertex.second.gCost}); + + + if (perfectVertex.first != endVertex) + continue; + + std::vector path = {}; + + for (std::optional current = perfectVertex.first; current; current = closedList.at(*current).cameFrom ) + path.push_back(current.value()); - for (const auto& neighbor : node.navVertex->connections) - if (!closedList.contains(neighbor->origin)) - closedList.emplace(neighbor->origin, PathNode{cord, neighbor, neighbor->origin.DistTo(cord) + node.gCost}); + return path; } return {}; diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/NavigationMesh.cpp index 5ff1b3f4..55de0f0e 100644 --- a/source/pathfinding/NavigationMesh.cpp +++ b/source/pathfinding/NavigationMesh.cpp @@ -1,4 +1,26 @@ // // Created by Vlad on 28.07.2024. // -#include "omath/pathfinding/NavigationMesh.h" \ No newline at end of file +#include "omath/pathfinding/NavigationMesh.h" + +namespace omath::pathfinding +{ + std::expected NavigationMesh::GetClossestVertex(const Vector3 &point) const + { + const auto res = std::ranges::min_element(m_verTextMap, + [&point](const auto& a, const auto& b) + { + return a.first.DistTo(point) < b.first.DistTo(point); + }); + + if (res == m_verTextMap.cend()) + return std::unexpected("Failed to get clossest point"); + + return res->first; + } + + const std::vector& NavigationMesh::GetNeighbors(const Vector3 &vertex) const + { + return m_verTextMap.at(vertex); + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3c09cbb1..9781a8bf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" file(GLOB TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include(GoogleTest) -add_executable(unit-tests UnitTestPrediction.cpp UnitTestMatrix.cpp) +add_executable(unit-tests UnitTestPrediction.cpp UnitTestMatrix.cpp UnitTestAstar.cpp) target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) diff --git a/tests/UnitTestAstar.cpp b/tests/UnitTestAstar.cpp new file mode 100644 index 00000000..91780e84 --- /dev/null +++ b/tests/UnitTestAstar.cpp @@ -0,0 +1,17 @@ +// +// Created by Vlad on 18.08.2024. +// +#include +#include + + +TEST(UnitTestAstar, FindingRightPath) +{ + omath::pathfinding::NavigationMesh mesh; + + mesh.m_verTextMap[{0.f, 0.f, 0.f}] = {{0.f, 1.f, 0.f}}; + mesh.m_verTextMap[{0.f, 1.f, 0.f}] = {{0.f, 2.f, 0.f}}; + mesh.m_verTextMap[{0.f, 2.f, 0.f}] = {{0.f, 3.f, 0.f}}; + mesh.m_verTextMap[{0.f, 3.f, 0.f}] = {}; + omath::pathfinding::Astar::FindPath({}, {0.f, 3.f, 0.f}, mesh); +} \ No newline at end of file From ee635b7f174f1a954994426153ca514dbf28552f Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 18 Aug 2024 11:04:24 +0300 Subject: [PATCH 027/795] added serialization --- include/omath/pathfinding/NavigationMesh.h | 5 +- source/pathfinding/NavigationMesh.cpp | 60 ++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/include/omath/pathfinding/NavigationMesh.h b/include/omath/pathfinding/NavigationMesh.h index f786501e..a3194f23 100644 --- a/include/omath/pathfinding/NavigationMesh.h +++ b/include/omath/pathfinding/NavigationMesh.h @@ -30,8 +30,9 @@ namespace omath::pathfinding [[nodiscard]] const std::vector& GetNeighbors(const Vector3& vertex) const; - std::list m_vertexes; - + [[nodiscard]] std::vector Serialize() const; + void Deserialize(const std::vector& raw); + std::unordered_map> m_verTextMap; }; } \ No newline at end of file diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/NavigationMesh.cpp index 55de0f0e..39ba6fb8 100644 --- a/source/pathfinding/NavigationMesh.cpp +++ b/source/pathfinding/NavigationMesh.cpp @@ -3,6 +3,8 @@ // #include "omath/pathfinding/NavigationMesh.h" +#include + namespace omath::pathfinding { std::expected NavigationMesh::GetClossestVertex(const Vector3 &point) const @@ -23,4 +25,62 @@ namespace omath::pathfinding { return m_verTextMap.at(vertex); } + + std::vector NavigationMesh::Serialize() const + { + auto dumpToVector =[](const T& t, std::vector& vec){ + for (size_t i = 0; i < sizeof(t); i++) + vec.push_back(*(reinterpret_cast(&t)+i)); + }; + + std::vector raw; + + + for (const auto& [vertex, neighbors] : m_verTextMap) + { + const uint16_t neighborsCount = neighbors.size(); + + dumpToVector(vertex, raw); + dumpToVector(neighborsCount, raw); + + for (const auto& neighbor : neighbors) + dumpToVector(neighbor, raw); + } + return raw; + + } + + void NavigationMesh::Deserialize(const std::vector &raw) + { + auto loadFromVector = [](const std::vector& vec, size_t& offset, auto& value) { + if (offset + sizeof(value) > vec.size()) { + throw std::runtime_error("Deserialize: Invalid input data size."); + } + std::memcpy(&value, vec.data() + offset, sizeof(value)); + offset += sizeof(value); + }; + + m_verTextMap.clear(); + + size_t offset = 0; + + while (offset < raw.size()) { + Vector3 vertex; + loadFromVector(raw, offset, vertex); + + uint16_t neighborsCount; + loadFromVector(raw, offset, neighborsCount); + + std::vector neighbors; + neighbors.reserve(neighborsCount); + + for (size_t i = 0; i < neighborsCount; ++i) { + Vector3 neighbor; + loadFromVector(raw, offset, neighbor); + neighbors.push_back(neighbor); + } + + m_verTextMap.emplace(vertex, std::move(neighbors)); + } + } } From a612832518607f064a9155fcdc363d4cf264b05d Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 18 Aug 2024 11:08:14 +0300 Subject: [PATCH 028/795] fixed code style --- include/omath/pathfinding/NavigationMesh.h | 2 +- source/pathfinding/NavigationMesh.cpp | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/include/omath/pathfinding/NavigationMesh.h b/include/omath/pathfinding/NavigationMesh.h index a3194f23..6143effd 100644 --- a/include/omath/pathfinding/NavigationMesh.h +++ b/include/omath/pathfinding/NavigationMesh.h @@ -32,7 +32,7 @@ namespace omath::pathfinding [[nodiscard]] std::vector Serialize() const; void Deserialize(const std::vector& raw); - + std::unordered_map> m_verTextMap; }; } \ No newline at end of file diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/NavigationMesh.cpp index 39ba6fb8..b09e33df 100644 --- a/source/pathfinding/NavigationMesh.cpp +++ b/source/pathfinding/NavigationMesh.cpp @@ -30,7 +30,7 @@ namespace omath::pathfinding { auto dumpToVector =[](const T& t, std::vector& vec){ for (size_t i = 0; i < sizeof(t); i++) - vec.push_back(*(reinterpret_cast(&t)+i)); + vec.push_back(*(reinterpret_cast(&t)+i)); }; std::vector raw; @@ -52,8 +52,10 @@ namespace omath::pathfinding void NavigationMesh::Deserialize(const std::vector &raw) { - auto loadFromVector = [](const std::vector& vec, size_t& offset, auto& value) { - if (offset + sizeof(value) > vec.size()) { + auto loadFromVector = [](const std::vector& vec, size_t& offset, auto& value) + { + if (offset + sizeof(value) > vec.size()) + { throw std::runtime_error("Deserialize: Invalid input data size."); } std::memcpy(&value, vec.data() + offset, sizeof(value)); @@ -64,7 +66,8 @@ namespace omath::pathfinding size_t offset = 0; - while (offset < raw.size()) { + while (offset < raw.size()) + { Vector3 vertex; loadFromVector(raw, offset, vertex); @@ -74,7 +77,8 @@ namespace omath::pathfinding std::vector neighbors; neighbors.reserve(neighborsCount); - for (size_t i = 0; i < neighborsCount; ++i) { + for (size_t i = 0; i < neighborsCount; ++i) + { Vector3 neighbor; loadFromVector(raw, offset, neighbor); neighbors.push_back(neighbor); From 6df8530ab5c0ac8ca22e6ea9d2cc0dc64cd68e7a Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 18 Aug 2024 15:46:10 +0300 Subject: [PATCH 029/795] added additional methods --- include/omath/pathfinding/NavigationMesh.h | 2 ++ source/pathfinding/Astar.cpp | 1 + source/pathfinding/NavigationMesh.cpp | 9 +++++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/omath/pathfinding/NavigationMesh.h b/include/omath/pathfinding/NavigationMesh.h index 6143effd..7b4ffa65 100644 --- a/include/omath/pathfinding/NavigationMesh.h +++ b/include/omath/pathfinding/NavigationMesh.h @@ -30,6 +30,8 @@ namespace omath::pathfinding [[nodiscard]] const std::vector& GetNeighbors(const Vector3& vertex) const; + [[nodiscard]] + bool Empty() const; [[nodiscard]] std::vector Serialize() const; void Deserialize(const std::vector& raw); diff --git a/source/pathfinding/Astar.cpp b/source/pathfinding/Astar.cpp index 06f20907..a5002e93 100644 --- a/source/pathfinding/Astar.cpp +++ b/source/pathfinding/Astar.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace omath::pathfinding diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/NavigationMesh.cpp index b09e33df..766558ee 100644 --- a/source/pathfinding/NavigationMesh.cpp +++ b/source/pathfinding/NavigationMesh.cpp @@ -4,7 +4,7 @@ #include "omath/pathfinding/NavigationMesh.h" #include - +#include namespace omath::pathfinding { std::expected NavigationMesh::GetClossestVertex(const Vector3 &point) const @@ -26,6 +26,11 @@ namespace omath::pathfinding return m_verTextMap.at(vertex); } + bool NavigationMesh::Empty() const + { + return m_verTextMap.empty(); + } + std::vector NavigationMesh::Serialize() const { auto dumpToVector =[](const T& t, std::vector& vec){ @@ -58,7 +63,7 @@ namespace omath::pathfinding { throw std::runtime_error("Deserialize: Invalid input data size."); } - std::memcpy(&value, vec.data() + offset, sizeof(value)); + std::copy_n(vec.data() + offset, sizeof(value), (uint8_t*)&value); offset += sizeof(value); }; From db57dfd5f98f4e6b76000ef7968f38e1242adc03 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 10:28:07 +0300 Subject: [PATCH 030/795] added camera --- include/omath/matrix.h | 2 + include/omath/projection/Camera.h | 38 ++++++++++++++++ source/CMakeLists.txt | 3 +- source/Vector3.cpp | 33 ++++++++++++-- source/matrix.cpp | 6 +++ source/projection/CMakeLists.txt | 1 + source/projection/Camera.cpp | 74 +++++++++++++++++++++++++++++++ tests/UnitTestAstar.cpp | 2 +- 8 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 include/omath/projection/Camera.h create mode 100644 source/projection/CMakeLists.txt create mode 100644 source/projection/Camera.cpp diff --git a/include/omath/matrix.h b/include/omath/matrix.h index 79655cc5..96103770 100644 --- a/include/omath/matrix.h +++ b/include/omath/matrix.h @@ -51,6 +51,8 @@ namespace omath Matrix operator*(const Matrix &other) const; + Matrix& operator*=(const Matrix &other); + Matrix operator*(float f) const; Matrix operator*(const Vector3 &vec3) const; diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h new file mode 100644 index 00000000..8f25902f --- /dev/null +++ b/include/omath/projection/Camera.h @@ -0,0 +1,38 @@ +// +// Created by Vlad on 27.08.2024. +// + +#pragma once + +#include +#include +#include +#include + + +namespace omath::projection +{ + class Camera + { + public: + void SetViewAngles(const Vector3& viewAngles); + [[nodiscard]] const Vector3& GetViewAngles() const; + + [[nodiscard]] Matrix GetViewMatrix() const; + [[nodiscard]] Matrix GetProjectionMatrix() const; + [[nodiscard]] Matrix GetTranslationMatrix() const; + [[nodiscard]] Matrix GetOrientationMatrix() const; + + [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; + + Vector3 m_viewPort; + float m_fieldOfView; + + float m_farPlaneDistance; + float m_nearPlaneDistance; + + private: + Vector3 m_viewAngles; + Vector3 m_origin; + }; +} diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 499567c0..c35e3a5f 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -5,4 +5,5 @@ target_sources(omath PRIVATE Vector4.cpp) add_subdirectory(prediction) -add_subdirectory(pathfinding) \ No newline at end of file +add_subdirectory(pathfinding) +add_subdirectory(projection) \ No newline at end of file diff --git a/source/Vector3.cpp b/source/Vector3.cpp index e0a68f14..35466f28 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -219,7 +219,12 @@ namespace omath const auto sinYaw = std::sin(angles::DegreesToRadians(yaw)); - return {cosPitch*cosYaw, cosPitch*sinYaw, sinPitch}; + return + { + cosPitch*cosYaw, + cosPitch*sinYaw, + sinPitch + }; } Vector3 Vector3::RightVector(const float pitch, const float yaw, const float roll) @@ -230,13 +235,33 @@ namespace omath const auto cosYaw = std::cos(angles::DegreesToRadians(yaw)); const auto sinYaw = std::sin(angles::DegreesToRadians(yaw)); - const auto cosRoll = std::cos(angles::DegreesToRadians(yaw)); - const auto sinRoll = std::sin(angles::DegreesToRadians(yaw)); + const auto cosRoll = std::cos(angles::DegreesToRadians(roll)); + const auto sinRoll = std::sin(angles::DegreesToRadians(roll)); return {-sinRoll*sinPitch*cosYaw + -cosRoll*-sinYaw, -sinRoll*sinPitch*sinYaw + -cosRoll*cosYaw, - sinRoll*cosPitch}; + -sinRoll*cosPitch}; + } + + Vector3 Vector3::UpVector(float pitch, float yaw, float roll) + { + const auto cosPitch = std::cos(angles::DegreesToRadians(pitch)); + const auto sinPitch = std::sin(angles::DegreesToRadians(pitch)); + + const auto cosYaw = std::cos(angles::DegreesToRadians(yaw)); + const auto sinYaw = std::sin(angles::DegreesToRadians(yaw)); + + const auto cosRoll = std::cos(angles::DegreesToRadians(roll)); + const auto sinRoll = std::sin(angles::DegreesToRadians(roll)); + + + return + { + cosRoll*sinPitch*cosYaw+-sinRoll*-sinYaw, + cosRoll*sinPitch*cosYaw+-sinRoll*-sinYaw, + cosRoll*cosPitch, + }; } Vector3 Vector3::Cross(const Vector3 &v) const diff --git a/source/matrix.cpp b/source/matrix.cpp index 7ca93098..40d9a8ff 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -120,6 +120,12 @@ namespace omath return outMat; } + Matrix & Matrix::operator*=(const Matrix &other) + { + *this = *this * other; + return *this; + } + Matrix Matrix::operator*(const float f) const { auto out = *this; diff --git a/source/projection/CMakeLists.txt b/source/projection/CMakeLists.txt new file mode 100644 index 00000000..0abf8686 --- /dev/null +++ b/source/projection/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(omath PRIVATE Camera.cpp) \ No newline at end of file diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp new file mode 100644 index 00000000..984c414c --- /dev/null +++ b/source/projection/Camera.cpp @@ -0,0 +1,74 @@ +// +// Created by Vlad on 27.08.2024. +// +#include "omath/projection/Camera.h" +#include "omath/angles.h" + + +namespace omath::projection +{ + Matrix Camera::GetViewMatrix() const + { + return GetOrientationMatrix() * GetTranslationMatrix(); + } + + Matrix Camera::GetProjectionMatrix() const + { + const auto aspectRatio = m_viewPort.x / m_viewPort.y; + const auto verticalFov = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); + + + return Matrix( + { + {1.f / (aspectRatio * verticalFov), 0.f, 0.f, 0.f}, + {0.f, 1.f / verticalFov, 0.f, 0.f}, + {0.f, 0.f, m_farPlaneDistance / (m_farPlaneDistance-m_nearPlaneDistance), -m_farPlaneDistance*m_nearPlaneDistance / (m_farPlaneDistance-m_nearPlaneDistance)}, + {0.f, 0.f, 1.f, 0.f} + }); + } + + Matrix Camera::GetTranslationMatrix() const + { + return Matrix( + { + {1.f, 0.f, 0.f, m_origin.x}, + {0.f, 1.f, 0.f, m_origin.y}, + {0.f, 0.f, 1.f, m_origin.z}, + {0.f, 0.f, 0.f, 1.f}, + }); + } + + Matrix Camera::GetOrientationMatrix() const + { + const auto forward = Vector3::ForwardVector(m_viewAngles.x, m_viewAngles.y); + const auto right = Vector3::RightVector(m_viewAngles.x, m_viewAngles.y, m_viewAngles.z); + const auto up = Vector3::UpVector(m_viewAngles.x, m_viewAngles.y, m_viewAngles.z); + + + return Matrix( + { + {right.x, up.y, right.z, 0.f}, + {up.x, up.y, up.y, 0.f}, + {forward.x, forward.y, forward.z, 0.f}, + {0.f, 0.f, 0.f, 1.f}, + }); + } + + std::expected Camera::WorldToScreen(const Vector3 &worldPosition) const + { + const auto posVecAsMatrix = Matrix({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); + + const auto viewProjectionMatrix = GetViewMatrix() * GetProjectionMatrix(); + + auto projected = posVecAsMatrix * viewProjectionMatrix; + + if (projected.At(0, 3) <= 0.f) + return std::unexpected("Projection point is out of camera field of view"); + + projected /= projected.At(0, 3); + + projected *= Matrix::ToScreenMatrix(m_viewPort.x, m_viewPort.y); + + return Vector3{projected.At(0, 0), projected.At(0, 1), projected.At(0, 2)}; + } +} diff --git a/tests/UnitTestAstar.cpp b/tests/UnitTestAstar.cpp index 91780e84..f16cd8e0 100644 --- a/tests/UnitTestAstar.cpp +++ b/tests/UnitTestAstar.cpp @@ -13,5 +13,5 @@ TEST(UnitTestAstar, FindingRightPath) mesh.m_verTextMap[{0.f, 1.f, 0.f}] = {{0.f, 2.f, 0.f}}; mesh.m_verTextMap[{0.f, 2.f, 0.f}] = {{0.f, 3.f, 0.f}}; mesh.m_verTextMap[{0.f, 3.f, 0.f}] = {}; - omath::pathfinding::Astar::FindPath({}, {0.f, 3.f, 0.f}, mesh); + std::ignore = omath::pathfinding::Astar::FindPath({}, {0.f, 3.f, 0.f}, mesh); } \ No newline at end of file From 13188615ed86b71bce15130648c48b06a55c4cf0 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 10:56:56 +0300 Subject: [PATCH 031/795] added unit test --- include/omath/projection/Camera.h | 1 + source/Vector3.cpp | 21 +++++++++++---------- source/projection/Camera.cpp | 11 +++++++++++ tests/CMakeLists.txt | 2 +- tests/UnitTestProjection.cpp | 15 +++++++++++++++ 5 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 tests/UnitTestProjection.cpp diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index 8f25902f..f86c8b61 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -15,6 +15,7 @@ namespace omath::projection class Camera { public: + Camera(const Vector3& position, const Vector3& viewAngles, const Vector3& viewPort, float fov, float near, float far); void SetViewAngles(const Vector3& viewAngles); [[nodiscard]] const Vector3& GetViewAngles() const; diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 35466f28..39d57578 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -238,10 +238,12 @@ namespace omath const auto cosRoll = std::cos(angles::DegreesToRadians(roll)); const auto sinRoll = std::sin(angles::DegreesToRadians(roll)); - - return {-sinRoll*sinPitch*cosYaw + -cosRoll*-sinYaw, - -sinRoll*sinPitch*sinYaw + -cosRoll*cosYaw, - -sinRoll*cosPitch}; + // Right vector calculation + return { + cosYaw * cosRoll - sinYaw * sinPitch * sinRoll, // X component + sinRoll * cosPitch, // Y component + sinYaw * cosRoll + cosYaw * sinPitch * sinRoll // Z component + }; } Vector3 Vector3::UpVector(float pitch, float yaw, float roll) @@ -255,12 +257,11 @@ namespace omath const auto cosRoll = std::cos(angles::DegreesToRadians(roll)); const auto sinRoll = std::sin(angles::DegreesToRadians(roll)); - - return - { - cosRoll*sinPitch*cosYaw+-sinRoll*-sinYaw, - cosRoll*sinPitch*cosYaw+-sinRoll*-sinYaw, - cosRoll*cosPitch, + // Up vector calculation + return { + -sinRoll * cosYaw + cosRoll * sinPitch * sinYaw, // X component + cosRoll * cosPitch, // Y component + -sinRoll * sinYaw - cosRoll * sinPitch * cosYaw // Z component }; } diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 984c414c..dac1ffee 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -7,6 +7,17 @@ namespace omath::projection { + Camera::Camera(const Vector3 &position, const Vector3 &viewAngles, const Vector3 &viewPort, const float fov, const float near, + const float far) + { + m_origin = position; + m_viewAngles = viewAngles; + m_viewPort = viewPort; + m_fieldOfView = fov; + m_nearPlaneDistance = near; + m_farPlaneDistance = far; + } + Matrix Camera::GetViewMatrix() const { return GetOrientationMatrix() * GetTranslationMatrix(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9781a8bf..f47be281 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" file(GLOB TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include(GoogleTest) -add_executable(unit-tests UnitTestPrediction.cpp UnitTestMatrix.cpp UnitTestAstar.cpp) +add_executable(unit-tests UnitTestPrediction.cpp UnitTestMatrix.cpp UnitTestAstar.cpp UnitTestProjection.cpp) target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp new file mode 100644 index 00000000..df7eaebf --- /dev/null +++ b/tests/UnitTestProjection.cpp @@ -0,0 +1,15 @@ +// +// Created by Vlad on 27.08.2024. +// +#include +#include +#include +#include + +TEST(UnitTestProjection, IsPointOnScreen) +{ + const omath::projection::Camera camera({}, {90, 0.f, 0.f} , {1920.f, 1080.f, 0.f}, 120, 10, 100); + + const auto proj = camera.WorldToScreen({0,0,15}); + EXPECT_TRUE(proj.has_value()); +} \ No newline at end of file From 15186785eb6e903a75ac987d25a3315e73c246a0 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 14:50:54 +0300 Subject: [PATCH 032/795] fixed direction vectors --- include/omath/{matrix.h => Matrix.h} | 0 source/Vector3.cpp | 26 +++++--------------------- source/matrix.cpp | 3 ++- source/projection/Camera.cpp | 3 +++ 4 files changed, 10 insertions(+), 22 deletions(-) rename include/omath/{matrix.h => Matrix.h} (100%) diff --git a/include/omath/matrix.h b/include/omath/Matrix.h similarity index 100% rename from include/omath/matrix.h rename to include/omath/Matrix.h diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 39d57578..40664cd1 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -238,31 +238,15 @@ namespace omath const auto cosRoll = std::cos(angles::DegreesToRadians(roll)); const auto sinRoll = std::sin(angles::DegreesToRadians(roll)); - // Right vector calculation - return { - cosYaw * cosRoll - sinYaw * sinPitch * sinRoll, // X component - sinRoll * cosPitch, // Y component - sinYaw * cosRoll + cosYaw * sinPitch * sinRoll // Z component - }; + + return {-sinRoll*sinPitch*cosYaw + -cosRoll*-sinYaw, + -sinRoll*sinPitch*sinYaw + -cosRoll*cosYaw, + sinRoll*cosPitch}; } Vector3 Vector3::UpVector(float pitch, float yaw, float roll) { - const auto cosPitch = std::cos(angles::DegreesToRadians(pitch)); - const auto sinPitch = std::sin(angles::DegreesToRadians(pitch)); - - const auto cosYaw = std::cos(angles::DegreesToRadians(yaw)); - const auto sinYaw = std::sin(angles::DegreesToRadians(yaw)); - - const auto cosRoll = std::cos(angles::DegreesToRadians(roll)); - const auto sinRoll = std::sin(angles::DegreesToRadians(roll)); - - // Up vector calculation - return { - -sinRoll * cosYaw + cosRoll * sinPitch * sinYaw, // X component - cosRoll * cosPitch, // Y component - -sinRoll * sinYaw - cosRoll * sinPitch * cosYaw // Z component - }; + return RightVector(pitch, yaw, roll).Cross(ForwardVector(pitch, yaw)); } Vector3 Vector3::Cross(const Vector3 &v) const diff --git a/source/matrix.cpp b/source/matrix.cpp index 40d9a8ff..7ef65e48 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -1,10 +1,11 @@ -#include "omath/matrix.h" +#include "omath/Matrix.h" #include #include "omath/Vector3.h" #include #include #include +#include namespace omath diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index dac1ffee..6a32d38a 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -2,6 +2,9 @@ // Created by Vlad on 27.08.2024. // #include "omath/projection/Camera.h" + +#include + #include "omath/angles.h" From 8359524cdb2395352b04062cf7d5a1872b3be51f Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 14:57:56 +0300 Subject: [PATCH 033/795] fixed unit test --- tests/UnitTestMatrix.cpp | 2 +- tests/UnitTestProjection.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index dceb2641..a86692f1 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -2,7 +2,7 @@ // Created by vlad on 5/18/2024. // #include -#include +#include #include diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index df7eaebf..c232da95 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 27.08.2024. // #include -#include +#include #include #include From 8fc9a5495877daedbc1ac87ce53db657f89fc88c Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 13:12:35 +0300 Subject: [PATCH 034/795] patched up matrixes --- source/projection/Camera.cpp | 37 +++++++++++++++++++++--------------- tests/UnitTestProjection.cpp | 4 ++-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 6a32d38a..9fd7851f 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -28,26 +28,33 @@ namespace omath::projection Matrix Camera::GetProjectionMatrix() const { - const auto aspectRatio = m_viewPort.x / m_viewPort.y; - const auto verticalFov = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); + const float fRight = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); + const float fLeft = -fRight; + const float vFov = angles::DegreesToRadians(m_fieldOfView) * (m_viewPort.y / m_viewPort.x); + + const float fTop = std::tan(vFov / 2.f); + const float fBottom = -fTop; + + const auto m_fFar = m_farPlaneDistance; + const auto m_fNear = m_nearPlaneDistance; + + return Matrix({ + {2.f / (fRight - fLeft), 0.f, 0.f, 0.f}, + {0.f, 0.f, 2.f / (fTop - fBottom), 0.f}, + {0.f, (m_fFar + m_fNear) / (m_fFar - m_fNear), 0.f, 1.f}, + {0.f, -2.f * m_fNear * m_fFar / (m_fFar - m_fNear), 0.f, 0.f}, + }); - return Matrix( - { - {1.f / (aspectRatio * verticalFov), 0.f, 0.f, 0.f}, - {0.f, 1.f / verticalFov, 0.f, 0.f}, - {0.f, 0.f, m_farPlaneDistance / (m_farPlaneDistance-m_nearPlaneDistance), -m_farPlaneDistance*m_nearPlaneDistance / (m_farPlaneDistance-m_nearPlaneDistance)}, - {0.f, 0.f, 1.f, 0.f} - }); } Matrix Camera::GetTranslationMatrix() const { return Matrix( { - {1.f, 0.f, 0.f, m_origin.x}, - {0.f, 1.f, 0.f, m_origin.y}, - {0.f, 0.f, 1.f, m_origin.z}, + {1.f, 0.f, 0.f, -m_origin.x}, + {0.f, 1.f, 0.f, -m_origin.y}, + {0.f, 0.f, 1.f, -m_origin.z}, {0.f, 0.f, 0.f, 1.f}, }); } @@ -61,9 +68,9 @@ namespace omath::projection return Matrix( { - {right.x, up.y, right.z, 0.f}, - {up.x, up.y, up.y, 0.f}, - {forward.x, forward.y, forward.z, 0.f}, + {right.x, up.x, forward.x, 0.f}, + {right.y, up.y, forward.y, 0.f}, + {right.z, up.z, forward.z, 0.f}, {0.f, 0.f, 0.f, 1.f}, }); } diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index c232da95..67f27b37 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -8,8 +8,8 @@ TEST(UnitTestProjection, IsPointOnScreen) { - const omath::projection::Camera camera({}, {90, 0.f, 0.f} , {1920.f, 1080.f, 0.f}, 120, 10, 100); + const omath::projection::Camera camera({}, {90, 0.f, 0.f} , {1920.f, 1080.f, 0.f}, 120, 10, 100); - const auto proj = camera.WorldToScreen({0,0,15}); + const auto proj = camera.WorldToScreen({0, 0, 20}); EXPECT_TRUE(proj.has_value()); } \ No newline at end of file From e5dbb78b1586e8ebb53bebc0a819549b0a891e81 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 17:35:17 +0300 Subject: [PATCH 035/795] patch --- source/projection/Camera.cpp | 20 ++++++++++---------- tests/UnitTestProjection.cpp | 7 +++++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 9fd7851f..34217d47 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -23,7 +23,7 @@ namespace omath::projection Matrix Camera::GetViewMatrix() const { - return GetOrientationMatrix() * GetTranslationMatrix(); + return GetTranslationMatrix() * GetOrientationMatrix(); } Matrix Camera::GetProjectionMatrix() const @@ -40,10 +40,10 @@ namespace omath::projection const auto m_fNear = m_nearPlaneDistance; return Matrix({ - {2.f / (fRight - fLeft), 0.f, 0.f, 0.f}, - {0.f, 0.f, 2.f / (fTop - fBottom), 0.f}, - {0.f, (m_fFar + m_fNear) / (m_fFar - m_fNear), 0.f, 1.f}, - {0.f, -2.f * m_fNear * m_fFar / (m_fFar - m_fNear), 0.f, 0.f}, + {2.f / (fRight - fLeft), 0.f, 0.f, 0.f}, + {0.f, 2.f / (fTop - fBottom), 0.f, 0.f}, + {0.f, 0.f, (m_fFar + m_fNear) / (m_fFar - m_fNear), 1.f}, + {0.f, 0.f, -2.f * m_fNear * m_fFar / (m_fFar - m_fNear), 0.f}, }); } @@ -52,10 +52,10 @@ namespace omath::projection { return Matrix( { - {1.f, 0.f, 0.f, -m_origin.x}, - {0.f, 1.f, 0.f, -m_origin.y}, - {0.f, 0.f, 1.f, -m_origin.z}, - {0.f, 0.f, 0.f, 1.f}, + {1.f, 0.f, 0.f, 0.f}, + {0.f, 1.f, 0.f, 1.f}, + {0.f, 0.f, 1.f, 0.f}, + {-m_origin.x, -m_origin.y, -m_origin.z, 1.f}, }); } @@ -90,6 +90,6 @@ namespace omath::projection projected *= Matrix::ToScreenMatrix(m_viewPort.x, m_viewPort.y); - return Vector3{projected.At(0, 0), projected.At(0, 1), projected.At(0, 2)}; + return Vector3{projected.At(0, 0), projected.At(0, 2), projected.At(0, 1)}; } } diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index 67f27b37..bb727a3a 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -1,6 +1,7 @@ // // Created by Vlad on 27.08.2024. // +#include #include #include #include @@ -8,8 +9,10 @@ TEST(UnitTestProjection, IsPointOnScreen) { - const omath::projection::Camera camera({}, {90, 0.f, 0.f} , {1920.f, 1080.f, 0.f}, 120, 10, 100); + const omath::projection::Camera camera({0, 0, 0}, {0, 0.f, 0.f} , {1920.f, 1080.f, 0.f}, 110, 0.1, 500); - const auto proj = camera.WorldToScreen({0, 0, 20}); + const auto proj = camera.WorldToScreen({5, 0, 0}); + if (proj) + std::print("{} {} {}", proj->x, proj->z, proj->y); EXPECT_TRUE(proj.has_value()); } \ No newline at end of file From 6356d06e34ea5719939990b3ea9335fcd4edf1a6 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 21:47:08 +0300 Subject: [PATCH 036/795] patch --- source/matrix.cpp | 1 - source/projection/Camera.cpp | 29 ++++++++++++++--------------- tests/UnitTestProjection.cpp | 6 +++--- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/source/matrix.cpp b/source/matrix.cpp index 7ef65e48..c6e0d699 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -5,7 +5,6 @@ #include #include #include -#include namespace omath diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 34217d47..fd93ec16 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -31,21 +31,20 @@ namespace omath::projection const float fRight = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); const float fLeft = -fRight; - const float vFov = angles::DegreesToRadians(m_fieldOfView) * (m_viewPort.y / m_viewPort.x); + const float verticalFov = angles::DegreesToRadians(m_fieldOfView) * (m_viewPort.y / m_viewPort.x); - const float fTop = std::tan(vFov / 2.f); - const float fBottom = -fTop; + const float top = std::tan(verticalFov / 2.f); + const float botton = -top; - const auto m_fFar = m_farPlaneDistance; - const auto m_fNear = m_nearPlaneDistance; + const auto far = m_farPlaneDistance; + const auto near = m_nearPlaneDistance; return Matrix({ - {2.f / (fRight - fLeft), 0.f, 0.f, 0.f}, - {0.f, 2.f / (fTop - fBottom), 0.f, 0.f}, - {0.f, 0.f, (m_fFar + m_fNear) / (m_fFar - m_fNear), 1.f}, - {0.f, 0.f, -2.f * m_fNear * m_fFar / (m_fFar - m_fNear), 0.f}, + {1.555f / (fRight - fLeft), 0.f, 0.f, 0.f}, + {0.f, 1.15f / (top - botton), 0.f, 0.f}, + {0.f, 0.f, (far + near) / (far - near), 1.f}, + {0.f, 0.f, -near * far / (far - near), 0.f}, }); - } Matrix Camera::GetTranslationMatrix() const @@ -68,10 +67,10 @@ namespace omath::projection return Matrix( { - {right.x, up.x, forward.x, 0.f}, - {right.y, up.y, forward.y, 0.f}, - {right.z, up.z, forward.z, 0.f}, - {0.f, 0.f, 0.f, 1.f}, + {right.x, up.x, forward.x, 0.f}, + {right.y, up.y, forward.y, 0.f}, + {right.z, up.z, forward.z, 0.f}, + {0.f, 0.f, 0.f, 1.f}, }); } @@ -90,6 +89,6 @@ namespace omath::projection projected *= Matrix::ToScreenMatrix(m_viewPort.x, m_viewPort.y); - return Vector3{projected.At(0, 0), projected.At(0, 2), projected.At(0, 1)}; + return Vector3{projected.At(0, 0), projected.At(0, 1), projected.At(0, 2)}; } } diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index bb727a3a..a989c9ee 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -9,10 +9,10 @@ TEST(UnitTestProjection, IsPointOnScreen) { - const omath::projection::Camera camera({0, 0, 0}, {0, 0.f, 0.f} , {1920.f, 1080.f, 0.f}, 110, 0.1, 500); + const omath::projection::Camera camera({5, 0, 0}, {0, 0.f, 0.f} , {1920.f, 1080.f, 0.f}, 110, 0.1, 500); - const auto proj = camera.WorldToScreen({5, 0, 0}); + const auto proj = camera.WorldToScreen({10, 0, 0}); if (proj) - std::print("{} {} {}", proj->x, proj->z, proj->y); + std::print("{} {} {}", proj->x, proj->y, proj->z); EXPECT_TRUE(proj.has_value()); } \ No newline at end of file From 927d56fef37e4da64490ef8ad942de558a2fb822 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 23:03:33 +0300 Subject: [PATCH 037/795] added debug methods --- include/omath/projection/Camera.h | 5 ++++- source/projection/Camera.cpp | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index f86c8b61..7b2f8643 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -18,9 +18,12 @@ namespace omath::projection Camera(const Vector3& position, const Vector3& viewAngles, const Vector3& viewPort, float fov, float near, float far); void SetViewAngles(const Vector3& viewAngles); [[nodiscard]] const Vector3& GetViewAngles() const; + static float& GetFloat1(); + + static float& GetFloat2(); [[nodiscard]] Matrix GetViewMatrix() const; - [[nodiscard]] Matrix GetProjectionMatrix() const; + [[nodiscard]] Matrix GetProjectionMatrix(float scaleX, float scaleY) const; [[nodiscard]] Matrix GetTranslationMatrix() const; [[nodiscard]] Matrix GetOrientationMatrix() const; diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index fd93ec16..7d6b2ac0 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -21,12 +21,22 @@ namespace omath::projection m_farPlaneDistance = far; } + float & Camera::GetFloat1() { + static float m_float1 = 1.52550f; + return m_float1; + } + + float & Camera::GetFloat2() { + static float m_float2 = 1.14500f; + return m_float2; + } + Matrix Camera::GetViewMatrix() const { return GetTranslationMatrix() * GetOrientationMatrix(); } - Matrix Camera::GetProjectionMatrix() const + Matrix Camera::GetProjectionMatrix(const float scaleX, const float scaleY) const { const float fRight = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); const float fLeft = -fRight; @@ -40,8 +50,8 @@ namespace omath::projection const auto near = m_nearPlaneDistance; return Matrix({ - {1.555f / (fRight - fLeft), 0.f, 0.f, 0.f}, - {0.f, 1.15f / (top - botton), 0.f, 0.f}, + {scaleX / (fRight - fLeft), 0.f, 0.f, 0.f}, + {0.f, scaleY / (top - botton), 0.f, 0.f}, {0.f, 0.f, (far + near) / (far - near), 1.f}, {0.f, 0.f, -near * far / (far - near), 0.f}, }); @@ -78,7 +88,7 @@ namespace omath::projection { const auto posVecAsMatrix = Matrix({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); - const auto viewProjectionMatrix = GetViewMatrix() * GetProjectionMatrix(); + const auto viewProjectionMatrix = GetViewMatrix() * GetProjectionMatrix(GetFloat1(), GetFloat2()); auto projected = posVecAsMatrix * viewProjectionMatrix; From dbc5c86cc4ef7dd1ace7f3ee59b083f027c32544 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 20:53:34 +0300 Subject: [PATCH 038/795] fixed typo --- source/projection/Camera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 7d6b2ac0..f8d490b3 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -62,7 +62,7 @@ namespace omath::projection return Matrix( { {1.f, 0.f, 0.f, 0.f}, - {0.f, 1.f, 0.f, 1.f}, + {0.f, 1.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {-m_origin.x, -m_origin.y, -m_origin.z, 1.f}, }); From 3b897e27a8e3facadbb1226c67bb99affc537e7e Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 21:01:16 +0300 Subject: [PATCH 039/795] fixed naming --- source/projection/Camera.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index f8d490b3..47540a04 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -38,20 +38,20 @@ namespace omath::projection Matrix Camera::GetProjectionMatrix(const float scaleX, const float scaleY) const { - const float fRight = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); - const float fLeft = -fRight; + const float right = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); + const float left = -right; const float verticalFov = angles::DegreesToRadians(m_fieldOfView) * (m_viewPort.y / m_viewPort.x); const float top = std::tan(verticalFov / 2.f); - const float botton = -top; + const float bottom = -top; const auto far = m_farPlaneDistance; const auto near = m_nearPlaneDistance; return Matrix({ - {scaleX / (fRight - fLeft), 0.f, 0.f, 0.f}, - {0.f, scaleY / (top - botton), 0.f, 0.f}, + {scaleX / (right - left), 0.f, 0.f, 0.f}, + {0.f, scaleY / (top - bottom), 0.f, 0.f}, {0.f, 0.f, (far + near) / (far - near), 1.f}, {0.f, 0.f, -near * far / (far - near), 0.f}, }); From 8a67b7edbe96cc85a27eb8482068c3f400b15e8c Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 27 Aug 2024 21:41:21 +0300 Subject: [PATCH 040/795] maybe improved matrix --- include/omath/projection/Camera.h | 2 +- source/projection/Camera.cpp | 24 ++++++++++-------------- tests/UnitTestProjection.cpp | 6 +++--- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index 7b2f8643..c96dd16d 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -23,7 +23,7 @@ namespace omath::projection static float& GetFloat2(); [[nodiscard]] Matrix GetViewMatrix() const; - [[nodiscard]] Matrix GetProjectionMatrix(float scaleX, float scaleY) const; + [[nodiscard]] Matrix GetProjectionMatrix() const; [[nodiscard]] Matrix GetTranslationMatrix() const; [[nodiscard]] Matrix GetOrientationMatrix() const; diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 47540a04..fe88929c 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -2,9 +2,7 @@ // Created by Vlad on 27.08.2024. // #include "omath/projection/Camera.h" - #include - #include "omath/angles.h" @@ -36,24 +34,22 @@ namespace omath::projection return GetTranslationMatrix() * GetOrientationMatrix(); } - Matrix Camera::GetProjectionMatrix(const float scaleX, const float scaleY) const + Matrix Camera::GetProjectionMatrix() const { - const float right = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); + const float right = m_nearPlaneDistance * std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); const float left = -right; - const float verticalFov = angles::DegreesToRadians(m_fieldOfView) * (m_viewPort.y / m_viewPort.x); - - const float top = std::tan(verticalFov / 2.f); + const float top = right * (m_viewPort.y / m_viewPort.x); const float bottom = -top; - const auto far = m_farPlaneDistance; - const auto near = m_nearPlaneDistance; + const float near = m_nearPlaneDistance; + const float far = m_farPlaneDistance; return Matrix({ - {scaleX / (right - left), 0.f, 0.f, 0.f}, - {0.f, scaleY / (top - bottom), 0.f, 0.f}, - {0.f, 0.f, (far + near) / (far - near), 1.f}, - {0.f, 0.f, -near * far / (far - near), 0.f}, + {2 * near / (right - left), 0.f, (right + left) / (right - left), 0.f}, + {0.f, 2 * near / (top - bottom), (top + bottom) / (top - bottom), 0.f}, + {0.f, 0.f, (far + near) / (far - near), 2 * near * far / (far - near)}, + {0.f, 0.f, -1.f, 0.f}, }); } @@ -88,7 +84,7 @@ namespace omath::projection { const auto posVecAsMatrix = Matrix({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); - const auto viewProjectionMatrix = GetViewMatrix() * GetProjectionMatrix(GetFloat1(), GetFloat2()); + const auto viewProjectionMatrix = GetViewMatrix() * GetProjectionMatrix(); auto projected = posVecAsMatrix * viewProjectionMatrix; diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index a989c9ee..dcd5d4ca 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -9,10 +9,10 @@ TEST(UnitTestProjection, IsPointOnScreen) { - const omath::projection::Camera camera({5, 0, 0}, {0, 0.f, 0.f} , {1920.f, 1080.f, 0.f}, 110, 0.1, 500); + const omath::projection::Camera camera({0, 0, 0}, {0, 0.f, 0.f} , {1920.f, 1080.f, 0.f}, 110, 0.1, 500); - const auto proj = camera.WorldToScreen({10, 0, 0}); + const auto proj = camera.WorldToScreen({100, 0, 15}); if (proj) - std::print("{} {} {}", proj->x, proj->y, proj->z); + std::print("{} {}", proj->x, proj->y); EXPECT_TRUE(proj.has_value()); } \ No newline at end of file From 74372a9f38dba696853770cbb1036e0732bc02cc Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 28 Aug 2024 01:26:27 +0300 Subject: [PATCH 041/795] patch --- source/projection/Camera.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index fe88929c..ee118765 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -2,7 +2,9 @@ // Created by Vlad on 27.08.2024. // #include "omath/projection/Camera.h" + #include + #include "omath/angles.h" @@ -36,20 +38,23 @@ namespace omath::projection Matrix Camera::GetProjectionMatrix() const { - const float right = m_nearPlaneDistance * std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); + const float right = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); const float left = -right; - const float top = right * (m_viewPort.y / m_viewPort.x); + const float verticalFov = angles::DegreesToRadians(m_fieldOfView) * (m_viewPort.y / m_viewPort.x); + + const float top = std::tan(verticalFov / 2.f); const float bottom = -top; - const float near = m_nearPlaneDistance; - const float far = m_farPlaneDistance; + const auto far = m_farPlaneDistance; + const auto near = m_nearPlaneDistance; - return Matrix({ - {2 * near / (right - left), 0.f, (right + left) / (right - left), 0.f}, - {0.f, 2 * near / (top - bottom), (top + bottom) / (top - bottom), 0.f}, - {0.f, 0.f, (far + near) / (far - near), 2 * near * far / (far - near)}, - {0.f, 0.f, -1.f, 0.f}, + return Matrix( + { + {GetFloat1() * 2.f * near / (right - left), 0.f, (right + left) / (right - left), 0.f}, + {0.f, GetFloat1() * 2.f * near / (top - bottom), (top + bottom) / (top - bottom), 0.f}, + {0.f, 0.f, (far + near) / (far - near), 2.f * near * far / (far - near)}, + {0.f, 0.f, 1.f, 0.f}, }); } From f447752c6fd4cdbaa433e611bbde170a0d160d3a Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 28 Aug 2024 17:50:22 +0300 Subject: [PATCH 042/795] changed world to screen --- source/projection/Camera.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index ee118765..e7ae0f94 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -38,23 +38,18 @@ namespace omath::projection Matrix Camera::GetProjectionMatrix() const { - const float right = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); - const float left = -right; - - const float verticalFov = angles::DegreesToRadians(m_fieldOfView) * (m_viewPort.y / m_viewPort.x); - - const float top = std::tan(verticalFov / 2.f); - const float bottom = -top; + const float fovHalfTan = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); + const auto aspectRatio = m_viewPort.x / m_viewPort.y; const auto far = m_farPlaneDistance; const auto near = m_nearPlaneDistance; return Matrix( { - {GetFloat1() * 2.f * near / (right - left), 0.f, (right + left) / (right - left), 0.f}, - {0.f, GetFloat1() * 2.f * near / (top - bottom), (top + bottom) / (top - bottom), 0.f}, + {1.f / (aspectRatio*fovHalfTan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fovHalfTan, 0.f, 0.f}, {0.f, 0.f, (far + near) / (far - near), 2.f * near * far / (far - near)}, - {0.f, 0.f, 1.f, 0.f}, + {0.f, 0.f, -1.f, 0.f}, }); } From 67713f52bdc07e724a860691290d29def4d33083 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 30 Aug 2024 02:42:58 +0300 Subject: [PATCH 043/795] extracted some methods --- include/omath/Matrix.h | 15 ++++-- include/omath/projection/Camera.h | 13 +++++- source/matrix.cpp | 77 +++++++++++++++++++++++++------ source/projection/Camera.cpp | 60 +++++------------------- tests/UnitTestProjection.cpp | 2 +- 5 files changed, 98 insertions(+), 69 deletions(-) diff --git a/include/omath/Matrix.h b/include/omath/Matrix.h index 96103770..1cb26337 100644 --- a/include/omath/Matrix.h +++ b/include/omath/Matrix.h @@ -2,7 +2,7 @@ #include #include #include - +#include namespace omath { @@ -13,11 +13,20 @@ namespace omath public: Matrix(size_t rows, size_t columns); - explicit Matrix(const std::vector> &rows); + Matrix(const std::initializer_list>& rows); [[nodiscard]] static Matrix ToScreenMatrix(float screenWidth, float screenHeight); + [[nodiscard]] + static Matrix TranslationMatrix(const Vector3& diff); + + [[nodiscard]] + static Matrix OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up); + + [[nodiscard]] + static Matrix ProjectionMatrix(float fielOfView, float aspectRatio,float near, float far); + Matrix(const Matrix &other); Matrix(size_t rows, size_t columns, const float *pRaw); @@ -42,7 +51,7 @@ namespace omath void SetDataFromRaw(const float* pRawMatrix); [[nodiscard]] - Matrix Transpose(); + Matrix Transpose() const; void Set(float val); diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index c96dd16d..d3890773 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -12,10 +12,19 @@ namespace omath::projection { + class ViewPort final + { + public: + float m_width; + float m_height; + + [[nodiscard]] float AspectRatio() const {return m_width / m_height;} + }; + class Camera { public: - Camera(const Vector3& position, const Vector3& viewAngles, const Vector3& viewPort, float fov, float near, float far); + Camera(const Vector3& position, const Vector3& viewAngles, const ViewPort& viewPort, float fov, float near, float far); void SetViewAngles(const Vector3& viewAngles); [[nodiscard]] const Vector3& GetViewAngles() const; static float& GetFloat1(); @@ -29,7 +38,7 @@ namespace omath::projection [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; - Vector3 m_viewPort; + ViewPort m_viewPort{}; float m_fieldOfView; float m_farPlaneDistance; diff --git a/source/matrix.cpp b/source/matrix.cpp index c6e0d699..cd98ee32 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -1,10 +1,13 @@ #include "omath/Matrix.h" +#include "omath/Vector3.h" +#include "omath/angles.h" + #include -#include "omath/Vector3.h" #include #include #include +#include namespace omath @@ -22,17 +25,26 @@ namespace omath Set(0.f); } - Matrix::Matrix(const std::vector> &rows) + Matrix::Matrix(const std::initializer_list>& rows) { m_rows = rows.size(); - m_columns = rows[0].size(); + m_columns = rows.begin()->size(); + for (const auto& row : rows) + if (row.size() != m_columns) + throw std::invalid_argument("All rows must have the same number of columns."); + m_data = std::make_unique(m_rows * m_columns); - for (size_t i = 0; i < m_rows; ++i) - for (size_t j = 0; j < m_columns; ++j) - At(i,j) = rows[i][j]; + size_t i = 0; + for (const auto& row : rows) + { + size_t j = 0; + for (const auto& value : row) + At(i, j++) = value; + ++i; + } } Matrix::Matrix(const Matrix &other) @@ -247,7 +259,7 @@ namespace omath return ((i + j + 2) % 2 == 0) ? tmp : -tmp; } - Matrix Matrix::Transpose() + Matrix Matrix::Transpose() const { Matrix transposed = {m_columns, m_rows}; @@ -298,14 +310,51 @@ namespace omath return Strip(i, j).Determinant(); } - Matrix Matrix::ToScreenMatrix(float screenWidth, float screenHeight) + Matrix Matrix::ToScreenMatrix(const float screenWidth, const float screenHeight) + { + return + { + {screenWidth / 2.f, 0.f, 0.f, 0.f}, + {0.f, -screenHeight / 2.f, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {screenWidth / 2.f, screenHeight / 2.f, 0.f, 1.f}, + }; + } + + Matrix Matrix::TranslationMatrix(const Vector3 &diff) + { + return + { + {1.f, 0.f, 0.f, 0.f}, + {0.f, 1.f, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {diff.x, diff.y, diff.z, 1.f}, + }; + } + + Matrix Matrix::OrientationMatrix(const Vector3 &forward, const Vector3 &right, const Vector3 &up) { - return Matrix({ - {screenWidth / 2.f, 0.f, 0.f, 0.f}, - {0.f, -screenHeight / 2.f, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {screenWidth / 2.f, screenHeight / 2.f, 0.f, 1.f}, - }); + return + { + {right.x, up.x, forward.x, 0.f}, + {right.y, up.y, forward.y, 0.f}, + {right.z, up.z, forward.z, 0.f}, + {0.f, 0.f, 0.f, 1.f}, + }; + } + + Matrix Matrix::ProjectionMatrix(const float fielOfView, const float aspectRatio, const float near, + const float far) + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fielOfView) / 2.f); + + return + { + {1.f / (aspectRatio*fovHalfTan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fovHalfTan, 0.f, 0.f}, + {0.f, 0.f, (far + near) / (far - near), 2.f * near * far / (far - near)}, + {0.f, 0.f, -1.f, 0.f} + }; } const float * Matrix::Raw() const diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index e7ae0f94..63e55783 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -10,8 +10,8 @@ namespace omath::projection { - Camera::Camera(const Vector3 &position, const Vector3 &viewAngles, const Vector3 &viewPort, const float fov, const float near, - const float far) + Camera::Camera(const Vector3 &position, const Vector3 &viewAngles, const ViewPort &viewPort, + const float fov, const float near, const float far) { m_origin = position; m_viewAngles = viewAngles; @@ -21,79 +21,41 @@ namespace omath::projection m_farPlaneDistance = far; } - float & Camera::GetFloat1() { - static float m_float1 = 1.52550f; + float& Camera::GetFloat1() { + static float m_float1 = 0.36689f; return m_float1; } - float & Camera::GetFloat2() { + float& Camera::GetFloat2() { static float m_float2 = 1.14500f; return m_float2; } Matrix Camera::GetViewMatrix() const - { - return GetTranslationMatrix() * GetOrientationMatrix(); - } - - Matrix Camera::GetProjectionMatrix() const - { - const float fovHalfTan = std::tan(angles::DegreesToRadians(m_fieldOfView) / 2.f); - const auto aspectRatio = m_viewPort.x / m_viewPort.y; - - const auto far = m_farPlaneDistance; - const auto near = m_nearPlaneDistance; - - return Matrix( - { - {1.f / (aspectRatio*fovHalfTan), 0.f, 0.f, 0.f}, - {0.f, 1.f / fovHalfTan, 0.f, 0.f}, - {0.f, 0.f, (far + near) / (far - near), 2.f * near * far / (far - near)}, - {0.f, 0.f, -1.f, 0.f}, - }); - } - - Matrix Camera::GetTranslationMatrix() const - { - return Matrix( - { - {1.f, 0.f, 0.f, 0.f}, - {0.f, 1.f, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {-m_origin.x, -m_origin.y, -m_origin.z, 1.f}, - }); - } - - Matrix Camera::GetOrientationMatrix() const { const auto forward = Vector3::ForwardVector(m_viewAngles.x, m_viewAngles.y); const auto right = Vector3::RightVector(m_viewAngles.x, m_viewAngles.y, m_viewAngles.z); const auto up = Vector3::UpVector(m_viewAngles.x, m_viewAngles.y, m_viewAngles.z); - - return Matrix( - { - {right.x, up.x, forward.x, 0.f}, - {right.y, up.y, forward.y, 0.f}, - {right.z, up.z, forward.z, 0.f}, - {0.f, 0.f, 0.f, 1.f}, - }); + return Matrix::TranslationMatrix(-m_origin) * Matrix::OrientationMatrix(forward, right, up); } std::expected Camera::WorldToScreen(const Vector3 &worldPosition) const { const auto posVecAsMatrix = Matrix({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); - const auto viewProjectionMatrix = GetViewMatrix() * GetProjectionMatrix(); - auto projected = posVecAsMatrix * viewProjectionMatrix; + const auto projectionMatrix = Matrix::ProjectionMatrix(m_fieldOfView, m_viewPort.AspectRatio(), + m_nearPlaneDistance, m_farPlaneDistance); + + auto projected = posVecAsMatrix * (GetViewMatrix() * projectionMatrix); if (projected.At(0, 3) <= 0.f) return std::unexpected("Projection point is out of camera field of view"); projected /= projected.At(0, 3); - projected *= Matrix::ToScreenMatrix(m_viewPort.x, m_viewPort.y); + projected *= Matrix::ToScreenMatrix(m_viewPort.m_width, m_viewPort.m_height); return Vector3{projected.At(0, 0), projected.At(0, 1), projected.At(0, 2)}; } diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index dcd5d4ca..2d929309 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -9,7 +9,7 @@ TEST(UnitTestProjection, IsPointOnScreen) { - const omath::projection::Camera camera({0, 0, 0}, {0, 0.f, 0.f} , {1920.f, 1080.f, 0.f}, 110, 0.1, 500); + const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.1f, 500.f); const auto proj = camera.WorldToScreen({100, 0, 15}); if (proj) From a5dbf040d34a5bd1250d4dc3443009a95cbd3027 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 30 Aug 2024 02:56:22 +0300 Subject: [PATCH 044/795] removed debug methods --- include/omath/projection/Camera.h | 5 +---- source/projection/Camera.cpp | 10 ---------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index d3890773..4bbd069e 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -26,11 +26,8 @@ namespace omath::projection public: Camera(const Vector3& position, const Vector3& viewAngles, const ViewPort& viewPort, float fov, float near, float far); void SetViewAngles(const Vector3& viewAngles); - [[nodiscard]] const Vector3& GetViewAngles() const; - static float& GetFloat1(); - - static float& GetFloat2(); + [[nodiscard]] const Vector3& GetViewAngles() const; [[nodiscard]] Matrix GetViewMatrix() const; [[nodiscard]] Matrix GetProjectionMatrix() const; [[nodiscard]] Matrix GetTranslationMatrix() const; diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 63e55783..49bb12c5 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -21,16 +21,6 @@ namespace omath::projection m_farPlaneDistance = far; } - float& Camera::GetFloat1() { - static float m_float1 = 0.36689f; - return m_float1; - } - - float& Camera::GetFloat2() { - static float m_float2 = 1.14500f; - return m_float2; - } - Matrix Camera::GetViewMatrix() const { const auto forward = Vector3::ForwardVector(m_viewAngles.x, m_viewAngles.y); From aa03e42d1df8e54de753467344e2b10c6862b349 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Fri, 30 Aug 2024 15:45:54 +0300 Subject: [PATCH 045/795] Update readme.md --- readme.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index fe3eb259..93f001d0 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,13 @@ -# Oranges's Math Library (omath) +
+ +![banner](https://i.imgur.com/sjtpKi8.png) + +![GitHub License](https://img.shields.io/github/license/orange-cpp/omath) +![GitHub contributors](https://img.shields.io/github/contributors/orange-cpp/omath) +![GitHub top language](https://img.shields.io/github/languages/top/orange-cpp/omath) +![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath) +
-## Overview Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. ## Features From 8a1744917589e37ec600d083b57483305902e7a4 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Sun, 1 Sep 2024 20:57:43 +0300 Subject: [PATCH 046/795] Update readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 93f001d0..94f20386 100644 --- a/readme.md +++ b/readme.md @@ -5,6 +5,7 @@ ![GitHub License](https://img.shields.io/github/license/orange-cpp/omath) ![GitHub contributors](https://img.shields.io/github/contributors/orange-cpp/omath) ![GitHub top language](https://img.shields.io/github/languages/top/orange-cpp/omath) +[![CodeFactor](https://www.codefactor.io/repository/github/orange-cpp/omath/badge)](https://www.codefactor.io/repository/github/orange-cpp/omath) ![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath) From e958cbeba289df6e72a9b7c308257f697e0792c3 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 2 Sep 2024 14:52:04 +0300 Subject: [PATCH 047/795] added additional check --- source/projection/Camera.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 49bb12c5..35c79dfc 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -45,6 +45,10 @@ namespace omath::projection projected /= projected.At(0, 3); + if (projected.At(0, 0) < -1.f || projected.At(0, 0) > 1.f || + projected.At(0, 1) < -1.f || projected.At(0, 1) > 1.f) + return std::unexpected("Projection point is out screen bounds"); + projected *= Matrix::ToScreenMatrix(m_viewPort.m_width, m_viewPort.m_height); return Vector3{projected.At(0, 0), projected.At(0, 1), projected.At(0, 2)}; From 813169a8289fa4b2e07024b8ce1415cc7c74e00e Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 1 Sep 2024 18:22:31 +0300 Subject: [PATCH 048/795] added unit test for vec3 --- tests/CMakeLists.txt | 2 +- tests/UnitTestVector3.cpp | 263 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 tests/UnitTestVector3.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f47be281..3f50f898 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" file(GLOB TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include(GoogleTest) -add_executable(unit-tests UnitTestPrediction.cpp UnitTestMatrix.cpp UnitTestAstar.cpp UnitTestProjection.cpp) +add_executable(unit-tests UnitTestPrediction.cpp UnitTestMatrix.cpp UnitTestAstar.cpp UnitTestProjection.cpp UnitTestVector3.cpp) target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) diff --git a/tests/UnitTestVector3.cpp b/tests/UnitTestVector3.cpp new file mode 100644 index 00000000..e55b786a --- /dev/null +++ b/tests/UnitTestVector3.cpp @@ -0,0 +1,263 @@ +// +// Created by Vlad on 01.09.2024. +// +#include +#include + +using namespace omath; + +class UnitTestVector3 : public ::testing::Test +{ +protected: + Vector3 v1; + Vector3 v2; + + void SetUp() override + { + v1 = Vector3(1.0f, 2.0f, 3.0f); + v2 = Vector3(4.0f, 5.0f, 6.0f); + } +}; + +// Test constructor and default values +TEST_F(UnitTestVector3, Constructor_Default) +{ + Vector3 v; + EXPECT_FLOAT_EQ(v.x, 0.0f); + EXPECT_FLOAT_EQ(v.y, 0.0f); + EXPECT_FLOAT_EQ(v.z, 0.0f); +} + +TEST_F(UnitTestVector3, Constructor_Values) +{ + Vector3 v(1.0f, 2.0f, 3.0f); + EXPECT_FLOAT_EQ(v.x, 1.0f); + EXPECT_FLOAT_EQ(v.y, 2.0f); + EXPECT_FLOAT_EQ(v.z, 3.0f); +} + +// Test equality operators +TEST_F(UnitTestVector3, EqualityOperator) +{ + Vector3 v3(1.0f, 2.0f, 3.0f); + EXPECT_TRUE(v1 == v3); + EXPECT_FALSE(v1 == v2); +} + +TEST_F(UnitTestVector3, InequalityOperator) +{ + Vector3 v3(1.0f, 2.0f, 3.0f); + EXPECT_FALSE(v1 != v3); + EXPECT_TRUE(v1 != v2); +} + +// Test arithmetic operators +TEST_F(UnitTestVector3, AdditionOperator) +{ + Vector3 v3 = v1 + v2; + EXPECT_FLOAT_EQ(v3.x, 5.0f); + EXPECT_FLOAT_EQ(v3.y, 7.0f); + EXPECT_FLOAT_EQ(v3.z, 9.0f); +} + +TEST_F(UnitTestVector3, SubtractionOperator) +{ + Vector3 v3 = v2 - v1; + EXPECT_FLOAT_EQ(v3.x, 3.0f); + EXPECT_FLOAT_EQ(v3.y, 3.0f); + EXPECT_FLOAT_EQ(v3.z, 3.0f); +} + +TEST_F(UnitTestVector3, MultiplicationOperator) +{ + Vector3 v3 = v1 * 2.0f; + EXPECT_FLOAT_EQ(v3.x, 2.0f); + EXPECT_FLOAT_EQ(v3.y, 4.0f); + EXPECT_FLOAT_EQ(v3.z, 6.0f); +} + +TEST_F(UnitTestVector3, MultiplicationWithVectorOperator) +{ + Vector3 v3 = v1 * v2; + EXPECT_FLOAT_EQ(v3.x, 4.0f); + EXPECT_FLOAT_EQ(v3.y, 10.0f); + EXPECT_FLOAT_EQ(v3.z, 18.0f); +} + +TEST_F(UnitTestVector3, DivisionOperator) +{ + Vector3 v3 = v2 / 2.0f; + EXPECT_FLOAT_EQ(v3.x, 2.0f); + EXPECT_FLOAT_EQ(v3.y, 2.5f); + EXPECT_FLOAT_EQ(v3.z, 3.0f); +} + +TEST_F(UnitTestVector3, DivisionWithVectorOperator) +{ + Vector3 v3 = v2 / v1; + EXPECT_FLOAT_EQ(v3.x, 4.0f); + EXPECT_FLOAT_EQ(v3.y, 2.5f); + EXPECT_FLOAT_EQ(v3.z, 2.0f); +} + +// Test compound assignment operators +TEST_F(UnitTestVector3, AdditionAssignmentOperator) +{ + v1 += v2; + EXPECT_FLOAT_EQ(v1.x, 5.0f); + EXPECT_FLOAT_EQ(v1.y, 7.0f); + EXPECT_FLOAT_EQ(v1.z, 9.0f); +} + +TEST_F(UnitTestVector3, SubtractionAssignmentOperator) +{ + v1 -= v2; + EXPECT_FLOAT_EQ(v1.x, -3.0f); + EXPECT_FLOAT_EQ(v1.y, -3.0f); + EXPECT_FLOAT_EQ(v1.z, -3.0f); +} + +TEST_F(UnitTestVector3, MultiplicationAssignmentOperator) +{ + v1 *= 2.0f; + EXPECT_FLOAT_EQ(v1.x, 2.0f); + EXPECT_FLOAT_EQ(v1.y, 4.0f); + EXPECT_FLOAT_EQ(v1.z, 6.0f); +} + +TEST_F(UnitTestVector3, MultiplicationWithVectorAssignmentOperator) +{ + v1 *= v2; + EXPECT_FLOAT_EQ(v1.x, 4.0f); + EXPECT_FLOAT_EQ(v1.y, 10.0f); + EXPECT_FLOAT_EQ(v1.z, 18.0f); +} + +TEST_F(UnitTestVector3, DivisionAssignmentOperator) +{ + v1 /= 2.0f; + EXPECT_FLOAT_EQ(v1.x, 0.5f); + EXPECT_FLOAT_EQ(v1.y, 1.0f); + EXPECT_FLOAT_EQ(v1.z, 1.5f); +} + +TEST_F(UnitTestVector3, DivisionWithVectorAssignmentOperator) +{ + v1 /= v2; + EXPECT_FLOAT_EQ(v1.x, 0.25f); + EXPECT_FLOAT_EQ(v1.y, 0.4f); + EXPECT_FLOAT_EQ(v1.z, 0.5f); +} + +TEST_F(UnitTestVector3, NegationOperator) +{ + Vector3 v3 = -v1; + EXPECT_FLOAT_EQ(v3.x, -1.0f); + EXPECT_FLOAT_EQ(v3.y, -2.0f); + EXPECT_FLOAT_EQ(v3.z, -3.0f); +} + +// Test other member functions +TEST_F(UnitTestVector3, DistTo) +{ + float dist = v1.DistTo(v2); + EXPECT_FLOAT_EQ(dist, sqrt(27.0f)); +} + +TEST_F(UnitTestVector3, DistToSqr) +{ + float distSqr = v1.DistToSqr(v2); + EXPECT_FLOAT_EQ(distSqr, 27.0f); +} + +TEST_F(UnitTestVector3, DotProduct) +{ + float dot = v1.Dot(v2); + EXPECT_FLOAT_EQ(dot, 32.0f); +} + +TEST_F(UnitTestVector3, CrossProduct) +{ + Vector3 v3 = v1.Cross(v2); + EXPECT_FLOAT_EQ(v3.x, -3.0f); + EXPECT_FLOAT_EQ(v3.y, 6.0f); + EXPECT_FLOAT_EQ(v3.z, -3.0f); +} + +TEST_F(UnitTestVector3, Length) +{ + float length = v1.Length(); + EXPECT_FLOAT_EQ(length, sqrt(14.0f)); +} + +TEST_F(UnitTestVector3, LengthSqr) +{ + float lengthSqr = v1.LengthSqr(); + EXPECT_FLOAT_EQ(lengthSqr, 14.0f); +} + +TEST_F(UnitTestVector3, Length2D) +{ + float length2D = v1.Length2D(); + EXPECT_FLOAT_EQ(length2D, sqrt(5.0f)); +} + +TEST_F(UnitTestVector3, Abs) +{ + Vector3 v3(-1.0f, -2.0f, -3.0f); + v3.Abs(); + EXPECT_FLOAT_EQ(v3.x, 1.0f); + EXPECT_FLOAT_EQ(v3.y, 2.0f); + EXPECT_FLOAT_EQ(v3.z, 3.0f); +} + +TEST_F(UnitTestVector3, Sum) +{ + float sum = v1.Sum(); + EXPECT_FLOAT_EQ(sum, 6.0f); +} + +TEST_F(UnitTestVector3, Sum2D) +{ + float sum2D = v1.Sum2D(); + EXPECT_FLOAT_EQ(sum2D, 3.0f); +} + +TEST_F(UnitTestVector3, ViewAngleTo) +{ + Vector3 angle = v1.ViewAngleTo(v1 + Vector3(0.f, 0.f, 5.f)); + EXPECT_NEAR(angle.x, 90.f, 0.01f); // Approximate values, you can fine-tune the expected values + EXPECT_NEAR(angle.y, 0.f, 0.01f); +} + +TEST_F(UnitTestVector3, ForwardVector) +{ + Vector3 forward = Vector3::ForwardVector(0.0f, 0.0f); + EXPECT_FLOAT_EQ(forward.x, 1.0f); + EXPECT_FLOAT_EQ(forward.y, 0.0f); + EXPECT_FLOAT_EQ(forward.z, 0.0f); +} + +TEST_F(UnitTestVector3, RightVector) +{ + Vector3 right = Vector3::RightVector(0.0f, 0.0f, 0.0f); + EXPECT_FLOAT_EQ(right.x, 0.0f); + EXPECT_FLOAT_EQ(right.y, -1.0f); + EXPECT_FLOAT_EQ(right.z, 0.0f); +} + +TEST_F(UnitTestVector3, UpVector) +{ + Vector3 up = Vector3::UpVector(0.f, 0.0f, 0.0f); + EXPECT_FLOAT_EQ(up.x, 0.0f); + EXPECT_FLOAT_EQ(up.y, 0.0f); + EXPECT_FLOAT_EQ(up.z, 1.0f); +} + +TEST_F(UnitTestVector3, Normalized) +{ + Vector3 v3 = v1.Normalized(); + EXPECT_NEAR(v3.x, 0.26726f, 0.0001f); + EXPECT_NEAR(v3.y, 0.53452f, 0.0001f); + EXPECT_NEAR(v3.z, 0.80178f, 0.0001f); +} \ No newline at end of file From 9a3a4214b877d97d043c58ad93f7e6dcd8a1a434 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 1 Sep 2024 18:38:25 +0300 Subject: [PATCH 049/795] improved matrix tests --- include/omath/Matrix.h | 7 +- source/matrix.cpp | 13 ++- tests/UnitTestMatrix.cpp | 176 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 186 insertions(+), 10 deletions(-) diff --git a/include/omath/Matrix.h b/include/omath/Matrix.h index 1cb26337..a8d0028d 100644 --- a/include/omath/Matrix.h +++ b/include/omath/Matrix.h @@ -11,6 +11,7 @@ namespace omath class Matrix final { public: + Matrix(); Matrix(size_t rows, size_t columns); Matrix(const std::initializer_list>& rows); @@ -99,8 +100,8 @@ namespace omath ~Matrix(); private: - size_t m_rows = 0; - size_t m_columns = 0; - std::unique_ptr m_data = nullptr; + size_t m_rows; + size_t m_columns; + std::unique_ptr m_data; }; } \ No newline at end of file diff --git a/source/matrix.cpp b/source/matrix.cpp index cd98ee32..597ee017 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -83,6 +83,10 @@ namespace omath m_columns = other.m_columns; m_data = std::move(other.m_data); + other.m_rows = 0; + other.m_columns = 0; + + other.m_data = nullptr; } size_t Matrix::ColumnsCount() const noexcept @@ -357,7 +361,7 @@ namespace omath }; } - const float * Matrix::Raw() const + const float* Matrix::Raw() const { return m_data.get(); } @@ -367,4 +371,11 @@ namespace omath for (size_t i = 0; i < m_columns*m_rows; ++i) At(i / m_rows, i % m_columns) = pRawMatrix[i]; } + + Matrix::Matrix() + { + m_columns = 0; + m_rows = 0; + m_data = nullptr; + } } \ No newline at end of file diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index a86692f1..c2f08407 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -3,15 +3,179 @@ // #include #include -#include +#include "omath/Vector3.h" +using namespace omath; -TEST(UnitTestMatrix, ToString) + +class UnitTestMatrix : public ::testing::Test +{ +protected: + Matrix m1; + Matrix m2; + + void SetUp() override { + m1 = Matrix(2, 2); + m2 = Matrix{{1.0f, 2.0f}, {3.0f, 4.0f}}; + } +}; + +// Test constructors +TEST_F(UnitTestMatrix, Constructor_Size) +{ + Matrix m(3, 3); + EXPECT_EQ(m.RowCount(), 3); + EXPECT_EQ(m.ColumnsCount(), 3); +} + +TEST_F(UnitTestMatrix, Constructor_InitializerList) { - omath::Matrix matrix(2, 2); - matrix.Set(1.1); - const auto str = matrix.ToSrtring(); + Matrix m{{1.0f, 2.0f}, {3.0f, 4.0f}}; + EXPECT_EQ(m.RowCount(), 2); + EXPECT_EQ(m.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m.At(1, 1), 4.0f); +} - std::cout << str; +TEST_F(UnitTestMatrix, Constructor_Copy) +{ + Matrix m3 = m2; + EXPECT_EQ(m3.RowCount(), m2.RowCount()); + EXPECT_EQ(m3.ColumnsCount(), m2.ColumnsCount()); + EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); +} + +TEST_F(UnitTestMatrix, Constructor_Move) +{ + Matrix m3 = std::move(m2); + EXPECT_EQ(m3.RowCount(), 2); + EXPECT_EQ(m3.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m3.At(0, 0), 1.0f); + EXPECT_EQ(m2.RowCount(), 0); // m2 should be empty after the move + EXPECT_EQ(m2.ColumnsCount(), 0); +} + +// Test matrix operations +TEST_F(UnitTestMatrix, Operator_Multiplication_Matrix) +{ + Matrix m3 = m2 * m2; + EXPECT_EQ(m3.RowCount(), 2); + EXPECT_EQ(m3.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m3.At(0, 0), 7.0f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 22.0f); +} +TEST_F(UnitTestMatrix, Operator_Multiplication_Vector3) +{ + Vector3 v(1.0f, 2.0f, 3.0f); + Matrix m3(3, 3, new float[9]{1, 0, 0, 0, 1, 0, 0, 0, 1}); + Matrix result = m3 * v; + + EXPECT_FLOAT_EQ(result.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(result.At(1, 0), 2.0f); + EXPECT_FLOAT_EQ(result.At(2, 0), 3.0f); +} + +TEST_F(UnitTestMatrix, Operator_Multiplication_Scalar) +{ + Matrix m3 = m2 * 2.0f; + EXPECT_FLOAT_EQ(m3.At(0, 0), 2.0f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 8.0f); +} + +TEST_F(UnitTestMatrix, Operator_Division_Scalar) +{ + Matrix m3 = m2 / 2.0f; + EXPECT_FLOAT_EQ(m3.At(0, 0), 0.5f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 2.0f); +} + +// Test matrix functions +TEST_F(UnitTestMatrix, Transpose) +{ + Matrix m3 = m2.Transpose(); + EXPECT_FLOAT_EQ(m3.At(0, 1), 3.0f); + EXPECT_FLOAT_EQ(m3.At(1, 0), 2.0f); +} + +TEST_F(UnitTestMatrix, Determinant) +{ + float det = m2.Determinant(); + EXPECT_FLOAT_EQ(det, -2.0f); +} + +TEST_F(UnitTestMatrix, Minor) +{ + float minor = m2.Minor(0, 0); + EXPECT_FLOAT_EQ(minor, 4.0f); +} + +TEST_F(UnitTestMatrix, AlgComplement) +{ + float algComp = m2.AlgComplement(0, 0); + EXPECT_FLOAT_EQ(algComp, 4.0f); +} + +TEST_F(UnitTestMatrix, Strip) +{ + Matrix m3 = m2.Strip(0, 0); + EXPECT_EQ(m3.RowCount(), 1); + EXPECT_EQ(m3.ColumnsCount(), 1); + EXPECT_FLOAT_EQ(m3.At(0, 0), 4.0f); +} + +TEST_F(UnitTestMatrix, ProjectionMatrix) +{ + Matrix proj = Matrix::ProjectionMatrix(45.0f, 1.33f, 0.1f, 100.0f); + EXPECT_EQ(proj.RowCount(), 4); + EXPECT_EQ(proj.ColumnsCount(), 4); + // Further checks on projection matrix elements could be added +} + +// Test other member functions +TEST_F(UnitTestMatrix, Set) +{ + m1.Set(3.0f); + EXPECT_FLOAT_EQ(m1.At(0, 0), 3.0f); + EXPECT_FLOAT_EQ(m1.At(1, 1), 3.0f); +} + +TEST_F(UnitTestMatrix, Sum) +{ + float sum = m2.Sum(); + EXPECT_FLOAT_EQ(sum, 10.0f); +} + +TEST_F(UnitTestMatrix, Clear) +{ + m2.Clear(); + EXPECT_FLOAT_EQ(m2.At(0, 0), 0.0f); + EXPECT_FLOAT_EQ(m2.At(1, 1), 0.0f); +} + +TEST_F(UnitTestMatrix, ToString) +{ + std::string str = m2.ToSrtring(); + EXPECT_FALSE(str.empty()); +} + +// Test assignment operators +TEST_F(UnitTestMatrix, AssignmentOperator_Copy) +{ + Matrix m3(2, 2); + m3 = m2; + EXPECT_EQ(m3.RowCount(), m2.RowCount()); + EXPECT_EQ(m3.ColumnsCount(), m2.ColumnsCount()); + EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); +} + +TEST_F(UnitTestMatrix, AssignmentOperator_Move) +{ + Matrix m3(2, 2); + m3 = std::move(m2); + EXPECT_EQ(m3.RowCount(), 2); + EXPECT_EQ(m3.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m3.At(0, 0), 1.0f); + EXPECT_EQ(m2.RowCount(), 0); // m2 should be empty after the move + EXPECT_EQ(m2.ColumnsCount(), 0); } \ No newline at end of file From b78a2e274773bd139e0968d88c5d1d13d4063ece Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 2 Sep 2024 13:57:30 +0300 Subject: [PATCH 050/795] added tests for color fixed matrix move operator --- include/omath/Vector4.h | 2 +- include/omath/color.h | 4 +- source/Vector4.cpp | 7 +++ source/color.cpp | 5 ++ source/matrix.cpp | 3 ++ tests/CMakeLists.txt | 9 +++- tests/UnitTestColor.cpp | 113 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 tests/UnitTestColor.cpp diff --git a/include/omath/Vector4.h b/include/omath/Vector4.h index 18ca171d..32fa324c 100644 --- a/include/omath/Vector4.h +++ b/include/omath/Vector4.h @@ -13,7 +13,7 @@ namespace omath float w = 0.f; Vector4(const float x = 0.f, const float y = 0.f, const float z = 0.f, const float w = 0.f) : Vector3(x, y, z), w(w) {} - Vector4() = default; + Vector4(); [[nodiscard]] bool operator==(const Vector4& src) const; diff --git a/include/omath/color.h b/include/omath/color.h index 856833f0..2a5f2ba0 100644 --- a/include/omath/color.h +++ b/include/omath/color.h @@ -21,11 +21,11 @@ namespace omath::color [[nodiscard]] Vector3 Blend(const Vector3& first, const Vector3& second, float ratio); - class Color : public Vector4 + class Color final : public Vector4 { public: Color(float r, float g, float b, float a); - + explicit Color(); [[nodiscard]] static Color FromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); diff --git a/source/Vector4.cpp b/source/Vector4.cpp index 526a9d17..43ee1750 100644 --- a/source/Vector4.cpp +++ b/source/Vector4.cpp @@ -131,4 +131,11 @@ namespace omath { return x + y + z + w; } + + Vector4::Vector4() + { + x = 0.f; + y = 0.f; + z = 0.f; + } } diff --git a/source/color.cpp b/source/color.cpp index 880c431b..cbe19957 100644 --- a/source/color.cpp +++ b/source/color.cpp @@ -95,4 +95,9 @@ namespace omath::color return hsvData; } + + Color::Color() : Vector4(0.f, 0.f, 0.f, 0.f) + { + + } } diff --git a/source/matrix.cpp b/source/matrix.cpp index 597ee017..37a5742d 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -200,6 +200,9 @@ namespace omath m_columns = other.m_columns; m_data = std::move(other.m_data); + other.m_rows = 0.f; + other.m_columns = 0.f; + return *this; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3f50f898..f2f2c9f5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,10 +2,15 @@ enable_testing() project(unit-tests) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") -file(GLOB TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include(GoogleTest) -add_executable(unit-tests UnitTestPrediction.cpp UnitTestMatrix.cpp UnitTestAstar.cpp UnitTestProjection.cpp UnitTestVector3.cpp) +add_executable(unit-tests + UnitTestPrediction.cpp + UnitTestMatrix.cpp + UnitTestAstar.cpp + UnitTestProjection.cpp + UnitTestVector3.cpp + UnitTestColor.cpp) target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp new file mode 100644 index 00000000..58686cda --- /dev/null +++ b/tests/UnitTestColor.cpp @@ -0,0 +1,113 @@ +// +// Created by Vlad on 01.09.2024. +// +#include +#include + + +using namespace omath::color; + +class ColorTest : public ::testing::Test +{ +protected: + Color color1; + Color color2; + + void SetUp() override + { + color1 = Color::Red(); + color2 = Color::Green(); + } +}; + +// Test constructors +TEST_F(ColorTest, Constructor_Float) +{ + Color color(0.5f, 0.5f, 0.5f, 1.0f); + EXPECT_FLOAT_EQ(color.x, 0.5f); + EXPECT_FLOAT_EQ(color.y, 0.5f); + EXPECT_FLOAT_EQ(color.z, 0.5f); + EXPECT_FLOAT_EQ(color.w, 1.0f); +} + +TEST_F(ColorTest, Constructor_Vector4) +{ + omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f); + Color color(vec); + EXPECT_FLOAT_EQ(color.x, 0.2f); + EXPECT_FLOAT_EQ(color.y, 0.4f); + EXPECT_FLOAT_EQ(color.z, 0.6f); + EXPECT_FLOAT_EQ(color.w, 0.8f); +} + +// Test static methods for color creation +TEST_F(ColorTest, FromRGBA) +{ + Color color = Color::FromRGBA(128, 64, 32, 255); + EXPECT_FLOAT_EQ(color.x, 128.0f / 255.0f); + EXPECT_FLOAT_EQ(color.y, 64.0f / 255.0f); + EXPECT_FLOAT_EQ(color.z, 32.0f / 255.0f); + EXPECT_FLOAT_EQ(color.w, 1.0f); +} + +TEST_F(ColorTest, FromHSV) +{ + Color color = Color::FromHSV(0.0f, 1.0f, 1.0f); // Red in HSV + EXPECT_FLOAT_EQ(color.x, 1.0f); + EXPECT_FLOAT_EQ(color.y, 0.0f); + EXPECT_FLOAT_EQ(color.z, 0.0f); + EXPECT_FLOAT_EQ(color.w, 1.0f); +} + +// Test HSV conversion +TEST_F(ColorTest, ToHSV) +{ + HSV hsv = color1.ToHSV(); // Red color + EXPECT_FLOAT_EQ(hsv.m_hue, 0.0f); + EXPECT_FLOAT_EQ(hsv.m_saturation, 1.0f); + EXPECT_FLOAT_EQ(hsv.m_value, 1.0f); +} + +// Test color blending +TEST_F(ColorTest, Blend) +{ + Color blended = color1.Blend(color2, 0.5f); + EXPECT_FLOAT_EQ(blended.x, 0.5f); + EXPECT_FLOAT_EQ(blended.y, 0.5f); + EXPECT_FLOAT_EQ(blended.z, 0.0f); + EXPECT_FLOAT_EQ(blended.w, 1.0f); +} + +// Test predefined colors +TEST_F(ColorTest, PredefinedColors) +{ + Color red = Color::Red(); + Color green = Color::Green(); + Color blue = Color::Blue(); + + EXPECT_FLOAT_EQ(red.x, 1.0f); + EXPECT_FLOAT_EQ(red.y, 0.0f); + EXPECT_FLOAT_EQ(red.z, 0.0f); + EXPECT_FLOAT_EQ(red.w, 1.0f); + + EXPECT_FLOAT_EQ(green.x, 0.0f); + EXPECT_FLOAT_EQ(green.y, 1.0f); + EXPECT_FLOAT_EQ(green.z, 0.0f); + EXPECT_FLOAT_EQ(green.w, 1.0f); + + EXPECT_FLOAT_EQ(blue.x, 0.0f); + EXPECT_FLOAT_EQ(blue.y, 0.0f); + EXPECT_FLOAT_EQ(blue.z, 1.0f); + EXPECT_FLOAT_EQ(blue.w, 1.0f); +} + +// Test non-member function: Blend for Vector3 +TEST_F(ColorTest, BlendVector3) +{ + omath::Vector3 v1(1.0f, 0.0f, 0.0f); // Red + omath::Vector3 v2(0.0f, 1.0f, 0.0f); // Green + omath::Vector3 blended = Blend(v1, v2, 0.5f); + EXPECT_FLOAT_EQ(blended.x, 0.5f); + EXPECT_FLOAT_EQ(blended.y, 0.5f); + EXPECT_FLOAT_EQ(blended.z, 0.0f); +} \ No newline at end of file From 48981fefbe0f645225c6c5cb47f2bb18f6850360 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 2 Sep 2024 14:13:02 +0300 Subject: [PATCH 051/795] fixed naming --- tests/UnitTestColor.cpp | 18 +++++++++--------- tests/UnitTestProjection.cpp | 2 -- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp index 58686cda..ffe828c7 100644 --- a/tests/UnitTestColor.cpp +++ b/tests/UnitTestColor.cpp @@ -7,7 +7,7 @@ using namespace omath::color; -class ColorTest : public ::testing::Test +class UnitTestColor : public ::testing::Test { protected: Color color1; @@ -21,7 +21,7 @@ class ColorTest : public ::testing::Test }; // Test constructors -TEST_F(ColorTest, Constructor_Float) +TEST_F(UnitTestColor, Constructor_Float) { Color color(0.5f, 0.5f, 0.5f, 1.0f); EXPECT_FLOAT_EQ(color.x, 0.5f); @@ -30,7 +30,7 @@ TEST_F(ColorTest, Constructor_Float) EXPECT_FLOAT_EQ(color.w, 1.0f); } -TEST_F(ColorTest, Constructor_Vector4) +TEST_F(UnitTestColor, Constructor_Vector4) { omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f); Color color(vec); @@ -41,7 +41,7 @@ TEST_F(ColorTest, Constructor_Vector4) } // Test static methods for color creation -TEST_F(ColorTest, FromRGBA) +TEST_F(UnitTestColor, FromRGBA) { Color color = Color::FromRGBA(128, 64, 32, 255); EXPECT_FLOAT_EQ(color.x, 128.0f / 255.0f); @@ -50,7 +50,7 @@ TEST_F(ColorTest, FromRGBA) EXPECT_FLOAT_EQ(color.w, 1.0f); } -TEST_F(ColorTest, FromHSV) +TEST_F(UnitTestColor, FromHSV) { Color color = Color::FromHSV(0.0f, 1.0f, 1.0f); // Red in HSV EXPECT_FLOAT_EQ(color.x, 1.0f); @@ -60,7 +60,7 @@ TEST_F(ColorTest, FromHSV) } // Test HSV conversion -TEST_F(ColorTest, ToHSV) +TEST_F(UnitTestColor, ToHSV) { HSV hsv = color1.ToHSV(); // Red color EXPECT_FLOAT_EQ(hsv.m_hue, 0.0f); @@ -69,7 +69,7 @@ TEST_F(ColorTest, ToHSV) } // Test color blending -TEST_F(ColorTest, Blend) +TEST_F(UnitTestColor, Blend) { Color blended = color1.Blend(color2, 0.5f); EXPECT_FLOAT_EQ(blended.x, 0.5f); @@ -79,7 +79,7 @@ TEST_F(ColorTest, Blend) } // Test predefined colors -TEST_F(ColorTest, PredefinedColors) +TEST_F(UnitTestColor, PredefinedColors) { Color red = Color::Red(); Color green = Color::Green(); @@ -102,7 +102,7 @@ TEST_F(ColorTest, PredefinedColors) } // Test non-member function: Blend for Vector3 -TEST_F(ColorTest, BlendVector3) +TEST_F(UnitTestColor, BlendVector3) { omath::Vector3 v1(1.0f, 0.0f, 0.0f); // Red omath::Vector3 v2(0.0f, 1.0f, 0.0f); // Green diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index 2d929309..fc2c54d9 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -12,7 +12,5 @@ TEST(UnitTestProjection, IsPointOnScreen) const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.1f, 500.f); const auto proj = camera.WorldToScreen({100, 0, 15}); - if (proj) - std::print("{} {}", proj->x, proj->y); EXPECT_TRUE(proj.has_value()); } \ No newline at end of file From de7ef015dddca3e205406f9b13d5790b9896ec12 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 2 Sep 2024 16:14:03 +0300 Subject: [PATCH 052/795] added vec2 --- include/omath/Vector2.h | 74 ++++++++++++++++ include/omath/Vector3.h | 26 ++---- source/CMakeLists.txt | 3 +- source/Vector2.cpp | 163 +++++++++++++++++++++++++++++++++++ source/Vector3.cpp | 71 +++++++-------- tests/UnitTestPrediction.cpp | 2 +- 6 files changed, 277 insertions(+), 62 deletions(-) create mode 100644 include/omath/Vector2.h create mode 100644 source/Vector2.cpp diff --git a/include/omath/Vector2.h b/include/omath/Vector2.h new file mode 100644 index 00000000..a19e3b44 --- /dev/null +++ b/include/omath/Vector2.h @@ -0,0 +1,74 @@ +// +// Created by Vlad on 02.09.2024. +// + +#pragma once +#include + + +namespace omath +{ + class Vector2 + { + public: + float x = 0.f; + float y = 0.f; + + // Constructors + Vector2() = default; + Vector2(float x, float y); + + // Equality operators + bool operator==(const Vector2& src) const; + bool operator!=(const Vector2& src) const; + + // Compound assignment operators + Vector2& operator+=(const Vector2& v); + Vector2& operator-=(const Vector2& v); + Vector2& operator*=(const Vector2& v); + Vector2& operator/=(const Vector2& v); + + Vector2& operator*=(float fl); + Vector2& operator/=(float fl); + Vector2& operator+=(float fl); + Vector2& operator-=(float fl); + + // Basic vector operations + [[nodiscard]] float DistTo(const Vector2& vOther) const; + [[nodiscard]] float DistToSqr(const Vector2& vOther) const; + [[nodiscard]] float Dot(const Vector2& vOther) const; + [[nodiscard]] float Length() const; + [[nodiscard]] float LengthSqr() const; + Vector2& Abs(); + + template + const type& As() const + { + return *reinterpret_cast(this); + } + template + type& As() + { + return *reinterpret_cast(this); + } + + // Unary negation operator + Vector2 operator-() const; + + // Binary arithmetic operators + Vector2 operator+(const Vector2& v) const; + Vector2 operator-(const Vector2& v) const; + Vector2 operator*(float fl) const; + Vector2 operator/(float fl) const; + + + // Normalize the vector + [[nodiscard]] Vector2 Normalized() const; + + // Sum of elements + [[nodiscard]] float Sum() const; + + [[nodiscard]] + std::tuple AsTuple() const; + }; +} \ No newline at end of file diff --git a/include/omath/Vector3.h b/include/omath/Vector3.h index 1bb88077..b3cc2bf0 100644 --- a/include/omath/Vector3.h +++ b/include/omath/Vector3.h @@ -6,20 +6,16 @@ #include #include +#include "omath/Vector2.h" + namespace omath { - class Vector3 { + class Vector3 : public Vector2 + { public: - float x = 0.f; - float y = 0.f; float z = 0.f; - Vector3(const float x, const float y, const float z) - { - this->x = x; - this->y = y; - this->z = z; - } + Vector3(float x, float y, float z); Vector3() = default; bool operator==(const Vector3& src) const; @@ -50,16 +46,6 @@ namespace omath Vector3 operator/(float fl) const; Vector3 operator/(const Vector3& v) const; - template - const type& As() const - { - return *reinterpret_cast(this); - } - template - type& As() - { - return *reinterpret_cast(this); - } [[nodiscard]] Vector3 Cross(const Vector3 &v) const; [[nodiscard]] static Vector3 CreateVelocity(float pitch, float yaw, float speed); @@ -74,6 +60,8 @@ namespace omath [[nodiscard]] Vector3 Normalized() const; + + [[nodiscard]] std::tuple AsTuple() const; }; } // ReSharper disable once CppRedundantNamespaceDefinition diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index c35e3a5f..495eacc5 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -2,7 +2,8 @@ target_sources(omath PRIVATE Vector3.cpp matrix.cpp color.cpp - Vector4.cpp) + Vector4.cpp + Vector2.cpp) add_subdirectory(prediction) add_subdirectory(pathfinding) diff --git a/source/Vector2.cpp b/source/Vector2.cpp new file mode 100644 index 00000000..bdba7af6 --- /dev/null +++ b/source/Vector2.cpp @@ -0,0 +1,163 @@ +// +// Created by Vlad on 02.09.2024. +// +#include "omath/Vector2.h" +#include + + +namespace omath +{ + // Constructors + Vector2::Vector2(const float x, const float y) : x(x), y(y) {} + + // Equality operators + bool Vector2::operator==(const Vector2& src) const + { + return x == src.x && y == src.y; + } + + bool Vector2::operator!=(const Vector2& src) const + { + return !(*this == src); + } + + // Compound assignment operators + Vector2& Vector2::operator+=(const Vector2& v) + { + x += v.x; + y += v.y; + return *this; + } + + Vector2& Vector2::operator-=(const Vector2& v) + { + x -= v.x; + y -= v.y; + return *this; + } + + Vector2& Vector2::operator*=(float fl) + { + x *= fl; + y *= fl; + return *this; + } + + Vector2& Vector2::operator/=(float fl) + { + x /= fl; + y /= fl; + return *this; + } + + Vector2& Vector2::operator+=(float fl) + { + x += fl; + y += fl; + return *this; + } + + Vector2& Vector2::operator-=(float fl) + { + x -= fl; + y -= fl; + return *this; + } + + // Basic vector operations + float Vector2::DistTo(const Vector2& vOther) const + { + return std::sqrt(DistToSqr(vOther)); + } + + float Vector2::DistToSqr(const Vector2& vOther) const + { + return (x - vOther.x) * (x - vOther.x) + (y - vOther.y) * (y - vOther.y); + } + + float Vector2::Dot(const Vector2& vOther) const + { + return x * vOther.x + y * vOther.y; + } + + float Vector2::Length() const + { + return std::sqrt(x * x + y * y); + } + + float Vector2::LengthSqr() const + { + return x * x + y * y; + } + + Vector2& Vector2::Abs() + { + x = std::abs(x); + y = std::abs(y); + return *this; + } + + // Unary negation operator + Vector2 Vector2::operator-() const + { + return {-x, -y}; + } + + // Binary arithmetic operators + Vector2 Vector2::operator+(const Vector2& v) const + { + return {x + v.x, y + v.y}; + } + + Vector2 Vector2::operator-(const Vector2& v) const + { + return {x - v.x, y - v.y}; + } + + Vector2 Vector2::operator*(float fl) const + { + return {x * fl, y * fl}; + } + + Vector2 Vector2::operator/(float fl) const + { + return {x / fl, y / fl}; + } + + // Normalize the vector + Vector2 Vector2::Normalized() const + { + float len = Length(); + if (len > 0.f) { + return {x / len, y / len}; + } + return {0.f, 0.f}; + } + + // Sum of elements + float Vector2::Sum() const + { + return x + y; + } + + Vector2 &Vector2::operator*=(const Vector2 &v) + { + x *= v.x; + y *= v.y; + + return *this; + } + + Vector2 &Vector2::operator/=(const Vector2 &v) + { + x /= v.x; + y /= v.y; + + return *this; + } + + std::tuple Vector2::AsTuple() const + { + return std::make_tuple(x, y); + } +} \ No newline at end of file diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 40664cd1..fcbd061b 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -8,20 +8,25 @@ namespace omath { + + Vector3::Vector3(const float x, const float y, const float z) : Vector2(x, y), z(z) + { + + } + bool Vector3::operator==(const Vector3 &src) const { - return (src.x == x) and (src.y == y) and (src.z == z); + return Vector2::operator==(src) && (src.z == z); } bool Vector3::operator!=(const Vector3 &src) const { - return (src.x != x) or (src.y != y) or (src.z != z); + return !(*this == src); } Vector3 &Vector3::operator+=(const Vector3 &v) { - x += v.x; - y += v.y; + Vector2::operator+=(v); z += v.z; return *this; @@ -29,8 +34,7 @@ namespace omath Vector3 &Vector3::operator-=(const Vector3 &v) { - x -= v.x; - y -= v.y; + Vector2::operator-=(v); z -= v.z; return *this; @@ -38,8 +42,7 @@ namespace omath Vector3 &Vector3::operator*=(const float fl) { - x *= fl; - y *= fl; + Vector2::operator*=(fl); z *= fl; return *this; @@ -47,8 +50,7 @@ namespace omath Vector3 &Vector3::operator*=(const Vector3 &v) { - x *= v.x; - y *= v.y; + Vector2::operator*=(v); z *= v.z; return *this; @@ -56,8 +58,7 @@ namespace omath Vector3 &Vector3::operator/=(const Vector3 &v) { - x /= v.x; - y /= v.y; + Vector2::operator/=(v); z /= v.z; return *this; @@ -65,8 +66,7 @@ namespace omath Vector3 &Vector3::operator+=(const float fl) { - x += fl; - y += fl; + Vector2::operator+=(fl); z += fl; return *this; @@ -74,8 +74,7 @@ namespace omath Vector3 &Vector3::operator/=(const float fl) { - x /= fl; - y /= fl; + Vector2::operator/=(fl); z /= fl; return *this; @@ -83,8 +82,7 @@ namespace omath Vector3 &Vector3::operator-=(const float fl) { - x -= fl; - y -= fl; + Vector2::operator-=(fl); z -= fl; return *this; @@ -92,19 +90,12 @@ namespace omath float Vector3::DistTo(const Vector3 &vOther) const { - Vector3 delta; - - delta.x = x - vOther.x; - delta.y = y - vOther.y; - delta.z = z - vOther.z; - - return delta.Length(); + return (*this - vOther).Length(); } Vector3 &Vector3::Abs() { - x = std::abs(x); - y = std::abs(y); + Vector2::Abs(); z = std::abs(z); return *this; @@ -112,34 +103,27 @@ namespace omath float Vector3::DistToSqr(const Vector3 &vOther) const { - Vector3 delta; - - delta.x = x - vOther.x; - delta.y = y - vOther.y; - delta.z = z - vOther.z; - - return delta.LengthSqr(); + return (*this - vOther).LengthSqr(); } float Vector3::Dot(const Vector3 &vOther) const { - return (x * vOther.x + y * vOther.y + z * vOther.z); + return Vector2::Dot(vOther) + z * vOther.z; } float Vector3::Length() const { - return std::sqrt(x * x + y * y + z * z); + return std::sqrt(Vector2::LengthSqr() + z * z); } float Vector3::LengthSqr() const { - return (x * x + y * y + z * z); + return Vector2::LengthSqr() + z * z; } float Vector3::Length2D() const { - return std::sqrt(x * x + y * y); - + return Vector2::Length(); } Vector3 Vector3::operator-() const @@ -189,12 +173,12 @@ namespace omath float Vector3::Sum() const { - return x + y + z; + return Vector3::Sum2D() + z; } float Vector3::Sum2D() const { - return x + y; + return Vector2::Sum(); } Vector3 Vector3::ViewAngleTo(const Vector3 &other) const @@ -265,4 +249,9 @@ namespace omath return length != 0 ? *this / length : *this; } + + std::tuple Vector3::AsTuple() const + { + return std::make_tuple(x, y, z); + } } \ No newline at end of file diff --git a/tests/UnitTestPrediction.cpp b/tests/UnitTestPrediction.cpp index d3ce5c2d..c57d79be 100644 --- a/tests/UnitTestPrediction.cpp +++ b/tests/UnitTestPrediction.cpp @@ -7,7 +7,7 @@ TEST(UnitTestPrediction, PredictionTest) const omath::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 5000, .m_gravityScale= 0.4}; const auto viewPoint = omath::prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); - const auto [pitch, yaw, _] = proj.m_origin.ViewAngleTo(viewPoint.value()); + const auto [pitch, yaw, _] = proj.m_origin.ViewAngleTo(viewPoint.value()).AsTuple(); EXPECT_NEAR(42.547142, pitch, 0.0001f); EXPECT_NEAR(-1.181189, yaw, 0.0001f); From d46af3e5197eb19a3b6923cce52c37cc0f4a21f7 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 2 Sep 2024 16:24:06 +0300 Subject: [PATCH 053/795] added unit tests --- tests/CMakeLists.txt | 1 + tests/UnitTestVector2.cpp | 170 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 tests/UnitTestVector2.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f2f2c9f5..65c45d29 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable(unit-tests UnitTestAstar.cpp UnitTestProjection.cpp UnitTestVector3.cpp + UnitTestVector2.cpp UnitTestColor.cpp) target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) diff --git a/tests/UnitTestVector2.cpp b/tests/UnitTestVector2.cpp new file mode 100644 index 00000000..b75cc3d6 --- /dev/null +++ b/tests/UnitTestVector2.cpp @@ -0,0 +1,170 @@ +// +// Created by Vlad on 02.09.2024. +// +// +// Created by Vlad on 01.09.2024. +// +#include +#include + +using namespace omath; + +class UnitTestVector2 : public ::testing::Test +{ +protected: + Vector2 v1; + Vector2 v2; + + void SetUp() override + { + v1 = Vector2(1.0f, 2.0f); + v2 = Vector2(4.0f, 5.0f); + } +}; + +// Test constructor and default values +TEST_F(UnitTestVector2, Constructor_Default) +{ + Vector2 v; + EXPECT_FLOAT_EQ(v.x, 0.0f); + EXPECT_FLOAT_EQ(v.y, 0.0f); +} + +TEST_F(UnitTestVector2, Constructor_Values) +{ + Vector2 v(1.0f, 2.0f); + EXPECT_FLOAT_EQ(v.x, 1.0f); + EXPECT_FLOAT_EQ(v.y, 2.0f); +} + +// Test equality operators +TEST_F(UnitTestVector2, EqualityOperator) +{ + Vector2 v3(1.0f, 2.0f); + EXPECT_TRUE(v1 == v3); + EXPECT_FALSE(v1 == v2); +} + +TEST_F(UnitTestVector2, InequalityOperator) +{ + Vector2 v3(1.0f, 2.0f); + EXPECT_FALSE(v1 != v3); + EXPECT_TRUE(v1 != v2); +} + +// Test arithmetic operators +TEST_F(UnitTestVector2, AdditionOperator) +{ + Vector2 v3 = v1 + v2; + EXPECT_FLOAT_EQ(v3.x, 5.0f); + EXPECT_FLOAT_EQ(v3.y, 7.0f); +} + +TEST_F(UnitTestVector2, SubtractionOperator) +{ + Vector2 v3 = v2 - v1; + EXPECT_FLOAT_EQ(v3.x, 3.0f); + EXPECT_FLOAT_EQ(v3.y, 3.0f); +} + +TEST_F(UnitTestVector2, MultiplicationOperator) +{ + Vector2 v3 = v1 * 2.0f; + EXPECT_FLOAT_EQ(v3.x, 2.0f); + EXPECT_FLOAT_EQ(v3.y, 4.0f); +} + +TEST_F(UnitTestVector2, DivisionOperator) +{ + Vector2 v3 = v2 / 2.0f; + EXPECT_FLOAT_EQ(v3.x, 2.0f); + EXPECT_FLOAT_EQ(v3.y, 2.5f); +} + +// Test compound assignment operators +TEST_F(UnitTestVector2, AdditionAssignmentOperator) +{ + v1 += v2; + EXPECT_FLOAT_EQ(v1.x, 5.0f); + EXPECT_FLOAT_EQ(v1.y, 7.0f); +} + +TEST_F(UnitTestVector2, SubtractionAssignmentOperator) +{ + v1 -= v2; + EXPECT_FLOAT_EQ(v1.x, -3.0f); + EXPECT_FLOAT_EQ(v1.y, -3.0f); +} + +TEST_F(UnitTestVector2, MultiplicationAssignmentOperator) +{ + v1 *= 2.0f; + EXPECT_FLOAT_EQ(v1.x, 2.0f); + EXPECT_FLOAT_EQ(v1.y, 4.0f); +} + +TEST_F(UnitTestVector2, DivisionAssignmentOperator) +{ + v1 /= 2.0f; + EXPECT_FLOAT_EQ(v1.x, 0.5f); + EXPECT_FLOAT_EQ(v1.y, 1.0f); +} + +TEST_F(UnitTestVector2, NegationOperator) +{ + Vector2 v3 = -v1; + EXPECT_FLOAT_EQ(v3.x, -1.0f); + EXPECT_FLOAT_EQ(v3.y, -2.0f); +} + +// Test other member functions +TEST_F(UnitTestVector2, DistTo) +{ + float dist = v1.DistTo(v2); + EXPECT_FLOAT_EQ(dist, sqrt(18.0f)); +} + +TEST_F(UnitTestVector2, DistToSqr) +{ + float distSqr = v1.DistToSqr(v2); + EXPECT_FLOAT_EQ(distSqr, 18.0f); +} + +TEST_F(UnitTestVector2, DotProduct) +{ + float dot = v1.Dot(v2); + EXPECT_FLOAT_EQ(dot, 14.0f); +} + +TEST_F(UnitTestVector2, Length) +{ + float length = v1.Length(); + EXPECT_FLOAT_EQ(length, sqrt(5.0f)); +} + +TEST_F(UnitTestVector2, LengthSqr) +{ + float lengthSqr = v1.LengthSqr(); + EXPECT_FLOAT_EQ(lengthSqr, 5.0f); +} + +TEST_F(UnitTestVector2, Abs) +{ + Vector2 v3(-1.0f, -2.0f); + v3.Abs(); + EXPECT_FLOAT_EQ(v3.x, 1.0f); + EXPECT_FLOAT_EQ(v3.y, 2.0f); +} + +TEST_F(UnitTestVector2, Sum) +{ + float sum = v1.Sum(); + EXPECT_FLOAT_EQ(sum, 3.0f); +} + +TEST_F(UnitTestVector2, Normalized) +{ + Vector2 v3 = v1.Normalized(); + EXPECT_NEAR(v3.x, 0.44721f, 0.0001f); + EXPECT_NEAR(v3.y, 0.89443f, 0.0001f); +} From c0d505b5e278542606e52b5dfd579860f2e1f2d9 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 2 Sep 2024 18:14:01 +0300 Subject: [PATCH 054/795] vec2 was made constexpr --- include/omath/Vector2.h | 151 +++++++++++++++++++++++++++++++------- source/Vector2.cpp | 148 ++----------------------------------- tests/UnitTestVector2.cpp | 45 ++++++------ 3 files changed, 154 insertions(+), 190 deletions(-) diff --git a/include/omath/Vector2.h b/include/omath/Vector2.h index a19e3b44..39e21973 100644 --- a/include/omath/Vector2.h +++ b/include/omath/Vector2.h @@ -4,6 +4,7 @@ #pragma once #include +#include namespace omath @@ -15,60 +16,158 @@ namespace omath float y = 0.f; // Constructors - Vector2() = default; - Vector2(float x, float y); + constexpr Vector2() : x(0.f), y(0.f) {} + + constexpr Vector2(float x, float y) : x(x), y(y) {} // Equality operators - bool operator==(const Vector2& src) const; - bool operator!=(const Vector2& src) const; + constexpr bool operator==(const Vector2& src) const + { + return x == src.x && y == src.y; + } + + constexpr bool operator!=(const Vector2& src) const + { + return !(*this == src); + } // Compound assignment operators - Vector2& operator+=(const Vector2& v); - Vector2& operator-=(const Vector2& v); - Vector2& operator*=(const Vector2& v); - Vector2& operator/=(const Vector2& v); + Vector2& operator+=(const Vector2& v) + { + x += v.x; + y += v.y; + return *this; + } + + Vector2& operator-=(const Vector2& v) + { + x -= v.x; + y -= v.y; + return *this; + } + + Vector2& operator*=(const Vector2& v) + { + x *= v.x; + y *= v.y; + + return *this; + } - Vector2& operator*=(float fl); - Vector2& operator/=(float fl); - Vector2& operator+=(float fl); - Vector2& operator-=(float fl); + Vector2& operator/=(const Vector2& v) + { + x /= v.x; + y /= v.y; + + return *this; + } + + Vector2& operator*=(float fl) + { + x *= fl; + y *= fl; + return *this; + } + + Vector2& operator/=(float fl) + { + x /= fl; + y /= fl; + return *this; + } + Vector2& operator+=(float fl) + { + x += fl; + y += fl; + return *this; + } + + Vector2& operator-=(float fl) + { + x -= fl; + y -= fl; + return *this; + } // Basic vector operations - [[nodiscard]] float DistTo(const Vector2& vOther) const; - [[nodiscard]] float DistToSqr(const Vector2& vOther) const; - [[nodiscard]] float Dot(const Vector2& vOther) const; + [[nodiscard]] float DistTo(const Vector2& vOther) const + { + return std::sqrt(DistToSqr(vOther)); + } + + [[nodiscard]] constexpr float DistToSqr(const Vector2& vOther) const + { + return (x - vOther.x) * (x - vOther.x) + (y - vOther.y) * (y - vOther.y); + } + + [[nodiscard]] constexpr float Dot(const Vector2& vOther) const + { + return x * vOther.x + y * vOther.y; + } + [[nodiscard]] float Length() const; - [[nodiscard]] float LengthSqr() const; - Vector2& Abs(); + + [[nodiscard]] constexpr float LengthSqr() const + { + return x * x + y * y; + } + constexpr Vector2& Abs() + { + x = x < 0 ? -x : x; + y = y < 0 ? -y : y; + return *this; + } template - const type& As() const + constexpr const type& As() const { return *reinterpret_cast(this); } template - type& As() + constexpr type& As() { return *reinterpret_cast(this); } // Unary negation operator - Vector2 operator-() const; + constexpr Vector2 operator-() const + { + return {-x, -y}; + } // Binary arithmetic operators - Vector2 operator+(const Vector2& v) const; - Vector2 operator-(const Vector2& v) const; - Vector2 operator*(float fl) const; - Vector2 operator/(float fl) const; + constexpr Vector2 operator+(const Vector2& v) const + { + return {x + v.x, y + v.y}; + } + + constexpr Vector2 operator-(const Vector2& v) const + { + return {x - v.x, y - v.y}; + } + constexpr Vector2 operator*(float fl) const + { + return {x * fl, y * fl}; + } + constexpr Vector2 operator/(float fl) const + { + return {x / fl, y / fl}; + } // Normalize the vector [[nodiscard]] Vector2 Normalized() const; // Sum of elements - [[nodiscard]] float Sum() const; + [[nodiscard]] constexpr float Sum() const + { + return x + y; + } [[nodiscard]] - std::tuple AsTuple() const; + constexpr std::tuple AsTuple() const + { + return std::make_tuple(x, y); + } }; } \ No newline at end of file diff --git a/source/Vector2.cpp b/source/Vector2.cpp index bdba7af6..66dc4f09 100644 --- a/source/Vector2.cpp +++ b/source/Vector2.cpp @@ -7,157 +7,19 @@ namespace omath { - // Constructors - Vector2::Vector2(const float x, const float y) : x(x), y(y) {} - // Equality operators - bool Vector2::operator==(const Vector2& src) const - { - return x == src.x && y == src.y; - } - - bool Vector2::operator!=(const Vector2& src) const - { - return !(*this == src); - } - - // Compound assignment operators - Vector2& Vector2::operator+=(const Vector2& v) - { - x += v.x; - y += v.y; - return *this; - } - - Vector2& Vector2::operator-=(const Vector2& v) - { - x -= v.x; - y -= v.y; - return *this; - } - - Vector2& Vector2::operator*=(float fl) - { - x *= fl; - y *= fl; - return *this; - } - - Vector2& Vector2::operator/=(float fl) - { - x /= fl; - y /= fl; - return *this; - } - - Vector2& Vector2::operator+=(float fl) - { - x += fl; - y += fl; - return *this; - } - - Vector2& Vector2::operator-=(float fl) - { - x -= fl; - y -= fl; - return *this; - } - - // Basic vector operations - float Vector2::DistTo(const Vector2& vOther) const + Vector2 Vector2::Normalized() const { - return std::sqrt(DistToSqr(vOther)); - } + const float len = Length(); - float Vector2::DistToSqr(const Vector2& vOther) const - { - return (x - vOther.x) * (x - vOther.x) + (y - vOther.y) * (y - vOther.y); - } + if (len > 0.f) + return {x / len, y / len}; - float Vector2::Dot(const Vector2& vOther) const - { - return x * vOther.x + y * vOther.y; + return {0.f, 0.f}; } float Vector2::Length() const { return std::sqrt(x * x + y * y); } - - float Vector2::LengthSqr() const - { - return x * x + y * y; - } - - Vector2& Vector2::Abs() - { - x = std::abs(x); - y = std::abs(y); - return *this; - } - - // Unary negation operator - Vector2 Vector2::operator-() const - { - return {-x, -y}; - } - - // Binary arithmetic operators - Vector2 Vector2::operator+(const Vector2& v) const - { - return {x + v.x, y + v.y}; - } - - Vector2 Vector2::operator-(const Vector2& v) const - { - return {x - v.x, y - v.y}; - } - - Vector2 Vector2::operator*(float fl) const - { - return {x * fl, y * fl}; - } - - Vector2 Vector2::operator/(float fl) const - { - return {x / fl, y / fl}; - } - - // Normalize the vector - Vector2 Vector2::Normalized() const - { - float len = Length(); - if (len > 0.f) { - return {x / len, y / len}; - } - return {0.f, 0.f}; - } - - // Sum of elements - float Vector2::Sum() const - { - return x + y; - } - - Vector2 &Vector2::operator*=(const Vector2 &v) - { - x *= v.x; - y *= v.y; - - return *this; - } - - Vector2 &Vector2::operator/=(const Vector2 &v) - { - x /= v.x; - y /= v.y; - - return *this; - } - - std::tuple Vector2::AsTuple() const - { - return std::make_tuple(x, y); - } } \ No newline at end of file diff --git a/tests/UnitTestVector2.cpp b/tests/UnitTestVector2.cpp index b75cc3d6..c1553beb 100644 --- a/tests/UnitTestVector2.cpp +++ b/tests/UnitTestVector2.cpp @@ -25,14 +25,14 @@ class UnitTestVector2 : public ::testing::Test // Test constructor and default values TEST_F(UnitTestVector2, Constructor_Default) { - Vector2 v; + constexpr Vector2 v; EXPECT_FLOAT_EQ(v.x, 0.0f); EXPECT_FLOAT_EQ(v.y, 0.0f); } TEST_F(UnitTestVector2, Constructor_Values) { - Vector2 v(1.0f, 2.0f); + constexpr Vector2 v(1.0f, 2.0f); EXPECT_FLOAT_EQ(v.x, 1.0f); EXPECT_FLOAT_EQ(v.y, 2.0f); } @@ -40,14 +40,14 @@ TEST_F(UnitTestVector2, Constructor_Values) // Test equality operators TEST_F(UnitTestVector2, EqualityOperator) { - Vector2 v3(1.0f, 2.0f); + constexpr Vector2 v3(1.0f, 2.0f); EXPECT_TRUE(v1 == v3); EXPECT_FALSE(v1 == v2); } TEST_F(UnitTestVector2, InequalityOperator) { - Vector2 v3(1.0f, 2.0f); + constexpr Vector2 v3(1.0f, 2.0f); EXPECT_FALSE(v1 != v3); EXPECT_TRUE(v1 != v2); } @@ -55,28 +55,28 @@ TEST_F(UnitTestVector2, InequalityOperator) // Test arithmetic operators TEST_F(UnitTestVector2, AdditionOperator) { - Vector2 v3 = v1 + v2; + constexpr Vector2 v3 = Vector2(1.0f, 2.0f) + Vector2(4.0f, 5.0f); EXPECT_FLOAT_EQ(v3.x, 5.0f); EXPECT_FLOAT_EQ(v3.y, 7.0f); } TEST_F(UnitTestVector2, SubtractionOperator) { - Vector2 v3 = v2 - v1; + constexpr Vector2 v3 = Vector2(4.0f, 5.0f) - Vector2(1.0f, 2.0f); EXPECT_FLOAT_EQ(v3.x, 3.0f); EXPECT_FLOAT_EQ(v3.y, 3.0f); } TEST_F(UnitTestVector2, MultiplicationOperator) { - Vector2 v3 = v1 * 2.0f; + constexpr Vector2 v3 = Vector2(1.0f, 2.0f) * 2.0f; EXPECT_FLOAT_EQ(v3.x, 2.0f); EXPECT_FLOAT_EQ(v3.y, 4.0f); } TEST_F(UnitTestVector2, DivisionOperator) { - Vector2 v3 = v2 / 2.0f; + constexpr Vector2 v3 = Vector2(4.0f, 5.0f) / 2.0f; EXPECT_FLOAT_EQ(v3.x, 2.0f); EXPECT_FLOAT_EQ(v3.y, 2.5f); } @@ -112,59 +112,62 @@ TEST_F(UnitTestVector2, DivisionAssignmentOperator) TEST_F(UnitTestVector2, NegationOperator) { - Vector2 v3 = -v1; + constexpr Vector2 v3 = -Vector2(1.0f, 2.0f); EXPECT_FLOAT_EQ(v3.x, -1.0f); EXPECT_FLOAT_EQ(v3.y, -2.0f); } - // Test other member functions TEST_F(UnitTestVector2, DistTo) { - float dist = v1.DistTo(v2); - EXPECT_FLOAT_EQ(dist, sqrt(18.0f)); + const float dist = v1.DistTo(v2); + EXPECT_FLOAT_EQ(dist, std::sqrt(18.0f)); } TEST_F(UnitTestVector2, DistToSqr) { - float distSqr = v1.DistToSqr(v2); + constexpr float distSqr = Vector2(1.0f, 2.0f).DistToSqr(Vector2(4.0f, 5.0f)); EXPECT_FLOAT_EQ(distSqr, 18.0f); } TEST_F(UnitTestVector2, DotProduct) { - float dot = v1.Dot(v2); + constexpr float dot = Vector2(1.0f, 2.0f).Dot(Vector2(4.0f, 5.0f)); EXPECT_FLOAT_EQ(dot, 14.0f); } TEST_F(UnitTestVector2, Length) { - float length = v1.Length(); - EXPECT_FLOAT_EQ(length, sqrt(5.0f)); + const float length = v1.Length(); + EXPECT_FLOAT_EQ(length, std::sqrt(5.0f)); } TEST_F(UnitTestVector2, LengthSqr) { - float lengthSqr = v1.LengthSqr(); + constexpr float lengthSqr = Vector2(1.0f, 2.0f).LengthSqr(); EXPECT_FLOAT_EQ(lengthSqr, 5.0f); } TEST_F(UnitTestVector2, Abs) { - Vector2 v3(-1.0f, -2.0f); - v3.Abs(); + constexpr Vector2 v3 = Vector2(-1.0f, -2.0f).Abs(); EXPECT_FLOAT_EQ(v3.x, 1.0f); EXPECT_FLOAT_EQ(v3.y, 2.0f); } TEST_F(UnitTestVector2, Sum) { - float sum = v1.Sum(); + constexpr float sum = Vector2(1.0f, 2.0f).Sum(); EXPECT_FLOAT_EQ(sum, 3.0f); } TEST_F(UnitTestVector2, Normalized) { - Vector2 v3 = v1.Normalized(); + const Vector2 v3 = v1.Normalized(); EXPECT_NEAR(v3.x, 0.44721f, 0.0001f); EXPECT_NEAR(v3.y, 0.89443f, 0.0001f); } + +static_assert(Vector2(1.0f, 2.0f).LengthSqr() == 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).DistToSqr(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"); From 9e32b043d0acaf771b6aa55b8c0063d36da8878d Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 01:54:23 +0300 Subject: [PATCH 055/795] maded vec3 constexprable, addded lvl waring option --- CMakeLists.txt | 8 +- include/omath/Vector2.h | 46 ++++--- include/omath/Vector3.h | 191 +++++++++++++++++++++----- include/omath/prediction/Projectile.h | 3 - source/Vector3.cpp | 170 ----------------------- source/prediction/Projectile.cpp | 7 +- 6 files changed, 196 insertions(+), 229 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b2cab68a..6a2ae732 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ project(omath) set(CMAKE_CXX_STANDARD 26) option(BUILD_TESTS "Build unit tests" ON) - +option(THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to threat them as errors" ON) add_library(omath STATIC source/Vector3.cpp) add_subdirectory(source) @@ -16,4 +16,10 @@ if(BUILD_TESTS) add_subdirectory(tests) endif () +if (WIN32 AND THREAT_WARNING_AS_ERROR) + target_compile_definitions(omath PRIVATE /W4 /WX) +elseif(UNIX AND THREAT_WARNING_AS_ERROR) + target_compile_definitions(omath PRIVATE -Wall -Wextra -Wpedantic) +endif() + target_include_directories(omath PUBLIC include) \ No newline at end of file diff --git a/include/omath/Vector2.h b/include/omath/Vector2.h index 39e21973..1cfdd7c6 100644 --- a/include/omath/Vector2.h +++ b/include/omath/Vector2.h @@ -21,32 +21,36 @@ namespace omath constexpr Vector2(float x, float y) : x(x), y(y) {} // Equality operators + [[nodiscard]] constexpr bool operator==(const Vector2& src) const { return x == src.x && y == src.y; } + [[nodiscard]] constexpr bool operator!=(const Vector2& src) const { return !(*this == src); } // Compound assignment operators - Vector2& operator+=(const Vector2& v) + constexpr Vector2& operator+=(const Vector2& v) { x += v.x; y += v.y; + return *this; } - Vector2& operator-=(const Vector2& v) + constexpr Vector2& operator-=(const Vector2& v) { x -= v.x; y -= v.y; + return *this; } - Vector2& operator*=(const Vector2& v) + constexpr Vector2& operator*=(const Vector2& v) { x *= v.x; y *= v.y; @@ -54,7 +58,7 @@ namespace omath return *this; } - Vector2& operator/=(const Vector2& v) + constexpr Vector2& operator/=(const Vector2& v) { x /= v.x; y /= v.y; @@ -62,30 +66,35 @@ namespace omath return *this; } - Vector2& operator*=(float fl) + constexpr Vector2& operator*=(float fl) { x *= fl; y *= fl; + return *this; } - Vector2& operator/=(float fl) + constexpr Vector2& operator/=(float fl) { x /= fl; y /= fl; + return *this; } - Vector2& operator+=(float fl) + + constexpr Vector2& operator+=(float fl) { x += fl; y += fl; + return *this; } - Vector2& operator-=(float fl) + constexpr Vector2& operator-=(float fl) { x -= fl; y -= fl; + return *this; } @@ -111,45 +120,48 @@ namespace omath { return x * x + y * y; } + constexpr Vector2& Abs() { + //FIXME: Replace with std::abs, if it will become constexprable x = x < 0 ? -x : x; y = y < 0 ? -y : y; return *this; } template - constexpr const type& As() const + [[nodiscard]] constexpr const type& As() const { return *reinterpret_cast(this); } template - constexpr type& As() + [[nodiscard]] constexpr type& As() { return *reinterpret_cast(this); } - // Unary negation operator - constexpr Vector2 operator-() const + [[nodiscard]] constexpr Vector2 operator-() const { return {-x, -y}; } // Binary arithmetic operators - constexpr Vector2 operator+(const Vector2& v) const + [[nodiscard]] constexpr Vector2 operator+(const Vector2& v) const { return {x + v.x, y + v.y}; } - constexpr Vector2 operator-(const Vector2& v) const + [[nodiscard]] constexpr Vector2 operator-(const Vector2& v) const { return {x - v.x, y - v.y}; } - constexpr Vector2 operator*(float fl) const + + [[nodiscard]] constexpr Vector2 operator*(float fl) const { return {x * fl, y * fl}; } - constexpr Vector2 operator/(float fl) const + + [[nodiscard]] constexpr Vector2 operator/(float fl) const { return {x / fl, y / fl}; } @@ -158,7 +170,7 @@ namespace omath // Normalize the vector [[nodiscard]] Vector2 Normalized() const; - // Sum of elements + // Sum of elements [[nodiscard]] constexpr float Sum() const { return x + y; diff --git a/include/omath/Vector3.h b/include/omath/Vector3.h index b3cc2bf0..0ec441b5 100644 --- a/include/omath/Vector3.h +++ b/include/omath/Vector3.h @@ -15,42 +15,166 @@ namespace omath { public: float z = 0.f; - Vector3(float x, float y, float z); - Vector3() = default; - - bool operator==(const Vector3& src) const; - bool operator!=(const Vector3& src) const; - - Vector3& operator+=(const Vector3& v); - Vector3& operator-=(const Vector3& v); - Vector3& operator*=(float fl); - Vector3& operator*=(const Vector3& v); - Vector3& operator/=(const Vector3& v); - Vector3& operator+=(float fl); - Vector3& operator/=(float fl); - Vector3& operator-=(float fl); - - [[nodiscard]] float DistTo(const Vector3& vOther) const; - Vector3& Abs(); - [[nodiscard]] float DistToSqr(const Vector3& vOther) const; - [[nodiscard]] float Dot(const Vector3& vOther) const; + constexpr Vector3(float x, float y, float z) : Vector2(x, y), z(z) { } + constexpr Vector3() : Vector2(), z(0.f) {}; + + [[nodiscard]] constexpr bool operator==(const Vector3& src) const + { + return Vector2::operator==(src) && (src.z == z); + } + + [[nodiscard]] constexpr bool operator!=(const Vector3& src) const + { + return !(*this == src); + } + + constexpr Vector3& operator+=(const Vector3& v) + { + Vector2::operator+=(v); + z += v.z; + + return *this; + } + + constexpr Vector3& operator-=(const Vector3& v) + { + Vector2::operator-=(v); + z -= v.z; + + return *this; + } + + constexpr Vector3& operator*=(float fl) + { + Vector2::operator*=(fl); + z *= fl; + + return *this; + } + + constexpr Vector3& operator*=(const Vector3& v) + { + Vector2::operator*=(v); + z *= v.z; + + return *this; + } + + constexpr Vector3& operator/=(const Vector3& v) + { + Vector2::operator/=(v); + z /= v.z; + + return *this; + } + + constexpr Vector3& operator+=(float fl) + { + Vector2::operator+=(fl); + z += fl; + + return *this; + } + + constexpr Vector3& operator/=(float fl) + { + Vector2::operator/=(fl); + z /= fl; + + return *this; + } + + constexpr Vector3& operator-=(float fl) + { + Vector2::operator-=(fl); + z -= fl; + + return *this; + } + + [[nodiscard]] + float DistTo(const Vector3& vOther) const; + + constexpr Vector3& Abs() + { + Vector2::Abs(); + z = z < 0.f ? -z : z; + + return *this; + } + + [[nodiscard]] constexpr float DistToSqr(const Vector3& vOther) const + { + return (*this - vOther).LengthSqr(); + } + + [[nodiscard]] constexpr float Dot(const Vector3& vOther) const + { + return Vector2::Dot(vOther) + z * vOther.z; + } [[nodiscard]] float Length() const; - [[nodiscard]] float LengthSqr() const; + + [[nodiscard]] constexpr float LengthSqr() const + { + return Vector2::LengthSqr() + z * z; + } + [[nodiscard]] float Length2D() const; - Vector3 operator-() const; - Vector3 operator+(const Vector3& v) const; - Vector3 operator-(const Vector3& v) const; - Vector3 operator*(float fl) const; - Vector3 operator*(const Vector3& v) const; - Vector3 operator/(float fl) const; - Vector3 operator/(const Vector3& v) const; + [[nodiscard]] constexpr Vector3 operator-() const + { + return {-x, -y, -z}; + } + + [[nodiscard]] constexpr Vector3 operator+(const Vector3& v) const + { + return {x + v.x, y + v.y, z + v.z}; + } + [[nodiscard]] constexpr Vector3 operator-(const Vector3& v) const + { + return {x - v.x, y - v.y, z - v.z}; + } + + [[nodiscard]] constexpr Vector3 operator*(float fl) const + { + return {x * fl, y * fl, z * fl}; + } + + [[nodiscard]] constexpr Vector3 operator*(const Vector3& v) const + { + return {x * v.x, y * v.y, z * v.z}; + } + + [[nodiscard]] constexpr Vector3 operator/(float fl) const + { + return {x / fl, y / fl, z / fl}; + } + + [[nodiscard]] constexpr Vector3 operator/(const Vector3& v) const + { + return {x / v.x, y / v.y, z / v.z}; + } + + [[nodiscard]] constexpr Vector3 Cross(const Vector3 &v) const + { + return + { + y * v.z - z * v.y, + z * v.x - x * v.z, + x * v.y - y * v.x + }; + } + [[nodiscard]] constexpr float Sum() const + { + return Vector3::Sum2D() + z; + } + + [[nodiscard]] constexpr float Sum2D() const + { + return Vector2::Sum(); + } - [[nodiscard]] Vector3 Cross(const Vector3 &v) const; - [[nodiscard]] static Vector3 CreateVelocity(float pitch, float yaw, float speed); - [[nodiscard]] float Sum() const; - [[nodiscard]] float Sum2D() const; [[nodiscard]] Vector3 ViewAngleTo(const Vector3& other) const; [[nodiscard]] static Vector3 ForwardVector(float pitch, float yaw); @@ -61,7 +185,10 @@ namespace omath [[nodiscard]] Vector3 Normalized() const; - [[nodiscard]] std::tuple AsTuple() const; + [[nodiscard]] std::tuple AsTuple() const + { + return std::make_tuple(x, y, z); + } }; } // ReSharper disable once CppRedundantNamespaceDefinition diff --git a/include/omath/prediction/Projectile.h b/include/omath/prediction/Projectile.h index 48038f81..1249e977 100644 --- a/include/omath/prediction/Projectile.h +++ b/include/omath/prediction/Projectile.h @@ -12,9 +12,6 @@ namespace omath::prediction { public: - [[nodiscard]] - Vector3 CalculateVelocity(float pitch, float yaw) const; - [[nodiscard]] Vector3 PredictPosition(float pitch, float yaw, float time, float gravity) const; diff --git a/source/Vector3.cpp b/source/Vector3.cpp index fcbd061b..0dd9be07 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -9,178 +9,22 @@ namespace omath { - Vector3::Vector3(const float x, const float y, const float z) : Vector2(x, y), z(z) - { - - } - - bool Vector3::operator==(const Vector3 &src) const - { - return Vector2::operator==(src) && (src.z == z); - } - - bool Vector3::operator!=(const Vector3 &src) const - { - return !(*this == src); - } - - Vector3 &Vector3::operator+=(const Vector3 &v) - { - Vector2::operator+=(v); - z += v.z; - - return *this; - } - - Vector3 &Vector3::operator-=(const Vector3 &v) - { - Vector2::operator-=(v); - z -= v.z; - - return *this; - } - - Vector3 &Vector3::operator*=(const float fl) - { - Vector2::operator*=(fl); - z *= fl; - - return *this; - } - - Vector3 &Vector3::operator*=(const Vector3 &v) - { - Vector2::operator*=(v); - z *= v.z; - - return *this; - } - - Vector3 &Vector3::operator/=(const Vector3 &v) - { - Vector2::operator/=(v); - z /= v.z; - - return *this; - } - - Vector3 &Vector3::operator+=(const float fl) - { - Vector2::operator+=(fl); - z += fl; - - return *this; - } - - Vector3 &Vector3::operator/=(const float fl) - { - Vector2::operator/=(fl); - z /= fl; - - return *this; - } - - Vector3 &Vector3::operator-=(const float fl) - { - Vector2::operator-=(fl); - z -= fl; - - return *this; - } - float Vector3::DistTo(const Vector3 &vOther) const { return (*this - vOther).Length(); } - Vector3 &Vector3::Abs() - { - Vector2::Abs(); - z = std::abs(z); - - return *this; - } - - float Vector3::DistToSqr(const Vector3 &vOther) const - { - return (*this - vOther).LengthSqr(); - } - - float Vector3::Dot(const Vector3 &vOther) const - { - return Vector2::Dot(vOther) + z * vOther.z; - } - float Vector3::Length() const { return std::sqrt(Vector2::LengthSqr() + z * z); } - float Vector3::LengthSqr() const - { - return Vector2::LengthSqr() + z * z; - } float Vector3::Length2D() const { return Vector2::Length(); } - Vector3 Vector3::operator-() const - { - return {-x, -y, -z}; - } - - Vector3 Vector3::operator+(const Vector3 &v) const - { - return {x + v.x, y + v.y, z + v.z}; - } - - Vector3 Vector3::operator-(const Vector3 &v) const - { - return {x - v.x, y - v.y, z - v.z}; - } - - Vector3 Vector3::operator*(float fl) const - { - return {x * fl, y * fl, z * fl}; - } - - Vector3 Vector3::operator*(const Vector3 &v) const - { - return {x * v.x, y * v.y, z * v.z}; - } - - Vector3 Vector3::operator/(const float fl) const - { - return {x / fl, y / fl, z / fl}; - } - - Vector3 Vector3::operator/(const Vector3 &v) const - { - return {x / v.x, y / v.y, z / v.z}; - } - - Vector3 Vector3::CreateVelocity(const float pitch, const float yaw, const float speed) - { - return - { - std::cos(angles::DegreesToRadians(pitch)) * std::cos(angles::DegreesToRadians(yaw)) * speed, - std::cos(angles::DegreesToRadians(pitch)) * std::sin(angles::DegreesToRadians(yaw)) * speed, - std::sin(angles::DegreesToRadians(pitch)) * speed, - }; - } - - float Vector3::Sum() const - { - return Vector3::Sum2D() + z; - } - - float Vector3::Sum2D() const - { - return Vector2::Sum(); - } - Vector3 Vector3::ViewAngleTo(const Vector3 &other) const { const float distance = DistTo(other); @@ -233,15 +77,6 @@ namespace omath return RightVector(pitch, yaw, roll).Cross(ForwardVector(pitch, yaw)); } - Vector3 Vector3::Cross(const Vector3 &v) const - { - return - { - y * v.z - z * v.y, - z * v.x - x * v.z, - x * v.y - y * v.x - }; - } Vector3 Vector3::Normalized() const { @@ -249,9 +84,4 @@ namespace omath return length != 0 ? *this / length : *this; } - - std::tuple Vector3::AsTuple() const - { - return std::make_tuple(x, y, z); - } } \ No newline at end of file diff --git a/source/prediction/Projectile.cpp b/source/prediction/Projectile.cpp index 3ce51643..32cbe68c 100644 --- a/source/prediction/Projectile.cpp +++ b/source/prediction/Projectile.cpp @@ -8,14 +8,9 @@ namespace omath::prediction { - Vector3 Projectile::CalculateVelocity(const float pitch, const float yaw) const - { - return Vector3::CreateVelocity(pitch, yaw, m_launchSpeed); - } - Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const { - auto currentPos = m_origin + Vector3::CreateVelocity(pitch, yaw, m_launchSpeed) * time; + auto currentPos = m_origin + Vector3::ForwardVector(pitch, yaw) * m_launchSpeed * time; currentPos.z -= (gravity * m_gravityScale) * std::pow(time, 2.f) * 0.5f; return currentPos; From 5edd25a00b32668792288f1e17374d10ed4bc440 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 01:57:26 +0300 Subject: [PATCH 056/795] changed unit tests --- tests/UnitTestVector3.cpp | 107 ++++++++++---------------------------- 1 file changed, 28 insertions(+), 79 deletions(-) diff --git a/tests/UnitTestVector3.cpp b/tests/UnitTestVector3.cpp index e55b786a..37f9fff5 100644 --- a/tests/UnitTestVector3.cpp +++ b/tests/UnitTestVector3.cpp @@ -3,6 +3,7 @@ // #include #include +#include using namespace omath; @@ -22,7 +23,7 @@ class UnitTestVector3 : public ::testing::Test // Test constructor and default values TEST_F(UnitTestVector3, Constructor_Default) { - Vector3 v; + constexpr Vector3 v; EXPECT_FLOAT_EQ(v.x, 0.0f); EXPECT_FLOAT_EQ(v.y, 0.0f); EXPECT_FLOAT_EQ(v.z, 0.0f); @@ -30,7 +31,7 @@ TEST_F(UnitTestVector3, Constructor_Default) TEST_F(UnitTestVector3, Constructor_Values) { - Vector3 v(1.0f, 2.0f, 3.0f); + constexpr Vector3 v(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v.x, 1.0f); EXPECT_FLOAT_EQ(v.y, 2.0f); EXPECT_FLOAT_EQ(v.z, 3.0f); @@ -39,14 +40,14 @@ TEST_F(UnitTestVector3, Constructor_Values) // Test equality operators TEST_F(UnitTestVector3, EqualityOperator) { - Vector3 v3(1.0f, 2.0f, 3.0f); + constexpr Vector3 v3(1.0f, 2.0f, 3.0f); EXPECT_TRUE(v1 == v3); EXPECT_FALSE(v1 == v2); } TEST_F(UnitTestVector3, InequalityOperator) { - Vector3 v3(1.0f, 2.0f, 3.0f); + constexpr Vector3 v3(1.0f, 2.0f, 3.0f); EXPECT_FALSE(v1 != v3); EXPECT_TRUE(v1 != v2); } @@ -54,7 +55,7 @@ TEST_F(UnitTestVector3, InequalityOperator) // Test arithmetic operators TEST_F(UnitTestVector3, AdditionOperator) { - Vector3 v3 = v1 + v2; + constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) + Vector3(4.0f, 5.0f, 6.0f); EXPECT_FLOAT_EQ(v3.x, 5.0f); EXPECT_FLOAT_EQ(v3.y, 7.0f); EXPECT_FLOAT_EQ(v3.z, 9.0f); @@ -62,7 +63,7 @@ TEST_F(UnitTestVector3, AdditionOperator) TEST_F(UnitTestVector3, SubtractionOperator) { - Vector3 v3 = v2 - v1; + constexpr Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) - Vector3(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v3.x, 3.0f); EXPECT_FLOAT_EQ(v3.y, 3.0f); EXPECT_FLOAT_EQ(v3.z, 3.0f); @@ -70,7 +71,7 @@ TEST_F(UnitTestVector3, SubtractionOperator) TEST_F(UnitTestVector3, MultiplicationOperator) { - Vector3 v3 = v1 * 2.0f; + constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) * 2.0f; EXPECT_FLOAT_EQ(v3.x, 2.0f); EXPECT_FLOAT_EQ(v3.y, 4.0f); EXPECT_FLOAT_EQ(v3.z, 6.0f); @@ -78,7 +79,7 @@ TEST_F(UnitTestVector3, MultiplicationOperator) TEST_F(UnitTestVector3, MultiplicationWithVectorOperator) { - Vector3 v3 = v1 * v2; + constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) * Vector3(4.0f, 5.0f, 6.0f); EXPECT_FLOAT_EQ(v3.x, 4.0f); EXPECT_FLOAT_EQ(v3.y, 10.0f); EXPECT_FLOAT_EQ(v3.z, 18.0f); @@ -86,7 +87,7 @@ TEST_F(UnitTestVector3, MultiplicationWithVectorOperator) TEST_F(UnitTestVector3, DivisionOperator) { - Vector3 v3 = v2 / 2.0f; + constexpr Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) / 2.0f; EXPECT_FLOAT_EQ(v3.x, 2.0f); EXPECT_FLOAT_EQ(v3.y, 2.5f); EXPECT_FLOAT_EQ(v3.z, 3.0f); @@ -94,7 +95,7 @@ TEST_F(UnitTestVector3, DivisionOperator) TEST_F(UnitTestVector3, DivisionWithVectorOperator) { - Vector3 v3 = v2 / v1; + constexpr Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) / Vector3(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v3.x, 4.0f); EXPECT_FLOAT_EQ(v3.y, 2.5f); EXPECT_FLOAT_EQ(v3.z, 2.0f); @@ -151,61 +152,34 @@ TEST_F(UnitTestVector3, DivisionWithVectorAssignmentOperator) TEST_F(UnitTestVector3, NegationOperator) { - Vector3 v3 = -v1; + constexpr Vector3 v3 = -Vector3(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v3.x, -1.0f); EXPECT_FLOAT_EQ(v3.y, -2.0f); EXPECT_FLOAT_EQ(v3.z, -3.0f); } // Test other member functions -TEST_F(UnitTestVector3, DistTo) -{ - float dist = v1.DistTo(v2); - EXPECT_FLOAT_EQ(dist, sqrt(27.0f)); -} - TEST_F(UnitTestVector3, DistToSqr) { - float distSqr = v1.DistToSqr(v2); + constexpr float distSqr = Vector3(1.0f, 2.0f, 3.0f).DistToSqr(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(distSqr, 27.0f); } TEST_F(UnitTestVector3, DotProduct) { - float dot = v1.Dot(v2); + constexpr float dot = Vector3(1.0f, 2.0f, 3.0f).Dot(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(dot, 32.0f); } -TEST_F(UnitTestVector3, CrossProduct) -{ - Vector3 v3 = v1.Cross(v2); - EXPECT_FLOAT_EQ(v3.x, -3.0f); - EXPECT_FLOAT_EQ(v3.y, 6.0f); - EXPECT_FLOAT_EQ(v3.z, -3.0f); -} - -TEST_F(UnitTestVector3, Length) -{ - float length = v1.Length(); - EXPECT_FLOAT_EQ(length, sqrt(14.0f)); -} - TEST_F(UnitTestVector3, LengthSqr) { - float lengthSqr = v1.LengthSqr(); + constexpr float lengthSqr = Vector3(1.0f, 2.0f, 3.0f).LengthSqr(); EXPECT_FLOAT_EQ(lengthSqr, 14.0f); } -TEST_F(UnitTestVector3, Length2D) -{ - float length2D = v1.Length2D(); - EXPECT_FLOAT_EQ(length2D, sqrt(5.0f)); -} - TEST_F(UnitTestVector3, Abs) { - Vector3 v3(-1.0f, -2.0f, -3.0f); - v3.Abs(); + constexpr Vector3 v3 = Vector3(-1.0f, -2.0f, -3.0f).Abs(); EXPECT_FLOAT_EQ(v3.x, 1.0f); EXPECT_FLOAT_EQ(v3.y, 2.0f); EXPECT_FLOAT_EQ(v3.z, 3.0f); @@ -213,51 +187,26 @@ TEST_F(UnitTestVector3, Abs) TEST_F(UnitTestVector3, Sum) { - float sum = v1.Sum(); + constexpr float sum = Vector3(1.0f, 2.0f, 3.0f).Sum(); EXPECT_FLOAT_EQ(sum, 6.0f); } TEST_F(UnitTestVector3, Sum2D) { - float sum2D = v1.Sum2D(); + constexpr float sum2D = Vector3(1.0f, 2.0f, 3.0f).Sum2D(); EXPECT_FLOAT_EQ(sum2D, 3.0f); } -TEST_F(UnitTestVector3, ViewAngleTo) -{ - Vector3 angle = v1.ViewAngleTo(v1 + Vector3(0.f, 0.f, 5.f)); - EXPECT_NEAR(angle.x, 90.f, 0.01f); // Approximate values, you can fine-tune the expected values - EXPECT_NEAR(angle.y, 0.f, 0.01f); -} - -TEST_F(UnitTestVector3, ForwardVector) -{ - Vector3 forward = Vector3::ForwardVector(0.0f, 0.0f); - EXPECT_FLOAT_EQ(forward.x, 1.0f); - EXPECT_FLOAT_EQ(forward.y, 0.0f); - EXPECT_FLOAT_EQ(forward.z, 0.0f); -} - -TEST_F(UnitTestVector3, RightVector) -{ - Vector3 right = Vector3::RightVector(0.0f, 0.0f, 0.0f); - EXPECT_FLOAT_EQ(right.x, 0.0f); - EXPECT_FLOAT_EQ(right.y, -1.0f); - EXPECT_FLOAT_EQ(right.z, 0.0f); -} - -TEST_F(UnitTestVector3, UpVector) +TEST_F(UnitTestVector3, CrossProduct) { - Vector3 up = Vector3::UpVector(0.f, 0.0f, 0.0f); - EXPECT_FLOAT_EQ(up.x, 0.0f); - EXPECT_FLOAT_EQ(up.y, 0.0f); - EXPECT_FLOAT_EQ(up.z, 1.0f); + constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f).Cross(Vector3(4.0f, 5.0f, 6.0f)); + EXPECT_FLOAT_EQ(v3.x, -3.0f); + EXPECT_FLOAT_EQ(v3.y, 6.0f); + EXPECT_FLOAT_EQ(v3.z, -3.0f); } -TEST_F(UnitTestVector3, Normalized) -{ - Vector3 v3 = v1.Normalized(); - EXPECT_NEAR(v3.x, 0.26726f, 0.0001f); - EXPECT_NEAR(v3.y, 0.53452f, 0.0001f); - EXPECT_NEAR(v3.z, 0.80178f, 0.0001f); -} \ No newline at end of file +// Test constexpr with static_assert +static_assert(Vector3(1.0f, 2.0f, 3.0f).LengthSqr() == 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).DistToSqr(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"); From f608a66be809a1936eb98d3f42632626fe20f58b Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 02:00:10 +0300 Subject: [PATCH 057/795] fixed naming --- source/CMakeLists.txt | 2 +- source/{matrix.cpp => Matrix.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename source/{matrix.cpp => Matrix.cpp} (100%) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 495eacc5..9d79f16a 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,6 @@ target_sources(omath PRIVATE Vector3.cpp - matrix.cpp + Matrix.cpp color.cpp Vector4.cpp Vector2.cpp) diff --git a/source/matrix.cpp b/source/Matrix.cpp similarity index 100% rename from source/matrix.cpp rename to source/Matrix.cpp From 311d38be02b81549c21944e4af38c98df5e741f1 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 02:04:35 +0300 Subject: [PATCH 058/795] removed trash --- include/omath/Vector2.h | 1 - include/omath/projection/Camera.h | 4 ---- source/prediction/Engine.cpp | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/include/omath/Vector2.h b/include/omath/Vector2.h index 1cfdd7c6..f04da2dd 100644 --- a/include/omath/Vector2.h +++ b/include/omath/Vector2.h @@ -166,7 +166,6 @@ namespace omath return {x / fl, y / fl}; } - // Normalize the vector [[nodiscard]] Vector2 Normalized() const; diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index 4bbd069e..799c37dd 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -27,11 +27,7 @@ namespace omath::projection Camera(const Vector3& position, const Vector3& viewAngles, const ViewPort& viewPort, float fov, float near, float far); void SetViewAngles(const Vector3& viewAngles); - [[nodiscard]] const Vector3& GetViewAngles() const; [[nodiscard]] Matrix GetViewMatrix() const; - [[nodiscard]] Matrix GetProjectionMatrix() const; - [[nodiscard]] Matrix GetTranslationMatrix() const; - [[nodiscard]] Matrix GetOrientationMatrix() const; [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; diff --git a/source/prediction/Engine.cpp b/source/prediction/Engine.cpp index 80cf4470..b6d7e5d7 100644 --- a/source/prediction/Engine.cpp +++ b/source/prediction/Engine.cpp @@ -45,7 +45,7 @@ namespace omath::prediction const Vector3 &targetPosition) const { const auto bulletGravity = m_gravityConstant * projectile.m_gravityScale; - const auto delta = targetPosition - projectile.m_origin;; + const auto delta = targetPosition - projectile.m_origin; const auto distance2d = delta.Length2D(); From ecd053c066a40778d92e11bc5ab8dd9529525413 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:10:28 +0300 Subject: [PATCH 059/795] vec4 is constexpr --- include/omath/Vector4.h | 141 +++++++++++++++++++++++++++++++++------- source/Vector4.cpp | 124 ----------------------------------- 2 files changed, 117 insertions(+), 148 deletions(-) diff --git a/include/omath/Vector4.h b/include/omath/Vector4.h index 32fa324c..0daec48c 100644 --- a/include/omath/Vector4.h +++ b/include/omath/Vector4.h @@ -4,59 +4,152 @@ #pragma once #include +#include + namespace omath { class Vector4 : public Vector3 { public: - float w = 0.f; + float w; - Vector4(const float x = 0.f, const float y = 0.f, const float z = 0.f, const float w = 0.f) : Vector3(x, y, z), w(w) {} - Vector4(); + constexpr Vector4(float x = 0.f, float y = 0.f, float z = 0.f, float w = 0.f) : Vector3(x, y, z), w(w) {} + constexpr Vector4() : Vector3(), w(0.f) {}; [[nodiscard]] - bool operator==(const Vector4& src) const; + constexpr bool operator==(const Vector4& src) const + { + return Vector3::operator==(src) && w == src.w; + } [[nodiscard]] - bool operator!=(const Vector4& src) const; - - Vector4& operator+=(const Vector4& v); - Vector4& operator-=(const Vector4& v); - Vector4& operator*=(float scalar); - Vector4& operator*=(const Vector4& v); - Vector4& operator/=(float scalar); - Vector4& operator/=(const Vector4& v); + constexpr bool operator!=(const Vector4& src) const + { + return !(*this == src); + } + + constexpr Vector4& operator+=(const Vector4& v) + { + Vector3::operator+=(v); + w += v.w; + + return *this; + } + + constexpr Vector4& operator-=(const Vector4& v) + { + Vector3::operator-=(v); + w -= v.w; + + return *this; + } + + constexpr Vector4& operator*=(float scalar) + { + Vector3::operator*=(scalar); + w *= scalar; + + return *this; + } + + constexpr Vector4& operator*=(const Vector4& v) + { + Vector3::operator*=(v); + w *= v.w; + + return *this; + } + + constexpr Vector4& operator/=(float scalar) + { + Vector3::operator/=(scalar); + w /= scalar; + + return *this; + } + + constexpr Vector4& operator/=(const Vector4& v) + { + Vector3::operator/=(v); + w /= v.w; + return *this; + } + + [[nodiscard]] constexpr float LengthSqr() const + { + return Vector3::LengthSqr() + w * w; + } + + [[nodiscard]] constexpr float Dot(const Vector4& vOther) const + { + return Vector3::Dot(vOther) + w * vOther.w; + } [[nodiscard]] float Length() const; - [[nodiscard]] float LengthSqr() const; - [[nodiscard]] float Dot(const Vector4& vOther) const; - Vector4& Abs(); - Vector4& Clamp(float min, float max); + constexpr Vector4& Abs() + { + Vector3::Abs(); + w = w < 0.f ? -w : w; + + return *this; + } + constexpr Vector4& Clamp(float min, float max) + { + x = std::clamp(x, min, max); + y = std::clamp(y, min, max); + z = std::clamp(z, min, max); + + return *this; + } [[nodiscard]] - Vector4 operator-() const; + constexpr Vector4 operator-() const + { + return {-x, -y, -z, -w}; + } [[nodiscard]] - Vector4 operator+(const Vector4& v) const; + constexpr Vector4 operator+(const Vector4& v) const + { + return {x + v.x, y + v.y, z + v.z, w + v.w}; + } [[nodiscard]] - Vector4 operator-(const Vector4& v) const; + constexpr Vector4 operator-(const Vector4& v) const + { + return {x - v.x, y - v.y, z - v.z, w - v.w}; + } [[nodiscard]] - Vector4 operator*(float scalar) const; + constexpr Vector4 operator*(float scalar) const + { + return {x * scalar, y * scalar, z * scalar, w * scalar}; + } [[nodiscard]] - Vector4 operator*(const Vector4& v) const; + constexpr Vector4 operator*(const Vector4& v) const + { + return {x * v.x, y * v.y, z * v.z, w * v.w}; + } [[nodiscard]] - Vector4 operator/(float scalar) const; + constexpr Vector4 operator/(float scalar) const + { + return {x / scalar, y / scalar, z / scalar, w / scalar}; + } [[nodiscard]] - Vector4 operator/(const Vector4& v) const; + constexpr Vector4 operator/(const Vector4& v) const + { + return {x / v.x, y / v.y, z / v.z, w / v.w}; + } [[nodiscard]] - float Sum() const; + constexpr float Sum() const + { + return Vector3::Sum() + w; + } }; } diff --git a/source/Vector4.cpp b/source/Vector4.cpp index 43ee1750..6d3ae825 100644 --- a/source/Vector4.cpp +++ b/source/Vector4.cpp @@ -9,133 +9,9 @@ namespace omath { - bool Vector4::operator==(const Vector4& src) const - { - return Vector3::operator==(src) && w == src.w; - } - - bool Vector4::operator!=(const Vector4& src) const - { - return !(*this == src); - } - - Vector4& Vector4::operator+=(const Vector4& v) - { - Vector3::operator+=(v); - w += v.w; - return *this; - } - - Vector4& Vector4::operator-=(const Vector4& v) - { - Vector3::operator-=(v); - w -= v.w; - return *this; - } - - Vector4& Vector4::operator*=(float scalar) - { - Vector3::operator*=(scalar); - w *= scalar; - return *this; - } - - Vector4& Vector4::operator*=(const Vector4& v) - { - Vector3::operator*=(v); - w *= v.w; - return *this; - } - - Vector4& Vector4::operator/=(float scalar) - { - Vector3::operator/=(scalar); - w /= scalar; - return *this; - } - - Vector4& Vector4::operator/=(const Vector4& v) - { - Vector3::operator/=(v); - w /= v.w; - return *this; - } float Vector4::Length() const { return std::sqrt(LengthSqr()); } - - float Vector4::LengthSqr() const - { - return Vector3::LengthSqr() + w * w; - } - - float Vector4::Dot(const Vector4& vOther) const - { - return Vector3::Dot(vOther) + w * vOther.w; - } - - Vector4& Vector4::Abs() - { - Vector3::Abs(); - w = std::abs(w); - return *this; - } - - Vector4& Vector4::Clamp(const float min, const float max) - { - x = std::clamp(x, min, max); - y = std::clamp(y, min, max); - z = std::clamp(z, min, max); - - return *this; - } - - Vector4 Vector4::operator-() const - { - return {-x, -y, -z, -w}; - } - - Vector4 Vector4::operator+(const Vector4& v) const - { - return {x + v.x, y + v.y, z + v.z, w + v.w}; - } - - Vector4 Vector4::operator-(const Vector4& v) const - { - return {x - v.x, y - v.y, z - v.z, w - v.w}; - } - - Vector4 Vector4::operator*(float scalar) const - { - return {x * scalar, y * scalar, z * scalar, w * scalar}; - } - - Vector4 Vector4::operator*(const Vector4& v) const - { - return {x * v.x, y * v.y, z * v.z, w * v.w}; - } - - Vector4 Vector4::operator/(float scalar) const - { - return {x / scalar, y / scalar, z / scalar, w / scalar}; - } - - Vector4 Vector4::operator/(const Vector4& v) const - { - return {x / v.x, y / v.y, z / v.z, w / v.w}; - } - - float Vector4::Sum() const - { - return x + y + z + w; - } - - Vector4::Vector4() - { - x = 0.f; - y = 0.f; - z = 0.f; - } } From a2ec1ef418e1c0e2d2b6b357eb1e059158046dee Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:23:25 +0300 Subject: [PATCH 060/795] added constexpr to color --- include/omath/color.h | 98 ++++++++++++++++++++++++++++++++++++----- source/color.cpp | 86 ------------------------------------ tests/UnitTestColor.cpp | 20 ++++----- 3 files changed, 96 insertions(+), 108 deletions(-) diff --git a/include/omath/color.h b/include/omath/color.h index 2a5f2ba0..6f574ae0 100644 --- a/include/omath/color.h +++ b/include/omath/color.h @@ -18,30 +18,104 @@ namespace omath::color float m_value{}; }; - [[nodiscard]] - Vector3 Blend(const Vector3& first, const Vector3& second, float ratio); class Color final : public Vector4 { public: - Color(float r, float g, float b, float a); - explicit Color(); + constexpr Color(float r, float g, float b, float a) : Vector4(r,g,b,a) + { + Clamp(0.f, 1.f); + } + + explicit Color() + ; [[nodiscard]] - static Color FromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + constexpr static Color FromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + { + return Color{Vector4(r, g, b, a) / 255.f}; + } [[nodiscard]] - static Color FromHSV(float hue, float saturation, float value); + constexpr static Color FromHSV(float hue, float saturation, float value) + { + float r{}, g{}, b{}; + + hue = std::clamp(hue, 0.f, 1.f); + + const int i = static_cast(hue * 6.f); + const float f = hue * 6 - i; + const float p = value * (1 - saturation); + const float q = value * (1 - f * saturation); + const float t = value * (1 - (1 - f) * saturation); + + switch (i % 6) + { + case 0: r = value, g = t, b = p; break; + case 1: r = q, g = value, b = p; break; + case 2: r = p, g = value, b = t; break; + case 3: r = p, g = q, b = value; break; + case 4: r = t, g = p, b = value; break; + case 5: r = value, g = p, b = q; break; + + default: return {0.f, 0.f, 0.f, 0.f}; + } + + return {r, g, b, 1.f}; + } [[nodiscard]] - HSV ToHSV() const; + constexpr HSV ToHSV() const + { + HSV hsvData; + + const float& red = x; + const float& green = y; + const float& blue = z; + + const float max = std::max({red, green, blue}); + const float min = std::min({red, green, blue}); + const float delta = max - min; - explicit Color(Vector4 vec); + + if (delta == 0.f) + hsvData.m_hue = 0.f; + + else if (max == red) + hsvData.m_hue = 60.f * (std::fmodf(((green - blue) / delta), 6.f)); + else if (max == green) + hsvData.m_hue = 60.f * (((blue - red) / delta) + 2.f); + else if (max == blue) + hsvData.m_hue = 60.f * (((red - green) / delta) + 4.f); + + if (hsvData.m_hue < 0.f) + hsvData.m_hue += 360.f; + + hsvData.m_hue /= 360.f; + hsvData.m_saturation = max == 0.f ? 0.f : delta / max; + hsvData.m_value = max; + + return hsvData; + } + + constexpr explicit Color(const Vector4& vec) : Vector4(vec) + { + Clamp(0.f, 1.f); + } [[nodiscard]] - Color Blend(const Color& other, float ratio) const; + constexpr Color Blend(const Color& other, float ratio) const + { + return Color( (*this * (1.f - ratio)) + (other * ratio) ); + } - [[nodiscard]] static Color Red() {return {1.f, 0.f, 0.f, 1.f};} - [[nodiscard]] static Color Green() {return {0.f, 1.f, 0.f, 1.f};} - [[nodiscard]] static Color Blue() {return {0.f, 0.f, 1.f, 1.f};} + [[nodiscard]] static constexpr Color Red() {return {1.f, 0.f, 0.f, 1.f};} + [[nodiscard]] static constexpr Color Green() {return {0.f, 1.f, 0.f, 1.f};} + [[nodiscard]] static constexpr Color Blue() {return {0.f, 0.f, 1.f, 1.f};} }; + + [[nodiscard]] + constexpr Color Blend(const Color& first, const Color& second, float ratio) + { + return Color{first * (1.f - std::clamp(ratio, 0.f, 1.f)) + second * ratio}; + } } \ No newline at end of file diff --git a/source/color.cpp b/source/color.cpp index cbe19957..1916733c 100644 --- a/source/color.cpp +++ b/source/color.cpp @@ -9,92 +9,6 @@ namespace omath::color { - Vector3 Blend(const Vector3 &first, const Vector3 &second, float ratio) - { - return first * (1.f - std::clamp(ratio, 0.f, 1.f)) + second * ratio; - } - - Color Color::Blend(const Color &other, float ratio) const - { - return Color( (*this * (1.f - ratio)) + (other * ratio) ); - } - - Color::Color(const float r, const float g, const float b, const float a) - : Vector4(std::clamp(r, 0.f, 1.f), - std::clamp(g, 0.f, 1.f), - std::clamp(b, 0.f, 1.f), - std::clamp(a, 0.f, 1.f)) - { - - } - - Color::Color(Vector4 vec) : Vector4(vec.Clamp(0.f, 1.f)) - { - - } - - Color Color::FromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) - { - return Color{Vector4(r, g, b, a) / 255.f}; - } - - Color Color::FromHSV(float hue, const float saturation, const float value) { - float r{}, g{}, b{}; - - hue = std::clamp(hue, 0.f, 1.f); - - const int i = static_cast(hue * 6.f); - const float f = hue * 6 - i; - const float p = value * (1 - saturation); - const float q = value * (1 - f * saturation); - const float t = value * (1 - (1 - f) * saturation); - - switch (i % 6) - { - case 0: r = value, g = t, b = p; break; - case 1: r = q, g = value, b = p; break; - case 2: r = p, g = value, b = t; break; - case 3: r = p, g = q, b = value; break; - case 4: r = t, g = p, b = value; break; - case 5: r = value, g = p, b = q; break; - - default: return {0.f, 0.f, 0.f, 0.f}; - } - - return {r, g, b, 1.f}; - } - - HSV Color::ToHSV() const { - HSV hsvData; - - const float& red = x; - const float& green = y; - const float& blue = z; - - const float max = std::max({red, green, blue}); - const float min = std::min({red, green, blue}); - const float delta = max - min; - - - if (delta == 0.f) - hsvData.m_hue = 0.f; - - else if (max == red) - hsvData.m_hue = 60.f * (std::fmodf(((green - blue) / delta), 6.f)); - else if (max == green) - hsvData.m_hue = 60.f * (((blue - red) / delta) + 2.f); - else if (max == blue) - hsvData.m_hue = 60.f * (((red - green) / delta) + 4.f); - - if (hsvData.m_hue < 0.f) - hsvData.m_hue += 360.f; - - hsvData.m_hue /= 360.f; - hsvData.m_saturation = max == 0.f ? 0.f : delta / max; - hsvData.m_value = max; - - return hsvData; - } Color::Color() : Vector4(0.f, 0.f, 0.f, 0.f) { diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp index ffe828c7..a74de5cf 100644 --- a/tests/UnitTestColor.cpp +++ b/tests/UnitTestColor.cpp @@ -23,7 +23,7 @@ class UnitTestColor : public ::testing::Test // Test constructors TEST_F(UnitTestColor, Constructor_Float) { - Color color(0.5f, 0.5f, 0.5f, 1.0f); + constexpr Color color(0.5f, 0.5f, 0.5f, 1.0f); EXPECT_FLOAT_EQ(color.x, 0.5f); EXPECT_FLOAT_EQ(color.y, 0.5f); EXPECT_FLOAT_EQ(color.z, 0.5f); @@ -32,7 +32,7 @@ TEST_F(UnitTestColor, Constructor_Float) TEST_F(UnitTestColor, Constructor_Vector4) { - omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f); + constexpr omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f); Color color(vec); EXPECT_FLOAT_EQ(color.x, 0.2f); EXPECT_FLOAT_EQ(color.y, 0.4f); @@ -43,7 +43,7 @@ TEST_F(UnitTestColor, Constructor_Vector4) // Test static methods for color creation TEST_F(UnitTestColor, FromRGBA) { - Color color = Color::FromRGBA(128, 64, 32, 255); + constexpr Color color = Color::FromRGBA(128, 64, 32, 255); EXPECT_FLOAT_EQ(color.x, 128.0f / 255.0f); EXPECT_FLOAT_EQ(color.y, 64.0f / 255.0f); EXPECT_FLOAT_EQ(color.z, 32.0f / 255.0f); @@ -52,7 +52,7 @@ TEST_F(UnitTestColor, FromRGBA) TEST_F(UnitTestColor, FromHSV) { - Color color = Color::FromHSV(0.0f, 1.0f, 1.0f); // Red in HSV + constexpr Color color = Color::FromHSV(0.0f, 1.0f, 1.0f); // Red in HSV EXPECT_FLOAT_EQ(color.x, 1.0f); EXPECT_FLOAT_EQ(color.y, 0.0f); EXPECT_FLOAT_EQ(color.z, 0.0f); @@ -81,9 +81,9 @@ TEST_F(UnitTestColor, Blend) // Test predefined colors TEST_F(UnitTestColor, PredefinedColors) { - Color red = Color::Red(); - Color green = Color::Green(); - Color blue = Color::Blue(); + constexpr Color red = Color::Red(); + constexpr Color green = Color::Green(); + constexpr Color blue = Color::Blue(); EXPECT_FLOAT_EQ(red.x, 1.0f); EXPECT_FLOAT_EQ(red.y, 0.0f); @@ -104,9 +104,9 @@ TEST_F(UnitTestColor, PredefinedColors) // Test non-member function: Blend for Vector3 TEST_F(UnitTestColor, BlendVector3) { - omath::Vector3 v1(1.0f, 0.0f, 0.0f); // Red - omath::Vector3 v2(0.0f, 1.0f, 0.0f); // Green - omath::Vector3 blended = Blend(v1, v2, 0.5f); + constexpr omath::color::Color v1(1.0f, 0.0f, 0.0f, 1.f); // Red + constexpr omath::color::Color v2(0.0f, 1.0f, 0.0f, 1.f); // Green + constexpr omath::color::Color blended = Blend(v1, v2, 0.5f); EXPECT_FLOAT_EQ(blended.x, 0.5f); EXPECT_FLOAT_EQ(blended.y, 0.5f); EXPECT_FLOAT_EQ(blended.z, 0.0f); From cb8c720f03837fdf242a137a808a28f82fbd1063 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:28:05 +0300 Subject: [PATCH 061/795] fixed naming --- include/omath/{color.h => Color.h} | 8 +++++--- include/omath/Vector4.h | 2 +- source/color.cpp | 8 ++------ tests/UnitTestColor.cpp | 8 ++++---- 4 files changed, 12 insertions(+), 14 deletions(-) rename include/omath/{color.h => Color.h} (97%) diff --git a/include/omath/color.h b/include/omath/Color.h similarity index 97% rename from include/omath/color.h rename to include/omath/Color.h index 6f574ae0..e8da221c 100644 --- a/include/omath/color.h +++ b/include/omath/Color.h @@ -9,7 +9,7 @@ #include "omath/Vector4.h" -namespace omath::color +namespace omath { struct HSV { @@ -27,8 +27,10 @@ namespace omath::color Clamp(0.f, 1.f); } - explicit Color() - ; + constexpr explicit Color() : Vector4() + { + + } [[nodiscard]] constexpr static Color FromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { diff --git a/include/omath/Vector4.h b/include/omath/Vector4.h index 0daec48c..46d61aef 100644 --- a/include/omath/Vector4.h +++ b/include/omath/Vector4.h @@ -14,7 +14,7 @@ namespace omath public: float w; - constexpr Vector4(float x = 0.f, float y = 0.f, float z = 0.f, float w = 0.f) : Vector3(x, y, z), w(w) {} + constexpr Vector4(float x, float y, float z, float w) : Vector3(x, y, z), w(w) {} constexpr Vector4() : Vector3(), w(0.f) {}; [[nodiscard]] diff --git a/source/color.cpp b/source/color.cpp index 1916733c..1de3bfdb 100644 --- a/source/color.cpp +++ b/source/color.cpp @@ -2,16 +2,12 @@ // Created by vlad on 2/4/24. // -#include "omath/color.h" +#include "omath/Color.h" #include #include -namespace omath::color +namespace omath { - Color::Color() : Vector4(0.f, 0.f, 0.f, 0.f) - { - - } } diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp index a74de5cf..a78276dd 100644 --- a/tests/UnitTestColor.cpp +++ b/tests/UnitTestColor.cpp @@ -5,7 +5,7 @@ #include -using namespace omath::color; +using namespace omath; class UnitTestColor : public ::testing::Test { @@ -104,9 +104,9 @@ TEST_F(UnitTestColor, PredefinedColors) // Test non-member function: Blend for Vector3 TEST_F(UnitTestColor, BlendVector3) { - constexpr omath::color::Color v1(1.0f, 0.0f, 0.0f, 1.f); // Red - constexpr omath::color::Color v2(0.0f, 1.0f, 0.0f, 1.f); // Green - constexpr omath::color::Color blended = Blend(v1, v2, 0.5f); + constexpr Color v1(1.0f, 0.0f, 0.0f, 1.f); // Red + constexpr Color v2(0.0f, 1.0f, 0.0f, 1.f); // Green + constexpr Color blended = Blend(v1, v2, 0.5f); EXPECT_FLOAT_EQ(blended.x, 0.5f); EXPECT_FLOAT_EQ(blended.y, 0.5f); EXPECT_FLOAT_EQ(blended.z, 0.0f); From f412d688ded459c57cbffe6a7288394b84bfe12a Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:35:48 +0300 Subject: [PATCH 062/795] removed usless code added constexpr --- include/omath/pathfinding/Astar.h | 2 +- include/omath/pathfinding/NavigationMesh.h | 9 +-------- include/omath/prediction/Target.h | 10 +++++++++- source/pathfinding/Astar.cpp | 4 ++-- source/pathfinding/NavigationMesh.cpp | 2 +- source/prediction/Target.cpp | 9 --------- 6 files changed, 14 insertions(+), 22 deletions(-) diff --git a/include/omath/pathfinding/Astar.h b/include/omath/pathfinding/Astar.h index 05256790..64bfc885 100644 --- a/include/omath/pathfinding/Astar.h +++ b/include/omath/pathfinding/Astar.h @@ -10,7 +10,7 @@ namespace omath::pathfinding { - class Astar + class Astar final { public: [[nodiscard]] diff --git a/include/omath/pathfinding/NavigationMesh.h b/include/omath/pathfinding/NavigationMesh.h index 7b4ffa65..11e08bbe 100644 --- a/include/omath/pathfinding/NavigationMesh.h +++ b/include/omath/pathfinding/NavigationMesh.h @@ -12,19 +12,12 @@ namespace omath::pathfinding { - struct NavigationVertex - { - Vector3 origin; - std::vector connections; - }; - - class NavigationMesh final { public: [[nodiscard]] - std::expected GetClossestVertex(const Vector3& point) const; + std::expected GetClosestVertex(const Vector3& point) const; [[nodiscard]] diff --git a/include/omath/prediction/Target.h b/include/omath/prediction/Target.h index bb21fb07..0332c53c 100644 --- a/include/omath/prediction/Target.h +++ b/include/omath/prediction/Target.h @@ -13,7 +13,15 @@ namespace omath::prediction public: [[nodiscard]] - Vector3 PredictPosition(float time, float gravity) const; + constexpr Vector3 PredictPosition(float time, float gravity) const + { + auto predicted = m_origin + m_velocity * time; + + if (m_isAirborne) + predicted.z -= gravity * std::pow(time, 2.f) * 0.5f; + + return predicted; + } Vector3 m_origin; Vector3 m_velocity; diff --git a/source/pathfinding/Astar.cpp b/source/pathfinding/Astar.cpp index a5002e93..10a03e9a 100644 --- a/source/pathfinding/Astar.cpp +++ b/source/pathfinding/Astar.cpp @@ -23,8 +23,8 @@ namespace omath::pathfinding std::unordered_map closedList; std::unordered_map openList; - const auto startVertex = navMesh.GetClossestVertex(start).value(); - const auto endVertex = navMesh.GetClossestVertex(end).value(); + const auto startVertex = navMesh.GetClosestVertex(start).value(); + const auto endVertex = navMesh.GetClosestVertex(end).value(); openList.emplace(startVertex, PathNode{std::nullopt, 0.f}); diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/NavigationMesh.cpp index 766558ee..8b962481 100644 --- a/source/pathfinding/NavigationMesh.cpp +++ b/source/pathfinding/NavigationMesh.cpp @@ -7,7 +7,7 @@ #include namespace omath::pathfinding { - std::expected NavigationMesh::GetClossestVertex(const Vector3 &point) const + std::expected NavigationMesh::GetClosestVertex(const Vector3 &point) const { const auto res = std::ranges::min_element(m_verTextMap, [&point](const auto& a, const auto& b) diff --git a/source/prediction/Target.cpp b/source/prediction/Target.cpp index 64c58a14..3b4c2da9 100644 --- a/source/prediction/Target.cpp +++ b/source/prediction/Target.cpp @@ -3,18 +3,9 @@ // #include "omath/prediction/Target.h" -#include namespace omath::prediction { - Vector3 Target::PredictPosition(const float time, const float gravity) const - { - auto predicted = m_origin + m_velocity * time; - if (m_isAirborne) - predicted.z -= gravity * std::pow(time, 2.f) * 0.5f; - - return predicted; - } } From 8e836a6d2dd7c23ee59b639e1508f9dcd05c559e Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:36:29 +0300 Subject: [PATCH 063/795] fixed naming --- include/omath/{angles.h => Angles.h} | 0 source/Matrix.cpp | 2 +- source/Vector3.cpp | 2 +- source/prediction/Engine.cpp | 2 +- source/projection/Camera.cpp | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename include/omath/{angles.h => Angles.h} (100%) diff --git a/include/omath/angles.h b/include/omath/Angles.h similarity index 100% rename from include/omath/angles.h rename to include/omath/Angles.h diff --git a/source/Matrix.cpp b/source/Matrix.cpp index 37a5742d..0545490f 100644 --- a/source/Matrix.cpp +++ b/source/Matrix.cpp @@ -1,6 +1,6 @@ #include "omath/Matrix.h" #include "omath/Vector3.h" -#include "omath/angles.h" +#include "omath/Angles.h" #include diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 0dd9be07..b74ef6ee 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace omath { diff --git a/source/prediction/Engine.cpp b/source/prediction/Engine.cpp index b6d7e5d7..47d5ea54 100644 --- a/source/prediction/Engine.cpp +++ b/source/prediction/Engine.cpp @@ -5,7 +5,7 @@ #include "omath/prediction/Engine.h" #include -#include +#include namespace omath::prediction diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 35c79dfc..99e2c113 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -5,7 +5,7 @@ #include -#include "omath/angles.h" +#include "omath/Angles.h" namespace omath::projection From 2947bbd95a2c693559f81fc71b15cfad6b154068 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:37:27 +0300 Subject: [PATCH 064/795] added constexpr to unit tests --- tests/UnitTestMatrix.cpp | 3 ++- tests/UnitTestVector2.cpp | 2 +- tests/UnitTestVector3.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index c2f08407..205bf03b 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -14,7 +14,8 @@ class UnitTestMatrix : public ::testing::Test Matrix m1; Matrix m2; - void SetUp() override { + constexpr void SetUp() override + { m1 = Matrix(2, 2); m2 = Matrix{{1.0f, 2.0f}, {3.0f, 4.0f}}; } diff --git a/tests/UnitTestVector2.cpp b/tests/UnitTestVector2.cpp index c1553beb..3e9740d2 100644 --- a/tests/UnitTestVector2.cpp +++ b/tests/UnitTestVector2.cpp @@ -15,7 +15,7 @@ class UnitTestVector2 : public ::testing::Test Vector2 v1; Vector2 v2; - void SetUp() override + constexpr void SetUp() override { v1 = Vector2(1.0f, 2.0f); v2 = Vector2(4.0f, 5.0f); diff --git a/tests/UnitTestVector3.cpp b/tests/UnitTestVector3.cpp index 37f9fff5..daa59dc6 100644 --- a/tests/UnitTestVector3.cpp +++ b/tests/UnitTestVector3.cpp @@ -13,7 +13,7 @@ class UnitTestVector3 : public ::testing::Test Vector3 v1; Vector3 v2; - void SetUp() override + constexpr void SetUp() override { v1 = Vector3(1.0f, 2.0f, 3.0f); v2 = Vector3(4.0f, 5.0f, 6.0f); From abfa3e8123d77349c8cc941eda61203cfffddd6a Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:41:22 +0300 Subject: [PATCH 065/795] added error codes --- include/omath/projection/Camera.h | 5 +++-- include/omath/projection/ErrorCodes.h | 16 ++++++++++++++++ source/projection/Camera.cpp | 6 +++--- 3 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 include/omath/projection/ErrorCodes.h diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index 799c37dd..a7ab73ba 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -8,6 +8,7 @@ #include #include #include +#include "ErrorCodes.h" namespace omath::projection @@ -18,7 +19,7 @@ namespace omath::projection float m_width; float m_height; - [[nodiscard]] float AspectRatio() const {return m_width / m_height;} + [[nodiscard]] constexpr float AspectRatio() const {return m_width / m_height;} }; class Camera @@ -29,7 +30,7 @@ namespace omath::projection [[nodiscard]] Matrix GetViewMatrix() const; - [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; + [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; ViewPort m_viewPort{}; float m_fieldOfView; diff --git a/include/omath/projection/ErrorCodes.h b/include/omath/projection/ErrorCodes.h new file mode 100644 index 00000000..968a15ed --- /dev/null +++ b/include/omath/projection/ErrorCodes.h @@ -0,0 +1,16 @@ +// +// Created by Vlad on 03.09.2024. +// + +#pragma once +#include + + +namespace omath::projection +{ + enum class Error : uint16_t + { + WORLD_POSITION_IS_BEHIND_CAMERA = 0, + WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS, + }; +} \ No newline at end of file diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 99e2c113..f0f7bafc 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -30,7 +30,7 @@ namespace omath::projection return Matrix::TranslationMatrix(-m_origin) * Matrix::OrientationMatrix(forward, right, up); } - std::expected Camera::WorldToScreen(const Vector3 &worldPosition) const + std::expected Camera::WorldToScreen(const Vector3 &worldPosition) const { const auto posVecAsMatrix = Matrix({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); @@ -41,13 +41,13 @@ namespace omath::projection auto projected = posVecAsMatrix * (GetViewMatrix() * projectionMatrix); if (projected.At(0, 3) <= 0.f) - return std::unexpected("Projection point is out of camera field of view"); + return std::unexpected(Error::WORLD_POSITION_IS_BEHIND_CAMERA); projected /= projected.At(0, 3); if (projected.At(0, 0) < -1.f || projected.At(0, 0) > 1.f || projected.At(0, 1) < -1.f || projected.At(0, 1) > 1.f) - return std::unexpected("Projection point is out screen bounds"); + return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); projected *= Matrix::ToScreenMatrix(m_viewPort.m_width, m_viewPort.m_height); From f0d4e60ce0e6a33189b0d12469f2d003323e86b2 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:42:25 +0300 Subject: [PATCH 066/795] fix --- tests/UnitTestMatrix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index 205bf03b..fb9566f4 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -14,7 +14,7 @@ class UnitTestMatrix : public ::testing::Test Matrix m1; Matrix m2; - constexpr void SetUp() override + void SetUp() override { m1 = Matrix(2, 2); m2 = Matrix{{1.0f, 2.0f}, {3.0f, 4.0f}}; From 7928cfa126d059c7db83f75295e67e464f3304e5 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:46:32 +0300 Subject: [PATCH 067/795] fixed naming --- include/omath/Matrix.h | 2 +- source/Matrix.cpp | 4 ++-- source/Vector4.cpp | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/include/omath/Matrix.h b/include/omath/Matrix.h index a8d0028d..c1571279 100644 --- a/include/omath/Matrix.h +++ b/include/omath/Matrix.h @@ -26,7 +26,7 @@ namespace omath static Matrix OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up); [[nodiscard]] - static Matrix ProjectionMatrix(float fielOfView, float aspectRatio,float near, float far); + static Matrix ProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); Matrix(const Matrix &other); diff --git a/source/Matrix.cpp b/source/Matrix.cpp index 0545490f..1c01ddd6 100644 --- a/source/Matrix.cpp +++ b/source/Matrix.cpp @@ -350,10 +350,10 @@ namespace omath }; } - Matrix Matrix::ProjectionMatrix(const float fielOfView, const float aspectRatio, const float near, + Matrix Matrix::ProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fielOfView) / 2.f); + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); return { diff --git a/source/Vector4.cpp b/source/Vector4.cpp index 6d3ae825..1d54d804 100644 --- a/source/Vector4.cpp +++ b/source/Vector4.cpp @@ -3,10 +3,9 @@ // #include "omath/Vector4.h" - -#include #include + namespace omath { From 51336b9669e0aedbfedd09c0a9f0be2f37965a33 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:47:14 +0300 Subject: [PATCH 068/795] added final --- include/omath/prediction/Engine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/prediction/Engine.h b/include/omath/prediction/Engine.h index 3a32f920..af7876f8 100644 --- a/include/omath/prediction/Engine.h +++ b/include/omath/prediction/Engine.h @@ -12,7 +12,7 @@ namespace omath::prediction { - class Engine + class Engine final { public: explicit Engine(float gravityConstant, float simulationTimeStep, From 251998e7f715fe3943bd0b9350b13b68688a0bd1 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 21:51:40 +0300 Subject: [PATCH 069/795] added some finals --- tests/UnitTestColor.cpp | 2 +- tests/UnitTestMatrix.cpp | 2 +- tests/UnitTestVector2.cpp | 2 +- tests/UnitTestVector3.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp index a78276dd..574e9ed1 100644 --- a/tests/UnitTestColor.cpp +++ b/tests/UnitTestColor.cpp @@ -7,7 +7,7 @@ using namespace omath; -class UnitTestColor : public ::testing::Test +class UnitTestColor final : public ::testing::Test { protected: Color color1; diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index fb9566f4..92c2781c 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -8,7 +8,7 @@ using namespace omath; -class UnitTestMatrix : public ::testing::Test +class UnitTestMatrix final : public ::testing::Test { protected: Matrix m1; diff --git a/tests/UnitTestVector2.cpp b/tests/UnitTestVector2.cpp index 3e9740d2..1e08947a 100644 --- a/tests/UnitTestVector2.cpp +++ b/tests/UnitTestVector2.cpp @@ -9,7 +9,7 @@ using namespace omath; -class UnitTestVector2 : public ::testing::Test +class UnitTestVector2 final : public ::testing::Test { protected: Vector2 v1; diff --git a/tests/UnitTestVector3.cpp b/tests/UnitTestVector3.cpp index daa59dc6..75474cfd 100644 --- a/tests/UnitTestVector3.cpp +++ b/tests/UnitTestVector3.cpp @@ -7,7 +7,7 @@ using namespace omath; -class UnitTestVector3 : public ::testing::Test +class UnitTestVector3 final : public ::testing::Test { protected: Vector3 v1; From 57ff88af6ced54bd23b179299feb5071bcfc908d Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 22:01:10 +0300 Subject: [PATCH 070/795] fixed wrong definitions --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a2ae732..3a11eaa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,9 +17,9 @@ if(BUILD_TESTS) endif () if (WIN32 AND THREAT_WARNING_AS_ERROR) - target_compile_definitions(omath PRIVATE /W4 /WX) + target_compile_options(omath PRIVATE /W4 /WX) elseif(UNIX AND THREAT_WARNING_AS_ERROR) - target_compile_definitions(omath PRIVATE -Wall -Wextra -Wpedantic) + target_compile_options(omath PRIVATE -Wall -Wextra -Wpedantic) endif() target_include_directories(omath PUBLIC include) \ No newline at end of file From fd080606dd9eb36f0ea0f6f6d5c6657ff27e0568 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 19:06:18 +0300 Subject: [PATCH 071/795] removed finals --- tests/UnitTestColor.cpp | 2 +- tests/UnitTestMatrix.cpp | 2 +- tests/UnitTestVector2.cpp | 2 +- tests/UnitTestVector3.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp index 574e9ed1..a78276dd 100644 --- a/tests/UnitTestColor.cpp +++ b/tests/UnitTestColor.cpp @@ -7,7 +7,7 @@ using namespace omath; -class UnitTestColor final : public ::testing::Test +class UnitTestColor : public ::testing::Test { protected: Color color1; diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index 92c2781c..fb9566f4 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -8,7 +8,7 @@ using namespace omath; -class UnitTestMatrix final : public ::testing::Test +class UnitTestMatrix : public ::testing::Test { protected: Matrix m1; diff --git a/tests/UnitTestVector2.cpp b/tests/UnitTestVector2.cpp index 1e08947a..3e9740d2 100644 --- a/tests/UnitTestVector2.cpp +++ b/tests/UnitTestVector2.cpp @@ -9,7 +9,7 @@ using namespace omath; -class UnitTestVector2 final : public ::testing::Test +class UnitTestVector2 : public ::testing::Test { protected: Vector2 v1; diff --git a/tests/UnitTestVector3.cpp b/tests/UnitTestVector3.cpp index 75474cfd..daa59dc6 100644 --- a/tests/UnitTestVector3.cpp +++ b/tests/UnitTestVector3.cpp @@ -7,7 +7,7 @@ using namespace omath; -class UnitTestVector3 final : public ::testing::Test +class UnitTestVector3 : public ::testing::Test { protected: Vector3 v1; From 0deaf73d9b1783264e0d7d02b131e48693e812da Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 22:10:09 +0300 Subject: [PATCH 072/795] fixed warnings --- source/Matrix.cpp | 4 ++-- source/pathfinding/NavigationMesh.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Matrix.cpp b/source/Matrix.cpp index 1c01ddd6..6d2e11b7 100644 --- a/source/Matrix.cpp +++ b/source/Matrix.cpp @@ -200,8 +200,8 @@ namespace omath m_columns = other.m_columns; m_data = std::move(other.m_data); - other.m_rows = 0.f; - other.m_columns = 0.f; + other.m_rows = 0; + other.m_columns = 0; return *this; diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/NavigationMesh.cpp index 8b962481..1a106dfa 100644 --- a/source/pathfinding/NavigationMesh.cpp +++ b/source/pathfinding/NavigationMesh.cpp @@ -43,7 +43,7 @@ namespace omath::pathfinding for (const auto& [vertex, neighbors] : m_verTextMap) { - const uint16_t neighborsCount = neighbors.size(); + const auto neighborsCount = neighbors.size(); dumpToVector(vertex, raw); dumpToVector(neighborsCount, raw); From 8f3e9e1aab43c5a81bbbc02a48c91a0d37024c0f Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 23:28:09 +0300 Subject: [PATCH 073/795] fixed right vector --- source/Vector3.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Vector3.cpp b/source/Vector3.cpp index b74ef6ee..306f5afa 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -67,9 +67,9 @@ namespace omath const auto sinRoll = std::sin(angles::DegreesToRadians(roll)); - return {-sinRoll*sinPitch*cosYaw + -cosRoll*-sinYaw, - -sinRoll*sinPitch*sinYaw + -cosRoll*cosYaw, - sinRoll*cosPitch}; + return {sinRoll*sinPitch*cosYaw + cosRoll*sinYaw, + sinRoll*sinPitch*sinYaw + -cosRoll*cosYaw, + -sinRoll*cosPitch}; } Vector3 Vector3::UpVector(float pitch, float yaw, float roll) From 05addefd63862b6e107b8c630cbaecf60b7bd580 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 23:42:41 +0300 Subject: [PATCH 074/795] fix --- source/Vector3.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 306f5afa..4d5d756a 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -57,19 +57,26 @@ namespace omath Vector3 Vector3::RightVector(const float pitch, const float yaw, const float roll) { - const auto cosPitch = std::cos(angles::DegreesToRadians(pitch)); - const auto sinPitch = std::sin(angles::DegreesToRadians(pitch)); + const auto radPitch = angles::DegreesToRadians(pitch); + const auto radYaw = angles::DegreesToRadians(yaw); + const auto radRoll = angles::DegreesToRadians(roll); - const auto cosYaw = std::cos(angles::DegreesToRadians(yaw)); - const auto sinYaw = std::sin(angles::DegreesToRadians(yaw)); + const auto cosPitch = std::cos(radPitch); + const auto sinPitch = std::sin(radPitch); - const auto cosRoll = std::cos(angles::DegreesToRadians(roll)); - const auto sinRoll = std::sin(angles::DegreesToRadians(roll)); + const auto cosYaw = std::cos(radYaw); + const auto sinYaw = std::sin(radYaw); + const auto cosRoll = std::cos(radRoll); + const auto sinRoll = std::sin(radRoll); - return {sinRoll*sinPitch*cosYaw + cosRoll*sinYaw, - sinRoll*sinPitch*sinYaw + -cosRoll*cosYaw, - -sinRoll*cosPitch}; + + return + { + sinRoll*sinPitch*cosYaw + cosRoll*sinYaw, + sinRoll*sinPitch*sinYaw - cosRoll*cosYaw, + -sinRoll*cosPitch + }; } Vector3 Vector3::UpVector(float pitch, float yaw, float roll) From 7bb8dcbb8ca026453dfc756e860fe373c9b836cd Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 22:19:17 +0300 Subject: [PATCH 075/795] changed return type --- include/omath/projection/Camera.h | 2 +- source/projection/Camera.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index a7ab73ba..a3bfbe19 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -30,7 +30,7 @@ namespace omath::projection [[nodiscard]] Matrix GetViewMatrix() const; - [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; + [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; ViewPort m_viewPort{}; float m_fieldOfView; diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index f0f7bafc..5f67a6e8 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -30,7 +30,7 @@ namespace omath::projection return Matrix::TranslationMatrix(-m_origin) * Matrix::OrientationMatrix(forward, right, up); } - std::expected Camera::WorldToScreen(const Vector3 &worldPosition) const + std::expected Camera::WorldToScreen(const Vector3 &worldPosition) const { const auto posVecAsMatrix = Matrix({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); @@ -51,6 +51,6 @@ namespace omath::projection projected *= Matrix::ToScreenMatrix(m_viewPort.m_width, m_viewPort.m_height); - return Vector3{projected.At(0, 0), projected.At(0, 1), projected.At(0, 2)}; + return Vector2{projected.At(0, 0), projected.At(0, 1)}; } } From bc340b0d243e84063cf0cf1bb018c020d158fb46 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 3 Sep 2024 22:21:42 +0300 Subject: [PATCH 076/795] removed operator since its not even --- include/omath/Matrix.h | 2 -- source/Matrix.cpp | 15 +-------------- tests/UnitTestMatrix.cpp | 11 ----------- 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/include/omath/Matrix.h b/include/omath/Matrix.h index c1571279..a191e37f 100644 --- a/include/omath/Matrix.h +++ b/include/omath/Matrix.h @@ -65,8 +65,6 @@ namespace omath Matrix operator*(float f) const; - Matrix operator*(const Vector3 &vec3) const; - Matrix &operator*=(float f); Matrix &operator/=(float f); diff --git a/source/Matrix.cpp b/source/Matrix.cpp index 6d2e11b7..8872774b 100644 --- a/source/Matrix.cpp +++ b/source/Matrix.cpp @@ -164,20 +164,7 @@ namespace omath { Set(0.f); } - - Matrix Matrix::operator*(const Vector3 &vec3) const - { - auto vecmatrix = Matrix(m_rows, 1); - vecmatrix.Set(1.f); - vecmatrix.At(0, 0) = vec3.x; - vecmatrix.At(1, 0) = vec3.y; - vecmatrix.At(2, 0) = vec3.z; - - return *this * vecmatrix; - - } - - + Matrix &Matrix::operator=(const Matrix &other) { if (this == &other) diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index fb9566f4..fc48e9b7 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -66,17 +66,6 @@ TEST_F(UnitTestMatrix, Operator_Multiplication_Matrix) EXPECT_FLOAT_EQ(m3.At(1, 1), 22.0f); } -TEST_F(UnitTestMatrix, Operator_Multiplication_Vector3) -{ - Vector3 v(1.0f, 2.0f, 3.0f); - Matrix m3(3, 3, new float[9]{1, 0, 0, 0, 1, 0, 0, 0, 1}); - Matrix result = m3 * v; - - EXPECT_FLOAT_EQ(result.At(0, 0), 1.0f); - EXPECT_FLOAT_EQ(result.At(1, 0), 2.0f); - EXPECT_FLOAT_EQ(result.At(2, 0), 3.0f); -} - TEST_F(UnitTestMatrix, Operator_Multiplication_Scalar) { Matrix m3 = m2 * 2.0f; From b0839630f4a5e3c072dc0e84a64d8d5af2799385 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 13 Sep 2024 00:27:30 +0300 Subject: [PATCH 077/795] added more tests --- tests/UnitTestVector2.cpp | 196 +++++++++++++++++++++++++++++-- tests/UnitTestVector3.cpp | 238 +++++++++++++++++++++++++++++++++----- 2 files changed, 398 insertions(+), 36 deletions(-) diff --git a/tests/UnitTestVector2.cpp b/tests/UnitTestVector2.cpp index 3e9740d2..eb4051c0 100644 --- a/tests/UnitTestVector2.cpp +++ b/tests/UnitTestVector2.cpp @@ -1,11 +1,11 @@ // // Created by Vlad on 02.09.2024. // -// -// Created by Vlad on 01.09.2024. -// + #include #include +#include // For std::isinf and std::isnan +#include // For FLT_MAX and FLT_MIN using namespace omath; @@ -81,6 +81,13 @@ TEST_F(UnitTestVector2, DivisionOperator) EXPECT_FLOAT_EQ(v3.y, 2.5f); } +TEST_F(UnitTestVector2, NegationOperator) +{ + constexpr Vector2 v3 = -Vector2(1.0f, 2.0f); + EXPECT_FLOAT_EQ(v3.x, -1.0f); + EXPECT_FLOAT_EQ(v3.y, -2.0f); +} + // Test compound assignment operators TEST_F(UnitTestVector2, AdditionAssignmentOperator) { @@ -110,12 +117,36 @@ TEST_F(UnitTestVector2, DivisionAssignmentOperator) EXPECT_FLOAT_EQ(v1.y, 1.0f); } -TEST_F(UnitTestVector2, NegationOperator) +// New tests for compound assignment with vectors +TEST_F(UnitTestVector2, MultiplicationAssignmentOperator_Vector) { - constexpr Vector2 v3 = -Vector2(1.0f, 2.0f); - EXPECT_FLOAT_EQ(v3.x, -1.0f); - EXPECT_FLOAT_EQ(v3.y, -2.0f); + v1 *= v2; + EXPECT_FLOAT_EQ(v1.x, 1.0f * 4.0f); + EXPECT_FLOAT_EQ(v1.y, 2.0f * 5.0f); +} + +TEST_F(UnitTestVector2, DivisionAssignmentOperator_Vector) +{ + v1 /= v2; + EXPECT_FLOAT_EQ(v1.x, 1.0f / 4.0f); + EXPECT_FLOAT_EQ(v1.y, 2.0f / 5.0f); +} + +// New tests for compound assignment with floats +TEST_F(UnitTestVector2, AdditionAssignmentOperator_Float) +{ + v1 += 3.0f; + EXPECT_FLOAT_EQ(v1.x, 4.0f); + EXPECT_FLOAT_EQ(v1.y, 5.0f); +} + +TEST_F(UnitTestVector2, SubtractionAssignmentOperator_Float) +{ + v1 -= 1.0f; + EXPECT_FLOAT_EQ(v1.x, 0.0f); + EXPECT_FLOAT_EQ(v1.y, 1.0f); } + // Test other member functions TEST_F(UnitTestVector2, DistTo) { @@ -123,24 +154,62 @@ TEST_F(UnitTestVector2, DistTo) EXPECT_FLOAT_EQ(dist, std::sqrt(18.0f)); } +TEST_F(UnitTestVector2, DistTo_SamePoint) +{ + const float dist = v1.DistTo(v1); + EXPECT_FLOAT_EQ(dist, 0.0f); +} + TEST_F(UnitTestVector2, DistToSqr) { constexpr float distSqr = Vector2(1.0f, 2.0f).DistToSqr(Vector2(4.0f, 5.0f)); EXPECT_FLOAT_EQ(distSqr, 18.0f); } +TEST_F(UnitTestVector2, DistToSqr_SamePoint) +{ + constexpr float distSqr = Vector2(1.0f, 2.0f).DistToSqr(Vector2(1.0f, 2.0f)); + EXPECT_FLOAT_EQ(distSqr, 0.0f); +} + TEST_F(UnitTestVector2, DotProduct) { constexpr float dot = Vector2(1.0f, 2.0f).Dot(Vector2(4.0f, 5.0f)); EXPECT_FLOAT_EQ(dot, 14.0f); } +TEST_F(UnitTestVector2, DotProduct_PerpendicularVectors) +{ + constexpr float dot = Vector2(1.0f, 0.0f).Dot(Vector2(0.0f, 1.0f)); + EXPECT_FLOAT_EQ(dot, 0.0f); +} + +TEST_F(UnitTestVector2, DotProduct_ParallelVectors) +{ + constexpr float dot = Vector2(1.0f, 1.0f).Dot(Vector2(2.0f, 2.0f)); + EXPECT_FLOAT_EQ(dot, 4.0f); +} + TEST_F(UnitTestVector2, Length) { const float length = v1.Length(); EXPECT_FLOAT_EQ(length, std::sqrt(5.0f)); } +TEST_F(UnitTestVector2, Length_ZeroVector) +{ + Vector2 v_zero(0.0f, 0.0f); + const float length = v_zero.Length(); + EXPECT_FLOAT_EQ(length, 0.0f); +} + +TEST_F(UnitTestVector2, Length_LargeValues) +{ + Vector2 v_large(FLT_MAX, FLT_MAX); + const float length = v_large.Length(); + EXPECT_TRUE(std::isinf(length)); +} + TEST_F(UnitTestVector2, LengthSqr) { constexpr float lengthSqr = Vector2(1.0f, 2.0f).LengthSqr(); @@ -149,17 +218,40 @@ TEST_F(UnitTestVector2, LengthSqr) TEST_F(UnitTestVector2, Abs) { - constexpr Vector2 v3 = Vector2(-1.0f, -2.0f).Abs(); + Vector2 v3(-1.0f, -2.0f); + v3.Abs(); + EXPECT_FLOAT_EQ(v3.x, 1.0f); + EXPECT_FLOAT_EQ(v3.y, 2.0f); +} + +TEST_F(UnitTestVector2, Abs_PositiveValues) +{ + Vector2 v3(1.0f, 2.0f); + v3.Abs(); EXPECT_FLOAT_EQ(v3.x, 1.0f); EXPECT_FLOAT_EQ(v3.y, 2.0f); } +TEST_F(UnitTestVector2, Abs_ZeroValues) +{ + Vector2 v3(0.0f, 0.0f); + v3.Abs(); + EXPECT_FLOAT_EQ(v3.x, 0.0f); + EXPECT_FLOAT_EQ(v3.y, 0.0f); +} + TEST_F(UnitTestVector2, Sum) { constexpr float sum = Vector2(1.0f, 2.0f).Sum(); EXPECT_FLOAT_EQ(sum, 3.0f); } +TEST_F(UnitTestVector2, Sum_NegativeValues) +{ + constexpr float sum = Vector2(-1.0f, -2.0f).Sum(); + EXPECT_FLOAT_EQ(sum, -3.0f); +} + TEST_F(UnitTestVector2, Normalized) { const Vector2 v3 = v1.Normalized(); @@ -167,6 +259,94 @@ TEST_F(UnitTestVector2, Normalized) EXPECT_NEAR(v3.y, 0.89443f, 0.0001f); } +TEST_F(UnitTestVector2, Normalized_ZeroVector) +{ + Vector2 v_zero(0.0f, 0.0f); + Vector2 v_norm = v_zero.Normalized(); + EXPECT_FLOAT_EQ(v_norm.x, 0.0f); + EXPECT_FLOAT_EQ(v_norm.y, 0.0f); +} + +// Test AsTuple method +TEST_F(UnitTestVector2, AsTuple) +{ + auto tuple = v1.AsTuple(); + EXPECT_FLOAT_EQ(std::get<0>(tuple), v1.x); + EXPECT_FLOAT_EQ(std::get<1>(tuple), v1.y); +} + +// Test division by zero +TEST_F(UnitTestVector2, DivisionOperator_DivideByZero) +{ + Vector2 v(1.0f, 2.0f); + float zero = 0.0f; + Vector2 result = v / zero; + EXPECT_TRUE(std::isinf(result.x) || std::isnan(result.x)); + EXPECT_TRUE(std::isinf(result.y) || std::isnan(result.y)); +} + +TEST_F(UnitTestVector2, DivisionAssignmentOperator_DivideByZero) +{ + Vector2 v(1.0f, 2.0f); + float zero = 0.0f; + v /= zero; + EXPECT_TRUE(std::isinf(v.x) || std::isnan(v.x)); + EXPECT_TRUE(std::isinf(v.y) || std::isnan(v.y)); +} + +TEST_F(UnitTestVector2, DivisionAssignmentOperator_VectorWithZero) +{ + Vector2 v(1.0f, 2.0f); + Vector2 v_zero(0.0f, 1.0f); + v /= v_zero; + EXPECT_TRUE(std::isinf(v.x) || std::isnan(v.x)); + EXPECT_FLOAT_EQ(v.y, 2.0f / 1.0f); +} + +// Test operations with infinity and NaN +TEST_F(UnitTestVector2, Operator_WithInfinity) +{ + Vector2 v_inf(INFINITY, INFINITY); + Vector2 result = v1 + v_inf; + EXPECT_TRUE(std::isinf(result.x)); + EXPECT_TRUE(std::isinf(result.y)); +} + +TEST_F(UnitTestVector2, Operator_WithNaN) +{ + Vector2 v_nan(NAN, NAN); + Vector2 result = v1 + v_nan; + EXPECT_TRUE(std::isnan(result.x)); + EXPECT_TRUE(std::isnan(result.y)); +} + +// Test negative values in arithmetic operations +TEST_F(UnitTestVector2, AdditionOperator_NegativeValues) +{ + Vector2 v_neg(-1.0f, -2.0f); + Vector2 result = v1 + v_neg; + EXPECT_FLOAT_EQ(result.x, 0.0f); + EXPECT_FLOAT_EQ(result.y, 0.0f); +} + +TEST_F(UnitTestVector2, SubtractionOperator_NegativeValues) +{ + Vector2 v_neg(-1.0f, -2.0f); + Vector2 result = v1 - v_neg; + EXPECT_FLOAT_EQ(result.x, 2.0f); + EXPECT_FLOAT_EQ(result.y, 4.0f); +} + +// Test negation of zero vector +TEST_F(UnitTestVector2, NegationOperator_ZeroVector) +{ + Vector2 v_zero(0.0f, 0.0f); + Vector2 result = -v_zero; + EXPECT_FLOAT_EQ(result.x, -0.0f); + EXPECT_FLOAT_EQ(result.y, -0.0f); +} + +// Static assertions (compile-time checks) static_assert(Vector2(1.0f, 2.0f).LengthSqr() == 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).DistToSqr(Vector2(1.0f, 2.0f)) == 18.0f, "DistToSqr should be 18"); diff --git a/tests/UnitTestVector3.cpp b/tests/UnitTestVector3.cpp index daa59dc6..282bdcef 100644 --- a/tests/UnitTestVector3.cpp +++ b/tests/UnitTestVector3.cpp @@ -1,9 +1,12 @@ // // Created by Vlad on 01.09.2024. // + #include #include #include +#include // For FLT_MAX, FLT_MIN +#include // For std::numeric_limits using namespace omath; @@ -13,7 +16,7 @@ class UnitTestVector3 : public ::testing::Test Vector3 v1; Vector3 v2; - constexpr void SetUp() override + void SetUp() override { v1 = Vector3(1.0f, 2.0f, 3.0f); v2 = Vector3(4.0f, 5.0f, 6.0f); @@ -23,7 +26,7 @@ class UnitTestVector3 : public ::testing::Test // Test constructor and default values TEST_F(UnitTestVector3, Constructor_Default) { - constexpr Vector3 v; + Vector3 v; EXPECT_FLOAT_EQ(v.x, 0.0f); EXPECT_FLOAT_EQ(v.y, 0.0f); EXPECT_FLOAT_EQ(v.z, 0.0f); @@ -31,7 +34,7 @@ TEST_F(UnitTestVector3, Constructor_Default) TEST_F(UnitTestVector3, Constructor_Values) { - constexpr Vector3 v(1.0f, 2.0f, 3.0f); + Vector3 v(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v.x, 1.0f); EXPECT_FLOAT_EQ(v.y, 2.0f); EXPECT_FLOAT_EQ(v.z, 3.0f); @@ -40,14 +43,14 @@ TEST_F(UnitTestVector3, Constructor_Values) // Test equality operators TEST_F(UnitTestVector3, EqualityOperator) { - constexpr Vector3 v3(1.0f, 2.0f, 3.0f); + Vector3 v3(1.0f, 2.0f, 3.0f); EXPECT_TRUE(v1 == v3); EXPECT_FALSE(v1 == v2); } TEST_F(UnitTestVector3, InequalityOperator) { - constexpr Vector3 v3(1.0f, 2.0f, 3.0f); + Vector3 v3(1.0f, 2.0f, 3.0f); EXPECT_FALSE(v1 != v3); EXPECT_TRUE(v1 != v2); } @@ -55,7 +58,7 @@ TEST_F(UnitTestVector3, InequalityOperator) // Test arithmetic operators TEST_F(UnitTestVector3, AdditionOperator) { - constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) + Vector3(4.0f, 5.0f, 6.0f); + Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) + Vector3(4.0f, 5.0f, 6.0f); EXPECT_FLOAT_EQ(v3.x, 5.0f); EXPECT_FLOAT_EQ(v3.y, 7.0f); EXPECT_FLOAT_EQ(v3.z, 9.0f); @@ -63,39 +66,39 @@ TEST_F(UnitTestVector3, AdditionOperator) TEST_F(UnitTestVector3, SubtractionOperator) { - constexpr Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) - Vector3(1.0f, 2.0f, 3.0f); + Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) - Vector3(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v3.x, 3.0f); EXPECT_FLOAT_EQ(v3.y, 3.0f); EXPECT_FLOAT_EQ(v3.z, 3.0f); } -TEST_F(UnitTestVector3, MultiplicationOperator) +TEST_F(UnitTestVector3, MultiplicationOperator_Scalar) { - constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) * 2.0f; + Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) * 2.0f; EXPECT_FLOAT_EQ(v3.x, 2.0f); EXPECT_FLOAT_EQ(v3.y, 4.0f); EXPECT_FLOAT_EQ(v3.z, 6.0f); } -TEST_F(UnitTestVector3, MultiplicationWithVectorOperator) +TEST_F(UnitTestVector3, MultiplicationOperator_Vector) { - constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) * Vector3(4.0f, 5.0f, 6.0f); + Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) * Vector3(4.0f, 5.0f, 6.0f); EXPECT_FLOAT_EQ(v3.x, 4.0f); EXPECT_FLOAT_EQ(v3.y, 10.0f); EXPECT_FLOAT_EQ(v3.z, 18.0f); } -TEST_F(UnitTestVector3, DivisionOperator) +TEST_F(UnitTestVector3, DivisionOperator_Scalar) { - constexpr Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) / 2.0f; + Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) / 2.0f; EXPECT_FLOAT_EQ(v3.x, 2.0f); EXPECT_FLOAT_EQ(v3.y, 2.5f); EXPECT_FLOAT_EQ(v3.z, 3.0f); } -TEST_F(UnitTestVector3, DivisionWithVectorOperator) +TEST_F(UnitTestVector3, DivisionOperator_Vector) { - constexpr Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) / Vector3(1.0f, 2.0f, 3.0f); + Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) / Vector3(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v3.x, 4.0f); EXPECT_FLOAT_EQ(v3.y, 2.5f); EXPECT_FLOAT_EQ(v3.z, 2.0f); @@ -118,7 +121,7 @@ TEST_F(UnitTestVector3, SubtractionAssignmentOperator) EXPECT_FLOAT_EQ(v1.z, -3.0f); } -TEST_F(UnitTestVector3, MultiplicationAssignmentOperator) +TEST_F(UnitTestVector3, MultiplicationAssignmentOperator_Scalar) { v1 *= 2.0f; EXPECT_FLOAT_EQ(v1.x, 2.0f); @@ -126,7 +129,7 @@ TEST_F(UnitTestVector3, MultiplicationAssignmentOperator) EXPECT_FLOAT_EQ(v1.z, 6.0f); } -TEST_F(UnitTestVector3, MultiplicationWithVectorAssignmentOperator) +TEST_F(UnitTestVector3, MultiplicationAssignmentOperator_Vector) { v1 *= v2; EXPECT_FLOAT_EQ(v1.x, 4.0f); @@ -134,7 +137,7 @@ TEST_F(UnitTestVector3, MultiplicationWithVectorAssignmentOperator) EXPECT_FLOAT_EQ(v1.z, 18.0f); } -TEST_F(UnitTestVector3, DivisionAssignmentOperator) +TEST_F(UnitTestVector3, DivisionAssignmentOperator_Scalar) { v1 /= 2.0f; EXPECT_FLOAT_EQ(v1.x, 0.5f); @@ -142,7 +145,7 @@ TEST_F(UnitTestVector3, DivisionAssignmentOperator) EXPECT_FLOAT_EQ(v1.z, 1.5f); } -TEST_F(UnitTestVector3, DivisionWithVectorAssignmentOperator) +TEST_F(UnitTestVector3, DivisionAssignmentOperator_Vector) { v1 /= v2; EXPECT_FLOAT_EQ(v1.x, 0.25f); @@ -152,7 +155,7 @@ TEST_F(UnitTestVector3, DivisionWithVectorAssignmentOperator) TEST_F(UnitTestVector3, NegationOperator) { - constexpr Vector3 v3 = -Vector3(1.0f, 2.0f, 3.0f); + Vector3 v3 = -Vector3(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v3.x, -1.0f); EXPECT_FLOAT_EQ(v3.y, -2.0f); EXPECT_FLOAT_EQ(v3.z, -3.0f); @@ -161,25 +164,26 @@ TEST_F(UnitTestVector3, NegationOperator) // Test other member functions TEST_F(UnitTestVector3, DistToSqr) { - constexpr float distSqr = Vector3(1.0f, 2.0f, 3.0f).DistToSqr(Vector3(4.0f, 5.0f, 6.0f)); + float distSqr = Vector3(1.0f, 2.0f, 3.0f).DistToSqr(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(distSqr, 27.0f); } TEST_F(UnitTestVector3, DotProduct) { - constexpr float dot = Vector3(1.0f, 2.0f, 3.0f).Dot(Vector3(4.0f, 5.0f, 6.0f)); + float dot = Vector3(1.0f, 2.0f, 3.0f).Dot(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(dot, 32.0f); } TEST_F(UnitTestVector3, LengthSqr) { - constexpr float lengthSqr = Vector3(1.0f, 2.0f, 3.0f).LengthSqr(); + float lengthSqr = Vector3(1.0f, 2.0f, 3.0f).LengthSqr(); EXPECT_FLOAT_EQ(lengthSqr, 14.0f); } TEST_F(UnitTestVector3, Abs) { - constexpr Vector3 v3 = Vector3(-1.0f, -2.0f, -3.0f).Abs(); + Vector3 v3 = Vector3(-1.0f, -2.0f, -3.0f); + v3.Abs(); EXPECT_FLOAT_EQ(v3.x, 1.0f); EXPECT_FLOAT_EQ(v3.y, 2.0f); EXPECT_FLOAT_EQ(v3.z, 3.0f); @@ -187,25 +191,203 @@ TEST_F(UnitTestVector3, Abs) TEST_F(UnitTestVector3, Sum) { - constexpr float sum = Vector3(1.0f, 2.0f, 3.0f).Sum(); + float sum = Vector3(1.0f, 2.0f, 3.0f).Sum(); EXPECT_FLOAT_EQ(sum, 6.0f); } TEST_F(UnitTestVector3, Sum2D) { - constexpr float sum2D = Vector3(1.0f, 2.0f, 3.0f).Sum2D(); + float sum2D = Vector3(1.0f, 2.0f, 3.0f).Sum2D(); EXPECT_FLOAT_EQ(sum2D, 3.0f); } TEST_F(UnitTestVector3, CrossProduct) { - constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f).Cross(Vector3(4.0f, 5.0f, 6.0f)); + Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f).Cross(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(v3.x, -3.0f); EXPECT_FLOAT_EQ(v3.y, 6.0f); EXPECT_FLOAT_EQ(v3.z, -3.0f); } -// Test constexpr with static_assert +// New tests to cover corner cases + +// Test operations with zero vectors +TEST_F(UnitTestVector3, Addition_WithZeroVector) +{ + Vector3 v_zero(0.0f, 0.0f, 0.0f); + Vector3 result = v1 + v_zero; + EXPECT_FLOAT_EQ(result.x, v1.x); + EXPECT_FLOAT_EQ(result.y, v1.y); + EXPECT_FLOAT_EQ(result.z, v1.z); +} + +TEST_F(UnitTestVector3, Subtraction_WithZeroVector) +{ + Vector3 v_zero(0.0f, 0.0f, 0.0f); + Vector3 result = v1 - v_zero; + EXPECT_FLOAT_EQ(result.x, v1.x); + EXPECT_FLOAT_EQ(result.y, v1.y); + EXPECT_FLOAT_EQ(result.z, v1.z); +} + +TEST_F(UnitTestVector3, Multiplication_WithZeroVector) +{ + Vector3 v_zero(0.0f, 0.0f, 0.0f); + Vector3 result = v1 * v_zero; + EXPECT_FLOAT_EQ(result.x, 0.0f); + EXPECT_FLOAT_EQ(result.y, 0.0f); + EXPECT_FLOAT_EQ(result.z, 0.0f); +} + +TEST_F(UnitTestVector3, Division_ByZeroVector) +{ + Vector3 v_zero(0.0f, 0.0f, 0.0f); + Vector3 result = v1 / v_zero; + EXPECT_TRUE(std::isinf(result.x) || std::isnan(result.x)); + EXPECT_TRUE(std::isinf(result.y) || std::isnan(result.y)); + EXPECT_TRUE(std::isinf(result.z) || std::isnan(result.z)); +} + +TEST_F(UnitTestVector3, Division_ByZeroScalar) +{ + float zero = 0.0f; + Vector3 result = v1 / zero; + EXPECT_TRUE(std::isinf(result.x) || std::isnan(result.x)); + EXPECT_TRUE(std::isinf(result.y) || std::isnan(result.y)); + EXPECT_TRUE(std::isinf(result.z) || std::isnan(result.z)); +} + +// Test operations with infinity +TEST_F(UnitTestVector3, Addition_WithInfinity) +{ + Vector3 v_inf(INFINITY, INFINITY, INFINITY); + Vector3 result = v1 + v_inf; + EXPECT_TRUE(std::isinf(result.x)); + EXPECT_TRUE(std::isinf(result.y)); + EXPECT_TRUE(std::isinf(result.z)); +} + +TEST_F(UnitTestVector3, Subtraction_WithInfinity) +{ + Vector3 v_inf(INFINITY, INFINITY, INFINITY); + Vector3 result = v1 - v_inf; + EXPECT_TRUE(std::isinf(result.x)); + EXPECT_TRUE(std::isinf(result.y)); + EXPECT_TRUE(std::isinf(result.z)); +} + +// Test operations with NaN +TEST_F(UnitTestVector3, Multiplication_WithNaN) +{ + Vector3 v_nan(NAN, NAN, NAN); + Vector3 result = v1 * v_nan; + EXPECT_TRUE(std::isnan(result.x)); + EXPECT_TRUE(std::isnan(result.y)); + EXPECT_TRUE(std::isnan(result.z)); +} + +TEST_F(UnitTestVector3, Division_WithNaN) +{ + Vector3 v_nan(NAN, NAN, NAN); + Vector3 result = v1 / v_nan; + EXPECT_TRUE(std::isnan(result.x)); + EXPECT_TRUE(std::isnan(result.y)); + EXPECT_TRUE(std::isnan(result.z)); +} + +// Test Length, Length2D, and Normalized +TEST_F(UnitTestVector3, Length) +{ + float length = v1.Length(); + EXPECT_FLOAT_EQ(length, std::sqrt(14.0f)); +} + +TEST_F(UnitTestVector3, Length_ZeroVector) +{ + Vector3 v_zero(0.0f, 0.0f, 0.0f); + float length = v_zero.Length(); + EXPECT_FLOAT_EQ(length, 0.0f); +} + +TEST_F(UnitTestVector3, Length_LargeValues) +{ + Vector3 v_large(FLT_MAX, FLT_MAX, FLT_MAX); + float length = v_large.Length(); + EXPECT_TRUE(std::isinf(length)); +} + +TEST_F(UnitTestVector3, Length2D) +{ + float length2D = v1.Length2D(); + EXPECT_FLOAT_EQ(length2D, std::sqrt(5.0f)); +} + +TEST_F(UnitTestVector3, Normalized) +{ + Vector3 v_norm = v1.Normalized(); + float length = v_norm.Length(); + EXPECT_NEAR(length, 1.0f, 0.0001f); +} + +TEST_F(UnitTestVector3, Normalized_ZeroVector) +{ + Vector3 v_zero(0.0f, 0.0f, 0.0f); + Vector3 v_norm = v_zero.Normalized(); + EXPECT_FLOAT_EQ(v_norm.x, 0.0f); + EXPECT_FLOAT_EQ(v_norm.y, 0.0f); + EXPECT_FLOAT_EQ(v_norm.z, 0.0f); +} + +// Test Cross Product edge cases +TEST_F(UnitTestVector3, CrossProduct_ParallelVectors) +{ + Vector3 v_a(1.0f, 2.0f, 3.0f); + Vector3 v_b = v_a * 2.0f; // Parallel to v_a + Vector3 cross = v_a.Cross(v_b); + EXPECT_FLOAT_EQ(cross.x, 0.0f); + EXPECT_FLOAT_EQ(cross.y, 0.0f); + EXPECT_FLOAT_EQ(cross.z, 0.0f); +} + +TEST_F(UnitTestVector3, CrossProduct_OrthogonalVectors) +{ + Vector3 v_a(1.0f, 0.0f, 0.0f); + Vector3 v_b(0.0f, 1.0f, 0.0f); + Vector3 cross = v_a.Cross(v_b); + EXPECT_FLOAT_EQ(cross.x, 0.0f); + EXPECT_FLOAT_EQ(cross.y, 0.0f); + EXPECT_FLOAT_EQ(cross.z, 1.0f); +} + +// Test negative values +TEST_F(UnitTestVector3, Addition_NegativeValues) +{ + Vector3 v_neg(-1.0f, -2.0f, -3.0f); + Vector3 result = v1 + v_neg; + EXPECT_FLOAT_EQ(result.x, 0.0f); + EXPECT_FLOAT_EQ(result.y, 0.0f); + EXPECT_FLOAT_EQ(result.z, 0.0f); +} + +TEST_F(UnitTestVector3, Subtraction_NegativeValues) +{ + Vector3 v_neg(-1.0f, -2.0f, -3.0f); + Vector3 result = v1 - v_neg; + EXPECT_FLOAT_EQ(result.x, 2.0f); + EXPECT_FLOAT_EQ(result.y, 4.0f); + EXPECT_FLOAT_EQ(result.z, 6.0f); +} + +// Test AsTuple method +TEST_F(UnitTestVector3, AsTuple) +{ + auto tuple = v1.AsTuple(); + EXPECT_FLOAT_EQ(std::get<0>(tuple), v1.x); + EXPECT_FLOAT_EQ(std::get<1>(tuple), v1.y); + EXPECT_FLOAT_EQ(std::get<2>(tuple), v1.z); +} + +// Static assertions (compile-time checks) static_assert(Vector3(1.0f, 2.0f, 3.0f).LengthSqr() == 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).DistToSqr(Vector3(1.0f, 2.0f, 3.0f)) == 27.0f, "DistToSqr should be 27"); From 01a2a3d055a0296f729501600dd7c264d32257e1 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 17 Sep 2024 09:31:30 -0700 Subject: [PATCH 078/795] updated presets --- CMakePresets.json | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 782b622c..d67436b9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -18,41 +18,17 @@ } }, { - "name": "x64-debug", - "displayName": "x64 Debug", + "name": "windows-debug", + "displayName": "Debug", "inherits": "windows-base", - "architecture": { - "value": "x64", - "strategy": "external" - }, "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } }, { - "name": "x64-release", - "displayName": "x64 Release", - "inherits": "x64-debug", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release" - } - }, - { - "name": "x86-debug", - "displayName": "x86 Debug", + "name": "windows-release", + "displayName": "Release", "inherits": "windows-base", - "architecture": { - "value": "x86", - "strategy": "external" - }, - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Debug" - } - }, - { - "name": "x86-release", - "displayName": "x86 Release", - "inherits": "x86-debug", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } From ee685745420f87259dc3a310d701c6a27c469543 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 23 Sep 2024 10:32:18 -0700 Subject: [PATCH 079/795] updated readme --- readme.md | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/readme.md b/readme.md index 94f20386..8120ed80 100644 --- a/readme.md +++ b/readme.md @@ -15,6 +15,8 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at - **Efficiency**: Optimized for performance, ensuring quick computations. - **Versatility**: Includes a wide array of mathematical functions and algorithms. - **Ease of Use**: Simplified interface for convenient integration into various projects. +- **Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot. +- **3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline. ## Getting Started ### Prerequisites @@ -39,22 +41,13 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at ## Usage Simple world to screen function ```c++ -std::optional WorldToScreen(omath::Vector3 worldPosition, float width, float height) - { - auto projected = (GetViewProjectionMatrix() * worldPosition).transpose(); +TEST(UnitTestProjection, IsPointOnScreen) +{ + const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.1f, 500.f); - projected /= projected.at(0, 3); - - const auto out = projected * omath::matrix::to_screen_matrix(width, - height); - - if (out.at(0, 2) <= 0.f) - return std::nullopt; - auto final = omath::Vector3(out.at(0, 0), - out.at(0, 1), - out.at(0, 3)); - return {final}; - } + const auto proj = camera.WorldToScreen({100, 0, 15}); + EXPECT_TRUE(proj.has_value()); +} ``` ## Contributing Contributions to `omath` are welcome! Please read `CONTRIBUTING.md` for details on our code of conduct and the process for submitting pull requests. From 46e7a82f1d9d1a36b65878d1e549781da2e54d74 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 23 Sep 2024 10:46:32 -0700 Subject: [PATCH 080/795] added const and constexpr refacotred build options --- CMakeLists.txt | 18 ++++-- include/omath/Vector3.h | 2 +- tests/UnitTestMatrix.cpp | 14 ++--- tests/UnitTestVector2.cpp | 40 ++++++------- tests/UnitTestVector3.cpp | 114 +++++++++++++++++++------------------- 5 files changed, 97 insertions(+), 91 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a11eaa8..ad7e13de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,20 +5,26 @@ project(omath) set(CMAKE_CXX_STANDARD 26) -option(BUILD_TESTS "Build unit tests" ON) -option(THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to threat them as errors" ON) -add_library(omath STATIC source/Vector3.cpp) +option(OMATH_BUILD_TESTS "Build unit tests" ON) +option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to threat them as errors" ON) +option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so\\.dll." OFF) + +if (OMATH_BUILD_AS_SHARED_LIBRARY) + add_library(omath SHARED source/Vector3.cpp) +else() + add_library(omath STATIC source/Vector3.cpp) +endif() add_subdirectory(source) add_subdirectory(extlibs) -if(BUILD_TESTS) +if(OMATH_BUILD_TESTS) add_subdirectory(tests) endif () -if (WIN32 AND THREAT_WARNING_AS_ERROR) +if (WIN32 AND OMATH_THREAT_WARNING_AS_ERROR) target_compile_options(omath PRIVATE /W4 /WX) -elseif(UNIX AND THREAT_WARNING_AS_ERROR) +elseif(UNIX AND OMATH_THREAT_WARNING_AS_ERROR) target_compile_options(omath PRIVATE -Wall -Wextra -Wpedantic) endif() diff --git a/include/omath/Vector3.h b/include/omath/Vector3.h index 0ec441b5..18e2ffd1 100644 --- a/include/omath/Vector3.h +++ b/include/omath/Vector3.h @@ -146,7 +146,7 @@ namespace omath return {x * v.x, y * v.y, z * v.z}; } - [[nodiscard]] constexpr Vector3 operator/(float fl) const + [[nodiscard]] constexpr Vector3 operator/(const float fl) const { return {x / fl, y / fl, z / fl}; } diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index fc48e9b7..7450bc61 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -24,7 +24,7 @@ class UnitTestMatrix : public ::testing::Test // Test constructors TEST_F(UnitTestMatrix, Constructor_Size) { - Matrix m(3, 3); + const Matrix m(3, 3); EXPECT_EQ(m.RowCount(), 3); EXPECT_EQ(m.ColumnsCount(), 3); } @@ -90,19 +90,19 @@ TEST_F(UnitTestMatrix, Transpose) TEST_F(UnitTestMatrix, Determinant) { - float det = m2.Determinant(); + const float det = m2.Determinant(); EXPECT_FLOAT_EQ(det, -2.0f); } TEST_F(UnitTestMatrix, Minor) { - float minor = m2.Minor(0, 0); + const float minor = m2.Minor(0, 0); EXPECT_FLOAT_EQ(minor, 4.0f); } TEST_F(UnitTestMatrix, AlgComplement) { - float algComp = m2.AlgComplement(0, 0); + const float algComp = m2.AlgComplement(0, 0); EXPECT_FLOAT_EQ(algComp, 4.0f); } @@ -116,7 +116,7 @@ TEST_F(UnitTestMatrix, Strip) TEST_F(UnitTestMatrix, ProjectionMatrix) { - Matrix proj = Matrix::ProjectionMatrix(45.0f, 1.33f, 0.1f, 100.0f); + const Matrix proj = Matrix::ProjectionMatrix(45.0f, 1.33f, 0.1f, 100.0f); EXPECT_EQ(proj.RowCount(), 4); EXPECT_EQ(proj.ColumnsCount(), 4); // Further checks on projection matrix elements could be added @@ -132,7 +132,7 @@ TEST_F(UnitTestMatrix, Set) TEST_F(UnitTestMatrix, Sum) { - float sum = m2.Sum(); + const float sum = m2.Sum(); EXPECT_FLOAT_EQ(sum, 10.0f); } @@ -145,7 +145,7 @@ TEST_F(UnitTestMatrix, Clear) TEST_F(UnitTestMatrix, ToString) { - std::string str = m2.ToSrtring(); + const std::string str = m2.ToSrtring(); EXPECT_FALSE(str.empty()); } diff --git a/tests/UnitTestVector2.cpp b/tests/UnitTestVector2.cpp index eb4051c0..dc74b298 100644 --- a/tests/UnitTestVector2.cpp +++ b/tests/UnitTestVector2.cpp @@ -198,14 +198,14 @@ TEST_F(UnitTestVector2, Length) TEST_F(UnitTestVector2, Length_ZeroVector) { - Vector2 v_zero(0.0f, 0.0f); + constexpr Vector2 v_zero(0.0f, 0.0f); const float length = v_zero.Length(); EXPECT_FLOAT_EQ(length, 0.0f); } TEST_F(UnitTestVector2, Length_LargeValues) { - Vector2 v_large(FLT_MAX, FLT_MAX); + constexpr Vector2 v_large(FLT_MAX, FLT_MAX); const float length = v_large.Length(); EXPECT_TRUE(std::isinf(length)); } @@ -261,8 +261,8 @@ TEST_F(UnitTestVector2, Normalized) TEST_F(UnitTestVector2, Normalized_ZeroVector) { - Vector2 v_zero(0.0f, 0.0f); - Vector2 v_norm = v_zero.Normalized(); + constexpr Vector2 v_zero(0.0f, 0.0f); + const Vector2 v_norm = v_zero.Normalized(); EXPECT_FLOAT_EQ(v_norm.x, 0.0f); EXPECT_FLOAT_EQ(v_norm.y, 0.0f); } @@ -270,7 +270,7 @@ TEST_F(UnitTestVector2, Normalized_ZeroVector) // Test AsTuple method TEST_F(UnitTestVector2, AsTuple) { - auto tuple = v1.AsTuple(); + const auto tuple = v1.AsTuple(); EXPECT_FLOAT_EQ(std::get<0>(tuple), v1.x); EXPECT_FLOAT_EQ(std::get<1>(tuple), v1.y); } @@ -278,9 +278,9 @@ TEST_F(UnitTestVector2, AsTuple) // Test division by zero TEST_F(UnitTestVector2, DivisionOperator_DivideByZero) { - Vector2 v(1.0f, 2.0f); - float zero = 0.0f; - Vector2 result = v / zero; + constexpr Vector2 v(1.0f, 2.0f); + constexpr float zero = 0.0f; + const Vector2 result = v / zero; EXPECT_TRUE(std::isinf(result.x) || std::isnan(result.x)); EXPECT_TRUE(std::isinf(result.y) || std::isnan(result.y)); } @@ -288,7 +288,7 @@ TEST_F(UnitTestVector2, DivisionOperator_DivideByZero) TEST_F(UnitTestVector2, DivisionAssignmentOperator_DivideByZero) { Vector2 v(1.0f, 2.0f); - float zero = 0.0f; + constexpr float zero = 0.0f; v /= zero; EXPECT_TRUE(std::isinf(v.x) || std::isnan(v.x)); EXPECT_TRUE(std::isinf(v.y) || std::isnan(v.y)); @@ -297,7 +297,7 @@ TEST_F(UnitTestVector2, DivisionAssignmentOperator_DivideByZero) TEST_F(UnitTestVector2, DivisionAssignmentOperator_VectorWithZero) { Vector2 v(1.0f, 2.0f); - Vector2 v_zero(0.0f, 1.0f); + constexpr Vector2 v_zero(0.0f, 1.0f); v /= v_zero; EXPECT_TRUE(std::isinf(v.x) || std::isnan(v.x)); EXPECT_FLOAT_EQ(v.y, 2.0f / 1.0f); @@ -306,16 +306,16 @@ TEST_F(UnitTestVector2, DivisionAssignmentOperator_VectorWithZero) // Test operations with infinity and NaN TEST_F(UnitTestVector2, Operator_WithInfinity) { - Vector2 v_inf(INFINITY, INFINITY); - Vector2 result = v1 + v_inf; + constexpr Vector2 v_inf(INFINITY, INFINITY); + const Vector2 result = v1 + v_inf; EXPECT_TRUE(std::isinf(result.x)); EXPECT_TRUE(std::isinf(result.y)); } TEST_F(UnitTestVector2, Operator_WithNaN) { - Vector2 v_nan(NAN, NAN); - Vector2 result = v1 + v_nan; + constexpr Vector2 v_nan(NAN, NAN); + const Vector2 result = v1 + v_nan; EXPECT_TRUE(std::isnan(result.x)); EXPECT_TRUE(std::isnan(result.y)); } @@ -323,16 +323,16 @@ TEST_F(UnitTestVector2, Operator_WithNaN) // Test negative values in arithmetic operations TEST_F(UnitTestVector2, AdditionOperator_NegativeValues) { - Vector2 v_neg(-1.0f, -2.0f); - Vector2 result = v1 + v_neg; + constexpr Vector2 v_neg(-1.0f, -2.0f); + const Vector2 result = v1 + v_neg; EXPECT_FLOAT_EQ(result.x, 0.0f); EXPECT_FLOAT_EQ(result.y, 0.0f); } TEST_F(UnitTestVector2, SubtractionOperator_NegativeValues) { - Vector2 v_neg(-1.0f, -2.0f); - Vector2 result = v1 - v_neg; + constexpr Vector2 v_neg(-1.0f, -2.0f); + const Vector2 result = v1 - v_neg; EXPECT_FLOAT_EQ(result.x, 2.0f); EXPECT_FLOAT_EQ(result.y, 4.0f); } @@ -340,8 +340,8 @@ TEST_F(UnitTestVector2, SubtractionOperator_NegativeValues) // Test negation of zero vector TEST_F(UnitTestVector2, NegationOperator_ZeroVector) { - Vector2 v_zero(0.0f, 0.0f); - Vector2 result = -v_zero; + constexpr Vector2 v_zero(0.0f, 0.0f); + constexpr Vector2 result = -v_zero; EXPECT_FLOAT_EQ(result.x, -0.0f); EXPECT_FLOAT_EQ(result.y, -0.0f); } diff --git a/tests/UnitTestVector3.cpp b/tests/UnitTestVector3.cpp index 282bdcef..67625bba 100644 --- a/tests/UnitTestVector3.cpp +++ b/tests/UnitTestVector3.cpp @@ -26,7 +26,7 @@ class UnitTestVector3 : public ::testing::Test // Test constructor and default values TEST_F(UnitTestVector3, Constructor_Default) { - Vector3 v; + constexpr Vector3 v; EXPECT_FLOAT_EQ(v.x, 0.0f); EXPECT_FLOAT_EQ(v.y, 0.0f); EXPECT_FLOAT_EQ(v.z, 0.0f); @@ -34,7 +34,7 @@ TEST_F(UnitTestVector3, Constructor_Default) TEST_F(UnitTestVector3, Constructor_Values) { - Vector3 v(1.0f, 2.0f, 3.0f); + constexpr Vector3 v(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v.x, 1.0f); EXPECT_FLOAT_EQ(v.y, 2.0f); EXPECT_FLOAT_EQ(v.z, 3.0f); @@ -43,14 +43,14 @@ TEST_F(UnitTestVector3, Constructor_Values) // Test equality operators TEST_F(UnitTestVector3, EqualityOperator) { - Vector3 v3(1.0f, 2.0f, 3.0f); + constexpr Vector3 v3(1.0f, 2.0f, 3.0f); EXPECT_TRUE(v1 == v3); EXPECT_FALSE(v1 == v2); } TEST_F(UnitTestVector3, InequalityOperator) { - Vector3 v3(1.0f, 2.0f, 3.0f); + constexpr Vector3 v3(1.0f, 2.0f, 3.0f); EXPECT_FALSE(v1 != v3); EXPECT_TRUE(v1 != v2); } @@ -58,7 +58,7 @@ TEST_F(UnitTestVector3, InequalityOperator) // Test arithmetic operators TEST_F(UnitTestVector3, AdditionOperator) { - Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) + Vector3(4.0f, 5.0f, 6.0f); + constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) + Vector3(4.0f, 5.0f, 6.0f); EXPECT_FLOAT_EQ(v3.x, 5.0f); EXPECT_FLOAT_EQ(v3.y, 7.0f); EXPECT_FLOAT_EQ(v3.z, 9.0f); @@ -66,7 +66,7 @@ TEST_F(UnitTestVector3, AdditionOperator) TEST_F(UnitTestVector3, SubtractionOperator) { - Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) - Vector3(1.0f, 2.0f, 3.0f); + constexpr Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) - Vector3(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v3.x, 3.0f); EXPECT_FLOAT_EQ(v3.y, 3.0f); EXPECT_FLOAT_EQ(v3.z, 3.0f); @@ -74,7 +74,7 @@ TEST_F(UnitTestVector3, SubtractionOperator) TEST_F(UnitTestVector3, MultiplicationOperator_Scalar) { - Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) * 2.0f; + constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) * 2.0f; EXPECT_FLOAT_EQ(v3.x, 2.0f); EXPECT_FLOAT_EQ(v3.y, 4.0f); EXPECT_FLOAT_EQ(v3.z, 6.0f); @@ -82,7 +82,7 @@ TEST_F(UnitTestVector3, MultiplicationOperator_Scalar) TEST_F(UnitTestVector3, MultiplicationOperator_Vector) { - Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f) * Vector3(4.0f, 5.0f, 6.0f); + constexpr auto v3 = Vector3(1.0f, 2.0f, 3.0f) * Vector3(4.0f, 5.0f, 6.0f); EXPECT_FLOAT_EQ(v3.x, 4.0f); EXPECT_FLOAT_EQ(v3.y, 10.0f); EXPECT_FLOAT_EQ(v3.z, 18.0f); @@ -90,7 +90,7 @@ TEST_F(UnitTestVector3, MultiplicationOperator_Vector) TEST_F(UnitTestVector3, DivisionOperator_Scalar) { - Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) / 2.0f; + constexpr auto v3 = Vector3(4.0f, 5.0f, 6.0f) / 2.0f; EXPECT_FLOAT_EQ(v3.x, 2.0f); EXPECT_FLOAT_EQ(v3.y, 2.5f); EXPECT_FLOAT_EQ(v3.z, 3.0f); @@ -98,7 +98,7 @@ TEST_F(UnitTestVector3, DivisionOperator_Scalar) TEST_F(UnitTestVector3, DivisionOperator_Vector) { - Vector3 v3 = Vector3(4.0f, 5.0f, 6.0f) / Vector3(1.0f, 2.0f, 3.0f); + constexpr auto v3 = Vector3(4.0f, 5.0f, 6.0f) / Vector3(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v3.x, 4.0f); EXPECT_FLOAT_EQ(v3.y, 2.5f); EXPECT_FLOAT_EQ(v3.z, 2.0f); @@ -155,7 +155,7 @@ TEST_F(UnitTestVector3, DivisionAssignmentOperator_Vector) TEST_F(UnitTestVector3, NegationOperator) { - Vector3 v3 = -Vector3(1.0f, 2.0f, 3.0f); + constexpr auto v3 = -Vector3(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v3.x, -1.0f); EXPECT_FLOAT_EQ(v3.y, -2.0f); EXPECT_FLOAT_EQ(v3.z, -3.0f); @@ -164,25 +164,25 @@ TEST_F(UnitTestVector3, NegationOperator) // Test other member functions TEST_F(UnitTestVector3, DistToSqr) { - float distSqr = Vector3(1.0f, 2.0f, 3.0f).DistToSqr(Vector3(4.0f, 5.0f, 6.0f)); + constexpr auto distSqr = Vector3(1.0f, 2.0f, 3.0f).DistToSqr(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(distSqr, 27.0f); } TEST_F(UnitTestVector3, DotProduct) { - float dot = Vector3(1.0f, 2.0f, 3.0f).Dot(Vector3(4.0f, 5.0f, 6.0f)); + constexpr auto dot = Vector3(1.0f, 2.0f, 3.0f).Dot(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(dot, 32.0f); } TEST_F(UnitTestVector3, LengthSqr) { - float lengthSqr = Vector3(1.0f, 2.0f, 3.0f).LengthSqr(); + constexpr auto lengthSqr = Vector3(1.0f, 2.0f, 3.0f).LengthSqr(); EXPECT_FLOAT_EQ(lengthSqr, 14.0f); } TEST_F(UnitTestVector3, Abs) { - Vector3 v3 = Vector3(-1.0f, -2.0f, -3.0f); + auto v3 = Vector3(-1.0f, -2.0f, -3.0f); v3.Abs(); EXPECT_FLOAT_EQ(v3.x, 1.0f); EXPECT_FLOAT_EQ(v3.y, 2.0f); @@ -191,19 +191,19 @@ TEST_F(UnitTestVector3, Abs) TEST_F(UnitTestVector3, Sum) { - float sum = Vector3(1.0f, 2.0f, 3.0f).Sum(); + constexpr auto sum = Vector3(1.0f, 2.0f, 3.0f).Sum(); EXPECT_FLOAT_EQ(sum, 6.0f); } TEST_F(UnitTestVector3, Sum2D) { - float sum2D = Vector3(1.0f, 2.0f, 3.0f).Sum2D(); + constexpr auto sum2D = Vector3(1.0f, 2.0f, 3.0f).Sum2D(); EXPECT_FLOAT_EQ(sum2D, 3.0f); } TEST_F(UnitTestVector3, CrossProduct) { - Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f).Cross(Vector3(4.0f, 5.0f, 6.0f)); + constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f).Cross(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(v3.x, -3.0f); EXPECT_FLOAT_EQ(v3.y, 6.0f); EXPECT_FLOAT_EQ(v3.z, -3.0f); @@ -214,8 +214,8 @@ TEST_F(UnitTestVector3, CrossProduct) // Test operations with zero vectors TEST_F(UnitTestVector3, Addition_WithZeroVector) { - Vector3 v_zero(0.0f, 0.0f, 0.0f); - Vector3 result = v1 + v_zero; + constexpr Vector3 v_zero(0.0f, 0.0f, 0.0f); + const Vector3 result = v1 + v_zero; EXPECT_FLOAT_EQ(result.x, v1.x); EXPECT_FLOAT_EQ(result.y, v1.y); EXPECT_FLOAT_EQ(result.z, v1.z); @@ -223,8 +223,8 @@ TEST_F(UnitTestVector3, Addition_WithZeroVector) TEST_F(UnitTestVector3, Subtraction_WithZeroVector) { - Vector3 v_zero(0.0f, 0.0f, 0.0f); - Vector3 result = v1 - v_zero; + constexpr Vector3 v_zero(0.0f, 0.0f, 0.0f); + const Vector3 result = v1 - v_zero; EXPECT_FLOAT_EQ(result.x, v1.x); EXPECT_FLOAT_EQ(result.y, v1.y); EXPECT_FLOAT_EQ(result.z, v1.z); @@ -232,8 +232,8 @@ TEST_F(UnitTestVector3, Subtraction_WithZeroVector) TEST_F(UnitTestVector3, Multiplication_WithZeroVector) { - Vector3 v_zero(0.0f, 0.0f, 0.0f); - Vector3 result = v1 * v_zero; + constexpr Vector3 v_zero(0.0f, 0.0f, 0.0f); + const Vector3 result = v1 * v_zero; EXPECT_FLOAT_EQ(result.x, 0.0f); EXPECT_FLOAT_EQ(result.y, 0.0f); EXPECT_FLOAT_EQ(result.z, 0.0f); @@ -241,8 +241,8 @@ TEST_F(UnitTestVector3, Multiplication_WithZeroVector) TEST_F(UnitTestVector3, Division_ByZeroVector) { - Vector3 v_zero(0.0f, 0.0f, 0.0f); - Vector3 result = v1 / v_zero; + constexpr Vector3 v_zero(0.0f, 0.0f, 0.0f); + const Vector3 result = v1 / v_zero; EXPECT_TRUE(std::isinf(result.x) || std::isnan(result.x)); EXPECT_TRUE(std::isinf(result.y) || std::isnan(result.y)); EXPECT_TRUE(std::isinf(result.z) || std::isnan(result.z)); @@ -250,8 +250,8 @@ TEST_F(UnitTestVector3, Division_ByZeroVector) TEST_F(UnitTestVector3, Division_ByZeroScalar) { - float zero = 0.0f; - Vector3 result = v1 / zero; + constexpr float zero = 0.0f; + const Vector3 result = v1 / zero; EXPECT_TRUE(std::isinf(result.x) || std::isnan(result.x)); EXPECT_TRUE(std::isinf(result.y) || std::isnan(result.y)); EXPECT_TRUE(std::isinf(result.z) || std::isnan(result.z)); @@ -260,8 +260,8 @@ TEST_F(UnitTestVector3, Division_ByZeroScalar) // Test operations with infinity TEST_F(UnitTestVector3, Addition_WithInfinity) { - Vector3 v_inf(INFINITY, INFINITY, INFINITY); - Vector3 result = v1 + v_inf; + constexpr Vector3 v_inf(INFINITY, INFINITY, INFINITY); + const Vector3 result = v1 + v_inf; EXPECT_TRUE(std::isinf(result.x)); EXPECT_TRUE(std::isinf(result.y)); EXPECT_TRUE(std::isinf(result.z)); @@ -269,8 +269,8 @@ TEST_F(UnitTestVector3, Addition_WithInfinity) TEST_F(UnitTestVector3, Subtraction_WithInfinity) { - Vector3 v_inf(INFINITY, INFINITY, INFINITY); - Vector3 result = v1 - v_inf; + constexpr Vector3 v_inf(INFINITY, INFINITY, INFINITY); + const Vector3 result = v1 - v_inf; EXPECT_TRUE(std::isinf(result.x)); EXPECT_TRUE(std::isinf(result.y)); EXPECT_TRUE(std::isinf(result.z)); @@ -279,8 +279,8 @@ TEST_F(UnitTestVector3, Subtraction_WithInfinity) // Test operations with NaN TEST_F(UnitTestVector3, Multiplication_WithNaN) { - Vector3 v_nan(NAN, NAN, NAN); - Vector3 result = v1 * v_nan; + constexpr Vector3 v_nan(NAN, NAN, NAN); + const Vector3 result = v1 * v_nan; EXPECT_TRUE(std::isnan(result.x)); EXPECT_TRUE(std::isnan(result.y)); EXPECT_TRUE(std::isnan(result.z)); @@ -288,8 +288,8 @@ TEST_F(UnitTestVector3, Multiplication_WithNaN) TEST_F(UnitTestVector3, Division_WithNaN) { - Vector3 v_nan(NAN, NAN, NAN); - Vector3 result = v1 / v_nan; + constexpr Vector3 v_nan(NAN, NAN, NAN); + const Vector3 result = v1 / v_nan; EXPECT_TRUE(std::isnan(result.x)); EXPECT_TRUE(std::isnan(result.y)); EXPECT_TRUE(std::isnan(result.z)); @@ -298,41 +298,41 @@ TEST_F(UnitTestVector3, Division_WithNaN) // Test Length, Length2D, and Normalized TEST_F(UnitTestVector3, Length) { - float length = v1.Length(); + const float length = v1.Length(); EXPECT_FLOAT_EQ(length, std::sqrt(14.0f)); } TEST_F(UnitTestVector3, Length_ZeroVector) { - Vector3 v_zero(0.0f, 0.0f, 0.0f); - float length = v_zero.Length(); + constexpr Vector3 v_zero(0.0f, 0.0f, 0.0f); + const float length = v_zero.Length(); EXPECT_FLOAT_EQ(length, 0.0f); } TEST_F(UnitTestVector3, Length_LargeValues) { - Vector3 v_large(FLT_MAX, FLT_MAX, FLT_MAX); - float length = v_large.Length(); + constexpr Vector3 v_large(FLT_MAX, FLT_MAX, FLT_MAX); + const float length = v_large.Length(); EXPECT_TRUE(std::isinf(length)); } TEST_F(UnitTestVector3, Length2D) { - float length2D = v1.Length2D(); + const float length2D = v1.Length2D(); EXPECT_FLOAT_EQ(length2D, std::sqrt(5.0f)); } TEST_F(UnitTestVector3, Normalized) { - Vector3 v_norm = v1.Normalized(); - float length = v_norm.Length(); + const Vector3 v_norm = v1.Normalized(); + const float length = v_norm.Length(); EXPECT_NEAR(length, 1.0f, 0.0001f); } TEST_F(UnitTestVector3, Normalized_ZeroVector) { - Vector3 v_zero(0.0f, 0.0f, 0.0f); - Vector3 v_norm = v_zero.Normalized(); + constexpr Vector3 v_zero(0.0f, 0.0f, 0.0f); + const Vector3 v_norm = v_zero.Normalized(); EXPECT_FLOAT_EQ(v_norm.x, 0.0f); EXPECT_FLOAT_EQ(v_norm.y, 0.0f); EXPECT_FLOAT_EQ(v_norm.z, 0.0f); @@ -341,9 +341,9 @@ TEST_F(UnitTestVector3, Normalized_ZeroVector) // Test Cross Product edge cases TEST_F(UnitTestVector3, CrossProduct_ParallelVectors) { - Vector3 v_a(1.0f, 2.0f, 3.0f); - Vector3 v_b = v_a * 2.0f; // Parallel to v_a - Vector3 cross = v_a.Cross(v_b); + constexpr Vector3 v_a(1.0f, 2.0f, 3.0f); + constexpr Vector3 v_b = v_a * 2.0f; // Parallel to v_a + constexpr Vector3 cross = v_a.Cross(v_b); EXPECT_FLOAT_EQ(cross.x, 0.0f); EXPECT_FLOAT_EQ(cross.y, 0.0f); EXPECT_FLOAT_EQ(cross.z, 0.0f); @@ -351,9 +351,9 @@ TEST_F(UnitTestVector3, CrossProduct_ParallelVectors) TEST_F(UnitTestVector3, CrossProduct_OrthogonalVectors) { - Vector3 v_a(1.0f, 0.0f, 0.0f); - Vector3 v_b(0.0f, 1.0f, 0.0f); - Vector3 cross = v_a.Cross(v_b); + constexpr Vector3 v_a(1.0f, 0.0f, 0.0f); + constexpr Vector3 v_b(0.0f, 1.0f, 0.0f); + constexpr Vector3 cross = v_a.Cross(v_b); EXPECT_FLOAT_EQ(cross.x, 0.0f); EXPECT_FLOAT_EQ(cross.y, 0.0f); EXPECT_FLOAT_EQ(cross.z, 1.0f); @@ -362,8 +362,8 @@ TEST_F(UnitTestVector3, CrossProduct_OrthogonalVectors) // Test negative values TEST_F(UnitTestVector3, Addition_NegativeValues) { - Vector3 v_neg(-1.0f, -2.0f, -3.0f); - Vector3 result = v1 + v_neg; + constexpr Vector3 v_neg(-1.0f, -2.0f, -3.0f); + const Vector3 result = v1 + v_neg; EXPECT_FLOAT_EQ(result.x, 0.0f); EXPECT_FLOAT_EQ(result.y, 0.0f); EXPECT_FLOAT_EQ(result.z, 0.0f); @@ -371,8 +371,8 @@ TEST_F(UnitTestVector3, Addition_NegativeValues) TEST_F(UnitTestVector3, Subtraction_NegativeValues) { - Vector3 v_neg(-1.0f, -2.0f, -3.0f); - Vector3 result = v1 - v_neg; + constexpr Vector3 v_neg(-1.0f, -2.0f, -3.0f); + const Vector3 result = v1 - v_neg; EXPECT_FLOAT_EQ(result.x, 2.0f); EXPECT_FLOAT_EQ(result.y, 4.0f); EXPECT_FLOAT_EQ(result.z, 6.0f); @@ -381,7 +381,7 @@ TEST_F(UnitTestVector3, Subtraction_NegativeValues) // Test AsTuple method TEST_F(UnitTestVector3, AsTuple) { - auto tuple = v1.AsTuple(); + const auto tuple = v1.AsTuple(); EXPECT_FLOAT_EQ(std::get<0>(tuple), v1.x); EXPECT_FLOAT_EQ(std::get<1>(tuple), v1.y); EXPECT_FLOAT_EQ(std::get<2>(tuple), v1.z); From 1594f3cc0ef14094ac96b604bd9698716743a95f Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 23 Sep 2024 11:01:21 -0700 Subject: [PATCH 081/795] updated readme --- readme.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/readme.md b/readme.md index 8120ed80..2e6f408c 100644 --- a/readme.md +++ b/readme.md @@ -49,6 +49,17 @@ TEST(UnitTestProjection, IsPointOnScreen) EXPECT_TRUE(proj.has_value()); } ``` + +
+ OMATH for making cheats + +With `omath/projection` module you can achieve simple ESP hack for powered by Source/Unreal/Unity engine games, like [Apex Legends](https://store.steampowered.com/app/1172470/Apex_Legends/). + +![banner](https://i.imgur.com/lcJrfcZ.png) + + +
+ ## Contributing Contributions to `omath` are welcome! Please read `CONTRIBUTING.md` for details on our code of conduct and the process for submitting pull requests. From d24867e18a69be90723eab67bc5875a4bab3434b Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 24 Sep 2024 08:30:10 -0700 Subject: [PATCH 082/795] added vector4 unit test --- tests/CMakeLists.txt | 3 +- tests/UnitTestVector4.cpp | 217 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 tests/UnitTestVector4.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 65c45d29..920fae4c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,7 +11,8 @@ add_executable(unit-tests UnitTestProjection.cpp UnitTestVector3.cpp UnitTestVector2.cpp - UnitTestColor.cpp) + UnitTestColor.cpp + UnitTestVector4.cpp) target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) diff --git a/tests/UnitTestVector4.cpp b/tests/UnitTestVector4.cpp new file mode 100644 index 00000000..f9c947f2 --- /dev/null +++ b/tests/UnitTestVector4.cpp @@ -0,0 +1,217 @@ +// +// Created by vlad on 9/24/2024. +// +// +// Vector4Test.cpp +// + +#include +#include +#include // For std::numeric_limits + +using namespace omath; + +class UnitTestVector4 : public ::testing::Test +{ +protected: + Vector4 v1; + Vector4 v2; + + void SetUp() override + { + v1 = Vector4(1.0f, 2.0f, 3.0f, 4.0f); + v2 = Vector4(4.0f, 5.0f, 6.0f, 7.0f); + } +}; + +// Test constructor and default values +TEST_F(UnitTestVector4, Constructor_Default) +{ + constexpr Vector4 v; + EXPECT_FLOAT_EQ(v.x, 0.0f); + EXPECT_FLOAT_EQ(v.y, 0.0f); + EXPECT_FLOAT_EQ(v.z, 0.0f); + EXPECT_FLOAT_EQ(v.w, 0.0f); +} + +TEST_F(UnitTestVector4, Constructor_Values) +{ + constexpr Vector4 v(1.0f, 2.0f, 3.0f, 4.0f); + EXPECT_FLOAT_EQ(v.x, 1.0f); + EXPECT_FLOAT_EQ(v.y, 2.0f); + EXPECT_FLOAT_EQ(v.z, 3.0f); + EXPECT_FLOAT_EQ(v.w, 4.0f); +} + +// Test equality operators +TEST_F(UnitTestVector4, EqualityOperator) +{ + constexpr Vector4 v3(1.0f, 2.0f, 3.0f, 4.0f); + EXPECT_TRUE(v1 == v3); + EXPECT_FALSE(v1 == v2); +} + +TEST_F(UnitTestVector4, InequalityOperator) +{ + constexpr Vector4 v3(1.0f, 2.0f, 3.0f, 4.0f); + EXPECT_FALSE(v1 != v3); + EXPECT_TRUE(v1 != v2); +} + +// Test arithmetic operators +TEST_F(UnitTestVector4, AdditionOperator) +{ + constexpr Vector4 v3 = Vector4(1.0f, 2.0f, 3.0f, 4.0f) + Vector4(4.0f, 5.0f, 6.0f, 7.0f); + EXPECT_FLOAT_EQ(v3.x, 5.0f); + EXPECT_FLOAT_EQ(v3.y, 7.0f); + EXPECT_FLOAT_EQ(v3.z, 9.0f); + EXPECT_FLOAT_EQ(v3.w, 11.0f); +} + +TEST_F(UnitTestVector4, SubtractionOperator) +{ + constexpr Vector4 v3 = Vector4(4.0f, 5.0f, 6.0f, 7.0f) - Vector4(1.0f, 2.0f, 3.0f, 4.0f); + EXPECT_FLOAT_EQ(v3.x, 3.0f); + EXPECT_FLOAT_EQ(v3.y, 3.0f); + EXPECT_FLOAT_EQ(v3.z, 3.0f); + EXPECT_FLOAT_EQ(v3.w, 3.0f); +} + +TEST_F(UnitTestVector4, MultiplicationOperator_Scalar) +{ + constexpr Vector4 v3 = Vector4(1.0f, 2.0f, 3.0f, 4.0f) * 2.0f; + EXPECT_FLOAT_EQ(v3.x, 2.0f); + EXPECT_FLOAT_EQ(v3.y, 4.0f); + EXPECT_FLOAT_EQ(v3.z, 6.0f); + EXPECT_FLOAT_EQ(v3.w, 8.0f); +} + +TEST_F(UnitTestVector4, MultiplicationOperator_Vector) +{ + constexpr Vector4 v3 = Vector4(1.0f, 2.0f, 3.0f, 4.0f) * Vector4(4.0f, 5.0f, 6.0f, 7.0f); + EXPECT_FLOAT_EQ(v3.x, 4.0f); + EXPECT_FLOAT_EQ(v3.y, 10.0f); + EXPECT_FLOAT_EQ(v3.z, 18.0f); + EXPECT_FLOAT_EQ(v3.w, 28.0f); +} + +TEST_F(UnitTestVector4, DivisionOperator_Scalar) +{ + constexpr Vector4 v3 = Vector4(4.0f, 5.0f, 6.0f, 7.0f) / 2.0f; + EXPECT_FLOAT_EQ(v3.x, 2.0f); + EXPECT_FLOAT_EQ(v3.y, 2.5f); + EXPECT_FLOAT_EQ(v3.z, 3.0f); + EXPECT_FLOAT_EQ(v3.w, 3.5f); +} + +TEST_F(UnitTestVector4, DivisionOperator_Vector) +{ + constexpr Vector4 v3 = Vector4(4.0f, 5.0f, 6.0f, 7.0f) / Vector4(1.0f, 2.0f, 3.0f, 4.0f); + EXPECT_FLOAT_EQ(v3.x, 4.0f); + EXPECT_FLOAT_EQ(v3.y, 2.5f); + EXPECT_FLOAT_EQ(v3.z, 2.0f); + EXPECT_FLOAT_EQ(v3.w, 1.75f); +} + +// Test compound assignment operators +TEST_F(UnitTestVector4, AdditionAssignmentOperator) +{ + v1 += v2; + EXPECT_FLOAT_EQ(v1.x, 5.0f); + EXPECT_FLOAT_EQ(v1.y, 7.0f); + EXPECT_FLOAT_EQ(v1.z, 9.0f); + EXPECT_FLOAT_EQ(v1.w, 11.0f); +} + +TEST_F(UnitTestVector4, SubtractionAssignmentOperator) +{ + v1 -= v2; + EXPECT_FLOAT_EQ(v1.x, -3.0f); + EXPECT_FLOAT_EQ(v1.y, -3.0f); + EXPECT_FLOAT_EQ(v1.z, -3.0f); + EXPECT_FLOAT_EQ(v1.w, -3.0f); +} + +TEST_F(UnitTestVector4, MultiplicationAssignmentOperator_Scalar) +{ + v1 *= 2.0f; + EXPECT_FLOAT_EQ(v1.x, 2.0f); + EXPECT_FLOAT_EQ(v1.y, 4.0f); + EXPECT_FLOAT_EQ(v1.z, 6.0f); + EXPECT_FLOAT_EQ(v1.w, 8.0f); +} + +TEST_F(UnitTestVector4, MultiplicationAssignmentOperator_Vector) +{ + v1 *= v2; + EXPECT_FLOAT_EQ(v1.x, 4.0f); + EXPECT_FLOAT_EQ(v1.y, 10.0f); + EXPECT_FLOAT_EQ(v1.z, 18.0f); + EXPECT_FLOAT_EQ(v1.w, 28.0f); +} + +TEST_F(UnitTestVector4, DivisionAssignmentOperator_Scalar) +{ + v1 /= 2.0f; + EXPECT_FLOAT_EQ(v1.x, 0.5f); + EXPECT_FLOAT_EQ(v1.y, 1.0f); + EXPECT_FLOAT_EQ(v1.z, 1.5f); + EXPECT_FLOAT_EQ(v1.w, 2.0f); +} + +TEST_F(UnitTestVector4, DivisionAssignmentOperator_Vector) +{ + v1 /= v2; + EXPECT_FLOAT_EQ(v1.x, 0.25f); + EXPECT_FLOAT_EQ(v1.y, 0.4f); + EXPECT_FLOAT_EQ(v1.z, 0.5f); + EXPECT_FLOAT_EQ(v1.w, 4.0f / 7.0f); +} + +TEST_F(UnitTestVector4, NegationOperator) +{ + constexpr Vector4 v3 = -Vector4(1.0f, 2.0f, 3.0f, 4.0f); + EXPECT_FLOAT_EQ(v3.x, -1.0f); + EXPECT_FLOAT_EQ(v3.y, -2.0f); + EXPECT_FLOAT_EQ(v3.z, -3.0f); + EXPECT_FLOAT_EQ(v3.w, -4.0f); +} + +// Test other member functions +TEST_F(UnitTestVector4, LengthSqr) +{ + constexpr float lengthSqr = Vector4(1.0f, 2.0f, 3.0f, 4.0f).LengthSqr(); + EXPECT_FLOAT_EQ(lengthSqr, 30.0f); +} + +TEST_F(UnitTestVector4, DotProduct) +{ + constexpr float dot = Vector4(1.0f, 2.0f, 3.0f, 4.0f).Dot(Vector4(4.0f, 5.0f, 6.0f, 7.0f)); + EXPECT_FLOAT_EQ(dot, 60.0f); +} + +TEST_F(UnitTestVector4, Abs) +{ + Vector4 v3 = Vector4(-1.0f, -2.0f, -3.0f, -4.0f); + v3.Abs(); + EXPECT_FLOAT_EQ(v3.x, 1.0f); + EXPECT_FLOAT_EQ(v3.y, 2.0f); + EXPECT_FLOAT_EQ(v3.z, 3.0f); + EXPECT_FLOAT_EQ(v3.w, 4.0f); +} + +TEST_F(UnitTestVector4, Sum) +{ + constexpr float sum = Vector4(1.0f, 2.0f, 3.0f, 4.0f).Sum(); + EXPECT_FLOAT_EQ(sum, 10.0f); +} + +TEST_F(UnitTestVector4, Clamp) +{ + Vector4 v3 = Vector4(1.0f, 2.0f, 3.0f, 4.0f); + v3.Clamp(1.5f, 2.5f); + EXPECT_FLOAT_EQ(v3.x, 1.5f); + EXPECT_FLOAT_EQ(v3.y, 2.0f); + EXPECT_FLOAT_EQ(v3.z, 2.5f); + EXPECT_FLOAT_EQ(v3.w, 4.0f); // w is not clamped in this method +} From 131bc01a522d0d91bcb73f2528156dbcc920374a Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 30 Sep 2024 11:04:42 -0700 Subject: [PATCH 083/795] updated license --- LICENSE | 698 ++------------------------------------------------------ 1 file changed, 24 insertions(+), 674 deletions(-) diff --git a/LICENSE b/LICENSE index f288702d..00d2e135 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,24 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to \ No newline at end of file From 4fb06d70fca79e361f98ab826b1659f3afb3c139 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 30 Sep 2024 10:58:54 -0700 Subject: [PATCH 084/795] refactored some unit tests --- include/omath/Mat.h | 304 ++++++++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/UnitTestMat.cpp | 245 ++++++++++++++++++++++++++++++++++ 3 files changed, 550 insertions(+) create mode 100644 include/omath/Mat.h create mode 100644 tests/UnitTestMat.cpp diff --git a/include/omath/Mat.h b/include/omath/Mat.h new file mode 100644 index 00000000..36789cf6 --- /dev/null +++ b/include/omath/Mat.h @@ -0,0 +1,304 @@ +// +// Created by vlad on 9/29/2024. +// +#pragma once +#include +#include +#include +#include "Vector3.h" +#include +#include "Angles.h" + + +namespace omath +{ + template + class Mat final + { + public: + + constexpr Mat() + { + Clear(); + } + + + constexpr Mat(const std::initializer_list>& rows) + { + if (rows.size() != Rows) + throw std::invalid_argument("Initializer list rows size does not match template parameter Rows"); + + auto rowIt = rows.begin(); + for (size_t i = 0; i < Rows; ++i, ++rowIt) + { + if (rowIt->size() != Columns) + throw std::invalid_argument("All rows must have the same number of columns as template parameter Columns"); + + auto colIt = rowIt->begin(); + for (size_t j = 0; j < Columns; ++j, ++colIt) + { + At(i, j) = *colIt; + } + } + } + + + constexpr Mat(const Mat& other) + { + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) = other.At(i, j); + } + + + constexpr Mat(Mat&& other) noexcept + { + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) = other.At(i, j) ; + } + + [[nodiscard]] + static constexpr size_t RowCount() noexcept { return Rows; } + + [[nodiscard]] + static constexpr size_t ColumnsCount() noexcept { return Columns; } + + [[nodiscard]] + constexpr std::pair Size() const noexcept { return { Rows, Columns }; } + + + [[nodiscard]] constexpr const float& At(const size_t rowIndex, const size_t columnIndex) const + { + if (rowIndex >= Rows || columnIndex >= Columns) + throw std::out_of_range("Index out of range"); + + return m_data[rowIndex * Columns + columnIndex]; + } + [[nodiscard]] constexpr float& At(const size_t rowIndex, const size_t columnIndex) + { + return const_cast(std::as_const(*this).At(rowIndex, columnIndex)); + } + [[nodiscard]] + constexpr float Sum() const + { + float sum = 0.f; + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + sum += At(i, j); + + return sum; + } + + constexpr void Clear() + { + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) = 0.f; + } + + // Operator overloading for multiplication with another Mat + template + constexpr Mat operator*(const Mat& other) const + { + Mat result; + + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < OtherColumns; ++j) + { + float sum = 0.f; + for (size_t k = 0; k < Columns; ++k) + sum += At(i, k) * other.At(k, j); + result.At(i, j) = sum; + } + return result; + } + + constexpr Mat& operator*=(float f) + { + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) *= f; + return *this; + } + + constexpr Mat operator*(float f) const + { + Mat result(*this); + result *= f; + return result; + } + + constexpr Mat& operator/=(float f) + { + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) /= f; + return *this; + } + + constexpr Mat operator/(float f) const + { + Mat result(*this); + result /= f; + return result; + } + + constexpr Mat& operator=(const Mat& other) + { + if (this == &other) + return *this; + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) = other.At(i, j); + return *this; + } + + constexpr Mat& operator=(Mat&& other) noexcept + { + if (this == &other) + return *this; + + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + At(i, j) = other.At(i, j); + + return *this; + } + + [[nodiscard]] + constexpr Mat Transpose() const + { + Mat transposed; + for (size_t i = 0; i < Rows; ++i) + for (size_t j = 0; j < Columns; ++j) + transposed.At(j, i) = At(i, j); + + return transposed; + } + + [[nodiscard]] + constexpr float Determinant() const + { + static_assert(Rows == Columns, "Determinant is only defined for square matrices."); + + if constexpr (Rows == 1) + return At(0, 0); + + else if constexpr (Rows == 2) + return At(0, 0) * At(1, 1) - At(0, 1) * At(1, 0); + else + { + float det = 0.f; + for (size_t i = 0; i < Columns; ++i) + { + const float cofactor = (i % 2 == 0 ? 1.f : -1.f) * At(0, i) * Minor(0, i).Determinant(); + det += cofactor; + } + return det; + } + } + + [[nodiscard]] + constexpr Mat Minor(size_t row, size_t column) const + { + Mat result; + for (size_t i = 0, m = 0; i < Rows; ++i) + { + if (i == row) + continue; + for (size_t j = 0, n = 0; j < Columns; ++j) + { + if (j == column) + continue; + result.At(m, n) = At(i, j); + ++n; + } + ++m; + } + return result; + } + + [[nodiscard]] + std::string ToString() const + { + std::ostringstream oss; + for (size_t i = 0; i < Rows; ++i) + { + for (size_t j = 0; j < Columns; ++j) + { + oss << At(i, j); + if (j != Columns - 1) + oss << ' '; + } + oss << '\n'; + } + return oss.str(); + } + + // Static methods that return fixed-size matrices + [[nodiscard]] + constexpr static Mat<4, 4> ToScreenMat(float screenWidth, float screenHeight) + { + Mat<4, 4> mat; + mat.At(0, 0) = screenWidth / 2.f; + mat.At(1, 1) = -screenHeight / 2.f; + mat.At(2, 2) = 1.f; + mat.At(3, 0) = screenWidth / 2.f; + mat.At(3, 1) = screenHeight / 2.f; + mat.At(3, 3) = 1.f; + return mat; + } + + [[nodiscard]] + constexpr static Mat<4, 4> TranslationMat(const Vector3& diff) + { + Mat<4, 4> mat; + mat.At(0, 0) = 1.f; + mat.At(1, 1) = 1.f; + mat.At(2, 2) = 1.f; + mat.At(3, 3) = 1.f; + mat.At(3, 0) = diff.x; + mat.At(3, 1) = diff.y; + mat.At(3, 2) = diff.z; + return mat; + } + + [[nodiscard]] + constexpr static Mat<4, 4> OrientationMat(const Vector3& forward, const Vector3& right, const Vector3& up) + { + Mat<4, 4> mat; + + mat.At(0, 0) = right.x; + mat.At(0, 1) = up.x; + mat.At(0, 2) = forward.x; + mat.At(1, 0) = right.y; + mat.At(1, 1) = up.y; + mat.At(1, 2) = forward.y; + mat.At(2, 0) = right.z; + mat.At(2, 1) = up.z; + mat.At(2, 2) = forward.z; + mat.At(3, 3) = 1.f; + + return mat; + } + + [[nodiscard]] + constexpr static Mat<4, 4> ProjectionMat(const float fieldOfView, const float aspectRatio, const float near, const float far) + { + Mat<4, 4> mat; + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + mat.At(0, 0) = 1.f / (aspectRatio * fovHalfTan); + mat.At(1, 1) = 1.f / fovHalfTan; + mat.At(2, 2) = (far + near) / (far - near); + mat.At(2, 3) = (2.f * near * far) / (far - near); + mat.At(3, 2) = -1.f; + + return mat; + } + + private: + std::array m_data; + }; +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 920fae4c..90f0beaf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,6 +7,7 @@ include(GoogleTest) add_executable(unit-tests UnitTestPrediction.cpp UnitTestMatrix.cpp + UnitTestMat.cpp UnitTestAstar.cpp UnitTestProjection.cpp UnitTestVector3.cpp diff --git a/tests/UnitTestMat.cpp b/tests/UnitTestMat.cpp new file mode 100644 index 00000000..7a0048c6 --- /dev/null +++ b/tests/UnitTestMat.cpp @@ -0,0 +1,245 @@ +// UnitTestMat.cpp +#include +#include "omath/Mat.h" +#include "omath/Vector3.h" + +using namespace omath; + +class UnitTestMat : public ::testing::Test +{ +protected: + Mat<2, 2> m1; + Mat<2, 2> m2; + + void SetUp() override + { + m1 = Mat<2, 2>(); + m2 = Mat<2, 2>{{1.0f, 2.0f}, {3.0f, 4.0f}}; + } +}; + +// Test constructors +TEST_F(UnitTestMat, Constructor_Default) +{ + Mat<3, 3> m; + EXPECT_EQ(m.RowCount(), 3); + EXPECT_EQ(m.ColumnsCount(), 3); + for (size_t i = 0; i < 3; ++i) + for (size_t j = 0; j < 3; ++j) + EXPECT_FLOAT_EQ(m.At(i, j), 0.0f); +} + +TEST_F(UnitTestMat, Constructor_InitializerList) +{ + constexpr Mat<2, 2> m{{1.0f, 2.0f}, {3.0f, 4.0f}}; + EXPECT_EQ(m.RowCount(), 2); + EXPECT_EQ(m.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m.At(0, 1), 2.0f); + EXPECT_FLOAT_EQ(m.At(1, 0), 3.0f); + EXPECT_FLOAT_EQ(m.At(1, 1), 4.0f); +} + +TEST_F(UnitTestMat, Constructor_Copy) +{ + Mat<2, 2> m3 = m2; + EXPECT_EQ(m3.RowCount(), m2.RowCount()); + EXPECT_EQ(m3.ColumnsCount(), m2.ColumnsCount()); + EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); + EXPECT_FLOAT_EQ(m3.At(1, 1), m2.At(1, 1)); +} + +TEST_F(UnitTestMat, Constructor_Move) +{ + Mat<2, 2> m3 = std::move(m2); + EXPECT_EQ(m3.RowCount(), 2); + EXPECT_EQ(m3.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m3.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 4.0f); + // m2 is in a valid but unspecified state after move +} + +// Test matrix operations +TEST_F(UnitTestMat, Operator_Multiplication_Matrix) +{ + Mat<2, 2> m3 = m2 * m2; + EXPECT_EQ(m3.RowCount(), 2); + EXPECT_EQ(m3.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m3.At(0, 0), 7.0f); + EXPECT_FLOAT_EQ(m3.At(0, 1), 10.0f); + EXPECT_FLOAT_EQ(m3.At(1, 0), 15.0f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 22.0f); +} + +TEST_F(UnitTestMat, Operator_Multiplication_Scalar) +{ + Mat<2, 2> m3 = m2 * 2.0f; + EXPECT_FLOAT_EQ(m3.At(0, 0), 2.0f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 8.0f); +} + +TEST_F(UnitTestMat, Operator_Division_Scalar) +{ + Mat<2, 2> m3 = m2 / 2.0f; + EXPECT_FLOAT_EQ(m3.At(0, 0), 0.5f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 2.0f); +} + +// Test matrix functions +TEST_F(UnitTestMat, Transpose) +{ + Mat<2, 2> m3 = m2.Transpose(); + EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); + EXPECT_FLOAT_EQ(m3.At(0, 1), m2.At(1, 0)); + EXPECT_FLOAT_EQ(m3.At(1, 0), m2.At(0, 1)); + EXPECT_FLOAT_EQ(m3.At(1, 1), m2.At(1, 1)); +} + +TEST_F(UnitTestMat, Determinant) +{ + const float det = m2.Determinant(); + EXPECT_FLOAT_EQ(det, -2.0f); +} + +TEST_F(UnitTestMat, Sum) +{ + const float sum = m2.Sum(); + EXPECT_FLOAT_EQ(sum, 10.0f); +} + +TEST_F(UnitTestMat, Clear) +{ + m2.Clear(); + for (size_t i = 0; i < m2.RowCount(); ++i) + for (size_t j = 0; j < m2.ColumnsCount(); ++j) + EXPECT_FLOAT_EQ(m2.At(i, j), 0.0f); +} + +TEST_F(UnitTestMat, ToString) +{ + const std::string str = m2.ToString(); + EXPECT_FALSE(str.empty()); + EXPECT_EQ(str, "1 2\n3 4\n"); +} + +// Test assignment operators +TEST_F(UnitTestMat, AssignmentOperator_Copy) +{ + Mat<2, 2> m3; + m3 = m2; + EXPECT_EQ(m3.RowCount(), m2.RowCount()); + EXPECT_EQ(m3.ColumnsCount(), m2.ColumnsCount()); + EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); +} + +TEST_F(UnitTestMat, AssignmentOperator_Move) +{ + Mat<2, 2> m3; + m3 = std::move(m2); + EXPECT_EQ(m3.RowCount(), 2); + EXPECT_EQ(m3.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(m3.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m3.At(1, 1), 4.0f); + // m2 is in a valid but unspecified state after move +} + +// Test static methods +TEST_F(UnitTestMat, StaticMethod_ToScreenMat) +{ + Mat<4, 4> screenMat = Mat<4, 4>::ToScreenMat(800.0f, 600.0f); + EXPECT_FLOAT_EQ(screenMat.At(0, 0), 400.0f); + EXPECT_FLOAT_EQ(screenMat.At(1, 1), -300.0f); + EXPECT_FLOAT_EQ(screenMat.At(3, 0), 400.0f); + EXPECT_FLOAT_EQ(screenMat.At(3, 1), 300.0f); + EXPECT_FLOAT_EQ(screenMat.At(3, 3), 1.0f); +} + +// Test static method: TranslationMat +TEST_F(UnitTestMat, StaticMethod_TranslationMat) +{ + Vector3 diff{10.0f, 20.0f, 30.0f}; + Mat<4, 4> transMat = Mat<4, 4>::TranslationMat(diff); + EXPECT_FLOAT_EQ(transMat.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(transMat.At(3, 0), diff.x); + EXPECT_FLOAT_EQ(transMat.At(3, 1), diff.y); + EXPECT_FLOAT_EQ(transMat.At(3, 2), diff.z); + EXPECT_FLOAT_EQ(transMat.At(3, 3), 1.0f); +} + +// Test static method: OrientationMat +TEST_F(UnitTestMat, StaticMethod_OrientationMat) +{ + constexpr Vector3 forward{0.0f, 0.0f, 1.0f}; + constexpr Vector3 right{1.0f, 0.0f, 0.0f}; + constexpr Vector3 up{0.0f, 1.0f, 0.0f}; + constexpr Mat<4, 4> orientMat = Mat<4, 4>::OrientationMat(forward, right, up); + EXPECT_FLOAT_EQ(orientMat.At(0, 0), right.x); + EXPECT_FLOAT_EQ(orientMat.At(0, 1), up.x); + EXPECT_FLOAT_EQ(orientMat.At(0, 2), forward.x); + EXPECT_FLOAT_EQ(orientMat.At(1, 0), right.y); + EXPECT_FLOAT_EQ(orientMat.At(1, 1), up.y); + EXPECT_FLOAT_EQ(orientMat.At(1, 2), forward.y); + EXPECT_FLOAT_EQ(orientMat.At(2, 0), right.z); + EXPECT_FLOAT_EQ(orientMat.At(2, 1), up.z); + EXPECT_FLOAT_EQ(orientMat.At(2, 2), forward.z); +} + +// Test static method: ProjectionMat +TEST_F(UnitTestMat, StaticMethod_ProjectionMat) +{ + constexpr float fieldOfView = 45.0f; + constexpr float aspectRatio = 1.33f; + constexpr float near = 0.1f; + constexpr float far = 100.0f; + const Mat<4, 4> projMat = Mat<4, 4>::ProjectionMat(fieldOfView, aspectRatio, near, far); + + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + EXPECT_FLOAT_EQ(projMat.At(0, 0), 1.f / (aspectRatio * fovHalfTan)); + EXPECT_FLOAT_EQ(projMat.At(1, 1), 1.f / fovHalfTan); + EXPECT_FLOAT_EQ(projMat.At(2, 2), (far + near) / (far - near)); + EXPECT_FLOAT_EQ(projMat.At(2, 3), (2.f * near * far) / (far - near)); + EXPECT_FLOAT_EQ(projMat.At(3, 2), -1.f); +} + +// Test exception handling in At() method +TEST_F(UnitTestMat, Method_At_OutOfRange) +{ + EXPECT_THROW(std::ignore = m2.At(2, 0), std::out_of_range); + EXPECT_THROW(std::ignore = m2.At(0, 2), std::out_of_range); +} + +// Test Determinant for 3x3 matrix +TEST(UnitTestMatStandalone, Determinant_3x3) +{ + constexpr auto det = Mat<3, 3>{{6, 1, 1}, {4, -2, 5}, {2, 8, 7}}.Determinant(); + EXPECT_FLOAT_EQ(det, -306.0f); +} + +// Test Minor for 3x3 matrix +TEST(UnitTestMatStandalone, Minor_3x3) +{ + constexpr Mat<3, 3> m{{3, 0, 2}, {2, 0, -2}, {0, 1, 1}}; + auto minor = m.Minor(0, 0); + EXPECT_EQ(minor.RowCount(), 2); + EXPECT_EQ(minor.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(minor.At(0, 0), 0.0f); + EXPECT_FLOAT_EQ(minor.At(0, 1), -2.0f); + EXPECT_FLOAT_EQ(minor.At(1, 0), 1.0f); + EXPECT_FLOAT_EQ(minor.At(1, 1), 1.0f); +} + +// Test Transpose for non-square matrix +TEST(UnitTestMatStandalone, Transpose_NonSquare) +{ + constexpr Mat<2, 3> m{{1.0f, 2.0f, 3.0f}, {4.0f, 5.0f, 6.0f}}; + auto transposed = m.Transpose(); + EXPECT_EQ(transposed.RowCount(), 3); + EXPECT_EQ(transposed.ColumnsCount(), 2); + EXPECT_FLOAT_EQ(transposed.At(0, 0), 1.0f); + EXPECT_FLOAT_EQ(transposed.At(1, 0), 2.0f); + EXPECT_FLOAT_EQ(transposed.At(2, 0), 3.0f); + EXPECT_FLOAT_EQ(transposed.At(0, 1), 4.0f); + EXPECT_FLOAT_EQ(transposed.At(1, 1), 5.0f); + EXPECT_FLOAT_EQ(transposed.At(2, 1), 6.0f); +} From ac79326da80a580be2bc5dfbe53feaf0e3784db2 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 30 Sep 2024 11:54:47 -0700 Subject: [PATCH 085/795] improved projection class --- include/omath/Mat.h | 6 ++++++ include/omath/projection/Camera.h | 4 ++-- source/projection/Camera.cpp | 12 ++++++------ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/include/omath/Mat.h b/include/omath/Mat.h index 36789cf6..b1165c96 100644 --- a/include/omath/Mat.h +++ b/include/omath/Mat.h @@ -121,6 +121,12 @@ namespace omath At(i, j) *= f; return *this; } + + template + constexpr Mat operator*=(const Mat& other) + { + return *this = *this * other; + } constexpr Mat operator*(float f) const { diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index a3bfbe19..4d37e676 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include "ErrorCodes.h" @@ -28,7 +28,7 @@ namespace omath::projection Camera(const Vector3& position, const Vector3& viewAngles, const ViewPort& viewPort, float fov, float near, float far); void SetViewAngles(const Vector3& viewAngles); - [[nodiscard]] Matrix GetViewMatrix() const; + [[nodiscard]] Mat<4, 4> GetViewMatrix() const; [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 5f67a6e8..0b49e708 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -21,24 +21,24 @@ namespace omath::projection m_farPlaneDistance = far; } - Matrix Camera::GetViewMatrix() const + Mat<4, 4> Camera::GetViewMatrix() const { const auto forward = Vector3::ForwardVector(m_viewAngles.x, m_viewAngles.y); const auto right = Vector3::RightVector(m_viewAngles.x, m_viewAngles.y, m_viewAngles.z); const auto up = Vector3::UpVector(m_viewAngles.x, m_viewAngles.y, m_viewAngles.z); - return Matrix::TranslationMatrix(-m_origin) * Matrix::OrientationMatrix(forward, right, up); + return Mat<4, 4>::TranslationMat(-m_origin) * Mat<4, 4>::OrientationMat(forward, right, up); } std::expected Camera::WorldToScreen(const Vector3 &worldPosition) const { - const auto posVecAsMatrix = Matrix({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); + const auto posVecAsMatrix = Mat<1, 4>({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); - const auto projectionMatrix = Matrix::ProjectionMatrix(m_fieldOfView, m_viewPort.AspectRatio(), + const auto projectionMatrix = Mat<4, 4>::ProjectionMat(m_fieldOfView, m_viewPort.AspectRatio(), m_nearPlaneDistance, m_farPlaneDistance); - auto projected = posVecAsMatrix * (GetViewMatrix() * projectionMatrix); + Mat<1, 4> projected = posVecAsMatrix * (GetViewMatrix() * projectionMatrix); if (projected.At(0, 3) <= 0.f) return std::unexpected(Error::WORLD_POSITION_IS_BEHIND_CAMERA); @@ -49,7 +49,7 @@ namespace omath::projection projected.At(0, 1) < -1.f || projected.At(0, 1) > 1.f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); - projected *= Matrix::ToScreenMatrix(m_viewPort.m_width, m_viewPort.m_height); + projected *= Mat<4, 4>::ToScreenMat(m_viewPort.m_width, m_viewPort.m_height); return Vector2{projected.At(0, 0), projected.At(0, 1)}; } From 1dedc7c01b63c2982a50100880e98af8251f6bc5 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 30 Sep 2024 11:58:11 -0700 Subject: [PATCH 086/795] added line --- include/omath/Mat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/Mat.h b/include/omath/Mat.h index b1165c96..812d3849 100644 --- a/include/omath/Mat.h +++ b/include/omath/Mat.h @@ -121,7 +121,7 @@ namespace omath At(i, j) *= f; return *this; } - + template constexpr Mat operator*=(const Mat& other) { From 2652f55b75104901f1564630386422792de1c9e9 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 1 Oct 2024 05:16:45 -0700 Subject: [PATCH 087/795] updated static methods --- include/omath/Mat.h | 69 ++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/include/omath/Mat.h b/include/omath/Mat.h index 812d3849..2923f90e 100644 --- a/include/omath/Mat.h +++ b/include/omath/Mat.h @@ -65,7 +65,7 @@ namespace omath static constexpr size_t ColumnsCount() noexcept { return Columns; } [[nodiscard]] - constexpr std::pair Size() const noexcept { return { Rows, Columns }; } + static constexpr std::pair Size() noexcept { return { Rows, Columns }; } [[nodiscard]] constexpr const float& At(const size_t rowIndex, const size_t columnIndex) const @@ -206,7 +206,7 @@ namespace omath } [[nodiscard]] - constexpr Mat Minor(size_t row, size_t column) const + constexpr Mat Minor(const size_t row, const size_t column) const { Mat result; for (size_t i = 0, m = 0; i < Rows; ++i) @@ -244,30 +244,27 @@ namespace omath // Static methods that return fixed-size matrices [[nodiscard]] - constexpr static Mat<4, 4> ToScreenMat(float screenWidth, float screenHeight) + constexpr static Mat<4, 4> ToScreenMat(const float screenWidth, const float screenHeight) { - Mat<4, 4> mat; - mat.At(0, 0) = screenWidth / 2.f; - mat.At(1, 1) = -screenHeight / 2.f; - mat.At(2, 2) = 1.f; - mat.At(3, 0) = screenWidth / 2.f; - mat.At(3, 1) = screenHeight / 2.f; - mat.At(3, 3) = 1.f; - return mat; + return + { + {screenWidth / 2.f, 0.f, 0.f, 0.f}, + {0.f, -screenHeight / 2.f, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {screenWidth / 2.f, screenHeight / 2.f, 0.f, 1.f}, + }; } [[nodiscard]] constexpr static Mat<4, 4> TranslationMat(const Vector3& diff) { - Mat<4, 4> mat; - mat.At(0, 0) = 1.f; - mat.At(1, 1) = 1.f; - mat.At(2, 2) = 1.f; - mat.At(3, 3) = 1.f; - mat.At(3, 0) = diff.x; - mat.At(3, 1) = diff.y; - mat.At(3, 2) = diff.z; - return mat; + return + { + {1.f, 0.f, 0.f, 0.f}, + {0.f, 1.f, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {diff.x, diff.y, diff.z, 1.f}, + }; } [[nodiscard]] @@ -275,16 +272,13 @@ namespace omath { Mat<4, 4> mat; - mat.At(0, 0) = right.x; - mat.At(0, 1) = up.x; - mat.At(0, 2) = forward.x; - mat.At(1, 0) = right.y; - mat.At(1, 1) = up.y; - mat.At(1, 2) = forward.y; - mat.At(2, 0) = right.z; - mat.At(2, 1) = up.z; - mat.At(2, 2) = forward.z; - mat.At(3, 3) = 1.f; + return + { + {right.x, up.x, forward.x, 0.f}, + {right.y, up.y, forward.y, 0.f}, + {right.z, up.z, forward.z, 0.f}, + {0.f, 0.f, 0.f, 1.f}, + }; return mat; } @@ -292,16 +286,15 @@ namespace omath [[nodiscard]] constexpr static Mat<4, 4> ProjectionMat(const float fieldOfView, const float aspectRatio, const float near, const float far) { - Mat<4, 4> mat; const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); - mat.At(0, 0) = 1.f / (aspectRatio * fovHalfTan); - mat.At(1, 1) = 1.f / fovHalfTan; - mat.At(2, 2) = (far + near) / (far - near); - mat.At(2, 3) = (2.f * near * far) / (far - near); - mat.At(3, 2) = -1.f; - - return mat; + return + { + {1.f / (aspectRatio * fovHalfTan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fovHalfTan, 0.f, 0.f}, + {0.f, 0.f, (far + near) / (far - near), 2.f * near * far / (far - near)}, + {0.f, 0.f, -1.f, 0.f} + }; } private: From f9a263c7c298d13840f2735f9e40db1f531b3088 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 2 Oct 2024 04:25:43 -0700 Subject: [PATCH 088/795] added files --- CMakeLists.txt | 56 +++++++++++++++++++++++++++++++++++--- cmake/omathConfig.cmake.in | 6 ++++ 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 cmake/omathConfig.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index ad7e13de..77bd952a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,13 @@ cmake_minimum_required(VERSION 3.26) +project(omath VERSION 1.0.0) + -project(omath) set(CMAKE_CXX_STANDARD 26) option(OMATH_BUILD_TESTS "Build unit tests" ON) -option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to threat them as errors" ON) +option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so\\.dll." OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) @@ -20,7 +21,7 @@ add_subdirectory(extlibs) if(OMATH_BUILD_TESTS) add_subdirectory(tests) -endif () +endif() if (WIN32 AND OMATH_THREAT_WARNING_AS_ERROR) target_compile_options(omath PRIVATE /W4 /WX) @@ -28,4 +29,51 @@ elseif(UNIX AND OMATH_THREAT_WARNING_AS_ERROR) target_compile_options(omath PRIVATE -Wall -Wextra -Wpedantic) endif() -target_include_directories(omath PUBLIC include) \ No newline at end of file +target_include_directories(omath + PUBLIC + $ # Use this path when building the project + $ # Use this path when the project is installed +) + + +# Installation rules + +# Install the library +install(TARGETS omath + EXPORT omathTargets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin +) + +# Install the public headers +install(DIRECTORY include/ DESTINATION include) + +# Export omath targets +install(EXPORT omathTargets + FILE omathTargets.cmake + NAMESPACE omath:: + DESTINATION lib/cmake/omath +) + + +# Generate the omathConfigVersion.cmake file +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion +) + +# Generate the omathConfig.cmake file from the template (which is in the cmake/ folder) +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/omathConfig.cmake.in" # Path to the .in file + "${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" # Output path for the generated file + INSTALL_DESTINATION lib/cmake/omath +) + +# Install the generated config files +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake" + DESTINATION lib/cmake/omath +) \ No newline at end of file diff --git a/cmake/omathConfig.cmake.in b/cmake/omathConfig.cmake.in new file mode 100644 index 00000000..a889c4df --- /dev/null +++ b/cmake/omathConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# Load the targets for the omath library +include("${CMAKE_CURRENT_LIST_DIR}/omathTargets.cmake") From 8eaf77a1d7ab46d1b62f26e069619dcb7b7cae93 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 2 Oct 2024 07:56:35 +0300 Subject: [PATCH 089/795] added component --- CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 77bd952a..69fcc24b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,19 +41,19 @@ target_include_directories(omath # Install the library install(TARGETS omath EXPORT omathTargets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin + ARCHIVE DESTINATION lib COMPONENT omath_component # For static libraries + LIBRARY DESTINATION lib COMPONENT omath_component # For shared libraries + RUNTIME DESTINATION bin COMPONENT omath_component # For executables (on Windows) ) -# Install the public headers -install(DIRECTORY include/ DESTINATION include) +# Install headers as part of omath_component +install(DIRECTORY include/ DESTINATION include COMPONENT omath_component) -# Export omath targets +# Export omath target for CMake find_package support, also under omath_component install(EXPORT omathTargets FILE omathTargets.cmake NAMESPACE omath:: - DESTINATION lib/cmake/omath + DESTINATION lib/cmake/omath COMPONENT omath_component ) From ef1d74d8fd2a8f03b7055230fddafe1ebc7db629 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 2 Oct 2024 07:58:04 +0300 Subject: [PATCH 090/795] fixed \\ slash --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 69fcc24b..94298d3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_CXX_STANDARD 26) option(OMATH_BUILD_TESTS "Build unit tests" ON) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) -option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so\\.dll." OFF) +option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Vector3.cpp) From 22d03f00b989bf3b320f7973339fa9edcd18ba42 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 5 Oct 2024 06:10:33 +0300 Subject: [PATCH 091/795] fix --- include/omath/Mat.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/omath/Mat.h b/include/omath/Mat.h index 2923f90e..e2d73a54 100644 --- a/include/omath/Mat.h +++ b/include/omath/Mat.h @@ -270,8 +270,6 @@ namespace omath [[nodiscard]] constexpr static Mat<4, 4> OrientationMat(const Vector3& forward, const Vector3& right, const Vector3& up) { - Mat<4, 4> mat; - return { {right.x, up.x, forward.x, 0.f}, @@ -279,8 +277,6 @@ namespace omath {right.z, up.z, forward.z, 0.f}, {0.f, 0.f, 0.f, 1.f}, }; - - return mat; } [[nodiscard]] From 128aafac6d336e6ef270cadccf93841da371157b Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 8 Oct 2024 18:55:30 +0300 Subject: [PATCH 092/795] renamed component --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94298d3f..72ca0fb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,19 +41,19 @@ target_include_directories(omath # Install the library install(TARGETS omath EXPORT omathTargets - ARCHIVE DESTINATION lib COMPONENT omath_component # For static libraries - LIBRARY DESTINATION lib COMPONENT omath_component # For shared libraries - RUNTIME DESTINATION bin COMPONENT omath_component # For executables (on Windows) + ARCHIVE DESTINATION lib COMPONENT omath # For static libraries + LIBRARY DESTINATION lib COMPONENT omath # For shared libraries + RUNTIME DESTINATION bin COMPONENT omath # For executables (on Windows) ) # Install headers as part of omath_component -install(DIRECTORY include/ DESTINATION include COMPONENT omath_component) +install(DIRECTORY include/ DESTINATION include COMPONENT omath) # Export omath target for CMake find_package support, also under omath_component install(EXPORT omathTargets FILE omathTargets.cmake NAMESPACE omath:: - DESTINATION lib/cmake/omath COMPONENT omath_component + DESTINATION lib/cmake/omath COMPONENT omath ) From dc26ed23f5cc33b79dd2adbd77f306d44cd6d660 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 14 Oct 2024 11:32:41 +0300 Subject: [PATCH 093/795] added more unit tests --- tests/UnitTestProjection.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index fc2c54d9..e25d66d1 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -7,10 +7,17 @@ #include #include -TEST(UnitTestProjection, IsPointOnScreen) +TEST(UnitTestProjection, Projection) { - const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.1f, 500.f); + const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.375f, 5000.f); - const auto proj = camera.WorldToScreen({100, 0, 15}); - EXPECT_TRUE(proj.has_value()); + EXPECT_EQ(camera.WorldToScreen({100, 0, 0}).value(), omath::Vector2(960, 540)); + EXPECT_EQ(camera.WorldToScreen({49.23, 0, 0}).value(), omath::Vector2(960, 540)); + const auto proj = camera.WorldToScreen({100, 50, -69}); + std::print("{} {}", proj->x, proj->y); + EXPECT_EQ(camera.WorldToScreen({100, 10, 0}).value(), omath::Vector2(909.58887, 540)); + EXPECT_EQ(camera.WorldToScreen({100, 10, 8}).value(), omath::Vector2(909.58887, 499.67108)); + EXPECT_EQ(camera.WorldToScreen({100, 50, -69}).value(), omath::Vector2(707.9442, 887.83704)); + + EXPECT_FALSE(camera.WorldToScreen({-10,0, 0}).has_value()); } \ No newline at end of file From 3e6cabb6c7db18328dd18378b88a09ed52b8aa30 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 18 Oct 2024 17:20:23 +0300 Subject: [PATCH 094/795] fixed projection matrix --- include/omath/Mat.h | 12 +++++++----- include/omath/projection/Camera.h | 6 ++++-- include/omath/projection/ErrorCodes.h | 1 - source/projection/Camera.cpp | 15 +++++++++------ tests/UnitTestMat.cpp | 2 +- tests/UnitTestProjection.cpp | 10 +--------- 6 files changed, 22 insertions(+), 24 deletions(-) diff --git a/include/omath/Mat.h b/include/omath/Mat.h index e2d73a54..6d8385d0 100644 --- a/include/omath/Mat.h +++ b/include/omath/Mat.h @@ -280,16 +280,18 @@ namespace omath } [[nodiscard]] - constexpr static Mat<4, 4> ProjectionMat(const float fieldOfView, const float aspectRatio, const float near, const float far) + constexpr static Mat<4, 4> ProjectionMat(const float fieldOfView, const float aspectRatio, + const float near, const float far, const float lensZoom) { const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + const float frustumHeight = far - near; return { - {1.f / (aspectRatio * fovHalfTan), 0.f, 0.f, 0.f}, - {0.f, 1.f / fovHalfTan, 0.f, 0.f}, - {0.f, 0.f, (far + near) / (far - near), 2.f * near * far / (far - near)}, - {0.f, 0.f, -1.f, 0.f} + {-1.f / (aspectRatio * fovHalfTan) * lensZoom, 0.f, 0.f, 0.f}, + {0.f, -1.f / fovHalfTan * lensZoom, 0.f, 0.f}, + {0.f, 0.f, -far / frustumHeight, -1}, + {0.f, 0.f, near * far / frustumHeight, 0.f} }; } diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index 4d37e676..23067ddc 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -25,18 +25,20 @@ namespace omath::projection class Camera { public: - Camera(const Vector3& position, const Vector3& viewAngles, const ViewPort& viewPort, float fov, float near, float far); + Camera(const Vector3& position, const Vector3& viewAngles, const ViewPort& viewPort, + float fov, float near, float far, float lensZoom); void SetViewAngles(const Vector3& viewAngles); [[nodiscard]] Mat<4, 4> GetViewMatrix() const; - [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; + [[nodiscard]] std::expected WorldToScreen(Vector3 worldPosition) const; ViewPort m_viewPort{}; float m_fieldOfView; float m_farPlaneDistance; float m_nearPlaneDistance; + float m_lensZoom; private: Vector3 m_viewAngles; diff --git a/include/omath/projection/ErrorCodes.h b/include/omath/projection/ErrorCodes.h index 968a15ed..3c5d26ce 100644 --- a/include/omath/projection/ErrorCodes.h +++ b/include/omath/projection/ErrorCodes.h @@ -10,7 +10,6 @@ namespace omath::projection { enum class Error : uint16_t { - WORLD_POSITION_IS_BEHIND_CAMERA = 0, WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS, }; } \ No newline at end of file diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 0b49e708..a7411395 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -11,7 +11,7 @@ namespace omath::projection { Camera::Camera(const Vector3 &position, const Vector3 &viewAngles, const ViewPort &viewPort, - const float fov, const float near, const float far) + const float fov, const float near, const float far, const float lensZoom) { m_origin = position; m_viewAngles = viewAngles; @@ -19,6 +19,8 @@ namespace omath::projection m_fieldOfView = fov; m_nearPlaneDistance = near; m_farPlaneDistance = far; + m_lensZoom = lensZoom; + } Mat<4, 4> Camera::GetViewMatrix() const @@ -30,23 +32,24 @@ namespace omath::projection return Mat<4, 4>::TranslationMat(-m_origin) * Mat<4, 4>::OrientationMat(forward, right, up); } - std::expected Camera::WorldToScreen(const Vector3 &worldPosition) const + std::expected Camera::WorldToScreen(Vector3 worldPosition) const { const auto posVecAsMatrix = Mat<1, 4>({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); const auto projectionMatrix = Mat<4, 4>::ProjectionMat(m_fieldOfView, m_viewPort.AspectRatio(), - m_nearPlaneDistance, m_farPlaneDistance); + m_nearPlaneDistance, m_farPlaneDistance, 1.335f); Mat<1, 4> projected = posVecAsMatrix * (GetViewMatrix() * projectionMatrix); - if (projected.At(0, 3) <= 0.f) - return std::unexpected(Error::WORLD_POSITION_IS_BEHIND_CAMERA); + if (projected.At(0, 3) == 0.f) + return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); projected /= projected.At(0, 3); if (projected.At(0, 0) < -1.f || projected.At(0, 0) > 1.f || - projected.At(0, 1) < -1.f || projected.At(0, 1) > 1.f) + projected.At(0, 1) < -1.f || projected.At(0, 1) > 1.f || + projected.At(0, 2) < -1.f || projected.At(0, 2) > 1.f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); projected *= Mat<4, 4>::ToScreenMat(m_viewPort.m_width, m_viewPort.m_height); diff --git a/tests/UnitTestMat.cpp b/tests/UnitTestMat.cpp index 7a0048c6..f76566a6 100644 --- a/tests/UnitTestMat.cpp +++ b/tests/UnitTestMat.cpp @@ -191,7 +191,7 @@ TEST_F(UnitTestMat, StaticMethod_ProjectionMat) constexpr float aspectRatio = 1.33f; constexpr float near = 0.1f; constexpr float far = 100.0f; - const Mat<4, 4> projMat = Mat<4, 4>::ProjectionMat(fieldOfView, aspectRatio, near, far); + const Mat<4, 4> projMat = Mat<4, 4>::ProjectionMat(fieldOfView, aspectRatio, near, far, 1.335f); const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index e25d66d1..a4199d17 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -11,13 +11,5 @@ TEST(UnitTestProjection, Projection) { const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.375f, 5000.f); - EXPECT_EQ(camera.WorldToScreen({100, 0, 0}).value(), omath::Vector2(960, 540)); - EXPECT_EQ(camera.WorldToScreen({49.23, 0, 0}).value(), omath::Vector2(960, 540)); - const auto proj = camera.WorldToScreen({100, 50, -69}); - std::print("{} {}", proj->x, proj->y); - EXPECT_EQ(camera.WorldToScreen({100, 10, 0}).value(), omath::Vector2(909.58887, 540)); - EXPECT_EQ(camera.WorldToScreen({100, 10, 8}).value(), omath::Vector2(909.58887, 499.67108)); - EXPECT_EQ(camera.WorldToScreen({100, 50, -69}).value(), omath::Vector2(707.9442, 887.83704)); - - EXPECT_FALSE(camera.WorldToScreen({-10,0, 0}).has_value()); + camera.WorldToScreen({5000, 0, 0}); } \ No newline at end of file From c45bca2e0bdb33f8805b4d277f4655d84bf60d5b Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 19 Oct 2024 17:16:45 +0300 Subject: [PATCH 095/795] fix --- include/omath/Mat.h | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/include/omath/Mat.h b/include/omath/Mat.h index 6d8385d0..e20410e0 100644 --- a/include/omath/Mat.h +++ b/include/omath/Mat.h @@ -2,6 +2,7 @@ // Created by vlad on 9/29/2024. // #pragma once +#include #include #include #include @@ -59,13 +60,13 @@ namespace omath } [[nodiscard]] - static constexpr size_t RowCount() noexcept { return Rows; } + static consteval size_t RowCount() noexcept { return Rows; } [[nodiscard]] - static constexpr size_t ColumnsCount() noexcept { return Columns; } + static consteval size_t ColumnsCount() noexcept { return Columns; } [[nodiscard]] - static constexpr std::pair Size() noexcept { return { Rows, Columns }; } + static consteval std::pair Size() noexcept { return { Rows, Columns }; } [[nodiscard]] constexpr const float& At(const size_t rowIndex, const size_t columnIndex) const @@ -92,11 +93,13 @@ namespace omath constexpr void Clear() { - for (size_t i = 0; i < Rows; ++i) - for (size_t j = 0; j < Columns; ++j) - At(i, j) = 0.f; + Set(0.f); } + constexpr void Set(const float value) + { + std::ranges::fill(m_data, value); + } // Operator overloading for multiplication with another Mat template constexpr Mat operator*(const Mat& other) const From ef24377049636842a7f309b12024273f16a14fb4 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 20 Oct 2024 00:25:03 +0300 Subject: [PATCH 096/795] fixed some stuff added constexpr --- include/omath/Vector2.h | 11 +++++++++-- include/omath/Vector3.h | 25 +++++++++++++++++++------ include/omath/projection/Camera.h | 2 +- source/Vector2.cpp | 14 -------------- source/Vector3.cpp | 25 ------------------------- source/projection/Camera.cpp | 4 ++-- tests/UnitTestMat.cpp | 18 ------------------ tests/UnitTestProjection.cpp | 8 ++++++-- 8 files changed, 37 insertions(+), 70 deletions(-) diff --git a/include/omath/Vector2.h b/include/omath/Vector2.h index f04da2dd..0bb73e92 100644 --- a/include/omath/Vector2.h +++ b/include/omath/Vector2.h @@ -114,7 +114,10 @@ namespace omath return x * vOther.x + y * vOther.y; } - [[nodiscard]] float Length() const; + [[nodiscard]] constexpr float Length() const + { + return std::hypot(x, y); + } [[nodiscard]] constexpr float LengthSqr() const { @@ -167,7 +170,11 @@ namespace omath } // Normalize the vector - [[nodiscard]] Vector2 Normalized() const; + [[nodiscard]] constexpr Vector2 Normalized() const + { + const float len = Length(); + return len > 0.f ? *this / len : *this; + } // Sum of elements [[nodiscard]] constexpr float Sum() const diff --git a/include/omath/Vector3.h b/include/omath/Vector3.h index 18e2ffd1..1b75d39b 100644 --- a/include/omath/Vector3.h +++ b/include/omath/Vector3.h @@ -92,8 +92,10 @@ namespace omath return *this; } - [[nodiscard]] - float DistTo(const Vector3& vOther) const; + [[nodiscard]] constexpr float DistTo(const Vector3& vOther) const + { + return (*this - vOther).Length(); + } constexpr Vector3& Abs() { @@ -112,14 +114,21 @@ namespace omath { return Vector2::Dot(vOther) + z * vOther.z; } - [[nodiscard]] float Length() const; + + [[nodiscard]] constexpr float Length() const + { + return std::hypot(x, y, z); + } [[nodiscard]] constexpr float LengthSqr() const { return Vector2::LengthSqr() + z * z; } - [[nodiscard]] float Length2D() const; + [[nodiscard]] constexpr float Length2D() const + { + return Vector2::Length(); + } [[nodiscard]] constexpr Vector3 operator-() const { @@ -182,8 +191,12 @@ namespace omath [[nodiscard]] static Vector3 UpVector(float pitch, float yaw, float roll); - [[nodiscard]] - Vector3 Normalized() const; + [[nodiscard]] constexpr Vector3 Normalized() const + { + const float length = this->Length(); + + return length != 0 ? *this / length : *this; + } [[nodiscard]] std::tuple AsTuple() const { diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.h index 23067ddc..f7020c85 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.h @@ -31,7 +31,7 @@ namespace omath::projection [[nodiscard]] Mat<4, 4> GetViewMatrix() const; - [[nodiscard]] std::expected WorldToScreen(Vector3 worldPosition) const; + [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; ViewPort m_viewPort{}; float m_fieldOfView; diff --git a/source/Vector2.cpp b/source/Vector2.cpp index 66dc4f09..56d80449 100644 --- a/source/Vector2.cpp +++ b/source/Vector2.cpp @@ -8,18 +8,4 @@ namespace omath { - Vector2 Vector2::Normalized() const - { - const float len = Length(); - - if (len > 0.f) - return {x / len, y / len}; - - return {0.f, 0.f}; - } - - float Vector2::Length() const - { - return std::sqrt(x * x + y * y); - } } \ No newline at end of file diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 4d5d756a..8da7c680 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -8,23 +8,6 @@ namespace omath { - - float Vector3::DistTo(const Vector3 &vOther) const - { - return (*this - vOther).Length(); - } - - float Vector3::Length() const - { - return std::sqrt(Vector2::LengthSqr() + z * z); - } - - - float Vector3::Length2D() const - { - return Vector2::Length(); - } - Vector3 Vector3::ViewAngleTo(const Vector3 &other) const { const float distance = DistTo(other); @@ -83,12 +66,4 @@ namespace omath { return RightVector(pitch, yaw, roll).Cross(ForwardVector(pitch, yaw)); } - - - Vector3 Vector3::Normalized() const - { - const float length = this->Length(); - - return length != 0 ? *this / length : *this; - } } \ No newline at end of file diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index a7411395..e5b206e4 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -32,7 +32,7 @@ namespace omath::projection return Mat<4, 4>::TranslationMat(-m_origin) * Mat<4, 4>::OrientationMat(forward, right, up); } - std::expected Camera::WorldToScreen(Vector3 worldPosition) const + std::expected Camera::WorldToScreen(const Vector3& worldPosition) const { const auto posVecAsMatrix = Mat<1, 4>({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); @@ -54,6 +54,6 @@ namespace omath::projection projected *= Mat<4, 4>::ToScreenMat(m_viewPort.m_width, m_viewPort.m_height); - return Vector2{projected.At(0, 0), projected.At(0, 1)}; + return Vector3{projected.At(0, 0), projected.At(0, 1), projected.At(0, 2)}; } } diff --git a/tests/UnitTestMat.cpp b/tests/UnitTestMat.cpp index f76566a6..931c72f6 100644 --- a/tests/UnitTestMat.cpp +++ b/tests/UnitTestMat.cpp @@ -184,24 +184,6 @@ TEST_F(UnitTestMat, StaticMethod_OrientationMat) EXPECT_FLOAT_EQ(orientMat.At(2, 2), forward.z); } -// Test static method: ProjectionMat -TEST_F(UnitTestMat, StaticMethod_ProjectionMat) -{ - constexpr float fieldOfView = 45.0f; - constexpr float aspectRatio = 1.33f; - constexpr float near = 0.1f; - constexpr float far = 100.0f; - const Mat<4, 4> projMat = Mat<4, 4>::ProjectionMat(fieldOfView, aspectRatio, near, far, 1.335f); - - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); - - EXPECT_FLOAT_EQ(projMat.At(0, 0), 1.f / (aspectRatio * fovHalfTan)); - EXPECT_FLOAT_EQ(projMat.At(1, 1), 1.f / fovHalfTan); - EXPECT_FLOAT_EQ(projMat.At(2, 2), (far + near) / (far - near)); - EXPECT_FLOAT_EQ(projMat.At(2, 3), (2.f * near * far) / (far - near)); - EXPECT_FLOAT_EQ(projMat.At(3, 2), -1.f); -} - // Test exception handling in At() method TEST_F(UnitTestMat, Method_At_OutOfRange) { diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index a4199d17..2bb50f90 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -9,7 +9,11 @@ TEST(UnitTestProjection, Projection) { - const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.375f, 5000.f); + const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.375f, 5000.f, 1.335f); - camera.WorldToScreen({5000, 0, 0}); + const auto projected = camera.WorldToScreen({5000, 0, 0}); + + + EXPECT_TRUE(projected.has_value()); + EXPECT_EQ(projected->z, 1.f); } \ No newline at end of file From 79ba0116995a00e2f3e9cb95e49b44e246236b45 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 20 Oct 2024 00:27:33 +0300 Subject: [PATCH 097/795] fixed empty line --- source/projection/Camera.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index e5b206e4..41275b47 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -20,7 +20,6 @@ namespace omath::projection m_nearPlaneDistance = near; m_farPlaneDistance = far; m_lensZoom = lensZoom; - } Mat<4, 4> Camera::GetViewMatrix() const From 7aa1db5f27fc3d21089b2f7657ce51a7387cc547 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 20 Oct 2024 12:39:32 +0300 Subject: [PATCH 098/795] forgot to optimize --- include/omath/Mat.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/include/omath/Mat.h b/include/omath/Mat.h index e20410e0..188aea07 100644 --- a/include/omath/Mat.h +++ b/include/omath/Mat.h @@ -46,17 +46,13 @@ namespace omath constexpr Mat(const Mat& other) { - for (size_t i = 0; i < Rows; ++i) - for (size_t j = 0; j < Columns; ++j) - At(i, j) = other.At(i, j); + m_data = other.m_data; } constexpr Mat(Mat&& other) noexcept { - for (size_t i = 0; i < Rows; ++i) - for (size_t j = 0; j < Columns; ++j) - At(i, j) = other.At(i, j) ; + m_data = std::move(other.m_data); } [[nodiscard]] From 046b560025f7589798de7edad96acc7b1922b2a4 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 20 Oct 2024 08:35:21 -0700 Subject: [PATCH 099/795] fixed bug when std::hypot is not constexpr for MSVC --- include/omath/Vector2.h | 25 +++++++++++++------ include/omath/Vector3.h | 54 +++++++++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/include/omath/Vector2.h b/include/omath/Vector2.h index 0bb73e92..1ad0341a 100644 --- a/include/omath/Vector2.h +++ b/include/omath/Vector2.h @@ -114,11 +114,29 @@ namespace omath return x * vOther.x + y * vOther.y; } +#ifndef _MSC_VER [[nodiscard]] constexpr float Length() const { return std::hypot(x, y); } + [[nodiscard]] constexpr Vector2 Normalized() const + { + const float len = Length(); + return len > 0.f ? *this / len : *this; + } +#else + [[nodiscard]] float Length() const + { + return std::hypot(x, y); + } + + [[nodiscard]] Vector2 Normalized() const + { + const float len = Length(); + return len > 0.f ? *this / len : *this; + } +#endif [[nodiscard]] constexpr float LengthSqr() const { return x * x + y * y; @@ -169,13 +187,6 @@ namespace omath return {x / fl, y / fl}; } - // Normalize the vector - [[nodiscard]] constexpr Vector2 Normalized() const - { - const float len = Length(); - return len > 0.f ? *this / len : *this; - } - // Sum of elements [[nodiscard]] constexpr float Sum() const { diff --git a/include/omath/Vector3.h b/include/omath/Vector3.h index 1b75d39b..53ea6ac2 100644 --- a/include/omath/Vector3.h +++ b/include/omath/Vector3.h @@ -92,11 +92,6 @@ namespace omath return *this; } - [[nodiscard]] constexpr float DistTo(const Vector3& vOther) const - { - return (*this - vOther).Length(); - } - constexpr Vector3& Abs() { Vector2::Abs(); @@ -115,21 +110,56 @@ namespace omath return Vector2::Dot(vOther) + z * vOther.z; } +#ifndef _MSC_VER [[nodiscard]] constexpr float Length() const { return std::hypot(x, y, z); } - [[nodiscard]] constexpr float LengthSqr() const + [[nodiscard]] constexpr float Length2D() const { - return Vector2::LengthSqr() + z * z; + return Vector2::Length(); + } + [[nodiscard]] float DistTo(const Vector3& vOther) const + { + return (*this - vOther).Length(); } + [[nodiscard]] constexpr Vector3 Normalized() const + { + const float length = this->Length(); - [[nodiscard]] constexpr float Length2D() const + return length != 0 ? *this / length : *this; + } +#else + [[nodiscard]] float Length() const + { + return std::hypot(x, y, z); + } + + [[nodiscard]] Vector3 Normalized() const + { + const float length = this->Length(); + + return length != 0 ? *this / length : *this; + } + + [[nodiscard]] float Length2D() const { return Vector2::Length(); } + [[nodiscard]] float DistTo(const Vector3& vOther) const + { + return (*this - vOther).Length(); + } +#endif + + + [[nodiscard]] constexpr float LengthSqr() const + { + return Vector2::LengthSqr() + z * z; + } + [[nodiscard]] constexpr Vector3 operator-() const { return {-x, -y, -z}; @@ -190,14 +220,6 @@ namespace omath [[nodiscard]] static Vector3 RightVector(float pitch, float yaw, float roll); [[nodiscard]] static Vector3 UpVector(float pitch, float yaw, float roll); - - [[nodiscard]] constexpr Vector3 Normalized() const - { - const float length = this->Length(); - - return length != 0 ? *this / length : *this; - } - [[nodiscard]] std::tuple AsTuple() const { return std::make_tuple(x, y, z); From 103c3d38e48814c5680d53e5d547b18ee5a04d09 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 20 Oct 2024 00:32:34 +0300 Subject: [PATCH 100/795] moving from .h to .hpp to make thing more clear --- include/omath/{Angles.h => Angles.hpp} | 0 include/omath/{Color.h => Color.hpp} | 4 ++-- include/omath/{Mat.h => Mat.hpp} | 4 ++-- include/omath/{Matrix.h => Matrix.hpp} | 0 include/omath/{Vector2.h => Vector2.hpp} | 0 include/omath/{Vector3.h => Vector3.hpp} | 2 +- include/omath/{Vector4.h => Vector4.hpp} | 2 +- include/omath/pathfinding/{Astar.h => Astar.hpp} | 4 ++-- .../pathfinding/{NavigationMesh.h => NavigationMesh.hpp} | 2 +- include/omath/prediction/{Engine.h => Engine.hpp} | 6 +++--- include/omath/prediction/{Projectile.h => Projectile.hpp} | 2 +- include/omath/prediction/{Target.h => Target.hpp} | 2 +- include/omath/projection/{Camera.h => Camera.hpp} | 6 +++--- include/omath/projection/{ErrorCodes.h => ErrorCodes.hpp} | 0 source/Matrix.cpp | 6 +++--- source/Vector2.cpp | 2 +- source/Vector3.cpp | 4 ++-- source/Vector4.cpp | 2 +- source/color.cpp | 2 +- source/pathfinding/Astar.cpp | 2 +- source/pathfinding/NavigationMesh.cpp | 2 +- source/prediction/Engine.cpp | 4 ++-- source/prediction/Projectile.cpp | 2 +- source/prediction/Target.cpp | 2 +- source/projection/Camera.cpp | 4 ++-- tests/UnitTestAstar.cpp | 2 +- tests/UnitTestColor.cpp | 2 +- tests/UnitTestMat.cpp | 4 ++-- tests/UnitTestMatrix.cpp | 4 ++-- tests/UnitTestPrediction.cpp | 2 +- tests/UnitTestProjection.cpp | 4 ++-- tests/UnitTestVector2.cpp | 2 +- tests/UnitTestVector3.cpp | 2 +- tests/UnitTestVector4.cpp | 2 +- 34 files changed, 45 insertions(+), 45 deletions(-) rename include/omath/{Angles.h => Angles.hpp} (100%) rename include/omath/{Color.h => Color.hpp} (98%) rename include/omath/{Mat.h => Mat.hpp} (99%) rename include/omath/{Matrix.h => Matrix.hpp} (100%) rename include/omath/{Vector2.h => Vector2.hpp} (100%) rename include/omath/{Vector3.h => Vector3.hpp} (99%) rename include/omath/{Vector4.h => Vector4.hpp} (99%) rename include/omath/pathfinding/{Astar.h => Astar.hpp} (83%) rename include/omath/pathfinding/{NavigationMesh.h => NavigationMesh.hpp} (95%) rename include/omath/prediction/{Engine.h => Engine.hpp} (90%) rename include/omath/prediction/{Projectile.h => Projectile.hpp} (92%) rename include/omath/prediction/{Target.h => Target.hpp} (94%) rename include/omath/projection/{Camera.h => Camera.hpp} (92%) rename include/omath/projection/{ErrorCodes.h => ErrorCodes.hpp} (100%) diff --git a/include/omath/Angles.h b/include/omath/Angles.hpp similarity index 100% rename from include/omath/Angles.h rename to include/omath/Angles.hpp diff --git a/include/omath/Color.h b/include/omath/Color.hpp similarity index 98% rename from include/omath/Color.h rename to include/omath/Color.hpp index e8da221c..46255455 100644 --- a/include/omath/Color.h +++ b/include/omath/Color.hpp @@ -4,9 +4,9 @@ #pragma once -#include "omath/Vector3.h" +#include "omath/Vector3.hpp" #include -#include "omath/Vector4.h" +#include "omath/Vector4.hpp" namespace omath diff --git a/include/omath/Mat.h b/include/omath/Mat.hpp similarity index 99% rename from include/omath/Mat.h rename to include/omath/Mat.hpp index 188aea07..d19dbca9 100644 --- a/include/omath/Mat.h +++ b/include/omath/Mat.hpp @@ -6,9 +6,9 @@ #include #include #include -#include "Vector3.h" +#include "Vector3.hpp" #include -#include "Angles.h" +#include "Angles.hpp" namespace omath diff --git a/include/omath/Matrix.h b/include/omath/Matrix.hpp similarity index 100% rename from include/omath/Matrix.h rename to include/omath/Matrix.hpp diff --git a/include/omath/Vector2.h b/include/omath/Vector2.hpp similarity index 100% rename from include/omath/Vector2.h rename to include/omath/Vector2.hpp diff --git a/include/omath/Vector3.h b/include/omath/Vector3.hpp similarity index 99% rename from include/omath/Vector3.h rename to include/omath/Vector3.hpp index 53ea6ac2..ec108483 100644 --- a/include/omath/Vector3.h +++ b/include/omath/Vector3.hpp @@ -6,7 +6,7 @@ #include #include -#include "omath/Vector2.h" +#include "omath/Vector2.hpp" namespace omath diff --git a/include/omath/Vector4.h b/include/omath/Vector4.hpp similarity index 99% rename from include/omath/Vector4.h rename to include/omath/Vector4.hpp index 46d61aef..55c364c8 100644 --- a/include/omath/Vector4.h +++ b/include/omath/Vector4.hpp @@ -3,7 +3,7 @@ // #pragma once -#include +#include #include diff --git a/include/omath/pathfinding/Astar.h b/include/omath/pathfinding/Astar.hpp similarity index 83% rename from include/omath/pathfinding/Astar.h rename to include/omath/pathfinding/Astar.hpp index 64bfc885..06098503 100644 --- a/include/omath/pathfinding/Astar.h +++ b/include/omath/pathfinding/Astar.hpp @@ -4,8 +4,8 @@ #pragma once #include -#include "NavigationMesh.h" -#include "omath/Vector3.h" +#include "NavigationMesh.hpp" +#include "omath/Vector3.hpp" namespace omath::pathfinding diff --git a/include/omath/pathfinding/NavigationMesh.h b/include/omath/pathfinding/NavigationMesh.hpp similarity index 95% rename from include/omath/pathfinding/NavigationMesh.h rename to include/omath/pathfinding/NavigationMesh.hpp index 11e08bbe..e7aceb42 100644 --- a/include/omath/pathfinding/NavigationMesh.h +++ b/include/omath/pathfinding/NavigationMesh.hpp @@ -4,7 +4,7 @@ #pragma once -#include "omath/Vector3.h" +#include "omath/Vector3.hpp" #include #include #include diff --git a/include/omath/prediction/Engine.h b/include/omath/prediction/Engine.hpp similarity index 90% rename from include/omath/prediction/Engine.h rename to include/omath/prediction/Engine.hpp index af7876f8..6d8e6177 100644 --- a/include/omath/prediction/Engine.h +++ b/include/omath/prediction/Engine.hpp @@ -5,9 +5,9 @@ #pragma once #include -#include "omath/Vector3.h" -#include "omath/prediction/Projectile.h" -#include "omath/prediction/Target.h" +#include "omath/Vector3.hpp" +#include "omath/prediction/Projectile.hpp" +#include "omath/prediction/Target.hpp" namespace omath::prediction diff --git a/include/omath/prediction/Projectile.h b/include/omath/prediction/Projectile.hpp similarity index 92% rename from include/omath/prediction/Projectile.h rename to include/omath/prediction/Projectile.hpp index 1249e977..93ded293 100644 --- a/include/omath/prediction/Projectile.h +++ b/include/omath/prediction/Projectile.hpp @@ -3,7 +3,7 @@ // #pragma once -#include "omath/Vector3.h" +#include "omath/Vector3.hpp" namespace omath::prediction diff --git a/include/omath/prediction/Target.h b/include/omath/prediction/Target.hpp similarity index 94% rename from include/omath/prediction/Target.h rename to include/omath/prediction/Target.hpp index 0332c53c..e44b8680 100644 --- a/include/omath/prediction/Target.h +++ b/include/omath/prediction/Target.hpp @@ -3,7 +3,7 @@ // #pragma once -#include "omath/Vector3.h" +#include "omath/Vector3.hpp" namespace omath::prediction diff --git a/include/omath/projection/Camera.h b/include/omath/projection/Camera.hpp similarity index 92% rename from include/omath/projection/Camera.h rename to include/omath/projection/Camera.hpp index f7020c85..d2d450ec 100644 --- a/include/omath/projection/Camera.h +++ b/include/omath/projection/Camera.hpp @@ -5,10 +5,10 @@ #pragma once #include -#include -#include +#include +#include #include -#include "ErrorCodes.h" +#include "ErrorCodes.hpp" namespace omath::projection diff --git a/include/omath/projection/ErrorCodes.h b/include/omath/projection/ErrorCodes.hpp similarity index 100% rename from include/omath/projection/ErrorCodes.h rename to include/omath/projection/ErrorCodes.hpp diff --git a/source/Matrix.cpp b/source/Matrix.cpp index 8872774b..03096e85 100644 --- a/source/Matrix.cpp +++ b/source/Matrix.cpp @@ -1,6 +1,6 @@ -#include "omath/Matrix.h" -#include "omath/Vector3.h" -#include "omath/Angles.h" +#include "omath/Matrix.hpp" +#include "omath/Vector3.hpp" +#include "omath/Angles.hpp" #include diff --git a/source/Vector2.cpp b/source/Vector2.cpp index 56d80449..58188ace 100644 --- a/source/Vector2.cpp +++ b/source/Vector2.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 02.09.2024. // -#include "omath/Vector2.h" +#include "omath/Vector2.hpp" #include diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 8da7c680..acd2b027 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -2,9 +2,9 @@ // Created by vlad on 10/28/23. // -#include +#include #include -#include +#include namespace omath { diff --git a/source/Vector4.cpp b/source/Vector4.cpp index 1d54d804..a995337c 100644 --- a/source/Vector4.cpp +++ b/source/Vector4.cpp @@ -2,7 +2,7 @@ // Vector4.cpp // -#include "omath/Vector4.h" +#include "omath/Vector4.hpp" #include diff --git a/source/color.cpp b/source/color.cpp index 1de3bfdb..3e8b584b 100644 --- a/source/color.cpp +++ b/source/color.cpp @@ -2,7 +2,7 @@ // Created by vlad on 2/4/24. // -#include "omath/Color.h" +#include "omath/Color.hpp" #include #include diff --git a/source/pathfinding/Astar.cpp b/source/pathfinding/Astar.cpp index 10a03e9a..37ec8e10 100644 --- a/source/pathfinding/Astar.cpp +++ b/source/pathfinding/Astar.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 28.07.2024. // -#include "omath/pathfinding/Astar.h" +#include "omath/pathfinding/Astar.hpp" #include #include diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/NavigationMesh.cpp index 1a106dfa..87b9db26 100644 --- a/source/pathfinding/NavigationMesh.cpp +++ b/source/pathfinding/NavigationMesh.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 28.07.2024. // -#include "omath/pathfinding/NavigationMesh.h" +#include "omath/pathfinding/NavigationMesh.hpp" #include #include diff --git a/source/prediction/Engine.cpp b/source/prediction/Engine.cpp index 47d5ea54..08cb81d6 100644 --- a/source/prediction/Engine.cpp +++ b/source/prediction/Engine.cpp @@ -3,9 +3,9 @@ // -#include "omath/prediction/Engine.h" +#include "omath/prediction/Engine.hpp" #include -#include +#include namespace omath::prediction diff --git a/source/prediction/Projectile.cpp b/source/prediction/Projectile.cpp index 32cbe68c..26cfd8e7 100644 --- a/source/prediction/Projectile.cpp +++ b/source/prediction/Projectile.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 6/9/2024. // -#include "omath/prediction/Projectile.h" +#include "omath/prediction/Projectile.hpp" #include diff --git a/source/prediction/Target.cpp b/source/prediction/Target.cpp index 3b4c2da9..9b123959 100644 --- a/source/prediction/Target.cpp +++ b/source/prediction/Target.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 6/9/2024. // -#include "omath/prediction/Target.h" +#include "omath/prediction/Target.hpp" namespace omath::prediction diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index 41275b47..be552fab 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -1,11 +1,11 @@ // // Created by Vlad on 27.08.2024. // -#include "omath/projection/Camera.h" +#include "omath/projection/Camera.hpp" #include -#include "omath/Angles.h" +#include "omath/Angles.hpp" namespace omath::projection diff --git a/tests/UnitTestAstar.cpp b/tests/UnitTestAstar.cpp index f16cd8e0..da65f9f4 100644 --- a/tests/UnitTestAstar.cpp +++ b/tests/UnitTestAstar.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 18.08.2024. // #include -#include +#include TEST(UnitTestAstar, FindingRightPath) diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp index a78276dd..aade7f88 100644 --- a/tests/UnitTestColor.cpp +++ b/tests/UnitTestColor.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 01.09.2024. // #include -#include +#include using namespace omath; diff --git a/tests/UnitTestMat.cpp b/tests/UnitTestMat.cpp index 931c72f6..613325cf 100644 --- a/tests/UnitTestMat.cpp +++ b/tests/UnitTestMat.cpp @@ -1,7 +1,7 @@ // UnitTestMat.cpp #include -#include "omath/Mat.h" -#include "omath/Vector3.h" +#include "omath/Mat.hpp" +#include "omath/Vector3.hpp" using namespace omath; diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index 7450bc61..c9a8b932 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -2,8 +2,8 @@ // Created by vlad on 5/18/2024. // #include -#include -#include "omath/Vector3.h" +#include +#include "omath/Vector3.hpp" using namespace omath; diff --git a/tests/UnitTestPrediction.cpp b/tests/UnitTestPrediction.cpp index c57d79be..64fe5780 100644 --- a/tests/UnitTestPrediction.cpp +++ b/tests/UnitTestPrediction.cpp @@ -1,5 +1,5 @@ #include -#include +#include TEST(UnitTestPrediction, PredictionTest) { diff --git a/tests/UnitTestProjection.cpp b/tests/UnitTestProjection.cpp index 2bb50f90..34246333 100644 --- a/tests/UnitTestProjection.cpp +++ b/tests/UnitTestProjection.cpp @@ -3,9 +3,9 @@ // #include #include -#include +#include #include -#include +#include TEST(UnitTestProjection, Projection) { diff --git a/tests/UnitTestVector2.cpp b/tests/UnitTestVector2.cpp index dc74b298..0255363a 100644 --- a/tests/UnitTestVector2.cpp +++ b/tests/UnitTestVector2.cpp @@ -3,7 +3,7 @@ // #include -#include +#include #include // For std::isinf and std::isnan #include // For FLT_MAX and FLT_MIN diff --git a/tests/UnitTestVector3.cpp b/tests/UnitTestVector3.cpp index 67625bba..f08a35f8 100644 --- a/tests/UnitTestVector3.cpp +++ b/tests/UnitTestVector3.cpp @@ -3,7 +3,7 @@ // #include -#include +#include #include #include // For FLT_MAX, FLT_MIN #include // For std::numeric_limits diff --git a/tests/UnitTestVector4.cpp b/tests/UnitTestVector4.cpp index f9c947f2..07ca8989 100644 --- a/tests/UnitTestVector4.cpp +++ b/tests/UnitTestVector4.cpp @@ -6,7 +6,7 @@ // #include -#include +#include #include // For std::numeric_limits using namespace omath; From 6c85515dd0c5fe2ea91118804580553e288d09e9 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 20 Oct 2024 08:52:11 -0700 Subject: [PATCH 101/795] small fixes --- include/omath/Vector2.hpp | 2 +- source/prediction/Engine.cpp | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/include/omath/Vector2.hpp b/include/omath/Vector2.hpp index 1ad0341a..ba5017e5 100644 --- a/include/omath/Vector2.hpp +++ b/include/omath/Vector2.hpp @@ -18,7 +18,7 @@ namespace omath // Constructors constexpr Vector2() : x(0.f), y(0.f) {} - constexpr Vector2(float x, float y) : x(x), y(y) {} + constexpr Vector2(const float x, const float y) : x(x), y(y) {} // Equality operators [[nodiscard]] diff --git a/source/prediction/Engine.cpp b/source/prediction/Engine.cpp index 08cb81d6..2bc1b1c6 100644 --- a/source/prediction/Engine.cpp +++ b/source/prediction/Engine.cpp @@ -11,11 +11,11 @@ namespace omath::prediction { Engine::Engine(const float gravityConstant, const float simulationTimeStep, - const float maximumSimulationTime, const float distanceTolerance) - : m_gravityConstant(gravityConstant), - m_simulationTimeStep(simulationTimeStep), - m_maximumSimulationTime(maximumSimulationTime), - m_distanceTolerance(distanceTolerance) + const float maximumSimulationTime, const float distanceTolerance) + : m_gravityConstant(gravityConstant), + m_simulationTimeStep(simulationTimeStep), + m_maximumSimulationTime(maximumSimulationTime), + m_distanceTolerance(distanceTolerance) { } @@ -28,7 +28,7 @@ namespace omath::prediction const auto projectilePitch = MaybeCalculateProjectileLaunchPitchAngle(projectile, predictedTargetPosition); if (!projectilePitch.has_value()) [[unlikely]] - continue; + continue; if (!IsProjectileReachedTarget(predictedTargetPosition, projectile, projectilePitch.value(), time)) continue; @@ -42,19 +42,20 @@ namespace omath::prediction } std::optional Engine::MaybeCalculateProjectileLaunchPitchAngle(const Projectile &projectile, - const Vector3 &targetPosition) const + const Vector3 &targetPosition) const { const auto bulletGravity = m_gravityConstant * projectile.m_gravityScale; const auto delta = targetPosition - projectile.m_origin; const auto distance2d = delta.Length2D(); + const auto distance2dSqr = distance2d * distance2d; + const auto launchSpeedSqr = projectile.m_launchSpeed * projectile.m_launchSpeed; - - float root = std::pow(projectile.m_launchSpeed, 4.f) - bulletGravity * (bulletGravity * - std::pow(distance2d, 2.f) + 2.0f * delta.z * std::pow(projectile.m_launchSpeed, 2.f)); + float root = launchSpeedSqr * launchSpeedSqr - bulletGravity * (bulletGravity * + distance2dSqr + 2.0f * delta.z * launchSpeedSqr); if (root < 0.0f) [[unlikely]] - return std::nullopt; + return std::nullopt; root = std::sqrt(root); const float angle = std::atan((std::pow(projectile.m_launchSpeed, 2.f) - root) / (bulletGravity * distance2d)); @@ -63,7 +64,7 @@ namespace omath::prediction } bool Engine::IsProjectileReachedTarget(const Vector3 &targetPosition, const Projectile &projectile, - const float pitch, const float time) const + const float pitch, const float time) const { const auto yaw = projectile.m_origin.ViewAngleTo(targetPosition).y; const auto projectilePosition = projectile.PredictPosition(pitch, yaw, time, m_gravityConstant); From 50c322dbfcced270d45074635d96ae2f9a550fe5 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 20 Oct 2024 09:01:18 -0700 Subject: [PATCH 102/795] Mat support multiple integral types --- include/omath/Mat.hpp | 118 ++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index d19dbca9..c41bfb91 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -13,18 +13,17 @@ namespace omath { - template + template class Mat final { public: - constexpr Mat() { Clear(); } - constexpr Mat(const std::initializer_list>& rows) + constexpr Mat(const std::initializer_list > &rows) { if (rows.size() != Rows) throw std::invalid_argument("Initializer list rows size does not match template parameter Rows"); @@ -33,7 +32,8 @@ namespace omath for (size_t i = 0; i < Rows; ++i, ++rowIt) { if (rowIt->size() != Columns) - throw std::invalid_argument("All rows must have the same number of columns as template parameter Columns"); + throw std::invalid_argument( + "All rows must have the same number of columns as template parameter Columns"); auto colIt = rowIt->begin(); for (size_t j = 0; j < Columns; ++j, ++colIt) @@ -44,13 +44,13 @@ namespace omath } - constexpr Mat(const Mat& other) + constexpr Mat(const Mat &other) noexcept { m_data = other.m_data; } - constexpr Mat(Mat&& other) noexcept + constexpr Mat(Mat &&other) noexcept { m_data = std::move(other.m_data); } @@ -62,24 +62,26 @@ namespace omath static consteval size_t ColumnsCount() noexcept { return Columns; } [[nodiscard]] - static consteval std::pair Size() noexcept { return { Rows, Columns }; } + static consteval std::pair Size() noexcept { return {Rows, Columns}; } - [[nodiscard]] constexpr const float& At(const size_t rowIndex, const size_t columnIndex) const + [[nodiscard]] constexpr const Type &At(const size_t rowIndex, const size_t columnIndex) const { if (rowIndex >= Rows || columnIndex >= Columns) throw std::out_of_range("Index out of range"); return m_data[rowIndex * Columns + columnIndex]; } - [[nodiscard]] constexpr float& At(const size_t rowIndex, const size_t columnIndex) + + [[nodiscard]] constexpr Type &At(const size_t rowIndex, const size_t columnIndex) { - return const_cast(std::as_const(*this).At(rowIndex, columnIndex)); + return const_cast(std::as_const(*this).At(rowIndex, columnIndex)); } + [[nodiscard]] - constexpr float Sum() const + constexpr Type Sum() const noexcept { - float sum = 0.f; + Type sum = 0; for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) sum += At(i, j); @@ -87,25 +89,26 @@ namespace omath return sum; } - constexpr void Clear() + constexpr void Clear() noexcept { - Set(0.f); + Set(0); } - constexpr void Set(const float value) + constexpr void Set(const Type &value) noexcept { std::ranges::fill(m_data, value); } + // Operator overloading for multiplication with another Mat - template - constexpr Mat operator*(const Mat& other) const + template + constexpr Mat operator*(const Mat &other) const { Mat result; for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < OtherColumns; ++j) { - float sum = 0.f; + Type sum = 0; for (size_t k = 0; k < Columns; ++k) sum += At(i, k) * other.At(k, j); result.At(i, j) = sum; @@ -113,7 +116,7 @@ namespace omath return result; } - constexpr Mat& operator*=(float f) + constexpr Mat &operator*=(Type f) noexcept { for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) @@ -121,20 +124,20 @@ namespace omath return *this; } - template - constexpr Mat operator*=(const Mat& other) + template + constexpr Mat operator*=(const Mat &other) { return *this = *this * other; } - constexpr Mat operator*(float f) const + constexpr Mat operator*(const Type &f) const noexcept { Mat result(*this); result *= f; return result; } - constexpr Mat& operator/=(float f) + constexpr Mat &operator/=(const Type &f) noexcept { for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) @@ -142,14 +145,14 @@ namespace omath return *this; } - constexpr Mat operator/(float f) const + constexpr Mat operator/(const Type &f) const noexcept { Mat result(*this); result /= f; return result; } - constexpr Mat& operator=(const Mat& other) + constexpr Mat &operator=(const Mat &other) noexcept { if (this == &other) return *this; @@ -159,7 +162,7 @@ namespace omath return *this; } - constexpr Mat& operator=(Mat&& other) noexcept + constexpr Mat &operator=(Mat &&other) noexcept { if (this == &other) return *this; @@ -172,7 +175,7 @@ namespace omath } [[nodiscard]] - constexpr Mat Transpose() const + constexpr Mat Transpose() const noexcept { Mat transposed; for (size_t i = 0; i < Rows; ++i) @@ -183,7 +186,7 @@ namespace omath } [[nodiscard]] - constexpr float Determinant() const + constexpr Type Determinant() const { static_assert(Rows == Columns, "Determinant is only defined for square matrices."); @@ -194,10 +197,10 @@ namespace omath return At(0, 0) * At(1, 1) - At(0, 1) * At(1, 0); else { - float det = 0.f; + Type det = 0; for (size_t i = 0; i < Columns; ++i) { - const float cofactor = (i % 2 == 0 ? 1.f : -1.f) * At(0, i) * Minor(0, i).Determinant(); + const Type cofactor = (i % 2 == 0 ? 1 : -1) * At(0, i) * Minor(0, i).Determinant(); det += cofactor; } return det; @@ -225,7 +228,7 @@ namespace omath } [[nodiscard]] - std::string ToString() const + std::string ToString() const noexcept { std::ostringstream oss; for (size_t i = 0; i < Rows; ++i) @@ -243,58 +246,59 @@ namespace omath // Static methods that return fixed-size matrices [[nodiscard]] - constexpr static Mat<4, 4> ToScreenMat(const float screenWidth, const float screenHeight) + constexpr static Mat<4, 4> ToScreenMat(const Type &screenWidth, const Type &screenHeight) noexcept { return { - {screenWidth / 2.f, 0.f, 0.f, 0.f}, - {0.f, -screenHeight / 2.f, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {screenWidth / 2.f, screenHeight / 2.f, 0.f, 1.f}, - }; + {screenWidth / 2, 0, 0, 0}, + {0, -screenHeight / 2, 0, 0}, + {0, 0, 1, 0}, + {screenWidth / 2, screenHeight / 2, 0, 1}, + }; } [[nodiscard]] - constexpr static Mat<4, 4> TranslationMat(const Vector3& diff) + constexpr static Mat<4, 4> TranslationMat(const Vector3 &diff) noexcept { return { - {1.f, 0.f, 0.f, 0.f}, - {0.f, 1.f, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {diff.x, diff.y, diff.z, 1.f}, + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {diff.x, diff.y, diff.z, 1}, }; } [[nodiscard]] - constexpr static Mat<4, 4> OrientationMat(const Vector3& forward, const Vector3& right, const Vector3& up) + constexpr static Mat<4, 4> OrientationMat(const Vector3 &forward, const Vector3 &right, + const Vector3 &up) noexcept { return { - {right.x, up.x, forward.x, 0.f}, - {right.y, up.y, forward.y, 0.f}, - {right.z, up.z, forward.z, 0.f}, - {0.f, 0.f, 0.f, 1.f}, + {right.x, up.x, forward.x, 0}, + {right.y, up.y, forward.y, 0}, + {right.z, up.z, forward.z, 0}, + {0, 0, 0, 1}, }; } [[nodiscard]] - constexpr static Mat<4, 4> ProjectionMat(const float fieldOfView, const float aspectRatio, - const float near, const float far, const float lensZoom) + constexpr static Mat<4, 4> ProjectionMat(const Type &fieldOfView, const Type &aspectRatio, + const Type &near, const Type &far, const Type &lensZoom) noexcept { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); - const float frustumHeight = far - near; + const Type &fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); + const Type &frustumHeight = far - near; return { - {-1.f / (aspectRatio * fovHalfTan) * lensZoom, 0.f, 0.f, 0.f}, - {0.f, -1.f / fovHalfTan * lensZoom, 0.f, 0.f}, - {0.f, 0.f, -far / frustumHeight, -1}, - {0.f, 0.f, near * far / frustumHeight, 0.f} + {-1 / (aspectRatio * fovHalfTan) * lensZoom, 0, 0, 0}, + {0, -1 / fovHalfTan * lensZoom, 0, 0}, + {0, 0, -far / frustumHeight, -1}, + {0, 0, near * far / frustumHeight, 0} }; } private: - std::array m_data; + std::array m_data; }; -} \ No newline at end of file +} From 68f583c93676289a3de971c09f22062948e2a7d0 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 20 Oct 2024 09:58:55 -0700 Subject: [PATCH 103/795] fix --- include/omath/Mat.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index c41bfb91..c68dc954 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -116,7 +116,7 @@ namespace omath return result; } - constexpr Mat &operator*=(Type f) noexcept + constexpr Mat &operator*=(const Type& f) noexcept { for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) From 295cc35cf09917e76bfc1955bee516082a080241 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 21 Oct 2024 05:31:04 -0700 Subject: [PATCH 104/795] renamed method --- include/omath/Matrix.hpp | 2 +- source/Matrix.cpp | 2 +- tests/UnitTestMatrix.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/omath/Matrix.hpp b/include/omath/Matrix.hpp index a191e37f..2488815d 100644 --- a/include/omath/Matrix.hpp +++ b/include/omath/Matrix.hpp @@ -93,7 +93,7 @@ namespace omath Matrix operator/(float f) const; [[nodiscard]] - std::string ToSrtring() const; + std::string ToString() const; ~Matrix(); diff --git a/source/Matrix.cpp b/source/Matrix.cpp index 03096e85..de92ab9b 100644 --- a/source/Matrix.cpp +++ b/source/Matrix.cpp @@ -213,7 +213,7 @@ namespace omath return out; } - std::string Matrix::ToSrtring() const + std::string Matrix::ToString() const { std::string str; diff --git a/tests/UnitTestMatrix.cpp b/tests/UnitTestMatrix.cpp index c9a8b932..bc749d07 100644 --- a/tests/UnitTestMatrix.cpp +++ b/tests/UnitTestMatrix.cpp @@ -145,7 +145,7 @@ TEST_F(UnitTestMatrix, Clear) TEST_F(UnitTestMatrix, ToString) { - const std::string str = m2.ToSrtring(); + const std::string str = m2.ToString(); EXPECT_FALSE(str.empty()); } From 4766e46c4fc36be194a36f592b602f26c48b0b9c Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 21 Oct 2024 15:30:08 -0700 Subject: [PATCH 105/795] swithed to MIT --- LICENSE | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/LICENSE b/LICENSE index 00d2e135..0fccfbbf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,24 +1,9 @@ -This is free and unencumbered software released into the public domain. +The MIT License (MIT) -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. +Copyright (c) 2024 Orange++ -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -For more information, please refer to \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file From 455e8d250966dcbbbfc998fed2b1758030a95e30 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Tue, 22 Oct 2024 04:30:49 -0700 Subject: [PATCH 106/795] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 2e6f408c..07bb7c43 100644 --- a/readme.md +++ b/readme.md @@ -56,7 +56,7 @@ TEST(UnitTestProjection, IsPointOnScreen) With `omath/projection` module you can achieve simple ESP hack for powered by Source/Unreal/Unity engine games, like [Apex Legends](https://store.steampowered.com/app/1172470/Apex_Legends/). ![banner](https://i.imgur.com/lcJrfcZ.png) - +![Watch Video](https://youtu.be/lM_NJ1yCunw?si=5E87OrQMeypxSJ3E) From 918036605b28eccb511f7506a90f814e1773ed1e Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 14 Nov 2024 06:37:41 +0300 Subject: [PATCH 107/795] added trace line --- include/omath/Triangle3d.hpp | 32 ++++++++++++ include/omath/collision/LineTracer.hpp | 22 +++++++++ source/CMakeLists.txt | 7 ++- source/Triangle3d.cpp | 36 ++++++++++++++ source/collision/CMakeLists.txt | 1 + source/collision/LineTracer.cpp | 40 +++++++++++++++ tests/CMakeLists.txt | 4 +- tests/UnitTestLineTrace.cpp | 67 ++++++++++++++++++++++++++ 8 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 include/omath/Triangle3d.hpp create mode 100644 include/omath/collision/LineTracer.hpp create mode 100644 source/Triangle3d.cpp create mode 100644 source/collision/CMakeLists.txt create mode 100644 source/collision/LineTracer.cpp create mode 100644 tests/UnitTestLineTrace.cpp diff --git a/include/omath/Triangle3d.hpp b/include/omath/Triangle3d.hpp new file mode 100644 index 00000000..5f6df62e --- /dev/null +++ b/include/omath/Triangle3d.hpp @@ -0,0 +1,32 @@ +// +// Created by Orange on 11/13/2024. +// +#pragma once +#include "omath/Vector3.hpp" + +namespace omath +{ + class Triangle3d + { + public: + Triangle3d(const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3); + Vector3 m_vertex1; + Vector3 m_vertex2; + Vector3 m_vertex3; + + [[nodiscard]] + Vector3 CalculateNormal() const; + + [[nodiscard]] + float SideALength() const; + + [[nodiscard]] + float SideBLength() const; + + [[nodiscard]] + Vector3 SideAVector() const; + + [[nodiscard]] + Vector3 SideBVector() const; + }; +} \ No newline at end of file diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/LineTracer.hpp new file mode 100644 index 00000000..aef9a996 --- /dev/null +++ b/include/omath/collision/LineTracer.hpp @@ -0,0 +1,22 @@ +// +// Created by Orange on 11/13/2024. +// +#pragma once +#include "omath/Vector3.hpp" +#include "omath/Triangle3d.hpp" +namespace omath::collision +{ + struct Ray + { + Vector3 start; + Vector3 end; + }; + class LineTracer + { + public: + LineTracer() = delete; + + [[nodiscard]] + static bool CanTraceLine(const Ray& ray, const Triangle3d& triangle); + }; +} diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 9d79f16a..ab4e8249 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -3,8 +3,11 @@ target_sources(omath PRIVATE Matrix.cpp color.cpp Vector4.cpp - Vector2.cpp) + Vector2.cpp + Triangle3d.cpp +) add_subdirectory(prediction) add_subdirectory(pathfinding) -add_subdirectory(projection) \ No newline at end of file +add_subdirectory(projection) +add_subdirectory(collision) \ No newline at end of file diff --git a/source/Triangle3d.cpp b/source/Triangle3d.cpp new file mode 100644 index 00000000..54dcf758 --- /dev/null +++ b/source/Triangle3d.cpp @@ -0,0 +1,36 @@ +#include "omath/Triangle3d.hpp" + + +namespace omath +{ + Triangle3d::Triangle3d(const Vector3 &vertex1, const Vector3 &vertex2, const Vector3 &vertex3) + : m_vertex1(vertex1), m_vertex2(vertex2), m_vertex3(vertex3) + { + + } + + Vector3 Triangle3d::CalculateNormal() const + { + return (m_vertex1 - m_vertex2).Cross(m_vertex3 - m_vertex1).Normalized(); + } + + float Triangle3d::SideALength() const + { + return m_vertex1.DistTo(m_vertex2); + } + + float Triangle3d::SideBLength() const + { + return m_vertex3.DistTo(m_vertex2); + } + + Vector3 Triangle3d::SideAVector() const + { + return m_vertex1 - m_vertex2; + } + + Vector3 Triangle3d::SideBVector() const + { + return m_vertex3 - m_vertex2; + } +} diff --git a/source/collision/CMakeLists.txt b/source/collision/CMakeLists.txt new file mode 100644 index 00000000..2904603b --- /dev/null +++ b/source/collision/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(omath PRIVATE LineTracer.cpp) diff --git a/source/collision/LineTracer.cpp b/source/collision/LineTracer.cpp new file mode 100644 index 00000000..7daefdcd --- /dev/null +++ b/source/collision/LineTracer.cpp @@ -0,0 +1,40 @@ +// +// Created by Orange on 11/13/2024. +// +#pragma once +#include "omath/collision/LineTracer.hpp" + +namespace omath::collision +{ + bool LineTracer::CanTraceLine(const Ray &ray, const Triangle3d &triangle) + { + const auto sideA = triangle.SideAVector(); + const auto sideB = triangle.SideBVector(); + + const auto rayDir = ray.end - ray.start; + + const auto p = rayDir.Cross(sideB); + + const auto det = sideA.Dot(p); + + if (std::abs(det) < 1e-6) + return true; + + const auto invDet = 1 / det; + + const auto t = ray.start - triangle.m_vertex2; + + const auto u = t.Dot(p) * invDet; + + if (u < 0.f || u > 1.f) + return true; + + const auto q = t.Cross(sideA); + const auto v = rayDir.Dot(q) * invDet; + + if (v < 0.f || u + v > 1.f) + return true; + + return sideB.Dot(q) * invDet <= 0.f; + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 90f0beaf..1c604c12 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,7 +13,9 @@ add_executable(unit-tests UnitTestVector3.cpp UnitTestVector2.cpp UnitTestColor.cpp - UnitTestVector4.cpp) + UnitTestVector4.cpp + UnitTestLineTrace.cpp +) target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) diff --git a/tests/UnitTestLineTrace.cpp b/tests/UnitTestLineTrace.cpp new file mode 100644 index 00000000..722a1508 --- /dev/null +++ b/tests/UnitTestLineTrace.cpp @@ -0,0 +1,67 @@ +#include "gtest/gtest.h" +#include "omath/collision/LineTracer.hpp" +#include "omath/Triangle3d.hpp" +#include "omath/Vector3.hpp" + +using namespace omath; +using namespace omath::collision; + +class LineTracerTest : public ::testing::Test +{ +protected: + // Set up common variables for use in each test + Vector3 vertex1{0.0f, 0.0f, 0.0f}; + Vector3 vertex2{1.0f, 0.0f, 0.0f}; + Vector3 vertex3{0.0f, 1.0f, 0.0f}; + Triangle3d triangle{vertex1, vertex2, vertex3}; +}; + +// Test that a ray intersecting the triangle returns false for CanTraceLine +TEST_F(LineTracerTest, RayIntersectsTriangle) +{ + constexpr Ray ray{{0.3f, 0.3f, -1.0f}, {0.3f, 0.3f, 1.0f}}; + EXPECT_FALSE(LineTracer::CanTraceLine(ray, triangle)); +} + +// Test that a ray parallel to the triangle plane returns true for CanTraceLine +TEST_F(LineTracerTest, RayParallelToTriangle) +{ + constexpr Ray ray{{0.3f, 0.3f, 1.0f}, {0.3f, 0.3f, 2.0f}}; + EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); +} + +// Test that a ray starting inside the triangle but pointing away returns true +TEST_F(LineTracerTest, RayStartsInTriangleButDoesNotIntersect) +{ + constexpr Ray ray{{0.3f, 0.3f, 0.0f}, {0.3f, 0.3f, -1.0f}}; + EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); +} + +// Test that a ray not intersecting the triangle plane returns true +TEST_F(LineTracerTest, RayMissesTriangle) +{ + constexpr Ray ray{{2.0f, 2.0f, -1.0f}, {2.0f, 2.0f, 1.0f}}; + EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); +} + +// Test that a ray lying exactly in the plane of the triangle without intersecting returns true +TEST_F(LineTracerTest, RayInPlaneNotIntersecting) +{ + constexpr Ray ray{{-1.0f, -1.0f, 0.0f}, {1.5f, 1.5f, 0.0f}}; + EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); +} + +// Test edge case where the ray exactly intersects one of the triangle's vertices, expecting false +TEST_F(LineTracerTest, RayIntersectsVertex) +{ + const Ray ray{{-1.0f, -1.0f, -1.0f}, vertex1}; // Intersecting at vertex1 + EXPECT_FALSE(LineTracer::CanTraceLine(ray, triangle)); +} + +// Test edge case where the ray exactly intersects one of the triangle's edges, expecting false +TEST_F(LineTracerTest, RayIntersectsEdge) +{ + constexpr Ray ray{{-1.0f, 0.0f, -1.0f}, {0.5f, 0.0f, 0.0f}}; + // Intersecting on the edge between vertex1 and vertex2 + EXPECT_FALSE(LineTracer::CanTraceLine(ray, triangle)); +} From 0ba787f6e0cc07c7088d9535dec3b588620f180d Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 14 Nov 2024 07:30:58 +0300 Subject: [PATCH 108/795] improved traceline --- include/omath/collision/LineTracer.hpp | 19 ++++++++++- source/collision/LineTracer.cpp | 45 +++++++++++++++++++------- tests/UnitTestLineTrace.cpp | 21 +++++++++--- 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/LineTracer.hpp index aef9a996..eb1f8fc6 100644 --- a/include/omath/collision/LineTracer.hpp +++ b/include/omath/collision/LineTracer.hpp @@ -2,14 +2,25 @@ // Created by Orange on 11/13/2024. // #pragma once +#include + #include "omath/Vector3.hpp" #include "omath/Triangle3d.hpp" + + namespace omath::collision { - struct Ray + class Ray { + public: Vector3 start; Vector3 end; + + [[nodiscard]] + Vector3 DirectionVector() const; + + [[nodiscard]] + Vector3 DirectionVectorNormalized() const; }; class LineTracer { @@ -18,5 +29,11 @@ namespace omath::collision [[nodiscard]] static bool CanTraceLine(const Ray& ray, const Triangle3d& triangle); + + + // Realization of Möller–Trumbore intersection algorithm + // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm + [[nodiscard]] + static std::optional GetRayHitPoint(const Ray& ray, const Triangle3d& triangle); }; } diff --git a/source/collision/LineTracer.cpp b/source/collision/LineTracer.cpp index 7daefdcd..602b92f7 100644 --- a/source/collision/LineTracer.cpp +++ b/source/collision/LineTracer.cpp @@ -8,33 +8,56 @@ namespace omath::collision { bool LineTracer::CanTraceLine(const Ray &ray, const Triangle3d &triangle) { + return GetRayHitPoint(ray, triangle) == ray.end; + } + Vector3 Ray::DirectionVector() const + { + return end - start; + } + + Vector3 Ray::DirectionVectorNormalized() const + { + return DirectionVector().Normalized(); + } + + std::optional LineTracer::GetRayHitPoint(const Ray &ray, const Triangle3d &triangle) + { + constexpr float kEpsilon = std::numeric_limits::epsilon(); + const auto sideA = triangle.SideAVector(); const auto sideB = triangle.SideBVector(); - const auto rayDir = ray.end - ray.start; - const auto p = rayDir.Cross(sideB); + const auto rayDir = ray.DirectionVector(); + const auto p = rayDir.Cross(sideB); const auto det = sideA.Dot(p); - if (std::abs(det) < 1e-6) - return true; - const auto invDet = 1 / det; + if (std::abs(det) < kEpsilon) + return ray.end; + const auto invDet = 1.0f / det; const auto t = ray.start - triangle.m_vertex2; - const auto u = t.Dot(p) * invDet; - if (u < 0.f || u > 1.f) - return true; + + if ((u < 0 && std::abs(u) > kEpsilon) || (u > 1 && std::abs(u-1) > kEpsilon)) + return ray.end; const auto q = t.Cross(sideA); const auto v = rayDir.Dot(q) * invDet; - if (v < 0.f || u + v > 1.f) - return true; - return sideB.Dot(q) * invDet <= 0.f; + if ((v < 0 && std::abs(v) > kEpsilon) || (u + v > 1 && std::abs(u + v - 1) > kEpsilon)) + return ray.end; + + const auto tHit = sideB.Dot(q) * invDet; + + + if (tHit <= kEpsilon) + return ray.end; + + return ray.start + rayDir * tHit; } } diff --git a/tests/UnitTestLineTrace.cpp b/tests/UnitTestLineTrace.cpp index 722a1508..67884d94 100644 --- a/tests/UnitTestLineTrace.cpp +++ b/tests/UnitTestLineTrace.cpp @@ -51,17 +51,30 @@ TEST_F(LineTracerTest, RayInPlaneNotIntersecting) EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); } -// Test edge case where the ray exactly intersects one of the triangle's vertices, expecting false + TEST_F(LineTracerTest, RayIntersectsVertex) { const Ray ray{{-1.0f, -1.0f, -1.0f}, vertex1}; // Intersecting at vertex1 - EXPECT_FALSE(LineTracer::CanTraceLine(ray, triangle)); + EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); } -// Test edge case where the ray exactly intersects one of the triangle's edges, expecting false TEST_F(LineTracerTest, RayIntersectsEdge) { constexpr Ray ray{{-1.0f, 0.0f, -1.0f}, {0.5f, 0.0f, 0.0f}}; // Intersecting on the edge between vertex1 and vertex2 - EXPECT_FALSE(LineTracer::CanTraceLine(ray, triangle)); + EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); +} + +TEST_F(LineTracerTest, TriangleFarBeyondRayEndPoint) +{ + // Define a ray with a short length + constexpr Ray ray{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}; + + // Define a triangle far beyond the ray's endpoint + const Triangle3d distantTriangle{ + {1000.0f, 1000.0f, 1000.0f}, {1001.0f, 1000.0f, 1000.0f}, {1000.0f, 1001.0f, 1000.0f} + }; + + // Expect true because the ray ends long before it could reach the distant triangle + EXPECT_TRUE(LineTracer::CanTraceLine(ray, distantTriangle)); } From a0b9d35eddbca6e6bb5a80ba999f2aeeaf7f1255 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 15 Nov 2024 11:28:13 +0300 Subject: [PATCH 109/795] fixed trace line, improved collision --- CMakeLists.txt | 5 ++++- include/omath/collision/Cube.h | 22 ++++++++++++++++++++++ include/omath/collision/ICollidable.h | 20 ++++++++++++++++++++ include/omath/collision/LineTracer.hpp | 3 +-- source/collision/CMakeLists.txt | 5 ++++- source/collision/Cube.cpp | 13 +++++++++++++ source/collision/LineTracer.cpp | 2 +- 7 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 include/omath/collision/Cube.h create mode 100644 include/omath/collision/ICollidable.h create mode 100644 source/collision/Cube.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 72ca0fb0..8ce6573b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,10 @@ option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Vector3.cpp) else() - add_library(omath STATIC source/Vector3.cpp) + add_library(omath STATIC source/Vector3.cpp + include/omath/collision/ICollidable.h + include/omath/collision/Cube.h + source/collision/Cube.cpp) endif() add_subdirectory(source) diff --git a/include/omath/collision/Cube.h b/include/omath/collision/Cube.h new file mode 100644 index 00000000..4c43796b --- /dev/null +++ b/include/omath/collision/Cube.h @@ -0,0 +1,22 @@ +// +// Created by vlad on 11/15/2024. +// +#pragma once +#include "ICollidable.h" + +namespace omath::collision +{ + class Cube final : public ICollidable + { + public: + + [[nodiscard]] + bool IsCollideWith(const std::shared_ptr& other) override; + + private: + [[nodiscard]] + bool IsCollideWithCube(const Cube& other); + bool IsCollideWithCapsule(const Cube& other); + + }; +} \ No newline at end of file diff --git a/include/omath/collision/ICollidable.h b/include/omath/collision/ICollidable.h new file mode 100644 index 00000000..6d894641 --- /dev/null +++ b/include/omath/collision/ICollidable.h @@ -0,0 +1,20 @@ +// +// Created by vlad on 11/15/2024. +// +#pragma once +#include "ICollidable.h" +#include + + + +namespace omath::collision +{ + class ICollidable + { + public: + virtual ~ICollidable() = default; + + [[nodiscard]] + virtual bool IsCollideWith(const std::shared_ptr& other) = 0; + }; +} diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/LineTracer.hpp index eb1f8fc6..1de6d1e7 100644 --- a/include/omath/collision/LineTracer.hpp +++ b/include/omath/collision/LineTracer.hpp @@ -2,7 +2,6 @@ // Created by Orange on 11/13/2024. // #pragma once -#include #include "omath/Vector3.hpp" #include "omath/Triangle3d.hpp" @@ -34,6 +33,6 @@ namespace omath::collision // Realization of Möller–Trumbore intersection algorithm // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm [[nodiscard]] - static std::optional GetRayHitPoint(const Ray& ray, const Triangle3d& triangle); + static Vector3 GetRayHitPoint(const Ray& ray, const Triangle3d& triangle); }; } diff --git a/source/collision/CMakeLists.txt b/source/collision/CMakeLists.txt index 2904603b..c82e0fb2 100644 --- a/source/collision/CMakeLists.txt +++ b/source/collision/CMakeLists.txt @@ -1 +1,4 @@ -target_sources(omath PRIVATE LineTracer.cpp) +target_sources(omath PRIVATE + LineTracer.cpp + Cube.cpp +) diff --git a/source/collision/Cube.cpp b/source/collision/Cube.cpp new file mode 100644 index 00000000..e9dcb610 --- /dev/null +++ b/source/collision/Cube.cpp @@ -0,0 +1,13 @@ +// +// Created by vlad on 11/15/2024. +// +#include "omath/collision/Cube.h" + + +namespace omath::collision +{ + bool Cube::IsCollideWith(const std::shared_ptr& other) + { + + } +} diff --git a/source/collision/LineTracer.cpp b/source/collision/LineTracer.cpp index 602b92f7..905350b9 100644 --- a/source/collision/LineTracer.cpp +++ b/source/collision/LineTracer.cpp @@ -20,7 +20,7 @@ namespace omath::collision return DirectionVector().Normalized(); } - std::optional LineTracer::GetRayHitPoint(const Ray &ray, const Triangle3d &triangle) + Vector3 LineTracer::GetRayHitPoint(const Ray &ray, const Triangle3d &triangle) { constexpr float kEpsilon = std::numeric_limits::epsilon(); From a0ff952ef4fa6e594e3bc1af6cf1882873e40a68 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 15 Nov 2024 11:51:19 +0300 Subject: [PATCH 110/795] improved README added CoC --- CODE_OF_CONDUCT.md | 92 ++++++++++++++++++++++++++++++++++++++++++ readme.md => README.md | 10 +++-- SECURITY.md | 5 +++ 3 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 CODE_OF_CONDUCT.md rename readme.md => README.md (82%) create mode 100644 SECURITY.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..bbdccf95 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,92 @@ +## Goal + +My goal is to provide a space where it is safe for everyone to contribute to, +and get support for, open-source software in a respectful and cooperative +manner. + +I value all contributions and want to make this project and its +surrounding community a place for everyone. + +As members, contributors, and everyone else who may participate in the +development, I strive to keep the entire experience civil. + +## Standards + +Our community standards exist in order to make sure everyone feels comfortable +contributing to the project(s) together. + +Our standards are: +- Do not harass, attack, or in any other way discriminate against anyone, including + for their protected traits, including, but not limited to, sex, religion, race, + appearance, gender, identity, nationality, sexuality, etc. +- Do not go off-topic, do not post spam. +- Treat everyone with respect. + +Examples of breaking each rule respectively include: +- Harassment, bullying or inappropriate jokes about another person. +- Posting distasteful imagery, trolling, or posting things unrelated to the topic at hand. +- Treating someone as worse because of their lack of understanding of an issue. + +## Enforcement + +Enforcement of this CoC is done by Orange++ and/or other core contributors. + +I, as the core developer, will strive my best to keep this community civil and +following the standards outlined above. + +### Reporting incidents + +If you believe an incident of breaking these standards has occurred, but nobody has +taken appropriate action, you can privately contact the people responsible for dealing +with such incidents in multiple ways: + +***E-Mail*** +- `orange-cpp@yandex.ru` +- +***Discord*** +- `@orange_cpp` + +***Telegram*** +- `@orange-cpp` +I guarantee your privacy and will not share those reports with anyone. + +## Enforcement Strategy + +Depending on the severity of the infraction, any action from the list below may be applied. +Please keep in mind cases are reviewed on a per-case basis and members are the ultimate +deciding factor in the type of punishment. + +If the matter would benefit from an outside opinion, a member might reach for more opinions +from people unrelated to the organization, however, the final decision regarding the action +to be taken is still up to the member. + +For example, if the matter at hand regards a representative of a marginalized group or minority, +the member might ask for a first-hand opinion from another representative of such group. + +### Correction/Edit + +If your message is found to be misleading or poorly worded, a member might +edit your message. + +### Warning/Deletion + +If your message is found inappropriate, a member might give you a public or private warning, +and/or delete your message. + +### Mute + +If your message is disruptive, or you have been repeatedly violating the standards, +a member might mute (or temporarily ban) you. + +### Ban + +If your message is hateful, very disruptive, or other, less serious infractions are repeated +ignoring previous punishments, a member might ban you permanently. + +## Scope + +This CoC shall apply to all projects ran under the Orange++ lead and all _official_ communities +outside of GitHub. + +However, it is worth noting that official communities outside of GitHub might have their own, +additional sets of rules. \ No newline at end of file diff --git a/readme.md b/README.md similarity index 82% rename from readme.md rename to README.md index 07bb7c43..4fca58da 100644 --- a/readme.md +++ b/README.md @@ -17,6 +17,8 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at - **Ease of Use**: Simplified interface for convenient integration into various projects. - **Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot. - **3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline. +- **Collision Detection**: Production ready code to handle collision detection by using simple interfaces. +- **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution ## Getting Started ### Prerequisites @@ -34,10 +36,10 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at ``` 3. Build the project using CMake: ``` - cmake --preset x64-release -S . - cmake --build cmake-build/build/x64-release --target server -j 6 + cmake --preset windows-release -S . + cmake --build cmake-build/build/windows-release --target server -j 6 ``` - + Use **\-\** preset to build siutable version for yourself. Like **windows-release** or **linux-release**. ## Usage Simple world to screen function ```c++ @@ -64,7 +66,7 @@ With `omath/projection` module you can achieve simple ESP hack for powered by So Contributions to `omath` are welcome! Please read `CONTRIBUTING.md` for details on our code of conduct and the process for submitting pull requests. ## License -This project is licensed under the GPL V3 - see the `LICENSE` file for details. +This project is licensed under the MIT - see the `LICENSE` file for details. ## Acknowledgments - Orange | [Telegram](https://t.me/orange_cpp) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..190b1a5a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security issues to `orange-cpp@yandex.com` \ No newline at end of file From 761edbd11c1ec8f6690262b696fa213c0aa31a66 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 15 Nov 2024 11:55:39 +0300 Subject: [PATCH 111/795] removed empty part of list --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index bbdccf95..b865b6e5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -42,7 +42,7 @@ with such incidents in multiple ways: ***E-Mail*** - `orange-cpp@yandex.ru` -- + ***Discord*** - `@orange_cpp` From b482a0bd996ad67058e1bc058b268f05b6c73ae9 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 15 Nov 2024 11:58:33 +0300 Subject: [PATCH 112/795] edit --- CODE_OF_CONDUCT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b865b6e5..a3f36ab4 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -56,8 +56,8 @@ Depending on the severity of the infraction, any action from the list below may Please keep in mind cases are reviewed on a per-case basis and members are the ultimate deciding factor in the type of punishment. -If the matter would benefit from an outside opinion, a member might reach for more opinions -from people unrelated to the organization, however, the final decision regarding the action +If the matter benefited from an outside opinion, a member might reach for more opinions +from people unrelated, however, the final decision regarding the action to be taken is still up to the member. For example, if the matter at hand regards a representative of a marginalized group or minority, From 21ff315bc6170c1ae403508b04aa4525d77bead7 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 18 Nov 2024 20:43:58 +0300 Subject: [PATCH 113/795] removed useless stufff --- CMakeLists.txt | 5 +---- include/omath/collision/Cube.h | 22 ---------------------- include/omath/collision/ICollidable.h | 20 -------------------- source/collision/CMakeLists.txt | 1 - source/collision/Cube.cpp | 13 ------------- 5 files changed, 1 insertion(+), 60 deletions(-) delete mode 100644 include/omath/collision/Cube.h delete mode 100644 include/omath/collision/ICollidable.h delete mode 100644 source/collision/Cube.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ce6573b..72ca0fb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,10 +13,7 @@ option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Vector3.cpp) else() - add_library(omath STATIC source/Vector3.cpp - include/omath/collision/ICollidable.h - include/omath/collision/Cube.h - source/collision/Cube.cpp) + add_library(omath STATIC source/Vector3.cpp) endif() add_subdirectory(source) diff --git a/include/omath/collision/Cube.h b/include/omath/collision/Cube.h deleted file mode 100644 index 4c43796b..00000000 --- a/include/omath/collision/Cube.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// Created by vlad on 11/15/2024. -// -#pragma once -#include "ICollidable.h" - -namespace omath::collision -{ - class Cube final : public ICollidable - { - public: - - [[nodiscard]] - bool IsCollideWith(const std::shared_ptr& other) override; - - private: - [[nodiscard]] - bool IsCollideWithCube(const Cube& other); - bool IsCollideWithCapsule(const Cube& other); - - }; -} \ No newline at end of file diff --git a/include/omath/collision/ICollidable.h b/include/omath/collision/ICollidable.h deleted file mode 100644 index 6d894641..00000000 --- a/include/omath/collision/ICollidable.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Created by vlad on 11/15/2024. -// -#pragma once -#include "ICollidable.h" -#include - - - -namespace omath::collision -{ - class ICollidable - { - public: - virtual ~ICollidable() = default; - - [[nodiscard]] - virtual bool IsCollideWith(const std::shared_ptr& other) = 0; - }; -} diff --git a/source/collision/CMakeLists.txt b/source/collision/CMakeLists.txt index c82e0fb2..22a2abc6 100644 --- a/source/collision/CMakeLists.txt +++ b/source/collision/CMakeLists.txt @@ -1,4 +1,3 @@ target_sources(omath PRIVATE LineTracer.cpp - Cube.cpp ) diff --git a/source/collision/Cube.cpp b/source/collision/Cube.cpp deleted file mode 100644 index e9dcb610..00000000 --- a/source/collision/Cube.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// -// Created by vlad on 11/15/2024. -// -#include "omath/collision/Cube.h" - - -namespace omath::collision -{ - bool Cube::IsCollideWith(const std::shared_ptr& other) - { - - } -} From 8e923fc8280945f577fd6251267f4bc984edb058 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 18 Nov 2024 20:49:13 +0300 Subject: [PATCH 114/795] added final --- include/omath/Triangle3d.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/Triangle3d.hpp b/include/omath/Triangle3d.hpp index 5f6df62e..f755ebf3 100644 --- a/include/omath/Triangle3d.hpp +++ b/include/omath/Triangle3d.hpp @@ -6,7 +6,7 @@ namespace omath { - class Triangle3d + class Triangle3d final { public: Triangle3d(const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3); From 5454c43b18c11c7d41ab036a3f41616029f756f3 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 22 Nov 2024 20:53:43 +0300 Subject: [PATCH 115/795] minor improvement --- include/omath/Mat.hpp | 45 +++++++++++++++++++++++++++++++----- include/omath/Matrix.hpp | 1 - include/omath/Vector2.hpp | 14 +++++------ include/omath/Vector3.hpp | 16 ++++++------- include/omath/Vector4.hpp | 12 +++++----- source/projection/Camera.cpp | 8 +++---- tests/UnitTestMat.cpp | 4 ++-- 7 files changed, 66 insertions(+), 34 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index c68dc954..cf6c4977 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -13,7 +13,11 @@ namespace omath { - template + struct MatSize + { + size_t rows, columns; + }; + template class Mat final { public: @@ -56,13 +60,22 @@ namespace omath } [[nodiscard]] - static consteval size_t RowCount() noexcept { return Rows; } + static consteval size_t RowCount() noexcept + { + return Rows; + } [[nodiscard]] - static consteval size_t ColumnsCount() noexcept { return Columns; } + static consteval size_t ColumnsCount() noexcept + { + return Columns; + } [[nodiscard]] - static consteval std::pair Size() noexcept { return {Rows, Columns}; } + static consteval MatSize Size() noexcept + { + return {Rows, Columns}; + } [[nodiscard]] constexpr const Type &At(const size_t rowIndex, const size_t columnIndex) const @@ -116,7 +129,7 @@ namespace omath return result; } - constexpr Mat &operator*=(const Type& f) noexcept + constexpr Mat &operator*=(const Type &f) noexcept { for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) @@ -175,7 +188,7 @@ namespace omath } [[nodiscard]] - constexpr Mat Transpose() const noexcept + constexpr Mat Transposed() const noexcept { Mat transposed; for (size_t i = 0; i < Rows; ++i) @@ -298,6 +311,26 @@ namespace omath }; } + [[nodiscard]] + constexpr static Mat<4, 1> MatRowFromVector(const Vector3 &vector) noexcept + { + return {{vector.x, vector.y, vector.z, 1}}; + } + + [[nodiscard]] + constexpr static Mat<1, 4> MatColumnFromVector(const Vector3 &vector) noexcept + { + return + { + { + {vector.x}, + {vector.y}, + {vector.z}, + {1} + } + }; + } + private: std::array m_data; }; diff --git a/include/omath/Matrix.hpp b/include/omath/Matrix.hpp index 2488815d..81e7b4fa 100644 --- a/include/omath/Matrix.hpp +++ b/include/omath/Matrix.hpp @@ -1,5 +1,4 @@ #pragma once -#include #include #include #include diff --git a/include/omath/Vector2.hpp b/include/omath/Vector2.hpp index ba5017e5..46bc4676 100644 --- a/include/omath/Vector2.hpp +++ b/include/omath/Vector2.hpp @@ -16,7 +16,7 @@ namespace omath float y = 0.f; // Constructors - constexpr Vector2() : x(0.f), y(0.f) {} + constexpr Vector2() = default; constexpr Vector2(const float x, const float y) : x(x), y(y) {} @@ -66,7 +66,7 @@ namespace omath return *this; } - constexpr Vector2& operator*=(float fl) + constexpr Vector2& operator*=(const float fl) { x *= fl; y *= fl; @@ -74,7 +74,7 @@ namespace omath return *this; } - constexpr Vector2& operator/=(float fl) + constexpr Vector2& operator/=(const float fl) { x /= fl; y /= fl; @@ -82,7 +82,7 @@ namespace omath return *this; } - constexpr Vector2& operator+=(float fl) + constexpr Vector2& operator+=(const float fl) { x += fl; y += fl; @@ -90,7 +90,7 @@ namespace omath return *this; } - constexpr Vector2& operator-=(float fl) + constexpr Vector2& operator-=(const float fl) { x -= fl; y -= fl; @@ -177,12 +177,12 @@ namespace omath return {x - v.x, y - v.y}; } - [[nodiscard]] constexpr Vector2 operator*(float fl) const + [[nodiscard]] constexpr Vector2 operator*(const float fl) const { return {x * fl, y * fl}; } - [[nodiscard]] constexpr Vector2 operator/(float fl) const + [[nodiscard]] constexpr Vector2 operator/(const float fl) const { return {x / fl, y / fl}; } diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index ec108483..a9a687ed 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -15,8 +15,8 @@ namespace omath { public: float z = 0.f; - constexpr Vector3(float x, float y, float z) : Vector2(x, y), z(z) { } - constexpr Vector3() : Vector2(), z(0.f) {}; + constexpr Vector3(const float x, const float y, const float z) : Vector2(x, y), z(z) { } + constexpr Vector3() : Vector2() {}; [[nodiscard]] constexpr bool operator==(const Vector3& src) const { @@ -44,7 +44,7 @@ namespace omath return *this; } - constexpr Vector3& operator*=(float fl) + constexpr Vector3& operator*=(const float fl) { Vector2::operator*=(fl); z *= fl; @@ -68,7 +68,7 @@ namespace omath return *this; } - constexpr Vector3& operator+=(float fl) + constexpr Vector3& operator+=(const float fl) { Vector2::operator+=(fl); z += fl; @@ -76,7 +76,7 @@ namespace omath return *this; } - constexpr Vector3& operator/=(float fl) + constexpr Vector3& operator/=(const float fl) { Vector2::operator/=(fl); z /= fl; @@ -84,7 +84,7 @@ namespace omath return *this; } - constexpr Vector3& operator-=(float fl) + constexpr Vector3& operator-=(const float fl) { Vector2::operator-=(fl); z -= fl; @@ -175,7 +175,7 @@ namespace omath return {x - v.x, y - v.y, z - v.z}; } - [[nodiscard]] constexpr Vector3 operator*(float fl) const + [[nodiscard]] constexpr Vector3 operator*(const float fl) const { return {x * fl, y * fl, z * fl}; } @@ -206,7 +206,7 @@ namespace omath } [[nodiscard]] constexpr float Sum() const { - return Vector3::Sum2D() + z; + return Sum2D() + z; } [[nodiscard]] constexpr float Sum2D() const diff --git a/include/omath/Vector4.hpp b/include/omath/Vector4.hpp index 55c364c8..4b43501b 100644 --- a/include/omath/Vector4.hpp +++ b/include/omath/Vector4.hpp @@ -14,7 +14,7 @@ namespace omath public: float w; - constexpr Vector4(float x, float y, float z, float w) : Vector3(x, y, z), w(w) {} + constexpr Vector4(const float x, const float y, const float z, const float w) : Vector3(x, y, z), w(w) {} constexpr Vector4() : Vector3(), w(0.f) {}; [[nodiscard]] @@ -45,7 +45,7 @@ namespace omath return *this; } - constexpr Vector4& operator*=(float scalar) + constexpr Vector4& operator*=(const float scalar) { Vector3::operator*=(scalar); w *= scalar; @@ -61,7 +61,7 @@ namespace omath return *this; } - constexpr Vector4& operator/=(float scalar) + constexpr Vector4& operator/=(const float scalar) { Vector3::operator/=(scalar); w /= scalar; @@ -95,7 +95,7 @@ namespace omath return *this; } - constexpr Vector4& Clamp(float min, float max) + constexpr Vector4& Clamp(const float min, const float max) { x = std::clamp(x, min, max); y = std::clamp(y, min, max); @@ -123,7 +123,7 @@ namespace omath } [[nodiscard]] - constexpr Vector4 operator*(float scalar) const + constexpr Vector4 operator*(const float scalar) const { return {x * scalar, y * scalar, z * scalar, w * scalar}; } @@ -135,7 +135,7 @@ namespace omath } [[nodiscard]] - constexpr Vector4 operator/(float scalar) const + constexpr Vector4 operator/(const float scalar) const { return {x / scalar, y / scalar, z / scalar, w / scalar}; } diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index be552fab..c26f75ae 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -28,16 +28,16 @@ namespace omath::projection const auto right = Vector3::RightVector(m_viewAngles.x, m_viewAngles.y, m_viewAngles.z); const auto up = Vector3::UpVector(m_viewAngles.x, m_viewAngles.y, m_viewAngles.z); - return Mat<4, 4>::TranslationMat(-m_origin) * Mat<4, 4>::OrientationMat(forward, right, up); + return Mat<>::TranslationMat(-m_origin) * Mat<>::OrientationMat(forward, right, up); } std::expected Camera::WorldToScreen(const Vector3& worldPosition) const { - const auto posVecAsMatrix = Mat<1, 4>({{worldPosition.x, worldPosition.y, worldPosition.z, 1.f}}); + const auto posVecAsMatrix = Mat<>::MatColumnFromVector(worldPosition); - const auto projectionMatrix = Mat<4, 4>::ProjectionMat(m_fieldOfView, m_viewPort.AspectRatio(), - m_nearPlaneDistance, m_farPlaneDistance, 1.335f); + const auto projectionMatrix = Mat<>::ProjectionMat(m_fieldOfView, m_viewPort.AspectRatio(), + m_nearPlaneDistance, m_farPlaneDistance, m_lensZoom); Mat<1, 4> projected = posVecAsMatrix * (GetViewMatrix() * projectionMatrix); diff --git a/tests/UnitTestMat.cpp b/tests/UnitTestMat.cpp index 613325cf..94d451ad 100644 --- a/tests/UnitTestMat.cpp +++ b/tests/UnitTestMat.cpp @@ -88,7 +88,7 @@ TEST_F(UnitTestMat, Operator_Division_Scalar) // Test matrix functions TEST_F(UnitTestMat, Transpose) { - Mat<2, 2> m3 = m2.Transpose(); + Mat<2, 2> m3 = m2.Transposed(); EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); EXPECT_FLOAT_EQ(m3.At(0, 1), m2.At(1, 0)); EXPECT_FLOAT_EQ(m3.At(1, 0), m2.At(0, 1)); @@ -215,7 +215,7 @@ TEST(UnitTestMatStandalone, Minor_3x3) TEST(UnitTestMatStandalone, Transpose_NonSquare) { constexpr Mat<2, 3> m{{1.0f, 2.0f, 3.0f}, {4.0f, 5.0f, 6.0f}}; - auto transposed = m.Transpose(); + auto transposed = m.Transposed(); EXPECT_EQ(transposed.RowCount(), 3); EXPECT_EQ(transposed.ColumnsCount(), 2); EXPECT_FLOAT_EQ(transposed.At(0, 0), 1.0f); From ea4e27b87b9717aa0f305b59082436140046406e Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 23 Nov 2024 12:31:01 +0300 Subject: [PATCH 116/795] improved matrix class --- include/omath/Mat.hpp | 45 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index cf6c4977..18907fa7 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -17,7 +17,15 @@ namespace omath { size_t rows, columns; }; - template + + enum class MatStoreType : uint8_t + { + ROW_MAJOR = 0, + COLUMN_MAJOR + }; + + template + requires (std::is_floating_point_v || std::is_integral_v) class Mat final { public: @@ -26,8 +34,7 @@ namespace omath Clear(); } - - constexpr Mat(const std::initializer_list > &rows) + constexpr Mat(const std::initializer_list>& rows) { if (rows.size() != Rows) throw std::invalid_argument("Initializer list rows size does not match template parameter Rows"); @@ -42,18 +49,21 @@ namespace omath auto colIt = rowIt->begin(); for (size_t j = 0; j < Columns; ++j, ++colIt) { - At(i, j) = *colIt; + At(i, j) = std::move(*colIt); } } } + constexpr explicit Mat(const Type* rawData) + { + std::copy_n(rawData, Rows * Columns, m_data.begin()); + } constexpr Mat(const Mat &other) noexcept { m_data = other.m_data; } - constexpr Mat(Mat &&other) noexcept { m_data = std::move(other.m_data); @@ -77,13 +87,22 @@ namespace omath return {Rows, Columns}; } - [[nodiscard]] constexpr const Type &At(const size_t rowIndex, const size_t columnIndex) const { if (rowIndex >= Rows || columnIndex >= Columns) throw std::out_of_range("Index out of range"); - return m_data[rowIndex * Columns + columnIndex]; + if constexpr (StoreType == MatStoreType::ROW_MAJOR) + return m_data[rowIndex * Columns + columnIndex]; + + else if constexpr (StoreType == MatStoreType::COLUMN_MAJOR) + return m_data[rowIndex + columnIndex * Rows]; + + else + { + static_assert(false, "Invalid matrix access convention"); + std::unreachable(); + } } [[nodiscard]] constexpr Type &At(const size_t rowIndex, const size_t columnIndex) @@ -240,6 +259,18 @@ namespace omath return result; } + [[nodiscard]] + constexpr const std::array& RawArray() const + { + return m_data; + } + + [[nodiscard]] + constexpr std::array& RawArray() + { + return const_cast>(std::as_const(*this).RawArray()); + } + [[nodiscard]] std::string ToString() const noexcept { From a25aef049e62c56b03fd55bdcad07cd0e29bd32b Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 24 Nov 2024 20:37:15 +0300 Subject: [PATCH 117/795] improved code quality --- include/omath/Mat.hpp | 40 +++++++++++++++++++++------------ source/collision/LineTracer.cpp | 1 - 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 18907fa7..1e717ab0 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -70,13 +70,13 @@ namespace omath } [[nodiscard]] - static consteval size_t RowCount() noexcept + static constexpr size_t RowCount() noexcept { return Rows; } [[nodiscard]] - static consteval size_t ColumnsCount() noexcept + static constexpr size_t ColumnsCount() noexcept { return Columns; } @@ -133,9 +133,9 @@ namespace omath // Operator overloading for multiplication with another Mat template - constexpr Mat operator*(const Mat &other) const + constexpr Mat operator*(const Mat &other) const { - Mat result; + Mat result; for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < OtherColumns; ++j) @@ -157,7 +157,7 @@ namespace omath } template - constexpr Mat operator*=(const Mat &other) + constexpr Mat operator*=(const Mat &other) { return *this = *this * other; } @@ -207,9 +207,9 @@ namespace omath } [[nodiscard]] - constexpr Mat Transposed() const noexcept + constexpr Mat Transposed() const noexcept { - Mat transposed; + Mat transposed; for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) transposed.At(j, i) = At(i, j); @@ -240,9 +240,9 @@ namespace omath } [[nodiscard]] - constexpr Mat Minor(const size_t row, const size_t column) const + constexpr Mat Minor(const size_t row, const size_t column) const { - Mat result; + Mat result; for (size_t i = 0, m = 0; i < Rows; ++i) { if (i == row) @@ -288,6 +288,18 @@ namespace omath return oss.str(); } + [[nodiscard]] + bool operator==(const Mat & mat) const + { + return m_data == mat.m_data; + } + + [[nodiscard]] + bool operator!=(const Mat & mat) const + { + return !operator==(mat); + } + // Static methods that return fixed-size matrices [[nodiscard]] constexpr static Mat<4, 4> ToScreenMat(const Type &screenWidth, const Type &screenHeight) noexcept @@ -322,7 +334,7 @@ namespace omath {right.x, up.x, forward.x, 0}, {right.y, up.y, forward.y, 0}, {right.z, up.z, forward.z, 0}, - {0, 0, 0, 1}, + {0, 0, 0, 1}, }; } @@ -354,10 +366,10 @@ namespace omath return { { - {vector.x}, - {vector.y}, - {vector.z}, - {1} + vector.x, + vector.y, + vector.z, + 1 } }; } diff --git a/source/collision/LineTracer.cpp b/source/collision/LineTracer.cpp index 905350b9..83bcdd2a 100644 --- a/source/collision/LineTracer.cpp +++ b/source/collision/LineTracer.cpp @@ -1,7 +1,6 @@ // // Created by Orange on 11/13/2024. // -#pragma once #include "omath/collision/LineTracer.hpp" namespace omath::collision From 6e5f2331adbe05054a7a01ddacfecb5ac9d8cb89 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 27 Nov 2024 15:21:07 +0300 Subject: [PATCH 118/795] added files --- .gitmodules | 3 ++ .idea/vcs.xml | 1 + CMakeLists.txt | 6 ++-- CMakePresets.json | 4 +-- extlibs/CMakeLists.txt | 3 +- extlibs/glm | 1 + include/omath/engines/opengl.hpp | 35 +++++++++++++++++++++++ include/omath/engines/source.hpp | 28 +++++++++++++++++++ tests/CMakeLists.txt | 3 +- tests/UnitTestOpenGL.cpp | 48 ++++++++++++++++++++++++++++++++ 10 files changed, 125 insertions(+), 7 deletions(-) create mode 160000 extlibs/glm create mode 100644 include/omath/engines/opengl.hpp create mode 100644 include/omath/engines/source.hpp create mode 100644 tests/UnitTestOpenGL.cpp diff --git a/.gitmodules b/.gitmodules index c16587cb..b6c1697b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "extlibs/googletest"] path = extlibs/googletest url = https://github.com/google/googletest.git +[submodule "extlibs/glm"] + path = extlibs/glm + url = https://github.com/g-truc/glm.git diff --git a/.idea/vcs.xml b/.idea/vcs.xml index adc159a8..edda5903 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,7 @@ + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 72ca0fb0..b2d004b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,10 +23,10 @@ if(OMATH_BUILD_TESTS) add_subdirectory(tests) endif() -if (WIN32 AND OMATH_THREAT_WARNING_AS_ERROR) +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND OMATH_THREAT_WARNING_AS_ERROR) target_compile_options(omath PRIVATE /W4 /WX) -elseif(UNIX AND OMATH_THREAT_WARNING_AS_ERROR) - target_compile_options(omath PRIVATE -Wall -Wextra -Wpedantic) +elseif(OMATH_THREAT_WARNING_AS_ERROR) + target_compile_options(omath PRIVATE -Wall -Wextra -Wpedantic -Werror) endif() target_include_directories(omath diff --git a/CMakePresets.json b/CMakePresets.json index d67436b9..74671b4a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,8 +8,8 @@ "binaryDir": "${sourceDir}/cmake-build/build/${presetName}", "installDir": "${sourceDir}/cmake-build/install/${presetName}", "cacheVariables": { - "CMAKE_C_COMPILER": "cl.exe", - "CMAKE_CXX_COMPILER": "cl.exe" + "CMAKE_C_COMPILER": "clang.exe", + "CMAKE_CXX_COMPILER": "clang++.exe" }, "condition": { "type": "equals", diff --git a/extlibs/CMakeLists.txt b/extlibs/CMakeLists.txt index bf73c7af..caca17c7 100644 --- a/extlibs/CMakeLists.txt +++ b/extlibs/CMakeLists.txt @@ -1 +1,2 @@ -add_subdirectory(googletest) \ No newline at end of file +add_subdirectory(googletest) +add_subdirectory(glm) \ No newline at end of file diff --git a/extlibs/glm b/extlibs/glm new file mode 160000 index 00000000..33b4a621 --- /dev/null +++ b/extlibs/glm @@ -0,0 +1 @@ +Subproject commit 33b4a621a697a305bc3a7610d290677b96beb181 diff --git a/include/omath/engines/opengl.hpp b/include/omath/engines/opengl.hpp new file mode 100644 index 00000000..6b707565 --- /dev/null +++ b/include/omath/engines/opengl.hpp @@ -0,0 +1,35 @@ +// +// Created by Orange on 11/23/2024. +// +#pragma once +#include "omath/Vector3.hpp" +#include "omath/Mat.hpp" + + +namespace omath::opengl +{ + constexpr Vector3 kAbsUp = {0, 1, 0}; + constexpr Vector3 kAbsRight = {1, 0, 0}; + constexpr Vector3 kAbsForward = {0, 0, -1}; + + template requires std::is_floating_point_v || std::is_integral_v + [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> PerspectiveProjectionMatrix() + { + + } + + template requires std::is_floating_point_v || std::is_integral_v + [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> PerspectiveProjectionMatrix( + const float fieldOfView, const Type &aspectRatio, const Type &near, const Type &far) + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); + + return + { + {static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, static_cast(1) / (fovHalfTan), 0, 0}, + {0, 0, -(far + near) / (far - near), -(static_cast(2) * far * near) / (far - near)}, + {0, 0, -1, 0}, + }; + } +} diff --git a/include/omath/engines/source.hpp b/include/omath/engines/source.hpp new file mode 100644 index 00000000..24308fc5 --- /dev/null +++ b/include/omath/engines/source.hpp @@ -0,0 +1,28 @@ +// +// Created by Orange on 11/24/2024. +// +#pragma once + + +namespace omath::source +{ + constexpr Vector3 kAbsUp = {0, 0, 1}; + constexpr Vector3 kAbsRight = {0, -1, 0}; + constexpr Vector3 kAbsForward = {1, 0, 0}; + + + template requires std::is_floating_point_v || std::is_integral_v + [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> PerspectiveProjectionMatrix( + const float fieldOfView, const Type &aspectRatio, const Type &near, const Type &far) + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); + + return + { + {static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, static_cast(1) / (fovHalfTan), 0, 0}, + {0, 0, (far + near) / (far - near), -(static_cast(2) * far * near) / (far - near)}, + {0, 0, 1, 0}, + }; + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1c604c12..3b9b0562 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,8 +15,9 @@ add_executable(unit-tests UnitTestColor.cpp UnitTestVector4.cpp UnitTestLineTrace.cpp + UnitTestOpenGL.cpp ) -target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) +target_link_libraries(unit-tests PRIVATE gtest gtest_main omath glm) gtest_discover_tests(unit-tests) \ No newline at end of file diff --git a/tests/UnitTestOpenGL.cpp b/tests/UnitTestOpenGL.cpp new file mode 100644 index 00000000..e4f15619 --- /dev/null +++ b/tests/UnitTestOpenGL.cpp @@ -0,0 +1,48 @@ +// +// Created by Orange on 11/23/2024. +// +#include +#include +#include +#include +#include +#include +#include + +#include "glm/ext/matrix_clip_space.hpp" +#include "glm/ext/matrix_transform.hpp" + + +TEST(UnitTestOpenGL, Projection) +{ + + const auto proj_glm = glm::perspective(glm::radians(90.f), 16.f / 9.f, 0.1f, 1000.f); + const auto proj_glm2 = glm::perspectiveLH_NO(glm::radians(90.f), 16.f / 9.f, 0.1f, 1000.f); + // const auto proj_omath = omath::Mat<4, 4, float, omath::MatStoreType::COLUMN_MAJOR>((const float*)&proj_glm); + // EXPECT_EQ(omath::opengl::PerspectiveProjectionMatrix(90, 16.f / 9.f, 0.1f, 1000.f), proj_omath); + + + glm::vec4 ndc_glm2 = proj_glm * glm::vec4(300.f, 0.f, -1000.f, 1.f); + ndc_glm2 /= ndc_glm2.w; + const omath::Mat<4, 1, float, omath::MatStoreType::COLUMN_MAJOR> cords_omath = + { + {0}, + {0}, + {-0.2f}, + {1} + }; + + //auto ndc_omath = proj_omath * cords_omath; + // ndc_omath /= ndc_omath.At(3, 0); + +} +TEST(UnitTestOpenGL, Projection2) +{ + const auto orient = omath::Mat<>::OrientationMat(omath::opengl::kAbsForward, omath::opengl::kAbsRight, omath::opengl::kAbsUp); + + const omath::Mat<4, 1> cords_omath = + { + {0}, {0}, {10}, {1} + }; + std::cout << (orient.Transposed() * cords_omath).ToString(); +} \ No newline at end of file From 480d11dbc0c3405e54cf7e97ff7c5b7ed6c9a355 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 27 Nov 2024 15:35:30 +0300 Subject: [PATCH 119/795] added clang tidy --- .clang-tidy | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 .clang-tidy diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..fd8c681c --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,147 @@ +# Generated from CLion Inspection settings +--- +Checks: '-*, +bugprone-argument-comment, +bugprone-assert-side-effect, +bugprone-bad-signal-to-kill-thread, +bugprone-branch-clone, +bugprone-copy-constructor-init, +bugprone-dangling-handle, +bugprone-dynamic-static-initializers, +bugprone-fold-init-type, +bugprone-forward-declaration-namespace, +bugprone-forwarding-reference-overload, +bugprone-inaccurate-erase, +bugprone-incorrect-roundings, +bugprone-integer-division, +bugprone-lambda-function-name, +bugprone-macro-parentheses, +bugprone-macro-repeated-side-effects, +bugprone-misplaced-operator-in-strlen-in-alloc, +bugprone-misplaced-pointer-arithmetic-in-alloc, +bugprone-misplaced-widening-cast, +bugprone-move-forwarding-reference, +bugprone-multiple-statement-macro, +bugprone-no-escape, +bugprone-parent-virtual-call, +bugprone-posix-return, +bugprone-reserved-identifier, +bugprone-sizeof-container, +bugprone-sizeof-expression, +bugprone-spuriously-wake-up-functions, +bugprone-string-constructor, +bugprone-string-integer-assignment, +bugprone-string-literal-with-embedded-nul, +bugprone-suspicious-enum-usage, +bugprone-suspicious-include, +bugprone-suspicious-memset-usage, +bugprone-suspicious-missing-comma, +bugprone-suspicious-semicolon, +bugprone-suspicious-string-compare, +bugprone-suspicious-memory-comparison, +bugprone-suspicious-realloc-usage, +bugprone-swapped-arguments, +bugprone-terminating-continue, +bugprone-throw-keyword-missing, +bugprone-too-small-loop-variable, +bugprone-undefined-memory-manipulation, +bugprone-undelegated-constructor, +bugprone-unhandled-self-assignment, +bugprone-unused-raii, +bugprone-unused-return-value, +bugprone-use-after-move, +bugprone-virtual-near-miss, +cert-dcl21-cpp, +cert-dcl58-cpp, +cert-err34-c, +cert-err52-cpp, +cert-err60-cpp, +cert-flp30-c, +cert-msc50-cpp, +cert-msc51-cpp, +cert-str34-c, +cppcoreguidelines-interfaces-global-init, +cppcoreguidelines-narrowing-conversions, +cppcoreguidelines-pro-type-member-init, +cppcoreguidelines-pro-type-static-cast-downcast, +cppcoreguidelines-slicing, +google-default-arguments, +google-explicit-constructor, +google-runtime-operator, +hicpp-exception-baseclass, +hicpp-multiway-paths-covered, +misc-misplaced-const, +misc-new-delete-overloads, +misc-no-recursion, +misc-non-copyable-objects, +misc-throw-by-value-catch-by-reference, +misc-unconventional-assign-operator, +misc-uniqueptr-reset-release, +modernize-avoid-bind, +modernize-concat-nested-namespaces, +modernize-deprecated-headers, +modernize-deprecated-ios-base-aliases, +modernize-loop-convert, +modernize-make-shared, +modernize-make-unique, +modernize-pass-by-value, +modernize-raw-string-literal, +modernize-redundant-void-arg, +modernize-replace-auto-ptr, +modernize-replace-disallow-copy-and-assign-macro, +modernize-replace-random-shuffle, +modernize-return-braced-init-list, +modernize-shrink-to-fit, +modernize-unary-static-assert, +modernize-use-auto, +modernize-use-bool-literals, +modernize-use-emplace, +modernize-use-equals-default, +modernize-use-equals-delete, +modernize-use-nodiscard, +modernize-use-noexcept, +modernize-use-nullptr, +modernize-use-override, +modernize-use-transparent-functors, +modernize-use-uncaught-exceptions, +mpi-buffer-deref, +mpi-type-mismatch, +openmp-use-default-none, +performance-faster-string-find, +performance-for-range-copy, +performance-implicit-conversion-in-loop, +performance-inefficient-algorithm, +performance-inefficient-string-concatenation, +performance-inefficient-vector-operation, +performance-move-const-arg, +performance-move-constructor-init, +performance-no-automatic-move, +performance-noexcept-move-constructor, +performance-trivially-destructible, +performance-type-promotion-in-math-fn, +performance-unnecessary-copy-initialization, +performance-unnecessary-value-param, +portability-simd-intrinsics, +readability-avoid-const-params-in-decls, +readability-const-return-type, +readability-container-size-empty, +readability-convert-member-functions-to-static, +readability-delete-null-pointer, +readability-deleted-default, +readability-inconsistent-declaration-parameter-name, +readability-make-member-function-const, +readability-misleading-indentation, +readability-misplaced-array-index, +readability-non-const-parameter, +readability-redundant-control-flow, +readability-redundant-declaration, +readability-redundant-function-ptr-dereference, +readability-redundant-smartptr-get, +readability-redundant-string-cstr, +readability-redundant-string-init, +readability-simplify-subscript-expr, +readability-static-accessed-through-instance, +readability-static-definition-in-anonymous-namespace, +readability-string-compare, +readability-uniqueptr-delete-release, +readability-use-anyofallof' \ No newline at end of file From 6a9a51b39c61c27e98586cc2e8ec2f6c5d3745eb Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 27 Nov 2024 19:36:28 +0300 Subject: [PATCH 120/795] refactored tests --- include/omath/engines/opengl.hpp | 22 ++++++++++++++----- include/omath/engines/source.hpp | 3 ++- include/omath/engines/unity.hpp | 12 +++++++++++ include/omath/projection/Camera.hpp | 1 - source/projection/Camera.cpp | 4 ---- tests/CMakeLists.txt | 25 ++++++++++++---------- tests/{ => engines}/UnitTestOpenGL.cpp | 8 +++---- tests/engines/UnitTestSourceEngine.cpp | 3 +++ tests/engines/UnitTestUnityEngine.cpp | 3 +++ tests/{ => general}/UnitTestAstar.cpp | 0 tests/{ => general}/UnitTestColor.cpp | 0 tests/{ => general}/UnitTestLineTrace.cpp | 0 tests/{ => general}/UnitTestMat.cpp | 0 tests/{ => general}/UnitTestMatrix.cpp | 0 tests/{ => general}/UnitTestPrediction.cpp | 0 tests/{ => general}/UnitTestProjection.cpp | 0 tests/{ => general}/UnitTestVector2.cpp | 0 tests/{ => general}/UnitTestVector3.cpp | 0 tests/{ => general}/UnitTestVector4.cpp | 0 19 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 include/omath/engines/unity.hpp rename tests/{ => engines}/UnitTestOpenGL.cpp (79%) create mode 100644 tests/engines/UnitTestSourceEngine.cpp create mode 100644 tests/engines/UnitTestUnityEngine.cpp rename tests/{ => general}/UnitTestAstar.cpp (100%) rename tests/{ => general}/UnitTestColor.cpp (100%) rename tests/{ => general}/UnitTestLineTrace.cpp (100%) rename tests/{ => general}/UnitTestMat.cpp (100%) rename tests/{ => general}/UnitTestMatrix.cpp (100%) rename tests/{ => general}/UnitTestPrediction.cpp (100%) rename tests/{ => general}/UnitTestProjection.cpp (100%) rename tests/{ => general}/UnitTestVector2.cpp (100%) rename tests/{ => general}/UnitTestVector3.cpp (100%) rename tests/{ => general}/UnitTestVector4.cpp (100%) diff --git a/include/omath/engines/opengl.hpp b/include/omath/engines/opengl.hpp index 6b707565..6bed17e3 100644 --- a/include/omath/engines/opengl.hpp +++ b/include/omath/engines/opengl.hpp @@ -12,15 +12,27 @@ namespace omath::opengl constexpr Vector3 kAbsRight = {1, 0, 0}; constexpr Vector3 kAbsForward = {0, 0, -1}; - template requires std::is_floating_point_v || std::is_integral_v - [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> PerspectiveProjectionMatrix() - { + template + requires std::is_floating_point_v || std::is_integral_v + [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> ViewMatrix(const Vector3& forward, + const Vector3& right, + const Vector3& up, + const Vector3& cam_origin) + { + return + { + {right.x, up.x, -forward.x, 0}, + {right.y, up.y, -forward.y, 0}, + {right.z, up.z, -forward.z, 0}, + {-cam_origin.x, -cam_origin.y, -cam_origin.z, 1}, + }; } - template requires std::is_floating_point_v || std::is_integral_v + template + requires std::is_floating_point_v || std::is_integral_v [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> PerspectiveProjectionMatrix( - const float fieldOfView, const Type &aspectRatio, const Type &near, const Type &far) + const float fieldOfView, const Type& aspectRatio, const Type& near, const Type& far) { const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); diff --git a/include/omath/engines/source.hpp b/include/omath/engines/source.hpp index 24308fc5..32c248a4 100644 --- a/include/omath/engines/source.hpp +++ b/include/omath/engines/source.hpp @@ -2,7 +2,8 @@ // Created by Orange on 11/24/2024. // #pragma once - +#include "omath/Vector3.hpp" +#include "omath/Mat.hpp" namespace omath::source { diff --git a/include/omath/engines/unity.hpp b/include/omath/engines/unity.hpp new file mode 100644 index 00000000..ff43bb2e --- /dev/null +++ b/include/omath/engines/unity.hpp @@ -0,0 +1,12 @@ +// +// Created by Orange on 11/27/2024. +// + +#pragma once +#include + + +namespace omath::unity +{ + +}; diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index d2d450ec..ac5a2415 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -7,7 +7,6 @@ #include #include #include -#include #include "ErrorCodes.hpp" diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index c26f75ae..af9782be 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -3,10 +3,6 @@ // #include "omath/projection/Camera.hpp" -#include - -#include "omath/Angles.hpp" - namespace omath::projection { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3b9b0562..6d8d48e9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,17 +5,20 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" include(GoogleTest) add_executable(unit-tests - UnitTestPrediction.cpp - UnitTestMatrix.cpp - UnitTestMat.cpp - UnitTestAstar.cpp - UnitTestProjection.cpp - UnitTestVector3.cpp - UnitTestVector2.cpp - UnitTestColor.cpp - UnitTestVector4.cpp - UnitTestLineTrace.cpp - UnitTestOpenGL.cpp + general/UnitTestPrediction.cpp + general/UnitTestMatrix.cpp + general/UnitTestMat.cpp + general/UnitTestAstar.cpp + general/UnitTestProjection.cpp + general/UnitTestVector3.cpp + general/UnitTestVector2.cpp + general/UnitTestColor.cpp + general/UnitTestVector4.cpp + general/UnitTestLineTrace.cpp + + engines/UnitTestOpenGL.cpp + engines/UnitTestUnityEngine.cpp + engines/UnitTestSourceEngine.cpp ) target_link_libraries(unit-tests PRIVATE gtest gtest_main omath glm) diff --git a/tests/UnitTestOpenGL.cpp b/tests/engines/UnitTestOpenGL.cpp similarity index 79% rename from tests/UnitTestOpenGL.cpp rename to tests/engines/UnitTestOpenGL.cpp index e4f15619..0c44e4a3 100644 --- a/tests/UnitTestOpenGL.cpp +++ b/tests/engines/UnitTestOpenGL.cpp @@ -38,11 +38,11 @@ TEST(UnitTestOpenGL, Projection) } TEST(UnitTestOpenGL, Projection2) { - const auto orient = omath::Mat<>::OrientationMat(omath::opengl::kAbsForward, omath::opengl::kAbsRight, omath::opengl::kAbsUp); + const auto orient = omath::opengl::ViewMatrix(omath::opengl::kAbsForward, -omath::opengl::kAbsRight, omath::opengl::kAbsUp, {}); - const omath::Mat<4, 1> cords_omath = + const omath::Mat<4, 1,float, omath::MatStoreType::COLUMN_MAJOR> cords_omath = { - {0}, {0}, {10}, {1} + {0}, {0}, {-10}, {1} }; - std::cout << (orient.Transposed() * cords_omath).ToString(); + std::cout << (orient * cords_omath).ToString(); } \ No newline at end of file diff --git a/tests/engines/UnitTestSourceEngine.cpp b/tests/engines/UnitTestSourceEngine.cpp new file mode 100644 index 00000000..4f6b72b7 --- /dev/null +++ b/tests/engines/UnitTestSourceEngine.cpp @@ -0,0 +1,3 @@ +// +// Created by Orange on 11/27/2024. +// diff --git a/tests/engines/UnitTestUnityEngine.cpp b/tests/engines/UnitTestUnityEngine.cpp new file mode 100644 index 00000000..4f6b72b7 --- /dev/null +++ b/tests/engines/UnitTestUnityEngine.cpp @@ -0,0 +1,3 @@ +// +// Created by Orange on 11/27/2024. +// diff --git a/tests/UnitTestAstar.cpp b/tests/general/UnitTestAstar.cpp similarity index 100% rename from tests/UnitTestAstar.cpp rename to tests/general/UnitTestAstar.cpp diff --git a/tests/UnitTestColor.cpp b/tests/general/UnitTestColor.cpp similarity index 100% rename from tests/UnitTestColor.cpp rename to tests/general/UnitTestColor.cpp diff --git a/tests/UnitTestLineTrace.cpp b/tests/general/UnitTestLineTrace.cpp similarity index 100% rename from tests/UnitTestLineTrace.cpp rename to tests/general/UnitTestLineTrace.cpp diff --git a/tests/UnitTestMat.cpp b/tests/general/UnitTestMat.cpp similarity index 100% rename from tests/UnitTestMat.cpp rename to tests/general/UnitTestMat.cpp diff --git a/tests/UnitTestMatrix.cpp b/tests/general/UnitTestMatrix.cpp similarity index 100% rename from tests/UnitTestMatrix.cpp rename to tests/general/UnitTestMatrix.cpp diff --git a/tests/UnitTestPrediction.cpp b/tests/general/UnitTestPrediction.cpp similarity index 100% rename from tests/UnitTestPrediction.cpp rename to tests/general/UnitTestPrediction.cpp diff --git a/tests/UnitTestProjection.cpp b/tests/general/UnitTestProjection.cpp similarity index 100% rename from tests/UnitTestProjection.cpp rename to tests/general/UnitTestProjection.cpp diff --git a/tests/UnitTestVector2.cpp b/tests/general/UnitTestVector2.cpp similarity index 100% rename from tests/UnitTestVector2.cpp rename to tests/general/UnitTestVector2.cpp diff --git a/tests/UnitTestVector3.cpp b/tests/general/UnitTestVector3.cpp similarity index 100% rename from tests/UnitTestVector3.cpp rename to tests/general/UnitTestVector3.cpp diff --git a/tests/UnitTestVector4.cpp b/tests/general/UnitTestVector4.cpp similarity index 100% rename from tests/UnitTestVector4.cpp rename to tests/general/UnitTestVector4.cpp From a33ee638b9715d606594ebbce93402a223bfac06 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 30 Nov 2024 03:37:25 +0300 Subject: [PATCH 121/795] added more unit tests --- include/omath/Angles.hpp | 35 +++++++++++++++--- include/omath/collision/LineTracer.hpp | 1 + include/omath/engines/opengl.hpp | 8 ++--- include/omath/pathfinding/NavigationMesh.hpp | 6 ++++ tests/CMakeLists.txt | 2 ++ tests/engines/UnitTestOpenGL.cpp | 4 +-- tests/general/UnitTestAngles.cpp | 38 ++++++++++++++++++++ 7 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 tests/general/UnitTestAngles.cpp diff --git a/include/omath/Angles.hpp b/include/omath/Angles.hpp index 9e557fd6..0dc6d61e 100644 --- a/include/omath/Angles.hpp +++ b/include/omath/Angles.hpp @@ -7,12 +7,39 @@ namespace omath::angles { - [[nodiscard]] constexpr float RadiansToDegrees(const float radiands) + template + requires std::is_floating_point_v + [[nodiscard]] constexpr float RadiansToDegrees(const type& radians) { - return radiands * (180.f / std::numbers::pi_v); + return radians * (type(180) / std::numbers::pi_v); } - [[nodiscard]] constexpr float DegreesToRadians(const float degrees) + + template + requires std::is_floating_point_v + [[nodiscard]] constexpr float DegreesToRadians(const type& degrees) + { + return degrees * (std::numbers::pi_v / type(180)); + } + + template + requires std::is_floating_point_v + [[nodiscard]] type HorizontalFovToVertical(const type& horFov, const type& aspect) { - return degrees * (std::numbers::pi_v / 180.f); + const auto fovRad = DegreesToRadians(horFov); + + const auto vertFov = type(2) * std::atan(std::tan(fovRad / type(2)) / aspect); + + return RadiansToDegrees(vertFov); + } + + template + requires std::is_floating_point_v + [[nodiscard]] type VerticalFovToHorizontal(const type& vertFov, const type& aspect) + { + const auto fovRad = DegreesToRadians(vertFov); + + const auto horFov = type(2) * std::atan(std::tan(fovRad / type(2)) * aspect); + + return RadiansToDegrees(horFov); } } diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/LineTracer.hpp index 1de6d1e7..fc1d7fa1 100644 --- a/include/omath/collision/LineTracer.hpp +++ b/include/omath/collision/LineTracer.hpp @@ -26,6 +26,7 @@ namespace omath::collision public: LineTracer() = delete; + [[nodiscard]] static bool CanTraceLine(const Ray& ray, const Triangle3d& triangle); diff --git a/include/omath/engines/opengl.hpp b/include/omath/engines/opengl.hpp index 6bed17e3..bcda994e 100644 --- a/include/omath/engines/opengl.hpp +++ b/include/omath/engines/opengl.hpp @@ -22,10 +22,10 @@ namespace omath::opengl { return { - {right.x, up.x, -forward.x, 0}, - {right.y, up.y, -forward.y, 0}, - {right.z, up.z, -forward.z, 0}, - {-cam_origin.x, -cam_origin.y, -cam_origin.z, 1}, + {right.x, up.x, -forward.x, 0}, + {right.y, up.y, -forward.y, 0}, + {right.z, up.z, -forward.z, 0}, + {-cam_origin.x, -cam_origin.y, -cam_origin.z, 1}, }; } diff --git a/include/omath/pathfinding/NavigationMesh.hpp b/include/omath/pathfinding/NavigationMesh.hpp index e7aceb42..525cbc42 100644 --- a/include/omath/pathfinding/NavigationMesh.hpp +++ b/include/omath/pathfinding/NavigationMesh.hpp @@ -12,6 +12,12 @@ namespace omath::pathfinding { + + enum Error + { + + }; + class NavigationMesh final { public: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6d8d48e9..109cdc79 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,10 +15,12 @@ add_executable(unit-tests general/UnitTestColor.cpp general/UnitTestVector4.cpp general/UnitTestLineTrace.cpp + general/UnitTestAngles.cpp engines/UnitTestOpenGL.cpp engines/UnitTestUnityEngine.cpp engines/UnitTestSourceEngine.cpp + ) target_link_libraries(unit-tests PRIVATE gtest gtest_main omath glm) diff --git a/tests/engines/UnitTestOpenGL.cpp b/tests/engines/UnitTestOpenGL.cpp index 0c44e4a3..2cb68898 100644 --- a/tests/engines/UnitTestOpenGL.cpp +++ b/tests/engines/UnitTestOpenGL.cpp @@ -5,8 +5,8 @@ #include #include #include -#include -#include +#include +#include #include #include "glm/ext/matrix_clip_space.hpp" diff --git a/tests/general/UnitTestAngles.cpp b/tests/general/UnitTestAngles.cpp new file mode 100644 index 00000000..324ce4dc --- /dev/null +++ b/tests/general/UnitTestAngles.cpp @@ -0,0 +1,38 @@ +// +// Created by Orange on 11/30/2024. +// +#include +#include + + +TEST(UnitTestAngles, RadiansToDeg) +{ + constexpr float rad = 67; + + EXPECT_NEAR(omath::angles::RadiansToDegrees(rad), 3838.82f, 0.01f); +} + +TEST(UnitTestAngles, DegreesToRadians) +{ + constexpr float degree = 90; + + EXPECT_NEAR(omath::angles::DegreesToRadians(degree), 1.5708f, 0.01f); +} + +TEST(UnitTestAngles, HorizontalFovToVerical) +{ + constexpr float hFov = 90; + constexpr float aspectRation = 16.0f / 9.0f; + const auto verticalFov = omath::angles::HorizontalFovToVertical(hFov, aspectRation); + + EXPECT_NEAR(verticalFov, 58.71f, 0.01f); +} + +TEST(UnitTestAngles, VerticalToHorizontal) +{ + constexpr float vFov = 58.71; + constexpr float aspectRation = 16.0f / 9.0f; + const auto horizontalFov = omath::angles::VerticalFovToHorizontal(vFov, aspectRation); + + EXPECT_NEAR(horizontalFov, 89.99f, 0.01f); +} \ No newline at end of file From 1fe5e6e276b88a689c91ae276d601b0cbe8132d7 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 30 Nov 2024 13:54:06 +0300 Subject: [PATCH 122/795] added new class --- SECURITY.md | 2 +- include/omath/Angle.hpp | 156 +++++++++++++++++++++++++++ include/omath/Angles.hpp | 43 +++++--- include/omath/Mat.hpp | 66 ++++++------ include/omath/Triangle3d.hpp | 3 +- include/omath/ViewAngles.hpp | 35 ++++++ include/omath/engines/source.hpp | 3 + tests/CMakeLists.txt | 1 + tests/general/UnitTestAngles.cpp | 14 ++- tests/general/UnitTestViewAngles.cpp | 4 + 10 files changed, 280 insertions(+), 47 deletions(-) create mode 100644 include/omath/Angle.hpp create mode 100644 include/omath/ViewAngles.hpp create mode 100644 tests/general/UnitTestViewAngles.cpp diff --git a/SECURITY.md b/SECURITY.md index 190b1a5a..f114405b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,4 +2,4 @@ ## Reporting a Vulnerability -Please report security issues to `orange-cpp@yandex.com` \ No newline at end of file +Please report security issues to `orange-cpp@yandex.ru` \ No newline at end of file diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp new file mode 100644 index 00000000..8ddaa2ea --- /dev/null +++ b/include/omath/Angle.hpp @@ -0,0 +1,156 @@ +// +// Created by Orange on 11/30/2024. +// + +#pragma once +#include "omath/Angles.hpp" + + +namespace omath +{ + enum class AngleFlags + { + Normalized = 0, + Clamped = 1, + }; + + template + requires std::is_arithmetic_v + class Angle + { + Type m_angle; + public: + + constexpr explicit Angle(const Type& degrees) + { + if constexpr (flags == AngleFlags::Normalized) + m_angle = angles::WrapAngle(degrees, min, max); + + else if constexpr (flags == AngleFlags::Clamped) + m_angle = std::clamp(degrees, min, max); + else + { + static_assert(false); + std::unreachable(); + } + } + + [[nodiscard]] + constexpr static Angle FromDegrees(const Type& degrees) + { + return {degrees}; + } + + [[nodiscard]] + constexpr static Angle FromRadians(const Type& degrees) + { + return {angles::RadiansToDegrees(degrees)}; + } + + [[nodiscard]] + constexpr const Type& operator*() const + { + return m_angle; + } + + [[nodiscard]] + constexpr Type& operator*() + { + return m_angle; + } + + [[nodiscard]] + constexpr const Type& Value() const + { + return **std::as_const(this); + } + + [[nodiscard]] + constexpr Type& Value() + { + return **this; + } + + [[nodiscard]] + constexpr Type AsRadians() const + { + return angles::RadiansToDegrees(m_angle); + } + + [[nodiscard]] + Type Sin() const + { + return std::sin(AsRadians()); + } + + [[nodiscard]] + Type Cos() const + { + return std::sin(AsRadians()); + } + + [[nodiscard]] + Type Tan() const + { + return std::tan(AsRadians()); + } + + [[nodiscard]] + Type Atan() const + { + return std::atan(AsRadians()); + } + + [[nodiscard]] + Type Cot() const + { + return Cos() / Sin(); + } + + [[nodiscard]] + constexpr Angle& operator+=(const Type& other) + { + if constexpr (flags == AngleFlags::Normalized) + m_angle = angles::WrapAngle(m_angle + other, min, max); + + else if constexpr (flags == AngleFlags::Clamped) + m_angle = std::clamp(m_angle + other, min, max); + else + { + static_assert(false); + std::unreachable(); + } + + return *this; + } + + [[nodiscard]] + constexpr Angle& operator-=(const Type& other) + { + return operator+=(-other); + } + + [[nodiscard]] + constexpr Angle& operator+(const Type& other) + { + if constexpr (flags == AngleFlags::Normalized) + return {angles::WrapAngle(m_angle + other, min, max)}; + + else if constexpr (flags == AngleFlags::Clamped) + return {std::clamp(m_angle + other, min, max)}; + + else + static_assert(false); + + std::unreachable(); + } + + [[nodiscard]] + constexpr Angle& operator-(const Type& other) + { + return operator+(-other); + } + + + }; +} diff --git a/include/omath/Angles.hpp b/include/omath/Angles.hpp index 0dc6d61e..7b0a3d19 100644 --- a/include/omath/Angles.hpp +++ b/include/omath/Angles.hpp @@ -4,21 +4,23 @@ #pragma once #include +#include + namespace omath::angles { - template - requires std::is_floating_point_v - [[nodiscard]] constexpr float RadiansToDegrees(const type& radians) + template + requires std::is_floating_point_v + [[nodiscard]] constexpr float RadiansToDegrees(const Type& radians) { - return radians * (type(180) / std::numbers::pi_v); + return radians * (Type(180) / std::numbers::pi_v); } - template - requires std::is_floating_point_v - [[nodiscard]] constexpr float DegreesToRadians(const type& degrees) + template + requires std::is_floating_point_v + [[nodiscard]] constexpr float DegreesToRadians(const Type& degrees) { - return degrees * (std::numbers::pi_v / type(180)); + return degrees * (std::numbers::pi_v / Type(180)); } template @@ -32,14 +34,31 @@ namespace omath::angles return RadiansToDegrees(vertFov); } - template - requires std::is_floating_point_v - [[nodiscard]] type VerticalFovToHorizontal(const type& vertFov, const type& aspect) + template + requires std::is_floating_point_v + [[nodiscard]] Type VerticalFovToHorizontal(const Type& vertFov, const Type& aspect) { const auto fovRad = DegreesToRadians(vertFov); - const auto horFov = type(2) * std::atan(std::tan(fovRad / type(2)) * aspect); + const auto horFov = Type(2) * std::atan(std::tan(fovRad / Type(2)) * aspect); return RadiansToDegrees(horFov); } + + template + requires std::is_arithmetic_v + [[nodiscard]] Type WrapAngle(const Type& angle, const Type& min, const Type& max) + { + if (angle <= max && angle >= min) + return angle; + + const Type range = max - min; + + Type wrappedAngle = std::fmod(angle - min, range); + + if (wrappedAngle < 0) + wrappedAngle += range; + + return wrappedAngle + min; + } } diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 1e717ab0..536ca37f 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -25,7 +25,7 @@ namespace omath }; template - requires (std::is_floating_point_v || std::is_integral_v) + requires std::is_arithmetic_v class Mat final { public: @@ -34,7 +34,7 @@ namespace omath Clear(); } - constexpr Mat(const std::initializer_list>& rows) + constexpr Mat(const std::initializer_list >& rows) { if (rows.size() != Rows) throw std::invalid_argument("Initializer list rows size does not match template parameter Rows"); @@ -59,12 +59,12 @@ namespace omath std::copy_n(rawData, Rows * Columns, m_data.begin()); } - constexpr Mat(const Mat &other) noexcept + constexpr Mat(const Mat& other) noexcept { m_data = other.m_data; } - constexpr Mat(Mat &&other) noexcept + constexpr Mat(Mat&& other) noexcept { m_data = std::move(other.m_data); } @@ -87,7 +87,7 @@ namespace omath return {Rows, Columns}; } - [[nodiscard]] constexpr const Type &At(const size_t rowIndex, const size_t columnIndex) const + [[nodiscard]] constexpr const Type& At(const size_t rowIndex, const size_t columnIndex) const { if (rowIndex >= Rows || columnIndex >= Columns) throw std::out_of_range("Index out of range"); @@ -105,9 +105,9 @@ namespace omath } } - [[nodiscard]] constexpr Type &At(const size_t rowIndex, const size_t columnIndex) + [[nodiscard]] constexpr Type& At(const size_t rowIndex, const size_t columnIndex) { - return const_cast(std::as_const(*this).At(rowIndex, columnIndex)); + return const_cast(std::as_const(*this).At(rowIndex, columnIndex)); } [[nodiscard]] @@ -126,14 +126,15 @@ namespace omath Set(0); } - constexpr void Set(const Type &value) noexcept + constexpr void Set(const Type& value) noexcept { std::ranges::fill(m_data, value); } // Operator overloading for multiplication with another Mat template - constexpr Mat operator*(const Mat &other) const + constexpr Mat operator*( + const Mat& other) const { Mat result; @@ -148,7 +149,7 @@ namespace omath return result; } - constexpr Mat &operator*=(const Type &f) noexcept + constexpr Mat& operator*=(const Type& f) noexcept { for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) @@ -157,19 +158,20 @@ namespace omath } template - constexpr Mat operator*=(const Mat &other) + constexpr Mat operator*=( + const Mat& other) { return *this = *this * other; } - constexpr Mat operator*(const Type &f) const noexcept + constexpr Mat operator*(const Type& f) const noexcept { Mat result(*this); result *= f; return result; } - constexpr Mat &operator/=(const Type &f) noexcept + constexpr Mat& operator/=(const Type& f) noexcept { for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) @@ -177,14 +179,14 @@ namespace omath return *this; } - constexpr Mat operator/(const Type &f) const noexcept + constexpr Mat operator/(const Type& f) const noexcept { Mat result(*this); result /= f; return result; } - constexpr Mat &operator=(const Mat &other) noexcept + constexpr Mat& operator=(const Mat& other) noexcept { if (this == &other) return *this; @@ -194,7 +196,7 @@ namespace omath return *this; } - constexpr Mat &operator=(Mat &&other) noexcept + constexpr Mat& operator=(Mat&& other) noexcept { if (this == &other) return *this; @@ -260,15 +262,15 @@ namespace omath } [[nodiscard]] - constexpr const std::array& RawArray() const + constexpr const std::array& RawArray() const { return m_data; } [[nodiscard]] - constexpr std::array& RawArray() + constexpr std::array& RawArray() { - return const_cast>(std::as_const(*this).RawArray()); + return const_cast>(std::as_const(*this).RawArray()); } [[nodiscard]] @@ -289,20 +291,20 @@ namespace omath } [[nodiscard]] - bool operator==(const Mat & mat) const + bool operator==(const Mat& mat) const { return m_data == mat.m_data; } [[nodiscard]] - bool operator!=(const Mat & mat) const + bool operator!=(const Mat& mat) const { return !operator==(mat); } // Static methods that return fixed-size matrices [[nodiscard]] - constexpr static Mat<4, 4> ToScreenMat(const Type &screenWidth, const Type &screenHeight) noexcept + constexpr static Mat<4, 4> ToScreenMat(const Type& screenWidth, const Type& screenHeight) noexcept { return { @@ -314,7 +316,7 @@ namespace omath } [[nodiscard]] - constexpr static Mat<4, 4> TranslationMat(const Vector3 &diff) noexcept + constexpr static Mat<4, 4> TranslationMat(const Vector3& diff) noexcept { return { @@ -326,24 +328,24 @@ namespace omath } [[nodiscard]] - constexpr static Mat<4, 4> OrientationMat(const Vector3 &forward, const Vector3 &right, - const Vector3 &up) noexcept + constexpr static Mat<4, 4> OrientationMat(const Vector3& forward, const Vector3& right, + const Vector3& up) noexcept { return { {right.x, up.x, forward.x, 0}, {right.y, up.y, forward.y, 0}, {right.z, up.z, forward.z, 0}, - {0, 0, 0, 1}, + {0, 0, 0, 1}, }; } [[nodiscard]] - constexpr static Mat<4, 4> ProjectionMat(const Type &fieldOfView, const Type &aspectRatio, - const Type &near, const Type &far, const Type &lensZoom) noexcept + constexpr static Mat<4, 4> ProjectionMat(const Type& fieldOfView, const Type& aspectRatio, + const Type& near, const Type& far, const Type& lensZoom) noexcept { - const Type &fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); - const Type &frustumHeight = far - near; + const Type& fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); + const Type& frustumHeight = far - near; return { @@ -355,13 +357,13 @@ namespace omath } [[nodiscard]] - constexpr static Mat<4, 1> MatRowFromVector(const Vector3 &vector) noexcept + constexpr static Mat<4, 1> MatRowFromVector(const Vector3& vector) noexcept { return {{vector.x, vector.y, vector.z, 1}}; } [[nodiscard]] - constexpr static Mat<1, 4> MatColumnFromVector(const Vector3 &vector) noexcept + constexpr static Mat<1, 4> MatColumnFromVector(const Vector3& vector) noexcept { return { diff --git a/include/omath/Triangle3d.hpp b/include/omath/Triangle3d.hpp index f755ebf3..370aa957 100644 --- a/include/omath/Triangle3d.hpp +++ b/include/omath/Triangle3d.hpp @@ -10,6 +10,7 @@ namespace omath { public: Triangle3d(const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3); + Vector3 m_vertex1; Vector3 m_vertex2; Vector3 m_vertex3; @@ -29,4 +30,4 @@ namespace omath [[nodiscard]] Vector3 SideBVector() const; }; -} \ No newline at end of file +} diff --git a/include/omath/ViewAngles.hpp b/include/omath/ViewAngles.hpp new file mode 100644 index 00000000..cf36ca21 --- /dev/null +++ b/include/omath/ViewAngles.hpp @@ -0,0 +1,35 @@ +// +// Created by Orange on 11/30/2024. +// +#pragma once +#include +#include <__algorithm/clamp.h> + +#include "omath/Angles.hpp" + + +namespace omath +{ + template + requires std::is_arithmetic_v + class ViewAngles + { + Type pitch; + Type yaw; + Type roll; + + constexpr void SetPitch(const Type& newPitch) + { + pitch = std::clamp(newPitch, min, max); + } + void SetYaw(const Type& newYaw) + { + yaw = std::clamp(newYaw, min, max); + } + void SetRoll(const Type& newRoll) + { + roll = angles::WrapAngle(newRoll, min, max); + } + + }; +} diff --git a/include/omath/engines/source.hpp b/include/omath/engines/source.hpp index 32c248a4..61eb1b62 100644 --- a/include/omath/engines/source.hpp +++ b/include/omath/engines/source.hpp @@ -12,6 +12,7 @@ namespace omath::source constexpr Vector3 kAbsForward = {1, 0, 0}; + template requires std::is_floating_point_v || std::is_integral_v [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> PerspectiveProjectionMatrix( const float fieldOfView, const Type &aspectRatio, const Type &near, const Type &far) @@ -26,4 +27,6 @@ namespace omath::source {0, 0, 1, 0}, }; } + + } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 109cdc79..dbde66ff 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable(unit-tests general/UnitTestVector4.cpp general/UnitTestLineTrace.cpp general/UnitTestAngles.cpp + general/UnitTestViewAngles.cpp engines/UnitTestOpenGL.cpp engines/UnitTestUnityEngine.cpp diff --git a/tests/general/UnitTestAngles.cpp b/tests/general/UnitTestAngles.cpp index 324ce4dc..37b00ffd 100644 --- a/tests/general/UnitTestAngles.cpp +++ b/tests/general/UnitTestAngles.cpp @@ -3,7 +3,7 @@ // #include #include - +#include TEST(UnitTestAngles, RadiansToDeg) { @@ -35,4 +35,16 @@ TEST(UnitTestAngles, VerticalToHorizontal) const auto horizontalFov = omath::angles::VerticalFovToHorizontal(vFov, aspectRation); EXPECT_NEAR(horizontalFov, 89.99f, 0.01f); +} +TEST(UnitTestAngles, WrapAngle) +{ + const float wrapped = omath::angles::WrapAngle(361.f, 0.f, 360.f); + + EXPECT_NEAR(wrapped, 1.f, 0.01f); +} +TEST(UnitTestAngles, WrapAngleNegativeRange) +{ + const float wrapped = omath::angles::WrapAngle(-90.f, 0.f, 360.f); + + EXPECT_NEAR(wrapped, 270.f, 0.01f); } \ No newline at end of file diff --git a/tests/general/UnitTestViewAngles.cpp b/tests/general/UnitTestViewAngles.cpp new file mode 100644 index 00000000..97c90e3e --- /dev/null +++ b/tests/general/UnitTestViewAngles.cpp @@ -0,0 +1,4 @@ +// +// Created by Orange on 11/30/2024. +// +#include \ No newline at end of file From dac0684405c62e0e6dc2e84a6d0f702a2b6d8e41 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 30 Nov 2024 13:55:15 +0300 Subject: [PATCH 123/795] fixed format --- .clang-format | 62 ++++++++++++++++++++++++++++++++++++++++ include/omath/Matrix.hpp | 26 ++++++++--------- 2 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..d0745355 --- /dev/null +++ b/.clang-format @@ -0,0 +1,62 @@ +# Generated from CLion C/C++ Code Style settings +--- +Language: Cpp +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignOperands: true +AlignTrailingComments: false +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AlwaysBreakTemplateDeclarations: Yes +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: true + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBraces: Custom +BreakConstructorInitializers: AfterColon +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 120 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ContinuationIndentWidth: 8 +IncludeCategories: + - Regex: '^<.*' + Priority: 1 + - Regex: '^".*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentWidth: 4 +InsertNewlineAtEOF: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +PointerAlignment: Left +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: false +SpaceBeforeRangeBasedForLoopColon: false +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +TabWidth: 4 +... diff --git a/include/omath/Matrix.hpp b/include/omath/Matrix.hpp index 81e7b4fa..1fbdb4b9 100644 --- a/include/omath/Matrix.hpp +++ b/include/omath/Matrix.hpp @@ -1,7 +1,7 @@ #pragma once +#include #include #include -#include namespace omath { @@ -27,11 +27,11 @@ namespace omath [[nodiscard]] static Matrix ProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); - Matrix(const Matrix &other); + Matrix(const Matrix& other); - Matrix(size_t rows, size_t columns, const float *pRaw); + Matrix(size_t rows, size_t columns, const float* pRaw); - Matrix(Matrix &&other) noexcept; + Matrix(Matrix&& other) noexcept; [[nodiscard]] size_t RowCount() const noexcept; @@ -43,7 +43,7 @@ namespace omath std::pair Size() const noexcept; [[nodiscard]] - float &At(size_t iRow, size_t iCol); + float& At(size_t iRow, size_t iCol); [[nodiscard]] float Sum(); @@ -56,17 +56,17 @@ namespace omath void Set(float val); [[nodiscard]] - const float &At(size_t iRow, size_t iCol) const; + const float& At(size_t iRow, size_t iCol) const; - Matrix operator*(const Matrix &other) const; + Matrix operator*(const Matrix& other) const; - Matrix& operator*=(const Matrix &other); + Matrix& operator*=(const Matrix& other); Matrix operator*(float f) const; - Matrix &operator*=(float f); + Matrix& operator*=(float f); - Matrix &operator/=(float f); + Matrix& operator/=(float f); void Clear(); @@ -85,9 +85,9 @@ namespace omath [[nodiscard]] const float* Raw() const; - Matrix &operator=(const Matrix &other); + Matrix& operator=(const Matrix& other); - Matrix &operator=(Matrix &&other) noexcept; + Matrix& operator=(Matrix&& other) noexcept; Matrix operator/(float f) const; @@ -101,4 +101,4 @@ namespace omath size_t m_columns; std::unique_ptr m_data; }; -} \ No newline at end of file +} // namespace omath From 7e29ea339e6d48df9d4d618bbe711a269abf790e Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 30 Nov 2024 14:11:39 +0300 Subject: [PATCH 124/795] modified output dir --- CMakeLists.txt | 3 ++ source/Matrix.cpp | 103 +++++++++++++++++++++------------------------- 2 files changed, 49 insertions(+), 57 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b2d004b4..f79bac97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,9 @@ project(omath VERSION 1.0.0) set(CMAKE_CXX_STANDARD 26) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") + option(OMATH_BUILD_TESTS "Build unit tests" ON) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) diff --git a/source/Matrix.cpp b/source/Matrix.cpp index de92ab9b..b07dde40 100644 --- a/source/Matrix.cpp +++ b/source/Matrix.cpp @@ -1,13 +1,12 @@ #include "omath/Matrix.hpp" -#include "omath/Vector3.hpp" #include "omath/Angles.hpp" +#include "omath/Vector3.hpp" +#include #include -#include #include #include -#include namespace omath @@ -31,23 +30,23 @@ namespace omath m_columns = rows.begin()->size(); - for (const auto& row : rows) + for (const auto& row: rows) if (row.size() != m_columns) throw std::invalid_argument("All rows must have the same number of columns."); m_data = std::make_unique(m_rows * m_columns); size_t i = 0; - for (const auto& row : rows) + for (const auto& row: rows) { size_t j = 0; - for (const auto& value : row) + for (const auto& value: row) At(i, j++) = value; ++i; } } - Matrix::Matrix(const Matrix &other) + Matrix::Matrix(const Matrix& other) { m_rows = other.m_rows; m_columns = other.m_columns; @@ -59,7 +58,7 @@ namespace omath At(i, j) = other.At(i, j); } - Matrix::Matrix(const size_t rows, const size_t columns, const float *pRaw) + Matrix::Matrix(const size_t rows, const size_t columns, const float* pRaw) { m_rows = rows; m_columns = columns; @@ -67,9 +66,8 @@ namespace omath m_data = std::make_unique(m_rows * m_columns); - for (size_t i = 0; i < rows*columns; ++i) + for (size_t i = 0; i < rows * columns; ++i) At(i / rows, i % columns) = pRaw[i]; - } size_t Matrix::RowCount() const noexcept @@ -77,7 +75,7 @@ namespace omath return m_rows; } - Matrix::Matrix(Matrix &&other) noexcept + Matrix::Matrix(Matrix&& other) noexcept { m_rows = other.m_rows; m_columns = other.m_columns; @@ -99,7 +97,7 @@ namespace omath return {RowCount(), ColumnsCount()}; } - float &Matrix::At(const size_t iRow, const size_t iCol) + float& Matrix::At(const size_t iRow, const size_t iCol) { return const_cast(std::as_const(*this).At(iRow, iCol)); } @@ -115,12 +113,12 @@ namespace omath return sum; } - const float &Matrix::At(const size_t iRow, const size_t iCol) const + const float& Matrix::At(const size_t iRow, const size_t iCol) const { return m_data[iRow * m_columns + iCol]; } - Matrix Matrix::operator*(const Matrix &other) const + Matrix Matrix::operator*(const Matrix& other) const { if (m_columns != other.m_rows) throw std::runtime_error("n != m"); @@ -136,7 +134,7 @@ namespace omath return outMat; } - Matrix & Matrix::operator*=(const Matrix &other) + Matrix& Matrix::operator*=(const Matrix& other) { *this = *this * other; return *this; @@ -152,7 +150,7 @@ namespace omath return out; } - Matrix &Matrix::operator*=(const float f) + Matrix& Matrix::operator*=(const float f) { for (size_t i = 0; i < RowCount(); i++) for (size_t j = 0; j < ColumnsCount(); j++) @@ -164,8 +162,8 @@ namespace omath { Set(0.f); } - - Matrix &Matrix::operator=(const Matrix &other) + + Matrix& Matrix::operator=(const Matrix& other) { if (this == &other) return *this; @@ -175,10 +173,9 @@ namespace omath At(i, j) = other.At(i, j); return *this; - } - Matrix &Matrix::operator=(Matrix &&other) noexcept + Matrix& Matrix::operator=(Matrix&& other) noexcept { if (this == &other) return *this; @@ -191,10 +188,9 @@ namespace omath other.m_columns = 0; return *this; - } - Matrix &Matrix::operator/=(const float f) + Matrix& Matrix::operator/=(const float f) { for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) @@ -221,9 +217,9 @@ namespace omath { for (size_t j = 0; j < m_columns; ++j) { - str += std::format("{:.1f}",At(i, j)); + str += std::format("{:.1f}", At(i, j)); - if (j == m_columns-1) + if (j == m_columns - 1) str += '\n'; else str += ' '; @@ -306,49 +302,42 @@ namespace omath Matrix Matrix::ToScreenMatrix(const float screenWidth, const float screenHeight) { - return - { - {screenWidth / 2.f, 0.f, 0.f, 0.f}, - {0.f, -screenHeight / 2.f, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {screenWidth / 2.f, screenHeight / 2.f, 0.f, 1.f}, - }; + return { + {screenWidth / 2.f, 0.f, 0.f, 0.f}, + {0.f, -screenHeight / 2.f, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {screenWidth / 2.f, screenHeight / 2.f, 0.f, 1.f}, + }; } - Matrix Matrix::TranslationMatrix(const Vector3 &diff) + Matrix Matrix::TranslationMatrix(const Vector3& diff) { - return - { - {1.f, 0.f, 0.f, 0.f}, - {0.f, 1.f, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {diff.x, diff.y, diff.z, 1.f}, + return { + {1.f, 0.f, 0.f, 0.f}, + {0.f, 1.f, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {diff.x, diff.y, diff.z, 1.f}, }; } - Matrix Matrix::OrientationMatrix(const Vector3 &forward, const Vector3 &right, const Vector3 &up) + Matrix Matrix::OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up) { - return - { - {right.x, up.x, forward.x, 0.f}, - {right.y, up.y, forward.y, 0.f}, - {right.z, up.z, forward.z, 0.f}, - {0.f, 0.f, 0.f, 1.f}, + return { + {right.x, up.x, forward.x, 0.f}, + {right.y, up.y, forward.y, 0.f}, + {right.z, up.z, forward.z, 0.f}, + {0.f, 0.f, 0.f, 1.f}, }; } - Matrix Matrix::ProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, - const float far) + Matrix Matrix::ProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); - return - { - {1.f / (aspectRatio*fovHalfTan), 0.f, 0.f, 0.f}, - {0.f, 1.f / fovHalfTan, 0.f, 0.f}, - {0.f, 0.f, (far + near) / (far - near), 2.f * near * far / (far - near)}, - {0.f, 0.f, -1.f, 0.f} - }; + return {{1.f / (aspectRatio * fovHalfTan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fovHalfTan, 0.f, 0.f}, + {0.f, 0.f, (far + near) / (far - near), 2.f * near * far / (far - near)}, + {0.f, 0.f, -1.f, 0.f}}; } const float* Matrix::Raw() const @@ -356,9 +345,9 @@ namespace omath return m_data.get(); } - void Matrix::SetDataFromRaw(const float *pRawMatrix) + void Matrix::SetDataFromRaw(const float* pRawMatrix) { - for (size_t i = 0; i < m_columns*m_rows; ++i) + for (size_t i = 0; i < m_columns * m_rows; ++i) At(i / m_rows, i % m_columns) = pRawMatrix[i]; } @@ -368,4 +357,4 @@ namespace omath m_rows = 0; m_data = nullptr; } -} \ No newline at end of file +} // namespace omath From 883d27ed581ddf403be2cdf0800b1e98ff4959d4 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 30 Nov 2024 14:27:32 +0300 Subject: [PATCH 125/795] updated coc --- CODE_OF_CONDUCT.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index a3f36ab4..b394840e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ -## Goal +## 🎯 Goal My goal is to provide a space where it is safe for everyone to contribute to, and get support for, open-source software in a respectful and cooperative @@ -10,7 +10,7 @@ surrounding community a place for everyone. As members, contributors, and everyone else who may participate in the development, I strive to keep the entire experience civil. -## Standards +## 📜 Standards Our community standards exist in order to make sure everyone feels comfortable contributing to the project(s) together. @@ -27,14 +27,14 @@ Examples of breaking each rule respectively include: - Posting distasteful imagery, trolling, or posting things unrelated to the topic at hand. - Treating someone as worse because of their lack of understanding of an issue. -## Enforcement +## ⚡ Enforcement Enforcement of this CoC is done by Orange++ and/or other core contributors. I, as the core developer, will strive my best to keep this community civil and following the standards outlined above. -### Reporting incidents +### 🚩 Reporting incidents If you believe an incident of breaking these standards has occurred, but nobody has taken appropriate action, you can privately contact the people responsible for dealing @@ -47,10 +47,10 @@ with such incidents in multiple ways: - `@orange_cpp` ***Telegram*** -- `@orange-cpp` +- `@orange_cpp` I guarantee your privacy and will not share those reports with anyone. -## Enforcement Strategy +## ⚖️ Enforcement Strategy Depending on the severity of the infraction, any action from the list below may be applied. Please keep in mind cases are reviewed on a per-case basis and members are the ultimate @@ -63,27 +63,27 @@ to be taken is still up to the member. For example, if the matter at hand regards a representative of a marginalized group or minority, the member might ask for a first-hand opinion from another representative of such group. -### Correction/Edit +### ✏️ Correction/Edit If your message is found to be misleading or poorly worded, a member might edit your message. -### Warning/Deletion +### ⚠️ Warning/Deletion If your message is found inappropriate, a member might give you a public or private warning, and/or delete your message. -### Mute +### 🔇 Mute If your message is disruptive, or you have been repeatedly violating the standards, a member might mute (or temporarily ban) you. -### Ban +### ⛔ Ban If your message is hateful, very disruptive, or other, less serious infractions are repeated ignoring previous punishments, a member might ban you permanently. -## Scope +## 🔎 Scope This CoC shall apply to all projects ran under the Orange++ lead and all _official_ communities outside of GitHub. From f8e7faa5709ac03e474fc19597eb2bdb106a036c Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 30 Nov 2024 14:28:15 +0300 Subject: [PATCH 126/795] coc fix --- CODE_OF_CONDUCT.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b394840e..f2126066 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -48,6 +48,7 @@ with such incidents in multiple ways: ***Telegram*** - `@orange_cpp` + I guarantee your privacy and will not share those reports with anyone. ## ⚖️ Enforcement Strategy From 46b4eb9151ee59f945ea8b9f170f0af0a21e8278 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 1 Dec 2024 03:51:40 +0300 Subject: [PATCH 127/795] huge improvement --- CMakePresets.json | 4 +- extlibs/CMakeLists.txt | 3 +- include/omath/Angle.hpp | 52 +++++------ include/omath/Angles.hpp | 4 +- include/omath/Mat.hpp | 118 ++++++++++++------------ include/omath/Vector3.hpp | 4 - include/omath/ViewAngles.hpp | 30 ++----- include/omath/engines/source.hpp | 119 +++++++++++++++++++++++-- include/omath/projection/Camera.hpp | 55 +++++++++--- source/Vector3.cpp | 46 ---------- source/prediction/Projectile.cpp | 4 +- source/projection/Camera.cpp | 45 ---------- tests/CMakeLists.txt | 3 +- tests/engines/UnitTestOpenGL.cpp | 12 +-- tests/engines/UnitTestSourceEngine.cpp | 28 +++++- tests/general/UnitTestAngle.cpp | 3 + tests/general/UnitTestProjection.cpp | 13 ++- 17 files changed, 293 insertions(+), 250 deletions(-) create mode 100644 tests/general/UnitTestAngle.cpp diff --git a/CMakePresets.json b/CMakePresets.json index 74671b4a..d67436b9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,8 +8,8 @@ "binaryDir": "${sourceDir}/cmake-build/build/${presetName}", "installDir": "${sourceDir}/cmake-build/install/${presetName}", "cacheVariables": { - "CMAKE_C_COMPILER": "clang.exe", - "CMAKE_CXX_COMPILER": "clang++.exe" + "CMAKE_C_COMPILER": "cl.exe", + "CMAKE_CXX_COMPILER": "cl.exe" }, "condition": { "type": "equals", diff --git a/extlibs/CMakeLists.txt b/extlibs/CMakeLists.txt index caca17c7..bf73c7af 100644 --- a/extlibs/CMakeLists.txt +++ b/extlibs/CMakeLists.txt @@ -1,2 +1 @@ -add_subdirectory(googletest) -add_subdirectory(glm) \ No newline at end of file +add_subdirectory(googletest) \ No newline at end of file diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index 8ddaa2ea..9b5c64f2 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -5,6 +5,8 @@ #pragma once #include "omath/Angles.hpp" +#include + namespace omath { @@ -14,14 +16,12 @@ namespace omath Clamped = 1, }; - template + template requires std::is_arithmetic_v class Angle { Type m_angle; - public: - - constexpr explicit Angle(const Type& degrees) + constexpr Angle(const Type& degrees) { if constexpr (flags == AngleFlags::Normalized) m_angle = angles::WrapAngle(degrees, min, max); @@ -34,17 +34,20 @@ namespace omath std::unreachable(); } } - + public: [[nodiscard]] constexpr static Angle FromDegrees(const Type& degrees) { return {degrees}; } + constexpr Angle() : m_angle(0) + { + } [[nodiscard]] constexpr static Angle FromRadians(const Type& degrees) { - return {angles::RadiansToDegrees(degrees)}; + return {angles::RadiansToDegrees(degrees)}; } [[nodiscard]] @@ -54,23 +57,11 @@ namespace omath } [[nodiscard]] - constexpr Type& operator*() + constexpr Type AsDegrees() const { return m_angle; } - [[nodiscard]] - constexpr const Type& Value() const - { - return **std::as_const(this); - } - - [[nodiscard]] - constexpr Type& Value() - { - return **this; - } - [[nodiscard]] constexpr Type AsRadians() const { @@ -86,7 +77,7 @@ namespace omath [[nodiscard]] Type Cos() const { - return std::sin(AsRadians()); + return std::cos(AsRadians()); } [[nodiscard]] @@ -108,13 +99,13 @@ namespace omath } [[nodiscard]] - constexpr Angle& operator+=(const Type& other) + constexpr Angle& operator+=(const Angle& other) { if constexpr (flags == AngleFlags::Normalized) - m_angle = angles::WrapAngle(m_angle + other, min, max); + m_angle = angles::WrapAngle(m_angle + other.m_angle, min, max); else if constexpr (flags == AngleFlags::Clamped) - m_angle = std::clamp(m_angle + other, min, max); + m_angle = std::clamp(m_angle + other.m_angle, min, max); else { static_assert(false); @@ -125,19 +116,22 @@ namespace omath } [[nodiscard]] - constexpr Angle& operator-=(const Type& other) + constexpr std::partial_ordering operator<=>(const Angle& other) const = default; + + [[nodiscard]] + constexpr Angle& operator-=(const Angle& other) { return operator+=(-other); } [[nodiscard]] - constexpr Angle& operator+(const Type& other) + constexpr Angle& operator+(const Angle& other) { if constexpr (flags == AngleFlags::Normalized) - return {angles::WrapAngle(m_angle + other, min, max)}; + return {angles::WrapAngle(m_angle + other.m_angle, min, max)}; else if constexpr (flags == AngleFlags::Clamped) - return {std::clamp(m_angle + other, min, max)}; + return {std::clamp(m_angle + other.m_angle, min, max)}; else static_assert(false); @@ -146,11 +140,9 @@ namespace omath } [[nodiscard]] - constexpr Angle& operator-(const Type& other) + constexpr Angle& operator-(const Angle& other) { return operator+(-other); } - - }; } diff --git a/include/omath/Angles.hpp b/include/omath/Angles.hpp index 7b0a3d19..47a287a7 100644 --- a/include/omath/Angles.hpp +++ b/include/omath/Angles.hpp @@ -11,14 +11,14 @@ namespace omath::angles { template requires std::is_floating_point_v - [[nodiscard]] constexpr float RadiansToDegrees(const Type& radians) + [[nodiscard]] constexpr Type RadiansToDegrees(const Type& radians) { return radians * (Type(180) / std::numbers::pi_v); } template requires std::is_floating_point_v - [[nodiscard]] constexpr float DegreesToRadians(const Type& degrees) + [[nodiscard]] constexpr Type DegreesToRadians(const Type& degrees) { return degrees * (std::numbers::pi_v / Type(180)); } diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 536ca37f..2f25bd50 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -5,10 +5,10 @@ #include #include #include -#include -#include "Vector3.hpp" #include +#include #include "Angles.hpp" +#include "Vector3.hpp" namespace omath @@ -33,8 +33,11 @@ namespace omath { Clear(); } - - constexpr Mat(const std::initializer_list >& rows) + constexpr static MatStoreType GetStoreOrdering() + { + return StoreType; + } + constexpr Mat(const std::initializer_list>& rows) { if (rows.size() != Rows) throw std::invalid_argument("Initializer list rows size does not match template parameter Rows"); @@ -44,7 +47,7 @@ namespace omath { if (rowIt->size() != Columns) throw std::invalid_argument( - "All rows must have the same number of columns as template parameter Columns"); + "All rows must have the same number of columns as template parameter Columns"); auto colIt = rowIt->begin(); for (size_t j = 0; j < Columns; ++j, ++colIt) @@ -133,8 +136,8 @@ namespace omath // Operator overloading for multiplication with another Mat template - constexpr Mat operator*( - const Mat& other) const + constexpr Mat + operator*(const Mat& other) const { Mat result; @@ -158,8 +161,8 @@ namespace omath } template - constexpr Mat operator*=( - const Mat& other) + constexpr Mat + operator*=(const Mat& other) { return *this = *this * other; } @@ -306,24 +309,22 @@ namespace omath [[nodiscard]] constexpr static Mat<4, 4> ToScreenMat(const Type& screenWidth, const Type& screenHeight) noexcept { - return - { - {screenWidth / 2, 0, 0, 0}, - {0, -screenHeight / 2, 0, 0}, - {0, 0, 1, 0}, - {screenWidth / 2, screenHeight / 2, 0, 1}, + return { + {screenWidth / 2, 0, 0, 0}, + {0, -screenHeight / 2, 0, 0}, + {0, 0, 1, 0}, + {screenWidth / 2, screenHeight / 2, 0, 1}, }; } [[nodiscard]] constexpr static Mat<4, 4> TranslationMat(const Vector3& diff) noexcept { - return - { - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {diff.x, diff.y, diff.z, 1}, + return { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {diff.x, diff.y, diff.z, 1}, }; } @@ -331,52 +332,55 @@ namespace omath constexpr static Mat<4, 4> OrientationMat(const Vector3& forward, const Vector3& right, const Vector3& up) noexcept { - return - { - {right.x, up.x, forward.x, 0}, - {right.y, up.y, forward.y, 0}, - {right.z, up.z, forward.z, 0}, - {0, 0, 0, 1}, + return { + {right.x, up.x, forward.x, 0}, + {right.y, up.y, forward.y, 0}, + {right.z, up.z, forward.z, 0}, + {0, 0, 0, 1}, }; } [[nodiscard]] - constexpr static Mat<4, 4> ProjectionMat(const Type& fieldOfView, const Type& aspectRatio, - const Type& near, const Type& far, const Type& lensZoom) noexcept + constexpr static Mat<4, 4> ProjectionMat(const Type& fieldOfView, const Type& aspectRatio, const Type& near, + const Type& far, const Type& lensZoom) noexcept { const Type& fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); const Type& frustumHeight = far - near; - return - { - {-1 / (aspectRatio * fovHalfTan) * lensZoom, 0, 0, 0}, - {0, -1 / fovHalfTan * lensZoom, 0, 0}, - {0, 0, -far / frustumHeight, -1}, - {0, 0, near * far / frustumHeight, 0} - }; - } - - [[nodiscard]] - constexpr static Mat<4, 1> MatRowFromVector(const Vector3& vector) noexcept - { - return {{vector.x, vector.y, vector.z, 1}}; - } - - [[nodiscard]] - constexpr static Mat<1, 4> MatColumnFromVector(const Vector3& vector) noexcept - { - return - { - { - vector.x, - vector.y, - vector.z, - 1 - } - }; + return {{-1 / (aspectRatio * fovHalfTan) * lensZoom, 0, 0, 0}, + {0, -1 / fovHalfTan * lensZoom, 0, 0}, + {0, 0, -far / frustumHeight, -1}, + {0, 0, near * far / frustumHeight, 0}}; } private: std::array m_data; }; -} + + template + [[nodiscard]] + constexpr static Mat<1, 4, T, St> MatRowFromVector(const Vector3& vector) noexcept + { + return {{vector.x, vector.y, vector.z, 1}}; + } + + template + [[nodiscard]] + constexpr static Mat<4, 1, T, St> MatColumnFromVector(const Vector3& vector) noexcept + { + return { + {vector.x}, {vector.y}, {vector.z}, {1}}; + } + + template + [[nodiscard]] + constexpr Mat<4, 4, T, St> MatTranslation(const Vector3& diff) noexcept + { + return { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {diff.x, diff.y, diff.z, 1}, + }; + } +} // namespace omath diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index a9a687ed..e22d8156 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -216,10 +216,6 @@ namespace omath [[nodiscard]] Vector3 ViewAngleTo(const Vector3& other) const; - [[nodiscard]] static Vector3 ForwardVector(float pitch, float yaw); - [[nodiscard]] static Vector3 RightVector(float pitch, float yaw, float roll); - [[nodiscard]] static Vector3 UpVector(float pitch, float yaw, float roll); - [[nodiscard]] std::tuple AsTuple() const { return std::make_tuple(x, y, z); diff --git a/include/omath/ViewAngles.hpp b/include/omath/ViewAngles.hpp index cf36ca21..d744f6ba 100644 --- a/include/omath/ViewAngles.hpp +++ b/include/omath/ViewAngles.hpp @@ -2,34 +2,14 @@ // Created by Orange on 11/30/2024. // #pragma once -#include -#include <__algorithm/clamp.h> - -#include "omath/Angles.hpp" - namespace omath { - template - requires std::is_arithmetic_v - class ViewAngles + template + struct ViewAngles { - Type pitch; - Type yaw; - Type roll; - - constexpr void SetPitch(const Type& newPitch) - { - pitch = std::clamp(newPitch, min, max); - } - void SetYaw(const Type& newYaw) - { - yaw = std::clamp(newYaw, min, max); - } - void SetRoll(const Type& newRoll) - { - roll = angles::WrapAngle(newRoll, min, max); - } - + PitchType pitch; + YawType yaw; + RollType roll; }; } diff --git a/include/omath/engines/source.hpp b/include/omath/engines/source.hpp index 61eb1b62..5bb085e7 100644 --- a/include/omath/engines/source.hpp +++ b/include/omath/engines/source.hpp @@ -2,8 +2,12 @@ // Created by Orange on 11/24/2024. // #pragma once -#include "omath/Vector3.hpp" #include "omath/Mat.hpp" +#include "omath/Vector3.hpp" + +#include +#include +#include namespace omath::source { @@ -11,22 +15,119 @@ namespace omath::source constexpr Vector3 kAbsRight = {0, -1, 0}; constexpr Vector3 kAbsForward = {1, 0, 0}; + using PitchAngle = Angle; + using YawAngle = Angle; + using RollAngle = Angle; + + using ViewAngles = omath::ViewAngles; + + + inline Vector3 ForwardVector(const ViewAngles& angles); + inline Vector3 RightVector(const ViewAngles& angles); + inline Vector3 UpVector(const ViewAngles& angles); + + + template + requires std::is_floating_point_v || std::is_integral_v + [[nodiscard]] constexpr Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> ViewMatrixFromVecs(const Vector3& forward, const Vector3& right, + const Vector3& up, const Vector3& camera_pos) + { + return MatTranslation(-camera_pos) * Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR>{ + {right.x, up.x, forward.x, 0}, + {right.y, up.y, forward.y, 0}, + {right.z, up.z, forward.z, 0}, + {0, 0, 0, 1}, + }; + } + + template + requires std::is_floating_point_v || std::is_integral_v + [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> ViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + { + return ViewMatrixFromVecs(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + } - template requires std::is_floating_point_v || std::is_integral_v - [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> PerspectiveProjectionMatrix( - const float fieldOfView, const Type &aspectRatio, const Type &near, const Type &far) + template + requires std::is_floating_point_v || std::is_integral_v + [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> + PerspectiveProjectionMatrix(const Type& fieldOfView, const Type& aspectRatio, const Type& near, const Type& far) { const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); + return { + {static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, static_cast(1) / (fovHalfTan), 0, 0}, + {0, 0, (far + near) / (far - near), -(static_cast(2) * far * near) / (far - near)}, + {0, 0, 1, 0}, + }; + } + // Copied from + // https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/mp/src/mathlib/mathlib_base.cpp#L919 + [[nodiscard]] + inline Vector3 ForwardVector(const ViewAngles& angles) + { + const auto cosPitch = angles.pitch.Cos(); + const auto sinPitch = angles.pitch.Sin(); + + const auto cosYaw = angles.yaw.Cos(); + const auto sinYaw = angles.yaw.Sin(); + + + return {cosPitch * cosYaw, cosPitch * sinYaw, -sinPitch}; + } + + [[nodiscard]] + inline Vector3 RightVector(const ViewAngles& angles) + { + const auto cosPitch = angles.pitch.Cos(); + const auto sinPitch = angles.pitch.Sin(); + + const auto cosYaw = angles.yaw.Cos(); + const auto sinYaw = angles.yaw.Sin(); + + const auto cosRoll = angles.roll.Cos(); + const auto sinRoll = angles.roll.Sin(); + + + return + { + -1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, + -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, + -1 * sinRoll * cosPitch + }; + } + + [[nodiscard]] + inline Vector3 UpVector(const ViewAngles& angles) + { + const auto cosPitch = angles.pitch.Cos(); + const auto sinPitch = angles.pitch.Sin(); + + const auto cosYaw = angles.yaw.Cos(); + const auto sinYaw = angles.yaw.Sin(); + + const auto cosRoll = angles.roll.Cos(); + const auto sinRoll = angles.roll.Sin(); + + return { - {static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, static_cast(1) / (fovHalfTan), 0, 0}, - {0, 0, (far + near) / (far - near), -(static_cast(2) * far * near) / (far - near)}, - {0, 0, 1, 0}, + cosRoll * sinPitch * cosYaw + - sinRoll * -sinYaw, + cosRoll * sinPitch * sinYaw + - sinRoll * cosYaw, + cosRoll * cosPitch, }; } + using Camera = omath::projection::Camera), decltype(PerspectiveProjectionMatrix)>; + + // Camera(const Vector3& position, const ViewAnglesType& viewAngles, const ViewPort& viewPort, + // const Angle& fov, const float near, const float far, + // const std::function& viewMatFunc, const std::function& projFunc) -} + inline Camera CreateCamera(const Vector3& position, const auto& viewAngles, const projection::ViewPort& viewPort, + const Angle& fov, const float near, const float far) + { + return Camera(position, viewAngles, viewPort, fov, near, far, ViewMatrix, PerspectiveProjectionMatrix); + } +} // namespace omath::source diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index ac5a2415..e190664c 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -5,9 +5,11 @@ #pragma once #include -#include #include +#include #include "ErrorCodes.hpp" +#include +#include namespace omath::projection @@ -18,29 +20,60 @@ namespace omath::projection float m_width; float m_height; - [[nodiscard]] constexpr float AspectRatio() const {return m_width / m_height;} + [[nodiscard]] constexpr float AspectRatio() const + { + return m_width / m_height; + } }; + template + requires std::is_same_v, + std::invoke_result_t> class Camera { public: - Camera(const Vector3& position, const Vector3& viewAngles, const ViewPort& viewPort, - float fov, float near, float far, float lensZoom); - void SetViewAngles(const Vector3& viewAngles); + Camera(const Vector3& position, const ViewAnglesType& viewAngles, const ViewPort& viewPort, + const Angle& fov, const float near, const float far, + const std::function& viewMatFunc, const std::function& projFunc) : + m_viewPort(viewPort), m_fieldOfView(fov), m_farPlaneDistance(far), m_nearPlaneDistance(near), + m_viewAngles(viewAngles), m_origin(position), CreateViewMatrix(viewMatFunc), CreateProjectionMatrix(projFunc) + { + } + + void LookAt(const Vector3& target); + + [[nodiscard]] auto GetViewMatrix() const + { + return CreateViewMatrix(m_viewAngles, m_origin); + } - [[nodiscard]] Mat<4, 4> GetViewMatrix() const; + [[nodiscard]] auto GetProjectionMatrix() const + { + return CreateProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, m_farPlaneDistance); + } - [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const; + [[nodiscard]] std::expected WorldToScreen([[maybe_unused]] const Vector3& worldPosition) const + { + using mat = std::invoke_result_t; + const auto vecAsMatrix = MatColumnFromVector(worldPosition); + + const auto projected = GetViewMatrix().Transposed() * vecAsMatrix; + + + return Vector3{projected.At(0,0), projected.At(1,0), projected.At(2,0)}; + } ViewPort m_viewPort{}; - float m_fieldOfView; + Angle m_fieldOfView; float m_farPlaneDistance; float m_nearPlaneDistance; - float m_lensZoom; private: - Vector3 m_viewAngles; + ViewAnglesType m_viewAngles; Vector3 m_origin; + + std::function CreateViewMatrix; + std::function CreateProjectionMatrix; }; -} +} // namespace omath::projection diff --git a/source/Vector3.cpp b/source/Vector3.cpp index acd2b027..45b9f1e9 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -20,50 +20,4 @@ namespace omath 0.f }; } - - Vector3 Vector3::ForwardVector(const float pitch, const float yaw) - { - const auto cosPitch = std::cos(angles::DegreesToRadians(pitch)); - const auto sinPitch = std::sin(angles::DegreesToRadians(pitch)); - - const auto cosYaw = std::cos(angles::DegreesToRadians(yaw)); - const auto sinYaw = std::sin(angles::DegreesToRadians(yaw)); - - - return - { - cosPitch*cosYaw, - cosPitch*sinYaw, - sinPitch - }; - } - - Vector3 Vector3::RightVector(const float pitch, const float yaw, const float roll) - { - const auto radPitch = angles::DegreesToRadians(pitch); - const auto radYaw = angles::DegreesToRadians(yaw); - const auto radRoll = angles::DegreesToRadians(roll); - - const auto cosPitch = std::cos(radPitch); - const auto sinPitch = std::sin(radPitch); - - const auto cosYaw = std::cos(radYaw); - const auto sinYaw = std::sin(radYaw); - - const auto cosRoll = std::cos(radRoll); - const auto sinRoll = std::sin(radRoll); - - - return - { - sinRoll*sinPitch*cosYaw + cosRoll*sinYaw, - sinRoll*sinPitch*sinYaw - cosRoll*cosYaw, - -sinRoll*cosPitch - }; - } - - Vector3 Vector3::UpVector(float pitch, float yaw, float roll) - { - return RightVector(pitch, yaw, roll).Cross(ForwardVector(pitch, yaw)); - } } \ No newline at end of file diff --git a/source/prediction/Projectile.cpp b/source/prediction/Projectile.cpp index 26cfd8e7..3ceb951f 100644 --- a/source/prediction/Projectile.cpp +++ b/source/prediction/Projectile.cpp @@ -4,13 +4,13 @@ #include "omath/prediction/Projectile.hpp" #include - +#include namespace omath::prediction { Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const { - auto currentPos = m_origin + Vector3::ForwardVector(pitch, yaw) * m_launchSpeed * time; + auto currentPos = m_origin + omath::source::ForwardVector({source::PitchAngle::FromDegrees(pitch), source::YawAngle::FromDegrees(yaw), source::RollAngle::FromDegrees(0)}) * m_launchSpeed * time; currentPos.z -= (gravity * m_gravityScale) * std::pow(time, 2.f) * 0.5f; return currentPos; diff --git a/source/projection/Camera.cpp b/source/projection/Camera.cpp index af9782be..dc0e0707 100644 --- a/source/projection/Camera.cpp +++ b/source/projection/Camera.cpp @@ -6,49 +6,4 @@ namespace omath::projection { - Camera::Camera(const Vector3 &position, const Vector3 &viewAngles, const ViewPort &viewPort, - const float fov, const float near, const float far, const float lensZoom) - { - m_origin = position; - m_viewAngles = viewAngles; - m_viewPort = viewPort; - m_fieldOfView = fov; - m_nearPlaneDistance = near; - m_farPlaneDistance = far; - m_lensZoom = lensZoom; - } - - Mat<4, 4> Camera::GetViewMatrix() const - { - const auto forward = Vector3::ForwardVector(m_viewAngles.x, m_viewAngles.y); - const auto right = Vector3::RightVector(m_viewAngles.x, m_viewAngles.y, m_viewAngles.z); - const auto up = Vector3::UpVector(m_viewAngles.x, m_viewAngles.y, m_viewAngles.z); - - return Mat<>::TranslationMat(-m_origin) * Mat<>::OrientationMat(forward, right, up); - } - - std::expected Camera::WorldToScreen(const Vector3& worldPosition) const - { - const auto posVecAsMatrix = Mat<>::MatColumnFromVector(worldPosition); - - - const auto projectionMatrix = Mat<>::ProjectionMat(m_fieldOfView, m_viewPort.AspectRatio(), - m_nearPlaneDistance, m_farPlaneDistance, m_lensZoom); - - Mat<1, 4> projected = posVecAsMatrix * (GetViewMatrix() * projectionMatrix); - - if (projected.At(0, 3) == 0.f) - return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); - - projected /= projected.At(0, 3); - - if (projected.At(0, 0) < -1.f || projected.At(0, 0) > 1.f || - projected.At(0, 1) < -1.f || projected.At(0, 1) > 1.f || - projected.At(0, 2) < -1.f || projected.At(0, 2) > 1.f) - return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); - - projected *= Mat<4, 4>::ToScreenMat(m_viewPort.m_width, m_viewPort.m_height); - - return Vector3{projected.At(0, 0), projected.At(0, 1), projected.At(0, 2)}; - } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dbde66ff..77c9508b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable(unit-tests general/UnitTestLineTrace.cpp general/UnitTestAngles.cpp general/UnitTestViewAngles.cpp + general/UnitTestAngle.cpp engines/UnitTestOpenGL.cpp engines/UnitTestUnityEngine.cpp @@ -24,6 +25,6 @@ add_executable(unit-tests ) -target_link_libraries(unit-tests PRIVATE gtest gtest_main omath glm) +target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) gtest_discover_tests(unit-tests) \ No newline at end of file diff --git a/tests/engines/UnitTestOpenGL.cpp b/tests/engines/UnitTestOpenGL.cpp index 2cb68898..cbe252b9 100644 --- a/tests/engines/UnitTestOpenGL.cpp +++ b/tests/engines/UnitTestOpenGL.cpp @@ -7,17 +7,17 @@ #include #include #include -#include +// #include -#include "glm/ext/matrix_clip_space.hpp" -#include "glm/ext/matrix_transform.hpp" +// #include "glm/ext/matrix_clip_space.hpp" +// #include "glm/ext/matrix_transform.hpp" TEST(UnitTestOpenGL, Projection) { - const auto proj_glm = glm::perspective(glm::radians(90.f), 16.f / 9.f, 0.1f, 1000.f); - const auto proj_glm2 = glm::perspectiveLH_NO(glm::radians(90.f), 16.f / 9.f, 0.1f, 1000.f); + /*const auto proj_glm = glm::perspective(glm::radians(90.f), 16.f / 9.f, 0.1f, 1000.f); + // const auto proj_glm2 = glm::perspectiveLH_NO(glm::radians(90.f), 16.f / 9.f, 0.1f, 1000.f); // const auto proj_omath = omath::Mat<4, 4, float, omath::MatStoreType::COLUMN_MAJOR>((const float*)&proj_glm); // EXPECT_EQ(omath::opengl::PerspectiveProjectionMatrix(90, 16.f / 9.f, 0.1f, 1000.f), proj_omath); @@ -34,7 +34,7 @@ TEST(UnitTestOpenGL, Projection) //auto ndc_omath = proj_omath * cords_omath; // ndc_omath /= ndc_omath.At(3, 0); - + */ } TEST(UnitTestOpenGL, Projection2) { diff --git a/tests/engines/UnitTestSourceEngine.cpp b/tests/engines/UnitTestSourceEngine.cpp index 4f6b72b7..9c1861fe 100644 --- a/tests/engines/UnitTestSourceEngine.cpp +++ b/tests/engines/UnitTestSourceEngine.cpp @@ -1,3 +1,29 @@ // -// Created by Orange on 11/27/2024. +// Created by Orange on 11/23/2024. // +#include +#include + + + + + +TEST(UnitTestSourceEngine, ForwardVector) +{ + const auto forward = omath::source::ForwardVector({}); + + EXPECT_EQ(forward, omath::source::kAbsForward); +} + +TEST(UnitTestSourceEngine, RightVector) +{ + const auto right = omath::source::RightVector({}); + + EXPECT_EQ(right, omath::source::kAbsRight); +} + +TEST(UnitTestSourceEngine, UpVector) +{ + const auto up = omath::source::UpVector({}); + EXPECT_EQ(up, omath::source::kAbsUp); +} \ No newline at end of file diff --git a/tests/general/UnitTestAngle.cpp b/tests/general/UnitTestAngle.cpp new file mode 100644 index 00000000..66bc0d12 --- /dev/null +++ b/tests/general/UnitTestAngle.cpp @@ -0,0 +1,3 @@ +// +// Created by Orange on 11/30/2024. +// diff --git a/tests/general/UnitTestProjection.cpp b/tests/general/UnitTestProjection.cpp index 34246333..fc0ced4f 100644 --- a/tests/general/UnitTestProjection.cpp +++ b/tests/general/UnitTestProjection.cpp @@ -4,16 +4,15 @@ #include #include #include -#include +#include #include +#include TEST(UnitTestProjection, Projection) { - const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.375f, 5000.f, 1.335f); - - const auto projected = camera.WorldToScreen({5000, 0, 0}); - + auto x = omath::Angle::FromDegrees(90.f); + auto cam = omath::source::CreateCamera({-10, 0, 0}, omath::source::ViewAngles{}, {1920.f, 1080.f}, x, 0.1f, 1000.f); - EXPECT_TRUE(projected.has_value()); - EXPECT_EQ(projected->z, 1.f); + const auto projected = cam.WorldToScreen({10, 0, 0}); + std::print("{} {} {}", projected->x, projected->y, projected->z); } \ No newline at end of file From 339dbade7e1dfffdaeb7d97f171a78f7df975ff6 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 1 Dec 2024 04:10:25 +0300 Subject: [PATCH 128/795] patch --- include/omath/engines/source.hpp | 8 ++++---- include/omath/projection/Camera.hpp | 11 ++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/include/omath/engines/source.hpp b/include/omath/engines/source.hpp index 5bb085e7..a5e7ac93 100644 --- a/include/omath/engines/source.hpp +++ b/include/omath/engines/source.hpp @@ -29,10 +29,10 @@ namespace omath::source template requires std::is_floating_point_v || std::is_integral_v - [[nodiscard]] constexpr Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> ViewMatrixFromVecs(const Vector3& forward, const Vector3& right, + [[nodiscard]] constexpr Mat<4, 4, Type, MatStoreType::ROW_MAJOR> ViewMatrixFromVecs(const Vector3& forward, const Vector3& right, const Vector3& up, const Vector3& camera_pos) { - return MatTranslation(-camera_pos) * Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR>{ + return MatTranslation(-camera_pos) * Mat<4, 4, Type, MatStoreType::ROW_MAJOR>{ {right.x, up.x, forward.x, 0}, {right.y, up.y, forward.y, 0}, {right.z, up.z, forward.z, 0}, @@ -43,14 +43,14 @@ namespace omath::source template requires std::is_floating_point_v || std::is_integral_v - [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> ViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + [[nodiscard]] Mat<4, 4, Type, MatStoreType::ROW_MAJOR> ViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { return ViewMatrixFromVecs(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); } template requires std::is_floating_point_v || std::is_integral_v - [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> + [[nodiscard]] Mat<4, 4, Type, MatStoreType::ROW_MAJOR> PerspectiveProjectionMatrix(const Type& fieldOfView, const Type& aspectRatio, const Type& near, const Type& far) { const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index e190664c..0a19dca0 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -52,13 +52,18 @@ namespace omath::projection return CreateProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, m_farPlaneDistance); } + [[nodiscard]] auto GetViewProjectionMatrix() const + { + return GetProjectionMatrix() * GetViewMatrix().Transposed(); + } [[nodiscard]] std::expected WorldToScreen([[maybe_unused]] const Vector3& worldPosition) const { using mat = std::invoke_result_t; - const auto vecAsMatrix = MatColumnFromVector(worldPosition); - - const auto projected = GetViewMatrix().Transposed() * vecAsMatrix; + Mat<4, 1> projected = GetViewProjectionMatrix() * MatColumnFromVector(worldPosition); + if (projected.At(3, 0) == 0.0f) + return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); + projected /= projected.At(3, 0); return Vector3{projected.At(0,0), projected.At(1,0), projected.At(2,0)}; } From 5473515ffd2f932928384015d05fe45ac7c82058 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 1 Dec 2024 14:06:52 +0300 Subject: [PATCH 129/795] refactored some stuff --- include/omath/Mat.hpp | 12 ++++++------ include/omath/engines/source.hpp | 10 +++++----- include/omath/projection/Camera.hpp | 4 ++-- tests/general/UnitTestMat.cpp | 29 ----------------------------- 4 files changed, 13 insertions(+), 42 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 2f25bd50..2840212c 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -321,10 +321,10 @@ namespace omath constexpr static Mat<4, 4> TranslationMat(const Vector3& diff) noexcept { return { - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {diff.x, diff.y, diff.z, 1}, + {1, 0, 0, diff.x}, + {0, 1, 0, diff.y}, + {0, 0, 1, diff.z}, + {0, 0, 0, 1}, }; } @@ -376,11 +376,11 @@ namespace omath [[nodiscard]] constexpr Mat<4, 4, T, St> MatTranslation(const Vector3& diff) noexcept { - return { + return Mat<4, 4, T, St>{ {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {diff.x, diff.y, diff.z, 1}, - }; + }.Transposed(); } } // namespace omath diff --git a/include/omath/engines/source.hpp b/include/omath/engines/source.hpp index a5e7ac93..669a5315 100644 --- a/include/omath/engines/source.hpp +++ b/include/omath/engines/source.hpp @@ -32,12 +32,12 @@ namespace omath::source [[nodiscard]] constexpr Mat<4, 4, Type, MatStoreType::ROW_MAJOR> ViewMatrixFromVecs(const Vector3& forward, const Vector3& right, const Vector3& up, const Vector3& camera_pos) { - return MatTranslation(-camera_pos) * Mat<4, 4, Type, MatStoreType::ROW_MAJOR>{ - {right.x, up.x, forward.x, 0}, - {right.y, up.y, forward.y, 0}, - {right.z, up.z, forward.z, 0}, + return Mat<4, 4, Type, MatStoreType::ROW_MAJOR>{ + {right.x, right.y, right.z, 0}, + {up.x, up.y, up.z, 0}, + {forward.x, forward.y, forward.z, 0}, {0, 0, 0, 1}, - }; + } * MatTranslation(-camera_pos) ; } diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index 0a19dca0..3932677d 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -54,7 +54,7 @@ namespace omath::projection [[nodiscard]] auto GetViewProjectionMatrix() const { - return GetProjectionMatrix() * GetViewMatrix().Transposed(); + return GetProjectionMatrix() * GetViewMatrix(); } [[nodiscard]] std::expected WorldToScreen([[maybe_unused]] const Vector3& worldPosition) const { @@ -65,7 +65,7 @@ namespace omath::projection return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); projected /= projected.At(3, 0); - return Vector3{projected.At(0,0), projected.At(1,0), projected.At(2,0)}; + return Vector3{++projected.At(0,0) / 2 * m_viewPort.m_width , ++projected.At(1,0) / 2 * m_viewPort.m_height, projected.At(2,0)}; } ViewPort m_viewPort{}; diff --git a/tests/general/UnitTestMat.cpp b/tests/general/UnitTestMat.cpp index 94d451ad..0105c283 100644 --- a/tests/general/UnitTestMat.cpp +++ b/tests/general/UnitTestMat.cpp @@ -154,35 +154,6 @@ TEST_F(UnitTestMat, StaticMethod_ToScreenMat) EXPECT_FLOAT_EQ(screenMat.At(3, 3), 1.0f); } -// Test static method: TranslationMat -TEST_F(UnitTestMat, StaticMethod_TranslationMat) -{ - Vector3 diff{10.0f, 20.0f, 30.0f}; - Mat<4, 4> transMat = Mat<4, 4>::TranslationMat(diff); - EXPECT_FLOAT_EQ(transMat.At(0, 0), 1.0f); - EXPECT_FLOAT_EQ(transMat.At(3, 0), diff.x); - EXPECT_FLOAT_EQ(transMat.At(3, 1), diff.y); - EXPECT_FLOAT_EQ(transMat.At(3, 2), diff.z); - EXPECT_FLOAT_EQ(transMat.At(3, 3), 1.0f); -} - -// Test static method: OrientationMat -TEST_F(UnitTestMat, StaticMethod_OrientationMat) -{ - constexpr Vector3 forward{0.0f, 0.0f, 1.0f}; - constexpr Vector3 right{1.0f, 0.0f, 0.0f}; - constexpr Vector3 up{0.0f, 1.0f, 0.0f}; - constexpr Mat<4, 4> orientMat = Mat<4, 4>::OrientationMat(forward, right, up); - EXPECT_FLOAT_EQ(orientMat.At(0, 0), right.x); - EXPECT_FLOAT_EQ(orientMat.At(0, 1), up.x); - EXPECT_FLOAT_EQ(orientMat.At(0, 2), forward.x); - EXPECT_FLOAT_EQ(orientMat.At(1, 0), right.y); - EXPECT_FLOAT_EQ(orientMat.At(1, 1), up.y); - EXPECT_FLOAT_EQ(orientMat.At(1, 2), forward.y); - EXPECT_FLOAT_EQ(orientMat.At(2, 0), right.z); - EXPECT_FLOAT_EQ(orientMat.At(2, 1), up.z); - EXPECT_FLOAT_EQ(orientMat.At(2, 2), forward.z); -} // Test exception handling in At() method TEST_F(UnitTestMat, Method_At_OutOfRange) From 04a5535ade2af9920040da6cf97c70c8ef258a5c Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 1 Dec 2024 22:02:16 +0300 Subject: [PATCH 130/795] added multiply factor for source engine matrix --- include/omath/engines/source.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/omath/engines/source.hpp b/include/omath/engines/source.hpp index 669a5315..0aa6e7b3 100644 --- a/include/omath/engines/source.hpp +++ b/include/omath/engines/source.hpp @@ -49,15 +49,17 @@ namespace omath::source } template - requires std::is_floating_point_v || std::is_integral_v + requires std::is_arithmetic_v && std::is_signed_v [[nodiscard]] Mat<4, 4, Type, MatStoreType::ROW_MAJOR> PerspectiveProjectionMatrix(const Type& fieldOfView, const Type& aspectRatio, const Type& near, const Type& far) { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); + constexpr auto kMultiplyFactor = Type(0.75); + + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2) * kMultiplyFactor; return { - {static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, static_cast(1) / (fovHalfTan), 0, 0}, + {-static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, -static_cast(1) / (fovHalfTan), 0, 0}, {0, 0, (far + near) / (far - near), -(static_cast(2) * far * near) / (far - near)}, {0, 0, 1, 0}, }; From 9c0285e353e8f242d64dae4369a4e447ee259fac Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 2 Dec 2024 10:57:47 +0300 Subject: [PATCH 131/795] added check, removed deprecated code --- include/omath/Mat.hpp | 53 +++++------------------------ include/omath/engines/opengl.hpp | 2 +- include/omath/engines/source.hpp | 2 +- include/omath/projection/Camera.hpp | 19 +++++++++-- 4 files changed, 27 insertions(+), 49 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 2840212c..a3f18079 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -7,7 +7,6 @@ #include #include #include -#include "Angles.hpp" #include "Vector3.hpp" @@ -317,42 +316,6 @@ namespace omath }; } - [[nodiscard]] - constexpr static Mat<4, 4> TranslationMat(const Vector3& diff) noexcept - { - return { - {1, 0, 0, diff.x}, - {0, 1, 0, diff.y}, - {0, 0, 1, diff.z}, - {0, 0, 0, 1}, - }; - } - - [[nodiscard]] - constexpr static Mat<4, 4> OrientationMat(const Vector3& forward, const Vector3& right, - const Vector3& up) noexcept - { - return { - {right.x, up.x, forward.x, 0}, - {right.y, up.y, forward.y, 0}, - {right.z, up.z, forward.z, 0}, - {0, 0, 0, 1}, - }; - } - - [[nodiscard]] - constexpr static Mat<4, 4> ProjectionMat(const Type& fieldOfView, const Type& aspectRatio, const Type& near, - const Type& far, const Type& lensZoom) noexcept - { - const Type& fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); - const Type& frustumHeight = far - near; - - return {{-1 / (aspectRatio * fovHalfTan) * lensZoom, 0, 0, 0}, - {0, -1 / fovHalfTan * lensZoom, 0, 0}, - {0, 0, -far / frustumHeight, -1}, - {0, 0, near * far / frustumHeight, 0}}; - } - private: std::array m_data; }; @@ -368,19 +331,19 @@ namespace omath [[nodiscard]] constexpr static Mat<4, 1, T, St> MatColumnFromVector(const Vector3& vector) noexcept { - return { - {vector.x}, {vector.y}, {vector.z}, {1}}; + return {{vector.x}, {vector.y}, {vector.z}, {1}}; } template [[nodiscard]] constexpr Mat<4, 4, T, St> MatTranslation(const Vector3& diff) noexcept { - return Mat<4, 4, T, St>{ - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {diff.x, diff.y, diff.z, 1}, - }.Transposed(); + return + { + {1, 0, 0, diff.x}, + {0, 1, 0, diff.y}, + {0, 0, 1, diff.z}, + {0, 0, 0, 1}, + }; } } // namespace omath diff --git a/include/omath/engines/opengl.hpp b/include/omath/engines/opengl.hpp index bcda994e..78d7821a 100644 --- a/include/omath/engines/opengl.hpp +++ b/include/omath/engines/opengl.hpp @@ -4,7 +4,7 @@ #pragma once #include "omath/Vector3.hpp" #include "omath/Mat.hpp" - +#include "omath/Angle.hpp" namespace omath::opengl { diff --git a/include/omath/engines/source.hpp b/include/omath/engines/source.hpp index 0aa6e7b3..cabbaea8 100644 --- a/include/omath/engines/source.hpp +++ b/include/omath/engines/source.hpp @@ -55,7 +55,7 @@ namespace omath::source { constexpr auto kMultiplyFactor = Type(0.75); - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2) * kMultiplyFactor; + const Type fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2) * kMultiplyFactor; return { {-static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index 3932677d..ba87ba68 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -31,6 +31,7 @@ namespace omath::projection std::invoke_result_t> class Camera { + public: Camera(const Vector3& position, const ViewAnglesType& viewAngles, const ViewPort& viewPort, const Angle& fov, const float near, const float far, @@ -56,15 +57,20 @@ namespace omath::projection { return GetProjectionMatrix() * GetViewMatrix(); } - [[nodiscard]] std::expected WorldToScreen([[maybe_unused]] const Vector3& worldPosition) const + + [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const { using mat = std::invoke_result_t; - Mat<4, 1> projected = GetViewProjectionMatrix() * MatColumnFromVector(worldPosition); + auto projected = GetViewProjectionMatrix() * MatColumnFromVector(worldPosition); if (projected.At(3, 0) == 0.0f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); + projected /= projected.At(3, 0); + if (IsNdcOutOfBounds(projected)) + return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); + return Vector3{++projected.At(0,0) / 2 * m_viewPort.m_width , ++projected.At(1,0) / 2 * m_viewPort.m_height, projected.At(2,0)}; } @@ -75,10 +81,19 @@ namespace omath::projection float m_nearPlaneDistance; private: + ViewAnglesType m_viewAngles; Vector3 m_origin; std::function CreateViewMatrix; std::function CreateProjectionMatrix; + + + template + [[nodiscard]] + constexpr static bool IsNdcOutOfBounds(const Type& ndc) + { + return std::ranges::any_of( ndc.RawArray(), [](const auto& val) { return val < -1 || val > 1; }); + } }; } // namespace omath::projection From 0afa20b4e5d763213c03cc3a89f75a3c325cafa9 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 4 Dec 2024 04:58:29 +0300 Subject: [PATCH 132/795] refactored camera --- include/omath/Angle.hpp | 2 +- include/omath/Mat.hpp | 50 +++++++- include/omath/engines/Source/Camera.hpp | 22 ++++ include/omath/engines/Source/Constants.h | 24 ++++ include/omath/engines/Source/Formulas.hpp | 98 ++++++++++++++++ include/omath/engines/opengl.hpp | 44 ++++--- include/omath/engines/source.hpp | 135 ---------------------- include/omath/projection/Camera.hpp | 33 ++---- source/CMakeLists.txt | 3 +- source/engines/CMakeLists.txt | 1 + source/engines/Source/CMakeLists.txt | 1 + source/engines/Source/Camera.cpp | 31 +++++ source/prediction/Projectile.cpp | 4 +- tests/engines/UnitTestOpenGL.cpp | 1 - tests/engines/UnitTestSourceEngine.cpp | 3 +- tests/general/UnitTestProjection.cpp | 4 +- 16 files changed, 262 insertions(+), 194 deletions(-) create mode 100644 include/omath/engines/Source/Camera.hpp create mode 100644 include/omath/engines/Source/Constants.h create mode 100644 include/omath/engines/Source/Formulas.hpp delete mode 100644 include/omath/engines/source.hpp create mode 100644 source/engines/CMakeLists.txt create mode 100644 source/engines/Source/CMakeLists.txt create mode 100644 source/engines/Source/Camera.cpp diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index 9b5c64f2..7d50e939 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -16,7 +16,7 @@ namespace omath Clamped = 1, }; - template + template requires std::is_arithmetic_v class Angle { diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index a3f18079..3059dd3c 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -320,23 +320,23 @@ namespace omath std::array m_data; }; - template + template [[nodiscard]] - constexpr static Mat<1, 4, T, St> MatRowFromVector(const Vector3& vector) noexcept + constexpr static Mat<1, 4, Type, St> MatRowFromVector(const Vector3& vector) noexcept { return {{vector.x, vector.y, vector.z, 1}}; } - template + template [[nodiscard]] - constexpr static Mat<4, 1, T, St> MatColumnFromVector(const Vector3& vector) noexcept + constexpr static Mat<4, 1, Type, St> MatColumnFromVector(const Vector3& vector) noexcept { return {{vector.x}, {vector.y}, {vector.z}, {1}}; } - template + template [[nodiscard]] - constexpr Mat<4, 4, T, St> MatTranslation(const Vector3& diff) noexcept + constexpr Mat<4, 4, Type, St> MatTranslation(const Vector3& diff) noexcept { return { @@ -346,4 +346,42 @@ namespace omath {0, 0, 0, 1}, }; } + + template + [[nodiscard]] + constexpr Mat<3, 3, Type, St> RotationMatrixAxisX(const Angle& roll) noexcept + { + return + { + {1, 0, 0}, + {0, 0, roll.Cos(), -roll.Sin()}, + {0, 0, roll.Sin(), roll.Cos()}, + }; + } + + template + [[nodiscard]] + constexpr Mat<3, 3, Type, St> RotationMatrixAxisY(const Angle& pitch) noexcept + { + return + { + {pitch.Cos(), 0, pitch.Sin()}, + {0 , 1, 0}, + {-pitch.Sin(), 0, pitch.Cos()}, + }; + } + + template + [[nodiscard]] + constexpr Mat<3, 3, Type, St> RotationMatrixAxisZ(const Angle& Yaw) noexcept + { + return + { + {Yaw.Cos(), -Yaw.Sin(), 0}, + {Yaw.Sin(), Yaw.Cos(), 0}, + {0, 0, 1}, + }; + } + + } // namespace omath diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/Source/Camera.hpp new file mode 100644 index 00000000..7e73c98c --- /dev/null +++ b/include/omath/engines/Source/Camera.hpp @@ -0,0 +1,22 @@ +// +// Created by Orange on 12/4/2024. +// +#pragma once +#include "Constants.h" +#include "omath/projection/Camera.hpp" + +namespace omath::source +{ + class Camera final : public projection::Camera + { + public: + Camera(const Vector3& position, const ViewAngles& viewAngles, + const projection::ViewPort& viewPort, const Angle& fov, float near, const float far) : + projection::Camera(position, viewAngles, viewPort, fov, near, far) + { + } + void LookAt(const Vector3& target) override; + [[nodiscard]] Mat4x4 GetViewMatrix() const override; + [[nodiscard]] Mat4x4 GetProjectionMatrix() const override; + }; +} \ No newline at end of file diff --git a/include/omath/engines/Source/Constants.h b/include/omath/engines/Source/Constants.h new file mode 100644 index 00000000..f77b1950 --- /dev/null +++ b/include/omath/engines/Source/Constants.h @@ -0,0 +1,24 @@ +// +// Created by Orange on 12/4/2024. +// +#pragma once + +#include +#include +#include +#include +namespace omath::source +{ + constexpr Vector3 kAbsUp = {0, 0, 1}; + constexpr Vector3 kAbsRight = {0, -1, 0}; + constexpr Vector3 kAbsForward = {1, 0, 0}; + + using Mat4x4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; + using Mat3x3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; + + using PitchAngle = Angle; + using YawAngle = Angle; + using RollAngle = Angle; + + using ViewAngles = omath::ViewAngles; +} // namespace omath::source diff --git a/include/omath/engines/Source/Formulas.hpp b/include/omath/engines/Source/Formulas.hpp new file mode 100644 index 00000000..39e16c36 --- /dev/null +++ b/include/omath/engines/Source/Formulas.hpp @@ -0,0 +1,98 @@ +// +// Created by Orange on 12/4/2024. +// +#pragma once +#include "Constants.h" + + +namespace omath::source +{ + [[nodiscard]] + inline Vector3 ForwardVector(const ViewAngles& angles) + { + const auto cosPitch = angles.pitch.Cos(); + const auto sinPitch = angles.pitch.Sin(); + + const auto cosYaw = angles.yaw.Cos(); + const auto sinYaw = angles.yaw.Sin(); + + + return {cosPitch * cosYaw, cosPitch * sinYaw, -sinPitch}; + } + + [[nodiscard]] + inline Vector3 RightVector(const ViewAngles& angles) + { + const auto cosPitch = angles.pitch.Cos(); + const auto sinPitch = angles.pitch.Sin(); + + const auto cosYaw = angles.yaw.Cos(); + const auto sinYaw = angles.yaw.Sin(); + + const auto cosRoll = angles.roll.Cos(); + const auto sinRoll = angles.roll.Sin(); + + + return + { + -1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, + -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, + -1 * sinRoll * cosPitch + }; + } + + [[nodiscard]] + inline Vector3 UpVector(const ViewAngles& angles) + { + const auto cosPitch = angles.pitch.Cos(); + const auto sinPitch = angles.pitch.Sin(); + + const auto cosYaw = angles.yaw.Cos(); + const auto sinYaw = angles.yaw.Sin(); + + const auto cosRoll = angles.roll.Cos(); + const auto sinRoll = angles.roll.Sin(); + + + return + { + cosRoll * sinPitch * cosYaw + - sinRoll * -sinYaw, + cosRoll * sinPitch * sinYaw + - sinRoll * cosYaw, + cosRoll * cosPitch, + }; + } + + [[nodiscard]] + constexpr Mat4x4 ViewMatrixFromVecs(const Vector3& forward, const Vector3& right, const Vector3& up, + const Vector3& camera_pos) + { + return Mat4x4{ + {right.x, right.y, right.z, 0}, + {up.x, up.y, up.z, 0}, + {forward.x, forward.y, forward.z, 0}, + {0, 0, 0, 1}, + } * + MatTranslation(-camera_pos); + } + + [[nodiscard]] inline Mat4x4 ViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + { + return ViewMatrixFromVecs(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + } + + + [[nodiscard]] + inline Mat4x4 PerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) + { + constexpr auto kMultiplyFactor = 0.75f; + + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; + + return { + {-1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, -1.f / (fovHalfTan), 0, 0}, + {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, + {0, 0, 1, 0}, + }; + } +} // namespace omath::source diff --git a/include/omath/engines/opengl.hpp b/include/omath/engines/opengl.hpp index 78d7821a..68edf9db 100644 --- a/include/omath/engines/opengl.hpp +++ b/include/omath/engines/opengl.hpp @@ -2,9 +2,10 @@ // Created by Orange on 11/23/2024. // #pragma once -#include "omath/Vector3.hpp" -#include "omath/Mat.hpp" + #include "omath/Angle.hpp" +#include "omath/Mat.hpp" +#include "omath/Vector3.hpp" namespace omath::opengl { @@ -14,34 +15,31 @@ namespace omath::opengl template - requires std::is_floating_point_v || std::is_integral_v - [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> ViewMatrix(const Vector3& forward, - const Vector3& right, - const Vector3& up, - const Vector3& cam_origin) + requires std::is_arithmetic_v + [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> ViewMatrix(const Vector3& forward, const Vector3& right, + const Vector3& up, const Vector3& cam_origin) { - return + return Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> { - {right.x, up.x, -forward.x, 0}, - {right.y, up.y, -forward.y, 0}, - {right.z, up.z, -forward.z, 0}, - {-cam_origin.x, -cam_origin.y, -cam_origin.z, 1}, - }; + {right.x, right.y, right.z, 0}, + {up.x, up.y, up.z, 0}, + {-forward.x, -forward.y, -forward.z, 0}, + {0, 0, 0, 1}, + } * MatTranslation(-cam_origin); } template - requires std::is_floating_point_v || std::is_integral_v - [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> PerspectiveProjectionMatrix( - const float fieldOfView, const Type& aspectRatio, const Type& near, const Type& far) + requires std::is_arithmetic_v + [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> + PerspectiveProjectionMatrix(const float fieldOfView, const Type& aspectRatio, const Type& near, const Type& far) { const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); - return - { - {static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, static_cast(1) / (fovHalfTan), 0, 0}, - {0, 0, -(far + near) / (far - near), -(static_cast(2) * far * near) / (far - near)}, - {0, 0, -1, 0}, + return { + {static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, static_cast(1) / (fovHalfTan), 0, 0}, + {0, 0, -(far + near) / (far - near), -(static_cast(2) * far * near) / (far - near)}, + {0, 0, -1, 0}, }; } -} +} // namespace omath::opengl diff --git a/include/omath/engines/source.hpp b/include/omath/engines/source.hpp deleted file mode 100644 index cabbaea8..00000000 --- a/include/omath/engines/source.hpp +++ /dev/null @@ -1,135 +0,0 @@ -// -// Created by Orange on 11/24/2024. -// -#pragma once -#include "omath/Mat.hpp" -#include "omath/Vector3.hpp" - -#include -#include -#include - -namespace omath::source -{ - constexpr Vector3 kAbsUp = {0, 0, 1}; - constexpr Vector3 kAbsRight = {0, -1, 0}; - constexpr Vector3 kAbsForward = {1, 0, 0}; - - using PitchAngle = Angle; - using YawAngle = Angle; - using RollAngle = Angle; - - using ViewAngles = omath::ViewAngles; - - - inline Vector3 ForwardVector(const ViewAngles& angles); - inline Vector3 RightVector(const ViewAngles& angles); - inline Vector3 UpVector(const ViewAngles& angles); - - - template - requires std::is_floating_point_v || std::is_integral_v - [[nodiscard]] constexpr Mat<4, 4, Type, MatStoreType::ROW_MAJOR> ViewMatrixFromVecs(const Vector3& forward, const Vector3& right, - const Vector3& up, const Vector3& camera_pos) - { - return Mat<4, 4, Type, MatStoreType::ROW_MAJOR>{ - {right.x, right.y, right.z, 0}, - {up.x, up.y, up.z, 0}, - {forward.x, forward.y, forward.z, 0}, - {0, 0, 0, 1}, - } * MatTranslation(-camera_pos) ; - } - - - template - requires std::is_floating_point_v || std::is_integral_v - [[nodiscard]] Mat<4, 4, Type, MatStoreType::ROW_MAJOR> ViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) - { - return ViewMatrixFromVecs(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); - } - - template - requires std::is_arithmetic_v && std::is_signed_v - [[nodiscard]] Mat<4, 4, Type, MatStoreType::ROW_MAJOR> - PerspectiveProjectionMatrix(const Type& fieldOfView, const Type& aspectRatio, const Type& near, const Type& far) - { - constexpr auto kMultiplyFactor = Type(0.75); - - const Type fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2) * kMultiplyFactor; - - return { - {-static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, -static_cast(1) / (fovHalfTan), 0, 0}, - {0, 0, (far + near) / (far - near), -(static_cast(2) * far * near) / (far - near)}, - {0, 0, 1, 0}, - }; - } - // Copied from - // https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/mp/src/mathlib/mathlib_base.cpp#L919 - [[nodiscard]] - inline Vector3 ForwardVector(const ViewAngles& angles) - { - const auto cosPitch = angles.pitch.Cos(); - const auto sinPitch = angles.pitch.Sin(); - - const auto cosYaw = angles.yaw.Cos(); - const auto sinYaw = angles.yaw.Sin(); - - - return {cosPitch * cosYaw, cosPitch * sinYaw, -sinPitch}; - } - - [[nodiscard]] - inline Vector3 RightVector(const ViewAngles& angles) - { - const auto cosPitch = angles.pitch.Cos(); - const auto sinPitch = angles.pitch.Sin(); - - const auto cosYaw = angles.yaw.Cos(); - const auto sinYaw = angles.yaw.Sin(); - - const auto cosRoll = angles.roll.Cos(); - const auto sinRoll = angles.roll.Sin(); - - - return - { - -1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, - -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, - -1 * sinRoll * cosPitch - }; - } - - [[nodiscard]] - inline Vector3 UpVector(const ViewAngles& angles) - { - const auto cosPitch = angles.pitch.Cos(); - const auto sinPitch = angles.pitch.Sin(); - - const auto cosYaw = angles.yaw.Cos(); - const auto sinYaw = angles.yaw.Sin(); - - const auto cosRoll = angles.roll.Cos(); - const auto sinRoll = angles.roll.Sin(); - - - return - { - cosRoll * sinPitch * cosYaw + - sinRoll * -sinYaw, - cosRoll * sinPitch * sinYaw + - sinRoll * cosYaw, - cosRoll * cosPitch, - }; - } - using Camera = omath::projection::Camera), decltype(PerspectiveProjectionMatrix)>; - - - // Camera(const Vector3& position, const ViewAnglesType& viewAngles, const ViewPort& viewPort, - // const Angle& fov, const float near, const float far, - // const std::function& viewMatFunc, const std::function& projFunc) - - inline Camera CreateCamera(const Vector3& position, const auto& viewAngles, const projection::ViewPort& viewPort, - const Angle& fov, const float near, const float far) - { - return Camera(position, viewAngles, viewPort, fov, near, far, ViewMatrix, PerspectiveProjectionMatrix); - } -} // namespace omath::source diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index ba87ba68..c94da1f5 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -26,42 +26,33 @@ namespace omath::projection } }; - template - requires std::is_same_v, - std::invoke_result_t> + template class Camera { public: + virtual ~Camera() = default; Camera(const Vector3& position, const ViewAnglesType& viewAngles, const ViewPort& viewPort, - const Angle& fov, const float near, const float far, - const std::function& viewMatFunc, const std::function& projFunc) : + const Angle& fov, const float near, const float far) : m_viewPort(viewPort), m_fieldOfView(fov), m_farPlaneDistance(far), m_nearPlaneDistance(near), - m_viewAngles(viewAngles), m_origin(position), CreateViewMatrix(viewMatFunc), CreateProjectionMatrix(projFunc) + m_viewAngles(viewAngles), m_origin(position) { } - void LookAt(const Vector3& target); + virtual void LookAt(const Vector3& target) = 0; - [[nodiscard]] auto GetViewMatrix() const - { - return CreateViewMatrix(m_viewAngles, m_origin); - } + [[nodiscard]] virtual Mat4x4Type GetViewMatrix() const = 0; - [[nodiscard]] auto GetProjectionMatrix() const - { - return CreateProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, m_farPlaneDistance); - } + [[nodiscard]] virtual Mat4x4Type GetProjectionMatrix() const = 0; - [[nodiscard]] auto GetViewProjectionMatrix() const + [[nodiscard]] Mat4x4Type GetViewProjectionMatrix() const { return GetProjectionMatrix() * GetViewMatrix(); } [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const { - using mat = std::invoke_result_t; - auto projected = GetViewProjectionMatrix() * MatColumnFromVector(worldPosition); + auto projected = GetViewProjectionMatrix() * MatColumnFromVector(worldPosition); if (projected.At(3, 0) == 0.0f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); @@ -74,20 +65,18 @@ namespace omath::projection return Vector3{++projected.At(0,0) / 2 * m_viewPort.m_width , ++projected.At(1,0) / 2 * m_viewPort.m_height, projected.At(2,0)}; } + protected: ViewPort m_viewPort{}; Angle m_fieldOfView; float m_farPlaneDistance; float m_nearPlaneDistance; - private: ViewAnglesType m_viewAngles; Vector3 m_origin; - std::function CreateViewMatrix; - std::function CreateProjectionMatrix; - + private: template [[nodiscard]] diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index ab4e8249..0e0ea741 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -10,4 +10,5 @@ target_sources(omath PRIVATE add_subdirectory(prediction) add_subdirectory(pathfinding) add_subdirectory(projection) -add_subdirectory(collision) \ No newline at end of file +add_subdirectory(collision) +add_subdirectory(engines) \ No newline at end of file diff --git a/source/engines/CMakeLists.txt b/source/engines/CMakeLists.txt new file mode 100644 index 00000000..f01db76d --- /dev/null +++ b/source/engines/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Source) \ No newline at end of file diff --git a/source/engines/Source/CMakeLists.txt b/source/engines/Source/CMakeLists.txt new file mode 100644 index 00000000..0abf8686 --- /dev/null +++ b/source/engines/Source/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(omath PRIVATE Camera.cpp) \ No newline at end of file diff --git a/source/engines/Source/Camera.cpp b/source/engines/Source/Camera.cpp new file mode 100644 index 00000000..285d9300 --- /dev/null +++ b/source/engines/Source/Camera.cpp @@ -0,0 +1,31 @@ +// +// Created by Orange on 12/4/2024. +// +#include "omath/engines/Source/Camera.hpp" +#include "omath/engines/Source/Formulas.hpp" + + +namespace omath::source +{ + + void Camera::LookAt(const Vector3& target) + { + const float distance = m_origin.DistTo(target); + const auto delta = target - m_origin; + + + m_viewAngles.pitch = PitchAngle::FromRadians(std::asin(delta.z / distance)); + m_viewAngles.yaw = YawAngle::FromRadians(std::atan2(delta.y, delta.x)); + m_viewAngles.roll = RollAngle::FromRadians(0.f); + } + + Mat4x4 Camera::GetViewMatrix() const + { + return ViewMatrix(m_viewAngles, m_origin); + } + + Mat4x4 Camera::GetProjectionMatrix() const + { + return PerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, m_farPlaneDistance); + } +} // namespace omath::source diff --git a/source/prediction/Projectile.cpp b/source/prediction/Projectile.cpp index 3ceb951f..3c58d293 100644 --- a/source/prediction/Projectile.cpp +++ b/source/prediction/Projectile.cpp @@ -4,13 +4,13 @@ #include "omath/prediction/Projectile.hpp" #include -#include +#include namespace omath::prediction { Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const { - auto currentPos = m_origin + omath::source::ForwardVector({source::PitchAngle::FromDegrees(pitch), source::YawAngle::FromDegrees(yaw), source::RollAngle::FromDegrees(0)}) * m_launchSpeed * time; + auto currentPos = m_origin + omath::source::ForwardVector({source::PitchAngle::FromDegrees(-pitch), source::YawAngle::FromDegrees(yaw), source::RollAngle::FromDegrees(0)}) * m_launchSpeed * time; currentPos.z -= (gravity * m_gravityScale) * std::pow(time, 2.f) * 0.5f; return currentPos; diff --git a/tests/engines/UnitTestOpenGL.cpp b/tests/engines/UnitTestOpenGL.cpp index cbe252b9..1ea94593 100644 --- a/tests/engines/UnitTestOpenGL.cpp +++ b/tests/engines/UnitTestOpenGL.cpp @@ -6,7 +6,6 @@ #include #include #include -#include // #include // #include "glm/ext/matrix_clip_space.hpp" diff --git a/tests/engines/UnitTestSourceEngine.cpp b/tests/engines/UnitTestSourceEngine.cpp index 9c1861fe..28a5805e 100644 --- a/tests/engines/UnitTestSourceEngine.cpp +++ b/tests/engines/UnitTestSourceEngine.cpp @@ -2,7 +2,8 @@ // Created by Orange on 11/23/2024. // #include -#include +#include +#include diff --git a/tests/general/UnitTestProjection.cpp b/tests/general/UnitTestProjection.cpp index fc0ced4f..70fce84a 100644 --- a/tests/general/UnitTestProjection.cpp +++ b/tests/general/UnitTestProjection.cpp @@ -4,14 +4,14 @@ #include #include #include -#include +#include #include #include TEST(UnitTestProjection, Projection) { auto x = omath::Angle::FromDegrees(90.f); - auto cam = omath::source::CreateCamera({-10, 0, 0}, omath::source::ViewAngles{}, {1920.f, 1080.f}, x, 0.1f, 1000.f); + auto cam = omath::source::Camera({-10, 0, 0}, omath::source::ViewAngles{}, {1920.f, 1080.f}, x, 0.1f, 1000.f); const auto projected = cam.WorldToScreen({10, 0, 0}); std::print("{} {} {}", projected->x, projected->y, projected->z); From db060e7f4d6dc54741fc4fe333f7d1f4f38fbd2f Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 4 Dec 2024 06:15:03 +0300 Subject: [PATCH 133/795] fixed bug --- include/omath/Angle.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index 7d50e939..b46da506 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -65,7 +65,7 @@ namespace omath [[nodiscard]] constexpr Type AsRadians() const { - return angles::RadiansToDegrees(m_angle); + return angles::DegreesToRadians(m_angle); } [[nodiscard]] From 95c5073d86cf4ca7a30ee3aa8dfb2230d20fdda0 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 4 Dec 2024 06:43:54 +0300 Subject: [PATCH 134/795] added rotation matrix --- include/omath/Mat.hpp | 19 ++++++---- include/omath/engines/Source/Formulas.hpp | 43 ++++------------------- 2 files changed, 18 insertions(+), 44 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 3059dd3c..7f6aae50 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -349,19 +349,19 @@ namespace omath template [[nodiscard]] - constexpr Mat<3, 3, Type, St> RotationMatrixAxisX(const Angle& roll) noexcept + Mat<3, 3, Type, St> RotationMatAxisX(const Angle& roll) noexcept { return { - {1, 0, 0}, - {0, 0, roll.Cos(), -roll.Sin()}, - {0, 0, roll.Sin(), roll.Cos()}, + {1, 0, 0}, + {0, roll.Cos(), -roll.Sin()}, + {0, roll.Sin(), roll.Cos()}, }; } template [[nodiscard]] - constexpr Mat<3, 3, Type, St> RotationMatrixAxisY(const Angle& pitch) noexcept + Mat<3, 3, Type, St> RotationMatAxisY(const Angle& pitch) noexcept { return { @@ -373,7 +373,7 @@ namespace omath template [[nodiscard]] - constexpr Mat<3, 3, Type, St> RotationMatrixAxisZ(const Angle& Yaw) noexcept + Mat<3, 3, Type, St> RotationMatAxisZ(const Angle& Yaw) noexcept { return { @@ -383,5 +383,10 @@ namespace omath }; } - + template + [[nodiscard]] + Mat<3, 3, Type, St> RotationMat(const ViewAngles& angles) noexcept + { + return RotationMatAxisY(angles.pitch) * RotationMatAxisZ(angles.yaw) * RotationMatAxisX(angles.roll); + } } // namespace omath diff --git a/include/omath/engines/Source/Formulas.hpp b/include/omath/engines/Source/Formulas.hpp index 39e16c36..0721d96d 100644 --- a/include/omath/engines/Source/Formulas.hpp +++ b/include/omath/engines/Source/Formulas.hpp @@ -10,56 +10,25 @@ namespace omath::source [[nodiscard]] inline Vector3 ForwardVector(const ViewAngles& angles) { - const auto cosPitch = angles.pitch.Cos(); - const auto sinPitch = angles.pitch.Sin(); + const auto vec = RotationMat(angles) * Mat<3, 1>({{kAbsForward.x}, {kAbsForward.y}, {kAbsForward.z}}); - const auto cosYaw = angles.yaw.Cos(); - const auto sinYaw = angles.yaw.Sin(); - - - return {cosPitch * cosYaw, cosPitch * sinYaw, -sinPitch}; + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } [[nodiscard]] inline Vector3 RightVector(const ViewAngles& angles) { - const auto cosPitch = angles.pitch.Cos(); - const auto sinPitch = angles.pitch.Sin(); - - const auto cosYaw = angles.yaw.Cos(); - const auto sinYaw = angles.yaw.Sin(); - - const auto cosRoll = angles.roll.Cos(); - const auto sinRoll = angles.roll.Sin(); + const auto vec = RotationMat(angles) * Mat<3, 1>({{kAbsRight.x}, {kAbsRight.y}, {kAbsRight.z}}); - - return - { - -1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, - -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, - -1 * sinRoll * cosPitch - }; + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } [[nodiscard]] inline Vector3 UpVector(const ViewAngles& angles) { - const auto cosPitch = angles.pitch.Cos(); - const auto sinPitch = angles.pitch.Sin(); - - const auto cosYaw = angles.yaw.Cos(); - const auto sinYaw = angles.yaw.Sin(); - - const auto cosRoll = angles.roll.Cos(); - const auto sinRoll = angles.roll.Sin(); - + const auto vec = RotationMat(angles) * Mat<3, 1>({{kAbsUp.x}, {kAbsUp.y}, {kAbsUp.z}}); - return - { - cosRoll * sinPitch * cosYaw + - sinRoll * -sinYaw, - cosRoll * sinPitch * sinYaw + - sinRoll * cosYaw, - cosRoll * cosPitch, - }; + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } [[nodiscard]] From e0dd432e69c73d8b62e2221095086f5610368410 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 4 Dec 2024 12:09:23 +0300 Subject: [PATCH 135/795] switched to 4x4 --- include/omath/Mat.hpp | 31 +++++++++++++---------- include/omath/engines/Source/Formulas.hpp | 8 +++--- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 7f6aae50..84628dad 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -349,44 +349,47 @@ namespace omath template [[nodiscard]] - Mat<3, 3, Type, St> RotationMatAxisX(const Angle& roll) noexcept + Mat<4, 4, Type, St> RotationMatAxisX(const Angle& angle) noexcept { return { - {1, 0, 0}, - {0, roll.Cos(), -roll.Sin()}, - {0, roll.Sin(), roll.Cos()}, + {1, 0, 0, 0}, + {0, angle.Cos(), -angle.Sin(), 0}, + {0, angle.Sin(), angle.Cos(), 0}, + {0, 0, 0, 1} }; } template [[nodiscard]] - Mat<3, 3, Type, St> RotationMatAxisY(const Angle& pitch) noexcept + Mat<4, 4, Type, St> RotationMatAxisY(const Angle& angle) noexcept { return { - {pitch.Cos(), 0, pitch.Sin()}, - {0 , 1, 0}, - {-pitch.Sin(), 0, pitch.Cos()}, + {angle.Cos(), 0, angle.Sin(), 0}, + {0 , 1, 0, 0}, + {-angle.Sin(), 0, angle.Cos(), 0}, + {0 , 0, 0, 1} }; } template [[nodiscard]] - Mat<3, 3, Type, St> RotationMatAxisZ(const Angle& Yaw) noexcept + Mat<4, 4, Type, St> RotationMatAxisZ(const Angle& angle) noexcept { return { - {Yaw.Cos(), -Yaw.Sin(), 0}, - {Yaw.Sin(), Yaw.Cos(), 0}, - {0, 0, 1}, + {angle.Cos(), -angle.Sin(), 0, 0}, + {angle.Sin(), angle.Cos(), 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1}, }; } template [[nodiscard]] - Mat<3, 3, Type, St> RotationMat(const ViewAngles& angles) noexcept + Mat<4, 4, Type, St> RotationMat(const ViewAngles& angles) noexcept { - return RotationMatAxisY(angles.pitch) * RotationMatAxisZ(angles.yaw) * RotationMatAxisX(angles.roll); + return RotationMatAxisZ(angles.yaw) * RotationMatAxisY(angles.pitch) * RotationMatAxisX(angles.roll); } } // namespace omath diff --git a/include/omath/engines/Source/Formulas.hpp b/include/omath/engines/Source/Formulas.hpp index 0721d96d..28a37c63 100644 --- a/include/omath/engines/Source/Formulas.hpp +++ b/include/omath/engines/Source/Formulas.hpp @@ -10,7 +10,7 @@ namespace omath::source [[nodiscard]] inline Vector3 ForwardVector(const ViewAngles& angles) { - const auto vec = RotationMat(angles) * Mat<3, 1>({{kAbsForward.x}, {kAbsForward.y}, {kAbsForward.z}}); + const auto vec = RotationMat(angles) * MatColumnFromVector(kAbsForward); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } @@ -18,7 +18,7 @@ namespace omath::source [[nodiscard]] inline Vector3 RightVector(const ViewAngles& angles) { - const auto vec = RotationMat(angles) * Mat<3, 1>({{kAbsRight.x}, {kAbsRight.y}, {kAbsRight.z}}); + const auto vec = RotationMat(angles) * MatColumnFromVector(kAbsRight); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } @@ -26,7 +26,7 @@ namespace omath::source [[nodiscard]] inline Vector3 UpVector(const ViewAngles& angles) { - const auto vec = RotationMat(angles) * Mat<3, 1>({{kAbsUp.x}, {kAbsUp.y}, {kAbsUp.z}}); + const auto vec = RotationMat(angles) * MatColumnFromVector(kAbsUp); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } @@ -58,7 +58,7 @@ namespace omath::source const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; return { - {-1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, + {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, {0, -1.f / (fovHalfTan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, 1, 0}, From 0587ee440d49361ce92f9c83c8df01f7a80334ae Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 4 Dec 2024 12:27:17 +0300 Subject: [PATCH 136/795] fixed view matrix --- include/omath/engines/Source/Formulas.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/omath/engines/Source/Formulas.hpp b/include/omath/engines/Source/Formulas.hpp index 28a37c63..8a9c9e8d 100644 --- a/include/omath/engines/Source/Formulas.hpp +++ b/include/omath/engines/Source/Formulas.hpp @@ -37,7 +37,7 @@ namespace omath::source { return Mat4x4{ {right.x, right.y, right.z, 0}, - {up.x, up.y, up.z, 0}, + {-up.x, -up.y, -up.z, 0}, {forward.x, forward.y, forward.z, 0}, {0, 0, 0, 1}, } * @@ -59,7 +59,7 @@ namespace omath::source return { {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, -1.f / (fovHalfTan), 0, 0}, + {0, 1.f / (fovHalfTan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, 1, 0}, }; From 4b50ac8c1d60593b3457030da287264349ef7980 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 4 Dec 2024 16:04:41 +0300 Subject: [PATCH 137/795] optimized view proj matrix generation --- include/omath/projection/Camera.hpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index c94da1f5..a6723a18 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -37,6 +37,7 @@ namespace omath::projection m_viewPort(viewPort), m_fieldOfView(fov), m_farPlaneDistance(far), m_nearPlaneDistance(near), m_viewAngles(viewAngles), m_origin(position) { + } virtual void LookAt(const Vector3& target) = 0; @@ -45,12 +46,15 @@ namespace omath::projection [[nodiscard]] virtual Mat4x4Type GetProjectionMatrix() const = 0; - [[nodiscard]] Mat4x4Type GetViewProjectionMatrix() const + [[nodiscard]] Mat4x4Type GetViewProjectionMatrix() { - return GetProjectionMatrix() * GetViewMatrix(); + if (!m_viewProjectionMatrix) + m_viewProjectionMatrix = GetProjectionMatrix() * GetViewMatrix(); + + return m_viewProjectionMatrix.value(); } - [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const + [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) { auto projected = GetViewProjectionMatrix() * MatColumnFromVector(worldPosition); @@ -77,7 +81,7 @@ namespace omath::projection Vector3 m_origin; private: - + std::optional m_viewProjectionMatrix = std::nullopt; template [[nodiscard]] constexpr static bool IsNdcOutOfBounds(const Type& ndc) From ecdd9ecdd6f584b816caa20e0c3072740066c42d Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 8 Dec 2024 05:19:49 +0300 Subject: [PATCH 138/795] improved some code --- include/omath/Angle.hpp | 6 +++ include/omath/Mat.hpp | 31 +++++++++++---- include/omath/engines/OpenGL/Constants.hpp | 14 +++++++ include/omath/engines/OpenGL/Formulas.hpp | 4 ++ .../engines/{opengl.hpp => OpenGL/OpenGL.hpp} | 0 include/omath/engines/Source/Camera.hpp | 7 +--- include/omath/engines/Source/Constants.h | 2 +- include/omath/engines/Source/Formulas.hpp | 39 +++++++------------ include/omath/projection/Camera.hpp | 15 +++---- source/engines/Source/Camera.cpp | 11 ++++-- tests/engines/UnitTestOpenGL.cpp | 12 +----- tests/engines/UnitTestSourceEngine.cpp | 28 +++++++++++-- tests/general/UnitTestProjection.cpp | 4 +- 13 files changed, 106 insertions(+), 67 deletions(-) create mode 100644 include/omath/engines/OpenGL/Constants.hpp create mode 100644 include/omath/engines/OpenGL/Formulas.hpp rename include/omath/engines/{opengl.hpp => OpenGL/OpenGL.hpp} (100%) diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index b46da506..26c6ebfb 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -144,5 +144,11 @@ namespace omath { return operator+(-other); } + + [[nodiscard]] + constexpr Angle operator-() const + { + return {-m_angle}; + } }; } diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 84628dad..66ade7ec 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -349,7 +349,7 @@ namespace omath template [[nodiscard]] - Mat<4, 4, Type, St> RotationMatAxisX(const Angle& angle) noexcept + Mat<4, 4, Type, St> MatRotationAxisX(const Angle& angle) noexcept { return { @@ -362,7 +362,7 @@ namespace omath template [[nodiscard]] - Mat<4, 4, Type, St> RotationMatAxisY(const Angle& angle) noexcept + Mat<4, 4, Type, St> MatRotationAxisY(const Angle& angle) noexcept { return { @@ -375,21 +375,36 @@ namespace omath template [[nodiscard]] - Mat<4, 4, Type, St> RotationMatAxisZ(const Angle& angle) noexcept + Mat<4, 4, Type, St> MatRotationAxisZ(const Angle& angle) noexcept { return { {angle.Cos(), -angle.Sin(), 0, 0}, - {angle.Sin(), angle.Cos(), 0, 0}, - {0, 0, 1, 0}, - {0, 0, 0, 1}, + {angle.Sin(), angle.Cos(), 0, 0}, + { 0, 0, 1, 0}, + { 0, 0, 0, 1}, }; } + template + [[nodiscard]] + static Mat<4, 4, Type, St> MatCameraView(const Vector3& forward, const Vector3& right, const Vector3& up, + const Vector3& cameraOrigin) noexcept + { + return Mat<4, 4, Type, St> + { + {right.x, right.y, right.z, 0}, + {up.x, up.y, up.z, 0}, + {forward.x, forward.y, forward.z, 0}, + {0, 0, 0, 1}, + + } * MatTranslation(-cameraOrigin); + } + template [[nodiscard]] - Mat<4, 4, Type, St> RotationMat(const ViewAngles& angles) noexcept + Mat<4, 4, Type, St> MatRotation(const ViewAngles& angles) noexcept { - return RotationMatAxisZ(angles.yaw) * RotationMatAxisY(angles.pitch) * RotationMatAxisX(angles.roll); + return MatRotationAxisZ(angles.yaw) * MatRotationAxisY(angles.pitch) * MatRotationAxisX(angles.roll); } } // namespace omath diff --git a/include/omath/engines/OpenGL/Constants.hpp b/include/omath/engines/OpenGL/Constants.hpp new file mode 100644 index 00000000..aec3520f --- /dev/null +++ b/include/omath/engines/OpenGL/Constants.hpp @@ -0,0 +1,14 @@ +// +// Created by Orange on 12/4/2024. +// +#pragma once + +#include + + +namespace omath::opengl +{ + constexpr Vector3 kAbsUp = {0, 1, 0}; + constexpr Vector3 kAbsRight = {1, 0, 0}; + constexpr Vector3 kAbsForward = {0, 0, -1}; +} \ No newline at end of file diff --git a/include/omath/engines/OpenGL/Formulas.hpp b/include/omath/engines/OpenGL/Formulas.hpp new file mode 100644 index 00000000..2a507567 --- /dev/null +++ b/include/omath/engines/OpenGL/Formulas.hpp @@ -0,0 +1,4 @@ +// +// Created by Orange on 12/4/2024. +// +#pragma once \ No newline at end of file diff --git a/include/omath/engines/opengl.hpp b/include/omath/engines/OpenGL/OpenGL.hpp similarity index 100% rename from include/omath/engines/opengl.hpp rename to include/omath/engines/OpenGL/OpenGL.hpp diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/Source/Camera.hpp index 7e73c98c..aca13cf8 100644 --- a/include/omath/engines/Source/Camera.hpp +++ b/include/omath/engines/Source/Camera.hpp @@ -10,11 +10,8 @@ namespace omath::source class Camera final : public projection::Camera { public: - Camera(const Vector3& position, const ViewAngles& viewAngles, - const projection::ViewPort& viewPort, const Angle& fov, float near, const float far) : - projection::Camera(position, viewAngles, viewPort, fov, near, far) - { - } + Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + const Angle& fov, float near, float far); void LookAt(const Vector3& target) override; [[nodiscard]] Mat4x4 GetViewMatrix() const override; [[nodiscard]] Mat4x4 GetProjectionMatrix() const override; diff --git a/include/omath/engines/Source/Constants.h b/include/omath/engines/Source/Constants.h index f77b1950..ecf1b400 100644 --- a/include/omath/engines/Source/Constants.h +++ b/include/omath/engines/Source/Constants.h @@ -15,7 +15,7 @@ namespace omath::source 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; diff --git a/include/omath/engines/Source/Formulas.hpp b/include/omath/engines/Source/Formulas.hpp index 8a9c9e8d..652f2636 100644 --- a/include/omath/engines/Source/Formulas.hpp +++ b/include/omath/engines/Source/Formulas.hpp @@ -10,7 +10,7 @@ namespace omath::source [[nodiscard]] inline Vector3 ForwardVector(const ViewAngles& angles) { - const auto vec = RotationMat(angles) * MatColumnFromVector(kAbsForward); + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } @@ -18,7 +18,7 @@ namespace omath::source [[nodiscard]] inline Vector3 RightVector(const ViewAngles& angles) { - const auto vec = RotationMat(angles) * MatColumnFromVector(kAbsRight); + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } @@ -26,42 +26,33 @@ namespace omath::source [[nodiscard]] inline Vector3 UpVector(const ViewAngles& angles) { - const auto vec = RotationMat(angles) * MatColumnFromVector(kAbsUp); + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } - [[nodiscard]] - constexpr Mat4x4 ViewMatrixFromVecs(const Vector3& forward, const Vector3& right, const Vector3& up, - const Vector3& camera_pos) + [[nodiscard]] inline Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { - return Mat4x4{ - {right.x, right.y, right.z, 0}, - {-up.x, -up.y, -up.z, 0}, - {forward.x, forward.y, forward.z, 0}, - {0, 0, 0, 1}, - } * - MatTranslation(-camera_pos); - } - - [[nodiscard]] inline Mat4x4 ViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) - { - return ViewMatrixFromVecs(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); } [[nodiscard]] - inline Mat4x4 PerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) + inline Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, + const float far) { + // NOTE: Needed tp make thing draw normal, since source is wierd + // and use tricky projection matrix formula. constexpr auto kMultiplyFactor = 0.75f; const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; return { - {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, 1.f / (fovHalfTan), 0, 0}, - {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, - {0, 0, 1, 0}, - }; + {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, 1.f / (fovHalfTan), 0, 0}, + {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, + {0, 0, 1, 0}, + + }; } } // namespace omath::source diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index a6723a18..15db451a 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -25,6 +25,7 @@ namespace omath::projection return m_width / m_height; } }; + using FieldOfView = const Angle; template class Camera @@ -33,7 +34,7 @@ namespace omath::projection public: virtual ~Camera() = default; Camera(const Vector3& position, const ViewAnglesType& viewAngles, const ViewPort& viewPort, - const Angle& fov, const float near, const float far) : + const FieldOfView& fov, const float near, const float far) : m_viewPort(viewPort), m_fieldOfView(fov), m_farPlaneDistance(far), m_nearPlaneDistance(near), m_viewAngles(viewAngles), m_origin(position) { @@ -48,15 +49,12 @@ namespace omath::projection [[nodiscard]] Mat4x4Type GetViewProjectionMatrix() { - if (!m_viewProjectionMatrix) - m_viewProjectionMatrix = GetProjectionMatrix() * GetViewMatrix(); - - return m_viewProjectionMatrix.value(); + return GetProjectionMatrix() * GetViewMatrix(); } - [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) + [[nodiscard]] std::expected WorldToScreen(const Mat4x4Type& viewProj, const Vector3& worldPosition) const { - auto projected = GetViewProjectionMatrix() * MatColumnFromVector(worldPosition); + auto projected = viewProj * MatColumnFromVector(worldPosition); if (projected.At(3, 0) == 0.0f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); @@ -66,7 +64,7 @@ namespace omath::projection if (IsNdcOutOfBounds(projected)) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); - return Vector3{++projected.At(0,0) / 2 * m_viewPort.m_width , ++projected.At(1,0) / 2 * m_viewPort.m_height, projected.At(2,0)}; + return Vector3{(projected.At(0,0)+1) / 2 * m_viewPort.m_width , (-projected.At(1,0)+1) / 2 * m_viewPort.m_height, projected.At(2,0)}; } protected: @@ -81,7 +79,6 @@ namespace omath::projection Vector3 m_origin; private: - std::optional m_viewProjectionMatrix = std::nullopt; template [[nodiscard]] constexpr static bool IsNdcOutOfBounds(const Type& ndc) diff --git a/source/engines/Source/Camera.cpp b/source/engines/Source/Camera.cpp index 285d9300..51eb5073 100644 --- a/source/engines/Source/Camera.cpp +++ b/source/engines/Source/Camera.cpp @@ -8,6 +8,11 @@ namespace omath::source { + Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + const projection::FieldOfView& fov, const float near, const float far) : + projection::Camera(position, viewAngles, viewPort, fov, near, far) + { + } void Camera::LookAt(const Vector3& target) { const float distance = m_origin.DistTo(target); @@ -15,17 +20,17 @@ namespace omath::source m_viewAngles.pitch = PitchAngle::FromRadians(std::asin(delta.z / distance)); - m_viewAngles.yaw = YawAngle::FromRadians(std::atan2(delta.y, delta.x)); + m_viewAngles.yaw = -YawAngle::FromRadians(std::atan2(delta.y, delta.x)); m_viewAngles.roll = RollAngle::FromRadians(0.f); } Mat4x4 Camera::GetViewMatrix() const { - return ViewMatrix(m_viewAngles, m_origin); + return CalcViewMatrix(m_viewAngles, m_origin); } Mat4x4 Camera::GetProjectionMatrix() const { - return PerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, m_farPlaneDistance); + return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, m_farPlaneDistance); } } // namespace omath::source diff --git a/tests/engines/UnitTestOpenGL.cpp b/tests/engines/UnitTestOpenGL.cpp index 1ea94593..46f5afc8 100644 --- a/tests/engines/UnitTestOpenGL.cpp +++ b/tests/engines/UnitTestOpenGL.cpp @@ -5,7 +5,7 @@ #include #include #include -#include + // #include // #include "glm/ext/matrix_clip_space.hpp" @@ -34,14 +34,4 @@ TEST(UnitTestOpenGL, Projection) //auto ndc_omath = proj_omath * cords_omath; // ndc_omath /= ndc_omath.At(3, 0); */ -} -TEST(UnitTestOpenGL, Projection2) -{ - const auto orient = omath::opengl::ViewMatrix(omath::opengl::kAbsForward, -omath::opengl::kAbsRight, omath::opengl::kAbsUp, {}); - - const omath::Mat<4, 1,float, omath::MatStoreType::COLUMN_MAJOR> cords_omath = - { - {0}, {0}, {-10}, {1} - }; - std::cout << (orient * cords_omath).ToString(); } \ No newline at end of file diff --git a/tests/engines/UnitTestSourceEngine.cpp b/tests/engines/UnitTestSourceEngine.cpp index 28a5805e..9ec3ae57 100644 --- a/tests/engines/UnitTestSourceEngine.cpp +++ b/tests/engines/UnitTestSourceEngine.cpp @@ -2,16 +2,14 @@ // Created by Orange on 11/23/2024. // #include +#include #include #include - - - TEST(UnitTestSourceEngine, ForwardVector) { - const auto forward = omath::source::ForwardVector({}); + const auto forward = omath::source::ForwardVector({{}, {}, {}}); EXPECT_EQ(forward, omath::source::kAbsForward); } @@ -27,4 +25,26 @@ TEST(UnitTestSourceEngine, UpVector) { const auto up = omath::source::UpVector({}); EXPECT_EQ(up, omath::source::kAbsUp); +} + +TEST(UnitTestSourceEngine, PerpectiveProjectionAtCenter) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + auto cam = omath::source::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + + + const auto viewProjMatrix = cam.GetViewProjectionMatrix(); + + for (float distance = 0.02f; distance < 1000.f; distance += 0.01f) + { + const auto projected = cam.WorldToScreen(viewProjMatrix, {distance, 0, 0}); + + EXPECT_TRUE(projected.has_value()); + + if (!projected.has_value()) + continue; + + EXPECT_NEAR(projected->x, 960, 0.00001f); + EXPECT_NEAR(projected->y, 540, 0.00001f); + } } \ No newline at end of file diff --git a/tests/general/UnitTestProjection.cpp b/tests/general/UnitTestProjection.cpp index 70fce84a..ab8b71cb 100644 --- a/tests/general/UnitTestProjection.cpp +++ b/tests/general/UnitTestProjection.cpp @@ -11,8 +11,8 @@ TEST(UnitTestProjection, Projection) { auto x = omath::Angle::FromDegrees(90.f); - auto cam = omath::source::Camera({-10, 0, 0}, omath::source::ViewAngles{}, {1920.f, 1080.f}, x, 0.1f, 1000.f); + auto cam = omath::source::Camera({0, 0, 0}, omath::source::ViewAngles{}, {1920.f, 1080.f}, x, 0.01f, 1000.f); - const auto projected = cam.WorldToScreen({10, 0, 0}); + const auto projected = cam.WorldToScreen(cam.GetViewProjectionMatrix(), {1000, 0, 50}); std::print("{} {} {}", projected->x, projected->y, projected->z); } \ No newline at end of file From 2488388aa4f482ee8ddb7bf6284fba31aedeff00 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 8 Dec 2024 05:39:24 +0300 Subject: [PATCH 139/795] moved file --- include/omath/Mat.hpp | 6 +++--- include/omath/engines/{unity.hpp => Unity/Unity.hpp} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename include/omath/engines/{unity.hpp => Unity/Unity.hpp} (100%) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 66ade7ec..3cb6f910 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -353,10 +353,10 @@ namespace omath { return { - {1, 0, 0, 0}, + {1, 0, 0, 0}, {0, angle.Cos(), -angle.Sin(), 0}, - {0, angle.Sin(), angle.Cos(), 0}, - {0, 0, 0, 1} + {0, angle.Sin(), angle.Cos(), 0}, + {0, 0, 0, 1} }; } diff --git a/include/omath/engines/unity.hpp b/include/omath/engines/Unity/Unity.hpp similarity index 100% rename from include/omath/engines/unity.hpp rename to include/omath/engines/Unity/Unity.hpp From ce9da764137ef6c3c44ea76b483a4678f407e366 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 16 Dec 2024 12:31:56 +0300 Subject: [PATCH 140/795] removed useless stuff --- .gitmodules | 5 +-- extlibs/glm | 1 - include/omath/engines/OpenGL/Constants.hpp | 14 ------- include/omath/engines/OpenGL/Formulas.hpp | 4 -- include/omath/engines/OpenGL/OpenGL.hpp | 45 ---------------------- include/omath/engines/Source/Formulas.hpp | 1 - include/omath/engines/Unity/Unity.hpp | 12 ------ include/omath/prediction/Target.hpp | 2 +- source/prediction/Engine.cpp | 2 +- source/prediction/Projectile.cpp | 9 +++-- tests/engines/UnitTestSourceEngine.cpp | 2 +- tests/general/UnitTestPrediction.cpp | 5 ++- 12 files changed, 13 insertions(+), 89 deletions(-) delete mode 160000 extlibs/glm delete mode 100644 include/omath/engines/OpenGL/Constants.hpp delete mode 100644 include/omath/engines/OpenGL/Formulas.hpp delete mode 100644 include/omath/engines/OpenGL/OpenGL.hpp delete mode 100644 include/omath/engines/Unity/Unity.hpp diff --git a/.gitmodules b/.gitmodules index b6c1697b..c158edf6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "extlibs/googletest"] path = extlibs/googletest - url = https://github.com/google/googletest.git -[submodule "extlibs/glm"] - path = extlibs/glm - url = https://github.com/g-truc/glm.git + url = https://github.com/google/googletest.git \ No newline at end of file diff --git a/extlibs/glm b/extlibs/glm deleted file mode 160000 index 33b4a621..00000000 --- a/extlibs/glm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 33b4a621a697a305bc3a7610d290677b96beb181 diff --git a/include/omath/engines/OpenGL/Constants.hpp b/include/omath/engines/OpenGL/Constants.hpp deleted file mode 100644 index aec3520f..00000000 --- a/include/omath/engines/OpenGL/Constants.hpp +++ /dev/null @@ -1,14 +0,0 @@ -// -// Created by Orange on 12/4/2024. -// -#pragma once - -#include - - -namespace omath::opengl -{ - constexpr Vector3 kAbsUp = {0, 1, 0}; - constexpr Vector3 kAbsRight = {1, 0, 0}; - constexpr Vector3 kAbsForward = {0, 0, -1}; -} \ No newline at end of file diff --git a/include/omath/engines/OpenGL/Formulas.hpp b/include/omath/engines/OpenGL/Formulas.hpp deleted file mode 100644 index 2a507567..00000000 --- a/include/omath/engines/OpenGL/Formulas.hpp +++ /dev/null @@ -1,4 +0,0 @@ -// -// Created by Orange on 12/4/2024. -// -#pragma once \ No newline at end of file diff --git a/include/omath/engines/OpenGL/OpenGL.hpp b/include/omath/engines/OpenGL/OpenGL.hpp deleted file mode 100644 index 68edf9db..00000000 --- a/include/omath/engines/OpenGL/OpenGL.hpp +++ /dev/null @@ -1,45 +0,0 @@ -// -// Created by Orange on 11/23/2024. -// -#pragma once - -#include "omath/Angle.hpp" -#include "omath/Mat.hpp" -#include "omath/Vector3.hpp" - -namespace omath::opengl -{ - constexpr Vector3 kAbsUp = {0, 1, 0}; - constexpr Vector3 kAbsRight = {1, 0, 0}; - constexpr Vector3 kAbsForward = {0, 0, -1}; - - - template - requires std::is_arithmetic_v - [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> ViewMatrix(const Vector3& forward, const Vector3& right, - const Vector3& up, const Vector3& cam_origin) - { - return Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> - { - {right.x, right.y, right.z, 0}, - {up.x, up.y, up.z, 0}, - {-forward.x, -forward.y, -forward.z, 0}, - {0, 0, 0, 1}, - } * MatTranslation(-cam_origin); - } - - template - requires std::is_arithmetic_v - [[nodiscard]] Mat<4, 4, Type, MatStoreType::COLUMN_MAJOR> - PerspectiveProjectionMatrix(const float fieldOfView, const Type& aspectRatio, const Type& near, const Type& far) - { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2); - - return { - {static_cast(1) / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, static_cast(1) / (fovHalfTan), 0, 0}, - {0, 0, -(far + near) / (far - near), -(static_cast(2) * far * near) / (far - near)}, - {0, 0, -1, 0}, - }; - } -} // namespace omath::opengl diff --git a/include/omath/engines/Source/Formulas.hpp b/include/omath/engines/Source/Formulas.hpp index 652f2636..3c8a6d70 100644 --- a/include/omath/engines/Source/Formulas.hpp +++ b/include/omath/engines/Source/Formulas.hpp @@ -4,7 +4,6 @@ #pragma once #include "Constants.h" - namespace omath::source { [[nodiscard]] diff --git a/include/omath/engines/Unity/Unity.hpp b/include/omath/engines/Unity/Unity.hpp deleted file mode 100644 index ff43bb2e..00000000 --- a/include/omath/engines/Unity/Unity.hpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Created by Orange on 11/27/2024. -// - -#pragma once -#include - - -namespace omath::unity -{ - -}; diff --git a/include/omath/prediction/Target.hpp b/include/omath/prediction/Target.hpp index e44b8680..a2b37ce3 100644 --- a/include/omath/prediction/Target.hpp +++ b/include/omath/prediction/Target.hpp @@ -13,7 +13,7 @@ namespace omath::prediction public: [[nodiscard]] - constexpr Vector3 PredictPosition(float time, float gravity) const + constexpr Vector3 PredictPosition(const float time, const float gravity) const { auto predicted = m_origin + m_velocity * time; diff --git a/source/prediction/Engine.cpp b/source/prediction/Engine.cpp index 2bc1b1c6..4452a525 100644 --- a/source/prediction/Engine.cpp +++ b/source/prediction/Engine.cpp @@ -58,7 +58,7 @@ namespace omath::prediction return std::nullopt; root = std::sqrt(root); - const float angle = std::atan((std::pow(projectile.m_launchSpeed, 2.f) - root) / (bulletGravity * distance2d)); + const float angle = std::atan((launchSpeedSqr - root) / (bulletGravity * distance2d)); return angles::RadiansToDegrees(angle); } diff --git a/source/prediction/Projectile.cpp b/source/prediction/Projectile.cpp index 3c58d293..281b3276 100644 --- a/source/prediction/Projectile.cpp +++ b/source/prediction/Projectile.cpp @@ -10,9 +10,12 @@ namespace omath::prediction { Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const { - auto currentPos = m_origin + omath::source::ForwardVector({source::PitchAngle::FromDegrees(-pitch), source::YawAngle::FromDegrees(yaw), source::RollAngle::FromDegrees(0)}) * m_launchSpeed * time; - currentPos.z -= (gravity * m_gravityScale) * std::pow(time, 2.f) * 0.5f; + auto currentPos = m_origin + source::ForwardVector({source::PitchAngle::FromDegrees(-pitch), + source::YawAngle::FromDegrees(yaw), + source::RollAngle::FromDegrees(0)}) * + m_launchSpeed * time; + currentPos.z -= (gravity * m_gravityScale) * (time * time) * 0.5f; return currentPos; } -} +} // namespace omath::prediction diff --git a/tests/engines/UnitTestSourceEngine.cpp b/tests/engines/UnitTestSourceEngine.cpp index 9ec3ae57..4d641a1f 100644 --- a/tests/engines/UnitTestSourceEngine.cpp +++ b/tests/engines/UnitTestSourceEngine.cpp @@ -9,7 +9,7 @@ TEST(UnitTestSourceEngine, ForwardVector) { - const auto forward = omath::source::ForwardVector({{}, {}, {}}); + const auto forward = omath::source::ForwardVector({}); EXPECT_EQ(forward, omath::source::kAbsForward); } diff --git a/tests/general/UnitTestPrediction.cpp b/tests/general/UnitTestPrediction.cpp index 64fe5780..5002d391 100644 --- a/tests/general/UnitTestPrediction.cpp +++ b/tests/general/UnitTestPrediction.cpp @@ -3,8 +3,9 @@ TEST(UnitTestPrediction, PredictionTest) { - const omath::prediction::Target target{.m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; - const omath::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 5000, .m_gravityScale= 0.4}; + constexpr omath::prediction::Target target{ + .m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; + constexpr omath::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 5000, .m_gravityScale= 0.4}; const auto viewPoint = omath::prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); const auto [pitch, yaw, _] = proj.m_origin.ViewAngleTo(viewPoint.value()).AsTuple(); From 075c553521725b87d3ef10f24e8e66cae2bbc99a Mon Sep 17 00:00:00 2001 From: Saikari Date: Tue, 17 Dec 2024 04:55:13 +0300 Subject: [PATCH 141/795] Export --- CMakeLists.txt | 12 +++++++--- include/omath/Angle.hpp | 4 ++-- include/omath/Angles.hpp | 1 - include/omath/Color.hpp | 6 ++--- include/omath/Mat.hpp | 6 ++--- include/omath/Matrix.hpp | 3 ++- include/omath/Triangle3d.hpp | 3 ++- include/omath/Vector2.hpp | 4 ++-- include/omath/Vector3.hpp | 4 ++-- include/omath/Vector4.hpp | 4 ++-- include/omath/ViewAngles.hpp | 4 +++- include/omath/collision/LineTracer.hpp | 6 ++--- include/omath/engines/Source/Camera.hpp | 3 ++- include/omath/omath_export.h | 24 ++++++++++++++++++++ include/omath/pathfinding/Astar.hpp | 4 ++-- include/omath/pathfinding/NavigationMesh.hpp | 4 ++-- include/omath/prediction/Engine.hpp | 4 ++-- include/omath/prediction/Projectile.hpp | 4 ++-- include/omath/prediction/Target.hpp | 4 ++-- include/omath/projection/Camera.hpp | 6 ++--- 20 files changed, 72 insertions(+), 38 deletions(-) create mode 100644 include/omath/omath_export.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f79bac97..23713a4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.26) project(omath VERSION 1.0.0) - +include(CMakePackageConfigHelpers) set(CMAKE_CXX_STANDARD 26) @@ -19,10 +19,16 @@ else() add_library(omath STATIC source/Vector3.cpp) endif() +target_compile_definitions(omath PUBLIC OMATH_EXPORT) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(omath PRIVATE /Zc:static_assert-) +endif() + add_subdirectory(source) -add_subdirectory(extlibs) if(OMATH_BUILD_TESTS) + add_subdirectory(extlibs) add_subdirectory(tests) endif() @@ -79,4 +85,4 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake" DESTINATION lib/cmake/omath -) \ No newline at end of file +) diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index 26c6ebfb..3622a1c5 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -6,7 +6,7 @@ #include "omath/Angles.hpp" #include - +#include "omath_export.h" namespace omath { @@ -18,7 +18,7 @@ namespace omath template requires std::is_arithmetic_v - class Angle + class OMATH_API Angle { Type m_angle; constexpr Angle(const Type& degrees) diff --git a/include/omath/Angles.hpp b/include/omath/Angles.hpp index 47a287a7..c578afb0 100644 --- a/include/omath/Angles.hpp +++ b/include/omath/Angles.hpp @@ -6,7 +6,6 @@ #include #include - namespace omath::angles { template diff --git a/include/omath/Color.hpp b/include/omath/Color.hpp index 46255455..cf4644bd 100644 --- a/include/omath/Color.hpp +++ b/include/omath/Color.hpp @@ -7,11 +7,11 @@ #include "omath/Vector3.hpp" #include #include "omath/Vector4.hpp" - +#include "omath_export.h" namespace omath { - struct HSV + struct OMATH_API HSV { float m_hue{}; float m_saturation{}; @@ -19,7 +19,7 @@ namespace omath }; - class Color final : public Vector4 + class OMATH_API Color final : public Vector4 { public: constexpr Color(float r, float g, float b, float a) : Vector4(r,g,b,a) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 3cb6f910..3adb480c 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -8,11 +8,11 @@ #include #include #include "Vector3.hpp" - +#include "omath_export.h" namespace omath { - struct MatSize + struct OMATH_API MatSize { size_t rows, columns; }; @@ -25,7 +25,7 @@ namespace omath template requires std::is_arithmetic_v - class Mat final + class OMATH_API Mat final { public: constexpr Mat() diff --git a/include/omath/Matrix.hpp b/include/omath/Matrix.hpp index 1fbdb4b9..fc1e296e 100644 --- a/include/omath/Matrix.hpp +++ b/include/omath/Matrix.hpp @@ -2,12 +2,13 @@ #include #include #include +#include "omath_export.h" namespace omath { class Vector3; - class Matrix final + class OMATH_API Matrix final { public: Matrix(); diff --git a/include/omath/Triangle3d.hpp b/include/omath/Triangle3d.hpp index 370aa957..826e4fce 100644 --- a/include/omath/Triangle3d.hpp +++ b/include/omath/Triangle3d.hpp @@ -3,10 +3,11 @@ // #pragma once #include "omath/Vector3.hpp" +#include "omath_export.h" namespace omath { - class Triangle3d final + class OMATH_API Triangle3d final { public: Triangle3d(const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3); diff --git a/include/omath/Vector2.hpp b/include/omath/Vector2.hpp index 46bc4676..a4cf0af5 100644 --- a/include/omath/Vector2.hpp +++ b/include/omath/Vector2.hpp @@ -5,11 +5,11 @@ #pragma once #include #include - +#include "omath_export.h" namespace omath { - class Vector2 + class OMATH_API Vector2 { public: float x = 0.f; diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index e22d8156..c0521b3a 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -7,11 +7,11 @@ #include #include #include "omath/Vector2.hpp" - +#include "omath_export.h" namespace omath { - class Vector3 : public Vector2 + class OMATH_API Vector3 : public Vector2 { public: float z = 0.f; diff --git a/include/omath/Vector4.hpp b/include/omath/Vector4.hpp index 4b43501b..8a3c84f2 100644 --- a/include/omath/Vector4.hpp +++ b/include/omath/Vector4.hpp @@ -5,11 +5,11 @@ #include #include - +#include "omath_export.h" namespace omath { - class Vector4 : public Vector3 + class OMATH_API Vector4 : public Vector3 { public: float w; diff --git a/include/omath/ViewAngles.hpp b/include/omath/ViewAngles.hpp index d744f6ba..e3ef7a80 100644 --- a/include/omath/ViewAngles.hpp +++ b/include/omath/ViewAngles.hpp @@ -3,10 +3,12 @@ // #pragma once +#include "omath_export.h" + namespace omath { template - struct ViewAngles + struct OMATH_API ViewAngles { PitchType pitch; YawType yaw; diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/LineTracer.hpp index fc1d7fa1..2c42e60f 100644 --- a/include/omath/collision/LineTracer.hpp +++ b/include/omath/collision/LineTracer.hpp @@ -5,11 +5,11 @@ #include "omath/Vector3.hpp" #include "omath/Triangle3d.hpp" - +#include "../omath_export.h" namespace omath::collision { - class Ray + class OMATH_API Ray { public: Vector3 start; @@ -21,7 +21,7 @@ namespace omath::collision [[nodiscard]] Vector3 DirectionVectorNormalized() const; }; - class LineTracer + class OMATH_API LineTracer { public: LineTracer() = delete; diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/Source/Camera.hpp index aca13cf8..4ebe113b 100644 --- a/include/omath/engines/Source/Camera.hpp +++ b/include/omath/engines/Source/Camera.hpp @@ -4,10 +4,11 @@ #pragma once #include "Constants.h" #include "omath/projection/Camera.hpp" +#include "../../omath_export.h" namespace omath::source { - class Camera final : public projection::Camera + class OMATH_API Camera final : public projection::Camera { public: Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, diff --git a/include/omath/omath_export.h b/include/omath/omath_export.h new file mode 100644 index 00000000..091fd56c --- /dev/null +++ b/include/omath/omath_export.h @@ -0,0 +1,24 @@ +#pragma once + +/* Export prefix for functions */ +#ifdef _MSC_VER + /* MSVC */ +# define OMATH_API_EXPORT __declspec(dllexport) +#else + /* GCC/Clang */ +# define OMATH_API_EXPORT __attribute__((visibility("default"))) +#endif + +/* Import prefix for functions */ +#ifdef _MSC_VER +# define OMATH_API_IMPORT __declspec(dllimport) +#else +# define OMATH_API_IMPORT extern +#endif + +/* Resolve import/export */ +#ifdef OMATH_EXPORT +# define OMATH_API OMATH_API_EXPORT +#else +# define OMATH_API OMATH_API_IMPORT +#endif \ No newline at end of file diff --git a/include/omath/pathfinding/Astar.hpp b/include/omath/pathfinding/Astar.hpp index 06098503..07d73232 100644 --- a/include/omath/pathfinding/Astar.hpp +++ b/include/omath/pathfinding/Astar.hpp @@ -6,11 +6,11 @@ #include #include "NavigationMesh.hpp" #include "omath/Vector3.hpp" - +#include "../omath_export.h" namespace omath::pathfinding { - class Astar final + class OMATH_API Astar final { public: [[nodiscard]] diff --git a/include/omath/pathfinding/NavigationMesh.hpp b/include/omath/pathfinding/NavigationMesh.hpp index 525cbc42..325fe9d0 100644 --- a/include/omath/pathfinding/NavigationMesh.hpp +++ b/include/omath/pathfinding/NavigationMesh.hpp @@ -8,7 +8,7 @@ #include #include #include - +#include "../omath_export.h" namespace omath::pathfinding { @@ -18,7 +18,7 @@ namespace omath::pathfinding }; - class NavigationMesh final + class OMATH_API NavigationMesh final { public: diff --git a/include/omath/prediction/Engine.hpp b/include/omath/prediction/Engine.hpp index 6d8e6177..d81eea01 100644 --- a/include/omath/prediction/Engine.hpp +++ b/include/omath/prediction/Engine.hpp @@ -8,11 +8,11 @@ #include "omath/Vector3.hpp" #include "omath/prediction/Projectile.hpp" #include "omath/prediction/Target.hpp" - +#include "../omath_export.h" namespace omath::prediction { - class Engine final + class OMATH_API Engine final { public: explicit Engine(float gravityConstant, float simulationTimeStep, diff --git a/include/omath/prediction/Projectile.hpp b/include/omath/prediction/Projectile.hpp index 93ded293..51da5ae6 100644 --- a/include/omath/prediction/Projectile.hpp +++ b/include/omath/prediction/Projectile.hpp @@ -4,11 +4,11 @@ #pragma once #include "omath/Vector3.hpp" - +#include "../omath_export.h" namespace omath::prediction { - class Projectile final + class OMATH_API Projectile final { public: diff --git a/include/omath/prediction/Target.hpp b/include/omath/prediction/Target.hpp index a2b37ce3..2f062d08 100644 --- a/include/omath/prediction/Target.hpp +++ b/include/omath/prediction/Target.hpp @@ -4,11 +4,11 @@ #pragma once #include "omath/Vector3.hpp" - +#include "../omath_export.h" namespace omath::prediction { - class Target final + class OMATH_API Target final { public: diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index 15db451a..2254d4c9 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -10,11 +10,11 @@ #include "ErrorCodes.hpp" #include #include - +#include "../omath_export.h" namespace omath::projection { - class ViewPort final + class OMATH_API ViewPort final { public: float m_width; @@ -28,7 +28,7 @@ namespace omath::projection using FieldOfView = const Angle; template - class Camera + class OMATH_API Camera { public: From 193e87847ad1a7480e89c29d4f19d4c6c9c698a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=95=E3=81=8F=E3=82=89=E3=81=BF=E3=81=93?= Date: Tue, 17 Dec 2024 07:24:44 +0300 Subject: [PATCH 142/795] fixup --- include/omath/Angle.hpp | 2 +- include/omath/Color.hpp | 2 +- include/omath/Mat.hpp | 2 +- include/omath/Matrix.hpp | 2 +- include/omath/Triangle3d.hpp | 2 +- include/omath/Vector2.hpp | 2 +- include/omath/Vector3.hpp | 2 +- include/omath/Vector4.hpp | 2 +- include/omath/ViewAngles.hpp | 2 +- include/omath/collision/LineTracer.hpp | 2 +- include/omath/engines/Source/Camera.hpp | 2 +- .../{omath_export.h => omath_export.hpp} | 46 +++++++++---------- include/omath/pathfinding/Astar.hpp | 2 +- include/omath/pathfinding/NavigationMesh.hpp | 2 +- include/omath/prediction/Engine.hpp | 2 +- include/omath/prediction/Projectile.hpp | 2 +- include/omath/prediction/Target.hpp | 2 +- include/omath/projection/Camera.hpp | 2 +- 18 files changed, 40 insertions(+), 40 deletions(-) rename include/omath/{omath_export.h => omath_export.hpp} (95%) diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index 3622a1c5..9b664953 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -6,7 +6,7 @@ #include "omath/Angles.hpp" #include -#include "omath_export.h" +#include "omath/omath_export.hpp" namespace omath { diff --git a/include/omath/Color.hpp b/include/omath/Color.hpp index cf4644bd..19185e80 100644 --- a/include/omath/Color.hpp +++ b/include/omath/Color.hpp @@ -7,7 +7,7 @@ #include "omath/Vector3.hpp" #include #include "omath/Vector4.hpp" -#include "omath_export.h" +#include "omath/omath_export.hpp" namespace omath { diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 3adb480c..2b99de57 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -8,7 +8,7 @@ #include #include #include "Vector3.hpp" -#include "omath_export.h" +#include "omath/omath_export.hpp" namespace omath { diff --git a/include/omath/Matrix.hpp b/include/omath/Matrix.hpp index fc1e296e..3cac39b2 100644 --- a/include/omath/Matrix.hpp +++ b/include/omath/Matrix.hpp @@ -2,7 +2,7 @@ #include #include #include -#include "omath_export.h" +#include "omath/omath_export.hpp" namespace omath { diff --git a/include/omath/Triangle3d.hpp b/include/omath/Triangle3d.hpp index 826e4fce..af944828 100644 --- a/include/omath/Triangle3d.hpp +++ b/include/omath/Triangle3d.hpp @@ -3,7 +3,7 @@ // #pragma once #include "omath/Vector3.hpp" -#include "omath_export.h" +#include "omath/omath_export.hpp" namespace omath { diff --git a/include/omath/Vector2.hpp b/include/omath/Vector2.hpp index a4cf0af5..7c49135c 100644 --- a/include/omath/Vector2.hpp +++ b/include/omath/Vector2.hpp @@ -5,7 +5,7 @@ #pragma once #include #include -#include "omath_export.h" +#include "omath/omath_export.hpp" namespace omath { diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index c0521b3a..9b80bc32 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -7,7 +7,7 @@ #include #include #include "omath/Vector2.hpp" -#include "omath_export.h" +#include "omath/omath_export.hpp" namespace omath { diff --git a/include/omath/Vector4.hpp b/include/omath/Vector4.hpp index 8a3c84f2..2a6fc32d 100644 --- a/include/omath/Vector4.hpp +++ b/include/omath/Vector4.hpp @@ -5,7 +5,7 @@ #include #include -#include "omath_export.h" +#include "omath/omath_export.hpp" namespace omath { diff --git a/include/omath/ViewAngles.hpp b/include/omath/ViewAngles.hpp index e3ef7a80..164feb40 100644 --- a/include/omath/ViewAngles.hpp +++ b/include/omath/ViewAngles.hpp @@ -3,7 +3,7 @@ // #pragma once -#include "omath_export.h" +#include "omath/omath_export.hpp" namespace omath { diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/LineTracer.hpp index 2c42e60f..d0675628 100644 --- a/include/omath/collision/LineTracer.hpp +++ b/include/omath/collision/LineTracer.hpp @@ -5,7 +5,7 @@ #include "omath/Vector3.hpp" #include "omath/Triangle3d.hpp" -#include "../omath_export.h" +#include "omath/omath_export.hpp" namespace omath::collision { diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/Source/Camera.hpp index 4ebe113b..b9429095 100644 --- a/include/omath/engines/Source/Camera.hpp +++ b/include/omath/engines/Source/Camera.hpp @@ -4,7 +4,7 @@ #pragma once #include "Constants.h" #include "omath/projection/Camera.hpp" -#include "../../omath_export.h" +#include "omath/omath_export.hpp" namespace omath::source { diff --git a/include/omath/omath_export.h b/include/omath/omath_export.hpp similarity index 95% rename from include/omath/omath_export.h rename to include/omath/omath_export.hpp index 091fd56c..199e78fd 100644 --- a/include/omath/omath_export.h +++ b/include/omath/omath_export.hpp @@ -1,24 +1,24 @@ -#pragma once - -/* Export prefix for functions */ -#ifdef _MSC_VER - /* MSVC */ -# define OMATH_API_EXPORT __declspec(dllexport) -#else - /* GCC/Clang */ -# define OMATH_API_EXPORT __attribute__((visibility("default"))) -#endif - -/* Import prefix for functions */ -#ifdef _MSC_VER -# define OMATH_API_IMPORT __declspec(dllimport) -#else -# define OMATH_API_IMPORT extern -#endif - -/* Resolve import/export */ -#ifdef OMATH_EXPORT -# define OMATH_API OMATH_API_EXPORT -#else -# define OMATH_API OMATH_API_IMPORT +#pragma once + +/* Export prefix for functions */ +#ifdef _MSC_VER + /* MSVC */ +# define OMATH_API_EXPORT __declspec(dllexport) +#else + /* GCC/Clang */ +# define OMATH_API_EXPORT __attribute__((visibility("default"))) +#endif + +/* Import prefix for functions */ +#ifdef _MSC_VER +# define OMATH_API_IMPORT __declspec(dllimport) +#else +# define OMATH_API_IMPORT extern +#endif + +/* Resolve import/export */ +#ifdef OMATH_EXPORT +# define OMATH_API OMATH_API_EXPORT +#else +# define OMATH_API OMATH_API_IMPORT #endif \ No newline at end of file diff --git a/include/omath/pathfinding/Astar.hpp b/include/omath/pathfinding/Astar.hpp index 07d73232..435bd5d6 100644 --- a/include/omath/pathfinding/Astar.hpp +++ b/include/omath/pathfinding/Astar.hpp @@ -6,7 +6,7 @@ #include #include "NavigationMesh.hpp" #include "omath/Vector3.hpp" -#include "../omath_export.h" +#include "omath/omath_export.hpp" namespace omath::pathfinding { diff --git a/include/omath/pathfinding/NavigationMesh.hpp b/include/omath/pathfinding/NavigationMesh.hpp index 325fe9d0..39cc2eac 100644 --- a/include/omath/pathfinding/NavigationMesh.hpp +++ b/include/omath/pathfinding/NavigationMesh.hpp @@ -8,7 +8,7 @@ #include #include #include -#include "../omath_export.h" +#include "omath/omath_export.hpp" namespace omath::pathfinding { diff --git a/include/omath/prediction/Engine.hpp b/include/omath/prediction/Engine.hpp index d81eea01..c48456c7 100644 --- a/include/omath/prediction/Engine.hpp +++ b/include/omath/prediction/Engine.hpp @@ -8,7 +8,7 @@ #include "omath/Vector3.hpp" #include "omath/prediction/Projectile.hpp" #include "omath/prediction/Target.hpp" -#include "../omath_export.h" +#include "omath/omath_export.hpp" namespace omath::prediction { diff --git a/include/omath/prediction/Projectile.hpp b/include/omath/prediction/Projectile.hpp index 51da5ae6..51f6ebce 100644 --- a/include/omath/prediction/Projectile.hpp +++ b/include/omath/prediction/Projectile.hpp @@ -4,7 +4,7 @@ #pragma once #include "omath/Vector3.hpp" -#include "../omath_export.h" +#include "omath/omath_export.hpp" namespace omath::prediction { diff --git a/include/omath/prediction/Target.hpp b/include/omath/prediction/Target.hpp index 2f062d08..083d3bd4 100644 --- a/include/omath/prediction/Target.hpp +++ b/include/omath/prediction/Target.hpp @@ -4,7 +4,7 @@ #pragma once #include "omath/Vector3.hpp" -#include "../omath_export.h" +#include "omath/omath_export.hpp" namespace omath::prediction { diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index 2254d4c9..7a374ba1 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -10,7 +10,7 @@ #include "ErrorCodes.hpp" #include #include -#include "../omath_export.h" +#include namespace omath::projection { From 58fb6a030477718a324ec13fd15ff16dee76e9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=95=E3=81=8F=E3=82=89=E3=81=BF=E3=81=93?= Date: Tue, 17 Dec 2024 08:40:48 +0300 Subject: [PATCH 143/795] clean --- include/omath/omath_export.hpp | 2 +- include/omath/projection/Camera.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/omath/omath_export.hpp b/include/omath/omath_export.hpp index 199e78fd..b0949e76 100644 --- a/include/omath/omath_export.hpp +++ b/include/omath/omath_export.hpp @@ -21,4 +21,4 @@ # define OMATH_API OMATH_API_EXPORT #else # define OMATH_API OMATH_API_IMPORT -#endif \ No newline at end of file +#endif diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index 7a374ba1..59f6fe8f 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -10,7 +10,7 @@ #include "ErrorCodes.hpp" #include #include -#include +#include "omath/omath_export.hpp" namespace omath::projection { From fc164b339bfbf4ff33e15158261d5e67d283101d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=95=E3=81=8F=E3=82=89=E3=81=BF=E3=81=93?= Date: Tue, 17 Dec 2024 12:08:18 +0300 Subject: [PATCH 144/795] fix --- include/omath/Mat.hpp | 74 ++++++++++---------- include/omath/Matrix.hpp | 72 +++++++++---------- include/omath/pathfinding/NavigationMesh.hpp | 10 +-- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 2b99de57..7b05f426 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -25,18 +25,18 @@ namespace omath template requires std::is_arithmetic_v - class OMATH_API Mat final + class Mat final { public: - constexpr Mat() + OMATH_API constexpr Mat() { Clear(); } - constexpr static MatStoreType GetStoreOrdering() + OMATH_API constexpr static MatStoreType GetStoreOrdering() { return StoreType; } - constexpr Mat(const std::initializer_list>& rows) + OMATH_API constexpr Mat(const std::initializer_list>& rows) { if (rows.size() != Rows) throw std::invalid_argument("Initializer list rows size does not match template parameter Rows"); @@ -56,40 +56,40 @@ namespace omath } } - constexpr explicit Mat(const Type* rawData) + OMATH_API constexpr explicit Mat(const Type* rawData) { std::copy_n(rawData, Rows * Columns, m_data.begin()); } - constexpr Mat(const Mat& other) noexcept + OMATH_API constexpr Mat(const Mat& other) noexcept { m_data = other.m_data; } - constexpr Mat(Mat&& other) noexcept + OMATH_API constexpr Mat(Mat&& other) noexcept { m_data = std::move(other.m_data); } [[nodiscard]] - static constexpr size_t RowCount() noexcept + OMATH_API static constexpr size_t RowCount() noexcept { return Rows; } [[nodiscard]] - static constexpr size_t ColumnsCount() noexcept + OMATH_API static constexpr size_t ColumnsCount() noexcept { return Columns; } [[nodiscard]] - static consteval MatSize Size() noexcept + OMATH_API static consteval MatSize Size() noexcept { return {Rows, Columns}; } - [[nodiscard]] constexpr const Type& At(const size_t rowIndex, const size_t columnIndex) const + [[nodiscard]] OMATH_API constexpr const Type& At(const size_t rowIndex, const size_t columnIndex) const { if (rowIndex >= Rows || columnIndex >= Columns) throw std::out_of_range("Index out of range"); @@ -107,13 +107,13 @@ namespace omath } } - [[nodiscard]] constexpr Type& At(const size_t rowIndex, const size_t columnIndex) + [[nodiscard]] OMATH_API constexpr Type& At(const size_t rowIndex, const size_t columnIndex) { return const_cast(std::as_const(*this).At(rowIndex, columnIndex)); } [[nodiscard]] - constexpr Type Sum() const noexcept + OMATH_API constexpr Type Sum() const noexcept { Type sum = 0; for (size_t i = 0; i < Rows; ++i) @@ -123,12 +123,12 @@ namespace omath return sum; } - constexpr void Clear() noexcept + OMATH_API constexpr void Clear() noexcept { Set(0); } - constexpr void Set(const Type& value) noexcept + OMATH_API constexpr void Set(const Type& value) noexcept { std::ranges::fill(m_data, value); } @@ -136,7 +136,7 @@ namespace omath // Operator overloading for multiplication with another Mat template constexpr Mat - operator*(const Mat& other) const + OMATH_API operator*(const Mat& other) const { Mat result; @@ -151,7 +151,7 @@ namespace omath return result; } - constexpr Mat& operator*=(const Type& f) noexcept + OMATH_API constexpr Mat& operator*=(const Type& f) noexcept { for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) @@ -161,19 +161,19 @@ namespace omath template constexpr Mat - operator*=(const Mat& other) + OMATH_API operator*=(const Mat& other) { return *this = *this * other; } - constexpr Mat operator*(const Type& f) const noexcept + OMATH_API constexpr Mat operator*(const Type& f) const noexcept { Mat result(*this); result *= f; return result; } - constexpr Mat& operator/=(const Type& f) noexcept + OMATH_API constexpr Mat& operator/=(const Type& f) noexcept { for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) @@ -181,14 +181,14 @@ namespace omath return *this; } - constexpr Mat operator/(const Type& f) const noexcept + OMATH_API constexpr Mat operator/(const Type& f) const noexcept { Mat result(*this); result /= f; return result; } - constexpr Mat& operator=(const Mat& other) noexcept + OMATH_API constexpr Mat& operator=(const Mat& other) noexcept { if (this == &other) return *this; @@ -198,7 +198,7 @@ namespace omath return *this; } - constexpr Mat& operator=(Mat&& other) noexcept + OMATH_API constexpr Mat& operator=(Mat&& other) noexcept { if (this == &other) return *this; @@ -211,7 +211,7 @@ namespace omath } [[nodiscard]] - constexpr Mat Transposed() const noexcept + OMATH_API constexpr Mat Transposed() const noexcept { Mat transposed; for (size_t i = 0; i < Rows; ++i) @@ -222,7 +222,7 @@ namespace omath } [[nodiscard]] - constexpr Type Determinant() const + OMATH_API constexpr Type Determinant() const { static_assert(Rows == Columns, "Determinant is only defined for square matrices."); @@ -244,7 +244,7 @@ namespace omath } [[nodiscard]] - constexpr Mat Minor(const size_t row, const size_t column) const + OMATH_API constexpr Mat Minor(const size_t row, const size_t column) const { Mat result; for (size_t i = 0, m = 0; i < Rows; ++i) @@ -264,19 +264,19 @@ namespace omath } [[nodiscard]] - constexpr const std::array& RawArray() const + OMATH_API constexpr const std::array& RawArray() const { return m_data; } [[nodiscard]] - constexpr std::array& RawArray() + OMATH_API constexpr std::array& RawArray() { return const_cast>(std::as_const(*this).RawArray()); } [[nodiscard]] - std::string ToString() const noexcept + OMATH_API std::string ToString() const noexcept { std::ostringstream oss; for (size_t i = 0; i < Rows; ++i) @@ -293,20 +293,20 @@ namespace omath } [[nodiscard]] - bool operator==(const Mat& mat) const + OMATH_API bool operator==(const Mat& mat) const { return m_data == mat.m_data; } [[nodiscard]] - bool operator!=(const Mat& mat) const + OMATH_API bool operator!=(const Mat& mat) const { return !operator==(mat); } // Static methods that return fixed-size matrices [[nodiscard]] - constexpr static Mat<4, 4> ToScreenMat(const Type& screenWidth, const Type& screenHeight) noexcept + OMATH_API constexpr static Mat<4, 4> ToScreenMat(const Type& screenWidth, const Type& screenHeight) noexcept { return { {screenWidth / 2, 0, 0, 0}, @@ -336,7 +336,7 @@ namespace omath template [[nodiscard]] - constexpr Mat<4, 4, Type, St> MatTranslation(const Vector3& diff) noexcept + OMATH_API constexpr Mat<4, 4, Type, St> MatTranslation(const Vector3& diff) noexcept { return { @@ -349,7 +349,7 @@ namespace omath template [[nodiscard]] - Mat<4, 4, Type, St> MatRotationAxisX(const Angle& angle) noexcept + OMATH_API Mat<4, 4, Type, St> MatRotationAxisX(const Angle& angle) noexcept { return { @@ -362,7 +362,7 @@ namespace omath template [[nodiscard]] - Mat<4, 4, Type, St> MatRotationAxisY(const Angle& angle) noexcept + OMATH_API Mat<4, 4, Type, St> MatRotationAxisY(const Angle& angle) noexcept { return { @@ -375,7 +375,7 @@ namespace omath template [[nodiscard]] - Mat<4, 4, Type, St> MatRotationAxisZ(const Angle& angle) noexcept + OMATH_API Mat<4, 4, Type, St> MatRotationAxisZ(const Angle& angle) noexcept { return { @@ -403,7 +403,7 @@ namespace omath template [[nodiscard]] - Mat<4, 4, Type, St> MatRotation(const ViewAngles& angles) noexcept + OMATH_API Mat<4, 4, Type, St> MatRotation(const ViewAngles& angles) noexcept { return MatRotationAxisZ(angles.yaw) * MatRotationAxisY(angles.pitch) * MatRotationAxisX(angles.roll); } diff --git a/include/omath/Matrix.hpp b/include/omath/Matrix.hpp index 3cac39b2..3ffe5ec1 100644 --- a/include/omath/Matrix.hpp +++ b/include/omath/Matrix.hpp @@ -8,94 +8,94 @@ namespace omath { class Vector3; - class OMATH_API Matrix final + class Matrix final { public: - Matrix(); - Matrix(size_t rows, size_t columns); + OMATH_API Matrix(); + OMATH_API Matrix(size_t rows, size_t columns); - Matrix(const std::initializer_list>& rows); + OMATH_API Matrix(const std::initializer_list>& rows); [[nodiscard]] - static Matrix ToScreenMatrix(float screenWidth, float screenHeight); + OMATH_API static Matrix ToScreenMatrix(float screenWidth, float screenHeight); [[nodiscard]] - static Matrix TranslationMatrix(const Vector3& diff); + OMATH_API static Matrix TranslationMatrix(const Vector3& diff); [[nodiscard]] - static Matrix OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up); + OMATH_API static Matrix OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up); [[nodiscard]] - static Matrix ProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); + OMATH_API static Matrix ProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); - Matrix(const Matrix& other); + OMATH_API Matrix(const Matrix& other); - Matrix(size_t rows, size_t columns, const float* pRaw); + OMATH_API Matrix(size_t rows, size_t columns, const float* pRaw); - Matrix(Matrix&& other) noexcept; + OMATH_API Matrix(Matrix&& other) noexcept; [[nodiscard]] - size_t RowCount() const noexcept; + OMATH_API size_t RowCount() const noexcept; [[nodiscard]] - size_t ColumnsCount() const noexcept; + OMATH_API size_t ColumnsCount() const noexcept; [[nodiscard]] - std::pair Size() const noexcept; + OMATH_API std::pair Size() const noexcept; [[nodiscard]] - float& At(size_t iRow, size_t iCol); + OMATH_API float& At(size_t iRow, size_t iCol); [[nodiscard]] - float Sum(); + OMATH_API float Sum(); - void SetDataFromRaw(const float* pRawMatrix); + OMATH_API void SetDataFromRaw(const float* pRawMatrix); [[nodiscard]] - Matrix Transpose() const; + OMATH_API Matrix Transpose() const; - void Set(float val); + OMATH_API void Set(float val); [[nodiscard]] - const float& At(size_t iRow, size_t iCol) const; + OMATH_API const float& At(size_t iRow, size_t iCol) const; - Matrix operator*(const Matrix& other) const; + OMATH_API Matrix operator*(const Matrix& other) const; - Matrix& operator*=(const Matrix& other); + OMATH_API Matrix& operator*=(const Matrix& other); - Matrix operator*(float f) const; + OMATH_API Matrix operator*(float f) const; - Matrix& operator*=(float f); + OMATH_API Matrix& operator*=(float f); - Matrix& operator/=(float f); + OMATH_API Matrix& operator/=(float f); - void Clear(); + OMATH_API void Clear(); [[nodiscard]] - Matrix Strip(size_t row, size_t column) const; + OMATH_API Matrix Strip(size_t row, size_t column) const; [[nodiscard]] - float Minor(size_t i, size_t j) const; + OMATH_API float Minor(size_t i, size_t j) const; [[nodiscard]] - float AlgComplement(size_t i, size_t j) const; + OMATH_API float AlgComplement(size_t i, size_t j) const; [[nodiscard]] - float Determinant() const; + OMATH_API float Determinant() const; [[nodiscard]] - const float* Raw() const; + OMATH_API const float* Raw() const; - Matrix& operator=(const Matrix& other); + OMATH_API Matrix& operator=(const Matrix& other); - Matrix& operator=(Matrix&& other) noexcept; + OMATH_API Matrix& operator=(Matrix&& other) noexcept; - Matrix operator/(float f) const; + OMATH_API Matrix operator/(float f) const; [[nodiscard]] - std::string ToString() const; + OMATH_API std::string ToString() const; - ~Matrix(); + OMATH_API ~Matrix(); private: size_t m_rows; diff --git a/include/omath/pathfinding/NavigationMesh.hpp b/include/omath/pathfinding/NavigationMesh.hpp index 39cc2eac..8ab5c24b 100644 --- a/include/omath/pathfinding/NavigationMesh.hpp +++ b/include/omath/pathfinding/NavigationMesh.hpp @@ -18,21 +18,21 @@ namespace omath::pathfinding }; - class OMATH_API NavigationMesh final + class NavigationMesh final { public: [[nodiscard]] - std::expected GetClosestVertex(const Vector3& point) const; + OMATH_API std::expected GetClosestVertex(const Vector3& point) const; [[nodiscard]] - const std::vector& GetNeighbors(const Vector3& vertex) const; + OMATH_API const std::vector& GetNeighbors(const Vector3& vertex) const; [[nodiscard]] - bool Empty() const; + OMATH_API bool Empty() const; [[nodiscard]] std::vector Serialize() const; - void Deserialize(const std::vector& raw); + OMATH_API void Deserialize(const std::vector& raw); std::unordered_map> m_verTextMap; }; From 221fd54f5afdb39f00b82590e0c67b5f5c8345ea Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 17 Dec 2024 12:30:05 +0300 Subject: [PATCH 145/795] fixed warning related with clang --- include/omath/engines/Source/Camera.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/Source/Camera.hpp index b9429095..1250d960 100644 --- a/include/omath/engines/Source/Camera.hpp +++ b/include/omath/engines/Source/Camera.hpp @@ -12,7 +12,7 @@ namespace omath::source { public: Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, - const Angle& fov, float near, float far); + const Angle& fov, float near, float far); void LookAt(const Vector3& target) override; [[nodiscard]] Mat4x4 GetViewMatrix() const override; [[nodiscard]] Mat4x4 GetProjectionMatrix() const override; From 24ad4ac69808f20bc69413ddfc83346488697032 Mon Sep 17 00:00:00 2001 From: Saikari Date: Sat, 21 Dec 2024 01:18:25 +0300 Subject: [PATCH 146/795] Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 4fca58da..9b3d91be 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,20 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at - CMake (for building the project) ### Installation +### vcpkg +**Note**: Support vcpkg for package management +1. Install vcpkg (https://github.com/microsoft/vcpkg) +2. Run the following command to install the orange-math package: +``` +vcpkg install orange-math +``` +CMakeLists.txt +```cmake +find_package(omath CONFIG REQUIRED) +target_link_libraries(main PRIVATE omath::omath) +``` +For detailed commands on installing different versions and more information, please refer to Microsoft's official instructions (https://learn.microsoft.com/en-us/vcpkg/get_started/overview) +### Build from source 1. Clone the repository: ``` git clone https://github.com/orange-cpp/omath.git From 93082d6a8d324c36988f6523643ad8565c098757 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 21 Dec 2024 15:53:22 +0300 Subject: [PATCH 147/795] added components check --- cmake/omathConfig.cmake.in | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/omathConfig.cmake.in b/cmake/omathConfig.cmake.in index a889c4df..6c0631d6 100644 --- a/cmake/omathConfig.cmake.in +++ b/cmake/omathConfig.cmake.in @@ -4,3 +4,4 @@ include(CMakeFindDependencyMacro) # Load the targets for the omath library include("${CMAKE_CURRENT_LIST_DIR}/omathTargets.cmake") +check_required_components(omath) From 390e584aee41d554c12ecacbb85f18c90fe52d22 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 21 Dec 2024 16:03:54 +0300 Subject: [PATCH 148/795] updated version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 23713a4d..31fdfbc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.26) -project(omath VERSION 1.0.0) +project(omath VERSION 1.0.1) include(CMakePackageConfigHelpers) From e4973083492a11c3194d1a12eb37dbcf3dbd7cb9 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 21 Dec 2024 19:29:33 +0300 Subject: [PATCH 149/795] added operator square brackets --- include/omath/Mat.hpp | 5 +++++ tests/general/UnitTestMat.cpp | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 7b05f426..63cbdefc 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -66,6 +66,11 @@ namespace omath m_data = other.m_data; } + OMATH_API constexpr Type& operator[](const size_t row, const size_t col) + { + return At(row, col); + } + OMATH_API constexpr Mat(Mat&& other) noexcept { m_data = std::move(other.m_data); diff --git a/tests/general/UnitTestMat.cpp b/tests/general/UnitTestMat.cpp index 0105c283..cdaecfc8 100644 --- a/tests/general/UnitTestMat.cpp +++ b/tests/general/UnitTestMat.cpp @@ -40,6 +40,14 @@ TEST_F(UnitTestMat, Constructor_InitializerList) EXPECT_FLOAT_EQ(m.At(1, 1), 4.0f); } +TEST_F(UnitTestMat, Operator_SquareBrackets) +{ + EXPECT_EQ((m2[0, 0]), 1.0f); + EXPECT_EQ((m2[0, 1]), 2.0f); + EXPECT_EQ((m2[1, 0]), 3.0f); + EXPECT_EQ((m2[1, 1]), 4.0f); +} + TEST_F(UnitTestMat, Constructor_Copy) { Mat<2, 2> m3 = m2; From 2b9575311eb777e5b4a7553a2bb00281b2f8613e Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 21 Dec 2024 19:32:09 +0300 Subject: [PATCH 150/795] added operator for matrix --- include/omath/Matrix.hpp | 7 +++++++ tests/general/UnitTestMatrix.cpp | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/include/omath/Matrix.hpp b/include/omath/Matrix.hpp index 3ffe5ec1..78314a3c 100644 --- a/include/omath/Matrix.hpp +++ b/include/omath/Matrix.hpp @@ -37,6 +37,13 @@ namespace omath [[nodiscard]] OMATH_API size_t RowCount() const noexcept; + + [[nodiscard]] + OMATH_API float& operator[](size_t row, size_t column) + { + return At(row, column); + } + [[nodiscard]] OMATH_API size_t ColumnsCount() const noexcept; diff --git a/tests/general/UnitTestMatrix.cpp b/tests/general/UnitTestMatrix.cpp index bc749d07..1fa94b03 100644 --- a/tests/general/UnitTestMatrix.cpp +++ b/tests/general/UnitTestMatrix.cpp @@ -29,6 +29,15 @@ TEST_F(UnitTestMatrix, Constructor_Size) EXPECT_EQ(m.ColumnsCount(), 3); } +TEST_F(UnitTestMatrix, Operator_SquareBrackets) +{ + EXPECT_EQ((m2[0, 0]), 1.0f); + EXPECT_EQ((m2[0, 1]), 2.0f); + EXPECT_EQ((m2[1, 0]), 3.0f); + EXPECT_EQ((m2[1, 1]), 4.0f); +} + + TEST_F(UnitTestMatrix, Constructor_InitializerList) { Matrix m{{1.0f, 2.0f}, {3.0f, 4.0f}}; From 30bc3f2c6dde6fea14246f4022edd3a9357b11f9 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 21 Dec 2024 19:41:31 +0300 Subject: [PATCH 151/795] changed readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b3d91be..b18c9029 100644 --- a/README.md +++ b/README.md @@ -83,4 +83,4 @@ Contributions to `omath` are welcome! Please read `CONTRIBUTING.md` for details This project is licensed under the MIT - see the `LICENSE` file for details. ## Acknowledgments -- Orange | [Telegram](https://t.me/orange_cpp) +- [All contributors](https://github.com/orange-cpp/omath/graphs/contributors) From 001c6a8a47ffb4df1bb897fe045bc1a22b755df4 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 23 Dec 2024 13:43:14 +0300 Subject: [PATCH 152/795] fixed vcs.mxl --- .idea/vcs.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index edda5903..adc159a8 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,7 +2,6 @@ - \ No newline at end of file From 409cd18de66c6a4818772c75af4589b4c8dc2423 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 23 Dec 2024 13:47:28 +0300 Subject: [PATCH 153/795] dropped .dll/.so support --- CMakeLists.txt | 4 - include/omath/Angle.hpp | 4 +- include/omath/Color.hpp | 5 +- include/omath/Mat.hpp | 77 ++++++++++---------- include/omath/Matrix.hpp | 73 +++++++++---------- include/omath/Triangle3d.hpp | 3 +- include/omath/Vector2.hpp | 3 +- include/omath/Vector3.hpp | 3 +- include/omath/Vector4.hpp | 3 +- include/omath/ViewAngles.hpp | 4 +- include/omath/collision/LineTracer.hpp | 5 +- include/omath/engines/Source/Camera.hpp | 3 +- include/omath/pathfinding/Astar.hpp | 3 +- include/omath/pathfinding/NavigationMesh.hpp | 9 +-- include/omath/prediction/Engine.hpp | 3 +- include/omath/prediction/Projectile.hpp | 3 +- include/omath/prediction/Target.hpp | 3 +- include/omath/projection/Camera.hpp | 5 +- 18 files changed, 95 insertions(+), 118 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 31fdfbc1..9af21107 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,10 +21,6 @@ endif() target_compile_definitions(omath PUBLIC OMATH_EXPORT) -if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - target_compile_options(omath PRIVATE /Zc:static_assert-) -endif() - add_subdirectory(source) if(OMATH_BUILD_TESTS) diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index 9b664953..4ae0dee2 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -4,9 +4,7 @@ #pragma once #include "omath/Angles.hpp" - #include -#include "omath/omath_export.hpp" namespace omath { @@ -18,7 +16,7 @@ namespace omath template requires std::is_arithmetic_v - class OMATH_API Angle + class Angle { Type m_angle; constexpr Angle(const Type& degrees) diff --git a/include/omath/Color.hpp b/include/omath/Color.hpp index 19185e80..642adcc3 100644 --- a/include/omath/Color.hpp +++ b/include/omath/Color.hpp @@ -7,11 +7,10 @@ #include "omath/Vector3.hpp" #include #include "omath/Vector4.hpp" -#include "omath/omath_export.hpp" namespace omath { - struct OMATH_API HSV + struct HSV { float m_hue{}; float m_saturation{}; @@ -19,7 +18,7 @@ namespace omath }; - class OMATH_API Color final : public Vector4 + class Color final : public Vector4 { public: constexpr Color(float r, float g, float b, float a) : Vector4(r,g,b,a) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 63cbdefc..382d900a 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -8,11 +8,10 @@ #include #include #include "Vector3.hpp" -#include "omath/omath_export.hpp" namespace omath { - struct OMATH_API MatSize + struct MatSize { size_t rows, columns; }; @@ -28,15 +27,15 @@ namespace omath class Mat final { public: - OMATH_API constexpr Mat() + constexpr Mat() { Clear(); } - OMATH_API constexpr static MatStoreType GetStoreOrdering() + constexpr static MatStoreType GetStoreOrdering() { return StoreType; } - OMATH_API constexpr Mat(const std::initializer_list>& rows) + constexpr Mat(const std::initializer_list>& rows) { if (rows.size() != Rows) throw std::invalid_argument("Initializer list rows size does not match template parameter Rows"); @@ -56,45 +55,45 @@ namespace omath } } - OMATH_API constexpr explicit Mat(const Type* rawData) + constexpr explicit Mat(const Type* rawData) { std::copy_n(rawData, Rows * Columns, m_data.begin()); } - OMATH_API constexpr Mat(const Mat& other) noexcept + constexpr Mat(const Mat& other) noexcept { m_data = other.m_data; } - OMATH_API constexpr Type& operator[](const size_t row, const size_t col) + constexpr Type& operator[](const size_t row, const size_t col) { return At(row, col); } - OMATH_API constexpr Mat(Mat&& other) noexcept + constexpr Mat(Mat&& other) noexcept { m_data = std::move(other.m_data); } [[nodiscard]] - OMATH_API static constexpr size_t RowCount() noexcept + static constexpr size_t RowCount() noexcept { return Rows; } [[nodiscard]] - OMATH_API static constexpr size_t ColumnsCount() noexcept + static constexpr size_t ColumnsCount() noexcept { return Columns; } [[nodiscard]] - OMATH_API static consteval MatSize Size() noexcept + static consteval MatSize Size() noexcept { return {Rows, Columns}; } - [[nodiscard]] OMATH_API constexpr const Type& At(const size_t rowIndex, const size_t columnIndex) const + [[nodiscard]] constexpr const Type& At(const size_t rowIndex, const size_t columnIndex) const { if (rowIndex >= Rows || columnIndex >= Columns) throw std::out_of_range("Index out of range"); @@ -112,13 +111,13 @@ namespace omath } } - [[nodiscard]] OMATH_API constexpr Type& At(const size_t rowIndex, const size_t columnIndex) + [[nodiscard]] constexpr Type& At(const size_t rowIndex, const size_t columnIndex) { return const_cast(std::as_const(*this).At(rowIndex, columnIndex)); } [[nodiscard]] - OMATH_API constexpr Type Sum() const noexcept + constexpr Type Sum() const noexcept { Type sum = 0; for (size_t i = 0; i < Rows; ++i) @@ -128,12 +127,12 @@ namespace omath return sum; } - OMATH_API constexpr void Clear() noexcept + constexpr void Clear() noexcept { Set(0); } - OMATH_API constexpr void Set(const Type& value) noexcept + constexpr void Set(const Type& value) noexcept { std::ranges::fill(m_data, value); } @@ -141,7 +140,7 @@ namespace omath // Operator overloading for multiplication with another Mat template constexpr Mat - OMATH_API operator*(const Mat& other) const + operator*(const Mat& other) const { Mat result; @@ -156,7 +155,7 @@ namespace omath return result; } - OMATH_API constexpr Mat& operator*=(const Type& f) noexcept + constexpr Mat& operator*=(const Type& f) noexcept { for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) @@ -166,19 +165,19 @@ namespace omath template constexpr Mat - OMATH_API operator*=(const Mat& other) + operator*=(const Mat& other) { return *this = *this * other; } - OMATH_API constexpr Mat operator*(const Type& f) const noexcept + constexpr Mat operator*(const Type& f) const noexcept { Mat result(*this); result *= f; return result; } - OMATH_API constexpr Mat& operator/=(const Type& f) noexcept + constexpr Mat& operator/=(const Type& f) noexcept { for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) @@ -186,14 +185,14 @@ namespace omath return *this; } - OMATH_API constexpr Mat operator/(const Type& f) const noexcept + constexpr Mat operator/(const Type& f) const noexcept { Mat result(*this); result /= f; return result; } - OMATH_API constexpr Mat& operator=(const Mat& other) noexcept + constexpr Mat& operator=(const Mat& other) noexcept { if (this == &other) return *this; @@ -203,7 +202,7 @@ namespace omath return *this; } - OMATH_API constexpr Mat& operator=(Mat&& other) noexcept + constexpr Mat& operator=(Mat&& other) noexcept { if (this == &other) return *this; @@ -216,7 +215,7 @@ namespace omath } [[nodiscard]] - OMATH_API constexpr Mat Transposed() const noexcept + constexpr Mat Transposed() const noexcept { Mat transposed; for (size_t i = 0; i < Rows; ++i) @@ -227,7 +226,7 @@ namespace omath } [[nodiscard]] - OMATH_API constexpr Type Determinant() const + constexpr Type Determinant() const { static_assert(Rows == Columns, "Determinant is only defined for square matrices."); @@ -249,7 +248,7 @@ namespace omath } [[nodiscard]] - OMATH_API constexpr Mat Minor(const size_t row, const size_t column) const + constexpr Mat Minor(const size_t row, const size_t column) const { Mat result; for (size_t i = 0, m = 0; i < Rows; ++i) @@ -269,19 +268,19 @@ namespace omath } [[nodiscard]] - OMATH_API constexpr const std::array& RawArray() const + constexpr const std::array& RawArray() const { return m_data; } [[nodiscard]] - OMATH_API constexpr std::array& RawArray() + constexpr std::array& RawArray() { return const_cast>(std::as_const(*this).RawArray()); } [[nodiscard]] - OMATH_API std::string ToString() const noexcept + std::string ToString() const noexcept { std::ostringstream oss; for (size_t i = 0; i < Rows; ++i) @@ -298,20 +297,20 @@ namespace omath } [[nodiscard]] - OMATH_API bool operator==(const Mat& mat) const + bool operator==(const Mat& mat) const { return m_data == mat.m_data; } [[nodiscard]] - OMATH_API bool operator!=(const Mat& mat) const + bool operator!=(const Mat& mat) const { return !operator==(mat); } // Static methods that return fixed-size matrices [[nodiscard]] - OMATH_API constexpr static Mat<4, 4> ToScreenMat(const Type& screenWidth, const Type& screenHeight) noexcept + constexpr static Mat<4, 4> ToScreenMat(const Type& screenWidth, const Type& screenHeight) noexcept { return { {screenWidth / 2, 0, 0, 0}, @@ -341,7 +340,7 @@ namespace omath template [[nodiscard]] - OMATH_API constexpr Mat<4, 4, Type, St> MatTranslation(const Vector3& diff) noexcept + constexpr Mat<4, 4, Type, St> MatTranslation(const Vector3& diff) noexcept { return { @@ -354,7 +353,7 @@ namespace omath template [[nodiscard]] - OMATH_API Mat<4, 4, Type, St> MatRotationAxisX(const Angle& angle) noexcept + Mat<4, 4, Type, St> MatRotationAxisX(const Angle& angle) noexcept { return { @@ -367,7 +366,7 @@ namespace omath template [[nodiscard]] - OMATH_API Mat<4, 4, Type, St> MatRotationAxisY(const Angle& angle) noexcept + Mat<4, 4, Type, St> MatRotationAxisY(const Angle& angle) noexcept { return { @@ -380,7 +379,7 @@ namespace omath template [[nodiscard]] - OMATH_API Mat<4, 4, Type, St> MatRotationAxisZ(const Angle& angle) noexcept + Mat<4, 4, Type, St> MatRotationAxisZ(const Angle& angle) noexcept { return { @@ -408,7 +407,7 @@ namespace omath template [[nodiscard]] - OMATH_API Mat<4, 4, Type, St> MatRotation(const ViewAngles& angles) noexcept + Mat<4, 4, Type, St> MatRotation(const ViewAngles& angles) noexcept { return MatRotationAxisZ(angles.yaw) * MatRotationAxisY(angles.pitch) * MatRotationAxisX(angles.roll); } diff --git a/include/omath/Matrix.hpp b/include/omath/Matrix.hpp index 78314a3c..6e7e4097 100644 --- a/include/omath/Matrix.hpp +++ b/include/omath/Matrix.hpp @@ -2,7 +2,6 @@ #include #include #include -#include "omath/omath_export.hpp" namespace omath { @@ -11,98 +10,98 @@ namespace omath class Matrix final { public: - OMATH_API Matrix(); - OMATH_API Matrix(size_t rows, size_t columns); + Matrix(); + Matrix(size_t rows, size_t columns); - OMATH_API Matrix(const std::initializer_list>& rows); + Matrix(const std::initializer_list>& rows); [[nodiscard]] - OMATH_API static Matrix ToScreenMatrix(float screenWidth, float screenHeight); + static Matrix ToScreenMatrix(float screenWidth, float screenHeight); [[nodiscard]] - OMATH_API static Matrix TranslationMatrix(const Vector3& diff); + static Matrix TranslationMatrix(const Vector3& diff); [[nodiscard]] - OMATH_API static Matrix OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up); + static Matrix OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up); [[nodiscard]] - OMATH_API static Matrix ProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); + static Matrix ProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); - OMATH_API Matrix(const Matrix& other); + Matrix(const Matrix& other); - OMATH_API Matrix(size_t rows, size_t columns, const float* pRaw); + Matrix(size_t rows, size_t columns, const float* pRaw); - OMATH_API Matrix(Matrix&& other) noexcept; + Matrix(Matrix&& other) noexcept; [[nodiscard]] - OMATH_API size_t RowCount() const noexcept; + size_t RowCount() const noexcept; [[nodiscard]] - OMATH_API float& operator[](size_t row, size_t column) + float& operator[](size_t row, size_t column) { return At(row, column); } [[nodiscard]] - OMATH_API size_t ColumnsCount() const noexcept; + size_t ColumnsCount() const noexcept; [[nodiscard]] - OMATH_API std::pair Size() const noexcept; + std::pair Size() const noexcept; [[nodiscard]] - OMATH_API float& At(size_t iRow, size_t iCol); + float& At(size_t iRow, size_t iCol); [[nodiscard]] - OMATH_API float Sum(); + float Sum(); - OMATH_API void SetDataFromRaw(const float* pRawMatrix); + void SetDataFromRaw(const float* pRawMatrix); [[nodiscard]] - OMATH_API Matrix Transpose() const; + Matrix Transpose() const; - OMATH_API void Set(float val); + void Set(float val); [[nodiscard]] - OMATH_API const float& At(size_t iRow, size_t iCol) const; + const float& At(size_t iRow, size_t iCol) const; - OMATH_API Matrix operator*(const Matrix& other) const; + Matrix operator*(const Matrix& other) const; - OMATH_API Matrix& operator*=(const Matrix& other); + Matrix& operator*=(const Matrix& other); - OMATH_API Matrix operator*(float f) const; + Matrix operator*(float f) const; - OMATH_API Matrix& operator*=(float f); + Matrix& operator*=(float f); - OMATH_API Matrix& operator/=(float f); + Matrix& operator/=(float f); - OMATH_API void Clear(); + void Clear(); [[nodiscard]] - OMATH_API Matrix Strip(size_t row, size_t column) const; + Matrix Strip(size_t row, size_t column) const; [[nodiscard]] - OMATH_API float Minor(size_t i, size_t j) const; + float Minor(size_t i, size_t j) const; [[nodiscard]] - OMATH_API float AlgComplement(size_t i, size_t j) const; + float AlgComplement(size_t i, size_t j) const; [[nodiscard]] - OMATH_API float Determinant() const; + float Determinant() const; [[nodiscard]] - OMATH_API const float* Raw() const; + const float* Raw() const; - OMATH_API Matrix& operator=(const Matrix& other); + Matrix& operator=(const Matrix& other); - OMATH_API Matrix& operator=(Matrix&& other) noexcept; + Matrix& operator=(Matrix&& other) noexcept; - OMATH_API Matrix operator/(float f) const; + Matrix operator/(float f) const; [[nodiscard]] - OMATH_API std::string ToString() const; + std::string ToString() const; - OMATH_API ~Matrix(); + ~Matrix(); private: size_t m_rows; diff --git a/include/omath/Triangle3d.hpp b/include/omath/Triangle3d.hpp index af944828..370aa957 100644 --- a/include/omath/Triangle3d.hpp +++ b/include/omath/Triangle3d.hpp @@ -3,11 +3,10 @@ // #pragma once #include "omath/Vector3.hpp" -#include "omath/omath_export.hpp" namespace omath { - class OMATH_API Triangle3d final + class Triangle3d final { public: Triangle3d(const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3); diff --git a/include/omath/Vector2.hpp b/include/omath/Vector2.hpp index 7c49135c..db028cd2 100644 --- a/include/omath/Vector2.hpp +++ b/include/omath/Vector2.hpp @@ -5,11 +5,10 @@ #pragma once #include #include -#include "omath/omath_export.hpp" namespace omath { - class OMATH_API Vector2 + class Vector2 { public: float x = 0.f; diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index 9b80bc32..b24ad660 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -7,11 +7,10 @@ #include #include #include "omath/Vector2.hpp" -#include "omath/omath_export.hpp" namespace omath { - class OMATH_API Vector3 : public Vector2 + class Vector3 : public Vector2 { public: float z = 0.f; diff --git a/include/omath/Vector4.hpp b/include/omath/Vector4.hpp index 2a6fc32d..c70e2d3b 100644 --- a/include/omath/Vector4.hpp +++ b/include/omath/Vector4.hpp @@ -5,11 +5,10 @@ #include #include -#include "omath/omath_export.hpp" namespace omath { - class OMATH_API Vector4 : public Vector3 + class Vector4 : public Vector3 { public: float w; diff --git a/include/omath/ViewAngles.hpp b/include/omath/ViewAngles.hpp index 164feb40..d744f6ba 100644 --- a/include/omath/ViewAngles.hpp +++ b/include/omath/ViewAngles.hpp @@ -3,12 +3,10 @@ // #pragma once -#include "omath/omath_export.hpp" - namespace omath { template - struct OMATH_API ViewAngles + struct ViewAngles { PitchType pitch; YawType yaw; diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/LineTracer.hpp index d0675628..e86f1ddb 100644 --- a/include/omath/collision/LineTracer.hpp +++ b/include/omath/collision/LineTracer.hpp @@ -5,11 +5,10 @@ #include "omath/Vector3.hpp" #include "omath/Triangle3d.hpp" -#include "omath/omath_export.hpp" namespace omath::collision { - class OMATH_API Ray + class Ray { public: Vector3 start; @@ -21,7 +20,7 @@ namespace omath::collision [[nodiscard]] Vector3 DirectionVectorNormalized() const; }; - class OMATH_API LineTracer + class LineTracer { public: LineTracer() = delete; diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/Source/Camera.hpp index 1250d960..7298cfef 100644 --- a/include/omath/engines/Source/Camera.hpp +++ b/include/omath/engines/Source/Camera.hpp @@ -4,11 +4,10 @@ #pragma once #include "Constants.h" #include "omath/projection/Camera.hpp" -#include "omath/omath_export.hpp" namespace omath::source { - class OMATH_API Camera final : public projection::Camera + class Camera final : public projection::Camera { public: Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, diff --git a/include/omath/pathfinding/Astar.hpp b/include/omath/pathfinding/Astar.hpp index 435bd5d6..921375ca 100644 --- a/include/omath/pathfinding/Astar.hpp +++ b/include/omath/pathfinding/Astar.hpp @@ -6,11 +6,10 @@ #include #include "NavigationMesh.hpp" #include "omath/Vector3.hpp" -#include "omath/omath_export.hpp" namespace omath::pathfinding { - class OMATH_API Astar final + class Astar final { public: [[nodiscard]] diff --git a/include/omath/pathfinding/NavigationMesh.hpp b/include/omath/pathfinding/NavigationMesh.hpp index 8ab5c24b..5fc34d2c 100644 --- a/include/omath/pathfinding/NavigationMesh.hpp +++ b/include/omath/pathfinding/NavigationMesh.hpp @@ -8,7 +8,6 @@ #include #include #include -#include "omath/omath_export.hpp" namespace omath::pathfinding { @@ -23,16 +22,16 @@ namespace omath::pathfinding public: [[nodiscard]] - OMATH_API std::expected GetClosestVertex(const Vector3& point) const; + std::expected GetClosestVertex(const Vector3& point) const; [[nodiscard]] - OMATH_API const std::vector& GetNeighbors(const Vector3& vertex) const; + const std::vector& GetNeighbors(const Vector3& vertex) const; [[nodiscard]] - OMATH_API bool Empty() const; + bool Empty() const; [[nodiscard]] std::vector Serialize() const; - OMATH_API void Deserialize(const std::vector& raw); + void Deserialize(const std::vector& raw); std::unordered_map> m_verTextMap; }; diff --git a/include/omath/prediction/Engine.hpp b/include/omath/prediction/Engine.hpp index c48456c7..2571cc51 100644 --- a/include/omath/prediction/Engine.hpp +++ b/include/omath/prediction/Engine.hpp @@ -8,11 +8,10 @@ #include "omath/Vector3.hpp" #include "omath/prediction/Projectile.hpp" #include "omath/prediction/Target.hpp" -#include "omath/omath_export.hpp" namespace omath::prediction { - class OMATH_API Engine final + class Engine final { public: explicit Engine(float gravityConstant, float simulationTimeStep, diff --git a/include/omath/prediction/Projectile.hpp b/include/omath/prediction/Projectile.hpp index 51f6ebce..54998156 100644 --- a/include/omath/prediction/Projectile.hpp +++ b/include/omath/prediction/Projectile.hpp @@ -4,11 +4,10 @@ #pragma once #include "omath/Vector3.hpp" -#include "omath/omath_export.hpp" namespace omath::prediction { - class OMATH_API Projectile final + class Projectile final { public: diff --git a/include/omath/prediction/Target.hpp b/include/omath/prediction/Target.hpp index 083d3bd4..f3a775e4 100644 --- a/include/omath/prediction/Target.hpp +++ b/include/omath/prediction/Target.hpp @@ -4,11 +4,10 @@ #pragma once #include "omath/Vector3.hpp" -#include "omath/omath_export.hpp" namespace omath::prediction { - class OMATH_API Target final + class Target final { public: diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index 59f6fe8f..1b93b4a7 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -10,11 +10,10 @@ #include "ErrorCodes.hpp" #include #include -#include "omath/omath_export.hpp" namespace omath::projection { - class OMATH_API ViewPort final + class ViewPort final { public: float m_width; @@ -28,7 +27,7 @@ namespace omath::projection using FieldOfView = const Angle; template - class OMATH_API Camera + class Camera { public: From 9ad8ec755b093db7629484b0b364906448916460 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 23 Dec 2024 13:52:12 +0300 Subject: [PATCH 154/795] removed header --- include/omath/omath_export.hpp | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 include/omath/omath_export.hpp diff --git a/include/omath/omath_export.hpp b/include/omath/omath_export.hpp deleted file mode 100644 index b0949e76..00000000 --- a/include/omath/omath_export.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/* Export prefix for functions */ -#ifdef _MSC_VER - /* MSVC */ -# define OMATH_API_EXPORT __declspec(dllexport) -#else - /* GCC/Clang */ -# define OMATH_API_EXPORT __attribute__((visibility("default"))) -#endif - -/* Import prefix for functions */ -#ifdef _MSC_VER -# define OMATH_API_IMPORT __declspec(dllimport) -#else -# define OMATH_API_IMPORT extern -#endif - -/* Resolve import/export */ -#ifdef OMATH_EXPORT -# define OMATH_API OMATH_API_EXPORT -#else -# define OMATH_API OMATH_API_IMPORT -#endif From af880be056c45efde0ae6bdcdf2dca2391a9b9c3 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 23 Dec 2024 15:34:38 +0300 Subject: [PATCH 155/795] added methods --- include/omath/engines/Source/Camera.hpp | 4 +- include/omath/projection/Camera.hpp | 79 ++++++++++++++++++++++--- source/engines/Source/Camera.cpp | 6 +- tests/engines/UnitTestSourceEngine.cpp | 27 +++++++-- tests/general/UnitTestProjection.cpp | 4 +- 5 files changed, 102 insertions(+), 18 deletions(-) diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/Source/Camera.hpp index 7298cfef..53641f25 100644 --- a/include/omath/engines/Source/Camera.hpp +++ b/include/omath/engines/Source/Camera.hpp @@ -13,7 +13,7 @@ namespace omath::source Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, const Angle& fov, float near, float far); void LookAt(const Vector3& target) override; - [[nodiscard]] Mat4x4 GetViewMatrix() const override; - [[nodiscard]] Mat4x4 GetProjectionMatrix() const override; + [[nodiscard]] Mat4x4 CalcViewMatrix() const override; + [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; }; } \ No newline at end of file diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index 1b93b4a7..381aee66 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -29,7 +29,6 @@ namespace omath::projection template class Camera { - public: virtual ~Camera() = default; Camera(const Vector3& position, const ViewAnglesType& viewAngles, const ViewPort& viewPort, @@ -42,18 +41,82 @@ namespace omath::projection virtual void LookAt(const Vector3& target) = 0; - [[nodiscard]] virtual Mat4x4Type GetViewMatrix() const = 0; + [[nodiscard]] virtual Mat4x4Type CalcViewMatrix() const = 0; + + [[nodiscard]] virtual Mat4x4Type CalcProjectionMatrix() const = 0; + + [[nodiscard]] Mat4x4Type CalcViewProjectionMatrix() const + { + return CalcProjectionMatrix() * CalcViewMatrix(); + } + + void SetFieldOfView(const FieldOfView& fov) + { + m_fieldOfView = fov; + m_viewProjectionMatrix = CalcViewProjectionMatrix(); + } + + void SetNearPlane(const float near) + { + m_nearPlaneDistance = near; + m_viewProjectionMatrix = CalcViewProjectionMatrix(); + } + + void SetFarPlane(const float far) + { + m_farPlaneDistance = far; + m_viewProjectionMatrix = CalcViewProjectionMatrix(); + } + + void SetViewAngles(const ViewAnglesType& viewAngles) + { + m_viewAngles = viewAngles; + m_viewProjectionMatrix = CalcViewProjectionMatrix(); + } + + void SetOrigin(const Vector3& origin) + { + m_origin = origin; + m_viewProjectionMatrix = CalcViewProjectionMatrix(); + } + + void SetViewPort(const ViewPort& viewPort) + { + m_viewPort = viewPort; + m_viewProjectionMatrix = CalcViewProjectionMatrix(); + } + + [[nodiscard]] const FieldOfView& GetFieldOfView() const + { + return m_fieldOfView; + } + + [[nodiscard]] const float& GetNearPlane() const + { + return m_nearPlaneDistance; + } - [[nodiscard]] virtual Mat4x4Type GetProjectionMatrix() const = 0; + [[nodiscard]] const float& GetFarPlane() const + { + return m_farPlaneDistance; + } - [[nodiscard]] Mat4x4Type GetViewProjectionMatrix() + [[nodiscard]] const ViewAnglesType& GetViewAngles() const { - return GetProjectionMatrix() * GetViewMatrix(); + return m_viewAngles; } - [[nodiscard]] std::expected WorldToScreen(const Mat4x4Type& viewProj, const Vector3& worldPosition) const + [[nodiscard]] const Vector3& GetOrigin() const { - auto projected = viewProj * MatColumnFromVector(worldPosition); + return m_origin; + } + + [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const + { + if (!m_viewProjectionMatrix.has_value()) + m_viewProjectionMatrix = CalcViewProjectionMatrix(); + + auto projected = m_viewProjectionMatrix.value() * MatColumnFromVector(worldPosition); if (projected.At(3, 0) == 0.0f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); @@ -70,6 +133,8 @@ namespace omath::projection ViewPort m_viewPort{}; Angle m_fieldOfView; + mutable std::optional m_viewProjectionMatrix; + float m_farPlaneDistance; float m_nearPlaneDistance; diff --git a/source/engines/Source/Camera.cpp b/source/engines/Source/Camera.cpp index 51eb5073..370e2bd7 100644 --- a/source/engines/Source/Camera.cpp +++ b/source/engines/Source/Camera.cpp @@ -24,12 +24,12 @@ namespace omath::source m_viewAngles.roll = RollAngle::FromRadians(0.f); } - Mat4x4 Camera::GetViewMatrix() const + Mat4x4 Camera::CalcViewMatrix() const { - return CalcViewMatrix(m_viewAngles, m_origin); + return source::CalcViewMatrix(m_viewAngles, m_origin); } - Mat4x4 Camera::GetProjectionMatrix() const + Mat4x4 Camera::CalcProjectionMatrix() const { return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, m_farPlaneDistance); } diff --git a/tests/engines/UnitTestSourceEngine.cpp b/tests/engines/UnitTestSourceEngine.cpp index 4d641a1f..2313ae3f 100644 --- a/tests/engines/UnitTestSourceEngine.cpp +++ b/tests/engines/UnitTestSourceEngine.cpp @@ -27,17 +27,15 @@ TEST(UnitTestSourceEngine, UpVector) EXPECT_EQ(up, omath::source::kAbsUp); } -TEST(UnitTestSourceEngine, PerpectiveProjectionAtCenter) +TEST(UnitTestSourceEngine, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); auto cam = omath::source::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); - const auto viewProjMatrix = cam.GetViewProjectionMatrix(); - for (float distance = 0.02f; distance < 1000.f; distance += 0.01f) { - const auto projected = cam.WorldToScreen(viewProjMatrix, {distance, 0, 0}); + const auto projected = cam.WorldToScreen({distance, 0, 0}); EXPECT_TRUE(projected.has_value()); @@ -47,4 +45,25 @@ TEST(UnitTestSourceEngine, PerpectiveProjectionAtCenter) EXPECT_NEAR(projected->x, 960, 0.00001f); EXPECT_NEAR(projected->y, 540, 0.00001f); } +} + +TEST(UnitTestSourceEngine, CameraSetAndGetFov) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + auto cam = omath::source::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); + cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); +} + +TEST(UnitTestSourceEngine, CameraSetAndGetOrigin) +{ + auto cam = omath::source::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); + + EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); + cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); } \ No newline at end of file diff --git a/tests/general/UnitTestProjection.cpp b/tests/general/UnitTestProjection.cpp index ab8b71cb..3d9b507b 100644 --- a/tests/general/UnitTestProjection.cpp +++ b/tests/general/UnitTestProjection.cpp @@ -10,9 +10,9 @@ TEST(UnitTestProjection, Projection) { - auto x = omath::Angle::FromDegrees(90.f); + const auto x = omath::Angle::FromDegrees(90.f); auto cam = omath::source::Camera({0, 0, 0}, omath::source::ViewAngles{}, {1920.f, 1080.f}, x, 0.01f, 1000.f); - const auto projected = cam.WorldToScreen(cam.GetViewProjectionMatrix(), {1000, 0, 50}); + const auto projected = cam.WorldToScreen({1000, 0, 50}); std::print("{} {} {}", projected->x, projected->y, projected->z); } \ No newline at end of file From 931937d010ede8eb4dafb4517eb6c9b4801379cf Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 23 Dec 2024 17:53:47 +0300 Subject: [PATCH 156/795] added open gl stuff --- CMakeLists.txt | 5 +- include/omath/engines/OpenGL/Camera.hpp | 19 +++++ include/omath/engines/OpenGL/Constants.hpp | 25 ++++++ include/omath/engines/OpenGL/Formulas.hpp | 54 +++++++++++++ include/omath/engines/Source/Camera.hpp | 2 +- .../Source/{Constants.h => Constants.hpp} | 0 include/omath/engines/Source/Formulas.hpp | 2 +- include/omath/projection/Camera.hpp | 5 +- source/engines/CMakeLists.txt | 3 +- source/engines/OpenGL/CMakeLists.txt | 1 + source/engines/OpenGL/Camera.cpp | 35 +++++++++ source/engines/Source/Camera.cpp | 5 +- tests/engines/UnitTestOpenGL.cpp | 78 +++++++++++++------ tests/engines/UnitTestSourceEngine.cpp | 2 +- 14 files changed, 205 insertions(+), 31 deletions(-) create mode 100644 include/omath/engines/OpenGL/Camera.hpp create mode 100644 include/omath/engines/OpenGL/Constants.hpp create mode 100644 include/omath/engines/OpenGL/Formulas.hpp rename include/omath/engines/Source/{Constants.h => Constants.hpp} (100%) create mode 100644 source/engines/OpenGL/CMakeLists.txt create mode 100644 source/engines/OpenGL/Camera.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9af21107..08132d17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,10 @@ option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Vector3.cpp) else() - add_library(omath STATIC source/Vector3.cpp) + add_library(omath STATIC source/Vector3.cpp + include/omath/engines/OpenGL/Constants.hpp + include/omath/engines/OpenGL/Formulas.hpp + include/omath/engines/OpenGL/Camera.hpp) endif() target_compile_definitions(omath PUBLIC OMATH_EXPORT) diff --git a/include/omath/engines/OpenGL/Camera.hpp b/include/omath/engines/OpenGL/Camera.hpp new file mode 100644 index 00000000..0ad97291 --- /dev/null +++ b/include/omath/engines/OpenGL/Camera.hpp @@ -0,0 +1,19 @@ +// +// Created by Orange on 12/23/2024. +// +#pragma once +#include "Constants.hpp" +#include "omath/projection/Camera.hpp" + +namespace omath::opengl +{ + class Camera final : public projection::Camera + { + public: + Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + const Angle& fov, float near, float far); + void LookAt(const Vector3& target) override; + [[nodiscard]] Mat4x4 CalcViewMatrix() const override; + [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; + }; +} \ No newline at end of file diff --git a/include/omath/engines/OpenGL/Constants.hpp b/include/omath/engines/OpenGL/Constants.hpp new file mode 100644 index 00000000..a8912a21 --- /dev/null +++ b/include/omath/engines/OpenGL/Constants.hpp @@ -0,0 +1,25 @@ +// +// Created by Orange on 12/23/2024. +// +#pragma once + +#include +#include +#include +#include + +namespace omath::opengl +{ + constexpr Vector3 kAbsUp = {0, 1, 0}; + constexpr Vector3 kAbsRight = {1, 0, 0}; + constexpr Vector3 kAbsForward = {0, 0, -1}; + + using Mat4x4 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>; + using Mat3x3 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>; + using Mat1x3 = Mat<1, 3, float, MatStoreType::COLUMN_MAJOR>; + using PitchAngle = Angle; + using YawAngle = Angle; + using RollAngle = Angle; + + using ViewAngles = omath::ViewAngles; +} \ No newline at end of file diff --git a/include/omath/engines/OpenGL/Formulas.hpp b/include/omath/engines/OpenGL/Formulas.hpp new file mode 100644 index 00000000..8e6b2edb --- /dev/null +++ b/include/omath/engines/OpenGL/Formulas.hpp @@ -0,0 +1,54 @@ +// +// Created by Orange on 12/23/2024. +// +#pragma once +#include "Constants.hpp" + + +namespace omath::opengl +{ + [[nodiscard]] + inline Vector3 ForwardVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + + [[nodiscard]] + inline Vector3 RightVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + + [[nodiscard]] + inline Vector3 UpVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + + [[nodiscard]] inline Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + { + return MatCameraView(-ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + } + + + [[nodiscard]] + inline Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, + const float far) + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + return { + {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, 1.f / (fovHalfTan), 0, 0}, + {0, 0, -(far + near) / (far - near), -(2.f * far * near) / (far - near)}, + {0, 0, -1, 0}, + + }; + } +} \ No newline at end of file diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/Source/Camera.hpp index 53641f25..739aa861 100644 --- a/include/omath/engines/Source/Camera.hpp +++ b/include/omath/engines/Source/Camera.hpp @@ -2,7 +2,7 @@ // Created by Orange on 12/4/2024. // #pragma once -#include "Constants.h" +#include "Constants.hpp" #include "omath/projection/Camera.hpp" namespace omath::source diff --git a/include/omath/engines/Source/Constants.h b/include/omath/engines/Source/Constants.hpp similarity index 100% rename from include/omath/engines/Source/Constants.h rename to include/omath/engines/Source/Constants.hpp diff --git a/include/omath/engines/Source/Formulas.hpp b/include/omath/engines/Source/Formulas.hpp index 3c8a6d70..0b8ff039 100644 --- a/include/omath/engines/Source/Formulas.hpp +++ b/include/omath/engines/Source/Formulas.hpp @@ -2,7 +2,7 @@ // Created by Orange on 12/4/2024. // #pragma once -#include "Constants.h" +#include "Constants.hpp" namespace omath::source { diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index 381aee66..95f4eb0f 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -126,7 +126,10 @@ namespace omath::projection if (IsNdcOutOfBounds(projected)) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); - return Vector3{(projected.At(0,0)+1) / 2 * m_viewPort.m_width , (-projected.At(1,0)+1) / 2 * m_viewPort.m_height, projected.At(2,0)}; + const auto screenPositionX = (projected.At(0,0)+1.f) / 2.f * m_viewPort.m_width; + const auto screenPositionY = (-projected.At(1,0)+1) / 2.f * m_viewPort.m_height; + + return Vector3{screenPositionX, screenPositionY, projected.At(2,0)}; } protected: diff --git a/source/engines/CMakeLists.txt b/source/engines/CMakeLists.txt index f01db76d..2d2a3c9e 100644 --- a/source/engines/CMakeLists.txt +++ b/source/engines/CMakeLists.txt @@ -1 +1,2 @@ -add_subdirectory(Source) \ No newline at end of file +add_subdirectory(Source) +add_subdirectory(OpenGL) \ No newline at end of file diff --git a/source/engines/OpenGL/CMakeLists.txt b/source/engines/OpenGL/CMakeLists.txt new file mode 100644 index 00000000..0abf8686 --- /dev/null +++ b/source/engines/OpenGL/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(omath PRIVATE Camera.cpp) \ No newline at end of file diff --git a/source/engines/OpenGL/Camera.cpp b/source/engines/OpenGL/Camera.cpp new file mode 100644 index 00000000..9ad5a479 --- /dev/null +++ b/source/engines/OpenGL/Camera.cpp @@ -0,0 +1,35 @@ +// +// Created by Orange on 12/23/2024. +// +#include "omath/engines/OpenGL/Camera.hpp" +#include "omath/engines/OpenGL/Formulas.hpp" + + +namespace omath::opengl +{ + + Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + const Angle& fov, const float near, const float far) : + projection::Camera(position, viewAngles, viewPort, fov, near, far) + { + } + void Camera::LookAt([[maybe_unused]] const Vector3& target) + { + const float distance = m_origin.DistTo(target); + const auto delta = target - m_origin; + + + m_viewAngles.pitch = PitchAngle::FromRadians(std::asin(delta.z / distance)); + m_viewAngles.yaw = -YawAngle::FromRadians(std::atan2(delta.y, delta.x)); + m_viewAngles.roll = RollAngle::FromRadians(0.f); + } + Mat4x4 Camera::CalcViewMatrix() const + { + return opengl::CalcViewMatrix(m_viewAngles, m_origin); + } + Mat4x4 Camera::CalcProjectionMatrix() const + { + return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, + m_farPlaneDistance); + } +} // namespace omath::opengl diff --git a/source/engines/Source/Camera.cpp b/source/engines/Source/Camera.cpp index 370e2bd7..444e208a 100644 --- a/source/engines/Source/Camera.cpp +++ b/source/engines/Source/Camera.cpp @@ -26,11 +26,12 @@ namespace omath::source Mat4x4 Camera::CalcViewMatrix() const { - return source::CalcViewMatrix(m_viewAngles, m_origin); + return source::CalcViewMatrix(m_viewAngles, m_origin); } Mat4x4 Camera::CalcProjectionMatrix() const { - return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, m_farPlaneDistance); + return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, + m_farPlaneDistance); } } // namespace omath::source diff --git a/tests/engines/UnitTestOpenGL.cpp b/tests/engines/UnitTestOpenGL.cpp index 46f5afc8..461d9d24 100644 --- a/tests/engines/UnitTestOpenGL.cpp +++ b/tests/engines/UnitTestOpenGL.cpp @@ -1,37 +1,69 @@ // // Created by Orange on 11/23/2024. // -#include #include -#include -#include +#include +#include +#include -// #include -// #include "glm/ext/matrix_clip_space.hpp" -// #include "glm/ext/matrix_transform.hpp" +TEST(UnitTestOpenGL, ForwardVector) +{ + const auto forward = omath::opengl::ForwardVector({}); + + EXPECT_EQ(forward, omath::opengl::kAbsForward); +} + +TEST(UnitTestOpenGL, RightVector) +{ + const auto right = omath::opengl::RightVector({}); + EXPECT_EQ(right, omath::opengl::kAbsRight); +} -TEST(UnitTestOpenGL, Projection) +TEST(UnitTestOpenGL, UpVector) { + const auto up = omath::opengl::UpVector({}); + EXPECT_EQ(up, omath::opengl::kAbsUp); +} - /*const auto proj_glm = glm::perspective(glm::radians(90.f), 16.f / 9.f, 0.1f, 1000.f); - // const auto proj_glm2 = glm::perspectiveLH_NO(glm::radians(90.f), 16.f / 9.f, 0.1f, 1000.f); - // const auto proj_omath = omath::Mat<4, 4, float, omath::MatStoreType::COLUMN_MAJOR>((const float*)&proj_glm); - // EXPECT_EQ(omath::opengl::PerspectiveProjectionMatrix(90, 16.f / 9.f, 0.1f, 1000.f), proj_omath); +TEST(UnitTestOpenGL, ProjectTargetMovedFromCamera) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + auto cam = omath::opengl::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); - glm::vec4 ndc_glm2 = proj_glm * glm::vec4(300.f, 0.f, -1000.f, 1.f); - ndc_glm2 /= ndc_glm2.w; - const omath::Mat<4, 1, float, omath::MatStoreType::COLUMN_MAJOR> cords_omath = + for (float distance = -10.f; distance > -1000.f; distance -= 0.01f) { - {0}, - {0}, - {-0.2f}, - {1} - }; - - //auto ndc_omath = proj_omath * cords_omath; - // ndc_omath /= ndc_omath.At(3, 0); - */ + const auto projected = cam.WorldToScreen({0, 0, distance}); + + EXPECT_TRUE(projected.has_value()); + + if (!projected.has_value()) + continue; + + EXPECT_NEAR(projected->x, 960, 0.00001f); + EXPECT_NEAR(projected->y, 540, 0.00001f); + } +} + +TEST(UnitTestOpenGL, CameraSetAndGetFov) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + auto cam = omath::opengl::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); + cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); +} + +TEST(UnitTestOpenGL, CameraSetAndGetOrigin) +{ + auto cam = omath::opengl::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); + + EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); + cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); } \ No newline at end of file diff --git a/tests/engines/UnitTestSourceEngine.cpp b/tests/engines/UnitTestSourceEngine.cpp index 2313ae3f..cb73a44d 100644 --- a/tests/engines/UnitTestSourceEngine.cpp +++ b/tests/engines/UnitTestSourceEngine.cpp @@ -3,7 +3,7 @@ // #include #include -#include +#include #include From b2db06a7395855947153e376dec58480f0568088 Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 26 Dec 2024 18:52:24 +0300 Subject: [PATCH 157/795] updated read me --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b18c9029..bd5de4ae 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. -## Features +## 👁‍🗨 Features - **Efficiency**: Optimized for performance, ensuring quick computations. - **Versatility**: Includes a wide array of mathematical functions and algorithms. - **Ease of Use**: Simplified interface for convenient integration into various projects. @@ -20,7 +20,7 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at - **Collision Detection**: Production ready code to handle collision detection by using simple interfaces. - **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution -## Getting Started +## ⏬ Getting Started ### Prerequisites - C++ Compiler - CMake (for building the project) @@ -54,7 +54,7 @@ For detailed commands on installing different versions and more information, ple cmake --build cmake-build/build/windows-release --target server -j 6 ``` Use **\-\** preset to build siutable version for yourself. Like **windows-release** or **linux-release**. -## Usage +## ❔ Usage Simple world to screen function ```c++ TEST(UnitTestProjection, IsPointOnScreen) @@ -76,11 +76,11 @@ With `omath/projection` module you can achieve simple ESP hack for powered by So -## Contributing +## 🫵🏻 Contributing Contributions to `omath` are welcome! Please read `CONTRIBUTING.md` for details on our code of conduct and the process for submitting pull requests. -## License +## 📜 License This project is licensed under the MIT - see the `LICENSE` file for details. -## Acknowledgments +## 💘 Acknowledgments - [All contributors](https://github.com/orange-cpp/omath/graphs/contributors) From dc0bca14c50a1b6adb72d9be5f1494d5cb85d4d9 Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 2 Jan 2025 12:52:34 +0300 Subject: [PATCH 158/795] added method + tests --- include/omath/Vector3.hpp | 20 ++++++++++++++++++++ tests/general/UnitTestVector3.cpp | 9 +++++++++ 2 files changed, 29 insertions(+) diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index b24ad660..7d3ff50d 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -7,9 +7,18 @@ #include #include #include "omath/Vector2.hpp" +#include "omath/Angle.hpp" +#include + namespace omath { + + enum class Vector3Error + { + IMPOSSIBLE_BETWEEN_ANGLE, + }; + class Vector3 : public Vector2 { public: @@ -208,6 +217,17 @@ namespace omath return Sum2D() + z; } + [[nodiscard]] std::expected, Vector3Error> + AngleBetween(const Vector3& other) const + { + const auto bottom = Length() * other.Length(); + + if (bottom == 0.f) + return std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE); + + return Angle::FromRadians(std::acos(Dot(other) / bottom)); + } + [[nodiscard]] constexpr float Sum2D() const { return Vector2::Sum(); diff --git a/tests/general/UnitTestVector3.cpp b/tests/general/UnitTestVector3.cpp index f08a35f8..14002d34 100644 --- a/tests/general/UnitTestVector3.cpp +++ b/tests/general/UnitTestVector3.cpp @@ -387,6 +387,15 @@ TEST_F(UnitTestVector3, AsTuple) EXPECT_FLOAT_EQ(std::get<2>(tuple), v1.z); } +// Test AsTuple method +TEST_F(UnitTestVector3, AngleBeatween) +{ + EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).AngleBetween({1, 0 ,0}).value().AsDegrees(), 90.0f); + EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).AngleBetween({0.0f, 0.0f, 1.0f}).value().AsDegrees(), 0.0f); + EXPECT_FALSE(Vector3(0.0f, 0.0f, 0.0f).AngleBetween({0.0f, 0.0f, 1.0f}).has_value()); +} + + // Static assertions (compile-time checks) static_assert(Vector3(1.0f, 2.0f, 3.0f).LengthSqr() == 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"); From 824b301d406e7dc940042f7dcabc572f6e66e9b6 Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 2 Jan 2025 18:40:19 +0300 Subject: [PATCH 159/795] small code improvement --- include/omath/Angle.hpp | 2 +- include/omath/Matrix.hpp | 5 +---- source/Matrix.cpp | 5 +++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index 4ae0dee2..e5af33f9 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -19,7 +19,7 @@ namespace omath class Angle { Type m_angle; - constexpr Angle(const Type& degrees) + constexpr explicit Angle(const Type& degrees) { if constexpr (flags == AngleFlags::Normalized) m_angle = angles::WrapAngle(degrees, min, max); diff --git a/include/omath/Matrix.hpp b/include/omath/Matrix.hpp index 6e7e4097..2f23e406 100644 --- a/include/omath/Matrix.hpp +++ b/include/omath/Matrix.hpp @@ -38,10 +38,7 @@ namespace omath [[nodiscard]] - float& operator[](size_t row, size_t column) - { - return At(row, column); - } + float& operator[](size_t row, size_t column); [[nodiscard]] size_t ColumnsCount() const noexcept; diff --git a/source/Matrix.cpp b/source/Matrix.cpp index b07dde40..f976f415 100644 --- a/source/Matrix.cpp +++ b/source/Matrix.cpp @@ -74,6 +74,11 @@ namespace omath { return m_rows; } + + float& Matrix::operator[](const size_t row, const size_t column) + { + return At(row, column); + } Matrix::Matrix(Matrix&& other) noexcept { From 9a4fb672895fe8bb861089e2730f59a299fdd31e Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 2 Jan 2025 20:32:25 +0300 Subject: [PATCH 160/795] hotfix --- include/omath/Angle.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index e5af33f9..4ae0dee2 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -19,7 +19,7 @@ namespace omath class Angle { Type m_angle; - constexpr explicit Angle(const Type& degrees) + constexpr Angle(const Type& degrees) { if constexpr (flags == AngleFlags::Normalized) m_angle = angles::WrapAngle(degrees, min, max); From 42c84f2523502d06e63cf558d1a7cff8f1fb6706 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 6 Jan 2025 04:30:03 +0300 Subject: [PATCH 161/795] added midpoint for triangle class --- include/omath/Triangle3d.hpp | 3 +++ source/Triangle3d.cpp | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/omath/Triangle3d.hpp b/include/omath/Triangle3d.hpp index 370aa957..de71a80e 100644 --- a/include/omath/Triangle3d.hpp +++ b/include/omath/Triangle3d.hpp @@ -29,5 +29,8 @@ namespace omath [[nodiscard]] Vector3 SideBVector() const; + + [[nodiscard]] + Vector3 MidPoint() const; }; } diff --git a/source/Triangle3d.cpp b/source/Triangle3d.cpp index 54dcf758..aacabc8b 100644 --- a/source/Triangle3d.cpp +++ b/source/Triangle3d.cpp @@ -33,4 +33,8 @@ namespace omath { return m_vertex3 - m_vertex2; } -} + Vector3 Triangle3d::MidPoint() const + { + return (m_vertex1 + m_vertex2 + m_vertex3) / 3; + } +} // namespace omath From 6a324e8c0e27ffcb445f75947ed46ebfe1a53c56 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 6 Jan 2025 04:42:43 +0300 Subject: [PATCH 162/795] improvement --- include/omath/Triangle.hpp | 59 ++++++++++++++++++++++++++ include/omath/Triangle3d.hpp | 36 ---------------- include/omath/collision/LineTracer.hpp | 6 +-- source/CMakeLists.txt | 1 - source/Triangle3d.cpp | 40 ----------------- source/collision/LineTracer.cpp | 8 ++-- tests/CMakeLists.txt | 1 + tests/general/UnitTestTriangle.cpp | 4 ++ 8 files changed, 71 insertions(+), 84 deletions(-) create mode 100644 include/omath/Triangle.hpp delete mode 100644 include/omath/Triangle3d.hpp delete mode 100644 source/Triangle3d.cpp create mode 100644 tests/general/UnitTestTriangle.cpp diff --git a/include/omath/Triangle.hpp b/include/omath/Triangle.hpp new file mode 100644 index 00000000..68a88943 --- /dev/null +++ b/include/omath/Triangle.hpp @@ -0,0 +1,59 @@ +// +// Created by Orange on 11/13/2024. +// +#pragma once +#include "omath/Vector3.hpp" + +namespace omath +{ + template + class Triangle final + { + public: + constexpr Triangle(const Vector& vertex1, const Vector& vertex2, const Vector& vertex3) + : m_vertex1(vertex1), m_vertex2(vertex2), m_vertex3(vertex3) + { + + } + + Vector3 m_vertex1; + Vector3 m_vertex2; + Vector3 m_vertex3; + + [[nodiscard]] + constexpr Vector3 CalculateNormal() const + { + return (m_vertex1 - m_vertex2).Cross(m_vertex3 - m_vertex1).Normalized(); + } + + [[nodiscard]] + constexpr float SideALength() const + { + return m_vertex1.DistTo(m_vertex2); + } + + [[nodiscard]] + constexpr float SideBLength() const + { + return m_vertex3.DistTo(m_vertex2); + } + + [[nodiscard]] + constexpr Vector3 SideAVector() const + { + return m_vertex1 - m_vertex2; + } + + [[nodiscard]] + constexpr Vector3 SideBVector() const + { + return m_vertex3 - m_vertex2; + } + + [[nodiscard]] + constexpr Vector3 MidPoint() const + { + return (m_vertex1 + m_vertex2 + m_vertex3) / 3; + } + }; +} // namespace omath diff --git a/include/omath/Triangle3d.hpp b/include/omath/Triangle3d.hpp deleted file mode 100644 index de71a80e..00000000 --- a/include/omath/Triangle3d.hpp +++ /dev/null @@ -1,36 +0,0 @@ -// -// Created by Orange on 11/13/2024. -// -#pragma once -#include "omath/Vector3.hpp" - -namespace omath -{ - class Triangle3d final - { - public: - Triangle3d(const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3); - - Vector3 m_vertex1; - Vector3 m_vertex2; - Vector3 m_vertex3; - - [[nodiscard]] - Vector3 CalculateNormal() const; - - [[nodiscard]] - float SideALength() const; - - [[nodiscard]] - float SideBLength() const; - - [[nodiscard]] - Vector3 SideAVector() const; - - [[nodiscard]] - Vector3 SideBVector() const; - - [[nodiscard]] - Vector3 MidPoint() const; - }; -} diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/LineTracer.hpp index e86f1ddb..33b81a24 100644 --- a/include/omath/collision/LineTracer.hpp +++ b/include/omath/collision/LineTracer.hpp @@ -4,7 +4,7 @@ #pragma once #include "omath/Vector3.hpp" -#include "omath/Triangle3d.hpp" +#include "omath/Triangle.hpp" namespace omath::collision { @@ -27,12 +27,12 @@ namespace omath::collision [[nodiscard]] - static bool CanTraceLine(const Ray& ray, const Triangle3d& triangle); + static bool CanTraceLine(const Ray& ray, const Triangle& triangle); // Realization of Möller–Trumbore intersection algorithm // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm [[nodiscard]] - static Vector3 GetRayHitPoint(const Ray& ray, const Triangle3d& triangle); + static Vector3 GetRayHitPoint(const Ray& ray, const Triangle& triangle); }; } diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 0e0ea741..fe42cba6 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -4,7 +4,6 @@ target_sources(omath PRIVATE color.cpp Vector4.cpp Vector2.cpp - Triangle3d.cpp ) add_subdirectory(prediction) diff --git a/source/Triangle3d.cpp b/source/Triangle3d.cpp deleted file mode 100644 index aacabc8b..00000000 --- a/source/Triangle3d.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "omath/Triangle3d.hpp" - - -namespace omath -{ - Triangle3d::Triangle3d(const Vector3 &vertex1, const Vector3 &vertex2, const Vector3 &vertex3) - : m_vertex1(vertex1), m_vertex2(vertex2), m_vertex3(vertex3) - { - - } - - Vector3 Triangle3d::CalculateNormal() const - { - return (m_vertex1 - m_vertex2).Cross(m_vertex3 - m_vertex1).Normalized(); - } - - float Triangle3d::SideALength() const - { - return m_vertex1.DistTo(m_vertex2); - } - - float Triangle3d::SideBLength() const - { - return m_vertex3.DistTo(m_vertex2); - } - - Vector3 Triangle3d::SideAVector() const - { - return m_vertex1 - m_vertex2; - } - - Vector3 Triangle3d::SideBVector() const - { - return m_vertex3 - m_vertex2; - } - Vector3 Triangle3d::MidPoint() const - { - return (m_vertex1 + m_vertex2 + m_vertex3) / 3; - } -} // namespace omath diff --git a/source/collision/LineTracer.cpp b/source/collision/LineTracer.cpp index 83bcdd2a..5f8c5b60 100644 --- a/source/collision/LineTracer.cpp +++ b/source/collision/LineTracer.cpp @@ -5,7 +5,7 @@ namespace omath::collision { - bool LineTracer::CanTraceLine(const Ray &ray, const Triangle3d &triangle) + bool LineTracer::CanTraceLine(const Ray& ray, const Triangle& triangle) { return GetRayHitPoint(ray, triangle) == ray.end; } @@ -19,7 +19,7 @@ namespace omath::collision return DirectionVector().Normalized(); } - Vector3 LineTracer::GetRayHitPoint(const Ray &ray, const Triangle3d &triangle) + Vector3 LineTracer::GetRayHitPoint(const Ray& ray, const Triangle& triangle) { constexpr float kEpsilon = std::numeric_limits::epsilon(); @@ -41,7 +41,7 @@ namespace omath::collision const auto u = t.Dot(p) * invDet; - if ((u < 0 && std::abs(u) > kEpsilon) || (u > 1 && std::abs(u-1) > kEpsilon)) + if ((u < 0 && std::abs(u) > kEpsilon) || (u > 1 && std::abs(u - 1) > kEpsilon)) return ray.end; const auto q = t.Cross(sideA); @@ -59,4 +59,4 @@ namespace omath::collision return ray.start + rayDir * tHit; } -} +} // namespace omath::collision diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 77c9508b..aa377b82 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(unit-tests general/UnitTestAngles.cpp general/UnitTestViewAngles.cpp general/UnitTestAngle.cpp + general/UnitTestTriangle.cpp engines/UnitTestOpenGL.cpp engines/UnitTestUnityEngine.cpp diff --git a/tests/general/UnitTestTriangle.cpp b/tests/general/UnitTestTriangle.cpp new file mode 100644 index 00000000..b7f486fc --- /dev/null +++ b/tests/general/UnitTestTriangle.cpp @@ -0,0 +1,4 @@ +// +// Created by Orange on 1/6/2025. +// +#include "omath/Triangle.hpp" From 29629a737d287a2700d039f787993b9e89fdb176 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 6 Jan 2025 05:08:32 +0300 Subject: [PATCH 163/795] added some methods --- include/omath/Triangle.hpp | 24 +++++- tests/general/UnitTestLineTrace.cpp | 6 +- tests/general/UnitTestTriangle.cpp | 129 ++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 7 deletions(-) diff --git a/include/omath/Triangle.hpp b/include/omath/Triangle.hpp index 68a88943..77c1532c 100644 --- a/include/omath/Triangle.hpp +++ b/include/omath/Triangle.hpp @@ -10,6 +10,7 @@ namespace omath class Triangle final { public: + constexpr Triangle() = default; constexpr Triangle(const Vector& vertex1, const Vector& vertex2, const Vector& vertex3) : m_vertex1(vertex1), m_vertex2(vertex2), m_vertex3(vertex3) { @@ -23,17 +24,19 @@ namespace omath [[nodiscard]] constexpr Vector3 CalculateNormal() const { - return (m_vertex1 - m_vertex2).Cross(m_vertex3 - m_vertex1).Normalized(); + const auto b = SideBVector(); + const auto a = SideAVector(); + return b.Cross(a).Normalized(); } [[nodiscard]] - constexpr float SideALength() const + float SideALength() const { return m_vertex1.DistTo(m_vertex2); } [[nodiscard]] - constexpr float SideBLength() const + float SideBLength() const { return m_vertex3.DistTo(m_vertex2); } @@ -44,12 +47,25 @@ namespace omath return m_vertex1 - m_vertex2; } + [[nodiscard]] + constexpr float Hypot() const + { + return m_vertex1.DistTo(m_vertex3); + } + [[nodiscard]] + constexpr bool IsRectangular() const + { + const auto sideA = SideALength(); + const auto sideB = SideBLength(); + const auto hypot = Hypot(); + + return sideA*sideA + sideB*sideB == hypot*hypot; + } [[nodiscard]] constexpr Vector3 SideBVector() const { return m_vertex3 - m_vertex2; } - [[nodiscard]] constexpr Vector3 MidPoint() const { diff --git a/tests/general/UnitTestLineTrace.cpp b/tests/general/UnitTestLineTrace.cpp index 67884d94..4e37916d 100644 --- a/tests/general/UnitTestLineTrace.cpp +++ b/tests/general/UnitTestLineTrace.cpp @@ -1,6 +1,6 @@ #include "gtest/gtest.h" #include "omath/collision/LineTracer.hpp" -#include "omath/Triangle3d.hpp" +#include "omath/Triangle.hpp" #include "omath/Vector3.hpp" using namespace omath; @@ -13,7 +13,7 @@ class LineTracerTest : public ::testing::Test Vector3 vertex1{0.0f, 0.0f, 0.0f}; Vector3 vertex2{1.0f, 0.0f, 0.0f}; Vector3 vertex3{0.0f, 1.0f, 0.0f}; - Triangle3d triangle{vertex1, vertex2, vertex3}; + Triangle triangle{vertex1, vertex2, vertex3}; }; // Test that a ray intersecting the triangle returns false for CanTraceLine @@ -71,7 +71,7 @@ TEST_F(LineTracerTest, TriangleFarBeyondRayEndPoint) constexpr Ray ray{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}; // Define a triangle far beyond the ray's endpoint - const Triangle3d distantTriangle{ + constexpr Triangle distantTriangle{ {1000.0f, 1000.0f, 1000.0f}, {1001.0f, 1000.0f, 1000.0f}, {1000.0f, 1001.0f, 1000.0f} }; diff --git a/tests/general/UnitTestTriangle.cpp b/tests/general/UnitTestTriangle.cpp index b7f486fc..8fc1aa60 100644 --- a/tests/general/UnitTestTriangle.cpp +++ b/tests/general/UnitTestTriangle.cpp @@ -2,3 +2,132 @@ // Created by Orange on 1/6/2025. // #include "omath/Triangle.hpp" +#include +#include +#include // For std::sqrt, std::isinf, std::isnan + + +using namespace omath; + +class UnitTestTriangle : public ::testing::Test +{ +protected: + // Define some Triangles to use in tests + Triangle t1; + Triangle t2; + Triangle t3; + + constexpr void SetUp() override + { + // Triangle with vertices (0, 0, 0), (1, 0, 0), (0, 1, 0) + t1 = Triangle( + Vector3(0.0f, 0.0f, 0.0f), + Vector3(1.0f, 0.0f, 0.0f), + Vector3(0.0f, 1.0f, 0.0f) + ); + + // Triangle with vertices (1, 2, 3), (4, 5, 6), (7, 8, 9) + t2 = Triangle( + Vector3(1.0f, 2.0f, 3.0f), + Vector3(4.0f, 5.0f, 6.0f), + Vector3(7.0f, 8.0f, 9.0f) + ); + + // An isosceles right triangle + t3 = Triangle( + Vector3(0.0f, 0.0f, 0.0f), + Vector3(2.0f, 0.0f, 0.0f), + Vector3(0.0f, 2.0f, 0.0f) + ); + } +}; + +// Test constructor and vertices +TEST_F(UnitTestTriangle, Constructor) +{ + constexpr Triangle t( + Vector3(1.0f, 2.0f, 3.0f), + Vector3(4.0f, 5.0f, 6.0f), + Vector3(7.0f, 8.0f, 9.0f) + ); + + EXPECT_FLOAT_EQ(t.m_vertex1.x, 1.0f); + EXPECT_FLOAT_EQ(t.m_vertex1.y, 2.0f); + EXPECT_FLOAT_EQ(t.m_vertex1.z, 3.0f); + + EXPECT_FLOAT_EQ(t.m_vertex2.x, 4.0f); + EXPECT_FLOAT_EQ(t.m_vertex2.y, 5.0f); + EXPECT_FLOAT_EQ(t.m_vertex2.z, 6.0f); + + EXPECT_FLOAT_EQ(t.m_vertex3.x, 7.0f); + EXPECT_FLOAT_EQ(t.m_vertex3.y, 8.0f); + EXPECT_FLOAT_EQ(t.m_vertex3.z, 9.0f); +} + +// Test CalculateNormal +TEST_F(UnitTestTriangle, CalculateNormal) +{ + // For t1, the normal should point in the +Z direction (0, 0, 1) or (0, 0, -1) + const Vector3 normal_t1 = t1.CalculateNormal(); + // Check if it's normalized and pointed along Z (sign can differ, so use absolute check) + EXPECT_NEAR(std::fabs(normal_t1.z), 1.0f, 1e-5f); + EXPECT_NEAR(normal_t1.Length(), 1.0f, 1e-5f); + + + // For t3, we expect the normal to be along +Z as well + const Vector3 normal_t3 = t3.CalculateNormal(); + EXPECT_NEAR(std::fabs(normal_t3.z), 1.0f, 1e-5f); +} + +// Test side lengths +TEST_F(UnitTestTriangle, SideLengths) +{ + // For t1 side lengths + EXPECT_FLOAT_EQ(t1.SideALength(), std::sqrt(1.0f)); // distance between (0,0,0) and (1,0,0) + EXPECT_FLOAT_EQ(t1.SideBLength(), std::sqrt(1.0f + 1.0f)); // distance between (4,5,6) & (7,8,9)... but we are testing t1, so let's be accurate: + // Actually, for t1: vertex2=(1,0,0), vertex3=(0,1,0) + // Dist between (0,1,0) and (1,0,0) = sqrt((1-0)^2 + (0-1)^2) = sqrt(1 + 1) = sqrt(2) + EXPECT_FLOAT_EQ(t1.SideBLength(), std::sqrt(2.0f)); + + // For t3, side a = distance between vertex1=(0,0,0) and vertex2=(2,0,0), which is 2 + // side b = distance between vertex3=(0,2,0) and vertex2=(2,0,0), which is sqrt(2^2 + (-2)^2)= sqrt(8)= 2.828... + // We'll just check side a first: + EXPECT_FLOAT_EQ(t3.SideALength(), 2.0f); + // Then side b: + EXPECT_FLOAT_EQ(t3.SideBLength(), std::sqrt(8.0f)); +} + +// Test side vectors +TEST_F(UnitTestTriangle, SideVectors) +{ + const Vector3 sideA_t1 = t1.SideAVector(); // m_vertex1 - m_vertex2 + EXPECT_FLOAT_EQ(sideA_t1.x, 0.0f - 1.0f); + EXPECT_FLOAT_EQ(sideA_t1.y, 0.0f - 0.0f); + EXPECT_FLOAT_EQ(sideA_t1.z, 0.0f - 0.0f); + + const Vector3 sideB_t1 = t1.SideBVector(); // m_vertex3 - m_vertex2 + EXPECT_FLOAT_EQ(sideB_t1.x, 0.0f - 1.0f); + EXPECT_FLOAT_EQ(sideB_t1.y, 1.0f - 0.0f); + EXPECT_FLOAT_EQ(sideB_t1.z, 0.0f - 0.0f); +} + +TEST_F(UnitTestTriangle, IsRectangular) +{ + EXPECT_TRUE(t1.IsRectangular()); + EXPECT_TRUE(t3.IsRectangular()); +} +// Test midpoint +TEST_F(UnitTestTriangle, MidPoint) +{ + // For t1, midpoint of (0,0,0), (1,0,0), (0,1,0) + const Vector3 mid1 = t1.MidPoint(); + EXPECT_FLOAT_EQ(mid1.x, (0.0f + 1.0f + 0.0f) / 3.0f); + EXPECT_FLOAT_EQ(mid1.y, (0.0f + 0.0f + 1.0f) / 3.0f); + EXPECT_FLOAT_EQ(mid1.z, 0.0f); + + // For t2, midpoint of (1,2,3), (4,5,6), (7,8,9) + const Vector3 mid2 = t2.MidPoint(); + EXPECT_FLOAT_EQ(mid2.x, (1.0f + 4.0f + 7.0f) / 3.0f); + 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); +} From 835fd110ba9a3d5b660548f01d22aac04a5e6b1a Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 6 Jan 2025 05:15:12 +0300 Subject: [PATCH 164/795] fix --- include/omath/Triangle.hpp | 2 +- tests/general/UnitTestTriangle.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/omath/Triangle.hpp b/include/omath/Triangle.hpp index 77c1532c..8107938e 100644 --- a/include/omath/Triangle.hpp +++ b/include/omath/Triangle.hpp @@ -59,7 +59,7 @@ namespace omath const auto sideB = SideBLength(); const auto hypot = Hypot(); - return sideA*sideA + sideB*sideB == hypot*hypot; + return std::abs(sideA*sideA + sideB*sideB - hypot*hypot) <= 0.0001f; } [[nodiscard]] constexpr Vector3 SideBVector() const diff --git a/tests/general/UnitTestTriangle.cpp b/tests/general/UnitTestTriangle.cpp index 8fc1aa60..258cb972 100644 --- a/tests/general/UnitTestTriangle.cpp +++ b/tests/general/UnitTestTriangle.cpp @@ -113,8 +113,7 @@ TEST_F(UnitTestTriangle, SideVectors) TEST_F(UnitTestTriangle, IsRectangular) { - EXPECT_TRUE(t1.IsRectangular()); - EXPECT_TRUE(t3.IsRectangular()); + EXPECT_TRUE(Triangle({2,0,0}, {}, {0,2,0}).IsRectangular()); } // Test midpoint TEST_F(UnitTestTriangle, MidPoint) From afcfed483439bd0ed64fd7f78a7589289ebbdb94 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 6 Jan 2025 05:17:42 +0300 Subject: [PATCH 165/795] path --- include/omath/Triangle.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/omath/Triangle.hpp b/include/omath/Triangle.hpp index 8107938e..e9e36655 100644 --- a/include/omath/Triangle.hpp +++ b/include/omath/Triangle.hpp @@ -14,7 +14,6 @@ namespace omath constexpr Triangle(const Vector& vertex1, const Vector& vertex2, const Vector& vertex3) : m_vertex1(vertex1), m_vertex2(vertex2), m_vertex3(vertex3) { - } Vector3 m_vertex1; From d0c532df39f460ac032cbcc881d6d6fc1a8f1670 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 6 Jan 2025 05:36:27 +0300 Subject: [PATCH 166/795] hot fix --- include/omath/projection/Camera.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index 95f4eb0f..e722d768 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -24,7 +24,7 @@ namespace omath::projection return m_width / m_height; } }; - using FieldOfView = const Angle; + using FieldOfView = Angle; template class Camera From e0dcb65e3f643e501650507683dbeaa1d65eb67e Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 17 Jan 2025 16:56:47 +0300 Subject: [PATCH 167/795] improved cmake+hotfix of clang support --- CMakeLists.txt | 8 ++++++-- source/engines/OpenGL/Camera.cpp | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 08132d17..76d43212 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,6 @@ project(omath VERSION 1.0.1) include(CMakePackageConfigHelpers) -set(CMAKE_CXX_STANDARD 26) - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") @@ -13,6 +11,7 @@ option(OMATH_BUILD_TESTS "Build unit tests" ON) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) + if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Vector3.cpp) else() @@ -22,6 +21,11 @@ else() include/omath/engines/OpenGL/Camera.hpp) endif() +set_target_properties(omath PROPERTIES + CXX_STANDARD 23 + CXX_STANDARD_REQUIRED ON) +target_compile_features(omath PUBLIC cxx_std_23) + target_compile_definitions(omath PUBLIC OMATH_EXPORT) add_subdirectory(source) diff --git a/source/engines/OpenGL/Camera.cpp b/source/engines/OpenGL/Camera.cpp index 9ad5a479..11dd30c3 100644 --- a/source/engines/OpenGL/Camera.cpp +++ b/source/engines/OpenGL/Camera.cpp @@ -9,7 +9,7 @@ namespace omath::opengl { Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, - const Angle& fov, const float near, const float far) : + const Angle& fov, const float near, const float far) : projection::Camera(position, viewAngles, viewPort, fov, near, far) { } From 96e4e1c9d66818987bcd62e4f986aad23a1f6610 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 16 Feb 2025 10:06:04 +0300 Subject: [PATCH 168/795] added new method added concept for mat type --- include/omath/Mat.hpp | 7 +++++++ include/omath/Vector3.hpp | 11 ++++++++++- tests/general/UnitTestVector3.cpp | 6 ++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 382d900a..3a23fbce 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -22,6 +22,13 @@ namespace omath COLUMN_MAJOR }; + + template + concept MatTemplateEqual = + (M1::rows == M2::rows) && (M1::columns == M2::columns) && + std::is_same_v && + (M1::store_type == M2::store_type); + template requires std::is_arithmetic_v class Mat final diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index 7d3ff50d..88dce1c7 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -9,6 +9,7 @@ #include "omath/Vector2.hpp" #include "omath/Angle.hpp" #include +#include namespace omath @@ -228,6 +229,14 @@ namespace omath return Angle::FromRadians(std::acos(Dot(other) / bottom)); } + [[nodiscard]] bool IsPerpendicular(const Vector3& other) const + { + if (const auto angle = AngleBetween(other)) + return angle->AsDegrees() == 90.f; + + return false; + } + [[nodiscard]] constexpr float Sum2D() const { return Vector2::Sum(); @@ -235,7 +244,7 @@ namespace omath [[nodiscard]] Vector3 ViewAngleTo(const Vector3& other) const; - [[nodiscard]] std::tuple AsTuple() const + [[nodiscard]] constexpr std::tuple AsTuple() const { return std::make_tuple(x, y, z); } diff --git a/tests/general/UnitTestVector3.cpp b/tests/general/UnitTestVector3.cpp index 14002d34..d7560a65 100644 --- a/tests/general/UnitTestVector3.cpp +++ b/tests/general/UnitTestVector3.cpp @@ -395,6 +395,12 @@ TEST_F(UnitTestVector3, AngleBeatween) EXPECT_FALSE(Vector3(0.0f, 0.0f, 0.0f).AngleBetween({0.0f, 0.0f, 1.0f}).has_value()); } +TEST_F(UnitTestVector3, IsPerpendicular) +{ + EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).IsPerpendicular({1, 0 ,0}), true); + EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).IsPerpendicular({0.0f, 0.0f, 1.0f}), false); + EXPECT_FALSE(Vector3(0.0f, 0.0f, 0.0f).IsPerpendicular({0.0f, 0.0f, 1.0f})); +} // Static assertions (compile-time checks) static_assert(Vector3(1.0f, 2.0f, 3.0f).LengthSqr() == 14.0f, "LengthSqr should be 14"); From 906f5099d1add18f119758fc719cd67412b59ad5 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 16 Feb 2025 10:57:03 +0300 Subject: [PATCH 169/795] improvement --- include/omath/pathfinding/Astar.hpp | 6 +++ source/pathfinding/Astar.cpp | 84 +++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 23 deletions(-) diff --git a/include/omath/pathfinding/Astar.hpp b/include/omath/pathfinding/Astar.hpp index 921375ca..31aa127b 100644 --- a/include/omath/pathfinding/Astar.hpp +++ b/include/omath/pathfinding/Astar.hpp @@ -9,11 +9,17 @@ namespace omath::pathfinding { + struct PathNode; class Astar final { public: [[nodiscard]] static std::vector FindPath(const Vector3& start, const Vector3& end, const NavigationMesh& navMesh); + private: + [[nodiscard]] + static std::vector ReconstructFinalPath(const std::unordered_map& closedList, const Vector3& current); + [[nodiscard]] + static auto GetPerfectNode(const std::unordered_map& openList, const Vector3& endVertex); }; } \ No newline at end of file diff --git a/source/pathfinding/Astar.cpp b/source/pathfinding/Astar.cpp index 37ec8e10..1ea69a6f 100644 --- a/source/pathfinding/Astar.cpp +++ b/source/pathfinding/Astar.cpp @@ -3,10 +3,10 @@ // #include "omath/pathfinding/Astar.hpp" +#include #include #include #include -#include namespace omath::pathfinding @@ -18,45 +18,83 @@ namespace omath::pathfinding }; - std::vector Astar::FindPath(const Vector3 &start, const Vector3 &end, const NavigationMesh &navMesh) + std::vector Astar::ReconstructFinalPath(const std::unordered_map& closedList, + const Vector3& current) + { + std::vector path; + std::optional currentOpt = current; + + while (currentOpt) + { + path.push_back(*currentOpt); + + auto it = closedList.find(*currentOpt); + + if (it == closedList.end()) + break; + + currentOpt = it->second.cameFrom; + } + + std::ranges::reverse(path); + return path; + } + auto Astar::GetPerfectNode(const std::unordered_map& openList, const Vector3& endVertex) + { + return std::ranges::min_element(openList, + [&endVertex](const auto& a, const auto& b) + { + const float fA = a.second.gCost + a.first.DistTo(endVertex); + const float fB = b.second.gCost + b.first.DistTo(endVertex); + return fA < fB; + }); + } + + std::vector Astar::FindPath(const Vector3& start, const Vector3& end, const NavigationMesh& navMesh) { std::unordered_map closedList; std::unordered_map openList; - const auto startVertex = navMesh.GetClosestVertex(start).value(); - const auto endVertex = navMesh.GetClosestVertex(end).value(); + auto maybeStartVertex = navMesh.GetClosestVertex(start); + auto maybeEndVertex = navMesh.GetClosestVertex(end); + + if (!maybeStartVertex || !maybeEndVertex) + return {}; + + const auto startVertex = maybeStartVertex.value(); + const auto endVertex = maybeEndVertex.value(); + openList.emplace(startVertex, PathNode{std::nullopt, 0.f}); while (!openList.empty()) { - const auto perfectVertex = *std::ranges::min_element(openList, - [&endVertex](const auto& a, const auto& b) -> bool - { - const auto aCost = a.second.gCost + a.first.DistTo(endVertex); - const auto bCost = b.second.gCost + b.first.DistTo(endVertex); - return aCost < bCost; - }); + auto currentIt = GetPerfectNode(openList, endVertex); + + const auto current = currentIt->first; + const auto currentNode = currentIt->second; - closedList.emplace(perfectVertex); - openList.erase(perfectVertex.first); + if (current == endVertex) + return ReconstructFinalPath(closedList, current); - for (const auto& neighbor : navMesh.GetNeighbors(perfectVertex.first)) - if (!closedList.contains(neighbor)) - openList.emplace(neighbor, PathNode{perfectVertex.first, neighbor.DistTo(perfectVertex.first) + perfectVertex.second.gCost}); + closedList.emplace(current, currentNode); + openList.erase(currentIt); - if (perfectVertex.first != endVertex) - continue; + for (const auto& neighbor: navMesh.GetNeighbors(current)) + { + if (closedList.contains(neighbor)) + continue; - std::vector path = {}; + const float tentativeGCost = currentNode.gCost + neighbor.DistTo(current); - for (std::optional current = perfectVertex.first; current; current = closedList.at(*current).cameFrom ) - path.push_back(current.value()); + const auto openIt = openList.find(neighbor); - return path; + if (openIt == openList.end() || tentativeGCost < openIt->second.gCost) + openList[neighbor] = PathNode{current, tentativeGCost}; + } } return {}; } -} +} // namespace omath::pathfinding From e31ffac103a55dc182e69e6671b1c6e3546b25f9 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 22 Feb 2025 23:32:29 +0300 Subject: [PATCH 170/795] added unity build support --- CMakeLists.txt | 8 ++++++-- tests/CMakeLists.txt | 10 +++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76d43212..810cac1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,6 @@ project(omath VERSION 1.0.1) include(CMakePackageConfigHelpers) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") option(OMATH_BUILD_TESTS "Build unit tests" ON) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) @@ -22,8 +20,14 @@ else() endif() set_target_properties(omath PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + UNITY_BUILD ON + UNITY_BUILD_BATCH_SIZE 20 CXX_STANDARD 23 CXX_STANDARD_REQUIRED ON) + + target_compile_features(omath PUBLIC cxx_std_23) target_compile_definitions(omath PUBLIC OMATH_EXPORT) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aa377b82..55b9d831 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,6 @@ enable_testing() project(unit-tests) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") include(GoogleTest) add_executable(unit-tests @@ -26,6 +25,15 @@ add_executable(unit-tests ) +set_target_properties(unit-tests PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + UNITY_BUILD ON + UNITY_BUILD_BATCH_SIZE 20 + CXX_STANDARD 23 + CXX_STANDARD_REQUIRED ON) + + target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) gtest_discover_tests(unit-tests) \ No newline at end of file From 5639cd0eb5b222e9f50994ba0f54fb336eb2149d Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 22 Feb 2025 22:57:29 +0300 Subject: [PATCH 171/795] added AVX2 --- include/omath/prediction/Engine.hpp | 2 + source/prediction/Engine.cpp | 158 +++++++++++++++++++-------- tests/general/UnitTestPrediction.cpp | 4 +- 3 files changed, 116 insertions(+), 48 deletions(-) diff --git a/include/omath/prediction/Engine.hpp b/include/omath/prediction/Engine.hpp index 2571cc51..e2e5034b 100644 --- a/include/omath/prediction/Engine.hpp +++ b/include/omath/prediction/Engine.hpp @@ -31,6 +31,8 @@ namespace omath::prediction const Vector3& targetPosition) const; + [[nodiscard]] static std::optional CalculatePitch(const Vector3 &projOrigin, const Vector3 &targetPos, + float bulletGravity, float v0, float time) ; [[nodiscard]] bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, float time) const; diff --git a/source/prediction/Engine.cpp b/source/prediction/Engine.cpp index 4452a525..c5ce26a9 100644 --- a/source/prediction/Engine.cpp +++ b/source/prediction/Engine.cpp @@ -1,74 +1,140 @@ -// -// Created by Vlad on 6/9/2024. -// - - #include "omath/prediction/Engine.hpp" #include #include - namespace omath::prediction { - Engine::Engine(const float gravityConstant, const float simulationTimeStep, - const float maximumSimulationTime, const float distanceTolerance) - : m_gravityConstant(gravityConstant), - m_simulationTimeStep(simulationTimeStep), - m_maximumSimulationTime(maximumSimulationTime), - m_distanceTolerance(distanceTolerance) + + Engine::Engine(const float gravityConstant, const float simulationTimeStep, const float maximumSimulationTime, + const float distanceTolerance) : + m_gravityConstant(gravityConstant), m_simulationTimeStep(simulationTimeStep), + m_maximumSimulationTime(maximumSimulationTime), m_distanceTolerance(distanceTolerance) { } - std::optional Engine::MaybeCalculateAimPoint(const Projectile &projectile, const Target &target) const + + std::optional Engine::MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const { - for (float time = 0.f; time < m_maximumSimulationTime; time += m_simulationTimeStep) + const float bulletGravity = m_gravityConstant * projectile.m_gravityScale; + const float v0 = projectile.m_launchSpeed; + const float v0Sqr = v0 * v0; + const Vector3 projOrigin = projectile.m_origin; + + constexpr int SIMD_FACTOR = 8; + float currentTime = m_simulationTimeStep; + + for (; currentTime <= m_maximumSimulationTime; currentTime += m_simulationTimeStep * SIMD_FACTOR) { - const auto predictedTargetPosition = target.PredictPosition(time, m_gravityConstant); + const __m256 times = + _mm256_setr_ps(currentTime, currentTime + m_simulationTimeStep, + currentTime + m_simulationTimeStep * 2, currentTime + m_simulationTimeStep * 3, + currentTime + m_simulationTimeStep * 4, currentTime + m_simulationTimeStep * 5, + currentTime + m_simulationTimeStep * 6, currentTime + m_simulationTimeStep * 7); + + const __m256 targetX = + _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.x), times, _mm256_set1_ps(target.m_origin.x)); + const __m256 targetY = + _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.y), times, _mm256_set1_ps(target.m_origin.y)); + const __m256 timesSq = _mm256_mul_ps(times, times); + const __m256 targetZ = _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.z), times, + _mm256_fnmadd_ps(_mm256_set1_ps(0.5f * m_gravityConstant), timesSq, + _mm256_set1_ps(target.m_origin.z))); + + const __m256 deltaX = _mm256_sub_ps(targetX, _mm256_set1_ps(projOrigin.x)); + const __m256 deltaY = _mm256_sub_ps(targetY, _mm256_set1_ps(projOrigin.y)); + const __m256 deltaZ = _mm256_sub_ps(targetZ, _mm256_set1_ps(projOrigin.z)); + + const __m256 dSqr = _mm256_add_ps(_mm256_mul_ps(deltaX, deltaX), _mm256_mul_ps(deltaY, deltaY)); + + const __m256 bgTimesSq = _mm256_mul_ps(_mm256_set1_ps(bulletGravity), timesSq); + const __m256 term = _mm256_add_ps(deltaZ, _mm256_mul_ps(_mm256_set1_ps(0.5f), bgTimesSq)); + const __m256 termSq = _mm256_mul_ps(term, term); + const __m256 numerator = _mm256_add_ps(dSqr, termSq); + const __m256 denominator = _mm256_add_ps(timesSq, _mm256_set1_ps(1e-8f)); // Avoid division by zero + const __m256 requiredV0Sqr = _mm256_div_ps(numerator, denominator); + + const __m256 v0SqrVec = _mm256_set1_ps(v0Sqr + 1e-3f); + const __m256 mask = _mm256_cmp_ps(requiredV0Sqr, v0SqrVec, _CMP_LE_OQ); + + const unsigned validMask = _mm256_movemask_ps(mask); + + if (!validMask) + continue; - const auto projectilePitch = MaybeCalculateProjectileLaunchPitchAngle(projectile, predictedTargetPosition); + alignas(32) float validTimes[SIMD_FACTOR]; + _mm256_store_ps(validTimes, times); - if (!projectilePitch.has_value()) [[unlikely]] + for (int i = 0; i < SIMD_FACTOR; ++i) + { + if (!(validMask & (1 << i))) continue; - if (!IsProjectileReachedTarget(predictedTargetPosition, projectile, projectilePitch.value(), time)) - continue; + const float candidateTime = validTimes[i]; + + if (candidateTime > m_maximumSimulationTime) + continue; - const auto delta2d = (predictedTargetPosition - projectile.m_origin).Length2D(); - const auto height = delta2d * std::tan(angles::DegreesToRadians(projectilePitch.value())); + // Fine search around candidate time + for (float fineTime = candidateTime - m_simulationTimeStep * 2; + fineTime <= candidateTime + m_simulationTimeStep * 2; fineTime += m_simulationTimeStep) + { + if (fineTime < 0) + continue; + + const Vector3 targetPos = target.PredictPosition(fineTime, m_gravityConstant); + const auto pitch = CalculatePitch(projOrigin, targetPos, bulletGravity, v0, fineTime); + if (!pitch) + continue; + + const Vector3 delta = targetPos - projOrigin; + const float d = std::sqrt(delta.x * delta.x + delta.y * delta.y); + const float height = d * std::tan(angles::DegreesToRadians(*pitch)); + return Vector3(targetPos.x, targetPos.y, projOrigin.z + height); + } + } + } + + // Fallback scalar processing for remaining times + for (; currentTime <= m_maximumSimulationTime; currentTime += m_simulationTimeStep) + { + const Vector3 targetPos = target.PredictPosition(currentTime, m_gravityConstant); + const auto pitch = CalculatePitch(projOrigin, targetPos, bulletGravity, v0, currentTime); + if (!pitch) + continue; - return Vector3(predictedTargetPosition.x, predictedTargetPosition.y, projectile.m_origin.z + height); + const Vector3 delta = targetPos - projOrigin; + const float d = std::sqrt(delta.x * delta.x + delta.y * delta.y); + const float height = d * std::tan(angles::DegreesToRadians(*pitch)); + return Vector3(targetPos.x, targetPos.y, projOrigin.z + height); } + return std::nullopt; } - std::optional Engine::MaybeCalculateProjectileLaunchPitchAngle(const Projectile &projectile, - const Vector3 &targetPosition) const + std::optional Engine::CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, + const float bulletGravity, const float v0, const float time) { - const auto bulletGravity = m_gravityConstant * projectile.m_gravityScale; - const auto delta = targetPosition - projectile.m_origin; - - const auto distance2d = delta.Length2D(); - const auto distance2dSqr = distance2d * distance2d; - const auto launchSpeedSqr = projectile.m_launchSpeed * projectile.m_launchSpeed; + if (time <= 0.0f) + return std::nullopt; - float root = launchSpeedSqr * launchSpeedSqr - bulletGravity * (bulletGravity * - distance2dSqr + 2.0f * delta.z * launchSpeedSqr); + const Vector3 delta = targetPos - projOrigin; + const float dSqr = delta.x * delta.x + delta.y * delta.y; + const float h = delta.z; - if (root < 0.0f) [[unlikely]] - return std::nullopt; + const float term = h + 0.5f * bulletGravity * time * time; + const float requiredV0Sqr = (dSqr + term * term) / (time * time); + const float v0Sqr = v0 * v0; - root = std::sqrt(root); - const float angle = std::atan((launchSpeedSqr - root) / (bulletGravity * distance2d)); + if (requiredV0Sqr > v0Sqr + 1e-3f) + return std::nullopt; - return angles::RadiansToDegrees(angle); - } - - bool Engine::IsProjectileReachedTarget(const Vector3 &targetPosition, const Projectile &projectile, - const float pitch, const float time) const - { - const auto yaw = projectile.m_origin.ViewAngleTo(targetPosition).y; - const auto projectilePosition = projectile.PredictPosition(pitch, yaw, time, m_gravityConstant); + if (dSqr == 0.0f) + { + return term >= 0.0f ? 90.0f : -90.0f; + } - return projectilePosition.DistTo(targetPosition) <= m_distanceTolerance; + const float d = std::sqrt(dSqr); + const float tanTheta = term / d; + return angles::RadiansToDegrees(std::atan(tanTheta)); } -} +} // namespace omath::prediction diff --git a/tests/general/UnitTestPrediction.cpp b/tests/general/UnitTestPrediction.cpp index 5002d391..dc88341f 100644 --- a/tests/general/UnitTestPrediction.cpp +++ b/tests/general/UnitTestPrediction.cpp @@ -10,6 +10,6 @@ TEST(UnitTestPrediction, PredictionTest) const auto [pitch, yaw, _] = proj.m_origin.ViewAngleTo(viewPoint.value()).AsTuple(); - EXPECT_NEAR(42.547142, pitch, 0.0001f); - EXPECT_NEAR(-1.181189, yaw, 0.0001f); + EXPECT_NEAR(42.547142, pitch, 0.01f); + EXPECT_NEAR(-1.181189, yaw, 0.01f); } \ No newline at end of file From 900501f37ec316dc1aa0f4df92cc882f9ecc8a81 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 22 Feb 2025 23:02:08 +0300 Subject: [PATCH 172/795] added language mention --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 810cac1d..5197e0ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.26) -project(omath VERSION 1.0.1) +project(omath VERSION 1.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) From d9684ff73ffbe1140fae89dc43a791e57b55453d Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 22 Feb 2025 23:23:01 +0300 Subject: [PATCH 173/795] read me update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd5de4ae..0bae67a2 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ For detailed commands on installing different versions and more information, ple 3. Build the project using CMake: ``` cmake --preset windows-release -S . - cmake --build cmake-build/build/windows-release --target server -j 6 + cmake --build cmake-build/build/windows-release --target omath -j 6 ``` Use **\-\** preset to build siutable version for yourself. Like **windows-release** or **linux-release**. ## ❔ Usage From 28a35d5bc92778fc999af19d96d8adc01cd19357 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 23 Feb 2025 09:57:29 +0300 Subject: [PATCH 174/795] added more classes --- .../Engine.hpp | 15 +- .../projectile_prediction/ProjPredEngine.hpp | 18 +++ .../ProjPredEngineAVX2.hpp | 26 ++++ .../Projectile.hpp | 2 +- .../Target.hpp | 2 +- source/CMakeLists.txt | 2 +- .../CMakeLists.txt | 2 +- .../Engine.cpp | 4 +- .../projectile_prediction/ProjPredEngine.cpp | 10 ++ .../ProjPredEngineAVX2.cpp | 138 ++++++++++++++++++ .../Projectile.cpp | 6 +- .../Target.cpp | 2 +- tests/general/UnitTestPrediction.cpp | 12 +- 13 files changed, 213 insertions(+), 26 deletions(-) rename include/omath/{prediction => projectile_prediction}/Engine.hpp (60%) create mode 100644 include/omath/projectile_prediction/ProjPredEngine.hpp create mode 100644 include/omath/projectile_prediction/ProjPredEngineAVX2.hpp rename include/omath/{prediction => projectile_prediction}/Projectile.hpp (89%) rename include/omath/{prediction => projectile_prediction}/Target.hpp (93%) rename source/{prediction => projectile_prediction}/CMakeLists.txt (59%) rename source/{prediction => projectile_prediction}/Engine.cpp (98%) create mode 100644 source/projectile_prediction/ProjPredEngine.cpp create mode 100644 source/projectile_prediction/ProjPredEngineAVX2.cpp rename source/{prediction => projectile_prediction}/Projectile.cpp (88%) rename source/{prediction => projectile_prediction}/Target.cpp (57%) diff --git a/include/omath/prediction/Engine.hpp b/include/omath/projectile_prediction/Engine.hpp similarity index 60% rename from include/omath/prediction/Engine.hpp rename to include/omath/projectile_prediction/Engine.hpp index e2e5034b..512e27a6 100644 --- a/include/omath/prediction/Engine.hpp +++ b/include/omath/projectile_prediction/Engine.hpp @@ -6,10 +6,10 @@ #include #include "omath/Vector3.hpp" -#include "omath/prediction/Projectile.hpp" -#include "omath/prediction/Target.hpp" +#include "omath/projectile_prediction/Projectile.hpp" +#include "omath/projectile_prediction/Target.hpp" -namespace omath::prediction +namespace omath::projectile_prediction { class Engine final { @@ -26,15 +26,8 @@ namespace omath::prediction const float m_maximumSimulationTime; const float m_distanceTolerance; - [[nodiscard]] - std::optional MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, - const Vector3& targetPosition) const; - [[nodiscard]] static std::optional CalculatePitch(const Vector3 &projOrigin, const Vector3 &targetPos, - float bulletGravity, float v0, float time) ; - [[nodiscard]] - bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, float time) const; - + float bulletGravity, float v0, float time); }; } \ No newline at end of file diff --git a/include/omath/projectile_prediction/ProjPredEngine.hpp b/include/omath/projectile_prediction/ProjPredEngine.hpp new file mode 100644 index 00000000..ba4bc33c --- /dev/null +++ b/include/omath/projectile_prediction/ProjPredEngine.hpp @@ -0,0 +1,18 @@ +// +// Created by Vlad on 2/23/2025. +// +#pragma once +#include "omath/Vector3.hpp" + + +namespace omath::projectile_prediction +{ + class ProjPredEngine + { + public: + [[nodiscard]] + virtual std::optional MaybeCalculateAimPoint(const Projectile& projectile, + const Target& target) const = 0; + virtual ~ProjPredEngine() = default; + }; +} // namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp b/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp new file mode 100644 index 00000000..0c0f5f35 --- /dev/null +++ b/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp @@ -0,0 +1,26 @@ +// +// Created by Vlad on 2/23/2025. +// +#pragma once +#include "ProjPredEngine.hpp" + +namespace omath::projectile_prediction +{ + class ProjPredEngineAVX2 final : public ProjPredEngine + { + public: + [[nodiscard]] std::optional MaybeCalculateAimPoint(const Projectile& projectile, + const Target& target) const override; + + + ProjPredEngineAVX2(float gravityConstant, float simulationTimeStep, float maximumSimulationTime); + ~ProjPredEngineAVX2() override = default; + + private: + [[nodiscard]] static std::optional CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, + float bulletGravity, float v0, float time); + const float m_gravityConstant; + const float m_simulationTimeStep; + const float m_maximumSimulationTime; + }; +} // namespace omath::projectile_prediction diff --git a/include/omath/prediction/Projectile.hpp b/include/omath/projectile_prediction/Projectile.hpp similarity index 89% rename from include/omath/prediction/Projectile.hpp rename to include/omath/projectile_prediction/Projectile.hpp index 54998156..4292100d 100644 --- a/include/omath/prediction/Projectile.hpp +++ b/include/omath/projectile_prediction/Projectile.hpp @@ -5,7 +5,7 @@ #pragma once #include "omath/Vector3.hpp" -namespace omath::prediction +namespace omath::projectile_prediction { class Projectile final { diff --git a/include/omath/prediction/Target.hpp b/include/omath/projectile_prediction/Target.hpp similarity index 93% rename from include/omath/prediction/Target.hpp rename to include/omath/projectile_prediction/Target.hpp index f3a775e4..7dfed42e 100644 --- a/include/omath/prediction/Target.hpp +++ b/include/omath/projectile_prediction/Target.hpp @@ -5,7 +5,7 @@ #pragma once #include "omath/Vector3.hpp" -namespace omath::prediction +namespace omath::projectile_prediction { class Target final { diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index fe42cba6..b7a84afe 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -6,7 +6,7 @@ target_sources(omath PRIVATE Vector2.cpp ) -add_subdirectory(prediction) +add_subdirectory(projectile_prediction) add_subdirectory(pathfinding) add_subdirectory(projection) add_subdirectory(collision) diff --git a/source/prediction/CMakeLists.txt b/source/projectile_prediction/CMakeLists.txt similarity index 59% rename from source/prediction/CMakeLists.txt rename to source/projectile_prediction/CMakeLists.txt index da6b5de1..d93ca75c 100644 --- a/source/prediction/CMakeLists.txt +++ b/source/projectile_prediction/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE Engine.cpp Projectile.cpp Target.cpp) \ No newline at end of file +target_sources(omath PRIVATE Engine.cpp Projectile.cpp Target.cpp ProjPredEngineAVX2.cpp ProjPredEngine.cpp) \ No newline at end of file diff --git a/source/prediction/Engine.cpp b/source/projectile_prediction/Engine.cpp similarity index 98% rename from source/prediction/Engine.cpp rename to source/projectile_prediction/Engine.cpp index c5ce26a9..08616c29 100644 --- a/source/prediction/Engine.cpp +++ b/source/projectile_prediction/Engine.cpp @@ -1,8 +1,8 @@ -#include "omath/prediction/Engine.hpp" +#include "omath/projectile_prediction/Engine.hpp" #include #include -namespace omath::prediction +namespace omath::projectile_prediction { Engine::Engine(const float gravityConstant, const float simulationTimeStep, const float maximumSimulationTime, diff --git a/source/projectile_prediction/ProjPredEngine.cpp b/source/projectile_prediction/ProjPredEngine.cpp new file mode 100644 index 00000000..7be6708b --- /dev/null +++ b/source/projectile_prediction/ProjPredEngine.cpp @@ -0,0 +1,10 @@ +// +// Created by Vlad on 2/23/2025. +// +#include "omath/projectile_prediction/ProjPredEngine.hpp" + + +namespace omath::projectile_prediction +{ + +} // namespace omath::projectile_prediction diff --git a/source/projectile_prediction/ProjPredEngineAVX2.cpp b/source/projectile_prediction/ProjPredEngineAVX2.cpp new file mode 100644 index 00000000..9787e1a1 --- /dev/null +++ b/source/projectile_prediction/ProjPredEngineAVX2.cpp @@ -0,0 +1,138 @@ +// +// Created by Vlad on 2/23/2025. +// +#include "omath/projectile_prediction/ProjPredEngineAVX2.hpp" + + +namespace omath::projectile_prediction +{ + std::optional ProjPredEngineAVX2::MaybeCalculateAimPoint(const Projectile& projectile, + const Target& target) const + { + const float bulletGravity = m_gravityConstant * projectile.m_gravityScale; + const float v0 = projectile.m_launchSpeed; + const float v0Sqr = v0 * v0; + const Vector3 projOrigin = projectile.m_origin; + + constexpr int SIMD_FACTOR = 8; + float currentTime = m_simulationTimeStep; + + for (; currentTime <= m_maximumSimulationTime; currentTime += m_simulationTimeStep * SIMD_FACTOR) + { + const __m256 times = + _mm256_setr_ps(currentTime, currentTime + m_simulationTimeStep, + currentTime + m_simulationTimeStep * 2, currentTime + m_simulationTimeStep * 3, + currentTime + m_simulationTimeStep * 4, currentTime + m_simulationTimeStep * 5, + currentTime + m_simulationTimeStep * 6, currentTime + m_simulationTimeStep * 7); + + const __m256 targetX = + _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.x), times, _mm256_set1_ps(target.m_origin.x)); + const __m256 targetY = + _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.y), times, _mm256_set1_ps(target.m_origin.y)); + const __m256 timesSq = _mm256_mul_ps(times, times); + const __m256 targetZ = _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.z), times, + _mm256_fnmadd_ps(_mm256_set1_ps(0.5f * m_gravityConstant), timesSq, + _mm256_set1_ps(target.m_origin.z))); + + const __m256 deltaX = _mm256_sub_ps(targetX, _mm256_set1_ps(projOrigin.x)); + const __m256 deltaY = _mm256_sub_ps(targetY, _mm256_set1_ps(projOrigin.y)); + const __m256 deltaZ = _mm256_sub_ps(targetZ, _mm256_set1_ps(projOrigin.z)); + + const __m256 dSqr = _mm256_add_ps(_mm256_mul_ps(deltaX, deltaX), _mm256_mul_ps(deltaY, deltaY)); + + const __m256 bgTimesSq = _mm256_mul_ps(_mm256_set1_ps(bulletGravity), timesSq); + const __m256 term = _mm256_add_ps(deltaZ, _mm256_mul_ps(_mm256_set1_ps(0.5f), bgTimesSq)); + const __m256 termSq = _mm256_mul_ps(term, term); + const __m256 numerator = _mm256_add_ps(dSqr, termSq); + const __m256 denominator = _mm256_add_ps(timesSq, _mm256_set1_ps(1e-8f)); // Avoid division by zero + const __m256 requiredV0Sqr = _mm256_div_ps(numerator, denominator); + + const __m256 v0SqrVec = _mm256_set1_ps(v0Sqr + 1e-3f); + const __m256 mask = _mm256_cmp_ps(requiredV0Sqr, v0SqrVec, _CMP_LE_OQ); + + const unsigned validMask = _mm256_movemask_ps(mask); + + if (!validMask) + continue; + + alignas(32) float validTimes[SIMD_FACTOR]; + _mm256_store_ps(validTimes, times); + + for (int i = 0; i < SIMD_FACTOR; ++i) + { + if (!(validMask & (1 << i))) + continue; + + const float candidateTime = validTimes[i]; + + if (candidateTime > m_maximumSimulationTime) + continue; + + // Fine search around candidate time + for (float fineTime = candidateTime - m_simulationTimeStep * 2; + fineTime <= candidateTime + m_simulationTimeStep * 2; fineTime += m_simulationTimeStep) + { + if (fineTime < 0) + continue; + + const Vector3 targetPos = target.PredictPosition(fineTime, m_gravityConstant); + const auto pitch = CalculatePitch(projOrigin, targetPos, bulletGravity, v0, fineTime); + if (!pitch) + continue; + + const Vector3 delta = targetPos - projOrigin; + const float d = std::sqrt(delta.x * delta.x + delta.y * delta.y); + const float height = d * std::tan(angles::DegreesToRadians(*pitch)); + return Vector3(targetPos.x, targetPos.y, projOrigin.z + height); + } + } + } + + // Fallback scalar processing for remaining times + for (; currentTime <= m_maximumSimulationTime; currentTime += m_simulationTimeStep) + { + const Vector3 targetPos = target.PredictPosition(currentTime, m_gravityConstant); + const auto pitch = CalculatePitch(projOrigin, targetPos, bulletGravity, v0, currentTime); + if (!pitch) + continue; + + const Vector3 delta = targetPos - projOrigin; + const float d = std::sqrt(delta.x * delta.x + delta.y * delta.y); + const float height = d * std::tan(angles::DegreesToRadians(*pitch)); + return Vector3(targetPos.x, targetPos.y, projOrigin.z + height); + } + + return std::nullopt; + } + ProjPredEngineAVX2::ProjPredEngineAVX2(const float gravityConstant, const float simulationTimeStep, + const float maximumSimulationTime) : + m_gravityConstant(gravityConstant), m_simulationTimeStep(maximumSimulationTime), + m_maximumSimulationTime(simulationTimeStep) + { + } + std::optional ProjPredEngineAVX2::CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, + const float bulletGravity, const float v0, const float time) + { + if (time <= 0.0f) + return std::nullopt; + + const Vector3 delta = targetPos - projOrigin; + const float dSqr = delta.x * delta.x + delta.y * delta.y; + const float h = delta.z; + + const float term = h + 0.5f * bulletGravity * time * time; + const float requiredV0Sqr = (dSqr + term * term) / (time * time); + const float v0Sqr = v0 * v0; + + if (requiredV0Sqr > v0Sqr + 1e-3f) + return std::nullopt; + + if (dSqr == 0.0f) + return term >= 0.0f ? 90.0f : -90.0f; + + + const float d = std::sqrt(dSqr); + const float tanTheta = term / d; + return angles::RadiansToDegrees(std::atan(tanTheta)); + } +} // namespace omath::projectile_prediction diff --git a/source/prediction/Projectile.cpp b/source/projectile_prediction/Projectile.cpp similarity index 88% rename from source/prediction/Projectile.cpp rename to source/projectile_prediction/Projectile.cpp index 281b3276..c1ce1567 100644 --- a/source/prediction/Projectile.cpp +++ b/source/projectile_prediction/Projectile.cpp @@ -2,11 +2,11 @@ // Created by Vlad on 6/9/2024. // -#include "omath/prediction/Projectile.hpp" -#include +#include "omath/projectile_prediction/Projectile.hpp" + #include -namespace omath::prediction +namespace omath::projectile_prediction { Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const { diff --git a/source/prediction/Target.cpp b/source/projectile_prediction/Target.cpp similarity index 57% rename from source/prediction/Target.cpp rename to source/projectile_prediction/Target.cpp index 9b123959..5c30ee0a 100644 --- a/source/prediction/Target.cpp +++ b/source/projectile_prediction/Target.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 6/9/2024. // -#include "omath/prediction/Target.hpp" +#include "omath/projectile_prediction/Projectile.hpp" namespace omath::prediction diff --git a/tests/general/UnitTestPrediction.cpp b/tests/general/UnitTestPrediction.cpp index dc88341f..59667bf8 100644 --- a/tests/general/UnitTestPrediction.cpp +++ b/tests/general/UnitTestPrediction.cpp @@ -1,15 +1,17 @@ #include -#include +#include TEST(UnitTestPrediction, PredictionTest) { - constexpr omath::prediction::Target target{ + constexpr omath::projectile_prediction::Target target{ .m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; - constexpr omath::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 5000, .m_gravityScale= 0.4}; - const auto viewPoint = omath::prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); + constexpr omath::projectile_prediction::Projectile proj = { + .m_origin = {3, 2, 1}, .m_launchSpeed = 5000, .m_gravityScale = 0.4}; + const auto viewPoint = + omath::projectile_prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); const auto [pitch, yaw, _] = proj.m_origin.ViewAngleTo(viewPoint.value()).AsTuple(); EXPECT_NEAR(42.547142, pitch, 0.01f); EXPECT_NEAR(-1.181189, yaw, 0.01f); -} \ No newline at end of file +} From f21d29c6c2b8fd148e01a13ade20d4b9d61da40c Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 23 Feb 2025 10:10:35 +0300 Subject: [PATCH 175/795] added legacy impl --- .../omath/projectile_prediction/Engine.hpp | 33 ----- .../projectile_prediction/ProjPredEngine.hpp | 2 + .../ProjPredEngineLegacy.hpp | 41 +++++ source/projectile_prediction/CMakeLists.txt | 2 +- source/projectile_prediction/Engine.cpp | 140 ------------------ .../ProjPredEngineLegacy.cpp | 68 +++++++++ tests/general/UnitTestPrediction.cpp | 4 +- 7 files changed, 114 insertions(+), 176 deletions(-) delete mode 100644 include/omath/projectile_prediction/Engine.hpp create mode 100644 include/omath/projectile_prediction/ProjPredEngineLegacy.hpp delete mode 100644 source/projectile_prediction/Engine.cpp create mode 100644 source/projectile_prediction/ProjPredEngineLegacy.cpp diff --git a/include/omath/projectile_prediction/Engine.hpp b/include/omath/projectile_prediction/Engine.hpp deleted file mode 100644 index 512e27a6..00000000 --- a/include/omath/projectile_prediction/Engine.hpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// Created by Vlad on 6/9/2024. -// - -#pragma once - -#include -#include "omath/Vector3.hpp" -#include "omath/projectile_prediction/Projectile.hpp" -#include "omath/projectile_prediction/Target.hpp" - -namespace omath::projectile_prediction -{ - class Engine final - { - public: - explicit Engine(float gravityConstant, float simulationTimeStep, - float maximumSimulationTime, float distanceTolerance); - - [[nodiscard]] - std::optional MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const; - - private: - const float m_gravityConstant; - const float m_simulationTimeStep; - const float m_maximumSimulationTime; - const float m_distanceTolerance; - - - [[nodiscard]] static std::optional CalculatePitch(const Vector3 &projOrigin, const Vector3 &targetPos, - float bulletGravity, float v0, float time); - }; -} \ No newline at end of file diff --git a/include/omath/projectile_prediction/ProjPredEngine.hpp b/include/omath/projectile_prediction/ProjPredEngine.hpp index ba4bc33c..9938db72 100644 --- a/include/omath/projectile_prediction/ProjPredEngine.hpp +++ b/include/omath/projectile_prediction/ProjPredEngine.hpp @@ -2,6 +2,8 @@ // Created by Vlad on 2/23/2025. // #pragma once +#include "Projectile.hpp" +#include "Target.hpp" #include "omath/Vector3.hpp" diff --git a/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp b/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp new file mode 100644 index 00000000..6c9a9e85 --- /dev/null +++ b/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp @@ -0,0 +1,41 @@ +// +// Created by Vlad on 6/9/2024. +// + +#pragma once + +#include +#include "omath/Vector3.hpp" +#include "omath/projectile_prediction/ProjPredEngine.hpp" +#include "omath/projectile_prediction/Projectile.hpp" +#include "omath/projectile_prediction/Target.hpp" + + +namespace omath::projectile_prediction +{ + class ProjPredEngineLegacy final : public ProjPredEngine + { + public: + explicit ProjPredEngineLegacy(float gravityConstant, float simulationTimeStep, float maximumSimulationTime, + float distanceTolerance); + + [[nodiscard]] + std::optional MaybeCalculateAimPoint(const Projectile& projectile, + const Target& target) const override; + + private: + const float m_gravityConstant; + const float m_simulationTimeStep; + const float m_maximumSimulationTime; + const float m_distanceTolerance; + + [[nodiscard]] + std::optional MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, + const Vector3& targetPosition) const; + + + [[nodiscard]] + bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, + float time) const; + }; +} // namespace omath::projectile_prediction diff --git a/source/projectile_prediction/CMakeLists.txt b/source/projectile_prediction/CMakeLists.txt index d93ca75c..623aa656 100644 --- a/source/projectile_prediction/CMakeLists.txt +++ b/source/projectile_prediction/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE Engine.cpp Projectile.cpp Target.cpp ProjPredEngineAVX2.cpp ProjPredEngine.cpp) \ No newline at end of file +target_sources(omath PRIVATE ProjPredEngineLegacy.cpp Projectile.cpp Target.cpp ProjPredEngineAVX2.cpp ProjPredEngine.cpp) \ No newline at end of file diff --git a/source/projectile_prediction/Engine.cpp b/source/projectile_prediction/Engine.cpp deleted file mode 100644 index 08616c29..00000000 --- a/source/projectile_prediction/Engine.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "omath/projectile_prediction/Engine.hpp" -#include -#include - -namespace omath::projectile_prediction -{ - - Engine::Engine(const float gravityConstant, const float simulationTimeStep, const float maximumSimulationTime, - const float distanceTolerance) : - m_gravityConstant(gravityConstant), m_simulationTimeStep(simulationTimeStep), - m_maximumSimulationTime(maximumSimulationTime), m_distanceTolerance(distanceTolerance) - { - } - - - std::optional Engine::MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const - { - const float bulletGravity = m_gravityConstant * projectile.m_gravityScale; - const float v0 = projectile.m_launchSpeed; - const float v0Sqr = v0 * v0; - const Vector3 projOrigin = projectile.m_origin; - - constexpr int SIMD_FACTOR = 8; - float currentTime = m_simulationTimeStep; - - for (; currentTime <= m_maximumSimulationTime; currentTime += m_simulationTimeStep * SIMD_FACTOR) - { - const __m256 times = - _mm256_setr_ps(currentTime, currentTime + m_simulationTimeStep, - currentTime + m_simulationTimeStep * 2, currentTime + m_simulationTimeStep * 3, - currentTime + m_simulationTimeStep * 4, currentTime + m_simulationTimeStep * 5, - currentTime + m_simulationTimeStep * 6, currentTime + m_simulationTimeStep * 7); - - const __m256 targetX = - _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.x), times, _mm256_set1_ps(target.m_origin.x)); - const __m256 targetY = - _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.y), times, _mm256_set1_ps(target.m_origin.y)); - const __m256 timesSq = _mm256_mul_ps(times, times); - const __m256 targetZ = _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.z), times, - _mm256_fnmadd_ps(_mm256_set1_ps(0.5f * m_gravityConstant), timesSq, - _mm256_set1_ps(target.m_origin.z))); - - const __m256 deltaX = _mm256_sub_ps(targetX, _mm256_set1_ps(projOrigin.x)); - const __m256 deltaY = _mm256_sub_ps(targetY, _mm256_set1_ps(projOrigin.y)); - const __m256 deltaZ = _mm256_sub_ps(targetZ, _mm256_set1_ps(projOrigin.z)); - - const __m256 dSqr = _mm256_add_ps(_mm256_mul_ps(deltaX, deltaX), _mm256_mul_ps(deltaY, deltaY)); - - const __m256 bgTimesSq = _mm256_mul_ps(_mm256_set1_ps(bulletGravity), timesSq); - const __m256 term = _mm256_add_ps(deltaZ, _mm256_mul_ps(_mm256_set1_ps(0.5f), bgTimesSq)); - const __m256 termSq = _mm256_mul_ps(term, term); - const __m256 numerator = _mm256_add_ps(dSqr, termSq); - const __m256 denominator = _mm256_add_ps(timesSq, _mm256_set1_ps(1e-8f)); // Avoid division by zero - const __m256 requiredV0Sqr = _mm256_div_ps(numerator, denominator); - - const __m256 v0SqrVec = _mm256_set1_ps(v0Sqr + 1e-3f); - const __m256 mask = _mm256_cmp_ps(requiredV0Sqr, v0SqrVec, _CMP_LE_OQ); - - const unsigned validMask = _mm256_movemask_ps(mask); - - if (!validMask) - continue; - - alignas(32) float validTimes[SIMD_FACTOR]; - _mm256_store_ps(validTimes, times); - - for (int i = 0; i < SIMD_FACTOR; ++i) - { - if (!(validMask & (1 << i))) - continue; - - const float candidateTime = validTimes[i]; - - if (candidateTime > m_maximumSimulationTime) - continue; - - // Fine search around candidate time - for (float fineTime = candidateTime - m_simulationTimeStep * 2; - fineTime <= candidateTime + m_simulationTimeStep * 2; fineTime += m_simulationTimeStep) - { - if (fineTime < 0) - continue; - - const Vector3 targetPos = target.PredictPosition(fineTime, m_gravityConstant); - const auto pitch = CalculatePitch(projOrigin, targetPos, bulletGravity, v0, fineTime); - if (!pitch) - continue; - - const Vector3 delta = targetPos - projOrigin; - const float d = std::sqrt(delta.x * delta.x + delta.y * delta.y); - const float height = d * std::tan(angles::DegreesToRadians(*pitch)); - return Vector3(targetPos.x, targetPos.y, projOrigin.z + height); - } - } - } - - // Fallback scalar processing for remaining times - for (; currentTime <= m_maximumSimulationTime; currentTime += m_simulationTimeStep) - { - const Vector3 targetPos = target.PredictPosition(currentTime, m_gravityConstant); - const auto pitch = CalculatePitch(projOrigin, targetPos, bulletGravity, v0, currentTime); - if (!pitch) - continue; - - const Vector3 delta = targetPos - projOrigin; - const float d = std::sqrt(delta.x * delta.x + delta.y * delta.y); - const float height = d * std::tan(angles::DegreesToRadians(*pitch)); - return Vector3(targetPos.x, targetPos.y, projOrigin.z + height); - } - - return std::nullopt; - } - - std::optional Engine::CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, - const float bulletGravity, const float v0, const float time) - { - if (time <= 0.0f) - return std::nullopt; - - const Vector3 delta = targetPos - projOrigin; - const float dSqr = delta.x * delta.x + delta.y * delta.y; - const float h = delta.z; - - const float term = h + 0.5f * bulletGravity * time * time; - const float requiredV0Sqr = (dSqr + term * term) / (time * time); - const float v0Sqr = v0 * v0; - - if (requiredV0Sqr > v0Sqr + 1e-3f) - return std::nullopt; - - if (dSqr == 0.0f) - { - return term >= 0.0f ? 90.0f : -90.0f; - } - - const float d = std::sqrt(dSqr); - const float tanTheta = term / d; - return angles::RadiansToDegrees(std::atan(tanTheta)); - } -} // namespace omath::prediction diff --git a/source/projectile_prediction/ProjPredEngineLegacy.cpp b/source/projectile_prediction/ProjPredEngineLegacy.cpp new file mode 100644 index 00000000..84c5e222 --- /dev/null +++ b/source/projectile_prediction/ProjPredEngineLegacy.cpp @@ -0,0 +1,68 @@ +#include "omath/projectile_prediction/ProjPredEngineLegacy.hpp" +#include +#include + +namespace omath::projectile_prediction +{ + ProjPredEngineLegacy::ProjPredEngineLegacy(const float gravityConstant, const float simulationTimeStep, + const float maximumSimulationTime, const float distanceTolerance) : + m_gravityConstant(gravityConstant), m_simulationTimeStep(simulationTimeStep), + m_maximumSimulationTime(maximumSimulationTime), m_distanceTolerance(distanceTolerance) + { + } + + std::optional ProjPredEngineLegacy::MaybeCalculateAimPoint(const Projectile& projectile, + const Target& target) const + { + for (float time = 0.f; time < m_maximumSimulationTime; time += m_simulationTimeStep) + { + const auto predictedTargetPosition = target.PredictPosition(time, m_gravityConstant); + + const auto projectilePitch = MaybeCalculateProjectileLaunchPitchAngle(projectile, predictedTargetPosition); + + if (!projectilePitch.has_value()) [[unlikely]] + continue; + + if (!IsProjectileReachedTarget(predictedTargetPosition, projectile, projectilePitch.value(), time)) + continue; + + const auto delta2d = (predictedTargetPosition - projectile.m_origin).Length2D(); + const auto height = delta2d * std::tan(angles::DegreesToRadians(projectilePitch.value())); + + return Vector3(predictedTargetPosition.x, predictedTargetPosition.y, projectile.m_origin.z + height); + } + return std::nullopt; + } + + std::optional + ProjPredEngineLegacy::MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, + const Vector3& targetPosition) const + { + const auto bulletGravity = m_gravityConstant * projectile.m_gravityScale; + const auto delta = targetPosition - projectile.m_origin; + + const auto distance2d = delta.Length2D(); + const auto distance2dSqr = distance2d * distance2d; + const auto launchSpeedSqr = projectile.m_launchSpeed * projectile.m_launchSpeed; + + float root = launchSpeedSqr * launchSpeedSqr - + bulletGravity * (bulletGravity * distance2dSqr + 2.0f * delta.z * launchSpeedSqr); + + if (root < 0.0f) [[unlikely]] + return std::nullopt; + + root = std::sqrt(root); + const float angle = std::atan((launchSpeedSqr - root) / (bulletGravity * distance2d)); + + return angles::RadiansToDegrees(angle); + } + + bool ProjPredEngineLegacy::IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, + const float pitch, const float time) const + { + const auto yaw = projectile.m_origin.ViewAngleTo(targetPosition).y; + const auto projectilePosition = projectile.PredictPosition(pitch, yaw, time, m_gravityConstant); + + return projectilePosition.DistTo(targetPosition) <= m_distanceTolerance; + } +} // namespace omath::projectile_prediction diff --git a/tests/general/UnitTestPrediction.cpp b/tests/general/UnitTestPrediction.cpp index 59667bf8..c77c4386 100644 --- a/tests/general/UnitTestPrediction.cpp +++ b/tests/general/UnitTestPrediction.cpp @@ -1,5 +1,5 @@ #include -#include +#include TEST(UnitTestPrediction, PredictionTest) { @@ -8,7 +8,7 @@ TEST(UnitTestPrediction, PredictionTest) constexpr omath::projectile_prediction::Projectile proj = { .m_origin = {3, 2, 1}, .m_launchSpeed = 5000, .m_gravityScale = 0.4}; const auto viewPoint = - omath::projectile_prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); + omath::projectile_prediction::ProjPredEngineLegacy(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); const auto [pitch, yaw, _] = proj.m_origin.ViewAngleTo(viewPoint.value()).AsTuple(); From 9ba3bc754a9abeca58515772c743a40f0b954c6f Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 23 Feb 2025 21:56:28 +0300 Subject: [PATCH 176/795] removed define --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5197e0ce..aba995aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,6 @@ set_target_properties(omath PROPERTIES target_compile_features(omath PUBLIC cxx_std_23) -target_compile_definitions(omath PUBLIC OMATH_EXPORT) add_subdirectory(source) From 6d0d267743f97104d85f788e8099d7717eae2986 Mon Sep 17 00:00:00 2001 From: Vladislav Alpatov Date: Sat, 1 Mar 2025 21:11:46 +0300 Subject: [PATCH 177/795] now template --- CMakeLists.txt | 5 +- include/omath/Color.hpp | 4 +- include/omath/Mat.hpp | 10 +-- include/omath/Matrix.hpp | 6 +- include/omath/Triangle.hpp | 14 ++-- include/omath/Vector2.hpp | 36 ++++---- include/omath/Vector3.hpp | 83 +++++++++++-------- include/omath/Vector4.hpp | 60 +++++++------- include/omath/collision/LineTracer.hpp | 12 +-- include/omath/engines/OpenGL/Camera.hpp | 4 +- include/omath/engines/OpenGL/Constants.hpp | 6 +- include/omath/engines/OpenGL/Formulas.hpp | 8 +- include/omath/engines/Source/Camera.hpp | 4 +- include/omath/engines/Source/Constants.hpp | 6 +- include/omath/engines/Source/Formulas.hpp | 8 +- include/omath/pathfinding/Astar.hpp | 13 ++- include/omath/pathfinding/NavigationMesh.hpp | 6 +- .../projectile_prediction/ProjPredEngine.hpp | 2 +- .../ProjPredEngineAVX2.hpp | 4 +- .../ProjPredEngineLegacy.hpp | 6 +- .../projectile_prediction/Projectile.hpp | 4 +- .../omath/projectile_prediction/Target.hpp | 6 +- include/omath/projection/Camera.hpp | 12 +-- source/CMakeLists.txt | 3 - source/Matrix.cpp | 4 +- source/Vector2.cpp | 11 --- source/Vector3.cpp | 23 ----- source/Vector4.cpp | 16 ---- source/collision/LineTracer.cpp | 8 +- source/engines/OpenGL/Camera.cpp | 4 +- source/engines/Source/Camera.cpp | 4 +- source/pathfinding/Astar.cpp | 16 ++-- source/pathfinding/NavigationMesh.cpp | 10 +-- .../ProjPredEngineAVX2.cpp | 4 +- .../ProjPredEngineLegacy.cpp | 6 +- source/projectile_prediction/Projectile.cpp | 2 +- tests/engines/UnitTestOpenGL.cpp | 2 +- tests/engines/UnitTestSourceEngine.cpp | 2 +- tests/general/UnitTestLineTrace.cpp | 10 +-- tests/general/UnitTestTriangle.cpp | 16 ++-- tests/general/UnitTestVector2.cpp | 6 +- tests/general/UnitTestVector3.cpp | 8 +- tests/general/UnitTestVector4.cpp | 6 +- 43 files changed, 224 insertions(+), 256 deletions(-) delete mode 100644 source/Vector2.cpp delete mode 100644 source/Vector3.cpp delete mode 100644 source/Vector4.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index aba995aa..b1631f66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,10 +13,7 @@ option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Vector3.cpp) else() - add_library(omath STATIC source/Vector3.cpp - include/omath/engines/OpenGL/Constants.hpp - include/omath/engines/OpenGL/Formulas.hpp - include/omath/engines/OpenGL/Camera.hpp) + add_library(omath STATIC source/Matrix.cpp) endif() set_target_properties(omath PROPERTIES diff --git a/include/omath/Color.hpp b/include/omath/Color.hpp index 642adcc3..54848fcf 100644 --- a/include/omath/Color.hpp +++ b/include/omath/Color.hpp @@ -18,10 +18,10 @@ namespace omath }; - class Color final : public Vector4 + class Color final : public Vector4 { public: - constexpr Color(float r, float g, float b, float a) : Vector4(r,g,b,a) + constexpr Color(const float r, const float g, const float b, const float a) : Vector4(r,g,b,a) { Clamp(0.f, 1.f); } diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 3a23fbce..9d91ab53 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -333,21 +333,21 @@ namespace omath template [[nodiscard]] - constexpr static Mat<1, 4, Type, St> MatRowFromVector(const Vector3& vector) noexcept + constexpr static Mat<1, 4, Type, St> MatRowFromVector(const Vector3& vector) noexcept { return {{vector.x, vector.y, vector.z, 1}}; } template [[nodiscard]] - constexpr static Mat<4, 1, Type, St> MatColumnFromVector(const Vector3& vector) noexcept + constexpr static Mat<4, 1, Type, St> MatColumnFromVector(const Vector3& vector) noexcept { return {{vector.x}, {vector.y}, {vector.z}, {1}}; } template [[nodiscard]] - constexpr Mat<4, 4, Type, St> MatTranslation(const Vector3& diff) noexcept + constexpr Mat<4, 4, Type, St> MatTranslation(const Vector3& diff) noexcept { return { @@ -399,8 +399,8 @@ namespace omath template [[nodiscard]] - static Mat<4, 4, Type, St> MatCameraView(const Vector3& forward, const Vector3& right, const Vector3& up, - const Vector3& cameraOrigin) noexcept + static Mat<4, 4, Type, St> MatCameraView(const Vector3& forward, const Vector3& right, + const Vector3& up, const Vector3& cameraOrigin) noexcept { return Mat<4, 4, Type, St> { diff --git a/include/omath/Matrix.hpp b/include/omath/Matrix.hpp index 2f23e406..3043bc5a 100644 --- a/include/omath/Matrix.hpp +++ b/include/omath/Matrix.hpp @@ -2,10 +2,10 @@ #include #include #include +#include "Vector3.hpp" namespace omath { - class Vector3; class Matrix final { @@ -19,10 +19,10 @@ namespace omath static Matrix ToScreenMatrix(float screenWidth, float screenHeight); [[nodiscard]] - static Matrix TranslationMatrix(const Vector3& diff); + static Matrix TranslationMatrix(const Vector3& diff); [[nodiscard]] - static Matrix OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up); + static Matrix OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up); [[nodiscard]] static Matrix ProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); diff --git a/include/omath/Triangle.hpp b/include/omath/Triangle.hpp index e9e36655..9779982a 100644 --- a/include/omath/Triangle.hpp +++ b/include/omath/Triangle.hpp @@ -16,12 +16,12 @@ namespace omath { } - Vector3 m_vertex1; - Vector3 m_vertex2; - Vector3 m_vertex3; + Vector3 m_vertex1; + Vector3 m_vertex2; + Vector3 m_vertex3; [[nodiscard]] - constexpr Vector3 CalculateNormal() const + constexpr Vector3 CalculateNormal() const { const auto b = SideBVector(); const auto a = SideAVector(); @@ -41,7 +41,7 @@ namespace omath } [[nodiscard]] - constexpr Vector3 SideAVector() const + constexpr Vector3 SideAVector() const { return m_vertex1 - m_vertex2; } @@ -61,12 +61,12 @@ namespace omath return std::abs(sideA*sideA + sideB*sideB - hypot*hypot) <= 0.0001f; } [[nodiscard]] - constexpr Vector3 SideBVector() const + constexpr Vector3 SideBVector() const { return m_vertex3 - m_vertex2; } [[nodiscard]] - constexpr Vector3 MidPoint() const + constexpr Vector3 MidPoint() const { return (m_vertex1 + m_vertex2 + m_vertex3) / 3; } diff --git a/include/omath/Vector2.hpp b/include/omath/Vector2.hpp index db028cd2..60d2ed56 100644 --- a/include/omath/Vector2.hpp +++ b/include/omath/Vector2.hpp @@ -8,16 +8,18 @@ namespace omath { + + template requires std::is_arithmetic_v class Vector2 { public: - float x = 0.f; - float y = 0.f; + Type x = static_cast(0); + Type y = static_cast(0); // Constructors constexpr Vector2() = default; - constexpr Vector2(const float x, const float y) : x(x), y(y) {} + constexpr Vector2(const Type& x, const Type& y) : x(x), y(y) {} // Equality operators [[nodiscard]] @@ -65,7 +67,7 @@ namespace omath return *this; } - constexpr Vector2& operator*=(const float fl) + constexpr Vector2& operator*=(const Type& fl) { x *= fl; y *= fl; @@ -73,7 +75,7 @@ namespace omath return *this; } - constexpr Vector2& operator/=(const float fl) + constexpr Vector2& operator/=(const Type& fl) { x /= fl; y /= fl; @@ -81,7 +83,7 @@ namespace omath return *this; } - constexpr Vector2& operator+=(const float fl) + constexpr Vector2& operator+=(const Type& fl) { x += fl; y += fl; @@ -89,7 +91,7 @@ namespace omath return *this; } - constexpr Vector2& operator-=(const float fl) + constexpr Vector2& operator-=(const Type& fl) { x -= fl; y -= fl; @@ -98,45 +100,45 @@ namespace omath } // Basic vector operations - [[nodiscard]] float DistTo(const Vector2& vOther) const + [[nodiscard]] Type DistTo(const Vector2& vOther) const { return std::sqrt(DistToSqr(vOther)); } - [[nodiscard]] constexpr float DistToSqr(const Vector2& vOther) const + [[nodiscard]] constexpr Type DistToSqr(const Vector2& vOther) const { return (x - vOther.x) * (x - vOther.x) + (y - vOther.y) * (y - vOther.y); } - [[nodiscard]] constexpr float Dot(const Vector2& vOther) const + [[nodiscard]] constexpr Type Dot(const Vector2& vOther) const { return x * vOther.x + y * vOther.y; } #ifndef _MSC_VER - [[nodiscard]] constexpr float Length() const + [[nodiscard]] constexpr Type& Length() const { return std::hypot(x, y); } [[nodiscard]] constexpr Vector2 Normalized() const { - const float len = Length(); + const Type len = Length(); return len > 0.f ? *this / len : *this; } #else - [[nodiscard]] float Length() const + [[nodiscard]] Type Length() const { return std::hypot(x, y); } [[nodiscard]] Vector2 Normalized() const { - const float len = Length(); + const Type len = Length(); return len > 0.f ? *this / len : *this; } #endif - [[nodiscard]] constexpr float LengthSqr() const + [[nodiscard]] constexpr Type LengthSqr() const { return x * x + y * y; } @@ -187,13 +189,13 @@ namespace omath } // Sum of elements - [[nodiscard]] constexpr float Sum() const + [[nodiscard]] constexpr Type Sum() const { return x + y; } [[nodiscard]] - constexpr std::tuple AsTuple() const + constexpr std::tuple AsTuple() const { return std::make_tuple(x, y); } diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index 88dce1c7..9c2e9c3f 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -20,16 +20,17 @@ namespace omath IMPOSSIBLE_BETWEEN_ANGLE, }; - class Vector3 : public Vector2 + template requires std::is_arithmetic_v + class Vector3 : public Vector2 { public: - float z = 0.f; - constexpr Vector3(const float x, const float y, const float z) : Vector2(x, y), z(z) { } - constexpr Vector3() : Vector2() {}; + Type z = 0.f; + constexpr Vector3(const Type& x, const Type& y, const Type& z) : Vector2(x, y), z(z) { } + constexpr Vector3() : Vector2() {}; [[nodiscard]] constexpr bool operator==(const Vector3& src) const { - return Vector2::operator==(src) && (src.z == z); + return Vector2::operator==(src) && (src.z == z); } [[nodiscard]] constexpr bool operator!=(const Vector3& src) const @@ -39,7 +40,7 @@ namespace omath constexpr Vector3& operator+=(const Vector3& v) { - Vector2::operator+=(v); + Vector2::operator+=(v); z += v.z; return *this; @@ -47,7 +48,7 @@ namespace omath constexpr Vector3& operator-=(const Vector3& v) { - Vector2::operator-=(v); + Vector2::operator-=(v); z -= v.z; return *this; @@ -55,7 +56,7 @@ namespace omath constexpr Vector3& operator*=(const float fl) { - Vector2::operator*=(fl); + Vector2::operator*=(fl); z *= fl; return *this; @@ -63,7 +64,7 @@ namespace omath constexpr Vector3& operator*=(const Vector3& v) { - Vector2::operator*=(v); + Vector2::operator*=(v); z *= v.z; return *this; @@ -71,7 +72,7 @@ namespace omath constexpr Vector3& operator/=(const Vector3& v) { - Vector2::operator/=(v); + Vector2::operator/=(v); z /= v.z; return *this; @@ -79,7 +80,7 @@ namespace omath constexpr Vector3& operator+=(const float fl) { - Vector2::operator+=(fl); + Vector2::operator+=(fl); z += fl; return *this; @@ -87,7 +88,7 @@ namespace omath constexpr Vector3& operator/=(const float fl) { - Vector2::operator/=(fl); + Vector2::operator/=(fl); z /= fl; return *this; @@ -95,7 +96,7 @@ namespace omath constexpr Vector3& operator-=(const float fl) { - Vector2::operator-=(fl); + Vector2::operator-=(fl); z -= fl; return *this; @@ -103,7 +104,7 @@ namespace omath constexpr Vector3& Abs() { - Vector2::Abs(); + Vector2::Abs(); z = z < 0.f ? -z : z; return *this; @@ -116,7 +117,7 @@ namespace omath [[nodiscard]] constexpr float Dot(const Vector3& vOther) const { - return Vector2::Dot(vOther) + z * vOther.z; + return Vector2::Dot(vOther) + z * vOther.z; } #ifndef _MSC_VER @@ -142,7 +143,7 @@ namespace omath #else [[nodiscard]] float Length() const { - return std::hypot(x, y, z); + return std::hypot(this->x, this->y, z); } [[nodiscard]] Vector3 Normalized() const @@ -152,9 +153,9 @@ namespace omath return length != 0 ? *this / length : *this; } - [[nodiscard]] float Length2D() const + [[nodiscard]] Type Length2D() const { - return Vector2::Length(); + return Vector2::Length(); } [[nodiscard]] float DistTo(const Vector3& vOther) const @@ -166,51 +167,52 @@ namespace omath [[nodiscard]] constexpr float LengthSqr() const { - return Vector2::LengthSqr() + z * z; + return Vector2::LengthSqr() + z * z; } [[nodiscard]] constexpr Vector3 operator-() const { - return {-x, -y, -z}; + + return Vector3{-this->x, -this->y, -z}; } [[nodiscard]] constexpr Vector3 operator+(const Vector3& v) const { - return {x + v.x, y + v.y, z + v.z}; + return {this->x + v.x, this->y + v.y, z + v.z}; } [[nodiscard]] constexpr Vector3 operator-(const Vector3& v) const { - return {x - v.x, y - v.y, z - v.z}; + return {this->x - v.x, this->y - v.y, z - v.z}; } [[nodiscard]] constexpr Vector3 operator*(const float fl) const { - return {x * fl, y * fl, z * fl}; + return {this->x * fl, this->y * fl, z * fl}; } [[nodiscard]] constexpr Vector3 operator*(const Vector3& v) const { - return {x * v.x, y * v.y, z * v.z}; + return {this->x * v.x, this->y * v.y, z * v.z}; } [[nodiscard]] constexpr Vector3 operator/(const float fl) const { - return {x / fl, y / fl, z / fl}; + return {this->x / fl, this->y / fl, z / fl}; } [[nodiscard]] constexpr Vector3 operator/(const Vector3& v) const { - return {x / v.x, y / v.y, z / v.z}; + return {this->x / v.x, this->y / v.y, z / v.z}; } [[nodiscard]] constexpr Vector3 Cross(const Vector3 &v) const { return { - y * v.z - z * v.y, - z * v.x - x * v.z, - x * v.y - y * v.x + this->y * v.z - z * v.y, + z * v.x - this->x * v.z, + this->x * v.y - this->y * v.x }; } [[nodiscard]] constexpr float Sum() const @@ -239,14 +241,25 @@ namespace omath [[nodiscard]] constexpr float Sum2D() const { - return Vector2::Sum(); + return Vector2::Sum(); } - [[nodiscard]] Vector3 ViewAngleTo(const Vector3& other) const; - [[nodiscard]] constexpr std::tuple AsTuple() const { - return std::make_tuple(x, y, z); + return std::make_tuple(this->x, this->y, z); + } + + [[nodiscard]] Vector3 ViewAngleTo(const Vector3 &other) const + { + const float distance = DistTo(other); + const auto delta = other - *this; + + return + { + angles::RadiansToDegrees(std::asin(delta.z / distance)), + angles::RadiansToDegrees(std::atan2(delta.y, delta.x)), + 0 + }; } }; } @@ -254,9 +267,9 @@ namespace omath namespace std { template<> - struct hash + struct hash> { - 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; diff --git a/include/omath/Vector4.hpp b/include/omath/Vector4.hpp index c70e2d3b..05530ee1 100644 --- a/include/omath/Vector4.hpp +++ b/include/omath/Vector4.hpp @@ -8,18 +8,19 @@ namespace omath { - class Vector4 : public Vector3 + template + class Vector4 : public Vector3 { public: - float w; + Type w; - constexpr Vector4(const float x, const float y, const float z, const float w) : Vector3(x, y, z), w(w) {} - constexpr Vector4() : Vector3(), w(0.f) {}; + constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w) : Vector3(x, y, z), w(w) {} + constexpr Vector4() : Vector3(), w(0) {}; [[nodiscard]] constexpr bool operator==(const Vector4& src) const { - return Vector3::operator==(src) && w == src.w; + return Vector3::operator==(src) && w == src.w; } [[nodiscard]] @@ -30,7 +31,7 @@ namespace omath constexpr Vector4& operator+=(const Vector4& v) { - Vector3::operator+=(v); + Vector3::operator+=(v); w += v.w; return *this; @@ -38,7 +39,7 @@ namespace omath constexpr Vector4& operator-=(const Vector4& v) { - Vector3::operator-=(v); + Vector3::operator-=(v); w -= v.w; return *this; @@ -46,7 +47,7 @@ namespace omath constexpr Vector4& operator*=(const float scalar) { - Vector3::operator*=(scalar); + Vector3::operator*=(scalar); w *= scalar; return *this; @@ -54,7 +55,7 @@ namespace omath constexpr Vector4& operator*=(const Vector4& v) { - Vector3::operator*=(v); + Vector3::operator*=(v); w *= v.w; return *this; @@ -62,7 +63,7 @@ namespace omath constexpr Vector4& operator/=(const float scalar) { - Vector3::operator/=(scalar); + Vector3::operator/=(scalar); w /= scalar; return *this; @@ -70,35 +71,38 @@ namespace omath constexpr Vector4& operator/=(const Vector4& v) { - Vector3::operator/=(v); + Vector3::operator/=(v); w /= v.w; return *this; } - [[nodiscard]] constexpr float LengthSqr() const + [[nodiscard]] constexpr Type LengthSqr() const { - return Vector3::LengthSqr() + w * w; + return Vector3::LengthSqr() + w * w; } [[nodiscard]] constexpr float Dot(const Vector4& vOther) const { - return Vector3::Dot(vOther) + w * vOther.w; + return Vector3::Dot(vOther) + w * vOther.w; } - [[nodiscard]] float Length() const; + [[nodiscard]] Vector3 Length() const + { + return std::sqrt(LengthSqr()); + } constexpr Vector4& Abs() { - Vector3::Abs(); + Vector3::Abs(); w = w < 0.f ? -w : w; return *this; } constexpr Vector4& Clamp(const float min, const float max) { - x = std::clamp(x, min, max); - y = std::clamp(y, min, max); - z = std::clamp(z, min, max); + this->x = std::clamp(this->x, min, max); + this->y = std::clamp(this->y, min, max); + this->z = std::clamp(this->z, min, max); return *this; } @@ -106,49 +110,49 @@ namespace omath [[nodiscard]] constexpr Vector4 operator-() const { - return {-x, -y, -z, -w}; + return {-this->x, -this->y, -this->z, -w}; } [[nodiscard]] constexpr Vector4 operator+(const Vector4& v) const { - return {x + v.x, y + v.y, z + v.z, w + v.w}; + return {this->x + v.x, this->y + v.y, this->z + v.z, w + v.w}; } [[nodiscard]] constexpr Vector4 operator-(const Vector4& v) const { - return {x - v.x, y - v.y, z - v.z, w - v.w}; + return {this->x - v.x, this->y - v.y, this->z - v.z, w - v.w}; } [[nodiscard]] constexpr Vector4 operator*(const float scalar) const { - return {x * scalar, y * scalar, z * scalar, w * scalar}; + return {this->x * scalar, this->y * scalar, this->z * scalar, w * scalar}; } [[nodiscard]] constexpr Vector4 operator*(const Vector4& v) const { - return {x * v.x, y * v.y, z * v.z, w * v.w}; + return {this->x * v.x, this->y * v.y, this->z * v.z, w * v.w}; } [[nodiscard]] constexpr Vector4 operator/(const float scalar) const { - return {x / scalar, y / scalar, z / scalar, w / scalar}; + return {this->x / scalar, this->y / scalar, this->z / scalar, w / scalar}; } [[nodiscard]] constexpr Vector4 operator/(const Vector4& v) const { - return {x / v.x, y / v.y, z / v.z, w / v.w}; + return {this->x / v.x, this->y / v.y, this->z / v.z, w / v.w}; } [[nodiscard]] - constexpr float Sum() const + constexpr Type Sum() const { - return Vector3::Sum() + w; + return Vector3::Sum() + w; } }; } diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/LineTracer.hpp index 33b81a24..e8c0b15b 100644 --- a/include/omath/collision/LineTracer.hpp +++ b/include/omath/collision/LineTracer.hpp @@ -11,14 +11,14 @@ namespace omath::collision class Ray { public: - Vector3 start; - Vector3 end; + Vector3 start; + Vector3 end; [[nodiscard]] - Vector3 DirectionVector() const; + Vector3 DirectionVector() const; [[nodiscard]] - Vector3 DirectionVectorNormalized() const; + Vector3 DirectionVectorNormalized() const; }; class LineTracer { @@ -27,12 +27,12 @@ namespace omath::collision [[nodiscard]] - static bool CanTraceLine(const Ray& ray, const Triangle& triangle); + static bool CanTraceLine(const Ray& ray, const Triangle>& triangle); // Realization of Möller–Trumbore intersection algorithm // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm [[nodiscard]] - static Vector3 GetRayHitPoint(const Ray& ray, const Triangle& triangle); + static Vector3 GetRayHitPoint(const Ray& ray, const Triangle>& triangle); }; } diff --git a/include/omath/engines/OpenGL/Camera.hpp b/include/omath/engines/OpenGL/Camera.hpp index 0ad97291..0f3e7fbb 100644 --- a/include/omath/engines/OpenGL/Camera.hpp +++ b/include/omath/engines/OpenGL/Camera.hpp @@ -10,9 +10,9 @@ namespace omath::opengl class Camera final : public projection::Camera { public: - Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, const Angle& fov, float near, float far); - void LookAt(const Vector3& target) override; + void LookAt(const Vector3& target) override; [[nodiscard]] Mat4x4 CalcViewMatrix() const override; [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; }; diff --git a/include/omath/engines/OpenGL/Constants.hpp b/include/omath/engines/OpenGL/Constants.hpp index a8912a21..6cedf43f 100644 --- a/include/omath/engines/OpenGL/Constants.hpp +++ b/include/omath/engines/OpenGL/Constants.hpp @@ -10,9 +10,9 @@ namespace omath::opengl { - constexpr Vector3 kAbsUp = {0, 1, 0}; - constexpr Vector3 kAbsRight = {1, 0, 0}; - constexpr Vector3 kAbsForward = {0, 0, -1}; + constexpr Vector3 kAbsUp = {0, 1, 0}; + constexpr Vector3 kAbsRight = {1, 0, 0}; + constexpr Vector3 kAbsForward = {0, 0, -1}; using Mat4x4 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>; using Mat3x3 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>; diff --git a/include/omath/engines/OpenGL/Formulas.hpp b/include/omath/engines/OpenGL/Formulas.hpp index 8e6b2edb..c551619c 100644 --- a/include/omath/engines/OpenGL/Formulas.hpp +++ b/include/omath/engines/OpenGL/Formulas.hpp @@ -8,7 +8,7 @@ namespace omath::opengl { [[nodiscard]] - inline Vector3 ForwardVector(const ViewAngles& angles) + inline Vector3 ForwardVector(const ViewAngles& angles) { const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); @@ -16,7 +16,7 @@ namespace omath::opengl } [[nodiscard]] - inline Vector3 RightVector(const ViewAngles& angles) + inline Vector3 RightVector(const ViewAngles& angles) { const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); @@ -24,14 +24,14 @@ namespace omath::opengl } [[nodiscard]] - inline Vector3 UpVector(const ViewAngles& angles) + inline Vector3 UpVector(const ViewAngles& angles) { const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } - [[nodiscard]] inline Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + [[nodiscard]] inline Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { return MatCameraView(-ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); } diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/Source/Camera.hpp index 739aa861..ef895baf 100644 --- a/include/omath/engines/Source/Camera.hpp +++ b/include/omath/engines/Source/Camera.hpp @@ -10,9 +10,9 @@ namespace omath::source class Camera final : public projection::Camera { public: - Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, const Angle& fov, float near, float far); - void LookAt(const Vector3& target) override; + void LookAt(const Vector3& target) override; [[nodiscard]] Mat4x4 CalcViewMatrix() const override; [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; }; diff --git a/include/omath/engines/Source/Constants.hpp b/include/omath/engines/Source/Constants.hpp index ecf1b400..c0a7b017 100644 --- a/include/omath/engines/Source/Constants.hpp +++ b/include/omath/engines/Source/Constants.hpp @@ -9,9 +9,9 @@ #include namespace omath::source { - constexpr Vector3 kAbsUp = {0, 0, 1}; - constexpr Vector3 kAbsRight = {0, -1, 0}; - constexpr Vector3 kAbsForward = {1, 0, 0}; + constexpr Vector3 kAbsUp = {0, 0, 1}; + constexpr Vector3 kAbsRight = {0, -1, 0}; + constexpr Vector3 kAbsForward = {1, 0, 0}; using Mat4x4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; using Mat3x3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>; diff --git a/include/omath/engines/Source/Formulas.hpp b/include/omath/engines/Source/Formulas.hpp index 0b8ff039..49b9166b 100644 --- a/include/omath/engines/Source/Formulas.hpp +++ b/include/omath/engines/Source/Formulas.hpp @@ -7,7 +7,7 @@ namespace omath::source { [[nodiscard]] - inline Vector3 ForwardVector(const ViewAngles& angles) + inline Vector3 ForwardVector(const ViewAngles& angles) { const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); @@ -15,7 +15,7 @@ namespace omath::source } [[nodiscard]] - inline Vector3 RightVector(const ViewAngles& angles) + inline Vector3 RightVector(const ViewAngles& angles) { const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); @@ -23,14 +23,14 @@ namespace omath::source } [[nodiscard]] - inline Vector3 UpVector(const ViewAngles& angles) + inline Vector3 UpVector(const ViewAngles& angles) { const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } - [[nodiscard]] inline Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + [[nodiscard]] inline Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); } diff --git a/include/omath/pathfinding/Astar.hpp b/include/omath/pathfinding/Astar.hpp index 31aa127b..9cbcedbb 100644 --- a/include/omath/pathfinding/Astar.hpp +++ b/include/omath/pathfinding/Astar.hpp @@ -14,12 +14,17 @@ namespace omath::pathfinding { public: [[nodiscard]] - static std::vector FindPath(const Vector3& start, const Vector3& end, const NavigationMesh& navMesh); + static std::vector> FindPath(const Vector3& start, const Vector3& end, + const NavigationMesh& navMesh); + private: [[nodiscard]] - static std::vector ReconstructFinalPath(const std::unordered_map& closedList, const Vector3& current); + static std::vector> + ReconstructFinalPath(const std::unordered_map, PathNode>& closedList, + const Vector3& current); [[nodiscard]] - static auto GetPerfectNode(const std::unordered_map& openList, const Vector3& endVertex); + static auto GetPerfectNode(const std::unordered_map, PathNode>& openList, + const Vector3& endVertex); }; -} \ No newline at end of file +} // namespace omath::pathfinding diff --git a/include/omath/pathfinding/NavigationMesh.hpp b/include/omath/pathfinding/NavigationMesh.hpp index 5fc34d2c..785f619e 100644 --- a/include/omath/pathfinding/NavigationMesh.hpp +++ b/include/omath/pathfinding/NavigationMesh.hpp @@ -22,17 +22,17 @@ namespace omath::pathfinding public: [[nodiscard]] - std::expected GetClosestVertex(const Vector3& point) const; + std::expected, std::string> GetClosestVertex(const Vector3& point) const; [[nodiscard]] - const std::vector& GetNeighbors(const Vector3& vertex) const; + const std::vector>& GetNeighbors(const Vector3& vertex) const; [[nodiscard]] bool Empty() const; [[nodiscard]] std::vector Serialize() const; void Deserialize(const std::vector& raw); - std::unordered_map> m_verTextMap; + std::unordered_map, std::vector>> m_verTextMap; }; } \ No newline at end of file diff --git a/include/omath/projectile_prediction/ProjPredEngine.hpp b/include/omath/projectile_prediction/ProjPredEngine.hpp index 9938db72..83adde05 100644 --- a/include/omath/projectile_prediction/ProjPredEngine.hpp +++ b/include/omath/projectile_prediction/ProjPredEngine.hpp @@ -13,7 +13,7 @@ namespace omath::projectile_prediction { public: [[nodiscard]] - virtual std::optional MaybeCalculateAimPoint(const Projectile& projectile, + virtual std::optional> MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const = 0; virtual ~ProjPredEngine() = default; }; diff --git a/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp b/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp index 0c0f5f35..ffc0c5ed 100644 --- a/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp +++ b/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp @@ -9,7 +9,7 @@ namespace omath::projectile_prediction class ProjPredEngineAVX2 final : public ProjPredEngine { public: - [[nodiscard]] std::optional MaybeCalculateAimPoint(const Projectile& projectile, + [[nodiscard]] std::optional> MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const override; @@ -17,7 +17,7 @@ namespace omath::projectile_prediction ~ProjPredEngineAVX2() override = default; private: - [[nodiscard]] static std::optional CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, + [[nodiscard]] static std::optional CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, float bulletGravity, float v0, float time); const float m_gravityConstant; const float m_simulationTimeStep; diff --git a/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp b/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp index 6c9a9e85..f22308d6 100644 --- a/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp +++ b/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp @@ -20,7 +20,7 @@ namespace omath::projectile_prediction float distanceTolerance); [[nodiscard]] - std::optional MaybeCalculateAimPoint(const Projectile& projectile, + std::optional> MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const override; private: @@ -31,11 +31,11 @@ namespace omath::projectile_prediction [[nodiscard]] std::optional MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, - const Vector3& targetPosition) const; + const Vector3& targetPosition) const; [[nodiscard]] - bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, + bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, float time) const; }; } // namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/Projectile.hpp b/include/omath/projectile_prediction/Projectile.hpp index 4292100d..c2e0f7bc 100644 --- a/include/omath/projectile_prediction/Projectile.hpp +++ b/include/omath/projectile_prediction/Projectile.hpp @@ -12,9 +12,9 @@ namespace omath::projectile_prediction public: [[nodiscard]] - Vector3 PredictPosition(float pitch, float yaw, float time, float gravity) const; + Vector3 PredictPosition(float pitch, float yaw, float time, float gravity) const; - Vector3 m_origin; + Vector3 m_origin; float m_launchSpeed{}; float m_gravityScale{}; }; diff --git a/include/omath/projectile_prediction/Target.hpp b/include/omath/projectile_prediction/Target.hpp index 7dfed42e..a5e4a12b 100644 --- a/include/omath/projectile_prediction/Target.hpp +++ b/include/omath/projectile_prediction/Target.hpp @@ -12,7 +12,7 @@ namespace omath::projectile_prediction public: [[nodiscard]] - constexpr Vector3 PredictPosition(const float time, const float gravity) const + constexpr Vector3 PredictPosition(const float time, const float gravity) const { auto predicted = m_origin + m_velocity * time; @@ -22,8 +22,8 @@ namespace omath::projectile_prediction return predicted; } - Vector3 m_origin; - Vector3 m_velocity; + Vector3 m_origin; + Vector3 m_velocity; bool m_isAirborne{}; }; } \ No newline at end of file diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index e722d768..4d967374 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -31,7 +31,7 @@ namespace omath::projection { public: virtual ~Camera() = default; - Camera(const Vector3& position, const ViewAnglesType& viewAngles, const ViewPort& viewPort, + Camera(const Vector3& position, const ViewAnglesType& viewAngles, const ViewPort& viewPort, const FieldOfView& fov, const float near, const float far) : m_viewPort(viewPort), m_fieldOfView(fov), m_farPlaneDistance(far), m_nearPlaneDistance(near), m_viewAngles(viewAngles), m_origin(position) @@ -39,7 +39,7 @@ namespace omath::projection } - virtual void LookAt(const Vector3& target) = 0; + virtual void LookAt(const Vector3& target) = 0; [[nodiscard]] virtual Mat4x4Type CalcViewMatrix() const = 0; @@ -74,7 +74,7 @@ namespace omath::projection m_viewProjectionMatrix = CalcViewProjectionMatrix(); } - void SetOrigin(const Vector3& origin) + void SetOrigin(const Vector3& origin) { m_origin = origin; m_viewProjectionMatrix = CalcViewProjectionMatrix(); @@ -106,12 +106,12 @@ namespace omath::projection return m_viewAngles; } - [[nodiscard]] const Vector3& GetOrigin() const + [[nodiscard]] const Vector3& GetOrigin() const { return m_origin; } - [[nodiscard]] std::expected WorldToScreen(const Vector3& worldPosition) const + [[nodiscard]] std::expected, Error> WorldToScreen(const Vector3& worldPosition) const { if (!m_viewProjectionMatrix.has_value()) m_viewProjectionMatrix = CalcViewProjectionMatrix(); @@ -143,7 +143,7 @@ namespace omath::projection ViewAnglesType m_viewAngles; - Vector3 m_origin; + Vector3 m_origin; private: template diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index b7a84afe..10ca8ed2 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,9 +1,6 @@ target_sources(omath PRIVATE - Vector3.cpp Matrix.cpp color.cpp - Vector4.cpp - Vector2.cpp ) add_subdirectory(projectile_prediction) diff --git a/source/Matrix.cpp b/source/Matrix.cpp index f976f415..98bea2c3 100644 --- a/source/Matrix.cpp +++ b/source/Matrix.cpp @@ -315,7 +315,7 @@ namespace omath }; } - Matrix Matrix::TranslationMatrix(const Vector3& diff) + Matrix Matrix::TranslationMatrix(const Vector3& diff) { return { {1.f, 0.f, 0.f, 0.f}, @@ -325,7 +325,7 @@ namespace omath }; } - Matrix Matrix::OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up) + Matrix Matrix::OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up) { return { {right.x, up.x, forward.x, 0.f}, diff --git a/source/Vector2.cpp b/source/Vector2.cpp deleted file mode 100644 index 58188ace..00000000 --- a/source/Vector2.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// -// Created by Vlad on 02.09.2024. -// -#include "omath/Vector2.hpp" -#include - - -namespace omath -{ - -} \ No newline at end of file diff --git a/source/Vector3.cpp b/source/Vector3.cpp deleted file mode 100644 index 45b9f1e9..00000000 --- a/source/Vector3.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// -// Created by vlad on 10/28/23. -// - -#include -#include -#include - -namespace omath -{ - Vector3 Vector3::ViewAngleTo(const Vector3 &other) const - { - const float distance = DistTo(other); - const auto delta = other - *this; - - return - { - angles::RadiansToDegrees(std::asin(delta.z / distance)), - angles::RadiansToDegrees(std::atan2(delta.y, delta.x)), - 0.f - }; - } -} \ No newline at end of file diff --git a/source/Vector4.cpp b/source/Vector4.cpp deleted file mode 100644 index a995337c..00000000 --- a/source/Vector4.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// -// Vector4.cpp -// - -#include "omath/Vector4.hpp" -#include - - -namespace omath -{ - - float Vector4::Length() const - { - return std::sqrt(LengthSqr()); - } -} diff --git a/source/collision/LineTracer.cpp b/source/collision/LineTracer.cpp index 5f8c5b60..05f5bef7 100644 --- a/source/collision/LineTracer.cpp +++ b/source/collision/LineTracer.cpp @@ -5,21 +5,21 @@ namespace omath::collision { - bool LineTracer::CanTraceLine(const Ray& ray, const Triangle& triangle) + bool LineTracer::CanTraceLine(const Ray& ray, const Triangle>& triangle) { return GetRayHitPoint(ray, triangle) == ray.end; } - Vector3 Ray::DirectionVector() const + Vector3 Ray::DirectionVector() const { return end - start; } - Vector3 Ray::DirectionVectorNormalized() const + Vector3 Ray::DirectionVectorNormalized() const { return DirectionVector().Normalized(); } - Vector3 LineTracer::GetRayHitPoint(const Ray& ray, const Triangle& triangle) + Vector3 LineTracer::GetRayHitPoint(const Ray& ray, const Triangle>& triangle) { constexpr float kEpsilon = std::numeric_limits::epsilon(); diff --git a/source/engines/OpenGL/Camera.cpp b/source/engines/OpenGL/Camera.cpp index 11dd30c3..17e12aa0 100644 --- a/source/engines/OpenGL/Camera.cpp +++ b/source/engines/OpenGL/Camera.cpp @@ -8,12 +8,12 @@ namespace omath::opengl { - Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, const Angle& fov, const float near, const float far) : projection::Camera(position, viewAngles, viewPort, fov, near, far) { } - void Camera::LookAt([[maybe_unused]] const Vector3& target) + void Camera::LookAt([[maybe_unused]] const Vector3& target) { const float distance = m_origin.DistTo(target); const auto delta = target - m_origin; diff --git a/source/engines/Source/Camera.cpp b/source/engines/Source/Camera.cpp index 444e208a..1f00af55 100644 --- a/source/engines/Source/Camera.cpp +++ b/source/engines/Source/Camera.cpp @@ -8,12 +8,12 @@ namespace omath::source { - Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, const projection::FieldOfView& fov, const float near, const float far) : projection::Camera(position, viewAngles, viewPort, fov, near, far) { } - void Camera::LookAt(const Vector3& target) + void Camera::LookAt(const Vector3& target) { const float distance = m_origin.DistTo(target); const auto delta = target - m_origin; diff --git a/source/pathfinding/Astar.cpp b/source/pathfinding/Astar.cpp index 1ea69a6f..4d553e72 100644 --- a/source/pathfinding/Astar.cpp +++ b/source/pathfinding/Astar.cpp @@ -13,15 +13,15 @@ namespace omath::pathfinding { struct PathNode final { - std::optional cameFrom; + std::optional> cameFrom; float gCost = 0.f; }; - std::vector Astar::ReconstructFinalPath(const std::unordered_map& closedList, - const Vector3& current) + std::vector> Astar::ReconstructFinalPath(const std::unordered_map, PathNode>& closedList, + const Vector3& current) { - std::vector path; + std::vector> path; std::optional currentOpt = current; while (currentOpt) @@ -39,7 +39,7 @@ namespace omath::pathfinding std::ranges::reverse(path); return path; } - auto Astar::GetPerfectNode(const std::unordered_map& openList, const Vector3& endVertex) + auto Astar::GetPerfectNode(const std::unordered_map, PathNode>& openList, const Vector3& endVertex) { return std::ranges::min_element(openList, [&endVertex](const auto& a, const auto& b) @@ -50,10 +50,10 @@ namespace omath::pathfinding }); } - std::vector Astar::FindPath(const Vector3& start, const Vector3& end, const NavigationMesh& navMesh) + std::vector> Astar::FindPath(const Vector3& start, const Vector3& end, const NavigationMesh& navMesh) { - std::unordered_map closedList; - std::unordered_map openList; + std::unordered_map, PathNode> closedList; + std::unordered_map, PathNode> openList; auto maybeStartVertex = navMesh.GetClosestVertex(start); auto maybeEndVertex = navMesh.GetClosestVertex(end); diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/NavigationMesh.cpp index 87b9db26..e5491a3a 100644 --- a/source/pathfinding/NavigationMesh.cpp +++ b/source/pathfinding/NavigationMesh.cpp @@ -7,7 +7,7 @@ #include namespace omath::pathfinding { - std::expected NavigationMesh::GetClosestVertex(const Vector3 &point) const + std::expected, std::string> NavigationMesh::GetClosestVertex(const Vector3 &point) const { const auto res = std::ranges::min_element(m_verTextMap, [&point](const auto& a, const auto& b) @@ -21,7 +21,7 @@ namespace omath::pathfinding return res->first; } - const std::vector& NavigationMesh::GetNeighbors(const Vector3 &vertex) const + const std::vector>& NavigationMesh::GetNeighbors(const Vector3 &vertex) const { return m_verTextMap.at(vertex); } @@ -73,18 +73,18 @@ namespace omath::pathfinding while (offset < raw.size()) { - Vector3 vertex; + Vector3 vertex; loadFromVector(raw, offset, vertex); uint16_t neighborsCount; loadFromVector(raw, offset, neighborsCount); - std::vector neighbors; + std::vector> neighbors; neighbors.reserve(neighborsCount); for (size_t i = 0; i < neighborsCount; ++i) { - Vector3 neighbor; + Vector3 neighbor; loadFromVector(raw, offset, neighbor); neighbors.push_back(neighbor); } diff --git a/source/projectile_prediction/ProjPredEngineAVX2.cpp b/source/projectile_prediction/ProjPredEngineAVX2.cpp index 9787e1a1..208d5738 100644 --- a/source/projectile_prediction/ProjPredEngineAVX2.cpp +++ b/source/projectile_prediction/ProjPredEngineAVX2.cpp @@ -6,7 +6,7 @@ namespace omath::projectile_prediction { - std::optional ProjPredEngineAVX2::MaybeCalculateAimPoint(const Projectile& projectile, + std::optional> ProjPredEngineAVX2::MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const { const float bulletGravity = m_gravityConstant * projectile.m_gravityScale; @@ -110,7 +110,7 @@ namespace omath::projectile_prediction m_maximumSimulationTime(simulationTimeStep) { } - std::optional ProjPredEngineAVX2::CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, + std::optional ProjPredEngineAVX2::CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, const float bulletGravity, const float v0, const float time) { if (time <= 0.0f) diff --git a/source/projectile_prediction/ProjPredEngineLegacy.cpp b/source/projectile_prediction/ProjPredEngineLegacy.cpp index 84c5e222..3a8c7652 100644 --- a/source/projectile_prediction/ProjPredEngineLegacy.cpp +++ b/source/projectile_prediction/ProjPredEngineLegacy.cpp @@ -11,7 +11,7 @@ namespace omath::projectile_prediction { } - std::optional ProjPredEngineLegacy::MaybeCalculateAimPoint(const Projectile& projectile, + std::optional> ProjPredEngineLegacy::MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const { for (float time = 0.f; time < m_maximumSimulationTime; time += m_simulationTimeStep) @@ -36,7 +36,7 @@ namespace omath::projectile_prediction std::optional ProjPredEngineLegacy::MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, - const Vector3& targetPosition) const + const Vector3& targetPosition) const { const auto bulletGravity = m_gravityConstant * projectile.m_gravityScale; const auto delta = targetPosition - projectile.m_origin; @@ -57,7 +57,7 @@ namespace omath::projectile_prediction return angles::RadiansToDegrees(angle); } - bool ProjPredEngineLegacy::IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, + bool ProjPredEngineLegacy::IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, const float pitch, const float time) const { const auto yaw = projectile.m_origin.ViewAngleTo(targetPosition).y; diff --git a/source/projectile_prediction/Projectile.cpp b/source/projectile_prediction/Projectile.cpp index c1ce1567..dcf4c26b 100644 --- a/source/projectile_prediction/Projectile.cpp +++ b/source/projectile_prediction/Projectile.cpp @@ -8,7 +8,7 @@ namespace omath::projectile_prediction { - Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const + Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const { auto currentPos = m_origin + source::ForwardVector({source::PitchAngle::FromDegrees(-pitch), source::YawAngle::FromDegrees(yaw), diff --git a/tests/engines/UnitTestOpenGL.cpp b/tests/engines/UnitTestOpenGL.cpp index 461d9d24..db748e87 100644 --- a/tests/engines/UnitTestOpenGL.cpp +++ b/tests/engines/UnitTestOpenGL.cpp @@ -62,7 +62,7 @@ TEST(UnitTestOpenGL, CameraSetAndGetOrigin) { auto cam = omath::opengl::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); - EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); + EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); diff --git a/tests/engines/UnitTestSourceEngine.cpp b/tests/engines/UnitTestSourceEngine.cpp index cb73a44d..876557ea 100644 --- a/tests/engines/UnitTestSourceEngine.cpp +++ b/tests/engines/UnitTestSourceEngine.cpp @@ -62,7 +62,7 @@ TEST(UnitTestSourceEngine, CameraSetAndGetOrigin) { auto cam = omath::source::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); - EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); + EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); diff --git a/tests/general/UnitTestLineTrace.cpp b/tests/general/UnitTestLineTrace.cpp index 4e37916d..3c0bf044 100644 --- a/tests/general/UnitTestLineTrace.cpp +++ b/tests/general/UnitTestLineTrace.cpp @@ -10,10 +10,10 @@ class LineTracerTest : public ::testing::Test { protected: // Set up common variables for use in each test - Vector3 vertex1{0.0f, 0.0f, 0.0f}; - Vector3 vertex2{1.0f, 0.0f, 0.0f}; - Vector3 vertex3{0.0f, 1.0f, 0.0f}; - Triangle triangle{vertex1, vertex2, vertex3}; + Vector3 vertex1{0.0f, 0.0f, 0.0f}; + Vector3 vertex2{1.0f, 0.0f, 0.0f}; + Vector3 vertex3{0.0f, 1.0f, 0.0f}; + Triangle> triangle{vertex1, vertex2, vertex3}; }; // Test that a ray intersecting the triangle returns false for CanTraceLine @@ -71,7 +71,7 @@ TEST_F(LineTracerTest, TriangleFarBeyondRayEndPoint) constexpr Ray ray{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}; // Define a triangle far beyond the ray's endpoint - constexpr Triangle distantTriangle{ + constexpr Triangle> distantTriangle{ {1000.0f, 1000.0f, 1000.0f}, {1001.0f, 1000.0f, 1000.0f}, {1000.0f, 1001.0f, 1000.0f} }; diff --git a/tests/general/UnitTestTriangle.cpp b/tests/general/UnitTestTriangle.cpp index 258cb972..8e352289 100644 --- a/tests/general/UnitTestTriangle.cpp +++ b/tests/general/UnitTestTriangle.cpp @@ -13,28 +13,28 @@ class UnitTestTriangle : public ::testing::Test { protected: // Define some Triangles to use in tests - Triangle t1; - Triangle t2; - Triangle t3; + Triangle> t1; + Triangle> t2; + Triangle> t3; constexpr void SetUp() override { // Triangle with vertices (0, 0, 0), (1, 0, 0), (0, 1, 0) - t1 = Triangle( + t1 = Triangle>( Vector3(0.0f, 0.0f, 0.0f), Vector3(1.0f, 0.0f, 0.0f), Vector3(0.0f, 1.0f, 0.0f) ); // Triangle with vertices (1, 2, 3), (4, 5, 6), (7, 8, 9) - t2 = Triangle( + t2 = Triangle>( Vector3(1.0f, 2.0f, 3.0f), Vector3(4.0f, 5.0f, 6.0f), Vector3(7.0f, 8.0f, 9.0f) ); // An isosceles right triangle - t3 = Triangle( + t3 = Triangle>( Vector3(0.0f, 0.0f, 0.0f), Vector3(2.0f, 0.0f, 0.0f), Vector3(0.0f, 2.0f, 0.0f) @@ -45,7 +45,7 @@ class UnitTestTriangle : public ::testing::Test // Test constructor and vertices TEST_F(UnitTestTriangle, Constructor) { - constexpr Triangle t( + constexpr Triangle> t( Vector3(1.0f, 2.0f, 3.0f), Vector3(4.0f, 5.0f, 6.0f), Vector3(7.0f, 8.0f, 9.0f) @@ -113,7 +113,7 @@ TEST_F(UnitTestTriangle, SideVectors) TEST_F(UnitTestTriangle, IsRectangular) { - EXPECT_TRUE(Triangle({2,0,0}, {}, {0,2,0}).IsRectangular()); + EXPECT_TRUE(Triangle>({2,0,0}, {}, {0,2,0}).IsRectangular()); } // Test midpoint TEST_F(UnitTestTriangle, MidPoint) diff --git a/tests/general/UnitTestVector2.cpp b/tests/general/UnitTestVector2.cpp index 0255363a..918115de 100644 --- a/tests/general/UnitTestVector2.cpp +++ b/tests/general/UnitTestVector2.cpp @@ -12,8 +12,8 @@ using namespace omath; class UnitTestVector2 : public ::testing::Test { protected: - Vector2 v1; - Vector2 v2; + Vector2 v1; + Vector2 v2; constexpr void SetUp() override { @@ -25,7 +25,7 @@ class UnitTestVector2 : public ::testing::Test // Test constructor and default values TEST_F(UnitTestVector2, Constructor_Default) { - constexpr Vector2 v; + constexpr Vector2 v; EXPECT_FLOAT_EQ(v.x, 0.0f); EXPECT_FLOAT_EQ(v.y, 0.0f); } diff --git a/tests/general/UnitTestVector3.cpp b/tests/general/UnitTestVector3.cpp index d7560a65..678cdb19 100644 --- a/tests/general/UnitTestVector3.cpp +++ b/tests/general/UnitTestVector3.cpp @@ -13,8 +13,8 @@ using namespace omath; class UnitTestVector3 : public ::testing::Test { protected: - Vector3 v1; - Vector3 v2; + Vector3 v1; + Vector3 v2; void SetUp() override { @@ -26,7 +26,7 @@ class UnitTestVector3 : public ::testing::Test // Test constructor and default values TEST_F(UnitTestVector3, Constructor_Default) { - constexpr Vector3 v; + constexpr Vector3 v; EXPECT_FLOAT_EQ(v.x, 0.0f); EXPECT_FLOAT_EQ(v.y, 0.0f); EXPECT_FLOAT_EQ(v.z, 0.0f); @@ -34,7 +34,7 @@ TEST_F(UnitTestVector3, Constructor_Default) TEST_F(UnitTestVector3, Constructor_Values) { - constexpr Vector3 v(1.0f, 2.0f, 3.0f); + constexpr Vector3 v(1.0f, 2.0f, 3.0f); EXPECT_FLOAT_EQ(v.x, 1.0f); EXPECT_FLOAT_EQ(v.y, 2.0f); EXPECT_FLOAT_EQ(v.z, 3.0f); diff --git a/tests/general/UnitTestVector4.cpp b/tests/general/UnitTestVector4.cpp index 07ca8989..3f6351a6 100644 --- a/tests/general/UnitTestVector4.cpp +++ b/tests/general/UnitTestVector4.cpp @@ -14,8 +14,8 @@ using namespace omath; class UnitTestVector4 : public ::testing::Test { protected: - Vector4 v1; - Vector4 v2; + Vector4 v1; + Vector4 v2; void SetUp() override { @@ -27,7 +27,7 @@ class UnitTestVector4 : public ::testing::Test // Test constructor and default values TEST_F(UnitTestVector4, Constructor_Default) { - constexpr Vector4 v; + constexpr Vector4 v; EXPECT_FLOAT_EQ(v.x, 0.0f); EXPECT_FLOAT_EQ(v.y, 0.0f); EXPECT_FLOAT_EQ(v.z, 0.0f); From a0f746b84c26a4bcfe6b96982eca15c0a6b23b5e Mon Sep 17 00:00:00 2001 From: Vladislav Alpatov Date: Sat, 1 Mar 2025 21:15:26 +0300 Subject: [PATCH 178/795] fixed style --- include/omath/Vector3.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index 9c2e9c3f..6fe1229d 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -172,7 +172,6 @@ namespace omath [[nodiscard]] constexpr Vector3 operator-() const { - return Vector3{-this->x, -this->y, -z}; } From 978656b573507b5e82652aed5f8e4e7a7dada01e Mon Sep 17 00:00:00 2001 From: Vladislav Alpatov Date: Sat, 1 Mar 2025 21:17:03 +0300 Subject: [PATCH 179/795] refactored --- include/omath/Vector3.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index 6fe1229d..af90ed6e 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -24,7 +24,7 @@ namespace omath class Vector3 : public Vector2 { public: - Type z = 0.f; + Type z = static_cast(0); constexpr Vector3(const Type& x, const Type& y, const Type& z) : Vector2(x, y), z(z) { } constexpr Vector3() : Vector2() {}; @@ -172,7 +172,7 @@ namespace omath [[nodiscard]] constexpr Vector3 operator-() const { - return Vector3{-this->x, -this->y, -z}; + return {-this->x, -this->y, -z}; } [[nodiscard]] constexpr Vector3 operator+(const Vector3& v) const From ed372a1c7819eeec894f7234bff5325704922950 Mon Sep 17 00:00:00 2001 From: Vladislav Alpatov Date: Sat, 1 Mar 2025 21:22:44 +0300 Subject: [PATCH 180/795] fixed for clang support --- CMakeLists.txt | 3 +++ CMakePresets.json | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1631f66..727a9d1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,9 @@ set_target_properties(omath PROPERTIES CXX_STANDARD 23 CXX_STANDARD_REQUIRED ON) +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options(omath PRIVATE -mavx2 -mfma) +endif() target_compile_features(omath PUBLIC cxx_std_23) diff --git a/CMakePresets.json b/CMakePresets.json index d67436b9..74671b4a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,8 +8,8 @@ "binaryDir": "${sourceDir}/cmake-build/build/${presetName}", "installDir": "${sourceDir}/cmake-build/install/${presetName}", "cacheVariables": { - "CMAKE_C_COMPILER": "cl.exe", - "CMAKE_CXX_COMPILER": "cl.exe" + "CMAKE_C_COMPILER": "clang.exe", + "CMAKE_CXX_COMPILER": "clang++.exe" }, "condition": { "type": "equals", From 39ab9d065d89d0cdd34f109c43490a5ec688e80f Mon Sep 17 00:00:00 2001 From: Vladislav Alpatov Date: Sat, 1 Mar 2025 21:30:53 +0300 Subject: [PATCH 181/795] added option to disable avx2 --- CMakeLists.txt | 4 ++++ source/projectile_prediction/ProjPredEngineAVX2.cpp | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 727a9d1c..63acd4a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ include(CMakePackageConfigHelpers) option(OMATH_BUILD_TESTS "Build unit tests" ON) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) +option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) if (OMATH_BUILD_AS_SHARED_LIBRARY) @@ -30,6 +31,9 @@ endif() target_compile_features(omath PUBLIC cxx_std_23) +if (OMATH_USE_AVX2) + target_compile_definitions(omath PUBLIC OMATH_USE_AVX2) +endif() add_subdirectory(source) diff --git a/source/projectile_prediction/ProjPredEngineAVX2.cpp b/source/projectile_prediction/ProjPredEngineAVX2.cpp index 208d5738..9c63fd94 100644 --- a/source/projectile_prediction/ProjPredEngineAVX2.cpp +++ b/source/projectile_prediction/ProjPredEngineAVX2.cpp @@ -2,13 +2,14 @@ // Created by Vlad on 2/23/2025. // #include "omath/projectile_prediction/ProjPredEngineAVX2.hpp" - +#include "source_location" namespace omath::projectile_prediction { - std::optional> ProjPredEngineAVX2::MaybeCalculateAimPoint(const Projectile& projectile, - const Target& target) const + std::optional> ProjPredEngineAVX2::MaybeCalculateAimPoint([[maybe_unused]] const Projectile& projectile, + [[maybe_unused]] const Target& target) const { +#ifdef OMATH_USE_AVX2 const float bulletGravity = m_gravityConstant * projectile.m_gravityScale; const float v0 = projectile.m_launchSpeed; const float v0Sqr = v0 * v0; @@ -134,5 +135,7 @@ namespace omath::projectile_prediction const float d = std::sqrt(dSqr); const float tanTheta = term / d; return angles::RadiansToDegrees(std::atan(tanTheta)); +#endif + throw std::runtime_error(std::format("{} AVX2 feature is not enabled!", std::source_location::current().function_name())); } } // namespace omath::projectile_prediction From c3202a3bc3d12bfbc710ddca90cc0a15849374a6 Mon Sep 17 00:00:00 2001 From: Vladislav Alpatov Date: Sat, 1 Mar 2025 21:32:02 +0300 Subject: [PATCH 182/795] patch --- CMakePresets.json | 4 ++-- .../ProjPredEngineAVX2.cpp | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 74671b4a..d67436b9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,8 +8,8 @@ "binaryDir": "${sourceDir}/cmake-build/build/${presetName}", "installDir": "${sourceDir}/cmake-build/install/${presetName}", "cacheVariables": { - "CMAKE_C_COMPILER": "clang.exe", - "CMAKE_CXX_COMPILER": "clang++.exe" + "CMAKE_C_COMPILER": "cl.exe", + "CMAKE_CXX_COMPILER": "cl.exe" }, "condition": { "type": "equals", diff --git a/source/projectile_prediction/ProjPredEngineAVX2.cpp b/source/projectile_prediction/ProjPredEngineAVX2.cpp index 9c63fd94..ad4496ff 100644 --- a/source/projectile_prediction/ProjPredEngineAVX2.cpp +++ b/source/projectile_prediction/ProjPredEngineAVX2.cpp @@ -6,8 +6,9 @@ namespace omath::projectile_prediction { - std::optional> ProjPredEngineAVX2::MaybeCalculateAimPoint([[maybe_unused]] const Projectile& projectile, - [[maybe_unused]] const Target& target) const + std::optional> + ProjPredEngineAVX2::MaybeCalculateAimPoint([[maybe_unused]] const Projectile& projectile, + [[maybe_unused]] const Target& target) const { #ifdef OMATH_USE_AVX2 const float bulletGravity = m_gravityConstant * projectile.m_gravityScale; @@ -32,8 +33,8 @@ namespace omath::projectile_prediction _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.y), times, _mm256_set1_ps(target.m_origin.y)); const __m256 timesSq = _mm256_mul_ps(times, times); const __m256 targetZ = _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.z), times, - _mm256_fnmadd_ps(_mm256_set1_ps(0.5f * m_gravityConstant), timesSq, - _mm256_set1_ps(target.m_origin.z))); + _mm256_fnmadd_ps(_mm256_set1_ps(0.5f * m_gravityConstant), timesSq, + _mm256_set1_ps(target.m_origin.z))); const __m256 deltaX = _mm256_sub_ps(targetX, _mm256_set1_ps(projOrigin.x)); const __m256 deltaY = _mm256_sub_ps(targetY, _mm256_set1_ps(projOrigin.y)); @@ -111,8 +112,9 @@ namespace omath::projectile_prediction m_maximumSimulationTime(simulationTimeStep) { } - std::optional ProjPredEngineAVX2::CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, - const float bulletGravity, const float v0, const float time) + std::optional ProjPredEngineAVX2::CalculatePitch(const Vector3& projOrigin, + const Vector3& targetPos, const float bulletGravity, + const float v0, const float time) { if (time <= 0.0f) return std::nullopt; @@ -135,7 +137,9 @@ namespace omath::projectile_prediction const float d = std::sqrt(dSqr); const float tanTheta = term / d; return angles::RadiansToDegrees(std::atan(tanTheta)); +#else + throw std::runtime_error( + std::format("{} AVX2 feature is not enabled!", std::source_location::current().function_name())); #endif - throw std::runtime_error(std::format("{} AVX2 feature is not enabled!", std::source_location::current().function_name())); } } // namespace omath::projectile_prediction From 58e2c3b5b7f6104bc5b49fa5f55729f61692c04a Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 14 Mar 2025 19:43:50 +0300 Subject: [PATCH 183/795] fixed members inititalization miss match --- source/projectile_prediction/ProjPredEngineAVX2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/projectile_prediction/ProjPredEngineAVX2.cpp b/source/projectile_prediction/ProjPredEngineAVX2.cpp index ad4496ff..8a4f8f63 100644 --- a/source/projectile_prediction/ProjPredEngineAVX2.cpp +++ b/source/projectile_prediction/ProjPredEngineAVX2.cpp @@ -108,8 +108,8 @@ namespace omath::projectile_prediction } ProjPredEngineAVX2::ProjPredEngineAVX2(const float gravityConstant, const float simulationTimeStep, const float maximumSimulationTime) : - m_gravityConstant(gravityConstant), m_simulationTimeStep(maximumSimulationTime), - m_maximumSimulationTime(simulationTimeStep) + m_gravityConstant(gravityConstant), m_simulationTimeStep(simulationTimeStep), + m_maximumSimulationTime(maximumSimulationTime) { } std::optional ProjPredEngineAVX2::CalculatePitch(const Vector3& projOrigin, From 4200ef63a6ab10b99b9b6be9b470e18491a3ab8d Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 15 Mar 2025 18:53:18 +0300 Subject: [PATCH 184/795] improved mat & camera interface --- include/omath/Mat.hpp | 12 +++++++++--- include/omath/projection/Camera.hpp | 22 ++++++++++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 9d91ab53..17964cf4 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -34,11 +34,13 @@ namespace omath class Mat final { public: - constexpr Mat() + constexpr Mat() noexcept { Clear(); } - constexpr static MatStoreType GetStoreOrdering() + + [[nodiscard]] + constexpr static MatStoreType GetStoreOrdering() noexcept { return StoreType; } @@ -72,6 +74,7 @@ namespace omath m_data = other.m_data; } + [[nodiscard]] constexpr Type& operator[](const size_t row, const size_t col) { return At(row, col); @@ -100,7 +103,8 @@ namespace omath return {Rows, Columns}; } - [[nodiscard]] constexpr const Type& At(const size_t rowIndex, const size_t columnIndex) const + [[nodiscard]] + constexpr const Type& At(const size_t rowIndex, const size_t columnIndex) const { if (rowIndex >= Rows || columnIndex >= Columns) throw std::out_of_range("Index out of range"); @@ -177,6 +181,7 @@ namespace omath return *this = *this * other; } + [[nodiscard]] constexpr Mat operator*(const Type& f) const noexcept { Mat result(*this); @@ -192,6 +197,7 @@ namespace omath return *this; } + [[nodiscard]] constexpr Mat operator/(const Type& f) const noexcept { Mat result(*this); diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index 4d967374..c6bacbba 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -38,7 +38,7 @@ namespace omath::projection { } - + protected: virtual void LookAt(const Vector3& target) = 0; [[nodiscard]] virtual Mat4x4Type CalcViewMatrix() const = 0; @@ -49,41 +49,44 @@ namespace omath::projection { return CalcProjectionMatrix() * CalcViewMatrix(); } + public: + + [[nodiscard]] const Mat4x4Type& GetViewProjectionMatrix() const + { + if (!m_viewProjectionMatrix.has_value()) + m_viewProjectionMatrix = CalcViewProjectionMatrix(); + + return m_viewProjectionMatrix.value(); + } void SetFieldOfView(const FieldOfView& fov) { m_fieldOfView = fov; - m_viewProjectionMatrix = CalcViewProjectionMatrix(); } void SetNearPlane(const float near) { m_nearPlaneDistance = near; - m_viewProjectionMatrix = CalcViewProjectionMatrix(); } void SetFarPlane(const float far) { m_farPlaneDistance = far; - m_viewProjectionMatrix = CalcViewProjectionMatrix(); } void SetViewAngles(const ViewAnglesType& viewAngles) { m_viewAngles = viewAngles; - m_viewProjectionMatrix = CalcViewProjectionMatrix(); } void SetOrigin(const Vector3& origin) { m_origin = origin; - m_viewProjectionMatrix = CalcViewProjectionMatrix(); } void SetViewPort(const ViewPort& viewPort) { m_viewPort = viewPort; - m_viewProjectionMatrix = CalcViewProjectionMatrix(); } [[nodiscard]] const FieldOfView& GetFieldOfView() const @@ -113,10 +116,9 @@ namespace omath::projection [[nodiscard]] std::expected, Error> WorldToScreen(const Vector3& worldPosition) const { - if (!m_viewProjectionMatrix.has_value()) - m_viewProjectionMatrix = CalcViewProjectionMatrix(); + const auto& viewProjMatrix = GetViewProjectionMatrix(); - auto projected = m_viewProjectionMatrix.value() * MatColumnFromVector(worldPosition); + auto projected = viewProjMatrix * MatColumnFromVector(worldPosition); if (projected.At(3, 0) == 0.0f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); From 934ca0da0a2e8a8062fbfb2ad44f3c08eaee25f7 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 15 Mar 2025 18:57:41 +0300 Subject: [PATCH 185/795] added explicit constructor for angle and comment for angle --- include/omath/Angle.hpp | 8 ++++---- include/omath/Triangle.hpp | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index 4ae0dee2..45e8e067 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -19,7 +19,7 @@ namespace omath class Angle { Type m_angle; - constexpr Angle(const Type& degrees) + constexpr explicit Angle(const Type& degrees) { if constexpr (flags == AngleFlags::Normalized) m_angle = angles::WrapAngle(degrees, min, max); @@ -36,7 +36,7 @@ namespace omath [[nodiscard]] constexpr static Angle FromDegrees(const Type& degrees) { - return {degrees}; + return Angle{degrees}; } constexpr Angle() : m_angle(0) { @@ -45,7 +45,7 @@ namespace omath [[nodiscard]] constexpr static Angle FromRadians(const Type& degrees) { - return {angles::RadiansToDegrees(degrees)}; + return Angle{angles::RadiansToDegrees(degrees)}; } [[nodiscard]] @@ -146,7 +146,7 @@ namespace omath [[nodiscard]] constexpr Angle operator-() const { - return {-m_angle}; + return Angle{-m_angle}; } }; } diff --git a/include/omath/Triangle.hpp b/include/omath/Triangle.hpp index 9779982a..a2fc92e1 100644 --- a/include/omath/Triangle.hpp +++ b/include/omath/Triangle.hpp @@ -6,6 +6,16 @@ namespace omath { + /* + |\ + | \ + a | \ hypot + | \ + ----- + b + */ + + template class Triangle final { From 169011e238e41b019b08e7ad68e7503bc1b3632d Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 15 Mar 2025 19:11:31 +0300 Subject: [PATCH 186/795] fixed Vec3 Vec4 --- include/omath/Vector3.hpp | 26 +++++++++++++------------- include/omath/Vector4.hpp | 8 ++++---- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index af90ed6e..588c608e 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -110,45 +110,45 @@ namespace omath return *this; } - [[nodiscard]] constexpr float DistToSqr(const Vector3& vOther) const + [[nodiscard]] constexpr Type DistToSqr(const Vector3& vOther) const { return (*this - vOther).LengthSqr(); } - [[nodiscard]] constexpr float Dot(const Vector3& vOther) const + [[nodiscard]] constexpr Type Dot(const Vector3& vOther) const { return Vector2::Dot(vOther) + z * vOther.z; } #ifndef _MSC_VER - [[nodiscard]] constexpr float Length() const + [[nodiscard]] constexpr Type Length() const { return std::hypot(x, y, z); } - [[nodiscard]] constexpr float Length2D() const + [[nodiscard]] constexpr Type Length2D() const { return Vector2::Length(); } - [[nodiscard]] float DistTo(const Vector3& vOther) const + [[nodiscard]] Type DistTo(const Vector3& vOther) const { return (*this - vOther).Length(); } [[nodiscard]] constexpr Vector3 Normalized() const { - const float length = this->Length(); + const Type length = this->Length(); return length != 0 ? *this / length : *this; } #else - [[nodiscard]] float Length() const + [[nodiscard]] Type Length() const { return std::hypot(this->x, this->y, z); } [[nodiscard]] Vector3 Normalized() const { - const float length = this->Length(); + const Type length = this->Length(); return length != 0 ? *this / length : *this; } @@ -158,14 +158,14 @@ namespace omath return Vector2::Length(); } - [[nodiscard]] float DistTo(const Vector3& vOther) const + [[nodiscard]] Type DistTo(const Vector3& vOther) const { return (*this - vOther).Length(); } #endif - [[nodiscard]] constexpr float LengthSqr() const + [[nodiscard]] constexpr Type LengthSqr() const { return Vector2::LengthSqr() + z * z; } @@ -214,7 +214,7 @@ namespace omath this->x * v.y - this->y * v.x }; } - [[nodiscard]] constexpr float Sum() const + [[nodiscard]] constexpr Type Sum() const { return Sum2D() + z; } @@ -238,12 +238,12 @@ namespace omath return false; } - [[nodiscard]] constexpr float Sum2D() const + [[nodiscard]] constexpr Type Sum2D() const { return Vector2::Sum(); } - [[nodiscard]] constexpr std::tuple AsTuple() const + [[nodiscard]] constexpr std::tuple AsTuple() const { return std::make_tuple(this->x, this->y, z); } diff --git a/include/omath/Vector4.hpp b/include/omath/Vector4.hpp index 05530ee1..f4fcaddb 100644 --- a/include/omath/Vector4.hpp +++ b/include/omath/Vector4.hpp @@ -81,7 +81,7 @@ namespace omath return Vector3::LengthSqr() + w * w; } - [[nodiscard]] constexpr float Dot(const Vector4& vOther) const + [[nodiscard]] constexpr Type Dot(const Vector4& vOther) const { return Vector3::Dot(vOther) + w * vOther.w; } @@ -98,7 +98,7 @@ namespace omath return *this; } - constexpr Vector4& Clamp(const float min, const float max) + constexpr Vector4& Clamp(const Type& min, const Type& max) { this->x = std::clamp(this->x, min, max); this->y = std::clamp(this->y, min, max); @@ -126,7 +126,7 @@ namespace omath } [[nodiscard]] - constexpr Vector4 operator*(const float scalar) const + constexpr Vector4 operator*(const Type& scalar) const { return {this->x * scalar, this->y * scalar, this->z * scalar, w * scalar}; } @@ -138,7 +138,7 @@ namespace omath } [[nodiscard]] - constexpr Vector4 operator/(const float scalar) const + constexpr Vector4 operator/(const Type& scalar) const { return {this->x / scalar, this->y / scalar, this->z / scalar, w / scalar}; } From d1d06c24ca4c9fad5e9fdb9a236f57b9c11f2101 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 15 Mar 2025 19:13:06 +0300 Subject: [PATCH 187/795] unit tests buid off by defalut --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 63acd4a4..a8229e3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(omath VERSION 1.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) -option(OMATH_BUILD_TESTS "Build unit tests" ON) +option(OMATH_BUILD_TESTS "Build unit tests" OFF) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) From c76f6e91b00f3c40b928ca461602cfdfd8b782ea Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 17 Mar 2025 02:41:08 +0300 Subject: [PATCH 188/795] fix --- include/omath/Angle.hpp | 2 -- include/omath/engines/Source/Camera.hpp | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/include/omath/Angle.hpp b/include/omath/Angle.hpp index 45e8e067..253604df 100644 --- a/include/omath/Angle.hpp +++ b/include/omath/Angle.hpp @@ -96,7 +96,6 @@ namespace omath return Cos() / Sin(); } - [[nodiscard]] constexpr Angle& operator+=(const Angle& other) { if constexpr (flags == AngleFlags::Normalized) @@ -116,7 +115,6 @@ namespace omath [[nodiscard]] constexpr std::partial_ordering operator<=>(const Angle& other) const = default; - [[nodiscard]] constexpr Angle& operator-=(const Angle& other) { return operator+=(-other); diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/Source/Camera.hpp index ef895baf..a0b9f771 100644 --- a/include/omath/engines/Source/Camera.hpp +++ b/include/omath/engines/Source/Camera.hpp @@ -13,6 +13,7 @@ namespace omath::source Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, const Angle& fov, float near, float far); void LookAt(const Vector3& target) override; + protected: [[nodiscard]] Mat4x4 CalcViewMatrix() const override; [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; }; From 6dd72d2448d568e87b40943985a01cba09af1d86 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 17 Mar 2025 03:56:09 +0300 Subject: [PATCH 189/795] added convertors --- CMakeLists.txt | 13 +++++++++---- include/omath/Vector2.hpp | 34 ++++++++++++++++++++++++++++------ include/omath/Vector3.hpp | 7 +++---- include/omath/Vector4.hpp | 18 ++++++++++++++++++ 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8229e3a..3c94fc30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ option(OMATH_BUILD_TESTS "Build unit tests" OFF) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) - +option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" ON) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Vector3.cpp) @@ -17,6 +17,14 @@ else() add_library(omath STATIC source/Matrix.cpp) endif() +if (OMATH_IMGUI_INTEGRATION) + target_compile_definitions(omath PUBLIC OMATH_IMGUI_INTEGRATION) +endif() + +if (OMATH_USE_AVX2) + target_compile_definitions(omath PUBLIC OMATH_USE_AVX2) +endif() + set_target_properties(omath PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" @@ -31,9 +39,6 @@ endif() target_compile_features(omath PUBLIC cxx_std_23) -if (OMATH_USE_AVX2) - target_compile_definitions(omath PUBLIC OMATH_USE_AVX2) -endif() add_subdirectory(source) diff --git a/include/omath/Vector2.hpp b/include/omath/Vector2.hpp index 60d2ed56..5873eb7c 100644 --- a/include/omath/Vector2.hpp +++ b/include/omath/Vector2.hpp @@ -3,13 +3,19 @@ // #pragma once -#include #include +#include + +#ifdef OMATH_IMGUI_INTEGRATION + class ImVec2; +#endif + namespace omath { - template requires std::is_arithmetic_v + template + requires std::is_arithmetic_v class Vector2 { public: @@ -19,7 +25,9 @@ namespace omath // Constructors constexpr Vector2() = default; - constexpr Vector2(const Type& x, const Type& y) : x(x), y(y) {} + constexpr Vector2(const Type& x, const Type& y) : x(x), y(y) + { + } // Equality operators [[nodiscard]] @@ -145,7 +153,7 @@ namespace omath constexpr Vector2& Abs() { - //FIXME: Replace with std::abs, if it will become constexprable + // FIXME: Replace with std::abs, if it will become constexprable x = x < 0 ? -x : x; y = y < 0 ? -y : y; return *this; @@ -188,7 +196,7 @@ namespace omath return {x / fl, y / fl}; } - // Sum of elements + // Sum of elements [[nodiscard]] constexpr Type Sum() const { return x + y; @@ -199,5 +207,19 @@ namespace omath { return std::make_tuple(x, y); } + +#ifdef OMATH_IMGUI_INTEGRATION + [[nodiscard]] + const ImVec2& ToImVec2() const + { + return *reinterpret_cast(this); + } + + [[nodiscard]] + ImVec2& ToImVec2() + { + return *reinterpret_cast(this); + } +#endif }; -} \ No newline at end of file +} // namespace omath diff --git a/include/omath/Vector3.hpp b/include/omath/Vector3.hpp index 588c608e..c3ffcd0b 100644 --- a/include/omath/Vector3.hpp +++ b/include/omath/Vector3.hpp @@ -4,13 +4,12 @@ #pragma once +#include "Vector2.hpp" #include +#include #include -#include "omath/Vector2.hpp" #include "omath/Angle.hpp" -#include -#include - +#include "omath/Vector2.hpp" namespace omath { diff --git a/include/omath/Vector4.hpp b/include/omath/Vector4.hpp index f4fcaddb..f9274455 100644 --- a/include/omath/Vector4.hpp +++ b/include/omath/Vector4.hpp @@ -6,6 +6,10 @@ #include #include +#ifdef OMATH_IMGUI_INTEGRATION + class ImVec4; +#endif + namespace omath { template @@ -154,5 +158,19 @@ namespace omath { return Vector3::Sum() + w; } + +#ifdef OMATH_IMGUI_INTEGRATION + [[nodiscard]] + constexpr ImVec4& ToImVec4() const + { + return *reinterpret_cast(this); + } + + [[nodiscard]] + constexpr ImVec2& ToImVec2() + { + return *reinterpret_cast(this); + } +#endif }; } From 8fc107ec0fb4abfcbad025b1a48a813c5e4c64af Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 17 Mar 2025 04:07:21 +0300 Subject: [PATCH 190/795] fix --- include/omath/Vector2.hpp | 23 +++-------------------- include/omath/Vector4.hpp | 21 +++++++++------------ 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/include/omath/Vector2.hpp b/include/omath/Vector2.hpp index 5873eb7c..f2ae0c36 100644 --- a/include/omath/Vector2.hpp +++ b/include/omath/Vector2.hpp @@ -7,7 +7,7 @@ #include #ifdef OMATH_IMGUI_INTEGRATION - class ImVec2; +#include #endif @@ -159,17 +159,6 @@ namespace omath return *this; } - template - [[nodiscard]] constexpr const type& As() const - { - return *reinterpret_cast(this); - } - template - [[nodiscard]] constexpr type& As() - { - return *reinterpret_cast(this); - } - [[nodiscard]] constexpr Vector2 operator-() const { return {-x, -y}; @@ -210,15 +199,9 @@ namespace omath #ifdef OMATH_IMGUI_INTEGRATION [[nodiscard]] - const ImVec2& ToImVec2() const - { - return *reinterpret_cast(this); - } - - [[nodiscard]] - ImVec2& ToImVec2() + ImVec2 ToImVec2() const { - return *reinterpret_cast(this); + return {static_cast(this->x), static_cast(this->y)}; } #endif }; diff --git a/include/omath/Vector4.hpp b/include/omath/Vector4.hpp index f9274455..258cf738 100644 --- a/include/omath/Vector4.hpp +++ b/include/omath/Vector4.hpp @@ -6,9 +6,6 @@ #include #include -#ifdef OMATH_IMGUI_INTEGRATION - class ImVec4; -#endif namespace omath { @@ -161,15 +158,15 @@ namespace omath #ifdef OMATH_IMGUI_INTEGRATION [[nodiscard]] - constexpr ImVec4& ToImVec4() const - { - return *reinterpret_cast(this); - } - - [[nodiscard]] - constexpr ImVec2& ToImVec2() - { - return *reinterpret_cast(this); + ImVec4 ToImVec4() const + { + return + { + static_cast(this->x), + static_cast(this->y), + static_cast(this->z), + static_cast(w), + }; } #endif }; From e5d0adf24765e399c5dadfbaab763317f84735f3 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 17 Mar 2025 04:11:49 +0300 Subject: [PATCH 191/795] changed default option --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c94fc30..b2bf4526 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ option(OMATH_BUILD_TESTS "Build unit tests" OFF) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) -option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" ON) +option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Vector3.cpp) From f5c271cfa6a8954b0be80f86beaaeff0bd0788d7 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 17 Mar 2025 05:27:00 +0300 Subject: [PATCH 192/795] changed naming of engines section --- .../{OpenGL => opengl_engine}/Camera.hpp | 2 +- .../{OpenGL => opengl_engine}/Constants.hpp | 2 +- .../{OpenGL => opengl_engine}/Formulas.hpp | 2 +- .../{Source => source_engine}/Camera.hpp | 2 +- .../{Source => source_engine}/Constants.hpp | 3 ++- .../{Source => source_engine}/Formulas.hpp | 2 +- source/engines/CMakeLists.txt | 4 ++-- .../{OpenGL => opengl_engine}/CMakeLists.txt | 0 .../{OpenGL => opengl_engine}/Camera.cpp | 8 +++---- .../{Source => source_engine}/CMakeLists.txt | 0 .../{Source => source_engine}/Camera.cpp | 8 +++---- source/projectile_prediction/Projectile.cpp | 8 +++---- tests/engines/UnitTestOpenGL.cpp | 24 +++++++++---------- tests/engines/UnitTestSourceEngine.cpp | 24 +++++++++---------- tests/general/UnitTestProjection.cpp | 5 ++-- 15 files changed, 47 insertions(+), 47 deletions(-) rename include/omath/engines/{OpenGL => opengl_engine}/Camera.hpp (95%) rename include/omath/engines/{OpenGL => opengl_engine}/Constants.hpp (96%) rename include/omath/engines/{OpenGL => opengl_engine}/Formulas.hpp (98%) rename include/omath/engines/{Source => source_engine}/Camera.hpp (95%) rename include/omath/engines/{Source => source_engine}/Constants.hpp (96%) rename include/omath/engines/{Source => source_engine}/Formulas.hpp (98%) rename source/engines/{OpenGL => opengl_engine}/CMakeLists.txt (100%) rename source/engines/{OpenGL => opengl_engine}/Camera.cpp (84%) rename source/engines/{Source => source_engine}/CMakeLists.txt (100%) rename source/engines/{Source => source_engine}/Camera.cpp (84%) diff --git a/include/omath/engines/OpenGL/Camera.hpp b/include/omath/engines/opengl_engine/Camera.hpp similarity index 95% rename from include/omath/engines/OpenGL/Camera.hpp rename to include/omath/engines/opengl_engine/Camera.hpp index 0f3e7fbb..bbe29025 100644 --- a/include/omath/engines/OpenGL/Camera.hpp +++ b/include/omath/engines/opengl_engine/Camera.hpp @@ -5,7 +5,7 @@ #include "Constants.hpp" #include "omath/projection/Camera.hpp" -namespace omath::opengl +namespace omath::opengl_engine { class Camera final : public projection::Camera { diff --git a/include/omath/engines/OpenGL/Constants.hpp b/include/omath/engines/opengl_engine/Constants.hpp similarity index 96% rename from include/omath/engines/OpenGL/Constants.hpp rename to include/omath/engines/opengl_engine/Constants.hpp index 6cedf43f..d06873b8 100644 --- a/include/omath/engines/OpenGL/Constants.hpp +++ b/include/omath/engines/opengl_engine/Constants.hpp @@ -8,7 +8,7 @@ #include #include -namespace omath::opengl +namespace omath::opengl_engine { constexpr Vector3 kAbsUp = {0, 1, 0}; constexpr Vector3 kAbsRight = {1, 0, 0}; diff --git a/include/omath/engines/OpenGL/Formulas.hpp b/include/omath/engines/opengl_engine/Formulas.hpp similarity index 98% rename from include/omath/engines/OpenGL/Formulas.hpp rename to include/omath/engines/opengl_engine/Formulas.hpp index c551619c..b63ffdcc 100644 --- a/include/omath/engines/OpenGL/Formulas.hpp +++ b/include/omath/engines/opengl_engine/Formulas.hpp @@ -5,7 +5,7 @@ #include "Constants.hpp" -namespace omath::opengl +namespace omath::opengl_engine { [[nodiscard]] inline Vector3 ForwardVector(const ViewAngles& angles) diff --git a/include/omath/engines/Source/Camera.hpp b/include/omath/engines/source_engine/Camera.hpp similarity index 95% rename from include/omath/engines/Source/Camera.hpp rename to include/omath/engines/source_engine/Camera.hpp index a0b9f771..fa7b2b26 100644 --- a/include/omath/engines/Source/Camera.hpp +++ b/include/omath/engines/source_engine/Camera.hpp @@ -5,7 +5,7 @@ #include "Constants.hpp" #include "omath/projection/Camera.hpp" -namespace omath::source +namespace omath::source_engine { class Camera final : public projection::Camera { diff --git a/include/omath/engines/Source/Constants.hpp b/include/omath/engines/source_engine/Constants.hpp similarity index 96% rename from include/omath/engines/Source/Constants.hpp rename to include/omath/engines/source_engine/Constants.hpp index c0a7b017..6c331b29 100644 --- a/include/omath/engines/Source/Constants.hpp +++ b/include/omath/engines/source_engine/Constants.hpp @@ -7,7 +7,8 @@ #include #include #include -namespace omath::source + +namespace omath::source_engine { constexpr Vector3 kAbsUp = {0, 0, 1}; constexpr Vector3 kAbsRight = {0, -1, 0}; diff --git a/include/omath/engines/Source/Formulas.hpp b/include/omath/engines/source_engine/Formulas.hpp similarity index 98% rename from include/omath/engines/Source/Formulas.hpp rename to include/omath/engines/source_engine/Formulas.hpp index 49b9166b..9e272986 100644 --- a/include/omath/engines/Source/Formulas.hpp +++ b/include/omath/engines/source_engine/Formulas.hpp @@ -4,7 +4,7 @@ #pragma once #include "Constants.hpp" -namespace omath::source +namespace omath::source_engine { [[nodiscard]] inline Vector3 ForwardVector(const ViewAngles& angles) diff --git a/source/engines/CMakeLists.txt b/source/engines/CMakeLists.txt index 2d2a3c9e..bfb07650 100644 --- a/source/engines/CMakeLists.txt +++ b/source/engines/CMakeLists.txt @@ -1,2 +1,2 @@ -add_subdirectory(Source) -add_subdirectory(OpenGL) \ No newline at end of file +add_subdirectory(source_engine) +add_subdirectory(opengl_engine) \ No newline at end of file diff --git a/source/engines/OpenGL/CMakeLists.txt b/source/engines/opengl_engine/CMakeLists.txt similarity index 100% rename from source/engines/OpenGL/CMakeLists.txt rename to source/engines/opengl_engine/CMakeLists.txt diff --git a/source/engines/OpenGL/Camera.cpp b/source/engines/opengl_engine/Camera.cpp similarity index 84% rename from source/engines/OpenGL/Camera.cpp rename to source/engines/opengl_engine/Camera.cpp index 17e12aa0..d7bb7f46 100644 --- a/source/engines/OpenGL/Camera.cpp +++ b/source/engines/opengl_engine/Camera.cpp @@ -1,11 +1,11 @@ // // Created by Orange on 12/23/2024. // -#include "omath/engines/OpenGL/Camera.hpp" -#include "omath/engines/OpenGL/Formulas.hpp" +#include "omath/engines/opengl_engine/Camera.hpp" +#include "omath/engines/opengl_engine/Formulas.hpp" -namespace omath::opengl +namespace omath::opengl_engine { Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, @@ -25,7 +25,7 @@ namespace omath::opengl } Mat4x4 Camera::CalcViewMatrix() const { - return opengl::CalcViewMatrix(m_viewAngles, m_origin); + return opengl_engine::CalcViewMatrix(m_viewAngles, m_origin); } Mat4x4 Camera::CalcProjectionMatrix() const { diff --git a/source/engines/Source/CMakeLists.txt b/source/engines/source_engine/CMakeLists.txt similarity index 100% rename from source/engines/Source/CMakeLists.txt rename to source/engines/source_engine/CMakeLists.txt diff --git a/source/engines/Source/Camera.cpp b/source/engines/source_engine/Camera.cpp similarity index 84% rename from source/engines/Source/Camera.cpp rename to source/engines/source_engine/Camera.cpp index 1f00af55..2ea32402 100644 --- a/source/engines/Source/Camera.cpp +++ b/source/engines/source_engine/Camera.cpp @@ -1,11 +1,11 @@ // // Created by Orange on 12/4/2024. // -#include "omath/engines/Source/Camera.hpp" -#include "omath/engines/Source/Formulas.hpp" +#include "omath/engines/source_engine/Camera.hpp" +#include "omath/engines/source_engine/Formulas.hpp" -namespace omath::source +namespace omath::source_engine { Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, @@ -26,7 +26,7 @@ namespace omath::source Mat4x4 Camera::CalcViewMatrix() const { - return source::CalcViewMatrix(m_viewAngles, m_origin); + return source_engine::CalcViewMatrix(m_viewAngles, m_origin); } Mat4x4 Camera::CalcProjectionMatrix() const diff --git a/source/projectile_prediction/Projectile.cpp b/source/projectile_prediction/Projectile.cpp index dcf4c26b..193b9a57 100644 --- a/source/projectile_prediction/Projectile.cpp +++ b/source/projectile_prediction/Projectile.cpp @@ -4,15 +4,15 @@ #include "omath/projectile_prediction/Projectile.hpp" -#include +#include namespace omath::projectile_prediction { Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const { - auto currentPos = m_origin + source::ForwardVector({source::PitchAngle::FromDegrees(-pitch), - source::YawAngle::FromDegrees(yaw), - source::RollAngle::FromDegrees(0)}) * + auto currentPos = m_origin + source_engine::ForwardVector({source_engine::PitchAngle::FromDegrees(-pitch), + source_engine::YawAngle::FromDegrees(yaw), + source_engine::RollAngle::FromDegrees(0)}) * m_launchSpeed * time; currentPos.z -= (gravity * m_gravityScale) * (time * time) * 0.5f; diff --git a/tests/engines/UnitTestOpenGL.cpp b/tests/engines/UnitTestOpenGL.cpp index db748e87..ab42268d 100644 --- a/tests/engines/UnitTestOpenGL.cpp +++ b/tests/engines/UnitTestOpenGL.cpp @@ -2,35 +2,35 @@ // Created by Orange on 11/23/2024. // #include -#include -#include -#include +#include +#include +#include TEST(UnitTestOpenGL, ForwardVector) { - const auto forward = omath::opengl::ForwardVector({}); + const auto forward = omath::opengl_engine::ForwardVector({}); - EXPECT_EQ(forward, omath::opengl::kAbsForward); + EXPECT_EQ(forward, omath::opengl_engine::kAbsForward); } TEST(UnitTestOpenGL, RightVector) { - const auto right = omath::opengl::RightVector({}); + const auto right = omath::opengl_engine::RightVector({}); - EXPECT_EQ(right, omath::opengl::kAbsRight); + EXPECT_EQ(right, omath::opengl_engine::kAbsRight); } TEST(UnitTestOpenGL, UpVector) { - const auto up = omath::opengl::UpVector({}); - EXPECT_EQ(up, omath::opengl::kAbsUp); + const auto up = omath::opengl_engine::UpVector({}); + EXPECT_EQ(up, omath::opengl_engine::kAbsUp); } TEST(UnitTestOpenGL, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); - auto cam = omath::opengl::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + const auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); for (float distance = -10.f; distance > -1000.f; distance -= 0.01f) @@ -50,7 +50,7 @@ TEST(UnitTestOpenGL, ProjectTargetMovedFromCamera) TEST(UnitTestOpenGL, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); - auto cam = omath::opengl::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); @@ -60,7 +60,7 @@ TEST(UnitTestOpenGL, CameraSetAndGetFov) TEST(UnitTestOpenGL, CameraSetAndGetOrigin) { - auto cam = omath::opengl::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); + auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); diff --git a/tests/engines/UnitTestSourceEngine.cpp b/tests/engines/UnitTestSourceEngine.cpp index 876557ea..7d4d71a9 100644 --- a/tests/engines/UnitTestSourceEngine.cpp +++ b/tests/engines/UnitTestSourceEngine.cpp @@ -2,35 +2,35 @@ // Created by Orange on 11/23/2024. // #include -#include -#include -#include +#include +#include +#include TEST(UnitTestSourceEngine, ForwardVector) { - const auto forward = omath::source::ForwardVector({}); + const auto forward = omath::source_engine::ForwardVector({}); - EXPECT_EQ(forward, omath::source::kAbsForward); + EXPECT_EQ(forward, omath::source_engine::kAbsForward); } TEST(UnitTestSourceEngine, RightVector) { - const auto right = omath::source::RightVector({}); + const auto right = omath::source_engine::RightVector({}); - EXPECT_EQ(right, omath::source::kAbsRight); + EXPECT_EQ(right, omath::source_engine::kAbsRight); } TEST(UnitTestSourceEngine, UpVector) { - const auto up = omath::source::UpVector({}); - EXPECT_EQ(up, omath::source::kAbsUp); + const auto up = omath::source_engine::UpVector({}); + EXPECT_EQ(up, omath::source_engine::kAbsUp); } TEST(UnitTestSourceEngine, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); - auto cam = omath::source::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + const auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); for (float distance = 0.02f; distance < 1000.f; distance += 0.01f) @@ -50,7 +50,7 @@ TEST(UnitTestSourceEngine, ProjectTargetMovedFromCamera) TEST(UnitTestSourceEngine, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); - auto cam = omath::source::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); @@ -60,7 +60,7 @@ TEST(UnitTestSourceEngine, CameraSetAndGetFov) TEST(UnitTestSourceEngine, CameraSetAndGetOrigin) { - auto cam = omath::source::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); + auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); diff --git a/tests/general/UnitTestProjection.cpp b/tests/general/UnitTestProjection.cpp index 3d9b507b..65819e08 100644 --- a/tests/general/UnitTestProjection.cpp +++ b/tests/general/UnitTestProjection.cpp @@ -3,15 +3,14 @@ // #include #include -#include -#include +#include #include #include TEST(UnitTestProjection, Projection) { const auto x = omath::Angle::FromDegrees(90.f); - auto cam = omath::source::Camera({0, 0, 0}, omath::source::ViewAngles{}, {1920.f, 1080.f}, x, 0.01f, 1000.f); + auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, x, 0.01f, 1000.f); const auto projected = cam.WorldToScreen({1000, 0, 50}); std::print("{} {} {}", projected->x, projected->y, projected->z); From 0740d0778c77202f6abda448f161a17eb5e89bdf Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 17 Mar 2025 07:20:13 +0300 Subject: [PATCH 193/795] added iw engine files --- CMakeLists.txt | 2 +- include/omath/engines/iw_engine/Camera.hpp | 21 ++++++++ include/omath/engines/iw_engine/Constants.hpp | 25 +++++++++ include/omath/engines/iw_engine/Formulas.hpp | 54 +++++++++++++++++++ source/engines/CMakeLists.txt | 3 +- source/engines/iw_engine/CMakeLists.txt | 1 + source/engines/iw_engine/Camera.cpp | 34 ++++++++++++ tests/engines/UnitTestIwEngine.cpp | 3 ++ 8 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 include/omath/engines/iw_engine/Camera.hpp create mode 100644 include/omath/engines/iw_engine/Constants.hpp create mode 100644 include/omath/engines/iw_engine/Formulas.hpp create mode 100644 source/engines/iw_engine/CMakeLists.txt create mode 100644 source/engines/iw_engine/Camera.cpp create mode 100644 tests/engines/UnitTestIwEngine.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b2bf4526..dab8ddf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(omath VERSION 1.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) -option(OMATH_BUILD_TESTS "Build unit tests" OFF) +option(OMATH_BUILD_TESTS "Build unit tests" ON) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) diff --git a/include/omath/engines/iw_engine/Camera.hpp b/include/omath/engines/iw_engine/Camera.hpp new file mode 100644 index 00000000..cd413f3b --- /dev/null +++ b/include/omath/engines/iw_engine/Camera.hpp @@ -0,0 +1,21 @@ +// +// Created by Vlad on 3/17/2025. +// + +#pragma once +#include "Constants.hpp" +#include "omath/projection/Camera.hpp" + +namespace omath::iw_engine +{ + class Camera final : public projection::Camera + { + public: + Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + const Angle& fov, float near, float far); + void LookAt(const Vector3& target) override; + protected: + [[nodiscard]] Mat4x4 CalcViewMatrix() const override; + [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; + }; +} \ No newline at end of file diff --git a/include/omath/engines/iw_engine/Constants.hpp b/include/omath/engines/iw_engine/Constants.hpp new file mode 100644 index 00000000..88979158 --- /dev/null +++ b/include/omath/engines/iw_engine/Constants.hpp @@ -0,0 +1,25 @@ +// +// Created by Vlad on 3/17/2025. +// + +#pragma once +#include +#include +#include +#include + +namespace omath::iw_engine +{ + constexpr Vector3 kAbsUp = {0, 0, 1}; + constexpr Vector3 kAbsRight = {0, -1, 0}; + constexpr Vector3 kAbsForward = {1, 0, 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; +} \ No newline at end of file diff --git a/include/omath/engines/iw_engine/Formulas.hpp b/include/omath/engines/iw_engine/Formulas.hpp new file mode 100644 index 00000000..bd757d25 --- /dev/null +++ b/include/omath/engines/iw_engine/Formulas.hpp @@ -0,0 +1,54 @@ +// +// Created by Vlad on 3/17/2025. +// + +#pragma once +#include "Constants.hpp" + +namespace omath::iw_engine +{ + [[nodiscard]] + inline Vector3 ForwardVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + + [[nodiscard]] + inline Vector3 RightVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + + [[nodiscard]] + inline Vector3 UpVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + + [[nodiscard]] inline Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + { + return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + } + + + [[nodiscard]] + inline Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, + const float far) + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + return + { + {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, 1.f / fovHalfTan, 0, 0}, + {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, + {0, 0, 1, 0}, + }; + } +} // namespace omath::source diff --git a/source/engines/CMakeLists.txt b/source/engines/CMakeLists.txt index bfb07650..eb223b0e 100644 --- a/source/engines/CMakeLists.txt +++ b/source/engines/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(source_engine) -add_subdirectory(opengl_engine) \ No newline at end of file +add_subdirectory(opengl_engine) +add_subdirectory(iw_engine) diff --git a/source/engines/iw_engine/CMakeLists.txt b/source/engines/iw_engine/CMakeLists.txt new file mode 100644 index 00000000..0abf8686 --- /dev/null +++ b/source/engines/iw_engine/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(omath PRIVATE Camera.cpp) \ No newline at end of file diff --git a/source/engines/iw_engine/Camera.cpp b/source/engines/iw_engine/Camera.cpp new file mode 100644 index 00000000..5b7cfe09 --- /dev/null +++ b/source/engines/iw_engine/Camera.cpp @@ -0,0 +1,34 @@ +// +// Created by Vlad on 3/17/2025. +// +#include "omath/engines/iw_engine/Camera.hpp" +#include "omath/engines/iw_engine/Formulas.hpp" + +namespace omath::iw_engine +{ + + Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + const Angle& fov, const float near, const float far) : + projection::Camera(position, viewAngles, viewPort, fov, near, far) + { + } + void Camera::LookAt([[maybe_unused]] const Vector3& target) + { + const float distance = m_origin.DistTo(target); + const auto delta = target - m_origin; + + + m_viewAngles.pitch = PitchAngle::FromRadians(std::asin(delta.z / distance)); + m_viewAngles.yaw = -YawAngle::FromRadians(std::atan2(delta.y, delta.x)); + m_viewAngles.roll = RollAngle::FromRadians(0.f); + } + Mat4x4 Camera::CalcViewMatrix() const + { + return iw_engine::CalcViewMatrix(m_viewAngles, m_origin); + } + Mat4x4 Camera::CalcProjectionMatrix() const + { + return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, + m_farPlaneDistance); + } +} // namespace omath::openg \ No newline at end of file diff --git a/tests/engines/UnitTestIwEngine.cpp b/tests/engines/UnitTestIwEngine.cpp new file mode 100644 index 00000000..9df832cc --- /dev/null +++ b/tests/engines/UnitTestIwEngine.cpp @@ -0,0 +1,3 @@ +// +// Created by Vlad on 3/17/2025. +// From 2fa0c500a777be8ec2729efa3d45df3c3a81c3a6 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 17 Mar 2025 08:59:41 +0300 Subject: [PATCH 194/795] added magic number --- include/omath/engines/iw_engine/Formulas.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/engines/iw_engine/Formulas.hpp b/include/omath/engines/iw_engine/Formulas.hpp index bd757d25..3af95c7c 100644 --- a/include/omath/engines/iw_engine/Formulas.hpp +++ b/include/omath/engines/iw_engine/Formulas.hpp @@ -41,7 +41,7 @@ namespace omath::iw_engine inline Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * 0.75f; return { From cd452b0397ceb36e6787276b6af377333800c3c6 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 17 Mar 2025 09:14:42 +0300 Subject: [PATCH 195/795] updated comment --- CMakeLists.txt | 2 +- include/omath/engines/iw_engine/Formulas.hpp | 4 +- .../omath/engines/source_engine/Formulas.hpp | 3 +- tests/CMakeLists.txt | 1 + tests/engines/UnitTestIwEngine.cpp | 66 +++++++++++++++++++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dab8ddf4..b2bf4526 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(omath VERSION 1.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) -option(OMATH_BUILD_TESTS "Build unit tests" ON) +option(OMATH_BUILD_TESTS "Build unit tests" OFF) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) diff --git a/include/omath/engines/iw_engine/Formulas.hpp b/include/omath/engines/iw_engine/Formulas.hpp index 3af95c7c..c98e7278 100644 --- a/include/omath/engines/iw_engine/Formulas.hpp +++ b/include/omath/engines/iw_engine/Formulas.hpp @@ -41,7 +41,9 @@ namespace omath::iw_engine inline Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * 0.75f; + // NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation + constexpr auto kMultiplyFactor = 0.75f; + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; return { diff --git a/include/omath/engines/source_engine/Formulas.hpp b/include/omath/engines/source_engine/Formulas.hpp index 9e272986..549d16a5 100644 --- a/include/omath/engines/source_engine/Formulas.hpp +++ b/include/omath/engines/source_engine/Formulas.hpp @@ -40,8 +40,7 @@ namespace omath::source_engine inline Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { - // NOTE: Needed tp make thing draw normal, since source is wierd - // and use tricky projection matrix formula. + // NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation constexpr auto kMultiplyFactor = 0.75f; const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 55b9d831..bc043bd3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable(unit-tests engines/UnitTestOpenGL.cpp engines/UnitTestUnityEngine.cpp engines/UnitTestSourceEngine.cpp + engines/UnitTestIwEngine.cpp ) diff --git a/tests/engines/UnitTestIwEngine.cpp b/tests/engines/UnitTestIwEngine.cpp index 9df832cc..00bdc409 100644 --- a/tests/engines/UnitTestIwEngine.cpp +++ b/tests/engines/UnitTestIwEngine.cpp @@ -1,3 +1,69 @@ // // Created by Vlad on 3/17/2025. // +#include +#include +#include +#include + + +TEST(UnitTestEwEngine, ForwardVector) +{ + const auto forward = omath::source_engine::ForwardVector({}); + + EXPECT_EQ(forward, omath::source_engine::kAbsForward); +} + +TEST(UnitTestEwEngine, RightVector) +{ + const auto right = omath::source_engine::RightVector({}); + + EXPECT_EQ(right, omath::source_engine::kAbsRight); +} + +TEST(UnitTestEwEngine, UpVector) +{ + const auto up = omath::source_engine::UpVector({}); + EXPECT_EQ(up, omath::source_engine::kAbsUp); +} + +TEST(UnitTestEwEngine, ProjectTargetMovedFromCamera) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + const auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + + + for (float distance = 0.02f; distance < 1000.f; distance += 0.01f) + { + const auto projected = cam.WorldToScreen({distance, 0, 0}); + + EXPECT_TRUE(projected.has_value()); + + if (!projected.has_value()) + continue; + + EXPECT_NEAR(projected->x, 960, 0.00001f); + EXPECT_NEAR(projected->y, 540, 0.00001f); + } +} + +TEST(UnitTestEwEngine, CameraSetAndGetFov) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); + cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); +} + +TEST(UnitTestEwEngine, CameraSetAndGetOrigin) +{ + auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); + + EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); + cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); +} \ No newline at end of file From 9b6d0beb030ed5fd0d1d17b4f4af477ba9c001f4 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Mon, 17 Mar 2025 18:20:40 +0300 Subject: [PATCH 196/795] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0bae67a2..c4d896ab 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,11 @@ TEST(UnitTestProjection, IsPointOnScreen) With `omath/projection` module you can achieve simple ESP hack for powered by Source/Unreal/Unity engine games, like [Apex Legends](https://store.steampowered.com/app/1172470/Apex_Legends/). ![banner](https://i.imgur.com/lcJrfcZ.png) -![Watch Video](https://youtu.be/lM_NJ1yCunw?si=5E87OrQMeypxSJ3E) +Or for InfinityWard Engine based games. Like Call of Duty Black Ops 2! +![banner](https://i.imgur.com/F8dmdoo.png) +Or even advanced projectile aimbot +[Watch Video](https://youtu.be/lM_NJ1yCunw?si=5E87OrQMeypxSJ3E) ## 🫵🏻 Contributing From f85243e8923bc5581051713bab618fd0016a332e Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 19 Mar 2025 00:56:39 +0300 Subject: [PATCH 197/795] improved print method --- include/omath/Mat.hpp | 12 +++++++++--- tests/general/UnitTestMat.cpp | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/omath/Mat.hpp b/include/omath/Mat.hpp index 17964cf4..864ad58d 100644 --- a/include/omath/Mat.hpp +++ b/include/omath/Mat.hpp @@ -8,6 +8,7 @@ #include #include #include "Vector3.hpp" +#include namespace omath { @@ -296,15 +297,20 @@ namespace omath std::string ToString() const noexcept { std::ostringstream oss; + oss << "[["; + for (size_t i = 0; i < Rows; ++i) { + if (i > 0) + oss << " ["; + for (size_t j = 0; j < Columns; ++j) { - oss << At(i, j); + oss << std::setw(9) << std::fixed << std::setprecision(3) << At(i, j); if (j != Columns - 1) - oss << ' '; + oss << ", "; } - oss << '\n'; + oss << (i == Rows - 1 ? "]]" : "]\n"); } return oss.str(); } diff --git a/tests/general/UnitTestMat.cpp b/tests/general/UnitTestMat.cpp index cdaecfc8..710a2a2b 100644 --- a/tests/general/UnitTestMat.cpp +++ b/tests/general/UnitTestMat.cpp @@ -127,7 +127,7 @@ TEST_F(UnitTestMat, ToString) { const std::string str = m2.ToString(); EXPECT_FALSE(str.empty()); - EXPECT_EQ(str, "1 2\n3 4\n"); + EXPECT_EQ(str, "[[ 1.000, 2.000]\n [ 3.000, 4.000]]"); } // Test assignment operators From 27576ed7611fc56458d1d6951c115e26edb9704b Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 19 Mar 2025 18:50:47 +0300 Subject: [PATCH 198/795] added alias --- CMakeLists.txt | 2 ++ tests/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b2bf4526..406feb4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,8 @@ else() add_library(omath STATIC source/Matrix.cpp) endif() +add_library(omath::omath ALIAS omath) + if (OMATH_IMGUI_INTEGRATION) target_compile_definitions(omath PUBLIC OMATH_IMGUI_INTEGRATION) endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bc043bd3..2320af96 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -35,6 +35,6 @@ set_target_properties(unit-tests PROPERTIES CXX_STANDARD_REQUIRED ON) -target_link_libraries(unit-tests PRIVATE gtest gtest_main omath) +target_link_libraries(unit-tests PRIVATE gtest gtest_main omath::omath) gtest_discover_tests(unit-tests) \ No newline at end of file From e80e22bd5bd375397e29a0b0d4921b36c0971cb0 Mon Sep 17 00:00:00 2001 From: Vladislav Alpatov Date: Sat, 1 Mar 2025 21:46:29 +0300 Subject: [PATCH 199/795] added some files --- CMakeLists.txt | 5 +++++ README.md | 4 ++-- examples/CMakeLists.txt | 0 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 examples/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 406feb4b..7d5e8b56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force co option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" OFF) +option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" ON) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Vector3.cpp) @@ -49,6 +50,10 @@ if(OMATH_BUILD_TESTS) add_subdirectory(tests) endif() +if (OMATH_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND OMATH_THREAT_WARNING_AS_ERROR) target_compile_options(omath PRIVATE /W4 /WX) elseif(OMATH_THREAT_WARNING_AS_ERROR) diff --git a/README.md b/README.md index c4d896ab..0058d791 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,14 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. ## 👁‍🗨 Features -- **Efficiency**: Optimized for performance, ensuring quick computations. +- **Efficiency**: Optimized for performance, ensuring quick computations using AVX2. - **Versatility**: Includes a wide array of mathematical functions and algorithms. - **Ease of Use**: Simplified interface for convenient integration into various projects. - **Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot. - **3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline. - **Collision Detection**: Production ready code to handle collision detection by using simple interfaces. - **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution - +- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types! ## ⏬ Getting Started ### Prerequisites - C++ Compiler diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..e69de29b From 50c336e0442419480c630a08e4998136de7e0fbe Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 19 Mar 2025 19:16:22 +0300 Subject: [PATCH 200/795] added example --- CMakeLists.txt | 2 +- examples/CMakeLists.txt | 4 +++ examples/ExampleProjMatBuilder.cpp | 40 ++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 examples/ExampleProjMatBuilder.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d5e8b56..c4350750 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" ON) if (OMATH_BUILD_AS_SHARED_LIBRARY) - add_library(omath SHARED source/Vector3.cpp) + add_library(omath SHARED source/Matrix.cpp) else() add_library(omath STATIC source/Matrix.cpp) endif() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e69de29b..11580f0e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -0,0 +1,4 @@ +project(examples) + +add_executable(ExampleProjectionMatrixBuilder ExampleProjMatBuilder.cpp) +target_link_libraries(ExampleProjectionMatrixBuilder PRIVATE omath::omath) \ No newline at end of file diff --git a/examples/ExampleProjMatBuilder.cpp b/examples/ExampleProjMatBuilder.cpp new file mode 100644 index 00000000..f06cc3c9 --- /dev/null +++ b/examples/ExampleProjMatBuilder.cpp @@ -0,0 +1,40 @@ +// +// Created by Vlad on 3/19/2025. +// +#include +#include +#include +#include +#include + + +int main() +{ + std::println("OMATH Projection Matrix Builder"); + + float fov = 0; + float near = 0; + float far = 0; + float viewPortWidth = 0; + float viewPortHeight = 0; + + std::print("Enter camera fov: "); + std::cin >> fov; + + std::print("Enter camera z near: "); + std::cin >> near; + + std::print("Enter camera z far: "); + std::cin >> far; + + std::print("Enter camera screen width: "); + std::cin >> viewPortWidth; + + std::print("Enter camera screen height: "); + std::cin >> viewPortHeight; + + const auto mat = + omath::opengl_engine::CalcPerspectiveProjectionMatrix(fov, viewPortWidth / viewPortHeight, near, far); + + std::print("{}", mat.ToString()); +}; From 832c2ea5eff9234dbf06fc981511384987658ed3 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 19 Mar 2025 20:07:44 +0300 Subject: [PATCH 201/795] removed inline functions --- include/omath/engines/iw_engine/Formulas.hpp | 42 ++-------------- .../omath/engines/opengl_engine/Formulas.hpp | 42 +++------------- .../omath/engines/source_engine/Formulas.hpp | 43 ++-------------- source/engines/iw_engine/CMakeLists.txt | 2 +- source/engines/iw_engine/Formulas.cpp | 47 ++++++++++++++++++ source/engines/opengl_engine/CMakeLists.txt | 2 +- source/engines/opengl_engine/Formulas.cpp | 46 +++++++++++++++++ source/engines/source_engine/CMakeLists.txt | 2 +- source/engines/source_engine/Formulas.cpp | 49 +++++++++++++++++++ 9 files changed, 161 insertions(+), 114 deletions(-) create mode 100644 source/engines/iw_engine/Formulas.cpp create mode 100644 source/engines/opengl_engine/Formulas.cpp create mode 100644 source/engines/source_engine/Formulas.cpp diff --git a/include/omath/engines/iw_engine/Formulas.hpp b/include/omath/engines/iw_engine/Formulas.hpp index c98e7278..1249c983 100644 --- a/include/omath/engines/iw_engine/Formulas.hpp +++ b/include/omath/engines/iw_engine/Formulas.hpp @@ -8,49 +8,17 @@ namespace omath::iw_engine { [[nodiscard]] - inline Vector3 ForwardVector(const ViewAngles& angles) - { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); - - return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; - } + Vector3 ForwardVector(const ViewAngles& angles); [[nodiscard]] - inline Vector3 RightVector(const ViewAngles& angles) - { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); - - return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; - } + Vector3 RightVector(const ViewAngles& angles); [[nodiscard]] - inline Vector3 UpVector(const ViewAngles& angles) - { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + Vector3 UpVector(const ViewAngles& angles); - return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; - } - - [[nodiscard]] inline Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) - { - return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); - } + [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); [[nodiscard]] - inline Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, - const float far) - { - // NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation - constexpr auto kMultiplyFactor = 0.75f; - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; - - return - { - {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, 1.f / fovHalfTan, 0, 0}, - {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, - {0, 0, 1, 0}, - }; - } + Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); } // namespace omath::source diff --git a/include/omath/engines/opengl_engine/Formulas.hpp b/include/omath/engines/opengl_engine/Formulas.hpp index b63ffdcc..e0e97414 100644 --- a/include/omath/engines/opengl_engine/Formulas.hpp +++ b/include/omath/engines/opengl_engine/Formulas.hpp @@ -8,47 +8,17 @@ namespace omath::opengl_engine { [[nodiscard]] - inline Vector3 ForwardVector(const ViewAngles& angles) - { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); - - return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; - } + Vector3 ForwardVector(const ViewAngles& angles); [[nodiscard]] - inline Vector3 RightVector(const ViewAngles& angles) - { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); - - return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; - } + Vector3 RightVector(const ViewAngles& angles); [[nodiscard]] - inline Vector3 UpVector(const ViewAngles& angles) - { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); - - return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; - } + Vector3 UpVector(const ViewAngles& angles); - [[nodiscard]] inline Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) - { - return MatCameraView(-ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); - } + [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); [[nodiscard]] - inline Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, - const float far) - { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); - - return { - {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, 1.f / (fovHalfTan), 0, 0}, - {0, 0, -(far + near) / (far - near), -(2.f * far * near) / (far - near)}, - {0, 0, -1, 0}, - - }; - } -} \ No newline at end of file + Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); +} // namespace omath::opengl_engine diff --git a/include/omath/engines/source_engine/Formulas.hpp b/include/omath/engines/source_engine/Formulas.hpp index 549d16a5..d47d2b62 100644 --- a/include/omath/engines/source_engine/Formulas.hpp +++ b/include/omath/engines/source_engine/Formulas.hpp @@ -7,50 +7,17 @@ namespace omath::source_engine { [[nodiscard]] - inline Vector3 ForwardVector(const ViewAngles& angles) - { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); - - return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; - } + Vector3 ForwardVector(const ViewAngles& angles); [[nodiscard]] - inline Vector3 RightVector(const ViewAngles& angles) - { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); - - return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; - } + Vector3 RightVector(const ViewAngles& angles); [[nodiscard]] - inline Vector3 UpVector(const ViewAngles& angles) - { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); - - return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; - } + Vector3 UpVector(const ViewAngles& angles); - [[nodiscard]] inline Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) - { - return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); - } + [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); [[nodiscard]] - inline Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, - const float far) - { - // NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation - constexpr auto kMultiplyFactor = 0.75f; - - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; - - return { - {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, 1.f / (fovHalfTan), 0, 0}, - {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, - {0, 0, 1, 0}, - - }; - } + Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); } // namespace omath::source diff --git a/source/engines/iw_engine/CMakeLists.txt b/source/engines/iw_engine/CMakeLists.txt index 0abf8686..0153efab 100644 --- a/source/engines/iw_engine/CMakeLists.txt +++ b/source/engines/iw_engine/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE Camera.cpp) \ No newline at end of file +target_sources(omath PRIVATE Camera.cpp Formulas.cpp) \ No newline at end of file diff --git a/source/engines/iw_engine/Formulas.cpp b/source/engines/iw_engine/Formulas.cpp new file mode 100644 index 00000000..0561b431 --- /dev/null +++ b/source/engines/iw_engine/Formulas.cpp @@ -0,0 +1,47 @@ +// +// Created by Vlad on 3/19/2025. +// +#include "omath/engines/iw_engine/Formulas.hpp" + + +namespace omath::iw_engine +{ + + Vector3 ForwardVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + + Vector3 RightVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Vector3 UpVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + { + return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + } + Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, + const float far) + { + // NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation + constexpr auto kMultiplyFactor = 0.75f; + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; + + return { + {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, 1.f / fovHalfTan, 0, 0}, + {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, + {0, 0, 1, 0}, + }; + } +} // namespace omath::iw_engine diff --git a/source/engines/opengl_engine/CMakeLists.txt b/source/engines/opengl_engine/CMakeLists.txt index 0abf8686..0153efab 100644 --- a/source/engines/opengl_engine/CMakeLists.txt +++ b/source/engines/opengl_engine/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE Camera.cpp) \ No newline at end of file +target_sources(omath PRIVATE Camera.cpp Formulas.cpp) \ No newline at end of file diff --git a/source/engines/opengl_engine/Formulas.cpp b/source/engines/opengl_engine/Formulas.cpp new file mode 100644 index 00000000..c2fa6984 --- /dev/null +++ b/source/engines/opengl_engine/Formulas.cpp @@ -0,0 +1,46 @@ +// +// Created by Vlad on 3/19/2025. +// +#include "omath/engines/opengl_engine/Formulas.hpp" + + +namespace omath::opengl_engine +{ + + Vector3 ForwardVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Vector3 RightVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Vector3 UpVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + { + return MatCameraView(-ForwardVector(angles), RightVector(angles), + UpVector(angles), cam_origin); + } + Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, + const float far) + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + return { + {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, 1.f / (fovHalfTan), 0, 0}, + {0, 0, -(far + near) / (far - near), -(2.f * far * near) / (far - near)}, + {0, 0, -1, 0}, + + }; + } +} // namespace omath::opengl_engine diff --git a/source/engines/source_engine/CMakeLists.txt b/source/engines/source_engine/CMakeLists.txt index 0abf8686..0153efab 100644 --- a/source/engines/source_engine/CMakeLists.txt +++ b/source/engines/source_engine/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE Camera.cpp) \ No newline at end of file +target_sources(omath PRIVATE Camera.cpp Formulas.cpp) \ No newline at end of file diff --git a/source/engines/source_engine/Formulas.cpp b/source/engines/source_engine/Formulas.cpp new file mode 100644 index 00000000..1bbb90a4 --- /dev/null +++ b/source/engines/source_engine/Formulas.cpp @@ -0,0 +1,49 @@ +// +// Created by Vlad on 3/19/2025. +// +#include + + +namespace omath::source_engine +{ + Vector3 ForwardVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + + Vector3 RightVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Vector3 UpVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + + Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + { + return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + } + Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, + const float far) + { + // NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation + constexpr auto kMultiplyFactor = 0.75f; + + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; + + return { + {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, 1.f / (fovHalfTan), 0, 0}, + {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, + {0, 0, 1, 0}, + + }; + } +} // namespace omath::source_engine From 8f4b61319f7a6a184e78baadd5b56720fe192c00 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 19 Mar 2025 20:13:06 +0300 Subject: [PATCH 202/795] disabled tests by default --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c4350750..c50a9453 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force co option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" OFF) -option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" ON) +option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Matrix.cpp) From b9ac44a901e2a782bbff1ee28175735b8a3600cc Mon Sep 17 00:00:00 2001 From: Vladislav Alpatov Date: Fri, 21 Mar 2025 04:17:42 +0300 Subject: [PATCH 203/795] renamed headers --- include/omath/collision/{LineTracer.hpp => line_tracer.hpp} | 0 include/omath/engines/iw_engine/Constants.hpp | 2 +- include/omath/engines/iw_engine/Formulas.hpp | 2 +- include/omath/engines/opengl_engine/Constants.hpp | 2 +- include/omath/engines/source_engine/Constants.hpp | 2 +- include/omath/pathfinding/{Astar.hpp => a_star.hpp} | 2 +- .../pathfinding/{NavigationMesh.hpp => navigation_mesh.hpp} | 0 .../{ProjPredEngine.hpp => proj_pred_engine.hpp} | 0 .../{ProjPredEngineAVX2.hpp => proj_pred_engine_avx2.hpp} | 2 +- .../{ProjPredEngineLegacy.hpp => proj_pred_engine_legacy.hpp} | 2 +- include/omath/projection/Camera.hpp | 2 +- include/omath/projection/{ErrorCodes.hpp => error_codes.hpp} | 0 include/omath/{ViewAngles.hpp => view_angles.hpp} | 0 source/collision/LineTracer.cpp | 2 +- source/pathfinding/Astar.cpp | 2 +- source/pathfinding/NavigationMesh.cpp | 2 +- source/projectile_prediction/ProjPredEngine.cpp | 2 +- source/projectile_prediction/ProjPredEngineAVX2.cpp | 2 +- source/projectile_prediction/ProjPredEngineLegacy.cpp | 2 +- tests/general/UnitTestAstar.cpp | 2 +- tests/general/UnitTestLineTrace.cpp | 2 +- tests/general/UnitTestPrediction.cpp | 2 +- tests/general/UnitTestViewAngles.cpp | 2 +- 23 files changed, 18 insertions(+), 18 deletions(-) rename include/omath/collision/{LineTracer.hpp => line_tracer.hpp} (100%) rename include/omath/pathfinding/{Astar.hpp => a_star.hpp} (96%) rename include/omath/pathfinding/{NavigationMesh.hpp => navigation_mesh.hpp} (100%) rename include/omath/projectile_prediction/{ProjPredEngine.hpp => proj_pred_engine.hpp} (100%) rename include/omath/projectile_prediction/{ProjPredEngineAVX2.hpp => proj_pred_engine_avx2.hpp} (96%) rename include/omath/projectile_prediction/{ProjPredEngineLegacy.hpp => proj_pred_engine_legacy.hpp} (95%) rename include/omath/projection/{ErrorCodes.hpp => error_codes.hpp} (100%) rename include/omath/{ViewAngles.hpp => view_angles.hpp} (100%) diff --git a/include/omath/collision/LineTracer.hpp b/include/omath/collision/line_tracer.hpp similarity index 100% rename from include/omath/collision/LineTracer.hpp rename to include/omath/collision/line_tracer.hpp diff --git a/include/omath/engines/iw_engine/Constants.hpp b/include/omath/engines/iw_engine/Constants.hpp index 88979158..bea065ed 100644 --- a/include/omath/engines/iw_engine/Constants.hpp +++ b/include/omath/engines/iw_engine/Constants.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include namespace omath::iw_engine { diff --git a/include/omath/engines/iw_engine/Formulas.hpp b/include/omath/engines/iw_engine/Formulas.hpp index 1249c983..72b38535 100644 --- a/include/omath/engines/iw_engine/Formulas.hpp +++ b/include/omath/engines/iw_engine/Formulas.hpp @@ -21,4 +21,4 @@ namespace omath::iw_engine [[nodiscard]] Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); -} // namespace omath::source +} // namespace omath::iw_engine diff --git a/include/omath/engines/opengl_engine/Constants.hpp b/include/omath/engines/opengl_engine/Constants.hpp index d06873b8..cca54285 100644 --- a/include/omath/engines/opengl_engine/Constants.hpp +++ b/include/omath/engines/opengl_engine/Constants.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include namespace omath::opengl_engine { diff --git a/include/omath/engines/source_engine/Constants.hpp b/include/omath/engines/source_engine/Constants.hpp index 6c331b29..6d6d0003 100644 --- a/include/omath/engines/source_engine/Constants.hpp +++ b/include/omath/engines/source_engine/Constants.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include namespace omath::source_engine { diff --git a/include/omath/pathfinding/Astar.hpp b/include/omath/pathfinding/a_star.hpp similarity index 96% rename from include/omath/pathfinding/Astar.hpp rename to include/omath/pathfinding/a_star.hpp index 9cbcedbb..daac92d0 100644 --- a/include/omath/pathfinding/Astar.hpp +++ b/include/omath/pathfinding/a_star.hpp @@ -4,7 +4,7 @@ #pragma once #include -#include "NavigationMesh.hpp" +#include "navigation_mesh.hpp" #include "omath/Vector3.hpp" namespace omath::pathfinding diff --git a/include/omath/pathfinding/NavigationMesh.hpp b/include/omath/pathfinding/navigation_mesh.hpp similarity index 100% rename from include/omath/pathfinding/NavigationMesh.hpp rename to include/omath/pathfinding/navigation_mesh.hpp diff --git a/include/omath/projectile_prediction/ProjPredEngine.hpp b/include/omath/projectile_prediction/proj_pred_engine.hpp similarity index 100% rename from include/omath/projectile_prediction/ProjPredEngine.hpp rename to include/omath/projectile_prediction/proj_pred_engine.hpp diff --git a/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp b/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp similarity index 96% rename from include/omath/projectile_prediction/ProjPredEngineAVX2.hpp rename to include/omath/projectile_prediction/proj_pred_engine_avx2.hpp index ffc0c5ed..84a57e5c 100644 --- a/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp @@ -2,7 +2,7 @@ // Created by Vlad on 2/23/2025. // #pragma once -#include "ProjPredEngine.hpp" +#include "proj_pred_engine.hpp" namespace omath::projectile_prediction { diff --git a/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp similarity index 95% rename from include/omath/projectile_prediction/ProjPredEngineLegacy.hpp rename to include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index f22308d6..0b38e73d 100644 --- a/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -6,7 +6,7 @@ #include #include "omath/Vector3.hpp" -#include "omath/projectile_prediction/ProjPredEngine.hpp" +#include "omath/projectile_prediction/proj_pred_engine.hpp" #include "omath/projectile_prediction/Projectile.hpp" #include "omath/projectile_prediction/Target.hpp" diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/Camera.hpp index c6bacbba..8fdd05dc 100644 --- a/include/omath/projection/Camera.hpp +++ b/include/omath/projection/Camera.hpp @@ -7,7 +7,7 @@ #include #include #include -#include "ErrorCodes.hpp" +#include "error_codes.hpp" #include #include diff --git a/include/omath/projection/ErrorCodes.hpp b/include/omath/projection/error_codes.hpp similarity index 100% rename from include/omath/projection/ErrorCodes.hpp rename to include/omath/projection/error_codes.hpp diff --git a/include/omath/ViewAngles.hpp b/include/omath/view_angles.hpp similarity index 100% rename from include/omath/ViewAngles.hpp rename to include/omath/view_angles.hpp diff --git a/source/collision/LineTracer.cpp b/source/collision/LineTracer.cpp index 05f5bef7..0dfc7f54 100644 --- a/source/collision/LineTracer.cpp +++ b/source/collision/LineTracer.cpp @@ -1,7 +1,7 @@ // // Created by Orange on 11/13/2024. // -#include "omath/collision/LineTracer.hpp" +#include "omath/collision/line_tracer.hpp" namespace omath::collision { diff --git a/source/pathfinding/Astar.cpp b/source/pathfinding/Astar.cpp index 4d553e72..aba160f1 100644 --- a/source/pathfinding/Astar.cpp +++ b/source/pathfinding/Astar.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 28.07.2024. // -#include "omath/pathfinding/Astar.hpp" +#include "omath/pathfinding/a_star.hpp" #include #include diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/NavigationMesh.cpp index e5491a3a..f908cc86 100644 --- a/source/pathfinding/NavigationMesh.cpp +++ b/source/pathfinding/NavigationMesh.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 28.07.2024. // -#include "omath/pathfinding/NavigationMesh.hpp" +#include "omath/pathfinding/navigation_mesh.hpp" #include #include diff --git a/source/projectile_prediction/ProjPredEngine.cpp b/source/projectile_prediction/ProjPredEngine.cpp index 7be6708b..e8e7f92c 100644 --- a/source/projectile_prediction/ProjPredEngine.cpp +++ b/source/projectile_prediction/ProjPredEngine.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 2/23/2025. // -#include "omath/projectile_prediction/ProjPredEngine.hpp" +#include "omath/projectile_prediction/proj_pred_engine.hpp" namespace omath::projectile_prediction diff --git a/source/projectile_prediction/ProjPredEngineAVX2.cpp b/source/projectile_prediction/ProjPredEngineAVX2.cpp index 8a4f8f63..771c8e0f 100644 --- a/source/projectile_prediction/ProjPredEngineAVX2.cpp +++ b/source/projectile_prediction/ProjPredEngineAVX2.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 2/23/2025. // -#include "omath/projectile_prediction/ProjPredEngineAVX2.hpp" +#include "omath/projectile_prediction/proj_pred_engine_avx2.hpp" #include "source_location" namespace omath::projectile_prediction diff --git a/source/projectile_prediction/ProjPredEngineLegacy.cpp b/source/projectile_prediction/ProjPredEngineLegacy.cpp index 3a8c7652..2e49b787 100644 --- a/source/projectile_prediction/ProjPredEngineLegacy.cpp +++ b/source/projectile_prediction/ProjPredEngineLegacy.cpp @@ -1,4 +1,4 @@ -#include "omath/projectile_prediction/ProjPredEngineLegacy.hpp" +#include "omath/projectile_prediction/proj_pred_engine_legacy.hpp" #include #include diff --git a/tests/general/UnitTestAstar.cpp b/tests/general/UnitTestAstar.cpp index da65f9f4..e33f8c0a 100644 --- a/tests/general/UnitTestAstar.cpp +++ b/tests/general/UnitTestAstar.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 18.08.2024. // #include -#include +#include TEST(UnitTestAstar, FindingRightPath) diff --git a/tests/general/UnitTestLineTrace.cpp b/tests/general/UnitTestLineTrace.cpp index 3c0bf044..0b72d2c8 100644 --- a/tests/general/UnitTestLineTrace.cpp +++ b/tests/general/UnitTestLineTrace.cpp @@ -1,5 +1,5 @@ #include "gtest/gtest.h" -#include "omath/collision/LineTracer.hpp" +#include "omath/collision/line_tracer.hpp" #include "omath/Triangle.hpp" #include "omath/Vector3.hpp" diff --git a/tests/general/UnitTestPrediction.cpp b/tests/general/UnitTestPrediction.cpp index c77c4386..94e8ae48 100644 --- a/tests/general/UnitTestPrediction.cpp +++ b/tests/general/UnitTestPrediction.cpp @@ -1,5 +1,5 @@ #include -#include +#include TEST(UnitTestPrediction, PredictionTest) { diff --git a/tests/general/UnitTestViewAngles.cpp b/tests/general/UnitTestViewAngles.cpp index 97c90e3e..28605456 100644 --- a/tests/general/UnitTestViewAngles.cpp +++ b/tests/general/UnitTestViewAngles.cpp @@ -1,4 +1,4 @@ // // Created by Orange on 11/30/2024. // -#include \ No newline at end of file +#include \ No newline at end of file From 2688d977a94597e9a5cb9a3d0b1f5d35fceb856e Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 21 Mar 2025 04:21:31 +0300 Subject: [PATCH 204/795] change --- include/omath/{Angle.hpp => angle.hpp} | 0 include/omath/{Angles.hpp => angles.hpp} | 0 include/omath/{Color.hpp => color.hpp} | 0 include/omath/engines/iw_engine/{Camera.hpp => camera.hpp} | 0 .../omath/engines/iw_engine/{Constants.hpp => constants.hpp} | 0 include/omath/engines/iw_engine/{Formulas.hpp => formulas.hpp} | 0 include/omath/engines/opengl_engine/{Camera.hpp => camera.hpp} | 0 .../engines/opengl_engine/{Constants.hpp => constants.hpp} | 0 .../omath/engines/opengl_engine/{Formulas.hpp => formulas.hpp} | 0 include/omath/engines/source_engine/{Camera.hpp => camera.hpp} | 0 .../engines/source_engine/{Constants.hpp => constants.hpp} | 0 .../omath/engines/source_engine/{Formulas.hpp => formulas.hpp} | 0 include/omath/{Mat.hpp => mat.hpp} | 0 include/omath/{Matrix.hpp => matrix.hpp} | 0 .../projectile_prediction/{Projectile.hpp => projectile.hpp} | 0 include/omath/projectile_prediction/{Target.hpp => target.hpp} | 0 include/omath/projection/{Camera.hpp => camera.hpp} | 0 include/omath/{Triangle.hpp => triangle.hpp} | 0 include/omath/{Vector2.hpp => vector2.hpp} | 0 include/omath/{Vector3.hpp => vector3.hpp} | 0 include/omath/{Vector4.hpp => vector4.hpp} | 0 source/CMakeLists.txt | 2 +- source/{Matrix.cpp => matrix.cpp} | 0 source/projection/{Camera.cpp => camera.cpp} | 0 24 files changed, 1 insertion(+), 1 deletion(-) rename include/omath/{Angle.hpp => angle.hpp} (100%) rename include/omath/{Angles.hpp => angles.hpp} (100%) rename include/omath/{Color.hpp => color.hpp} (100%) rename include/omath/engines/iw_engine/{Camera.hpp => camera.hpp} (100%) rename include/omath/engines/iw_engine/{Constants.hpp => constants.hpp} (100%) rename include/omath/engines/iw_engine/{Formulas.hpp => formulas.hpp} (100%) rename include/omath/engines/opengl_engine/{Camera.hpp => camera.hpp} (100%) rename include/omath/engines/opengl_engine/{Constants.hpp => constants.hpp} (100%) rename include/omath/engines/opengl_engine/{Formulas.hpp => formulas.hpp} (100%) rename include/omath/engines/source_engine/{Camera.hpp => camera.hpp} (100%) rename include/omath/engines/source_engine/{Constants.hpp => constants.hpp} (100%) rename include/omath/engines/source_engine/{Formulas.hpp => formulas.hpp} (100%) rename include/omath/{Mat.hpp => mat.hpp} (100%) rename include/omath/{Matrix.hpp => matrix.hpp} (100%) rename include/omath/projectile_prediction/{Projectile.hpp => projectile.hpp} (100%) rename include/omath/projectile_prediction/{Target.hpp => target.hpp} (100%) rename include/omath/projection/{Camera.hpp => camera.hpp} (100%) rename include/omath/{Triangle.hpp => triangle.hpp} (100%) rename include/omath/{Vector2.hpp => vector2.hpp} (100%) rename include/omath/{Vector3.hpp => vector3.hpp} (100%) rename include/omath/{Vector4.hpp => vector4.hpp} (100%) rename source/{Matrix.cpp => matrix.cpp} (100%) rename source/projection/{Camera.cpp => camera.cpp} (100%) diff --git a/include/omath/Angle.hpp b/include/omath/angle.hpp similarity index 100% rename from include/omath/Angle.hpp rename to include/omath/angle.hpp diff --git a/include/omath/Angles.hpp b/include/omath/angles.hpp similarity index 100% rename from include/omath/Angles.hpp rename to include/omath/angles.hpp diff --git a/include/omath/Color.hpp b/include/omath/color.hpp similarity index 100% rename from include/omath/Color.hpp rename to include/omath/color.hpp diff --git a/include/omath/engines/iw_engine/Camera.hpp b/include/omath/engines/iw_engine/camera.hpp similarity index 100% rename from include/omath/engines/iw_engine/Camera.hpp rename to include/omath/engines/iw_engine/camera.hpp diff --git a/include/omath/engines/iw_engine/Constants.hpp b/include/omath/engines/iw_engine/constants.hpp similarity index 100% rename from include/omath/engines/iw_engine/Constants.hpp rename to include/omath/engines/iw_engine/constants.hpp diff --git a/include/omath/engines/iw_engine/Formulas.hpp b/include/omath/engines/iw_engine/formulas.hpp similarity index 100% rename from include/omath/engines/iw_engine/Formulas.hpp rename to include/omath/engines/iw_engine/formulas.hpp diff --git a/include/omath/engines/opengl_engine/Camera.hpp b/include/omath/engines/opengl_engine/camera.hpp similarity index 100% rename from include/omath/engines/opengl_engine/Camera.hpp rename to include/omath/engines/opengl_engine/camera.hpp diff --git a/include/omath/engines/opengl_engine/Constants.hpp b/include/omath/engines/opengl_engine/constants.hpp similarity index 100% rename from include/omath/engines/opengl_engine/Constants.hpp rename to include/omath/engines/opengl_engine/constants.hpp diff --git a/include/omath/engines/opengl_engine/Formulas.hpp b/include/omath/engines/opengl_engine/formulas.hpp similarity index 100% rename from include/omath/engines/opengl_engine/Formulas.hpp rename to include/omath/engines/opengl_engine/formulas.hpp diff --git a/include/omath/engines/source_engine/Camera.hpp b/include/omath/engines/source_engine/camera.hpp similarity index 100% rename from include/omath/engines/source_engine/Camera.hpp rename to include/omath/engines/source_engine/camera.hpp diff --git a/include/omath/engines/source_engine/Constants.hpp b/include/omath/engines/source_engine/constants.hpp similarity index 100% rename from include/omath/engines/source_engine/Constants.hpp rename to include/omath/engines/source_engine/constants.hpp diff --git a/include/omath/engines/source_engine/Formulas.hpp b/include/omath/engines/source_engine/formulas.hpp similarity index 100% rename from include/omath/engines/source_engine/Formulas.hpp rename to include/omath/engines/source_engine/formulas.hpp diff --git a/include/omath/Mat.hpp b/include/omath/mat.hpp similarity index 100% rename from include/omath/Mat.hpp rename to include/omath/mat.hpp diff --git a/include/omath/Matrix.hpp b/include/omath/matrix.hpp similarity index 100% rename from include/omath/Matrix.hpp rename to include/omath/matrix.hpp diff --git a/include/omath/projectile_prediction/Projectile.hpp b/include/omath/projectile_prediction/projectile.hpp similarity index 100% rename from include/omath/projectile_prediction/Projectile.hpp rename to include/omath/projectile_prediction/projectile.hpp diff --git a/include/omath/projectile_prediction/Target.hpp b/include/omath/projectile_prediction/target.hpp similarity index 100% rename from include/omath/projectile_prediction/Target.hpp rename to include/omath/projectile_prediction/target.hpp diff --git a/include/omath/projection/Camera.hpp b/include/omath/projection/camera.hpp similarity index 100% rename from include/omath/projection/Camera.hpp rename to include/omath/projection/camera.hpp diff --git a/include/omath/Triangle.hpp b/include/omath/triangle.hpp similarity index 100% rename from include/omath/Triangle.hpp rename to include/omath/triangle.hpp diff --git a/include/omath/Vector2.hpp b/include/omath/vector2.hpp similarity index 100% rename from include/omath/Vector2.hpp rename to include/omath/vector2.hpp diff --git a/include/omath/Vector3.hpp b/include/omath/vector3.hpp similarity index 100% rename from include/omath/Vector3.hpp rename to include/omath/vector3.hpp diff --git a/include/omath/Vector4.hpp b/include/omath/vector4.hpp similarity index 100% rename from include/omath/Vector4.hpp rename to include/omath/vector4.hpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 10ca8ed2..f690fe38 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,5 +1,5 @@ target_sources(omath PRIVATE - Matrix.cpp + matrix.cpp color.cpp ) diff --git a/source/Matrix.cpp b/source/matrix.cpp similarity index 100% rename from source/Matrix.cpp rename to source/matrix.cpp diff --git a/source/projection/Camera.cpp b/source/projection/camera.cpp similarity index 100% rename from source/projection/Camera.cpp rename to source/projection/camera.cpp From c7dda0ff10c1d307bcf16caa66fe21dfce85a9a8 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 21 Mar 2025 04:30:17 +0300 Subject: [PATCH 205/795] changed source files naming --- source/collision/CMakeLists.txt | 2 +- .../{LineTracer.cpp => line_tracer.cpp} | 0 source/engines/iw_engine/CMakeLists.txt | 2 +- .../iw_engine/{Camera.cpp => camera.cpp} | 0 .../iw_engine/{Formulas.cpp => formulas.cpp} | 0 source/engines/opengl_engine/CMakeLists.txt | 2 +- .../opengl_engine/{Camera.cpp => camera.cpp} | 0 .../{Formulas.cpp => formulas.cpp} | 0 source/engines/source_engine/CMakeLists.txt | 2 +- .../source_engine/{Camera.cpp => camera.cpp} | 0 .../{Formulas.cpp => formulas.cpp} | 0 source/pathfinding/CMakeLists.txt | 2 +- source/pathfinding/{Astar.cpp => a_star.cpp} | 0 ...NavigationMesh.cpp => navigation_mesh.cpp} | 0 source/projectile_prediction/CMakeLists.txt | 2 +- ...rojPredEngine.cpp => proj_pred_engine.cpp} | 0 ...gineAVX2.cpp => proj_pred_engine_avx2.cpp} | 0 ...Legacy.cpp => proj_pred_engine_legacy.cpp} | 0 .../{Projectile.cpp => projectile.cpp} | 0 .../{Target.cpp => target.cpp} | 0 source/projection/CMakeLists.txt | 2 +- tests/CMakeLists.txt | 38 +++++++++---------- ...stIwEngine.cpp => unit_test_iw_engine.cpp} | 0 ...itTestOpenGL.cpp => unit_test_open_gl.cpp} | 0 ...Engine.cpp => unit_test_source_engine.cpp} | 0 ...yEngine.cpp => unit_test_unity_engine.cpp} | 0 ...UnitTestAstar.cpp => unit_test_a_star.cpp} | 0 ...{UnitTestAngle.cpp => unit_test_angle.cpp} | 0 ...nitTestAngles.cpp => unit_test_angles.cpp} | 0 ...{UnitTestColor.cpp => unit_test_color.cpp} | 0 ...LineTrace.cpp => unit_test_line_trace.cpp} | 0 .../{UnitTestMat.cpp => unit_test_mat.cpp} | 0 ...nitTestMatrix.cpp => unit_test_matrix.cpp} | 0 ...rediction.cpp => unit_test_prediction.cpp} | 0 ...rojection.cpp => unit_test_projection.cpp} | 0 ...estTriangle.cpp => unit_test_triangle.cpp} | 0 ...tTestVector2.cpp => unit_test_vector2.cpp} | 0 ...tTestVector3.cpp => unit_test_vector3.cpp} | 0 ...tTestVector4.cpp => unit_test_vector4.cpp} | 0 ...ewAngles.cpp => unit_test_view_angles.cpp} | 0 40 files changed, 26 insertions(+), 26 deletions(-) rename source/collision/{LineTracer.cpp => line_tracer.cpp} (100%) rename source/engines/iw_engine/{Camera.cpp => camera.cpp} (100%) rename source/engines/iw_engine/{Formulas.cpp => formulas.cpp} (100%) rename source/engines/opengl_engine/{Camera.cpp => camera.cpp} (100%) rename source/engines/opengl_engine/{Formulas.cpp => formulas.cpp} (100%) rename source/engines/source_engine/{Camera.cpp => camera.cpp} (100%) rename source/engines/source_engine/{Formulas.cpp => formulas.cpp} (100%) rename source/pathfinding/{Astar.cpp => a_star.cpp} (100%) rename source/pathfinding/{NavigationMesh.cpp => navigation_mesh.cpp} (100%) rename source/projectile_prediction/{ProjPredEngine.cpp => proj_pred_engine.cpp} (100%) rename source/projectile_prediction/{ProjPredEngineAVX2.cpp => proj_pred_engine_avx2.cpp} (100%) rename source/projectile_prediction/{ProjPredEngineLegacy.cpp => proj_pred_engine_legacy.cpp} (100%) rename source/projectile_prediction/{Projectile.cpp => projectile.cpp} (100%) rename source/projectile_prediction/{Target.cpp => target.cpp} (100%) rename tests/engines/{UnitTestIwEngine.cpp => unit_test_iw_engine.cpp} (100%) rename tests/engines/{UnitTestOpenGL.cpp => unit_test_open_gl.cpp} (100%) rename tests/engines/{UnitTestSourceEngine.cpp => unit_test_source_engine.cpp} (100%) rename tests/engines/{UnitTestUnityEngine.cpp => unit_test_unity_engine.cpp} (100%) rename tests/general/{UnitTestAstar.cpp => unit_test_a_star.cpp} (100%) rename tests/general/{UnitTestAngle.cpp => unit_test_angle.cpp} (100%) rename tests/general/{UnitTestAngles.cpp => unit_test_angles.cpp} (100%) rename tests/general/{UnitTestColor.cpp => unit_test_color.cpp} (100%) rename tests/general/{UnitTestLineTrace.cpp => unit_test_line_trace.cpp} (100%) rename tests/general/{UnitTestMat.cpp => unit_test_mat.cpp} (100%) rename tests/general/{UnitTestMatrix.cpp => unit_test_matrix.cpp} (100%) rename tests/general/{UnitTestPrediction.cpp => unit_test_prediction.cpp} (100%) rename tests/general/{UnitTestProjection.cpp => unit_test_projection.cpp} (100%) rename tests/general/{UnitTestTriangle.cpp => unit_test_triangle.cpp} (100%) rename tests/general/{UnitTestVector2.cpp => unit_test_vector2.cpp} (100%) rename tests/general/{UnitTestVector3.cpp => unit_test_vector3.cpp} (100%) rename tests/general/{UnitTestVector4.cpp => unit_test_vector4.cpp} (100%) rename tests/general/{UnitTestViewAngles.cpp => unit_test_view_angles.cpp} (100%) diff --git a/source/collision/CMakeLists.txt b/source/collision/CMakeLists.txt index 22a2abc6..09b4793a 100644 --- a/source/collision/CMakeLists.txt +++ b/source/collision/CMakeLists.txt @@ -1,3 +1,3 @@ target_sources(omath PRIVATE - LineTracer.cpp + line_tracer.cpp ) diff --git a/source/collision/LineTracer.cpp b/source/collision/line_tracer.cpp similarity index 100% rename from source/collision/LineTracer.cpp rename to source/collision/line_tracer.cpp diff --git a/source/engines/iw_engine/CMakeLists.txt b/source/engines/iw_engine/CMakeLists.txt index 0153efab..e5e97417 100644 --- a/source/engines/iw_engine/CMakeLists.txt +++ b/source/engines/iw_engine/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE Camera.cpp Formulas.cpp) \ No newline at end of file +target_sources(omath PRIVATE camera.cpp formulas.cpp) \ No newline at end of file diff --git a/source/engines/iw_engine/Camera.cpp b/source/engines/iw_engine/camera.cpp similarity index 100% rename from source/engines/iw_engine/Camera.cpp rename to source/engines/iw_engine/camera.cpp diff --git a/source/engines/iw_engine/Formulas.cpp b/source/engines/iw_engine/formulas.cpp similarity index 100% rename from source/engines/iw_engine/Formulas.cpp rename to source/engines/iw_engine/formulas.cpp diff --git a/source/engines/opengl_engine/CMakeLists.txt b/source/engines/opengl_engine/CMakeLists.txt index 0153efab..e5e97417 100644 --- a/source/engines/opengl_engine/CMakeLists.txt +++ b/source/engines/opengl_engine/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE Camera.cpp Formulas.cpp) \ No newline at end of file +target_sources(omath PRIVATE camera.cpp formulas.cpp) \ No newline at end of file diff --git a/source/engines/opengl_engine/Camera.cpp b/source/engines/opengl_engine/camera.cpp similarity index 100% rename from source/engines/opengl_engine/Camera.cpp rename to source/engines/opengl_engine/camera.cpp diff --git a/source/engines/opengl_engine/Formulas.cpp b/source/engines/opengl_engine/formulas.cpp similarity index 100% rename from source/engines/opengl_engine/Formulas.cpp rename to source/engines/opengl_engine/formulas.cpp diff --git a/source/engines/source_engine/CMakeLists.txt b/source/engines/source_engine/CMakeLists.txt index 0153efab..e5e97417 100644 --- a/source/engines/source_engine/CMakeLists.txt +++ b/source/engines/source_engine/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE Camera.cpp Formulas.cpp) \ No newline at end of file +target_sources(omath PRIVATE camera.cpp formulas.cpp) \ No newline at end of file diff --git a/source/engines/source_engine/Camera.cpp b/source/engines/source_engine/camera.cpp similarity index 100% rename from source/engines/source_engine/Camera.cpp rename to source/engines/source_engine/camera.cpp diff --git a/source/engines/source_engine/Formulas.cpp b/source/engines/source_engine/formulas.cpp similarity index 100% rename from source/engines/source_engine/Formulas.cpp rename to source/engines/source_engine/formulas.cpp diff --git a/source/pathfinding/CMakeLists.txt b/source/pathfinding/CMakeLists.txt index d1da67f1..e7cc7b93 100644 --- a/source/pathfinding/CMakeLists.txt +++ b/source/pathfinding/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE NavigationMesh.cpp Astar.cpp) \ No newline at end of file +target_sources(omath PRIVATE navigation_mesh.cpp a_star.cpp) \ No newline at end of file diff --git a/source/pathfinding/Astar.cpp b/source/pathfinding/a_star.cpp similarity index 100% rename from source/pathfinding/Astar.cpp rename to source/pathfinding/a_star.cpp diff --git a/source/pathfinding/NavigationMesh.cpp b/source/pathfinding/navigation_mesh.cpp similarity index 100% rename from source/pathfinding/NavigationMesh.cpp rename to source/pathfinding/navigation_mesh.cpp diff --git a/source/projectile_prediction/CMakeLists.txt b/source/projectile_prediction/CMakeLists.txt index 623aa656..1f189f7f 100644 --- a/source/projectile_prediction/CMakeLists.txt +++ b/source/projectile_prediction/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE ProjPredEngineLegacy.cpp Projectile.cpp Target.cpp ProjPredEngineAVX2.cpp ProjPredEngine.cpp) \ No newline at end of file +target_sources(omath PRIVATE proj_pred_engine_legacy.cpp projectile.cpp target.cpp proj_pred_engine_avx2.cpp proj_pred_engine.cpp) \ No newline at end of file diff --git a/source/projectile_prediction/ProjPredEngine.cpp b/source/projectile_prediction/proj_pred_engine.cpp similarity index 100% rename from source/projectile_prediction/ProjPredEngine.cpp rename to source/projectile_prediction/proj_pred_engine.cpp diff --git a/source/projectile_prediction/ProjPredEngineAVX2.cpp b/source/projectile_prediction/proj_pred_engine_avx2.cpp similarity index 100% rename from source/projectile_prediction/ProjPredEngineAVX2.cpp rename to source/projectile_prediction/proj_pred_engine_avx2.cpp diff --git a/source/projectile_prediction/ProjPredEngineLegacy.cpp b/source/projectile_prediction/proj_pred_engine_legacy.cpp similarity index 100% rename from source/projectile_prediction/ProjPredEngineLegacy.cpp rename to source/projectile_prediction/proj_pred_engine_legacy.cpp diff --git a/source/projectile_prediction/Projectile.cpp b/source/projectile_prediction/projectile.cpp similarity index 100% rename from source/projectile_prediction/Projectile.cpp rename to source/projectile_prediction/projectile.cpp diff --git a/source/projectile_prediction/Target.cpp b/source/projectile_prediction/target.cpp similarity index 100% rename from source/projectile_prediction/Target.cpp rename to source/projectile_prediction/target.cpp diff --git a/source/projection/CMakeLists.txt b/source/projection/CMakeLists.txt index 0abf8686..1ca3a279 100644 --- a/source/projection/CMakeLists.txt +++ b/source/projection/CMakeLists.txt @@ -1 +1 @@ -target_sources(omath PRIVATE Camera.cpp) \ No newline at end of file +target_sources(omath PRIVATE camera.cpp) \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2320af96..244f7bb6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,25 +4,25 @@ project(unit-tests) include(GoogleTest) add_executable(unit-tests - general/UnitTestPrediction.cpp - general/UnitTestMatrix.cpp - general/UnitTestMat.cpp - general/UnitTestAstar.cpp - general/UnitTestProjection.cpp - general/UnitTestVector3.cpp - general/UnitTestVector2.cpp - general/UnitTestColor.cpp - general/UnitTestVector4.cpp - general/UnitTestLineTrace.cpp - general/UnitTestAngles.cpp - general/UnitTestViewAngles.cpp - general/UnitTestAngle.cpp - general/UnitTestTriangle.cpp - - engines/UnitTestOpenGL.cpp - engines/UnitTestUnityEngine.cpp - engines/UnitTestSourceEngine.cpp - engines/UnitTestIwEngine.cpp + general/unit_test_prediction.cpp + general/unit_test_matrix.cpp + general/unit_test_mat.cpp + general/unit_test_a_star.cpp + general/unit_test_projection.cpp + general/unit_test_vector3.cpp + general/unit_test_vector2.cpp + general/unit_test_color.cpp + general/unit_test_vector4.cpp + general/unit_test_line_trace.cpp + general/unit_test_angles.cpp + general/unit_test_view_angles.cpp + general/unit_test_angle.cpp + general/unit_test_triangle.cpp + + engines/unit_test_open_gl.cpp + engines/unit_test_unity_engine.cpp + engines/unit_test_source_engine.cpp + engines/unit_test_iw_engine.cpp ) diff --git a/tests/engines/UnitTestIwEngine.cpp b/tests/engines/unit_test_iw_engine.cpp similarity index 100% rename from tests/engines/UnitTestIwEngine.cpp rename to tests/engines/unit_test_iw_engine.cpp diff --git a/tests/engines/UnitTestOpenGL.cpp b/tests/engines/unit_test_open_gl.cpp similarity index 100% rename from tests/engines/UnitTestOpenGL.cpp rename to tests/engines/unit_test_open_gl.cpp diff --git a/tests/engines/UnitTestSourceEngine.cpp b/tests/engines/unit_test_source_engine.cpp similarity index 100% rename from tests/engines/UnitTestSourceEngine.cpp rename to tests/engines/unit_test_source_engine.cpp diff --git a/tests/engines/UnitTestUnityEngine.cpp b/tests/engines/unit_test_unity_engine.cpp similarity index 100% rename from tests/engines/UnitTestUnityEngine.cpp rename to tests/engines/unit_test_unity_engine.cpp diff --git a/tests/general/UnitTestAstar.cpp b/tests/general/unit_test_a_star.cpp similarity index 100% rename from tests/general/UnitTestAstar.cpp rename to tests/general/unit_test_a_star.cpp diff --git a/tests/general/UnitTestAngle.cpp b/tests/general/unit_test_angle.cpp similarity index 100% rename from tests/general/UnitTestAngle.cpp rename to tests/general/unit_test_angle.cpp diff --git a/tests/general/UnitTestAngles.cpp b/tests/general/unit_test_angles.cpp similarity index 100% rename from tests/general/UnitTestAngles.cpp rename to tests/general/unit_test_angles.cpp diff --git a/tests/general/UnitTestColor.cpp b/tests/general/unit_test_color.cpp similarity index 100% rename from tests/general/UnitTestColor.cpp rename to tests/general/unit_test_color.cpp diff --git a/tests/general/UnitTestLineTrace.cpp b/tests/general/unit_test_line_trace.cpp similarity index 100% rename from tests/general/UnitTestLineTrace.cpp rename to tests/general/unit_test_line_trace.cpp diff --git a/tests/general/UnitTestMat.cpp b/tests/general/unit_test_mat.cpp similarity index 100% rename from tests/general/UnitTestMat.cpp rename to tests/general/unit_test_mat.cpp diff --git a/tests/general/UnitTestMatrix.cpp b/tests/general/unit_test_matrix.cpp similarity index 100% rename from tests/general/UnitTestMatrix.cpp rename to tests/general/unit_test_matrix.cpp diff --git a/tests/general/UnitTestPrediction.cpp b/tests/general/unit_test_prediction.cpp similarity index 100% rename from tests/general/UnitTestPrediction.cpp rename to tests/general/unit_test_prediction.cpp diff --git a/tests/general/UnitTestProjection.cpp b/tests/general/unit_test_projection.cpp similarity index 100% rename from tests/general/UnitTestProjection.cpp rename to tests/general/unit_test_projection.cpp diff --git a/tests/general/UnitTestTriangle.cpp b/tests/general/unit_test_triangle.cpp similarity index 100% rename from tests/general/UnitTestTriangle.cpp rename to tests/general/unit_test_triangle.cpp diff --git a/tests/general/UnitTestVector2.cpp b/tests/general/unit_test_vector2.cpp similarity index 100% rename from tests/general/UnitTestVector2.cpp rename to tests/general/unit_test_vector2.cpp diff --git a/tests/general/UnitTestVector3.cpp b/tests/general/unit_test_vector3.cpp similarity index 100% rename from tests/general/UnitTestVector3.cpp rename to tests/general/unit_test_vector3.cpp diff --git a/tests/general/UnitTestVector4.cpp b/tests/general/unit_test_vector4.cpp similarity index 100% rename from tests/general/UnitTestVector4.cpp rename to tests/general/unit_test_vector4.cpp diff --git a/tests/general/UnitTestViewAngles.cpp b/tests/general/unit_test_view_angles.cpp similarity index 100% rename from tests/general/UnitTestViewAngles.cpp rename to tests/general/unit_test_view_angles.cpp From 5acd166d8f0982b51d7bc0982ce30719e10d15b7 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 21 Mar 2025 04:40:59 +0300 Subject: [PATCH 206/795] fixed include names --- include/omath/angle.hpp | 2 +- include/omath/angles.hpp | 2 +- include/omath/color.hpp | 4 ++-- include/omath/engines/iw_engine/camera.hpp | 4 ++-- include/omath/engines/iw_engine/constants.hpp | 6 +++--- include/omath/engines/iw_engine/formulas.hpp | 2 +- include/omath/engines/opengl_engine/camera.hpp | 4 ++-- include/omath/engines/opengl_engine/constants.hpp | 6 +++--- include/omath/engines/opengl_engine/formulas.hpp | 2 +- include/omath/engines/source_engine/camera.hpp | 4 ++-- include/omath/engines/source_engine/constants.hpp | 6 +++--- include/omath/engines/source_engine/formulas.hpp | 2 +- include/omath/mat.hpp | 4 ++-- include/omath/matrix.hpp | 2 +- include/omath/pathfinding/a_star.hpp | 4 ++-- include/omath/pathfinding/navigation_mesh.hpp | 6 +++--- include/omath/projectile_prediction/proj_pred_engine.hpp | 6 +++--- .../omath/projectile_prediction/proj_pred_engine_legacy.hpp | 6 +++--- include/omath/projectile_prediction/projectile.hpp | 2 +- include/omath/projectile_prediction/target.hpp | 2 +- include/omath/projection/camera.hpp | 6 +++--- include/omath/triangle.hpp | 2 +- include/omath/vector3.hpp | 5 ++--- include/omath/vector4.hpp | 2 +- source/engines/iw_engine/camera.cpp | 4 ++-- source/engines/iw_engine/formulas.cpp | 2 +- source/engines/opengl_engine/camera.cpp | 4 ++-- source/engines/opengl_engine/formulas.cpp | 2 +- source/engines/source_engine/camera.cpp | 4 ++-- source/engines/source_engine/formulas.cpp | 2 +- source/pathfinding/navigation_mesh.cpp | 2 +- source/projectile_prediction/proj_pred_engine_legacy.cpp | 2 +- source/projectile_prediction/projectile.cpp | 4 ++-- source/projectile_prediction/target.cpp | 2 +- source/projection/camera.cpp | 2 +- 35 files changed, 60 insertions(+), 61 deletions(-) diff --git a/include/omath/angle.hpp b/include/omath/angle.hpp index 253604df..0f88ab55 100644 --- a/include/omath/angle.hpp +++ b/include/omath/angle.hpp @@ -3,7 +3,7 @@ // #pragma once -#include "omath/Angles.hpp" +#include "omath/angles.hpp" #include namespace omath diff --git a/include/omath/angles.hpp b/include/omath/angles.hpp index c578afb0..11a69d4b 100644 --- a/include/omath/angles.hpp +++ b/include/omath/angles.hpp @@ -3,8 +3,8 @@ // #pragma once -#include #include +#include namespace omath::angles { diff --git a/include/omath/color.hpp b/include/omath/color.hpp index 54848fcf..3734f371 100644 --- a/include/omath/color.hpp +++ b/include/omath/color.hpp @@ -4,9 +4,9 @@ #pragma once -#include "omath/Vector3.hpp" #include -#include "omath/Vector4.hpp" +#include "omath/vector3.hpp" +#include "omath/vector4.hpp" namespace omath { diff --git a/include/omath/engines/iw_engine/camera.hpp b/include/omath/engines/iw_engine/camera.hpp index cd413f3b..844ff5dc 100644 --- a/include/omath/engines/iw_engine/camera.hpp +++ b/include/omath/engines/iw_engine/camera.hpp @@ -3,8 +3,8 @@ // #pragma once -#include "Constants.hpp" -#include "omath/projection/Camera.hpp" +#include "omath/engines/iw_engine/constants.hpp" +#include "omath/projection/camera.hpp" namespace omath::iw_engine { diff --git a/include/omath/engines/iw_engine/constants.hpp b/include/omath/engines/iw_engine/constants.hpp index bea065ed..ff7d41d9 100644 --- a/include/omath/engines/iw_engine/constants.hpp +++ b/include/omath/engines/iw_engine/constants.hpp @@ -3,9 +3,9 @@ // #pragma once -#include -#include -#include +#include +#include +#include #include namespace omath::iw_engine diff --git a/include/omath/engines/iw_engine/formulas.hpp b/include/omath/engines/iw_engine/formulas.hpp index 72b38535..a604f48a 100644 --- a/include/omath/engines/iw_engine/formulas.hpp +++ b/include/omath/engines/iw_engine/formulas.hpp @@ -3,7 +3,7 @@ // #pragma once -#include "Constants.hpp" +#include "omath/engines/iw_engine/constants.hpp" namespace omath::iw_engine { diff --git a/include/omath/engines/opengl_engine/camera.hpp b/include/omath/engines/opengl_engine/camera.hpp index bbe29025..bfee7c44 100644 --- a/include/omath/engines/opengl_engine/camera.hpp +++ b/include/omath/engines/opengl_engine/camera.hpp @@ -2,8 +2,8 @@ // Created by Orange on 12/23/2024. // #pragma once -#include "Constants.hpp" -#include "omath/projection/Camera.hpp" +#include "omath/engines/opengl_engine/constants.hpp" +#include "omath/projection/camera.hpp" namespace omath::opengl_engine { diff --git a/include/omath/engines/opengl_engine/constants.hpp b/include/omath/engines/opengl_engine/constants.hpp index cca54285..66aac7d7 100644 --- a/include/omath/engines/opengl_engine/constants.hpp +++ b/include/omath/engines/opengl_engine/constants.hpp @@ -3,9 +3,9 @@ // #pragma once -#include -#include -#include +#include +#include +#include #include namespace omath::opengl_engine diff --git a/include/omath/engines/opengl_engine/formulas.hpp b/include/omath/engines/opengl_engine/formulas.hpp index e0e97414..d9dcfe7f 100644 --- a/include/omath/engines/opengl_engine/formulas.hpp +++ b/include/omath/engines/opengl_engine/formulas.hpp @@ -2,7 +2,7 @@ // Created by Orange on 12/23/2024. // #pragma once -#include "Constants.hpp" +#include "omath/engines/opengl_engine/constants.hpp" namespace omath::opengl_engine diff --git a/include/omath/engines/source_engine/camera.hpp b/include/omath/engines/source_engine/camera.hpp index fa7b2b26..baa11e31 100644 --- a/include/omath/engines/source_engine/camera.hpp +++ b/include/omath/engines/source_engine/camera.hpp @@ -2,8 +2,8 @@ // Created by Orange on 12/4/2024. // #pragma once -#include "Constants.hpp" -#include "omath/projection/Camera.hpp" +#include "omath/engines/source_engine/constants.hpp" +#include "omath/projection/camera.hpp" namespace omath::source_engine { diff --git a/include/omath/engines/source_engine/constants.hpp b/include/omath/engines/source_engine/constants.hpp index 6d6d0003..3c7e1df1 100644 --- a/include/omath/engines/source_engine/constants.hpp +++ b/include/omath/engines/source_engine/constants.hpp @@ -3,9 +3,9 @@ // #pragma once -#include -#include -#include +#include +#include +#include #include namespace omath::source_engine diff --git a/include/omath/engines/source_engine/formulas.hpp b/include/omath/engines/source_engine/formulas.hpp index d47d2b62..fd6415e4 100644 --- a/include/omath/engines/source_engine/formulas.hpp +++ b/include/omath/engines/source_engine/formulas.hpp @@ -2,7 +2,7 @@ // Created by Orange on 12/4/2024. // #pragma once -#include "Constants.hpp" +#include "omath/engines/source_engine/constants.hpp" namespace omath::source_engine { diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 864ad58d..674612a3 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -4,11 +4,11 @@ #pragma once #include #include +#include #include #include #include -#include "Vector3.hpp" -#include +#include "omath/vector3.hpp" namespace omath { diff --git a/include/omath/matrix.hpp b/include/omath/matrix.hpp index 3043bc5a..a8c19e4d 100644 --- a/include/omath/matrix.hpp +++ b/include/omath/matrix.hpp @@ -2,7 +2,7 @@ #include #include #include -#include "Vector3.hpp" +#include "omath/vector3.hpp" namespace omath { diff --git a/include/omath/pathfinding/a_star.hpp b/include/omath/pathfinding/a_star.hpp index daac92d0..b2a315e8 100644 --- a/include/omath/pathfinding/a_star.hpp +++ b/include/omath/pathfinding/a_star.hpp @@ -4,8 +4,8 @@ #pragma once #include -#include "navigation_mesh.hpp" -#include "omath/Vector3.hpp" +#include "omath/pathfinding/navigation_mesh.hpp" +#include "omath/vector3.hpp" namespace omath::pathfinding { diff --git a/include/omath/pathfinding/navigation_mesh.hpp b/include/omath/pathfinding/navigation_mesh.hpp index 785f619e..f9d8684f 100644 --- a/include/omath/pathfinding/navigation_mesh.hpp +++ b/include/omath/pathfinding/navigation_mesh.hpp @@ -4,17 +4,17 @@ #pragma once -#include "omath/Vector3.hpp" #include -#include #include +#include +#include "omath/vector3.hpp" namespace omath::pathfinding { enum Error { - + }; class NavigationMesh final diff --git a/include/omath/projectile_prediction/proj_pred_engine.hpp b/include/omath/projectile_prediction/proj_pred_engine.hpp index 83adde05..b256641f 100644 --- a/include/omath/projectile_prediction/proj_pred_engine.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine.hpp @@ -2,9 +2,9 @@ // Created by Vlad on 2/23/2025. // #pragma once -#include "Projectile.hpp" -#include "Target.hpp" -#include "omath/Vector3.hpp" +#include "omath/projectile_prediction/target.hpp" +#include "omath/vector3.hpp" +#include "projectile.hpp" namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index 0b38e73d..8eb5a5ec 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -5,10 +5,10 @@ #pragma once #include -#include "omath/Vector3.hpp" #include "omath/projectile_prediction/proj_pred_engine.hpp" -#include "omath/projectile_prediction/Projectile.hpp" -#include "omath/projectile_prediction/Target.hpp" +#include "omath/projectile_prediction/projectile.hpp" +#include "omath/projectile_prediction/target.hpp" +#include "omath/vector3.hpp" namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/projectile.hpp b/include/omath/projectile_prediction/projectile.hpp index c2e0f7bc..b32b4afc 100644 --- a/include/omath/projectile_prediction/projectile.hpp +++ b/include/omath/projectile_prediction/projectile.hpp @@ -3,7 +3,7 @@ // #pragma once -#include "omath/Vector3.hpp" +#include "omath/vector3.hpp" namespace omath::projectile_prediction { diff --git a/include/omath/projectile_prediction/target.hpp b/include/omath/projectile_prediction/target.hpp index a5e4a12b..a7808585 100644 --- a/include/omath/projectile_prediction/target.hpp +++ b/include/omath/projectile_prediction/target.hpp @@ -3,7 +3,7 @@ // #pragma once -#include "omath/Vector3.hpp" +#include "omath/vector3.hpp" namespace omath::projectile_prediction { diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index 8fdd05dc..add81ced 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -5,11 +5,11 @@ #pragma once #include -#include -#include -#include "error_codes.hpp" #include +#include +#include #include +#include "omath/projection/error_codes.hpp" namespace omath::projection { diff --git a/include/omath/triangle.hpp b/include/omath/triangle.hpp index a2fc92e1..5c006018 100644 --- a/include/omath/triangle.hpp +++ b/include/omath/triangle.hpp @@ -2,7 +2,7 @@ // Created by Orange on 11/13/2024. // #pragma once -#include "omath/Vector3.hpp" +#include "omath/vector3.hpp" namespace omath { diff --git a/include/omath/vector3.hpp b/include/omath/vector3.hpp index c3ffcd0b..6b769071 100644 --- a/include/omath/vector3.hpp +++ b/include/omath/vector3.hpp @@ -4,12 +4,11 @@ #pragma once -#include "Vector2.hpp" #include #include #include -#include "omath/Angle.hpp" -#include "omath/Vector2.hpp" +#include "omath/angle.hpp" +#include "omath/vector2.hpp" namespace omath { diff --git a/include/omath/vector4.hpp b/include/omath/vector4.hpp index 258cf738..e50528fb 100644 --- a/include/omath/vector4.hpp +++ b/include/omath/vector4.hpp @@ -3,8 +3,8 @@ // #pragma once -#include #include +#include namespace omath diff --git a/source/engines/iw_engine/camera.cpp b/source/engines/iw_engine/camera.cpp index 5b7cfe09..6261e086 100644 --- a/source/engines/iw_engine/camera.cpp +++ b/source/engines/iw_engine/camera.cpp @@ -1,8 +1,8 @@ // // Created by Vlad on 3/17/2025. // -#include "omath/engines/iw_engine/Camera.hpp" -#include "omath/engines/iw_engine/Formulas.hpp" +#include "omath/engines/iw_engine/camera.hpp" +#include "omath/engines/iw_engine/formulas.hpp" namespace omath::iw_engine { diff --git a/source/engines/iw_engine/formulas.cpp b/source/engines/iw_engine/formulas.cpp index 0561b431..3bc47dce 100644 --- a/source/engines/iw_engine/formulas.cpp +++ b/source/engines/iw_engine/formulas.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 3/19/2025. // -#include "omath/engines/iw_engine/Formulas.hpp" +#include "omath/engines/iw_engine/formulas.hpp" namespace omath::iw_engine diff --git a/source/engines/opengl_engine/camera.cpp b/source/engines/opengl_engine/camera.cpp index d7bb7f46..07d4f508 100644 --- a/source/engines/opengl_engine/camera.cpp +++ b/source/engines/opengl_engine/camera.cpp @@ -1,8 +1,8 @@ // // Created by Orange on 12/23/2024. // -#include "omath/engines/opengl_engine/Camera.hpp" -#include "omath/engines/opengl_engine/Formulas.hpp" +#include "omath/engines/opengl_engine/camera.hpp" +#include "omath/engines/opengl_engine/formulas.hpp" namespace omath::opengl_engine diff --git a/source/engines/opengl_engine/formulas.cpp b/source/engines/opengl_engine/formulas.cpp index c2fa6984..be1a6380 100644 --- a/source/engines/opengl_engine/formulas.cpp +++ b/source/engines/opengl_engine/formulas.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 3/19/2025. // -#include "omath/engines/opengl_engine/Formulas.hpp" +#include "omath/engines/opengl_engine/formulas.hpp" namespace omath::opengl_engine diff --git a/source/engines/source_engine/camera.cpp b/source/engines/source_engine/camera.cpp index 2ea32402..2906b550 100644 --- a/source/engines/source_engine/camera.cpp +++ b/source/engines/source_engine/camera.cpp @@ -1,8 +1,8 @@ // // Created by Orange on 12/4/2024. // -#include "omath/engines/source_engine/Camera.hpp" -#include "omath/engines/source_engine/Formulas.hpp" +#include "omath/engines/source_engine/camera.hpp" +#include "omath/engines/source_engine/formulas.hpp" namespace omath::source_engine diff --git a/source/engines/source_engine/formulas.cpp b/source/engines/source_engine/formulas.cpp index 1bbb90a4..10511381 100644 --- a/source/engines/source_engine/formulas.cpp +++ b/source/engines/source_engine/formulas.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 3/19/2025. // -#include +#include namespace omath::source_engine diff --git a/source/pathfinding/navigation_mesh.cpp b/source/pathfinding/navigation_mesh.cpp index f908cc86..2f985eed 100644 --- a/source/pathfinding/navigation_mesh.cpp +++ b/source/pathfinding/navigation_mesh.cpp @@ -3,8 +3,8 @@ // #include "omath/pathfinding/navigation_mesh.hpp" -#include #include +#include namespace omath::pathfinding { std::expected, std::string> NavigationMesh::GetClosestVertex(const Vector3 &point) const diff --git a/source/projectile_prediction/proj_pred_engine_legacy.cpp b/source/projectile_prediction/proj_pred_engine_legacy.cpp index 2e49b787..557fa2a2 100644 --- a/source/projectile_prediction/proj_pred_engine_legacy.cpp +++ b/source/projectile_prediction/proj_pred_engine_legacy.cpp @@ -1,6 +1,6 @@ #include "omath/projectile_prediction/proj_pred_engine_legacy.hpp" #include -#include +#include namespace omath::projectile_prediction { diff --git a/source/projectile_prediction/projectile.cpp b/source/projectile_prediction/projectile.cpp index 193b9a57..c003562f 100644 --- a/source/projectile_prediction/projectile.cpp +++ b/source/projectile_prediction/projectile.cpp @@ -2,9 +2,9 @@ // Created by Vlad on 6/9/2024. // -#include "omath/projectile_prediction/Projectile.hpp" +#include "omath/projectile_prediction/projectile.hpp" -#include +#include namespace omath::projectile_prediction { diff --git a/source/projectile_prediction/target.cpp b/source/projectile_prediction/target.cpp index 5c30ee0a..b0edda4a 100644 --- a/source/projectile_prediction/target.cpp +++ b/source/projectile_prediction/target.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 6/9/2024. // -#include "omath/projectile_prediction/Projectile.hpp" +#include "omath/projectile_prediction/projectile.hpp" namespace omath::prediction diff --git a/source/projection/camera.cpp b/source/projection/camera.cpp index dc0e0707..e50b6ebd 100644 --- a/source/projection/camera.cpp +++ b/source/projection/camera.cpp @@ -1,7 +1,7 @@ // // Created by Vlad on 27.08.2024. // -#include "omath/projection/Camera.hpp" +#include "omath/projection/camera.hpp" namespace omath::projection From b8d79eb8b4802a32c93c98120bb3b26f2421480e Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 21 Mar 2025 04:43:57 +0300 Subject: [PATCH 207/795] fixed example --- examples/ExampleProjMatBuilder.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/ExampleProjMatBuilder.cpp b/examples/ExampleProjMatBuilder.cpp index f06cc3c9..737b4e8b 100644 --- a/examples/ExampleProjMatBuilder.cpp +++ b/examples/ExampleProjMatBuilder.cpp @@ -1,11 +1,11 @@ // // Created by Vlad on 3/19/2025. // + + #include -#include -#include -#include #include +#include int main() From a8922230b359a4bb6490149c7b630cf6418dd8bc Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 21 Mar 2025 04:45:02 +0300 Subject: [PATCH 208/795] fixed name --- examples/CMakeLists.txt | 2 +- .../{ExampleProjMatBuilder.cpp => example_proj_mat_builder.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/{ExampleProjMatBuilder.cpp => example_proj_mat_builder.cpp} (100%) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 11580f0e..36088b7e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ project(examples) -add_executable(ExampleProjectionMatrixBuilder ExampleProjMatBuilder.cpp) +add_executable(ExampleProjectionMatrixBuilder example_proj_mat_builder.cpp) target_link_libraries(ExampleProjectionMatrixBuilder PRIVATE omath::omath) \ No newline at end of file diff --git a/examples/ExampleProjMatBuilder.cpp b/examples/example_proj_mat_builder.cpp similarity index 100% rename from examples/ExampleProjMatBuilder.cpp rename to examples/example_proj_mat_builder.cpp From 713af1b7729507b987fbccbf859c7da24cb7471a Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 21 Mar 2025 04:46:23 +0300 Subject: [PATCH 209/795] fix --- include/omath/projectile_prediction/proj_pred_engine.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/projectile_prediction/proj_pred_engine.hpp b/include/omath/projectile_prediction/proj_pred_engine.hpp index b256641f..d507dbee 100644 --- a/include/omath/projectile_prediction/proj_pred_engine.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine.hpp @@ -2,9 +2,9 @@ // Created by Vlad on 2/23/2025. // #pragma once +#include "omath/projectile_prediction/projectile.hpp" #include "omath/projectile_prediction/target.hpp" #include "omath/vector3.hpp" -#include "projectile.hpp" namespace omath::projectile_prediction From 59d686e2523179775e82264fb06bf1f2c7d7d083 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 21 Mar 2025 04:47:01 +0300 Subject: [PATCH 210/795] fix --- include/omath/projectile_prediction/proj_pred_engine_avx2.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp b/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp index 84a57e5c..f4b925ec 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp @@ -2,7 +2,7 @@ // Created by Vlad on 2/23/2025. // #pragma once -#include "proj_pred_engine.hpp" +#include "omath/projectile_prediction/proj_pred_engine.hpp" namespace omath::projectile_prediction { From 17de6d407c17a85463bbcf5605b8ebc6122d4dbb Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 21 Mar 2025 04:48:38 +0300 Subject: [PATCH 211/795] style fix --- include/omath/pathfinding/navigation_mesh.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/omath/pathfinding/navigation_mesh.hpp b/include/omath/pathfinding/navigation_mesh.hpp index f9d8684f..ce763e9a 100644 --- a/include/omath/pathfinding/navigation_mesh.hpp +++ b/include/omath/pathfinding/navigation_mesh.hpp @@ -20,19 +20,19 @@ namespace omath::pathfinding class NavigationMesh final { public: - [[nodiscard]] std::expected, std::string> GetClosestVertex(const Vector3& point) const; - [[nodiscard]] const std::vector>& GetNeighbors(const Vector3& vertex) const; [[nodiscard]] bool Empty() const; + [[nodiscard]] std::vector Serialize() const; + void Deserialize(const std::vector& raw); std::unordered_map, std::vector>> m_verTextMap; }; -} \ No newline at end of file +} // namespace omath::pathfinding From e46067b0b98d9c6e95c696ccab583434539dbc72 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 21 Mar 2025 04:58:28 +0300 Subject: [PATCH 212/795] fixed tests includes --- tests/engines/unit_test_iw_engine.cpp | 6 +++--- tests/engines/unit_test_open_gl.cpp | 6 +++--- tests/engines/unit_test_source_engine.cpp | 6 +++--- tests/general/unit_test_angles.cpp | 3 +-- tests/general/unit_test_color.cpp | 2 +- tests/general/unit_test_line_trace.cpp | 4 ++-- tests/general/unit_test_mat.cpp | 4 ++-- tests/general/unit_test_matrix.cpp | 4 ++-- tests/general/unit_test_projection.cpp | 4 ++-- tests/general/unit_test_triangle.cpp | 6 +++--- tests/general/unit_test_vector2.cpp | 6 +++--- tests/general/unit_test_vector3.cpp | 6 +++--- tests/general/unit_test_vector4.cpp | 2 +- 13 files changed, 29 insertions(+), 30 deletions(-) diff --git a/tests/engines/unit_test_iw_engine.cpp b/tests/engines/unit_test_iw_engine.cpp index 00bdc409..cd42ee44 100644 --- a/tests/engines/unit_test_iw_engine.cpp +++ b/tests/engines/unit_test_iw_engine.cpp @@ -2,9 +2,9 @@ // Created by Vlad on 3/17/2025. // #include -#include -#include -#include +#include +#include +#include TEST(UnitTestEwEngine, ForwardVector) diff --git a/tests/engines/unit_test_open_gl.cpp b/tests/engines/unit_test_open_gl.cpp index ab42268d..5e0a4624 100644 --- a/tests/engines/unit_test_open_gl.cpp +++ b/tests/engines/unit_test_open_gl.cpp @@ -2,9 +2,9 @@ // Created by Orange on 11/23/2024. // #include -#include -#include -#include +#include +#include +#include TEST(UnitTestOpenGL, ForwardVector) diff --git a/tests/engines/unit_test_source_engine.cpp b/tests/engines/unit_test_source_engine.cpp index 7d4d71a9..1b9592c7 100644 --- a/tests/engines/unit_test_source_engine.cpp +++ b/tests/engines/unit_test_source_engine.cpp @@ -2,9 +2,9 @@ // Created by Orange on 11/23/2024. // #include -#include -#include -#include +#include +#include +#include TEST(UnitTestSourceEngine, ForwardVector) diff --git a/tests/general/unit_test_angles.cpp b/tests/general/unit_test_angles.cpp index 37b00ffd..adbabeae 100644 --- a/tests/general/unit_test_angles.cpp +++ b/tests/general/unit_test_angles.cpp @@ -2,8 +2,7 @@ // Created by Orange on 11/30/2024. // #include -#include -#include +#include TEST(UnitTestAngles, RadiansToDeg) { diff --git a/tests/general/unit_test_color.cpp b/tests/general/unit_test_color.cpp index aade7f88..01208622 100644 --- a/tests/general/unit_test_color.cpp +++ b/tests/general/unit_test_color.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 01.09.2024. // #include -#include +#include using namespace omath; diff --git a/tests/general/unit_test_line_trace.cpp b/tests/general/unit_test_line_trace.cpp index 0b72d2c8..c5fc23b5 100644 --- a/tests/general/unit_test_line_trace.cpp +++ b/tests/general/unit_test_line_trace.cpp @@ -1,7 +1,7 @@ #include "gtest/gtest.h" #include "omath/collision/line_tracer.hpp" -#include "omath/Triangle.hpp" -#include "omath/Vector3.hpp" +#include "omath/triangle.hpp" +#include "omath/vector3.hpp" using namespace omath; using namespace omath::collision; diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index 710a2a2b..97b94d18 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -1,7 +1,7 @@ // UnitTestMat.cpp #include -#include "omath/Mat.hpp" -#include "omath/Vector3.hpp" +#include "omath/mat.hpp" +#include "omath/vector3.hpp" using namespace omath; diff --git a/tests/general/unit_test_matrix.cpp b/tests/general/unit_test_matrix.cpp index 1fa94b03..0594ff44 100644 --- a/tests/general/unit_test_matrix.cpp +++ b/tests/general/unit_test_matrix.cpp @@ -2,8 +2,8 @@ // Created by vlad on 5/18/2024. // #include -#include -#include "omath/Vector3.hpp" +#include +#include "omath/vector3.hpp" using namespace omath; diff --git a/tests/general/unit_test_projection.cpp b/tests/general/unit_test_projection.cpp index 65819e08..6acc9d1c 100644 --- a/tests/general/unit_test_projection.cpp +++ b/tests/general/unit_test_projection.cpp @@ -3,8 +3,8 @@ // #include #include -#include -#include +#include +#include #include TEST(UnitTestProjection, Projection) diff --git a/tests/general/unit_test_triangle.cpp b/tests/general/unit_test_triangle.cpp index 8e352289..bf73b6e5 100644 --- a/tests/general/unit_test_triangle.cpp +++ b/tests/general/unit_test_triangle.cpp @@ -1,10 +1,10 @@ // // Created by Orange on 1/6/2025. // -#include "omath/Triangle.hpp" +#include // For std::sqrt, std::isinf, std::isnan #include -#include -#include // For std::sqrt, std::isinf, std::isnan +#include +#include "omath/triangle.hpp" using namespace omath; diff --git a/tests/general/unit_test_vector2.cpp b/tests/general/unit_test_vector2.cpp index 918115de..e4133d0c 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 -#include // For std::isinf and std::isnan #include // For FLT_MAX and FLT_MIN +#include // For std::isinf and std::isnan +#include +#include using namespace omath; diff --git a/tests/general/unit_test_vector3.cpp b/tests/general/unit_test_vector3.cpp index 678cdb19..e543bf4e 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 -#include #include // For FLT_MAX, FLT_MIN +#include +#include #include // For std::numeric_limits +#include using namespace omath; diff --git a/tests/general/unit_test_vector4.cpp b/tests/general/unit_test_vector4.cpp index 3f6351a6..00752166 100644 --- a/tests/general/unit_test_vector4.cpp +++ b/tests/general/unit_test_vector4.cpp @@ -6,8 +6,8 @@ // #include -#include #include // For std::numeric_limits +#include using namespace omath; From 326d8baaae5b5918fddeefc253ae939d3f8ab49c Mon Sep 17 00:00:00 2001 From: Orange++ Date: Fri, 21 Mar 2025 05:31:44 +0300 Subject: [PATCH 213/795] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0058d791..b8ce55ea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-![banner](https://i.imgur.com/sjtpKi8.png) +![banner](https://i.imgur.com/SM9ccP6.png) ![GitHub License](https://img.shields.io/github/license/orange-cpp/omath) ![GitHub contributors](https://img.shields.io/github/contributors/orange-cpp/omath) From ef11183c3f1a39a861ab257fc818ab0cc89ae2ac Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 22 Mar 2025 04:26:54 +0300 Subject: [PATCH 214/795] added camera files --- include/omath/engines/unity_engine/camera.hpp | 21 +++++++++ .../omath/engines/unity_engine/constants.hpp | 26 +++++++++++ .../omath/engines/unity_engine/formulas.hpp | 24 ++++++++++ source/engines/CMakeLists.txt | 1 + source/engines/unity_engine/CMakeLists.txt | 1 + source/engines/unity_engine/camera.cpp | 27 +++++++++++ source/engines/unity_engine/formulas.cpp | 45 +++++++++++++++++++ 7 files changed, 145 insertions(+) create mode 100644 include/omath/engines/unity_engine/camera.hpp create mode 100644 include/omath/engines/unity_engine/constants.hpp create mode 100644 include/omath/engines/unity_engine/formulas.hpp create mode 100644 source/engines/unity_engine/CMakeLists.txt create mode 100644 source/engines/unity_engine/camera.cpp create mode 100644 source/engines/unity_engine/formulas.cpp diff --git a/include/omath/engines/unity_engine/camera.hpp b/include/omath/engines/unity_engine/camera.hpp new file mode 100644 index 00000000..e3a7f4e1 --- /dev/null +++ b/include/omath/engines/unity_engine/camera.hpp @@ -0,0 +1,21 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once +#include "omath/engines/unity_engine/constants.hpp" +#include "omath/projection/camera.hpp" + +namespace omath::unity_engine +{ + class Camera final : public projection::Camera + { + public: + Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + const Angle& fov, float near, float far); + void LookAt(const Vector3& target) override; + protected: + [[nodiscard]] Mat4x4 CalcViewMatrix() const override; + [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; + }; +} \ No newline at end of file diff --git a/include/omath/engines/unity_engine/constants.hpp b/include/omath/engines/unity_engine/constants.hpp new file mode 100644 index 00000000..dae1775d --- /dev/null +++ b/include/omath/engines/unity_engine/constants.hpp @@ -0,0 +1,26 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once + +#include +#include +#include +#include + +namespace omath::unity_engine +{ + constexpr Vector3 kAbsUp = {0, 1, 0}; + constexpr Vector3 kAbsRight = {1, 0, 0}; + constexpr Vector3 kAbsForward = {0, 0, 1}; + + 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::source diff --git a/include/omath/engines/unity_engine/formulas.hpp b/include/omath/engines/unity_engine/formulas.hpp new file mode 100644 index 00000000..6352cd18 --- /dev/null +++ b/include/omath/engines/unity_engine/formulas.hpp @@ -0,0 +1,24 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once +#include "omath/engines/unity_engine/constants.hpp" + +namespace omath::unity_engine +{ + [[nodiscard]] + Vector3 ForwardVector(const ViewAngles& angles); + + [[nodiscard]] + Vector3 RightVector(const ViewAngles& angles); + + [[nodiscard]] + Vector3 UpVector(const ViewAngles& angles); + + [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); + + + [[nodiscard]] + Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); +} // namespace omath::source diff --git a/source/engines/CMakeLists.txt b/source/engines/CMakeLists.txt index eb223b0e..aea648a7 100644 --- a/source/engines/CMakeLists.txt +++ b/source/engines/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(source_engine) add_subdirectory(opengl_engine) add_subdirectory(iw_engine) +add_subdirectory(unity_engine) \ No newline at end of file diff --git a/source/engines/unity_engine/CMakeLists.txt b/source/engines/unity_engine/CMakeLists.txt new file mode 100644 index 00000000..5da7def7 --- /dev/null +++ b/source/engines/unity_engine/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(omath PRIVATE formulas.cpp camera.cpp) \ No newline at end of file diff --git a/source/engines/unity_engine/camera.cpp b/source/engines/unity_engine/camera.cpp new file mode 100644 index 00000000..e1b256c4 --- /dev/null +++ b/source/engines/unity_engine/camera.cpp @@ -0,0 +1,27 @@ +// +// Created by Vlad on 3/22/2025. +// +#include + + +namespace omath::unity_engine +{ + Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + const projection::FieldOfView& fov, const float near, const float far) : + projection::Camera(position, viewAngles, viewPort, fov, near, far) + { + } + void Camera::LookAt([[maybe_unused]] const Vector3& target) + { + throw std::runtime_error("Not implemented"); + } + Mat4x4 Camera::CalcViewMatrix() const + { + return unity_engine::CalcViewMatrix(m_viewAngles, m_origin); + } + Mat4x4 Camera::CalcProjectionMatrix() const + { + return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, + m_farPlaneDistance); + } +} // namespace omath::unity_engine diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp new file mode 100644 index 00000000..7a273cf2 --- /dev/null +++ b/source/engines/unity_engine/formulas.cpp @@ -0,0 +1,45 @@ +// +// Created by Vlad on 3/22/2025. +// +#include "omath/engines/unity_engine/formulas.hpp" + + +namespace omath::unity_engine +{ + Vector3 unity_engine::ForwardVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Vector3 RightVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Vector3 UpVector(const ViewAngles& angles) + { + const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + + return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; + } + Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + { + return MatCameraView(ForwardVector(angles), RightVector(angles), + UpVector(angles), cam_origin); + } + Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, + const float far) + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + return { + {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, + {0, 1.f / (fovHalfTan), 0, 0}, + {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, + {0, 0, 1, 0}, + + }; + } +} // namespace omath::unity_engine From 4f037a195232bd3900d120753419a99952215c9b Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 22 Mar 2025 08:12:16 +0300 Subject: [PATCH 215/795] added unity engine tests --- include/omath/projection/camera.hpp | 5 +- source/engines/unity_engine/formulas.cpp | 3 +- tests/engines/unit_test_source_engine.cpp | 20 ++++++ tests/engines/unit_test_unity_engine.cpp | 78 +++++++++++++++++++++++ 4 files changed, 103 insertions(+), 3 deletions(-) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index add81ced..b386cfbb 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -117,8 +117,9 @@ namespace omath::projection [[nodiscard]] std::expected, Error> WorldToScreen(const Vector3& worldPosition) const { const auto& viewProjMatrix = GetViewProjectionMatrix(); + const auto cameraCord = CalcViewMatrix() * MatColumnFromVector(worldPosition);; - auto projected = viewProjMatrix * MatColumnFromVector(worldPosition); + auto projected = CalcProjectionMatrix() * cameraCord; if (projected.At(3, 0) == 0.0f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); @@ -129,7 +130,7 @@ namespace omath::projection return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); const auto screenPositionX = (projected.At(0,0)+1.f) / 2.f * m_viewPort.m_width; - const auto screenPositionY = (-projected.At(1,0)+1) / 2.f * m_viewPort.m_height; + const auto screenPositionY = (projected.At(1,0)+1.f) / 2.f * m_viewPort.m_height; return Vector3{screenPositionX, screenPositionY, projected.At(2,0)}; } diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp index 7a273cf2..2a09d7de 100644 --- a/source/engines/unity_engine/formulas.cpp +++ b/source/engines/unity_engine/formulas.cpp @@ -4,6 +4,7 @@ #include "omath/engines/unity_engine/formulas.hpp" + namespace omath::unity_engine { Vector3 unity_engine::ForwardVector(const ViewAngles& angles) @@ -38,7 +39,7 @@ namespace omath::unity_engine {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, {0, 1.f / (fovHalfTan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, - {0, 0, 1, 0}, + {0, 0, 1.f, 0}, }; } diff --git a/tests/engines/unit_test_source_engine.cpp b/tests/engines/unit_test_source_engine.cpp index 1b9592c7..c129249c 100644 --- a/tests/engines/unit_test_source_engine.cpp +++ b/tests/engines/unit_test_source_engine.cpp @@ -47,6 +47,26 @@ TEST(UnitTestSourceEngine, ProjectTargetMovedFromCamera) } } +TEST(UnitTestSourceEngine, ProjectTargetMovedUp) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + const auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + + auto prev = 1080.f; + for (float distance = 0.0f; distance < 10.f; distance += 1.f) + { + const auto projected = cam.WorldToScreen({100.f, 0, distance}); + EXPECT_TRUE(projected.has_value()); + + if (!projected.has_value()) + continue; + + EXPECT_TRUE(projected->y < prev); + + prev = projected->y; + } +} + TEST(UnitTestSourceEngine, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); diff --git a/tests/engines/unit_test_unity_engine.cpp b/tests/engines/unit_test_unity_engine.cpp index 4f6b72b7..607c6f60 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -1,3 +1,81 @@ // // Created by Orange on 11/27/2024. // +// +// Created by Orange on 11/23/2024. +// +#include +#include +#include +#include + + +TEST(UnitTestUnityEngine, ForwardVector) +{ + const auto forward = omath::unity_engine::ForwardVector({}); + + EXPECT_EQ(forward, omath::unity_engine::kAbsForward); +} + +TEST(UnitTestUnityEngine, RightVector) +{ + const auto right = omath::unity_engine::RightVector({}); + + EXPECT_EQ(right, omath::unity_engine::kAbsRight); +} + +TEST(UnitTestUnityEngine, UpVector) +{ + const auto up = omath::unity_engine::UpVector({}); + EXPECT_EQ(up, omath::unity_engine::kAbsUp); +} + +/*TEST(UnitTestUnityEngine, ProjectTargetMovedFromCamera) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(60.f); + const auto cam = omath::unity_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.WorldToScreen({0, 0, distance}); + + 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(UnitTestUnityEngine, Project) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(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.WorldToScreen({0.f, 2.f, 10.f}); + std::println("Unity projected: {}, {}", proj->x, proj->y); + std::println("{}", cam.GetViewProjectionMatrix().ToString()); +} + +TEST(UnitTestUnityEngine, CameraSetAndGetFov) +{ + constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); + cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); +} + +TEST(UnitTestUnityEngine, CameraSetAndGetOrigin) +{ + auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); + + EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); + cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + + EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); +} \ No newline at end of file From 2b59fb6aa2835660890ae8dfe57a1ed8a8adee7c Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 22 Mar 2025 08:36:06 +0300 Subject: [PATCH 216/795] updated formulas --- include/omath/mat.hpp | 26 +++++++++++++++++++++++ source/engines/iw_engine/formulas.cpp | 11 ++++++---- source/engines/source_engine/formulas.cpp | 4 ++-- source/engines/unity_engine/camera.cpp | 1 + 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 674612a3..09252cd0 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -430,4 +430,30 @@ namespace omath { return MatRotationAxisZ(angles.yaw) * MatRotationAxisY(angles.pitch) * MatRotationAxisX(angles.roll); } + + template + [[nodiscard]] + Mat<4, 4, Type, St> MatPerspectiveLeftHanded(const float fieldOfView, const float aspectRatio, const float near, + const float far) noexcept + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + return {{1.f / (aspectRatio * fovHalfTan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fovHalfTan, 0.f, 0.f}, + {0.f, 0.f, (far + near) / (far - near), -(2.f * near * far) / (far - near)}, + {0.f, 0.f, 1.f, 0.f}}; + } + + template + [[nodiscard]] + Mat<4, 4, Type, St> MatPerspectiveRightHanded(const float fieldOfView, const float aspectRatio, const float near, + const float far) noexcept + { + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + + return {{1.f / (aspectRatio * fovHalfTan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fovHalfTan, 0.f, 0.f}, + {0.f, 0.f, -(far + near) / (far - near), -(2.f * near * far) / (far - near)}, + {0.f, 0.f, -1.f, 0.f}}; + } } // namespace omath diff --git a/source/engines/iw_engine/formulas.cpp b/source/engines/iw_engine/formulas.cpp index 3bc47dce..ec9220dd 100644 --- a/source/engines/iw_engine/formulas.cpp +++ b/source/engines/iw_engine/formulas.cpp @@ -26,22 +26,25 @@ namespace omath::iw_engine return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } + Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { - return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + return MatCameraView(ForwardVector(angles), RightVector(angles), -UpVector(angles), cam_origin); } + Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { - // NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation + // NOTE: Need magic number to fix fov calculation, since IW engine inherit Quake proj matrix calculation constexpr auto kMultiplyFactor = 0.75f; + const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; return { {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, 1.f / fovHalfTan, 0, 0}, + {0, 1.f / (fovHalfTan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, 1, 0}, }; - } + }; } // namespace omath::iw_engine diff --git a/source/engines/source_engine/formulas.cpp b/source/engines/source_engine/formulas.cpp index 10511381..620e9f30 100644 --- a/source/engines/source_engine/formulas.cpp +++ b/source/engines/source_engine/formulas.cpp @@ -28,8 +28,9 @@ namespace omath::source_engine Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { - return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + return MatCameraView(ForwardVector(angles), RightVector(angles), -UpVector(angles), cam_origin); } + Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { @@ -43,7 +44,6 @@ namespace omath::source_engine {0, 1.f / (fovHalfTan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, 1, 0}, - }; } } // namespace omath::source_engine diff --git a/source/engines/unity_engine/camera.cpp b/source/engines/unity_engine/camera.cpp index e1b256c4..5270f5ac 100644 --- a/source/engines/unity_engine/camera.cpp +++ b/source/engines/unity_engine/camera.cpp @@ -2,6 +2,7 @@ // Created by Vlad on 3/22/2025. // #include +#include namespace omath::unity_engine From 481d7b85df675ca9ddd822213dc8c8be58179819 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 22 Mar 2025 17:39:40 +0300 Subject: [PATCH 217/795] improved camera --- include/omath/projection/camera.hpp | 35 ++++++++++++++++------- source/engines/iw_engine/formulas.cpp | 4 +-- source/engines/opengl_engine/formulas.cpp | 1 - source/engines/source_engine/formulas.cpp | 2 +- source/engines/unity_engine/formulas.cpp | 2 +- tests/engines/unit_test_iw_engine.cpp | 18 ++++++------ 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index b386cfbb..e0dd8ca9 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -36,8 +36,8 @@ namespace omath::projection m_viewPort(viewPort), m_fieldOfView(fov), m_farPlaneDistance(far), m_nearPlaneDistance(near), m_viewAngles(viewAngles), m_origin(position) { - } + protected: virtual void LookAt(const Vector3& target) = 0; @@ -49,8 +49,8 @@ namespace omath::projection { return CalcProjectionMatrix() * CalcViewMatrix(); } - public: + public: [[nodiscard]] const Mat4x4Type& GetViewProjectionMatrix() const { if (!m_viewProjectionMatrix.has_value()) @@ -116,10 +116,18 @@ namespace omath::projection [[nodiscard]] std::expected, Error> WorldToScreen(const Vector3& worldPosition) const { - const auto& viewProjMatrix = GetViewProjectionMatrix(); - const auto cameraCord = CalcViewMatrix() * MatColumnFromVector(worldPosition);; + auto normalizedCords = WorldToViewPort(worldPosition); + + if (!normalizedCords.has_value()) + return normalizedCords; + - auto projected = CalcProjectionMatrix() * cameraCord; + return NdcToScreenPosition(*normalizedCords); + } + [[nodiscard]] std::expected, Error> WorldToViewPort(const Vector3& worldPosition) const + { + auto projected = GetViewProjectionMatrix() * + MatColumnFromVector(worldPosition); if (projected.At(3, 0) == 0.0f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); @@ -129,10 +137,7 @@ namespace omath::projection if (IsNdcOutOfBounds(projected)) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); - const auto screenPositionX = (projected.At(0,0)+1.f) / 2.f * m_viewPort.m_width; - const auto screenPositionY = (projected.At(1,0)+1.f) / 2.f * m_viewPort.m_height; - - return Vector3{screenPositionX, screenPositionY, projected.At(2,0)}; + return Vector3{projected.At(0, 0), projected.At(1, 0), projected.At(2, 0)}; } protected: @@ -153,7 +158,17 @@ namespace omath::projection [[nodiscard]] constexpr static bool IsNdcOutOfBounds(const Type& ndc) { - return std::ranges::any_of( ndc.RawArray(), [](const auto& val) { return val < -1 || val > 1; }); + return std::ranges::any_of(ndc.RawArray(), [](const auto& val) { return val < -1 || val > 1; }); + } + + [[nodiscard]] Vector3 NdcToScreenPosition(const Vector3& ndc) const + { + return + { + (ndc.x + 1.f) / 2.f * m_viewPort.m_width, + (1.f - ndc.y) / 2.f * m_viewPort.m_height, + ndc.z + }; } }; } // namespace omath::projection diff --git a/source/engines/iw_engine/formulas.cpp b/source/engines/iw_engine/formulas.cpp index ec9220dd..f4fe076e 100644 --- a/source/engines/iw_engine/formulas.cpp +++ b/source/engines/iw_engine/formulas.cpp @@ -26,10 +26,10 @@ namespace omath::iw_engine return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } - + Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { - return MatCameraView(ForwardVector(angles), RightVector(angles), -UpVector(angles), cam_origin); + return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); } Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, diff --git a/source/engines/opengl_engine/formulas.cpp b/source/engines/opengl_engine/formulas.cpp index be1a6380..8162bdb3 100644 --- a/source/engines/opengl_engine/formulas.cpp +++ b/source/engines/opengl_engine/formulas.cpp @@ -40,7 +40,6 @@ namespace omath::opengl_engine {0, 1.f / (fovHalfTan), 0, 0}, {0, 0, -(far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, -1, 0}, - }; } } // namespace omath::opengl_engine diff --git a/source/engines/source_engine/formulas.cpp b/source/engines/source_engine/formulas.cpp index 620e9f30..f037cadd 100644 --- a/source/engines/source_engine/formulas.cpp +++ b/source/engines/source_engine/formulas.cpp @@ -28,7 +28,7 @@ namespace omath::source_engine Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { - return MatCameraView(ForwardVector(angles), RightVector(angles), -UpVector(angles), cam_origin); + return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); } Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp index 2a09d7de..ef851008 100644 --- a/source/engines/unity_engine/formulas.cpp +++ b/source/engines/unity_engine/formulas.cpp @@ -39,7 +39,7 @@ namespace omath::unity_engine {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, {0, 1.f / (fovHalfTan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, - {0, 0, 1.f, 0}, + {0, 0, -1.f, 0}, }; } diff --git a/tests/engines/unit_test_iw_engine.cpp b/tests/engines/unit_test_iw_engine.cpp index cd42ee44..286d599d 100644 --- a/tests/engines/unit_test_iw_engine.cpp +++ b/tests/engines/unit_test_iw_engine.cpp @@ -9,28 +9,28 @@ TEST(UnitTestEwEngine, ForwardVector) { - const auto forward = omath::source_engine::ForwardVector({}); + const auto forward = omath::iw_engine::ForwardVector({}); - EXPECT_EQ(forward, omath::source_engine::kAbsForward); + EXPECT_EQ(forward, omath::iw_engine::kAbsForward); } TEST(UnitTestEwEngine, RightVector) { - const auto right = omath::source_engine::RightVector({}); + const auto right = omath::iw_engine::RightVector({}); - EXPECT_EQ(right, omath::source_engine::kAbsRight); + EXPECT_EQ(right, omath::iw_engine::kAbsRight); } TEST(UnitTestEwEngine, UpVector) { - const auto up = omath::source_engine::UpVector({}); - EXPECT_EQ(up, omath::source_engine::kAbsUp); + const auto up = omath::iw_engine::UpVector({}); + EXPECT_EQ(up, omath::iw_engine::kAbsUp); } TEST(UnitTestEwEngine, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); - const auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + const auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); for (float distance = 0.02f; distance < 1000.f; distance += 0.01f) @@ -50,7 +50,7 @@ TEST(UnitTestEwEngine, ProjectTargetMovedFromCamera) TEST(UnitTestEwEngine, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); - auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); + auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); @@ -60,7 +60,7 @@ TEST(UnitTestEwEngine, CameraSetAndGetFov) TEST(UnitTestEwEngine, CameraSetAndGetOrigin) { - auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); + auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); From 8977557a61b3e1e272b622d2ad66b5c2959327ee Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 22 Mar 2025 17:41:01 +0300 Subject: [PATCH 218/795] fix --- tests/engines/unit_test_unity_engine.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/engines/unit_test_unity_engine.cpp b/tests/engines/unit_test_unity_engine.cpp index 607c6f60..95d44954 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -55,8 +55,6 @@ TEST(UnitTestUnityEngine, Project) const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 1000.f); const auto proj = cam.WorldToScreen({0.f, 2.f, 10.f}); - std::println("Unity projected: {}, {}", proj->x, proj->y); - std::println("{}", cam.GetViewProjectionMatrix().ToString()); } TEST(UnitTestUnityEngine, CameraSetAndGetFov) From 74f2241bcfa71c79f8803efb98f2919de1502c3b Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 23 Mar 2025 00:52:12 +0300 Subject: [PATCH 219/795] uncommented test --- include/omath/projection/camera.hpp | 3 ++- tests/engines/unit_test_unity_engine.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index e0dd8ca9..a2816309 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -119,11 +119,12 @@ namespace omath::projection auto normalizedCords = WorldToViewPort(worldPosition); if (!normalizedCords.has_value()) - return normalizedCords; + return std::unexpected{normalizedCords.error()}; return NdcToScreenPosition(*normalizedCords); } + [[nodiscard]] std::expected, Error> WorldToViewPort(const Vector3& worldPosition) const { auto projected = GetViewProjectionMatrix() * diff --git a/tests/engines/unit_test_unity_engine.cpp b/tests/engines/unit_test_unity_engine.cpp index 95d44954..ed953a3c 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -30,7 +30,7 @@ TEST(UnitTestUnityEngine, UpVector) EXPECT_EQ(up, omath::unity_engine::kAbsUp); } -/*TEST(UnitTestUnityEngine, ProjectTargetMovedFromCamera) +TEST(UnitTestUnityEngine, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(60.f); const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.01f, 1000.f); @@ -48,7 +48,7 @@ TEST(UnitTestUnityEngine, UpVector) EXPECT_NEAR(projected->x, 640, 0.00001f); EXPECT_NEAR(projected->y, 360, 0.00001f); } -}*/ +} TEST(UnitTestUnityEngine, Project) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(60.f); From 5a1014a23996b3960b9af3ccde483bae5a48d183 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 23 Mar 2025 00:57:58 +0300 Subject: [PATCH 220/795] fix --- source/engines/unity_engine/formulas.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp index ef851008..cccc16c3 100644 --- a/source/engines/unity_engine/formulas.cpp +++ b/source/engines/unity_engine/formulas.cpp @@ -40,7 +40,6 @@ namespace omath::unity_engine {0, 1.f / (fovHalfTan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, -1.f, 0}, - }; } } // namespace omath::unity_engine From d9219cdddb3549e7b50fd96f449f1bd973c97d4c Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 23 Mar 2025 01:02:11 +0300 Subject: [PATCH 221/795] naming fixed --- include/omath/projection/camera.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index a2816309..7c3473bd 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -5,8 +5,8 @@ #pragma once #include -#include -#include +#include +#include #include #include #include "omath/projection/error_codes.hpp" From ffba4e256ac3e01a4c61f1a22b7ef3eb19a16889 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 23 Mar 2025 01:06:36 +0300 Subject: [PATCH 222/795] name fix --- tests/engines/unit_test_iw_engine.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/engines/unit_test_iw_engine.cpp b/tests/engines/unit_test_iw_engine.cpp index 286d599d..679af031 100644 --- a/tests/engines/unit_test_iw_engine.cpp +++ b/tests/engines/unit_test_iw_engine.cpp @@ -7,27 +7,27 @@ #include -TEST(UnitTestEwEngine, ForwardVector) +TEST(UnitTestIwEngine, ForwardVector) { const auto forward = omath::iw_engine::ForwardVector({}); EXPECT_EQ(forward, omath::iw_engine::kAbsForward); } -TEST(UnitTestEwEngine, RightVector) +TEST(UnitTestIwEngine, RightVector) { const auto right = omath::iw_engine::RightVector({}); EXPECT_EQ(right, omath::iw_engine::kAbsRight); } -TEST(UnitTestEwEngine, UpVector) +TEST(UnitTestIwEngine, UpVector) { const auto up = omath::iw_engine::UpVector({}); EXPECT_EQ(up, omath::iw_engine::kAbsUp); } -TEST(UnitTestEwEngine, ProjectTargetMovedFromCamera) +TEST(UnitTestIwEngine, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); const auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); @@ -47,7 +47,7 @@ TEST(UnitTestEwEngine, ProjectTargetMovedFromCamera) } } -TEST(UnitTestEwEngine, CameraSetAndGetFov) +TEST(UnitTestIwEngine, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); @@ -58,7 +58,7 @@ TEST(UnitTestEwEngine, CameraSetAndGetFov) EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); } -TEST(UnitTestEwEngine, CameraSetAndGetOrigin) +TEST(UnitTestIwEngine, CameraSetAndGetOrigin) { auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); From a48a257648daf1f57ef38bc559038fd4c4672763 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 23 Mar 2025 01:12:46 +0300 Subject: [PATCH 223/795] removed comment --- tests/engines/unit_test_unity_engine.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/engines/unit_test_unity_engine.cpp b/tests/engines/unit_test_unity_engine.cpp index ed953a3c..48b8b464 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -1,9 +1,6 @@ // // Created by Orange on 11/27/2024. // -// -// Created by Orange on 11/23/2024. -// #include #include #include From 5d5bd215b28d93e4583cec0a781ea19895983562 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 23 Mar 2025 01:17:05 +0300 Subject: [PATCH 224/795] fix --- include/omath/collision/line_tracer.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/omath/collision/line_tracer.hpp b/include/omath/collision/line_tracer.hpp index e8c0b15b..88341361 100644 --- a/include/omath/collision/line_tracer.hpp +++ b/include/omath/collision/line_tracer.hpp @@ -3,8 +3,8 @@ // #pragma once -#include "omath/Vector3.hpp" -#include "omath/Triangle.hpp" +#include "omath/triangle.hpp" +#include "omath/vector3.hpp" namespace omath::collision { From dc43411bd215be029740d4f16e16b3b22637dd99 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 24 Mar 2025 06:30:09 +0300 Subject: [PATCH 225/795] fixed unity view matrix building --- source/engines/unity_engine/formulas.cpp | 2 +- tests/engines/unit_test_unity_engine.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp index cccc16c3..0019c4a1 100644 --- a/source/engines/unity_engine/formulas.cpp +++ b/source/engines/unity_engine/formulas.cpp @@ -27,7 +27,7 @@ namespace omath::unity_engine } Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { - return MatCameraView(ForwardVector(angles), RightVector(angles), + return MatCameraView(ForwardVector(angles), -RightVector(angles), UpVector(angles), cam_origin); } Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, diff --git a/tests/engines/unit_test_unity_engine.cpp b/tests/engines/unit_test_unity_engine.cpp index 48b8b464..2e0daaa3 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -51,7 +51,8 @@ TEST(UnitTestUnityEngine, Project) constexpr auto fov = omath::projection::FieldOfView::FromDegrees(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.WorldToScreen({0.f, 2.f, 10.f}); + const auto proj = cam.WorldToScreen({5.f, 3, 10.f}); + std::println("{} {}", proj->x, proj->y); } TEST(UnitTestUnityEngine, CameraSetAndGetFov) From a797dd134a2bd3d0dd74a73df854bbd3be119bec Mon Sep 17 00:00:00 2001 From: Orange++ Date: Mon, 24 Mar 2025 06:46:02 +0300 Subject: [PATCH 226/795] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index b8ce55ea..403ef0a1 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,14 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at - **Collision Detection**: Production ready code to handle collision detection by using simple interfaces. - **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution - **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types! + +|ENGINE |SUPPORT| +|--------|-------| +|Source |✅YES | +|Unity |✅YES | +|IWEngine|✅YES | +|Unreal |❌NO | + ## ⏬ Getting Started ### Prerequisites - C++ Compiler From b6b01845230cf8c97bde24ff8718d56299ea4ae8 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 24 Mar 2025 06:48:43 +0300 Subject: [PATCH 227/795] fixed for clang --- source/color.cpp | 2 +- source/engines/unity_engine/formulas.cpp | 2 +- source/matrix.cpp | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/color.cpp b/source/color.cpp index 3e8b584b..981ae737 100644 --- a/source/color.cpp +++ b/source/color.cpp @@ -2,7 +2,7 @@ // Created by vlad on 2/4/24. // -#include "omath/Color.hpp" +#include "omath/color.hpp" #include #include diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp index 0019c4a1..d3a872fd 100644 --- a/source/engines/unity_engine/formulas.cpp +++ b/source/engines/unity_engine/formulas.cpp @@ -7,7 +7,7 @@ namespace omath::unity_engine { - Vector3 unity_engine::ForwardVector(const ViewAngles& angles) + Vector3 ForwardVector(const ViewAngles& angles) { const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); diff --git a/source/matrix.cpp b/source/matrix.cpp index 98bea2c3..7782d566 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -1,6 +1,6 @@ -#include "omath/Matrix.hpp" -#include "omath/Angles.hpp" -#include "omath/Vector3.hpp" +#include "omath/matrix.hpp" +#include "omath/angles.hpp" +#include "omath/vector3.hpp" #include From 5773cc7798b39ac891d50fdf9a64bd420d6d0547 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 24 Mar 2025 21:48:51 +0300 Subject: [PATCH 228/795] improved imgui handling --- CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c50a9453..95b93586 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,16 @@ add_library(omath::omath ALIAS omath) if (OMATH_IMGUI_INTEGRATION) target_compile_definitions(omath PUBLIC OMATH_IMGUI_INTEGRATION) + + if(TARGET imgui) + target_link_libraries(omath PUBLIC imgui) + install(TARGETS imgui + EXPORT omathTargets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + endif() + endif() if (OMATH_USE_AVX2) From 79f76a07555b96363c0a4f67c9bd5af0cc61b253 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 29 Mar 2025 01:53:04 +0300 Subject: [PATCH 229/795] added new option --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95b93586..469fd657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" OFF) option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF) +option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/Matrix.cpp) @@ -46,6 +47,12 @@ set_target_properties(omath PROPERTIES CXX_STANDARD 23 CXX_STANDARD_REQUIRED ON) +if (OMATH_STATIC_MSVC_RUNTIME_LIBRARY) + set_target_properties(omath PROPERTIES + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" + ) +endif() + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") target_compile_options(omath PRIVATE -mavx2 -mfma) endif() From a8ce5cbaa023de2fe0501c02aaca70943d21caac Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 29 Mar 2025 01:56:09 +0300 Subject: [PATCH 230/795] added vcpkg imgui package auto link --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 469fd657..c495eef1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(omath::omath ALIAS omath) if (OMATH_IMGUI_INTEGRATION) target_compile_definitions(omath PUBLIC OMATH_IMGUI_INTEGRATION) + # IMGUI is being linked as submodule if(TARGET imgui) target_link_libraries(omath PUBLIC imgui) install(TARGETS imgui @@ -31,6 +32,10 @@ if (OMATH_IMGUI_INTEGRATION) ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) + else() + # Assume that IMGUI linked via VCPKG. + find_package(imgui CONFIG REQUIRED) + target_link_libraries(omath PUBLIC imgui::imgui) endif() endif() From 3e75d32f5925093f2b5521442754f014f836d710 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 29 Mar 2025 04:00:35 +0300 Subject: [PATCH 231/795] fixed style --- include/omath/color.hpp | 218 ++++++++++++++++-------------- tests/general/unit_test_color.cpp | 8 +- 2 files changed, 123 insertions(+), 103 deletions(-) diff --git a/include/omath/color.hpp b/include/omath/color.hpp index 3734f371..bafefc39 100644 --- a/include/omath/color.hpp +++ b/include/omath/color.hpp @@ -12,111 +12,131 @@ namespace omath { struct HSV { - float m_hue{}; - float m_saturation{}; - float m_value{}; + float hue{}; + float saturation{}; + float value{}; }; class Color final : public Vector4 { - public: - constexpr Color(const float r, const float g, const float b, const float a) : Vector4(r,g,b,a) + public: + constexpr Color(const float r, const float g, const float b, const float a) : Vector4(r, g, b, a) + { + Clamp(0.f, 1.f); + } + + constexpr explicit Color() = default; + [[nodiscard]] + constexpr static Color FromRGBA(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) + { + return Color{Vector4(r, g, b, a) / 255.f}; + } + + [[nodiscard]] + constexpr static Color FromHSV(float hue, const float saturation, const float value) + { + float r{}, g{}, b{}; + + hue = std::clamp(hue, 0.f, 1.f); + + const int i = static_cast(hue * 6.f); + const float f = hue * 6 - i; + const float p = value * (1 - saturation); + const float q = value * (1 - f * saturation); + const float t = value * (1 - (1 - f) * saturation); + + switch (i % 6) { - Clamp(0.f, 1.f); + case 0: + r = value, g = t, b = p; + break; + case 1: + r = q, g = value, b = p; + break; + case 2: + r = p, g = value, b = t; + break; + case 3: + r = p, g = q, b = value; + break; + case 4: + r = t, g = p, b = value; + break; + case 5: + r = value, g = p, b = q; + break; + + default: + return {0.f, 0.f, 0.f, 0.f}; } - constexpr explicit Color() : Vector4() - { - - } - [[nodiscard]] - constexpr static Color FromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) - { - return Color{Vector4(r, g, b, a) / 255.f}; - } - - [[nodiscard]] - constexpr static Color FromHSV(float hue, float saturation, float value) - { - float r{}, g{}, b{}; - - hue = std::clamp(hue, 0.f, 1.f); - - const int i = static_cast(hue * 6.f); - const float f = hue * 6 - i; - const float p = value * (1 - saturation); - const float q = value * (1 - f * saturation); - const float t = value * (1 - (1 - f) * saturation); - - switch (i % 6) - { - case 0: r = value, g = t, b = p; break; - case 1: r = q, g = value, b = p; break; - case 2: r = p, g = value, b = t; break; - case 3: r = p, g = q, b = value; break; - case 4: r = t, g = p, b = value; break; - case 5: r = value, g = p, b = q; break; - - default: return {0.f, 0.f, 0.f, 0.f}; - } - - return {r, g, b, 1.f}; - } - - [[nodiscard]] - constexpr HSV ToHSV() const - { - HSV hsvData; - - const float& red = x; - const float& green = y; - const float& blue = z; - - const float max = std::max({red, green, blue}); - const float min = std::min({red, green, blue}); - const float delta = max - min; - - - if (delta == 0.f) - hsvData.m_hue = 0.f; - - else if (max == red) - hsvData.m_hue = 60.f * (std::fmodf(((green - blue) / delta), 6.f)); - else if (max == green) - hsvData.m_hue = 60.f * (((blue - red) / delta) + 2.f); - else if (max == blue) - hsvData.m_hue = 60.f * (((red - green) / delta) + 4.f); - - if (hsvData.m_hue < 0.f) - hsvData.m_hue += 360.f; - - hsvData.m_hue /= 360.f; - hsvData.m_saturation = max == 0.f ? 0.f : delta / max; - hsvData.m_value = max; - - return hsvData; - } - - constexpr explicit Color(const Vector4& vec) : Vector4(vec) - { - Clamp(0.f, 1.f); - } - - [[nodiscard]] - constexpr Color Blend(const Color& other, float ratio) const - { - return Color( (*this * (1.f - ratio)) + (other * ratio) ); - } - - [[nodiscard]] static constexpr Color Red() {return {1.f, 0.f, 0.f, 1.f};} - [[nodiscard]] static constexpr Color Green() {return {0.f, 1.f, 0.f, 1.f};} - [[nodiscard]] static constexpr Color Blue() {return {0.f, 0.f, 1.f, 1.f};} + return {r, g, b, 1.f}; + } + + [[nodiscard]] + constexpr static Color FromHSV(const HSV& hsv) + { + return FromHSV(hsv.hue, hsv.saturation, hsv.value); + } + + [[nodiscard]] + constexpr HSV ToHSV() const + { + HSV hsvData; + + const float& red = x; + const float& green = y; + const float& blue = z; + + const float max = std::max({red, green, blue}); + const float min = std::min({red, green, blue}); + const float delta = max - min; + + + if (delta == 0.f) + hsvData.hue = 0.f; + + else if (max == red) + hsvData.hue = 60.f * (std::fmodf(((green - blue) / delta), 6.f)); + else if (max == green) + hsvData.hue = 60.f * (((blue - red) / delta) + 2.f); + else if (max == blue) + hsvData.hue = 60.f * (((red - green) / delta) + 4.f); + + if (hsvData.hue < 0.f) + hsvData.hue += 360.f; + + hsvData.hue /= 360.f; + hsvData.saturation = max == 0.f ? 0.f : delta / max; + hsvData.value = max; + + return hsvData; + } + + constexpr explicit Color(const Vector4& vec) : Vector4(vec) + { + Clamp(0.f, 1.f); + } + + [[nodiscard]] + constexpr Color Blend(const Color& other, float ratio) const + { + ratio = std::clamp(ratio, 0.f, 1.f); + return Color(*this * (1.f - ratio) + other * ratio); + } + + [[nodiscard]] static constexpr Color Red() + { + return {1.f, 0.f, 0.f, 1.f}; + } + [[nodiscard]] static constexpr Color Green() + { + return {0.f, 1.f, 0.f, 1.f}; + } + [[nodiscard]] static constexpr Color Blue() + { + return {0.f, 0.f, 1.f, 1.f}; + } }; - - [[nodiscard]] - constexpr Color Blend(const Color& first, const Color& second, float ratio) - { - return Color{first * (1.f - std::clamp(ratio, 0.f, 1.f)) + second * ratio}; - } -} \ No newline at end of file +} // namespace omath diff --git a/tests/general/unit_test_color.cpp b/tests/general/unit_test_color.cpp index 01208622..77aeaf30 100644 --- a/tests/general/unit_test_color.cpp +++ b/tests/general/unit_test_color.cpp @@ -63,9 +63,9 @@ TEST_F(UnitTestColor, FromHSV) TEST_F(UnitTestColor, ToHSV) { HSV hsv = color1.ToHSV(); // Red color - EXPECT_FLOAT_EQ(hsv.m_hue, 0.0f); - EXPECT_FLOAT_EQ(hsv.m_saturation, 1.0f); - EXPECT_FLOAT_EQ(hsv.m_value, 1.0f); + EXPECT_FLOAT_EQ(hsv.hue, 0.0f); + EXPECT_FLOAT_EQ(hsv.saturation, 1.0f); + EXPECT_FLOAT_EQ(hsv.value, 1.0f); } // Test color blending @@ -106,7 +106,7 @@ TEST_F(UnitTestColor, BlendVector3) { constexpr Color v1(1.0f, 0.0f, 0.0f, 1.f); // Red constexpr Color v2(0.0f, 1.0f, 0.0f, 1.f); // Green - constexpr Color blended = Blend(v1, v2, 0.5f); + constexpr Color blended = v1.Blend(v2, 0.5f); EXPECT_FLOAT_EQ(blended.x, 0.5f); EXPECT_FLOAT_EQ(blended.y, 0.5f); EXPECT_FLOAT_EQ(blended.z, 0.0f); From d72ad663cd9f8c96d4440a8dca71fc7592a4bade Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 29 Mar 2025 05:41:55 +0300 Subject: [PATCH 232/795] added new methods --- include/omath/color.hpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/include/omath/color.hpp b/include/omath/color.hpp index bafefc39..c82c19de 100644 --- a/include/omath/color.hpp +++ b/include/omath/color.hpp @@ -118,7 +118,29 @@ namespace omath { Clamp(0.f, 1.f); } + consteval void SetHue(const float hue) + { + auto hsv = ToHSV(); + hsv.hue = hue; + + *this = FromHSV(hsv); + } + + consteval void SetSaturation(const float saturation) + { + auto hsv = ToHSV(); + hsv.saturation = saturation; + *this = FromHSV(hsv); + } + + consteval void SetValue(const float value) + { + auto hsv = ToHSV(); + hsv.value = value; + + *this = FromHSV(hsv); + } [[nodiscard]] constexpr Color Blend(const Color& other, float ratio) const { From ea6c1cc92953f70c3f3497affc2c8a0fd2bd038f Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 29 Mar 2025 18:20:17 +0300 Subject: [PATCH 233/795] fix --- include/omath/color.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/omath/color.hpp b/include/omath/color.hpp index c82c19de..c6a7263f 100644 --- a/include/omath/color.hpp +++ b/include/omath/color.hpp @@ -118,7 +118,7 @@ namespace omath { Clamp(0.f, 1.f); } - consteval void SetHue(const float hue) + constexpr void SetHue(const float hue) { auto hsv = ToHSV(); hsv.hue = hue; @@ -126,7 +126,7 @@ namespace omath *this = FromHSV(hsv); } - consteval void SetSaturation(const float saturation) + constexpr void SetSaturation(const float saturation) { auto hsv = ToHSV(); hsv.saturation = saturation; @@ -134,7 +134,7 @@ namespace omath *this = FromHSV(hsv); } - consteval void SetValue(const float value) + constexpr void SetValue(const float value) { auto hsv = ToHSV(); hsv.value = value; From 0f2a8583063529597d5dc096c54ea3d3a01b7da7 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 29 Mar 2025 21:57:35 +0300 Subject: [PATCH 234/795] fixed in some cases confilcting with win api --- include/omath/color.hpp | 9 +++++++++ include/omath/mat.hpp | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/include/omath/color.hpp b/include/omath/color.hpp index c6a7263f..48a02dfd 100644 --- a/include/omath/color.hpp +++ b/include/omath/color.hpp @@ -8,6 +8,15 @@ #include "omath/vector3.hpp" #include "omath/vector4.hpp" +#ifdef max +#undef max +#endif + + +#ifdef min +#undef min +#endif + namespace omath { struct HSV diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 09252cd0..2116cf7d 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -10,6 +10,16 @@ #include #include "omath/vector3.hpp" + +#ifdef near +#undef near +#endif + + +#ifdef far +#undef fa +#endif + namespace omath { struct MatSize From 138c99639371f40a5045b76fe0a8f7a423e6d7af Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 29 Mar 2025 22:03:30 +0300 Subject: [PATCH 235/795] oops --- include/omath/mat.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 2116cf7d..6b53821a 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -17,7 +17,7 @@ #ifdef far -#undef fa +#undef far #endif namespace omath From 7b712ed96039469490f40d86e7de2b4b9446aeb1 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 5 Apr 2025 13:00:00 +0300 Subject: [PATCH 236/795] fixed for clang --- .gitignore | 1 + CMakeLists.txt | 7 ++-- CMakePresets.json | 32 +++++++++++++++++++ include/omath/vector2.hpp | 4 +-- include/omath/vector3.hpp | 4 +-- .../proj_pred_engine_avx2.cpp | 6 +++- 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 98568e70..e0999e5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /cmake-build/ /.idea /out +*.DS_Store \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c495eef1..a6935022 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,11 +14,12 @@ option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" O option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) - add_library(omath SHARED source/Matrix.cpp) + add_library(omath SHARED source/matrix.cpp) else() - add_library(omath STATIC source/Matrix.cpp) + add_library(omath STATIC source/matrix.cpp + source/matrix.cpp) endif() - +message(STATUS "Building on ${CMAKE_HOST_SYSTEM_NAME}") add_library(omath::omath ALIAS omath) if (OMATH_IMGUI_INTEGRATION) diff --git a/CMakePresets.json b/CMakePresets.json index d67436b9..1f5ceda8 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -64,6 +64,38 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } + }, + { + "name": "darwin-base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/cmake-build/build/${presetName}", + "installDir": "${sourceDir}/cmake-build/install/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "darwin-debug", + "displayName": "Darwin Debug", + "inherits": "darwin-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "darwin-release", + "displayName": "Darwin Release", + "inherits": "darwin-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } } ] } \ No newline at end of file diff --git a/include/omath/vector2.hpp b/include/omath/vector2.hpp index f2ae0c36..5753f60f 100644 --- a/include/omath/vector2.hpp +++ b/include/omath/vector2.hpp @@ -124,9 +124,9 @@ namespace omath } #ifndef _MSC_VER - [[nodiscard]] constexpr Type& Length() const + [[nodiscard]] constexpr Type Length() const { - return std::hypot(x, y); + return std::hypot(this->x, this->y); } [[nodiscard]] constexpr Vector2 Normalized() const diff --git a/include/omath/vector3.hpp b/include/omath/vector3.hpp index 6b769071..683e4f41 100644 --- a/include/omath/vector3.hpp +++ b/include/omath/vector3.hpp @@ -121,12 +121,12 @@ namespace omath #ifndef _MSC_VER [[nodiscard]] constexpr Type Length() const { - return std::hypot(x, y, z); + return std::hypot(this->x, this->y, z); } [[nodiscard]] constexpr Type Length2D() const { - return Vector2::Length(); + return Vector2::Length(); } [[nodiscard]] Type DistTo(const Vector3& vOther) const { diff --git a/source/projectile_prediction/proj_pred_engine_avx2.cpp b/source/projectile_prediction/proj_pred_engine_avx2.cpp index 771c8e0f..9b5f6b63 100644 --- a/source/projectile_prediction/proj_pred_engine_avx2.cpp +++ b/source/projectile_prediction/proj_pred_engine_avx2.cpp @@ -4,13 +4,17 @@ #include "omath/projectile_prediction/proj_pred_engine_avx2.hpp" #include "source_location" +#if defined(OMATH_USE_AVX2) && defined(__i386__) && defined(__x86_64__) +#include +#endif + namespace omath::projectile_prediction { std::optional> ProjPredEngineAVX2::MaybeCalculateAimPoint([[maybe_unused]] const Projectile& projectile, [[maybe_unused]] const Target& target) const { -#ifdef OMATH_USE_AVX2 +#if defined(OMATH_USE_AVX2) && defined(__i386__) && defined(__x86_64__) const float bulletGravity = m_gravityConstant * projectile.m_gravityScale; const float v0 = projectile.m_launchSpeed; const float v0Sqr = v0 * v0; From 55085604fdb573e4ece63c639867ed3cbad061c9 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 5 Apr 2025 13:20:18 +0300 Subject: [PATCH 237/795] added include --- CMakeLists.txt | 2 +- source/projectile_prediction/proj_pred_engine_avx2.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a6935022..d5ad87f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(omath VERSION 1.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) -option(OMATH_BUILD_TESTS "Build unit tests" OFF) +option(OMATH_BUILD_TESTS "Build unit tests" ON) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) diff --git a/source/projectile_prediction/proj_pred_engine_avx2.cpp b/source/projectile_prediction/proj_pred_engine_avx2.cpp index 9b5f6b63..8d36d849 100644 --- a/source/projectile_prediction/proj_pred_engine_avx2.cpp +++ b/source/projectile_prediction/proj_pred_engine_avx2.cpp @@ -4,8 +4,10 @@ #include "omath/projectile_prediction/proj_pred_engine_avx2.hpp" #include "source_location" + #if defined(OMATH_USE_AVX2) && defined(__i386__) && defined(__x86_64__) #include +#include #endif namespace omath::projectile_prediction From 8e861b8a85519e036f1bc29c38dbe7caef72b7b9 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 5 Apr 2025 13:28:28 +0300 Subject: [PATCH 238/795] updated read me --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 403ef0a1..dd70a7da 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,21 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at - **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution - **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types! -|ENGINE |SUPPORT| -|--------|-------| -|Source |✅YES | -|Unity |✅YES | -|IWEngine|✅YES | -|Unreal |❌NO | +## Supported Render Pipelines +| ENGINE | SUPPORT | +|----------|---------| +| Source | ✅YES | +| Unity | ✅YES | +| IWEngine | ✅YES | +| Unreal | ❌NO | + +## Supported Operating Systems + +| OS | SUPPORT | +|----------------|---------| +| Windows 10/11 | ✅YES | +| Linux | ✅YES | +| Darwin (MacOS) | ✅YES | ## ⏬ Getting Started ### Prerequisites From fc1e0c62b89239f839e3b5e7fe440bebe1be7e44 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 5 Apr 2025 19:33:23 +0300 Subject: [PATCH 239/795] disabled tests --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5ad87f7..a6935022 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(omath VERSION 1.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) -option(OMATH_BUILD_TESTS "Build unit tests" ON) +option(OMATH_BUILD_TESTS "Build unit tests" OFF) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) From b58956efe3852bf7e45bdbedd8f3d72dd4bbde1a Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 11 Apr 2025 22:59:56 +0300 Subject: [PATCH 240/795] added missing header --- include/omath/angle.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/omath/angle.hpp b/include/omath/angle.hpp index 0f88ab55..07d55106 100644 --- a/include/omath/angle.hpp +++ b/include/omath/angle.hpp @@ -5,6 +5,8 @@ #pragma once #include "omath/angles.hpp" #include +#include + namespace omath { From 3631c5d6986fd807effd934009c23a57129fa206 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 11 Apr 2025 23:10:02 +0300 Subject: [PATCH 241/795] replaced with STL relization --- include/omath/mat.hpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 6b53821a..d8cf4039 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -9,6 +9,7 @@ #include #include #include "omath/vector3.hpp" +#include #ifdef near @@ -141,12 +142,7 @@ namespace omath [[nodiscard]] constexpr Type Sum() const noexcept { - Type sum = 0; - for (size_t i = 0; i < Rows; ++i) - for (size_t j = 0; j < Columns; ++j) - sum += At(i, j); - - return sum; + return std::accumulate(m_data.begin(), m_data.end(), Type(0)); } constexpr void Clear() noexcept From 466d8f7becc9ad6e903c76dcec9fce7bf995eef0 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 11 Apr 2025 23:20:16 +0300 Subject: [PATCH 242/795] improvement --- include/omath/mat.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index d8cf4039..78737d20 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -198,9 +198,7 @@ namespace omath constexpr Mat& operator/=(const Type& f) noexcept { - for (size_t i = 0; i < Rows; ++i) - for (size_t j = 0; j < Columns; ++j) - At(i, j) /= f; + std::ranges::for_each(m_data,[&f](auto& val) {val /= f;}); return *this; } From 1b47f45af940098d62ecb97f8d4240e0cdaa9bc4 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 11 Apr 2025 23:30:07 +0300 Subject: [PATCH 243/795] improved naming --- include/omath/mat.hpp | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 78737d20..55f61ee6 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -175,9 +175,7 @@ namespace omath constexpr Mat& operator*=(const Type& f) noexcept { - for (size_t i = 0; i < Rows; ++i) - for (size_t j = 0; j < Columns; ++j) - At(i, j) *= f; + std::ranges::for_each(m_data,[&f](auto& val) {val *= f;}); return *this; } @@ -189,45 +187,39 @@ namespace omath } [[nodiscard]] - constexpr Mat operator*(const Type& f) const noexcept + constexpr Mat operator*(const Type& value) const noexcept { Mat result(*this); - result *= f; + result *= value; return result; } - constexpr Mat& operator/=(const Type& f) noexcept + constexpr Mat& operator/=(const Type& value) noexcept { - std::ranges::for_each(m_data,[&f](auto& val) {val /= f;}); + std::ranges::for_each(m_data,[&value](auto& val) {val /= value;}); return *this; } [[nodiscard]] - constexpr Mat operator/(const Type& f) const noexcept + constexpr Mat operator/(const Type& value) const noexcept { Mat result(*this); - result /= f; + result /= value; return result; } constexpr Mat& operator=(const Mat& other) noexcept { - if (this == &other) - return *this; - for (size_t i = 0; i < Rows; ++i) - for (size_t j = 0; j < Columns; ++j) - At(i, j) = other.At(i, j); + if (this != &other) + m_data = other.m_data; + return *this; } constexpr Mat& operator=(Mat&& other) noexcept { - if (this == &other) - return *this; - - for (size_t i = 0; i < Rows; ++i) - for (size_t j = 0; j < Columns; ++j) - At(i, j) = other.At(i, j); + if (this != &other) + m_data = std::move(other.m_data); return *this; } From e08c22f6047ebea81cadf767a3dcfa28d53f3d31 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 11 Apr 2025 23:54:56 +0300 Subject: [PATCH 244/795] added new build option --- CMakeLists.txt | 6 ++++++ include/omath/mat.hpp | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a6935022..f5684e30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" OFF) option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF) option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF) +option(OMATH_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build to improve general performance" ON) + if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/matrix.cpp) @@ -45,6 +47,10 @@ if (OMATH_USE_AVX2) target_compile_definitions(omath PUBLIC OMATH_USE_AVX2) endif() +if (OMATH_SUPRESS_SAFETY_CHECKS) + target_compile_definitions(omath PUBLIC OMATH_SUPRESS_SAFETY_CHECKS) +endif() + set_target_properties(omath PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 55f61ee6..f73fb206 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -118,9 +118,10 @@ namespace omath [[nodiscard]] constexpr const Type& At(const size_t rowIndex, const size_t columnIndex) const { +#if !defined(NDEBUG) && defined(OMATH_SUPRESS_SAFETY_CHECKS) if (rowIndex >= Rows || columnIndex >= Columns) throw std::out_of_range("Index out of range"); - +#endif if constexpr (StoreType == MatStoreType::ROW_MAJOR) return m_data[rowIndex * Columns + columnIndex]; @@ -175,7 +176,7 @@ namespace omath constexpr Mat& operator*=(const Type& f) noexcept { - std::ranges::for_each(m_data,[&f](auto& val) {val *= f;}); + std::ranges::for_each(m_data, [&f](auto& val) {val *= f;}); return *this; } @@ -196,7 +197,7 @@ namespace omath constexpr Mat& operator/=(const Type& value) noexcept { - std::ranges::for_each(m_data,[&value](auto& val) {val /= value;}); + std::ranges::for_each(m_data, [&value](auto& val) {val /= value;}); return *this; } @@ -243,7 +244,7 @@ namespace omath if constexpr (Rows == 1) return At(0, 0); - else if constexpr (Rows == 2) + if constexpr (Rows == 2) return At(0, 0) * At(1, 1) - At(0, 1) * At(1, 0); else { From 4a7a63193208ff23fb61b81f9074718ee3c09024 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 11 Apr 2025 23:57:56 +0300 Subject: [PATCH 245/795] added const method to mat --- include/omath/mat.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index f73fb206..8b89a523 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -92,6 +92,12 @@ namespace omath return At(row, col); } + [[nodiscard]] + constexpr Type& operator[](const size_t row, const size_t col) const + { + return At(row, col); + } + constexpr Mat(Mat&& other) noexcept { m_data = std::move(other.m_data); From 14acebad5febeab9e515816485e1fecf01c9e04d Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 12 Apr 2025 00:04:07 +0300 Subject: [PATCH 246/795] fixed tests --- tests/general/unit_test_mat.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index 97b94d18..43b25c18 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -166,8 +166,10 @@ TEST_F(UnitTestMat, StaticMethod_ToScreenMat) // Test exception handling in At() method TEST_F(UnitTestMat, Method_At_OutOfRange) { +#if !defined(NDEBUG) && defined(OMATH_SUPRESS_SAFETY_CHECKS) EXPECT_THROW(std::ignore = m2.At(2, 0), std::out_of_range); EXPECT_THROW(std::ignore = m2.At(0, 2), std::out_of_range); +#endif } // Test Determinant for 3x3 matrix From b613ff9ef115ec10a2ead0c8175103d4ab8851a3 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 13 Apr 2025 23:15:27 +0300 Subject: [PATCH 247/795] added missing header --- source/projectile_prediction/proj_pred_engine_avx2.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/projectile_prediction/proj_pred_engine_avx2.cpp b/source/projectile_prediction/proj_pred_engine_avx2.cpp index 8d36d849..632f5b61 100644 --- a/source/projectile_prediction/proj_pred_engine_avx2.cpp +++ b/source/projectile_prediction/proj_pred_engine_avx2.cpp @@ -2,7 +2,8 @@ // Created by Vlad on 2/23/2025. // #include "omath/projectile_prediction/proj_pred_engine_avx2.hpp" -#include "source_location" +#include +#include #if defined(OMATH_USE_AVX2) && defined(__i386__) && defined(__x86_64__) From 2180f8ab97407accadbb00cf27a3f44b12a168c2 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 16 Apr 2025 12:21:10 +0300 Subject: [PATCH 248/795] removed whitespaces --- include/omath/mat.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 24c57cef..68f867f3 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -97,7 +97,7 @@ namespace omath { return At(row, col); } - + constexpr Mat(Mat&& other) noexcept { m_data = std::move(other.m_data); From 1601f3cbc8a5e65b24074f102317da2df7e142e0 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 16 Apr 2025 13:33:08 +0300 Subject: [PATCH 249/795] added func added rotation matrix for opengl --- include/omath/angle.hpp | 8 ++++---- include/omath/engines/opengl_engine/formulas.hpp | 2 ++ include/omath/engines/source_engine/formulas.hpp | 6 ++++++ source/engines/opengl_engine/formulas.cpp | 12 +++++++++--- source/engines/source_engine/formulas.cpp | 11 ++++++++--- .../projectile_prediction/proj_pred_engine_avx2.cpp | 2 +- 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/include/omath/angle.hpp b/include/omath/angle.hpp index 07d55106..11d36d99 100644 --- a/include/omath/angle.hpp +++ b/include/omath/angle.hpp @@ -3,9 +3,9 @@ // #pragma once -#include "omath/angles.hpp" #include #include +#include "omath/angles.hpp" namespace omath @@ -17,7 +17,7 @@ namespace omath }; template - requires std::is_arithmetic_v + requires std::is_arithmetic_v class Angle { Type m_angle; @@ -34,6 +34,7 @@ namespace omath std::unreachable(); } } + public: [[nodiscard]] constexpr static Angle FromDegrees(const Type& degrees) @@ -42,7 +43,6 @@ namespace omath } constexpr Angle() : m_angle(0) { - } [[nodiscard]] constexpr static Angle FromRadians(const Type& degrees) @@ -149,4 +149,4 @@ namespace omath return Angle{-m_angle}; } }; -} +} // namespace omath diff --git a/include/omath/engines/opengl_engine/formulas.hpp b/include/omath/engines/opengl_engine/formulas.hpp index d9dcfe7f..7f24ef6e 100644 --- a/include/omath/engines/opengl_engine/formulas.hpp +++ b/include/omath/engines/opengl_engine/formulas.hpp @@ -18,6 +18,8 @@ namespace omath::opengl_engine [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); + [[nodiscard]] + Mat4x4 RotationMatrix(const ViewAngles& angles); [[nodiscard]] Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); diff --git a/include/omath/engines/source_engine/formulas.hpp b/include/omath/engines/source_engine/formulas.hpp index fd6415e4..817ee140 100644 --- a/include/omath/engines/source_engine/formulas.hpp +++ b/include/omath/engines/source_engine/formulas.hpp @@ -9,6 +9,10 @@ namespace omath::source_engine [[nodiscard]] Vector3 ForwardVector(const ViewAngles& angles); + [[nodiscard]] + Mat4x4 RotationMatrix(const ViewAngles& angles); + + [[nodiscard]] Vector3 RightVector(const ViewAngles& angles); @@ -20,4 +24,6 @@ namespace omath::source_engine [[nodiscard]] Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); + + } // namespace omath::source diff --git a/source/engines/opengl_engine/formulas.cpp b/source/engines/opengl_engine/formulas.cpp index 8162bdb3..3bd7e478 100644 --- a/source/engines/opengl_engine/formulas.cpp +++ b/source/engines/opengl_engine/formulas.cpp @@ -9,19 +9,19 @@ namespace omath::opengl_engine Vector3 ForwardVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsForward); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } Vector3 RightVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsRight); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } Vector3 UpVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsUp); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } @@ -30,6 +30,12 @@ namespace omath::opengl_engine return MatCameraView(-ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); } + Mat4x4 RotationMatrix(const ViewAngles& angles) + { + return MatRotationAxisZ(angles.roll) * + MatRotationAxisY(angles.yaw) * + MatRotationAxisX(angles.pitch); + } Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { diff --git a/source/engines/source_engine/formulas.cpp b/source/engines/source_engine/formulas.cpp index f037cadd..5dcca886 100644 --- a/source/engines/source_engine/formulas.cpp +++ b/source/engines/source_engine/formulas.cpp @@ -8,20 +8,25 @@ namespace omath::source_engine { Vector3 ForwardVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsForward); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } + Mat4x4 RotationMatrix(const ViewAngles& angles) + { + return MatRotationAxisZ(angles.yaw) * MatRotationAxisY(angles.pitch) * MatRotationAxisX(angles.roll); + } + Vector3 RightVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsRight); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } Vector3 UpVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsUp); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } diff --git a/source/projectile_prediction/proj_pred_engine_avx2.cpp b/source/projectile_prediction/proj_pred_engine_avx2.cpp index 632f5b61..48d2b691 100644 --- a/source/projectile_prediction/proj_pred_engine_avx2.cpp +++ b/source/projectile_prediction/proj_pred_engine_avx2.cpp @@ -5,7 +5,7 @@ #include #include - +#include #if defined(OMATH_USE_AVX2) && defined(__i386__) && defined(__x86_64__) #include #include From 7873047550b5b1d0219db1c5e983f3da1b5d9f21 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 16 Apr 2025 17:38:12 +0300 Subject: [PATCH 250/795] added func added rotation matrix for opengl updated unit tests --- tests/engines/unit_test_open_gl.cpp | 6 ++++++ tests/engines/unit_test_source_engine.cpp | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/tests/engines/unit_test_open_gl.cpp b/tests/engines/unit_test_open_gl.cpp index 5e0a4624..a1075339 100644 --- a/tests/engines/unit_test_open_gl.cpp +++ b/tests/engines/unit_test_open_gl.cpp @@ -10,7 +10,13 @@ TEST(UnitTestOpenGL, ForwardVector) { const auto forward = omath::opengl_engine::ForwardVector({}); + omath::opengl_engine::ViewAngles angles = {}; + angles.pitch = omath::opengl_engine::PitchAngle::FromDegrees(90); + std::print("{}\n", angles.pitch.AsDegrees()); + const auto forward2 = omath::opengl_engine::ForwardVector(angles); + + std::println("{} {} {}", forward2.x, (int)forward2.y, forward2.z); EXPECT_EQ(forward, omath::opengl_engine::kAbsForward); } diff --git a/tests/engines/unit_test_source_engine.cpp b/tests/engines/unit_test_source_engine.cpp index c129249c..529e69d1 100644 --- a/tests/engines/unit_test_source_engine.cpp +++ b/tests/engines/unit_test_source_engine.cpp @@ -10,6 +10,11 @@ TEST(UnitTestSourceEngine, ForwardVector) { const auto forward = omath::source_engine::ForwardVector({}); + omath::source_engine::ViewAngles angles; + //angles.pitch = omath::source_engine::PitchAngle::FromDegrees(-90); + const auto forward2 = omath::source_engine::ForwardVector(angles); + + //std::println("{} {} {}", forward2.x, forward2.y, forward2.z); EXPECT_EQ(forward, omath::source_engine::kAbsForward); } From 592a98f38c61232fdbe4eeff58d78cbf05e4486b Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 16 Apr 2025 17:52:19 +0300 Subject: [PATCH 251/795] removed method from Mat added method for unity --- include/omath/engines/iw_engine/formulas.hpp | 3 +++ include/omath/engines/source_engine/formulas.hpp | 4 ---- include/omath/engines/unity_engine/formulas.hpp | 2 ++ include/omath/mat.hpp | 7 ------- source/engines/iw_engine/formulas.cpp | 10 +++++++--- source/engines/unity_engine/formulas.cpp | 12 +++++++++--- tests/engines/unit_test_open_gl.cpp | 2 +- 7 files changed, 22 insertions(+), 18 deletions(-) diff --git a/include/omath/engines/iw_engine/formulas.hpp b/include/omath/engines/iw_engine/formulas.hpp index a604f48a..1a275543 100644 --- a/include/omath/engines/iw_engine/formulas.hpp +++ b/include/omath/engines/iw_engine/formulas.hpp @@ -16,6 +16,9 @@ namespace omath::iw_engine [[nodiscard]] Vector3 UpVector(const ViewAngles& angles); + [[nodiscard]] + Mat4x4 RotationMatrix(const ViewAngles& angles); + [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); diff --git a/include/omath/engines/source_engine/formulas.hpp b/include/omath/engines/source_engine/formulas.hpp index 817ee140..450de1eb 100644 --- a/include/omath/engines/source_engine/formulas.hpp +++ b/include/omath/engines/source_engine/formulas.hpp @@ -12,7 +12,6 @@ namespace omath::source_engine [[nodiscard]] Mat4x4 RotationMatrix(const ViewAngles& angles); - [[nodiscard]] Vector3 RightVector(const ViewAngles& angles); @@ -21,9 +20,6 @@ namespace omath::source_engine [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); - [[nodiscard]] Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); - - } // namespace omath::source diff --git a/include/omath/engines/unity_engine/formulas.hpp b/include/omath/engines/unity_engine/formulas.hpp index 6352cd18..8b369535 100644 --- a/include/omath/engines/unity_engine/formulas.hpp +++ b/include/omath/engines/unity_engine/formulas.hpp @@ -18,6 +18,8 @@ namespace omath::unity_engine [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); + [[nodiscard]] + Mat4x4 RotationMatrix(const ViewAngles& angles); [[nodiscard]] Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 68f867f3..1d7b4511 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -428,13 +428,6 @@ namespace omath } * MatTranslation(-cameraOrigin); } - template - [[nodiscard]] - Mat<4, 4, Type, St> MatRotation(const ViewAngles& angles) noexcept - { - return MatRotationAxisZ(angles.yaw) * MatRotationAxisY(angles.pitch) * MatRotationAxisX(angles.roll); - } - template [[nodiscard]] Mat<4, 4, Type, St> MatPerspectiveLeftHanded(const float fieldOfView, const float aspectRatio, const float near, diff --git a/source/engines/iw_engine/formulas.cpp b/source/engines/iw_engine/formulas.cpp index f4fe076e..0c293fee 100644 --- a/source/engines/iw_engine/formulas.cpp +++ b/source/engines/iw_engine/formulas.cpp @@ -9,23 +9,27 @@ namespace omath::iw_engine Vector3 ForwardVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsForward); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } Vector3 RightVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsRight); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } Vector3 UpVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsUp); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } + Mat4x4 RotationMatrix(const ViewAngles& angles) + { + return MatRotationAxisZ(angles.yaw) * MatRotationAxisY(angles.pitch) * MatRotationAxisX(angles.roll); + } Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) { diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp index d3a872fd..798920d4 100644 --- a/source/engines/unity_engine/formulas.cpp +++ b/source/engines/unity_engine/formulas.cpp @@ -9,19 +9,19 @@ namespace omath::unity_engine { Vector3 ForwardVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsForward); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsForward); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } Vector3 RightVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsRight); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsRight); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } Vector3 UpVector(const ViewAngles& angles) { - const auto vec = MatRotation(angles) * MatColumnFromVector(kAbsUp); + const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsUp); return {vec.At(0, 0), vec.At(1, 0), vec.At(2, 0)}; } @@ -30,6 +30,12 @@ namespace omath::unity_engine return MatCameraView(ForwardVector(angles), -RightVector(angles), UpVector(angles), cam_origin); } + Mat4x4 RotationMatrix(const ViewAngles& angles) + { + return MatRotationAxisZ(angles.roll) * + MatRotationAxisY(angles.yaw) * + MatRotationAxisX(angles.pitch); + } Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) { diff --git a/tests/engines/unit_test_open_gl.cpp b/tests/engines/unit_test_open_gl.cpp index a1075339..66c86c80 100644 --- a/tests/engines/unit_test_open_gl.cpp +++ b/tests/engines/unit_test_open_gl.cpp @@ -16,7 +16,7 @@ TEST(UnitTestOpenGL, ForwardVector) std::print("{}\n", angles.pitch.AsDegrees()); const auto forward2 = omath::opengl_engine::ForwardVector(angles); - std::println("{} {} {}", forward2.x, (int)forward2.y, forward2.z); + std::println("{} {} {}", forward2.x, (int)forward2.y, (int)forward2.z); EXPECT_EQ(forward, omath::opengl_engine::kAbsForward); } From 3f6ea010dc10b25754926e4d6ddf14001c4cc7e3 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 16 Apr 2025 17:52:57 +0300 Subject: [PATCH 252/795] fixed formating --- include/omath/engines/iw_engine/formulas.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/omath/engines/iw_engine/formulas.hpp b/include/omath/engines/iw_engine/formulas.hpp index 1a275543..60d1fee8 100644 --- a/include/omath/engines/iw_engine/formulas.hpp +++ b/include/omath/engines/iw_engine/formulas.hpp @@ -21,7 +21,6 @@ namespace omath::iw_engine [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); - [[nodiscard]] Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); } // namespace omath::iw_engine From bed204a66378eaa2e56f96c72be31b6277f1e527 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 16 Apr 2025 18:35:50 +0300 Subject: [PATCH 253/795] added unit tests --- .../omath/engines/opengl_engine/constants.hpp | 7 ++-- .../omath/engines/unity_engine/constants.hpp | 2 +- source/engines/opengl_engine/formulas.cpp | 2 +- tests/engines/unit_test_open_gl.cpp | 2 +- tests/engines/unit_test_source_engine.cpp | 5 --- tests/engines/unit_test_unity_engine.cpp | 36 +++++++++++++++++++ 6 files changed, 43 insertions(+), 11 deletions(-) diff --git a/include/omath/engines/opengl_engine/constants.hpp b/include/omath/engines/opengl_engine/constants.hpp index 66aac7d7..3e0a7624 100644 --- a/include/omath/engines/opengl_engine/constants.hpp +++ b/include/omath/engines/opengl_engine/constants.hpp @@ -17,9 +17,10 @@ namespace omath::opengl_engine using Mat4x4 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>; using Mat3x3 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>; using Mat1x3 = Mat<1, 3, float, MatStoreType::COLUMN_MAJOR>; - using PitchAngle = Angle; - using YawAngle = Angle; - using RollAngle = Angle; + using PitchAngle = Angle; + using YawAngle = Angle; + using RollAngle = Angle; + using ViewAngles = omath::ViewAngles; } \ No newline at end of file diff --git a/include/omath/engines/unity_engine/constants.hpp b/include/omath/engines/unity_engine/constants.hpp index dae1775d..27ec9a3b 100644 --- a/include/omath/engines/unity_engine/constants.hpp +++ b/include/omath/engines/unity_engine/constants.hpp @@ -18,7 +18,7 @@ namespace omath::unity_engine 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 PitchAngle = Angle; using YawAngle = Angle; using RollAngle = Angle; diff --git a/source/engines/opengl_engine/formulas.cpp b/source/engines/opengl_engine/formulas.cpp index 3bd7e478..62652255 100644 --- a/source/engines/opengl_engine/formulas.cpp +++ b/source/engines/opengl_engine/formulas.cpp @@ -34,7 +34,7 @@ namespace omath::opengl_engine { return MatRotationAxisZ(angles.roll) * MatRotationAxisY(angles.yaw) * - MatRotationAxisX(angles.pitch); + MatRotationAxisX(-angles.pitch); } Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) diff --git a/tests/engines/unit_test_open_gl.cpp b/tests/engines/unit_test_open_gl.cpp index 66c86c80..cab6465d 100644 --- a/tests/engines/unit_test_open_gl.cpp +++ b/tests/engines/unit_test_open_gl.cpp @@ -16,7 +16,7 @@ TEST(UnitTestOpenGL, ForwardVector) std::print("{}\n", angles.pitch.AsDegrees()); const auto forward2 = omath::opengl_engine::ForwardVector(angles); - std::println("{} {} {}", forward2.x, (int)forward2.y, (int)forward2.z); + std::println("OpenGL {} {} {}", std::round(forward2.x), std::round(forward2.y), std::round(forward2.z)); EXPECT_EQ(forward, omath::opengl_engine::kAbsForward); } diff --git a/tests/engines/unit_test_source_engine.cpp b/tests/engines/unit_test_source_engine.cpp index 529e69d1..c129249c 100644 --- a/tests/engines/unit_test_source_engine.cpp +++ b/tests/engines/unit_test_source_engine.cpp @@ -10,11 +10,6 @@ TEST(UnitTestSourceEngine, ForwardVector) { const auto forward = omath::source_engine::ForwardVector({}); - omath::source_engine::ViewAngles angles; - //angles.pitch = omath::source_engine::PitchAngle::FromDegrees(-90); - const auto forward2 = omath::source_engine::ForwardVector(angles); - - //std::println("{} {} {}", forward2.x, forward2.y, forward2.z); EXPECT_EQ(forward, omath::source_engine::kAbsForward); } diff --git a/tests/engines/unit_test_unity_engine.cpp b/tests/engines/unit_test_unity_engine.cpp index 2e0daaa3..7fb95a28 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -14,6 +14,42 @@ TEST(UnitTestUnityEngine, ForwardVector) EXPECT_EQ(forward, omath::unity_engine::kAbsForward); } +TEST(UnitTestUnityEngine, ForwardVectorRotationYaw) +{ + omath::unity_engine::ViewAngles angles; + + angles.yaw = omath::unity_engine::YawAngle::FromDegrees(90.f); + + const auto forward = omath::unity_engine::ForwardVector(angles); + EXPECT_NEAR(forward.x, omath::unity_engine::kAbsRight.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::unity_engine::kAbsRight.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::unity_engine::kAbsRight.z, 0.00001f); +} + +TEST(UnitTestUnityEngine, ForwardVectorRotationPitch) +{ + omath::unity_engine::ViewAngles angles; + + angles.pitch = omath::unity_engine::PitchAngle::FromDegrees(-90.f); + + const auto forward = omath::unity_engine::ForwardVector(angles); + EXPECT_NEAR(forward.x, omath::unity_engine::kAbsUp.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::unity_engine::kAbsUp.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::unity_engine::kAbsUp.z, 0.00001f); +} + +TEST(UnitTestUnityEngine, ForwardVectorRotationRoll) +{ + omath::unity_engine::ViewAngles angles; + + angles.roll = omath::unity_engine::RollAngle::FromDegrees(-90.f); + + const auto forward = omath::unity_engine::UpVector(angles); + EXPECT_NEAR(forward.x, omath::unity_engine::kAbsRight.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::unity_engine::kAbsRight.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::unity_engine::kAbsRight.z, 0.00001f); +} + TEST(UnitTestUnityEngine, RightVector) { const auto right = omath::unity_engine::RightVector({}); From 127bae0b78d34740702e5337eb7a0c59f712d390 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 16 Apr 2025 18:53:31 +0300 Subject: [PATCH 254/795] added tests for source --- tests/engines/unit_test_source_engine.cpp | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/engines/unit_test_source_engine.cpp b/tests/engines/unit_test_source_engine.cpp index c129249c..eebd58eb 100644 --- a/tests/engines/unit_test_source_engine.cpp +++ b/tests/engines/unit_test_source_engine.cpp @@ -27,6 +27,42 @@ TEST(UnitTestSourceEngine, UpVector) EXPECT_EQ(up, omath::source_engine::kAbsUp); } +TEST(UnitTestSourceEngine, ForwardVectorRotationYaw) +{ + omath::source_engine::ViewAngles angles; + + angles.yaw = omath::source_engine::YawAngle::FromDegrees(-90.f); + + const auto forward = omath::source_engine::ForwardVector(angles); + EXPECT_NEAR(forward.x, omath::source_engine::kAbsRight.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::source_engine::kAbsRight.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::source_engine::kAbsRight.z, 0.00001f); +} + +TEST(UnitTestSourceEngine, ForwardVectorRotationPitch) +{ + omath::source_engine::ViewAngles angles; + + angles.pitch = omath::source_engine::PitchAngle::FromDegrees(-89.f); + + const auto forward = omath::source_engine::ForwardVector(angles); + EXPECT_NEAR(forward.x, omath::source_engine::kAbsUp.x, 0.02f); + EXPECT_NEAR(forward.y, omath::source_engine::kAbsUp.y, 0.01f); + EXPECT_NEAR(forward.z, omath::source_engine::kAbsUp.z, 0.01f); +} + +TEST(UnitTestSourceEngine, ForwardVectorRotationRoll) +{ + omath::source_engine::ViewAngles angles; + + angles.roll = omath::source_engine::RollAngle::FromDegrees(90.f); + + const auto forward = omath::source_engine::UpVector(angles); + EXPECT_NEAR(forward.x, omath::source_engine::kAbsRight.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::source_engine::kAbsRight.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::source_engine::kAbsRight.z, 0.00001f); +} + TEST(UnitTestSourceEngine, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); From 0069b8bd963cfc56a144afb88f1588a1e5495795 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 16 Apr 2025 19:11:02 +0300 Subject: [PATCH 255/795] improved openg gl rotation matrix, added tests --- source/engines/opengl_engine/formulas.cpp | 2 +- tests/engines/unit_test_iw_engine.cpp | 36 ++++++++++++++++++ tests/engines/unit_test_open_gl.cpp | 46 +++++++++++++++++++---- 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/source/engines/opengl_engine/formulas.cpp b/source/engines/opengl_engine/formulas.cpp index 62652255..f3e5b5ce 100644 --- a/source/engines/opengl_engine/formulas.cpp +++ b/source/engines/opengl_engine/formulas.cpp @@ -33,7 +33,7 @@ namespace omath::opengl_engine Mat4x4 RotationMatrix(const ViewAngles& angles) { return MatRotationAxisZ(angles.roll) * - MatRotationAxisY(angles.yaw) * + MatRotationAxisY(-angles.yaw) * MatRotationAxisX(-angles.pitch); } Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, diff --git a/tests/engines/unit_test_iw_engine.cpp b/tests/engines/unit_test_iw_engine.cpp index 679af031..86b7d0b2 100644 --- a/tests/engines/unit_test_iw_engine.cpp +++ b/tests/engines/unit_test_iw_engine.cpp @@ -27,6 +27,42 @@ TEST(UnitTestIwEngine, UpVector) EXPECT_EQ(up, omath::iw_engine::kAbsUp); } +TEST(UnitTestIwEngine, ForwardVectorRotationYaw) +{ + omath::iw_engine::ViewAngles angles; + + angles.yaw = omath::iw_engine::YawAngle::FromDegrees(-90.f); + + const auto forward = omath::iw_engine::ForwardVector(angles); + EXPECT_NEAR(forward.x, omath::iw_engine::kAbsRight.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::iw_engine::kAbsRight.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::iw_engine::kAbsRight.z, 0.00001f); +} + +TEST(UnitTestIwEngine, ForwardVectorRotationPitch) +{ + omath::iw_engine::ViewAngles angles; + + angles.pitch = omath::iw_engine::PitchAngle::FromDegrees(-89.f); + + const auto forward = omath::iw_engine::ForwardVector(angles); + EXPECT_NEAR(forward.x, omath::iw_engine::kAbsUp.x, 0.02f); + EXPECT_NEAR(forward.y, omath::iw_engine::kAbsUp.y, 0.01f); + EXPECT_NEAR(forward.z, omath::iw_engine::kAbsUp.z, 0.01f); +} + +TEST(UnitTestIwEngine, ForwardVectorRotationRoll) +{ + omath::iw_engine::ViewAngles angles; + + angles.roll = omath::iw_engine::RollAngle::FromDegrees(90.f); + + const auto forward = omath::iw_engine::UpVector(angles); + EXPECT_NEAR(forward.x, omath::iw_engine::kAbsRight.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::iw_engine::kAbsRight.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::iw_engine::kAbsRight.z, 0.00001f); +} + TEST(UnitTestIwEngine, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); diff --git a/tests/engines/unit_test_open_gl.cpp b/tests/engines/unit_test_open_gl.cpp index cab6465d..170b306d 100644 --- a/tests/engines/unit_test_open_gl.cpp +++ b/tests/engines/unit_test_open_gl.cpp @@ -10,20 +10,12 @@ TEST(UnitTestOpenGL, ForwardVector) { const auto forward = omath::opengl_engine::ForwardVector({}); - omath::opengl_engine::ViewAngles angles = {}; - angles.pitch = omath::opengl_engine::PitchAngle::FromDegrees(90); - - std::print("{}\n", angles.pitch.AsDegrees()); - const auto forward2 = omath::opengl_engine::ForwardVector(angles); - - std::println("OpenGL {} {} {}", std::round(forward2.x), std::round(forward2.y), std::round(forward2.z)); EXPECT_EQ(forward, omath::opengl_engine::kAbsForward); } TEST(UnitTestOpenGL, RightVector) { const auto right = omath::opengl_engine::RightVector({}); - EXPECT_EQ(right, omath::opengl_engine::kAbsRight); } @@ -33,6 +25,44 @@ TEST(UnitTestOpenGL, UpVector) EXPECT_EQ(up, omath::opengl_engine::kAbsUp); } +TEST(UnitTestOpenGL, ForwardVectorRotationYaw) +{ + omath::opengl_engine::ViewAngles angles; + + angles.yaw = omath::opengl_engine::YawAngle::FromDegrees(90.f); + + const auto forward = omath::opengl_engine::ForwardVector(angles); + EXPECT_NEAR(forward.x, omath::opengl_engine::kAbsRight.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::opengl_engine::kAbsRight.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::opengl_engine::kAbsRight.z, 0.00001f); +} + + + +TEST(UnitTestOpenGL, ForwardVectorRotationPitch) +{ + omath::opengl_engine::ViewAngles angles; + + angles.pitch = omath::opengl_engine::PitchAngle::FromDegrees(-90.f); + + const auto forward = omath::opengl_engine::ForwardVector(angles); + EXPECT_NEAR(forward.x, omath::opengl_engine::kAbsUp.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::opengl_engine::kAbsUp.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::opengl_engine::kAbsUp.z, 0.00001f); +} + +TEST(UnitTestOpenGL, ForwardVectorRotationRoll) +{ + omath::opengl_engine::ViewAngles angles; + + angles.roll = omath::opengl_engine::RollAngle::FromDegrees(-90.f); + + const auto forward = omath::opengl_engine::UpVector(angles); + EXPECT_NEAR(forward.x, omath::opengl_engine::kAbsRight.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::opengl_engine::kAbsRight.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::opengl_engine::kAbsRight.z, 0.00001f); +} + TEST(UnitTestOpenGL, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); From baf7ee8f88d5e862edd1675517603489f21cbbc0 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 16 Apr 2025 19:11:02 +0300 Subject: [PATCH 256/795] fixed include --- source/projectile_prediction/proj_pred_engine_avx2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/projectile_prediction/proj_pred_engine_avx2.cpp b/source/projectile_prediction/proj_pred_engine_avx2.cpp index 48d2b691..055e0608 100644 --- a/source/projectile_prediction/proj_pred_engine_avx2.cpp +++ b/source/projectile_prediction/proj_pred_engine_avx2.cpp @@ -5,9 +5,9 @@ #include #include -#include #if defined(OMATH_USE_AVX2) && defined(__i386__) && defined(__x86_64__) #include +#else #include #endif From 492ddfd566addac5f10fc36e079b23f36a7fadaf Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 18 Apr 2025 00:43:46 +0300 Subject: [PATCH 257/795] added box --- CMakeLists.txt | 3 +-- include/omath/3d_primitives/box.hpp | 14 ++++++++++ source/3d_primitives/CMakeLists.txt | 1 + source/3d_primitives/box.cpp | 32 +++++++++++++++++++++++ source/CMakeLists.txt | 3 ++- tests/CMakeLists.txt | 1 + tests/general/unit_test_box_primitive.cpp | 4 +++ 7 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 include/omath/3d_primitives/box.hpp create mode 100644 source/3d_primitives/CMakeLists.txt create mode 100644 source/3d_primitives/box.cpp create mode 100644 tests/general/unit_test_box_primitive.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ade1bb1b..d87052c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,8 +18,7 @@ option(OMATH_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/matrix.cpp) else() - add_library(omath STATIC source/matrix.cpp - source/matrix.cpp) + add_library(omath STATIC source/matrix.cpp) endif() message(STATUS "Building on ${CMAKE_HOST_SYSTEM_NAME}") diff --git a/include/omath/3d_primitives/box.hpp b/include/omath/3d_primitives/box.hpp new file mode 100644 index 00000000..a59b235f --- /dev/null +++ b/include/omath/3d_primitives/box.hpp @@ -0,0 +1,14 @@ +// +// Created by Vlad on 4/18/2025. +// + +#pragma once +#include + + +namespace omath::primitives +{ + [[nodiscard]] + std::array, 8> CreateBox(const Vector3& top, const Vector3& bottom, + const Vector3& dirForward, const Vector3& dirRight); +} diff --git a/source/3d_primitives/CMakeLists.txt b/source/3d_primitives/CMakeLists.txt new file mode 100644 index 00000000..ef4e0a46 --- /dev/null +++ b/source/3d_primitives/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(omath PRIVATE box.cpp) \ No newline at end of file diff --git a/source/3d_primitives/box.cpp b/source/3d_primitives/box.cpp new file mode 100644 index 00000000..652cef69 --- /dev/null +++ b/source/3d_primitives/box.cpp @@ -0,0 +1,32 @@ +// +// Created by Vlad on 4/18/2025. +// +#include "omath/3d_primitives/box.hpp" + +namespace omath::primitives +{ + + std::array, 8> CreateBox(const Vector3& top, const Vector3& bottom, + const Vector3& dirForward, + const Vector3& dirRight) + { + const auto height = top.DistTo(bottom); + const auto sideSize = height / 6.f; + + std::array, 8> points; + + points[0] = bottom + (dirForward + dirRight) * sideSize; + points[1] = bottom + (dirForward - dirRight) * sideSize; + + points[2] = bottom + (-dirForward + dirRight) * sideSize; + points[3] = bottom + (-dirForward - dirRight) * sideSize; + + points[4] = top + (dirForward + dirRight) * sideSize; + points[5] = top + (dirForward - dirRight) * sideSize; + + points[6] = top + (-dirForward + dirRight) * sideSize; + points[7] = top + (-dirForward - dirRight) * sideSize; + + return points; + } +} diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index f690fe38..1053134c 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -7,4 +7,5 @@ add_subdirectory(projectile_prediction) add_subdirectory(pathfinding) add_subdirectory(projection) add_subdirectory(collision) -add_subdirectory(engines) \ No newline at end of file +add_subdirectory(engines) +add_subdirectory(3d_primitives) \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 244f7bb6..5f1c8e08 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(unit-tests general/unit_test_view_angles.cpp general/unit_test_angle.cpp general/unit_test_triangle.cpp + general/unit_test_box_primitive.cpp engines/unit_test_open_gl.cpp engines/unit_test_unity_engine.cpp diff --git a/tests/general/unit_test_box_primitive.cpp b/tests/general/unit_test_box_primitive.cpp new file mode 100644 index 00000000..f38e6e1a --- /dev/null +++ b/tests/general/unit_test_box_primitive.cpp @@ -0,0 +1,4 @@ +// +// Created by Vlad on 4/18/2025. +// +#include \ No newline at end of file From 0ce30a70383e63e111133417ef1dbbc98f5d1323 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 18 Apr 2025 00:51:07 +0300 Subject: [PATCH 258/795] added new build option --- CMakeLists.txt | 12 ++++++++---- include/omath/3d_primitives/box.hpp | 2 +- tests/general/unit_test_box_primitive.cpp | 4 +++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d87052c6..a51519c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(omath VERSION 1.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) -option(OMATH_BUILD_TESTS "Build unit tests" OFF) +option(OMATH_BUILD_TESTS "Build unit tests" ON) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) @@ -13,7 +13,7 @@ option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF) option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF) option(OMATH_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build to improve general performance" ON) - +option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" OFF) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/matrix.cpp) @@ -54,11 +54,15 @@ endif() set_target_properties(omath PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" - UNITY_BUILD ON - UNITY_BUILD_BATCH_SIZE 20 CXX_STANDARD 23 CXX_STANDARD_REQUIRED ON) +if (OMATH_USE_UNITY_BUILD) + set_target_properties(omath PROPERTIES + UNITY_BUILD ON + UNITY_BUILD_BATCH_SIZE 20) +endif() + if (OMATH_STATIC_MSVC_RUNTIME_LIBRARY) set_target_properties(omath PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" diff --git a/include/omath/3d_primitives/box.hpp b/include/omath/3d_primitives/box.hpp index a59b235f..afc7003b 100644 --- a/include/omath/3d_primitives/box.hpp +++ b/include/omath/3d_primitives/box.hpp @@ -4,7 +4,7 @@ #pragma once #include - +#include "omath/vector3.hpp" namespace omath::primitives { diff --git a/tests/general/unit_test_box_primitive.cpp b/tests/general/unit_test_box_primitive.cpp index f38e6e1a..215f2229 100644 --- a/tests/general/unit_test_box_primitive.cpp +++ b/tests/general/unit_test_box_primitive.cpp @@ -1,4 +1,6 @@ // // Created by Vlad on 4/18/2025. // -#include \ No newline at end of file +#include +#include + From 97c2da893b9498f824189d91f0519ea2aa61ad69 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 18 Apr 2025 01:33:47 +0300 Subject: [PATCH 259/795] added ratio param --- include/omath/3d_primitives/box.hpp | 2 +- source/3d_primitives/box.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/omath/3d_primitives/box.hpp b/include/omath/3d_primitives/box.hpp index afc7003b..bbe43b22 100644 --- a/include/omath/3d_primitives/box.hpp +++ b/include/omath/3d_primitives/box.hpp @@ -10,5 +10,5 @@ namespace omath::primitives { [[nodiscard]] std::array, 8> CreateBox(const Vector3& top, const Vector3& bottom, - const Vector3& dirForward, const Vector3& dirRight); + const Vector3& dirForward, const Vector3& dirRight, float ratio = 4.f); } diff --git a/source/3d_primitives/box.cpp b/source/3d_primitives/box.cpp index 652cef69..4403b234 100644 --- a/source/3d_primitives/box.cpp +++ b/source/3d_primitives/box.cpp @@ -8,10 +8,11 @@ namespace omath::primitives std::array, 8> CreateBox(const Vector3& top, const Vector3& bottom, const Vector3& dirForward, - const Vector3& dirRight) + const Vector3& dirRight, + const float ratio) { const auto height = top.DistTo(bottom); - const auto sideSize = height / 6.f; + const auto sideSize = height / ratio; std::array, 8> points; From 254674a62ee8f514ac3ae33f3c72a0972d6cb250 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 18 Apr 2025 12:11:43 +0300 Subject: [PATCH 260/795] fixed code style --- include/omath/3d_primitives/box.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/omath/3d_primitives/box.hpp b/include/omath/3d_primitives/box.hpp index bbe43b22..3b786e31 100644 --- a/include/omath/3d_primitives/box.hpp +++ b/include/omath/3d_primitives/box.hpp @@ -10,5 +10,6 @@ namespace omath::primitives { [[nodiscard]] std::array, 8> CreateBox(const Vector3& top, const Vector3& bottom, - const Vector3& dirForward, const Vector3& dirRight, float ratio = 4.f); + const Vector3& dirForward, const Vector3& dirRight, + float ratio = 4.f); } From a34076634880d5b2a35b6179dc40c08f15929519 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 18 Apr 2025 12:34:24 +0300 Subject: [PATCH 261/795] switched to polygons --- include/omath/3d_primitives/box.hpp | 8 +++++--- source/3d_primitives/box.cpp | 10 ++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/include/omath/3d_primitives/box.hpp b/include/omath/3d_primitives/box.hpp index 3b786e31..d915ca2e 100644 --- a/include/omath/3d_primitives/box.hpp +++ b/include/omath/3d_primitives/box.hpp @@ -4,12 +4,14 @@ #pragma once #include +#include "omath/triangle.hpp" #include "omath/vector3.hpp" + namespace omath::primitives { [[nodiscard]] - std::array, 8> CreateBox(const Vector3& top, const Vector3& bottom, - const Vector3& dirForward, const Vector3& dirRight, - float ratio = 4.f); + std::array>, 8> CreateBox(const Vector3& top, const Vector3& bottom, + const Vector3& dirForward, const Vector3& dirRight, + float ratio = 4.f); } diff --git a/source/3d_primitives/box.cpp b/source/3d_primitives/box.cpp index 4403b234..202e47b1 100644 --- a/source/3d_primitives/box.cpp +++ b/source/3d_primitives/box.cpp @@ -3,10 +3,12 @@ // #include "omath/3d_primitives/box.hpp" + + namespace omath::primitives { - std::array, 8> CreateBox(const Vector3& top, const Vector3& bottom, + std::array>, 8> CreateBox(const Vector3& top, const Vector3& bottom, const Vector3& dirForward, const Vector3& dirRight, const float ratio) @@ -28,6 +30,10 @@ namespace omath::primitives points[6] = top + (-dirForward + dirRight) * sideSize; points[7] = top + (-dirForward - dirRight) * sideSize; - return points; + + std::array>, 8> polygons; + + polygons[0] = {points[0], points[2], points[3]}; + return polygons; } } From 8bf0bb8e0d7e2d9bb22f78605333c57eb7b71669 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 18 Apr 2025 13:56:08 +0300 Subject: [PATCH 262/795] improved line trace and box primitive --- include/omath/3d_primitives/box.hpp | 2 +- include/omath/collision/line_tracer.hpp | 2 +- source/3d_primitives/box.cpp | 53 ++++++++++++++++--------- source/collision/line_tracer.cpp | 7 +++- 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/include/omath/3d_primitives/box.hpp b/include/omath/3d_primitives/box.hpp index d915ca2e..0f18c40b 100644 --- a/include/omath/3d_primitives/box.hpp +++ b/include/omath/3d_primitives/box.hpp @@ -11,7 +11,7 @@ namespace omath::primitives { [[nodiscard]] - std::array>, 8> CreateBox(const Vector3& top, const Vector3& bottom, + std::array>, 12> CreateBox(const Vector3& top, const Vector3& bottom, const Vector3& dirForward, const Vector3& dirRight, float ratio = 4.f); } diff --git a/include/omath/collision/line_tracer.hpp b/include/omath/collision/line_tracer.hpp index 88341361..cd7a87ae 100644 --- a/include/omath/collision/line_tracer.hpp +++ b/include/omath/collision/line_tracer.hpp @@ -13,7 +13,7 @@ namespace omath::collision public: Vector3 start; Vector3 end; - + bool infinite_length = false; [[nodiscard]] Vector3 DirectionVector() const; diff --git a/source/3d_primitives/box.cpp b/source/3d_primitives/box.cpp index 202e47b1..0e45eaaa 100644 --- a/source/3d_primitives/box.cpp +++ b/source/3d_primitives/box.cpp @@ -4,36 +4,53 @@ #include "omath/3d_primitives/box.hpp" - namespace omath::primitives { - - std::array>, 8> CreateBox(const Vector3& top, const Vector3& bottom, - const Vector3& dirForward, - const Vector3& dirRight, - const float ratio) + std::array>, 12> CreateBox(const Vector3& top, const Vector3& bottom, + const Vector3& dirForward, + const Vector3& dirRight, + const float ratio) { const auto height = top.DistTo(bottom); const auto sideSize = height / ratio; - std::array, 8> points; + // corner layout (0‑3 bottom, 4‑7 top) + std::array, 8> p; + p[0] = bottom + (dirForward + dirRight) * sideSize; // front‑right‑bottom + p[1] = bottom + (dirForward - dirRight) * sideSize; // front‑left‑bottom + p[2] = bottom + (-dirForward + dirRight) * sideSize; // back‑right‑bottom + p[3] = bottom + (-dirForward - dirRight) * sideSize; // back‑left‑bottom + p[4] = top + (dirForward + dirRight) * sideSize; // front‑right‑top + p[5] = top + (dirForward - dirRight) * sideSize; // front‑left‑top + p[6] = top + (-dirForward + dirRight) * sideSize; // back‑right‑top + p[7] = top + (-dirForward - dirRight) * sideSize; // back‑left‑top + + std::array>, 12> poly; - points[0] = bottom + (dirForward + dirRight) * sideSize; - points[1] = bottom + (dirForward - dirRight) * sideSize; + // bottom face (+Y up ⇒ wind CW when viewed from above) + poly[0] = {p[0], p[2], p[3]}; + poly[1] = {p[0], p[3], p[1]}; - points[2] = bottom + (-dirForward + dirRight) * sideSize; - points[3] = bottom + (-dirForward - dirRight) * sideSize; + // top face + poly[2] = {p[4], p[7], p[6]}; + poly[3] = {p[4], p[5], p[7]}; - points[4] = top + (dirForward + dirRight) * sideSize; - points[5] = top + (dirForward - dirRight) * sideSize; + // front face + poly[4] = {p[0], p[5], p[1]}; + poly[5] = {p[0], p[4], p[5]}; - points[6] = top + (-dirForward + dirRight) * sideSize; - points[7] = top + (-dirForward - dirRight) * sideSize; + // right face + poly[6] = {p[0], p[6], p[2]}; + poly[7] = {p[0], p[4], p[6]}; + // back face + poly[8] = {p[2], p[7], p[3]}; + poly[9] = {p[2], p[6], p[7]}; - std::array>, 8> polygons; + // left face + poly[10] = {p[1], p[7], p[5]}; + poly[11] = {p[1], p[3], p[7]}; - polygons[0] = {points[0], points[2], points[3]}; - return polygons; + return poly; } } diff --git a/source/collision/line_tracer.cpp b/source/collision/line_tracer.cpp index 0dfc7f54..2d559568 100644 --- a/source/collision/line_tracer.cpp +++ b/source/collision/line_tracer.cpp @@ -54,7 +54,12 @@ namespace omath::collision const auto tHit = sideB.Dot(q) * invDet; - if (tHit <= kEpsilon) + if (ray.infinite_length) + { + if (tHit <= kEpsilon) + return ray.end; + } + else if (tHit < 0.0f || tHit > 1.0f) return ray.end; return ray.start + rayDir * tHit; From f8202b116d8554c819d8f874183d1e0e17db4ce2 Mon Sep 17 00:00:00 2001 From: Vladislav Alpatov Date: Fri, 18 Apr 2025 16:00:23 +0300 Subject: [PATCH 263/795] improved line tracer --- source/collision/line_tracer.cpp | 2 +- tests/general/unit_test_line_trace.cpp | 124 ++++++++++++++----------- 2 files changed, 73 insertions(+), 53 deletions(-) diff --git a/source/collision/line_tracer.cpp b/source/collision/line_tracer.cpp index 2d559568..74aa308c 100644 --- a/source/collision/line_tracer.cpp +++ b/source/collision/line_tracer.cpp @@ -59,7 +59,7 @@ namespace omath::collision if (tHit <= kEpsilon) return ray.end; } - else if (tHit < 0.0f || tHit > 1.0f) + else if (tHit <= kEpsilon || tHit >= 1.0f - kEpsilon) return ray.end; return ray.start + rayDir * tHit; diff --git a/tests/general/unit_test_line_trace.cpp b/tests/general/unit_test_line_trace.cpp index c5fc23b5..600a18ba 100644 --- a/tests/general/unit_test_line_trace.cpp +++ b/tests/general/unit_test_line_trace.cpp @@ -1,80 +1,100 @@ +// +// Revised unit‑test suite for LineTracer (segment‑based Möller–Trumbore) +// Pure ASCII: avoids non‑standard characters that MSVC rejects. +// #include "gtest/gtest.h" #include "omath/collision/line_tracer.hpp" #include "omath/triangle.hpp" #include "omath/vector3.hpp" +#include using namespace omath; using namespace omath::collision; -class LineTracerTest : public ::testing::Test -{ -protected: - // Set up common variables for use in each test - Vector3 vertex1{0.0f, 0.0f, 0.0f}; - Vector3 vertex2{1.0f, 0.0f, 0.0f}; - Vector3 vertex3{0.0f, 1.0f, 0.0f}; - Triangle> triangle{vertex1, vertex2, vertex3}; -}; +using Vec3 = Vector3; -// Test that a ray intersecting the triangle returns false for CanTraceLine -TEST_F(LineTracerTest, RayIntersectsTriangle) -{ - constexpr Ray ray{{0.3f, 0.3f, -1.0f}, {0.3f, 0.3f, 1.0f}}; - EXPECT_FALSE(LineTracer::CanTraceLine(ray, triangle)); -} +namespace { -// Test that a ray parallel to the triangle plane returns true for CanTraceLine -TEST_F(LineTracerTest, RayParallelToTriangle) -{ - constexpr Ray ray{{0.3f, 0.3f, 1.0f}, {0.3f, 0.3f, 2.0f}}; - EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); -} +// ----------------------------------------------------------------------------- +// Constants & helpers +// ----------------------------------------------------------------------------- +constexpr float kTol = 1e-5f; -// Test that a ray starting inside the triangle but pointing away returns true -TEST_F(LineTracerTest, RayStartsInTriangleButDoesNotIntersect) +bool VecEqual(const Vec3& a, const Vec3& b, float tol = kTol) { - constexpr Ray ray{{0.3f, 0.3f, 0.0f}, {0.3f, 0.3f, -1.0f}}; - EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); + return std::fabs(a.x - b.x) < tol && + std::fabs(a.y - b.y) < tol && + std::fabs(a.z - b.z) < tol; } -// Test that a ray not intersecting the triangle plane returns true -TEST_F(LineTracerTest, RayMissesTriangle) +// ----------------------------------------------------------------------------- +// Fixture with one canonical right‑angled triangle in the XY plane. +// ----------------------------------------------------------------------------- +class LineTracerFixture : public ::testing::Test { - constexpr Ray ray{{2.0f, 2.0f, -1.0f}, {2.0f, 2.0f, 1.0f}}; - EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); -} +protected: + LineTracerFixture() + : triangle({0.f, 0.f, 0.f}, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}) {} -// Test that a ray lying exactly in the plane of the triangle without intersecting returns true -TEST_F(LineTracerTest, RayInPlaneNotIntersecting) + Triangle triangle; +}; + +// ----------------------------------------------------------------------------- +// Data‑driven tests for CanTraceLine +// ----------------------------------------------------------------------------- +struct TraceCase { - constexpr Ray ray{{-1.0f, -1.0f, 0.0f}, {1.5f, 1.5f, 0.0f}}; - EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); -} + Ray ray; + bool expected_clear; // true => segment does NOT hit the triangle +}; +class CanTraceLineParam : public LineTracerFixture, + public ::testing::WithParamInterface {}; -TEST_F(LineTracerTest, RayIntersectsVertex) +TEST_P(CanTraceLineParam, VariousRays) { - const Ray ray{{-1.0f, -1.0f, -1.0f}, vertex1}; // Intersecting at vertex1 - EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); + const auto& p = GetParam(); + EXPECT_EQ(LineTracer::CanTraceLine(p.ray, triangle), p.expected_clear); } -TEST_F(LineTracerTest, RayIntersectsEdge) +INSTANTIATE_TEST_SUITE_P( + BasicScenarios, + CanTraceLineParam, + ::testing::Values( + TraceCase{Ray{{ 0.3f, 0.3f, -1.f},{ 0.3f, 0.3f, 1.f}}, false}, // hit through centre + TraceCase{Ray{{ 0.3f, 0.3f, 1.f},{ 0.3f, 0.3f, 2.f}}, true}, // parallel above + TraceCase{Ray{{ 0.3f, 0.3f, 0.f},{ 0.3f, 0.3f,-1.f}}, true}, // starts inside, goes away + TraceCase{Ray{{ 2.0f, 2.0f, -1.f},{ 2.0f, 2.0f, 1.f}}, true}, // misses entirely + TraceCase{Ray{{-1.0f,-1.0f, 0.f},{ 1.5f, 1.5f, 0.f}},true}, // lies in plane, outside tri + TraceCase{Ray{{-1.0f,-1.0f, -1.f},{ 0.0f, 0.0f, 0.f}}, true}, // endpoint on vertex + TraceCase{Ray{{-1.0f, 0.0f, -1.f},{ 0.5f, 0.0f, 0.f}}, true} // endpoint on edge + ) +); + +// ----------------------------------------------------------------------------- +// Validate that the reported hit point is correct for a genuine intersection. +// ----------------------------------------------------------------------------- +TEST_F(LineTracerFixture, HitPointCorrect) { - constexpr Ray ray{{-1.0f, 0.0f, -1.0f}, {0.5f, 0.0f, 0.0f}}; - // Intersecting on the edge between vertex1 and vertex2 - EXPECT_TRUE(LineTracer::CanTraceLine(ray, triangle)); + Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}}; + Vec3 expected{0.3f, 0.3f, 0.f}; + + Vec3 hit = LineTracer::GetRayHitPoint(ray, triangle); + ASSERT_FALSE(VecEqual(hit, ray.end)); + EXPECT_TRUE(VecEqual(hit, expected)); } -TEST_F(LineTracerTest, TriangleFarBeyondRayEndPoint) +// ----------------------------------------------------------------------------- +// Triangle far beyond the ray should not block. +// ----------------------------------------------------------------------------- +TEST_F(LineTracerFixture, DistantTriangleClear) { - // Define a ray with a short length - constexpr Ray ray{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}; + Ray short_ray{{0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}}; + Triangle distant{{1000.f,1000.f,1000.f}, + {1001.f,1000.f,1000.f}, + {1000.f,1001.f,1000.f}}; - // Define a triangle far beyond the ray's endpoint - constexpr Triangle> distantTriangle{ - {1000.0f, 1000.0f, 1000.0f}, {1001.0f, 1000.0f, 1000.0f}, {1000.0f, 1001.0f, 1000.0f} - }; - - // Expect true because the ray ends long before it could reach the distant triangle - EXPECT_TRUE(LineTracer::CanTraceLine(ray, distantTriangle)); + EXPECT_TRUE(LineTracer::CanTraceLine(short_ray, distant)); } + +} // namespace From 9c934c5d9cc2c0b2b8bac6efc708fbf64fc7959e Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 18 Apr 2025 16:36:22 +0300 Subject: [PATCH 264/795] improved tests --- source/collision/line_tracer.cpp | 2 +- tests/general/unit_test_line_trace.cpp | 185 ++++++++++++++----------- 2 files changed, 104 insertions(+), 83 deletions(-) diff --git a/source/collision/line_tracer.cpp b/source/collision/line_tracer.cpp index 74aa308c..5bf78375 100644 --- a/source/collision/line_tracer.cpp +++ b/source/collision/line_tracer.cpp @@ -59,7 +59,7 @@ namespace omath::collision if (tHit <= kEpsilon) return ray.end; } - else if (tHit <= kEpsilon || tHit >= 1.0f - kEpsilon) + else if (tHit <= kEpsilon || tHit > 1.0f - kEpsilon) return ray.end; return ray.start + rayDir * tHit; diff --git a/tests/general/unit_test_line_trace.cpp b/tests/general/unit_test_line_trace.cpp index 600a18ba..9472494a 100644 --- a/tests/general/unit_test_line_trace.cpp +++ b/tests/general/unit_test_line_trace.cpp @@ -13,88 +13,109 @@ using namespace omath::collision; using Vec3 = Vector3; -namespace { - -// ----------------------------------------------------------------------------- -// Constants & helpers -// ----------------------------------------------------------------------------- -constexpr float kTol = 1e-5f; - -bool VecEqual(const Vec3& a, const Vec3& b, float tol = kTol) -{ - return std::fabs(a.x - b.x) < tol && - std::fabs(a.y - b.y) < tol && - std::fabs(a.z - b.z) < tol; -} - -// ----------------------------------------------------------------------------- -// Fixture with one canonical right‑angled triangle in the XY plane. -// ----------------------------------------------------------------------------- -class LineTracerFixture : public ::testing::Test -{ -protected: - LineTracerFixture() - : triangle({0.f, 0.f, 0.f}, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}) {} - - Triangle triangle; -}; - -// ----------------------------------------------------------------------------- -// Data‑driven tests for CanTraceLine -// ----------------------------------------------------------------------------- -struct TraceCase +namespace { - Ray ray; - bool expected_clear; // true => segment does NOT hit the triangle -}; - -class CanTraceLineParam : public LineTracerFixture, - public ::testing::WithParamInterface {}; - -TEST_P(CanTraceLineParam, VariousRays) -{ - const auto& p = GetParam(); - EXPECT_EQ(LineTracer::CanTraceLine(p.ray, triangle), p.expected_clear); -} - -INSTANTIATE_TEST_SUITE_P( - BasicScenarios, - CanTraceLineParam, - ::testing::Values( - TraceCase{Ray{{ 0.3f, 0.3f, -1.f},{ 0.3f, 0.3f, 1.f}}, false}, // hit through centre - TraceCase{Ray{{ 0.3f, 0.3f, 1.f},{ 0.3f, 0.3f, 2.f}}, true}, // parallel above - TraceCase{Ray{{ 0.3f, 0.3f, 0.f},{ 0.3f, 0.3f,-1.f}}, true}, // starts inside, goes away - TraceCase{Ray{{ 2.0f, 2.0f, -1.f},{ 2.0f, 2.0f, 1.f}}, true}, // misses entirely - TraceCase{Ray{{-1.0f,-1.0f, 0.f},{ 1.5f, 1.5f, 0.f}},true}, // lies in plane, outside tri - TraceCase{Ray{{-1.0f,-1.0f, -1.f},{ 0.0f, 0.0f, 0.f}}, true}, // endpoint on vertex - TraceCase{Ray{{-1.0f, 0.0f, -1.f},{ 0.5f, 0.0f, 0.f}}, true} // endpoint on edge - ) -); - -// ----------------------------------------------------------------------------- -// Validate that the reported hit point is correct for a genuine intersection. -// ----------------------------------------------------------------------------- -TEST_F(LineTracerFixture, HitPointCorrect) -{ - Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}}; - Vec3 expected{0.3f, 0.3f, 0.f}; - - Vec3 hit = LineTracer::GetRayHitPoint(ray, triangle); - ASSERT_FALSE(VecEqual(hit, ray.end)); - EXPECT_TRUE(VecEqual(hit, expected)); -} - -// ----------------------------------------------------------------------------- -// Triangle far beyond the ray should not block. -// ----------------------------------------------------------------------------- -TEST_F(LineTracerFixture, DistantTriangleClear) -{ - Ray short_ray{{0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}}; - Triangle distant{{1000.f,1000.f,1000.f}, - {1001.f,1000.f,1000.f}, - {1000.f,1001.f,1000.f}}; - - EXPECT_TRUE(LineTracer::CanTraceLine(short_ray, distant)); -} + // ----------------------------------------------------------------------------- + // Constants & helpers + // ----------------------------------------------------------------------------- + constexpr float kTol = 1e-5f; + + bool VecEqual(const Vec3& a, const Vec3& b, float tol = kTol) + { + return std::fabs(a.x - b.x) < tol && + std::fabs(a.y - b.y) < tol && + std::fabs(a.z - b.z) < tol; + } + + // ----------------------------------------------------------------------------- + // Fixture with one canonical right‑angled triangle in the XY plane. + // ----------------------------------------------------------------------------- + class LineTracerFixture : public ::testing::Test + { + protected: + LineTracerFixture() : + triangle({0.f, 0.f, 0.f}, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}) + { + } + + Triangle triangle; + }; + + // ----------------------------------------------------------------------------- + // Data‑driven tests for CanTraceLine + // ----------------------------------------------------------------------------- + struct TraceCase + { + Ray ray; + bool expected_clear; // true => segment does NOT hit the triangle + }; + + class CanTraceLineParam : public LineTracerFixture, + public ::testing::WithParamInterface + { + }; + + TEST_P(CanTraceLineParam, VariousRays) + { + const auto& p = GetParam(); + EXPECT_EQ(LineTracer::CanTraceLine(p.ray, triangle), p.expected_clear); + } + + INSTANTIATE_TEST_SUITE_P( + BasicScenarios, + CanTraceLineParam, + ::testing::Values( + TraceCase{Ray{{ 0.3f, 0.3f, -1.f},{ 0.3f, 0.3f, 1.f}}, false}, // hit through centre + TraceCase{Ray{{ 0.3f, 0.3f, 1.f},{ 0.3f, 0.3f, 2.f}}, true}, // parallel above + TraceCase{Ray{{ 0.3f, 0.3f, 0.f},{ 0.3f, 0.3f,-1.f}}, true}, // starts inside, goes away + TraceCase{Ray{{ 2.0f, 2.0f, -1.f},{ 2.0f, 2.0f, 1.f}}, true}, // misses entirely + TraceCase{Ray{{-1.0f,-1.0f, 0.f},{ 1.5f, 1.5f, 0.f}},true}, // lies in plane, outside tri + TraceCase{Ray{{-1.0f,-1.0f, -1.f},{ 0.0f, 0.0f, 0.f}}, true}, // endpoint on vertex + TraceCase{Ray{{-1.0f, 0.0f, -1.f},{ 0.5f, 0.0f, 0.f}}, true} // endpoint on edge + ) + ); + + // ----------------------------------------------------------------------------- + // Validate that the reported hit point is correct for a genuine intersection. + // ----------------------------------------------------------------------------- + TEST_F(LineTracerFixture, HitPointCorrect) + { + constexpr Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}}; + constexpr Vec3 expected{0.3f, 0.3f, 0.f}; + + const Vec3 hit = LineTracer::GetRayHitPoint(ray, triangle); + ASSERT_FALSE(VecEqual(hit, ray.end)); + EXPECT_TRUE(VecEqual(hit, expected)); + } + + // ----------------------------------------------------------------------------- + // Triangle far beyond the ray should not block. + // ----------------------------------------------------------------------------- + TEST_F(LineTracerFixture, DistantTriangleClear) + { + constexpr Ray short_ray{{0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}}; + constexpr Triangle distant{{1000.f, 1000.f, 1000.f}, + {1001.f, 1000.f, 1000.f}, + {1000.f, 1001.f, 1000.f}}; + + EXPECT_TRUE(LineTracer::CanTraceLine(short_ray, distant)); + } + + TEST(LineTracerTraceRayEdge, CantHit) + { + constexpr omath::Triangle> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}}; + + constexpr Ray ray{{}, {1.0, 0, 0}, false}; + + EXPECT_TRUE(omath::collision::LineTracer::CanTraceLine(ray, triangle)); + } + TEST(LineTracerTraceRayEdge, CanHit) + { + constexpr omath::Triangle> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}}; + + constexpr Ray ray{{}, {2.1, 0, 0}, false}; + auto endPoint = omath::collision::LineTracer::GetRayHitPoint(ray, triangle); + EXPECT_FALSE(omath::collision::LineTracer::CanTraceLine(ray, triangle)); + } } // namespace From c692cf39e1a31b87da12e4e77f210c2b6c3031a5 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 18 Apr 2025 16:53:53 +0300 Subject: [PATCH 265/795] updated readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 02cfeb8e..21dc8ec6 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,8 @@ With `omath/projection` module you can achieve simple ESP hack for powered by So ![banner](https://i.imgur.com/lcJrfcZ.png) Or for InfinityWard Engine based games. Like Call of Duty Black Ops 2! ![banner](https://i.imgur.com/F8dmdoo.png) - +Or create simple trigger bot with embeded traceline from omath::collision::LineTrace +![banner](https://i.imgur.com/fxMjRKo.jpeg) Or even advanced projectile aimbot [Watch Video](https://youtu.be/lM_NJ1yCunw?si=5E87OrQMeypxSJ3E) From d14cb1e93eb70913703ad35c5bee53faa23b616b Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 18 Apr 2025 16:55:07 +0300 Subject: [PATCH 266/795] reset to default --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a51519c2..5dfad3e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(omath VERSION 1.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) -option(OMATH_BUILD_TESTS "Build unit tests" ON) +option(OMATH_BUILD_TESTS "Build unit tests" OFF) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) @@ -13,7 +13,7 @@ option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF) option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF) option(OMATH_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build to improve general performance" ON) -option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" OFF) +option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" ON) if (OMATH_BUILD_AS_SHARED_LIBRARY) add_library(omath SHARED source/matrix.cpp) From 3e67d8a99ce40e1cf57365897a75d7edbf597c95 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 23 Apr 2025 02:46:08 +0300 Subject: [PATCH 267/795] added credits --- CREDITS.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 CREDITS.md diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 00000000..ff9b60a0 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,11 @@ +# OMATH CREDITS + +Thanks to everyone who made this possible, including: + +- Saikari aka luadebug for VCPKG port. + +And a big hand to everyone else who has contributed over the past! + +THANKS! <3 + + -- Orange++ \ No newline at end of file From 94b1453cae548f26c42346c3d7d641cf50bac9aa Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 23 Apr 2025 02:48:32 +0300 Subject: [PATCH 268/795] removed .idea folder --- .idea/modules.xml | 8 -------- .idea/uml.iml | 2 -- .idea/vcs.xml | 7 ------- 3 files changed, 17 deletions(-) delete mode 100644 .idea/modules.xml delete mode 100644 .idea/uml.iml delete mode 100644 .idea/vcs.xml diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index a572362a..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/uml.iml b/.idea/uml.iml deleted file mode 100644 index f08604bb..00000000 --- a/.idea/uml.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index adc159a8..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file From d7f1f4916556618eceaf025885d13ece64e0ab17 Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 25 Apr 2025 23:52:10 +0300 Subject: [PATCH 269/795] resetting state --- include/omath/projection/camera.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index 7c3473bd..ca7b19d5 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -62,31 +62,37 @@ namespace omath::projection void SetFieldOfView(const FieldOfView& fov) { m_fieldOfView = fov; + m_viewProjectionMatrix = std::nullopt; } void SetNearPlane(const float near) { m_nearPlaneDistance = near; + m_viewProjectionMatrix = std::nullopt; } void SetFarPlane(const float far) { m_farPlaneDistance = far; + m_viewProjectionMatrix = std::nullopt; } void SetViewAngles(const ViewAnglesType& viewAngles) { m_viewAngles = viewAngles; + m_viewProjectionMatrix = std::nullopt; } void SetOrigin(const Vector3& origin) { m_origin = origin; + m_viewProjectionMatrix = std::nullopt; } void SetViewPort(const ViewPort& viewPort) { m_viewPort = viewPort; + m_viewProjectionMatrix = std::nullopt; } [[nodiscard]] const FieldOfView& GetFieldOfView() const From 2734b58bdd689848cc71c4bb8d638515519dfcbc Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 26 Apr 2025 00:32:53 +0300 Subject: [PATCH 270/795] fixed gimba lock for opengl camera --- source/engines/opengl_engine/formulas.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/engines/opengl_engine/formulas.cpp b/source/engines/opengl_engine/formulas.cpp index f3e5b5ce..316ed118 100644 --- a/source/engines/opengl_engine/formulas.cpp +++ b/source/engines/opengl_engine/formulas.cpp @@ -32,9 +32,9 @@ namespace omath::opengl_engine } Mat4x4 RotationMatrix(const ViewAngles& angles) { - return MatRotationAxisZ(angles.roll) * + return MatRotationAxisX(-angles.pitch) * MatRotationAxisY(-angles.yaw) * - MatRotationAxisX(-angles.pitch); + MatRotationAxisZ(angles.roll); } Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) From 69b9049fb0e37d121e1b03fb4f53f1de01c5abd7 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 26 Apr 2025 00:52:46 +0300 Subject: [PATCH 271/795] fixed gimba lock for unity --- include/omath/mat.hpp | 2 +- source/engines/unity_engine/formulas.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 1d7b4511..47773c61 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -294,7 +294,7 @@ namespace omath [[nodiscard]] constexpr std::array& RawArray() { - return const_cast>(std::as_const(*this).RawArray()); + return m_data; } [[nodiscard]] diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp index 798920d4..4477253a 100644 --- a/source/engines/unity_engine/formulas.cpp +++ b/source/engines/unity_engine/formulas.cpp @@ -32,9 +32,9 @@ namespace omath::unity_engine } Mat4x4 RotationMatrix(const ViewAngles& angles) { - return MatRotationAxisZ(angles.roll) * - MatRotationAxisY(angles.yaw) * - MatRotationAxisX(angles.pitch); + return MatRotationAxisX(angles.pitch) * + MatRotationAxisY(angles.yaw) * + MatRotationAxisZ(angles.roll); } Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) From 4ef674f7b46dcbc5bfc8660ad2ad2bb82d91c7d1 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 29 Apr 2025 20:08:27 +0300 Subject: [PATCH 272/795] fixed infinite recursion in compile time --- include/omath/mat.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 47773c61..07ae9133 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -253,7 +253,8 @@ namespace omath if constexpr (Rows == 2) return At(0, 0) * At(1, 1) - At(0, 1) * At(1, 0); - else + + if constexpr (Rows > 2) { Type det = 0; for (size_t i = 0; i < Columns; ++i) @@ -263,11 +264,13 @@ namespace omath } return det; } + std::unreachable(); } [[nodiscard]] constexpr Mat Minor(const size_t row, const size_t column) const { + static_assert(Rows-1 > 0 && Columns-1 > 0); Mat result; for (size_t i = 0, m = 0; i < Rows; ++i) { From 46157696822bcce37b4c802ac7d2bb87b637315f Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 29 Apr 2025 20:10:17 +0300 Subject: [PATCH 273/795] added additional methods --- include/omath/mat.hpp | 19 ++++++++++++++++--- tests/general/unit_test_mat.cpp | 4 ++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 07ae9133..9bb653f0 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -257,9 +257,9 @@ namespace omath if constexpr (Rows > 2) { Type det = 0; - for (size_t i = 0; i < Columns; ++i) + for (size_t column = 0; column < Columns; ++column) { - const Type cofactor = (i % 2 == 0 ? 1 : -1) * At(0, i) * Minor(0, i).Determinant(); + const Type cofactor = At(0, column) * AlgComplement(0, column); det += cofactor; } return det; @@ -268,7 +268,7 @@ namespace omath } [[nodiscard]] - constexpr Mat Minor(const size_t row, const size_t column) const + constexpr Mat Strip(const size_t row, const size_t column) const { static_assert(Rows-1 > 0 && Columns-1 > 0); Mat result; @@ -288,6 +288,19 @@ namespace omath return result; } + [[nodiscard]] + constexpr Type Minor(const size_t row, const size_t column) const + { + return Strip(row, column).Determinant(); + } + + [[nodiscard]] + constexpr Type AlgComplement(const size_t row, const size_t column) const + { + const auto minor = Minor(row, column); + return (row + column + 2) % 2 == 0 ? minor: -minor; + } + [[nodiscard]] constexpr const std::array& RawArray() const { diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index 43b25c18..ec60eb3d 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -180,10 +180,10 @@ TEST(UnitTestMatStandalone, Determinant_3x3) } // Test Minor for 3x3 matrix -TEST(UnitTestMatStandalone, Minor_3x3) +TEST(UnitTestMatStandalone, Strip_3x3) { constexpr Mat<3, 3> m{{3, 0, 2}, {2, 0, -2}, {0, 1, 1}}; - auto minor = m.Minor(0, 0); + auto minor = m.Strip(0, 0); EXPECT_EQ(minor.RowCount(), 2); EXPECT_EQ(minor.ColumnsCount(), 2); EXPECT_FLOAT_EQ(minor.At(0, 0), 0.0f); From 1c5c9360c80a5cbb318af45e6bcafd44da4f5823 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 29 Apr 2025 20:33:39 +0300 Subject: [PATCH 274/795] added inverse method --- include/omath/mat.hpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 9bb653f0..93d3771a 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -359,6 +359,25 @@ namespace omath }; } + [[nodiscard]] + constexpr std::optional Inverted() const + { + const auto det = Determinant(); + + if (det == 0) + return std::nullopt; + + const auto transposed = Transposed(); + Mat result; + + for (std::size_t row = 0; row < Rows; row++) + for (std::size_t column = 0; column < Rows; column++) + result.At(row, column) = transposed.AlgComplement(row, column); + + result /= det; + + return {result}; + } private: std::array m_data; }; From a0d1dc4313a4df9618836b195ce6426c97501650 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 29 Apr 2025 20:49:59 +0300 Subject: [PATCH 275/795] added test case --- include/omath/mat.hpp | 2 +- tests/general/unit_test_mat.cpp | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 93d3771a..c4187023 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -375,7 +375,7 @@ namespace omath result.At(row, column) = transposed.AlgComplement(row, column); result /= det; - + return {result}; } private: diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index ec60eb3d..abf6f1ed 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -206,3 +206,12 @@ TEST(UnitTestMatStandalone, Transpose_NonSquare) EXPECT_FLOAT_EQ(transposed.At(1, 1), 5.0f); EXPECT_FLOAT_EQ(transposed.At(2, 1), 6.0f); } + +TEST(UnitTestMatStandalone, Enverse) +{ + + constexpr Mat<2, 2> m{{1.0f, 3.0f}, {2.0f, 5.0f}}; + constexpr Mat<2,2> mv{{-5.0f, 3.0f}, {2.0f, -1.0f}}; + + EXPECT_EQ(mv, m.Inverted()); +} From a41526c494580bf2b3aeadf8bf221577b788b7dd Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 29 Apr 2025 20:52:41 +0300 Subject: [PATCH 276/795] style fix --- tests/general/unit_test_mat.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index abf6f1ed..7fc25f6c 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -209,7 +209,6 @@ TEST(UnitTestMatStandalone, Transpose_NonSquare) TEST(UnitTestMatStandalone, Enverse) { - constexpr Mat<2, 2> m{{1.0f, 3.0f}, {2.0f, 5.0f}}; constexpr Mat<2,2> mv{{-5.0f, 3.0f}, {2.0f, -1.0f}}; From faeef594b9137b29d944bd7d7418c0406a1a34f6 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 30 Apr 2025 18:15:46 +0300 Subject: [PATCH 277/795] moved installation stuff to INSTALL.md --- INSTALL.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 35 ++--------------------------------- 2 files changed, 56 insertions(+), 33 deletions(-) create mode 100644 INSTALL.md diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000..2725ae9b --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,54 @@ +# 📥Installation Guide + +## Using vcpkg +**Note**: Support vcpkg for package management +1. Install [vcpkg](https://github.com/microsoft/vcpkg) +2. Run the following command to install the orange-math package: +``` +vcpkg install orange-math +``` +CMakeLists.txt +```cmake +find_package(omath CONFIG REQUIRED) +target_link_libraries(main PRIVATE omath::omath) +``` +For detailed commands on installing different versions and more information, please refer to Microsoft's [official instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/overview). + +## Build from source using CMake +1. **Preparation** + + Install needed tools: cmake, clang, git, msvc (windows only). + + 1. **Linux:** + ```bash + sudo pacman -Sy cmake ninja clang git + ``` + 2. **MacOS:** + ```bash + brew install llvm git cmake ninja + ``` + 3. **Windows:** + + Install Visual Studio from [here](https://visualstudio.microsoft.com/downloads/) and Git from [here](https://git-scm.com/downloads). + + Use x64 Native Tools shell to execute needed commands down below. +2. **Clone the repository:** + ```bash + git clone https://github.com/orange-cpp/omath.git + ``` +3. **Navigate to the project directory:** + ```bash + cd omath + ``` +4. **Build the project using CMake:** + ```bash + cmake --preset windows-release -S . + cmake --build cmake-build/build/windows-release --target omath -j 6 + ``` + Use **\-\** preset to build siutable version for yourself. Like **windows-release** or **linux-release**. + + | Platform Name | Build Config | + |---------------|---------------| + | windows | release/debug | + | linux | release/debug | + | darwin | release/debug | diff --git a/README.md b/README.md index 21dc8ec6..0822b339 100644 --- a/README.md +++ b/README.md @@ -37,40 +37,9 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at | Linux | ✅YES | | Darwin (MacOS) | ✅YES | -## ⏬ Getting Started -### Prerequisites -- C++ Compiler -- CMake (for building the project) +## ⏬ Installation +Please read our [installation guide](https://github.com/orange-cpp/omath/blob/main/INSTALL.md). If this link doesn't work check out INSTALL.md file. -### Installation -### vcpkg -**Note**: Support vcpkg for package management -1. Install vcpkg (https://github.com/microsoft/vcpkg) -2. Run the following command to install the orange-math package: -``` -vcpkg install orange-math -``` -CMakeLists.txt -```cmake -find_package(omath CONFIG REQUIRED) -target_link_libraries(main PRIVATE omath::omath) -``` -For detailed commands on installing different versions and more information, please refer to Microsoft's official instructions (https://learn.microsoft.com/en-us/vcpkg/get_started/overview) -### Build from source -1. Clone the repository: - ``` - git clone https://github.com/orange-cpp/omath.git - ``` -2. Navigate to the project directory: - ``` - cd omath - ``` -3. Build the project using CMake: - ``` - cmake --preset windows-release -S . - cmake --build cmake-build/build/windows-release --target omath -j 6 - ``` - Use **\-\** preset to build siutable version for yourself. Like **windows-release** or **linux-release**. ## ❔ Usage Simple world to screen function ```c++ From 998c8f3a4331eb875ca62441220f7631562d79c0 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 30 Apr 2025 21:26:25 +0300 Subject: [PATCH 278/795] improved cmake, removed useless cmake files --- CMakeLists.txt | 44 +++++++++++---------- source/3d_primitives/CMakeLists.txt | 1 - source/CMakeLists.txt | 11 ------ source/collision/CMakeLists.txt | 3 -- source/engines/CMakeLists.txt | 4 -- source/engines/iw_engine/CMakeLists.txt | 1 - source/engines/opengl_engine/CMakeLists.txt | 1 - source/engines/source_engine/CMakeLists.txt | 1 - source/engines/unity_engine/CMakeLists.txt | 1 - source/pathfinding/CMakeLists.txt | 1 - source/projectile_prediction/CMakeLists.txt | 1 - source/projection/CMakeLists.txt | 1 - tests/CMakeLists.txt | 32 +++------------ tests/engines/unit_test_unity_engine.cpp | 2 +- 14 files changed, 30 insertions(+), 74 deletions(-) delete mode 100644 source/3d_primitives/CMakeLists.txt delete mode 100644 source/CMakeLists.txt delete mode 100644 source/collision/CMakeLists.txt delete mode 100644 source/engines/CMakeLists.txt delete mode 100644 source/engines/iw_engine/CMakeLists.txt delete mode 100644 source/engines/opengl_engine/CMakeLists.txt delete mode 100644 source/engines/source_engine/CMakeLists.txt delete mode 100644 source/engines/unity_engine/CMakeLists.txt delete mode 100644 source/pathfinding/CMakeLists.txt delete mode 100644 source/projectile_prediction/CMakeLists.txt delete mode 100644 source/projection/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dfad3e4..d4190b1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,15 +11,19 @@ option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" OFF) option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF) -option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF) +option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF) option(OMATH_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build to improve general performance" ON) option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" ON) +file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") +file(GLOB_RECURSE OMATH_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp") + + if (OMATH_BUILD_AS_SHARED_LIBRARY) - add_library(omath SHARED source/matrix.cpp) -else() - add_library(omath STATIC source/matrix.cpp) -endif() + add_library(omath SHARED ${OMATH_SOURCES} ${OMATH_HEADERS}) +else () + add_library(omath STATIC ${OMATH_SOURCES} ${OMATH_HEADERS}) +endif () message(STATUS "Building on ${CMAKE_HOST_SYSTEM_NAME}") add_library(omath::omath ALIAS omath) @@ -28,28 +32,28 @@ if (OMATH_IMGUI_INTEGRATION) target_compile_definitions(omath PUBLIC OMATH_IMGUI_INTEGRATION) # IMGUI is being linked as submodule - if(TARGET imgui) + if (TARGET imgui) target_link_libraries(omath PUBLIC imgui) install(TARGETS imgui EXPORT omathTargets ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) - else() + else () # Assume that IMGUI linked via VCPKG. find_package(imgui CONFIG REQUIRED) target_link_libraries(omath PUBLIC imgui::imgui) - endif() + endif () -endif() +endif () if (OMATH_USE_AVX2) target_compile_definitions(omath PUBLIC OMATH_USE_AVX2) -endif() +endif () if (OMATH_SUPRESS_SAFETY_CHECKS) target_compile_definitions(omath PUBLIC OMATH_SUPRESS_SAFETY_CHECKS) -endif() +endif () set_target_properties(omath PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" @@ -61,37 +65,35 @@ if (OMATH_USE_UNITY_BUILD) set_target_properties(omath PROPERTIES UNITY_BUILD ON UNITY_BUILD_BATCH_SIZE 20) -endif() +endif () if (OMATH_STATIC_MSVC_RUNTIME_LIBRARY) set_target_properties(omath PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" ) -endif() +endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") target_compile_options(omath PRIVATE -mavx2 -mfma) -endif() +endif () target_compile_features(omath PUBLIC cxx_std_23) -add_subdirectory(source) - -if(OMATH_BUILD_TESTS) +if (OMATH_BUILD_TESTS) add_subdirectory(extlibs) add_subdirectory(tests) -endif() +endif () if (OMATH_BUILD_EXAMPLES) add_subdirectory(examples) -endif() +endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND OMATH_THREAT_WARNING_AS_ERROR) target_compile_options(omath PRIVATE /W4 /WX) -elseif(OMATH_THREAT_WARNING_AS_ERROR) +elseif (OMATH_THREAT_WARNING_AS_ERROR) target_compile_options(omath PRIVATE -Wall -Wextra -Wpedantic -Werror) -endif() +endif () target_include_directories(omath PUBLIC diff --git a/source/3d_primitives/CMakeLists.txt b/source/3d_primitives/CMakeLists.txt deleted file mode 100644 index ef4e0a46..00000000 --- a/source/3d_primitives/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -target_sources(omath PRIVATE box.cpp) \ No newline at end of file diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt deleted file mode 100644 index 1053134c..00000000 --- a/source/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -target_sources(omath PRIVATE - matrix.cpp - color.cpp -) - -add_subdirectory(projectile_prediction) -add_subdirectory(pathfinding) -add_subdirectory(projection) -add_subdirectory(collision) -add_subdirectory(engines) -add_subdirectory(3d_primitives) \ No newline at end of file diff --git a/source/collision/CMakeLists.txt b/source/collision/CMakeLists.txt deleted file mode 100644 index 09b4793a..00000000 --- a/source/collision/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -target_sources(omath PRIVATE - line_tracer.cpp -) diff --git a/source/engines/CMakeLists.txt b/source/engines/CMakeLists.txt deleted file mode 100644 index aea648a7..00000000 --- a/source/engines/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_subdirectory(source_engine) -add_subdirectory(opengl_engine) -add_subdirectory(iw_engine) -add_subdirectory(unity_engine) \ No newline at end of file diff --git a/source/engines/iw_engine/CMakeLists.txt b/source/engines/iw_engine/CMakeLists.txt deleted file mode 100644 index e5e97417..00000000 --- a/source/engines/iw_engine/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -target_sources(omath PRIVATE camera.cpp formulas.cpp) \ No newline at end of file diff --git a/source/engines/opengl_engine/CMakeLists.txt b/source/engines/opengl_engine/CMakeLists.txt deleted file mode 100644 index e5e97417..00000000 --- a/source/engines/opengl_engine/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -target_sources(omath PRIVATE camera.cpp formulas.cpp) \ No newline at end of file diff --git a/source/engines/source_engine/CMakeLists.txt b/source/engines/source_engine/CMakeLists.txt deleted file mode 100644 index e5e97417..00000000 --- a/source/engines/source_engine/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -target_sources(omath PRIVATE camera.cpp formulas.cpp) \ No newline at end of file diff --git a/source/engines/unity_engine/CMakeLists.txt b/source/engines/unity_engine/CMakeLists.txt deleted file mode 100644 index 5da7def7..00000000 --- a/source/engines/unity_engine/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -target_sources(omath PRIVATE formulas.cpp camera.cpp) \ No newline at end of file diff --git a/source/pathfinding/CMakeLists.txt b/source/pathfinding/CMakeLists.txt deleted file mode 100644 index e7cc7b93..00000000 --- a/source/pathfinding/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -target_sources(omath PRIVATE navigation_mesh.cpp a_star.cpp) \ No newline at end of file diff --git a/source/projectile_prediction/CMakeLists.txt b/source/projectile_prediction/CMakeLists.txt deleted file mode 100644 index 1f189f7f..00000000 --- a/source/projectile_prediction/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -target_sources(omath PRIVATE proj_pred_engine_legacy.cpp projectile.cpp target.cpp proj_pred_engine_avx2.cpp proj_pred_engine.cpp) \ No newline at end of file diff --git a/source/projection/CMakeLists.txt b/source/projection/CMakeLists.txt deleted file mode 100644 index 1ca3a279..00000000 --- a/source/projection/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -target_sources(omath PRIVATE camera.cpp) \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5f1c8e08..b72c6775 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,33 +1,13 @@ enable_testing() -project(unit-tests) +project(unit_tests) include(GoogleTest) -add_executable(unit-tests - general/unit_test_prediction.cpp - general/unit_test_matrix.cpp - general/unit_test_mat.cpp - general/unit_test_a_star.cpp - general/unit_test_projection.cpp - general/unit_test_vector3.cpp - general/unit_test_vector2.cpp - general/unit_test_color.cpp - general/unit_test_vector4.cpp - general/unit_test_line_trace.cpp - general/unit_test_angles.cpp - general/unit_test_view_angles.cpp - general/unit_test_angle.cpp - general/unit_test_triangle.cpp - general/unit_test_box_primitive.cpp - engines/unit_test_open_gl.cpp - engines/unit_test_unity_engine.cpp - engines/unit_test_source_engine.cpp - engines/unit_test_iw_engine.cpp +file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +add_executable(unit_tests ${UNIT_TESTS_SOURCES}) -) - -set_target_properties(unit-tests PROPERTIES +set_target_properties(unit_tests PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" UNITY_BUILD ON @@ -36,6 +16,6 @@ set_target_properties(unit-tests PROPERTIES CXX_STANDARD_REQUIRED ON) -target_link_libraries(unit-tests PRIVATE gtest gtest_main omath::omath) +target_link_libraries(unit_tests PRIVATE gtest gtest_main omath::omath) -gtest_discover_tests(unit-tests) \ No newline at end of file +gtest_discover_tests(unit_tests) \ 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 7fb95a28..b5a5e8e8 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -5,7 +5,7 @@ #include #include #include - +#include TEST(UnitTestUnityEngine, ForwardVector) { From dd731b60c34e069230485c0e479990d9ae096cb1 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 3 May 2025 16:39:38 +0300 Subject: [PATCH 279/795] updated clang format --- .clang-format | 82 ++++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/.clang-format b/.clang-format index d0745355..989123a2 100644 --- a/.clang-format +++ b/.clang-format @@ -1,62 +1,64 @@ -# Generated from CLion C/C++ Code Style settings ---- -Language: Cpp +# Generated by CLion for Stroustrup +# The Stroustrup style, named after Bjarne Stroustrup, the creator of C++, is similar to the K&R style but differs +# in its treatment of the class definitions and the placement of braces in certain contexts. The opening brace is +# placed on the same line as the control statement, and the closing brace is on its own line. BasedOnStyle: LLVM + AccessModifierOffset: -4 -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AlignOperands: true +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: AcrossEmptyLinesAndComments AlignTrailingComments: false -AllowShortBlocksOnASingleLine: false -AllowShortFunctionsOnASingleLine: None -AlwaysBreakTemplateDeclarations: Yes -BraceWrapping: +AllowShortBlocksOnASingleLine: Always +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakTemplateDeclarations: No +BreakBeforeBraces: Custom +BraceWrapping: AfterCaseLabel: true AfterClass: true + AfterFunction: true AfterControlStatement: true + SplitEmptyFunction: true AfterEnum: true - AfterFunction: true AfterNamespace: true AfterStruct: true AfterUnion: true AfterExternBlock: true - BeforeCatch: false - BeforeElse: false + BeforeCatch: true + BeforeElse: true BeforeLambdaBody: true - BeforeWhile: false - IndentBraces: false - SplitEmptyFunction: true + BeforeWhile: true SplitEmptyRecord: true SplitEmptyNamespace: true -BreakBeforeBraces: Custom -BreakConstructorInitializers: AfterColon -BreakConstructorInitializersBeforeComma: false +BreakBeforeBinaryOperators: All +BreakBeforeConceptDeclarations: false ColumnLimit: 120 -ConstructorInitializerAllOnOneLineOrOnePerLine: false -ContinuationIndentWidth: 8 -IncludeCategories: - - Regex: '^<.*' - Priority: 1 - - Regex: '^".*' - Priority: 2 - - Regex: '.*' - Priority: 3 -IncludeIsMainRegex: '([-_](test|unittest))?$' -IndentCaseLabels: true +IncludeBlocks: Merge +IndentExternBlock: Indent +IndentRequiresClause: false IndentWidth: 4 -InsertNewlineAtEOF: true -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 2 +ContinuationIndentWidth: 8 +KeepEmptyLinesAtTheStartOfBlocks: false NamespaceIndentation: All PointerAlignment: Left -SpaceAfterCStyleCast: true +SortUsingDeclarations: true SpaceAfterTemplateKeyword: false +SpaceBeforeCtorInitializerColon: false +SpaceBeforeParens: Custom +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterFunctionDeclarationName: false + AfterFunctionDefinitionName: false + AfterForeachMacros: true + AfterIfMacros: true + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false SpaceBeforeRangeBasedForLoopColon: false SpaceInEmptyParentheses: false -SpacesInAngles: false -SpacesInConditionalStatement: false SpacesInCStyleCastParentheses: false -SpacesInParentheses: false -TabWidth: 4 -... +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false \ No newline at end of file From be3fae63b8a276348cdcfa5e99b856fc6c197858 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 3 May 2025 16:50:29 +0300 Subject: [PATCH 280/795] patched clang format --- .clang-format | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.clang-format b/.clang-format index 989123a2..0217b65c 100644 --- a/.clang-format +++ b/.clang-format @@ -10,10 +10,10 @@ AlignConsecutiveBitFields: None AlignConsecutiveDeclarations: None AlignConsecutiveMacros: AcrossEmptyLinesAndComments AlignTrailingComments: false -AllowShortBlocksOnASingleLine: Always -AllowShortFunctionsOnASingleLine: Inline -AllowShortIfStatementsOnASingleLine: true -AllowShortLoopsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false AlwaysBreakTemplateDeclarations: No BreakBeforeBraces: Custom BraceWrapping: From df6d75e554929e94247e07c461ea65425fad75cf Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 3 May 2025 20:31:59 +0300 Subject: [PATCH 281/795] changed code style --- .clang-format | 2 +- include/omath/3d_primitives/box.hpp | 4 +- include/omath/angle.hpp | 48 ++--- include/omath/angles.hpp | 24 +-- include/omath/collision/line_tracer.hpp | 13 +- include/omath/color.hpp | 117 ++++++----- include/omath/engines/iw_engine/camera.hpp | 13 +- include/omath/engines/iw_engine/constants.hpp | 18 +- include/omath/engines/iw_engine/formulas.hpp | 12 +- .../omath/engines/opengl_engine/camera.hpp | 12 +- .../omath/engines/opengl_engine/constants.hpp | 15 +- .../omath/engines/opengl_engine/formulas.hpp | 12 +- .../omath/engines/source_engine/camera.hpp | 13 +- .../omath/engines/source_engine/constants.hpp | 18 +- .../omath/engines/source_engine/formulas.hpp | 14 +- include/omath/engines/unity_engine/camera.hpp | 13 +- .../omath/engines/unity_engine/constants.hpp | 18 +- .../omath/engines/unity_engine/formulas.hpp | 14 +- include/omath/mat.hpp | 184 +++++++++--------- include/omath/matrix.hpp | 46 ++--- include/omath/pathfinding/a_star.hpp | 14 +- include/omath/pathfinding/navigation_mesh.hpp | 14 +- .../proj_pred_engine.hpp | 5 +- .../proj_pred_engine_avx2.hpp | 24 +-- .../proj_pred_engine_legacy.hpp | 28 ++- .../projectile_prediction/projectile.hpp | 9 +- .../omath/projectile_prediction/target.hpp | 9 +- include/omath/projection/camera.hpp | 137 +++++++------ include/omath/projection/error_codes.hpp | 1 - include/omath/triangle.hpp | 54 ++--- include/omath/vector2.hpp | 33 ++-- include/omath/vector3.hpp | 110 +++++------ include/omath/vector4.hpp | 44 ++--- include/omath/view_angles.hpp | 2 +- source/3d_primitives/box.cpp | 30 ++- source/collision/line_tracer.cpp | 51 +++-- source/color.cpp | 1 - source/engines/iw_engine/camera.cpp | 29 ++- source/engines/iw_engine/formulas.cpp | 39 ++-- source/engines/opengl_engine/camera.cpp | 30 ++- source/engines/opengl_engine/formulas.cpp | 45 ++--- source/engines/source_engine/camera.cpp | 30 ++- source/engines/source_engine/formulas.cpp | 39 ++-- source/engines/unity_engine/camera.cpp | 19 +- source/engines/unity_engine/formulas.cpp | 44 ++--- source/matrix.cpp | 140 +++++++------ source/pathfinding/a_star.cpp | 84 ++++---- source/pathfinding/navigation_mesh.cpp | 65 +++---- .../proj_pred_engine.cpp | 1 - .../proj_pred_engine_avx2.cpp | 50 ++--- .../proj_pred_engine_legacy.cpp | 62 +++--- source/projectile_prediction/projectile.cpp | 19 +- source/projectile_prediction/target.cpp | 1 - source/projection/camera.cpp | 1 - tests/engines/unit_test_iw_engine.cpp | 60 +++--- tests/engines/unit_test_open_gl.cpp | 60 +++--- tests/engines/unit_test_source_engine.cpp | 64 +++--- tests/engines/unit_test_unity_engine.cpp | 64 +++--- tests/general/unit_test_a_star.cpp | 13 +- tests/general/unit_test_angles.cpp | 24 +-- tests/general/unit_test_color.cpp | 20 +- tests/general/unit_test_line_trace.cpp | 11 +- tests/general/unit_test_mat.cpp | 132 ++++++------- tests/general/unit_test_matrix.cpp | 102 +++++----- tests/general/unit_test_prediction.cpp | 8 +- tests/general/unit_test_projection.cpp | 4 +- tests/general/unit_test_triangle.cpp | 26 +-- tests/general/unit_test_vector2.cpp | 46 ++--- tests/general/unit_test_vector3.cpp | 54 ++--- tests/general/unit_test_vector4.cpp | 10 +- 70 files changed, 1259 insertions(+), 1313 deletions(-) diff --git a/.clang-format b/.clang-format index 0217b65c..402faea9 100644 --- a/.clang-format +++ b/.clang-format @@ -14,7 +14,7 @@ AllowShortBlocksOnASingleLine: Never AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false -AlwaysBreakTemplateDeclarations: No +BreakTemplateDeclarations: Leave BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: true diff --git a/include/omath/3d_primitives/box.hpp b/include/omath/3d_primitives/box.hpp index 0f18c40b..23a59671 100644 --- a/include/omath/3d_primitives/box.hpp +++ b/include/omath/3d_primitives/box.hpp @@ -11,7 +11,7 @@ namespace omath::primitives { [[nodiscard]] - std::array>, 12> CreateBox(const Vector3& top, const Vector3& bottom, - const Vector3& dirForward, const Vector3& dirRight, + std::array>, 12> create_box(const Vector3& top, const Vector3& bottom, + const Vector3& dir_forward, const Vector3& dir_right, float ratio = 4.f); } diff --git a/include/omath/angle.hpp b/include/omath/angle.hpp index 11d36d99..61e75d0d 100644 --- a/include/omath/angle.hpp +++ b/include/omath/angle.hpp @@ -3,10 +3,9 @@ // #pragma once +#include "omath/angles.hpp" #include #include -#include "omath/angles.hpp" - namespace omath { @@ -17,14 +16,14 @@ namespace omath }; template - requires std::is_arithmetic_v + requires std::is_arithmetic_v class Angle { Type m_angle; constexpr explicit Angle(const Type& degrees) { if constexpr (flags == AngleFlags::Normalized) - m_angle = angles::WrapAngle(degrees, min, max); + m_angle = angles::wrap_angle(degrees, min, max); else if constexpr (flags == AngleFlags::Clamped) m_angle = std::clamp(degrees, min, max); @@ -37,17 +36,17 @@ namespace omath public: [[nodiscard]] - constexpr static Angle FromDegrees(const Type& degrees) + constexpr static Angle from_degrees(const Type& degrees) { return Angle{degrees}; } - constexpr Angle() : m_angle(0) + constexpr Angle(): m_angle(0) { } [[nodiscard]] - constexpr static Angle FromRadians(const Type& degrees) + constexpr static Angle from_radians(const Type& degrees) { - return Angle{angles::RadiansToDegrees(degrees)}; + return Angle{angles::radians_to_degrees(degrees)}; } [[nodiscard]] @@ -57,51 +56,51 @@ namespace omath } [[nodiscard]] - constexpr Type AsDegrees() const + constexpr Type as_degrees() const { return m_angle; } [[nodiscard]] - constexpr Type AsRadians() const + constexpr Type as_radians() const { - return angles::DegreesToRadians(m_angle); + return angles::degrees_to_radians(m_angle); } [[nodiscard]] - Type Sin() const + Type sin() const { - return std::sin(AsRadians()); + return std::sin(as_radians()); } [[nodiscard]] - Type Cos() const + Type cos() const { - return std::cos(AsRadians()); + return std::cos(as_radians()); } [[nodiscard]] - Type Tan() const + Type tan() const { - return std::tan(AsRadians()); + return std::tan(as_radians()); } [[nodiscard]] - Type Atan() const + Type atan() const { - return std::atan(AsRadians()); + return std::atan(as_radians()); } [[nodiscard]] - Type Cot() const + Type cot() const { - return Cos() / Sin(); + return cos() / sin(); } constexpr Angle& operator+=(const Angle& other) { if constexpr (flags == AngleFlags::Normalized) - m_angle = angles::WrapAngle(m_angle + other.m_angle, min, max); + m_angle = angles::wrap_angle(m_angle + other.m_angle, min, max); else if constexpr (flags == AngleFlags::Clamped) m_angle = std::clamp(m_angle + other.m_angle, min, max); @@ -115,7 +114,8 @@ namespace omath } [[nodiscard]] - constexpr std::partial_ordering operator<=>(const Angle& other) const = default; + constexpr std::partial_ordering operator<=>(const Angle& other) const + = default; constexpr Angle& operator-=(const Angle& other) { @@ -126,7 +126,7 @@ namespace omath constexpr Angle& operator+(const Angle& other) { if constexpr (flags == AngleFlags::Normalized) - return {angles::WrapAngle(m_angle + other.m_angle, min, max)}; + return {angles::wrap_angle(m_angle + other.m_angle, min, max)}; else if constexpr (flags == AngleFlags::Clamped) return {std::clamp(m_angle + other.m_angle, min, max)}; diff --git a/include/omath/angles.hpp b/include/omath/angles.hpp index 11a69d4b..bd654148 100644 --- a/include/omath/angles.hpp +++ b/include/omath/angles.hpp @@ -10,43 +10,43 @@ namespace omath::angles { template requires std::is_floating_point_v - [[nodiscard]] constexpr Type RadiansToDegrees(const Type& radians) + [[nodiscard]] constexpr Type radians_to_degrees(const Type& radians) { return radians * (Type(180) / std::numbers::pi_v); } template requires std::is_floating_point_v - [[nodiscard]] constexpr Type DegreesToRadians(const Type& degrees) + [[nodiscard]] constexpr Type degrees_to_radians(const Type& degrees) { return degrees * (std::numbers::pi_v / Type(180)); } template requires std::is_floating_point_v - [[nodiscard]] type HorizontalFovToVertical(const type& horFov, const type& aspect) + [[nodiscard]] type horizontal_fov_to_vertical(const type& horizontal_fov, const type& aspect) { - const auto fovRad = DegreesToRadians(horFov); + const auto fov_rad = degrees_to_radians(horizontal_fov); - const auto vertFov = type(2) * std::atan(std::tan(fovRad / type(2)) / aspect); + const auto vert_fov = type(2) * std::atan(std::tan(fov_rad / type(2)) / aspect); - return RadiansToDegrees(vertFov); + return radians_to_degrees(vert_fov); } template requires std::is_floating_point_v - [[nodiscard]] Type VerticalFovToHorizontal(const Type& vertFov, const Type& aspect) + [[nodiscard]] Type vertical_fov_to_horizontal(const Type& vertical_fov, const Type& aspect) { - const auto fovRad = DegreesToRadians(vertFov); + const auto fov_as_radians = degrees_to_radians(vertical_fov); - const auto horFov = Type(2) * std::atan(std::tan(fovRad / Type(2)) * aspect); + const auto horizontal_fov = Type(2) * std::atan(std::tan(fov_as_radians / Type(2)) * aspect); - return RadiansToDegrees(horFov); + return radians_to_degrees(horizontal_fov); } template requires std::is_arithmetic_v - [[nodiscard]] Type WrapAngle(const Type& angle, const Type& min, const Type& max) + [[nodiscard]] Type wrap_angle(const Type& angle, const Type& min, const Type& max) { if (angle <= max && angle >= min) return angle; @@ -60,4 +60,4 @@ namespace omath::angles return wrappedAngle + min; } -} +} // namespace omath::angles diff --git a/include/omath/collision/line_tracer.hpp b/include/omath/collision/line_tracer.hpp index cd7a87ae..6a655b0d 100644 --- a/include/omath/collision/line_tracer.hpp +++ b/include/omath/collision/line_tracer.hpp @@ -14,25 +14,24 @@ namespace omath::collision Vector3 start; Vector3 end; bool infinite_length = false; + [[nodiscard]] - Vector3 DirectionVector() const; + Vector3 direction_vector() const; [[nodiscard]] - Vector3 DirectionVectorNormalized() const; + Vector3 direction_vector_normalized() const; }; class LineTracer { public: LineTracer() = delete; - [[nodiscard]] - static bool CanTraceLine(const Ray& ray, const Triangle>& triangle); - + static bool can_trace_line(const Ray& ray, const Triangle>& triangle); // Realization of Möller–Trumbore intersection algorithm // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm [[nodiscard]] - static Vector3 GetRayHitPoint(const Ray& ray, const Triangle>& triangle); + static Vector3 get_ray_hit_point(const Ray& ray, const Triangle>& triangle); }; -} +} // namespace omath::collision diff --git a/include/omath/color.hpp b/include/omath/color.hpp index 48a02dfd..e460059c 100644 --- a/include/omath/color.hpp +++ b/include/omath/color.hpp @@ -4,95 +4,93 @@ #pragma once -#include #include "omath/vector3.hpp" #include "omath/vector4.hpp" +#include #ifdef max #undef max #endif - #ifdef min #undef min #endif namespace omath { - struct HSV + struct Hsv { float hue{}; float saturation{}; float value{}; }; - class Color final : public Vector4 { public: - constexpr Color(const float r, const float g, const float b, const float a) : Vector4(r, g, b, a) + constexpr Color(const float r, const float g, const float b, const float a): Vector4(r, g, b, a) { - Clamp(0.f, 1.f); + clamp(0.f, 1.f); } constexpr explicit Color() = default; [[nodiscard]] - constexpr static Color FromRGBA(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) + constexpr static Color from_rgba(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) { return Color{Vector4(r, g, b, a) / 255.f}; } [[nodiscard]] - constexpr static Color FromHSV(float hue, const float saturation, const float value) + constexpr static Color from_hsv(float hue, const float saturation, const float value) { float r{}, g{}, b{}; hue = std::clamp(hue, 0.f, 1.f); const int i = static_cast(hue * 6.f); - const float f = hue * 6 - i; + const float f = hue * 6.f - static_cast(i); const float p = value * (1 - saturation); const float q = value * (1 - f * saturation); const float t = value * (1 - (1 - f) * saturation); switch (i % 6) { - case 0: - r = value, g = t, b = p; - break; - case 1: - r = q, g = value, b = p; - break; - case 2: - r = p, g = value, b = t; - break; - case 3: - r = p, g = q, b = value; - break; - case 4: - r = t, g = p, b = value; - break; - case 5: - r = value, g = p, b = q; - break; - - default: - return {0.f, 0.f, 0.f, 0.f}; + case 0: + r = value, g = t, b = p; + break; + case 1: + r = q, g = value, b = p; + break; + case 2: + r = p, g = value, b = t; + break; + case 3: + r = p, g = q, b = value; + break; + case 4: + r = t, g = p, b = value; + break; + case 5: + r = value, g = p, b = q; + break; + + default: + return {0.f, 0.f, 0.f, 0.f}; } return {r, g, b, 1.f}; } [[nodiscard]] - constexpr static Color FromHSV(const HSV& hsv) + constexpr static Color from_hsv(const Hsv& hsv) { - return FromHSV(hsv.hue, hsv.saturation, hsv.value); + return from_hsv(hsv.hue, hsv.saturation, hsv.value); } [[nodiscard]] - constexpr HSV ToHSV() const + constexpr Hsv to_hsv() const { - HSV hsvData; + Hsv hsv_data; const float& red = x; const float& green = y; @@ -102,70 +100,69 @@ namespace omath const float min = std::min({red, green, blue}); const float delta = max - min; - if (delta == 0.f) - hsvData.hue = 0.f; + hsv_data.hue = 0.f; else if (max == red) - hsvData.hue = 60.f * (std::fmodf(((green - blue) / delta), 6.f)); + hsv_data.hue = 60.f * (std::fmodf(((green - blue) / delta), 6.f)); else if (max == green) - hsvData.hue = 60.f * (((blue - red) / delta) + 2.f); + hsv_data.hue = 60.f * (((blue - red) / delta) + 2.f); else if (max == blue) - hsvData.hue = 60.f * (((red - green) / delta) + 4.f); + hsv_data.hue = 60.f * (((red - green) / delta) + 4.f); - if (hsvData.hue < 0.f) - hsvData.hue += 360.f; + if (hsv_data.hue < 0.f) + hsv_data.hue += 360.f; - hsvData.hue /= 360.f; - hsvData.saturation = max == 0.f ? 0.f : delta / max; - hsvData.value = max; + hsv_data.hue /= 360.f; + hsv_data.saturation = max == 0.f ? 0.f : delta / max; + hsv_data.value = max; - return hsvData; + return hsv_data; } - constexpr explicit Color(const Vector4& vec) : Vector4(vec) + constexpr explicit Color(const Vector4& vec): Vector4(vec) { - Clamp(0.f, 1.f); + clamp(0.f, 1.f); } - constexpr void SetHue(const float hue) + constexpr void set_hue(const float hue) { - auto hsv = ToHSV(); + auto hsv = to_hsv(); hsv.hue = hue; - *this = FromHSV(hsv); + *this = from_hsv(hsv); } - constexpr void SetSaturation(const float saturation) + constexpr void set_saturation(const float saturation) { - auto hsv = ToHSV(); + auto hsv = to_hsv(); hsv.saturation = saturation; - *this = FromHSV(hsv); + *this = from_hsv(hsv); } - constexpr void SetValue(const float value) + constexpr void set_value(const float value) { - auto hsv = ToHSV(); + auto hsv = to_hsv(); hsv.value = value; - *this = FromHSV(hsv); + *this = from_hsv(hsv); } [[nodiscard]] - constexpr Color Blend(const Color& other, float ratio) const + constexpr Color blend(const Color& other, float ratio) const { ratio = std::clamp(ratio, 0.f, 1.f); return Color(*this * (1.f - ratio) + other * ratio); } - [[nodiscard]] static constexpr Color Red() + [[nodiscard]] static constexpr Color red() { return {1.f, 0.f, 0.f, 1.f}; } - [[nodiscard]] static constexpr Color Green() + [[nodiscard]] static constexpr Color green() { return {0.f, 1.f, 0.f, 1.f}; } - [[nodiscard]] static constexpr Color Blue() + [[nodiscard]] static constexpr Color blue() { return {0.f, 0.f, 1.f, 1.f}; } diff --git a/include/omath/engines/iw_engine/camera.hpp b/include/omath/engines/iw_engine/camera.hpp index 844ff5dc..ea59474e 100644 --- a/include/omath/engines/iw_engine/camera.hpp +++ b/include/omath/engines/iw_engine/camera.hpp @@ -8,14 +8,15 @@ namespace omath::iw_engine { - class Camera final : public projection::Camera + class Camera final : public projection::Camera { public: - Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, const Angle& fov, float near, float far); - void LookAt(const Vector3& target) override; + void look_at(const Vector3& target) override; + protected: - [[nodiscard]] Mat4x4 CalcViewMatrix() const override; - [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; + [[nodiscard]] Mat4X4 calc_view_matrix() const override; + [[nodiscard]] Mat4X4 calc_projection_matrix() const override; }; -} \ No newline at end of file +} // namespace omath::iw_engine \ No newline at end of file diff --git a/include/omath/engines/iw_engine/constants.hpp b/include/omath/engines/iw_engine/constants.hpp index ff7d41d9..48e29c19 100644 --- a/include/omath/engines/iw_engine/constants.hpp +++ b/include/omath/engines/iw_engine/constants.hpp @@ -3,23 +3,23 @@ // #pragma once -#include -#include #include +#include +#include #include namespace omath::iw_engine { - constexpr Vector3 kAbsUp = {0, 0, 1}; - constexpr Vector3 kAbsRight = {0, -1, 0}; - constexpr Vector3 kAbsForward = {1, 0, 0}; + constexpr Vector3 k_abs_up = {0, 0, 1}; + constexpr Vector3 k_abs_right = {0, -1, 0}; + constexpr Vector3 k_abs_forward = {1, 0, 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 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; -} \ No newline at end of file +} // namespace omath::iw_engine \ No newline at end of file diff --git a/include/omath/engines/iw_engine/formulas.hpp b/include/omath/engines/iw_engine/formulas.hpp index 60d1fee8..424621f7 100644 --- a/include/omath/engines/iw_engine/formulas.hpp +++ b/include/omath/engines/iw_engine/formulas.hpp @@ -8,19 +8,19 @@ namespace omath::iw_engine { [[nodiscard]] - Vector3 ForwardVector(const ViewAngles& angles); + Vector3 forward_vector(const ViewAngles& angles); [[nodiscard]] - Vector3 RightVector(const ViewAngles& angles); + Vector3 right_vector(const ViewAngles& angles); [[nodiscard]] - Vector3 UpVector(const ViewAngles& angles); + Vector3 up_vector(const ViewAngles& angles); [[nodiscard]] - Mat4x4 RotationMatrix(const ViewAngles& angles); + Mat4X4 rotation_matrix(const ViewAngles& angles); - [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); + [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin); [[nodiscard]] - Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); + Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far); } // namespace omath::iw_engine diff --git a/include/omath/engines/opengl_engine/camera.hpp b/include/omath/engines/opengl_engine/camera.hpp index bfee7c44..605ee449 100644 --- a/include/omath/engines/opengl_engine/camera.hpp +++ b/include/omath/engines/opengl_engine/camera.hpp @@ -7,13 +7,13 @@ namespace omath::opengl_engine { - class Camera final : public projection::Camera + class Camera final : public projection::Camera { public: - Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, const Angle& fov, float near, float far); - void LookAt(const Vector3& target) override; - [[nodiscard]] Mat4x4 CalcViewMatrix() const override; - [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; + void look_at(const Vector3& target) override; + [[nodiscard]] Mat4X4 calc_view_matrix() const override; + [[nodiscard]] Mat4X4 calc_projection_matrix() const override; }; -} \ No newline at end of file +} // namespace omath::opengl_engine \ No newline at end of file diff --git a/include/omath/engines/opengl_engine/constants.hpp b/include/omath/engines/opengl_engine/constants.hpp index 3e0a7624..ff62f46e 100644 --- a/include/omath/engines/opengl_engine/constants.hpp +++ b/include/omath/engines/opengl_engine/constants.hpp @@ -10,17 +10,16 @@ namespace omath::opengl_engine { - constexpr Vector3 kAbsUp = {0, 1, 0}; - constexpr Vector3 kAbsRight = {1, 0, 0}; - constexpr Vector3 kAbsForward = {0, 0, -1}; + constexpr Vector3 k_abs_up = {0, 1, 0}; + constexpr Vector3 k_abs_right = {1, 0, 0}; + constexpr Vector3 k_abs_forward = {0, 0, -1}; - using Mat4x4 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>; - using Mat3x3 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>; - using Mat1x3 = Mat<1, 3, float, MatStoreType::COLUMN_MAJOR>; + using Mat4X4 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>; + using Mat3X3 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>; + using Mat1X3 = Mat<1, 3, float, MatStoreType::COLUMN_MAJOR>; using PitchAngle = Angle; using YawAngle = Angle; using RollAngle = Angle; - using ViewAngles = omath::ViewAngles; -} \ No newline at end of file +} // namespace omath::opengl_engine \ No newline at end of file diff --git a/include/omath/engines/opengl_engine/formulas.hpp b/include/omath/engines/opengl_engine/formulas.hpp index 7f24ef6e..dc6ae64b 100644 --- a/include/omath/engines/opengl_engine/formulas.hpp +++ b/include/omath/engines/opengl_engine/formulas.hpp @@ -8,19 +8,19 @@ namespace omath::opengl_engine { [[nodiscard]] - Vector3 ForwardVector(const ViewAngles& angles); + Vector3 forward_vector(const ViewAngles& angles); [[nodiscard]] - Vector3 RightVector(const ViewAngles& angles); + Vector3 right_vector(const ViewAngles& angles); [[nodiscard]] - Vector3 UpVector(const ViewAngles& angles); + Vector3 up_vector(const ViewAngles& angles); - [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); + [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin); [[nodiscard]] - Mat4x4 RotationMatrix(const ViewAngles& angles); + Mat4X4 rotation_matrix(const ViewAngles& angles); [[nodiscard]] - Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); + Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far); } // namespace omath::opengl_engine diff --git a/include/omath/engines/source_engine/camera.hpp b/include/omath/engines/source_engine/camera.hpp index baa11e31..0ea11121 100644 --- a/include/omath/engines/source_engine/camera.hpp +++ b/include/omath/engines/source_engine/camera.hpp @@ -7,14 +7,15 @@ namespace omath::source_engine { - class Camera final : public projection::Camera + class Camera final : public projection::Camera { public: - Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, const Angle& fov, float near, float far); - void LookAt(const Vector3& target) override; + void look_at(const Vector3& target) override; + protected: - [[nodiscard]] Mat4x4 CalcViewMatrix() const override; - [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; + [[nodiscard]] Mat4X4 calc_view_matrix() const override; + [[nodiscard]] Mat4X4 calc_projection_matrix() const override; }; -} \ No newline at end of file +} // namespace omath::source_engine \ No newline at end of file diff --git a/include/omath/engines/source_engine/constants.hpp b/include/omath/engines/source_engine/constants.hpp index 3c7e1df1..e081df50 100644 --- a/include/omath/engines/source_engine/constants.hpp +++ b/include/omath/engines/source_engine/constants.hpp @@ -3,23 +3,23 @@ // #pragma once -#include -#include #include +#include +#include #include namespace omath::source_engine { - constexpr Vector3 kAbsUp = {0, 0, 1}; - constexpr Vector3 kAbsRight = {0, -1, 0}; - constexpr Vector3 kAbsForward = {1, 0, 0}; + constexpr Vector3 k_abs_up = {0, 0, 1}; + constexpr Vector3 k_abs_right = {0, -1, 0}; + constexpr Vector3 k_abs_forward = {1, 0, 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 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::source +} // namespace omath::source_engine diff --git a/include/omath/engines/source_engine/formulas.hpp b/include/omath/engines/source_engine/formulas.hpp index 450de1eb..2b651e02 100644 --- a/include/omath/engines/source_engine/formulas.hpp +++ b/include/omath/engines/source_engine/formulas.hpp @@ -7,19 +7,19 @@ namespace omath::source_engine { [[nodiscard]] - Vector3 ForwardVector(const ViewAngles& angles); + Vector3 forward_vector(const ViewAngles& angles); [[nodiscard]] - Mat4x4 RotationMatrix(const ViewAngles& angles); + Mat4X4 rotation_matrix(const ViewAngles& angles); [[nodiscard]] - Vector3 RightVector(const ViewAngles& angles); + Vector3 right_vector(const ViewAngles& angles); [[nodiscard]] - Vector3 UpVector(const ViewAngles& angles); + Vector3 up_vector(const ViewAngles& angles); - [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); + [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin); [[nodiscard]] - Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); -} // namespace omath::source + Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far); +} // namespace omath::source_engine diff --git a/include/omath/engines/unity_engine/camera.hpp b/include/omath/engines/unity_engine/camera.hpp index e3a7f4e1..7a96757a 100644 --- a/include/omath/engines/unity_engine/camera.hpp +++ b/include/omath/engines/unity_engine/camera.hpp @@ -8,14 +8,15 @@ namespace omath::unity_engine { - class Camera final : public projection::Camera + class Camera final : public projection::Camera { public: - Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, + Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, const Angle& fov, float near, float far); - void LookAt(const Vector3& target) override; + void look_at(const Vector3& target) override; + protected: - [[nodiscard]] Mat4x4 CalcViewMatrix() const override; - [[nodiscard]] Mat4x4 CalcProjectionMatrix() const override; + [[nodiscard]] Mat4X4 calc_view_matrix() const override; + [[nodiscard]] Mat4X4 calc_projection_matrix() const override; }; -} \ No newline at end of file +} // namespace omath::unity_engine \ No newline at end of file diff --git a/include/omath/engines/unity_engine/constants.hpp b/include/omath/engines/unity_engine/constants.hpp index 27ec9a3b..d1308efe 100644 --- a/include/omath/engines/unity_engine/constants.hpp +++ b/include/omath/engines/unity_engine/constants.hpp @@ -4,23 +4,23 @@ #pragma once -#include -#include #include +#include +#include #include namespace omath::unity_engine { - constexpr Vector3 kAbsUp = {0, 1, 0}; - constexpr Vector3 kAbsRight = {1, 0, 0}; - constexpr Vector3 kAbsForward = {0, 0, 1}; + constexpr Vector3 k_abs_up = {0, 1, 0}; + constexpr Vector3 k_abs_right = {1, 0, 0}; + constexpr Vector3 k_abs_forward = {0, 0, 1}; - 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 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::source +} // namespace omath::unity_engine diff --git a/include/omath/engines/unity_engine/formulas.hpp b/include/omath/engines/unity_engine/formulas.hpp index 8b369535..db691d5d 100644 --- a/include/omath/engines/unity_engine/formulas.hpp +++ b/include/omath/engines/unity_engine/formulas.hpp @@ -8,19 +8,19 @@ namespace omath::unity_engine { [[nodiscard]] - Vector3 ForwardVector(const ViewAngles& angles); + Vector3 forward_vector(const ViewAngles& angles); [[nodiscard]] - Vector3 RightVector(const ViewAngles& angles); + Vector3 right_vector(const ViewAngles& angles); [[nodiscard]] - Vector3 UpVector(const ViewAngles& angles); + Vector3 up_vector(const ViewAngles& angles); - [[nodiscard]] Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin); + [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin); [[nodiscard]] - Mat4x4 RotationMatrix(const ViewAngles& angles); + Mat4X4 rotation_matrix(const ViewAngles& angles); [[nodiscard]] - Mat4x4 CalcPerspectiveProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); -} // namespace omath::source + Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far); +} // namespace omath::unity_engine diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index c4187023..d13dfc52 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -2,21 +2,19 @@ // Created by vlad on 9/29/2024. // #pragma once +#include "omath/vector3.hpp" #include #include #include +#include #include #include #include -#include "omath/vector3.hpp" -#include - #ifdef near #undef near #endif - #ifdef far #undef far #endif @@ -34,25 +32,22 @@ namespace omath COLUMN_MAJOR }; - - template - concept MatTemplateEqual = - (M1::rows == M2::rows) && (M1::columns == M2::columns) && - std::is_same_v && - (M1::store_type == M2::store_type); + template concept MatTemplateEqual + = (M1::rows == M2::rows) && (M1::columns == M2::columns) + && std::is_same_v && (M1::store_type == M2::store_type); template - requires std::is_arithmetic_v + requires std::is_arithmetic_v class Mat final { public: constexpr Mat() noexcept { - Clear(); + clear(); } [[nodiscard]] - constexpr static MatStoreType GetStoreOrdering() noexcept + constexpr static MatStoreType get_store_ordering() noexcept { return StoreType; } @@ -61,24 +56,24 @@ namespace omath if (rows.size() != Rows) throw std::invalid_argument("Initializer list rows size does not match template parameter Rows"); - auto rowIt = rows.begin(); - for (size_t i = 0; i < Rows; ++i, ++rowIt) + auto row_it = rows.begin(); + for (size_t i = 0; i < Rows; ++i, ++row_it) { - if (rowIt->size() != Columns) + if (row_it->size() != Columns) throw std::invalid_argument( "All rows must have the same number of columns as template parameter Columns"); - auto colIt = rowIt->begin(); - for (size_t j = 0; j < Columns; ++j, ++colIt) + auto col_it = row_it->begin(); + for (size_t j = 0; j < Columns; ++j, ++col_it) { - At(i, j) = std::move(*colIt); + at(i, j) = std::move(*col_it); } } } - constexpr explicit Mat(const Type* rawData) + constexpr explicit Mat(const Type* raw_data) { - std::copy_n(rawData, Rows * Columns, m_data.begin()); + std::copy_n(raw_data, Rows * Columns, m_data.begin()); } constexpr Mat(const Mat& other) noexcept @@ -89,13 +84,13 @@ namespace omath [[nodiscard]] constexpr Type& operator[](const size_t row, const size_t col) { - return At(row, col); + return at(row, col); } [[nodiscard]] constexpr Type& operator[](const size_t row, const size_t col) const { - return At(row, col); + return at(row, col); } constexpr Mat(Mat&& other) noexcept @@ -104,35 +99,35 @@ namespace omath } [[nodiscard]] - static constexpr size_t RowCount() noexcept + static constexpr size_t row_count() noexcept { return Rows; } [[nodiscard]] - static constexpr size_t ColumnsCount() noexcept + static constexpr size_t columns_count() noexcept { return Columns; } [[nodiscard]] - static consteval MatSize Size() noexcept + static consteval MatSize size() noexcept { return {Rows, Columns}; } [[nodiscard]] - constexpr const Type& At(const size_t rowIndex, const size_t columnIndex) const + constexpr const Type& at(const size_t row_index, const size_t column_index) const { #if !defined(NDEBUG) && defined(OMATH_SUPRESS_SAFETY_CHECKS) - if (rowIndex >= Rows || columnIndex >= Columns) + if (row_index >= Rows || column_index >= Columns) throw std::out_of_range("Index out of range"); #endif if constexpr (StoreType == MatStoreType::ROW_MAJOR) - return m_data[rowIndex * Columns + columnIndex]; + return m_data[row_index * Columns + column_index]; else if constexpr (StoreType == MatStoreType::COLUMN_MAJOR) - return m_data[rowIndex + columnIndex * Rows]; + return m_data[row_index + column_index * Rows]; else { @@ -141,30 +136,29 @@ namespace omath } } - [[nodiscard]] constexpr Type& At(const size_t rowIndex, const size_t columnIndex) + [[nodiscard]] constexpr Type& at(const size_t row_index, const size_t column_index) { - return const_cast(std::as_const(*this).At(rowIndex, columnIndex)); + return const_cast(std::as_const(*this).at(row_index, column_index)); } [[nodiscard]] - constexpr Type Sum() const noexcept + constexpr Type sum() const noexcept { - return std::accumulate(m_data.begin(), m_data.end(), Type(0)); + return std::accumulate(m_data.begin(), m_data.end(), static_cast(0)); } - constexpr void Clear() noexcept + constexpr void clear() noexcept { - Set(0); + set(static_cast(0)); } - constexpr void Set(const Type& value) noexcept + constexpr void set(const Type& value) noexcept { std::ranges::fill(m_data, value); } // Operator overloading for multiplication with another Mat - template - [[nodiscard]] + template [[nodiscard]] constexpr Mat operator*(const Mat& other) const { @@ -175,20 +169,19 @@ namespace omath { Type sum = 0; for (size_t k = 0; k < Columns; ++k) - sum += At(i, k) * other.At(k, j); - result.At(i, j) = sum; + sum += at(i, k) * other.at(k, j); + result.at(i, j) = sum; } return result; } constexpr Mat& operator*=(const Type& f) noexcept { - std::ranges::for_each(m_data, [&f](auto& val) {val *= f;}); + std::ranges::for_each(m_data, [&f](auto& val) { val *= f; }); return *this; } - template - constexpr Mat + template constexpr Mat operator*=(const Mat& other) { return *this = *this * other; @@ -204,7 +197,7 @@ namespace omath constexpr Mat& operator/=(const Type& value) noexcept { - std::ranges::for_each(m_data, [&value](auto& val) {val /= value;}); + std::ranges::for_each(m_data, [&value](auto& val) { val /= value; }); return *this; } @@ -238,7 +231,7 @@ namespace omath Mat transposed; for (size_t i = 0; i < Rows; ++i) for (size_t j = 0; j < Columns; ++j) - transposed.At(j, i) = At(i, j); + transposed.at(j, i) = at(i, j); return transposed; } @@ -249,17 +242,17 @@ namespace omath static_assert(Rows == Columns, "Determinant is only defined for square matrices."); if constexpr (Rows == 1) - return At(0, 0); + return at(0, 0); if constexpr (Rows == 2) - return At(0, 0) * At(1, 1) - At(0, 1) * At(1, 0); + return at(0, 0) * at(1, 1) - at(0, 1) * at(1, 0); if constexpr (Rows > 2) { Type det = 0; for (size_t column = 0; column < Columns; ++column) { - const Type cofactor = At(0, column) * AlgComplement(0, column); + const Type cofactor = at(0, column) * alg_complement(0, column); det += cofactor; } return det; @@ -268,9 +261,9 @@ namespace omath } [[nodiscard]] - constexpr Mat Strip(const size_t row, const size_t column) const + constexpr Mat strip(const size_t row, const size_t column) const { - static_assert(Rows-1 > 0 && Columns-1 > 0); + static_assert(Rows - 1 > 0 && Columns - 1 > 0); Mat result; for (size_t i = 0, m = 0; i < Rows; ++i) { @@ -280,7 +273,7 @@ namespace omath { if (j == column) continue; - result.At(m, n) = At(i, j); + result.at(m, n) = at(i, j); ++n; } ++m; @@ -289,32 +282,32 @@ namespace omath } [[nodiscard]] - constexpr Type Minor(const size_t row, const size_t column) const + constexpr Type minor(const size_t row, const size_t column) const { - return Strip(row, column).Determinant(); + return strip(row, column).Determinant(); } [[nodiscard]] - constexpr Type AlgComplement(const size_t row, const size_t column) const + constexpr Type alg_complement(const size_t row, const size_t column) const { - const auto minor = Minor(row, column); - return (row + column + 2) % 2 == 0 ? minor: -minor; + const auto minor_value = minor(row, column); + return (row + column + 2) % 2 == 0 ? minor_value : -minor_value; } [[nodiscard]] - constexpr const std::array& RawArray() const + constexpr const std::array& raw_array() const { return m_data; } [[nodiscard]] - constexpr std::array& RawArray() + constexpr std::array& raw_array() { return m_data; } [[nodiscard]] - std::string ToString() const noexcept + std::string to_string() const noexcept { std::ostringstream oss; oss << "[["; @@ -326,7 +319,7 @@ namespace omath for (size_t j = 0; j < Columns; ++j) { - oss << std::setw(9) << std::fixed << std::setprecision(3) << At(i, j); + oss << std::setw(9) << std::fixed << std::setprecision(3) << at(i, j); if (j != Columns - 1) oss << ", "; } @@ -349,18 +342,18 @@ namespace omath // Static methods that return fixed-size matrices [[nodiscard]] - constexpr static Mat<4, 4> ToScreenMat(const Type& screenWidth, const Type& screenHeight) noexcept + constexpr static Mat<4, 4> to_screen_mat(const Type& screen_width, const Type& screen_height) noexcept { return { - {screenWidth / 2, 0, 0, 0}, - {0, -screenHeight / 2, 0, 0}, + {screen_width / 2, 0, 0, 0}, + {0, -screen_height / 2, 0, 0}, {0, 0, 1, 0}, - {screenWidth / 2, screenHeight / 2, 0, 1}, + {screen_width / 2, screen_height / 2, 0, 1}, }; } [[nodiscard]] - constexpr std::optional Inverted() const + constexpr std::optional inverted() const { const auto det = Determinant(); @@ -372,33 +365,32 @@ namespace omath for (std::size_t row = 0; row < Rows; row++) for (std::size_t column = 0; column < Rows; column++) - result.At(row, column) = transposed.AlgComplement(row, column); + result.at(row, column) = transposed.alg_complement(row, column); result /= det; return {result}; } + private: std::array m_data; }; - template - [[nodiscard]] - constexpr static Mat<1, 4, Type, St> MatRowFromVector(const Vector3& vector) noexcept + template [[nodiscard]] + constexpr static Mat<1, 4, Type, St> mat_row_from_vector(const Vector3& vector) noexcept { return {{vector.x, vector.y, vector.z, 1}}; } - template - [[nodiscard]] - constexpr static Mat<4, 1, Type, St> MatColumnFromVector(const Vector3& vector) noexcept + template [[nodiscard]] + constexpr static Mat<4, 1, Type, St> mat_column_from_vector(const Vector3& vector) noexcept { return {{vector.x}, {vector.y}, {vector.z}, {1}}; } template [[nodiscard]] - constexpr Mat<4, 4, Type, St> MatTranslation(const Vector3& diff) noexcept + constexpr Mat<4, 4, Type, St> mat_translation(const Vector3& diff) noexcept { return { @@ -411,38 +403,38 @@ namespace omath template [[nodiscard]] - Mat<4, 4, Type, St> MatRotationAxisX(const Angle& angle) noexcept + Mat<4, 4, Type, St> mat_rotation_axis_x(const Angle& angle) noexcept { return { {1, 0, 0, 0}, - {0, angle.Cos(), -angle.Sin(), 0}, - {0, angle.Sin(), angle.Cos(), 0}, + {0, angle.cos(), -angle.sin(), 0}, + {0, angle.sin(), angle.cos(), 0}, {0, 0, 0, 1} }; } template [[nodiscard]] - Mat<4, 4, Type, St> MatRotationAxisY(const Angle& angle) noexcept + Mat<4, 4, Type, St> mat_rotation_axis_y(const Angle& angle) noexcept { return { - {angle.Cos(), 0, angle.Sin(), 0}, + {angle.cos(), 0, angle.sin(), 0}, {0 , 1, 0, 0}, - {-angle.Sin(), 0, angle.Cos(), 0}, + {-angle.sin(), 0, angle.cos(), 0}, {0 , 0, 0, 1} }; } template [[nodiscard]] - Mat<4, 4, Type, St> MatRotationAxisZ(const Angle& angle) noexcept + Mat<4, 4, Type, St> mat_rotation_axis_z(const Angle& angle) noexcept { return { - {angle.Cos(), -angle.Sin(), 0, 0}, - {angle.Sin(), angle.Cos(), 0, 0}, + {angle.cos(), -angle.sin(), 0, 0}, + {angle.sin(), angle.cos(), 0, 0}, { 0, 0, 1, 0}, { 0, 0, 0, 1}, }; @@ -450,8 +442,8 @@ namespace omath template [[nodiscard]] - static Mat<4, 4, Type, St> MatCameraView(const Vector3& forward, const Vector3& right, - const Vector3& up, const Vector3& cameraOrigin) noexcept + 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> { @@ -460,31 +452,31 @@ namespace omath {forward.x, forward.y, forward.z, 0}, {0, 0, 0, 1}, - } * MatTranslation(-cameraOrigin); + } * mat_translation(-camera_origin); } template [[nodiscard]] - Mat<4, 4, Type, St> MatPerspectiveLeftHanded(const float fieldOfView, const float aspectRatio, const float near, - const float far) noexcept + Mat<4, 4, Type, St> mat_perspective_left_handed(const float field_of_view, const float aspect_ratio, + const float near, const float far) noexcept { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f); - return {{1.f / (aspectRatio * fovHalfTan), 0.f, 0.f, 0.f}, - {0.f, 1.f / fovHalfTan, 0.f, 0.f}, + return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fov_half_tan, 0.f, 0.f}, {0.f, 0.f, (far + near) / (far - near), -(2.f * near * far) / (far - near)}, {0.f, 0.f, 1.f, 0.f}}; } template [[nodiscard]] - Mat<4, 4, Type, St> MatPerspectiveRightHanded(const float fieldOfView, const float aspectRatio, const float near, - const float far) noexcept + Mat<4, 4, Type, St> mat_perspective_right_handed(const float field_of_view, const float aspect_ratio, + const float near, const float far) noexcept { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f); - return {{1.f / (aspectRatio * fovHalfTan), 0.f, 0.f, 0.f}, - {0.f, 1.f / fovHalfTan, 0.f, 0.f}, + return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fov_half_tan, 0.f, 0.f}, {0.f, 0.f, -(far + near) / (far - near), -(2.f * near * far) / (far - near)}, {0.f, 0.f, -1.f, 0.f}}; } diff --git a/include/omath/matrix.hpp b/include/omath/matrix.hpp index a8c19e4d..1e2df876 100644 --- a/include/omath/matrix.hpp +++ b/include/omath/matrix.hpp @@ -1,8 +1,8 @@ #pragma once +#include "omath/vector3.hpp" #include #include #include -#include "omath/vector3.hpp" namespace omath { @@ -16,51 +16,51 @@ namespace omath Matrix(const std::initializer_list>& rows); [[nodiscard]] - static Matrix ToScreenMatrix(float screenWidth, float screenHeight); + static Matrix to_screen_matrix(float screen_width, float screen_height); [[nodiscard]] - static Matrix TranslationMatrix(const Vector3& diff); + static Matrix translation_matrix(const Vector3& diff); [[nodiscard]] - static Matrix OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up); + static Matrix orientation_matrix(const Vector3& forward, const Vector3& right, + const Vector3& up); [[nodiscard]] - static Matrix ProjectionMatrix(float fieldOfView, float aspectRatio, float near, float far); + static Matrix projection_matrix(float field_of_view, float aspect_ratio, float near, float far); Matrix(const Matrix& other); - Matrix(size_t rows, size_t columns, const float* pRaw); + Matrix(size_t rows, size_t columns, const float* raw_data); Matrix(Matrix&& other) noexcept; [[nodiscard]] - size_t RowCount() const noexcept; - + size_t row_count() const noexcept; [[nodiscard]] float& operator[](size_t row, size_t column); [[nodiscard]] - size_t ColumnsCount() const noexcept; + size_t columns_count() const noexcept; [[nodiscard]] - std::pair Size() const noexcept; + std::pair size() const noexcept; [[nodiscard]] - float& At(size_t iRow, size_t iCol); + float& at(size_t row, size_t col); [[nodiscard]] - float Sum(); + float sum(); - void SetDataFromRaw(const float* pRawMatrix); + void set_data_from_raw(const float* raw_matrix); [[nodiscard]] - Matrix Transpose() const; + Matrix transpose() const; - void Set(float val); + void set(float val); [[nodiscard]] - const float& At(size_t iRow, size_t iCol) const; + const float& at(size_t row, size_t col) const; Matrix operator*(const Matrix& other) const; @@ -72,22 +72,22 @@ namespace omath Matrix& operator/=(float f); - void Clear(); + void clear(); [[nodiscard]] - Matrix Strip(size_t row, size_t column) const; + Matrix strip(size_t row, size_t column) const; [[nodiscard]] - float Minor(size_t i, size_t j) const; + float minor(size_t i, size_t j) const; [[nodiscard]] - float AlgComplement(size_t i, size_t j) const; + float alg_complement(size_t i, size_t j) const; [[nodiscard]] - float Determinant() const; + float determinant() const; [[nodiscard]] - const float* Raw() const; + const float* raw() const; Matrix& operator=(const Matrix& other); @@ -96,7 +96,7 @@ namespace omath Matrix operator/(float f) const; [[nodiscard]] - std::string ToString() const; + std::string to_string() const; ~Matrix(); diff --git a/include/omath/pathfinding/a_star.hpp b/include/omath/pathfinding/a_star.hpp index b2a315e8..02ec0a41 100644 --- a/include/omath/pathfinding/a_star.hpp +++ b/include/omath/pathfinding/a_star.hpp @@ -3,9 +3,9 @@ // #pragma once -#include #include "omath/pathfinding/navigation_mesh.hpp" #include "omath/vector3.hpp" +#include namespace omath::pathfinding { @@ -14,17 +14,17 @@ namespace omath::pathfinding { public: [[nodiscard]] - static std::vector> FindPath(const Vector3& start, const Vector3& end, - const NavigationMesh& navMesh); + static std::vector> find_path(const Vector3& start, const Vector3& end, + const NavigationMesh& nav_mesh); private: [[nodiscard]] static std::vector> - ReconstructFinalPath(const std::unordered_map, PathNode>& closedList, - const Vector3& current); + reconstruct_final_path(const std::unordered_map, PathNode>& closed_list, + const Vector3& current); [[nodiscard]] - static auto GetPerfectNode(const std::unordered_map, PathNode>& openList, - const Vector3& endVertex); + static auto get_perfect_node(const std::unordered_map, PathNode>& open_list, + const Vector3& endVertex); }; } // namespace omath::pathfinding diff --git a/include/omath/pathfinding/navigation_mesh.hpp b/include/omath/pathfinding/navigation_mesh.hpp index ce763e9a..e5ea0cbb 100644 --- a/include/omath/pathfinding/navigation_mesh.hpp +++ b/include/omath/pathfinding/navigation_mesh.hpp @@ -4,10 +4,10 @@ #pragma once +#include "omath/vector3.hpp" #include #include #include -#include "omath/vector3.hpp" namespace omath::pathfinding { @@ -21,18 +21,18 @@ namespace omath::pathfinding { public: [[nodiscard]] - std::expected, std::string> GetClosestVertex(const Vector3& point) const; + std::expected, std::string> get_closest_vertex(const Vector3& point) const; [[nodiscard]] - const std::vector>& GetNeighbors(const Vector3& vertex) const; + const std::vector>& get_neighbors(const Vector3& vertex) const; [[nodiscard]] - bool Empty() const; + bool empty() const; - [[nodiscard]] std::vector Serialize() const; + [[nodiscard]] std::vector serialize() const; - void Deserialize(const std::vector& raw); + void deserialize(const std::vector& raw); - std::unordered_map, std::vector>> m_verTextMap; + std::unordered_map, std::vector>> m_vertex_map; }; } // namespace omath::pathfinding diff --git a/include/omath/projectile_prediction/proj_pred_engine.hpp b/include/omath/projectile_prediction/proj_pred_engine.hpp index d507dbee..e4be97ec 100644 --- a/include/omath/projectile_prediction/proj_pred_engine.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine.hpp @@ -6,15 +6,14 @@ #include "omath/projectile_prediction/target.hpp" #include "omath/vector3.hpp" - namespace omath::projectile_prediction { class ProjPredEngine { public: [[nodiscard]] - virtual std::optional> MaybeCalculateAimPoint(const Projectile& projectile, - const Target& target) const = 0; + virtual std::optional> maybe_calculate_aim_point(const Projectile& projectile, + const Target& target) const = 0; virtual ~ProjPredEngine() = default; }; } // namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp b/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp index f4b925ec..85e3fc52 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp @@ -6,21 +6,23 @@ namespace omath::projectile_prediction { - class ProjPredEngineAVX2 final : public ProjPredEngine + class ProjPredEngineAvx2 final : public ProjPredEngine { public: - [[nodiscard]] std::optional> MaybeCalculateAimPoint(const Projectile& projectile, - const Target& target) const override; + [[nodiscard]] std::optional> maybe_calculate_aim_point(const Projectile& projectile, + const Target& target) const override; - - ProjPredEngineAVX2(float gravityConstant, float simulationTimeStep, float maximumSimulationTime); - ~ProjPredEngineAVX2() override = default; + ProjPredEngineAvx2(float gravity_constant, float simulation_time_step, float maximum_simulation_time); + ~ProjPredEngineAvx2() override = default; private: - [[nodiscard]] static std::optional CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, - float bulletGravity, float v0, float time); - const float m_gravityConstant; - const float m_simulationTimeStep; - const float m_maximumSimulationTime; + [[nodiscard]] static std::optional calculate_pitch(const Vector3& proj_origin, + const Vector3& target_pos, + float bullet_gravity, float v0, float time); + + // We use [[maybe_unused]] here since AVX2 is not available for ARM and ARM64 CPU + [[maybe_unused]] const float m_gravity_constant; + [[maybe_unused]] const float m_simulation_time_step; + [[maybe_unused]] const float m_maximum_simulation_time; }; } // namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index 8eb5a5ec..caa5cf5f 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -4,38 +4,36 @@ #pragma once -#include #include "omath/projectile_prediction/proj_pred_engine.hpp" #include "omath/projectile_prediction/projectile.hpp" #include "omath/projectile_prediction/target.hpp" #include "omath/vector3.hpp" - +#include namespace omath::projectile_prediction { class ProjPredEngineLegacy final : public ProjPredEngine { public: - explicit ProjPredEngineLegacy(float gravityConstant, float simulationTimeStep, float maximumSimulationTime, - float distanceTolerance); + explicit ProjPredEngineLegacy(float gravity_constant, float simulation_time_step, float maximum_simulation_time, + float distance_tolerance); [[nodiscard]] - std::optional> MaybeCalculateAimPoint(const Projectile& projectile, - const Target& target) const override; + std::optional> maybe_calculate_aim_point(const Projectile& projectile, + const Target& target) const override; private: - const float m_gravityConstant; - const float m_simulationTimeStep; - const float m_maximumSimulationTime; - const float m_distanceTolerance; + const float m_gravity_constant; + const float m_simulation_time_step; + const float m_maximum_simulation_time; + const float m_distance_tolerance; [[nodiscard]] - std::optional MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, - const Vector3& targetPosition) const; - + std::optional maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile, + const Vector3& target_position) const; [[nodiscard]] - bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, - float time) const; + bool is_projectile_reached_target(const Vector3& target_position, const Projectile& projectile, + float pitch, float time) const; }; } // namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/projectile.hpp b/include/omath/projectile_prediction/projectile.hpp index b32b4afc..d1188c5c 100644 --- a/include/omath/projectile_prediction/projectile.hpp +++ b/include/omath/projectile_prediction/projectile.hpp @@ -10,12 +10,11 @@ namespace omath::projectile_prediction class Projectile final { public: - [[nodiscard]] - Vector3 PredictPosition(float pitch, float yaw, float time, float gravity) const; + Vector3 predict_position(float pitch, float yaw, float time, float gravity) const; Vector3 m_origin; - float m_launchSpeed{}; - float m_gravityScale{}; + float m_launch_speed{}; + float m_gravity_scale{}; }; -} \ No newline at end of file +} // namespace omath::projectile_prediction \ No newline at end of file diff --git a/include/omath/projectile_prediction/target.hpp b/include/omath/projectile_prediction/target.hpp index a7808585..40e010c8 100644 --- a/include/omath/projectile_prediction/target.hpp +++ b/include/omath/projectile_prediction/target.hpp @@ -10,13 +10,12 @@ namespace omath::projectile_prediction class Target final { public: - [[nodiscard]] - constexpr Vector3 PredictPosition(const float time, const float gravity) const + constexpr Vector3 predict_position(const float time, const float gravity) const { auto predicted = m_origin + m_velocity * time; - if (m_isAirborne) + if (m_is_airborne) predicted.z -= gravity * std::pow(time, 2.f) * 0.5f; return predicted; @@ -24,6 +23,6 @@ namespace omath::projectile_prediction Vector3 m_origin; Vector3 m_velocity; - bool m_isAirborne{}; + bool m_is_airborne{}; }; -} \ No newline at end of file +} // namespace omath::projectile_prediction \ No newline at end of file diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index ca7b19d5..2abb511d 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -4,12 +4,12 @@ #pragma once +#include "omath/projection/error_codes.hpp" #include #include #include #include #include -#include "omath/projection/error_codes.hpp" namespace omath::projection { @@ -26,156 +26,149 @@ namespace omath::projection }; using FieldOfView = Angle; - template + template class Camera { public: virtual ~Camera() = default; - Camera(const Vector3& position, const ViewAnglesType& viewAngles, const ViewPort& viewPort, - const FieldOfView& fov, const float near, const float far) : - m_viewPort(viewPort), m_fieldOfView(fov), m_farPlaneDistance(far), m_nearPlaneDistance(near), - m_viewAngles(viewAngles), m_origin(position) + Camera(const Vector3& position, const ViewAnglesType& view_angles, const ViewPort& view_port, + const FieldOfView& fov, const float near, const float far) + : m_view_port(view_port), m_field_of_view(fov), m_far_plane_distance(far), m_near_plane_distance(near), + m_view_angles(view_angles), m_origin(position) { } protected: - virtual void LookAt(const Vector3& target) = 0; + virtual void look_at(const Vector3& target) = 0; - [[nodiscard]] virtual Mat4x4Type CalcViewMatrix() const = 0; + [[nodiscard]] virtual Mat4X4Type calc_view_matrix() const = 0; - [[nodiscard]] virtual Mat4x4Type CalcProjectionMatrix() const = 0; + [[nodiscard]] virtual Mat4X4Type calc_projection_matrix() const = 0; - [[nodiscard]] Mat4x4Type CalcViewProjectionMatrix() const + [[nodiscard]] Mat4X4Type calc_view_projection_matrix() const { - return CalcProjectionMatrix() * CalcViewMatrix(); + return calc_projection_matrix() * calc_view_matrix(); } public: - [[nodiscard]] const Mat4x4Type& GetViewProjectionMatrix() const + [[nodiscard]] const Mat4X4Type& get_view_projection_matrix() const { - if (!m_viewProjectionMatrix.has_value()) - m_viewProjectionMatrix = CalcViewProjectionMatrix(); + if (!m_view_projection_matrix.has_value()) + m_view_projection_matrix = calc_view_projection_matrix(); - return m_viewProjectionMatrix.value(); + return m_view_projection_matrix.value(); } - void SetFieldOfView(const FieldOfView& fov) + void set_field_of_view(const FieldOfView& fov) { - m_fieldOfView = fov; - m_viewProjectionMatrix = std::nullopt; + m_field_of_view = fov; + m_view_projection_matrix = std::nullopt; } - void SetNearPlane(const float near) + void set_near_plane(const float near) { - m_nearPlaneDistance = near; - m_viewProjectionMatrix = std::nullopt; + m_near_plane_distance = near; + m_view_projection_matrix = std::nullopt; } - void SetFarPlane(const float far) + void set_far_plane(const float far) { - m_farPlaneDistance = far; - m_viewProjectionMatrix = std::nullopt; + m_far_plane_distance = far; + m_view_projection_matrix = std::nullopt; } - void SetViewAngles(const ViewAnglesType& viewAngles) + void set_view_angles(const ViewAnglesType& view_angles) { - m_viewAngles = viewAngles; - m_viewProjectionMatrix = std::nullopt; + m_view_angles = view_angles; + m_view_projection_matrix = std::nullopt; } - void SetOrigin(const Vector3& origin) + void set_origin(const Vector3& origin) { m_origin = origin; - m_viewProjectionMatrix = std::nullopt; + m_view_projection_matrix = std::nullopt; } - void SetViewPort(const ViewPort& viewPort) + void set_view_port(const ViewPort& view_port) { - m_viewPort = viewPort; - m_viewProjectionMatrix = std::nullopt; + m_view_port = view_port; + m_view_projection_matrix = std::nullopt; } - [[nodiscard]] const FieldOfView& GetFieldOfView() const + [[nodiscard]] const FieldOfView& get_field_of_view() const { - return m_fieldOfView; + return m_field_of_view; } - [[nodiscard]] const float& GetNearPlane() const + [[nodiscard]] const float& get_near_plane() const { - return m_nearPlaneDistance; + return m_near_plane_distance; } - [[nodiscard]] const float& GetFarPlane() const + [[nodiscard]] const float& get_far_plane() const { - return m_farPlaneDistance; + return m_far_plane_distance; } - [[nodiscard]] const ViewAnglesType& GetViewAngles() const + [[nodiscard]] const ViewAnglesType& get_view_angles() const { - return m_viewAngles; + return m_view_angles; } - [[nodiscard]] const Vector3& GetOrigin() const + [[nodiscard]] const Vector3& get_origin() const { return m_origin; } - [[nodiscard]] std::expected, Error> WorldToScreen(const Vector3& worldPosition) const + [[nodiscard]] std::expected, Error> world_to_screen(const Vector3& world_position) const { - auto normalizedCords = WorldToViewPort(worldPosition); + auto normalized_cords = world_to_view_port(world_position); - if (!normalizedCords.has_value()) - return std::unexpected{normalizedCords.error()}; + if (!normalized_cords.has_value()) + return std::unexpected{normalized_cords.error()}; - - return NdcToScreenPosition(*normalizedCords); + return ndc_to_screen_position(*normalized_cords); } - [[nodiscard]] std::expected, Error> WorldToViewPort(const Vector3& worldPosition) const + [[nodiscard]] std::expected, Error> + world_to_view_port(const Vector3& world_position) const { - auto projected = GetViewProjectionMatrix() * - MatColumnFromVector(worldPosition); + auto projected = get_view_projection_matrix() + * mat_column_from_vector(world_position); - if (projected.At(3, 0) == 0.0f) + if (projected.at(3, 0) == 0.0f) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); - projected /= projected.At(3, 0); + projected /= projected.at(3, 0); - if (IsNdcOutOfBounds(projected)) + if (is_ndc_out_of_bounds(projected)) return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS); - return Vector3{projected.At(0, 0), projected.At(1, 0), projected.At(2, 0)}; + return Vector3{projected.at(0, 0), projected.at(1, 0), projected.at(2, 0)}; } protected: - ViewPort m_viewPort{}; - Angle m_fieldOfView; - - mutable std::optional m_viewProjectionMatrix; + ViewPort m_view_port{}; + Angle m_field_of_view; - float m_farPlaneDistance; - float m_nearPlaneDistance; + mutable std::optional m_view_projection_matrix; + float m_far_plane_distance; + float m_near_plane_distance; - ViewAnglesType m_viewAngles; + ViewAnglesType m_view_angles; Vector3 m_origin; private: - template - [[nodiscard]] - constexpr static bool IsNdcOutOfBounds(const Type& ndc) + template [[nodiscard]] + constexpr static bool is_ndc_out_of_bounds(const Type& ndc) { - return std::ranges::any_of(ndc.RawArray(), [](const auto& val) { return val < -1 || val > 1; }); + return std::ranges::any_of(ndc.raw_array(), [](const auto& val) { return val < -1 || val > 1; }); } - [[nodiscard]] Vector3 NdcToScreenPosition(const Vector3& ndc) const + [[nodiscard]] Vector3 ndc_to_screen_position(const Vector3& ndc) const { - return - { - (ndc.x + 1.f) / 2.f * m_viewPort.m_width, - (1.f - ndc.y) / 2.f * m_viewPort.m_height, - ndc.z - }; + return {(ndc.x + 1.f) / 2.f * m_view_port.m_width, (1.f - ndc.y) / 2.f * m_view_port.m_height, ndc.z}; } }; } // namespace omath::projection diff --git a/include/omath/projection/error_codes.hpp b/include/omath/projection/error_codes.hpp index 3c5d26ce..ae29fc07 100644 --- a/include/omath/projection/error_codes.hpp +++ b/include/omath/projection/error_codes.hpp @@ -5,7 +5,6 @@ #pragma once #include - namespace omath::projection { enum class Error : uint16_t diff --git a/include/omath/triangle.hpp b/include/omath/triangle.hpp index 5c006018..b5e9822d 100644 --- a/include/omath/triangle.hpp +++ b/include/omath/triangle.hpp @@ -6,15 +6,14 @@ namespace omath { - /* - |\ - | \ - a | \ hypot - | \ - ----- - b - */ - + /* + |\ + | \ + a | \ hypot + | \ + ----- + b + */ template class Triangle final @@ -31,52 +30,53 @@ namespace omath Vector3 m_vertex3; [[nodiscard]] - constexpr Vector3 CalculateNormal() const + constexpr Vector3 calculate_normal() const { - const auto b = SideBVector(); - const auto a = SideAVector(); - return b.Cross(a).Normalized(); + const auto b = side_b_vector(); + const auto a = side_a_vector(); + + return b.cross(a).normalized(); } [[nodiscard]] - float SideALength() const + float side_a_length() const { - return m_vertex1.DistTo(m_vertex2); + return m_vertex1.distance_to(m_vertex2); } [[nodiscard]] - float SideBLength() const + float side_b_length() const { - return m_vertex3.DistTo(m_vertex2); + return m_vertex3.distance_to(m_vertex2); } [[nodiscard]] - constexpr Vector3 SideAVector() const + constexpr Vector3 side_a_vector() const { return m_vertex1 - m_vertex2; } [[nodiscard]] - constexpr float Hypot() const + constexpr float hypot() const { - return m_vertex1.DistTo(m_vertex3); + return m_vertex1.distance_to(m_vertex3); } [[nodiscard]] - constexpr bool IsRectangular() const + constexpr bool is_rectangular() const { - const auto sideA = SideALength(); - const auto sideB = SideBLength(); - const auto hypot = Hypot(); + const auto side_a = side_a_length(); + const auto side_b = side_b_length(); + const auto hypot_value = hypot(); - return std::abs(sideA*sideA + sideB*sideB - hypot*hypot) <= 0.0001f; + return std::abs(side_a * side_a + side_b * side_b - hypot_value * hypot_value) <= 0.0001f; } [[nodiscard]] - constexpr Vector3 SideBVector() const + constexpr Vector3 side_b_vector() const { return m_vertex3 - m_vertex2; } [[nodiscard]] - constexpr Vector3 MidPoint() const + constexpr Vector3 mid_point() const { return (m_vertex1 + m_vertex2 + m_vertex3) / 3; } diff --git a/include/omath/vector2.hpp b/include/omath/vector2.hpp index 5753f60f..9a512a26 100644 --- a/include/omath/vector2.hpp +++ b/include/omath/vector2.hpp @@ -10,12 +10,11 @@ #include #endif - namespace omath { template - requires std::is_arithmetic_v + requires std::is_arithmetic_v class Vector2 { public: @@ -25,7 +24,7 @@ namespace omath // Constructors constexpr Vector2() = default; - constexpr Vector2(const Type& x, const Type& y) : x(x), y(y) + constexpr Vector2(const Type& x, const Type& y): x(x), y(y) { } @@ -108,30 +107,30 @@ namespace omath } // Basic vector operations - [[nodiscard]] Type DistTo(const Vector2& vOther) const + [[nodiscard]] Type distance_to(const Vector2& other) const { - return std::sqrt(DistToSqr(vOther)); + return std::sqrt(distance_to_sqr(other)); } - [[nodiscard]] constexpr Type DistToSqr(const Vector2& vOther) const + [[nodiscard]] constexpr Type distance_to_sqr(const Vector2& other) const { - return (x - vOther.x) * (x - vOther.x) + (y - vOther.y) * (y - vOther.y); + return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y); } - [[nodiscard]] constexpr Type Dot(const Vector2& vOther) const + [[nodiscard]] constexpr Type dot(const Vector2& other) const { - return x * vOther.x + y * vOther.y; + return x * other.x + y * other.y; } #ifndef _MSC_VER - [[nodiscard]] constexpr Type Length() const + [[nodiscard]] constexpr Type length() const { return std::hypot(this->x, this->y); } - [[nodiscard]] constexpr Vector2 Normalized() const + [[nodiscard]] constexpr Vector2 normalized() const { - const Type len = Length(); + const Type len = length(); return len > 0.f ? *this / len : *this; } #else @@ -146,12 +145,12 @@ namespace omath return len > 0.f ? *this / len : *this; } #endif - [[nodiscard]] constexpr Type LengthSqr() const + [[nodiscard]] constexpr Type length_sqr() const { return x * x + y * y; } - constexpr Vector2& Abs() + constexpr Vector2& abs() { // FIXME: Replace with std::abs, if it will become constexprable x = x < 0 ? -x : x; @@ -186,20 +185,20 @@ namespace omath } // Sum of elements - [[nodiscard]] constexpr Type Sum() const + [[nodiscard]] constexpr Type sum() const { return x + y; } [[nodiscard]] - constexpr std::tuple AsTuple() const + constexpr std::tuple as_tuple() const { return std::make_tuple(x, y); } #ifdef OMATH_IMGUI_INTEGRATION [[nodiscard]] - ImVec2 ToImVec2() const + ImVec2 to_im_vec2() const { return {static_cast(this->x), static_cast(this->y)}; } diff --git a/include/omath/vector3.hpp b/include/omath/vector3.hpp index 683e4f41..8f266096 100644 --- a/include/omath/vector3.hpp +++ b/include/omath/vector3.hpp @@ -4,11 +4,11 @@ #pragma once +#include "omath/angle.hpp" +#include "omath/vector2.hpp" #include #include #include -#include "omath/angle.hpp" -#include "omath/vector2.hpp" namespace omath { @@ -18,13 +18,16 @@ namespace omath IMPOSSIBLE_BETWEEN_ANGLE, }; - template requires std::is_arithmetic_v + template + requires std::is_arithmetic_v class Vector3 : public Vector2 { public: Type z = static_cast(0); - constexpr Vector3(const Type& x, const Type& y, const Type& z) : Vector2(x, y), z(z) { } - constexpr Vector3() : Vector2() {}; + constexpr Vector3(const Type& x, const Type& y, const Type& z): Vector2(x, y), z(z) + { + } + constexpr Vector3(): Vector2() {}; [[nodiscard]] constexpr bool operator==(const Vector3& src) const { @@ -100,72 +103,71 @@ namespace omath return *this; } - constexpr Vector3& Abs() + constexpr Vector3& abs() { - Vector2::Abs(); + Vector2::abs(); z = z < 0.f ? -z : z; return *this; } - [[nodiscard]] constexpr Type DistToSqr(const Vector3& vOther) const + [[nodiscard]] constexpr Type distance_to_sqr(const Vector3& other) const { - return (*this - vOther).LengthSqr(); + return (*this - other).length_sqr(); } - [[nodiscard]] constexpr Type Dot(const Vector3& vOther) const + [[nodiscard]] constexpr Type dot(const Vector3& other) const { - return Vector2::Dot(vOther) + z * vOther.z; + return Vector2::dot(other) + z * other.z; } #ifndef _MSC_VER - [[nodiscard]] constexpr Type Length() const + [[nodiscard]] constexpr Type length() const { return std::hypot(this->x, this->y, z); } - [[nodiscard]] constexpr Type Length2D() const + [[nodiscard]] constexpr Type length_2d() const { - return Vector2::Length(); + return Vector2::length(); } - [[nodiscard]] Type DistTo(const Vector3& vOther) const + [[nodiscard]] Type distance_to(const Vector3& other) const { - return (*this - vOther).Length(); + return (*this - other).length(); } - [[nodiscard]] constexpr Vector3 Normalized() const + [[nodiscard]] constexpr Vector3 normalized() const { - const Type length = this->Length(); + const Type length_value = this->length(); - return length != 0 ? *this / length : *this; + return length_value != 0 ? *this / length_value : *this; } #else - [[nodiscard]] Type Length() const + [[nodiscard]] Type length() const { return std::hypot(this->x, this->y, z); } - [[nodiscard]] Vector3 Normalized() const + [[nodiscard]] Vector3 normalized() const { const Type length = this->Length(); return length != 0 ? *this / length : *this; } - [[nodiscard]] Type Length2D() const + [[nodiscard]] Type length_2d() const { return Vector2::Length(); } - [[nodiscard]] Type DistTo(const Vector3& vOther) const + [[nodiscard]] Type distance_to(const Vector3& vOther) const { return (*this - vOther).Length(); } #endif - - [[nodiscard]] constexpr Type LengthSqr() const + [[nodiscard]] constexpr Type length_sqr() const { - return Vector2::LengthSqr() + z * z; + return Vector2::length_sqr() + z * z; } [[nodiscard]] constexpr Vector3 operator-() const @@ -203,79 +205,69 @@ namespace omath return {this->x / v.x, this->y / v.y, z / v.z}; } - [[nodiscard]] constexpr Vector3 Cross(const Vector3 &v) const + [[nodiscard]] constexpr Vector3 cross(const Vector3& v) const { - return - { - this->y * v.z - z * v.y, - z * v.x - this->x * v.z, - this->x * v.y - this->y * v.x - }; + return {this->y * v.z - z * v.y, z * v.x - this->x * v.z, this->x * v.y - this->y * v.x}; } - [[nodiscard]] constexpr Type Sum() const + [[nodiscard]] constexpr Type sum() const { - return Sum2D() + z; + return sum_2d() + z; } [[nodiscard]] std::expected, Vector3Error> - AngleBetween(const Vector3& other) const + angle_between(const Vector3& other) const { - const auto bottom = Length() * other.Length(); + const auto bottom = length() * other.length(); if (bottom == 0.f) return std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE); - return Angle::FromRadians(std::acos(Dot(other) / bottom)); + return Angle::from_radians(std::acos(dot(other) / bottom)); } - [[nodiscard]] bool IsPerpendicular(const Vector3& other) const + [[nodiscard]] bool is_perpendicular(const Vector3& other) const { - if (const auto angle = AngleBetween(other)) - return angle->AsDegrees() == 90.f; + if (const auto angle = angle_between(other)) + return angle->as_degrees() == 90.f; return false; } - [[nodiscard]] constexpr Type Sum2D() const + [[nodiscard]] constexpr Type sum_2d() const { - return Vector2::Sum(); + return Vector2::sum(); } - [[nodiscard]] constexpr std::tuple AsTuple() const + [[nodiscard]] constexpr std::tuple as_tuple() const { return std::make_tuple(this->x, this->y, z); } - [[nodiscard]] Vector3 ViewAngleTo(const Vector3 &other) const + [[nodiscard]] Vector3 view_angle_to(const Vector3& other) const { - const float distance = DistTo(other); + const float distance = distance_to(other); const auto delta = other - *this; - return - { - angles::RadiansToDegrees(std::asin(delta.z / distance)), - angles::RadiansToDegrees(std::atan2(delta.y, delta.x)), - 0 - }; + return {angles::radians_to_degrees(std::asin(delta.z / distance)), + angles::radians_to_degrees(std::atan2(delta.y, delta.x)), 0}; } }; -} +} // namespace omath // ReSharper disable once CppRedundantNamespaceDefinition namespace std { - template<> - struct hash> + template<> struct hash> { std::size_t operator()(const omath::Vector3& vec) const noexcept { std::size_t hash = 0; constexpr std::hash hasher; - hash ^= hasher(vec.x) + 0x9e3779b9 + (hash<<6) + (hash>>2); - hash ^= hasher(vec.y) + 0x9e3779b9 + (hash<<6) + (hash>>2); - hash ^= hasher(vec.z) + 0x9e3779b9 + (hash<<6) + (hash>>2); + hash ^= hasher(vec.x) + 0x9e3779b9 + (hash << 6) + (hash >> 2); + hash ^= hasher(vec.y) + 0x9e3779b9 + (hash << 6) + (hash >> 2); + hash ^= hasher(vec.z) + 0x9e3779b9 + (hash << 6) + (hash >> 2); return hash; } }; -} +} // namespace std diff --git a/include/omath/vector4.hpp b/include/omath/vector4.hpp index e50528fb..33d8380c 100644 --- a/include/omath/vector4.hpp +++ b/include/omath/vector4.hpp @@ -6,17 +6,18 @@ #include #include - namespace omath { - template + template class Vector4 : public Vector3 { public: Type w; - constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w) : Vector3(x, y, z), w(w) {} - constexpr Vector4() : Vector3(), w(0) {}; + constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w): Vector3(x, y, z), w(w) + { + } + constexpr Vector4(): Vector3(), w(0) {}; [[nodiscard]] constexpr bool operator==(const Vector4& src) const @@ -77,29 +78,29 @@ namespace omath return *this; } - [[nodiscard]] constexpr Type LengthSqr() const + [[nodiscard]] constexpr Type length_sqr() const { - return Vector3::LengthSqr() + w * w; + return Vector3::length_sqr() + w * w; } - [[nodiscard]] constexpr Type Dot(const Vector4& vOther) const + [[nodiscard]] constexpr Type dot(const Vector4& other) const { - return Vector3::Dot(vOther) + w * vOther.w; + return Vector3::dot(other) + w * other.w; } - [[nodiscard]] Vector3 Length() const + [[nodiscard]] Vector3 length() const { - return std::sqrt(LengthSqr()); + return std::sqrt(length_sqr()); } - constexpr Vector4& Abs() + constexpr Vector4& abs() { - Vector3::Abs(); + Vector3::abs(); w = w < 0.f ? -w : w; return *this; } - constexpr Vector4& Clamp(const Type& min, const Type& max) + constexpr Vector4& clamp(const Type& min, const Type& max) { this->x = std::clamp(this->x, min, max); this->y = std::clamp(this->y, min, max); @@ -151,23 +152,22 @@ namespace omath } [[nodiscard]] - constexpr Type Sum() const + constexpr Type sum() const { - return Vector3::Sum() + w; + return Vector3::sum() + w; } #ifdef OMATH_IMGUI_INTEGRATION [[nodiscard]] ImVec4 ToImVec4() const { - return - { - static_cast(this->x), - static_cast(this->y), - static_cast(this->z), - static_cast(w), + return { + static_cast(this->x), + static_cast(this->y), + static_cast(this->z), + static_cast(w), }; } #endif }; -} +} // namespace omath diff --git a/include/omath/view_angles.hpp b/include/omath/view_angles.hpp index d744f6ba..cd63640a 100644 --- a/include/omath/view_angles.hpp +++ b/include/omath/view_angles.hpp @@ -12,4 +12,4 @@ namespace omath YawType yaw; RollType roll; }; -} +} // namespace omath diff --git a/source/3d_primitives/box.cpp b/source/3d_primitives/box.cpp index 0e45eaaa..81da0bb6 100644 --- a/source/3d_primitives/box.cpp +++ b/source/3d_primitives/box.cpp @@ -3,27 +3,25 @@ // #include "omath/3d_primitives/box.hpp" - namespace omath::primitives { - std::array>, 12> CreateBox(const Vector3& top, const Vector3& bottom, - const Vector3& dirForward, - const Vector3& dirRight, - const float ratio) + std::array>, 12> create_box(const Vector3& top, const Vector3& bottom, + const Vector3& dir_forward, + const Vector3& dir_right, const float ratio) { - const auto height = top.DistTo(bottom); - const auto sideSize = height / ratio; + const auto height = top.distance_to(bottom); + const auto side_size = height / ratio; // corner layout (0‑3 bottom, 4‑7 top) std::array, 8> p; - p[0] = bottom + (dirForward + dirRight) * sideSize; // front‑right‑bottom - p[1] = bottom + (dirForward - dirRight) * sideSize; // front‑left‑bottom - p[2] = bottom + (-dirForward + dirRight) * sideSize; // back‑right‑bottom - p[3] = bottom + (-dirForward - dirRight) * sideSize; // back‑left‑bottom - p[4] = top + (dirForward + dirRight) * sideSize; // front‑right‑top - p[5] = top + (dirForward - dirRight) * sideSize; // front‑left‑top - p[6] = top + (-dirForward + dirRight) * sideSize; // back‑right‑top - p[7] = top + (-dirForward - dirRight) * sideSize; // back‑left‑top + p[0] = bottom + (dir_forward + dir_right) * side_size; // front‑right‑bottom + p[1] = bottom + (dir_forward - dir_right) * side_size; // front‑left‑bottom + p[2] = bottom + (-dir_forward + dir_right) * side_size; // back‑right‑bottom + p[3] = bottom + (-dir_forward - dir_right) * side_size; // back‑left‑bottom + p[4] = top + (dir_forward + dir_right) * side_size; // front‑right‑top + p[5] = top + (dir_forward - dir_right) * side_size; // front‑left‑top + p[6] = top + (-dir_forward + dir_right) * side_size; // back‑right‑top + p[7] = top + (-dir_forward - dir_right) * side_size; // back‑left‑top std::array>, 12> poly; @@ -53,4 +51,4 @@ namespace omath::primitives return poly; } -} +} // namespace omath::primitives diff --git a/source/collision/line_tracer.cpp b/source/collision/line_tracer.cpp index 5bf78375..06f22c7f 100644 --- a/source/collision/line_tracer.cpp +++ b/source/collision/line_tracer.cpp @@ -5,63 +5,58 @@ namespace omath::collision { - bool LineTracer::CanTraceLine(const Ray& ray, const Triangle>& triangle) + bool LineTracer::can_trace_line(const Ray& ray, const Triangle>& triangle) { - return GetRayHitPoint(ray, triangle) == ray.end; + return get_ray_hit_point(ray, triangle) == ray.end; } - Vector3 Ray::DirectionVector() const + Vector3 Ray::direction_vector() const { return end - start; } - Vector3 Ray::DirectionVectorNormalized() const + Vector3 Ray::direction_vector_normalized() const { - return DirectionVector().Normalized(); + return direction_vector().normalized(); } - Vector3 LineTracer::GetRayHitPoint(const Ray& ray, const Triangle>& triangle) + Vector3 LineTracer::get_ray_hit_point(const Ray& ray, const Triangle>& triangle) { - constexpr float kEpsilon = std::numeric_limits::epsilon(); + constexpr float k_epsilon = std::numeric_limits::epsilon(); - const auto sideA = triangle.SideAVector(); - const auto sideB = triangle.SideBVector(); + const auto side_a = triangle.side_a_vector(); + const auto side_b = triangle.side_b_vector(); + const auto ray_dir = ray.direction_vector(); - const auto rayDir = ray.DirectionVector(); + const auto p = ray_dir.cross(side_b); + const auto det = side_a.dot(p); - const auto p = rayDir.Cross(sideB); - const auto det = sideA.Dot(p); - - - if (std::abs(det) < kEpsilon) + if (std::abs(det) < k_epsilon) return ray.end; - const auto invDet = 1.0f / det; + const auto inv_det = 1.0f / det; const auto t = ray.start - triangle.m_vertex2; - const auto u = t.Dot(p) * invDet; - + const auto u = t.dot(p) * inv_det; - if ((u < 0 && std::abs(u) > kEpsilon) || (u > 1 && std::abs(u - 1) > kEpsilon)) + if ((u < 0 && std::abs(u) > k_epsilon) || (u > 1 && std::abs(u - 1) > k_epsilon)) return ray.end; - const auto q = t.Cross(sideA); - const auto v = rayDir.Dot(q) * invDet; + const auto q = t.cross(side_a); + const auto v = ray_dir.dot(q) * inv_det; - - if ((v < 0 && std::abs(v) > kEpsilon) || (u + v > 1 && std::abs(u + v - 1) > kEpsilon)) + if ((v < 0 && std::abs(v) > k_epsilon) || (u + v > 1 && std::abs(u + v - 1) > k_epsilon)) return ray.end; - const auto tHit = sideB.Dot(q) * invDet; - + const auto t_hit = side_b.dot(q) * inv_det; if (ray.infinite_length) { - if (tHit <= kEpsilon) + if (t_hit <= k_epsilon) return ray.end; } - else if (tHit <= kEpsilon || tHit > 1.0f - kEpsilon) + else if (t_hit <= k_epsilon || t_hit > 1.0f - k_epsilon) return ray.end; - return ray.start + rayDir * tHit; + return ray.start + ray_dir * t_hit; } } // namespace omath::collision diff --git a/source/color.cpp b/source/color.cpp index 981ae737..090725ca 100644 --- a/source/color.cpp +++ b/source/color.cpp @@ -6,7 +6,6 @@ #include #include - namespace omath { diff --git a/source/engines/iw_engine/camera.cpp b/source/engines/iw_engine/camera.cpp index 6261e086..68849f9e 100644 --- a/source/engines/iw_engine/camera.cpp +++ b/source/engines/iw_engine/camera.cpp @@ -7,28 +7,27 @@ namespace omath::iw_engine { - Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, - const Angle& fov, const float near, const float far) : - projection::Camera(position, viewAngles, viewPort, fov, near, far) + Camera::Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, + const Angle& fov, const float near, const float far) + : projection::Camera(position, view_angles, view_port, fov, near, far) { } - void Camera::LookAt([[maybe_unused]] const Vector3& target) + void Camera::look_at([[maybe_unused]] const Vector3& target) { - const float distance = m_origin.DistTo(target); + const float distance = m_origin.distance_to(target); const auto delta = target - m_origin; - - m_viewAngles.pitch = PitchAngle::FromRadians(std::asin(delta.z / distance)); - m_viewAngles.yaw = -YawAngle::FromRadians(std::atan2(delta.y, delta.x)); - m_viewAngles.roll = RollAngle::FromRadians(0.f); + m_view_angles.pitch = PitchAngle::from_radians(std::asin(delta.z / distance)); + m_view_angles.yaw = -YawAngle::from_radians(std::atan2(delta.y, delta.x)); + m_view_angles.roll = RollAngle::from_radians(0.f); } - Mat4x4 Camera::CalcViewMatrix() const + Mat4X4 Camera::calc_view_matrix() const { - return iw_engine::CalcViewMatrix(m_viewAngles, m_origin); + return iw_engine::calc_view_matrix(m_view_angles, m_origin); } - Mat4x4 Camera::CalcProjectionMatrix() const + Mat4X4 Camera::calc_projection_matrix() const { - return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, - m_farPlaneDistance); + return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.AspectRatio(), + m_near_plane_distance, m_far_plane_distance); } -} // namespace omath::openg \ No newline at end of file +} // namespace omath::iw_engine \ No newline at end of file diff --git a/source/engines/iw_engine/formulas.cpp b/source/engines/iw_engine/formulas.cpp index 0c293fee..37635dc9 100644 --- a/source/engines/iw_engine/formulas.cpp +++ b/source/engines/iw_engine/formulas.cpp @@ -3,50 +3,49 @@ // #include "omath/engines/iw_engine/formulas.hpp" - namespace omath::iw_engine { - Vector3 ForwardVector(const ViewAngles& angles) + Vector3 forward_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsForward); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Vector3 RightVector(const ViewAngles& angles) + Vector3 right_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsRight); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Vector3 UpVector(const ViewAngles& angles) + Vector3 up_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsUp); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Mat4x4 RotationMatrix(const ViewAngles& angles) + Mat4X4 rotation_matrix(const ViewAngles& angles) { - return MatRotationAxisZ(angles.yaw) * MatRotationAxisY(angles.pitch) * MatRotationAxisX(angles.roll); + return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll); } - Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) { - return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + return mat_camera_view(forward_vector(angles), right_vector(angles), up_vector(angles), cam_origin); } - Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, - const float far) + Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, + const float far) { // NOTE: Need magic number to fix fov calculation, since IW engine inherit Quake proj matrix calculation - constexpr auto kMultiplyFactor = 0.75f; + constexpr auto k_multiply_factor = 0.75f; - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; + const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f) * k_multiply_factor; return { - {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, 1.f / (fovHalfTan), 0, 0}, + {1.f / (aspect_ratio * fov_half_tan), 0, 0, 0}, + {0, 1.f / (fov_half_tan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, 1, 0}, }; diff --git a/source/engines/opengl_engine/camera.cpp b/source/engines/opengl_engine/camera.cpp index 07d4f508..8fc0ec59 100644 --- a/source/engines/opengl_engine/camera.cpp +++ b/source/engines/opengl_engine/camera.cpp @@ -4,32 +4,30 @@ #include "omath/engines/opengl_engine/camera.hpp" #include "omath/engines/opengl_engine/formulas.hpp" - namespace omath::opengl_engine { - Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, - const Angle& fov, const float near, const float far) : - projection::Camera(position, viewAngles, viewPort, fov, near, far) + Camera::Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, + const Angle& fov, const float near, const float far) + : projection::Camera(position, view_angles, view_port, fov, near, far) { } - void Camera::LookAt([[maybe_unused]] const Vector3& target) + void Camera::look_at([[maybe_unused]] const Vector3& target) { - const float distance = m_origin.DistTo(target); + const float distance = m_origin.distance_to(target); const auto delta = target - m_origin; - - m_viewAngles.pitch = PitchAngle::FromRadians(std::asin(delta.z / distance)); - m_viewAngles.yaw = -YawAngle::FromRadians(std::atan2(delta.y, delta.x)); - m_viewAngles.roll = RollAngle::FromRadians(0.f); + m_view_angles.pitch = PitchAngle::from_radians(std::asin(delta.z / distance)); + m_view_angles.yaw = -YawAngle::from_radians(std::atan2(delta.y, delta.x)); + m_view_angles.roll = RollAngle::from_radians(0.f); } - Mat4x4 Camera::CalcViewMatrix() const + Mat4X4 Camera::calc_view_matrix() const { - return opengl_engine::CalcViewMatrix(m_viewAngles, m_origin); + return opengl_engine::calc_view_matrix(m_view_angles, m_origin); } - Mat4x4 Camera::CalcProjectionMatrix() const + Mat4X4 Camera::calc_projection_matrix() const { - return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, - m_farPlaneDistance); + return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.AspectRatio(), + m_near_plane_distance, m_far_plane_distance); } -} // namespace omath::opengl +} // namespace omath::opengl_engine diff --git a/source/engines/opengl_engine/formulas.cpp b/source/engines/opengl_engine/formulas.cpp index 316ed118..79868b53 100644 --- a/source/engines/opengl_engine/formulas.cpp +++ b/source/engines/opengl_engine/formulas.cpp @@ -3,47 +3,48 @@ // #include "omath/engines/opengl_engine/formulas.hpp" - namespace omath::opengl_engine { - Vector3 ForwardVector(const ViewAngles& angles) + Vector3 forward_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsForward); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Vector3 RightVector(const ViewAngles& angles) + Vector3 right_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsRight); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Vector3 UpVector(const ViewAngles& angles) + Vector3 up_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsUp); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) { - return MatCameraView(-ForwardVector(angles), RightVector(angles), - UpVector(angles), cam_origin); + return mat_camera_view(-forward_vector(angles), right_vector(angles), + up_vector(angles), cam_origin); } - Mat4x4 RotationMatrix(const ViewAngles& angles) + Mat4X4 rotation_matrix(const ViewAngles& angles) { - return MatRotationAxisX(-angles.pitch) * - MatRotationAxisY(-angles.yaw) * - MatRotationAxisZ(angles.roll); + return mat_rotation_axis_x(-angles.pitch) + * mat_rotation_axis_y(-angles.yaw) + * mat_rotation_axis_z(angles.roll); } - Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, - const float far) + Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, + const float far) { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f); return { - {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, 1.f / (fovHalfTan), 0, 0}, + {1.f / (aspect_ratio * fov_half_tan), 0, 0, 0}, + {0, 1.f / (fov_half_tan), 0, 0}, {0, 0, -(far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, -1, 0}, }; diff --git a/source/engines/source_engine/camera.cpp b/source/engines/source_engine/camera.cpp index 2906b550..efc059c7 100644 --- a/source/engines/source_engine/camera.cpp +++ b/source/engines/source_engine/camera.cpp @@ -4,34 +4,32 @@ #include "omath/engines/source_engine/camera.hpp" #include "omath/engines/source_engine/formulas.hpp" - namespace omath::source_engine { - Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, - const projection::FieldOfView& fov, const float near, const float far) : - projection::Camera(position, viewAngles, viewPort, fov, near, far) + Camera::Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, + const projection::FieldOfView& fov, const float near, const float far) + : projection::Camera(position, view_angles, view_port, fov, near, far) { } - void Camera::LookAt(const Vector3& target) + void Camera::look_at(const Vector3& target) { - const float distance = m_origin.DistTo(target); + const float distance = m_origin.distance_to(target); const auto delta = target - m_origin; - - m_viewAngles.pitch = PitchAngle::FromRadians(std::asin(delta.z / distance)); - m_viewAngles.yaw = -YawAngle::FromRadians(std::atan2(delta.y, delta.x)); - m_viewAngles.roll = RollAngle::FromRadians(0.f); + m_view_angles.pitch = PitchAngle::from_radians(std::asin(delta.z / distance)); + m_view_angles.yaw = -YawAngle::from_radians(std::atan2(delta.y, delta.x)); + m_view_angles.roll = RollAngle::from_radians(0.f); } - Mat4x4 Camera::CalcViewMatrix() const + Mat4X4 Camera::calc_view_matrix() const { - return source_engine::CalcViewMatrix(m_viewAngles, m_origin); + return source_engine::calc_view_matrix(m_view_angles, m_origin); } - Mat4x4 Camera::CalcProjectionMatrix() const + Mat4X4 Camera::calc_projection_matrix() const { - return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, - m_farPlaneDistance); + return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.AspectRatio(), + m_near_plane_distance, m_far_plane_distance); } -} // namespace omath::source +} // namespace omath::source_engine diff --git a/source/engines/source_engine/formulas.cpp b/source/engines/source_engine/formulas.cpp index 5dcca886..fe869e4e 100644 --- a/source/engines/source_engine/formulas.cpp +++ b/source/engines/source_engine/formulas.cpp @@ -3,50 +3,49 @@ // #include - namespace omath::source_engine { - Vector3 ForwardVector(const ViewAngles& angles) + Vector3 forward_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsForward); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Mat4x4 RotationMatrix(const ViewAngles& angles) + Mat4X4 rotation_matrix(const ViewAngles& angles) { - return MatRotationAxisZ(angles.yaw) * MatRotationAxisY(angles.pitch) * MatRotationAxisX(angles.roll); + return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll); } - Vector3 RightVector(const ViewAngles& angles) + Vector3 right_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsRight); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Vector3 UpVector(const ViewAngles& angles) + Vector3 up_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsUp); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) { - return MatCameraView(ForwardVector(angles), RightVector(angles), UpVector(angles), cam_origin); + return mat_camera_view(forward_vector(angles), right_vector(angles), up_vector(angles), cam_origin); } - Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, - const float far) + Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, + const float far) { // NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation - constexpr auto kMultiplyFactor = 0.75f; + constexpr auto k_multiply_factor = 0.75f; - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f) * kMultiplyFactor; + const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f) * k_multiply_factor; return { - {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, 1.f / (fovHalfTan), 0, 0}, + {1.f / (aspect_ratio * fov_half_tan), 0, 0, 0}, + {0, 1.f / (fov_half_tan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, 1, 0}, }; diff --git a/source/engines/unity_engine/camera.cpp b/source/engines/unity_engine/camera.cpp index 5270f5ac..836b4e9e 100644 --- a/source/engines/unity_engine/camera.cpp +++ b/source/engines/unity_engine/camera.cpp @@ -4,25 +4,24 @@ #include #include - namespace omath::unity_engine { - Camera::Camera(const Vector3& position, const ViewAngles& viewAngles, const projection::ViewPort& viewPort, - const projection::FieldOfView& fov, const float near, const float far) : - projection::Camera(position, viewAngles, viewPort, fov, near, far) + Camera::Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, + const projection::FieldOfView& fov, const float near, const float far) + : projection::Camera(position, view_angles, view_port, fov, near, far) { } - void Camera::LookAt([[maybe_unused]] const Vector3& target) + void Camera::look_at([[maybe_unused]] const Vector3& target) { throw std::runtime_error("Not implemented"); } - Mat4x4 Camera::CalcViewMatrix() const + Mat4X4 Camera::calc_view_matrix() const { - return unity_engine::CalcViewMatrix(m_viewAngles, m_origin); + return unity_engine::calc_view_matrix(m_view_angles, m_origin); } - Mat4x4 Camera::CalcProjectionMatrix() const + Mat4X4 Camera::calc_projection_matrix() const { - return CalcPerspectiveProjectionMatrix(m_fieldOfView.AsDegrees(), m_viewPort.AspectRatio(), m_nearPlaneDistance, - m_farPlaneDistance); + return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.AspectRatio(), + m_near_plane_distance, m_far_plane_distance); } } // namespace omath::unity_engine diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp index 4477253a..ed6579fd 100644 --- a/source/engines/unity_engine/formulas.cpp +++ b/source/engines/unity_engine/formulas.cpp @@ -3,47 +3,45 @@ // #include "omath/engines/unity_engine/formulas.hpp" - - namespace omath::unity_engine { - Vector3 ForwardVector(const ViewAngles& angles) + Vector3 forward_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsForward); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Vector3 RightVector(const ViewAngles& angles) + Vector3 right_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsRight); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Vector3 UpVector(const ViewAngles& angles) + Vector3 up_vector(const ViewAngles& angles) { - const auto vec = RotationMatrix(angles) * MatColumnFromVector(kAbsUp); + 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)}; + return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)}; } - Mat4x4 CalcViewMatrix(const ViewAngles& angles, const Vector3& cam_origin) + Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) { - return MatCameraView(ForwardVector(angles), -RightVector(angles), - UpVector(angles), cam_origin); + return mat_camera_view(forward_vector(angles), -right_vector(angles), + up_vector(angles), cam_origin); } - Mat4x4 RotationMatrix(const ViewAngles& angles) + Mat4X4 rotation_matrix(const ViewAngles& angles) { - return MatRotationAxisX(angles.pitch) * - MatRotationAxisY(angles.yaw) * - MatRotationAxisZ(angles.roll); + return mat_rotation_axis_x(angles.pitch) + * mat_rotation_axis_y(angles.yaw) + * mat_rotation_axis_z(angles.roll); } - Mat4x4 CalcPerspectiveProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, - const float far) + Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, + const float far) { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f); return { - {1.f / (aspectRatio * fovHalfTan), 0, 0, 0}, - {0, 1.f / (fovHalfTan), 0, 0}, + {1.f / (aspect_ratio * fov_half_tan), 0, 0, 0}, + {0, 1.f / (fov_half_tan), 0, 0}, {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, {0, 0, -1.f, 0}, }; diff --git a/source/matrix.cpp b/source/matrix.cpp index 7782d566..428aaa87 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -1,14 +1,11 @@ #include "omath/matrix.hpp" #include "omath/angles.hpp" #include "omath/vector3.hpp" - - #include #include #include #include - namespace omath { Matrix::Matrix(const size_t rows, const size_t columns) @@ -21,7 +18,7 @@ namespace omath m_data = std::make_unique(m_rows * m_columns); - Set(0.f); + set(0.f); } Matrix::Matrix(const std::initializer_list>& rows) @@ -29,7 +26,6 @@ namespace omath m_rows = rows.size(); m_columns = rows.begin()->size(); - for (const auto& row: rows) if (row.size() != m_columns) throw std::invalid_argument("All rows must have the same number of columns."); @@ -41,7 +37,7 @@ namespace omath { size_t j = 0; for (const auto& value: row) - At(i, j++) = value; + at(i, j++) = value; ++i; } } @@ -55,29 +51,28 @@ namespace omath for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - At(i, j) = other.At(i, j); + at(i, j) = other.at(i, j); } - Matrix::Matrix(const size_t rows, const size_t columns, const float* pRaw) + Matrix::Matrix(const size_t rows, const size_t columns, const float* raw_data) { m_rows = rows; m_columns = columns; - m_data = std::make_unique(m_rows * m_columns); for (size_t i = 0; i < rows * columns; ++i) - At(i / rows, i % columns) = pRaw[i]; + at(i / rows, i % columns) = raw_data[i]; } - size_t Matrix::RowCount() const noexcept + size_t Matrix::row_count() const noexcept { return m_rows; } - + float& Matrix::operator[](const size_t row, const size_t column) { - return At(row, column); + return at(row, column); } Matrix::Matrix(Matrix&& other) noexcept @@ -92,35 +87,35 @@ namespace omath other.m_data = nullptr; } - size_t Matrix::ColumnsCount() const noexcept + size_t Matrix::columns_count() const noexcept { return m_columns; } - std::pair Matrix::Size() const noexcept + std::pair Matrix::size() const noexcept { - return {RowCount(), ColumnsCount()}; + return {row_count(), columns_count()}; } - float& Matrix::At(const size_t iRow, const size_t iCol) + float& Matrix::at(const size_t row, const size_t col) { - return const_cast(std::as_const(*this).At(iRow, iCol)); + return const_cast(std::as_const(*this).at(row, col)); } - float Matrix::Sum() + float Matrix::sum() { float sum = 0; - for (size_t i = 0; i < RowCount(); i++) - for (size_t j = 0; j < ColumnsCount(); j++) - sum += At(i, j); + for (size_t i = 0; i < row_count(); i++) + for (size_t j = 0; j < columns_count(); j++) + sum += at(i, j); return sum; } - const float& Matrix::At(const size_t iRow, const size_t iCol) const + const float& Matrix::at(const size_t row, const size_t col) const { - return m_data[iRow * m_columns + iCol]; + return m_data[row * m_columns + col]; } Matrix Matrix::operator*(const Matrix& other) const @@ -128,15 +123,14 @@ namespace omath if (m_columns != other.m_rows) throw std::runtime_error("n != m"); - auto outMat = Matrix(m_rows, other.m_columns); + auto out_mat = Matrix(m_rows, other.m_columns); for (size_t d = 0; d < m_rows; ++d) for (size_t i = 0; i < other.m_columns; ++i) for (size_t j = 0; j < other.m_rows; ++j) - outMat.At(d, i) += At(d, j) * other.At(j, i); - + out_mat.at(d, i) += at(d, j) * other.at(j, i); - return outMat; + return out_mat; } Matrix& Matrix::operator*=(const Matrix& other) @@ -150,22 +144,22 @@ namespace omath auto out = *this; for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - out.At(i, j) *= f; + out.at(i, j) *= f; return out; } Matrix& Matrix::operator*=(const float f) { - for (size_t i = 0; i < RowCount(); i++) - for (size_t j = 0; j < ColumnsCount(); j++) - At(i, j) *= f; + for (size_t i = 0; i < row_count(); i++) + for (size_t j = 0; j < columns_count(); j++) + at(i, j) *= f; return *this; } - void Matrix::Clear() + void Matrix::clear() { - Set(0.f); + set(0.f); } Matrix& Matrix::operator=(const Matrix& other) @@ -175,7 +169,7 @@ namespace omath for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - At(i, j) = other.At(i, j); + at(i, j) = other.at(i, j); return *this; } @@ -199,7 +193,7 @@ namespace omath { for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - At(i, j) /= f; + at(i, j) /= f; return *this; } @@ -209,12 +203,12 @@ namespace omath auto out = *this; for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - out.At(i, j) /= f; + out.at(i, j) /= f; return out; } - std::string Matrix::ToString() const + std::string Matrix::to_string() const { std::string str; @@ -222,7 +216,7 @@ namespace omath { for (size_t j = 0; j < m_columns; ++j) { - str += std::format("{:.1f}", At(i, j)); + str += std::format("{:.1f}", at(i, j)); if (j == m_columns - 1) str += '\n'; @@ -233,89 +227,89 @@ namespace omath return str; } - float Matrix::Determinant() const + float Matrix::determinant() const // NOLINT(*-no-recursion) { if (m_rows + m_columns == 2) - return At(0, 0); + return at(0, 0); if (m_rows == 2 and m_columns == 2) - return At(0, 0) * At(1, 1) - At(0, 1) * At(1, 0); + return at(0, 0) * at(1, 1) - at(0, 1) * at(1, 0); - float fDet = 0; + float det = 0; for (size_t i = 0; i < m_columns; i++) - fDet += AlgComplement(0, i) * At(0, i); + det += alg_complement(0, i) * at(0, i); - return fDet; + return det; } - float Matrix::AlgComplement(const size_t i, const size_t j) const + float Matrix::alg_complement(const size_t i, const size_t j) const // NOLINT(*-no-recursion) { - const auto tmp = Minor(i, j); + const auto tmp = minor(i, j); return ((i + j + 2) % 2 == 0) ? tmp : -tmp; } - Matrix Matrix::Transpose() const + Matrix Matrix::transpose() const { Matrix transposed = {m_columns, m_rows}; for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - transposed.At(j, i) = At(i, j); + transposed.at(j, i) = at(i, j); return transposed; } Matrix::~Matrix() = default; - void Matrix::Set(const float val) + void Matrix::set(const float val) { for (size_t i = 0; i < m_rows; ++i) for (size_t j = 0; j < m_columns; ++j) - At(i, j) = val; + at(i, j) = val; } - Matrix Matrix::Strip(const size_t row, const size_t column) const + Matrix Matrix::strip(const size_t row, const size_t column) const { Matrix stripped = {m_rows - 1, m_columns - 1}; - size_t iStripRowIndex = 0; + size_t strip_row_index = 0; for (size_t i = 0; i < m_rows; i++) { if (i == row) continue; - size_t iStripColumnIndex = 0; + size_t strip_column_index = 0; for (size_t j = 0; j < m_columns; ++j) { if (j == column) continue; - stripped.At(iStripRowIndex, iStripColumnIndex) = At(i, j); - iStripColumnIndex++; + stripped.at(strip_row_index, strip_column_index) = at(i, j); + strip_column_index++; } - iStripRowIndex++; + strip_row_index++; } return stripped; } - float Matrix::Minor(const size_t i, const size_t j) const + float Matrix::minor(const size_t i, const size_t j) const // NOLINT(*-no-recursion) { - return Strip(i, j).Determinant(); + return strip(i, j).determinant(); } - Matrix Matrix::ToScreenMatrix(const float screenWidth, const float screenHeight) + Matrix Matrix::to_screen_matrix(const float screen_width, const float screen_height) { return { - {screenWidth / 2.f, 0.f, 0.f, 0.f}, - {0.f, -screenHeight / 2.f, 0.f, 0.f}, + {screen_width / 2.f, 0.f, 0.f, 0.f}, + {0.f, -screen_height / 2.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, - {screenWidth / 2.f, screenHeight / 2.f, 0.f, 1.f}, + {screen_width / 2.f, screen_height / 2.f, 0.f, 1.f}, }; } - Matrix Matrix::TranslationMatrix(const Vector3& diff) + Matrix Matrix::translation_matrix(const Vector3& diff) { return { {1.f, 0.f, 0.f, 0.f}, @@ -325,7 +319,8 @@ namespace omath }; } - Matrix Matrix::OrientationMatrix(const Vector3& forward, const Vector3& right, const Vector3& up) + Matrix Matrix::orientation_matrix(const Vector3& forward, const Vector3& right, + const Vector3& up) { return { {right.x, up.x, forward.x, 0.f}, @@ -335,25 +330,26 @@ namespace omath }; } - Matrix Matrix::ProjectionMatrix(const float fieldOfView, const float aspectRatio, const float near, const float far) + Matrix Matrix::projection_matrix(const float field_of_view, const float aspect_ratio, const float near, + const float far) { - const float fovHalfTan = std::tan(angles::DegreesToRadians(fieldOfView) / 2.f); + const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f); - return {{1.f / (aspectRatio * fovHalfTan), 0.f, 0.f, 0.f}, - {0.f, 1.f / fovHalfTan, 0.f, 0.f}, + return {{1.f / (aspect_ratio * fov_half_tan), 0.f, 0.f, 0.f}, + {0.f, 1.f / fov_half_tan, 0.f, 0.f}, {0.f, 0.f, (far + near) / (far - near), 2.f * near * far / (far - near)}, {0.f, 0.f, -1.f, 0.f}}; } - const float* Matrix::Raw() const + const float* Matrix::raw() const { return m_data.get(); } - void Matrix::SetDataFromRaw(const float* pRawMatrix) + void Matrix::set_data_from_raw(const float* raw_matrix) { for (size_t i = 0; i < m_columns * m_rows; ++i) - At(i / m_rows, i % m_columns) = pRawMatrix[i]; + at(i / m_rows, i % m_columns) = raw_matrix[i]; } Matrix::Matrix() diff --git a/source/pathfinding/a_star.cpp b/source/pathfinding/a_star.cpp index aba160f1..658d9ed2 100644 --- a/source/pathfinding/a_star.cpp +++ b/source/pathfinding/a_star.cpp @@ -2,96 +2,94 @@ // Created by Vlad on 28.07.2024. // #include "omath/pathfinding/a_star.hpp" - #include #include #include #include - namespace omath::pathfinding { struct PathNode final { - std::optional> cameFrom; - float gCost = 0.f; + std::optional> came_from; + float g_cost = 0.f; }; - - std::vector> Astar::ReconstructFinalPath(const std::unordered_map, PathNode>& closedList, - const Vector3& current) + std::vector> + Astar::reconstruct_final_path(const std::unordered_map, PathNode>& closed_list, + const Vector3& current) { std::vector> path; - std::optional currentOpt = current; + std::optional current_opt = current; - while (currentOpt) + while (current_opt) { - path.push_back(*currentOpt); + path.push_back(*current_opt); - auto it = closedList.find(*currentOpt); + auto it = closed_list.find(*current_opt); - if (it == closedList.end()) + if (it == closed_list.end()) break; - currentOpt = it->second.cameFrom; + current_opt = it->second.came_from; } std::ranges::reverse(path); return path; } - auto Astar::GetPerfectNode(const std::unordered_map, PathNode>& openList, const Vector3& endVertex) + auto Astar::get_perfect_node(const std::unordered_map, PathNode>& open_list, + const Vector3& endVertex) { - return std::ranges::min_element(openList, + return std::ranges::min_element(open_list, [&endVertex](const auto& a, const auto& b) { - const float fA = a.second.gCost + a.first.DistTo(endVertex); - const float fB = b.second.gCost + b.first.DistTo(endVertex); - return fA < fB; + const float fa = a.second.g_cost + a.first.distance_to(endVertex); + const float fb = b.second.g_cost + b.first.distance_to(endVertex); + return fa < fb; }); } - std::vector> Astar::FindPath(const Vector3& start, const Vector3& end, const NavigationMesh& navMesh) + std::vector> Astar::find_path(const Vector3& start, const Vector3& end, + const NavigationMesh& nav_mesh) { - std::unordered_map, PathNode> closedList; - std::unordered_map, PathNode> openList; + std::unordered_map, PathNode> closed_list; + std::unordered_map, PathNode> open_list; - auto maybeStartVertex = navMesh.GetClosestVertex(start); - auto maybeEndVertex = navMesh.GetClosestVertex(end); + auto maybe_start_vertex = nav_mesh.get_closest_vertex(start); + auto maybe_end_vertex = nav_mesh.get_closest_vertex(end); - if (!maybeStartVertex || !maybeEndVertex) + if (!maybe_start_vertex || !maybe_end_vertex) return {}; - const auto startVertex = maybeStartVertex.value(); - const auto endVertex = maybeEndVertex.value(); + const auto start_vertex = maybe_start_vertex.value(); + const auto end_vertex = maybe_end_vertex.value(); + open_list.emplace(start_vertex, PathNode{std::nullopt, 0.f}); - openList.emplace(startVertex, PathNode{std::nullopt, 0.f}); - - while (!openList.empty()) + while (!open_list.empty()) { - auto currentIt = GetPerfectNode(openList, endVertex); - - const auto current = currentIt->first; - const auto currentNode = currentIt->second; + auto current_it = get_perfect_node(open_list, end_vertex); - if (current == endVertex) - return ReconstructFinalPath(closedList, current); + const auto current = current_it->first; + const auto current_node = current_it->second; + if (current == end_vertex) + return reconstruct_final_path(closed_list, current); - closedList.emplace(current, currentNode); - openList.erase(currentIt); + closed_list.emplace(current, current_node); + open_list.erase(current_it); - for (const auto& neighbor: navMesh.GetNeighbors(current)) + for (const auto& neighbor: nav_mesh.get_neighbors(current)) { - if (closedList.contains(neighbor)) + if (closed_list.contains(neighbor)) continue; - const float tentativeGCost = currentNode.gCost + neighbor.DistTo(current); + const float tentative_g_cost = current_node.g_cost + neighbor.distance_to(current); - const auto openIt = openList.find(neighbor); + const auto open_it = open_list.find(neighbor); - if (openIt == openList.end() || tentativeGCost < openIt->second.gCost) - openList[neighbor] = PathNode{current, tentativeGCost}; + if (open_it == open_list.end() || tentative_g_cost < open_it->second.g_cost) + open_list[neighbor] = PathNode{current, tentative_g_cost}; } } diff --git a/source/pathfinding/navigation_mesh.cpp b/source/pathfinding/navigation_mesh.cpp index 2f985eed..97c7dc1b 100644 --- a/source/pathfinding/navigation_mesh.cpp +++ b/source/pathfinding/navigation_mesh.cpp @@ -2,94 +2,89 @@ // Created by Vlad on 28.07.2024. // #include "omath/pathfinding/navigation_mesh.hpp" - #include #include namespace omath::pathfinding { - std::expected, std::string> NavigationMesh::GetClosestVertex(const Vector3 &point) const + std::expected, std::string> NavigationMesh::get_closest_vertex(const Vector3& point) const { - const auto res = std::ranges::min_element(m_verTextMap, - [&point](const auto& a, const auto& b) - { - return a.first.DistTo(point) < b.first.DistTo(point); - }); + const auto res = std::ranges::min_element(m_vertex_map, [&point](const auto& a, const auto& b) + { return a.first.distance_to(point) < b.first.distance_to(point); }); - if (res == m_verTextMap.cend()) + if (res == m_vertex_map.cend()) return std::unexpected("Failed to get clossest point"); return res->first; } - const std::vector>& NavigationMesh::GetNeighbors(const Vector3 &vertex) const + const std::vector>& NavigationMesh::get_neighbors(const Vector3& vertex) const { - return m_verTextMap.at(vertex); + return m_vertex_map.at(vertex); } - bool NavigationMesh::Empty() const + bool NavigationMesh::empty() const { - return m_verTextMap.empty(); + return m_vertex_map.empty(); } - std::vector NavigationMesh::Serialize() const + std::vector NavigationMesh::serialize() const { - auto dumpToVector =[](const T& t, std::vector& vec){ + auto dump_to_vector = [](const T& t, std::vector& vec) + { for (size_t i = 0; i < sizeof(t); i++) - vec.push_back(*(reinterpret_cast(&t)+i)); + vec.push_back(*(reinterpret_cast(&t) + i)); }; std::vector raw; - - for (const auto& [vertex, neighbors] : m_verTextMap) + for (const auto& [vertex, neighbors]: m_vertex_map) { - const auto neighborsCount = neighbors.size(); + const auto neighbors_count = neighbors.size(); - dumpToVector(vertex, raw); - dumpToVector(neighborsCount, raw); + dump_to_vector(vertex, raw); + dump_to_vector(neighbors_count, raw); - for (const auto& neighbor : neighbors) - dumpToVector(neighbor, raw); + for (const auto& neighbor: neighbors) + dump_to_vector(neighbor, raw); } return raw; - } - void NavigationMesh::Deserialize(const std::vector &raw) + void NavigationMesh::deserialize(const std::vector& raw) { - auto loadFromVector = [](const std::vector& vec, size_t& offset, auto& value) + auto load_from_vector = [](const std::vector& vec, size_t& offset, auto& value) { if (offset + sizeof(value) > vec.size()) { throw std::runtime_error("Deserialize: Invalid input data size."); } - std::copy_n(vec.data() + offset, sizeof(value), (uint8_t*)&value); + std::copy_n(vec.data() + offset, sizeof(value), reinterpret_cast(&value)); offset += sizeof(value); }; - m_verTextMap.clear(); + m_vertex_map.clear(); size_t offset = 0; while (offset < raw.size()) { Vector3 vertex; - loadFromVector(raw, offset, vertex); + load_from_vector(raw, offset, vertex); - uint16_t neighborsCount; - loadFromVector(raw, offset, neighborsCount); + uint16_t neighbors_count; + load_from_vector(raw, offset, neighbors_count); std::vector> neighbors; - neighbors.reserve(neighborsCount); + neighbors.reserve(neighbors_count); - for (size_t i = 0; i < neighborsCount; ++i) + for (size_t i = 0; i < neighbors_count; ++i) { Vector3 neighbor; - loadFromVector(raw, offset, neighbor); + load_from_vector(raw, offset, neighbor); neighbors.push_back(neighbor); } - m_verTextMap.emplace(vertex, std::move(neighbors)); + m_vertex_map.emplace(vertex, std::move(neighbors)); } } -} +} // namespace omath::pathfinding diff --git a/source/projectile_prediction/proj_pred_engine.cpp b/source/projectile_prediction/proj_pred_engine.cpp index e8e7f92c..f1229006 100644 --- a/source/projectile_prediction/proj_pred_engine.cpp +++ b/source/projectile_prediction/proj_pred_engine.cpp @@ -3,7 +3,6 @@ // #include "omath/projectile_prediction/proj_pred_engine.hpp" - namespace omath::projectile_prediction { diff --git a/source/projectile_prediction/proj_pred_engine_avx2.cpp b/source/projectile_prediction/proj_pred_engine_avx2.cpp index 055e0608..9a26c435 100644 --- a/source/projectile_prediction/proj_pred_engine_avx2.cpp +++ b/source/projectile_prediction/proj_pred_engine_avx2.cpp @@ -14,8 +14,8 @@ namespace omath::projectile_prediction { std::optional> - ProjPredEngineAVX2::MaybeCalculateAimPoint([[maybe_unused]] const Projectile& projectile, - [[maybe_unused]] const Target& target) const + ProjPredEngineAvx2::maybe_calculate_aim_point([[maybe_unused]] const Projectile& projectile, + [[maybe_unused]] const Target& target) const { #if defined(OMATH_USE_AVX2) && defined(__i386__) && defined(__x86_64__) const float bulletGravity = m_gravityConstant * projectile.m_gravityScale; @@ -28,16 +28,16 @@ namespace omath::projectile_prediction for (; currentTime <= m_maximumSimulationTime; currentTime += m_simulationTimeStep * SIMD_FACTOR) { - const __m256 times = - _mm256_setr_ps(currentTime, currentTime + m_simulationTimeStep, - currentTime + m_simulationTimeStep * 2, currentTime + m_simulationTimeStep * 3, - currentTime + m_simulationTimeStep * 4, currentTime + m_simulationTimeStep * 5, - currentTime + m_simulationTimeStep * 6, currentTime + m_simulationTimeStep * 7); - - const __m256 targetX = - _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.x), times, _mm256_set1_ps(target.m_origin.x)); - const __m256 targetY = - _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.y), times, _mm256_set1_ps(target.m_origin.y)); + const __m256 times + = _mm256_setr_ps(currentTime, currentTime + m_simulationTimeStep, + currentTime + m_simulationTimeStep * 2, currentTime + m_simulationTimeStep * 3, + currentTime + m_simulationTimeStep * 4, currentTime + m_simulationTimeStep * 5, + currentTime + m_simulationTimeStep * 6, currentTime + m_simulationTimeStep * 7); + + const __m256 targetX + = _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.x), times, _mm256_set1_ps(target.m_origin.x)); + const __m256 targetY + = _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.y), times, _mm256_set1_ps(target.m_origin.y)); const __m256 timesSq = _mm256_mul_ps(times, times); const __m256 targetZ = _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.z), times, _mm256_fnmadd_ps(_mm256_set1_ps(0.5f * m_gravityConstant), timesSq, @@ -112,25 +112,32 @@ namespace omath::projectile_prediction } return std::nullopt; +#else + throw std::runtime_error( + std::format("{} AVX2 feature is not enabled!", std::source_location::current().function_name())); +#endif } - ProjPredEngineAVX2::ProjPredEngineAVX2(const float gravityConstant, const float simulationTimeStep, - const float maximumSimulationTime) : - m_gravityConstant(gravityConstant), m_simulationTimeStep(simulationTimeStep), - m_maximumSimulationTime(maximumSimulationTime) + ProjPredEngineAvx2::ProjPredEngineAvx2(const float gravity_constant, const float simulation_time_step, + const float maximum_simulation_time) + : m_gravity_constant(gravity_constant), m_simulation_time_step(simulation_time_step), + m_maximum_simulation_time(maximum_simulation_time) { } - std::optional ProjPredEngineAVX2::CalculatePitch(const Vector3& projOrigin, - const Vector3& targetPos, const float bulletGravity, - const float v0, const float time) + std::optional ProjPredEngineAvx2::calculate_pitch([[maybe_unused]] const Vector3& proj_origin, + [[maybe_unused]] const Vector3& target_pos, + [[maybe_unused]] const float bullet_gravity, + [[maybe_unused]] const float v0, + [[maybe_unused]] const float time) { +#if defined(OMATH_USE_AVX2) && defined(__i386__) && defined(__x86_64__) if (time <= 0.0f) return std::nullopt; - const Vector3 delta = targetPos - projOrigin; + const Vector3 delta = target_pos - proj_origin; const float dSqr = delta.x * delta.x + delta.y * delta.y; const float h = delta.z; - const float term = h + 0.5f * bulletGravity * time * time; + const float term = h + 0.5f * bullet_gravity * time * time; const float requiredV0Sqr = (dSqr + term * term) / (time * time); const float v0Sqr = v0 * v0; @@ -140,7 +147,6 @@ namespace omath::projectile_prediction if (dSqr == 0.0f) return term >= 0.0f ? 90.0f : -90.0f; - const float d = std::sqrt(dSqr); const float tanTheta = term / d; return angles::RadiansToDegrees(std::atan(tanTheta)); diff --git a/source/projectile_prediction/proj_pred_engine_legacy.cpp b/source/projectile_prediction/proj_pred_engine_legacy.cpp index 557fa2a2..5d3c0412 100644 --- a/source/projectile_prediction/proj_pred_engine_legacy.cpp +++ b/source/projectile_prediction/proj_pred_engine_legacy.cpp @@ -4,65 +4,67 @@ namespace omath::projectile_prediction { - ProjPredEngineLegacy::ProjPredEngineLegacy(const float gravityConstant, const float simulationTimeStep, - const float maximumSimulationTime, const float distanceTolerance) : - m_gravityConstant(gravityConstant), m_simulationTimeStep(simulationTimeStep), - m_maximumSimulationTime(maximumSimulationTime), m_distanceTolerance(distanceTolerance) + ProjPredEngineLegacy::ProjPredEngineLegacy(const float gravity_constant, const float simulation_time_step, + const float maximum_simulation_time, const float distance_tolerance) + : m_gravity_constant(gravity_constant), m_simulation_time_step(simulation_time_step), + m_maximum_simulation_time(maximum_simulation_time), m_distance_tolerance(distance_tolerance) { } - std::optional> ProjPredEngineLegacy::MaybeCalculateAimPoint(const Projectile& projectile, - const Target& target) const + std::optional> ProjPredEngineLegacy::maybe_calculate_aim_point(const Projectile& projectile, + const Target& target) const { - for (float time = 0.f; time < m_maximumSimulationTime; time += m_simulationTimeStep) + for (float time = 0.f; time < m_maximum_simulation_time; time += m_simulation_time_step) { - const auto predictedTargetPosition = target.PredictPosition(time, m_gravityConstant); + const auto predicted_target_position = target.predict_position(time, m_gravity_constant); - const auto projectilePitch = MaybeCalculateProjectileLaunchPitchAngle(projectile, predictedTargetPosition); + const auto projectile_pitch + = maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position); - if (!projectilePitch.has_value()) [[unlikely]] + if (!projectile_pitch.has_value()) [[unlikely]] continue; - if (!IsProjectileReachedTarget(predictedTargetPosition, projectile, projectilePitch.value(), time)) + if (!is_projectile_reached_target(predicted_target_position, projectile, projectile_pitch.value(), time)) continue; - const auto delta2d = (predictedTargetPosition - projectile.m_origin).Length2D(); - const auto height = delta2d * std::tan(angles::DegreesToRadians(projectilePitch.value())); + const auto delta2d = (predicted_target_position - projectile.m_origin).length_2d(); + const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value())); - return Vector3(predictedTargetPosition.x, predictedTargetPosition.y, projectile.m_origin.z + height); + return Vector3(predicted_target_position.x, predicted_target_position.y, projectile.m_origin.z + height); } return std::nullopt; } std::optional - ProjPredEngineLegacy::MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, - const Vector3& targetPosition) const + ProjPredEngineLegacy::maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile, + const Vector3& target_position) const { - const auto bulletGravity = m_gravityConstant * projectile.m_gravityScale; - const auto delta = targetPosition - projectile.m_origin; + const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale; + const auto delta = target_position - projectile.m_origin; - const auto distance2d = delta.Length2D(); - const auto distance2dSqr = distance2d * distance2d; - const auto launchSpeedSqr = projectile.m_launchSpeed * projectile.m_launchSpeed; + const auto distance2d = delta.length_2d(); + const auto distance2d_sqr = distance2d * distance2d; + const auto launch_speed_sqr = projectile.m_launch_speed * projectile.m_launch_speed; - float root = launchSpeedSqr * launchSpeedSqr - - bulletGravity * (bulletGravity * distance2dSqr + 2.0f * delta.z * launchSpeedSqr); + float root = launch_speed_sqr * launch_speed_sqr + - bullet_gravity * (bullet_gravity * distance2d_sqr + 2.0f * delta.z * launch_speed_sqr); if (root < 0.0f) [[unlikely]] return std::nullopt; root = std::sqrt(root); - const float angle = std::atan((launchSpeedSqr - root) / (bulletGravity * distance2d)); + const float angle = std::atan((launch_speed_sqr - root) / (bullet_gravity * distance2d)); - return angles::RadiansToDegrees(angle); + return angles::radians_to_degrees(angle); } - bool ProjPredEngineLegacy::IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, - const float pitch, const float time) const + bool ProjPredEngineLegacy::is_projectile_reached_target(const Vector3& target_position, + const Projectile& projectile, const float pitch, + const float time) const { - const auto yaw = projectile.m_origin.ViewAngleTo(targetPosition).y; - const auto projectilePosition = projectile.PredictPosition(pitch, yaw, time, m_gravityConstant); + const auto yaw = projectile.m_origin.view_angle_to(target_position).y; + const auto projectile_position = projectile.predict_position(pitch, yaw, time, m_gravity_constant); - return projectilePosition.DistTo(targetPosition) <= m_distanceTolerance; + return projectile_position.distance_to(target_position) <= m_distance_tolerance; } } // namespace omath::projectile_prediction diff --git a/source/projectile_prediction/projectile.cpp b/source/projectile_prediction/projectile.cpp index c003562f..30202db4 100644 --- a/source/projectile_prediction/projectile.cpp +++ b/source/projectile_prediction/projectile.cpp @@ -3,19 +3,20 @@ // #include "omath/projectile_prediction/projectile.hpp" - #include namespace omath::projectile_prediction { - Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const + Vector3 Projectile::predict_position(const float pitch, const float yaw, const float time, + const float gravity) const { - auto currentPos = m_origin + source_engine::ForwardVector({source_engine::PitchAngle::FromDegrees(-pitch), - source_engine::YawAngle::FromDegrees(yaw), - source_engine::RollAngle::FromDegrees(0)}) * - m_launchSpeed * time; - currentPos.z -= (gravity * m_gravityScale) * (time * time) * 0.5f; + auto current_pos = m_origin + + source_engine::forward_vector({source_engine::PitchAngle::from_degrees(-pitch), + source_engine::YawAngle::from_degrees(yaw), + source_engine::RollAngle::from_degrees(0)}) + * m_launch_speed * time; + current_pos.z -= (gravity * m_gravity_scale) * (time * time) * 0.5f; - return currentPos; + return current_pos; } -} // namespace omath::prediction +} // namespace omath::projectile_prediction diff --git a/source/projectile_prediction/target.cpp b/source/projectile_prediction/target.cpp index b0edda4a..e704b290 100644 --- a/source/projectile_prediction/target.cpp +++ b/source/projectile_prediction/target.cpp @@ -4,7 +4,6 @@ #include "omath/projectile_prediction/projectile.hpp" - namespace omath::prediction { diff --git a/source/projection/camera.cpp b/source/projection/camera.cpp index e50b6ebd..26d3d0db 100644 --- a/source/projection/camera.cpp +++ b/source/projection/camera.cpp @@ -3,7 +3,6 @@ // #include "omath/projection/camera.hpp" - namespace omath::projection { } diff --git a/tests/engines/unit_test_iw_engine.cpp b/tests/engines/unit_test_iw_engine.cpp index 86b7d0b2..76133f5f 100644 --- a/tests/engines/unit_test_iw_engine.cpp +++ b/tests/engines/unit_test_iw_engine.cpp @@ -9,69 +9,69 @@ TEST(UnitTestIwEngine, ForwardVector) { - const auto forward = omath::iw_engine::ForwardVector({}); + const auto forward = omath::iw_engine::forward_vector({}); - EXPECT_EQ(forward, omath::iw_engine::kAbsForward); + EXPECT_EQ(forward, omath::iw_engine::k_abs_forward); } TEST(UnitTestIwEngine, RightVector) { - const auto right = omath::iw_engine::RightVector({}); + const auto right = omath::iw_engine::right_vector({}); - EXPECT_EQ(right, omath::iw_engine::kAbsRight); + EXPECT_EQ(right, omath::iw_engine::k_abs_right); } TEST(UnitTestIwEngine, UpVector) { - const auto up = omath::iw_engine::UpVector({}); - EXPECT_EQ(up, omath::iw_engine::kAbsUp); + const auto up = omath::iw_engine::up_vector({}); + EXPECT_EQ(up, omath::iw_engine::k_abs_up); } TEST(UnitTestIwEngine, ForwardVectorRotationYaw) { omath::iw_engine::ViewAngles angles; - angles.yaw = omath::iw_engine::YawAngle::FromDegrees(-90.f); + angles.yaw = omath::iw_engine::YawAngle::from_degrees(-90.f); - const auto forward = omath::iw_engine::ForwardVector(angles); - EXPECT_NEAR(forward.x, omath::iw_engine::kAbsRight.x, 0.00001f); - EXPECT_NEAR(forward.y, omath::iw_engine::kAbsRight.y, 0.00001f); - EXPECT_NEAR(forward.z, omath::iw_engine::kAbsRight.z, 0.00001f); + const auto forward = omath::iw_engine::forward_vector(angles); + EXPECT_NEAR(forward.x, omath::iw_engine::k_abs_right.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::iw_engine::k_abs_right.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::iw_engine::k_abs_right.z, 0.00001f); } TEST(UnitTestIwEngine, ForwardVectorRotationPitch) { omath::iw_engine::ViewAngles angles; - angles.pitch = omath::iw_engine::PitchAngle::FromDegrees(-89.f); + angles.pitch = omath::iw_engine::PitchAngle::from_degrees(-89.f); - const auto forward = omath::iw_engine::ForwardVector(angles); - EXPECT_NEAR(forward.x, omath::iw_engine::kAbsUp.x, 0.02f); - EXPECT_NEAR(forward.y, omath::iw_engine::kAbsUp.y, 0.01f); - EXPECT_NEAR(forward.z, omath::iw_engine::kAbsUp.z, 0.01f); + const auto forward = omath::iw_engine::forward_vector(angles); + EXPECT_NEAR(forward.x, omath::iw_engine::k_abs_up.x, 0.02f); + EXPECT_NEAR(forward.y, omath::iw_engine::k_abs_up.y, 0.01f); + EXPECT_NEAR(forward.z, omath::iw_engine::k_abs_up.z, 0.01f); } TEST(UnitTestIwEngine, ForwardVectorRotationRoll) { omath::iw_engine::ViewAngles angles; - angles.roll = omath::iw_engine::RollAngle::FromDegrees(90.f); + angles.roll = omath::iw_engine::RollAngle::from_degrees(90.f); - const auto forward = omath::iw_engine::UpVector(angles); - EXPECT_NEAR(forward.x, omath::iw_engine::kAbsRight.x, 0.00001f); - EXPECT_NEAR(forward.y, omath::iw_engine::kAbsRight.y, 0.00001f); - EXPECT_NEAR(forward.z, omath::iw_engine::kAbsRight.z, 0.00001f); + const auto forward = omath::iw_engine::up_vector(angles); + EXPECT_NEAR(forward.x, omath::iw_engine::k_abs_right.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::iw_engine::k_abs_right.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::iw_engine::k_abs_right.z, 0.00001f); } TEST(UnitTestIwEngine, ProjectTargetMovedFromCamera) { - constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); const auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); for (float distance = 0.02f; distance < 1000.f; distance += 0.01f) { - const auto projected = cam.WorldToScreen({distance, 0, 0}); + const auto projected = cam.world_to_screen({distance, 0, 0}); EXPECT_TRUE(projected.has_value()); @@ -85,21 +85,21 @@ TEST(UnitTestIwEngine, ProjectTargetMovedFromCamera) TEST(UnitTestIwEngine, CameraSetAndGetFov) { - constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); - EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); - cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.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.GetFieldOfView().AsDegrees(), 50.f); + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } TEST(UnitTestIwEngine, CameraSetAndGetOrigin) { auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); - EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); - cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + EXPECT_EQ(cam.get_origin(), omath::Vector3{}); + cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(50.f)); - EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } \ 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 170b306d..1e9ddf8e 100644 --- a/tests/engines/unit_test_open_gl.cpp +++ b/tests/engines/unit_test_open_gl.cpp @@ -9,32 +9,32 @@ TEST(UnitTestOpenGL, ForwardVector) { - const auto forward = omath::opengl_engine::ForwardVector({}); - EXPECT_EQ(forward, omath::opengl_engine::kAbsForward); + const auto forward = omath::opengl_engine::forward_vector({}); + EXPECT_EQ(forward, omath::opengl_engine::k_abs_forward); } TEST(UnitTestOpenGL, RightVector) { - const auto right = omath::opengl_engine::RightVector({}); - EXPECT_EQ(right, omath::opengl_engine::kAbsRight); + const auto right = omath::opengl_engine::right_vector({}); + EXPECT_EQ(right, omath::opengl_engine::k_abs_right); } TEST(UnitTestOpenGL, UpVector) { - const auto up = omath::opengl_engine::UpVector({}); - EXPECT_EQ(up, omath::opengl_engine::kAbsUp); + const auto up = omath::opengl_engine::up_vector({}); + EXPECT_EQ(up, omath::opengl_engine::k_abs_up); } TEST(UnitTestOpenGL, ForwardVectorRotationYaw) { omath::opengl_engine::ViewAngles angles; - angles.yaw = omath::opengl_engine::YawAngle::FromDegrees(90.f); + angles.yaw = omath::opengl_engine::YawAngle::from_degrees(90.f); - const auto forward = omath::opengl_engine::ForwardVector(angles); - EXPECT_NEAR(forward.x, omath::opengl_engine::kAbsRight.x, 0.00001f); - EXPECT_NEAR(forward.y, omath::opengl_engine::kAbsRight.y, 0.00001f); - EXPECT_NEAR(forward.z, omath::opengl_engine::kAbsRight.z, 0.00001f); + const auto forward = omath::opengl_engine::forward_vector(angles); + EXPECT_NEAR(forward.x, omath::opengl_engine::k_abs_right.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::opengl_engine::k_abs_right.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::opengl_engine::k_abs_right.z, 0.00001f); } @@ -43,35 +43,35 @@ TEST(UnitTestOpenGL, ForwardVectorRotationPitch) { omath::opengl_engine::ViewAngles angles; - angles.pitch = omath::opengl_engine::PitchAngle::FromDegrees(-90.f); + angles.pitch = omath::opengl_engine::PitchAngle::from_degrees(-90.f); - const auto forward = omath::opengl_engine::ForwardVector(angles); - EXPECT_NEAR(forward.x, omath::opengl_engine::kAbsUp.x, 0.00001f); - EXPECT_NEAR(forward.y, omath::opengl_engine::kAbsUp.y, 0.00001f); - EXPECT_NEAR(forward.z, omath::opengl_engine::kAbsUp.z, 0.00001f); + const auto forward = omath::opengl_engine::forward_vector(angles); + EXPECT_NEAR(forward.x, omath::opengl_engine::k_abs_up.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::opengl_engine::k_abs_up.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::opengl_engine::k_abs_up.z, 0.00001f); } TEST(UnitTestOpenGL, ForwardVectorRotationRoll) { omath::opengl_engine::ViewAngles angles; - angles.roll = omath::opengl_engine::RollAngle::FromDegrees(-90.f); + angles.roll = omath::opengl_engine::RollAngle::from_degrees(-90.f); - const auto forward = omath::opengl_engine::UpVector(angles); - EXPECT_NEAR(forward.x, omath::opengl_engine::kAbsRight.x, 0.00001f); - EXPECT_NEAR(forward.y, omath::opengl_engine::kAbsRight.y, 0.00001f); - EXPECT_NEAR(forward.z, omath::opengl_engine::kAbsRight.z, 0.00001f); + const auto forward = omath::opengl_engine::up_vector(angles); + EXPECT_NEAR(forward.x, omath::opengl_engine::k_abs_right.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::opengl_engine::k_abs_right.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::opengl_engine::k_abs_right.z, 0.00001f); } TEST(UnitTestOpenGL, ProjectTargetMovedFromCamera) { - constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); const auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); for (float distance = -10.f; distance > -1000.f; distance -= 0.01f) { - const auto projected = cam.WorldToScreen({0, 0, distance}); + const auto projected = cam.world_to_screen({0, 0, distance}); EXPECT_TRUE(projected.has_value()); @@ -85,21 +85,21 @@ TEST(UnitTestOpenGL, ProjectTargetMovedFromCamera) TEST(UnitTestOpenGL, CameraSetAndGetFov) { - constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); - EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); - cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.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.GetFieldOfView().AsDegrees(), 50.f); + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } TEST(UnitTestOpenGL, CameraSetAndGetOrigin) { auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); - EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); - cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + EXPECT_EQ(cam.get_origin(), omath::Vector3{}); + cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(50.f)); - EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } \ 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 eebd58eb..a0749f32 100644 --- a/tests/engines/unit_test_source_engine.cpp +++ b/tests/engines/unit_test_source_engine.cpp @@ -9,69 +9,69 @@ TEST(UnitTestSourceEngine, ForwardVector) { - const auto forward = omath::source_engine::ForwardVector({}); + const auto forward = omath::source_engine::forward_vector({}); - EXPECT_EQ(forward, omath::source_engine::kAbsForward); + EXPECT_EQ(forward, omath::source_engine::k_abs_forward); } TEST(UnitTestSourceEngine, RightVector) { - const auto right = omath::source_engine::RightVector({}); + const auto right = omath::source_engine::right_vector({}); - EXPECT_EQ(right, omath::source_engine::kAbsRight); + EXPECT_EQ(right, omath::source_engine::k_abs_right); } TEST(UnitTestSourceEngine, UpVector) { - const auto up = omath::source_engine::UpVector({}); - EXPECT_EQ(up, omath::source_engine::kAbsUp); + const auto up = omath::source_engine::up_vector({}); + EXPECT_EQ(up, omath::source_engine::k_abs_up); } TEST(UnitTestSourceEngine, ForwardVectorRotationYaw) { omath::source_engine::ViewAngles angles; - angles.yaw = omath::source_engine::YawAngle::FromDegrees(-90.f); + angles.yaw = omath::source_engine::YawAngle::from_degrees(-90.f); - const auto forward = omath::source_engine::ForwardVector(angles); - EXPECT_NEAR(forward.x, omath::source_engine::kAbsRight.x, 0.00001f); - EXPECT_NEAR(forward.y, omath::source_engine::kAbsRight.y, 0.00001f); - EXPECT_NEAR(forward.z, omath::source_engine::kAbsRight.z, 0.00001f); + const auto forward = omath::source_engine::forward_vector(angles); + EXPECT_NEAR(forward.x, omath::source_engine::k_abs_right.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::source_engine::k_abs_right.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::source_engine::k_abs_right.z, 0.00001f); } TEST(UnitTestSourceEngine, ForwardVectorRotationPitch) { omath::source_engine::ViewAngles angles; - angles.pitch = omath::source_engine::PitchAngle::FromDegrees(-89.f); + angles.pitch = omath::source_engine::PitchAngle::from_degrees(-89.f); - const auto forward = omath::source_engine::ForwardVector(angles); - EXPECT_NEAR(forward.x, omath::source_engine::kAbsUp.x, 0.02f); - EXPECT_NEAR(forward.y, omath::source_engine::kAbsUp.y, 0.01f); - EXPECT_NEAR(forward.z, omath::source_engine::kAbsUp.z, 0.01f); + const auto forward = omath::source_engine::forward_vector(angles); + EXPECT_NEAR(forward.x, omath::source_engine::k_abs_up.x, 0.02f); + EXPECT_NEAR(forward.y, omath::source_engine::k_abs_up.y, 0.01f); + EXPECT_NEAR(forward.z, omath::source_engine::k_abs_up.z, 0.01f); } TEST(UnitTestSourceEngine, ForwardVectorRotationRoll) { omath::source_engine::ViewAngles angles; - angles.roll = omath::source_engine::RollAngle::FromDegrees(90.f); + angles.roll = omath::source_engine::RollAngle::from_degrees(90.f); - const auto forward = omath::source_engine::UpVector(angles); - EXPECT_NEAR(forward.x, omath::source_engine::kAbsRight.x, 0.00001f); - EXPECT_NEAR(forward.y, omath::source_engine::kAbsRight.y, 0.00001f); - EXPECT_NEAR(forward.z, omath::source_engine::kAbsRight.z, 0.00001f); + const auto forward = omath::source_engine::up_vector(angles); + EXPECT_NEAR(forward.x, omath::source_engine::k_abs_right.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::source_engine::k_abs_right.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::source_engine::k_abs_right.z, 0.00001f); } TEST(UnitTestSourceEngine, ProjectTargetMovedFromCamera) { - constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); const auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); for (float distance = 0.02f; distance < 1000.f; distance += 0.01f) { - const auto projected = cam.WorldToScreen({distance, 0, 0}); + const auto projected = cam.world_to_screen({distance, 0, 0}); EXPECT_TRUE(projected.has_value()); @@ -85,13 +85,13 @@ TEST(UnitTestSourceEngine, ProjectTargetMovedFromCamera) TEST(UnitTestSourceEngine, ProjectTargetMovedUp) { - constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); const auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); auto prev = 1080.f; for (float distance = 0.0f; distance < 10.f; distance += 1.f) { - const auto projected = cam.WorldToScreen({100.f, 0, distance}); + const auto projected = cam.world_to_screen({100.f, 0, distance}); EXPECT_TRUE(projected.has_value()); if (!projected.has_value()) @@ -105,21 +105,21 @@ TEST(UnitTestSourceEngine, ProjectTargetMovedUp) TEST(UnitTestSourceEngine, CameraSetAndGetFov) { - constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); - EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); - cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.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.GetFieldOfView().AsDegrees(), 50.f); + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } TEST(UnitTestSourceEngine, CameraSetAndGetOrigin) { auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); - EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); - cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + EXPECT_EQ(cam.get_origin(), omath::Vector3{}); + cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(50.f)); - EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } \ 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 b5a5e8e8..932bed6a 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -9,69 +9,69 @@ TEST(UnitTestUnityEngine, ForwardVector) { - const auto forward = omath::unity_engine::ForwardVector({}); + const auto forward = omath::unity_engine::forward_vector({}); - EXPECT_EQ(forward, omath::unity_engine::kAbsForward); + EXPECT_EQ(forward, omath::unity_engine::k_abs_forward); } TEST(UnitTestUnityEngine, ForwardVectorRotationYaw) { omath::unity_engine::ViewAngles angles; - angles.yaw = omath::unity_engine::YawAngle::FromDegrees(90.f); + angles.yaw = omath::unity_engine::YawAngle::from_degrees(90.f); - const auto forward = omath::unity_engine::ForwardVector(angles); - EXPECT_NEAR(forward.x, omath::unity_engine::kAbsRight.x, 0.00001f); - EXPECT_NEAR(forward.y, omath::unity_engine::kAbsRight.y, 0.00001f); - EXPECT_NEAR(forward.z, omath::unity_engine::kAbsRight.z, 0.00001f); + const auto forward = omath::unity_engine::forward_vector(angles); + EXPECT_NEAR(forward.x, omath::unity_engine::k_abs_right.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::unity_engine::k_abs_right.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::unity_engine::k_abs_right.z, 0.00001f); } TEST(UnitTestUnityEngine, ForwardVectorRotationPitch) { omath::unity_engine::ViewAngles angles; - angles.pitch = omath::unity_engine::PitchAngle::FromDegrees(-90.f); + angles.pitch = omath::unity_engine::PitchAngle::from_degrees(-90.f); - const auto forward = omath::unity_engine::ForwardVector(angles); - EXPECT_NEAR(forward.x, omath::unity_engine::kAbsUp.x, 0.00001f); - EXPECT_NEAR(forward.y, omath::unity_engine::kAbsUp.y, 0.00001f); - EXPECT_NEAR(forward.z, omath::unity_engine::kAbsUp.z, 0.00001f); + const auto forward = omath::unity_engine::forward_vector(angles); + EXPECT_NEAR(forward.x, omath::unity_engine::k_abs_up.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::unity_engine::k_abs_up.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::unity_engine::k_abs_up.z, 0.00001f); } TEST(UnitTestUnityEngine, ForwardVectorRotationRoll) { omath::unity_engine::ViewAngles angles; - angles.roll = omath::unity_engine::RollAngle::FromDegrees(-90.f); + angles.roll = omath::unity_engine::RollAngle::from_degrees(-90.f); - const auto forward = omath::unity_engine::UpVector(angles); - EXPECT_NEAR(forward.x, omath::unity_engine::kAbsRight.x, 0.00001f); - EXPECT_NEAR(forward.y, omath::unity_engine::kAbsRight.y, 0.00001f); - EXPECT_NEAR(forward.z, omath::unity_engine::kAbsRight.z, 0.00001f); + const auto forward = omath::unity_engine::up_vector(angles); + EXPECT_NEAR(forward.x, omath::unity_engine::k_abs_right.x, 0.00001f); + EXPECT_NEAR(forward.y, omath::unity_engine::k_abs_right.y, 0.00001f); + EXPECT_NEAR(forward.z, omath::unity_engine::k_abs_right.z, 0.00001f); } TEST(UnitTestUnityEngine, RightVector) { - const auto right = omath::unity_engine::RightVector({}); + const auto right = omath::unity_engine::right_vector({}); - EXPECT_EQ(right, omath::unity_engine::kAbsRight); + EXPECT_EQ(right, omath::unity_engine::k_abs_right); } TEST(UnitTestUnityEngine, UpVector) { - const auto up = omath::unity_engine::UpVector({}); - EXPECT_EQ(up, omath::unity_engine::kAbsUp); + const auto up = omath::unity_engine::up_vector({}); + EXPECT_EQ(up, omath::unity_engine::k_abs_up); } TEST(UnitTestUnityEngine, ProjectTargetMovedFromCamera) { - constexpr auto fov = omath::projection::FieldOfView::FromDegrees(60.f); + 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.01f, 1000.f); for (float distance = 0.02f; distance < 100.f; distance += 0.01f) { - const auto projected = cam.WorldToScreen({0, 0, distance}); + const auto projected = cam.world_to_screen({0, 0, distance}); EXPECT_TRUE(projected.has_value()); @@ -84,30 +84,30 @@ TEST(UnitTestUnityEngine, ProjectTargetMovedFromCamera) } TEST(UnitTestUnityEngine, Project) { - constexpr auto fov = omath::projection::FieldOfView::FromDegrees(60.f); + 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.WorldToScreen({5.f, 3, 10.f}); + const auto proj = cam.world_to_screen({5.f, 3, 10.f}); std::println("{} {}", proj->x, proj->y); } TEST(UnitTestUnityEngine, CameraSetAndGetFov) { - constexpr auto fov = omath::projection::FieldOfView::FromDegrees(90.f); + constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); - EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 90.f); - cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.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.GetFieldOfView().AsDegrees(), 50.f); + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } TEST(UnitTestUnityEngine, CameraSetAndGetOrigin) { auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); - EXPECT_EQ(cam.GetOrigin(), omath::Vector3{}); - cam.SetFieldOfView(omath::projection::FieldOfView::FromDegrees(50.f)); + EXPECT_EQ(cam.get_origin(), omath::Vector3{}); + cam.set_field_of_view(omath::projection::FieldOfView::from_degrees(50.f)); - EXPECT_EQ(cam.GetFieldOfView().AsDegrees(), 50.f); + EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } \ No newline at end of file diff --git a/tests/general/unit_test_a_star.cpp b/tests/general/unit_test_a_star.cpp index e33f8c0a..6c341c4f 100644 --- a/tests/general/unit_test_a_star.cpp +++ b/tests/general/unit_test_a_star.cpp @@ -4,14 +4,13 @@ #include #include - -TEST(UnitTestAstar, FindingRightPath) +TEST(unit_test_a_star, finding_right_path) { omath::pathfinding::NavigationMesh mesh; - mesh.m_verTextMap[{0.f, 0.f, 0.f}] = {{0.f, 1.f, 0.f}}; - mesh.m_verTextMap[{0.f, 1.f, 0.f}] = {{0.f, 2.f, 0.f}}; - mesh.m_verTextMap[{0.f, 2.f, 0.f}] = {{0.f, 3.f, 0.f}}; - mesh.m_verTextMap[{0.f, 3.f, 0.f}] = {}; - std::ignore = omath::pathfinding::Astar::FindPath({}, {0.f, 3.f, 0.f}, mesh); + mesh.m_vertex_map[{0.f, 0.f, 0.f}] = {{0.f, 1.f, 0.f}}; + mesh.m_vertex_map[{0.f, 1.f, 0.f}] = {{0.f, 2.f, 0.f}}; + mesh.m_vertex_map[{0.f, 2.f, 0.f}] = {{0.f, 3.f, 0.f}}; + mesh.m_vertex_map[{0.f, 3.f, 0.f}] = {}; + std::ignore = omath::pathfinding::Astar::find_path({}, {0.f, 3.f, 0.f}, mesh); } \ No newline at end of file diff --git a/tests/general/unit_test_angles.cpp b/tests/general/unit_test_angles.cpp index adbabeae..1883d780 100644 --- a/tests/general/unit_test_angles.cpp +++ b/tests/general/unit_test_angles.cpp @@ -4,46 +4,46 @@ #include #include -TEST(UnitTestAngles, RadiansToDeg) +TEST(unit_test_angles, radians_to_deg) { constexpr float rad = 67; - EXPECT_NEAR(omath::angles::RadiansToDegrees(rad), 3838.82f, 0.01f); + EXPECT_NEAR(omath::angles::radians_to_degrees(rad), 3838.82f, 0.01f); } -TEST(UnitTestAngles, DegreesToRadians) +TEST(unit_test_angles, degrees_to_radians) { constexpr float degree = 90; - EXPECT_NEAR(omath::angles::DegreesToRadians(degree), 1.5708f, 0.01f); + EXPECT_NEAR(omath::angles::degrees_to_radians(degree), 1.5708f, 0.01f); } -TEST(UnitTestAngles, HorizontalFovToVerical) +TEST(unit_test_angles, horizontal_fov_to_verical) { constexpr float hFov = 90; constexpr float aspectRation = 16.0f / 9.0f; - const auto verticalFov = omath::angles::HorizontalFovToVertical(hFov, aspectRation); + const auto verticalFov = omath::angles::horizontal_fov_to_vertical(hFov, aspectRation); EXPECT_NEAR(verticalFov, 58.71f, 0.01f); } -TEST(UnitTestAngles, VerticalToHorizontal) +TEST(unit_test_angles, vertical_to_horizontal) { constexpr float vFov = 58.71; constexpr float aspectRation = 16.0f / 9.0f; - const auto horizontalFov = omath::angles::VerticalFovToHorizontal(vFov, aspectRation); + const auto horizontalFov = omath::angles::vertical_fov_to_horizontal(vFov, aspectRation); EXPECT_NEAR(horizontalFov, 89.99f, 0.01f); } -TEST(UnitTestAngles, WrapAngle) +TEST(unit_test_angles, wrap_angle) { - const float wrapped = omath::angles::WrapAngle(361.f, 0.f, 360.f); + const float wrapped = omath::angles::wrap_angle(361.f, 0.f, 360.f); EXPECT_NEAR(wrapped, 1.f, 0.01f); } -TEST(UnitTestAngles, WrapAngleNegativeRange) +TEST(unit_test_angles, wrap_angle_negative_range) { - const float wrapped = omath::angles::WrapAngle(-90.f, 0.f, 360.f); + const float wrapped = omath::angles::wrap_angle(-90.f, 0.f, 360.f); EXPECT_NEAR(wrapped, 270.f, 0.01f); } \ No newline at end of file diff --git a/tests/general/unit_test_color.cpp b/tests/general/unit_test_color.cpp index 77aeaf30..5bfb39a9 100644 --- a/tests/general/unit_test_color.cpp +++ b/tests/general/unit_test_color.cpp @@ -15,8 +15,8 @@ class UnitTestColor : public ::testing::Test void SetUp() override { - color1 = Color::Red(); - color2 = Color::Green(); + color1 = Color::red(); + color2 = Color::green(); } }; @@ -43,7 +43,7 @@ TEST_F(UnitTestColor, Constructor_Vector4) // Test static methods for color creation TEST_F(UnitTestColor, FromRGBA) { - constexpr Color color = Color::FromRGBA(128, 64, 32, 255); + constexpr Color color = Color::from_rgba(128, 64, 32, 255); EXPECT_FLOAT_EQ(color.x, 128.0f / 255.0f); EXPECT_FLOAT_EQ(color.y, 64.0f / 255.0f); EXPECT_FLOAT_EQ(color.z, 32.0f / 255.0f); @@ -52,7 +52,7 @@ TEST_F(UnitTestColor, FromRGBA) TEST_F(UnitTestColor, FromHSV) { - constexpr Color color = Color::FromHSV(0.0f, 1.0f, 1.0f); // Red in HSV + constexpr Color color = Color::from_hsv(0.0f, 1.0f, 1.0f); // Red in HSV EXPECT_FLOAT_EQ(color.x, 1.0f); EXPECT_FLOAT_EQ(color.y, 0.0f); EXPECT_FLOAT_EQ(color.z, 0.0f); @@ -62,7 +62,7 @@ TEST_F(UnitTestColor, FromHSV) // Test HSV conversion TEST_F(UnitTestColor, ToHSV) { - HSV hsv = color1.ToHSV(); // Red color + Hsv hsv = color1.to_hsv(); // Red color EXPECT_FLOAT_EQ(hsv.hue, 0.0f); EXPECT_FLOAT_EQ(hsv.saturation, 1.0f); EXPECT_FLOAT_EQ(hsv.value, 1.0f); @@ -71,7 +71,7 @@ TEST_F(UnitTestColor, ToHSV) // Test color blending TEST_F(UnitTestColor, Blend) { - Color blended = color1.Blend(color2, 0.5f); + Color blended = color1.blend(color2, 0.5f); EXPECT_FLOAT_EQ(blended.x, 0.5f); EXPECT_FLOAT_EQ(blended.y, 0.5f); EXPECT_FLOAT_EQ(blended.z, 0.0f); @@ -81,9 +81,9 @@ TEST_F(UnitTestColor, Blend) // Test predefined colors TEST_F(UnitTestColor, PredefinedColors) { - constexpr Color red = Color::Red(); - constexpr Color green = Color::Green(); - constexpr Color blue = Color::Blue(); + constexpr Color red = Color::red(); + constexpr Color green = Color::green(); + constexpr Color blue = Color::blue(); EXPECT_FLOAT_EQ(red.x, 1.0f); EXPECT_FLOAT_EQ(red.y, 0.0f); @@ -106,7 +106,7 @@ TEST_F(UnitTestColor, BlendVector3) { constexpr Color v1(1.0f, 0.0f, 0.0f, 1.f); // Red constexpr Color v2(0.0f, 1.0f, 0.0f, 1.f); // Green - constexpr Color blended = v1.Blend(v2, 0.5f); + constexpr Color blended = v1.blend(v2, 0.5f); EXPECT_FLOAT_EQ(blended.x, 0.5f); EXPECT_FLOAT_EQ(blended.y, 0.5f); EXPECT_FLOAT_EQ(blended.z, 0.0f); diff --git a/tests/general/unit_test_line_trace.cpp b/tests/general/unit_test_line_trace.cpp index 9472494a..e47d2b76 100644 --- a/tests/general/unit_test_line_trace.cpp +++ b/tests/general/unit_test_line_trace.cpp @@ -59,7 +59,7 @@ namespace TEST_P(CanTraceLineParam, VariousRays) { const auto& p = GetParam(); - EXPECT_EQ(LineTracer::CanTraceLine(p.ray, triangle), p.expected_clear); + EXPECT_EQ(LineTracer::can_trace_line(p.ray, triangle), p.expected_clear); } INSTANTIATE_TEST_SUITE_P( @@ -84,7 +84,7 @@ namespace constexpr Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}}; constexpr Vec3 expected{0.3f, 0.3f, 0.f}; - const Vec3 hit = LineTracer::GetRayHitPoint(ray, triangle); + const Vec3 hit = LineTracer::get_ray_hit_point(ray, triangle); ASSERT_FALSE(VecEqual(hit, ray.end)); EXPECT_TRUE(VecEqual(hit, expected)); } @@ -99,7 +99,7 @@ namespace {1001.f, 1000.f, 1000.f}, {1000.f, 1001.f, 1000.f}}; - EXPECT_TRUE(LineTracer::CanTraceLine(short_ray, distant)); + EXPECT_TRUE(LineTracer::can_trace_line(short_ray, distant)); } TEST(LineTracerTraceRayEdge, CantHit) @@ -108,14 +108,13 @@ namespace constexpr Ray ray{{}, {1.0, 0, 0}, false}; - EXPECT_TRUE(omath::collision::LineTracer::CanTraceLine(ray, triangle)); + EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, triangle)); } TEST(LineTracerTraceRayEdge, CanHit) { constexpr omath::Triangle> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}}; constexpr Ray ray{{}, {2.1, 0, 0}, false}; - auto endPoint = omath::collision::LineTracer::GetRayHitPoint(ray, triangle); - EXPECT_FALSE(omath::collision::LineTracer::CanTraceLine(ray, triangle)); + EXPECT_FALSE(omath::collision::LineTracer::can_trace_line(ray, triangle)); } } // namespace diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index 7fc25f6c..9350f825 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -22,22 +22,22 @@ class UnitTestMat : public ::testing::Test TEST_F(UnitTestMat, Constructor_Default) { Mat<3, 3> m; - EXPECT_EQ(m.RowCount(), 3); - EXPECT_EQ(m.ColumnsCount(), 3); + EXPECT_EQ(m.row_count(), 3); + EXPECT_EQ(m.columns_count(), 3); for (size_t i = 0; i < 3; ++i) for (size_t j = 0; j < 3; ++j) - EXPECT_FLOAT_EQ(m.At(i, j), 0.0f); + EXPECT_FLOAT_EQ(m.at(i, j), 0.0f); } TEST_F(UnitTestMat, Constructor_InitializerList) { constexpr Mat<2, 2> m{{1.0f, 2.0f}, {3.0f, 4.0f}}; - EXPECT_EQ(m.RowCount(), 2); - EXPECT_EQ(m.ColumnsCount(), 2); - EXPECT_FLOAT_EQ(m.At(0, 0), 1.0f); - EXPECT_FLOAT_EQ(m.At(0, 1), 2.0f); - EXPECT_FLOAT_EQ(m.At(1, 0), 3.0f); - EXPECT_FLOAT_EQ(m.At(1, 1), 4.0f); + EXPECT_EQ(m.row_count(), 2); + EXPECT_EQ(m.columns_count(), 2); + EXPECT_FLOAT_EQ(m.at(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m.at(0, 1), 2.0f); + EXPECT_FLOAT_EQ(m.at(1, 0), 3.0f); + EXPECT_FLOAT_EQ(m.at(1, 1), 4.0f); } TEST_F(UnitTestMat, Operator_SquareBrackets) @@ -51,19 +51,19 @@ TEST_F(UnitTestMat, Operator_SquareBrackets) TEST_F(UnitTestMat, Constructor_Copy) { Mat<2, 2> m3 = m2; - EXPECT_EQ(m3.RowCount(), m2.RowCount()); - EXPECT_EQ(m3.ColumnsCount(), m2.ColumnsCount()); - EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); - EXPECT_FLOAT_EQ(m3.At(1, 1), m2.At(1, 1)); + EXPECT_EQ(m3.row_count(), m2.row_count()); + EXPECT_EQ(m3.columns_count(), m2.columns_count()); + EXPECT_FLOAT_EQ(m3.at(0, 0), m2.at(0, 0)); + EXPECT_FLOAT_EQ(m3.at(1, 1), m2.at(1, 1)); } TEST_F(UnitTestMat, Constructor_Move) { Mat<2, 2> m3 = std::move(m2); - EXPECT_EQ(m3.RowCount(), 2); - EXPECT_EQ(m3.ColumnsCount(), 2); - EXPECT_FLOAT_EQ(m3.At(0, 0), 1.0f); - EXPECT_FLOAT_EQ(m3.At(1, 1), 4.0f); + EXPECT_EQ(m3.row_count(), 2); + EXPECT_EQ(m3.columns_count(), 2); + EXPECT_FLOAT_EQ(m3.at(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m3.at(1, 1), 4.0f); // m2 is in a valid but unspecified state after move } @@ -71,36 +71,36 @@ TEST_F(UnitTestMat, Constructor_Move) TEST_F(UnitTestMat, Operator_Multiplication_Matrix) { Mat<2, 2> m3 = m2 * m2; - EXPECT_EQ(m3.RowCount(), 2); - EXPECT_EQ(m3.ColumnsCount(), 2); - EXPECT_FLOAT_EQ(m3.At(0, 0), 7.0f); - EXPECT_FLOAT_EQ(m3.At(0, 1), 10.0f); - EXPECT_FLOAT_EQ(m3.At(1, 0), 15.0f); - EXPECT_FLOAT_EQ(m3.At(1, 1), 22.0f); + EXPECT_EQ(m3.row_count(), 2); + EXPECT_EQ(m3.columns_count(), 2); + EXPECT_FLOAT_EQ(m3.at(0, 0), 7.0f); + EXPECT_FLOAT_EQ(m3.at(0, 1), 10.0f); + EXPECT_FLOAT_EQ(m3.at(1, 0), 15.0f); + EXPECT_FLOAT_EQ(m3.at(1, 1), 22.0f); } TEST_F(UnitTestMat, Operator_Multiplication_Scalar) { Mat<2, 2> m3 = m2 * 2.0f; - EXPECT_FLOAT_EQ(m3.At(0, 0), 2.0f); - EXPECT_FLOAT_EQ(m3.At(1, 1), 8.0f); + EXPECT_FLOAT_EQ(m3.at(0, 0), 2.0f); + EXPECT_FLOAT_EQ(m3.at(1, 1), 8.0f); } TEST_F(UnitTestMat, Operator_Division_Scalar) { Mat<2, 2> m3 = m2 / 2.0f; - EXPECT_FLOAT_EQ(m3.At(0, 0), 0.5f); - EXPECT_FLOAT_EQ(m3.At(1, 1), 2.0f); + EXPECT_FLOAT_EQ(m3.at(0, 0), 0.5f); + EXPECT_FLOAT_EQ(m3.at(1, 1), 2.0f); } // Test matrix functions TEST_F(UnitTestMat, Transpose) { Mat<2, 2> m3 = m2.Transposed(); - EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); - EXPECT_FLOAT_EQ(m3.At(0, 1), m2.At(1, 0)); - EXPECT_FLOAT_EQ(m3.At(1, 0), m2.At(0, 1)); - EXPECT_FLOAT_EQ(m3.At(1, 1), m2.At(1, 1)); + EXPECT_FLOAT_EQ(m3.at(0, 0), m2.at(0, 0)); + EXPECT_FLOAT_EQ(m3.at(0, 1), m2.at(1, 0)); + EXPECT_FLOAT_EQ(m3.at(1, 0), m2.at(0, 1)); + EXPECT_FLOAT_EQ(m3.at(1, 1), m2.at(1, 1)); } TEST_F(UnitTestMat, Determinant) @@ -111,21 +111,21 @@ TEST_F(UnitTestMat, Determinant) TEST_F(UnitTestMat, Sum) { - const float sum = m2.Sum(); + const float sum = m2.sum(); EXPECT_FLOAT_EQ(sum, 10.0f); } TEST_F(UnitTestMat, Clear) { - m2.Clear(); - for (size_t i = 0; i < m2.RowCount(); ++i) - for (size_t j = 0; j < m2.ColumnsCount(); ++j) - EXPECT_FLOAT_EQ(m2.At(i, j), 0.0f); + m2.clear(); + for (size_t i = 0; i < m2.row_count(); ++i) + for (size_t j = 0; j < m2.columns_count(); ++j) + EXPECT_FLOAT_EQ(m2.at(i, j), 0.0f); } TEST_F(UnitTestMat, ToString) { - const std::string str = m2.ToString(); + const std::string str = m2.to_string(); EXPECT_FALSE(str.empty()); EXPECT_EQ(str, "[[ 1.000, 2.000]\n [ 3.000, 4.000]]"); } @@ -135,31 +135,31 @@ TEST_F(UnitTestMat, AssignmentOperator_Copy) { Mat<2, 2> m3; m3 = m2; - EXPECT_EQ(m3.RowCount(), m2.RowCount()); - EXPECT_EQ(m3.ColumnsCount(), m2.ColumnsCount()); - EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); + EXPECT_EQ(m3.row_count(), m2.row_count()); + EXPECT_EQ(m3.columns_count(), m2.columns_count()); + EXPECT_FLOAT_EQ(m3.at(0, 0), m2.at(0, 0)); } TEST_F(UnitTestMat, AssignmentOperator_Move) { Mat<2, 2> m3; m3 = std::move(m2); - EXPECT_EQ(m3.RowCount(), 2); - EXPECT_EQ(m3.ColumnsCount(), 2); - EXPECT_FLOAT_EQ(m3.At(0, 0), 1.0f); - EXPECT_FLOAT_EQ(m3.At(1, 1), 4.0f); + EXPECT_EQ(m3.row_count(), 2); + EXPECT_EQ(m3.columns_count(), 2); + EXPECT_FLOAT_EQ(m3.at(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m3.at(1, 1), 4.0f); // m2 is in a valid but unspecified state after move } // Test static methods TEST_F(UnitTestMat, StaticMethod_ToScreenMat) { - Mat<4, 4> screenMat = Mat<4, 4>::ToScreenMat(800.0f, 600.0f); - EXPECT_FLOAT_EQ(screenMat.At(0, 0), 400.0f); - EXPECT_FLOAT_EQ(screenMat.At(1, 1), -300.0f); - EXPECT_FLOAT_EQ(screenMat.At(3, 0), 400.0f); - EXPECT_FLOAT_EQ(screenMat.At(3, 1), 300.0f); - EXPECT_FLOAT_EQ(screenMat.At(3, 3), 1.0f); + Mat<4, 4> screenMat = Mat<4, 4>::to_screen_mat(800.0f, 600.0f); + EXPECT_FLOAT_EQ(screenMat.at(0, 0), 400.0f); + EXPECT_FLOAT_EQ(screenMat.at(1, 1), -300.0f); + EXPECT_FLOAT_EQ(screenMat.at(3, 0), 400.0f); + EXPECT_FLOAT_EQ(screenMat.at(3, 1), 300.0f); + EXPECT_FLOAT_EQ(screenMat.at(3, 3), 1.0f); } @@ -183,13 +183,13 @@ TEST(UnitTestMatStandalone, Determinant_3x3) TEST(UnitTestMatStandalone, Strip_3x3) { constexpr Mat<3, 3> m{{3, 0, 2}, {2, 0, -2}, {0, 1, 1}}; - auto minor = m.Strip(0, 0); - EXPECT_EQ(minor.RowCount(), 2); - EXPECT_EQ(minor.ColumnsCount(), 2); - EXPECT_FLOAT_EQ(minor.At(0, 0), 0.0f); - EXPECT_FLOAT_EQ(minor.At(0, 1), -2.0f); - EXPECT_FLOAT_EQ(minor.At(1, 0), 1.0f); - EXPECT_FLOAT_EQ(minor.At(1, 1), 1.0f); + auto minor = m.strip(0, 0); + EXPECT_EQ(minor.row_count(), 2); + EXPECT_EQ(minor.columns_count(), 2); + EXPECT_FLOAT_EQ(minor.at(0, 0), 0.0f); + EXPECT_FLOAT_EQ(minor.at(0, 1), -2.0f); + EXPECT_FLOAT_EQ(minor.at(1, 0), 1.0f); + EXPECT_FLOAT_EQ(minor.at(1, 1), 1.0f); } // Test Transpose for non-square matrix @@ -197,14 +197,14 @@ TEST(UnitTestMatStandalone, Transpose_NonSquare) { constexpr Mat<2, 3> m{{1.0f, 2.0f, 3.0f}, {4.0f, 5.0f, 6.0f}}; auto transposed = m.Transposed(); - EXPECT_EQ(transposed.RowCount(), 3); - EXPECT_EQ(transposed.ColumnsCount(), 2); - EXPECT_FLOAT_EQ(transposed.At(0, 0), 1.0f); - EXPECT_FLOAT_EQ(transposed.At(1, 0), 2.0f); - EXPECT_FLOAT_EQ(transposed.At(2, 0), 3.0f); - EXPECT_FLOAT_EQ(transposed.At(0, 1), 4.0f); - EXPECT_FLOAT_EQ(transposed.At(1, 1), 5.0f); - EXPECT_FLOAT_EQ(transposed.At(2, 1), 6.0f); + EXPECT_EQ(transposed.row_count(), 3); + EXPECT_EQ(transposed.columns_count(), 2); + EXPECT_FLOAT_EQ(transposed.at(0, 0), 1.0f); + EXPECT_FLOAT_EQ(transposed.at(1, 0), 2.0f); + EXPECT_FLOAT_EQ(transposed.at(2, 0), 3.0f); + EXPECT_FLOAT_EQ(transposed.at(0, 1), 4.0f); + EXPECT_FLOAT_EQ(transposed.at(1, 1), 5.0f); + EXPECT_FLOAT_EQ(transposed.at(2, 1), 6.0f); } TEST(UnitTestMatStandalone, Enverse) @@ -212,5 +212,5 @@ TEST(UnitTestMatStandalone, Enverse) constexpr Mat<2, 2> m{{1.0f, 3.0f}, {2.0f, 5.0f}}; constexpr Mat<2,2> mv{{-5.0f, 3.0f}, {2.0f, -1.0f}}; - EXPECT_EQ(mv, m.Inverted()); + EXPECT_EQ(mv, m.inverted()); } diff --git a/tests/general/unit_test_matrix.cpp b/tests/general/unit_test_matrix.cpp index 0594ff44..ec56cbc0 100644 --- a/tests/general/unit_test_matrix.cpp +++ b/tests/general/unit_test_matrix.cpp @@ -25,8 +25,8 @@ class UnitTestMatrix : public ::testing::Test TEST_F(UnitTestMatrix, Constructor_Size) { const Matrix m(3, 3); - EXPECT_EQ(m.RowCount(), 3); - EXPECT_EQ(m.ColumnsCount(), 3); + EXPECT_EQ(m.row_count(), 3); + EXPECT_EQ(m.columns_count(), 3); } TEST_F(UnitTestMatrix, Operator_SquareBrackets) @@ -41,120 +41,120 @@ TEST_F(UnitTestMatrix, Operator_SquareBrackets) TEST_F(UnitTestMatrix, Constructor_InitializerList) { Matrix m{{1.0f, 2.0f}, {3.0f, 4.0f}}; - EXPECT_EQ(m.RowCount(), 2); - EXPECT_EQ(m.ColumnsCount(), 2); - EXPECT_FLOAT_EQ(m.At(0, 0), 1.0f); - EXPECT_FLOAT_EQ(m.At(1, 1), 4.0f); + EXPECT_EQ(m.row_count(), 2); + EXPECT_EQ(m.columns_count(), 2); + EXPECT_FLOAT_EQ(m.at(0, 0), 1.0f); + EXPECT_FLOAT_EQ(m.at(1, 1), 4.0f); } TEST_F(UnitTestMatrix, Constructor_Copy) { Matrix m3 = m2; - EXPECT_EQ(m3.RowCount(), m2.RowCount()); - EXPECT_EQ(m3.ColumnsCount(), m2.ColumnsCount()); - EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); + EXPECT_EQ(m3.row_count(), m2.row_count()); + EXPECT_EQ(m3.columns_count(), m2.columns_count()); + EXPECT_FLOAT_EQ(m3.at(0, 0), m2.at(0, 0)); } TEST_F(UnitTestMatrix, Constructor_Move) { Matrix m3 = std::move(m2); - EXPECT_EQ(m3.RowCount(), 2); - EXPECT_EQ(m3.ColumnsCount(), 2); - EXPECT_FLOAT_EQ(m3.At(0, 0), 1.0f); - EXPECT_EQ(m2.RowCount(), 0); // m2 should be empty after the move - EXPECT_EQ(m2.ColumnsCount(), 0); + EXPECT_EQ(m3.row_count(), 2); + EXPECT_EQ(m3.columns_count(), 2); + EXPECT_FLOAT_EQ(m3.at(0, 0), 1.0f); + EXPECT_EQ(m2.row_count(), 0); // m2 should be empty after the move + EXPECT_EQ(m2.columns_count(), 0); } // Test matrix operations TEST_F(UnitTestMatrix, Operator_Multiplication_Matrix) { Matrix m3 = m2 * m2; - EXPECT_EQ(m3.RowCount(), 2); - EXPECT_EQ(m3.ColumnsCount(), 2); - EXPECT_FLOAT_EQ(m3.At(0, 0), 7.0f); - EXPECT_FLOAT_EQ(m3.At(1, 1), 22.0f); + EXPECT_EQ(m3.row_count(), 2); + EXPECT_EQ(m3.columns_count(), 2); + EXPECT_FLOAT_EQ(m3.at(0, 0), 7.0f); + EXPECT_FLOAT_EQ(m3.at(1, 1), 22.0f); } TEST_F(UnitTestMatrix, Operator_Multiplication_Scalar) { Matrix m3 = m2 * 2.0f; - EXPECT_FLOAT_EQ(m3.At(0, 0), 2.0f); - EXPECT_FLOAT_EQ(m3.At(1, 1), 8.0f); + EXPECT_FLOAT_EQ(m3.at(0, 0), 2.0f); + EXPECT_FLOAT_EQ(m3.at(1, 1), 8.0f); } TEST_F(UnitTestMatrix, Operator_Division_Scalar) { Matrix m3 = m2 / 2.0f; - EXPECT_FLOAT_EQ(m3.At(0, 0), 0.5f); - EXPECT_FLOAT_EQ(m3.At(1, 1), 2.0f); + EXPECT_FLOAT_EQ(m3.at(0, 0), 0.5f); + EXPECT_FLOAT_EQ(m3.at(1, 1), 2.0f); } // Test matrix functions TEST_F(UnitTestMatrix, Transpose) { - Matrix m3 = m2.Transpose(); - EXPECT_FLOAT_EQ(m3.At(0, 1), 3.0f); - EXPECT_FLOAT_EQ(m3.At(1, 0), 2.0f); + Matrix m3 = m2.transpose(); + EXPECT_FLOAT_EQ(m3.at(0, 1), 3.0f); + EXPECT_FLOAT_EQ(m3.at(1, 0), 2.0f); } TEST_F(UnitTestMatrix, Determinant) { - const float det = m2.Determinant(); + const float det = m2.determinant(); EXPECT_FLOAT_EQ(det, -2.0f); } TEST_F(UnitTestMatrix, Minor) { - const float minor = m2.Minor(0, 0); + const float minor = m2.minor(0, 0); EXPECT_FLOAT_EQ(minor, 4.0f); } TEST_F(UnitTestMatrix, AlgComplement) { - const float algComp = m2.AlgComplement(0, 0); + const float algComp = m2.alg_complement(0, 0); EXPECT_FLOAT_EQ(algComp, 4.0f); } TEST_F(UnitTestMatrix, Strip) { - Matrix m3 = m2.Strip(0, 0); - EXPECT_EQ(m3.RowCount(), 1); - EXPECT_EQ(m3.ColumnsCount(), 1); - EXPECT_FLOAT_EQ(m3.At(0, 0), 4.0f); + Matrix m3 = m2.strip(0, 0); + EXPECT_EQ(m3.row_count(), 1); + EXPECT_EQ(m3.columns_count(), 1); + EXPECT_FLOAT_EQ(m3.at(0, 0), 4.0f); } TEST_F(UnitTestMatrix, ProjectionMatrix) { - const Matrix proj = Matrix::ProjectionMatrix(45.0f, 1.33f, 0.1f, 100.0f); - EXPECT_EQ(proj.RowCount(), 4); - EXPECT_EQ(proj.ColumnsCount(), 4); + const Matrix proj = Matrix::projection_matrix(45.0f, 1.33f, 0.1f, 100.0f); + EXPECT_EQ(proj.row_count(), 4); + EXPECT_EQ(proj.columns_count(), 4); // Further checks on projection matrix elements could be added } // Test other member functions TEST_F(UnitTestMatrix, Set) { - m1.Set(3.0f); - EXPECT_FLOAT_EQ(m1.At(0, 0), 3.0f); - EXPECT_FLOAT_EQ(m1.At(1, 1), 3.0f); + m1.set(3.0f); + EXPECT_FLOAT_EQ(m1.at(0, 0), 3.0f); + EXPECT_FLOAT_EQ(m1.at(1, 1), 3.0f); } TEST_F(UnitTestMatrix, Sum) { - const float sum = m2.Sum(); + const float sum = m2.sum(); EXPECT_FLOAT_EQ(sum, 10.0f); } TEST_F(UnitTestMatrix, Clear) { - m2.Clear(); - EXPECT_FLOAT_EQ(m2.At(0, 0), 0.0f); - EXPECT_FLOAT_EQ(m2.At(1, 1), 0.0f); + m2.clear(); + EXPECT_FLOAT_EQ(m2.at(0, 0), 0.0f); + EXPECT_FLOAT_EQ(m2.at(1, 1), 0.0f); } TEST_F(UnitTestMatrix, ToString) { - const std::string str = m2.ToString(); + const std::string str = m2.to_string(); EXPECT_FALSE(str.empty()); } @@ -163,18 +163,18 @@ TEST_F(UnitTestMatrix, AssignmentOperator_Copy) { Matrix m3(2, 2); m3 = m2; - EXPECT_EQ(m3.RowCount(), m2.RowCount()); - EXPECT_EQ(m3.ColumnsCount(), m2.ColumnsCount()); - EXPECT_FLOAT_EQ(m3.At(0, 0), m2.At(0, 0)); + EXPECT_EQ(m3.row_count(), m2.row_count()); + EXPECT_EQ(m3.columns_count(), m2.columns_count()); + EXPECT_FLOAT_EQ(m3.at(0, 0), m2.at(0, 0)); } TEST_F(UnitTestMatrix, AssignmentOperator_Move) { Matrix m3(2, 2); m3 = std::move(m2); - EXPECT_EQ(m3.RowCount(), 2); - EXPECT_EQ(m3.ColumnsCount(), 2); - EXPECT_FLOAT_EQ(m3.At(0, 0), 1.0f); - EXPECT_EQ(m2.RowCount(), 0); // m2 should be empty after the move - EXPECT_EQ(m2.ColumnsCount(), 0); + EXPECT_EQ(m3.row_count(), 2); + EXPECT_EQ(m3.columns_count(), 2); + EXPECT_FLOAT_EQ(m3.at(0, 0), 1.0f); + EXPECT_EQ(m2.row_count(), 0); // m2 should be empty after the move + EXPECT_EQ(m2.columns_count(), 0); } \ No newline at end of file diff --git a/tests/general/unit_test_prediction.cpp b/tests/general/unit_test_prediction.cpp index 94e8ae48..6fb054a5 100644 --- a/tests/general/unit_test_prediction.cpp +++ b/tests/general/unit_test_prediction.cpp @@ -4,13 +4,13 @@ TEST(UnitTestPrediction, PredictionTest) { constexpr omath::projectile_prediction::Target target{ - .m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; + .m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_is_airborne = false}; constexpr omath::projectile_prediction::Projectile proj = { - .m_origin = {3, 2, 1}, .m_launchSpeed = 5000, .m_gravityScale = 0.4}; + .m_origin = {3, 2, 1}, .m_launch_speed = 5000, .m_gravity_scale = 0.4}; const auto viewPoint = - omath::projectile_prediction::ProjPredEngineLegacy(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); + omath::projectile_prediction::ProjPredEngineLegacy(400, 1.f / 1000.f, 50, 5.f).maybe_calculate_aim_point(proj, target); - const auto [pitch, yaw, _] = proj.m_origin.ViewAngleTo(viewPoint.value()).AsTuple(); + const auto [pitch, yaw, _] = proj.m_origin.view_angle_to(viewPoint.value()).as_tuple(); EXPECT_NEAR(42.547142, pitch, 0.01f); EXPECT_NEAR(-1.181189, yaw, 0.01f); diff --git a/tests/general/unit_test_projection.cpp b/tests/general/unit_test_projection.cpp index 6acc9d1c..ac4892a7 100644 --- a/tests/general/unit_test_projection.cpp +++ b/tests/general/unit_test_projection.cpp @@ -9,9 +9,9 @@ TEST(UnitTestProjection, Projection) { - const auto x = omath::Angle::FromDegrees(90.f); + const auto x = omath::Angle::from_degrees(90.f); auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, x, 0.01f, 1000.f); - const auto projected = cam.WorldToScreen({1000, 0, 50}); + const auto projected = cam.world_to_screen({1000, 0, 50}); std::print("{} {} {}", projected->x, projected->y, projected->z); } \ No newline at end of file diff --git a/tests/general/unit_test_triangle.cpp b/tests/general/unit_test_triangle.cpp index bf73b6e5..28d84120 100644 --- a/tests/general/unit_test_triangle.cpp +++ b/tests/general/unit_test_triangle.cpp @@ -68,14 +68,14 @@ TEST_F(UnitTestTriangle, Constructor) TEST_F(UnitTestTriangle, CalculateNormal) { // For t1, the normal should point in the +Z direction (0, 0, 1) or (0, 0, -1) - const Vector3 normal_t1 = t1.CalculateNormal(); + const Vector3 normal_t1 = t1.calculate_normal(); // Check if it's normalized and pointed along Z (sign can differ, so use absolute check) EXPECT_NEAR(std::fabs(normal_t1.z), 1.0f, 1e-5f); - EXPECT_NEAR(normal_t1.Length(), 1.0f, 1e-5f); + EXPECT_NEAR(normal_t1.length(), 1.0f, 1e-5f); // For t3, we expect the normal to be along +Z as well - const Vector3 normal_t3 = t3.CalculateNormal(); + const Vector3 normal_t3 = t3.calculate_normal(); EXPECT_NEAR(std::fabs(normal_t3.z), 1.0f, 1e-5f); } @@ -83,29 +83,29 @@ TEST_F(UnitTestTriangle, CalculateNormal) TEST_F(UnitTestTriangle, SideLengths) { // For t1 side lengths - EXPECT_FLOAT_EQ(t1.SideALength(), std::sqrt(1.0f)); // distance between (0,0,0) and (1,0,0) - EXPECT_FLOAT_EQ(t1.SideBLength(), std::sqrt(1.0f + 1.0f)); // distance between (4,5,6) & (7,8,9)... but we are testing t1, so let's be accurate: + EXPECT_FLOAT_EQ(t1.side_a_length(), std::sqrt(1.0f)); // distance between (0,0,0) and (1,0,0) + EXPECT_FLOAT_EQ(t1.side_b_length(), std::sqrt(1.0f + 1.0f)); // distance between (4,5,6) & (7,8,9)... but we are testing t1, so let's be accurate: // Actually, for t1: vertex2=(1,0,0), vertex3=(0,1,0) // Dist between (0,1,0) and (1,0,0) = sqrt((1-0)^2 + (0-1)^2) = sqrt(1 + 1) = sqrt(2) - EXPECT_FLOAT_EQ(t1.SideBLength(), std::sqrt(2.0f)); + EXPECT_FLOAT_EQ(t1.side_b_length(), std::sqrt(2.0f)); // For t3, side a = distance between vertex1=(0,0,0) and vertex2=(2,0,0), which is 2 // side b = distance between vertex3=(0,2,0) and vertex2=(2,0,0), which is sqrt(2^2 + (-2)^2)= sqrt(8)= 2.828... // We'll just check side a first: - EXPECT_FLOAT_EQ(t3.SideALength(), 2.0f); + EXPECT_FLOAT_EQ(t3.side_a_length(), 2.0f); // Then side b: - EXPECT_FLOAT_EQ(t3.SideBLength(), std::sqrt(8.0f)); + EXPECT_FLOAT_EQ(t3.side_b_length(), std::sqrt(8.0f)); } // Test side vectors TEST_F(UnitTestTriangle, SideVectors) { - const Vector3 sideA_t1 = t1.SideAVector(); // m_vertex1 - m_vertex2 + const Vector3 sideA_t1 = t1.side_a_vector(); // m_vertex1 - m_vertex2 EXPECT_FLOAT_EQ(sideA_t1.x, 0.0f - 1.0f); EXPECT_FLOAT_EQ(sideA_t1.y, 0.0f - 0.0f); EXPECT_FLOAT_EQ(sideA_t1.z, 0.0f - 0.0f); - const Vector3 sideB_t1 = t1.SideBVector(); // m_vertex3 - m_vertex2 + const Vector3 sideB_t1 = t1.side_b_vector(); // m_vertex3 - m_vertex2 EXPECT_FLOAT_EQ(sideB_t1.x, 0.0f - 1.0f); EXPECT_FLOAT_EQ(sideB_t1.y, 1.0f - 0.0f); EXPECT_FLOAT_EQ(sideB_t1.z, 0.0f - 0.0f); @@ -113,19 +113,19 @@ TEST_F(UnitTestTriangle, SideVectors) TEST_F(UnitTestTriangle, IsRectangular) { - EXPECT_TRUE(Triangle>({2,0,0}, {}, {0,2,0}).IsRectangular()); + EXPECT_TRUE(Triangle>({2,0,0}, {}, {0,2,0}).is_rectangular()); } // Test midpoint TEST_F(UnitTestTriangle, MidPoint) { // For t1, midpoint of (0,0,0), (1,0,0), (0,1,0) - const Vector3 mid1 = t1.MidPoint(); + const Vector3 mid1 = t1.mid_point(); EXPECT_FLOAT_EQ(mid1.x, (0.0f + 1.0f + 0.0f) / 3.0f); EXPECT_FLOAT_EQ(mid1.y, (0.0f + 0.0f + 1.0f) / 3.0f); EXPECT_FLOAT_EQ(mid1.z, 0.0f); // For t2, midpoint of (1,2,3), (4,5,6), (7,8,9) - const Vector3 mid2 = t2.MidPoint(); + const Vector3 mid2 = t2.mid_point(); EXPECT_FLOAT_EQ(mid2.x, (1.0f + 4.0f + 7.0f) / 3.0f); 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); diff --git a/tests/general/unit_test_vector2.cpp b/tests/general/unit_test_vector2.cpp index e4133d0c..1896b152 100644 --- a/tests/general/unit_test_vector2.cpp +++ b/tests/general/unit_test_vector2.cpp @@ -150,76 +150,76 @@ TEST_F(UnitTestVector2, SubtractionAssignmentOperator_Float) // Test other member functions TEST_F(UnitTestVector2, DistTo) { - const float dist = v1.DistTo(v2); + const float dist = v1.distance_to(v2); EXPECT_FLOAT_EQ(dist, std::sqrt(18.0f)); } TEST_F(UnitTestVector2, DistTo_SamePoint) { - const float dist = v1.DistTo(v1); + const float dist = v1.distance_to(v1); EXPECT_FLOAT_EQ(dist, 0.0f); } TEST_F(UnitTestVector2, DistToSqr) { - constexpr float distSqr = Vector2(1.0f, 2.0f).DistToSqr(Vector2(4.0f, 5.0f)); + constexpr float distSqr = Vector2(1.0f, 2.0f).distance_to_sqr(Vector2(4.0f, 5.0f)); EXPECT_FLOAT_EQ(distSqr, 18.0f); } TEST_F(UnitTestVector2, DistToSqr_SamePoint) { - constexpr float distSqr = Vector2(1.0f, 2.0f).DistToSqr(Vector2(1.0f, 2.0f)); + constexpr float distSqr = Vector2(1.0f, 2.0f).distance_to_sqr(Vector2(1.0f, 2.0f)); EXPECT_FLOAT_EQ(distSqr, 0.0f); } TEST_F(UnitTestVector2, DotProduct) { - constexpr float dot = Vector2(1.0f, 2.0f).Dot(Vector2(4.0f, 5.0f)); + constexpr float dot = Vector2(1.0f, 2.0f).dot(Vector2(4.0f, 5.0f)); EXPECT_FLOAT_EQ(dot, 14.0f); } TEST_F(UnitTestVector2, DotProduct_PerpendicularVectors) { - constexpr float dot = Vector2(1.0f, 0.0f).Dot(Vector2(0.0f, 1.0f)); + constexpr float dot = Vector2(1.0f, 0.0f).dot(Vector2(0.0f, 1.0f)); EXPECT_FLOAT_EQ(dot, 0.0f); } TEST_F(UnitTestVector2, DotProduct_ParallelVectors) { - constexpr float dot = Vector2(1.0f, 1.0f).Dot(Vector2(2.0f, 2.0f)); + constexpr float dot = Vector2(1.0f, 1.0f).dot(Vector2(2.0f, 2.0f)); EXPECT_FLOAT_EQ(dot, 4.0f); } TEST_F(UnitTestVector2, Length) { - const float length = v1.Length(); + const float length = v1.length(); EXPECT_FLOAT_EQ(length, std::sqrt(5.0f)); } TEST_F(UnitTestVector2, Length_ZeroVector) { constexpr Vector2 v_zero(0.0f, 0.0f); - const float length = v_zero.Length(); + const float length = v_zero.length(); EXPECT_FLOAT_EQ(length, 0.0f); } TEST_F(UnitTestVector2, Length_LargeValues) { constexpr Vector2 v_large(FLT_MAX, FLT_MAX); - const float length = v_large.Length(); + const float length = v_large.length(); EXPECT_TRUE(std::isinf(length)); } TEST_F(UnitTestVector2, LengthSqr) { - constexpr float lengthSqr = Vector2(1.0f, 2.0f).LengthSqr(); + constexpr float lengthSqr = Vector2(1.0f, 2.0f).length_sqr(); EXPECT_FLOAT_EQ(lengthSqr, 5.0f); } TEST_F(UnitTestVector2, Abs) { Vector2 v3(-1.0f, -2.0f); - v3.Abs(); + v3.abs(); EXPECT_FLOAT_EQ(v3.x, 1.0f); EXPECT_FLOAT_EQ(v3.y, 2.0f); } @@ -227,7 +227,7 @@ TEST_F(UnitTestVector2, Abs) TEST_F(UnitTestVector2, Abs_PositiveValues) { Vector2 v3(1.0f, 2.0f); - v3.Abs(); + v3.abs(); EXPECT_FLOAT_EQ(v3.x, 1.0f); EXPECT_FLOAT_EQ(v3.y, 2.0f); } @@ -235,26 +235,26 @@ TEST_F(UnitTestVector2, Abs_PositiveValues) TEST_F(UnitTestVector2, Abs_ZeroValues) { Vector2 v3(0.0f, 0.0f); - v3.Abs(); + v3.abs(); EXPECT_FLOAT_EQ(v3.x, 0.0f); EXPECT_FLOAT_EQ(v3.y, 0.0f); } TEST_F(UnitTestVector2, Sum) { - constexpr float sum = Vector2(1.0f, 2.0f).Sum(); + constexpr float sum = Vector2(1.0f, 2.0f).sum(); EXPECT_FLOAT_EQ(sum, 3.0f); } TEST_F(UnitTestVector2, Sum_NegativeValues) { - constexpr float sum = Vector2(-1.0f, -2.0f).Sum(); + constexpr float sum = Vector2(-1.0f, -2.0f).sum(); EXPECT_FLOAT_EQ(sum, -3.0f); } TEST_F(UnitTestVector2, Normalized) { - const Vector2 v3 = v1.Normalized(); + const Vector2 v3 = v1.normalized(); EXPECT_NEAR(v3.x, 0.44721f, 0.0001f); EXPECT_NEAR(v3.y, 0.89443f, 0.0001f); } @@ -262,7 +262,7 @@ TEST_F(UnitTestVector2, Normalized) TEST_F(UnitTestVector2, Normalized_ZeroVector) { constexpr Vector2 v_zero(0.0f, 0.0f); - const Vector2 v_norm = v_zero.Normalized(); + const Vector2 v_norm = v_zero.normalized(); EXPECT_FLOAT_EQ(v_norm.x, 0.0f); EXPECT_FLOAT_EQ(v_norm.y, 0.0f); } @@ -270,7 +270,7 @@ TEST_F(UnitTestVector2, Normalized_ZeroVector) // Test AsTuple method TEST_F(UnitTestVector2, AsTuple) { - const auto tuple = v1.AsTuple(); + const auto tuple = v1.as_tuple(); EXPECT_FLOAT_EQ(std::get<0>(tuple), v1.x); EXPECT_FLOAT_EQ(std::get<1>(tuple), v1.y); } @@ -347,7 +347,7 @@ TEST_F(UnitTestVector2, NegationOperator_ZeroVector) } // Static assertions (compile-time checks) -static_assert(Vector2(1.0f, 2.0f).LengthSqr() == 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).DistToSqr(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"); +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"); diff --git a/tests/general/unit_test_vector3.cpp b/tests/general/unit_test_vector3.cpp index e543bf4e..9cf66ef3 100644 --- a/tests/general/unit_test_vector3.cpp +++ b/tests/general/unit_test_vector3.cpp @@ -164,26 +164,26 @@ TEST_F(UnitTestVector3, NegationOperator) // Test other member functions TEST_F(UnitTestVector3, DistToSqr) { - constexpr auto distSqr = Vector3(1.0f, 2.0f, 3.0f).DistToSqr(Vector3(4.0f, 5.0f, 6.0f)); + constexpr auto distSqr = Vector3(1.0f, 2.0f, 3.0f).distance_to_sqr(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(distSqr, 27.0f); } TEST_F(UnitTestVector3, DotProduct) { - constexpr auto dot = Vector3(1.0f, 2.0f, 3.0f).Dot(Vector3(4.0f, 5.0f, 6.0f)); + constexpr auto dot = Vector3(1.0f, 2.0f, 3.0f).dot(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(dot, 32.0f); } TEST_F(UnitTestVector3, LengthSqr) { - constexpr auto lengthSqr = Vector3(1.0f, 2.0f, 3.0f).LengthSqr(); + constexpr auto lengthSqr = Vector3(1.0f, 2.0f, 3.0f).length_sqr(); EXPECT_FLOAT_EQ(lengthSqr, 14.0f); } TEST_F(UnitTestVector3, Abs) { auto v3 = Vector3(-1.0f, -2.0f, -3.0f); - v3.Abs(); + v3.abs(); EXPECT_FLOAT_EQ(v3.x, 1.0f); EXPECT_FLOAT_EQ(v3.y, 2.0f); EXPECT_FLOAT_EQ(v3.z, 3.0f); @@ -191,19 +191,19 @@ TEST_F(UnitTestVector3, Abs) TEST_F(UnitTestVector3, Sum) { - constexpr auto sum = Vector3(1.0f, 2.0f, 3.0f).Sum(); + constexpr auto sum = Vector3(1.0f, 2.0f, 3.0f).sum(); EXPECT_FLOAT_EQ(sum, 6.0f); } TEST_F(UnitTestVector3, Sum2D) { - constexpr auto sum2D = Vector3(1.0f, 2.0f, 3.0f).Sum2D(); + constexpr auto sum2D = Vector3(1.0f, 2.0f, 3.0f).sum_2d(); EXPECT_FLOAT_EQ(sum2D, 3.0f); } TEST_F(UnitTestVector3, CrossProduct) { - constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f).Cross(Vector3(4.0f, 5.0f, 6.0f)); + constexpr Vector3 v3 = Vector3(1.0f, 2.0f, 3.0f).cross(Vector3(4.0f, 5.0f, 6.0f)); EXPECT_FLOAT_EQ(v3.x, -3.0f); EXPECT_FLOAT_EQ(v3.y, 6.0f); EXPECT_FLOAT_EQ(v3.z, -3.0f); @@ -298,41 +298,41 @@ TEST_F(UnitTestVector3, Division_WithNaN) // Test Length, Length2D, and Normalized TEST_F(UnitTestVector3, Length) { - const float length = v1.Length(); + const float length = v1.length(); EXPECT_FLOAT_EQ(length, std::sqrt(14.0f)); } TEST_F(UnitTestVector3, Length_ZeroVector) { constexpr Vector3 v_zero(0.0f, 0.0f, 0.0f); - const float length = v_zero.Length(); + const float length = v_zero.length(); EXPECT_FLOAT_EQ(length, 0.0f); } TEST_F(UnitTestVector3, Length_LargeValues) { constexpr Vector3 v_large(FLT_MAX, FLT_MAX, FLT_MAX); - const float length = v_large.Length(); + const float length = v_large.length(); EXPECT_TRUE(std::isinf(length)); } TEST_F(UnitTestVector3, Length2D) { - const float length2D = v1.Length2D(); + const float length2D = v1.length_2d(); EXPECT_FLOAT_EQ(length2D, std::sqrt(5.0f)); } TEST_F(UnitTestVector3, Normalized) { - const Vector3 v_norm = v1.Normalized(); - const float length = v_norm.Length(); + const Vector3 v_norm = v1.normalized(); + const float length = v_norm.length(); EXPECT_NEAR(length, 1.0f, 0.0001f); } TEST_F(UnitTestVector3, Normalized_ZeroVector) { constexpr Vector3 v_zero(0.0f, 0.0f, 0.0f); - const Vector3 v_norm = v_zero.Normalized(); + const Vector3 v_norm = v_zero.normalized(); EXPECT_FLOAT_EQ(v_norm.x, 0.0f); EXPECT_FLOAT_EQ(v_norm.y, 0.0f); EXPECT_FLOAT_EQ(v_norm.z, 0.0f); @@ -343,7 +343,7 @@ TEST_F(UnitTestVector3, CrossProduct_ParallelVectors) { constexpr Vector3 v_a(1.0f, 2.0f, 3.0f); constexpr Vector3 v_b = v_a * 2.0f; // Parallel to v_a - constexpr Vector3 cross = v_a.Cross(v_b); + constexpr Vector3 cross = v_a.cross(v_b); EXPECT_FLOAT_EQ(cross.x, 0.0f); EXPECT_FLOAT_EQ(cross.y, 0.0f); EXPECT_FLOAT_EQ(cross.z, 0.0f); @@ -353,7 +353,7 @@ TEST_F(UnitTestVector3, CrossProduct_OrthogonalVectors) { constexpr Vector3 v_a(1.0f, 0.0f, 0.0f); constexpr Vector3 v_b(0.0f, 1.0f, 0.0f); - constexpr Vector3 cross = v_a.Cross(v_b); + constexpr Vector3 cross = v_a.cross(v_b); EXPECT_FLOAT_EQ(cross.x, 0.0f); EXPECT_FLOAT_EQ(cross.y, 0.0f); EXPECT_FLOAT_EQ(cross.z, 1.0f); @@ -381,7 +381,7 @@ TEST_F(UnitTestVector3, Subtraction_NegativeValues) // Test AsTuple method TEST_F(UnitTestVector3, AsTuple) { - const auto tuple = v1.AsTuple(); + const auto tuple = v1.as_tuple(); EXPECT_FLOAT_EQ(std::get<0>(tuple), v1.x); EXPECT_FLOAT_EQ(std::get<1>(tuple), v1.y); EXPECT_FLOAT_EQ(std::get<2>(tuple), v1.z); @@ -390,20 +390,20 @@ TEST_F(UnitTestVector3, AsTuple) // Test AsTuple method TEST_F(UnitTestVector3, AngleBeatween) { - EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).AngleBetween({1, 0 ,0}).value().AsDegrees(), 90.0f); - EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).AngleBetween({0.0f, 0.0f, 1.0f}).value().AsDegrees(), 0.0f); - EXPECT_FALSE(Vector3(0.0f, 0.0f, 0.0f).AngleBetween({0.0f, 0.0f, 1.0f}).has_value()); + EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).angle_between({1, 0 ,0}).value().as_degrees(), 90.0f); + EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).angle_between({0.0f, 0.0f, 1.0f}).value().as_degrees(), 0.0f); + 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).IsPerpendicular({1, 0 ,0}), true); - EXPECT_EQ(Vector3(0.0f, 0.0f, 1.0f).IsPerpendicular({0.0f, 0.0f, 1.0f}), false); - EXPECT_FALSE(Vector3(0.0f, 0.0f, 0.0f).IsPerpendicular({0.0f, 0.0f, 1.0f})); + 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})); } // Static assertions (compile-time checks) -static_assert(Vector3(1.0f, 2.0f, 3.0f).LengthSqr() == 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).DistToSqr(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).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"); diff --git a/tests/general/unit_test_vector4.cpp b/tests/general/unit_test_vector4.cpp index 00752166..dcdb7fea 100644 --- a/tests/general/unit_test_vector4.cpp +++ b/tests/general/unit_test_vector4.cpp @@ -180,20 +180,20 @@ TEST_F(UnitTestVector4, NegationOperator) // Test other member functions TEST_F(UnitTestVector4, LengthSqr) { - constexpr float lengthSqr = Vector4(1.0f, 2.0f, 3.0f, 4.0f).LengthSqr(); + constexpr float lengthSqr = Vector4(1.0f, 2.0f, 3.0f, 4.0f).length_sqr(); EXPECT_FLOAT_EQ(lengthSqr, 30.0f); } TEST_F(UnitTestVector4, DotProduct) { - constexpr float dot = Vector4(1.0f, 2.0f, 3.0f, 4.0f).Dot(Vector4(4.0f, 5.0f, 6.0f, 7.0f)); + constexpr float dot = Vector4(1.0f, 2.0f, 3.0f, 4.0f).dot(Vector4(4.0f, 5.0f, 6.0f, 7.0f)); EXPECT_FLOAT_EQ(dot, 60.0f); } TEST_F(UnitTestVector4, Abs) { Vector4 v3 = Vector4(-1.0f, -2.0f, -3.0f, -4.0f); - v3.Abs(); + v3.abs(); EXPECT_FLOAT_EQ(v3.x, 1.0f); EXPECT_FLOAT_EQ(v3.y, 2.0f); EXPECT_FLOAT_EQ(v3.z, 3.0f); @@ -202,14 +202,14 @@ TEST_F(UnitTestVector4, Abs) TEST_F(UnitTestVector4, Sum) { - constexpr float sum = Vector4(1.0f, 2.0f, 3.0f, 4.0f).Sum(); + constexpr float sum = Vector4(1.0f, 2.0f, 3.0f, 4.0f).sum(); EXPECT_FLOAT_EQ(sum, 10.0f); } TEST_F(UnitTestVector4, Clamp) { Vector4 v3 = Vector4(1.0f, 2.0f, 3.0f, 4.0f); - v3.Clamp(1.5f, 2.5f); + v3.clamp(1.5f, 2.5f); EXPECT_FLOAT_EQ(v3.x, 1.5f); EXPECT_FLOAT_EQ(v3.y, 2.0f); EXPECT_FLOAT_EQ(v3.z, 2.5f); From b5e788385d670fc2965faabd1f868d350822531d Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 3 May 2025 20:38:58 +0300 Subject: [PATCH 282/795] fixed style --- include/omath/angles.hpp | 23 ++++++++++++----------- include/omath/mat.hpp | 12 ++++++------ include/omath/pathfinding/a_star.hpp | 2 +- include/omath/projection/camera.hpp | 2 +- source/collision/line_tracer.cpp | 1 + source/engines/iw_engine/camera.cpp | 2 +- source/engines/opengl_engine/camera.cpp | 2 +- source/engines/source_engine/camera.cpp | 2 +- source/engines/unity_engine/camera.cpp | 2 +- source/pathfinding/a_star.cpp | 9 +++++---- tests/general/unit_test_mat.cpp | 8 ++++---- 11 files changed, 34 insertions(+), 31 deletions(-) diff --git a/include/omath/angles.hpp b/include/omath/angles.hpp index bd654148..cafacbb0 100644 --- a/include/omath/angles.hpp +++ b/include/omath/angles.hpp @@ -12,23 +12,23 @@ namespace omath::angles requires std::is_floating_point_v [[nodiscard]] constexpr Type radians_to_degrees(const Type& radians) { - return radians * (Type(180) / std::numbers::pi_v); + return radians * (static_cast(180) / std::numbers::pi_v); } template requires std::is_floating_point_v [[nodiscard]] constexpr Type degrees_to_radians(const Type& degrees) { - return degrees * (std::numbers::pi_v / Type(180)); + return degrees * (std::numbers::pi_v / static_cast(180)); } - template - requires std::is_floating_point_v - [[nodiscard]] type horizontal_fov_to_vertical(const type& horizontal_fov, const type& aspect) + template + requires std::is_floating_point_v + [[nodiscard]] Type horizontal_fov_to_vertical(const Type& horizontal_fov, const Type& aspect) { const auto fov_rad = degrees_to_radians(horizontal_fov); - const auto vert_fov = type(2) * std::atan(std::tan(fov_rad / type(2)) / aspect); + const auto vert_fov = static_cast(2) * std::atan(std::tan(fov_rad / static_cast(2)) / aspect); return radians_to_degrees(vert_fov); } @@ -39,7 +39,8 @@ namespace omath::angles { const auto fov_as_radians = degrees_to_radians(vertical_fov); - const auto horizontal_fov = Type(2) * std::atan(std::tan(fov_as_radians / Type(2)) * aspect); + const auto horizontal_fov + = static_cast(2) * std::atan(std::tan(fov_as_radians / static_cast(2)) * aspect); return radians_to_degrees(horizontal_fov); } @@ -53,11 +54,11 @@ namespace omath::angles const Type range = max - min; - Type wrappedAngle = std::fmod(angle - min, range); + Type wrapped_angle = std::fmod(angle - min, range); - if (wrappedAngle < 0) - wrappedAngle += range; + if (wrapped_angle < 0) + wrapped_angle += range; - return wrappedAngle + min; + return wrapped_angle + min; } } // namespace omath::angles diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index d13dfc52..8ebbd22e 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -226,7 +226,7 @@ namespace omath } [[nodiscard]] - constexpr Mat Transposed() const noexcept + constexpr Mat transposed() const noexcept { Mat transposed; for (size_t i = 0; i < Rows; ++i) @@ -237,7 +237,7 @@ namespace omath } [[nodiscard]] - constexpr Type Determinant() const + constexpr Type determinant() const { static_assert(Rows == Columns, "Determinant is only defined for square matrices."); @@ -284,7 +284,7 @@ namespace omath [[nodiscard]] constexpr Type minor(const size_t row, const size_t column) const { - return strip(row, column).Determinant(); + return strip(row, column).determinant(); } [[nodiscard]] @@ -355,17 +355,17 @@ namespace omath [[nodiscard]] constexpr std::optional inverted() const { - const auto det = Determinant(); + const auto det = determinant(); if (det == 0) return std::nullopt; - const auto transposed = Transposed(); + const auto transposed_mat = transposed(); Mat result; for (std::size_t row = 0; row < Rows; row++) for (std::size_t column = 0; column < Rows; column++) - result.at(row, column) = transposed.alg_complement(row, column); + result.at(row, column) = transposed_mat.alg_complement(row, column); result /= det; diff --git a/include/omath/pathfinding/a_star.hpp b/include/omath/pathfinding/a_star.hpp index 02ec0a41..ba5041da 100644 --- a/include/omath/pathfinding/a_star.hpp +++ b/include/omath/pathfinding/a_star.hpp @@ -25,6 +25,6 @@ namespace omath::pathfinding [[nodiscard]] static auto get_perfect_node(const std::unordered_map, PathNode>& open_list, - const Vector3& endVertex); + const Vector3& end_vertex); }; } // namespace omath::pathfinding diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index 2abb511d..93f956a9 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -19,7 +19,7 @@ namespace omath::projection float m_width; float m_height; - [[nodiscard]] constexpr float AspectRatio() const + [[nodiscard]] constexpr float aspect_ratio() const { return m_width / m_height; } diff --git a/source/collision/line_tracer.cpp b/source/collision/line_tracer.cpp index 06f22c7f..e60c0de9 100644 --- a/source/collision/line_tracer.cpp +++ b/source/collision/line_tracer.cpp @@ -42,6 +42,7 @@ namespace omath::collision return ray.end; const auto q = t.cross(side_a); + // ReSharper disable once CppTooWideScopeInitStatement const auto v = ray_dir.dot(q) * inv_det; if ((v < 0 && std::abs(v) > k_epsilon) || (u + v > 1 && std::abs(u + v - 1) > k_epsilon)) diff --git a/source/engines/iw_engine/camera.cpp b/source/engines/iw_engine/camera.cpp index 68849f9e..77aaf204 100644 --- a/source/engines/iw_engine/camera.cpp +++ b/source/engines/iw_engine/camera.cpp @@ -27,7 +27,7 @@ namespace omath::iw_engine } Mat4X4 Camera::calc_projection_matrix() const { - return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.AspectRatio(), + return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), m_near_plane_distance, m_far_plane_distance); } } // namespace omath::iw_engine \ No newline at end of file diff --git a/source/engines/opengl_engine/camera.cpp b/source/engines/opengl_engine/camera.cpp index 8fc0ec59..4b694100 100644 --- a/source/engines/opengl_engine/camera.cpp +++ b/source/engines/opengl_engine/camera.cpp @@ -27,7 +27,7 @@ namespace omath::opengl_engine } Mat4X4 Camera::calc_projection_matrix() const { - return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.AspectRatio(), + return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), m_near_plane_distance, m_far_plane_distance); } } // namespace omath::opengl_engine diff --git a/source/engines/source_engine/camera.cpp b/source/engines/source_engine/camera.cpp index efc059c7..3b550f5b 100644 --- a/source/engines/source_engine/camera.cpp +++ b/source/engines/source_engine/camera.cpp @@ -29,7 +29,7 @@ namespace omath::source_engine Mat4X4 Camera::calc_projection_matrix() const { - return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.AspectRatio(), + return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), m_near_plane_distance, m_far_plane_distance); } } // namespace omath::source_engine diff --git a/source/engines/unity_engine/camera.cpp b/source/engines/unity_engine/camera.cpp index 836b4e9e..f06304db 100644 --- a/source/engines/unity_engine/camera.cpp +++ b/source/engines/unity_engine/camera.cpp @@ -21,7 +21,7 @@ namespace omath::unity_engine } Mat4X4 Camera::calc_projection_matrix() const { - return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.AspectRatio(), + return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), m_near_plane_distance, m_far_plane_distance); } } // namespace omath::unity_engine diff --git a/source/pathfinding/a_star.cpp b/source/pathfinding/a_star.cpp index 658d9ed2..593c9079 100644 --- a/source/pathfinding/a_star.cpp +++ b/source/pathfinding/a_star.cpp @@ -38,13 +38,13 @@ namespace omath::pathfinding return path; } auto Astar::get_perfect_node(const std::unordered_map, PathNode>& open_list, - const Vector3& endVertex) + const Vector3& end_vertex) { return std::ranges::min_element(open_list, - [&endVertex](const auto& a, const auto& b) + [&end_vertex](const auto& a, const auto& b) { - const float fa = a.second.g_cost + a.first.distance_to(endVertex); - const float fb = b.second.g_cost + b.first.distance_to(endVertex); + const float fa = a.second.g_cost + a.first.distance_to(end_vertex); + const float fb = b.second.g_cost + b.first.distance_to(end_vertex); return fa < fb; }); } @@ -86,6 +86,7 @@ namespace omath::pathfinding const float tentative_g_cost = current_node.g_cost + neighbor.distance_to(current); + // ReSharper disable once CppTooWideScopeInitStatement const auto open_it = open_list.find(neighbor); if (open_it == open_list.end() || tentative_g_cost < open_it->second.g_cost) diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index 9350f825..c6162c73 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -96,7 +96,7 @@ TEST_F(UnitTestMat, Operator_Division_Scalar) // Test matrix functions TEST_F(UnitTestMat, Transpose) { - Mat<2, 2> m3 = m2.Transposed(); + Mat<2, 2> m3 = m2.transposed(); EXPECT_FLOAT_EQ(m3.at(0, 0), m2.at(0, 0)); EXPECT_FLOAT_EQ(m3.at(0, 1), m2.at(1, 0)); EXPECT_FLOAT_EQ(m3.at(1, 0), m2.at(0, 1)); @@ -105,7 +105,7 @@ TEST_F(UnitTestMat, Transpose) TEST_F(UnitTestMat, Determinant) { - const float det = m2.Determinant(); + const float det = m2.determinant(); EXPECT_FLOAT_EQ(det, -2.0f); } @@ -175,7 +175,7 @@ TEST_F(UnitTestMat, Method_At_OutOfRange) // Test Determinant for 3x3 matrix TEST(UnitTestMatStandalone, Determinant_3x3) { - constexpr auto det = Mat<3, 3>{{6, 1, 1}, {4, -2, 5}, {2, 8, 7}}.Determinant(); + constexpr auto det = Mat<3, 3>{{6, 1, 1}, {4, -2, 5}, {2, 8, 7}}.determinant(); EXPECT_FLOAT_EQ(det, -306.0f); } @@ -196,7 +196,7 @@ TEST(UnitTestMatStandalone, Strip_3x3) TEST(UnitTestMatStandalone, Transpose_NonSquare) { constexpr Mat<2, 3> m{{1.0f, 2.0f, 3.0f}, {4.0f, 5.0f, 6.0f}}; - auto transposed = m.Transposed(); + auto transposed = m.transposed(); EXPECT_EQ(transposed.row_count(), 3); EXPECT_EQ(transposed.columns_count(), 2); EXPECT_FLOAT_EQ(transposed.at(0, 0), 1.0f); From dd738f365da236feebf645b9baa284a465e5ad4e Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 3 May 2025 20:51:50 +0300 Subject: [PATCH 283/795] fix --- include/omath/vector4.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/vector4.hpp b/include/omath/vector4.hpp index 33d8380c..36e0c0a3 100644 --- a/include/omath/vector4.hpp +++ b/include/omath/vector4.hpp @@ -159,7 +159,7 @@ namespace omath #ifdef OMATH_IMGUI_INTEGRATION [[nodiscard]] - ImVec4 ToImVec4() const + ImVec4 to_im_vec4() const { return { static_cast(this->x), From 449c60133c7eaf283d49fa7e7de07269cd839fec Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 3 May 2025 21:36:16 +0300 Subject: [PATCH 284/795] bugfix --- include/omath/vector2.hpp | 6 +++--- include/omath/vector3.hpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/omath/vector2.hpp b/include/omath/vector2.hpp index 9a512a26..915b1d30 100644 --- a/include/omath/vector2.hpp +++ b/include/omath/vector2.hpp @@ -134,14 +134,14 @@ namespace omath return len > 0.f ? *this / len : *this; } #else - [[nodiscard]] Type Length() const + [[nodiscard]] Type length() const { return std::hypot(x, y); } - [[nodiscard]] Vector2 Normalized() const + [[nodiscard]] Vector2 normalized() const { - const Type len = Length(); + const Type len = length(); return len > 0.f ? *this / len : *this; } #endif diff --git a/include/omath/vector3.hpp b/include/omath/vector3.hpp index 8f266096..d5351686 100644 --- a/include/omath/vector3.hpp +++ b/include/omath/vector3.hpp @@ -149,19 +149,19 @@ namespace omath [[nodiscard]] Vector3 normalized() const { - const Type length = this->Length(); + const Type len = this->length(); - return length != 0 ? *this / length : *this; + return len != 0 ? *this / len : *this; } [[nodiscard]] Type length_2d() const { - return Vector2::Length(); + return Vector2::length(); } [[nodiscard]] Type distance_to(const Vector3& vOther) const { - return (*this - vOther).Length(); + return (*this - vOther).length(); } #endif From 7f55383bc13d70ada372bca2ab24a52b6eb049be Mon Sep 17 00:00:00 2001 From: Orange++ Date: Sat, 3 May 2025 22:04:10 +0300 Subject: [PATCH 285/795] Create cmake-multi-platform.yml --- .github/workflows/cmake-multi-platform.yml | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/cmake-multi-platform.yml diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml new file mode 100644 index 00000000..72405a38 --- /dev/null +++ b/.github/workflows/cmake-multi-platform.yml @@ -0,0 +1,57 @@ +name: Omath CI (presets) + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-test: + strategy: + fail-fast: false + matrix: + # ------- preset ↔ runner pairs ------------- + include: + - os: ubuntu-latest # GCC / Ninja (from linux-release preset) + preset: linux-release + - os: macos-latest # Clang / Ninja (from darwin-release preset) + preset: darwin-release + - os: windows-latest # MSVC / Ninja (from windows-release preset) + preset: windows-release + + runs-on: ${{ matrix.os }} + + steps: + # 1) checkout (incl. GoogleTest sub-module) + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + # 2) Ninja (needed only on Windows images) + - name: Install Ninja + uses: seanmiddleditch/gha-setup-ninja@v4 + + # 3) MSVC environment + - name: Set up MSVC developer command-prompt + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + # 4) Configure with the selected CMake preset + - name: Configure (cmake --preset) + run: cmake --preset ${{ matrix.preset }} -DOMATH_BUILD_TESTS=ON + + # 5) Build + - name: Build + # All configure presets put the binary dir in cmake-build/build/${presetName} + run: cmake --build cmake-build/build/${{ matrix.preset }} + + # 6) Run unit-tests + - name: Test + working-directory: cmake-build/build/${{ matrix.preset }} + run: ctest --output-on-failure From 30eef59360fc90345c10515b5cec423e588b3795 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Sat, 3 May 2025 22:12:08 +0300 Subject: [PATCH 286/795] Update cmake-multi-platform.yml switched to clang Update cmake-multi-platform.yml Update cmake-multi-platform.yml Update cmake-multi-platform.yml Update cmake-multi-platform.yml Update cmake-multi-platform.yml Update cmake-multi-platform.yml Update cmake-multi-platform.yml Update cmake-multi-platform.yml added runtime dir fix --- .github/workflows/cmake-multi-platform.yml | 80 ++++++++++++++-------- CMakePresets.json | 4 +- tests/CMakeLists.txt | 1 + 3 files changed, 54 insertions(+), 31 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 72405a38..f72a6c27 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -1,4 +1,4 @@ -name: Omath CI (presets) +name: Omath CI (Arch Linux / Windows) on: push: @@ -10,48 +10,70 @@ concurrency: group: ci-${{ github.ref }} cancel-in-progress: true + +############################################################################## +# 1) ARCH LINUX – Clang / Ninja +############################################################################## jobs: - build-and-test: - strategy: - fail-fast: false - matrix: - # ------- preset ↔ runner pairs ------------- - include: - - os: ubuntu-latest # GCC / Ninja (from linux-release preset) - preset: linux-release - - os: macos-latest # Clang / Ninja (from darwin-release preset) - preset: darwin-release - - os: windows-latest # MSVC / Ninja (from windows-release preset) - preset: windows-release - - runs-on: ${{ matrix.os }} + arch-build-and-test: + name: Arch Linux (Clang) + runs-on: ubuntu-latest + container: archlinux:latest + + steps: + - name: Install basic tool-chain with pacman + shell: bash + run: | + pacman -Sy --noconfirm archlinux-keyring + pacman -Syu --noconfirm --needed \ + git base-devel clang cmake ninja + + - name: Checkout repository (with sub-modules) + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Configure (cmake --preset) + shell: bash + run: cmake --preset linux-release -DOMATH_BUILD_TESTS=ON + + - name: Build + shell: bash + run: cmake --build cmake-build/build/linux-release --target all + + - name: Run unit_tests + shell: bash + run: ./out/Release/unit_tests + + + +############################################################################## +# 2) Windows – MSVC / Ninja +############################################################################## + windows-build-and-test: + name: Windows (MSVC) + runs-on: windows-latest steps: - # 1) checkout (incl. GoogleTest sub-module) - - name: Checkout repository + - name: Checkout repository (with sub-modules) uses: actions/checkout@v4 with: submodules: recursive - # 2) Ninja (needed only on Windows images) - name: Install Ninja uses: seanmiddleditch/gha-setup-ninja@v4 - # 3) MSVC environment - name: Set up MSVC developer command-prompt - if: runner.os == 'Windows' uses: ilammy/msvc-dev-cmd@v1 - # 4) Configure with the selected CMake preset - name: Configure (cmake --preset) - run: cmake --preset ${{ matrix.preset }} -DOMATH_BUILD_TESTS=ON + shell: bash + run: cmake --preset windows-release -DOMATH_BUILD_TESTS=ON - # 5) Build - name: Build - # All configure presets put the binary dir in cmake-build/build/${presetName} - run: cmake --build cmake-build/build/${{ matrix.preset }} + shell: bash + run: cmake --build cmake-build/build/windows-release --target all - # 6) Run unit-tests - - name: Test - working-directory: cmake-build/build/${{ matrix.preset }} - run: ctest --output-on-failure + - name: Run unit_tests.exe + shell: bash + run: ./out/Release/unit_tests.exe diff --git a/CMakePresets.json b/CMakePresets.json index 1f5ceda8..65705a9c 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -40,8 +40,8 @@ "binaryDir": "${sourceDir}/cmake-build/build/${presetName}", "installDir": "${sourceDir}/cmake-build/install/${presetName}", "cacheVariables": { - "CMAKE_C_COMPILER": "gcc", - "CMAKE_CXX_COMPILER": "g++" + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" }, "condition": { "type": "equals", diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b72c6775..5660336b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable(unit_tests ${UNIT_TESTS_SOURCES}) set_target_properties(unit_tests PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" UNITY_BUILD ON UNITY_BUILD_BATCH_SIZE 20 CXX_STANDARD 23 From e025e99d18a6766e4ac661492dd1da8d1fba91e8 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Sun, 4 May 2025 00:55:35 +0300 Subject: [PATCH 287/795] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0822b339..071fd0c3 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ ![GitHub contributors](https://img.shields.io/github/contributors/orange-cpp/omath) ![GitHub top language](https://img.shields.io/github/languages/top/orange-cpp/omath) [![CodeFactor](https://www.codefactor.io/repository/github/orange-cpp/omath/badge)](https://www.codefactor.io/repository/github/orange-cpp/omath) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml) ![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath)
From f096f7179fd02ad845dc81788d8f016a7d4bdb0e Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 4 May 2025 17:42:32 +0300 Subject: [PATCH 288/795] fixed clang format --- .clang-format | 2 +- include/omath/angle.hpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.clang-format b/.clang-format index 402faea9..0cd7d628 100644 --- a/.clang-format +++ b/.clang-format @@ -33,7 +33,7 @@ BraceWrapping: BeforeWhile: true SplitEmptyRecord: true SplitEmptyNamespace: true -BreakBeforeBinaryOperators: All +BreakBeforeBinaryOperators: NonAssignment BreakBeforeConceptDeclarations: false ColumnLimit: 120 IncludeBlocks: Merge diff --git a/include/omath/angle.hpp b/include/omath/angle.hpp index 61e75d0d..e2949a32 100644 --- a/include/omath/angle.hpp +++ b/include/omath/angle.hpp @@ -114,8 +114,7 @@ namespace omath } [[nodiscard]] - constexpr std::partial_ordering operator<=>(const Angle& other) const - = default; + constexpr std::partial_ordering operator<=>(const Angle& other) const = default; constexpr Angle& operator-=(const Angle& other) { From af21f9a946791c98e0f9f0d47596d5c21510b42b Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 4 May 2025 18:07:35 +0300 Subject: [PATCH 289/795] removed pow --- include/omath/projectile_prediction/target.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/projectile_prediction/target.hpp b/include/omath/projectile_prediction/target.hpp index 40e010c8..74707177 100644 --- a/include/omath/projectile_prediction/target.hpp +++ b/include/omath/projectile_prediction/target.hpp @@ -16,7 +16,7 @@ namespace omath::projectile_prediction auto predicted = m_origin + m_velocity * time; if (m_is_airborne) - predicted.z -= gravity * std::pow(time, 2.f) * 0.5f; + predicted.z -= gravity * (time*time) * 0.5f; return predicted; } From 49ea1133480f93a5fcd149f9f05a218dab82d29a Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 4 May 2025 18:54:33 +0300 Subject: [PATCH 290/795] fixed style --- include/omath/mat.hpp | 1 - include/omath/pathfinding/navigation_mesh.hpp | 1 - 2 files changed, 2 deletions(-) diff --git a/include/omath/mat.hpp b/include/omath/mat.hpp index 8ebbd22e..4f527340 100644 --- a/include/omath/mat.hpp +++ b/include/omath/mat.hpp @@ -451,7 +451,6 @@ namespace omath {up.x, up.y, up.z, 0}, {forward.x, forward.y, forward.z, 0}, {0, 0, 0, 1}, - } * mat_translation(-camera_origin); } diff --git a/include/omath/pathfinding/navigation_mesh.hpp b/include/omath/pathfinding/navigation_mesh.hpp index e5ea0cbb..588681b3 100644 --- a/include/omath/pathfinding/navigation_mesh.hpp +++ b/include/omath/pathfinding/navigation_mesh.hpp @@ -14,7 +14,6 @@ namespace omath::pathfinding enum Error { - }; class NavigationMesh final From ce08fcdd29b99efd5639318a99c21f808325390e Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 4 May 2025 19:03:18 +0300 Subject: [PATCH 291/795] removed useless source files --- source/color.cpp | 12 ------------ source/projectile_prediction/proj_pred_engine.cpp | 9 --------- source/projectile_prediction/target.cpp | 10 ---------- source/projection/camera.cpp | 8 -------- 4 files changed, 39 deletions(-) delete mode 100644 source/color.cpp delete mode 100644 source/projectile_prediction/proj_pred_engine.cpp delete mode 100644 source/projectile_prediction/target.cpp delete mode 100644 source/projection/camera.cpp diff --git a/source/color.cpp b/source/color.cpp deleted file mode 100644 index 090725ca..00000000 --- a/source/color.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Created by vlad on 2/4/24. -// - -#include "omath/color.hpp" -#include -#include - -namespace omath -{ - -} diff --git a/source/projectile_prediction/proj_pred_engine.cpp b/source/projectile_prediction/proj_pred_engine.cpp deleted file mode 100644 index f1229006..00000000 --- a/source/projectile_prediction/proj_pred_engine.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// -// Created by Vlad on 2/23/2025. -// -#include "omath/projectile_prediction/proj_pred_engine.hpp" - -namespace omath::projectile_prediction -{ - -} // namespace omath::projectile_prediction diff --git a/source/projectile_prediction/target.cpp b/source/projectile_prediction/target.cpp deleted file mode 100644 index e704b290..00000000 --- a/source/projectile_prediction/target.cpp +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by Vlad on 6/9/2024. -// - -#include "omath/projectile_prediction/projectile.hpp" - -namespace omath::prediction -{ - -} diff --git a/source/projection/camera.cpp b/source/projection/camera.cpp deleted file mode 100644 index 26d3d0db..00000000 --- a/source/projection/camera.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// -// Created by Vlad on 27.08.2024. -// -#include "omath/projection/camera.hpp" - -namespace omath::projection -{ -} From f6f8bba0327e5c23e5cc3bfaff94684f7a02434e Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 4 May 2025 19:05:10 +0300 Subject: [PATCH 292/795] changed license to zlib --- LICENSE | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/LICENSE b/LICENSE index 0fccfbbf..c7ea068d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,17 @@ -The MIT License (MIT) +Copyright (C) 2024-2025 Orange++ -Copyright (c) 2024 Orange++ +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. \ No newline at end of file From 9a38d47b0d46b05ba000cbfa292c1d9c79ed2fa0 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 4 May 2025 19:13:26 +0300 Subject: [PATCH 293/795] added noexcept for vector types --- include/omath/vector2.hpp | 56 ++++++++++++++++---------------- include/omath/vector3.hpp | 68 +++++++++++++++++++-------------------- include/omath/vector4.hpp | 46 +++++++++++++------------- 3 files changed, 85 insertions(+), 85 deletions(-) diff --git a/include/omath/vector2.hpp b/include/omath/vector2.hpp index 915b1d30..03a98cca 100644 --- a/include/omath/vector2.hpp +++ b/include/omath/vector2.hpp @@ -24,25 +24,25 @@ namespace omath // Constructors constexpr Vector2() = default; - constexpr Vector2(const Type& x, const Type& y): x(x), y(y) + constexpr Vector2(const Type& x, const Type& y) noexcept: x(x), y(y) { } // Equality operators [[nodiscard]] - constexpr bool operator==(const Vector2& src) const + constexpr bool operator==(const Vector2& src) const noexcept { return x == src.x && y == src.y; } [[nodiscard]] - constexpr bool operator!=(const Vector2& src) const + constexpr bool operator!=(const Vector2& src) const noexcept { return !(*this == src); } // Compound assignment operators - constexpr Vector2& operator+=(const Vector2& v) + constexpr Vector2& operator+=(const Vector2& v) noexcept { x += v.x; y += v.y; @@ -50,7 +50,7 @@ namespace omath return *this; } - constexpr Vector2& operator-=(const Vector2& v) + constexpr Vector2& operator-=(const Vector2& v) noexcept { x -= v.x; y -= v.y; @@ -58,7 +58,7 @@ namespace omath return *this; } - constexpr Vector2& operator*=(const Vector2& v) + constexpr Vector2& operator*=(const Vector2& v) noexcept { x *= v.x; y *= v.y; @@ -66,7 +66,7 @@ namespace omath return *this; } - constexpr Vector2& operator/=(const Vector2& v) + constexpr Vector2& operator/=(const Vector2& v) noexcept { x /= v.x; y /= v.y; @@ -74,7 +74,7 @@ namespace omath return *this; } - constexpr Vector2& operator*=(const Type& fl) + constexpr Vector2& operator*=(const Type& fl) noexcept { x *= fl; y *= fl; @@ -82,7 +82,7 @@ namespace omath return *this; } - constexpr Vector2& operator/=(const Type& fl) + constexpr Vector2& operator/=(const Type& fl) noexcept { x /= fl; y /= fl; @@ -90,7 +90,7 @@ namespace omath return *this; } - constexpr Vector2& operator+=(const Type& fl) + constexpr Vector2& operator+=(const Type& fl) noexcept { x += fl; y += fl; @@ -98,7 +98,7 @@ namespace omath return *this; } - constexpr Vector2& operator-=(const Type& fl) + constexpr Vector2& operator-=(const Type& fl) noexcept { x -= fl; y -= fl; @@ -107,50 +107,50 @@ namespace omath } // Basic vector operations - [[nodiscard]] Type distance_to(const Vector2& other) const + [[nodiscard]] Type distance_to(const Vector2& other) const noexcept { return std::sqrt(distance_to_sqr(other)); } - [[nodiscard]] constexpr Type distance_to_sqr(const Vector2& other) const + [[nodiscard]] constexpr Type distance_to_sqr(const Vector2& other) const noexcept { return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y); } - [[nodiscard]] constexpr Type dot(const Vector2& other) const + [[nodiscard]] constexpr Type dot(const Vector2& other) const noexcept { return x * other.x + y * other.y; } #ifndef _MSC_VER - [[nodiscard]] constexpr Type length() const + [[nodiscard]] constexpr Type length() const noexcept { return std::hypot(this->x, this->y); } - [[nodiscard]] constexpr Vector2 normalized() const + [[nodiscard]] constexpr Vector2 normalized() const noexcept { const Type len = length(); return len > 0.f ? *this / len : *this; } #else - [[nodiscard]] Type length() const + [[nodiscard]] Type length() const noexcept { return std::hypot(x, y); } - [[nodiscard]] Vector2 normalized() const + [[nodiscard]] Vector2 normalized() const noexcept { const Type len = length(); return len > 0.f ? *this / len : *this; } #endif - [[nodiscard]] constexpr Type length_sqr() const + [[nodiscard]] constexpr Type length_sqr() const noexcept { return x * x + y * y; } - constexpr Vector2& abs() + constexpr Vector2& abs() noexcept { // FIXME: Replace with std::abs, if it will become constexprable x = x < 0 ? -x : x; @@ -158,47 +158,47 @@ namespace omath return *this; } - [[nodiscard]] constexpr Vector2 operator-() const + [[nodiscard]] constexpr Vector2 operator-() const noexcept { return {-x, -y}; } // Binary arithmetic operators - [[nodiscard]] constexpr Vector2 operator+(const Vector2& v) const + [[nodiscard]] constexpr Vector2 operator+(const Vector2& v) const noexcept { return {x + v.x, y + v.y}; } - [[nodiscard]] constexpr Vector2 operator-(const Vector2& v) const + [[nodiscard]] constexpr Vector2 operator-(const Vector2& v) const noexcept { return {x - v.x, y - v.y}; } - [[nodiscard]] constexpr Vector2 operator*(const float fl) const + [[nodiscard]] constexpr Vector2 operator*(const float fl) const noexcept { return {x * fl, y * fl}; } - [[nodiscard]] constexpr Vector2 operator/(const float fl) const + [[nodiscard]] constexpr Vector2 operator/(const float fl) const noexcept { return {x / fl, y / fl}; } // Sum of elements - [[nodiscard]] constexpr Type sum() const + [[nodiscard]] constexpr Type sum() const noexcept { return x + y; } [[nodiscard]] - constexpr std::tuple as_tuple() const + constexpr std::tuple as_tuple() const noexcept { return std::make_tuple(x, y); } #ifdef OMATH_IMGUI_INTEGRATION [[nodiscard]] - ImVec2 to_im_vec2() const + ImVec2 to_im_vec2() const noexcept { return {static_cast(this->x), static_cast(this->y)}; } diff --git a/include/omath/vector3.hpp b/include/omath/vector3.hpp index d5351686..fc302978 100644 --- a/include/omath/vector3.hpp +++ b/include/omath/vector3.hpp @@ -24,22 +24,22 @@ namespace omath { public: Type z = static_cast(0); - constexpr Vector3(const Type& x, const Type& y, const Type& z): Vector2(x, y), z(z) + constexpr Vector3(const Type& x, const Type& y, const Type& z) noexcept: Vector2(x, y), z(z) { } - constexpr Vector3(): Vector2() {}; + constexpr Vector3() noexcept: Vector2() {}; - [[nodiscard]] constexpr bool operator==(const Vector3& src) const + [[nodiscard]] constexpr bool operator==(const Vector3& src) const noexcept { return Vector2::operator==(src) && (src.z == z); } - [[nodiscard]] constexpr bool operator!=(const Vector3& src) const + [[nodiscard]] constexpr bool operator!=(const Vector3& src) const noexcept { return !(*this == src); } - constexpr Vector3& operator+=(const Vector3& v) + constexpr Vector3& operator+=(const Vector3& v) noexcept { Vector2::operator+=(v); z += v.z; @@ -47,7 +47,7 @@ namespace omath return *this; } - constexpr Vector3& operator-=(const Vector3& v) + constexpr Vector3& operator-=(const Vector3& v) noexcept { Vector2::operator-=(v); z -= v.z; @@ -55,7 +55,7 @@ namespace omath return *this; } - constexpr Vector3& operator*=(const float fl) + constexpr Vector3& operator*=(const float fl) noexcept { Vector2::operator*=(fl); z *= fl; @@ -63,7 +63,7 @@ namespace omath return *this; } - constexpr Vector3& operator*=(const Vector3& v) + constexpr Vector3& operator*=(const Vector3& v) noexcept { Vector2::operator*=(v); z *= v.z; @@ -71,7 +71,7 @@ namespace omath return *this; } - constexpr Vector3& operator/=(const Vector3& v) + constexpr Vector3& operator/=(const Vector3& v) noexcept { Vector2::operator/=(v); z /= v.z; @@ -79,7 +79,7 @@ namespace omath return *this; } - constexpr Vector3& operator+=(const float fl) + constexpr Vector3& operator+=(const float fl) noexcept { Vector2::operator+=(fl); z += fl; @@ -87,7 +87,7 @@ namespace omath return *this; } - constexpr Vector3& operator/=(const float fl) + constexpr Vector3& operator/=(const float fl) noexcept { Vector2::operator/=(fl); z /= fl; @@ -95,7 +95,7 @@ namespace omath return *this; } - constexpr Vector3& operator-=(const float fl) + constexpr Vector3& operator-=(const float fl) noexcept { Vector2::operator-=(fl); z -= fl; @@ -103,7 +103,7 @@ namespace omath return *this; } - constexpr Vector3& abs() + constexpr Vector3& abs() noexcept { Vector2::abs(); z = z < 0.f ? -z : z; @@ -111,12 +111,12 @@ namespace omath return *this; } - [[nodiscard]] constexpr Type distance_to_sqr(const Vector3& other) const + [[nodiscard]] constexpr Type distance_to_sqr(const Vector3& other) const noexcept { return (*this - other).length_sqr(); } - [[nodiscard]] constexpr Type dot(const Vector3& other) const + [[nodiscard]] constexpr Type dot(const Vector3& other) const noexcept { return Vector2::dot(other) + z * other.z; } @@ -142,80 +142,80 @@ namespace omath return length_value != 0 ? *this / length_value : *this; } #else - [[nodiscard]] Type length() const + [[nodiscard]] Type length() const noexcept { return std::hypot(this->x, this->y, z); } - [[nodiscard]] Vector3 normalized() const + [[nodiscard]] Vector3 normalized() const noexcept { const Type len = this->length(); return len != 0 ? *this / len : *this; } - [[nodiscard]] Type length_2d() const + [[nodiscard]] Type length_2d() const noexcept { return Vector2::length(); } - [[nodiscard]] Type distance_to(const Vector3& vOther) const + [[nodiscard]] Type distance_to(const Vector3& vOther) const noexcept { return (*this - vOther).length(); } #endif - [[nodiscard]] constexpr Type length_sqr() const + [[nodiscard]] constexpr Type length_sqr() const noexcept { return Vector2::length_sqr() + z * z; } - [[nodiscard]] constexpr Vector3 operator-() const + [[nodiscard]] constexpr Vector3 operator-() const noexcept { return {-this->x, -this->y, -z}; } - [[nodiscard]] constexpr Vector3 operator+(const Vector3& v) const + [[nodiscard]] constexpr Vector3 operator+(const Vector3& v) const noexcept { return {this->x + v.x, this->y + v.y, z + v.z}; } - [[nodiscard]] constexpr Vector3 operator-(const Vector3& v) const + [[nodiscard]] constexpr Vector3 operator-(const Vector3& v) const noexcept { return {this->x - v.x, this->y - v.y, z - v.z}; } - [[nodiscard]] constexpr Vector3 operator*(const float fl) const + [[nodiscard]] constexpr Vector3 operator*(const float fl) const noexcept { return {this->x * fl, this->y * fl, z * fl}; } - [[nodiscard]] constexpr Vector3 operator*(const Vector3& v) const + [[nodiscard]] constexpr Vector3 operator*(const Vector3& v) const noexcept { return {this->x * v.x, this->y * v.y, z * v.z}; } - [[nodiscard]] constexpr Vector3 operator/(const float fl) const + [[nodiscard]] constexpr Vector3 operator/(const float fl) const noexcept { return {this->x / fl, this->y / fl, z / fl}; } - [[nodiscard]] constexpr Vector3 operator/(const Vector3& v) const + [[nodiscard]] constexpr Vector3 operator/(const Vector3& v) const noexcept { return {this->x / v.x, this->y / v.y, z / v.z}; } - [[nodiscard]] constexpr Vector3 cross(const Vector3& v) const + [[nodiscard]] constexpr Vector3 cross(const Vector3& v) const noexcept { return {this->y * v.z - z * v.y, z * v.x - this->x * v.z, this->x * v.y - this->y * v.x}; } - [[nodiscard]] constexpr Type sum() const + [[nodiscard]] constexpr Type sum() const noexcept { return sum_2d() + z; } [[nodiscard]] std::expected, Vector3Error> - angle_between(const Vector3& other) const + angle_between(const Vector3& other) const noexcept { const auto bottom = length() * other.length(); @@ -225,7 +225,7 @@ namespace omath return Angle::from_radians(std::acos(dot(other) / bottom)); } - [[nodiscard]] bool is_perpendicular(const Vector3& other) const + [[nodiscard]] bool is_perpendicular(const Vector3& other) const noexcept { if (const auto angle = angle_between(other)) return angle->as_degrees() == 90.f; @@ -233,17 +233,17 @@ namespace omath return false; } - [[nodiscard]] constexpr Type sum_2d() const + [[nodiscard]] constexpr Type sum_2d() const noexcept { return Vector2::sum(); } - [[nodiscard]] constexpr std::tuple as_tuple() const + [[nodiscard]] constexpr std::tuple as_tuple() const noexcept { return std::make_tuple(this->x, this->y, z); } - [[nodiscard]] Vector3 view_angle_to(const Vector3& other) const + [[nodiscard]] Vector3 view_angle_to(const Vector3& other) const noexcept { const float distance = distance_to(other); const auto delta = other - *this; diff --git a/include/omath/vector4.hpp b/include/omath/vector4.hpp index 36e0c0a3..2f699897 100644 --- a/include/omath/vector4.hpp +++ b/include/omath/vector4.hpp @@ -17,21 +17,21 @@ namespace omath constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w): Vector3(x, y, z), w(w) { } - constexpr Vector4(): Vector3(), w(0) {}; + constexpr Vector4() noexcept : Vector3(), w(0) {}; [[nodiscard]] - constexpr bool operator==(const Vector4& src) const + constexpr bool operator==(const Vector4& src) const noexcept { return Vector3::operator==(src) && w == src.w; } [[nodiscard]] - constexpr bool operator!=(const Vector4& src) const + constexpr bool operator!=(const Vector4& src) const noexcept { return !(*this == src); } - constexpr Vector4& operator+=(const Vector4& v) + constexpr Vector4& operator+=(const Vector4& v) noexcept { Vector3::operator+=(v); w += v.w; @@ -39,7 +39,7 @@ namespace omath return *this; } - constexpr Vector4& operator-=(const Vector4& v) + constexpr Vector4& operator-=(const Vector4& v) noexcept { Vector3::operator-=(v); w -= v.w; @@ -47,7 +47,7 @@ namespace omath return *this; } - constexpr Vector4& operator*=(const float scalar) + constexpr Vector4& operator*=(const float scalar) noexcept { Vector3::operator*=(scalar); w *= scalar; @@ -55,7 +55,7 @@ namespace omath return *this; } - constexpr Vector4& operator*=(const Vector4& v) + constexpr Vector4& operator*=(const Vector4& v) noexcept { Vector3::operator*=(v); w *= v.w; @@ -63,7 +63,7 @@ namespace omath return *this; } - constexpr Vector4& operator/=(const float scalar) + constexpr Vector4& operator/=(const float scalar) noexcept { Vector3::operator/=(scalar); w /= scalar; @@ -71,36 +71,36 @@ namespace omath return *this; } - constexpr Vector4& operator/=(const Vector4& v) + constexpr Vector4& operator/=(const Vector4& v) noexcept { Vector3::operator/=(v); w /= v.w; return *this; } - [[nodiscard]] constexpr Type length_sqr() const + [[nodiscard]] constexpr Type length_sqr() const noexcept { return Vector3::length_sqr() + w * w; } - [[nodiscard]] constexpr Type dot(const Vector4& other) const + [[nodiscard]] constexpr Type dot(const Vector4& other) const noexcept { return Vector3::dot(other) + w * other.w; } - [[nodiscard]] Vector3 length() const + [[nodiscard]] Vector3 length() const noexcept { return std::sqrt(length_sqr()); } - constexpr Vector4& abs() + constexpr Vector4& abs() noexcept { Vector3::abs(); w = w < 0.f ? -w : w; return *this; } - constexpr Vector4& clamp(const Type& min, const Type& max) + constexpr Vector4& clamp(const Type& min, const Type& max) noexcept { this->x = std::clamp(this->x, min, max); this->y = std::clamp(this->y, min, max); @@ -110,56 +110,56 @@ namespace omath } [[nodiscard]] - constexpr Vector4 operator-() const + constexpr Vector4 operator-() const noexcept { return {-this->x, -this->y, -this->z, -w}; } [[nodiscard]] - constexpr Vector4 operator+(const Vector4& v) const + constexpr Vector4 operator+(const Vector4& v) const noexcept { return {this->x + v.x, this->y + v.y, this->z + v.z, w + v.w}; } [[nodiscard]] - constexpr Vector4 operator-(const Vector4& v) const + constexpr Vector4 operator-(const Vector4& v) const noexcept { return {this->x - v.x, this->y - v.y, this->z - v.z, w - v.w}; } [[nodiscard]] - constexpr Vector4 operator*(const Type& scalar) const + constexpr Vector4 operator*(const Type& scalar) const noexcept { return {this->x * scalar, this->y * scalar, this->z * scalar, w * scalar}; } [[nodiscard]] - constexpr Vector4 operator*(const Vector4& v) const + constexpr Vector4 operator*(const Vector4& v) const noexcept { return {this->x * v.x, this->y * v.y, this->z * v.z, w * v.w}; } [[nodiscard]] - constexpr Vector4 operator/(const Type& scalar) const + constexpr Vector4 operator/(const Type& scalar) const noexcept { return {this->x / scalar, this->y / scalar, this->z / scalar, w / scalar}; } [[nodiscard]] - constexpr Vector4 operator/(const Vector4& v) const + constexpr Vector4 operator/(const Vector4& v) const noexcept { return {this->x / v.x, this->y / v.y, this->z / v.z, w / v.w}; } [[nodiscard]] - constexpr Type sum() const + constexpr Type sum() const noexcept { return Vector3::sum() + w; } #ifdef OMATH_IMGUI_INTEGRATION [[nodiscard]] - ImVec4 to_im_vec4() const + ImVec4 to_im_vec4() const noexcept { return { static_cast(this->x), From 6749f9f759cf908d835d5556d0c2afb555660b9a Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 4 May 2025 19:16:49 +0300 Subject: [PATCH 294/795] added noexcept for color and angles --- include/omath/angle.hpp | 36 ++++++++++++++++++------------------ include/omath/angles.hpp | 14 +++++++------- include/omath/color.hpp | 22 +++++++++++----------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/include/omath/angle.hpp b/include/omath/angle.hpp index e2949a32..e7d08795 100644 --- a/include/omath/angle.hpp +++ b/include/omath/angle.hpp @@ -20,7 +20,7 @@ namespace omath class Angle { Type m_angle; - constexpr explicit Angle(const Type& degrees) + constexpr explicit Angle(const Type& degrees) noexcept { if constexpr (flags == AngleFlags::Normalized) m_angle = angles::wrap_angle(degrees, min, max); @@ -36,68 +36,68 @@ namespace omath public: [[nodiscard]] - constexpr static Angle from_degrees(const Type& degrees) + constexpr static Angle from_degrees(const Type& degrees) noexcept { return Angle{degrees}; } - constexpr Angle(): m_angle(0) + constexpr Angle() noexcept: m_angle(0) { } [[nodiscard]] - constexpr static Angle from_radians(const Type& degrees) + constexpr static Angle from_radians(const Type& degrees) noexcept { return Angle{angles::radians_to_degrees(degrees)}; } [[nodiscard]] - constexpr const Type& operator*() const + constexpr const Type& operator*() const noexcept { return m_angle; } [[nodiscard]] - constexpr Type as_degrees() const + constexpr Type as_degrees() const noexcept { return m_angle; } [[nodiscard]] - constexpr Type as_radians() const + constexpr Type as_radians() const noexcept { return angles::degrees_to_radians(m_angle); } [[nodiscard]] - Type sin() const + Type sin() const noexcept { return std::sin(as_radians()); } [[nodiscard]] - Type cos() const + Type cos() const noexcept { return std::cos(as_radians()); } [[nodiscard]] - Type tan() const + Type tan() const noexcept { return std::tan(as_radians()); } [[nodiscard]] - Type atan() const + Type atan() const noexcept { return std::atan(as_radians()); } [[nodiscard]] - Type cot() const + Type cot() const noexcept { return cos() / sin(); } - constexpr Angle& operator+=(const Angle& other) + constexpr Angle& operator+=(const Angle& other) noexcept { if constexpr (flags == AngleFlags::Normalized) m_angle = angles::wrap_angle(m_angle + other.m_angle, min, max); @@ -114,15 +114,15 @@ namespace omath } [[nodiscard]] - constexpr std::partial_ordering operator<=>(const Angle& other) const = default; + constexpr std::partial_ordering operator<=>(const Angle& other) const noexcept = default; - constexpr Angle& operator-=(const Angle& other) + constexpr Angle& operator-=(const Angle& other) noexcept { return operator+=(-other); } [[nodiscard]] - constexpr Angle& operator+(const Angle& other) + constexpr Angle& operator+(const Angle& other) noexcept { if constexpr (flags == AngleFlags::Normalized) return {angles::wrap_angle(m_angle + other.m_angle, min, max)}; @@ -137,13 +137,13 @@ namespace omath } [[nodiscard]] - constexpr Angle& operator-(const Angle& other) + constexpr Angle& operator-(const Angle& other) noexcept { return operator+(-other); } [[nodiscard]] - constexpr Angle operator-() const + constexpr Angle operator-() const noexcept { return Angle{-m_angle}; } diff --git a/include/omath/angles.hpp b/include/omath/angles.hpp index cafacbb0..ce07dd83 100644 --- a/include/omath/angles.hpp +++ b/include/omath/angles.hpp @@ -10,21 +10,21 @@ namespace omath::angles { template requires std::is_floating_point_v - [[nodiscard]] constexpr Type radians_to_degrees(const Type& radians) + [[nodiscard]] constexpr Type radians_to_degrees(const Type& radians) noexcept { return radians * (static_cast(180) / std::numbers::pi_v); } template requires std::is_floating_point_v - [[nodiscard]] constexpr Type degrees_to_radians(const Type& degrees) + [[nodiscard]] constexpr Type degrees_to_radians(const Type& degrees) noexcept { return degrees * (std::numbers::pi_v / static_cast(180)); } template requires std::is_floating_point_v - [[nodiscard]] Type horizontal_fov_to_vertical(const Type& horizontal_fov, const Type& aspect) + [[nodiscard]] Type horizontal_fov_to_vertical(const Type& horizontal_fov, const Type& aspect) noexcept { const auto fov_rad = degrees_to_radians(horizontal_fov); @@ -35,19 +35,19 @@ namespace omath::angles template requires std::is_floating_point_v - [[nodiscard]] Type vertical_fov_to_horizontal(const Type& vertical_fov, const Type& aspect) + [[nodiscard]] Type vertical_fov_to_horizontal(const Type& vertical_fov, const Type& aspect) noexcept { const auto fov_as_radians = degrees_to_radians(vertical_fov); - const auto horizontal_fov - = static_cast(2) * std::atan(std::tan(fov_as_radians / static_cast(2)) * aspect); + const auto horizontal_fov = + static_cast(2) * std::atan(std::tan(fov_as_radians / static_cast(2)) * aspect); return radians_to_degrees(horizontal_fov); } template requires std::is_arithmetic_v - [[nodiscard]] Type wrap_angle(const Type& angle, const Type& min, const Type& max) + [[nodiscard]] Type wrap_angle(const Type& angle, const Type& min, const Type& max) noexcept { if (angle <= max && angle >= min) return angle; diff --git a/include/omath/color.hpp b/include/omath/color.hpp index e460059c..3513e74a 100644 --- a/include/omath/color.hpp +++ b/include/omath/color.hpp @@ -28,20 +28,20 @@ namespace omath class Color final : public Vector4 { public: - constexpr Color(const float r, const float g, const float b, const float a): Vector4(r, g, b, a) + constexpr Color(const float r, const float g, const float b, const float a) noexcept: Vector4(r, g, b, a) { clamp(0.f, 1.f); } - constexpr explicit Color() = default; + constexpr explicit Color() noexcept = default; [[nodiscard]] - constexpr static Color from_rgba(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) + constexpr static Color from_rgba(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) noexcept { return Color{Vector4(r, g, b, a) / 255.f}; } [[nodiscard]] - constexpr static Color from_hsv(float hue, const float saturation, const float value) + constexpr static Color from_hsv(float hue, const float saturation, const float value) noexcept { float r{}, g{}, b{}; @@ -82,13 +82,13 @@ namespace omath } [[nodiscard]] - constexpr static Color from_hsv(const Hsv& hsv) + constexpr static Color from_hsv(const Hsv& hsv) noexcept { return from_hsv(hsv.hue, hsv.saturation, hsv.value); } [[nodiscard]] - constexpr Hsv to_hsv() const + constexpr Hsv to_hsv() const noexcept { Hsv hsv_data; @@ -120,11 +120,11 @@ namespace omath return hsv_data; } - constexpr explicit Color(const Vector4& vec): Vector4(vec) + constexpr explicit Color(const Vector4& vec) noexcept: Vector4(vec) { clamp(0.f, 1.f); } - constexpr void set_hue(const float hue) + constexpr void set_hue(const float hue) noexcept { auto hsv = to_hsv(); hsv.hue = hue; @@ -132,7 +132,7 @@ namespace omath *this = from_hsv(hsv); } - constexpr void set_saturation(const float saturation) + constexpr void set_saturation(const float saturation) noexcept { auto hsv = to_hsv(); hsv.saturation = saturation; @@ -140,7 +140,7 @@ namespace omath *this = from_hsv(hsv); } - constexpr void set_value(const float value) + constexpr void set_value(const float value) noexcept { auto hsv = to_hsv(); hsv.value = value; @@ -148,7 +148,7 @@ namespace omath *this = from_hsv(hsv); } [[nodiscard]] - constexpr Color blend(const Color& other, float ratio) const + constexpr Color blend(const Color& other, float ratio) const noexcept { ratio = std::clamp(ratio, 0.f, 1.f); return Color(*this * (1.f - ratio) + other * ratio); From a6e4c0461d72fa45be296be16729eda55e7f6e5d Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 5 May 2025 01:16:12 +0300 Subject: [PATCH 295/795] added noexcept --- include/omath/projection/camera.hpp | 39 +++++++++++++++-------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index 93f956a9..be9a9f83 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -32,7 +32,7 @@ namespace omath::projection public: virtual ~Camera() = default; Camera(const Vector3& position, const ViewAnglesType& view_angles, const ViewPort& view_port, - const FieldOfView& fov, const float near, const float far) + const FieldOfView& fov, const float near, const float far) noexcept : m_view_port(view_port), m_field_of_view(fov), m_far_plane_distance(far), m_near_plane_distance(near), m_view_angles(view_angles), m_origin(position) { @@ -45,13 +45,13 @@ namespace omath::projection [[nodiscard]] virtual Mat4X4Type calc_projection_matrix() const = 0; - [[nodiscard]] Mat4X4Type calc_view_projection_matrix() const + [[nodiscard]] Mat4X4Type calc_view_projection_matrix() const noexcept { return calc_projection_matrix() * calc_view_matrix(); } public: - [[nodiscard]] const Mat4X4Type& get_view_projection_matrix() const + [[nodiscard]] const Mat4X4Type& get_view_projection_matrix() const noexcept { if (!m_view_projection_matrix.has_value()) m_view_projection_matrix = calc_view_projection_matrix(); @@ -59,68 +59,69 @@ namespace omath::projection return m_view_projection_matrix.value(); } - void set_field_of_view(const FieldOfView& fov) + void set_field_of_view(const FieldOfView& fov) noexcept { m_field_of_view = fov; m_view_projection_matrix = std::nullopt; } - void set_near_plane(const float near) + void set_near_plane(const float near) noexcept { m_near_plane_distance = near; m_view_projection_matrix = std::nullopt; } - void set_far_plane(const float far) + void set_far_plane(const float far) noexcept { m_far_plane_distance = far; m_view_projection_matrix = std::nullopt; } - void set_view_angles(const ViewAnglesType& view_angles) + void set_view_angles(const ViewAnglesType& view_angles) noexcept { m_view_angles = view_angles; m_view_projection_matrix = std::nullopt; } - void set_origin(const Vector3& origin) + void set_origin(const Vector3& origin) noexcept { m_origin = origin; m_view_projection_matrix = std::nullopt; } - void set_view_port(const ViewPort& view_port) + void set_view_port(const ViewPort& view_port) noexcept { m_view_port = view_port; m_view_projection_matrix = std::nullopt; } - [[nodiscard]] const FieldOfView& get_field_of_view() const + [[nodiscard]] const FieldOfView& get_field_of_view() const noexcept { return m_field_of_view; } - [[nodiscard]] const float& get_near_plane() const + [[nodiscard]] const float& get_near_plane() const noexcept { return m_near_plane_distance; } - [[nodiscard]] const float& get_far_plane() const + [[nodiscard]] const float& get_far_plane() const noexcept { return m_far_plane_distance; } - [[nodiscard]] const ViewAnglesType& get_view_angles() const + [[nodiscard]] const ViewAnglesType& get_view_angles() const noexcept { return m_view_angles; } - [[nodiscard]] const Vector3& get_origin() const + [[nodiscard]] const Vector3& get_origin() const noexcept { return m_origin; } - [[nodiscard]] std::expected, Error> world_to_screen(const Vector3& world_position) const + [[nodiscard]] std::expected, Error> + world_to_screen(const Vector3& world_position) const noexcept { auto normalized_cords = world_to_view_port(world_position); @@ -131,7 +132,7 @@ namespace omath::projection } [[nodiscard]] std::expected, Error> - world_to_view_port(const Vector3& world_position) const + world_to_view_port(const Vector3& world_position) const noexcept { auto projected = get_view_projection_matrix() * mat_column_from_vector(world_position); @@ -160,13 +161,13 @@ namespace omath::projection Vector3 m_origin; private: - template [[nodiscard]] - constexpr static bool is_ndc_out_of_bounds(const Type& ndc) + template + [[nodiscard]] constexpr static bool is_ndc_out_of_bounds(const Type& ndc) noexcept { return std::ranges::any_of(ndc.raw_array(), [](const auto& val) { return val < -1 || val > 1; }); } - [[nodiscard]] Vector3 ndc_to_screen_position(const Vector3& ndc) const + [[nodiscard]] Vector3 ndc_to_screen_position(const Vector3& ndc) const noexcept { return {(ndc.x + 1.f) / 2.f * m_view_port.m_width, (1.f - ndc.y) / 2.f * m_view_port.m_height, ndc.z}; } From 50ddf2d31e9a285e1c3f61152c4d76459a14ef43 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 5 May 2025 01:46:50 +0300 Subject: [PATCH 296/795] added more noexcept --- include/omath/3d_primitives/box.hpp | 2 +- include/omath/collision/line_tracer.hpp | 8 ++++---- include/omath/engines/iw_engine/camera.hpp | 4 ++-- include/omath/engines/opengl_engine/camera.hpp | 4 ++-- include/omath/engines/opengl_engine/formulas.hpp | 12 ++++++------ include/omath/engines/source_engine/camera.hpp | 4 ++-- include/omath/engines/source_engine/formulas.hpp | 12 ++++++------ include/omath/engines/unity_engine/camera.hpp | 4 ++-- include/omath/engines/unity_engine/formulas.hpp | 12 ++++++------ .../projectile_prediction/proj_pred_engine_avx2.hpp | 6 +++--- .../proj_pred_engine_legacy.hpp | 7 ++++--- include/omath/projectile_prediction/projectile.hpp | 2 +- include/omath/projectile_prediction/target.hpp | 2 +- include/omath/projection/camera.hpp | 4 ++-- source/3d_primitives/box.cpp | 2 +- source/collision/line_tracer.cpp | 8 ++++---- source/engines/iw_engine/camera.cpp | 4 ++-- source/engines/opengl_engine/camera.cpp | 4 ++-- source/engines/opengl_engine/formulas.cpp | 12 ++++++------ source/engines/source_engine/camera.cpp | 4 ++-- source/engines/source_engine/formulas.cpp | 12 ++++++------ source/engines/unity_engine/camera.cpp | 4 ++-- source/engines/unity_engine/formulas.cpp | 12 ++++++------ .../proj_pred_engine_legacy.cpp | 11 +++++------ source/projectile_prediction/projectile.cpp | 2 +- 25 files changed, 79 insertions(+), 79 deletions(-) diff --git a/include/omath/3d_primitives/box.hpp b/include/omath/3d_primitives/box.hpp index 23a59671..49cc4751 100644 --- a/include/omath/3d_primitives/box.hpp +++ b/include/omath/3d_primitives/box.hpp @@ -13,5 +13,5 @@ namespace omath::primitives [[nodiscard]] std::array>, 12> create_box(const Vector3& top, const Vector3& bottom, const Vector3& dir_forward, const Vector3& dir_right, - float ratio = 4.f); + float ratio = 4.f) noexcept; } diff --git a/include/omath/collision/line_tracer.hpp b/include/omath/collision/line_tracer.hpp index 6a655b0d..a79a3cf5 100644 --- a/include/omath/collision/line_tracer.hpp +++ b/include/omath/collision/line_tracer.hpp @@ -16,10 +16,10 @@ namespace omath::collision bool infinite_length = false; [[nodiscard]] - Vector3 direction_vector() const; + Vector3 direction_vector() const noexcept; [[nodiscard]] - Vector3 direction_vector_normalized() const; + Vector3 direction_vector_normalized() const noexcept; }; class LineTracer { @@ -27,11 +27,11 @@ namespace omath::collision LineTracer() = delete; [[nodiscard]] - static bool can_trace_line(const Ray& ray, const Triangle>& triangle); + static bool can_trace_line(const Ray& ray, const Triangle>& triangle) noexcept; // Realization of Möller–Trumbore intersection algorithm // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm [[nodiscard]] - static Vector3 get_ray_hit_point(const Ray& ray, const Triangle>& triangle); + static Vector3 get_ray_hit_point(const Ray& ray, const Triangle>& triangle) noexcept; }; } // namespace omath::collision diff --git a/include/omath/engines/iw_engine/camera.hpp b/include/omath/engines/iw_engine/camera.hpp index ea59474e..2f530e18 100644 --- a/include/omath/engines/iw_engine/camera.hpp +++ b/include/omath/engines/iw_engine/camera.hpp @@ -16,7 +16,7 @@ namespace omath::iw_engine void look_at(const Vector3& target) override; protected: - [[nodiscard]] Mat4X4 calc_view_matrix() const override; - [[nodiscard]] Mat4X4 calc_projection_matrix() const override; + [[nodiscard]] Mat4X4 calc_view_matrix() const noexcept override; + [[nodiscard]] Mat4X4 calc_projection_matrix() const noexcept override; }; } // namespace omath::iw_engine \ No newline at end of file diff --git a/include/omath/engines/opengl_engine/camera.hpp b/include/omath/engines/opengl_engine/camera.hpp index 605ee449..b4cdd210 100644 --- a/include/omath/engines/opengl_engine/camera.hpp +++ b/include/omath/engines/opengl_engine/camera.hpp @@ -13,7 +13,7 @@ namespace omath::opengl_engine Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, const Angle& fov, float near, float far); void look_at(const Vector3& target) override; - [[nodiscard]] Mat4X4 calc_view_matrix() const override; - [[nodiscard]] Mat4X4 calc_projection_matrix() const override; + [[nodiscard]] Mat4X4 calc_view_matrix() const noexcept override; + [[nodiscard]] Mat4X4 calc_projection_matrix() const noexcept override; }; } // namespace omath::opengl_engine \ No newline at end of file diff --git a/include/omath/engines/opengl_engine/formulas.hpp b/include/omath/engines/opengl_engine/formulas.hpp index dc6ae64b..31714f56 100644 --- a/include/omath/engines/opengl_engine/formulas.hpp +++ b/include/omath/engines/opengl_engine/formulas.hpp @@ -8,19 +8,19 @@ namespace omath::opengl_engine { [[nodiscard]] - Vector3 forward_vector(const ViewAngles& angles); + Vector3 forward_vector(const ViewAngles& angles) noexcept; [[nodiscard]] - Vector3 right_vector(const ViewAngles& angles); + Vector3 right_vector(const ViewAngles& angles) noexcept; [[nodiscard]] - Vector3 up_vector(const ViewAngles& angles); + Vector3 up_vector(const ViewAngles& angles) noexcept; - [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin); + [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept; [[nodiscard]] - Mat4X4 rotation_matrix(const ViewAngles& angles); + Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; [[nodiscard]] - Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far); + Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; } // namespace omath::opengl_engine diff --git a/include/omath/engines/source_engine/camera.hpp b/include/omath/engines/source_engine/camera.hpp index 0ea11121..cba5c434 100644 --- a/include/omath/engines/source_engine/camera.hpp +++ b/include/omath/engines/source_engine/camera.hpp @@ -15,7 +15,7 @@ namespace omath::source_engine void look_at(const Vector3& target) override; protected: - [[nodiscard]] Mat4X4 calc_view_matrix() const override; - [[nodiscard]] Mat4X4 calc_projection_matrix() const override; + [[nodiscard]] Mat4X4 calc_view_matrix() const noexcept override; + [[nodiscard]] Mat4X4 calc_projection_matrix() const noexcept override; }; } // namespace omath::source_engine \ No newline at end of file diff --git a/include/omath/engines/source_engine/formulas.hpp b/include/omath/engines/source_engine/formulas.hpp index 2b651e02..a51a6333 100644 --- a/include/omath/engines/source_engine/formulas.hpp +++ b/include/omath/engines/source_engine/formulas.hpp @@ -7,19 +7,19 @@ namespace omath::source_engine { [[nodiscard]] - Vector3 forward_vector(const ViewAngles& angles); + Vector3 forward_vector(const ViewAngles& angles) noexcept; [[nodiscard]] - Mat4X4 rotation_matrix(const ViewAngles& angles); + Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; [[nodiscard]] - Vector3 right_vector(const ViewAngles& angles); + Vector3 right_vector(const ViewAngles& angles) noexcept; [[nodiscard]] - Vector3 up_vector(const ViewAngles& angles); + Vector3 up_vector(const ViewAngles& angles) noexcept; - [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin); + [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept; [[nodiscard]] - Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far); + Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; } // namespace omath::source_engine diff --git a/include/omath/engines/unity_engine/camera.hpp b/include/omath/engines/unity_engine/camera.hpp index 7a96757a..568b2a75 100644 --- a/include/omath/engines/unity_engine/camera.hpp +++ b/include/omath/engines/unity_engine/camera.hpp @@ -16,7 +16,7 @@ namespace omath::unity_engine void look_at(const Vector3& target) override; protected: - [[nodiscard]] Mat4X4 calc_view_matrix() const override; - [[nodiscard]] Mat4X4 calc_projection_matrix() const override; + [[nodiscard]] Mat4X4 calc_view_matrix() const noexcept override; + [[nodiscard]] Mat4X4 calc_projection_matrix() const noexcept override; }; } // namespace omath::unity_engine \ No newline at end of file diff --git a/include/omath/engines/unity_engine/formulas.hpp b/include/omath/engines/unity_engine/formulas.hpp index db691d5d..be071367 100644 --- a/include/omath/engines/unity_engine/formulas.hpp +++ b/include/omath/engines/unity_engine/formulas.hpp @@ -8,19 +8,19 @@ namespace omath::unity_engine { [[nodiscard]] - Vector3 forward_vector(const ViewAngles& angles); + Vector3 forward_vector(const ViewAngles& angles) noexcept; [[nodiscard]] - Vector3 right_vector(const ViewAngles& angles); + Vector3 right_vector(const ViewAngles& angles) noexcept; [[nodiscard]] - Vector3 up_vector(const ViewAngles& angles); + Vector3 up_vector(const ViewAngles& angles) noexcept; - [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin); + [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept; [[nodiscard]] - Mat4X4 rotation_matrix(const ViewAngles& angles); + Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; [[nodiscard]] - Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far); + Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; } // namespace omath::unity_engine diff --git a/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp b/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp index 85e3fc52..59d0592b 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp @@ -9,8 +9,8 @@ namespace omath::projectile_prediction class ProjPredEngineAvx2 final : public ProjPredEngine { public: - [[nodiscard]] std::optional> maybe_calculate_aim_point(const Projectile& projectile, - const Target& target) const override; + [[nodiscard]] std::optional> + maybe_calculate_aim_point(const Projectile& projectile, const Target& target) const override; ProjPredEngineAvx2(float gravity_constant, float simulation_time_step, float maximum_simulation_time); ~ProjPredEngineAvx2() override = default; @@ -18,7 +18,7 @@ namespace omath::projectile_prediction private: [[nodiscard]] static std::optional calculate_pitch(const Vector3& proj_origin, const Vector3& target_pos, - float bullet_gravity, float v0, float time); + float bullet_gravity, float v0, float time) ; // We use [[maybe_unused]] here since AVX2 is not available for ARM and ARM64 CPU [[maybe_unused]] const float m_gravity_constant; diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index caa5cf5f..44eec147 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -29,11 +29,12 @@ namespace omath::projectile_prediction const float m_distance_tolerance; [[nodiscard]] - std::optional maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile, - const Vector3& target_position) const; + std::optional + maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile, + const Vector3& target_position) const noexcept; [[nodiscard]] bool is_projectile_reached_target(const Vector3& target_position, const Projectile& projectile, - float pitch, float time) const; + float pitch, float time) const noexcept; }; } // namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/projectile.hpp b/include/omath/projectile_prediction/projectile.hpp index d1188c5c..ee0cafd7 100644 --- a/include/omath/projectile_prediction/projectile.hpp +++ b/include/omath/projectile_prediction/projectile.hpp @@ -11,7 +11,7 @@ namespace omath::projectile_prediction { public: [[nodiscard]] - Vector3 predict_position(float pitch, float yaw, float time, float gravity) const; + Vector3 predict_position(float pitch, float yaw, float time, float gravity) const noexcept; Vector3 m_origin; float m_launch_speed{}; diff --git a/include/omath/projectile_prediction/target.hpp b/include/omath/projectile_prediction/target.hpp index 74707177..4dd38b01 100644 --- a/include/omath/projectile_prediction/target.hpp +++ b/include/omath/projectile_prediction/target.hpp @@ -11,7 +11,7 @@ namespace omath::projectile_prediction { public: [[nodiscard]] - constexpr Vector3 predict_position(const float time, const float gravity) const + constexpr Vector3 predict_position(const float time, const float gravity) const noexcept { auto predicted = m_origin + m_velocity * time; diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index be9a9f83..cf93a78f 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -41,9 +41,9 @@ namespace omath::projection protected: virtual void look_at(const Vector3& target) = 0; - [[nodiscard]] virtual Mat4X4Type calc_view_matrix() const = 0; + [[nodiscard]] virtual Mat4X4Type calc_view_matrix() const noexcept = 0; - [[nodiscard]] virtual Mat4X4Type calc_projection_matrix() const = 0; + [[nodiscard]] virtual Mat4X4Type calc_projection_matrix() const noexcept = 0; [[nodiscard]] Mat4X4Type calc_view_projection_matrix() const noexcept { diff --git a/source/3d_primitives/box.cpp b/source/3d_primitives/box.cpp index 81da0bb6..9c251b98 100644 --- a/source/3d_primitives/box.cpp +++ b/source/3d_primitives/box.cpp @@ -7,7 +7,7 @@ namespace omath::primitives { std::array>, 12> create_box(const Vector3& top, const Vector3& bottom, const Vector3& dir_forward, - const Vector3& dir_right, const float ratio) + const Vector3& dir_right, const float ratio) noexcept { const auto height = top.distance_to(bottom); const auto side_size = height / ratio; diff --git a/source/collision/line_tracer.cpp b/source/collision/line_tracer.cpp index e60c0de9..311c99a2 100644 --- a/source/collision/line_tracer.cpp +++ b/source/collision/line_tracer.cpp @@ -5,21 +5,21 @@ namespace omath::collision { - bool LineTracer::can_trace_line(const Ray& ray, const Triangle>& triangle) + bool LineTracer::can_trace_line(const Ray& ray, const Triangle>& triangle) noexcept { return get_ray_hit_point(ray, triangle) == ray.end; } - Vector3 Ray::direction_vector() const + Vector3 Ray::direction_vector() const noexcept { return end - start; } - Vector3 Ray::direction_vector_normalized() const + Vector3 Ray::direction_vector_normalized() const noexcept { return direction_vector().normalized(); } - Vector3 LineTracer::get_ray_hit_point(const Ray& ray, const Triangle>& triangle) + Vector3 LineTracer::get_ray_hit_point(const Ray& ray, const Triangle>& triangle) noexcept { constexpr float k_epsilon = std::numeric_limits::epsilon(); diff --git a/source/engines/iw_engine/camera.cpp b/source/engines/iw_engine/camera.cpp index 77aaf204..27d8ae78 100644 --- a/source/engines/iw_engine/camera.cpp +++ b/source/engines/iw_engine/camera.cpp @@ -21,11 +21,11 @@ namespace omath::iw_engine m_view_angles.yaw = -YawAngle::from_radians(std::atan2(delta.y, delta.x)); m_view_angles.roll = RollAngle::from_radians(0.f); } - Mat4X4 Camera::calc_view_matrix() const + Mat4X4 Camera::calc_view_matrix() const noexcept { return iw_engine::calc_view_matrix(m_view_angles, m_origin); } - Mat4X4 Camera::calc_projection_matrix() const + Mat4X4 Camera::calc_projection_matrix() const noexcept { return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), m_near_plane_distance, m_far_plane_distance); diff --git a/source/engines/opengl_engine/camera.cpp b/source/engines/opengl_engine/camera.cpp index 4b694100..269974cc 100644 --- a/source/engines/opengl_engine/camera.cpp +++ b/source/engines/opengl_engine/camera.cpp @@ -21,11 +21,11 @@ namespace omath::opengl_engine m_view_angles.yaw = -YawAngle::from_radians(std::atan2(delta.y, delta.x)); m_view_angles.roll = RollAngle::from_radians(0.f); } - Mat4X4 Camera::calc_view_matrix() const + Mat4X4 Camera::calc_view_matrix() const noexcept { return opengl_engine::calc_view_matrix(m_view_angles, m_origin); } - Mat4X4 Camera::calc_projection_matrix() const + Mat4X4 Camera::calc_projection_matrix() const noexcept { return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), m_near_plane_distance, m_far_plane_distance); diff --git a/source/engines/opengl_engine/formulas.cpp b/source/engines/opengl_engine/formulas.cpp index 79868b53..4a14ab2f 100644 --- a/source/engines/opengl_engine/formulas.cpp +++ b/source/engines/opengl_engine/formulas.cpp @@ -6,39 +6,39 @@ namespace omath::opengl_engine { - Vector3 forward_vector(const ViewAngles& angles) + 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) + 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) + 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) + 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) + Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept { return mat_rotation_axis_x(-angles.pitch) * mat_rotation_axis_y(-angles.yaw) * mat_rotation_axis_z(angles.roll); } Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, - const float far) + const float far) noexcept { const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f); diff --git a/source/engines/source_engine/camera.cpp b/source/engines/source_engine/camera.cpp index 3b550f5b..edd4c529 100644 --- a/source/engines/source_engine/camera.cpp +++ b/source/engines/source_engine/camera.cpp @@ -22,12 +22,12 @@ namespace omath::source_engine m_view_angles.roll = RollAngle::from_radians(0.f); } - Mat4X4 Camera::calc_view_matrix() const + Mat4X4 Camera::calc_view_matrix() const noexcept { return source_engine::calc_view_matrix(m_view_angles, m_origin); } - Mat4X4 Camera::calc_projection_matrix() const + Mat4X4 Camera::calc_projection_matrix() const noexcept { return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), m_near_plane_distance, m_far_plane_distance); diff --git a/source/engines/source_engine/formulas.cpp b/source/engines/source_engine/formulas.cpp index fe869e4e..326507e8 100644 --- a/source/engines/source_engine/formulas.cpp +++ b/source/engines/source_engine/formulas.cpp @@ -5,38 +5,38 @@ namespace omath::source_engine { - Vector3 forward_vector(const ViewAngles& angles) + 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)}; } - Mat4X4 rotation_matrix(const ViewAngles& angles) + Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept { return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll); } - Vector3 right_vector(const ViewAngles& angles) + 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) + 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) + 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 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, - const float far) + const float far) noexcept { // NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation constexpr auto k_multiply_factor = 0.75f; diff --git a/source/engines/unity_engine/camera.cpp b/source/engines/unity_engine/camera.cpp index f06304db..5455c766 100644 --- a/source/engines/unity_engine/camera.cpp +++ b/source/engines/unity_engine/camera.cpp @@ -15,11 +15,11 @@ namespace omath::unity_engine { throw std::runtime_error("Not implemented"); } - Mat4X4 Camera::calc_view_matrix() const + Mat4X4 Camera::calc_view_matrix() const noexcept { return unity_engine::calc_view_matrix(m_view_angles, m_origin); } - Mat4X4 Camera::calc_projection_matrix() const + Mat4X4 Camera::calc_projection_matrix() const noexcept { return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), m_near_plane_distance, m_far_plane_distance); diff --git a/source/engines/unity_engine/formulas.cpp b/source/engines/unity_engine/formulas.cpp index ed6579fd..4560e277 100644 --- a/source/engines/unity_engine/formulas.cpp +++ b/source/engines/unity_engine/formulas.cpp @@ -5,37 +5,37 @@ namespace omath::unity_engine { - Vector3 forward_vector(const ViewAngles& angles) + 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) + 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) + 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) + 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) + Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept { return mat_rotation_axis_x(angles.pitch) * mat_rotation_axis_y(angles.yaw) * mat_rotation_axis_z(angles.roll); } Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, - const float far) + const float far) noexcept { const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f); diff --git a/source/projectile_prediction/proj_pred_engine_legacy.cpp b/source/projectile_prediction/proj_pred_engine_legacy.cpp index 5d3c0412..b2f5c7dc 100644 --- a/source/projectile_prediction/proj_pred_engine_legacy.cpp +++ b/source/projectile_prediction/proj_pred_engine_legacy.cpp @@ -18,8 +18,8 @@ namespace omath::projectile_prediction { const auto predicted_target_position = target.predict_position(time, m_gravity_constant); - const auto projectile_pitch - = maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position); + const auto projectile_pitch = + maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position); if (!projectile_pitch.has_value()) [[unlikely]] continue; @@ -35,9 +35,8 @@ namespace omath::projectile_prediction return std::nullopt; } - std::optional - ProjPredEngineLegacy::maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile, - const Vector3& target_position) const + std::optional ProjPredEngineLegacy::maybe_calculate_projectile_launch_pitch_angle( + const Projectile& projectile, const Vector3& target_position) const noexcept { const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale; const auto delta = target_position - projectile.m_origin; @@ -60,7 +59,7 @@ namespace omath::projectile_prediction bool ProjPredEngineLegacy::is_projectile_reached_target(const Vector3& target_position, const Projectile& projectile, const float pitch, - const float time) const + const float time) const noexcept { const auto yaw = projectile.m_origin.view_angle_to(target_position).y; const auto projectile_position = projectile.predict_position(pitch, yaw, time, m_gravity_constant); diff --git a/source/projectile_prediction/projectile.cpp b/source/projectile_prediction/projectile.cpp index 30202db4..c37151fe 100644 --- a/source/projectile_prediction/projectile.cpp +++ b/source/projectile_prediction/projectile.cpp @@ -8,7 +8,7 @@ namespace omath::projectile_prediction { Vector3 Projectile::predict_position(const float pitch, const float yaw, const float time, - const float gravity) const + const float gravity) const noexcept { auto current_pos = m_origin + source_engine::forward_vector({source_engine::PitchAngle::from_degrees(-pitch), From 5489c296e9342717d2134c445f045a6a0dc23b3e Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 5 May 2025 02:24:23 +0300 Subject: [PATCH 297/795] added more noexcept --- include/omath/pathfinding/a_star.hpp | 6 +++--- include/omath/pathfinding/navigation_mesh.hpp | 8 ++++---- source/pathfinding/a_star.cpp | 6 +++--- source/pathfinding/navigation_mesh.cpp | 9 +++++---- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/include/omath/pathfinding/a_star.hpp b/include/omath/pathfinding/a_star.hpp index ba5041da..d894e761 100644 --- a/include/omath/pathfinding/a_star.hpp +++ b/include/omath/pathfinding/a_star.hpp @@ -15,16 +15,16 @@ namespace omath::pathfinding public: [[nodiscard]] static std::vector> find_path(const Vector3& start, const Vector3& end, - const NavigationMesh& nav_mesh); + const NavigationMesh& nav_mesh) noexcept; private: [[nodiscard]] static std::vector> reconstruct_final_path(const std::unordered_map, PathNode>& closed_list, - const Vector3& current); + const Vector3& current) noexcept; [[nodiscard]] static auto get_perfect_node(const std::unordered_map, PathNode>& open_list, - const Vector3& end_vertex); + const Vector3& end_vertex) noexcept; }; } // namespace omath::pathfinding diff --git a/include/omath/pathfinding/navigation_mesh.hpp b/include/omath/pathfinding/navigation_mesh.hpp index 588681b3..75ba634d 100644 --- a/include/omath/pathfinding/navigation_mesh.hpp +++ b/include/omath/pathfinding/navigation_mesh.hpp @@ -20,17 +20,17 @@ namespace omath::pathfinding { public: [[nodiscard]] - std::expected, std::string> get_closest_vertex(const Vector3& point) const; + std::expected, std::string> get_closest_vertex(const Vector3& point) const noexcept; [[nodiscard]] - const std::vector>& get_neighbors(const Vector3& vertex) const; + const std::vector>& get_neighbors(const Vector3& vertex) const noexcept; [[nodiscard]] bool empty() const; - [[nodiscard]] std::vector serialize() const; + [[nodiscard]] std::vector serialize() const noexcept; - void deserialize(const std::vector& raw); + void deserialize(const std::vector& raw) noexcept; std::unordered_map, std::vector>> m_vertex_map; }; diff --git a/source/pathfinding/a_star.cpp b/source/pathfinding/a_star.cpp index 593c9079..bcd73484 100644 --- a/source/pathfinding/a_star.cpp +++ b/source/pathfinding/a_star.cpp @@ -17,7 +17,7 @@ namespace omath::pathfinding std::vector> Astar::reconstruct_final_path(const std::unordered_map, PathNode>& closed_list, - const Vector3& current) + const Vector3& current) noexcept { std::vector> path; std::optional current_opt = current; @@ -38,7 +38,7 @@ namespace omath::pathfinding return path; } auto Astar::get_perfect_node(const std::unordered_map, PathNode>& open_list, - const Vector3& end_vertex) + const Vector3& end_vertex) noexcept { return std::ranges::min_element(open_list, [&end_vertex](const auto& a, const auto& b) @@ -50,7 +50,7 @@ namespace omath::pathfinding } std::vector> Astar::find_path(const Vector3& start, const Vector3& end, - const NavigationMesh& nav_mesh) + const NavigationMesh& nav_mesh) noexcept { std::unordered_map, PathNode> closed_list; std::unordered_map, PathNode> open_list; diff --git a/source/pathfinding/navigation_mesh.cpp b/source/pathfinding/navigation_mesh.cpp index 97c7dc1b..b2a831b0 100644 --- a/source/pathfinding/navigation_mesh.cpp +++ b/source/pathfinding/navigation_mesh.cpp @@ -6,7 +6,8 @@ #include namespace omath::pathfinding { - std::expected, std::string> NavigationMesh::get_closest_vertex(const Vector3& point) const + std::expected, std::string> + NavigationMesh::get_closest_vertex(const Vector3& point) const noexcept { const auto res = std::ranges::min_element(m_vertex_map, [&point](const auto& a, const auto& b) { return a.first.distance_to(point) < b.first.distance_to(point); }); @@ -17,7 +18,7 @@ namespace omath::pathfinding return res->first; } - const std::vector>& NavigationMesh::get_neighbors(const Vector3& vertex) const + const std::vector>& NavigationMesh::get_neighbors(const Vector3& vertex) const noexcept { return m_vertex_map.at(vertex); } @@ -27,7 +28,7 @@ namespace omath::pathfinding return m_vertex_map.empty(); } - std::vector NavigationMesh::serialize() const + std::vector NavigationMesh::serialize() const noexcept { auto dump_to_vector = [](const T& t, std::vector& vec) { @@ -50,7 +51,7 @@ namespace omath::pathfinding return raw; } - void NavigationMesh::deserialize(const std::vector& raw) + void NavigationMesh::deserialize(const std::vector& raw) noexcept { auto load_from_vector = [](const std::vector& vec, size_t& offset, auto& value) { From f179aea4d758e1e6eea3b8e39a47fa90b2db71c7 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 13 May 2025 09:22:23 +0300 Subject: [PATCH 298/795] removed even float type from vector classes --- include/omath/vector2.hpp | 4 ++-- include/omath/vector3.hpp | 6 +++--- include/omath/vector4.hpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/omath/vector2.hpp b/include/omath/vector2.hpp index 03a98cca..0f31bc3f 100644 --- a/include/omath/vector2.hpp +++ b/include/omath/vector2.hpp @@ -174,12 +174,12 @@ namespace omath return {x - v.x, y - v.y}; } - [[nodiscard]] constexpr Vector2 operator*(const float fl) const noexcept + [[nodiscard]] constexpr Vector2 operator*(const Type& fl) const noexcept { return {x * fl, y * fl}; } - [[nodiscard]] constexpr Vector2 operator/(const float fl) const noexcept + [[nodiscard]] constexpr Vector2 operator/(const Type& fl) const noexcept { return {x / fl, y / fl}; } diff --git a/include/omath/vector3.hpp b/include/omath/vector3.hpp index fc302978..1f0316b1 100644 --- a/include/omath/vector3.hpp +++ b/include/omath/vector3.hpp @@ -185,7 +185,7 @@ namespace omath return {this->x - v.x, this->y - v.y, z - v.z}; } - [[nodiscard]] constexpr Vector3 operator*(const float fl) const noexcept + [[nodiscard]] constexpr Vector3 operator*(const Type& fl) const noexcept { return {this->x * fl, this->y * fl, z * fl}; } @@ -195,7 +195,7 @@ namespace omath return {this->x * v.x, this->y * v.y, z * v.z}; } - [[nodiscard]] constexpr Vector3 operator/(const float fl) const noexcept + [[nodiscard]] constexpr Vector3 operator/(const Type& fl) const noexcept { return {this->x / fl, this->y / fl, z / fl}; } @@ -245,7 +245,7 @@ namespace omath [[nodiscard]] Vector3 view_angle_to(const Vector3& other) const noexcept { - const float distance = distance_to(other); + const auto distance = distance_to(other); const auto delta = other - *this; return {angles::radians_to_degrees(std::asin(delta.z / distance)), diff --git a/include/omath/vector4.hpp b/include/omath/vector4.hpp index 2f699897..dfc403f7 100644 --- a/include/omath/vector4.hpp +++ b/include/omath/vector4.hpp @@ -47,7 +47,7 @@ namespace omath return *this; } - constexpr Vector4& operator*=(const float scalar) noexcept + constexpr Vector4& operator*=(const Type& scalar) noexcept { Vector3::operator*=(scalar); w *= scalar; @@ -63,7 +63,7 @@ namespace omath return *this; } - constexpr Vector4& operator/=(const float scalar) noexcept + constexpr Vector4& operator/=(const Type& scalar) noexcept { Vector3::operator/=(scalar); w /= scalar; From 52024285d25cdc07b9432a057115ec3915c4c5fe Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 13 May 2025 09:34:39 +0300 Subject: [PATCH 299/795] added noexcept --- include/omath/engines/iw_engine/formulas.hpp | 12 ++++++------ source/engines/iw_engine/formulas.cpp | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/omath/engines/iw_engine/formulas.hpp b/include/omath/engines/iw_engine/formulas.hpp index 424621f7..433987da 100644 --- a/include/omath/engines/iw_engine/formulas.hpp +++ b/include/omath/engines/iw_engine/formulas.hpp @@ -8,19 +8,19 @@ namespace omath::iw_engine { [[nodiscard]] - Vector3 forward_vector(const ViewAngles& angles); + Vector3 forward_vector(const ViewAngles& angles) noexcept; [[nodiscard]] - Vector3 right_vector(const ViewAngles& angles); + Vector3 right_vector(const ViewAngles& angles) noexcept; [[nodiscard]] - Vector3 up_vector(const ViewAngles& angles); + Vector3 up_vector(const ViewAngles& angles) noexcept; [[nodiscard]] - Mat4X4 rotation_matrix(const ViewAngles& angles); + Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept; - [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin); + [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept; [[nodiscard]] - Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far); + Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept; } // namespace omath::iw_engine diff --git a/source/engines/iw_engine/formulas.cpp b/source/engines/iw_engine/formulas.cpp index 37635dc9..c346d50c 100644 --- a/source/engines/iw_engine/formulas.cpp +++ b/source/engines/iw_engine/formulas.cpp @@ -6,37 +6,37 @@ namespace omath::iw_engine { - Vector3 forward_vector(const ViewAngles& angles) + 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) + 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) + 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 rotation_matrix(const ViewAngles& angles) + Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept { return mat_rotation_axis_z(angles.yaw) * mat_rotation_axis_y(angles.pitch) * mat_rotation_axis_x(angles.roll); } - Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) + 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 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, - const float far) + const float far) noexcept { // NOTE: Need magic number to fix fov calculation, since IW engine inherit Quake proj matrix calculation constexpr auto k_multiply_factor = 0.75f; From 17eb0cd0dc1d068ee238095d453774dcf1036ac6 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 13 May 2025 09:47:08 +0300 Subject: [PATCH 300/795] improved naming --- include/omath/vector2.hpp | 72 ++++++++++++++++---------------- include/omath/vector3.hpp | 86 ++++++++++++++++++++------------------- include/omath/vector4.hpp | 69 +++++++++++++++---------------- 3 files changed, 115 insertions(+), 112 deletions(-) diff --git a/include/omath/vector2.hpp b/include/omath/vector2.hpp index 0f31bc3f..6287131e 100644 --- a/include/omath/vector2.hpp +++ b/include/omath/vector2.hpp @@ -30,78 +30,78 @@ namespace omath // Equality operators [[nodiscard]] - constexpr bool operator==(const Vector2& src) const noexcept + constexpr bool operator==(const Vector2& other) const noexcept { - return x == src.x && y == src.y; + return x == other.x && y == other.y; } [[nodiscard]] - constexpr bool operator!=(const Vector2& src) const noexcept + constexpr bool operator!=(const Vector2& other) const noexcept { - return !(*this == src); + return !(*this == other); } // Compound assignment operators - constexpr Vector2& operator+=(const Vector2& v) noexcept + constexpr Vector2& operator+=(const Vector2& other) noexcept { - x += v.x; - y += v.y; + x += other.x; + y += other.y; return *this; } - constexpr Vector2& operator-=(const Vector2& v) noexcept + constexpr Vector2& operator-=(const Vector2& other) noexcept { - x -= v.x; - y -= v.y; + x -= other.x; + y -= other.y; return *this; } - constexpr Vector2& operator*=(const Vector2& v) noexcept + constexpr Vector2& operator*=(const Vector2& other) noexcept { - x *= v.x; - y *= v.y; + x *= other.x; + y *= other.y; return *this; } - constexpr Vector2& operator/=(const Vector2& v) noexcept + constexpr Vector2& operator/=(const Vector2& other) noexcept { - x /= v.x; - y /= v.y; + x /= other.x; + y /= other.y; return *this; } - constexpr Vector2& operator*=(const Type& fl) noexcept + constexpr Vector2& operator*=(const Type& value) noexcept { - x *= fl; - y *= fl; + x *= value; + y *= value; return *this; } - constexpr Vector2& operator/=(const Type& fl) noexcept + constexpr Vector2& operator/=(const Type& value) noexcept { - x /= fl; - y /= fl; + x /= value; + y /= value; return *this; } - constexpr Vector2& operator+=(const Type& fl) noexcept + constexpr Vector2& operator+=(const Type& value) noexcept { - x += fl; - y += fl; + x += value; + y += value; return *this; } - constexpr Vector2& operator-=(const Type& fl) noexcept + constexpr Vector2& operator-=(const Type& value) noexcept { - x -= fl; - y -= fl; + x -= value; + y -= value; return *this; } @@ -164,24 +164,24 @@ namespace omath } // Binary arithmetic operators - [[nodiscard]] constexpr Vector2 operator+(const Vector2& v) const noexcept + [[nodiscard]] constexpr Vector2 operator+(const Vector2& other) const noexcept { - return {x + v.x, y + v.y}; + return {x + other.x, y + other.y}; } - [[nodiscard]] constexpr Vector2 operator-(const Vector2& v) const noexcept + [[nodiscard]] constexpr Vector2 operator-(const Vector2& other) const noexcept { - return {x - v.x, y - v.y}; + return {x - other.x, y - other.y}; } - [[nodiscard]] constexpr Vector2 operator*(const Type& fl) const noexcept + [[nodiscard]] constexpr Vector2 operator*(const Type& value) const noexcept { - return {x * fl, y * fl}; + return {x * value, y * value}; } - [[nodiscard]] constexpr Vector2 operator/(const Type& fl) const noexcept + [[nodiscard]] constexpr Vector2 operator/(const Type& value) const noexcept { - return {x / fl, y / fl}; + return {x / value, y / value}; } // Sum of elements diff --git a/include/omath/vector3.hpp b/include/omath/vector3.hpp index 1f0316b1..a4654c0d 100644 --- a/include/omath/vector3.hpp +++ b/include/omath/vector3.hpp @@ -29,76 +29,76 @@ namespace omath } constexpr Vector3() noexcept: Vector2() {}; - [[nodiscard]] constexpr bool operator==(const Vector3& src) const noexcept + [[nodiscard]] constexpr bool operator==(const Vector3& other) const noexcept { - return Vector2::operator==(src) && (src.z == z); + return Vector2::operator==(other) && (other.z == z); } - [[nodiscard]] constexpr bool operator!=(const Vector3& src) const noexcept + [[nodiscard]] constexpr bool operator!=(const Vector3& other) const noexcept { - return !(*this == src); + return !(*this == other); } - constexpr Vector3& operator+=(const Vector3& v) noexcept + constexpr Vector3& operator+=(const Vector3& other) noexcept { - Vector2::operator+=(v); - z += v.z; + Vector2::operator+=(other); + z += other.z; return *this; } - constexpr Vector3& operator-=(const Vector3& v) noexcept + constexpr Vector3& operator-=(const Vector3& other) noexcept { - Vector2::operator-=(v); - z -= v.z; + Vector2::operator-=(other); + z -= other.z; return *this; } - constexpr Vector3& operator*=(const float fl) noexcept + constexpr Vector3& operator*=(const Type& value) noexcept { - Vector2::operator*=(fl); - z *= fl; + Vector2::operator*=(value); + z *= value; return *this; } - constexpr Vector3& operator*=(const Vector3& v) noexcept + constexpr Vector3& operator*=(const Vector3& other) noexcept { - Vector2::operator*=(v); - z *= v.z; + Vector2::operator*=(other); + z *= other.z; return *this; } - constexpr Vector3& operator/=(const Vector3& v) noexcept + constexpr Vector3& operator/=(const Vector3& other) noexcept { - Vector2::operator/=(v); - z /= v.z; + Vector2::operator/=(other); + z /= other.z; return *this; } - constexpr Vector3& operator+=(const float fl) noexcept + constexpr Vector3& operator+=(const Type& value) noexcept { - Vector2::operator+=(fl); - z += fl; + Vector2::operator+=(value); + z += value; return *this; } - constexpr Vector3& operator/=(const float fl) noexcept + constexpr Vector3& operator/=(const Type& value) noexcept { - Vector2::operator/=(fl); - z /= fl; + Vector2::operator/=(value); + z /= value; return *this; } - constexpr Vector3& operator-=(const float fl) noexcept + constexpr Vector3& operator-=(const Type& value) noexcept { - Vector2::operator-=(fl); - z -= fl; + Vector2::operator-=(value); + z -= value; return *this; } @@ -175,40 +175,42 @@ namespace omath return {-this->x, -this->y, -z}; } - [[nodiscard]] constexpr Vector3 operator+(const Vector3& v) const noexcept + [[nodiscard]] constexpr Vector3 operator+(const Vector3& other) const noexcept { - return {this->x + v.x, this->y + v.y, z + v.z}; + return {this->x + other.x, this->y + other.y, z + other.z}; } - [[nodiscard]] constexpr Vector3 operator-(const Vector3& v) const noexcept + [[nodiscard]] constexpr Vector3 operator-(const Vector3& other) const noexcept { - return {this->x - v.x, this->y - v.y, z - v.z}; + return {this->x - other.x, this->y - other.y, z - other.z}; } - [[nodiscard]] constexpr Vector3 operator*(const Type& fl) const noexcept + [[nodiscard]] constexpr Vector3 operator*(const Type& value) const noexcept { - return {this->x * fl, this->y * fl, z * fl}; + return {this->x * value, this->y * value, z * value}; } - [[nodiscard]] constexpr Vector3 operator*(const Vector3& v) const noexcept + [[nodiscard]] constexpr Vector3 operator*(const Vector3& other) const noexcept { - return {this->x * v.x, this->y * v.y, z * v.z}; + return {this->x * other.x, this->y * other.y, z * other.z}; } - [[nodiscard]] constexpr Vector3 operator/(const Type& fl) const noexcept + [[nodiscard]] constexpr Vector3 operator/(const Type& value) const noexcept { - return {this->x / fl, this->y / fl, z / fl}; + return {this->x / value, this->y / value, z / value}; } - [[nodiscard]] constexpr Vector3 operator/(const Vector3& v) const noexcept + [[nodiscard]] constexpr Vector3 operator/(const Vector3& other) const noexcept { - return {this->x / v.x, this->y / v.y, z / v.z}; + return {this->x / other.x, this->y / other.y, z / other.z}; } - [[nodiscard]] constexpr Vector3 cross(const Vector3& v) const noexcept + [[nodiscard]] constexpr Vector3 cross(const Vector3& other) const noexcept { - return {this->y * v.z - z * v.y, z * v.x - this->x * v.z, this->x * v.y - this->y * v.x}; + return {this->y * other.z - z * other.y, z * other.x - this->x * other.z, + this->x * other.y - this->y * other.x}; } + [[nodiscard]] constexpr Type sum() const noexcept { return sum_2d() + z; diff --git a/include/omath/vector4.hpp b/include/omath/vector4.hpp index dfc403f7..9e54fbbd 100644 --- a/include/omath/vector4.hpp +++ b/include/omath/vector4.hpp @@ -9,6 +9,7 @@ namespace omath { template + requires std::is_arithmetic_v class Vector4 : public Vector3 { public: @@ -20,61 +21,61 @@ namespace omath constexpr Vector4() noexcept : Vector3(), w(0) {}; [[nodiscard]] - constexpr bool operator==(const Vector4& src) const noexcept + constexpr bool operator==(const Vector4& other) const noexcept { - return Vector3::operator==(src) && w == src.w; + return Vector3::operator==(other) && w == other.w; } [[nodiscard]] - constexpr bool operator!=(const Vector4& src) const noexcept + constexpr bool operator!=(const Vector4& other) const noexcept { - return !(*this == src); + return !(*this == other); } - constexpr Vector4& operator+=(const Vector4& v) noexcept + constexpr Vector4& operator+=(const Vector4& other) noexcept { - Vector3::operator+=(v); - w += v.w; + Vector3::operator+=(other); + w += other.w; return *this; } - constexpr Vector4& operator-=(const Vector4& v) noexcept + constexpr Vector4& operator-=(const Vector4& other) noexcept { - Vector3::operator-=(v); - w -= v.w; + Vector3::operator-=(other); + w -= other.w; return *this; } - constexpr Vector4& operator*=(const Type& scalar) noexcept + constexpr Vector4& operator*=(const Type& value) noexcept { - Vector3::operator*=(scalar); - w *= scalar; + Vector3::operator*=(value); + w *= value; return *this; } - constexpr Vector4& operator*=(const Vector4& v) noexcept + constexpr Vector4& operator*=(const Vector4& other) noexcept { - Vector3::operator*=(v); - w *= v.w; + Vector3::operator*=(other); + w *= other.w; return *this; } - constexpr Vector4& operator/=(const Type& scalar) noexcept + constexpr Vector4& operator/=(const Type& value) noexcept { - Vector3::operator/=(scalar); - w /= scalar; + Vector3::operator/=(value); + w /= value; return *this; } - constexpr Vector4& operator/=(const Vector4& v) noexcept + constexpr Vector4& operator/=(const Vector4& other) noexcept { - Vector3::operator/=(v); - w /= v.w; + Vector3::operator/=(other); + w /= other.w; return *this; } @@ -116,39 +117,39 @@ namespace omath } [[nodiscard]] - constexpr Vector4 operator+(const Vector4& v) const noexcept + constexpr Vector4 operator+(const Vector4& other) const noexcept { - return {this->x + v.x, this->y + v.y, this->z + v.z, w + v.w}; + return {this->x + other.x, this->y + other.y, this->z + other.z, w + other.w}; } [[nodiscard]] - constexpr Vector4 operator-(const Vector4& v) const noexcept + constexpr Vector4 operator-(const Vector4& other) const noexcept { - return {this->x - v.x, this->y - v.y, this->z - v.z, w - v.w}; + return {this->x - other.x, this->y - other.y, this->z - other.z, w - other.w}; } [[nodiscard]] - constexpr Vector4 operator*(const Type& scalar) const noexcept + constexpr Vector4 operator*(const Type& value) const noexcept { - return {this->x * scalar, this->y * scalar, this->z * scalar, w * scalar}; + return {this->x * value, this->y * value, this->z * value, w * value}; } [[nodiscard]] - constexpr Vector4 operator*(const Vector4& v) const noexcept + constexpr Vector4 operator*(const Vector4& other) const noexcept { - return {this->x * v.x, this->y * v.y, this->z * v.z, w * v.w}; + return {this->x * other.x, this->y * other.y, this->z * other.z, w * other.w}; } [[nodiscard]] - constexpr Vector4 operator/(const Type& scalar) const noexcept + constexpr Vector4 operator/(const Type& value) const noexcept { - return {this->x / scalar, this->y / scalar, this->z / scalar, w / scalar}; + return {this->x / value, this->y / value, this->z / value, w / value}; } [[nodiscard]] - constexpr Vector4 operator/(const Vector4& v) const noexcept + constexpr Vector4 operator/(const Vector4& other) const noexcept { - return {this->x / v.x, this->y / v.y, this->z / v.z, w / v.w}; + return {this->x / other.x, this->y / other.y, this->z / other.z, w / other.w}; } [[nodiscard]] From a81d12d4804fa5143817cde9de00e3ba60247088 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 13 May 2025 09:48:46 +0300 Subject: [PATCH 301/795] fixed version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d4190b1a..2a45a815 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.26) -project(omath VERSION 1.0.1 LANGUAGES CXX) +project(omath VERSION 3.0.2 LANGUAGES CXX) include(CMakePackageConfigHelpers) From b56801ac912ce0da3147ca30ff189c098e4adcd3 Mon Sep 17 00:00:00 2001 From: Orange Date: Sat, 29 Mar 2025 17:47:50 +0300 Subject: [PATCH 302/795] added files --- writerside/c.list | 6 ++ writerside/cfg/buildprofiles.xml | 13 +++ writerside/images/completion_procedure.png | Bin 0 -> 128146 bytes .../images/completion_procedure_dark.png | Bin 0 -> 68718 bytes writerside/images/convert_table_to_xml.png | Bin 0 -> 349338 bytes .../images/convert_table_to_xml_dark.png | Bin 0 -> 121495 bytes writerside/images/new_topic_options.png | Bin 0 -> 313855 bytes writerside/images/new_topic_options_dark.png | Bin 0 -> 281000 bytes writerside/o.tree | 10 +++ writerside/redirection-rules.xml | 13 +++ writerside/topics/starter-topic.md | 78 ++++++++++++++++++ writerside/v.list | 5 ++ writerside/writerside.cfg | 8 ++ 13 files changed, 133 insertions(+) create mode 100644 writerside/c.list create mode 100644 writerside/cfg/buildprofiles.xml create mode 100644 writerside/images/completion_procedure.png create mode 100644 writerside/images/completion_procedure_dark.png create mode 100644 writerside/images/convert_table_to_xml.png create mode 100644 writerside/images/convert_table_to_xml_dark.png create mode 100644 writerside/images/new_topic_options.png create mode 100644 writerside/images/new_topic_options_dark.png create mode 100644 writerside/o.tree create mode 100644 writerside/redirection-rules.xml create mode 100644 writerside/topics/starter-topic.md create mode 100644 writerside/v.list create mode 100644 writerside/writerside.cfg diff --git a/writerside/c.list b/writerside/c.list new file mode 100644 index 00000000..c4c77a29 --- /dev/null +++ b/writerside/c.list @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/writerside/cfg/buildprofiles.xml b/writerside/cfg/buildprofiles.xml new file mode 100644 index 00000000..d2654201 --- /dev/null +++ b/writerside/cfg/buildprofiles.xml @@ -0,0 +1,13 @@ + + + + + + + + true + + + + diff --git a/writerside/images/completion_procedure.png b/writerside/images/completion_procedure.png new file mode 100644 index 0000000000000000000000000000000000000000..3535a3f3facfc560b3ee53afbdcb36cb28d7339a GIT binary patch literal 128146 zcma&N1z1#H+b&KiC{iMV)X*&`O2^QMAV@cev>@FzC@tM7g8~8~jWi4;-8GbSH$x8- z|H0q;e&0FY|Mi@6J=bt;X0N^C+4mFce%3_1R#harOMMp$3yVnUg`5T!7G5nD7B&vS zZOk|Kx&<6Df9-5#WnU}F$}+rmakR3vx5UDF5s|Emucz7jFxxO8E^Y*e;KiM0W)0jI zcfQ<;TX-ML68HM83IW%7W#Q7S`~&U=uCH(OZ9Vq-zD{4im6q41bg%dVV`ItLZGknP z_`4p0pIM_c4^$2$uxtXBUb0*jKE%4UB4aD#^@RDcM3A)4ZBnUQ7J;~rdDYm=e*Kci zpvz274vrY;qi{-Wt+a>A)3Kx?>;Chnu6Y?vgKpWiub-qd>-o53}(Loz@9>yWW%cZ51u zrur`#kAa+Mt?Zoe#k(tbAB4sAdSfXpl{@Zho#)@GZ1bxalp$kR2)UrVo5+-O)~0$~ zhkM*%RNjyCl-oN_kwH0s%7zsgM-i06|!sImgHA9F@2Yjj0WZn`k9CLJ&JOFe%RMB0kwB3#)j7e^@f{x{L@ zqf{5Rdrlu7tYKk0>Db=Z%_ANNUlt&H5Cmknk2N#Wu=GJ6o8u##Pu5_4@{t9}z(b-R zJ-BL&yjbcC08|RekNYIv%Ghc-3a2E8M#5fC7W{-CoS;pBg~)Ga(yyvtG6PO~Mrf;9 zLhs9llXX7%D&Y`}zZBs0yyaGQ9`40-nb**mcW&^vU&bL4Wa`9L`lYBwp= z6Ya;9EoHYHa784J-^>FlWvz?=LO4Tp_@^E1xL>T*3N4FQbhJW_aW5A6FAr=B_j*b# zlEkBEc7=X7En*)5rxq7MTbaBt zDy-|Kd-oD$;JdbVsMgS?65PlVVgWt;XyD*}}dtmQ#hb=Z7M_#dS>!tp8? z?&Jj>m-ork4d=h%Pb1z4y#M-k%7@7}bkle>A3wh#nI@rbHFLtr!lrDqbfTLNX)GXb z!jo8{+ma;>4jv&DeH)&}I2F#k68a<_i{(Lt!u>cm7V=){&T!iFm;u?oIEo%o3qp

)`$sJ*Fqs<6Xm5Cg^+PYtjQzGM; zd$x7wl*xx~U&{1rGga%vQ~v>4*RC`wn#f=L<@PF{20LF?xcsfZ(w-4@lI^ktUog67$s`x!^AzqAtt`-~>U&=P&qhIEbMcs+PGsOvuQ|Mt-VM`+I zyEAtO|4zZO{tinM^Zf|8oZ(j?@yP8?qm}0?Z&z-=r8^d`_v<8AxDaojh_T{bo6qgK_SeTBn@o$;miV`Nqd6I_BGX7<7 zk+ZNJK#`~xK3Aq_3ReN|mxCPof`xA_`J*<1)eHu=OX6yRbzX@n`AS{qNJE`Qmu!v% ztZcR3P1qTYdgUukRZm&i#z1+dc%b4r-bz)5kv}YRe@(5t8=Uw)@ouc4XtMYfl)i|r z*f^(I&o}kldw)}ug^uR)Yr2*pzt1J2?C$jAPa9Z2zuFw#9IYKy8y9nrXsBtR{cZG{ z|F=ZL`vwUgQ6G9AP9M!vuCtge_bqd{Ae4r87 zQ8|!WFg)Q9D127uaMT-TM)fYux^BL_?Wl#!VeQQW$LBLPxudqZ2g?l|$k3C}RN_S- zBIfD4y*F8J#0R~Kor|5zB8rsiB04QfdX*9#H2udJ#*N0czbU?I9N4nLwqmN%w(1@* zd2&j{qad04(fQM;xr05u8L!ad+XbryYcsoe`=`*aLn|$;zY=WI-`Dq5{;KTm8+cbe z=~hf#EH-6f_wDD1PxP7o?uB5DV38o9J+*!I9QCw|edK#P`@@;hJ)C{E8SNS4D)!lD z`zL!s)21F$bmDZu#L2{|wCaj{it($ryFJ)HCdMXC7xis_dp4QAZnS2!iWre!WSzAv zRSxPeCevZ&SKt9SdvxmRszLpr?@K)uccP+bMLaD|dW0N^9mH#2*4iCBzj$(?e-M38 zCP6EKM^7W+;5{ax=*xR#e~egJ-)BAhezpUvfeoFS9Qr2~^+%cWT4kRwUgBL3z(n>= zMxp(+Bc6~$aKKQ2cR(W|JGPRJmFnmF33Cu3s1*GW9fPGHW!1V77$bEeCbIJ_`?olHoCjh4P3W1M`OPJ|hz^+15@={w_zT$?Z(TelM%H@CH|Vm*dp&w# zYbg%~lPWkTIS(~uHFWbl@}gg4#(O;d&0(ChkS5*tqIWubRE*7SVCU&)bBXShsOG0b z9`i}go82_g!vK)Q#|^xLVBGZJfr$a*bZ~veJePyxLv}lV9g^5V!w%nGl9jR*&pU`) z+<3#4$SPe6;PCOVe)>4;y$30Tjq>)RqNBU>;q$NR4^Q5nc&wdm7HmK%3MnieK96wk z^j^^&r1>a*>dr1#b!Gn7X2ZyVp4b3U<7F;Ne; zJ#p=QW8>z2FX$L`wSvj`Pv5*QX1Yi=93rj&R;F4{7dfB=89&{%A)+4va2P=R9P|NOhBplS5J#=^}f+7#p_rK zz^(+Z$bvu9X6I_koyVSP-##~aaG|v3whPBMhk`$gc&E6ZPm7>k!-lW39UEGkMw~`> z-})C@@3t+9gT$scCUro)*BYyq;Qqn=+}T)@J54St?|LSmL4~2bCb9Jk0nArjC7I@q z9`h@UJ^^Cr^Ib7{Px!%5i(;LwkLhl}749m&He?QM0$rYTUNh_Im{N?0J&@dx2)L3v zUvu3=6jT?i4-aH(13i5M>bQUG!XlsYiJHDNId@M*d_i`@bv?=gkk7V;r?RWFhoECn zq~k+~fM4S(Uo6DHXVnML#O(#WXgLj94*Lv92Mh5-FoOx!MEl{zrNgDW zD|uk^+?(rLR{~~b)QY$az4$o?2ePuPmG+)OtdtbzoWb}=q@®2A2ER7)h%UmJ-u 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�!lFX~rCr75VWh?J-(s35(!CkS-mh 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

|$jL7IRZrv24FHba(65 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

=)-_NO+-z@*T{(Ps6 z+qGYh9+mZM9SV|BFQ$yxk*Xvkcio;GQV*vP_1tOG1N+4_HheW;;}5@;bzDd95zK^U zrbvFYA@H*6EtJ7?RLynSMe*Sc4@C=~PKy>9h?%m;*Q1@3|Cl>$Z&DWBCoaF1VS1nc zYWaqQ6tAfasg8W9)aWs)3D!kHbvZG+t1{@}o!TL?bz!;zbtv~2x@7sXL-8XF%(9vi z+=xi=KR5SFe)+&7kvvnx)Z?dFYSlJNKXbV($bIt{IZaX+Vmdm-kPv|3Gm{0mXcV+e z3DVctn=|yYxi1sYig>>1w?Ij*{$;t^R{EvF1g=+?l5B-FzuHRI;1_U>o5}(vXj#>f zlm1)!T`AY+PP#F><=`~!F)e5x50cLz#bK2HBn+{3$l9vv^UF;pXTC#)P_CpxqKP;? zc^HxoX#%TeKhv8l_M?3gAvKWKPiZKm5dWYqKNeLcWllbJg>h~4fs zBTd|zTT}Di^a%wwTHtPSa;c?!hRB4lZ`)nC)v;9`4xE4SnM+2O7S7khQA{rHb@MQqR% zGZk$#4s_@dm5rA3Dl~N`(CN5or)#n9O66+(a(smw%y6Qgl=0z?7Z-Eg%;b$x@1ELgIXTGWj!giY4{t0VY2|%vGGXifIiMBnktZRG@)t-vL-{X!!i=)H zbPLe@1#3}Sk{QWoJYJ>yho=LqkLCT z{X4fXX9T;9Z}Xc>qr1}@C_nE&`sul5XWl%H#iFXXG!B&;12SR>gwXcXkRhL!Lg7g6 z(V&0orF#CO9y?`rpIWD6C~E(t{5~v&ogDlKR{>o36~<2IRa|?q6<`9#{9M~nW&cgK zZW`u4eOJP&d;1CIz+rW9xwl+1NH@N5bQ*^|_I|m-mY&bUYlNgVuBBO7&^UfJ?{IYLe@q?3z18Ws^QsvKgPKHl(*H#ZK1vIkwv% z!D%r-&bCQqUWOr-`_5?cKnClft$mG*42ZuVX&B|(SN%W&bXVg(t~D@A%?6Phyq zrdSTLswC<%?lP)V@vP9 z2{-k9P98@?sA7};>_32wvx*zPUHXVmr+LN^L$`}GBw%u$V=Gw|CGa^+MX#8#RW1>Y z3?P4DnLkc8o^QaO+f-u&QrX7rlbDb?a4Lq1mzlfZBR5Fwzo;cG$iQi}A1n890WzG3 z`5W}3Viho;pcl7e@NGc!c^dXjuTw-(7eFkmFZW z-v$yyyI}?0;IFG);Is~b<+4=H!(S3?=^YFaArLDE8rR=4J&!dBN~8!hIo>dZzu>^< z!(^cbUH?12OSP#x2OHz0hb~xfUCYcuh!9&zBbttAKglTcN#4yfWT4|ab!){mR2|uT z;}*&XH|{!>9E$md|C9(8!YSTUKa7uY%9$`F_quAd;jMMHiR8ZmzGs!Aa8;KG(fJk< zL*6FJ63lq0F-{p1=;=FnHb30&o%3;-W>xRS`Jbl;&S=Tr~r0#t2uh_IUmT##MzQ9P~V3!W2` zUFwwoBFbTcCp6XE!KyVCSJX`y&>nwzlw|qrGx2$Y5${oZuqVjbo6oz0_4{{)F~WAx z-S}R;%^s^6v}Y?A4H7DP7~0&gGXb!i`&F*w(|x3<3_AEdQ%arHWgO#+70|A)coS>* z?k}f`4@L#&Q;tdC)KKlO?7_4Qu=>5m$bpQ`?pOGMvsHFzmyXJO-GxRPhh77CoJ}WX zvGeUwM^L4Tuv|eVqq zLNnEeRf&?Py39Kxd-j48vRuI6N~(5-=1otI9dSYcbMQ}qNaYwFqN_bI^n;Jy7!$T(#{-ut}|$3xfjKPqe5HjbnNjCmUo zXf{C#Xps50=t6_w0t({T3}EsFugq(PL9pY1`im zD~jC`nj_Il8jy17wGU6k%Lch#gh+f(A^tPZ7ySWEmZnCR0Yr0OG!Q2OSY_aR!Rbvl zs1&N6gazQ;8X%OwdDkBwaaKn$ND+rQh*p`~*evXujxIMJglW&FOBkxF>G!8Z;%Mu; zD>(`PtDHz=_%>TW7rT+Tqqm46dI&%2k7mH1Skg~MNmnzTOruXc^7G&pw$c3Rn|lYK zs1^E|^sYkp4kMqZ2SgD&>~<;ULpkRB0mBwbpE>*?1u-W6FW;pqdk}=gH$~}$U5{Py zT(*(7mbeEhDL!f3@S$ERG(;xvsa}Nd#X$>S2_}C&fId%ZN%>0 z6invnz0E7EyD@cK-D1N)n!g=`cD~xZBlyW$dXl_j8nlA;>-t0=LX1V5q`GJ_ys2fj zl$MeUA$>$I)OKS+ipu94=;}R{X&zCaGz_`{E8BoN7jTyvOLBv`l51fAossLhyyAos z?O+jPWS)@(H|HU!r>X#YaF>y^(vr~Lb1B9Q3T0FM`8CaLCx0*jp63y0W4T^;1d%>$DfZtwMCRfbiOM z(-8U#g$SG`&KME`lj$&w&jSvOZ%a68eg5*m@(i)Kb06wgssz>EKr?M{Aib zltry#!;RU2%%5o<+mF?OI zcy} zn=0q~c*|e)STvJcSnPB{&0&h&9$nKXhh<64+5skj7kXc z%6aT)JW1L5KNZe!Kf8F3gNgb2{(kdwAfFj-3a`R9)e?KD#H6E!U=z}uNVKbfpi1 zS5i+k2Idg&QlJ90AG3+jS??maMO!>!CJSzrl~2cnRUKGz7I-q^HeFlxi+ZJzf7{>E zn);xB;y}cF>;d*9C!6TQ2GL3H4mNkU-aeZ?x;zW{Za~g#Oy%UkWJYJyLEvFpI94$& ztUXQBd)hfuE}9?ti+;m={ln z#-Q5i>%=PI%nm!>M&-D@VReT8ea(C>M|6nyq=M@J%8&(cPB$eH{P4|llos-B(U3KdyOtU;+$U+dA)p^8dom3XI-Tn@CTB0 zr~9c*XU>QggSp?oj)T9*JshB#PbBIrj4Jq`O(tkY-GDh3*Uiy!->@EF_+AhSen@{h zO?8tLe2CtXlVh|biu=?-vKD9t+u-R4Uq5Wu$EofK==$4o%>8o2C*{wY z)MG6{AW~Q+A;mfA2&-u5Et|?gvP3az)*SR;s6N!H|@{)BJf0; zy?xaJIhz;GnIt`%4q@(<%ECtOl3$!-&@^2wMMl zrjyg}G?teQ-%nx~t8by*kZ0d+;YM_;u=_+dL%?8WG>Wqb%(5s8sJ$}n)06ICMB-Wz z`SS=j3X=#5LZ41ZYSI@@tBTZ;zl2BjM!KdK0Q;ML^@140?27b&_sd6dy-?Xr>^9yI z$x2pQ`sL48-g^dAf_+f%z*0G4xkvErG=y+anW! zfnRHbgL!NY78?~6zH)kJq(l?cg@(PaAMk0L{9;3$mhqX$&)H#-EB?Z3ZR~H4^8Un! zAn`BMeW)Ur6)U?>-zv9&7q6&A)QyncGHg`anlK8s^sH@5Um0uZ-1Sh?$4lW^Qsjjp zWD)5j)N0j4z+r9k^Ni|3!kp_+{NJei?#OJSB`5DK) zRjco?tLu^$>Qdx~)(`nW|4#C++ZIHV0Hlmm+B%p7n=noiCTCE9zSkq+*vCg6Jo{Ik z)*$IEcL)EQ*1`|cfM1d${1|3T_(Shb31whl)%DLtRqj)97mGu*RwPh6H&!m65WU8= zV2(qJ6ASoe=_V@-Q$JAo_^1-pEy)znm-9vc9%o|c1UE_F6gt23n;5E$zTjK=oKA#3 z*&Pj3glL{^{uSH*gG?b{54x=wHj{62QEXC}D6pp)YU4h^GvVxPxIb^Vy}8883a;V| zAkr+^t7_R4|t z*$f2jzEFX%5LYi4Aq1U`4>_^ni`!^n%zwyZ{JV9g=s2~W6>n2n9Ky9?6=1tkvY zqxRYTRFuICTV}0h#xv%Oi4KR(i1~L0 z6N&;Qme}n`{*r!j=9!S?GPb)=%=4}8Z7ocjQ2lu->=}$m{^tR)i4_tWwI6rM9JpUG zNv>yp<6n69+y5HPPhR(wgIfMp;f-i*QX|g!Fs_Kd4lhe;E1KpE)?A=vxK@7N*>V8~ zjYjKvx_dubp}`$s=Mt8;{a)x@Z1uN*pWy{}XdpV>6hxK2z-;59N$%ekI03c0f8psH z_B~+lbAYaKcbfY-w{RT|0pgxv8};vauA|<)J*boWYx}7X7ZiBjxpURy8t^;wtM>_m zVEgzgld=2uzzU|Xhwled5=sEgj(!UG+hwIzU?O6hwu|#495RYLFK#%<@oSjc{2awK z^VRq5QHi1Bd@9wM+HA@Fd(U&^4sGshd#=9e8O*0EGN$K8;?*rt<}~ON{hyB#%((b;xniV!e-(>l!PhT za-4hP@~p*#1KKvRy^0BY)mjp}cA^kY4FUnS+Y8eNr`t$KX|6IM;8JPj!-2gwlRf;Z zW#?!MFdK1m)PNk#pCk%Mio-aTCpAqdQ8`n+F`}u4343kSKnkKdK>Hp;B_lcapz3|kH~`KCE(VG*$yi`Ux>?C7{*2X?9)3YxGj)hw+Ofcy)J$D zYiMc8+jf+JadTal0LCP<4j+DCxj2pkj; zR4r|X+ln)HkJsFH7qEsVfX9;iY>d^3pArm_c7*t_B4^qYc@r_%N@|qD9WVW&bHypQ z)vlCU$E(#5TJ4yAaYD|S>It*_GW*b`#-jE*j?;srbRtt%uKT5nn^ zPbitO@EXy#i6R)Wm4(Pj1Jk=Eu<>Dhrs{&13T?sx+AI|YI*VT@=UH?-QuXpK1&eE6 zirBa9VFg^VrQ&Yu$0Rwo?&EZw?5T_P28>?|i`TOERsIqIY1lu2Br6sGo?H$kpo^5t zNh3qZA&|&p7=cbGkw7zj=MCO3d!BixZpv6-knOi&1^F{Nb=}o~G-)rUuJ3X$bW-z` z2IJVuL$|3AppHw50CV?LBZ?C?W0tOXz!M8Wnxxp>L!=HVmL>!sJBSM2Oo}o19&#)!k<_CLjX_^f?Py zB%?xuoxcb9#PMIM7bk~I_;=1+cwdLJ~u0v~X199*Do#%jj?A!V`HxX4qs zC0s%~lKi;p$lcTGNSe+;kNlAtyJ&E@4Siu@U+mQCSOENcpIQ>dliW3uY+Z}9U8GTeH}4d(?#XjVht55JDenpWwn^^-`c;A2qx zNX2mHy>~#sFIS8J5Dl#XSp^;KXCLVfz}kv1f80cN>hQN-4$Ua*9)N=g2l3eOOUEQx z=E*No1oz^pD#VX5N9`tRE$me1v06`iR_e96b0l~Jf@Bb5dnnpm142ot^U4Q{kow~} zy*WfHMK4{(tA^vA-_u?z9efuZuyw|n<~lQdJD9!2TG6kgt7zd&NaZTWAK)LkYFT&0 zVP9NX?&@7r{KZ*Jk;kx)LBy_)mFa20hMl5`rl)?3d%P>x-Bzx)5f6;<+C`Ax?#jyd zgXQJZIHLw+OqFa<-H*ayqJR|+OZzaCTG`k}!J~Ixd-IFRxsHvOnHrfHB}eo~COxBU z$2N$k>|3`%!DCudWB+1;_SB6Z3%d2qQ{LR6V7=k3Eb)-a382DO4m&mMU@0Vulh~p& z8CknJwK%%|X$<$VcM!dc_glOInRg+hXu>|9Dn%A?P|F|UQY z6-@R_ox>CBQ;y6=5rSSq5lzMJgL|Vhv^CJlsaUcug%$IwmxG45$b`3#Q&`U2ah%B~ zKI#GZ2tvSP45X9=zP2&tgF?@WkA>eU&Mu%zB6RymXO#hPeX-Ldq_*KGE1#cdhE#xW znB*b&0w1Kxk_{Dz|EsDAc-YMH~Y(G)l8?aut$5+<#kUV|6g{ zz(Z?3kQONM*JL2-32Ux>mbB|GT~PMGiECl;OHDG`?riS8k?(dZsr$S247<#)j|v&rUfs8;CS%+X{(t__bsYy1(~lS80Ej4KyacKCKKErK92w zT}&4h^tx>=KWhOtfhMT}=Gtcty3Usc-S$Z!E?fmQt@|0!^vOUe$x=`Zfw5be_ht#J z>CEk}T6xIDT{pB4zKKNK78PzF)JN_I+)wIqRW{*&n{}*qCO{LmVXNerA$&n&Tg{l# zrp^oC6?U(?Z6_4~e1s=K0j<#QWC-vXv*w`42>@T#@aQguzF)GL-1r@nyncx5mA(?u0A6jw3NmsGv_8QaW@E)mVuqX?co)!;9@_;b7J5|! zH1TlcCWjhu_eYo(5dIr?i{N`69!HUnE3Q%F9yK2~%0Esqw}?ThaKPDQ4D4aJrxiG+N;hs z+k?7<0`>(=lk#^(11{uYDKS&bJ0GXQ%X!udyWo2}U7t_EBi}CUHal+{vRXt&)I|K^ zNdpga2lA!m@ky%r3Ib1MfHw;Qv-6AV3@v+*W}7T~3Fj9gAbOsu5Gd-h*V~3;+p+Yn zdrMqB(!yfql)o$LdiX6;_E@GCSS#w1v4W$k=Wi+;SeduE#_mnfVnMvad`F0{S%MqMv99TU*4p>|~yID8% z$Q6;2EOul5+vOD>eX6Ha+#vN~U37Fv&MEcj4^8KI`NrQyN$zgOji4<-~<4x+pq ze~CIpXI4abpnGlHr`fCw0gn`VGYL0o9t*Efd|KS+#u|)^qt$+wZ}DJL!aIZe9(tjOZrRemS*`{XVhN_&1}bfJ@u`2TyR=^Pj%f-7ZM#} z@?qI?HC>lRUHxHP-;*f9U*vd?WP9)0Vlda7!d^I6N>oqt6W&YEU&kZ3HcWYQ!zR~w zUPHK!Cwilg%EWc)KBEXRoIfkoWKw*ge9LHG;B>uYaA}%2R(_h^N^%Mv zs^SSvUU{(VTB&FX7ViHQ?yYZX^Gu3!htRRpLvh*B>oVlMl9_wGA5$1a)o2+l962UH z!C2I1(yj74s7`vtf93VwXuKRbJZ^`6bsRx5FgbT)I62qCT^JXP{UjhWkNT7gs^e7p zoJLm5xSG8`%R$a?kai-r1F|oU!4$Aq6QAwXo7Yws8pIT5zSS);_u*=uwQQI*r}!S zG8&P8FfrO;1Y<&;uW#~n$?w@1XgR!fYXffUur;hEl{bhIk~pg)Ka)4#S5(e(>HTf3 zavD=6HF7WhRo1de=~%TxUJ}uTnlx>xhg{q9fMZoaYS(zmSi~3Vm$5hdXeOry_qoGs zHGm4|@&ytNTa)GC=rl8Tn15B64-=Il(UT~L`!82gzP!DmNu}=lhmuvY+zYVbg%9?- z0?k<^sZPZj2I+=FZeQZ}L#p;)FPN>cjbbrQXsuK?a|!165xT|jrHG3? zl4)d|4f8wz6;#W`Li^PKUGLQ0Ap$GKHQm2#axi)$CEyKbuNtx~zPO(Q7%gCF)5lGJ zMzQ^72@}$tZ=%&*e%v&_`5C-uwmnt*&`9@9;?7(g=Rp%y&UD^~=*v=TSx2BJD^a3K zkZ~Ox@Y!0ICii06dD;c6gQVC=OBUPRYHw; zziavohbsTGPdfc4ZT81*U9hY}o7M|LU*GEJJx_BF>zl*r@_|?wAlzmR4z;Tfb=Gcp6OWwlYQ9B!^xk)e<~A3A^Md1`0!pR^hV2!I`xfAwI&O?A zcPy*PJx?l9kH*y**6b9(!qsuM;XeAcS@lfTDSHcC@paJNOG_Ckkb7U1O>GJ1JO&OU zC&bUxa_{;}D_}3Zb7AtrJ0!09)UaUxG1ze_p=!A4{i4&7dK!_s1Y35Qss1ug00h@@ z?RQwy80;oKj;1m*w|zEVk?K8`1~lp;A{Tj5`t_D(psf+GbE7Er=pw!ti2$AqNm)mn zS@U!3^?+4(u=q~VM`F;t=9)U0ILBK-u7m@O^&D|lAU4(ZGm4nA(B^=ES~ojB zOP^r0UaUK9`as6&c^W|Ra23e(kB4tqLe0XUVBP+Z(*# z12`rmEvZM)ZG=TH&&!7 z>3AnRh3ox=>amj9Z+w$!^P32qtK|Lr2i3R&dp?_q%YLoG(}bE3u8MHEvDKi#rCRL|0ADa@Egtr`PH#PF@_e9XDs#^UNgO9m| zZLm8*ZAL!*>h}E3j!&LYX(kel<>s}#u?sJt5y>v3L;`FhOh%C3^ZRBwOzWCSUp@^d zR1E$$=Q(H{EcnxA)7GG+5x$+7ZEFKZ$i&!dWSUQ((LZw#?CA2FSXVwUv65BBg$v0@ z7OWq}QDRBWo2E~#eb_ODBCi`U`&%%)EG)Nr8sh+DO1#$Oe~nnM=8gkbUV7a4q&74l z3B;8AuIv(xO~T4NH|!{B7dL007z zcPPt!Uj)8J1XY`cPPHd2ALzz^c2aVXY(mqNbPDY5vezE0CPBOxB8CxT=7FEP%?KI( zRtj|U)g_Fdlsb@srzMt&yzmuavw&4MuQS~z4uU*+QoAe)C1EoY{5hsRG5f9;@D{u+ zvV0eMBz)l{Yjjfl?Bhe5O|f&x?Y$yMU^VMafWX1bMxgk(GR2~E@uOFW&@pz5fW-M0 zno0u}lyJ%ER%jsdI?!Jne@=d($?OP8Rd@+R5Cge87)%rh{o zap8j|hYkVk{MV3Cwd~*pj&rFY_5BsEP3Bij z(PYK;oV9aWD9f_yh6&et=+3u-9Up_xHYi_VQzf|bL<~E6uxfY9M`1H}SGZOeOQb}( zGTIo~?*D?Zu8-=W6`Hm1f0oER%md}>^xe_-jiOsvWf>eKk3EjsCU41>Z|gDNUYd&d zx@R<%pJe3N$}7Yks-6X$mbvdDR`g$K;~*lAFh?M&6WK|UR!?AOz|HpA=R+uzLfqw< z9PRwep!z*%ZJ$ZRD zt9K;UT{F%vP_@HyzNhvk$(6IY;a^^6)Egdxuh08tf3Wxi7PG&I2YA+YY~x4RJwfnr z*i0kkSm%5c^Gp?h4~=BN_51#PM)dxyEUbHf(hl2w_Ss7N$lBty8U6bx4sv zZBoYia%&l+vuA*PphStx$?zCvXl>}g(V!#nmq&^pc6ZL$o3!S7YR7(b^|~xbiyed3cj!P$T+nxu^vEgX9AWARzD0bzQXS zA)%BRND%m!3P|yuZRDr1+Tlo6c9Z&**QS1&|D(RexCEBm^*JtLF=6`eA){$&cEv1o z>K&-=AR$Tb!&T+vypGG#tW&e7c#*7!#qUb>1r7YQA>JbdY>_7I=DXge%!lBjJaEO% zWY0t2!-U=43(0w0|4WD#8H!#B=OglM!CxjiE8Ro{S!a#cH-fs{>g+mMLBeMSyK!pa zzdzI!Mi)Cx(<7yOX-mRqN#Sbk`!U*2o6SO1&u(aEW0L-QXta6`@37zq$v);>! zK2HTcLHnYf-sDX3%JU}`0w!CvK1>MfQ^cF4YAb~p>Tr`fT_pWLP&aJ)cl!Pc=28m< zTB=Jryk~B~iF;+dc*}2W8oFNKx~sJS-K$0CFb4f5*Io@9dvwbgAw)bsn7Mp9$6ibj za7RhN2P=M5wC(aAK)agFxjL{U*Cl($Y?AlqvN`J=JJ9Nowg3Uo&^vVk`Tjb2>%gv> zo6cO=-yZ8zf9m*#@m!i=je~Gmxbx^|fI}%^O1H;kyb32#UW#~~UA zXIR@joR!pFZ|R*_wM&+pDgz%CqBXD}#Pg!s3fQQ+udlBq0W}WD=Y-48r@@H$qvHWb}-f=wUPXSr|MP3yX~A;n^cxE4ek9uDRUu-Pw?^sG4~G*>G{YdfdC z8$N{;lsyI!B4V7X^{juKd7M7huO&SF284lILjvc$-uKulIw4J9HpeGM5eWz-$g1W{ zbG2M%{UVj>dtgl7dkh>{F@l;?l%@gcFCI0q(4o-%LsJsUV;QF*1kZXq_v|Y#S;76r z@h&{#;?(2lAo`T61`EE06)Bmm$3pEIwBo;^-efPd4_L`EZm1&&_Un#!i4uV6qp!ed zouQDQzz0|qby+kL3RYf$#AV6>U(s6%nTMPyWfkcOk`@xfXbNU1J%)E z_jdO_Jh#>7Swx_}vLwjqQlv5dI8ATiAhnfve$;}>MOl@4ayow-!4)wZwirDMxLr@ZS#<(u;EPljnaD+&J!xj%`}8K9wmW$Kx^uUjY)i^1ixP_Z>%F>pR5pxVlf({EDpAxo+bQI@~1bQWu|?s4!$Q|K|FORd^&8a--` zz@N?5!DGpX3;9Yb$z83ZrpFJ)Xj!!UPJ>8nJrcq*%V85I0jPjIFv?PI{_PX@~U-H7h_#wsWXcM~U-bn^SjIr`_G7~xW|*b$lE z=g7y!xtOuKp+G_EYdu90_)Nax=>9)i`p&T_z##75M9Z^qPuCIHN`uSc7GJ6KaxDF; zGCAE=FX7hj+vs5eFqgPu)EWa>ZMCu}$&YO;=RH3K%ZSaC>_U0Rmv~DHMO0D!=jbh$ zl8nE6xc*9K+so|yQjI_Xa{_Q#tvFiTmuH}!onn6L9)UN5u8>1+-vH7=LE&^=-pFsJ z(^)f5+? z@jd6GL@qAApIR|UnsbeQb!QS;$t)ibVE^Ov4MqL{lg(poOT{Sh>qt6zm;ai!xUI`| z^za~1`RX>5@W{*NEy=^k+tQ@y7Q#!5bIJ6)eyqZ!QRdxJLM% zn>B*$Q8rhP#akt|$9COgaXwByU<|3tUpy{;b1MA~ySNooC_cQ}a9GKiJ+Vo6xrE z+^d0)qMz;>wGXJKEM4r;P2g)6I@-;h?4MVDX?imkU)m@Dts6>Rs#M7`9ggwLR)zdy za*qC&=P368xT41lNv1x8W1YURCCDTUsASzQAlIV91ItkTyG+Jr%7D@1Cy$#NNT1V> z0Y>eEEm&lQ#`cJ0sLIFcg2+!PM6h3Cy-39KgYzv~YBC)j`<{tU@mcvDnc zym}gag|Vpb_cd2lU^L2|uq|hSiA<8C8%M)$=J%{(iL~$|&R12Lmuw+6IX)!;L9uT+ zKs2APw#+q_d}=Yw{aGe>deIaCeflsnie|c?D~#Z?E}XvC{VVt%(QqYv?#zn{68iac zw?oBe&KdFiw=ifS-IwaTpbIMZDd{&hda89e@^p_+PXCB2IlXgL7vIThdvp0=eO$k; zjdS&bF@6%RlI0#lQ;Vk0iRtzepK&z5BZD^zC6DhkS?1Yj`&v8>uI%Pke3>rX#Bkr# z%F#WxIV%oJ`=Wd9H zLw2NrP4|CHUwlND?LYdMY98Z>{Kt}6me~jXw-2g{0qE}9W|JQ+;VXHzv)rQdlIy8S z3A_HT_y6i`Gq_RR`F{q^nz~>aF7uj%H~Bs8&?dN4KK!bv2uiJS(Iyw2LaFWJQ00eb zy-tb-{nY=Vn7ls1$XbWvyt}{ATFaq(x2E#S1Y@CVy`Yzj*8Aq7{i;uUHy#r&_SfSu zx+2*xP65*pw(}=gH8U7+CAZ}tO&E~}pX>5Q40))7K3Zb8EE(_VfVdK4#hW9|`1G#D zh&b{>0uNN!>CRoUpc!UeugT_*bL)LI=EwaC+#*^$%=$Ew+ZY6rE@anQ>E-3Y4 zTO=_6@QjNzm%Gs%k_crOt(Vx42lDZJ#xnF`zD(4cT4`f7{3(h6E7KC(~ zq*HM7g9taR-WjKj6p(W@8*s3tu(YbTy`{OtjFVDf_@xU1U<42;?w95xhn$cAmU~cs z{kx{cgMnr((_+RgnX|szhMrmA_Cw+gI2`fr@_=~zYVx6TKc)me?e7&TiomrfM2T;Qq zxS^fdA-$n(Cwxeb~XksL3=S^9J!cEq*2I2)f`F6LMXSC^uD;}pyWY@3P-K7|o_n_Bxi$~WPB z^2MMPiJW7lb?-Y<0PgR;oiz~S__BK*eG#Vys4%{mokA8iA#*Dq;U7%qYd$`(zt9l^ zU;i;^V29BTQ@}?P9tBQ-j!R!w06e9#R)#uYrLWLUN3BX9ue^Jn{skzqN?jhATZV3} zc(_B_cN$6y6oos55UlSR#clhO6@G5V`S#l~I622&a~7F_h+yt-Aqs51_F~l%KL`z2J;T+oN=LD0_L9vz@xC>8KBv9jXo)mb<^0>{TP#ZK#xL{b)CsYeF%{zhKE^uWUb<`|l6Q zw1EM0Z||%hbr$XRbFC5cyR&0}^@yJ-tv4DX$Q+M_WxGH9KWST|V#dx+hO?N z+A?VnoJO#W7p!r2Yy!Gi&2eZ8jo2L9>`VA z$#})?qq8hJH4L5x=*5;G%T)z;Gz1V$gz-8ZY4#Nt4N62t0?4^~F9BrxAHgdg>cS(r zuff*vm%VoTrsWC(NTDmIRcuYgsMaP5jYvT>)K>vyc-Nzo39w-m*c(pdnd688dwNPh zju9*U)-kLHhxVyvzS58qF<|Bn<>BkP{iFUf_X9PYW`%fneTQL$R6g5@PUf@Uzw{rP z|GaGej;|dsoPRta=Sy7}OJHH=5l-yX`D`ORaCqR2W)ve!MAqx^UtCNCAGr1vP?6^? zJdS%V8bJR{;kzUEV6tzw-5~8qIf){P!LahQ_2bul;B2JtAd|sR#(i@-F!SFJ6v7DN zOFEguf^BDiTY-tQ;4zPK8LEpOstfr1T2B}izkGG)5dx@cFLvF(O$B>pFJj%huB_%-D$!eFU5dZa*Wp8KwH}5wzS+d2LE{pvP^K*OG>`U1->SD!^13Cqa7d5!C9%6>i2pKPW3-=hL<5yELVxzfUpVfiD^*b0eV zqV*qnKZcn{J@R^nx!~r~Vsb{Ce(ReeUJ(Di(`$?AF!d(Jc}pOziV{wBXU>gElhZf| zp5&_qeli|aN(z?m(OiBjM*p%{@gj@#0%c#$M+Jnwg_@R(#utn)(8;$<(1CW+-Y+my zdLrdQ8rLKfMYWGFod$D%>fepu$(rKWeao4{LD`-=Q$;4ZHshl7=a1K=|F+Axz%sY= z5!`V)RJ-F&`yQ6X9&BfM*@0640qVaVztcMW_Wx_B@xKko{=_tXir-Uv92}4+F6H>=)_-?baudE)sV(;h9R*ESW zwM?AD7eRI{#ut{;^7?dSze7xnYkLhyoeuS6*q#<^bFTekC}; zJ8}4A$qJU)Y=36b3<7?~V`8}Jl^C@Ab7o=9+YZt`mzG2|W=^W`db{H!fLICAx04e< zWU%7+Ood)Pn_UB1N-HrJb7*m`je*A>qe4CjCn(HO55KD1m>&ZmEe>+$>yT!oI1ev9 z3u*!QZ7!99N-qhpT-YbLxs1BwX~H>rMcA>ZE=Wzh^HriQd!C34V-aAoMQBLHK7R0q zN;ZoG_lnAO*I8Hgk~kY_=E+$25R4@2Jz-bE@BLf*yWRefF+xQ~pWV9=k4uw|UVyM6 zrH+ggy<$}8(g_NhNh(3=>lJ|IO}`A%%6+s`N+C3n4F6Q{PR5$Xmdk>V8&f;=>pzEF zxnMClu!`gDKPSiFr z#qb1V{1uO}_$sg#t-1<6(2Gg&sT$N-_mYw=oV^IZnQPk&l? zKbTKFZL&;~aF)}5>?S|oqC0K1kuN38;$!%|n6|`1x$5da!<3Z5H}_{lhfIn~8>8Du zWPD|5#(z_HtKb>N`D@2R)fekoC@@_VzS{140!D$__psg#Ggd!haNgxUjfFDrwB_Scm_*2ZICW`%&yF_nEjS`-ywrgZ)B=jD6HDDxw5ZnQ2 zaE(9Cj)A3ARzx6L75EwBFj?0N^_ePg5!E=XULPoYOFIr%tSZZV;K}98e9#!_aRfcm zj22z8v>^Mf1wP&6ws=`vL4O&OrK`|}zQ$z1Gb$Ro;Un9Mz{REJZCX{a3K7jF^~kJHXI!fdh$$vqs%&`KpsEKm+g;!>vS-)@)I5MQ*|v0 z;Z$xF|yYlAyav$JBW-I?jJq7lD zC9*y(vLasRm!+56ymDc6k^X_*nVKr^`d#jlEN)3+`HPant9V_LdB4qPv3Jv8iF&yD z`(dQCko;)+s_`R5BY}xXi#6IzPG5ecuBIvoV@K1_)%Rskf2ybte!%nmcyOg!A#lUg z)f9F5{-b)HD>cbt!7D;H&MO;K3V2ND8%lLFRRvaiJq{B%4pr@2Bx=hj;kEp^Q3ZZ4 zhK=%}pD!{V;?{UF9!v)#_7R&A5rClwP)`FAufhd4K1`au*ZjxmA3?&x*MnuRgzyZy z6G_2AOn_*8&DeD#b1FtE!B7&bG^m4~uDX%qN;V@IW#l!{Pp+raD!rHbns?yp)@nf? zsbbjtc~HmiJaN_k!2&2b%nuJ_L{?eO#GUq#p^~z5%G8uyW0!Hl1qb)pQ!BaZmT0P3#2z)zkWP_QNjU#lmCB+ddsM&-Y;xe zL_r#)8|g-*b0m}&5ou)T?nYvy1(oiU?(Q5yTDrTXWk6yWX6C&7{_pdwXRY($taZNJ z`#$%z*WNzaT~VOfsnWa(S_qbFQ6o8Nl09o^2y6IroAh8?*3n{J1G+EAcS5z z{Asf7pb7DOD9y)_yA9ldg%9w?V#o7JwMIR_dFrVLer_d~tfQnk>L1?VbiI0+J`A9H7Q*(Hvg#*t&ChXs}m=s4C!nEe9y|KXcbEP)2k)9EjW?Md4o^gW~mm6wX* za8xwYJhpytuiF2^h?Qz8fqqySF=2r=RaPk-p@kk*8S@X^f8WGitoNa#`T4`Kf?PEcX+we^=kg($lc3l?igRbunkPmDVYn? zJ}kKCSuvi$C+T8`0*D4M+ki#O*ZRL23NbhR8-$-Wj$Ew-WnAkupdA-B@gop!nbT_! z;al)Hs>CbezZHk0@-k7_w8YThQ;-=)AJKz@rEPgHjITi)mHqLd>Z7{*nDP>YuZ0{H zhap{i+UrL12VZA3yirZZUjONGV($FBr$KtDsY6MDOsWMLG*gxLR&dsep^o10?rmt5 z@r@!$(((IK)pEK)o&qb%%#h)0;cyy+!AEu{Jh4lkfmcy!XywSb2Y^@eJbyb26w+6- zKYz$09~^8f$^RVeIHKQrOj=L)q{bRGvJy4&;g-<*N2&P(NbaNf*aIG<2B8XshUOwC zE=CTkUsu*3zTo(m8TUljBX`{xkM9_5AXMuAi+81IQ6EXk z26?`}gHr{-;~68W#Y6GnOUrM>FL6@&XVmEAYF=4dA$kTib>hZ8h0J3v_m>jxH zZni&V^}xm{NXqPY&tXDXaFm)AKl7`6S8w*aZ>k6W=kAZl{~xBWKyUG}o_xx@kuUTB>4sV*yyj!d(_#%|oA=+*VAHMXO1c&sL&_{8KZ zPvPISzOqOF1r(;5I$`F9qkYVf@t)adSoeMKG5j#$P!bz6 zqyZIV*8@I(3sQt?IKUy%MbuRkuF~6`kLw!$;Y5-`ai;IcW{H)$TR-#s;gEekdJoU^ zL{?W+G|*D2kODLNaT}xGCxn7;pXy02s-25hJZ>=M;kb&?ht`dR{~{sQtdVwSu;kxGLSffR6X9H_82dGvaOu6KrVfc%Q+6W0=B zB?shH?$diCTL>%mp<*72?+S#A5;!%_1ETmIjEfKNgZKO6hF_#w246J)P|$c)iK#;c z+BIzf%$%fxVzw%<+fdDE1c_0ps$8B zP!8^?Kdjj9eYSEJ!ba)?T8X}O+x}P~*+NV&f7KrRS}{Rf-qZ79HA|D?*#rh zr0&m;Dx-Z_D@J44;tlQ$ODNO{&_%lZI7Q86~vT|l_G7T56&|i@|8CH;y{BAvZ#0d?3(;M z43WV??Ix@pu5MCJkawfl8ep!&c`VH|M-;#{=R^Mq2Kw0`f!aK5>CFxO$BhVspo7 zCp}ObSMdni1F`HwN5+8h$Bvc~zyW0)){OC48abzBl;kxsz@W=13M+Ko{ zvtJE1NT0OtU(_g`e$5|OGl`E-f}({+c(C*ExR(X-RO1V69_9Z+(?+T81TrirR{m%G z6Qvg{!k_tv8CT4Bxemd-Xx8%J*D(EB=+0vTndXKmG}#I`DtLKJ9_Z&&EanOqJd748 zLst#S13Wh`8~yXAfCNS2SMmi14>q6T@N!*2aZxpjB2y$nMfhLwP_fqc;8e{@{8Vmh zbK@OmR>%SeMR87O%PMO#`%3QEr3YPF(UWWitw|{;F>1Bh$;bCTwL#-AIPm)J<)4&z zoDHxess#1cjYM0L9}xpP+oJ7TRM<+ts=OJ6r#+80+~;|`BPCh$8q4&;TXCZ|9wv`tBLi%_u11Nsji6kc^cITn`^N!-`8|*{(@U*5=mSvJ zl~0v*U*X;;b;9DPw1XNSeBLIQBqgw$+i|r{NgcQx-fU}_H~aU8-s~=V-t7M7Q(k-f z;4sMJM!mG6%0svDF>28QqF1HQC<|_u%&IBlX@r=tEI7bl-GC;tWN7VA_XBeKs(YP-vhlX+_q zC;2pxeD7RO4_zu>p~+xwbqRlZ4SJVdt!-31z%NFN4j*-G+u>_DA8y^stXhPFE{BjL zQA995aMCRd6`qTcUZcUE}ms*hpp?WyM4GL@MkyE-5{Mj{y^-aut55;l@|Lxu_kQN zvWa{wVf;jqq3a^Gn5^=5{m-+Pox6?>`O-MfoSn7NYRp1M$yjOUlSYN9TW3!PIxAIA zR*KZ9WCL8Oj1504Y|(BYIA`umdJf{WhSiN(J1-7s6@jLnn(}g7JXr*<^hGkZg32Pl zsF2IPF(y#+G{|8A;?VP;8t$s!Wo==**{$GfzY?m)2C2dTu}lJ?Fkq^e#cV!4sZ7MNA{P`(>jga2x-b*7A*j@a-bWR`%8OsQ-UN7&F}-f*^>0olY;Qvh-Q0>?psNeyL+hjS#hoD^w6NTsrg>5iW~-f18gwhsRnCI2Kq2s+G6%xE=05fn>r$^2 za9{*Z*P1vSxK0Y!g9+{_2?{Qx^abFf<+^boHFd;vT*ZQIbequ||M41FU_iUj6h)yIt;uMZ2(-@Ycs+!pRjNp_f<1S#3*wyTeBfATE0Dxa{QW{XGFJWHSF zKHRx((qo7e9Kq$}y4EB~iL{D9$Yu4CJ7>21t+(-5zqIUP+sHJ)9ZRXgMcv;L&gSM) ze7<^Ud@>{o3Mr*y=1+@gc}-h=!6v@Wgkbra(gzA`=kC1t=F@+qUD6*ls=a*zhR7xj zW7zKZX!m#RBcaccyWEiT4OCu1)>~5c7?tBUVDKw+ZaigRg=MKmX7hF#2LqW(l>$_V zTmpGH2P!od0C-}v@jGslE3{6C*$ul?!?iCbzdxZ*YIgdzzxMfA9GK<~JNcvtl;zKMbTAfs@dBx;fAMZ|L5yHxiL@tl>7r1!%*;Ttj5ihs$05zE{)!80FBE z-pytwz0yi)>Ii&>pH?dc30ZqyMw>~fSf-gvC80$cB#4Xlt{15}m*U4!u35vd@bL$Y zAn$L#^8#0uhL@@PS1TPg%<9B7N5boz_YmYSoaGNk#!5$P@VXuQy^02S@clWCl+QVl z!-5Nr&$?eG-I%9<;RHlYHGQxn1m!W&yIYY6{d464@q6<+iyxe@b}B>6{w^WLL{UKI1m$FR?^5|;SAo&=8)Bzx>OmHL-T2H!sSxNLJ)IOh)Z6Aw(7rf|eQr(79BUC}rfIbkUSAP>wUooxlh9a1!r5v$G+)5YK@B ziG5@)Uc4i(~*^wOMHCyT2lq=CAtDlv=|_?|H_bTT9+{(;``zco-g zNeA7Z*ITsAy2hb$E?7N3ilO^3&Fo5Cm+mUI_>%Iqr2CU+iC`S`z%*2?q9i3C zWWq}=%SbE{$fwP8dU3y=h1LRcYxH?CKQb1pGnA~2=GlOfGjsLofq1~+u7d*3-|LJ$ zOoYVT$KDT%Eo$YG<}J;aOku&98ys1BuiFbjjvylRc0 z#oOK;p5_OGkuKFJ5j2&OZ+rU9rkO}c&1hX6Hu;MV)lXJa+H3Qdz_*{!ogT9v@k^;B zeHAsOd{lyp<~C2b2JKe5Jiz*miYR6Y>rU`qSaTt&qaFznJlkwqf;!^aH#w3Aid^-p zg){-wHyhFjg4XT#HS^2Y+mpa*G3)@;$wJnoLm(oA{(dtw)+&m~Mued$en5f?(6bR1 z+b$bR)T;MB!Uwv#z15WfI9F8tF5cm^LIK=y=DE~<|Irq_CfbR7;?7TXHH81+St#P( z{Hu%fCyIC3ZA?m*_pmLpDp&3HlPVx(3Jh)5FY<=LW?4jDfgpglr z{yjMWNs~0IuzZ`1w&|XIfq*PgM}Fe?v3$!)0yUa%d(%dSYZ`~4ugJ_ynQdJ75v1_PoeC|E$ggC=1ATSMvE5H$W=8{VgdCzx@p_}qxN`MZrvxP zRR^O*3O7Zxa~GI4@KG+$82!nwp;p%dA*GIZ0pL(1=5+Lz&yB-plNzBeoR~(}Sv_KR zU0gSdJtjh^)qP$W_r{s#x)>S}`Eacb${NVQUS~*Zwv-Qk|NfF{3kVZ&jq@-!>Z#K> zMuX#0i1r_+u`!K7ld=R0!5O^9(n#E$&zjEaeAZ-NG)`mWiW#B1E6>0cE1>chM1a%l z;`WP--i`9l?pHOym!R#%ZbU%+kFq8N$OI7kfL7mQb-B9-i&1pnl5acYJLqkJuGgTD zT-c8qnba$&GOy80#u-=$=Hh#wIIL5RhEShuwh;k!vE?AtCm#U7`w19(H63}Cyrb{` zy3IADJBkjXUID;f_upcS;PR`GRKsJB^=9quv#0+36`J^JrY2QI%Dg*r+!UVn4(A zHu7*uW=cpX(`$?gsVvPuzRwd#Ud#|v((7KxDAzB(VjdSeH)jI5el0MT4{&*@v(3+q zW&)WOYx+|6Dl$JyNL>oPcs6M>8M2)YC+`q@RyM=*ezfAlUG3(Zl@@zYLY2#w?*V;c zAA?iDN{JJFhj7P(inLEfARn&HWEKuK5k0}#-YIq2{=k8QLsL}r@zx) ziA-T)6Fw8N38Z?bdyFz_=wE!r(^?$^w81p(-YiYCa5VmcH@?v2P&)J`jG{oVi&V^m zG5bAHi{Ns~$=LAF$k_1qe@%Iry4khYC9T;XSqFfMJgAAudBIR@WIuLn1l|f*<|lab z15;Qi@1PhydAYV5;&TYQa782U zrn-#8M&@d#OoY<{xLsuLpuGA+I-8MKKIniSRjt$#`M)ozRW+s{@WVP+^$HS+({{DR zcV<5=3te(eW#Fwu%zb1btPJ|#-4Lc0IV?#@p7N-P|92OXQ`3+k>HZF2t ziHPui)nkh>poGF6PTm7u0;N~5hb!x<_M7$fbiZuZK!Y@_msUcY*~k^XCAf@rK+BiT^lmKBI@*Fg>iHmda@1aI<3BtuMbeEU(>8#gS=)Ouf!qRez(lAUpP3J6-S!s zy4|;5Yq828ljp%UBXL2iZrV1(0$9p^{wSHSB;+}}Cnx=`^u6IseAL!W$=|*Q$~TgV z*_(m0{+=1xJTTb$uX4`bZv_ZH(iYIIS~G=3q!Nf4;T8tK;tO_{z(-V!Ry|NwegO;B--V-n1eCSm^94!MZ5~Sb;emhCinwiak_YAEi z^Gw-&Kjn(@RIzm#6O0>EWwzCaI3YHLu^YvWO?*6b;!ri$STS4eET9L(dnX&Ct~)F0 zNm#TXdNn!)QC%I5WhnDBezLbSpe8x&8w-;^Xs6y(!AA-fL_`vW6P*nX{*db5&tEI$AC8m-Rv6@)QFhU2TL}MQ-154;OC1l3<>X59{P?&laSGn% zO8d8jj)LvlQ~5~mDEzWdrmIMj=~WXa`(;?(AdoT&)=6l|cKrLbKJPJRw<+Jx&yQri zS3Ff}9p*PO2^vME>t^H1Ud!So9I%wLTxQLh&3u|?Ao>}DC`IYk=~X~HaZJ(%V9g-32sHb@e$I=V-pk(-%xQciV|W? z2o7VBnk|qcgilx&dN$+PDrD;?;dnLSS<`r%&E#Ln4$m)5d2Kf}<;Ru1{0ojC?~I$< z+S9XO%t(3Kqn}mf*r|o+f4wyw?`@oBNtRi!aAj{xH0oAd=#1-SSRgscms%@eh8mbR7w&@ZGrHqC6Kc_>M>zPvR!l`}Yd*kwfal@mSgV z8H<5(Kei^Jui$+?rBVz%>8ByDyD-F4`=PhE-mA=xyuaToA3qFeI7VBB%3;~rTS-m5 zaNKygT=`r>Ha~N`1!@4F5J4>{7y|EdBuPh`UR`GqDxP!7UMIz*sxDHu5@+N?%g{td zuVcTaM(X&S6;2k}Z{C+dttz)zu!GvsMk|1WA5h?j2nH7gU7%43=N=+?3Pav2lFKak z2>})$W>h0($-9qMr!Ei5+>OvIJdsjQQ`0eK(F_Y&Tc8r$=u|VkR^YF95geqYfOa5B zJH9L(v7`4^ZXd9(HE%kydnh``CBkAjKI`cD%bi6HawZQcEXTcA04q+KUU2>}JIpNH zKTq4cE<9y+S~}FS`f+vs2oYGV>%Kgn)rV!w7#|dq#z`4#t1a2+Liss8YZ$9^Tw@h# zjH;1=2%gTwvtC6-+VP5fv?yytidX@`ndq@a;Ac&M`o7^NB|YyM?dRL$_`OqYAxHJ~^a90WIBYQFUp z*AC=Lx;Y~&jyYkIQp5^Z1STuSja$ybn4|l?$kO*u1+Qo%T0p%2N|AC`9Lq-%I`t(P zeGV^3KIqR0)iFEoZNP#yPw2S?AAFI`?yC)!#3MUZ{5W}1j{G&~S9zwu#to>CrQ+@r2n?T?v#JYyj=Z_JpaW=Vj8~i*2-ZK~ zaSY+$`hI|jlQsIjBg4G{UMiDpN)0g;C=+77y3dEFz@SC2_&A;>5Ice1H1OjZc+(#{ zq;AFo?*~E|BcW8bndUtU7ySIu%sByJj@xXUtPeCGUdNZhUPSqt3CEFQ2`e;tR#Xmhq1wJU1YNCm8Xe1 zNn5K@4GgU0CMoZ(Scz~Xab6~;^?ZXx;?tN;@O^(7yh!eD^82%o+~Hc8cG^ucc3+kc z2$%8=Bfb09@*~b*$ZNZUPC!eX#ll70@WmVNzf+jEnJT#~v4!($^$l#T@pdu2*ix+Y{Pjd@7 z=H&uUs@fJF&6)+cArkY{>17{dE>oJMSrb;PZX=3yT?3QJjV0;Y29T4Okj1}&X1#qy zAxys&8MAk(8xE-1TI@4>{z<-EDZ|Y({&BK)OA*0+L2j7$6zy}mB(dgp4|m;^(XKHD z{I(jhZQp%h?-WgWUp(Q0}Akh$N(kcay$F6aRK2(Elw%u!Hb%=EVM*Up`9sReH}1DbJhY zjpBgYwFPCgW@(Nre9Iyf=P$cM$Uw82E}!i66$?WuG1We_bH~(o(U+Ny^ojC}oVcv- zCjE9Z*9zgncl6l;6RO}%E)B+B)q>-nN{N5?|LS(Qu8*UsuVZ7n;9PBz6^${^Z&<(P zhW2^va7-Abn!aI&5q(vS zy6SJZ*c}Qh$qCRetA}v~K<-|Q>_0y0pqY6hYgeib7kPsqlv^-+|JRnN3f(&rHNFIO z*oTC}()eg~y#8cuD<_BAV_W*ID43n{vE13kWIn|MdECvOH_!y+c`&JD9OUS&FA0oQ zV`QJK+YyvjJ_`H^p_lX~i!F&5N#r=zv$*fDI*ECyd8x-?qh*L@xAW%?-Q-1?&Uc~? z*2+U^$X;OL^L{VefmEZo7MZ9&jWj{C!_Ge*Ed|~x4?VI)l<6&#Rl;FNEH?GD=R-ShAY>nf0#^a_pC(F!$C_{7LP@x|N8)a%`nFv)}{9{M)gbY zpK+CJo(G&~lC69R!g6mE$%@5h9>fcr;+HNJMT#yPTUawgIJ_d(BIl;jxNOaHDk7#1 zR$`V81?XLIrzrIHRxaC}ZAI@f6dt|TIO{|;^8%#zS%qu_vrv3Z|QlqL4b+-^7v z_dc3ti~ls`Tjc=H)dZhn_Y3pN(bqYts>BUrr_s8tr4F^HE&(~)?j zl(y~)+H2lhb+q41-7Fp*icO@pNfGwW9o>!WOYGYZbRyVeZ*#ntA~Y_>p}bb;>6d4Q zERi3Rb9g?Sk0V|wy0{$O+_zsII39AF0qkG9%N|9U?xp#eFkJ6J_`BotF62z&c4}Xp zA*t4cz#_==xQv7GLu6N1^yCUOQF7F5BT{V+9M4-SJ%s&@y3co{Pw05epb^94KDKk3 zXj@%#7BXqsd@t8z(PBWG@Nm|BzdCx75HY5XXZ-scS#QAo4UvMJy=i=?yAY-4Z3u8d zrpYE?8Z~jS$|0HH8PQ62YyYk7QAE^+@jdPJq_Xm!z$K(%ViEbev$xmkW|P{?=$|d= zjyH9F6(Yf9v^@h2{&vq8&!3$85Hlu6)31XkCRf;2?P&=}5oH?eR5`jRB1V&9aBBY` zLE}@}uvYhD$O@Y9qY;e0>1l`8!nTsVKhD(Tub%G`8zxhmBD4giPrIi)CLEtY zdpu-7O=vgv95uKolFp%2xkKvNr+$R7kt0q@qri@)?s_kHd`Du+8yL|K0Ju zc`VLWVf&wTMMSq~&dj;>F_`Psm3BnqYeAD38Kb&4+lyc-29wMVJ&Tfdm<>?Nh1}0w zD+?$7t~GlzB&e~PD~#lGw?!SEi$?Uj%fHFNtTI~k-u400ljn5iqQCSXWc|+ly+P|e zDmd3+GXzTnP8hN`|5&)&N(UhV;FN${)9G;$tY@yZZQ2H- z1|$n$7yj5`AjJ3u^?ix58g}DXHGWhC?iB}D`b)R_^D_Y}MTD5c$qp2CLLl09xdE23 zH^6B2TO5J8psfp5wr{op^tRJB7&!3S9*&B*(rjx|g70qTlnT=)O&*ESS`3T)1(v14 zZ2hqVg+Sj?0XsW0l z_PyavN*qRjXgBSUGCGE5>N}dz5(6Nz!{F1Km7Qkb7f+;y>XOA+wUBL2dc#V3_32WOVj6X-Vvoy zXy#Fl6bzA6aKOG9ToZ67**&hhN)^6uKK&=6yHpx))y%EDbkagev7MUAIQ=@rh;IdJ z!jg$jDv0@XsoRLuH}&l0#<%juoD74LA}qfzlFRtbU!ZUF(A`27CZ#f94#RIw;XMVR03rS-+=srvHqEvxMdw$>u==p*sI28LpTDmMylAErl>*zM9E9JFd z65y6Wb7t8ruCiu?9e8uTDCM{qEt>V-b4DqarkIc_yW`JYE)n|aw7Utt1wuBxi-w6UL4luFAKe99`+2!LRB1)rnDtTTyiaYr&c%a# zBP`Al{D+zBw}F*YxnQ2sRs2?Op}e~JY{>}@f1QzGHZYGfa4sUT)KOy`^YEj1D6pwK z(jXGAFZki@>>Tj9lNIi}j5X75(^mx*n7E}}&Hfhf8uybHe{`evr}TW=P-RO|d^ay} zu#z`t&w`D~Cj4dMP+lwLlgxQMEi~7`wHT{*MwtSd`WFGstEoL=X@lf}*Hv>_PYmuh zxpO*&c0*lFDhT4Wb@ko&fB9LeHH>)DP`lR36VPqryl6r9mKbI;U=hqG1nO#SbH1_B zcYA))bO~3fnoBep5fukjXykr&_|yGP_%(L@$fmwi(df&!rY(+oD3!%64j%4%HPDoZ zH7{5cP`iL2G(!5Zm10Y7E!DE zdYCjI==wIg73!9`tmUa&-;0U#t?G6UzgWe~ZfK7e8cI@@-eC8C+XmBba2O7~(7&5d zvp%Y1;ZXRs+lczqX5ZypjMA4Zorf;TRblm$#5mXW5n^Ba$q4yoaSt`B#Ep=WCrN)% z;HI1T;@m|jcYq3Xf9!KIcxtO~x7ul?l+S}7u)5S1UHPSSnkjsq;a<4myRnsSU+D*K zk=+S^g4YGmq(2H3YLA zEKbQLsTQ}5SHJ!2rC6a`KIc-JLbuy!Y0kt4;nA&5oZV0Cy!6pOW50|H9OZyC;+>aP z$)%>*eHSH`>cQpLRcIuYUBWtuS|r&I5fdf6_wJ_2u}#K3Yaxtb5Fg$zEkgKIK5t7} zQ_gi8e*53v>ZNgIVg@2XxB3H!Afe%P|KeG(GPpWCkzk+mi{qh9ri$ zv2-zJiKMFW*j?k2>QvS5P6gN=k#u*(A;BM>LT^^^JtR%RZ3JZLMVnz=r4?4>H_j5e zN=6XYMRcA*WlRY=aG-5Pe!qhg`&^KoKSqf5?gs{3oG5LsgVz9a=~3t-D{Qk#wa>N*)P}-4`wJ4?%@aXvWs@5(EJa=ty;z-ly&lu>Ze$ikkNlTl*F%r4aMAT}a_T4h3PSd^{SOC~Y3cz8$ zBWqx>$#Au0AEJ=kf8h8!A$5RXK;i~~<*|tRg?9}%0pfjW0XB;m?#lO?j0~UZy9I92 zx@(S1n*k+5l%6sJGIGF|_rvQ#^wqPPAoO1bf6&=RusicL9-bZ@0Y{VJaRQ-9u6u>9 zj?sttn3;1~U-p1{!$7rxz=J!I6oE@Xh+Y}sfYixmN%;3p)X)gkv*I&MsXzrrRXz*y zeyYIS>S!dei1ZPICy9A0w!k)q$TPC$(wCyM^L5^70qC73C$rVRLK&t4gb$2BYZQ_r z7dkvjHuDl$1aG|2g|1M4Jzu|g^aVRU-e-xcP-zO{scdU``?~6Up&3R^H;v7 z4pfp>0+HVWYb%ebW1ZHQD+6YTNgHx$cknFC65i7oVG{ zLv8!7a+YP$((lV{3$C8X3f(>WJKq(_A!y){)rxNVCi(8SIN>x?ggJA7?Wa!LbV=!J znyjm7`^2HDehgddk^|d67MtC;>%*;vLVqoUEow3;pZJ__;L=;3k0pWqj<_<9gij0C zgvBcs9Pt@Xwhjafl*0H8ox6QDgL|ATIREen$T}IVyv~;R9xLnt{d`?q3s0OJZ<~43 zP(pC%6a69g9iS^R<5*KiZe*A@aLkfo`sWhTO7n}d1oT2_7Q1h|kpTBRO>+4d4?klc zg8M4Aax@RK{c8?&iOCNhR*uG*UX2>;`0uZoi31<5!~e7Rdr)J z|1@hS7I;E4>Jkw6nW~Zx7nXZw+YpB5rOtuLK?zT2&?SKP4UElExAGpmR`K00DoH#m z;8q$NqEFPF<2OB|Ci1A;a%>bFbVTjs6leTc6EoewUAorLv<*gA z_0UI_^+fVlWo>AV-qSJXV=aah83AKX>Ued&X}NQYx$W&}zdayd^64Nc!KK^MyG2}l z$+uHu_0EZ4iJ6q3zn5dbrILyFL7#yB#uclylvD`6&8x^1pLy?Ly)ge>roty*G_fu> z=YC}2r<}E5@d_RIuKy(l8B{HOqW7i2m$dlCO6vLYuuz)E*zIe6tTCaW5>J_{Wk9oM zldi1lBL3pwRIkJ_X!SdfSDs@0F~aI-zVw&DGZzZg8r`Z5&Mw31cRR{bU#JfHnCH1{ zuxt4u=B+$G;AAa}>G6!-sOV{IHW^0gf{%XfQ$pZ^{GI62n+4HF&BM@ z)yH&C{7-T-S?Nu zv2qWE!pPT|rm#$!bzFZJ7YOY&uGK@=J`b~Ti1&jN)a!Av&@3K!bE1{;RRBDZyj{e- zaCJuUfMNvgp)7iBufZs;ZOdJ2B@#9s?4-1$-~2|Z`R~mE1VLWcI4z(rd8tcHh!8PVR_cpJ=Ff`E}EF5$ZnNCU>B^ zwM!&@ksCKlX{qO8#%_gY#bU*!PO%>S-HEgS9l!mwzR6*sG)C5=qjwv|HxG?4+LD7R zlS*tb)SfM@$d4#8ItgM^BuWdBQiF3E!|~r&?I#d#IhwsH3zaO~ME~*3L_WuK4Ea;l zvf0E!u~4~6#^Aff@9|5N1E^SZe9y3~hRSqb4THfHeC$?BYY_P|K?)T;(`8=V|KgSY zil8wk?Tv1TY&c0RHW7p03~^>AchY%3pPbz~zH+OA-)y0L?0WDoE74WSpHz-@y!Y9v zUR!*Vx*XbzTqd`a_xyZ+&Xjoquu}Ch^-s8$;ln3c*;jd?D1rk@u8GlRXq8c6tbKy{ zc>DNvFZQ_#V(Qu~*E!c}j9OKxV=U$ERjjC`+>BU5ch*(tyygbc-P%=TFzVlAz?;Cg z*G@?#gDZ8hl`a-eUv&eg|70vNx8p9w#8mPpj?-{+;Edy9+&S3YXHKH47yKHAypk|# zi*3D#hddfxYJ#Ij`BRa*Jq738-s>r_LzY{KlPik!7kf9*@_8}DM}#^?porV`E2ogF z^q%s@h8h`m%1#ybnoxa%_?no@)NyI#BB70snt zacda7sd$pS$|}`#VtV6Oi}*(2l1n$mA30c3@rV?`B(MZaLP3;xxYC8cn4CZiRZ~IE zDKh*+4Y#{F^%iPBL%VmBtYbxT>bq+YZuLVN^AL}lN#Dd^#=fq^`v@Bb-sJuxQ(upb zZr8;^S+kI=w+8o^vst;YUQF*=S>!yelqDX2xSRV+)X2FyexScUxV#)851)pt6YIK2 zQU|@V(1ef56uG;%Ntwclf3Or2kXE9$$BD-JwTbZYG4DN78je2d*Pg~*gp-52I+|KO zXNi~FY@KJv-b6zE0+kM{`qzQ8bIp*)iO@z=LA8-Q=z{~YBwx{38&P9vB7G(H?0E01 z!dkw*2ihEtUVIy#%E)N#m|xkJs~ZMX&Rdf!+4r5CpAkTBuLVO6p0=uUz^c}yVQPc= zn^8Im)*HzammF5U;rayBA+WyhoJX9bop;RIBUK~+bb9n=98vz=5xK6lc>u;&zI_n6 z0}@+h-ys@UxjPIKe&QUz_?R(`a4`@hy*m^YQ+DWW9_ERaBRgmGgKxmD8kPE70&X4hDy%tsFAK4xw)*YUUS#};h2;gmV;%bfyNVqbjY)@AD67-v z^V*}8hM##gbE8;Jt+%uDoQ+kbEUmx*h z4N=@d4UJOuXPiRir+aV(huFeyB>OQUKZ!}Cf9cEPB2)b9=mi~vgZd|6}u8Ajl1+5EyGmK~mxSsA@@h4WcPW0X?ibUQ3M zCm?g?D|P)x&V?nKd*aOlaL^u;K?7JUzgMa^Ym4HMm@F;mrxUL^tmVT+l0ZshAjS{a z8NK?IkY>3)%~U+=S!(67Cu|2PViWOpHHch(8^U&qZ4aLHf9||2w3PfIC%Q_H4c)g$ zNsnyqDbs%HDHQ2xp~W!{&5pLUHD#G|0(G*#O<3Zcd=!IKbRDDKT#QNP$`jx59L?w; z(4|-m^p3CFO+p_Ly?>+*>`yKZL9(1HgTk8+av^=X_;2nbwcRqBW0_cB$$WBy_@U?tumG}Evq;UX0O`~v>P{7_?Bn5mj1%l!W+F_wmEYdPw*LNz#y*a;RO1<-#IQteS(h;SNf3@p5GRhd5Z+z#UuX1P9 zOw5pjnJLYnz+18<-O-(#qbQDW|7iYp~R*a`fB8O=Kncqf(CMaV|vx& zbI{#AE6`O>wmadirYp{|o+l1bIUfsA%##(uNTO=oG6V;^R_en~J5ath(J;m}+~RiS z7Znnl!e*XePL<;nbRa8{NA|}mAqMtyJH%p4wfW{S54-mbX27*Ty6ZRCQDz+?zlti1 zg=&c6sRF1%etCv`QtmlY!7yQbcqi=?d&Q9 zM!Y#gZuhaQ;hGalM`JQkLCW9(k5MBQzkcson*c5MiQ@`=Ae+sF&gBd0&Bmb+O=_5P z64l;QYFf#UF`svmnMQMqo&$N+WjB<@|CTqUir+P6qAQ&l1ADS0*R@>4FoIRA#Rxxy$QDvdrwv6HsJmh0rVZai9y2y<2NRL>V{T4W_6 z_T#Fu>VC<4$I{t-#3!A0GAn=VZCbv!M}GZjxg15_>&VeTy&rpq%o@i!N*>xK>BWh? zn%y9*yGemRku_LMu$98lB?v`oaL@?%U~~g0F=8p9J3N>w_w71t5Qi zuss=cs}SGM`T#B)ZAf+=`8Me-2UJD{Rkh>#7g_Z3ydnBK>giq$2(-4forcU5^Lg_2 zFXV2;yLVQ?$ihFYgz=|le2TzLLDV!&x$*w(vu?<}$)%6d*+@-628KdPygMn}c~=H4xgz!le2Q>h|^m zJ7l_U;QsK`eZW(RYI0g_$ive!K4o){<2^-iBMCd9P|oZ=*JH)20Vzs>RFfrDL)B>y zOV1=%9kCoOn+}c_xj;cDE2E%pMDp|22-=59>fGvQxwa|yGTA<`Spe;w27P_Mos6yY zApEwTLRR`LS%n3nXy>QD%_rSX}OHVQTrG<+?(`94~ z4g(TzWwb%j=-D9?jzMps&`URF8x>zALS`(%(kUrxlzy|nyxJiPiuX20{79Ep@lRju zmC>@U4B@7(`ZF}9JAZ9*9vkxDwc6%~u;E`FN4o3&9nqaC65{9nqwD zE;|Sn29{T44TP+p;Y7|{{#Csu3Lr$Y*UCgGoM^oNaH(`~h7G-~g8hF0pg>>0`@i?? zjKdKeT>c!l5pwcTR$Bg8QP#d=d&SqA<0Hx+TTbD2*{~s+Y42@)AuEp(l!%VqB+dL$ z{(*8F5Dvq)71mp)f*E>;Z<+Lgeie&$&1UEyXT2E^(LY$*S}B8>`u9IrXiL^({gY>s zX8XF^;K1QSKZkEp{w-P|$W{;MF<48Igbq$W4*Nx}SUKpSA^j14Uq!|vU>8whyA_B!B>8C{lfnnd`}&)FA4%>kg! zo%j{ugFmYxV~FstmH%w%01vhin=I$ZKNlSQ;CH_jzW@F2xT8~P8}Q$%SRX0*hesTs z4E>{_PlHF}MZ$mR$HB!whZ;G-0G6O)I9Q(twmq0*fk^#D`f(Txm!rmmFh@!F%YmOO zf2`7^5N4149VKMtkBbw4Gfd9;-mGQ;Ge9Ts+fawHjYL)Kmz}Vu_*$veSsq?iraa=0 z^5(*~^eOt4$kJu>A0;t5T>7){Nk&`@N|&yG<>OHEeDu>ckn_TYI@x;Zu~y1hIVz8~ zm9s~BvU&x433};#c4%q(J)T4kpL2O)V4F=o_*S3_g*2XrXp0qe;WVE2!6F;jMfx2d z>4@4Y=RoAYvgW1VarC0~%me(@-%)D=>+RrU>{Ntr@0E;gGsru1NBD|1bXifu0|PlO z60k@xGC<0!)r}*DiQg6)%HN?wl3k%P8XFj8UJ(&K2ZJ*K1wHQ6DAM8LzAcEANbk>E zq*Yv6$zcD4o7!Wu8}KB7Z-iR?JN>|Oxq)_3V+oa5FhGWX5&mIqnTG#mT0LRyK@R=a z{!bob`3@X7V#Q|YH+n;`!}nA0(Lc5YswgGZDyS-Vlt=jFkMJY^c}M?kxZ1$`blxMA zCKf>1ctahiK$XX~d&-FPTM~mGEPt;45A5GxsR{MMrOLj6&*AwL!s6Oie)k&Im`o38t@lL|?59k-j<{IXYy2CQ4sz z#Ztn5+XAl?w`e7436p6#^-r721SVyeGeX1}ZYCQH*xLjq8zX#ssdtfnPn@Y*l|vAM zIrVRXA|fMvb_EPqAJ5M6RPr?azRSQl#CFhJ{iFUP{K$V+Ws!q60sYH`&+2*G4hwx$ zAUGP0*Vq5MR{DXDNOJ+mJ;*m+A_`0RZ#yRpOPB97{|)^FaHOBS9|pc9vanuF8rbaPpdwV5)bG_ntnq%sehb;&M4?{zESyY20rb-#l)+HAIpjSZ%99+ zsKf+*F8y!+a<=UN;+UIX>KWW_Cj5wduKc6B&G~;w6BkWg$^TQF)Jv;;MQ0U&K_EJs zA|er~=d4C2#a}%^R$vTI7$dCx@KMaO0vhQ6`m>C3-GU4%e6LQBDXucI~x4p1ou@J)p?`>?gF^ft#6P za9vVmX-f?IOgWzgwMI)Ke4Fu96lZgB06lrK@J*$(vOu%03f_SP-6GxKF+f|Z`q~YE zTet1dc|1F9^~GSF(SZYnM&BvWrwRo>&HXw#x%v-3nf0~vAQJjd%9#R2oOw~M)kyjDpA{ISu|uH+qbc~2 ze$LV=E$w4xHNZ2@sY0*d8CQCj!860#`R}(4*4z162P%#TlA!S(pbDT`r7bK3wnIhu z7NLSj+8X>nAz20_vYdu*@DhB18R3%_IwE}d&F@_O_Z1vPvS)j2Cf9RNlGmd+sx3`t z1Y7bg`Yfm%w&Mb$uie^8ERY()=X?#jAy4rw)j!Upf~l-fp=odqX7$hPgg_0@>~jX5 zfzHC`EHXlkD_S@|c!eKqZml>sLll)=dIpPyjMOG?)-J@U(9ghTWm^m z?Jsayv4G=qv)Pqh+BPU1(RS#O8VKSzaXJ%t4q6b5QhjIX*AI)O{=vs=8c6H!linq@VNUigp9Pq5PdXEoX#aaN{Ly zcs5WXskMzttB3*MGGDK=w74OcIyz$KCt)`@^N8C5ao7)&-C)(g)#{(ILNJY=3H_%| z`w9qTAYvc;ACZ2}TmoC1O5PUY#Po8QEW^&mKrHr;GhChjl;SqHrSe~)^Ut<#-)mdA zxNVsg(K!0RpV@b>n=JhXTk)JzSE303prza2PF>`@Hhd`{!Bq5f8z_bL)v9OMVSG{U zH2th(<1A1@R{y--a`{gh$}rtklC=EC(&^K(z1ILLtr4Z~jyhur)W{A0qWsZTME%&R zbE0T>Gkvq9FNd(}zXfb8dD(V3b%Y=5Kh6IPd@-4Gi)dq6g-&0D)kJJHbs!E5{Y%@Q2JlHD?7*_cuYh4 z6Vo~Wg_|;^?LS8_P|gnNRYk??u|$1)fJ5;jqf_E7@J*7H37KFEsp}u@~P$(ndOExNR=TCzTJ^N zf85f@xaXM8{X(XZ6V8D{|2<^+Z@(2!ADXs2SO1{}n#gIldXpise^LH{&#j$WQr5`+ zbLN-XW7QjN6yu}V_oW{f?J>nV|HT^tOY^@a?Y~zq^>3V~IAisMkv+oKo*Ww9cz0j; z;M0TQUTsgj|2A%*h9YXt)A^USfa=^S*tFpDBbgfzV||i_a7>4+6Ao9Z?NK>t{!ba# zJ3M~hz%c)_L*ZkcYXy|=zAz$`_U`1cEG8>|l#@j1E8G9STKdUr^ytgwKPfkn`v&$` z1R{@-e&{9eZM5pUtW^E~HjVrL+~n9vH$K*!*NwH6H98**`|lj0^-kOW1SKmw!Y}Gv z7K-{ktXbEEicgReJV$}LOYopfL(=Ord1HBtIuErn)AHx^HxEBTYO4LA{b)-590s5b zOBLncS402xz>j%z^$(1q4=4J1aA3ti96lrdqECot`q=n?_>O#&EK#-kmn0zuCKSS1iB&egy?nN;FoQ&+jB#Rc!FIPk+Mb31 zjC5EDq)|SH)--%>RssK7ZOJ)*LCzDwvpjqQRv!XoIN*e^w|WO(zwv7?>Fkux!|Ju0 zt>NTM5Dp7wTUvjejl%76J+$=-N|ii(>+4GYDd{&^XXB_=Es%bd&Z*$=$mgEd9(^5F z4h$na(l7XwVew$6*Q{8u`X<&8C5>Jr!3;h5TZ8{i!8feHLkYks1^?{X^ZKO@rM7qx z^3{Kr&fX!09=ozyX`KRnrt3d}&sjv&4Tn*hY$;8PcUcW`mH*}WC zl24X}wQDx(Fx!hdaJSM9v^5#&-#a{~Z5+r*tp9Vm!WjrG2S2ZwJ3}9+)JZ~%!539r zUeFZ%x%x*Z)&5Z-RuqNtLw|hPljNFjU@4^QpE&TM40|U4bnHlJxSjx4-X)(DtHKNq z#dG*70Wly`Q~YPU%QvJ~OQeTuq=y_Dj2^-_cs4*hd;Zxc?BI3y&rL@Z$<}~6op+ND zJ~)F1hq_1jhJ@msCwiag-?j4pob&_AS&0L$a`|t7^XR|vGvNtex%}r!Zz>4f;WN+9 zJ9w#Qvk6`Rmksa^`^$CVdg&{ylqVm2w!S+3Bo+25FBiT5S}>ZFh6CAw!3tOGcCP(D zrvt)C%Ynp724sbX@cnEOB!`=-s?8DXX(<^zct>{6&2xm`y!}h^Q%0)&@j;rXO^O5# z@H70kUyb<>Y0x0{QRo zIbh$&)K3OB&GFypS2>)K#Mx$?w_^vUOFxbpK5DDvJY-97PqnX3F7nS)WBOy9V-vB` zFC}M^0!;0kAJOBo*0BBWpe@%1Lmz4T%Yn<#%a&OyED!zMHf1$HUgM3+-x=8z%5v)ElB&#ppNrkj;fwksm=!W)A^ffzpXu1IbKGp{+;AJ z{0C^{8#B+Btvw*t+AZFxwA}W`&h`^c1ebAN`RelL_!?i5vZMXs4YV7(?8%B~QvNUL zfbpaY-|M5ID_yve4f0Xl0+K|tZc2~CQs3w?> zsDHLf$1AW^)7o8aKO`gP!x3n2YwLWH!U|M533TH>@{B{CVC}!b^pv^vpKJfG)c#ss z=ki}LE(vdL-rKcnZvr34P^T^TC7pE^*=0FSy1i7?DB~mS@!_N&w_5$U{&N05&smDn zL93cP@SPHsP4P{%KMA!;Ac&fj1#TRFe)oluVa28sVb}hu@Z*2p9L9~j*S5w{p4Y#X zZ15{}!y^2t(U!aY`isFO&EYGPf_5*Q!!e-Lss2CkpSY*LoLh&T9;JLj&Mw4&JRXT` z=%>YHN>-d|_FCyTL!+{wKN%ZF`qTX%1Dv1%MovG-NCv33r2XyEk=}UcoCKgD;lyTnqo1Lu_dTJg z|8th7`Tk!E{0}O=0sX@DO8kFC+oIokdyZ(<8K-(SoUVa>=dqD-Rq%nAPyWqkgX{<| z{11T}H_{M@Bebe>D`OZFNs3Fx0LD8IifJNZ(Qy@th|Vy!#t~vX z=`olwL}K`61sD0E=Z-jnmnGO%X%HPn%}X(179ZM3$jM>MZXk}NCnTatex)@}sT4OO zuf|JK7HRqIZFbPKzavi8xB-01^8kGN(@!8Z1;2|9BR;3Y9&a5xLg!ITam4|Nh=Aw? z1;YN}l{O-4PRtRliiflX?q{#f3cvq5-wtisL6_6PCmi%Sztm;3@ zBev_glLgv)vVE1+PsoUj4u`$of8U+9U6U=FE>ow>VS|kPK-(^H>QMhIb?5Azv13O{ zekUugsUSzAbu0||M)X4j@LlwUsqivGW~^u^e@Z4kMZW1-tjN`W%7b2S6i7~E*U>tb zTwi~u;Rs?>}mH_LH@ z)DM35JK}ATt$?6z#_G1DJ9YkyhsN*$PNxu9UW2664~Vzq4*y#DkBn4HKA0`F@>Be8 zO8${Q;79%&X3Cf5f3#=FLzcdXyV9E6z+~uiXBFgh)zbQNx&f5~xGfElNdP~n#o3!n ze{=Sylg^$&cdB*EqOmRW>H5bO9_r(4wU(|6t$g~=VdMlF&VcJJJy+1yjMmDt;cZ>bG& z4XMv#f4roI@N25osZQv}A!5&Y^e>lwX7C?V+jGBe%_`RouWX0Lt{m6dGdSIT_Qs6x zed$O$@fg~(^uyK0`o}h4LqkX+f~4uD^H8egeB?~KT=>!cS8*00dGrVG`Q*8zq?29X zQl*2co07jXWs-n(Oty7`FK5VcJ|^ukc7v6#yEQ02 zrtQdwjnXL+8Ra^(z+(ipN z3oCR^mQ711g*p^UhllzjKDYffOu)uNgl{roZKzLdcx}5(d1`l9!$uoO#OVL{git>DYZK0h57b#!Mw~#-jl9SJK`i&eW+0$iTIhx@B6H}w4UnA31|TQ!2s!C2$JBx)pV0G;_^NIkG$l})7%Y84WY*+;cwsE z8UE#OXj`IezX3}MGyYQ`;R^h38T~*bI86~F@Q?N<10OUjIjK_mZv!Dl#BIU}-7?v; zq`lR58b0NpsH#;O?R@E#I6JKr3M3^ZIvxfhAAG%TGvw89xlUX4&+fL=&<_i!f40|B z3RRtagrbrWq0Qj0dRo0`Z2zwgJ_UhmOYINo$BY>j{?#A0udj zZeihuGiE;aYv92dPmzA$JN-^4e6`;#qW}8&e4e;PjLTW}5pOU|3B|7s1;sXq=E=FBDJk6`ttvXR*cKP`WN zwUC0(LDsCeIH2X*L_=pT|0~o86W>ke0dOuB^;V)YV~$Fu>`;5vlj$w>mXiMp4W^Ll zZrw^WcoLq}DnR%X{@j8_$Aohj9@LFLD^}>Ho+ZoGVXU@Wb2vW@`7aUgj_QUauB#rv z0lI1Vr__x8#d^w5eeT zS^Za8DwLu!`lqOL{paI<0#3Pn>+uM60@1^VbsN^_@JzQGp@-%B%R`y;je~GA`p(u+ zU>coy;H%6me4|z?L#a(=XNp<%s2Wo@Zp_m zM(7al8Rb9}k2(7Y=Yt)d+o=9k7i6G*`AX>LF!3Xjql41d2%p->(y!(9a@gn6p9?=C zgq{GSL$Y$SRPGWZD0Vw&2*oy zSR0lsmL6*WQm&h+kV)hZv@I~Y$)P{N)ZpVVCqREEIg5?WMgt!p2;Sh!xA4!@zti8Y zL$PovQ~NV4JR-ZYOb3`R(XE{|8gy`@8Y`fIk4+*!Hw`l&VPe7PH?I4|8_pd;5?FBT zrbFNlX|{b2s|J)8%bSKz{rjLxE`MkS%S8UVt8NczHQAnh<+`a$d6k*>QU2IsLtC2V zzw005mLNkqv&(Ft25TT>^n+`Lev5`Hf(^oY5^UhJ)swA=*p5fkelPoct!@EUd(0qF zDL14@gA)24FYA`)$bYL#p>C@$#B}Np-bVgATv+BXqy6Kj!M6|S2P3El zv3jir)5MKvKcww@x%wCRe?$XU$}!dHj+7p7+u$*6KSkEAa*LMvoSL=!fPm4a-gvb@Tl6mEz* zo?idy_TS)(|HeiE%<|vi8yQA4gc*e@-|2z3l$);q4FC0Je>r`TBGYd!@i7i1@=rci zVo^S&H49$}tWA(kUa9|cL}yrn7dUz0ULn`r{}b(3OF!^g*~Ir~=`y!FX6s+Y2}Sw> z$bWAA8$Xm&jZ@R|N6AUTM)I#xt>2f}A-1Kb`R}%nm{j|#XJzBR)kh+_dEv9tzf{gT zobbDL?+e!NRB3kRsS4mio_>A%YW1mB8|oqej&~r896j7&>ND!!0m%vUkJ6cOFX?tr zo=wsW0rBVzlUVTI@pbvL6j!ESeNk4wR_JV8XtMgt*k6awIkx%u4__kv?TclPrGNB) zACwL*r_I!OiV1@9a&5a*d_Mj=GeH^&=UXsy;DeZHliu04fI9sSzie=q@LO8J^>=S= z51+5A)XGa8+2biqexm*j?AtNyEI%upE`+kderZ@E_;59)f06(6e}1`OU)Z^iEz3pW zsrv_p_dlr!^L1O|<9GK9Q^)EU2FV%tW$d4RJCFDzTekf}t_`-*lkZCOQwrshjBntl z=_e{{e^r!z=F;!b#0eF$MYlZOdmFM&#w`3TyH9DQsCXy(2M_3?H-aoB0Dijv*QEVZ z+29BLq05L+B9x)YiTWBX~BcH`Re~#;OEjG;nV)Y$E)T4)zaTu@R1n; zz9Ihb|3n`V{vU$LmHJw=SuAM6$%QY7fFFq8v2SOQt@RNdTa?FhbU*==XAp}XH_{Mi zofO5BCjRk`Kw1K2K}aMbI=>>J@pnv59^zQ$jkr3?Tn&&$1kjaI(yTR)p30Ic58|bN ziZqsCfXGTJmq&Z#!#v6Q&WJJZt;?)$JvR^Efh3}C<>$huBLm!B+M|xM#c%=P1U^o$ zH{YBU=IZeJeS0c0@Ru%K9bW(WEE(OUN=Z;5{E4W^$pOeve(I#}LtnaVb@=&fGsBWa zOO>i2{gWn)O%$YF`{+5Ng7VVz`)|s=I{I7Af38Qi>wzJorSRz_2qVs_O4CpMj~H2| zH$`fMkLa8L_~uC>|IcZ#yME&~%QjeNUKmg%%H=;0oWI^_qs)UEBrvO*TmRd1#=>9z z)lb7;|J^@nkd)Mw$)8F{6g$GtmA@#ZhG#-QP7)sJm~+i?F8^z^wQR%2t(Io+V4vY3 z&8SgBjb1v1Tr*A?Y5JMXLYoMEB3%}J#?6)N|e=qwoShF#_aI!`@(hK!3Ol_!jJSXTed2^ zu32^T&~QlLQ?~Jp-}+}~?Fgxn!-iVAU3>T2c`0f5R1({x-gpasEew12R%GZmTJ%0c zt)8!k|08t{2K9eXGQ#RjqmT0S2g5i&=r8~Jr{Qn@_Mh#X4T}^l^$dI!z%y6=!EWTR zVOIaU_LSS8B-j4dNgiI8J)0vQ@6qgUq<^RmbtXS&$n2LB0eEToL;eq`)0#^^d0aj~ zTz~L8W&iu>G70#PlkPBw`RB+V+bzh3a|`*5^hfwL8jx+2od62wAw~N~9^2+*Zv>hM z{&L{w(w~M;4$sDusL)p~d}wp}^a=lyUu4b4f6^@be3h;ASfoL5F8s;kZ&gm!yox)? zc|-yT%zCxK#B*++gJ{Tqi&WV-%?aH#!0u4o!SXnM(4fCO^nXsQ9Uo2Zb<*K&sSSp8u<^Nk^eaADAVl9fx`{t5B}HH*4g#; zbKm_a?6>VY^Qkat>iG8p$|Uj&n`Mi;BzRf^EaWt8U2g=S1}p-?Y1+~i+zsp z)9nwdk=JkBZaD@G8fdJJ^aER*uusa4@*nBvo#W>aBYb$9%YTZYJ>U&nQPcbf3M+uQ zVGaF7XVdy`P?SFdP0x}4S@_hTk4_=%eB@1eISrvkQm@K5=Po6jqP8i+v<=`o+fe3qcm)lLY zmfQ+g*`lBA?ysrMo%7zJM(scH2>f*Y8%*faE34NQw7-@vr~hsJFu`ZrGu4o;e;fmQ zRktmH4=V6G-tbyIG!XQeDB{#F81zIJIbVPL;FYn<_*c&-1io! zE)G~7DUqSm68RsYNpkdW{f2GkSnbxe#Ls+H5foB3tkmjghAz3*ZwbEw#LRs zi=>0O{GU8glc%-9l@f$&WBa4XME-K+A2vf@lz)nIRcuH<<-VfZ|0a*=rS#(U zRzK(0kW!&WlZv@#q836(|CX&g9(HOQqZgiqUoEHD|Ni5(;XnLjbJ!#YP=pWv59MU`X!kWam@ItOKX)bq8_AMJ0#PAe!~ z|Mo0U5k9ute)1XVH)0(ATOX?LYQOo=pVFXzug}}7Us`G3wKxp!r`z2rQXdgM@@|v} z;FaJbe{DL^H&q7Zr%cC3F%+U0{|y8^cjS1Y*7iTBXrM&;fn>Bah9BvNwp{sF#5K|H z^j$yv=>Ng56MaQc)|~%`_k`%!;@M+(YK0ITP0<0xHx4EM@(w2!anVT?om%l51o?}O zD&iqF2Hp`z;G4ggmU4Md`UszAJh#?LD^FAbNu2T~zgU8yI`PV(WPZ><_#s;+Bv82mCdPFw+QE2k7UVcad6`O~4UDr4r% zd3LzxB@SyB5v*4E`5QBJu2QX53>Isv%v4Hu7|C}&`X`K6nK}~y9?X7sfexMAZnONz z;_lu1!~BmvRqm0aIG15?|)H25u)6j2|>kJZhW!jt-+F+0+Zxzml04k|*=*e61Z`-~AZ#c)2F~3%a~GI%$M|IjuP4kTx8Nn*qp0iR|M>B;J0-f^Qh_=GcWmor zISzJfX7)qLGW2t1o!yjX@Hv29Iw!8sL0u{*%E=Q|HlrCOcj83JtUA5WJWG1FO)|?f z0Upnh-s}^PH*b+Wvn^oI#ejaCZJ(kXWPOLW(82r5_0p|9`*cRf{JivEss7Qwf#N&c zM!}hzDgkELxY$U3Z!!A|LuVxR7v~9S4j(zJLFYT>JZEO<0kacMi5`q?V=DSYJ}@Ga;YUW^_!LMshvD~rNsOIPX4ig%TFt>CV;yq{QJ-U3ZQ zc_RD?6I_?DfwN|RU1>=!7-b5IEU+?80wwpXd*sy7vtpcHZ%Ef8=%Ng(6HZ+Ig^f#2h z#`aH@U~r?{hI@X-(CzEb-uG@t{%rTEXm_|T8@!&dfOR-aVqj%oGz zJE~h&<>jM47e2H?|K!QH>h|D{{8Zh2pt{>5+4c!@9LY21s_q1zw6Xr9{9RA~?^@{x zPbL}BKPE&DNaszy70*gLlW+BP!56+*(6`giFR1Vro`&36`Rk|4eV~?e%W$@zICWAE zGe5(Oe8@$&Zk^SRhcJu2m~3r?e_V(A&sN`}Tr00OCUz*oZ>ju`9$jYeFWDt6az5hB zO4UCXSyBJ=v!Q}jhCDb$P9Ma&oX7^iBU>@*HOPu~my>EGR-DR8(@)v??5~$xWRn72 zyLL4G&zv{Q>%T5{>T>-vHw<&UkDY?WWBejJl2Ty6n;R(iQT)=&MmzHdTGuKW`n z<u!9{H+4^@mG06yz-#bvZ!0T8Cso1~%tMy^odbNkf#`;ix zxH|mVKWq%EH&urBbvXU;N|zBK2NB`NKie8=b^cvR=i=~tuZ}XF{IA~{r?GsSaI~^E z{KE|T#R)A5{Nbfq9j5GO&enxre7G+h)nWB#PvASIB^^GdwEw5rQnlg-2t%lgZqdt08`s6jg6!_!~ z#zc6V(IzM5xyo5Oq?QN$H3y$%kX9xM$&&|ukviMcb#Uz4@4RnkVlc34*SE8Kk0M>9_2( zS!4$B?X-ohy|%1y&P_aMaFS*T`*OpcU_|=$WSzmZQn!@;$l&Jqzeg)nI1C*wZ_=Rm zVVxDz68P-X?;-~ld+a~fSsqJ1T`s5AnDETgkJ=z+`U}&-FW!Dnoq<{#xE8k;k92N; z9Xzc1K;9mC_-<{*&>15}RO9+j)6e15)2BbKGT*lY$7j#|D7;@RN22(}RyT|t`}Uun z_MpL!bp`z<>eQW4Dd0Ks2V|>S5+?iy-iyya8Q%KkyEf>YA%{${2H6bOIhdFnqXn!Q6h?0f;86V;vT=8;XsadzA0 zO*=yE#X31zK2oF5R zUo0?SK>^|)g*Bxm86o&RN_ialFA=5uDHrIHilssdopn) zFkZ$}w#%{0PQcq>jA0DuNqSHF6CmaLIeovsU%wx3)8joMk59jMf4!YPr~BOQ)2Gkv z+jpiW#NGF+|B5FpV*gOIX3u&q>Ul(8VhrIc2JzHy`}QrPw!h~8dfnH>3l|CBp;*{{ zRg2s0+F5b$J+}pK;>Qf7V!C?AKTRs*56l2aSZb1cU{~P_XO2AsU&Ir}&QaF zvvelFYbOuk?kSfc>ox5ZP^9=Bm>E*%~K1uDzmpRy&Q4>u}#cmH*tIeqE#s&a1AtBtE19|ARMb1oQvH z50=AJU(++Ktpe(+uywf-nxZQ*NQFKKsW z!Ol*#Ma|YNo5EP8#3<%SM`IwIj{Kiep;YF@of1Tup z=I>*=N6&oWS-l4SSd17VDylaL&YIQS)yfqP z+WrH{6&?lw6~|(T*PAcAAdKlC^7tE5{|JI`BO2eOAA@T+eR$Prf16JV^%ZHdLpO`@ zs#m!=j6C$e+2TXI0!*E>a93Yg#ZDKI#6bIiKI{@QR&z53)zJUu%+#1FJL4$BJhokP zNSD5dfdHz|hi@g;{+9NSJO8$Y-2XvId$#F80Xu#V>cJ4_#qnT-dm9Jfs}C=TGojjr z_89aTmF6q3Q(+|p6^R$eAv;Rc;2(n-O1p55(%H6h$w?_X9 zisCi&OD%+`I6L9F8SVXFw|=!A?9P|IjR8-)`eh8m zb^2es{=xA0Lw${qvn+YY%*TvoOAn);p#RkUWIX*R2(=<4nN z6Iy=;!&IF66_qdO%5NEeAYQ#fX<-`Sl#dIFJIYw;9Bj*Wq^qP&wfJ&y#Kp5$o;JkU8o~j zWFS-4hSdw2NAEnZ`OF``*gW&xtIe&~tZue#;sX}t{_{1*70c%~dk2Q}%H&U)|EBA% zo7Oh}P`kc-|0mS`=xZWhx>;}_>KZlfwx9lwxa9sjKYaQdP7%(cymZU5<}2TNBd%r3 zW@}#2I!p}$_R@=%HTU0)VTul-l(zpxUx)uB7gX)n>nNk573VWuRsHz(7~lXvcntb7 z^R;i6?2ow@`=a-YmuT z&f9N%NBdFsq9aA4)rOXSFmIgpPn7>CC`)BvqCaDt0ypdxDfy$*M{#=W!@IOQV|D2p z;@4~1Eo8TD_3GxaNA8O!`q5AIjxS%qW5LX85sy83e{<#K+uIw82VqrW4Ek%&5l-E! z3pZULn)(oe3;`^@aOcQ?@z6u}=(WHLBJTDRiT;Z}!u_Xj&|ZEqzSZT#ArxVHbr?gTPM8Gj{<#SN3A zxJ!b(b=qci-$ zr<%nWsPkSHER1u7Zf9Jz^tu0Q*RF}a7vo*88DUJ~HCZaixV}M#>jyu0x4L4PY#D!a zOOPPmB1nbbCI8pZM;_Mrpl^i?c~j>U6vNi4Reic=_r_+z4#hjFu3_niKZ(uE2+G2Q}T$?*Xiswn#Q=|RTn-) zuyf~)dhKC@gn|r&s%w;M+mBb%Cx-L*yYkBI(T{kI=R`bQ5y6%;csNVs(GRt9LUnpu z@)v_Ri>I?0V`}de!V%8$FQlR*^eFwWreF8Jdi)2B@Uak!FqLWL54;JLMvbfakNyO^ z=>NX|z3t}!@t7O7ZjM80ga0ue>0k5WoW(ouxG7i|KSISE{NYFMi``S4xdcw`Yu*2U zqF1rsl9K88!A9=XBUk1HR8_(AqadsSbzT?`34*8`f%!-xFUQqs)+abV!CR9@^ZfgPbqh zF5{o-1bUI`w4bI>3HTYomOcsY|Hy^5$p8l)5OJ2-Bbt}jaSoAy-;`Xzu*F$|cibYm z60cin9MU;k1ikd@_Oq~z@dxsXe$<=F!LN#bJ^mp<3z*lS!OueN&ZaN$krCPu`k#yw z^fY!ZaSq!Z8n5A6{v&@w#%`4!qWQ9naXs)7{`t%yJFaXV-mUYD^k@1ZM}k=b(!c2w z#zl>UU?THx6t4O|rO)4RuJoU~tb%>BR$070sVW9 zjB+<=SLCJJHb*2&ADg9QydL2(9b+)|Z?-fDfFkhR1` ztR*y8zoot&Umq30(8b<&rOpCVzi8!zRw!a?^bmz0Tq-saeGzOnY7VBRXK9YO;JkIE zEWk=-y|aG9nyC9?%|Yj`WoH-v`YFv5sS`ScetnEbK7hn`OnBIQP(0#6)J^)35&eWV zfwt5B7&t;Qiu{ED?cb>57SJu$aelGlp)bo2%7+#1xb^+fN29KqbHN`!Lfe@$(c^Rv z&^=-eriw8C+^jk3PRZk5`4_wkM1_9c|DX^4VeJ>Md9cj=M}IPXs?y8caw}2@<|m z`d>f$!=SWn^U~(-8+8AuyYg@!yYIYa%}UL+;_)Z7yNmH>{?D4N9bfSntPUbAIX z;{N>jrNO{m_rG!5ulvusjrND$Q_=5jzivd(kLtB`jP@%GO@_)p{mt#owU=qvxp+M= z@TRiSF|~fUYeVzf|KJ*p0dN2ujS(wol_KC!D1OM3>!n|}Kgt7C@xN3;*aOi7@Bgnb zeRK}=iJ1H3W8cBm_8+fRN3#FKT2LJ){5|~x!S=wQpOyO5_GBHEqB2Y(HaHO`4Pvz) zh6FW)bi?*GiaZ}E#?CF52abz4&W&`Kyf`TRXFtGH+RG95-9!t*_B{|OCxlx>W8wX3T%TKlZ2S3IDo^=2_}*Vz9E-lh zLpxB$=l@adSnA)mPoMjh0;AV|A+Ksm%b&jESaNM!layzN58%|EEJ=z zZX|t6{U3S8S0m|1Jat~-_Knm3c@>o}*Ug(dH=Y(V0S12kp6dN)&iLKm`)spU{rQ6r z+!0T2N9+GQJ!Y(+cp@jt7z*1-y3r!yo@N9=6=5#j5L5TGsZ}p8Fp{uVnyPF-DLpn%|e}JQjA2#v%Y@1z`(a zJSWjNSs*e-PWAp9A@u+EvtMiYwEHT((*7I2{%D~&!TF!E-&WbPSFULO#$W%?sO>+h zG2P!U8K`$g*yYIQ;~CRtP1L9A|Gb{dS(k+;i`xf1w0t;A3NO0-esN)!CX4w970SV`c~C@>3`E(s%q+?muP1sr`p^aP%);e_cB_=IVa*72_dHs@zo5 zACO!bM6O6jSlPECc5$6a`XA%9qszs3)vj{JtDO1W+y5~XqcfhA{>Q=I%k||+?(J0Q zA9Wu)U7dS8)9?GolN>6CPI6i(iXtXtj!SaNDOAW9`5@)6A%{K~VJfFY$Z2v+2bM#Q zISq3hAtPtQ9K&X@+2J>z&-ZtD{Pyo2+v9QF_jO;_^Yyyl@B4mB-F+SZpgCB7_dY4h zHdk3Oev~EKgIJ2L4a$6S|1n>uk;Q|ufIZg>8*~B_Y|rqm*f6SDINsPiNd3HSxIizh zBdsx6K05=h?>F;>O=f=j+{J3bMmQw9ZupRdMuLH7S{{yw(4bNy19y|yj=kiF z#ZP=d+9g4O`zzI^G}Db~eg-3IUq9%GEW^$Hll~FrZRVdpU_S4zqXB7)Vo{+1NR7&o zB6-9))Mt9uZKk9q?LK*RE!J&8cYNn+)Wh3%YtCVnLp4QfJ$qN~AQK?xHRCKRv5wZacaGf-8lJRT zw^3Y)!wW_A56{c8Dp`wrpO8jRpvKgwi0aQ7iq!q7&E~<-6;tRB8-jn9QFYeobhd7M z@{r*>+tRwUt4kTrNN4?-p}#mQP1fAyXOVqz%|G`}s9d_js+JlG?x7?RkI<~HLT6Xs0a=VIT`cfEPP|{1X{ziK zHW%cRun(M~BS+-Bz!VFCo zVL4(c$@uY$Rb8HsK$Y+-kX>ue!o~t?y<}g-MpTfUmn*_}^)tK;znyfk&bh|pXWNF9 zw{1W2(lx$qT+W~8g<79)jCPKFk^1u}gAk71U?qh5R-Seg9d zu%jsh75J;>ilir%WZNu+yecyl^6XrRkasDfka=%O^GruJYxX$=$0}tRTNg8h3(n(F ztH<9Yyuc1!oSvdhJU<&+V#=CTuvA<^jIwgl?vkNK^kytc(A1z~UB#UdGsUeX_(yUg zQADcRoF|MgOYo0$+w?dWpJCY`zD2LK)bF}no%IR07*a0Qq=a5PEFxYSjF1uxt*)g?#(RKDJVe&|z)T7US8~ z?IiZA0y4z+jzj+K8M}5Yr0#XVy80NxYhUjoCOPq3moOUS1qMyTHYz`U;A9#N5~^%H z+E#XGn%GPYYhX94IW>9puTOyYMxX7zey*c^G<_<I^20`%!kMpg1Jz1)srhq+S znWI=%co|~er_rBIc8S{zB~J9$5p%^Gz5?&NDBV=T>@Eg)>44(xLBtdZw_-xy)fZ#q zuie^2@_21H+RB1M{p@1^yH8#rRU`-kv^YP!O7E#m9U6UyI~RY#`15d1@A(bPH<>paUPG2l4_5ECaEip6 z0^aOY@RwtE#!d=yTQeVr_fd^fmUaf3LkkyLA~E?-^F#kK(D%=0Y>$!nG|N?lwu#bVopAP@y9_WMT*sse*|A^}L?Y^$q2y&ydrz~mUU&0F8f*k3*$57d zE+9MT7Bgplve^Fos`2ae%%7jAJ+ye-r0xNZk&rs+i@qsZR-7x zN1tdN@45v_H7L?QBYg(0=nT0WyCI%NQnQ#3@AxW^f-KM$)~ey==H8)v>8LnTmefeX zq?}f++JG*;gO&Q1^m-aD*_3QAs<&0h*}QtG>3rdfv$TLbC+8u)Sx`w7J?(63qA9JL zwT{qW%AAl&{p%+BYpK9Q;*of7 z)4t<2OH!{m>*QkuT@*<#2|z;*BJ9WxAr!lzbxzyo!-@+~C49EKWINQt?*^*!?0b>j z!`8KONA(_n;kx&g&AW~7t#SgM>VJI&lRN7zVN&$&^<&c`dDkAe@rAyAe*cUqtrdAt z!~ux&D%MOXZ5!qi9a9k<7kg_1z;0##E_l($YBWt-dGXh0OA)b+3nj3NB5&7{Ac&Yg zXY!3Qus?%g>V|lY!_Uk}LQUF7T_^5zfmRNGV~rut{1IJ0>NkckuF>HgtGy8PDjW#Z zJ99yR;O_!!E0;esUVpMZ5>8mv-t4=UE0%_yX^@#|zyN*V#_O{!Op%>PC58LSf{)&e z$iHbtk=BVaCD=}{^Gk-BV6RH7R9RpZSsr@lVcXTjcE2$nER0*KM>GXU^sRPFY1(^3 zPx9duuV9mcDHQ`VraCOW$VT`vuM*Bv1ovLkY!7M}ktU6Y)-n4iyHK}%OshW6U;CG# zTJ6ZFFYWBvxT*WCCs1PI4IiOg<`kA0l|{mzQg*UZe!;^Lv(crp4cr0gH}PBVmBARa z^D9rI9)0F_oL#&y!;dMUM{+*Q+=2$rr6p>7R476#${C@aVP{VbAoFpzl`=1$Rp2T` zjJ4@+ax0SFS_s_aF{%~uX)&oaR~TRT_8NEpWg&__z!WB2!lD*TdQcZsZJ3>*R>flO zY1xSS;nF=lR@XtYCB%<3zk2?TCi}3Wt7oLywMQ=Q4FvH*wBwyn9aFzx(5{=}Q3f7} zpb+hUUEL{=WZVcuRc3a?2?q(JU~{7qc>X?tyDrTK84ChF(%?t+UcWSJdikc!vef)B zidbE?k#0zn*M~N&Rfx0q=W77ZK-K6_60eq^B=?P|DTi1AiFo|!hKU_J1>+H}D=KAS zk0p-Fx92G`OQpXGOkH;@1ptm6EAQY9XfYPP?W6@yNNJ*`u)`{WrxGl!FDxcyNY%X~ z)o8tIk53^B+IZudOA*Gpg-o}r*C>|Uw|+?3uy7QkvSvTVOYY0*Yn#y9`xs_qSbwr~ zEatvz)Y}d4x3zc=*4G@l`p9_TSaNYF@xJ5Hc(Zm#y-@M*AC3s+pPGKpr459DoV(XL z^fMD&qRlyjVfEUez0CPMi#f30f^ntWTE}AEHTNSw<9`86bU@g#l7e3T| zHA~#!D-B*gk^B>vmHd->UW<~{CYkZ(-;6W8vOp^k%H$n^Ah?7x9%i_nNUdY`P)eS) ztvo>R^PhX-6wIW$mo{Dh9QcbaW9ba<+g^_<7?*p%%snjl zWLQ1J9_{ROPG=D))H2?3GvDKGSa&)LI}pE{IlTc*;$Ntb#s2>Ncl6v?76N zW{lI07x$rw&8gta2!D2=t0{2jv$>$^7I4j)^}#OX-`G^7{20k=<@bU2Nk4|z#3gp2 zj`=w~_he$(wf;3roIZ=%tF(5t;az{eL0sDs=Qhx+8y%yA<^ELv{F|`o=%0(2)7WX1 zHt#P#%oM#I-fc6yB%bS4du-mjDG2KFBA-tncwUjnP1wIMur>Q>#rkaL>qIbuNq1i! z`?|Dv*?sc(&r^b}afXq5EEC-@a| zwAq}_*1l;177}mLf`RtDc`xdR~fndAbzw_{9hPGdXdI$p^#H6*~fU{maga z+h|Y&Q^eG8VeD_5_EiV72Vq0CtKdxZ9eSDRnKK$vvMOg#v)&sRr|-p?{5<@!uPKzf_d&^mtl3 z-0b3nQ>lfC+(VDI^|j4s!6Q<0Rfh*JAEhR0#bL%}I4r7e%RJ9ZRhociowlyKhssbm zT)a>rCw=B?A@nz*_h;Mt1%#QxLvG!3MO#H~)@yDodA^=)+h;}x*NKtCONP;`<; znyp#4xGWhBa&MLs>zG}X^uP5Fy1+{YSDh5`ZTsCaaO_`yhpdz+UFQja+=W7+0HK(x zSn<^7W${A+;4Qm^vQ>P<+TP^@1U*cvM3x(mQSdU0EOp2>z>4cpJ5@HfGI**jXFuY?}Z zjcYLQ#w+;(qh<}JHM5Yuk(h8n?#uvHmN;|Y327@+yjY?pGGB_wgT4TxlYN zqrvgxA`L_43^$Wej!7NA#jal^Or4{QENb9pQ&_+Judf~N>_bJ99UVl9(D+%`iS6Qx zq3bvI#^5lVw8hvrcEg7h?q_QJ^zbp@-_dt7k5-*mgSTCOWTlrK)`l#{biVYp6XR6` z?5vBA!e=9+BK?>8lEIZB6gE3xG>Z_UW=;+}_O4T{fQh}Qr!l%0p@!aRf*Ua3VQFs1 zk&ZWl{qx4HZ?CC*fEvFzEY5W-V%6!E5eF=P=s;l9+aC1&%Lr?B&$n@ZbhIPGseydS zie`k@$R;a6ZZq^5=j%;Zp~}~^wMEoR5p7(K2K91zAKFS0HC`qR6^dp3%~eT*o5uH} z>G4M|E^z-C0YATNY+2_M41?V;y7_%_J%m63fpTk>(H+hU6<@GT8z5=XYOghpvpfRg zjoCz!4YT?d@2^K(C*yDLT{6xkELfRb!79pUe+gDf=onLzwn#+CfRfbY4+^6YWR(_hei6fSI^juHCkoYGXwK|X#wg*DE4NdKh2z(xhOEO@C; zHo$>3Sj9~CO8)S=6j98~IpCc|n#$S4ui0gzBVPrQvh6&vLt5cCgU^OtaXvFZ2^yg7 z-y;8U@KwhqN_hV1{ZL)g4E9n2h=GHCPr6SxhPV)wEtO&8I9J#74ULvyn^SY>ZsVlt zoUT9#`Y*#F3&>>C^?PDFE;?xu|1WwB-xlLMwjvD`?d1KDeDo5N#$u!bmJVGb* zFXL|6EH_hOBZiy@J(cTh{hxlN;iHAlC(2$9@{|<|>H<7{5JQ_{uKhv#-K=X)Q#1MG zWh{KZfbtLRS~CN{EpJHbL{v7yhGooFImTQ&WZ3@c$&_DAQHYaZ>BqM zs7=$3Gc&QHU@uI|bVL*$@(ujj62KGQCQ=%#pBQH=xNU-w;|dqAiYVes1crcS<9h=j z>ZAxYN0JGKLm$;*9tS_(exlP#Pf_Jj6_8q{&;}qP4GR+*8o6}gvwgvBs*_0hp2M!$ z?T+egss15^_AUc4*j_P5sJY}NO+mXh(2#kOs^ATxcuB7{QdM!)B8Qt>FnLnJu>$R1 z`6BB0gO(FZ`2f#oe%J6DQN0uu^Ael0cvJAHi2%aI1lWJ{POQT0ZFWBV6Z03@*%GGJ z*~6oi)~wa0*49v$rb4!GR9|}qLGVFYt)~H@Um+JQKnFOP`H>lkyu!(lvn0dCZa z3us_JurTYTd(}#0?^D@$*R^xF#rQZ?Lg1Ks-(IU)0K|LHblPjt|6Dxh%nmPMvgyTg zY(QD^a@4@$hhF2INRATEhImFHl=hLDL02Nk??dNq{GX=(Hy>9$CrMC zFqWYtA~@Qxq2i?NzFxx2q16!2E<7p&`!OQd(OF$I45`?bsp&&?1%(21fjeMln4*40 zsMqD+DSJrinMB%y++=P(P-{%36kDu8a`M(7hfe%nJN$_vO-H1Y}~HZZ7=or z1<;XdS1~8B?33a)^R})(y9&3O&Fe4J$0N0eFHZgMUxe%h&KC6J3c^jm{}<;{c|3C^ z{h8(rjOM*nXer`PPmE?_;zrZWG4>DsM>|w`WqhR6BpA4MOFkMU@x1$smRiGvPAC}j z$O@oHpb|X1S7VlU|8S>EPvp!ZZ-M_CTbvJu(?OLPa&Ynssy)})d(>x$;JQ1mqTBt!jRefuBa90U;H?iuC%6TO*P?SQaxtl`#)}oWf^4Op`S?%BAdM|}ouXP8fg|uifUF6FK#cIp?IY*`8OM;%neo&}g<1D!m}$$*HbB72&U znsOv(r{an%Yf5-)ig+OX{Gm6Tq`(Iqy`SC7p$n0e_y^_23~7qe?s3}9RTL(u@tyi! z;8tm1uD=aCrF+97ng5V_HPbuma9F!vk-8T(F0_>BIkaJ1!*&leB}lQ_inHFkXqTQH zM+sDgbWzky@H*#OKsJhr;m+4^s!z0|O?V_hFZ|zW2&7nw5YcPKnx3E`vzZOQbFH3$vg2;XC5_u+ zNdzD8%KD@xeEzS>(DD4l>XZQIR?MOazZgU<%o+ZDXNntN?&DyNs%U}LrMyYoIcR8p z^dh>+0kzuSU8HBxj1E$`nCUqBXqh{(j=5Zhm@_SFuE!q!VdTfKmx%bGG#y8&!TorsxE?v_f z_f+%In|vFz`23Lr{EGg?ZWUK!l}bqG+dz9~enGxOP{QZ1y=_QI(Ld zSa=e9vP2h|bc+#@u00fa(Wxe>7C!6$ld9-Ji{cUBNo*2YtiF}5%wNMvTd>O29#&@j z*aM4&tsapew8>swN&lN0Jo}W?9N%!@$NbMEukm$7jeuV<(`mN$jdn`aO|NOhE*fHW zrjB_d@j===ktUBvr8pucfT~v1CptKW;J(Qe3t*H;fGBNEr0LrO7TIeJ6)?M@A4fgu zQNK51BIVsqX?#8w^m_Ci1M+!bt@9H8&~ zdZ=xq`zZT*Y%N2Do{Y2T=$xg1`B`b7%+91Fm9Pw7!ddm>Jky!FX!m;ZP^a&(&}X;+RoTEIGl z)d|dy%tUKyYM`?v6ci5V5`SxPfOeqVm&r zZ8m%awrswz@6?cuJr-^*8LU-uI_P^mPwk`qfX&J0p!mG}CSw&s4pCKox=(5fd4%tQ znt=0@V9XpLqT}Nuqe)!AhK*#pv=`EfyB^2~Y47GNZt5!0)_rMXzb9KR7XWhS}jL@VS_KNYr|V1%kDE1!6~)4*ffHnV`h-^pS4?(Ge1 zr;}tCeGgc(>A)?PJ`fhzQPzKakDg2oc64RHpKHDoSC&0$e=%=3`LYPQuieKNC`z84 zF_xN%Fh`H{qQ=e3=wgDOTDFUFXk;8HcmskAh0Qmd8*cLdR2D6NbFuW0P{&V~`4-Vp zp(4Gzxu8XO^b_v-Y?l{Va79y%p~%Rl-VqRglpsLBu5Gq-ZR45MbnR9`1M=b&4%mJ) zOBdeuceTH%hUT4cWs@8n;aQ4dU1rm8{DkhF9D8^|6o*Y`(0|@@CKk?Y)NEa%&P$ABvdV|4HP8R^Ala>duRo_ zJ^idcyZ022Z1@Hwe1^&SgULo@gX>RaePx14>k-Ndo@zR6kWQ7eVLh3w=meFOW^(vN z_V%N)A?%>y*xetJv}UG-WQIupA280xvQ;3B7F(x+mvmzsSNd$Tz%Dt^9UiT6^=E`a zLgx^8#YUPUyKt&(n#*lWOhT;K_t}E@_19J4csD%P*e|)IioMiW-MRXB3il99(e;^y z^wdtT6@^1L3|w|7+&uesZH2oW)rU8{B>$HWQrZU<3KPo>NSF1hAIt-UL@PhG;`Rt8 zB-~<%lzYO^qAqwI0I23Au(o!+i|kUV7B-{pY+h1qN$Yzz<-p08!{MQ=)@)YuC$b1Y z3D|$>Zyv1+3ye+)C!v_H8RcEc0K}AS^OHIPFpR4%`QMB+U^#2U7U>f@#fQV~%5S>Q zzCK?#angdF!h$sY1KU_i@&FN!hd75-;$@RUq$vYGD3;{%T*TZvB6O(EnC;|M(mR5j z`u}!@cea8ISe$99OYq+>;VDQJry+RZ=}S2zNa4v??%-{sPe!w#b8L<5O|emrcvW|P zL2Er=K3ZAeCm+12x3;zAbeT9m-_~HqxcXqShNnSE}`&eB8lD9;?VHEQ2KTfBA?soz{txP0=^Br z$lKs@0$wo1t3D_Mi-}ZGi04fF#5_~f`&!W7EF~cJvW-elZ5~u>7LM!)5>s`wj4jO~ zx0(>8EHrTxSuekIpg5=v`^?Q!!RiY@036b{Xd3QLiiBtZ2bg3IMj| zv1re&>RXc)MaQ*rxwKj-=bd{82P@jR}aq^SnMjNKnSJ7rShQo*ox=`LA#!00R$XlIzpCj(AKFrk@(s06am40q2Fx#vzq`+G zG)D0kVRwsEHC>p5hM!q&mU~m6&pYIHy*H9a(zDYSG?c?V009qS3w*^|E-~gT1^crW z5H+lWZ8;D76jF(Z`|F$~lLMmnV{SB+Wzw)PMx`yQ@?SIZz?brNJUmlz{y?vzy)yb} zaT?e~Y$l>>Z3B_|X&*%{HS#X3n0pyO;7Fs+?Bv)Ir2BD;UD#x-TQD|Kf8bJ1)iA00 zmhF(PP)lpxhjw0Fpu0A@WxNgmEF6|268t%$^m_Z$%wZ#Q^#Hdnl@v3<8CbNV(pHL( z`+UG^MG97zBe1hxssPT4*NFZ%8RVw~>|SmMBLU(*jBx6oiM(I*<1TaY*Z z?pQG%uR%qUVbwVM*sJo7+0RTcjd3g0%pb=+bZUw%#e}P1ql9d3C(>(=+3SsP{8qG4 z!REI{+0Lt1Ou0S)_*-4%^4r`24wKm)cC(Yt;$^GlC*LG-^03(yE;?VR)m#k4Bnvv> z-ST(dN2!Fj`J}Ho(`Ez2yHvt@{J#I8O+Ny6UL95!2<;hyul`9%EYNm?r4;!#&KlE- z$}h3vU_JjUzxUQSL!Sx$~o!_CFU-q9KZLn$g%7fW zy1`wLK=-p;0bAs|(>JG*754JWbI{rc%N}ehRFEcBbCOr`iRZNtcOI~Zs@Olr<$r?lOE0qFhTjD9#{g!}JcM~l79;Y0*Y1vN z6xMjSp6-3@P@hz+J@yMV+%e6qK5QXn0>?-m>^y~Cw<1vtyOj_>1u`Byuw6j-g7ye? z6y}7-Cy|eAJK(1}KTfqYc;5zdGB$@?kw=}DMwmyQZh{PxA0*I^l0b=rySEcYykB8{zl zquxHxeEU<@Ya}mLD<>~<{r(nCu!w}-U_6zzN*}q_RS|A|Z$RCsEE$_Z*fsV2WTq5o zui8Z`_C=pj?JyP}r*DEHqsr$eBVj!F1lqsjB&j2lDYPXRn8SJrhMGj=lNd_s2e@!x zoA|x#cr|MvQAFyj#sbLU;)qev?2n?}b9=U(a0^uvC zRif4NbT`%qAA%`&Ffc#p*x%FrMm!R^DM&^c0$?P^Se$6z2-e4B|8&bEXRtf_*otK2 zAyMToZ1tzy7;hOFkZB~9wHvU9qL36?TDrz0QAgYF~Q% ztJ*3>B8Ki&Gu*W(JS2RN^mD=7`n=*}hdGU|ML7Y$tu0VDP^we$&Mu1U2eFxL9mRt0 zG4CA4qXoW7GKZgW?B^4we*Co^_qtdf{7A<0eT6z7(1vMgY5q3YUfR9Y7u99Y&@2`4 zIgUyy(|j9Oo6!5o1rIai+3z+Hz7?a@6-g{RGUMr7Y3pS9HDdX&v-=p`6qv^pZd;f;b#gtcV1KUr*n# zrjQO1EEC`nlx*rBGIu^BkGg$jSRgF%`QV4q*2^u6t$P;q7b11pHyH~mOo%#O^g%zU5WpZW3LeRgQptBhW|qu}aIReYsR48IIm03KmE%0WKWElo;F*9zI!YnyGo4B^T{+{J4?3e-sIk7%cT0WxMx&*b35It z(JKF{WV=(lq@S4I6F&~WcbCthar>V8mbXH;baw%FMYnLM%RZ3ChCfE|4X@*5?UK1} zc$e@aXHqLhUUtD!fmY}9!36V1?=x&$S899DyUCn)%qU%6F4`4N+83T~w)fqIL&DRE z*8zw)zW2vwS!NQWJ{7JNt~F6*%B@j9tf~f;lbzoMPBTs$O=}k`s&l z37kNVg%?b{r0FH-Ly1#~)9KzS@+c;5-y85^`;;7?yihiDP%JR}W!GrOXd5vhzs|B` zU9A!_TtTMuj9=jegR9pMU0wD0fO)5CZ^gry7&=jJE66WlXJTiGme(x~r!TL0uk}x3 zPirLUBypb5N;>;aNh6w@i40&T@msf_#HI5IOPn^em5>oMtS6gurUlLsT4wg0xM~?~ifP5NT&K?Ttdl zVaA||t32Bm{yN+a!bjS*4z(|D%+CFrdHnCeeefTWKhdO_dRR{Jm9CxpQYJ%QPfbr% zPrcRlrty56EHtt-a+ge4l$GvD$=7L$1A5VCU@c0)=X~#tXA12i?Cd=tb{-x(`>BOz zKlKJ8Eom%dUa)ytAK!^Aw<^0vY(-tTUZh^AQz)>$Q5VTnQqM|g<-9P~{4h23tz*z@ z5WJH{Ihs<(G0SoGPEJ$zo7cBkrOZSxzEyVPl(h_*A*I2EoJnz3kC8*ZY)i?3w3sfw zF|U;r*S!JS*l`A+)u-P$r=i$iLPussjKAcz)vY{tc6rF=5U4{EKWf1wE~P^jO%}yW_duFM%W?mI4L%Hv=X_Z+I9x9fOzdd_ey@xQ&Tq)bzzQ>i14Q-eN8f=azraI(x_G zi{g74zjsDqPSr;%RPLy+>0&j$-Ghil2m^`|`A#)TA*ylbHx>vt^WheuJmK=gHXehH zeFqNErR*06ec#2spPfIcgPy?Le~#a&)TcLcK@HknAG?0>S&ZEc9`>%DFnQa#bul!_ zd8EsylUjZ201chp8s@A=yJ69!a0`Z#(NpmfmsUW#GLB zHX%LlWNv$>{JsUf4^$7S%`fEZs_WvYFDqKCfo(iZmlG$>WT=)X@U}X5J2VMXgvV1c z97%GEt_3pf{n$<;;PO^4&hGSjj?`K{D4pIL3(XewP4m235JkC1jKgzW+Iu=DK1?22 z1XkD{^{z_*#TS0h>Hr7fn%ma7!=opKOYtTIoo-w2f6WTai_CMI#J8;lJ%jaEWm>v; zt!%CP1&O1sj>P4?Z%@a%6?ImM{>Q>ovQL9ka> zJMMdklE#wV@sS*DfVY28D`({q_%k1mnCWYiE6;R9{@uW>u2)UaoxuM1Tux)o*!$}TJ7qIQrUNp~@=~s|KVAs6q z#5&LB>o)9MtVeWqUP2fyUKr9Ucpr)3LPL+Nc|;f^>w)yr8vt`_kO-p*#?1G#>l^1A zPj`xsUCU;0T$rGF4UHl;;~-w%>8YF?OTDAFFbg%+6-OxE-JOg1xPk#ePrCKpU7+^e zooVxTGxIYaqsn6$D%nuUzTX)<`ndWV>yh<boRllV^|1R*Jmfgz2d z{7Od42lKegHi31}cQ1Wy<*HT;#&_+^Q zebx|qdm7ZSm)kM@1-g^`MH@6S)Ut-k)Nb0uX3GiKV15%5|IC6hxR`XI-ZQht_V{;f z@yx($rH?rOcnwk5apsO1kuzfcHHcjMw{fnlqd8U zpli5rU6NlX{_`-+jm^r;%(o8L?e@&fY%ar# zUujOxR#wbcqTW|AnYp>2TfY3ARI{wLr(xaA%|hNA8|K0^E<4wpgN$AD4+S}VZoz#Zx#Jns%{gvJZp*`)? zx8dR8l81G5eBK9Jut)G8KUzCH9Y*NIy^Nf9o!sy4u!)6;6yu*juJA~nw5p_Yw;dcP zpH;wO{<=tcsrZ#g>8>7LUOy|&Pggq(zX#g*Epfeg;p30$k$%oF4$Vxpz3zgKEuYRU z#A-be{ztC-^D=K1-4a!xk9qi-o4AO`e!qZ)sf$Z)ALNR(etf^f7g9{ygq}O?k=rjoCMSOZpZE)t{KY~H}2@v_6`bK%T8@M z-O>+u@HZ}HuGJx^^v@@0S<4akUpiU!?r~bI{}~$-^CK{957Om+=;W)m9^h#C_r;rI z3ibpZaf?}NI4`a?+9#jo78S9eR-_y^U;*OZCtf}m_ptvm4>bE*V;u#_ug^IxujWYl z2QsX72QC|Td-QT~bGNI4N4f*yS8(m1z`(%MyX&h^ll88`!ot(&aIKN(fpN7%!o+!=4DN7b<@wVR>}=8zY}!^Q|EJnp|l9Of0hyG zRdI;vX@7WI%*qz%-PqF75gZ)LVjPnG;Q4YHrg#%lAaDDM9 z|LYT#FKa$`-&SZk6Y5<<{<__Dl6u;^J*gQl>SMjveV5ID=1^@vk?`3lwq{u?Qb}L= zNx1h~XXhzsN#nCiljl_YUmJS=w6C%yznaM?VJ;QL} zz-C&P`Q~rzk|3=@;lmy(wTumcKgmqg8jXeD; zEd}M!`s?e#^HcV}ZU{;WwCWiN7ybL9+u&>L8wwczq-XhSKNeV&x6zBd5q~Az+yl(} zfeytLt%ZN>N8`;$SyMjR-+z^6Jzt-q73ru4m3PWt`-vvd*yptAqx*|O$0XMiz!8hS z^4Q)md;9Q&Swj#ruCV;=$>>B?*?jyF7AEST9+xjQUko>^oAa$t{5Mmp1LxiZrE1V0^zIY6SW~qB2s*()vQ`yX-nXxz5v#8O$}-=cRPO1bQ?zm^#NJt$z5eT zLK!>bcUWHXV0oNPgJjrmXs6}WNYZomUHHi!MQMb!q&3*k7B$~jpTobCtDg5H4Vp6O zlD!T3^I)%yl!rIz9MejD*ix?PM;p2&SH5U2e(zu9{NTZZ)^#`{eZ`p!2L8UXk_BJ8 zM)r5XPs_Bf&d+P%A%GjX7FSEDh_*KU>sXh7RpWM#r`pw2!eF&l3EHc>U_^@yvVv0M ziSyk296=(bxz|@K$o(kvTf zbJGk5ubNb88voPV zw0|CSo^e|bs8%iiYutSZ*t;Xx)Eks%;1Tz|*|jdX1W%;r`c415g_lb=tUc08aV5R` zk_vS!`OREzq0!#`1=zf3y`>v!VMM$vARushP?GEVEK~6t^6qq{O~5tCf#q^;@q=r! z)L;VjVqq(|dzc0Dq2^Ac{9=8pPY-HS-5#C`SMNSn*bzEBC{w;(r3M|w<@!v~OZlyw ztao~;7tz(7`&FS&EPv{5puVky$gEXkoav993NzuYY&Ws?$0l2 zKh7S*7zly5fMC_2zNcthxVYUJPtJ;;LxewU?eU15L(+3Wn*lxR5u3NqM3-jOsvPoN zj^>=ITUy2iq`Xg!2#y47)x=lYKWhhyd!NRHNl6T=_YEf3yyuTI51ELlwH{8>roN31 z;+P~{^ci>;Mby?ZQ*2~~{mB^Y5dZDih)Q&?>1^0%Jqt_o^dr6-dXKtZ%g@k0TH1+| z0GIX=s%ubxCq4*Kj0^1Px|s!dmM~#H94I^?aar4fTx`0+=Txtc6#)lJ;yczHNTF4s zh;B5Q@K*8_9QF(gouBo`9=#uny(oyEB~DeF zJ4Tv+QYv0bDkv%P2dZenVhD}K9g>FnnAy$gCpWQ#fjM|Xhl&0rKdj9AFQDc6!y$9F>j-f=%Z(#3!t{7Ym zl=Gfw^-Zd2NT7U>7YIWDBwn(`P_XOv`7D1@uK*P71{}pP09OEpXFsCYR%l&1e;fXY zu89vsBum9=2cC6Zt97#JRvUTU=u$G0z83)^vslw5`|(~)M!dFv!uk$2J5>4WUG9|F zCzm4Z2{28c=`>9R(el^1s~NoIn+T*#0Bp_C zS&u5oajoe_G-w&-R|}O{PA;Qz0k`WbyZb-cfBA2EckfFf5rk4_i}68B+9uZnXL*Rp zz%b+Mw-ColUz%s?=)OkN`#eL?PDZV9V&%P$;eOfZ4|+|=uOg;TNtE~0r*bh;+ z%M=OLU4e`P`|maxkd`f~I7RSl+ZAsR+zCkNB6!o$-o6?|BDHB;ci+ilMoO;wD^2-z zu#&Bu*QXb`)c5q)qVXTRmTZ%y-^3!Z&n%I8KYaxa4Fxrs(_#w2dGwM&r7p@QA3r6N z9WVr(8P*r@_#QzPMrlv?05E@45~UG`^i7CqGZ%iC$d~gMQdF>s`v@fRto!7g)xMI3 zZtZ2#OKuLyCxY+0+G7K7XlZoNTuo#BQOq-1LVK=M`b3C7dTG9v`)k#4x7iS z{`-4-U-aj3po}l|G7r3lNS234?QupM_@)jc^dOa7DZin388>LnbdfPxm%pttK+^2_ zpe+9P;v;*gT&3?^@vb=p^$TEA>_{4t4eo2^e?=n$ag1raeL5%MKv?9942ePwTu{e9 zrl%F__CS~!4kX;#<-{iH0DgS6buy4zcs~tjtc=}#QJ3io=df(dhbQi&np`vca^@Ob z%QCFh!qb5ld&E53(&(KY37?3_@yrH)5C5Zg_kc)L`*g{A#aG0h)U_d!g^vAvh!a%Z zZX_AUZ~QI#U_9E4RYE+lD1_guljpy#(AtoUcITfnY zru?7so12m@&>7!7MXIFPf>xXBpyS1mp)%D#vTf73CrcW=96xHK9Ox3}{A8c-_tnre zkF{dB?M@T=HZ(NUySWUORGp=C#(MYNdoYeyU1e-iPnLQSwNIjRl%Jzd=BTpleHy(& z9w4(88DkjI@YFwx-DSB2x~WIn_+TnEp1~yeGZ%HGvhfn}G8-@sWHScXeADm-x*}J( z$2<<=O>{?YA}q(LBzeAz_KgF?Z5c<$QM~*qVe~wUjbly91Y@9~SNc!;L6wrc+b}%h zp?mf@z&uhcy?v2SESb$K1x$P@Q27|(Va3(P_{G1<;{Y1?=6CU+4_VD9=z?JL-e=Wk z0M1A6zof>@Lj`Uz!0&SX)+8Y#5t7q{)MIUDfoCpsIxxxW6~z@rDKIVgxLa`r$l7vY zG0X6ysNmxsB5;dh9Da<}vaM$U@SjZcG$%5Ya!pA(fkcCU^Vdt$YCbtHc!$t2di zDVKDZuAj?kIuNp`Qk;(TG)+iPCt--JCxuj!OwoLYz+{b^rAAT6^?>R0pI$xnAU!i+s97DFl|G%*+_d9dA{B(iz zHz~Pq)Z5+7+uyH1OHITbPSamOOBd}XlPylmUe}k?<9_`<1OvLDTWV*coaJzeGiuPy z7e|yLN;Wca^cmA_(fY`H@!#j0&rTb1@3?rxR8>{ms7(>=k)O=9^Pm+x#z;AXqecp) zN$GkicH5T3IbiJ#kuMsb+mz!Q#xYIM_lGYvs+36ou2pts;U>W*!n~3DsI^8u% zLRIitoWk2>$3pPD95yT!7~CdBHSEJ zeUpF(HvxvmeUsL!adf%weVJI+8)_(#vE^8#z~8f~;8axSWRB*@`nfsv8ngx? z-LGYOu$ihGq&-FJ({QTo`=vi;)qkb?4(~PCe4)X%2EMv}*V#EzDCaEDbtd0kSC`q= zJxgKjucA7Q zp&|HbT79(sk$m80?q}WOgNp;H#ujfM28ojK=|t>#i7IM~{BiXY1v|hIp>uO!mE2uD znhBQ#b^MFA=ly1L|A;PA5!&One0xcdhq$QAci$S=`#l&Qd07;XY#)ro==A^@T3Z7) zDeEldro}+}ULO3gBi4nNrYBiI=}(aOxCrLoXp;@?pcFya0YDgx)<#)e=w(%IO?wrj z>`zaExghq!r^@L$nRe52HE@i~v}uHfb*b)fer$n<5RZ#^KpZRley&XfI@Ic7X4V7w znw#rwX=&MZe7e?la&i}V#{x6lb*jllA<~U>b<^eO*Kg;9S7$49UnAZN?r+Gn@s>6w z3#!dO3fL(avfYC-d&(E&wB~0K_h{fb`g)c)d2w>2bKAxD7CY&GO9mg%W%1t3FD`Bh z4Mx8vGMIm;9~)KMc-f;m%r$Eef4itRte08bWOUUk2%xn+dOak3ebaDqD735|?*b;- zRukQcO+LZhKvmL(Nv772$(4e4m!AYRK@7B4{xSapGvK>reQ=x1lni1q(X-v*(|0D;LD)9YH=XaQJFrLralA% z8aAf>o>+?irT{ZPM19zT)*j#1V9vkK?C>*$siWC!_>a|~w;VEhOI75gpQ#|pj!i~# zxT`3IJZP1wI=W!XmElBYoUU~5ezsj!Kh%$|mR)&Z_a{pzFw{uayok3F_-^8RsM%AXlAH_GVg+Mdh;Ypc`OhS{03?e4_c>% zMyCe$Do@H?;o;kH1*KkJcDTS_HyPRGU*2%G(4|&heH5T7ULLoatJQargr*Ij8gxNt4C78p z2xf-s*4z%}98kPdU`IfX8I`iJYZu)SSNiIp-%(i$-Lg7GX}V?C?o<^7QS;~F7eF=7 zBo3OU(lT~o$}`|MoCy3X{jV>a(GjsfCC^YVgZ)J347peR_>tPwgF~3_2bjKjWNzL9 z21(iRZL#?3TlDpmO5pQmc+^S@DopfJ9?Q&Vc6jqjwbO(75~^4pFW5UYG)Kqt z1tz65x3sqI*qn{G|1t9ONq|Pkz!7nCl`GM&Y~Y_ z%F7LTVswPeiwA@tyl$!qoCVp*LB3Mks!P8a`YD5#+<#aWg1fvZu`mUzeMvKPW?O0; zc=^avd`NJs=(vfdke*_5YQ32d(|s`TWE{0%Oxwj&u$pAL*}}ZFdVE|7zqm&A-<`B{ zKTK6t{QMab6m&gsN(J?R+n-HcIR$-xd1KvD<&rda1&#+?$VH#{KdHm2I>st`adzx= zK7^bV`voY-z_n@?MM^w~1KGXVZ=(=u*faE6e!!eOBj$sYrzFaBOxnw3`wroudE|E* zI(iICTOErlF4eO74NL!>sQ7s=x8zSDodoJwKPlU#etAw+!waV_pL)%IFaG5G*sJJHKJ~&^ zyMM7c_bg5733OhBJEXrLu)o{O`&wxZn`hyt6zjz}>LFPKgDk3y*$^j6N=PXtCyQY4*vh)vor7X#Ugwg|*1##~+=T_Q`D!xK`Nlji9 zxQ@hZ0|Ze&jGi;U7D>BtN4TP+bc*VPlBO?pIzS7&xv07>*@AC5 zShV~cw5r2b5%>%;6)Tb3?>MMTrsEcN;t?SWOVwdk!Nc3)75Q|YkBlTcZce*+j0-HV zaIH>@CrbQS?6fraKx)qP`suM{smw5LASPZ?Q^Xyyj$!V?1*merPeqL!y4+Tg&T|W7 zpSFW{6P2+YmsIh*2Q2aRK54?0^B_&KfJb5Is-u!YF;8RIAJoqHY~4)PVRECb;OdK) z=L2|S0JLkm5-byZVkZ{$JM{zo<{f`7$S-bDO!g}zzMkNXFEXsHf4YZ(;g1th=Z<#| zw^;be~E|?Y;&Ft&~vK2b2)()Gl=Eam}tzVsd#I6K)J#^ljce8-Sxcc z5)+Yqpu+#5HY`s{>4;Ib{pAjc!MfDFJj!$XXYZqa&Obihdb<=*9w>riIzx4Zx9kP- zFmyVMxRa;hSv^M$hTj_G9=n#Q*Xqu9A8T}~H#@I)iw2EOi!2K@FLs}pf(Lx?w+nm_ z?uZKnXBJ8Xbpv4wBo1@u7~@4drCC6zQTf$v2s&nIN-uD=L~L*5|9(OucGlVQ&ul$j zxFR>9fEtt`3LZapF5+^6pubgf=Npb_Q-!}AfeQij@>Ut`=$gMbS21{jxWovMeJCLY z`+&KHJb0`V(asK)9kzipI#T_m4?h*d)MkeF>|XvcqaAV;tzINk%?Xx-mLs5H{8Dv0u2 z$y3=Gm;3HF_b+;NKBPDhS?n}u=D3`dtx#L%9XOnC+Ta^z-Om<)D?6RooSkpzQ??#zOqdjx9+G^Uwx%Y zWsuwKQor@hN%KxH`6NUw_O9lfStn=5Mez$Jj)KiQ-?FIf_1?bkMwT4g z*Ng`%ly+>#D|=(9Xe3(%8R>ZY%$%=>HjNR5~sl@_cokd!lzI zKfM%wDEa(QutL>PJ#*EpJqT;b-dC{Ht&;ve!%>a>jz5El*)g1yZH9E9S9owh=unez zs+@HI`ugBi+~2?_!ak{trnT%x(us?j)wM{kheqU=cx;w3Asyb z(?PWkw9VXAa*zcnm^*@HFENMVDhusOA?kde0<5=T)WKM6*}6>cB+@zl=UZJ z=@8J}idweAlP7wQO(akfsXaJGmvkZe14-a13Fy0y{^kc$_(JL}v9SnA7Vtnu4}jj% zyz*PkyrBA>2c5m+Fk`G4#AA6u1S!E12Z$j+O!GbLzdw-chDl;wm__=`j&2HpP=%aF zZUKG?z$(O0R#z-*ikbNEa>*Le_So*`yQ<|nE-5K4eHz2jXZt+)SFLfSk+;34cdskJ zX^w4JEzvgj1F6VTz1ioc#+qgpZIxoMT-M%K3(j3ZVONu;- zJ-oynYGO#62p7^IKnExv{lt^it}{ZDh-St5tk8prczPnbN0X^)RuQM9I_?1x9s>rhb(X zX+-TZTCuOXK?S1;)US78_8g`POA|kJf{+59jHXWQB#XB%O^+&to!6TK2S5s&ZHtRq z^sB3@WlqjJ6zqri%Tecv@d1GF0I|c3g8CvZ^G7&!uOWy8qV2g%4_^Bq=a^u6@z>_L zaorPzA07K}go(_7o}3)cUy1#Tp$d=b=}$E6LJW~?qPa~V-2c|KhUo*K0zU<)E2a{y!9=}`~KQ3+bM+8K2*0HqDiRl!_W(KSc$ zKB*npWb6hZ%@7|D`mZ-*rXYlTd!VT{RTz!|EGEHr@VSa$roi76&lzzes;TCm=*#_$~UFqa-V~)2+Y%f^9?unG|f?f((5;V9Nht zWI-t=>3MxgmYWcG=rQd;F7Nh@%DuS9v{7$0<*!oSjVYLjT5jJ^*w6t7d-yD$@x3np zmE$(F_>0rlaaaoJ=EY&n4^OHuQIu!p+~;4o_St8ATg*Lp6+xp-i6e=y)611kr-Ifm zFWyzzX1c6aMHt9@sidi(564U(Iy^clTC{j*X{AUR-}}R{{6Pg+)>!3f^!o`UxpJc?0v9U2yjw# znASTHa#rY~=pZw4HR-=IelY-vhp6Asz5cD3ZA+n|RF(~?K@p|p0wysC zayt=-*l}tT;1716I~Mdkcmx!X1jF$_zzewUvvmOU!F>jSvUkIoJ71-cINuSRs2>DD zhnZBrpS&`V|1iO6td)fCUB2t%pp|IK7}kC#6p(0WtZ}Dkd2Gx61X*tL4c2^jXrSc( zc3%ZjYEIZ?xYW=r*Vv#@wv@~>KWWF|$am@sND6SU;Ga+Iwz7K``A|hA@@Zw5lgP48 zZ8@(~6wSQcq)3PQT($jiWSj^6=C9#USlfQuha+r9S4s9fPwV@fCRM`PYQwKwwN#74 zHH2dv0eP6il!_Ut)*qr!NkC~#8+;Dup%>AKH9<-PPx;*txJ*5XTlnrjc!%S+dCbGL zqBHqFTkP1vdW#j}0P20)O0F@Fg4NR)228&{D5DgP1HG>XZpr+F@H|#2bH!CV<~RRTeR~woNnbjit~wG*XTiCU)-qCxSdC@I<91-|EjU1^9P>= zDDaxpJV-%qYpf_LetG$9X?dFGZMeD)NAn54>Obdfl03s?f1&qtelZG|T@5rVTyFIJj34yQ8F5PQ zeZ6KpKX)%%z9}tl31_pq-mK&{zWSb4zYfFwW+hThc@6rb7>-MAB5uz1RL)0YD_%Lo zp$p+#7C*e2r<1=X2N&*c#O$qmdV02@k40L8V`=YtLQ94Hf&HVS;~l=RmRjDku%2(+ z#ed5|Ou-24jEhs_x%7-$h!AJSdv$(vcIHis7qJpcJHLWXMRK5!A*E4kZ4$x{GEjV5 zDHf;e#Y9HZtPznG-B|nznHXbm$yUV{#c|2<;`ukt8TY_<^fqEVrz@rRUj^w=at3_4 z?PHXcBq`7icxKq#dYWTG26?zTa`~-;XZQ`@&7;4K>p4YFc9J{T855C7?){?t@Zi){ z60G+UKX9R2P{>B-I;`X>Z1&?%F)|Oy)Br71RAaYKi6)JvqkKW@T8d%sy|8X9UT6k| zwod!xrI}LrtbZwWwxhTti0dMnj!%RagBL^CW$9R<*%|U4QW@z#J^jwvOUQ3iK1jYX z=`TP4Eyj;yDQ>lc-#GQ0OUKoNtKANiGYRNvs7GlcaX$OWJlV?g#GfYh~Mt_Y{Zd;q`LsCp~uD ztsu@OVepX9vh4K19^dIZY1bQ!SoV4RH_k|lz-Xkl^-FgbRg|FGZOmE=g3)d20SF4C>O7+@OU126?^G zpAS-6z{HL#`{CgcD>pZx^M;JLVWi}POgxV1_r2?YDLtc|u&1DaZkMIk`8=bANUp*$ z7G)1TaKcZEI-VQ*?WK$JYnMg(wHMf>qafp!o1e6_D;pbmK)r8seHs5e3Z=Xhj8~#B z(!PU`$RM8IvGMVrEi2c4hUNWS$^GIQc!$k&>Ev~Ny5>&ut@FM=%>eFw+|CWmRs1Cq z4+hl;F|7?wa*X7M(VCr}Es<~Ju4?(m2|Xuo*dM|s61=QFD6qj9>DO3lJp1o=;km(2 z1NEPOrRIGk7O}NBN$_?p3tdCtC0{l|Ig|uU|02XdcMszf%IB%m3mWQ z9qI}@sce6<+w~(~_lTA?{i?WXsB9Y^H|nMNVr{qiquN|5FshvMA&9xLt7~*L$}dJ& zVlOO~7H>B=Vbxc^G4XGEQwC${{>!vZ4Apx}4__k`21dA;(gvCWyBCCSPSrpCbTg+ki&_`^||x&Z&T^wNn0V)5L)7eoxRPzkf`}^)ZCRveAoYXAqle@=K|^pWo6S6h#M4qPjT$ z!7<|&%W}SyV};B9$V6G$TmV}|bZ2b>sDQ*}-uiDy2s>g-J1z=}BF{3RLIYL3da6r$ zgz8ML(sl5AUBm0qEK8K{Um9~@L zyO_~FADGsPr`FasW(J%$8*Kg3so$wCtGiQSNAm3DQv1(>O#NjaCC%uRq{O`uf z`9>igJ`(qnzIj7KEE$(}2pd^x(6B7~O9GnfC(IdLN&eggs)Z;60_^-FZmcn)0@^pU@=+anKZ!m6E z%S85YCGe%CDqb=urtB;Fc*{)$o5@8|r-qG|qpJT6k7}!~<|cXO;<*+R=}AaPC!hEv@X!M_D_l347n5NfpUb=x%R*>_2ZQ zo%E0Mo*vY%ap`y#qSU`#mRUY0u7Hszj@$Ns%H^Shv)#UP98&#rE)*0r_SsDY+}qnr zj?vW>c)^+6Fuva~b^KlnMC*2u;`Vz6($}_mhnZ%tZi$y1p1P(g9u@B_A(z@diqGX=CtLpGR zU2$D*aY;w(@m~Vhr)^Oe{!ip>ZA+FLymQrg&<99^gIV)sO5D<0kAu3px`J@m!XSGH zLqABG#{2oQ1!}RGr1>?l+|vj6ne^+^nf%Q^`dtDqh6PUs){)S82XKkNKQnbD6;^%d zaD|AI$>CdhCC}W>4w22|9ozWnqc^;_MIQ~M_7!)BtdI1sW!IY~D~N04pQNR`Y|Jei zwm74+u^*!ccyG77RI*= z*sggeQ&7|`;H}s9Ug!8IF~C|He|Z^w-;>YQZ6>!#l7`@K1fVlu;%h#jGY?kLgY`{q zVEqFR(PP272_^wdJ)IkO?R-TDIORj55MD3@6Z@_ZjY;Alc*oqGef7>3>iS|G7Y{kt z+dVV=_p;vwRU5|5op<4V+6uu_al@OLaeH~8G1EpK7%u+;jY12PxDah^ZPm*dj{)O| zfOa9h;$$DXv{f@mPnc;v;_AFcgarhy=YRNU6R}UOX>BU&IE8guiPLc+>YEd`tGYEx zIhEvlg~^aF>ZUHy`FJ0Gg@njCPZz5q_tVqT5QHMUjIQ;kD@}heaQWwbWXO`WAOY5v zw})aMHuCyjwR@ihk=*=_&l)OzB;eU^9X*=^qyrmi}PN9_1pp z%fNWmy&kaH(4CWu++jgOUMQ?_(JNe`jFCHvhUF!VBSu*|g2y4W^EGXCb#+tY8{}tA z_6zNv6^KVsim@-+&_J@9(U1+38hb2K7%`p1PW&dg=reKrgt?tmaE{U&`B8eN@0l6js(cxXpY; z=3N^z_*Ei0}LK zJQKmqo|1gCp0l4ZC41PMC534c3?se+6-0Wm{uxe0ubqmK^sYTR^zN8CUF{%q+ zBqd1bi6(DlnO_dFP782tMQavqCI+tr_g8_dGY_1P8*UdGw2mkyjw*K-rk(ZQN-+72 z%D^xQ=3T2EA+o>LTRtDbO{|fo0!JGg;EX&;j`lM#OnQH~+kpnTZF03~M3Q|l+-jQS z)SbGukRU+@s@;>y=8DAY$4*P1C9M0w5Wnefl`tCeG60kq|M-;-L+@d;KJxn8zmL6 zp&RNRJ5~KgGg}8i%g1H+JuHP)awUb>OmX@PTh56yO+jz(nch3+;d$}JmR8UDF;i}{ zuEE$^b>_*Xo#AtKHp;ZQHt<7yU(kqt9UiTGcj_5Q^S@&br1%dWIL5p+YWG?D9KgF# z07j#SkkMGD-fXMBiQsSjUv#}?R9k)bEn1{VDbiA;IJ8JhAyjZHZUstl3tn7;ThQW? z0tJd&Dee;7Ary+cJHaJLAVF@P_n!BR^S|dlW8_o5>}2n~)^Dx3)|}Hl_$2H@S4W@p z!f7bwHM!RLLpSblmnO5yhr(})A3SW8gHG~C*b`O~=gf+~N>M30rT{zZb78Z!hJ=`R z=;C_f9FLAx`qzidGo#k7SstMK_Suyfu;-RDjR#dS6)dgnY4CX|uN%d7q|UQ0xy1uR z|EmRH5kLBfVb61C^td_pF2lk4vb@;`HDYkwuU_smm{4vxk>_CTH!!3ObS8)2&vX4< z{JS;Ty8D*D68wi(;N>zW#P&KDtHuyP|2=R@`NR-!D#|LAGV-Kx#x} z1Jg{r*_8-0qCblG?0Y@V>Veq&)8(8Rh!_z=rZ2p!?zMmRga#0qLY;LoLU*+V@YlTz zobt5pdjSzyuisc3{KCL;LHwkm;*a7K|B7$TddTwW25c!07W-$Rfd3lM*f6xgI|pB^ zDinTmN|be5O8XtqBQVT(w~Eik{t|gGcbMt5E!cj#>~q`%T~Wzga=tn-@mTM{2QPD0 z+{{CL|U*>fW%-n%Sk%na)G76bKE`VpR=UtR@wKV)H8O()jb z%^vyQ8JuO`QPGF0E>Gik9Pp*cJdSGg3%)59!5}gf*y%sz{i^D>cw;Q@P0Npy;kp+C zIAc^)lv`f%>MTMkjV<_}G260NpeLMwN`U7uL)XD#X>R!^Cr)?@ zDwowr?Hqqb1nRI<{aawEzYUf+nNkR!-M4$q{rkX)-s2YKwqkh|*kFXaAyYLZpSALX zO2eY!tTyOt^z$2?Xi(VQQ9ORiM$-c$tCL**=ov+rTTcmeyljo`Mjvk%LdVSH z=%?^}L}tnjqhJ%2rS8wewV2FqOqL}Bh`n^hY({LU0HV)18}ejfjY+oVu^;Jw>hr+X z40iVu;Fo`pB`MZ|+MB7af<}nSaTn?L4RwlH)+;mQ6`PImOYPn$12i$59LS?{tW)OU zH^?9*x^O|qKW{xX=%Xu&~Pja{Kb$YD=MNk%=P72 zxKuw;eYJnxJ0l(G+=lE&au!O>(p9gIp=>T7P;>w}Ui-PK%I_6KP$zaAY3H``09Px) zUH2zo8wfdU-(KeL`s#JPTXrY^F^j=qw(XeqD=xr*w4gYpDO~3;ii^}I*K!jy!+Rbc z?wwHHD#vXD6-|&F=XLG5u=kl>@;HUiy;MZK{~YiS zk)0U239KdwCbNm5%P$qF7fk-3?Z$7r>WX7OUuy~S40GDoLe<;vIaGmBpIXyaP9Rk1psbt(Q8fh%vE@zg46+x3PZf$F3Lv5Q=}IQy*C(iwcf zZ_jYrd-Bi+9h*@kNRHiI`FF<5jDKlos>Evw&G;B2_Hb}=0Hk$l{-gDFHI7*Z(7$th zd1SSgi`YmlazoKugkXU?Z{*Unk!sVj zSw#VtJ{}7<3eOA+;@w$8Z2ytLkFJ?%#C6!m6P#y;$MS$76#`9}54h_g^(IF&`X~Uu zs=ds#TjbcnG{OMW$}Jxs^v!!3HT+Du7*G~*%q4%OON2`gUU*{gJ1xgQf?v2F57?id za6QEn782U1A~KnO1de$J^W*hC*ox%>I9l&Y_=-q0JT58WsXxgBV3${E>;x0TkG_ep z^*_qF0U1_H;b!{gf!b*x$=PM%K;f`vFNwDwF+}X&TF-t-X)Kv#cohE(k+VXbXwogP=dBjY$4x080f+D?{>>R%VloAcI#yz0%IHp&W$^~v zKMmGZqMxUOQ6v0JqN8Imn(_LCv--}8z*k?|sryl&-HB1SIk#302@7#1Itt+}8fg2y zMblm9E$HO)7fKH%AU!C5tQz3gT!POL5pDmbBYu{uw<|UPO zfy(|?k;X9ih{CY2RME91CBo%hB?-ME$7fKqzxc1_P+zD-Af!Wij~ zq8|91^||@emw0?y8)|R%&=%9KEw|a2N_~;qSw?{>)f?7rpAyh^WlSA|3u!Tvt&M&8 zIYUJi+z)gpE#JPGj^4oQ_J$ZLlN|L zy}s+fzTv70k7|bCN}{j4ywl!Ghbv6qt16NffIq<)u?k7#@$SU0!Yyg|usXjLmnXMH z^Ls*EyRx7_U+eET%c&0bW=XOc!L>0mB@&2wA=`)QU80@}6d+1nOh9$|@R}-3mRLkkQ-gSTiD8QqcP8%n#}iu$ zZl)8vcQ@Fptb*6ZnF&VQC&v9ATf{XkE^C)ezQ+49O-ZVz1io#qv8p?z9Kx^G_1~v` z`c1HyV0m8D9~RwawOi zcYMFJ(#yi=TW-*}r_zQ}Jc!k_nhOAg6P%s79HCe4fqR2590on%8VCd7#aP>J5--~) z*nNBAH|GU>gDZS@dz?A;(d{_+B)xo*f0TDUSg@Yz-jxyWSj|QVYDNI zlpD2&zAQ(zTs`IMGMbE<^1t%HU{PsX75Yqf)*W1sA(EH_pFDK+JggiIS*X(73?`O5 zAU=FQrxhrBTLUUH!^0|=KCT~pZWq8rcKU%T5OIWZjgL|o5Zog2ga1`+lEi*LPzW9k%!S{F-oG5T>IWKSqyxBx5Z|Emelj8Gh#ho;-Dk#z+(Zj{=$3pE^&TQ1$jD`pdAIr2# zEMZK`8sK_(Mmxp+N}PME`?d6t)JB(BWm4?`{S9k; zqc!~r$Xd?)OnO5Qir)@gxX0MqG}g3OOMeiQu#LB4^Lrm~f(-01ay-VjPda+tfgMDG zC%q3!_N%E3UjH_Cgid%L+z=TxcRk{)swt_eqcgpER{@pUd(EA;V$~D}cC0;|5_3Ng z*V3H38#AkxjC%ATH95wX{yj$63A_A#Sx4c;`kX5bqAhyw5l=L<1<{Z+YCC+;#yh_< zfNgI}+f)&L*rxAOGcmU@j!NDanL_PHjExYyUbntqtG7H4yYmb<=J=^kR$U^{3QD|R z)#F9KAaGZJkXn~=*nhPO5zI}2)oAV6xMo^y3Z#v}FJk`_@u0<3;BgiIxaFHPHZlca zQ|Z@luD*~`Txr~^HOFjxSvMEAyg2JbB`NWH@Nv(4iP{VaTiO;cY+mG+r2ZRv5aP$T z;$X|3-z(xOBH2WKn?0&VMt8-}a$@}UzUwJqceK((3K36SH|J^1+!#=Qi8-bSQR6b@ zo?ucgS-Qu$>AiefUlir16_~O?RbSU-}B86CC;o3~> zIEYJM?tT3Q^Tb?}tX#z-gt<`dbqqsuS(NoZP-S6Kz}-pw1#(1&tKr>*W%qic71}v7 zzrf|~UhP?ZeW0(1AC|cHDy4uhCm%YhroIT2hc3UEq^;PlGRvZp*%>5?is}Q79_y7) zzs=joDNL^`si?QukhxE{vL&<*_s2|5fmT}nRCV@9(TdxzbCL79!)LY2U~RQ84}lbp zCNRv*5ex9Wh<+x@3)`XfkiW!WOl&RW#o%)-pIDt?Gux@AG+xz7AK#cuuRJrix^1W| zgtmoN^wJxBkOQzh7g3hHKb>*8%;m<*=e#hM7v7298yv(4^~!_PvPQMWwVE$R3Bi}E zwhEj(pV_cVsEnBzOD^ronoP7W=6nw|E+$0grr+>#x!mX@y24~7%eL+>E83UkT?W5k zwBJ_T;K})Wp`5({TTdsXHxZ!q{7bKtUIh$z+OdntUZY=dPefK)_^0PHdTRVbL%R>X zrh(kVAkwqua>OfmbGp~XKly3mHEekY^enOB>)&UIh7M?(bEGpP`CdinwIXA4Idw=s z=^AjvZT6Iff&ff*t+CV0SoXkx5LmX{=biOJFXLV@i*xm~^76Xdd$G)qWU}7Bco&zM z{YEzY!$DuDa(KYDdjKNEG7`_d9mjOX+GzByempIQqQb#@{5~z-E(Z=6J3%ZF-$}bgX4sI}VF& zW_RrjpgP|a%$g0QM+ZId5g`qK&>wItoBl$}hpzOz5%K^2nG(^qw0XbM=0DhgqFGK+ z*^ROK(jIaV-U0(Un@_fiTQ^b+A#DTE&LXZM48Ynr_LKam)lC@>Tllv{&ks!OG{pT8 zVX{seE6)OR7o7R~U8s_UHP5ZRo@BlVg_rXQuU;F^C4+w0 z;*6+G0g&W~(zY}BNMb{`rOdZB8f2@vob89&fhsC++mxq(TO(DSq4@~kmolSzx_Vps zk~yKsdG2c1R^H$*miC|te(vRHY0UNZ#cD+(BUI5PuavFY_Ehs3WRk}5<0-txxp6g0qnbzV=QS8&P<>#=#%>0L zBEaTdUXCvgspe5=EO2I7)WG>KJy_UmVf@B3-9c6|t8+%2=ezzAW-@8k?9cb9h2m-A z0Hd(`-&^SYQv3OR+erxEx8epX4Z$tWT0TwD-`9NS$);c*t@?ghFE)+F0DPGjtCzaM ze{53L)_wsHv4*#M5&s14y!mrXNm{4$z_CSUG|bu(f@@R>byzsMzqsT>9@e3FyhV&E zl8liXM@yTBP4%%YjWgT)spTFkwDwSnSbA6hymRlp$>H0zp+XwUE&G_E$EZa;u%J}E zgK+57463lKP^9gHnBRKv-OACDkKNc+FJWD*W|?%m`C3w2i!o9ZV1#Tf1p9pQRQme9 z+a|`1F1XE$YR1g-RrXO7upnRS{B+uImv4bZ&M0T<+GM2Z-^6*yz~M`j%E5j}<`;YR9q|a2XXrHC;hD<9HwC~A z?H`+wZ7lB^FQpZfD~1Q%VxtvcYy(`?#t@;hUl66oYKDVUb?fiXB4gc+N8pRC@BQNj zj#f_pc>0*2yfM#XJ#$f(_a&g4nk?ryf<4ozqV-a*c%D?`&a(w41%-rVECk|~C#{OK^q-UdkgP-h<7p{pIA0o{{cCX4wLC)h@@vJ z7Z(@J%b7sTy4wp!2IUoRsN234luJ)p!281HCKs2}d^18h8*<$~U-_&x9qY^mvdEw_ zlqS5tgtS2x^;gWnfRie`mrtJ4@;fZbH_kJjYD6mtey9wriiE$Fc-9TC0TaD1?uEDE zp)ZZ<<`A9{M9b4w-IR?}`W2jCzjl$jr>GY7SoX#+FICzdvZHp`j`rqZFyRk7lj z;gj2ki61OwALL5ept1|~bZ=M8N9J0F^~zj&k8IzIk)OqurI(Feml-T=A+*p9gNF+? z@UKicwI5Z#uGGNnU$nuSORT6zL zS0iSB-+~?%23@Oy_q-Y(+D0||?#AD?Kn&wuznmeYG^o*YEeLS`Ej$TjvAI2zcC+{_AuEV10*o(Ps;QVwzdxenSid<5l9si)O*i`43jhx6k_$t2@cAQoC1&8#wp!A4Mrs;oaWvsJt{LT zo`%Awn@VEJ=?qe{oJc9*OC4=1r}x!|ss=aZ`afS~eyqNmvTMJX)<0U!kviHLlQV2C z4MMNtXK}p3i#vjeqtJ^+&oNUK;&z(j{ugp!uW*;EWa9R#-Vr*js=0vrw`-#4#jxAM znyjZeZvp8d*Tv1wTQ&-mTC1Wg=oke`BN}nU*oyr1s@G@y%|TDl!N>aQ>f@NsP~AN!5rg)XKF21TX8O4r^;j(Hf^TUOs~?{mXkZQy zQonX{SXCOI;!JLJ3e=}z4%iH#twzj6Qe7cD<)j(I6@Qvl1<~Bw5tMW#z+TR_*wHxB zh0GGZ#mXhceM4IbnVSl|IBG6~X1dluFzD>Z=cQx;On6$lz<#-`xhcv@UYd@Zq?TF> zY1AVlJH&jim|>gmA8{Dh0z=!k4I)yM(2tc@Zsx7&j0xT)rQ3Y-E7WGx6%A95J@7ti zK(W>ll?E=)9!9sP@t32``r3}e{SIpH1jH-)tN4X6cuLFxX(Be0C)V#rRQ zAPZpia^Q_zG%1}U-{gBa^L?G7L#>n-PAwNswuxdG@lzM*W-K$sEOR;rXPJ?$`2DSI zN=RF)d4x7&<;{fNcnNs1?!Yz^aP+%>SYj{C_ATuRof$<%DZTYy(A>zgi$)N*cGIVN zyH4^zGnbBwR0v`auP9&SXQIiN9ezj)|fVYL0eEMK>JX zs?hG}OM7Hg*8Lkd{jwqd-TY*_Oi{P9&Kzj#LPM-Tz{$-4)Ps zqN{7+%(U$2tI)v2d3KV2+z}YOOmFlW(X|3T_CbWKCxyq1^tRV_liq1p7=Xl4W27$Z z%U^xt95!dqVng^arP776F=oNUU$>L0^q{8ecR|5Tx`n@>HjNBbMyio=^fmR_`d3}A ziG>BK5hzC#JY$&*B&8iahbr$Ed2~uj)$Q&lXQ*@ikeRHcmhAN*`}WfNf|caNbz#FS z1JC_EX8Vdl-&RahH%-fbHoL!sZ_u70?lFBh*UW$M96#v_5jC>Y^10{T2>(dv$jGZF zraW4wQt5c-7=hq#Mie$OCPOpS2TnxGB(Ji-v6NaT#+ue+T;7;@A4alw(;?KbD3Ow^ zF$atNBQrO$~OcC0Vg8z1yqoF(;X;WHc*qrB#87F#m%OM<-D|kA!1wUO985k#|hllBm%+mQ>87xaNIW{tpR!`m}T4bHvASdWPBt zDvyk5isipiDsK?P8KLB47Lv52o05(upw5h|RdunA)WTUW6IiU%f+Y|rTCZg|2ECp0 zKWqwR04bd%91TG3X#K4+aGDL}M%w3TV=@^EC zVGjk`J}(eW{n&VC_rdvKuu`}*9wQ{)OSqbBLV@PdmiVY!6hQR;uPyZ5+S4 z8@1o*ybc_@`|xg>cGvNMAE*}u(gOfWA06!N{SXSedWgqKK_hsXI4@UEPEguMCviy_ zh(Z1_Ng?fRJ1cI9YS|@7yS;!E0l94B)XEEtRxyey8K$k-GvizJF~m{9V~ui5lJ2gOq~F)a0e z&g{7Pb+gWSuO+nVC8(}WweLW=Zoq||VNcK=kR|BSms+f{QD5I;NuXsIC}3(KHMSS3 zw^sy%#$iT)qDdGXWt|0Frt+<_W&Mb}t`OZa`;vZj$mqmhvtmn3C(^DNOMjpp>`ylE zb-!=6U&rxRd)3OEey6P{!J^mbfqq`eQkZOP>pb7i3?24Atpwa^8pu@wDlf(K4`zNP z1qIsZhW$CqBtkuv(ZguqGjsaR1j?P8q#6I9b#hyo=ijM`2!8jK%FP)(0zWrMAOn?jPEJ~pb)7T43jIU?Sz1N`e;r}1wSUDZw? z;P~j-9^C)`?ZNLKOUMF})PJFVA;)apS(YjG@5}EMlG%^IcPe96 z-M`fz<`F*vdub@z&A;6}3ew*h`9(f16Bng25b3+Tcjk-FOkvPUtaPKO|45f~e?|EC z_D{F>j)=qmz6u+8N1D<9K_AFg!;WyzYf}3~-b=yGz*FR;t#Ym)-DVZ2+w=&4+cyP& ziNU)_etc0+wF$^vWG~D3Tx4J^wns26|MW-9$>WZ9>kh@K>-Fjmi~qLyTp09GQ!nZf zKX682Uv?|4lCu>d+MT!lw7oJ$#yoP=GQ&A)vsu4Bx3zWun@FKYOao{=#%K>cr;<{#vzOOviAEHbAR zeqh0FCE~}ah-dh!>ct42VV&g#`(&Q`ep>SH(PX)NS$&!|E;=FZ_V?pUj$zWdN58># zL#df5DdR8A{`&+l!>j-E6NpEskzW^Xjx)v1!Ee3BtvwUL%J9-vYNN9I75G)L^uFR& zqM+C#u*K@8#a4v7SN$(LPZFcu!v<>HKfx5;-z)vqq`b3Vy0I07ggoRIRjvXscRl;> z>-InS0F!bLXozZ)1|*iNm{fFI@fRXe6Mj8i>G>);#&FI`aUO*GlMgZBg3rK~0)}n|A5I;Xm-&3h$^#2ai z|9qqQG@u?kCcik_^gESnP%+MPSVhN~S7rWW=6AS#ZxDn6o&BYFj+u$xK(o;`!r(slK?udA+j)ZHi^+tfeg4_r6=whV}oW_ZceDd~RRM{U9 z8nZIiCfYo#ldmH}eAf;OIwE&vp}2Z#k3Y|N=cQ2O1XxWFfs7nQJ@T`NM3u%WlKdTc z8ju{38AXpPKf7u8_uEut)E*YNF3-AMDtxssajKar5wKzP!BD zxmIp;{ugn622<=&w-a~6|8RFoj@%82{&MmIn*vpy;VU?vNJ4noo~q8%B)?X8*#&&b zkm$Kd%W=Nt@~R5YL^Z33swqGIQHdgO=cG0rP9bKbJk3zwy<*cWK4~?xWu- z;xUs3sd|m!yA{qRUk+K1emMJFx)V$WwbBVY)2h?eWX`DfFvmo$IQQ`sXB8AcqZ4>;_9w4`xevetv#2FgR>! zboBFH2O5Q`T=K++m=@DYtai8i`s-8f z{$uo^wOZ8AFD7=xZ6I*0(te-$4~as=6(j-F8T*G?@w((@AaAzGzM1Me;Bq`aDz1sf z3GXf7SB*b7EXoFdjhkzEt=?!-%LZ~Q>0Yns@NbE%lr2!chLA>uW%Xn11cle!(iuw4 z?$bceZw;2TtR)pe80|mCA()Lw@!9|78FSUa&epe7rf+sMTeSh)?6z;L+zKf#cYxZ? zaA&z8Gh22fmmT&sg}VhP;a@1EZAwHwJSoo?>mONe(r-GaV8(wJ5A`aNC$cSiS-*uT$2qd}W+Yc`ZqofT8OR=! zkR*NIIXrZsr#DeE8+(rm4dcJA-U+gukzHyL$r>pxDSWmd6Uim?&|(Z?%~<6vRqrz7 z-t-{V^naxL|9yF^r&h19N1eyLRkX&7EVz>QRA^?L3-|SQaKbSXr=AS|r=1PG)*kb! zE;u2990O$4)REwzh-1V71z3dFR&zE%hwF|BY9p z6(2z5@JoVmLzU;My-NPaH25jx`@n)>3BN>em%HFChd!*yU-lnnL^(zfikOsiP>G)* z4vtuI#wfizz=tysEV1tV;^N1{1Nt7C9W?>&M~hob1T4q7A}gv`8$->aqOB}s+)W@a zpi$F;9j1dt3rvY}aNwQm_Cst29N4FL{w-})&f|VD{rvKzU#v*b``*DDAoBn*|Fm?D z)j!U1G&4x$N5e;DN%?ugg?#^++sOaYAi|PCOwrNN4Wd4mo^g&QCY}G-?C^_RD-5(5(LTAJYZSB3-SUFtK6ELjYYnUG(fbE&Ljr++Qv|04{HAtE>r9%1GT zH)9(iG%kpAQCyKMM|kVlF5W;(k7ch%C~-LrR_RG!F$nK}zSx0b;Q@R6-`!bm z3wCzY_aMj+%Yjt&58;x<;#&fnssHeC^er@(F}6UrotVS(Kej+C&dmy$a~R7izkg^X zGwdbEW&dOPdo4qO^@nsAMh>vQ+4s6y|3{)f-$w$w1plV^3JsIGWi@}L`}|vt-WqyN zYu5%LoAh!A&HB5u`@Qz^qNV#2|9f=m&F&{`+lUTwRgqn>%Tv_`?#8S)_ zGCo9s1V2Qdl(IHN7E?HJcR#DmH{IoP>_d* zxx;~p%qQm)jLum%lV=XerMR*UY-f+8-eQ${`l5C%Z_);hd=7s#yY8A+ozaX~CsV9@ zlDxSSy`EG*s%SrT^D^%l;`P&51V1gz_^7Ynynvy_hzvF?EPYS@xz^bKAF5e4u7Df| zVIQYNz=l05hxn$wbQjsnob0(yGSQR~T9z<^gyaj2g$V)toSF1rUrHpl8Yohy>qhx8 zzF9JYbhj8Wb5^qLP30!fls-)MHtH>qWTc}w*kI2LAM6ZWHOXTqzM6fNNakn8bVcyX95D9-oPCDCz7`Q{_RF}VuFHOm}9?Y z53RWm8uiBvTdoV?U3WNPi%Q~mCcbg=kL1Rf#kco13=o%?{3GmOVj>2Js&`Y#E-4xG zXYVkHg+K;HuJ3D*Y{uR%EmMpO9j0C-YP?R=h*z{(vbkU`QibNwn$a%Q9`n{>G4iYi zK7W5e@_wXdRdDw7ar4Mu1P{*O?CQf{5L9;|V5#k7sva#iwPdsOx=2xb2?_o@4J@iLCXLg{hqvS?(#86KcYa;s2QzfTUwjo^!kbuJ;PTn;-$b2 zp>}jOQpgXPiaYu&GpQT)K1lUU^zP0!61L{`(+C0QVIHCRo*a}X$pMcK`<+=?9j-qn zZN$1%x#k3dcw2QRej#c!x{(amFxygm)yI6T$e{@LuFuGgx z4f1YN^{%<;Z1}}qPS|oT-$eP*Y#lv(+6?0Nw%Y#sQ-F*xg9B7R>(Je>f!#Erm^;WIGS%Ufsu*8gO*KAO?glEOlqwEwsiQ7?c-!9e&@AAX?rOvNB<#MerA!B(1Y<^l(w)Y>d|Zcg`h zjG6KF)wjQPC}6nzR&%Vuc{ACD@)74X10gH-3T)033vwlePEmUYJ}rBML3S+yJ2m~fHwq@?`^^^eE z2U)CxI;tff&~n`@3NP>KAw+K{6ZTGF?e(Ro@5*M-u_jDiAzZg*%D1J4w?EZ-VedG@ z_=OvIps^e8%aIm^VrD-dkNPIkumLktvmD7(%epyS$?@rtWx0zTZ>rRuN>IHMAgyDi zig0^bMad=n-KPQG$wGnbS@OBG_Gc3^(H&e1Z!<%2ptz)sOn5_Ip)RB^P397?Qk(P@ zUm6L?KSQL(srbBvFjbC|^{h)EzhOc)W_{Z>%V-Ez!vqCrQB(t*8nYm=+fG zOPCOMknm5XJv>`&%(!M3Lv8-V^r#0Nk!&coh@IdIVxXP-QIm`z#>cOsF-)MP#W7!5 zsB#x?ad!4+6cO<83DuS{JwfGZ0evm(?jth?!J9YMqTgY!X!{}AoC6o%%wIccFqX+o z&zupnpynkB#o4aDtEMwm#*=yOHedXiT0!($Dm!^vxNqL~in^(G zbLHrkoHRb)jz^iLTWQ zhU5>gC5`U`ar8cjk=@*Cx5uBq_}DgInZ7kVJj`*kH5mAjDBcQ>LOb2P$f2|rUrw3I8u(sZcD$x-=2sCl^LzP zkv1CsP9+ijpgdV|t>T<6>4lELhxcL)ZWsr+gTy>0CW2=V0>8A$KFdabkqLa#kMvAH zD+W^N1x+j>Dk_um0Ui;RpyCXH?GzWF1Gjbd6^V~hJ4K-eVFVSb^p+g?U%0A0w6W*$~ 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 literal 0 HcmV?d00001 diff --git a/writerside/images/convert_table_to_xml.png b/writerside/images/convert_table_to_xml.png new file mode 100644 index 0000000000000000000000000000000000000000..2518a64cd1920655dfcf412207f13397b3ac0ece GIT binary patch literal 349338 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 z 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

4xDE?=DV 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%0D1G(Fs1|mcpKj2MZ~D#z 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 AasU7T literal 0 HcmV?d00001 diff --git a/writerside/images/new_topic_options_dark.png b/writerside/images/new_topic_options_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..bf3e48d1320ec72f2b161ebe2be64d4ca62ddfe3 GIT binary patch literal 281000 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

TA0$s2H0+oW7 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�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 + + + + + + \ No newline at end of file diff --git a/writerside/redirection-rules.xml b/writerside/redirection-rules.xml new file mode 100644 index 00000000..e06c4572 --- /dev/null +++ b/writerside/redirection-rules.xml @@ -0,0 +1,13 @@ + + + + + + " from OMATH]]> + Empty-MD-Topic.html + + \ No newline at end of file diff --git a/writerside/topics/starter-topic.md b/writerside/topics/starter-topic.md new file mode 100644 index 00000000..7fcb3d05 --- /dev/null +++ b/writerside/topics/starter-topic.md @@ -0,0 +1,78 @@ +# About OMATH + +Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. + +## Add new topics +You can create empty topics, or choose a template for different types of content that contains some boilerplate structure to help you get started: + +![Create new topic options](new_topic_options.png){ width=290 }{border-effect=line} + +## Write content +%product% supports two types of markup: Markdown and XML. +When you create a new help article, you can choose between two topic types, but this doesn't mean you have to stick to a single format. +You can author content in Markdown and extend it with semantic attributes or inject entire XML elements. + +## Inject XML +For example, this is how you inject a procedure: + + + +

Start typing and select a procedure type from the completion suggestions:

+ completion suggestions for procedure + + +

Press Tab or Enter to insert the markup.

+
+ + +## Add interactive elements + +### Tabs +To add switchable content, you can make use of tabs (inject them by starting to type `tab` on a new line): + + + + ![Alt Text](new_topic_options.png){ width=450 } + + + + ]]> + + + +### Collapsible blocks +Apart from injecting entire XML elements, you can use attributes to configure the behavior of certain elements. +For example, you can collapse a chapter that contains non-essential information: + +#### Supplementary info {collapsible="true"} +Content under a collapsible header will be collapsed by default, +but you can modify the behavior by adding the following attribute: +`default-state="expanded"` + +### Convert selection to XML +If you need to extend an element with more functions, you can convert selected content from Markdown to semantic markup. +For example, if you want to merge cells in a table, it's much easier to convert it to XML than do this in Markdown. +Position the caret anywhere in the table and press Alt+Enter: + +Convert table to XML + +## Feedback and support +Please report any issues, usability improvements, or feature requests to our +
YouTrack project +(you will need to register). + +You are welcome to join our +public Slack workspace. +Before you do, please read our [Code of conduct](https://www.jetbrains.com/help/writerside/writerside-code-of-conduct.html). +We assume that you’ve read and acknowledged it before joining. + +You can also always email us at [writerside@jetbrains.com](mailto:writerside@jetbrains.com). + + + + Markup reference + Reorder topics in the TOC + Build and publish + Configure Search + + \ No newline at end of file diff --git a/writerside/v.list b/writerside/v.list new file mode 100644 index 00000000..2d12cb39 --- /dev/null +++ b/writerside/v.list @@ -0,0 +1,5 @@ + + + + + diff --git a/writerside/writerside.cfg b/writerside/writerside.cfg new file mode 100644 index 00000000..4e8ae9ec --- /dev/null +++ b/writerside/writerside.cfg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From faf9f34af823efa4db5845fb1ae60b98d0be47d9 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 2 Apr 2025 06:23:55 +0300 Subject: [PATCH 303/795] stipped away text --- writerside/c.list | 1 + writerside/topics/starter-topic.md | 78 ++---------------------------- 2 files changed, 6 insertions(+), 73 deletions(-) diff --git a/writerside/c.list b/writerside/c.list index c4c77a29..5fca6a61 100644 --- a/writerside/c.list +++ b/writerside/c.list @@ -3,4 +3,5 @@ SYSTEM "https://resources.jetbrains.com/writerside/1.0/categories.dtd"> + \ No newline at end of file diff --git a/writerside/topics/starter-topic.md b/writerside/topics/starter-topic.md index 7fcb3d05..e06fd7a8 100644 --- a/writerside/topics/starter-topic.md +++ b/writerside/topics/starter-topic.md @@ -1,78 +1,10 @@ -# About OMATH +# Introduction Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. -## Add new topics -You can create empty topics, or choose a template for different types of content that contains some boilerplate structure to help you get started: - -![Create new topic options](new_topic_options.png){ width=290 }{border-effect=line} - -## Write content -%product% supports two types of markup: Markdown and XML. -When you create a new help article, you can choose between two topic types, but this doesn't mean you have to stick to a single format. -You can author content in Markdown and extend it with semantic attributes or inject entire XML elements. - -## Inject XML -For example, this is how you inject a procedure: - - - -

Start typing and select a procedure type from the completion suggestions:

- completion suggestions for procedure -
- -

Press Tab or Enter to insert the markup.

-
-
- -## Add interactive elements - -### Tabs -To add switchable content, you can make use of tabs (inject them by starting to type `tab` on a new line): - - - - ![Alt Text](new_topic_options.png){ width=450 } - - - - ]]> - - - -### Collapsible blocks -Apart from injecting entire XML elements, you can use attributes to configure the behavior of certain elements. -For example, you can collapse a chapter that contains non-essential information: - -#### Supplementary info {collapsible="true"} -Content under a collapsible header will be collapsed by default, -but you can modify the behavior by adding the following attribute: -`default-state="expanded"` - -### Convert selection to XML -If you need to extend an element with more functions, you can convert selected content from Markdown to semantic markup. -For example, if you want to merge cells in a table, it's much easier to convert it to XML than do this in Markdown. -Position the caret anywhere in the table and press Alt+Enter: - -Convert table to XML - -## Feedback and support -Please report any issues, usability improvements, or feature requests to our -YouTrack project -(you will need to register). - -You are welcome to join our -public Slack workspace. -Before you do, please read our [Code of conduct](https://www.jetbrains.com/help/writerside/writerside-code-of-conduct.html). -We assume that you’ve read and acknowledged it before joining. - -You can also always email us at [writerside@jetbrains.com](mailto:writerside@jetbrains.com). - - - Markup reference - Reorder topics in the TOC - Build and publish - Configure Search + + GitHub repository + YouGame thread - \ No newline at end of file + From a642feafb51574b97c9e7313f6b71bb809818be6 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 2 Apr 2025 07:28:42 +0300 Subject: [PATCH 304/795] added new topic --- writerside/o.tree | 1 + writerside/topics/License.md | 9 +++++++++ writerside/topics/starter-topic.md | 1 + 3 files changed, 11 insertions(+) create mode 100644 writerside/topics/License.md diff --git a/writerside/o.tree b/writerside/o.tree index a5ea91ae..04378781 100644 --- a/writerside/o.tree +++ b/writerside/o.tree @@ -7,4 +7,5 @@ start-page="starter-topic.md"> + \ No newline at end of file diff --git a/writerside/topics/License.md b/writerside/topics/License.md new file mode 100644 index 00000000..4f4ff045 --- /dev/null +++ b/writerside/topics/License.md @@ -0,0 +1,9 @@ +# License + +Copyright (c) 2025 Orange++ + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/writerside/topics/starter-topic.md b/writerside/topics/starter-topic.md index e06fd7a8..a27f5176 100644 --- a/writerside/topics/starter-topic.md +++ b/writerside/topics/starter-topic.md @@ -8,3 +8,4 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at YouGame thread +ц \ No newline at end of file From 73ccd24e3edcbdf7ce4e13272f8568c834394dcb Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 2 Apr 2025 07:36:45 +0300 Subject: [PATCH 305/795] improved intro --- writerside/topics/starter-topic.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/writerside/topics/starter-topic.md b/writerside/topics/starter-topic.md index a27f5176..623be012 100644 --- a/writerside/topics/starter-topic.md +++ b/writerside/topics/starter-topic.md @@ -2,10 +2,31 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. +## 👁‍🗨 Features +- **Efficiency**: Optimized for performance, ensuring quick computations using AVX2. +- **Versatility**: Includes a wide array of mathematical functions and algorithms. +- **Ease of Use**: Simplified interface for convenient integration into various projects. +- **Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot. +- **3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline. +- **Collision Detection**: Production ready code to handle collision detection by using simple interfaces. +- **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution +- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types! + +|ENGINE |SUPPORT| +|--------|-------| +|Source |✅YES | +|Unity |✅YES | +|IWEngine|✅YES | +|Unreal |❌NO | + + +## 💘 Acknowledgments +We value each person that made pull request to this project! And YOU can become one of them! +- [All contributors](https://github.com/orange-cpp/omath/graphs/contributors) GitHub repository YouGame thread -ц \ No newline at end of file + From 76fca7f527b5e8671daa6443ce0641f5cf0a9551 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 2 Apr 2025 07:38:11 +0300 Subject: [PATCH 306/795] fix --- writerside/topics/starter-topic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/writerside/topics/starter-topic.md b/writerside/topics/starter-topic.md index 623be012..054411c4 100644 --- a/writerside/topics/starter-topic.md +++ b/writerside/topics/starter-topic.md @@ -2,7 +2,7 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. -## 👁‍🗨 Features +## Features - **Efficiency**: Optimized for performance, ensuring quick computations using AVX2. - **Versatility**: Includes a wide array of mathematical functions and algorithms. - **Ease of Use**: Simplified interface for convenient integration into various projects. @@ -20,7 +20,7 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at |Unreal |❌NO | -## 💘 Acknowledgments +## Acknowledgments We value each person that made pull request to this project! And YOU can become one of them! - [All contributors](https://github.com/orange-cpp/omath/graphs/contributors) From c2a772142c7a18c8ca96381c1c0004d4720555cd Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 2 Apr 2025 07:43:37 +0300 Subject: [PATCH 307/795] fix --- writerside/topics/starter-topic.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/writerside/topics/starter-topic.md b/writerside/topics/starter-topic.md index 054411c4..4b755de8 100644 --- a/writerside/topics/starter-topic.md +++ b/writerside/topics/starter-topic.md @@ -12,12 +12,12 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at - **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution - **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types! -|ENGINE |SUPPORT| -|--------|-------| -|Source |✅YES | -|Unity |✅YES | -|IWEngine|✅YES | -|Unreal |❌NO | +| ENGINE | SUPPORT | +|:--------:|:-------:| +| Source | ✅YES | +| Unity | ✅YES | +| IWEngine | ✅YES | +| Unreal | ❌NO | ## Acknowledgments From b4a3b5d5290184563cee47af42d4eab29ae5c7dc Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 2 Apr 2025 08:12:37 +0300 Subject: [PATCH 308/795] added text --- writerside/o.tree | 2 ++ writerside/topics/Community.md | 3 +++ writerside/topics/Documentation.md | 3 +++ 3 files changed, 8 insertions(+) create mode 100644 writerside/topics/Community.md create mode 100644 writerside/topics/Documentation.md diff --git a/writerside/o.tree b/writerside/o.tree index 04378781..19fe877d 100644 --- a/writerside/o.tree +++ b/writerside/o.tree @@ -7,5 +7,7 @@ start-page="starter-topic.md"> + + \ No newline at end of file diff --git a/writerside/topics/Community.md b/writerside/topics/Community.md new file mode 100644 index 00000000..da45b235 --- /dev/null +++ b/writerside/topics/Community.md @@ -0,0 +1,3 @@ +# Community + +Start typing here... \ No newline at end of file diff --git a/writerside/topics/Documentation.md b/writerside/topics/Documentation.md new file mode 100644 index 00000000..ce9cb240 --- /dev/null +++ b/writerside/topics/Documentation.md @@ -0,0 +1,3 @@ +# Documentation + +Start typing here... \ No newline at end of file From d37840d4ef3beec07ba680e7496f642878a03fc5 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 21 May 2025 16:57:17 +0300 Subject: [PATCH 309/795] fixed stuff --- writerside/o.tree | 1 + writerside/topics/Code-Of-Conduct.md | 95 ++++++++++++++++++++++++++++ writerside/topics/Community.md | 12 +++- writerside/topics/Documentation.md | 55 +++++++++++++++- writerside/topics/starter-topic.md | 69 +++++++++++++++----- 5 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 writerside/topics/Code-Of-Conduct.md diff --git a/writerside/o.tree b/writerside/o.tree index 19fe877d..df951e50 100644 --- a/writerside/o.tree +++ b/writerside/o.tree @@ -8,6 +8,7 @@ + \ No newline at end of file diff --git a/writerside/topics/Code-Of-Conduct.md b/writerside/topics/Code-Of-Conduct.md new file mode 100644 index 00000000..bf542488 --- /dev/null +++ b/writerside/topics/Code-Of-Conduct.md @@ -0,0 +1,95 @@ +# Code Of Conduct + +## 🎯 Goal + +My goal is to provide a space where it is safe for everyone to contribute to, +and get support for, open-source software in a respectful and cooperative +manner. + +I value all contributions and want to make this project and its +surrounding community a place for everyone. + +As members, contributors, and everyone else who may participate in the +development, I strive to keep the entire experience civil. + +## 📜 Standards + +Our community standards exist in order to make sure everyone feels comfortable +contributing to the project(s) together. + +Our standards are: +- Do not harass, attack, or in any other way discriminate against anyone, including + for their protected traits, including, but not limited to, sex, religion, race, + appearance, gender, identity, nationality, sexuality, etc. +- Do not go off-topic, do not post spam. +- Treat everyone with respect. + +Examples of breaking each rule respectively include: +- Harassment, bullying or inappropriate jokes about another person. +- Posting distasteful imagery, trolling, or posting things unrelated to the topic at hand. +- Treating someone as worse because of their lack of understanding of an issue. + +## ⚡ Enforcement + +Enforcement of this CoC is done by Orange++ and/or other core contributors. + +I, as the core developer, will strive my best to keep this community civil and +following the standards outlined above. + +### 🚩 Reporting incidents + +If you believe an incident of breaking these standards has occurred, but nobody has +taken appropriate action, you can privately contact the people responsible for dealing +with such incidents in multiple ways: + +***E-Mail*** +- `orange-cpp@yandex.ru` + +***Discord*** +- `@orange_cpp` + +***Telegram*** +- `@orange_cpp` + +I guarantee your privacy and will not share those reports with anyone. + +## ⚖️ Enforcement Strategy + +Depending on the severity of the infraction, any action from the list below may be applied. +Please keep in mind cases are reviewed on a per-case basis and members are the ultimate +deciding factor in the type of punishment. + +If the matter benefited from an outside opinion, a member might reach for more opinions +from people unrelated, however, the final decision regarding the action +to be taken is still up to the member. + +For example, if the matter at hand regards a representative of a marginalized group or minority, +the member might ask for a first-hand opinion from another representative of such group. + +### ✏️ Correction/Edit + +If your message is found to be misleading or poorly worded, a member might +edit your message. + +### ⚠️ Warning/Deletion + +If your message is found inappropriate, a member might give you a public or private warning, +and/or delete your message. + +### 🔇 Mute + +If your message is disruptive, or you have been repeatedly violating the standards, +a member might mute (or temporarily ban) you. + +### ⛔ Ban + +If your message is hateful, very disruptive, or other, less serious infractions are repeated +ignoring previous punishments, a member might ban you permanently. + +## 🔎 Scope + +This CoC shall apply to all projects ran under the Orange++ lead and all _official_ communities +outside of GitHub. + +However, it is worth noting that official communities outside of GitHub might have their own, +additional sets of rules. \ No newline at end of file diff --git a/writerside/topics/Community.md b/writerside/topics/Community.md index da45b235..4f18848b 100644 --- a/writerside/topics/Community.md +++ b/writerside/topics/Community.md @@ -1,3 +1,11 @@ -# Community +# Credits -Start typing here... \ No newline at end of file +Thanks to everyone who made this possible, including: + +- Saikari aka luadebug for VCPKG port. + +And a big hand to everyone else who has contributed over the past! + +THANKS! <3 + + -- Orange++ \ No newline at end of file diff --git a/writerside/topics/Documentation.md b/writerside/topics/Documentation.md index ce9cb240..a99f4a2b 100644 --- a/writerside/topics/Documentation.md +++ b/writerside/topics/Documentation.md @@ -1,3 +1,54 @@ -# Documentation +# 📥Installation Guide -Start typing here... \ No newline at end of file +## Using vcpkg +**Note**: Support vcpkg for package management +1. Install [vcpkg](https://github.com/microsoft/vcpkg) +2. Run the following command to install the orange-math package: +``` +vcpkg install orange-math +``` +CMakeLists.txt +```cmake +find_package(omath CONFIG REQUIRED) +target_link_libraries(main PRIVATE omath::omath) +``` +For detailed commands on installing different versions and more information, please refer to Microsoft's [official instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/overview). + +## Build from source using CMake +1. **Preparation** + + Install needed tools: cmake, clang, git, msvc (windows only). + + 1. **Linux:** + ```bash + sudo pacman -Sy cmake ninja clang git + ``` + 2. **MacOS:** + ```bash + brew install llvm git cmake ninja + ``` + 3. **Windows:** + + Install Visual Studio from [here](https://visualstudio.microsoft.com/downloads/) and Git from [here](https://git-scm.com/downloads). + + Use x64 Native Tools shell to execute needed commands down below. +2. **Clone the repository:** + ```bash + git clone https://github.com/orange-cpp/omath.git + ``` +3. **Navigate to the project directory:** + ```bash + cd omath + ``` +4. **Build the project using CMake:** + ```bash + cmake --preset windows-release -S . + cmake --build cmake-build/build/windows-release --target omath -j 6 + ``` + Use **\-\** preset to build siutable version for yourself. Like **windows-release** or **linux-release**. + + | Platform Name | Build Config | + |---------------|---------------| + | windows | release/debug | + | linux | release/debug | + | darwin | release/debug | \ No newline at end of file diff --git a/writerside/topics/starter-topic.md b/writerside/topics/starter-topic.md index 4b755de8..878557f7 100644 --- a/writerside/topics/starter-topic.md +++ b/writerside/topics/starter-topic.md @@ -1,8 +1,10 @@ -# Introduction +# Intro + +![banner](https://i.imgur.com/SM9ccP6.png) Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. -## Features +## 👁‍🗨 Features - **Efficiency**: Optimized for performance, ensuring quick computations using AVX2. - **Versatility**: Includes a wide array of mathematical functions and algorithms. - **Ease of Use**: Simplified interface for convenient integration into various projects. @@ -12,21 +14,54 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at - **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution - **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types! -| ENGINE | SUPPORT | -|:--------:|:-------:| -| Source | ✅YES | -| Unity | ✅YES | -| IWEngine | ✅YES | -| Unreal | ❌NO | +## Supported Render Pipelines +| ENGINE | SUPPORT | +|----------|---------| +| Source | ✅YES | +| Unity | ✅YES | +| IWEngine | ✅YES | +| Unreal | ❌NO | +## Supported Operating Systems -## Acknowledgments -We value each person that made pull request to this project! And YOU can become one of them! -- [All contributors](https://github.com/orange-cpp/omath/graphs/contributors) - - - GitHub repository - YouGame thread - - +| OS | SUPPORT | +|----------------|---------| +| Windows 10/11 | ✅YES | +| Linux | ✅YES | +| Darwin (MacOS) | ✅YES | + +## ⏬ Installation +Please read our [installation guide](https://github.com/orange-cpp/omath/blob/main/INSTALL.md). If this link doesn't work check out INSTALL.md file. + +## ❔ Usage +Simple world to screen function +```c++ +TEST(UnitTestProjection, IsPointOnScreen) +{ + const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.1f, 500.f); + + const auto proj = camera.WorldToScreen({100, 0, 15}); + EXPECT_TRUE(proj.has_value()); +} +``` +## Showcase + +With `omath/projection` module you can achieve simple ESP hack for powered by Source/Unreal/Unity engine games, like [Apex Legends](https://store.steampowered.com/app/1172470/Apex_Legends/). +![banner](https://i.imgur.com/lcJrfcZ.png) +Or for InfinityWard Engine based games. Like Call of Duty Black Ops 2! +![banner](https://i.imgur.com/F8dmdoo.png) +Or create simple trigger bot with embeded traceline from omath::collision::LineTrace +![banner](https://i.imgur.com/fxMjRKo.jpeg) +Or even advanced projectile aimbot +[Watch Video](https://youtu.be/lM_NJ1yCunw?si=5E87OrQMeypxSJ3E) + + +## 🫵🏻 Contributing +Contributions to `omath` are welcome! Please read `CONTRIBUTING.md` for details on our code of conduct and the process for submitting pull requests. + +## 📜 License +This project is licensed under the MIT - see the `LICENSE` file for details. + +## 💘 Acknowledgments +- [All contributors](https://github.com/orange-cpp/omath/graphs/contributors) From 2c710555d6ebbb5eb33537a1263487a1beb2c120 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 15 Jun 2025 22:04:58 +0300 Subject: [PATCH 310/795] fixed example --- examples/example_proj_mat_builder.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/example_proj_mat_builder.cpp b/examples/example_proj_mat_builder.cpp index 737b4e8b..5857e373 100644 --- a/examples/example_proj_mat_builder.cpp +++ b/examples/example_proj_mat_builder.cpp @@ -15,8 +15,8 @@ int main() float fov = 0; float near = 0; float far = 0; - float viewPortWidth = 0; - float viewPortHeight = 0; + float view_port_width = 0; + float view_port_height = 0; std::print("Enter camera fov: "); std::cin >> fov; @@ -28,13 +28,13 @@ int main() std::cin >> far; std::print("Enter camera screen width: "); - std::cin >> viewPortWidth; + std::cin >> view_port_width; std::print("Enter camera screen height: "); - std::cin >> viewPortHeight; + std::cin >> view_port_height; const auto mat = - omath::opengl_engine::CalcPerspectiveProjectionMatrix(fov, viewPortWidth / viewPortHeight, near, far); + omath::opengl_engine::calc_perspective_projection_matrix(fov, view_port_width / view_port_height, near, far); - std::print("{}", mat.ToString()); + std::print("{}", mat.to_string()); }; From f01bbde5377e879f73b85b6980184e1506ebe45e Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 17 Jun 2025 21:33:39 +0300 Subject: [PATCH 311/795] added banner to repo --- .github/images/banner.png | Bin 0 -> 140186 bytes README.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 .github/images/banner.png diff --git a/.github/images/banner.png b/.github/images/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..dff276c8d50ae772d8e084d37f298f807b011468 GIT binary patch literal 140186 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 071fd0c3..9cd61f78 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. + + ## 👁‍🗨 Features - **Efficiency**: Optimized for performance, ensuring quick computations using AVX2. - **Versatility**: Includes a wide array of mathematical functions and algorithms. From 646d2958768e959239c5d8ecda7f186018d84aff Mon Sep 17 00:00:00 2001 From: Saikari Date: Mon, 14 Jul 2025 16:25:16 +0300 Subject: [PATCH 323/795] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7b9f496f..13310b95 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ ![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath) +[![Packaging status](https://repology.org/badge/vertical-allrepos/orange-math.svg)](https://repology.org/project/orange-math/versions) + Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research.
From 7a1c7d6cc40cd5c451f2b488b31333d5fdcc9738 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Mon, 14 Jul 2025 17:38:50 +0300 Subject: [PATCH 324/795] Update README.md made badge in read me smaller --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 13310b95..b4b276ef 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,9 @@ [![CodeFactor](https://www.codefactor.io/repository/github/orange-cpp/omath/badge)](https://www.codefactor.io/repository/github/orange-cpp/omath) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml) ![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath) +[![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/orange-math.svg)](https://repology.org/project/orange-math/versions)
-[![Packaging status](https://repology.org/badge/vertical-allrepos/orange-math.svg)](https://repology.org/project/orange-math/versions) - Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research.
From f1cd9dbeb307c3312c335ce2e173a3e59d0c1bbb Mon Sep 17 00:00:00 2001 From: Orange++ Date: Mon, 14 Jul 2025 17:39:16 +0300 Subject: [PATCH 325/795] Update README.md fixed ordering --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b4b276ef..90594965 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ ![GitHub top language](https://img.shields.io/github/languages/top/orange-cpp/omath) [![CodeFactor](https://www.codefactor.io/repository/github/orange-cpp/omath/badge)](https://www.codefactor.io/repository/github/orange-cpp/omath) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml) -![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath) [![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/orange-math.svg)](https://repology.org/project/orange-math/versions) +![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath)
Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. From 851ec3735022c2a98a82b12504ac1c1023b36917 Mon Sep 17 00:00:00 2001 From: Saikari Date: Mon, 14 Jul 2025 18:53:41 +0300 Subject: [PATCH 326/795] Update INSTALL.md --- INSTALL.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index 2725ae9b..6d2e000d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -14,6 +14,20 @@ target_link_libraries(main PRIVATE omath::omath) ``` For detailed commands on installing different versions and more information, please refer to Microsoft's [official instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/overview). +## Using xrepo +**Note**: Support xrepo for package management +1. Install [xmake](https://xmake.io/) +2. Run the following command to install the omath package: +``` +xrepo install omath +``` +xmake.lua +```xmake +add_requires("omath") +target("...") + add_packages("omath") +``` + ## Build from source using CMake 1. **Preparation** From d65852d1a45025726be8cb4f73c4174b592f4d37 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Mon, 14 Jul 2025 23:11:46 +0300 Subject: [PATCH 327/795] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 90594965..c9a96907 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Or even advanced projectile aimbot Contributions to `omath` are welcome! Please read `CONTRIBUTING.md` for details on our code of conduct and the process for submitting pull requests. ## 📜 License -This project is licensed under the MIT - see the `LICENSE` file for details. +This project is licensed under the ZLIB - see the `LICENSE` file for details. ## 💘 Acknowledgments - [All contributors](https://github.com/orange-cpp/omath/graphs/contributors) From 8e411771c2dd0d5bb6cea786160da8f128cc57d9 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 15 Jul 2025 11:48:27 +0300 Subject: [PATCH 328/795] Adds option to enable legacy classes Introduces a CMake option to enable legacy classes, allowing for backward compatibility with older code. This ensures that older codebases can still function while new development can utilize updated classes. --- CMakeLists.txt | 6 ++++++ include/omath/matrix.hpp | 4 ++++ source/matrix.cpp | 3 +++ 3 files changed, 13 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d5891f2..af67dd59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,8 @@ option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" O option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF) option(OMATH_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build to improve general performance" ON) option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" ON) +option(OMATH_ENABLE_LEGACY "Will enable legacy classes that MUST be used ONLY for backward compatibility" OFF) + file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") file(GLOB_RECURSE OMATH_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp") @@ -56,6 +58,10 @@ if (OMATH_SUPRESS_SAFETY_CHECKS) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_SUPRESS_SAFETY_CHECKS) endif () +if (OMATH_ENABLE_LEGACY) + target_compile_options(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LEGACY) +endif () + set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" diff --git a/include/omath/matrix.hpp b/include/omath/matrix.hpp index 1e2df876..a8dc520f 100644 --- a/include/omath/matrix.hpp +++ b/include/omath/matrix.hpp @@ -1,4 +1,7 @@ #pragma once + +#ifdef OMATH_ENABLE_LEGACY + #include "omath/vector3.hpp" #include #include @@ -106,3 +109,4 @@ namespace omath std::unique_ptr m_data; }; } // namespace omath +#endif diff --git a/source/matrix.cpp b/source/matrix.cpp index 428aaa87..322ea967 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -1,3 +1,5 @@ +#ifdef OMATH_ENABLE_LEGACY + #include "omath/matrix.hpp" #include "omath/angles.hpp" #include "omath/vector3.hpp" @@ -359,3 +361,4 @@ namespace omath m_data = nullptr; } } // namespace omath +#endif From 1aa62cb39663e0bad936c3f1b9bf58c5e979c7b6 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 15 Jul 2025 11:51:14 +0300 Subject: [PATCH 329/795] Enables legacy code compilation The changes encapsulate the matrix tests within an `#ifdef` block, allowing conditional compilation based on whether `OMATH_ENABLE_LEGACY` is defined. This enables the legacy code to be compiled only when needed. --- tests/general/unit_test_matrix.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/general/unit_test_matrix.cpp b/tests/general/unit_test_matrix.cpp index ec56cbc0..b7543c6b 100644 --- a/tests/general/unit_test_matrix.cpp +++ b/tests/general/unit_test_matrix.cpp @@ -1,6 +1,9 @@ // // Created by vlad on 5/18/2024. // + +#ifdef OMATH_ENABLE_LEGACY + #include #include #include "omath/vector3.hpp" @@ -177,4 +180,5 @@ TEST_F(UnitTestMatrix, AssignmentOperator_Move) EXPECT_FLOAT_EQ(m3.at(0, 0), 1.0f); EXPECT_EQ(m2.row_count(), 0); // m2 should be empty after the move EXPECT_EQ(m2.columns_count(), 0); -} \ No newline at end of file +} +#endif From 4b44ce06676bb4a55526b414d447a1b73e7d0950 Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 31 Jul 2025 21:51:52 +0300 Subject: [PATCH 330/795] Documents projectile launch angle formula Adds a comment documenting the formula used for calculating the projectile launch pitch angle. The comment includes a link to the Stack Overflow discussion where the formula was found and the LaTeX representation of the formula for clarity. --- .../proj_pred_engine_legacy.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index 44eec147..8a72ee96 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -28,6 +28,18 @@ namespace omath::projectile_prediction const float m_maximum_simulation_time; const float m_distance_tolerance; + // Realization of this formula: + // https://stackoverflow.com/questions/54917375/how-to-calculate-the-angle-to-shoot-a-bullet-in-order-to-hit-a-moving-target + /* + \[ + \theta \;=\; \arctan\!\Biggl( + \frac{% + v^{2}\;\pm\;\sqrt{\,v^{4}-g\!\left(gx^{2}+2yv^{2}\right)\,} + }{% + gx + }\Biggr) + \] + */ [[nodiscard]] std::optional maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile, From f1fbea21a7f3ef1d27cc2050a09fa8abebc040b3 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 3 Aug 2025 17:33:22 +0300 Subject: [PATCH 331/795] Refactors projectile prediction engine Refactors the projectile prediction engine by introducing an interface and making the legacy implementation more flexible. The legacy engine is updated to allow for coordinate system customization through virtual methods, enabling usage in different game environments. Also introduces vcpkg support for easier dependency management and adds boost-asio as a dependency. --- .../proj_pred_engine.hpp | 4 +- .../proj_pred_engine_avx2.hpp | 2 +- .../proj_pred_engine_legacy.hpp | 23 +++++++++- .../projectile_prediction/projectile.hpp | 3 -- .../proj_pred_engine_legacy.cpp | 45 ++++++++++++++++--- source/projectile_prediction/projectile.cpp | 12 ----- 6 files changed, 63 insertions(+), 26 deletions(-) diff --git a/include/omath/projectile_prediction/proj_pred_engine.hpp b/include/omath/projectile_prediction/proj_pred_engine.hpp index e4be97ec..5117c8be 100644 --- a/include/omath/projectile_prediction/proj_pred_engine.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine.hpp @@ -8,12 +8,12 @@ namespace omath::projectile_prediction { - class ProjPredEngine + class ProjPredEngineInterface { public: [[nodiscard]] virtual std::optional> maybe_calculate_aim_point(const Projectile& projectile, const Target& target) const = 0; - virtual ~ProjPredEngine() = default; + virtual ~ProjPredEngineInterface() = default; }; } // namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp b/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp index 59d0592b..e4a7dc51 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_avx2.hpp @@ -6,7 +6,7 @@ namespace omath::projectile_prediction { - class ProjPredEngineAvx2 final : public ProjPredEngine + class ProjPredEngineAvx2 final : public ProjPredEngineInterface { public: [[nodiscard]] std::optional> diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index 8a72ee96..4de7a8cb 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -12,7 +12,8 @@ namespace omath::projectile_prediction { - class ProjPredEngineLegacy final : public ProjPredEngine + // ReSharper disable once CppClassCanBeFinal + class ProjPredEngineLegacy : public ProjPredEngineInterface { public: explicit ProjPredEngineLegacy(float gravity_constant, float simulation_time_step, float maximum_simulation_time, @@ -48,5 +49,25 @@ namespace omath::projectile_prediction [[nodiscard]] bool is_projectile_reached_target(const Vector3& target_position, const Projectile& projectile, float pitch, float time) const noexcept; + + protected: + // NOTE: Override this if you need to use engine with different coordinate system + // Like where Z is not height coordinate + // =============================================================================================== + [[nodiscard]] + virtual float calc_vector_2d_distance(const Vector3& delta) const; + + [[nodiscard]] + virtual float get_vector_height_coordinate(const Vector3& vec) const; + + [[nodiscard]] + virtual Vector3 calc_viewpoint_from_angles(const Projectile& projectile, + Vector3 predicted_target_position, + std::optional projectile_pitch) const; + + [[nodiscard]] + virtual Vector3 predict_projectile_position(const Projectile& projectile, float pitch, float yaw, + float time, float gravity) const; + // =============================================================================================== }; } // namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/projectile.hpp b/include/omath/projectile_prediction/projectile.hpp index ee0cafd7..b772712b 100644 --- a/include/omath/projectile_prediction/projectile.hpp +++ b/include/omath/projectile_prediction/projectile.hpp @@ -10,9 +10,6 @@ namespace omath::projectile_prediction class Projectile final { public: - [[nodiscard]] - Vector3 predict_position(float pitch, float yaw, float time, float gravity) const noexcept; - Vector3 m_origin; float m_launch_speed{}; float m_gravity_scale{}; diff --git a/source/projectile_prediction/proj_pred_engine_legacy.cpp b/source/projectile_prediction/proj_pred_engine_legacy.cpp index b2f5c7dc..96fd7a0b 100644 --- a/source/projectile_prediction/proj_pred_engine_legacy.cpp +++ b/source/projectile_prediction/proj_pred_engine_legacy.cpp @@ -27,10 +27,7 @@ namespace omath::projectile_prediction if (!is_projectile_reached_target(predicted_target_position, projectile, projectile_pitch.value(), time)) continue; - const auto delta2d = (predicted_target_position - projectile.m_origin).length_2d(); - const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value())); - - return Vector3(predicted_target_position.x, predicted_target_position.y, projectile.m_origin.z + height); + return calc_viewpoint_from_angles(projectile, predicted_target_position, projectile_pitch); } return std::nullopt; } @@ -41,12 +38,14 @@ namespace omath::projectile_prediction const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale; const auto delta = target_position - projectile.m_origin; - const auto distance2d = delta.length_2d(); + const auto distance2d = calc_vector_2d_distance(delta); const auto distance2d_sqr = distance2d * distance2d; const auto launch_speed_sqr = projectile.m_launch_speed * projectile.m_launch_speed; float root = launch_speed_sqr * launch_speed_sqr - - bullet_gravity * (bullet_gravity * distance2d_sqr + 2.0f * delta.z * launch_speed_sqr); + - bullet_gravity + * (bullet_gravity * distance2d_sqr + + 2.0f * get_vector_height_coordinate(delta) * launch_speed_sqr); if (root < 0.0f) [[unlikely]] return std::nullopt; @@ -62,8 +61,40 @@ namespace omath::projectile_prediction const float time) const noexcept { const auto yaw = projectile.m_origin.view_angle_to(target_position).y; - const auto projectile_position = projectile.predict_position(pitch, yaw, time, m_gravity_constant); + const auto projectile_position = predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant); return projectile_position.distance_to(target_position) <= m_distance_tolerance; } + + float ProjPredEngineLegacy::calc_vector_2d_distance(const Vector3& delta) const + { + return std::sqrt(delta.x * delta.x + delta.y * delta.y); + } + + float ProjPredEngineLegacy::get_vector_height_coordinate(const Vector3& vec) const + { + return vec.z; + } + Vector3 ProjPredEngineLegacy::calc_viewpoint_from_angles(const Projectile& projectile, + const Vector3 predicted_target_position, + const std::optional projectile_pitch) const + { + 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}; + } + Vector3 ProjPredEngineLegacy::predict_projectile_position(const Projectile& projectile, const float pitch, + const float yaw, const float time, + const float gravity) const + { + auto current_pos = projectile.m_origin + + source_engine::forward_vector({source_engine::PitchAngle::from_degrees(-pitch), + source_engine::YawAngle::from_degrees(yaw), + source_engine::RollAngle::from_degrees(0)}) + * projectile.m_launch_speed * time; + current_pos.z -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f; + + return current_pos; + } } // namespace omath::projectile_prediction diff --git a/source/projectile_prediction/projectile.cpp b/source/projectile_prediction/projectile.cpp index c37151fe..c37daaae 100644 --- a/source/projectile_prediction/projectile.cpp +++ b/source/projectile_prediction/projectile.cpp @@ -7,16 +7,4 @@ namespace omath::projectile_prediction { - Vector3 Projectile::predict_position(const float pitch, const float yaw, const float time, - const float gravity) const noexcept - { - auto current_pos = m_origin - + source_engine::forward_vector({source_engine::PitchAngle::from_degrees(-pitch), - source_engine::YawAngle::from_degrees(yaw), - source_engine::RollAngle::from_degrees(0)}) - * m_launch_speed * time; - current_pos.z -= (gravity * m_gravity_scale) * (time * time) * 0.5f; - - return current_pos; - } } // namespace omath::projectile_prediction From 9e1990942b89c39fc8db9cd4dc5eec1c39fafb84 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 3 Aug 2025 18:28:47 +0300 Subject: [PATCH 332/795] Refactors projectile prediction engine Migrates projectile prediction logic to leverage engine traits for improved flexibility and testability. This change decouples core prediction algorithms from specific engine implementations, allowing for easier adaptation to different game engines or simulation environments. --- CMakeLists.txt | 2 +- .../engine_traits/source_engine_trait.hpp | 62 +++++++++++++ .../proj_pred_engine_legacy.hpp | 69 +++++++++----- .../proj_pred_engine_legacy.cpp | 92 ------------------- 4 files changed, 109 insertions(+), 116 deletions(-) create mode 100644 include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index af67dd59..2bad1e2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(omath VERSION 3.0.2 LANGUAGES CXX) include(CMakePackageConfigHelpers) -option(OMATH_BUILD_TESTS "Build unit tests" OFF) +option(OMATH_BUILD_TESTS "Build unit tests" ${PROJECT_IS_TOP_LEVEL}) option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON) diff --git a/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp b/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp new file mode 100644 index 00000000..367e93cb --- /dev/null +++ b/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp @@ -0,0 +1,62 @@ +// +// Created by Vlad on 8/3/2025. +// + +#pragma once +#include "omath/engines/source_engine/formulas.hpp" +#include "omath/projectile_prediction/projectile.hpp" +#include + +namespace omath::projectile_prediction::traits +{ + class SourceEngineTrait final + { + public: + constexpr static Vector3 predict_projectile_position(const Projectile& projectile, const float pitch, + const float yaw, const float time, + const float gravity) noexcept + { + auto current_pos = projectile.m_origin + + source_engine::forward_vector({source_engine::PitchAngle::from_degrees(-pitch), + source_engine::YawAngle::from_degrees(yaw), + source_engine::RollAngle::from_degrees(0)}) + * projectile.m_launch_speed * time; + current_pos.z -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f; + + return current_pos; + } + + static bool is_projectile_reached_target(const Vector3& target_position, + const Projectile& projectile, const float pitch, + const float time, const float gravity, + const float tolerance) noexcept + { + const auto yaw = projectile.m_origin.view_angle_to(target_position).y; + const auto projectile_position = predict_projectile_position(projectile, pitch, yaw, time, gravity); + + return projectile_position.distance_to(target_position) <= tolerance; + } + [[nodiscard]] + static float calc_vector_2d_distance(const Vector3& delta) + { + return std::sqrt(delta.x * delta.x + delta.y * delta.y); + } + + [[nodiscard]] + constexpr static float get_vector_height_coordinate(const Vector3& vec) + { + return vec.z; + } + + [[nodiscard]] + static Vector3 calc_viewpoint_from_angles(const Projectile& projectile, + Vector3 predicted_target_position, + const std::optional projectile_pitch) + { + 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}; + } + }; +} // namespace omath::projectile_prediction::traits \ No newline at end of file diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index 4de7a8cb..f463f847 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -4,6 +4,7 @@ #pragma once +#include "engine_traits/source_engine_trait.hpp" #include "omath/projectile_prediction/proj_pred_engine.hpp" #include "omath/projectile_prediction/projectile.hpp" #include "omath/projectile_prediction/target.hpp" @@ -13,15 +14,40 @@ namespace omath::projectile_prediction { // ReSharper disable once CppClassCanBeFinal + template class ProjPredEngineLegacy : public ProjPredEngineInterface { public: - explicit ProjPredEngineLegacy(float gravity_constant, float simulation_time_step, float maximum_simulation_time, - float distance_tolerance); + explicit ProjPredEngineLegacy(const float gravity_constant, const float simulation_time_step, + const float maximum_simulation_time, const float distance_tolerance) + : m_gravity_constant(gravity_constant), m_simulation_time_step(simulation_time_step), + m_maximum_simulation_time(maximum_simulation_time), m_distance_tolerance(distance_tolerance) + { + } [[nodiscard]] std::optional> maybe_calculate_aim_point(const Projectile& projectile, - const Target& target) const override; + const Target& target) const override + { + for (float time = 0.f; time < m_maximum_simulation_time; time += m_simulation_time_step) + { + const auto predicted_target_position = target.predict_position(time, m_gravity_constant); + + const auto projectile_pitch = + maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position); + + if (!projectile_pitch.has_value()) [[unlikely]] + continue; + + if (!EngineTrait::is_projectile_reached_target(predicted_target_position, projectile, + projectile_pitch.value(), time, m_gravity_constant, + m_distance_tolerance)) + continue; + + return EngineTrait::calc_viewpoint_from_angles(projectile, predicted_target_position, projectile_pitch); + } + return std::nullopt; + } private: const float m_gravity_constant; @@ -44,30 +70,27 @@ namespace omath::projectile_prediction [[nodiscard]] std::optional maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile, - const Vector3& target_position) const noexcept; + const Vector3& target_position) const noexcept + { + const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale; + const auto delta = target_position - projectile.m_origin; - [[nodiscard]] - bool is_projectile_reached_target(const Vector3& target_position, const Projectile& projectile, - float pitch, float time) const noexcept; + const auto distance2d = EngineTrait::calc_vector_2d_distance(delta); + const auto distance2d_sqr = distance2d * distance2d; + const auto launch_speed_sqr = projectile.m_launch_speed * projectile.m_launch_speed; - protected: - // NOTE: Override this if you need to use engine with different coordinate system - // Like where Z is not height coordinate - // =============================================================================================== - [[nodiscard]] - virtual float calc_vector_2d_distance(const Vector3& delta) const; + float root = launch_speed_sqr * launch_speed_sqr + - bullet_gravity + * (bullet_gravity * distance2d_sqr + + 2.0f * EngineTrait::get_vector_height_coordinate(delta) * launch_speed_sqr); - [[nodiscard]] - virtual float get_vector_height_coordinate(const Vector3& vec) const; + if (root < 0.0f) [[unlikely]] + return std::nullopt; - [[nodiscard]] - virtual Vector3 calc_viewpoint_from_angles(const Projectile& projectile, - Vector3 predicted_target_position, - std::optional projectile_pitch) const; + root = std::sqrt(root); + const float angle = std::atan((launch_speed_sqr - root) / (bullet_gravity * distance2d)); - [[nodiscard]] - virtual Vector3 predict_projectile_position(const Projectile& projectile, float pitch, float yaw, - float time, float gravity) const; - // =============================================================================================== + return angles::radians_to_degrees(angle); + } }; } // namespace omath::projectile_prediction diff --git a/source/projectile_prediction/proj_pred_engine_legacy.cpp b/source/projectile_prediction/proj_pred_engine_legacy.cpp index 96fd7a0b..7ea7d158 100644 --- a/source/projectile_prediction/proj_pred_engine_legacy.cpp +++ b/source/projectile_prediction/proj_pred_engine_legacy.cpp @@ -4,97 +4,5 @@ namespace omath::projectile_prediction { - ProjPredEngineLegacy::ProjPredEngineLegacy(const float gravity_constant, const float simulation_time_step, - const float maximum_simulation_time, const float distance_tolerance) - : m_gravity_constant(gravity_constant), m_simulation_time_step(simulation_time_step), - m_maximum_simulation_time(maximum_simulation_time), m_distance_tolerance(distance_tolerance) - { - } - std::optional> ProjPredEngineLegacy::maybe_calculate_aim_point(const Projectile& projectile, - const Target& target) const - { - for (float time = 0.f; time < m_maximum_simulation_time; time += m_simulation_time_step) - { - const auto predicted_target_position = target.predict_position(time, m_gravity_constant); - - const auto projectile_pitch = - maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position); - - if (!projectile_pitch.has_value()) [[unlikely]] - continue; - - if (!is_projectile_reached_target(predicted_target_position, projectile, projectile_pitch.value(), time)) - continue; - - return calc_viewpoint_from_angles(projectile, predicted_target_position, projectile_pitch); - } - return std::nullopt; - } - - std::optional ProjPredEngineLegacy::maybe_calculate_projectile_launch_pitch_angle( - const Projectile& projectile, const Vector3& target_position) const noexcept - { - const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale; - const auto delta = target_position - projectile.m_origin; - - const auto distance2d = calc_vector_2d_distance(delta); - const auto distance2d_sqr = distance2d * distance2d; - const auto launch_speed_sqr = projectile.m_launch_speed * projectile.m_launch_speed; - - float root = launch_speed_sqr * launch_speed_sqr - - bullet_gravity - * (bullet_gravity * distance2d_sqr - + 2.0f * get_vector_height_coordinate(delta) * launch_speed_sqr); - - if (root < 0.0f) [[unlikely]] - return std::nullopt; - - root = std::sqrt(root); - const float angle = std::atan((launch_speed_sqr - root) / (bullet_gravity * distance2d)); - - return angles::radians_to_degrees(angle); - } - - bool ProjPredEngineLegacy::is_projectile_reached_target(const Vector3& target_position, - const Projectile& projectile, const float pitch, - const float time) const noexcept - { - const auto yaw = projectile.m_origin.view_angle_to(target_position).y; - const auto projectile_position = predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant); - - return projectile_position.distance_to(target_position) <= m_distance_tolerance; - } - - float ProjPredEngineLegacy::calc_vector_2d_distance(const Vector3& delta) const - { - return std::sqrt(delta.x * delta.x + delta.y * delta.y); - } - - float ProjPredEngineLegacy::get_vector_height_coordinate(const Vector3& vec) const - { - return vec.z; - } - Vector3 ProjPredEngineLegacy::calc_viewpoint_from_angles(const Projectile& projectile, - const Vector3 predicted_target_position, - const std::optional projectile_pitch) const - { - 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}; - } - Vector3 ProjPredEngineLegacy::predict_projectile_position(const Projectile& projectile, const float pitch, - const float yaw, const float time, - const float gravity) const - { - auto current_pos = projectile.m_origin - + source_engine::forward_vector({source_engine::PitchAngle::from_degrees(-pitch), - source_engine::YawAngle::from_degrees(yaw), - source_engine::RollAngle::from_degrees(0)}) - * projectile.m_launch_speed * time; - current_pos.z -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f; - - return current_pos; - } } // namespace omath::projectile_prediction From 493931ef0fc6fcd95f4ceb469776fea31c881edf Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 3 Aug 2025 18:31:02 +0300 Subject: [PATCH 333/795] Ignores vcpkg directory Excludes the vcpkg directory from being tracked by Git. This prevents accidental commits of external library files managed by vcpkg, keeping the repository cleaner. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e0999e5b..fe2f01ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /cmake-build/ /.idea /out -*.DS_Store \ No newline at end of file +*.DS_Store +/extlibs/vcpkg \ No newline at end of file From 2758f549a3fe56dd87f6294571e1daf4eb13e624 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 3 Aug 2025 18:35:52 +0300 Subject: [PATCH 334/795] Updates project version and removes legacy code Updates the project version to prepare for a new release. Removes the legacy projectile prediction engine, which is no longer needed. --- CMakeLists.txt | 2 +- source/projectile_prediction/proj_pred_engine_legacy.cpp | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 source/projectile_prediction/proj_pred_engine_legacy.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bad1e2f..8e84e028 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.26) -project(omath VERSION 3.0.2 LANGUAGES CXX) +project(omath VERSION 3.0.4.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) diff --git a/source/projectile_prediction/proj_pred_engine_legacy.cpp b/source/projectile_prediction/proj_pred_engine_legacy.cpp deleted file mode 100644 index 7ea7d158..00000000 --- a/source/projectile_prediction/proj_pred_engine_legacy.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "omath/projectile_prediction/proj_pred_engine_legacy.hpp" -#include -#include - -namespace omath::projectile_prediction -{ - -} // namespace omath::projectile_prediction From ec76a7239ca8d115685aa7bed0585c51f37a2f26 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 4 Aug 2025 01:11:11 +0300 Subject: [PATCH 335/795] Adds direct pitch angle calculation Implements a direct pitch angle calculation for scenarios with zero gravity, ensuring accurate projectile trajectory predictions in such conditions. Also marks several methods as noexcept for better performance and exception safety. --- .../engine_traits/source_engine_trait.hpp | 16 +++++++++++++--- .../proj_pred_engine_legacy.hpp | 5 +++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp b/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp index 367e93cb..1d4b1137 100644 --- a/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp +++ b/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp @@ -37,13 +37,13 @@ namespace omath::projectile_prediction::traits return projectile_position.distance_to(target_position) <= tolerance; } [[nodiscard]] - static float calc_vector_2d_distance(const Vector3& delta) + 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) + constexpr static float get_vector_height_coordinate(const Vector3& vec) noexcept { return vec.z; } @@ -51,12 +51,22 @@ namespace omath::projectile_prediction::traits [[nodiscard]] static Vector3 calc_viewpoint_from_angles(const Projectile& projectile, Vector3 predicted_target_position, - const std::optional projectile_pitch) + 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 distance = origin.distance_to(view_to); + const auto delta = view_to - origin; + + return angles::radians_to_degrees(std::asin(delta.z / distance)); + } }; } // namespace omath::projectile_prediction::traits \ No newline at end of file diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index f463f847..667954f7 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -73,6 +73,11 @@ namespace omath::projectile_prediction const Vector3& target_position) const noexcept { const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale; + + if (bullet_gravity == 0.f) + return EngineTrait::calc_direct_pitch_angle(projectile.m_origin, target_position); + + const auto delta = target_position - projectile.m_origin; const auto distance2d = EngineTrait::calc_vector_2d_distance(delta); From 7a5090d9f6c121244d4a07a53c47b1d0745e259a Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 4 Aug 2025 01:12:22 +0300 Subject: [PATCH 336/795] Marks legacy engine class as final Prevents further inheritance from the legacy projectile prediction engine class. --- .../omath/projectile_prediction/proj_pred_engine_legacy.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index 667954f7..69a8582f 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -13,9 +13,8 @@ namespace omath::projectile_prediction { - // ReSharper disable once CppClassCanBeFinal template - class ProjPredEngineLegacy : public ProjPredEngineInterface + class ProjPredEngineLegacy final : public ProjPredEngineInterface { public: explicit ProjPredEngineLegacy(const float gravity_constant, const float simulation_time_step, From d12b236e5609bcae0bf665034399c5d32aa2eec8 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 4 Aug 2025 03:16:04 +0300 Subject: [PATCH 337/795] Refactors target position prediction Moves target prediction logic into engine traits, improving modularity. This change consolidates target position prediction within the engine traits, allowing for a more flexible and maintainable design. This eliminates redundant code and simplifies the core prediction engine by delegating target movement calculations to the appropriate trait. --- .../engine_traits/source_engine_trait.hpp | 24 ++++++++++++------- .../proj_pred_engine_legacy.hpp | 19 +++++++++++---- .../omath/projectile_prediction/target.hpp | 11 --------- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp b/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp index 1d4b1137..d124a924 100644 --- a/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp +++ b/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp @@ -5,6 +5,7 @@ #pragma once #include "omath/engines/source_engine/formulas.hpp" #include "omath/projectile_prediction/projectile.hpp" +#include "omath/projectile_prediction/target.hpp" #include namespace omath::projectile_prediction::traits @@ -25,16 +26,16 @@ namespace omath::projectile_prediction::traits return current_pos; } - - static bool is_projectile_reached_target(const Vector3& target_position, - const Projectile& projectile, const float pitch, - const float time, const float gravity, - const float tolerance) noexcept + [[nodiscard]] + static constexpr Vector3 predict_target_position(const Target& target, const float time, + const float gravity) noexcept { - const auto yaw = projectile.m_origin.view_angle_to(target_position).y; - const auto projectile_position = predict_projectile_position(projectile, pitch, yaw, time, gravity); + auto predicted = target.m_origin + target.m_velocity * time; - return projectile_position.distance_to(target_position) <= tolerance; + 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 @@ -68,5 +69,12 @@ namespace omath::projectile_prediction::traits return angles::radians_to_degrees(std::asin(delta.z / distance)); } + [[nodiscard]] + static float calc_direct_yaw_angle(const Vector3& origin, const Vector3& view_to) noexcept + { + const auto delta = view_to - origin; + + return angles::radians_to_degrees(std::atan2(delta.y, delta.x)); + }; }; } // namespace omath::projectile_prediction::traits \ No newline at end of file diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index 69a8582f..801f7196 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -30,7 +30,8 @@ namespace omath::projectile_prediction { for (float time = 0.f; time < m_maximum_simulation_time; time += m_simulation_time_step) { - const auto predicted_target_position = target.predict_position(time, m_gravity_constant); + const auto predicted_target_position = + EngineTrait::predict_target_position(target, time, m_gravity_constant); const auto projectile_pitch = maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position); @@ -38,9 +39,8 @@ namespace omath::projectile_prediction if (!projectile_pitch.has_value()) [[unlikely]] continue; - if (!EngineTrait::is_projectile_reached_target(predicted_target_position, projectile, - projectile_pitch.value(), time, m_gravity_constant, - m_distance_tolerance)) + if (!is_projectile_reached_target(predicted_target_position, projectile, projectile_pitch.value(), + time)) continue; return EngineTrait::calc_viewpoint_from_angles(projectile, predicted_target_position, projectile_pitch); @@ -76,7 +76,6 @@ namespace omath::projectile_prediction if (bullet_gravity == 0.f) return EngineTrait::calc_direct_pitch_angle(projectile.m_origin, target_position); - const auto delta = target_position - projectile.m_origin; const auto distance2d = EngineTrait::calc_vector_2d_distance(delta); @@ -96,5 +95,15 @@ namespace omath::projectile_prediction return angles::radians_to_degrees(angle); } + [[nodiscard]] + bool is_projectile_reached_target(const Vector3& target_position, const Projectile& projectile, + const float pitch, const float time) const noexcept + { + const auto yaw = EngineTrait::calc_direct_yaw_angle(projectile.m_origin, target_position); + const auto projectile_position = + EngineTrait::predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant); + + return projectile_position.distance_to(target_position) <= m_distance_tolerance; + } }; } // namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/target.hpp b/include/omath/projectile_prediction/target.hpp index 4dd38b01..5b1b0f8f 100644 --- a/include/omath/projectile_prediction/target.hpp +++ b/include/omath/projectile_prediction/target.hpp @@ -10,17 +10,6 @@ namespace omath::projectile_prediction class Target final { public: - [[nodiscard]] - constexpr Vector3 predict_position(const float time, const float gravity) const noexcept - { - auto predicted = m_origin + m_velocity * time; - - if (m_is_airborne) - predicted.z -= gravity * (time*time) * 0.5f; - - return predicted; - } - Vector3 m_origin; Vector3 m_velocity; bool m_is_airborne{}; From 695a8035b5ff54e750f0aca4002384f34cba1fd8 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 6 Aug 2025 05:45:37 +0300 Subject: [PATCH 338/795] Adds engine traits for projectile prediction Implements engine-specific traits for projectile and target position prediction. Each trait encapsulates logic tailored to a specific game engine (IW, OpenGL, Unity), accounting for engine-specific coordinate systems and calculations. This allows for accurate projectile prediction across different game environments. --- .../engine_traits/iw_engine_trait.hpp | 80 +++++++++++++++++++ .../engine_traits/opengl_engine_trait.hpp | 79 ++++++++++++++++++ .../engine_traits/unity_engine_trait.hpp | 79 ++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 include/omath/projectile_prediction/engine_traits/iw_engine_trait.hpp create mode 100644 include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp create mode 100644 include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp diff --git a/include/omath/projectile_prediction/engine_traits/iw_engine_trait.hpp b/include/omath/projectile_prediction/engine_traits/iw_engine_trait.hpp new file mode 100644 index 00000000..0361e3b9 --- /dev/null +++ b/include/omath/projectile_prediction/engine_traits/iw_engine_trait.hpp @@ -0,0 +1,80 @@ +// +// Created by Vlad on 8/6/2025. +// +#pragma once + +#include "omath/engines/iw_engine/formulas.hpp" +#include "omath/projectile_prediction/projectile.hpp" +#include "omath/projectile_prediction/target.hpp" +#include + +namespace omath::projectile_prediction::traits +{ + class IwEngineTrait final + { + public: + constexpr static Vector3 predict_projectile_position(const Projectile& projectile, const float pitch, + const float yaw, const float time, + const float gravity) noexcept + { + auto current_pos = projectile.m_origin + + iw_engine::forward_vector({iw_engine::PitchAngle::from_degrees(-pitch), + iw_engine::YawAngle::from_degrees(yaw), + iw_engine::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 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& 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 distance = origin.distance_to(view_to); + const auto delta = view_to - origin; + + return angles::radians_to_degrees(std::asin(delta.z / distance)); + } + [[nodiscard]] + static float calc_direct_yaw_angle(const Vector3& origin, const Vector3& view_to) noexcept + { + const auto delta = view_to - origin; + + return angles::radians_to_degrees(std::atan2(delta.y, delta.x)); + }; + }; +} // namespace omath::projectile_prediction::traits \ No newline at end of file diff --git a/include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp b/include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp new file mode 100644 index 00000000..32418424 --- /dev/null +++ b/include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp @@ -0,0 +1,79 @@ +// +// Created by Vlad on 8/6/2025. +// +#pragma once +#include "omath/engines/opengl_engine/formulas.hpp" +#include "omath/projectile_prediction/projectile.hpp" +#include "omath/projectile_prediction/target.hpp" +#include + +namespace omath::projectile_prediction::traits +{ + class OpenGlEngineTrait final + { + public: + constexpr static Vector3 predict_projectile_position(const Projectile& projectile, const float pitch, + const float yaw, const float time, + const float gravity) noexcept + { + auto current_pos = projectile.m_origin + + opengl_engine::forward_vector({opengl_engine::PitchAngle::from_degrees(-pitch), + opengl_engine::YawAngle::from_degrees(yaw), + opengl_engine::RollAngle::from_degrees(0)}) + * projectile.m_launch_speed * time; + current_pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f; + + return current_pos; + } + [[nodiscard]] + static constexpr Vector3 predict_target_position(const Target& target, const float time, + const float gravity) noexcept + { + auto predicted = target.m_origin + target.m_velocity * time; + + if (target.m_is_airborne) + predicted.y -= 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.z * delta.z); + } + + [[nodiscard]] + constexpr static float get_vector_height_coordinate(const Vector3& vec) noexcept + { + return vec.z; + } + + [[nodiscard]] + static Vector3 calc_viewpoint_from_angles(const 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 + height, projectile.m_origin.z}; + } + // 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 distance = origin.distance_to(view_to); + const auto delta = view_to - origin; + + return angles::radians_to_degrees(std::asin(delta.y / distance)); + } + [[nodiscard]] + static float calc_direct_yaw_angle(const Vector3& origin, const Vector3& view_to) noexcept + { + const auto delta = view_to - origin; + + return angles::radians_to_degrees(std::atan2(delta.z, delta.x)); + }; + }; +} // namespace omath::projectile_prediction::traits diff --git a/include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp b/include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp new file mode 100644 index 00000000..4246ba06 --- /dev/null +++ b/include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp @@ -0,0 +1,79 @@ +// +// Created by Vlad on 8/6/2025. +// +#pragma once +#include "omath/engines/unity_engine/formulas.hpp" +#include "omath/projectile_prediction/projectile.hpp" +#include "omath/projectile_prediction/target.hpp" +#include + +namespace omath::projectile_prediction::traits +{ + class UnityEngineTrait final + { + public: + constexpr static Vector3 predict_projectile_position(const Projectile& projectile, const float pitch, + const float yaw, const float time, + const float gravity) noexcept + { + auto current_pos = projectile.m_origin + + unity_engine::forward_vector({unity_engine::PitchAngle::from_degrees(-pitch), + unity_engine::YawAngle::from_degrees(yaw), + unity_engine::RollAngle::from_degrees(0)}) + * projectile.m_launch_speed * time; + current_pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f; + + return current_pos; + } + [[nodiscard]] + static constexpr Vector3 predict_target_position(const Target& target, const float time, + const float gravity) noexcept + { + auto predicted = target.m_origin + target.m_velocity * time; + + if (target.m_is_airborne) + predicted.y -= 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.z * delta.z); + } + + [[nodiscard]] + constexpr static float get_vector_height_coordinate(const Vector3& vec) noexcept + { + return vec.z; + } + + [[nodiscard]] + static Vector3 calc_viewpoint_from_angles(const 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 + height, projectile.m_origin.z}; + } + // 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 distance = origin.distance_to(view_to); + const auto delta = view_to - origin; + + return angles::radians_to_degrees(std::asin(delta.y / distance)); + } + [[nodiscard]] + static float calc_direct_yaw_angle(const Vector3& origin, const Vector3& view_to) noexcept + { + const auto delta = view_to - origin; + + return angles::radians_to_degrees(std::atan2(delta.z, delta.x)); + }; + }; +} // namespace omath::projectile_prediction::traits From 21ec23d77bd11d5b3e663e013a2f2412feb4ef88 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 6 Aug 2025 05:56:09 +0300 Subject: [PATCH 339/795] patch --- .../projectile_prediction/engine_traits/opengl_engine_trait.hpp | 2 +- .../projectile_prediction/engine_traits/unity_engine_trait.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp b/include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp index 32418424..59e30dfe 100644 --- a/include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp +++ b/include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp @@ -45,7 +45,7 @@ namespace omath::projectile_prediction::traits [[nodiscard]] constexpr static float get_vector_height_coordinate(const Vector3& vec) noexcept { - return vec.z; + return vec.y; } [[nodiscard]] diff --git a/include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp b/include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp index 4246ba06..c70caf84 100644 --- a/include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp +++ b/include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp @@ -45,7 +45,7 @@ namespace omath::projectile_prediction::traits [[nodiscard]] constexpr static float get_vector_height_coordinate(const Vector3& vec) noexcept { - return vec.z; + return vec.y; } [[nodiscard]] From 08d2ccc03aa827ae5ce5f115b9590b03c4ac84fd Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 6 Aug 2025 06:06:42 +0300 Subject: [PATCH 340/795] Refactors Vector operations for type safety Ensures type safety in Vector2, Vector3, and Vector4 operations by using static_cast(0) instead of relying on implicit conversions. This prevents potential issues with different numeric types. Adds from_im_vec2 and from_im_vec4 methods for creating vectors from ImVec2/ImVec4 types. --- include/omath/vector2.hpp | 11 ++++++++--- include/omath/vector3.hpp | 6 +++--- include/omath/vector4.hpp | 10 ++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/include/omath/vector2.hpp b/include/omath/vector2.hpp index 6287131e..2c59b06f 100644 --- a/include/omath/vector2.hpp +++ b/include/omath/vector2.hpp @@ -142,7 +142,7 @@ namespace omath [[nodiscard]] Vector2 normalized() const noexcept { const Type len = length(); - return len > 0.f ? *this / len : *this; + return len > static_cast(0) ? *this / len : *this; } #endif [[nodiscard]] constexpr Type length_sqr() const noexcept @@ -153,8 +153,8 @@ namespace omath constexpr Vector2& abs() noexcept { // FIXME: Replace with std::abs, if it will become constexprable - x = x < 0 ? -x : x; - y = y < 0 ? -y : y; + x = x < static_cast(0) ? -x : x; + y = y < static_cast(0) ? -y : y; return *this; } @@ -202,6 +202,11 @@ namespace omath { return {static_cast(this->x), static_cast(this->y)}; } + [[nodiscard]] + static Vector3 from_im_vec2(const ImVec2& other) noexcept + { + return {static_cast(other.x), static_cast(other.y)}; + } #endif }; } // namespace omath diff --git a/include/omath/vector3.hpp b/include/omath/vector3.hpp index a4654c0d..662ff809 100644 --- a/include/omath/vector3.hpp +++ b/include/omath/vector3.hpp @@ -151,7 +151,7 @@ namespace omath { const Type len = this->length(); - return len != 0 ? *this / len : *this; + return len != static_cast(0) ? *this / len : *this; } [[nodiscard]] Type length_2d() const noexcept @@ -221,7 +221,7 @@ namespace omath { const auto bottom = length() * other.length(); - if (bottom == 0.f) + if (bottom == static_cast(0)) return std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE); return Angle::from_radians(std::acos(dot(other) / bottom)); @@ -230,7 +230,7 @@ namespace omath [[nodiscard]] bool is_perpendicular(const Vector3& other) const noexcept { if (const auto angle = angle_between(other)) - return angle->as_degrees() == 90.f; + return angle->as_degrees() == static_cast(90); return false; } diff --git a/include/omath/vector4.hpp b/include/omath/vector4.hpp index 9e54fbbd..24d3d594 100644 --- a/include/omath/vector4.hpp +++ b/include/omath/vector4.hpp @@ -18,7 +18,7 @@ namespace omath constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w): Vector3(x, y, z), w(w) { } - constexpr Vector4() noexcept : Vector3(), w(0) {}; + constexpr Vector4() noexcept: Vector3(), w(static_cast(0)) {}; [[nodiscard]] constexpr bool operator==(const Vector4& other) const noexcept @@ -169,6 +169,12 @@ namespace omath static_cast(w), }; } + [[nodiscard]] + static Vector4 from_im_vec4(const ImVec4& other) noexcept + { + return {static_cast(other.x), static_cast(other.y), static_cast(other.z)}; + } + } #endif - }; +}; } // namespace omath From ea8f3d8d515e5ab2b1dc89c7fde5f6f29360784b Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 6 Aug 2025 06:17:24 +0300 Subject: [PATCH 341/795] Adds contributing guidelines Introduces a CONTRIBUTING.MD file to provide guidelines for contributing to the project, including prerequisites, setup instructions, pull request workflow, code style, and building instructions. xd returned back patch --- CONTRIBUTING.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..f9594555 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,32 @@ +## 🤝 Contributing to OMath or other Orange's Projects + +### ❕ Prerequisites + +- A working up-to-date OMath installation +- C++ knowledge +- Git knowledge +- Ability to ask for help (Feel free to create empty pull-request or PM a maintainer + in [Telegram](https://t.me/orange_cpp)) + +### ⏬ Setting up OMath + +Please read INSTALL.md file in repository + +### 🔀 Pull requests and Branches + +In order to send code back to the official OMath repository, you must first create a copy of OMath on your github +account ([fork](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)) and +then [create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) back to OMath. + +OMath developement is performed on multiple branches. Changes are then pull requested into master. By default, changes +merged into master will not roll out to stable build users unless the `stable` tag is updated. + +### 📜 Code-Style + +The orange code-style can be found in `.clang-format`. + +### 📦 Building + +OMath has already created the `cmake-build` and `out` directories where cmake/bin files are located. By default, you +can build OMath by running `cmake --build cmake-build/build/windows-release --target omath -j 6` in the source +directory. From a16050242adf364a56540365975635964a57a2d4 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 6 Aug 2025 06:39:57 +0300 Subject: [PATCH 342/795] Adds community badges to README Enhances the README by adding Discord and Telegram badges, providing users with direct access to community channels for support and discussions. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c9a96907..21ae56ac 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml) [![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/orange-math.svg)](https://repology.org/project/orange-math/versions) ![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath) +[![discord badge](https://dcbadge.limes.pink/api/server/https://discord.gg/eDgdaWbqwZ?style=flat)](https://discord.gg/eDgdaWbqwZ) +[![telegram badge](https://img.shields.io/badge/Telegram-2CA5E0?style=flat-squeare&logo=telegram&logoColor=white)](https://t.me/orangennotes) + Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. From 7a9f5d4966496ff33822daccc4a393bd10688ba5 Mon Sep 17 00:00:00 2001 From: Orange Date: Wed, 6 Aug 2025 18:37:20 +0300 Subject: [PATCH 343/795] Removes projectile.cpp Removes the projectile.cpp file. The removal indicates that the associated projectile prediction functionality is no longer needed or has been moved elsewhere. --- source/projectile_prediction/projectile.cpp | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 source/projectile_prediction/projectile.cpp diff --git a/source/projectile_prediction/projectile.cpp b/source/projectile_prediction/projectile.cpp deleted file mode 100644 index c37daaae..00000000 --- a/source/projectile_prediction/projectile.cpp +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by Vlad on 6/9/2024. -// - -#include "omath/projectile_prediction/projectile.hpp" -#include - -namespace omath::projectile_prediction -{ -} // namespace omath::projectile_prediction From c7228c967467548c64c775276db18e705c27ec60 Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 7 Aug 2025 23:33:30 +0300 Subject: [PATCH 344/795] Updates googletest submodule Updates the googletest submodule to the latest commit. This brings in the latest fixes and improvements from the googletest project. --- extlibs/googletest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extlibs/googletest b/extlibs/googletest index d83fee13..52eb8108 160000 --- a/extlibs/googletest +++ b/extlibs/googletest @@ -1 +1 @@ -Subproject commit d83fee138a9ae6cb7c03688a2d08d4043a39815d +Subproject commit 52eb8108c5bdec04579160ae17225d66034bd723 From 17b150499d81a7cff7ad111aebca513deee6fc8a Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 11 Aug 2025 00:32:09 +0300 Subject: [PATCH 345/795] Refactors camera and prediction engine traits. Moves camera and prediction engine implementations into traits for each engine, decoupling the engine-specific logic from the core classes, promoting code reuse and maintainability. This change allows for easier addition of new engines and customization of existing ones. --- include/omath/engines/iw_engine/camera.hpp | 13 ++----- .../engines/iw_engine/traits/camera_trait.hpp | 24 +++++++++++++ .../iw_engine/traits/pred_engine_trait.hpp} | 23 ++++++------ .../omath/engines/opengl_engine/camera.hpp | 11 ++---- .../opengl_engine/traits/camera_trait.hpp | 24 +++++++++++++ .../traits/pred_engine_trait.hpp} | 23 ++++++------ .../omath/engines/source_engine/camera.hpp | 14 ++------ .../source_engine/traits/camera_trait.hpp | 24 +++++++++++++ .../traits/pred_engine_trait.hpp} | 23 ++++++------ include/omath/engines/unity_engine/camera.hpp | 13 ++----- .../unity_engine/traits/camera_trait.hpp | 24 +++++++++++++ .../traits/pred_engine_trait.hpp} | 23 ++++++------ .../proj_pred_engine_legacy.hpp | 4 +-- include/omath/projection/camera.hpp | 15 ++++---- source/engines/iw_engine/camera.cpp | 33 ----------------- .../engines/iw_engine/traits/camera_trait.cpp | 27 ++++++++++++++ source/engines/opengl_engine/camera.cpp | 33 ----------------- .../opengl_engine/traits/camera_trait.cpp | 28 +++++++++++++++ source/engines/source_engine/camera.cpp | 35 ------------------- .../source_engine/traits/camera_trait.cpp | 27 ++++++++++++++ source/engines/unity_engine/camera.cpp | 27 -------------- .../unity_engine/traits/camera_trait.cpp | 27 ++++++++++++++ 22 files changed, 267 insertions(+), 228 deletions(-) create mode 100644 include/omath/engines/iw_engine/traits/camera_trait.hpp rename include/omath/{projectile_prediction/engine_traits/iw_engine_trait.hpp => engines/iw_engine/traits/pred_engine_trait.hpp} (79%) create mode 100644 include/omath/engines/opengl_engine/traits/camera_trait.hpp rename include/omath/{projectile_prediction/engine_traits/opengl_engine_trait.hpp => engines/opengl_engine/traits/pred_engine_trait.hpp} (79%) create mode 100644 include/omath/engines/source_engine/traits/camera_trait.hpp rename include/omath/{projectile_prediction/engine_traits/source_engine_trait.hpp => engines/source_engine/traits/pred_engine_trait.hpp} (79%) create mode 100644 include/omath/engines/unity_engine/traits/camera_trait.hpp rename include/omath/{projectile_prediction/engine_traits/unity_engine_trait.hpp => engines/unity_engine/traits/pred_engine_trait.hpp} (79%) delete mode 100644 source/engines/iw_engine/camera.cpp create mode 100644 source/engines/iw_engine/traits/camera_trait.cpp delete mode 100644 source/engines/opengl_engine/camera.cpp create mode 100644 source/engines/opengl_engine/traits/camera_trait.cpp delete mode 100644 source/engines/source_engine/camera.cpp create mode 100644 source/engines/source_engine/traits/camera_trait.cpp delete mode 100644 source/engines/unity_engine/camera.cpp create mode 100644 source/engines/unity_engine/traits/camera_trait.cpp diff --git a/include/omath/engines/iw_engine/camera.hpp b/include/omath/engines/iw_engine/camera.hpp index 2f530e18..2efe29b1 100644 --- a/include/omath/engines/iw_engine/camera.hpp +++ b/include/omath/engines/iw_engine/camera.hpp @@ -5,18 +5,9 @@ #pragma once #include "omath/engines/iw_engine/constants.hpp" #include "omath/projection/camera.hpp" +#include "traits/camera_trait.hpp" namespace omath::iw_engine { - class Camera final : public projection::Camera - { - public: - Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, - const Angle& fov, float near, float far); - void look_at(const Vector3& target) override; - - protected: - [[nodiscard]] Mat4X4 calc_view_matrix() const noexcept override; - [[nodiscard]] Mat4X4 calc_projection_matrix() const noexcept override; - }; + using Camera = projection::Camera; } // namespace omath::iw_engine \ No newline at end of file diff --git a/include/omath/engines/iw_engine/traits/camera_trait.hpp b/include/omath/engines/iw_engine/traits/camera_trait.hpp new file mode 100644 index 00000000..88c21569 --- /dev/null +++ b/include/omath/engines/iw_engine/traits/camera_trait.hpp @@ -0,0 +1,24 @@ +// +// Created by Vlad on 8/10/2025. +// + +#pragma once +#include "omath/engines/iw_engine/constants.hpp" +#include "omath/projection/camera.hpp" + +namespace omath::iw_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::iw_engine \ No newline at end of file diff --git a/include/omath/projectile_prediction/engine_traits/iw_engine_trait.hpp b/include/omath/engines/iw_engine/traits/pred_engine_trait.hpp similarity index 79% rename from include/omath/projectile_prediction/engine_traits/iw_engine_trait.hpp rename to include/omath/engines/iw_engine/traits/pred_engine_trait.hpp index 0361e3b9..7961268a 100644 --- a/include/omath/projectile_prediction/engine_traits/iw_engine_trait.hpp +++ b/include/omath/engines/iw_engine/traits/pred_engine_trait.hpp @@ -8,27 +8,26 @@ #include "omath/projectile_prediction/target.hpp" #include -namespace omath::projectile_prediction::traits +namespace omath::iw_engine { - class IwEngineTrait final + class PredEngineTrait final { public: - constexpr static Vector3 predict_projectile_position(const Projectile& projectile, const float pitch, - const float yaw, const float time, - const float gravity) noexcept + 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 - + iw_engine::forward_vector({iw_engine::PitchAngle::from_degrees(-pitch), - iw_engine::YawAngle::from_degrees(yaw), - iw_engine::RollAngle::from_degrees(0)}) + + 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 Target& target, const float time, - const float gravity) noexcept + 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; @@ -50,7 +49,7 @@ namespace omath::projectile_prediction::traits } [[nodiscard]] - static Vector3 calc_viewpoint_from_angles(const Projectile& projectile, + static Vector3 calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile, Vector3 predicted_target_position, const std::optional projectile_pitch) noexcept { @@ -77,4 +76,4 @@ namespace omath::projectile_prediction::traits return angles::radians_to_degrees(std::atan2(delta.y, delta.x)); }; }; -} // namespace omath::projectile_prediction::traits \ No newline at end of file +} // namespace omath::iw_engine \ No newline at end of file diff --git a/include/omath/engines/opengl_engine/camera.hpp b/include/omath/engines/opengl_engine/camera.hpp index b4cdd210..e69ee340 100644 --- a/include/omath/engines/opengl_engine/camera.hpp +++ b/include/omath/engines/opengl_engine/camera.hpp @@ -4,16 +4,9 @@ #pragma once #include "omath/engines/opengl_engine/constants.hpp" #include "omath/projection/camera.hpp" +#include "traits/camera_trait.hpp" namespace omath::opengl_engine { - class Camera final : public projection::Camera - { - public: - Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, - const Angle& fov, float near, float far); - void look_at(const Vector3& target) override; - [[nodiscard]] Mat4X4 calc_view_matrix() const noexcept override; - [[nodiscard]] Mat4X4 calc_projection_matrix() const noexcept override; - }; + using Camera = projection::Camera; } // namespace omath::opengl_engine \ No newline at end of file diff --git a/include/omath/engines/opengl_engine/traits/camera_trait.hpp b/include/omath/engines/opengl_engine/traits/camera_trait.hpp new file mode 100644 index 00000000..3fb57c07 --- /dev/null +++ b/include/omath/engines/opengl_engine/traits/camera_trait.hpp @@ -0,0 +1,24 @@ +// +// Created by Vlad on 8/10/2025. +// + +#pragma once +#include "omath/engines/opengl_engine/constants.hpp" +#include "omath/projection/camera.hpp" + +namespace omath::opengl_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::opengl_engine \ No newline at end of file diff --git a/include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp b/include/omath/engines/opengl_engine/traits/pred_engine_trait.hpp similarity index 79% rename from include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp rename to include/omath/engines/opengl_engine/traits/pred_engine_trait.hpp index 59e30dfe..9c014ff8 100644 --- a/include/omath/projectile_prediction/engine_traits/opengl_engine_trait.hpp +++ b/include/omath/engines/opengl_engine/traits/pred_engine_trait.hpp @@ -7,27 +7,26 @@ #include "omath/projectile_prediction/target.hpp" #include -namespace omath::projectile_prediction::traits +namespace omath::opengl_engine { - class OpenGlEngineTrait final + class PredEngineTrait final { public: - constexpr static Vector3 predict_projectile_position(const Projectile& projectile, const float pitch, - const float yaw, const float time, - const float gravity) noexcept + 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 - + opengl_engine::forward_vector({opengl_engine::PitchAngle::from_degrees(-pitch), - opengl_engine::YawAngle::from_degrees(yaw), - opengl_engine::RollAngle::from_degrees(0)}) + + forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw), + RollAngle::from_degrees(0)}) * projectile.m_launch_speed * time; current_pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f; return current_pos; } [[nodiscard]] - static constexpr Vector3 predict_target_position(const Target& target, const float time, - const float gravity) noexcept + 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; @@ -49,7 +48,7 @@ namespace omath::projectile_prediction::traits } [[nodiscard]] - static Vector3 calc_viewpoint_from_angles(const Projectile& projectile, + static Vector3 calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile, Vector3 predicted_target_position, const std::optional projectile_pitch) noexcept { @@ -76,4 +75,4 @@ namespace omath::projectile_prediction::traits return angles::radians_to_degrees(std::atan2(delta.z, delta.x)); }; }; -} // namespace omath::projectile_prediction::traits +} // namespace omath::opengl_engine diff --git a/include/omath/engines/source_engine/camera.hpp b/include/omath/engines/source_engine/camera.hpp index cba5c434..6769d40d 100644 --- a/include/omath/engines/source_engine/camera.hpp +++ b/include/omath/engines/source_engine/camera.hpp @@ -4,18 +4,8 @@ #pragma once #include "omath/engines/source_engine/constants.hpp" #include "omath/projection/camera.hpp" - +#include "traits/camera_trait.hpp" namespace omath::source_engine { - class Camera final : public projection::Camera - { - public: - Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, - const Angle& fov, float near, float far); - void look_at(const Vector3& target) override; - - protected: - [[nodiscard]] Mat4X4 calc_view_matrix() const noexcept override; - [[nodiscard]] Mat4X4 calc_projection_matrix() const noexcept override; - }; + using Camera = projection::Camera; } // namespace omath::source_engine \ No newline at end of file diff --git a/include/omath/engines/source_engine/traits/camera_trait.hpp b/include/omath/engines/source_engine/traits/camera_trait.hpp new file mode 100644 index 00000000..d027d25e --- /dev/null +++ b/include/omath/engines/source_engine/traits/camera_trait.hpp @@ -0,0 +1,24 @@ +// +// Created by Vlad on 8/10/2025. +// + +#pragma once +#include "omath/engines/source_engine/constants.hpp" +#include "omath/projection/camera.hpp" + +namespace omath::source_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::source_engine \ No newline at end of file diff --git a/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp b/include/omath/engines/source_engine/traits/pred_engine_trait.hpp similarity index 79% rename from include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp rename to include/omath/engines/source_engine/traits/pred_engine_trait.hpp index d124a924..ca9771e1 100644 --- a/include/omath/projectile_prediction/engine_traits/source_engine_trait.hpp +++ b/include/omath/engines/source_engine/traits/pred_engine_trait.hpp @@ -8,27 +8,26 @@ #include "omath/projectile_prediction/target.hpp" #include -namespace omath::projectile_prediction::traits +namespace omath::source_engine { - class SourceEngineTrait final + class PredEngineTrait final { public: - constexpr static Vector3 predict_projectile_position(const Projectile& projectile, const float pitch, - const float yaw, const float time, - const float gravity) noexcept + 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 - + source_engine::forward_vector({source_engine::PitchAngle::from_degrees(-pitch), - source_engine::YawAngle::from_degrees(yaw), - source_engine::RollAngle::from_degrees(0)}) + + 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 Target& target, const float time, - const float gravity) noexcept + 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; @@ -50,7 +49,7 @@ namespace omath::projectile_prediction::traits } [[nodiscard]] - static Vector3 calc_viewpoint_from_angles(const Projectile& projectile, + static Vector3 calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile, Vector3 predicted_target_position, const std::optional projectile_pitch) noexcept { @@ -77,4 +76,4 @@ namespace omath::projectile_prediction::traits return angles::radians_to_degrees(std::atan2(delta.y, delta.x)); }; }; -} // namespace omath::projectile_prediction::traits \ No newline at end of file +} // namespace omath::source_engine \ No newline at end of file diff --git a/include/omath/engines/unity_engine/camera.hpp b/include/omath/engines/unity_engine/camera.hpp index 568b2a75..642290a3 100644 --- a/include/omath/engines/unity_engine/camera.hpp +++ b/include/omath/engines/unity_engine/camera.hpp @@ -5,18 +5,9 @@ #pragma once #include "omath/engines/unity_engine/constants.hpp" #include "omath/projection/camera.hpp" +#include "traits/camera_trait.hpp" namespace omath::unity_engine { - class Camera final : public projection::Camera - { - public: - Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, - const Angle& fov, float near, float far); - void look_at(const Vector3& target) override; - - protected: - [[nodiscard]] Mat4X4 calc_view_matrix() const noexcept override; - [[nodiscard]] Mat4X4 calc_projection_matrix() const noexcept override; - }; + using Camera = projection::Camera; } // namespace omath::unity_engine \ No newline at end of file diff --git a/include/omath/engines/unity_engine/traits/camera_trait.hpp b/include/omath/engines/unity_engine/traits/camera_trait.hpp new file mode 100644 index 00000000..2d98b9db --- /dev/null +++ b/include/omath/engines/unity_engine/traits/camera_trait.hpp @@ -0,0 +1,24 @@ +// +// Created by Vlad on 8/10/2025. +// + +#pragma once +#include "omath/engines/unity_engine/formulas.hpp" +#include "omath/projection/camera.hpp" + +namespace omath::unity_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::unity_engine \ No newline at end of file diff --git a/include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp b/include/omath/engines/unity_engine/traits/pred_engine_trait.hpp similarity index 79% rename from include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp rename to include/omath/engines/unity_engine/traits/pred_engine_trait.hpp index c70caf84..5851e4c6 100644 --- a/include/omath/projectile_prediction/engine_traits/unity_engine_trait.hpp +++ b/include/omath/engines/unity_engine/traits/pred_engine_trait.hpp @@ -7,27 +7,26 @@ #include "omath/projectile_prediction/target.hpp" #include -namespace omath::projectile_prediction::traits +namespace omath::unity_engine { - class UnityEngineTrait final + class PredEngineTrait final { public: - constexpr static Vector3 predict_projectile_position(const Projectile& projectile, const float pitch, - const float yaw, const float time, - const float gravity) noexcept + 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 - + unity_engine::forward_vector({unity_engine::PitchAngle::from_degrees(-pitch), - unity_engine::YawAngle::from_degrees(yaw), - unity_engine::RollAngle::from_degrees(0)}) + + forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw), + RollAngle::from_degrees(0)}) * projectile.m_launch_speed * time; current_pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f; return current_pos; } [[nodiscard]] - static constexpr Vector3 predict_target_position(const Target& target, const float time, - const float gravity) noexcept + 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; @@ -49,7 +48,7 @@ namespace omath::projectile_prediction::traits } [[nodiscard]] - static Vector3 calc_viewpoint_from_angles(const Projectile& projectile, + static Vector3 calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile, Vector3 predicted_target_position, const std::optional projectile_pitch) noexcept { @@ -76,4 +75,4 @@ namespace omath::projectile_prediction::traits return angles::radians_to_degrees(std::atan2(delta.z, delta.x)); }; }; -} // namespace omath::projectile_prediction::traits +} // namespace omath::unity_engine diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index 801f7196..8d44255e 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -4,7 +4,7 @@ #pragma once -#include "engine_traits/source_engine_trait.hpp" +#include "omath/engines/source_engine/traits/pred_engine_trait.hpp" #include "omath/projectile_prediction/proj_pred_engine.hpp" #include "omath/projectile_prediction/projectile.hpp" #include "omath/projectile_prediction/target.hpp" @@ -13,7 +13,7 @@ namespace omath::projectile_prediction { - template + template class ProjPredEngineLegacy final : public ProjPredEngineInterface { public: diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index cf93a78f..b56879e9 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -26,7 +26,7 @@ namespace omath::projection }; using FieldOfView = Angle; - template + template class Camera { public: @@ -39,15 +39,16 @@ namespace omath::projection } protected: - virtual void look_at(const Vector3& target) = 0; - - [[nodiscard]] virtual Mat4X4Type calc_view_matrix() const noexcept = 0; - - [[nodiscard]] virtual Mat4X4Type calc_projection_matrix() const noexcept = 0; + void look_at(const Vector3& target) + { + m_view_angles = TraitClass::calc_look_at_angle(m_origin, target); + } [[nodiscard]] Mat4X4Type calc_view_projection_matrix() const noexcept { - return calc_projection_matrix() * calc_view_matrix(); + return TraitClass::calc_projection_matrix(m_field_of_view, m_view_port, m_near_plane_distance, + m_far_plane_distance) + * TraitClass::calc_view_matrix(m_view_angles, m_origin); } public: diff --git a/source/engines/iw_engine/camera.cpp b/source/engines/iw_engine/camera.cpp deleted file mode 100644 index 27d8ae78..00000000 --- a/source/engines/iw_engine/camera.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// Created by Vlad on 3/17/2025. -// -#include "omath/engines/iw_engine/camera.hpp" -#include "omath/engines/iw_engine/formulas.hpp" - -namespace omath::iw_engine -{ - - Camera::Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, - const Angle& fov, const float near, const float far) - : projection::Camera(position, view_angles, view_port, fov, near, far) - { - } - void Camera::look_at([[maybe_unused]] const Vector3& target) - { - const float distance = m_origin.distance_to(target); - const auto delta = target - m_origin; - - m_view_angles.pitch = PitchAngle::from_radians(std::asin(delta.z / distance)); - m_view_angles.yaw = -YawAngle::from_radians(std::atan2(delta.y, delta.x)); - m_view_angles.roll = RollAngle::from_radians(0.f); - } - Mat4X4 Camera::calc_view_matrix() const noexcept - { - return iw_engine::calc_view_matrix(m_view_angles, m_origin); - } - Mat4X4 Camera::calc_projection_matrix() const noexcept - { - return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), - m_near_plane_distance, m_far_plane_distance); - } -} // namespace omath::iw_engine \ No newline at end of file diff --git a/source/engines/iw_engine/traits/camera_trait.cpp b/source/engines/iw_engine/traits/camera_trait.cpp new file mode 100644 index 00000000..087f7b09 --- /dev/null +++ b/source/engines/iw_engine/traits/camera_trait.cpp @@ -0,0 +1,27 @@ +// +// Created by Vlad on 8/11/2025. +// +#include "omath/engines/iw_engine/traits/camera_trait.hpp" + +namespace omath::iw_engine +{ + + ViewAngles CameraTrait::calc_look_at_angle(const Vector3& cam_origin, const Vector3& look_at) noexcept + { + const auto distance = cam_origin.distance_to(look_at); + const auto delta = cam_origin - look_at; + + return {PitchAngle::from_radians(-std::asin(delta.z / distance)), + YawAngle::from_radians(std::atan2(delta.y, delta.x)), RollAngle::from_radians(0.f)}; + } + Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept + { + return iw_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::iw_engine \ No newline at end of file diff --git a/source/engines/opengl_engine/camera.cpp b/source/engines/opengl_engine/camera.cpp deleted file mode 100644 index 269974cc..00000000 --- a/source/engines/opengl_engine/camera.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// Created by Orange on 12/23/2024. -// -#include "omath/engines/opengl_engine/camera.hpp" -#include "omath/engines/opengl_engine/formulas.hpp" - -namespace omath::opengl_engine -{ - - Camera::Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, - const Angle& fov, const float near, const float far) - : projection::Camera(position, view_angles, view_port, fov, near, far) - { - } - void Camera::look_at([[maybe_unused]] const Vector3& target) - { - const float distance = m_origin.distance_to(target); - const auto delta = target - m_origin; - - m_view_angles.pitch = PitchAngle::from_radians(std::asin(delta.z / distance)); - m_view_angles.yaw = -YawAngle::from_radians(std::atan2(delta.y, delta.x)); - m_view_angles.roll = RollAngle::from_radians(0.f); - } - Mat4X4 Camera::calc_view_matrix() const noexcept - { - return opengl_engine::calc_view_matrix(m_view_angles, m_origin); - } - Mat4X4 Camera::calc_projection_matrix() const noexcept - { - return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), - m_near_plane_distance, m_far_plane_distance); - } -} // namespace omath::opengl_engine diff --git a/source/engines/opengl_engine/traits/camera_trait.cpp b/source/engines/opengl_engine/traits/camera_trait.cpp new file mode 100644 index 00000000..9fb644fc --- /dev/null +++ b/source/engines/opengl_engine/traits/camera_trait.cpp @@ -0,0 +1,28 @@ +// +// Created by Vlad on 8/11/2025. +// +#include "omath/engines/opengl_engine/traits/camera_trait.hpp" + + +namespace omath::opengl_engine +{ + + ViewAngles CameraTrait::calc_look_at_angle(const Vector3& cam_origin, const Vector3& look_at) noexcept + { + const auto distance = cam_origin.distance_to(look_at); + const auto delta = cam_origin - look_at; + + return {PitchAngle::from_radians(-std::asin(delta.y / distance)), + YawAngle::from_radians(std::atan2(delta.z, delta.x)), RollAngle::from_radians(0.f)}; + } + Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept + { + return opengl_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::opengl_engine \ No newline at end of file diff --git a/source/engines/source_engine/camera.cpp b/source/engines/source_engine/camera.cpp deleted file mode 100644 index edd4c529..00000000 --- a/source/engines/source_engine/camera.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// Created by Orange on 12/4/2024. -// -#include "omath/engines/source_engine/camera.hpp" -#include "omath/engines/source_engine/formulas.hpp" - -namespace omath::source_engine -{ - - Camera::Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, - const projection::FieldOfView& fov, const float near, const float far) - : projection::Camera(position, view_angles, view_port, fov, near, far) - { - } - void Camera::look_at(const Vector3& target) - { - const float distance = m_origin.distance_to(target); - const auto delta = target - m_origin; - - m_view_angles.pitch = PitchAngle::from_radians(std::asin(delta.z / distance)); - m_view_angles.yaw = -YawAngle::from_radians(std::atan2(delta.y, delta.x)); - m_view_angles.roll = RollAngle::from_radians(0.f); - } - - Mat4X4 Camera::calc_view_matrix() const noexcept - { - return source_engine::calc_view_matrix(m_view_angles, m_origin); - } - - Mat4X4 Camera::calc_projection_matrix() const noexcept - { - return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), - m_near_plane_distance, m_far_plane_distance); - } -} // namespace omath::source_engine diff --git a/source/engines/source_engine/traits/camera_trait.cpp b/source/engines/source_engine/traits/camera_trait.cpp new file mode 100644 index 00000000..bf810b76 --- /dev/null +++ b/source/engines/source_engine/traits/camera_trait.cpp @@ -0,0 +1,27 @@ +// +// Created by Vlad on 8/11/2025. +// +#include "omath/engines/source_engine/traits/camera_trait.hpp" + +namespace omath::source_engine +{ + + ViewAngles CameraTrait::calc_look_at_angle(const Vector3& cam_origin, const Vector3& look_at) noexcept + { + const auto distance = cam_origin.distance_to(look_at); + const auto delta = cam_origin - look_at; + + return {PitchAngle::from_radians(-std::asin(delta.z / distance)), + YawAngle::from_radians(std::atan2(delta.y, delta.x)), RollAngle::from_radians(0.f)}; + } + Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept + { + return source_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::source_engine \ No newline at end of file diff --git a/source/engines/unity_engine/camera.cpp b/source/engines/unity_engine/camera.cpp deleted file mode 100644 index 5455c766..00000000 --- a/source/engines/unity_engine/camera.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// -// Created by Vlad on 3/22/2025. -// -#include -#include - -namespace omath::unity_engine -{ - Camera::Camera(const Vector3& position, const ViewAngles& view_angles, const projection::ViewPort& view_port, - const projection::FieldOfView& fov, const float near, const float far) - : projection::Camera(position, view_angles, view_port, fov, near, far) - { - } - void Camera::look_at([[maybe_unused]] const Vector3& target) - { - throw std::runtime_error("Not implemented"); - } - Mat4X4 Camera::calc_view_matrix() const noexcept - { - return unity_engine::calc_view_matrix(m_view_angles, m_origin); - } - Mat4X4 Camera::calc_projection_matrix() const noexcept - { - return calc_perspective_projection_matrix(m_field_of_view.as_degrees(), m_view_port.aspect_ratio(), - m_near_plane_distance, m_far_plane_distance); - } -} // namespace omath::unity_engine diff --git a/source/engines/unity_engine/traits/camera_trait.cpp b/source/engines/unity_engine/traits/camera_trait.cpp new file mode 100644 index 00000000..d76426b7 --- /dev/null +++ b/source/engines/unity_engine/traits/camera_trait.cpp @@ -0,0 +1,27 @@ +// +// Created by Vlad on 8/11/2025. +// +#include "omath/engines/unity_engine/traits/camera_trait.hpp" + +namespace omath::unity_engine +{ + + ViewAngles CameraTrait::calc_look_at_angle(const Vector3& cam_origin, const Vector3& look_at) noexcept + { + const auto distance = cam_origin.distance_to(look_at); + const auto delta = cam_origin - look_at; + + return {PitchAngle::from_radians(-std::asin(delta.y / distance)), + YawAngle::from_radians(std::atan2(delta.z, delta.x)), RollAngle::from_radians(0.f)}; + } + Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept + { + return unity_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 From d96b0cd2f76126f295f2ec7d4aea7ac419ea3314 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 11 Aug 2025 01:18:39 +0300 Subject: [PATCH 346/795] Marks Camera class as final Prevents inheritance from the Camera class. --- include/omath/projection/camera.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index b56879e9..816d47fd 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -27,7 +27,7 @@ namespace omath::projection using FieldOfView = Angle; template - class Camera + class Camera final { public: virtual ~Camera() = default; From 4d24815f3e198a2bfd5277fed004395b8785f726 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Mon, 11 Aug 2025 01:30:26 +0300 Subject: [PATCH 347/795] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/custom.md | 10 ++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 00000000..48d5f81f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 15c055beb724c2d9e9106cd5e5f7ec887fa28d2a Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 11 Aug 2025 01:43:20 +0300 Subject: [PATCH 348/795] Removes virtual destructor from Camera Removes the virtual destructor from the Camera class as it is not required, as the class does not act as a base class. This simplifies the class definition and avoids potential vtable overhead. --- include/omath/projection/camera.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index 816d47fd..79eb1912 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -30,7 +30,7 @@ namespace omath::projection class Camera final { public: - virtual ~Camera() = default; + ~Camera() = default; Camera(const Vector3& position, const ViewAnglesType& view_angles, const ViewPort& view_port, const FieldOfView& fov, const float near, const float far) noexcept : m_view_port(view_port), m_field_of_view(fov), m_far_plane_distance(far), m_near_plane_distance(near), From f277b1038c37fdb11f48146c5f82a0c9dc96fbff Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 12 Aug 2025 08:54:33 +0300 Subject: [PATCH 349/795] Adds comparison operators to Vector types Adds less than, greater than, less than or equal, and greater than or equal operators to the Vector2, Vector3 and Vector4 classes. The comparison is based on the lengths of the vectors. Adds corresponding unit tests. --- include/omath/vector2.hpp | 23 +++++++++++++++++++++++ include/omath/vector3.hpp | 24 ++++++++++++++++++++++++ include/omath/vector4.hpp | 26 +++++++++++++++++++++++++- tests/general/unit_test_vector2.cpp | 22 ++++++++++++++++++++++ tests/general/unit_test_vector3.cpp | 21 +++++++++++++++++++++ tests/general/unit_test_vector4.cpp | 20 ++++++++++++++++++++ 6 files changed, 135 insertions(+), 1 deletion(-) diff --git a/include/omath/vector2.hpp b/include/omath/vector2.hpp index 2c59b06f..8470d75e 100644 --- a/include/omath/vector2.hpp +++ b/include/omath/vector2.hpp @@ -190,6 +190,29 @@ namespace omath return x + y; } + [[nodiscard]] + bool operator<(const Vector2& other) const noexcept + { + return length() < other.length(); + } + [[nodiscard]] + bool operator>(const Vector2& other) const noexcept + { + return length() > other.length(); + } + + [[nodiscard]] + bool operator<=(const Vector2& other) const noexcept + { + return length() <= other.length(); + } + + [[nodiscard]] + bool operator>=(const Vector2& other) const noexcept + { + return length() >= other.length(); + } + [[nodiscard]] constexpr std::tuple as_tuple() const noexcept { diff --git a/include/omath/vector3.hpp b/include/omath/vector3.hpp index 662ff809..37490329 100644 --- a/include/omath/vector3.hpp +++ b/include/omath/vector3.hpp @@ -253,6 +253,30 @@ namespace omath return {angles::radians_to_degrees(std::asin(delta.z / distance)), angles::radians_to_degrees(std::atan2(delta.y, delta.x)), 0}; } + + [[nodiscard]] + bool operator<(const Vector3& other) const noexcept + { + return length() < other.length(); + } + + [[nodiscard]] + bool operator>(const Vector3& other) const noexcept + { + return length() > other.length(); + } + + [[nodiscard]] + bool operator<=(const Vector3& other) const noexcept + { + return length() <= other.length(); + } + + [[nodiscard]] + bool operator>=(const Vector3& other) const noexcept + { + return length() >= other.length(); + } }; } // namespace omath // ReSharper disable once CppRedundantNamespaceDefinition diff --git a/include/omath/vector4.hpp b/include/omath/vector4.hpp index 24d3d594..2815b454 100644 --- a/include/omath/vector4.hpp +++ b/include/omath/vector4.hpp @@ -89,7 +89,7 @@ namespace omath return Vector3::dot(other) + w * other.w; } - [[nodiscard]] Vector3 length() const noexcept + [[nodiscard]] Type length() const noexcept { return std::sqrt(length_sqr()); } @@ -158,6 +158,30 @@ namespace omath return Vector3::sum() + w; } + [[nodiscard]] + bool operator<(const Vector4& other) const noexcept + { + return length() < other.length(); + } + + [[nodiscard]] + bool operator>(const Vector4& other) const noexcept + { + return length() > other.length(); + } + + [[nodiscard]] + bool operator<=(const Vector4& other) const noexcept + { + return length() <= other.length(); + } + + [[nodiscard]] + bool operator>=(const Vector4& other) const noexcept + { + return length() >= other.length(); + } + #ifdef OMATH_IMGUI_INTEGRATION [[nodiscard]] ImVec4 to_im_vec4() const noexcept diff --git a/tests/general/unit_test_vector2.cpp b/tests/general/unit_test_vector2.cpp index 1896b152..b24bceaa 100644 --- a/tests/general/unit_test_vector2.cpp +++ b/tests/general/unit_test_vector2.cpp @@ -346,6 +346,28 @@ TEST_F(UnitTestVector2, NegationOperator_ZeroVector) EXPECT_FLOAT_EQ(result.y, -0.0f); } +TEST_F(UnitTestVector2, LessOperator) +{ + EXPECT_TRUE(v1 < v2); +} + +TEST_F(UnitTestVector2, GreaterOperator) +{ + EXPECT_TRUE(v2 > v1); +} +TEST_F(UnitTestVector2, LessEqualOperator) +{ + EXPECT_TRUE(omath::Vector2{} <= omath::Vector2{}); + EXPECT_TRUE(omath::Vector2{} <= omath::Vector2(1.f, 1.f)); +} + +TEST_F(UnitTestVector2, GreaterEqualOperator) +{ + EXPECT_TRUE(omath::Vector2{} >= omath::Vector2{}); + EXPECT_TRUE(omath::Vector2(1.f, 1.f) >= omath::Vector2{}); +} + + // Static assertions (compile-time checks) 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"); diff --git a/tests/general/unit_test_vector3.cpp b/tests/general/unit_test_vector3.cpp index 9cf66ef3..66e1a60f 100644 --- a/tests/general/unit_test_vector3.cpp +++ b/tests/general/unit_test_vector3.cpp @@ -402,6 +402,27 @@ TEST_F(UnitTestVector3, IsPerpendicular) EXPECT_FALSE(Vector3(0.0f, 0.0f, 0.0f).is_perpendicular({0.0f, 0.0f, 1.0f})); } +TEST_F(UnitTestVector3, LessOperator) +{ + EXPECT_TRUE(v1 < v2); +} + +TEST_F(UnitTestVector3, GreaterOperator) +{ + EXPECT_TRUE(v2 > v1); +} +TEST_F(UnitTestVector3, LessEqualOperator) +{ + EXPECT_TRUE(omath::Vector3{} <= omath::Vector3{}); + EXPECT_TRUE(omath::Vector3{} <= omath::Vector3(1.f, 1.f, 1.f)); +} + +TEST_F(UnitTestVector3, GreaterEqualOperator) +{ + EXPECT_TRUE(omath::Vector3{} >= omath::Vector3{}); + EXPECT_TRUE(omath::Vector3(1.f, 1.f, 1.f) >= omath::Vector3{}); +} + // Static assertions (compile-time checks) 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"); diff --git a/tests/general/unit_test_vector4.cpp b/tests/general/unit_test_vector4.cpp index dcdb7fea..6071a2c2 100644 --- a/tests/general/unit_test_vector4.cpp +++ b/tests/general/unit_test_vector4.cpp @@ -215,3 +215,23 @@ TEST_F(UnitTestVector4, Clamp) EXPECT_FLOAT_EQ(v3.z, 2.5f); EXPECT_FLOAT_EQ(v3.w, 4.0f); // w is not clamped in this method } +TEST_F(UnitTestVector4, LessOperator) +{ + EXPECT_TRUE(v1 < v2); +} + +TEST_F(UnitTestVector4, GreaterOperator) +{ + EXPECT_TRUE(v2 > v1); +} +TEST_F(UnitTestVector4, LessEqualOperator) +{ + EXPECT_TRUE(omath::Vector4{} <= omath::Vector4{}); + EXPECT_TRUE(omath::Vector4{} <= omath::Vector4(1.f, 1.f, 1.f, 1.f)); +} + +TEST_F(UnitTestVector4, GreaterEqualOperator) +{ + EXPECT_TRUE(omath::Vector4{} >= omath::Vector4{}); + EXPECT_TRUE(omath::Vector4(1.f, 1.f, 1.f, 1.f) >= omath::Vector4{}); +} \ No newline at end of file From 0bd9eda48fa24f8e309860ad84117c7c389f77c2 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 12 Aug 2025 09:12:57 +0300 Subject: [PATCH 350/795] Introduces CameraEngine concept Adds a concept `CameraEngineConcept` to ensure that camera engine implementations provide the necessary functions with the correct signatures and `noexcept` specifications. This enables compile-time checks for valid camera engine implementations, improving code reliability and preventing runtime errors. --- include/omath/projection/camera.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index 79eb1912..76b07d82 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -26,7 +26,23 @@ namespace omath::projection }; using FieldOfView = Angle; + template + concept CameraEngineConcept = + requires(const Vector3& cam_origin, const Vector3& look_at, const ViewAnglesType& angles, + const FieldOfView& fov, const ViewPort& viewport, float znear, float zfar) { + // Presence + return types + { T::calc_look_at_angle(cam_origin, look_at) } -> std::same_as; + { T::calc_view_matrix(angles, cam_origin) } -> std::same_as; + { T::calc_projection_matrix(fov, viewport, znear, zfar) } -> std::same_as; + + // Enforce noexcept as in the trait declaration + requires noexcept(T::calc_look_at_angle(cam_origin, look_at)); + requires noexcept(T::calc_view_matrix(angles, cam_origin)); + requires noexcept(T::calc_projection_matrix(fov, viewport, znear, zfar)); + }; + template + requires CameraEngineConcept class Camera final { public: From 0450dc35474370b4e6daf89738bbec76562c5e6f Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 12 Aug 2025 09:21:34 +0300 Subject: [PATCH 351/795] Adds concept for prediction engine traits Introduces a concept `PredEngineConcept` to ensure that classes used as projectile prediction engine traits conform to a specific interface. This enforces the presence and return types of required methods and ensures that these methods are `noexcept`, improving type safety and predictability. --- .../proj_pred_engine_legacy.hpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp index 8d44255e..f253c8ea 100644 --- a/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp +++ b/include/omath/projectile_prediction/proj_pred_engine_legacy.hpp @@ -13,7 +13,32 @@ namespace omath::projectile_prediction { + template + concept PredEngineConcept = + requires(const Projectile& projectile, const Target& target, const Vector3& vec_a, + const Vector3& vec_b, + Vector3 v3, // by-value for calc_viewpoint_from_angles + float pitch, float yaw, float time, float gravity, std::optional maybe_pitch) { + // Presence + return types + { T::predict_projectile_position(projectile, pitch, yaw, time, gravity) } -> std::same_as; + { T::predict_target_position(target, time, gravity) } -> std::same_as; + { T::calc_vector_2d_distance(vec_a) } -> std::same_as; + { T::get_vector_height_coordinate(vec_b) } -> std::same_as; + { T::calc_viewpoint_from_angles(projectile, v3, maybe_pitch) } -> std::same_as; + { T::calc_direct_pitch_angle(vec_a, vec_b) } -> std::same_as; + { T::calc_direct_yaw_angle(vec_a, vec_b) } -> std::same_as; + + // Enforce noexcept as in PredEngineTrait + requires noexcept(T::predict_projectile_position(projectile, pitch, yaw, time, gravity)); + requires noexcept(T::predict_target_position(target, time, gravity)); + requires noexcept(T::calc_vector_2d_distance(vec_a)); + requires noexcept(T::get_vector_height_coordinate(vec_b)); + requires noexcept(T::calc_viewpoint_from_angles(projectile, v3, maybe_pitch)); + requires noexcept(T::calc_direct_pitch_angle(vec_a, vec_b)); + requires noexcept(T::calc_direct_yaw_angle(vec_a, vec_b)); + }; template + requires PredEngineConcept class ProjPredEngineLegacy final : public ProjPredEngineInterface { public: From c66df11754a7af8c0a31797744e709f2df220cdd Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 12 Aug 2025 09:25:40 +0300 Subject: [PATCH 352/795] Updates project version to 3.2.1 Increments the project version number in CMakeLists.txt from 3.0.4.1 to 3.2.1, indicating a release or significant changes in the project. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e84e028..3b769790 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.26) -project(omath VERSION 3.0.4.1 LANGUAGES CXX) +project(omath VERSION 3.2.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) From 687772f073563e83b3f75647195251637397ccba Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 12 Aug 2025 09:29:09 +0300 Subject: [PATCH 353/795] feat(omath): Add NDC to screen position coordinate diagram --- include/omath/projection/camera.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index 76b07d82..b02ccdb7 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -186,6 +186,18 @@ namespace omath::projection [[nodiscard]] Vector3 ndc_to_screen_position(const Vector3& ndc) const noexcept { +/* + ^ + | y + 1 | + | + | + -1 ---------0--------- 1 --> x + | + | + -1 | + v +*/ return {(ndc.x + 1.f) / 2.f * m_view_port.m_width, (1.f - ndc.y) / 2.f * m_view_port.m_height, ndc.z}; } }; From 0328fef4f75423e687f90062fb2bbea0fee788a9 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 12 Aug 2025 09:35:38 +0300 Subject: [PATCH 354/795] Fixes Vector3 conversion from ImVec2 Corrects the static function signature for creating a Vector3 from an ImVec2. It was mistakenly returning a Vector3 instead of Vector3. patch --- include/omath/vector2.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/omath/vector2.hpp b/include/omath/vector2.hpp index 8470d75e..4123068e 100644 --- a/include/omath/vector2.hpp +++ b/include/omath/vector2.hpp @@ -226,7 +226,7 @@ namespace omath return {static_cast(this->x), static_cast(this->y)}; } [[nodiscard]] - static Vector3 from_im_vec2(const ImVec2& other) noexcept + static Vector2 from_im_vec2(const ImVec2& other) noexcept { return {static_cast(other.x), static_cast(other.y)}; } From 29795e99061777711c4e7416d8f2fdf5cd74c8a6 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 12 Aug 2025 19:08:30 +0300 Subject: [PATCH 355/795] Includes missing formula headers Includes the necessary formula headers in the camera trait files for different rendering engines. This ensures that the required mathematical functions and definitions are available when using camera traits. --- include/omath/vector4.hpp | 1 - source/engines/iw_engine/traits/camera_trait.cpp | 2 +- source/engines/opengl_engine/traits/camera_trait.cpp | 2 +- source/engines/source_engine/traits/camera_trait.cpp | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/include/omath/vector4.hpp b/include/omath/vector4.hpp index 2815b454..815ffccf 100644 --- a/include/omath/vector4.hpp +++ b/include/omath/vector4.hpp @@ -198,7 +198,6 @@ namespace omath { return {static_cast(other.x), static_cast(other.y), static_cast(other.z)}; } - } #endif }; } // namespace omath diff --git a/source/engines/iw_engine/traits/camera_trait.cpp b/source/engines/iw_engine/traits/camera_trait.cpp index 087f7b09..6619751d 100644 --- a/source/engines/iw_engine/traits/camera_trait.cpp +++ b/source/engines/iw_engine/traits/camera_trait.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 8/11/2025. // #include "omath/engines/iw_engine/traits/camera_trait.hpp" - +#include "omath/engines/iw_engine/formulas.hpp" namespace omath::iw_engine { diff --git a/source/engines/opengl_engine/traits/camera_trait.cpp b/source/engines/opengl_engine/traits/camera_trait.cpp index 9fb644fc..98f886df 100644 --- a/source/engines/opengl_engine/traits/camera_trait.cpp +++ b/source/engines/opengl_engine/traits/camera_trait.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 8/11/2025. // #include "omath/engines/opengl_engine/traits/camera_trait.hpp" - +#include "omath/engines/opengl_engine/formulas.hpp" namespace omath::opengl_engine { diff --git a/source/engines/source_engine/traits/camera_trait.cpp b/source/engines/source_engine/traits/camera_trait.cpp index bf810b76..688ee3b2 100644 --- a/source/engines/source_engine/traits/camera_trait.cpp +++ b/source/engines/source_engine/traits/camera_trait.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 8/11/2025. // #include "omath/engines/source_engine/traits/camera_trait.hpp" - +#include "omath/engines/source_engine/formulas.hpp" namespace omath::source_engine { From 2d9ed4cfc8225aa8db61c9f235662f31ceab3478 Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 21 Aug 2025 00:11:37 +0300 Subject: [PATCH 356/795] Configures project IDE settings and improves gitignore Adds project-specific IDE settings to improve code style consistency and development environment. Removes the .idea directory from the .gitignore file, as these files are now intended to be part of the project for consistent IDE configuration across contributors. --- .gitignore | 3 +- .idea/codeStyles/Project.xml | 216 +++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/developer-tools.xml | 6 + .idea/editor.xml | 435 ++++++++++++++++++ .idea/inspectionProfiles/Project_Default.xml | 8 + .../inspectionProfiles/profiles_settings.xml | 7 + .idea/misc.xml | 12 + .idea/modules.xml | 8 + .idea/omath.iml | 2 + .idea/vcs.xml | 7 + .idea/workspace.xml | 257 +++++++++++ tests/general/unit_test_angles.cpp | 16 +- tests/general/unit_test_color.cpp | 12 +- 14 files changed, 978 insertions(+), 16 deletions(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/developer-tools.xml create mode 100644 .idea/editor.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/omath.iml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml diff --git a/.gitignore b/.gitignore index fe2f01ea..8d0081d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /cmake-build/ -/.idea /out *.DS_Store -/extlibs/vcpkg \ No newline at end of file +/extlibs/vcpkg diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..defa978b --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/developer-tools.xml b/.idea/developer-tools.xml new file mode 100644 index 00000000..360c55c3 --- /dev/null +++ b/.idea/developer-tools.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 00000000..fde53486 --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,435 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..e9caadd2 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..dd4c951e --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..1046494f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..58855d11 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/omath.iml b/.idea/omath.iml new file mode 100644 index 00000000..f08604bb --- /dev/null +++ b/.idea/omath.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..adc159a8 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..08ddd13e --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,257 @@ + + + + + + + { + "useNewFormat": true +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "associatedIndex": 6 +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1743838419436 + + + + + + + + + + + + + - - \ No newline at end of file From ab463e1caa89045909f0c705bd8afd1f490371ec Mon Sep 17 00:00:00 2001 From: Orange++ Date: Mon, 25 Aug 2025 16:00:36 +0300 Subject: [PATCH 360/795] Update README.md --- README.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 21ae56ac..f8fac408 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,25 @@ Oranges's Math Library (omath) is a comprehensive, open-source library aimed at Please read our [installation guide](https://github.com/orange-cpp/omath/blob/main/INSTALL.md). If this link doesn't work check out INSTALL.md file. ## ❔ Usage -Simple world to screen function +ESP example ```c++ -TEST(UnitTestProjection, IsPointOnScreen) +omath::source_engine::Camera cam{localPlayer.GetCameraOrigin(), + localPlayer.GetAimPunch(), + {1920.f, 1080.f}, + localPlayer.GetFieldOfView(), + 0.01.f, 30000.f}; + +std::optional> target_to_aim = std::nullopt; +for (auto ent: apex_sdk::EntityList::GetAllEntities()) { - const omath::projection::Camera camera({0.f, 0.f, 0.f}, {0, 0.f, 0.f} , {1920.f, 1080.f}, 110.f, 0.1f, 500.f); + const auto bottom = cam.world_to_screen(ent.GetOrigin()); + const auto top = cam.world_to_screen(ent.GetBonePosition(8) + omath::Vector3{0, 0, 10}); - const auto proj = camera.WorldToScreen({100, 0, 15}); - EXPECT_TRUE(proj.has_value()); + const auto ent_health = ent.GetHealth(); + + if (!top || !bottom || ent_health <= 0) + continue; + // esp rendering... } ``` ## Showcase From 1246c4ed07ba47ad3c132c3f937a8c9ccab88514 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Mon, 25 Aug 2025 17:26:56 +0300 Subject: [PATCH 361/795] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f8fac408..e78e420f 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,6 @@ omath::source_engine::Camera cam{localPlayer.GetCameraOrigin(), localPlayer.GetFieldOfView(), 0.01.f, 30000.f}; -std::optional> target_to_aim = std::nullopt; for (auto ent: apex_sdk::EntityList::GetAllEntities()) { const auto bottom = cam.world_to_screen(ent.GetOrigin()); From 8004c8c181c891c5fd03b40a998000bf035300c8 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 25 Aug 2025 21:30:27 +0300 Subject: [PATCH 362/795] Adds Unreal Engine math implementations Adds Unreal Engine-specific implementations for camera and projectile prediction calculations. This includes: - Defining constants and data types tailored for Unreal Engine's coordinate system and conventions. - Implementing functions for calculating forward, right, and up vectors, view matrices, and perspective projection matrices. - Providing camera trait for look-at angle calculations and projection matrix generation. - Implements projectile prediction traits and utilities. --- .../omath/engines/unreal_engine/camera.hpp | 13 ++++ .../omath/engines/unreal_engine/constants.hpp | 26 +++++++ .../omath/engines/unreal_engine/formulas.hpp | 26 +++++++ .../unreal_engine/traits/camera_trait.hpp | 24 ++++++ .../traits/pred_engine_trait.hpp | 78 +++++++++++++++++++ source/engines/unreal_engine/formulas.cpp | 49 ++++++++++++ .../unreal_engine/traits/camera_trait.cpp | 27 +++++++ tests/engines/unit_test_unreal_engine.cpp | 3 + 8 files changed, 246 insertions(+) create mode 100644 include/omath/engines/unreal_engine/camera.hpp create mode 100644 include/omath/engines/unreal_engine/constants.hpp create mode 100644 include/omath/engines/unreal_engine/formulas.hpp create mode 100644 include/omath/engines/unreal_engine/traits/camera_trait.hpp create mode 100644 include/omath/engines/unreal_engine/traits/pred_engine_trait.hpp create mode 100644 source/engines/unreal_engine/formulas.cpp create mode 100644 source/engines/unreal_engine/traits/camera_trait.cpp create mode 100644 tests/engines/unit_test_unreal_engine.cpp diff --git a/include/omath/engines/unreal_engine/camera.hpp b/include/omath/engines/unreal_engine/camera.hpp new file mode 100644 index 00000000..e9448520 --- /dev/null +++ b/include/omath/engines/unreal_engine/camera.hpp @@ -0,0 +1,13 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once +#include "omath/engines/unreal_engine/constants.hpp" +#include "omath/projection/camera.hpp" +#include "traits/camera_trait.hpp" + +namespace omath::unreal_engine +{ + using Camera = projection::Camera; +} // namespace omath::unity_engine \ No newline at end of file diff --git a/include/omath/engines/unreal_engine/constants.hpp b/include/omath/engines/unreal_engine/constants.hpp new file mode 100644 index 00000000..6e8f929c --- /dev/null +++ b/include/omath/engines/unreal_engine/constants.hpp @@ -0,0 +1,26 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once + +#include +#include +#include +#include + +namespace omath::unreal_engine +{ + constexpr Vector3 k_abs_up = {0, 0, 1}; + constexpr Vector3 k_abs_right = {0, 1, 0}; + constexpr Vector3 k_abs_forward = {1, 0, 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::unity_engine diff --git a/include/omath/engines/unreal_engine/formulas.hpp b/include/omath/engines/unreal_engine/formulas.hpp new file mode 100644 index 00000000..304698f9 --- /dev/null +++ b/include/omath/engines/unreal_engine/formulas.hpp @@ -0,0 +1,26 @@ +// +// Created by Vlad on 3/22/2025. +// + +#pragma once +#include "omath/engines/unreal_engine/constants.hpp" + +namespace omath::unreal_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; +} // namespace omath::unity_engine diff --git a/include/omath/engines/unreal_engine/traits/camera_trait.hpp b/include/omath/engines/unreal_engine/traits/camera_trait.hpp new file mode 100644 index 00000000..0f704d7e --- /dev/null +++ b/include/omath/engines/unreal_engine/traits/camera_trait.hpp @@ -0,0 +1,24 @@ +// +// Created by Vlad on 8/10/2025. +// + +#pragma once +#include "omath/engines/unreal_engine/formulas.hpp" +#include "omath/projection/camera.hpp" + +namespace omath::unreal_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::unity_engine \ No newline at end of file diff --git a/include/omath/engines/unreal_engine/traits/pred_engine_trait.hpp b/include/omath/engines/unreal_engine/traits/pred_engine_trait.hpp new file mode 100644 index 00000000..9b5e0f33 --- /dev/null +++ b/include/omath/engines/unreal_engine/traits/pred_engine_trait.hpp @@ -0,0 +1,78 @@ +// +// Created by Vlad on 8/6/2025. +// +#pragma once +#include "omath/engines/unreal_engine/formulas.hpp" +#include "omath/projectile_prediction/projectile.hpp" +#include "omath/projectile_prediction/target.hpp" +#include + +namespace omath::unreal_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.y -= (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.y -= 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.z * delta.z); + } + + [[nodiscard]] + constexpr static float get_vector_height_coordinate(const Vector3& vec) noexcept + { + return vec.y; + } + + [[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 + height, projectile.m_origin.z}; + } + // 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 distance = origin.distance_to(view_to); + const auto delta = view_to - origin; + + return angles::radians_to_degrees(std::asin(delta.y / distance)); + } + [[nodiscard]] + static float calc_direct_yaw_angle(const Vector3& origin, const Vector3& view_to) noexcept + { + const auto delta = view_to - origin; + + return angles::radians_to_degrees(std::atan2(delta.z, delta.x)); + }; + }; +} // namespace omath::unity_engine diff --git a/source/engines/unreal_engine/formulas.cpp b/source/engines/unreal_engine/formulas.cpp new file mode 100644 index 00000000..67788f0f --- /dev/null +++ b/source/engines/unreal_engine/formulas.cpp @@ -0,0 +1,49 @@ +// +// Created by Vlad on 3/22/2025. +// +#include "omath/engines/unreal_engine/formulas.hpp" + +namespace omath::unreal_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_x(angles.pitch) + * mat_rotation_axis_y(angles.yaw) + * mat_rotation_axis_z(angles.roll); + } + Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near, + const float far) noexcept + { + const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f); + + return { + {1.f / (aspect_ratio * fov_half_tan), 0, 0, 0}, + {0, 1.f / (fov_half_tan), 0, 0}, + {0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)}, + {0, 0, -1.f, 0}, + }; + } +} // namespace omath::unity_engine diff --git a/source/engines/unreal_engine/traits/camera_trait.cpp b/source/engines/unreal_engine/traits/camera_trait.cpp new file mode 100644 index 00000000..a0d5b51b --- /dev/null +++ b/source/engines/unreal_engine/traits/camera_trait.cpp @@ -0,0 +1,27 @@ +// +// Created by Vlad on 8/11/2025. +// +#include "omath/engines/unreal_engine/traits/camera_trait.hpp" + +namespace omath::unreal_engine +{ + + ViewAngles CameraTrait::calc_look_at_angle(const Vector3& cam_origin, const Vector3& look_at) noexcept + { + const auto distance = cam_origin.distance_to(look_at); + const auto delta = cam_origin - look_at; + + return {PitchAngle::from_radians(-std::asin(delta.z / distance)), + YawAngle::from_radians(std::atan2(delta.x, delta.y)), RollAngle::from_radians(0.f)}; + } + Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept + { + return unreal_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/tests/engines/unit_test_unreal_engine.cpp b/tests/engines/unit_test_unreal_engine.cpp new file mode 100644 index 00000000..12d601e4 --- /dev/null +++ b/tests/engines/unit_test_unreal_engine.cpp @@ -0,0 +1,3 @@ +// +// Created by Vlad on 8/25/2025. +// From 0de6b4589fbd16a7899c5d660f549c6e36e9c187 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 25 Aug 2025 21:31:07 +0300 Subject: [PATCH 363/795] Adds .idea/ to .gitignore Adds the standard .idea/ folder contents to the .gitignore file. This prevents project-specific IDE settings and workspace files from being tracked by Git, keeping the repository cleaner and avoiding potential conflicts between developers using different IDE configurations. --- .idea/.gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .idea/.gitignore diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml From c90497f77b704374362196568776fe27c2949919 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 25 Aug 2025 21:42:58 +0300 Subject: [PATCH 364/795] Fixes Unreal Engine view angle matrix order The view angle rotation matrix order in Unreal Engine was incorrect, leading to incorrect camera orientation. This commit fixes the order of rotation matrices to roll, pitch, yaw to correctly implement Unreal Engine camera rotations. Adds comprehensive unit tests for Unreal Engine formulas, camera and constants. Marks Unreal Engine as supported in README.md. --- .idea/codeStyles/Project.xml | 7 -- .idea/codeStyles/codeStyleConfig.xml | 1 + README.md | 3 +- source/engines/unreal_engine/formulas.cpp | 6 +- tests/engines/unit_test_unreal_engine.cpp | 105 ++++++++++++++++++++++ 5 files changed, 111 insertions(+), 11 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index defa978b..5d6ba7c9 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -182,19 +182,12 @@
-![banner](https://i.imgur.com/SM9ccP6.png) +![banner](raw.githubusercontent.com/orange-cpp/omath/refs/heads/main/.github/images/banner.png) ![GitHub License](https://img.shields.io/github/license/orange-cpp/omath) ![GitHub contributors](https://img.shields.io/github/contributors/orange-cpp/omath) From a54a537239b089b6335da8713167aa845f8f6316 Mon Sep 17 00:00:00 2001 From: Orange Date: Tue, 17 Jun 2025 21:34:25 +0300 Subject: [PATCH 312/795] fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9cd61f78..d5acf4d1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-![banner](raw.githubusercontent.com/orange-cpp/omath/refs/heads/main/.github/images/banner.png) +![banner](https://github.com/orange-cpp/omath/blob/main/.github/images/banner.png?raw=true) ![GitHub License](https://img.shields.io/github/license/orange-cpp/omath) ![GitHub contributors](https://img.shields.io/github/contributors/orange-cpp/omath) From 7e4a6134bfb3a705984ac600ee74aeef731a8a4b Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 23 Jun 2025 06:14:06 +0300 Subject: [PATCH 313/795] added new method --- include/omath/color.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/omath/color.hpp b/include/omath/color.hpp index 3513e74a..41720621 100644 --- a/include/omath/color.hpp +++ b/include/omath/color.hpp @@ -166,5 +166,12 @@ namespace omath { return {0.f, 0.f, 1.f, 1.f}; } +#ifdef OMATH_IMGUI_INTEGRATION + [[nodiscard]] + ImColor to_im_color() const noexcept + { + return {to_im_vec4()}; + } +#endif }; } // namespace omath From 65541fa2c7e3b052fbf007174645c80344a0f18d Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 6 Jul 2025 11:07:15 +0300 Subject: [PATCH 314/795] Renames library target to project name Updates the CMakeLists.txt to use the project name as the library target name instead of hardcoding "omath". This change ensures consistency and avoids potential conflicts when integrating the library into other projects. It also aligns the target naming with CMake best practices. --- CMakeLists.txt | 55 ++++++++++++++++++++++---------------------- tests/CMakeLists.txt | 6 ++--- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a45a815..909b862c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,20 +20,21 @@ file(GLOB_RECURSE OMATH_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/i if (OMATH_BUILD_AS_SHARED_LIBRARY) - add_library(omath SHARED ${OMATH_SOURCES} ${OMATH_HEADERS}) + add_library(${PROJECT_NAME} SHARED ${OMATH_SOURCES} ${OMATH_HEADERS}) else () - add_library(omath STATIC ${OMATH_SOURCES} ${OMATH_HEADERS}) + add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS}) endif () -message(STATUS "Building on ${CMAKE_HOST_SYSTEM_NAME}") -add_library(omath::omath ALIAS omath) +message(STATUS "[OMATH]: Building on ${CMAKE_HOST_SYSTEM_NAME}") + +add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) if (OMATH_IMGUI_INTEGRATION) - target_compile_definitions(omath PUBLIC OMATH_IMGUI_INTEGRATION) + target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_IMGUI_INTEGRATION) # IMGUI is being linked as submodule if (TARGET imgui) - target_link_libraries(omath PUBLIC imgui) + target_link_libraries(${PROJECT_NAME} PUBLIC imgui) install(TARGETS imgui EXPORT omathTargets ARCHIVE DESTINATION lib @@ -42,42 +43,42 @@ if (OMATH_IMGUI_INTEGRATION) else () # Assume that IMGUI linked via VCPKG. find_package(imgui CONFIG REQUIRED) - target_link_libraries(omath PUBLIC imgui::imgui) + target_link_libraries(${PROJECT_NAME} PUBLIC imgui::imgui) endif () endif () if (OMATH_USE_AVX2) - target_compile_definitions(omath PUBLIC OMATH_USE_AVX2) + target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_USE_AVX2) endif () if (OMATH_SUPRESS_SAFETY_CHECKS) - target_compile_definitions(omath PUBLIC OMATH_SUPRESS_SAFETY_CHECKS) + target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_SUPRESS_SAFETY_CHECKS) endif () -set_target_properties(omath PROPERTIES +set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" CXX_STANDARD 23 CXX_STANDARD_REQUIRED ON) if (OMATH_USE_UNITY_BUILD) - set_target_properties(omath PROPERTIES + set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON UNITY_BUILD_BATCH_SIZE 20) endif () if (OMATH_STATIC_MSVC_RUNTIME_LIBRARY) - set_target_properties(omath PROPERTIES + set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" ) endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - target_compile_options(omath PRIVATE -mavx2 -mfma) + target_compile_options(${PROJECT_NAME} PRIVATE -mavx2 -mfma) endif () -target_compile_features(omath PUBLIC cxx_std_23) +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) if (OMATH_BUILD_TESTS) @@ -90,12 +91,12 @@ if (OMATH_BUILD_EXAMPLES) endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND OMATH_THREAT_WARNING_AS_ERROR) - target_compile_options(omath PRIVATE /W4 /WX) + target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX) elseif (OMATH_THREAT_WARNING_AS_ERROR) - target_compile_options(omath PRIVATE -Wall -Wextra -Wpedantic -Werror) + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror) endif () -target_include_directories(omath +target_include_directories(${PROJECT_NAME} PUBLIC $ # Use this path when building the project $ # Use this path when the project is installed @@ -105,21 +106,21 @@ target_include_directories(omath # Installation rules # Install the library -install(TARGETS omath - EXPORT omathTargets - ARCHIVE DESTINATION lib COMPONENT omath # For static libraries - LIBRARY DESTINATION lib COMPONENT omath # For shared libraries - RUNTIME DESTINATION bin COMPONENT omath # For executables (on Windows) +install(TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}Targets + ARCHIVE DESTINATION lib COMPONENT ${PROJECT_NAME} # For static libraries + LIBRARY DESTINATION lib COMPONENT ${PROJECT_NAME} # For shared libraries + RUNTIME DESTINATION bin COMPONENT ${PROJECT_NAME} # For executables (on Windows) ) # Install headers as part of omath_component -install(DIRECTORY include/ DESTINATION include COMPONENT omath) +install(DIRECTORY include/ DESTINATION include COMPONENT ${PROJECT_NAME}) # Export omath target for CMake find_package support, also under omath_component install(EXPORT omathTargets FILE omathTargets.cmake - NAMESPACE omath:: - DESTINATION lib/cmake/omath COMPONENT omath + NAMESPACE ${PROJECT_NAME}:: + DESTINATION lib/cmake/omath COMPONENT ${PROJECT_NAME} ) @@ -134,12 +135,12 @@ write_basic_package_version_file( configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/omathConfig.cmake.in" # Path to the .in file "${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" # Output path for the generated file - INSTALL_DESTINATION lib/cmake/omath + INSTALL_DESTINATION lib/cmake/${PROJECT_NAME} ) # Install the generated config files install(FILES "${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake" - DESTINATION lib/cmake/omath + DESTINATION lib/cmake/${PROJECT_NAME} ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5660336b..061a516a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,7 +5,7 @@ project(unit_tests) include(GoogleTest) file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") -add_executable(unit_tests ${UNIT_TESTS_SOURCES}) +add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES}) set_target_properties(unit_tests PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" @@ -17,6 +17,6 @@ set_target_properties(unit_tests PROPERTIES CXX_STANDARD_REQUIRED ON) -target_link_libraries(unit_tests PRIVATE gtest gtest_main omath::omath) +target_link_libraries(${PROJECT_NAME} PRIVATE gtest gtest_main omath::omath) -gtest_discover_tests(unit_tests) \ No newline at end of file +gtest_discover_tests(${PROJECT_NAME}) \ No newline at end of file From 66258f0f6df387301e66ff21f6011a6631663523 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 6 Jul 2025 11:14:46 +0300 Subject: [PATCH 315/795] Updates CMake export target and namespace. Updates the CMake export target and namespace to use the project name, improving consistency and avoiding naming conflicts. Adds a simple diagram to the triangle header file. --- CMakeLists.txt | 6 +++--- include/omath/triangle.hpp | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 909b862c..1d5891f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,10 +117,10 @@ install(TARGETS ${PROJECT_NAME} install(DIRECTORY include/ DESTINATION include COMPONENT ${PROJECT_NAME}) # Export omath target for CMake find_package support, also under omath_component -install(EXPORT omathTargets - FILE omathTargets.cmake +install(EXPORT ${PROJECT_NAME}Targets + FILE ${PROJECT_NAME}Targets.cmake NAMESPACE ${PROJECT_NAME}:: - DESTINATION lib/cmake/omath COMPONENT ${PROJECT_NAME} + DESTINATION lib/cmake/${PROJECT_NAME} COMPONENT ${PROJECT_NAME} ) diff --git a/include/omath/triangle.hpp b/include/omath/triangle.hpp index b5e9822d..e15151b0 100644 --- a/include/omath/triangle.hpp +++ b/include/omath/triangle.hpp @@ -7,11 +7,12 @@ namespace omath { /* + v1 |\ | \ a | \ hypot | \ - ----- + v2 ----- v3 b */ From 8241d9c355036c2089d736825d287b6bc577fe1e Mon Sep 17 00:00:00 2001 From: Saikari Date: Mon, 7 Jul 2025 04:53:56 +0300 Subject: [PATCH 316/795] Update omathConfig.cmake.in --- cmake/omathConfig.cmake.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/omathConfig.cmake.in b/cmake/omathConfig.cmake.in index 6c0631d6..1e3c774b 100644 --- a/cmake/omathConfig.cmake.in +++ b/cmake/omathConfig.cmake.in @@ -2,6 +2,10 @@ include(CMakeFindDependencyMacro) +if (@OMATH_IMGUI_INTEGRATION@) + find_dependency(imgui CONFIG) +endif() + # Load the targets for the omath library include("${CMAKE_CURRENT_LIST_DIR}/omathTargets.cmake") check_required_components(omath) From 06d9b4c91053184f3b9c1534037ce83136319e12 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 7 Jul 2025 08:02:13 +0300 Subject: [PATCH 317/795] fixed naming in tests --- tests/engines/unit_test_iw_engine.cpp | 18 ++++++------ tests/engines/unit_test_open_gl.cpp | 18 ++++++------ tests/engines/unit_test_source_engine.cpp | 20 ++++++------- tests/engines/unit_test_unity_engine.cpp | 20 ++++++------- tests/general/unit_test_color.cpp | 18 ++++++------ tests/general/unit_test_line_trace.cpp | 14 ++++----- tests/general/unit_test_mat.cpp | 36 +++++++++++------------ 7 files changed, 72 insertions(+), 72 deletions(-) diff --git a/tests/engines/unit_test_iw_engine.cpp b/tests/engines/unit_test_iw_engine.cpp index 76133f5f..199fa9b2 100644 --- a/tests/engines/unit_test_iw_engine.cpp +++ b/tests/engines/unit_test_iw_engine.cpp @@ -7,27 +7,27 @@ #include -TEST(UnitTestIwEngine, ForwardVector) +TEST(unit_test_iw_engine, ForwardVector) { const auto forward = omath::iw_engine::forward_vector({}); EXPECT_EQ(forward, omath::iw_engine::k_abs_forward); } -TEST(UnitTestIwEngine, RightVector) +TEST(unit_test_iw_engine, RightVector) { const auto right = omath::iw_engine::right_vector({}); EXPECT_EQ(right, omath::iw_engine::k_abs_right); } -TEST(UnitTestIwEngine, UpVector) +TEST(unit_test_iw_engine, UpVector) { const auto up = omath::iw_engine::up_vector({}); EXPECT_EQ(up, omath::iw_engine::k_abs_up); } -TEST(UnitTestIwEngine, ForwardVectorRotationYaw) +TEST(unit_test_iw_engine, ForwardVectorRotationYaw) { omath::iw_engine::ViewAngles angles; @@ -39,7 +39,7 @@ TEST(UnitTestIwEngine, ForwardVectorRotationYaw) EXPECT_NEAR(forward.z, omath::iw_engine::k_abs_right.z, 0.00001f); } -TEST(UnitTestIwEngine, ForwardVectorRotationPitch) +TEST(unit_test_iw_engine, ForwardVectorRotationPitch) { omath::iw_engine::ViewAngles angles; @@ -51,7 +51,7 @@ TEST(UnitTestIwEngine, ForwardVectorRotationPitch) EXPECT_NEAR(forward.z, omath::iw_engine::k_abs_up.z, 0.01f); } -TEST(UnitTestIwEngine, ForwardVectorRotationRoll) +TEST(unit_test_iw_engine, ForwardVectorRotationRoll) { omath::iw_engine::ViewAngles angles; @@ -63,7 +63,7 @@ TEST(UnitTestIwEngine, ForwardVectorRotationRoll) EXPECT_NEAR(forward.z, omath::iw_engine::k_abs_right.z, 0.00001f); } -TEST(UnitTestIwEngine, ProjectTargetMovedFromCamera) +TEST(unit_test_iw_engine, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); const auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); @@ -83,7 +83,7 @@ TEST(UnitTestIwEngine, ProjectTargetMovedFromCamera) } } -TEST(UnitTestIwEngine, CameraSetAndGetFov) +TEST(unit_test_iw_engine, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); @@ -94,7 +94,7 @@ TEST(UnitTestIwEngine, CameraSetAndGetFov) EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } -TEST(UnitTestIwEngine, CameraSetAndGetOrigin) +TEST(unit_test_iw_engine, CameraSetAndGetOrigin) { auto cam = omath::iw_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); diff --git a/tests/engines/unit_test_open_gl.cpp b/tests/engines/unit_test_open_gl.cpp index 1e9ddf8e..89506e61 100644 --- a/tests/engines/unit_test_open_gl.cpp +++ b/tests/engines/unit_test_open_gl.cpp @@ -7,25 +7,25 @@ #include -TEST(UnitTestOpenGL, ForwardVector) +TEST(unit_test_opengl, ForwardVector) { const auto forward = omath::opengl_engine::forward_vector({}); EXPECT_EQ(forward, omath::opengl_engine::k_abs_forward); } -TEST(UnitTestOpenGL, RightVector) +TEST(unit_test_opengl, RightVector) { const auto right = omath::opengl_engine::right_vector({}); EXPECT_EQ(right, omath::opengl_engine::k_abs_right); } -TEST(UnitTestOpenGL, UpVector) +TEST(unit_test_opengl, UpVector) { const auto up = omath::opengl_engine::up_vector({}); EXPECT_EQ(up, omath::opengl_engine::k_abs_up); } -TEST(UnitTestOpenGL, ForwardVectorRotationYaw) +TEST(unit_test_opengl, ForwardVectorRotationYaw) { omath::opengl_engine::ViewAngles angles; @@ -39,7 +39,7 @@ TEST(UnitTestOpenGL, ForwardVectorRotationYaw) -TEST(UnitTestOpenGL, ForwardVectorRotationPitch) +TEST(unit_test_opengl, ForwardVectorRotationPitch) { omath::opengl_engine::ViewAngles angles; @@ -51,7 +51,7 @@ TEST(UnitTestOpenGL, ForwardVectorRotationPitch) EXPECT_NEAR(forward.z, omath::opengl_engine::k_abs_up.z, 0.00001f); } -TEST(UnitTestOpenGL, ForwardVectorRotationRoll) +TEST(unit_test_opengl, ForwardVectorRotationRoll) { omath::opengl_engine::ViewAngles angles; @@ -63,7 +63,7 @@ TEST(UnitTestOpenGL, ForwardVectorRotationRoll) EXPECT_NEAR(forward.z, omath::opengl_engine::k_abs_right.z, 0.00001f); } -TEST(UnitTestOpenGL, ProjectTargetMovedFromCamera) +TEST(unit_test_opengl, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); const auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); @@ -83,7 +83,7 @@ TEST(UnitTestOpenGL, ProjectTargetMovedFromCamera) } } -TEST(UnitTestOpenGL, CameraSetAndGetFov) +TEST(unit_test_opengl, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); @@ -94,7 +94,7 @@ TEST(UnitTestOpenGL, CameraSetAndGetFov) EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } -TEST(UnitTestOpenGL, CameraSetAndGetOrigin) +TEST(unit_test_opengl, CameraSetAndGetOrigin) { auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); diff --git a/tests/engines/unit_test_source_engine.cpp b/tests/engines/unit_test_source_engine.cpp index a0749f32..82dcc918 100644 --- a/tests/engines/unit_test_source_engine.cpp +++ b/tests/engines/unit_test_source_engine.cpp @@ -7,27 +7,27 @@ #include -TEST(UnitTestSourceEngine, ForwardVector) +TEST(unit_test_source_engine, ForwardVector) { const auto forward = omath::source_engine::forward_vector({}); EXPECT_EQ(forward, omath::source_engine::k_abs_forward); } -TEST(UnitTestSourceEngine, RightVector) +TEST(unit_test_source_engine, RightVector) { const auto right = omath::source_engine::right_vector({}); EXPECT_EQ(right, omath::source_engine::k_abs_right); } -TEST(UnitTestSourceEngine, UpVector) +TEST(unit_test_source_engine, UpVector) { const auto up = omath::source_engine::up_vector({}); EXPECT_EQ(up, omath::source_engine::k_abs_up); } -TEST(UnitTestSourceEngine, ForwardVectorRotationYaw) +TEST(unit_test_source_engine, ForwardVectorRotationYaw) { omath::source_engine::ViewAngles angles; @@ -39,7 +39,7 @@ TEST(UnitTestSourceEngine, ForwardVectorRotationYaw) EXPECT_NEAR(forward.z, omath::source_engine::k_abs_right.z, 0.00001f); } -TEST(UnitTestSourceEngine, ForwardVectorRotationPitch) +TEST(unit_test_source_engine, ForwardVectorRotationPitch) { omath::source_engine::ViewAngles angles; @@ -51,7 +51,7 @@ TEST(UnitTestSourceEngine, ForwardVectorRotationPitch) EXPECT_NEAR(forward.z, omath::source_engine::k_abs_up.z, 0.01f); } -TEST(UnitTestSourceEngine, ForwardVectorRotationRoll) +TEST(unit_test_source_engine, ForwardVectorRotationRoll) { omath::source_engine::ViewAngles angles; @@ -63,7 +63,7 @@ TEST(UnitTestSourceEngine, ForwardVectorRotationRoll) EXPECT_NEAR(forward.z, omath::source_engine::k_abs_right.z, 0.00001f); } -TEST(UnitTestSourceEngine, ProjectTargetMovedFromCamera) +TEST(unit_test_source_engine, ProjectTargetMovedFromCamera) { constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); const auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); @@ -83,7 +83,7 @@ TEST(UnitTestSourceEngine, ProjectTargetMovedFromCamera) } } -TEST(UnitTestSourceEngine, ProjectTargetMovedUp) +TEST(unit_test_source_engine, ProjectTargetMovedUp) { constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); const auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); @@ -103,7 +103,7 @@ TEST(UnitTestSourceEngine, ProjectTargetMovedUp) } } -TEST(UnitTestSourceEngine, CameraSetAndGetFov) +TEST(unit_test_source_engine, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); @@ -114,7 +114,7 @@ TEST(UnitTestSourceEngine, CameraSetAndGetFov) EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } -TEST(UnitTestSourceEngine, CameraSetAndGetOrigin) +TEST(unit_test_source_engine, CameraSetAndGetOrigin) { auto cam = omath::source_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); diff --git a/tests/engines/unit_test_unity_engine.cpp b/tests/engines/unit_test_unity_engine.cpp index 932bed6a..2baac708 100644 --- a/tests/engines/unit_test_unity_engine.cpp +++ b/tests/engines/unit_test_unity_engine.cpp @@ -7,14 +7,14 @@ #include #include -TEST(UnitTestUnityEngine, ForwardVector) +TEST(unit_test_unity_engine, ForwardVector) { const auto forward = omath::unity_engine::forward_vector({}); EXPECT_EQ(forward, omath::unity_engine::k_abs_forward); } -TEST(UnitTestUnityEngine, ForwardVectorRotationYaw) +TEST(unit_test_unity_engine, ForwardVectorRotationYaw) { omath::unity_engine::ViewAngles angles; @@ -26,7 +26,7 @@ TEST(UnitTestUnityEngine, ForwardVectorRotationYaw) EXPECT_NEAR(forward.z, omath::unity_engine::k_abs_right.z, 0.00001f); } -TEST(UnitTestUnityEngine, ForwardVectorRotationPitch) +TEST(unit_test_unity_engine, ForwardVectorRotationPitch) { omath::unity_engine::ViewAngles angles; @@ -38,7 +38,7 @@ TEST(UnitTestUnityEngine, ForwardVectorRotationPitch) EXPECT_NEAR(forward.z, omath::unity_engine::k_abs_up.z, 0.00001f); } -TEST(UnitTestUnityEngine, ForwardVectorRotationRoll) +TEST(unit_test_unity_engine, ForwardVectorRotationRoll) { omath::unity_engine::ViewAngles angles; @@ -50,20 +50,20 @@ TEST(UnitTestUnityEngine, ForwardVectorRotationRoll) EXPECT_NEAR(forward.z, omath::unity_engine::k_abs_right.z, 0.00001f); } -TEST(UnitTestUnityEngine, RightVector) +TEST(unit_test_unity_engine, RightVector) { const auto right = omath::unity_engine::right_vector({}); EXPECT_EQ(right, omath::unity_engine::k_abs_right); } -TEST(UnitTestUnityEngine, UpVector) +TEST(unit_test_unity_engine, UpVector) { const auto up = omath::unity_engine::up_vector({}); EXPECT_EQ(up, omath::unity_engine::k_abs_up); } -TEST(UnitTestUnityEngine, ProjectTargetMovedFromCamera) +TEST(unit_test_unity_engine, ProjectTargetMovedFromCamera) { 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.01f, 1000.f); @@ -82,7 +82,7 @@ TEST(UnitTestUnityEngine, ProjectTargetMovedFromCamera) EXPECT_NEAR(projected->y, 360, 0.00001f); } } -TEST(UnitTestUnityEngine, Project) +TEST(unit_test_unity_engine, Project) { constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f); @@ -91,7 +91,7 @@ TEST(UnitTestUnityEngine, Project) std::println("{} {}", proj->x, proj->y); } -TEST(UnitTestUnityEngine, CameraSetAndGetFov) +TEST(unit_test_unity_engine, CameraSetAndGetFov) { constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f); auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f); @@ -102,7 +102,7 @@ TEST(UnitTestUnityEngine, CameraSetAndGetFov) EXPECT_EQ(cam.get_field_of_view().as_degrees(), 50.f); } -TEST(UnitTestUnityEngine, CameraSetAndGetOrigin) +TEST(unit_test_unity_engine, CameraSetAndGetOrigin) { auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, {}, 0.01f, 1000.f); diff --git a/tests/general/unit_test_color.cpp b/tests/general/unit_test_color.cpp index 5bfb39a9..4498fff2 100644 --- a/tests/general/unit_test_color.cpp +++ b/tests/general/unit_test_color.cpp @@ -7,7 +7,7 @@ using namespace omath; -class UnitTestColor : public ::testing::Test +class unit_test_color : public ::testing::Test { protected: Color color1; @@ -21,7 +21,7 @@ class UnitTestColor : public ::testing::Test }; // Test constructors -TEST_F(UnitTestColor, Constructor_Float) +TEST_F(unit_test_color, Constructor_Float) { constexpr Color color(0.5f, 0.5f, 0.5f, 1.0f); EXPECT_FLOAT_EQ(color.x, 0.5f); @@ -30,7 +30,7 @@ TEST_F(UnitTestColor, Constructor_Float) EXPECT_FLOAT_EQ(color.w, 1.0f); } -TEST_F(UnitTestColor, Constructor_Vector4) +TEST_F(unit_test_color, Constructor_Vector4) { constexpr omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f); Color color(vec); @@ -41,7 +41,7 @@ TEST_F(UnitTestColor, Constructor_Vector4) } // Test static methods for color creation -TEST_F(UnitTestColor, FromRGBA) +TEST_F(unit_test_color, FromRGBA) { constexpr Color color = Color::from_rgba(128, 64, 32, 255); EXPECT_FLOAT_EQ(color.x, 128.0f / 255.0f); @@ -50,7 +50,7 @@ TEST_F(UnitTestColor, FromRGBA) EXPECT_FLOAT_EQ(color.w, 1.0f); } -TEST_F(UnitTestColor, FromHSV) +TEST_F(unit_test_color, FromHSV) { constexpr Color color = Color::from_hsv(0.0f, 1.0f, 1.0f); // Red in HSV EXPECT_FLOAT_EQ(color.x, 1.0f); @@ -60,7 +60,7 @@ TEST_F(UnitTestColor, FromHSV) } // Test HSV conversion -TEST_F(UnitTestColor, ToHSV) +TEST_F(unit_test_color, ToHSV) { Hsv hsv = color1.to_hsv(); // Red color EXPECT_FLOAT_EQ(hsv.hue, 0.0f); @@ -69,7 +69,7 @@ TEST_F(UnitTestColor, ToHSV) } // Test color blending -TEST_F(UnitTestColor, Blend) +TEST_F(unit_test_color, Blend) { Color blended = color1.blend(color2, 0.5f); EXPECT_FLOAT_EQ(blended.x, 0.5f); @@ -79,7 +79,7 @@ TEST_F(UnitTestColor, Blend) } // Test predefined colors -TEST_F(UnitTestColor, PredefinedColors) +TEST_F(unit_test_color, PredefinedColors) { constexpr Color red = Color::red(); constexpr Color green = Color::green(); @@ -102,7 +102,7 @@ TEST_F(UnitTestColor, PredefinedColors) } // Test non-member function: Blend for Vector3 -TEST_F(UnitTestColor, BlendVector3) +TEST_F(unit_test_color, BlendVector3) { constexpr Color v1(1.0f, 0.0f, 0.0f, 1.f); // Red constexpr Color v2(0.0f, 1.0f, 0.0f, 1.f); // Green diff --git a/tests/general/unit_test_line_trace.cpp b/tests/general/unit_test_line_trace.cpp index e47d2b76..30a0e222 100644 --- a/tests/general/unit_test_line_trace.cpp +++ b/tests/general/unit_test_line_trace.cpp @@ -31,10 +31,10 @@ namespace // ----------------------------------------------------------------------------- // Fixture with one canonical right‑angled triangle in the XY plane. // ----------------------------------------------------------------------------- - class LineTracerFixture : public ::testing::Test + class lline_tracer_fixture : public ::testing::Test { protected: - LineTracerFixture() : + lline_tracer_fixture() : triangle({0.f, 0.f, 0.f}, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}) { } @@ -51,7 +51,7 @@ namespace bool expected_clear; // true => segment does NOT hit the triangle }; - class CanTraceLineParam : public LineTracerFixture, + class CanTraceLineParam : public lline_tracer_fixture, public ::testing::WithParamInterface { }; @@ -79,7 +79,7 @@ namespace // ----------------------------------------------------------------------------- // Validate that the reported hit point is correct for a genuine intersection. // ----------------------------------------------------------------------------- - TEST_F(LineTracerFixture, HitPointCorrect) + TEST_F(lline_tracer_fixture, HitPointCorrect) { constexpr Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}}; constexpr Vec3 expected{0.3f, 0.3f, 0.f}; @@ -92,7 +92,7 @@ namespace // ----------------------------------------------------------------------------- // Triangle far beyond the ray should not block. // ----------------------------------------------------------------------------- - TEST_F(LineTracerFixture, DistantTriangleClear) + TEST_F(lline_tracer_fixture, DistantTriangleClear) { constexpr Ray short_ray{{0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}}; constexpr Triangle distant{{1000.f, 1000.f, 1000.f}, @@ -102,7 +102,7 @@ namespace EXPECT_TRUE(LineTracer::can_trace_line(short_ray, distant)); } - TEST(LineTracerTraceRayEdge, CantHit) + TEST(unit_test_unity_engine, CantHit) { constexpr omath::Triangle> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}}; @@ -110,7 +110,7 @@ namespace EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, triangle)); } - TEST(LineTracerTraceRayEdge, CanHit) + TEST(unit_test_unity_engine, CanHit) { constexpr omath::Triangle> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}}; diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index c6162c73..1d6869af 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -5,7 +5,7 @@ using namespace omath; -class UnitTestMat : public ::testing::Test +class unit_test_mat : public ::testing::Test { protected: Mat<2, 2> m1; @@ -19,7 +19,7 @@ class UnitTestMat : public ::testing::Test }; // Test constructors -TEST_F(UnitTestMat, Constructor_Default) +TEST_F(unit_test_mat, Constructor_Default) { Mat<3, 3> m; EXPECT_EQ(m.row_count(), 3); @@ -29,7 +29,7 @@ TEST_F(UnitTestMat, Constructor_Default) EXPECT_FLOAT_EQ(m.at(i, j), 0.0f); } -TEST_F(UnitTestMat, Constructor_InitializerList) +TEST_F(unit_test_mat, Constructor_InitializerList) { constexpr Mat<2, 2> m{{1.0f, 2.0f}, {3.0f, 4.0f}}; EXPECT_EQ(m.row_count(), 2); @@ -40,7 +40,7 @@ TEST_F(UnitTestMat, Constructor_InitializerList) EXPECT_FLOAT_EQ(m.at(1, 1), 4.0f); } -TEST_F(UnitTestMat, Operator_SquareBrackets) +TEST_F(unit_test_mat, Operator_SquareBrackets) { EXPECT_EQ((m2[0, 0]), 1.0f); EXPECT_EQ((m2[0, 1]), 2.0f); @@ -48,7 +48,7 @@ TEST_F(UnitTestMat, Operator_SquareBrackets) EXPECT_EQ((m2[1, 1]), 4.0f); } -TEST_F(UnitTestMat, Constructor_Copy) +TEST_F(unit_test_mat, Constructor_Copy) { Mat<2, 2> m3 = m2; EXPECT_EQ(m3.row_count(), m2.row_count()); @@ -57,7 +57,7 @@ TEST_F(UnitTestMat, Constructor_Copy) EXPECT_FLOAT_EQ(m3.at(1, 1), m2.at(1, 1)); } -TEST_F(UnitTestMat, Constructor_Move) +TEST_F(unit_test_mat, Constructor_Move) { Mat<2, 2> m3 = std::move(m2); EXPECT_EQ(m3.row_count(), 2); @@ -68,7 +68,7 @@ TEST_F(UnitTestMat, Constructor_Move) } // Test matrix operations -TEST_F(UnitTestMat, Operator_Multiplication_Matrix) +TEST_F(unit_test_mat, Operator_Multiplication_Matrix) { Mat<2, 2> m3 = m2 * m2; EXPECT_EQ(m3.row_count(), 2); @@ -79,14 +79,14 @@ TEST_F(UnitTestMat, Operator_Multiplication_Matrix) EXPECT_FLOAT_EQ(m3.at(1, 1), 22.0f); } -TEST_F(UnitTestMat, Operator_Multiplication_Scalar) +TEST_F(unit_test_mat, Operator_Multiplication_Scalar) { Mat<2, 2> m3 = m2 * 2.0f; EXPECT_FLOAT_EQ(m3.at(0, 0), 2.0f); EXPECT_FLOAT_EQ(m3.at(1, 1), 8.0f); } -TEST_F(UnitTestMat, Operator_Division_Scalar) +TEST_F(unit_test_mat, Operator_Division_Scalar) { Mat<2, 2> m3 = m2 / 2.0f; EXPECT_FLOAT_EQ(m3.at(0, 0), 0.5f); @@ -94,7 +94,7 @@ TEST_F(UnitTestMat, Operator_Division_Scalar) } // Test matrix functions -TEST_F(UnitTestMat, Transpose) +TEST_F(unit_test_mat, Transpose) { Mat<2, 2> m3 = m2.transposed(); EXPECT_FLOAT_EQ(m3.at(0, 0), m2.at(0, 0)); @@ -103,19 +103,19 @@ TEST_F(UnitTestMat, Transpose) EXPECT_FLOAT_EQ(m3.at(1, 1), m2.at(1, 1)); } -TEST_F(UnitTestMat, Determinant) +TEST_F(unit_test_mat, Determinant) { const float det = m2.determinant(); EXPECT_FLOAT_EQ(det, -2.0f); } -TEST_F(UnitTestMat, Sum) +TEST_F(unit_test_mat, Sum) { const float sum = m2.sum(); EXPECT_FLOAT_EQ(sum, 10.0f); } -TEST_F(UnitTestMat, Clear) +TEST_F(unit_test_mat, Clear) { m2.clear(); for (size_t i = 0; i < m2.row_count(); ++i) @@ -123,7 +123,7 @@ TEST_F(UnitTestMat, Clear) EXPECT_FLOAT_EQ(m2.at(i, j), 0.0f); } -TEST_F(UnitTestMat, ToString) +TEST_F(unit_test_mat, ToString) { const std::string str = m2.to_string(); EXPECT_FALSE(str.empty()); @@ -131,7 +131,7 @@ TEST_F(UnitTestMat, ToString) } // Test assignment operators -TEST_F(UnitTestMat, AssignmentOperator_Copy) +TEST_F(unit_test_mat, AssignmentOperator_Copy) { Mat<2, 2> m3; m3 = m2; @@ -140,7 +140,7 @@ TEST_F(UnitTestMat, AssignmentOperator_Copy) EXPECT_FLOAT_EQ(m3.at(0, 0), m2.at(0, 0)); } -TEST_F(UnitTestMat, AssignmentOperator_Move) +TEST_F(unit_test_mat, AssignmentOperator_Move) { Mat<2, 2> m3; m3 = std::move(m2); @@ -152,7 +152,7 @@ TEST_F(UnitTestMat, AssignmentOperator_Move) } // Test static methods -TEST_F(UnitTestMat, StaticMethod_ToScreenMat) +TEST_F(unit_test_mat, StaticMethod_ToScreenMat) { Mat<4, 4> screenMat = Mat<4, 4>::to_screen_mat(800.0f, 600.0f); EXPECT_FLOAT_EQ(screenMat.at(0, 0), 400.0f); @@ -164,7 +164,7 @@ TEST_F(UnitTestMat, StaticMethod_ToScreenMat) // Test exception handling in At() method -TEST_F(UnitTestMat, Method_At_OutOfRange) +TEST_F(unit_test_mat, Method_At_OutOfRange) { #if !defined(NDEBUG) && defined(OMATH_SUPRESS_SAFETY_CHECKS) EXPECT_THROW(std::ignore = m2.At(2, 0), std::out_of_range); From 4be2986681552bb94235d1c2b288ff8fb2096448 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 7 Jul 2025 08:18:00 +0300 Subject: [PATCH 318/795] Fixes potential compile error Corrects the usage of `At` method within the unit tests to `at` to resolve a potential compile error due to incorrect case sensitivity. --- tests/general/unit_test_mat.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/general/unit_test_mat.cpp b/tests/general/unit_test_mat.cpp index 1d6869af..de2c6988 100644 --- a/tests/general/unit_test_mat.cpp +++ b/tests/general/unit_test_mat.cpp @@ -167,8 +167,8 @@ TEST_F(unit_test_mat, StaticMethod_ToScreenMat) TEST_F(unit_test_mat, Method_At_OutOfRange) { #if !defined(NDEBUG) && defined(OMATH_SUPRESS_SAFETY_CHECKS) - EXPECT_THROW(std::ignore = m2.At(2, 0), std::out_of_range); - EXPECT_THROW(std::ignore = m2.At(0, 2), std::out_of_range); + EXPECT_THROW(std::ignore = m2.at(2, 0), std::out_of_range); + EXPECT_THROW(std::ignore = m2.at(0, 2), std::out_of_range); #endif } From 647cf02a383efe093affba366f3159c0439c0c54 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 7 Jul 2025 08:28:29 +0300 Subject: [PATCH 319/795] Renames test fixture for clarity. Updates the name of the test fixture from "lline_tracer_fixture" to "line_tracer_fixture" for improved readability and consistency in the test code. --- tests/general/unit_test_line_trace.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/general/unit_test_line_trace.cpp b/tests/general/unit_test_line_trace.cpp index 30a0e222..3f0e1d67 100644 --- a/tests/general/unit_test_line_trace.cpp +++ b/tests/general/unit_test_line_trace.cpp @@ -31,10 +31,10 @@ namespace // ----------------------------------------------------------------------------- // Fixture with one canonical right‑angled triangle in the XY plane. // ----------------------------------------------------------------------------- - class lline_tracer_fixture : public ::testing::Test + class line_tracer_fixture : public ::testing::Test { protected: - lline_tracer_fixture() : + line_tracer_fixture() : triangle({0.f, 0.f, 0.f}, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}) { } @@ -51,7 +51,7 @@ namespace bool expected_clear; // true => segment does NOT hit the triangle }; - class CanTraceLineParam : public lline_tracer_fixture, + class CanTraceLineParam : public line_tracer_fixture, public ::testing::WithParamInterface { }; @@ -79,7 +79,7 @@ namespace // ----------------------------------------------------------------------------- // Validate that the reported hit point is correct for a genuine intersection. // ----------------------------------------------------------------------------- - TEST_F(lline_tracer_fixture, HitPointCorrect) + TEST_F(line_tracer_fixture, HitPointCorrect) { constexpr Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}}; constexpr Vec3 expected{0.3f, 0.3f, 0.f}; @@ -92,7 +92,7 @@ namespace // ----------------------------------------------------------------------------- // Triangle far beyond the ray should not block. // ----------------------------------------------------------------------------- - TEST_F(lline_tracer_fixture, DistantTriangleClear) + TEST_F(line_tracer_fixture, DistantTriangleClear) { constexpr Ray short_ray{{0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}}; constexpr Triangle distant{{1000.f, 1000.f, 1000.f}, From 278ffba0ff1ab800c5e4c6af25c822a3befcb59a Mon Sep 17 00:00:00 2001 From: Orange++ Date: Tue, 8 Jul 2025 17:01:56 +0300 Subject: [PATCH 320/795] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index d5acf4d1..e62acd9b 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,16 @@ Or even advanced projectile aimbot [Watch Video](https://youtu.be/lM_NJ1yCunw?si=5E87OrQMeypxSJ3E) +## ⭐ Star History + + + + + + Star History Chart + + + ## 🫵🏻 Contributing Contributions to `omath` are welcome! Please read `CONTRIBUTING.md` for details on our code of conduct and the process for submitting pull requests. From 7dbebc996de1b9dabe1477a7c5dd79d79aa34574 Mon Sep 17 00:00:00 2001 From: Orange++ Date: Tue, 8 Jul 2025 17:13:24 +0300 Subject: [PATCH 321/795] Update README.md --- README.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e62acd9b..c4dfc949 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,15 @@ [![CodeFactor](https://www.codefactor.io/repository/github/orange-cpp/omath/badge)](https://www.codefactor.io/repository/github/orange-cpp/omath) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml) ![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath) + + + + + + Star History Chart + + +
Oranges's Math Library (omath) is a comprehensive, open-source library aimed at providing efficient, reliable, and versatile mathematical functions and algorithms. Developed primarily in C++, this library is designed to cater to a wide range of mathematical operations essential in scientific computing, engineering, and academic research. @@ -67,16 +76,6 @@ Or even advanced projectile aimbot [Watch Video](https://youtu.be/lM_NJ1yCunw?si=5E87OrQMeypxSJ3E) -## ⭐ Star History - - - - - - Star History Chart - - - ## 🫵🏻 Contributing Contributions to `omath` are welcome! Please read `CONTRIBUTING.md` for details on our code of conduct and the process for submitting pull requests. From 8e09556c25f3bfd6bf60a9df08d617eab2f33b0f Mon Sep 17 00:00:00 2001 From: Orange++ Date: Tue, 8 Jul 2025 17:14:52 +0300 Subject: [PATCH 322/795] Update README.md --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c4dfc949..7b9f496f 100644 --- a/README.md +++ b/README.md @@ -8,19 +8,20 @@ [![CodeFactor](https://www.codefactor.io/repository/github/orange-cpp/omath/badge)](https://www.codefactor.io/repository/github/orange-cpp/omath) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml) ![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath) - - - - - - Star History Chart - - -