diff --git a/include/SFML/System/Vector2.hpp b/include/SFML/System/Vector2.hpp index c0e3ae066..14dd6f2db 100644 --- a/include/SFML/System/Vector2.hpp +++ b/include/SFML/System/Vector2.hpp @@ -431,7 +431,7 @@ template /// /// float s = v.dot(w); /// -/// bool different = (v2 != v3); +/// bool different = (v != u); /// \endcode /// /// Note: for 3-dimensional vectors, see sf::Vector3. diff --git a/include/SFML/System/Vector3.hpp b/include/SFML/System/Vector3.hpp index b50707015..2a4af9ac1 100644 --- a/include/SFML/System/Vector3.hpp +++ b/include/SFML/System/Vector3.hpp @@ -25,6 +25,9 @@ #ifndef SFML_VECTOR3_HPP #define SFML_VECTOR3_HPP +#include +#include +#include namespace sf { @@ -70,6 +73,65 @@ public: template constexpr explicit Vector3(const Vector3& 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]] Vector3 normalized() const; + + //////////////////////////////////////////////////////////// + /// \brief Dot product of two 3D vectors. + /// + //////////////////////////////////////////////////////////// + constexpr T dot(const Vector3& rhs) const; + + //////////////////////////////////////////////////////////// + /// \brief Cross product of two 3D vectors. + /// + //////////////////////////////////////////////////////////// + constexpr Vector3 cross(const Vector3& rhs) const; + + //////////////////////////////////////////////////////////// + /// \brief Component-wise multiplication of \c *this and \c rhs. + /// + /// Computes (lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z). + /// + /// Scaling is the most common use case for component-wise multiplication/division. + /// This operation is also known as the Hadamard or Schur product. + /// + //////////////////////////////////////////////////////////// + constexpr Vector3 cwiseMul(const Vector3& rhs) const; + + //////////////////////////////////////////////////////////// + /// \brief Component-wise division of \c *this and \c rhs. + /// + /// Computes (lhs.x/rhs.x, lhs.y/rhs.y, lhs.z/rhs.z). + /// + /// Scaling is the most common use case for component-wise multiplication/division. + /// + /// \pre Neither component of \c rhs is zero. + /// + //////////////////////////////////////////////////////////// + constexpr Vector3 cwiseDiv(const Vector3& rhs) const; + //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// @@ -273,28 +335,33 @@ using Vector3f = Vector3; /// 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::Vector3), /// the most common specializations have special type aliases: /// \li sf::Vector3 is sf::Vector3f /// \li sf::Vector3 is sf::Vector3i /// -/// The sf::Vector3 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::Vector3 class has a small and simple interface, its x, y and z members +/// can be accessed directly (there are no accessors like setX(), getX()). /// /// Usage example: /// \code -/// sf::Vector3f v1(16.5f, 24.f, -8.2f); -/// v1.x = 18.2f; -/// float y = v1.y; -/// float z = v1.z; +/// sf::Vector3f v(16.5f, 24.f, -3.2f); +/// v.x = 18.2f; +/// float y = v.y; /// -/// sf::Vector3f v2 = v1 * 5.f; -/// sf::Vector3f v3; -/// v3 = v1 + v2; +/// sf::Vector3f w = v * 5.f; +/// sf::Vector3f u; +/// u = v + w; /// -/// bool different = (v2 != v3); +/// float s = v.dot(w); +/// sf::Vector3f t = v.cross(w); +/// +/// bool different = (v != u); /// \endcode /// /// Note: for 2-dimensional vectors, see sf::Vector2. diff --git a/include/SFML/System/Vector3.inl b/include/SFML/System/Vector3.inl index 68d356de0..a23737f2d 100644 --- a/include/SFML/System/Vector3.inl +++ b/include/SFML/System/Vector3.inl @@ -56,6 +56,74 @@ z(static_cast(vector.z)) } +//////////////////////////////////////////////////////////// +template +T Vector3::length() const +{ + static_assert(std::is_floating_point_v, "Vector3::length() is only supported for floating point types"); + + return std::hypot(x, y, z); +} + + +//////////////////////////////////////////////////////////// +template +constexpr T Vector3::lengthSq() const +{ + return dot(*this); +} + + +//////////////////////////////////////////////////////////// +template +Vector3 Vector3::normalized() const +{ + static_assert(std::is_floating_point_v, "Vector3::normalized() is only supported for floating point types"); + + assert(*this != Vector3()); + return (*this) / length(); +} + + +//////////////////////////////////////////////////////////// +template +constexpr T Vector3::dot(const Vector3& rhs) const +{ + return x * rhs.x + y * rhs.y + z * rhs.z; +} + + +//////////////////////////////////////////////////////////// +template +constexpr Vector3 Vector3::cross(const Vector3& rhs) const +{ + return Vector3( + (y * rhs.z) - (z * rhs.y), + (z * rhs.x) - (x * rhs.z), + (x * rhs.y) - (y * rhs.x) + ); +} + + +//////////////////////////////////////////////////////////// +template +constexpr Vector3 Vector3::cwiseMul(const Vector3& rhs) const +{ + return Vector3(x * rhs.x, y * rhs.y, z * rhs.z); +} + + +//////////////////////////////////////////////////////////// +template +constexpr Vector3 Vector3::cwiseDiv(const Vector3& rhs) const +{ + assert(rhs.x != 0); + assert(rhs.y != 0); + assert(rhs.z != 0); + return Vector3(x / rhs.x, y / rhs.y, z / rhs.z); +} + + //////////////////////////////////////////////////////////// template constexpr Vector3 operator -(const Vector3& left) diff --git a/test/System/Vector2.cpp b/test/System/Vector2.cpp index 7246ee0d9..e586f15cb 100644 --- a/test/System/Vector2.cpp +++ b/test/System/Vector2.cpp @@ -195,13 +195,13 @@ TEST_CASE("sf::Vector2 class template - [system]") CHECK(v.length() == Approx(3.84187)); CHECK(v.lengthSq() == Approx(14.7599650969)); - CHECK(v.normalized() == ApproxVec(0.624695f, 0.780869f)); + CHECK(v.normalized() == ApproxVec2(0.624695f, 0.780869f)); const sf::Vector2f w(-0.7f, -2.2f); CHECK(w.length() == Approx(2.30868)); CHECK(w.lengthSq() == Approx(5.3300033)); - CHECK(w.normalized() == ApproxVec(-0.303204f, -0.952926f)); + CHECK(w.normalized() == ApproxVec2(-0.303204f, -0.952926f)); } SUBCASE("Rotations and angles") @@ -222,15 +222,15 @@ TEST_CASE("sf::Vector2 class template - [system]") CHECK(w.angleTo(v) == ApproxDeg(158.9902f)); const float ratio = w.length() / v.length(); - CHECK(v.rotatedBy(-158.9902_deg) * ratio == ApproxVec(w)); - CHECK(w.rotatedBy(158.9902_deg) / ratio == ApproxVec(v)); + CHECK(v.rotatedBy(-158.9902_deg) * ratio == ApproxVec2(w)); + CHECK(w.rotatedBy(158.9902_deg) / ratio == ApproxVec2(v)); CHECK(v.perpendicular() == sf::Vector2f(-3.0f, 2.4f)); CHECK(v.perpendicular().perpendicular().perpendicular().perpendicular() == v); - CHECK(v.rotatedBy(90_deg) == ApproxVec(-3.0f, 2.4f)); - CHECK(v.rotatedBy(27.14_deg) == ApproxVec(0.767248f, 3.76448f)); - CHECK(v.rotatedBy(-36.11_deg) == ApproxVec(3.70694f, 1.00925f)); + CHECK(v.rotatedBy(90_deg) == ApproxVec2(-3.0f, 2.4f)); + CHECK(v.rotatedBy(27.14_deg) == ApproxVec2(0.767248f, 3.76448f)); + CHECK(v.rotatedBy(-36.11_deg) == ApproxVec2(3.70694f, 1.00925f)); } SUBCASE("Products and quotients") @@ -244,10 +244,10 @@ TEST_CASE("sf::Vector2 class template - [system]") CHECK(v.cross(w) == Approx(-3.18)); CHECK(w.cross(v) == Approx(+3.18)); - CHECK(v.cwiseMul(w) == ApproxVec(-1.68f, -6.6f)); - CHECK(w.cwiseMul(v) == ApproxVec(-1.68f, -6.6f)); - CHECK(v.cwiseDiv(w) == ApproxVec(-3.428571f, -1.363636f)); - CHECK(w.cwiseDiv(v) == ApproxVec(-0.291666f, -0.733333f)); + CHECK(v.cwiseMul(w) == ApproxVec2(-1.68f, -6.6f)); + CHECK(w.cwiseMul(v) == ApproxVec2(-1.68f, -6.6f)); + CHECK(v.cwiseDiv(w) == ApproxVec2(-3.428571f, -1.363636f)); + CHECK(w.cwiseDiv(v) == ApproxVec2(-0.291666f, -0.733333f)); } SUBCASE("Projection") @@ -255,14 +255,14 @@ TEST_CASE("sf::Vector2 class template - [system]") const sf::Vector2f v(2.4f, 3.0f); const sf::Vector2f w(-0.7f, -2.2f); - CHECK(v.projectedOnto(w) == ApproxVec(1.087430f, 3.417636f)); - CHECK(v.projectedOnto(w) == ApproxVec(-1.55347f * w)); + CHECK(v.projectedOnto(w) == ApproxVec2(1.087430f, 3.417636f)); + CHECK(v.projectedOnto(w) == ApproxVec2(-1.55347f * w)); - CHECK(w.projectedOnto(v) == ApproxVec(-1.346342f, -1.682927f)); - CHECK(w.projectedOnto(v) == ApproxVec(-0.560976f * v)); + CHECK(w.projectedOnto(v) == ApproxVec2(-1.346342f, -1.682927f)); + CHECK(w.projectedOnto(v) == ApproxVec2(-0.560976f * v)); - CHECK(v.projectedOnto(sf::Vector2f::UnitX) == ApproxVec(2.4f, 0.0f)); - CHECK(v.projectedOnto(sf::Vector2f::UnitY) == ApproxVec(0.0f, 3.0f)); + CHECK(v.projectedOnto(sf::Vector2f::UnitX) == ApproxVec2(2.4f, 0.0f)); + CHECK(v.projectedOnto(sf::Vector2f::UnitY) == ApproxVec2(0.0f, 3.0f)); } SUBCASE("Constexpr support") diff --git a/test/System/Vector3.cpp b/test/System/Vector3.cpp index dd96e3c45..14013bf6d 100644 --- a/test/System/Vector3.cpp +++ b/test/System/Vector3.cpp @@ -4,7 +4,10 @@ #include -// Use sf::Vector3i for tests. Test coverage is given, as there are no template specializations. +using doctest::Approx; + +// Use sf::Vector3i for tests (except for float vector algebra). +// Test coverage is given, as there are no template specializations. TEST_CASE("sf::Vector3 class template - [system]") { @@ -199,6 +202,32 @@ TEST_CASE("sf::Vector3 class template - [system]") } } + SUBCASE("Length and normalization") + { + const sf::Vector3f v(2.4f, 3.0f, 5.2f); + + CHECK(v.length() == Approx(6.46529)); + CHECK(v.lengthSq() == Approx(41.79997)); + CHECK(v.normalized() == ApproxVec3(0.37121f, 0.46401f, 0.80429f)); + } + + SUBCASE("Products and quotients") + { + const sf::Vector3f v(2.4f, 3.0f, 5.2f); + const sf::Vector3f w(-0.7f, -2.2f, -4.8f); + + CHECK(v.dot(w) == Approx(-33.24)); + CHECK(w.dot(v) == Approx(-33.24)); + + CHECK(v.cross(w) == ApproxVec3(-2.96f, 7.88f, -3.18f)); + CHECK(w.cross(v) == ApproxVec3(2.96f, -7.88f, 3.18f)); + + CHECK(v.cwiseMul(w) == ApproxVec3(-1.68f, -6.6f, -24.96f)); + CHECK(w.cwiseMul(v) == ApproxVec3(-1.68f, -6.6f, -24.96f)); + CHECK(v.cwiseDiv(w) == ApproxVec3(-3.428571f, -1.363636f, -1.0833333f)); + CHECK(w.cwiseDiv(v) == ApproxVec3(-0.291666f, -0.733333f, -0.9230769f)); + } + SUBCASE("Constexpr support") { constexpr sf::Vector3i vector(1, 2, 3); diff --git a/test/TestUtilities/SystemUtil.cpp b/test/TestUtilities/SystemUtil.cpp index 699b344fb..51d3e6eb6 100644 --- a/test/TestUtilities/SystemUtil.cpp +++ b/test/TestUtilities/SystemUtil.cpp @@ -35,7 +35,12 @@ namespace sf } } -bool operator==(const sf::Vector2f& lhs, const ApproxVec& rhs) +bool operator==(const sf::Vector2f& lhs, const ApproxVec2& rhs) +{ + return (lhs - rhs.vector).length() == doctest::Approx(0.0); +} + +bool operator==(const sf::Vector3f& lhs, const ApproxVec3& rhs) { return static_cast((lhs - rhs.vector).length()) == doctest::Approx(0.0); } @@ -45,7 +50,13 @@ bool operator==(const sf::Angle& lhs, const ApproxDeg& rhs) return static_cast(lhs.asDegrees()) == doctest::Approx(static_cast(rhs.degrees)); } -std::ostream& operator <<(std::ostream& os, const ApproxVec& approx) +std::ostream& operator <<(std::ostream& os, const ApproxVec2& approx) +{ + os << approx.vector; + return os; +} + +std::ostream& operator <<(std::ostream& os, const ApproxVec3& approx) { os << approx.vector; return os; diff --git a/test/TestUtilities/SystemUtil.hpp b/test/TestUtilities/SystemUtil.hpp index 99c877cee..2fac550e5 100644 --- a/test/TestUtilities/SystemUtil.hpp +++ b/test/TestUtilities/SystemUtil.hpp @@ -40,17 +40,28 @@ namespace sf } // Utilities for approximate equality -struct ApproxVec +struct ApproxVec2 { - ApproxVec(float x, float y) + ApproxVec2(float x, float y) : vector(x, y) {} - explicit ApproxVec(const sf::Vector2f& v) + explicit ApproxVec2(const sf::Vector2f& v) : vector(v) {} sf::Vector2f vector; }; +struct ApproxVec3 +{ + ApproxVec3(float x, float y, float z) + : vector(x, y, z) {} + + explicit ApproxVec3(const sf::Vector3f& v) + : vector(v) {} + + sf::Vector3f vector; +}; + // Utilities for approximate equality struct ApproxDeg { @@ -60,10 +71,12 @@ struct ApproxDeg float degrees; }; -bool operator==(const sf::Vector2f& lhs, const ApproxVec& rhs); +bool operator==(const sf::Vector2f& lhs, const ApproxVec2& rhs); +bool operator==(const sf::Vector3f& lhs, const ApproxVec3& rhs); bool operator==(const sf::Angle& lhs, const ApproxDeg& rhs); -std::ostream& operator <<(std::ostream& os, const ApproxVec& approx); +std::ostream& operator <<(std::ostream& os, const ApproxVec2& approx); +std::ostream& operator <<(std::ostream& os, const ApproxVec3& approx); std::ostream& operator <<(std::ostream& os, const ApproxDeg& approx); namespace sf::Testing