Source: math/Euler.js

import Class from '../core/Class';
import math from './math';
import Matrix4 from './Matrix4';
import log from '../utils/log';

const tempMatrix = new Matrix4();
const DEG2RAD = math.DEG2RAD;
const RAD2DEG = math.RAD2DEG;

/**
 * @class
 */
const Euler = Class.create(/** @lends Euler.prototype */ {
    /**
     * 类名
     * @type {String}
     * @default Euler
     */
    className: 'Euler',
    /**
     * @type {boolean}
     * @default true
     */
    isEuler: true,
    /**
     * 旋转顺序,默认为 ZYX
     * @type {string}
     * @default 'ZYX'
     */
    order: 'ZYX',
    /**
     * @constructs
     * @param  {Number} [x=0]  角度 X, 弧度制
     * @param  {Number} [y=0]  角度 Y, 弧度制
     * @param  {Number} [z=0]  角度 Z, 弧度制
     */
    constructor(x = 0, y = 0, z = 0) {
        /**
         * 数据
         * @type {Float32Array}
         */
        this.elements = new Float32Array([x, y, z]);
        this.updateDegrees();
    },
    /**
     * 克隆
     * @return {Euler}
     */
    clone() {
        const euler = new this.constructor();
        euler.copy(this);
        return euler;
    },
    /**
     * 复制
     * @param  {Euler} euler
     * @return {Euler} this
     */
    copy(euler) {
        this.elements[0] = euler.x;
        this.elements[1] = euler.y;
        this.elements[2] = euler.z;
        this.order = euler.order;
        this.updateDegrees();
        return this;
    },
    /**
     * Set the components of a euler to the given values
     * @param {Number} x x 轴旋转角度, 弧度制
     * @param {Number} y y 轴旋转角度, 弧度制
     * @param {Number} z z 轴旋转角度, 弧度制
     * @return {Euler} this
     */
    set(x, y, z) {
        this.elements[0] = x;
        this.elements[1] = y;
        this.elements[2] = z;
        this.updateDegrees();
        return this;
    },
    /**
     * 设置角度
     * @param {Number} degX x 轴旋转角度, 角度制
     * @param {Number} degY y 轴旋转角度, 角度制
     * @param {Number} degZ z 轴旋转角度, 角度制
     * @return {Euler} this
     */
    setDegree(degX, degY, degZ) {
        this._degX = degX;
        this._degY = degY;
        this._degZ = degZ;
        this.updateRadians();
        return this;
    },
    /**
     * 从数组赋值
     * @param  {number[]|TypedArray} array  数组
     * @param  {Number} [offset=0] 数组偏移值
     * @return {Euler} this
     */
    fromArray(array, offset = 0) {
        this.elements[0] = array[offset];
        this.elements[1] = array[offset + 1];
        this.elements[2] = array[offset + 2];
        this.updateDegrees();
        return this;
    },
    /**
     * 转换到数组
     * @param  {number[]|TypedArray}  [array=[]] 数组
     * @param  {Number} [offset=0] 数组偏移值
     * @return {Array}
     */
    toArray(array = [], offset = 0) {
        array[offset] = this.elements[0];
        array[offset + 1] = this.elements[0 + 1];
        array[offset + 2] = this.elements[0 + 2];
        return array;
    },
    /**
     * Creates a euler from the given 4x4 rotation matrix.
     * @param {Matrix4} mat rotation matrix
     * @param {string} [order=this.order] 旋转顺序,默认为当前Euler实例的order
     * @return {Euler} this
     */
    fromMat4(mat, order) {
        // Based on https://github.com/mrdoob/three.js/blob/dev/src/math/Euler.js#L133

        const elements = mat.elements;
        const m11 = elements[0];
        const m21 = elements[1];
        const m31 = elements[2];
        const m12 = elements[4];
        const m22 = elements[5];
        const m32 = elements[6];
        const m13 = elements[8];
        const m23 = elements[9];
        const m33 = elements[10];

        order = order || this.order;
        this.order = order;

        const clamp = math.clamp;

        if (order === 'XYZ') {
            this.elements[1] = Math.asin(clamp(m13, -1, 1));
            if (Math.abs(m13) < 0.99999) {
                this.elements[0] = Math.atan2(-m23, m33);
                this.elements[2] = Math.atan2(-m12, m11);
            } else {
                this.elements[0] = Math.atan2(m32, m22);
                this.elements[2] = 0;
            }
        } else if (order === 'YXZ') {
            this.elements[0] = Math.asin(-clamp(m23, -1, 1));
            if (Math.abs(m23) < 0.99999) {
                this.elements[1] = Math.atan2(m13, m33);
                this.elements[2] = Math.atan2(m21, m22);
            } else {
                this.elements[1] = Math.atan2(-m31, m11);
                this.elements[2] = 0;
            }
        } else if (order === 'ZXY') {
            this.elements[0] = Math.asin(clamp(m32, -1, 1));
            if (Math.abs(m32) < 0.99999) {
                this.elements[1] = Math.atan2(-m31, m33);
                this.elements[2] = Math.atan2(-m12, m22);
            } else {
                this.elements[1] = 0;
                this.elements[2] = Math.atan2(m21, m11);
            }
        } else if (order === 'ZYX') {
            this.elements[1] = Math.asin(-clamp(m31, -1, 1));
            if (Math.abs(m31) < 0.99999) {
                this.elements[0] = Math.atan2(m32, m33);
                this.elements[2] = Math.atan2(m21, m11);
            } else {
                this.elements[0] = 0;
                this.elements[2] = Math.atan2(-m12, m22);
            }
        } else if (order === 'YZX') {
            this.elements[2] = Math.asin(clamp(m21, -1, 1));
            if (Math.abs(m21) < 0.99999) {
                this.elements[0] = Math.atan2(-m23, m22);
                this.elements[1] = Math.atan2(-m31, m11);
            } else {
                this.elements[0] = 0;
                this.elements[1] = Math.atan2(m13, m33);
            }
        } else if (order === 'XZY') {
            this.elements[2] = Math.asin(-clamp(m12, -1, 1));
            if (Math.abs(m12) < 0.99999) {
                this.elements[0] = Math.atan2(m32, m22);
                this.elements[1] = Math.atan2(m13, m11);
            } else {
                this.elements[0] = Math.atan2(-m23, m33);
                this.elements[1] = 0;
            }
        } else {
            log.warn('Euler fromMat4() unsupported order: ' + order);
        }

        this.updateDegrees();
        return this;
    },
    /**
     * Creates a euler from the given quat.
     * @param  {Quaternion} quat
     * @param  {String} [order=this.order] 旋转顺序,默认为当前Euler实例的order
     * @return {Euler} this
     */
    fromQuat(quat, order) {
        tempMatrix.fromQuat(quat);
        return this.fromMat4(tempMatrix, order);
    },

    updateDegrees() {
        this._degX = this.elements[0] * RAD2DEG;
        this._degY = this.elements[1] * RAD2DEG;
        this._degZ = this.elements[2] * RAD2DEG;
        return this;
    },

    updateRadians() {
        this.elements[0] = this._degX * DEG2RAD;
        this.elements[1] = this._degY * DEG2RAD;
        this.elements[2] = this._degZ * DEG2RAD;
        return this;
    },

    /**
     * 角度 X, 角度制
     * @type {Number}
     */
    degX: {
        get() {
            return this._degX;
        },
        set(value) {
            this._degX = value;
            this.elements[0] = value * DEG2RAD;
        }
    },

    /**
     * 角度 Y, 角度制
     * @type {Number}
     */
    degY: {
        get() {
            return this._degY;
        },
        set(value) {
            this._degY = value;
            this.elements[1] = value * DEG2RAD;
        }
    },

    /**
     * 角度 Z, 角度制
     * @type {Number}
     */
    degZ: {
        get() {
            return this._degZ;
        },
        set(value) {
            this._degZ = value;
            this.elements[2] = value * DEG2RAD;
        }
    },

    /**
     * 角度 X, 弧度制
     * @type {Number}
     */
    x: {
        get() {
            return this.elements[0];
        },
        set(value) {
            this.elements[0] = value;
            this._degX = value * RAD2DEG;
        }
    },
    /**
     * 角度 Y, 弧度制
     * @type {Number}
     */
    y: {
        get() {
            return this.elements[1];
        },
        set(value) {
            this.elements[1] = value;
            this._degY = value * RAD2DEG;
        }
    },
    /**
     * 角度 Z, 弧度制
     * @type {Number}
     */
    z: {
        get() {
            return this.elements[2];
        },
        set(value) {
            this.elements[2] = value;
            this._degZ = value * RAD2DEG;
        }
    }
});

export default Euler;