WebGL_math_math.ts

import { Quaternion } from "./Quaternion";
import { degToRad } from "./utils/degToRad";
import { clamp } from "./utils/clamp";
import { Sphere } from "../shapes";

var mRotation: Matrix4;
var mScale: Matrix4;
var x: Vector3;
var y: Vector3;
var z: Vector3;

/** @class 
 *  @subcategory  Math
 * */ 
export class Matrix4 {
  elements: Float32Array;
  constructor(n11: Array<number>);
  constructor(
    n11?: number,
    n12?: number,
    n13?: number,
    n14?: number,
    n21?: number,
    n22?: number,
    n23?: number,
    n24?: number,
    n31?: number,
    n32?: number,
    n33?: number,
    n34?: number,
    n41?: number,
    n42?: number,
    n43?: number,
    n44?: number
  );
  constructor(
    n11: Array<number> | number = 1,
    n12: number = 0,
    n13: number = 0,
    n14: number = 0,
    n21: number = 0,
    n22: number = 1,
    n23: number = 0,
    n24: number = 0,
    n31: number = 0,
    n32: number = 0,
    n33: number = 1,
    n34: number = 0,
    n41: number = 0,
    n42: number = 0,
    n43: number = 0,
    n44: number = 1
  ) {
    if (typeof n11 !== "undefined" && typeof n11 !== "number") {
      // passing list like initialization
      this.elements = new Float32Array(n11);
    } else {
      this.elements = new Float32Array(16);
      this.elements[0] = n11;
      this.elements[4] = n12;
      this.elements[8] = n13;
      this.elements[12] = n14;
      this.elements[1] = n21;
      this.elements[5] = n22;
      this.elements[9] = n23;
      this.elements[13] = n24;
      this.elements[2] = n31;
      this.elements[6] = n32;
      this.elements[10] = n33;
      this.elements[14] = n34;
      this.elements[3] = n41;
      this.elements[7] = n42;
      this.elements[11] = n43;
      this.elements[15] = n44;
    }
  }

  // eslint-disable-next-line no-unused-vars, class-methods-use-this
  makeScale(x: any, y: any, z: any) {
    throw new Error("Method not implemented.");
  }

  set(
    n11: number,
    n12: number,
    n13: number,
    n14: number,
    n21: number,
    n22: number,
    n23: number,
    n24: number,
    n31: number,
    n32: number,
    n33: number,
    n34: number,
    n41: number,
    n42: number,
    n43: number,
    n44: number
  ) {
    const te = this.elements;

    te[0] = n11;
    te[4] = n12;
    te[8] = n13;
    te[12] = n14;
    te[1] = n21;
    te[5] = n22;
    te[9] = n23;
    te[13] = n24;
    te[2] = n31;
    te[6] = n32;
    te[10] = n33;
    te[14] = n34;
    te[3] = n41;
    te[7] = n42;
    te[11] = n43;
    te[15] = n44;

    return this;
  }

  identity() {
    this.set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);

