/** \file Matrix3.h 3x3 matrix class \maintainer Morgan McGuire, http://graphics.cs.williams.edu \cite Portions based on Dave Eberly's Magic Software Library at http://www.magic-software.com \created 2001-06-02 \edited 2011-05-05 */ #ifndef G3D_Matrix3_h #define G3D_Matrix3_h #include "G3D/platform.h" #include "G3D/Vector3.h" #include "G3D/Vector4.h" #include "G3D/debugAssert.h" #include namespace G3D { #ifdef _MSC_VER // Turn off "conditional expression is constant" warning; MSVC generates this // for debug assertions in inlined methods. # pragma warning (disable : 4127) #endif class Any; /** A 3x3 matrix. Do not subclass. Data is unitializd when default constructed. */ class Matrix3 { private: // Row, column float elt[3][3]; // Hidden operators bool operator<(const Matrix3&) const; bool operator>(const Matrix3&) const; bool operator<=(const Matrix3&) const; bool operator>=(const Matrix3&) const; public: /** Must be in one of the following forms: - Matrix3(#, #, # .... #) - Matrix3::fromAxisAngle(#, #) - Matrix3::diagonal(#, #, #) - Matrix3::identity() */ Matrix3(const Any& any); static Matrix3 fromColumns(const Vector3& c0, const Vector3& c1, const Vector3& c2) { Matrix3 m; for (int r = 0; r < 3; ++r) { m.elt[r][0] = c0[r]; m.elt[r][1] = c1[r]; m.elt[r][2] = c2[r]; } return m; } static Matrix3 fromRows(const Vector3& r0, const Vector3& r1, const Vector3& r2) { Matrix3 m; for (int c = 0; c < 3; ++c) { m.elt[0][c] = r0[c]; m.elt[1][c] = r1[c]; m.elt[2][c] = r2[c]; } return m; } Any toAny() const; /** Initial values are undefined for performance. \sa Matrix3::zero, Matrix3::identity, Matrix3::fromAxisAngle, etc.*/ Matrix3() {} Matrix3 (class BinaryInput& b); Matrix3 (const float aafEntry[3][3]); Matrix3 (const Matrix3& rkMatrix); Matrix3 (float fEntry00, float fEntry01, float fEntry02, float fEntry10, float fEntry11, float fEntry12, float fEntry20, float fEntry21, float fEntry22); bool fuzzyEq(const Matrix3& b) const; /** Constructs a matrix from a quaternion. @cite Graphics Gems II, p. 351--354 @cite Implementation from Watt and Watt, pg 362*/ Matrix3(const class Quat& q); static Matrix3 diagonal(float e00, float e11, float e22) { return Matrix3(e00, 0, 0, 0, e11, 0, 0, 0, e22); } void serialize(class BinaryOutput& b) const; void deserialize(class BinaryInput& b); /** Returns true if column(0).cross(column(1)).dot(column(2)) > 0. */ bool isRightHanded() const; /** Sets all elements. */ void set(float fEntry00, float fEntry01, float fEntry02, float fEntry10, float fEntry11, float fEntry12, float fEntry20, float fEntry21, float fEntry22); /** Member access, allows use of construct mat[r][c] */ inline float* operator[] (int iRow) { debugAssert(iRow >= 0); debugAssert(iRow < 3); return (float*)&elt[iRow][0]; } inline const float* operator[] (int iRow) const { debugAssert(iRow >= 0); debugAssert(iRow < 3); return (const float*)&elt[iRow][0]; } inline operator float* () { return (float*)&elt[0][0]; } inline operator const float* () const{ return (const float*)&elt[0][0]; } Vector3 column(int c) const; const Vector3& row(int r) const; void setColumn(int iCol, const Vector3 &vector); void setRow(int iRow, const Vector3 &vector); // assignment and comparison inline Matrix3& operator= (const Matrix3& rkMatrix) { memcpy(elt, rkMatrix.elt, 9 * sizeof(float)); return *this; } bool operator== (const Matrix3& rkMatrix) const; bool operator!= (const Matrix3& rkMatrix) const; // arithmetic operations Matrix3 operator+ (const Matrix3& rkMatrix) const; Matrix3 operator- (const Matrix3& rkMatrix) const; /** Matrix-matrix multiply */ Matrix3 operator* (const Matrix3& rkMatrix) const; Matrix3 operator- () const; Matrix3& operator+= (const Matrix3& rkMatrix); Matrix3& operator-= (const Matrix3& rkMatrix); Matrix3& operator*= (const Matrix3& rkMatrix); /** * matrix * vector [3x3 * 3x1 = 3x1] */ inline Vector3 operator* (const Vector3& v) const { Vector3 kProd; for (int r = 0; r < 3; ++r) { kProd[r] = elt[r][0] * v[0] + elt[r][1] * v[1] + elt[r][2] * v[2]; } return kProd; } /** * vector * matrix [1x3 * 3x3 = 1x3] */ friend Vector3 operator* (const Vector3& rkVector, const Matrix3& rkMatrix); /** * matrix * scalar */ Matrix3 operator* (float fScalar) const; /** scalar * matrix */ friend Matrix3 operator* (double fScalar, const Matrix3& rkMatrix); friend Matrix3 operator* (float fScalar, const Matrix3& rkMatrix); friend Matrix3 operator* (int fScalar, const Matrix3& rkMatrix); Matrix3& operator*= (float k); Matrix3& operator/= (float k); private: /** Multiplication where out != A and out != B */ static void _mul(const Matrix3& A, const Matrix3& B, Matrix3& out); public: /** Optimized implementation of out = A * B. It is safe (but slow) to call with A, B, and out possibly pointer equal to one another.*/ // This is a static method so that it is not ambiguous whether "this" // is an input or output argument. inline static void mul(const Matrix3& A, const Matrix3& B, Matrix3& out) { if ((&out == &A) || (&out == &B)) { // We need a temporary anyway, so revert to the stack method. out = A * B; } else { // Optimized in-place multiplication. _mul(A, B, out); } } private: static void _transpose(const Matrix3& A, Matrix3& out); public: /** Optimized implementation of out = A.transpose(). It is safe (but slow) to call with A and out possibly pointer equal to one another. Note that A.transpose() * v can be computed more efficiently as v * A. */ inline static void transpose(const Matrix3& A, Matrix3& out) { if (&A == &out) { out = A.transpose(); } else { _transpose(A, out); } } /** Returns true if the rows and column L2 norms are 1.0 and the rows are orthogonal. */ bool isOrthonormal() const; Matrix3 transpose () const; bool inverse (Matrix3& rkInverse, float fTolerance = 1e-06f) const; Matrix3 inverse (float fTolerance = 1e-06f) const; float determinant () const; /** singular value decomposition */ void singularValueDecomposition (Matrix3& rkL, Vector3& rkS, Matrix3& rkR) const; /** singular value decomposition */ void singularValueComposition (const Matrix3& rkL, const Vector3& rkS, const Matrix3& rkR); /** Gram-Schmidt orthonormalization (applied to columns of rotation matrix) */ void orthonormalize(); /** orthogonal Q, diagonal D, upper triangular U stored as (u01,u02,u12) */ void qDUDecomposition (Matrix3& rkQ, Vector3& rkD, Vector3& rkU) const; /** Polar decomposition of a matrix. Based on pseudocode from Nicholas J Higham, "Computing the Polar Decomposition -- with Applications Siam Journal of Science and Statistical Computing, Vol 7, No. 4, October 1986. Decomposes A into R*S, where R is orthogonal and S is symmetric. Ken Shoemake's "Matrix animation and polar decomposition" in Proceedings of the conference on Graphics interface '92 seems to be better known in the world of graphics, but Higham's version uses a scaling constant that can lead to faster convergence than Shoemake's when the initial matrix is far from orthogonal. */ void polarDecomposition(Matrix3 &R, Matrix3 &S) const; /** * Matrix norms. */ float spectralNorm () const; float squaredFrobeniusNorm() const; float frobeniusNorm() const; float l1Norm() const; float lInfNorm() const; float diffOneNorm(const Matrix3 &y) const; /** matrix must be orthonormal */ void toAxisAngle(Vector3& rkAxis, float& rfRadians) const; static Matrix3 fromDiagonal(const Vector3& d) { return Matrix3(d.x, 0, 0, 0, d.y, 0, 0, 0, d.z); } /** \sa fromUnitAxisAngle */ static Matrix3 fromAxisAngle(const Vector3& rkAxis, float fRadians); /** Assumes that rkAxis has unit length */ static Matrix3 fromUnitAxisAngle(const Vector3& rkAxis, float fRadians); /** * The matrix must be orthonormal. The decomposition is yaw*pitch*roll * where yaw is rotation about the Up vector, pitch is rotation about the * right axis, and roll is rotation about the Direction axis. */ bool toEulerAnglesXYZ (float& rfYAngle, float& rfPAngle, float& rfRAngle) const; bool toEulerAnglesXZY (float& rfYAngle, float& rfPAngle, float& rfRAngle) const; bool toEulerAnglesYXZ (float& rfYAngle, float& rfPAngle, float& rfRAngle) const; bool toEulerAnglesYZX (float& rfYAngle, float& rfPAngle, float& rfRAngle) const; bool toEulerAnglesZXY (float& rfYAngle, float& rfPAngle, float& rfRAngle) const; bool toEulerAnglesZYX (float& rfYAngle, float& rfPAngle, float& rfRAngle) const; static Matrix3 fromEulerAnglesXYZ (float fYAngle, float fPAngle, float fRAngle); static Matrix3 fromEulerAnglesXZY (float fYAngle, float fPAngle, float fRAngle); static Matrix3 fromEulerAnglesYXZ (float fYAngle, float fPAngle, float fRAngle); static Matrix3 fromEulerAnglesYZX (float fYAngle, float fPAngle, float fRAngle); static Matrix3 fromEulerAnglesZXY (float fYAngle, float fPAngle, float fRAngle); static Matrix3 fromEulerAnglesZYX (float fYAngle, float fPAngle, float fRAngle); /** eigensolver, matrix must be symmetric */ void eigenSolveSymmetric (float afEigenvalue[3], Vector3 akEigenvector[3]) const; static void tensorProduct (const Vector3& rkU, const Vector3& rkV, Matrix3& rkProduct); std::string toString() const; static const float EPSILON; // Special values. // The unguaranteed order of initialization of static variables across // translation units can be a source of annoying bugs, so now the static // special values (like Vector3::ZERO, Color3::WHITE, ...) are wrapped // inside static functions that return references to them. // These functions are intentionally not inlined, because: // "You might be tempted to write [...] them as inline functions // inside their respective header files, but this is something you // must definitely not do. An inline function can be duplicated // in every file in which it appears œóõ½ and this duplication // includes the static object definition. Because inline functions // automatically default to internal linkage, this would result in // having multiple static objects across the various translation // units, which would certainly cause problems. So you must // ensure that there is only one definition of each wrapping // function, and this means not making the wrapping functions inline", // according to Chapter 10 of "Thinking in C++, 2nd ed. Volume 1" by Bruce Eckel, // http://www.mindview.net/ static const Matrix3& zero(); static const Matrix3& identity(); protected: // support for eigensolver void tridiagonal (float afDiag[3], float afSubDiag[3]); bool qLAlgorithm (float afDiag[3], float afSubDiag[3]); // support for singular value decomposition static const float ms_fSvdEpsilon; static const int ms_iSvdMaxIterations; static void bidiagonalize (Matrix3& kA, Matrix3& kL, Matrix3& kR); static void golubKahanStep (Matrix3& kA, Matrix3& kL, Matrix3& kR); // support for spectral norm static float maxCubicRoot (float afCoeff[3]); }; //---------------------------------------------------------------------------- /** v * M == M.transpose() * v */ inline Vector3 operator* (const Vector3& rkPoint, const Matrix3& rkMatrix) { Vector3 kProd; for (int r = 0; r < 3; ++r) { kProd[r] = rkPoint[0] * rkMatrix.elt[0][r] + rkPoint[1] * rkMatrix.elt[1][r] + rkPoint[2] * rkMatrix.elt[2][r]; } return kProd; } } // namespace #endif