UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

2,175 lines (1,769 loc) 1.9 MB
/** * @license * PlayCanvas Engine v1.41.0-dev revision bbc2326ad * Copyright 2011-2021 PlayCanvas Ltd. All rights reserved. */ if (!Array.prototype.find) { Object.defineProperty(Array.prototype, 'find', { value: function (predicate) { if (this == null) { throw TypeError('"this" is null or not defined'); } var o = Object(this); var len = o.length >>> 0; if (typeof predicate !== 'function') { throw TypeError('predicate must be a function'); } var thisArg = arguments[1]; var k = 0; while (k < len) { var kValue = o[k]; if (predicate.call(thisArg, kValue, k, o)) { return kValue; } k++; } return undefined; }, configurable: true, writable: true }); } if (!Array.prototype.findIndex) { Object.defineProperty(Array.prototype, 'findIndex', { value: function (predicate) { if (this == null) { throw new TypeError('"this" is null or not defined'); } var o = Object(this); var len = o.length >>> 0; if (typeof predicate !== 'function') { throw new TypeError('predicate must be a function'); } var thisArg = arguments[1]; var k = 0; while (k < len) { var kValue = o[k]; if (predicate.call(thisArg, kValue, k, o)) { return k; } k++; } return -1; }, configurable: true, writable: true }); } Math.log2 = Math.log2 || function (x) { return Math.log(x) * Math.LOG2E; }; if (!Math.sign) { Math.sign = function (x) { return (x > 0) - (x < 0) || +x; }; } if (Number.isFinite === undefined) Number.isFinite = function (value) { return typeof value === 'number' && isFinite(value); }; if (typeof Object.assign != 'function') { Object.defineProperty(Object, "assign", { value: function assign(target, varArgs) { if (target == null) { throw new TypeError('Cannot convert undefined or null to object'); } var to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource != null) { for (var nextKey in nextSource) { if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }, writable: true, configurable: true }); } (function () { if (typeof navigator === 'undefined' || typeof document === 'undefined') { return; } navigator.pointer = navigator.pointer || navigator.webkitPointer || navigator.mozPointer; var pointerlockchange = function pointerlockchange() { var e = document.createEvent('CustomEvent'); e.initCustomEvent('pointerlockchange', true, false, null); document.dispatchEvent(e); }; var pointerlockerror = function pointerlockerror() { var e = document.createEvent('CustomEvent'); e.initCustomEvent('pointerlockerror', true, false, null); document.dispatchEvent(e); }; document.addEventListener('webkitpointerlockchange', pointerlockchange, false); document.addEventListener('webkitpointerlocklost', pointerlockchange, false); document.addEventListener('mozpointerlockchange', pointerlockchange, false); document.addEventListener('mozpointerlocklost', pointerlockchange, false); document.addEventListener('webkitpointerlockerror', pointerlockerror, false); document.addEventListener('mozpointerlockerror', pointerlockerror, false); if (Element.prototype.mozRequestPointerLock) { Element.prototype.requestPointerLock = function () { this.mozRequestPointerLock(); }; } else { Element.prototype.requestPointerLock = Element.prototype.requestPointerLock || Element.prototype.webkitRequestPointerLock || Element.prototype.mozRequestPointerLock; } if (!Element.prototype.requestPointerLock && navigator.pointer) { Element.prototype.requestPointerLock = function () { var el = this; document.pointerLockElement = el; navigator.pointer.lock(el, pointerlockchange, pointerlockerror); }; } document.exitPointerLock = document.exitPointerLock || document.webkitExitPointerLock || document.mozExitPointerLock; if (!document.exitPointerLock) { document.exitPointerLock = function () { if (navigator.pointer) { document.pointerLockElement = null; navigator.pointer.unlock(); } }; } })(); (function () { if (typeof window === 'undefined') return; var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; } if (!window.requestAnimationFrame) window.requestAnimationFrame = function (callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function () { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function (id) { clearTimeout(id); }; })(); if (!String.prototype.endsWith) { String.prototype.endsWith = function (search, this_len) { if (this_len === undefined || this_len > this.length) { this_len = this.length; } return this.substring(this_len - search.length, this_len) === search; }; } if (!String.prototype.includes) { String.prototype.includes = function (search, start) { if (typeof start !== 'number') { start = 0; } if (start + search.length > this.length) { return false; } else { return this.indexOf(search, start) !== -1; } }; } if (!String.prototype.startsWith) { String.prototype.startsWith = function (search, pos) { return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; }; } (function () { var glErrorShadow = {}; function error(msg) { if (window.console && window.console.error) { window.console.error(msg); } } function log(msg) { if (window.console && window.console.log) { window.console.log(msg); } } function synthesizeGLError(err, opt_msg) { glErrorShadow[err] = true; if (opt_msg !== undefined) { error(opt_msg); } } function wrapGLError(gl) { var f = gl.getError; gl.getError = function () { var err; do { err = f.apply(gl); if (err != gl.NO_ERROR) { glErrorShadow[err] = true; } } while (err != gl.NO_ERROR); for (var err in glErrorShadow) { if (glErrorShadow[err]) { delete glErrorShadow[err]; return parseInt(err); } } return gl.NO_ERROR; }; } var WebGLVertexArrayObjectOES = function WebGLVertexArrayObjectOES(ext) { var gl = ext.gl; this.ext = ext; this.isAlive = true; this.hasBeenBound = false; this.elementArrayBuffer = null; this.attribs = new Array(ext.maxVertexAttribs); for (var n = 0; n < this.attribs.length; n++) { var attrib = new WebGLVertexArrayObjectOES.VertexAttrib(gl); this.attribs[n] = attrib; } this.maxAttrib = 0; }; WebGLVertexArrayObjectOES.VertexAttrib = function VertexAttrib(gl) { this.enabled = false; this.buffer = null; this.size = 4; this.type = gl.FLOAT; this.normalized = false; this.stride = 16; this.offset = 0; this.cached = ""; this.recache(); }; WebGLVertexArrayObjectOES.VertexAttrib.prototype.recache = function recache() { this.cached = [this.size, this.type, this.normalized, this.stride, this.offset].join(":"); }; var OESVertexArrayObject = function OESVertexArrayObject(gl) { var self = this; this.gl = gl; wrapGLError(gl); var original = this.original = { getParameter: gl.getParameter, enableVertexAttribArray: gl.enableVertexAttribArray, disableVertexAttribArray: gl.disableVertexAttribArray, bindBuffer: gl.bindBuffer, getVertexAttrib: gl.getVertexAttrib, vertexAttribPointer: gl.vertexAttribPointer }; gl.getParameter = function getParameter(pname) { if (pname == self.VERTEX_ARRAY_BINDING_OES) { if (self.currentVertexArrayObject == self.defaultVertexArrayObject) { return null; } else { return self.currentVertexArrayObject; } } return original.getParameter.apply(this, arguments); }; gl.enableVertexAttribArray = function enableVertexAttribArray(index) { var vao = self.currentVertexArrayObject; vao.maxAttrib = Math.max(vao.maxAttrib, index); var attrib = vao.attribs[index]; attrib.enabled = true; return original.enableVertexAttribArray.apply(this, arguments); }; gl.disableVertexAttribArray = function disableVertexAttribArray(index) { var vao = self.currentVertexArrayObject; vao.maxAttrib = Math.max(vao.maxAttrib, index); var attrib = vao.attribs[index]; attrib.enabled = false; return original.disableVertexAttribArray.apply(this, arguments); }; gl.bindBuffer = function bindBuffer(target, buffer) { switch (target) { case gl.ARRAY_BUFFER: self.currentArrayBuffer = buffer; break; case gl.ELEMENT_ARRAY_BUFFER: self.currentVertexArrayObject.elementArrayBuffer = buffer; break; } return original.bindBuffer.apply(this, arguments); }; gl.getVertexAttrib = function getVertexAttrib(index, pname) { var vao = self.currentVertexArrayObject; var attrib = vao.attribs[index]; switch (pname) { case gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: return attrib.buffer; case gl.VERTEX_ATTRIB_ARRAY_ENABLED: return attrib.enabled; case gl.VERTEX_ATTRIB_ARRAY_SIZE: return attrib.size; case gl.VERTEX_ATTRIB_ARRAY_STRIDE: return attrib.stride; case gl.VERTEX_ATTRIB_ARRAY_TYPE: return attrib.type; case gl.VERTEX_ATTRIB_ARRAY_NORMALIZED: return attrib.normalized; default: return original.getVertexAttrib.apply(this, arguments); } }; gl.vertexAttribPointer = function vertexAttribPointer(indx, size, type, normalized, stride, offset) { var vao = self.currentVertexArrayObject; vao.maxAttrib = Math.max(vao.maxAttrib, indx); var attrib = vao.attribs[indx]; attrib.buffer = self.currentArrayBuffer; attrib.size = size; attrib.type = type; attrib.normalized = normalized; attrib.stride = stride; attrib.offset = offset; attrib.recache(); return original.vertexAttribPointer.apply(this, arguments); }; if (gl.instrumentExtension) { gl.instrumentExtension(this, "OES_vertex_array_object"); } gl.canvas.addEventListener('webglcontextrestored', function () { log("OESVertexArrayObject emulation library context restored"); self.reset_(); }, true); this.reset_(); }; OESVertexArrayObject.prototype.VERTEX_ARRAY_BINDING_OES = 0x85B5; OESVertexArrayObject.prototype.reset_ = function reset_() { var contextWasLost = this.vertexArrayObjects !== undefined; if (contextWasLost) { for (var ii = 0; ii < this.vertexArrayObjects.length; ++ii) { this.vertexArrayObjects.isAlive = false; } } var gl = this.gl; this.maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); this.defaultVertexArrayObject = new WebGLVertexArrayObjectOES(this); this.currentVertexArrayObject = null; this.currentArrayBuffer = null; this.vertexArrayObjects = [this.defaultVertexArrayObject]; this.bindVertexArrayOES(null); }; OESVertexArrayObject.prototype.createVertexArrayOES = function createVertexArrayOES() { var arrayObject = new WebGLVertexArrayObjectOES(this); this.vertexArrayObjects.push(arrayObject); return arrayObject; }; OESVertexArrayObject.prototype.deleteVertexArrayOES = function deleteVertexArrayOES(arrayObject) { arrayObject.isAlive = false; this.vertexArrayObjects.splice(this.vertexArrayObjects.indexOf(arrayObject), 1); if (this.currentVertexArrayObject == arrayObject) { this.bindVertexArrayOES(null); } }; OESVertexArrayObject.prototype.isVertexArrayOES = function isVertexArrayOES(arrayObject) { if (arrayObject && arrayObject instanceof WebGLVertexArrayObjectOES) { if (arrayObject.hasBeenBound && arrayObject.ext == this) { return true; } } return false; }; OESVertexArrayObject.prototype.bindVertexArrayOES = function bindVertexArrayOES(arrayObject) { var gl = this.gl; if (arrayObject && !arrayObject.isAlive) { synthesizeGLError(gl.INVALID_OPERATION, "bindVertexArrayOES: attempt to bind deleted arrayObject"); return; } var original = this.original; var oldVAO = this.currentVertexArrayObject; this.currentVertexArrayObject = arrayObject || this.defaultVertexArrayObject; this.currentVertexArrayObject.hasBeenBound = true; var newVAO = this.currentVertexArrayObject; if (oldVAO == newVAO) { return; } if (!oldVAO || newVAO.elementArrayBuffer != oldVAO.elementArrayBuffer) { original.bindBuffer.call(gl, gl.ELEMENT_ARRAY_BUFFER, newVAO.elementArrayBuffer); } var currentBinding = this.currentArrayBuffer; var maxAttrib = Math.max(oldVAO ? oldVAO.maxAttrib : 0, newVAO.maxAttrib); for (var n = 0; n <= maxAttrib; n++) { var attrib = newVAO.attribs[n]; var oldAttrib = oldVAO ? oldVAO.attribs[n] : null; if (!oldVAO || attrib.enabled != oldAttrib.enabled) { if (attrib.enabled) { original.enableVertexAttribArray.call(gl, n); } else { original.disableVertexAttribArray.call(gl, n); } } if (attrib.enabled) { var bufferChanged = false; if (!oldVAO || attrib.buffer != oldAttrib.buffer) { if (currentBinding != attrib.buffer) { original.bindBuffer.call(gl, gl.ARRAY_BUFFER, attrib.buffer); currentBinding = attrib.buffer; } bufferChanged = true; } if (bufferChanged || attrib.cached != oldAttrib.cached) { original.vertexAttribPointer.call(gl, n, attrib.size, attrib.type, attrib.normalized, attrib.stride, attrib.offset); } } } if (this.currentArrayBuffer != currentBinding) { original.bindBuffer.call(gl, gl.ARRAY_BUFFER, this.currentArrayBuffer); } }; window.setupVertexArrayObject = function (gl) { if (gl.getSupportedExtensions) { var exts = gl.getSupportedExtensions(); if (exts.indexOf("OES_vertex_array_object") != -1) { return; } } else if (gl.getExtension) { var vao = gl.getExtension("OES_vertex_array_object"); if (vao) { return; } } if (gl.getSupportedExtensions) { var original_getSupportedExtensions = gl.getSupportedExtensions; gl.getSupportedExtensions = function getSupportedExtensions() { var list = original_getSupportedExtensions.call(this) || []; list.push("OES_vertex_array_object"); return list; }; } var original_getExtension = gl.getExtension; gl.getExtension = function getExtension(name) { if (name == "OES_vertex_array_object") { if (!gl.__OESVertexArrayObject) { gl.__OESVertexArrayObject = new OESVertexArrayObject(gl); } return gl.__OESVertexArrayObject; } if (original_getExtension) { return original_getExtension.call(this, name); } else { return null; } }; }; })(); const _typeLookup = function () { const result = {}; const names = ["Array", "Object", "Function", "Date", "RegExp", "Float32Array"]; for (let i = 0; i < names.length; i++) result["[object " + names[i] + "]"] = names[i].toLowerCase(); return result; }(); const version = "1.41.0-dev"; const revision = "bbc2326ad"; const config = {}; const common = {}; const apps = {}; const data = {}; function type$1(obj) { if (obj === null) { return "null"; } const type = typeof obj; if (type === "undefined" || type === "number" || type === "string" || type === "boolean") { return type; } return _typeLookup[Object.prototype.toString.call(obj)]; } function extend(target, ex) { for (const prop in ex) { const copy = ex[prop]; if (type$1(copy) === "object") { target[prop] = extend({}, copy); } else if (type$1(copy) === "array") { target[prop] = extend([], copy); } else { target[prop] = copy; } } return target; } function isDefined(o) { let a; return o !== a; } let table = null; let row = null; let title = null; let field = null; function init() { table = document.createElement('table'); row = document.createElement('tr'); title = document.createElement('td'); field = document.createElement('td'); table.style.cssText = 'position:absolute;font-family:sans-serif;font-size:12px;color:#cccccc'; table.style.top = '0px'; table.style.left = '0px'; table.style.border = 'thin solid #cccccc'; document.body.appendChild(table); } const debug = { display: function (data) { if (!table) { init(); } table.innerHTML = ''; for (const key in data) { const r = row.cloneNode(); const t = title.cloneNode(); const f = field.cloneNode(); t.textContent = key; f.textContent = data[key]; r.appendChild(t); r.appendChild(f); table.appendChild(r); } } }; class EventHandler { constructor() { this.initEventHandler(); } initEventHandler() { this._callbacks = {}; this._callbackActive = {}; } _addCallback(name, callback, scope, once = false) { if (!name || typeof name !== 'string' || !callback) return; if (!this._callbacks[name]) this._callbacks[name] = []; if (this._callbackActive[name] && this._callbackActive[name] === this._callbacks[name]) this._callbackActive[name] = this._callbackActive[name].slice(); this._callbacks[name].push({ callback: callback, scope: scope || this, once: once }); } on(name, callback, scope) { this._addCallback(name, callback, scope, false); return this; } off(name, callback, scope) { if (name) { if (this._callbackActive[name] && this._callbackActive[name] === this._callbacks[name]) this._callbackActive[name] = this._callbackActive[name].slice(); } else { for (const key in this._callbackActive) { if (!this._callbacks[key]) continue; if (this._callbacks[key] !== this._callbackActive[key]) continue; this._callbackActive[key] = this._callbackActive[key].slice(); } } if (!name) { this._callbacks = {}; } else if (!callback) { if (this._callbacks[name]) this._callbacks[name] = []; } else { const events = this._callbacks[name]; if (!events) return this; let count = events.length; for (let i = 0; i < count; i++) { if (events[i].callback !== callback) continue; if (scope && events[i].scope !== scope) continue; events[i--] = events[--count]; } events.length = count; } return this; } fire(name, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { if (!name || !this._callbacks[name]) return this; let callbacks; if (!this._callbackActive[name]) { this._callbackActive[name] = this._callbacks[name]; } else { if (this._callbackActive[name] === this._callbacks[name]) this._callbackActive[name] = this._callbackActive[name].slice(); callbacks = this._callbacks[name].slice(); } for (let i = 0; (callbacks || this._callbackActive[name]) && i < (callbacks || this._callbackActive[name]).length; i++) { const evt = (callbacks || this._callbackActive[name])[i]; evt.callback.call(evt.scope, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); if (evt.once) { const ind = this._callbacks[name].indexOf(evt); if (ind !== -1) { if (this._callbackActive[name] === this._callbacks[name]) this._callbackActive[name] = this._callbackActive[name].slice(); this._callbacks[name].splice(ind, 1); } } } if (!callbacks) this._callbackActive[name] = null; return this; } once(name, callback, scope) { this._addCallback(name, callback, scope, true); return this; } hasEvent(name) { return this._callbacks[name] && this._callbacks[name].length !== 0 || false; } } const events = { attach: function (target) { const ev = events; target._addCallback = ev._addCallback; target.on = ev.on; target.off = ev.off; target.fire = ev.fire; target.once = ev.once; target.hasEvent = ev.hasEvent; target._callbacks = {}; target._callbackActive = {}; return target; }, _addCallback: EventHandler.prototype._addCallback, on: EventHandler.prototype.on, off: EventHandler.prototype.off, fire: EventHandler.prototype.fire, once: EventHandler.prototype.once, hasEvent: EventHandler.prototype.hasEvent }; const guid = { create: function () { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : r & 0x3 | 0x8; return v.toString(16); }); } }; const path = { delimiter: "/", join: function () { const num = arguments.length; let result = arguments[0]; for (let index = 0; index < num - 1; ++index) { const one = arguments[index]; const two = arguments[index + 1]; if (!isDefined(one) || !isDefined(two)) { throw new Error("undefined argument to pc.path.join"); } if (two[0] === path.delimiter) { result = two; continue; } if (one && two && one[one.length - 1] !== path.delimiter && two[0] !== path.delimiter) { result += path.delimiter + two; } else { result += two; } } return result; }, normalize: function (pathname) { const lead = pathname.startsWith(path.delimiter); const trail = pathname.endsWith(path.delimiter); const parts = pathname.split('/'); let result = ''; let cleaned = []; for (let i = 0; i < parts.length; i++) { if (parts[i] === '') continue; if (parts[i] === '.') continue; if (parts[i] === '..' && cleaned.length > 0) { cleaned = cleaned.slice(0, cleaned.length - 2); continue; } if (i > 0) cleaned.push(path.delimiter); cleaned.push(parts[i]); } result = cleaned.join(''); if (!lead && result[0] === path.delimiter) { result = result.slice(1); } if (trail && result[result.length - 1] !== path.delimiter) { result += path.delimiter; } return result; }, split: function (pathname) { const parts = pathname.split(path.delimiter); const tail = parts.slice(parts.length - 1)[0]; const head = parts.slice(0, parts.length - 1).join(path.delimiter); return [head, tail]; }, getBasename: function (pathname) { return path.split(pathname)[1]; }, getDirectory: function (pathname) { const parts = pathname.split(path.delimiter); return parts.slice(0, parts.length - 1).join(path.delimiter); }, getExtension: function (pathname) { const ext = pathname.split('?')[0].split('.').pop(); if (ext !== pathname) { return "." + ext; } return ""; }, isRelativePath: function (pathname) { return pathname.charAt(0) !== "/" && pathname.match(/:\/\//) === null; }, extractPath: function (pathname) { let result = ""; const parts = pathname.split("/"); let i = 0; if (parts.length > 1) { if (path.isRelativePath(pathname)) { if (parts[0] === ".") { for (i = 0; i < parts.length - 1; ++i) { result += i === 0 ? parts[i] : "/" + parts[i]; } } else if (parts[0] === "..") { for (i = 0; i < parts.length - 1; ++i) { result += i === 0 ? parts[i] : "/" + parts[i]; } } else { result = "."; for (i = 0; i < parts.length - 1; ++i) { result += "/" + parts[i]; } } } else { for (i = 0; i < parts.length - 1; ++i) { result += i === 0 ? parts[i] : "/" + parts[i]; } } } return result; } }; let desktop = false; let mobile = false; let windows = false; let xbox = false; let android = false; let ios = false; let touch = false; let gamepads = false; let workers = false; let passiveEvents = false; if (typeof navigator !== 'undefined') { const ua = navigator.userAgent; if (/(windows|mac os|linux|cros)/i.test(ua)) desktop = true; if (/xbox/i.test(ua)) xbox = true; if (/(windows phone|iemobile|wpdesktop)/i.test(ua)) { desktop = false; mobile = true; windows = true; } else if (/android/i.test(ua)) { desktop = false; mobile = true; android = true; } else if (/ip([ao]d|hone)/i.test(ua)) { desktop = false; mobile = true; ios = true; } if (typeof window !== 'undefined') { touch = 'ontouchstart' in window || 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0; } gamepads = 'getGamepads' in navigator; workers = typeof Worker !== 'undefined'; try { const opts = Object.defineProperty({}, 'passive', { get: function () { passiveEvents = true; return false; } }); window.addEventListener("testpassive", null, opts); window.removeEventListener("testpassive", null, opts); } catch (e) {} } const platform = { desktop: desktop, mobile: mobile, ios: ios, android: android, windows: windows, xbox: xbox, gamepads: gamepads, touch: touch, workers: workers, passiveEvents: passiveEvents }; const ASCII_LOWERCASE = "abcdefghijklmnopqrstuvwxyz"; const ASCII_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const ASCII_LETTERS = ASCII_LOWERCASE + ASCII_UPPERCASE; const HIGH_SURROGATE_BEGIN = 0xD800; const HIGH_SURROGATE_END = 0xDBFF; const LOW_SURROGATE_BEGIN = 0xDC00; const LOW_SURROGATE_END = 0xDFFF; const ZERO_WIDTH_JOINER = 0x200D; const REGIONAL_INDICATOR_BEGIN = 0x1F1E6; const REGIONAL_INDICATOR_END = 0x1F1FF; const FITZPATRICK_MODIFIER_BEGIN = 0x1F3FB; const FITZPATRICK_MODIFIER_END = 0x1F3FF; const DIACRITICAL_MARKS_BEGIN = 0x20D0; const DIACRITICAL_MARKS_END = 0x20FF; const VARIATION_MODIFIER_BEGIN = 0xFE00; const VARIATION_MODIFIER_END = 0xFE0F; function getCodePointData(string, i = 0) { const size = string.length; if (i < 0 || i >= size) { return null; } const first = string.charCodeAt(i); if (size > 1 && first >= HIGH_SURROGATE_BEGIN && first <= HIGH_SURROGATE_END) { const second = string.charCodeAt(i + 1); if (second >= LOW_SURROGATE_BEGIN && second <= LOW_SURROGATE_END) { return { code: (first - HIGH_SURROGATE_BEGIN) * 0x400 + second - LOW_SURROGATE_BEGIN + 0x10000, long: true }; } } return { code: first, long: false }; } function isCodeBetween(string, begin, end) { if (!string) return false; const codeData = getCodePointData(string); if (codeData) { const code = codeData.code; return code >= begin && code <= end; } return false; } function numCharsToTakeForNextSymbol(string, index) { if (index === string.length - 1) { return 1; } if (isCodeBetween(string[index], HIGH_SURROGATE_BEGIN, HIGH_SURROGATE_END)) { const first = string.substring(index, index + 2); const second = string.substring(index + 2, index + 4); if (isCodeBetween(second, FITZPATRICK_MODIFIER_BEGIN, FITZPATRICK_MODIFIER_END) || isCodeBetween(first, REGIONAL_INDICATOR_BEGIN, REGIONAL_INDICATOR_END) && isCodeBetween(second, REGIONAL_INDICATOR_BEGIN, REGIONAL_INDICATOR_END)) { return 4; } if (isCodeBetween(second, VARIATION_MODIFIER_BEGIN, VARIATION_MODIFIER_END)) { return 3; } return 2; } if (isCodeBetween(string[index + 1], VARIATION_MODIFIER_BEGIN, VARIATION_MODIFIER_END)) { return 2; } return 1; } const string = { ASCII_LOWERCASE: ASCII_LOWERCASE, ASCII_UPPERCASE: ASCII_UPPERCASE, ASCII_LETTERS: ASCII_LETTERS, format: function (s) { for (let i = 1; i < arguments.length; i++) { s = s.replace('{' + (i - 1) + '}', arguments[i]); } return s; }, toBool: function (s, strict = false) { if (s === 'true') { return true; } if (strict) { if (s === 'false') { return false; } throw new TypeError('Not a boolean string'); } return false; }, getCodePoint: function (string, i) { const codePointData = getCodePointData(string, i); return codePointData && codePointData.code; }, getCodePoints: function (string) { if (typeof string !== 'string') { throw new TypeError('Not a string'); } let i = 0; const arr = []; let codePoint; while (!!(codePoint = getCodePointData(string, i))) { arr.push(codePoint.code); i += codePoint.long ? 2 : 1; } return arr; }, getSymbols: function (string) { if (typeof string !== 'string') { throw new TypeError('Not a string'); } let index = 0; const length = string.length; const output = []; let take = 0; let ch; while (index < length) { take += numCharsToTakeForNextSymbol(string, index + take); ch = string[index + take]; if (isCodeBetween(ch, DIACRITICAL_MARKS_BEGIN, DIACRITICAL_MARKS_END)) { ch = string[index + take++]; } if (isCodeBetween(ch, VARIATION_MODIFIER_BEGIN, VARIATION_MODIFIER_END)) { ch = string[index + take++]; } if (ch && ch.charCodeAt(0) === ZERO_WIDTH_JOINER) { ch = string[index + take++]; continue; } const char = string.substring(index, index + take); output.push(char); index += take; take = 0; } return output; }, fromCodePoint: function () { const chars = []; let current; let codePoint; let units; for (let i = 0; i < arguments.length; ++i) { current = Number(arguments[i]); codePoint = current - 0x10000; units = current > 0xFFFF ? [(codePoint >> 10) + 0xD800, codePoint % 0x400 + 0xDC00] : [current]; chars.push(String.fromCharCode.apply(null, units)); } return chars.join(''); } }; class IndexedList { constructor() { this._list = []; this._index = {}; } push(key, item) { if (this._index[key]) { throw Error("Key already in index " + key); } const location = this._list.push(item) - 1; this._index[key] = location; } has(key) { return this._index[key] !== undefined; } get(key) { const location = this._index[key]; if (location !== undefined) { return this._list[location]; } return null; } remove(key) { const location = this._index[key]; if (location !== undefined) { this._list.splice(location, 1); delete this._index[key]; for (key in this._index) { const idx = this._index[key]; if (idx > location) { this._index[key] = idx - 1; } } return true; } return false; } list() { return this._list; } clear() { this._list.length = 0; for (const prop in this._index) { delete this._index[prop]; } } } class SortedLoopArray { constructor(args) { this._sortBy = args.sortBy; this.items = []; this.length = 0; this.loopIndex = -1; this._sortHandler = this._doSort.bind(this); } _binarySearch(item) { let left = 0; let right = this.items.length - 1; const search = item[this._sortBy]; let middle; let current; while (left <= right) { middle = Math.floor((left + right) / 2); current = this.items[middle][this._sortBy]; if (current <= search) { left = middle + 1; } else if (current > search) { right = middle - 1; } } return left; } _doSort(a, b) { const sortBy = this._sortBy; return a[sortBy] - b[sortBy]; } insert(item) { const index = this._binarySearch(item); this.items.splice(index, 0, item); this.length++; if (this.loopIndex >= index) { this.loopIndex++; } } append(item) { this.items.push(item); this.length++; } remove(item) { const idx = this.items.indexOf(item); if (idx < 0) return; this.items.splice(idx, 1); this.length--; if (this.loopIndex >= idx) { this.loopIndex--; } } sort() { const current = this.loopIndex >= 0 ? this.items[this.loopIndex] : null; this.items.sort(this._sortHandler); if (current !== null) { this.loopIndex = this.items.indexOf(current); } } } class Tags extends EventHandler { constructor(parent) { super(); this._index = {}; this._list = []; this._parent = parent; } add() { let changed = false; const tags = this._processArguments(arguments, true); if (!tags.length) return changed; for (let i = 0; i < tags.length; i++) { if (this._index[tags[i]]) continue; changed = true; this._index[tags[i]] = true; this._list.push(tags[i]); this.fire('add', tags[i], this._parent); } if (changed) this.fire('change', this._parent); return changed; } remove() { let changed = false; if (!this._list.length) return changed; const tags = this._processArguments(arguments, true); if (!tags.length) return changed; for (let i = 0; i < tags.length; i++) { if (!this._index[tags[i]]) continue; changed = true; delete this._index[tags[i]]; this._list.splice(this._list.indexOf(tags[i]), 1); this.fire('remove', tags[i], this._parent); } if (changed) this.fire('change', this._parent); return changed; } clear() { if (!this._list.length) return; const tags = this._list.slice(0); this._list = []; this._index = {}; for (let i = 0; i < tags.length; i++) this.fire('remove', tags[i], this._parent); this.fire('change', this._parent); } has() { if (!this._list.length) return false; return this._has(this._processArguments(arguments)); } _has(tags) { if (!this._list.length || !tags.length) return false; for (let i = 0; i < tags.length; i++) { if (tags[i].length === 1) { if (this._index[tags[i][0]]) return true; } else { let multiple = true; for (let t = 0; t < tags[i].length; t++) { if (this._index[tags[i][t]]) continue; multiple = false; break; } if (multiple) return true; } } return false; } list() { return this._list.slice(0); } _processArguments(args, flat) { const tags = []; let tmp = []; if (!args || !args.length) return tags; for (let i = 0; i < args.length; i++) { if (args[i] instanceof Array) { if (!flat) tmp = []; for (let t = 0; t < args[i].length; t++) { if (typeof args[i][t] !== 'string') continue; if (flat) { tags.push(args[i][t]); } else { tmp.push(args[i][t]); } } if (!flat && tmp.length) tags.push(tmp); } else if (typeof args[i] === 'string') { if (flat) { tags.push(args[i]); } else { tags.push([args[i]]); } } } return tags; } get size() { return this._list.length; } } const now = typeof window !== 'undefined' && window.performance && window.performance.now && window.performance.timing ? function () { return window.performance.now(); } : Date.now; class Timer { constructor() { this._isRunning = false; this._a = 0; this._b = 0; } start() { this._isRunning = true; this._a = now(); } stop() { this._isRunning = false; this._b = now(); } getMilliseconds() { return this._b - this._a; } } function createURI(options) { let s = ""; if ((options.authority || options.scheme) && (options.host || options.hostpath)) { throw new Error("Can't have 'scheme' or 'authority' and 'host' or 'hostpath' option"); } if (options.host && options.hostpath) { throw new Error("Can't have 'host' and 'hostpath' option"); } if (options.path && options.hostpath) { throw new Error("Can't have 'path' and 'hostpath' option"); } if (options.scheme) { s += options.scheme + ":"; } if (options.authority) { s += "//" + options.authority; } if (options.host) { s += options.host; } if (options.path) { s += options.path; } if (options.hostpath) { s += options.hostpath; } if (options.query) { s += "?" + options.query; } if (options.fragment) { s += "#" + options.fragment; } return s; } const re = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; class URI { constructor(uri) { const result = uri.match(re); this.scheme = result[2]; this.authority = result[4]; this.path = result[5]; this.query = result[7]; this.fragment = result[9]; } toString() { let s = ""; if (this.scheme) { s += this.scheme + ":"; } if (this.authority) { s += "//" + this.authority; } s += this.path; if (this.query) { s += "?" + this.query; } if (this.fragment) { s += "#" + this.fragment; } return s; } getQuery() { const result = {}; if (this.query) { const queryParams = decodeURIComponent(this.query).split("&"); for (const queryParam of queryParams) { const pair = queryParam.split("="); result[pair[0]] = pair[1]; } } return result; } setQuery(params) { let q = ""; for (const key in params) { if (params.hasOwnProperty(key)) { if (q !== "") { q += "&"; } q += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); } } this.query = q; } } const math = { DEG_TO_RAD: Math.PI / 180, RAD_TO_DEG: 180 / Math.PI, clamp: function (value, min, max) { if (value >= max) return max; if (value <= min) return min; return value; }, intToBytes24: function (i) { const r = i >> 16 & 0xff; const g = i >> 8 & 0xff; const b = i & 0xff; return [r, g, b]; }, intToBytes32: function (i) { const r = i >> 24 & 0xff; const g = i >> 16 & 0xff; const b = i >> 8 & 0xff; const a = i & 0xff; return [r, g, b, a]; }, bytesToInt24: function (r, g, b) { if (r.length) { b = r[2]; g = r[1]; r = r[0]; } return r << 16 | g << 8 | b; }, bytesToInt32: function (r, g, b, a) { if (r.length) { a = r[3]; b = r[2]; g = r[1]; r = r[0]; } return (r << 24 | g << 16 | b << 8 | a) >>> 32; }, lerp: function (a, b, alpha) { return a + (b - a) * math.clamp(alpha, 0, 1); }, lerpAngle: function (a, b, alpha) { if (b - a > 180) { b -= 360; } if (b - a < -180) { b += 360; } return math.lerp(a, b, math.clamp(alpha, 0, 1)); }, powerOfTwo: function (x) { return x !== 0 && !(x & x - 1); }, nextPowerOfTwo: function (val) { val--; val |= val >> 1; val |= val >> 2; val |= val >> 4; val |= val >> 8; val |= val >> 16; val++; return val; }, random: function (min, max) { const diff = max - min; return Math.random() * diff + min; }, smoothstep: function (min, max, x) { if (x <= min) return 0; if (x >= max) return 1; x = (x - min) / (max - min); return x * x * (3 - 2 * x); }, smootherstep: function (min, max, x) { if (x <= min) return 0; if (x >= max) return 1; x = (x - min) / (max - min); return x * x * x * (x * (x * 6 - 15) + 10); }, roundUp: function (numToRound, multiple) { if (multiple === 0) return numToRound; return Math.ceil(numToRound / multiple) * multiple; }, float2Half: function () { const floatView = new Float32Array(1); const int32View = new Int32Array(floatView.buffer); return function (val) { floatView[0] = val; const x = int32View[0]; let bits = x >> 16 & 0x8000; let m = x >> 12 & 0x07ff; const e = x >> 23 & 0xff; if (e < 103) { return bits; } if (e > 142) { bits |= 0x7c00; bits |= (e === 255 ? 0 : 1) && x & 0x007fffff; return bits; } if (e < 113) { m |= 0x0800; bits |= (m >> 114 - e) + (m >> 113 - e & 1); return bits; } bits |= e - 112 << 10 | m >> 1; bits += m & 1; return bits; }; }(), between: function (num, a, b, inclusive) { const min = Math.min(a, b); const max = Math.max(a, b); return inclusive ? num >= min && num <= max : num > min && num < max; } }; class Http { constructor() { this.ContentType = Http.ContentType; this.ResponseType = Http.ResponseType; this.binaryExtensions = Http.binaryExtensions; } get(url, options, callback) { if (typeof options === "function") { callback = options; options = {}; } return this.request("GET", url, options, callback); } post(url, data, options, callback) { if (typeof options === "function") { callback = options; options = {}; } options.postdata = data; return this.request("POST", url, options, callback); } put(url, data, options, callback) { if (typeof options === "function") { callback = options; options = {}; } options.postdata = data; return this.request("PUT", url, options, callback); } del(url, options, callback) { if (typeof options === "function") { callback = options; options = {}; } return this.request("DELETE", url, options, callback); } request(method, url, options, callback) { var uri, query, timestamp, postdata, xhr; var errored = false; if (typeof options === "function") { callback = options; options = {}; } if (options.retry) { options = Object.assign({ retries: 0, maxRetries: 5 }, options); } options.callback = callback; if (options.async == null) { options.async = true; } if (options.headers == null) { options.headers = {}; } if (options.postdata != null) { if (options.postdata instanceof Document) { postdata = options.postdata; } else if (options.postdata instanceof FormData) { postdata = options.postdata; } else if (options.postdata instanceof Object) { var contentType = options.headers["Content-Type"]; if (contentType === undefined) { options.headers["Content-Type"] = Http.ContentType.FORM_URLENCODED; contentType = options.headers["Content-Type"]; } switch (contentType) { case Http.ContentType.FORM_URLENCODED: postdata = ""; var bFirstItem = true; for (var key in options.postdata) { if (options.postdata.hasOwnProperty(key)) { if (bFirstItem) { bFirstItem = false; } else { postdata += "&"; } postdata += escape(key) + "=" + escape(options.postdata[key]); } } break; default: case Http.ContentType.JSON: if (contentType == null) { options.headers["Content-Type"] = Http.ContentType.JSON; } postdata = JSON.stringify(options.postdata); break; } } else { postdata = options.postdata; } } if (options.cache === false) { timestamp = now(); uri = new URI(url); if (!uri.query) { uri.query = "ts=" + timestamp; } else { uri.query = uri.query + "&ts=" + timestamp; } url = uri.toString(); } if (options.query) { uri = new URI(url); query = extend(uri.getQuery(), options.query); uri.setQuery(query); url = uri.toString(); } xhr = new XMLHttpRequest(); xhr.open(method, url, options.async); xhr.withCredentials = options.withCredentials !== undefined ? options.withCredentials : false; xhr.responseType = options.responseType || this._guessResponseType(url); for (var header in options.headers) { if (options.headers.hasOwnProperty(header)) { xhr.setRequestHeader(header, options.headers[header]); } } xhr.onreadystatechange = function () { this._onReadyStateChange(method, url, options, xhr); }.bind(this); xhr.onerror = function () { this._onError(method, url, options, xhr); errored = true; }.bind(this); try { xhr.send(postdata); } catch (e) { if (!errored) { options.error(xhr.status, xhr, e); } } return xhr; } _guessResponseType(url) { var uri = new URI(url); var ext = path.getExtension(uri.path); if (Http.binaryExtensions.indexOf(ext) >= 0) { return Http.ResponseType.ARRAY_BUFFER; } if (ext === ".xml") { return Http.ResponseType.DOCUMENT; } return Http.ResponseType.TEXT; } _isBinaryContentType(contentType) { var binTypes = [Http.ContentType.MP4, Http.ContentType.WAV, Http.ContentType.OGG, Http.ContentType.MP3, Http.ContentType.BIN, Http.ContentType.DDS, Http.ContentType.BASIS, Http.ContentType.GLB]; if (binTypes.indexOf(contentType) >= 0) { return true; } return false; } _onReadyStateChange(method, url, options, xhr) { if (xhr.readyState === 4) { switch (xhr.status) { case 0: { if (xhr.responseURL && xhr.responseURL.startsWith('file:///')) { this._onSuccess(method, url, options, xhr); } else { this._onError(method, url, options, xhr); } break; } case 200: case 201: case 206: case 304: { this._onSuccess(method, url, options, xhr); break; } default: { this._onError(method, url, options, xhr); break; } } } } _onSuccess(method, url, options, xhr) { var response; var header; var contentType; var parts; header = xhr.getResponseHeader("Content-Type"); if (header) { parts = header.split(";"); contentType = parts[0].trim(); } try { if (contentType === this.ContentType.JSON || url.split('?')[0].endsWith(".json")) { response = JSON.parse(xhr.responseText); } else if (this._isBinaryContentType(contentType)) { response = xhr.response; } else { if (contentType) { console.warn("responseType: " + xhr.responseType + " being served with Content-Type: " + contentType); } if (xhr.responseType === Http.ResponseType.ARRAY_BUFFER) { response = xhr.response; } else if (xhr.responseType === Http.ResponseType.BLOB || xhr.responseType === Http.ResponseType.JSON) { response = xhr.response; } else { if (xhr.responseType === Http.ResponseType.DOCUMENT || contentType === this.ContentType.XML) { response = xhr.responseXML; } else { response = xhr.responseText; } } } options.callback(null, response); } catch (err) { options.callback(err); } } _onError(method, url, options, xhr) { if (options.retrying) { return; } if (options.retry && options.retries < options.maxRetries) { options.retries++; options.retrying = true; var retryDelay = math.clamp(Math.pow(2, options.retries) * Http.retryDelay, 0, options.maxRetryDelay || 5000); console.log(method + ': ' + url + ' - Error ' + xhr.status + '. Retrying in ' + retryDelay + ' ms'); setTimeout(function () { options.retrying = false; this.request(method, url, options, options.callback); }.bind(this), retryDelay); } else { options.callback(xhr.status === 0 ? 'Network error' : xhr.status, null); } } } Http.ContentType = { FORM_URLENCODED: "application/x-www-form-urlencoded", GIF: "image/gif", JPEG: "image/jpeg", DDS: "image/dds", JSON: "application/json", PNG: "image/png", TEXT: "text/plain", XML: "application/xml", WAV: "audio/x-wav", OGG: "audio/ogg", MP3: "audio/mpeg", MP4: "audio/mp4", AAC: "audio/aac", BIN: "application/octet-stream", BASIS: "image/basis", GLB: "model/gltf-binary" }; Http.ResponseType = { TEXT: 'text', ARRAY_BUFFER: 'arraybuffer', BLOB: 'blob', DOCUMENT: 'document', JSON: 'json' }; Http.binaryExtensions = ['.model', '.wav', '.ogg', '.mp3', '.mp4', '.m4a', '.aac', '.dds', '.basis', '.glb']; Http.retryDelay = 100; const http = new Http(); const CURVE_LINEAR = 0; const CURVE_SMOOTHSTEP = 1; const CURVE_CATMULL = 2; const CURVE_CARDINAL = 3; const CURVE_SPLINE = 4; const CURVE_STEP = 5; class Color { constructor(r = 0, g = 0, b = 0, a = 1) { const length = r.length; if (length === 3 || length === 4) { this.r = r[0]; this.g = r[1]; this.b = r[2]; this.a = r[3] !== undefined ? r[3] : 1; } else { this.r = r; this.g = g; this.b = b; this.a = a; } } clone() { return new Color(this.r, this.g, this.b, this.a); } copy(rhs) { this.r = rhs.r; this.g = rhs.g; this.b = rhs.b; this.a = rhs.a; return this; } equals(rhs) { return this.r === rhs.r && this.g === rhs.g && this.b === rhs.b && this.a === rhs.a; } set(r, g, b, a = 1) { this.r = r; this.g = g; this.b = b; this.a = a; return this; } lerp(lhs, rhs, alpha) { this.r = lhs.r + alpha * (rhs.r - lhs.r); this.g = lhs.g + alpha * (rhs.g - lhs.g); this.b = lhs.b + alpha * (rhs.b - lhs.b); this.a = lhs.a + alpha * (rhs.a - lhs.a); return this; } fromString(hex) { const i = parseInt(hex.replace('#', '0x'), 16); let bytes; if (hex.length > 7) { bytes = math.intToBytes32(i); } else { bytes = math.intToBytes24(i); bytes[3] = 255; } this.set(bytes[0] / 255, bytes[1] / 255, bytes[2] / 255, bytes[3] / 255); return this; } toString(alpha) { let s = "#" + ((1 << 24) + (Math.round(this.r * 255) << 16) + (Math.round(this.g * 255) << 8) + Math.round(this.b * 255)).toString(16).slice(1); if (alpha === true) { const a = Math.round(this.a * 255).toString(16); if (this.a < 16 / 255) { s += '0' + a; } else { s += a; } } return s; } } Color.BLACK = Object.freeze(new Color(0, 0, 0, 1)); Color.BLUE = Object.freeze(new Color(0, 0, 1, 1)); Color.CYAN = Object.freeze(new Color(0, 1, 1, 1)); Color.GRAY = Object.freeze(new Color(0.5, 0.5, 0.5, 1)); Color.GREEN = Object.freeze(new Color(0, 1, 0, 1)); Color.MAGENTA = Object.freeze(new Color(1, 0, 1, 1)); Color.RED = Object.freeze(new Color(1, 0, 0, 1)); Color.WHITE = Object.freeze(new Color(1, 1, 1, 1)); Color.YELLOW = Object.freeze(new Color(1, 1, 0, 1)); class CurveEvaluator { constructor(curve, time) { this._curve = curve; this._left = -Infinity; this._right = Infinity; this._recip = 0; this._p0 = 0; this._p1 = 0; this._m0 = 0; this._m1 = 0; this._reset(time || 0); } evaluate(time, forceReset) { if (forceReset || time < this._left || time >= this._right) { this._reset(time); } let result; const type = this._curve.type; if (type === CURVE_STEP) { result = this._p0; } else { const t = this._recip === 0 ? 0 : (time - this._left) * this._recip; if (type === CURVE_LINEAR) { result = math.lerp(this._p0, this._p1, t); } else if (type === CURVE_SMOOTHSTEP) { result = math.lerp(this._p0, this._p1, t * t * (3 - 2 * t)); } else { result = this._evaluateHermite(this._p0, this._p1, this._m0, this._m1, t); } } return result; } _reset(time) { const keys = this._curve.keys; const len = keys.length; if (!len) { this._left = -Infinity; this._right = Infinity; this._recip = 0; this._p0 = this._p1 = this._m0 = this._m1 = 0; } else { if (time < keys[0][0]) { this._left = -Infinity; this._right = keys[0][0]; this._recip = 0; this._p0 = this._p1 = keys[0][1]; this._m0 = this._m1 = 0; } else if (time >= keys[len - 1][0]) { this._left = keys[len - 1][0]; this._right = Infinity; this._recip = 0; this._p0 = this._p1 = keys[len - 1][1]; this._m0 = this._m1 = 0; } else { let index = 0; while (time >= keys[index + 1][0]) { index++; } this._left = keys[index][0]; this._right = keys[index + 1][0]; const diff = 1.0 / (this._right - this._left); th