    return this;
  }

  copy(m: { elements: any }) {
    const me = m.elements;

    this.set(
      me[0],
      me[4],
      me[8],
      me[12],
      me[1],
      me[5],
      me[9],
      me[13],
      me[2],
      me[6],
      me[10],
      me[14],
      me[3],
      me[7],
      me[11],
      me[15]
    );

    return this;
  }

  matrix3FromTopLeft() {
    const te = this.elements;
    return new Matrix3(
      te[0],
      te[4],
      te[8],
      te[1],
      te[5],
      te[9],
      te[2],
      te[6],
      te[10]
    );
  }

  setRotationFromEuler(v: Vector3, order?: string) {
    const te = this.elements;

    const { x, y, z } = v;
    const a = Math.cos(x);
    const b = Math.sin(x);
    const c = Math.cos(y);
    const d = Math.sin(y);
    const e = Math.cos(z);
    const f = Math.sin(z);

    if (order === undefined || order === "XYZ") {
      const ae = a * e;
      const af = a * f;
      const be = b * e;
      const bf = b * f;

      te[0] = c * e;
      te[4] = -c * f;
      te[8] = d;

      te[1] = af + be * d;
      te[5] = ae - bf * d;
      te[9] = -b * c;

      te[2] = bf - ae * d;
      te[6] = be + af * d;
      te[10] = a * c;
    } else
      console.error(`Error with matrix4 setRotationFromEuler. Order: ${order}`);

    return this;
  }

  setRotationFromQuaternion(q: Quaternion) {
    const te = this.elements;

    const { x, y, z, w } = q;
    const x2 = x + x;
    const y2 = y + y;
    const z2 = z + z;
    const xx = x * x2;
    const xy = x * y2;
    const xz = x * z2;
    const yy = y * y2;
    const yz = y * z2;
    const zz = z * z2;
    const wx = w * x2;
    const wy = w * y2;
    const wz = w * z2;

    te[0] = 1 - (yy + zz);
    te[4] = xy - wz;
    te[8] = xz + wy;

    te[1] = xy + wz;
    te[5] = 1 - (xx + zz);
    te[9] = yz - wx;

    te[2] = xz - wy;
    te[6] = yz + wx;
    te[10] = 1 - (xx + yy);

    return this;
  }

  multiplyMatrices(a: { elements: any }, b: Matrix4) {
    const ae = a.elements;
    const be = b.elements;
    const te = this.elements;

    const a11 = ae[0];
    const a12 = ae[4];
    const a13 = ae[8];
    const a14 = ae[12];
    const a21 = ae[1];
    const a22 = ae[5];
    const a23 = ae[9];
    const a24 = ae[13];
    const a31 = ae[2];
    const a32 = ae[6];
    const a33 = ae[10];
    const a34 = ae[14];
    const a41 = ae[3];
    const a42 = ae[7];
    const a43 = ae[11];
    const a44 = ae[15];

    const b11 = be[0];
    const b12 = be[4];
    const b13 = be[8];
    const b14 = be[12];
    const b21 = be[1];
    const b22 = be[5];
    const b23 = be[9];
    const b24 = be[13];
    const b31 = be[2];
    const b32 = be[6];
    const b33 = be[10];
    const b34 = be[14];
    const b41 = be[3];
    const b42 = be[7];
    const b43 = be[11];
    const b44 = be[15];

    te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
    te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
    te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
    te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;

    te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
    te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
    te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
    te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;

    te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
    te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
    te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
    te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;

    te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
    te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
    te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
    te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;

    return this;
  }

  multiplyScalar(s: number) {
    const te = this.elements;

    te[0] *= s;
    te[4] *= s;
    te[8] *= s;
    te[12] *= s;
    te[1] *= s;
    te[5] *= s;
    te[9] *= s;
    te[13] *= s;
    te[2] *= s;
    te[6] *= s;
    te[10] *= s;
    te[14] *= s;
    te[3] *= s;
    te[7] *= s;
    te[11] *= s;
    te[15] *= s;

    return this;
  }

  makeTranslation(x: any, y: any, z: any) {
    this.set(1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1);

    return this;
  }

  // snap values close to integers to their integer value
  // useful and identifying identity matrices
  snap(digits: number) {
    if (!digits) digits = 4;
    const mult = 10 ** 4;
    const te = this.elements;
    for (let i = 0; i < 16; i++) {
      const rounded = Math.round(te[i]);
      if (rounded === Math.round(te[i] * mult) / mult) {
        te[i] = rounded;
      }
    }
    return this;
  }

  transpose() {
    const te = this.elements;
    let tmp: any;

    tmp = te[1];
    te[1] = te[4];
    te[4] = tmp;
    tmp = te[2];
    te[2] = te[8];
    te[8] = tmp;
    tmp = te[6];
    te[6] = te[9];
    te[9] = tmp;

    tmp = te[3];
    te[3] = te[12];
    te[12] = tmp;
    tmp = te[7];
    te[7] = te[13];
    te[13] = tmp;
    tmp = te[11];
    te[11] = te[14];
    te[14] = tmp;

    return this;
  }

  setPosition(v: Vector3) {
    const te = this.elements;

    te[12] = v.x;
    te[13] = v.y;
    te[14] = v.z;

    return this;
  }

  translate(v: Vector3) {
    const te = this.elements;

    te[12] += v.x;
    te[13] += v.y;
    te[14] += v.z;

    return this;
  }

  getInverse(m: Matrix4, throwOnInvertible?: boolean) {
    // based on
    // http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
    const te = this.elements;
    const me = m.elements;

    const n11 = me[0];
    const n12 = me[4];
    const n13 = me[8];
    const n14 = me[12];
    const n21 = me[1];
    const n22 = me[5];
    const n23 = me[9];
    const n24 = me[13];
    const n31 = me[2];
    const n32 = me[6];
    const n33 = me[10];
    const n34 = me[14];
    const n41 = me[3];
    const n42 = me[7];
    const n43 = me[11];
    const n44 = me[15];

    te[0] =
      n23 * n34 * n42 -
      n24 * n33 * n42 +
      n24 * n32 * n43 -
      n22 * n34 * n43 -
      n23 * n32 * n44 +
      n22 * n33 * n44;
    te[4] =
      n14 * n33 * n42 -
      n13 * n34 * n42 -
      n14 * n32 * n43 +
      n12 * n34 * n43 +
      n13 * n32 * n44 -
      n12 * n33 * n44;
    te[8] =
      n13 * n24 * n42 -
      n14 * n23 * n42 +
      n14 * n22 * n43 -
      n12 * n24 * n43 -
      n13 * n22 * n44 +
      n12 * n23 * n44;
    te[12] =
      n14 * n23 * n32 -
      n13 * n24 * n32 -
      n14 * n22 * n33 +
      n12 * n24 * n33 +
      n13 * n22 * n34 -
      n12 * n23 * n34;
    te[1] =
      n24 * n33 * n41 -
      n23 * n34 * n41 -
      n24 * n31 * n43 +
      n21 * n34 * n43 +
      n23 * n31 * n44 -
      n21 * n33 * n44;
    te[5] =
      n13 * n34 * n41 -
      n14 * n33 * n41 +
      n14 * n31 * n43 -
      n11 * n34 * n43 -
      n13 * n31 * n44 +
      n11 * n33 * n44;
    te[9] =
      n14 * n23 * n41 -
      n13 * n24 * n41 -
      n14 * n21 * n43 +
      n11 * n24 * n43 +
      n13 * n21 * n44 -
      n11 * n23 * n44;
    te[13] =
      n13 * n24 * n31 -
      n14 * n23 * n31 +
      n14 * n21 * n33 -
      n11 * n24 * n33 -
      n13 * n21 * n34 +
      n11 * n23 * n34;
    te[2] =
      n22 * n34 * n41 -
      n24 * n32 * n41 +
      n24 * n31 * n42 -
      n21 * n34 * n42 -
      n22 * n31 * n44 +
      n21 * n32 * n44;
    te[6] =
      n14 * n32 * n41 -
      n12 * n34 * n41 -
      n14 * n31 * n42 +
      n11 * n34 * n42 +
      n12 * n31 * n44 -
      n11 * n32 * n44;
    te[10] =
      n12 * n24 * n41 -
      n14 * n22 * n41 +
      n14 * n21 * n42 -
      n11 * n24 * n42 -
      n12 * n21 * n44 +
      n11 * n22 * n44;
    te[14] =
      n14 * n22 * n31 -
      n12 * n24 * n31 -
      n14 * n21 * n32 +
      n11 * n24 * n32 +
      n12 * n21 * n34 -
      n11 * n22 * n34;
    te[3] =
      n23 * n32 * n41 -
      n22 * n33 * n41 -
      n23 * n31 * n42 +
      n21 * n33 * n42 +
      n22 * n31 * n43 -
      n21 * n32 * n43;
    te[7] =
      n12 * n33 * n41 -
      n13 * n32 * n41 +
      n13 * n31 * n42 -
      n11 * n33 * n42 -
      n12 * n31 * n43 +
      n11 * n32 * n43;
    te[11] =
      n13 * n22 * n41 -
      n12 * n23 * n41 -
      n13 * n21 * n42 +
      n11 * n23 * n42 +
      n12 * n21 * n43 -
      n11 * n22 * n43;
    te[15] =
      n12 * n23 * n31 -
      n13 * n22 * n31 +
      n13 * n21 * n32 -
      n11 * n23 * n32 -
      n12 * n21 * n33 +
      n11 * n22 * n33;

    const det = n11 * te[0] + n21 * te[4] + n31 * te[8] + n41 * te[12];

    if (det === 0) {
      const msg = "Matrix4.getInverse(): can't invert matrix, determinant is 0";

      if (throwOnInvertible || false) {
        throw new Error(msg);
      } else {
        console.warn(msg);
      }

      this.identity();

      return this;
    }

    this.multiplyScalar(1 / det);

    return this;
  }

  isReflected() {
    const te = this.elements;

    const m0 = te[0];
    const m3 = te[4];
    const m6 = te[8];
    const m1 = te[1];
    const m4 = te[5];
    const m7 = te[9];
    const m2 = te[2];
    const m5 = te[6];
    const m8 = te[10];

    const determinant =
      m0 * m4 * m8 + // +aei
      m1 * m5 * m6 + // +bfg
      m2 * m3 * m7 - // +cdh
      m2 * m4 * m6 - // -ceg
      m1 * m3 * m8 - // -bdi
      m0 * m5 * m7; // -afh

    return determinant < 0;
  }

  scale(v: { x?: any; y?: any; z?: any }) {
    const te = this.elements;
    const { x } = v;
    const { y } = v;
    const { z } = v;

    te[0] *= x;
    te[4] *= y;
    te[8] *= z;
    te[1] *= x;
    te[5] *= y;
    te[9] *= z;
    te[2] *= x;
    te[6] *= y;
    te[10] *= z;
    te[3] *= x;
    te[7] *= y;
    te[11] *= z;

    return this;
  }

  getMaxScaleOnAxis() {
    const te = this.elements;

    const scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2];
    const scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6];
    const scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10];

    return Math.sqrt(Math.max(scaleXSq, Math.max(scaleYSq, scaleZSq)));
  }

  makeFrustum(
    left: number,
    right: number,
    bottom: number,
    top: number,
    near: number,
    far: number
  ) {
    const te = this.elements;

    const x = (2 * near) / (right - left);
    const y = (2 * near) / (top - bottom);

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

    te[0] = x;
    te[4] = 0;
    te[8] = a;
    te[12] = 0;
    te[1] = 0;
    te[5] = y;
    te[9] = b;
    te[13] = 0;
    te[2] = 0;
    te[6] = 0;
    te[10] = c;
    te[14] = d;
    te[3] = 0;
    te[7] = 0;
    te[11] = -1;
    te[15] = 0;

    return this;
  }

  makePerspective(fov: number, aspect: number, near: number, far: any) {
    const ymax = near * Math.tan(degToRad(fov * 0.5));
    const ymin = -ymax;
    const xmin = ymin * aspect;
    const xmax = ymax * aspect;

    return this.makeFrustum(xmin, xmax, ymin, ymax, near, far);
  }

  makeOrthographic(
    left: number,
    right: number,
    top: number,
    bottom: number,
    near: number,
    far: number
  ) {
    const te = this.elements;
    const w = 1.0 / (right - left);
    const h = 1.0 / (top - bottom);
    const p = 1.0 / (far - near);

    const x = (right + left) * w;
    const y = (top + bottom) * h;
    const z = (far + near) * p;

    te[0] = 2 * w;
    te[4] = 0;
    te[8] = 0;
    te[12] = -x;
    te[1] = 0;
    te[5] = 2 * h;
    te[9] = 0;
    te[13] = -y;
    te[2] = 0;
    te[6] = 0;
    te[10] = -2 * p;
    te[14] = -z;
    te[3] = 0;
    te[7] = 0;
    te[11] = 0;
    te[15] = 1;

    return this;
  }

  isEqual(m: { elements: any }) {
    const me = m.elements;
    const te = this.elements;

    if (
      te[0] === me[0] &&
      te[4] === me[4] &&
      te[8] === me[8] &&
      te[12] === me[12] &&
      te[1] === me[1] &&
      te[5] === me[5] &&
      te[9] === me[9] &&
      te[13] === me[13] &&
      te[2] === me[2] &&
      te[6] === me[6] &&
      te[10] === me[10] &&
      te[14] === me[14] &&
      te[3] === me[3] &&
      te[7] === me[7] &&
      te[11] === me[11] &&
      te[15] === me[15]
    ) {
      return true;
    }
    return false;
  }

  clone() {
    const te = this.elements;

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

  isIdentity() {
    const te = this.elements;

    if (
      te[0] === 1 &&
      te[4] === 0 &&
      te[8] === 0 &&
      te[12] === 0 &&
      te[1] === 0 &&
      te[5] === 1 &&
      te[9] === 0 &&
      te[13] === 0 &&
      te[2] === 0 &&
      te[6] === 0 &&
      te[10] === 1 &&
      te[14] === 0 &&
      te[3] === 0 &&
      te[7] === 0 &&
      te[11] === 0 &&
      te[15] === 1
    ) {
      return true;
    }
    return false;
  }

  // return true if elements are with digits of identity
  isNearlyIdentity(digits: any) {
    const snapped = this.clone().snap(digits);
    return snapped.isIdentity();
  }


  getScale(scale?: Vector3): Vector3 {
    const te = this.elements;
    scale = scale || new Vector3();
    // grab the axis vectors
    x.set(te[0], te[1], te[2]);
    y.set(te[4], te[5], te[6]);
    z.set(te[8], te[9], te[10]);

    scale.x = x.length();
    scale.y = y.length();
    scale.z = z.length();

    return scale;
  }

  lookAt(eye: Vector3, target: Vector3, up: Vector3) {
    const te = this.elements;

    z.subVectors(eye, target).normalize();

    if (z.length() === 0) {
      z.z = 1;
    }

    x.crossVectors(up, z).normalize();

    if (x.length() === 0) {
      z.x += 0.0001;
      x.crossVectors(up, z).normalize();
    }

    y.crossVectors(z, x);

    te[0] = x.x;
    te[4] = y.x;
    te[8] = z.x;
    te[1] = x.y;
    te[5] = y.y;
    te[9] = z.y;
    te[2] = x.z;
    te[6] = y.z;
    te[10] = z.z;

    return this;
  }

  compose(translation: Vector3, rotation: Quaternion, scale: Vector3) {
    const te = this.elements;

    mRotation.identity();
    mRotation.setRotationFromQuaternion(rotation);

    mScale.makeScale(scale.x, scale.y, scale.z);

    this.multiplyMatrices(mRotation, mScale);

    te[12] = translation.x;
    te[13] = translation.y;
    te[14] = translation.z;

    return this;
  }
}

