From 70eeba5067d02b8e617989c90311a866a7bacd9f Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Tue, 25 Jan 2022 22:25:52 +0100 Subject: [PATCH] Add Vector2 extension methods (inspired from Thor) Adds vector algebra functionality as member functions for Vector2, with some methods limited to floating-point T. Also adds UnitX and UnitY constants for the two axis unit vectors. --- include/SFML/System/Vector2.hpp | 202 +++++++++++++++++++++++++++----- include/SFML/System/Vector2.inl | 133 +++++++++++++++++++++ 2 files changed, 303 insertions(+), 32 deletions(-) diff --git a/include/SFML/System/Vector2.hpp b/include/SFML/System/Vector2.hpp index 0dad5ff6..c0e3ae06 100644 --- a/include/SFML/System/Vector2.hpp +++ b/include/SFML/System/Vector2.hpp @@ -25,11 +25,16 @@ #ifndef SFML_VECTOR2_HPP #define SFML_VECTOR2_HPP +#include +#include +#include +#include + namespace sf { //////////////////////////////////////////////////////////// -/// \brief Utility template class for manipulating +/// \brief Class template for manipulating /// 2-dimensional vectors /// //////////////////////////////////////////////////////////// @@ -68,14 +73,143 @@ public: //////////////////////////////////////////////////////////// template constexpr explicit Vector2(const Vector2& vector); + + //////////////////////////////////////////////////////////// + /// \brief Length of the vector (floating-point). + /// + /// If you are not interested in the actual length, but only in comparisons, consider using lengthSq(). + /// + //////////////////////////////////////////////////////////// + T length() const; + + //////////////////////////////////////////////////////////// + /// \brief Square of vector's length. + /// + /// Suitable for comparisons, more efficient than length(). + /// + //////////////////////////////////////////////////////////// + constexpr T lengthSq() const; + + //////////////////////////////////////////////////////////// + /// \brief Vector with same direction but length 1 (floating-point). + /// + /// \pre \c *this is no zero vector. + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Vector2 normalized() const; + + //////////////////////////////////////////////////////////// + /// \brief Signed angle from \c *this to \c rhs (floating-point). + /// + /// \return The smallest angle which rotates \c *this in positive + /// or negative direction, until it has the same direction as \c rhs. + /// The result has a sign and lies in the range [-180, 180°). + /// \pre Neither \c *this nor \c rhs is a zero vector. + /// + //////////////////////////////////////////////////////////// + Angle angleTo(const Vector2& rhs) const; + + //////////////////////////////////////////////////////////// + /// \brief Signed angle from +X or (1,0) vector (floating-point). + /// + /// For example, the vector (1,0) corresponds to 0 degrees, (0,1) corresponds to 90 degrees. + /// + /// \return Angle in the range [-180°, 180°). + /// \pre This vector is no zero vector. + /// + //////////////////////////////////////////////////////////// + Angle angle() const; + + //////////////////////////////////////////////////////////// + /// \brief Rotate by angle \c phi (floating-point). + /// + /// Returns a vector with same length but different direction. + /// + /// In SFML's default coordinate system with +X right and +Y down, + /// this amounts to a clockwise rotation by \c phi. + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Vector2 rotatedBy(Angle phi) const; + + //////////////////////////////////////////////////////////// + /// \brief Projection of this vector onto \c axis (floating-point). + /// + /// \param axis Vector being projected onto. Need not be normalized. + /// \pre \c axis must not have length zero. + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] constexpr Vector2 projectedOnto(const Vector2& axis) const; + + //////////////////////////////////////////////////////////// + /// \brief Returns a perpendicular vector. + /// + /// Returns \c *this rotated by +90 degrees; (x,y) becomes (-y,x). + /// For example, the vector (1,0) is transformed to (0,1). + /// + /// In SFML's default coordinate system with +X right and +Y down, + /// this amounts to a clockwise rotation. + /// + //////////////////////////////////////////////////////////// + constexpr Vector2 perpendicular() const; + + //////////////////////////////////////////////////////////// + /// \brief Dot product of two 2D vectors. + /// + //////////////////////////////////////////////////////////// + constexpr T dot(const Vector2& rhs) const; + + //////////////////////////////////////////////////////////// + /// \brief Z component of the cross product of two 2D vectors. + /// + /// Treats the operands as 3D vectors, computes their cross product + /// and returns the result's Z component (X and Y components are always zero). + /// + //////////////////////////////////////////////////////////// + constexpr T cross(const Vector2& rhs) const; + + //////////////////////////////////////////////////////////// + /// \brief Component-wise multiplication of \c *this and \c rhs. + /// + /// Computes (lhs.x*rhs.x, lhs.y*rhs.y). + /// + /// Scaling is the most common use case for component-wise multiplication/division. + /// This operation is also known as the Hadamard or Schur product. + /// + //////////////////////////////////////////////////////////// + constexpr Vector2 cwiseMul(const Vector2& rhs) const; + + //////////////////////////////////////////////////////////// + /// \brief Component-wise division of \c *this and \c rhs. + /// + /// Computes (lhs.x/rhs.x, lhs.y/rhs.y). + /// + /// Scaling is the most common use case for component-wise multiplication/division. + /// + /// \pre Neither component of \c rhs is zero. + /// + //////////////////////////////////////////////////////////// + constexpr Vector2 cwiseDiv(const Vector2& rhs) const; + //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// T x; //!< X coordinate of the vector T y; //!< Y coordinate of the vector + + + //////////////////////////////////////////////////////////// + // Static member data + //////////////////////////////////////////////////////////// + static const Vector2 UnitX; //!< The X unit vector (1, 0), usually facing right + static const Vector2 UnitY; //!< The Y unit vector (0, 1), usually facing down }; +// Define the most common types +using Vector2i = Vector2; +using Vector2u = Vector2; +using Vector2f = Vector2; + //////////////////////////////////////////////////////////// /// \relates Vector2 /// \brief Overload of unary operator - @@ -93,12 +227,12 @@ template /// \brief Overload of binary operator += /// /// This operator performs a memberwise addition of both vectors, -/// and assigns the result to \a left. +/// and assigns the result to \c left. /// /// \param left Left operand (a vector) /// \param right Right operand (a vector) /// -/// \return Reference to \a left +/// \return Reference to \c left /// //////////////////////////////////////////////////////////// template @@ -109,12 +243,12 @@ constexpr Vector2& operator +=(Vector2& left, const Vector2& right); /// \brief Overload of binary operator -= /// /// This operator performs a memberwise subtraction of both vectors, -/// and assigns the result to \a left. +/// and assigns the result to \c left. /// /// \param left Left operand (a vector) /// \param right Right operand (a vector) /// -/// \return Reference to \a left +/// \return Reference to \c left /// //////////////////////////////////////////////////////////// template @@ -153,7 +287,7 @@ template /// \param left Left operand (a vector) /// \param right Right operand (a scalar value) /// -/// \return Memberwise multiplication by \a right +/// \return Memberwise multiplication by \c right /// //////////////////////////////////////////////////////////// template @@ -166,7 +300,7 @@ template /// \param left Left operand (a scalar value) /// \param right Right operand (a vector) /// -/// \return Memberwise multiplication by \a left +/// \return Memberwise multiplication by \c left /// //////////////////////////////////////////////////////////// template @@ -176,13 +310,13 @@ template /// \relates Vector2 /// \brief Overload of binary operator *= /// -/// This operator performs a memberwise multiplication by \a right, -/// and assigns the result to \a left. +/// This operator performs a memberwise multiplication by \c right, +/// and assigns the result to \c left. /// /// \param left Left operand (a vector) /// \param right Right operand (a scalar value) /// -/// \return Reference to \a left +/// \return Reference to \c left /// //////////////////////////////////////////////////////////// template @@ -195,7 +329,7 @@ constexpr Vector2& operator *=(Vector2& left, T right); /// \param left Left operand (a vector) /// \param right Right operand (a scalar value) /// -/// \return Memberwise division by \a right +/// \return Memberwise division by \c right /// //////////////////////////////////////////////////////////// template @@ -205,13 +339,13 @@ template /// \relates Vector2 /// \brief Overload of binary operator /= /// -/// This operator performs a memberwise division by \a right, -/// and assigns the result to \a left. +/// This operator performs a memberwise division by \c right, +/// and assigns the result to \c left. /// /// \param left Left operand (a vector) /// \param right Right operand (a scalar value) /// -/// \return Reference to \a left +/// \return Reference to \c left /// //////////////////////////////////////////////////////////// template @@ -226,7 +360,7 @@ constexpr Vector2& operator /=(Vector2& left, T right); /// \param left Left operand (a vector) /// \param right Right operand (a vector) /// -/// \return True if \a left is equal to \a right +/// \return True if \c left is equal to \c right /// //////////////////////////////////////////////////////////// template @@ -241,7 +375,7 @@ template /// \param left Left operand (a vector) /// \param right Right operand (a vector) /// -/// \return True if \a left is not equal to \a right +/// \return True if \c left is not equal to \c right /// //////////////////////////////////////////////////////////// template @@ -249,11 +383,6 @@ template #include -// Define the most common types -using Vector2i = Vector2; -using Vector2u = Vector2; -using Vector2f = Vector2; - } // namespace sf @@ -267,31 +396,40 @@ using Vector2f = Vector2; /// sf::Vector2 is a simple class that defines a mathematical /// vector with two coordinates (x and y). It can be used to /// represent anything that has two dimensions: a size, a point, -/// a velocity, etc. +/// a velocity, a scale, etc. /// +/// The API provides basic arithmetic (addition, subtraction, scale), as +/// well as more advanced geometric operations, such as dot/cross products, +/// length and angle computations, projections, rotations, etc. +/// /// The template parameter T is the type of the coordinates. It /// can be any type that supports arithmetic operations (+, -, /, *) /// and comparisons (==, !=), for example int or float. -/// +/// Note that some operations are only meaningful for vectors where T is +/// a floating point type (e.g. float or double), often because +/// results cannot be represented accurately with integers. +/// The method documentation mentions "(floating-point)" in those cases. +/// /// You generally don't have to care about the templated form (sf::Vector2), /// the most common specializations have special type aliases: /// \li sf::Vector2 is sf::Vector2f /// \li sf::Vector2 is sf::Vector2i /// \li sf::Vector2 is sf::Vector2u /// -/// The sf::Vector2 class has a small and simple interface, its x and y members -/// can be accessed directly (there are no accessors like setX(), getX()) and it -/// contains no mathematical function like dot product, cross product, length, etc. +/// The sf::Vector2 class has a simple interface, its x and y members +/// can be accessed directly (there are no accessors like setX(), getX()). /// /// Usage example: /// \code -/// sf::Vector2f v1(16.5f, 24.f); -/// v1.x = 18.2f; -/// float y = v1.y; +/// sf::Vector2f v(16.5f, 24.f); +/// v.x = 18.2f; +/// float y = v.y; /// -/// sf::Vector2f v2 = v1 * 5.f; -/// sf::Vector2f v3; -/// v3 = v1 + v2; +/// sf::Vector2f w = v * 5.f; +/// sf::Vector2f u; +/// u = v + w; +/// +/// float s = v.dot(w); /// /// bool different = (v2 != v3); /// \endcode diff --git a/include/SFML/System/Vector2.inl b/include/SFML/System/Vector2.inl index 93334b68..9a256d0f 100644 --- a/include/SFML/System/Vector2.inl +++ b/include/SFML/System/Vector2.inl @@ -53,6 +53,128 @@ y(static_cast(vector.y)) } +//////////////////////////////////////////////////////////// +template +T Vector2::length() const +{ + static_assert(std::is_floating_point_v, "Vector2::length() is only supported for floating point types"); + + return std::hypot(x, y); +} + + +//////////////////////////////////////////////////////////// +template +constexpr T Vector2::lengthSq() const +{ + return this->dot(*this); +} + + +//////////////////////////////////////////////////////////// +template +Vector2 Vector2::normalized() const +{ + static_assert(std::is_floating_point_v, "Vector2::normalized() is only supported for floating point types"); + + assert(*this != Vector2()); + return (*this) / length(); +} + + +//////////////////////////////////////////////////////////// +template +Angle Vector2::angleTo(const Vector2& rhs) const +{ + static_assert(std::is_floating_point_v, "Vector2::angleTo() is only supported for floating point types"); + + assert(*this != Vector2()); + assert(rhs != Vector2()); + return radians(std::atan2(this->cross(rhs), this->dot(rhs))); +} + + +//////////////////////////////////////////////////////////// +template +Angle Vector2::angle() const +{ + static_assert(std::is_floating_point_v, "Vector2::angle() is only supported for floating point types"); + + assert(*this != Vector2()); + return radians(std::atan2(y, x)); +} + + +//////////////////////////////////////////////////////////// +template +Vector2 Vector2::rotatedBy(Angle angle) const +{ + static_assert(std::is_floating_point_v, "Vector2::rotatedBy() is only supported for floating point types"); + + // No zero vector assert, because rotating a zero vector is well-defined (yields always itself) + T cos = std::cos(angle.asRadians()); + T sin = std::sin(angle.asRadians()); + + // Don't manipulate x and y separately, otherwise they're overwritten too early + return Vector2( + cos * x - sin * y, + sin * x + cos * y); +} + + +//////////////////////////////////////////////////////////// +template +constexpr Vector2 Vector2::projectedOnto(const Vector2& axis) const +{ + static_assert(std::is_floating_point_v, "Vector2::projectedOnto() is only supported for floating point types"); + + assert(axis != Vector2()); + return this->dot(axis) / axis.lengthSq() * axis; +} + + +//////////////////////////////////////////////////////////// +template +constexpr Vector2 Vector2::perpendicular() const +{ + return Vector2(-y, x); +} + + +//////////////////////////////////////////////////////////// +template +constexpr T Vector2::dot(const Vector2& rhs) const +{ + return x * rhs.x + y * rhs.y; +} + + +//////////////////////////////////////////////////////////// +template +constexpr T Vector2::cross(const Vector2& rhs) const +{ + return x * rhs.y - y * rhs.x; +} + + +//////////////////////////////////////////////////////////// +template +constexpr Vector2 Vector2::cwiseMul(const Vector2& rhs) const +{ + return Vector2(x * rhs.x, y * rhs.y); +} + + +//////////////////////////////////////////////////////////// +template +constexpr Vector2 Vector2::cwiseDiv(const Vector2& rhs) const +{ + assert(rhs.x != 0); + assert(rhs.y != 0); + return Vector2(x / rhs.x, y / rhs.y); +} + + //////////////////////////////////////////////////////////// template constexpr Vector2 operator -(const Vector2& right) @@ -159,3 +281,14 @@ constexpr bool operator !=(const Vector2& left, const Vector2& right) { return (left.x != right.x) || (left.y != right.y); } + + +//////////////////////////////////////////////////////////// +// Static member data +//////////////////////////////////////////////////////////// + +template +constexpr Vector2 Vector2::UnitX(static_cast(1), static_cast(0)); + +template +constexpr Vector2 Vector2::UnitY(static_cast(0), static_cast(1));