Source: matrix4.js

/**
 * A 4x4 matrix.
 * Its elements are stored in column-major order.
 */
class Matrix4 {
  /**
   * Creates a new identity Matrix4.
   */
  constructor() {
    this.elements = [
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1,
    ];
  }

  /**
   * Creates a new Matrix4 from the values of each element.
   * @param {Number} a00 The element at row 0 and column 0.
   * @param {Number} a01 The element at row 0 and column 1.
   * @param {Number} a02 The element at row 0 and column 2.
   * @param {Number} a03 The element at row 0 and column 3.
   * @param {Number} a10 The element at row 1 and column 0.
   * @param {Number} a11 The element at row 1 and column 1.
   * @param {Number} a12 The element at row 1 and column 2.
   * @param {Number} a13 The element at row 1 and column 3.
   * @param {Number} a20 The element at row 2 and column 0.
   * @param {Number} a21 The element at row 2 and column 1.
   * @param {Number} a22 The element at row 2 and column 2.
   * @param {Number} a23 The element at row 2 and column 3.
   * @param {Number} a30 The element at row 3 and column 0.
   * @param {Number} a31 The element at row 3 and column 1.
   * @param {Number} a32 The element at row 3 and column 2.
   * @param {Number} a33 The element at row 3 and column 3.
   * @returns {Matrix4} The created matrix.
   */
  static fromValues(
    a00, a01, a02, a03,
    a10, a11, a12, a13,
    a20, a21, a22, a23,
    a30, a31, a32, a33,
  ) {
    const matrix = new Matrix4();
    const t = matrix.elements;

    t[0] = a00; t[4] = a01; t[8] = a02; t[12] = a03;
    t[1] = a10; t[5] = a11; t[9] = a12; t[13] = a13;
    t[2] = a20; t[6] = a21; t[10] = a22; t[14] = a23;
    t[3] = a30; t[7] = a31; t[11] = a32; t[15] = a33;

    return matrix;
  }

  /**
   * Compares if the elements of the matrix are equal to the elements of another matrix.
   * @param {Matrix4} matrix The matrix to compare the components to.
   * @returns {Boolean} A value indicating whether all the elements are equal.
   */
  equals(matrix) {
    const m = matrix.elements;
    const t = this.elements;

    return (
      m[0] === t[0]
      && m[1] === t[1]
      && m[2] === t[2]
      && m[3] === t[3]
      && m[4] === t[4]
      && m[5] === t[5]
      && m[6] === t[6]
      && m[7] === t[7]
      && m[8] === t[8]
      && m[9] === t[9]
      && m[10] === t[10]
      && m[11] === t[11]
      && m[12] === t[12]
      && m[13] === t[13]
      && m[14] === t[14]
      && m[15] === t[15]
    );
  }

  /**
   * Sets the elements of the matrix.
   * @param {Number} a00 The element at row 0 and column 0.
   * @param {Number} a01 The element at row 0 and column 1.
   * @param {Number} a02 The element at row 0 and column 2.
   * @param {Number} a03 The element at row 0 and column 3.
   * @param {Number} a10 The element at row 1 and column 0.
   * @param {Number} a11 The element at row 1 and column 1.
   * @param {Number} a12 The element at row 1 and column 2.
   * @param {Number} a13 The element at row 1 and column 3.
   * @param {Number} a20 The element at row 2 and column 0.
   * @param {Number} a21 The element at row 2 and column 1.
   * @param {Number} a22 The element at row 2 and column 2.
   * @param {Number} a23 The element at row 2 and column 3.
   * @param {Number} a30 The element at row 3 and column 0.
   * @param {Number} a31 The element at row 3 and column 1.
   * @param {Number} a32 The element at row 3 and column 2.
   * @param {Number} a33 The element at row 3 and column 3.
   * @returns {Matrix4} The matrix.
   */
  set(
    a00, a01, a02, a03,
    a10, a11, a12, a13,
    a20, a21, a22, a23,
    a30, a31, a32, a33,
  ) {
    const t = this.elements;

    t[0] = a00; t[4] = a01; t[8] = a02; t[12] = a03;
    t[1] = a10; t[5] = a11; t[9] = a12; t[13] = a13;
    t[2] = a20; t[6] = a21; t[10] = a22; t[14] = a23;
    t[3] = a30; t[7] = a31; t[11] = a32; t[15] = a33;

    return this;
  }

  /**
   * Sets the elements of the matrix to the identity.
   * @returns {Matrix4} The matrix.
   */
  setIdentity() {
    return this.set(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1,
    );
  }

  /**
   * Copies the elements from another matrix.
   * @param {Matrix4} matrix The matrix to copy the elements from.
   * @returns {Matrix4} The matrix.
   */
  copy(matrix) {
    const t = this.elements;

    [
      t[0], t[1], t[2], t[3],
      t[4], t[5], t[6], t[7],
      t[8], t[9], t[10], t[11],
      t[12], t[13], t[14], t[15],
    ] = matrix.elements;

    return this;
  }

