@basementuniverse/particles-2d 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +5 -0
- package/.vscode/settings.json +17 -0
- package/LICENSE +7 -0
- package/README.md +347 -0
- package/build/index.d.ts +357 -0
- package/build/index.js +129 -0
- package/package.json +32 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
|
|
3
|
+
* This devtool is neither made for production nor for readable output files.
|
|
4
|
+
* It uses "eval()" calls to create a separate source file in the browser devtools.
|
|
5
|
+
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
|
6
|
+
* or disable the default devtool with "devtool: false".
|
|
7
|
+
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
|
8
|
+
*/
|
|
9
|
+
(function webpackUniversalModuleDefinition(root, factory) {
|
|
10
|
+
if(typeof exports === 'object' && typeof module === 'object')
|
|
11
|
+
module.exports = factory();
|
|
12
|
+
else if(typeof define === 'function' && define.amd)
|
|
13
|
+
define([], factory);
|
|
14
|
+
else {
|
|
15
|
+
var a = factory();
|
|
16
|
+
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
|
|
17
|
+
}
|
|
18
|
+
})(self, () => {
|
|
19
|
+
return /******/ (() => { // webpackBootstrap
|
|
20
|
+
/******/ var __webpack_modules__ = ({
|
|
21
|
+
|
|
22
|
+
/***/ "./node_modules/@basementuniverse/canvas-helpers/build/index.js":
|
|
23
|
+
/*!**********************************************************************!*\
|
|
24
|
+
!*** ./node_modules/@basementuniverse/canvas-helpers/build/index.js ***!
|
|
25
|
+
\**********************************************************************/
|
|
26
|
+
/***/ ((module) => {
|
|
27
|
+
|
|
28
|
+
eval("/*\n * ATTENTION: The \"eval\" devtool has been used (maybe by default in mode: \"development\").\n * This devtool is neither made for production nor for readable output files.\n * It uses \"eval()\" calls to create a separate source file in the browser devtools.\n * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)\n * or disable the default devtool with \"devtool: false\".\n * If you are looking for production-ready output files, see mode: \"production\" (https://webpack.js.org/configuration/mode/).\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(true)\n\t\tmodule.exports = factory();\n\telse { var i, a; }\n})(self, () => {\nreturn /******/ (() => { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ \"./node_modules/@basementuniverse/utils/utils.js\":\n/*!*******************************************************!*\\\n !*** ./node_modules/@basementuniverse/utils/utils.js ***!\n \\*******************************************************/\n/***/ ((module) => {\n\neval(\"/**\\n * @overview A library of useful functions\\n * @author Gordon Larrigan\\n */\\n\\n/**\\n * Memoize a function\\n * @param {Function} f The function to memoize\\n * @returns {Function} A memoized version of the function\\n */\\nconst memoize = f => {\\n var cache = {};\\n return function(...args) {\\n return cache[args] ?? (cache[args] = f.apply(this, args));\\n };\\n};\\n\\n/**\\n * Check if two numbers are approximately equal\\n * @param {number} a Number a\\n * @param {number} b Number b\\n * @param {number} [p=Number.EPSILON] The precision value\\n * @return {boolean} True if numbers a and b are approximately equal\\n */\\nconst floatEquals = (a, b, p = Number.EPSILON) => Math.abs(a - b) < p;\\n\\n/**\\n * Clamp a number between min and max\\n * @param {number} a The number to clamp\\n * @param {number} [min=0] The minimum value\\n * @param {number} [max=1] The maximum value\\n * @return {number} A clamped number\\n */\\nconst clamp = (a, min = 0, max = 1) => a < min ? min : (a > max ? max : a);\\n\\n/**\\n * Get the fractional part of a number\\n * @param {number} a The number from which to get the fractional part\\n * @return {number} The fractional part of the number\\n */\\nconst frac = a => a >= 0 ? a - Math.floor(a) : a - Math.ceil(a);\\n\\n/**\\n * Round n to d decimal places\\n * @param {number} n The number to round\\n * @param {number} [d=0] The number of decimal places to round to\\n * @return {number} A rounded number\\n */\\nconst round = (n, d = 0) => {\\n const p = Math.pow(10, d);\\n return Math.round(n * p + Number.EPSILON) / p;\\n}\\n\\n/**\\n * Do a linear interpolation between a and b\\n * @param {number} a The minimum number\\n * @param {number} b The maximum number\\n * @param {number} i The interpolation value, should be in the interval [0, 1]\\n * @return {number} An interpolated value in the interval [a, b]\\n */\\nconst lerp = (a, b, i) => a + (b - a) * i;\\n\\n/**\\n * Get the position of i between a and b\\n * @param {number} a The minimum number\\n * @param {number} b The maximum number\\n * @param {number} i The interpolated value in the interval [a, b]\\n * @return {number} The position of i between a and b\\n */\\nconst unlerp = (a, b, i) => (i - a) / (b - a);\\n\\n/**\\n * Do a bilinear interpolation\\n * @param {number} c00 Top-left value\\n * @param {number} c10 Top-right value\\n * @param {number} c01 Bottom-left value\\n * @param {number} c11 Bottom-right value\\n * @param {number} ix Interpolation value along x\\n * @param {number} iy Interpolation value along y\\n * @return {number} A bilinear interpolated value\\n */\\nconst blerp = (c00, c10, c01, c11, ix, iy) => lerp(lerp(c00, c10, ix), lerp(c01, c11, ix), iy);\\n\\n/**\\n * Re-map a number i from range a1...a2 to b1...b2\\n * @param {number} i The number to re-map\\n * @param {number} a1\\n * @param {number} a2\\n * @param {number} b1\\n * @param {number} b2\\n * @return {number}\\n */\\nconst remap = (i, a1, a2, b1, b2) => b1 + (i - a1) * (b2 - b1) / (a2 - a1);\\n\\n/**\\n * Do a smooth interpolation between a and b\\n * @param {number} a The minimum number\\n * @param {number} b The maximum number\\n * @param {number} i The interpolation value\\n * @return {number} An interpolated value in the interval [a, b]\\n */\\nconst smoothstep = (a, b, i) => lerp(a, b, 3 * Math.pow(i, 2) - 2 * Math.pow(i, 3));\\n\\n/**\\n * Get an angle in radians\\n * @param {number} degrees The angle in degrees\\n * @return {number} The angle in radians\\n */\\nconst radians = degrees => (Math.PI / 180) * degrees;\\n\\n/**\\n * Get an angle in degrees\\n * @param {number} radians The angle in radians\\n * @return {number} The angle in degrees\\n */\\nconst degrees = radians => (180 / Math.PI) * radians;\\n\\n/**\\n * Get a random float in the interval [min, max)\\n * @param {number} min Inclusive min\\n * @param {number} max Exclusive max\\n * @return {number} A random float in the interval [min, max)\\n */\\nconst randomBetween = (min, max) => Math.random() * (max - min) + min;\\n\\n/**\\n * Get a random integer in the interval [min, max]\\n * @param {number} min Inclusive min\\n * @param {number} max Inclusive max\\n * @return {number} A random integer in the interval [min, max]\\n */\\nconst randomIntBetween = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;\\n\\n/**\\n * Get a normally-distributed random number\\n * @param {number} [mu=0.5] The mean value\\n * @param {number} [sigma=0.5] The standard deviation\\n * @param {number} [samples=2] The number of samples\\n * @return {number} A normally-distributed random number\\n */\\nconst cltRandom = (mu = 0.5, sigma = 0.5, samples = 2) => {\\n let total = 0;\\n for (let i = samples; i--;) {\\n total += Math.random();\\n }\\n return mu + (total - samples / 2) / (samples / 2) * sigma;\\n};\\n\\n/**\\n * Get a normally-distributed random integer in the interval [min, max]\\n * @param {number} min Inclusive min\\n * @param {number} max Inclusive max\\n * @return {number} A normally-distributed random integer\\n */\\nconst cltRandomInt = (min, max) => Math.floor(min + cltRandom(0.5, 0.5, 2) * (max + 1 - min));\\n\\n/**\\n * Return a weighted random integer\\n * @param {Array<number>} w An array of weights\\n * @return {number} An index from w\\n */\\nconst weightedRandom = w => {\\n let total = w.reduce((a, i) => a + i, 0), n = 0;\\n const r = Math.random() * total;\\n while (total > r) {\\n total -= w[n++];\\n }\\n return n - 1;\\n};\\n\\n/**\\n * An interpolation function\\n * @callback InterpolationFunction\\n * @param {number} a The minimum number\\n * @param {number} b The maximum number\\n * @param {number} i The interpolation value, should be in the interval [0, 1]\\n * @return {number} The interpolated value in the interval [a, b]\\n */\\n\\n/**\\n * Return an interpolated value from an array\\n * @param {Array<number>} a An array of values interpolate\\n * @param {number} i A number in the interval [0, 1]\\n * @param {InterpolationFunction} [f=Math.lerp] The interpolation function to use\\n * @return {number} An interpolated value in the interval [min(a), max(a)]\\n */\\nconst lerpArray = (a, i, f = lerp) => {\\n const s = i * (a.length - 1);\\n const p = clamp(Math.trunc(s), 0, a.length - 1);\\n return f(a[p] || 0, a[p + 1] || 0, frac(s));\\n};\\n\\n/**\\n * Get the dot product of two vectors\\n * @param {Array<number>} a Vector a\\n * @param {Array<number>} b Vector b\\n * @return {number} a ∙ b\\n */\\nconst dot = (a, b) => a.reduce((n, v, i) => n + v * b[i], 0);\\n\\n/**\\n * Get the factorial of a number\\n * @param {number} a\\n * @return {number} a!\\n */\\nconst factorial = a => {\\n let result = 1;\\n for (let i = 2; i <= a; i++) {\\n result *= i;\\n }\\n return result;\\n};\\n\\n/**\\n * Get the number of permutations of r elements from a set of n elements\\n * @param {number} n\\n * @param {number} r\\n * @return {number} nPr\\n */\\nconst npr = (n, r) => factorial(n) / factorial(n - r);\\n\\n/**\\n * Get the number of combinations of r elements from a set of n elements\\n * @param {number} n\\n * @param {number} r\\n * @return {number} nCr\\n */\\nconst ncr = (n, r) => factorial(n) / (factorial(r) * factorial(n - r));\\n\\n/**\\n * Generate all permutations of r elements from an array\\n *\\n * @example\\n * ```js\\n * permutations([1, 2, 3], 2);\\n * ```\\n *\\n * Output:\\n * ```json\\n * [\\n * [1, 2],\\n * [1, 3],\\n * [2, 1],\\n * [2, 3],\\n * [3, 1],\\n * [3, 2]\\n * ]\\n * ```\\n * @param {Array<*>} a\\n * @param {number} r The number of elements to choose in each permutation\\n * @return {Array<Array<*>>} An array of permutation arrays\\n */\\nconst permutations = (a, r) => {\\n if (r === 1) {\\n return a.map(item => [item]);\\n }\\n\\n return a.reduce(\\n (acc, item, i) => [\\n ...acc,\\n ...permutations(a.slice(0, i).concat(a.slice(i + 1)), r - 1).map(c => [item, ...c]),\\n ],\\n []\\n );\\n}\\n\\n/**\\n * Generate all combinations of r elements from an array\\n *\\n * @example\\n * ```js\\n * combinations([1, 2, 3], 2);\\n * ```\\n *\\n * Output:\\n * ```json\\n * [\\n * [1, 2],\\n * [1, 3],\\n * [2, 3]\\n * ]\\n * ```\\n * @param {Array<*>} a\\n * @param {number} r The number of elements to choose in each combination\\n * @return {Array<Array<*>>} An array of combination arrays\\n */\\nconst combinations = (a, r) => {\\n if (r === 1) {\\n return a.map(item => [item]);\\n }\\n\\n return a.reduce(\\n (acc, item, i) => [\\n ...acc,\\n ...combinations(a.slice(i + 1), r - 1).map(c => [item, ...c]),\\n ],\\n []\\n );\\n};\\n\\n/**\\n * Get a cartesian product of arrays\\n *\\n * @example\\n * ```js\\n * cartesian([1, 2, 3], ['a', 'b']);\\n * ```\\n *\\n * Output:\\n * ```json\\n * [\\n * [1, \\\"a\\\"],\\n * [1, \\\"b\\\"],\\n * [2, \\\"a\\\"],\\n * [2, \\\"b\\\"],\\n * [3, \\\"a\\\"],\\n * [3, \\\"b\\\"]\\n * ]\\n * ```\\n */\\nconst cartesian = (...arr) =>\\n arr.reduce(\\n (a, b) => a.flatMap(c => b.map(d => [...c, d])),\\n [[]]\\n );\\n\\n/**\\n * A function for generating array values\\n * @callback TimesFunction\\n * @param {number} i The array index\\n * @return {*} The array value\\n */\\n\\n/**\\n * Return a new array with length n by calling function f(i) on each element\\n * @param {TimesFunction} f\\n * @param {number} n The size of the array\\n * @return {Array<*>}\\n */\\nconst times = (f, n) => Array(n).fill(0).map((_, i) => f(i));\\n\\n/**\\n * Return an array containing numbers 0->(n - 1)\\n * @param {number} n The size of the array\\n * @return {Array<number>} An array of integers 0->(n - 1)\\n */\\nconst range = n => times(i => i, n);\\n\\n/**\\n * Zip multiple arrays together, i.e. ([1, 2, 3], [a, b, c]) => [[1, a], [2, b], [3, c]]\\n * @param {...Array<*>} a The arrays to zip\\n * @return {Array<Array<*>>}\\n */\\nconst zip = (...a) => times(i => a.map(a => a[i]), Math.max(...a.map(a => a.length)));\\n\\n/**\\n * Return array[i] with positive and negative wrapping\\n * @param {Array<*>} a The array to access\\n * @param {number} i The positively/negatively wrapped array index\\n * @return {*} An element from the array\\n */\\nconst at = (a, i) => a[i < 0 ? a.length - (Math.abs(i + 1) % a.length) - 1 : i % a.length];\\n\\n/**\\n * Return the last element of an array without removing it\\n * @param {Array<*>} a\\n * @return {*} The last element from the array\\n */\\nconst peek = (a) => {\\n if (!a.length) {\\n return undefined;\\n }\\n\\n return a[a.length - 1];\\n};\\n\\n/**\\n * Return the index for a given position in an unrolled 2d array\\n * @param {number} x The x position\\n * @param {number} y The y position\\n * @param {number} w The width of the 2d array\\n * @returns {number} The index in the unrolled array\\n */\\nconst ind = (x, y, w) => x + y * w;\\n\\n/**\\n * Return the position for a given index in an unrolled 2d array\\n * @param {number} i The index\\n * @param {number} w The width of the 2d array\\n * @returns {Array<number>} The position as a 2-tuple\\n */\\nconst pos = (i, w) => [i % w, Math.floor(i / w)];\\n\\n/**\\n * Chop an array into chunks of size n\\n * @param {Array<*>} a\\n * @param {number} n The chunk size\\n * @return {Array<Array<*>>} An array of array chunks\\n */\\nconst chunk = (a, n) => times(i => a.slice(i * n, i * n + n), Math.ceil(a.length / n));\\n\\n/**\\n * Randomly shuffle a shallow copy of an array\\n * @param {Array<*>} a\\n * @return {Array<*>} The shuffled array\\n */\\nconst shuffle = a => a.slice().sort(() => Math.random() - 0.5);\\n\\n/**\\n * Flatten an object\\n * @param {object} o\\n * @param {string} concatenator The string to use for concatenating keys\\n * @return {object} A flattened object\\n */\\nconst flat = (o, concatenator = '.') => {\\n return Object.keys(o).reduce((acc, key) => {\\n if (o[key] instanceof Date) {\\n return {\\n ...acc,\\n [key]: o[key].toISOString(),\\n };\\n }\\n\\n if (typeof o[key] !== 'object' || !o[key]) {\\n return {\\n ...acc,\\n [key]: o[key],\\n };\\n }\\n const flattened = flat(o[key], concatenator);\\n\\n return {\\n ...acc,\\n ...Object.keys(flattened).reduce(\\n (childAcc, childKey) => ({\\n ...childAcc,\\n [`${key}${concatenator}${childKey}`]: flattened[childKey],\\n }),\\n {}\\n ),\\n };\\n }, {});\\n};\\n\\n/**\\n * Unflatten an object\\n * @param {object} o\\n * @param {string} concatenator The string to check for in concatenated keys\\n * @return {object} An un-flattened object\\n */\\nconst unflat = (o, concatenator = '.') => {\\n let result = {}, temp, substrings, property, i;\\n\\n for (property in o) {\\n substrings = property.split(concatenator);\\n temp = result;\\n for (i = 0; i < substrings.length - 1; i++) {\\n if (!(substrings[i] in temp)) {\\n if (isFinite(substrings[i + 1])) {\\n temp[substrings[i]] = [];\\n } else {\\n temp[substrings[i]] = {};\\n }\\n }\\n temp = temp[substrings[i]];\\n }\\n temp[substrings[substrings.length - 1]] = o[property];\\n }\\n\\n return result;\\n};\\n\\n/**\\n * A split predicate\\n * @callback SplitPredicate\\n * @param {any} value The current value\\n * @return {boolean} True if the array should split at this index\\n */\\n\\n/**\\n * Split an array into sub-arrays based on a predicate\\n * @param {Array<*>} array\\n * @param {SplitPredicate} predicate\\n * @return {Array<Array<*>>} An array of arrays\\n */\\nconst split = (array, predicate) => {\\n const result = [];\\n let current = [];\\n for (const value of array) {\\n if (predicate(value)) {\\n if (current.length) {\\n result.push(current);\\n }\\n current = [value];\\n } else {\\n current.push(value);\\n }\\n }\\n result.push(current);\\n\\n return result;\\n};\\n\\n/**\\n * Pluck keys from an object\\n * @param {object} o\\n * @param {...string} keys The keys to pluck from the object\\n * @return {object} An object containing the plucked keys\\n */\\nconst pluck = (o, ...keys) => {\\n return keys.reduce(\\n (result, key) => Object.assign(result, { [key]: o[key] }),\\n {}\\n );\\n};\\n\\n/**\\n * Exclude keys from an object\\n * @param {object} o\\n * @param {...string} keys The keys to exclude from the object\\n * @return {object} An object containing all keys except excluded keys\\n */\\nconst exclude = (o, ...keys) => {\\n return Object.fromEntries(\\n Object.entries(o).filter(([key]) => !keys.includes(key))\\n );\\n};\\n\\nif (true) {\\n module.exports = {\\n memoize,\\n floatEquals,\\n clamp,\\n frac,\\n round,\\n lerp,\\n unlerp,\\n blerp,\\n remap,\\n smoothstep,\\n radians,\\n degrees,\\n randomBetween,\\n randomIntBetween,\\n cltRandom,\\n cltRandomInt,\\n weightedRandom,\\n lerpArray,\\n dot,\\n factorial,\\n npr,\\n ncr,\\n permutations,\\n combinations,\\n cartesian,\\n times,\\n range,\\n zip,\\n at,\\n peek,\\n ind,\\n pos,\\n chunk,\\n shuffle,\\n flat,\\n unflat,\\n split,\\n pluck,\\n exclude,\\n };\\n}\\n\\n\\n//# sourceURL=webpack://@basementuniverse/canvas-helpers/./node_modules/@basementuniverse/utils/utils.js?\");\n\n/***/ }),\n\n/***/ \"./node_modules/@basementuniverse/vec/vec.js\":\n/*!***************************************************!*\\\n !*** ./node_modules/@basementuniverse/vec/vec.js ***!\n \\***************************************************/\n/***/ ((module) => {\n\neval(\"/**\\n * @overview A small vector and matrix library\\n * @author Gordon Larrigan\\n */\\n\\nconst _vec_times = (f, n) => Array(n).fill(0).map((_, i) => f(i));\\nconst _vec_chunk = (a, n) => _vec_times(i => a.slice(i * n, i * n + n), Math.ceil(a.length / n));\\nconst _vec_dot = (a, b) => a.reduce((n, v, i) => n + v * b[i], 0);\\nconst _vec_is_vec2 = a => typeof a === 'object' && 'x' in a && 'y' in a;\\nconst _vec_is_vec3 = a => typeof a === 'object' && 'x' in a && 'y' in a && 'z' in a;\\n\\n/**\\n * A 2d vector\\n * @typedef {Object} vec2\\n * @property {number} x The x component of the vector\\n * @property {number} y The y component of the vector\\n */\\n\\n/**\\n * Create a new 2d vector\\n * @param {number|vec2} [x] The x component of the vector, or a vector to copy\\n * @param {number} [y] The y component of the vector\\n * @return {vec2} A new 2d vector\\n * @example <caption>various ways to initialise a vector</caption>\\n * let a = vec2(3, 2); // (3, 2)\\n * let b = vec2(4); // (4, 4)\\n * let c = vec2(a); // (3, 2)\\n * let d = vec2(); // (0, 0)\\n */\\nconst vec2 = (x, y) => {\\n if (!x && !y) {\\n return { x: 0, y: 0 };\\n }\\n if (_vec_is_vec2(x)) {\\n return { x: x.x || 0, y: x.y || 0 };\\n }\\n return { x: x, y: y ?? x };\\n};\\n\\n/**\\n * Get the components of a vector as an array\\n * @param {vec2} a The vector to get components from\\n * @return {Array<number>} The vector components as an array\\n */\\nvec2.components = a => [a.x, a.y];\\n\\n/**\\n * Create a vector from an array of components\\n * @param {Array<number>} components The components of the vector\\n * @return {vec2} A new vector\\n */\\nvec2.fromComponents = components => vec2(...components.slice(0, 2));\\n\\n/**\\n * Return a unit vector (1, 0)\\n * @return {vec2} A unit vector (1, 0)\\n */\\nvec2.ux = () => vec2(1, 0);\\n\\n/**\\n * Return a unit vector (0, 1)\\n * @return {vec2} A unit vector (0, 1)\\n */\\nvec2.uy = () => vec2(0, 1);\\n\\n/**\\n * Add vectors\\n * @param {vec2} a Vector a\\n * @param {vec2|number} b Vector or scalar b\\n * @return {vec2} a + b\\n */\\nvec2.add = (a, b) => ({ x: a.x + (b.x ?? b), y: a.y + (b.y ?? b) });\\n\\n/**\\n * Subtract vectors\\n * @param {vec2} a Vector a\\n * @param {vec2|number} b Vector or scalar b\\n * @return {vec2} a - b\\n */\\nvec2.sub = (a, b) => ({ x: a.x - (b.x ?? b), y: a.y - (b.y ?? b) });\\n\\n/**\\n * Scale a vector\\n * @param {vec2} a Vector a\\n * @param {vec2|number} b Vector or scalar b\\n * @return {vec2} a * b\\n */\\nvec2.mul = (a, b) => ({ x: a.x * (b.x ?? b), y: a.y * (b.y ?? b) });\\n\\n/**\\n * Scale a vector by a scalar, alias for vec2.mul\\n * @param {vec2} a Vector a\\n * @param {number} b Scalar b\\n * @return {vec2} a * b\\n */\\nvec2.scale = (a, b) => vec2.mul(a, b);\\n\\n/**\\n * Divide a vector\\n * @param {vec2} a Vector a\\n * @param {vec2|number} b Vector or scalar b\\n * @return {vec2} a / b\\n */\\nvec2.div = (a, b) => ({ x: a.x / (b.x ?? b), y: a.y / (b.y ?? b) });\\n\\n/**\\n * Get the length of a vector\\n * @param {vec2} a Vector a\\n * @return {number} |a|\\n */\\nvec2.len = a => Math.sqrt(a.x * a.x + a.y * a.y);\\n\\n/**\\n * Get the length of a vector using taxicab geometry\\n * @param {vec2} a Vector a\\n * @return {number} |a|\\n */\\nvec2.manhattan = a => Math.abs(a.x) + Math.abs(a.y);\\n\\n/**\\n * Normalise a vector\\n * @param {vec2} a The vector to normalise\\n * @return {vec2} ^a\\n */\\nvec2.nor = a => {\\n let len = vec2.len(a);\\n return len ? { x: a.x / len, y: a.y / len } : vec2();\\n};\\n\\n/**\\n * Get a dot product of vectors\\n * @param {vec2} a Vector a\\n * @param {vec2} b Vector b\\n * @return {number} a ∙ b\\n */\\nvec2.dot = (a, b) => a.x * b.x + a.y * b.y;\\n\\n/**\\n * Rotate a vector by r radians\\n * @param {vec2} a The vector to rotate\\n * @param {number} r The angle to rotate by, measured in radians\\n * @return {vec2} A rotated vector\\n */\\nvec2.rot = (a, r) => {\\n let s = Math.sin(r),\\n c = Math.cos(r);\\n return { x: c * a.x - s * a.y, y: s * a.x + c * a.y };\\n};\\n\\n/**\\n * Fast method to rotate a vector by -90, 90 or 180 degrees\\n * @param {vec2} a The vector to rotate\\n * @param {number} r 1 for 90 degrees (cw), -1 for -90 degrees (ccw), 2 or -2 for 180 degrees\\n * @return {vec2} A rotated vector\\n */\\nvec2.rotf = (a, r) => {\\n switch (r) {\\n case 1: return vec2(a.y, -a.x);\\n case -1: return vec2(-a.y, a.x);\\n case 2: case -2: return vec2(-a.x, -a.y);\\n default: return a;\\n }\\n};\\n\\n/**\\n * Scalar cross product of two vectors\\n * @param {vec2} a Vector a\\n * @param {vec2} b Vector b\\n * @return {number} a × b\\n */\\nvec2.cross = (a, b) => {\\n return a.x * b.y - a.y * b.x;\\n};\\n\\n/**\\n * Check if two vectors are equal\\n * @param {vec2} a Vector a\\n * @param {vec2} b Vector b\\n * @return {boolean} True if vectors a and b are equal, false otherwise\\n */\\nvec2.eq = (a, b) => a.x === b.x && a.y === b.y;\\n\\n/**\\n * Get the angle of a vector\\n * @param {vec2} a Vector a\\n * @return {number} The angle of vector a in radians\\n */\\nvec2.rad = a => Math.atan2(a.y, a.x);\\n\\n/**\\n * Copy a vector\\n * @param {vec2} a The vector to copy\\n * @return {vec2} A copy of vector a\\n */\\nvec2.cpy = a => vec2(a);\\n\\n/**\\n * A function to call on each component of a 2d vector\\n * @callback vec2MapCallback\\n * @param {number} value The component value\\n * @param {'x' | 'y'} label The component label (x or y)\\n * @return {number} The mapped component\\n */\\n\\n/**\\n * Call a function on each component of a vector and build a new vector from the results\\n * @param {vec2} a Vector a\\n * @param {vec2MapCallback} f The function to call on each component of the vector\\n * @return {vec2} Vector a mapped through f\\n */\\nvec2.map = (a, f) => ({ x: f(a.x, 'x'), y: f(a.y, 'y') });\\n\\n/**\\n * Convert a vector into a string\\n * @param {vec2} a The vector to convert\\n * @param {string} [s=', '] The separator string\\n * @return {string} A string representation of the vector\\n */\\nvec2.str = (a, s = ', ') => `${a.x}${s}${a.y}`;\\n\\n/**\\n * Swizzle a vector with a string of component labels\\n *\\n * The string can contain:\\n * - `x` or `y`\\n * - `u` or `v` (aliases for `x` and `y`, respectively)\\n * - `X`, `Y`, `U`, `V` (negated versions of the above)\\n * - `0` or `1` (these will be passed through unchanged)\\n * - `.` to return the component that would normally be at this position (or 0)\\n *\\n * Any other characters will default to 0\\n * @param {vec2} a The vector to swizzle\\n * @param {string} [s='..'] The swizzle string\\n * @return {Array<number>} The swizzled components\\n * @example <caption>swizzling a vector</caption>\\n * let a = vec2(3, -2);\\n * vec2.swiz(a, 'x'); // [3]\\n * vec2.swiz(a, 'yx'); // [-2, 3]\\n * vec2.swiz(a, 'xY'); // [3, 2]\\n * vec2.swiz(a, 'Yy'); // [2, -2]\\n * vec2.swiz(a, 'x.x'); // [3, -2, 3]\\n * vec2.swiz(a, 'y01x'); // [-2, 0, 1, 3]\\n */\\nvec2.swiz = (a, s = '..') => {\\n const result = [];\\n s.split('').forEach((c, i) => {\\n switch (c) {\\n case 'x': case 'u': result.push(a.x); break;\\n case 'y': case 'v': result.push(a.y); break;\\n case 'X': case 'U': result.push(-a.x); break;\\n case 'Y': case 'V': result.push(-a.y); break;\\n case '0': result.push(0); break;\\n case '1': result.push(1); break;\\n case '.': result.push([a.x, a.y][i] ?? 0); break;\\n default: result.push(0);\\n }\\n });\\n return result;\\n};\\n\\n/**\\n * Polar coordinates for a 2d vector\\n * @typedef {Object} polarCoordinates2d\\n * @property {number} r The magnitude (radius) of the vector\\n * @property {number} theta The angle of the vector\\n */\\n\\n/**\\n * Convert a vector into polar coordinates\\n * @param {vec2} a The vector to convert\\n * @return {polarCoordinates2d} The magnitude and angle of the vector\\n */\\nvec2.polar = a => ({ r: vec2.len(a), theta: Math.atan2(a.y, a.x) });\\n\\n/**\\n * Convert polar coordinates into a vector\\n * @param {number} r The magnitude (radius) of the vector\\n * @param {number} theta The angle of the vector\\n * @return {vec2} A vector with the given angle and magnitude\\n */\\nvec2.fromPolar = (r, theta) => vec2(r * Math.cos(theta), r * Math.sin(theta));\\n\\n/**\\n * A 3d vector\\n * @typedef {Object} vec3\\n * @property {number} x The x component of the vector\\n * @property {number} y The y component of the vector\\n * @property {number} z The z component of the vector\\n */\\n\\n/**\\n * Create a new 3d vector\\n * @param {number|vec3|vec2} [x] The x component of the vector, or a vector to copy\\n * @param {number} [y] The y component of the vector, or the z component if x is a vec2\\n * @param {number} [z] The z component of the vector\\n * @return {vec3} A new 3d vector\\n * @example <caption>various ways to initialise a vector</caption>\\n * let a = vec3(3, 2, 1); // (3, 2, 1)\\n * let b = vec3(4, 5); // (4, 5, 0)\\n * let c = vec3(6); // (6, 6, 6)\\n * let d = vec3(a); // (3, 2, 1)\\n * let e = vec3(); // (0, 0, 0)\\n * let f = vec3(vec2(1, 2), 3); // (1, 2, 3)\\n * let g = vec3(vec2(4, 5)); // (4, 5, 0)\\n */\\nconst vec3 = (x, y, z) => {\\n if (!x && !y && !z) {\\n return { x: 0, y: 0, z: 0 };\\n }\\n if (_vec_is_vec3(x)) {\\n return { x: x.x || 0, y: x.y || 0, z: x.z || 0 };\\n }\\n if (_vec_is_vec2(x)) {\\n return { x: x.x || 0, y: x.y || 0, z: y || 0 };\\n }\\n return { x: x, y: y ?? x, z: z ?? x };\\n};\\n\\n/**\\n * Get the components of a vector as an array\\n * @param {vec3} a The vector to get components from\\n * @return {Array<number>} The vector components as an array\\n */\\nvec3.components = a => [a.x, a.y, a.z];\\n\\n/**\\n * Create a vector from an array of components\\n * @param {Array<number>} components The components of the vector\\n * @return {vec3} A new vector\\n */\\nvec3.fromComponents = components => vec3(...components.slice(0, 3));\\n\\n/**\\n * Return a unit vector (1, 0, 0)\\n * @return {vec3} A unit vector (1, 0, 0)\\n */\\nvec3.ux = () => vec3(1, 0, 0);\\n\\n/**\\n * Return a unit vector (0, 1, 0)\\n * @return {vec3} A unit vector (0, 1, 0)\\n */\\nvec3.uy = () => vec3(0, 1, 0);\\n\\n/**\\n * Return a unit vector (0, 0, 1)\\n * @return {vec3} A unit vector (0, 0, 1)\\n */\\nvec3.uz = () => vec3(0, 0, 1);\\n\\n/**\\n * Add vectors\\n * @param {vec3} a Vector a\\n * @param {vec3|number} b Vector or scalar b\\n * @return {vec3} a + b\\n */\\nvec3.add = (a, b) => ({ x: a.x + (b.x ?? b), y: a.y + (b.y ?? b), z: a.z + (b.z ?? b) });\\n\\n/**\\n * Subtract vectors\\n * @param {vec3} a Vector a\\n * @param {vec3|number} b Vector or scalar b\\n * @return {vec3} a - b\\n */\\nvec3.sub = (a, b) => ({ x: a.x - (b.x ?? b), y: a.y - (b.y ?? b), z: a.z - (b.z ?? b) });\\n\\n/**\\n * Scale a vector\\n * @param {vec3} a Vector a\\n * @param {vec3|number} b Vector or scalar b\\n * @return {vec3} a * b\\n */\\nvec3.mul = (a, b) => ({ x: a.x * (b.x ?? b), y: a.y * (b.y ?? b), z: a.z * (b.z ?? b) });\\n\\n/**\\n * Scale a vector by a scalar, alias for vec3.mul\\n * @param {vec3} a Vector a\\n * @param {number} b Scalar b\\n * @return {vec3} a * b\\n */\\nvec3.scale = (a, b) => vec3.mul(a, b);\\n\\n/**\\n * Divide a vector\\n * @param {vec3} a Vector a\\n * @param {vec3|number} b Vector or scalar b\\n * @return {vec3} a / b\\n */\\nvec3.div = (a, b) => ({ x: a.x / (b.x ?? b), y: a.y / (b.y ?? b), z: a.z / (b.z ?? b) });\\n\\n/**\\n * Get the length of a vector\\n * @param {vec3} a Vector a\\n * @return {number} |a|\\n */\\nvec3.len = a => Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z);\\n\\n/**\\n * Get the length of a vector using taxicab geometry\\n * @param {vec3} a Vector a\\n * @return {number} |a|\\n */\\nvec3.manhattan = a => Math.abs(a.x) + Math.abs(a.y) + Math.abs(a.z);\\n\\n/**\\n * Normalise a vector\\n * @param {vec3} a The vector to normalise\\n * @return {vec3} ^a\\n */\\nvec3.nor = a => {\\n let len = vec3.len(a);\\n return len ? { x: a.x / len, y: a.y / len, z: a.z / len } : vec3();\\n};\\n\\n/**\\n * Get a dot product of vectors\\n * @param {vec3} a Vector a\\n * @param {vec3} b Vector b\\n * @return {number} a ∙ b\\n */\\nvec3.dot = (a, b) => a.x * b.x + a.y * b.y + a.z * b.z;\\n\\n/**\\n * Rotate a vector using a rotation matrix\\n * @param {vec3} a The vector to rotate\\n * @param {mat} m The rotation matrix\\n * @return {vec3} A rotated vector\\n */\\nvec3.rot = (a, m) => vec3(\\n vec3.dot(vec3.fromComponents(mat.row(m, 1)), a),\\n vec3.dot(vec3.fromComponents(mat.row(m, 2)), a),\\n vec3.dot(vec3.fromComponents(mat.row(m, 3)), a)\\n);\\n\\n/**\\n * Rotate a vector by r radians around the x axis\\n * @param {vec3} a The vector to rotate\\n * @param {number} r The angle to rotate by, measured in radians\\n * @return {vec3} A rotated vector\\n */\\nvec3.rotx = (a, r) => vec3(\\n a.x,\\n a.y * Math.cos(r) - a.z * Math.sin(r),\\n a.y * Math.sin(r) + a.z * Math.cos(r)\\n);\\n\\n/**\\n * Rotate a vector by r radians around the y axis\\n * @param {vec3} a The vector to rotate\\n * @param {number} r The angle to rotate by, measured in radians\\n * @return {vec3} A rotated vector\\n */\\nvec3.roty = (a, r) => vec3(\\n a.x * Math.cos(r) + a.z * Math.sin(r),\\n a.y,\\n -a.x * Math.sin(r) + a.z * Math.cos(r)\\n);\\n\\n/**\\n * Rotate a vector by r radians around the z axis\\n * @param {vec3} a The vector to rotate\\n * @param {number} r The angle to rotate by, measured in radians\\n * @return {vec3} A rotated vector\\n */\\nvec3.rotz = (a, r) => vec3(\\n a.x * Math.cos(r) - a.y * Math.sin(r),\\n a.x * Math.sin(r) + a.y * Math.cos(r),\\n a.z\\n);\\n\\n/**\\n * Rotate a vector using a quaternion\\n * @param {vec3} a The vector to rotate\\n * @param {Array<number>} q The quaternion to rotate by\\n * @return {vec3} A rotated vector\\n */\\nvec3.rotq = (v, q) => {\\n if (q.length !== 4) {\\n return vec3();\\n }\\n\\n const d = Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);\\n if (d === 0) {\\n return vec3();\\n }\\n\\n const uq = [q[0] / d, q[1] / d, q[2] / d, q[3] / d];\\n const u = vec3(...uq.slice(0, 3));\\n const s = uq[3];\\n return vec3.add(\\n vec3.add(\\n vec3.mul(u, 2 * vec3.dot(u, v)),\\n vec3.mul(v, s * s - vec3.dot(u, u))\\n ),\\n vec3.mul(vec3.cross(u, v), 2 * s)\\n );\\n};\\n\\n/**\\n * Rotate a vector using Euler angles\\n * @param {vec3} a The vector to rotate\\n * @param {vec3} e The Euler angles to rotate by\\n * @return {vec3} A rotated vector\\n */\\nvec3.rota = (a, e) => vec3.rotz(vec3.roty(vec3.rotx(a, e.x), e.y), e.z);\\n\\n/**\\n * Get the cross product of vectors\\n * @param {vec3} a Vector a\\n * @param {vec3} b Vector b\\n * @return {vec3} a × b\\n */\\nvec3.cross = (a, b) => vec3(\\n a.y * b.z - a.z * b.y,\\n a.z * b.x - a.x * b.z,\\n a.x * b.y - a.y * b.x\\n);\\n\\n/**\\n * Check if two vectors are equal\\n * @param {vec3} a Vector a\\n * @param {vec3} b Vector b\\n * @return {boolean} True if vectors a and b are equal, false otherwise\\n */\\nvec3.eq = (a, b) => a.x === b.x && a.y === b.y && a.z === b.z;\\n\\n/**\\n * Get the angle of a vector from the x axis\\n * @param {vec3} a Vector a\\n * @return {number} The angle of vector a in radians\\n */\\nvec3.radx = a => Math.atan2(a.z, a.y);\\n\\n/**\\n * Get the angle of a vector from the y axis\\n * @param {vec3} a Vector a\\n * @return {number} The angle of vector a in radians\\n */\\nvec3.rady = a => Math.atan2(a.x, a.y);\\n\\n/**\\n * Get the angle of a vector from the z axis\\n * @param {vec3} a Vector a\\n * @return {number} The angle of vector a in radians\\n */\\nvec3.radz = a => Math.atan2(a.y, a.z);\\n\\n/**\\n * Copy a vector\\n * @param {vec3} a The vector to copy\\n * @return {vec3} A copy of vector a\\n */\\nvec3.cpy = a => vec3(a);\\n\\n/**\\n * A function to call on each component of a 3d vector\\n * @callback vec3MapCallback\\n * @param {number} value The component value\\n * @param {'x' | 'y' | 'z'} label The component label (x, y or z)\\n * @return {number} The mapped component\\n */\\n\\n/**\\n * Call a function on each component of a vector and build a new vector from the results\\n * @param {vec3} a Vector a\\n * @param {vec3MapCallback} f The function to call on each component of the vector\\n * @return {vec3} Vector a mapped through f\\n */\\nvec3.map = (a, f) => ({ x: f(a.x, 'x'), y: f(a.y, 'y'), z: f(a.z, 'z') });\\n\\n/**\\n * Convert a vector into a string\\n * @param {vec3} a The vector to convert\\n * @param {string} [s=', '] The separator string\\n * @return {string} A string representation of the vector\\n */\\nvec3.str = (a, s = ', ') => `${a.x}${s}${a.y}${s}${a.z}`;\\n\\n/**\\n * Swizzle a vector with a string of component labels\\n *\\n * The string can contain:\\n * - `x`, `y` or `z`\\n * - `u`, `v` or `w` (aliases for `x`, `y` and `z`, respectively)\\n * - `r`, `g` or `b` (aliases for `x`, `y` and `z`, respectively)\\n * - `X`, `Y`, `Z`, `U`, `V`, `W`, `R`, `G`, `B` (negated versions of the above)\\n * - `0` or `1` (these will be passed through unchanged)\\n * - `.` to return the component that would normally be at this position (or 0)\\n *\\n * Any other characters will default to 0\\n * @param {vec3} a The vector to swizzle\\n * @param {string} [s='...'] The swizzle string\\n * @return {Array<number>} The swizzled components\\n * @example <caption>swizzling a vector</caption>\\n * let a = vec3(3, -2, 1);\\n * vec3.swiz(a, 'x'); // [3]\\n * vec3.swiz(a, 'zyx'); // [1, -2, 3]\\n * vec3.swiz(a, 'xYZ'); // [3, 2, -1]\\n * vec3.swiz(a, 'Zzx'); // [-1, 1, 3]\\n * vec3.swiz(a, 'x.x'); // [3, -2, 3]\\n * vec3.swiz(a, 'y01zx'); // [-2, 0, 1, 1, 3]\\n */\\nvec3.swiz = (a, s = '...') => {\\n const result = [];\\n s.split('').forEach((c, i) => {\\n switch (c) {\\n case 'x': case 'u': case 'r': result.push(a.x); break;\\n case 'y': case 'v': case 'g': result.push(a.y); break;\\n case 'z': case 'w': case 'b': result.push(a.z); break;\\n case 'X': case 'U': case 'R': result.push(-a.x); break;\\n case 'Y': case 'V': case 'G': result.push(-a.y); break;\\n case 'Z': case 'W': case 'B': result.push(-a.z); break;\\n case '0': result.push(0); break;\\n case '1': result.push(1); break;\\n case '.': result.push([a.x, a.y, a.z][i] ?? 0); break;\\n default: result.push(0);\\n }\\n });\\n return result;\\n};\\n\\n/**\\n * Polar coordinates for a 3d vector\\n * @typedef {Object} polarCoordinates3d\\n * @property {number} r The magnitude (radius) of the vector\\n * @property {number} theta The tilt angle of the vector\\n * @property {number} phi The pan angle of the vector\\n */\\n\\n/**\\n * Convert a vector into polar coordinates\\n * @param {vec3} a The vector to convert\\n * @return {polarCoordinates3d} The magnitude, tilt and pan of the vector\\n */\\nvec3.polar = a => {\\n let r = vec3.len(a),\\n theta = Math.acos(a.y / r),\\n phi = Math.atan2(a.z, a.x);\\n return { r, theta, phi };\\n};\\n\\n/**\\n * Convert polar coordinates into a vector\\n * @param {number} r The magnitude (radius) of the vector\\n * @param {number} theta The tilt of the vector\\n * @param {number} phi The pan of the vector\\n * @return {vec3} A vector with the given angle and magnitude\\n */\\nvec3.fromPolar = (r, theta, phi) => {\\n const sinTheta = Math.sin(theta);\\n return vec3(\\n r * sinTheta * Math.cos(phi),\\n r * Math.cos(theta),\\n r * sinTheta * Math.sin(phi)\\n );\\n};\\n\\n/**\\n * A matrix\\n * @typedef {Object} mat\\n * @property {number} m The number of rows in the matrix\\n * @property {number} n The number of columns in the matrix\\n * @property {Array<number>} entries The matrix values\\n */\\n\\n/**\\n * Create a new matrix\\n * @param {number} [m=4] The number of rows\\n * @param {number} [n=4] The number of columns\\n * @param {Array<number>} [entries=[]] Matrix values in reading order\\n * @return {mat} A new matrix\\n */\\nconst mat = (m = 4, n = 4, entries = []) => ({\\n m, n,\\n entries: entries.concat(Array(m * n).fill(0)).slice(0, m * n)\\n});\\n\\n/**\\n * Get an identity matrix of size n\\n * @param {number} n The size of the matrix\\n * @return {mat} An identity matrix\\n */\\nmat.identity = n => mat(n, n, Array(n * n).fill(0).map((v, i) => +(Math.floor(i / n) === i % n)));\\n\\n/**\\n * Get an entry from a matrix\\n * @param {mat} a Matrix a\\n * @param {number} i The row offset\\n * @param {number} j The column offset\\n * @return {number} The value at position (i, j) in matrix a\\n */\\nmat.get = (a, i, j) => a.entries[(j - 1) + (i - 1) * a.n];\\n\\n/**\\n * Set an entry of a matrix\\n * @param {mat} a Matrix a\\n * @param {number} i The row offset\\n * @param {number} j The column offset\\n * @param {number} v The value to set in matrix a\\n */\\nmat.set = (a, i, j, v) => { a.entries[(j - 1) + (i - 1) * a.n] = v; };\\n\\n/**\\n * Get a row from a matrix as an array\\n * @param {mat} a Matrix a\\n * @param {number} m The row offset\\n * @return {Array<number>} Row m from matrix a\\n */\\nmat.row = (a, m) => {\\n const s = (m - 1) * a.n;\\n return a.entries.slice(s, s + a.n);\\n};\\n\\n/**\\n * Get a column from a matrix as an array\\n * @param {mat} a Matrix a\\n * @param {number} n The column offset\\n * @return {Array<number>} Column n from matrix a\\n */\\nmat.col = (a, n) => _vec_times(i => mat.get(a, (i + 1), n), a.m);\\n\\n/**\\n * Add matrices\\n * @param {mat} a Matrix a\\n * @param {mat} b Matrix b\\n * @return {mat} a + b\\n */\\nmat.add = (a, b) => a.m === b.m && a.n === b.n && mat.map(a, (v, i) => v + b.entries[i]);\\n\\n/**\\n * Subtract matrices\\n * @param {mat} a Matrix a\\n * @param {mat} b Matrix b\\n * @return {mat} a - b\\n */\\nmat.sub = (a, b) => a.m === b.m && a.n === b.n && mat.map(a, (v, i) => v - b.entries[i]);\\n\\n/**\\n * Multiply matrices\\n * @param {mat} a Matrix a\\n * @param {mat} b Matrix b\\n * @return {mat|false} ab or false if the matrices cannot be multiplied\\n */\\nmat.mul = (a, b) => {\\n if (a.n !== b.m) { return false; }\\n const result = mat(a.m, b.n);\\n for (let i = 1; i <= a.m; i++) {\\n for (let j = 1; j <= b.n; j++) {\\n mat.set(result, i, j, _vec_dot(mat.row(a, i), mat.col(b, j)));\\n }\\n }\\n return result;\\n};\\n\\n/**\\n * Multiply a matrix by a vector\\n * @param {mat} a Matrix a\\n * @param {vec2|vec3|number[]} b Vector b\\n * @return {vec2|vec3|number[]|false} ab or false if the matrix and vector cannot be multiplied\\n */\\nmat.mulv = (a, b) => {\\n let n, bb, rt;\\n if (_vec_is_vec3(b)) {\\n bb = vec3.components(b);\\n n = 3;\\n rt = vec3.fromComponents;\\n } else if (_vec_is_vec2(b)) {\\n bb = vec2.components(b);\\n n = 2;\\n rt = vec2.fromComponents;\\n } else {\\n bb = b;\\n n = b.length ?? 0;\\n rt = v => v;\\n }\\n if (a.n !== n) { return false; }\\n const result = [];\\n for (let i = 1; i <= a.m; i++) {\\n result.push(_vec_dot(mat.row(a, i), bb));\\n }\\n return rt(result);\\n}\\n\\n/**\\n * Scale a matrix\\n * @param {mat} a Matrix a\\n * @param {number} b Scalar b\\n * @return {mat} a * b\\n */\\nmat.scale = (a, b) => mat.map(a, v => v * b);\\n\\n/**\\n * Transpose a matrix\\n * @param {mat} a The matrix to transpose\\n * @return {mat} A transposed matrix\\n */\\nmat.trans = a => mat(a.n, a.m, _vec_times(i => mat.col(a, (i + 1)), a.n).flat());\\n\\n/**\\n * Get the minor of a matrix\\n * @param {mat} a Matrix a\\n * @param {number} i The row offset\\n * @param {number} j The column offset\\n * @return {mat|false} The (i, j) minor of matrix a or false if the matrix is not square\\n */\\nmat.minor = (a, i, j) => {\\n if (a.m !== a.n) { return false; }\\n const entries = [];\\n for (let ii = 1; ii <= a.m; ii++) {\\n if (ii === i) { continue; }\\n for (let jj = 1; jj <= a.n; jj++) {\\n if (jj === j) { continue; }\\n entries.push(mat.get(a, ii, jj));\\n }\\n }\\n return mat(a.m - 1, a.n - 1, entries);\\n};\\n\\n/**\\n * Get the determinant of a matrix\\n * @param {mat} a Matrix a\\n * @return {number|false} |a| or false if the matrix is not square\\n */\\nmat.det = a => {\\n if (a.m !== a.n) { return false; }\\n if (a.m === 1) {\\n return a.entries[0];\\n }\\n if (a.m === 2) {\\n return a.entries[0] * a.entries[3] - a.entries[1] * a.entries[2];\\n }\\n let total = 0, sign = 1;\\n for (let j = 1; j <= a.n; j++) {\\n total += sign * a.entries[j - 1] * mat.det(mat.minor(a, 1, j));\\n sign *= -1;\\n }\\n return total;\\n};\\n\\n/**\\n * Normalise a matrix\\n * @param {mat} a The matrix to normalise\\n * @return {mat|false} ^a or false if the matrix is not square\\n */\\nmat.nor = a => {\\n if (a.m !== a.n) { return false; }\\n const d = mat.det(a);\\n return mat.map(a, i => i * d);\\n};\\n\\n/**\\n * Get the adjugate of a matrix\\n * @param {mat} a The matrix from which to get the adjugate\\n * @return {mat} The adjugate of a\\n */\\nmat.adj = a => {\\n const minors = mat(a.m, a.n);\\n for (let i = 1; i <= a.m; i++) {\\n for (let j = 1; j <= a.n; j++) {\\n mat.set(minors, i, j, mat.det(mat.minor(a, i, j)));\\n }\\n }\\n const cofactors = mat.map(minors, (v, i) => v * (i % 2 ? -1 : 1));\\n return mat.trans(cofactors);\\n};\\n\\n/**\\n * Get the inverse of a matrix\\n * @param {mat} a The matrix to invert\\n * @return {mat|false} a^-1 or false if the matrix has no inverse\\n */\\nmat.inv = a => {\\n if (a.m !== a.n) { return false; }\\n const d = mat.det(a);\\n if (d === 0) { return false; }\\n return mat.scale(mat.adj(a), 1 / d);\\n};\\n\\n/**\\n * Check if two matrices are equal\\n * @param {mat} a Matrix a\\n * @param {mat} b Matrix b\\n * @return {boolean} True if matrices a and b are identical, false otherwise\\n */\\nmat.eq = (a, b) => a.m === b.m && a.n === b.n && mat.str(a) === mat.str(b);\\n\\n/**\\n * Copy a matrix\\n * @param {mat} a The matrix to copy\\n * @return {mat} A copy of matrix a\\n */\\nmat.cpy = a => mat(a.m, a.n, [...a.entries]);\\n\\n/**\\n * A function to call on each entry of a matrix\\n * @callback matrixMapCallback\\n * @param {number} value The entry value\\n * @param {number} index The entry index\\n * @param {Array<number>} entries The array of matrix entries\\n * @return {number} The mapped entry\\n */\\n\\n/**\\n * Call a function on each entry of a matrix and build a new matrix from the results\\n * @param {mat} a Matrix a\\n * @param {matrixMapCallback} f The function to call on each entry of the matrix\\n * @return {mat} Matrix a mapped through f\\n */\\nmat.map = (a, f) => mat(a.m, a.n, a.entries.map(f));\\n\\n/**\\n * Convert a matrix into a string\\n * @param {mat} a The matrix to convert\\n * @param {string} [ms=', '] The separator string for columns\\n * @param {string} [ns='\\\\n'] The separator string for rows\\n * @return {string} A string representation of the matrix\\n */\\nmat.str = (a, ms = ', ', ns = '\\\\n') => _vec_chunk(a.entries, a.n).map(r => r.join(ms)).join(ns);\\n\\nif (true) {\\n module.exports = { vec2, vec3, mat };\\n}\\n\\n\\n//# sourceURL=webpack://@basementuniverse/canvas-helpers/./node_modules/@basementuniverse/vec/vec.js?\");\n\n/***/ }),\n\n/***/ \"./index.ts\":\n/*!******************!*\\\n !*** ./index.ts ***!\n \\******************/\n/***/ ((__unused_webpack_module, exports, __webpack_require__) => {\n\n\"use strict\";\neval(\"\\nObject.defineProperty(exports, \\\"__esModule\\\", ({ value: true }));\\nexports.withContext = withContext;\\nexports.line = line;\\nexports.cross = cross;\\nexports.arrow = arrow;\\nexports.circle = circle;\\nexports.rectangle = rectangle;\\nexports.polygon = polygon;\\nexports.path = path;\\nconst utils_1 = __webpack_require__(/*! @basementuniverse/utils */ \\\"./node_modules/@basementuniverse/utils/utils.js\\\");\\nconst vec_1 = __webpack_require__(/*! @basementuniverse/vec */ \\\"./node_modules/@basementuniverse/vec/vec.js\\\");\\nconst DEFAULT_STYLE_OPTIONS = {\\n batch: false,\\n fill: false,\\n fillColor: null,\\n gradient: null,\\n stroke: true,\\n strokeColor: null,\\n lineWidth: 1,\\n lineStyle: 'solid',\\n lineDash: null,\\n crossStyle: 'x',\\n rounded: false,\\n arrow: {\\n type: 'caret',\\n size: 5,\\n },\\n};\\nconst DEFAULT_LINE_DASHES = {\\n solid: [],\\n dashed: [5, 5],\\n dotted: [1, 3],\\n};\\nconst BEZIER_MATRICES = {\\n 1: (0, vec_1.mat)(2, 2, [-1, 1, 1, 0]),\\n 2: (0, vec_1.mat)(3, 3, [1, -2, 1, -2, 2, 0, 1, 0, 0]),\\n 3: (0, vec_1.mat)(4, 4, [-1, 3, -3, 1, 3, -6, 3, 0, -3, 3, 0, 0, 1, 0, 0, 0]),\\n};\\nconst BEZIER_COEFFICIENTS = (t, order) => ({\\n 1: [t, 1],\\n 2: [t * t, t, 1],\\n 3: [t * t * t, t * t, t, 1],\\n}[order]);\\nconst CATMULL_ROM_BASIS_FUNCTIONS = [\\n (t, tension) => -tension * Math.pow(t, 3) + 2 * tension * Math.pow(t, 2) - tension * t,\\n (t, tension) => (2 - tension) * Math.pow(t, 3) + (tension - 3) * Math.pow(t, 2) + 1,\\n (t, tension) => (tension - 2) * Math.pow(t, 3) +\\n (3 - 2 * tension) * Math.pow(t, 2) +\\n tension * t,\\n (t, tension) => tension * Math.pow(t, 3) - tension * Math.pow(t, 2),\\n];\\nconst CATMULL_ROM_BASIS_VECTOR = (t, tension) => CATMULL_ROM_BASIS_FUNCTIONS.map(f => f(t, tension));\\n/**\\n * Type guard to check if a value is a Color object\\n */\\nfunction isColorObject(color) {\\n return (typeof color === 'object' &&\\n 'r' in color &&\\n 'g' in color &&\\n 'b' in color &&\\n (typeof color.a === 'number' || !('a' in color)));\\n}\\n/**\\n * Convert a color object to a string in the format \\\"rgba(r, g, b, a)\\\"\\n */\\nfunction colourToString(color) {\\n var _a;\\n return `rgba(${color.r}, ${color.g}, ${color.b}, ${(_a = color.a) !== null && _a !== void 0 ? _a : 1})`;\\n}\\n/**\\n * Prepare a color value (string or Color object) for use in styles\\n */\\nfunction prepareColor(color) {\\n if (typeof color === 'string') {\\n // Assume it's already a valid CSS color string\\n return color;\\n }\\n else if (isColorObject(color)) {\\n // Convert Color object to CSS color string\\n return colourToString(color);\\n }\\n // If it's neither, default to black\\n return 'black';\\n}\\n/**\\n * Prepare a gradient for use in styles\\n *\\n * Returns a CanvasGradient object or null if no gradient is specified\\n */\\nfunction prepareGradient(context, style) {\\n if (!style) {\\n return null;\\n }\\n let gradient;\\n if (style.type === 'linear') {\\n gradient = context.createLinearGradient(style.start.x, style.start.y, style.end.x, style.end.y);\\n }\\n else {\\n gradient = context.createRadialGradient(style.start.x, style.start.y, 0, style.start.x, style.start.y, vec_1.vec2.len(vec_1.vec2.sub(style.end, style.start)) / 2);\\n }\\n for (const stop of style.colorStops) {\\n gradient.addColorStop(stop.position, prepareColor(stop.color));\\n }\\n return gradient;\\n}\\n/**\\n * Get a complete style object with default values filled in\\n */\\nfunction getStyle(style) {\\n var _a;\\n return Object.assign({}, DEFAULT_STYLE_OPTIONS, {\\n ...(style !== null && style !== void 0 ? style : {}),\\n lineDash: style && style.lineDash !== undefined\\n ? style.lineDash\\n : (style === null || style === void 0 ? void 0 : style.lineStyle) === undefined\\n ? []\\n : DEFAULT_LINE_DASHES[(_a = style.lineStyle) !== null && _a !== void 0 ? _a : 'solid'],\\n });\\n}\\n/**\\n * Pass in a context and some number of functions that take a context as their\\n * first argument, and return an array of functions that don't require the\\n * context argument\\n *\\n * If only one function is passed, this will return a single function\\n */\\nfunction withContext(context, ...functions) {\\n const result = functions.map(f => {\\n return (...args) => {\\n f(context, ...args);\\n };\\n });\\n return result.length === 1 ? result[0] : result;\\n}\\n/**\\n * Draw a straight line segment between two points\\n */\\nfunction line(context, start, end, style) {\\n context.save();\\n // Apply styles\\n const actualStyle = getStyle(style);\\n if (actualStyle.strokeColor !== null) {\\n context.strokeStyle = prepareColor(actualStyle.strokeColor);\\n }\\n if (actualStyle.lineWidth !== null) {\\n context.lineWidth = actualStyle.lineWidth;\\n }\\n if (actualStyle.lineDash !== null) {\\n context.setLineDash(actualStyle.lineDash);\\n }\\n // If this is a batch operation, don't begin a new path so we can add to any\\n // existing path and draw multiple lines in one go\\n if (!actualStyle.batch) {\\n context.beginPath();\\n }\\n context.moveTo(start.x, start.y);\\n context.lineTo(end.x, end.y);\\n // Stroke the path if required\\n // Additionally, if this is a batch operation, we don't stroke right away so\\n // that we can add more lines to the same path if we want\\n if (actualStyle.stroke && !actualStyle.batch) {\\n context.stroke();\\n }\\n context.restore();\\n}\\n/**\\n * Draw a cross at a given position with a specified size\\n */\\nfunction cross(context, position, size, style) {\\n context.save();\\n // Apply styles\\n const actualStyle = getStyle(style);\\n if (actualStyle.strokeColor !== null) {\\n context.strokeStyle = prepareColor(actualStyle.strokeColor);\\n }\\n if (actualStyle.lineWidth !== null) {\\n context.lineWidth = actualStyle.lineWidth;\\n }\\n if (actualStyle.lineDash !== null) {\\n context.setLineDash(actualStyle.lineDash);\\n }\\n // If this is a batch operation, don't begin a new path so we can add to any\\n // existing path and draw multiple lines in one go\\n if (!actualStyle.batch) {\\n context.beginPath();\\n }\\n // Draw the cross\\n const halfSize = size / 2;\\n if (actualStyle.crossStyle === '+') {\\n // Plus sign cross\\n context.moveTo(position.x - halfSize, position.y);\\n context.lineTo(position.x + halfSize, position.y);\\n context.moveTo(position.x, position.y - halfSize);\\n context.lineTo(position.x, position.y + halfSize);\\n }\\n else if (actualStyle.crossStyle === 'x') {\\n // X cross\\n context.moveTo(position.x - halfSize, position.y - halfSize);\\n context.lineTo(position.x + halfSize, position.y + halfSize);\\n context.moveTo(position.x - halfSize, position.y + halfSize);\\n context.lineTo(position.x + halfSize, position.y - halfSize);\\n }\\n // Stroke the path if required\\n if (actualStyle.stroke && !actualStyle.batch) {\\n context.stroke();\\n }\\n context.restore();\\n}\\n/**\\n * Draw an arrow from a start point to an end point with an optional arrowhead\\n * at the end\\n *\\n * This function does not support batch drawing since it requires\\n * beginning a new path for the arrowhead\\n */\\nfunction arrow(context, start, end, style) {\\n var _a;\\n context.save();\\n // Apply styles\\n const actualStyle = getStyle(style);\\n if (actualStyle.strokeColor !== null) {\\n context.strokeStyle = prepareColor(actualStyle.strokeColor);\\n }\\n if (actualStyle.lineWidth !== null) {\\n context.lineWidth = actualStyle.lineWidth;\\n }\\n if (actualStyle.lineDash !== null) {\\n context.setLineDash(actualStyle.lineDash);\\n }\\n // Arrows don't support batch drawing since we have to begin a new path\\n // when drawing the arrowhead\\n context.beginPath();\\n // Draw the line segment\\n context.moveTo(start.x, start.y);\\n context.lineTo(end.x, end.y);\\n context.stroke();\\n // Draw the arrowhead if specified\\n if (actualStyle.arrow) {\\n const arrowSize = (_a = actualStyle.arrow.size) !== null && _a !== void 0 ? _a : 10;\\n const halfSize = arrowSize / 2;\\n const angle = vec_1.vec2.rad(vec_1.vec2.sub(end, start));\\n const arrowType = actualStyle.arrow.type;\\n context.save();\\n context.translate(end.x, end.y);\\n context.rotate(angle);\\n if (typeof arrowType === 'function') {\\n arrowType(context, arrowSize);\\n }\\n else if (arrowType === 'caret') {\\n if (actualStyle.strokeColor !== null) {\\n context.fillStyle = prepareColor(actualStyle.strokeColor);\\n }\\n context.beginPath();\\n context.moveTo(0, -halfSize);\\n context.lineTo(arrowSize, 0);\\n context.lineTo(0, halfSize);\\n context.closePath();\\n context.fill();\\n }\\n else if (arrowType === 'chevron') {\\n context.beginPath();\\n context.moveTo(-halfSize, -halfSize);\\n context.lineTo(0, 0);\\n context.lineTo(-halfSize, halfSize);\\n context.stroke();\\n }\\n context.restore();\\n }\\n context.restore();\\n}\\n/**\\n * Draw a circle at a specified center point with a given radius\\n */\\nfunction circle(context, center, radius, style) {\\n context.save();\\n // Apply styles\\n const actualStyle = getStyle(style);\\n if (actualStyle.fillColor !== null) {\\n context.fillStyle = prepareColor(actualStyle.fillColor);\\n }\\n if (actualStyle.gradient) {\\n const gradient = prepareGradient(context, actualStyle.gradient);\\n if (gradient) {\\n context.fillStyle = gradient;\\n }\\n }\\n if (actualStyle.strokeColor !== null) {\\n context.strokeStyle = prepareColor(actualStyle.strokeColor);\\n }\\n if (actualStyle.lineWidth !== null) {\\n context.lineWidth = actualStyle.lineWidth;\\n }\\n if (actualStyle.lineDash !== null) {\\n context.setLineDash(actualStyle.lineDash);\\n }\\n // If this is a batch operation, don't begin a new path so we can add to any\\n // existing path and draw multiple shapes in one go\\n if (!actualStyle.batch) {\\n context.beginPath();\\n }\\n // Draw the circle\\n context.arc(center.x, center.y, radius, 0, Math.PI * 2);\\n // Fill the circle if required\\n if (actualStyle.fill && !actualStyle.batch) {\\n context.fill();\\n }\\n // Stroke the circle if required\\n if (actualStyle.stroke && !actualStyle.batch) {\\n context.stroke();\\n }\\n context.restore();\\n}\\n/**\\n * Draw a rectangle at a specified position with a given size\\n */\\nfunction rectangle(context, position, size, style) {\\n var _a;\\n context.save();\\n // Apply styles\\n const actualStyle = getStyle(style);\\n if (actualStyle.fillColor !== null) {\\n context.fillStyle = prepareColor(actualStyle.fillColor);\\n }\\n if (actualStyle.gradient) {\\n const gradient = prepareGradient(context, actualStyle.gradient);\\n if (gradient) {\\n context.fillStyle = gradient;\\n }\\n }\\n if (actualStyle.strokeColor !== null) {\\n context.strokeStyle = prepareColor(actualStyle.strokeColor);\\n }\\n if (actualStyle.lineWidth !== null) {\\n context.lineWidth = actualStyle.lineWidth;\\n }\\n if (actualStyle.lineDash !== null) {\\n context.setLineDash(actualStyle.lineDash);\\n }\\n // If this is a batch operation, don't begin a new path so we can add to any\\n // existing path and draw multiple shapes in one go\\n if (!actualStyle.batch) {\\n context.beginPath();\\n }\\n // Draw the rectangle\\n if (actualStyle.rounded) {\\n context.roundRect(position.x, position.y, size.x, size.y, (_a = actualStyle.borderRadius) !== null && _a !== void 0 ? _a : 1);\\n }\\n else {\\n context.rect(position.x, position.y, size.x, size.y);\\n }\\n // Fill the rectangle if required\\n if (actualStyle.fill && !actualStyle.batch) {\\n context.fill();\\n }\\n // Stroke the rectangle if required\\n if (actualStyle.stroke && !actualStyle.batch) {\\n context.stroke();\\n }\\n context.restore();\\n}\\n/**\\n * Draw a polygon defined by an array of vertices\\n */\\nfunction polygon(context, vertices, style) {\\n if (vertices.length < 3) {\\n return;\\n }\\n context.save();\\n // Apply styles\\n const actualStyle = getStyle(style);\\n if (actualStyle.fillColor !== null) {\\n context.fillStyle = prepareColor(actualStyle.fillColor);\\n }\\n if (actualStyle.gradient) {\\n const gradient = prepareGradient(context, actualStyle.gradient);\\n if (gradient) {\\n context.fillStyle = gradient;\\n }\\n }\\n if (actualStyle.strokeColor !== null) {\\n context.strokeStyle = prepareColor(actualStyle.strokeColor);\\n }\\n if (actualStyle.lineWidth !== null) {\\n context.lineWidth = actualStyle.lineWidth;\\n }\\n if (actualStyle.lineDash !== null) {\\n context.setLineDash(actualStyle.lineDash);\\n }\\n // If this is a batch operation, don't begin a new path so we can add to any\\n // existing path and draw multiple shapes in one go\\n if (!actualStyle.batch) {\\n context.beginPath();\\n }\\n // Draw the polygon path\\n context.moveTo(vertices[0].x, vertices[0].y);\\n for (let i = 1; i < vertices.length; i++) {\\n context.lineTo(vertices[i].x, vertices[i].y);\\n }\\n context.closePath();\\n // Fill the rectangle if required\\n if (actualStyle.fill && !actualStyle.batch) {\\n context.fill();\\n }\\n // Stroke the rectangle if required\\n if (actualStyle.stroke && !actualStyle.batch) {\\n context.stroke();\\n }\\n context.restore();\\n}\\n/**\\n * Draw a path defined by an array of vertices\\n */\\nfunction path(context, vertices, style) {\\n var _a, _b, _c;\\n if (vertices.length < 2)\\n return;\\n context.save();\\n // Apply styles\\n const actualStyle = getStyle(style);\\n if (actualStyle.strokeColor !== null) {\\n context.strokeStyle = prepareColor(actualStyle.strokeColor);\\n }\\n if (actualStyle.lineWidth !== null) {\\n context.lineWidth = actualStyle.lineWidth;\\n }\\n if (actualStyle.lineDash !== null) {\\n context.setLineDash(actualStyle.lineDash);\\n }\\n // If this is a batch operation, don't begin a new path\\n if (!actualStyle.batch) {\\n context.beginPath();\\n }\\n // Handle different path types\\n const pathType = (_a = actualStyle.pathType) !== null && _a !== void 0 ? _a : 'linear';\\n if (pathType === 'linear') {\\n // Simple linear path\\n context.moveTo(vertices[0].x, vertices[0].y);\\n for (let i = 1; i < vertices.length; i++) {\\n context.lineTo(vertices[i].x, vertices[i].y);\\n }\\n }\\n else if (pathType === 'bezier') {\\n const order = (0, utils_1.clamp)((_b = actualStyle.bezierOrder) !== null && _b !== void 0 ? _b : 3, 1, 3);\\n // Draw bezier curve segments\\n const segmentSize = order + 1;\\n for (let i = 0; i + segmentSize <= vertices.length; i += order) {\\n const segmentPoints = vertices.slice(i, i + segmentSize);\\n // Draw first point of segment\\n if (i === 0) {\\n context.moveTo(segmentPoints[0].x, segmentPoints[0].y);\\n }\\n // Draw bezier curve through points\\n for (let t = 0; t <= 1; t += 0.01) {\\n const q = vec_1.mat.mulv(BEZIER_MATRICES[order], BEZIER_COEFFICIENTS(t, order));\\n if (q === false) {\\n context.restore();\\n return;\\n }\\n let p = (0, vec_1.vec2)();\\n for (let j = 0; j < segmentSize; j++) {\\n p.x += segmentPoints[j].x * q[j];\\n p.y += segmentPoints[j].y * q[j];\\n }\\n context.lineTo(p.x, p.y);\\n }\\n }\\n }\\n else if (pathType === 'catmull-rom') {\\n const tension = (_c = actualStyle.catmullRomTension) !== null && _c !== void 0 ? _c : 0.5;\\n // Need at least 4 points for Catmull-Rom\\n if (vertices.length >= 4) {\\n context.moveTo(vertices[1].x, vertices[1].y);\\n // Draw curve segments\\n for (let i = 1; i < vertices.length - 2; i++) {\\n const points = [\\n vertices[i - 1],\\n vertices[i],\\n vertices[i + 1],\\n vertices[i + 2],\\n ];\\n for (let t = 0; t <= 1; t += 0.01) {\\n const x = (0, utils_1.dot)(points.map(p => p.x), CATMULL_ROM_BASIS_VECTOR(t, tension));\\n const y = (0, utils_1.dot)(points.map(p => p.y), CATMULL_ROM_BASIS_VECTOR(t, tension));\\n context.lineTo(x, y);\\n }\\n }\\n }\\n else {\\n // Fall back to linear if not enough points\\n context.moveTo(vertices[0].x, vertices[0].y);\\n for (let i = 1; i < vertices.length; i++) {\\n context.lineTo(vertices[i].x, vertices[i].y);\\n }\\n }\\n }\\n // Stroke the path if required\\n if (actualStyle.stroke && !actualStyle.batch) {\\n context.stroke();\\n }\\n context.restore();\\n}\\n\\n\\n//# sourceURL=webpack://@basementuniverse/canvas-helpers/./index.ts?\");\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __nested_webpack_require_60904__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tvar cachedModule = __webpack_module_cache__[moduleId];\n/******/ \t\tif (cachedModule !== undefined) {\n/******/ \t\t\treturn cachedModule.exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __nested_webpack_require_60904__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \t// This entry module can't be inlined because the eval devtool is used.\n/******/ \tvar __nested_webpack_exports__ = __nested_webpack_require_60904__(\"./index.ts\");\n/******/ \t\n/******/ \treturn __nested_webpack_exports__;\n/******/ })()\n;\n});\n\n//# sourceURL=webpack://@basementuniverse/particles-2d/./node_modules/@basementuniverse/canvas-helpers/build/index.js?");
|
|
29
|
+
|
|
30
|
+
/***/ }),
|
|
31
|
+
|
|
32
|
+
/***/ "./node_modules/@basementuniverse/intersection-helpers/build/2d/index.js":
|
|
33
|
+
/*!*******************************************************************************!*\
|
|
34
|
+
!*** ./node_modules/@basementuniverse/intersection-helpers/build/2d/index.js ***!
|
|
35
|
+
\*******************************************************************************/
|
|
36
|
+
/***/ (function(module) {
|
|
37
|
+
|
|
38
|
+
eval("(function webpackUniversalModuleDefinition(root, factory) {\n\tif(true)\n\t\tmodule.exports = factory();\n\telse { var i, a; }\n})(this, () => {\nreturn /******/ (() => { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ \"./node_modules/@basementuniverse/utils/utils.js\":\n/*!*******************************************************!*\\\n !*** ./node_modules/@basementuniverse/utils/utils.js ***!\n \\*******************************************************/\n/***/ ((module) => {\n\n/**\n * @overview A library of useful functions\n * @author Gordon Larrigan\n */\n\n/**\n * Memoize a function\n * @param {Function} f The function to memoize\n * @returns {Function} A memoized version of the function\n */\nconst memoize = f => {\n var cache = {};\n return function(...args) {\n return cache[args] ?? (cache[args] = f.apply(this, args));\n };\n};\n\n/**\n * Check if two numbers are approximately equal\n * @param {number} a Number a\n * @param {number} b Number b\n * @param {number} [p=Number.EPSILON] The precision value\n * @return {boolean} True if numbers a and b are approximately equal\n */\nconst floatEquals = (a, b, p = Number.EPSILON) => Math.abs(a - b) < p;\n\n/**\n * Clamp a number between min and max\n * @param {number} a The number to clamp\n * @param {number} [min=0] The minimum value\n * @param {number} [max=1] The maximum value\n * @return {number} A clamped number\n */\nconst clamp = (a, min = 0, max = 1) => a < min ? min : (a > max ? max : a);\n\n/**\n * Get the fractional part of a number\n * @param {number} a The number from which to get the fractional part\n * @return {number} The fractional part of the number\n */\nconst frac = a => a >= 0 ? a - Math.floor(a) : a - Math.ceil(a);\n\n/**\n * Round n to d decimal places\n * @param {number} n The number to round\n * @param {number} [d=0] The number of decimal places to round to\n * @return {number} A rounded number\n */\nconst round = (n, d = 0) => {\n const p = Math.pow(10, d);\n return Math.round(n * p + Number.EPSILON) / p;\n}\n\n/**\n * Do a linear interpolation between a and b\n * @param {number} a The minimum number\n * @param {number} b The maximum number\n * @param {number} i The interpolation value, should be in the interval [0, 1]\n * @return {number} An interpolated value in the interval [a, b]\n */\nconst lerp = (a, b, i) => a + (b - a) * i;\n\n/**\n * Get the position of i between a and b\n * @param {number} a The minimum number\n * @param {number} b The maximum number\n * @param {number} i The interpolated value in the interval [a, b]\n * @return {number} The position of i between a and b\n */\nconst unlerp = (a, b, i) => (i - a) / (b - a);\n\n/**\n * Do a bilinear interpolation\n * @param {number} c00 Top-left value\n * @param {number} c10 Top-right value\n * @param {number} c01 Bottom-left value\n * @param {number} c11 Bottom-right value\n * @param {number} ix Interpolation value along x\n * @param {number} iy Interpolation value along y\n * @return {number} A bilinear interpolated value\n */\nconst blerp = (c00, c10, c01, c11, ix, iy) => lerp(lerp(c00, c10, ix), lerp(c01, c11, ix), iy);\n\n/**\n * Re-map a number i from range a1...a2 to b1...b2\n * @param {number} i The number to re-map\n * @param {number} a1\n * @param {number} a2\n * @param {number} b1\n * @param {number} b2\n * @return {number}\n */\nconst remap = (i, a1, a2, b1, b2) => b1 + (i - a1) * (b2 - b1) / (a2 - a1);\n\n/**\n * Do a smooth interpolation between a and b\n * @param {number} a The minimum number\n * @param {number} b The maximum number\n * @param {number} i The interpolation value\n * @return {number} An interpolated value in the interval [a, b]\n */\nconst smoothstep = (a, b, i) => lerp(a, b, 3 * Math.pow(i, 2) - 2 * Math.pow(i, 3));\n\n/**\n * Get an angle in radians\n * @param {number} degrees The angle in degrees\n * @return {number} The angle in radians\n */\nconst radians = degrees => (Math.PI / 180) * degrees;\n\n/**\n * Get an angle in degrees\n * @param {number} radians The angle in radians\n * @return {number} The angle in degrees\n */\nconst degrees = radians => (180 / Math.PI) * radians;\n\n/**\n * Get a random float in the interval [min, max)\n * @param {number} min Inclusive min\n * @param {number} max Exclusive max\n * @return {number} A random float in the interval [min, max)\n */\nconst randomBetween = (min, max) => Math.random() * (max - min) + min;\n\n/**\n * Get a random integer in the interval [min, max]\n * @param {number} min Inclusive min\n * @param {number} max Inclusive max\n * @return {number} A random integer in the interval [min, max]\n */\nconst randomIntBetween = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;\n\n/**\n * Get a normally-distributed random number\n * @param {number} [mu=0.5] The mean value\n * @param {number} [sigma=0.5] The standard deviation\n * @param {number} [samples=2] The number of samples\n * @return {number} A normally-distributed random number\n */\nconst cltRandom = (mu = 0.5, sigma = 0.5, samples = 2) => {\n let total = 0;\n for (let i = samples; i--;) {\n total += Math.random();\n }\n return mu + (total - samples / 2) / (samples / 2) * sigma;\n};\n\n/**\n * Get a normally-distributed random integer in the interval [min, max]\n * @param {number} min Inclusive min\n * @param {number} max Inclusive max\n * @return {number} A normally-distributed random integer\n */\nconst cltRandomInt = (min, max) => Math.floor(min + cltRandom(0.5, 0.5, 2) * (max + 1 - min));\n\n/**\n * Return a weighted random integer\n * @param {Array<number>} w An array of weights\n * @return {number} An index from w\n */\nconst weightedRandom = w => {\n let total = w.reduce((a, i) => a + i, 0), n = 0;\n const r = Math.random() * total;\n while (total > r) {\n total -= w[n++];\n }\n return n - 1;\n};\n\n/**\n * An interpolation function\n * @callback InterpolationFunction\n * @param {number} a The minimum number\n * @param {number} b The maximum number\n * @param {number} i The interpolation value, should be in the interval [0, 1]\n * @return {number} The interpolated value in the interval [a, b]\n */\n\n/**\n * Return an interpolated value from an array\n * @param {Array<number>} a An array of values interpolate\n * @param {number} i A number in the interval [0, 1]\n * @param {InterpolationFunction} [f=Math.lerp] The interpolation function to use\n * @return {number} An interpolated value in the interval [min(a), max(a)]\n */\nconst lerpArray = (a, i, f = lerp) => {\n const s = i * (a.length - 1);\n const p = clamp(Math.trunc(s), 0, a.length - 1);\n return f(a[p] || 0, a[p + 1] || 0, frac(s));\n};\n\n/**\n * Get the dot product of two vectors\n * @param {Array<number>} a Vector a\n * @param {Array<number>} b Vector b\n * @return {number} a ∙ b\n */\nconst dot = (a, b) => a.reduce((n, v, i) => n + v * b[i], 0);\n\n/**\n * Get the factorial of a number\n * @param {number} a\n * @return {number} a!\n */\nconst factorial = a => {\n let result = 1;\n for (let i = 2; i <= a; i++) {\n result *= i;\n }\n return result;\n};\n\n/**\n * Get the number of permutations of r elements from a set of n elements\n * @param {number} n\n * @param {number} r\n * @return {number} nPr\n */\nconst npr = (n, r) => factorial(n) / factorial(n - r);\n\n/**\n * Get the number of combinations of r elements from a set of n elements\n * @param {number} n\n * @param {number} r\n * @return {number} nCr\n */\nconst ncr = (n, r) => factorial(n) / (factorial(r) * factorial(n - r));\n\n/**\n * Generate all permutations of r elements from an array\n *\n * @example\n * ```js\n * permutations([1, 2, 3], 2);\n * ```\n *\n * Output:\n * ```json\n * [\n * [1, 2],\n * [1, 3],\n * [2, 1],\n * [2, 3],\n * [3, 1],\n * [3, 2]\n * ]\n * ```\n * @param {Array<*>} a\n * @param {number} r The number of elements to choose in each permutation\n * @return {Array<Array<*>>} An array of permutation arrays\n */\nconst permutations = (a, r) => {\n if (r === 1) {\n return a.map(item => [item]);\n }\n\n return a.reduce(\n (acc, item, i) => [\n ...acc,\n ...permutations(a.slice(0, i).concat(a.slice(i + 1)), r - 1).map(c => [item, ...c]),\n ],\n []\n );\n}\n\n/**\n * Generate all combinations of r elements from an array\n *\n * @example\n * ```js\n * combinations([1, 2, 3], 2);\n * ```\n *\n * Output:\n * ```json\n * [\n * [1, 2],\n * [1, 3],\n * [2, 3]\n * ]\n * ```\n * @param {Array<*>} a\n * @param {number} r The number of elements to choose in each combination\n * @return {Array<Array<*>>} An array of combination arrays\n */\nconst combinations = (a, r) => {\n if (r === 1) {\n return a.map(item => [item]);\n }\n\n return a.reduce(\n (acc, item, i) => [\n ...acc,\n ...combinations(a.slice(i + 1), r - 1).map(c => [item, ...c]),\n ],\n []\n );\n};\n\n/**\n * Get a cartesian product of arrays\n *\n * @example\n * ```js\n * cartesian([1, 2, 3], ['a', 'b']);\n * ```\n *\n * Output:\n * ```json\n * [\n * [1, \"a\"],\n * [1, \"b\"],\n * [2, \"a\"],\n * [2, \"b\"],\n * [3, \"a\"],\n * [3, \"b\"]\n * ]\n * ```\n */\nconst cartesian = (...arr) =>\n arr.reduce(\n (a, b) => a.flatMap(c => b.map(d => [...c, d])),\n [[]]\n );\n\n/**\n * A function for generating array values\n * @callback TimesFunction\n * @param {number} i The array index\n * @return {*} The array value\n */\n\n/**\n * Return a new array with length n by calling function f(i) on each element\n * @param {TimesFunction} f\n * @param {number} n The size of the array\n * @return {Array<*>}\n */\nconst times = (f, n) => Array(n).fill(0).map((_, i) => f(i));\n\n/**\n * Return an array containing numbers 0->(n - 1)\n * @param {number} n The size of the array\n * @return {Array<number>} An array of integers 0->(n - 1)\n */\nconst range = n => times(i => i, n);\n\n/**\n * Zip multiple arrays together, i.e. ([1, 2, 3], [a, b, c]) => [[1, a], [2, b], [3, c]]\n * @param {...Array<*>} a The arrays to zip\n * @return {Array<Array<*>>}\n */\nconst zip = (...a) => times(i => a.map(a => a[i]), Math.max(...a.map(a => a.length)));\n\n/**\n * Return array[i] with positive and negative wrapping\n * @param {Array<*>} a The array to access\n * @param {number} i The positively/negatively wrapped array index\n * @return {*} An element from the array\n */\nconst at = (a, i) => a[i < 0 ? a.length - (Math.abs(i + 1) % a.length) - 1 : i % a.length];\n\n/**\n * Return the last element of an array without removing it\n * @param {Array<*>} a\n * @return {*} The last element from the array\n */\nconst peek = (a) => {\n if (!a.length) {\n return undefined;\n }\n\n return a[a.length - 1];\n};\n\n/**\n * Return the index for a given position in an unrolled 2d array\n * @param {number} x The x position\n * @param {number} y The y position\n * @param {number} w The width of the 2d array\n * @returns {number} The index in the unrolled array\n */\nconst ind = (x, y, w) => x + y * w;\n\n/**\n * Return the position for a given index in an unrolled 2d array\n * @param {number} i The index\n * @param {number} w The width of the 2d array\n * @returns {Array<number>} The position as a 2-tuple\n */\nconst pos = (i, w) => [i % w, Math.floor(i / w)];\n\n/**\n * Chop an array into chunks of size n\n * @param {Array<*>} a\n * @param {number} n The chunk size\n * @return {Array<Array<*>>} An array of array chunks\n */\nconst chunk = (a, n) => times(i => a.slice(i * n, i * n + n), Math.ceil(a.length / n));\n\n/**\n * Randomly shuffle a shallow copy of an array\n * @param {Array<*>} a\n * @return {Array<*>} The shuffled array\n */\nconst shuffle = a => a.slice().sort(() => Math.random() - 0.5);\n\n/**\n * Flatten an object\n * @param {object} o\n * @param {string} concatenator The string to use for concatenating keys\n * @return {object} A flattened object\n */\nconst flat = (o, concatenator = '.') => {\n return Object.keys(o).reduce((acc, key) => {\n if (o[key] instanceof Date) {\n return {\n ...acc,\n [key]: o[key].toISOString(),\n };\n }\n\n if (typeof o[key] !== 'object' || !o[key]) {\n return {\n ...acc,\n [key]: o[key],\n };\n }\n const flattened = flat(o[key], concatenator);\n\n return {\n ...acc,\n ...Object.keys(flattened).reduce(\n (childAcc, childKey) => ({\n ...childAcc,\n [`${key}${concatenator}${childKey}`]: flattened[childKey],\n }),\n {}\n ),\n };\n }, {});\n};\n\n/**\n * Unflatten an object\n * @param {object} o\n * @param {string} concatenator The string to check for in concatenated keys\n * @return {object} An un-flattened object\n */\nconst unflat = (o, concatenator = '.') => {\n let result = {}, temp, substrings, property, i;\n\n for (property in o) {\n substrings = property.split(concatenator);\n temp = result;\n for (i = 0; i < substrings.length - 1; i++) {\n if (!(substrings[i] in temp)) {\n if (isFinite(substrings[i + 1])) {\n temp[substrings[i]] = [];\n } else {\n temp[substrings[i]] = {};\n }\n }\n temp = temp[substrings[i]];\n }\n temp[substrings[substrings.length - 1]] = o[property];\n }\n\n return result;\n};\n\n/**\n * A split predicate\n * @callback SplitPredicate\n * @param {any} value The current value\n * @return {boolean} True if the array should split at this index\n */\n\n/**\n * Split an array into sub-arrays based on a predicate\n * @param {Array<*>} array\n * @param {SplitPredicate} predicate\n * @return {Array<Array<*>>} An array of arrays\n */\nconst split = (array, predicate) => {\n const result = [];\n let current = [];\n for (const value of array) {\n if (predicate(value)) {\n if (current.length) {\n result.push(current);\n }\n current = [value];\n } else {\n current.push(value);\n }\n }\n result.push(current);\n\n return result;\n};\n\n/**\n * Pluck keys from an object\n * @param {object} o\n * @param {...string} keys The keys to pluck from the object\n * @return {object} An object containing the plucked keys\n */\nconst pluck = (o, ...keys) => {\n return keys.reduce(\n (result, key) => Object.assign(result, { [key]: o[key] }),\n {}\n );\n};\n\n/**\n * Exclude keys from an object\n * @param {object} o\n * @param {...string} keys The keys to exclude from the object\n * @return {object} An object containing all keys except excluded keys\n */\nconst exclude = (o, ...keys) => {\n return Object.fromEntries(\n Object.entries(o).filter(([key]) => !keys.includes(key))\n );\n};\n\nif (true) {\n module.exports = {\n memoize,\n floatEquals,\n clamp,\n frac,\n round,\n lerp,\n unlerp,\n blerp,\n remap,\n smoothstep,\n radians,\n degrees,\n randomBetween,\n randomIntBetween,\n cltRandom,\n cltRandomInt,\n weightedRandom,\n lerpArray,\n dot,\n factorial,\n npr,\n ncr,\n permutations,\n combinations,\n cartesian,\n times,\n range,\n zip,\n at,\n peek,\n ind,\n pos,\n chunk,\n shuffle,\n flat,\n unflat,\n split,\n pluck,\n exclude,\n };\n}\n\n\n/***/ }),\n\n/***/ \"./node_modules/@basementuniverse/vec/vec.js\":\n/*!***************************************************!*\\\n !*** ./node_modules/@basementuniverse/vec/vec.js ***!\n \\***************************************************/\n/***/ ((module) => {\n\n/**\n * @overview A small vector and matrix library\n * @author Gordon Larrigan\n */\n\nconst _vec_times = (f, n) => Array(n).fill(0).map((_, i) => f(i));\nconst _vec_chunk = (a, n) => _vec_times(i => a.slice(i * n, i * n + n), Math.ceil(a.length / n));\nconst _vec_dot = (a, b) => a.reduce((n, v, i) => n + v * b[i], 0);\nconst _vec_is_vec2 = a => typeof a === 'object' && 'x' in a && 'y' in a;\nconst _vec_is_vec3 = a => typeof a === 'object' && 'x' in a && 'y' in a && 'z' in a;\n\n/**\n * A 2d vector\n * @typedef {Object} vec2\n * @property {number} x The x component of the vector\n * @property {number} y The y component of the vector\n */\n\n/**\n * Create a new 2d vector\n * @param {number|vec2} [x] The x component of the vector, or a vector to copy\n * @param {number} [y] The y component of the vector\n * @return {vec2} A new 2d vector\n * @example <caption>various ways to initialise a vector</caption>\n * let a = vec2(3, 2); // (3, 2)\n * let b = vec2(4); // (4, 4)\n * let c = vec2(a); // (3, 2)\n * let d = vec2(); // (0, 0)\n */\nconst vec2 = (x, y) => {\n if (!x && !y) {\n return { x: 0, y: 0 };\n }\n if (_vec_is_vec2(x)) {\n return { x: x.x || 0, y: x.y || 0 };\n }\n return { x: x, y: y ?? x };\n};\n\n/**\n * Get the components of a vector as an array\n * @param {vec2} a The vector to get components from\n * @return {Array<number>} The vector components as an array\n */\nvec2.components = a => [a.x, a.y];\n\n/**\n * Create a vector from an array of components\n * @param {Array<number>} components The components of the vector\n * @return {vec2} A new vector\n */\nvec2.fromComponents = components => vec2(...components.slice(0, 2));\n\n/**\n * Return a unit vector (1, 0)\n * @return {vec2} A unit vector (1, 0)\n */\nvec2.ux = () => vec2(1, 0);\n\n/**\n * Return a unit vector (0, 1)\n * @return {vec2} A unit vector (0, 1)\n */\nvec2.uy = () => vec2(0, 1);\n\n/**\n * Add vectors\n * @param {vec2} a Vector a\n * @param {vec2|number} b Vector or scalar b\n * @return {vec2} a + b\n */\nvec2.add = (a, b) => ({ x: a.x + (b.x ?? b), y: a.y + (b.y ?? b) });\n\n/**\n * Subtract vectors\n * @param {vec2} a Vector a\n * @param {vec2|number} b Vector or scalar b\n * @return {vec2} a - b\n */\nvec2.sub = (a, b) => ({ x: a.x - (b.x ?? b), y: a.y - (b.y ?? b) });\n\n/**\n * Scale a vector\n * @param {vec2} a Vector a\n * @param {vec2|number} b Vector or scalar b\n * @return {vec2} a * b\n */\nvec2.mul = (a, b) => ({ x: a.x * (b.x ?? b), y: a.y * (b.y ?? b) });\n\n/**\n * Scale a vector by a scalar, alias for vec2.mul\n * @param {vec2} a Vector a\n * @param {number} b Scalar b\n * @return {vec2} a * b\n */\nvec2.scale = (a, b) => vec2.mul(a, b);\n\n/**\n * Divide a vector\n * @param {vec2} a Vector a\n * @param {vec2|number} b Vector or scalar b\n * @return {vec2} a / b\n */\nvec2.div = (a, b) => ({ x: a.x / (b.x ?? b), y: a.y / (b.y ?? b) });\n\n/**\n * Get the length of a vector\n * @param {vec2} a Vector a\n * @return {number} |a|\n */\nvec2.len = a => Math.sqrt(a.x * a.x + a.y * a.y);\n\n/**\n * Get the length of a vector using taxicab geometry\n * @param {vec2} a Vector a\n * @return {number} |a|\n */\nvec2.manhattan = a => Math.abs(a.x) + Math.abs(a.y);\n\n/**\n * Normalise a vector\n * @param {vec2} a The vector to normalise\n * @return {vec2} ^a\n */\nvec2.nor = a => {\n let len = vec2.len(a);\n return len ? { x: a.x / len, y: a.y / len } : vec2();\n};\n\n/**\n * Get a dot product of vectors\n * @param {vec2} a Vector a\n * @param {vec2} b Vector b\n * @return {number} a ∙ b\n */\nvec2.dot = (a, b) => a.x * b.x + a.y * b.y;\n\n/**\n * Rotate a vector by r radians\n * @param {vec2} a The vector to rotate\n * @param {number} r The angle to rotate by, measured in radians\n * @return {vec2} A rotated vector\n */\nvec2.rot = (a, r) => {\n let s = Math.sin(r),\n c = Math.cos(r);\n return { x: c * a.x - s * a.y, y: s * a.x + c * a.y };\n};\n\n/**\n * Fast method to rotate a vector by -90, 90 or 180 degrees\n * @param {vec2} a The vector to rotate\n * @param {number} r 1 for 90 degrees (cw), -1 for -90 degrees (ccw), 2 or -2 for 180 degrees\n * @return {vec2} A rotated vector\n */\nvec2.rotf = (a, r) => {\n switch (r) {\n case 1: return vec2(a.y, -a.x);\n case -1: return vec2(-a.y, a.x);\n case 2: case -2: return vec2(-a.x, -a.y);\n default: return a;\n }\n};\n\n/**\n * Scalar cross product of two vectors\n * @param {vec2} a Vector a\n * @param {vec2} b Vector b\n * @return {number} a × b\n */\nvec2.cross = (a, b) => {\n return a.x * b.y - a.y * b.x;\n};\n\n/**\n * Check if two vectors are equal\n * @param {vec2} a Vector a\n * @param {vec2} b Vector b\n * @return {boolean} True if vectors a and b are equal, false otherwise\n */\nvec2.eq = (a, b) => a.x === b.x && a.y === b.y;\n\n/**\n * Get the angle of a vector\n * @param {vec2} a Vector a\n * @return {number} The angle of vector a in radians\n */\nvec2.rad = a => Math.atan2(a.y, a.x);\n\n/**\n * Copy a vector\n * @param {vec2} a The vector to copy\n * @return {vec2} A copy of vector a\n */\nvec2.cpy = a => vec2(a);\n\n/**\n * A function to call on each component of a 2d vector\n * @callback vec2MapCallback\n * @param {number} value The component value\n * @param {'x' | 'y'} label The component label (x or y)\n * @return {number} The mapped component\n */\n\n/**\n * Call a function on each component of a vector and build a new vector from the results\n * @param {vec2} a Vector a\n * @param {vec2MapCallback} f The function to call on each component of the vector\n * @return {vec2} Vector a mapped through f\n */\nvec2.map = (a, f) => ({ x: f(a.x, 'x'), y: f(a.y, 'y') });\n\n/**\n * Convert a vector into a string\n * @param {vec2} a The vector to convert\n * @param {string} [s=', '] The separator string\n * @return {string} A string representation of the vector\n */\nvec2.str = (a, s = ', ') => `${a.x}${s}${a.y}`;\n\n/**\n * Swizzle a vector with a string of component labels\n *\n * The string can contain:\n * - `x` or `y`\n * - `u` or `v` (aliases for `x` and `y`, respectively)\n * - `X`, `Y`, `U`, `V` (negated versions of the above)\n * - `0` or `1` (these will be passed through unchanged)\n * - `.` to return the component that would normally be at this position (or 0)\n *\n * Any other characters will default to 0\n * @param {vec2} a The vector to swizzle\n * @param {string} [s='..'] The swizzle string\n * @return {Array<number>} The swizzled components\n * @example <caption>swizzling a vector</caption>\n * let a = vec2(3, -2);\n * vec2.swiz(a, 'x'); // [3]\n * vec2.swiz(a, 'yx'); // [-2, 3]\n * vec2.swiz(a, 'xY'); // [3, 2]\n * vec2.swiz(a, 'Yy'); // [2, -2]\n * vec2.swiz(a, 'x.x'); // [3, -2, 3]\n * vec2.swiz(a, 'y01x'); // [-2, 0, 1, 3]\n */\nvec2.swiz = (a, s = '..') => {\n const result = [];\n s.split('').forEach((c, i) => {\n switch (c) {\n case 'x': case 'u': result.push(a.x); break;\n case 'y': case 'v': result.push(a.y); break;\n case 'X': case 'U': result.push(-a.x); break;\n case 'Y': case 'V': result.push(-a.y); break;\n case '0': result.push(0); break;\n case '1': result.push(1); break;\n case '.': result.push([a.x, a.y][i] ?? 0); break;\n default: result.push(0);\n }\n });\n return result;\n};\n\n/**\n * Polar coordinates for a 2d vector\n * @typedef {Object} polarCoordinates2d\n * @property {number} r The magnitude (radius) of the vector\n * @property {number} theta The angle of the vector\n */\n\n/**\n * Convert a vector into polar coordinates\n * @param {vec2} a The vector to convert\n * @return {polarCoordinates2d} The magnitude and angle of the vector\n */\nvec2.polar = a => ({ r: vec2.len(a), theta: Math.atan2(a.y, a.x) });\n\n/**\n * Convert polar coordinates into a vector\n * @param {number} r The magnitude (radius) of the vector\n * @param {number} theta The angle of the vector\n * @return {vec2} A vector with the given angle and magnitude\n */\nvec2.fromPolar = (r, theta) => vec2(r * Math.cos(theta), r * Math.sin(theta));\n\n/**\n * A 3d vector\n * @typedef {Object} vec3\n * @property {number} x The x component of the vector\n * @property {number} y The y component of the vector\n * @property {number} z The z component of the vector\n */\n\n/**\n * Create a new 3d vector\n * @param {number|vec3|vec2} [x] The x component of the vector, or a vector to copy\n * @param {number} [y] The y component of the vector, or the z component if x is a vec2\n * @param {number} [z] The z component of the vector\n * @return {vec3} A new 3d vector\n * @example <caption>various ways to initialise a vector</caption>\n * let a = vec3(3, 2, 1); // (3, 2, 1)\n * let b = vec3(4, 5); // (4, 5, 0)\n * let c = vec3(6); // (6, 6, 6)\n * let d = vec3(a); // (3, 2, 1)\n * let e = vec3(); // (0, 0, 0)\n * let f = vec3(vec2(1, 2), 3); // (1, 2, 3)\n * let g = vec3(vec2(4, 5)); // (4, 5, 0)\n */\nconst vec3 = (x, y, z) => {\n if (!x && !y && !z) {\n return { x: 0, y: 0, z: 0 };\n }\n if (_vec_is_vec3(x)) {\n return { x: x.x || 0, y: x.y || 0, z: x.z || 0 };\n }\n if (_vec_is_vec2(x)) {\n return { x: x.x || 0, y: x.y || 0, z: y || 0 };\n }\n return { x: x, y: y ?? x, z: z ?? x };\n};\n\n/**\n * Get the components of a vector as an array\n * @param {vec3} a The vector to get components from\n * @return {Array<number>} The vector components as an array\n */\nvec3.components = a => [a.x, a.y, a.z];\n\n/**\n * Create a vector from an array of components\n * @param {Array<number>} components The components of the vector\n * @return {vec3} A new vector\n */\nvec3.fromComponents = components => vec3(...components.slice(0, 3));\n\n/**\n * Return a unit vector (1, 0, 0)\n * @return {vec3} A unit vector (1, 0, 0)\n */\nvec3.ux = () => vec3(1, 0, 0);\n\n/**\n * Return a unit vector (0, 1, 0)\n * @return {vec3} A unit vector (0, 1, 0)\n */\nvec3.uy = () => vec3(0, 1, 0);\n\n/**\n * Return a unit vector (0, 0, 1)\n * @return {vec3} A unit vector (0, 0, 1)\n */\nvec3.uz = () => vec3(0, 0, 1);\n\n/**\n * Add vectors\n * @param {vec3} a Vector a\n * @param {vec3|number} b Vector or scalar b\n * @return {vec3} a + b\n */\nvec3.add = (a, b) => ({ x: a.x + (b.x ?? b), y: a.y + (b.y ?? b), z: a.z + (b.z ?? b) });\n\n/**\n * Subtract vectors\n * @param {vec3} a Vector a\n * @param {vec3|number} b Vector or scalar b\n * @return {vec3} a - b\n */\nvec3.sub = (a, b) => ({ x: a.x - (b.x ?? b), y: a.y - (b.y ?? b), z: a.z - (b.z ?? b) });\n\n/**\n * Scale a vector\n * @param {vec3} a Vector a\n * @param {vec3|number} b Vector or scalar b\n * @return {vec3} a * b\n */\nvec3.mul = (a, b) => ({ x: a.x * (b.x ?? b), y: a.y * (b.y ?? b), z: a.z * (b.z ?? b) });\n\n/**\n * Scale a vector by a scalar, alias for vec3.mul\n * @param {vec3} a Vector a\n * @param {number} b Scalar b\n * @return {vec3} a * b\n */\nvec3.scale = (a, b) => vec3.mul(a, b);\n\n/**\n * Divide a vector\n * @param {vec3} a Vector a\n * @param {vec3|number} b Vector or scalar b\n * @return {vec3} a / b\n */\nvec3.div = (a, b) => ({ x: a.x / (b.x ?? b), y: a.y / (b.y ?? b), z: a.z / (b.z ?? b) });\n\n/**\n * Get the length of a vector\n * @param {vec3} a Vector a\n * @return {number} |a|\n */\nvec3.len = a => Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z);\n\n/**\n * Get the length of a vector using taxicab geometry\n * @param {vec3} a Vector a\n * @return {number} |a|\n */\nvec3.manhattan = a => Math.abs(a.x) + Math.abs(a.y) + Math.abs(a.z);\n\n/**\n * Normalise a vector\n * @param {vec3} a The vector to normalise\n * @return {vec3} ^a\n */\nvec3.nor = a => {\n let len = vec3.len(a);\n return len ? { x: a.x / len, y: a.y / len, z: a.z / len } : vec3();\n};\n\n/**\n * Get a dot product of vectors\n * @param {vec3} a Vector a\n * @param {vec3} b Vector b\n * @return {number} a ∙ b\n */\nvec3.dot = (a, b) => a.x * b.x + a.y * b.y + a.z * b.z;\n\n/**\n * Rotate a vector using a rotation matrix\n * @param {vec3} a The vector to rotate\n * @param {mat} m The rotation matrix\n * @return {vec3} A rotated vector\n */\nvec3.rot = (a, m) => vec3(\n vec3.dot(vec3.fromComponents(mat.row(m, 1)), a),\n vec3.dot(vec3.fromComponents(mat.row(m, 2)), a),\n vec3.dot(vec3.fromComponents(mat.row(m, 3)), a)\n);\n\n/**\n * Rotate a vector by r radians around the x axis\n * @param {vec3} a The vector to rotate\n * @param {number} r The angle to rotate by, measured in radians\n * @return {vec3} A rotated vector\n */\nvec3.rotx = (a, r) => vec3(\n a.x,\n a.y * Math.cos(r) - a.z * Math.sin(r),\n a.y * Math.sin(r) + a.z * Math.cos(r)\n);\n\n/**\n * Rotate a vector by r radians around the y axis\n * @param {vec3} a The vector to rotate\n * @param {number} r The angle to rotate by, measured in radians\n * @return {vec3} A rotated vector\n */\nvec3.roty = (a, r) => vec3(\n a.x * Math.cos(r) + a.z * Math.sin(r),\n a.y,\n -a.x * Math.sin(r) + a.z * Math.cos(r)\n);\n\n/**\n * Rotate a vector by r radians around the z axis\n * @param {vec3} a The vector to rotate\n * @param {number} r The angle to rotate by, measured in radians\n * @return {vec3} A rotated vector\n */\nvec3.rotz = (a, r) => vec3(\n a.x * Math.cos(r) - a.y * Math.sin(r),\n a.x * Math.sin(r) + a.y * Math.cos(r),\n a.z\n);\n\n/**\n * Rotate a vector using a quaternion\n * @param {vec3} a The vector to rotate\n * @param {Array<number>} q The quaternion to rotate by\n * @return {vec3} A rotated vector\n */\nvec3.rotq = (v, q) => {\n if (q.length !== 4) {\n return vec3();\n }\n\n const d = Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);\n if (d === 0) {\n return vec3();\n }\n\n const uq = [q[0] / d, q[1] / d, q[2] / d, q[3] / d];\n const u = vec3(...uq.slice(0, 3));\n const s = uq[3];\n return vec3.add(\n vec3.add(\n vec3.mul(u, 2 * vec3.dot(u, v)),\n vec3.mul(v, s * s - vec3.dot(u, u))\n ),\n vec3.mul(vec3.cross(u, v), 2 * s)\n );\n};\n\n/**\n * Rotate a vector using Euler angles\n * @param {vec3} a The vector to rotate\n * @param {vec3} e The Euler angles to rotate by\n * @return {vec3} A rotated vector\n */\nvec3.rota = (a, e) => vec3.rotz(vec3.roty(vec3.rotx(a, e.x), e.y), e.z);\n\n/**\n * Get the cross product of vectors\n * @param {vec3} a Vector a\n * @param {vec3} b Vector b\n * @return {vec3} a × b\n */\nvec3.cross = (a, b) => vec3(\n a.y * b.z - a.z * b.y,\n a.z * b.x - a.x * b.z,\n a.x * b.y - a.y * b.x\n);\n\n/**\n * Check if two vectors are equal\n * @param {vec3} a Vector a\n * @param {vec3} b Vector b\n * @return {boolean} True if vectors a and b are equal, false otherwise\n */\nvec3.eq = (a, b) => a.x === b.x && a.y === b.y && a.z === b.z;\n\n/**\n * Get the angle of a vector from the x axis\n * @param {vec3} a Vector a\n * @return {number} The angle of vector a in radians\n */\nvec3.radx = a => Math.atan2(a.z, a.y);\n\n/**\n * Get the angle of a vector from the y axis\n * @param {vec3} a Vector a\n * @return {number} The angle of vector a in radians\n */\nvec3.rady = a => Math.atan2(a.x, a.y);\n\n/**\n * Get the angle of a vector from the z axis\n * @param {vec3} a Vector a\n * @return {number} The angle of vector a in radians\n */\nvec3.radz = a => Math.atan2(a.y, a.z);\n\n/**\n * Copy a vector\n * @param {vec3} a The vector to copy\n * @return {vec3} A copy of vector a\n */\nvec3.cpy = a => vec3(a);\n\n/**\n * A function to call on each component of a 3d vector\n * @callback vec3MapCallback\n * @param {number} value The component value\n * @param {'x' | 'y' | 'z'} label The component label (x, y or z)\n * @return {number} The mapped component\n */\n\n/**\n * Call a function on each component of a vector and build a new vector from the results\n * @param {vec3} a Vector a\n * @param {vec3MapCallback} f The function to call on each component of the vector\n * @return {vec3} Vector a mapped through f\n */\nvec3.map = (a, f) => ({ x: f(a.x, 'x'), y: f(a.y, 'y'), z: f(a.z, 'z') });\n\n/**\n * Convert a vector into a string\n * @param {vec3} a The vector to convert\n * @param {string} [s=', '] The separator string\n * @return {string} A string representation of the vector\n */\nvec3.str = (a, s = ', ') => `${a.x}${s}${a.y}${s}${a.z}`;\n\n/**\n * Swizzle a vector with a string of component labels\n *\n * The string can contain:\n * - `x`, `y` or `z`\n * - `u`, `v` or `w` (aliases for `x`, `y` and `z`, respectively)\n * - `r`, `g` or `b` (aliases for `x`, `y` and `z`, respectively)\n * - `X`, `Y`, `Z`, `U`, `V`, `W`, `R`, `G`, `B` (negated versions of the above)\n * - `0` or `1` (these will be passed through unchanged)\n * - `.` to return the component that would normally be at this position (or 0)\n *\n * Any other characters will default to 0\n * @param {vec3} a The vector to swizzle\n * @param {string} [s='...'] The swizzle string\n * @return {Array<number>} The swizzled components\n * @example <caption>swizzling a vector</caption>\n * let a = vec3(3, -2, 1);\n * vec3.swiz(a, 'x'); // [3]\n * vec3.swiz(a, 'zyx'); // [1, -2, 3]\n * vec3.swiz(a, 'xYZ'); // [3, 2, -1]\n * vec3.swiz(a, 'Zzx'); // [-1, 1, 3]\n * vec3.swiz(a, 'x.x'); // [3, -2, 3]\n * vec3.swiz(a, 'y01zx'); // [-2, 0, 1, 1, 3]\n */\nvec3.swiz = (a, s = '...') => {\n const result = [];\n s.split('').forEach((c, i) => {\n switch (c) {\n case 'x': case 'u': case 'r': result.push(a.x); break;\n case 'y': case 'v': case 'g': result.push(a.y); break;\n case 'z': case 'w': case 'b': result.push(a.z); break;\n case 'X': case 'U': case 'R': result.push(-a.x); break;\n case 'Y': case 'V': case 'G': result.push(-a.y); break;\n case 'Z': case 'W': case 'B': result.push(-a.z); break;\n case '0': result.push(0); break;\n case '1': result.push(1); break;\n case '.': result.push([a.x, a.y, a.z][i] ?? 0); break;\n default: result.push(0);\n }\n });\n return result;\n};\n\n/**\n * Polar coordinates for a 3d vector\n * @typedef {Object} polarCoordinates3d\n * @property {number} r The magnitude (radius) of the vector\n * @property {number} theta The tilt angle of the vector\n * @property {number} phi The pan angle of the vector\n */\n\n/**\n * Convert a vector into polar coordinates\n * @param {vec3} a The vector to convert\n * @return {polarCoordinates3d} The magnitude, tilt and pan of the vector\n */\nvec3.polar = a => {\n let r = vec3.len(a),\n theta = Math.acos(a.y / r),\n phi = Math.atan2(a.z, a.x);\n return { r, theta, phi };\n};\n\n/**\n * Convert polar coordinates into a vector\n * @param {number} r The magnitude (radius) of the vector\n * @param {number} theta The tilt of the vector\n * @param {number} phi The pan of the vector\n * @return {vec3} A vector with the given angle and magnitude\n */\nvec3.fromPolar = (r, theta, phi) => {\n const sinTheta = Math.sin(theta);\n return vec3(\n r * sinTheta * Math.cos(phi),\n r * Math.cos(theta),\n r * sinTheta * Math.sin(phi)\n );\n};\n\n/**\n * A matrix\n * @typedef {Object} mat\n * @property {number} m The number of rows in the matrix\n * @property {number} n The number of columns in the matrix\n * @property {Array<number>} entries The matrix values\n */\n\n/**\n * Create a new matrix\n * @param {number} [m=4] The number of rows\n * @param {number} [n=4] The number of columns\n * @param {Array<number>} [entries=[]] Matrix values in reading order\n * @return {mat} A new matrix\n */\nconst mat = (m = 4, n = 4, entries = []) => ({\n m, n,\n entries: entries.concat(Array(m * n).fill(0)).slice(0, m * n)\n});\n\n/**\n * Get an identity matrix of size n\n * @param {number} n The size of the matrix\n * @return {mat} An identity matrix\n */\nmat.identity = n => mat(n, n, Array(n * n).fill(0).map((v, i) => +(Math.floor(i / n) === i % n)));\n\n/**\n * Get an entry from a matrix\n * @param {mat} a Matrix a\n * @param {number} i The row offset\n * @param {number} j The column offset\n * @return {number} The value at position (i, j) in matrix a\n */\nmat.get = (a, i, j) => a.entries[(j - 1) + (i - 1) * a.n];\n\n/**\n * Set an entry of a matrix\n * @param {mat} a Matrix a\n * @param {number} i The row offset\n * @param {number} j The column offset\n * @param {number} v The value to set in matrix a\n */\nmat.set = (a, i, j, v) => { a.entries[(j - 1) + (i - 1) * a.n] = v; };\n\n/**\n * Get a row from a matrix as an array\n * @param {mat} a Matrix a\n * @param {number} m The row offset\n * @return {Array<number>} Row m from matrix a\n */\nmat.row = (a, m) => {\n const s = (m - 1) * a.n;\n return a.entries.slice(s, s + a.n);\n};\n\n/**\n * Get a column from a matrix as an array\n * @param {mat} a Matrix a\n * @param {number} n The column offset\n * @return {Array<number>} Column n from matrix a\n */\nmat.col = (a, n) => _vec_times(i => mat.get(a, (i + 1), n), a.m);\n\n/**\n * Add matrices\n * @param {mat} a Matrix a\n * @param {mat} b Matrix b\n * @return {mat} a + b\n */\nmat.add = (a, b) => a.m === b.m && a.n === b.n && mat.map(a, (v, i) => v + b.entries[i]);\n\n/**\n * Subtract matrices\n * @param {mat} a Matrix a\n * @param {mat} b Matrix b\n * @return {mat} a - b\n */\nmat.sub = (a, b) => a.m === b.m && a.n === b.n && mat.map(a, (v, i) => v - b.entries[i]);\n\n/**\n * Multiply matrices\n * @param {mat} a Matrix a\n * @param {mat} b Matrix b\n * @return {mat|false} ab or false if the matrices cannot be multiplied\n */\nmat.mul = (a, b) => {\n if (a.n !== b.m) { return false; }\n const result = mat(a.m, b.n);\n for (let i = 1; i <= a.m; i++) {\n for (let j = 1; j <= b.n; j++) {\n mat.set(result, i, j, _vec_dot(mat.row(a, i), mat.col(b, j)));\n }\n }\n return result;\n};\n\n/**\n * Multiply a matrix by a vector\n * @param {mat} a Matrix a\n * @param {vec2|vec3|number[]} b Vector b\n * @return {vec2|vec3|number[]|false} ab or false if the matrix and vector cannot be multiplied\n */\nmat.mulv = (a, b) => {\n let n, bb, rt;\n if (_vec_is_vec3(b)) {\n bb = vec3.components(b);\n n = 3;\n rt = vec3.fromComponents;\n } else if (_vec_is_vec2(b)) {\n bb = vec2.components(b);\n n = 2;\n rt = vec2.fromComponents;\n } else {\n bb = b;\n n = b.length ?? 0;\n rt = v => v;\n }\n if (a.n !== n) { return false; }\n const result = [];\n for (let i = 1; i <= a.m; i++) {\n result.push(_vec_dot(mat.row(a, i), bb));\n }\n return rt(result);\n}\n\n/**\n * Scale a matrix\n * @param {mat} a Matrix a\n * @param {number} b Scalar b\n * @return {mat} a * b\n */\nmat.scale = (a, b) => mat.map(a, v => v * b);\n\n/**\n * Transpose a matrix\n * @param {mat} a The matrix to transpose\n * @return {mat} A transposed matrix\n */\nmat.trans = a => mat(a.n, a.m, _vec_times(i => mat.col(a, (i + 1)), a.n).flat());\n\n/**\n * Get the minor of a matrix\n * @param {mat} a Matrix a\n * @param {number} i The row offset\n * @param {number} j The column offset\n * @return {mat|false} The (i, j) minor of matrix a or false if the matrix is not square\n */\nmat.minor = (a, i, j) => {\n if (a.m !== a.n) { return false; }\n const entries = [];\n for (let ii = 1; ii <= a.m; ii++) {\n if (ii === i) { continue; }\n for (let jj = 1; jj <= a.n; jj++) {\n if (jj === j) { continue; }\n entries.push(mat.get(a, ii, jj));\n }\n }\n return mat(a.m - 1, a.n - 1, entries);\n};\n\n/**\n * Get the determinant of a matrix\n * @param {mat} a Matrix a\n * @return {number|false} |a| or false if the matrix is not square\n */\nmat.det = a => {\n if (a.m !== a.n) { return false; }\n if (a.m === 1) {\n return a.entries[0];\n }\n if (a.m === 2) {\n return a.entries[0] * a.entries[3] - a.entries[1] * a.entries[2];\n }\n let total = 0, sign = 1;\n for (let j = 1; j <= a.n; j++) {\n total += sign * a.entries[j - 1] * mat.det(mat.minor(a, 1, j));\n sign *= -1;\n }\n return total;\n};\n\n/**\n * Normalise a matrix\n * @param {mat} a The matrix to normalise\n * @return {mat|false} ^a or false if the matrix is not square\n */\nmat.nor = a => {\n if (a.m !== a.n) { return false; }\n const d = mat.det(a);\n return mat.map(a, i => i * d);\n};\n\n/**\n * Get the adjugate of a matrix\n * @param {mat} a The matrix from which to get the adjugate\n * @return {mat} The adjugate of a\n */\nmat.adj = a => {\n const minors = mat(a.m, a.n);\n for (let i = 1; i <= a.m; i++) {\n for (let j = 1; j <= a.n; j++) {\n mat.set(minors, i, j, mat.det(mat.minor(a, i, j)));\n }\n }\n const cofactors = mat.map(minors, (v, i) => v * (i % 2 ? -1 : 1));\n return mat.trans(cofactors);\n};\n\n/**\n * Get the inverse of a matrix\n * @param {mat} a The matrix to invert\n * @return {mat|false} a^-1 or false if the matrix has no inverse\n */\nmat.inv = a => {\n if (a.m !== a.n) { return false; }\n const d = mat.det(a);\n if (d === 0) { return false; }\n return mat.scale(mat.adj(a), 1 / d);\n};\n\n/**\n * Check if two matrices are equal\n * @param {mat} a Matrix a\n * @param {mat} b Matrix b\n * @return {boolean} True if matrices a and b are identical, false otherwise\n */\nmat.eq = (a, b) => a.m === b.m && a.n === b.n && mat.str(a) === mat.str(b);\n\n/**\n * Copy a matrix\n * @param {mat} a The matrix to copy\n * @return {mat} A copy of matrix a\n */\nmat.cpy = a => mat(a.m, a.n, [...a.entries]);\n\n/**\n * A function to call on each entry of a matrix\n * @callback matrixMapCallback\n * @param {number} value The entry value\n * @param {number} index The entry index\n * @param {Array<number>} entries The array of matrix entries\n * @return {number} The mapped entry\n */\n\n/**\n * Call a function on each entry of a matrix and build a new matrix from the results\n * @param {mat} a Matrix a\n * @param {matrixMapCallback} f The function to call on each entry of the matrix\n * @return {mat} Matrix a mapped through f\n */\nmat.map = (a, f) => mat(a.m, a.n, a.entries.map(f));\n\n/**\n * Convert a matrix into a string\n * @param {mat} a The matrix to convert\n * @param {string} [ms=', '] The separator string for columns\n * @param {string} [ns='\\n'] The separator string for rows\n * @return {string} A string representation of the matrix\n */\nmat.str = (a, ms = ', ', ns = '\\n') => _vec_chunk(a.entries, a.n).map(r => r.join(ms)).join(ns);\n\nif (true) {\n module.exports = { vec2, vec3, mat };\n}\n\n\n/***/ }),\n\n/***/ \"./node_modules/poly-decomp/src/index.js\":\n/*!***********************************************!*\\\n !*** ./node_modules/poly-decomp/src/index.js ***!\n \\***********************************************/\n/***/ ((module) => {\n\nmodule.exports = {\n decomp: polygonDecomp,\n quickDecomp: polygonQuickDecomp,\n isSimple: polygonIsSimple,\n removeCollinearPoints: polygonRemoveCollinearPoints,\n removeDuplicatePoints: polygonRemoveDuplicatePoints,\n makeCCW: polygonMakeCCW\n};\n\n/**\n * Compute the intersection between two lines.\n * @static\n * @method lineInt\n * @param {Array} l1 Line vector 1\n * @param {Array} l2 Line vector 2\n * @param {Number} precision Precision to use when checking if the lines are parallel\n * @return {Array} The intersection point.\n */\nfunction lineInt(l1,l2,precision){\n precision = precision || 0;\n var i = [0,0]; // point\n var a1, b1, c1, a2, b2, c2, det; // scalars\n a1 = l1[1][1] - l1[0][1];\n b1 = l1[0][0] - l1[1][0];\n c1 = a1 * l1[0][0] + b1 * l1[0][1];\n a2 = l2[1][1] - l2[0][1];\n b2 = l2[0][0] - l2[1][0];\n c2 = a2 * l2[0][0] + b2 * l2[0][1];\n det = a1 * b2 - a2*b1;\n if (!scalar_eq(det, 0, precision)) { // lines are not parallel\n i[0] = (b2 * c1 - b1 * c2) / det;\n i[1] = (a1 * c2 - a2 * c1) / det;\n }\n return i;\n}\n\n/**\n * Checks if two line segments intersects.\n * @method segmentsIntersect\n * @param {Array} p1 The start vertex of the first line segment.\n * @param {Array} p2 The end vertex of the first line segment.\n * @param {Array} q1 The start vertex of the second line segment.\n * @param {Array} q2 The end vertex of the second line segment.\n * @return {Boolean} True if the two line segments intersect\n */\nfunction lineSegmentsIntersect(p1, p2, q1, q2){\n\tvar dx = p2[0] - p1[0];\n\tvar dy = p2[1] - p1[1];\n\tvar da = q2[0] - q1[0];\n\tvar db = q2[1] - q1[1];\n\n\t// segments are parallel\n\tif((da*dy - db*dx) === 0){\n\t\treturn false;\n\t}\n\n\tvar s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx);\n\tvar t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy);\n\n\treturn (s>=0 && s<=1 && t>=0 && t<=1);\n}\n\n/**\n * Get the area of a triangle spanned by the three given points. Note that the area will be negative if the points are not given in counter-clockwise order.\n * @static\n * @method area\n * @param {Array} a\n * @param {Array} b\n * @param {Array} c\n * @return {Number}\n */\nfunction triangleArea(a,b,c){\n return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1])));\n}\n\nfunction isLeft(a,b,c){\n return triangleArea(a,b,c) > 0;\n}\n\nfunction isLeftOn(a,b,c) {\n return triangleArea(a, b, c) >= 0;\n}\n\nfunction isRight(a,b,c) {\n return triangleArea(a, b, c) < 0;\n}\n\nfunction isRightOn(a,b,c) {\n return triangleArea(a, b, c) <= 0;\n}\n\nvar tmpPoint1 = [],\n tmpPoint2 = [];\n\n/**\n * Check if three points are collinear\n * @method collinear\n * @param {Array} a\n * @param {Array} b\n * @param {Array} c\n * @param {Number} [thresholdAngle=0] Threshold angle to use when comparing the vectors. The function will return true if the angle between the resulting vectors is less than this value. Use zero for max precision.\n * @return {Boolean}\n */\nfunction collinear(a,b,c,thresholdAngle) {\n if(!thresholdAngle){\n return triangleArea(a, b, c) === 0;\n } else {\n var ab = tmpPoint1,\n bc = tmpPoint2;\n\n ab[0] = b[0]-a[0];\n ab[1] = b[1]-a[1];\n bc[0] = c[0]-b[0];\n bc[1] = c[1]-b[1];\n\n var dot = ab[0]*bc[0] + ab[1]*bc[1],\n magA = Math.sqrt(ab[0]*ab[0] + ab[1]*ab[1]),\n magB = Math.sqrt(bc[0]*bc[0] + bc[1]*bc[1]),\n angle = Math.acos(dot/(magA*magB));\n return angle < thresholdAngle;\n }\n}\n\nfunction sqdist(a,b){\n var dx = b[0] - a[0];\n var dy = b[1] - a[1];\n return dx * dx + dy * dy;\n}\n\n/**\n * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle.\n * @method at\n * @param {Number} i\n * @return {Array}\n */\nfunction polygonAt(polygon, i){\n var s = polygon.length;\n return polygon[i < 0 ? i % s + s : i % s];\n}\n\n/**\n * Clear the polygon data\n * @method clear\n * @return {Array}\n */\nfunction polygonClear(polygon){\n polygon.length = 0;\n}\n\n/**\n * Append points \"from\" to \"to\"-1 from an other polygon \"poly\" onto this one.\n * @method append\n * @param {Polygon} poly The polygon to get points from.\n * @param {Number} from The vertex index in \"poly\".\n * @param {Number} to The end vertex index in \"poly\". Note that this vertex is NOT included when appending.\n * @return {Array}\n */\nfunction polygonAppend(polygon, poly, from, to){\n for(var i=from; i<to; i++){\n polygon.push(poly[i]);\n }\n}\n\n/**\n * Make sure that the polygon vertices are ordered counter-clockwise.\n * @method makeCCW\n */\nfunction polygonMakeCCW(polygon){\n var br = 0,\n v = polygon;\n\n // find bottom right point\n for (var i = 1; i < polygon.length; ++i) {\n if (v[i][1] < v[br][1] || (v[i][1] === v[br][1] && v[i][0] > v[br][0])) {\n br = i;\n }\n }\n\n // reverse poly if clockwise\n if (!isLeft(polygonAt(polygon, br - 1), polygonAt(polygon, br), polygonAt(polygon, br + 1))) {\n polygonReverse(polygon);\n return true;\n } else {\n return false;\n }\n}\n\n/**\n * Reverse the vertices in the polygon\n * @method reverse\n */\nfunction polygonReverse(polygon){\n var tmp = [];\n var N = polygon.length;\n for(var i=0; i!==N; i++){\n tmp.push(polygon.pop());\n }\n for(var i=0; i!==N; i++){\n\t\tpolygon[i] = tmp[i];\n }\n}\n\n/**\n * Check if a point in the polygon is a reflex point\n * @method isReflex\n * @param {Number} i\n * @return {Boolean}\n */\nfunction polygonIsReflex(polygon, i){\n return isRight(polygonAt(polygon, i - 1), polygonAt(polygon, i), polygonAt(polygon, i + 1));\n}\n\nvar tmpLine1=[],\n tmpLine2=[];\n\n/**\n * Check if two vertices in the polygon can see each other\n * @method canSee\n * @param {Number} a Vertex index 1\n * @param {Number} b Vertex index 2\n * @return {Boolean}\n */\nfunction polygonCanSee(polygon, a,b) {\n var p, dist, l1=tmpLine1, l2=tmpLine2;\n\n if (isLeftOn(polygonAt(polygon, a + 1), polygonAt(polygon, a), polygonAt(polygon, b)) && isRightOn(polygonAt(polygon, a - 1), polygonAt(polygon, a), polygonAt(polygon, b))) {\n return false;\n }\n dist = sqdist(polygonAt(polygon, a), polygonAt(polygon, b));\n for (var i = 0; i !== polygon.length; ++i) { // for each edge\n if ((i + 1) % polygon.length === a || i === a){ // ignore incident edges\n continue;\n }\n if (isLeftOn(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i + 1)) && isRightOn(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i))) { // if diag intersects an edge\n l1[0] = polygonAt(polygon, a);\n l1[1] = polygonAt(polygon, b);\n l2[0] = polygonAt(polygon, i);\n l2[1] = polygonAt(polygon, i + 1);\n p = lineInt(l1,l2);\n if (sqdist(polygonAt(polygon, a), p) < dist) { // if edge is blocking visibility to b\n return false;\n }\n }\n }\n\n return true;\n}\n\n/**\n * Check if two vertices in the polygon can see each other\n * @method canSee2\n * @param {Number} a Vertex index 1\n * @param {Number} b Vertex index 2\n * @return {Boolean}\n */\nfunction polygonCanSee2(polygon, a,b) {\n // for each edge\n for (var i = 0; i !== polygon.length; ++i) {\n // ignore incident edges\n if (i === a || i === b || (i + 1) % polygon.length === a || (i + 1) % polygon.length === b){\n continue;\n }\n if( lineSegmentsIntersect(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i), polygonAt(polygon, i+1)) ){\n return false;\n }\n }\n return true;\n}\n\n/**\n * Copy the polygon from vertex i to vertex j.\n * @method copy\n * @param {Number} i\n * @param {Number} j\n * @param {Polygon} [targetPoly] Optional target polygon to save in.\n * @return {Polygon} The resulting copy.\n */\nfunction polygonCopy(polygon, i,j,targetPoly){\n var p = targetPoly || [];\n polygonClear(p);\n if (i < j) {\n // Insert all vertices from i to j\n for(var k=i; k<=j; k++){\n p.push(polygon[k]);\n }\n\n } else {\n\n // Insert vertices 0 to j\n for(var k=0; k<=j; k++){\n p.push(polygon[k]);\n }\n\n // Insert vertices i to end\n for(var k=i; k<polygon.length; k++){\n p.push(polygon[k]);\n }\n }\n\n return p;\n}\n\n/**\n * Decomposes the polygon into convex pieces. Returns a list of edges [[p1,p2],[p2,p3],...] that cuts the polygon.\n * Note that this algorithm has complexity O(N^4) and will be very slow for polygons with many vertices.\n * @method getCutEdges\n * @return {Array}\n */\nfunction polygonGetCutEdges(polygon) {\n var min=[], tmp1=[], tmp2=[], tmpPoly = [];\n var nDiags = Number.MAX_VALUE;\n\n for (var i = 0; i < polygon.length; ++i) {\n if (polygonIsReflex(polygon, i)) {\n for (var j = 0; j < polygon.length; ++j) {\n if (polygonCanSee(polygon, i, j)) {\n tmp1 = polygonGetCutEdges(polygonCopy(polygon, i, j, tmpPoly));\n tmp2 = polygonGetCutEdges(polygonCopy(polygon, j, i, tmpPoly));\n\n for(var k=0; k<tmp2.length; k++){\n tmp1.push(tmp2[k]);\n }\n\n if (tmp1.length < nDiags) {\n min = tmp1;\n nDiags = tmp1.length;\n min.push([polygonAt(polygon, i), polygonAt(polygon, j)]);\n }\n }\n }\n }\n }\n\n return min;\n}\n\n/**\n * Decomposes the polygon into one or more convex sub-Polygons.\n * @method decomp\n * @return {Array} An array or Polygon objects.\n */\nfunction polygonDecomp(polygon){\n var edges = polygonGetCutEdges(polygon);\n if(edges.length > 0){\n return polygonSlice(polygon, edges);\n } else {\n return [polygon];\n }\n}\n\n/**\n * Slices the polygon given one or more cut edges. If given one, this function will return two polygons (false on failure). If many, an array of polygons.\n * @method slice\n * @param {Array} cutEdges A list of edges, as returned by .getCutEdges()\n * @return {Array}\n */\nfunction polygonSlice(polygon, cutEdges){\n if(cutEdges.length === 0){\n\t\treturn [polygon];\n }\n if(cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length===2 && cutEdges[0][0] instanceof Array){\n\n var polys = [polygon];\n\n for(var i=0; i<cutEdges.length; i++){\n var cutEdge = cutEdges[i];\n // Cut all polys\n for(var j=0; j<polys.length; j++){\n var poly = polys[j];\n var result = polygonSlice(poly, cutEdge);\n if(result){\n // Found poly! Cut and quit\n polys.splice(j,1);\n polys.push(result[0],result[1]);\n break;\n }\n }\n }\n\n return polys;\n } else {\n\n // Was given one edge\n var cutEdge = cutEdges;\n var i = polygon.indexOf(cutEdge[0]);\n var j = polygon.indexOf(cutEdge[1]);\n\n if(i !== -1 && j !== -1){\n return [polygonCopy(polygon, i,j),\n polygonCopy(polygon, j,i)];\n } else {\n return false;\n }\n }\n}\n\n/**\n * Checks that the line segments of this polygon do not intersect each other.\n * @method isSimple\n * @param {Array} path An array of vertices e.g. [[0,0],[0,1],...]\n * @return {Boolean}\n * @todo Should it check all segments with all others?\n */\nfunction polygonIsSimple(polygon){\n var path = polygon, i;\n // Check\n for(i=0; i<path.length-1; i++){\n for(var j=0; j<i-1; j++){\n if(lineSegmentsIntersect(path[i], path[i+1], path[j], path[j+1] )){\n return false;\n }\n }\n }\n\n // Check the segment between the last and the first point to all others\n for(i=1; i<path.length-2; i++){\n if(lineSegmentsIntersect(path[0], path[path.length-1], path[i], path[i+1] )){\n return false;\n }\n }\n\n return true;\n}\n\nfunction getIntersectionPoint(p1, p2, q1, q2, delta){\n\tdelta = delta || 0;\n\tvar a1 = p2[1] - p1[1];\n\tvar b1 = p1[0] - p2[0];\n\tvar c1 = (a1 * p1[0]) + (b1 * p1[1]);\n\tvar a2 = q2[1] - q1[1];\n\tvar b2 = q1[0] - q2[0];\n\tvar c2 = (a2 * q1[0]) + (b2 * q1[1]);\n\tvar det = (a1 * b2) - (a2 * b1);\n\n\tif(!scalar_eq(det,0,delta)){\n\t\treturn [((b2 * c1) - (b1 * c2)) / det, ((a1 * c2) - (a2 * c1)) / det];\n\t} else {\n\t\treturn [0,0];\n }\n}\n\n/**\n * Quickly decompose the Polygon into convex sub-polygons.\n * @method quickDecomp\n * @param {Array} result\n * @param {Array} [reflexVertices]\n * @param {Array} [steinerPoints]\n * @param {Number} [delta]\n * @param {Number} [maxlevel]\n * @param {Number} [level]\n * @return {Array}\n */\nfunction polygonQuickDecomp(polygon, result,reflexVertices,steinerPoints,delta,maxlevel,level){\n maxlevel = maxlevel || 100;\n level = level || 0;\n delta = delta || 25;\n result = typeof(result)!==\"undefined\" ? result : [];\n reflexVertices = reflexVertices || [];\n steinerPoints = steinerPoints || [];\n\n var upperInt=[0,0], lowerInt=[0,0], p=[0,0]; // Points\n var upperDist=0, lowerDist=0, d=0, closestDist=0; // scalars\n var upperIndex=0, lowerIndex=0, closestIndex=0; // Integers\n var lowerPoly=[], upperPoly=[]; // polygons\n var poly = polygon,\n v = polygon;\n\n if(v.length < 3){\n\t\treturn result;\n }\n\n level++;\n if(level > maxlevel){\n console.warn(\"quickDecomp: max level (\"+maxlevel+\") reached.\");\n return result;\n }\n\n for (var i = 0; i < polygon.length; ++i) {\n if (polygonIsReflex(poly, i)) {\n reflexVertices.push(poly[i]);\n upperDist = lowerDist = Number.MAX_VALUE;\n\n\n for (var j = 0; j < polygon.length; ++j) {\n if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j)) && isRightOn(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j - 1))) { // if line intersects with an edge\n p = getIntersectionPoint(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j - 1)); // find the point of intersection\n if (isRight(polygonAt(poly, i + 1), polygonAt(poly, i), p)) { // make sure it's inside the poly\n d = sqdist(poly[i], p);\n if (d < lowerDist) { // keep only the closest intersection\n lowerDist = d;\n lowerInt = p;\n lowerIndex = j;\n }\n }\n }\n if (isLeft(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j + 1)) && isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))) {\n p = getIntersectionPoint(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j + 1));\n if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), p)) {\n d = sqdist(poly[i], p);\n if (d < upperDist) {\n upperDist = d;\n upperInt = p;\n upperIndex = j;\n }\n }\n }\n }\n\n // if there are no vertices to connect to, choose a point in the middle\n if (lowerIndex === (upperIndex + 1) % polygon.length) {\n //console.log(\"Case 1: Vertex(\"+i+\"), lowerIndex(\"+lowerIndex+\"), upperIndex(\"+upperIndex+\"), poly.size(\"+polygon.length+\")\");\n p[0] = (lowerInt[0] + upperInt[0]) / 2;\n p[1] = (lowerInt[1] + upperInt[1]) / 2;\n steinerPoints.push(p);\n\n if (i < upperIndex) {\n //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.begin() + upperIndex + 1);\n polygonAppend(lowerPoly, poly, i, upperIndex+1);\n lowerPoly.push(p);\n upperPoly.push(p);\n if (lowerIndex !== 0){\n //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.end());\n polygonAppend(upperPoly, poly,lowerIndex,poly.length);\n }\n //upperPoly.insert(upperPoly.end(), poly.begin(), poly.begin() + i + 1);\n polygonAppend(upperPoly, poly,0,i+1);\n } else {\n if (i !== 0){\n //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.end());\n polygonAppend(lowerPoly, poly,i,poly.length);\n }\n //lowerPoly.insert(lowerPoly.end(), poly.begin(), poly.begin() + upperIndex + 1);\n polygonAppend(lowerPoly, poly,0,upperIndex+1);\n lowerPoly.push(p);\n upperPoly.push(p);\n //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.begin() + i + 1);\n polygonAppend(upperPoly, poly,lowerIndex,i+1);\n }\n } else {\n // connect to the closest point within the triangle\n //console.log(\"Case 2: Vertex(\"+i+\"), closestIndex(\"+closestIndex+\"), poly.size(\"+polygon.length+\")\\n\");\n\n if (lowerIndex > upperIndex) {\n upperIndex += polygon.length;\n }\n closestDist = Number.MAX_VALUE;\n\n if(upperIndex < lowerIndex){\n return result;\n }\n\n for (var j = lowerIndex; j <= upperIndex; ++j) {\n if (\n isLeftOn(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j)) &&\n isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))\n ) {\n d = sqdist(polygonAt(poly, i), polygonAt(poly, j));\n if (d < closestDist && polygonCanSee2(poly, i, j)) {\n closestDist = d;\n closestIndex = j % polygon.length;\n }\n }\n }\n\n if (i < closestIndex) {\n polygonAppend(lowerPoly, poly,i,closestIndex+1);\n if (closestIndex !== 0){\n polygonAppend(upperPoly, poly,closestIndex,v.length);\n }\n polygonAppend(upperPoly, poly,0,i+1);\n } else {\n if (i !== 0){\n polygonAppend(lowerPoly, poly,i,v.length);\n }\n polygonAppend(lowerPoly, poly,0,closestIndex+1);\n polygonAppend(upperPoly, poly,closestIndex,i+1);\n }\n }\n\n // solve smallest poly first\n if (lowerPoly.length < upperPoly.length) {\n polygonQuickDecomp(lowerPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);\n polygonQuickDecomp(upperPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);\n } else {\n polygonQuickDecomp(upperPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);\n polygonQuickDecomp(lowerPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);\n }\n\n return result;\n }\n }\n result.push(polygon);\n\n return result;\n}\n\n/**\n * Remove collinear points in the polygon.\n * @method removeCollinearPoints\n * @param {Number} [precision] The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision.\n * @return {Number} The number of points removed\n */\nfunction polygonRemoveCollinearPoints(polygon, precision){\n var num = 0;\n for(var i=polygon.length-1; polygon.length>3 && i>=0; --i){\n if(collinear(polygonAt(polygon, i-1),polygonAt(polygon, i),polygonAt(polygon, i+1),precision)){\n // Remove the middle point\n polygon.splice(i%polygon.length,1);\n num++;\n }\n }\n return num;\n}\n\n/**\n * Remove duplicate points in the polygon.\n * @method removeDuplicatePoints\n * @param {Number} [precision] The threshold to use when determining whether two points are the same. Use zero for best precision.\n */\nfunction polygonRemoveDuplicatePoints(polygon, precision){\n for(var i=polygon.length-1; i>=1; --i){\n var pi = polygon[i];\n for(var j=i-1; j>=0; --j){\n if(points_eq(pi, polygon[j], precision)){\n polygon.splice(i,1);\n continue;\n }\n }\n }\n}\n\n/**\n * Check if two scalars are equal\n * @static\n * @method eq\n * @param {Number} a\n * @param {Number} b\n * @param {Number} [precision]\n * @return {Boolean}\n */\nfunction scalar_eq(a,b,precision){\n precision = precision || 0;\n return Math.abs(a-b) <= precision;\n}\n\n/**\n * Check if two points are equal\n * @static\n * @method points_eq\n * @param {Array} a\n * @param {Array} b\n * @param {Number} [precision]\n * @return {Boolean}\n */\nfunction points_eq(a,b,precision){\n return scalar_eq(a[0],b[0],precision) && scalar_eq(a[1],b[1],precision);\n}\n\n\n/***/ }),\n\n/***/ \"./src/2d/index.ts\":\n/*!*************************!*\\\n !*** ./src/2d/index.ts ***!\n \\*************************/\n/***/ (function(__unused_webpack_module, exports, __nested_webpack_require_62156__) {\n\n\"use strict\";\n\nvar __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n}));\nvar __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n});\nvar __importStar = (this && this.__importStar) || (function () {\n var ownKeys = function(o) {\n ownKeys = Object.getOwnPropertyNames || function (o) {\n var ar = [];\n for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;\n return ar;\n };\n return ownKeys(o);\n };\n return function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== \"default\") __createBinding(result, mod, k[i]);\n __setModuleDefault(result, mod);\n return result;\n };\n})();\nvar __exportStar = (this && this.__exportStar) || function(m, exports) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.distance = distance;\nexports.angle = angle;\nexports.angleBetween = angleBetween;\nexports.pointsAreCollinear = pointsAreCollinear;\nexports.lineToRay = lineToRay;\nexports.rayToLine = rayToLine;\nexports.aabb = aabb;\nexports.aabbToRectangle = aabbToRectangle;\nexports.aabbsOverlap = aabbsOverlap;\nexports.pointInAABB = pointInAABB;\nexports.rectangleIsRotated = rectangleIsRotated;\nexports.rectangleVertices = rectangleVertices;\nexports.verticesToEdges = verticesToEdges;\nexports.polygonIsConvex = polygonIsConvex;\nexports.polygonSelfIntersects = polygonSelfIntersects;\nexports.polygonIsValid = polygonIsValid;\nexports.polygonWindingOrder = polygonWindingOrder;\nexports.polygonArea = polygonArea;\nexports.polygonCentroid = polygonCentroid;\nexports.polygonConvexHull = polygonConvexHull;\nexports.optimisePolygon = optimisePolygon;\nexports.decomposePolygon = decomposePolygon;\nexports.pointOnRay = pointOnRay;\nexports.pointOnLine = pointOnLine;\nexports.pointInCircle = pointInCircle;\nexports.pointInRectangle = pointInRectangle;\nexports.pointInPolygon = pointInPolygon;\nexports.rayTraverseGrid = rayTraverseGrid;\nexports.rayIntersectsRay = rayIntersectsRay;\nexports.rayIntersectsLine = rayIntersectsLine;\nexports.rayIntersectsCircle = rayIntersectsCircle;\nexports.rayIntersectsRectangle = rayIntersectsRectangle;\nexports.rayIntersectsPolygon = rayIntersectsPolygon;\nexports.lineIntersectsRay = lineIntersectsRay;\nexports.lineIntersectsLine = lineIntersectsLine;\nexports.lineIntersectsCircle = lineIntersectsCircle;\nexports.lineIntersectsRectangle = lineIntersectsRectangle;\nexports.lineIntersectsPolygon = lineIntersectsPolygon;\nexports.circleIntersectsCircle = circleIntersectsCircle;\nexports.circleIntersectsRectangle = circleIntersectsRectangle;\nexports.circleIntersectsPolygon = circleIntersectsPolygon;\nexports.rectangleIntersectsRectangle = rectangleIntersectsRectangle;\nexports.rectangleIntersectsPolygon = rectangleIntersectsPolygon;\nexports.polygonIntersectsPolygon = polygonIntersectsPolygon;\nconst utils_1 = __nested_webpack_require_62156__(/*! @basementuniverse/utils */ \"./node_modules/@basementuniverse/utils/utils.js\");\nconst vec_1 = __nested_webpack_require_62156__(/*! @basementuniverse/vec */ \"./node_modules/@basementuniverse/vec/vec.js\");\nconst decomp = __importStar(__nested_webpack_require_62156__(/*! poly-decomp */ \"./node_modules/poly-decomp/src/index.js\"));\nconst utilities_1 = __nested_webpack_require_62156__(/*! ../utilities */ \"./src/utilities/index.ts\");\nconst constants = __importStar(__nested_webpack_require_62156__(/*! ../utilities/constants */ \"./src/utilities/constants.ts\"));\nconst types_1 = __nested_webpack_require_62156__(/*! ./types */ \"./src/2d/types.ts\");\n__exportStar(__nested_webpack_require_62156__(/*! ./types */ \"./src/2d/types.ts\"), exports);\n/**\n * Contents\n *\n * Utilities\n * @see distance\n * @see angle\n * @see angleBetween\n * @see pointsAreCollinear\n *\n * Line and ray utilities\n * @see lineToRay\n * @see rayToLine\n *\n * AABBs\n * @see aabb\n * @see aabbToRectangle\n * @see aabbsOverlap\n * @see pointInAABB\n *\n * Rectangle utilities\n * @see rectangleIsRotated\n * @see rectangleVertices\n *\n * Polygon utilities\n * @see verticesToEdges\n * @see findOuterEdges (not exported)\n * @see polygonIsConvex\n * @see polygonSelfIntersects\n * @see polygonIsValid\n * @see polygonWindingOrder\n * @see polygonArea\n * @see polygonCentroid\n * @see polygonConvexHull\n * @see removeDuplicateVertices (not exported)\n * @see removeDuplicateAdjacentVertices (not exported)\n * @see removeCollinearVertices (not exported)\n * @see optimisePolygon\n * @see decomposePolygon\n *\n * Points\n * @see pointOnRay\n * @see pointOnLine\n * @see pointInCircle\n * @see pointInRectangle\n * @see pointInPolygon\n *\n * Rays\n * @see rayTraverseGrid\n * @see rayIntersectsRay\n * @see rayIntersectsLine\n * @see rayIntersectsCircle\n * @see rayIntersectsRectangle\n * @see rayIntersectsValidConvexPolygonEdges (not exported)\n * @see rayIntersectsPolygon\n *\n * Lines\n * @see lineIntersectsRay\n * @see lineIntersectsLine\n * @see lineIntersectsCircle\n * @see lineIntersectsRectangle\n * @see lineIntersectsValidConvexPolygonEdges (not exported)\n * @see lineIntersectsPolygon\n *\n * Circles\n * @see circleIntersectsCircle\n * @see circleIntersectsRectangle\n * @see circleIntersectsValidConvexPolygonEdges (not exported)\n * @see circleIntersectsPolygon\n *\n * Rectangles\n * @see projectVerticesToAxis (not exported)\n * @see rectangleIntersectsRectangle\n * @see rectangleIntersectsPolygon\n *\n * Polygons\n * @see polygonIntersectsPolygon\n */\n/**\n * Calculate the distance between two points\n */\nfunction distance(a, b) {\n return vec_1.vec2.len(vec_1.vec2.sub(a, b));\n}\n/**\n * Calculate the clockwise angle from point a to point b\n *\n * The result is in radians and ranges from 0 to 2π (360 degrees)\n *\n * Returns 0 if the vectors are equal\n */\nfunction angle(a, b) {\n if ((0, utilities_1.vectorsAlmostEqual)(a, b)) {\n return 0;\n }\n const theta = vec_1.vec2.rad(vec_1.vec2.sub(b, a)) % (2 * Math.PI);\n if (theta < 0) {\n return theta + 2 * Math.PI; // Ensure angle is positive\n }\n return theta;\n}\n/**\n * Calculate the clockwise angle between two lines or rays\n *\n * Returns 0 if either line is zero-length\n */\nfunction angleBetween(a, b) {\n let aLine = (0, types_1.isRay)(a) ? rayToLine(a, 1) : a;\n let bLine = (0, types_1.isRay)(b) ? rayToLine(b, 1) : b;\n if ((0, utilities_1.vectorAlmostZero)(vec_1.vec2.sub(aLine.end, aLine.start)) ||\n (0, utilities_1.vectorAlmostZero)(vec_1.vec2.sub(bLine.end, bLine.start))) {\n return 0; // Zero-length line\n }\n const dirA = vec_1.vec2.nor(vec_1.vec2.sub(aLine.end, aLine.start));\n const dirB = vec_1.vec2.nor(vec_1.vec2.sub(bLine.end, bLine.start));\n // Clamp dot product to [-1, 1] to avoid NaN due to floating-point errors\n const dot = (0, utils_1.clamp)(vec_1.vec2.dot(dirA, dirB), -1, 1);\n const cross = vec_1.vec2.cross(dirA, dirB);\n const angle = Math.atan2(cross, dot);\n return angle < 0 ? angle + 2 * Math.PI : angle; // Ensure angle is positive\n}\n/**\n * Check if three points in 2D space are collinear\n */\nfunction pointsAreCollinear(a, b, c) {\n // Check if the area of the triangle formed by the points is zero\n const area = 0.5 * Math.abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));\n return Math.abs(area) < constants.EPSILON;\n}\n/**\n * Convert a line segment to a ray\n */\nfunction lineToRay(line) {\n return {\n origin: line.start,\n direction: vec_1.vec2.nor(vec_1.vec2.sub(line.end, line.start)),\n };\n}\n/**\n * Convert a ray to a line segment\n */\nfunction rayToLine(ray, length = 1) {\n return {\n start: ray.origin,\n end: vec_1.vec2.add(ray.origin, vec_1.vec2.mul(ray.direction, length)),\n };\n}\n/**\n * Get the bounding box (AABB) of a geometric object\n */\nfunction aabb(o) {\n if ((0, types_1.isLine)(o)) {\n return {\n position: (0, vec_1.vec2)(Math.min(o.start.x, o.end.x), Math.min(o.start.y, o.end.y)),\n size: (0, vec_1.vec2)(Math.abs(o.end.x - o.start.x), Math.abs(o.end.y - o.start.y)),\n };\n }\n if ((0, types_1.isRectangle)(o)) {\n const vertices = rectangleVertices(o);\n const position = (0, vec_1.vec2)(Math.min(...vertices.map(v => v.x)), Math.min(...vertices.map(v => v.y)));\n return {\n position,\n size: (0, vec_1.vec2)(Math.max(...vertices.map(v => v.x)) - position.x, Math.max(...vertices.map(v => v.y)) - position.y),\n };\n }\n if ((0, types_1.isCircle)(o)) {\n return {\n position: vec_1.vec2.sub(o.position, (0, vec_1.vec2)(o.radius, o.radius)),\n size: (0, vec_1.vec2)(o.radius * 2),\n };\n }\n if ((0, types_1.isPolygon)(o)) {\n const position = (0, vec_1.vec2)(Math.min(...o.vertices.map(v => v.x)), Math.min(...o.vertices.map(v => v.y)));\n return {\n position,\n size: (0, vec_1.vec2)(Math.max(...o.vertices.map(v => v.x)) - position.x, Math.max(...o.vertices.map(v => v.y)) - position.y),\n };\n }\n return null;\n}\n/**\n * Convert an AABB to a rectangle\n */\nfunction aabbToRectangle(aabb) {\n return {\n position: vec_1.vec2.add(aabb.position, vec_1.vec2.div(aabb.size, 2)),\n size: aabb.size,\n rotation: 0,\n };\n}\n/**\n * Check if two AABBs overlap and return the overlapping area if so\n */\nfunction aabbsOverlap(a, b) {\n const overlapX = (0, utilities_1.overlapInterval)({ min: a.position.x, max: a.position.x + a.size.x }, { min: b.position.x, max: b.position.x + b.size.x });\n const overlapY = (0, utilities_1.overlapInterval)({ min: a.position.y, max: a.position.y + a.size.y }, { min: b.position.y, max: b.position.y + b.size.y });\n // If the AABBs don't overlap on one or more axes, they don't overlap at all\n if (!overlapX || !overlapY) {\n return { intersects: false };\n }\n return {\n intersects: true,\n overlap: {\n position: (0, vec_1.vec2)(overlapX.min, overlapY.min),\n size: (0, vec_1.vec2)(overlapX.max - overlapX.min, overlapY.max - overlapY.min),\n },\n };\n}\n/**\n * Check if a point is inside an AABB\n *\n * This should be faster than pointInRectangle since we don't need to consider\n * rotation\n */\nfunction pointInAABB(point, aabb) {\n const { position, size } = aabb;\n const min = position;\n const max = vec_1.vec2.add(position, size);\n // Check if the point is inside the AABB\n const intersects = (0, utilities_1.valueInInterval)(point.x, { min: min.x, max: max.x }) &&\n (0, utilities_1.valueInInterval)(point.y, { min: min.y, max: max.y });\n // Find the closest point on the AABB perimeter to the given point\n let closestPoint;\n let normal = undefined;\n if (!intersects) {\n // If the point is outside, clamp to the box\n closestPoint = (0, vec_1.vec2)((0, utils_1.clamp)(point.x, min.x, max.x), (0, utils_1.clamp)(point.y, min.y, max.y));\n }\n else {\n // If the point is inside, project to the nearest edge\n const distances = [\n {\n x: min.x,\n y: point.y,\n d: Math.abs(point.x - min.x),\n normal: (0, vec_1.vec2)(-1, 0),\n }, // left\n {\n x: max.x,\n y: point.y,\n d: Math.abs(point.x - max.x),\n normal: (0, vec_1.vec2)(1, 0),\n }, // right\n {\n x: point.x,\n y: min.y,\n d: Math.abs(point.y - min.y),\n normal: (0, vec_1.vec2)(0, -1),\n }, // top\n {\n x: point.x,\n y: max.y,\n d: Math.abs(point.y - max.y),\n normal: (0, vec_1.vec2)(0, 1),\n }, // bottom\n ];\n const nearest = distances.reduce((a, b) => (a.d < b.d ? a : b));\n closestPoint = (0, vec_1.vec2)(nearest.x, nearest.y);\n normal = nearest.normal;\n }\n // Calculate the distance from the point to the closest point\n const distance = vec_1.vec2.len(vec_1.vec2.sub(point, closestPoint));\n // If the point is inside, distance should be negative\n return {\n intersects,\n closestPoint,\n distance: intersects ? -distance : distance,\n normal,\n };\n}\n/**\n * Check if a rectangle is rotated\n */\nfunction rectangleIsRotated(rectangle) {\n return (rectangle.rotation !== undefined &&\n Math.abs(rectangle.rotation) > constants.EPSILON);\n}\n/**\n * Get the vertices of a rectangle\n *\n * Vertices will be returned in clockwise order starting at the top-left:\n * - Top-left\n * - Top-right\n * - Bottom-right\n * - Bottom-left\n */\nfunction rectangleVertices(rectangle) {\n const { position, size, rotation = 0 } = rectangle;\n const halfSize = vec_1.vec2.div(size, 2);\n // Calculate the four corners of the rectangle\n let topLeftOffset = vec_1.vec2.fromComponents(vec_1.vec2.swiz(halfSize, 'XY'));\n let topRightOffset = vec_1.vec2.fromComponents(vec_1.vec2.swiz(halfSize, 'xY'));\n let bottomRightOffset = vec_1.vec2.fromComponents(vec_1.vec2.swiz(halfSize, 'xy'));\n let bottomLeftOffset = vec_1.vec2.fromComponents(vec_1.vec2.swiz(halfSize, 'Xy'));\n // Rotate the offsets if the rectangle is rotated\n if (rectangleIsRotated(rectangle)) {\n topLeftOffset = vec_1.vec2.rot(topLeftOffset, rotation);\n topRightOffset = vec_1.vec2.rot(topRightOffset, rotation);\n bottomRightOffset = vec_1.vec2.rot(bottomRightOffset, rotation);\n bottomLeftOffset = vec_1.vec2.rot(bottomLeftOffset, rotation);\n }\n return [\n vec_1.vec2.add(position, topLeftOffset),\n vec_1.vec2.add(position, topRightOffset),\n vec_1.vec2.add(position, bottomRightOffset),\n vec_1.vec2.add(position, bottomLeftOffset),\n ];\n}\n/**\n * Convert a list of vertices to a list of edges\n */\nfunction verticesToEdges(vertices) {\n const edges = [];\n for (let i = 0; i < vertices.length; i++) {\n const start = vertices[i];\n const end = (0, utils_1.at)(vertices, i + 1);\n edges.push({ start, end });\n }\n return edges;\n}\n/**\n * Find outer edges in a list of polygons\n *\n * We assume that the polygons were the result of decomposing a concave polygon\n * into a set of convex polygons, and as such they are all convex and share\n * one or more edges\n *\n * This means we can identify outer edges because they'll only appear once\n * in the list of edges, while inner edges will appear twice (once for each\n * polygon that shares them)\n */\nfunction findOuterEdges(polygons) {\n const allEdges = polygons.flatMap(polygon => verticesToEdges(polygon.vertices));\n // Edges are the duplicates if they overlap but have no intersection point\n // (this implies that they have infinitely many intersection points)\n const edgesOverlap = (a, b) => {\n const result = lineIntersectsLine(a, b);\n if (result.intersects && !result.intersectionPoint) {\n // Edge case: if the edges intersect and have no intersect point, but\n // share only one endpoint, then they aren't considered overlapping\n if (((0, utilities_1.vectorsAlmostEqual)(a.end, b.start) &&\n !(0, utilities_1.vectorsAlmostEqual)(a.start, b.end)) ||\n ((0, utilities_1.vectorsAlmostEqual)(a.start, b.end) &&\n !(0, utilities_1.vectorsAlmostEqual)(a.end, b.start))) {\n return false;\n }\n return true;\n }\n return false;\n };\n // Filter out the edges that appear more than once\n const result = [];\n for (const edge of allEdges) {\n if (!result.some(e => edgesOverlap(e, edge)) &&\n !allEdges.some(e => e !== edge && edgesOverlap(e, edge))) {\n result.push(edge); // This edge is an outer edge\n }\n }\n return result;\n}\n/**\n * Check if a polygon is convex\n *\n * Returns null if the polygon is invalid\n */\nfunction polygonIsConvex(polygon) {\n if (!polygonIsValid(polygon)) {\n return null;\n }\n let sign = 0;\n for (let i = 0; i < polygon.vertices.length; i++) {\n const a = polygon.vertices[i];\n const b = (0, utils_1.at)(polygon.vertices, i + 1);\n const c = (0, utils_1.at)(polygon.vertices, i + 2);\n const crossProduct = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);\n if (crossProduct !== 0) {\n if (sign === 0) {\n sign = Math.sign(crossProduct);\n }\n else if (Math.sign(crossProduct) !== sign) {\n return false; // Found a change in sign, polygon is not convex\n }\n }\n }\n return true; // All cross products have the same sign, polygon is convex\n}\n/**\n * Check if a polygon self-intersects\n */\nfunction polygonSelfIntersects(polygon) {\n if (polygon.vertices.length < 3) {\n return false; // A polygon must have at least 3 vertices\n }\n const n = polygon.vertices.length;\n for (let i = 0; i < n; i++) {\n const a = polygon.vertices[i];\n const b = (0, utils_1.at)(polygon.vertices, i + 1);\n for (let j = i + 2; j < n; j++) {\n const c = polygon.vertices[j];\n const d = (0, utils_1.at)(polygon.vertices, j + 1);\n // Skip adjacent edges\n if (i === 0 && j === n - 1) {\n continue;\n }\n // Check if the segments (a, b) and (c, d) intersect\n const { intersects } = lineIntersectsLine({ start: a, end: b }, { start: c, end: d });\n if (intersects) {\n return true; // Found an intersection, polygon self-intersects\n }\n }\n }\n return false; // No intersections found, polygon does not self-intersect\n}\n/**\n * Check if a polygon is valid\n *\n * A polygon is valid if it has at least 3 vertices and does not\n * self-intersect\n */\nfunction polygonIsValid(polygon) {\n return polygon.vertices.length >= 3 && !polygonSelfIntersects(polygon);\n}\n/**\n * Determine the winding order of a polygon's vertices\n *\n * Returns 'clockwise' or 'counter-clockwise' depending on the chosen\n * coordinate system\n *\n * By default we use the 'screen' coordinate system (y increases downwards)\n *\n * Returns null if the polygon is invalid\n */\nfunction polygonWindingOrder(polygon, options) {\n if (!polygonIsValid(polygon)) {\n return null;\n }\n let sum = 0;\n for (let i = 0; i < polygon.vertices.length; i++) {\n const a = polygon.vertices[i];\n const b = (0, utils_1.at)(polygon.vertices, i + 1);\n sum += (b.x - a.x) * (b.y + a.y);\n }\n const coordinateSystem = (options === null || options === void 0 ? void 0 : options.coordinateSystem) || 'screen';\n switch (coordinateSystem) {\n case 'cartesian':\n return sum > 0 ? 'clockwise' : 'counter-clockwise';\n case 'screen':\n return sum > 0 ? 'counter-clockwise' : 'clockwise';\n default:\n return null;\n }\n}\n/**\n * Calculate the area of a polygon\n *\n * Returns null if the polygon is invalid\n */\nfunction polygonArea(polygon) {\n if (!polygonIsValid(polygon)) {\n return null;\n }\n let area = 0;\n for (let i = 0; i < polygon.vertices.length; i++) {\n const a = polygon.vertices[i];\n const b = (0, utils_1.at)(polygon.vertices, i + 1);\n area += vec_1.vec2.cross(a, b);\n }\n return Math.abs(area) / 2;\n}\n/**\n * Calculate the centroid of a polygon\n *\n * Returns null if the polygon is invalid or degenerate (i.e. all vertices are\n * collinear)\n */\nfunction polygonCentroid(polygon) {\n if (!polygonIsValid(polygon)) {\n return null;\n }\n if (polygon.vertices.every((v, i, a) => pointsAreCollinear(v, (0, utils_1.at)(a, i + 1), (0, utils_1.at)(a, i + 2)))) {\n return null; // All vertices are collinear\n }\n return vec_1.vec2.div([...polygon.vertices].reduce((a, c) => vec_1.vec2.add(a, c), (0, vec_1.vec2)()), polygon.vertices.length);\n}\n/**\n * Calculate the convex hull of a polygon\n */\nfunction polygonConvexHull(polygon, options) {\n var _a;\n if (!polygonIsValid(polygon)) {\n return null;\n }\n if (polygonIsConvex(polygon)) {\n return polygon; // The polygon is already convex\n }\n const keepWindingOrder = (_a = options === null || options === void 0 ? void 0 : options.keepWindingOrder) !== null && _a !== void 0 ? _a : true;\n const originalWindingOrder = polygonWindingOrder(polygon);\n // Andrew's monotone chain algorithm for convex hull\n // Sort vertices lexicographically (first by x, then by y)\n const sorted = [...polygon.vertices].sort((a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y);\n const lower = [];\n for (const p of sorted) {\n while (lower.length >= 2 &&\n vec_1.vec2.cross(vec_1.vec2.sub(lower[lower.length - 1], lower[lower.length - 2]), vec_1.vec2.sub(p, lower[lower.length - 1])) <= 0) {\n lower.pop();\n }\n lower.push(p);\n }\n const upper = [];\n for (let i = sorted.length - 1; i >= 0; --i) {\n const p = sorted[i];\n while (upper.length >= 2 &&\n vec_1.vec2.cross(vec_1.vec2.sub(upper[upper.length - 1], upper[upper.length - 2]), vec_1.vec2.sub(p, upper[upper.length - 1])) <= 0) {\n upper.pop();\n }\n upper.push(p);\n }\n // Remove the last point of each half because it's repeated at the start of\n // the other\n lower.pop();\n upper.pop();\n const hull = lower.concat(upper);\n if (hull.length < 3) {\n return null;\n }\n // Optionally ensure the winding order is preserved\n if (keepWindingOrder &&\n polygonWindingOrder({ vertices: hull }) !== originalWindingOrder) {\n hull.reverse();\n }\n return {\n vertices: removeDuplicateVertices(hull),\n };\n}\n/**\n * Remove duplicate vertices from a list of vertices\n */\nfunction removeDuplicateVertices(vertices) {\n const result = [];\n const n = vertices.length;\n for (let i = 0; i < n; i++) {\n const current = vertices[i];\n if (!result.some(v => (0, utilities_1.vectorsAlmostEqual)(current, v))) {\n result.push(current);\n }\n }\n return result;\n}\n/**\n * Remove duplicate adjacent vertices from a list of vertices\n */\nfunction removeDuplicateAdjacentVertices(vertices) {\n const result = [];\n const n = vertices.length;\n for (let i = 0; i < n; i++) {\n const current = vertices[i];\n const next = (0, utils_1.at)(vertices, i + 1);\n if (!(0, utilities_1.vectorsAlmostEqual)(current, next)) {\n result.push(current);\n }\n }\n return result;\n}\n/**\n * Remove collinear vertices from a list of vertices\n */\nfunction removeCollinearVertices(vertices) {\n const result = [];\n const n = vertices.length;\n for (let i = 0; i < n; i++) {\n const a = (0, utils_1.at)(vertices, i - 1);\n const b = vertices[i];\n const c = (0, utils_1.at)(vertices, i + 1);\n // Skip collinear points\n if (pointsAreCollinear(a, b, c)) {\n continue;\n }\n result.push(b);\n }\n return result;\n}\n/**\n * Optimise a polygon by removing collinear vertices and duplicate adjacent\n * vertices\n */\nfunction optimisePolygon(polygon) {\n // Duplicate adjacent vertices will count the polygon as self-intersecting,\n // so skip that check for now and only check the number of vertices\n if (polygon.vertices.length < 3) {\n return null;\n }\n const optimisedVertices = removeCollinearVertices(removeDuplicateAdjacentVertices(polygon.vertices));\n // If we have less than 3 vertices after optimisation, return null\n if (optimisedVertices.length < 3) {\n return null;\n }\n return { vertices: optimisedVertices };\n}\n/**\n * Decompose a polygon into a set of convex polygons using the Bayazit\n * algorithm\n *\n * Returns null if the polygon is invalid or cannot be decomposed\n */\nfunction decomposePolygon(polygon, options) {\n var _a;\n if (!polygonIsValid(polygon)) {\n return null;\n }\n if (polygonIsConvex(polygon)) {\n return [polygon]; // The polygon is already convex\n }\n const mode = (options === null || options === void 0 ? void 0 : options.mode) || 'fast';\n const keepWindingOrder = (_a = options === null || options === void 0 ? void 0 : options.keepWindingOrder) !== null && _a !== void 0 ? _a : true;\n const originalWindingOrder = polygonWindingOrder(polygon);\n const vertices = polygon.vertices.map(v => [v.x, v.y]);\n if (originalWindingOrder === 'counter-clockwise') {\n vertices.reverse(); // Ensure clockwise winding\n }\n // Decompose the polygon\n let convexPolygons = [];\n switch (mode) {\n case 'fast':\n convexPolygons = decomp.quickDecomp(vertices);\n break;\n case 'optimal':\n convexPolygons = decomp.decomp(vertices);\n break;\n }\n // Convert the result into a list of Polygon objects\n const result = [];\n for (const convex of convexPolygons) {\n result.push({\n vertices: convex.map(v => (0, vec_1.vec2)(v[0], v[1])),\n });\n }\n // Optionally ensure the winding order is preserved\n if (keepWindingOrder) {\n for (const poly of result) {\n if (polygonWindingOrder(poly) !== originalWindingOrder) {\n poly.vertices.reverse();\n }\n }\n }\n return result.length > 0 ? result : null;\n}\n/**\n * Check if a point is on a ray\n */\nfunction pointOnRay(point, ray) {\n // Vector from ray origin to point\n const toPoint = vec_1.vec2.sub(point, ray.origin);\n // Get normalized ray direction\n const rayDirection = vec_1.vec2.nor(ray.direction);\n // Project toPoint onto the ray direction\n const projection = vec_1.vec2.dot(toPoint, rayDirection);\n // Calculate closest point on ray\n const closestPoint = vec_1.vec2.add(ray.origin, vec_1.vec2.mul(rayDirection, Math.max(0, projection)));\n // Calculate distance from point to closest point\n const distance = vec_1.vec2.len(vec_1.vec2.sub(point, closestPoint));\n // The point intersects the ray if the distance is effectively zero and the\n // projection is non-negative (meaning the point is in the direction of the\n // ray and not behind it)\n const intersects = distance < constants.EPSILON && projection >= 0;\n // Calculate the intersection normal\n let normal;\n if (!intersects) {\n // Get a vector perpendicular to the ray direction by rotating it 90 degrees\n const rayNormal = vec_1.vec2.rotf(rayDirection, -1);\n // Use the cross product to determine which side of the ray the point is on\n const crossProduct = vec_1.vec2.cross(rayDirection, toPoint);\n // If cross product is negative, point is on the right side of the ray\n // If positive, point is on the left side\n normal = vec_1.vec2.mul(rayNormal, Math.sign(crossProduct));\n }\n return {\n intersects,\n closestPoint,\n distance,\n normal,\n };\n}\n/**\n * Check if a point intersects a line segment\n */\nfunction pointOnLine(point, line) {\n // Get vector from line start to end\n const lineVector = vec_1.vec2.sub(line.end, line.start);\n // Get normalized line direction\n const lineDirection = vec_1.vec2.nor(lineVector);\n // Get vector from line start to point\n const toPoint = vec_1.vec2.sub(point, line.start);\n // Project toPoint onto the line direction\n const projection = vec_1.vec2.dot(toPoint, lineDirection);\n // Get line length\n const lineLength = vec_1.vec2.len(lineVector);\n // Clamp projection to line segment\n const clampedProjection = Math.max(0, Math.min(lineLength, projection));\n // Calculate closest point on line segment\n const closestPoint = vec_1.vec2.add(line.start, vec_1.vec2.mul(lineDirection, clampedProjection));\n // Calculate distance from point to closest point\n const distance = vec_1.vec2.len(vec_1.vec2.sub(point, closestPoint));\n // Point is on line if distance is effectively zero\n const intersects = distance < constants.EPSILON;\n // Calculate the intersection normal\n let normal;\n if (!intersects) {\n // Get a vector perpendicular to the ray direction by rotating it 90 degrees\n const rayNormal = vec_1.vec2.rotf(lineDirection, -1);\n // Use the cross product to determine which side of the ray the point is on\n const crossProduct = vec_1.vec2.cross(lineDirection, toPoint);\n // If cross product is negative, point is on the right side of the ray\n // If positive, point is on the left side\n normal = vec_1.vec2.mul(rayNormal, Math.sign(crossProduct));\n }\n return {\n intersects,\n closestPoint,\n distance,\n normal,\n };\n}\n/**\n * Check if a point is inside a circle\n */\nfunction pointInCircle(point, circle) {\n // Calculate vector from circle center to point\n const toPoint = vec_1.vec2.sub(point, circle.position);\n // Calculate distance from point to circle center\n const distanceToCenter = vec_1.vec2.len(toPoint);\n // Check if point is inside the circle\n const intersects = distanceToCenter <= circle.radius;\n // Calculate distance to circle edge\n const distance = intersects\n ? -(circle.radius - distanceToCenter) // Negative if inside\n : distanceToCenter - circle.radius; // Positive if outside\n // Calculate closest point on circle edge\n const closestPoint = vec_1.vec2.add(circle.position, vec_1.vec2.mul(vec_1.vec2.nor(toPoint), circle.radius));\n // Calculate the intersection normal\n const normal = intersects\n ? vec_1.vec2.nor(toPoint) // Normal points outward from circle center\n : undefined;\n return {\n intersects,\n closestPoint,\n distance,\n normal,\n };\n}\n/**\n * Check if a point is inside a rectangle\n *\n * In cases where the closest point is ambiguous (e.g. corners), the first edge\n * encountered with a closest point will be returned after evaluating edges in\n * this order:\n * top, right, bottom, left (before applying the rectangle's rotation)\n */\nfunction pointInRectangle(point, rectangle) {\n // Edge case: zero-size rectangle\n if ((0, utilities_1.vectorAlmostZero)(rectangle.size)) {\n // If the rectangle has no size, check if the point is at the rectangle's\n // position\n const isAtPosition = (0, utilities_1.vectorsAlmostEqual)(point, rectangle.position);\n return {\n intersects: isAtPosition,\n closestPoint: rectangle.position,\n distance: isAtPosition\n ? 0\n : vec_1.vec2.len(vec_1.vec2.sub(point, rectangle.position)),\n };\n }\n // Convert rectangle to polygon\n const vertices = rectangleVertices(rectangle);\n const polygonResult = pointInPolygon(point, { vertices });\n // We should always have a valid polygon, but just in case...\n if (!polygonResult) {\n throw new Error('Invalid rectangle vertices');\n }\n return polygonResult;\n}\n/**\n * Check if a point is inside a polygon\n *\n * Returns null if the polygon is invalid\n */\nfunction pointInPolygon(point, polygon) {\n // First check if the polygon is valid\n if (!polygonIsValid(polygon)) {\n return null;\n }\n // Check the polygon's winding order (we'll need this later to calculate\n // the intersecting surface normal)\n const windingOrder = polygonWindingOrder(polygon);\n // Find if point is inside polygon using ray casting algorithm\n let inside = false;\n const vertices = polygon.vertices;\n // We'll also keep track of the closest edge while we iterate\n let minDistanceSquared = Infinity;\n let closestPoint = point;\n let normal = undefined;\n for (let i = 0; i < vertices.length; i++) {\n const j = (i + 1) % vertices.length;\n const vi = vertices[i];\n const vj = vertices[j];\n // Ray casting algorithm\n if (vi.y > point.y !== vj.y > point.y &&\n point.x < ((vj.x - vi.x) * (point.y - vi.y)) / (vj.y - vi.y) + vi.x) {\n inside = !inside;\n }\n // Find closest point on this edge\n const edge = { start: vi, end: vj };\n const { closestPoint: edgeClosest, distance: edgeDistance } = pointOnLine(point, edge);\n const distanceSquared = edgeDistance * edgeDistance;\n if (distanceSquared < minDistanceSquared) {\n minDistanceSquared = distanceSquared;\n closestPoint = edgeClosest;\n normal = vec_1.vec2.rotf(vec_1.vec2.nor(vec_1.vec2.sub(vj, vi)), windingOrder === 'clockwise' ? 1 : -1);\n }\n }\n const distance = Math.sqrt(minDistanceSquared);\n return {\n intersects: inside,\n closestPoint,\n distance: inside ? -distance : distance,\n normal: inside ? normal : undefined,\n };\n}\n/**\n * Check which grid cells a ray traverses\n *\n * Based on \"A Fast Voxel Traversal Algorithm for Ray Tracing\" by Amanatides\n * and Woo\n *\n * We can optionally limit the number of cells traversed by the ray, or set\n * maxCells to -1 to continue traversing until the ray exits the grid (or until\n * we hit the hard limit of 10000 cells).\n */\nfunction rayTraverseGrid(ray, cellSize, gridTopLeft, gridBottomRight, maxCells = -1) {\n if (cellSize <= 0) {\n return { cells: [] }; // Invalid cell size, return empty cells array\n }\n // Set a limit on the number of cells traversed\n const HARD_LIMIT = 10000;\n maxCells = (0, utils_1.clamp)(maxCells === -1 ? HARD_LIMIT : maxCells, 0, HARD_LIMIT);\n if (maxCells <= 0) {\n return { cells: [] }; // No cells to traverse\n }\n // Make sure the grid top-left and bottom-right boundaries are integers\n gridTopLeft = vec_1.vec2.map(gridTopLeft, Math.floor);\n gridBottomRight = vec_1.vec2.map(gridBottomRight, Math.ceil);\n // Normalize ray direction and handle zero components\n const rayDir = vec_1.vec2.nor(ray.direction);\n if ((0, utilities_1.vectorAlmostZero)(rayDir)) {\n return { cells: [] };\n }\n const cells = [];\n // Calculate initial cell coordinates\n let currentCell = vec_1.vec2.map(vec_1.vec2.div(vec_1.vec2.sub(ray.origin, gridTopLeft), cellSize), Math.floor);\n // Calculate grid size in cells\n const gridSize = vec_1.vec2.sub(gridBottomRight, gridTopLeft);\n // If starting point is outside grid bounds, find entry point\n if (currentCell.x < 0 ||\n currentCell.x >= gridSize.x ||\n currentCell.y < 0 ||\n currentCell.y >= gridSize.y) {\n // Use rayIntersectsRectangle to find grid entry point\n const gridRect = {\n position: vec_1.vec2.add(gridTopLeft, vec_1.vec2.div(vec_1.vec2.sub(gridBottomRight, gridTopLeft), 2)),\n size: vec_1.vec2.sub(gridBottomRight, gridTopLeft),\n };\n const intersection = rayIntersectsRectangle(ray, gridRect);\n if (!intersection.intersects || !intersection.intersectionPoints) {\n return { cells }; // Ray misses grid entirely\n }\n // Get the first intersection point (closest to ray origin)\n const entryPoint = intersection.intersectionPoints[0];\n currentCell = vec_1.vec2.map(vec_1.vec2.div(vec_1.vec2.sub(entryPoint, gridTopLeft), cellSize), Math.floor);\n // Check if entry point is valid (this should never fail but check anyway)\n if (currentCell.x < 0 ||\n currentCell.x >= gridSize.x ||\n currentCell.y < 0 ||\n currentCell.y >= gridSize.y) {\n return { cells }; // No valid entry point found\n }\n }\n // Calculate step direction (either 1 or -1) for each axis\n const step = {\n x: Math.sign(rayDir.x),\n y: Math.sign(rayDir.y),\n };\n // Calculate tDelta - distance along ray from one grid line to next\n const tDelta = {\n x: rayDir.x !== 0 ? Math.abs(cellSize / rayDir.x) : Infinity,\n y: rayDir.y !== 0 ? Math.abs(cellSize / rayDir.y) : Infinity,\n };\n // Calculate initial cell boundary positions\n const initialBoundary = (0, vec_1.vec2)(gridTopLeft.x + (currentCell.x + (step.x > 0 ? 1 : 0)) * cellSize, gridTopLeft.y + (currentCell.y + (step.y > 0 ? 1 : 0)) * cellSize);\n // Calculate initial tMax values, handling boundary cases\n const tMax = {\n x: rayDir.x !== 0\n ? Math.abs((initialBoundary.x - ray.origin.x) / rayDir.x)\n : Infinity,\n y: rayDir.y !== 0\n ? Math.abs((initialBoundary.y - ray.origin.y) / rayDir.y)\n : Infinity,\n };\n // If we're exactly on a boundary, we need to adjust tMax\n if (Math.abs(ray.origin.x - initialBoundary.x) < constants.EPSILON) {\n tMax.x = tDelta.x;\n }\n if (Math.abs(ray.origin.y - initialBoundary.y) < constants.EPSILON) {\n tMax.y = tDelta.y;\n }\n // Add starting cell\n cells.push((0, vec_1.vec2)(currentCell.x, currentCell.y));\n let cellCount = 1;\n // Main loop\n while (cellCount < maxCells &&\n currentCell.x >= 0 &&\n currentCell.x < gridSize.x &&\n currentCell.y >= 0 &&\n currentCell.y < gridSize.y) {\n // Advance to next cell based on shortest tMax\n if (tMax.x < tMax.y) {\n tMax.x += tDelta.x;\n currentCell.x += step.x;\n }\n else {\n tMax.y += tDelta.y;\n currentCell.y += step.y;\n }\n // Check if we're still in bounds\n if (currentCell.x < 0 ||\n currentCell.x >= gridSize.x ||\n currentCell.y < 0 ||\n currentCell.y >= gridSize.y) {\n break;\n }\n // Add current cell\n cells.push((0, vec_1.vec2)(currentCell.x, currentCell.y));\n cellCount++;\n }\n return { cells };\n}\n/**\n * Check if two rays intersect\n */\nfunction rayIntersectsRay(rayA, rayB) {\n // Normalize the direction vectors\n const dirA = vec_1.vec2.nor(rayA.direction);\n const dirB = vec_1.vec2.nor(rayB.direction);\n // If either ray has zero direction, they cannot intersect\n if ((0, utilities_1.vectorAlmostZero)(dirA) || (0, utilities_1.vectorAlmostZero)(dirB)) {\n return {\n intersects: false,\n };\n }\n // Calculate the cross product determinant\n const det = vec_1.vec2.cross(dirA, dirB);\n // Get the vector between starting points\n const startDiff = vec_1.vec2.sub(rayB.origin, rayA.origin);\n // If determinant is close to 0, rays are parallel or collinear\n if (Math.abs(det) < constants.EPSILON) {\n // Check if rays are collinear\n if (Math.abs(vec_1.vec2.cross(startDiff, dirA)) < constants.EPSILON) {\n // Rays are collinear - check if they overlap\n const t = vec_1.vec2.dot(startDiff, dirA);\n // For rays pointing in the same direction:\n // If t <= 0: rayA's origin is behind or at rayB's origin\n // If t >= 0: rayB's origin is behind or at rayA's origin\n // dot(dirA, dirB) should be close to 1 for same direction\n if ((t <= 0 || t >= 0) && vec_1.vec2.dot(dirA, dirB) > 1 - constants.EPSILON) {\n return {\n intersects: true,\n // No single intersection point for overlapping rays\n };\n }\n }\n return {\n intersects: false,\n };\n }\n // Calculate intersection parameters\n const t = vec_1.vec2.cross(startDiff, dirB) / det;\n const s = vec_1.vec2.cross(startDiff, dirA) / det;\n // Check if intersection occurs on both rays (t >= 0 and s >= 0)\n if (t >= 0 && s >= 0) {\n return {\n intersects: true,\n intersectionPoint: vec_1.vec2.add(rayA.origin, vec_1.vec2.mul(dirA, t)),\n };\n }\n return {\n intersects: false,\n };\n}\n/**\n * Check if a ray intersects a line segment\n */\nfunction rayIntersectsLine(ray, line) {\n // Convert line to a direction vector\n const lineDir = vec_1.vec2.sub(line.end, line.start);\n // Normalize the ray direction\n const rayDir = vec_1.vec2.nor(ray.direction);\n // If either the ray or the line has zero direction, they cannot intersect\n if ((0, utilities_1.vectorAlmostZero)(lineDir) || (0, utilities_1.vectorAlmostZero)(rayDir)) {\n return {\n intersects: false,\n };\n }\n // Calculate the cross product determinant\n const det = vec_1.vec2.cross(rayDir, lineDir);\n // Get the vector between ray origin and line start\n const startDiff = vec_1.vec2.sub(line.start, ray.origin);\n // If determinant is close to 0, ray and line are parallel or collinear\n if (Math.abs(det) < constants.EPSILON) {\n // Check if they are collinear\n if (Math.abs(vec_1.vec2.cross(startDiff, rayDir)) < constants.EPSILON) {\n // They are collinear - project the line endpoints onto the ray\n const t1 = vec_1.vec2.dot(vec_1.vec2.sub(line.start, ray.origin), rayDir);\n const t2 = vec_1.vec2.dot(vec_1.vec2.sub(line.end, ray.origin), rayDir);\n // Check if any part of the line segment is in front of the ray\n if ((t1 >= 0 || t2 >= 0) && Math.min(t1, t2) <= vec_1.vec2.len(lineDir)) {\n return {\n intersects: true,\n // No single intersection point for overlapping segments\n };\n }\n }\n return {\n intersects: false,\n };\n }\n // Calculate intersection parameters\n const t = vec_1.vec2.cross(startDiff, lineDir) / det; // Ray parameter\n const s = vec_1.vec2.cross(startDiff, rayDir) / det; // Line parameter\n // Check if intersection occurs on the ray (t >= 0) and within the line\n // segment (0 <= s <= 1)\n if (t >= 0 && s >= 0 && s <= 1) {\n return {\n intersects: true,\n intersectionPoint: vec_1.vec2.add(ray.origin, vec_1.vec2.mul(rayDir, t)),\n };\n }\n return {\n intersects: false,\n };\n}\n/**\n * Check if a ray intersects a circle\n */\nfunction rayIntersectsCircle(ray, circle) {\n // 1. Parameterized ray equation: P(t) = origin + t * direction\n const rayDir = vec_1.vec2.nor(ray.direction);\n // Calculate vector from ray origin to circle center\n const toCenter = vec_1.vec2.sub(circle.position, ray.origin);\n // 2. Substitute ray equation into circle equation:\n // (origin.x + t*dir.x - circle.x)² + (origin.y + t*dir.y - circle.y)² = r²\n // Expand and collect terms to get quadratic equation: at² + bt + c = 0\n // a = dot(dir, dir) (should be 1 since dir is normalized)\n const a = vec_1.vec2.dot(rayDir, rayDir);\n // b = 2 * dot(dir, (origin - center))\n const b = 2 * vec_1.vec2.dot(rayDir, vec_1.vec2.mul(toCenter, -1));\n // c = dot((origin - center), (origin - center)) - radius²\n const c = vec_1.vec2.dot(toCenter, toCenter) - circle.radius * circle.radius;\n // 3. Solve quadratic equation using discriminant\n const discriminant = b * b - 4 * a * c;\n // 4. Check if solutions exist (discriminant >= 0)\n if (discriminant < -constants.EPSILON) {\n return { intersects: false };\n }\n // Handle case where ray just touches circle (discriminant ≈ 0)\n if (Math.abs(discriminant) < constants.EPSILON) {\n const t = -b / (2 * a);\n if (t >= 0) {\n const point = vec_1.vec2.add(ray.origin, vec_1.vec2.mul(rayDir, t));\n return {\n intersects: true,\n intersectionPoints: [point],\n };\n }\n return { intersects: false };\n }\n // 5. Calculate intersection points for discriminant > 0\n const sqrtDiscriminant = Math.sqrt(discriminant);\n const t1 = (-b - sqrtDiscriminant) / (2 * a);\n const t2 = (-b + sqrtDiscriminant) / (2 * a);\n // If both t values are negative, ray points away from circle\n if (t2 < 0) {\n return { intersects: false };\n }\n // Calculate intersection points for positive t values\n let intersectionPoints = [];\n if (t1 >= 0) {\n intersectionPoints.push(vec_1.vec2.add(ray.origin, vec_1.vec2.mul(rayDir, t1)));\n }\n if (t2 >= 0) {\n intersectionPoints.push(vec_1.vec2.add(ray.origin, vec_1.vec2.mul(rayDir, t2)));\n }\n intersectionPoints = removeDuplicateVertices(intersectionPoints);\n return {\n intersects: intersectionPoints.length > 0,\n intersectionPoints: intersectionPoints.length > 0 ? intersectionPoints : undefined,\n };\n}\n/**\n * Check if a ray intersects a rectangle\n */\nfunction rayIntersectsRectangle(ray, rectangle) {\n // Get vertices of the rectangle in clockwise order\n const vertices = rectangleVertices(rectangle);\n let intersectionPoints = [];\n // Check each edge of the rectangle for intersection\n const edges = verticesToEdges(vertices);\n for (const edge of edges) {\n const intersection = rayIntersectsLine(ray, edge);\n if (intersection.intersects && intersection.intersectionPoint) {\n intersectionPoints.push(intersection.intersectionPoint);\n }\n }\n // Remove duplicate intersection points and sort by distance to ray origin\n intersectionPoints = removeDuplicateVertices(intersectionPoints);\n if (intersectionPoints.length > 1) {\n const rayDir = vec_1.vec2.nor(ray.direction);\n intersectionPoints.sort((a, b) => {\n const distA = vec_1.vec2.dot(vec_1.vec2.sub(a, ray.origin), rayDir);\n const distB = vec_1.vec2.dot(vec_1.vec2.sub(b, ray.origin), rayDir);\n return distA - distB;\n });\n }\n return {\n intersects: intersectionPoints.length > 0,\n intersectionPoints: intersectionPoints.length > 0 ? intersectionPoints : undefined,\n };\n}\n/**\n * Check if a ray intersects the edges of a convex polygon\n *\n * We assume the polygon has already been checked for validity and convexity\n */\nfunction rayIntersectsValidConvexPolygonEdges(ray, edges) {\n let intersectionPoints = [];\n // Check each outer edge for intersections\n for (const edge of edges) {\n const intersection = rayIntersectsLine(ray, edge);\n if (intersection.intersects && intersection.intersectionPoint) {\n intersectionPoints.push(intersection.intersectionPoint);\n }\n }\n // Remove duplicate intersection points and sort by distance to ray origin\n intersectionPoints = removeDuplicateVertices(intersectionPoints);\n if (intersectionPoints.length > 1) {\n const rayDir = vec_1.vec2.nor(ray.direction);\n intersectionPoints.sort((a, b) => {\n const distA = vec_1.vec2.dot(vec_1.vec2.sub(a, ray.origin), rayDir);\n const distB = vec_1.vec2.dot(vec_1.vec2.sub(b, ray.origin), rayDir);\n return distA - distB;\n });\n }\n return {\n intersects: intersectionPoints.length > 0,\n intersectionPoints: intersectionPoints.length > 0 ? intersectionPoints : undefined,\n };\n}\n/**\n * Check if a ray intersects a polygon\n *\n * Returns null if the polygon is invalid\n */\nfunction rayIntersectsPolygon(ray, polygon) {\n // First check if the polygon is valid\n if (!polygonIsValid(polygon)) {\n return null;\n }\n // If polygon is not convex, decompose it into convex polygons\n if (!polygonIsConvex(polygon)) {\n const convexPolygons = decomposePolygon(polygon);\n if (!convexPolygons) {\n return null;\n }\n // Check the ray against the outer edges of each convex polygons\n return rayIntersectsValidConvexPolygonEdges(ray, findOuterEdges(convexPolygons));\n }\n // For convex polygons, check each edge\n return rayIntersectsValidConvexPolygonEdges(ray, verticesToEdges(polygon.vertices));\n}\n/**\n * Check if a line segment intersects a ray\n */\nfunction lineIntersectsRay(line, ray) {\n return rayIntersectsLine(ray, line);\n}\n/**\n * Check if two line segments intersect\n */\nfunction lineIntersectsLine(lineA, lineB) {\n // Get the vectors representing the directions of each line\n const dirA = vec_1.vec2.sub(lineA.end, lineA.start);\n const dirB = vec_1.vec2.sub(lineB.end, lineB.start);\n // If either line has zero direction, they cannot intersect\n if ((0, utilities_1.vectorAlmostZero)(dirA) || (0, utilities_1.vectorAlmostZero)(dirB)) {\n return {\n intersects: false,\n };\n }\n // Calculate the cross product determinant\n const det = vec_1.vec2.cross(dirA, dirB);\n // Get the vector between starting points\n const startDiff = vec_1.vec2.sub(lineB.start, lineA.start);\n // If determinant is close to 0, lines are parallel or collinear\n if (Math.abs(det) < constants.EPSILON) {\n // Check if lines are collinear\n if (Math.abs(vec_1.vec2.cross(startDiff, dirA)) < constants.EPSILON) {\n // Lines are collinear - check if they overlap\n const t0 = vec_1.vec2.dot(startDiff, dirA) / vec_1.vec2.dot(dirA, dirA);\n const t1 = t0 + vec_1.vec2.dot(dirB, dirA) / vec_1.vec2.dot(dirA, dirA);\n // Check if segments overlap\n const interval0 = Math.min(t0, t1);\n const interval1 = Math.max(t0, t1);\n if (interval0 <= 1 && interval1 >= 0) {\n return {\n intersects: true,\n // No single intersection point for overlapping lines\n };\n }\n }\n return {\n intersects: false,\n };\n }\n // Calculate intersection parameters\n const t = vec_1.vec2.cross(startDiff, dirB) / det;\n const s = vec_1.vec2.cross(startDiff, dirA) / det;\n // Check if intersection occurs within both line segments\n if (t >= 0 && t <= 1 && s >= 0 && s <= 1) {\n return {\n intersects: true,\n intersectionPoint: vec_1.vec2.add(lineA.start, vec_1.vec2.mul(dirA, t)),\n };\n }\n return {\n intersects: false,\n };\n}\n/**\n * Check if a line segment intersects a circle\n */\nfunction lineIntersectsCircle(line, circle) {\n // 1. Parameterized line equation: P(t) = start + t * (end - start)\n const lineDir = vec_1.vec2.sub(line.end, line.start);\n const lineLengthSquared = vec_1.vec2.dot(lineDir, lineDir);\n // If the line segment has zero length, it cannot intersect\n if (lineLengthSquared < constants.EPSILON) {\n return { intersects: false };\n }\n // If both endpoints of the line are inside the circle, then we have an\n // intersection (but no intersection points)\n if (pointInCircle(line.start, circle).intersects &&\n pointInCircle(line.end, circle).intersects) {\n return { intersects: true };\n }\n // Calculate vector from circle center to line start\n const toCenter = vec_1.vec2.sub(circle.position, line.start);\n // 2. Substitute line equation into circle equation:\n // (start.x + t*dir.x - circle.x)² + (start.y + t*dir.y - circle.y)² = r²\n // Expand and collect terms to get quadratic equation: at² + bt + c = 0\n // a = dot(dir, dir)\n const a = lineLengthSquared;\n // b = 2 * dot(dir, (start - center))\n const b = 2 * vec_1.vec2.dot(lineDir, vec_1.vec2.mul(toCenter, -1));\n // c = dot((start - center), (start - center)) - radius²\n const c = vec_1.vec2.dot(toCenter, toCenter) - circle.radius * circle.radius;\n // 3. Solve quadratic equation using discriminant\n const discriminant = b * b - 4 * a * c;\n // If discriminant is negative, no intersection\n if (discriminant < -constants.EPSILON) {\n return { intersects: false };\n }\n // Handle case where line just touches circle (discriminant ≈ 0)\n if (Math.abs(discriminant) < constants.EPSILON) {\n const t = -b / (2 * a);\n if (t >= 0 && t <= 1) {\n const point = vec_1.vec2.add(line.start, vec_1.vec2.mul(lineDir, t));\n return {\n intersects: true,\n intersectionPoints: [point],\n };\n }\n return { intersects: false };\n }\n // Calculate intersection points for discriminant > 0\n const sqrtDiscriminant = Math.sqrt(discriminant);\n const t1 = (-b - sqrtDiscriminant) / (2 * a);\n const t2 = (-b + sqrtDiscriminant) / (2 * a);\n let intersectionPoints = [];\n // If both t values are outside [0, 1], no intersection\n if (t2 < 0 || t1 > 1) {\n return { intersects: false };\n }\n // Calculate intersection points for valid t values\n if (t1 >= 0 && t1 <= 1) {\n intersectionPoints.push(vec_1.vec2.add(line.start, vec_1.vec2.mul(lineDir, t1)));\n }\n if (t2 >= 0 && t2 <= 1) {\n intersectionPoints.push(vec_1.vec2.add(line.start, vec_1.vec2.mul(lineDir, t2)));\n }\n // Remove duplicate intersection points and sort by distance to line start\n intersectionPoints = removeDuplicateVertices(intersectionPoints);\n if (intersectionPoints.length > 1) {\n intersectionPoints.sort((a, b) => {\n const distA = vec_1.vec2.len(vec_1.vec2.sub(a, line.start));\n const distB = vec_1.vec2.len(vec_1.vec2.sub(b, line.start));\n return distA - distB;\n });\n }\n return {\n intersects: intersectionPoints.length > 0,\n intersectionPoints: intersectionPoints.length > 0 ? intersectionPoints : undefined,\n };\n}\n/**\n * Check if a line segment intersects a rectangle\n */\nfunction lineIntersectsRectangle(line, rectangle) {\n // Edge case: zero-size rectangle\n if ((0, utilities_1.vectorAlmostZero)(rectangle.size)) {\n return {\n intersects: false,\n };\n }\n // Get vertices of the rectangle in clockwise order\n const vertices = rectangleVertices(rectangle);\n // If both endpoints are inside, line is completely contained\n if (pointInRectangle(line.start, rectangle).intersects &&\n pointInRectangle(line.end, rectangle).intersects) {\n return {\n intersects: true,\n };\n }\n let intersectionPoints = [];\n // Check each edge of the rectangle for intersection\n const edges = verticesToEdges(vertices);\n for (const edge of edges) {\n const intersection = lineIntersectsLine(line, edge);\n if (intersection.intersects && intersection.intersectionPoint) {\n intersectionPoints.push(intersection.intersectionPoint);\n }\n }\n // Remove duplicate intersection points and sort by distance to line start\n intersectionPoints = removeDuplicateVertices(intersectionPoints);\n if (intersectionPoints.length > 1) {\n intersectionPoints.sort((a, b) => {\n const distA = vec_1.vec2.len(vec_1.vec2.sub(a, line.start));\n const distB = vec_1.vec2.len(vec_1.vec2.sub(b, line.start));\n return distA - distB;\n });\n }\n return {\n intersects: intersectionPoints.length > 0,\n intersectionPoints: intersectionPoints.length > 0 ? intersectionPoints : undefined,\n };\n}\n/**\n * Check if a line segment intersects the edges of a convex polygon\n *\n * We assume the polygon has already been checked for validity and convexity\n */\nfunction lineIntersectsValidConvexPolygonEdges(line, polygon, edges) {\n // Special case: line segment is entirely inside polygon\n const midpoint = {\n x: (line.start.x + line.end.x) / 2,\n y: (line.start.y + line.end.y) / 2,\n };\n const pointInside = pointInPolygon(midpoint, polygon);\n const startInside = pointInPolygon(line.start, polygon);\n const endInside = pointInPolygon(line.end, polygon);\n if ((pointInside === null || pointInside === void 0 ? void 0 : pointInside.intersects) &&\n (startInside === null || startInside === void 0 ? void 0 : startInside.intersects) &&\n (endInside === null || endInside === void 0 ? void 0 : endInside.intersects)) {\n return {\n intersects: true,\n };\n }\n let intersectionPoints = [];\n // Check each outer edge for intersections\n for (const edge of edges) {\n const intersection = lineIntersectsLine(line, edge);\n if (intersection.intersects && intersection.intersectionPoint) {\n intersectionPoints.push(intersection.intersectionPoint);\n }\n }\n // Remove duplicate intersection points and sort by distance to line start\n intersectionPoints = removeDuplicateVertices(intersectionPoints);\n if (intersectionPoints.length > 1) {\n intersectionPoints.sort((a, b) => {\n const distA = vec_1.vec2.len(vec_1.vec2.sub(a, line.start));\n const distB = vec_1.vec2.len(vec_1.vec2.sub(b, line.start));\n return distA - distB;\n });\n }\n return {\n intersects: intersectionPoints.length > 0,\n intersectionPoints: intersectionPoints.length > 0 ? intersectionPoints : undefined,\n };\n}\n/**\n * Check if a line segment intersects a polygon\n *\n * Returns null if the polygon is invalid\n */\nfunction lineIntersectsPolygon(line, polygon) {\n // First check if the polygon is valid\n if (!polygonIsValid(polygon)) {\n return null;\n }\n // If polygon is not convex, decompose it into convex polygons\n if (!polygonIsConvex(polygon)) {\n const convexPolygons = decomposePolygon(polygon);\n if (!convexPolygons) {\n return null;\n }\n // Check the line against the outer edges of each convex polygon\n return lineIntersectsValidConvexPolygonEdges(line, polygon, findOuterEdges(convexPolygons));\n }\n // For convex polygons, check each edge\n return lineIntersectsValidConvexPolygonEdges(line, polygon, verticesToEdges(polygon.vertices));\n}\n/**\n * Check if two circles intersect\n */\nfunction circleIntersectsCircle(circleA, circleB) {\n // Calculate the vector from center A to center B\n const centerToCenterVec = vec_1.vec2.sub(circleB.position, circleA.position);\n const centerToCenter = vec_1.vec2.len(centerToCenterVec);\n const sumRadii = circleA.radius + circleB.radius;\n // If distance between centers is greater than sum of radii, the circles\n // don't intersect\n if (centerToCenter > sumRadii + constants.EPSILON) {\n return { intersects: false };\n }\n // If circles are identical (same position and radius), they have infinitely\n // many intersection points\n if (centerToCenter < constants.EPSILON &&\n Math.abs(circleA.radius - circleB.radius) < constants.EPSILON) {\n return {\n intersects: true,\n minimumSeparation: vec_1.vec2.mul(vec_1.vec2.ux(), 2 * circleA.radius),\n };\n }\n // Check if one circle is inside the other (no intersection points but still\n // intersecting)\n const radiusDiff = Math.abs(circleA.radius - circleB.radius);\n if (centerToCenter < radiusDiff - constants.EPSILON) {\n return {\n intersects: true,\n minimumSeparation: vec_1.vec2.mul(vec_1.vec2.nor(centerToCenterVec), circleA.radius - centerToCenter + circleB.radius),\n };\n }\n // Calculate intersection points for standard intersecting case\n // http://mathworld.wolfram.com/Circle-CircleIntersection.html\n const a = (circleA.radius * circleA.radius -\n circleB.radius * circleB.radius +\n centerToCenter * centerToCenter) /\n (2 * centerToCenter);\n const h = Math.sqrt(Math.max(0, circleA.radius * circleA.radius - a * a));\n // Calculate the point on the line between centers that is distance 'a' from\n // circle A's center\n const p = vec_1.vec2.add(circleA.position, vec_1.vec2.mul(vec_1.vec2.nor(centerToCenterVec), a));\n // If circles are tangent (touching at one point)\n if (Math.abs(centerToCenter - sumRadii) < constants.EPSILON) {\n return {\n intersects: true,\n intersectionPoints: [p],\n minimumSeparation: (0, vec_1.vec2)(),\n };\n }\n // Calculate the perpendicular vector to get both intersection points\n const perpVec = vec_1.vec2.mul((0, vec_1.vec2)({ x: -centerToCenterVec.y, y: centerToCenterVec.x }), h / centerToCenter);\n const intersectionPoints = [vec_1.vec2.add(p, perpVec), vec_1.vec2.sub(p, perpVec)];\n // Calculate the minimum separation vector\n const minimumSeparation = vec_1.vec2.mul(vec_1.vec2.nor(centerToCenterVec), sumRadii - centerToCenter);\n return {\n intersects: true,\n intersectionPoints,\n minimumSeparation,\n };\n}\n/**\n * Check if a circle intersects a rectangle\n */\nfunction circleIntersectsRectangle(circle, rectangle) {\n // Get rectangle vertices so we can test against rotated rectangles\n const vertices = rectangleVertices(rectangle);\n const edges = verticesToEdges(vertices);\n // Check if circle's center is inside rectangle\n const pointInRectResult = pointInRectangle(circle.position, rectangle);\n const circleCenterInsideRectangle = pointInRectResult.intersects;\n // Check if rectangle's center is inside circle\n const pointInCircleResult = pointInCircle(rectangle.position, circle);\n const rectangleCenterInsideCircle = pointInCircleResult.intersects;\n // Check circle intersection with rectangle edges\n const intersectionPoints = [];\n for (const edge of edges) {\n const result = lineIntersectsCircle(edge, circle);\n if (result.intersects && result.intersectionPoints) {\n intersectionPoints.push(...result.intersectionPoints);\n }\n }\n // Calculate the minimum separation vector\n let minimumSeparation;\n if (Math.abs(pointInRectResult.distance) < constants.EPSILON) {\n minimumSeparation = (0, vec_1.vec2)();\n }\n else if (pointInRectResult.distance < 0) {\n minimumSeparation = vec_1.vec2.mul(vec_1.vec2.nor(vec_1.vec2.sub(pointInRectResult.closestPoint, circle.position)), circle.radius + Math.abs(pointInRectResult.distance));\n }\n else {\n minimumSeparation = vec_1.vec2.mul(vec_1.vec2.nor(vec_1.vec2.sub(circle.position, pointInRectResult.closestPoint)), circle.radius - pointInRectResult.distance);\n }\n // If either shape's center is inside the other and there are no intersection\n // points, it means one of the shapes completely encloses the other\n if ((circleCenterInsideRectangle || rectangleCenterInsideCircle) &&\n intersectionPoints.length === 0) {\n return {\n intersects: true,\n minimumSeparation,\n };\n }\n // Remove duplicate intersection points\n const uniquePoints = removeDuplicateVertices(intersectionPoints);\n if (uniquePoints.length > 0) {\n return {\n intersects: true,\n intersectionPoints: uniquePoints,\n minimumSeparation,\n };\n }\n return { intersects: false };\n}\n/**\n * Check if a circle intersects the edges of a convex polygon\n *\n * We assume the polygon has already been checked for validity and convexity\n */\nfunction circleIntersectsValidConvexPolygonEdges(circle, edges, circleCenterInsidePolygon, polygonCenterInsideCircle) {\n let intersectionPoints = [];\n // Check each outer edge for intersections with the circle\n for (const edge of edges) {\n const result = lineIntersectsCircle(edge, circle);\n if (result.intersects && result.intersectionPoints) {\n intersectionPoints.push(...result.intersectionPoints);\n }\n }\n // If either shape's center is inside the other and there are no\n // intersection points, one shape completely encloses the other\n if ((circleCenterInsidePolygon || polygonCenterInsideCircle) &&\n intersectionPoints.length === 0) {\n return { intersects: true };\n }\n // Remove duplicate intersection points\n intersectionPoints = removeDuplicateVertices(intersectionPoints);\n return {\n intersects: intersectionPoints.length > 0,\n intersectionPoints: intersectionPoints.length > 0 ? intersectionPoints : undefined,\n };\n}\n/**\n * Check if a circle intersects a polygon\n *\n * Returns null if the polygon is invalid\n */\nfunction circleIntersectsPolygon(circle, polygon, options) {\n var _a, _b, _c, _d, _e, _f;\n // First check if the polygon is valid\n if (!polygonIsValid(polygon)) {\n return null;\n }\n const MAX_ITERATIONS = 10;\n const findMinimumSeparation = (_a = options === null || options === void 0 ? void 0 : options.findMinimumSeparation) !== null && _a !== void 0 ? _a : false;\n // Check if circle's center is inside polygon\n const pointInPolygonResult = pointInPolygon(circle.position, polygon);\n const circleCenterInsidePolygon = (_b = pointInPolygonResult === null || pointInPolygonResult === void 0 ? void 0 : pointInPolygonResult.intersects) !== null && _b !== void 0 ? _b : false;\n // If polygon is not convex, decompose it into convex polygons\n if (!polygonIsConvex(polygon)) {\n const convexPolygons = decomposePolygon(polygon);\n if (!convexPolygons) {\n return null;\n }\n // For a concave polygon, the centroid might be outside of the polygon, so\n // in order to check if the polygon is entirely inside the circle, we need\n // to check if all sub-polygon centroids are inside the circle\n const polygonCenterInsideCircle = convexPolygons.every(convexPolygon => {\n var _a;\n const centroid = polygonCentroid(convexPolygon);\n if (!centroid) {\n return false; // Invalid centroid\n }\n return (_a = pointInCircle(centroid, circle).intersects) !== null && _a !== void 0 ? _a : false;\n });\n // Find outer edges from the decomposed polygons\n const outerEdges = findOuterEdges(convexPolygons);\n const result = circleIntersectsValidConvexPolygonEdges(circle, outerEdges, circleCenterInsidePolygon, polygonCenterInsideCircle);\n if (result.intersects && findMinimumSeparation) {\n let iteration = 0;\n let previousSeparation = null;\n let currentSeparation = (0, vec_1.vec2)();\n let currentSeparationIntersects = true;\n while (\n // Continue if we still haven't found a separation that doesn't\n // intersect\n currentSeparationIntersects &&\n // Continue if we're still converging (i.e. if we didn't make any\n // progress in the last iteration then we can stop)\n (previousSeparation === null ||\n !(0, utilities_1.vectorsAlmostEqual)(previousSeparation, currentSeparation)) &&\n // Continue until we reach the maximum number of iterations\n ++iteration < MAX_ITERATIONS) {\n let minimumSeparations = [];\n let circlePosition = vec_1.vec2.add(circle.position, currentSeparation);\n // Find minimum separation vectors for each convex sub-polygon\n for (const convexPolygon of convexPolygons) {\n const pointInConvexPolygonResult = pointInPolygon(circlePosition, convexPolygon);\n if (!pointInConvexPolygonResult) {\n continue;\n }\n let minimumSeparation;\n if (Math.abs(pointInConvexPolygonResult.distance) < constants.EPSILON) {\n minimumSeparation = (0, vec_1.vec2)();\n }\n else if (pointInConvexPolygonResult.distance < 0) {\n minimumSeparation = vec_1.vec2.mul(vec_1.vec2.nor(vec_1.vec2.sub(pointInConvexPolygonResult.closestPoint, circlePosition)), circle.radius + Math.abs(pointInConvexPolygonResult.distance));\n }\n else {\n minimumSeparation = vec_1.vec2.mul(vec_1.vec2.nor(vec_1.vec2.sub(circlePosition, pointInConvexPolygonResult.closestPoint)), circle.radius - pointInConvexPolygonResult.distance);\n }\n minimumSeparations.push({\n separation: minimumSeparation,\n distance: Math.abs(pointInConvexPolygonResult.distance),\n });\n }\n // Sort minimum separations by penetration distance\n minimumSeparations = minimumSeparations.sort((a, b) => a.distance - b.distance);\n previousSeparation = vec_1.vec2.cpy(currentSeparation);\n currentSeparation = vec_1.vec2.add(currentSeparation, ((_c = minimumSeparations[0]) === null || _c === void 0 ? void 0 : _c.separation) || (0, vec_1.vec2)());\n // Check if the current separation still intersects\n currentSeparationIntersects =\n (_e = (_d = circleIntersectsPolygon({\n ...circle,\n position: vec_1.vec2.add(circle.position, \n // Add a small buffer to avoid numerical/precision issues\n vec_1.vec2.mul(currentSeparation, 1.01)),\n }, polygon, {\n ...options,\n // Don't recurse to avoid infinite loops\n findMinimumSeparation: false,\n })) === null || _d === void 0 ? void 0 : _d.intersects) !== null && _e !== void 0 ? _e : false;\n }\n return {\n ...result,\n minimumSeparation: currentSeparation,\n };\n }\n return result;\n }\n // Check if polygon's centroid is inside circle\n // For a convex polygon, the centroid is always inside the polygon\n const polygonCenter = polygonCentroid(polygon);\n const pointInCircleResult = pointInCircle(polygonCenter, circle);\n const polygonCenterInsideCircle = (_f = pointInCircleResult.intersects) !== null && _f !== void 0 ? _f : false;\n // For convex polygons, check each edge directly\n const edges = verticesToEdges(polygon.vertices);\n const result = circleIntersectsValidConvexPolygonEdges(circle, edges, circleCenterInsidePolygon, polygonCenterInsideCircle);\n if (result.intersects && findMinimumSeparation) {\n // Calculate the minimum separation vector\n let minimumSeparation;\n if (Math.abs(pointInPolygonResult.distance) < constants.EPSILON) {\n minimumSeparation = (0, vec_1.vec2)();\n }\n else if (pointInPolygonResult.distance < 0) {\n minimumSeparation = vec_1.vec2.mul(vec_1.vec2.nor(vec_1.vec2.sub(pointInPolygonResult.closestPoint, circle.position)), circle.radius + Math.abs(pointInPolygonResult.distance));\n }\n else {\n minimumSeparation = vec_1.vec2.mul(vec_1.vec2.nor(vec_1.vec2.sub(circle.position, pointInPolygonResult.closestPoint)), circle.radius - pointInPolygonResult.distance);\n }\n return {\n ...result,\n minimumSeparation,\n };\n }\n return result;\n}\n/**\n * Project vertices onto an axis and return the min/max values\n */\nfunction projectVerticesToAxis(vertices, axis) {\n let min = Infinity;\n let max = -Infinity;\n for (const vertex of vertices) {\n const projection = vec_1.vec2.dot(vertex, axis);\n min = Math.min(min, projection);\n max = Math.max(max, projection);\n }\n return { min, max };\n}\n/**\n * Check if two rectangles intersect\n */\nfunction rectangleIntersectsRectangle(rectangleA, rectangleB) {\n // Edge case: if either rectangle has zero size, they cannot intersect\n if ((0, utilities_1.vectorAlmostZero)(rectangleA.size) || (0, utilities_1.vectorAlmostZero)(rectangleB.size)) {\n return { intersects: false };\n }\n // Get vertices of both rectangles\n const verticesA = rectangleVertices(rectangleA);\n const verticesB = rectangleVertices(rectangleB);\n // Get edges of both rectangles\n const edgesA = verticesToEdges(verticesA);\n const edgesB = verticesToEdges(verticesB);\n // Get separating axes by calculating the normals of each edge\n const axes = [];\n for (const edge of [...edgesA, ...edgesB]) {\n const edgeVec = vec_1.vec2.sub(edge.end, edge.start);\n const normal = vec_1.vec2.nor(vec_1.vec2.rotf(edgeVec, -1));\n // Only add unique axes\n if (!axes.some(axis => Math.abs(vec_1.vec2.dot(axis, normal)) > 1 - constants.EPSILON)) {\n axes.push(normal);\n }\n }\n // Track minimum penetration for separation vector\n let minPenetration = Infinity;\n let minAxis = (0, vec_1.vec2)();\n // Test each axis\n for (const axis of axes) {\n // Project both rectangles onto the axis\n const projectionA = projectVerticesToAxis(verticesA, axis);\n const projectionB = projectVerticesToAxis(verticesB, axis);\n // If we find a separating axis, the rectangles don't intersect\n if (projectionA.max < projectionB.min ||\n projectionB.max < projectionA.min) {\n return { intersects: false };\n }\n // Calculate penetration depth\n const overlap = Math.min(projectionA.max - projectionB.min, projectionB.max - projectionA.min);\n // Track minimum penetration and its axis\n if (overlap < minPenetration) {\n minPenetration = overlap;\n minAxis = axis;\n }\n }\n // Find intersection points by checking each edge of rectangle A against each\n // edge of rectangle B\n const intersectionPoints = [];\n for (const edgeA of edgesA) {\n for (const edgeB of edgesB) {\n const intersection = lineIntersectsLine(edgeA, edgeB);\n if (intersection.intersects && intersection.intersectionPoint) {\n intersectionPoints.push(intersection.intersectionPoint);\n }\n }\n }\n // Remove duplicate intersection points\n const uniquePoints = removeDuplicateVertices(intersectionPoints);\n // Calculate the minimum separation vector\n const centerA = rectangleA.position;\n const centerB = rectangleB.position;\n const centerToCenter = vec_1.vec2.sub(centerB, centerA);\n // If the dot product is negative, we need to flip the axis\n if (vec_1.vec2.dot(minAxis, centerToCenter) < 0) {\n minAxis = vec_1.vec2.mul(minAxis, -1);\n }\n // The minimum separation vector is the axis scaled by the penetration depth\n const minimumSeparation = vec_1.vec2.mul(minAxis, minPenetration);\n return {\n intersects: true,\n intersectionPoints: uniquePoints.length > 0 ? uniquePoints : undefined,\n minimumSeparation,\n };\n}\n/**\n * Check if a rectangle intersects a polygon\n *\n * Returns null if the polygon is invalid\n */\nfunction rectangleIntersectsPolygon(rectangle, polygon) {\n // First check if the polygon is valid\n if (!polygonIsValid(polygon)) {\n return null;\n }\n // Edge case: if the rectangle has zero size, there is no intersection\n if ((0, utilities_1.vectorAlmostZero)(rectangle.size)) {\n return { intersects: false };\n }\n // Convert rectangle to polygon\n const rectVertices = rectangleVertices(rectangle);\n const rectPolygon = {\n vertices: rectVertices,\n };\n // Use polygon intersection algorithm\n return polygonIntersectsPolygon(rectPolygon, polygon);\n}\n/**\n * Check if two polygons intersect\n *\n * Returns null if either polygon is invalid\n */\nfunction polygonIntersectsPolygon(polygonA, polygonB) {\n // First check if both polygons are valid\n if (!polygonIsValid(polygonA) || !polygonIsValid(polygonB)) {\n return null;\n }\n // Decompose polygon A if it's concave\n let convexPolygonsA = [];\n if (!polygonIsConvex(polygonA)) {\n const decomposedA = decomposePolygon(polygonA);\n if (!decomposedA) {\n return null;\n }\n convexPolygonsA = decomposedA;\n }\n else {\n convexPolygonsA = [polygonA];\n }\n // Decompose polygon B if it's concave\n let convexPolygonsB = [];\n if (!polygonIsConvex(polygonB)) {\n const decomposedB = decomposePolygon(polygonB);\n if (!decomposedB) {\n return null;\n }\n convexPolygonsB = decomposedB;\n }\n else {\n convexPolygonsB = [polygonB];\n }\n // Get the outer edges of the decomposed polygons\n const outerEdgesA = findOuterEdges(convexPolygonsA);\n const outerEdgesB = findOuterEdges(convexPolygonsB);\n // Find intersection points between outer edges only\n const intersectionPoints = [];\n for (const edgeA of outerEdgesA) {\n for (const edgeB of outerEdgesB) {\n const intersection = lineIntersectsLine(edgeA, edgeB);\n if (intersection.intersects && intersection.intersectionPoint) {\n intersectionPoints.push(intersection.intersectionPoint);\n }\n }\n }\n // Check if one polygon is contained within the other\n // A polygon is contained within another if the centroids of all its\n // convex sub-polygons are inside the other polygon\n if (intersectionPoints.length === 0) {\n const polygonACentroids = convexPolygonsA\n .map(polygonCentroid)\n .filter(centroid => !!centroid);\n if (polygonACentroids.every(centroid => { var _a; return (_a = pointInPolygon(centroid, polygonB)) === null || _a === void 0 ? void 0 : _a.intersects; })) {\n return { intersects: true };\n }\n const polygonBCentroids = convexPolygonsB\n .map(polygonCentroid)\n .filter(centroid => !!centroid);\n if (polygonBCentroids.every(centroid => { var _a; return (_a = pointInPolygon(centroid, polygonA)) === null || _a === void 0 ? void 0 : _a.intersects; })) {\n return { intersects: true };\n }\n }\n // Remove duplicate intersection points\n const uniquePoints = removeDuplicateVertices(intersectionPoints);\n return {\n intersects: uniquePoints.length > 0,\n intersectionPoints: uniquePoints.length > 0 ? uniquePoints : undefined,\n };\n}\n\n\n/***/ }),\n\n/***/ \"./src/2d/types.ts\":\n/*!*************************!*\\\n !*** ./src/2d/types.ts ***!\n \\*************************/\n/***/ ((__unused_webpack_module, exports, __nested_webpack_require_140598__) => {\n\n\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.isPoint = isPoint;\nexports.isRay = isRay;\nexports.isLine = isLine;\nexports.isCircle = isCircle;\nexports.isAABB = isAABB;\nexports.isRectangle = isRectangle;\nexports.isPolygon = isPolygon;\nconst types_1 = __nested_webpack_require_140598__(/*! ../utilities/types */ \"./src/utilities/types.ts\");\n/**\n * Type guard to check if a value is a Point\n */\nfunction isPoint(value) {\n return (0, types_1.isVec2)(value);\n}\n/**\n * Check if a value is a Ray\n */\nfunction isRay(value) {\n return (value &&\n typeof value === 'object' &&\n 'origin' in value &&\n isPoint(value.origin) &&\n 'direction' in value &&\n (0, types_1.isVec2)(value.direction));\n}\n/**\n * Check if a value is a Line\n */\nfunction isLine(value) {\n return (value &&\n typeof value === 'object' &&\n 'start' in value &&\n isPoint(value.start) &&\n 'end' in value &&\n isPoint(value.end));\n}\n/**\n * Check if a value is a Circle\n */\nfunction isCircle(value) {\n return (value &&\n typeof value === 'object' &&\n 'position' in value &&\n isPoint(value.position) &&\n 'radius' in value &&\n typeof value.radius === 'number');\n}\n/**\n * Check if a value is an AABB\n */\nfunction isAABB(value) {\n return (value &&\n typeof value === 'object' &&\n 'position' in value &&\n isPoint(value.position) &&\n 'size' in value &&\n (0, types_1.isVec2)(value.size));\n}\n/**\n * Check if a value is a Rectangle\n */\nfunction isRectangle(value) {\n return (value &&\n typeof value === 'object' &&\n 'position' in value &&\n isPoint(value.position) &&\n 'size' in value &&\n (0, types_1.isVec2)(value.size) &&\n ('rotation' in value ? typeof value.rotation === 'number' : true));\n}\n/**\n * Check if a value is a Polygon\n */\nfunction isPolygon(value) {\n return (value &&\n typeof value === 'object' &&\n 'vertices' in value &&\n Array.isArray(value.vertices) &&\n value.vertices.every(isPoint));\n}\n\n\n/***/ }),\n\n/***/ \"./src/utilities/constants.ts\":\n/*!************************************!*\\\n !*** ./src/utilities/constants.ts ***!\n \\************************************/\n/***/ ((__unused_webpack_module, exports) => {\n\n\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.EPSILON = void 0;\nexports.EPSILON = 1e-6;\n\n\n/***/ }),\n\n/***/ \"./src/utilities/index.ts\":\n/*!********************************!*\\\n !*** ./src/utilities/index.ts ***!\n \\********************************/\n/***/ (function(__unused_webpack_module, exports, __nested_webpack_require_143285__) {\n\n\"use strict\";\n\nvar __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n}));\nvar __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n});\nvar __importStar = (this && this.__importStar) || (function () {\n var ownKeys = function(o) {\n ownKeys = Object.getOwnPropertyNames || function (o) {\n var ar = [];\n for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;\n return ar;\n };\n return ownKeys(o);\n };\n return function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== \"default\") __createBinding(result, mod, k[i]);\n __setModuleDefault(result, mod);\n return result;\n };\n})();\nvar __exportStar = (this && this.__exportStar) || function(m, exports) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.vectorAlmostZero = vectorAlmostZero;\nexports.vectorsAlmostEqual = vectorsAlmostEqual;\nexports.valueInInterval = valueInInterval;\nexports.intervalsOverlap = intervalsOverlap;\nexports.overlapInterval = overlapInterval;\nconst constants = __importStar(__nested_webpack_require_143285__(/*! ./constants */ \"./src/utilities/constants.ts\"));\nconst types_1 = __nested_webpack_require_143285__(/*! ./types */ \"./src/utilities/types.ts\");\n__exportStar(__nested_webpack_require_143285__(/*! ./types */ \"./src/utilities/types.ts\"), exports);\nfunction vectorAlmostZero(v) {\n if ((0, types_1.isVec3)(v)) {\n return (Math.abs(v.x) < constants.EPSILON &&\n Math.abs(v.y) < constants.EPSILON &&\n Math.abs(v.z) < constants.EPSILON);\n }\n if ((0, types_1.isVec2)(v)) {\n return (Math.abs(v.x) < constants.EPSILON && Math.abs(v.y) < constants.EPSILON);\n }\n return false;\n}\nfunction vectorsAlmostEqual(a, b) {\n if ((0, types_1.isVec3)(a) && (0, types_1.isVec3)(b)) {\n return (Math.abs(a.x - b.x) < constants.EPSILON &&\n Math.abs(a.y - b.y) < constants.EPSILON &&\n Math.abs(a.z - b.z) < constants.EPSILON);\n }\n if ((0, types_1.isVec2)(a) && (0, types_1.isVec2)(b)) {\n return (Math.abs(a.x - b.x) < constants.EPSILON &&\n Math.abs(a.y - b.y) < constants.EPSILON);\n }\n return false;\n}\n/**\n * Check if a value is within a specified interval\n */\nfunction valueInInterval(value, interval) {\n const { min, minInclusive = true, max, maxInclusive = true } = interval;\n return ((minInclusive ? value >= min : value > min) &&\n (maxInclusive ? value <= max : value < max));\n}\n/**\n * Check if two intervals (a1, a2) and (b1, b2) overlap\n */\nfunction intervalsOverlap(a, b) {\n return Math.max(a.min, b.min) <= Math.min(a.max, b.max);\n}\n/**\n * Get the overlapping part of two intervals (a1, a2) and (b1, b2)\n *\n * If the intervals do not overlap, return null\n */\nfunction overlapInterval(a, b) {\n if (!intervalsOverlap(a, b)) {\n return null;\n }\n return { min: Math.max(a.min, b.min), max: Math.min(a.max, b.max) };\n}\n\n\n/***/ }),\n\n/***/ \"./src/utilities/types.ts\":\n/*!********************************!*\\\n !*** ./src/utilities/types.ts ***!\n \\********************************/\n/***/ ((__unused_webpack_module, exports) => {\n\n\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.isVec2 = isVec2;\nexports.isVec3 = isVec3;\n/**\n * Check if a value is a vec2\n */\nfunction isVec2(value) {\n return (value &&\n typeof value === 'object' &&\n 'x' in value &&\n typeof value.x === 'number' &&\n 'y' in value &&\n typeof value.y === 'number' &&\n !('z' in value));\n}\n/**\n * Check if a value is a vec3\n */\nfunction isVec3(value) {\n return (value &&\n typeof value === 'object' &&\n 'x' in value &&\n typeof value.x === 'number' &&\n 'y' in value &&\n typeof value.y === 'number' &&\n 'z' in value &&\n typeof value.z === 'number');\n}\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __nested_webpack_require_148193__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tvar cachedModule = __webpack_module_cache__[moduleId];\n/******/ \t\tif (cachedModule !== undefined) {\n/******/ \t\t\treturn cachedModule.exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __nested_webpack_require_148193__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \t// This entry module is referenced by other modules so it can't be inlined\n/******/ \tvar __nested_webpack_exports__ = __nested_webpack_require_148193__(\"./src/2d/index.ts\");\n/******/ \t\n/******/ \treturn __nested_webpack_exports__;\n/******/ })()\n;\n});\n\n//# sourceURL=webpack://@basementuniverse/particles-2d/./node_modules/@basementuniverse/intersection-helpers/build/2d/index.js?");
|
|
39
|
+
|
|
40
|
+
/***/ }),
|
|
41
|
+
|
|
42
|
+
/***/ "./node_modules/@basementuniverse/intersection-helpers/build/utilities/index.js":
|
|
43
|
+
/*!**************************************************************************************!*\
|
|
44
|
+
!*** ./node_modules/@basementuniverse/intersection-helpers/build/utilities/index.js ***!
|
|
45
|
+
\**************************************************************************************/
|
|
46
|
+
/***/ (function(module) {
|
|
47
|
+
|
|
48
|
+
eval("(function webpackUniversalModuleDefinition(root, factory) {\n\tif(true)\n\t\tmodule.exports = factory();\n\telse { var i, a; }\n})(this, () => {\nreturn /******/ (() => { // webpackBootstrap\n/******/ \t\"use strict\";\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ \"./src/utilities/constants.ts\":\n/*!************************************!*\\\n !*** ./src/utilities/constants.ts ***!\n \\************************************/\n/***/ ((__unused_webpack_module, exports) => {\n\n\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.EPSILON = void 0;\nexports.EPSILON = 1e-6;\n\n\n/***/ }),\n\n/***/ \"./src/utilities/index.ts\":\n/*!********************************!*\\\n !*** ./src/utilities/index.ts ***!\n \\********************************/\n/***/ (function(__unused_webpack_module, exports, __nested_webpack_require_1000__) {\n\n\nvar __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n}));\nvar __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n});\nvar __importStar = (this && this.__importStar) || (function () {\n var ownKeys = function(o) {\n ownKeys = Object.getOwnPropertyNames || function (o) {\n var ar = [];\n for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;\n return ar;\n };\n return ownKeys(o);\n };\n return function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== \"default\") __createBinding(result, mod, k[i]);\n __setModuleDefault(result, mod);\n return result;\n };\n})();\nvar __exportStar = (this && this.__exportStar) || function(m, exports) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.vectorAlmostZero = vectorAlmostZero;\nexports.vectorsAlmostEqual = vectorsAlmostEqual;\nexports.valueInInterval = valueInInterval;\nexports.intervalsOverlap = intervalsOverlap;\nexports.overlapInterval = overlapInterval;\nconst constants = __importStar(__nested_webpack_require_1000__(/*! ./constants */ \"./src/utilities/constants.ts\"));\nconst types_1 = __nested_webpack_require_1000__(/*! ./types */ \"./src/utilities/types.ts\");\n__exportStar(__nested_webpack_require_1000__(/*! ./types */ \"./src/utilities/types.ts\"), exports);\nfunction vectorAlmostZero(v) {\n if ((0, types_1.isVec3)(v)) {\n return (Math.abs(v.x) < constants.EPSILON &&\n Math.abs(v.y) < constants.EPSILON &&\n Math.abs(v.z) < constants.EPSILON);\n }\n if ((0, types_1.isVec2)(v)) {\n return (Math.abs(v.x) < constants.EPSILON && Math.abs(v.y) < constants.EPSILON);\n }\n return false;\n}\nfunction vectorsAlmostEqual(a, b) {\n if ((0, types_1.isVec3)(a) && (0, types_1.isVec3)(b)) {\n return (Math.abs(a.x - b.x) < constants.EPSILON &&\n Math.abs(a.y - b.y) < constants.EPSILON &&\n Math.abs(a.z - b.z) < constants.EPSILON);\n }\n if ((0, types_1.isVec2)(a) && (0, types_1.isVec2)(b)) {\n return (Math.abs(a.x - b.x) < constants.EPSILON &&\n Math.abs(a.y - b.y) < constants.EPSILON);\n }\n return false;\n}\n/**\n * Check if a value is within a specified interval\n */\nfunction valueInInterval(value, interval) {\n const { min, minInclusive = true, max, maxInclusive = true } = interval;\n return ((minInclusive ? value >= min : value > min) &&\n (maxInclusive ? value <= max : value < max));\n}\n/**\n * Check if two intervals (a1, a2) and (b1, b2) overlap\n */\nfunction intervalsOverlap(a, b) {\n return Math.max(a.min, b.min) <= Math.min(a.max, b.max);\n}\n/**\n * Get the overlapping part of two intervals (a1, a2) and (b1, b2)\n *\n * If the intervals do not overlap, return null\n */\nfunction overlapInterval(a, b) {\n if (!intervalsOverlap(a, b)) {\n return null;\n }\n return { min: Math.max(a.min, b.min), max: Math.min(a.max, b.max) };\n}\n\n\n/***/ }),\n\n/***/ \"./src/utilities/types.ts\":\n/*!********************************!*\\\n !*** ./src/utilities/types.ts ***!\n \\********************************/\n/***/ ((__unused_webpack_module, exports) => {\n\n\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.isVec2 = isVec2;\nexports.isVec3 = isVec3;\n/**\n * Check if a value is a vec2\n */\nfunction isVec2(value) {\n return (value &&\n typeof value === 'object' &&\n 'x' in value &&\n typeof value.x === 'number' &&\n 'y' in value &&\n typeof value.y === 'number' &&\n !('z' in value));\n}\n/**\n * Check if a value is a vec3\n */\nfunction isVec3(value) {\n return (value &&\n typeof value === 'object' &&\n 'x' in value &&\n typeof value.x === 'number' &&\n 'y' in value &&\n typeof value.y === 'number' &&\n 'z' in value &&\n typeof value.z === 'number');\n}\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __nested_webpack_require_5880__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tvar cachedModule = __webpack_module_cache__[moduleId];\n/******/ \t\tif (cachedModule !== undefined) {\n/******/ \t\t\treturn cachedModule.exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __nested_webpack_require_5880__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \t// This entry module is referenced by other modules so it can't be inlined\n/******/ \tvar __nested_webpack_exports__ = __nested_webpack_require_5880__(\"./src/utilities/index.ts\");\n/******/ \t\n/******/ \treturn __nested_webpack_exports__;\n/******/ })()\n;\n});\n\n//# sourceURL=webpack://@basementuniverse/particles-2d/./node_modules/@basementuniverse/intersection-helpers/build/utilities/index.js?");
|
|
49
|
+
|
|
50
|
+
/***/ }),
|
|
51
|
+
|
|
52
|
+
/***/ "./node_modules/@basementuniverse/parsecolor/parsecolor.js":
|
|
53
|
+
/*!*****************************************************************!*\
|
|
54
|
+
!*** ./node_modules/@basementuniverse/parsecolor/parsecolor.js ***!
|
|
55
|
+
\*****************************************************************/
|
|
56
|
+
/***/ ((module) => {
|
|
57
|
+
|
|
58
|
+
eval("/*\r\n\r\nParse a valid CSS color string into an object like: { r, g, b, a }\r\nProperties will have the correct interval and precision.\r\nValid input examples:\r\n\r\nred\r\n#f00\r\n#ff0000\r\n#ff0000ff\r\nrgb(255, 0, 0)\r\nrgb(100%, 0%, 0%)\r\nrgba(255, 0, 0, 1)\r\nrgba(100%, 0%, 0%, 1)\r\nhsl(0, 100%, 50%)\r\nhsla(0, 100%, 50%, 1)\r\n\r\n*/\r\nconst parseColor = (function() {\r\n\tconst names = {\r\n\t\t\"aliceblue\": \"f0f8ff\",\r\n\t\t\"antiquewhite\": \"faebd7\",\r\n\t\t\"aqua\": \"0ff\",\r\n\t\t\"aquamarine\": \"7fffd4\",\r\n\t\t\"azure\": \"f0ffff\",\r\n\t\t\"beige\": \"f5f5dc\",\r\n\t\t\"bisque\": \"ffe4c4\",\r\n\t\t\"black\": \"000\",\r\n\t\t\"blanchedalmond\": \"ffebcd\",\r\n\t\t\"blue\": \"00f\",\r\n\t\t\"blueviolet\": \"8a2be2\",\r\n\t\t\"brown\": \"a52a2a\",\r\n\t\t\"burlywood\": \"deb887\",\r\n\t\t\"cadetblue\": \"5f9ea0\",\r\n\t\t\"chartreuse\": \"7fff00\",\r\n\t\t\"chocolate\": \"d2691e\",\r\n\t\t\"coral\": \"ff7f50\",\r\n\t\t\"cornflowerblue\": \"6495ed\",\r\n\t\t\"cornsilk\": \"fff8dc\",\r\n\t\t\"crimson\": \"dc143c\",\r\n\t\t\"cyan\": \"0ff\",\r\n\t\t\"darkblue\": \"00008b\",\r\n\t\t\"darkcyan\": \"008b8b\",\r\n\t\t\"darkgoldenrod\": \"b8860b\",\r\n\t\t\"darkgray\": \"a9a9a9\",\r\n\t\t\"darkgreen\": \"006400\",\r\n\t\t\"darkgrey\": \"a9a9a9\",\r\n\t\t\"darkkhaki\": \"bdb76b\",\r\n\t\t\"darkmagenta\": \"8b008b\",\r\n\t\t\"darkolivegreen\": \"556b2f\",\r\n\t\t\"darkorange\": \"ff8c00\",\r\n\t\t\"darkorchid\": \"9932cc\",\r\n\t\t\"darkred\": \"8b0000\",\r\n\t\t\"darksalmon\": \"e9967a\",\r\n\t\t\"darkseagreen\": \"8fbc8f\",\r\n\t\t\"darkslateblue\": \"483d8b\",\r\n\t\t\"darkslategray\": \"2f4f4f\",\r\n\t\t\"darkslategrey\": \"2f4f4f\",\r\n\t\t\"darkturquoise\": \"00ced1\",\r\n\t\t\"darkviolet\": \"9400d3\",\r\n\t\t\"deeppink\": \"ff1493\",\r\n\t\t\"deepskyblue\": \"00bfff\",\r\n\t\t\"dimgray\": \"696969\",\r\n\t\t\"dimgrey\": \"696969\",\r\n\t\t\"dodgerblue\": \"1e90ff\",\r\n\t\t\"firebrick\": \"b22222\",\r\n\t\t\"floralwhite\": \"fffaf0\",\r\n\t\t\"forestgreen\": \"228b22\",\r\n\t\t\"fuchsia\": \"f0f\",\r\n\t\t\"gainsboro\": \"dcdcdc\",\r\n\t\t\"ghostwhite\": \"f8f8ff\",\r\n\t\t\"gold\": \"ffd700\",\r\n\t\t\"goldenrod\": \"daa520\",\r\n\t\t\"gray\": \"808080\",\r\n\t\t\"green\": \"008000\",\r\n\t\t\"greenyellow\": \"adff2f\",\r\n\t\t\"grey\": \"808080\",\r\n\t\t\"honeydew\": \"f0fff0\",\r\n\t\t\"hotpink\": \"ff69b4\",\r\n\t\t\"indianred\": \"cd5c5c\",\r\n\t\t\"indigo\": \"4b0082\",\r\n\t\t\"ivory\": \"fffff0\",\r\n\t\t\"khaki\": \"f0e68c\",\r\n\t\t\"lavender\": \"e6e6fa\",\r\n\t\t\"lavenderblush\": \"fff0f5\",\r\n\t\t\"lawngreen\": \"7cfc00\",\r\n\t\t\"lemonchiffon\": \"fffacd\",\r\n\t\t\"lightblue\": \"add8e6\",\r\n\t\t\"lightcoral\": \"f08080\",\r\n\t\t\"lightcyan\": \"e0ffff\",\r\n\t\t\"lightgoldenrodyellow\": \"fafad2\",\r\n\t\t\"lightgray\": \"d3d3d3\",\r\n\t\t\"lightgreen\": \"90ee90\",\r\n\t\t\"lightgrey\": \"d3d3d3\",\r\n\t\t\"lightpink\": \"ffb6c1\",\r\n\t\t\"lightsalmon\": \"ffa07a\",\r\n\t\t\"lightseagreen\": \"20b2aa\",\r\n\t\t\"lightskyblue\": \"87cefa\",\r\n\t\t\"lightslategray\": \"789\",\r\n\t\t\"lightslategrey\": \"789\",\r\n\t\t\"lightsteelblue\": \"b0c4de\",\r\n\t\t\"lightyellow\": \"ffffe0\",\r\n\t\t\"lime\": \"0f0\",\r\n\t\t\"limegreen\": \"32cd32\",\r\n\t\t\"linen\": \"faf0e6\",\r\n\t\t\"magenta\": \"f0f\",\r\n\t\t\"maroon\": \"800000\",\r\n\t\t\"mediumaquamarine\": \"66cdaa\",\r\n\t\t\"mediumblue\": \"0000cd\",\r\n\t\t\"mediumorchid\": \"ba55d3\",\r\n\t\t\"mediumpurple\": \"9370db\",\r\n\t\t\"mediumseagreen\": \"3cb371\",\r\n\t\t\"mediumslateblue\": \"7b68ee\",\r\n\t\t\"mediumspringgreen\": \"00fa9a\",\r\n\t\t\"mediumturquoise\": \"48d1cc\",\r\n\t\t\"mediumvioletred\": \"c71585\",\r\n\t\t\"midnightblue\": \"191970\",\r\n\t\t\"mintcream\": \"f5fffa\",\r\n\t\t\"mistyrose\": \"ffe4e1\",\r\n\t\t\"moccasin\": \"ffe4b5\",\r\n\t\t\"navajowhite\": \"ffdead\",\r\n\t\t\"navy\": \"000080\",\r\n\t\t\"oldlace\": \"fdf5e6\",\r\n\t\t\"olive\": \"808000\",\r\n\t\t\"olivedrab\": \"6b8e23\",\r\n\t\t\"orange\": \"ffa500\",\r\n\t\t\"orangered\": \"ff4500\",\r\n\t\t\"orchid\": \"da70d6\",\r\n\t\t\"palegoldenrod\": \"eee8aa\",\r\n\t\t\"palegreen\": \"98fb98\",\r\n\t\t\"paleturquoise\": \"afeeee\",\r\n\t\t\"palevioletred\": \"db7093\",\r\n\t\t\"papayawhip\": \"ffefd5\",\r\n\t\t\"peachpuff\": \"ffdab9\",\r\n\t\t\"peru\": \"cd853f\",\r\n\t\t\"pink\": \"ffc0cb\",\r\n\t\t\"plum\": \"dda0dd\",\r\n\t\t\"powderblue\": \"b0e0e6\",\r\n\t\t\"purple\": \"800080\",\r\n\t\t\"rebeccapurple\": \"639\",\r\n\t\t\"red\": \"f00\",\r\n\t\t\"rosybrown\": \"bc8f8f\",\r\n\t\t\"royalblue\": \"4169e1\",\r\n\t\t\"saddlebrown\": \"8b4513\",\r\n\t\t\"salmon\": \"fa8072\",\r\n\t\t\"sandybrown\": \"f4a460\",\r\n\t\t\"seagreen\": \"2e8b57\",\r\n\t\t\"seashell\": \"fff5ee\",\r\n\t\t\"sienna\": \"a0522d\",\r\n\t\t\"silver\": \"c0c0c0\",\r\n\t\t\"skyblue\": \"87ceeb\",\r\n\t\t\"slateblue\": \"6a5acd\",\r\n\t\t\"slategray\": \"708090\",\r\n\t\t\"slategrey\": \"708090\",\r\n\t\t\"snow\": \"fffafa\",\r\n\t\t\"springgreen\": \"00ff7f\",\r\n\t\t\"steelblue\": \"4682b4\",\r\n\t\t\"tan\": \"d2b48c\",\r\n\t\t\"teal\": \"008080\",\r\n\t\t\"thistle\": \"d8bfd8\",\r\n\t\t\"tomato\": \"ff6347\",\r\n\t\t\"turquoise\": \"40e0d0\",\r\n\t\t\"violet\": \"ee82ee\",\r\n\t\t\"wheat\": \"f5deb3\",\r\n\t\t\"white\": \"fff\",\r\n\t\t\"whitesmoke\": \"f5f5f5\",\r\n\t\t\"yellow\": \"ff0\",\r\n\t\t\"yellowgreen\": \"9acd32\",\r\n\t\t\"transparent\": \"00000000\"\r\n\t};\r\n\t\r\n\tconst clamp = (n, a, b) => n < a ? a : (n > b ? b : n),\t// Clamp n in interval [a, b]\r\n\t\tround = (n, d) => {\t// Round n to nearest integer, or to d decimal places (if d is defined)\r\n\t\t\tvar p = Math.pow(10, d || 0);\r\n\t\t\treturn Math.round(n * p) / p;\r\n\t\t},\r\n\t\thi = n => clamp(parseInt(n, 16), 0, 255),\t// Convert 2-digit hex to int in interval [0, 255]\r\n\t\thf = n => clamp(round(parseInt(n, 16) / 255, 2), 0, 1),\t// Convert 2-digit hex to float with 2 decimal places in interval [0, 1]\r\n\t\tsi = n => clamp(round(parseFloat(n)), 0, 255),\t// Convert string to int in interval [0, 255]\r\n\t\tsf = n => clamp(round(parseFloat(n), 2), 0, 1),\t// Convert string to float with 2 decimal places in interval [0, 1]\r\n\t\tpi = n => clamp(round(parseFloat(n) / 100 * 255), 0, 255),\t// Convert percentage string to int in interval [0, 255]\r\n\t\tuf = n => clamp(parseFloat(n) / 360, 0, 1),\t// Convert hue string to float in interval [0, 1]\r\n\t\tpf = n => clamp(parseFloat(n) / 100, 0, 1);\t// Convert percentage string to float in interval [0, 1]\r\n\t\r\n\t// Convert hsl to rgb, alpha value gets passed straight through\r\n\t// h, s, l values are assumed to be in interval [0, 1]\r\n\t// Returns an object like { r, g, b, a }\r\n\t// http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c\r\n\tfunction hslToRgb(h, s, l, a) {\r\n\t\tvar r, g, b,\r\n\t\t\thue = function(p, q, t) {\r\n\t\t\t\tif (t < 0) { t += 1; }\r\n\t\t\t\tif (t > 1) { t -= 1; }\r\n\t\t\t\tif (t < 1 / 6) { return p + (q - p) * 6 * t };\r\n\t\t\t\tif (t < 1 / 2) { return q; }\r\n\t\t\t\tif (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6 };\r\n\t\t\t\treturn p;\r\n\t\t\t};\r\n\t\tif (s == 0) {\r\n\t\t\tr = g = b = l;\r\n\t\t} else {\r\n\t\t\tvar q = l < 0.5 ? l * (1 + s) : l + s - l * s,\r\n\t\t\t\tp = 2 * l - q;\r\n\t\t\tr = hue(p, q, h + 1 / 3);\r\n\t\t\tg = hue(p, q, h);\r\n\t\t\tb = hue(p, q, h - 1 / 3);\r\n\t\t}\r\n\t\treturn { r: round(r * 255), g: round(g * 255), b: round(b * 255), a: a };\r\n\t}\r\n\treturn function(c) {\r\n\t\tvar o = { r: 0, g: 0, b: 0, a: 0 }, m = null;\r\n\t\tif (typeof c == \"string\") {\r\n\t\t\tif (c in names) { c = \"#\" + names[c]; }\r\n\t\t\tif ((m = c.match(/#([a-f0-9])([a-f0-9])([a-f0-9])$/i)) !== null) {\r\n\t\t\t\to = { r: hi(m[1] + m[1]), g: hi(m[2] + m[2]), b: hi(m[3] + m[3]), a: 1 };\r\n\t\t\t} else if ((m = c.match(/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i)) !== null) {\r\n\t\t\t\to = { r: hi(m[1]), g: hi(m[2]), b: hi(m[3]), a: 1 };\r\n\t\t\t} else if ((m = c.match(/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i)) !== null) {\r\n\t\t\t\to = { r: hi(m[1]), g: hi(m[2]), b: hi(m[3]), a: hf(m[4]) };\r\n\t\t\t} else if ((m = c.match(/rgb\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*\\)/)) !== null) {\r\n\t\t\t\to = { r: si(m[1]), g: si(m[2]), b: si(m[3]), a: 1 };\r\n\t\t\t} else if ((m = c.match(/rgb\\(\\s*(\\d{1,3}\\.?\\d?%)\\s*,\\s*(\\d{1,3}\\.?\\d?%)\\s*,\\s*(\\d{1,3}\\.?\\d?%)\\s*\\)/)) !== null) {\r\n\t\t\t\to = { r: pi(m[1]), g: pi(m[2]), b: pi(m[3]), a: 1 };\r\n\t\t\t} else if ((m = c.match(/rgba\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d?\\.?\\d*?)?\\s*\\)/)) !== null) {\r\n\t\t\t\to = { r: si(m[1]), g: si(m[2]), b: si(m[3]), a: sf(m[4]) };\r\n\t\t\t} else if ((m = c.match(/rgba\\(\\s*(\\d{1,3}\\.?\\d?%)\\s*,\\s*(\\d{1,3}\\.?\\d?%)\\s*,\\s*(\\d{1,3}\\.?\\d?%)\\s*,\\s*(\\d?\\.?\\d*?)?\\s*\\)/)) !== null) {\r\n\t\t\t\to = { r: pi(m[1]), g: pi(m[2]), b: pi(m[3]), a: sf(m[4]) };\r\n\t\t\t} else if ((m = c.match(/hsl\\(\\s*(\\d{1,3}\\.?\\d?)\\s*,\\s*(\\d{1,3}\\.?\\d?%)\\s*,\\s*(\\d{1,3}\\.?\\d?%)\\s*\\)/)) !== null) {\r\n\t\t\t\to = hslToRgb(uf(m[1]), pf(m[2]), pf(m[3]), 1);\r\n\t\t\t} else if ((m = c.match(/hsla\\(\\s*(\\d{1,3}\\.?\\d?)\\s*,\\s*(\\d{1,3}\\.?\\d?%)\\s*,\\s*(\\d{1,3}\\.?\\d?%)\\s*,\\s*(\\d?\\.?\\d*?)?\\s*\\)/)) !== null) {\r\n\t\t\t\to = hslToRgb(uf(m[1]), pf(m[2]), pf(m[3]), sf(m[4]));\r\n\t\t\t}\r\n\t\t} else if (typeof c == \"object\") {\r\n\t\t\tif (c.r !== undefined && c.g != undefined && c.b !== undefined) {\r\n\t\t\t\to = { r: si(c.r), g: si(c.g), b: si(c.b), a: sf(c.a || 1) };\r\n\t\t\t} else if (c.h !== undefined && c.s !== undefined && c.l !== undefined) {\r\n\t\t\t\to = hslToRgb(uf(c.h), pf(c.s), pf(c.l), sf(c.a || 1));\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn o;\r\n\t};\r\n}());\r\n\r\nif (true) {\r\n module.exports = { parseColor };\r\n}\r\n\n\n//# sourceURL=webpack://@basementuniverse/particles-2d/./node_modules/@basementuniverse/parsecolor/parsecolor.js?");
|
|
59
|
+
|
|
60
|
+
/***/ }),
|
|
61
|
+
|
|
62
|
+
/***/ "./node_modules/@basementuniverse/utils/utils.js":
|
|
63
|
+
/*!*******************************************************!*\
|
|
64
|
+
!*** ./node_modules/@basementuniverse/utils/utils.js ***!
|
|
65
|
+
\*******************************************************/
|
|
66
|
+
/***/ ((module) => {
|
|
67
|
+
|
|
68
|
+
eval("/**\n * @overview A library of useful functions\n * @author Gordon Larrigan\n */\n\n/**\n * Memoize a function\n * @param {Function} f The function to memoize\n * @returns {Function} A memoized version of the function\n */\nconst memoize = f => {\n var cache = {};\n return function(...args) {\n return cache[args] ?? (cache[args] = f.apply(this, args));\n };\n};\n\n/**\n * Check if two numbers are approximately equal\n * @param {number} a Number a\n * @param {number} b Number b\n * @param {number} [p=Number.EPSILON] The precision value\n * @return {boolean} True if numbers a and b are approximately equal\n */\nconst floatEquals = (a, b, p = Number.EPSILON) => Math.abs(a - b) < p;\n\n/**\n * Clamp a number between min and max\n * @param {number} a The number to clamp\n * @param {number} [min=0] The minimum value\n * @param {number} [max=1] The maximum value\n * @return {number} A clamped number\n */\nconst clamp = (a, min = 0, max = 1) => a < min ? min : (a > max ? max : a);\n\n/**\n * Get the fractional part of a number\n * @param {number} a The number from which to get the fractional part\n * @return {number} The fractional part of the number\n */\nconst frac = a => a >= 0 ? a - Math.floor(a) : a - Math.ceil(a);\n\n/**\n * Round n to d decimal places\n * @param {number} n The number to round\n * @param {number} [d=0] The number of decimal places to round to\n * @return {number} A rounded number\n */\nconst round = (n, d = 0) => {\n const p = Math.pow(10, d);\n return Math.round(n * p + Number.EPSILON) / p;\n}\n\n/**\n * Do a linear interpolation between a and b\n * @param {number} a The minimum number\n * @param {number} b The maximum number\n * @param {number} i The interpolation value, should be in the interval [0, 1]\n * @return {number} An interpolated value in the interval [a, b]\n */\nconst lerp = (a, b, i) => a + (b - a) * i;\n\n/**\n * Get the position of i between a and b\n * @param {number} a The minimum number\n * @param {number} b The maximum number\n * @param {number} i The interpolated value in the interval [a, b]\n * @return {number} The position of i between a and b\n */\nconst unlerp = (a, b, i) => (i - a) / (b - a);\n\n/**\n * Do a bilinear interpolation\n * @param {number} c00 Top-left value\n * @param {number} c10 Top-right value\n * @param {number} c01 Bottom-left value\n * @param {number} c11 Bottom-right value\n * @param {number} ix Interpolation value along x\n * @param {number} iy Interpolation value along y\n * @return {number} A bilinear interpolated value\n */\nconst blerp = (c00, c10, c01, c11, ix, iy) => lerp(lerp(c00, c10, ix), lerp(c01, c11, ix), iy);\n\n/**\n * Re-map a number i from range a1...a2 to b1...b2\n * @param {number} i The number to re-map\n * @param {number} a1\n * @param {number} a2\n * @param {number} b1\n * @param {number} b2\n * @return {number}\n */\nconst remap = (i, a1, a2, b1, b2) => b1 + (i - a1) * (b2 - b1) / (a2 - a1);\n\n/**\n * Do a smooth interpolation between a and b\n * @param {number} a The minimum number\n * @param {number} b The maximum number\n * @param {number} i The interpolation value\n * @return {number} An interpolated value in the interval [a, b]\n */\nconst smoothstep = (a, b, i) => lerp(a, b, 3 * Math.pow(i, 2) - 2 * Math.pow(i, 3));\n\n/**\n * Get an angle in radians\n * @param {number} degrees The angle in degrees\n * @return {number} The angle in radians\n */\nconst radians = degrees => (Math.PI / 180) * degrees;\n\n/**\n * Get an angle in degrees\n * @param {number} radians The angle in radians\n * @return {number} The angle in degrees\n */\nconst degrees = radians => (180 / Math.PI) * radians;\n\n/**\n * Get a random float in the interval [min, max)\n * @param {number} min Inclusive min\n * @param {number} max Exclusive max\n * @return {number} A random float in the interval [min, max)\n */\nconst randomBetween = (min, max) => Math.random() * (max - min) + min;\n\n/**\n * Get a random integer in the interval [min, max]\n * @param {number} min Inclusive min\n * @param {number} max Inclusive max\n * @return {number} A random integer in the interval [min, max]\n */\nconst randomIntBetween = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;\n\n/**\n * Get a normally-distributed random number\n * @param {number} [mu=0.5] The mean value\n * @param {number} [sigma=0.5] The standard deviation\n * @param {number} [samples=2] The number of samples\n * @return {number} A normally-distributed random number\n */\nconst cltRandom = (mu = 0.5, sigma = 0.5, samples = 2) => {\n let total = 0;\n for (let i = samples; i--;) {\n total += Math.random();\n }\n return mu + (total - samples / 2) / (samples / 2) * sigma;\n};\n\n/**\n * Get a normally-distributed random integer in the interval [min, max]\n * @param {number} min Inclusive min\n * @param {number} max Inclusive max\n * @return {number} A normally-distributed random integer\n */\nconst cltRandomInt = (min, max) => Math.floor(min + cltRandom(0.5, 0.5, 2) * (max + 1 - min));\n\n/**\n * Return a weighted random integer\n * @param {Array<number>} w An array of weights\n * @return {number} An index from w\n */\nconst weightedRandom = w => {\n let total = w.reduce((a, i) => a + i, 0), n = 0;\n const r = Math.random() * total;\n while (total > r) {\n total -= w[n++];\n }\n return n - 1;\n};\n\n/**\n * An interpolation function\n * @callback InterpolationFunction\n * @param {number} a The minimum number\n * @param {number} b The maximum number\n * @param {number} i The interpolation value, should be in the interval [0, 1]\n * @return {number} The interpolated value in the interval [a, b]\n */\n\n/**\n * Return an interpolated value from an array\n * @param {Array<number>} a An array of values interpolate\n * @param {number} i A number in the interval [0, 1]\n * @param {InterpolationFunction} [f=Math.lerp] The interpolation function to use\n * @return {number} An interpolated value in the interval [min(a), max(a)]\n */\nconst lerpArray = (a, i, f = lerp) => {\n const s = i * (a.length - 1);\n const p = clamp(Math.trunc(s), 0, a.length - 1);\n return f(a[p] || 0, a[p + 1] || 0, frac(s));\n};\n\n/**\n * Get the dot product of two vectors\n * @param {Array<number>} a Vector a\n * @param {Array<number>} b Vector b\n * @return {number} a ∙ b\n */\nconst dot = (a, b) => a.reduce((n, v, i) => n + v * b[i], 0);\n\n/**\n * Get the factorial of a number\n * @param {number} a\n * @return {number} a!\n */\nconst factorial = a => {\n let result = 1;\n for (let i = 2; i <= a; i++) {\n result *= i;\n }\n return result;\n};\n\n/**\n * Get the number of permutations of r elements from a set of n elements\n * @param {number} n\n * @param {number} r\n * @return {number} nPr\n */\nconst npr = (n, r) => factorial(n) / factorial(n - r);\n\n/**\n * Get the number of combinations of r elements from a set of n elements\n * @param {number} n\n * @param {number} r\n * @return {number} nCr\n */\nconst ncr = (n, r) => factorial(n) / (factorial(r) * factorial(n - r));\n\n/**\n * Generate all permutations of r elements from an array\n *\n * @example\n * ```js\n * permutations([1, 2, 3], 2);\n * ```\n *\n * Output:\n * ```json\n * [\n * [1, 2],\n * [1, 3],\n * [2, 1],\n * [2, 3],\n * [3, 1],\n * [3, 2]\n * ]\n * ```\n * @param {Array<*>} a\n * @param {number} r The number of elements to choose in each permutation\n * @return {Array<Array<*>>} An array of permutation arrays\n */\nconst permutations = (a, r) => {\n if (r === 1) {\n return a.map(item => [item]);\n }\n\n return a.reduce(\n (acc, item, i) => [\n ...acc,\n ...permutations(a.slice(0, i).concat(a.slice(i + 1)), r - 1).map(c => [item, ...c]),\n ],\n []\n );\n}\n\n/**\n * Generate all combinations of r elements from an array\n *\n * @example\n * ```js\n * combinations([1, 2, 3], 2);\n * ```\n *\n * Output:\n * ```json\n * [\n * [1, 2],\n * [1, 3],\n * [2, 3]\n * ]\n * ```\n * @param {Array<*>} a\n * @param {number} r The number of elements to choose in each combination\n * @return {Array<Array<*>>} An array of combination arrays\n */\nconst combinations = (a, r) => {\n if (r === 1) {\n return a.map(item => [item]);\n }\n\n return a.reduce(\n (acc, item, i) => [\n ...acc,\n ...combinations(a.slice(i + 1), r - 1).map(c => [item, ...c]),\n ],\n []\n );\n};\n\n/**\n * Get a cartesian product of arrays\n *\n * @example\n * ```js\n * cartesian([1, 2, 3], ['a', 'b']);\n * ```\n *\n * Output:\n * ```json\n * [\n * [1, \"a\"],\n * [1, \"b\"],\n * [2, \"a\"],\n * [2, \"b\"],\n * [3, \"a\"],\n * [3, \"b\"]\n * ]\n * ```\n */\nconst cartesian = (...arr) =>\n arr.reduce(\n (a, b) => a.flatMap(c => b.map(d => [...c, d])),\n [[]]\n );\n\n/**\n * A function for generating array values\n * @callback TimesFunction\n * @param {number} i The array index\n * @return {*} The array value\n */\n\n/**\n * Return a new array with length n by calling function f(i) on each element\n * @param {TimesFunction} f\n * @param {number} n The size of the array\n * @return {Array<*>}\n */\nconst times = (f, n) => Array(n).fill(0).map((_, i) => f(i));\n\n/**\n * Return an array containing numbers 0->(n - 1)\n * @param {number} n The size of the array\n * @return {Array<number>} An array of integers 0->(n - 1)\n */\nconst range = n => times(i => i, n);\n\n/**\n * Zip multiple arrays together, i.e. ([1, 2, 3], [a, b, c]) => [[1, a], [2, b], [3, c]]\n * @param {...Array<*>} a The arrays to zip\n * @return {Array<Array<*>>}\n */\nconst zip = (...a) => times(i => a.map(a => a[i]), Math.max(...a.map(a => a.length)));\n\n/**\n * Return array[i] with positive and negative wrapping\n * @param {Array<*>} a The array to access\n * @param {number} i The positively/negatively wrapped array index\n * @return {*} An element from the array\n */\nconst at = (a, i) => a[i < 0 ? a.length - (Math.abs(i + 1) % a.length) - 1 : i % a.length];\n\n/**\n * Return the last element of an array without removing it\n * @param {Array<*>} a\n * @return {*} The last element from the array\n */\nconst peek = (a) => {\n if (!a.length) {\n return undefined;\n }\n\n return a[a.length - 1];\n};\n\n/**\n * Return the index for a given position in an unrolled 2d array\n * @param {number} x The x position\n * @param {number} y The y position\n * @param {number} w The width of the 2d array\n * @returns {number} The index in the unrolled array\n */\nconst ind = (x, y, w) => x + y * w;\n\n/**\n * Return the position for a given index in an unrolled 2d array\n * @param {number} i The index\n * @param {number} w The width of the 2d array\n * @returns {Array<number>} The position as a 2-tuple\n */\nconst pos = (i, w) => [i % w, Math.floor(i / w)];\n\n/**\n * Chop an array into chunks of size n\n * @param {Array<*>} a\n * @param {number} n The chunk size\n * @return {Array<Array<*>>} An array of array chunks\n */\nconst chunk = (a, n) => times(i => a.slice(i * n, i * n + n), Math.ceil(a.length / n));\n\n/**\n * Randomly shuffle a shallow copy of an array\n * @param {Array<*>} a\n * @return {Array<*>} The shuffled array\n */\nconst shuffle = a => a.slice().sort(() => Math.random() - 0.5);\n\n/**\n * Flatten an object\n * @param {object} o\n * @param {string} concatenator The string to use for concatenating keys\n * @return {object} A flattened object\n */\nconst flat = (o, concatenator = '.') => {\n return Object.keys(o).reduce((acc, key) => {\n if (o[key] instanceof Date) {\n return {\n ...acc,\n [key]: o[key].toISOString(),\n };\n }\n\n if (typeof o[key] !== 'object' || !o[key]) {\n return {\n ...acc,\n [key]: o[key],\n };\n }\n const flattened = flat(o[key], concatenator);\n\n return {\n ...acc,\n ...Object.keys(flattened).reduce(\n (childAcc, childKey) => ({\n ...childAcc,\n [`${key}${concatenator}${childKey}`]: flattened[childKey],\n }),\n {}\n ),\n };\n }, {});\n};\n\n/**\n * Unflatten an object\n * @param {object} o\n * @param {string} concatenator The string to check for in concatenated keys\n * @return {object} An un-flattened object\n */\nconst unflat = (o, concatenator = '.') => {\n let result = {}, temp, substrings, property, i;\n\n for (property in o) {\n substrings = property.split(concatenator);\n temp = result;\n for (i = 0; i < substrings.length - 1; i++) {\n if (!(substrings[i] in temp)) {\n if (isFinite(substrings[i + 1])) {\n temp[substrings[i]] = [];\n } else {\n temp[substrings[i]] = {};\n }\n }\n temp = temp[substrings[i]];\n }\n temp[substrings[substrings.length - 1]] = o[property];\n }\n\n return result;\n};\n\n/**\n * A split predicate\n * @callback SplitPredicate\n * @param {any} value The current value\n * @return {boolean} True if the array should split at this index\n */\n\n/**\n * Split an array into sub-arrays based on a predicate\n * @param {Array<*>} array\n * @param {SplitPredicate} predicate\n * @return {Array<Array<*>>} An array of arrays\n */\nconst split = (array, predicate) => {\n const result = [];\n let current = [];\n for (const value of array) {\n if (predicate(value)) {\n if (current.length) {\n result.push(current);\n }\n current = [value];\n } else {\n current.push(value);\n }\n }\n result.push(current);\n\n return result;\n};\n\n/**\n * Pluck keys from an object\n * @param {object} o\n * @param {...string} keys The keys to pluck from the object\n * @return {object} An object containing the plucked keys\n */\nconst pluck = (o, ...keys) => {\n return keys.reduce(\n (result, key) => Object.assign(result, { [key]: o[key] }),\n {}\n );\n};\n\n/**\n * Exclude keys from an object\n * @param {object} o\n * @param {...string} keys The keys to exclude from the object\n * @return {object} An object containing all keys except excluded keys\n */\nconst exclude = (o, ...keys) => {\n return Object.fromEntries(\n Object.entries(o).filter(([key]) => !keys.includes(key))\n );\n};\n\nif (true) {\n module.exports = {\n memoize,\n floatEquals,\n clamp,\n frac,\n round,\n lerp,\n unlerp,\n blerp,\n remap,\n smoothstep,\n radians,\n degrees,\n randomBetween,\n randomIntBetween,\n cltRandom,\n cltRandomInt,\n weightedRandom,\n lerpArray,\n dot,\n factorial,\n npr,\n ncr,\n permutations,\n combinations,\n cartesian,\n times,\n range,\n zip,\n at,\n peek,\n ind,\n pos,\n chunk,\n shuffle,\n flat,\n unflat,\n split,\n pluck,\n exclude,\n };\n}\n\n\n//# sourceURL=webpack://@basementuniverse/particles-2d/./node_modules/@basementuniverse/utils/utils.js?");
|
|
69
|
+
|
|
70
|
+
/***/ }),
|
|
71
|
+
|
|
72
|
+
/***/ "./node_modules/@basementuniverse/vec/vec.js":
|
|
73
|
+
/*!***************************************************!*\
|
|
74
|
+
!*** ./node_modules/@basementuniverse/vec/vec.js ***!
|
|
75
|
+
\***************************************************/
|
|
76
|
+
/***/ ((module) => {
|
|
77
|
+
|
|
78
|
+
eval("/**\n * @overview A small vector and matrix library\n * @author Gordon Larrigan\n */\n\nconst _vec_times = (f, n) => Array(n).fill(0).map((_, i) => f(i));\nconst _vec_chunk = (a, n) => _vec_times(i => a.slice(i * n, i * n + n), Math.ceil(a.length / n));\nconst _vec_dot = (a, b) => a.reduce((n, v, i) => n + v * b[i], 0);\nconst _vec_is_vec2 = a => typeof a === 'object' && 'x' in a && 'y' in a;\nconst _vec_is_vec3 = a => typeof a === 'object' && 'x' in a && 'y' in a && 'z' in a;\n\n/**\n * A 2d vector\n * @typedef {Object} vec2\n * @property {number} x The x component of the vector\n * @property {number} y The y component of the vector\n */\n\n/**\n * Create a new 2d vector\n * @param {number|vec2} [x] The x component of the vector, or a vector to copy\n * @param {number} [y] The y component of the vector\n * @return {vec2} A new 2d vector\n * @example <caption>various ways to initialise a vector</caption>\n * let a = vec2(3, 2); // (3, 2)\n * let b = vec2(4); // (4, 4)\n * let c = vec2(a); // (3, 2)\n * let d = vec2(); // (0, 0)\n */\nconst vec2 = (x, y) => {\n if (!x && !y) {\n return { x: 0, y: 0 };\n }\n if (_vec_is_vec2(x)) {\n return { x: x.x || 0, y: x.y || 0 };\n }\n return { x: x, y: y ?? x };\n};\n\n/**\n * Get the components of a vector as an array\n * @param {vec2} a The vector to get components from\n * @return {Array<number>} The vector components as an array\n */\nvec2.components = a => [a.x, a.y];\n\n/**\n * Create a vector from an array of components\n * @param {Array<number>} components The components of the vector\n * @return {vec2} A new vector\n */\nvec2.fromComponents = components => vec2(...components.slice(0, 2));\n\n/**\n * Return a unit vector (1, 0)\n * @return {vec2} A unit vector (1, 0)\n */\nvec2.ux = () => vec2(1, 0);\n\n/**\n * Return a unit vector (0, 1)\n * @return {vec2} A unit vector (0, 1)\n */\nvec2.uy = () => vec2(0, 1);\n\n/**\n * Add vectors\n * @param {vec2} a Vector a\n * @param {vec2|number} b Vector or scalar b\n * @return {vec2} a + b\n */\nvec2.add = (a, b) => ({ x: a.x + (b.x ?? b), y: a.y + (b.y ?? b) });\n\n/**\n * Subtract vectors\n * @param {vec2} a Vector a\n * @param {vec2|number} b Vector or scalar b\n * @return {vec2} a - b\n */\nvec2.sub = (a, b) => ({ x: a.x - (b.x ?? b), y: a.y - (b.y ?? b) });\n\n/**\n * Scale a vector\n * @param {vec2} a Vector a\n * @param {vec2|number} b Vector or scalar b\n * @return {vec2} a * b\n */\nvec2.mul = (a, b) => ({ x: a.x * (b.x ?? b), y: a.y * (b.y ?? b) });\n\n/**\n * Scale a vector by a scalar, alias for vec2.mul\n * @param {vec2} a Vector a\n * @param {number} b Scalar b\n * @return {vec2} a * b\n */\nvec2.scale = (a, b) => vec2.mul(a, b);\n\n/**\n * Divide a vector\n * @param {vec2} a Vector a\n * @param {vec2|number} b Vector or scalar b\n * @return {vec2} a / b\n */\nvec2.div = (a, b) => ({ x: a.x / (b.x ?? b), y: a.y / (b.y ?? b) });\n\n/**\n * Get the length of a vector\n * @param {vec2} a Vector a\n * @return {number} |a|\n */\nvec2.len = a => Math.sqrt(a.x * a.x + a.y * a.y);\n\n/**\n * Get the length of a vector using taxicab geometry\n * @param {vec2} a Vector a\n * @return {number} |a|\n */\nvec2.manhattan = a => Math.abs(a.x) + Math.abs(a.y);\n\n/**\n * Normalise a vector\n * @param {vec2} a The vector to normalise\n * @return {vec2} ^a\n */\nvec2.nor = a => {\n let len = vec2.len(a);\n return len ? { x: a.x / len, y: a.y / len } : vec2();\n};\n\n/**\n * Get a dot product of vectors\n * @param {vec2} a Vector a\n * @param {vec2} b Vector b\n * @return {number} a ∙ b\n */\nvec2.dot = (a, b) => a.x * b.x + a.y * b.y;\n\n/**\n * Rotate a vector by r radians\n * @param {vec2} a The vector to rotate\n * @param {number} r The angle to rotate by, measured in radians\n * @return {vec2} A rotated vector\n */\nvec2.rot = (a, r) => {\n let s = Math.sin(r),\n c = Math.cos(r);\n return { x: c * a.x - s * a.y, y: s * a.x + c * a.y };\n};\n\n/**\n * Fast method to rotate a vector by -90, 90 or 180 degrees\n * @param {vec2} a The vector to rotate\n * @param {number} r 1 for 90 degrees (cw), -1 for -90 degrees (ccw), 2 or -2 for 180 degrees\n * @return {vec2} A rotated vector\n */\nvec2.rotf = (a, r) => {\n switch (r) {\n case 1: return vec2(a.y, -a.x);\n case -1: return vec2(-a.y, a.x);\n case 2: case -2: return vec2(-a.x, -a.y);\n default: return a;\n }\n};\n\n/**\n * Scalar cross product of two vectors\n * @param {vec2} a Vector a\n * @param {vec2} b Vector b\n * @return {number} a × b\n */\nvec2.cross = (a, b) => {\n return a.x * b.y - a.y * b.x;\n};\n\n/**\n * Check if two vectors are equal\n * @param {vec2} a Vector a\n * @param {vec2} b Vector b\n * @return {boolean} True if vectors a and b are equal, false otherwise\n */\nvec2.eq = (a, b) => a.x === b.x && a.y === b.y;\n\n/**\n * Get the angle of a vector\n * @param {vec2} a Vector a\n * @return {number} The angle of vector a in radians\n */\nvec2.rad = a => Math.atan2(a.y, a.x);\n\n/**\n * Copy a vector\n * @param {vec2} a The vector to copy\n * @return {vec2} A copy of vector a\n */\nvec2.cpy = a => vec2(a);\n\n/**\n * A function to call on each component of a 2d vector\n * @callback vec2MapCallback\n * @param {number} value The component value\n * @param {'x' | 'y'} label The component label (x or y)\n * @return {number} The mapped component\n */\n\n/**\n * Call a function on each component of a vector and build a new vector from the results\n * @param {vec2} a Vector a\n * @param {vec2MapCallback} f The function to call on each component of the vector\n * @return {vec2} Vector a mapped through f\n */\nvec2.map = (a, f) => ({ x: f(a.x, 'x'), y: f(a.y, 'y') });\n\n/**\n * Convert a vector into a string\n * @param {vec2} a The vector to convert\n * @param {string} [s=', '] The separator string\n * @return {string} A string representation of the vector\n */\nvec2.str = (a, s = ', ') => `${a.x}${s}${a.y}`;\n\n/**\n * Swizzle a vector with a string of component labels\n *\n * The string can contain:\n * - `x` or `y`\n * - `u` or `v` (aliases for `x` and `y`, respectively)\n * - `X`, `Y`, `U`, `V` (negated versions of the above)\n * - `0` or `1` (these will be passed through unchanged)\n * - `.` to return the component that would normally be at this position (or 0)\n *\n * Any other characters will default to 0\n * @param {vec2} a The vector to swizzle\n * @param {string} [s='..'] The swizzle string\n * @return {Array<number>} The swizzled components\n * @example <caption>swizzling a vector</caption>\n * let a = vec2(3, -2);\n * vec2.swiz(a, 'x'); // [3]\n * vec2.swiz(a, 'yx'); // [-2, 3]\n * vec2.swiz(a, 'xY'); // [3, 2]\n * vec2.swiz(a, 'Yy'); // [2, -2]\n * vec2.swiz(a, 'x.x'); // [3, -2, 3]\n * vec2.swiz(a, 'y01x'); // [-2, 0, 1, 3]\n */\nvec2.swiz = (a, s = '..') => {\n const result = [];\n s.split('').forEach((c, i) => {\n switch (c) {\n case 'x': case 'u': result.push(a.x); break;\n case 'y': case 'v': result.push(a.y); break;\n case 'X': case 'U': result.push(-a.x); break;\n case 'Y': case 'V': result.push(-a.y); break;\n case '0': result.push(0); break;\n case '1': result.push(1); break;\n case '.': result.push([a.x, a.y][i] ?? 0); break;\n default: result.push(0);\n }\n });\n return result;\n};\n\n/**\n * Polar coordinates for a 2d vector\n * @typedef {Object} polarCoordinates2d\n * @property {number} r The magnitude (radius) of the vector\n * @property {number} theta The angle of the vector\n */\n\n/**\n * Convert a vector into polar coordinates\n * @param {vec2} a The vector to convert\n * @return {polarCoordinates2d} The magnitude and angle of the vector\n */\nvec2.polar = a => ({ r: vec2.len(a), theta: Math.atan2(a.y, a.x) });\n\n/**\n * Convert polar coordinates into a vector\n * @param {number} r The magnitude (radius) of the vector\n * @param {number} theta The angle of the vector\n * @return {vec2} A vector with the given angle and magnitude\n */\nvec2.fromPolar = (r, theta) => vec2(r * Math.cos(theta), r * Math.sin(theta));\n\n/**\n * A 3d vector\n * @typedef {Object} vec3\n * @property {number} x The x component of the vector\n * @property {number} y The y component of the vector\n * @property {number} z The z component of the vector\n */\n\n/**\n * Create a new 3d vector\n * @param {number|vec3|vec2} [x] The x component of the vector, or a vector to copy\n * @param {number} [y] The y component of the vector, or the z component if x is a vec2\n * @param {number} [z] The z component of the vector\n * @return {vec3} A new 3d vector\n * @example <caption>various ways to initialise a vector</caption>\n * let a = vec3(3, 2, 1); // (3, 2, 1)\n * let b = vec3(4, 5); // (4, 5, 0)\n * let c = vec3(6); // (6, 6, 6)\n * let d = vec3(a); // (3, 2, 1)\n * let e = vec3(); // (0, 0, 0)\n * let f = vec3(vec2(1, 2), 3); // (1, 2, 3)\n * let g = vec3(vec2(4, 5)); // (4, 5, 0)\n */\nconst vec3 = (x, y, z) => {\n if (!x && !y && !z) {\n return { x: 0, y: 0, z: 0 };\n }\n if (_vec_is_vec3(x)) {\n return { x: x.x || 0, y: x.y || 0, z: x.z || 0 };\n }\n if (_vec_is_vec2(x)) {\n return { x: x.x || 0, y: x.y || 0, z: y || 0 };\n }\n return { x: x, y: y ?? x, z: z ?? x };\n};\n\n/**\n * Get the components of a vector as an array\n * @param {vec3} a The vector to get components from\n * @return {Array<number>} The vector components as an array\n */\nvec3.components = a => [a.x, a.y, a.z];\n\n/**\n * Create a vector from an array of components\n * @param {Array<number>} components The components of the vector\n * @return {vec3} A new vector\n */\nvec3.fromComponents = components => vec3(...components.slice(0, 3));\n\n/**\n * Return a unit vector (1, 0, 0)\n * @return {vec3} A unit vector (1, 0, 0)\n */\nvec3.ux = () => vec3(1, 0, 0);\n\n/**\n * Return a unit vector (0, 1, 0)\n * @return {vec3} A unit vector (0, 1, 0)\n */\nvec3.uy = () => vec3(0, 1, 0);\n\n/**\n * Return a unit vector (0, 0, 1)\n * @return {vec3} A unit vector (0, 0, 1)\n */\nvec3.uz = () => vec3(0, 0, 1);\n\n/**\n * Add vectors\n * @param {vec3} a Vector a\n * @param {vec3|number} b Vector or scalar b\n * @return {vec3} a + b\n */\nvec3.add = (a, b) => ({ x: a.x + (b.x ?? b), y: a.y + (b.y ?? b), z: a.z + (b.z ?? b) });\n\n/**\n * Subtract vectors\n * @param {vec3} a Vector a\n * @param {vec3|number} b Vector or scalar b\n * @return {vec3} a - b\n */\nvec3.sub = (a, b) => ({ x: a.x - (b.x ?? b), y: a.y - (b.y ?? b), z: a.z - (b.z ?? b) });\n\n/**\n * Scale a vector\n * @param {vec3} a Vector a\n * @param {vec3|number} b Vector or scalar b\n * @return {vec3} a * b\n */\nvec3.mul = (a, b) => ({ x: a.x * (b.x ?? b), y: a.y * (b.y ?? b), z: a.z * (b.z ?? b) });\n\n/**\n * Scale a vector by a scalar, alias for vec3.mul\n * @param {vec3} a Vector a\n * @param {number} b Scalar b\n * @return {vec3} a * b\n */\nvec3.scale = (a, b) => vec3.mul(a, b);\n\n/**\n * Divide a vector\n * @param {vec3} a Vector a\n * @param {vec3|number} b Vector or scalar b\n * @return {vec3} a / b\n */\nvec3.div = (a, b) => ({ x: a.x / (b.x ?? b), y: a.y / (b.y ?? b), z: a.z / (b.z ?? b) });\n\n/**\n * Get the length of a vector\n * @param {vec3} a Vector a\n * @return {number} |a|\n */\nvec3.len = a => Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z);\n\n/**\n * Get the length of a vector using taxicab geometry\n * @param {vec3} a Vector a\n * @return {number} |a|\n */\nvec3.manhattan = a => Math.abs(a.x) + Math.abs(a.y) + Math.abs(a.z);\n\n/**\n * Normalise a vector\n * @param {vec3} a The vector to normalise\n * @return {vec3} ^a\n */\nvec3.nor = a => {\n let len = vec3.len(a);\n return len ? { x: a.x / len, y: a.y / len, z: a.z / len } : vec3();\n};\n\n/**\n * Get a dot product of vectors\n * @param {vec3} a Vector a\n * @param {vec3} b Vector b\n * @return {number} a ∙ b\n */\nvec3.dot = (a, b) => a.x * b.x + a.y * b.y + a.z * b.z;\n\n/**\n * Rotate a vector using a rotation matrix\n * @param {vec3} a The vector to rotate\n * @param {mat} m The rotation matrix\n * @return {vec3} A rotated vector\n */\nvec3.rot = (a, m) => vec3(\n vec3.dot(vec3.fromComponents(mat.row(m, 1)), a),\n vec3.dot(vec3.fromComponents(mat.row(m, 2)), a),\n vec3.dot(vec3.fromComponents(mat.row(m, 3)), a)\n);\n\n/**\n * Rotate a vector by r radians around the x axis\n * @param {vec3} a The vector to rotate\n * @param {number} r The angle to rotate by, measured in radians\n * @return {vec3} A rotated vector\n */\nvec3.rotx = (a, r) => vec3(\n a.x,\n a.y * Math.cos(r) - a.z * Math.sin(r),\n a.y * Math.sin(r) + a.z * Math.cos(r)\n);\n\n/**\n * Rotate a vector by r radians around the y axis\n * @param {vec3} a The vector to rotate\n * @param {number} r The angle to rotate by, measured in radians\n * @return {vec3} A rotated vector\n */\nvec3.roty = (a, r) => vec3(\n a.x * Math.cos(r) + a.z * Math.sin(r),\n a.y,\n -a.x * Math.sin(r) + a.z * Math.cos(r)\n);\n\n/**\n * Rotate a vector by r radians around the z axis\n * @param {vec3} a The vector to rotate\n * @param {number} r The angle to rotate by, measured in radians\n * @return {vec3} A rotated vector\n */\nvec3.rotz = (a, r) => vec3(\n a.x * Math.cos(r) - a.y * Math.sin(r),\n a.x * Math.sin(r) + a.y * Math.cos(r),\n a.z\n);\n\n/**\n * Rotate a vector using a quaternion\n * @param {vec3} a The vector to rotate\n * @param {Array<number>} q The quaternion to rotate by\n * @return {vec3} A rotated vector\n */\nvec3.rotq = (v, q) => {\n if (q.length !== 4) {\n return vec3();\n }\n\n const d = Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);\n if (d === 0) {\n return vec3();\n }\n\n const uq = [q[0] / d, q[1] / d, q[2] / d, q[3] / d];\n const u = vec3(...uq.slice(0, 3));\n const s = uq[3];\n return vec3.add(\n vec3.add(\n vec3.mul(u, 2 * vec3.dot(u, v)),\n vec3.mul(v, s * s - vec3.dot(u, u))\n ),\n vec3.mul(vec3.cross(u, v), 2 * s)\n );\n};\n\n/**\n * Rotate a vector using Euler angles\n * @param {vec3} a The vector to rotate\n * @param {vec3} e The Euler angles to rotate by\n * @return {vec3} A rotated vector\n */\nvec3.rota = (a, e) => vec3.rotz(vec3.roty(vec3.rotx(a, e.x), e.y), e.z);\n\n/**\n * Get the cross product of vectors\n * @param {vec3} a Vector a\n * @param {vec3} b Vector b\n * @return {vec3} a × b\n */\nvec3.cross = (a, b) => vec3(\n a.y * b.z - a.z * b.y,\n a.z * b.x - a.x * b.z,\n a.x * b.y - a.y * b.x\n);\n\n/**\n * Check if two vectors are equal\n * @param {vec3} a Vector a\n * @param {vec3} b Vector b\n * @return {boolean} True if vectors a and b are equal, false otherwise\n */\nvec3.eq = (a, b) => a.x === b.x && a.y === b.y && a.z === b.z;\n\n/**\n * Get the angle of a vector from the x axis\n * @param {vec3} a Vector a\n * @return {number} The angle of vector a in radians\n */\nvec3.radx = a => Math.atan2(a.z, a.y);\n\n/**\n * Get the angle of a vector from the y axis\n * @param {vec3} a Vector a\n * @return {number} The angle of vector a in radians\n */\nvec3.rady = a => Math.atan2(a.x, a.y);\n\n/**\n * Get the angle of a vector from the z axis\n * @param {vec3} a Vector a\n * @return {number} The angle of vector a in radians\n */\nvec3.radz = a => Math.atan2(a.y, a.z);\n\n/**\n * Copy a vector\n * @param {vec3} a The vector to copy\n * @return {vec3} A copy of vector a\n */\nvec3.cpy = a => vec3(a);\n\n/**\n * A function to call on each component of a 3d vector\n * @callback vec3MapCallback\n * @param {number} value The component value\n * @param {'x' | 'y' | 'z'} label The component label (x, y or z)\n * @return {number} The mapped component\n */\n\n/**\n * Call a function on each component of a vector and build a new vector from the results\n * @param {vec3} a Vector a\n * @param {vec3MapCallback} f The function to call on each component of the vector\n * @return {vec3} Vector a mapped through f\n */\nvec3.map = (a, f) => ({ x: f(a.x, 'x'), y: f(a.y, 'y'), z: f(a.z, 'z') });\n\n/**\n * Convert a vector into a string\n * @param {vec3} a The vector to convert\n * @param {string} [s=', '] The separator string\n * @return {string} A string representation of the vector\n */\nvec3.str = (a, s = ', ') => `${a.x}${s}${a.y}${s}${a.z}`;\n\n/**\n * Swizzle a vector with a string of component labels\n *\n * The string can contain:\n * - `x`, `y` or `z`\n * - `u`, `v` or `w` (aliases for `x`, `y` and `z`, respectively)\n * - `r`, `g` or `b` (aliases for `x`, `y` and `z`, respectively)\n * - `X`, `Y`, `Z`, `U`, `V`, `W`, `R`, `G`, `B` (negated versions of the above)\n * - `0` or `1` (these will be passed through unchanged)\n * - `.` to return the component that would normally be at this position (or 0)\n *\n * Any other characters will default to 0\n * @param {vec3} a The vector to swizzle\n * @param {string} [s='...'] The swizzle string\n * @return {Array<number>} The swizzled components\n * @example <caption>swizzling a vector</caption>\n * let a = vec3(3, -2, 1);\n * vec3.swiz(a, 'x'); // [3]\n * vec3.swiz(a, 'zyx'); // [1, -2, 3]\n * vec3.swiz(a, 'xYZ'); // [3, 2, -1]\n * vec3.swiz(a, 'Zzx'); // [-1, 1, 3]\n * vec3.swiz(a, 'x.x'); // [3, -2, 3]\n * vec3.swiz(a, 'y01zx'); // [-2, 0, 1, 1, 3]\n */\nvec3.swiz = (a, s = '...') => {\n const result = [];\n s.split('').forEach((c, i) => {\n switch (c) {\n case 'x': case 'u': case 'r': result.push(a.x); break;\n case 'y': case 'v': case 'g': result.push(a.y); break;\n case 'z': case 'w': case 'b': result.push(a.z); break;\n case 'X': case 'U': case 'R': result.push(-a.x); break;\n case 'Y': case 'V': case 'G': result.push(-a.y); break;\n case 'Z': case 'W': case 'B': result.push(-a.z); break;\n case '0': result.push(0); break;\n case '1': result.push(1); break;\n case '.': result.push([a.x, a.y, a.z][i] ?? 0); break;\n default: result.push(0);\n }\n });\n return result;\n};\n\n/**\n * Polar coordinates for a 3d vector\n * @typedef {Object} polarCoordinates3d\n * @property {number} r The magnitude (radius) of the vector\n * @property {number} theta The tilt angle of the vector\n * @property {number} phi The pan angle of the vector\n */\n\n/**\n * Convert a vector into polar coordinates\n * @param {vec3} a The vector to convert\n * @return {polarCoordinates3d} The magnitude, tilt and pan of the vector\n */\nvec3.polar = a => {\n let r = vec3.len(a),\n theta = Math.acos(a.y / r),\n phi = Math.atan2(a.z, a.x);\n return { r, theta, phi };\n};\n\n/**\n * Convert polar coordinates into a vector\n * @param {number} r The magnitude (radius) of the vector\n * @param {number} theta The tilt of the vector\n * @param {number} phi The pan of the vector\n * @return {vec3} A vector with the given angle and magnitude\n */\nvec3.fromPolar = (r, theta, phi) => {\n const sinTheta = Math.sin(theta);\n return vec3(\n r * sinTheta * Math.cos(phi),\n r * Math.cos(theta),\n r * sinTheta * Math.sin(phi)\n );\n};\n\n/**\n * A matrix\n * @typedef {Object} mat\n * @property {number} m The number of rows in the matrix\n * @property {number} n The number of columns in the matrix\n * @property {Array<number>} entries The matrix values\n */\n\n/**\n * Create a new matrix\n * @param {number} [m=4] The number of rows\n * @param {number} [n=4] The number of columns\n * @param {Array<number>} [entries=[]] Matrix values in reading order\n * @return {mat} A new matrix\n */\nconst mat = (m = 4, n = 4, entries = []) => ({\n m, n,\n entries: entries.concat(Array(m * n).fill(0)).slice(0, m * n)\n});\n\n/**\n * Get an identity matrix of size n\n * @param {number} n The size of the matrix\n * @return {mat} An identity matrix\n */\nmat.identity = n => mat(n, n, Array(n * n).fill(0).map((v, i) => +(Math.floor(i / n) === i % n)));\n\n/**\n * Get an entry from a matrix\n * @param {mat} a Matrix a\n * @param {number} i The row offset\n * @param {number} j The column offset\n * @return {number} The value at position (i, j) in matrix a\n */\nmat.get = (a, i, j) => a.entries[(j - 1) + (i - 1) * a.n];\n\n/**\n * Set an entry of a matrix\n * @param {mat} a Matrix a\n * @param {number} i The row offset\n * @param {number} j The column offset\n * @param {number} v The value to set in matrix a\n */\nmat.set = (a, i, j, v) => { a.entries[(j - 1) + (i - 1) * a.n] = v; };\n\n/**\n * Get a row from a matrix as an array\n * @param {mat} a Matrix a\n * @param {number} m The row offset\n * @return {Array<number>} Row m from matrix a\n */\nmat.row = (a, m) => {\n const s = (m - 1) * a.n;\n return a.entries.slice(s, s + a.n);\n};\n\n/**\n * Get a column from a matrix as an array\n * @param {mat} a Matrix a\n * @param {number} n The column offset\n * @return {Array<number>} Column n from matrix a\n */\nmat.col = (a, n) => _vec_times(i => mat.get(a, (i + 1), n), a.m);\n\n/**\n * Add matrices\n * @param {mat} a Matrix a\n * @param {mat} b Matrix b\n * @return {mat} a + b\n */\nmat.add = (a, b) => a.m === b.m && a.n === b.n && mat.map(a, (v, i) => v + b.entries[i]);\n\n/**\n * Subtract matrices\n * @param {mat} a Matrix a\n * @param {mat} b Matrix b\n * @return {mat} a - b\n */\nmat.sub = (a, b) => a.m === b.m && a.n === b.n && mat.map(a, (v, i) => v - b.entries[i]);\n\n/**\n * Multiply matrices\n * @param {mat} a Matrix a\n * @param {mat} b Matrix b\n * @return {mat|false} ab or false if the matrices cannot be multiplied\n */\nmat.mul = (a, b) => {\n if (a.n !== b.m) { return false; }\n const result = mat(a.m, b.n);\n for (let i = 1; i <= a.m; i++) {\n for (let j = 1; j <= b.n; j++) {\n mat.set(result, i, j, _vec_dot(mat.row(a, i), mat.col(b, j)));\n }\n }\n return result;\n};\n\n/**\n * Multiply a matrix by a vector\n * @param {mat} a Matrix a\n * @param {vec2|vec3|number[]} b Vector b\n * @return {vec2|vec3|number[]|false} ab or false if the matrix and vector cannot be multiplied\n */\nmat.mulv = (a, b) => {\n let n, bb, rt;\n if (_vec_is_vec3(b)) {\n bb = vec3.components(b);\n n = 3;\n rt = vec3.fromComponents;\n } else if (_vec_is_vec2(b)) {\n bb = vec2.components(b);\n n = 2;\n rt = vec2.fromComponents;\n } else {\n bb = b;\n n = b.length ?? 0;\n rt = v => v;\n }\n if (a.n !== n) { return false; }\n const result = [];\n for (let i = 1; i <= a.m; i++) {\n result.push(_vec_dot(mat.row(a, i), bb));\n }\n return rt(result);\n}\n\n/**\n * Scale a matrix\n * @param {mat} a Matrix a\n * @param {number} b Scalar b\n * @return {mat} a * b\n */\nmat.scale = (a, b) => mat.map(a, v => v * b);\n\n/**\n * Transpose a matrix\n * @param {mat} a The matrix to transpose\n * @return {mat} A transposed matrix\n */\nmat.trans = a => mat(a.n, a.m, _vec_times(i => mat.col(a, (i + 1)), a.n).flat());\n\n/**\n * Get the minor of a matrix\n * @param {mat} a Matrix a\n * @param {number} i The row offset\n * @param {number} j The column offset\n * @return {mat|false} The (i, j) minor of matrix a or false if the matrix is not square\n */\nmat.minor = (a, i, j) => {\n if (a.m !== a.n) { return false; }\n const entries = [];\n for (let ii = 1; ii <= a.m; ii++) {\n if (ii === i) { continue; }\n for (let jj = 1; jj <= a.n; jj++) {\n if (jj === j) { continue; }\n entries.push(mat.get(a, ii, jj));\n }\n }\n return mat(a.m - 1, a.n - 1, entries);\n};\n\n/**\n * Get the determinant of a matrix\n * @param {mat} a Matrix a\n * @return {number|false} |a| or false if the matrix is not square\n */\nmat.det = a => {\n if (a.m !== a.n) { return false; }\n if (a.m === 1) {\n return a.entries[0];\n }\n if (a.m === 2) {\n return a.entries[0] * a.entries[3] - a.entries[1] * a.entries[2];\n }\n let total = 0, sign = 1;\n for (let j = 1; j <= a.n; j++) {\n total += sign * a.entries[j - 1] * mat.det(mat.minor(a, 1, j));\n sign *= -1;\n }\n return total;\n};\n\n/**\n * Normalise a matrix\n * @param {mat} a The matrix to normalise\n * @return {mat|false} ^a or false if the matrix is not square\n */\nmat.nor = a => {\n if (a.m !== a.n) { return false; }\n const d = mat.det(a);\n return mat.map(a, i => i * d);\n};\n\n/**\n * Get the adjugate of a matrix\n * @param {mat} a The matrix from which to get the adjugate\n * @return {mat} The adjugate of a\n */\nmat.adj = a => {\n const minors = mat(a.m, a.n);\n for (let i = 1; i <= a.m; i++) {\n for (let j = 1; j <= a.n; j++) {\n mat.set(minors, i, j, mat.det(mat.minor(a, i, j)));\n }\n }\n const cofactors = mat.map(minors, (v, i) => v * (i % 2 ? -1 : 1));\n return mat.trans(cofactors);\n};\n\n/**\n * Get the inverse of a matrix\n * @param {mat} a The matrix to invert\n * @return {mat|false} a^-1 or false if the matrix has no inverse\n */\nmat.inv = a => {\n if (a.m !== a.n) { return false; }\n const d = mat.det(a);\n if (d === 0) { return false; }\n return mat.scale(mat.adj(a), 1 / d);\n};\n\n/**\n * Check if two matrices are equal\n * @param {mat} a Matrix a\n * @param {mat} b Matrix b\n * @return {boolean} True if matrices a and b are identical, false otherwise\n */\nmat.eq = (a, b) => a.m === b.m && a.n === b.n && mat.str(a) === mat.str(b);\n\n/**\n * Copy a matrix\n * @param {mat} a The matrix to copy\n * @return {mat} A copy of matrix a\n */\nmat.cpy = a => mat(a.m, a.n, [...a.entries]);\n\n/**\n * A function to call on each entry of a matrix\n * @callback matrixMapCallback\n * @param {number} value The entry value\n * @param {number} index The entry index\n * @param {Array<number>} entries The array of matrix entries\n * @return {number} The mapped entry\n */\n\n/**\n * Call a function on each entry of a matrix and build a new matrix from the results\n * @param {mat} a Matrix a\n * @param {matrixMapCallback} f The function to call on each entry of the matrix\n * @return {mat} Matrix a mapped through f\n */\nmat.map = (a, f) => mat(a.m, a.n, a.entries.map(f));\n\n/**\n * Convert a matrix into a string\n * @param {mat} a The matrix to convert\n * @param {string} [ms=', '] The separator string for columns\n * @param {string} [ns='\\n'] The separator string for rows\n * @return {string} A string representation of the matrix\n */\nmat.str = (a, ms = ', ', ns = '\\n') => _vec_chunk(a.entries, a.n).map(r => r.join(ms)).join(ns);\n\nif (true) {\n module.exports = { vec2, vec3, mat };\n}\n\n\n//# sourceURL=webpack://@basementuniverse/particles-2d/./node_modules/@basementuniverse/vec/vec.js?");
|
|
79
|
+
|
|
80
|
+
/***/ }),
|
|
81
|
+
|
|
82
|
+
/***/ "./index.ts":
|
|
83
|
+
/*!******************!*\
|
|
84
|
+
!*** ./index.ts ***!
|
|
85
|
+
\******************/
|
|
86
|
+
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
87
|
+
|
|
88
|
+
"use strict";
|
|
89
|
+
eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.Collider = exports.ForceField = exports.Attractor = exports.Emitter = exports.Particle = exports.ParticleSystem = void 0;\nconst canvas_helpers_1 = __webpack_require__(/*! @basementuniverse/canvas-helpers */ \"./node_modules/@basementuniverse/canvas-helpers/build/index.js\");\nconst _2d_1 = __webpack_require__(/*! @basementuniverse/intersection-helpers/2d */ \"./node_modules/@basementuniverse/intersection-helpers/build/2d/index.js\");\nconst utilities_1 = __webpack_require__(/*! @basementuniverse/intersection-helpers/utilities */ \"./node_modules/@basementuniverse/intersection-helpers/build/utilities/index.js\");\nconst parsecolor_1 = __webpack_require__(/*! @basementuniverse/parsecolor */ \"./node_modules/@basementuniverse/parsecolor/parsecolor.js\");\nconst utils_1 = __webpack_require__(/*! @basementuniverse/utils */ \"./node_modules/@basementuniverse/utils/utils.js\");\nconst vec_1 = __webpack_require__(/*! @basementuniverse/vec */ \"./node_modules/@basementuniverse/vec/vec.js\");\nfunction isVec2(value) {\n return typeof value === 'object' && 'x' in value && 'y' in value;\n}\nfunction isRandomRange(value) {\n return typeof value === 'object' && 'min' in value && 'max' in value;\n}\nfunction calculateRandomRange(range, integer = false) {\n const r = integer ? utils_1.randomIntBetween : utils_1.randomBetween;\n if (isVec2(range.min) && isVec2(range.max)) {\n return (0, vec_1.vec2)(r(range.min.x, range.max.x), r(range.min.y, range.max.y));\n }\n return r(range.min, range.max);\n}\nfunction colorToString(color) {\n var _a;\n return `rgba(${color.r}, ${color.g}, ${color.b}, ${(_a = color.a) !== null && _a !== void 0 ? _a : 1})`;\n}\nfunction isColorObject(color) {\n return (typeof color === 'object' && 'r' in color && 'g' in color && 'b' in color);\n}\nfunction prepareColor(color) {\n if (Array.isArray(color)) {\n return prepareColor(color[(0, utils_1.randomIntBetween)(0, color.length - 1)]);\n }\n if (isColorObject(color)) {\n return colorToString(color);\n }\n return color;\n}\nfunction makeTransparent(color) {\n if (isColorObject(color)) {\n return { ...color, a: 0 };\n }\n const parsed = (0, parsecolor_1.parseColor)(color);\n return { ...parsed, a: 0 };\n}\n// -----------------------------------------------------------------------------\n// Particle System\n// -----------------------------------------------------------------------------\nclass ParticleSystem {\n constructor() {\n this.particles = [];\n this.emitters = [];\n this.attractors = [];\n this.forceFields = [];\n this.colliders = [];\n }\n update(dt) {\n // Update particles\n this.particles.forEach(particle => {\n if (!particle.disposed) {\n particle.update(this, dt);\n }\n });\n this.particles = this.particles.filter(particle => !particle.disposed);\n // Update emitters\n this.emitters.forEach(emitter => {\n if (!emitter.disposed) {\n emitter.update(this, dt);\n }\n });\n this.emitters = this.emitters.filter(emitter => !emitter.disposed);\n // Update attractors\n this.attractors.forEach(attractor => {\n if (!attractor.disposed) {\n attractor.update(dt);\n }\n });\n this.attractors = this.attractors.filter(attractor => !attractor.disposed);\n // Update force fields\n this.forceFields.forEach(forceField => {\n if (!forceField.disposed) {\n forceField.update(dt);\n }\n });\n this.forceFields = this.forceFields.filter(forceField => !forceField.disposed);\n }\n draw(context) {\n this.particles.forEach(particle => {\n if (!particle.disposed) {\n particle.draw(this, context);\n }\n });\n }\n}\nexports.ParticleSystem = ParticleSystem;\n// -----------------------------------------------------------------------------\n// Particles\n// -----------------------------------------------------------------------------\nconst PARTICLE_DEFAULT_UPDATE_TYPES = [\n 'age',\n 'physics',\n 'direction',\n 'position',\n];\nconst PARTICLE_DEFAULT_DRAW_TYPES = ['transforms', 'fade', 'styles'];\nconst DEFAULT_PARTICLE_OPTIONS = {\n useAttractors: true,\n useForceFields: true,\n useColliders: true,\n defaultUpdates: 'all',\n defaultDraws: 'all',\n};\nfunction prepareDefaultUpdates(defaultUpdates) {\n if (!Array.isArray(defaultUpdates)) {\n return defaultUpdates === 'all' ? [...PARTICLE_DEFAULT_UPDATE_TYPES] : [];\n }\n return defaultUpdates.filter(update => PARTICLE_DEFAULT_UPDATE_TYPES.includes(update));\n}\nfunction prepareDefaultDraws(defaultDraws) {\n if (!Array.isArray(defaultDraws)) {\n return defaultDraws === 'all' ? [...PARTICLE_DEFAULT_DRAW_TYPES] : [];\n }\n return defaultDraws.filter(draw => PARTICLE_DEFAULT_DRAW_TYPES.includes(draw));\n}\nfunction prepareGlow(context, glow, actualColor = 'white') {\n context.shadowColor = actualColor;\n context.shadowBlur = glow.amount;\n context.shadowOffsetX = 0;\n context.shadowOffsetY = 0;\n}\nconst DEFAULT_PARTICLE_STYLE = {\n style: 'dot',\n color: 'white',\n};\nclass Particle {\n constructor(\n /**\n * Initial position of the particle\n */\n position, \n /**\n * Initial velocity of the particle\n */\n velocity, \n /**\n * Size of the particle. This is used differently based on the style:\n *\n * - 'dot' style: we use the maximum of x and y as the radius\n * - 'line' style: x is the length of the line, y is the line width\n * - 'radial' style: we use the maximum of x and y as the radius\n * - 'image' style: x and y are the width and height of the image\n */\n size, \n /**\n * Rotation of the particle in radians\n *\n * _(Note: not used for 'dot' and 'radial' styles)_\n *\n * If this is null, we calculate rotation based on velocity\n */\n rotation = null, \n /**\n * Lifespan of the particle in seconds\n */\n lifespan = 1, \n /**\n * Style options for the particle. This can be used to define a default\n * rendering style and associated settings for the style\n *\n * The style can be one of:\n *\n * - 'dot': a simple dot with a color and optional glow\n * - 'radial': a radial gradient with a color that fades to transparent\n * - 'line': a line segment with a color, optional glow, and optional\n * rotation (the rotation can be relative or absolute)\n * - 'image': an image with an optional rotation (the rotation can be\n * relative or absolute)\n *\n * If this is null, the particle will use the custom rendering hook if\n * provided, or it will be invisible if no custom rendering is provided\n *\n * Omit this field or set it to undefined to use the default style\n */\n style, \n /**\n * Provide custom update logic and rendering logic here\n */\n options) {\n var _a, _b;\n this.position = position;\n this.velocity = velocity;\n this.size = size;\n this.rotation = rotation;\n this.lifespan = lifespan;\n this.age = 0;\n this.style = null;\n this.actualRotation = 0;\n this.actualColor = '#fff';\n this.actualColorTransparent = '#fff0';\n this.actualGlowColor = '#fff';\n this._disposed = false;\n if (style !== null) {\n this.style = Object.assign({}, DEFAULT_PARTICLE_STYLE, style !== null && style !== void 0 ? style : {});\n }\n this.options = Object.assign({}, DEFAULT_PARTICLE_OPTIONS, options !== null && options !== void 0 ? options : {});\n // Prepare colors\n if (this.style && 'color' in this.style) {\n this.actualColor = prepareColor(this.style.color);\n this.actualColorTransparent = colorToString(makeTransparent(this.actualColor));\n if ('glow' in this.style) {\n this.actualGlowColor = prepareColor((_b = (_a = this.style.glow) === null || _a === void 0 ? void 0 : _a.color) !== null && _b !== void 0 ? _b : 'white');\n }\n }\n }\n get disposed() {\n return this._disposed;\n }\n get normalisedLifeRemaining() {\n if (this.lifespan <= 0) {\n return 0;\n }\n return (0, utils_1.unlerp)(this.age, 0, this.lifespan);\n }\n update(system, dt) {\n var _a;\n const defaultUpdates = prepareDefaultUpdates(this.options.defaultUpdates);\n // Optionally handle particle lifespan\n if (defaultUpdates.includes('age')) {\n this.age += dt;\n // Dispose the particle when its lifespan is reached\n if (this.age >= this.lifespan) {\n this._disposed = true;\n }\n }\n // Optionally handle particle physics, i.e. forces from attractors, force\n // fields, and colliders\n if (defaultUpdates.includes('physics')) {\n if (this.options.useAttractors) {\n system.attractors.forEach(attractor => {\n if (!attractor.disposed) {\n attractor.applyForce(this, dt);\n }\n });\n }\n if (this.options.useForceFields) {\n system.forceFields.forEach(forceField => {\n if (!forceField.disposed) {\n forceField.applyForce(this, dt);\n }\n });\n }\n if (this.options.useColliders) {\n system.colliders.forEach(collider => {\n collider.handleCollision(this);\n });\n }\n }\n // Call custom update hook if provided\n if (this.options.update) {\n this.options.update.bind(this)(system, dt);\n }\n // Update rotation and, if configured, calculate rotation based on velocity\n this.actualRotation = (_a = this.rotation) !== null && _a !== void 0 ? _a : 0;\n if (defaultUpdates.includes('direction') && this.rotation === null) {\n this.actualRotation = vec_1.vec2.rad(this.velocity);\n }\n // Optionally handle position integration over time\n if (defaultUpdates.includes('position')) {\n this.position = vec_1.vec2.add(this.position, vec_1.vec2.scale(this.velocity, dt));\n }\n }\n draw(system, context) {\n var _a, _b, _c, _d, _e;\n const defaultDraws = prepareDefaultDraws(this.options.defaultDraws);\n context.save();\n // Optionally apply transforms\n if (defaultDraws.includes('transforms')) {\n context.translate(this.position.x, this.position.y);\n }\n // Optionally handle fade in/out effects\n if (defaultDraws.includes('fade') && ((_a = this.style) === null || _a === void 0 ? void 0 : _a.fade)) {\n const fadeIn = this.style.fade.in === 0\n ? 1\n : (0, utils_1.clamp)((0, utils_1.unlerp)(0, this.style.fade.in, this.age), 0, 1);\n const fadeOut = this.style.fade.out === 0\n ? 1\n : (0, utils_1.clamp)((0, utils_1.unlerp)(this.lifespan, this.lifespan - this.style.fade.out, this.age), 0, 1);\n context.globalAlpha = (0, utils_1.clamp)(fadeIn * fadeOut, 0, 1);\n }\n // Call custom pre-draw hook if provided\n if (this.options.preDraw) {\n this.options.preDraw.bind(this)(system, context);\n }\n // Optionally render one of the default styles if configured\n if (defaultDraws.includes('styles') && this.style !== null) {\n switch (this.style.style) {\n case 'dot':\n // Dot style renders a circle with a fill color\n if (this.style.glow) {\n prepareGlow(context, this.style.glow, this.actualGlowColor);\n }\n (0, canvas_helpers_1.circle)(context, (0, vec_1.vec2)(), Math.max(this.size.x, this.size.y) / 2, {\n fill: true,\n fillColor: this.actualColor,\n stroke: false,\n });\n break;\n case 'radial':\n // Radial style renders a radial gradient circle\n const size = Math.max(this.size.x, this.size.y) / 2;\n const gradient = context.createRadialGradient(0, 0, 0, 0, 0, size);\n const startColor = this.actualColor;\n const endColor = this.actualColorTransparent;\n gradient.addColorStop(0, startColor);\n gradient.addColorStop(1, endColor);\n context.fillStyle = gradient;\n context.beginPath();\n context.arc(0, 0, size, 0, Math.PI * 2);\n context.fill();\n context.closePath();\n break;\n case 'line':\n // Line style renders a line segment with a stroke color\n if (this.style.glow) {\n prepareGlow(context, this.style.glow, this.actualGlowColor);\n }\n const angle = ((_b = this.actualRotation) !== null && _b !== void 0 ? _b : 0) + ((_c = this.style.rotationOffset) !== null && _c !== void 0 ? _c : 0);\n const length = this.size.x;\n const lineWidth = this.size.y;\n const vector = vec_1.vec2.rot((0, vec_1.vec2)(length, 0), angle);\n (0, canvas_helpers_1.line)(context, vec_1.vec2.scale(vector, -0.5), vec_1.vec2.scale(vector, 0.5), {\n lineWidth,\n strokeColor: this.actualColor,\n });\n break;\n case 'image':\n // Image style renders an image with optional rotation\n if (defaultDraws.includes('transforms')) {\n const angle = ((_d = this.actualRotation) !== null && _d !== void 0 ? _d : 0) + ((_e = this.style.rotationOffset) !== null && _e !== void 0 ? _e : 0);\n context.rotate(angle);\n }\n context.drawImage(this.style.image, -this.size.x / 2, -this.size.y / 2, this.size.x, this.size.y);\n break;\n }\n }\n // Call custom post-draw hook if provided\n if (this.options.postDraw) {\n this.options.postDraw.bind(this)(system, context);\n }\n context.restore();\n }\n}\nexports.Particle = Particle;\nconst DEFAULT_EMITTER_OPTIONS = {\n particles: {\n position: 'uniform',\n speed: 0,\n direction: 0,\n size: (0, vec_1.vec2)(1),\n rotation: null,\n lifespan: 1,\n style: DEFAULT_PARTICLE_STYLE,\n options: DEFAULT_PARTICLE_OPTIONS,\n },\n emission: {\n type: 'rate',\n rate: 1,\n },\n};\nclass Emitter {\n constructor(position, size = (0, vec_1.vec2)(0, 0), lifespan = -1, options) {\n this.position = position;\n this.size = size;\n this.lifespan = lifespan;\n this.age = 0;\n this.totalParticlesEmitted = 0;\n this._disposed = false;\n this.currentRate = 0;\n this.lastRateChange = 0;\n this.particlesToEmit = 0;\n this.options = Object.assign({}, DEFAULT_EMITTER_OPTIONS, options !== null && options !== void 0 ? options : {});\n }\n get disposed() {\n return this._disposed;\n }\n update(system, dt) {\n // Handle emitter aging and dispose if we've reached the lifespan\n this.age += dt;\n if (this.lifespan !== -1 && this.age >= this.lifespan) {\n this._disposed = true;\n return;\n }\n // Handle particle emission based on the type of emission configured\n switch (this.options.emission.type) {\n case 'rate':\n // Rate mode emits particles continuously at a specified rate\n // Handle random rate changes\n this.lastRateChange += dt;\n if (this.currentRate <= 0 ||\n this.lastRateChange >= Emitter.RANDOM_RATE_CHANGE_INTERVAL) {\n this.lastRateChange = 0;\n // The actual emission rate can be a fixed value or a random range\n this.currentRate = isRandomRange(this.options.emission.rate)\n ? calculateRandomRange(this.options.emission.rate)\n : this.options.emission.rate;\n }\n // Accumulate a fractional number of particles to emit\n this.particlesToEmit += this.currentRate * dt;\n // Emit particles if we have enough to emit\n if (this.particlesToEmit >= 1) {\n // Get the whole number of particles to emit\n const n = Math.floor(this.particlesToEmit);\n // Subtract the number of particles, keeping the remainder\n this.particlesToEmit -= n;\n // Emit the particles\n this.emitParticles(system, n);\n this.totalParticlesEmitted += n;\n }\n break;\n case 'burst':\n // Burst mode emits a fixed or random number of particles at once (or\n // after a delay) and then immediately disposes the emitter\n if (!this.options.emission.delay ||\n this.age >= this.options.emission.delay) {\n // The number of particles to emit can be a fixed value or a random\n // range\n const n = isRandomRange(this.options.emission.n)\n ? calculateRandomRange(this.options.emission.n, true)\n : Math.ceil(this.options.emission.n);\n if (n > 0) {\n this.emitParticles(system, n);\n this.totalParticlesEmitted += n;\n // Keep trying to emit until we've emitted at least one particle\n this._disposed = true;\n }\n }\n break;\n case 'custom':\n // Custom mode allows for a custom function to determine how many\n // particles to emit on each update\n const n = Math.ceil(this.options.emission.f.bind(this)());\n if (n > 0) {\n this.emitParticles(system, n);\n this.totalParticlesEmitted += n;\n }\n break;\n }\n }\n emitParticles(system, n) {\n for (let i = 0; i < n; i++) {\n const particle = this.createParticle(system, i);\n if (particle) {\n system.particles.push(particle);\n }\n }\n }\n createParticle(system, n) {\n // Generate position\n let position;\n if ((0, utilities_1.vectorAlmostZero)(this.size)) {\n // Emitter size is zero, so use the exact emitter position\n position = (0, vec_1.vec2)(this.position);\n }\n else {\n switch (this.options.particles.position) {\n case 'uniform':\n // Uniform distribution within the emitter area\n position = (0, vec_1.vec2)((0, utils_1.randomIntBetween)(this.position.x - this.size.x / 2, this.position.x + this.size.x / 2), (0, utils_1.randomIntBetween)(this.position.y - this.size.y / 2, this.position.y + this.size.y / 2));\n break;\n case 'normal':\n // Normal distribution from the center of the emitter area\n position = (0, vec_1.vec2)((0, utils_1.cltRandomInt)(this.position.x - this.size.x / 2, this.position.x + this.size.x / 2), (0, utils_1.cltRandomInt)(this.position.y - this.size.y / 2, this.position.y + this.size.y / 2));\n break;\n default:\n if (typeof this.options.particles.position === 'function') {\n // Custom position function\n position = this.options.particles.position.bind(this)(n);\n }\n else {\n // Something went wrong, fall back to emitter position\n position = (0, vec_1.vec2)(this.position);\n }\n }\n }\n // Generate velocity\n let speed;\n if (typeof this.options.particles.speed === 'function') {\n // Custom speed function\n speed = this.options.particles.speed.bind(this)(n);\n }\n else if (isRandomRange(this.options.particles.speed)) {\n // Random speed range\n speed = calculateRandomRange(this.options.particles.speed, true);\n }\n else {\n // Fixed speed\n speed = this.options.particles.speed;\n }\n let direction;\n if (typeof this.options.particles.direction === 'function') {\n // Custom direction function\n direction = this.options.particles.direction.bind(this)(n);\n }\n else if (isRandomRange(this.options.particles.direction)) {\n // Random direction range\n direction = calculateRandomRange(this.options.particles.direction);\n }\n else {\n // Fixed direction\n direction = this.options.particles.direction;\n }\n const velocity = vec_1.vec2.rot((0, vec_1.vec2)(speed, 0), direction);\n // Generate size\n let size;\n if (typeof this.options.particles.size === 'function') {\n // Custom size function\n size = this.options.particles.size.bind(this)(n);\n }\n else if (isRandomRange(this.options.particles.size)) {\n // Random size range\n size = calculateRandomRange(this.options.particles.size);\n }\n else {\n // Fixed size\n size = this.options.particles.size;\n }\n // Generate rotation\n let rotation;\n if (this.options.particles.rotation === null) {\n rotation = null;\n }\n else if (typeof this.options.particles.rotation === 'function') {\n // Custom rotation function\n rotation = this.options.particles.rotation.bind(this)(n);\n }\n else if (isRandomRange(this.options.particles.rotation)) {\n // Random rotation range\n rotation = calculateRandomRange(this.options.particles.rotation);\n }\n else {\n // Fixed rotation\n rotation = this.options.particles.rotation;\n }\n // Generate lifespan\n let lifespan;\n if (typeof this.options.particles.lifespan === 'function') {\n // Custom lifespan function\n lifespan = this.options.particles.lifespan.bind(this)(n);\n }\n else if (isRandomRange(this.options.particles.lifespan)) {\n // Random lifespan range\n lifespan = calculateRandomRange(this.options.particles.lifespan);\n }\n else {\n // Fixed lifespan\n lifespan = this.options.particles.lifespan;\n }\n return new Particle(position, velocity, size, rotation, lifespan, this.options.particles.style, this.options.particles.options);\n }\n}\nexports.Emitter = Emitter;\nEmitter.RANDOM_RATE_CHANGE_INTERVAL = 1;\n// -----------------------------------------------------------------------------\n// Attractors\n// -----------------------------------------------------------------------------\nclass Attractor {\n constructor(position, range = 100, force = 1, falloff = 1, lifespan = -1) {\n this.position = position;\n this.range = range;\n this.force = force;\n this.falloff = falloff;\n this.lifespan = lifespan;\n this.age = 0;\n this._disposed = false;\n }\n get disposed() {\n return this._disposed;\n }\n applyForce(particle, dt) {\n // Calculate distance to the particle\n const d = (0, _2d_1.distance)(this.position, particle.position);\n if (d > this.range) {\n return; // Particle is out of range\n }\n // Calculate force based on distance and falloff\n const falloffFactor = 1 - Math.min(1, d / this.range);\n const force = vec_1.vec2.scale(vec_1.vec2.sub(this.position, particle.position), (this.force * falloffFactor) / Math.pow(d, this.falloff));\n // Apply the force to the particle's velocity\n particle.velocity = vec_1.vec2.add(particle.velocity, vec_1.vec2.scale(force, dt));\n }\n update(dt) {\n this.age += dt;\n // Dispose the attractor when its lifespan is reached\n if (this.lifespan !== -1 && this.age >= this.lifespan) {\n this._disposed = true;\n }\n }\n}\nexports.Attractor = Attractor;\n// -----------------------------------------------------------------------------\n// Forcefields\n// -----------------------------------------------------------------------------\nclass ForceField {\n constructor(force = (0, vec_1.vec2)(0, 0), lifespan = -1) {\n this.force = force;\n this.lifespan = lifespan;\n this.age = 0;\n this._disposed = false;\n }\n get disposed() {\n return this._disposed;\n }\n applyForce(particle, dt) {\n particle.velocity = vec_1.vec2.add(particle.velocity, vec_1.vec2.scale(this.force, dt));\n }\n update(dt) {\n this.age += dt;\n // Dispose the force field when its lifespan is reached\n if (this.lifespan !== -1 && this.age >= this.lifespan) {\n this._disposed = true;\n }\n }\n}\nexports.ForceField = ForceField;\nclass Collider {\n constructor(geometry, restitution = 0.5, friction = 0.5) {\n this.geometry = geometry;\n this.restitution = restitution;\n this.friction = friction;\n }\n handleCollision(particle) {\n var _a, _b;\n // Broad phase: first check if the point is in the collider's AABB\n const geometryAABB = (0, _2d_1.aabb)(this.geometry);\n if (geometryAABB === null) {\n return; // Invalid polygon\n }\n if (!(0, _2d_1.pointInAABB)(particle.position, geometryAABB)) {\n return; // Particle is outside the collider's AABB\n }\n // Narrow phase: check if the particle collides with the collider geometry\n let collisionResult;\n switch (this.geometry.type) {\n case 'circle':\n collisionResult = (0, _2d_1.pointInCircle)(particle.position, {\n position: this.geometry.position,\n radius: this.geometry.radius,\n });\n break;\n case 'rectangle':\n collisionResult = (0, _2d_1.pointInRectangle)(particle.position, {\n position: this.geometry.position,\n size: this.geometry.size,\n rotation: (_a = this.geometry.rotation) !== null && _a !== void 0 ? _a : 0,\n });\n break;\n case 'polygon':\n collisionResult = (0, _2d_1.pointInPolygon)(particle.position, {\n vertices: this.geometry.vertices,\n });\n break;\n }\n if (collisionResult === null || !collisionResult.intersects) {\n return; // Invalid polygon or no intersection\n }\n // Handle the collision\n // The collider has a friction value which is used to reduce the particle's\n // velocity after the collision\n // The collider has a restitution value which is used to bounce the particle\n // off the collider surface\n const normal = (_b = collisionResult.normal) !== null && _b !== void 0 ? _b : (0, vec_1.vec2)(0, 0);\n const relativeVelocity = vec_1.vec2.sub(particle.velocity, (0, vec_1.vec2)(0, 0));\n const velocityAlongNormal = vec_1.vec2.dot(relativeVelocity, normal);\n if (velocityAlongNormal > 0) {\n return; // Particle is moving away from the collider, no collision\n }\n // Calculate the impulse to apply to the particle\n const impulseMagnitude = -(1 + this.restitution) * velocityAlongNormal;\n const impulse = vec_1.vec2.scale(normal, impulseMagnitude);\n // Apply the impulse to the particle's velocity\n particle.velocity = vec_1.vec2.add(particle.velocity, impulse);\n // Apply friction to the particle's velocity\n const frictionImpulse = vec_1.vec2.scale(vec_1.vec2.sub(relativeVelocity, vec_1.vec2.scale(normal, velocityAlongNormal)), -this.friction);\n particle.velocity = vec_1.vec2.add(particle.velocity, frictionImpulse);\n }\n}\nexports.Collider = Collider;\n\n\n//# sourceURL=webpack://@basementuniverse/particles-2d/./index.ts?");
|
|
90
|
+
|
|
91
|
+
/***/ })
|
|
92
|
+
|
|
93
|
+
/******/ });
|
|
94
|
+
/************************************************************************/
|
|
95
|
+
/******/ // The module cache
|
|
96
|
+
/******/ var __webpack_module_cache__ = {};
|
|
97
|
+
/******/
|
|
98
|
+
/******/ // The require function
|
|
99
|
+
/******/ function __webpack_require__(moduleId) {
|
|
100
|
+
/******/ // Check if module is in cache
|
|
101
|
+
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
|
102
|
+
/******/ if (cachedModule !== undefined) {
|
|
103
|
+
/******/ return cachedModule.exports;
|
|
104
|
+
/******/ }
|
|
105
|
+
/******/ // Create a new module (and put it into the cache)
|
|
106
|
+
/******/ var module = __webpack_module_cache__[moduleId] = {
|
|
107
|
+
/******/ // no module.id needed
|
|
108
|
+
/******/ // no module.loaded needed
|
|
109
|
+
/******/ exports: {}
|
|
110
|
+
/******/ };
|
|
111
|
+
/******/
|
|
112
|
+
/******/ // Execute the module function
|
|
113
|
+
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
114
|
+
/******/
|
|
115
|
+
/******/ // Return the exports of the module
|
|
116
|
+
/******/ return module.exports;
|
|
117
|
+
/******/ }
|
|
118
|
+
/******/
|
|
119
|
+
/************************************************************************/
|
|
120
|
+
/******/
|
|
121
|
+
/******/ // startup
|
|
122
|
+
/******/ // Load entry module and return exports
|
|
123
|
+
/******/ // This entry module can't be inlined because the eval devtool is used.
|
|
124
|
+
/******/ var __webpack_exports__ = __webpack_require__("./index.ts");
|
|
125
|
+
/******/
|
|
126
|
+
/******/ return __webpack_exports__;
|
|
127
|
+
/******/ })()
|
|
128
|
+
;
|
|
129
|
+
});
|