import { ArgumentError } from '../errors/ArgumentError'; import { Box } from './Box'; import { MathConsts } from './MathConsts'; import { Orientation3D } from './Orientation3D'; import { Plane3D } from './Plane3D'; import { Quaternion } from './Quaternion'; import { Sphere } from './Sphere'; import { Vector3D } from './Vector3D'; export class Matrix3D { private static _identityData: Float32Array = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); private static _tempMatrix: Matrix3D = new Matrix3D(); /** * A reference to a Matrix3D to be used as a temporary data container, preventing object creation. */ public static CALCULATION_MATRIX: Matrix3D = new Matrix3D(); static getAxisRotationMatrix(x: number, y: number, z: number, degrees: number, target: Matrix3D = null): Matrix3D { if (target == null) target = new Matrix3D(); const targetData: Float32Array = target._rawData; const rad = degrees * MathConsts.DEGREES_TO_RADIANS; const c: number = Math.cos(rad); const s: number = Math.sin(rad); const t: number = 1 - c; let tmp1: number, tmp2: number; targetData[0] = c + x * x * t; targetData[5] = c + y * y * t; targetData[10] = c + z * z * t; tmp1 = x * y * t; tmp2 = z * s; targetData[1] = tmp1 + tmp2; targetData[4] = tmp1 - tmp2; tmp1 = x * z * t; tmp2 = y * s; targetData[8] = tmp1 + tmp2; targetData[2] = tmp1 - tmp2; tmp1 = y * z * t; tmp2 = x * s; targetData[9] = tmp1 - tmp2; targetData[6] = tmp1 + tmp2; targetData[3] = 0; targetData[7] = 0; targetData[11] = 0; targetData[12] = 0; targetData[13] = 0; targetData[14] = 0; targetData[15] = 1; return target; } public static getPointAtMatrix(pos: Vector3D, dir: Vector3D, up: Vector3D, target: Matrix3D = null): Matrix3D { let upN: Vector3D; if (target == null) target = new Matrix3D(); const targetData: Float32Array = target._rawData; const dirN = dir.clone(); dirN.normalize(); upN = up.clone(); upN.normalize(); const lftN = upN.crossProduct(dirN); lftN.normalize(); if (lftN.length < 0.05) { lftN.x = upN.y; lftN.y = upN.x; lftN.z = 0; lftN.normalize(); } upN = dirN.crossProduct(lftN); targetData[0] = lftN.x; targetData[1] = lftN.y; targetData[2] = lftN.z; targetData[3] = 0; targetData[4] = upN.x; targetData[5] = upN.y; targetData[6] = upN.z; targetData[7] = 0; targetData[8] = dirN.x; targetData[9] = dirN.y; targetData[10] = dirN.z; targetData[11] = 0; targetData[12] = lftN.dotProduct(pos); targetData[13] = upN.dotProduct(pos); targetData[14] = dirN.dotProduct(pos); targetData[15] = 1; return target; } /** * Fills the 3d matrix with values representing the transformation made by the given quaternion. * * @param quarternion The quarterion object to convert. */ public static getQuaternionMatrix(quarternion: Quaternion, target: Matrix3D = null): Matrix3D { if (target == null) target = new Matrix3D(); const targetData: Float32Array = target._rawData; const x: number = quarternion.x; const y: number = quarternion.y; const z: number = quarternion.z; const w: number = quarternion.w; const xx: number = x * x; const xy: number = x * y; const xz: number = x * z; const xw: number = x * w; const yy: number = y * y; const yz: number = y * z; const yw: number = y * w; const zz: number = z * z; const zw: number = z * w; targetData[0] = 1 - 2 * (yy + zz); targetData[1] = 2 * (xy + zw); targetData[2] = 2 * (xz - yw); targetData[4] = 2 * (xy - zw); targetData[5] = 1 - 2 * (xx + zz); targetData[6] = 2 * (yz + xw); targetData[8] = 2 * (xz + yw); targetData[9] = 2 * (yz - xw); targetData[10] = 1 - 2 * (xx + yy); targetData[3] = 0; targetData[7] = 0; targetData[11] = 0; targetData[12] = 0; targetData[13] = 0; targetData[14] = 0; targetData[15] = 1; return target; } /** * Returns a boolean value representing whether there is any difference between the two given 3d matrices. */ public static compare(m1: Matrix3D, m2: Matrix3D): boolean { const r1: Float32Array = m1._rawData; const r2: Float32Array = m2._rawData; for (let i: number = 0; i < 16; ++i) if (r1[i] != r2[i]) return false; return true; } /** * A Vector of 16 Numbers, where every four elements is a column of a 4x4 matrix. * *

An exception is thrown if the _rawData property is set to a matrix that is not invertible. The Matrix3D * object must be invertible. If a non-invertible matrix is needed, * create a subexport class of the Matrix3D object.

*/ public _rawData: Float32Array; private _position: Vector3D; private _components: Array; /** * [read-only] A number that determines whether a matrix is invertible. */ public get determinant(): number { const raw: Float32Array = this._rawData; return ((raw[0] * raw[5] - raw[4] * raw[1]) * (raw[10] * raw[15] - raw[14] * raw[11]) - (raw[0] * raw[9] - raw[8] * raw[1]) * (raw[6] * raw[15] - raw[14] * raw[7]) + (raw[0] * raw[13] - raw[12] * raw[1]) * (raw[6] * raw[11] - raw[10] * raw[7]) + (raw[4] * raw[9] - raw[8] * raw[5]) * (raw[2] * raw[15] - raw[14] * raw[3]) - (raw[4] * raw[13] - raw[12] * raw[5]) * (raw[2] * raw[11] - raw[10] * raw[3]) + (raw[8] * raw[13] - raw[12] * raw[9]) * (raw[2] * raw[7] - raw[6] * raw[3])); } /** * A Vector3D object that holds the position, the 3D coordinate (x,y,z) of a display object within the * transformation's frame of reference. */ public get position(): Vector3D { if (!this._position) this._position = new Vector3D(new Float32Array(this._rawData.buffer, 48, 4)); return this._position; } /** * Creates a Matrix3D object. */ constructor(rawData: Float32Array = null) { if (rawData != null) { this._rawData = rawData; } else { this._rawData = new Float32Array(16); this.identity(); } } /** * Appends the matrix by multiplying another Matrix3D object by the current Matrix3D object. */ public append(lhs: Matrix3D): void { const raw: Float32Array = this._rawData; const rawLhs: Float32Array = lhs._rawData; const m111: number = raw[0]; const m112: number = raw[1]; const m113: number = raw[2]; const m114: number = raw[3]; const m121: number = raw[4]; const m122: number = raw[5]; const m123: number = raw[6]; const m124: number = raw[7]; const m131: number = raw[8]; const m132: number = raw[9]; const m133: number = raw[10]; const m134: number = raw[11]; const m141: number = raw[12]; const m142: number = raw[13]; const m143: number = raw[14]; const m144: number = raw[15]; const m211: number = rawLhs[0]; const m212: number = rawLhs[1]; const m213: number = rawLhs[2]; const m214: number = rawLhs[3]; const m221: number = rawLhs[4]; const m222: number = rawLhs[5]; const m223: number = rawLhs[6]; const m224: number = rawLhs[7]; const m231: number = rawLhs[8]; const m232: number = rawLhs[9]; const m233: number = rawLhs[10]; const m234: number = rawLhs[11]; const m241: number = rawLhs[12]; const m242: number = rawLhs[13]; const m243: number = rawLhs[14]; const m244: number = rawLhs[15]; raw[0] = m111 * m211 + m112 * m221 + m113 * m231 + m114 * m241; raw[1] = m111 * m212 + m112 * m222 + m113 * m232 + m114 * m242; raw[2] = m111 * m213 + m112 * m223 + m113 * m233 + m114 * m243; raw[3] = m111 * m214 + m112 * m224 + m113 * m234 + m114 * m244; raw[4] = m121 * m211 + m122 * m221 + m123 * m231 + m124 * m241; raw[5] = m121 * m212 + m122 * m222 + m123 * m232 + m124 * m242; raw[6] = m121 * m213 + m122 * m223 + m123 * m233 + m124 * m243; raw[7] = m121 * m214 + m122 * m224 + m123 * m234 + m124 * m244; raw[8] = m131 * m211 + m132 * m221 + m133 * m231 + m134 * m241; raw[9] = m131 * m212 + m132 * m222 + m133 * m232 + m134 * m242; raw[10] = m131 * m213 + m132 * m223 + m133 * m233 + m134 * m243; raw[11] = m131 * m214 + m132 * m224 + m133 * m234 + m134 * m244; raw[12] = m141 * m211 + m142 * m221 + m143 * m231 + m144 * m241; raw[13] = m141 * m212 + m142 * m222 + m143 * m232 + m144 * m242; raw[14] = m141 * m213 + m142 * m223 + m143 * m233 + m144 * m243; raw[15] = m141 * m214 + m142 * m224 + m143 * m234 + m144 * m244; } /** * Appends an incremental rotation to a Matrix3D object. */ public appendRotation(degrees: number, axis: Vector3D): void { this.append(Matrix3D.getAxisRotationMatrix(axis.x, axis.y, axis.z, degrees, Matrix3D._tempMatrix)); } /** * Appends an incremental skew change along the x, y, and z axes to a Matrix3D object. */ public appendSkew(xSkew: number, ySkew: number, zSkew: number): void { if (xSkew == 0 && ySkew == 0 && zSkew == 0) return; const rawData: Float32Array = Matrix3D._tempMatrix._rawData; rawData[0] = 1; rawData[1] = 0; rawData[2] = 0; rawData[3] = 0; rawData[4] = xSkew; rawData[5] = 1; rawData[6] = 0; rawData[7] = 0; rawData[8] = ySkew; rawData[9] = zSkew; rawData[10] = 1; rawData[11] = 0; rawData[12] = 0; rawData[13] = 0; rawData[14] = 0; rawData[15] = 1; this.append(Matrix3D._tempMatrix); } /** * Appends an incremental scale change along the x, y, and z axes to a Matrix3D object. */ public appendScale(xScale: number, yScale: number, zScale: number): void { if (xScale == 1 && yScale == 1 && zScale == 1) return; const rawData: Float32Array = Matrix3D._tempMatrix._rawData; rawData[0] = xScale; rawData[1] = 0; rawData[2] = 0; rawData[3] = 0; rawData[4] = 0; rawData[5] = yScale; rawData[6] = 0; rawData[7] = 0; rawData[8] = 0; rawData[9] = 0; rawData[10] = zScale; rawData[11] = 0; rawData[12] = 0; rawData[13] = 0; rawData[14] = 0; rawData[15] = 1; this.append(Matrix3D._tempMatrix); } /** *Appends an incremental translation, a repositioning along the x, y, and z axes, to a Matrix3D object. */ public appendTranslation(x: number, y: number, z: number): void { const raw: Float32Array = this._rawData; const m41 = raw[3], m42 = raw[7], m43 = raw[11], m44 = raw[15]; raw[0 ] += x * m41; raw[1 ] += y * m41; raw[2 ] += z * m41; raw[4 ] += x * m42; raw[5 ] += y * m42; raw[6 ] += z * m42; raw[8 ] += x * m43; raw[9 ] += y * m43; raw[10] += z * m43; raw[12] += x * m44; raw[13] += y * m44; raw[14] += z * m44; } /** * Returns a new Matrix3D object that is an exact copy of the current Matrix3D object. */ public clone(): Matrix3D { const matrix3D: Matrix3D = new Matrix3D(); matrix3D.copyFrom(this); return matrix3D; } /** * Copies a Vector3D object into specific column of the calling Matrix3D object. */ public copyColumnFrom(column: number, vector3D: Vector3D): void { if (column < 0 || column > 3) throw new ArgumentError('ArgumentError, Column ' + column + ' out of bounds [0, ..., 3]'); const targetData: Float32Array = this._rawData; const vectorData: Float32Array = vector3D._rawData; column *= 4; targetData[column] = vectorData[0]; targetData[column + 1] = vectorData[1]; targetData[column + 2] = vectorData[2]; targetData[column + 3] = vectorData[3]; } /** * Copies specific column of the calling Matrix3D object into the Vector3D object. */ public copyColumnTo(column: number, vector3D: Vector3D, negate: boolean = false): void { if (column < 0 || column > 3) throw new ArgumentError('ArgumentError, Column ' + column + ' out of bounds [0, ..., 3]'); column *= 4; const sourceData: Float32Array = this._rawData; const vectorData: Float32Array = vector3D._rawData; if (negate) { vectorData[0] = -sourceData[column]; vectorData[1] = -sourceData[column + 1]; vectorData[2] = -sourceData[column + 2]; vectorData[3] = -sourceData[column + 3]; } else { vectorData[0] = sourceData[column]; vectorData[1] = sourceData[column + 1]; vectorData[2] = sourceData[column + 2]; vectorData[3] = sourceData[column + 3]; } } /** * Copies all of the matrix data from the source Matrix3D object into the calling Matrix3D object. */ public copyFrom(source: Matrix3D, transpose: boolean = false): void { const sourceData = source._rawData, targetData = this._rawData; if (transpose) { targetData[0] = sourceData[0]; targetData[1] = sourceData[4]; targetData[2] = sourceData[8]; targetData[3] = sourceData[12]; targetData[4] = sourceData[1]; targetData[5] = sourceData[5]; targetData[6] = sourceData[9]; targetData[7] = sourceData[13]; targetData[8] = sourceData[2]; targetData[9] = sourceData[6]; targetData[10] = sourceData[10]; targetData[11] = sourceData[14]; targetData[12] = sourceData[3]; targetData[13] = sourceData[7]; targetData[14] = sourceData[11]; targetData[15] = sourceData[15]; } else { targetData.set(sourceData); } } /** * Copies this Matrix3D object into a destination Matrix3D object. */ public copyTo(target: Matrix3D, transpose: boolean = false): void { target.copyFrom(this, transpose); } public copyRawDataFrom(sourceData: Float32Array, offset: number = 0, transpose: boolean = false): void { const targetData = this._rawData; if (transpose) { targetData[0] = sourceData[offset + 0]; targetData[1] = sourceData[offset + 4]; targetData[2] = sourceData[offset + 8]; targetData[3] = sourceData[offset + 12]; targetData[4] = sourceData[offset + 1]; targetData[5] = sourceData[offset + 5]; targetData[6] = sourceData[offset + 9]; targetData[7] = sourceData[offset + 13]; targetData[8] = sourceData[offset + 2]; targetData[9] = sourceData[offset + 6]; targetData[10] = sourceData[offset + 10]; targetData[11] = sourceData[offset + 14]; targetData[12] = sourceData[offset + 3]; targetData[13] = sourceData[offset + 7]; targetData[14] = sourceData[offset + 11]; targetData[15] = sourceData[offset + 15]; } else { targetData.set(sourceData.subarray(offset, offset + 16)); } } public copyRawDataTo(targetData: Float32Array, offset: number = 0, transpose: boolean = false): void { const sourceData = this._rawData; if (transpose) { targetData[offset] = sourceData[0]; targetData[offset + 1] = sourceData[4]; targetData[offset + 2] = sourceData[8]; targetData[offset + 3] = sourceData[12]; targetData[offset + 4] = sourceData[1]; targetData[offset + 5] = sourceData[5]; targetData[offset + 6] = sourceData[9]; targetData[offset + 7] = sourceData[13]; targetData[offset + 8] = sourceData[2]; targetData[offset + 9] = sourceData[6]; targetData[offset + 10] = sourceData[10]; targetData[offset + 11] = sourceData[14]; targetData[offset + 12] = sourceData[3]; targetData[offset + 13] = sourceData[7]; targetData[offset + 14] = sourceData[11]; targetData[offset + 15] = sourceData[15]; } else { targetData.set(sourceData, offset); } } /** * Copies a Vector3D object into specific row of the calling Matrix3D object. */ public copyRowFrom(row: number, vector3D: Vector3D): void { if (row < 0 || row > 3) throw new ArgumentError('ArgumentError, Row ' + row + ' out of bounds [0, ..., 3]'); const targetData: Float32Array = this._rawData; const vectorData: Float32Array = vector3D._rawData; targetData[row] = vectorData[0]; targetData[row + 4] = vectorData[1]; targetData[row + 8] = vectorData[2]; targetData[row + 12] = vectorData[3]; } /** * Copies specific row of the calling Matrix3D object into the Vector3D object. */ public copyRowTo(row: number, vector3D: Vector3D, negate: boolean = false): void { if (row < 0 || row > 3) throw new ArgumentError('ArgumentError, Row ' + row + ' out of bounds [0, ..., 3]'); const sourceData: Float32Array = this._rawData; const vectorData: Float32Array = vector3D._rawData; if (negate) { vectorData[0] = -sourceData[row]; vectorData[1] = -sourceData[row + 4]; vectorData[2] = -sourceData[row + 8]; vectorData[3] = -sourceData[row + 12]; } else { vectorData[0] = sourceData[row]; vectorData[1] = sourceData[row + 4]; vectorData[2] = sourceData[row + 8]; vectorData[3] = sourceData[row + 12]; } } private static COL_X = new Vector3D(); private static COL_Y = new Vector3D(); private static COL_Z = new Vector3D(); /** * Returns the transformation matrix's translation, rotation, and scale * settings as a Vector of three Vector3D objects. */ public decompose(orientationStyle: string = 'eulerAngles'): Vector3D[] { if (this._components == null) this._components = [new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D()]; /// use TMP for avoid realocation const colX = Matrix3D.COL_X; colX.setTo(this._rawData[0], this._rawData[1], this._rawData[2]); const colY = Matrix3D.COL_Y; colY.setTo(this._rawData[4], this._rawData[5], this._rawData[6]); const colZ = Matrix3D.COL_Z; colZ.setTo(this._rawData[8], this._rawData[9], this._rawData[10]); const skew = this._components[3]; const scale = this._components[2]; //compute X scale factor and normalise colX scale.x = colX.length; if (scale.x) colX.scaleBy(1 / scale.x); //compute XY shear factor and make colY orthogonal to colX skew.x = colX.dotProduct(colY); Vector3D.combine(colY, colX, 1, -skew.x, colY); //compute Y scale factor and normalise colY scale.y = colY.length; if (scale.y) { colY.scaleBy(1 / scale.y); skew.x /= scale.y; } //compute XZ and YZ shears and make colZ orthogonal to colX and colY skew.y = colX.dotProduct(colZ); Vector3D.combine(colZ, colX, 1, -skew.y, colZ); skew.z = colY.dotProduct(colZ); Vector3D.combine(colZ, colY, 1, -skew.z, colZ); //compute Z scale and normalise colZ scale.z = colZ.length; if (scale.z) { colZ.scaleBy(1 / scale.z); skew.y /= scale.z; skew.z /= scale.z; } //at this point, the matrix (in cols) is orthonormal //check for a coordinate system flip. If the determinant is -1, negate the z scaling factor if (colX.dotProduct(colY.crossProduct(colZ)) < 0) { scale.z = -scale.z; colZ.x = -colZ.x; colZ.y = -colZ.y; colZ.z = -colZ.z; } const rot = this._components[1]; switch (orientationStyle) { case Orientation3D.AXIS_ANGLE: { rot.w = Math.acos((colX.x + colY.y + colZ.z - 1) / 2); const len: number = Math.sqrt( (colY.z - colZ.y) * (colY.z - colZ.y) + (colZ.x - colX.z) * (colZ.x - colX.z) + (colX.y - colY.x) * (colX.y - colY.x)); rot.x = len ? (colY.z - colZ.y) / len : 0; rot.y = len ? (colZ.x - colX.z) / len : 0; rot.z = len ? (colX.y - colY.x) / len : 0; break; } case Orientation3D.QUATERNION: { const tr = colX.x + colY.y + colZ.z; if (tr > 0) { rot.w = Math.sqrt(1 + tr) / 2; rot.x = (colY.z - colZ.y) / (4 * rot.w); rot.y = (colZ.x - colX.z) / (4 * rot.w); rot.z = (colX.y - colY.x) / (4 * rot.w); } else if ((colX.x > colY.y) && (colX.x > colZ.z)) { rot.x = Math.sqrt(1 + colX.x - colY.y - colZ.z) / 2; rot.w = (colY.z - colZ.y) / (4 * rot.x); rot.y = (colX.y + colY.x) / (4 * rot.x); rot.z = (colZ.x + colX.z) / (4 * rot.x); } else if (colY.y > colZ.z) { rot.y = Math.sqrt(1 + colY.y - colX.x - colZ.z) / 2; rot.x = (colX.y + colY.x) / (4 * rot.y); rot.w = (colZ.x - colX.z) / (4 * rot.y); rot.z = (colY.z + colZ.y) / (4 * rot.y); } else { rot.z = Math.sqrt(1 + colZ.z - colX.x - colY.y) / 2; rot.x = (colZ.x + colX.z) / (4 * rot.z); rot.y = (colY.z + colZ.y) / (4 * rot.z); rot.w = (colX.y - colY.x) / (4 * rot.z); } break; } case Orientation3D.EULER_ANGLES: rot.y = Math.asin(-colX.z) * MathConsts.RADIANS_TO_DEGREES; //var cos:number = Math.cos(rot.y); if (colX.z != 1 && colX.z != -1) { rot.x = Math.atan2(colY.z, colZ.z) * MathConsts.RADIANS_TO_DEGREES; rot.z = Math.atan2(colX.y, colX.x) * MathConsts.RADIANS_TO_DEGREES; } else { rot.z = 0; rot.x = Math.atan2(colY.x, colY.y) * MathConsts.RADIANS_TO_DEGREES; } break; } this._components[0].copyFrom(this.position); return this._components; } /** * Uses the transformation matrix without its translation elements to transform a Vector3D object from one space * coordinate to another. */ public deltaTransformVector(v: Vector3D, t: Vector3D = null): Vector3D { const x: number = v.x; const y: number = v.y; const z: number = v.z; if (!t) t = new Vector3D(); const raw: Float32Array = this._rawData; const rawT: Float32Array = t._rawData; rawT[0] = x * raw[0] + y * raw[4] + z * raw[8]; rawT[1] = x * raw[1] + y * raw[5] + z * raw[9]; rawT[2] = x * raw[2] + y * raw[6] + z * raw[10]; rawT[3] = x * raw[3] + y * raw[7] + z * raw[11]; return t; } public deltaTransformVectors(vin: Array, vout: Array): void { const raw: Float32Array = this._rawData; const a: number = raw[0]; const e: number = raw[1]; const i: number = raw[2]; const b: number = raw[4]; const f: number = raw[5]; const j: number = raw[6]; const c: number = raw[8]; const g: number = raw[9]; const k: number = raw[10]; let outIndex: number = 0; const length: number = vin.length; for (let index: number = 0; index < length; index += 3) { const x: number = vin[index]; const y: number = vin[index + 1]; const z: number = vin[index + 2]; vout[outIndex++] = a * x + b * y + c * z; vout[outIndex++] = e * x + f * y + g * z; vout[outIndex++] = i * x + j * y + k * z; } } /** * Converts the current matrix to an identity or unit matrix. */ public identity(): void { this._rawData.set(Matrix3D._identityData); } /** * Inverts the current matrix. */ public invert(): boolean { let d = this.determinant; const invertable: boolean = Math.abs(d) > 0.00000000001; const raw: Float32Array = this._rawData; if (invertable) { d = 1 / d; const m11: number = raw[0]; const m12: number = raw[1]; const m13: number = raw[2]; const m14: number = raw[3]; const m21: number = raw[4]; const m22: number = raw[5]; const m23: number = raw[6]; const m24: number = raw[7]; const m31: number = raw[8]; const m32: number = raw[9]; const m33: number = raw[10]; const m34: number = raw[11]; const m41: number = raw[12]; const m42: number = raw[13]; const m43: number = raw[14]; const m44: number = raw[15]; /* eslint-disable */ raw[0] = d * (m22 * (m33 * m44 - m43 * m34) - m32 * (m23 * m44 - m43 * m24) + m42 * (m23 * m34 - m33 * m24)); raw[1] = -d * (m12 * (m33 * m44 - m43 * m34) - m32 * (m13 * m44 - m43 * m14) + m42 * (m13 * m34 - m33 * m14)); raw[2] = d * (m12 * (m23 * m44 - m43 * m24) - m22 * (m13 * m44 - m43 * m14) + m42 * (m13 * m24 - m23 * m14)); raw[3] = -d * (m12 * (m23 * m34 - m33 * m24) - m22 * (m13 * m34 - m33 * m14) + m32 * (m13 * m24 - m23 * m14)); raw[4] = -d * (m21 * (m33 * m44 - m43 * m34) - m31 * (m23 * m44 - m43 * m24) + m41 * (m23 * m34 - m33 * m24)); raw[5] = d * (m11 * (m33 * m44 - m43 * m34) - m31 * (m13 * m44 - m43 * m14) + m41 * (m13 * m34 - m33 * m14)); raw[6] = -d * (m11 * (m23 * m44 - m43 * m24) - m21 * (m13 * m44 - m43 * m14) + m41 * (m13 * m24 - m23 * m14)); raw[7] = d * (m11 * (m23 * m34 - m33 * m24) - m21 * (m13 * m34 - m33 * m14) + m31 * (m13 * m24 - m23 * m14)); raw[8] = d * (m21 * (m32 * m44 - m42 * m34) - m31 * (m22 * m44 - m42 * m24) + m41 * (m22 * m34 - m32 * m24)); raw[9] = -d * (m11 * (m32 * m44 - m42 * m34) - m31 * (m12 * m44 - m42 * m14) + m41 * (m12 * m34 - m32 * m14)); raw[10] = d * (m11 * (m22 * m44 - m42 * m24) - m21 * (m12 * m44 - m42 * m14) + m41 * (m12 * m24 - m22 * m14)); raw[11] = -d * (m11 * (m22 * m34 - m32 * m24) - m21 * (m12 * m34 - m32 * m14) + m31 * (m12 * m24 - m22 * m14)); raw[12] = -d * (m21 * (m32 * m43 - m42 * m33) - m31 * (m22 * m43 - m42 * m23) + m41 * (m22 * m33 - m32 * m23)); raw[13] = d * (m11 * (m32 * m43 - m42 * m33) - m31 * (m12 * m43 - m42 * m13) + m41 * (m12 * m33 - m32 * m13)); raw[14] = -d * (m11 * (m22 * m43 - m42 * m23) - m21 * (m12 * m43 - m42 * m13) + m41 * (m12 * m23 - m22 * m13)); raw[15] = d * (m11 * (m22 * m33 - m32 * m23) - m21 * (m12 * m33 - m32 * m13) + m31 * (m12 * m23 - m22 * m13)); /* eslint-enable */ } return invertable; } public isIdentity(): boolean { const raw: Float32Array = this._rawData; if (raw[0] == 1 && raw[1] == 0 && raw[2] == 0 && raw[3] == 0 && raw[4] == 0 && raw[5] == 1 && raw[6] == 0 && raw[7] == 0 && raw[8] == 0 && raw[9] == 0 && raw[10] == 1 && raw[11] == 0 && raw[12] == 0 && raw[13] == 0 && raw[14] == 0 && raw[15] == 1) return true; return false; } /** * Prepends a matrix by multiplying the current Matrix3D object by another Matrix3D object. */ public prepend(rhs: Matrix3D): void { const raw: Float32Array = this._rawData; const rawRhs: Float32Array = rhs._rawData; const m111: number = rawRhs[0]; const m112: number = rawRhs[1]; const m113: number = rawRhs[2]; const m114: number = rawRhs[3]; const m121: number = rawRhs[4]; const m122: number = rawRhs[5]; const m123: number = rawRhs[6]; const m124: number = rawRhs[7]; const m131: number = rawRhs[8]; const m132: number = rawRhs[9]; const m133: number = rawRhs[10]; const m134: number = rawRhs[11]; const m141: number = rawRhs[12]; const m142: number = rawRhs[13]; const m143: number = rawRhs[14]; const m144: number = rawRhs[15]; const m211: number = raw[0]; const m212: number = raw[1]; const m213: number = raw[2]; const m214: number = raw[3]; const m221: number = raw[4]; const m222: number = raw[5]; const m223: number = raw[6]; const m224: number = raw[7]; const m231: number = raw[8]; const m232: number = raw[9]; const m233: number = raw[10]; const m234: number = raw[11]; const m241: number = raw[12]; const m242: number = raw[13]; const m243: number = raw[14]; const m244: number = raw[15]; raw[0] = m111 * m211 + m112 * m221 + m113 * m231 + m114 * m241; raw[1] = m111 * m212 + m112 * m222 + m113 * m232 + m114 * m242; raw[2] = m111 * m213 + m112 * m223 + m113 * m233 + m114 * m243; raw[3] = m111 * m214 + m112 * m224 + m113 * m234 + m114 * m244; raw[4] = m121 * m211 + m122 * m221 + m123 * m231 + m124 * m241; raw[5] = m121 * m212 + m122 * m222 + m123 * m232 + m124 * m242; raw[6] = m121 * m213 + m122 * m223 + m123 * m233 + m124 * m243; raw[7] = m121 * m214 + m122 * m224 + m123 * m234 + m124 * m244; raw[8] = m131 * m211 + m132 * m221 + m133 * m231 + m134 * m241; raw[9] = m131 * m212 + m132 * m222 + m133 * m232 + m134 * m242; raw[10] = m131 * m213 + m132 * m223 + m133 * m233 + m134 * m243; raw[11] = m131 * m214 + m132 * m224 + m133 * m234 + m134 * m244; raw[12] = m141 * m211 + m142 * m221 + m143 * m231 + m144 * m241; raw[13] = m141 * m212 + m142 * m222 + m143 * m232 + m144 * m242; raw[14] = m141 * m213 + m142 * m223 + m143 * m233 + m144 * m243; raw[15] = m141 * m214 + m142 * m224 + m143 * m234 + m144 * m244; } /** * Prepends an incremental rotation to a Matrix3D object. */ public prependRotation(degrees: number, axis: Vector3D) //, pivot:Vector3D = null ):void { // eslint-disable-line this.prepend(Matrix3D.getAxisRotationMatrix(axis.x, axis.y, axis.z, degrees, Matrix3D._tempMatrix)); } /** * Prepends an incremental scale change along the x, y, and z axes to a Matrix3D object. */ public prependScale(xScale: number, yScale: number, zScale: number): void { if (xScale == 1 && yScale == 1 && zScale == 1) return; const rawData: Float32Array = Matrix3D._tempMatrix._rawData; rawData[0] = xScale; rawData[1] = 0; rawData[2] = 0; rawData[3] = 0; rawData[4] = 0; rawData[5] = yScale; rawData[6] = 0; rawData[7] = 0; rawData[8] = 0; rawData[9] = 0; rawData[10] = zScale; rawData[11] = 0; rawData[12] = 0; rawData[13] = 0; rawData[14] = 0; rawData[15] = 1; this.prepend(Matrix3D._tempMatrix); } /** * Prepends an incremental translation, a repositioning along the x, y, and z axes, to a Matrix3D object. */ public prependTranslation(x: number, y: number, z: number): void { const rawData: Float32Array = Matrix3D._tempMatrix._rawData; rawData[0] = 1; rawData[1] = 0; rawData[2] = 0; rawData[3] = 0; rawData[4] = 0; rawData[5] = 1; rawData[6] = 0; rawData[7] = 0; rawData[8] = 0; rawData[9] = 0; rawData[10] = 1; rawData[11] = 0; rawData[12] = x; rawData[13] = y; rawData[14] = z; rawData[15] = 1; this.prepend(Matrix3D._tempMatrix); } // TODO orientationStyle /** * Sets the transformation matrix's translation, rotation, and scale settings. */ public recompose(components: Vector3D[]): void { //reset matrix ready for recompose this.identity(); const skew: Vector3D = components[3]; if (skew && (skew.x != 0 || skew.y != 0 || skew.z != 0)) this.appendSkew(skew.x, skew.y, skew.z); const scale: Vector3D = components[2]; if (scale && (scale.x != 1 || scale.y != 1 || scale.z != 1)) this.appendScale(scale.x, scale.y, scale.z); let sin: number; let cos: number; const rawData: Float32Array = Matrix3D._tempMatrix._rawData; rawData[12] = 0; rawData[13] = 0; rawData[14] = 0; rawData[15] = 0; const rotation: Vector3D = components[1]; if (rotation) { let angle: number = -rotation.x * MathConsts.DEGREES_TO_RADIANS; if (angle != 0) { sin = Math.sin(angle); cos = Math.cos(angle); rawData[0] = 1; rawData[1] = 0; rawData[2] = 0; rawData[3] = 0; rawData[4] = 0; rawData[5] = cos; rawData[6] = -sin; rawData[7] = 0; rawData[8] = 0; rawData[9] = sin; rawData[10] = cos; rawData[11] = 0; this.append(Matrix3D._tempMatrix); } angle = -rotation.y * MathConsts.DEGREES_TO_RADIANS; if (angle != 0) { sin = Math.sin(angle); cos = Math.cos(angle); rawData[0] = cos; rawData[1] = 0; rawData[2] = sin; rawData[3] = 0; rawData[4] = 0; rawData[5] = 1; rawData[6] = 0; rawData[7] = 0; rawData[8] = -sin; rawData[9] = 0; rawData[10] = cos; rawData[11] = 0; this.append(Matrix3D._tempMatrix); } angle = -rotation.z * MathConsts.DEGREES_TO_RADIANS; if (angle != 0) { sin = Math.sin(angle); cos = Math.cos(angle); rawData[0] = cos; rawData[1] = -sin; rawData[2] = 0; rawData[3] = 0; rawData[4] = sin; rawData[5] = cos; rawData[6] = 0; rawData[7] = 0; rawData[8] = 0; rawData[9] = 0; rawData[10] = 1; rawData[11] = 0; this.append(Matrix3D._tempMatrix); } } const pos: Vector3D = components[0]; if (pos) { this._rawData[12] = pos.x; this._rawData[13] = pos.y; this._rawData[14] = pos.z; this._rawData[15] = 1; } } public reflect(plane: Plane3D): void { const a: number = plane.a, b: number = plane.b, c: number = plane.c, d: number = plane.d; const ab2: number = -2 * a * b; const ac2: number = -2 * a * c; const bc2: number = -2 * b * c; // reflection matrix const rawData: Float32Array = this._rawData; rawData[0] = 1 - 2 * a * a; rawData[4] = ab2; rawData[8] = ac2; rawData[12] = -2 * a * d; rawData[1] = ab2; rawData[5] = 1 - 2 * b * b; rawData[9] = bc2; rawData[13] = -2 * b * d; rawData[2] = ac2; rawData[6] = bc2; rawData[10] = 1 - 2 * c * c; rawData[14] = -2 * c * d; rawData[3] = 0; rawData[7] = 0; rawData[11] = 0; rawData[15] = 1; } public transformBox(box: Box, target: Box = null): Box { if (box == null) throw new ArgumentError('ArgumentError, box cannot be null'); if (!target) target = new Box(); const hx: number = box.width / 2; const hy: number = box.height / 2; const hz: number = box.depth / 2; const cx: number = box.x + hx; const cy: number = box.y + hy; const cz: number = box.z + hz; const m11: number = this._rawData[0], m12: number = this._rawData[4], m13: number = this._rawData[8], m14: number = this._rawData[12]; const m21: number = this._rawData[1], m22: number = this._rawData[5], m23: number = this._rawData[9], m24: number = this._rawData[13]; const m31: number = this._rawData[2], m32: number = this._rawData[6], m33: number = this._rawData[10], m34: number = this._rawData[14]; const centerX: number = cx * m11 + cy * m12 + cz * m13 + m14; const centerY: number = cx * m21 + cy * m22 + cz * m23 + m24; const centerZ: number = cx * m31 + cy * m32 + cz * m33 + m34; const halfExtentsX: number = Math.max( Math.abs(hx * m11 + hy * m12 + hz * m13), Math.abs(-hx * m11 + hy * m12 + hz * m13), Math.abs(hx * m11 - hy * m12 + hz * m13), Math.abs(hx * m11 + hy * m12 - hz * m13)); const halfExtentsY: number = Math.max( Math.abs(hx * m21 + hy * m22 + hz * m23), Math.abs(-hx * m21 + hy * m22 + hz * m23), Math.abs(hx * m21 - hy * m22 + hz * m23), Math.abs(hx * m21 + hy * m22 - hz * m23)); const halfExtentsZ: number = Math.max( Math.abs(hx * m31 + hy * m32 + hz * m33), Math.abs(-hx * m31 + hy * m32 + hz * m33), Math.abs(hx * m31 - hy * m32 + hz * m33), Math.abs(hx * m31 + hy * m32 - hz * m33)); target.width = halfExtentsX * 2; target.height = halfExtentsY * 2; target.depth = halfExtentsZ * 2; target.x = centerX - halfExtentsX; target.y = centerY - halfExtentsY; target.z = centerZ - halfExtentsZ; return target; } public transformSphere(sphere: Sphere, target: Sphere = null): Sphere { //TODO: use a better solution than this if (sphere == null) throw new ArgumentError('ArgumentError, sphere cannot be null'); const box: Box = new Box( sphere.x - sphere.radius, // x sphere.y - sphere.radius, // y sphere.z - sphere.radius, // z sphere.radius * 2, // w sphere.radius * 2, // h sphere.radius * 2); // d this.transformBox(box, box); if (!target) target = new Sphere(); target.x = box.x + box.width / 2; target.y = box.y + box.height / 2; target.z = box.z + box.depth / 2; target.radius = Math.max(box.width, box.height, box.depth); return target; } public transformVector(vector: Vector3D, target: Vector3D = null, ignoreW: boolean = false): Vector3D { if (vector == null) throw new ArgumentError('ArgumentError, vector cannot be null'); const x: number = vector.x; const y: number = vector.y; const z: number = vector.z; const w: number = ignoreW ? 1 : vector.w; if (!target) target = new Vector3D(); const raw: Float32Array = this._rawData; const rawTarget: Float32Array = target._rawData; rawTarget[0] = x * raw[0] + y * raw[4] + z * raw[8] + w * raw[12]; rawTarget[1] = x * raw[1] + y * raw[5] + z * raw[9] + w * raw[13]; rawTarget[2] = x * raw[2] + y * raw[6] + z * raw[10] + w * raw[14]; rawTarget[3] = x * raw[3] + y * raw[7] + z * raw[11] + w * raw[15]; return target; } /** * Uses the transformation matrix to transform a Vector of Numbers from one coordinate space to another. */ public transformVectors(vin: number[], vout: number[]): void { let i: number = 0; let x: number = 0, y: number = 0, z: number = 0; const raw: Float32Array = this._rawData; while (i + 3 <= vin.length) { x = vin[i]; y = vin[i + 1]; z = vin[i + 2]; vout[i] = x * raw[0] + y * raw[4] + z * raw[8] + raw[12]; vout[i + 1] = x * raw[1] + y * raw[5] + z * raw[9] + raw[13]; vout[i + 2] = x * raw[2] + y * raw[6] + z * raw[10] + raw[14]; i += 3; } } /** * Converts the current Matrix3D object to a matrix where the rows and columns are swapped. */ public transpose(): void { const raw: Float32Array = this._rawData; const rawTemp: Float32Array = Matrix3D._tempMatrix._rawData; this.copyRawDataTo(rawTemp, 0, true); raw[1] = rawTemp[1]; raw[2] = rawTemp[2]; raw[3] = rawTemp[3]; raw[4] = rawTemp[4]; raw[6] = rawTemp[6]; raw[7] = rawTemp[7]; raw[8] = rawTemp[8]; raw[9] = rawTemp[9]; raw[11] = rawTemp[11]; raw[12] = rawTemp[12]; raw[13] = rawTemp[13]; raw[14] = rawTemp[14]; } public toFixed(decimalPlace: number): string { const magnitude: number = Math.pow(10, decimalPlace); return 'matrix3d(' + Math.round(this._rawData[0] * magnitude) / magnitude + ',' + Math.round(this._rawData[1] * magnitude) / magnitude + ',' + Math.round(this._rawData[2] * magnitude) / magnitude + ',' + Math.round(this._rawData[3] * magnitude) / magnitude + ',' + Math.round(this._rawData[4] * magnitude) / magnitude + ',' + Math.round(this._rawData[5] * magnitude) / magnitude + ',' + Math.round(this._rawData[6] * magnitude) / magnitude + ',' + Math.round(this._rawData[7] * magnitude) / magnitude + ',' + Math.round(this._rawData[8] * magnitude) / magnitude + ',' + Math.round(this._rawData[9] * magnitude) / magnitude + ',' + Math.round(this._rawData[10] * magnitude) / magnitude + ',' + Math.round(this._rawData[11] * magnitude) / magnitude + ',' + Math.round(this._rawData[12] * magnitude) / magnitude + ',' + Math.round(this._rawData[13] * magnitude) / magnitude + ',' + Math.round(this._rawData[14] * magnitude) / magnitude + ',' + Math.round(this._rawData[15] * magnitude) / magnitude + ')'; } public toString(): string { return 'matrix3d(' + Math.round(this._rawData[0] * 1000) / 1000 + ',' + Math.round(this._rawData[1] * 1000) / 1000 + ',' + Math.round(this._rawData[2] * 1000) / 1000 + ',' + Math.round(this._rawData[3] * 1000) / 1000 + ',' + Math.round(this._rawData[4] * 1000) / 1000 + ',' + Math.round(this._rawData[5] * 1000) / 1000 + ',' + Math.round(this._rawData[6] * 1000) / 1000 + ',' + Math.round(this._rawData[7] * 1000) / 1000 + ',' + Math.round(this._rawData[8] * 1000) / 1000 + ',' + Math.round(this._rawData[9] * 1000) / 1000 + ',' + Math.round(this._rawData[10] * 1000) / 1000 + ',' + Math.round(this._rawData[11] * 1000) / 1000 + ',' + Math.round(this._rawData[12] * 1000) / 1000 + ',' + Math.round(this._rawData[13] * 1000) / 1000 + ',' + Math.round(this._rawData[14] * 1000) / 1000 + ',' + Math.round(this._rawData[15] * 1000) / 1000 + ')'; } }