Source: objects/Object3D.js

import { ResolvablePromise, TypedObject, ObjectAttributes } from '@uino/base-thing';
import { BaseObject } from './BaseObject';
import { BodyObject } from './BodyObject';
import { MathUtils } from '../math/MathUtils';
import { Utils } from '../common/Utils';
import { Style } from '../resources/Style';
import { HelperComponent } from '../components/HelperComponent';
import { TransformComponent } from '../components/TransformComponent';
import { RenderComponent } from '../components/RenderComponent';
import { LerpComponent } from '../components/LerpComponent';
import { BoundingComponent } from '../components/BoundingComponent';
import { LevelComponent } from '../components/LevelComponent';
import { ActionGroupComponent } from '../components/ActionGroupComponent';
import { BlueprintComponent } from '../components/BlueprintComponent';
import { ColliderComponent } from '../components/ColliderComponent';
import { RelationshipComponent } from '../components/RelationshipComponent';
import { TaskExecutorComponent } from '../components/TaskExecutorComponent';
import { EventType, InheritType } from '../const';
import { MonitorDataComponent } from '../components/MonitorDataComponent';

const __ = {
	private: Symbol('private'),
}

// [0-7] index is reserved for BaseObject, so index starts from 8
const Flag = {
	// Common
	Initializing: 1 << 8,
	// State
	Pickable: 1 << 9,
	Active: 1 << 10,
	// On/Off
	KeepSize: 1 << 11
}

const registerComponentParam = { isResident: true };

let _emptyTags = [];

function _getStyleInheritance(object) {
	if (!object.hasInherit()) {
		return InheritType.Normal;
	}

	return object.inherit.style;
}

/**
 * @class Object3D
 * The base object 3D.
 * @memberof THING
 * @extends THING.BaseObject
 * @public
 */
class Object3D extends BaseObject {

	static defaultTagArray = ['Object3D'];

	// The resource state
	static ResourceState = {
		Error: -1,
		Ready: 1,
		Loaded: 2,
		Loading: 3,
	};

	/**
	 * @typedef {BaseObjectInitialOptions} Object3DInitialOptions
	 * @property {Array<Number>} localPosition? The local position.
	 * @property {Array<Number>} localScale? The local scale.
	 * @property {Array<Number>} localAngels? The local angles.
	 * @property {Array<Number>} position? The world position.
	 * @property {Array<Number>} scale? The world scale.
	 * @property {Array<Number>} angels? The world angles.
	 */

	/**
	 * The base object in scene.
	 * @param {Object3DInitialOptions} param The initial parameters.
	 */
	constructor(param = {}) {
		super(param);

		this[__.private] = {};
		let _private = this[__.private];

		// Common
		_private.tags = null;
		_private.external = null;

		// Inheritance
		_private.inherit = null;

		// Render
		_private.style = undefined; // null indicates it's not renderable object
		_private.styleObjectAttributes = [];
		_private.renderLayout = null;

		// Resource
		_private.copyPromise = null;
		_private.resource = {
			url: '',
		};

		_private.options = Object.assign({}, param);

		// Setup callbacks
		_private.onError = Utils.parseValue(param['onError'], param['error']);
		_private.onComplete = Utils.parseValue(param['onComplete'], param['complete']);
		_private.onSyncComplete = Utils.parseValue(param['onSyncComplete'], param['syncComplete']);
		_private.onSyncBeforeDestroy = Utils.parseValue(param['onSyncBeforeDestroy'], param['syncBeforeDestroy']);

		// Sync complete callbacks
		_private.syncCompleteCallbacks = [];

		// Setup flags
		this._flags.enable(Flag.Initializing | Flag.Pickable | Flag.Active, true);

		// Some members must be initialized in constructor, so would declarate here
		this._resourceState = this._resourceState || Object3D.ResourceState.Ready;
		this._loadPromise = this._loadPromise || null;

		// Setup before add object
		this.onSetupAttributes(param);
		this.onSetupTransform(param);
		this.onSetupState(param);
		this.onSetupStyle(param);
		this.onSetupResource(param);

		// Setup completed
		this.onSetupComplete(param);

		// Notify completed
		this.onCreate(param);
	}

	// #region Private

	_createResourcePromise() {
		// Object has resources, unload it first
		if (this.loaded) {
			return this.unloadResource(false);
		}
		// Object is loading resources
		else if (this.loading) {
			return this.waitForComplete();
		}
		// Object is in unloaded state
		else {
			return Promise.resolve();
		}
	}

	_updateResource(value) {
		this.setResource(value);

		return this.reloadResource(false, { url: value.url });
	}

	_unloadResource(recursive) {
		// Get the initial local bounding box
		let initialLocalBoundingBox = this.initialLocalBoundingBox || this._body.getLocalBoundingBox();

		// Notify all unloadable components
		let unloadableComponents = this.unloadableComponents;
		if (unloadableComponents) {
			unloadableComponents.forEach(component => {
				component.onUnloadResource();
			});
		}

		// When unload resource
		this.onUnloadResource();

		// Unload body object resource
		this._body.unloadResource((type) => {
			return this.onCreateBodyNode(type);
		});

		// Continue to unload children resources
		if (recursive) {
			if (this.hasChildren()) {
				this.children.forEach(child => {
					child.unloadResource(recursive);
				});
			}
		}

		// Resume the initial local bounding box
		this.initialLocalBoundingBox = initialLocalBoundingBox;
	}

	_onLoadResource(options, callback, error) {
		// Had loaded resource
		if (this.loaded) {
			callback();
		}
		else {
			// Prevent object had been destroyed
			if (this.destroyed) {
				error({ object: this, desc: 'The object had been destroyed' });
			}
			else {
				this.onLoadResource(options, () => {
					if (!this.destroyed) {
						this.onSetupState(options);
						this.taskExecutor.then(callback, error);
					}
				}, error);
			}
		}
	}

	_loadSelfResource(options, resolve, reject) {
		if (this.loading) {
			this.waitForComplete().then(() => {
				resolve();
			}).catch(ev => {
				reject(ev);
			});
		}
		else {
			this._onLoadResource(options, resolve, reject);
		}
	}

	_setChildrenAttributeState(inheritName, name, value, recursive) {
		let children = this.children;
		for (let i = 0; i < children.length; i++) {
			let child = children[i];

			if (child.hasInherit()) {
				let inheritType = child.inherit[inheritName];
				switch (inheritType) {
					case InheritType.Normal:
						child['set' + name](value, recursive);
						break;

					case InheritType.Break:
						child['set' + name](value, false);
						break;

					case InheritType.Jump:
						child.children.traverse(object => {
							object['set' + name](value, recursive);
						});
						break;

					default:
						break;
				}
			}
			else {
				child['set' + name](value, recursive);
			}
		}
	}

	_setAttributeState(inheritName, name, value, recursive = false) {
		let bodyNode = this.bodyNode;
		bodyNode.setAttribute(name, value);

		// Change all children if needed
		if (recursive && this.hasChildren()) {
			this._setChildrenAttributeState(inheritName, name, value, recursive);
		}
	}

	_syncAttributes() {
		let flags = this._flags;

		// Sync pickable state
		let pickable = flags.has(Flag.Pickable);
		if (!pickable) {
			this._setAttributeState('pickable', 'Pickable', pickable, false);
		}

		// Sync keep size state
		let keepSize = flags.has(Flag.KeepSize);
		if (keepSize) {
			this.transform.keepSize(true, this.app.camera);
		}

		// Sync renderable node state
		if (this.hasComponent('render')) {
			this.render.syncAttributes();
		}
	}

	_getTempData() {
		return super._getTempData((tempData) => {
			let basePivot = [0.5, 0.5, 0.5];

			if (this.hasComponent('transform')) {
				basePivot = this.transform.getPivot();
			}

			tempData['pivot'] = basePivot.slice(0);
			tempData['basePivot'] = basePivot;
		});
	}

	_isSameResource(value) {
		let _private = this[__.private];

		if (Utils.isString(value)) {
			value = { url: value };
		}

		let resource = _private.resource;

		let children = resource.children;
		if (children && children.length) {
			return false;
		}

		if (resource.url != value.url) {
			return false;
		}

		return true;
	}

	_beforeSetAttribute(callback) {
		// Check whether skip to set attribute
		if (Utils.isFunction(callback)) {
			if (callback(this) === false) {
				return false;
			}
		}

		return true;
	}

	_needCopyComponent(isResident, hasInstance, classType) {
		if (!hasInstance) {
			// Skip for not resident component
			if (!isResident) {
				return false;
			}

			// Skip for not copy with instance component
			if (classType.mustCopyWithInstance) {
				return false;
			}
		}

		return true;
	}

	_copyStyleValues(values) {
		let style = this.body.style;

		for (let key in values) {
			let value = values[key];

			switch (key) {
				case 'attributes':
					for (let attributeKey in value) {
						style.setAttribute(attributeKey, value[attributeKey]);
					}
					break;

				case 'uniforms':
					for (let uniformKey in value) {
						style.setUniform(uniformKey, value[uniformKey]);
					}
					break;

				case 'macros':
					for (let macroKey in value) {
						style.setMacro(macroKey, value[macroKey]);
					}
					break;

				default:
					style[key] = value;
					break;
			}
		}
	}

	_renderComponents() {
		let renderableComponents = this.renderableComponents;
		if (renderableComponents) {
			let _private = this[__.private];

			if (!_private.renderLayout) {
				let that = this;

				_private.renderLayout = {
					getCenter: function () {
						return that.orientedBox.center;
					},
					selfToWorld: function () {
						return that.selfToWorld.apply(that, arguments);
					},
					worldToScreen: function () {
						let camera = that.app.camera;
						return camera.worldToScreen.apply(camera, arguments);
					}
				};
			}

			for (let i = 0, l = renderableComponents.length; i < l; i++) {
				let component = renderableComponents[i];
				if (component.active === false) {
					continue;
				}

				component.onRender(_private.renderLayout);
			}
		}
	}

	_traverseInheritance(callback) {
		this.traverseInheritance(
			(object) => {
				let style = object.body.style;
				if (!style) {
					return;
				}

				callback(style);
			},
			_getStyleInheritance
		);
	}

	_createStyleAttributes(data, attributeKey) {
		let _private = this[__.private];

		let attributes = new ObjectAttributes({
			data,
			onChange: (ev) => {
				let key = ev.key;
				let propName = ev.propName;
				let value = Utils.parseValue(ev.value, null);

				this._traverseInheritance((bodyStyle) => {
					let attribute = bodyStyle[attributeKey];

					if (propName) {
						attribute[propName][key] = value;
					}
					else {
						attribute[key] = value;
					}
				});
			}
		});

		_private.styleObjectAttributes.push(attributes);

		return attributes.values;
	}

