Source: math/QuaternionNotifier.js

import {
    quat
} from 'gl-matrix';
import Matrix3 from './Matrix3';
import Class from '../core/Class';

const tempMat3 = new Matrix3();

/**
 * 四元数,具有 onUpdate 回调
 * @class
 */
const QuaternionNotifier = Class.create(/** @lends QuaternionNotifier.prototype */ {
    /**
     * 类名
     * @type {String}
     * @default QuaternionNotifier
     */
    className: 'QuaternionNotifier',
    /**
     * @type {Boolean}
     * @default true
     */
    isQuaternionNotifier: true,
    /**
     * @type {Boolean}
     * @default true
     */
    isQuaternion: true,
    /**
     * Creates a new identity quat
     * @constructs
     * @param  {Number} [x=0] X component
     * @param  {Number} [y=0] Y component
     * @param  {Number} [z=0] Z component
     * @param  {Number} [w=1] W component
    */
    constructor(x = 0, y = 0, z = 0, w = 1) {
    /**
     * 数据
     * @type {Float32Array}
     */
        this.elements = quat.fromValues(x, y, z, w);
    },

    /**
     * 更新的回调
     */
    onUpdate() {

    },

    /**
   * Copy the values from one quat to this
   * @param  {Quaternion} q
   * @return {Quaternion} this
   */
    copy(q) {
        quat.copy(this.elements, q.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Creates a new quat initialized with values from an existing quaternion
   * @return {Quaternion} a new quaternion
   */
    clone() {
        const el = this.elements;
        return new this.constructor(el[0], el[1], el[2], el[3]);
    },

    /**
   * 转换到数组
   * @param  {number[]|TypedArray}  [array=[]] 数组
   * @param  {Number} [offset=0] 数组偏移值
   * @return {Array}
   */
    toArray(array = [], offset = 0) {
        const el = this.elements;

        array[offset] = el[0];
        array[offset + 1] = el[1];
        array[offset + 2] = el[2];
        array[offset + 3] = el[3];

        return array;
    },
    /**
   * 从数组赋值
   * @param  {number[]|TypedArray} array  数组
   * @param  {Number} [offset=0] 数组偏移值
   * @return {Quaternion} this
   */
    fromArray(array, offset = 0) {
        const el = this.elements;

        el[0] = array[offset];
        el[1] = array[offset + 1];
        el[2] = array[offset + 2];
        el[3] = array[offset + 3];

        this.onUpdate();

        return this;
    },

    /**
   * Set the components of a quat to the given values
   * @param {Number} x  X component
   * @param {Number} y  Y component
   * @param {Number} z  Z component
   * @param {Number} w  W component
   * @return {Quaternion} this
   */
    set(x, y, z, w) {
        quat.set(this.elements, x, y, z, w);
        this.onUpdate();
        return this;
    },

    /**
   * Set this to the identity quaternion
   * @return {Quaternion} this
   */
    identity() {
        quat.identity(this.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Sets a quaternion to represent the shortest rotation from one
   * vector to another.
   * @param  {Vector3} a the initial vector
   * @param  {Vector3} b the destination vector
   * @return {Quaternion} this
   */
    rotationTo(a, b) {
        quat.rotationTo(this.elements, a.elements, b.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Sets the specified quaternion with values corresponding to the given
   * axes. Each axis is a vec3 and is expected to be unit length and
   * perpendicular to all other specified axes.
   *
   * @param {Vector3} view  the vector representing the viewing direction
   * @param {Vector3} right the vector representing the local "right" direction
   * @param {Vector3} up    the vector representing the local "up" direction
   * @return {Quaternion} this
   */
    setAxes(view, right, up) {
        quat.setAxes(this.elements, view.elements, right.elements, up.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Sets a quat from the given angle and rotation axis,
   * then returns it.
   * @param {Vector3} axis the axis around which to rotate
   * @param {Number} rad the angle in radians
   * @return {Quaternion} this
   */
    setAxisAngle(axis, rad) {
        quat.setAxisAngle(this.elements, axis.elements, rad);
        this.onUpdate();
        return this;
    },
    /**
   * Gets the rotation axis and angle for a given
   *  quaternion. If a quaternion is created with
   *  setAxisAngle, this method will return the same
   *  values as providied in the original parameter list
   *  OR functionally equivalent values.
   * Example: The quaternion formed by axis [0, 0, 1] and
   *  angle -90 is the same as the quaternion formed by
   *  [0, 0, 1] and 270. This method favors the latter.
   * @param  {Vector3} out_axis  Vector receiving the axis of rotation
   * @return {Number} Angle, in radians, of the rotation
   */
    getAxisAngle(axis) {
        return quat.getAxisAngle(axis.elements, this.elements);
    },
    /**
   * Adds two quat's
   * @param {Quaternion} q
   * @return {Quaternion} this
   */
    add(q) {
        quat.add(this.elements, this.elements, q.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Multiplies two quat's
   * @param  {Quaternion} q
   * @return {Quaternion} this
   */
    multiply(q) {
        quat.multiply(this.elements, this.elements, q.elements);
        this.onUpdate();
        return this;
    },
    /**
   * premultiply the quat
   * @param  {Quaternion} q
   * @return {Quaternion} this
   */
    premultiply(q) {
        quat.multiply(this.elements, q.elements, this.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Scales a quat by a scalar number
   * @param  {Vector3} scale the vector to scale
   * @return {Quaternion} this
   */
    scale(scale) {
        quat.scale(this.elements, this.elements, scale);
        this.onUpdate();
        return this;
    },
    /**
   * Rotates a quaternion by the given angle about the X axis
   * @param  {Number} rad angle (in radians) to rotate
   * @return {Quaternion} this
   */
    rotateX(rad) {
        quat.rotateX(this.elements, this.elements, rad);
        this.onUpdate();
        return this;
    },
    /**
   * Rotates a quaternion by the given angle about the Y axis
   * @param  {Number} rad angle (in radians) to rotate
   * @return {Quaternion} this
   */
    rotateY(rad) {
        quat.rotateY(this.elements, this.elements, rad);
        this.onUpdate();
        return this;
    },
    /**
   * Rotates a quaternion by the given angle about the Z axis
   * @param  {Number} rad angle (in radians) to rotate
   * @return {Quaternion} this
   */
    rotateZ(rad) {
        quat.rotateZ(this.elements, this.elements, rad);
        this.onUpdate();
        return this;
    },
    /**
   * Calculates the W component of a quat from the X, Y, and Z components.
   * Assumes that quaternion is 1 unit in length.
   * Any existing W component will be ignored.
   * @returns {Quaternion} this
   */
    calculateW() {
        quat.calculateW(this.elements, this.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Calculates the dot product of two quat's
   * @param  {Quaternion} q
   * @return {Number} dot product of two quat's
   */
    dot(q) {
        return quat.dot(this.elements, q.elements);
    },
    /**
   * Performs a linear interpolation between two quat's
   * @param  {Quaternion} q
   * @param  {Number} t interpolation amount between the two inputs
   * @return {Quaternion} this
   */
    lerp(q, t) {
        quat.lerp(this.elements, this.elements, q.elements, t);
        this.onUpdate();
        return this;
    },
    /**
   * Performs a spherical linear interpolation between two quat
   * @param  {Quaternion} q
   * @param  {Number} t interpolation amount between the two inputs
   * @return {Quaternion} this
   */
    slerp(q, t) {
        quat.slerp(this.elements, this.elements, q.elements, t);
        this.onUpdate();
        return this;
    },
    /**
   * Performs a spherical linear interpolation with two control points
   * @param  {Quaternion} qa
   * @param  {Quaternion} qb
   * @param  {Quaternion} qc
   * @param  {Quaternion} qd
   * @param  {Number} t interpolation amount
   * @return {Quaternion} this
   */
    sqlerp(qa, qb, qc, qd, t) {
        quat.sqlerp(this.elements, qa.elements, qb.elements, qc.elements, qd.elements, t);
        this.onUpdate();
        return this;
    },
    /**
   * Calculates the inverse of a quat
   * @return {Quaternion} this
   */
    invert() {
        quat.invert(this.elements, this.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Calculates the conjugate of a quat
   * If the quaternion is normalized, this function is faster than quat.inverse and produces the same result.
   * @return {Quaternion} this
   */
    conjugate() {
        quat.conjugate(this.elements, this.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Calculates the length of a quat
   * @return {Number} length of this
   */
    length() {
        return quat.length(this.elements);
    },
    /**
   * Calculates the squared length of a quat
   * @return {Number} squared length of this
   */
    squaredLength() {
        return quat.squaredLength(this.elements);
    },
    /**
   * Normalize this
   * @return {Quaternion} this
   */
    normalize() {
        quat.normalize(this.elements, this.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Creates a quaternion from the given 3x3 rotation matrix.
   *
   * NOTE: The resultant quaternion is not normalized, so you should be sure
   * to renormalize the quaternion yourself where necessary.
   *
   * @param {Matrix3} m rotation matrix
   * @return {Quaternion} this
   */
    fromMat3(mat) {
        quat.fromMat3(this.elements, mat.elements);
        this.onUpdate();
        return this;
    },
    /**
   * Creates a quaternion from the given 3x3 rotation matrix.
   *
   * NOTE: The resultant quaternion is not normalized, so you should be sure
   * to renormalize the quaternion yourself where necessary.
   *
   * @param {Matrix4} m rotation matrix
   * @return {Quaternion} this
   */
    fromMat4(mat) {
        tempMat3.fromMat4(mat);
        this.fromMat3(tempMat3);
        return this;
    },
    /**
   * Returns whether or not the quaternions have exactly the same elements in the same position (when compared with ===)
   * @param  {Quaternion} q
   * @return {Boolean}
   */
    exactEquals(q) {
        return quat.exactEquals(this.elements, q.elements);
    },
    /**
   * Returns whether or not the quaternions have approximately the same elements in the same position.
   * @param  {Quaternion} q
   * @return {Boolean}
   */
    equals(q) {
        return quat.equals(this.elements, q.elements);
    },
    /**
   * Creates a quaternion from the given euler.
   * @param  {Euler} euler
   * @param  {Boolean} [notCallUpdate=false] 是否需要调用onUpdate
   * @return {Quaternion} this
   */
    fromEuler(euler, notCallUpdate) {
    // Based on https://github.com/mrdoob/three.js/blob/dev/src/math/Quaternion.js#L200

        // quat.fromEuler(this.elements, euler.x, euler.y, euler.z);
        const x = euler.x * .5;
        const y = euler.y * .5;
        const z = euler.z * .5;
        const order = euler.order || 'ZYX';

        let sx = Math.sin(x);
        let cx = Math.cos(x);
        let sy = Math.sin(y);
        let cy = Math.cos(y);
        let sz = Math.sin(z);
        let cz = Math.cos(z);

        const out = this.elements;

        if (order === 'XYZ') {
            out[0] = sx * cy * cz + cx * sy * sz;
            out[1] = cx * sy * cz - sx * cy * sz;
            out[2] = cx * cy * sz + sx * sy * cz;
            out[3] = cx * cy * cz - sx * sy * sz;
        } else if (order === 'YXZ') {
            out[0] = sx * cy * cz + cx * sy * sz;
            out[1] = cx * sy * cz - sx * cy * sz;
            out[2] = cx * cy * sz - sx * sy * cz;
            out[3] = cx * cy * cz + sx * sy * sz;
        } else if (order === 'ZXY') {
            out[0] = sx * cy * cz - cx * sy * sz;
            out[1] = cx * sy * cz + sx * cy * sz;
            out[2] = cx * cy * sz + sx * sy * cz;
            out[3] = cx * cy * cz - sx * sy * sz;
        } else if (order === 'ZYX') {
            out[0] = sx * cy * cz - cx * sy * sz;
            out[1] = cx * sy * cz + sx * cy * sz;
            out[2] = cx * cy * sz - sx * sy * cz;
            out[3] = cx * cy * cz + sx * sy * sz;
        } else if (order === 'YZX') {
            out[0] = sx * cy * cz + cx * sy * sz;
            out[1] = cx * sy * cz + sx * cy * sz;
            out[2] = cx * cy * sz - sx * sy * cz;
            out[3] = cx * cy * cz - sx * sy * sz;
        } else if (order === 'XZY') {
            out[0] = sx * cy * cz - cx * sy * sz;
            out[1] = cx * sy * cz - sx * cy * sz;
            out[2] = cx * cy * sz + sx * sy * cz;
            out[3] = cx * cy * cz + sx * sy * sz;
        }

        if (!notCallUpdate) {
            this.onUpdate();
        }

        return this;
    },
    /**
   * X component
   * @type {Number}
   */
    x: {
        get() {
            return this.elements[0];
        },
        set(value) {
            this.elements[0] = value;
            this.onUpdate();
        }
    },
    /**
   * Y component
   * @type {Number}
   */
    y: {
        get() {
            return this.elements[1];
        },
        set(value) {
            this.elements[1] = value;
            this.onUpdate();
        }
    },
    /**
   * Z component
   * @type {Number}
   */
    z: {
        get() {
            return this.elements[2];
        },
        set(value) {
            this.elements[2] = value;
            this.onUpdate();
        }
    },
    /**
   * W component
   * @type {Number}
   */
    w: {
        get() {
            return this.elements[3];
        },
        set(value) {
            this.elements[3] = value;
            this.onUpdate();
        }
    }
});

/**
 * Alias for {@link QuaternionNotifier#multiply}
 * @function
 * @param  {QuaternionNotifier} q
 */
QuaternionNotifier.prototype.mul = QuaternionNotifier.prototype.multiply;

/**
 * Alias for {@link QuaternionNotifier#length}
 * @function
 */
QuaternionNotifier.prototype.len = QuaternionNotifier.prototype.length;

/**
 * Alias for {@link QuaternionNotifier#squaredLength}
 * @function
 */
QuaternionNotifier.prototype.sqrLen = QuaternionNotifier.prototype.squaredLength;

export default QuaternionNotifier;