Add extension methods to Vector3<T>

Includes relevant tests and updates TestUtilities to feature ApproxVec2 & ApproxVec3
This commit is contained in:
Bambo-Borris 2022-05-04 18:03:26 +01:00 committed by Lukas Dürrenberger
parent 65e357e901
commit b2ab6d6ab3
7 changed files with 225 additions and 37 deletions

View File

@ -431,7 +431,7 @@ template <typename T>
/// ///
/// float s = v.dot(w); /// float s = v.dot(w);
/// ///
/// bool different = (v2 != v3); /// bool different = (v != u);
/// \endcode /// \endcode
/// ///
/// Note: for 3-dimensional vectors, see sf::Vector3. /// Note: for 3-dimensional vectors, see sf::Vector3.

View File

@ -25,6 +25,9 @@
#ifndef SFML_VECTOR3_HPP #ifndef SFML_VECTOR3_HPP
#define SFML_VECTOR3_HPP #define SFML_VECTOR3_HPP
#include <cassert>
#include <cmath>
#include <type_traits>
namespace sf namespace sf
{ {
@ -70,6 +73,65 @@ public:
template <typename U> template <typename U>
constexpr explicit Vector3(const Vector3<U>& vector); constexpr explicit Vector3(const Vector3<U>& vector);
////////////////////////////////////////////////////////////
/// \brief Length of the vector <i><b>(floating-point)</b></i>.
///
/// 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 <i><b>(floating-point)</b></i>.
///
/// \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 <tt>(lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z)</tt>.
///
/// 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 <tt>(lhs.x/rhs.x, lhs.y/rhs.y, lhs.z/rhs.z)</tt>.
///
/// 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 // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@ -273,28 +335,33 @@ using Vector3f = Vector3<float>;
/// The template parameter T is the type of the coordinates. It /// The template parameter T is the type of the coordinates. It
/// can be any type that supports arithmetic operations (+, -, /, *) /// can be any type that supports arithmetic operations (+, -, /, *)
/// and comparisons (==, !=), for example int or float. /// 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<T>), /// You generally don't have to care about the templated form (sf::Vector3<T>),
/// the most common specializations have special type aliases: /// the most common specializations have special type aliases:
/// \li sf::Vector3<float> is sf::Vector3f /// \li sf::Vector3<float> is sf::Vector3f
/// \li sf::Vector3<int> is sf::Vector3i /// \li sf::Vector3<int> is sf::Vector3i
/// ///
/// The sf::Vector3 class has a small and simple interface, its x and y members /// 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()) and it /// can be accessed directly (there are no accessors like setX(), getX()).
/// contains no mathematical function like dot product, cross product, length, etc.
/// ///
/// Usage example: /// Usage example:
/// \code /// \code
/// sf::Vector3f v1(16.5f, 24.f, -8.2f); /// sf::Vector3f v(16.5f, 24.f, -3.2f);
/// v1.x = 18.2f; /// v.x = 18.2f;
/// float y = v1.y; /// float y = v.y;
/// float z = v1.z;
/// ///
/// sf::Vector3f v2 = v1 * 5.f; /// sf::Vector3f w = v * 5.f;
/// sf::Vector3f v3; /// sf::Vector3f u;
/// v3 = v1 + v2; /// u = v + w;
/// ///
/// bool different = (v2 != v3); /// float s = v.dot(w);
/// sf::Vector3f t = v.cross(w);
///
/// bool different = (v != u);
/// \endcode /// \endcode
/// ///
/// Note: for 2-dimensional vectors, see sf::Vector2. /// Note: for 2-dimensional vectors, see sf::Vector2.

View File

@ -56,6 +56,74 @@ z(static_cast<T>(vector.z))
} }
////////////////////////////////////////////////////////////
template <typename T>
T Vector3<T>::length() const
{
static_assert(std::is_floating_point_v<T>, "Vector3::length() is only supported for floating point types");
return std::hypot(x, y, z);
}
////////////////////////////////////////////////////////////
template <typename T>
constexpr T Vector3<T>::lengthSq() const
{
return dot(*this);
}
////////////////////////////////////////////////////////////
template <typename T>
Vector3<T> Vector3<T>::normalized() const
{
static_assert(std::is_floating_point_v<T>, "Vector3::normalized() is only supported for floating point types");
assert(*this != Vector3<T>());
return (*this) / length();
}
////////////////////////////////////////////////////////////
template <typename T>
constexpr T Vector3<T>::dot(const Vector3<T>& rhs) const
{
return x * rhs.x + y * rhs.y + z * rhs.z;
}
////////////////////////////////////////////////////////////
template <typename T>
constexpr Vector3<T> Vector3<T>::cross(const Vector3<T>& rhs) const
{
return Vector3<T>(
(y * rhs.z) - (z * rhs.y),
(z * rhs.x) - (x * rhs.z),
(x * rhs.y) - (y * rhs.x)
);
}
////////////////////////////////////////////////////////////
template <typename T>
constexpr Vector3<T> Vector3<T>::cwiseMul(const Vector3<T>& rhs) const
{
return Vector3<T>(x * rhs.x, y * rhs.y, z * rhs.z);
}
////////////////////////////////////////////////////////////
template <typename T>
constexpr Vector3<T> Vector3<T>::cwiseDiv(const Vector3<T>& rhs) const
{
assert(rhs.x != 0);
assert(rhs.y != 0);
assert(rhs.z != 0);
return Vector3<T>(x / rhs.x, y / rhs.y, z / rhs.z);
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
template <typename T> template <typename T>
constexpr Vector3<T> operator -(const Vector3<T>& left) constexpr Vector3<T> operator -(const Vector3<T>& left)

View File

@ -195,13 +195,13 @@ TEST_CASE("sf::Vector2 class template - [system]")
CHECK(v.length() == Approx(3.84187)); CHECK(v.length() == Approx(3.84187));
CHECK(v.lengthSq() == Approx(14.7599650969)); 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); const sf::Vector2f w(-0.7f, -2.2f);
CHECK(w.length() == Approx(2.30868)); CHECK(w.length() == Approx(2.30868));
CHECK(w.lengthSq() == Approx(5.3300033)); 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") SUBCASE("Rotations and angles")
@ -222,15 +222,15 @@ TEST_CASE("sf::Vector2 class template - [system]")
CHECK(w.angleTo(v) == ApproxDeg(158.9902f)); CHECK(w.angleTo(v) == ApproxDeg(158.9902f));
const float ratio = w.length() / v.length(); const float ratio = w.length() / v.length();
CHECK(v.rotatedBy(-158.9902_deg) * ratio == ApproxVec(w)); CHECK(v.rotatedBy(-158.9902_deg) * ratio == ApproxVec2(w));
CHECK(w.rotatedBy(158.9902_deg) / ratio == ApproxVec(v)); CHECK(w.rotatedBy(158.9902_deg) / ratio == ApproxVec2(v));
CHECK(v.perpendicular() == sf::Vector2f(-3.0f, 2.4f)); CHECK(v.perpendicular() == sf::Vector2f(-3.0f, 2.4f));
CHECK(v.perpendicular().perpendicular().perpendicular().perpendicular() == v); CHECK(v.perpendicular().perpendicular().perpendicular().perpendicular() == v);
CHECK(v.rotatedBy(90_deg) == ApproxVec(-3.0f, 2.4f)); CHECK(v.rotatedBy(90_deg) == ApproxVec2(-3.0f, 2.4f));
CHECK(v.rotatedBy(27.14_deg) == ApproxVec(0.767248f, 3.76448f)); CHECK(v.rotatedBy(27.14_deg) == ApproxVec2(0.767248f, 3.76448f));
CHECK(v.rotatedBy(-36.11_deg) == ApproxVec(3.70694f, 1.00925f)); CHECK(v.rotatedBy(-36.11_deg) == ApproxVec2(3.70694f, 1.00925f));
} }
SUBCASE("Products and quotients") SUBCASE("Products and quotients")
@ -244,10 +244,10 @@ TEST_CASE("sf::Vector2 class template - [system]")
CHECK(v.cross(w) == Approx(-3.18)); CHECK(v.cross(w) == Approx(-3.18));
CHECK(w.cross(v) == Approx(+3.18)); CHECK(w.cross(v) == Approx(+3.18));
CHECK(v.cwiseMul(w) == ApproxVec(-1.68f, -6.6f)); CHECK(v.cwiseMul(w) == ApproxVec2(-1.68f, -6.6f));
CHECK(w.cwiseMul(v) == ApproxVec(-1.68f, -6.6f)); CHECK(w.cwiseMul(v) == ApproxVec2(-1.68f, -6.6f));
CHECK(v.cwiseDiv(w) == ApproxVec(-3.428571f, -1.363636f)); CHECK(v.cwiseDiv(w) == ApproxVec2(-3.428571f, -1.363636f));
CHECK(w.cwiseDiv(v) == ApproxVec(-0.291666f, -0.733333f)); CHECK(w.cwiseDiv(v) == ApproxVec2(-0.291666f, -0.733333f));
} }
SUBCASE("Projection") SUBCASE("Projection")
@ -255,14 +255,14 @@ TEST_CASE("sf::Vector2 class template - [system]")
const sf::Vector2f v(2.4f, 3.0f); const sf::Vector2f v(2.4f, 3.0f);
const sf::Vector2f w(-0.7f, -2.2f); const sf::Vector2f w(-0.7f, -2.2f);
CHECK(v.projectedOnto(w) == ApproxVec(1.087430f, 3.417636f)); CHECK(v.projectedOnto(w) == ApproxVec2(1.087430f, 3.417636f));
CHECK(v.projectedOnto(w) == ApproxVec(-1.55347f * w)); CHECK(v.projectedOnto(w) == ApproxVec2(-1.55347f * w));
CHECK(w.projectedOnto(v) == ApproxVec(-1.346342f, -1.682927f)); CHECK(w.projectedOnto(v) == ApproxVec2(-1.346342f, -1.682927f));
CHECK(w.projectedOnto(v) == ApproxVec(-0.560976f * v)); CHECK(w.projectedOnto(v) == ApproxVec2(-0.560976f * v));
CHECK(v.projectedOnto(sf::Vector2f::UnitX) == ApproxVec(2.4f, 0.0f)); CHECK(v.projectedOnto(sf::Vector2f::UnitX) == ApproxVec2(2.4f, 0.0f));
CHECK(v.projectedOnto(sf::Vector2f::UnitY) == ApproxVec(0.0f, 3.0f)); CHECK(v.projectedOnto(sf::Vector2f::UnitY) == ApproxVec2(0.0f, 3.0f));
} }
SUBCASE("Constexpr support") SUBCASE("Constexpr support")

