Files
j3ml/include/J3ML/LinearAlgebra/Matrix3x3.hpp
josh 51d51a9cc7
Some checks failed
Run ReCI Build Test / Explore-Gitea-Actions (push) Failing after 1m2s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 26s
Add AxisAngle members and fill out more unit tests.
2025-03-03 23:33:03 -05:00

476 lines
27 KiB
C++

#pragma once
#include <J3ML/LinearAlgebra/Vector2.hpp>
#include <J3ML/LinearAlgebra/Vector3.hpp>
#include <J3ML/LinearAlgebra/Quaternion.hpp>
#include <J3ML/Algorithm/RNG.hpp>
using namespace J3ML::Algorithm;
#include <cstring>
namespace J3ML::LinearAlgebra {
/// A 3-by-3 matrix for linear transformations of 3D geometry.
/* This can represent any kind of linear transformations of 3D geometry, which include
* rotation, scale, shear, mirroring, and orthographic projection.
* A 3x3 matrix cannot represent translation, which requires a 3x4, or perspective projection (4x4).
* The elements of this matrix are
* m_00, m_01, m_02
* m_10, m_11, m_12
* m_20, m_21, m_22
*
* The element m_yx is the value on the row y and column x.
* You can access m_yx using the double-bracket notation m[y][x], or using the member function At.
*
* @note The member functions in this class use the convention that transforms are applied to
* vectors in the form M * v. This means that "Matrix3x3 M, M1, M2; M = M1 * M2;" gives a transformation M
* that applies M2 first, followed by M1 second. */
class Matrix3x3 {
public: /// Constant Values
enum { Rows = 3 };
enum { Cols = 3 };
public: /// Constant Members
static const Matrix3x3 Zero;
static const Matrix3x3 Identity;
static const Matrix3x3 NaN;
public: /// Constructors
/// Creates a new Matrix3x3 with uninitalized member values.
Matrix3x3() = default;
Matrix3x3(const Matrix3x3& rhs) { Set(rhs); }
/// Creates a new Matrix3x3 by setting all matrix elements equal to val.
explicit Matrix3x3(float val);
/// Creates a new Matrix3x3 by explicitly specifying all the matrix elements.
/// The elements are specified in row-major format, i.e. the first row first followed by the second and third row.
/// E.g. The element m10 denotes the scalar at second (idx 1) row, first (idx 0) column.
Matrix3x3(float m00, float m01, float m02,
float m10, float m11, float m12,
float m20, float m21, float m22);
/// Constructs the matrix by explicitly specifying the three column vectors.
/** @param col0 The first column. If this matrix represents a change-of-basis transformation, this parameter is the world-space
direction of the local X axis.
@param col1 The second column. If this matrix represents a change-of-basis transformation, this parameter is the world-space
direction of the local Y axis.
@param col2 The third column. If this matrix represents a change-of-basis transformation, this parameter is the world-space
direction of the local Z axis. */
Matrix3x3(const Vector3& col0, const Vector3& col1, const Vector3& col2);
/// Constructs this matrix3x3 from the given quaternion.
explicit Matrix3x3(const Quaternion& orientation);
//explicit Matrix3x3(const AxisAngle& orientation);
explicit Matrix3x3(const AxisAngle& orientation) : Matrix3x3(Quaternion(orientation)) {};
/// Constructs this Matrix3x3 from a pointer to an array of floats.
explicit Matrix3x3(const float *data);
/// Creates a new Matrix3x3 that rotates about one of the principal axes by the given angle.
/// Calling RotateX, RotateY, or RotateZ is slightly faster than calling the more generic RotateAxisAngle function.
static Matrix3x3 RotateX(float radians);
/// [similarOverload: RotateX] [hideIndex]
static Matrix3x3 RotateY(float radians);
/// [similarOverload: RotateX] [hideIndex]
static Matrix3x3 RotateZ(float radians);
/// Creates a new M3x3 that rotates about the given axis by the given angle
static Matrix3x3 RotateAxisAngle(const Vector3& axis, float angleRadians);
/// Creates a matrix that rotates the sourceDirection vector to coincide with the targetDirection vector.]
/** Both input direction vectors must be normalized.
@note There are infinite such rotations - this function returns the rotation that has the shortest angle
(when decomposed to axis-angle notation)
@return An orthonormal matrix M with a determinant of +1. For the matrix M it holds that
M * sourceDirection = targetDirection */
static Matrix3x3 RotateFromTo(const Vector3& source, const Vector3& direction);
/// Creates a LookAt matrix.
/** A LookAt matrix is a rotation matrix that orients an object to face towards a specified target direction.
* @param forward Specifies the forward direction in the local space of the object. This is the direction
the model is facing at in its own local/object space, often +X (1,0,0), +Y (0,1,0), or +Z (0,0,1). The
vector to pass in here depends on the conventions you or your modeling software is using, and it is best
pick one convention for all your objects, and be consistent.
* @param target Specifies the desired world space direction the object should look at. This function
will compute a rotation matrix which will rotate the localForward vector to orient towards this targetDirection
vector. This input parameter must be a normalized vector.
* @param localUp Specifies the up direction in the local space of the object. This is the up direction the model
was authored in, often +Y (0,1,0) or +Z (0,0,1). The vector to pass in here depends on the conventions you
or your modeling software is using, and it is best to pick one convention for all your objects, and be
consistent. This input parameter must be a normalized vector. This vector must be perpendicular to the
vector localForward, i.e. localForward.Dot(localUp) == 0.
* @param worldUp Specifies the global up direction of the scene in world space. Simply rotating one vector to
coincide with another (localForward->targetDirection) would cause the up direction of the resulting
orientation to drift (e.g. the model could be looking at its target its head slanted sideways). To keep
the up direction straight, this function orients the localUp direction of the model to point towards the
specified worldUp direction (as closely as possible). The worldUp and targetDirection vectors cannot be
collinear, but they do not need to be perpendicular either.
* @return A matrix that maps the given local space forward direction vector to point towards the given target
direction, and the given local up direction towards the given target world up direction. This matrix can be
used as the 'world transform' of an object. THe returned matrix M is orthogonal with a determinant of +1.
For the matrix M it holds that M * localForward = targetDirection, and M * localUp lies in the plane spanned by
the vectors targetDirection and worldUp.
* @see RotateFromTo()
* @note Be aware that the convention of a 'LookAt' matrix in J3ML differs from e.g. GLM. In J3ML, the returned
matrix is a mapping from local space to world space, meaning that the returned matrix can be used as the 'world transform'
for any 3D object (camera or not). The view space is the local space of the camera, so this function returns the mapping
view->world. In GLM, the LookAt function is tied to cameras only, and it returns the inverse mapping world->view.
*/
static Matrix3x3 LookAt(const Vector3& forward, const Vector3& target, const Vector3& localUp, const Vector3& worldUp);
// Returns a uniformly random 3x3 matrix that performs only rotation.
/** This matrix produces a random orthonormal bassi for an orientation of an object. There is no mirroring
or scaling present in the generated matrix. Also, naturally since Matrix3x3 cannot represent translation or projection,
these properties are not present either. */
static Matrix3x3 RandomRotation(RNG& rng);
/// Returns a random 3x3 matrix with each entry randomized between the range [minElem, maxElem]
/** Warning: The matrices returned by this function do not represent well-formed 3D transformations.
This function is mostly used for testing and debugging purposes only. */
static Matrix3x3 RandomGeneral(RNG& rng, float minElem, float maxElem);
/// Creates a new Matrix3x3 that performs the rotation expressed by the given quaternion.
static Matrix3x3 FromQuat(const Quaternion& orientation);
/// Creates a new Matrix3x3 as a combination of rotation and scale.
// This function creates a new matrix M in the form M = R * S
// where R is a rotation matrix and S is a scale matrix.
// Transforming a vector v using this matrix computes the vector
// v' == M * v == R*S*v == (R * (S * v)) which means the scale operation
// is applied to the vector first, followed by rotation, and finally translation
static Matrix3x3 FromRS(const Quaternion& rotate, const Vector3& scale);
static Matrix3x3 FromRS(const Matrix3x3 &rotate, const Vector3& scale);
/// Creates a new transformation matrix that scales by the given factors.
// This matrix scales with respect to origin.
static Matrix3x3 FromScale(float sx, float sy, float sz);
static Matrix3x3 FromScale(const Vector3& scale);
public: /// Member Methods
/// Sets this matrix to perform rotation about the positive X axis which passes through the origin
/// [similarOverload: SetRotatePart] [hideIndex]
void SetRotatePartX(float angle);
/// Sets this matrix to perform rotation about the positive Y axis.
void SetRotatePartY(float angle);
/// Sets this matrix to perform rotation about the positive Z axis.
void SetRotatePartZ(float angle);
/// Sets this matrix to perform a rotation about the given axis and angle.
void SetRotatePart(const Vector3& a, float angle);
void SetRotatePart(const AxisAngle& axisAngle);
/// Sets this matrix to perform the rotation expressed by the given quaternion.
void SetRotatePart(const Quaternion& quat);
Vector3 ForwardDir() const;
Vector3 BackwardDir() const;
Vector3 LeftDir() const;
Vector3 RightDir() const;
Vector3 UpDir() const;
Vector3 DownDir() const;
/// Returns the given row.
/** @param row The zero-based index [0, 2] of the row to get. */
Vector3 GetRow(int index) const;
Vector3 Row(int index) const;
/// This method also allows assignment to the retrieved row.
Vector3& Row(int row);
/// Returns only the first-three elements of the given row.
Vector3 GetRow3(int index) const;
Vector3 Row3(int index) const;
/// This method also allows assignment to the retrieved row.
Vector3& Row3(int index);
/// Returns the given column.
/** @param col The zero-based index [0, 2] of the column to get. */
[[nodiscard]] Vector3 GetColumn(int index) const;
[[nodiscard]] Vector3 Column(int index) const;
[[nodiscard]] Vector3 Col(int index) const;
/// This method also allows assignment to the retrieved column.
//Vector3& Col(int index);
/// Returns only the first three elements of the given column.
[[nodiscard]] Vector3 GetColumn3(int index) const;
[[nodiscard]] Vector3 Column3(int index) const;
[[nodiscard]] Vector3 Col3(int index) const;
/// This method also allows assignment to the retrieved column.
//Vector3& Col3(int index);
/// Sets the value of a given row.
/** @param row The index of the row to a set, in the range [0-2].
@param data A pointer to an array of 3 floats that contain the new x, y, and z values for the row.*/
void SetRow(int row, const float* data);
void SetRow(int row, const Vector3 & data);
void SetRow(int row, float x, float y, float z);
/// Sets the value of a given column.
/** @param column The index of the column to set, in the range [0-2]
@param data A pointer to an array of 3 floats that contain the new x, y, and z values for the column.*/
void SetColumn(int column, const float* data);
void SetColumn(int column, const Vector3 & data);
void SetColumn(int column, float x, float y, float z);
/// Sets a single element of this matrix
/** @param row The row index (y-coordinate) of the element to set, in the range [0-2].
@param col The col index (x-coordinate) of the element to set, in the range [0-2].
@param value The new value to set to the cell [row][col]. */
void SetAt(int x, int y, float value);
/// Sets this matrix to equal the identity.
void SetIdentity();
/// Swaps two columns.
void SwapColumns(int col1, int col2);
/// Swaps two rows.
void SwapRows(int row1, int row2);
float &At(int row, int col);
[[nodiscard]] float At(int x, int y) const;
/// Sets this to be a copy of the matrix rhs.
void Set(const Matrix3x3 &rhs);
/// Sets all values of this matrix/
void Set(float _00, float _01, float _02,
float _10, float _11, float _12,
float _20, float _21, float _22);
/// Sets all values of this matrix.
/// @param valuesThe values in this array will be copied over to this matrix. The source must contain 9 floats in row-major order
/// (the same order as the Set() function aove has its input parameters in).
void Set(const float *p);
/// Orthonormalizes the basis formed by the column vectors of this matrix.
void Orthonormalize(int c0, int c1, int c2);
/// Accesses this structure as a float array.
/// @return A pointer to the upper-left element. The data is contiguous in memory.
/// ptr[0] gives the element [0][0], ptr[1] is [0][1]. ptr[2] is [0][2]
inline float* ptr() { return &elems[0][0];}
[[nodiscard]] inline const float* ptr() const {return &elems[0][0];}
/// Attempts to convert this matrix to a quaternion. Returns false if the conversion cannot succeed (this matrix was not a rotation
/// matrix, and there is scaling ,shearing, or mirroring in this matrix)
bool TryConvertToQuat(Quaternion& q) const;
/// Returns the main diagonal.
/// The main diagonal consists of the elements at m[0][0], m[1][1], m[2][2]
[[nodiscard]] Vector3 Diagonal() const;
/// Returns the local +X/+Y/+Z axis in world space.
/// This is the same as transforming the vector{1,0,0} by this matrix.
[[nodiscard]] Vector3 WorldX() const;
/// Returns the local +Y axis in world space.
/// This is the same as transforming the vector{0,1,0} by this matrix.
[[nodiscard]] Vector3 WorldY() const;
/// Returns the local +Z axis in world space.
/// This is the same as transforming the vector{0,0,1} by this matrix.
[[nodiscard]] Vector3 WorldZ() const;
/// Computes the determinant of this matrix.
// If the determinant is nonzero, this matrix is invertible.
// If the determinant is negative, this matrix performs reflection about some axis.
// From http://msdn.microsoft.com/en-us/library/bb204853(VS.85).aspx :
// "If the determinant is positive, the basis is said to be "positively" oriented (or right-handed).
// If the determinant is negative, the basis is said to be "negatively" oriented (or left-handed)."
// @note This function computes 9 LOADs, 9 MULs and 5 ADDs. */
[[nodiscard]] float Determinant() const;
/// Computes the determinant of a symmetric matrix.
/** This function can be used to compute the determinant of a matrix in the case the matrix is known beforehand
to be symmetric. This function is slightly faster than Determinant().
* @return
*/
[[nodiscard]] float DeterminantSymmetric() const;
// Returns an inverted copy of this matrix.
[[nodiscard]] Matrix3x3 Inverted() const;
// Returns a transposed copy of this matrix.
[[nodiscard]] Matrix3x3 Transposed() const;
/// Returns the inverse transpose of this matrix.
/// Use the matrix to transform covariant vectors (normal vectors).
[[nodiscard]] Matrix3x3 InverseTransposed() const;
/// Computes the inverse transpose of this matrix.
/// Use the matrix to transform covariant vectors (normal vectors).
bool InverseTranspose();
/// Inverts this matrix using numerically stable Gaussian elimination.
/// @return Returns true on success, false otherwise;
bool Inverse(float epsilon = 1e-6f);
/// Inverts this matrix using Cramer's rule.
/// @return Returns true on success, false otherwise.
bool InverseFast(float epsilon = 1e-6f);
/// Solves the linear equation Ax=b.
/** The matrix A in the equations is this matrix. */
bool SolveAxb(Vector3 b, Vector3& x) const;
/// Inverts a column-orthogonal matrix.
/** If a matrix is of form M=R*S, where
R is a rotation matrix and S is a diagonal matrix with non-zero but potentially non-uniform scaling
factors (possibly mirroring), then the matrix M is column-orthogonal and this function can be used to compute the inverse.
Calling this function is faster than calling the generic matrix Inverted() function.\
Returns true on success. On failure, the matrix is not modified. This function fails if any of the
elements of this vector are not finite, or if the matrix contains a zero scaling factor on X, Y, or Z.
@note The returned matrix will be row-orthogonal, but not column-orthogonal in general.
The returned matrix will be column-orthogonal if the original matrix M was row-orthogonal as well.
(in which case S had uniform scale, InverseOrthogonalUniformScale() could have been used instead)*/
bool InverseColOrthogonal();
/// Inverts a rotation matrix.
/** If a matrix is of form M=R*S, where R is a rotation matrix and S is either identity or a mirroring matrix, then
the matrix M is orthonormal and this function can be used to compute the inverse.
This function is faster than calling InverseOrthogonalUniformScale(), InverseColOrthogonal(), or the generic
Inverted().
This function may not be called if this matrix contains any scaling or shearing, but it may contain mirroring.*/
bool InverseOrthogonalUniformScale();
/// Inverts a rotation matrix.
/// If a matrix is of form M = R*S, where R is a rotation matrix and S is either identity or a mirroring matrix, then
/// the matrix M is orthonormal and this function can be used to compute the inverse.
/// This function is faster than calling InverseOrthogonalUniformScale(), InverseColOrthogonal(), or the generic Inverted()
/// This function may not be called if this matrix contains any scaling or shearing, but it may contain mirroring.
void InverseOrthonormal();
/// Inverts a symmetric matrix.
/** This function is faster than directly calling Inverted()
This function computes 6 LOADs, 9 STOREs, 21 MULs, 1 DIV, 1 CMP, and 8 ADDs.
@return True if computing the inverse succeeded, false otherwise (determinant was zero).
@note If this function fails, the original matrix is not modified.
@note This function operates in-place. */
bool InverseSymmetric();
/// Transposes this matrix.
/// This operation swaps all elements with respect to the diagonal.
void Transpose();
/// Removes the scaling performed by this matrix. That is, decomposes this matrix M into a form M = M' * S, where
/// M' has unitary column vectors and S is a diagonal matrix. Then replaces this matrix with M'
/// @note This function assumes that this matrix does not contain projection (the fourth row of this matrix is [0 0 0 1]).
/// @note This function does not remove reflection (-1 scale along some axis).
void RemoveScale();
// Transforms the given vectors by this matrix M, i.e. returns M * (x,y,z)
[[nodiscard]] Vector2 Transform(const Vector2& rhs) const;
[[nodiscard]] Vector3 Transform(const Vector3& rhs) const;
/// Performs a batch transformation of the given array.
void BatchTransform(Vector3 *pointArray, int numPoints) const;
/// Performs a batch transformation of the given array.
void BatchTransform(Vector3 *pointArray, int numPoints, int stride) const;
/// Performs a batch transformation of the given array.
/// This function ignores the w component of the input vectors. These components are assumed to be either 0 or 1.
void BatchTransform(Vector4 *vectorArray, int numVectors) const;
/// Performs a batch transformation of the given array.
/// This function ignores the w component of the input vectors. These components are assumed to be either 0 or 1.
void BatchTransform(Vector4 *vectorArray, int numVectors, int stride) const;
/// Returns the sum of the diagonal elements of this matrix.
[[nodiscard]] float Trace() const;
Matrix3x3 ScaleBy(const Vector3& rhs);
[[nodiscard]] Vector3 GetScale() const;
/// Scales the given row by a scalar value.
void ScaleRow(int row, float scalar);
/// Scales the given column by a scalar value.
void ScaleCol(int col, float scalar);
Vector3 operator[](int row) const;
/// Transforms the given vector by this matrix (in the order M * v).
Vector2 operator * (const Vector2& rhs) const;
Vector3 operator * (const Vector3& rhs) const;
/// Transforms the given vector by this matrix (in the order M * v).
/// This function ignores the W component of the given input vector. This component is assumed to be either 0 or 1.
Vector4 operator * (const Vector4& rhs) const;
/// Unary operator + allows this structure to be used in an expression '+x'.
Matrix3x3 operator + () const { return *this;}
/// Multiplies the two matrices.
Matrix3x3 operator * (const Matrix3x3& rhs) const;
[[nodiscard]] Matrix3x3 Mul(const Matrix3x3& rhs) const;
/// Multiplies the two matrices.
Matrix4x4 operator * (const Matrix4x4& rhs) const;
[[nodiscard]] Matrix4x4 Mul(const Matrix4x4& rhs) const;
[[nodiscard]] Vector2 Mul(const Vector2& rhs) const;
[[nodiscard]] Vector3 Mul(const Vector3& rhs) const;
[[nodiscard]] Vector4 Mul(const Vector4& rhs) const;
/// Converts the quaternion to a M3x3 and multiplies the two matrices together.
Matrix3x3 operator *(const Quaternion& rhs) const;
[[nodiscard]] Matrix3x3 Mul(const Quaternion& rhs) const;
// Returns true if the column vectors of this matrix are all perpendicular to each other.
[[nodiscard]] bool IsColOrthogonal(float epsilon = 1e-3f) const;
[[nodiscard]] bool IsColOrthogonal3(float epsilon = 1e-3f) const { return IsColOrthogonal(epsilon);}
// Returns true if the row vectors of this matrix are all perpendicular to each other.
[[nodiscard]] bool IsRowOrthogonal(float epsilon = 1e-3f) const;
[[nodiscard]] bool HasUniformScale(float epsilon = 1e-3f) const;
[[nodiscard]] Vector3 ExtractScale() const;
/// Tests if this matrix does not contain any NaNs or infs
/// @return Returns true if the entries of this M3x3 are all finite.
[[nodiscard]] bool IsFinite() const;
/// Tests if this is the identity matrix.
/// @return Returns true if this matrix is the identity matrix, up to the given epsilon.
[[nodiscard]] bool IsIdentity(float epsilon = 1e-3f) const;
/// Tests if this matrix is in lower triangular form.
/// @return Returns true if this matrix is in lower triangular form, up to the given epsilon.
[[nodiscard]] bool IsLowerTriangular(float epsilon = 1e-3f) const;
/// Tests if this matrix is in upper triangular form.
/// @return Returns true if this matrix is in upper triangular form, up to the given epsilon.
[[nodiscard]] bool IsUpperTriangular(float epsilon = 1e-3f) const;
/// Tests if this matrix has an inverse.
/// @return Returns true if this matrix can be inverted, up to the given epsilon.
[[nodiscard]] bool IsInvertible(float epsilon = 1e-3f) const;
/// Tests if this matrix is symmetric (M == M^T).
/// The test compares the elements for equality. Up to the given epsilon. A matrix is symmetric if it is its own transpose.
[[nodiscard]] bool IsSymmetric(float epsilon = 1e-3f) const;
/// Tests if this matrix is skew-symmetric (M == -M^T).
/// The test compares the elements of this matrix up to the given epsilon. A matrix M is skew-symmetric if the identity M=-M^T holds.
[[nodiscard]] bool IsSkewSymmetric(float epsilon = 1e-3f) const;
/// Returns true if this matrix does not perform any scaling,
/** A matrix does not do any scaling if the column vectors of this matrix are normalized in length,
compared to the given epsilon. Note that this matrix may still perform reflection,
i.e. it has a -1 scale along some axis.
@note This function only examines the upper 3-by-3 part of this matrix.
@note This function assumes that this matrix does not contain projection (the fourth row of this matrix is [0,0,0,1] */
[[nodiscard]] bool HasUnitaryScale(float epsilon = 1e-3f) const;
/// Returns true if this matrix performs a reflection along some plane.
/** In 3D space, an even number of reflections corresponds to a rotation about some axis, so a matrix consisting of
an odd number of consecutive mirror operations can only reflect about one axis. A matrix that contains reflection reverses
the handedness of the coordinate system. This function tests if this matrix does perform mirroring.
This occurs if this matrix has a negative determinant.*/
[[nodiscard]] bool HasNegativeScale() const;
/// Returns true if the column and row vectors of this matrix form an orthonormal set.
/// @note In math terms, there does not exist such a thing as an 'orthonormal matrix'. In math terms, a matrix
/// is orthogonal if the column and row vectors are orthogonal *unit* vectors.
/// In terms of this library however, a matrix is orthogonal if its column and row vectors are orthogonal. (no need to be unitary),
/// and a matrix is orthonormal if the column and row vectors are orthonormal.
[[nodiscard]] bool IsOrthonormal(float epsilon = 1e-3f) const;
/// Returns true if this Matrix3x3 is equal to the given Matrix3x3, up to given per-element epsilon.
bool Equals(const Matrix3x3& other, float epsilon = 1e-3f) const;
protected: /// Member values
float elems[3][3]{};
};
}