onsight
Version:
Interactive, easy to use JavaScript game framework.
1,504 lines (1,493 loc) • 259 kB
JavaScript
/**
* @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