Source: components/BoundingComponent.js

import { Box3, OBB } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { MathUtils } from '../math/MathUtils';
import { BaseComponent } from './BaseComponent';
import { LightSphereModeType, NodeUserDataType, InheritType } from '../const';

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

let _aabb = { center: MathUtils.createVec3(), halfSize: MathUtils.createVec3(), rotation: MathUtils.createQuat() };
let _obb = { center: MathUtils.createVec3(), halfSize: MathUtils.createVec3(), rotation: MathUtils.createQuat() };
let _emptyAABB = new Box3();
let _emptyOBB = new OBB();
let _origin = [0, 0, 0];

// #region Private Functions

// Filter node callback.
function _filterNodeCallback(node) {
	let userData = node.getUserData();
	if (userData[NodeUserDataType.BoundingBoxInheritType] == InheritType.Stop) {
		return false;
	}

	// if the node's parent node type is break
	let parent = node.getParent();
	let parentUserData = parent ? parent.getUserData() : null;
	if (parentUserData && parentUserData[NodeUserDataType.BoundingBoxInheritType] == InheritType.Break) {
		// if node is bodyNode,return true
		// if node is child of that who's type is break,return false
		if (userData[NodeUserDataType.BoundingBoxInheritType] != 'continue') {
			return false;
		}
	}


	if (userData['isDebugNode']) {
		return false;
	}

	if (userData['active'] === false) {
		return false;
	}

	return true;
}

// #endregion

/**
 * @class BoundingComponent
 * The bounding component.
 * @memberof THING
 * @extends THING.BaseComponent
 */
class BoundingComponent extends BaseComponent {

	static mustCopyWithInstance = true;

	/**
	 * The bounding box(AABB, OBB) of object.
	 */
	constructor() {
		super();

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

		_private.inheritType = InheritType.Normal;

		_private.localBoundingBoxResult = null;

		_private.lightSphereModeType = LightSphereModeType.Dynamic;
		_private.lightCenterInSelfSpace = null;
		_private.lightSphere = null;
	}

	// #region Private Functions

	_getNode(recursive) {
		let object = this.object;

		return recursive ? object.node : object.bodyNode;
	}

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

