import { ResolvablePromise, TypedObject, ObjectAttributes, Flags } 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, SpaceType, Transform } from '../const';
import { MonitorDataComponent } from '../components/MonitorDataComponent';
import { Tags } from '../common/Tags';
const __ = {
private: Symbol('private'),
}
// [0-7] index is reserved for BaseObject, so index starts from 8
const Flag = {
// Common
Initializing: 1 << 8,
Initialized: 1 << 9,
// State
Pickable: 1 << 10,
Active: 1 << 11,
FrustumCulled: 1 << 12,
// On/Off
KeepSize: 1 << 13,
HadPromoted: 1 << 14,
Instance: 1 << 15,
}
const BreakInstanceFlag = {
AlwaysOnTop: 1 << 0,
CastShadow: 1 << 1,
ReceiveShadow: 1 << 2,
LayerMask: 1 << 3
}
const registerComponentParam = { isResident: true };
const cTranslateByLerpName = '__translateByLerp__';
function _getStyleInheritance(object) {
if (!object.hasInherit()) {
return InheritType.Normal;
}
return object.inherit.style;
}
/**
* @class Object3D
* ThingJS中最基础的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,
};
/**
* 3D对象类,负责ThingJS中最基础的3D对象功能实现
* @param {object} param 初始化参数
* @param {string} [param.name=''] 物体名称
* @param {string} [param.id=''] 物体id
* @param {string} [param.uuid] 物体唯一标识
* @param {object} [param.userData] 设置/获取 物体的自定义属性
* @param {boolean} [param.queryabled=true] 物体是否可被查询 默认true
* @param {boolean} [param.destroyable=true] 物体是否可被销毁 默认true
* @param {Array<number>} [param.userData=''] 对象name
* @param {Array<number>} [param.localPosition=[0,0,0]] 相对父物体的位置
* @param {Array<number>} [param.localScale=[1,1,1]] 相对父物体的缩放
* @param {Array<number>} [param.localAngles=[0,0,0]] 相对父物体的旋转角度
* @param {Array<number>} [param.position=[0,0,0]] 世界坐标系下的位置
* @param {Array<number>} [param.scale=[1,1,1]] 世界坐标系下的缩放
* @param {Array<number>} [param.angles=[0,0,0]] T世界坐标系下的旋转角度
* @param {boolean} [param.pickable=true] 是否可拾取
* @param {boolean} [param.visible=true] 是否可见 详见visible的属性说明
* @param {boolean} [param.active=true] 是否激活 详见active的属性说明
* @param {boolean} [param.keepSize] 是否保持像素级别大小不变
* @param {boolean} [param.keepSizeUseBodyLocalScale=false] keepSize时是否使用 body.localScale(true)而不是 object.localScale(false)
* @param {Array<number>} [param.pivot] 轴心点在自身包围盒中的位置(数组每一个分量取值范围0-1)
* @param {object} [param.style] 样式设置
* @param {boolean} [param.castShadow] 是否投射阴影
* @param {boolean} [param.receiveShadow] 是否接收阴影
* @param {boolean} [param.alwaysOnTop] 是否始终在顶部
* @param {Function} [param.onError] 创建失败的回调
* @param {Function} [param.onComplete] 创建成功的回调
* @param {Function} [param.onSyncComplete] 同步创建成功的回调
* @param {Function} [param.onSyncBeforeDestroy] 同步销毁前的回调
* @example
* let obj = new THING.Object3D({
* name: 'box',
* position: [0, 0, 0],
* style: {
* color: 'red'
* },
* onError: function(error) {
* console.error(error);
* },
* onComplete: function() {
* console.log('创建成功');
* }
* @constructor
* @public
*/
constructor(param = {}) {
super(param);
this[__.private] = {};
let _private = this[__.private];
// Common
_private.external = null;
_private.styleTag = '';
// Inheritance
_private.inherit = null;
const inherit = param.inherit;
if (inherit) {
if (inherit.visible) {
this.inherit.visible = inherit.visible;
}
if (inherit.pickable) {
this.inherit.pickable = inherit.pickable;
}
if (inherit.style) {
this.inherit.style = inherit.style;
}
if (inherit.renderOrder) {
this.inherit.renderOrder = inherit.renderOrder;
}
}
// 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 | Flag.FrustumCulled, 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.onTaskExecutorComplete(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();
}
}
_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;
case 'uvData':
let prevImageSlotType = style.imageSlotType;
for (let uvKey in value) {
style.imageSlotType = uvKey;
style.uv = value[uvKey];
}
style.imageSlotType = prevImageSlotType;
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;
}
_updateInstanceState(flag, value) {
this._breakInstanceFlags.enable(flag, value);
if (!this._flags.has(Flag.Instance)) {
return;
}
// 更新instance状态
if (this._breakInstanceFlags.values === 0) {
this.render.makeInstancedDrawing(true, this._makeInstancedDrawingOptions);
}
else {
if (!this.instanceGroupName) {
this.render.makeInstancedDrawing(false);
}
}
}
// #endregion
// #region Overrides
onSetupFlags(param) {
super.onSetupFlags(param);
this._breakInstanceFlags = new Flags();
}
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);
this._isDynamicLoad = Utils.parseValue(param['isDynamicLoad'], false) || Utils.parseValue(param['dynamic'], false);
// If we provide root or renderable node then indicates resource is ready
let rootNode = param['rootNode'];
let renderableNode = param['renderableNode'];
if (rootNode || renderableNode) {
if (!this._isDynamicLoad) {
this._resourceState = Object3D.ResourceState.Loaded;
}
}
}
onSetupAttributes(param) {
let _private = this[__.private];
const styleTag = param['styleTag'];
if (styleTag) {
_private.styleTag = styleTag;
}
// Setup extras
let external = param['extras'] || param['external'];
if (external) {
_private.external = Object.assign({}, external);
}
const instanceGroupName = param['instanceGroupName'];
if (instanceGroupName) {
this.instanceGroupName = instanceGroupName;
}
}
onSetupState(options) {
// We are loading resource now
if (!this._isDynamicLoad && this._resourceState != Object3D.ResourceState.Loaded) {
this._resourceState = Object3D.ResourceState.Loading;
}
// Setup pickable
let pickable = Utils.parseValue(options['pickable'], true);
if (!pickable) {
this.setPickable(false, false);
}
// Setup visibility
let visible = Utils.parseValue(options['visible'], true);
if (!visible) {
this.setVisible(false, false);
}
// Setup active
let active = Utils.parseValue(options['active'], true);
if (!active) {
this.setActive(false, false);
}
// Setup render attributes
if (Utils.isNumber(options['renderOrder'])) {
this.setRenderOrder(options['renderOrder'], false)
}
if (Utils.isBoolean(options['castShadow'])) {
this.setCastShadow(options['castShadow'], false);
}
if (Utils.isBoolean(options['receiveShadow'])) {
this.setReceiveShadow(options['receiveShadow'], false);
}
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'];
}
else 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 (Utils.isValid(param['keepSizeDistanceLimited'])) {
this.keepSizeDistanceLimited = param['keepSizeDistanceLimited'];
}
if (Utils.isValid(param['keepSizeUseBodyLocalScale'])) {
this.keepSizeUseBodyLocalScale = param['keepSizeUseBodyLocalScale'];
}
if (param['keepSize']) {
this.keepSize = param['keepSize'];
}
}
onSetupStyle(param) {
let styleValues = this._getStyleValues(param['style']);
if (!styleValues) {
return;
}
let style = this.body.style;
if (!style) {
return;
}
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, '');
_private.resource.isSceneRoot = Utils.parseBoolean(param.isSceneRoot, false)
_private.resource.isSceneNode = Utils.parseBoolean(param.isSceneNode, false)
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'];
let toolkit = param['toolkit'];
if (components && toolkit) {
toolkit['workPath'] = param['workPath'] || '';
this.onImportComponents(components, toolkit, param);
}
// Start to load resource
this._onLoadResource(param,
// Resolve
() => {
if (!this._isDynamicLoad) {
this.onLoadComplete();
}
},
// Reject
(ev) => {
let _private = this[__.private];
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);
this._flags.enable(Flag.Initialized, true);
// Set ignore parent transform
let ignoreParentTransform = param['ignoreParentTransform'];
if (ignoreParentTransform) {
this.ignoreParentTransform = ignoreParentTransform;
}
}
/**
* When create.
* @param {object} param The options.
* @private
* @example
* // 创建一个对象
* const obj = new THING.Object3D();
* obj.onCreate({});
*/
onCreate(param) {
// We have loaded resource
if (this.loaded) {
this.onNotifyComplete();
}
else {
// If we do not need any dynamic then notify complete directly
if (this._isDynamicLoad) {
this.onNotifyComplete();
}
}
// Notify create event in batch mode
Utils.setBatchTimeout(() => {
if (this.destroyed) {
return;
}
this.trigger(EventType.Create, param);
});
// Notify create event in sync mode
this.trigger(EventType.CreateSync, param);
}
clearInitialLocalBoundingBox() {
// Clear initial bounding box
if (this.hasComponent('bounding')) {
if (this.extras?.tempInitBoundingBox) {
this.initialLocalBoundingBox = null;
delete this.extras.tempInitBoundingBox;
}
}
}
onLoadComplete() {
if (this.destroyed) {
return;
}
switch (this._resourceState) {
// Load complete (loading->loaded)
case Object3D.ResourceState.Loading:
case Object3D.ResourceState.Loaded:
// 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;
case Object3D.ResourceState.Error:
// Load completed
if (this._loadPromise) {
this._loadPromise.resolve({ object: this });
}
this.onNotifyComplete();
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();
}
// Update status to loading
let dynamic = Utils.parseValue(options['dynamic'], false);
if (!dynamic) {
this._resourceState = Object3D.ResourceState.Loading;
}
// 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) {
const loadComponentsResource = () => {
// 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, () => {
loadComponentsResource();
resolve();
}, (error) => {
this._resourceState = Object3D.ResourceState.Error;
reject(error)
});
}
// Here we do not proivde any URL, so let it to be loaded state
else {
loadComponentsResource();
resolve();
}
}
else {
resolve();
}
});
}
onTaskExecutorComplete(callback, error) {
this.taskExecutor.then(callback, error);
}
/**
* 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, { app: this.app });
}
onGetPivot() {
let tempData = this._getTempData();
return tempData['pivot'];
}
onSetPivot(value) {
let tempData = this._getTempData();
const _setPivot = () => {
if (!tempData['initPivot']) {
tempData['initPivot'] = this.transform.getPivot();
}
if (value) {
this.transform.setPivot(value);
}
else {
// Reset pivot if it's already set
if (!tempData['pivot']) {
return;
}
this.transform.setPivot(tempData['initPivot']);
}
tempData['pivot'] = value;
}
if (this.loaded) {
_setPivot();
}
else {
this.addSyncCompleteCallback(() => {
_setPivot();
});
}
}
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 localPosition = options['localPosition'];
let localAngles = options['localAngles'];
// If we use any local arguments then means disable attach mode
if (!localPosition && !localAngles) {
// Only need to check attach mode when it's not local space
let attachMode = Utils.parseBoolean(options['attachMode'], true);
if (attachMode) {
return false;
}
}
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);
}
}
// Update angles
if (localAngles) {
object.localAngles = localAngles;
}
}, options);
}
// #endregion
// #region Common
/**
* 辅助组件 可显示坐标轴等
* @member {THING.HelperComponent} helper
* @memberof THING.Object3D
* @public
* @instance
*/
/**
* 物体的插值组件(可控制属性动态的修改)
* @member {THING.LerpComponent} lerp
* @memberof THING.Object3D
* @public
* @instance
*/
/**
* Get the BoundingComponent of object3D.
* @member {BoundingComponent} bounding
* @memberof THING.Object3D
* @instance
*/
/**
* 物体的层级组件
* @member {THING.LevelComponent} level
* @memberof THING.Object3D
* @instance
* @public
*/
/**
* Get the ActionGroupComponent of object3D.
* @member {ActionGroupComponent} actionGroup
* @memberof THING.Object3D
* @instance
*/
/**
* Get the BlueprintComponent of object3D.
* @member {BlueprintComponent} blueprint
* @memberof THING.Object3D
* @instance
*/
/**
* 物体的碰撞检测组件
* @member {ColliderComponent} collider
* @memberof THING.Object3D
* @instance
* @public
*/
/**
* Get the ActionGroupComponent of object3D.
* @member {ActionGroupComponent} actionGroup
* @memberof THING.Object3D
* @instance
*/
/**
* Get the TransformComponent of object3D.
* @member {TransformComponent} transform
* @memberof THING.Object3D
* @instance
*/
/**
* Get the RenderComponent of object3D.
* @member {RenderComponent} render
* @memberof THING.Object3D
* @instance
*/
/**
* Get the RelationshipComponent of object3D.
* @member {RelationshipComponent} relationship
* @memberof THING.Object3D
* @instance
*/
/**
* Get the TaskExecutorComponent of object3D.
* @member {TaskExecutorComponent} taskExecutor
* @memberof THING.Object3D
* @instance
*/
/**
* Get the MonitorDataComponent of object3D.
* @member {MonitorDataComponent} monitorData
* @memberof THING.Object3D
* @instance
*/
/**
* 设置/获取 对象名称
* @type {string}
* @example
* object.name = 'MyObject';
* @public
*/
get name() {
return this._name;
}
set name(value) {
super.name = value;
this.node.setName(value);
}
/**
* 设置/获取 对象为动态加载
* @type {string}
* @default false
* @example
* object.isDynamicLoad = true;
* @public
*/
get isDynamicLoad() {
return this._isDynamicLoad;
}
set isDynamicLoad(value) {
this._isDynamicLoad = value;
}
/**
* 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;
}
/**
* Get/Set the external info.
* @type {object}
* @private
*/
get extras() {
let _private = this[__.private];
return _private.external;
}
set extras(value) {
let _private = this[__.private];
_private.external = value;
}
// #region Render
/**
* 取/设置遮罩层(LayerMask)。默认值为 1。通过"按位与"的计算,来判定物体在指定摄像机中的layerMask的值。
* @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) {
this.setLayerMask(value, true);
}
/**
* 样式标签
* @type {string}
* @public
*/
get styleTag() {
let _private = this[__.private];
return _private.styleTag;
}
set styleTag(value) {
let _private = this[__.private];
_private.styleTag = 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.
*/
/**
*
* 获取visible的状态
* @returns {boolean} 可见状态。
* @example
* let visible = object.getVisible();
* if (visible) {
* console.log('object is showing');
* }
* @public
*/
getVisible() {
return this.bodyNode.getVisible();
}
/**
* 设置visible的状态
* @param {boolean} value 是否显示
* @param {boolean|ProcessObjectCallback} [recursive=false] 是否同时影响它的孩子
* @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);
}
}
/**
* 获取可拾取状态。
* @returns {boolean} 可拾取状态。
* @private
* @example
* let pickable = object.getPickable();
* if (pickable) {
* console.log('object is pickable');
* }
*/
getPickable() {
return this._flags.has(Flag.Pickable);
}
/**
* 设置可拾取状态。
* @param {boolean} value 可拾取状态。
* @param {boolean} [recursive=false] 为真表示处理所有子对象。
* @public
* @example
* object.setPickable(true, true);
*/
setPickable(value, recursive = false) {
this._flags.enable(Flag.Pickable, value);
this._setAttributeState('pickable', 'Pickable', value, recursive);
}
/**
* 获取视锥剔除状态。
* @returns {boolean} 视锥剔除状态。
* @public
* @example
* let frustumCulled = object.getFrustumCulled();
* if (frustumCulled) {
* console.log('object is frustum culled');
* }
*/
getFrustumCulled() {
return this._flags.has(Flag.FrustumCulled);
}
/**
* 设置视锥剔除状态。
* @param {boolean} value 为真表示启用视锥剔除,否则不启用。
* @param {boolean} [recursive=false] 为真表示处理所有子对象。
* @public
* @example
* object.setFrustumCulled(true, true);
*/
setFrustumCulled(value, recursive = false) {
this._flags.enable(Flag.FrustumCulled, value);
this._setAttributeState('frustumCulled', 'FrustumCulled', value, recursive);
}
/**
* 获取渲染状态。
* @returns {boolean} 渲染状态。
* @private
* @example
* let renderable = object.getRenderable();
* if (renderable) {
* console.log('object is renderable');
* }
*/
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
* @example
* object.setRenderable(true, true);
*/
setRenderable(value, recursive = false) {
console.warn('Please set visible with setVisible instead of setRenderable');
this.setVisible(value, recursive);
}
/**
* 获取激活状态。
* @returns {boolean} 激活状态。
* @private
* @example
* let active = object.getActive();
* if (active) {
* console.log('object is active');
* }
*/
getActive() {
return this._flags.has(Flag.Active);
}
/**
* 设置激活状态。
* @param {boolean} value 激活状态。
* @param {boolean} [recursive=false] 为真表示处理所有子对象。
* @private
* @example
* object.setActive(true, true);
*/
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);
});
}
}
setLayerMask(value, recursive = true) {
this.bodyNode.setLayerMask(value);
this._updateInstanceState(BreakInstanceFlag.LayerMask, value !== 1);
if (recursive) {
this.children.forEach(child => {
child.setLayerMask(value, true);
});
}
}
hasInherit(key) {
let _private = this[__.private];
if (!_private.inherit) {
return
}
if (Utils.isNull(key)) {
return !!_private.inherit;
}
else {
return !!_private.inherit && !Utils.isNull(_private.inherit[key])
}
}
/**
* @typedef {object} InheritData
* @property {InheritType} style? 是否继承样式
* @property {InheritType} visible? 是否继承可见状态
* @property {InheritType} pickable? 是否继承拾取状态
* @public
*/
/**
* 获取继承信息
* @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,
renderOrder: InheritType.Normal,
}
});
}
return _private.inherit;
}
/**
* 获取/设置显示状态,此属性操作会影响自身以及所有子对象。
* @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();
}
/**
* 获取/设置可拾取状态。此属性操作会影响自身以及所有子对象。
* @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);
}
/**
* 激活状态主要影响可见性,类似与控制节点的可见性,与visible的区别是:<br>
* 父物体active为false,孩子的active为true,孩子不可见<br>
* 父物体visible为false,孩子的visible为true,孩子可见<br>
* @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);
}
/**
* 获取/设置视锥剔除状态。
* @type {boolean}
* @public
*/
get frustumCulled() {
return this.getFrustumCulled();
}
set frustumCulled(value) {
this.setFrustumCulled(value, false);
}
get hadPromoted() {
return this._flags.has(Flag.HadPromoted);
}
// #endregion
// #region Transform
/**
* 获取距离另一个Object3D对象或者一个三维坐标的距离
* @param {THING.Object3D|Array<number>} target 目标物体或者世界坐标
* @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);
}
}
/**
* 是否自适应大小(缩放时保持像素级别大小不变)
* @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) {
if (Utils.isValid(value)) {
this._flags.enable(Flag.KeepSize, value);
this.transform.keepSize(value, this.app.camera);
}
}
/**
* 获取/设置轴心点信息。轴心点参考原点为自身包围盒的 [left, bottom, back] 的位置。数组每个分类的取值为0-1
* @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) {
if (MathUtils.equalsVector3(this.pivot, value)) {
return;
}
this.setPivot(value);
}
// #endregion
// #region Nodes
/**
* 设置父对象。
* @param {THING.BaseObject} parent 父对象。
* @param {Function} onAddNode 添加节点到父对象的回调函数。
* @param {object} options 选项。
* @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, options);
// Notify all components
let parentChangeComponents = this.parentChangeComponents;
if (parentChangeComponents) {
parentChangeComponents.forEach(component => {
if (component.onParentChange) {
component.onParentChange(parent);
}
});
}
}
/**
* 添加对象作为自己的孩子
* @param {THING.Object3D} object 待添加对象
* @param {object} options 选项
* @param {string} options.subNodeName 子节点名称
* @param {Array<number>} options.localPosition 相对父物体的位置
* @param {Array<number>} options.localAngles 相对父物体的旋转角度
* @param {boolean} [options.attachMode=true] 表示是否保持世界坐标系下的位置不变
* @param {boolean} [options.ignoreScale=false] 是否忽略缩放
* @param {boolean} [options.indexOfParent] 对象在子节点中的索引
* @returns {boolean} 添加成功返回true,否则返回false
* @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 = {}) {
return super.add(object, options);
}
/**
* 提升渲染节点(uNode)作为Object3D对象
* @param {string} name 节点名称
* @param {THING.Object3D} parent 父对象,如果为null,则表示使用当前对象作为父对象
* @param {*} cls 类
* @returns {THING.Object3D} 提升后的对象
* @example
* // Promote all sub nodes
* var nodeNames = entity.body.nodeNames;
* nodeNames.forEach(name => {
* entity.promoteNode(name);
* });
* @public
*/
promoteNode(name, parent, cls) {
if (!name) {
return null;
}
// if do not have the node,return null
if (!this._body.hasNode(name)) {
return null;
}
// We can not promote node when it's in instanced drawing mode
if (this.isInstancedDrawing) {
this.makeInstancedDrawing(false);
}
// Record it had been promoted
this._flags.enable(Flag.HadPromoted, true);
// 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');
// Create object
cls = cls ? cls : Object3D;
let object = new cls({
name,
parent
});
const style = object.body.style;
style.copy(this.body.style);
// Promote node in body object
let node = this._body.promoteNode(name, parent.node, style.resource);
object.matrixWorld = node.getMatrixWorld([]);
object.body.setNode(node)
return object;
}
/**
* Get pivot in self oriented box.
* @type {Array<number>}
* @private
*/
getPivot() {
return this.onGetPivot();
}
/**
* Set pivot in self oriented box.
* @param {Array<number>} value The pivot value.
* @private
*/
setPivot(value) {
this.onSetPivot(value);
}
/**
* Clear pivot node.
* @private
*/
clearPivot() {
this.transform.clearPivot();
this.setPivot(null);
let tempData = this._getTempData();
delete tempData['pivot'];
delete tempData['initPivot'];
}
/**
* 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 射线起点位置
* @property {Array<number>} direction 射线在世界坐标系下的方向
* @public
*/
/**
* @typedef {object} RaycastResult
* @property {THING.Object3D} object 被射线检测到的对象
* @property {Array<number>} position 射线与对象相交的位置
* @property {number} distance 射线起点到相交点的距离
* @public
*/
/**
* 射线检测,检测射线是否与对象相交
* @param {RaycastInfo} ray 射线信息
* @param {boolean} [recursive=true] 是否对所有子对象进行检测
* @returns {Array<RaycastResult>} 检测结果(按距离从近到远排序)
* @example
* // 示例1: 创建一个向前的射线
* let results = object.raycast({
* origin: [0, 0, 0], // 起点
* direction: [0, 0, 1] // 向前
* });
* if (results.length) {
* let nearest = results[0]; // 获取最近的相交点
* console.log('击中物体:', nearest.object);
* console.log('击中位置:', nearest.position);
* console.log('击中距离:', nearest.distance);
* }
*
* // 示例2: 创建一个指向目标的射线
* let direction = THING.Math.normalizeVector(
* THING.Math.subVector(targetPos, startPos) // 目标点减起点
* );
* console.log('从起点:', startPos, '朝向:', direction, '进行射线检测');
* let hits = object.raycast({
* origin: startPos,
* direction: direction
* });
* console.log('射线检测结果:', hits);
* @public
*/
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
};
}).sort((a, b) => a.distance - b.distance);
}
// 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;
}
}
/**
* 整体设置样式
* @param {object} value 样式对象
* @param {boolean} [recursive=false] 是否影响孩子样式
* @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.traverseInheritance(
(object) => {
object.body.setStyle(value);
},
_getStyleInheritance
);
}
else {
this.body.setStyle(value);
}
}
/**
* 获取/设置样式
* @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, true);
}
// #endregion
// #region Resources
onBeforeDestroy() {
if (!super.onBeforeDestroy()) {
return false;
}
this.onSyncBeforeDestroy();
// Disable instanced drawing first
if (this.isInstancedDrawing) {
this.makeInstancedDrawing(false);
}
// Skip matrix update to speed up render
this.node.setAttribute('MatrixUpdateMode', 'SkipMatrixUpdate');
return true;
}
/**
* When notify sync-before-destroy.
* @private
*/
onSyncBeforeDestroy() {
let _private = this[__.private];
let onSyncBeforeDestroy = _private.onSyncBeforeDestroy;
if (onSyncBeforeDestroy) {
onSyncBeforeDestroy({ object: this });
_private.onSyncBeforeDestroy = null;
}
}
/**
* 克隆对象
* @param {boolean} recursive 是否克隆所有的孩子
* @param {THING.BaseObject} parent 指定克隆出的对象父物体 默认用app.root作为父物体
* @param {object} options 选项
* @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, {
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;
}
/**
* 异步克隆
* @param {boolean} recursive 是否递归克隆
* @param {THING.BaseObject} parent 父对象,默认是app.root对象
* @param {object} options 选项
* @returns {Promise<any>}
* @private
* @example
* let object = await object.cloneAsync();
*/
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();
}
});
}
/**
* 等待物体资源加载完成 返回一个Promise对象
* @returns {Promise<any>} 返回一个Promise对象
* @example
* // 等待物体资源加载完成
* await object.waitForComplete();
* app.camera.fit(object);
* @public
*/
waitForComplete() {
if (this.destroyed) {
return Promise.resolve({ object: this });
}
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;
}
/**
* @public
* @typedef {object} ResourceResult
* @property {string} url 资源路径
* @property {Array<number>} [localPosition] 相对父物体的位置
* @property {Array<number>} [localAngles] 相对父物体的旋转
* @property {Array<number>} [localScale] 相对父物体的缩放
* @property {Array<ResourceResult>} [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 new Promise((resolve, reject) => {
this.unloadResource(recursive).then(() => {
this.loadResource(recursive, options).then(() => {
resolve()
})
});
})
}
/**
* 加载资源 返回一个Promise对象
* @param {boolean} [recursive=true] 是否加载所有孩子的资源
* @param {ResourceResult} [options={}] 加载选项
* @returns {Promise<any>} 返回一个Promise对象
* @example
* // 等待物体加载资源完成
* 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);
}).catch(reject);
}
else {
this._loadSelfResource(options, resolve, reject);
}
}
else {
this._loadSelfResource(options, resolve, reject);
}
}).then(
// OK
() => {
this.onLoadComplete();
},
// Error
(error) => {
Utils.warn('Load resource failed', error);
this.onLoadComplete();
}
);
}
/**
* 卸载资源 返回一个Promise对象
* @param {boolean} [recursive=true] 是否卸载所有孩子的资源
* @returns {Promise<any>} 返回一个Promise对象
* @example
* // 等待物体卸载资源完成
* 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 resource state
* @type {number}
* @private
*/
get resourceState() {
return this._resourceState;
}
/**
* 获取body对象 body对象可以理解这个对象自身,不包含它的孩子.
* @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
/**
* 拷贝对象(不拷贝uuid和parent),拷贝对象会重新加载资源
* @param {THING.BaseObject} object 源对象
* @param {object} options 选项
* @returns {Promise<any>} 返回一个Promise对象
* @example
* // 从查询结果拷贝对象
* 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 default style
this.style.beginDefaultValues();
object.style.beginDefaultValues();
this.onCopyStyle(object, options);
object.style.endDefaultValues();
this.style.endDefaultValues();
// 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);
}
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;
this.tags = new Tags(options.onUpdateValue ? options.onUpdateValue(object, 'tags') : object.tags);
// resource
this.setResource(Utils.cloneObject(object.resource));
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) {
const _private = this[__.private];
const external = {};
external['pivot'] && delete external['pivot']
const pivot = this.pivot
if (pivot) {
external['pivot'] = pivot
}
external['inherit'] && delete external['inherit']
const inherit = _private.inherit
if (inherit) {
const inheritExternal = {}
inherit._keys.forEach((key) => {
const value = inherit[key]
if (value !== InheritType.Normal) {
inheritExternal[key] = inherit[key]
}
})
if (Object.keys(inheritExternal).length) {
external['inherit'] = inheritExternal
}
}
if (!Object.keys(external).length) {
return null;
}
return external;
}
// #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; }
/**
* 获取/设置渲染顺序,绘制顺序从低到高。
* @type {number}
* @public
*/
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.setCastShadow(value, true);
}
/**
* Get/Set the receive shadow.
* @type {boolean}
* @private
*/
get receiveShadow() { return this.render.receiveShadow; }
set receiveShadow(value) {
this.setReceiveShadow(value, true);
}
/**
* 设置/获取 是否开启渲染置顶
* @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;
this._updateInstanceState(BreakInstanceFlag.AlwaysOnTop, value)
}
/**
* 获取渲染顺序。
* @returns {number} 渲染顺序
* @public
* @example
* let renderOrder = object.renderOrder;
* console.log(renderOrder);
*/
getRenderOrder() { return this.render.getRenderOrder.apply(this.render, arguments); }
/**
* 设置渲染顺序。
* @param {number} value 渲染顺序的值,渲染顺序从低到高。
* @param {boolean} recursive 为真表示处理所有子对象。
* @returns {boolean} 是否设置成功
* @public
* @example
* object.setRenderOrder(10);
*/
setRenderOrder() { return this.render.setRenderOrder.apply(this.render, arguments); }
/**
* 获取/设置 是否开启阴影。
* @returns {boolean} 是否开启阴影
* @private
*/
getCastShadow() { return this.render.getCastShadow.apply(this.render, arguments); }
/**
* 设置/获取 是否开启阴影。
* @param {boolean} value 是否开启阴影
* @param {boolean} recursive 为真表示处理所有子对象。
* @private
*/
setCastShadow(value, recursive) {
this.render.setCastShadow(value, recursive);
this._updateInstanceState(BreakInstanceFlag.CastShadow, !value)
}
/**
* 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(value, recursive) {
this.render.setReceiveShadow(value, recursive);
this._updateInstanceState(BreakInstanceFlag.ReceiveShadow, !value)
}
/**
* 开启/关闭批量渲染模式。
* @param {boolean} value 是否开启
* @param {object} options 选项
* @param {string} [options.renderMode='InstancedRendering'] 渲染模式 InstancedRendering/SharedRendering
* SharedRendering代表材质共用但是drawCall还是多个 InstancedRendering代表材质共用,drawCall只有一次
* @returns {boolean} 是否开启成功
* @example
* // 开启批量渲染模式
* if (object.makeInstancedDrawing()) {
* console.log('Enable instanced drawing');
* }
* @public
*/
makeInstancedDrawing(value = true, options = {}) {
this._flags.enable(Flag.Instance, value);
if (value) {
// 更新instance状态
if (this.instanceGroupName) {
this._makeInstancedDrawingOptions = options;
return this.render.makeInstancedDrawing(true, options);
}
if (this._breakInstanceFlags.values === 0) {
this._makeInstancedDrawingOptions = options;
return this.render.makeInstancedDrawing(true, options);
}
return false;
}
else {
this._makeInstancedDrawingOptions = null;
return this.render.makeInstancedDrawing(value, options);
}
}
/**
* @typedef {object} InstancingNodeOptions
* @property {number} maxNumber 实例的最大数量。
* @property {number} number 当前渲染的实例数量。
* @property {Array<Array<number>>} matrices 实例的变换矩阵数组。
* @property {Array<number>} [pickedIds] (可选)实例的拾取 ID,在拾取事件中返回 pickId。
* @property {Array<Array<number>>} [colors] (可选)实例的颜色数组。
* @public
*/
/**
* 启用实例化节点(启用后将使主体节点不可见)。
* @param {InstancingNodeOptions} options 选项。
* @returns {Promise<boolean>} 是否启用成功
* @example
* // 启用实例化渲染,设置最大数量为31104个实例
* object.enableInstancing({
* maxNumber: 31104,
* number: 0,
* matrices: [], // 包含所有实例的变换矩阵
* pickedIds: pickedIds, // 可选,包含所有实例的拾取ID
* colors: colors // 可选,包含所有实例的颜色
* });
* @public
*/
enableInstancing() { return this.render.enableInstancing.apply(this.render, arguments); }
/**
* 禁用实例化节点。
* @returns {boolean} 是否禁用成功
* @example
* // 禁用实例化渲染
* object.disableInstancing();
* @public
*/
disableInstancing() { return this.render.disableInstancing.apply(this.render, arguments); }
/**
* 设置/更新实例化节点选项。
* @param {InstancingNodeOptions} options 选项。
* @returns {boolean} 是否设置成功
* @example
* // 更新实例化渲染的参数
* object.setInstancing({
* maxNumber: 31104,
* number: 31104,
* matrices: matrices, // 包含所有实例的变换矩阵
* pickedIds: pickedIds, // 可选,包含所有实例的拾取ID
* colors: colors // 可选,包含所有实例的颜色
* });
* @public
*/
setInstancing() { return this.render.setInstancing.apply(this.render, arguments); }
/**
* 获取实例化节点信息。
* @returns {InstancingNodeOptions} 实例化节点信息
* @example
* // 获取实例化渲染的当前参数
* const info = object.getInstancing();
* console.log(info.number); // 当前实例数量
* console.log(info.maxNumber); // 最大实例数量
* @public
*/
getInstancing() { return this.render.getInstancing.apply(this.render, arguments); }
/**
* 按索引设置实例化颜色。
* @param {number} index 索引。
* @param {Array<number>} color 颜色。
* @returns {boolean} 是否设置成功
* @example
* // 将第30个实例设置为绿色
* object.setInstancingColor(30, [0, 1, 0]);
*
* // 随机将一些实例设置为红色警告色
* for(let i = 0; i < warnCount; i++) {
* const index = Math.floor(Math.random() * 31104);
* object.setInstancingColor(index, [1, 0, 0]);
* }
* @public
*/
setInstancingColor() { return this.render.setInstancingColor.apply(this.render, arguments); }
/**
* 按索引设置实例化清除颜色。
* @param {number} index 索引。
* @param {boolean} value 是否清除颜色。
* @returns {boolean} 是否设置成功
* @example
* // 清除第一个实例的颜色
* object.setInstancingClearOutColor(0, true);
* @public
*/
setInstancingClearOutColor() { return this.render.setInstancingClearOutColor.apply(this.render, arguments); }
/**
* 获取实例化清除颜色。
* @param {number} index 索引。
* @returns {boolean} 是否获取成功
* @example
* // 获取第一个实例的清除颜色
* const clearOutColor = object.getInstancingClearOutColor(0);
* console.log('Instance 0 clear out color:', clearOutColor);
* @public
*/
getInstancingClearOutColor() { return this.render.getInstancingClearOutColor.apply(this.render, arguments); }
/**
* 按索引设置实例化漫反射UV变换。
* @param {number} index 索引。
* @param {Array<number>} diffuseUVTransform 漫反射UV变换。
* @returns {boolean} 是否设置成功
* @example
* // 设置第一个实例的漫反射UV变换
* object.setInstancingDiffuseUVTransform(0, [0, 0, 1, 1]);
* @public
*/
setInstancingDiffuseUVTransform() { return this.render.setInstancingDiffuseUVTransform.apply(this.render, arguments); }
/**
* 获取实例化漫反射UV变换。
* @param {number} index 索引。
* @param {Array<number>} target 目标。
* @returns {Array<number>} 漫反射UV变换
* @example
* // 获取第一个实例的漫反射UV变换
* const diffuseUVTransform = object.getInstancingDiffuseUVTransform(0);
* console.log('Instance 0 diffuse UV transform:', diffuseUVTransform);
* @public
*/
getInstancingDiffuseUVTransform() { return this.render.getInstancingDiffuseUVTransform.apply(this.render, arguments); }
/**
* 按索引设置实例化可见性。
* @param {number} index 索引。
* @param {boolean} visible 可见性。
* @returns {boolean} 是否设置成功
* @example
* // 隐藏第一个实例
* object.setInstancingVisible(0, false);
* @public
*/
setInstancingVisible() { return this.render.setInstancingVisible.apply(this.render, arguments); }
/**
* 获取按索引的实例化可见性。
* @param {number} index 索引。
* @returns {boolean} 是否获取成功
* @example
* // 检查第一个实例是否可见
* const visible = object.getInstancingVisible(0);
* console.log('Instance 0 visible:', visible);
* @public
*/
getInstancingVisible() { return this.render.getInstancingVisible.apply(this.render, arguments); }
/**
* 获取实例化数量。
* @returns {number} 实例化数量
* @example
* // 获取当前实例的数量
* const count = object.getInstancedCount();
* console.log('Current instance count:', count);
* @public
*/
getInstancedCount() { return this.render.getInstancedCount.apply(this.render, arguments); }
/**
* 按索引设置实例化矩阵。
* @param {number} index 索引。
* @param {Array<number>} matrix 矩阵。
* @returns {boolean} 是否设置成功
* @example
* // 修改第30个实例的变换矩阵,将其Y轴位置上移1个单位
* const matrices = options[0].matrices[30];
* object.setInstancingMatrix(30, [
* 1, 0, 0, matrices[3],
* 0, 1, 0, matrices[7] + 1,
* 0, 0, 1, matrices[11],
* 0, 0, 0, 1
* ]);
* @public
*/
setInstancingMatrix() { return this.render.setInstancingMatrix.apply(this.render, arguments); }
/**
* 获取按索引的实例化矩阵。
* @param {number} index 索引。
* @param {Array<number>} target? 目标。
* @returns {Array<number>} 实例化矩阵
* @example
* // 获取第一个实例的变换矩阵
* const matrix = object.getInstancingMatrix(0);
* console.log('Instance 0 matrix:', matrix);
* @public
*/
getInstancingMatrix() { return this.render.getInstancingMatrix.apply(this.render, arguments); }
/**
* 设置实例化属性。
* @param {string} name 名称。
* @param {any} value 值。
* @returns {boolean} 是否设置成功
* @example
* // 启用颜色属性
* object.setInstancingAttribute('Colors', true);
* @public
*/
setInstancingAttribute() { return this.render.setInstancingAttribute.apply(this.render, arguments); }
/**
* 获取实例化属性。
* @param {string} name 名称。
* @returns {any} 实例化属性
* @example
* // 获取颜色属性的状态
* const useColors = object.getInstancingAttribute('Colors');
* console.log('Using colors:', useColors);
* @public
*/
getInstancingAttribute() { return this.render.getInstancingAttribute.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.
*/
/**
* 获取几何体信息。
* @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
/**
* 获取世界坐标系下的包围盒数据 等同于getAABB(true,true,false)
* @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; }
/**
* 获取旋转后的包围盒数据 包围盒坐标轴一般是物体自身坐标轴 等同于getOBB(true,true,false)
* @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; }
/**
* 获取包围盒
* @param {boolean} [recursive=true] 计算aabb时是否考虑孩子
* @param {boolean} [updateMatrix=true] 计算aabb时是否更新自己和孩子的矩阵
* @param {boolean} [local=false] 计算出的aabb的轴向是基于父物体坐标系,还是相对于世界坐标系(计算出的包围盒是不同的)
* @returns {THING.Box3} 包围盒对象
* @example
* let boundingBox = object.getAABB();
* console.log(boundingBox);
* @public
*/
getAABB() { return this.bounding.getAABB.apply(this.bounding, arguments); }
/**
* @typedef {object} OrientedBoxResult 旋转包围盒对象
* @property {Array<number>} angles 中心点旋转角度
* @property {Array<number>} center 中心点世界坐标
* @property {Array<number>} size 包围盒尺寸
* @property {Array<number>} halfSize 包围盒尺寸x,y,z三个方向分别除以2可得到该参数
* @property {number} radius 包围盒最小点和最大点的距离的一半
* @public
*/
/**
* 获取旋转后的包围盒
* @param {boolean} [recursive=true] 计算obb时是否考虑孩子
* @param {boolean} [updateMatrix=true] 计算obb时是否更新自己和孩子的矩阵
* @param {boolean} [local=false] 描述obb时的参数是基于父物体还是世界坐标系(算出的包围盒是一样的,只是描述不同)
* @returns {OrientedBoxResult} 旋转包围盒对象
* @example
* let orientedBox = object.getOBB();
* console.log(orientedBox);
* @public
*/
getOBB() { return this.bounding.getOBB.apply(this.bounding, arguments); }
// #endregion
// #region Component - lerp
/**
* @public
* @typedef LerpArgs
* @property {object} from 源属性
* @property {object} to 目标属性
* @property {number} [times=-1] 循环次数 -1代表无限
* @property {number} [duration=1000] 时间 单位毫秒
* @property {number} [delayTime=0] 延时 单位毫秒
* @property {LerpType} [lerpType] 插值类型 见THING.LerpType
* @property {LoopType} [loopType] 循环类型 见THING.LoopType
* @property {boolean} [orientToPath] 移动时是随路径调整自身朝向
* @property {Function} [onRepeat] 每一次插值结束,下一次插值开始时触发
* @property {Function} [onStart] 开始时触发
* @property {Function} [onStop] 停止时触发
* @property {Function} [onUpdate] 插值过程每帧触发
* @property {Function} [onComplete] 所有插值结束时触发
*/
/**
* @public
* @typedef LerpWithSpaceTypeArgs
* @property {object} from 源属性
* @property {object} to 目标属性
* @property {number} [times=-1] 循环次数 -1代表无限
* @property {number} [duration=1000] 时间 单位毫秒
* @property {number} [delayTime=0] 延时 单位毫秒
* @property {LerpType} [lerpType] 插值类型 见THING.LerpType
* @property {LoopType} [loopType] 循环类型 见THING.LoopType
* @property {boolean} [orientToPath] 移动时是随路径调整自身朝向
* @property {Function} [onRepeat] 每一次插值结束,下一次插值开始时触发
* @property {Function} [onStart] 开始时触发
* @property {Function} [onStop] 停止时触发
* @property {Function} [onUpdate] 插值过程每帧触发
* @property {Function} [onComplete] 所有插值结束时触发
* @property {THING.SpaceType} [spaceType=THING.SpaceType.World] 参数中的位置是在世界空间还是自身空间
*/
/**
* @public
* @typedef MovePathLerpArgs
* @property {object} from 源属性
* @property {object} to 目标属性
* @property {number} [times=-1] 循环次数 -1代表无限
* @property {number} [duration=1000] 时间 单位毫秒
* @property {number} [delayTime=0] 延时 单位毫秒
* @property {LerpType} lerpType? 插值类型 见THING.LerpType
* @property {LoopType} loopType? 循环类型 见THING.LoopType
* @property {boolean} orientToPath? 移动时是随路径调整自身朝向
* @property {Function} onRepeat 每一次插值结束,下一次插值开始时触发
* @property {Function} onStart 开始时触发
* @property {Function} onStop 停止时触发
* @property {Function} onUpdate 插值过程每帧触发
* @property {Function} onComplete 所有插值结束时触发
* @property {THING.SpaceType} [spaceType=THING.SpaceType.World] 参数中的位置是在世界空间还是自身空间
* @property {Array<number>} up 向上的方向
* @property {boolean} [closure=false] 路径是否闭合,即移动到最后一个点之后是否要返回第一个点
*/
/**
* 获取飞行状态
* @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); }
/**
* 停止移动
* @example
* object.stopMoving();
* @public
*/
stopMoving() { return this.lerp.stopMoving.apply(this.lerp, arguments); }
/**
* 暂停移动
* @example
* object.pauseMoving();
* @public
*/
pauseMoving() { return this.lerp.pauseMoving.apply(this.lerp, arguments); }
/**
* 恢复移动
* @example
* object.resumeMoving();
* @public
*/
resumeMoving() { return this.lerp.resumeMoving.apply(this.lerp, arguments); }
/**
* 对象移动到目标位置
* @param {Array<number>} value 目标位置
* @param {MovePathLerpArgs} param 移动的参数
* @returns {boolean} 是否移动成功
* @example
* object.moveTo(object.selfToWorld(THING.Math.randomVector([-200, -5, -200], [200, 5, 200])), {
* loopType: THING.LoopType.PingPong,
* duration: THING.Math.randomInt(1000, 5000)
* });
* object.moveTo({
* to: object.selfToWorld(THING.Math.randomVector([-200, -5, -200], [200, 5, 200])),
* 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); }
/**
* 对象沿路径移动
* @param {Array<Array<number>>} value 组成路径的坐标的集合
* @param {MovePathLerpArgs} param 移动参数
* @returns {boolean} 是否移动成功
* @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,
* });
* object.movePath({
* path: path.map(point => object.selfToWorld(point)),
* duration: 5 * 1000,
* loopType: THING.LoopType.Repeat,
* });
* @public
*/
movePath() { return this.lerp.movePath.apply(this.lerp, arguments); }
/**
* 对象沿路径移动 返回一个Promise对象
* @param {Array<Array<number>>} value 组成路径的坐标的集合
* @param {MovePathLerpArgs} param 移动参数
* @returns {Promise<any>} 返回一个Promise对象
* @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
* });
* await object.movePathAsync({
* path: path.map(point => object.selfToWorld(point)),
* duration: 5 * 1000
* });
* @public
*/
movePathAsync() { return this.lerp.movePathAsync.apply(this.lerp, arguments); }
/**
* 停止缩放动画
* @returns {boolean} 是否停止成功
* @example
* object.stopScaling();
* @public
*/
stopScaling() { return this.lerp.stopScaling.apply(this.lerp, arguments); }
/**
* 暂停缩放动画
* @returns {boolean} 是否暂停成功
* @example
* object.pauseScaling();
* @public
*/
pauseScaling() { return this.lerp.pauseScaling.apply(this.lerp, arguments); }
/**
* 恢复缩放动画
* @returns {boolean} 是否恢复成功
* @example
* object.resumeScaling();
* @public
*/
resumeScaling() { return this.lerp.resumeScaling.apply(this.lerp, arguments); }
/**
* 缩放动画
* @param {Array<number>} value 要缩放到三个轴向的倍数
* @param {LerpWithSpaceTypeArgs} param 插值参数
* @returns {boolean} 是否缩放成功
* @example
* object.scaleTo(THING.Math.randomVector([1, 1, 1], [3, 3, 3]), {
* loopType: THING.LoopType.PingPong,
* duration: THING.Math.randomInt(1000, 5000)
* });
* object.scaleTo({
* to: THING.Math.randomVector([1, 1, 1], [3, 3, 3]),
* 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)
* });
* await object.scaleToAsync({
* to: 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); }
/**
* 停止旋转
* @example
* object.stopRotating();
* @public
*/
stopRotating() { return this.lerp.stopRotating.apply(this.lerp, arguments); }
/**
* 暂停旋转
* @example
* object.pauseRotating();
* @public
*/
pauseRotating() { return this.lerp.pauseRotating.apply(this.lerp, arguments); }
/**
* 恢复旋转
* @example
* object.resumeRotating();
* @public
*/
resumeRotating() { return this.lerp.resumeRotating.apply(this.lerp, arguments); }
/**
* 旋转对象
* @param {Array<number>} value 三个轴向的旋转角度
* @param {LerpWithSpaceTypeArgs} param 插值参数
* @returns {boolean} 是否旋转成功
* @example
* object.rotateTo([0, 360, 0], {
* loopType: THING.LoopType.Repeat,
* duration: 10 * 1000
* });
* object.rotateTo({
* to: [0, 360, 0],
* duration: 10 * 1000
* });
* @public
*/
rotateTo() { return this.lerp.rotateTo.apply(this.lerp, arguments); }
// 沿特定轴旋转 可插值
rotateOnAxisByLerp(axis, angle, options = {}) {
const currentRotation = this.localQuaternion;
const rotation = MathUtils.getQuatFromAxisRadian(axis, angle * Math.PI / 180);
const newRotation = [];
MathUtils.quat.multiply(newRotation, currentRotation, rotation);
// 旋转一定是绕自身轴
options.space = SpaceType.Self;
return this.rotateTo(newRotation, options)
}
// 沿x轴旋转 可插值
rotateXByLerp(angle, options) {
let localXAxis = [this.matrix[0], this.matrix[1], this.matrix[2]];
MathUtils.normalizeVector(localXAxis);
return this.rotateOnAxisByLerp(localXAxis, angle, options);
}
// 沿y轴旋转 可插值
rotateYByLerp(angle, options) {
let localYAxis = [this.matrix[4], this.matrix[5], this.matrix[6]];
localYAxis = MathUtils.normalizeVector(localYAxis);
return this.rotateOnAxisByLerp(localYAxis, angle, options);
}
// 沿z轴旋转 可插值
rotateZByLerp(angle, options) {
let localZAxis = [this.matrix[8], this.matrix[9], this.matrix[10]];
localZAxis = MathUtils.normalizeVector(localZAxis);
return this.rotateOnAxisByLerp(localZAxis, angle, options);
}
/**
* 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); }
/**
* 停止淡入淡出动作。
* @example
* object.stopFading();
* @public
*/
stopFading() { return this.lerp.stopFading.apply(this.lerp, arguments); }
/**
* 暂停淡入淡出动作。
* @example
* object.pauseFading();
* @public
*/
pauseFading() { return this.lerp.pauseFading.apply(this.lerp, arguments); }
/**
* 恢复淡入淡出动作。
* @example
* object.resumeFading();
* @public
*/
resumeFading() { return this.lerp.resumeFading.apply(this.lerp, arguments); }
/**
* 对象渐现
* @param {LerpArgs} param The parameters. 插值参数
* @example
* object.fadeIn({
* duration: 2000,
* onComplete: () => {
* console.log('Fade in completed');
* }
* });
* @public
*/
fadeIn() { return this.lerp.fadeIn.apply(this.lerp, arguments); }
/**
* 对象渐隐
* @param {LerpArgs} param The parameters. 插值参数
* @example
* object.fadeOut({
* duration: 2000,
* onComplete: () => {
* console.log('Fade out completed');
* }
* });
* @public
*/
fadeOut() { return this.lerp.fadeOut.apply(this.lerp, arguments); }
/**
* 异步将对象渐现。
* @param {LerpArgs} param The parameters. 插值参数
* @returns {Promise} 一个在渐现完成时解析的承诺。
* @example
* await object.fadeInAsync({
* duration: 2000
* });
* @public
*/
fadeInAsync() { return this.lerp.fadeInAsync.apply(this.lerp, arguments); }
/**
* 异步将对象渐隐。
* @param {LerpArgs} param The parameters. 插值参数
* @returns {Promise} 一个在渐隐完成时解析的承诺。
* @example
* await object.fadeOutAsync({
* duration: 2000
* });
* @public
*/
fadeOutAsync() { return this.lerp.fadeOutAsync.apply(this.lerp, arguments); }
/**
* 停止飞行
* @example
* object.stopFlying();
* @public
*/
stopFlying() { return this.lerp.stopFlying.apply(this.lerp, arguments); }
/**
* 暂停飞行
* @example
* object.pauseFlying();
* @public
*/
pauseFlying() { return this.lerp.pauseFlying.apply(this.lerp, arguments); }
/**
* 恢复飞行
* @example
* object.resumeFlying();
* @public
*/
resumeFlying() { return this.lerp.resumeFlying.apply(this.lerp, arguments); }
/**
* @typedef {object} LerpFlyToArgs 飞行的插值参数
* @property {Array<number>} [position] 要飞到的位置
* @property {Array<number>|THING.BaseObject} target
* </br>飞到目标位置之后物体的target,即物体target和position的差代表对象的方向,
* </br>如果传入一个BaseObject,target就是目标对象的位置,如果没有传position,会根据距离目标对象的距离,水平角,垂直角来计算position
* </br>参见 https://en.wikipedia.org/wiki/Spherical_coordinate_system
* @property {Array<number>} [up] 飞到目标点后对象的up朝向
* @property {number} [duration] 飞行时间
* @property {number} [delayTime] 延迟时间
* @property {number} [distance] 如果目标点是一个Object3D,且没传position,代表距离目标对象的距离,用于计算position
* @property {number} [horzAngle] 如果目标点是一个Object3D,且没传position,代表与目标对象的水平夹角,用于计算position
* @property {number} [vertAngle] 如果目标点是一个Object3D,且没传position,代表与目标对象的垂直夹角,用于计算position
* @property {LerpType} [lerpType] 插值类型
* @property {LerpType} [positionLerpType] 飞行时positoin的插值类型
* @property {LerpType} [targetLerpType] 飞行时target的插值类型
* @property {LerpType} [upLerpType] 飞行时up的插值类型
* @property {Function} [onStart] 开始飞行时的回调
* @property {Function} [onStop] 停止飞行时的回调
* @property {Function} [onResume] 恢复飞行时的回调
* @property {Function} [onPause] 暂停飞行时的回调
* @property {Function} [onUpdate] 飞行时每帧回调
* @property {Function} [onComplete] 飞行完成时的回调
* @public
*/
/**
* 物体在特定时间内飞行到特定位置
* @param {THING.BaseObject|LerpFlyToArgs} param 可传入BaseObject或飞行参数
* @returns {boolean} 是否飞行成功
* @example
* object.flyTo(baseObject);//飞到物体
*
* object.flyTo({
* target: otherTarget,
* horzAngle: 0,
* vertAngle: 45
* });
*
* object.flyTo({
* position: [0,10,0],
* target:[0,0,0],
* up:[0,0,1]
* });
* @public
*/
flyTo() { return this.lerp.flyTo.apply(this.lerp, arguments); }
/**
* 物体在特定时间内飞行到特定位置
* @param {THING.BaseObject|LerpFlyToArgs} param 可传入BaseObject或飞行参数
* @returns {Promise<any>} 返回一个Promise对象
* @example
* await object.flyToAsync({
* target: otherTarget,
* horzAngle: 0,
* vertAngle: 45
* });
* @private
*/
flyToAsync() { return this.lerp.flyToAsync.apply(this.lerp, arguments); }
/**
* 根据目标对象计算最佳位置 一般用于调整摄像机最佳视角 <br>
* 注意参数中 times 和 duration 不起作用
* @param {THING.BaseObject|LerpFlyToArgs} param 可传入BaseObject或参数
* @returns {boolean} 是否计算成功
* @example
* app.camera.fit(otherTarget);
* @public
*/
fit() { return this.lerp.fit.apply(this.lerp, arguments); }
/**
* 停止UV变化 主要用于贴图动画
* @param {ImageSlotType} slotType 指定纹理类型
* @returns {boolean} 是否停止成功
* @example
* object.stopUVTransform(THING.ImageSlotType.Map);
* @public
*/
stopUVTransform() { return this.lerp.stopUVTransform.apply(this.lerp, arguments); }
/**
* 暂停UV变化
* @param {ImageSlotType} slotType 指定纹理类型
* @returns {boolean} 是否暂停成功
* @example
* object.pauseUVTransform(THING.ImageSlotType.Map);
* @public
*/
pauseUVTransform() { return this.lerp.pauseUVTransform.apply(this.lerp, arguments); }
/**
* 恢复UV变化
* @param {ImageSlotType} slotType 指定纹理类型
* @returns {boolean} 是否恢复成功
* @example
* object.resumeUVTransform(THING.ImageSlotType.Map);
* @public
*/
resumeUVTransform() { return this.lerp.resumeUVTransform.apply(this.lerp, arguments); }
/**
* 开始UV变化
* @param {ImageSlotType} slotType 指定纹理类型 (diffuseMap,alphaMap,emissiveMap等)
* @param {LerpArgs} value 插值参数 参数中需指定UV对象中要变化哪个属性
* </br>包含以下四种 {number[]} offset,{number[]} repeat,{number[]} center(number[]),{number} rotation
* @returns {boolean} 是否开始成功
* @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); }
/**
* 开始UV变化
* @param {ImageSlotType} slotType 指定纹理类型 (diffuseMap,alphaMap,emissiveMap等)
* @param {LerpArgs} value 插值参数 参数中需指定UV对象中要变化哪个属性
* </br>包含以下四种 {number[]} offset,{number[]} repeat,{number[]} center(number[]),{number} rotation
* @returns {Promise<any>} 返回一个Promise对象
* @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
/**
* 设置/获取 相对于父物体坐标系的位置(偏移)
* @type {Array<number>}
* @public
*/
get localPosition() { return this.transform.getLocalPosition(); }
set localPosition(value) { this.transform.setLocalPosition(value); }
/**
* 设置/获取 相对于父物体坐标系的旋转角度
* 欧拉角('XYZ'),单位为 degree(度)。
* @type {Array<number>}
* @public
*/
get localAngles() { return this.transform.getLocalAngles(); }
set localAngles(value) { this.transform.setLocalAngles(value); }
/**
* 等同于localAngles
* 欧拉角('XYZ'),单位为 degree(度)。
* @type {Array<number>}
* @public
*/
get localRotation() { return this.transform.getLocalAngles(); }
set localRotation(value) { this.transform.setLocalAngles(value); }
/**
* 设置/获取 相对于父物体坐标系的四元数
* @type {Array<number>}
* @public
*/
get localQuaternion() { return this.transform.getLocalQuaternion(); }
set localQuaternion(value) { this.transform.setLocalQuaternion(value); }
/**
* 设置/获取 父物体坐标系下的缩放
* @type {Array<number>}
* @public
*/
get localScale() { return this.transform.getLocalScale(); }
set localScale(value) { this.transform.setLocalScale(value); }
/**
* 设置/获取 世界坐标系下的位置
* @type {Array<number>}
* @public
*/
get position() { return this.transform.getWorldPosition(); }
set position(value) { this.transform.setWorldPosition(value); }
/**
* 设置/获取 世界坐标系下的旋转角度
* 欧拉角('XYZ'),单位为 degree(度)。
* @type {Array<number>}
* @public
*/
get angles() { return this.transform.getWorldAngles(); }
set angles(value) { this.transform.setWorldAngles(value); }
/**
* 等同于angles
* 欧拉角('XYZ'),单位为 degree(度)。
* @type {Array<number>}
* @public
*/
get rotation() { return this.transform.getWorldAngles(); }
set rotation(value) { this.transform.setWorldAngles(value); }
/**
* 设置/获取 世界坐标系下的四元数
* @type {Array<number>}
* @public
*/
get quaternion() { return this.transform.getWorldQuaternion(); }
set quaternion(value) { this.transform.setWorldQuaternion(value); }
/**
* 设置/获取 世界坐标系下的缩放
* @type {Array<number>}
* @public
*/
get scale() { return this.transform.getWorldScale(); }
set scale(value) { this.transform.setWorldScale(value); }
/**
* 设置/获取 自身的矩阵
* @type {Array<number>}
* @private
*/
get matrix() { return this.transform.getMatrix(); }
set matrix(value) { this.transform.setMatrix(value); }
/**
* 设置/获取 在世界坐标系下的矩阵
* @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; }
/**
* Gets/sets the distance limit to keep the size, with null indicates no limit
* @type {number}
* @private
*/
get keepSizeDistanceLimited() { return this.transform.keepSizeDistanceLimited; }
set keepSizeDistanceLimited(value) { this.transform.keepSizeDistanceLimited = value; }
/**
* 获取/设置 keepSize 时是否使用 body.localScale(true)而不是 object.localScale(false,默认)。
* @type {boolean}
* @default false
* @example
* // 使用 body.localScale 来保持大小
* object.keepSizeUseBodyLocalScale = true;
* object.keepSize = true;
* @public
*/
get keepSizeUseBodyLocalScale() { return this.transform.keepSizeUseBodyLocalScale; }
set keepSizeUseBodyLocalScale(value) { this.transform.keepSizeUseBodyLocalScale = value; }
/**
* 获取/设置是否忽略父物体的变换
* @type {THING.Transform}
* @example
* object.ignoreParentTransform = THING.Transform.Rotation | THING.Transform.Scale;
* @public
*/
get ignoreParentTransform() {
return this._ignoreParentTransform;
}
set ignoreParentTransform(value) {
const modeDefault = 'Default';
const modeR = 'EliminateParentR';
const modeS = 'EliminateParentS';
const modeRS = 'EliminateParentRS';
if (value == null || value === 0) {
this.node.setAttribute('MatrixUpdateMode', modeDefault);
}
else {
const hasR = (value & Transform.Rotation) !== 0;
const hasS = (value & Transform.Scale) !== 0;
if (hasR && hasS) {
this.node.setAttribute('MatrixUpdateMode', modeRS);
}
else if (hasR) {
this.node.setAttribute('MatrixUpdateMode', modeR);
}
else if (hasS) {
this.node.setAttribute('MatrixUpdateMode', modeS);
}
else {
this.node.setAttribute('MatrixUpdateMode', modeDefault);
}
}
this.updateWorldMatrix();
this._ignoreParentTransform = 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); }
/**
* 等同于rotateOnAxis
* @param {Array<number>} axis 在自身坐标系下的轴向信息
* @param {number} angle 旋转角度
* @param {LerpArgs} lerpParam 插值信息
* @returns {boolean} 是否旋转成功
* @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); }
/**
* 绕固定轴旋转特定角度
* @param {Array<number>} axis 在自身坐标系下的轴向信息
* @param {number} angle 旋转角度
* @param {LerpArgs} lerpParam 插值信息
* @returns {boolean} 是否旋转成功
* @example
* let box = new THING.Box();
* box.rotateOnAxis(THING.Utils.xAxis, 45);
* //@expect(THING.Math.equalsVector3([45, 0, 0], box.angles) == true);
* @public
*/
rotateOnAxis() {
if (arguments.length === 3) {
return this.rotateOnAxisByLerp.apply(this, arguments);
}
return this.transform.rotateOnAxis.apply(this.transform, arguments);
}
/**
* 绕自身坐标系下的x轴进行旋转
* @param {number} angle 旋转角度
* @param {LerpArgs} lerpParam 插值信息
* @returns {boolean} 是否旋转成功
* @public
*/
rotateX() {
if (arguments.length === 2) {
return this.rotateXByLerp.apply(this, arguments);
}
return this.transform.rotateX.apply(this.transform, arguments);
}
/**
* 绕自身坐标系下的y轴进行旋转
* @param {number} angle 旋转角度
* @param {LerpArgs} lerpParam 插值信息
* @returns {boolean} 是否旋转成功
* @public
*/
rotateY() {
if (arguments.length === 2) {
return this.rotateYByLerp.apply(this, arguments);
}
return this.transform.rotateY.apply(this.transform, arguments);
}
/**
* 绕自身坐标系下的z轴进行旋转
* @param {number} angle 旋转角度
* @param {LerpArgs} lerpParam 插值信息
* @returns {boolean} 是否旋转成功
* @public
*/
rotateZ() {
if (arguments.length === 2) {
return this.rotateZByLerp.apply(this, arguments);
}
return this.transform.rotateZ.apply(this.transform, arguments);
}
/**
* 沿自身坐标系指定轴进行平移
* @param {Array<number>} axis 在自身坐标系下的轴向信息
* @param {number} distance 移动距离
* @param {LerpArgs} lerpParam 插值信息
* @returns {boolean} 是否平移成功
* @private
*/
translateOnAxis() {
if (arguments.length === 3) {
return this.translateOnAxisByLerp.apply(this, arguments);
}
return this.transform.translateOnAxis.apply(this.transform, arguments);
}
/**
* 沿着自身X轴对物体做偏移
* @param {number} distance 偏移距离 单位米
* @param {LerpArgs} [lerpParam] 插值信息
* @returns {boolean} 是否平移成功
* @public
* @example
* object.translateX(10, {time: 1000})
*/
translateX() {
if (arguments.length === 2) {
return this.translateXByLerp.apply(this, arguments);
}
return this.transform.translateX.apply(this.transform, arguments);
}
/**
* 沿着自身Y轴对物体做偏移
* @param {number} distance 偏移距离 单位米
* @param {LerpArgs} [lerpParam] 插值信息
* @returns {boolean} 是否平移成功
* @public
* @example
* object.translateY(10, {time: 1000})
*/
translateY() {
if (arguments.length === 2) {
return this.translateYByLerp.apply(this, arguments);
}
return this.transform.translateY.apply(this.transform, arguments);
}
/**
* 沿着自身Z轴对物体做偏移
* @param {number} distance 偏移距离 单位米
* @param {LerpArgs} [lerpParam] 插值信息
* @returns {boolean} 是否平移成功
* @public
* @example
* object.translateZ(10, {time: 1000})
*/
translateZ() {
if (arguments.length === 2) {
return this.translateZByLerp.apply(this, arguments);
}
return this.transform.translateZ.apply(this.transform, arguments);
}
/**
* 沿三个轴向对物体做偏移
* @param {Array<number>} offset 三个轴向的偏移 单位米
* @param {LerpArgs} [lerpParam] 插值信息
* @returns {boolean} 是否平移成功
* @public
* @example
* object.translate([10, 10, 10], {time: 1000})
*/
translate() {
if (arguments.length === 2) {
return this.translateByLerp.apply(this, arguments);
}
return this.transform.translate.apply(this.transform, arguments);
}
/**
* 沿自身坐标系指定轴进行平移通过插值的方式
* @param {Array<number>} axis 移动轴方向
* @param {number} distance 移动距离
* @param {LerpArgs} options 插值参数
* @private
* @example
* app.camera.translateOnAxisByLerp([1, 0, 0], 10, {time: 1000})
*/
translateOnAxisByLerp(axis, distance, options) {
const localPosition = this.localPosition.slice()
let _axis = [0, 0, 0];
let _vector3 = [0, 0, 0]
MathUtils.vec3.normalize(_axis, axis);
MathUtils.vec3.transformQuat(_vector3, _axis, this.localQuaternion);
MathUtils.vec3.scale(_vector3, _vector3, distance);
const newLocalPosition = MathUtils.addVector(localPosition, _vector3)
const lerpParam = {
from: { localPosition },
to: {
localPosition: newLocalPosition
},
}
Object.assign(lerpParam, options);
this.lerp.to(lerpParam, cTranslateByLerpName);
}
/**
* 沿自身坐标系x轴方向进行平移通过插值的方式
* @param {number} distance 移动距离
* @param {LerpArgs} options 插值参数
* @private
* @example
* app.camera.translateXByLerp(10, {time: 1000})
*/
translateXByLerp(distance, options) {
this.translateOnAxisByLerp(Utils.xAxis, distance, options);
}
/**
* 沿自身坐标系y轴方向进行平移通过插值的方式
* @param {number} distance 移动距离
* @param {LerpArgs} options 插值参数
* @private
*/
translateYByLerp(distance, options) {
this.translateOnAxisByLerp(Utils.yAxis, distance, options);
}
/**
* 沿自身坐标系z轴方向进行平移通过插值的方式
* @param {number} distance 移动距离
* @param {LerpArgs} options 插值参数
* @private
*/
translateZByLerp(distance, options) {
this.translateOnAxisByLerp(Utils.zAxis, distance, options);
}
/**
* 沿自身坐标系平移指定位置通过插值的方式
* @param {Array<number>} offset 移动位置
* @param {LerpArgs} options 插值参数
* @private
* @example
* app.camera.translateByLerp([10,10,0], {time: 1000})
*/
translateByLerp(offset, options) {
const position = this.position.slice()
let newPosition = this.selfToWorld(offset, false)
const lerpParam = {
from: {
position: position
},
to: {
position: newPosition
},
}
Object.assign(lerpParam, options);
this.lerp.to(lerpParam, cTranslateByLerpName);
}
/**
* @public
* @typedef {object} LookAtArgs lookAt的参数
* @property {Array<number>} up lookAt时 物体的up方向
* @property {AxisType} [lockAxis] 锁定自身的轴向(x,y,z)
* @property {boolean} [always=false] 是否一直看目标 默认false 代表只看一次,如果传true,观察者或被观察者移动时也会看向目标
* @property {boolean} [lookOnPlane=false] 是看向目标物体还是看向目标物体的垂直平面 默认false 如果为true时,看的方向是当前位置与目标物体垂直平面的垂线方向
*/
/**
* 看对象或一个目标位置
* @param {Array<number>|THING.Object3D} target 要看的对象或目标位置
* @param {LookAtArgs} param 参数列表
* @returns {boolean} 是否看成功
* @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); }
/**
* 世界坐标转自身坐标
* @param {Array<number>} position 世界坐标
* @param {boolean} [ignoreScale=false] 转换时是否忽略缩放 默认不忽略
* @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); }
/**
* 自身坐标转世界坐标
* @param {Array<number>} position 自身坐标系下的坐标
* @param {boolean} [ignoreScale=false] 转换时是否忽略缩放 默认不忽略
* @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); }
/**
* 相对于父物体的坐标转为世界坐标
* @param {Array<number>} position 相对于父物体的坐标
* @param {boolean} [ignoreScale=false] 转换时是否忽略缩放 默认不忽略
* @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); }
/**
* 世界坐标转为相对于父物体的坐标
* @param {Array<number>} position 世界坐标
* @param {boolean} [ignoreScale=false] 转换时是否忽略缩放 默认不忽略
* @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 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); }
/**
* 绑定子节点,绑定后将自动根据子节点更新世界坐标。
* 绑定后位置、角度和缩放会锁定。
* @param {THING.Object3D} target 目标对象。
* @param {string} subNodeName 子节点名称。
* @returns {boolean} 是否绑定成功。
* @example
* let box = new THING.Box();
* box.bindSubNode(car, 'chair');
* @public
*/
bindSubNode() { return this.transform.bindSubNode.apply(this.transform, arguments); }
/**
* 解绑子节点。
* @example
* let box = new THING.Box();
* box.bindSubNode(car, 'chair');
* box.unbindSubNode();
* @public
*/
unbindSubNode() { this.transform.unbindSubNode.apply(this.transform, arguments); }
// #endregion
// #endregion
get isInitializing() {
return this._flags.has(Flag.Initializing);
}
get Initialized() {
return this._flags.has(Flag.Initialized);
}
get isObject3D() {
return true;
}
get isBaseObject3D() {
console.warn('Plase use isObject3D instead of isBaseObject3D');
return true;
}
get isSceneRoot() {
let _private = this[__.private];
return _private.resource.isSceneRoot;
}
get isSceneNode() {
let _private = this[__.private];
return _private.resource.isSceneNode;
}
/**
* 修正 keepSize 计算得到的缩放值的钩子,子类可根据自身需求进行重写。
* 例如 `Marker` 在开启 `autoFitBodyScale` 模式时,可以在此对缩放做额外比例修复。
* @param {Array<number>} scale 由 `TransformComponent` 计算出的缩放值 `[sx, sy, sz]`。
* @returns {Array<number>} 修正后的缩放值,默认直接返回原值。
* @protected
*/
onFixKeepSizeScale(scale) {
return scale;
}
}
export { Object3D }