import { StringEncoder, Utils as BaseUtils } from '@uino/base-thing';
import { MathUtils } from '../math/MathUtils';
import { System } from '../core/System';
import { createTypeChecker } from '../adapter/AdapterInterfaces';
import { EventType, LerpType, LoopType, NodeUserDataType, InheritType, MouseButtonType } from '../const';
const _hasExpiredKey = StringEncoder.toText("<@secret hasExpired>");
const _getMethod = StringEncoder.toText("<@secret GET>");
const _postMethod = StringEncoder.toText("<@secret POST>");
const _showLogoKey = StringEncoder.toText("<@secret showLogo>");
const _watermarkKey = StringEncoder.toText("<@secret watermark>");
const _watermarkEvent = StringEncoder.toText("<@secret __!water#mark*event@__>");
const _hideWatermarkEvent = StringEncoder.toText("<@secret __!hide!water#mark*event@__>");
const _authDataEvent = StringEncoder.toText("<@secret __!!a$u^t@h*event@__>");
const _expriedStr1 = StringEncoder.toText('<@secret Authorization>');
const _expriedStr2 = StringEncoder.toText('<@secret has>');
const _expriedStr3 = StringEncoder.toText('<@secret expried>');
const _officialStaticPath = StringEncoder.toText('<@secret https://www.thingjs.com/static>');
function _getAuthData() {
return Object.assign({}, _authData);
}
function _buildWASMInfo(platformInfo, wasmOptions) {
return {
VERSION: platformInfo.version,
SDKVERSION: platformInfo.sdkVersion,
COMPILETIME: platformInfo.compileTime,
features: wasmOptions.features,
exports: wasmOptions.exports
};
}
let _app = null;
let _defaultParams = {};
let _tempExpiredKey = '';
let _scriptLoader;
let _fileManager;
let _devDebugger;
let _renderDebugger;
let _logger;
if (!_NEED_LOGGER) {
_logger = {
dispose: function () { },
setOptions: function (options) { },
getOptions: function () { },
api: function (name, options) { },
error: function (msg, options) { },
sum: function (key, value) { },
setApiCollector: function (apiCollector) { },
getApiCollector: function () { }
};
}
let _typeCheker;
if (_DEBUG) {
_typeCheker = createTypeChecker();
}
function _dummyFunc() {
}
let _system = new System();
let _authData = null;
/**
* @class Utils
* Useful functions.
* @memberof THING
* @public
*/
class Utils extends BaseUtils {
static xAxis = [1, 0, 0];
static yAxis = [0, 1, 0];
static zAxis = [0, 0, 1];
static dependencyLoaders = [];
static get _system() {
return _system;
}
static get _scriptLoader() {
_scriptLoader = _scriptLoader || Utils.createObject('ScriptLoader');
return _scriptLoader;
}
static get _fileManager() {
_fileManager = _fileManager || Utils.createObject('FileManager');
return _fileManager;
}
static get _devDebugger() {
_devDebugger = _devDebugger || Utils.createObject('DevDebugger');
return _devDebugger;
}
static get _renderDebugger() {
_renderDebugger = _renderDebugger || Utils.createObject('RenderDebugger');
return _renderDebugger;
}
static get logger() {
if (_NEED_LOGGER) {
_logger = _logger || Utils.createObject('Logger');
}
return _logger;
}
static set logger(value) {
if (_NEED_LOGGER) {
if (_logger) {
_logger.dispose();
}
_logger = value;
}
}
static update(deltaTime) {
super.update(deltaTime);
_system.update(deltaTime);
if (_fileManager) {
_fileManager.update(deltaTime);
}
if (_tempExpiredKey) {
Utils.error(_expriedStr1 + ' ' + _expriedStr2 + ' ' + _expriedStr3);
_tempExpiredKey = '';
}
}
static getCurrentApp() {
return _app;
}
static setCurrentApp(app) {
if (_app && !_app.isDisposed && !_app.isDisposing) {
_app.trigger(EventType.AppLeave);
}
_app = app;
// maybe this no app; then the current app is null
if (!app) {
return;
}
// set current app use setTimeout because that the app maybe has not been initialed
Utils.setTimeout(() => {
app.trigger(EventType.AppEnter);
});
if (_authData) {
// Hide logo
if (_authData[_showLogoKey] === 0 || _authData[_showLogoKey] === false) {
// We delay to next frame to make sure app load completed
Utils.setTimeout(() => {
app.trigger(_hideWatermarkEvent);
});
}
else {
// Change watermark if login auth data has it
let watermark = _authData[_watermarkKey];
if (watermark) {
// We delay to next frame to make sure app load completed
Utils.setTimeout(() => {
app.trigger(_watermarkEvent, {
opacity: watermark['opacity'],
text: watermark['text'],
data: watermark['data']
});
});
}
}
if (_authData[_hasExpiredKey]) {
// Write result into temporary key and prepare to print expired log
_tempExpiredKey = MathUtils.generateUUID();
}
}
}
static onCompleteApp(app) {
// Initialize auth data when if we need
if (_NEED_AUTH) {
if (!_authData) {
_authData = {};
// Wait for auth to update expired date key
_authData[_hasExpiredKey] = true;
}
}
// Let auth pass when it's in development env
if (_DEV) {
_authData = _authData || {};
_authData[_hasExpiredKey] = false; // Let it always passed
}
else if (!_PRO_) {
_authData = _authData || {};
_authData[_hasExpiredKey] = true; // The community version can not use any pro features
}
if (_authData) {
app.trigger(_authDataEvent, _authData);
}
}
static lerpTo(from, to, time = 1000, delayTime = 0) {
return _app.tweenManager.lerpTo(from, to, time, delayTime);
}
static getMD5CodeFromFile(url, load, progress) {
this.loadBinaryFile(url,
(ev) => {
let blob = this.arrayBufferToBlob(ev.data);
this.getMD5CodeFromBuffer(blob).then(function (hash) {
if (load) {
load(hash);
}
});
},
progress,
);
}
static normalizeURL(url) {
return Utils._scriptLoader.normalizeURL(url);
}
static createObjectURL(data) {
return Utils._scriptLoader.createObjectURL(data);
}
/**
* Load code.
* @param {String} code The code string.
* @param {Object} options The options.
* @param {Boolean} options.es6Mode True indicates load code in es6 mode.
* @param {String} options.sourceURL The source URL in sync mode.
* @param {Function} options.onLoad The load finished callback function.
* @param {Function} options.onError The error occurred callback function.
* @example
* THING.Utils.loadCode('console.log("Code load finished")');
* @public
*/
static loadCode(code, options = {}) {
return new Promise((resolve, reject) => {
let onLoad = () => {
options.onLoad && options.onLoad();
resolve();
}
let onError = (ev) => {
options.onError && options.onError(ev);
reject(ev);
}
Utils._scriptLoader.loadCode(code, {
es6Mode: options['es6Mode'],
sourceURL: options['sourceURL'],
onLoad: onLoad,
onError: onError
});
});
}
/**
* Load code in async mode.
* @param {String} code The code string.
* @param {Object} options The options.
* @param {Boolean} options.es6Mode True indicates load code in es6 mode.
* @param {String} options.sourceURL The source URL in sync mode.
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* await THING.Utils.loadCodeAsync('console.log("Code load finished")');
* @public
*/
static loadCodeAsync(code, options = {}) {
return Utils.loadCode(code, options);
}
/**
* Load file(s).
* @param {String|Array<String>} resource The file(s) resource url.
* @param {Object|Array<String>} options The options.
* @param {Function} options.onLoad The load finished callback function.
* @param {Function} options.onError The error occurred callback function.
* @param {Boolean} [options.cache=true] True indicates try to use cache.
* @param {Boolean} [options.inOrder=true] True indicates keep order to load files one by one.
* @example
* // Load 'my-lib.js' file by load and error callback functions
* THING.Utils.loadFile('my-lib.js', {
* onLoad: function() {
* console.log('Load finished');
* },
* onError: function() {
* console.error('Load failed');
* }
* });
* @public
*/
static loadFile(resource, options) {
return Utils._scriptLoader.loadAsync(resource, options);
}
/**
* Load file(s) in async mode.
* @param {String|Array<String>} resource The file(s) resource url.
* @param {Object|Array<String>} options The options.
* @param {Boolean} [options.cache=true] True indicates try to use cache.
* @param {Boolean} [options.inOrder=true] True indicates keep order to load files one by one.
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* // Load 'my-lib.js' file and wait to load completed
* await THING.Utils.loadFileAsync('my-lib.js');
* @public
*/
static loadFileAsync(resource, options) {
return Utils.loadFile(resource, options);
}
/**
* Check whether it's compressed texture.
* @param {String} url The compressed texture url.
* @returns {Boolean}
* @example
* let compress = THING.Utils.isCompressedTexture('image.dds');
* let uncompress = THING.Utils.isCompressedTexture('image.png');
* // @expect(compress == true && uncompress == false);
* @public
*/
static isCompressedTexture(url) {
let extension = url._getExtension();
return (extension == 'dds' || extension == 'pvr');
}
/**
* Load image file.
* @param {String} url The file URL.
* @param {Function} onLoad When load finished callback function.
* @param {Function} onProgress When loading in progression callback function.
* @param {Function} onError When laod error occurred callback function.
* @example
* // Load image resource by load callback function.
* THING.Utils.loadImageFile('./assets/images/blue.png',
* function(image) {
* console.log(image);
* }
* );
* @public
*/
static loadImageFile(url, onLoad, onProgress, onError, options) {
return new Promise((resolve, reject) => {
let load = (image) => {
onLoad && onLoad(image);
resolve(image);
};
let error = (ev) => {
onError && onError(ev);
reject(ev);
};
if (Utils.isCompressedTexture(url)) {
Utils.getCurrentApp().resourceManager.loadCompressedTextureData(url, load, onProgress, error);
}
else {
Utils._fileManager.loadImageFile(url, load, onProgress, error, options);
}
});
}
/**
* Load image file in async mode.
* @param {String} url The file URL.
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* // Load image resource and wait to load finished.
* await THING.Utils.loadImageFileAsync('image.png');
* @public
*/
static loadImageFileAsync(url, options) {
return Utils.loadImageFile(url, null, null, null, options);
}
static loadImageFileFromBase64(src) {
return Utils._fileManager.loadImageFileFromBase64(src);
}
static loadBitmapImageFromBase64Async(src) {
return Utils._fileManager.loadBitmapImageFromBase64Async(src);
}
static loadBitmapImageFromUrlAsync(url, options) {
return Utils._fileManager.loadBitmapImageFromUrlAsync(url, options);
}
static loadImageFileFromData(type, data, load, progress, error, options) {
Utils._fileManager.loadImageFileFromData(type, data, load, progress, error, options);
}
static loadVideoFile(url, load, progress, error, options) {
return Utils._fileManager.loadVideoFile(url, load, progress, error, options);
}
/**
* Load binary file.
* @param {Object} resource The resource object.
* @example
* let resource = document.createElement('video');;
* let check = THING.Utils.isVideoResource(resource);
* // @expect(check == true);
* @public
*/
static isVideoResource(resource) {
return Utils._fileManager.isVideoResource(resource);
}
/**
* Load binary file.
* @param {String} url The file URL.
* @param {Function} onLoad When load finished callback function.
* @param {Function} onProgress When loading in progression callback function.
* @param {Function} onError When laod error occurred callback function.
* @param {LoadFileOptions} options The options.
* @example
* // Load 'my-file.bin' file in binary format.
* THING.Utils.loadBinaryFile('my-file.bin',
* function(data) {
* console.log('Load finished', data);
* },
* function() {
* console.error('Load failed');
* }
* );
* @public
*/
static loadBinaryFile(url, onLoad, onProgress, onError, options) {
return new Promise((resolve, reject) => {
let load = (ev) => {
let data = ev.data;
onLoad && onLoad(ev);
resolve(data);
};
let error = (ev) => {
onError && onError(ev);
reject(ev);
};
Utils._fileManager.loadBinaryFile(url, load, onProgress, error, options);
});
}
/**
* Load binary file in async mode.
* @param {String} url The file URL.
* @param {LoadFileOptions} options The options.
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* // Load 'my-file.bin' file and wait to load finished.
* await THING.Utils.loadBinaryFileAsync('my-file.bin');
* @public
*/
static loadBinaryFileAsync(url, options) {
return Utils.loadBinaryFile(url, null, null, null, options);
}
/**
* Save data as file.
* @param {String} fileName The file name.
* @param {String|Blob|Image} data The file data.
* @returns {Boolean}
* @example
* let data = JSON.stringify('{ name: "Nice to meet you"}');
* THING.Utils.saveAsFile('test.json', data);
* @public
*/
static saveAsFile(fileName, data) {
return Utils._fileManager.saveAsFile(fileName, data);
}
/**
* Save data as image.
* @param {Number} width The image width in pixel.
* @param {Number} height The image height in pixel.
* @param {Uint8Array} pixelBuffer The image pixel buffer.
* @returns {Object}
* @example
* // Save 32x32 image with random pixel color
* const width = 32, height = 32;
* let pixelBuffer = [];
* for(let y = 0; y < height; y++) {
* for(let x = 0; x < width * 4; x++) {
* pixelBuffer[y * width * 4 + x] = THING.Math.randomInt(0, 255);
* }
* }
* THING.Utils.saveAsImage(width, height, new Uint8Array(pixelBuffer));
* @public
*/
static saveAsImage(width, height, pixelBuffer) {
return Utils._fileManager.saveAsImage(width, height, pixelBuffer);
}
/**
* Load text file.
* @param {String} url The file URL.
* @param {Function} onLoad When load finished callback function.
* @param {Function} onProgress When loading in progression callback function.
* @param {Function} onError When laod error occurred callback function.
* @param {LoadFileOptions} options The options.
* @example
* // Load 'my-file.txt' file in text format.
* THING.Utils.loadTextFile('my-file.txt',
* function(data) {
* console.log('Load finished', data);
* },
* function() {
* console.error('Load failed');
* }
* );
* @public
*/
static loadTextFile(url, onLoad, onProgress, onError, options) {
return new Promise((resolve, reject) => {
let load = (ev) => {
let data = ev.data;
onLoad && onLoad(ev);
resolve(data);
};
let error = (ev) => {
onError && onError(ev);
reject(ev);
};
Utils._fileManager.loadTextFile(url, load, onProgress, error, options);
});
}
/**
* Load text file in async mode.
* @param {String} url The file URL.
* @param {LoadFileOptions} options The options.
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* // Load 'my-file.txt' file and wait to load finished.
* await THING.Utils.loadTextFileAsync('my-file.txt');
* @public
*/
static loadTextFileAsync(url, options) {
return this.loadTextFile(url, null, null, null, options);
}
/**
* Load JSON file.
* @param {String} url The file URL.
* @param {Function} onLoad When load finished callback function.
* @param {Function} onProgress When loading in progression callback function.
* @param {Function} onError When laod error occurred callback function.
* @param {LoadFileOptions} options The options.
* @example
* // Load 'my-file.json' file in json format.
* THING.Utils.loadJSONFile('my-file.json',
* function(data) {
* console.log('Load finished', data);
* },
* function() {
* console.error('Load failed');
* }
* );
* @public
*/
static loadJSONFile(url, onLoad, onProgress, onError, options) {
return new Promise((resolve, reject) => {
let load = (ev) => {
let data = ev.data;
onLoad && onLoad(ev);
resolve(data);
};
let error = (ev) => {
onError && onError(ev);
reject(ev);
};
Utils._fileManager.loadJSONFile(url, load, onProgress, error, options);
});
}
/**
* Load JSON file(s) in async mode.
* @param {String|Array<String>} url The file URL(s).
* @param {LoadFileOptions} options The options.
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* // Load 'my-file.json' file and wait to load finished.
* await THING.Utils.loadJSONFileAsync('my-file.json');
* @public
*/
static loadJSONFileAsync(url, options) {
if (Utils.isArray(url)) {
let promises = url.map(_url => {
return Utils.loadJSONFileAsync(_url, options).then((data) => {
return { url: _url, data };
});
});
return Promise.all(promises).then((items) => {
let result = {};
items.forEach(item => {
result[item.url] = item.data;
});
return result;
});
}
else {
return this.loadJSONFile(url, null, null, null, options);
}
}
static encodeImageFromURL(type, url, load, error, options) {
Utils._fileManager.encodeImageFromURL(type, url, load, error, options);
}
static encodeImageFromData(type, data, load, error, options) {
Utils._fileManager.encodeImageFromData(type, data, load, error, options);
}
static encodeImageFromRGBA(type, width, height, pixelBuffer, load, error, options) {
Utils._fileManager.encodeImageFromRGBA(type, width, height, pixelBuffer, load, error, options);
}
/**
* When run action.
* @param {number} value The button type.
* @example
* let type = THING.Utils.parseMouseButtonType(1);
* // @expect(type == 'Middle');
* @public
*/
static parseMouseButtonType(value) {
switch (value) {
case 0: return MouseButtonType.Left;
case 1: return MouseButtonType.Middle;
case 2: return MouseButtonType.Right;
default:
return MouseButtonType.None;
}
}
// Parse event with options.
static parseEventWithOptions(info, args) {
// .(tag)
if (Utils.isString(args[0])) {
info.tag = args[0];
// .(tag, {options})
if (Utils.isObject(args[1])) {
info.useCapture = Utils.parseValue(args[1].useCapture, info.useCapture);
info.enumerable = Utils.parseValue(args[1].enumerable, info.enumerable);
info.pausable = Utils.parseValue(args[1].pausable, info.pausable);
info.includeSelf = Utils.parseValue(args[1].includeSelf, info.includeSelf);
}
// .(tag, priority)
else if (Utils.isNumber(args[1])) {
info.priority = args[1];
// .(tag, priority, {options})
if (Utils.isObject(args[2])) {
info.useCapture = Utils.parseValue(args[2].useCapture, info.useCapture);
info.enumerable = Utils.parseValue(args[2].enumerable, info.enumerable);
info.pausable = Utils.parseValue(args[2].pausable, info.pausable);
info.includeSelf = Utils.parseValue(args[2].includeSelf, info.includeSelf);
}
}
}
// .(priority)
else if (Utils.isNumber(args[0])) {
info.priority = args[0];
// .(priority, {options})
if (Utils.isObject(args[1])) {
info.useCapture = Utils.parseValue(args[1].useCapture, info.useCapture);
info.enumerable = Utils.parseValue(args[1].enumerable, info.enumerable);
info.pausable = Utils.parseValue(args[1].pausable, info.pausable);
info.includeSelf = Utils.parseValue(args[1].includeSelf, info.includeSelf);
}
}
// .({options})
else if (Utils.isObject(args[0])) {
info.useCapture = Utils.parseValue(args[0].useCapture, info.useCapture);
info.enumerable = Utils.parseValue(args[0].enumerable, info.enumerable);
info.pausable = Utils.parseValue(args[0].pausable, info.pausable);
info.includeSelf = Utils.parseValue(args[0].includeSelf, info.includeSelf);
// .({options}, tag, priority)
if (Utils.isString(args[1])) {
info.tag = Utils.parseValue(args[1], '');
info.priority = Utils.parseValue(args[2], 0);
}
// .({options}, priority)
else {
info.priority = Utils.parseValue(args[1], 0);
}
}
// .(tag, priority, options)
else if (args.length == 3) {
info.tag = Utils.parseValue(args[0], '');
info.priority = Utils.parseValue(args[1], 0);
let options = args[2];
if (options) {
info.useCapture = Utils.parseValue(options.useCapture, info.useCapture);
info.enumerable = Utils.parseValue(options.enumerable, info.enumerable);
info.pausable = Utils.parseValue(options.pausable, info.pausable);
info.includeSelf = Utils.parseValue(options.includeSelf, info.includeSelf);
}
}
}
// Parse event.
static parseEvent() {
let info = {
type: arguments[0].toLowerCase(),
condition: null,
callback: _dummyFunc,
tag: null,
priority: 0,
once: false,
useCapture: false,
includeSelf: false,
enumerable: true,
pausable: true
};
let onInitEventArgs = arguments[6];
if (onInitEventArgs) {
onInitEventArgs(info);
}
// .(function())
if (Utils.isFunction(arguments[1])) {
info.callback = arguments[1];
Utils.parseEventWithOptions(info, Array.prototype.slice.call(arguments, 2));
}
// .('condition', function())
else if (Utils.isString(arguments[1])) {
if (!Utils.isFunction(arguments[2])) {
Utils.error(`Parse event failed, due to callback is not function with condition`);
}
info.condition = arguments[1];
info.callback = arguments[2];
Utils.parseEventWithOptions(info, Array.prototype.slice.call(arguments, 3));
}
// .(null/undefined, function())
else if (Utils.isNull(arguments[1])) {
if (!Utils.isFunction(arguments[2])) {
Utils.error(`Parse event failed, due to callback is not function with condition`);
}
info.callback = arguments[2];
Utils.parseEventWithOptions(info, Array.prototype.slice.call(arguments, 3));
}
// We must provide callback function
if (!info.callback) {
Utils.error(`Parse event failed, deu to callback function is not existing`);
}
return info;
}
/**
* parse fly param.
* @param {Object} param The fly param.
* @returns {Object}
* @example
* let ret1 = THING.Utils.parseFlyParam(100);
* let obj = new THING.BaseObject();
* let ret2 = THING.Utils.parseFlyParam(obj);
* // @expect(ret1 == 100 && ret2.target == obj);
* @public
*/
static parseFlyParam(param) {
if (param.isBaseObject || Utils.isArray(param)) {
return { target: param };
}
return param;
}
/**
* parse loop type.
* @param {String} name The loop type.
* @returns {String}
* @example
* let ret1 = THING.Utils.parseLoopType('repeat');
* let ret2 = THING.Utils.parseLoopType('pingpong');
* let ret3 = THING.Utils.parseLoopType('temp');
* // @expect(ret1 == 'Repeat' && ret2 == 'PingPong' && ret3 == '');
* @public
*/
static parseLoopType(name) {
if (name) {
switch (name.toLowerCase()) {
case 'repeat': return LoopType.Repeat;
case 'pingpong': return LoopType.PingPong;
default:
break;
}
}
return '';
}
/**
* parse lerp type
* @param {String | Object} name the lerp type or function.
* @returns {Function}
* @example
* let func1 = THING.Utils.parseLerpType('quadratic.in');
* let ret1 = func1(10);
* let customFunc = (amount) => {return amount + amount;};
* let func2 = THING.Utils.parseLerpType(customFunc);
* let ret2 = func2(10);
* let func3 = THING.Utils.parseLerpType('temp');
* let ret3 = func3(10);
* // @expect(ret1 == 100 && ret2 == 20 && ret3 == 10)
* @public
*/
static parseLerpType(name) {
if (Utils.isString(name)) {
let value = name.toLowerCase();
switch (value) {
case 'quadratic.in': return LerpType.Quadratic.In;
case 'quadratic.out': return LerpType.Quadratic.Out;
case 'quadratic.inout': return LerpType.Quadratic.InOut;
case 'cubic.in': return LerpType.Cubic.In;
case 'cubic.out': return LerpType.Cubic.Out;
case 'cubic.inout': return LerpType.Cubic.InOut;
case 'quartic.in': return LerpType.Quartic.In;
case 'quartic.out': return LerpType.Quartic.Out;
case 'quartic.inout': return LerpType.Quartic.InOut;
case 'quintic.in': return LerpType.Quintic.In;
case 'quintic.out': return LerpType.Quintic.Out;
case 'quintic.inout': return LerpType.Quintic.InOut;
case 'sinusoidal.in': return LerpType.Sinusoidal.In;
case 'sinusoidal.out': return LerpType.Sinusoidal.Out;
case 'sinusoidal.inout': return LerpType.Sinusoidal.InOut;
case 'exponential.in': return LerpType.Exponential.In;
case 'exponential.out': return LerpType.Exponential.Out;
case 'exponential.inout': return LerpType.Exponential.InOut;
case 'circular.in': return LerpType.Circular.In;
case 'circular.out': return LerpType.Circular.Out;
case 'circular.inout': return LerpType.Circular.InOut;
case 'elastic.in': return LerpType.Elastic.In;
case 'elastic.out': return LerpType.Elastic.Out;
case 'elastic.inout': return LerpType.Elastic.InOut;
case 'back.in': return LerpType.Back.In;
case 'back.out': return LerpType.Back.Out;
case 'back.inout': return LerpType.Back.InOut;
case 'bounce.in': return LerpType.Bounce.In;
case 'bounce.out': return LerpType.Bounce.Out;
case 'bounce.inout': return LerpType.Bounce.InOut;
default:
break;
}
}
else if (Utils.isFunction(name)) {
return name;
}
return LerpType.Linear.None;
}
/**
* parse the cubetexture url
* @param {String} path the cubetexture path.
* @returns {String}
* @example
* let ret1 = THING.Utils.parseCubeTextureUrlsByPath();
* let ret2 = THING.Utils.parseCubeTextureUrlsByPath('./tempPath');
* let ret3 = THING.Utils.parseCubeTextureUrlsByPath('./tempPath/');
* // @expect(ret1 == null && ret2[0] == './tempPath/posx.jpg' && ret3[0] == './tempPath/posx.jpg')
* @public
*/
static parseCubeTextureUrlsByPath(path) {
if (!path) {
return null;
}
if (!path.startsWith('.') && !path.startsWith('/')) {
path = _officialStaticPath._appendURL('skyboxes')._appendURL(path);
}
const urls = [
path._appendURL('posx.jpg'),
path._appendURL('posy.jpg'),
path._appendURL('posz.jpg'),
path._appendURL('negx.jpg'),
path._appendURL('negy.jpg'),
path._appendURL('negz.jpg')
];
return urls;
}
static markAsDebugNode(node) {
node.setAttribute('Pickable', false);
node.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Stop;
}
static addFactory(factory, priority = 0) {
if (_DEBUG) {
_typeCheker.checkObject(factory);
}
super.addFactory(factory, priority);
}
/**
* create object by type
* @param {string} type the object type.
* @param {Object} options the create options.
* @returns {Object}
* @example
* let line = THING.Utils.createObject('PixelLine');
* let ret = line instanceof THING.PixelLine;
* // @expect(ret == true)
* @public
*/
static createObject(type, options) {
if (_DEBUG) {
let object = super.createObject(type, options);
if (object) {
_typeCheker.checkObject(type, object);
}
return object;
}
else {
return super.createObject(type, options);
}
}
static createThreadWorkerFromClass(classType) {
let funcString = classType.toString();
return Utils.createThreadWorkerFromCode(funcString);
}
static createThreadWorkerFromCode(code) {
let funcString = Utils.isFunction(code) ? code.toString() : code;
let funcScript = funcString.substring(funcString.indexOf('{') + 1, funcString.lastIndexOf('}'));
let url = Utils.createObjectURL(funcScript);
return Utils.createObject('ThreadWorker', { url });
}
static waitForAttach(timeout) {
return Utils._devDebugger.waitForAttach(timeout);
}
static beginRenderAnalyze(object) {
return Utils._renderDebugger.beginAnalyze(object || Utils.getCurrentApp().root.node);
}
static endRenderAnalyze() {
return Utils._renderDebugger.endAnalyze();
}
static getRenderAnalyzeResult(type, options = {}) {
let app = Utils.getCurrentApp();
let objectManager = app.objectManager;
return Utils._renderDebugger.getAnalyzeResult(type, {
onGetObjectByNode: function (node) {
return objectManager.getBaseObjectFromNode(node);
},
onCloneObjet: function (node) {
return Utils.cloneObject(node);
},
onCheckIsArray: function (node) {
return Utils.isArray(node);
},
...options
});
}
/**
* check is valid texture
* @param {Object} value the texture object.
* @example
* let ret1 = THING.Utils.isValidTexture(new THING.ImageTexture());
* let ret2 = THING.Utils.isValidTexture(new THING.CubeTexture());
* let ret3 = THING.Utils.isValidTexture(new THING.RenderTexture());
* let ret4 = THING.Utils.isValidTexture(new THING.VideoTexture());
* let ret5 = THING.Utils.isValidTexture(new THING.EmptyTexture());
* let ret6 = THING.Utils.isValidTexture(new THING.BaseObject());
* // @expect(ret1 == true && ret2 == true && ret3 == true && ret4 == true && ret5 == true && ret6 == false)
* @public
*/
static isValidTexture(value) {
if (value) {
if (value.isImageTexture) {
return true;
}
if (value.isCubeTexture) {
return true;
}
if (value.isRenderTexture) {
return true;
}
if (value.isVideoTexture) {
return true;
}
if (value.isEmptyTexture) {
return true
}
}
return false;
}
static syncOptions(target, keys, param, external = _defaultParams) {
keys.forEach(key => {
let value = Utils.parseValue(param[key], external[key]);
if (Utils.isNull(value)) {
return;
}
target[key] = value;
});
}
/**
* Register class type.
* @param {*} classType The class type.
* @param {String} type The type name, it would define property with 'is${type}' as getter.
* @private
* @example
* THING.Utils.registerClassType(MyObject, 'MyObject');
* @public
*/
static registerClassType(classType, type) {
Utils.warn(`Please call 'THING.Utils.registerClass()' instead`);
if (!classType) {
return;
}
classType.className = type;
}
/**
* The function to call when get the module name.
* @callback OnGetWasmModuleCallback
* @param {String} url The url.
* @returns {Object}
*/
/**
* @typedef {Object} LoadWasmFileOptions
* @property {OnGetWasmModuleCallback} onGetWasmModule
*/
/**
* Load WASM file.
* @param {String} url The url.
* @param {LoadWasmFileOptions} options The options.
* @returns {Promise<any>}
*/
static loadWasmFile(url, options = {}) {
return new Promise(async (resolve, reject) => {
// Initialize dependency loaders
if (!Utils.dependencyLoaders.length) {
let factories = Utils.getFactories();
factories.forEach(factory => {
let loader = factory.createObject("DependencyLoader");
if (!loader) {
return;
}
let fileNames = loader.getWasmFileNames();
if (!fileNames.length) {
return;
}
Utils.dependencyLoaders.push({
loader,
fileNames
});
});
}
// Load wasm module
let module = await new Promise((resolve, reject) => {
Utils._scriptLoader.loadAsync(url, { onError: () => { reject(null); } }).then(() => {
let wasmModule = null;
if (options['onGetWasmModule']) {
wasmModule = options['onGetWasmModule']();
}
if (wasmModule) {
wasmModule().then(function (module) {
resolve(module);
});
}
else if (typeof Module !== 'undefined') {
Module().then(function (module) {
resolve(module);
});
}
else {
Utils.error(`The Module is not existing when load '${url}' wasm file`);
reject(null);
}
});
});
if (!module) {
reject(null);
}
// Load wasm and send the module result to loader
let fileName = url._getFileName();
for (let i = 0; i < Utils.dependencyLoaders.length; i++) {
let { loader, fileNames } = Utils.dependencyLoaders[i];
if (fileNames.includes(fileName)) {
loader.loadWasmFile(url, module);
break;
}
}
resolve(module);
})
}
/**
* @typedef {Object} LoginOptions
* @property {String} url The auth resource url.
* @property {String} wasmRootPath The wasm root path.
* @property {String} method The request method.
* @property {String} requestHeaders The request headers.
* @property {String} postFields The post field when using 'POST' method.
* @property {Number} maxWorkerNumber The max worker number, -1 indicates use as default.
* @property {Function} onGetResourceType The resource type callback function, we can set it to tell whether it's encrypted resource.
*/
/**
* Login.
* @param {String|LoginOptions} options The url or options.
* @return {Promise<any>}
* @example
* let promise1 = THING.Utils.login('http://127.0.0.1:3000/auth.json');
* let promise2 = THING.Utils.login({
* method: 'GET',
* url: 'http://127.0.0.1:3000/auth.json',
* wasmRootPath: 'js/wasm',
* postFields: 'post data'
* });
* @public
*/
static login(options = {}) {
if (Utils.isString(options)) {
options = { url: options };
}
let url = options['url'];
let wasmRootPath = Utils.parseValue(options['wasmRootPath'], Utils.currentScriptPath._appendPath('wasm'));
let method = Utils.parseValue(options['method'], _postMethod);
let requestHeaders = Utils.parseValue(options['requestHeaders'], '');
let postFields = Utils.parseValue(options['postFields'], '');
let maxWorkerNumber = Utils.parseValue(options['maxWorkerNumber'], -1);
let disableBackgroundThreads = Utils.parseValue(options['disableBackgroundThreads'], false);
return new Promise(async (resolve, reject) => {
let onGetResourceType = (url) => {
if (options['onGetResourceType']) {
let result = options['onGetResourceType'](url);
if (result && result.toLowerCase() == 'normal') {
return module.ResourceType.Normal;
}
}
};
let { module, wasmOptions } = await _system.loadWasmModule(wasmRootPath, { maxNumber: maxWorkerNumber, disableBackgroundThreads, onGetResourceType }, { onGetResourceType });
let helper = module.Helper;
// Add wasm module version and compile time
let platformInfo = helper.getPlatformInfo();
if (platformInfo) {
let macros = Utils.getMacros();
macros['WASM'] = _buildWASMInfo(platformInfo, wasmOptions);
// Add to THING namespace also
if (typeof THING !== 'undefined') {
THING.WASM = THING.WASM || {};
Utils.mergeObject(THING.WASM, macros['WASM'], true);
}
}
if (url) {
// We fill some random data when it's POST method and user did not provide it
if (method.toLowerCase() == _postMethod.toLowerCase() && !postFields) {
postFields = MathUtils.generateUUID();
}
let authData = await _system.login(method, url, requestHeaders, postFields);
if (_NEED_AUTH) {
if (authData) {
_authData = authData;
}
}
else {
_authData = _authData || {};
_authData[_hasExpiredKey] = false;
}
}
resolve();
});
}
static error() {
BaseUtils.error.apply(console, arguments);
if (_NEED_LOGGER) {
let msg = "";
try {
for (let index = 0; index < arguments.length; index++) {
let element = arguments[index].toString();
msg += element;
if (index != arguments.length - 1) {
msg += "\n";
}
}
}
catch (e) {
console.error(e);
msg = e;
}
Utils.logger.error(msg);
}
}
/**
* unique the object array. process the repeat and inherit object
* @param {Array<Object>} objects object array.
* @example
* let object1 = new THING.Object3D();
* let object2 = new THING.Object3D();
* object1.add(object2);
* let objArr = [object1, object2, object2];
* let ret = THING.Utils.uniqueObjects(objArr);
* // @expect(ret.length == 1 && ret[0] == object1)
*/
static uniqueObjects(objects) {
let cloneArr = objects.slice();
for (let i = cloneArr.length - 1; i >= 0; i--) {
const obj = cloneArr[i];
for (let j = i - 1; j >= 0; j--) {
const compObj = cloneArr[j];
if (obj == compObj) {
cloneArr._removeAt(i);
break;
}
if (obj.isChildOf(compObj)) {
cloneArr._removeAt(i);
break;
}
if (compObj.isChildOf(obj)) {
cloneArr._removeAt(j);
i -= 1;
}
}
}
return cloneArr;
}
static importScript(url) {
return Utils._scriptLoader.importScript(url);
}
static extractClassesFromScript(url) {
let data = {
componentClasses: new Map(),
bpnodeClasses: new Map(),
objectClasses: new Map(),
unknownClasses: new Map()
}
return new Promise((resolve, reject) => {
Utils._scriptLoader.importScript(url).then((module) => {
for (const key in module) {
const cls = module[key];
if (cls.prototype.isComponent) {
data.componentClasses.set(key, cls);
}
else if (cls.prototype.isBaseNode) {
data.bpnodeClasses.set(key, cls);
}
else if (cls.prototype.isBaseObject) {
data.objectClasses.set(key, cls);
}
else {
data.unknownClasses.set(key, cls);
}
}
resolve(data)
});
});
}
/**
* check is gltf or glb file
* @param {String} url the url of the scene.
* @example
* let ret1 = THING.Utils.isModelSceneUrl('a.gltf');
* let ret2 = THING.Utils.isModelSceneUrl('b.json');
* // @expect(ret1 == true && ret2 == false)
* @public
*/
static isModelSceneUrl(url) {
const ext = url._getExtension();
return Utils.isModelSceneExtension(ext);
}
/**
* check is gltf or glb extension
* @param {String} ext the file extension.
* @example
* let ret1 = THING.Utils.isModelSceneExtension('gltf');
* let ret2 = THING.Utils.isModelSceneExtension('json');
* // @expect(ret1 == true && ret2 == false)
* @public
*/
static isModelSceneExtension(ext) {
return ext == 'gltf' || ext == 'glb';
}
static getFileSize(url, callback) {
Utils._fileManager.getFileSize(url, callback);
}
static get currentScriptPath() {
return Utils._scriptLoader.getMainScriptUrl()._getPath();
}
}
export { Utils, _getAuthData };