Source: objects/Object3D.js

import { generateUUID } from '../base.js';
import { SHADOW_TYPE } from '../const.js';
import { Vector3 } from '../math/Vector3.js';
import { Euler } from '../math/Euler.js';
import { Quaternion } from '../math/Quaternion.js';
import { Matrix4 } from '../math/Matrix4.js';

var _object3DId = 0;

var _mat4_1 = new Matrix4();

/**
 * This is the base class for most objects in zen3d
 * and provides a set of properties and methods for manipulating objects in 3D space.
 * @constructor
 * @memberof zen3d
 */
function Object3D() {
	Object.defineProperty(this, 'id', { value: _object3DId++ });

	/**
     * UUID of this object instance.
     * This gets automatically assigned, so this shouldn't be edited.
     * @type {string}
     */
	this.uuid = generateUUID();

	/**
     * Optional name of the object (doesn't need to be unique).
     * @type {string}
     * @default ""
     */
	this.name = "";

	/**
     * Type of the object.
     * Set by Subclass.
     * @type {zen3d.OBJECT_TYPE}
     */
	this.type = "";

	/**
     * A Vector3 representing the object's local position.
     * @type {zen3d.Vector3}
     * @default Vector3(0, 0, 0)
     */
	this.position = new Vector3();

	/**
     * The object's local scale.
     * @type {zen3d.Vector3}
     * @default Vector3(1, 1, 1)
     */
	this.scale = new Vector3(1, 1, 1);

	/**
     * Object's local rotation as an {@link zen3d.Euler}, in radians.
     * @type {zen3d.Euler}
     * @default Euler(0, 0, 0)
     */
	this.euler = new Euler();

	/**
     * Object's local rotation as a {@link zen3d.Quaternion}.
     * @type {zen3d.Quaternion}
     * @default Quaternion(0, 0, 0, 1)
     */
	this.quaternion = new Quaternion();

	// bind euler and quaternion
	var euler = this.euler, quaternion = this.quaternion;
	euler.onChange(function() {
		quaternion.setFromEuler(euler, false);
	});
	quaternion.onChange(function() {
		euler.setFromQuaternion(quaternion, undefined, false);
	});

	/**
     * The local transform matrix.
     * @type {zen3d.Matrix4}
     */
	this.matrix = new Matrix4();

	/**
     * The global transform of the object.
     * If the Object3D has no parent, then it's identical to the local transform {@link zen3d.Object3D#matrix}.
     * @type {zen3d.Matrix4}
     */
	this.worldMatrix = new Matrix4();

	/**
     * Object's parent in the scene graph.
     * An object can have at most one parent.
     * @type {zen3d.Object3D[]}
     */
	this.children = new Array();

	/**
     * Object's parent in the scene graph.
     * An object can have at most one parent.
     * @type {zen3d.Object3D}
     */
	this.parent = null;

	/**
     * Whether the object gets rendered into shadow map.
     * @type {boolean}
     * @default false
     */
	this.castShadow = false;

	/**
     * Whether the material receives shadows.
     * @type {boolean}
     * @default false
     */
	this.receiveShadow = false;

	/**
     * Defines shadow map type.
     * @type {zen3d.SHADOW_TYPE}
     * @default SHADOW_TYPE.PCF3_SOFT
     */
	this.shadowType = SHADOW_TYPE.PCF3_SOFT;

	/**
     * When this is set, it checks every frame if the object is in the frustum of the camera before rendering the object.
     * Otherwise the object gets rendered every frame even if it isn't visible.
     * @type {boolean}
     * @default true
     */
	this.frustumCulled = true;

	/**
     * Object gets rendered if true.
     * @type {boolean}
     * @default true
     */
	this.visible = true;

	/**
     * This value allows the default rendering order of scene graph objects to be overridden although opaque and transparent objects remain sorted independently.
     * Sorting is from lowest to highest renderOrder.
     * @type {number}
     * @default 0
     */
	this.renderOrder = 0;

	/**
     * An object that can be used to store custom data about the {@link zen3d.Object3D}.
     * It should not hold references to functions as these will not be cloned.
     * @type {Object}
     * @default {}
     */
	this.userData = {};

	/**
	 * When this is set, it calculates the matrix of position, (rotation or quaternion) and scale every frame and also recalculates the worldMatrix property.
	 * @type {boolean}
	 * @default true
	 */
	this.matrixAutoUpdate = true;

	/**
	 * When this is set, it calculates the matrix in that frame and resets this property to false.
	 * @type {boolean}
	 * @default true
	 */
	this.matrixNeedsUpdate = true;

	/**
	 * When this is set, it calculates the world matrix in that frame and resets this property to false.
	 * @type {boolean}
	 * @default true
	 */
	this.worldMatrixNeedsUpdate = true;
}

