// David Eberly, Geometric Tools, Redmond WA 98052 // Copyright (c) 1998-2020 // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt // Version: 4.0.2020.01.10 #pragma once #include #include #include #include // Transforms when GTE_USE_MAT_VEC is defined in the preprocessor symbols. // // The transform is Y = M*X+T, where M is a 3-by-3 matrix and T is a 3x1 // translation. In most cases, M = R, a rotation matrix, or M = R*S, // where R is a rotation matrix and S is a diagonal matrix whose diagonal // entries are positive scales. To support modeling packages that allow // general affine transforms, M can be any invertible 3x3 matrix. The vector // X is transformed in the "forward" direction to Y. The "inverse" direction // transforms Y to X, namely X = M^{-1}*(Y-T) in the general case. In the // special case of M = R*S, the inverse direction is X = S^{-1}*R^t*(Y-T), // where S^{-1} is the diagonal matrix whose diagonal entries are the // reciprocoals of those of S and where R^t is the transpose of R. For SIMD // support of matrix-vector and matrix-matrix multiplications, a homogeneous // matrix H = {{M,T},{0,1}} is stored by this class. The forward transform is // {Y,1} = H*{X,1} and the inverse transform is {X,1} = H^{-1}*{Y,1}. // Transforms when GTE_USE_MAT_VEC is NOT defined in the preprocessor symbols. // // The transform is Y = T + X*M, where M is a 3-by-3 matrix and T is a 1x3 // translation. In most cases, M = R, a rotation matrix, or M = S*R, // where R is a rotation matrix and S is a diagonal matrix whose diagonal // entries are positive scales. To support modeling packages that allow // general affine transforms, M can be any invertible 3x3 matrix. The vector // X is transformed in the "forward" direction to Y. The "inverse" direction // transforms Y to X, namely X = (Y-T)*M^{-1} in the general case. In the // special case of M = S*R, the inverse direction is X = (Y-T)*R^t*S^{-1}, // where S^{-1} is the diagonal matrix whose diagonal entries are the // reciprocoals of those of S and where R^t is the transpose of R. For SIMD // support of matrix-vector and matrix-matrix multiplications, a homogeneous // matrix H = {{M,0},{T,1}} is stored by this class. The forward transform is // {Y,1} = {X,1}*H and the inverse transform is {X,1} = {Y,1}*H^{-1}. // With either multiplication convention, a matrix M = R*S (GTE_USE_MAT_VEC) // or a matrix M = S*R (!GTE_USE_VEC_MAT) is referred to as an "RS-matrix". // The class does not provide a member function to compute the inverse of a // transform: 'Transform GetInverse() const'. If one were to add this, // be aware that the inverse of an RS-matrix is not generally an RS-matrix; // that is, the inverse of R*S is S^{-1}*R^t which cannot always be factored // as S^{-1} * R^t = R' * S'. You would need to SetMatrix using S^{-1}*R^t // as the input. namespace WwiseGTE { template class Transform { public: // The default constructor produces the identity transformation. The // default copy constructor and assignment operator are generated by // the compiler. Transform() : mTranslate{ (Real)0, (Real)0, (Real)0, (Real)1 }, mScale{ (Real)1, (Real)1, (Real)1, (Real)1 }, mIsIdentity(true), mIsRSMatrix(true), mIsUniformScale(true), mInverseNeedsUpdate(false) { mHMatrix.MakeIdentity(); mInvHMatrix.MakeIdentity(); mMatrix.MakeIdentity(); } // Implicit conversion. inline operator Matrix4x4 const& () const { return GetHMatrix(); } // Set the transformation to the identity matrix. void MakeIdentity() { mMatrix.MakeIdentity(); mTranslate = { (Real)0, (Real)0, (Real)0, (Real)1 }; mScale = { (Real)1, (Real)1, (Real)1, (Real)1 }; mIsIdentity = true; mIsRSMatrix = true; mIsUniformScale = true; UpdateHMatrix(); } // Set the transformation to have scales of 1. void MakeUnitScale() { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); mScale = { (Real)1, (Real)1, (Real)1, (Real)1 }; mIsUniformScale = true; UpdateHMatrix(); } // Hints about the structure of the transformation. // M = I inline bool IsIdentity() const { return mIsIdentity; } // R*S (GTE_USE_MAT_VEC defined) or S*R (GTE_USE_MAT_VEC not defined) inline bool IsRSMatrix() const { return mIsRSMatrix; } // RS-matrix with S = c*I inline bool IsUniformScale() const { return mIsRSMatrix && mIsUniformScale; } // Member access. // (1) The Set* functions set the is-identity hint to false. // (2) The SetRotate function sets the is-rsmatrix hint to true. If this // hint is false, GetRotate triggers an assertion in debug mode. // (3) The SetMatrix function sets the is-rsmatrix and is-uniform-scale // hints to false. // (4) The SetScale function sets the is-uniform-scale hint to false. // The SetUniformScale function sets the is-uniform-scale hint to // true. If this hint is false, GetUniformScale triggers an assertion // in debug mode. // (5) All Set* functions set the inverse-needs-update to true. When // GetHInverse is called, the inverse must be computed in this case // and the inverse-needs-update is reset to false. // {{R,0},{0,1}} void SetRotation(Matrix4x4 const& rotate) { mMatrix = rotate; mIsIdentity = false; mIsRSMatrix = true; UpdateHMatrix(); } // {{M,0},{0,1}} void SetMatrix(Matrix4x4 const& matrix) { mMatrix = matrix; mIsIdentity = false; mIsRSMatrix = false; mIsUniformScale = false; UpdateHMatrix(); } void SetTranslation(Real x0, Real x1, Real x2) { mTranslate = Vector4{ x0, x1, x2, (Real)1 }; mIsIdentity = false; UpdateHMatrix(); } inline void SetTranslation(Vector3 const& translate) { SetTranslation(translate[0], translate[1], translate[2]); } inline void SetTranslation(Vector4 const& translate) { SetTranslation(translate[0], translate[1], translate[2]); } void SetScale(Real s0, Real s1, Real s2) { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); LogAssert(s0 != (Real)0 && s1 != (Real)0 && s2 != (Real)0, "Scales must be nonzero."); mScale = { s0, s1, s2, (Real)1 }; mIsIdentity = false; mIsUniformScale = false; UpdateHMatrix(); } inline void SetScale(Vector3 const& scale) { SetScale(scale[0], scale[1], scale[2]); } inline void SetScale(Vector4 const& scale) { SetScale(scale[0], scale[1], scale[2]); } void SetUniformScale(Real scale) { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); LogAssert(scale != (Real)0, "Scale must be nonzero."); mScale = { scale, scale, scale, (Real)1 }; mIsIdentity = false; mIsUniformScale = true; UpdateHMatrix(); } // {{R,0},{0,1}} Matrix4x4 const& GetRotation() const { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); return mMatrix; } // {{M,0},{0,1}} inline Matrix4x4 const& GetMatrix() const { return mMatrix; } // (x,y,z) inline Vector3 GetTranslation() const { return Vector3{ mTranslate[0], mTranslate[1], mTranslate[2] }; } // (x,y,z,0) inline Vector4 GetTranslationW0() const { return Vector4{ mTranslate[0], mTranslate[1], mTranslate[2], (Real)0 }; } // (x,y,z,1) inline Vector4 GetTranslationW1() const { return Vector4{ mTranslate[0], mTranslate[1], mTranslate[2], (Real)1 }; } // (s0,s1,s2) Vector3 GetScale() const { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); return Vector3{ mScale[0], mScale[1], mScale[2] }; } // (s0,s1,s2,1) Vector4 GetScaleW1() const { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); return Vector4{ mScale[0], mScale[1], mScale[2], (Real)1 }; } Real GetUniformScale() const { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); LogAssert(mIsUniformScale, "Transform is not uniform scale."); return mScale[0]; } // Alternate representations to set/get the rotation. // Set/get from 3x3 matrices. void SetRotation(Matrix3x3 const& rotate) { mMatrix.MakeIdentity(); for (int r = 0; r < 3; ++r) { for (int c = 0; c < 3; ++c) { mMatrix(r, c) = rotate(r, c); } } mIsIdentity = false; mIsRSMatrix = true; UpdateHMatrix(); } void GetRotation(Matrix3x3& rotate) const { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); for (int r = 0; r < 3; ++r) { for (int c = 0; c < 3; ++c) { rotate(r, c) = mMatrix(r, c); } } } // The quaternion is unit length. void SetRotation(Quaternion const& q) { mMatrix = Rotation<4, Real>(q); mIsIdentity = false; mIsRSMatrix = true; UpdateHMatrix(); } void GetRotation(Quaternion& q) const { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); q = Rotation<4, Real>(mMatrix); } // The axis is unit length and the angle is in radians. void SetRotation(AxisAngle<3, Real> const& axisAngle) { AxisAngle<4, Real> aa4(HLift(axisAngle.axis, (Real)1), axisAngle.angle); mMatrix = Rotation<4, Real>(aa4); mIsIdentity = false; mIsRSMatrix = true; UpdateHMatrix(); } void GetRotation(AxisAngle<3, Real>& axisAngle) const { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); AxisAngle<4, Real> aa4 = Rotation<4, Real>(mMatrix); axisAngle.axis = HProject(aa4.axis); axisAngle.angle = aa4.angle; } void SetRotation(AxisAngle<4, Real> const& axisAngle) { mMatrix = Rotation<4, Real>(axisAngle); mIsIdentity = false; mIsRSMatrix = true; UpdateHMatrix(); } void GetRotation(AxisAngle<4, Real>& axisAngle) const { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); axisAngle = Rotation<4, Real>(mMatrix); } // The Euler angles are in radians. The GetEulerAngles function // expects the eulerAngles.axis[] values to be set to the axis order // you want. void SetRotation(EulerAngles const& eulerAngles) { mMatrix = Rotation<4, Real>(eulerAngles); mIsIdentity = false; mIsRSMatrix = true; UpdateHMatrix(); } void GetRotation(EulerAngles& eulerAngles) const { LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); eulerAngles = Rotation<4, Real>(mMatrix)(eulerAngles.axis[0], eulerAngles.axis[1], eulerAngles.axis[2]); } // For M = R*S or M = S*R, the largest value of S in absolute value is // returned. For general M, the max-row-sum norm is returned when // GTE_USE_MAT_VEC is defined or the max-col-sum norm is returned when // the GTE_USE_MAT_VEC is not defined, which is a reasonable measure // of maximum scale of the transformation. Real GetNorm() const { Real sum0, sum1, sum2; if (mIsRSMatrix) { // A RS matrix (GTE_USE_MAT_VEC defined) or an SR matrix // (GTE_USE_MAT_VEC is not defined). sum0 = std::fabs(mScale[0]); sum1 = std::fabs(mScale[1]); sum2 = std::fabs(mScale[2]); } else { // The spectral norm (the maximum absolute value of the // eigenvalues) is smaller or equal to this norm. Therefore, // this function returns an approximation to the maximum // scale. #if defined(GTE_USE_MAT_VEC) // Use the max-row-sum matrix norm. sum0 = std::fabs(mMatrix(0, 0)) + std::fabs(mMatrix(0, 1)) + std::fabs(mMatrix(0, 2)); sum1 = std::fabs(mMatrix(1, 0)) + std::fabs(mMatrix(1, 1)) + std::fabs(mMatrix(1, 2)); sum2 = std::fabs(mMatrix(2, 0)) + std::fabs(mMatrix(2, 1)) + std::fabs(mMatrix(2, 2)); #else // Use the max-col-sum matrix norm. sum0 = std::fabs(mMatrix(0, 0)) + std::fabs(mMatrix(1, 0)) + std::fabs(mMatrix(2, 0)); sum1 = std::fabs(mMatrix(0, 1)) + std::fabs(mMatrix(1, 1)) + std::fabs(mMatrix(2, 1)); sum2 = std::fabs(mMatrix(0, 2)) + std::fabs(mMatrix(1, 2)) + std::fabs(mMatrix(2, 2)); #endif } return std::max(std::max(sum0, sum1), sum2); } // Get the homogeneous matrix (composite of all channels). inline Matrix4x4 const& GetHMatrix() const { return mHMatrix; } // Get the inverse homogeneous matrix, recomputing it when necessary. // GTE_USE_MAT_VEC // H = {{M,T},{0,1}}, then H^{-1} = {{M^{-1},-M^{-1}*T},{0,1}} // GTE_USE_VEC_MAT // H = {{M,0},{T,1}}, then H^{-1} = {{M^{-1},0},{-M^{-1}*T,1}} Matrix4x4 const& GetHInverse() const { if (mInverseNeedsUpdate) { if (mIsIdentity) { mInvHMatrix.MakeIdentity(); } else { if (mIsRSMatrix) { if (mIsUniformScale) { Real invScale = (Real)1 / mScale[0]; #if defined(GTE_USE_MAT_VEC) mInvHMatrix(0, 0) = invScale * mMatrix(0, 0); mInvHMatrix(0, 1) = invScale * mMatrix(1, 0); mInvHMatrix(0, 2) = invScale * mMatrix(2, 0); mInvHMatrix(1, 0) = invScale * mMatrix(0, 1); mInvHMatrix(1, 1) = invScale * mMatrix(1, 1); mInvHMatrix(1, 2) = invScale * mMatrix(2, 1); mInvHMatrix(2, 0) = invScale * mMatrix(0, 2); mInvHMatrix(2, 1) = invScale * mMatrix(1, 2); mInvHMatrix(2, 2) = invScale * mMatrix(2, 2); #else mInvHMatrix(0, 0) = mMatrix(0, 0) * invScale; mInvHMatrix(0, 1) = mMatrix(1, 0) * invScale; mInvHMatrix(0, 2) = mMatrix(2, 0) * invScale; mInvHMatrix(1, 0) = mMatrix(0, 1) * invScale; mInvHMatrix(1, 1) = mMatrix(1, 1) * invScale; mInvHMatrix(1, 2) = mMatrix(2, 1) * invScale; mInvHMatrix(2, 0) = mMatrix(0, 2) * invScale; mInvHMatrix(2, 1) = mMatrix(1, 2) * invScale; mInvHMatrix(2, 2) = mMatrix(2, 2) * invScale; #endif } else { // Replace 3 reciprocals by 6 multiplies and // 1 reciprocal. Real s01 = mScale[0] * mScale[1]; Real s02 = mScale[0] * mScale[2]; Real s12 = mScale[1] * mScale[2]; Real invs012 = (Real)1 / (s01 * mScale[2]); Real invS0 = s12 * invs012; Real invS1 = s02 * invs012; Real invS2 = s01 * invs012; #if defined(GTE_USE_MAT_VEC) mInvHMatrix(0, 0) = invS0 * mMatrix(0, 0); mInvHMatrix(0, 1) = invS0 * mMatrix(1, 0); mInvHMatrix(0, 2) = invS0 * mMatrix(2, 0); mInvHMatrix(1, 0) = invS1 * mMatrix(0, 1); mInvHMatrix(1, 1) = invS1 * mMatrix(1, 1); mInvHMatrix(1, 2) = invS1 * mMatrix(2, 1); mInvHMatrix(2, 0) = invS2 * mMatrix(0, 2); mInvHMatrix(2, 1) = invS2 * mMatrix(1, 2); mInvHMatrix(2, 2) = invS2 * mMatrix(2, 2); #else mInvHMatrix(0, 0) = mMatrix(0, 0) * invS0; mInvHMatrix(0, 1) = mMatrix(1, 0) * invS1; mInvHMatrix(0, 2) = mMatrix(2, 0) * invS2; mInvHMatrix(1, 0) = mMatrix(0, 1) * invS0; mInvHMatrix(1, 1) = mMatrix(1, 1) * invS1; mInvHMatrix(1, 2) = mMatrix(2, 1) * invS2; mInvHMatrix(2, 0) = mMatrix(0, 2) * invS0; mInvHMatrix(2, 1) = mMatrix(1, 2) * invS1; mInvHMatrix(2, 2) = mMatrix(2, 2) * invS2; #endif } } else { Invert3x3(mHMatrix, mInvHMatrix); } #if defined(GTE_USE_MAT_VEC) mInvHMatrix(0, 3) = -( mInvHMatrix(0, 0) * mTranslate[0] + mInvHMatrix(0, 1) * mTranslate[1] + mInvHMatrix(0, 2) * mTranslate[2] ); mInvHMatrix(1, 3) = -( mInvHMatrix(1, 0) * mTranslate[0] + mInvHMatrix(1, 1) * mTranslate[1] + mInvHMatrix(1, 2) * mTranslate[2] ); mInvHMatrix(2, 3) = -( mInvHMatrix(2, 0) * mTranslate[0] + mInvHMatrix(2, 1) * mTranslate[1] + mInvHMatrix(2, 2) * mTranslate[2] ); // The last row of mHMatrix is always (0,0,0,1) for an // affine transformation, so it is set once in the // constructor. It is not necessary to reset it here. #else mInvHMatrix(3, 0) = -( mInvHMatrix(0, 0) * mTranslate[0] + mInvHMatrix(1, 0) * mTranslate[1] + mInvHMatrix(2, 0) * mTranslate[2] ); mInvHMatrix(3, 1) = -( mInvHMatrix(0, 1) * mTranslate[0] + mInvHMatrix(1, 1) * mTranslate[1] + mInvHMatrix(2, 1) * mTranslate[2] ); mInvHMatrix(3, 2) = -( mInvHMatrix(0, 2) * mTranslate[0] + mInvHMatrix(1, 2) * mTranslate[1] + mInvHMatrix(2, 2) * mTranslate[2] ); // The last column of mHMatrix is always (0,0,0,1) for an // affine transformation, so it is set once in the // constructor. It is not necessary to reset it here. #endif } mInverseNeedsUpdate = false; } return mInvHMatrix; } // Invert the transform. If possible, the channels are properly // assigned. For example, if the input has mIsRSMatrix equal to // 'true', then the inverse also has mIsRSMatrix equal to 'true' // and the inverse's mMatrix is a rotation matrix and mScale is // set accordingly. Transform Inverse() const { Transform inverse; if (mIsIdentity) { inverse.MakeIdentity(); } else { if (mIsRSMatrix && mIsUniformScale) { inverse.SetRotation(Transpose(GetRotation())); inverse.SetUniformScale(1.0f / GetUniformScale()); } else { Matrix4x4 invUpper; Invert3x3(GetMatrix(), invUpper); inverse.SetMatrix(invUpper); } Vector4 trn = -GetTranslationW0(); inverse.SetTranslation(inverse.GetMatrix() * trn); } mInverseNeedsUpdate = true; return inverse; } // The identity transformation. static Transform Identity() { static Transform identity; return identity; } private: // Fill in the entries of mHMatrix whenever one of the components // mMatrix, mTranslate, or mScale changes. void UpdateHMatrix() { if (mIsIdentity) { mHMatrix.MakeIdentity(); } else { if (mIsRSMatrix) { #if defined(GTE_USE_MAT_VEC) mHMatrix(0, 0) = mMatrix(0, 0) * mScale[0]; mHMatrix(0, 1) = mMatrix(0, 1) * mScale[1]; mHMatrix(0, 2) = mMatrix(0, 2) * mScale[2]; mHMatrix(1, 0) = mMatrix(1, 0) * mScale[0]; mHMatrix(1, 1) = mMatrix(1, 1) * mScale[1]; mHMatrix(1, 2) = mMatrix(1, 2) * mScale[2]; mHMatrix(2, 0) = mMatrix(2, 0) * mScale[0]; mHMatrix(2, 1) = mMatrix(2, 1) * mScale[1]; mHMatrix(2, 2) = mMatrix(2, 2) * mScale[2]; #else mHMatrix(0, 0) = mScale[0] * mMatrix(0, 0); mHMatrix(0, 1) = mScale[0] * mMatrix(0, 1); mHMatrix(0, 2) = mScale[0] * mMatrix(0, 2); mHMatrix(1, 0) = mScale[1] * mMatrix(1, 0); mHMatrix(1, 1) = mScale[1] * mMatrix(1, 1); mHMatrix(1, 2) = mScale[1] * mMatrix(1, 2); mHMatrix(2, 0) = mScale[2] * mMatrix(2, 0); mHMatrix(2, 1) = mScale[2] * mMatrix(2, 1); mHMatrix(2, 2) = mScale[2] * mMatrix(2, 2); #endif } else { mHMatrix(0, 0) = mMatrix(0, 0); mHMatrix(0, 1) = mMatrix(0, 1); mHMatrix(0, 2) = mMatrix(0, 2); mHMatrix(1, 0) = mMatrix(1, 0); mHMatrix(1, 1) = mMatrix(1, 1); mHMatrix(1, 2) = mMatrix(1, 2); mHMatrix(2, 0) = mMatrix(2, 0); mHMatrix(2, 1) = mMatrix(2, 1); mHMatrix(2, 2) = mMatrix(2, 2); } #if defined(GTE_USE_MAT_VEC) mHMatrix(0, 3) = mTranslate[0]; mHMatrix(1, 3) = mTranslate[1]; mHMatrix(2, 3) = mTranslate[2]; // The last row of mHMatrix is always (0,0,0,1) for an affine // transformation, so it is set once in the constructor. It // is not necessary to reset it here. #else mHMatrix(3, 0) = mTranslate[0]; mHMatrix(3, 1) = mTranslate[1]; mHMatrix(3, 2) = mTranslate[2]; // The last column of mHMatrix is always (0,0,0,1) for an // affine transformation, so it is set once in the // constructor. It is not necessary to reset it here. #endif } mInverseNeedsUpdate = true; } // Invert the 3x3 upper-left block of the input matrix. static void Invert3x3(Matrix4x4 const& mat, Matrix4x4& invMat) { // Compute the adjoint of M (3x3). invMat(0, 0) = mat(1, 1) * mat(2, 2) - mat(1, 2) * mat(2, 1); invMat(0, 1) = mat(0, 2) * mat(2, 1) - mat(0, 1) * mat(2, 2); invMat(0, 2) = mat(0, 1) * mat(1, 2) - mat(0, 2) * mat(1, 1); invMat(0, 3) = 0.0f; invMat(1, 0) = mat(1, 2) * mat(2, 0) - mat(1, 0) * mat(2, 2); invMat(1, 1) = mat(0, 0) * mat(2, 2) - mat(0, 2) * mat(2, 0); invMat(1, 2) = mat(0, 2) * mat(1, 0) - mat(0, 0) * mat(1, 2); invMat(1, 3) = 0.0f; invMat(2, 0) = mat(1, 0) * mat(2, 1) - mat(1, 1) * mat(2, 0); invMat(2, 1) = mat(0, 1) * mat(2, 0) - mat(0, 0) * mat(2, 1); invMat(2, 2) = mat(0, 0) * mat(1, 1) - mat(0, 1) * mat(1, 0); invMat(2, 3) = 0.0f; invMat(3, 0) = 0.0f; invMat(3, 1) = 0.0f; invMat(3, 2) = 0.0f; invMat(3, 3) = 1.0f; // Compute the reciprocal of the determinant of M. Real invDet = (Real)1 / ( mat(0, 0) * invMat(0, 0) + mat(0, 1) * invMat(1, 0) + mat(0, 2) * invMat(2, 0) ); // inverse(M) = adjoint(M)/determinant(M). invMat(0, 0) *= invDet; invMat(0, 1) *= invDet; invMat(0, 2) *= invDet; invMat(1, 0) *= invDet; invMat(1, 1) *= invDet; invMat(1, 2) *= invDet; invMat(2, 0) *= invDet; invMat(2, 1) *= invDet; invMat(2, 2) *= invDet; } // The full 4x4 homogeneous matrix H and its inverse H^{-1}, stored // according to the conventions (see GetHInverse description). The // inverse is computed only on demand. Matrix4x4 mHMatrix; mutable Matrix4x4 mInvHMatrix; Matrix4x4 mMatrix; // M (general) or R (rotation) Vector4 mTranslate; // T Vector4 mScale; // S bool mIsIdentity, mIsRSMatrix, mIsUniformScale; mutable bool mInverseNeedsUpdate; }; // Compute M*V. template Vector4 operator*(Transform const& M, Vector4 const& V) { return M.GetHMatrix() * V; } // Compute V^T*M. template Vector4 operator*(Vector4 const& V, Transform const& M) { return V * M.GetHMatrix(); } // Compute A*B. template Transform operator*(Transform const& A, Transform const& B) { if (A.IsIdentity()) { return B; } if (B.IsIdentity()) { return A; } Transform product; if (A.IsRSMatrix() && B.IsRSMatrix()) { #if defined(GTE_USE_MAT_VEC) if (A.IsUniformScale()) { product.SetRotation(A.GetRotation() * B.GetRotation()); product.SetTranslation(A.GetUniformScale() * ( A.GetRotation() * B.GetTranslationW0()) + A.GetTranslationW1()); if (B.IsUniformScale()) { product.SetUniformScale(A.GetUniformScale() * B.GetUniformScale()); } else { product.SetScale(A.GetUniformScale() * B.GetScale()); } return product; } #else if (B.IsUniformScale()) { product.SetRotation(A.GetRotation() * B.GetRotation()); product.SetTranslation(B.GetUniformScale() * ( A.GetTranslationW0() * B.GetRotation()) + B.GetTranslationW1()); if (A.IsUniformScale()) { product.SetUniformScale(A.GetUniformScale() * B.GetUniformScale()); } else { product.SetScale(A.GetScale() * B.GetUniformScale()); } return product; } #endif } // In all remaining cases, the matrix cannot be written as R*S*X+T. Matrix4x4 matMA; if (A.IsRSMatrix()) { #if defined(GTE_USE_MAT_VEC) matMA = MultiplyMD(A.GetRotation(), A.GetScaleW1()); #else matMA = MultiplyDM(A.GetScaleW1(), A.GetRotation()); #endif } else { matMA = A.GetMatrix(); } Matrix4x4 matMB; if (B.IsRSMatrix()) { #if defined(GTE_USE_MAT_VEC) matMB = MultiplyMD(B.GetRotation(), B.GetScaleW1()); #else matMB = MultiplyDM(B.GetScaleW1(), B.GetRotation()); #endif } else { matMB = B.GetMatrix(); } product.SetMatrix(matMA * matMB); #if defined(GTE_USE_MAT_VEC) product.SetTranslation(matMA * B.GetTranslationW0() + A.GetTranslationW1()); #else product.SetTranslation(A.GetTranslationW0() * matMB + B.GetTranslationW1()); #endif return product; } template inline Matrix4x4 operator*(Matrix4x4 const& A, Transform const& B) { return A * B.GetHMatrix(); } template inline Matrix4x4 operator*(Transform const& A, Matrix4x4 const& B) { return A.GetHMatrix()* B; } }