	_createStyle() {
		let objectBodyStyle = this.body.style;
		if (!objectBodyStyle) {
			return null;
		}

		let style = {};

		// #region Hook functions

		style.begin = () => {
			this._traverseInheritance((bodyStyle) => {
				bodyStyle.begin();
			});
		};

		style.end = () => {
			this._traverseInheritance((bodyStyle) => {
				bodyStyle.end();
			});
		};

		style.beginDefaultValues = () => {
			this._traverseInheritance((bodyStyle) => {
				bodyStyle.beginDefaultValues();
			});
		};

		style.endDefaultValues = () => {
			this._traverseInheritance((bodyStyle) => {
				bodyStyle.endDefaultValues();
			});
		};

		style.setAttribute = (type, value) => {
			this._traverseInheritance((bodyStyle) => {
				bodyStyle.setAttribute(type, value);
			});
		};

		style.getAttribute = (type) => {
			return this.body.style.getAttribute(type);
		};

		style.setUniform = (name, value) => {
			this._traverseInheritance((bodyStyle) => {
				bodyStyle.setUniform(name, value);
			});
		};

		style.getUniform = (name) => {
			return this.body.style.getUniform(name);
		};

		style.setMacro = (name, value) => {
			this._traverseInheritance((bodyStyle) => {
				bodyStyle.setMacro(name, value);
			});
		};

		style.getMacro = (name) => {
			return this.body.style.getMacro(name);
		};

		style.getUV = (type) => {
			return this.body.style.getUV(type);
		}

		style.getDiffValues = () => {
			return this.body.style.getDiffValues();
		}

		style.setUV = (type, key, value) => {
			this._traverseInheritance((bodyStyle) => {
				bodyStyle.setUV(type, key, value);
			});
		}

		style.copy = (from) => {
			if (from.isObjectStyle) {
				from = from.object.body.style;
			}

			this._traverseInheritance((bodyStyle) => {
				bodyStyle.copy(from);
			});
		}

		style.copyFromProperties = (values) => {
			this._traverseInheritance((bodyStyle) => {
				bodyStyle.copyFromProperties(values);
			});
		}

		style.getProperties = () => {
			return this.body.style.getProperties();
		}

		// #endregion

		// #region Hook attributes

		let edge, effect, attributes, uniforms, macros, uv;

		// Hook get accessor
		Object.defineProperties(style, {
			'isInstancedDrawing': {
				get: () => {
					return this.body.style.isInstancedDrawing;
				}
			},
			'object': {
				get: () => {
					return this;
				}
			},
			'isObjectStyle': {
				get: () => {
					return true;
				}
			},
			'edge': {
				get: () => {
					edge = edge || this._createStyleAttributes(objectBodyStyle.getData('edge'), 'edge');
					return edge;
				},
				set: (value) => {
					this._traverseInheritance((bodyStyle) => {
						bodyStyle.edge = value;
					});
				}
			},
			'effect': {
				get: () => {
					effect = effect || this._createStyleAttributes(objectBodyStyle.getData('effect'), 'effect');
					return effect;
				},
				set: (value) => {
					this._traverseInheritance((bodyStyle) => {
						bodyStyle.effect = value;
					});
				}
			},
			'attributes': {
				get: () => {
					attributes = attributes || this._createStyleAttributes(objectBodyStyle.getData('attributes'), 'attributes');
					return attributes;
				},
				set: (value) => {
					this._traverseInheritance((bodyStyle) => {
						bodyStyle.attributes = value;
					});
				}
			},
			'uniforms': {
				get: () => {
					uniforms = uniforms || this._createStyleAttributes(objectBodyStyle.getData('uniforms'), 'uniforms');
					return uniforms;
				},
				set: (value) => {
					this._traverseInheritance((bodyStyle) => {
						bodyStyle.uniforms = value;
					});
				}
			},
			'macros': {
				get: () => {
					macros = macros || this._createStyleAttributes(objectBodyStyle.getData('macros'), 'macros');
					return macros;
				},
				set: (value) => {
					this._traverseInheritance((bodyStyle) => {
						bodyStyle.macros = value;
					});
				}
			},
			'uv': {
				get: () => {
					uv = uv || this._createStyleAttributes(objectBodyStyle.getData('uv').map, 'uv');
					return uv;
				},
				set: (value) => {
					this._traverseInheritance((bodyStyle) => {
						bodyStyle.uv = value;
					});
				}
			}
		});

		// Collect common accessors keys
		let keys = Object.keys(Style.cDefaultValues);
		keys.push('imageSlotType');
		keys.push('image');
		keys.push('map');
		keys.push('envMap');
		keys.push('alphaMap');
		keys.push('emissiveMap');
		keys.push('normalMap');
		keys.push('colorMapping');
		keys.push('aoMap');

		// Hook common accessors
		keys.forEach(key => {
			Object.defineProperty(style, key, {
				get: () => {
					let value = this.body.style[key];
					if (Utils.isArray(value)) {
						return value.slice(0);
					}
					else {
						return value;
					}
				},
				set: (value) => {
					this._traverseInheritance((bodyStyle) => {
						bodyStyle[key] = value;
					});
				}
			});
		});

		// #endregion

		// #region JSON

		style.toJSON = () => {
			return this.body.style.toJSON();
		};

		// #endregion

		return style;
	}

	_getStyleValues(style) {
		if (style) {
			if (style.isObjectStyle || style.isStyle) {
				return JSON.parse(JSON.stringify(style));
			}
		}

		return style;
	}

	// #endregion

	// #region Overrides

	onSetupParent(param) {
		this.onSetupComponent(param);
		this.onSetupBody(param);

		super.onSetupParent(param);
	}

	onSetupComponent(param) {
		this.addComponent(HelperComponent, 'helper', registerComponentParam);
		this.addComponent(LerpComponent, 'lerp', registerComponentParam);
		this.addComponent(BoundingComponent, 'bounding', registerComponentParam);
		this.addComponent(LevelComponent, 'level', registerComponentParam);
		this.addComponent(ActionGroupComponent, 'actionGroup', registerComponentParam);
		this.addComponent(BlueprintComponent, 'blueprint', registerComponentParam);
		this.addComponent(ColliderComponent, 'collider', registerComponentParam);
		this.addComponent(TransformComponent, 'transform', registerComponentParam);
		this.addComponent(RenderComponent, 'render', registerComponentParam);
		this.addComponent(RelationshipComponent, 'relationship', registerComponentParam);
		this.addComponent(TaskExecutorComponent, 'taskExecutor', registerComponentParam);
		this.addComponent(MonitorDataComponent, 'monitorData', registerComponentParam);
	}

	onSetupBody(param) {
		this._body = new BodyObject();
		this._body.init(this, param);

		// Sync body node name
		let name = param['name'] || this.name;
		this.node.setName(name);

		// If we provide root or renderable node then indicates resource is ready
		let rootNode = param['rootNode'];
		let renderableNode = param['renderableNode'];
		if (rootNode || renderableNode) {
			let dynamic = Utils.parseValue(param['dynamic'], false);
			if (!dynamic) {
				this._resourceState = Object3D.ResourceState.Loaded;
			}
		}
	}

	onSetupAttributes(param) {
		let _private = this[__.private];

		// Setup tags
		let defaultTags = this.onGetDefaultTags();
		_private.tags = Utils.mergeSet(param['tags'], defaultTags);

		// Setup extras
		let external = param['extras'] || param['external'];
		if (external) {
			_private.external = Object.assign({}, external);
		}
	}

	onGetDefaultTags() {
		return this.constructor.defaultTagArray;
	}

	onSetupState(options) {
		// We are loading resource now
		let dynamic = Utils.parseValue(options['dynamic'], false);
		if (!dynamic && this._resourceState != Object3D.ResourceState.Loaded) {
			this._resourceState = Object3D.ResourceState.Loading;
		}

		// Setup pickable
		let pickable = Utils.parseValue(options['pickable'], true);
		if (!pickable) {
			this.pickable = false;
		}

		// Setup visibility
		let visible = Utils.parseValue(options['visible'], true);
		if (!visible) {
			this.visible = false;
		}

		// Setup active
		let active = Utils.parseValue(options['active'], true);
		if (!active) {
			this.active = false;
		}

		// Setup render attributes
		if (Utils.isNumber(options['renderOrder'])) {
			this.renderOrder = options['renderOrder'];
		}

		if (Utils.isBoolean(options['castShadow'])) {
			this.castShadow = options['castShadow'];
		}

		if (Utils.isBoolean(options['receiveShadow'])) {
			this.receiveShadow = options['receiveShadow'];
		}

		if (Utils.isBoolean(options['alwaysOnTop'])) {
			this.alwaysOnTop = options['alwaysOnTop'];
		}

		if (Utils.isNumber(options['layerMask'])) {
			this.layerMask = options['layerMask'];
		}
	}

	onSetupPosition(param) {
		if (param['localPosition']) {
			this.localPosition = param['localPosition'];
		}
		else if (param['position']) {
			this.position = param['position'];
		}
	}

	onSetupAngles(param) {
		if (param['localAngles']) {
			this.localAngles = param['localAngles'];
		}
		if (param['localRotation']) {
			this.localRotation = param['localRotation'];
		}
		else if (param['localQuaternion']) {
			this.localQuaternion = param['localQuaternion'];
		}
		else if (param['angles']) {
			this.angles = param['angles'];
		}
		else if (param['rotation']) {
			this.rotation = param['rotation'];
		}
	}

	onSetupScale(param) {
		if (param['localScale']) {
			this.localScale = param['localScale'];
		}
		else if (param['scale']) {
			this.scale = param['scale'];
		}
	}

	onSetupMatrix(param) {
		if (param['matrix']) {
			this.matrix = param['matrix'];
		}
	}

	onSetupTransform(param) {
		this.onSetupPosition(param);
		this.onSetupAngles(param);
		this.onSetupScale(param);
		this.onSetupMatrix(param);

		// Setup keep size
		if (Utils.isValid(param['keepSizeDistance'])) {
			this.keepSizeDistance = param['keepSizeDistance'];
		}

		if (param['keepSize']) {
			this.keepSize = param['keepSize'];
		}
	}

	onSetupStyle(param) {
		let styleValues = this._getStyleValues(param['style']);
		if (!styleValues) {
			return;
		}

		let style = this.body.style;
		style.begin();

		this._copyStyleValues(styleValues);

		const defaultValues = styleValues['default'];
		if (defaultValues) {
			style.beginDefaultValues();

			this._copyStyleValues(defaultValues);

			style.endDefaultValues();
		}

		style.end();
	}

	onSetupURL(param) {
		let _private = this[__.private];

		// Load resource from URL if needed
		_private.resource.url = Utils.parseValue(param.url, '');

		if (param.nodeName) {
			_private.resource.nodeName = param.nodeName;
		}

		if (param.inverseRotationMode) {
			_private.resource.inverseRotationMode = param.inverseRotationMode;
		}

		if (param.excludeNodeNames) {
			_private.resource.excludeNodeNames = param.excludeNodeNames;
		}

		if (param.instanceId) {
			_private.resource.instanceId = param.instanceId;
		}

		if (param.instanceCount) {
			_private.resource.instanceCount = param.instanceCount;
		}
		if (param.instanceStyle) {
			_private.resource.instanceStyle = param.instanceStyle;
		}
	}

	onSetupResource(param) {
		this.onSetupURL(param);

		// Setup components
		const components = param['components'];
		const toolkit = param['toolkit'];
		if (components && toolkit) {
			this.onImportComponents(components, toolkit, param);
		}

		// Start to load resource
		this._onLoadResource(param,
			// Resolve
			() => {
				let dynamic = Utils.parseValue(param['dynamic'], false);
				if (!dynamic) {
					this.onLoadComplete();
				}
			},
			// Reject
			(ev) => {
				let _private = this[__.private];

				this._resourceState = Object3D.ResourceState.Error;

				if (this._loadPromise) {
					this._loadPromise.reject(ev);
				}

				// Notify error
				if (_private.onError) {
					_private.onError(ev);
				}
			}
		);
	}

	onSetupComplete(param) {
		// Set pivot
		let pivot = param['pivot'];
		if (pivot) {
			this.pivot = pivot;
		}

		// Initialize done
		this._flags.enable(Flag.Initializing, false);
	}

	/**
	 * When create.
	 * @param {Object} options The options.
	 * @private
	 */
	onCreate(param) {
		// We have loaded resource
		if (this.loaded) {
			this.onNotifyComplete();
		}
		else {
			// If we do not need any dynamic then notify complete directly
			let dynamic = Utils.parseValue(param['dynamic'], false);
			if (dynamic) {
				this.onNotifyComplete();
			}
		}

		// Notify create event in batch mode
		Utils.setBatchTimeout(() => {
			if (this.destroyed) {
				return;
			}

			this.trigger(EventType.Create);
		});
	}

	clearInitialLocalBoundingBox() {
		// Clear initial bounding box
		if (this.hasComponent('bounding')) {
			this.initialLocalBoundingBox = null;
		}
	}

	onLoadComplete() {
		if (this.destroyed) {
			return;
		}

		switch (this._resourceState) {
			// Load complete (loading->loaded)
			case Object3D.ResourceState.Loading:
			case Object3D.ResourceState.Loaded:
				let _private = this[__.private];

				// Update loaded flag
				this._resourceState = Object3D.ResourceState.Loaded;

				// Sync attributes
				this._syncAttributes();

				this.clearInitialLocalBoundingBox();

				// Load completed
				if (this._loadPromise) {
					this._loadPromise.resolve({ object: this });
				}

				// Notify load resource event in batch and delay mode to make sure event order is: create -> load
				Utils.setBatchTimeout(() => {
					Utils.setTimeout(() => {
						this.trigger(EventType.Load);
					});
				});

				// Notify outside if we have completed
				this.onNotifyComplete();
				break;

			// If it's not in loaded/loading state then indicates we have unload in during loading state
			case Object3D.ResourceState.Ready:
				// Unload resource what just loaded ... what a pity
				this._unloadResource(false);
				break;

			default:
				break;
		}
	}

	/**
	 * When notify sync-complete.
	 * @private
	 */
	onSyncComplete() {
		let _private = this[__.private];

		let onSyncComplete = _private.onSyncComplete;
		if (onSyncComplete) {
			onSyncComplete({ object: this });

			_private.onSyncComplete = null;
		}
	}

	onDelayComplete() {
		let _private = this[__.private];

		let onComplete = _private.onComplete;
		if (onComplete) {
			Utils.setTimeout(() => {
				if (this.destroyed) {
					return;
				}

				onComplete({ object: this });
			});

			_private.onComplete = null;
		}
	}