  /**
   * Creates a clone of the matrix.
   * @returns {Matrix4} The cloned matrix.
   */
  clone() {
    const t = this.elements;

    return Matrix4.fromValues(
      t[0], t[4], t[8], t[12],
      t[1], t[5], t[9], t[13],
      t[2], t[6], t[10], t[14],
      t[3], t[7], t[11], t[15],
    );
  }

  /**
   * Calculates the determinant of the matrix.
   * @returns {Number} The determinant of the matrix.
   */
  determinant() {
    const t = this.elements;

    const a00 = t[0], a01 = t[1], a02 = t[2], a03 = t[3];
    const a10 = t[4], a11 = t[5], a12 = t[6], a13 = t[7];
    const a20 = t[8], a21 = t[9], a22 = t[10], a23 = t[11];
    const a30 = t[12], a31 = t[13], a32 = t[14], a33 = t[15];

    const b00 = a00 * a11 - a01 * a10;
    const b01 = a00 * a12 - a02 * a10;
    const b02 = a00 * a13 - a03 * a10;
    const b03 = a01 * a12 - a02 * a11;
    const b04 = a01 * a13 - a03 * a11;
    const b05 = a02 * a13 - a03 * a12;
    const b06 = a20 * a31 - a21 * a30;
    const b07 = a20 * a32 - a22 * a30;
    const b08 = a20 * a33 - a23 * a30;
    const b09 = a21 * a32 - a22 * a31;
    const b10 = a21 * a33 - a23 * a31;
    const b11 = a22 * a33 - a23 * a32;

    return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
  }

  /**
   * Transposes the matrix.
   * @returns {Matrix4} The matrix.
   */
  transpose() {
    return this.set(...this.elements);
  }