mRotation = new Matrix4();
mScale = new Matrix4();

/**
 * @interface
 */
export interface XYZ {
  /**  */
  x: number;
  /**  */
  y: number;
  /**  */
  z: number;
}

/** @class 
 *  @subcategory  Math
 * */ 
export class Vector3 {
  // unaccounted for assignents to vector3 properties in other parts of the code
  // look in glcartoon.js for example
  color?: any;
  resi?: any;
  style?: any;
  smoothen?: any;
  atom?: any;
  skip?: any;
  atomid?: undefined;

  x: number;
  y: number;
  z: number;

  constructor(x?: number, y?: number, z?: number) {
    this.x = x || 0.0;
    this.y = y || 0.0;
    this.z = z || 0.0;
    this.atomid = undefined;
  }

  set(x: any, y: any, z: any) {
    this.x = x;
    this.y = y;
    this.z = z;

    return this;
  }

  copy(v: { x: any; y: any; z: any }) {
    this.x = v.x;
    this.y = v.y;
    this.z = v.z;

    return this;
  }

  add(v: { x: any; y: any; z: any }) {
    this.x += v.x;
    this.y += v.y;
    this.z += v.z;

    return this;
  }

  addVectors(a: { x: any; y: any; z: any }, b: { x: any; y: any; z: any }) {
    this.x = a.x + b.x;
    this.y = a.y + b.y;
    this.z = a.z + b.z;

    return this;
  }

