import { StringEncoder, Flags, Callbacks } from '@uino/base-thing';
import { Utils, _getAuthData } from '../common/Utils';
import { Selector } from '../selector/Selector';
import { MathUtils } from '../math/MathUtils';
import { Camera } from '../objects/Camera';
import { ResourceManager } from '../managers/ResourceManager';
import { EventManager } from '../managers/EventManager';
import { ObjectManager } from '../managers/ObjectManager';
import { TweenManager } from '../managers/TweenManager';
import { ActionManager } from '../managers/ActionManager';
import { CreateObjectAction } from '../actions/CreateObjectAction';
import { CameraFlyToAction } from '../actions/CameraFlyToAction';
import { ObjectSetColorAction } from '../actions/ObjectSetColorAction';
import { ObjectSetPropertyAction } from '../actions/ObjectSetPropertyAction';
import { StateManager } from '../managers/StateManager';
import { StateGroupManager } from '../managers/StateGroupManager';
import { ColliderManager } from '../managers/ColliderManager';
import { RelationshipManager } from '../managers/RelationshipManager';
import { DummyEventManager } from '../managers/DummyEventManager';
import { ViewPointManager } from '../managers/ViewPointManager';
import { Component } from '../components/Component';
import { AppTimerComponent } from '../components/AppTimerComponent';
import { AppHelperComponent } from '../components/AppHelperComponent';
import { AppResourcePoolComponent } from '../components/AppResourcePoolComponent';
import { BaseComponentGroup } from '../components/BaseComponentGroup';
import { AppRenderConfigComponent } from '../components/AppRenderConfigComponent';
import { AppTimeOfDayComponent } from '../components/AppTimeOfDayComponent';
import { Picker } from './Picker';
import { Global } from './Global';
import { Scene } from './Scene';
import { Marker } from './Marker';
import { EventType, ActionType } from '../const';
import { ThemeManager } from '../managers/ThemeManager';
import { AppPluginComponent } from '../components/AppPluginComponent';
import { AppBundleComponent } from '../components/AppBundleComponent';
import { MonitorManagerComponent } from '../components/MonitorManagerComponent';
import { SelectionComponent } from '../components/SelectionComponent';
const __ = {
private: Symbol('private'),
}
const State = {
Waiting: 0,
Running: 1,
Stopped: 2
};
const Flag = {
Disposing: 1 << 0,
Disposed: 1 << 1
};
const DumnyFunction = function () { }
const registerComponentParam = { isResident: true };
const _authDataEvent = StringEncoder.toText("<@secret __!!a$u^t@h*event@__>");
const _askAuthDataEvent = StringEncoder.toText("<@secret __a)s*k&!!a$u^t@h*event@__>");
/**
* @public
* @class App
* App 是 ThingJS 库入口,提供加载场景、搜索、事件绑定、摄像机控制等功能
* @memberof THING
* @extends THING.BaseComponentGroup
*/
class App extends BaseComponentGroup {
static instances = [];
static completeCallbacks = [];
/**
* 当前App对象
* @type {THING.App}
* @example
* let app = THING.App.current;
* let ret = app.picker != null;
* // @expect(ret == true);
* @public
*/
static get current() {
return Utils.getCurrentApp();
}
static set current(app) {
Utils.setCurrentApp(app);
}
static defaultSettings = {
envMap: true, // Control default envMap
};
/**
* Get alive instances.
* @returns {Array<THING.App>} 存活的应用实例
* @private
* @example
* let instances = THING.App.getAliveInstances();
* let ret = instances.length > 0;
* // @expect(ret == true);
*/
static getAliveInstances() {
return App.instances.filter(app => {
return !app.isDisposed;
});
}
/**
* App实例初始化结束的回调
* @param {Function} callback 回调函数
* @param {number} priority 优先级
* @public
* @example
* THING.App.addCompleteCallback((app)=>{
* console.log(The app instantiation is complete.);
* },-10);
*/
static addCompleteCallback(callback, priority = 0) {
App.instances.forEach(app => {
if (app.isDisposed) {
return;
}
callback(app);
});
App.completeCallbacks.push({ callback, priority });
App.completeCallbacks.sort((a, b) => {
return b.priority - a.priority;
});
}
/**
* App构造方法
* @param {object} param App初始化的参数
* @param {HTMLElement} param.container 渲染场景需要的dom元素
* @param {boolean} [param.isEditor=false] 是否在编辑器环境使用
* @param {boolean} [param.enableDefaultLevelControl=true] 是否默认开启层级控制
* @param {number | string | Array<number>} param.background? 背景
* @param {string} [param.envMap] 环境图url
* @param {string} [param.url] 场景url
* @param {Function} [param.onComplete] 场景加载完成后的回调函数
* @param {Function} [param.onProgress] 场景加载进度的回调函数
* @param {Function} [param.onError] 场景加载失败后的回调函数
* @example
* // Load bundle scene
* const app = new THING.App({
* url: './scene-bundle',
* onComplete: (e) => {
* console.log(e);
* }
* });
*
* // Load gltf scene
* const app = new THING.App({
* url: './gltf/scene.gltf',
* onComplete: (e) => {
* console.log(e);
* }
* });
* @constructor
* @public
*/
constructor(param = {}) {
super(param);
this[__.private] = {};
let _private = this[__.private];
// Add instance
App.instances.push(this);
// Initialize global setting
this.settings = {
_useDefaultViewpoint: true
};
// The initial options
_private.options = param;
_private.isEditor = Utils.parseBoolean(param['isEditor'], false);
// Initialize global
_private.global = new Global(param);
// Initialize application delegate
this.#initAppDelegate(param);
this.#initAppView(param);
// Make as current
this.makeCurrent();
// The default pixel ratio is base on application delegate attributes
let devicePixelRatio = _private.delegate.getAttribute('DevicePixelRatio');
if (devicePixelRatio) {
_private.view.setPixelRatio(devicePixelRatio);
}
// Initialize common
_private.uuid = MathUtils.generateUUID();
_private.size = [0, 0];
_private.state = State.Waiting;
_private.flags = new Flags();
// Picker
_private.picker = null;
// Create managers
this.#initManager();
// Core
_private.scene = null;
_private.root = null;
// Callbacks
_private.beforeDisposeCallbacks = new Callbacks();
_private.afterDisposeCallbacks = new Callbacks();
_private.beforeTickCallbacks = new Callbacks();
_private.afterTickCallbacks = new Callbacks();
// Create scene
let scene = Utils.createObject('Scene', { name: 'RootScene' });
// Initialize core
this.#initSelector(param);
this.#initResourceManager(param['resourceManager']);
this.#initScene(scene, param);
this.#initPicker();
this.#initCamera(param['camera']);
this.#initComponents();
this.#initEvents();
this.#initLevelManager();
// register actions
this.#registerAction();
// Initialize delegate
_private.delegate.init();
// Start to run
this.run();
// Get the initial size
let size = [];
_private.delegate.getSize(size);
// Resize it
this.resize(size[0], size[1]);
// Notify resize event when start up
this.trigger('Resize', {
width: size[0],
height: size[1]
});
this.#setupScene();
// Create logo
_private.marker = new Marker(this);
const accessors = _private.scene.getAttribute(StringEncoder.toText("<@secret Accessors>"));
_private.marker.init(accessors[StringEncoder.toText("<@secret overlayMarkerRootNode>")]);
// Notify outside the application has been completed
Utils.onCompleteApp(this);
// Notify complete callbacks
App.completeCallbacks.forEach(info => {
info.callback(this);
});
// Load scene
if (param.url) {
this.load(param);
}
if (_NEED_LOGGER) {
let msg = { "renderCapabilities": this.renderCapabilities }
Utils.logger.api("App_create", { msg: JSON.stringify(msg) })
}
this._workPath = "";
}
// #region Private
#initAppDelegate(param) {
let _private = this[__.private];
let delegate = Utils.createObject('AppDelegate', param);
if (!delegate) {
Utils.error(`Create app delegate failed`);
}
_private.delegate = delegate;
}
#initAppView(param) {
let _private = this[__.private];
let canvas = Utils.parseValue(param['canvas'], null);
let compatibleOptions = param.compatibleOptions || {};
let asyncGPUPicking = param['asyncGPUPicking'] || false;
if (Utils.settings['compatibleOptions']) {
compatibleOptions = Utils.mergeObject(compatibleOptions, Utils.settings['compatibleOptions']);
}
_private.view = Utils.createObject('AppView', { canvas, appDelegate: this.delegate, compatibleOptions, asyncGPUPicking });
_private.renderCapabilities = _private.view.getCapabilities();
if (_DEBUG) {
let rendererType = _private.view.getAttribute('RendererType');
let disableBackward = _private.delegate.getAttribute('DisableBackward');
Utils.log(`Init app view(rendererType: ${rendererType}, disableBackward: ${disableBackward})`);
}
}
#initManager() {
this.addComponent(EventManager, 'eventManager', registerComponentParam);
this.addComponent(ObjectManager, 'objectManager', registerComponentParam);
this.addComponent(TweenManager, 'tweenManager', registerComponentParam);
this.addComponent(ColliderManager, 'colliderManager', registerComponentParam);
this.addComponent(ActionManager, 'actionManager', registerComponentParam);
this.addComponent(StateManager, 'stateManager', registerComponentParam);
this.addComponent(StateGroupManager, 'stateGroupManager', registerComponentParam);
this.addComponent(RelationshipManager, 'relationshipManager', registerComponentParam);
this.addComponent(ThemeManager, 'themeManager', registerComponentParam);
this.addComponent(ViewPointManager, 'viewPointManager', registerComponentParam);
}
#initSelector(param) {
Selector.init(param['selector']);
}
#initScene(scene, param) {
let _private = this[__.private];
_private.scene = new Scene({
app: this,
scene,
initLights: param['initLights'],
envMap: param['envMap'] || param['env'],
defaultSettings: App.defaultSettings,
});
_private.root = _private.scene.rootObjects['scene'];
}
#initResourceManager(options = {}) {
let _private = this[__.private];
const resourceManager = new ResourceManager(options);
this.addComponent(resourceManager, 'resourceManager', registerComponentParam);
// Load resources for global cache
_private.global.loadResource(resourceManager);
}
#initPicker() {
let _private = this[__.private];
_private.picker = new Picker();
}
#initCamera(options = {}) {
let _private = this[__.private];
// We set initial position to prevent target and position is the same
let cameraOptions = Object.assign({}, options);
cameraOptions['position'] = [10, 10, 10];
cameraOptions['tags'] = ['MainCamera'];
let camera = new Camera(cameraOptions);
camera.enable = true;
camera.enableViewport = true;
camera.background = Utils.parseValue(cameraOptions['background'], [0.5647058823529412, 0.5647058823529412, 0.5647058823529412]);
camera.control.autoAdjustInstanceOffset = true;
// Add camera into scene
_private.root.add(camera);
// Make as main camera
let scene = _private.scene;
scene.camera = camera;
scene.renderCamera = camera;
}
#initComponents() {
this.addComponent(AppTimerComponent, 'timer', registerComponentParam);
this.addComponent(AppHelperComponent, 'helper', registerComponentParam);
this.addComponent(AppResourcePoolComponent, 'resourcePool', registerComponentParam);
this.addComponent(AppRenderConfigComponent, 'renderConfig', registerComponentParam);
this.addComponent(AppPluginComponent, 'plugin', registerComponentParam);
this.addComponent(AppBundleComponent, 'bundle', registerComponentParam);
this.addComponent(MonitorManagerComponent, 'monitorManager', registerComponentParam);
this.addComponent(AppTimeOfDayComponent, 'time', registerComponentParam);
this.addComponent(SelectionComponent, 'selection', registerComponentParam);
}
#initEvents() {
let _private = this[__.private];
let root = _private.root;
// Hook focus in
root.on('windowfocusin', (ev) => {
root.trigger(EventType.FocusIn, ev);
}, {
enumerable: false
});
// Hook focus out
root.on('windowfocusout', (ev) => {
root.trigger(EventType.FocusOut, ev);
}, {
enumerable: false
});
// Hook resize event
root.on('windowresize', (ev) => {
this.resize(ev.width, ev.height);
}, {
enumerable: false
});
// Hook drop files
root.on('windowdrop', (ev) => {
root.trigger(EventType.DropFiles, ev);
}, {
enumerable: false
});
// Hook auth event
root.on(_askAuthDataEvent, (ev) => {
let authData = _getAuthData();
if (authData) {
root.trigger(_authDataEvent, authData);
}
}, {
enumerable: false
});
}
#initLevelManager() {
const _private = this[__.private];
const LevelManager = Utils.getRegisteredClass('LevelManager');
_private.level = new LevelManager(this);
}
#setupScene() {
const _private = this[__.private];
if (!_private.view.getAttribute('CompatibleRender') && _private.scene.mainLight) {
_private.scene.mainLight.adapter.bindCamera(_private.scene.camera);
}
}
#registerAction() {
this.actionManager.register(ActionType.CreateObject, new CreateObjectAction());
this.actionManager.register(ActionType.CameraFlyTo, new CameraFlyToAction());
this.actionManager.register(ActionType.ObjectSetColor, new ObjectSetColorAction());
this.actionManager.register(ActionType.ObjectSetProperty, new ObjectSetPropertyAction());
}
// #endregion
// #region Context
/**
* Make application as current.
* @private
* @example
* let app = THING.App.current;
* app.makeCurrent();
*/
makeCurrent() {
Utils.setCurrentApp(this);
const _private = this[__.private];
if (_private.delegate) {
_private.delegate.makeCurrent();
}
}
// #endregion
// #region Common
/**
* When focus change.
* @callback onFocusChangeCallback
* @param {FocusEvent} ev The event info.
*/
/**
* Get/Set onFocusChange callback
* @type {onFocusChangeCallback}
* @private
*/
get onFocusChange() {
let _private = this[__.private];
_private.delegate.getFocusChangeCallback();
}
set onFocusChange(value) {
let _private = this[__.private];
_private.delegate.setFocusChangeCallback(value);
}
/**
* Get/Set resize throttling wait time in milliseconds.
* @type {number}
* @private
*/
get resizeThrottlingWaitTime() {
let _private = this[__.private];
return _private.delegate.getAttribute('ResizeThrottlingWaitTime');
}
set resizeThrottlingWaitTime(value) {
let _private = this[__.private];
_private.delegate.setAttribute('ResizeThrottlingWaitTime', value);
}
/**
* Set foucs on it.
* @private
* @example
* let app = THING.App.current;
* app.focus();
*/
focus() {
let _private = this[__.private];
_private.delegate.focus();
}
/**
* 销毁app
* @public
* @example
* let app = THING.App.current;
* app.dispose();
*/
dispose() {
let _private = this[__.private];
if (_private.flags.has(Flag.Disposed)) {
return;
}
this.trigger(EventType.AppQuit);
_private.beforeDisposeCallbacks.invoke();
{
// Stop all blueprint, because onStop may use app and other interfaces
// So it must be called before dispose
this.stopAllBlueprints();
_private.flags.enable(Flag.Disposing, true);
this.stop();
_private.level.dispose();
_private.level = null;
this.removeComponent('plugin');
_private.scene.dispose();
_private.scene = null;
_private.root = null;
_private.picker.dispose();
_private.picker = null;
this.removeAllComponents(true);
// We do not set to null due to keep code clear
this.addComponent(new DummyEventManager(), 'eventManager', registerComponentParam);
_private.global.dispose();
_private.global = null;
_private.view.dispose();
_private.view = null;
_private.delegate.dispose();
_private.delegate = null;
_private.beforeTickCallbacks.clear();
_private.afterTickCallbacks.clear();
_private.flags.enable(Flag.Disposing, false);
}
_private.afterDisposeCallbacks.invoke();
// Dispose complete
_private.flags.enable(Flag.Disposed, true);
// Update current app if it's needed
if (App.current == this) {
let instances = App.instances;
Utils.removeFromArray(instances, this);
if (instances.length) {
App.current = instances[0];
App.current.makeCurrent();
}
else {
App.current = null;
}
}
// Make some interfaces as dummy function for easy coding
this.trigger = DumnyFunction;
if (_NEED_LOGGER) {
let msg = { "renderCapabilities": this.renderCapabilities };
Utils.logger.api("App_dispose", { msg: JSON.stringify(msg) })
}
}
/**
* 创建对象
* 2.0兼容1.0中通过app.create({type:'ClassName'})的方式创建对象,<br>
* 但是推荐使用new THING.ClassName({xxx})的方式创建对象。
* @param {object} param The parameter list 参数列表
* @param {string} param.type Object type 类型
* @param {object} param.options? Object create parameters 创建对于类型对象的初始化参数
* @returns {THING.BaseObject} 返回创建的对象
* @example
* let app = THING.App.current;
* let box = app.create({
* type: 'Box',
* name: 'box',
* position: [1, 1, 1],
* onComplete: function() {
* console.log('box01 created!');
* }
* });
* let ret = box instanceof THING.Box;
* // @expect(ret == true);
* @public
* @deprecated
*/
create(param) {
if (!param) {
console.warn('create object failed, due to param is null');
return null;
}
const typeClass = Utils.getRegisteredClasses()[param['type']];
if (!typeClass) {
console.warn('Object creation failed, due to type not found or not supported');
return null;
}
const paramCopy = Object.assign({}, param);
delete paramCopy['type'];
const targetOptions = paramCopy['options'] || paramCopy;
if (targetOptions.app != null && targetOptions.app != this) {
console.error('app.create failed, due to options.app is not this');
return null;
}
const options = Object.assign({ app: this }, targetOptions);
return new typeClass(options);
}
/**
* 加载场景
* @param {string | object} url 场景url
* @param {object} options 加载参数
* @param {boolean} options.dynamic 是否动态加载
* @param {boolean} options.hidden 加载时是否隐藏
* @param {boolean} options.useDefaultTheme 是否使用场景自带的样式
* @param {boolean} options.useDefaultViewpoint 是否使用场景自带的相机视角
* @param {boolean} options.useDefaultRenderSettings 是否使用场景自带的环境参数
* @param {number[]} options.position 场景位置
* @param {number[]} options.rotation 场景的旋转角度
* @param {Function} options.onComplete 加载完成的回调
* @param {Function} options.onProgress 加载进度的回调
* @param {Function} options.onError 加载异常的回调
* @returns {Promise} 由于加载时异步过程,返回一个Promise对象
* @example
* THING.App.current.load('./assets/scenes/scene.gltf').then((ev) => {
* let objs = ev.objects;
* let count = objs.length;
* // @expect(count > 0);
* })
* @public
*/
load(url, options) {
return this.resourcePool.load(url, options);
}
/**
* 加载插件
* @param {string} url 插件路径
* @param {object} options 加载参数
* @param {string} options.name 插件名称
* @param {Function} options.onComplete 加载成功的回调
* @param {Function} options.onError 加载失败的回调
* @returns {Promise} Promise对象
* @example
* THING.App.current.loadPlugin('./assets/plugins/plugin.json').then((plugin)=>{
* console.log(plugin);
* });
* @public
*/
loadPlugin(url, options) {
return this.resourcePool.loadPlugin(url, options);
}
getRunState() {
const _private = this[__.private];
return _private.state
}
/**
* 加载图片纹理
* @param {string} url 资源路径
* @param {LoadTextureResourceSamplerInfo} sampler 纹理采样参数
* @returns {THING.ImageTexture} 返回图片纹理
* @example
* let image = THING.App.current.loadImageTexture('./flower.png');
* await image.waitForComplete();
* console.log(image);
* @public
*/
loadImageTexture(url, sampler) {
return this.resourcePool.loadImageTexture(url, sampler);
}
/**
* Load cube texture。
* @param {string} url The resource url
* @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
* @returns {THING.CubeTexture} 返回立方体纹理
* @example
* let image = THING.App.current.loadCubeTexture([
* './skyboxes/bluesky/posx.jpg', './skyboxes/bluesky/negx.jpg',
* './skyboxes/bluesky/posy.jpg', './skyboxes/bluesky/negy.jpg',
* './skyboxes/bluesky/posz.jpg', './skyboxes/bluesky/negz.jpg'
* ]);
* @public
*/
loadCubeTexture(url, sampler) {
return this.resourcePool.loadCubeTexture(url, sampler);
}
/**
* 根据名称卸载插件
* @param {string} name 插件名称
* @returns {boolean} 是否卸载成功
* @example
* THING.App.current.uninstall(plugin.name);
* @public
*/
uninstall(name) {
return this.plugin.uninstall(name);
}
/**
* Load from URL or data.
* @param {object} options The options.
* @param {string | Array<string>} options.url The resource URL(s).
* @param {object} options.data The json data.
* @example
* app.load({ url: './blueprints/myBP.json' });
* app.run();
* @private
*/
loadBlueprint(options = {}) {
this.blueprint.load(options);
this.blueprint.run();
}
/**
* Stop all blueprints. include all objects' blueprints.
* @private
*/
stopAllBlueprints() {
// RootObject is not queryable, so it must be stopped first
this.blueprint.stop();
let all = this.query('*');
all.forEach(item => {
item.hasComponent('blueprint') && item.blueprint.stop();
});
}
/**
* Check whether it's disposing.
* @type {boolean}
* @private
*/
get isDisposing() {
let _private = this[__.private];
return _private.flags.has(Flag.Disposing);
}
/**
* Check whether it had been disposed.
* @type {boolean}
* @private
*/
get isDisposed() {
let _private = this[__.private];
return _private.flags.has(Flag.Disposed);
}
/**
* Get the options.
* @type {AppOptions}
* @private
*/
get options() {
let _private = this[__.private];
return _private.options;
}
/**
* Get the global.
* @type {THING.Global}
* @private
*/
get global() {
return this[__.private].global;
}
/**
* @public
* 获取全局渲染配置。
* @name renderSettings
* @memberof THING.App
* @instance
* @kind member
* @type {RenderSettingsInfo}
*/
/**
* Get the uuid.
* @type {string}
* @private
*/
get uuid() {
return this[__.private].uuid;
}
/**
* Get the system info.
* @type {object}
* @private
*/
get systemInfo() {
let _private = this[__.private];
let extensions = _private.view.getExtensions();
return _private.delegate.getAttribute('System', {
context: _private.view.getContext(),
extensions,
});
}
/**
* 获取拾取器
* @type {THING.Picker}
* @example
* let picker = app.picker;
* // @expect(picker != null)
* picker.enable = false;
* // @expect(picker.enable == false)
* @public
*/
get picker() {
let _private = this[__.private];
return _private.picker;
}
// #endregion
// #region Components
addComponent(component, name, args) {
if (component.className == Component.className) {
Utils.error(`Add component (name: '${name}') to app failed`);
return false;
}
return super.addComponent(component, name, args);
}
/**
* Get the blueprint component.
* @type {THING.BlueprintComponent}
* @private
*/
get blueprint() {
return this.root.blueprint;
}
/**
* Get the dynamic load component.
* @type {THING.DynamicLoadComponent}
* @private
*/
get dynamicLoad() {
return this.root.dynamicLoad;
}
// #endregion
// #region Manager
/**
* 获取帮助对象
* @member {THING.AppHelperComponent} helper
* @memberof THING.App
* @public
* @instance
*/
/**
* 获取TweenManager
* @member {THING.TweenManager} tweenManager
* @memberof THING.App
* @public
* @instance
*/
/**
* 获取关系管理器
* @member {THING.RelationshipManager} relationshipManager
* @memberof THING.App
* @public
* @instance
*/
/**
* 获取层级管理器
* @type {THING.LevelManager}
* @example
* let app = THING.App.current;
* let level = app.level;
* let target = app.query('.Entity')[0];
* level.change(target, {
* onComplete: function(){
* let ret = level.current == target;
* // @expect(ret == true);
* }
* });
* @public
*/
get level() {
return this[__.private].level;
}
/**
* Get the level manager.
* @type {THING.LevelManager}
* @example
* let app = THING.App.current;
* let level = app.level;
* let levelManager = app.levelManager;
* let ret = level == levelManager;
* // @expect(ret == true);
* @deprecated 2.7
* @private
*/
get levelManager() {
return this[__.private].level;
}
// #endregion
// #region Render
/**
* Add callback before dispose.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.addBeforeDisposeCallback(function() {
* console.log('before dispose');
* });
*/
addBeforeDisposeCallback(callback) {
this[__.private].beforeDisposeCallbacks.add(callback);
}
/**
* Remove callback before dispose.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.removeBeforeDisposeCallback(function() {
* console.log('before dispose');
* });
*/
removeBeforeDisposeCallback(callback) {
this[__.private].beforeDisposeCallbacks.remove(callback);
}
/**
* Add callback after dispose.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.addAfterDisposeCallback(function() {
* console.log('after dispose');
* });
*/
addAfterDisposeCallback(callback) {
this[__.private].afterDisposeCallbacks.add(callback);
}
/**
* Remove callback after dispose.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.removeAfterDisposeCallback(function() {
* console.log('after dispose');
* });
*/
removeAfterDisposeCallback(callback) {
this[__.private].afterDisposeCallbacks.remvoe(callback);
}
/**
* Add callback before tick.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.addBeforeTickCallback(function() {
* console.log('before tick');
* });
*/
addBeforeTickCallback(callback) {
this[__.private].beforeTickCallbacks.add(callback);
}
/**
* Remove callback before tick.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.removeBeforeTickCallback(function() {
* console.log('before tick');
* });
*/
removeBeforeTickCallback(callback) {
this[__.private].beforeTickCallbacks.remove(callback);
}
/**
* Add callback after tick.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.addAfterTickCallback(function() {
* console.log('after tick');
* });
*/
addAfterTickCallback(callback) {
this[__.private].afterTickCallbacks.add(callback);
}
/**
* Remove callback after tick.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.removeAfterTickCallback(function() {
* console.log('after tick');
* });
*/
removeAfterTickCallback(callback) {
this[__.private].afterTickCallbacks.remove(callback);
}
/**
* Add callback before render.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.addBeforeRenderCallback(function() {
* console.log('before render');
* });
*/
addBeforeRenderCallback(callback) {
this[__.private].scene.addBeforeRenderCallback(callback);
}
/**
* Remove callback before render.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.removeBeforeRenderCallback(function() {
* console.log('before render');
* });
*/
removeBeforeRenderCallback(callback) {
this[__.private].scene.removeBeforeRenderCallback(callback);
}
/**
* Add callback after render.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.addAfterRenderCallback(function() {
* console.log('after render');
* });
*/
addAfterRenderCallback(callback) {
this[__.private].scene.addAfterRenderCallback(callback);
}
/**
* Remove callback after render.
* @param {Function} callback The function.
* @private
* @example
* let app = THING.App.current;
* app.removeAfterRenderCallback(function() {
* console.log('after render');
* });
*/
removeAfterRenderCallback(callback) {
this[__.private].scene.removeAfterRenderCallback(callback);
}
/**
* Convert pixel to screen coordinates in [-1, 1].
* @param {Array<number>} position The position.
* @returns {Array<number>} 返回屏幕坐标
* @private
* @example
* let app = THING.App.current;
* let position = [100, 100];
* let screenPosition = app.pixelToScreenCoordinate(position);
* // @expect(screenPosition[0] == 0.5 && screenPosition[1] == 0.5);
*/
pixelToScreenCoordinate(position) {
return MathUtils.pixelToScreenCoordinate(position, this.size);
}
/**
* Convert screen coordinates in [-1, 1] to pixel.
* @param {Array<number>} position The position in [-1, 1].
* @returns {Array<number>} 返回像素坐标
* @private
* @example
* let app = THING.App.current;
* let position = [0.5, 0.5];
* let pixelPosition = app.screenCoordinateToPixel(position);
* // @expect(pixelPosition[0] == 100 && pixelPosition[1] == 100);
*/
screenCoordinateToPixel(position) {
return MathUtils.screenCoordinateToPixel(position, this.size);
}
/**
* 启动app的运行和渲染
* @public
* @example
* let app = THING.App.current;
* app.run();
*/
run() {
let _private = this[__.private];
if (_private.state == State.Running) {
return;
}
_private.state = State.Running;
// Get internal variables
let timer = this.timer;
// Start to render, elapsedTime is in milliseconds
timer.init({
frameRate: _private.options['frameRate'],
onTick: (deltaTime) => {
// The delta time in seconds
// Before tick
_private.beforeTickCallbacks.invoke(deltaTime);
// Update core objects
_private.marker.update(deltaTime);
// Update managers
this.objectManager.update(deltaTime);
this.tweenManager.update(deltaTime);
this.colliderManager.update(deltaTime);
this.stateManager.update(deltaTime);
this.eventManager.update(deltaTime);
this.monitorManager.update(deltaTime);
// After tick
this.objectManager.lateUpdate(deltaTime);
_private.afterTickCallbacks.invoke(deltaTime);
_private.view.update(deltaTime);
// Render scene
_private.scene.render(deltaTime);
}
});
// Start timer with delegate
_private.delegate.run((elapsedTime, frame) => {
this.timer.update(elapsedTime);
});
}
/**
* 停止app的渲染
* @public
* @example
* let app = THING.App.current;
* app.stop();
*/
stop() {
let _private = this[__.private];
if (_private.state == State.Stopped) {
return;
}
_private.state = State.Stopped;
_private.delegate.stop();
}
/**
* Lock size to ignore resize event.
* @param {number} width The width in pixel.
* @param {number} height The height in pixel.
* @returns {boolean} 返回是否锁定成功
* @private
* @example
* let app = THING.App.current;
* let ret = app.lockSize(1024, 768);
* // @expect(ret == true);
*/
lockSize(width, height) {
let _private = this[__.private];
if (!_private.delegate.lockSize(width, height)) {
return false;
}
this.resize(width, height);
return true;
}
/**
* Unlock size.
* @private
* @example
* let app = THING.App.current;
* app.unlockSize();
*/
unlockSize() {
let _private = this[__.private];
_private.delegate.unlockSize();
let size = [0, 0];
_private.delegate.getSize(size);
this.resize(size[0], size[1]);
}
/**
* Capture screen shot into pixel buffer in RGBA color format.
* @param {number} width The image width in pixel, if it not provide then use the current width.
* @param {number} height The image height in pixel, if it not provide then use the current height.
* @returns {Uint8Array} 返回像素缓冲区
* @example
* let data = THING.App.current.captureScreenshotToData(640, 480);
* let ret = data.length == (640 * 480 * 4);
* // @expect(ret == true);
*/
captureScreenshotToData(width, height) {
return this.camera.captureToData(width, height);
}
/**
* Capture screen shot into image.
* @param {number} width The image width in pixel.
* @param {number} height The image height in pixel.
* @returns {object} 返回图片对象
* @example
* let image = THING.App.current.captureScreenshotToImage(640, 480);
* let ret1 = image instanceof Image;
* let ret2 = image.width == 640 && image.height == 480;
* THING.Utils.setTimeout(() => {
* // @expect(ret1 == true && ret2 == true);
* }, 100);
*/
captureScreenshotToImage(width, height) {
return this.camera.captureToImage(width, height);
}
/**
* Capture screen shot into file.
* @param {string} fileName The file name.
* @param {number} width The image width in pixel.
* @param {number} height The image height in pixel.
* @example
* THING.App.current.captureScreenshotToFile('cameraCapture', 640, 480);
*/
captureScreenshotToFile(fileName, width, height) {
this.camera.captureToFile(fileName, width, height);
}
/**
* Get the delegate.
* @type {object}
* @private
*/
get delegate() {
let _private = this[__.private];
return _private.delegate;
}
/**
* Get the view.
* @type {object}
* @private
*/
get view() {
let _private = this[__.private];
return _private.view;
}
/**
* Get the container.
* @type {object}
* @example
* //the html div id is div3d
* let id = THING.App.current.container.id;
* let ret = id == 'div3d';
* // @expect(ret == true);
* @public
*/
get container() {
let _private = this[__.private];
return _private.delegate.getAttribute('Container');
}
/**
* Get the cursor position in container.
* @type {Array<number>}
* @private
*/
get cursorPosition() {
let _private = this[__.private];
return _private.delegate.getAttribute('CursorPosition');
}
/**
* 获取大小。
* @type {Array<number>}
* @public
* @example
* // div3d的大小是1024*768
* let size = THING.App.current.size;
* // @expect(size[0] == 1024 && size[1] == 768);
*/
get size() {
let _private = this[__.private];
return _private.size.slice(0);
}
/**
* Get the scene.
* @type {THING.Scene}
* @private
*/
get scene() {
let _private = this[__.private];
return _private.scene;
}
/**
* 获取根对象。
* @type {THING.RootObject}
* @public
* @example
* let root = THING.App.current.root;
* // @expect(root.isRootObject == true)
*/
get root() {
let _private = this[__.private];
return _private.root;
}
/**
* 获取所有的对象关系。
* @type {THING.Relationship[]}
* @public
* @readonly
* @example
* let app = THING.App.current;
* let count1 = app.relationships.length;
* app.relationshipManager.addRelationship({
* source: app.root,
* target: app.root
* })
* let count2 = app.relationships.length;
* let ret = count2 - count1;
* // @expect(ret == 1)
*/
get relationships() {
return this.relationshipManager.relationships;
}
/**
* 获取/设置 相机对象
* @type {THING.Camera}
* @public
* @example
* let camera = new THING.Camera();
* camera.far = 100;
* THING.App.current.camera = camera;
* let ret = THING.App.current.camera.far == 100;
* // @expect(ret == true)
*/
get camera() {
return this[__.private].scene.camera;
}
set camera(value) {
this[__.private].scene.camera = value;
}
/**
* Get/Set the render camera.
* @type {THING.Camera}
* @private
*/
get renderCamera() {
return this[__.private].scene.renderCamera;
}
set renderCamera(value) {
this[__.private].scene.renderCamera = value;
}
/**
* Get the render capabilities.
* @type {THING.RendererCapabilities}
* @private
*/
get renderCapabilities() {
return this[__.private].renderCapabilities;
}
/**
* 获取/设置像素比例。
* 设置/获取像素比例,默认为1,可以设置为0-1之间的值。
* 值越大,渲染效果越清晰(帧率较低);
* 值越小,渲染效果越模糊(帧率较高)。
* 在移动设备上,为了提高渲染帧率,可以将app.pixelRatio设置为小于1的值。
* @type {number}
* @public
* @example
* let app = THING.App.current;
* app.pixelRatio = 0.5;
* let ret = app.pixelRatio == 0.5;
* // @expect(ret == true)
*/
get pixelRatio() {
let _private = this[__.private];
return _private.view.getPixelRatio();
}
set pixelRatio(value) {
let _private = this[__.private];
_private.view.setPixelRatio(value);
}
// #endregion
// #region logger
_outputLogger(funcName, condition) {
let msg = { "condition": condition };
Utils.logger.api("App_" + funcName, { msg: JSON.stringify(msg) });
}
// #endregion
// #region Query
/**
* 查询app下所有符合条件的对象并返回第一个
* @param {string} condition The conditions. 查询条件
* @returns {THING.BaseObject} 返回第一个符合条件的对象
* @example
* let obj = THING.App.current.find('.BaseObject');
* let ret = obj instanceof THING.BaseObject;
* // @expect(ret == true)
* let cylinder = THING.App.current.find('.Cylinder');
* ret = cylinder == null;
* // @expect(ret == true)
* @public
*/
find(condition) {
return this.root.find(condition);
}
/**
* 根据条件查询对象 返回所有满足条件的对象集合
* @param {string} condition 查询条件
* @param {ObjectQueryOptions} options 查询参数 主要包括是否递归查询和是否包含自身
* @returns {THING.Selector} 查询结果
* @example
* // 1. 基本查询
* let obj = THING.App.current.query('.BaseObject');
* let ret = obj[0] instanceof THING.BaseObject;
* // @expect(ret == true)
*
* // 2. 按对象名称查询
* let car = app.query('car01'); // 精确匹配名称
* let door = app.query('正门摄像头'); // 查询名称为"正门摄像头"的对象
*
* // 3. 按正则表达式查询名称
* // 方式一: 直接使用正则
* let frontDoors = app.query(/正门/); // 查询名称包含"正门"的对象
*
* // 方式二: 构造正则
* let regex = new RegExp("南天门");
* let gates = app.query(regex);
*
* // 方式三: 使用正则组合
* let doors = app.query(/(正门|侧门|后门)/); // 查询包含任意门的对象
*
* // 方式四: 动态构造正则
* let name = "南天门";
* let dynamicRegex = eval("/"+name+"/");
* let dynamicResult = app.query(dynamicRegex);
*
* // 4. 按对象类查询
* let points = app.query('.GeoBasePoint'); // 查询所有地图点对象
* let campus = app.query('.Campus'); // 查询园区类
* let buildings = app.query('.Building'); // 查询建筑类
* let floors = app.query('.Floor'); // 查询楼层类
* let rooms = app.query('.Room'); // 查询房间类
* let devices = app.query('.Device'); // 查询设备类对象
* let groups = app.query('.Group'); // 查询组
* let cabinets = app.query('.Cabinet'); // 查询机柜
* let racks = app.query('.Rack'); // 查询柜内架式设备
*
* // 5. 按对象ID查询 (注意: ID查询通常只返回一个对象,建议加[0])
* let obj1 = app.query('#100')[0]; // id查询需要加#
* let earth = app.query('[uuid=earthRoot]')[0]; // 获取地图根对象
*
* // 6. 按对象分类查询
* let alarms = app.query('[alarm]'); // 查询有alarm属性的对象
* let cameras = app.query('[twinType=摄像头]'); // 查询分类为摄像头的对象
*
* // 7. 按对象属性查询 (注意: 条件需要用[]括起来)
* // 7.1 自定义属性查询
* let chinaDevices = app.query('[userData/DATA/产地=中国]');
*
* // 7.2 系统属性查询
* let dbObj = app.query('[userData/_DBID_=7499775862803471]'); // 按数据库ID查询
* let ciObj = app.query('[userData/_CICODE_=7366821180693235]'); // 按CICODE查询
* let typeObj = app.query('[userData/_SHOWTYPE_=建筑]'); // 按对象分类查询
* let childObj = app.query('[userData/_PARENT_=优锘产业园_PARK]'); // 按父对象编号查询
* let parentNameObj = app.query('[userData/_PARENTNAME_=T1]'); // 按父对象名称查询
*
* // 7.3 其他属性查询
* let upperFloors = app.query('[levelNum>2]'); // 查询2层以上的楼层
* let hasChildren = app.query('[children!=""]'); // 查询有子对象的对象
* let visibleObj = app.query('[visible=1]'); // 查询可见对象
* let hiddenObj = app.query('[visible="0"]'); // 查询隐藏对象
* let uuidObj = app.query('[uuid="xxxx"]'); // 查询指定uuid的对象
*
* // 8. 按监控数据查询
* let tempObj = app.query('[MONITOR/_/_/温度/value=38]'); // 查询温度为38的对象
* let highTempObj = app.query('[MONITOR/_/_/温度/value>18.8]'); // 查询温度大于18.8的对象
*
* // 9. 查询全部对象
* // 9.1 按类型查询全部
* let allDevices = app.query('.Device'); // 查询全部设备
* let allThings = app.query('.Thing'); // 查询全部物体对象(含设备、标记)
* let allTwins = app.query('[twinType]'); // 获取孪生体管理中全部对象
*
* // 9.2 查询所有可管理对象
* let allObjects = app.query('.BaseObject');
* // 或使用以下方式
* let objects1 = app.query('[isBaseObject=1]');
* let objects2 = app.query('[queryID]');
* let objects3 = app.query('[pickable=1]');
*
* // 9.3 通配符查询
* let everything1 = app.query('*'); // 使用星号通配符
*
* // 9.4 查询所有对象(包括运行时创建的)
* let all = app.query('/');
*
* // 10. 局部查询
* let building = app.query('.Building')[0];
* let upperFloors = building.query('[levelNum > 1]'); // 查询该建筑二层以上楼层
*
* // 11. 多条件查询
* let visibleTrains = app.query('[businessType=高铁列车]&&[visible=1]'); // 与查询
* let floorsAndBuildings = app.query('.Floor || .Building'); // 或查询
*
* // 12. 筛选查询
* // 12.1 链式查询
* let ibmDevices = app.query('.Device').query('[userData/DATA/品牌=IBM]');
*
* // 12.2 组合查询
* let brandAA = app.query('[userData/DATA/品牌=AA]');
* let deviceOrBrandAA = app.query('.Device').add(brandAA); // 并集
* let devicesExceptCar = app.query('.Device').not('car01'); // 差集
*
* // 13. 查询结果操作
* // 13.1 批量修改基本属性
* app.query('.Building').visible = false; // 隐藏所有建筑
*
* // 13.2 遍历操作
* app.query('.Building').forEach(building => {
* console.log(building.name);
* });
*
* // 13.3 层级访问
* app.query('.Building').forEach(building => {
* building.floors.forEach(floor => { // 访问建筑的楼层
* floor.style.opacity = 0.5;
* });
* });
*
* // 14. 根据标签查询
* // 查询包含 Floor 标签的对象
* let floors = app.query('tags:Floor');
*
* // 使用 and 操作符查询同时包含多个标签的对象
* let objects = app.query('tags:and(Floor,Wall)');
*
* // 使用 or 操作符查询包含任一标签的对象
* let objects = app.query('tags:or(Floor,Wall)');
*
* // 使用 not 操作符查询不包含指定标签的对象
* let objects = app.query('tags:not(Floor)');
* @public
*/
query(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('query', condition);
}
return this.root.query(condition, options);
}
/**
* 查询app下所有符合条件的对象并返回所有匹配的结果
* @param {string} condition The conditions. 查询条件
* @param {ObjectQueryOptions} options 查询参数 主要包括是否递归查询和是否包含自身
* @returns {THING.Selector} 查询结果
* @example
* let objects = THING.App.current.queryAll('.BaseObject');
* let ret = objects.length > 0;
* // @expect(ret == true)
* let cylinders = THING.App.current.queryAll('.Cylinder');
* ret = cylinders.length == 0;
* // @expect(ret == true)
* @public
*/
queryAll(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryAll', condition);
}
return this.root.queryAll(condition, options);
}
/**
* 根据名称查询
* @param {string} condition 查询条件
* @param {ObjectQueryOptions} options 查询参数 主要包括是否递归查询和是否包含自身
* @returns {THING.Selector} 查询结果
* @example
* let car = THING.App.current.queryByName('car01')
* let ret = car.length == 0;
* // @expect(ret == true)
* car = THING.App.current.queryByName('car1')
* ret = car[0].name == 'car1';
* // @expect(ret == true)
* @public
*/
queryByName(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryByName', condition);
}
return this.root.queryByName(condition, options);
}
/**
* 根据id查询
* @param {string} condition 查询条件
* @param {ObjectQueryOptions} options 查询参数
* @returns {THING.Selector} 查询结果
* @example
* let car = THING.App.current.queryById('1')
* let ret = car.length == 0;
* // @expect(ret == true);
* car = THING.App.current.queryById('car01')
* ret = car[0].id == 'car01';
* // @expect(ret == true)
* @public
*/
queryById(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryById', condition);
}
return this.root.queryById(condition, options);
}
/**
* 根据tag查询
* @param {string} condition 查询条件
* @param {ObjectQueryOptions} options 查询参数
* @returns {THING.Selector} 查询结果
* @example
* let car = THING.App.current.queryByTags('car');
* let ret = car.length == 0;
* // @expect(ret == true)
* let entity = THING.App.current.queryByTags('Entity');
* ret = entity[0].tags.has('Entity');
* // @expect(ret == true)
* @public
*/
queryByTags(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryByTags', condition);
}
return this.root.queryByTags(condition, options);
}
/**
* 根据uuid查询
* @param {string} condition 查询条件
* @param {ObjectQueryOptions} options 查询参数
* @returns {THING.Selector} 查询结果
* @example
* let car = app.queryByUUID('1')
* let ret = car.length == 0;
* // @expect(ret == true);
* car = app.queryByUUID('1605')
* ret = car[0].uuid == '1605';
* // @expect(ret == true)
* @public
*/
queryByUUID(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryByUUID', condition);
}
return this.root.queryByUUID(condition, options);
}
/**
* 根据类型查询
* @param {string} condition 查询条件
* @param {ObjectQueryOptions} options 查询参数
* @returns {THING.Selector} 查询结果
* @example
* let car = THING.App.current.queryByType('car')
* let ret = car.length == 0;
* // @expect(ret == true)
* let obj = THING.App.current.queryByType('Entity')
* ret = obj[0].type == 'Entity';
* // @expect(ret == true)
* @public
*/
queryByType(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryByType', condition);
}
return this.root.queryByType(condition, options);
}
/**
* 根据孪生体类型查询
* @param {string} condition 查询条件
* @param {ObjectQueryOptions} options 查询参数
* @returns {THING.Selector} 查询结果
* @example
* let equipment = THING.App.current.queryByDTType('Equipment')
* let ret = equipment.length == 0;
* // @expect(ret == true)
* let sensor = THING.App.current.queryByDTType('Sensor')
* ret = sensor[0].dtType == 'Sensor';
* // @expect(ret == true)
* @public
*/
queryByDTType(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryByDTType', condition);
}
return this.root.queryByDTType(condition, options);
}
/**
* 根据userData(用户数据)查询
* @param {string} condition 查询条件
* @param {ObjectQueryOptions} options 查询参数
* @returns {THING.Selector} 查询结果
* @example
* let car = THING.App.current.queryByUserData('test=1');
* let ret = car.length == 0;
* // @expect(ret == true)
* car = THING.App.current.queryByUserData('id=666');
* ret = car[0].userData.id == 666;
* // @expect(ret == true)
* @public
*/
queryByUserData(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryByUserData', condition);
}
return this.root.queryByUserData(condition, options);
}
/**
* 根据userData查询子对象。
* @param {string} condition 查询条件。
* @param {ObjectQueryOptions} options 查询选项。
* @returns {THING.Selector} 返回查询结果
* @example
* let car = THING.App.current.queryByData('test=1');
* let ret = car.length == 0;
* // @expect(ret == true)
* car = THING.App.current.queryByData('id=666');
* ret = car[0].userData.id == 666;
* // @expect(ret == true)
* @deprecated 2.7
* @private
*/
queryByData(condition, options) {
return this.queryByUserData(condition, options);
}
/**
* 根据正则表达式查询
* @param {string} condition 正则表达式
* @param {ObjectQueryOptions} options 查询参数
* @returns {THING.Selector} 查询结果
* @example
* let car = THING.App.current.queryByRegExp(/car/);
* let ret = car.length == 4;
* //@expect(ret == true)
* @public
*/
queryByRegExp(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryByRegExp', condition.toString());
}
return this.root.queryByRegExp(condition, options);
}
/**
* 根据正则表达式查询子对象。
* @param {string} condition 查询条件。
* @param {ObjectQueryOptions} options 查询选项。
* @returns {THING.Selector} 查询结果
* @example
* let car = THING.App.current.queryByRegExp(/car/);
* let ret = car.length == 4;
* //@expect(ret == true)
* @deprecated 2.7
* @private
*/
queryByReg(condition, options) {
return this.queryByRegExp(condition, options);
}
/**
* 异步的方式查询对象 返回一个Promise对象
* </br>以异步模式根据条件查询子对象。
* @param {string} condition 查询条件。
* @param {ObjectQueryOptions} options 查询选项。
* @returns {Promise<any>} 返回Promise对象
* @private
* @example
* let app = THING.App.current;
* let promise = app.queryAsync('.BaseObject');
* promise.then(function(objects) {
* console.log(objects);
* });
*/
queryAsync(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryAsync', condition);
}
return this.root.queryAsync(condition, options);
}
/**
* 查询关系
* @param {object} options 参数
* @param {string} options.type 关系类型
* @param {string} options.name 关系名称
* @returns {Array<THING.Relationship>} 返回关系数组
* @example
* let lightSwitch = new THING.Box();
* let light = new THING.Box();
* let rel = new THING.Relationship({
* type: 'control',
* source: lightSwitch,
* target: light
* });
* let relationships = app.queryRelationships({type: 'control'});
* let ret1 = relationships[0].source == lightSwitch;
* let ret2 = relationships[0].target == light;
* // @expect(ret1 == true && ret2 == true);
* @public
*/
queryRelationships(options = {}) {
if (_NEED_LOGGER) {
this._outputLogger('queryRelationships', options);
}
return this.relationshipManager.queryRelationships(options);
}
// #endregion
// #region Events
/**
* 根据类型获取事件。
* @param {string} type 事件类型,null 表示获取所有事件。
* @returns {Array<ObjectListenerInfo>} 返回事件信息数组
* @public
* @example
* let app = THING.App.current;
* let events = app.getEvents();
* let ret = events.length > 0;
* // @expect(ret == true);
*/
getEvents(type) {
return this.root.getEvents(type);
}
/**
* 暂事件。
* @param {string} type 事件类型。
* @param {string} condition 选择对象的条件。
* @param {string} tag 事件标签。
* @returns {ObjectListenerInfo} 返回事件信息
* @public
* @example
* let app = THING.App.current;
* let event = app.getEvent('test', '.BaseObject', 'test');
* let ret = event instanceof THING.ObjectListenerInfo;
* // @expect(ret == true);
*/
getEvent(type, condition, tag) {
return this.root.getEvent(type.toLowerCase(), condition, tag);
}
/**
* The base event.
* @typedef BaseEvent
* @property {string} type The case-insensitive name identifying the type of the event.
* @property {THING.BaseObject} object The object that triggered the event.
* @property {THING.BaseObject} target A reference to the object to which the event was originally dispatched.
*/
/**
* The MouseEvent interface represents events that occur due to the user interacting with a pointing device (such as a mouse).
* @typedef {BaseEvent} MouseEvent
* @property {number} x The X coordinate of the mouse pointer in local (screen) coordinates.
* @property {number} y The Y coordinate of the mouse pointer in local (screen) coordinates.
* @property {number} button The button number that was pressed when the mouse event was fired.
* @property {string} buttonType The button type that was pressed when the mouse event was fired.
* @property {boolean} ctrlKey Returns true if the ctrl key was down when the mouse event was fired.
* @property {boolean} shiftKey Returns true if the shift key was down when the mouse event was fired.
* @property {boolean} altKey Returns true if the alt key was down when the mouse event was fired.
*/
/**
* The KeyboardEvent objects describe a user interaction with the keyboard.
* @typedef {BaseEvent} KeyboardEvent
* @property {number} keyCode Returns a number representing the key value of the key represented by the event.
* @property {string} code Returns a string representing the key value of the key represented by the event.
* @property {boolean} ctrlKey Returns a boolean value that is true if the Ctrl key was active when the key event was generated.
* @property {boolean} shiftKey Returns a boolean value that is true if the Shift key was active when the key event was generated.
* @property {boolean} altKey Returns a boolean value that is true if the Alt key was active when the key event was generated.
*/
/**
* EventCallback表示一个事件触发的回调函数。
* @callback EventCallback
* @param {MouseEvent|KeyboardEvent} ev 事件对象,包含了事件的类型和相关属性。
* @returns {any}
* @public
*/
/**
* 注册事件
* @param {string} type 事件类型。
* @param {string} condition 选择对象的条件。在注册事件之前进行查询,对符合条件的对象进行注册。
* @param {EventCallback} callback 回调函数。事件触发时执行的函数。
* @param {string} tag 事件标签。用于标识事件。
* @param {number} priority 优先级值(默认为0,数值越高,优先级越高)。
* @param {ObjectEventOptions} options 事件参数。
* @example
* let app = THING.App.current;
* let markOn = 0;
* app.on('testOn', function(ev) {
* markOn = 1;
* });
* app.trigger('testOn');
* // @expect(markOn == 1);
* @public
* @note 以下事件默认开启 useCapture: true:
* 'wheel',
* 'mouseenter',
* 'mouseleave',
* 'mousemove',
* 'mousedown',
* 'mouseup',
* 'keydown',
* 'keyup',
* 'keypress',
* 'click',
* 'dblclick',
* 'beforeaddchild',
* 'afteraddchild',
* 'beforeremovechild',
* 'afterremovechild'
*/
on(type, condition, callback, tag, priority, options) {
this.root.on(type, condition, callback, tag, priority, options);
}
/**
* 注册一次性的事件,此事件响应只会触发一次,触发后会被自动注销。
* @param {string} type 事件类型
* @param {string} condition 选择对象的条件。在注册事件之前进行查询,对符合条件的对象进行注册。
* @param {EventCallback} callback 回调函数。事件触发时执行的函数。
* @param {string} tag 事件标签。用于标识事件。
* @param {number} priority 优先级值(默认为0,数值越高,优先级越高)。
* @param {ObjectEventOptions} options 事件参数。
* @example
* let app = THING.App.current;
* let markOnce = 0;
* app.once('testOnce', function(ev) {
* markOnce = 1;
* });
* app.trigger('testOnce');
* // @expect(markOnce == 1);
* markOnce = 0;
* app.trigger('testOnce');
* // @expect(markOnce == 0);
* @public
*/
once(type, condition, callback, tag, priority, options) {
this.root.once(type, condition, callback, tag, priority, options);
}
/**
* 注销事件
* @param {string} type 事件类型。
* @param {string} condition 选择对象的条件。注册事件的条件。
* @param {string} tag 事件标签。用于标识事件。
* @example
* let app = THING.App.current;
* let markOff = 0;
* app.on('testOff', function(ev) {
* markOff = 1;
* });
* app.trigger('testOff');
* // @expect(markOff == 1);
* markOff = 0;
* app.off('testOff');
* app.trigger('testOff');
* // @expect(markOff == 0);
* @public
*/
off(type, condition, tag) {
this.root.off(type, condition, tag);
}
/**
* 暂停事件
* @param {string} type 事件类型
* @param {string} condition 注册事件的条件
* @param {string} tag 事件的tag
* @returns {boolean} 返回是否暂停成功
* @example
* let app = THING.App.current;
* let markPause = 0;
* app.on('testPause', function(ev) {
* markPause = 1;
* });
* app.trigger('testPause');
* // @expect(markPause == 1);
* markPause = 0;
* app.pauseEvent('testPause');
* app.trigger('testPause');
* // @expect(markPause == 0);
* @public
*/
pauseEvent(type, condition, tag) {
return this.root.pauseEvent(type, condition, tag);
}
/**
* 恢复事件
* @param {string} type 事件类型。
* @param {string} condition 选择对象的条件。注册事件的条件。
* @param {string} tag 事件标签。用于标识事件。
* @returns {boolean} 返回是否恢复成功
* @example
* let app = THING.App.current;
* let markResume = 0;
* app.on('testResume', function(ev) {
* markResume = 1;
* });
* app.trigger('testResume');
* // @expect(markResume == 1);
* markResume = 0;
* app.pauseEvent('testResume');
* app.trigger('testResume');
* // @expect(markResume == 0);
* app.resumeEvent('testResume');
* app.trigger('testResume');
* // @expect(markResume == 1);
* @public
*/
resumeEvent(type, condition, tag) {
return this.root.resumeEvent(type, condition, tag);
}
/**
* 触发事件。
* @param {string} type 事件类型。
* @param {string} condition 事件条件。
* @param {object} data 事件数据
* @param {string} tagName 事件标签名称。
* @returns {*} 返回事件信息
* @example
* let markTrigger = {};
* THING.App.current.on('myEvent', function(ev) {
* markTrigger = ev;
* });
* THING.App.current.trigger('myEvent', { result: true });
* let ret = markTrigger.result;
* // @expect(ret == true);
*
* new THING.Box();
*
* app.on('test', (e) => {
* console.log('无条件+tag1', e)
* }, 'MyTag1');
* app.on('test', (e) => {
* console.log('无条件+tag2', e)
* }, 'MyTag2');
*
* app.on('test', ".Box", (e) => {
* console.log('条件+tag1', e)
* }, 'MyTag1');
* app.on('test', ".Box", (e) => {
* console.log('条件+tag2', e)
* }, 'MyTag2');
*
* // 事件类型 + 事件条件,触发事件。
* app.trigger('test', '.Box');
* // 事件类型 + 事件数据,触发事件
* app.trigger('test', { result: true });
* // 事件类型 + 事件条件 + 事件标签,触发事件
* app.trigger('test', '.Box', 'MyTag2');
* // 事件类型 + 事件条件 + 事件参数,触发事件
* app.trigger('test', '.Box', { result: true });
* // 事件类型 + 事件条件 + 事件数据 + 事件标签,触发事件
* app.trigger('test', '.Box', { result: true },'MyTag2');
*
* // 注意事项:
* // 事件标签不能单独使用,否则事件标签和事件条件无法区分。
* app.trigger('test', null, 'MyTag2');
* @public
*/
trigger(type, condition, data, tagName) {
return this.root.trigger(type, condition, data, tagName);
}
/**
* 触发事件。
* @param {string} type 事件类型。
* @param {string} condition 事件条件。
* @param {object} data 事件数据
* @param {string} tagName 事件标签名称。
* @returns {Promis} 返回Promise
* @public
*/
invoke(type, condition, data, tagName) {
return this.root.invoke(type, condition, data, tagName);
}
/**
* 添加事件过滤器
* @param {string} type 事件类型。
* @param {string} condition 事件条件。
* @param {object} tag 事件标签。
* @param {Function} callback 事件回调函数。
*/
addEventFilter(type, condition, tag, callback) {
this.root.addEventFilter(type, condition, tag, callback);
}
/**
* 移除事件过滤器
* @param {string} type 事件类型。
* @param {string} condition 事件条件。
* @param {object} tag 事件标签。
*/
removeEventFilter(type, condition, tag) {
this.root.removeEventFilter(type, condition, tag);
}
/**
* 移除所有事件过滤器
*/
removeAllEventFilters() {
this.root.removeAllEventFilters();
}
// #endregion
// #region Clear Buffers
/**
* Enable/Disable auto clear color buffer.
* @type {boolean}
* @private
*/
get autoClearColorBuffer() {
return this.camera.autoClearColorBuffer;
}
set autoClearColorBuffer(value) {
this.camera.autoClearColorBuffer = value;
}
/**
* Enable/Disable auto clear depth buffer.
* @type {boolean}
* @private
*/
get autoClearDepthBuffer() {
return this.camera.autoClearDepthBuffer;
}
set autoClearDepthBuffer(value) {
this.camera.autoClearDepthBuffer = value;
}
/**
* Enable/Disable auto clear stencil buffer.
* @type {boolean}
* @private
*/
get autoClearStencilBuffer() {
return this.camera.autoClearStencilBuffer;
}
set autoClearStencilBuffer(value) {
this.camera.autoClearStencilBuffer = value;
}
/**
* scale of gpupicker‘s buffer
* @type {number}
* @private
*/
get gpuPickerBufferScale() {
return this.camera.gpuPickerBufferScale;
}
set gpuPickerBufferScale(value) {
this.camera.gpuPickerBufferScale = value;
}
// #endregion
// #region Resources
/**
* 获取/设置背景。
* @type {number | string | Array<number> | THING.ImageTexture | THING.VideoTexture}
* @example
* THING.App.current.background = 'gray';
* let ret = THING.App.current.background instanceof Array;
* // @expect(ret == true )
* // 设置背景为天空盒(多张)
* THING.App.current.background = new THING.CubeTexture([
* './skyboxes/bluesky/posx.jpg', './skyboxes/bluesky/negx.jpg',
* './skyboxes/bluesky/posy.jpg', './skyboxes/bluesky/negy.jpg',
* './skyboxes/bluesky/posz.jpg', './skyboxes/bluesky/negz.jpg'
* ]);
* // 设置背景为天空盒(单张)
* // 使用环境贴图创建新的图像纹理,作为天空盒
* THING.App.current.background = new THING.ImageTexture({url: "../texture/envmap.jpg", mappingType: THING.ImageMappingType.EquirectangularReflection });
* // 创建新的图像纹理,作为天空盒
* THING.App.current.background = new THING.ImageTexture({url: "../texture/envmap.jpg" });
* // 设置背景为图像路径,作为天空盒
* THING.App.current.background = "../texture/envmap.jpg";
* @public
*/
get background() {
return this.camera.background;
}
set background(value) {
this.camera.background = value;
}
/**
* 获取/设置场景默认的环境图。
* @type {THING.CubeTexture}
* @example
* THING.App.current.envMap = new THING.CubeTexture('./skyboxes/bluesky');
* let ret = THING.App.current.envMap.url == './skyboxes/bluesky';
* // @expect(ret == true )
* @public
*/
get envMap() {
return this[__.private].scene.envMap;
}
set envMap(value) {
this[__.private].scene.envMap = value;
}
// #endregion
// #region Event
/**
* 重置尺寸。
* @param {number} width 像素宽度。
* @param {number} height 像素高度。
* @public
* @example
* let app = THING.App.current;
* app.resize(100, 100);
*/
resize(width, height) {
let _private = this[__.private];
// Check whether size has changed
let size = _private.size;
if (width == size[0] && height == size[1]) {
return;
}
// Update size
size[0] = width;
size[1] = height;
// Resize core and managers
_private.scene.resize(width, height);
this.objectManager.resize(width, height);
// Reisze view
_private.view.setSize([width, height]);
// Notify resize event
this.trigger('Resize', { width, height });
}
// #endregion
/**
* 是否是app对象
* @type {boolean}
* @example
* let app = THING.App.current;
* if (app.isApp) {
* console.log(`It's app`);
* }
* @public
*/
get isApp() {
return true;
}
/**
* 是否在编辑器环境运行
* @type {boolean}
* @public
*/
get isEditor() {
let _private = this[__.private];
return _private.isEditor;
}
/**
* 设置/获取工作路径 调用app.resolveURL(url)时,如果url以"/"开头,会在url之前拼上workPath
* @type {string}
* @example
* THING.App.current.workPath = 'project/root/';
* let ret = THING.App.current.workPath;
* // @expect(ret == 'project/root/' )
* @public
*/
get workPath() {
return this._workPath;
}
set workPath(val) {
this._workPath = val;
}
/**
* 解析url
* @param {string} url 带解析的url 解析时会根据app.workPath进行url的拼接
* @returns {string} 解析后的url
* @public
* @example
* let app = THING.App.current;
* let url = app.resolveURL('test.png');
* // @expect(url == 'project/root/test.png');
*/
resolveURL(url) {
if (url._startsWith('/')) {
if (this.workPath) {
return this.workPath._appendURL(url);
}
else {
return url;
}
}
return url;
}
}
let startupTimer = setInterval(() => {
if (!Utils._scriptLoader) {
return;
}
Utils._scriptLoader.startAnimationFrame((deltaTime) => {
// Make delta time to seconds
deltaTime /= 1000;
// Update selector
Selector.update(deltaTime);
// Update Utils
Utils.update(deltaTime);
});
clearInterval(startupTimer);
}, 100);
export { App }