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 _pluginsKey = StringEncoder.toText("<@secret plugins>");
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;
let _settings = {};
/**
* @class Utils
* 实用函数。
* @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);
}
/**
* 加载代码。
* @param {string} code 代码字符串。
* @param {object} options 选项。
* @param {boolean} options.es6Mode 是否以es6模式加载代码。
* @param {string} options.sourceURL 同步模式下的源URL。
* @param {Function} options.onLoad 加载完成的回调函数。
* @param {Function} options.onError 发生错误的回调函数。
* @example
* THING.Utils.loadCode('console.log("代码加载完成")');
* @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
});
});
}
/**
* 异步加载代码。
* @param {string} code 代码字符串。
* @param {object} options 选项。
* @param {boolean} options.es6Mode 是否以es6模式加载代码。
* @param {string} options.sourceURL 同步模式下的源URL。
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* await THING.Utils.loadCodeAsync('console.log("代码加载完成")');
* @public
*/
static loadCodeAsync(code, options = {}) {
return Utils.loadCode(code, options);
}
/**
* 加载文件。
* @param {string|Array<string>} resource 文件的资源URL。
* @param {object|Array<string>} options 选项。
* @param {Function} options.onLoad 加载完成的回调函数。
* @param {Function} options.onError 发生错误的回调函数。
* @param {boolean} [options.cache=true] 是否使用缓存。
* @param {boolean} [options.inOrder=true] 是否按顺序加载文件。
* @returns {Promise<any>}
* @example
* // 通过加载和错误回调函数加载'my-lib.js'文件
* THING.Utils.loadFile('my-lib.js', {
* onLoad: function() {
* console.log('加载完成');
* },
* onError: function() {
* console.error('加载失败');
* }
* });
* @public
*/
static loadFile(resource, options) {
return Utils._scriptLoader.loadAsync(resource, options);
}
/**
* 异步加载文件(们)。
* @param {string|Array<string>} resource 文件的资源URL。
* @param {object|Array<string>} options 选项。
* @param {boolean} [options.cache=true] 是否使用缓存。
* @param {boolean} [options.inOrder=true] 是否按顺序加载文件。
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* // 加载'my-lib.js'文件并等待加载完成
* await THING.Utils.loadFileAsync('my-lib.js');
* @public
*/
static loadFileAsync(resource, options) {
return Utils.loadFile(resource, options);
}
/**
* 检查是否为压缩纹理。
* @param {string} url 压缩纹理的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');
}
/**
* 加载图片文件。
* @param {string} url 图片文件的URL。
* @param {Function} onLoad 加载完成时的回调函数。
* @param {Function} onProgress 加载过程中的回调函数。
* @param {Function} onError 加载错误时的回调函数。
* @param options
* @example
* // 通过加载回调函数加载图片资源。
* 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);
}
});
}
/**
* 异步加载图片文件
* @param {string} url 图片地址
* @param options
* @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);
}
/**
* 加载二进制文件。
* @param {object} resource 资源对象。
* @example
* let resource = document.createElement('video');;
* let check = THING.Utils.isVideoResource(resource);
* // @expect(check == true);
* @public
*/
static isVideoResource(resource) {
return Utils._fileManager.isVideoResource(resource);
}
/**
* @typedef {object} LoadFileOptions
* @property {boolean} useCache 是否启用缓存,true表示启用缓存。
*/
/**
* 加载二进制文件。
* @param {string} url 文件的URL。
* @param {Function} onLoad 加载完成时的回调函数。
* @param {Function} onProgress 加载过程中的回调函数。
* @param {Function} onError 加载错误时的回调函数。
* @param {LoadFileOptions} 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);
});
}
/**
* 异步加载二进制文件。
* @param {string} url 文件的URL。
* @param {LoadFileOptions} options 选项。
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* // 加载'my-file.bin'文件并等待加载完成。
* await THING.Utils.loadBinaryFileAsync('my-file.bin');
* @public
*/
static loadBinaryFileAsync(url, options) {
return Utils.loadBinaryFile(url, null, null, null, options);
}
/**
* 保存数据为文件。
* @param {string} fileName 文件名。
* @param {string|Blob|Image} data 文件数据。
* @returns {boolean}
* @example
* let data = JSON.stringify('{ name: "很高兴见到你" }');
* THING.Utils.saveAsFile('test.json', data);
* @public
*/
static saveAsFile(fileName, data) {
return Utils._fileManager.saveAsFile(fileName, data);
}
/**
* 保存数据为图片。
* @param {number} width 图片的宽度(像素)。
* @param {number} height 图片的高度(像素)。
* @param {Uint8Array} pixelBuffer 图片的像素缓冲区。
* @returns {object}
* @example
* // 保存一个32x32像素的图片,像素颜色随机
* 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);
}
/**
* 加载文本文件。
* @param {string} url 文件的URL。
* @param {Function} onLoad 加载完成时的回调函数。
* @param {Function} onProgress 加载过程中的回调函数。
* @param {Function} onError 加载错误时的回调函数。
* @param {LoadFileOptions} options 选项。
* @example
* // 以文本格式加载'my-file.txt'文件。
* THING.Utils.loadTextFile('my-file.txt',
* function(data) {
* console.log('加载完成', data);
* },
* function() {
* console.error('加载失败');
* }
* );
* @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);
});
}
/**
* 异步加载文本文件。
* @param {string} url 文件的URL。
* @param {LoadFileOptions} options 选项。
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* // 加载'my-file.txt'文件并等待加载完成。
* await THING.Utils.loadTextFileAsync('my-file.txt');
* @public
*/
static loadTextFileAsync(url, options) {
return this.loadTextFile(url, null, null, null, options);
}
/**
* 加载JSON文件。
* @param {string} url 文件的URL。
* @param {Function} onLoad 加载完成时的回调函数。
* @param {Function} onProgress 加载过程中的回调函数。
* @param {Function} onError 加载错误时的回调函数。
* @param {LoadFileOptions} options 选项。
* @example
* // 以JSON格式加载'my-file.json'文件。
* THING.Utils.loadJSONFile('my-file.json',
* function(data) {
* console.log('加载完成', data);
* },
* function() {
* console.error('加载失败');
* }
* );
* @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);
});
}
static fileExists(url) {
return Utils._fileManager.exist(url);
}
/**
* 异步加载JSON文件。
* @param {string|Array<string>} url 文件的URL。
* @param {LoadFileOptions} options 选项。
* @returns {Promise<any>}
* @private
* @deprecated 2.7
* @private
* @example
* // 加载'my-file.json'文件并等待加载完成。
* 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);
}
/**
* 当执行动作时。
* @param {number} value 按钮类型。
* @example
* let type = THING.Utils.parseMouseButtonType(1);
* // @expect(type == '中键');
* @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) {
// 定义需要处理的属性列表
const optionKeys = ['propagate', 'useCapture', 'enumerable', 'pausable', 'includeSelf'];
// 处理选项对象的辅助函数
const parseOptions = (options) => {
if (Utils.isObject(options)) {
optionKeys.forEach(key => {
info[key] = Utils.parseValue(options[key], info[key]);
});
}
};
// .(tag)
if (Utils.isString(args[0])) {
info.tag = args[0];
// .(tag, {options})
if (Utils.isObject(args[1])) {
parseOptions(args[1]);
}
// .(tag, priority)
else if (Utils.isNumber(args[1])) {
info.priority = args[1];
// .(tag, priority, {options})
parseOptions(args[2]);
}
}
// .(priority)
else if (Utils.isNumber(args[0])) {
info.priority = args[0];
// .(priority, {options})
parseOptions(args[1]);
}
// .({options})
else if (Utils.isObject(args[0])) {
parseOptions(args[0]);
// .({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) || .(tag, priority, options, onInitEventArgs)
else if (args.length == 3 || args.length == 4) {
info.tag = Utils.parseValue(args[0], '');
info.priority = Utils.parseValue(args[1], 0);
parseOptions(args[2]);
}
}
// Parse event.
static parseEvent() {
let info = {
type: arguments[0].toLowerCase(),
condition: null,
callback: _dummyFunc,
tag: null,
priority: 0,
once: false,
propagate: true,
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;
}
/**
* 解析飞行参数。
* @param {object} 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;
}
/**
* 解析循环类型。
* @param {string} name 循环类型。
* @returns {string}
* @example
* let ret1 = THING.Utils.parseLoopType('repeat');
* let ret2 = THING.Utils.parseLoopType('pingpong');
* let ret3 = THING.Utils.parseLoopType('temp');
* // @expect(ret1 == '重复' && ret2 == '来回' && ret3 == '');
* @public
*/
static parseLoopType(name) {
if (name) {
switch (name.toLowerCase()) {
case 'repeat': return LoopType.Repeat;
case 'pingpong': return LoopType.PingPong;
default:
break;
}
}
return '';
}
/**
* 解析插值类型
* @param {string | object} name 插值名称。
* @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;
}
static parseCubeTextureParams(param) {
if (Utils.isString(param)) {
param = { url: param };
}
const _url = param['url'];
if (Utils.isObject(_url)) {
// convert object to array with a fixed order
if (_url['negx'] && _url['negy'] && _url['negz'] && _url['posx'] && _url['posy'] && _url['posz']) {
param['url'] = [_url['posx'], _url['negx'], _url['posy'], _url['negy'], _url['posz'], _url['negz']]
}
}
// Parse folder url path
else if (Utils.isString(_url)) {
if (!Utils.isImagePath(_url)) {
let suffix = '.jpg';
let preffix = _url;
if (_url.indexOf('?') > -1) {
let arr = _url.split('?');
suffix = '.' + arr.pop();
preffix = arr.join('');
}
let baseUrl = preffix + '/';
if (preffix.charAt(preffix.length - 1) === '/' || preffix.slice(preffix.length - 1, preffix.length - 2) === '\\') {
baseUrl = preffix;
}
let fileNames = ['posx', 'negx', 'posy', 'negy', 'posz', 'negz'];
param = {
url: fileNames.map(name => baseUrl + name + suffix),
};
}
}
if (Utils.isArray(param) || Utils.isString(param)) {
param = { url: param };
}
return param
}
/**
* 解析立方体纹理的URL。
* @param {string} 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('negx.jpg'),
path._appendURL('posy.jpg'),
path._appendURL('negy.jpg'),
path._appendURL('posz.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);
}
/**
* 根据类型创建对象
* @param {string} type 对象类型。
* @param {object} 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 (Utils.isNull(options)) {
options = {}
}
let app = options.app || _app;
if (app && app.scene && app.view) {
options = Utils.cloneObject(options)
options.view = options.view || app.view;
options.scene = options.scene || app.scene.context;
}
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
});
}
/**
* 检查是否为有效的纹理
* @param {object} value 纹理对象。
* @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');
*/
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.
* @property {Function} onUseResourceCache The resource cache callback function, we can set it to tell whether use cache.
*/
/**
* 登录。
* @param {string|LoginOptions} options URL或选项。
* @returns {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 };
}
const _defaultAuthURL = StringEncoder.toText("<@secret https://www.thingjs.org.cn/auth/login>");
let url = options['url'] || _defaultAuthURL;
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 workerAliveTime = Utils.parseValue(options['workerAliveTime'], 60 * 1000);
let disableBackgroundThreads = Utils.parseValue(options['disableBackgroundThreads'], false);
let wasmFileName = Utils.parseValue(options['wasmFileName'], '');
let loadMode = Utils.parseValue(options['loadMode'], 'load');
let cache = Utils.parseValue(options['cache'], 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 onUseResourceCache = (url) => {
if (options['onUseResourceCache']) {
let result = options['onUseResourceCache'](url);
return result;
}
return false;
}
let { module, wasmOptions } = await _system.loadWasmModule(wasmRootPath, wasmFileName, loadMode,
// cache
{
enable: cache
},
// workers
{
maxNumber: maxWorkerNumber,
aliveTime: workerAliveTime,
disableBackgroundThreads
},
// callbacks
{
onGetResourceType,
onUseResourceCache
});
let helper = module.System || 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);
}
}
_authData = null;
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[_pluginsKey] = authData[_pluginsKey];
_authData[_hasExpiredKey] = false;
_authData[_showLogoKey] = authData[_showLogoKey];
_authData[_watermarkKey] = authData[_watermarkKey];
}
authData[_hasExpiredKey] = Utils.parseValue(authData[_hasExpiredKey], true);
}
resolve(_authData);
});
}
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, options) {
return Utils._scriptLoader.importScript(url, options);
}
static extractClassesFromScript(url, options) {
let data = {
componentClasses: new Map(),
bpnodeClasses: new Map(),
objectClasses: new Map(),
unknownClasses: new Map()
}
return new Promise((resolve, reject) => {
Utils._scriptLoader.importScript(url, options).then((module) => {
const defaultExport = module['default'];
if (!defaultExport) {
for (const key in module) {
_addToMap(module[key], key)
}
}
else if (Array.isArray(defaultExport)) {
defaultExport.forEach(element => {
_addToMap(element)
});
}
else {
for (const key in defaultExport) {
_addToMap(defaultExport[key], key)
}
}
resolve(data)
});
});
function _addToMap(cls, clsName) {
clsName = clsName || cls.name;
if (cls.prototype.isComponent) {
data.componentClasses.set(clsName, cls);
}
else if (cls.prototype.isBaseNode) {
data.bpnodeClasses.set(clsName, cls);
}
else if (cls.prototype.isBaseObject) {
data.objectClasses.set(clsName, cls);
}
else {
data.unknownClasses.set(clsName, cls);
}
}
}
/**
* 检查是否为gltf或glb文件
* @param {string} url 场景的URL。
* @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);
}
/**
* 检查是否为gltf或glb文件
* @param {string} ext 文件扩展名。
* @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();
}
static get settings() {
return _settings;
}
/**
* Get similar color based on array and index.
* @param {number[][]} colors The colors
* @param {number} prevIndex The previous index
* @param {number} [colorStride=3] The color stride, 3 for RGB, 4 for RGBA
* @returns {number[]} The color
*/
static getSimilarColor(colors = [], prevIndex, colorStride = 3) {
if (colors.length === 0) {
return colorStride === 4 ? [1, 1, 1, 1] : [1, 1, 1];
}
if (!colors[0].length) {
if (colors.length === colorStride) {
return colors.slice(0, colorStride);
}
if (Utils.isNull(prevIndex)) {
const length = colors.length;
return colors.slice(length - colorStride, length);
}
const realPrevIndex = prevIndex % colorStride;
const nextIndex = (prevIndex + 1) * colorStride;
if (!Utils.isNull(colors[prevIndex * colorStride])) {
if (!Utils.isNull(colors[nextIndex])) {
const result = [];
for (let i = 0; i < colorStride; i++) {
result[i] = (colors[realPrevIndex + i] + colors[nextIndex + i]) / 2;
}
return result;
}
else {
return colors.slice(prevIndex * colorStride, prevIndex * colorStride + colorStride);
}
}
return colorStride === 4 ? [1, 1, 1, 1] : [1, 1, 1];
}
if (colors.length === 1) {
return colors[0].slice(0, colorStride);
}
if (Utils.isNull(prevIndex)) {
return colors[colors.length - 1].slice(0, colorStride);
}
const nextIndex = prevIndex + 1;
if (colors[prevIndex]) {
if (colors[nextIndex]) {
const result = [];
for (let i = 0; i < colorStride; i++) {
result[i] = (colors[prevIndex][i] + colors[nextIndex][i]) / 2;
}
return result;
}
else {
return colors[prevIndex].slice(0, colorStride);
}
}
return colorStride === 4 ? [1, 1, 1, 1] : [1, 1, 1];
}
static convertSRGBToLinear(r, g, b) {
return [
Utils.SRGBToLinear(r),
Utils.SRGBToLinear(g),
Utils.SRGBToLinear(b)
]
}
static SRGBToLinear(c) {
return (c < 0.04045) ? c * 0.0773993808 : Math.pow(c * 0.9478672986 + 0.0521327014, 2.4);
}
static prependWorkPath(url, workPath) {
if (!url || !workPath) {
return url;
}
if (!url.startsWith('/')) {
return url;
}
if (url.startsWith(workPath)) {
return url;
}
return workPath._appendPath(url)
}
/**
* 清除缓存
* @public
*/
static clearCaches() {
if (_fileManager) {
_fileManager.clearCache();
}
if (_system) {
_system.clearCaches();
}
THING.App?.instances?.forEach(app => {
app && !app.isDisposed && app.resourceManager.clearCache();
})
}
}
export { Utils, _getAuthData };