  multiplyVectors(
    a: { x: number; y: number; z: number },
    b: { x: number; y: number; z: number }
  ) {
    // elementwise
    this.x = a.x * b.x;
    this.y = a.y * b.y;
    this.z = a.z * b.z;

    return this;
  }

  sub(v: { x: number; y: number; z: number }) {
    this.x -= v.x;
    this.y -= v.y;
    this.z -= v.z;

    return this;
  }

  subVectors(
    a: { x: number; y: number; z: number },
    b: { x: number; y: number; z: number }
  ) {
    this.x = a.x - b.x;
    this.y = a.y - b.y;
    this.z = a.z - b.z;

    return this;
  }

  multiplyScalar(s: number) {
    this.x *= s;
    this.y *= s;
    this.z *= s;

    return this;
  }

  divideScalar(s: number) {
    if (s !== 0) {
      this.x /= s;
      this.y /= s;
      this.z /= s;
    } else {
      this.x = 0;
      this.y = 0;
      this.z = 0;
    }

    return this;
  }

  // accumulate maximum
  max(s: { x: number; y: number; z: number }) {
    this.x = Math.max(this.x, s.x);
    this.y = Math.max(this.y, s.y);
    this.z = Math.max(this.z, s.z);

    return this;
  }

  // accumulate min
  min(s: { x: number; y: number; z: number }) {
    this.x = Math.min(this.x, s.x);
    this.y = Math.min(this.y, s.y);
    this.z = Math.min(this.z, s.z);

    return this;
  }

