import { Utils } from '../common/Utils';
import { MathUtils } from '../math/MathUtils';
import { NodeObject } from './NodeObject';
import { Style } from '../resources/Style';
import { StyleModifier } from '../resources/StyleModifier';
const __ = {
private: Symbol('private'),
}
let _vec3 = MathUtils.createVec3();
let _scale = MathUtils.createVec3();
let _quat = MathUtils.createQuat();
let _mat4 = MathUtils.createMat4();
let _identity = MathUtils.createMat4();
let _axis = [0, 0, 0];
const _defaultOptions = {};
/**
* @class BodyObject
* The body object.
* @memberof THING
*/
class BodyObject {
/**
* The body object to access object's self renderable node(s).
*/
constructor() {
this[__.private] = {};
let _private = this[__.private];
_private.node = null;
_private.object = null;
_private.pivotNode = null;
_private.rootNode = null;
_private.renderableNode = null;
_private.style = null;
}
// #region Private
_createParentNode(name, node) {
// Create parent node
let parentNode = Utils.createObject('Node');
parentNode.setName(name);
// Link to root node
if (node.getParent()) {
node.getParent().add(parentNode);
}
// Keep transform
parentNode.setMatrixWorld(node.getMatrixWorld(_mat4));
node.setMatrix(_identity);
parentNode.add(node);
// Copy user data
let toUserData = parentNode.getUserData();
let fromUserData = node.getUserData();
for (let key in fromUserData) {
toUserData[key] = fromUserData[key];
}
return parentNode;
}
_updateNode() {
let _private = this[__.private];
_private.node = _private.rootNode || _private.pivotNode || _private.renderableNode;
}
_updateNodeOptions(options = _defaultOptions) {
let _private = this[__.private];
let renderableNode = _private.renderableNode;
// Set the local scale
let localScale = options['localScale'];
if (localScale) {
if (localScale[0] != 1 || localScale[1] != 1 || localScale[2] != 1) {
this.createRootNode();
MathUtils.vec3.multiply(_vec3, renderableNode.getScale(_vec3), localScale);
renderableNode.setScale(_vec3);
}
}
// Set the local position(offset)
let localPosition = options['localPosition'];
if (localPosition) {
if (localPosition[0] != 0 || localPosition[1] != 0 || localPosition[2] != 0) {
this.createRootNode();
MathUtils.vec3.add(_vec3, renderableNode.getPosition(_vec3), localPosition)
renderableNode.setPosition(_vec3);
}
}
// Set the local angles
let localAngles = options['localAngles'];
if (localAngles) {
if (localAngles[0] != 0 || localAngles[1] != 0 || localAngles[2] != 0) {
this.createRootNode();
MathUtils.quat.multiply(_quat, renderableNode.getQuaternion(_quat), MathUtils.getQuatFromAngles(localAngles));
renderableNode.setQuaternion(_quat);
}
}
}
_getNodeByName(name, rootNode, filter, complete) {
let _private = this[__.private];
let nodes = this._getSubNodes(_private.renderableNode, rootNode);
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
if (filter) {
if (!filter(node)) {
continue;
}
}
if (node.name == name) {
if (complete) {
return complete(node);
}
else {
return node;
}
}
}
return null;
}
// #endregion
// #region BaseComponent
dispose() {
let _private = this[__.private];
// Unbind sub node for renderable node
let renderableNode = this.getRenderableNode();
if (renderableNode) {
renderableNode.unbindSubNode();
}
// Unapply current style
if (_private.style) {
_private.style.dispose();
_private.style = null;
}
if (_private.rootNode) {
_private.rootNode.dispose();
}
else if (_private.pivotNode) {
_private.pivotNode.dispose();
}
else if (_private.renderableNode) {
_private.renderableNode.dispose();
}
_private.rootNode = null;
_private.pivotNode = null;
_private.renderableNode = null;
_private.node = null;
_private.object = null;
}
// #endregion
init(object, options = _defaultOptions) {
let _private = this[__.private];
_private.object = object;
let node = options['node'];
let rootNode = options['rootNode'];
let renderableNode = options['renderableNode'];
if (rootNode || renderableNode) {
_private.rootNode = rootNode;
_private.renderableNode = renderableNode || rootNode;
}
else {
let app = object.app;
let nodePool = app.resourceManager.getNodePool();
_private.renderableNode = nodePool ? nodePool.alloc() : Utils.createObject('Node');
if (node) {
_private.renderableNode.add(node);
}
}
// Update current node
this._updateNode();
// Update node options
let bodyOptions = options['body'];
if (bodyOptions) {
this._updateNodeOptions(bodyOptions);
}
// Bind renderable node with object
let userData = _private.node.getUserData();
userData['baseObject_orderID'] = _private.object.orderId;
}
translateOnAxis(axis, distance) {
MathUtils.vec3.normalize(_axis, axis);
MathUtils.vec3.transformQuat(_vec3, _axis, this.localQuaternion);
MathUtils.vec3.scale(_vec3, _vec3, distance);
let position = this.localPosition;
this.localPosition = MathUtils.addVector(position, _vec3);
}
translateX(distance) {
this.translateOnAxis(Utils.xAxis, distance);
}
translateY(distance) {
this.translateOnAxis(Utils.yAxis, distance);
}
translateZ(distance) {
this.translateOnAxis(Utils.zAxis, distance);
}
// #region State
/**
* Get/Set visible state.
* @type {Boolean}
* @example
* let object = new THING.Object3D();
* let ret = object.body.visible == true;
* // @expect(ret == true)
* object.body.visible = false;
* ret = object.body.visible == false;
* // @expect(ret == true)
*/
get visible() {
let _private = this[__.private];
return _private.renderableNode.getVisible();
}
set visible(value) {
let _private = this[__.private];
_private.renderableNode.setVisible(value);
}
// #endregion
// #region Transform
/**
* Get/Set local(offset) position of the parent space.
* @type {Array<Number>}
* @example
* let object = new THING.Object3D();
* let ret = object.body.localPosition[1] == 0;
* // @expect(ret == true)
* object.body.localPosition = [0, 10, 0];
* ret = object.body.localPosition[1] == 10;
* // @expect(ret == true)
*/
get localPosition() {
let _private = this[__.private];
let target = [0, 0, 0];
return _private.renderableNode.getPosition(target);
}
set localPosition(value) {
let _private = this[__.private];
this.createRootNode();
_private.renderableNode.setPosition(value);
}
/**
* Get/Set quaternion of the inertial space.
* @type {Array<Number>}
* @example
* let object = new THING.Object3D();
* let ret = object.body.localQuaternion[1] == 0;
* // @expect(ret == true)
* object.body.localQuaternion = [0, 10, 0, 1];
* ret = object.body.localQuaternion[1] == 10;
* // @expect(ret == true)
* object.body.localQuaternion = [0, 10, 0, 1];
*/
get localQuaternion() {
let _private = this[__.private];
let target = [0, 0, 0, 1]
return _private.renderableNode.getQuaternion(target);
}
set localQuaternion(value) {
let _private = this[__.private];
this.createRootNode();
_private.renderableNode.setQuaternion(value);
}
get quaternion() {
let _private = this[__.private];
let target = [0, 0, 0, 1]
return _private.renderableNode.getWorldQuaternion(target);
}
set quaternion(value) {
let _private = this[__.private];
this.createRootNode();
_private.renderableNode.setWorldQuaternion(value);
}
/**
* Get/Set rotation of the inertial space.
* @type {Array<Number>}
* @example
* let object = new THING.Object3D();
* let ret = object.body.localRotation[1] == 0;
* // @expect(ret == true)
* object.body.localRotation = [0, 10, 0];
* ret = object.body.localRotation[1] == 10;
* // @expect(ret == true)
*/
get localRotation() {
return this.localAngles;
}
set localRotation(value) {
this.localAngles = value;
}
get localAngles() {
let _private = this[__.private];
let target = [0, 0, 0, 1]
return MathUtils.getAnglesFromQuat(_private.renderableNode.getQuaternion(target));
}
set localAngles(value) {
let _private = this[__.private];
this.createRootNode();
_private.renderableNode.setQuaternion(MathUtils.getQuatFromAngles(value));
}
/**
* Get/Set scale of the self coordinate system.
* @type {Array<Number>}
* @example
* let object = new THING.Object3D();
* let ret = object.body.localScale[1] == 1;
* // @expect(ret == true)
* object.body.localScale = [10, 10, 10];
* ret = object.body.localScale[1] == 10;
* // @expect(ret == true)
*/
get localScale() {
let _private = this[__.private];
let target = [1, 1, 1];
return _private.renderableNode.getScale(target);
}
set localScale(value) {
let _private = this[__.private];
this.createRootNode();
_private.renderableNode.setScale(value);
}
/**
* Get/Set world position of the world space.
* @type {Array<Number>}
* @example
* let object = new THING.Object3D();
* let ret = object.body.position[1] == 0;
* // @expect(ret == true)
* object.body.position = [10, 10, 10];
* ret = object.body.position[1] == 10;
* // @expect(ret == true)
*/
get position() {
let _private = this[__.private];
let target = [0, 0, 0];
return _private.renderableNode.getWorldPosition(target);
}
set position(value) {
let _private = this[__.private];
this.createRootNode();
_private.renderableNode.setWorldPosition(value);
}
/**
* Get/Set rotation of the world space.
* @type {Array<Number>}
* @example
* let object = new THING.Object3D();
* let ret = object.body.rotation[1] == 0;
* // @expect(ret == true)
* object.body.rotation = [10, 10, 10];
* ret = object.body.rotation[1] == 10;
* // @expect(ret == true)
*/
get rotation() {
return this.angles;
}
set rotation(value) {
this.angles = value;
}
get angles() {
let _private = this[__.private];
let target = [0, 0, 0, 1]
return MathUtils.getAnglesFromQuat(_private.renderableNode.getWorldQuaternion(target));
}
set angles(value) {
let _private = this[__.private];
this.createRootNode();
_private.renderableNode.setWorldQuaternion(MathUtils.getQuatFromAngles(value));
}
/**
* Get/Set scale of the world coordinate system.
* @type {Array<Number>}
* @example
* let object = new THING.Object3D();
* let ret = object.body.scale[1] == 1;
* // @expect(ret == true)
* object.body.scale = [10, 10, 10];
* ret = object.body.scale[1] == 10;
* // @expect(ret == true)
*/
get scale() {
let _private = this[__.private];
let target = [1, 1, 1];
return _private.renderableNode.getWorldScale(target);
}
set scale(value) {
let _private = this[__.private];
this.createRootNode();
_private.renderableNode.setWorldScale(value);
}
/**
* Get/Set the transform.
* @type {Array<Number>}
* @example
* let object = new THING.Object3D();
* let ret = object.body.matrix[12] == 0;
* // @expect(ret == true)
* object.body.matrix =THING.Math.mat4.fromTranslation([], [10, 10, 10]);
* ret = object.body.matrix[12] == 10;
* // @expect(ret == true)
*/
get matrix() {
let _private = this[__.private];
return _private.renderableNode.getMatrix();
}
set matrix(value) {
let _private = this[__.private];
this.createRootNode();
_private.renderableNode.setMatrix(value);
}
/**
* Get/Set the world transform.
* @type {Array<Number>}
* @example
* let object = new THING.Object3D();
* let ret = object.body.matrixWorld[12] == 0;
* // @expect(ret == true)
* object.body.matrixWorld =THING.Math.mat4.fromTranslation([], [10, 10, 10]);
* ret = object.body.matrixWorld[12] == 10;
* // @expect(ret == true)
*/
get matrixWorld() {
let _private = this[__.private];
return _private.renderableNode.getMatrixWorld();
}
set matrixWorld(value) {
let _private = this[__.private];
this.createRootNode();
_private.renderableNode.setMatrixWorld(value);
}
// #endregion
// #region BoundingBox
/**
* @typedef {Object} BoundingBoxResult
* @property {Array<Number>} center The center of box.
* @property {Array<Number>} halfSize The half size of box.
*/
/**
* Get the local bounding box of body.
* @returns {BoundingBoxResult}
* @private
*/
getLocalBoundingBox() {
let target = {
center: [0, 0, 0],
halfSize: [0, 0, 0],
};
let node = this.getRenderableNode();
if (node.isRenderableNode) {
node.getLocalBoundingBox(target);
}
else {
node.getWorldPosition(target.center);
}
return target;
}
// #endregion
// #region Resource
/**
* Create the root node.
* @private
*/
createRootNode() {
let _private = this[__.private];
if (!_private.rootNode) {
let node = _private.renderableNode;
if (node.isRenderableNode) {
_private.rootNode = this._createParentNode('root', node);
}
else {
_private.rootNode = node;
}
this._updateNode();
}
}
/**
* Clear pivot node.
* @private
*/
clearPivotNode() {
let _private = this[__.private];
let pivotNode = _private.pivotNode;
if (!pivotNode) {
return;
}
let parentNode = pivotNode.getParent();
if (parentNode) {
let children = pivotNode.getChildren().slice(0);
children.forEach(child => {
child.setPosition([0, 0, 0]);
parentNode.attach(child);
});
parentNode.remove(pivotNode);
}
pivotNode.dispose();
_private.pivotNode = null;
this._updateNode();
}
/**
* Create pivot node.
* @private
*/
createPivotNode() {
let _private = this[__.private];
if (!_private.pivotNode) {
let node = _private.renderableNode;
_private.pivotNode = this._createParentNode('pivot', node);
this._updateNode();
}
return _private.pivotNode;
}
/**
* Get the pivot node.
* @type {Object}
* @private
*/
getPivotNode() {
return this[__.private].pivotNode;
}
/**
* Get the root node.
* @type {Object}
* @private
*/
getRootNode() {
return this[__.private].rootNode;
}
/**
* Get the renderable node.
* @type {Object}
* @private
*/
getRenderableNode() {
return this[__.private].renderableNode;
}
/**
* Get the node.
* @type {Object}
* @private
*/
getNode() {
return this[__.private].node;
}
/**
* Set node.
* @param {Object} node The node.
* @private
*/
setNode(node) {
let _private = this[__.private];
// Get the renderable node
let renderableNode = _private.renderableNode;
// Get the previous node's matrix to sync
// Becareful: do not use matrix here, due to local angles would be changed by negative scale or some angels value
renderableNode.getPosition(_vec3);
renderableNode.getScale(_scale);
renderableNode.getQuaternion(_quat);
// Get the current states
let curStates = {
parentNode: renderableNode.getParent(),
name: renderableNode.getName(),
userData: renderableNode.getUserData()
};
// Get the current render node stats
if (renderableNode.isRenderableNode) {
if (renderableNode.hasStyle()) {
curStates.style = renderableNode.getStyle();
}
curStates.visible = renderableNode.getVisible();
curStates.renderOrder = renderableNode.getRenderOrder();
curStates.castShadow = renderableNode.getCastShadow();
curStates.receiveShadow = renderableNode.getReceiveShadow();
curStates.layerMask = renderableNode.getLayerMask();
}
let app = _private.object.app;
// Dispose current node
let nodePool = app.resourceManager.getNodePool();
if (nodePool) {
nodePool.free(renderableNode);
}
else {
renderableNode.dispose();
}
// Update node
renderableNode = node;
_private.renderableNode = node;
// Resume matrix
renderableNode.setPosition(_vec3);
renderableNode.setScale(_scale);
renderableNode.setQuaternion(_quat);
// Relink to parent node
if (curStates.parentNode) {
curStates.parentNode.add(renderableNode);
}
// Resume the states
renderableNode.setName(curStates.name);
renderableNode.setUserData(curStates.userData);
if (renderableNode.isRenderableNode) {
if (curStates.style) {
if (this.style.isChanged) {
let cloneStyle = curStates.style.clone();
let oriStyle = renderableNode.getStyle();
this.style.initResource(oriStyle);
this.style.updateResource(cloneStyle);
}
}
renderableNode.setVisible(curStates.visible);
if (renderableNode.getRenderOrder() != curStates.renderOrder) {
renderableNode.setRenderOrder(curStates.renderOrder);
}
if (renderableNode.getCastShadow() != curStates.castShadow) {
renderableNode.setCastShadow(curStates.castShadow);
}
if (renderableNode.getReceiveShadow() != curStates.receiveShadow) {
renderableNode.setReceiveShadow(curStates.receiveShadow);
}
if (renderableNode.getLayerMask() != curStates.layerMask) {
renderableNode.setLayerMask(curStates.layerMask);
}
}
this._updateNode();
}
/**
* Just update node.
* @param {Object} node The node.
* @private
*/
updateNode(node) {
let _private = this[__.private];
_private.renderableNode = node;
this._updateNode();
}
/**
* Unload resource.
* @param {Function} onCreateBodyNode When create body node callback function.
* @private
*/
unloadResource(onCreateBodyNode) {
let _private = this[__.private];
let renderableNode = _private.renderableNode;
if (!renderableNode) {
return;
}
// Get the user data and prepare to resume
let userData = renderableNode.getUserData();
// Get some useful info and wait to resume
let parentNode = renderableNode.getParent();
let visible = renderableNode.getVisible();
let type = renderableNode.getType();
let name = renderableNode.getName();
let matrix = renderableNode.getMatrix(_mat4);
// Get style
let style;
if (renderableNode.isRenderableNode) {
style = renderableNode.getStyle();
}
// Destroy renderable node
renderableNode.dispose();
// Recreate renderable node
renderableNode = onCreateBodyNode(type);
renderableNode.setName(name);
// Resume info
parentNode.add(renderableNode);
renderableNode.setMatrix(matrix);
renderableNode.setUserData(userData);
renderableNode.setVisible(visible);
if (style) {
renderableNode.setStyle(style);
}
// Update renderable node
_private.renderableNode = renderableNode;
// Update node
this._updateNode();
}
getGeometryInfo() {
return this.getRenderableNode().getGeometryInfo();
}
// #endregion
// #region SubNodes
traverseNodes(callback) {
let _private = this[__.private];
let target = this._getSubNodes(_private.renderableNode, _private.node);
target.forEach(node => {
let nodeObject = new NodeObject({
renderableNode: _private.renderableNode,
node
});
callback(nodeObject);
});
}
/**
* Get nodes sorted by node depth.
* @type {Array<NodeObject>}
* @private
*/
get nodes() {
let _private = this[__.private];
let target = this._getSubNodes(_private.renderableNode, _private.node);
let subNodes = target.sort(function (a, b) {
return b.depth - a.depth;
}).map(node => {
return new NodeObject({
renderableNode: _private.renderableNode,
node
});
});
return subNodes;
}
/**
* Get the renderable nodes sorted by node depth.
* @type {Array<NodeObject>}
* @private
*/
get renderableNodes() {
let _private = this[__.private];
let target = this._getSubNodes(_private.renderableNode, _private.node);
let subNodes = target.filter(node => {
return node.isRenderable;
}).sort(function (a, b) {
return b.depth - a.depth;
}).map(node => {
return new NodeObject({
renderableNode: _private.renderableNode,
node
});
});
return subNodes;
}
_getSubNodes(renderableNode, rootNode) {
let subNodes = [];
let name = renderableNode.getName();
renderableNode.getSubNodes(subNodes, rootNode);
return subNodes.filter(subNode => {
return subNode.name !== name;
})
}
/**
* Get node names sorted by node depth.
* @type {Array<String>}
* @private
*/
get nodeNames() {
let _private = this[__.private];
// Use 'null' to skip matrix calculation
let subNodes = this._getSubNodes(_private.renderableNode, null);
let names = subNodes.sort(function (a, b) {
return b.depth - a.depth;
}).map(node => {
return node.name;
});
return names;
}
/**
* Get sub node result by name.
* @param {String} name The node name.
* @returns {NodeObject}
* @private
*/
getNodeByName(name) {
let _private = this[__.private];
return this._getNodeByName(name, _private.node, null,
(node) => {
return new NodeObject({
renderableNode: _private.renderableNode,
node
});
}
);
}
/**
* Promote node to object.
* @param {String} name The node name.
* @param {Object} parentNode The parent node.
* @returns {Object}
* @private
*/
promoteNode(name, parentNode) {
// Find sub node by name
let node = this._getNodeByName(name, null);
if (!node) {
return null;
}
// Attach sub node to parent
return parentNode.attachSubNode(node, {
keepLocalTransform: true
});
}
// #endregion
// #region Style
hasStyle() {
let _private = this[__.private];
return !!_private.style;
}
getStyle() {
let _private = this[__.private];
if (!_private.style) {
let bodyNode = this.getRenderableNode();
if (!bodyNode.isRenderableNode) {
return null;
}
_private.style = new Style(new StyleModifier(_private.object));
}
return _private.style;
}
setStyle(value) {
if (!value) {
return;
}
let style = this.getStyle();
if (style == value) {
return;
}
style.copy(value);
}
/**
* Get/Set style of body.
* @type {THING.Style}
* @example
* let object = new THING.Object3D();
* let style = object.body.style;
* style.color = 'red';
* style.opacity = 0.1;
* let ret = object.body.style.color[0] == 1;
* // @expect(ret == true)
* ret = object.body.style.opacity == 0.1;
* // @expect(ret == true)
*/
get style() {
return this.getStyle();
}
set style(value) {
this.setStyle(value);
}
// #endregion
}
export {
BodyObject
}