View File

@ -4,7 +4,10 @@
#include <doctest.h> #include <doctest.h>
// 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]") 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") SUBCASE("Constexpr support")
{ {
constexpr sf::Vector3i vector(1, 2, 3); constexpr sf::Vector3i vector(1, 2, 3);

View File

@ -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<double>((lhs - rhs.vector).length()) == doctest::Approx(0.0); return static_cast<double>((lhs - rhs.vector).length()) == doctest::Approx(0.0);
} }
@ -45,7 +50,13 @@ bool operator==(const sf::Angle& lhs, const ApproxDeg& rhs)
return static_cast<double>(lhs.asDegrees()) == doctest::Approx(static_cast<double>(rhs.degrees)); return static_cast<double>(lhs.asDegrees()) == doctest::Approx(static_cast<double>(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; os << approx.vector;
return os; return os;

View File

@ -40,17 +40,28 @@ namespace sf
} }
// Utilities for approximate equality // Utilities for approximate equality
struct ApproxVec struct ApproxVec2
{ {
ApproxVec(float x, float y) ApproxVec2(float x, float y)
: vector(x, y) {} : vector(x, y) {}
explicit ApproxVec(const sf::Vector2f& v) explicit ApproxVec2(const sf::Vector2f& v)
: vector(v) {} : vector(v) {}
sf::Vector2f vector; 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 // Utilities for approximate equality
struct ApproxDeg struct ApproxDeg
{ {
@ -60,10 +71,12 @@ struct ApproxDeg
float degrees; 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); 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); std::ostream& operator <<(std::ostream& os, const ApproxDeg& approx);
namespace sf::Testing namespace sf::Testing