mirror of
https://github.com/orange-cpp/omath.git
synced 2026-02-13 07:03:25 +00:00
Implements angle class with normalization
Adds an angle class with support for different normalization and clamping strategies. Includes trigonometric functions and arithmetic operators. Introduces unit tests to verify correct functionality. Disables unity builds to address a compilation issue.
This commit is contained in:
@@ -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_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_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)
|
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}")
|
message(STATUS "[${PROJECT_NAME}]: Building on ${CMAKE_HOST_SYSTEM_NAME}")
|
||||||
|
|||||||
@@ -123,13 +123,13 @@ namespace omath
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
constexpr Angle& operator+(const Angle& other) noexcept
|
constexpr Angle operator+(const Angle& other) noexcept
|
||||||
{
|
{
|
||||||
if constexpr (flags == AngleFlags::Normalized)
|
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)
|
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
|
else
|
||||||
static_assert(false);
|
static_assert(false);
|
||||||
@@ -138,7 +138,7 @@ namespace omath
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
constexpr Angle& operator-(const Angle& other) noexcept
|
constexpr Angle operator-(const Angle& other) noexcept
|
||||||
{
|
{
|
||||||
return operator+(-other);
|
return operator+(-other);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,192 @@
|
|||||||
//
|
//
|
||||||
// Created by Orange on 11/30/2024.
|
// 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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user