	onProcessSyncCompleteCallbacks() {
		let _private = this[__.private];

		let syncCompleteCallbacks = _private.syncCompleteCallbacks;
		if (syncCompleteCallbacks.length) {
			_private.syncCompleteCallbacks.forEach(callback => {
				callback();
			});

			_private.syncCompleteCallbacks.length = 0;
		}
	}

	/**
	 * When notify complete.
	 * @private
	 */
	onNotifyComplete() {
		this.onSyncComplete();
		this.onDelayComplete();
		this.onProcessSyncCompleteCallbacks();
	}

	/**
	 * When load reosurce.
	 * @param {Object} options The options to load.
	 * @param {Function} resolve The promise resolve callback function.
	 * @param {Function} reject The promise reject callback function.
	 * @private
	 */
	onLoadResource(options, resolve, reject) {
		if (!this._flags.has(Flag.Initializing)) {
			this.onRefresh();
		}

		// Prevent call this function from parent class, so we delay it
		Utils.setTimeout(() => {
			if (this.destroyed) {
				return;
			}
			let dynamic = Utils.parseValue(options['dynamic'], false);
			if (!dynamic) {
				// Notify all loadable components
				let loadableComponents = this.loadableComponents;
				if (loadableComponents) {
					loadableComponents.forEach(component => {
						const promise = component.onLoadResource();
						promise && this.taskExecutor.add(promise);
					});
				}

				if (this.hasResource()) {
					this.onLoadRenderableResource(options, resolve, reject);
				}
				// Here we do not proivde any URL, so let it to be loaded state
				else {
					resolve();
				}
			}
			else {
				resolve();
			}
		});
	}

	/**
	 * When unload resource.
	 * @private
	 */
	onUnloadResource() {
	}

	onAfterDestroy() {
		let _private = this[__.private];

		// Destroy body object
		if (this._body) {
			this._body.dispose();
			this._body = null;
		}

		_private.style = undefined;

		_private.styleObjectAttributes.forEach(objectAttribute => {
			objectAttribute.dispose();
		});

		_private.styleObjectAttributes.length = 0;

		_private.renderLayout = null;

		_private.onError = null;
		_private.onComplete = null;
		_private.onSyncComplete = null;

		_private.options = {};

		super.onAfterDestroy();
	}

	onAddToParentNode(node, parentNode, options) {
		// If node has not parent then we can not use attach mode, due to world position is always [0, 0, 0]
		let enableAttachMode = node.getParent() ? true : false;
		if (enableAttachMode) {
			let attachMode = Utils.parseValue(options['attachMode'], true);
			if (attachMode) {
				parentNode.attach(node);
			}
			else {
				parentNode.add(node);
			}
		}
		else {
			parentNode.add(node);
		}
	}

	onCopy(object) {
	}

	onCreateBodyNode(type) {
		return Utils.createObject(type);
	}

	onGetPivot() {
		let tempData = this._getTempData();

		let pivot = this.transform.getPivot();
		tempData['pivot'] = pivot;

		return pivot;
	}

	onSetPivot(value) {
		if (this.loaded) {
			this.transform.setPivot(value, this._getTempData()['basePivot']);
		}
		else {
			this.addSyncCompleteCallback(() => {
				let tempData = this._getTempData();

				let pivot = tempData['pivot'];
				if (pivot) {
					let position = this.position;
					this.transform.setPivot(value, this._getTempData()['basePivot']);
					this.position = position;
				}
			});
		}
	}

	onUpdate(deltaTime) {
		super.onUpdate(deltaTime);

		this._renderComponents();
	}

	onLoadRenderableResource(options, resolve, reject) {
		this.render.load(
			options,
			resolve,
			() => {
				if (this.options.error) {
					this.options.error();
				}
				reject();
			}
		);
	}

	onBeforeSetParent(parent) {
		// If the final parent is set to Object3D, you need to remove yourself from the parent.
		this._needRemove = false;
	}

	onAddChild(object, options) {
		this._body.createRootNode();

		object.onSetParent(this, (parentNode) => {
			this.onAddToParentNode(object.node, parentNode, options);

			let attachMode = Utils.parseBoolean(options['attachMode'], true);
			if (attachMode) {
				return false;
			}

			let localPosition = options['localPosition'];
			let ignoreScale = Utils.parseValue(options['ignoreScale'], true);

			// Update position by sub node
			let subNodeName = options['subNodeName'];
			if (subNodeName) {
				let node = this._body.getNodeByName(subNodeName);
				if (!node) {
					Utils.error(`Sub node '${subNodeName}' is not existing when add object`, this);
					return false;
				}

				if (localPosition) {
					let subNodeLocalPosition = this.worldToSelf(node.position);
					let offset = MathUtils.addVector(subNodeLocalPosition, localPosition);

					object.position = this.selfToWorld(offset, ignoreScale);
				}
				else {
					object.position = node.position;
				}
			}
			// Update position without sub node
			else {
				if (localPosition) {
					object.position = this.selfToWorld(localPosition, ignoreScale);
				}
			}
		});
	}

	// #endregion

	// #region Common

	/**
	 * Get/Set name.
	 * @type {String}
	 * @example
	 * 	object.name = 'MyObject';
	 * @public
	 */
	get name() {
		return this._name;
	}
	set name(value) {
		super.name = value;

		this.node.setName(value);
	}

	/**
	 * Get/Set tags.
	 * @type {Set<String>}
	 * @example
	 *  // Get tags
	 * 	let tags = object.tags;
	 *  console.log(tags);
	 *
	 *  // Set tags by array
	 *  object.tags = ['one','two','three'];
	 *
	 *  // Set tags by set
	 *  object.tags = new Set(['one','two','three']);
	 * @public
	 */
	get tags() {
		let _private = this[__.private];
		_private.tags = _private.tags || new Set();
		return _private.tags;
	}
	set tags(value) {
		if (Utils.isArray(value) || Utils.isSet(value)) {
			const tags = this.tags;
			tags.clear();

			value.forEach(element => {
				tags.add(element);
			});
		}
	}

	/**
	 * Get/Set the external info.
	 * @type {Object}
	 * @private
	 */
	get external() {
		let _private = this[__.private];

		return _private.external;
	}
	set external(value) {
		let _private = this[__.private];

		_private.external = value;
	}

	// #region Render

	/**
	 * Get/Set layer mask, it would use AND operator to test with camera's layer mask.
	 * If it's equals to 0 then indicates it can not render in this camera.
	 * The default value is 1.
	 * It CAN NOT work with instanced drawing mode for now.
	 * @type {Number}
	 * @example
	 * // Hide object by changing layer mask
	 * object.layerMask = 0;
	 * @public
	 */
	get layerMask() {
		let bodyNode = this.bodyNode;

		return bodyNode.getLayerMask();
	}
	set layerMask(value) {
		let bodyNode = this.bodyNode;

		let style = this.body.style;
		if (style && style.isInstancedDrawing) {
			Utils.warn(`The layer mask can not work in instanced drawing mode`);
		}

		bodyNode.setLayerMask(value);
	}

	// #endregion

	// #endregion

	// #region State

	/**
	 * The function to call when start to process some action with object(s).
	 * @callback ProcessObjectCallback
	 * @param {Object3D} object The object.
	 * @returns {Boolean} False indicates reject to set attribute, otherwise continue to set attribute.
	 */

	/**
	 * Get visible state.
	 * @returns {Boolean}
	 * @example
	 * 	let visible = object.getVisible();
	 * 	if (visible) {
	 * 		console.log('object is showing');
	 * 	}
	 * @public
	 */
	getVisible() {
		return this.bodyNode.getVisible();
	}

	/**
	 * Set visible state.
	 * @param {Boolean} value True indicates show it, otherwise hide it.
	 * @param {Boolean|ProcessObjectCallback} [recursive=false] If it's boolean value type then means whether process it with all children.
	 * @example
	 * 	// Hide object self only, exclude all children
	 * 	object.setVisible(false, false);
	 *
	 * 	// Hide object(s) but exclude children what name equals to 'stone'
	 * 	object.setVisible(false, (obj) => {
	 * 		if (obj.name == 'stone') {
	 * 			return false;
	 * 		}
	 * 	});
	 * @public
	 */
	setVisible(value, recursive = false) {
		if (this._beforeSetAttribute(recursive)) {
			// Update body visible state
			let bodyNode = this.bodyNode;
			if (bodyNode.getVisible() != value) {
				bodyNode.setVisible(value);

				this.trigger(EventType.VisibleChange, { value });
			}
			// If resource is not loaded then we would notify visible changed forcely
			// In order to let dynamic load component work
			else if (!this.loaded) {
				this.trigger(EventType.VisibleChange, { value });
			}

			// Notify all components
			let visibleComponents = this.visibleComponents;
			if (visibleComponents) {
				visibleComponents.forEach(component => {
					if (component.onVisibleChange) {
						component.onVisibleChange(value);
					}
				});
			}
		}

		// Change all children if needed
		// We make recursive as true value even though recursive is callback function
		if (recursive && this.hasChildren()) {
			this._setChildrenAttributeState('visible', 'Visible', value, recursive);
		}
	}

	/**
	 * Get pickable state.
	 * @returns {Boolean}
	 * @private
	 */
	getPickable() {
		return this._flags.has(Flag.Pickable);
	}

	/**
	 * Set pickable state.
	 * @param {Boolean} value The pickable state.
	 * @param {Boolean} [recursive=false] True indicates process it with all children.
	 * @private
	 */
	setPickable(value, recursive = false) {
		this._flags.enable(Flag.Pickable, value);

		this._setAttributeState('pickable', 'Pickable', value, recursive);
	}

	/**
	 * Get renderable state.
	 * @returns {Boolean}
	 * @private
	 */
	getRenderable() {
		console.warn('Please get visible with getVisible instead of getRenderable');
		return this.getVisible();
	}

	/**
	 * Set renderable state.
	 * @param {Boolean} value True indicates render it, otherwise do not render it.
	 * @param {Boolean} [recursive=false] True indicates process it with all children.
	 * @private
	 */
	setRenderable(value, recursive = false) {
		console.warn('Please set visible with setVisible instead of setRenderable');
		this.setVisible(value, recursive);
	}

	/**
	 * Get active state.
	 * @return {Boolean}
	 * @private
	 */
	getActive() {
		return this._flags.has(Flag.Active);
	}

	/**
	 * Set active state.
	 * @param {Boolean} value True indicates active it, otherwise do not active it.
	 * @param {Boolean} [recursive=false] True indicates process it with all children.
	 * @private
	 */
	setActive(value, recursive = false) {
		// Create root node to make sure active is different with visible
		this._body.createRootNode();

		// Update active state
		if (this._flags.enable(Flag.Active, value)) {
			// Update visible state of root node
			this._body.getRootNode().setVisible(value);

			// Update root node active state
			this.node.getUserData()['active'] = value;

			// Update all components
			this.getAllComponents().forEach(component => {
				component.active = value;
			});

			// Notify active changed
			this.trigger(EventType.ActiveChange, { value });
		}
		// Let the dynamic load component to work
		else if (!this.loaded) {
			this.trigger(EventType.ActiveChange, { value });
		}

		// Change all children if needed
		if (recursive) {
			this.children.forEach(child => {
				child.setActive(value, true);
			});
		}
	}

	hasInherit() {
		let _private = this[__.private];

		return !!_private.inherit;
	}

	/**
	 * @typedef {Object} InheritData
	 * @property {InheritType} style? The style attributes inherit type (default value is InheritType.Normal).
	 * @property {InheritType} visible? The visible attributes inherit type (default value is InheritType.Normal).
	 * @property {InheritType} pickable? The pickable attributes inherit type (default value is InheritType.Normal).
	 * @public
	 */

	/**
	 * Get the inherit.
	 * @type {InheritData}
	 * @example
	 * 	object.inherit.style = THING.InheritType.Jump;
	 * 	object.inherit.visible = THING.InheritType.Break;
	 * 	object.inherit.pickable = THING.InheritType.Stop;
	 * @public
	 */
	get inherit() {
		let _private = this[__.private];

		if (!_private.inherit) {
			_private.inherit = new TypedObject({
				onConvertValue: (key, value) => {
					if (Utils.isBoolean(value)) {
						return value ? InheritType.Normal : InheritType.Stop;
					}

					return value;
				},
				values: {
					visible: InheritType.Normal,
					pickable: InheritType.Normal,
					style: InheritType.Normal
				}
			});
		}

		return _private.inherit;
	}

