Skip to content

Commit 6bb549e

Browse files
authored
Merge pull request #67 from orange-cpp/feature/angle-improvement
Implements angle class with normalization
2 parents b964661 + 14fa810 commit 6bb549e

File tree

3 files changed

+194
-5
lines changed

3 files changed

+194
-5
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types
1313
option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF)
1414
option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF)
1515
option(OMATH_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build to improve general performance" ON)
16-
option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" ON)
16+
option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" OFF)
1717
option(OMATH_ENABLE_LEGACY "Will enable legacy classes that MUST be used ONLY for backward compatibility" OFF)
1818

1919
message(STATUS "[${PROJECT_NAME}]: Building on ${CMAKE_HOST_SYSTEM_NAME}")

include/omath/angle.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,13 @@ namespace omath
123123
}
124124

125125
[[nodiscard]]
126-
constexpr Angle& operator+(const Angle& other) noexcept
126+
constexpr Angle operator+(const Angle& other) noexcept
127127
{
128128
if constexpr (flags == AngleFlags::Normalized)
129-
return {angles::wrap_angle(m_angle + other.m_angle, min, max)};
129+
return Angle{angles::wrap_angle(m_angle + other.m_angle, min, max)};
130130

131131
else if constexpr (flags == AngleFlags::Clamped)
132-
return {std::clamp(m_angle + other.m_angle, min, max)};
132+
return Angle{std::clamp(m_angle + other.m_angle, min, max)};
133133

134134
else
135135
static_assert(false);
@@ -138,7 +138,7 @@ namespace omath
138138
}
139139

140140
[[nodiscard]]
141-
constexpr Angle& operator-(const Angle& other) noexcept
141+
constexpr Angle operator-(const Angle& other) noexcept
142142
{
143143
return operator+(-other);
144144
}