  /**
   * Inverts the matrix.
   * @returns {Matrix4} The matrix.
   */
  invert() {
    const t = this.elements;

    const a00 = t[0], a01 = t[4], a02 = t[8], a03 = t[12];
    const a10 = t[1], a11 = t[5], a12 = t[9], a13 = t[13];
    const a20 = t[2], a21 = t[6], a22 = t[10], a23 = t[14];
    const a30 = t[3], a31 = t[7], a32 = t[11], a33 = t[15];

    const b00 = a00 * a11 - a01 * a10;
    const b01 = a00 * a12 - a02 * a10;
    const b02 = a00 * a13 - a03 * a10;
    const b03 = a01 * a12 - a02 * a11;
    const b04 = a01 * a13 - a03 * a11;
    const b05 = a02 * a13 - a03 * a12;
    const b06 = a20 * a31 - a21 * a30;
    const b07 = a20 * a32 - a22 * a30;
    const b08 = a20 * a33 - a23 * a30;
    const b09 = a21 * a32 - a22 * a31;
    const b10 = a21 * a33 - a23 * a31;
    const b11 = a22 * a33 - a23 * a32;

    let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;

    if (det !== 0.0) {
      det = 1.0 / det;

      t[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
      t[4] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
      t[8] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
      t[12] = (a22 * b04 - a21 * b05 - a23 * b03) * det;

      t[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
      t[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
      t[9] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
      t[13] = (a20 * b05 - a22 * b02 + a23 * b01) * det;

      t[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
      t[6] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
      t[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
      t[14] = (a21 * b02 - a20 * b04 - a23 * b00) * det;

      t[3] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
      t[7] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
      t[11] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
      t[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
    }

    return this;
  }

  /**
   * Post-multiplies another matrix, and sets this matrix to the result.
   * @param {Matrix4} a The matrix to post-multiply with.
   * @returns {Matrix4} The matrix.
   */
  multiply(matrix) {
    const m = matrix.elements;
    const t = this.elements;

    const a00 = t[0], a01 = t[4], a02 = t[8], a03 = t[12];
    const a10 = t[1], a11 = t[5], a12 = t[9], a13 = t[13];
    const a20 = t[2], a21 = t[6], a22 = t[10], a23 = t[14];
    const a30 = t[3], a31 = t[7], a32 = t[11], a33 = t[15];

    const b00 = m[0], b01 = m[4], b02 = m[8], b03 = m[12];
    const b10 = m[1], b11 = m[5], b12 = m[9], b13 = m[13];
    const b20 = m[2], b21 = m[6], b22 = m[10], b23 = m[14];
    const b30 = m[3], b31 = m[7], b32 = m[11], b33 = m[15];

    t[0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30;
    t[4] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31;
    t[8] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32;
    t[12] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33;

    t[1] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30;
    t[5] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31;
    t[9] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32;
    t[13] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33;

    t[2] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30;
    t[6] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31;
    t[10] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32;
    t[14] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33;

    t[3] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30;
    t[7] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31;
    t[11] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32;
    t[15] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33;

    return this;
  }

  /**
   * Pre-multiplies another matrix, and sets this matrix to the result.
   * @param {Matrix4} a The matrix to pre-multiply with.
   * @returns {Matrix4} The matrix.
   */
  premultiply(matrix) {
    const m = matrix.elements;
    const t = this.elements;

    const a00 = m[0], a01 = m[4], a02 = m[8], a03 = m[12];
    const a10 = m[1], a11 = m[5], a12 = m[9], a13 = m[13];
    const a20 = m[2], a21 = m[6], a22 = m[10], a23 = m[14];
    const a30 = m[3], a31 = m[7], a32 = m[11], a33 = m[15];

    const b00 = t[0], b01 = t[4], b02 = t[8], b03 = t[12];
    const b10 = t[1], b11 = t[5], b12 = t[9], b13 = t[13];
    const b20 = t[2], b21 = t[6], b22 = t[10], b23 = t[14];
    const b30 = t[3], b31 = t[7], b32 = t[11], b33 = t[15];

    t[0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30;
    t[4] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31;
    t[8] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32;
    t[12] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33;

    t[1] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30;
    t[5] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31;
    t[9] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32;
    t[13] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33;

    t[2] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30;
    t[6] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31;
    t[10] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32;
    t[14] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33;

    t[3] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30;
    t[7] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31;
    t[11] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32;
    t[15] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33;

    return this;
  }

  /**
   * Sets the matrix to the multiplication of two matrices.
   * @param {Matrix4} a The first matrix.
   * @param {Matrix4} b The second matrix.
   * @returns {Matrix4} The matrix.
   */
  multiplyMatrices(a, b) {
    const ae = a.elements;
    const be = b.elements;
    const t = this.elements;

    const a00 = ae[0], a01 = ae[4], a02 = ae[8], a03 = ae[12];
    const a10 = ae[1], a11 = ae[5], a12 = ae[9], a13 = ae[13];
    const a20 = ae[2], a21 = ae[6], a22 = ae[10], a23 = ae[14];
    const a30 = ae[3], a31 = ae[7], a32 = ae[11], a33 = ae[15];

    const b00 = be[0], b01 = be[4], b02 = be[8], b03 = be[12];
    const b10 = be[1], b11 = be[5], b12 = be[9], b13 = be[13];
    const b20 = be[2], b21 = be[6], b22 = be[10], b23 = be[14];
    const b30 = be[3], b31 = be[7], b32 = be[11], b33 = be[15];

    t[0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30;
    t[4] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31;
    t[8] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32;
    t[12] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33;

    t[1] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30;
    t[5] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31;
    t[9] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32;
    t[13] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33;

    t[2] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30;
    t[6] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31;
    t[10] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32;
    t[14] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33;

    t[3] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30;
    t[7] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31;
    t[11] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32;
    t[15] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33;

    return this;
  }

  /**
   * Sets the matrix to a rotation matrix around the x axis.
   * @param {Number} theta The angle of the rotation in radians.
   * @returns {Matrix4} The matrix.
   */
  makeRotationX(theta) {
    const cos = Math.cos(theta);
    const sin = Math.sin(theta);

    return this.set(
      1, 0, 0, 0,
      0, cos, -sin, 0,
      0, sin, cos, 0,
      0, 0, 0, 1,
    );
  }

  /**
   * Sets the matrix to a rotation matrix around the y axis.
   * @param {Number} theta The angle of the rotation in radians.
   * @returns {Matrix4} The matrix.
   */
  makeRotationY(theta) {
    const cos = Math.cos(theta);
    const sin = Math.sin(theta);

    return this.set(
      cos, 0, sin, 0,
      0, 1, 0, 0,
      -sin, 0, cos, 0,
      0, 0, 0, 1,
    );
  }

  /**
   * Sets the matrix to a rotation matrix around the z axis.
   * @param {Number} theta The angle of the rotation in radians.
   * @returns {Matrix4} The matrix.
   */
  makeRotationZ(theta) {
    const cos = Math.cos(theta);
    const sin = Math.sin(theta);

    return this.set(
      cos, -sin, 0, 0,
      sin, cos, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1,
    );
  }

  /**
   * Sets the matrix to a scale matrix.
   * @param {Vector3} scale The vector containing the scale values.
   * @returns {Matrix4} The matrix.
   */
  makeScale(scale) {
    return this.set(
      scale.x, 0, 0, 0,
      0, scale.y, 0, 0,
      0, 0, scale.z, 0,
      0, 0, 0, 1,
    );
  }

  /**
   * Sets the matrix to a scale matrix.
   * @param {Number} x The scale along the x axis.
   * @param {Number} y The scale along the y axis.
   * @param {Number} z The scale along the z axis.
   * @returns {Matrix4} The matrix.
   */
  makeScaleValues(x, y, z) {
    return this.set(
      x, 0, 0, 0,
      0, y, 0, 0,
      0, 0, z, 0,
      0, 0, 0, 1,
    );
  }

  /**
   * Sets the matrix to a translation matrix.
   * @param {Vector3} translation The vector containing the translation values.
   * @returns {Matrix4} The matrix.
   */
  makeTranslation(translation) {
    return this.set(
      1, 0, 0, translation.x,
      0, 1, 0, translation.y,
      0, 0, 1, translation.z,
      0, 0, 0, 1,
    );
  }

  /**
   * Sets the matrix to a translation matrix.
   * @param {Number} x The translation along the x axis.
   * @param {Number} y The translation along the y axis.
   * @param {Number} z The translation along the z axis.
   * @returns {Matrix4} The matrix.
   */
  makeTranslationValues(x, y, z) {
    return this.set(
      1, 0, 0, x,
      0, 1, 0, y,
      0, 0, 1, z,
      0, 0, 0, 1,
    );
  }

  /**
   * Sets the matrix to an orthographic projection matrix.
   * @param {Number} left The left clipping plane distance.
   * @param {Number} right The right clipping plane distance.
   * @param {Number} top The top clipping plane distance.
   * @param {Number} bottom The bottom clipping plane distance.
   * @param {Number} near The near clipping plane distance.
   * @param {Number} far The far clipping plane distance.
   * @returns {Matrix4} The matrix.
   */
  makeOrthographic(left, right, top, bottom, near, far) {
    const width = 1 / (right - left);
    const height = 1 / (top - bottom);
    const depth = 1 / (far - near);

    return this.set(
      2 * width, 0, 0, -(right + left) * width,
      0, 2 * height, 0, -(top + bottom) * height,
      0, 0, -2 * depth, -(far + near) * depth,
      0, 0, 0, 1,
    );
  }

  /**
   * Sets the matrix to a perspective projection matrix.
   * @param {Number} left The left clipping plane distance.
   * @param {Number} right The right clipping plane distance.
   * @param {Number} top The top clipping plane distance.
   * @param {Number} bottom The bottom clipping plane distance.
   * @param {Number} near The near clipping plane distance.
   * @param {Number} far The far clipping plane distance.
   * @returns {Matrix4} The matrix.
   */
  makePerspective(left, right, top, bottom, near, far) {
    const width = right - left;
    const height = top - bottom;
    const depth = far - near;

    const x = (2 * near) / width;
    const y = (2 * near) / height;

    const a = (right + left) / width;
    const b = (top + bottom) / height;
    const c = -(far + near) / depth;
    const d = -(2 * far * near) / depth;

    return this.set(
      x, 0, a, 0,
      0, y, b, 0,
      0, 0, c, d,
      0, 0, -1, 0,
    );
  }

  /**
   * Sets the matrix to a view matrix that looks at a target.
   * @param {Vector3} source The position to translate to.
   * @param {Vector3} target The target to look at.
   * @param {Vector3} up The up direction.
   * @returns {Vector3} The matrix.
   */
  lookAt(source, target, up) {
    let ix, iy, iz;
    let kx, ky, kz;
    let length;

    kx = source.x - target.x;
    ky = source.y - target.y;
    kz = source.z - target.z;

    length = Math.sqrt(kx * kx + ky * ky + kz * kz);

    kx /= length;
    ky /= length;
    kz /= length;

    ix = up.y * kz - up.z * ky;
    iy = up.z * kx - up.x * kz;
    iz = up.x * ky - up.y * kx;

    length = Math.sqrt(ix * ix + iy * iy + iz * iz);

    ix /= length;
    iy /= length;
    iz /= length;

    const jx = ky * iz - kz * iy;
    const jy = kz * ix - kx * iz;
    const jz = kx * iy - ky * ix;

    const tx = -ix * source.x - iy * source.y - iz * source.z;
    const ty = -jx * source.x - jy * source.y - jz * source.z;
    const tz = -kx * source.x - ky * source.y - kz * source.z;

    return this.set(
      ix, iy, iz, tx,
      jx, jy, jz, ty,
      kx, ky, kz, tz,
      0, 0, 0, 1,
    );
  }
}

/**
 * An identity Matrix4.
 */
Matrix4.identity = Matrix4.fromValues(
  1, 0, 0, 0,
  0, 1, 0, 0,
  0, 0, 1, 0,
  0, 0, 0, 1,
);

/**
 * A Matrix4 with all elements equal to zero.
 */
Matrix4.zero = Matrix4.fromValues(
  0, 0, 0, 0,
  0, 0, 0, 0,
  0, 0, 0, 0,
  0, 0, 0, 0,
);


export default Matrix4;