	/**
	 * Get/Set visible state.
	 * @type {Boolean}
	 * @example
	 * 	let object = new THING.Box();
	 * 	// @expect(object.visible == true)
	 * 	object.visible = false;
	 * 	// @expect(object.visible == false)
	 * @public
	 */
	get visible() {
		return this.getVisible();
	}
	set visible(value) {
		this.setVisible(value, true);
	}

	/**
	 * Check whether it's invisible in scene.
	 * @type {Boolean}
	 * @private
	 */
	get invisible() {
		return !this._body.getRenderableNode().getSceneVisible();
	}

	/**
	 * Get/Set the pickable state.
	 * @type {Boolean}
	 * @example
	 * 	let object = new THING.Box();
	 * 	object.pickable = false;
	 * @public
	 */
	get pickable() {
		return this.getPickable();
	}
	set pickable(value) {
		this.setPickable(value, true);
	}

	/**
	 * Get/Set renderable state, would not change any children active.
	 * @type {Boolean}
	 * @private
	 */
	get renderable() {
		return this.getRenderable();
	}
	set renderable(value) {
		this.setRenderable(value, false);
	}

	/**
	 * Get/Set active state, would not change any children active.
	 * @type {Boolean}
	 * @example
	 * 	let box = new THING.Box();
	 * 	let box2 = new THING.Box({parent: box});
	 * 	box.active = false;
	 * 	// @expect(box2.active == true);
	 * 	// @expect(box.active == false);
	 * @public
	 */
	get active() {
		return this.getActive();
	}
	set active(value) {
		this.setActive(value, false);
	}

	// #endregion

	// #region Transform

	/**
	 * Get the distance to world position.
	 * @param {THING.Object3D|Array<Number>} target The target object or world position.
	 * @returns {Number}
	 * @example
	 * 	let distance = object.distanceTo([0, 10, 0]);
	 * 	if (distance > 5000) {
	 * 		console.log('object is so far from specified position');
	 * 	}
	 * @public
	 */
	distanceTo(target) {
		if (target.isObject3D) {
			return MathUtils.getDistance(this.position, target.position);
		}
		else {
			return MathUtils.getDistance(this.position, target);
		}
	}

	/**
	 * Keep object's size in screen(auto adjust object's scale).
	 * @type {Boolean}
	 * @example
	 * 	let keepSize = object.keepSize;
	 * 	if (keepSize) {
	 * 		console.log('object is keep size to render');
	 * 	}
	 * @public
	 */
	get keepSize() {
		return this._flags.has(Flag.KeepSize);
	}
	set keepSize(value) {
		this._flags.enable(Flag.KeepSize, value);

		this.transform.keepSize(value, this.app.camera);
	}

	/**
	 * Get/Set pivot in self oriented box from [left, bottom, back].
	 * @type {Array<Number>}
	 * @example
	 * 	// Make object origin to [right, top, front] position
	 * 	object.pivot = [1, 1, 1];
	 * @public
	 */
	get pivot() {
		return this.getPivot();
	}
	set pivot(value) {
		value = value || [0.5, 0.5, 0.5];
		if (MathUtils.equalsVector3(this.pivot, value)) {
			return;
		}

		this.setPivot(value);
	}

	// #endregion

	// #region Traverse

	traverseInheritance(callback, onGetInheritance, jumpSelf) {
		if (!jumpSelf) {
			callback(this);
		}

		if (this.hasChildren()) {
			let children = this.children;
			for (let i = 0, l = children.length; i < l; i++) {
				let child = children[i];

				let inheritance = onGetInheritance(child);
				switch (inheritance) {
					case InheritType.Normal:
						child.traverseInheritance(callback, onGetInheritance);
						break;

					case InheritType.Break:
						callback(child);
						break;

					case InheritType.Jump:
						child.traverseInheritance(callback, onGetInheritance, true);
						break;

					case InheritType.Stop:
						break;

					default:
						break;
				}
			}
		}
	}

	// #endregion

	// #region Nodes

	/**
	 * Set the parent.
	 * @param {THING.BaseObject} parent The parent.
	 * @param {Function} onAddNode The add node into parent callback function.
	 * @private
	 */
	onSetParent(parent, onAddNode, options = {}) {
		// Attach to new parent
		if (parent) {
			if (onAddNode) {
				onAddNode(parent.node);
			}
		}
		// Just remove from current parent in render tree
		else if (this.parent && this.parent.node) {
			if (this._needRemove) {
				this.parent.node.remove(this.node);
			}
			else {
				// When modifying the parent, ensure that the parent is not empty
				// Otherwise, the "attach" will fail
				let attachMode = Utils.parseBoolean(options.attachMode, true);
				if (attachMode) {
					this.app.root.node.attach(this.node);
				}
				else {
					this.app.root.node.add(this.node);
				}
			}
		}

		this._needRemove = true;

		// Set the parent
		super.onSetParent(parent);

		// Notify all components
		let parentChangeComponents = this.parentChangeComponents;
		if (parentChangeComponents) {
			parentChangeComponents.forEach(component => {
				if (component.onParentChange) {
					component.onParentChange(parent);
				}
			});
		}
	}

	/**
	 * Add object as child.
	 * @param {THING.BaseObject} object The object what you want to add.
	 * @param {Object} options The options.
	 * @param {String} options.subNodeName The sub node name.
	 * @param {Array<Number>} options.localPosition The local position of parent or sub node.
	 * @param {Boolean} [options.attachMode=true] True indicates to keep world transform.
	 * @param {Boolean} [options.ignoreScale=false] True indicates ignore scale when add it as child.
	 * @returns {Boolean}
	 * @example
	 * 	// Keep local transform of box to be added to object
	 * 	object.add(new THING.Box({ localPosition: [0, 10, 0]}), { attachMode: false });
	 * @public
	 */
	add(object, options = {}) {
		super.add(object, options);
	}

	/**
	 * Promote node as child object.
	 * @param {String} name The node name.
	 * @param {THING.Object3D} parent The parent object, if it's null then indicates use current object as parent.
	 * @returns {THING.Object3D}
	 * @example
	 * 	// Promote all sub nodes
	 * 	var nodeNames = entity.body.nodeNames;
	 * 	nodeNames.forEach(name => {
	 * 		entity.promoteNode(name);
	 * 	});
	 * @public
	 */
	promoteNode(name, parent) {
		if (!name) {
			return null;
		}

		// We can not promote node when it's in instanced drawing mode
		if (this.isInstancedDrawing) {
			this.makeInstancedDrawing(false);
		}

		// Create root node to let sub node attach it
		this._body.createRootNode();

		// Get the parent
		parent = parent || this;

		// Remove some components, due to we can not work it together
		this.removeComponent('animation');

		// Promote node in body object
		let node = this._body.promoteNode(name, parent.node);

		// Create object
		let object = new Object3D({
			name,
			renderableNode: node,
			parent,
		});

		return object;
	}

	/**
	 * Get pivot in self oriented box.
	 * @type {Array<Number>}
	 * @private
	 */
	getPivot() {
		let tempData = this._getTempData();

		let data = tempData['pivot'];
		if (data) {
			return data;
		}

		return this.onGetPivot();
	}

	/**
	 * Set pivot in self oriented box.
	 * @param {Array<Number>} value The pivot value.
	 * @private
	 */
	setPivot(value) {
		if (value.length < 3) {
			value[0] = Utils.parseValue(value[0], 0.5);
			value[1] = Utils.parseValue(value[1], 0.5);
			value[2] = Utils.parseValue(value[2], 0.5);
		}

		let tempData = this._getTempData();

		this.onSetPivot(value);

		tempData['pivot'] = value;
	}

	/**
	 * Clear pivot node.
	 * @private
	 */
	clearPivot() {
		this.transform.clearPivot();

		let tempData = this._getTempData();
		delete tempData['pivot'];
	}

	/**
	 * Get the attached points.
	 * @type {THING.Selector}
	 * @private
	 */
	get attachedPoints() {
		return this.query('.AttachedPoint', { recursive: false });
	}

	// #endregion

	// #region Picker

	/**
	 * @typedef {Object} RaycastInfo
	 * @property {Array<Number>} origin The start position.
	 * @property {Array<Number>} direction The direction in world space.
	 */

	/**
	 * @typedef {Object} RaycastResult
	 * @property {THING.Object3D} object The picked object.
	 * @property {Array<Number>} position The picked position.
	 * @property {Number} distance The distance to ray origin.
	 */

	/**
	 * Raycast test.
	 * @param {RaycastInfo} ray The ray info.
	 * @param {Boolean} [recursive=true] True indicates process it with all children.
	 * @returns {Array<RaycastResult>} The test result(sort by distance from near to far).
	 * @private
	 */
	raycast(ray, recursive = true) {
		let node = recursive ? this.node : this.bodyNode;

		// Only works on renderable object
		if (node.isRenderableNode) {
			let targets = [];
			node.raycast(ray, targets);

			return targets.map(target => {
				return {
					node: target.node,
					distance: target.distance,
					position: target.position
				};
			});
		}
		// We can not ray cast on none-renderable object
		else {
			return {
				node: null,
				distance: 0,
				position: null
			};
		}
	}

	/**
	 * Get/Set pick mode.
	 * @type {PickMode}
	 * @private
	 */
	get pickMode() {
		return this.bodyNode.getAttribute('PickMode');
	}
	set pickMode(value) {
		this.bodyNode.setAttribute('PickMode', value);
	}

	// #endregion

	// #region Style

	hasStyle() {
		return this.body.hasStyle();
	}

	getStyle() {
		let _private = this[__.private];

		if (_private.style === undefined) {
			_private.style = this._createStyle();
		}

		if (_private.style) {
			return _private.style;
		}
		else {
			return null;
		}
	}

	/**
	 * Set style values.
	 * @param {*} value The style values.
	 * @param {Boolean} [recursive=false] True indicates to set all children style values.
	 * @example
	 * 	let box1 = new THING.Box();
	 * 	let box2 = new THING.Box({parent: box1});
	 * 	box1.setStyle({color: 'red'});
	 * 	// @expect(box1.style.color[0] == 1);
	 * 	// @expect(box1.style.color[1] == 0);
	 * 	// @expect(box1.style.color[2] == 0);
	 * 	// @expect(box2.style.color == null);
	 * @public
	 */
	setStyle(value, recursive = false) {
		if (recursive) {
			this.traverse((object) => {
				object.body.setStyle(value);
			});
		}
		else {
			this.body.setStyle(value);
		}
	}

	/**
	 * Get/Set style.
	 * @type {THING.Style}
	 * @example
	 * 	let style = object.style;
	 * 	style.color = 'red';
	 * 	style.opacity = 0.1;
	 * @public
	 */
	get style() {
		return this.getStyle();
	}
	set style(value) {
		this.setStyle(value);
	}

	// #endregion

	// #region Resources

	onBeforeDestroy() {
		if (this.destroyed) {
			return false;
		}

		this.onSyncBeforeDestroy();

		// Disable instanced drawing first
		if (this.isInstancedDrawing) {
			this.makeInstancedDrawing(false);
		}

		// Skip matrix update to speed up render
		this.node.setAttribute('SkipMatrixUpdate', true);

		return super.onBeforeDestroy();
	}

	/**
	 * When notify sync-before-destroy.
	 * @private
	 */
	onSyncBeforeDestroy() {
		let _private = this[__.private];

		let onSyncBeforeDestroy = _private.onSyncBeforeDestroy;
		if (onSyncBeforeDestroy) {
			onSyncBeforeDestroy({ object: this });

			_private.onSyncBeforeDestroy = null;
		}
	}

	/**
	 * Clone the object, if the clone target has been loaded, clone the node directly, otherwise use url clone.
	 * @param {Boolean} recursive True indicates to load all children resources.
	 * @param {THING.BaseObject} parent The parent object, default is app root object.
	 * @returns {THING.Object3D}
	 * @example
	 * 	// Clone the object and move up
	 * 	let otherObject = object.clone();
	 * 	otherObject.translateY(10);
	 * @public
	 */
	clone(recursive, parent, options = {}) {
		let object = null;
		let attachMode = Utils.parseValue(options['attachMode'], true);
		let objParent = parent !== undefined ? parent : this.app.root;

		object = Utils.applyNew(this.constructor, {
			dynamic: true,
			parent: objParent
		});

		object.options = Object.assign({}, options);
		object.copy(this);

		// Update the localPosition whit attachMode
		if (!attachMode && objParent != this.parent) {
			object.localPosition = this.localPosition;
		}

		recursive = Utils.parseBoolean(recursive, true);

		if (recursive) {
			this.children.forEach(child => {
				child.clone(true, object, { attachMode: false });
			});
		}

		return object;
	}

