UNPKG

onsight

Version:

Interactive, easy to use JavaScript game framework.

1,504 lines (1,493 loc) 259 kB
/** * @description Onsight Engine * @about Interactive, easy to use JavaScript game framework. * @author Stephens Nunnally <@stevinz> * @version v0.0.8 * @license MIT - Copyright (c) 2024 Stephens Nunnally * @source https://github.com/onsightengine/onsight */ var name = "onsight"; var version = "0.0.8"; var description = "Interactive, easy to use JavaScript game framework."; var module = "src/Onsight.js"; var main = "dist/onsight.module.js"; var type = "module"; var scripts = { build: "rollup -c", prepublishOnly: "npm run build" }; var files = [ "dist/*", "files/*", "src/*" ]; var keywords = [ "onsight", "game", "engine", "webgl", "javascript", "graphics", "framework", "canvas" ]; var repository = { type: "git", url: "git+https://github.com/onsightengine/onsight.git" }; var author = "Stephens Nunnally <stephens@scidian.com>"; var license = "MIT"; var bugs = { url: "https://github.com/onsightengine/onsight/issues" }; var homepage = "https://github.com/onsightengine"; var publishConfig = { access: "public", registry: "https://registry.npmjs.org/" }; var devDependencies = { "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-terser": "^0.4.4", "rollup-plugin-cleanup": "^3.2.1", "rollup-plugin-visualizer": "^5.12.0" }; var pkg = { name: name, version: version, description: description, module: module, main: main, type: type, scripts: scripts, files: files, keywords: keywords, repository: repository, author: author, license: license, bugs: bugs, homepage: homepage, "private": false, publishConfig: publishConfig, devDependencies: devDependencies }; const VERSION = pkg.version; const APP_SIZE = 1000; const MOUSE_CLICK_TIME = 350; const MOUSE_DOUBLE_TIME = 500; const MOUSE_SLOP = 2; const OUTLINE_THICKNESS = 2; const APP_EVENTS = [ 'init', 'update', 'destroy', 'keydown', 'keyup', 'pointerdown', 'pointerup', 'pointermove', ]; const APP_ORIENTATION = { PORTRAIT: 'portrait', LANDSCAPE: 'landscape', }; const STAGE_TYPES = { STAGE_2D: 'Stage2D', STAGE_3D: 'Stage3D', STAGE_UI: 'StageUI', }; const WORLD_TYPES = { WORLD_2D: 'World2D', WORLD_3D: 'World3D', WORLD_UI: 'WorldUI', }; const SCRIPT_FORMAT = { JAVASCRIPT: 'javascript', PYTHON: 'python', }; class ArrayUtils { static isIterable(array) { return (array && (typeof array[Symbol.iterator] === 'function') || Array.isArray(array)); } static swapItems(array, a, b) { array[a] = array.splice(b, 1, array[a])[0]; return array; } static combineThingArrays(arrayOne, arrayTwo) { const things = [ ...arrayOne ]; for (const thing of arrayTwo) { if (ArrayUtils.includesThing(thing, arrayOne) === false) things.push(thing); } return things; } static compareThingArrays(arrayOne, arrayTwo) { arrayOne = Array.isArray(arrayOne) ? arrayOne : [ arrayOne ]; arrayTwo = Array.isArray(arrayTwo) ? arrayTwo : [ arrayTwo ]; if (arrayOne.length === 0 && arrayTwo.length === 0) return true; for (const thing of arrayOne) if (ArrayUtils.includesThing(thing, arrayTwo) === false) return false; for (const thing of arrayTwo) if (ArrayUtils.includesThing(thing, arrayOne) === false) return false; return true; } static filterThings(things, properties = {}) { const filtered = things.filter((object) => { return Object.keys(properties).every((key) => { return object[key] == properties[key]; }); }); return filtered; } static includesThing(findThing, ...things) { if (!findThing || !findThing.uuid) return false; if (things.length === 0) return false; if (things.length > 0 && Array.isArray(things[0])) things = things[0]; for (const thing of things) if (thing.uuid && thing.uuid === findThing.uuid) return true; return false; } static removeThingFromArray(removeThing, ...things) { if (things.length > 0 && Array.isArray(things[0])) things = things[0]; if (!removeThing || !removeThing.uuid) return [ ...things ]; const newArray = []; for (const thing of things) if (thing.uuid !== removeThing.uuid) newArray.push(thing); return newArray; } static shareValues(arrayOne, arrayTwo) { for (let i = 0; i < arrayOne.length; i++) { if (arrayTwo.includes(arrayOne[i])) return true; } return false; } } const _assets = {}; class AssetManager { static get(uuid) { if (uuid && uuid.uuid) uuid = uuid.uuid; return _assets[uuid]; } static library(type, category) { const library = []; if (type && typeof type === 'string') type = type.toLowerCase(); if (category && typeof category === 'string') category = category.toLowerCase(); for (const [ uuid, asset ] of Object.entries(_assets)) { if (type && typeof asset.type === 'string' && asset.type.toLowerCase() !== type) continue; if (category == undefined || (typeof asset.category === 'string' && asset.category.toLowerCase() === category)) { library.push(asset); } } return library; } static add(...assets) { if (assets.length > 0 && Array.isArray(assets[0])) assets = assets[0]; let addedAsset = undefined; for (const asset of assets) { if (!asset || !asset.uuid) continue; if (!asset.name || asset.name === '') asset.name = asset.constructor.name; _assets[asset.uuid] = asset; addedAsset = addedAsset ?? asset; } return addedAsset; } static clear() { for (const uuid in _assets) { const asset = _assets[uuid]; if (asset.isBuiltIn) continue; AssetManager.remove(_assets[uuid], true); } } static remove(asset, dispose = true) { const assets = Array.isArray(asset) ? asset : [ asset ]; for (const asset of assets) { if (!asset || !asset.uuid) continue; if (_assets[asset.uuid]) { if (dispose && typeof asset.dispose === 'function') asset.dispose(); delete _assets[asset.uuid]; } } } static toJSON() { const data = {}; for (const type of _types$2.keys()) { const assets = AssetManager.library(type); if (assets.length > 0) { data[type] = []; for (const asset of assets) { data[type].push(asset.toJSON()); } } } return data; } static fromJSON(json, onLoad = () => {}) { AssetManager.clear(); for (const type of _types$2.keys()) { if (!json[type]) continue; for (const assetData of json[type]) { const Constructor = AssetManager.type(type); if (Constructor) { const asset = new Constructor().fromJSON(assetData); AssetManager.add(asset); } else { console.warn(`AssetManager.fromJSON(): Unknown asset type '${assetData.type}'`); } } } if (typeof onLoad === 'function') onLoad(); } static register(type, AssetClass) { _types$2.set(type, AssetClass); } static type(type) { return _types$2.get(type); } } const _types$2 = new Map(); class Clock { #running = false; #startTime = 0; #elapsedTime = 0; #lastChecked = 0; #deltaCount = 0; #frameTime = 0; #frameCount = 0; #lastFrameCount = null; constructor(autoStart = true, msRewind = 0) { if (autoStart) this.start(); this.#startTime -= msRewind; this.#lastChecked -= msRewind; } start(reset = false) { if (reset) this.reset(); this.#startTime = performance.now(); this.#lastChecked = this.#startTime; this.#running = true; } stop() { this.getDeltaTime(); this.#running = false; } toggle() { if (this.#running) this.stop(); else this.start(); } reset() { this.#startTime = performance.now(); this.#lastChecked = this.#startTime; this.#elapsedTime = 0; this.#deltaCount = 0; } getElapsedTime() { return this.#elapsedTime; } getDeltaTime() { if (!this.#running) { this.#lastFrameCount = null; return 0; } const newTime = performance.now(); const dt = (newTime - this.#lastChecked) / 1000; this.#lastChecked = newTime; this.#elapsedTime += dt; this.#deltaCount++; this.#frameTime += dt; this.#frameCount++; if (this.#frameTime > 1) { this.#lastFrameCount = this.#frameCount; this.#frameTime = 0; this.#frameCount = 0; } return dt; } isRunning() { return this.#running; } isStopped() { return !(this.#running); } count() { return this.#deltaCount; } averageDelta() { const frameRate = (this.#lastFrameCount !== null) ? (1 / this.#lastFrameCount) : (this.#frameTime / this.#frameCount); return Math.min(1, frameRate); } fps() { return (this.#lastFrameCount !== null) ? this.#lastFrameCount : (this.#frameCount / this.#frameTime); } } const EPSILON = 0.000001; class Vector3 { constructor(x = 0, y = 0, z = 0) { if (typeof x === 'object') { this.x = x.x; this.y = x.y; this.z = x.z; } else { this.x = x; this.y = y; this.z = z; } } set(x, y, z) { if (typeof x === 'object') return this.copy(x); this.x = x; this.y = y; this.z = z; return this; } setScalar(scalar) { this.x = scalar; this.y = scalar; this.z = scalar; return this; } clone() { return new Vector3(this.x, this.y, this.z); } copy(x, y, z) { if (typeof x === 'object') { this.x = x.x; this.y = x.y; this.z = x.z; } else { this.x = x; this.y = y; this.z = z; } return this; } add(x, y, z) { if (typeof x === 'object') { this.x += x.x; this.y += x.y; this.z += x.z; } else { this.x += x; this.y += y; this.z += z; } return this; } addScalar(scalar) { this.x += scalar; this.y += scalar; this.z += scalar; return this; } addVectors(a, b) { this.x = a.x + b.x; this.y = a.y + b.y; this.z = a.z + b.z; return this; } addScaledVector(vec, scale) { this.x += vec.x * scale; this.y += vec.y * scale; this.z += vec.z * scale; return this; } sub(x, y, z) { if (typeof x === 'object') { this.x -= x.x; this.y -= x.y; this.z -= x.z; } else { this.x -= x; this.y -= y; this.z -= z; } return this; } subScalar(scalar) { this.x -= scalar; this.y -= scalar; this.z -= scalar; return this; } subVectors(a, b) { this.x = a.x - b.x; this.y = a.y - b.y; this.z = a.z - b.z; return this; } multiply(x, y, z) { if (typeof x === 'object') { this.x *= x.x; this.y *= x.y; this.z *= x.z; } else { this.x *= x; this.y *= y; this.z *= z; } return this; } multiplyScalar(scalar) { this.x *= scalar; this.y *= scalar; this.z *= scalar; return this; } divide(x, y) { if (typeof x === 'object') { this.x /= x.x; this.y /= x.y; this.z /= x.z; } else { this.x /= x; this.y /= y; this.z /= z; } return this; } divideScalar(scalar) { return this.multiplyScalar(1 / scalar); } min(vec) { this.x = Math.min(this.x, vec.x); this.y = Math.min(this.y, vec.y); this.z = Math.min(this.z, vec.z); return this; } max(vec) { this.x = Math.max(this.x, vec.x); this.y = Math.max(this.y, vec.y); this.z = Math.max(this.z, vec.z); return this; } clamp(minv, maxv) { if (minv.x < maxv.x) this.x = Math.max(minv.x, Math.min(maxv.x, this.x)); else this.x = Math.max(maxv.x, Math.min(minv.x, this.x)); if (minv.y < maxv.y) this.y = Math.max(minv.y, Math.min(maxv.y, this.y)); else this.y = Math.max(maxv.y, Math.min(minv.y, this.y)); if (minv.z < maxv.z) this.z = Math.max(minv.z, Math.min(maxv.z, this.z)); else this.z = Math.max(maxv.z, Math.min(minv.z, this.z)); return this; } clampScalar(minVal, maxVal) { this.x = Math.max(minVal, Math.min(maxVal, this.x)); this.y = Math.max(minVal, Math.min(maxVal, this.y)); this.z = Math.max(minVal, Math.min(maxVal, this.z)); return this; } clampLength(min, max) { const length = this.length(); return this.divideScalar(length || 1).multiplyScalar(Math.max(min, Math.min(max, length))); } floor() { this.x = Math.floor(this.x); this.y = Math.floor(this.y); this.z = Math.floor(this.z); return this; } ceil() { this.x = Math.ceil(this.x); this.y = Math.ceil(this.y); this.z = Math.ceil(this.z); return this; } round() { this.x = Math.round(this.x); this.y = Math.round(this.y); this.z = Math.round(this.z); return this; } negate() { this.x = -this.x; this.y = -this.y; this.z = -this.z; return this; } abs() { this.x = Math.abs(this.x); this.y = Math.abs(this.y); this.z = Math.abs(this.z); return this; } dot(vec) { return this.x * vec.x + this.y * vec.y + this.z * vec.z; } cross(vec) { return this.crossVectors(this, vec); } crossVectors(a, b) { const ax = a.x, ay = a.y, az = a.z; const bx = b.x, by = b.y, bz = b.z; this.x = ay * bz - az * by; this.y = az * bx - ax * bz; this.z = ax * by - ay * bx; return this; } length() { return Math.sqrt(this.lengthSq()); } lengthSq() { return this.x * this.x + this.y * this.y + this.z * this.z; } manhattanLength() { return Math.abs(this.x) + Math.abs(this.y) + Math.abs(this.z); } normalize() { return this.divideScalar(this.length() || 1); } angle(vec) { _temp1.copy(this).normalize(); _temp2.copy(vec).normalize(); const cosine = _temp1.dot(_temp2); if (cosine > 1.0) return 0; if (cosine < -1.0) return Math.PI; return Math.acos(cosine); } distanceTo(vec) { return Math.sqrt(this.distanceToSquared(vec)); } distanceToSquared(vec) { const dx = this.x - vec.x; const dy = this.y - vec.y; const dz = this.z - vec.z; return dx * dx + dy * dy + dz * dz; } manhattanDistanceTo(vec) { return Math.abs(this.x - vec.x) + Math.abs(this.y - vec.y) + Math.abs(this.z - vec.z); } lerp(vec, t) { return this.lerpVectors(this, vec, t); } lerpVectors(a, b, t) { this.x = a.x + ((b.x - a.x) * t); this.y = a.y + ((b.y - a.y) * t); this.z = a.z + ((b.z - a.z) * t); return this; } applyMatrix3(mat3) { this.x = this.x * mat3[0] + this.y * mat3[3] + this.z * mat3[6]; this.y = this.x * mat3[1] + this.y * mat3[4] + this.z * mat3[7]; this.z = this.x * mat3[2] + this.y * mat3[5] + this.z * mat3[8]; return this; } applyMatrix4(mat3) { const m = mat3.m; if (!m) return this; let x = this.x; let y = this.y; let z = this.z; let w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0; this.x = (m[0] * x + m[4] * y + m[ 8] * z + m[12]) / w; this.y = (m[1] * x + m[5] * y + m[ 9] * z + m[13]) / w; this.z = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w; return this; } scaleRotateMatrix4(mat4) { const m = mat4.m; if (!m) return this; let x = this.x; let y = this.y; let z = this.z; let w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0; this.x = (m[0] * x + m[4] * y + m[ 8] * z) / w; this.y = (m[1] * x + m[5] * y + m[ 9] * z) / w; this.z = (m[2] * x + m[6] * y + m[10] * z) / w; return this; } applyQuaternion(q) { let x = this.x; let y = this.y; let z = this.z; let qx = q[0]; let qy = q[1]; let qz = q[2]; let qw = q[3]; let uvx = qy * z - qz * y; let uvy = qz * x - qx * z; let uvz = qx * y - qy * x; let uuvx = qy * uvz - qz * uvy; let uuvy = qz * uvx - qx * uvz; let uuvz = qx * uvy - qy * uvx; let w2 = qw * 2; uvx *= w2; uvy *= w2; uvz *= w2; uuvx *= 2; uuvy *= 2; uuvz *= 2; this.x = x + uvx + uuvx; this.y = y + uvy + uuvy; this.z = z + uvz + uuvz; return this; } transformDirection(mat4) { const x = this.x; const y = this.y; const z = this.z; this.x = mat4[0] * x + mat4[4] * y + mat4[ 8] * z; this.y = mat4[1] * x + mat4[5] * y + mat4[ 9] * z; this.z = mat4[2] * x + mat4[6] * y + mat4[10] * z; return this.normalize(); } calculateNormal(target = new Vector3(), a, b, c) { _temp1.subVectors(a, b); target.subVectors(b, c); target.cross(_temp1); target.normalize(); return target; } equals(vec) { return ((vec.x === this.x) && (vec.y === this.y) && (vec.z === this.z)); } fuzzyEquals(vec, tolerance = 0.001) { if (fuzzyFloat$1(this.x, vec.x, tolerance) === false) return false; if (fuzzyFloat$1(this.y, vec.y, tolerance) === false) return false; if (fuzzyFloat$1(this.z, vec.z, tolerance) === false) return false; return true; } random() { this.x = Math.random(); this.y = Math.random(); this.z = Math.random(); } log(description = '') { if (description !== '') description += ' - '; console.log(`${description}X: ${this.x}, Y: ${this.y}, Z: ${this.z}`); return this; } toArray() { return [ this.x, this.y, this.z ]; } fromArray(array, offset = 0) { this.set(array[offset + 0], array[offset + 1], array[offset + 2]); return this; } } const _temp1 = new Vector3(); const _temp2 = new Vector3(); function fuzzyFloat$1(a, b, tolerance = 0.001) { return ((a < (b + tolerance)) && (a > (b - tolerance))); } const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ]; const v0 = new Vector3(); const v1 = new Vector3(); const vc = new Vector3(); class MathUtils { static radiansToDegrees(radians) { return radians * (180 / Math.PI); } static degreesToRadians(degrees) { return (Math.PI / 180) * degrees; } static equalizeAngle0to360(angle, degrees = true) { let equalized = (degrees) ? angle : MathUtils.radiansToDegrees(angle); while (equalized < 0) { equalized += 360; } while (equalized >= 360) { equalized -= 360; } return (degrees) ? equalized : MathUtils.degreesToRadians(equalized); } static clamp(number, min, max) { number = Number(number); if (number < min) number = min; if (number > max) number = max; return number; } static roundTo(number, decimalPlaces = 0) { const shift = Math.pow(10, decimalPlaces); return Math.round(number * shift) / shift; } static damp(a, b, lambda, dt) { return MathUtils.lerp(a, b, 1 - Math.exp(-lambda * dt)); } static lerp(a, b, t) { return (1 - t) * a + t * b; } static smoothstep(a, b, t) { t = 3 * t * t - 2 * t * t * t; t = Math.min(1, Math.max(0, t)); return a + ((b - a) * t); } static smootherstep(a, b, t) { t = t * t * t * (t * (t * 6 - 15) + 10); t = Math.min(1, Math.max(0, t)); return a + ((b - a) * t); } static fuzzyFloat(a, b, tolerance = 0.001) { return ((a < (b + tolerance)) && (a > (b - tolerance))); } static fuzzyVector(a, b, tolerance = 0.001) { if (MathUtils.fuzzyFloat(a.x, b.x, tolerance) === false) return false; if (MathUtils.fuzzyFloat(a.y, b.y, tolerance) === false) return false; if (('z' in a) && ('z' in b) && MathUtils.fuzzyFloat(a.z, b.z, tolerance) === false) return false; if (('w' in a) && ('w' in b) && MathUtils.fuzzyFloat(a.w, b.w, tolerance) === false) return false; return true; } static isPowerOfTwo(value) { return (value & (value - 1)) === 0 && value !== 0; } static addCommas(number) { return number.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); } static countDecimals(number) { if (Math.floor(number.valueOf()) === number.valueOf()) return 0; return number.toString().split('.')[1].length || 0; } static isNumber(number) { return (number != null && typeof number === 'number' && Number.isFinite(number)); } static noZero(number, min = 0.00001) { min = Math.abs(min); number = MathUtils.sanity(number); if (number >= 0 && number < min) number = min; if (number < 0 && number > min * -1.0) number = min * -1.0; return number; } static sanity(number) { if (MathUtils.isNumber(number)) return number; return 0; } static lineCollision(x1, y1, x2, y2, x3, y3, x4, y4) { let denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1)); if (MathUtils.fuzzyFloat(denom, 0, 0.0000001)) return false; let ua = (((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3))) / denom; let ub = (((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3))) / denom; if ((ua >= 0) && (ua <= 1) && (ub >= 0) && (ub <= 1)) { return true; } return false; } static lineRectCollision(x1, y1, x2, y2, left, top, right, down) { const rectLeft = MathUtils.lineCollision(x1, y1, x2, y2, left, top, left, down); const rectRight = MathUtils.lineCollision(x1, y1, x2, y2, right, top, right, down); const rectTop = MathUtils.lineCollision(x1, y1, x2, y2, left, top, right, top); const rectDown = MathUtils.lineCollision(x1, y1, x2, y2, left, down, right, down); return (rectLeft || rectRight || rectTop || rectDown); } static triangleArea(a, b, c) { v0.subVectors(c, b); v1.subVectors(a, b); vc.crossVectors(v0, v1); return (vc.length() * 0.5); } static randomFloat(min, max) { return min + Math.random() * (max - min); } static randomInt(min = 0, max = 1) { return min + Math.floor(Math.random() * (max - min)); } static randomUUID() { if (window.crypto && window.crypto.randomUUID) return crypto.randomUUID(); const d0 = Math.random() * 0xffffffff | 0; const d1 = Math.random() * 0xffffffff | 0; const d2 = Math.random() * 0xffffffff | 0; const d3 = Math.random() * 0xffffffff | 0; const uuid = _lut[d0 & 0xff] + _lut[d0 >> 8 & 0xff] + _lut[d0 >> 16 & 0xff] + _lut[d0 >> 24 & 0xff] + '-' + _lut[d1 & 0xff] + _lut[d1 >> 8 & 0xff] + '-' + _lut[d1 >> 16 & 0x0f | 0x40] + _lut[d1 >> 24 & 0xff] + '-' + _lut[d2 & 0x3f | 0x80] + _lut[d2 >> 8 & 0xff] + '-' + _lut[d2 >> 16 & 0xff] + _lut[d2 >> 24 & 0xff] + _lut[d3 & 0xff] + _lut[d3 >> 8 & 0xff] + _lut[d3 >> 16 & 0xff] + _lut[d3 >> 24 & 0xff]; return uuid.toLowerCase(); } static toUUIDArray(...objects) { if (objects.length > 0 && Array.isArray(objects[0])) objects = objects[0]; const uuids = []; for (const object of objects) { if (typeof object === 'object') { if (object.uuid) uuids.push(object.uuid); } } return uuids; } } class SysUtils { static isObject(variable) { return (variable && typeof variable === 'object' && !Array.isArray(variable)); } static save(url, filename) { try { const link = document.createElement('a'); link.href = url; link.download = filename || 'data.json'; link.click(); setTimeout(function() { window.URL.revokeObjectURL(url); }, 0); } catch (error) { return console.warn(error); } } static saveBuffer(buffer, filename, optionalType = { type: 'application/octet-stream' }) { const url = URL.createObjectURL(new Blob([ buffer ], { type: optionalType })); SysUtils.save(url, filename); } static saveImage(imageUrl, filename) { SysUtils.save(imageUrl, filename); } static saveString(text, filename) { const url = URL.createObjectURL(new Blob([ text ], { type: 'text/plain' })); SysUtils.save(url, filename); } static detectOS() { const systems = { Android: [ 'android' ], iOS: [ 'iphone', 'ipad', 'ipod', 'ios' ], Linux: [ 'linux', 'x11', 'wayland' ], MacOS: [ 'mac', 'darwin', 'osx', 'os x' ], Windows: [ 'win' ], }; const userAgent = window.navigator.userAgent; const userAgentData = window.navigator.userAgentData; const platform = ((userAgentData) ? userAgentData.platform : userAgent).toLowerCase(); for (const key in systems) { for (const os of systems[key]) { if (platform.indexOf(os) !== -1) return key; } } return 'Unknown OS'; } static fullscreen(element) { const isFullscreen = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement; if (isFullscreen) { const el = document; const cancelMethod = el.cancelFullScreen || el.exitFullscreen || el.webkitCancelFullScreen || el.webkitExitFullscreen || el.mozCancelFullScreen; cancelMethod.call(el); } else { const el = element ?? document.body; const requestMethod = el.requestFullScreen || el.webkitRequestFullScreen || el.mozRequestFullScreen || el.msRequestFullScreen; requestMethod.call(el); } } static metaKeyOS() { const system = SysUtils.detectOS(); if (system === 'Mac') { return '⌘'; } else { return '⌃'; } } static sleep(ms) { const beginTime = performance.now(); let endTime = beginTime; while (endTime - beginTime < ms) { endTime = performance.now(); } } static waitForObject( operationName = '', getter, callback, checkFrequencyMs = 100, timeoutMs = -1, alertMs = 5000, ) { let startTimeMs = performance.now(); let alertTimeMs = performance.now(); function loopSearch() { if (timeoutMs > 0 && (performance.now() - startTimeMs > timeoutMs)) { console.info(`Operation: ${operationName} timed out`); return; } if ((alertMs > 0) && performance.now() - alertTimeMs > alertMs) { console.info(`Still waiting on operation: ${operationName}`); alertTimeMs = performance.now(); } if (!getter || typeof getter !== 'function' || getter()) { if (callback && typeof callback === 'function') callback(); return; } else { setTimeout(loopSearch, checkFrequencyMs); } } loopSearch(); } } const _types$1 = new Map(); class Thing { constructor(name = 'Thing') { this.isThing = true; this.type = 'Thing'; this.name = name; this.uuid = MathUtils.randomUUID(); } clone(recursive = false) { return new this.constructor().copy(this, recursive); } copy(source, recursive = true) { this.dispose(); this.name = source.name; return this; } toJSON() { const data = {}; data.meta = { type: this.type, version: VERSION, }; data.name = this.name; data.uuid = this.uuid; return data; } fromJSON(data) { if (!SysUtils.isObject(data)) { console.warn(`Thing.fromJSON(): No json data provided for ${this.constructor.name}`); return this; } if (data.name !== undefined) this.name = data.name; if (data.uuid !== undefined) this.uuid = data.uuid; return this; } static register(type, ThingClass) { _types$1.set(type, ThingClass); } static type(type) { return _types$1.get(type); } } Thing.register('Thing', Thing); const _registered = {}; class ComponentManager { static defaultValue(type) { switch (type) { case 'select': return null; case 'number': return 0; case 'int': return 0; case 'angle': return 0; case 'slider': return 0; case 'variable': return [ 0, 0 ]; case 'vector': return [ 0 ]; case 'option': return [ false ]; case 'boolean': return false; case 'color': return 0xffffff; case 'string': return ''; case 'key': return ''; case 'asset': return null; case 'object': return {}; case 'divider': return null; default: console.warn(`ComponentManager.defaultValue(): Unknown property type: '${type}'`); } return null; } static registered(type = '') { const ComponentClass = _registered[type]; if (!ComponentClass) console.warn(`ComponentManager.registered(): Component '${type}' not registered'`); return ComponentClass; } static registeredTypes() { return Object.keys(_registered); } static register(type = '', ComponentClass) { type = type.toLowerCase(); if (_registered[type]) return console.warn(`ComponentManager.register(): Component '${type}' already registered`); if (!SysUtils.isObject(ComponentClass.config)) ComponentClass.config = {}; if (!SysUtils.isObject(ComponentClass.config.schema)) ComponentClass.config.schema = {}; const schema = ComponentClass.config.schema; for (const key in schema) { const properties = Array.isArray(schema[key]) ? schema[key] : [ schema[key] ]; for (const property of properties) { if (property.type === undefined) { console.warn(`ComponentManager.register(): All schema properties require a 'type' value`); } else if (property.type === 'divider') { continue; } if (property.default === undefined) property.default = ComponentManager.defaultValue(property.type); if (property.proMode !== undefined) property.promode = property.proMode; } } class Component extends ComponentClass { constructor() { super(); this.isComponent = true; this.type = type; this.attached = true; this.expanded = true; this.order = 0; this.tag = ''; this.entity = null; this.backend = undefined; this.data = {}; } init(data = {}) { this.dispose(); if (typeof super.init === 'function') super.init(data); } dispose() { if (typeof super.dispose === 'function') super.dispose(); if (typeof this.backend === 'object' && typeof this.backend.dispose === 'function') this.backend.dispose(); this.backend = undefined; } attach() { this.attached = true; if (typeof super.attach === 'function') super.attach(); } detach() { this.attached = false; if (typeof super.detach === 'function') super.detach(); } defaultData() { const data = {}; for (let i = 0, l = arguments.length; i < l; i += 2) { data[arguments[i]] = arguments[i + 1]; } ComponentManager.sanitizeData(this.type, data); data.base = { isComponent: true, attached: this.attached, expanded: this.expanded, order: this.order, tag: this.tag, type: this.type, }; return data; } toJSON() { let data; if (this.data && this.data.style) { data = this.defaultData('style', this.data.style); } else { data = this.defaultData(); } for (const key in data) { if (this.data[key] !== undefined) { if (this.data[key] && this.data[key].isTexture) { data[key] = this.data[key].uuid; } else { data[key] = structuredClone(this.data[key]); } } } return data; } } _registered[type] = Component; } static includeData(item, data1, data2 = undefined) { for (const key in item.if) { const conditions = Array.isArray(item.if[key]) ? item.if[key] : [ item.if[key] ]; let check1 = false, check2 = false; for (const condition of conditions) { check1 = check1 || (data1[key] === condition); check2 = check2 || (data2 === undefined) ? true : (data2[key] === condition); } if (!check1 || !check2) return false; } for (const key in item.not) { const conditions = Array.isArray(item.not[key]) ? item.not[key] : [ item.not[key] ]; let check1 = false, check2 = false; for (const condition of conditions) { check1 = check1 || (data1[key] === condition); check2 = check2 || (data2 === undefined) ? false : (data2[key] === condition); } if (check1 || check2) return false; } return true; } static sanitizeData(type, data) { if (!data || typeof data !== 'object') data = {}; const ComponentClass = ComponentManager.registered(type); if (!ComponentClass || !ComponentClass.config || !ComponentClass.config.schema) return; const schema = ComponentClass.config.schema; if (!SysUtils.isObject(schema)) return; for (const schemaKey in schema) { const itemArray = Array.isArray(schema[schemaKey]) ? schema[schemaKey] : [ schema[schemaKey] ]; let itemToInclude = undefined; for (const item of itemArray) { if (item.type === 'divider') continue; if (!ComponentManager.includeData(item, data)) continue; itemToInclude = item; break; } if (itemToInclude !== undefined) { if (data[schemaKey] === undefined) { if (Array.isArray(itemToInclude.default)) { data[schemaKey] = [...itemToInclude.default]; } else if (typeof itemToInclude.default === 'object') { data[schemaKey] = structuredClone(itemToInclude.default); } else { data[schemaKey] = itemToInclude.default; } } if (MathUtils.isNumber(data[schemaKey])) { const min = itemToInclude['min'] ?? -Infinity; const max = itemToInclude['max'] ?? Infinity; if (data[schemaKey] < min) data[schemaKey] = min; if (data[schemaKey] > max) data[schemaKey] = max; } } else { delete data[schemaKey]; } } } static stripData(type, oldData, newData) { const ComponentClass = ComponentManager.registered(type); if (!ComponentClass || !ComponentClass.config || !ComponentClass.config.schema) return; const schema = ComponentClass.config.schema; if (!SysUtils.isObject(schema)) return; for (const schemaKey in schema) { let matchedConditions = false; const itemArray = Array.isArray(schema[schemaKey]) ? schema[schemaKey] : [ schema[schemaKey] ]; for (const item of itemArray) { if (item.type === 'divider') continue; if (!ComponentManager.includeData(item, oldData, newData)) continue; matchedConditions = true; break; } if (matchedConditions !== true) { delete newData[schemaKey]; } } } } class Vector2 { constructor(x = 0, y = 0) { if (typeof x === 'object') { this.x = x.x; this.y = x.y; } else { this.x = x; this.y = y; } } set(x, y) { if (typeof x === 'object') return this.copy(x); this.x = x; this.y = y; return this; } setScalar(scalar) { this.x = scalar; this.y = scalar; return this; } clone() { return new Vector2(this.x, this.y); } copy(x, y) { if (typeof x === 'object') { this.x = x.x; this.y = x.y; } else { this.x = x; this.y = y; } return this; } add(x, y) { if (typeof x === 'object') { this.x += x.x; this.y += x.y; } else { this.x += x; this.y += y; } return this; } addScalar(scalar) { this.x += scalar; this.y += scalar; return this; } addVectors(a, b) { this.x = a.x + b.x; this.y = a.y + b.y; return this; } addScaledVector(vec, scale) { this.x += vec.x * scale; this.y += vec.y * scale; return this; } sub(x, y) { if (typeof x === 'object') { this.x -= x.x; this.y -= x.y; } else { this.x -= x; this.y -= y; } return this; } subScalar(scalar) { this.x -= scalar; this.y -= scalar; return this; } subVectors(a, b) { this.x = a.x - b.x; this.y = a.y - b.y; return this; } multiply(x, y) { if (typeof x === 'object') { this.x *= x.x; this.y *= x.y; } else { this.x *= x; this.y *= y; } return this; } multiplyScalar(scalar) { this.x *= scalar; this.y *= scalar; return this; } divide(x, y) { if (typeof x === 'object') { this.x /= x.x; this.y /= x.y; } else { this.x /= x; this.y /= y; } return this; } divideScalar(scalar) { return this.multiplyScalar(1 / scalar); } min(vec) { this.x = Math.min(this.x, vec.x); this.y = Math.min(this.y, vec.y); return this; } max(vec) { this.x = Math.max(this.x, vec.x); this.y = Math.max(this.y, vec.y); return this; } clamp(minv, maxv) { if (minv.x < maxv.x) this.x = Math.max(minv.x, Math.min(maxv.x, this.x)); else this.x = Math.max(maxv.x, Math.min(minv.x, this.x)); if (minv.y < maxv.y) this.y = Math.max(minv.y, Math.min(maxv.y, this.y)); else this.y = Math.max(maxv.y, Math.min(minv.y, this.y)); return this; } clampScalar(minVal, maxVal) { this.x = Math.max(minVal, Math.min(maxVal, this.x)); this.y = Math.max(minVal, Math.min(maxVal, this.y)); return this; } clampLength(min, max) { const length = this.length(); return this.divideScalar(length || 1).multiplyScalar(Math.max(min, Math.min(max, length))); } floor() { this.x = Math.floor(this.x); this.y = Math.floor(this.y); return this; } ceil() { this.x = Math.ceil(this.x); this.y = Math.ceil(this.y); return this; } round() { this.x = Math.round(this.x); this.y = Math.round(this.y); return this; } negate() { this.x = -this.x; this.y = -this.y; return this; } abs() { this.x = Math.abs(this.x); this.y = Math.abs(this.y); return this; } dot(vec) { return this.x * vec.x + this.y * vec.y; } cross(vec) { return this.x * vec.y - this.y * vec.x; } length() { return Math.sqrt(this.x * this.x + this.y * this.y); } lengthSq() { return this.x * this.x + this.y * this.y; } manhattanLength() { return Math.abs(this.x) + Math.abs(this.y); } normalize() { return this.divideScalar(this.length() || 1); } angle(forcePositive) { let angle = Math.atan2(this.y, this.x); if (forcePositive && angle < 0) angle += 2 * Math.PI; return angle; } angleBetween(vec) { const magnitudes = this.length() * vec.length(); const dot = this.dot(vec); const theta = dot / magnitudes; const clampedDot = Math.min(Math.max(theta, -1), 1); return Math.acos(clampedDot); } distanceTo(vec) { return Math.sqrt(this.distanceToSquared(vec)); } distanceToSquared(vec) { const dx = this.x - vec.x; const dy = this.y - vec.y; return dx * dx + dy * dy; } manhattanDistanceTo(vec) { return Math.abs(this.x - vec.x) + Math.abs(this.y - vec.y); } setLength(length) { return this.normalize().multiplyScalar(length); } lerp(vec, t) { return this.lerpVectors(this, vec, t); } lerpVectors(a, b, t) { this.x = (a.x * (1.0 - t)) + (b.x * t); this.y = (a.y * (1.0 - t)) + (b.y * t); return this; } smoothstep(vec, t) { t = 3 * t * t - 2 * t * t * t; t = Math.min(1, Math.max(0, t)); this.x = this.x + ((vec.x - this.x) * t); this.y = this.y + ((vec.y - this.y) * t); return this; } equals(vec) { return ((vec.x === this.x) && (vec.y === this.y)); } fuzzyEquals(vec, tolerance = 0.001) { if (fuzzyFloat(this.x, vec.x, tolerance) === false) return false; if (fuzzyFloat(this.y, vec.y, tolerance) === false) return false; return true; } random() { this.x = Math.random(); this.y = Math.random(); } log(description = '') { if (description !== '') description += ' - '; console.log(`${description}X: ${this.x}, Y: ${this.y}`); return this; } toArray() { return [ this.x, this.y ]; } fromArray(array, offset = 0) { this.set(array[offset + 0], array[offset + 1]); return this; } } function fuzzyFloat(a, b, tolerance = 0.001) { return ((a < (b + tolerance)) && (a > (b - tolerance))); } const _clamp = new Vector2(); const _half = new Vector2(); class Box2 { constructor(min, max) { this.min = new Vector2(+Infinity, +Infinity); this.max = new Vector2(-Infinity, -Infinity); if (typeof min === 'object') this.min.copy(min); if (typeof max === 'object') this.max.copy(max); } set(min, max) { this.min.copy(min); this.max.copy(max); return this; } setFromPoints(...points) { this.clear(); if (points.length > 0 && Array.isArray(points[0])) points = points[0]; for (const point of points) this.expandByPoint(point); return this; } setFromCenterAndSize(center, size) { _half.copy(size).multiplyScalar(0.5); this.min.copy(center).sub(_half); this.max.copy(center).add(_half); return this; } clone() { return new Box2().copy(this); } copy(box) { this.min.copy(box.min); this.max.copy(box.max); return this; } clear() { this.min.set(+Infinity, +Infinity); this.max.set(-Infinity, -Infinity); } isEmpty() { return (this.max.x < this.min.x) || (this.max.y < this.min.y); } getCenter(target = new Vector2()) { this.isEmpty() ? target.set(0, 0) : target.addVectors(this.min, this.max).multiplyScalar(0.5); return target; } getSize(target = new Vector2()) { this.isEmpty() ? target.set(0, 0) : target.subVectors(this.max, this.min).abs(); return target; } expandByPoint(point) { this.min.min(point); this.max.max(point); return this; } expandByVector(vector, y) { let ex, ey; if (typeof vector === 'object') { ex = vector.x / 2; ey = vector.y / 2; } else { ex = vector / 2; ey = y / 2; } this.min.sub(ex, ey); this.max.add(ex, ey); return this; } expandByScalar(scalar) { this.min.addScalar(scalar * -1); this.max.addScalar(scalar * +1); return this; } multiply(x, y) { if (typeof x === 'object') { y = x.y; x = x.x; } this.min.multiply(x, y); this.max.multiply(x, y); return this; } containsPoint(point) { return !(point.x < this.min.x || point.x > this.max.x || point.y < this.min.y || point.y > this.max.y); } containsBox(box) { return this.min.x <= box.min.x && box.max.x <= this.max.x && this.min.y <= box.min.y && box.max.y <= this.max.y; } intersectsBox(box) { return !(box.max.x < this.min.x || box.min.x > this.max.x || box.max.y < this.min.y || box.min.y > this.max.y); } distanceToPoint(point) { _clamp.copy(point).clamp(this.min, this.max).sub(point); return _clamp.length(); } intersect(box) { this.min.max(box.min); this.max.min(box.max); return this; } union(box) { this.min.min(box.min); this.max.max(box.max); return this; } translate(x, y) { this.min.add(x, y); this.max.add(x, y); return this; } equals(box) { return box.min.equals(this.min) && box.max.equals(this.max); } toArray() { return [ this.min.x, this.min.y, this.max.x, this.max.y ]; } fromArray(array, offset = 0) { this.min.set(array[offset + 0], array[offset + 1