Source: objects/NodeObject.js

import { MathUtils } from '../math/MathUtils';

let _position = MathUtils.createVec3();
let _scale = MathUtils.createVec3();
let _quat_0 = MathUtils.createQuat();
let _quat_1 = MathUtils.createQuat();
let _angles = MathUtils.createVec3();
let _mat4_0 = MathUtils.createMat4();
let _mat4_1 = MathUtils.createMat4();
let _mat4_2 = MathUtils.createMat4();

/**
 * @class NodeObject
 * The node object.
 * @memberof THING
 * @public
 */
class NodeObject {

	/**
	 * The sub node object that to get meshes from renderable object in scene.
	 * @param {Object} param The initial parameters.
	 */
	constructor(param = {}) {
		let node = param['node'];
		let renderableNode = param['renderableNode'];

		this._name = node.name;

		this._position = [];
		this._quaternion = [];
		this._scale = [];
		MathUtils.decomposeFromMat4(node.matrix, this._position, this._quaternion, this._scale);

		this._renderableNode = renderableNode;
		this._node = node;
	}

	// #region Private

	_updateMatrix() {
		let node = this._node;

		let matrix = MathUtils.composeToMat4(this._position, this._quaternion, this._scale, _mat4_0);
		this._renderableNode.setSubNodeMatrix(node, matrix);
	}

	// #endregion

	// #region Coordinates Transform

	/**
	 * Convert local position to self position.
	 * @param {Array<Number>} position The local position.
	 * @param {Boolean} [ignoreScale=false] True indicates ignore scale factor.
	 * @returns {Array<Number>}
	 * @public
	 */
	localToSelf(position, ignoreScale = false) {
		let worldPosition = this.localToWorld(position, ignoreScale);

		return this.worldToSelf(worldPosition, ignoreScale);
	}

	/**
	 * Convert self position to local position.
	 * @param {Array<Number>} position The self position.
	 * @param {Boolean} [ignoreScale=false] True indicates ignore scale factor.
	 * @returns {Array<Number>}
	 * @public
	 */
	selfToLocal(position, ignoreScale = false) {
		let worldPosition = this.selfToWorld(position, ignoreScale);

		return this.worldToLocal(worldPosition, ignoreScale);
	}

	/**
	 * Convert world position to self position.
	 * @param {Array<Number>} position The world position.
	 * @param {Boolean} [ignoreScale=false] True indicates ignore scale factor.
	 * @returns {Array<Number>}
	 * @public
	 */
	worldToSelf(position, ignoreScale = false) {
		return MathUtils.worldToSelf(this.matrixWorld, position, ignoreScale);
	}

	/**
	 * Convert self position to world position.
	 * @param {Array<Number>} position The self position.
	 * @param {Boolean} [ignoreScale=false] True indicates ignore scale factor.
	 * @returns {Array<Number>}
	 * @public
	 */
	selfToWorld(position, ignoreScale = false) {
		return MathUtils.selfToWorld(this.matrixWorld, position, ignoreScale);
	}

	/**
	 * Convert local position to world position.
	 * @param {Array<Number>} position The local position.
	 * @param {Boolean} [ignoreScale=false] True indicates ignore scale factor.
	 * @returns {Array<Number>}
	 * @public
	 */
	localToWorld(position, ignoreScale = false) {
		let root = this._node.root;
		if (root) {
			return root.selfToWorld(_position, position, ignoreScale);
		}
		else {
			return position;
		}
	}

	/**
	 * Convert world position to local position.
	 * @param {Array<Number>} position The world position.
	 * @param {Boolean} [ignoreScale=false] True indicates ignore scale factor.
	 * @returns {Array<Number>}
	 * @public
	 */
	worldToLocal(position, ignoreScale = false) {
		let root = this._node.root;
		if (root) {
			return root.worldToSelf(_position, position, ignoreScale);
		}
		else {
			return position;
		}
	}

	// #endregion

	// #region Common

	get name() {
		return this._name;
	}

	get node() {
		return this._node;
	}

	get isRenderable() {
		return !!this._node.isRenderable;
	}

	// #endregion

	// #region Transform

	rotateX(angle) {
		let quat = MathUtils.getQuatFromAngles([angle, 0, 0]);
		MathUtils.quat.multiply(quat, this._quaternion, quat);

		this.localQuaternion = quat;
	}

	rotateY(angle) {
		let quat = MathUtils.getQuatFromAngles([0, angle, 0]);
		MathUtils.quat.multiply(quat, this._quaternion, quat);

		this.localQuaternion = quat;
	}

	rotateZ(angle) {
		let quat = MathUtils.getQuatFromAngles([0, 0, angle]);
		MathUtils.quat.multiply(quat, this._quaternion, quat);

		this.localQuaternion = quat;
	}

	/**
	 * Get the up direction of world space.
	 * @type {Array<Number>}
	 * @private
	 */
	get up() {
		const matrixWorld = this.matrixWorld;
		return MathUtils.normalizeVector([matrixWorld[4], matrixWorld[5], matrixWorld[6]]);
	}

	/**
	 * Get the forward direction in world space.
	 * @type {Array<Number>}
	 * @private
	 */
	get forward() {
		const matrixWorld = this.matrixWorld;
		return MathUtils.normalizeVector([matrixWorld[8], matrixWorld[9], matrixWorld[10]]);
	}