Object.assign(Object3D.prototype, /** @lends zen3d.Object3D.prototype */{

	/**
     * An optional callback that is executed immediately before the Object3D is rendered.
     * @type {Function}
     */
	onBeforeRender: function () {},

	/**
     * An optional callback that is executed immediately after the Object3D is rendered.
     * @type {Function}
     */
	onAfterRender: function () {},

	/**
     * Add object as child of this object.
     * @param {zen3d.Object3D} object
     */
	add: function(object) {
		if (object === this) {
			console.error("Object3D.add: object can't be added as a child of itself.", object);
			return;
		}

		if (object.parent !== null) {
			object.parent.remove(object);
		}

		object.parent = this;
		this.children.push(object);

		object.worldMatrixNeedsUpdate = true;
	},

	/**
     * Remove object as child of this object.
     * @param {zen3d.Object3D} object
     */
	remove: function(object) {
		var index = this.children.indexOf(object);
		if (index !== -1) {
			object.parent = null;
			this.children.splice(index, 1);

			object.worldMatrixNeedsUpdate = true;
		}
	},

	/**
     * Searches through the object's children and returns the first with a matching name.
     * Note that for most objects the name is an empty string by default.
     * You will have to set it manually to make use of this method.
     * @param {string} name - String to match to the children's {@link zen3d.Object3D#name} property.
     * @return {zen3d.Object3D}
     */
	getObjectByName: function(name) {
		return this.getObjectByProperty('name', name);
	},

	/**
     * Searches through the object's children and returns the first with a property that matches the value given.
     * @param {string} name - the property name to search for.
     * @param {number} value - value of the given property.
     * @return {zen3d.Object3D}
     */
	getObjectByProperty: function(name, value) {
		if (this[name] === value) return this;

		for (var i = 0, l = this.children.length; i < l; i++) {
			var child = this.children[i];
			var object = child.getObjectByProperty(name, value);

			if (object !== undefined) {
				return object;
			}
		}

		return undefined;
	},

	/**
     * Update the local transform.
     */
	updateMatrix: function(force) {
		if (this.matrixAutoUpdate || this.matrixNeedsUpdate) {
			this.matrix.transform(this.position, this.scale, this.quaternion);

			this.matrixNeedsUpdate = false;
			this.worldMatrixNeedsUpdate = true;
		}

		if (this.worldMatrixNeedsUpdate || force) {
			this.worldMatrix.copy(this.matrix);

			if (this.parent) {
				var parentMatrix = this.parent.worldMatrix;
				this.worldMatrix.premultiply(parentMatrix);
			}

			this.worldMatrixNeedsUpdate = false;
			force = true;
		}

		var children = this.children;
		for (var i = 0, l = children.length; i < l; i++) {
			children[i].updateMatrix(force);
		}
	},

	/**
     * Returns a vector representing the direction of object's positive z-axis in world space.
     * This call must be after {@link zen3d.Object3D#updateMatrix}.
     * @method
     * @param {Vector3} [optionalTarget=] — the result will be copied into this Vector3.
     * @return {Vector3} - the result.
     */
	getWorldDirection: function(optionalTarget) {
		optionalTarget = optionalTarget || new Vector3();
		var e = this.worldMatrix.elements;
		return optionalTarget.set(e[8], e[9], e[10]).normalize();
	},

	/**
     * Rotates the object to face a point in local space.
     * @method
     * @param {Vector3} target - A vector representing a position in local space.
     * @param {Vector3} up — A vector representing the up direction in local space.
     */
	lookAt: function(target, up) {
		_mat4_1.lookAtRH(target, this.position, up);
		this.quaternion.setFromRotationMatrix(_mat4_1);
	},

	/**
     * Method to get intersections between a casted ray and this object.
     * @abstract
     * @param {Raycaster} raycaster - The {@link zen3d.Raycaster} instance.
     * @param {Array} intersects - output intersects array.
     */
	raycast: function(raycaster, intersects) {

	},

	/**
     * Executes the callback on this object and all descendants.
     * @param {Function} callback - A function with as first argument an object3D object.
     */
	traverse: function (callback) {
		callback(this);

		var children = this.children;
		for (var i = 0, l = children.length; i < l; i++) {
			children[i].traverse(callback);
		}
	},

	/**
     * Returns a clone of this object and optionally all descendants.
     * @param {Function} [recursive=true] - if true, descendants of the object are also cloned.
     * @return {zen3d.Object3D}
     */
	clone: function (recursive) {
		return new this.constructor().copy(this, recursive);
	},

	/**
     * Copy the given object into this object.
     * @param {zen3d.Object3D} source - The object to be copied.
     * @param {Function} [recursive=true] - if true, descendants of the object are also copied.
     * @return {zen3d.Object3D}
     */
	copy: function(source, recursive) {
		if (recursive === undefined) recursive = true;

		this.name = source.name;

		this.type = source.type;

		this.position.copy(source.position);
		this.quaternion.copy(source.quaternion);
		this.scale.copy(source.scale);

		this.matrix.copy(source.matrix);
		this.worldMatrix.copy(source.worldMatrix);

		this.castShadow = source.castShadow;
		this.receiveShadow = source.receiveShadow;

		this.frustumCulled = source.frustumCulled;

		this.userData = JSON.parse(JSON.stringify(source.userData));

		if (recursive === true) {
			for (var i = 0; i < source.children.length; i++) {
				var child = source.children[i];
				this.add(child.clone());
			}
		}

		return this;
	}

});

export { Object3D };