Home Reference Source

src/Euler.js

import { Matrix3 } from "./Matrix3.js";
import { Quaternion } from "./Quaternion.js";
import { RotationOrder } from "./RotationOrder.js";
import { Vector3 } from "./Vector3.js";

/**
 * Clamps the given value.
 *
 * @private
 * @param {Number} value - The value.
 * @param {Number} min - The lower limit.
 * @param {Number} max - The upper limit.
 * @return {Number} The clamped value.
 */

function clamp(value, min, max) {

	return Math.max(Math.min(value, max), min);

}

/**
 * A matrix.
 *
 * @type {Matrix3}
 * @private
 */

const m = new Matrix3();

/**
 * A quaternion.
 *
 * @type {Quaternion}
 * @private
 */

const q = new Quaternion();

/**
 * Euler angles.
 */

export class Euler {

	/**
	 * Constructs a new set of Euler angles.
	 *
	 * @param {Number} [x=0] - The rotation around the X-axis.
	 * @param {Number} [y=0] - The rotation around the Y-axis.
	 * @param {Number} [z=0] - The rotation around the Z-axis.
	 */

	constructor(x = 0, y = 0, z = 0) {

		/**
		 * The rotation around the X-axis.
		 *
		 * @type {Number}
		 */

		this.x = x;

		/**
		 * The rotation around the Y-axis.
		 *
		 * @type {Number}
		 */

		this.y = y;

		/**
		 * The rotation around the Z-axis.
		 *
		 * @type {Number}
		 */

		this.z = z;

		/**
		 * The rotation order.
		 *
		 * @type {RotationOrder}
		 * @default Euler.defaultOrder
		 */

		this.order = Euler.defaultOrder;

	}

	/**
	 * Sets the Euler angles and rotation order.
	 *
	 * @param {Number} x - The rotation around the X-axis.
	 * @param {Number} y - The rotation around the Y-axis.
	 * @param {Number} z - The rotation around the Z-axis.
	 * @param {Number} order - The rotation order.
	 * @return {Euler} This set of Euler angles.
	 */

	set(x, y, z, order) {

		this.x = x;
		this.y = y;
		this.z = z;
		this.order = order;

		return this;

	}

	/**
	 * Copies the values of another set of Euler angles.
	 *
	 * @param {Euler} e - A set of Euler angles.
	 * @return {Euler} This set of Euler angles.
	 */

	copy(e) {

		this.x = e.x;
		this.y = e.y;
		this.z = e.z;
		this.order = e.order;

		return this;

	}

	/**
	 * Clones this set of Euler angles.
	 *
	 * @return {Euler} A clone of this set of Euler angles.
	 */

	clone() {

		return new this.constructor(this.x, this.y, this.z, this.order);

	}

	/**
	 * Copies angles and the rotation order from an array.
	 *
	 * @param {Number[]} array - An array.
	 * @param {Number} offset - An offset.
	 * @return {Euler} This set of Euler angles.
	 */

	fromArray(array, offset = 0) {

		this.x = array[offset];
		this.y = array[offset + 1];
		this.z = array[offset + 2];
		this.order = array[offset + 3];

		return this;

	}

	/**
	 * Stores this set of Euler angles and the rotation order in an array.
	 *
	 * @param {Array} [array] - A target array.
	 * @param {Number} offset - An offset.
	 * @return {Number[]} The array.
	 */

	toArray(array = [], offset = 0) {

		array[offset] = this.x;
		array[offset + 1] = this.y;
		array[offset + 2] = this.z;
		array[offset + 3] = this.order;

		return array;

	}

	/**
	 * Stores this set of Euler angles in a vector.
	 *
	 * @param {Vector3} [vector] - A target vector. If none is provided, a new one will be created.
	 * @return {Vector3} The vector.
	 */

	toVector3(vector = new Vector3()) {

		return vector.set(this.x, this.y, this.z);

	}

	/**
	 * Copies the rotation from a given matrix.
	 *
	 * @param {Matrix4} m - A rotation matrix. The upper 3x3 is assumed to be a pure rotation matrix (i.e. unscaled).
	 * @param {RotationOrder} [order] - An override rotation order.
	 * @return {Euler} This set of Euler angles.
	 */