	/**
	 * Clone in async mode.
	 * @param {Boolean} recursive True indicates to load all children resources.
	 * @param {THING.BaseObject} parent The parent object, default is app root object.
	 * @returns {Promise<any>}
	 * @private
	 */
	cloneAsync(recursive, parent, options) {
		return new Promise((resolve, reject) => {
			let object = this.clone(recursive, parent, options);
			if (object) {
				object.copyPromise.then(() => {
					resolve(object);
				});
			}
			else {
				reject();
			}
		});
	}

	/**
	 * Wait for object load completed.
	 * @returns {Promise<any>}
	 * @example
	 * 	// Wait for object load completed then watch it
	 * 	await object.waitForComplete();
	 * 	app.camera.fit(object);
	 * @public
	 */
	waitForComplete() {
		if (this.loaded) {
			return Promise.resolve({ object: this });
		}
		else if (this._resourceState == Object3D.ResourceState.Error) {
			return Promise.reject({ object: this, desc: 'Object load failed' });
		}

		if (!this._loadPromise) {
			this._loadPromise = new ResolvablePromise();
		}

		return this._loadPromise;
	}

	/**
	 * @typedef {Object} ResourceResult
	 * @property {String} url The url.
	 * @property {Array<Number>} localPosition? The local position.
	 * @property {Array<Number>} localAngles? The local angles.
	 * @property {Array<Number>} localScale? The local scale.
	 * @property {Array<ResourceResult>} children? The children.
	 */

	/**
	 * Set resource only, do not load resource.
	 * @param {ResourceResult} value The resource.
	 * @private
	 */
	setResource(value) {
		let _private = this[__.private];

		if (Utils.isString(value)) {
			value = { url: value };
		}

		_private.resource = value;
	}

	/**
	 * Get resource.
	 * @returns {ResourceResult}
	 * @private
	 */
	getResource() {
		let _private = this[__.private];

		return _private.resource;
	}

	/**
	 * Check whether has resource.
	 * @returns {Boolean}
	 * @private
	 */
	hasResource() {
		let _private = this[__.private];

		let resource = _private.resource;
		if (resource.url) {
			return true;
		}

		let children = resource.children;
		if (children && children.length) {
			return true;
		}

		return false;
	}

	/**
	 * Reload resource.
	 * @param {Boolean} [recursive=true] True indicates to load all children resources.
	 * @param {Object} [options={}] The load options.
	 * @returns {Promise<any>}
	 * @private
	 */
	reloadResource(recursive = true, options = {}) {
		return this.unloadResource(recursive).then(() => {
			this.loadResource(recursive, options);
		});
	}

	/**
	 * Load resource.
	 * @param {Boolean} [recursive=true] True indicates to load all children resources.
	 * @param {Object} [options={}] The load options.
	 * @returns {Promise<any>}
	 * @example
	 * 	// Wait the object to load resource completed
	 * 	await object.loadResource();
	 * @public
	 */
	loadResource(recursive = true, options = {}) {
		return new Promise((resolve, reject) => {
			// Load child resources
			if (recursive) {
				let promises = [];

				if (this.hasChildren()) {
					this.children.forEach(child => {
						let promise = child.loadResource(recursive, options);
						if (!promise) {
							Utils.error(`Load resource failed, due to promise is invalid`, child);
							return;
						}

						promises.push(promise);
					});
				}

				if (promises.length) {
					Promise.all(promises).then(() => {
						this._loadSelfResource(options, resolve, reject);
					});
				}
				else {
					this._loadSelfResource(options, resolve, reject);
				}
			}
			else {
				this._loadSelfResource(options, resolve, reject);
			}
		}).then(
			// OK
			() => {
				this.onLoadComplete();
			},
			// Error
			() => {

			}
		);
	}

	/**
	 * Unload resource.
	 * @param {Boolean} [recursive=true] True indicates to unload all children resources.
	 * @returns {Promise<any>}
	 * @example
	 * 	// Wait the object to unload resource completed
	 * 	await object.unloadResource();
	 * @public
	 */
	unloadResource(recursive = true) {
		// We can not unload resource when it's in instanced drawing mode
		this.makeInstancedDrawing(false);

		return new Promise((resolve, reject) => {
			// Loaded -> Unload
			if (this.loaded) {
				// Start to unload resource
				this._unloadResource(recursive);

				// We are in ready to load resource state now
				this._resourceState = Object3D.ResourceState.Ready;

				// Clear some promises
				this._loadPromise = null;

				resolve();
			}
			// Loading -> Wait for complete -> Unload
			else if (this.loading) {
				// Wait for complete
				this.waitForComplete().then(() => {
					this.unloadResource();

					resolve();
				});
			}
			else {
				resolve();
			}
		});
	}

	setBodyNode(node) {
		this.body.setNode(node);
	}

	getBodyNode() {
		return this.bodyNode;
	}

	/**
	 * Get/Set the options.
	 * @type {Object}
	 * @private
	 */
	get options() {
		let _private = this[__.private];

		return _private.options;
	}
	set options(value) {
		let _private = this[__.private];

		if (value) {
			_private.options = Object.assign({}, value);
		}
		else {
			_private.options = {};
		}
	}

	get copyPromise() {
		return this[__.private].copyPromise;
	}

	/**
	 * Get/Set the loaded flag.
	 * @type {Boolean}
	 * @private
	 */
	get loaded() {
		return this._resourceState == Object3D.ResourceState.Loaded;
	}
	set loaded(value) {
		if (value) {
			this._resourceState = Object3D.ResourceState.Loaded;
		}
		else {
			this._resourceState = Object3D.ResourceState.Ready;
		}
	}

	/**
	 * Get the loading flag.
	 * @type {Boolean}
	 * @private
	 */
	get loading() {
		return this._resourceState == Object3D.ResourceState.Loading;
	}

	/**
	 * Get the body object.
	 * @type {THING.BodyObject}
	 * @public
	 */
	get body() {
		return this._body;
	}

	/**
	 * Get the root node.
	 * @type {Object}
	 * @private
	 */
	get node() {
		return this._body.getNode();
	}

	/**
	 * Get the pivot node.
	 * @type {Object}
	 * @private
	 */
	get pivotNode() {
		return this._body.getPivotNode();
	}

	/**
	 * Get the body node.
	 * @type {Object}
	 * @private
	 */
	get bodyNode() {
		return this._body.getRenderableNode();
	}

	/**
	 * Get/Set resource.
	 * @type {ResourceResult}
	 * @private
	 */
	get resource() {
		return this[__.private].resource;
	}
	set resource(value) {
		if (!value) {
			return;
		}

		// Skip for the same resource
		if (this._isSameResource(value)) {
			return;
		}

		this._updateResource(value);
	}

	// #endregion

	// #region copy

	/**
	 * Copy from object (except UUID attribute and parent).
	 * It would reload resource.
	 * @param {THING.BaseObject} object The source object.
	 * @returns {Promise<any>}
	 * @example
	 * 	// Copy the object from query result
	 * 	let sourceObject = app.query('#master')[0];
	 * 	if (sourceObject) {
	 * 		object.copy(sourceObject);
	 * 	}
	 * @public
	 */
	copy(object, options = {}) {
		let _private = this[__.private];

		// Start to copy values
		this.onBeforeCopy(object, options);

		// Start to copy style
		this.onCopyStyle(object, options);


		// Create copy promise
		_private.copyPromise = new ResolvablePromise();

		// Wait resource promise to finish
		let promises = [];
		promises.push(this._createResourcePromise());
		promises.push(object.waitForComplete());

		return Promise.all(promises).then(() => {
			if (object.isInstancedDrawing === false && object.style.isInstancedDrawing === true) {
				let ustyle = object.bodyNode.getStyle();
				let curInstanceCount = ustyle.getInstancedCount();
				let maxInstanceCOunt = ustyle.getInstancedMaxCount();
				if (curInstanceCount >= maxInstanceCOunt) {
					ustyle.setInstancedMaxCount(maxInstanceCOunt * 2);
				}
			}

			// Create copy promise
			const cloneNode = object.bodyNode.clone();
			this.body.setNode(cloneNode);

			// Copy component
			this.onCopyComponents(object);

			// Continue to copy object on inherited class
			this.onCopy(object);

			// Make instanced drawing mode when it's needed
			if (object.isInstancedDrawing) {
				this.makeInstancedDrawing();
			}

			this._resourceState = Object3D.ResourceState.Loaded;
			this.onLoadComplete();

			// Copy finished
			_private.copyPromise.resolve();
		});
	}

	onCopyComponents(object) {
		// Copy components
		let accessorInfo = object.getAccessorInfo();
		for (let name in accessorInfo) {
			let { isResident, hasInstance, classType } = accessorInfo[name];

			// Check whether need to copy it
			if (!this._needCopyComponent(isResident, hasInstance, classType)) {
				continue;
			}

			let accessor = accessorInfo[name].accessor;
			let sourceComponent = accessor.get();

			let targetComponent = this[name];
			if (!targetComponent) {
				this.addComponent(classType, name, accessorInfo[name].args);

				targetComponent = this[name];
			}

			if (targetComponent.onCopy) {
				targetComponent.onCopy(sourceComponent);
			}
			else if (targetComponent.onImport) {
				if (sourceComponent && sourceComponent.onExport) {
					let data = sourceComponent.onExport();
					if (data) {
						targetComponent.onImport(Utils.cloneObject(data, false));
					}
				}
			}
		}
	}

	onCopyResource(object) {
		// Update resource
		this._updateResource(object.resource).then(() => {
			this.onCopyComponents(object);
		});
	}

	onBeforeCopy(object, options = {}) {
		// Common
		this.onCopyAttribute(object, options);

		// Update transform
		this.onCopyTransform(object, options);
	}

	onAfterCopy(object) {
		this.onCopyResource(object);

		// Continue to copy object on inherited class
		this.onCopy(object);

		// Make instanced drawing mode when it's needed
		if (object.isInstancedDrawing) {
			this.makeInstancedDrawing();
		}
	}

	copyCompnents(object) {
		this.onCopyComponents(object);
	}

	onCopyStyle(object, options = {}) {
		// Update style
		let style = object.style;
		if (style) {
			let styleData = options.onUpdateStyle ? options.onUpdateStyle(style.getProperties()) : style.getProperties();
			this.style.copyFromProperties(styleData);
		}
	}

	onCopyAttribute(object, options = {}) {
		let _private = this[__.private];

		// Common
		this.name = options.onUpdateValue ? options.onUpdateValue(object, 'name') : object.name;
		this.id = options.onUpdateValue ? options.onUpdateValue(object, 'id') : object.id;
		this.queryable = options.onUpdateValue ? options.onUpdateValue(object, 'queryable') : object.queryable;
		this.destroyable = options.onUpdateValue ? options.onUpdateValue(object, 'destroyable') : object.destroyable;
		this.active = options.onUpdateValue ? options.onUpdateValue(object, 'active') : object.active;
		this.visible = options.onUpdateValue ? options.onUpdateValue(object, 'visible') : object.visible;
		this.userData = options.onUpdateValue ? options.onUpdateValue(object, 'userData') : object.userData;
		this.alwaysOnTop = options.onUpdateValue ? options.onUpdateValue(object, 'alwaysOnTop') : object.alwaysOnTop;

		// Update private info
		_private.tags = new Set(options.onUpdateValue ? options.onUpdateValue(object, 'tags') : object.tags);

		if (object.external) {
			_private.external = Object.assign({}, options.onUpdateValue ? options.onUpdateValue(object, 'external') : object.external);
		}
	}

	onCopyTransform(object, options = {}) {
		// We must keep root and body transform both, due to body and root would be 2 different nodes
		const position = options.onUpdateValue ? options.onUpdateValue(object, 'position') : object.position;
		const quaternion = options.onUpdateValue ? options.onUpdateValue(object, 'quaternion') : object.quaternion;
		const scale = options.onUpdateValue ? options.onUpdateValue(object, 'scale') : object.scale;

		const objectMatrixWorld = MathUtils.composeToMat4(position, quaternion, scale);
		const bodyMatrixWorld = MathUtils.composeToMat4(object.body.position, object.body.quaternion, object.body.scale);

		this.matrixWorld = objectMatrixWorld;
		this.body.matrixWorld = bodyMatrixWorld;
	}

	// #endregion

	// #region Notifier

	addSyncCompleteCallback(callback) {
		let _private = this[__.private];

		_private.syncCompleteCallbacks.push(callback);
	}

	// #endregion

	// #region Import/Export

	/**
	 * When check whether need to collect model.
	 * @returns {Boolean}
	 * @private
	 */
	onCollectMesh() {
		// Skip to collect mesh for prefab
		if (this.isSubObject) {
			return false;
		}

		return true;
	}


