Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down
8 changes: 4 additions & 4 deletions include/omath/angle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -138,7 +138,7 @@ namespace omath
}

[[nodiscard]]
constexpr Angle& operator-(const Angle& other) noexcept
constexpr Angle operator-(const Angle& other) noexcept
{
return operator+(-other);
}
Expand Down
189 changes: 189 additions & 0 deletions tests/general/unit_test_angle.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,192 @@
//
// Created by Orange on 11/30/2024.
//

#include <cmath>
#include <gtest/gtest.h>
#include <numbers>
#include <omath/angle.hpp>

using namespace omath;

namespace
{

// Handy aliases (defaults: Type=float, [0,360], Normalized)
using Deg = Angle<float, float(0), float(360), AngleFlags::Normalized>;
using Pitch = Angle<float, float(-90), float(90), AngleFlags::Clamped>;
using Turn = Angle<float, float(-180), float(180), AngleFlags::Normalized>;

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<float>);
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<float>, 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);
}
Loading