From 71a4d93147e709f862641d914fa4299c01d3a5f4 Mon Sep 17 00:00:00 2001 From: olivi Date: Sun, 4 Oct 2026 10:34:21 +0200 Subject: [PATCH] Add EPSILON comparisons, == operators and exceptions - Add Constants.h (sizes, axes, EPSILON = 1e-4 as in Aventura) - Add MathTools::equals(float, float) with EPSILON tolerance - Add the Aventura exception hierarchy (Vector3DException and subclasses) - Add operator== and operator!= with EPSILON tolerance on vectors and matrices - Vector get(i)/set(i) now throw IndexOutOfBoundException instead of returning NAN or ignoring the call, as in Aventura - Tests use the new operators and cover the exceptions Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01FmLcUnAfhtVMCNPGNw5Lxb --- VectorMatrixLib/Constants.h | 29 ++++ VectorMatrixLib/Exceptions.h | 54 +++++++ VectorMatrixLib/MathTools.h | 29 ++++ VectorMatrixLib/Matrix3f.cpp | 14 ++ VectorMatrixLib/Matrix3f.h | 3 + VectorMatrixLib/Matrix4f.cpp | 14 ++ VectorMatrixLib/Matrix4f.h | 3 + VectorMatrixLib/Vector3f.cpp | 17 ++- VectorMatrixLib/Vector3f.h | 5 +- VectorMatrixLib/Vector4f.cpp | 17 ++- VectorMatrixLib/Vector4f.h | 5 +- VectorMatrixLib/VectorMatrixLib.vcxproj | 3 + .../VectorMatrixLib.vcxproj.filters | 9 ++ tests/TestVectorMatrix.cpp | 132 ++++++++++-------- 14 files changed, 271 insertions(+), 63 deletions(-) create mode 100644 VectorMatrixLib/Constants.h create mode 100644 VectorMatrixLib/Exceptions.h create mode 100644 VectorMatrixLib/MathTools.h diff --git a/VectorMatrixLib/Constants.h b/VectorMatrixLib/Constants.h new file mode 100644 index 0000000..6306462 --- /dev/null +++ b/VectorMatrixLib/Constants.h @@ -0,0 +1,29 @@ +// +// MIT License +// Copyright(c) 2021 - 2026 Olivier BARRY +// +// This file is part of the C++ Aventura Project +// +// VectorMatrix Math Library +// + +#ifndef CONSTANTS_H +#define CONSTANTS_H + +namespace vectormatrix +{ + // Sizes of the vectors and matrices + constexpr int SIZE_2 = 2; + constexpr int SIZE_3 = 3; + constexpr int SIZE_4 = 4; + + // Indices of the axes + constexpr int X_AXIS = 0; + constexpr int Y_AXIS = 1; + constexpr int Z_AXIS = 2; + + // Tolerance used to compare floats (same value as Aventura Constants.EPSILON) + constexpr float EPSILON = 1.0E-4f; +} + +#endif // CONSTANTS_H diff --git a/VectorMatrixLib/Exceptions.h b/VectorMatrixLib/Exceptions.h new file mode 100644 index 0000000..c1481ae --- /dev/null +++ b/VectorMatrixLib/Exceptions.h @@ -0,0 +1,54 @@ +// +// MIT License +// Copyright(c) 2021 - 2026 Olivier BARRY +// +// This file is part of the C++ Aventura Project +// +// VectorMatrix Math Library +// + +#ifndef EXCEPTIONS_H +#define EXCEPTIONS_H + +#include +#include + +namespace vectormatrix +{ + // Base class of all the exceptions of the library (same hierarchy as Aventura) + class Vector3DException : public std::runtime_error + { + public: + explicit Vector3DException(const std::string& message) : std::runtime_error(message) {} + }; + + // An index of a vector or matrix element is out of bound + class IndexOutOfBoundException : public Vector3DException + { + public: + explicit IndexOutOfBoundException(const std::string& message) : Vector3DException(message) {} + }; + + // An array used to set a vector has a wrong size + class VectorArrayWrongSizeException : public Vector3DException + { + public: + explicit VectorArrayWrongSizeException(const std::string& message) : Vector3DException(message) {} + }; + + // An array used to set a matrix has a wrong size + class MatrixArrayWrongSizeException : public Vector3DException + { + public: + explicit MatrixArrayWrongSizeException(const std::string& message) : Vector3DException(message) {} + }; + + // A matrix is singular and cannot be inverted + class NotInvertibleMatrixException : public Vector3DException + { + public: + explicit NotInvertibleMatrixException(const std::string& message = "Matrix is not invertible") : Vector3DException(message) {} + }; +} + +#endif // EXCEPTIONS_H diff --git a/VectorMatrixLib/MathTools.h b/VectorMatrixLib/MathTools.h new file mode 100644 index 0000000..8953221 --- /dev/null +++ b/VectorMatrixLib/MathTools.h @@ -0,0 +1,29 @@ +// +// MIT License +// Copyright(c) 2021 - 2026 Olivier BARRY +// +// This file is part of the C++ Aventura Project +// +// VectorMatrix Math Library +// + +#ifndef MATHTOOLS_H +#define MATHTOOLS_H + +#include +#include "Constants.h" + +namespace vectormatrix +{ + class MathTools + { + public: + // true if a and b are equal within EPSILON tolerance + static bool equals(float a, float b) + { + return std::fabs(a - b) <= EPSILON; + } + }; +} + +#endif // MATHTOOLS_H diff --git a/VectorMatrixLib/Matrix3f.cpp b/VectorMatrixLib/Matrix3f.cpp index f0c7037..8d2d908 100644 --- a/VectorMatrixLib/Matrix3f.cpp +++ b/VectorMatrixLib/Matrix3f.cpp @@ -10,6 +10,7 @@ #include #include "Matrix3f.h" #include "Matrix4f.h" +#include "MathTools.h" namespace vectormatrix { @@ -114,6 +115,19 @@ namespace vectormatrix return v * *this; } + bool Matrix3f::operator==(const Matrix3f& m) const + { + for (int i = 0; i < SIZE3; i++) + for (int j = 0; j < SIZE3; j++) + if (!MathTools::equals(array_[i][j], m.array_[i][j])) return false; + return true; + } + + bool Matrix3f::operator!=(const Matrix3f& m) const + { + return !(*this == m); + } + float Matrix3f::get(int x, int y) const { return array_[x][y]; diff --git a/VectorMatrixLib/Matrix3f.h b/VectorMatrixLib/Matrix3f.h index 7bcb1dc..30fe3c8 100644 --- a/VectorMatrixLib/Matrix3f.h +++ b/VectorMatrixLib/Matrix3f.h @@ -40,6 +40,9 @@ namespace vectormatrix Matrix3f& operator*=(float a); // W = A.V Vector3f operator*(const Vector3f& v) const; + // Equality within EPSILON tolerance + bool operator==(const Matrix3f& m) const; + bool operator!=(const Matrix3f& m) const; // Getters and Setters float get(int x, int y) const; diff --git a/VectorMatrixLib/Matrix4f.cpp b/VectorMatrixLib/Matrix4f.cpp index b455a44..6510831 100644 --- a/VectorMatrixLib/Matrix4f.cpp +++ b/VectorMatrixLib/Matrix4f.cpp @@ -10,6 +10,7 @@ #include #include "Matrix3f.h" #include "Matrix4f.h" +#include "MathTools.h" namespace vectormatrix { @@ -114,6 +115,19 @@ namespace vectormatrix return v * *this; } + bool Matrix4f::operator==(const Matrix4f& m) const + { + for (int i = 0; i < SIZE4; i++) + for (int j = 0; j < SIZE4; j++) + if (!MathTools::equals(array_[i][j], m.array_[i][j])) return false; + return true; + } + + bool Matrix4f::operator!=(const Matrix4f& m) const + { + return !(*this == m); + } + float Matrix4f::get(int x, int y) const { return array_[x][y]; diff --git a/VectorMatrixLib/Matrix4f.h b/VectorMatrixLib/Matrix4f.h index fb41870..601ab75 100644 --- a/VectorMatrixLib/Matrix4f.h +++ b/VectorMatrixLib/Matrix4f.h @@ -40,6 +40,9 @@ namespace vectormatrix Matrix4f& operator*=(float a); // W = A.V Vector4f operator*(const Vector4f& v) const; + // Equality within EPSILON tolerance + bool operator==(const Matrix4f& m) const; + bool operator!=(const Matrix4f& m) const; // Getters and Setters float get(int x, int y) const; diff --git a/VectorMatrixLib/Vector3f.cpp b/VectorMatrixLib/Vector3f.cpp index 3e0ded7..5030d95 100644 --- a/VectorMatrixLib/Vector3f.cpp +++ b/VectorMatrixLib/Vector3f.cpp @@ -9,7 +9,10 @@ #include #include +#include #include "Vector3f.h" +#include "Exceptions.h" +#include "MathTools.h" #include "Vector4f.h" #include "Matrix3f.h" @@ -108,6 +111,16 @@ namespace vectormatrix return x * v.x + y * v.y + z * v.z; } + bool Vector3f::operator==(const Vector3f& v) const + { + return MathTools::equals(x, v.x) && MathTools::equals(y, v.y) && MathTools::equals(z, v.z); + } + + bool Vector3f::operator!=(const Vector3f& v) const + { + return !(*this == v); + } + float Vector3f::get(int i) const { switch (i) { @@ -118,7 +131,7 @@ namespace vectormatrix case 2: return z; default: - return NAN; + throw IndexOutOfBoundException("Index out of bound while getting coordinate (" + std::to_string(i) + ") of Vector3f"); } } @@ -150,7 +163,7 @@ namespace vectormatrix z = a; break; default: - break; + throw IndexOutOfBoundException("Index out of bound while setting coordinate (" + std::to_string(i) + ") of Vector3f"); } } diff --git a/VectorMatrixLib/Vector3f.h b/VectorMatrixLib/Vector3f.h index aa53fa9..4e26266 100644 --- a/VectorMatrixLib/Vector3f.h +++ b/VectorMatrixLib/Vector3f.h @@ -43,8 +43,11 @@ namespace vectormatrix Vector3f& operator/=(float a); // dot operator cannot use * operator as same signature than operator*(const Vector3f& v) except return type but that is not sufficient float dot(const Vector3f& v) const; + // Equality within EPSILON tolerance + bool operator==(const Vector3f& v) const; + bool operator!=(const Vector3f& v) const; - // getter and setter + // getter and setter (get and set throw IndexOutOfBoundException if i is out of bound) float get(int i) const; float getX() const; float getY() const; diff --git a/VectorMatrixLib/Vector4f.cpp b/VectorMatrixLib/Vector4f.cpp index 756bea1..e1202c2 100644 --- a/VectorMatrixLib/Vector4f.cpp +++ b/VectorMatrixLib/Vector4f.cpp @@ -9,7 +9,10 @@ #include #include +#include #include "Vector4f.h" +#include "Exceptions.h" +#include "MathTools.h" #include "Vector3f.h" #include "Matrix4f.h" @@ -117,6 +120,16 @@ namespace vectormatrix return x * v.x + y * v.y + z * v.z + w * v.w; } + bool Vector4f::operator==(const Vector4f& v) const + { + return MathTools::equals(x, v.x) && MathTools::equals(y, v.y) && MathTools::equals(z, v.z) && MathTools::equals(w, v.w); + } + + bool Vector4f::operator!=(const Vector4f& v) const + { + return !(*this == v); + } + float Vector4f::get(int i) const { switch (i) { @@ -129,7 +142,7 @@ namespace vectormatrix case 3: return w; default: - return NAN; + throw IndexOutOfBoundException("Index out of bound while getting coordinate (" + std::to_string(i) + ") of Vector4f"); } } @@ -169,7 +182,7 @@ namespace vectormatrix w = a; break; default: - break; + throw IndexOutOfBoundException("Index out of bound while setting coordinate (" + std::to_string(i) + ") of Vector4f"); } } diff --git a/VectorMatrixLib/Vector4f.h b/VectorMatrixLib/Vector4f.h index eaf46a5..116ced5 100644 --- a/VectorMatrixLib/Vector4f.h +++ b/VectorMatrixLib/Vector4f.h @@ -45,8 +45,11 @@ namespace vectormatrix Vector4f& operator/=(float a); // dot operator cannot use * operator as same signature than operator*(const Vector4f& v) except return type but that is not sufficient float dot(const Vector4f& v) const; + // Equality within EPSILON tolerance + bool operator==(const Vector4f& v) const; + bool operator!=(const Vector4f& v) const; - // getter and setter + // getter and setter (get and set throw IndexOutOfBoundException if i is out of bound) float get(int i) const; float getX() const; float getY() const; diff --git a/VectorMatrixLib/VectorMatrixLib.vcxproj b/VectorMatrixLib/VectorMatrixLib.vcxproj index 50e2969..9ea0970 100644 --- a/VectorMatrixLib/VectorMatrixLib.vcxproj +++ b/VectorMatrixLib/VectorMatrixLib.vcxproj @@ -143,6 +143,9 @@ + + + diff --git a/VectorMatrixLib/VectorMatrixLib.vcxproj.filters b/VectorMatrixLib/VectorMatrixLib.vcxproj.filters index 8ee0b22..fe6fdf0 100644 --- a/VectorMatrixLib/VectorMatrixLib.vcxproj.filters +++ b/VectorMatrixLib/VectorMatrixLib.vcxproj.filters @@ -27,6 +27,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + diff --git a/tests/TestVectorMatrix.cpp b/tests/TestVectorMatrix.cpp index 83e35b9..f0afd54 100644 --- a/tests/TestVectorMatrix.cpp +++ b/tests/TestVectorMatrix.cpp @@ -11,16 +11,18 @@ // #include +#include #include #include "Vector3f.h" #include "Vector4f.h" #include "Matrix3f.h" #include "Matrix4f.h" +#include "MathTools.h" +#include "Exceptions.h" using namespace vectormatrix; static int nb_failures = 0; -static const float EPSILON = 1.0E-4f; static void check(bool condition, const char* expression, const char* file, int line) { @@ -32,25 +34,38 @@ static void check(bool condition, const char* expression, const char* file, int } #define CHECK(expr) check((expr), #expr, __FILE__, __LINE__) -#define CHECK_NEAR(a, b) check(std::fabs((a) - (b)) <= EPSILON, #a " == " #b, __FILE__, __LINE__) +#define CHECK_THROWS(expr, Exception) \ + do { bool thrown = false; try { (void)(expr); } catch (const Exception&) { thrown = true; } check(thrown, #expr " throws " #Exception, __FILE__, __LINE__); } while (0) +#define CHECK_NEAR(a, b) check(MathTools::equals((a), (b)), #a " == " #b, __FILE__, __LINE__) -static bool equals(const Vector3f& a, const Vector3f& b) +static void testMathTools() { - return std::fabs(a.getX() - b.getX()) <= EPSILON && std::fabs(a.getY() - b.getY()) <= EPSILON && std::fabs(a.getZ() - b.getZ()) <= EPSILON; -} - -static bool equals(const Vector4f& a, const Vector4f& b) -{ - return std::fabs(a.getX() - b.getX()) <= EPSILON && std::fabs(a.getY() - b.getY()) <= EPSILON && std::fabs(a.getZ() - b.getZ()) <= EPSILON && std::fabs(a.getW() - b.getW()) <= EPSILON; -} - -template -static bool equals(const M& a, const M& b, int size) -{ - for (int i = 0; i < size; i++) - for (int j = 0; j < size; j++) - if (std::fabs(a.get(i, j) - b.get(i, j)) > EPSILON) return false; - return true; + CHECK(MathTools::equals(1.0f, 1.0f + EPSILON / 2)); + CHECK(!MathTools::equals(1.0f, 1.0f + EPSILON * 2)); + + CHECK(Vector3f(1, 2, 3) == Vector3f(1, 2, 3.00001f)); + CHECK(Vector3f(1, 2, 3) != Vector3f(1, 2, 3.01f)); + CHECK(Vector4f(1, 2, 3, 4) == Vector4f(1, 2, 3, 4.00001f)); + CHECK(Vector4f(1, 2, 3, 4) != Vector4f(1, 2, 3, 4.01f)); + Matrix3f m3(1); + Matrix3f n3(1); + n3.set(2, 1, 1.00001f); + CHECK(m3 == n3); + n3.set(2, 1, 1.01f); + CHECK(m3 != n3); + Matrix4f m4(1); + Matrix4f n4(1); + n4.set(3, 3, 1.01f); + CHECK(m4 != n4); + + try + { + throw NotInvertibleMatrixException(); + } + catch (const Vector3DException& e) + { + CHECK(std::string(e.what()) == "Matrix is not invertible"); + } } static void testVector3f() @@ -58,34 +73,35 @@ static void testVector3f() Vector3f a(1, 2, 3); Vector3f b(4, 5, 6); - CHECK(equals(a + b, Vector3f(5, 7, 9))); - CHECK(equals(b - a, Vector3f(3, 3, 3))); - CHECK(equals(a * 2, Vector3f(2, 4, 6))); - CHECK(equals(a / 2, Vector3f(0.5f, 1, 1.5f))); + CHECK(a + b == Vector3f(5, 7, 9)); + CHECK(b - a == Vector3f(3, 3, 3)); + CHECK(a * 2 == Vector3f(2, 4, 6)); + CHECK(a / 2 == Vector3f(0.5f, 1, 1.5f)); CHECK_NEAR(a.dot(b), 32.0f); - CHECK(equals(a * b, Vector3f(-3, 6, -3))); // cross product + CHECK(a * b == Vector3f(-3, 6, -3)); // cross product Vector3f c(a); (c += b) -= a; // compound assignments return a reference and can be chained - CHECK(equals(c, b)); + CHECK(c == b); c *= 2; c /= 4; - CHECK(equals(c, Vector3f(2, 2.5f, 3))); + CHECK(c == Vector3f(2, 2.5f, 3)); float array[3] = { 7, 8, 9 }; Vector3f d(array); - CHECK(equals(d, Vector3f(7, 8, 9))); + CHECK(d == Vector3f(7, 8, 9)); d.set(1, 0); CHECK_NEAR(d.get(1), 0.0f); - CHECK(std::isnan(d.get(3))); + CHECK_THROWS(d.get(3), IndexOutOfBoundException); + CHECK_THROWS(d.set(-1, 0), IndexOutOfBoundException); Vector3f e(3, 0, 4); CHECK_NEAR(e.length(), 5.0f); CHECK_NEAR(e.normalize().length(), 1.0f); - CHECK(equals(e, Vector3f(0.6f, 0, 0.8f))); // normalize() modifies this vector + CHECK(e == Vector3f(0.6f, 0, 0.8f)); // normalize() modifies this vector - CHECK(equals(Vector3f::interpolate(a, b, 0.5f), Vector3f(2.5f, 3.5f, 4.5f))); - CHECK(equals(a.V4(), Vector4f(1, 2, 3, 0))); + CHECK(Vector3f::interpolate(a, b, 0.5f) == Vector3f(2.5f, 3.5f, 4.5f)); + CHECK(a.V4() == Vector4f(1, 2, 3, 0)); } static void testVector4f() @@ -93,31 +109,32 @@ static void testVector4f() Vector4f a(1, 2, 3, 4); Vector4f b(5, 6, 7, 8); - CHECK(equals(a + b, Vector4f(6, 8, 10, 12))); - CHECK(equals(b - a, Vector4f(4, 4, 4, 4))); - CHECK(equals(a * 2, Vector4f(2, 4, 6, 8))); - CHECK(equals(a / 2, Vector4f(0.5f, 1, 1.5f, 2))); + CHECK(a + b == Vector4f(6, 8, 10, 12)); + CHECK(b - a == Vector4f(4, 4, 4, 4)); + CHECK(a * 2 == Vector4f(2, 4, 6, 8)); + CHECK(a / 2 == Vector4f(0.5f, 1, 1.5f, 2)); CHECK_NEAR(a.dot(b), 70.0f); - CHECK(equals(Vector4f(1, 0, 0, 0) * Vector4f(0, 1, 0, 0), Vector4f(0, 0, 1, 0))); // cross product + CHECK(Vector4f(1, 0, 0, 0) * Vector4f(0, 1, 0, 0) == Vector4f(0, 0, 1, 0)); // cross product Vector4f c; c.setX(1); c.setY(2); c.setZ(3); c.setW(4); - CHECK(equals(c, a)); // setW used to modify z instead of w + CHECK(c == a); // setW used to modify z instead of w c.set(3, 9); CHECK_NEAR(c.getW(), 9.0f); CHECK_NEAR(c.getZ(), 3.0f); - CHECK(std::isnan(c.get(4))); + CHECK_THROWS(c.get(4), IndexOutOfBoundException); + CHECK_THROWS(c.set(4, 0), Vector3DException); // caught through the base class Vector4f e(0, 3, 0, 4); CHECK_NEAR(e.length(), 5.0f); e.normalize(); - CHECK(equals(e, Vector4f(0, 0.6f, 0, 0.8f))); + CHECK(e == Vector4f(0, 0.6f, 0, 0.8f)); - CHECK(equals(Vector4f(Vector3f(1, 2, 3)), Vector4f(1, 2, 3, 0))); - CHECK(equals(Vector4f::interpolate(a, b, 0.25f), Vector4f(2, 3, 4, 5))); + CHECK(Vector4f(Vector3f(1, 2, 3)) == Vector4f(1, 2, 3, 0)); + CHECK(Vector4f::interpolate(a, b, 0.25f) == Vector4f(2, 3, 4, 5)); } static void testMatrix3f() @@ -128,27 +145,27 @@ static void testMatrix3f() Matrix3f a(va); Matrix3f b(vb); - CHECK(equals(a + b, Matrix3f(10), 3)); - CHECK(equals((a + b) - b, a, 3)); - CHECK(equals(a * b, Matrix3f(vab), 3)); - CHECK(equals(a * 2.0f, a + a, 3)); + CHECK(a + b == Matrix3f(10)); + CHECK((a + b) - b == a); + CHECK(a * b == Matrix3f(vab)); + CHECK(a * 2.0f == a + a); Matrix3f c(a); c += b; c -= b; // -= used to add instead of subtract - CHECK(equals(c, a, 3)); + CHECK(c == a); c *= b; - CHECK(equals(c, Matrix3f(vab), 3)); + CHECK(c == Matrix3f(vab)); c = a; c *= 2; - CHECK(equals(c, a * 2.0f, 3)); + CHECK(c == a * 2.0f); // W = A.V Vector3f v(1, 0, -1); - CHECK(equals(a * v, Vector3f(-2, -2, -2))); - CHECK(equals(v * a, a * v)); + CHECK(a * v == Vector3f(-2, -2, -2)); + CHECK(v * a == a * v); v *= a; - CHECK(equals(v, Vector3f(-2, -2, -2))); + CHECK(v == Vector3f(-2, -2, -2)); } static void testMatrix4f() @@ -158,21 +175,21 @@ static void testMatrix4f() Matrix4f id; for (int i = 0; i < Matrix4f::SIZE4; i++) id.set(i, i, 1); - CHECK(equals(a * id, a, 4)); - CHECK(equals(id * a, a, 4)); - CHECK(equals(a + a, a * 2.0f, 4)); - CHECK(equals((a + a) - a, a, 4)); + CHECK(a * id == a); + CHECK(id * a == a); + CHECK(a + a == a * 2.0f); + CHECK((a + a) - a == a); Matrix4f c(a); c *= id; c -= a; - CHECK(equals(c, Matrix4f(), 4)); + CHECK(c == Matrix4f()); // W = A.V Vector4f v(1, 1, 1, 1); - CHECK(equals(a * v, Vector4f(10, 26, 42, 58))); + CHECK(a * v == Vector4f(10, 26, 42, 58)); v *= a; - CHECK(equals(v, Vector4f(10, 26, 42, 58))); + CHECK(v == Vector4f(10, 26, 42, 58)); // Conversions between Matrix3f and Matrix4f Matrix3f m3(a); @@ -191,6 +208,7 @@ static void testMatrix4f() int main() { + testMathTools(); testVector3f(); testVector4f(); testMatrix3f();