From 28f273b9c96818aa471ef53d3a3e5c836a0cd09e Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Sat, 22 Jan 2022 18:11:08 -0700 Subject: [PATCH] Add sf::Angle Similar to sf::Time, sf::Angle provides a typesafe API for working with angles and provides named functions for converting to and from degrees and radians. --- examples/X11/X11.cpp | 3 +- examples/shader/Shader.cpp | 2 +- examples/tennis/Tennis.cpp | 17 +- examples/vulkan/Vulkan.cpp | 49 +-- examples/win32/Win32.cpp | 2 +- include/SFML/Graphics/Transform.hpp | 17 +- include/SFML/Graphics/Transformable.hpp | 14 +- include/SFML/Graphics/View.hpp | 16 +- include/SFML/System.hpp | 1 + include/SFML/System/Angle.hpp | 519 ++++++++++++++++++++++++ include/SFML/System/Angle.inl | 259 ++++++++++++ src/SFML/Graphics/CircleShape.cpp | 9 +- src/SFML/Graphics/Text.cpp | 2 +- src/SFML/Graphics/Transform.cpp | 8 +- src/SFML/Graphics/Transformable.cpp | 14 +- src/SFML/Graphics/View.cpp | 20 +- src/SFML/System/Angle.cpp | 38 ++ src/SFML/System/CMakeLists.txt | 2 + src/SFML/Window/iOS/SensorImpl.mm | 3 +- test/CMakeLists.txt | 1 + test/Graphics/Transform.cpp | 4 +- test/System/Angle.cpp | 302 ++++++++++++++ test/TestUtilities/SystemUtil.cpp | 12 +- test/TestUtilities/SystemUtil.hpp | 2 + 24 files changed, 1223 insertions(+), 93 deletions(-) create mode 100644 include/SFML/System/Angle.hpp create mode 100644 include/SFML/System/Angle.inl create mode 100644 src/SFML/System/Angle.cpp create mode 100644 test/System/Angle.cpp diff --git a/examples/X11/X11.cpp b/examples/X11/X11.cpp index 9aa37db2e..f6a4b9d34 100644 --- a/examples/X11/X11.cpp +++ b/examples/X11/X11.cpp @@ -47,8 +47,7 @@ // Setup a perspective projection glMatrixMode(GL_PROJECTION); glLoadIdentity(); - static const float pi = 3.141592654f; - float extent = std::tan(90.0f * pi / 360.0f); + float extent = std::tan(sf::degrees(45).asRadians()); #ifdef SFML_OPENGL_ES glFrustumf(-extent, extent, -extent, extent, 1.0f, 500.0f); diff --git a/examples/shader/Shader.cpp b/examples/shader/Shader.cpp index 6e3e72fc9..f979f1c40 100644 --- a/examples/shader/Shader.cpp +++ b/examples/shader/Shader.cpp @@ -308,7 +308,7 @@ public: // Move to the center of the window m_transform.translate({400.f, 300.f}); // Rotate everything based on cursor position - m_transform.rotate(x * 360.f); + m_transform.rotate(sf::degrees(x * 360.f)); // Adjust billboard size to scale between 25 and 75 float size = 25 + std::abs(y) * 50; diff --git a/examples/tennis/Tennis.cpp b/examples/tennis/Tennis.cpp index 4b3daa179..d1b1e951c 100644 --- a/examples/tennis/Tennis.cpp +++ b/examples/tennis/Tennis.cpp @@ -32,7 +32,6 @@ int main() std::srand(static_cast(std::time(nullptr))); // Define some constants - const float pi = 3.14159f; const float gameWidth = 800; const float gameHeight = 600; sf::Vector2f paddleSize(25, 100); @@ -105,7 +104,7 @@ int main() const float paddleSpeed = 400.f; float rightPaddleSpeed = 0.f; const float ballSpeed = 400.f; - float ballAngle = 0.f; // to be changed later + sf::Angle ballAngle = sf::degrees(0); // to be changed later sf::Clock clock; bool isPlaying = false; @@ -142,9 +141,9 @@ int main() do { // Make sure the ball initial angle is not too much vertical - ballAngle = static_cast(std::rand() % 360) * 2.f * pi / 360.f; + ballAngle = sf::degrees(static_cast(std::rand() % 360)); } - while (std::abs(std::cos(ballAngle)) < 0.7f); + while (std::abs(std::cos(ballAngle.asRadians())) < 0.7f); } } @@ -202,7 +201,7 @@ int main() // Move the ball float factor = ballSpeed * deltaTime; - ball.move({std::cos(ballAngle) * factor, std::sin(ballAngle) * factor}); + ball.move({std::cos(ballAngle.asRadians()) * factor, std::sin(ballAngle.asRadians()) * factor}); #ifdef SFML_SYSTEM_IOS const std::string inputString = "Touch the screen to restart."; @@ -242,9 +241,9 @@ int main() ball.getPosition().y - ballRadius <= leftPaddle.getPosition().y + paddleSize.y / 2) { if (ball.getPosition().y > leftPaddle.getPosition().y) - ballAngle = pi - ballAngle + static_cast(std::rand() % 20) * pi / 180; + ballAngle = sf::degrees(180) - ballAngle + sf::degrees(static_cast(std::rand() % 20)); else - ballAngle = pi - ballAngle - static_cast(std::rand() % 20) * pi / 180; + ballAngle = sf::degrees(180) - ballAngle - sf::degrees(static_cast(std::rand() % 20)); ballSound.play(); ball.setPosition({leftPaddle.getPosition().x + ballRadius + paddleSize.x / 2 + 0.1f, ball.getPosition().y}); @@ -257,9 +256,9 @@ int main() ball.getPosition().y - ballRadius <= rightPaddle.getPosition().y + paddleSize.y / 2) { if (ball.getPosition().y > rightPaddle.getPosition().y) - ballAngle = pi - ballAngle + static_cast(std::rand() % 20) * pi / 180; + ballAngle = sf::degrees(180) - ballAngle + sf::degrees(static_cast(std::rand() % 20)); else - ballAngle = pi - ballAngle - static_cast(std::rand() % 20) * pi / 180; + ballAngle = sf::degrees(180) - ballAngle - sf::degrees(static_cast(std::rand() % 20)); ballSound.play(); ball.setPosition({rightPaddle.getPosition().x - ballRadius - paddleSize.x / 2 - 0.1f, ball.getPosition().y}); diff --git a/examples/vulkan/Vulkan.cpp b/examples/vulkan/Vulkan.cpp index fe1d2c697..12db43786 100644 --- a/examples/vulkan/Vulkan.cpp +++ b/examples/vulkan/Vulkan.cpp @@ -39,39 +39,42 @@ namespace } // Rotate a matrix around the x-axis - void matrixRotateX(Matrix& result, float angle) + void matrixRotateX(Matrix& result, sf::Angle angle) { + float rad = angle.asRadians(); Matrix matrix = { - {1.f, 0.f, 0.f, 0.f}, - {0.f, std::cos(angle), std::sin(angle), 0.f}, - {0.f, -std::sin(angle), std::cos(angle), 0.f}, - {0.f, 0.f, 0.f, 1.f} + {1.f, 0.f, 0.f, 0.f}, + {0.f, std::cos(rad), std::sin(rad), 0.f}, + {0.f, -std::sin(rad), std::cos(rad), 0.f}, + {0.f, 0.f, 0.f, 1.f} }; matrixMultiply(result, result, matrix); } // Rotate a matrix around the y-axis - void matrixRotateY(Matrix& result, float angle) + void matrixRotateY(Matrix& result, sf::Angle angle) { + float rad = angle.asRadians(); Matrix matrix = { - { std::cos(angle), 0.f, std::sin(angle), 0.f}, - { 0.f, 1.f, 0.f, 0.f}, - {-std::sin(angle), 0.f, std::cos(angle), 0.f}, - { 0.f, 0.f, 0.f, 1.f} + { std::cos(rad), 0.f, std::sin(rad), 0.f}, + { 0.f, 1.f, 0.f, 0.f}, + {-std::sin(rad), 0.f, std::cos(rad), 0.f}, + { 0.f, 0.f, 0.f, 1.f} }; matrixMultiply(result, result, matrix); } // Rotate a matrix around the z-axis - void matrixRotateZ(Matrix& result, float angle) + void matrixRotateZ(Matrix& result, sf::Angle angle) { + float rad = angle.asRadians(); Matrix matrix = { - { std::cos(angle), std::sin(angle), 0.f, 0.f}, - {-std::sin(angle), std::cos(angle), 0.f, 0.f}, - { 0.f, 0.f, 1.f, 0.f}, - { 0.f, 0.f, 0.f, 1.f} + { std::cos(rad), std::sin(rad), 0.f, 0.f}, + {-std::sin(rad), std::cos(rad), 0.f, 0.f}, + { 0.f, 0.f, 1.f, 0.f}, + { 0.f, 0.f, 0.f, 1.f} }; matrixMultiply(result, result, matrix); @@ -128,9 +131,9 @@ namespace } // Construct a perspective projection matrix - void matrixPerspective(Matrix& result, float fov, float aspect, float nearPlane, float farPlane) + void matrixPerspective(Matrix& result, sf::Angle fov, float aspect, float nearPlane, float farPlane) { - const float a = 1.f / std::tan(fov / 2.f); + const float a = 1.f / std::tan(fov.asRadians() / 2.f); result[0][0] = a / aspect; result[0][1] = 0.f; @@ -2316,8 +2319,6 @@ public: // Update the matrices in our uniform buffer every frame void updateUniformBuffer(float elapsed) { - const float pi = 3.14159265359f; - // Construct the model matrix Matrix model = { { 1.0f, 0.0f, 0.0f, 0.0f }, @@ -2326,9 +2327,9 @@ public: { 0.0f, 0.0f, 0.0f, 1.0f } }; - matrixRotateX(model, elapsed * 59.0f * pi / 180.f); - matrixRotateY(model, elapsed * 83.0f * pi / 180.f); - matrixRotateZ(model, elapsed * 109.0f * pi / 180.f); + matrixRotateX(model, sf::degrees(elapsed * 59.0f)); + matrixRotateY(model, sf::degrees(elapsed * 83.0f)); + matrixRotateZ(model, sf::degrees(elapsed * 109.0f)); // Translate the model based on the mouse position sf::Vector2f mousePosition = sf::Vector2f(sf::Mouse::getPosition(window)); @@ -2349,14 +2350,14 @@ public: matrixLookAt(view, eye, center, up); // Construct the projection matrix - const float fov = 45.0f; + const sf::Angle fov = sf::degrees(45); const float aspect = static_cast(swapchainExtent.width) / static_cast(swapchainExtent.height); const float nearPlane = 0.1f; const float farPlane = 10.0f; Matrix projection; - matrixPerspective(projection, fov * pi / 180.f, aspect, nearPlane, farPlane); + matrixPerspective(projection, fov, aspect, nearPlane, farPlane); char* ptr; diff --git a/examples/win32/Win32.cpp b/examples/win32/Win32.cpp index bb705260b..a779c2355 100644 --- a/examples/win32/Win32.cpp +++ b/examples/win32/Win32.cpp @@ -112,7 +112,7 @@ int main() SFMLView2.clear(); // Draw sprite 1 on view 1 - sprite1.setRotation(time * 100); + sprite1.setRotation(sf::degrees(time * 100)); SFMLView1.draw(sprite1); // Draw sprite 2 on view 2 diff --git a/include/SFML/Graphics/Transform.hpp b/include/SFML/Graphics/Transform.hpp index c47d36353..a7db6f466 100644 --- a/include/SFML/Graphics/Transform.hpp +++ b/include/SFML/Graphics/Transform.hpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace sf @@ -156,7 +157,7 @@ public: /// can be chained. /// \code /// sf::Transform transform; - /// transform.translate(sf::Vector2f(100, 200)).rotate(45); + /// transform.translate(sf::Vector2f(100, 200)).rotate(sf::degrees(45)); /// \endcode /// /// \param offset Translation offset to apply @@ -175,17 +176,17 @@ public: /// can be chained. /// \code /// sf::Transform transform; - /// transform.rotate(90).translate(50, 20); + /// transform.rotate(sf::degrees(90)).translate(50, 20); /// \endcode /// - /// \param angle Rotation angle, in degrees + /// \param angle Rotation angle /// /// \return Reference to *this /// /// \see translate, scale /// //////////////////////////////////////////////////////////// - Transform& rotate(float angle); + Transform& rotate(Angle angle); //////////////////////////////////////////////////////////// /// \brief Combine the current transform with a rotation @@ -199,10 +200,10 @@ public: /// can be chained. /// \code /// sf::Transform transform; - /// transform.rotate(90, sf::Vector2f(8, 3)).translate(sf::Vector2f(50, 20)); + /// transform.rotate(sf::degrees(90), sf::Vector2f(8, 3)).translate(sf::Vector2f(50, 20)); /// \endcode /// - /// \param angle Rotation angle, in degrees + /// \param angle Rotation angle /// \param center Center of rotation /// /// \return Reference to *this @@ -210,7 +211,7 @@ public: /// \see translate, scale /// //////////////////////////////////////////////////////////// - Transform& rotate(float angle, const Vector2f& center); + Transform& rotate(Angle angle, const Vector2f& center); //////////////////////////////////////////////////////////// /// \brief Combine the current transform with a scaling @@ -219,7 +220,7 @@ public: /// can be chained. /// \code /// sf::Transform transform; - /// transform.scale(sf::Vector2f(2, 1)).rotate(45); + /// transform.scale(sf::Vector2f(2, 1)).rotate(sf::degrees(45)); /// \endcode /// /// \param factors Scaling factors diff --git a/include/SFML/Graphics/Transformable.hpp b/include/SFML/Graphics/Transformable.hpp index e47049f91..381718def 100644 --- a/include/SFML/Graphics/Transformable.hpp +++ b/include/SFML/Graphics/Transformable.hpp @@ -75,12 +75,12 @@ public: /// See the rotate function to add an angle based on the previous rotation instead. /// The default rotation of a transformable object is 0. /// - /// \param angle New rotation, in degrees + /// \param angle New rotation /// /// \see rotate, getRotation /// //////////////////////////////////////////////////////////// - void setRotation(float angle); + void setRotation(Angle angle); //////////////////////////////////////////////////////////// /// \brief set the scale factors of the object @@ -128,12 +128,12 @@ public: /// /// The rotation is always in the range [0, 360]. /// - /// \return Current rotation, in degrees + /// \return Current rotation /// /// \see setRotation /// //////////////////////////////////////////////////////////// - float getRotation() const; + Angle getRotation() const; //////////////////////////////////////////////////////////// /// \brief get the current scale of the object @@ -182,10 +182,10 @@ public: /// object.setRotation(object.getRotation() + angle); /// \endcode /// - /// \param angle Angle of rotation, in degrees + /// \param angle Angle of rotation /// //////////////////////////////////////////////////////////// - void rotate(float angle); + void rotate(Angle angle); //////////////////////////////////////////////////////////// /// \brief Scale the object @@ -232,7 +232,7 @@ private: //////////////////////////////////////////////////////////// Vector2f m_origin; //!< Origin of translation/rotation/scaling of the object Vector2f m_position; //!< Position of the object in the 2D world - float m_rotation; //!< Orientation of the object, in degrees + Angle m_rotation; //!< Orientation of the object Vector2f m_scale; //!< Scale of the object mutable Transform m_transform; //!< Combined transformation of the object mutable bool m_transformNeedUpdate; //!< Does the transform need to be recomputed? diff --git a/include/SFML/Graphics/View.hpp b/include/SFML/Graphics/View.hpp index 15e42d415..10669680c 100644 --- a/include/SFML/Graphics/View.hpp +++ b/include/SFML/Graphics/View.hpp @@ -105,12 +105,12 @@ public: /// /// The default rotation of a view is 0 degree. /// - /// \param angle New angle, in degrees + /// \param angle New angle /// /// \see getRotation /// //////////////////////////////////////////////////////////// - void setRotation(float angle); + void setRotation(Angle angle); //////////////////////////////////////////////////////////// /// \brief Set the target viewport @@ -164,12 +164,12 @@ public: //////////////////////////////////////////////////////////// /// \brief Get the current orientation of the view /// - /// \return Rotation angle of the view, in degrees + /// \return Rotation angle of the view /// /// \see setRotation /// //////////////////////////////////////////////////////////// - float getRotation() const; + Angle getRotation() const; //////////////////////////////////////////////////////////// /// \brief Get the target viewport rectangle of the view @@ -205,12 +205,12 @@ public: //////////////////////////////////////////////////////////// /// \brief Rotate the view relatively to its current orientation /// - /// \param angle Angle to rotate, in degrees + /// \param angle Angle to rotate /// /// \see setRotation, move, zoom /// //////////////////////////////////////////////////////////// - void rotate(float angle); + void rotate(Angle angle); //////////////////////////////////////////////////////////// /// \brief Resize the view rectangle relatively to its current size @@ -260,7 +260,7 @@ private: //////////////////////////////////////////////////////////// Vector2f m_center; //!< Center of the view, in scene coordinates Vector2f m_size; //!< Size of the view, in scene coordinates - float m_rotation; //!< Angle of rotation of the view rectangle, in degrees + Angle m_rotation; //!< Angle of rotation of the view rectangle FloatRect m_viewport; //!< Viewport rectangle, expressed as a factor of the render-target's size mutable Transform m_transform; //!< Precomputed projection transform corresponding to the view mutable Transform m_inverseTransform; //!< Precomputed inverse projection transform corresponding to the view @@ -307,7 +307,7 @@ private: /// view.reset(sf::FloatRect(100, 100, 400, 200)); /// /// // Rotate it by 45 degrees -/// view.rotate(45); +/// view.rotate(sf::degrees(45)); /// /// // Set its target viewport to be half of the window /// view.setViewport(sf::FloatRect(0.f, 0.f, 0.5f, 1.f)); diff --git a/include/SFML/System.hpp b/include/SFML/System.hpp index 6524bac59..ee17d90d6 100644 --- a/include/SFML/System.hpp +++ b/include/SFML/System.hpp @@ -30,6 +30,7 @@ //////////////////////////////////////////////////////////// #include +#include #include #include #include diff --git a/include/SFML/System/Angle.hpp b/include/SFML/System/Angle.hpp new file mode 100644 index 000000000..306dcf58f --- /dev/null +++ b/include/SFML/System/Angle.hpp @@ -0,0 +1,519 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2022 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#ifndef SFML_ANGLE_HPP +#define SFML_ANGLE_HPP + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +/// \brief Represents an angle value. +/// +//////////////////////////////////////////////////////////// +class Angle +{ +public: + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + /// Sets the angle value to zero. + /// + //////////////////////////////////////////////////////////// + constexpr Angle(); + + //////////////////////////////////////////////////////////// + /// \brief Return the angle's value in degrees + /// + /// \return Angle in degrees + /// + /// \see asRadians + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] constexpr float asDegrees() const; + + //////////////////////////////////////////////////////////// + /// \brief Return the angle's value in radians + /// + /// \return Angle in radians + /// + /// \see asDegrees + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] constexpr float asRadians() const; + + //////////////////////////////////////////////////////////// + /// \brief Wrap to a range such that -180° <= angle < 180° + /// + /// Similar to a modulo operation, this returns a copy of the angle + /// constrained to the range [-180°, 180°) == [-Pi, Pi). + /// The resulting angle represents a rotation which is equivalent to *this. + /// + /// The name "signed" originates from the similarity to signed integers: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
signedunsigned
char[-128, 128)[0, 256)
Angle[-180°, 180°)[0°, 360°)
+ /// + /// \return Signed angle, wrapped to [-180°, 180°) + /// + /// \see wrapUnsigned + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] constexpr Angle wrapSigned() const; + + //////////////////////////////////////////////////////////// + /// \brief Wrap to a range such that 0° <= angle < 360° + /// + /// Similar to a modulo operation, this returns a copy of the angle + /// constrained to the range [0°, 360°) == [0, Tau) == [0, 2*Pi). + /// The resulting angle represents a rotation which is equivalent to *this. + /// + /// The name "unsigned" originates from the similarity to unsigned integers: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
signedunsigned
char[-128, 128)[0, 256)
Angle[-180°, 180°)[0°, 360°)
+ /// + /// \return Unsigned angle, wrapped to [0°, 360°) + /// + /// \see wrapSigned + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] constexpr Angle wrapUnsigned() const; + + //////////////////////////////////////////////////////////// + // Static member data + //////////////////////////////////////////////////////////// + SFML_SYSTEM_API static const Angle Zero; //!< Predefined 0 degree angle value + +private: + + friend constexpr Angle degrees(float angle); + friend constexpr Angle radians(float angle); + + //////////////////////////////////////////////////////////// + /// \brief Construct from a number of degrees + /// + /// This function is internal. To construct angle values, + /// use sf::radians or sf::degrees instead. + /// + /// \param degrees Angle in degrees + /// + //////////////////////////////////////////////////////////// + constexpr explicit Angle(float degrees); + +private: + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + float m_degrees; //!< Angle value stored as degrees +}; + +//////////////////////////////////////////////////////////// +/// \brief Construct an angle value from a number of degrees +/// +/// \param angle Number of degrees +/// +/// \return Angle value constructed from the number of degrees +/// +/// \see radians +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle degrees(float angle); + +//////////////////////////////////////////////////////////// +/// \brief Construct an angle value from a number of radians +/// +/// \param angle Number of radians +/// +/// \return Angle value constructed from the number of radians +/// +/// \see degrees +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle radians(float angle); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of == operator to compare two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return True if both angle values are equal +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr bool operator ==(Angle left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of != operator to compare two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return True if both angle values are different +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr bool operator !=(Angle left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of < operator to compare two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return True if \a left is less than \a right +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr bool operator <(Angle left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of > operator to compare two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return True if \a left is greater than \a right +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr bool operator >(Angle left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of <= operator to compare two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return True if \a left is less than or equal to \a right +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr bool operator <=(Angle left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of >= operator to compare two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return True if \a left is greater than or equal to \a right +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr bool operator >=(Angle left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of unary - operator to negate an angle value. +/// +/// Represents a rotation in the opposite direction. +/// +/// \param right Right operand (an angle) +/// +/// \return Negative of the angle value +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator -(Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary + operator to add two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return Sum of the two angle values +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator +(Angle left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary += operator to add/assign two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return Sum of the two angle values +/// +//////////////////////////////////////////////////////////// +constexpr Angle& operator +=(Angle& left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary - operator to subtract two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return Difference of the two angle values +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator -(Angle left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary -= operator to subtract/assign two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return Difference of the two angle values +/// +//////////////////////////////////////////////////////////// +constexpr Angle& operator -=(Angle& left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary * operator to scale an angle value +/// +/// \param left Left operand (an angle) +/// \param right Right operand (a number) +/// +/// \return \a left multiplied by \a right +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator *(Angle left, float right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary * operator to scale an angle value +/// +/// \param left Left operand (a number) +/// \param right Right operand (an angle) +/// +/// \return \a left multiplied by \a right +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator *(float left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary *= operator to scale/assign an angle value +/// +/// \param left Left operand (an angle) +/// \param right Right operand (a number) +/// +/// \return \a left multiplied by \a right +/// +//////////////////////////////////////////////////////////// +constexpr Angle& operator *=(Angle& left, float right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary / operator to scale an angle value +/// +/// \param left Left operand (an angle) +/// \param right Right operand (a number) +/// +/// \return \a left divided by \a right +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator /(Angle left, float right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary /= operator to scale/assign an angle value +/// +/// \param left Left operand (an angle) +/// \param right Right operand (a number) +/// +/// \return \a left divided by \a right +/// +//////////////////////////////////////////////////////////// +constexpr Angle& operator /=(Angle& left, float right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary / operator to compute the ratio of two angle values +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return \a left divided by \a right +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr float operator /(Angle left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary % operator to compute modulo of an angle value. +/// +/// Right hand angle must be greater than zero. +/// +/// Examples: +/// \code +/// sf::degrees(90) % sf::degrees(40) // 10 degrees +/// sf::degrees(-90) % sf::degrees(40) // 30 degrees (not -10) +/// \endcode +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return \a left modulo \a right +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator %(Angle left, Angle right); + +//////////////////////////////////////////////////////////// +/// \relates Angle +/// \brief Overload of binary %= operator to compute/assign remainder of an angle value +/// +/// \param left Left operand (an angle) +/// \param right Right operand (an angle) +/// +/// \return \a left modulo \a right +/// +//////////////////////////////////////////////////////////// +constexpr Angle& operator %=(Angle& left, Angle right); + +namespace Literals +{ + +//////////////////////////////////////////////////////////// +/// \relates sf::Angle +/// \brief User defined literal for angles in degrees, e.g. 10.5_deg +/// +/// \param angle Angle in degrees +/// +/// \return \a Angle +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator "" _deg(long double angle); + +//////////////////////////////////////////////////////////// +/// \relates sf::Angle +/// \brief User defined literal for angles in degrees, e.g. 90_deg +/// +/// \param angle Angle in degrees +/// +/// \return \a Angle +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator "" _deg(unsigned long long int angle); + +//////////////////////////////////////////////////////////// +/// \relates sf::Angle +/// \brief User defined literal for angles in radians, e.g. 0.1_rad +/// +/// \param angle Angle in radians +/// +/// \return \a Angle +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator "" _rad(long double angle); + +//////////////////////////////////////////////////////////// +/// \relates sf::Angle +/// \brief User defined literal for angles in radians, e.g. 2_rad +/// +/// \param angle Angle in radians +/// +/// \return \a Angle +/// +//////////////////////////////////////////////////////////// +[[nodiscard]] constexpr Angle operator "" _rad(unsigned long long int angle); + +} // namespace Literals + +#include + +} // namespace sf + + +#endif // SFML_ANGLE_HPP + + +//////////////////////////////////////////////////////////// +/// \class sf::Angle +/// \ingroup system +/// +/// sf::Angle encapsulates an angle value in a flexible way. +/// It allows for defining an angle value either as a number +/// of degrees or radians. It also works the other way +/// around. You can read an angle value as either a number +/// of degrees or radians. +/// +/// By using such a flexible interface, the API doesn't +/// impose any fixed type or unit for angle values and lets +/// the user choose their own preferred representation. +/// +/// Angle values support the usual mathematical operations. +/// You can add or subtract two angles, multiply or divide +/// an angle by a number, compare two angles, etc. +/// +/// Usage example: +/// \code +/// sf::Angle a1 = sf::degrees(90); +/// float radians = a1.asRadians(); // 1.5708f +/// +/// sf::Angle a2 = sf::radians(3.141592654f); +/// float degrees = a2.asDegrees(); // 180.0f +/// +/// using namespace sf::Literals; +/// sf::Angle a3 = 10_deg; // 10 degrees +/// sf::Angle a4 = 1.5_deg; // 1.5 degrees +/// sf::Angle a5 = 1_rad; // 1 radians +/// sf::Angle a6 = 3.14_rad; // 3.14 radians +/// \endcode +/// +//////////////////////////////////////////////////////////// diff --git a/include/SFML/System/Angle.inl b/include/SFML/System/Angle.inl new file mode 100644 index 000000000..c71d6a580 --- /dev/null +++ b/include/SFML/System/Angle.inl @@ -0,0 +1,259 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2022 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +namespace priv +{ + constexpr float pi = 3.141592654f; + + constexpr float positiveRemainder(float a, float b) + { + assert(b > 0.0f); + const float val = a - static_cast(static_cast(a / b)) * b; + if (val >= 0.f) + return val; + else + return val + b; + } +} + + +//////////////////////////////////////////////////////////// +constexpr Angle::Angle() : +m_degrees(0.0f) +{ +} + + +//////////////////////////////////////////////////////////// +constexpr float Angle::asDegrees() const +{ + return m_degrees; +} + + +//////////////////////////////////////////////////////////// +constexpr float Angle::asRadians() const +{ + return m_degrees * (priv::pi / 180); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle Angle::wrapSigned() const +{ + return degrees(priv::positiveRemainder(m_degrees + 180, 360) - 180); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle Angle::wrapUnsigned() const +{ + return degrees(priv::positiveRemainder(m_degrees, 360)); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle::Angle(float degrees) : +m_degrees(degrees) +{ +} + + +//////////////////////////////////////////////////////////// +constexpr Angle degrees(float angle) +{ + return Angle(angle); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle radians(float angle) +{ + return Angle(angle * (180 / priv::pi)); +} + + +//////////////////////////////////////////////////////////// +constexpr bool operator ==(Angle left, Angle right) +{ + return left.asDegrees() == right.asDegrees(); +} + + +//////////////////////////////////////////////////////////// +constexpr bool operator !=(Angle left, Angle right) +{ + return left.asDegrees() != right.asDegrees(); +} + + +//////////////////////////////////////////////////////////// +constexpr bool operator <(Angle left, Angle right) +{ + return left.asDegrees() < right.asDegrees(); +} + + +//////////////////////////////////////////////////////////// +constexpr bool operator >(Angle left, Angle right) +{ + return left.asDegrees() > right.asDegrees(); +} + + +//////////////////////////////////////////////////////////// +constexpr bool operator <=(Angle left, Angle right) +{ + return left.asDegrees() <= right.asDegrees(); +} + + +//////////////////////////////////////////////////////////// +constexpr bool operator >=(Angle left, Angle right) +{ + return left.asDegrees() >= right.asDegrees(); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle operator -(Angle right) +{ + return degrees(-right.asDegrees()); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle operator +(Angle left, Angle right) +{ + return degrees(left.asDegrees() + right.asDegrees()); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle& operator +=(Angle& left, Angle right) +{ + return left = left + right; +} + + +//////////////////////////////////////////////////////////// +constexpr Angle operator -(Angle left, Angle right) +{ + return degrees(left.asDegrees() - right.asDegrees()); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle& operator -=(Angle& left, Angle right) +{ + return left = left - right; +} + + +//////////////////////////////////////////////////////////// +constexpr Angle operator *(Angle left, float right) +{ + return degrees(left.asDegrees() * right); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle operator *(float left, Angle right) +{ + return right * left; +} + + +//////////////////////////////////////////////////////////// +constexpr Angle& operator *=(Angle& left, float right) +{ + return left = left * right; +} + + +//////////////////////////////////////////////////////////// +constexpr Angle operator /(Angle left, float right) +{ + return degrees(left.asDegrees() / right); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle& operator /=(Angle& left, float right) +{ + return left = left / right; +} + + +//////////////////////////////////////////////////////////// +constexpr float operator /(Angle left, Angle right) +{ + return left.asDegrees() / right.asDegrees(); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle operator %(Angle left, Angle right) +{ + return degrees(priv::positiveRemainder(left.asDegrees(), right.asDegrees())); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle& operator %=(Angle& left, Angle right) +{ + return left = left % right; +} + +namespace Literals +{ + +//////////////////////////////////////////////////////////// +constexpr Angle operator "" _deg(long double angle) +{ + return degrees(static_cast(angle)); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle operator "" _deg(unsigned long long angle) +{ + return degrees(static_cast(angle)); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle operator "" _rad(long double angle) +{ + return radians(static_cast(angle)); +} + + +//////////////////////////////////////////////////////////// +constexpr Angle operator "" _rad(unsigned long long angle) +{ + return radians(static_cast(angle)); +} + +} // namespace Literals diff --git a/src/SFML/Graphics/CircleShape.cpp b/src/SFML/Graphics/CircleShape.cpp index a3183d654..9d1c0596b 100644 --- a/src/SFML/Graphics/CircleShape.cpp +++ b/src/SFML/Graphics/CircleShape.cpp @@ -72,11 +72,10 @@ std::size_t CircleShape::getPointCount() const //////////////////////////////////////////////////////////// Vector2f CircleShape::getPoint(std::size_t index) const { - static const float pi = 3.141592654f; - - float angle = static_cast(index) * 2.f * pi / static_cast(m_pointCount) - pi / 2.f; - float x = std::cos(angle) * m_radius; - float y = std::sin(angle) * m_radius; + Angle angle = static_cast(index) / static_cast(m_pointCount) * sf::degrees(360) - sf::degrees(90); + float rad = angle.asRadians(); + float x = std::cos(rad) * m_radius; + float y = std::sin(rad) * m_radius; return Vector2f(m_radius + x, m_radius + y); } diff --git a/src/SFML/Graphics/Text.cpp b/src/SFML/Graphics/Text.cpp index 6c661e5ed..374a1eb1e 100644 --- a/src/SFML/Graphics/Text.cpp +++ b/src/SFML/Graphics/Text.cpp @@ -405,7 +405,7 @@ void Text::ensureGeometryUpdate() const bool isBold = m_style & Bold; bool isUnderlined = m_style & Underlined; bool isStrikeThrough = m_style & StrikeThrough; - float italicShear = (m_style & Italic) ? 0.209f : 0.f; // 12 degrees in radians + float italicShear = (m_style & Italic) ? sf::degrees(12).asRadians() : 0.f; float underlineOffset = m_font->getUnderlinePosition(m_characterSize); float underlineThickness = m_font->getUnderlineThickness(m_characterSize); diff --git a/src/SFML/Graphics/Transform.cpp b/src/SFML/Graphics/Transform.cpp index d37f8370c..804c916e2 100644 --- a/src/SFML/Graphics/Transform.cpp +++ b/src/SFML/Graphics/Transform.cpp @@ -163,9 +163,9 @@ Transform& Transform::translate(const Vector2f& offset) //////////////////////////////////////////////////////////// -Transform& Transform::rotate(float angle) +Transform& Transform::rotate(Angle angle) { - float rad = angle * 3.141592654f / 180.f; + float rad = angle.asRadians(); float cos = std::cos(rad); float sin = std::sin(rad); @@ -178,9 +178,9 @@ Transform& Transform::rotate(float angle) //////////////////////////////////////////////////////////// -Transform& Transform::rotate(float angle, const Vector2f& center) +Transform& Transform::rotate(Angle angle, const Vector2f& center) { - float rad = angle * 3.141592654f / 180.f; + float rad = angle.asRadians(); float cos = std::cos(rad); float sin = std::sin(rad); diff --git a/src/SFML/Graphics/Transformable.cpp b/src/SFML/Graphics/Transformable.cpp index 2c12420e6..7bac21ed1 100644 --- a/src/SFML/Graphics/Transformable.cpp +++ b/src/SFML/Graphics/Transformable.cpp @@ -35,7 +35,7 @@ namespace sf Transformable::Transformable() : m_origin (0, 0), m_position (0, 0), -m_rotation (0), +m_rotation (), m_scale (1, 1), m_transform (), m_transformNeedUpdate (true), @@ -61,11 +61,9 @@ void Transformable::setPosition(const Vector2f& position) //////////////////////////////////////////////////////////// -void Transformable::setRotation(float angle) +void Transformable::setRotation(Angle angle) { - m_rotation = std::fmod(angle, 360.f); - if (m_rotation < 0) - m_rotation += 360.f; + m_rotation = angle.wrapUnsigned(); m_transformNeedUpdate = true; m_inverseTransformNeedUpdate = true; @@ -98,7 +96,7 @@ const Vector2f& Transformable::getPosition() const //////////////////////////////////////////////////////////// -float Transformable::getRotation() const +Angle Transformable::getRotation() const { return m_rotation; } @@ -126,7 +124,7 @@ void Transformable::move(const Vector2f& offset) //////////////////////////////////////////////////////////// -void Transformable::rotate(float angle) +void Transformable::rotate(Angle angle) { setRotation(m_rotation + angle); } @@ -145,7 +143,7 @@ const Transform& Transformable::getTransform() const // Recompute the combined transform if needed if (m_transformNeedUpdate) { - float angle = -m_rotation * 3.141592654f / 180.f; + float angle = -m_rotation.asRadians(); float cosine = std::cos(angle); float sine = std::sin(angle); float sxc = m_scale.x * cosine; diff --git a/src/SFML/Graphics/View.cpp b/src/SFML/Graphics/View.cpp index 26b51086b..e3e254ec7 100644 --- a/src/SFML/Graphics/View.cpp +++ b/src/SFML/Graphics/View.cpp @@ -35,7 +35,7 @@ namespace sf View::View() : m_center (), m_size (), -m_rotation (0), +m_rotation (), m_viewport ({0, 0}, {1, 1}), m_transformUpdated (false), m_invTransformUpdated(false) @@ -48,7 +48,7 @@ m_invTransformUpdated(false) View::View(const FloatRect& rectangle) : m_center (), m_size (), -m_rotation (0), +m_rotation (), m_viewport ({0, 0}, {1, 1}), m_transformUpdated (false), m_invTransformUpdated(false) @@ -61,7 +61,7 @@ m_invTransformUpdated(false) View::View(const Vector2f& center, const Vector2f& size) : m_center (center), m_size (size), -m_rotation (0), +m_rotation (), m_viewport ({0, 0}, {1, 1}), m_transformUpdated (false), m_invTransformUpdated(false) @@ -98,11 +98,9 @@ void View::setSize(const Vector2f& size) //////////////////////////////////////////////////////////// -void View::setRotation(float angle) +void View::setRotation(Angle angle) { - m_rotation = std::fmod(angle, 360.f); - if (m_rotation < 0) - m_rotation += 360.f; + m_rotation = angle.wrapUnsigned(); m_transformUpdated = false; m_invTransformUpdated = false; @@ -123,7 +121,7 @@ void View::reset(const FloatRect& rectangle) m_center.y = rectangle.top + rectangle.height / 2.f; m_size.x = rectangle.width; m_size.y = rectangle.height; - m_rotation = 0; + m_rotation = Angle::Zero; m_transformUpdated = false; m_invTransformUpdated = false; @@ -145,7 +143,7 @@ const Vector2f& View::getSize() const //////////////////////////////////////////////////////////// -float View::getRotation() const +Angle View::getRotation() const { return m_rotation; } @@ -166,7 +164,7 @@ void View::move(const Vector2f& offset) //////////////////////////////////////////////////////////// -void View::rotate(float angle) +void View::rotate(Angle angle) { setRotation(m_rotation + angle); } @@ -186,7 +184,7 @@ const Transform& View::getTransform() const if (!m_transformUpdated) { // Rotation components - float angle = m_rotation * 3.141592654f / 180.f; + float angle = m_rotation.asRadians(); float cosine = std::cos(angle); float sine = std::sin(angle); float tx = -m_center.x * cosine - m_center.y * sine + m_center.x; diff --git a/src/SFML/System/Angle.cpp b/src/SFML/System/Angle.cpp new file mode 100644 index 000000000..59feda734 --- /dev/null +++ b/src/SFML/System/Angle.cpp @@ -0,0 +1,38 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2022 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +// Static member data +//////////////////////////////////////////////////////////// +const Angle Angle::Zero; + +} // namespace sf diff --git a/src/SFML/System/CMakeLists.txt b/src/SFML/System/CMakeLists.txt index d19affc0a..9f28fdfe6 100644 --- a/src/SFML/System/CMakeLists.txt +++ b/src/SFML/System/CMakeLists.txt @@ -4,6 +4,8 @@ set(SRCROOT ${PROJECT_SOURCE_DIR}/src/SFML/System) # all source files set(SRC + ${SRCROOT}/Angle.cpp + ${INCROOT}/Angle.hpp ${SRCROOT}/Clock.cpp ${INCROOT}/Clock.hpp ${SRCROOT}/Err.cpp diff --git a/src/SFML/Window/iOS/SensorImpl.mm b/src/SFML/Window/iOS/SensorImpl.mm index 9f509c3d2..fc993fb53 100644 --- a/src/SFML/Window/iOS/SensorImpl.mm +++ b/src/SFML/Window/iOS/SensorImpl.mm @@ -25,6 +25,7 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include #include #include @@ -35,7 +36,7 @@ namespace float toDegrees(float radians) { - return radians * 180.f / 3.141592654f; + return sf::radians(radians).asDegrees(); } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 43dde5d0f..164143366 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,7 @@ target_compile_features(sfml-test-main PRIVATE cxx_std_17) # System is always built SET(SYSTEM_SRC + "${SRCROOT}/System/Angle.cpp" "${SRCROOT}/System/Clock.cpp" "${SRCROOT}/System/FileInputStream.cpp" "${SRCROOT}/System/Time.cpp" diff --git a/test/Graphics/Transform.cpp b/test/Graphics/Transform.cpp index 72e9ce352..e785ef454 100644 --- a/test/Graphics/Transform.cpp +++ b/test/Graphics/Transform.cpp @@ -123,7 +123,7 @@ TEST_CASE("sf::Transform class - [graphics]") SUBCASE("Around origin") { sf::Transform transform; - transform.rotate(90); + transform.rotate(sf::degrees(90)); CHECK(transform.getMatrix()[0] == Approx(0)); CHECK(transform.getMatrix()[4] == Approx(-1)); CHECK(transform.getMatrix()[12] == Approx(0)); @@ -138,7 +138,7 @@ TEST_CASE("sf::Transform class - [graphics]") SUBCASE("Around custom point") { sf::Transform transform; - transform.rotate(90, {1.0f, 0.0f}); + transform.rotate(sf::degrees(90), {1.0f, 0.0f}); CHECK(transform.getMatrix()[0] == Approx(0)); CHECK(transform.getMatrix()[4] == Approx(-1)); CHECK(transform.getMatrix()[12] == Approx(1)); diff --git a/test/System/Angle.cpp b/test/System/Angle.cpp new file mode 100644 index 000000000..f4c249cbc --- /dev/null +++ b/test/System/Angle.cpp @@ -0,0 +1,302 @@ +#include +#include "SystemUtil.hpp" + +#include + +using doctest::Approx; + +TEST_CASE("sf::Angle class - [system]") +{ + SUBCASE("Construction") + { + SUBCASE("Default constructor") + { + const sf::Angle angle; + CHECK(angle.asDegrees() == 0); + CHECK(angle.asRadians() == 0); + } + + SUBCASE("wrapSigned()") + { + CHECK(sf::Angle::Zero.wrapSigned() == sf::Angle::Zero); + CHECK(sf::degrees(0).wrapSigned() == sf::degrees(0)); + CHECK(sf::degrees(1).wrapSigned() == sf::degrees(1)); + CHECK(sf::degrees(-1).wrapSigned() == sf::degrees(-1)); + CHECK(sf::degrees(90).wrapSigned() == sf::degrees(90)); + CHECK(sf::degrees(-90).wrapSigned() == sf::degrees(-90)); + CHECK(sf::degrees(180).wrapSigned() == sf::degrees(-180)); + CHECK(sf::degrees(-180).wrapSigned() == sf::degrees(-180)); + CHECK(sf::degrees(360).wrapSigned() == sf::degrees(0)); + CHECK(sf::degrees(-360).wrapSigned() == sf::degrees(0)); + CHECK(sf::degrees(720).wrapSigned() == sf::degrees(0)); + CHECK(sf::degrees(-720).wrapSigned() == sf::degrees(0)); + } + + SUBCASE("wrapUnsigned()") + { + CHECK(sf::Angle::Zero.wrapUnsigned() == sf::Angle::Zero); + CHECK(sf::degrees(0).wrapUnsigned() == sf::degrees(0)); + CHECK(sf::degrees(1).wrapUnsigned() == sf::degrees(1)); + CHECK(sf::degrees(-1).wrapUnsigned() == sf::degrees(359)); + CHECK(sf::degrees(90).wrapUnsigned() == sf::degrees(90)); + CHECK(sf::degrees(-90).wrapUnsigned() == sf::degrees(270)); + CHECK(sf::degrees(180).wrapUnsigned() == sf::degrees(180)); + CHECK(sf::degrees(-180).wrapUnsigned() == sf::degrees(180)); + CHECK(sf::degrees(360).wrapUnsigned() == sf::degrees(0)); + CHECK(sf::degrees(-360).wrapUnsigned() == sf::degrees(0)); + CHECK(sf::degrees(720).wrapUnsigned() == sf::degrees(0)); + CHECK(sf::degrees(-720).wrapUnsigned() == sf::degrees(0)); + } + + SUBCASE("degrees()") + { + const sf::Angle angle = sf::degrees(15); + CHECK(angle == sf::degrees(15)); + CHECK(angle.asRadians() == Approx(0.26179939f)); + + const sf::Angle bigAngle = sf::degrees(1000); + CHECK(bigAngle == sf::degrees(1000)); + CHECK(bigAngle.asRadians() == Approx(17.453293f)); + + const sf::Angle bigNegativeAngle = sf::degrees(-4321); + CHECK(bigNegativeAngle == sf::degrees(-4321)); + CHECK(bigNegativeAngle.asRadians() == Approx(-75.415677f)); + } + + SUBCASE("radians()") + { + const sf::Angle angle = sf::radians(1); + CHECK(angle.asDegrees() == Approx(57.2957795f)); + CHECK(angle.asRadians() == Approx(1.0f)); + + const sf::Angle bigAngle = sf::radians(72); + CHECK(bigAngle.asDegrees() == Approx(4125.29612f)); + CHECK(bigAngle.asRadians() == Approx(72.0f)); + + const sf::Angle bigNegativeAngle = sf::radians(-200); + CHECK(bigNegativeAngle.asDegrees() == Approx(-11459.1559f)); + CHECK(bigNegativeAngle.asRadians() == Approx(-200.0f)); + } + } + + SUBCASE("Constants") + { + CHECK(sf::Angle::Zero.asDegrees() == 0); + CHECK(sf::Angle::Zero.asRadians() == 0); + } + + SUBCASE("Operators") + { + SUBCASE("operator==") + { + CHECK(sf::Angle() == sf::Angle()); + CHECK(sf::Angle() == sf::Angle::Zero); + CHECK(sf::Angle() == sf::degrees(0)); + CHECK(sf::Angle() == sf::radians(0)); + CHECK(sf::degrees(0) == sf::radians(0)); + CHECK(sf::degrees(15) == sf::degrees(15)); + CHECK(sf::radians(1) == sf::radians(1)); + CHECK(sf::degrees(360) == sf::degrees(360)); + CHECK(sf::degrees(720) == sf::degrees(720)); + } + + SUBCASE("operator!=") + { + CHECK(sf::Angle() != sf::radians(2)); + CHECK(sf::degrees(1) != sf::radians(1)); + CHECK(sf::radians(0) != sf::radians(0.1f)); + } + + SUBCASE("operator<") + { + CHECK(sf::radians(0) < sf::degrees(0.1f)); + CHECK(sf::degrees(0) < sf::radians(0.1f)); + CHECK(sf::radians(-0.1f) < sf::radians(0)); + CHECK(sf::degrees(-0.1f) < sf::degrees(0)); + } + + SUBCASE("operator>") + { + CHECK(sf::radians(0.1f) > sf::degrees(0)); + CHECK(sf::degrees(0.1f) > sf::radians(0)); + CHECK(sf::radians(0) > sf::radians(-0.1f)); + CHECK(sf::degrees(0) > sf::degrees(-0.1f)); + } + + SUBCASE("operator<=") + { + CHECK(sf::radians(0) <= sf::degrees(0.1f)); + CHECK(sf::degrees(0) <= sf::radians(0.1f)); + CHECK(sf::radians(-0.1f) <= sf::radians(0)); + CHECK(sf::degrees(-0.1f) <= sf::degrees(0)); + + CHECK(sf::Angle() <= sf::Angle()); + CHECK(sf::Angle() <= sf::Angle::Zero); + CHECK(sf::Angle() <= sf::degrees(0)); + CHECK(sf::Angle() <= sf::radians(0)); + CHECK(sf::degrees(0) <= sf::radians(0)); + CHECK(sf::degrees(15) <= sf::degrees(15)); + CHECK(sf::radians(1) <= sf::radians(1)); + CHECK(sf::degrees(360) <= sf::degrees(360)); + CHECK(sf::degrees(720) <= sf::degrees(720)); + } + + SUBCASE("operator>=") + { + CHECK(sf::radians(0.1f) >= sf::degrees(0)); + CHECK(sf::degrees(0.1f) >= sf::radians(0)); + CHECK(sf::radians(0) >= sf::radians(-0.1f)); + CHECK(sf::degrees(0) >= sf::degrees(-0.1f)); + + CHECK(sf::Angle() >= sf::Angle()); + CHECK(sf::Angle() >= sf::Angle::Zero); + CHECK(sf::Angle() >= sf::degrees(0)); + CHECK(sf::Angle() >= sf::radians(0)); + CHECK(sf::degrees(0) >= sf::radians(0)); + CHECK(sf::degrees(15) >= sf::degrees(15)); + CHECK(sf::radians(1) >= sf::radians(1)); + CHECK(sf::degrees(360) >= sf::degrees(360)); + CHECK(sf::degrees(720) >= sf::degrees(720)); + } + + SUBCASE("Unary operator-") + { + CHECK(-sf::Angle() == sf::Angle()); + CHECK(-sf::radians(-1) == sf::radians(1)); + CHECK(-sf::degrees(15) == sf::degrees(-15)); + CHECK(-sf::radians(1) == sf::radians(-1)); + } + + SUBCASE("operator+") + { + CHECK(sf::Angle() + sf::Angle() == sf::Angle()); + CHECK(sf::Angle::Zero + sf::radians(0.5f) == sf::radians(0.5f)); + CHECK(sf::radians(6) + sf::radians(0.5f) == sf::radians(6.5f)); + CHECK(sf::radians(10) + sf::radians(0.5f) == sf::radians(10.5f)); + CHECK(sf::degrees(360) + sf::degrees(360) == sf::degrees(720)); + } + + SUBCASE("operator+=") + { + sf::Angle angle = sf::degrees(-15); + angle += sf::degrees(15); + CHECK(angle == sf::degrees(0)); + angle += sf::radians(10); + CHECK(angle == sf::radians(10)); + } + + SUBCASE("operator-") + { + CHECK(sf::Angle() - sf::Angle() == sf::Angle()); + CHECK(sf::radians(1) - sf::radians(0.5f) == sf::radians(0.5f)); + CHECK(sf::Angle::Zero - sf::radians(0.5f) == sf::radians(-0.5f)); + CHECK(sf::degrees(900) - sf::degrees(1) == sf::degrees(899)); + } + + SUBCASE("operator-=") + { + sf::Angle angle = sf::degrees(15); + angle -= sf::degrees(15); + CHECK(angle == sf::degrees(0)); + angle -= sf::radians(10); + CHECK(angle == sf::radians(-10)); + } + + SUBCASE("operator*") + { + CHECK(sf::radians(0) * 10 == sf::Angle::Zero); + CHECK(sf::degrees(10) * 2.5f == sf::degrees(25)); + CHECK(sf::degrees(100) * 10.0f == sf::degrees(1000)); + + CHECK(10 * sf::radians(0) == sf::Angle::Zero); + CHECK(2.5f * sf::degrees(10) == sf::degrees(25)); + CHECK(10.0f * sf::degrees(100) == sf::degrees(1000)); + } + + SUBCASE("operator*=") + { + sf::Angle angle = sf::degrees(1); + angle *= 10; + CHECK(angle == sf::degrees(10)); + } + + SUBCASE("operator/") + { + CHECK(sf::Angle::Zero / 10 == sf::Angle::Zero); + CHECK(sf::degrees(10) / 2.5f == sf::degrees(4)); + CHECK(sf::radians(12) / 3 == sf::radians(4)); + + CHECK(sf::Angle::Zero / sf::degrees(1) == 0); + CHECK(sf::degrees(10) / sf::degrees(10) == 1); + CHECK(sf::radians(10) / sf::radians(2) == Approx(5.0f)); + } + + SUBCASE("operator/=") + { + sf::Angle angle = sf::degrees(60); + angle /= 5; + CHECK(angle == sf::degrees(12)); + } + + SUBCASE("operator%") + { + CHECK(sf::Angle::Zero % sf::radians(0.5f) == sf::Angle::Zero); + CHECK(sf::radians(10) % sf::radians(1) == sf::radians(0)); + CHECK(sf::degrees(90) % sf::degrees(30) == sf::degrees(0)); + CHECK(sf::degrees(90) % sf::degrees(40) == sf::degrees(10)); + CHECK(sf::degrees(-90) % sf::degrees(30) == sf::degrees(0)); + CHECK(sf::degrees(-90) % sf::degrees(40) == sf::degrees(30)); + } + + SUBCASE("operator%=") + { + sf::Angle angle = sf::degrees(59); + angle %= sf::degrees(10); + CHECK(angle == sf::degrees(9)); + } + + SUBCASE("operator _deg") + { + using namespace sf::Literals; + CHECK(0.0_deg == sf::Angle::Zero); + CHECK(1.0_deg == sf::degrees(1)); + CHECK(-1.0_deg == sf::degrees(-1)); + CHECK(3.14_deg == sf::degrees(3.14f)); + CHECK(-3.14_deg == sf::degrees(-3.14f)); + + CHECK(0_deg == sf::Angle::Zero); + CHECK(1_deg == sf::degrees(1)); + CHECK(-1_deg == sf::degrees(-1)); + CHECK(100_deg == sf::degrees(100)); + CHECK(-100_deg == sf::degrees(-100)); + } + + SUBCASE("operator _rad") + { + using namespace sf::Literals; + CHECK(0.0_rad == sf::Angle::Zero); + CHECK(1.0_rad == sf::radians(1)); + CHECK(-1.0_rad ==sf::radians(-1)); + CHECK(3.14_rad == sf::radians(3.14f)); + CHECK(-3.14_rad == sf::radians(-3.14f)); + + CHECK(0_rad == sf::Angle::Zero); + CHECK(1_rad == sf::radians(1)); + CHECK(-1_rad == sf::radians(-1)); + CHECK(100_rad ==sf::radians(100)); + CHECK(-100_rad ==sf::radians(-100)); + } + } + + SUBCASE("Constexpr support") + { + constexpr auto result = [] + { + sf::Angle angle = sf::degrees(9); + angle %= sf::degrees(2); + return angle; + }(); + + static_assert(result == sf::degrees(1)); + } +} diff --git a/test/TestUtilities/SystemUtil.cpp b/test/TestUtilities/SystemUtil.cpp index c1c14ea44..a6f862b5c 100644 --- a/test/TestUtilities/SystemUtil.cpp +++ b/test/TestUtilities/SystemUtil.cpp @@ -1,5 +1,6 @@ #include "SystemUtil.hpp" +#include #include #include @@ -8,13 +9,22 @@ #include #endif // !defined(__GNUC__) || (__GNUC__ >= 9) +#include #include +#include +#include #include #include -#include namespace sf { + std::ostream& operator <<(std::ostream& os, const sf::Angle& angle) + { + os << std::fixed << std::setprecision(std::numeric_limits::max_digits10); + os << angle.asDegrees() << " deg"; + return os; + } + std::ostream& operator <<(std::ostream& os, const sf::String& string) { os << string.toAnsiString(); diff --git a/test/TestUtilities/SystemUtil.hpp b/test/TestUtilities/SystemUtil.hpp index 1c0c1b685..5b30f7cad 100644 --- a/test/TestUtilities/SystemUtil.hpp +++ b/test/TestUtilities/SystemUtil.hpp @@ -16,9 +16,11 @@ // String conversions for doctest framework namespace sf { + class Angle; class String; class Time; + std::ostream& operator <<(std::ostream& os, const sf::Angle& angle); std::ostream& operator <<(std::ostream& os, const sf::String& string); std::ostream& operator <<(std::ostream& os, sf::Time time);