From 50c4b614886d577b2d740affce8533e5a9b907d5 Mon Sep 17 00:00:00 2001 From: Olivier BARRY Date: Sun, 4 Oct 2026 11:05:19 +0200 Subject: [PATCH 1/2] Complete Vector3f, Vector4f, Matrix3f and Matrix4f as in Aventura Vectors: - Static axes, zeroVector and zeroPoint - Constructors from std::vector (VectorArrayWrongSizeException), from 2 points, from a matrix row or column, Vector3f from Vector4f - cross/crossEquals (operator* stays an alias), set(x, y, z[, w]), lengthSquared, distance, distanceSquared, toArray - Vector4f: get3DX/Y/Z, get3DPoint (std::optional, empty if w is 0), V3, + and - with a Vector3f, isPoint/isVector/point/vector Matrices: - identity, setDiagonal, getRow/getColumn/setRow/setColumn (IndexOutOfBoundException), getArray/setArray (MatrixArrayWrongSizeException), trace, isIdentity, determinant, transpose/transposeEquals, Matrix4f::getMatrix3 - inverse() via a GaussJordanSolver template shared by both sizes (NotInvertibleMatrixException on a singular matrix) The Visual Studio project now builds in C++17 (needed for std::optional). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01FmLcUnAfhtVMCNPGNw5Lxb --- VectorMatrixLib/GaussJordanSolver.h | 106 +++++++++ VectorMatrixLib/Matrix3f.cpp | 103 +++++++++ VectorMatrixLib/Matrix3f.h | 21 ++ VectorMatrixLib/Matrix4f.cpp | 124 +++++++++++ VectorMatrixLib/Matrix4f.h | 23 ++ VectorMatrixLib/Vector3f.cpp | 106 +++++++++ VectorMatrixLib/Vector3f.h | 33 ++- VectorMatrixLib/Vector4f.cpp | 167 ++++++++++++++ VectorMatrixLib/Vector4f.h | 51 ++++- VectorMatrixLib/VectorMatrixLib.vcxproj | 5 + .../VectorMatrixLib.vcxproj.filters | 3 + tests/TestVectorMatrix.cpp | 209 ++++++++++++++++++ 12 files changed, 948 insertions(+), 3 deletions(-) create mode 100644 VectorMatrixLib/GaussJordanSolver.h diff --git a/VectorMatrixLib/GaussJordanSolver.h b/VectorMatrixLib/GaussJordanSolver.h new file mode 100644 index 0000000..ad4b518 --- /dev/null +++ b/VectorMatrixLib/GaussJordanSolver.h @@ -0,0 +1,106 @@ +// +// MIT License +// Copyright(c) 2021 - 2026 Olivier BARRY +// +// This file is part of the C++ Aventura Project +// +// VectorMatrix Math Library +// + +#ifndef GAUSSJORDANSOLVER_H +#define GAUSSJORDANSOLVER_H + +#include +#include "Exceptions.h" + +namespace vectormatrix +{ + // Matrix inversion by Gauss-Jordan elimination with partial pivoting, + // shared by Matrix3f and Matrix4f (same algorithm as Aventura GaussJordanSolver) + class GaussJordanSolver + { + public: + // Invert the N x N matrix a into result. + // Throws NotInvertibleMatrixException if a pivot is smaller than epsilon (singular matrix). + template + static void invert(const float (&a)[N][N], float (&result)[N][N], float epsilon) + { + float matrix[N][N]; + for (int i = 0; i < N; i++) + for (int j = 0; j < N; j++) + { + matrix[i][j] = a[i][j]; + result[i][j] = (i == j) ? 1.0f : 0.0f; + } + + int r = 0; // last pivot row + + // Browsing columns one by one + for (int j = 0; j < N; j++) + { + int k = indiceOfMaxRowInColumn(matrix, j, r); + float pivot = matrix[k][j]; + + if (std::fabs(pivot) < epsilon) throw NotInvertibleMatrixException(); + + // Divide all the row by the pivot to reduce the pivot to 1 + for (int col = 0; col < N; col++) + { + matrix[k][col] /= pivot; + result[k][col] /= pivot; + } + // Swap the rows k and r + if (r != k) + { + swapRows(matrix, r, k); + swapRows(result, r, k); + } + for (int i = 0; i < N; i++) + { + if (i != r) + { + float matrix_ij = matrix[i][j]; + for (int col = 0; col < N; col++) + { + matrix[i][col] -= matrix[r][col] * matrix_ij; + result[i][col] -= result[r][col] * matrix_ij; + } + } + } + r++; + } + } + + private: + template + static void swapRows(float (&a)[N][N], int r1, int r2) + { + for (int col = 0; col < N; col++) + { + float tmp = a[r1][col]; + a[r1][col] = a[r2][col]; + a[r2][col] = tmp; + } + } + + // Row index (from pivot row down) of the greatest absolute value in column col + template + static int indiceOfMaxRowInColumn(const float (&m)[N][N], int col, int pivot) + { + float max = std::fabs(m[pivot][col]); + int indiceMax = pivot; + for (int i = pivot + 1; i < N; i++) + { + float val = std::fabs(m[i][col]); + if (max < val) + { + max = val; + indiceMax = i; + } + } + return indiceMax; + } + }; +} + +#endif // GAUSSJORDANSOLVER_H diff --git a/VectorMatrixLib/Matrix3f.cpp b/VectorMatrixLib/Matrix3f.cpp index 8d2d908..e9f4052 100644 --- a/VectorMatrixLib/Matrix3f.cpp +++ b/VectorMatrixLib/Matrix3f.cpp @@ -8,9 +8,12 @@ // #include +#include #include "Matrix3f.h" #include "Matrix4f.h" #include "MathTools.h" +#include "Exceptions.h" +#include "GaussJordanSolver.h" namespace vectormatrix { @@ -138,6 +141,106 @@ namespace vectormatrix array_[x][y] = a; } + Matrix3f Matrix3f::identity() + { + Matrix3f r; + r.setDiagonal(1); + return r; + } + + void Matrix3f::setDiagonal(float v) + { + for (int i = 0; i < SIZE3; i++) + array_[i][i] = v; + } + + Vector3f Matrix3f::getRow(int r) const + { + if (r < 0 || r >= SIZE3) throw IndexOutOfBoundException("Index out of bound while getting Row (" + std::to_string(r) + ") of Matrix3f"); + return Vector3f(r, *this); + } + + Vector3f Matrix3f::getColumn(int c) const + { + if (c < 0 || c >= SIZE3) throw IndexOutOfBoundException("Index out of bound while getting Column (" + std::to_string(c) + ") of Matrix3f"); + return Vector3f(*this, c); + } + + void Matrix3f::setRow(int r, const Vector3f& v) + { + if (r < 0 || r >= SIZE3) throw IndexOutOfBoundException("Index out of bound while setting Row (" + std::to_string(r) + ") of Matrix3f"); + for (int j = 0; j < SIZE3; j++) + array_[r][j] = v.get(j); + } + + void Matrix3f::setColumn(int c, const Vector3f& v) + { + if (c < 0 || c >= SIZE3) throw IndexOutOfBoundException("Index out of bound while setting Column (" + std::to_string(c) + ") of Matrix3f"); + for (int i = 0; i < SIZE3; i++) + array_[i][c] = v.get(i); + } + + std::vector> Matrix3f::getArray() const + { + std::vector> a(SIZE3, std::vector(SIZE3)); + for (int i = 0; i < SIZE3; i++) + for (int j = 0; j < SIZE3; j++) + a[i][j] = array_[i][j]; + return a; + } + + void Matrix3f::setArray(const std::vector>& a) + { + if (a.size() != SIZE3) throw MatrixArrayWrongSizeException("Wrong array row size (" + std::to_string(a.size()) + ") while setting Matrix3f from array"); + for (int i = 0; i < SIZE3; i++) + if (a[i].size() != SIZE3) throw MatrixArrayWrongSizeException("Wrong array column size (" + std::to_string(a[i].size()) + ") while setting Matrix3f from array"); + for (int i = 0; i < SIZE3; i++) + for (int j = 0; j < SIZE3; j++) + array_[i][j] = a[i][j]; + } + + float Matrix3f::trace() const + { + float t = 0; + for (int i = 0; i < SIZE3; i++) + t += array_[i][i]; + return t; + } + + bool Matrix3f::isIdentity() const + { + return *this == identity(); + } + + float Matrix3f::determinant() const + { + return array_[0][0] * (array_[1][1] * array_[2][2] - array_[1][2] * array_[2][1]) + - array_[0][1] * (array_[1][0] * array_[2][2] - array_[1][2] * array_[2][0]) + + array_[0][2] * (array_[1][0] * array_[2][1] - array_[1][1] * array_[2][0]); + } + + Matrix3f Matrix3f::transpose() const + { + Matrix3f r; + for (int i = 0; i < SIZE3; i++) + for (int j = 0; j < SIZE3; j++) + r.array_[i][j] = array_[j][i]; + return r; + } + + Matrix3f& Matrix3f::transposeEquals() + { + *this = transpose(); + return *this; + } + + Matrix3f Matrix3f::inverse() const + { + Matrix3f r; + GaussJordanSolver::invert(array_, r.array_, EPSILON); + return r; + } + std::ostream& operator<<(std::ostream& strm, const Matrix3f& m) { strm << "Matrix3f(" << std::endl; diff --git a/VectorMatrixLib/Matrix3f.h b/VectorMatrixLib/Matrix3f.h index 30fe3c8..fb4f901 100644 --- a/VectorMatrixLib/Matrix3f.h +++ b/VectorMatrixLib/Matrix3f.h @@ -11,6 +11,7 @@ #define MATRIX3F_H #include +#include #include "Vector3f.h" namespace vectormatrix @@ -23,6 +24,9 @@ namespace vectormatrix // Constants static const int SIZE3 = 3; + // Identity matrix (a new copy is returned each time) + static Matrix3f identity(); + // Constructors Matrix3f(); explicit Matrix3f(float a); @@ -47,6 +51,23 @@ namespace vectormatrix // Getters and Setters float get(int x, int y) const; void set(int x, int y, float a); + void setDiagonal(float v); + // Rows and columns (throw IndexOutOfBoundException if the index is out of bound) + Vector3f getRow(int r) const; + Vector3f getColumn(int c) const; + void setRow(int r, const Vector3f& v); + void setColumn(int c, const Vector3f& v); + // Copy of the elements, and set from an array (throws MatrixArrayWrongSizeException if not 3x3) + std::vector> getArray() const; + void setArray(const std::vector>& a); + // Other methods + float trace() const; + bool isIdentity() const; + float determinant() const; + Matrix3f transpose() const; + Matrix3f& transposeEquals(); + // Throws NotInvertibleMatrixException if this matrix is singular + Matrix3f inverse() const; private: float array_[SIZE3][SIZE3]; diff --git a/VectorMatrixLib/Matrix4f.cpp b/VectorMatrixLib/Matrix4f.cpp index 6510831..ac67893 100644 --- a/VectorMatrixLib/Matrix4f.cpp +++ b/VectorMatrixLib/Matrix4f.cpp @@ -8,9 +8,12 @@ // #include +#include #include "Matrix3f.h" #include "Matrix4f.h" #include "MathTools.h" +#include "Exceptions.h" +#include "GaussJordanSolver.h" namespace vectormatrix { @@ -138,6 +141,127 @@ namespace vectormatrix array_[x][y] = a; } + Matrix4f Matrix4f::identity() + { + Matrix4f r; + r.setDiagonal(1); + return r; + } + + void Matrix4f::setDiagonal(float v) + { + for (int i = 0; i < SIZE4; i++) + array_[i][i] = v; + } + + Vector4f Matrix4f::getRow(int r) const + { + if (r < 0 || r >= SIZE4) throw IndexOutOfBoundException("Index out of bound while getting Row (" + std::to_string(r) + ") of Matrix4f"); + return Vector4f(r, *this); + } + + Vector4f Matrix4f::getColumn(int c) const + { + if (c < 0 || c >= SIZE4) throw IndexOutOfBoundException("Index out of bound while getting Column (" + std::to_string(c) + ") of Matrix4f"); + return Vector4f(*this, c); + } + + void Matrix4f::setRow(int r, const Vector4f& v) + { + if (r < 0 || r >= SIZE4) throw IndexOutOfBoundException("Index out of bound while setting Row (" + std::to_string(r) + ") of Matrix4f"); + for (int j = 0; j < SIZE4; j++) + array_[r][j] = v.get(j); + } + + void Matrix4f::setColumn(int c, const Vector4f& v) + { + if (c < 0 || c >= SIZE4) throw IndexOutOfBoundException("Index out of bound while setting Column (" + std::to_string(c) + ") of Matrix4f"); + for (int i = 0; i < SIZE4; i++) + array_[i][c] = v.get(i); + } + + std::vector> Matrix4f::getArray() const + { + std::vector> a(SIZE4, std::vector(SIZE4)); + for (int i = 0; i < SIZE4; i++) + for (int j = 0; j < SIZE4; j++) + a[i][j] = array_[i][j]; + return a; + } + + void Matrix4f::setArray(const std::vector>& a) + { + if (a.size() != SIZE4) throw MatrixArrayWrongSizeException("Wrong array row size (" + std::to_string(a.size()) + ") while setting Matrix4f from array"); + for (int i = 0; i < SIZE4; i++) + if (a[i].size() != SIZE4) throw MatrixArrayWrongSizeException("Wrong array column size (" + std::to_string(a[i].size()) + ") while setting Matrix4f from array"); + for (int i = 0; i < SIZE4; i++) + for (int j = 0; j < SIZE4; j++) + array_[i][j] = a[i][j]; + } + + Matrix3f Matrix4f::getMatrix3() const + { + return Matrix3f(*this); + } + + float Matrix4f::trace() const + { + float t = 0; + for (int i = 0; i < SIZE4; i++) + t += array_[i][i]; + return t; + } + + bool Matrix4f::isIdentity() const + { + return *this == identity(); + } + + float Matrix4f::determinant() const + { + // Cofactor expansion along the first row + float det = 0; + for (int col = 0; col < SIZE4; col++) + { + // 3x3 minor obtained by removing row 0 and column col + Matrix3f minor; + for (int i = 1; i < SIZE4; i++) + { + int mj = 0; + for (int j = 0; j < SIZE4; j++) + { + if (j == col) continue; + minor.set(i - 1, mj++, array_[i][j]); + } + } + float sign = (col % 2 == 0) ? 1.0f : -1.0f; + det += sign * array_[0][col] * minor.determinant(); + } + return det; + } + + Matrix4f Matrix4f::transpose() const + { + Matrix4f r; + for (int i = 0; i < SIZE4; i++) + for (int j = 0; j < SIZE4; j++) + r.array_[i][j] = array_[j][i]; + return r; + } + + Matrix4f& Matrix4f::transposeEquals() + { + *this = transpose(); + return *this; + } + + Matrix4f Matrix4f::inverse() const + { + Matrix4f r; + GaussJordanSolver::invert(array_, r.array_, EPSILON); + return r; + } + std::ostream& operator<<(std::ostream& strm, const Matrix4f& m) { strm << "Matrix4f(" << std::endl; diff --git a/VectorMatrixLib/Matrix4f.h b/VectorMatrixLib/Matrix4f.h index 601ab75..75efd5c 100644 --- a/VectorMatrixLib/Matrix4f.h +++ b/VectorMatrixLib/Matrix4f.h @@ -11,6 +11,7 @@ #define MATRIX4F_H #include +#include #include "Vector4f.h" namespace vectormatrix @@ -23,6 +24,9 @@ namespace vectormatrix // Constants static const int SIZE4 = 4; + // Identity matrix (a new copy is returned each time) + static Matrix4f identity(); + // Constructors Matrix4f(); explicit Matrix4f(float a); @@ -47,6 +51,25 @@ namespace vectormatrix // Getters and Setters float get(int x, int y) const; void set(int x, int y, float a); + void setDiagonal(float v); + // Rows and columns (throw IndexOutOfBoundException if the index is out of bound) + Vector4f getRow(int r) const; + Vector4f getColumn(int c) const; + void setRow(int r, const Vector4f& v); + void setColumn(int c, const Vector4f& v); + // Copy of the elements, and set from an array (throws MatrixArrayWrongSizeException if not 4x4) + std::vector> getArray() const; + void setArray(const std::vector>& a); + // Upper-left 3x3 part of this matrix + Matrix3f getMatrix3() const; + // Other methods + float trace() const; + bool isIdentity() const; + float determinant() const; + Matrix4f transpose() const; + Matrix4f& transposeEquals(); + // Throws NotInvertibleMatrixException if this matrix is singular + Matrix4f inverse() const; private: float array_[SIZE4][SIZE4]; diff --git a/VectorMatrixLib/Vector3f.cpp b/VectorMatrixLib/Vector3f.cpp index 5030d95..68be059 100644 --- a/VectorMatrixLib/Vector3f.cpp +++ b/VectorMatrixLib/Vector3f.cpp @@ -18,6 +18,41 @@ namespace vectormatrix { + Vector3f Vector3f::xAxis() + { + return Vector3f(1, 0, 0); + } + + Vector3f Vector3f::yAxis() + { + return Vector3f(0, 1, 0); + } + + Vector3f Vector3f::zAxis() + { + return Vector3f(0, 0, 1); + } + + Vector3f Vector3f::xOppAxis() + { + return Vector3f(-1, 0, 0); + } + + Vector3f Vector3f::yOppAxis() + { + return Vector3f(0, -1, 0); + } + + Vector3f Vector3f::zOppAxis() + { + return Vector3f(0, 0, -1); + } + + Vector3f Vector3f::zeroVector() + { + return Vector3f(0, 0, 0); + } + Vector3f::Vector3f() : x(0), y(0), z(0) { } @@ -34,6 +69,30 @@ namespace vectormatrix { } + Vector3f::Vector3f(const std::vector& array) + { + if (array.size() < 3) throw VectorArrayWrongSizeException("Array passed in parameter of Vector3f constructor is out of bound: " + std::to_string(array.size())); + x = array[0]; + y = array[1]; + z = array[2]; + } + + Vector3f::Vector3f(const Vector4f& v) : x(v.getX()), y(v.getY()), z(v.getZ()) + { + } + + Vector3f::Vector3f(const Vector4f& a, const Vector4f& b) : x(b.getX() - a.getX()), y(b.getY() - a.getY()), z(b.getZ() - a.getZ()) + { + } + + Vector3f::Vector3f(int r, const Matrix3f& A) : x(A.get(r, 0)), y(A.get(r, 1)), z(A.get(r, 2)) + { + } + + Vector3f::Vector3f(const Matrix3f& A, int c) : x(A.get(0, c)), y(A.get(1, c)), z(A.get(2, c)) + { + } + Vector3f Vector3f::operator+(const Vector3f& v) const { return Vector3f(x + v.x, y + v.y, z + v.z); @@ -61,11 +120,22 @@ namespace vectormatrix } Vector3f Vector3f::operator*(const Vector3f& v) const + { + return cross(v); + } + + Vector3f Vector3f::cross(const Vector3f& v) const { // a=(a1,a2,a3) and b=(b1,b2,b3) then a^b=(a2b3-a3b2, a3b1-a1b3, a1b2-a2b1) return Vector3f(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x); } + Vector3f& Vector3f::crossEquals(const Vector3f& v) + { + *this = cross(v); + return *this; + } + Vector3f Vector3f::operator*(const Matrix3f& m) const { return Vector3f( @@ -167,6 +237,13 @@ namespace vectormatrix } } + void Vector3f::set(float x, float y, float z) + { + this->x = x; + this->y = y; + this->z = z; + } + void Vector3f::setX(float v) { x = v; @@ -187,6 +264,22 @@ namespace vectormatrix return std::sqrt(x * x + y * y + z * z); } + float Vector3f::lengthSquared() const + { + return x * x + y * y + z * z; + } + + float Vector3f::distance(const Vector3f& v) const + { + return std::sqrt(distanceSquared(v)); + } + + float Vector3f::distanceSquared(const Vector3f& v) const + { + float dx = x - v.x, dy = y - v.y, dz = z - v.z; + return dx * dx + dy * dy + dz * dz; + } + Vector3f& Vector3f::normalize() { float length = this->length(); @@ -201,6 +294,19 @@ namespace vectormatrix return Vector4f(*this); } + std::array Vector3f::toArray() const + { + return { x, y, z }; + } + + float* Vector3f::toArray(float* dest) const + { + dest[0] = x; + dest[1] = y; + dest[2] = z; + return dest; + } + // static member function (see declaration) Vector3f Vector3f::interpolate(const Vector3f& v1, const Vector3f& v2, float t) { diff --git a/VectorMatrixLib/Vector3f.h b/VectorMatrixLib/Vector3f.h index 4e26266..a15daa0 100644 --- a/VectorMatrixLib/Vector3f.h +++ b/VectorMatrixLib/Vector3f.h @@ -10,7 +10,9 @@ #ifndef VECTOR3F_H #define VECTOR3F_H +#include #include +#include namespace vectormatrix { @@ -21,18 +23,37 @@ namespace vectormatrix { public: + // Static vectors (a new copy is returned each time) + static Vector3f xAxis(); + static Vector3f yAxis(); + static Vector3f zAxis(); + static Vector3f xOppAxis(); + static Vector3f yOppAxis(); + static Vector3f zOppAxis(); + static Vector3f zeroVector(); + // Constructors Vector3f(); explicit Vector3f(float a); Vector3f(float x, float y, float z); explicit Vector3f(const float array[3]); + // Throws VectorArrayWrongSizeException if the array has less than 3 elements + explicit Vector3f(const std::vector& array); + // The 3 first coordinates of v (w is ignored) + explicit Vector3f(const Vector4f& v); + // Vector ab from 2 points a and b (w is ignored) + Vector3f(const Vector4f& a, const Vector4f& b); + // Row r of matrix A + Vector3f(int r, const Matrix3f& A); + // Column c of matrix A + Vector3f(const Matrix3f& A, int c); // Operators Vector3f operator+(const Vector3f& v) const; Vector3f& operator+=(const Vector3f& v); Vector3f operator-(const Vector3f& v) const; Vector3f& operator-=(const Vector3f& v); - // Cross product + // Cross product (same as cross()) Vector3f operator*(const Vector3f& v) const; // W = A.V (same semantic as Java Vector3.times(Matrix3)) Vector3f operator*(const Matrix3f& m) const; @@ -43,6 +64,9 @@ 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; + // Cross product V^W, cross returns a new vector, crossEquals modifies this vector + Vector3f cross(const Vector3f& v) const; + Vector3f& crossEquals(const Vector3f& v); // Equality within EPSILON tolerance bool operator==(const Vector3f& v) const; bool operator!=(const Vector3f& v) const; @@ -54,15 +78,22 @@ namespace vectormatrix float getZ() const; void set(int i, float v); + void set(float x, float y, float z); void setX(float v); void setY(float v); void setZ(float v); // Other methods float length() const; + float lengthSquared() const; + float distance(const Vector3f& v) const; + float distanceSquared(const Vector3f& v) const; // Normalize this vector (modified) and return it Vector3f& normalize(); Vector4f V4() const; + std::array toArray() const; + // Copy the coordinates in dest (at least 3 elements) and return dest + float* toArray(float* dest) const; // Static methods static Vector3f interpolate(const Vector3f& v1, const Vector3f& v2, float t); diff --git a/VectorMatrixLib/Vector4f.cpp b/VectorMatrixLib/Vector4f.cpp index e1202c2..7a54233 100644 --- a/VectorMatrixLib/Vector4f.cpp +++ b/VectorMatrixLib/Vector4f.cpp @@ -18,6 +18,46 @@ namespace vectormatrix { + Vector4f Vector4f::xAxis() + { + return Vector4f(1, 0, 0, 0); + } + + Vector4f Vector4f::yAxis() + { + return Vector4f(0, 1, 0, 0); + } + + Vector4f Vector4f::zAxis() + { + return Vector4f(0, 0, 1, 0); + } + + Vector4f Vector4f::xOppAxis() + { + return Vector4f(-1, 0, 0, 0); + } + + Vector4f Vector4f::yOppAxis() + { + return Vector4f(0, -1, 0, 0); + } + + Vector4f Vector4f::zOppAxis() + { + return Vector4f(0, 0, -1, 0); + } + + Vector4f Vector4f::zeroVector() + { + return Vector4f(0, 0, 0, 0); + } + + Vector4f Vector4f::zeroPoint() + { + return Vector4f(0, 0, 0, 1); + } + Vector4f::Vector4f() : x(0), y(0), z(0), w(0) { } @@ -34,10 +74,31 @@ namespace vectormatrix { } + Vector4f::Vector4f(const std::vector& array) + { + if (array.size() < 4) throw VectorArrayWrongSizeException("Array passed in parameter of Vector4f constructor is out of bound: " + std::to_string(array.size())); + x = array[0]; + y = array[1]; + z = array[2]; + w = array[3]; + } + Vector4f::Vector4f(const Vector3f& v) : x(v.getX()), y(v.getY()), z(v.getZ()), w(0) { } + Vector4f::Vector4f(const Vector4f& a, const Vector4f& b) : x(b.x - a.x), y(b.y - a.y), z(b.z - a.z), w(b.w - a.w) + { + } + + Vector4f::Vector4f(int r, const Matrix4f& A) : x(A.get(r, 0)), y(A.get(r, 1)), z(A.get(r, 2)), w(A.get(r, 3)) + { + } + + Vector4f::Vector4f(const Matrix4f& A, int c) : x(A.get(0, c)), y(A.get(1, c)), z(A.get(2, c)), w(A.get(3, c)) + { + } + Vector4f Vector4f::operator+(const Vector4f& v) const { return Vector4f(x + v.x, y + v.y, z + v.z, w + v.w); @@ -66,12 +127,33 @@ namespace vectormatrix return *this; } + Vector4f Vector4f::operator+(const Vector3f& v) const + { + return Vector4f(x + v.getX(), y + v.getY(), z + v.getZ(), w); + } + + Vector4f Vector4f::operator-(const Vector3f& v) const + { + return Vector4f(x - v.getX(), y - v.getY(), z - v.getZ(), w); + } + Vector4f Vector4f::operator*(const Vector4f& v) const + { + return cross(v); + } + + Vector4f Vector4f::cross(const Vector4f& v) const { // w = 0: assuming Vector, not Point return Vector4f(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x, 0); } + Vector4f& Vector4f::crossEquals(const Vector4f& v) + { + *this = cross(v); + return *this; + } + Vector4f Vector4f::operator*(const Matrix4f& m) const { return Vector4f( @@ -186,6 +268,14 @@ namespace vectormatrix } } + void Vector4f::set(float x, float y, float z, float w) + { + this->x = x; + this->y = y; + this->z = z; + this->w = w; + } + void Vector4f::setX(float v) { x = v; @@ -206,11 +296,69 @@ namespace vectormatrix w = v; } + float Vector4f::get3DX() const + { + return x / w; + } + + float Vector4f::get3DY() const + { + return y / w; + } + + float Vector4f::get3DZ() const + { + return z / w; + } + + std::optional Vector4f::get3DPoint() const + { + if (w != 0) + return Vector3f(x / w, y / w, z / w); + return std::nullopt; + } + + bool Vector4f::isVector() const + { + return w == 0; + } + + bool Vector4f::isPoint() const + { + return w != 0; + } + + void Vector4f::point() + { + w = 1; + } + + void Vector4f::vector() + { + w = 0; + } + float Vector4f::length() const { return std::sqrt(x * x + y * y + z * z + w * w); } + float Vector4f::lengthSquared() const + { + return x * x + y * y + z * z + w * w; + } + + float Vector4f::distance(const Vector4f& v) const + { + return std::sqrt(distanceSquared(v)); + } + + float Vector4f::distanceSquared(const Vector4f& v) const + { + float dx = x - v.x, dy = y - v.y, dz = z - v.z, dw = w - v.w; + return dx * dx + dy * dy + dz * dz + dw * dw; + } + Vector4f& Vector4f::normalize() { float length = this->length(); @@ -221,6 +369,25 @@ namespace vectormatrix return *this; } + Vector3f Vector4f::V3() const + { + return Vector3f(*this); + } + + std::array Vector4f::toArray() const + { + return { x, y, z, w }; + } + + float* Vector4f::toArray(float* dest) const + { + dest[0] = x; + dest[1] = y; + dest[2] = z; + dest[3] = w; + return dest; + } + // static member function (see declaration) Vector4f Vector4f::interpolate(const Vector4f& v1, const Vector4f& v2, float t) { diff --git a/VectorMatrixLib/Vector4f.h b/VectorMatrixLib/Vector4f.h index 116ced5..09bb0ff 100644 --- a/VectorMatrixLib/Vector4f.h +++ b/VectorMatrixLib/Vector4f.h @@ -10,31 +10,55 @@ #ifndef VECTOR4F_H #define VECTOR4F_H +#include #include +#include +#include +#include "Vector3f.h" namespace vectormatrix { class Matrix4f; - class Vector3f; class Vector4f { public: + // Static vectors and point (a new copy is returned each time) + static Vector4f xAxis(); + static Vector4f yAxis(); + static Vector4f zAxis(); + static Vector4f xOppAxis(); + static Vector4f yOppAxis(); + static Vector4f zOppAxis(); + static Vector4f zeroVector(); + static Vector4f zeroPoint(); + // Constructors Vector4f(); explicit Vector4f(float a); Vector4f(float x, float y, float z, float w); explicit Vector4f(const float array[4]); + // Throws VectorArrayWrongSizeException if the array has less than 4 elements + explicit Vector4f(const std::vector& array); // w is set to 0 (vector, not point) explicit Vector4f(const Vector3f& v); + // Vector ab from 2 points a and b (w = 0 if a and b are points) + Vector4f(const Vector4f& a, const Vector4f& b); + // Row r of matrix A + Vector4f(int r, const Matrix4f& A); + // Column c of matrix A + Vector4f(const Matrix4f& A, int c); // Operators Vector4f operator+(const Vector4f& v) const; Vector4f& operator+=(const Vector4f& v); Vector4f operator-(const Vector4f& v) const; Vector4f& operator-=(const Vector4f& v); - // Cross product (assuming vectors, w forced to 0) + // Add or subtract a Vector3f to x, y, z (w is unchanged) + Vector4f operator+(const Vector3f& v) const; + Vector4f operator-(const Vector3f& v) const; + // Cross product (same as cross(), assuming vectors, w forced to 0) Vector4f operator*(const Vector4f& v) const; // W = A.V (same semantic as Java Vector4.times(Matrix4)) Vector4f operator*(const Matrix4f& m) const; @@ -45,6 +69,9 @@ 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; + // Cross product V^W assuming vectors (w forced to 0), cross returns a new vector, crossEquals modifies this vector + Vector4f cross(const Vector4f& v) const; + Vector4f& crossEquals(const Vector4f& v); // Equality within EPSILON tolerance bool operator==(const Vector4f& v) const; bool operator!=(const Vector4f& v) const; @@ -55,17 +82,37 @@ namespace vectormatrix float getY() const; float getZ() const; float getW() const; + // Coordinates of the 3D point (x/w, y/w, z/w) + float get3DX() const; + float get3DY() const; + float get3DZ() const; + // 3D point (x/w, y/w, z/w), or nothing if w is 0 (Java returns null) + std::optional get3DPoint() const; void set(int i, float v); + void set(float x, float y, float z, float w); void setX(float v); void setY(float v); void setZ(float v); void setW(float v); + // Point or vector (w != 0 or w == 0) + bool isVector() const; + bool isPoint() const; + void point(); + void vector(); + // Other methods float length() const; + float lengthSquared() const; + float distance(const Vector4f& v) const; + float distanceSquared(const Vector4f& v) const; // Normalize this vector (modified) and return it Vector4f& normalize(); + Vector3f V3() const; + std::array toArray() const; + // Copy the coordinates in dest (at least 4 elements) and return dest + float* toArray(float* dest) const; // Static methods static Vector4f interpolate(const Vector4f& v1, const Vector4f& v2, float t); diff --git a/VectorMatrixLib/VectorMatrixLib.vcxproj b/VectorMatrixLib/VectorMatrixLib.vcxproj index 9ea0970..9589dcd 100644 --- a/VectorMatrixLib/VectorMatrixLib.vcxproj +++ b/VectorMatrixLib/VectorMatrixLib.vcxproj @@ -88,6 +88,7 @@ true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp17 Console @@ -102,6 +103,7 @@ true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp17 Console @@ -116,6 +118,7 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp17 Console @@ -130,6 +133,7 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp17 Console @@ -146,6 +150,7 @@ + diff --git a/VectorMatrixLib/VectorMatrixLib.vcxproj.filters b/VectorMatrixLib/VectorMatrixLib.vcxproj.filters index fe6fdf0..4195d10 100644 --- a/VectorMatrixLib/VectorMatrixLib.vcxproj.filters +++ b/VectorMatrixLib/VectorMatrixLib.vcxproj.filters @@ -36,6 +36,9 @@ Header Files + + Header Files + diff --git a/tests/TestVectorMatrix.cpp b/tests/TestVectorMatrix.cpp index f0afd54..e846f29 100644 --- a/tests/TestVectorMatrix.cpp +++ b/tests/TestVectorMatrix.cpp @@ -11,7 +11,9 @@ // #include +#include #include +#include #include #include "Vector3f.h" #include "Vector4f.h" @@ -206,6 +208,209 @@ static void testMatrix4f() } } +static void testVector3fMethods() +{ + CHECK(Vector3f::xAxis() == Vector3f(1, 0, 0)); + CHECK(Vector3f::zOppAxis() == Vector3f(0, 0, -1)); + CHECK(Vector3f::zeroVector() == Vector3f(0, 0, 0)); + CHECK(Vector3f::xAxis().cross(Vector3f::yAxis()) == Vector3f::zAxis()); + + Vector3f a(1, 2, 3); + Vector3f b(4, 5, 6); + CHECK(a.cross(b) == a * b); + Vector3f c(a); + c.crossEquals(b); + CHECK(c == Vector3f(-3, 6, -3)); + + CHECK_NEAR(a.lengthSquared(), 14.0f); + CHECK_NEAR(a.distanceSquared(b), 27.0f); + CHECK_NEAR(Vector3f(1, 1, 1).distance(Vector3f(1, 4, 5)), 5.0f); + + c.set(7, 8, 9); + CHECK(c == Vector3f(7, 8, 9)); + std::array array = c.toArray(); + CHECK_NEAR(array[2], 9.0f); + float dest[3]; + CHECK(c.toArray(dest) == dest); + CHECK_NEAR(dest[0], 7.0f); + + CHECK(Vector3f(std::vector{ 1, 2, 3 }) == a); + CHECK_THROWS(Vector3f(std::vector{ 1, 2 }), VectorArrayWrongSizeException); + + // From Vector4f + CHECK(Vector3f(Vector4f(1, 2, 3, 4)) == a); + CHECK(Vector3f(Vector4f(1, 1, 1, 1), Vector4f(2, 3, 4, 1)) == a); + + // Row and column of a matrix + float va[3][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + Matrix3f m(va); + CHECK(Vector3f(1, m) == Vector3f(4, 5, 6)); + CHECK(Vector3f(m, 1) == Vector3f(2, 5, 8)); +} + +static void testVector4fMethods() +{ + CHECK(Vector4f::yAxis() == Vector4f(0, 1, 0, 0)); + CHECK(Vector4f::xOppAxis() == Vector4f(-1, 0, 0, 0)); + CHECK(Vector4f::zeroPoint() == Vector4f(0, 0, 0, 1)); + CHECK(Vector4f::zeroPoint().isPoint()); + CHECK(Vector4f::zeroVector().isVector()); + + Vector4f a(1, 2, 3, 0); + Vector4f b(4, 5, 6, 0); + CHECK(a.cross(b) == Vector4f(-3, 6, -3, 0)); + Vector4f c(a); + c.crossEquals(b); + CHECK(c == a * b); + + // Vector ab from 2 points + Vector4f p1(1, 1, 1, 1); + Vector4f p2(2, 3, 4, 1); + CHECK(Vector4f(p1, p2) == a); + CHECK((p1 + Vector3f(1, 2, 3)) == p2); + CHECK((p2 - Vector3f(1, 2, 3)) == p1); + + // 3D point + Vector4f h(2, 4, 6, 2); + CHECK_NEAR(h.get3DX(), 1.0f); + CHECK_NEAR(h.get3DY(), 2.0f); + CHECK_NEAR(h.get3DZ(), 3.0f); + CHECK(h.get3DPoint().has_value()); + CHECK(*h.get3DPoint() == Vector3f(1, 2, 3)); + CHECK(!a.get3DPoint().has_value()); + CHECK(h.V3() == Vector3f(2, 4, 6)); + + c.set(1, 2, 3, 4); + CHECK(c == Vector4f(1, 2, 3, 4)); + c.vector(); + CHECK(c.isVector()); + c.point(); + CHECK(c.isPoint()); + CHECK_NEAR(c.getW(), 1.0f); + + CHECK_NEAR(Vector4f(1, 2, 3, 4).lengthSquared(), 30.0f); + CHECK_NEAR(Vector4f(0, 0, 0, 1).distance(Vector4f(0, 3, 4, 1)), 5.0f); + CHECK_NEAR(Vector4f(0, 0, 0, 1).distanceSquared(Vector4f(0, 3, 4, 1)), 25.0f); + + std::array array = h.toArray(); + CHECK_NEAR(array[3], 2.0f); + float dest[4]; + h.toArray(dest); + CHECK_NEAR(dest[1], 4.0f); + + CHECK(Vector4f(std::vector{ 1, 2, 3, 0 }) == a); + CHECK_THROWS(Vector4f(std::vector{ 1, 2, 3 }), VectorArrayWrongSizeException); + + float va[4][4] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } }; + Matrix4f m(va); + CHECK(Vector4f(2, m) == Vector4f(9, 10, 11, 12)); + CHECK(Vector4f(m, 2) == Vector4f(3, 7, 11, 15)); +} + +static void testMatrix3fMethods() +{ + float va[3][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + Matrix3f a(va); + + CHECK(Matrix3f::identity().isIdentity()); + CHECK(!a.isIdentity()); + CHECK(a * Matrix3f::identity() == a); + Matrix3f d; + d.setDiagonal(2); + CHECK(d == Matrix3f::identity() * 2.0f); + + CHECK(a.getRow(0) == Vector3f(1, 2, 3)); + CHECK(a.getColumn(0) == Vector3f(1, 4, 7)); + CHECK_THROWS(a.getRow(3), IndexOutOfBoundException); + CHECK_THROWS(a.getColumn(-1), IndexOutOfBoundException); + Matrix3f b(a); + b.setRow(1, Vector3f(0, 0, 0)); + CHECK(b.getRow(1) == Vector3f(0, 0, 0)); + b.setColumn(2, Vector3f(1, 1, 1)); + CHECK(b.getColumn(2) == Vector3f(1, 1, 1)); + CHECK_THROWS(b.setRow(3, Vector3f()), IndexOutOfBoundException); + CHECK_THROWS(b.setColumn(3, Vector3f()), IndexOutOfBoundException); + + std::vector> array = a.getArray(); + CHECK(array.size() == 3 && array[2].size() == 3); + CHECK_NEAR(array[2][1], 8.0f); + array[2][1] = 0; // a copy + CHECK_NEAR(a.get(2, 1), 8.0f); + b.setArray(array); + CHECK_NEAR(b.get(2, 1), 0.0f); + CHECK_THROWS(b.setArray(std::vector>(2, std::vector(3))), MatrixArrayWrongSizeException); + CHECK_THROWS(b.setArray(std::vector>(3, std::vector(4))), MatrixArrayWrongSizeException); + + CHECK_NEAR(a.trace(), 15.0f); + CHECK_NEAR(a.determinant(), 0.0f); + CHECK_NEAR(Matrix3f::identity().determinant(), 1.0f); + + float vt[3][3] = { { 1, 4, 7 }, { 2, 5, 8 }, { 3, 6, 9 } }; + CHECK(a.transpose() == Matrix3f(vt)); + b = a; + b.transposeEquals(); + CHECK(b == Matrix3f(vt)); + + // Inverse + float vi[3][3] = { { 2, -1, 0 }, { -1, 2, -1 }, { 0, -1, 2 } }; + Matrix3f m(vi); + CHECK_NEAR(m.determinant(), 4.0f); + Matrix3f inv = m.inverse(); + CHECK((m * inv).isIdentity()); + CHECK((inv * m).isIdentity()); + CHECK(inv.inverse() == m); + CHECK(Matrix3f::identity().inverse().isIdentity()); + CHECK_THROWS(a.inverse(), NotInvertibleMatrixException); +} + +static void testMatrix4fMethods() +{ + CHECK(Matrix4f::identity().isIdentity()); + CHECK_NEAR(Matrix4f::identity().trace(), 4.0f); + CHECK_NEAR(Matrix4f::identity().determinant(), 1.0f); + + // Same matrix as Aventura testMatrix4_inverse1 and testMatrix4_inverse2 + float va[4][4]; + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + va[i][j] = (i > j) ? 0.0f : static_cast(10 - 2 * i - j); + Matrix4f a(va); + CHECK_NEAR(a.determinant(), 280.0f); // triangular: 10 * 7 * 4 * 1 + Matrix4f inv = a.inverse(); + CHECK(inv.inverse() == a); + CHECK(inv * a == Matrix4f::identity()); + + // Same matrix as Aventura testMatrix4_inverse_precision_generalCase + float vg[4][4] = { { 4, 7, 2, 1 }, { 3, 5, 1, 2 }, { 2, 3, 1, 0 }, { 1, 0, 2, 3 } }; + Matrix4f g(vg); + CHECK((g * g.inverse()).isIdentity()); + CHECK_NEAR(g.determinant(), g.transpose().determinant()); + + // Singular matrix (last row is 0) + Matrix4f singular(a); + singular.setRow(3, Vector4f()); + CHECK_NEAR(singular.determinant(), 0.0f); + CHECK_THROWS(singular.inverse(), NotInvertibleMatrixException); + + CHECK(a.getRow(1) == Vector4f(0, 7, 6, 5)); + CHECK(a.getColumn(3) == Vector4f(7, 5, 3, 1)); + CHECK_THROWS(a.getRow(4), IndexOutOfBoundException); + CHECK(a.transpose().getRow(3) == a.getColumn(3)); + Matrix4f t(a); + t.transposeEquals(); + CHECK(t == a.transpose()); + + Matrix3f m3 = a.getMatrix3(); + CHECK(m3.getRow(0) == Vector3f(10, 9, 8)); + CHECK(m3 == Matrix3f(a)); + + std::vector> array = a.getArray(); + Matrix4f b; + b.setArray(array); + CHECK(b == a); + CHECK_THROWS(b.setArray(std::vector>(3, std::vector(3))), MatrixArrayWrongSizeException); +} + int main() { testMathTools(); @@ -213,6 +418,10 @@ int main() testVector4f(); testMatrix3f(); testMatrix4f(); + testVector3fMethods(); + testVector4fMethods(); + testMatrix3fMethods(); + testMatrix4fMethods(); if (nb_failures == 0) { From 68bd28e053a4dad6fee9bb9ca319fdc77a353553 Mon Sep 17 00:00:00 2001 From: Olivier BARRY Date: Sun, 4 Oct 2026 11:06:05 +0200 Subject: [PATCH 2/2] Rename set() parameters to avoid shadowing warnings MSVC /W4 reported C4458 because the parameters of Vector3f::set(x, y, z) and Vector4f::set(x, y, z, w) hid the members. Co-Authored-By: Claude Opus 5.5 (1M context) --- VectorMatrixLib/Vector3f.cpp | 8 ++++---- VectorMatrixLib/Vector3f.h | 2 +- VectorMatrixLib/Vector4f.cpp | 10 +++++----- VectorMatrixLib/Vector4f.h | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/VectorMatrixLib/Vector3f.cpp b/VectorMatrixLib/Vector3f.cpp index 68be059..ddc1144 100644 --- a/VectorMatrixLib/Vector3f.cpp +++ b/VectorMatrixLib/Vector3f.cpp @@ -237,11 +237,11 @@ namespace vectormatrix } } - void Vector3f::set(float x, float y, float z) + void Vector3f::set(float nx, float ny, float nz) { - this->x = x; - this->y = y; - this->z = z; + x = nx; + y = ny; + z = nz; } void Vector3f::setX(float v) diff --git a/VectorMatrixLib/Vector3f.h b/VectorMatrixLib/Vector3f.h index a15daa0..9867307 100644 --- a/VectorMatrixLib/Vector3f.h +++ b/VectorMatrixLib/Vector3f.h @@ -78,7 +78,7 @@ namespace vectormatrix float getZ() const; void set(int i, float v); - void set(float x, float y, float z); + void set(float nx, float ny, float nz); void setX(float v); void setY(float v); void setZ(float v); diff --git a/VectorMatrixLib/Vector4f.cpp b/VectorMatrixLib/Vector4f.cpp index 7a54233..641a171 100644 --- a/VectorMatrixLib/Vector4f.cpp +++ b/VectorMatrixLib/Vector4f.cpp @@ -268,12 +268,12 @@ namespace vectormatrix } } - void Vector4f::set(float x, float y, float z, float w) + void Vector4f::set(float nx, float ny, float nz, float nw) { - this->x = x; - this->y = y; - this->z = z; - this->w = w; + x = nx; + y = ny; + z = nz; + w = nw; } void Vector4f::setX(float v) diff --git a/VectorMatrixLib/Vector4f.h b/VectorMatrixLib/Vector4f.h index 09bb0ff..ef64fac 100644 --- a/VectorMatrixLib/Vector4f.h +++ b/VectorMatrixLib/Vector4f.h @@ -90,7 +90,7 @@ namespace vectormatrix std::optional get3DPoint() const; void set(int i, float v); - void set(float x, float y, float z, float w); + void set(float nx, float ny, float nz, float nw); void setX(float v); void setY(float v); void setZ(float v);