	/**
	 * Get the cross direction in world space.
	 * @type {Array<Number>}
	 * @private
	 */
	get cross() {
		let up = this.up;
		let forward = this.forward;

		let target = [0, 0, 0];
		MathUtils.vec3.cross(target, up, forward);
		MathUtils.vec3.normalize(target, target);

		return target;
	}

	/**
	 * Get/Set local position.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].localPosition = [10, 10, 10];
	 * @public
	 */
	get localPosition() {
		return this._position;
	}
	set localPosition(value) {
		this._position = value.slice(0);

		this._updateMatrix();
	}

	/**
	 * Get/Set local quaternion.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].localQuaternion = THING.Math.getQuatFromAngles([45, 45, 45]);
	 * @public
	 */
	get localQuaternion() {
		return this._quaternion;
	}
	set localQuaternion(value) {
		this._quaternion = value.slice(0);

		this._updateMatrix();
	}

	/**
	 * Get/Set local angles.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].localAngles = [45, 45, 45];
	 * @public
	 */
	get localAngles() {
		return MathUtils.getAnglesFromQuat(this.localQuaternion, _angles);
	}
	set localAngles(value) {
		this.localQuaternion = MathUtils.getQuatFromAngles(value, _quat_1);
	}

	/**
	 * Get/Set local scale.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].localScale = [3, 3, 3];
	 * @public
	 */
	get localScale() {
		return this._scale;
	}
	set localScale(value) {
		this._scale = value.slice(0);

		this._updateMatrix();
	}

	/**
	 * Get/Set world position.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].position = [10, 10, 10];
	 * @public
	 */
	get position() {
		MathUtils.decomposeFromMat4(this.matrixWorld, _position, _quat_0, _scale);

		return _position;
	}
	set position(value) {
		MathUtils.decomposeFromMat4(this.matrixWorld, _position, _quat_0, _scale);
		let matrixWorld = MathUtils.composeToMat4(value, _quat_0, _scale, _mat4_2);

		this.matrixWorld = matrixWorld;
	}

	/**
	 * Get/Set world quaternion.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].quaternion = THING.Math.getQuatFromAngles([45, 45, 45]);
	 * @public
	 */
	get quaternion() {
		MathUtils.decomposeFromMat4(this.matrixWorld, _position, _quat_0, _scale);

		return _quat_0;
	}
	set quaternion(value) {
		MathUtils.decomposeFromMat4(this.matrixWorld, _position, _quat_0, _scale);
		let matrixWorld = MathUtils.composeToMat4(_position, value, _scale, _mat4_2);

		this.matrixWorld = matrixWorld;
	}

	/**
	 * Get/Set world angles.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].angles = [45, 45, 45];
	 * @public
	 */
	get angles() {
		return MathUtils.getAnglesFromQuat(this.quaternion, _angles);
	}
	set angles(value) {
		this.quaternion = MathUtils.getQuatFromAngles(value, _quat_1);
	}

	/**
	 * Get/Set world rotation.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].rotation = [45, 45, 45];
	 * @public
	 */
	get rotation() {
		return MathUtils.getAnglesFromQuat(this.quaternion, _angles);
	}
	set rotation(value) {
		this.quaternion = MathUtils.getQuatFromAngles(value, _quat_1);
	}

	/**
	 * Get/Set world scale.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].scale = [3, 3, 3];
	 * @public
	 */
	get scale() {
		MathUtils.decomposeFromMat4(this.matrixWorld, _position, _quat_0, _scale);

		return _scale;
	}
	set scale(value) {
		MathUtils.decomposeFromMat4(this.matrixWorld, _position, _quat_0, _scale);
		let matrixWorld = MathUtils.composeToMat4(_position, _quat_0, value, _mat4_2);

		this.matrixWorld = matrixWorld;
	}

	/**
	 * Get/Set matrix.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].matrix = THING.Math.composeToMat4([10, 10, 10], [0, 0, 0, 1], [2, 2, 2]);
	 * @public
	 */
	get matrix() {
		let node = this._node;

		return node.matrix;
	}
	set matrix(value) {
		let node = this._node;
		this._renderableNode.setSubNodeMatrix(node, value);

		MathUtils.decomposeFromMat4(value, this._position, this._quaternion, this._scale);
	}

	/**
	 * Get/Set matrix world.
	 * @type {Array<Number>}
	 * @example
	 * 	object.body.nodes[0].matrixWorld = THING.Math.composeToMat4([10, 10, 10], [0, 0, 0, 1], [2, 2, 2]);
	 * @public
	 */
	get matrixWorld() {
		let node = this._node;

		let root = node.root;
		root.updateMatrixWorld();
		root.getMatrixWorld(_mat4_0);

		return MathUtils.mat4.multiply(_mat4_0, _mat4_0, node.matrix).slice(0);
	}
	set matrixWorld(value) {
		let node = this._node;

		let root = node.root;
		root.updateMatrixWorld();
		root.getMatrixWorld(_mat4_0);

		MathUtils.mat4.invert(_mat4_1, _mat4_0);
		MathUtils.mat4.multiply(_mat4_1, _mat4_1, value);

		this._renderableNode.setSubNodeMatrix(node, _mat4_1);

		MathUtils.decomposeFromMat4(_mat4_1, this._position, this._quaternion, this._scale);
	}

	// #endregion

	get isSubNodeObject() {
		Utils.warn(`Please use .isNodeObject`);

		return true;
	}

	get isNodeObject() {
		return true;
	}

}

export { NodeObject }