		return _private.lightSphere = _private.lightSphere || {
			center: [0, 0, 0],
			radius: 0,
			shadowRadius: 0
		};
	}

	// #endregion

	getAABB(recursive = true, updateMatrix = true, local = false) {
		let node = this._getNode(recursive);

		// Get bounding box
		if (node.getAABB) {
			let box = node.getAABB(_aabb, _filterNodeCallback, updateMatrix, local);

			if (MathUtils.exactEqualsVector3(box.halfSize, _origin)) {
				return new Box3(this.object.position, [Number.EPSILON, Number.EPSILON, Number.EPSILON]);
			}
			else {
				return new Box3(box.center, MathUtils.fixScaleFactor(box.halfSize));
			}
		}
		else {
			return _emptyAABB;
		}
	}

	getOBB(recursive = true, updateMatrix = true, local = false) {
		let node = this._getNode(recursive);

		// Get oriented bounding box
		if (node.getOBB) {
			let box = node.getOBB(_obb, _filterNodeCallback, updateMatrix, local);

			if (MathUtils.exactEqualsVector3(box.halfSize, _origin)) {
				return new OBB(this.object.position, [Number.EPSILON, Number.EPSILON, Number.EPSILON], box.rotation);
			}
			else {
				return new OBB(box.center, MathUtils.fixScaleFactor(box.halfSize), box.rotation);
			}
		}
		else {
			return _emptyOBB;
		}
	}

	/**
	 * Get the initial local bounding box.
	 * @returns {BoundingBoxResult}
	 * @private
	 */
	getInitialLocalBoundingBox() {
		let _private = this[__.private];

		return _private.localBoundingBoxResult;
	}

	/**
	 * Set the local bounding box.
	 * @param {BoundingBoxResult} value The value.
	 * @private
	 */
	setInitialLocalBoundingBox(value) {
		let _private = this[__.private];

		let bodyNode = this.object.bodyNode;

		if (value) {
			_private.localBoundingBoxResult = {
				center: value.center,
				halfSize: value.halfSize
			};

			if (bodyNode.isRenderableNode) {
				bodyNode.setLocalBoundingBox(_private.localBoundingBoxResult);
			}
		}
		else if (_private.localBoundingBoxResult) {
			_private.localBoundingBoxResult = null;

			if (bodyNode.isRenderableNode) {
				bodyNode.clearLocalBoundingBox();
			}
		}
	}

	/**
	 * @typedef {Object} LightSphereInfo
	 * @property {Array<Number>} center The center of light sphere.
	 * @property {Number} radius The radius of light sphere, 0 indicates use the radius of bounding box.
	 * @property {Number} shadowRadius The shadow's radius of light sphere, 0 indicates use the radius or radius of bounding box.
	 */

	/**
	 * Get light sphere info.
	 * @param {Boolean} [updateMatrix=true] True indicates update matrix for self and all children.
	 * @returns {LightSphereInfo}
	 * @private
	 */
	getLightSphere(updateMatrix = true) {
		let _private = this[__.private];

		let lightSphere = this._getLightSphere();

		switch (_private.lightSphereModeType) {
			case LightSphereModeType.Dynamic:
				let box = this.getAABB(true, updateMatrix);
				lightSphere.center = box.center;
				lightSphere.radius = box.radius;
				break;

			case LightSphereModeType.Static:
				lightSphere.center = this.object.selfToWorld(_private.lightCenterInSelfSpace);
				break;

			default:
				break;
		}

		return lightSphere;
	}

	// #region Accessor

	/**
	 * Get/Set light sphere mode type.
	 * @type {LightSphereModeType}
	 * @private
	 */
	get lightSphereModeType() {
		let _private = this[__.private];

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

		switch (value) {
			case LightSphereModeType.Static:
				let lightSphere = this._getLightSphere();

				let box = this.getAABB(true);
				_private.lightCenterInSelfSpace = this.object.worldToSelf(box.center);
				lightSphere.radius = box.radius;
				break;

			default:
				break;
		}

		_private.lightSphereModeType = value;
	}

	get boundingBox() {
		return this.getAABB();
	}

	get orientedBox() {
		return this.getOBB();
	}

	get initialLocalBoundingBox() {
		return this.getInitialLocalBoundingBox();
	}
	set initialLocalBoundingBox(value) {
		this.setInitialLocalBoundingBox(value);
	}

	/**
	 * Get/Set the bounding box inherit type.
	 * @type {InheritType}
	 * @example
	 * let component = new THING.BoundingComponent();;
	 * component.inheritType = InheritType.Normal
	 * // @expect(component.inheritType == InheritType.Normal)
	 */
	get inheritType() {
		let _private = this[__.private];

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

		if (value == InheritType.Normal) {
			object.node.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Normal;
			object.bodyNode.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Normal;
		}
		else if (value == InheritType.Break) {
			object.node.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Break;
			object.bodyNode.getUserData()[NodeUserDataType.BoundingBoxInheritType] = 'continue';
		}
		else if (value == InheritType.Jump) {
			object.node.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Normal;
			object.bodyNode.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Stop;
		}
		else if (value == InheritType.Stop) {
			object.node.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Stop;
			object.bodyNode.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Stop;
		}

		_private.inheritType = value;
	}

	/**
	 * Get/Set the bounding box inherit type.
	 * @type {InheritType}
	 * @deprecated Since 2.7.0
	 * @private
	 */
	get inheritActionType() {
		Utils.warn(`Please use '.inheritType' attribute, '.inheritActionType' has been deprecated`);

		return this.inheritType;
	}
	set inheritActionType(value) {
		Utils.warn(`Please use '.inheritType' attribute, '.inheritActionType' has been deprecated`);

		this.inheritType = value;
	}

	/**
	 * Get/Set the size of the picked bounding box (set null to clear).
	 * @type {Array<Number>}
	 */
	get pickedSize() {
		let target = this.object.node.getPickBoundingBox();
		if (!target) {
			return null;
		}

		return MathUtils.scaleVector(target.halfSize, 2);
	}
	set pickedSize(value) {
		if (value) {
			let center = this.getAABB().center;

			this.object.node.setPickBoundingBox({ center, halfSize: MathUtils.scaleVector(value, 0.5) });
		}
		else {
			this.object.node.clearPickBoundingBox();
		}
	}

	// #endregion

}

// #endregion
export { BoundingComponent }