tests/general/unit_test_angle.cpp

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,192 @@
11
//
22
// Created by Orange on 11/30/2024.
33
//
4+
5+
#include <cmath>
6+
#include <gtest/gtest.h>
7+
#include <numbers>
8+
#include <omath/angle.hpp>
9+
10+
using namespace omath;
11+
12+
namespace
13+
{
14+
15+
// Handy aliases (defaults: Type=float, [0,360], Normalized)
16+
using Deg = Angle<float, float(0), float(360), AngleFlags::Normalized>;
17+
using Pitch = Angle<float, float(-90), float(90), AngleFlags::Clamped>;
18+
using Turn = Angle<float, float(-180), float(180), AngleFlags::Normalized>;
19+
20+
constexpr float kEps = 1e-5f;
21+
22+
} // namespace
23+
24+
// ---------- Construction / factories ----------
25+
26+
TEST(UnitTestAngle, DefaultConstructor_IsZeroDegrees)
27+
{
28+
Deg a; // default ctor
29+
EXPECT_FLOAT_EQ(*a, 0.0f);
30+
EXPECT_FLOAT_EQ(a.as_degrees(), 0.0f);
31+
}
32+
33+
TEST(UnitTestAngle, FromDegrees_Normalized_WrapsAboveMax)
34+
{
35+
const Deg a = Deg::from_degrees(370.0f);
36+
EXPECT_FLOAT_EQ(a.as_degrees(), 10.0f);
37+
}
38+
39+
TEST(UnitTestAngle, FromDegrees_Normalized_WrapsBelowMin)
40+
{
41+
const Deg a = Deg::from_degrees(-10.0f);
42+
EXPECT_FLOAT_EQ(a.as_degrees(), 350.0f);
43+
}
44+
45+
TEST(UnitTestAngle, FromDegrees_Clamped_ClampsToRange)
46+
{
47+
const Pitch hi = Pitch::from_degrees(100.0f);
48+
const Pitch lo = Pitch::from_degrees(-120.0f);
49+
50+
EXPECT_FLOAT_EQ(hi.as_degrees(), 90.0f);
51+
EXPECT_FLOAT_EQ(lo.as_degrees(), -90.0f);
52+
}
53+
54+
TEST(UnitTestAngle, FromRadians_And_AsRadians)
55+
{
56+
const Deg a = Deg::from_radians(std::numbers::pi_v<float>);
57+
EXPECT_FLOAT_EQ(a.as_degrees(), 180.0f);
58+
59+
const Deg b = Deg::from_degrees(180.0f);
60+
EXPECT_NEAR(b.as_radians(), std::numbers::pi_v<float>, 1e-6f);
61+
}
62+
63+
// ---------- Unary minus & deref ----------
64+
65+
TEST(UnitTestAngle, UnaryMinus_Normalized)
66+
{
67+
const Deg a = Deg::from_degrees(30.0f);
68+
const Deg b = -a; // wraps to 330 in [0,360)
69+
EXPECT_FLOAT_EQ(b.as_degrees(), 330.0f);
70+
}
71+
72+
TEST(UnitTestAngle, DereferenceReturnsDegrees)
73+
{
74+
const Deg a = Deg::from_degrees(42.0f);
75+
EXPECT_FLOAT_EQ(*a, 42.0f);
76+
}
77+
78+
// ---------- Trigonometric helpers ----------
79+
80+
TEST(UnitTestAngle, SinCosTanCot_BasicCases)
81+
{
82+
const Deg a0 = Deg::from_degrees(0.0f);
83+
EXPECT_NEAR(a0.sin(), 0.0f, kEps);
84+
EXPECT_NEAR(a0.cos(), 1.0f, kEps);
85+
// cot(0) -> cos/sin -> div by 0: allow inf or nan
86+
const float cot0 = a0.cot();
87+
EXPECT_TRUE(std::isinf(cot0) || std::isnan(cot0));
88+
89+
const Deg a45 = Deg::from_degrees(45.0f);
90+
EXPECT_NEAR(a45.tan(), 1.0f, 1e-4f);
91+
EXPECT_NEAR(a45.cot(), 1.0f, 1e-4f);
92+
93+
const Deg a90 = Deg::from_degrees(90.0f);
94+
EXPECT_NEAR(a90.sin(), 1.0f, 1e-4f);
95+
EXPECT_NEAR(a90.cos(), 0.0f, 1e-4f);
96+
}
97+
98+
TEST(UnitTestAngle, Atan_IsAtanOfRadians)
99+
{
100+
// atan(as_radians). For 0° -> atan(0)=0.
101+
const Deg a0 = Deg::from_degrees(0.0f);
102+
EXPECT_NEAR(a0.atan(), 0.0f, kEps);
103+
104+
const Deg a45 = Deg::from_degrees(45.0f);
105+
// atan(pi/4) ≈ 0.665773...
106+
EXPECT_NEAR(a45.atan(), 0.66577375f, 1e-6f);
107+
}
108+
109+
// ---------- Compound arithmetic ----------
110+
111+
TEST(UnitTestAngle, PlusEquals_Normalized_Wraps)
112+
{
113+
Deg a = Deg::from_degrees(350.0f);
114+
a += Deg::from_degrees(20.0f); // 370 -> 10
115+
EXPECT_FLOAT_EQ(a.as_degrees(), 10.0f);
116+
}
117+
118+
TEST(UnitTestAngle, MinusEquals_Normalized_Wraps)
119+
{
120+
Deg a = Deg::from_degrees(10.0f);
121+
a -= Deg::from_degrees(30.0f); // -20 -> 340
122+
EXPECT_FLOAT_EQ(a.as_degrees(), 340.0f);
123+
}
124+
125+
TEST(UnitTestAngle, PlusEquals_Clamped_Clamps)
126+
{
127+
Pitch p = Pitch::from_degrees(80.0f);
128+
p += Pitch::from_degrees(30.0f); // 110 -> clamp to 90
129+
EXPECT_FLOAT_EQ(p.as_degrees(), 90.0f);
130+
}
131+
132+
TEST(UnitTestAngle, MinusEquals_Clamped_Clamps)
133+
{
134+
Pitch p = Pitch::from_degrees(-70.0f);
135+
p -= Pitch::from_degrees(40.0f); // -110 -> clamp to -90
136+
EXPECT_FLOAT_EQ(p.as_degrees(), -90.0f);
137+
}
138+
139+
// ---------- Alternative ranges ----------
140+
141+
TEST(UnitTestAngle, NormalizedRange_Neg180To180)
142+
{
143+
const Turn a = Turn::from_degrees(190.0f); // -> -170
144+
const Turn b = Turn::from_degrees(-190.0f); // -> 170
145+
146+
EXPECT_FLOAT_EQ(a.as_degrees(), -170.0f);
147+
EXPECT_FLOAT_EQ(b.as_degrees(), 170.0f);
148+
}
149+
150+
// ---------- Comparisons (via <=>) ----------
151+
152+
TEST(UnitTestAngle, Comparisons_WorkWithPartialOrdering)
153+
{
154+
const Deg a = Deg::from_degrees(10.0f);
155+
const Deg b = Deg::from_degrees(20.0f);
156+
const Deg c = Deg::from_degrees(10.0f);
157+
158+
EXPECT_TRUE(a < b);
159+
EXPECT_TRUE(b > a);
160+
EXPECT_TRUE(a <= c);
161+
EXPECT_TRUE(c >= a);
162+
}
163+
164+
// ---------- std::format formatter ----------
165+
166+
TEST(UnitTestAngle, Formatter_PrintsDegreesWithSuffix)
167+
{
168+
const Deg a = Deg::from_degrees(15.0f);
169+
EXPECT_EQ(std::format("{}", a), "15deg");
170+
171+
const Deg b = Deg::from_degrees(10.5f);
172+
EXPECT_EQ(std::format("{}", b), "10.5deg");
173+
174+
const Turn t = Turn::from_degrees(-170.0f);
175+
EXPECT_EQ(std::format("{}", t), "-170deg");
176+
}
177+
178+
TEST(UnitTestAngle, BinaryPlus_ReturnsWrappedSum)
179+
{
180+
Angle<> a = Deg::from_degrees(350.0f);
181+
const Deg b = Deg::from_degrees(20.0f);
182+
const Deg c = a + b; // expect 10°
183+
EXPECT_FLOAT_EQ(c.as_degrees(), 10.0f);
184+
}
185+
186+
TEST(UnitTestAngle, BinaryMinus_ReturnsWrappedDiff)
187+
{
188+
Angle<> a = Deg::from_degrees(10.0f);
189+
const Deg b = Deg::from_degrees(30.0f);
190+
const Deg c = a - b; // expect 340°
191+
EXPECT_FLOAT_EQ(c.as_degrees(), 340.0f);
192+
}

0 commit comments

Comments
 (0)