UNPKG

kaplay

Version:

KAPLAY is a JavaScript & TypeScript game library that helps you make games fast and fun! (formerly known as Kaboom.js)

4 lines 909 kB
{ "version": 3, "sources": ["../package.json", "../src/math/clamp.ts", "../src/math/color.ts", "../src/math/math.ts", "../src/constants.ts", "../src/events/events.ts", "../src/utils/asserts.ts", "../src/utils/binaryheap.ts", "../src/utils/dataURL.ts", "../src/utils/deepEq.ts", "../src/utils/log.ts", "../src/utils/numbers.ts", "../src/utils/overload.ts", "../src/utils/runes.ts", "../src/utils/sets.ts", "../src/utils/uid.ts", "../src/data/gamepad.json", "../src/gfx/viewport.ts", "../src/app/inputBindings.ts", "../src/app/app.ts", "../src/app/frame.ts", "../src/gfx/anchor.ts", "../src/math/easings.ts", "../src/math/navigation.ts", "../src/math/navigationmesh.ts", "../src/math/various.ts", "../src/gfx/bg.ts", "../src/gfx/stack.ts", "../src/gfx/texPacker.ts", "../src/assets/utils.ts", "../src/assets/asset.ts", "../src/assets/sprite.ts", "../src/assets/aseprite.ts", "../src/assets/font.ts", "../src/assets/bitmapFont.ts", "../src/assets/pedit.ts", "../src/assets/shader.ts", "../src/assets/sound.ts", "../src/assets/spriteAtlas.ts", "../src/gfx/draw/drawRaw.ts", "../src/gfx/draw/drawPolygon.ts", "../src/gfx/draw/drawEllipse.ts", "../src/gfx/draw/drawCircle.ts", "../src/gfx/draw/drawLine.ts", "../src/gfx/draw/drawCurve.ts", "../src/gfx/draw/drawBezier.ts", "../src/gfx/gfx.ts", "../src/gfx/formatText.ts", "../src/gfx/draw/drawUVQuad.ts", "../src/gfx/draw/drawFormattedText.ts", "../src/gfx/draw/drawRect.ts", "../src/gfx/draw/drawUnscaled.ts", "../src/gfx/draw/drawInspectText.ts", "../src/gfx/draw/drawTriangle.ts", "../src/gfx/draw/drawDebug.ts", "../src/gfx/draw/drawFrame.ts", "../src/gfx/draw/drawLoadingScreen.ts", "../src/gfx/draw/drawStenciled.ts", "../src/gfx/draw/drawMasked.ts", "../src/gfx/draw/drawTexture.ts", "../src/gfx/draw/drawSprite.ts", "../src/gfx/draw/drawSubstracted.ts", "../src/gfx/draw/drawText.ts", "../src/gfx/gfxApp.ts", "../src/game/utils.ts", "../src/components/draw/circle.ts", "../src/components/draw/color.ts", "../src/components/draw/drawon.ts", "../src/components/draw/fadeIn.ts", "../src/components/draw/mask.ts", "../src/components/draw/opacity.ts", "../src/components/draw/outline.ts", "../src/components/draw/particles.ts", "../src/components/draw/polygon.ts", "../src/components/draw/raycast.ts", "../src/components/draw/rect.ts", "../src/components/draw/shader.ts", "../src/game/level.ts", "../src/events/globalEvents.ts", "../src/game/camera.ts", "../src/game/make.ts", "../src/game/game.ts", "../src/game/gravity.ts", "../src/audio/audio.ts", "../src/audio/playMusic.ts", "../src/audio/play.ts", "../src/audio/burp.ts", "../src/audio/volume.ts", "../src/game/initEvents.ts", "../src/game/kaboom.ts", "../src/game/layers.ts", "../src/game/object.ts", "../src/game/scenes.ts", "../src/components/draw/sprite.ts", "../src/components/draw/text.ts", "../src/components/draw/uvquad.ts", "../src/components/level/agent.ts", "../src/components/level/pathfinder.ts", "../src/components/level/patrol.ts", "../src/components/level/sentry.ts", "../src/components/level/tile.ts", "../src/components/misc/animate.ts", "../src/components/misc/boom.ts", "../src/components/misc/health.ts", "../src/components/misc/lifespan.ts", "../src/components/misc/named.ts", "../src/components/misc/state.ts", "../src/components/misc/stay.ts", "../src/components/misc/textInput.ts", "../src/components/misc/timer.ts", "../src/components/physics/area.ts", "../src/components/physics/body.ts", "../src/components/physics/doubleJump.ts", "../src/components/physics/effectors.ts", "../src/components/transform/anchor.ts", "../src/components/transform/fixed.ts", "../src/components/transform/follow.ts", "../src/components/transform/layer.ts", "../src/components/transform/move.ts", "../src/components/transform/offscreen.ts", "../src/components/transform/pos.ts", "../src/components/transform/rotate.ts", "../src/components/transform/scale.ts", "../src/components/transform/z.ts", "../src/kaplay.ts"], "sourcesContent": ["{\n \"name\": \"kaplay\",\n \"description\": \"KAPLAY is a JavaScript & TypeScript game library that helps you make games fast and fun! (formerly known as Kaboom.js)\",\n \"version\": \"3001.0.19\",\n \"license\": \"MIT\",\n \"homepage\": \"https://kaplayjs.com/\",\n \"bugs\": {\n \"url\": \"https://github.com/kaplayjs/kaplay/issues\"\n },\n \"funding\": {\n \"type\": \"opencollective\",\n \"url\": \"https://opencollective.com/kaplay\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/kaplayjs/kaplay.git\"\n },\n \"type\": \"module\",\n \"main\": \"./dist/kaplay.cjs\",\n \"module\": \"./dist/kaplay.mjs\",\n \"types\": \"./dist/doc.d.ts\",\n \"readme\": \"./README.md\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/doc.d.ts\",\n \"default\": \"./dist/kaplay.mjs\"\n },\n \"require\": {\n \"types\": \"./dist/doc.d.ts\",\n \"default\": \"./dist/kaplay.cjs\"\n }\n },\n \"./global\": \"./dist/declaration/global.js\"\n },\n \"typesVersions\": {\n \"*\": {\n \"global\": [\n \"./dist/declaration/global.d.ts\"\n ]\n }\n },\n \"keywords\": [\n \"game development\",\n \"javascript\",\n \"typescript\",\n \"game engine\",\n \"2d games\",\n \"physics engine\",\n \"webgl\",\n \"canvas\",\n \"game library\",\n \"kaplay\",\n \"kaboom\",\n \"kaboomjs\",\n \"kaboom.js\"\n ],\n \"files\": [\n \"dist/\",\n \"kaplay.webp\",\n \"CHANGELOG.md\"\n ],\n \"scripts\": {\n \"dev\": \"NODE_ENV=development node scripts/dev.js\",\n \"win:dev\": \"set NODE_ENV=development && node scripts/dev.js\",\n \"build\": \"node scripts/generateIndex.js && npm run doc-dts && node scripts/build.js\",\n \"build:fast\": \"node scripts/buildFast.js\",\n \"check\": \"tsc\",\n \"fmt\": \"dprint fmt\",\n \"test\": \"node scripts/test.js\",\n \"doc-dts\": \"dts-bundle-generator -o dist/doc.d.ts src/index.ts\",\n \"test:vite\": \"vitest --typecheck\",\n \"desktop\": \"tauri dev\",\n \"prepare\": \"npm run build\",\n \"publish:next\": \"npm publish --tag next\"\n },\n \"devDependencies\": {\n \"@kaplayjs/dprint-config\": \"^1.2.0\",\n \"@types/jest\": \"^29.5.14\",\n \"dprint\": \"^0.49.1\",\n \"dts-bundle-generator\": \"^9.5.1\",\n \"ejs\": \"^3.1.10\",\n \"esbuild\": \"^0.25.2\",\n \"express\": \"^5.1.0\",\n \"puppeteer\": \"^22.15.0\",\n \"tar-fs\": \"3.0.8\",\n \"typescript\": \"5.6.3\",\n \"vite\": \"5.4.16\",\n \"vitest\": \"^3.1.1\",\n \"vitest-environment-puppeteer\": \"^11.0.3\",\n \"vitest-puppeteer\": \"^11.0.3\"\n },\n \"engines\": {\n \"node\": \">=20.0.0\"\n },\n \"packageManager\": \"pnpm@9.9.0+sha512.60c18acd138bff695d339be6ad13f7e936eea6745660d4cc4a776d5247c540d0edee1a563695c183a66eb917ef88f2b4feb1fc25f32a7adcadc7aaf3438e99c1\"\n}\n", "export function clamp(\n val: number,\n min: number,\n max: number,\n): number {\n if (min > max) {\n return clamp(val, max, min);\n }\n return Math.min(Math.max(val, min), max);\n}\n", "import { clamp } from \"./clamp.js\";\nimport { lerp } from \"./math\";\n\nexport type RGBValue = [number, number, number];\nexport type RGBAValue = [number, number, number, number];\n\n/**\n * 0-255 RGBA color.\n *\n * @group Math\n */\nexport class Color {\n /** Red (0-255. */\n r: number = 255;\n /** Green (0-255). */\n g: number = 255;\n /** Blue (0-255). */\n b: number = 255;\n\n constructor(r: number, g: number, b: number) {\n this.r = clamp(r, 0, 255);\n this.g = clamp(g, 0, 255);\n this.b = clamp(b, 0, 255);\n }\n\n // TODO: Type arr as tuple (no in ts-strict branch yet)\n static fromArray(arr: number[]) {\n return new Color(arr[0], arr[1], arr[2]);\n }\n\n /**\n * Create color from hex string or literal.\n *\n * @example\n * ```js\n * Color.fromHex(0xfcef8d)\n * Color.fromHex(\"#5ba675\")\n * Color.fromHex(\"d46eb3\")\n * ```\n *\n * @since v3000.0\n */\n static fromHex(hex: string | number) {\n if (typeof hex === \"number\") {\n return new Color(\n (hex >> 16) & 0xff,\n (hex >> 8) & 0xff,\n (hex >> 0) & 0xff,\n );\n }\n else if (typeof hex === \"string\") {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(\n hex,\n );\n\n if (!result) throw new Error(\"Invalid hex color format\");\n\n return new Color(\n parseInt(result[1], 16),\n parseInt(result[2], 16),\n parseInt(result[3], 16),\n );\n }\n else {\n throw new Error(\"Invalid hex color format\");\n }\n }\n\n // TODO: use range of [0, 360] [0, 100] [0, 100]?\n static fromHSL(h: number, s: number, l: number) {\n if (s == 0) {\n return new Color(255 * l, 255 * l, 255 * l);\n }\n\n const hue2rgb = (p: number, q: number, t: number) => {\n if (t < 0) t += 1;\n if (t > 1) t -= 1;\n if (t < 1 / 6) return p + (q - p) * 6 * t;\n if (t < 1 / 2) return q;\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n return p;\n };\n\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n const p = 2 * l - q;\n const r = hue2rgb(p, q, h + 1 / 3);\n const g = hue2rgb(p, q, h);\n const b = hue2rgb(p, q, h - 1 / 3);\n\n return new Color(\n Math.round(r * 255),\n Math.round(g * 255),\n Math.round(b * 255),\n );\n }\n\n static RED = new Color(255, 0, 0);\n static GREEN = new Color(0, 255, 0);\n static BLUE = new Color(0, 0, 255);\n static YELLOW = new Color(255, 255, 0);\n static MAGENTA = new Color(255, 0, 255);\n static CYAN = new Color(0, 255, 255);\n static WHITE = new Color(255, 255, 255);\n static BLACK = new Color(0, 0, 0);\n\n clone(): Color {\n return new Color(this.r, this.g, this.b);\n }\n\n /** Lighten the color (adds RGB by n). */\n lighten(a: number): Color {\n return new Color(this.r + a, this.g + a, this.b + a);\n }\n\n /** Darkens the color (subtracts RGB by n). */\n darken(a: number): Color {\n return this.lighten(-a);\n }\n\n invert(): Color {\n return new Color(255 - this.r, 255 - this.g, 255 - this.b);\n }\n\n mult(other: Color): Color {\n return new Color(\n this.r * other.r / 255,\n this.g * other.g / 255,\n this.b * other.b / 255,\n );\n }\n\n /**\n * Linear interpolate to a destination color.\n *\n * @since v3000.0\n */\n lerp(dest: Color, t: number): Color {\n return new Color(\n lerp(this.r, dest.r, t),\n lerp(this.g, dest.g, t),\n lerp(this.b, dest.b, t),\n );\n }\n\n /**\n * Convert color into HSL format.\n *\n * @since v3001.0\n */\n toHSL(): [number, number, number] {\n const r = this.r / 255;\n const g = this.g / 255;\n const b = this.b / 255;\n const max = Math.max(r, g, b), min = Math.min(r, g, b);\n let h = (max + min) / 2;\n let s = h;\n const l = h;\n if (max == min) {\n h = s = 0;\n }\n else {\n const d = max - min;\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n switch (max) {\n case r:\n h = (g - b) / d + (g < b ? 6 : 0);\n break;\n case g:\n h = (b - r) / d + 2;\n break;\n case b:\n h = (r - g) / d + 4;\n break;\n }\n h /= 6;\n }\n return [h, s, l];\n }\n\n eq(other: Color): boolean {\n return this.r === other.r\n && this.g === other.g\n && this.b === other.b;\n }\n\n toString(): string {\n return `rgb(${this.r}, ${this.g}, ${this.b})`;\n }\n\n /**\n * Return the hex string of color.\n *\n * @since v3000.0\n */\n toHex(): string {\n return \"#\"\n + ((1 << 24) + (this.r << 16) + (this.g << 8) + this.b).toString(16)\n .slice(1);\n }\n\n /**\n * Return the color converted to an array.\n *\n * @since v3001.0\n */\n toArray(): Array<number> {\n return [this.r, this.g, this.b];\n }\n}\n\nexport type ColorArgs =\n // rgb(new Color(255, 255, 255))\n | [Color]\n /**\n * rgb(new Color(255, 255, 255), 1)\n *\n * This is only used to parse directly the color of background. This\n * syntax shouldn't be used to set opacity. Use `opacity()` comp instead.\n */\n | [Color, number]\n // rgb(255, 255, 255)\n | RGBValue\n /**\n * rgb(255, 255, 255, 1)\n *\n * This is only used to parse directly the color of background. This\n * syntax shouldn't be used to set opacity. Use `opacity()` comp instead.\n */\n | RGBAValue\n // rgb(\"#ffffff\")\n | [string]\n | [number[]]\n | [];\n\nexport function rgb(...args: ColorArgs): Color {\n if (args.length === 0) {\n return new Color(255, 255, 255);\n }\n else if (args.length === 1) {\n if (args[0] instanceof Color) {\n // rgb(new Color(255, 255, 255))\n return args[0].clone();\n }\n else if (typeof args[0] === \"string\") {\n // rgb(\"#ffffff\")\n return Color.fromHex(args[0]);\n }\n else if (Array.isArray(args[0]) && args[0].length === 3) {\n // rgb([255, 255, 255])\n return Color.fromArray(args[0]);\n }\n }\n else if (args.length === 2) {\n if (args[0] instanceof Color) {\n return args[0].clone();\n }\n }\n else if (args.length === 3 || args.length === 4) {\n return new Color(args[0], args[1], args[2]);\n }\n\n throw new Error(\"Invalid color arguments\");\n}\n\nexport const hsl2rgb = (h: number, s: number, l: number) =>\n Color.fromHSL(h, s, l);\n", "import type { GameObj, LerpValue, RNGValue } from \"../types\";\nimport { clamp } from \"./clamp.js\";\nimport { Color, rgb } from \"./color\";\n\n/**\n * Possible arguments for a Vec2.\n *\n * @group Math\n */\nexport type Vec2Args =\n | [number, number]\n | [number]\n | [Vec2]\n | [number | Vec2]\n | [];\n\nexport function deg2rad(deg: number): number {\n return deg * Math.PI / 180;\n}\n\nexport function rad2deg(rad: number): number {\n return rad * 180 / Math.PI;\n}\n\nexport function lerp<V extends LerpValue>(\n a: V,\n b: V,\n t: number,\n): V {\n if (typeof a === \"number\" && typeof b === \"number\") {\n return a + (b - a) * t as V;\n }\n else if (a instanceof Vec2 && b instanceof Vec2) {\n return a.lerp(b, t) as V;\n }\n else if (a instanceof Color && b instanceof Color) {\n return a.lerp(b, t) as V;\n }\n throw new Error(\n `Bad value for lerp(): ${a}, ${b}. Only number, Vec2 and Color is supported.`,\n );\n}\n\nexport function map(\n v: number,\n l1: number,\n h1: number,\n l2: number,\n h2: number,\n): number {\n return l2 + (v - l1) / (h1 - l1) * (h2 - l2);\n}\n\nexport function mapc(\n v: number,\n l1: number,\n h1: number,\n l2: number,\n h2: number,\n): number {\n return clamp(map(v, l1, h1, l2, h2), l2, h2);\n}\n\nexport function step(edge: number, x: number) {\n return x < edge ? 0 : 1;\n}\n\nexport function smoothstep(edge0: number, edge1: number, x: number) {\n x = clamp((x - edge0) / (edge1 - edge0), 0, 1);\n return x * x * (3.0 - 2.0 * x);\n}\n\n/**\n * A 2D vector.\n *\n * @group Math\n */\nexport class Vec2 {\n /** The x coordinate */\n x: number = 0;\n /** The y coordinate */\n y: number = 0;\n\n constructor(x: number = 0, y: number = x) {\n this.x = x;\n this.y = y;\n }\n\n /** Create a new Vec2 from an angle in degrees */\n static fromAngle(deg: number) {\n const angle = deg2rad(deg);\n return new Vec2(Math.cos(angle), Math.sin(angle));\n }\n\n /** Create a new Vec2 from an array */\n static fromArray(arr: Array<number>) {\n return new Vec2(arr[0], arr[1]);\n }\n\n /** An empty vector. (0, 0) */\n static ZERO = new Vec2(0, 0);\n /** A vector with both components of 1. (1, 1) */\n static ONE = new Vec2(1, 1);\n /** A vector signaling to the left. (-1, 0) */\n static LEFT = new Vec2(-1, 0);\n /** A vector signaling to the right. (1, 0) */\n static RIGHT = new Vec2(1, 0);\n /** A vector signaling up. (0, -1) */\n static UP = new Vec2(0, -1);\n /** A vector signaling down. (0, 1) */\n static DOWN = new Vec2(0, 1);\n\n /** Clone the vector */\n clone(): Vec2 {\n return new Vec2(this.x, this.y);\n }\n\n /** Returns the addition with another vector. */\n add(...args: Vec2Args): Vec2 {\n const p2 = vec2(...args);\n return new Vec2(this.x + p2.x, this.y + p2.y);\n }\n\n /** Returns the subtraction with another vector. */\n sub(...args: Vec2Args): Vec2 {\n const p2 = vec2(...args);\n return new Vec2(this.x - p2.x, this.y - p2.y);\n }\n\n /** Scale by another vector. or a single number */\n scale(...args: Vec2Args): Vec2 {\n const s = vec2(...args);\n return new Vec2(this.x * s.x, this.y * s.y);\n }\n\n /** Get distance between another vector */\n dist(...args: Vec2Args): number {\n const p2 = vec2(...args);\n return this.sub(p2).len();\n }\n\n /** Get squared distance between another vector */\n sdist(...args: Vec2Args): number {\n const p2 = vec2(...args);\n return this.sub(p2).slen();\n }\n\n /**\n * Calculates the squared distance between the vectors\n * @param v The vector\n * @param other The other vector\n * @returns The distance between the vectors\n */\n static sdist(v: Vec2, other: Vec2): number {\n const x = v.x - other.x;\n const y = v.y - other.y;\n return x * x + y * y;\n }\n\n /**\n * Get length of the vector\n *\n * @since v3000.0\n */\n len(): number {\n return Math.sqrt(this.dot(this));\n }\n\n /**\n * Get squared length of the vector\n *\n * @since v3000.0\n */\n slen(): number {\n return this.dot(this);\n }\n\n /**\n * Get the unit vector (length of 1).\n */\n unit(): Vec2 {\n const len = this.len();\n return len === 0 ? new Vec2(0) : this.scale(1 / len);\n }\n\n /**\n * Get the perpendicular vector.\n */\n normal(): Vec2 {\n return new Vec2(this.y, -this.x);\n }\n\n /**\n * Get the reflection of a vector with a normal.\n *\n * @since v3000.0\n */\n reflect(normal: Vec2) {\n return this.sub(normal.scale(2 * this.dot(normal)));\n }\n\n /**\n * Get the projection of a vector onto another vector.\n *\n * @since v3000.0\n */\n project(on: Vec2) {\n return on.scale(on.dot(this) / on.len());\n }\n\n /**\n * Get the rejection of a vector onto another vector.\n *\n * @since v3000.0\n */\n reject(on: Vec2) {\n return this.sub(this.project(on));\n }\n\n /**\n * Get the dot product with another vector.\n */\n dot(p2: Vec2): number {\n return this.x * p2.x + this.y * p2.y;\n }\n\n /**\n * Get the dot product between 2 vectors.\n *\n * @since v3000.0\n */\n static dot(v: Vec2, other: Vec2): number {\n return v.x * other.x + v.y * other.y;\n }\n\n /**\n * Get the cross product with another vector.\n *\n * @since v3000.0\n */\n cross(p2: Vec2): number {\n return this.x * p2.y - this.y * p2.x;\n }\n\n /**\n * Get the cross product between 2 vectors.\n *\n * @since v3000.0\n */\n static cross(v: Vec2, other: Vec2): number {\n return v.x * other.y - v.y * other.x;\n }\n\n /**\n * Get the angle of the vector in degrees.\n */\n angle(...args: Vec2Args): number {\n const p2 = vec2(...args);\n return rad2deg(Math.atan2(this.y - p2.y, this.x - p2.x));\n }\n\n /**\n * Get the angle between this vector and another vector.\n *\n * @since v3000.0\n */\n angleBetween(...args: Vec2Args): number {\n const p2 = vec2(...args);\n return rad2deg(Math.atan2(this.cross(p2), this.dot(p2)));\n }\n\n /**\n * Linear interpolate to a destination vector (for positions).\n */\n lerp(dest: Vec2, t: number): Vec2 {\n return new Vec2(lerp(this.x, dest.x, t), lerp(this.y, dest.y, t));\n }\n\n /**\n * Spherical linear interpolate to a destination vector (for rotations).\n *\n * @since v3000.0\n */\n slerp(dest: Vec2, t: number): Vec2 {\n const cos = this.dot(dest);\n const sin = this.cross(dest);\n const angle = Math.atan2(sin, cos);\n return this\n .scale(Math.sin((1 - t) * angle))\n .add(dest.scale(Math.sin(t * angle)))\n .scale(1 / sin);\n }\n\n /**\n * If the vector (x, y) is zero.\n *\n * @since v3000.0\n */\n isZero(): boolean {\n return this.x === 0 && this.y === 0;\n }\n\n /**\n * To n precision floating point.\n */\n toFixed(n: number): Vec2 {\n return new Vec2(Number(this.x.toFixed(n)), Number(this.y.toFixed(n)));\n }\n\n /**\n * Multiply by a Mat4.\n *\n * @since v3000.0\n */\n transform(m: Mat4): Vec2 {\n return m.multVec2(this);\n }\n\n /**\n * See if one vector is equal to another.\n *\n * @since v3000.0\n */\n eq(other: Vec2): boolean {\n return this.x === other.x && this.y === other.y;\n }\n\n /** Converts the vector to a {@link Rect `Rect()`} with the vector as the origin.\n * @since v3000.0.\n */\n bbox(): Rect {\n return new Rect(this, 0, 0);\n }\n\n /** Converts the vector to a readable string. */\n toString(): string {\n return `vec2(${this.x.toFixed(2)}, ${this.y.toFixed(2)})`;\n }\n\n /** Converts the vector to an array.\n * @since v3001.0\n */\n toArray(): Array<number> {\n return [this.x, this.y];\n }\n}\n\nexport function vec2(...args: Vec2Args): Vec2 {\n if (args.length === 1) {\n if (args[0] instanceof Vec2) {\n return new Vec2(args[0].x, args[0].y);\n }\n else if (Array.isArray(args[0]) && args[0].length === 2) {\n return new Vec2(...args[0]);\n }\n }\n // @ts-ignore\n return new Vec2(...args);\n}\n\n/**\n * @group Math\n */\nexport class Quad {\n x: number = 0;\n y: number = 0;\n w: number = 1;\n h: number = 1;\n constructor(x: number, y: number, w: number, h: number) {\n this.x = x;\n this.y = y;\n this.w = w;\n this.h = h;\n }\n scale(other: Quad): Quad {\n return new Quad(\n this.x + this.w * other.x,\n this.y + this.h * other.y,\n this.w * other.w,\n this.h * other.h,\n );\n }\n pos() {\n return new Vec2(this.x, this.y);\n }\n clone(): Quad {\n return new Quad(this.x, this.y, this.w, this.h);\n }\n eq(other: Quad): boolean {\n return this.x === other.x\n && this.y === other.y\n && this.w === other.w\n && this.h === other.h;\n }\n toString(): string {\n return `quad(${this.x}, ${this.y}, ${this.w}, ${this.h})`;\n }\n}\n\nexport function quad(x: number, y: number, w: number, h: number): Quad {\n return new Quad(x, y, w, h);\n}\n\n// Internal class\nclass Mat2 {\n // 2x2 matrix\n a: number;\n b: number;\n c: number;\n d: number;\n\n constructor(a: number, b: number, c: number, d: number) {\n this.a = a;\n this.b = b;\n this.c = c;\n this.d = d;\n }\n\n mul(other: Mat2) {\n return new Mat2(\n this.a * other.a + this.b * other.c,\n this.a * other.b + this.b * other.d,\n this.c * other.a + this.d * other.c,\n this.c * other.b + this.d * other.d,\n );\n }\n\n transform(point: Vec2): Vec2 {\n return vec2(\n this.a * point.x + this.b * point.y,\n this.c * point.x + this.d * point.y,\n );\n }\n\n get inverse() {\n const det = this.det;\n return new Mat2(\n this.d / det,\n -this.b / det,\n -this.c / det,\n this.a / det,\n );\n }\n\n get transpose() {\n return new Mat2(\n this.a,\n this.c,\n this.b,\n this.d,\n );\n }\n\n get eigenvalues() {\n const m = this.trace / 2;\n const d = this.det;\n const e1 = m + Math.sqrt(m * m - d);\n const e2 = m - Math.sqrt(m * m - d);\n return [e1, e2];\n }\n\n eigenvectors(e1: number, e2: number) {\n if (this.c != 0) {\n return [[e1 - this.d, this.c], [e2 - this.d, this.c]];\n }\n else if (this.b != 0) {\n return [[this.b, e1 - this.a], [this.b, e2 - this.a]];\n }\n else {\n if (Math.abs(this.transform(vec2(1, 0)).x - e1) < Number.EPSILON) {\n return [[1, 0], [0, 1]];\n }\n else {\n return [[0, 1], [1, 0]];\n }\n }\n }\n\n get det() {\n return this.a * this.d - this.b * this.c;\n }\n\n get trace() {\n return this.a + this.d;\n }\n\n static rotation(radians: number) {\n const c = Math.cos(radians);\n const s = Math.sin(radians);\n return new Mat2(\n c,\n s,\n -s,\n c,\n );\n }\n\n static scale(x: number, y: number) {\n return new Mat2(x, 0, 0, y);\n }\n}\n\n// Internal class\nclass Mat23 {\n // 2x3 matrix, since the last column is always (0, 0, 1)\n a: number;\n b: number; // 0\n c: number;\n d: number; // 0\n e: number;\n f: number; // 1\n constructor(\n a: number = 1,\n b: number = 0,\n c: number = 0,\n d: number = 1,\n e: number = 0,\n f: number = 0,\n ) {\n this.a = a;\n this.b = b;\n this.c = c;\n this.d = d;\n this.e = e;\n this.f = f;\n }\n static fromMat2(m: Mat2) {\n return new Mat23(\n m.a,\n m.b,\n m.c,\n m.d,\n 0,\n 0,\n );\n }\n toMat2() {\n return new Mat2(\n this.a,\n this.b,\n this.c,\n this.d,\n );\n }\n static fromTranslation(t: Vec2) {\n return new Mat23(\n 1,\n 0,\n 0,\n 1,\n t.x,\n t.y,\n );\n }\n static fromRotation(radians: number) {\n const c = Math.cos(radians);\n const s = Math.sin(radians);\n return new Mat23(\n c,\n s,\n -s,\n c,\n 0,\n 0,\n );\n }\n static fromScale(s: Vec2): Mat23 {\n return new Mat23(\n s.x,\n 0,\n 0,\n s.y,\n 0,\n 0,\n );\n }\n mul(other: Mat23): Mat23 {\n return new Mat23(\n other.a * this.a + other.b * this.c,\n other.a * this.b + other.b * this.d,\n other.c * this.a + other.d * this.c,\n other.c * this.b + other.d * this.d,\n other.e * this.a + other.f * this.c + this.e,\n other.e * this.b + other.f * this.d + this.f,\n );\n }\n translate(t: Vec2): Mat23 {\n this.e += t.x * this.a + t.y * this.c;\n this.f += t.y * this.b + t.x * this.d;\n return this;\n }\n rotate(radians: number): Mat23 {\n const c = Math.cos(radians);\n const s = Math.sin(radians);\n const oldA = this.a;\n const oldB = this.b;\n this.a = c * this.a + s * this.c;\n this.b = c * this.b + s * this.d;\n this.c = c * this.c - s * oldA;\n this.d = c * this.d - s * oldB;\n return this;\n }\n scale(s: Vec2): Mat23 {\n this.a *= s.x;\n this.b *= s.x;\n this.c *= s.y;\n this.d *= s.y;\n return this;\n }\n transform(p: Vec2) {\n return vec2(\n this.a * p.x + this.c * p.y + this.e,\n this.b * p.x + this.d * p.y + this.f,\n );\n }\n\n get det() {\n return this.a * this.d - this.b * this.c;\n }\n\n get inverse() {\n const det = this.det;\n return new Mat23(\n this.d / det,\n -this.b / det,\n -this.c / det,\n this.a / det,\n (this.c * this.f - this.d * this.e) / det,\n (this.b * this.e - this.a * this.f) / det,\n );\n }\n}\n\n// Internal class\nclass Mat3 {\n // m11 m12 m13\n // m21 m22 m23\n // m31 m32 m33\n m11: number;\n m12: number;\n m13: number;\n m21: number;\n m22: number;\n m23: number;\n m31: number;\n m32: number;\n m33: number;\n\n constructor(\n m11: number,\n m12: number,\n m13: number,\n m21: number,\n m22: number,\n m23: number,\n m31: number,\n m32: number,\n m33: number,\n ) {\n this.m11 = m11;\n this.m12 = m12;\n this.m13 = m13;\n this.m21 = m21;\n this.m22 = m22;\n this.m23 = m23;\n this.m31 = m31;\n this.m32 = m32;\n this.m33 = m33;\n }\n\n static fromMat2(m: Mat2) {\n return new Mat3(\n m.a,\n m.b,\n 0,\n m.c,\n m.d,\n 0,\n 0,\n 0,\n 1,\n );\n }\n\n toMat2() {\n return new Mat2(\n this.m11,\n this.m12,\n this.m21,\n this.m22,\n );\n }\n\n mul(other: Mat3): Mat3 {\n return new Mat3(\n this.m11 * other.m11 + this.m12 * other.m21 + this.m13 * other.m31,\n this.m11 * other.m12 + this.m12 * other.m22 + this.m13 * other.m32,\n this.m11 * other.m13 + this.m12 * other.m23 + this.m13 * other.m33,\n this.m21 * other.m11 + this.m22 * other.m21 + this.m23 * other.m31,\n this.m21 * other.m12 + this.m22 * other.m22 + this.m23 * other.m32,\n this.m21 * other.m13 + this.m22 * other.m23 + this.m23 * other.m33,\n this.m31 * other.m11 + this.m32 * other.m21 + this.m33 * other.m31,\n this.m31 * other.m12 + this.m32 * other.m22 + this.m33 * other.m32,\n this.m31 * other.m13 + this.m32 * other.m23 + this.m33 * other.m33,\n );\n }\n\n get det(): number {\n return this.m11 * this.m22 * this.m33 + this.m12 * this.m23 * this.m31\n + this.m13 * this.m21 * this.m32 - this.m13 * this.m22 * this.m31\n - this.m12 * this.m21 * this.m33 - this.m11 * this.m23 * this.m32;\n }\n\n rotate(radians: number) {\n const c = Math.cos(radians);\n const s = Math.sin(radians);\n const oldA = this.m11;\n const oldB = this.m12;\n this.m11 = c * this.m11 + s * this.m21;\n this.m12 = c * this.m12 + s * this.m22;\n this.m21 = c * this.m21 - s * oldA;\n this.m22 = c * this.m22 - s * oldB;\n return this;\n }\n\n scale(x: number, y: number) {\n this.m11 *= x;\n this.m12 *= x;\n this.m21 *= y;\n this.m22 *= y;\n return this;\n }\n\n get inverse(): Mat3 {\n const det = this.det;\n return new Mat3(\n (this.m22 * this.m33 - this.m23 * this.m32) / det,\n (this.m13 * this.m32 - this.m12 * this.m33) / det,\n (this.m12 * this.m23 - this.m13 * this.m22) / det,\n (this.m23 * this.m31 - this.m21 * this.m33) / det,\n (this.m11 * this.m33 - this.m13 * this.m31) / det,\n (this.m13 * this.m21 - this.m11 * this.m23) / det,\n (this.m21 * this.m32 - this.m22 * this.m31) / det,\n (this.m12 * this.m31 - this.m11 * this.m32) / det,\n (this.m11 * this.m22 - this.m12 * this.m21) / det,\n );\n }\n\n get transpose(): Mat3 {\n return new Mat3(\n this.m11,\n this.m21,\n this.m31,\n this.m12,\n this.m22,\n this.m32,\n this.m13,\n this.m23,\n this.m33,\n );\n }\n}\n\n/**\n * @group Math\n */\nexport class Mat4 {\n m: number[] = [\n 1,\n 0,\n 0,\n 0,\n 0,\n 1,\n 0,\n 0,\n 0,\n 0,\n 1,\n 0,\n 0,\n 0,\n 0,\n 1,\n ];\n\n constructor(m?: number[]) {\n if (m) {\n this.m = m;\n }\n }\n\n static translate(p: Vec2): Mat4 {\n return new Mat4([\n 1,\n 0,\n 0,\n 0,\n 0,\n 1,\n 0,\n 0,\n 0,\n 0,\n 1,\n 0,\n p.x,\n p.y,\n 0,\n 1,\n ]);\n }\n\n static scale(s: Vec2): Mat4 {\n return new Mat4([\n s.x,\n 0,\n 0,\n 0,\n 0,\n s.y,\n 0,\n 0,\n 0,\n 0,\n 1,\n 0,\n 0,\n 0,\n 0,\n 1,\n ]);\n }\n\n static rotateX(a: number): Mat4 {\n a = deg2rad(-a);\n const c = Math.cos(a);\n const s = Math.sin(a);\n return new Mat4([\n 1,\n 0,\n 0,\n 0,\n 0,\n c,\n -s,\n 0,\n 0,\n s,\n c,\n 0,\n 0,\n 0,\n 0,\n 1,\n ]);\n }\n\n static rotateY(a: number): Mat4 {\n a = deg2rad(-a);\n const c = Math.cos(a);\n const s = Math.sin(a);\n return new Mat4([\n c,\n 0,\n s,\n 0,\n 0,\n 1,\n 0,\n 0,\n -s,\n 0,\n c,\n 0,\n 0,\n 0,\n 0,\n 1,\n ]);\n }\n\n static rotateZ(a: number): Mat4 {\n a = deg2rad(-a);\n const c = Math.cos(a);\n const s = Math.sin(a);\n return new Mat4([\n c,\n -s,\n 0,\n 0,\n s,\n c,\n 0,\n 0,\n 0,\n 0,\n 1,\n 0,\n 0,\n 0,\n 0,\n 1,\n ]);\n }\n\n translate(p: Vec2) {\n this.m[12] += this.m[0] * p.x + this.m[4] * p.y;\n this.m[13] += this.m[1] * p.x + this.m[5] * p.y;\n this.m[14] += this.m[2] * p.x + this.m[6] * p.y;\n this.m[15] += this.m[3] * p.x + this.m[7] * p.y;\n return this;\n }\n\n scale(p: Vec2) {\n this.m[0] *= p.x;\n this.m[4] *= p.y;\n this.m[1] *= p.x;\n this.m[5] *= p.y;\n this.m[2] *= p.x;\n this.m[6] *= p.y;\n this.m[3] *= p.x;\n this.m[7] *= p.y;\n return this;\n }\n\n rotate(a: number): Mat4 {\n a = deg2rad(-a);\n const c = Math.cos(a);\n const s = Math.sin(a);\n const m0 = this.m[0];\n const m1 = this.m[1];\n const m4 = this.m[4];\n const m5 = this.m[5];\n this.m[0] = m0 * c + m1 * s;\n this.m[1] = -m0 * s + m1 * c;\n this.m[4] = m4 * c + m5 * s;\n this.m[5] = -m4 * s + m5 * c;\n return this;\n }\n\n // TODO: in-place variant\n mult(other: Mat4): Mat4 {\n const out = [];\n for (let i = 0; i < 4; i++) {\n for (let j = 0; j < 4; j++) {\n out[i * 4 + j] = this.m[0 * 4 + j] * other.m[i * 4 + 0]\n + this.m[1 * 4 + j] * other.m[i * 4 + 1]\n + this.m[2 * 4 + j] * other.m[i * 4 + 2]\n + this.m[3 * 4 + j] * other.m[i * 4 + 3];\n }\n }\n return new Mat4(out);\n }\n\n multVec2(p: Vec2): Vec2 {\n return new Vec2(\n p.x * this.m[0] + p.y * this.m[4] + this.m[12],\n p.x * this.m[1] + p.y * this.m[5] + this.m[13],\n );\n }\n\n getTranslation() {\n return new Vec2(this.m[12], this.m[13]);\n }\n\n getScale() {\n if (this.m[0] != 0 || this.m[1] != 0) {\n const det = this.m[0] * this.m[5] - this.m[1] * this.m[4];\n const r = Math.sqrt(this.m[0] * this.m[0] + this.m[1] * this.m[1]);\n return new Vec2(r, det / r);\n }\n else if (this.m[4] != 0 || this.m[5] != 0) {\n const det = this.m[0] * this.m[5] - this.m[1] * this.m[4];\n const s = Math.sqrt(this.m[4] * this.m[4] + this.m[5] * this.m[5]);\n return new Vec2(det / s, s);\n }\n else {\n return new Vec2(0, 0);\n }\n }\n\n getRotation() {\n if (this.m[0] != 0 || this.m[1] != 0) {\n const r = Math.sqrt(this.m[0] * this.m[0] + this.m[1] * this.m[1]);\n return rad2deg(\n this.m[1] > 0\n ? Math.acos(this.m[0] / r)\n : -Math.acos(this.m[0] / r),\n );\n }\n else if (this.m[4] != 0 || this.m[5] != 0) {\n const s = Math.sqrt(this.m[4] * this.m[4] + this.m[5] * this.m[5]);\n return rad2deg(\n Math.PI / 2 - (this.m[5] > 0\n ? Math.acos(-this.m[4] / s)\n : -Math.acos(this.m[4] / s)),\n );\n }\n else {\n return 0;\n }\n }\n\n getSkew() {\n if (this.m[0] != 0 || this.m[1] != 0) {\n const r = Math.sqrt(this.m[0] * this.m[0] + this.m[1] * this.m[1]);\n return new Vec2(\n Math.atan(this.m[0] * this.m[4] + this.m[1] * this.m[5])\n / (r * r),\n 0,\n );\n }\n else if (this.m[4] != 0 || this.m[5] != 0) {\n const s = Math.sqrt(this.m[4] * this.m[4] + this.m[5] * this.m[5]);\n return new Vec2(\n 0,\n Math.atan(this.m[0] * this.m[4] + this.m[1] * this.m[5])\n / (s * s),\n );\n }\n else {\n return new Vec2(0, 0);\n }\n }\n\n invert(): Mat4 {\n const out = [];\n\n const f00 = this.m[10] * this.m[15] - this.m[14] * this.m[11];\n const f01 = this.m[9] * this.m[15] - this.m[13] * this.m[11];\n const f02 = this.m[9] * this.m[14] - this.m[13] * this.m[10];\n const f03 = this.m[8] * this.m[15] - this.m[12] * this.m[11];\n const f04 = this.m[8] * this.m[14] - this.m[12] * this.m[10];\n const f05 = this.m[8] * this.m[13] - this.m[12] * this.m[9];\n const f06 = this.m[6] * this.m[15] - this.m[14] * this.m[7];\n const f07 = this.m[5] * this.m[15] - this.m[13] * this.m[7];\n const f08 = this.m[5] * this.m[14] - this.m[13] * this.m[6];\n const f09 = this.m[4] * this.m[15] - this.m[12] * this.m[7];\n const f10 = this.m[4] * this.m[14] - this.m[12] * this.m[6];\n const f11 = this.m[5] * this.m[15] - this.m[13] * this.m[7];\n const f12 = this.m[4] * this.m[13] - this.m[12] * this.m[5];\n const f13 = this.m[6] * this.m[11] - this.m[10] * this.m[7];\n const f14 = this.m[5] * this.m[11] - this.m[9] * this.m[7];\n const f15 = this.m[5] * this.m[10] - this.m[9] * this.m[6];\n const f16 = this.m[4] * this.m[11] - this.m[8] * this.m[7];\n const f17 = this.m[4] * this.m[10] - this.m[8] * this.m[6];\n const f18 = this.m[4] * this.m[9] - this.m[8] * this.m[5];\n\n out[0] = this.m[5] * f00 - this.m[6] * f01 + this.m[7] * f02;\n out[4] = -(this.m[4] * f00 - this.m[6] * f03 + this.m[7] * f04);\n out[8] = this.m[4] * f01 - this.m[5] * f03 + this.m[7] * f05;\n out[12] = -(this.m[4] * f02 - this.m[5] * f04 + this.m[6] * f05);\n\n out[1] = -(this.m[1] * f00 - this.m[2] * f01 + this.m[3] * f02);\n out[5] = this.m[0] * f00 - this.m[2] * f03 + this.m[3] * f04;\n out[9] = -(this.m[0] * f01 - this.m[1] * f03 + this.m[3] * f05);\n out[13] = this.m[0] * f02 - this.m[1] * f04 + this.m[2] * f05;\n\n out[2] = this.m[1] * f06 - this.m[2] * f07 + this.m[3] * f08;\n out[6] = -(this.m[0] * f06 - this.m[2] * f09 + this.m[3] * f10);\n out[10] = this.m[0] * f11 - this.m[1] * f09 + this.m[3] * f12;\n out[14] = -(this.m[0] * f08 - this.m[1] * f10 + this.m[2] * f12);\n\n out[3] = -(this.m[1] * f13 - this.m[2] * f14 + this.m[3] * f15);\n out[7] = this.m[0] * f13 - this.m[2] * f16 + this.m[3] * f17;\n out[11] = -(this.m[0] * f14 - this.m[1] * f16 + this.m[3] * f18);\n out[15] = this.m[0] * f15 - this.m[1] * f17 + this.m[2] * f18;\n\n const det = this.m[0] * out[0]\n + this.m[1] * out[4]\n + this.m[2] * out[8]\n + this.m[3] * out[12];\n\n for (let i = 0; i < 4; i++) {\n for (let j = 0; j < 4; j++) {\n out[i * 4 + j] *= 1.0 / det;\n }\n }\n\n return new Mat4(out);\n }\n\n clone(): Mat4 {\n return new Mat4([...this.m]);\n }\n\n toString(): string {\n return this.m.toString();\n }\n}\n\nexport function wave(\n lo: number,\n hi: number,\n t: number,\n f = (t: number) => -Math.cos(t),\n): number {\n return lo + (f(t) + 1) / 2 * (hi - lo);\n}\n\n// basic ANSI C LCG\nconst A = 1103515245;\nconst C = 12345;\nconst M = 2147483648;\n\n/**\n * @group Math\n */\nexport class RNG {\n seed: number;\n constructor(seed: number) {\n this.seed = seed;\n }\n gen(): number {\n this.seed = (A * this.seed + C) % M;\n return this.seed / M;\n }\n genNumber(a: number, b: number): number {\n return a + this.gen() * (b - a);\n }\n genVec2(a: Vec2, b: Vec2): Vec2 {\n return new Vec2(\n this.genNumber(a.x, b.x),\n this.genNumber(a.y, b.y),\n );\n }\n genColor(a: Color, b: Color): Color {\n return new Color(\n this.genNumber(a.r, b.r),\n this.genNumber(a.g, b.g),\n this.genNumber(a.b, b.b),\n );\n }\n genAny<T = RNGValue>(...args: [] | [T] | [T, T]): T {\n if (args.length === 0) {\n return this.gen() as T;\n }\n else if (args.length === 1) {\n if (typeof args[0] === \"number\") {\n return this.genNumber(0, args[0]) as T;\n }\n else if (args[0] instanceof Vec2) {\n return this.genVec2(vec2(0, 0), args[0]) as T;\n }\n else if (args[0] instanceof Color) {\n return this.genColor(rgb(0, 0, 0), args[0]) as T;\n }\n }\n else if (args.length === 2) {\n if (typeof args[0] === \"number\" && typeof args[1] === \"number\") {\n return this.genNumber(args[0], args[1]) as T;\n }\n else if (args[0] instanceof Vec2 && args[1] instanceof Vec2) {\n return this.genVec2(args[0], args[1]) as T;\n }\n else if (args[0] instanceof Color && args[1] instanceof Color) {\n return this.genColor(args[0], args[1]) as T;\n }\n }\n\n throw new Error(\"More than 2 arguments not supported\");\n }\n}\n\n// TODO: let user pass seed\nconst defRNG = new RNG(Date.now());\n\nexport function randSeed(seed?: number): number {\n if (seed != null) {\n defRNG.seed = seed;\n }\n return defRNG.seed;\n}\n\nexport function rand<T = number>(...args: [] | [T] | [T, T]) {\n return defRNG.genAny(...args);\n}\n\nexport function randi(...args: [] | [number] | [number, number]) {\n return Math.floor(rand(...(args.length > 0 ? args : [2])));\n}\n\nexport function chance(p: number): boolean {\n return rand() <= p;\n}\n\nexport function shuffle<T>(list: T[]): T[] {\n for (let i = list.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [list[i], list[j]] = [list[j], list[i]];\n }\n return list;\n}\n\nexport function chooseMultiple<T>(list: T[], count: number): T[] {\n return list.length <= count\n ? list.slice()\n : shuffle(list.slice()).slice(0, count);\n}\n\nexport function choose<T>(list: T[]): T {\n return list[randi(list.length)];\n}\n\n// TODO: better name\nexport function testRectRect2(r1: Rect, r2: Rect): boolean {\n return r1.pos.x + r1.width >= r2.pos.x\n && r1.pos.x <= r2.pos.x + r2.width\n && r1.pos.y + r1.height >= r2.pos.y\n && r1.pos.y <= r2.pos.y + r2.height;\n}\n\nexport function testRectRect(r1: Rect, r2: Rect): boolean {\n return r1.pos.x + r1.width > r2.pos.x\n && r1.pos.x < r2.pos.x + r2.width\n && r1.pos.y + r1.height > r2.pos.y\n && r1.pos.y < r2.pos.y + r2.height;\n}\n\n// TODO: better name\nexport function testLineLineT(l1: Line, l2: Line): number | null {\n if (\n (l1.p1.x === l1.p2.x && l1.p1.y === l1.p2.y)\n || (l2.p1.x === l2.p2.x && l2.p1.y === l2.p2.y)\n ) {\n return null;\n }\n\n const denom = (l2.p2.y - l2.p1.y) * (l1.p2.x - l1.p1.x)\n - (l2.p2.x - l2.p1.x) * (l1.p2.y - l1.p1.y);\n\n // parallel\n if (denom === 0) {\n return null;\n }\n\n const ua = ((l2.p2.x - l2.p1.x) * (l1.p1.y - l2.p1.y)\n - (l2.p2.y - l2.p1.y) * (l1.p1.x - l2.p1.x)) / denom;\n const ub = ((l1.p2.x - l1.p1.x) * (l1.p1.y - l2.p1.y)\n - (l1.p2.y - l1.p1.y) * (l1.p1.x - l2.p1.x)) / denom;\n\n // is the intersection on the segments\n if (ua < 0 || ua > 1 || ub < 0 || ub > 1) {\n return null;\n }\n\n return ua;\n}\n\nexport function testLineLine(l1: Line, l2: Line): Vec2 | null {\n const t = testLineLineT(l1, l2);\n if (!t) return null;\n return vec2(\n l1.p1.x + t * (l1.p2.x - l1.p1.x),\n l1.p1.y + t * (l1.p2.y - l1.p1.y),\n );\n}\n\nexport function testRectLine(r: Rect, l: Line): boolean {\n /*if (testRectPoint(r, l.p1) || testRectPoint(r, l.p2)) {\n return true\n }\n const pts = r.points()\n return !!testLineLine(l, new Line(pts[0], pts[1]))\n || !!testLineLine(l, new Line(pts[1], pts[2]))\n || !!testLineLine(l, new Line(pts[2], pts[3]))\n || !!testLineLine(l, new Line(pts[3], pts[0]))*/\n const dir = l.p2.sub(l.p1);\n let tmin = Number.NEGATIVE_INFINITY, tmax = Number.POSITIVE_INFINITY;\n\n if (dir.x != 0.0) {\n const tx1 = (r.pos.x - l.p1.x) / dir.x;\n const tx2 = (r.pos.x + r.width - l.p1.x) / dir.x;\n\n tmin = Math.max(tmin, Math.min(tx1, tx2));\n tmax = Math.min(tmax, Math.max(tx1, tx2));\n }\n\n if (dir.y != 0.0) {\n const ty1 = (r.pos.y - l.p1.y) / dir.y;\n const ty2 = (r.pos.y + r.height - l.p1.y) / dir.y;\n\n tmin = Math.max(tmin, Math.min(ty1, ty2));\n tmax = Math.min(tmax, Math.max(ty1, ty2));\n }\n\n return tmax >= tmin && tmax >= 0 && tmin <= 1;\n}\n\nexport function testRectPoint2(r: Rect, pt: Vec2): boolean {\n return pt.x >= r.pos.x\n && pt.x <= r.pos.x + r.width\n && pt.y >= r.pos.y\n && pt.y <= r.pos.y + r.height;\n}\n\nexport function testRectPoint(r: Rect, pt: Vec2): boolean {\n return pt.x > r.pos.x\n && pt.x < r.pos.x + r.width\n && pt.y > r.pos.y\n && pt.y < r.pos.y + r.height;\n}\n\nexport function testRectCircle(r: Rect, c: Circle): boolean {\n const nx = Math.max(r.pos.x, Math.min(c.center.x, r.pos.x + r.width));\n const ny = Math.max(r.pos.y, Math.min(c.center.y, r.pos.y + r.height));\n const nearestPoint = vec2(nx, ny);\n return nearestPoint.sdist(c.center) <= c.radius * c.radius;\n}\n\nexport function testRectPolygon(r: Rect, p: Polygon): boolean {\n return testPolygonPolygon(p, new Polygon(r.points()));\n}\n\nexport function testLinePoint(l: Line, pt: Vec2): boolean {\n const v1 = pt.sub(l.p1);\n const v2 = l.p2.sub(l.p1);\n\n // Check if sine is 0, in that case lines are parallel.\n // If not parallel, the point cannot lie on the line.\n if (Math.abs(v1.cross(v2)) > Number.EPSILON) {\n return false;\n }\n\n // Scalar projection of v1 on v2\n const t = v1.dot(v2) / v2.dot(v2);\n // Since t is percentual distance of pt from line.p1 on the line,\n // it should be between 0% and 100%\n return t >= 0 && t <= 1;\n}\n\nexport function testLineCircle(l: Line, circle: Circle): boolean {\n const v = l.p2.sub(l.p1);\n const a = v.dot(v);\n const centerToOrigin = l.p1.sub(circle.center);\n const b = 2 * v.dot(centerToOrigin);\n const c = centerToOrigin.dot(centerToOrigin)\n - circle.radius *