  distanceTo(v: any) {
    return Math.sqrt(this.distanceToSquared(v));
  }

  distanceToSquared(v: { x: number; y: number; z: number }) {
    const dx = this.x - v.x;
    const dy = this.y - v.y;
    const dz = this.z - v.z;

    return dx * dx + dy * dy + dz * dz;
  }

  applyMatrix3(m: { elements: any }) {
    const { x } = this;
    const { y } = this;
    const { z } = this;

    const e = m.elements;
    // column major ordering
    this.x = e[0] * x + e[3] * y + e[6] * z;
    this.y = e[1] * x + e[4] * y + e[7] * z;
    this.z = e[2] * x + e[5] * y + e[8] * z;

    return this;
  }

  applyMatrix4(m: { elements: any }) {
    const { x } = this;
    const { y } = this;
    const { z } = this;

    const e = m.elements;

    this.x = e[0] * x + e[4] * y + e[8] * z + e[12];
    this.y = e[1] * x + e[5] * y + e[9] * z + e[13];
    this.z = e[2] * x + e[6] * y + e[10] * z + e[14];

    return this;
  }

  applyProjection(m: { elements: any }) {
    // input: Matrix4 projection matrix
    const { x } = this;
    const { y } = this;
    const { z } = this;

    const e = m.elements;
    const d = e[3] * x + e[7] * y + e[11] * z + e[15];

    this.x = (e[0] * x + e[4] * y + e[8] * z + e[12]) / d;
    this.y = (e[1] * x + e[5] * y + e[9] * z + e[13]) / d;
    this.z = (e[2] * x + e[6] * y + e[10] * z + e[14]) / d;

    return this;
  }