	/**
	 * When import external data.
	 * @param {Object} external The external data.
	 * @param {Object} options The options
	 * @private
	 */
	onImportExternalData(external, options) {
		let _private = this[__.private];
		if (external) {
			_private.external = external;
		}
	}

	/**
	 * When export external data.
	 * @param {Object} options The options.
	 * @returns {Object}
	 * @private
	 */
	onExportExternalData(options) {
		if (this.external) {
			return this.external;
		}

		return null;
	}

	// #endregion

	// #region Internal components attributes and functions

	// #region Component - render

	/**
	 * Check whether it's in instanced drawing mode.
	 * @type {Boolean}
	 * @private
	 */
	get isInstancedDrawing() { return this.render.isInstancedDrawing; }

	/**
	 * Get/Set the instance group name.
	 * @type {String}
	 * @private
	 */
	get instanceGroupName() { return this.render.instanceGroupName; }
	set instanceGroupName(value) { this.render.instanceGroupName = value; }

	/**
	 * Get/Set the render order, drawing order is from low to high.
	 * @type {Number}
	 * @private
	 */
	get renderOrder() { return this.render.renderOrder; }
	set renderOrder(value) { this.render.renderOrder = value; }

	/**
	 * Get/Set the cast shadow.
	 * @type {Boolean}
	 * @private
	 */
	get castShadow() { return this.render.castShadow; }
	set castShadow(value) { this.render.castShadow = value; }

	/**
	 * Get/Set the receive shadow.
	 * @type {Boolean}
	 * @private
	 */
	get receiveShadow() { return this.render.receiveShadow; }
	set receiveShadow(value) { this.render.receiveShadow = value; }

	/**
	 * Enable/Disable always on top.
	 * @type {Boolean}
	 * @example
	 * // Keep object render as the top of render layer
	 * object.alwaysOnTop = true;
	 * @public
	 */
	get alwaysOnTop() { return this.render.alwaysOnTop; }
	set alwaysOnTop(value) { this.render.alwaysOnTop = value; }

	/**
	 * Get render order.
	 * @returns {Number}
	 * @private
	 */
	getRenderOrder() { return this.render.getRenderOrder.apply(this.render, arguments); }

	/**
	 * Set render order.
	 * @param {Number} value The value.
	 * @param {Boolean} recursive True indicates process it with all children.
	 * @private
	 */
	setRenderOrder() { return this.render.setRenderOrder.apply(this.render, arguments); }

	/**
	 * Get cast shadow.
	 * @returns {Boolean}
	 * @private
	 */
	getCastShadow() { return this.render.getCastShadow.apply(this.render, arguments); }

	/**
	 * Set cast shadow.
	 * @param {Boolean} value The value.
	 * @param {Boolean} recursive True indicates process it with all children.
	 * @private
	 */
	setCastShadow() { return this.render.setCastShadow.apply(this.render, arguments); }

	/**
	 * Get receive shadow.
	 * @returns {Boolean}
	 * @private
	 */
	getReceiveShadow() { return this.render.getReceiveShadow.apply(this.render, arguments); }

	/**
	 * Set receive shadow.
	 * @param {Boolean} value The value.
	 * @param {Boolean} recursive True indicates process it with all children.
	 * @private
	 */
	setReceiveShadow() { return this.render.setReceiveShadow.apply(this.render, arguments); }

	/**
	 * Make object in instanced drawing mode.
	 * @param {Boolean} value True indicates enable instanced drawing mode.
	 * @param {Object} options The options.
	 * @param {String} [options.renderMode='InstancedRendering'] The render mode(could be 'SharedRendering/InstancedRendering').
	 * @returns {Boolean}
	 * @example
	 * 	// Make object render in instanced drawing mode
	 * 	if (object.makeInstancedDrawing()) {
	 * 		console.log('Enable instanced drawing');
	 * 	}
	 * @public
	 */
	makeInstancedDrawing() { return this.render.makeInstancedDrawing.apply(this.render, arguments); }

	/**
	 * @typedef {Object} InstancingNodeOptions
	 * @property {Number} maxNumber The max number.
	 * @property {Number} number The current number.
	 * @property {Array<Array<Number>>} matrices The matrices.
	 * @property {Array<Number>} pickedIds The picked Ids.
	 * @private
	 */

	/**
	 * Enable instancing node.
	 * @remark It will make body node invisible after enable it.
	 * @param {InstancingNodeOptions} options The options.
	 * @returns {Boolean}
	 * @private
	 */
	enableInstancing() { return this.render.enableInstancing.apply(this.render, arguments); }

	/**
	 * Disable instancing node.
	 * @private
	 */
	disableInstancing() { return this.render.disableInstancing.apply(this.render, arguments); }

	/**
	 * Set/Update instancing node options.
	 * @param {InstancingNodeOptions} options The options.
	 * @returns {Boolean}
	 * @private
	 */
	setInstancing() { return this.render.setInstancing.apply(this.render, arguments); }

	/**
	 * Get the instancing node info.
	 * @returns {InstancingNodeOptions}
	 * @private
	 */
	getInstancing() { return this.render.getInstancing.apply(this.render, arguments); }

	/**
	 * @typedef {Object} RenderableNodeGeometryInfo
	 * @property {Number} vertices The number of vertices.
	 * @property {Number} triangles The number of triangles.
	 * @property {Number} materials The number of materials.
	 * @property {Number} textures The number of textures.
	 */

	/**
	 * Get the geometry info.
	 * @returns {RenderableNodeGeometryInfo}
	 * @private
	 */
	getGeometryInfo() { return this.render.getGeometryInfo.apply(this.render, arguments); }

	// #endregion

	// #region Component - level

	/**
	 * Check whether it's current level.
	 * @type {Boolean}
	 * @deprecated Deprecated, call it with LevelComponent.
	 * @private
	 */
	get isCurrentLevel() { return this.level.isCurrentLevel; }

	// #endregion

	// #region Component - bounding

	/**
	 * Get bounding box recursively.
	 * @type {THING.Box3}
	 * @example
	 * 	let boundingBox = object.boundingBox;
	 * 	if (boundingBox.halfSize[1] > 100) {
	 * 		console.log('The object is so tall');
	 * 	}
	 * @public
	 */
	get boundingBox() { return this.bounding.boundingBox; }

	/**
	 * Get oriented box recursively.
	 * @type {OrientedBoxResult}
	 * @example
	 * 	let orientedBox = object.orientedBox;
	 * 	if (orientedBox.angles[1] > 0) {
	 * 		console.log('The object has rotated by Y-axis');
	 * 	}
	 * @public
	 */
	get orientedBox() { return this.bounding.orientedBox; }

	/**
	 * Get/Set the initial local bounding box.
	 * @type {BoundingBoxResult}
	 * @private
	 */
	get initialLocalBoundingBox() { return this.bounding.initialLocalBoundingBox; }
	set initialLocalBoundingBox(value) { this.bounding.initialLocalBoundingBox = value; }

	/**
	 * Get the axis-aligned bounding box (AABB).
	 * @param {Boolean} [recursive=true] True indicates get bounding box including all children.
	 * @param {Boolean} [updateMatrix=true] True indicates update matrix for self and all children.
	 * @param {Boolean} [local=false] True indicates get the local transform from parent.
	 * @returns {THING.Box3}
	 * @private
	 */
	getAABB() { return this.bounding.getAABB.apply(this.bounding, arguments); }

	/**
	 * @typedef {Object} OrientedBoxResult
	 * @property {Array<Number>} angles The angles base on center.
	 * @property {Array<Number>} center The center world position.
	 * @property {Array<Number>} size The bounding box size.
	 * @property {Array<Number>} halfSize The bounding box half size.
	 * @property {Number} radius The bounding box radius.
	 */

	/**
	 * Get the oriented bounding box (OBB).
	 * @param {Boolean} [recursive=true] True indicates get oriented box including all children.
	 * @param {Boolean} [updateMatrix=true] True indicates update matrix for self and all children.
	 * @param {Boolean} [local=false] True indicates get the local transform from parent.
	 * @returns {OrientedBoxResult}
	 * @private
	 */
	getOBB() { return this.bounding.getOBB.apply(this.bounding, arguments); }

	// #endregion

	// #region Component - lerp

	/**
	 * @typedef LerpArgs
	 * @property {Object} from The source attributes.
	 * @property {Object} to The target attributes.
	 * @property {Number} [loop=-1] The loop times, -1 indicates unlimited, and set the loop type to repeat.
	 * @property {Number} [times=-1] The loop times, -1 indicates unlimited.
	 * @property {Number} [duration=1000] The time in milliseconds.
	 * @property {Number} [delayTime=0] The delay time in milliseconds.
	 * @property {LerpType} lerpType? The lerp type.
	 * @property {LoopType} loopType? The loop type.
	 * @property {Boolean} orientToPath? Whether to face the path when moving.
	 * @property {Function} onRepeat The callback function to trigger repeat action.
	 * @property {Function} onStart The callback function to trigger start action.
	 * @property {Function} onStop The callback function to trigger stop action.
	 * @property {Function} onUpdate The callback function to trigger update action.
	 * @property {Function} onComplete The callback function to trigger complete action.
	 */

	/**
	 * The position/rotate/scale lerp args.
	 * @typedef {LerpArgs} LerpWithSpaceTypeArgs
	 * @param {THING.SpaceType} [spaceType=THING.SpaceType.World] The space type.
	 */

	/**
	 * Get the flying state.
	 * @type {Boolean}
	 * @public
	 */
	get isFlying() { return this.lerp.isFlying; }

	/**
	 * Lerp points.
	 * @private
	 */
	lerpPoints() { return this.lerp.lerpPoints.apply(this.lerp, arguments); }

	/**
	 * Lerp points in async mode.
	 * @private
	 */
	lerpPointsAsync() { return this.lerp.lerpPointsAsync.apply(this.lerp, arguments); }

	/**
	 * Stop moving.
	 * @example
	 * 	object.stopMoving();
	 * @public
	 */
	stopMoving() { return this.lerp.stopMoving.apply(this.lerp, arguments); }

	/**
	 * Pause moving.
	 * @example
	 * 	object.pauseMoving();
	 * @public
	 */
	pauseMoving() { return this.lerp.pauseMoving.apply(this.lerp, arguments); }

	/**
	 * Resume moving.
	 * @example
	 * 	object.resumeMoving();
	 * @public
	 */
	resumeMoving() { return this.lerp.resumeMoving.apply(this.lerp, arguments); }

	/**
	 * @typedef {LerpWithSpaceTypeArgs} MovePathLerpArgs
	 * @param {Array<Number>} up The up direction.
	 * @param {Boolean} [closure=false] True indicates it's closure path.
	 */

	/**
	 * Move object in duration.
	 * @param {Array<Number>} value The position.
	 * @param {MovePathLerpArgs} param The parameters.
	 * @example
	 * 	object.moveTo(object.selfToWorld(THING.Math.randomVector([-200, -5, -200], [200, 5, 200])), {
	 * 		loopType: THING.LoopType.PingPong,
	 * 		duration: THING.Math.randomInt(1000, 5000)
	 * });
	 * @public
	 */
	moveTo() { return this.lerp.moveTo.apply(this.lerp, arguments); }

	/**
	 * Move object in duration (async).
	 * @param {Array<Number>} value The position.
	 * @param {MovePathLerpArgs} param The parameters.
	 * @returns {Promise<any>}
	 * @example
	 * 	await object.moveToAsync(object.selfToWorld(THING.Math.randomVector([-200, -5, -200], [200, 5, 200])), {
	 * 		duration: THING.Math.randomInt(1000, 5000)
	 * });
	 * @private
	 */
	moveToAsync() { return this.lerp.moveToAsync.apply(this.lerp, arguments); }

	/**
	 * Move object with path in duration.
	 * @param {Array<Array<Number>>} value The path of position.
	 * @param {MovePathLerpArgs} param The parameters.
	 * @example
	 * let path = [
	 * 	[100, 0, 0],
	 * 	[100, 0, 100],
	 * 	[0, 0, 100],
	 * [0, 0, 0],
	 * ];
	 *
	 * object.movePath(path.map(point => object.selfToWorld(point)), {
	 * 	duration: 5 * 1000,
	 * 	loopType: THING.LoopType.Repeat,
	 * });
	 * @public
	 */
	movePath() { return this.lerp.movePath.apply(this.lerp, arguments); }

	/**
	 * Move object with path in duration (async).
	 * @param {Array<Array<Number>>} value The path of position.
	 * @param {MovePathLerpArgs} param The parameters.
	 * @returns {Promise<any>}
	 * @example
	 * let path = [
	 * 	[100, 0, 0],
	 * 	[100, 0, 100],
	 * 	[0, 0, 100],
	 * [0, 0, 0],
	 * ];
	 *
	 * await object.movePathAsync(path.map(point => object.selfToWorld(point)), {
	 * 	duration: 5 * 1000
	 * });
	 * @private
	 */
	movePathAsync() { return this.lerp.movePathAsync.apply(this.lerp, arguments); }

