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 { StateManager } from '../managers/StateManager';
import { ColliderManager } from '../managers/ColliderManager';
import { LevelManager } from '../managers/LevelManager';
import { RelationshipManager } from '../managers/RelationshipManager';
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 { Picker } from './Picker';
import { Global } from './Global';
import { Scene } from './Scene';
import { Logo } from './Logo';
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';
const __ = {
private: Symbol('private'),
}
const State = {
Waiting: 0,
Running: 1,
Stopped: 2
};
const Flag = {
Disposing: 1 << 0,
Disposed: 1 << 1
};
const DummyEventManager = {
dispatchEvent: function () { },
};
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@__>");
/**
* @class App
* The main application.
* @memberof THING
* @extends THING.BaseComponentGroup
* @public
*/
class App extends BaseComponentGroup {
#logo;
static instances = [];
static completeCallbacks = [];
/**
* The current application.
* @type {THING.App}
* @example
* let app = THING.App.current;
* let ret = app.picker != null;
* // @expect(ret == true);
*/
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
*/
static getAliveInstances() {
return App.instances.filter(app => {
return !app.isDisposed;
});
}
/**
* Add a callback to the end of the app instantiation
* @param {Function} callback The callback.
* @param {Number} priority The priority. (The default is 0, the smaller one is executed first)
* @public
* @example
* THING.App.addCompleteCallback((app)=>{
* console.log(The app instantiation is complete.);
* },-10);
*/
static addCompleteCallback(callback, priority = 0) {
App.instances.forEach(app => {
callback(app);
});
App.completeCallbacks.push({ callback, priority });
App.completeCallbacks.sort((a, b) => {
return b.priority - a.priority;
});
}
/**
* The app options.
* @typedef {Object} AppOptions
* @property {HTMLElement} container? The dom element to render scene.
* @property {Boolean} [isEditor=false] True indicates it's running in editor env.
* @property {Number|String|Array<Number>} background? The background color.
* @property {String} envMap? The environment map resource url.
*/
/**
* The application constructor.
* @param {AppOptions} param The initial parameters.
* @example
* // Load bundle scene
* var app = new THING.App({
* url: './scene-bundle',
* onComplete: (e) = {
* console.log(e);
* }
* });
*
* // Load gltf scene
* var app = new THING.App({
* url: './gltf/scene.gltf',
* onComplete: (e) => {
* console.log(e);
* }
* });
*/
constructor(param = {}) {
super(param);
this[__.private] = {};
let _private = this[__.private];
// Register the first callback for complete
if (!App.current) {
App.addCompleteCallback((app) => {
let options = app.options;
// Load scene
if (options.url) {
app.load(options);
}
});
}
// Add instance
App.instances.push(this);
// Make as current
this.makeCurrent();
// The initial options
_private.options = param;
// Initialize global
_private.global = new Global(param);
// Initialize application delegate
this.#initAppDelegate(param);
this.#initAppView(param);
// 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
_private.eventManager = new EventManager();
_private.objectManager = new ObjectManager();
_private.tweenManager = new TweenManager();
_private.colliderManager = new ColliderManager();
_private.actionManager = new ActionManager();
_private.stateManager = new StateManager();
_private.levelManager = null;
_private.resourceManager = null;
_private.relationshipManager = new RelationshipManager();
_private.themeManager = new ThemeManager();
// 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.onResize(size[0], size[1]);
// Notify resize event when start up
this.trigger('Resize', {
width: size[0],
height: size[1]
});
this.#setupScene();
// Create logo
this.#logo = new Logo();
const accessors = _private.scene.getAttribute(StringEncoder.toText("<@secret Accessors>"));
this.#logo.init(accessors[StringEncoder.toText("<@secret overlayLogoRootNode>")]);
// Notify outside the application has been completed
Utils.onCompleteApp(this);
// Notify complete callbacks
App.completeCallbacks.forEach(info => {
info.callback(this);
});
if (_NEED_LOGGER) {
let msg = { "renderCapabilities": this.renderCapabilities }
Utils.logger.api("App_create", { msg: JSON.stringify(msg) })
}
}
// #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);
_private.view = Utils.createObject('AppView', { canvas, appDelegate: this.delegate, compatibleOptions: param.compatibleOptions || {} });
_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})`);
}
}
#initSelector(param) {
Selector.init(param['selector']);
}
#initScene(scene, param) {
let _private = this[__.private];
_private.scene = new Scene({
scene,
initLights: param['initLights'],
envMap: param['envMap'] || param['env'],
defaultSettings: App.defaultSettings,
});
_private.root = _private.scene.rootObjects['scene'];
}
#initResourceManager(options = {}) {
let _private = this[__.private];
_private.resourceManager = new ResourceManager(options);
// Load resources for global cache
_private.global.loadResource(_private.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];
let camera = new Camera(cameraOptions);
camera.enableViewport = true;
camera.background = Utils.parseValue(cameraOptions['background'], [0.5647058823529412, 0.5647058823529412, 0.5647058823529412]);
// 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);
}
#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.onResize(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() {
let _private = this[__.private];
_private.levelManager = new LevelManager();
}
#setupScene() {
const _private = this[__.private];
if (!_private.view.getAttribute('CompatibleRender')) {
_private.scene.mainLight.adapter.bindCamera(_private.scene.camera);
}
}
#registerAction() {
let _private = this[__.private];
_private.actionManager.registerAction(ActionType.CreateObject, new CreateObjectAction());
_private.actionManager.registerAction(ActionType.CameraFlyTo, new CameraFlyToAction());
_private.actionManager.registerAction(ActionType.ObjectSetColor, new ObjectSetColorAction());
}
// #endregion
// #region Context
/**
* Make application as current.
* @private
*/
makeCurrent() {
Utils.setCurrentApp(this);
}
// #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);
}
/**
* Set foucs on it.
* @private
*/
focus() {
let _private = this[__.private];
_private.delegate.focus();
}
/**
* Dispose.
* @public
*/
dispose() {
let _private = this[__.private];
if (_private.flags.has(Flag.Disposed)) {
return;
}
this.trigger(EventType.AppQuit);
_private.beforeDisposeCallbacks.invoke();
{
_private.flags.enable(Flag.Disposing, true);
this.stop();
_private.levelManager.dispose();
_private.levelManager = null;
this.removeAllComponents(true);
_private.scene.dispose();
_private.scene = null;
_private.root = null;
_private.picker.dispose();
_private.picker = null;
_private.eventManager.dispose();
_private.eventManager = DummyEventManager; // We do not set to null due to keep code clear
_private.colliderManager.dispose();
_private.colliderManager = null;
_private.actionManager.dispose();
_private.actionManager = null;
_private.stateManager.dispose();
_private.stateManager = null;
_private.tweenManager.dispose();
_private.tweenManager = null;
_private.objectManager.dispose();
_private.objectManager = null;
_private.resourceManager.dispose();
_private.resourceManager = null;
_private.relationshipManager.dispose();
_private.relationshipManager = null;
_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) })
}
}
/**
* Create Object
* @param {Object} param The parameter list
* @param {String} param.type Object type
* @param {Object} param.options? Object create parameters
* @return {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
*/
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;
}
delete param['type'];
if (param['options']) {
return new typeClass(param.options);
}
return new typeClass(param);
}
/**
* Load scene file
* @param {String|Object} url - The load URL or options.
* @param {Object} options - The load options.
* @param {String} options.url - The resource URL.
* @param {Boolean} options.dynamic - Dynamic loading scene.
* @param {Boolean} options.hidden - Hidden loading.
* @param {Boolean} options.useDefaultTheme - Use defalt theme for scene. (The last registered theme is used by default)
* @param {Boolean} options.useDefaultViewpoint - Use default viewpoint for scene file.
* @param {Boolean} options.useDefaultRenderSettings - Use default render settings for scene file.
* @param {Array} options.position - Set the scene position.
* @param {Array} options.rotation - Set the scene rotation.
* @param {Function} options.onComplete - The load complete callback function.
* @param {Function} options.onProgress - The progress callback function.
* @param {Function} options.onError - The error callback function.
* @returns {Promise} - A Promise that resolves when the loading is complete.
* @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);
}
/**
* Load plugin
* @param {String|Object} url - The load URL or options.
* @param {Object} options - The load options.
* @param {String} options.url The plugin URL.
* @param {String} options.name The plugin name.
* @param {Function} options.onComplete - The load complete callback function.
* @param {Function} options.onError - The error callback function.
* @return {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);
}
/**
* Load image texture from URL.
* @param {String} url The resource url.
* @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
* @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);
}
/**
* Uninstall plugin by name.
* @param {String} name The plugin 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>} url The resource URL(s).
* @param {Object} data The json data.
* @example
* app.load({ url: './blueprints/myBP.json' });
* app.run();
* @private
*/
loadBlueprint(options = {}) {
this.blueprint.load(options);
this.blueprint.run();
}
/**
* 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;
}
/**
* 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,
});
}
/**
* Get the picker.
* @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
/**
* Get the object manager.
* @type {THING.ObjectManager}
* @private
*/
get objectManager() {
return this[__.private].objectManager;
}
/**
* Get the event manager.
* @type {THING.EventManager}
* @private
*/
get eventManager() {
return this[__.private].eventManager;
}
/**
* Get the resource manager.
* @type {THING.ResourceManager}
* @private
*/
get resourceManager() {
return this[__.private].resourceManager;
}
/**
* Get the tween manager.
* @type {THING.TweenManager}
* @private
*/
get tweenManager() {
return this[__.private].tweenManager;
}
/**
* Get the tween manager.
* @type {THING.TweenManager}
* @public
*/
get tween() {
return this[__.private].tweenManager;
}
/**
* Get the collider manager.
* @type {THING.ColliderManager}
* @private
*/
get colliderManager() {
return this[__.private].colliderManager;
}
/**
* Get the action manager.
* @type {THING.ActionManager}
* @private
*/
get actionManager() {
return this[__.private].actionManager;
}
/**
* Get the state manager.
* @type {THING.StateManager}
* @private
*/
get stateManager() {
return this[__.private].stateManager;
}
/**
* Get the level manager.
* @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].levelManager;
}
/**
* 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.level;
}
/**
* Get the relationship manager.
* @type {THING.RelationshipManager}
* @private
*/
get relationshipManager() {
return this[__.private].relationshipManager;
}
/**
* Get the theme manager.
* @type {THING.ThemeManager}
* @private
*/
get themeManager() {
return this[__.private].themeManager;
}
// #endregion
// #region Render
/**
* Add callback before dispose.
* @param {Function} callback The function.
* @private
*/
addBeforeDisposeCallback(callback) {
this[__.private].beforeDisposeCallbacks.add(callback);
}
/**
* Remove callback before dispose.
* @param {Function} callback The function.
* @private
*/
removeBeforeDisposeCallback(callback) {
this[__.private].beforeDisposeCallbacks.remove(callback);
}
/**
* Add callback after dispose.
* @param {Function} callback The function.
* @private
*/
addAfterDisposeCallback(callback) {
this[__.private].afterDisposeCallbacks.add(callback);
}
/**
* Remove callback after dispose.
* @param {Function} callback The function.
* @private
*/
removeAfterDisposeCallback(callback) {
this[__.private].afterDisposeCallbacks.remvoe(callback);
}
/**
* Add callback before tick.
* @param {Function} callback The function.
* @private
*/
addBeforeTickCallback(callback) {
this[__.private].beforeTickCallbacks.add(callback);
}
/**
* Remove callback before tick.
* @param {Function} callback The function.
* @private
*/
removeBeforeTickCallback(callback) {
this[__.private].beforeTickCallbacks.remove(callback);
}
/**
* Add callback after tick.
* @param {Function} callback The function.
* @private
*/
addAfterTickCallback(callback) {
this[__.private].afterTickCallbacks.add(callback);
}
/**
* Remove callback after tick.
* @param {Function} callback The function.
* @private
*/
removeAfterTickCallback(callback) {
this[__.private].afterTickCallbacks.remove(callback);
}
/**
* Add callback before render.
* @param {Function} callback The function.
* @private
*/
addBeforeRenderCallback(callback) {
this[__.private].scene.addBeforeRenderCallback(callback);
}
/**
* Remove callback before render.
* @param {Function} callback The function.
* @private
*/
removeBeforeRenderCallback(callback) {
this[__.private].scene.removeBeforeRenderCallback(callback);
}
/**
* Add callback after render.
* @param {Function} callback The function.
* @private
*/
addAfterRenderCallback(callback) {
this[__.private].scene.addAfterRenderCallback(callback);
}
/**
* Remove callback after render.
* @param {Function} callback The function.
* @private
*/
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
*/
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
*/
screenCoordinateToPixel(position) {
return MathUtils.screenCoordinateToPixel(position, this.size);
}
/**
* Start to run and render.
* @private
*/
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
this.#logo.update(deltaTime);
// Update managers
_private.objectManager.update(deltaTime);
_private.tweenManager.update(deltaTime);
_private.colliderManager.update(deltaTime);
_private.stateManager.update(deltaTime);
_private.eventManager.update(deltaTime);
// After tick
_private.objectManager.lateUpdate(deltaTime);
_private.afterTickCallbacks.invoke(deltaTime);
// Render scene
_private.scene.render(deltaTime);
}
});
// Start timer with delegate
_private.delegate.run((elapsedTime, frame) => {
this.timer.update(elapsedTime);
});
}
/**
* Stop to run and render.
* @private
*/
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
*/
lockSize(width, height) {
let _private = this[__.private];
if (!_private.delegate.lockSize(width, height)) {
return false;
}
this.onResize(width, height);
return true;
}
/**
* Unlock size.
* @private
*/
unlockSize() {
let _private = this[__.private];
_private.delegate.unlockSize();
let size = [0, 0];
_private.delegate.getSize(size);
this.onResize(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);
*/
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');
}
/**
* Get the size.
* @type {Array<Number>}
* @example
* //the div3d size is 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;
}
/**
* Get the root.
* @type {THING.RootObject}
* @example
* let root = THING.App.current.root;
* // @expect(root.isRootObject == true)
*/
get root() {
let _private = this[__.private];
return _private.root;
}
/**
* get all relationships
* @type {Array<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() {
let _private = this[__.private];
return _private.relationshipManager.relationships;
}
/**
* Get/Set the camera.
* @type {THING.Camera}
* @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 {RendererCapabilities}
* @private
*/
get renderCapabilities() {
return this[__.private].renderCapabilities;
}
/**
* Get/Set pixel ratio.
* Set/Get Pixel Ratio The default is 1, and it can be set to a value between 0-1.
* The larger the value, the clearer the rendering effect (lower frame rate),
* The smaller the value, the blurrier the rendering (increased frame rate)
* On mobile devices, in order to render the frame rate, app.pixelRatio can be set to a value less than 1.
* @type {Number}
*/
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
/**
* Find children by conditions and returns the first one.
* @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);
}
/**
* Query children by condition.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.Selector}
* @example
* let obj = THING.App.current.query('.BaseObject');
* let ret = obj[0] instanceof THING.BaseObject;
* // @expect(ret == true)
* let cylinder = THING.App.current.query('.Cylinder');
* ret = cylinder.length == 0;
* // @expect(ret == true)
* @public
*/
query(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('query', condition);
}
return this.root.query(condition, options);
}
/**
* Query children by name.
* @param {String} condition The condition to select objects.
* @param {Object} options The 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);
}
/**
* Query children by id.
* @param {String} condition The condition to select objects.
* @param {Object} options The 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);
}
/**
* Query children by tag.
* @param {String} condition The condition to select objects.
* @param {Object} options The 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);
}
/**
* Query children by uuid.
* @param {String} condition The condition to select objects.
* @param {Object} options The 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);
}
/**
* Query children by type.
* @param {String} condition The condition to select objects.
* @param {Object} options The 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);
}
/**
* Query children by userData.
* @param {String} condition The condition to select objects.
* @param {Object} options The 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);
}
/**
* Query children by userData.
* @param {String} condition The condition to select objects.
* @param {Object} options The 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);
}
/**
* Query children by reg exp.
* @param {String} condition The condition to select objects.
* @param {Object} options The 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);
}
/**
* Query children by reg.
* @param {String} condition The condition to select objects.
* @param {Object} options The 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);
}
/**
* Query children by condition in async mode.
* @param {String} condition The condition to select objects.
* @param {Object} options The options.
* @returns {Promise<any>}
* @private
*/
queryAsync(condition, options) {
if (_NEED_LOGGER) {
this._outputLogger('queryAsync', condition);
}
return this.root.queryAsync(condition, options);
}
/**
* Query relationships.
* @param {Object} options The options.
* @param {String} options.type The type of relationship.
* @param {String} options.name The name of relationship.
* @returns {Array<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
/**
* Get events with type.
* @param {String} type The event type, null indicates get all events.
* @returns {Array<ObjectListenerInfo>}
* @private
*/
getEvents(type) {
return this.root.getEvents(type);
}
/**
* Pause event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {String} tag The event tag.
* @returns {ObjectListenerInfo}
* @private
*/
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.
*/
/**
* The Event interface represents an event trigger.
* @callback EventCallback
* @param {MouseEvent|KeyboardEvent} ev The case-insensitive name identifying the type of the event.
* @returns {any}
*/
/**
* Register event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {EventCallback} callback The callback function.
* @param {String} tag The event tag.
* @param {Number} priority The priority value(default is 0, higher value will be processed first)
* @param {Object} options The options.
* @param {Boolean} options.useCapture True indicates capture all same events from children.
* @example
* let app = THING.App.current;
* let markOn = 0;
* app.on('testOn', function(ev) {
* markOn = 1;
* });
* app.trigger('testOn');
* // @expect(markOn == 1);
* @public
*/
on(type, condition, callback, tag, priority, options) {
this.root.on(type, condition, callback, tag, priority, options);
}
/**
* Register event what just trigger once time.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {EventCallback} callback The callback function.
* @param {String} tag The event tag.
* @param {Number} priority The priority value(default is 0, higher value will be processed first)
* @param {Object} options The options.
* @param {Boolean} options.useCapture True indicates capture all same events from children.
* @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);
}
/**
* Unregister event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {String} tag The event 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);
}
/**
* Pause event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {String} tag The event 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);
}
/**
* Resume event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {String} tag The event 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);
}
/**
* Trigger event.
* @param {String} type The event type.
* @param {Object} ev The event info.
* @param {Object} options The options.
* @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);
* @public
*/
trigger(type, ev, options) {
return this.root.trigger(type, ev, options);
}
// #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;
}
// #endregion
// #region Resources
/**
* Get/Set the background.
* @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 )
* @public
*/
get background() {
return this.camera.background;
}
set background(value) {
this.camera.background = value;
}
/**
* Get/Set the default environment map of scene.
* @type {THING.CubeTexture}
* @example
* THING.App.current.background = new THING.CubeTexture('./skyboxes/bluesky');
* let ret = THING.App.current.background.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
/**
* When resize.
* @param {Number} width The width in pixel.
* @param {Number} height The height in pixel.
* @private
*/
onResize(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);
_private.objectManager.resize(width, height);
// Reisze view
_private.view.setSize([width, height]);
// Notify resize event
this.trigger('Resize', { width, height });
}
// #endregion
/**
* Check class type.
* @type {Boolean}
* @example
* if (app.isApp) {
* console.log(`It's app`);
* }
* @public
*/
get isApp() {
return true;
}
}
setTimeout(() => {
Utils._scriptLoader.startAnimationFrame((deltaTime) => {
// Make delta time to seconds
deltaTime /= 1000;
// Update selector
Selector.update(deltaTime);
// Update Utils
Utils.update(deltaTime);
});
}, 100);
export { App }