  applyQuaternion(q: Quaternion): Vector3 {
    // save values
    const { x } = this;
    const { y } = this;
    const { z } = this;

    const qx = q.x;
    const qy = q.y;
    const qz = q.z;
    const qw = q.w;

    // compute this as
    // t = 2 * cross(q.xyz, v)
    // newv = v + q.w * t + cross(q.xyz, t)
    // this from molecularmusings
    // http://molecularmusings.wordpress.com/2013/05/24/a-faster-quaternion-vector-multiplication/
    const t: { x?: number; y?: number; z?: number } = {};
    t.x = 2 * (y * qz - z * qy);
    t.y = 2 * (z * qx - x * qz);
    t.z = 2 * (x * qy - y * qx);

    // cross t with q
    const t2: { x?: number; y?: number; z?: number } = {};
    t2.x = t.y * qz - t.z * qy;
    t2.y = t.z * qx - t.x * qz;
    t2.z = t.x * qy - t.y * qx;

    this.x = x + qw * t.x + t2.x;
    this.y = y + qw * t.y + t2.y;
    this.z = z + qw * t.z + t2.z;

    return this;
  }

  negate() {
    return this.multiplyScalar(-1);
  }

  dot(v: Vector3) {
    return this.x * v.x + this.y * v.y + this.z * v.z;
  }

  length() {
    return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
  }

  lengthSq() {
    return this.x * this.x + this.y * this.y + this.z * this.z;
  }

  normalize() {
    return this.divideScalar(this.length());
  }

  cross(v: Vector3) {
    const { x } = this;
    const { y } = this;
    const { z } = this;

    this.x = y * v.z - z * v.y;
    this.y = z * v.x - x * v.z;
    this.z = x * v.y - y * v.x;

    return this;
  }

  crossVectors(a: Vector3, b: Vector3) {
    this.x = a.y * b.z - a.z * b.y;
    this.y = a.z * b.x - a.x * b.z;
    this.z = a.x * b.y - a.y * b.x;

    return this;
  }

  equals(b: Vector3) {
    return this.x == b.x && this.y == b.y && this.z == b.z;
  }
  
  getPositionFromMatrix(m: Matrix4) {
    this.x = m.elements[12];
    this.y = m.elements[13];
    this.z = m.elements[14];

    return this;
  }

  setEulerFromRotationMatrix(m: Matrix4, order: string) {
    // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
    const te = m.elements;
    const m11 = te[0];
    const m12 = te[4];
    const m13 = te[8];
    // var m21 = te[1];
    const m22 = te[5];
    const m23 = te[9];
    // var m31 = te[2];
    const m32 = te[6];
    const m33 = te[10];

    if (order === undefined || order === "XYZ") {
      this.y = Math.asin(clamp(m13, -1, 1));

      if (Math.abs(m13) < 0.99999) {
        this.x = Math.atan2(-m23, m33);
        this.z = Math.atan2(-m12, m11);
      } else {
        this.x = Math.atan2(m32, m22);
        this.z = 0;
      }
    } else {
      console.error(
        `Error with vector's setEulerFromRotationMatrix: Unknown order: ${order}`
      );
    }

    return this;
  }

  rotateAboutVector(axis: Vector3, ang: number) {
    axis.normalize();
    const cosang = Math.cos(ang);
    const sinang = Math.sin(ang);
    // Rodrigues' rotation formula, from wikipedia
    const term1 = this.clone().multiplyScalar(cosang);
    const term2 = axis.clone().cross(this).multiplyScalar(sinang);
    const term3 = axis
      .clone()
      .multiplyScalar(axis.clone().dot(this))
      .multiplyScalar(1 - cosang);

    const rot = term1.add(term2).add(term3);

    this.x = rot.x;
    this.y = rot.y;
    this.z = rot.z;

    return this;
  }

