import { Vector3 } from './Vector3.js';
import { WorldUp } from '../constants.js';
const localRight = new Vector3();
const worldRight = new Vector3();
const perpWorldUp = new Vector3();
const temp = new Vector3();
const colVal = [ 2, 2, 1 ];
const rowVal = [ 1, 0, 0 ];
/**
* Class representing a 3x3 matrix. The elements of the matrix
* are stored in column-major order.
*
* @author {@link https://github.com/Mugen87|Mugen87}
*/
class Matrix3 {
/**
* Constructs a new 3x3 identity matrix.
*/
constructor() {
/**
* The elements of the matrix in column-major order.
* @type {Array<Number>}
*/
this.elements = [
1, 0, 0,
0, 1, 0,
0, 0, 1
];
}
/**
* Sets the given values to this matrix. The arguments are in row-major order.
*
* @param {Number} n11 - An element of the matrix.
* @param {Number} n12 - An element of the matrix.
* @param {Number} n13 - An element of the matrix.
* @param {Number} n21 - An element of the matrix.
* @param {Number} n22 - An element of the matrix.
* @param {Number} n23 - An element of the matrix.
* @param {Number} n31 - An element of the matrix.
* @param {Number} n32 - An element of the matrix.
* @param {Number} n33 - An element of the matrix.
* @return {Matrix3} A reference to this matrix.
*/
set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
const e = this.elements;
e[ 0 ] = n11; e[ 3 ] = n12; e[ 6 ] = n13;
e[ 1 ] = n21; e[ 4 ] = n22; e[ 7 ] = n23;
e[ 2 ] = n31; e[ 5 ] = n32; e[ 8 ] = n33;
return this;
}
/**
* Copies all values from the given matrix to this matrix.
*
* @param {Matrix3} m - The matrix to copy.
* @return {Matrix3} A reference to this matrix.
*/
copy( m ) {
const e = this.elements;
const me = m.elements;
e[ 0 ] = me[ 0 ]; e[ 1 ] = me[ 1 ]; e[ 2 ] = me[ 2 ];
e[ 3 ] = me[ 3 ]; e[ 4 ] = me[ 4 ]; e[ 5 ] = me[ 5 ];
e[ 6 ] = me[ 6 ]; e[ 7 ] = me[ 7 ]; e[ 8 ] = me[ 8 ];
return this;
}
/**
* Creates a new matrix and copies all values from this matrix.
*
* @return {Matrix3} A new matrix.
*/
clone() {
return new this.constructor().copy( this );
}
/**
* Transforms this matrix to an identity matrix.
*
* @return {Matrix3} A reference to this matrix.
*/
identity() {
this.set(
1, 0, 0,
0, 1, 0,
0, 0, 1
);
return this;
}
/**
* Multiplies this matrix with the given matrix.
*
* @param {Matrix3} m - The matrix to multiply.
* @return {Matrix3} A reference to this matrix.
*/
multiply( m ) {
return this.multiplyMatrices( this, m );
}
/**
* Multiplies this matrix with the given matrix.
* So the order of the multiplication is switched compared to {@link Matrix3#multiply}.
*
* @param {Matrix3} m - The matrix to multiply.
* @return {Matrix3} A reference to this matrix.
*/
premultiply( m ) {
return this.multiplyMatrices( m, this );
}
/**
* Multiplies two given matrices and stores the result in this matrix.
*
* @param {Matrix3} a - The first matrix of the operation.
* @param {Matrix3} b - The second matrix of the operation.
* @return {Matrix3} A reference to this matrix.
*/
multiplyMatrices( a, b ) {
const ae = a.elements;
const be = b.elements;
const e = this.elements;
const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ];
const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ];
const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ];
const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ];
const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ];
const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ];
e[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31;
e[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32;
e[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33;
e[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31;
e[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32;
e[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33;
e[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31;
e[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32;
e[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33;
return this;
}
/**
* Multiplies the given scalar with this matrix.
*
* @param {Number} s - The scalar to multiply.
* @return {Matrix3} A reference to this matrix.
*/
multiplyScalar( s ) {
const e = this.elements;
e[ 0 ] *= s; e[ 3 ] *= s; e[ 6 ] *= s;
e[ 1 ] *= s; e[ 4 ] *= s; e[ 7 ] *= s;
e[ 2 ] *= s; e[ 5 ] *= s; e[ 8 ] *= s;
return this;
}
/**
* Extracts the basis vectors and stores them to the given vectors.
*
* @param {Vector3} xAxis - The first result vector for the x-axis.
* @param {Vector3} yAxis - The second result vector for the y-axis.
* @param {Vector3} zAxis - The third result vector for the z-axis.
* @return {Matrix3} A reference to this matrix.
*/
extractBasis( xAxis, yAxis, zAxis ) {
xAxis.fromMatrix3Column( this, 0 );
yAxis.fromMatrix3Column( this, 1 );
zAxis.fromMatrix3Column( this, 2 );
return this;
}
/**
* Makes a basis from the given vectors.
*
* @param {Vector3} xAxis - The first basis vector for the x-axis.
* @param {Vector3} yAxis - The second basis vector for the y-axis.
* @param {Vector3} zAxis - The third basis vector for the z-axis.
* @return {Matrix3} A reference to this matrix.
*/
makeBasis( xAxis, yAxis, zAxis ) {
this.set(
xAxis.x, yAxis.x, zAxis.x,
xAxis.y, yAxis.y, zAxis.y,
xAxis.z, yAxis.z, zAxis.z
);
return this;
}
/**
* Creates a rotation matrix that orients an object to face towards a specified target direction.
*
* @param {Vector3} localForward - Specifies the forward direction in the local space of the object.
* @param {Vector3} targetDirection - Specifies the desired world space direction the object should look at.
* @param {Vector3} localUp - Specifies the up direction in the local space of the object.
* @return {Matrix3} A reference to this matrix.
*/
lookAt( localForward, targetDirection, localUp ) {
localRight.crossVectors( localUp, localForward ).normalize();
// orthonormal linear basis A { localRight, localUp, localForward } for the object local space
worldRight.crossVectors( WorldUp, targetDirection ).normalize();
if ( worldRight.squaredLength() === 0 ) {
// handle case when it's not possible to build a basis from targetDirection and worldUp
// slightly shift targetDirection in order to avoid collinearity
temp.copy( targetDirection ).addScalar( Number.EPSILON );
worldRight.crossVectors( WorldUp, temp ).normalize();
}
perpWorldUp.crossVectors( targetDirection, worldRight ).normalize();
// orthonormal linear basis B { worldRight, perpWorldUp, targetDirection } for the desired target orientation
m1.makeBasis( worldRight, perpWorldUp, targetDirection );
m2.makeBasis( localRight, localUp, localForward );
// construct a matrix that maps basis A to B
this.multiplyMatrices( m1, m2.transpose() );
return this;
}
/**
* Transposes this matrix.
*
* @return {Matrix3} A reference to this matrix.
*/
transpose() {
const e = this.elements;
let t;
t = e[ 1 ]; e[ 1 ] = e[ 3 ]; e[ 3 ] = t;
t = e[ 2 ]; e[ 2 ] = e[ 6 ]; e[ 6 ] = t;
t = e[ 5 ]; e[ 5 ] = e[ 7 ]; e[ 7 ] = t;
return this;
}
/**
* Computes the element index according to the given column and row.
*
* @param {Number} column - Index of the column.
* @param {Number} row - Index of the row.
* @return {Number} The index of the element at the provided row and column.
*/
getElementIndex( column, row ) {
return column * 3 + row;
}
/**
* Computes the frobenius norm. It's the squareroot of the sum of all
* squared matrix elements.
*
* @return {Number} The frobenius norm.
*/
frobeniusNorm() {
const e = this.elements;
let norm = 0;
for ( let i = 0; i < 9; i ++ ) {
norm += e[ i ] * e[ i ];
}
return Math.sqrt( norm );
}
/**
* Computes the "off-diagonal" frobenius norm. Assumes the matrix is symmetric.
*
* @return {Number} The "off-diagonal" frobenius norm.
*/
offDiagonalFrobeniusNorm() {
const e = this.elements;
let norm = 0;
for ( let i = 0; i < 3; i ++ ) {
const t = e[ this.getElementIndex( colVal[ i ], rowVal[ i ] ) ];
norm += 2 * t * t; // multiply the result by two since the matrix is symetric
}
return Math.sqrt( norm );
}
/**
* Computes the eigenvectors and eigenvalues.
*
* Reference: https://github.com/AnalyticalGraphicsInc/cesium/blob/411a1afbd36b72df64d7362de6aa934730447234/Source/Core/Matrix3.js#L1141 (Apache License 2.0)
*
* The values along the diagonal of the diagonal matrix are the eigenvalues.
* The columns of the unitary matrix are the corresponding eigenvectors.
*
* @param {Object} result - An object with unitary and diagonal properties which are matrices onto which to store the result.
* @return {Object} An object with unitary and diagonal properties which are matrices onto which to store the result.
*/
eigenDecomposition( result ) {
let count = 0;
let sweep = 0;
const maxSweeps = 10;
result.unitary.identity();
result.diagonal.copy( this );
const unitaryMatrix = result.unitary;
const diagonalMatrix = result.diagonal;
const epsilon = Number.EPSILON * diagonalMatrix.frobeniusNorm();
while ( sweep < maxSweeps && diagonalMatrix.offDiagonalFrobeniusNorm() > epsilon ) {
diagonalMatrix.shurDecomposition( m1 );
m2.copy( m1 ).transpose();
diagonalMatrix.multiply( m1 );
diagonalMatrix.premultiply( m2 );
unitaryMatrix.multiply( m1 );
if ( ++ count > 2 ) {
sweep ++;
count = 0;
}
}
return result;
}
/**
* Finds the largest off-diagonal term and then creates a matrix
* which can be used to help reduce it.
*
* @param {Matrix3} result - The result matrix.
* @return {Matrix3} The result matrix.
*/
shurDecomposition( result ) {
let maxDiagonal = 0;
let rotAxis = 1;
// find pivot (rotAxis) based on largest off-diagonal term
const e = this.elements;
for ( let i = 0; i < 3; i ++ ) {
const t = Math.abs( e[ this.getElementIndex( colVal[ i ], rowVal[ i ] ) ] );
if ( t > maxDiagonal ) {
maxDiagonal = t;
rotAxis = i;
}
}
let c = 1;
let s = 0;
const p = rowVal[ rotAxis ];
const q = colVal[ rotAxis ];
if ( Math.abs( e[ this.getElementIndex( q, p ) ] ) > Number.EPSILON ) {
const qq = e[ this.getElementIndex( q, q ) ];
const pp = e[ this.getElementIndex( p, p ) ];
const qp = e[ this.getElementIndex( q, p ) ];
const tau = ( qq - pp ) / 2 / qp;
let t;
if ( tau < 0 ) {
t = - 1 / ( - tau + Math.sqrt( 1 + tau * tau ) );
} else {
t = 1 / ( tau + Math.sqrt( 1.0 + tau * tau ) );
}
c = 1.0 / Math.sqrt( 1.0 + t * t );
s = t * c;
}
result.identity();
result.elements[ this.getElementIndex( p, p ) ] = c;
result.elements[ this.getElementIndex( q, q ) ] = c;
result.elements[ this.getElementIndex( q, p ) ] = s;
result.elements[ this.getElementIndex( p, q ) ] = - s;
return result;
}
/**
* Creates a rotation matrix from the given quaternion.
*
* @param {Quaternion} q - A quaternion representing a rotation.
* @return {Matrix3} A reference to this matrix.
*/
fromQuaternion( q ) {
const e = this.elements;
const x = q.x, y = q.y, z = q.z, w = q.w;
const x2 = x + x, y2 = y + y, z2 = z + z;
const xx = x * x2, xy = x * y2, xz = x * z2;
const yy = y * y2, yz = y * z2, zz = z * z2;
const wx = w * x2, wy = w * y2, wz = w * z2;
e[ 0 ] = 1 - ( yy + zz );
e[ 3 ] = xy - wz;
e[ 6 ] = xz + wy;
e[ 1 ] = xy + wz;
e[ 4 ] = 1 - ( xx + zz );
e[ 7 ] = yz - wx;
e[ 2 ] = xz - wy;
e[ 5 ] = yz + wx;
e[ 8 ] = 1 - ( xx + yy );
return this;
}
/**
* Sets the elements of this matrix by extracting the upper-left 3x3 portion
* from a 4x4 matrix.
*
* @param {Matrix4} m - A 4x4 matrix.
* @return {Matrix3} A reference to this matrix.
*/
fromMatrix4( m ) {
const e = this.elements;
const me = m.elements;
e[ 0 ] = me[ 0 ]; e[ 1 ] = me[ 1 ]; e[ 2 ] = me[ 2 ];
e[ 3 ] = me[ 4 ]; e[ 4 ] = me[ 5 ]; e[ 5 ] = me[ 6 ];
e[ 6 ] = me[ 8 ]; e[ 7 ] = me[ 9 ]; e[ 8 ] = me[ 10 ];
return this;
}
/**
* Sets the elements of this matrix from an array.
*
* @param {Array<Number>} array - An array.
* @param {Number} offset - An optional offset.
* @return {Matrix3} A reference to this matrix.
*/
fromArray( array, offset = 0 ) {
const e = this.elements;
for ( let i = 0; i < 9; i ++ ) {
e[ i ] = array[ i + offset ];
}
return this;
}
/**
* Copies all elements of this matrix to the given array.
*
* @param {Array<Number>} array - An array.
* @param {Number} offset - An optional offset.
* @return {Array<Number>} The array with the elements of the matrix.
*/
toArray( array, offset = 0 ) {
const e = this.elements;
array[ offset + 0 ] = e[ 0 ];
array[ offset + 1 ] = e[ 1 ];
array[ offset + 2 ] = e[ 2 ];
array[ offset + 3 ] = e[ 3 ];
array[ offset + 4 ] = e[ 4 ];
array[ offset + 5 ] = e[ 5 ];
array[ offset + 6 ] = e[ 6 ];
array[ offset + 7 ] = e[ 7 ];
array[ offset + 8 ] = e[ 8 ];
return array;
}
/**
* Returns true if the given matrix is deep equal with this matrix.
*
* @param {Matrix3} m - The matrix to test.
* @return {Boolean} The result of the equality test.
*/
equals( m ) {
const e = this.elements;
const me = m.elements;
for ( let i = 0; i < 9; i ++ ) {
if ( e[ i ] !== me[ i ] ) return false;
}
return true;
}
}
const m1 = new Matrix3();
const m2 = new Matrix3();
export { Matrix3 };