	setFromRotationMatrix(m, order = this.order) {

		const te = m.elements;
		const m00 = te[0], m01 = te[4], m02 = te[8];
		const m10 = te[1], m11 = te[5], m12 = te[9];
		const m20 = te[2], m21 = te[6], m22 = te[10];

		const THRESHOLD = 1.0 - 1e-7;

		switch(order) {

			case RotationOrder.XYZ: {

				this.y = Math.asin(clamp(m02, -1, 1));

				if(Math.abs(m02) < THRESHOLD) {

					this.x = Math.atan2(-m12, m22);
					this.z = Math.atan2(-m01, m00);

				} else {

					this.x = Math.atan2(m21, m11);
					this.z = 0;

				}

				break;

			}

			case RotationOrder.YXZ: {

				this.x = Math.asin(-clamp(m12, -1, 1));

				if(Math.abs(m12) < THRESHOLD) {

					this.y = Math.atan2(m02, m22);
					this.z = Math.atan2(m10, m11);

				} else {

					this.y = Math.atan2(-m20, m00);
					this.z = 0;

				}

				break;

			}

			case RotationOrder.ZXY: {

				this.x = Math.asin(clamp(m21, -1, 1));

				if(Math.abs(m21) < THRESHOLD) {

					this.y = Math.atan2(-m20, m22);
					this.z = Math.atan2(-m01, m11);

				} else {

					this.y = 0;
					this.z = Math.atan2(m10, m00);

				}

				break;

			}

			case RotationOrder.ZYX: {

				this.y = Math.asin(-clamp(m20, -1, 1));

				if(Math.abs(m20) < THRESHOLD) {

					this.x = Math.atan2(m21, m22);
					this.z = Math.atan2(m10, m00);

				} else {

					this.x = 0;
					this.z = Math.atan2(-m01, m11);

				}

				break;

			}

			case RotationOrder.YZX: {

				this.z = Math.asin(clamp(m10, -1, 1));

				if(Math.abs(m10) < THRESHOLD) {

					this.x = Math.atan2(-m12, m11);
					this.y = Math.atan2(-m20, m00);

				} else {

					this.x = 0;
					this.y = Math.atan2(m02, m22);

				}

				break;

			}

			case RotationOrder.XZY: {

				this.z = Math.asin(-clamp(m01, -1, 1));

				if(Math.abs(m01) < THRESHOLD) {

					this.x = Math.atan2(m21, m11);
					this.y = Math.atan2(m02, m00);

				} else {

					this.x = Math.atan2(-m12, m22);
					this.y = 0;

				}

				break;

			}

		}

		this.order = order;

		return this;

	}

	/**
	 * Copies the rotation from a given quaternion.
	 *
	 * @param {Matrix4} q - A quaternion.
	 * @param {RotationOrder} [order] - An override rotation order.
	 * @return {Euler} This set of Euler angles.
	 */

	setFromQuaternion(q, order) {

		m.makeRotationFromQuaternion(q);

		return this.setFromRotationMatrix(m, order);

	}

	/**
	 * Copies the rotation from a given vector.
	 *
	 * @param {Matrix4} v - A vector.
	 * @param {RotationOrder} [order] - A rotation order.
	 * @return {Euler} This set of Euler angles.
	 */

	setFromVector3(v, order = this.order) {

		return this.set(v.x, v.y, v.z, order);

	}

	/**
	 * Reorder the rotation angles.
	 *
	 * WARNING: this operation discards revolution information!
	 *
	 * @param {RotationOrder} newOrder - The new rotation order.
	 * @return {Euler} This set of Euler angles.
	 */

	reorder(newOrder) {

		q.setFromEuler(this);

		return this.setFromQuaternion(q, newOrder);

	}

	/**
	 * Checks if this set of Euler angles equals the given one.
	 *
	 * @param {Euler} e - Euler angles.
	 * @return {Boolean} Whether this set of Euler angles equals the given one.
	 */

	equals(e) {

		return (e.x === this.x && e.y === this.y && e.z === this.z && e.order === this.order);

	}

	/**
	 * The default rotation order.
	 *
	 * @type {RotationOrder}
	 * @final
	 */

	static get defaultOrder() {

		return RotationOrder.XYZ;

	}

}