  setFromMatrixPosition(m: { elements: any }) {
    const e = m.elements;

    this.x = e[12];
    this.y = e[13];
    this.z = e[14];

    return this;
  }

  // unproject is defined after Matrix4
  transformDirection(m: { elements: any }) {
    // input: THREE.Matrix4 affine matrix
    // vector interpreted as a direction
    const { x } = this;
    const { y } = this;
    const { z } = this;
    const e = m.elements;

    this.x = e[0] * x + e[4] * y + e[8] * z;
    this.y = e[1] * x + e[5] * y + e[9] * z;
    this.z = e[2] * x + e[6] * y + e[10] * z;

    return this.normalize();
  }

  clone() {
    return new Vector3(this.x, this.y, this.z);
  }

  unproject(camera: { matrixWorld: any; projectionMatrix: any }) {
    const mat4 = mRotation;
    mat4.multiplyMatrices(
      camera.matrixWorld,
      mat4.getInverse(camera.projectionMatrix)
    );
    return this.applyMatrix4(mat4);
  }
}

x = new Vector3();
y = new Vector3();
z = new Vector3();

/* 
 * @interface Matrix3
 */
export interface Matrix3 {
  elements: Float32Array;
}

/** @class 
 *  @subcategory  Math
 * */ 
export class Matrix3 {
  constructor(
    n11: number = 1,
    n12: number = 0,
    n13: number = 0,
    n21: number = 0,
    n22: number = 1,
    n23: number = 0,
    n31: number = 0,
    n32: number = 0,
    n33: number = 1
  ) {
    this.elements = new Float32Array(9);
    this.set(n11, n12, n13, n21, n22, n23, n31, n32, n33);
  }

  set(
    n11: number,
    n12: number,
    n13: number,
    n21: number,
    n22: number,
    n23: number,
    n31: number,
    n32: number,
    n33: number
  ) {
    const te = this.elements;

    te[0] = n11;
    te[3] = n12;
    te[6] = n13;
    te[1] = n21;
    te[4] = n22;
    te[7] = n23;
    te[2] = n31;
    te[5] = n32;
    te[8] = n33;

    return this;
  }

  identity() {
    this.set(1, 0, 0, 0, 1, 0, 0, 0, 1);

    return this;
  }

  copy(m: { elements: any }) {
    const me = m.elements;

    this.set(me[0], me[3], me[6], me[1], me[4], me[7], me[2], me[5], me[8]);
  }

  multiplyScalar(s: number) {
    const te = this.elements;

    te[0] *= s;
    te[3] *= s;
    te[6] *= s;
    te[1] *= s;
    te[4] *= s;
    te[7] *= s;
    te[2] *= s;
    te[5] *= s;
    te[8] *= s;

    return this;
  }

  getInverse3(matrix: { elements: any }) {
    // input: Matrix3
    const me = matrix.elements;
    const te = this.elements;

    te[0] = me[4] * me[8] - me[5] * me[7];
    te[3] = me[6] * me[5] - me[3] * me[8];
    te[6] = me[3] * me[7] - me[6] * me[4];
    te[1] = me[7] * me[2] - me[1] * me[8];
    te[4] = me[0] * me[8] - me[6] * me[2];
    te[7] = me[1] * me[6] - me[0] * me[7];
    te[2] = me[1] * me[5] - me[2] * me[4];
    te[5] = me[2] * me[3] - me[0] * me[5];
    te[8] = me[0] * me[4] - me[1] * me[3];

    const det = me[0] * te[0] + me[3] * te[1] + me[6] * te[2];
    this.multiplyScalar(1.0 / det);

    return this;
  }

  getInverse(matrix: { elements: any }, throwOnInvertible: any) {
    // input: Matrix4
    const me = matrix.elements;
    const te = this.elements;

    te[0] = me[10] * me[5] - me[6] * me[9];
    te[1] = -me[10] * me[1] + me[2] * me[9];
    te[2] = me[6] * me[1] - me[2] * me[5];
    te[3] = -me[10] * me[4] + me[6] * me[8];
    te[4] = me[10] * me[0] - me[2] * me[8];
    te[5] = -me[6] * me[0] + me[2] * me[4];
    te[6] = me[9] * me[4] - me[5] * me[8];
    te[7] = -me[9] * me[0] + me[1] * me[8];
    te[8] = me[5] * me[0] - me[1] * me[4];

    const det = me[0] * te[0] + me[1] * te[3] + me[2] * te[6];

    // no inverse
    if (det === 0) {
      const msg = "Matrix3.getInverse(): can't invert matrix, determinant is 0";

      if (throwOnInvertible || false) {
        throw new Error(msg);
      } else {
        console.warn(msg);
      }

      this.identity();

      return this;
    }

    this.multiplyScalar(1.0 / det);

    return this;
  }

