Math: Add Angle class

This commit is contained in:
Lynix 2018-09-02 22:06:03 +02:00
parent 960af3afa3
commit f59810b68e
3 changed files with 623 additions and 0 deletions

View File

@ -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 <Nazara/Core/String.hpp>
#include <Nazara/Math/Algorithm.hpp>
#include <Nazara/Math/Enums.hpp>
#include <type_traits>
namespace Nz
{
struct SerializationContext;
template<AngleUnit Unit, typename T>
class Angle
{
public:
Angle() = default;
Angle(T Angle);
template<AngleUnit U = Unit, typename C = std::enable_if_t<Unit == AngleUnit::Degree>> explicit Angle(const Angle<AngleUnit::Radian, T>& Angle) { Set(Angle); }
template<AngleUnit U = Unit, typename C = std::enable_if_t<Unit == AngleUnit::Radian>> explicit Angle(const Angle<AngleUnit::Degree, T>& Angle) { Set(Angle); }
template<typename U> explicit Angle(const Angle<Unit, U>& Angle);
Angle(const Angle&) = default;
~Angle() = default;
Angle& MakeZero();
void Normalize();
template<AngleUnit U = Unit, typename = std::enable_if_t<U == AngleUnit::Degree>> Angle& Set(const Angle<AngleUnit::Radian, T>& Angle);
template<AngleUnit U = Unit, typename = std::enable_if_t<U == AngleUnit::Radian>> Angle& Set(const Angle<AngleUnit::Degree, T>& Angle);
Angle& Set(const Angle& Angle);
template<typename U> Angle& Set(const Angle<Unit, U>& Angle);
Angle<AngleUnit::Degree, T> ToDegrees() const;
Angle<AngleUnit::Radian, T> 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<typename T>
using DegreeAngle = Angle<AngleUnit::Degree, T>;
using DegreeAngled = DegreeAngle<double>;
using DegreeAnglef = DegreeAngle<float>;
template<typename T>
using RadianAngle = Angle<AngleUnit::Radian, T>;
using RadianAngled = RadianAngle<double>;
using RadianAnglef = RadianAngle<float>;
template<AngleUnit Unit, typename T> bool Serialize(SerializationContext& context, const Angle<Unit, T>& angle, TypeTag<Angle<Unit, T>>);
template<AngleUnit Unit, typename T> bool Unserialize(SerializationContext& context, Angle<Unit, T>* angle, TypeTag<Angle<Unit, T>>);
}
template<Nz::AngleUnit Unit, typename T>
Nz::Angle<Unit, T> operator*(T scale, const Nz::Angle<Unit, T>& angle);
template<Nz::AngleUnit Unit, typename T>
Nz::Angle<Unit, T> operator/(T divider, const Nz::Angle<Unit, T>& angle);
template<Nz::AngleUnit Unit, typename T>
std::ostream& operator<<(std::ostream& out, const Nz::Angle<Unit, T>& angle);
#include <Nazara/Math/Angle.inl>
#endif // NAZARA_ANGLE_HPP

View File

@ -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 <Nazara/Math/Angle.hpp>
#include <algorithm>
#include <cstring>
#include <Nazara/Core/Debug.hpp>
namespace Nz
{
namespace Detail
{
template<AngleUnit Unit> struct AngleUtils;
template<>
struct AngleUtils<AngleUnit::Degree>
{
template<typename T> static constexpr T GetLimit()
{
return 180;
}
template<typename T> static T FromDegrees(T degrees)
{
return degrees;
}
template<typename T> static T FromRadians(T radians)
{
return RadianToDegree(radians);
}
template<typename T> static T ToDegrees(T degrees)
{
return degrees;
}
template<typename T> static T ToRadians(T degrees)
{
return DegreeToRadian(degrees);
}
template<typename T> static String ToString(T value)
{
return "Angle(" + String::Number(value) + "deg)";
}
template<typename T> static std::ostream& ToString(std::ostream& out, T value)
{
return out << "Angle(" << value << "deg)";
}
};
template<>
struct AngleUtils<AngleUnit::Radian>
{
template<typename T> static constexpr T GetLimit()
{
return M_PI;
}
template<typename T> static T FromDegrees(T degrees)
{
return DegreeToRadian(degrees);
}
template<typename T> static T FromRadians(T radians)
{
return radians;
}
template<typename T> static T ToDegrees(T radians)
{
return RadianToDegree(radians);
}
template<typename T> static T ToRadians(T radians)
{
return radians;
}
template<typename T> static String ToString(T value)
{
return "Angle(" + String::Number(value) + "rad)";
}
template<typename T> 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<AngleUnit Unit, typename T>
Angle<Unit, T>::Angle(T Angle) :
angle(Angle)
{
}
/*!
* \brief Changes the angle value to zero
*/
template<AngleUnit Unit, typename T>
Angle<Unit, T>& Angle<Unit, T>::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<AngleUnit Unit, typename T>
void Angle<Unit, T>::Normalize()
{
constexpr T limit = Detail::AngleUtils<Unit>::GetLimit<T>();
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<AngleUnit Unit, typename T>
template<AngleUnit U, typename>
Angle<Unit, T>& Angle<Unit, T>::Set(const Angle<AngleUnit::Radian, T>& 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<AngleUnit Unit, typename T>
template<AngleUnit U, typename>
Angle<Unit, T>& Angle<Unit, T>::Set(const Angle<AngleUnit::Degree, T>& Angle)
{
angle = DegreeToRadian(Angle.angle);
return *this;
}
/*!
* \brief Copies the angle value of an angle
*
* \param Angle Angle which will be copied
*/
template<AngleUnit Unit, typename T>
Angle<Unit, T>& Angle<Unit, T>::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<AngleUnit Unit, typename T>
template<typename U>
Angle<Unit, T>& Angle<Unit, T>::Set(const Angle<Unit, U>& Angle)
{
angle = static_cast<T>(Angle.angle);
return *this;
}
/*!
* \brief Returns the degree angle that is equivalent to this one
* \return Equivalent degree angle
*/
template<AngleUnit Unit, typename T>
Angle<AngleUnit::Degree, T> Angle<Unit, T>::ToDegrees() const
{
return DegreeAngle<T>(Detail::AngleUtils<Unit>::ToDegrees(angle));
}
/*!
* \brief Returns the radian angle that is equivalent to this angle
* \return Equivalent radian angle
*/
template<AngleUnit Unit, typename T>
Angle<AngleUnit::Radian, T> Angle<Unit, T>::ToRadians() const
{
return RadianAngle<T>(Detail::AngleUtils<Unit>::ToRadians(angle));
}
/*!
* \brief Converts the angle to a string representation
* \return String representation of the angle
*/
template<AngleUnit Unit, typename T>
String Angle<Unit, T>::ToString() const
{
return Detail::AngleUtils<Unit>::ToString(angle);
}
/*!
* \brief Addition operator
* \return Adds two angles together
*
* \param angle Angle to add
*/
template<AngleUnit Unit, typename T>
Angle<Unit, T> Angle<Unit, T>::operator+(const Angle& Angle) const
{
return Angle(angle + Angle.angle);
}
/*!
* \brief Subtraction operator
* \return Subtracts two angles together
*
* \param angle Angle to subtract
*/
template<AngleUnit Unit, typename T>
Angle<Unit, T> Angle<Unit, T>::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<AngleUnit Unit, typename T>
Angle<Unit, T>& Angle<Unit, T>::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<AngleUnit Unit, typename T>
Angle<Unit, T>& Angle<Unit, T>::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<AngleUnit Unit, typename T>
Angle<Unit, T>& Angle<Unit, T>::operator*=(T scalar)
{
angle *= scalar;
return *this;
}
/*!
* \brief Divides the angle by a scalar
* \return A reference to the angle
*
* \param divider Divider
*/
template<AngleUnit Unit, typename T>
Angle<Unit, T>& Angle<Unit, T>::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<AngleUnit Unit, typename T>
bool Angle<Unit, T>::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<AngleUnit Unit, typename T>
bool Angle<Unit, T>::operator!=(const Angle& Angle) const
{
return !NumberEquals(angle, Angle.angle);
}
/*!
* \brief Returns an angle with an angle of zero
* \return Zero angle
*/
template<AngleUnit Unit, typename T>
Angle<Unit, T> Angle<Unit, T>::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<AngleUnit Unit, typename T>
bool Serialize(SerializationContext& context, const Angle<Unit, T>& angle, TypeTag<Angle<Unit, T>>)
{
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<AngleUnit Unit, typename T>
bool Unserialize(SerializationContext& context, Angle<Unit, T>* angle, TypeTag<Angle<Unit, T>>)
{
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::AngleUnit Unit, typename T>
Nz::Angle<Unit, T> operator*(T scale, const Nz::Angle<Unit, T>& angle)
{
return Nz::Angle<Unit, T>(scale * angle.angle);
}
/*!
* \brief Division operator
* \return An angle corresponding to scale / angle
*
* \param scale Divisor
* \param angle Angle
*/
template<Nz::AngleUnit Unit, typename T>
Nz::Angle<Unit, T> operator/(T scale, const Nz::Angle<Unit, T>& angle)
{
return Nz::Angle<Unit, T>(scale / angle.angle);
}
/*!
* \brief Output operator
* \return The stream
*
* \param out The stream
* \param box The box to output
*/
template<Nz::AngleUnit Unit, typename T>
std::ostream& operator<<(std::ostream& out, const Nz::Angle<Unit, T>& angle)
{
return Nz::Detail::AngleUtils<Unit>::ToString(out, angle.angle);
}
#include <Nazara/Core/DebugOff.hpp>

128
tests/Engine/Math/Angle.cpp Normal file
View File

@ -0,0 +1,128 @@
#include <Nazara/Math/Angle.hpp>
#include <Catch/catch.hpp>
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);
}
}
}
}