diff --git a/CMakeLists.txt b/CMakeLists.txt index f2ebbed8..ad862534 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" 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) +option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" OFF) option(OMATH_ENABLE_LEGACY "Will enable legacy classes that MUST be used ONLY for backward compatibility" OFF) message(STATUS "[${PROJECT_NAME}]: Building on ${CMAKE_HOST_SYSTEM_NAME}") diff --git a/include/omath/angle.hpp b/include/omath/angle.hpp index a13b27fa..ee539219 100644 --- a/include/omath/angle.hpp +++ b/include/omath/angle.hpp @@ -123,13 +123,13 @@ namespace omath } [[nodiscard]] - constexpr Angle& operator+(const Angle& other) noexcept + constexpr Angle operator+(const Angle& other) noexcept { if constexpr (flags == AngleFlags::Normalized) - return {angles::wrap_angle(m_angle + other.m_angle, min, max)}; + return Angle{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)}; + return Angle{std::clamp(m_angle + other.m_angle, min, max)}; else static_assert(false); @@ -138,7 +138,7 @@ namespace omath } [[nodiscard]] - constexpr Angle& operator-(const Angle& other) noexcept + constexpr Angle operator-(const Angle& other) noexcept { return operator+(-other); } diff --git a/tests/general/unit_test_angle.cpp b/tests/general/unit_test_angle.cpp index 66bc0d12..3fafd24e 100644 --- a/tests/general/unit_test_angle.cpp +++ b/tests/general/unit_test_angle.cpp @@ -1,3 +1,192 @@ // // Created by Orange on 11/30/2024. // + +#include +#include +#include +#include + +using namespace omath; + +namespace +{ + + // Handy aliases (defaults: Type=float, [0,360], Normalized) + using Deg = Angle; + using Pitch = Angle; + using Turn = Angle; + + constexpr float kEps = 1e-5f; + +} // namespace + +// ---------- Construction / factories ---------- + +TEST(UnitTestAngle, DefaultConstructor_IsZeroDegrees) +{ + Deg a; // default ctor + EXPECT_FLOAT_EQ(*a, 0.0f); + EXPECT_FLOAT_EQ(a.as_degrees(), 0.0f); +} + +TEST(UnitTestAngle, FromDegrees_Normalized_WrapsAboveMax) +{ + const Deg a = Deg::from_degrees(370.0f); + EXPECT_FLOAT_EQ(a.as_degrees(), 10.0f); +} + +TEST(UnitTestAngle, FromDegrees_Normalized_WrapsBelowMin) +{ + const Deg a = Deg::from_degrees(-10.0f); + EXPECT_FLOAT_EQ(a.as_degrees(), 350.0f); +} + +TEST(UnitTestAngle, FromDegrees_Clamped_ClampsToRange) +{ + const Pitch hi = Pitch::from_degrees(100.0f); + const Pitch lo = Pitch::from_degrees(-120.0f); + + EXPECT_FLOAT_EQ(hi.as_degrees(), 90.0f); + EXPECT_FLOAT_EQ(lo.as_degrees(), -90.0f); +} + +TEST(UnitTestAngle, FromRadians_And_AsRadians) +{ + const Deg a = Deg::from_radians(std::numbers::pi_v); + EXPECT_FLOAT_EQ(a.as_degrees(), 180.0f); + + const Deg b = Deg::from_degrees(180.0f); + EXPECT_NEAR(b.as_radians(), std::numbers::pi_v, 1e-6f); +} + +// ---------- Unary minus & deref ---------- + +TEST(UnitTestAngle, UnaryMinus_Normalized) +{ + const Deg a = Deg::from_degrees(30.0f); + const Deg b = -a; // wraps to 330 in [0,360) + EXPECT_FLOAT_EQ(b.as_degrees(), 330.0f); +} + +TEST(UnitTestAngle, DereferenceReturnsDegrees) +{ + const Deg a = Deg::from_degrees(42.0f); + EXPECT_FLOAT_EQ(*a, 42.0f); +} + +// ---------- Trigonometric helpers ---------- + +TEST(UnitTestAngle, SinCosTanCot_BasicCases) +{ + const Deg a0 = Deg::from_degrees(0.0f); + EXPECT_NEAR(a0.sin(), 0.0f, kEps); + EXPECT_NEAR(a0.cos(), 1.0f, kEps); + // cot(0) -> cos/sin -> div by 0: allow inf or nan + const float cot0 = a0.cot(); + EXPECT_TRUE(std::isinf(cot0) || std::isnan(cot0)); + + const Deg a45 = Deg::from_degrees(45.0f); + EXPECT_NEAR(a45.tan(), 1.0f, 1e-4f); + EXPECT_NEAR(a45.cot(), 1.0f, 1e-4f); + + const Deg a90 = Deg::from_degrees(90.0f); + EXPECT_NEAR(a90.sin(), 1.0f, 1e-4f); + EXPECT_NEAR(a90.cos(), 0.0f, 1e-4f); +} + +TEST(UnitTestAngle, Atan_IsAtanOfRadians) +{ + // atan(as_radians). For 0° -> atan(0)=0. + const Deg a0 = Deg::from_degrees(0.0f); + EXPECT_NEAR(a0.atan(), 0.0f, kEps); + + const Deg a45 = Deg::from_degrees(45.0f); + // atan(pi/4) ≈ 0.665773... + EXPECT_NEAR(a45.atan(), 0.66577375f, 1e-6f); +} + +// ---------- Compound arithmetic ---------- + +TEST(UnitTestAngle, PlusEquals_Normalized_Wraps) +{ + Deg a = Deg::from_degrees(350.0f); + a += Deg::from_degrees(20.0f); // 370 -> 10 + EXPECT_FLOAT_EQ(a.as_degrees(), 10.0f); +} + +TEST(UnitTestAngle, MinusEquals_Normalized_Wraps) +{ + Deg a = Deg::from_degrees(10.0f); + a -= Deg::from_degrees(30.0f); // -20 -> 340 + EXPECT_FLOAT_EQ(a.as_degrees(), 340.0f); +} + +TEST(UnitTestAngle, PlusEquals_Clamped_Clamps) +{ + Pitch p = Pitch::from_degrees(80.0f); + p += Pitch::from_degrees(30.0f); // 110 -> clamp to 90 + EXPECT_FLOAT_EQ(p.as_degrees(), 90.0f); +} + +TEST(UnitTestAngle, MinusEquals_Clamped_Clamps) +{ + Pitch p = Pitch::from_degrees(-70.0f); + p -= Pitch::from_degrees(40.0f); // -110 -> clamp to -90 + EXPECT_FLOAT_EQ(p.as_degrees(), -90.0f); +} + +// ---------- Alternative ranges ---------- + +TEST(UnitTestAngle, NormalizedRange_Neg180To180) +{ + const Turn a = Turn::from_degrees(190.0f); // -> -170 + const Turn b = Turn::from_degrees(-190.0f); // -> 170 + + EXPECT_FLOAT_EQ(a.as_degrees(), -170.0f); + EXPECT_FLOAT_EQ(b.as_degrees(), 170.0f); +} + +// ---------- Comparisons (via <=>) ---------- + +TEST(UnitTestAngle, Comparisons_WorkWithPartialOrdering) +{ + const Deg a = Deg::from_degrees(10.0f); + const Deg b = Deg::from_degrees(20.0f); + const Deg c = Deg::from_degrees(10.0f); + + EXPECT_TRUE(a < b); + EXPECT_TRUE(b > a); + EXPECT_TRUE(a <= c); + EXPECT_TRUE(c >= a); +} + +// ---------- std::format formatter ---------- + +TEST(UnitTestAngle, Formatter_PrintsDegreesWithSuffix) +{ + const Deg a = Deg::from_degrees(15.0f); + EXPECT_EQ(std::format("{}", a), "15deg"); + + const Deg b = Deg::from_degrees(10.5f); + EXPECT_EQ(std::format("{}", b), "10.5deg"); + + const Turn t = Turn::from_degrees(-170.0f); + EXPECT_EQ(std::format("{}", t), "-170deg"); +} + +TEST(UnitTestAngle, BinaryPlus_ReturnsWrappedSum) +{ + Angle<> a = Deg::from_degrees(350.0f); + const Deg b = Deg::from_degrees(20.0f); + const Deg c = a + b; // expect 10° + EXPECT_FLOAT_EQ(c.as_degrees(), 10.0f); +} + +TEST(UnitTestAngle, BinaryMinus_ReturnsWrappedDiff) +{ + Angle<> a = Deg::from_degrees(10.0f); + const Deg b = Deg::from_degrees(30.0f); + const Deg c = a - b; // expect 340° + EXPECT_FLOAT_EQ(c.as_degrees(), 340.0f); +}