  // https://en.wikipedia.org/wiki/Determinant
  getDeterminant() {
    const m = this.elements;

    /*
     * |a b c| |d e f| |g h i|
     */
    const determinant =
      m[0] * m[4] * m[8] + // +aei
      m[1] * m[5] * m[6] + // +bfg
      m[2] * m[3] * m[7] - // +cdh
      m[2] * m[4] * m[6] - // -ceg
      m[1] * m[3] * m[8] - // -bdi
      m[0] * m[5] * m[7]; // -afh
    return determinant;
  }

  transpose() {
    let tmp: any;
    const m = this.elements;

    tmp = m[1];
    m[1] = m[3];
    m[3] = tmp;
    tmp = m[2];
    m[2] = m[6];
    m[6] = tmp;
    tmp = m[5];
    m[5] = m[7];
    m[7] = tmp;

    return this;
  }

  clone() {
    const te = this.elements;

    return new Matrix3(
      te[0],
      te[3],
      te[6],
      te[1],
      te[4],
      te[7],
      te[2],
      te[5],
      te[8]
    );
  }

  getMatrix4() {
    const m = this.elements;
    return new Matrix4(
      m[0],
      m[3],
      m[6],
      0,
      m[1],
      m[4],
      m[7],
      0,
      m[2],
      m[5],
      m[8],
      0
    );
  }
}

/** @class 
 *  @subcategory  Math
 * */ 
export class Ray {
  origin: Vector3;
  direction: Vector3;

  constructor(origin?: Vector3, direction?: Vector3) {
    this.origin = origin !== undefined ? origin : new Vector3();
    this.direction = direction !== undefined ? direction : new Vector3();
  }

  set(origin: Vector3, direction: Vector3) {
    this.origin.copy(origin);
    this.direction.copy(direction);

    return this;
  }

  copy(ray: Ray) {
    this.origin.copy(ray.origin);
    this.direction.copy(ray.direction);

    return this;
  }

  at(t: number, optionalTarget: Vector3) {
    const result = optionalTarget || new Vector3();

    return result.copy(this.direction).multiplyScalar(t).add(this.origin);
  }

  recast(t: any) {
    const v1 = x;
    this.origin.copy(this.at(t, v1));

    return this;
  }

  closestPointToPoint(point: Vector3, optionalTarget: Vector3) {
    const result = optionalTarget || new Vector3();
    result.subVectors(point, this.origin);
    const directionDistance = result.dot(this.direction);

    // returns a point on this ray
    return result
      .copy(this.direction)
      .multiplyScalar(directionDistance)
      .add(this.origin);
  }

  distanceToPoint(point: Vector3) {
    const v1 = x;
    const directionDistance = v1
      .subVectors(point, this.origin)
      .dot(this.direction);
    v1.copy(this.direction).multiplyScalar(directionDistance).add(this.origin);
    return v1.distanceTo(point);
  }

  // eslint-disable-next-line class-methods-use-this
  isIntersectionCylinder() {}

  isIntersectionSphere(sphere: Sphere) {
    return this.distanceToPoint(sphere.center) <= sphere.radius;
  }

  isIntersectionPlane(plane: any) {
    const denominator = plane.normal.dot(this.direction);

    // plane and ray are not perpendicular
    if (denominator !== 0) return true;

    if (plane.distanceToPoint(this.origin) === 0) return true;

    return false;
  }

  distanceToPlane(plane: any) {
    const denominator = plane.normal.dot(this.direction);
    if (denominator === 0) {
      // line is coplanar
      if (plane.distanceToPoint(this.origin) === 0) return 0;

      // ray is parallel
      return undefined;
    }

    const t = -(this.origin.dot(plane.normal) + plane.constant) / denominator;

    return t;
  }

  intersectPlane(plane: any, optionalTarget: any) {
    const t = this.distanceToPlane(plane);

    if (t === undefined) return undefined;

    return this.at(t, optionalTarget);
  }

  applyMatrix4(matrix4: any) {
    this.direction.add(this.origin).applyMatrix4(matrix4);
    this.origin.applyMatrix4(matrix4);
    this.direction.sub(this.origin);

    return this;
  }

  clone() {
    return new Ray().copy(this);
  }
}