	/**
	 * Stop scaling.
	 * @example
	 * 	object.stopScaling();
	 * @public
	 */
	stopScaling() { return this.lerp.stopScaling.apply(this.lerp, arguments); }

	/**
	 * Pause scaling.
	 * @example
	 * 	object.pauseScaling();
	 * @public
	 */
	pauseScaling() { return this.lerp.pauseScaling.apply(this.lerp, arguments); }

	/**
	 * Resume scaling.
	 * @example
	 * 	object.resumeScaling();
	 * @public
	 */
	resumeScaling() { return this.lerp.resumeScaling.apply(this.lerp, arguments); }

	/**
	 * Scale object in duration.
	 * @param {Array<Number>} value The scale.
	 * @param {LerpWithSpaceTypeArgs} param The parameters.
	 * @example
	 * object.scaleTo(THING.Math.randomVector([1, 1, 1], [3, 3, 3]), {
	 * 	loopType: THING.LoopType.PingPong,
	 * 	duration: THING.Math.randomInt(1000, 5000)
	 * });
	 * @public
	 */
	scaleTo() { return this.lerp.scaleTo.apply(this.lerp, arguments); }

	/**
	 * Scale object in duration (async).
	 * @example
	 * await object.scaleToAsync(THING.Math.randomVector([1, 1, 1], [3, 3, 3]), {
	 * 	duration: THING.Math.randomInt(1000, 5000)
	 * });
	 * @private
	 */
	scaleToAsync() { return this.lerp.scaleToAsync.apply(this.lerp, arguments); }

	/**
	 * Stop rotating.
	 * @example
	 * 	object.stopRotating();
	 * @public
	 */
	stopRotating() { return this.lerp.stopRotating.apply(this.lerp, arguments); }

	/**
	 * Pause rotating.
	 * @example
	 * 	object.pauseRotating();
	 * @public
	 */
	pauseRotating() { return this.lerp.pauseRotating.apply(this.lerp, arguments); }

	/**
	 * Resume rotating.
	 * @example
	 * 	object.resumeRotating();
	 * @public
	 */
	resumeRotating() { return this.lerp.resumeRotating.apply(this.lerp, arguments); }

	/**
	 * Rotate object in duration.
	 * @param {Array<Number>} value The angles.
	 * @param {LerpWithSpaceTypeArgs} param The parameters.
	 * @example
	 * object.rotateTo([0, 360, 0], {
	 * 	loopType: THING.LoopType.Repeat,
	 * 	duration: 10 * 1000
	 * });
	 * @public
	 */
	rotateTo() { return this.lerp.rotateTo.apply(this.lerp, arguments); }

	/**
	 * Rotate object in duration (async).
	 * @example
	 * await object.rotateToAsync([0, 360, 0], {
	 * 	duration: 10 * 1000
	 * });
	 * @private
	 */
	rotateToAsync() { return this.lerp.rotateToAsync.apply(this.lerp, arguments); }

	/**
	 * Stop fading.
	 * @private
	 */
	stopFading() { return this.lerp.stopFading.apply(this.lerp, arguments); }

	/**
	 * Pause fading.
	 * @private
	 */
	pauseFading() { return this.lerp.pauseFading.apply(this.lerp, arguments); }

	/**
	 * Resume fading.
	 * @private
	 */
	resumeFading() { return this.lerp.resumeFading.apply(this.lerp, arguments); }

	/**
	 * Fade object in.
	 * @param {LerpArgs} param The parameters.
	 * @public
	 */
	fadeIn() { return this.lerp.fadeIn.apply(this.lerp, arguments); }

	/**
	 * Fade object out.
	 * @param {LerpArgs} param The parameters.
	 * @public
	 */
	fadeOut() { return this.lerp.fadeOut.apply(this.lerp, arguments); }

	/**
	 * Fade object in (async).
	 * @private
	 */
	fadeInAsync() { return this.lerp.fadeInAsync.apply(this.lerp, arguments); }

	/**
	 * Fade object out (async).
	 * @private
	 */
	fadeOutAsync() { return this.lerp.fadeOutAsync.apply(this.lerp, arguments); }

	/**
	 * Stop flying.
	 * @example
	 * 	object.stopFlying();
	 * @public
	 */
	stopFlying() { return this.lerp.stopFlying.apply(this.lerp, arguments); }

	/**
	 * Pause flying.
	 * @example
	 * 	object.pauseFlying();
	 * @public
	 */
	pauseFlying() { return this.lerp.pauseFlying.apply(this.lerp, arguments); }

	/**
	 * Resume flying.
	 * @example
	 * 	object.resumeFlying();
	 * @public
	 */
	resumeFlying() { return this.lerp.resumeFlying.apply(this.lerp, arguments); }

	/**
	 * @typedef {Object} LerpFlyToArgs
	 * @property {Array<Number>} position The position where to fly.
	 * @property {Array<Number>|THING.BaseObject} target The target where to fly, if it's object then auto select the best position from its bounding box.
	 * @property {Number} duration The action time in milliseconds.
	 * @property {Number} delayTime The delay time in milliseconds.
	 * @property {Number} distance The distance(only works for object target mode).
	 * @property {Number} horzAngle The horz angle(only works for object target mode).
	 * @property {Number} vertAngle The vert angle(only works for object target mode).
	 * @property {LerpType} lerpType The lerp type.
	 * @property {LerpType} positionLerpType The position lerp type.
	 * @property {LerpType} targetLerpType The target lerp type.
	 * @property {LerpType} upLerpType The lerp type.
	 * @property {Function} onStart The start callback function.
	 * @property {Function} onStop The stop callback function.
	 * @property {Function} onUpdate The update callback function.
	 * @property {Function} onComplete The complete callback function.
	 */

	/**
	 * Fly to specified position in duration.
	 * @param {THING.BaseObject|LerpFlyToArgs} param The object or parameters.
	 * @example
	 * 	object.flyTo({
	 * 		target: otherTarget,
	 * 		horzAngle: 0,
	 * 		vertAngle: 45
	 * 	});
	 * @public
	 */
	flyTo() { return this.lerp.flyTo.apply(this.lerp, arguments); }

	/**
	 * Fly to specified position in duration (async).
	 * @param {THING.BaseObject|LerpFlyToArgs} param The object or parameters.
	 * @returns {Promise<any>}
	 * @example
	 * 	await object.flyToAsync({
	 * 		target: otherTarget,
	 * 		horzAngle: 0,
	 * 		vertAngle: 45
	 * 	});
	 * @private
	 */
	flyToAsync() { return this.lerp.flyToAsync.apply(this.lerp, arguments); }

	/**
	 * Auto set it to best position of object.
	 * @param {Object} param The parameters.
	 * @param {THING.BaseObject} param.target The target object.
	 * @example
	 * 	THING.App.current.camera.fit(otherTarget);
	 * @public
	 */
	fit() { return this.lerp.fit.apply(this.lerp, arguments); }

	/**
	 * Stop UV transform.
	 * @param {ImageSlotType} slotType The slot type of style.
	 * @example
	 * 	object.stopUVTransform(THING.ImageSlotType.Map);
	 * @public
	 */
	stopUVTransform() { return this.lerp.stopUVTransform.apply(this.lerp, arguments); }

	/**
	 * Pause UV transform.
	 * @param {ImageSlotType} slotType The slot type of style.
	 * @example
	 * 	object.pauseUVTransform(THING.ImageSlotType.Map);
	 * @public
	 */
	pauseUVTransform() { return this.lerp.pauseUVTransform.apply(this.lerp, arguments); }

	/**
	 * Resume UV transform.
	 * @param {ImageSlotType} slotType The slot type of style.
	 * @example
	 * 	object.resumeUVTransform(THING.ImageSlotType.Map);
	 * @public
	 */
	resumeUVTransform() { return this.lerp.resumeUVTransform.apply(this.lerp, arguments); }

	/**
	 * Start UV transform in duration.
	 * @param {ImageSlotType} slotType The slot type of style.
	 * @param {StyleUVMatrixResult|LerpArgs} value The UV transform info.
	 * @param {LerpArgs} param? The parameters.
	 * @example
	 * // Lerp UV offset from [0, 0] to [-1, 0] in 2 seconds by repeat mode
	 * object.uvTransformTo(THING.ImageSlotType.Map, {
	 * 	from: { offset: [0, 0] },
	 * 	to: { offset: [-1, 0] },
	 * 	duration: 2000,
	 * 	loopType: THING.LoopType.Repeat,
	 * 	times: 3
	 * });
	 * @public
	 */
	uvTransformTo() { return this.lerp.uvTransformTo.apply(this.lerp, arguments); }

	/**
	 * Start UV transform in duration (async).
	 * @param {ImageSlotType} slotType The slot type of style.
	 * @param {StyleUVMatrixResult|LerpArgs} value The UV transform info.
	 * @param {LerpArgs} param? The parameters.
	 * @returns {Promise<any>}
	 * @example
	 * // Lerp UV offset from [0, 0] to [-1, 0] in 2 seconds by repeat mode and wait for complete
	 * await object.uvTransformToAsync(THING.ImageSlotType.Map, {
	 * 	from: { offset: [0, 0] },
	 * 	to: { offset: [-1, 0] },
	 * 	duration: 2000,
	 * 	loopType: THING.LoopType.Repeat,
	 * 	times: 3
	 * });
	 * @private
	 */
	uvTransformToAsync() { return this.lerp.uvTransformToAsync.apply(this.lerp, arguments); }

	// #endregion

	// #region Component - transform

	/**
	 * Get/Set local(offset) position of the parent space.
	 * @type {Array<Number>}
	 * @public
	 */
	get localPosition() { return this.transform.getLocalPosition(); }
	set localPosition(value) { this.transform.setLocalPosition(value); }

	/**
	 * Get/Set angles of the inertial space.
	 * @type {Array<Number>}
	 * @public
	 */
	get localAngles() { return this.transform.getLocalAngles(); }
	set localAngles(value) { this.transform.setLocalAngles(value); }

	/**
	 * Get/Set angles of the inertial space.
	 * @type {Array<Number>}
	 * @public
	 */
	get localRotation() { return this.transform.getLocalAngles(); }
	set localRotation(value) { this.transform.setLocalAngles(value); }

	/**
	 * Get/Set quaternion of the inertial space.
	 * @type {Array<Number>}
	 * @public
	 */
	get localQuaternion() { return this.transform.getLocalQuaternion(); }
	set localQuaternion(value) { this.transform.setLocalQuaternion(value); }

	/**
	 * Get/Set scale of the self coordinate system.
	 * @type {Array<Number>}
	 * @public
	 */
	get localScale() { return this.transform.getLocalScale(); }
	set localScale(value) { this.transform.setLocalScale(value); }

	/**
	 * Get/Set world position of the world space.
	 * @type {Array<Number>}
	 * @public
	 */
	get position() { return this.transform.getWorldPosition(); }
	set position(value) { this.transform.setWorldPosition(value); }

	/**
	 * Get/Set angles of the world space.
	 * @type {Array<Number>}
	 * @public
	 */
	get angles() { return this.transform.getWorldAngles(); }
	set angles(value) { this.transform.setWorldAngles(value); }

	/**
	 * Get/Set angles of the world space.
	 * @type {Array<Number>}
	 * @public
	 */
	get rotation() { return this.transform.getWorldAngles(); }
	set rotation(value) { this.transform.setWorldAngles(value); }

	/**
	 * Get/Set quaternion of the world space.
	 * @type {Array<Number>}
	 * @public
	 */
	get quaternion() { return this.transform.getWorldQuaternion(); }
	set quaternion(value) { this.transform.setWorldQuaternion(value); }

	/**
	 * Get/Set scale of the world coordinate system.
	 * @type {Array<Number>}
	 * @public
	 */
	get scale() { return this.transform.getWorldScale(); }
	set scale(value) { this.transform.setWorldScale(value); }

	/**
	 * Get/Set the matrix.
	 * @type {Array<Number>}
	 * @private
	 */
	get matrix() { return this.transform.getMatrix(); }
	set matrix(value) { this.transform.setMatrix(value); }

	/**
	 * Get/Set the matrix world.
	 * @type {Array<Number>}
	 * @private
	 */
	get matrixWorld() { return this.transform.getMatrixWorld([], true); }
	set matrixWorld(value) { this.transform.setMatrixWorld(value); }

