diff --git a/include/smath.hpp b/include/smath.hpp index 45829ac..db30463 100644 --- a/include/smath.hpp +++ b/include/smath.hpp @@ -25,8 +25,14 @@ #include #include #include +#include +#include #include +#ifndef SMATH_ANGLE_UNIT +#define SMATH_ANGLE_UNIT rad +#endif // SMATH_ANGLE_UNIT + namespace smath { template @@ -35,6 +41,38 @@ struct Vec; namespace detail { +#define SMATH_STR(x) #x +#define SMATH_XSTR(x) SMATH_STR(x) + +consteval bool streq(const char *a, const char *b) { + for (;; ++a, ++b) { + if (*a != *b) + return false; + if (*a == '\0') + return true; + } +} + +enum class AngularUnit { + Radians, + Degrees, + Turns, +}; + +consteval std::optional parse_unit(const char *s) { + if (streq(s, "rad")) + return AngularUnit::Radians; + if (streq(s, "deg")) + return AngularUnit::Degrees; + if (streq(s, "turns")) + return AngularUnit::Turns; + return std::nullopt; +} + +constexpr auto SMATH_ANGLE_UNIT_ID = parse_unit(SMATH_XSTR(SMATH_ANGLE_UNIT)); +static_assert(SMATH_ANGLE_UNIT_ID != std::nullopt, + "Invalid SMATH_ANGLE_UNIT. Should be rad, deg, or turns."); + template struct FixedString { char data[N]{}; static constexpr std::size_t size = N - 1; @@ -401,6 +439,42 @@ using Vec2d = Vec<2, double>; using Vec3d = Vec<3, double>; using Vec4d = Vec<4, double>; +template constexpr auto deg(T const value) -> T { + if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) { + return value; + } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == + detail::AngularUnit::Radians) { + return value * static_cast(std::numbers::pi / 180.0); + } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == + detail::AngularUnit::Turns) { + return value / static_cast(360.0); + } +} + +template constexpr auto rad(T const value) -> T { + if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) { + return value * static_cast(180.0 / std::numbers::pi); + } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == + detail::AngularUnit::Radians) { + return value; + } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == + detail::AngularUnit::Turns) { + return value / (static_cast(2.0) * static_cast(std::numbers::pi)); + } +} + +template constexpr auto turns(T const value) -> T { + if constexpr (detail::SMATH_ANGLE_UNIT_ID == detail::AngularUnit::Degrees) { + return value * static_cast(360.0); + } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == + detail::AngularUnit::Radians) { + return value * (static_cast(2.0) * static_cast(std::numbers::pi)); + } else if constexpr (detail::SMATH_ANGLE_UNIT_ID == + detail::AngularUnit::Turns) { + return value; + } +} + } // namespace smath template @@ -435,4 +509,5 @@ struct tuple_element> { static_assert(I < N); using type = T; }; + } // namespace std diff --git a/tests/angles.cpp b/tests/angles.cpp new file mode 100644 index 0000000..f454e14 --- /dev/null +++ b/tests/angles.cpp @@ -0,0 +1,15 @@ +#include + +#include + +TEST(AngleReturnRadians, DegInput) { + EXPECT_NEAR(smath::deg(180.0), std::numbers::pi, 1e-12); +} + +TEST(AngleReturnRadians, RadInput) { + EXPECT_DOUBLE_EQ(smath::rad(std::numbers::pi), std::numbers::pi); +} + +TEST(AngleReturnRadians, TurnsInput) { + EXPECT_NEAR(smath::turns(0.5), std::numbers::pi, 1e-12); +}