import { MathUtils } from '../math/MathUtils';
import { BaseTickableObject3D } from './BaseTickableObject3D';
import { ObjectAttributes } from '@uino/base-thing';
const __ = {
private: Symbol('private'),
}
let _vec3_1 = MathUtils.createVec3();
let _vec3_2 = MathUtils.createVec3();
let _mat3 = MathUtils.createMat3();
let _mat4 = MathUtils.createMat4();
// #region Private Functions
// Convert clipping plane to world space.
function _convertPlaneToWorldSpace(plane, matrixWorld) {
let dir_1 = MathUtils.vec3.copy(_vec3_1, plane.direction);
let dir_2 = MathUtils.vec3.copy(_vec3_2, plane.direction);
const normalMatrix = MathUtils.mat3.normalFromMat4(_mat3, matrixWorld);
const referencePoint = MathUtils.vec3.transformMat4(dir_1, MathUtils.vec3.scale(dir_1, dir_1, -plane.height), matrixWorld);
const normal = MathUtils.vec3.normalize(dir_2, MathUtils.vec3.transformMat3(dir_2, dir_2, normalMatrix));
const constant = -MathUtils.vec3.dot(referencePoint, normal);
return {
direction: normal.slice(0),
height: constant
}
}
// #endregion
/**
* ClippingPlanes - 裁剪平面
*
* 提供创建裁剪面的能力,支持六个方向(上、下、左、右、前、后)的平面裁剪。
* 裁剪面需要在对象的 style 下使用。
*
* @class ClippingPlanes
* @memberof THING
* @extends THING.BaseTickableObject3D
* @public
*
* @example
* const planes = new THING.ClippingPlanes({
* planes: [
* { direction: [0, -1, 0], height: 6 },
* { direction: [0, 1, 0], height: 2 },
* ],
* });
* obj.style.clippingPlanes = planes;
*/
class ClippingPlanes extends BaseTickableObject3D {
/**
*
* 提供创建裁剪面的能力 裁剪面需要在对象的 style 下使用。
* @param {object} param 初始化参数列表
* @param {ClippingPlaneResult[]} param.planes 上,下,左,右,前,后的平面法线方向以及距离 每个裁剪面的格式为{ direction: [x, y, z], height: h }
* @example
let clippingPlanes = [
{ direction: [0, -1, 0], height: 6 }, // top
{ direction: [0, 1, 0], height: 2 }, // bottom
{ direction: [-1, 0, 0], height: 10 }, // right
{ direction: [1, 0, 0], height: 10 }, // left
{ direction: [0, 0, -1], height: 10 }, // front
{ direction: [0, 0, 1], height: 10 } // back
];
let clippingPlanesObject = new THING.ClippingPlanes({
parent: parentObj,
planes: clippingPlanes,
});
parentObj.style.clippingPlanes = clippingPlanesObject;
* @constructor
* @public
*/
constructor(param) {
super(param);
this[__.private] = {};
let _private = this[__.private];
_private.lastMat4 = null;
_private.resources = [];
_private.worldPlanes = null;
// Create ObjectAttributes to monitor planes array changes
_private.planesData = new ObjectAttributes({
data: { planes: null },
onChange: (ev) => {
this._updatePlanes(true);
},
onNeedProxy: (prop, value) => {
return true;
},
onCloneObject: (data) => {
return {
planes: data.planes ? data.planes.map(plane => ({
direction: plane.direction.slice(0),
height: plane.height
})) : null
};
}
});
let planes = param['planes'];
if (planes) {
this.planes = planes;
}
}
// #region Private
_updatePlanes(force) {
let _private = this[__.private];
// Get the object's matrix world
this.node.getMatrixWorld(_mat4);
// Check whether matrix world has been changed
if (!force) {
if (_private.lastMat4) {
if (MathUtils.mat4.equals(_mat4, _private.lastMat4)) {
return;
}
}
else {
_private.lastMat4 = MathUtils.createMat4();
}
}
// Keep matrix world to latest
if (_private.lastMat4) {
MathUtils.mat4.copy(_private.lastMat4, _mat4);
}
// Convert local planes to world planes
_private.worldPlanes = _private.planesData.getValue('planes').map(plane => {
return _convertPlaneToWorldSpace(plane, _mat4);
});
// Update styles's resource clipping planes
_private.resources.forEach(resource => {
resource.setClippingPlanes(_private.worldPlanes);
});
}
// #endregion
// #region BaseObject Interface
onUpdate(deltaTime) {
super.onUpdate(deltaTime);
let _private = this[__.private];
let planes = _private.planesData.getValue('planes');
if (!planes) {
return;
}
if (_private.resources.length) {
this._updatePlanes();
}
}
// #endregion
addStyleResource(resource) {
let _private = this[__.private];
if (_private.resources.indexOf(resource) !== -1) {
return;
}
_private.resources.push(resource);
resource.setClippingPlanes(_private.worldPlanes);
}
removeStyleResource(resource) {
let _private = this[__.private];
let index = _private.resources.indexOf(resource);
if (index === -1) {
return;
}
_private.resources.splice(index, 1);
resource.setClippingPlanes(null);
}
// #endregion
// #region Accessor
/**
* @typedef {object} ClippingPlaneResult
* @property {Array<number>} direction 世界坐标系下的方向
* @property {number} height 高度,单位:米
* @public
*/
/**
* 获取/设置裁剪平面。
* @type {Array<ClippingPlaneResult>}
* @public
*/
get planes() {
let _private = this[__.private];
return _private.planesData.getValue('planes');
}
set planes(value) {
let _private = this[__.private];
_private.planesData.setValue('planes', value);
}
// #endregion
/**
* 检查是否为ClippingPlanes类型或继承自它。
* @type {boolean}
* @public
*/
get isClippingPlanes() {
return true;
}
}
export { ClippingPlanes }