diff --git a/include/Nazara/Math/Angle.hpp b/include/Nazara/Math/Angle.hpp new file mode 100644 index 000000000..ccf770959 --- /dev/null +++ b/include/Nazara/Math/Angle.hpp @@ -0,0 +1,89 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Mathematics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_ANGLE_HPP +#define NAZARA_ANGLE_HPP + +#include +#include +#include +#include + +namespace Nz +{ + struct SerializationContext; + + template + class Angle + { + public: + Angle() = default; + Angle(T Angle); + template> explicit Angle(const Angle& Angle) { Set(Angle); } + template> explicit Angle(const Angle& Angle) { Set(Angle); } + template explicit Angle(const Angle& Angle); + Angle(const Angle&) = default; + ~Angle() = default; + + Angle& MakeZero(); + + void Normalize(); + + template> Angle& Set(const Angle& Angle); + template> Angle& Set(const Angle& Angle); + Angle& Set(const Angle& Angle); + template Angle& Set(const Angle& Angle); + + Angle ToDegrees() const; + Angle ToRadians() const; + String ToString() const; + + Angle& operator=(const Angle&) = default; + + Angle operator+(const Angle& Angle) const; + Angle operator-(const Angle& Angle) const; + + Angle& operator+=(const Angle& Angle); + Angle& operator-=(const Angle& Angle); + Angle& operator*=(T scalar); + Angle& operator/=(T divider); + + bool operator==(const Angle& Angle) const; + bool operator!=(const Angle& Angle) const; + + static Angle Zero(); + + T angle; + }; + + template + using DegreeAngle = Angle; + + using DegreeAngled = DegreeAngle; + using DegreeAnglef = DegreeAngle; + + template + using RadianAngle = Angle; + + using RadianAngled = RadianAngle; + using RadianAnglef = RadianAngle; + + template bool Serialize(SerializationContext& context, const Angle& angle, TypeTag>); + template bool Unserialize(SerializationContext& context, Angle* angle, TypeTag>); +} + +template +Nz::Angle operator*(T scale, const Nz::Angle& angle); + +template +Nz::Angle operator/(T divider, const Nz::Angle& angle); + +template +std::ostream& operator<<(std::ostream& out, const Nz::Angle& angle); + +#include + +#endif // NAZARA_ANGLE_HPP diff --git a/include/Nazara/Math/Angle.inl b/include/Nazara/Math/Angle.inl new file mode 100644 index 000000000..0e8f8dbfe --- /dev/null +++ b/include/Nazara/Math/Angle.inl @@ -0,0 +1,406 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Mathematics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +namespace Nz +{ + namespace Detail + { + template struct AngleUtils; + + template<> + struct AngleUtils + { + template static constexpr T GetLimit() + { + return 180; + } + + template static T FromDegrees(T degrees) + { + return degrees; + } + + template static T FromRadians(T radians) + { + return RadianToDegree(radians); + } + + template static T ToDegrees(T degrees) + { + return degrees; + } + + template static T ToRadians(T degrees) + { + return DegreeToRadian(degrees); + } + + template static String ToString(T value) + { + return "Angle(" + String::Number(value) + "deg)"; + } + + template static std::ostream& ToString(std::ostream& out, T value) + { + return out << "Angle(" << value << "deg)"; + } + }; + + template<> + struct AngleUtils + { + template static constexpr T GetLimit() + { + return M_PI; + } + + template static T FromDegrees(T degrees) + { + return DegreeToRadian(degrees); + } + + template static T FromRadians(T radians) + { + return radians; + } + + template static T ToDegrees(T radians) + { + return RadianToDegree(radians); + } + + template static T ToRadians(T radians) + { + return radians; + } + + template static String ToString(T value) + { + return "Angle(" + String::Number(value) + "rad)"; + } + + template static std::ostream& ToString(std::ostream& out, T value) + { + return out << "Angle(" << value << "rad)"; + } + }; + } + /*! + * \ingroup math + * \class Nz::Angle + * \brief Math class that represents an angle + */ + + /*! + * \brief Constructs an Angle object with an angle value + * + * \param Angle value of the angle + */ + template + Angle::Angle(T Angle) : + angle(Angle) + { + } + + /*! + * \brief Changes the angle value to zero + */ + template + Angle& Angle::MakeZero() + { + angle = T(0); + } + + /*! + * \brief Normalizes the angle value + * + * If angle exceeds local limits positively or negatively, bring it back between them. + * For degree angles, local limits are [-180, 180] + * For radian angles, local limits are [-M_PI, M_PI] + */ + template + void Angle::Normalize() + { + constexpr T limit = Detail::AngleUtils::GetLimit(); + constexpr T twoLimit = limit * T(2); + + angle = std::fmod(angle, twoLimit); + if (angle < T(0)) + angle += twoLimit; + } + + /*! + * \brief Changes the angle value by converting a radian angle + * + * \param Angle Radian angle which will be converted + */ + template + template + Angle& Angle::Set(const Angle& Angle) + { + angle = RadianToDegree(Angle.angle); + return *this; + } + + /*! + * \brief Changes the angle value by converting a degree angle + * + * \param Angle Degree angle which will be converted + */ + template + template + Angle& Angle::Set(const Angle& Angle) + { + angle = DegreeToRadian(Angle.angle); + return *this; + } + + /*! + * \brief Copies the angle value of an angle + * + * \param Angle Angle which will be copied + */ + template + Angle& Angle::Set(const Angle& Angle) + { + angle = Angle.angle; + return *this; + } + + /*! + * \brief Changes the angle value to the same as an Angle of a different type + * + * \param Angle Angle which will be casted + * + * \remark Conversion from U to T occurs using static_cast + */ + template + template + Angle& Angle::Set(const Angle& Angle) + { + angle = static_cast(Angle.angle); + return *this; + } + + /*! + * \brief Returns the degree angle that is equivalent to this one + * \return Equivalent degree angle + */ + template + Angle Angle::ToDegrees() const + { + return DegreeAngle(Detail::AngleUtils::ToDegrees(angle)); + } + + /*! + * \brief Returns the radian angle that is equivalent to this angle + * \return Equivalent radian angle + */ + template + Angle Angle::ToRadians() const + { + return RadianAngle(Detail::AngleUtils::ToRadians(angle)); + } + + /*! + * \brief Converts the angle to a string representation + * \return String representation of the angle + */ + template + String Angle::ToString() const + { + return Detail::AngleUtils::ToString(angle); + } + + /*! + * \brief Addition operator + * \return Adds two angles together + * + * \param angle Angle to add + */ + template + Angle Angle::operator+(const Angle& Angle) const + { + return Angle(angle + Angle.angle); + } + + /*! + * \brief Subtraction operator + * \return Subtracts two angles together + * + * \param angle Angle to subtract + */ + template + Angle Angle::operator-(const Angle& Angle) const + { + return Angle(angle - Angle.angle); + } + + /*! + * \brief Adds an angle by another + * \return A reference to the angle + * + * \param angle Angle to add + */ + template + Angle& Angle::operator+=(const Angle& Angle) + { + angle += Angle.angle; + return *this; + } + + /*! + * \brief Subtract an angle by another + * \return A reference to the angle + * + * \param angle Angle to subtract + */ + template + Angle& Angle::operator-=(const Angle& Angle) + { + angle -= Angle.angle; + return *this; + } + + /*! + * \brief Scales the angle by a scalar + * \return A reference to the angle + * + * \param scalar Multiplier + */ + template + Angle& Angle::operator*=(T scalar) + { + angle *= scalar; + return *this; + } + + /*! + * \brief Divides the angle by a scalar + * \return A reference to the angle + * + * \param divider Divider + */ + template + Angle& Angle::operator/=(T divider) + { + angle /= divider; + return *this; + } + + /*! + * \brief Compares the angle to another for equality + * \return True if both angles are equal + * + * \param Angle The other angle to compare to + */ + template + bool Angle::operator==(const Angle& Angle) const + { + return NumberEquals(angle, Angle.angle); + } + + /*! + * \brief Compares the angle to another for inequality + * \return True if both angles are equal + * + * \param Angle The other angle to compare to + */ + template + bool Angle::operator!=(const Angle& Angle) const + { + return !NumberEquals(angle, Angle.angle); + } + + /*! + * \brief Returns an angle with an angle of zero + * \return Zero angle + */ + template + Angle Angle::Zero() + { + Angle angle; + angle.MakeZero(); + + return angle; + } + + /*! + * \brief Serializes an Angle + * \return true if successfully serialized + * + * \param context Serialization context + * \param angle Input Angle + */ + template + bool Serialize(SerializationContext& context, const Angle& angle, TypeTag>) + { + if (!Serialize(context, angle.angle)) + return false; + + return true; + } + + /*! + * \brief Unserializes an Angle + * \return true if successfully unserialized + * + * \param context Serialization context + * \param angle Output Angle + */ + template + bool Unserialize(SerializationContext& context, Angle* angle, TypeTag>) + { + if (!Unserialize(context, &angle->angle)) + return false; + + return true; + } +} + +/*! +* \brief Multiplication operator +* \return An angle corresponding to scale * angle +* +* \param scale Multiplier +* \param angle Angle +*/ +template +Nz::Angle operator*(T scale, const Nz::Angle& angle) +{ + return Nz::Angle(scale * angle.angle); +} + +/*! +* \brief Division operator +* \return An angle corresponding to scale / angle +* +* \param scale Divisor +* \param angle Angle +*/ +template +Nz::Angle operator/(T scale, const Nz::Angle& angle) +{ + return Nz::Angle(scale / angle.angle); +} + +/*! +* \brief Output operator +* \return The stream +* +* \param out The stream +* \param box The box to output +*/ +template +std::ostream& operator<<(std::ostream& out, const Nz::Angle& angle) +{ + return Nz::Detail::AngleUtils::ToString(out, angle.angle); +} + +#include diff --git a/tests/Engine/Math/Angle.cpp b/tests/Engine/Math/Angle.cpp new file mode 100644 index 000000000..81e83c261 --- /dev/null +++ b/tests/Engine/Math/Angle.cpp @@ -0,0 +1,128 @@ +#include +#include + +SCENARIO("Angle", "[MATH][ANGLE]") +{ + GIVEN("A degree angle of 90deg") + { + Nz::DegreeAnglef angle(90.f); + + WHEN("We convert it to degrees") + { + Nz::DegreeAnglef copyAngle = angle.ToDegrees(); + + THEN("It should compare to itself") + { + CHECK(angle == copyAngle); + } + } + + WHEN("We convert it to radians") + { + Nz::RadianAnglef radAngle(angle); + + THEN("It should be equal to pi/2") + { + Nz::RadianAnglef expectedResult(float(M_PI_2)); + + CHECK(radAngle == expectedResult); + CHECK(angle.ToRadians() == expectedResult); + } + } + } + + GIVEN("A degree angle of 480deg") + { + Nz::DegreeAnglef angle(480.f); + + WHEN("We normalize it") + { + angle.Normalize(); + + THEN("It should be equal to a normalized version of itself") + { + Nz::DegreeAnglef expectedResult(120.f); + + CHECK(angle == expectedResult); + } + } + } + + GIVEN("A degree angle of -270deg") + { + Nz::DegreeAnglef angle(-270.f); + + WHEN("We normalize it") + { + angle.Normalize(); + + THEN("It should be equal to a normalized version of itself") + { + Nz::DegreeAnglef expectedResult(90.f); + + CHECK(angle == expectedResult); + } + } + } + + GIVEN("A radian angle of -M_PI") + { + Nz::RadianAnglef angle(float(-M_PI)); + + WHEN("We convert it to radians") + { + Nz::RadianAnglef copyAngle = angle.ToRadians(); + + THEN("It should compare to itself") + { + CHECK(angle == copyAngle); + } + } + + WHEN("We convert it to degrees") + { + Nz::DegreeAnglef degAngle(angle); + + THEN("It should be equal to pi/2") + { + Nz::DegreeAnglef expectedResult(-180.f); + + CHECK(degAngle == expectedResult); + CHECK(angle.ToDegrees() == expectedResult); + } + } + } + GIVEN("A radian angle of 7pi") + { + Nz::RadianAnglef angle(float(7 * M_PI)); + + WHEN("We normalize it") + { + angle.Normalize(); + + THEN("It should be equal to a normalized version of itself") + { + Nz::RadianAnglef expectedResult(float(M_PI)); + + CHECK(angle == expectedResult); + } + } + } + + GIVEN("A radian angle of -4pi") + { + Nz::RadianAnglef angle(float(-4 * M_PI)); + + WHEN("We normalize it") + { + angle.Normalize(); + + THEN("It should be equal to a normalized version of itself") + { + Nz::RadianAnglef expectedResult(0.f); + + CHECK(angle == expectedResult); + } + } + } +}