Source: quaternion.js

/**
 * A quaternion.
 * It has x, y, z and w components.
 */
class Quaternion {
  /**
   * Creates a new quaternion.
   * @param {Number} x The x component.
   * @param {Number} y The y component.
   * @param {Number} z The z component.
   * @param {Number} w The w component.
   */
  constructor(x = 0, y = 0, z = 0, w = 1) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.w = w;
  }

  /**
   * Sets the components of the quaternion.
   * @param {Number} x The x component.
   * @param {Number} y The y component.
   * @param {Number} z The z component.
   * @param {Number} w The w component.
   * @returns {Quaternion} The quaternion.
   */
  set(x, y, z, w) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.w = w;

    return this;
  }

  /**
   * Sets the components of the quaternion to the identity.
   * @returns {Quaternion} The quaternion.
   */
  setIdentity() {
    this.x = 0;
    this.y = 0;
    this.z = 0;
    this.w = 1;

    return this;
  }

  /**
   * Sets the quaternion to a rotation.
   * @param {Vector3} axis The axis to rotate around.
   * @param {Number} angle The angle to rotate.
   * @returns {Quaternion} The quaternion.
   */
  setAxisAngle(axis, angle) {
    const sin = Math.sin(angle / 2);

    this.x = sin * axis.x;
    this.y = sin * axis.y;
    this.z = sin * axis.z;
    this.w = Math.cos(angle / 2);

    return this;
  }

  /**
   * Copies the components of another quaternion.
   * @param {Quaternion} q The quaternion to copy the components from.
   * @returns {Quaternion} The quaternion.
   */
  copy(q) {
    this.x = q.x;
    this.y = q.y;
    this.z = q.z;
    this.w = q.w;

    return this;
  }

  /**
   * Creates a clone of the quaternion.
   * @returns {Quaternion} The cloned quaternion.
   */
  clone() {
    return new Quaternion(this.x, this.y, this.z, this.w);
  }

  /**
   * Sets the quaternion to its conjugate.
   * @returns {Quaternion} The quaternion.
   */
  conjugate() {
    this.x = -this.x;
    this.y = -this.y;
    this.z = -this.z;

    return this;
  }

  /**
   * Sets the quaternion to the conjugate of another quaternion.
   * @param {Quaternion} q The quaternion to calculate the conjugate.
   * @returns {Quaternion} The quaternion.
   */
  conjugateQuaternion(q) {
    this.x = -q.x;
    this.y = -q.y;
    this.z = -q.z;
    this.w = q.w;

    return this;
  }

  /**
   * Sets the quaternion to its inverse.
   * @returns {Quaternion} The quaternion.
   */
  invert() {
    const { x, y, z, w } = this;
    const dot = x * x + y * y + z * z + w * w;

    this.x = -x / dot;
    this.y = -y / dot;
    this.z = -z / dot;
    this.w = w / dot;

    return this;
  }

  /**
   * Sets the quaternion to the inverse of another quaternion.
   * @param {Quaternion} q The quaternion to calculate the inverse.
   */
  invertQuaternion(q) {
    const { x, y, z, w } = q;
    const dot = x * x + y * y + z * z + w * w;

    this.x = -x / dot;
    this.y = -y / dot;
    this.z = -z / dot;
    this.w = w / dot;

    return this;
  }

  /**
   * Calculates the length of the quaternion.
   * @returns {Number} The length of the quaternion.
   */
  length() {
    return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
  }

  /**
   * Calculates the squared length of the quaternion.
   * @returns {Number} The squared length of the quaternion.
   */
  lengthSquared() {
    return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
  }

  /**
   * Normalizes the quaternion.
   * @returns {Quaternion} The quaternion.
   */
  normalize() {
    const length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);

    if (length !== 0) {
      this.x /= length;
      this.y /= length;
      this.z /= length;
      this.w /= length;
    }

    return this;
  }

  /**
   * Sets the quaternion to the product of other two quaternions.
   * @param {Quaternion} q The first quaternion.
   * @param {Quaternion} r The second quaternion.
   * @returns {Quaternion} The quaternion.
   */
  multiplyQuaternions(q, r) {
    this.x = q.x * r.w + q.w * r.x + q.y * r.z - q.z * r.y;
    this.y = q.y * r.w + q.w * r.y + q.z * r.x - q.x * r.z;
    this.z = q.z * r.w + q.w * r.z + q.x * r.y - q.y * r.x;
    this.w = q.w * r.w - q.x * r.x - q.y * r.y - q.z * r.z;

    return this;
  }

  /**
   * Calculates the dot product with another quaternion.
   * @param {Quaternion} q The quaternion to calculate the dot product with.
   * @returns {Number} The dot product between the quaternions.
   */
  dot(q) {
    return this.x * q.x + this.y * q.y + this.z * q.z + this.w * q.w;
  }
}

/**
 * An identity quaternion.
 */
Quaternion.identity = new Quaternion(0, 0, 0, 1);

export default Quaternion;