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, AccuracyMode } 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
* 包围盒组件。
* @memberof THING
* @extends THING.BaseComponent
* @public
*/
class BoundingComponent extends BaseComponent {
static mustCopyWithInstance = true;
/**
* 包围盒(AABB, OBB)组件。
*/
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;
_private.accuracyMode = AccuracyMode.Basic;
}
// #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);
const _private = this[__.private];
let funName = _private.accuracyMode === AccuracyMode.Accurate ? 'getAccurateAABB' : 'getAABB';
// Get bounding box
if (node[funName]) {
let box = node[funName](_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);
const _private = this[__.private];
let funName = _private.accuracyMode === AccuracyMode.Accurate ? 'getAccurateOBB' : 'getOBB';
// Get oriented bounding box
if (node[funName]) {
let box = node[funName](_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;
}
}
getSelfBoundingBox() {
const _private = this[__.private];
const node = this.object.body.getRenderableNode();
const target = {
center: [0, 0, 0],
halfSize: [Number.EPSILON, Number.EPSILON, Number.EPSILON]
};
const funName = _private.accuracyMode === AccuracyMode.Accurate ? 'getAccurateAABB' : 'getAABB';
// Get bounding box
if (node[funName]) {
node[funName](target, _filterNodeCallback, true, 0);
}
return target;
}
/**
* 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;
}
/**
* 获取世界坐标系下的包围盒(AABB)。
* @returns {Box3}
* @example
* let aabb = object.bounding.boundingBox;
* console.log('center: ', aabb.center);
* console.log('halfSize: ', aabb.halfSize);
* @public
*/
get boundingBox() {
return this.getAABB();
}
/**
* 获取世界坐标系下的定向包围盒(OBB)。
* @returns {OBB}
* @example
* let obb = object.bounding.orientedBox;
* console.log('center: ', obb.center);
* console.log('halfSize: ', obb.halfSize);
* console.log('angles: ', obb.angles);
* @public
*/
get orientedBox() {
return this.getOBB();
}
/**
* 获取/设置初始局部包围盒。
* @type {BoundingBoxResult}
* @example
* // 设置初始局部包围盒
* object.bounding.initialLocalBoundingBox = {
* center: [0, 0, 0],
* halfSize: [5, 5, 5]
* };
*
* // 清除初始局部包围盒
* object.bounding.initialLocalBoundingBox = null;
* @public
*/
get initialLocalBoundingBox() {
return this.getInitialLocalBoundingBox();
}
set initialLocalBoundingBox(value) {
this.setInitialLocalBoundingBox(value);
}
/**
* 获取/设置包围盒继承类型。
* @type {InheritType}
* @example
* // 对象将进行包围盒计算,但是子对象将被忽略
* object.bounding.inheritType = InheritType.Break
* // 对象将跳过包围盒计算,但是子对象将被正常计算进包围盒
* object.bounding.inheritType = InheritType.Jump
* // 对象将中断包围盒计算,且子对象也将被忽略
* object.bounding.inheritType = InheritType.Stop
* @public
*/
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;
}
/**
* 获取/设置选中包围盒的尺寸(设置为 null 可清除)。
* @type {Array<number>}
* @example
* // 获取当前选中包围盒的尺寸
* let size = object.bounding.pickedSize;
*
* // 设置选中包围盒的尺寸
* object.bounding.pickedSize = [10, 20, 30];
*
* // 清除选中包围盒
* object.bounding.pickedSize = null;
* @public
*/
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();
}
}
/**
* 获取/设置精度模式。
* @example
* // 用于在包围盒计算精度和性能之间进行权衡,精确模式提供更准确的包围盒但性能消耗更大(通常建议使用基础模式, 只在需要特别精确的包围盒数据时才临时切换到精确模式)。可以参考以下示例使用该接口:
* // 设置精度模式为精确模式
* object.bounding.accuracyMode = THING.AccuracyMode.Accurate;
* // 获取包围盒
* let boundingBox = object.bounding.boundingBox;
* // 设置初始局部包围盒
* object.bounding.initialLocalBoundingBox = boundingBox;
* // 设置精度模式为基础模式
* object.bounding.accuracyMode = THING.AccuracyMode.Basic;
* @type {AccuracyMode}
* @public
*/
get accuracyMode() {
const _private = this[__.private];
return _private.accuracyMode;
}
set accuracyMode(value) {
const _private = this[__.private];
_private.accuracyMode = value;
}
// #endregion
}
// #endregion
export { BoundingComponent }