	/**
	 * Get the inversed matrix.
	 * @type {Array<Number>}
	 * @private
	 */
	get inversedMatrix() { return this.transform.inversedMatrix; }

	/**
	 * Get the inversed matrix world.
	 * @type {Array<Number>}
	 * @private
	 */
	get inversedMatrixWorld() { return this.transform.inversedMatrixWorld; }

	/**
	 * Get the up direction of world space.
	 * @type {Array<Number>}
	 * @private
	 */
	get up() { return this.transform.up; }

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

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

	/**
	 * Get/Set keep size distance, null indicates use the current camera position to calculate distance.
	 * @type {Number}
	 * @private
	 */
	get keepSizeDistance() { return this.transform.keepSizeDistance; }
	set keepSizeDistance(value) { this.transform.keepSizeDistance = value; }

	/**
	 * Get local position.
	 * @param {Array<Number>} target? The target to save result.
	 * @returns {Array<Number>} The reference of target.
	 * @private
	 */
	getLocalPosition() { return this.transform.getLocalPosition.apply(this.transform, arguments); }

	/**
	 * Get quaternion of the inertial space.
	 * @param {Array<Number>} target? The target to save result.
	 * @returns {Array<Number>} The reference of target.
	 * @private
	 */
	getLocalQuaternion() { return this.transform.getLocalQuaternion.apply(this.transform, arguments); }

	/**
	 * Get scale of the self coordinate system.
	 * @param {Array<Number>} target? The target to save result.
	 * @returns {Array<Number>} The reference of target.
	 * @private
	 */
	getLocalScale() { return this.transform.getLocalScale.apply(this.transform, arguments); }

	/**
	 * Get world position.
	 * @param {Array<Number>} target? The target to save result.
	 * @returns {Array<Number>} The reference of target.
	 * @private
	 */
	getWorldPosition() { return this.transform.getWorldPosition.apply(this.transform, arguments); }

	/**
	 * Get quaternion of the world space.
	 * @param {Array<Number>} target? The target to save result.
	 * @returns {Array<Number>} The reference of target.
	 * @private
	 */
	getWorldQuaternion() { return this.transform.getWorldQuaternion.apply(this.transform, arguments); }

	/**
	 * Get scale of the world coordinate system.
	 * @param {Array<Number>} target? The target to save result.
	 * @returns {Array<Number>} The reference of target.
	 * @private
	 */
	getWorldScale() { return this.transform.getWorldScale.apply(this.transform, arguments); }

	/**
	 * Get the forward direction in world space.
	 * @param {Array<Number>} target? The target to save result.
	 * @returns {Array<Number>} The reference of target.
	 * @private
	 */
	getForward() { return this.transform.getForward.apply(this.transform, arguments); }

	/**
	 * Rotate on axis by angle in self space.
	 * @param {Array<Number>} axis The axis in self space.
	 * @param {Number} angle The degree.
	 * @example
	 * 	let box = new THING.Box();
	 * 	box.rotate(THING.Utils.xAxis, 45);
	 * 	@expect(THING.Math.equalsVector3([45, 0, 0], box.angles) == true);
	 * @public
	 */
	rotate() { return this.transform.rotateOnAxis.apply(this.transform, arguments); }

	/**
	 * Rotate on axis by angle in self space.
	 * @param {Array<Number>} axis The axis in self space.
	 * @param {Number} angle The degree.
	 * @example
	 * 	let box = new THING.Box();
	 * 	box.rotateOnAxis(THING.Utils.xAxis, 45);
	 * 	@expect(THING.Math.equalsVector3([45, 0, 0], box.angles) == true);
	 * @public
	 */
	rotateOnAxis() { return this.transform.rotateOnAxis.apply(this.transform, arguments); }

	/**
	 * Rotate on x-axis in self coordinates.
	 * @param {Number} angle The angle.
	 * @public
	 */
	rotateX() { return this.transform.rotateX.apply(this.transform, arguments); }

	/**
	 * Rotate on y-axis in self coordinates.
	 * @param {Number} angle The angle.
	 * @public
	 */
	rotateY() { return this.transform.rotateY.apply(this.transform, arguments); }

	/**
	 * Rotate on z-axis in self coordinates.
	 * @param {Number} angle The angle.
	 * @public
	 */
	rotateZ() { return this.transform.rotateZ.apply(this.transform, arguments); }

	/**
	 * Translate on axis by distance in self space.
	 * @param {Array<Number>} axis The axis in self space.
	 * @param {Number} distance The distance.
	 * @private
	 */
	translateOnAxis() { return this.transform.translateOnAxis.apply(this.transform, arguments); }

	/**
	 * Translate on x-axis in self coordinates.
	 * @param {Number} distance The distance.
	 * @public
	 */
	translateX() { return this.transform.translateX.apply(this.transform, arguments); }

	/**
	 * Translate on y-axis in self coordinates.
	 * @param {Number} distance The distance.
	 * @public
	 */
	translateY() { return this.transform.translateY.apply(this.transform, arguments); }

	/**
	 * Translate on z-axis in self coordinates.
	 * @param {Number} distance The distance.
	 * @public
	 */
	translateZ() { return this.transform.translateZ.apply(this.transform, arguments); }

	/**
	 * Translate in self coordinates.
	 * @param {Array<Number>} offset The offset.
	 * @public
	 */
	translate() { return this.transform.translate.apply(this.transform, arguments); }

	/**
	 * @typedef {Object} LookAtArgs
	 * @property {Array<Number>} up The up direction.
	 * @property {AxisType} [lockAxis] The lock axis type.
	 * @property {Boolean} [always=false] True indicates look at target always
	 * @property {Boolean} [lookOnPlane=false] True indicates look on target's plane by its forward.
	 */

	/**
	 * Look at object or position.
	 * @param {Array<Number>|THING.Object3D} target The target what to look at.
	 * @param {LookAtArgs} param The parameters.
	 * @public
	 */
	lookAt() { return this.transform.lookAt.apply(this.transform, arguments); }

	/**
	 * 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>}
	 * @private
	 */
	localToSelf() { return this.transform.localToSelf.apply(this.transform, arguments); }

	/**
	 * 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>}
	 * @private
	 */
	selfToLocal() { return this.transform.selfToLocal.apply(this.transform, arguments); }

	/**
	 * 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>}
	 * @example
	 * let worldPos = [5, 10, 0];
	 * let obj = new THING.Object3D({position: worldPos});
	 * let selfPos = obj.worldToSelf(worldPos);
	 * // print [0, 0, 0]
	 * console.log(selfPos);
	 * @public
	 */
	worldToSelf() { return this.transform.worldToSelf.apply(this.transform, arguments); }

	/**
	 * 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>}
	 * @example
	 * let obj = new THING.Object3D({ position: [0, 15, 0] });
	 * let worldPos = obj.selfToWorld([0, -15, 0]);
	 * // print [0, 0, 0]
	 * console.log(worldPos);
	 * @public
	 */
	selfToWorld() { return this.transform.selfToWorld.apply(this.transform, arguments); }

	/**
	 * Convert local position to world position. same as call parent selfToWorld.
	 * @param {Array<Number>} position The local position. (The local position is relative to the parent)
	 * @param {Boolean} [ignoreScale=false] True indicates ignore scale factor.
	 * @returns {Array<Number>}
	 * @example
	 * let parentObj = new THING.Object3D({
	 * 	position: [0, 5, 0]
	 * });
	 *
	 * let childObj = new THING.Object3D({
	 * 	localPosition: [0, 10, 0],
	 *  parent: parentObj
	 * });
	 *
	 * let worldPos = childObj.localToWorld([0, -5, 0]);
	 *
	 * // print [0, 0, 0]
	 * console.log(worldPos);
	 * @public
	 */
	localToWorld() { return this.transform.localToWorld.apply(this.transform, arguments); }

	/**
	 * Convert world position to local position. same as call parent worldToSelf.
	 * @param {Array<Number>} position The world position.
	 * @param {Boolean} [ignoreScale=false] True indicates ignore scale factor.
	 * @returns {Array<Number>} Returns local position relative to the parent
	 * @example
	 * let parentObj = new THING.Object3D({
	 * 	position: [0, 5, 0]
	 * });
	 *
	 * let childObj = new THING.Object3D({
	 * 	localPosition: [0, 10, 0],
	 * 	parent: parentObj
	 * });
	 *
	 * let localPosition = childObj.worldToLocal([0, 5, 0]);
	 *
	 * // print [0, 0, 0]
	 * console.log(localPosition);
	 * @public
	 */
	worldToLocal() { return this.transform.worldToLocal.apply(this.transform, arguments); }

	/**
	 * Get self quaternion from target position.
	 * @param {Array<Number>} target The target position.
	 * @returns {Array<Number>}
	 * @private
	 */
	getSelfQuaternionFromTarget() { return this.transform.getSelfQuaternionFromTarget.apply(this.transform, arguments); }

	/**
	 * Get self angles from target position.
	 * @param {Array<Number>} target The target position.
	 * @returns {Array<Number>}
	 * @private
	 */
	getSelfAnglesFromTarget() { return this.transform.getSelfAnglesFromTarget.apply(this.transform, arguments); }

	/**
	 * Get world quaternion from target position.
	 * @param {Array<Number>} target The target position.
	 * @returns {Array<Number>}
	 * @private
	 */
	getWorldQuaternionFromTarget() { return this.transform.getWorldQuaternionFromTarget.apply(this.transform, arguments); }

	/**
	 * Get world angles from target position.
	 * @param {Array<Number>} target The target position.
	 * @returns {Array<Number>}
	 * @private
	 */
	getWorldAnglesFromTarget() { return this.transform.getWorldAnglesFromTarget.apply(this.transform, arguments); }

	/**
	 * Get the world position from angles in self space.
	 * @param {Array<Number>} value The angles in self space.
	 * @param {Number} distance The distance in meter(s).
	 * @returns {Array<Number>}
	 * @private
	 */
	getWorldPositionFromSelfAngles() { return this.transform.getWorldPositionFromSelfAngles.apply(this.transform, arguments); }

	/**
	 * Get matrix world.
	 * @returns {Array<Number>}
	 * @private
	 */
	getMatrixWorld() { return this.transform.getMatrixWorld.apply(this.transform, arguments); }

	/**
	 * Set matrix world.
	 * @param {Array<Number>} value The matrix value.
	 * @param {Boolean} [updateMatrix=true] True indicates update all parents matrix world.
	 * @private
	 */
	setMatrixWorld() { return this.transform.setMatrixWorld.apply(this.transform, arguments); }

	/**
	 * Get the local matrix of target object, it would return the relative matrix of target space world.
	 * @param {THING.Object3D} target The target object.
	 * @param {Boolean} [updateMatrix=false] True indicates to update matrix world forcely.
	 * @returns {Array<Number>}
	 * @example
	 * 	let box1 = new THING.Box(10, 5, 2.5);
	 * 	let box2 = new THING.Box(10, 5, 2.5, { position: [0, 10, 0] });
	 * 	THING.Math.mat4.equals(box1.matrixWorld, THING.Math.mat4.mul([], box2.matrixWorld, box1.getLocalMatrix(box2)));
	 * @private
	 */
	getLocalMatrix() { return this.transform.getLocalMatrix.apply(this.transform, arguments); }

	/**
	 * Update self and all parents and children matrix world.
	 * @param {Boolean} updateParents True indicates update all parents.
	 * @param {Boolean} updateChildren True indicates update all children.
	 * @private
	 */
	updateWorldMatrix() { return this.transform.updateWorldMatrix.apply(this.transform, arguments); }

	/**
	 * Update self and all children matrix world.
	 * @private
	 */
	updateMatrixWorld() { return this.transform.updateMatrixWorld.apply(this.transform, arguments); }

	/**
	 * Bind sub node, it will auto update world position by sub node.
	 * The position, angles and scale will lock after binding sub node.
	 * @param {THING.Object3D} target The target.
	 * @param {String} subNodeName The sub node name.
	 * @returns {Boolean}
	 * @example
	 * 	let box = new THING.Box();
	 * 	box.bindSubNode(car, 'chair');
	 * @private
	 */
	bindSubNode() { return this.transform.bindSubNode.apply(this.transform, arguments); }

	/**
	 * Unbind sub node.
	 * @example
	 * 	let box = new THING.Box();
	 * 	box.bindSubNode(car, 'chair');
	 * 	box.unbindSubNode();
	 * @private
	 */
	unbindSubNode() { return this.transform.unbindSubNode.apply(this.transform, arguments); }

	// #endregion

	// #endregion

	get isBaseObject3D() {
		console.warn('Plase use isObject3D instead of isBaseObject3D');
		return true;
	}

}

export { Object3D }