diff --git a/README.md b/README.md index 2b4c777f..5b8dc8cc 100644 --- a/README.md +++ b/README.md @@ -126,21 +126,12 @@ And that's it! You're now ready to utilize the Detect-Collisions library in your ## Visual Debugging with Rendering -To facilitate debugging, Detect-Collisions allows you to visually represent the collision bodies. By invoking the `draw()` method and supplying a 2D context of a `` element, you can draw all the bodies within a collision system. +To facilitate debugging, Detect-Collisions allows you to visually represent the collision bodies. By invoking the `draw()` method and supplying a 2D context of a `` element, you can draw all the bodies within a collision system. You can also opt to draw individual bodies. ```ts const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); -context.strokeStyle = "#FFFFFF"; -context.beginPath(); -system.draw(context); -context.stroke(); -``` - -You can also opt to draw individual bodies. - -```ts context.strokeStyle = "#FFFFFF"; context.beginPath(); // draw specific body diff --git a/dist/base-system.d.ts b/dist/base-system.d.ts index 03822b77..efa7750a 100644 --- a/dist/base-system.d.ts +++ b/dist/base-system.d.ts @@ -1,4 +1,15 @@ -import { Body, BodyOptions, ChildrenData, Data, InTest, Leaf, PotentialVector, RBush, TraverseFunction, Vector } from "./model"; +import { + Body, + BodyOptions, + ChildrenData, + Data, + InTest, + Leaf, + PotentialVector, + RBush, + TraverseFunction, + Vector, +} from "./model"; import { Box } from "./bodies/box"; import { Circle } from "./bodies/circle"; import { Ellipse } from "./bodies/ellipse"; @@ -8,66 +19,93 @@ import { Polygon } from "./bodies/polygon"; /** * very base collision system (create, insert, update, draw, remove) */ -export declare class BaseSystem extends RBush implements Data { - data: ChildrenData; - /** - * create point at position with options and add to system - */ - createPoint(position: PotentialVector, options?: BodyOptions): Point; - /** - * create line at position with options and add to system - */ - createLine(start: Vector, end: Vector, options?: BodyOptions): Line; - /** - * create circle at position with options and add to system - */ - createCircle(position: PotentialVector, radius: number, options?: BodyOptions): Circle; - /** - * create box at position with options and add to system - */ - createBox(position: PotentialVector, width: number, height: number, options?: BodyOptions): Box; - /** - * create ellipse at position with options and add to system - */ - createEllipse(position: PotentialVector, radiusX: number, radiusY?: number, step?: number, options?: BodyOptions): Ellipse; - /** - * create polygon at position with options and add to system - */ - createPolygon(position: PotentialVector, points: PotentialVector[], options?: BodyOptions): Polygon; - /** - * re-insert body into collision tree and update its bbox - * every body can be part of only one system - */ - insert(body: TBody): this; - /** - * updates body in collision tree - */ - updateBody(body: TBody): void; - /** - * update all bodies aabb - */ - update(): void; - /** - * draw exact bodies colliders outline - */ - draw(context: CanvasRenderingContext2D): void; - /** - * draw bounding boxes hierarchy outline - */ - drawBVH(context: CanvasRenderingContext2D): void; - /** - * remove body aabb from collision tree - */ - remove(body: TBody, equals?: InTest): this; - /** - * get object potential colliders - * @deprecated because it's slower to use than checkOne() or checkAll() - */ - getPotentials(body: TBody): TBody[]; - /** - * used to find body deep inside data with finder function returning boolean found or not - */ - traverse(traverseFunction: TraverseFunction, { children }?: { - children?: Leaf[]; - }): TBody | undefined; +export declare class BaseSystem + extends RBush + implements Data +{ + data: ChildrenData; + /** + * create point at position with options and add to system + */ + createPoint(position: PotentialVector, options?: BodyOptions): Point; + /** + * create line at position with options and add to system + */ + createLine(start: Vector, end: Vector, options?: BodyOptions): Line; + /** + * create circle at position with options and add to system + */ + createCircle( + position: PotentialVector, + radius: number, + options?: BodyOptions, + ): Circle; + /** + * create box at position with options and add to system + */ + createBox( + position: PotentialVector, + width: number, + height: number, + options?: BodyOptions, + ): Box; + /** + * create ellipse at position with options and add to system + */ + createEllipse( + position: PotentialVector, + radiusX: number, + radiusY?: number, + step?: number, + options?: BodyOptions, + ): Ellipse; + /** + * create polygon at position with options and add to system + */ + createPolygon( + position: PotentialVector, + points: PotentialVector[], + options?: BodyOptions, + ): Polygon; + /** + * re-insert body into collision tree and update its bbox + * every body can be part of only one system + */ + insert(body: TBody): this; + /** + * updates body in collision tree + */ + updateBody(body: TBody): void; + /** + * update all bodies aabb + */ + update(): void; + /** + * draw exact bodies colliders outline + */ + draw(context: CanvasRenderingContext2D): void; + /** + * draw bounding boxes hierarchy outline + */ + drawBVH(context: CanvasRenderingContext2D): void; + /** + * remove body aabb from collision tree + */ + remove(body: TBody, equals?: InTest): this; + /** + * get object potential colliders + * @deprecated because it's slower to use than checkOne() or checkAll() + */ + getPotentials(body: TBody): TBody[]; + /** + * used to find body deep inside data with finder function returning boolean found or not + */ + traverse( + traverseFunction: TraverseFunction, + { + children, + }?: { + children?: Leaf[]; + }, + ): TBody | undefined; } diff --git a/dist/base-system.js b/dist/base-system.js index 725f9302..43c63f91 100644 --- a/dist/base-system.js +++ b/dist/base-system.js @@ -14,141 +14,152 @@ const polygon_1 = require("./bodies/polygon"); * very base collision system (create, insert, update, draw, remove) */ class BaseSystem extends model_1.RBush { - /** - * create point at position with options and add to system - */ - createPoint(position, options) { - const point = new point_1.Point(position, options); - this.insert(point); - return point; + /** + * create point at position with options and add to system + */ + createPoint(position, options) { + const point = new point_1.Point(position, options); + this.insert(point); + return point; + } + /** + * create line at position with options and add to system + */ + createLine(start, end, options) { + const line = new line_1.Line(start, end, options); + this.insert(line); + return line; + } + /** + * create circle at position with options and add to system + */ + createCircle(position, radius, options) { + const circle = new circle_1.Circle(position, radius, options); + this.insert(circle); + return circle; + } + /** + * create box at position with options and add to system + */ + createBox(position, width, height, options) { + const box = new box_1.Box(position, width, height, options); + this.insert(box); + return box; + } + /** + * create ellipse at position with options and add to system + */ + createEllipse(position, radiusX, radiusY = radiusX, step, options) { + const ellipse = new ellipse_1.Ellipse( + position, + radiusX, + radiusY, + step, + options, + ); + this.insert(ellipse); + return ellipse; + } + /** + * create polygon at position with options and add to system + */ + createPolygon(position, points, options) { + const polygon = new polygon_1.Polygon(position, points, options); + this.insert(polygon); + return polygon; + } + /** + * re-insert body into collision tree and update its bbox + * every body can be part of only one system + */ + insert(body) { + body.bbox = body.getAABBAsBBox(); + if (body.system) { + // allow end if body inserted and not moved + if (!(0, utils_1.bodyMoved)(body)) { + return this; + } + // old bounding box *needs* to be removed + body.system.remove(body); } - /** - * create line at position with options and add to system - */ - createLine(start, end, options) { - const line = new line_1.Line(start, end, options); - this.insert(line); - return line; - } - /** - * create circle at position with options and add to system - */ - createCircle(position, radius, options) { - const circle = new circle_1.Circle(position, radius, options); - this.insert(circle); - return circle; - } - /** - * create box at position with options and add to system - */ - createBox(position, width, height, options) { - const box = new box_1.Box(position, width, height, options); - this.insert(box); - return box; - } - /** - * create ellipse at position with options and add to system - */ - createEllipse(position, radiusX, radiusY = radiusX, step, options) { - const ellipse = new ellipse_1.Ellipse(position, radiusX, radiusY, step, options); - this.insert(ellipse); - return ellipse; - } - /** - * create polygon at position with options and add to system - */ - createPolygon(position, points, options) { - const polygon = new polygon_1.Polygon(position, points, options); - this.insert(polygon); - return polygon; - } - /** - * re-insert body into collision tree and update its bbox - * every body can be part of only one system - */ - insert(body) { - body.bbox = body.getAABBAsBBox(); - if (body.system) { - // allow end if body inserted and not moved - if (!(0, utils_1.bodyMoved)(body)) { - return this; - } - // old bounding box *needs* to be removed - body.system.remove(body); - } - // only then we update min, max - body.minX = body.bbox.minX - body.padding; - body.minY = body.bbox.minY - body.padding; - body.maxX = body.bbox.maxX + body.padding; - body.maxY = body.bbox.maxY + body.padding; - // reinsert bounding box to collision tree - return super.insert(body); - } - /** - * updates body in collision tree - */ - updateBody(body) { - body.updateBody(); - } - /** - * update all bodies aabb - */ - update() { - (0, optimized_1.forEach)(this.all(), (body) => { - this.updateBody(body); + // only then we update min, max + body.minX = body.bbox.minX - body.padding; + body.minY = body.bbox.minY - body.padding; + body.maxX = body.bbox.maxX + body.padding; + body.maxY = body.bbox.maxY + body.padding; + // reinsert bounding box to collision tree + return super.insert(body); + } + /** + * updates body in collision tree + */ + updateBody(body) { + body.updateBody(); + } + /** + * update all bodies aabb + */ + update() { + (0, optimized_1.forEach)(this.all(), (body) => { + this.updateBody(body); + }); + } + /** + * draw exact bodies colliders outline + */ + draw(context) { + (0, optimized_1.forEach)(this.all(), (body) => { + body.draw(context); + }); + } + /** + * draw bounding boxes hierarchy outline + */ + drawBVH(context) { + const drawChildren = (body) => { + (0, utils_1.drawBVH)(context, body); + if (body.children) { + (0, optimized_1.forEach)(body.children, drawChildren); + } + }; + (0, optimized_1.forEach)(this.data.children, drawChildren); + } + /** + * remove body aabb from collision tree + */ + remove(body, equals) { + body.system = undefined; + return super.remove(body, equals); + } + /** + * get object potential colliders + * @deprecated because it's slower to use than checkOne() or checkAll() + */ + getPotentials(body) { + // filter here is required as collides with self + return (0, optimized_1.filter)( + this.search(body), + (candidate) => candidate !== body, + ); + } + /** + * used to find body deep inside data with finder function returning boolean found or not + */ + traverse(traverseFunction, { children } = this.data) { + return children === null || children === void 0 + ? void 0 + : children.find((body, index) => { + if (!body) { + return false; + } + if (body.typeGroup && traverseFunction(body, children, index)) { + return true; + } + // if callback returns true, ends forEach + if (body.children) { + this.traverse(traverseFunction, body); + } }); - } - /** - * draw exact bodies colliders outline - */ - draw(context) { - (0, optimized_1.forEach)(this.all(), (body) => { - body.draw(context); - }); - } - /** - * draw bounding boxes hierarchy outline - */ - drawBVH(context) { - const drawChildren = (body) => { - (0, utils_1.drawBVH)(context, body); - if (body.children) { - (0, optimized_1.forEach)(body.children, drawChildren); - } - }; - (0, optimized_1.forEach)(this.data.children, drawChildren); - } - /** - * remove body aabb from collision tree - */ - remove(body, equals) { - body.system = undefined; - return super.remove(body, equals); - } - /** - * get object potential colliders - * @deprecated because it's slower to use than checkOne() or checkAll() - */ - getPotentials(body) { - // filter here is required as collides with self - return (0, optimized_1.filter)(this.search(body), (candidate) => candidate !== body); - } - /** - * used to find body deep inside data with finder function returning boolean found or not - */ - traverse(traverseFunction, { children } = this.data) { - return children === null || children === void 0 ? void 0 : children.find((body, index) => { - if (!body) { - return false; - } - if (body.typeGroup && traverseFunction(body, children, index)) { - return true; - } - // if callback returns true, ends forEach - if (body.children) { - this.traverse(traverseFunction, body); - } - }); - } + } } exports.BaseSystem = BaseSystem; diff --git a/dist/benchmarks/index.js b/dist/benchmarks/index.js index fbf6c1ad..ceab2150 100644 --- a/dist/benchmarks/index.js +++ b/dist/benchmarks/index.js @@ -2,6 +2,16 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.stressBenchmark = exports.insertionBenchmark = void 0; var insertion_bench_1 = require("./insertion.bench"); -Object.defineProperty(exports, "insertionBenchmark", { enumerable: true, get: function () { return insertion_bench_1.insertionBenchmark; } }); +Object.defineProperty(exports, "insertionBenchmark", { + enumerable: true, + get: function () { + return insertion_bench_1.insertionBenchmark; + }, +}); var stress_bench_1 = require("./stress.bench"); -Object.defineProperty(exports, "stressBenchmark", { enumerable: true, get: function () { return stress_bench_1.stressBenchmark; } }); +Object.defineProperty(exports, "stressBenchmark", { + enumerable: true, + get: function () { + return stress_bench_1.stressBenchmark; + }, +}); diff --git a/dist/benchmarks/insertion.bench.js b/dist/benchmarks/insertion.bench.js index 6f682f7b..53ae3231 100644 --- a/dist/benchmarks/insertion.bench.js +++ b/dist/benchmarks/insertion.bench.js @@ -8,94 +8,143 @@ const polygon_js_1 = require("../bodies/polygon.js"); const model_js_1 = require("../model.js"); const system_js_1 = require("../system.js"); const insertionBenchmark = () => { - const benchmark = new tinybench_1.Bench({}); - const nonoverlappingBodies = []; - const nonoverlappingTriangles = []; - const nonoverlappingRectangles = []; - const overlappingBodies = []; - const overlappingTriangles = []; - const overlappingRectangles = []; - const BODY_COUNT = 1000; - for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - nonoverlappingBodies.push(new circle_js_1.Circle(new model_js_1.SATVector(ndx, 0), 0.25)); - overlappingBodies.push(new circle_js_1.Circle(new model_js_1.SATVector(0, 0), 0.25)); - nonoverlappingTriangles.push(new polygon_js_1.Polygon(new model_js_1.SATVector(ndx * 2, 0), [ - new model_js_1.SATVector(0, 0), - new model_js_1.SATVector(0, 1), - new model_js_1.SATVector(1, 0) - ])); - overlappingTriangles.push(new polygon_js_1.Polygon(new model_js_1.SATVector(0, 0), [ - new model_js_1.SATVector(0, 0), - new model_js_1.SATVector(0, 1), - new model_js_1.SATVector(1, 0) - ])); - nonoverlappingRectangles.push(new polygon_js_1.Polygon(new model_js_1.SATVector(0, 0), [ - new model_js_1.SATVector(0, 0), - new model_js_1.SATVector(0, 1), - new model_js_1.SATVector(1, 1), - new model_js_1.SATVector(1, 0) - ])); - overlappingRectangles.push(new polygon_js_1.Polygon(new model_js_1.SATVector(0, 0), [ - new model_js_1.SATVector(0, 0), - new model_js_1.SATVector(0, 1), - new model_js_1.SATVector(1, 1), - new model_js_1.SATVector(1, 0) - ])); - } - benchmark - .add("non overlapping circles", () => { - const uut = new system_js_1.System(BODY_COUNT); - for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(nonoverlappingBodies[ndx]); - } + const benchmark = new tinybench_1.Bench({}); + const nonoverlappingBodies = []; + const nonoverlappingTriangles = []; + const nonoverlappingRectangles = []; + const overlappingBodies = []; + const overlappingTriangles = []; + const overlappingRectangles = []; + const BODY_COUNT = 1000; + for (let ndx = 0; ndx < BODY_COUNT; ndx++) { + nonoverlappingBodies.push( + new circle_js_1.Circle(new model_js_1.SATVector(ndx, 0), 0.25), + ); + overlappingBodies.push( + new circle_js_1.Circle(new model_js_1.SATVector(0, 0), 0.25), + ); + nonoverlappingTriangles.push( + new polygon_js_1.Polygon(new model_js_1.SATVector(ndx * 2, 0), [ + new model_js_1.SATVector(0, 0), + new model_js_1.SATVector(0, 1), + new model_js_1.SATVector(1, 0), + ]), + ); + overlappingTriangles.push( + new polygon_js_1.Polygon(new model_js_1.SATVector(0, 0), [ + new model_js_1.SATVector(0, 0), + new model_js_1.SATVector(0, 1), + new model_js_1.SATVector(1, 0), + ]), + ); + nonoverlappingRectangles.push( + new polygon_js_1.Polygon(new model_js_1.SATVector(0, 0), [ + new model_js_1.SATVector(0, 0), + new model_js_1.SATVector(0, 1), + new model_js_1.SATVector(1, 1), + new model_js_1.SATVector(1, 0), + ]), + ); + overlappingRectangles.push( + new polygon_js_1.Polygon(new model_js_1.SATVector(0, 0), [ + new model_js_1.SATVector(0, 0), + new model_js_1.SATVector(0, 1), + new model_js_1.SATVector(1, 1), + new model_js_1.SATVector(1, 0), + ]), + ); + } + benchmark + .add("non overlapping circles", () => { + const uut = new system_js_1.System(BODY_COUNT); + for (let ndx = 0; ndx < BODY_COUNT; ndx++) { + uut.insert(nonoverlappingBodies[ndx]); + } }) - .add("overlapping circles", () => { - const uut = new system_js_1.System(BODY_COUNT); - for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(overlappingBodies[ndx]); - } + .add("overlapping circles", () => { + const uut = new system_js_1.System(BODY_COUNT); + for (let ndx = 0; ndx < BODY_COUNT; ndx++) { + uut.insert(overlappingBodies[ndx]); + } }) - .add("non-overlapping triangles", () => { - const uut = new system_js_1.System(BODY_COUNT); - for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(nonoverlappingTriangles[ndx]); - } + .add("non-overlapping triangles", () => { + const uut = new system_js_1.System(BODY_COUNT); + for (let ndx = 0; ndx < BODY_COUNT; ndx++) { + uut.insert(nonoverlappingTriangles[ndx]); + } }) - .add("overlapping triangles", () => { - const uut = new system_js_1.System(BODY_COUNT); - for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(overlappingTriangles[ndx]); - } + .add("overlapping triangles", () => { + const uut = new system_js_1.System(BODY_COUNT); + for (let ndx = 0; ndx < BODY_COUNT; ndx++) { + uut.insert(overlappingTriangles[ndx]); + } }) - .add("non-overlapping quad", () => { - const uut = new system_js_1.System(BODY_COUNT); - for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(nonoverlappingRectangles[ndx]); - } + .add("non-overlapping quad", () => { + const uut = new system_js_1.System(BODY_COUNT); + for (let ndx = 0; ndx < BODY_COUNT; ndx++) { + uut.insert(nonoverlappingRectangles[ndx]); + } }) - .add("overlapping quad", () => { - const uut = new system_js_1.System(BODY_COUNT); - for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(overlappingRectangles[ndx]); - } + .add("overlapping quad", () => { + const uut = new system_js_1.System(BODY_COUNT); + for (let ndx = 0; ndx < BODY_COUNT; ndx++) { + uut.insert(overlappingRectangles[ndx]); + } }); - benchmark - .run() - .then(() => { - console.table(benchmark.tasks.map(({ name, result }) => { - var _a, _b, _c, _d, _e; - return ({ - "Task Name": name, - "Average Time (s)": parseFloat(((_a = result === null || result === void 0 ? void 0 : result.mean) !== null && _a !== void 0 ? _a : 0).toFixed(3)), - "Standard Deviation (s)": parseFloat(((_b = result === null || result === void 0 ? void 0 : result.sd) !== null && _b !== void 0 ? _b : 0).toFixed(3)), - hz: parseFloat(((_c = result === null || result === void 0 ? void 0 : result.hz) !== null && _c !== void 0 ? _c : 0).toFixed(3)), - "p99 (s)": parseFloat(((_d = result === null || result === void 0 ? void 0 : result.p99) !== null && _d !== void 0 ? _d : 0).toFixed(3)), - "p995 (s)": parseFloat(((_e = result === null || result === void 0 ? void 0 : result.p995) !== null && _e !== void 0 ? _e : 0).toFixed(3)) - }); - })); + benchmark + .run() + .then(() => { + console.table( + benchmark.tasks.map(({ name, result }) => { + var _a, _b, _c, _d, _e; + return { + "Task Name": name, + "Average Time (s)": parseFloat( + ((_a = + result === null || result === void 0 ? void 0 : result.mean) !== + null && _a !== void 0 + ? _a + : 0 + ).toFixed(3), + ), + "Standard Deviation (s)": parseFloat( + ((_b = + result === null || result === void 0 ? void 0 : result.sd) !== + null && _b !== void 0 + ? _b + : 0 + ).toFixed(3), + ), + hz: parseFloat( + ((_c = + result === null || result === void 0 ? void 0 : result.hz) !== + null && _c !== void 0 + ? _c + : 0 + ).toFixed(3), + ), + "p99 (s)": parseFloat( + ((_d = + result === null || result === void 0 ? void 0 : result.p99) !== + null && _d !== void 0 + ? _d + : 0 + ).toFixed(3), + ), + "p995 (s)": parseFloat( + ((_e = + result === null || result === void 0 ? void 0 : result.p995) !== + null && _e !== void 0 + ? _e + : 0 + ).toFixed(3), + ), + }; + }), + ); }) - .catch(err => { - console.warn(err.message || err); + .catch((err) => { + console.warn(err.message || err); }); }; exports.insertionBenchmark = insertionBenchmark; diff --git a/dist/benchmarks/stress.bench.js b/dist/benchmarks/stress.bench.js index 393185cd..b6b9b41f 100644 --- a/dist/benchmarks/stress.bench.js +++ b/dist/benchmarks/stress.bench.js @@ -1,63 +1,115 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if ( + !desc || + ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k]; + }, + }; + } + Object.defineProperty(o, k2, desc); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) + __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + }; +var __awaiter = + (this && this.__awaiter) || + function (thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P + ? value + : new P(function (resolve) { + resolve(value); + }); + } return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done + ? resolve(result.value) + : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); }); -}; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.stressBenchmark = void 0; /* tslint:disable:no-implicit-dependencies variable-name no-any */ const tinybench_1 = require("tinybench"); -const stressBenchmark = () => __awaiter(void 0, void 0, void 0, function* () { - const { default: Stress } = yield Promise.resolve().then(() => __importStar(require("../demo/stress.js"))); +const stressBenchmark = () => + __awaiter(void 0, void 0, void 0, function* () { + const { default: Stress } = yield Promise.resolve().then(() => + __importStar(require("../demo/stress.js")), + ); let stressTest; const benchmark = new tinybench_1.Bench({ - time: 1000, - warmupIterations: 0, - setup: ({ opts }) => { - stressTest = new Stress(opts.items); - }, - teardown: () => { - stressTest.physics.clear(); - } + time: 1000, + warmupIterations: 0, + setup: ({ opts }) => { + stressTest = new Stress(opts.items); + }, + teardown: () => { + stressTest.physics.clear(); + }, }); const recursiveAddTest = (items) => { - benchmark.add(`stress test, items=${items}`, () => { - stressTest.update(); - }, { items }); - if (items < 10000) { - recursiveAddTest(items + 1000); - } + benchmark.add( + `stress test, items=${items}`, + () => { + stressTest.update(); + }, + { items }, + ); + if (items < 10000) { + recursiveAddTest(items + 1000); + } }; recursiveAddTest(1000); yield benchmark.run(); console.table(benchmark.table()); -}); + }); exports.stressBenchmark = stressBenchmark; diff --git a/dist/bodies/box.d.ts b/dist/bodies/box.d.ts index 4468eaa1..946c25f0 100644 --- a/dist/bodies/box.d.ts +++ b/dist/bodies/box.d.ts @@ -4,53 +4,58 @@ import { Polygon } from "./polygon"; * collider - box */ export declare class Box extends Polygon { - /** - * type of body - */ - readonly type: BodyType.Box | BodyType.Point; - /** - * faster than type - */ - readonly typeGroup: BodyGroup.Box | BodyGroup.Point; - /** - * boxes are convex - */ - readonly isConvex = true; - /** - * inner width - */ - protected _width: number; - /** - * inner height - */ - protected _height: number; - /** - * collider - box - */ - constructor(position: PotentialVector, width: number, height: number, options?: BodyOptions); - /** - * get box width - */ - get width(): number; - /** - * set box width, update points - */ - set width(width: number); - /** - * get box height - */ - get height(): number; - /** - * set box height, update points - */ - set height(height: number); - /** - * after setting width/height update translate - * see https://github.com/Prozi/detect-collisions/issues/70 - */ - protected afterUpdateSize(): void; - /** - * do not attempt to use Polygon.updateIsConvex() - */ - protected updateIsConvex(): void; + /** + * type of body + */ + readonly type: BodyType.Box | BodyType.Point; + /** + * faster than type + */ + readonly typeGroup: BodyGroup.Box | BodyGroup.Point; + /** + * boxes are convex + */ + readonly isConvex = true; + /** + * inner width + */ + protected _width: number; + /** + * inner height + */ + protected _height: number; + /** + * collider - box + */ + constructor( + position: PotentialVector, + width: number, + height: number, + options?: BodyOptions, + ); + /** + * get box width + */ + get width(): number; + /** + * set box width, update points + */ + set width(width: number); + /** + * get box height + */ + get height(): number; + /** + * set box height, update points + */ + set height(height: number); + /** + * after setting width/height update translate + * see https://github.com/Prozi/detect-collisions/issues/70 + */ + protected afterUpdateSize(): void; + /** + * do not attempt to use Polygon.updateIsConvex() + */ + protected updateIsConvex(): void; } diff --git a/dist/bodies/box.js b/dist/bodies/box.js index b00d2de2..5061440a 100644 --- a/dist/bodies/box.js +++ b/dist/bodies/box.js @@ -8,70 +8,70 @@ const utils_1 = require("../utils"); * collider - box */ class Box extends polygon_1.Polygon { + /** + * collider - box + */ + constructor(position, width, height, options) { + super(position, (0, utils_1.createBox)(width, height), options); /** - * collider - box + * type of body */ - constructor(position, width, height, options) { - super(position, (0, utils_1.createBox)(width, height), options); - /** - * type of body - */ - this.type = model_1.BodyType.Box; - /** - * faster than type - */ - this.typeGroup = model_1.BodyGroup.Box; - /** - * boxes are convex - */ - this.isConvex = true; - this._width = width; - this._height = height; - } + this.type = model_1.BodyType.Box; /** - * get box width + * faster than type */ - get width() { - return this._width; - } + this.typeGroup = model_1.BodyGroup.Box; /** - * set box width, update points + * boxes are convex */ - set width(width) { - this._width = width; - this.afterUpdateSize(); + this.isConvex = true; + this._width = width; + this._height = height; + } + /** + * get box width + */ + get width() { + return this._width; + } + /** + * set box width, update points + */ + set width(width) { + this._width = width; + this.afterUpdateSize(); + } + /** + * get box height + */ + get height() { + return this._height; + } + /** + * set box height, update points + */ + set height(height) { + this._height = height; + this.afterUpdateSize(); + } + /** + * after setting width/height update translate + * see https://github.com/Prozi/detect-collisions/issues/70 + */ + afterUpdateSize() { + if (this.isCentered) { + this.retranslate(false); } - /** - * get box height - */ - get height() { - return this._height; - } - /** - * set box height, update points - */ - set height(height) { - this._height = height; - this.afterUpdateSize(); - } - /** - * after setting width/height update translate - * see https://github.com/Prozi/detect-collisions/issues/70 - */ - afterUpdateSize() { - if (this.isCentered) { - this.retranslate(false); - } - this.setPoints((0, utils_1.createBox)(this._width, this._height)); - if (this.isCentered) { - this.retranslate(); - } - } - /** - * do not attempt to use Polygon.updateIsConvex() - */ - updateIsConvex() { - return; + this.setPoints((0, utils_1.createBox)(this._width, this._height)); + if (this.isCentered) { + this.retranslate(); } + } + /** + * do not attempt to use Polygon.updateIsConvex() + */ + updateIsConvex() { + return; + } } exports.Box = Box; diff --git a/dist/bodies/circle.d.ts b/dist/bodies/circle.d.ts index 7d20ad88..5513b2ff 100644 --- a/dist/bodies/circle.d.ts +++ b/dist/bodies/circle.d.ts @@ -1,166 +1,175 @@ -import { BBox, BodyGroup, BodyOptions, BodyProps, BodyType, PotentialVector, SATVector, Vector } from "../model"; +import { + BBox, + BodyGroup, + BodyOptions, + BodyProps, + BodyType, + PotentialVector, + SATVector, + Vector, +} from "../model"; import { Circle as SATCircle } from "sat"; import { System } from "../system"; /** * collider - circle */ export declare class Circle extends SATCircle implements BBox, BodyProps { - /** - * minimum x bound of body - */ - minX: number; - /** - * maximum x bound of body - */ - maxX: number; - /** - * minimum y bound of body - */ - minY: number; - /** - * maximum y bound of body - */ - maxY: number; - /** - * bounding box cache, without padding - */ - bbox: BBox; - /** - * offset - */ - offset: SATVector; - /** - * offset copy without angle applied - */ - offsetCopy: Vector; - /** - * bodies are not reinserted during update if their bbox didnt move outside bbox + padding - */ - padding: number; - /** - * for compatibility reasons circle has angle - */ - angle: number; - /** - * static bodies don't move but they collide - */ - isStatic: boolean; - /** - * trigger bodies move but are like ghosts - */ - isTrigger: boolean; - /** - * reference to collision system - */ - system?: System; - /** - * was the polygon modified and needs update in the next checkCollision - */ - dirty: boolean; - readonly isConvex = true; - /** - * circle type - */ - readonly type: BodyType.Circle; - /** - * faster than type - */ - readonly typeGroup: BodyGroup.Circle; - /** - * always centered - */ - readonly isCentered = true; - /** - * group for collision filtering - */ - protected _group: number; - /** - * saved initial radius - internal - */ - protected readonly unscaledRadius: number; - /** - * collider - circle - */ - constructor(position: PotentialVector, radius: number, options?: BodyOptions); - /** - * get this.pos.x - */ - get x(): number; - /** - * updating this.pos.x by this.x = x updates AABB - */ - set x(x: number); - /** - * get this.pos.y - */ - get y(): number; - /** - * updating this.pos.y by this.y = y updates AABB - */ - set y(y: number); - /** - * allow get scale - */ - get scale(): number; - /** - * shorthand for setScale() - */ - set scale(scale: number); - /** - * scaleX = scale in case of Circles - */ - get scaleX(): number; - /** - * scaleY = scale in case of Circles - */ - get scaleY(): number; - /** - * group for collision filtering - */ - get group(): number; - set group(group: number); - /** - * update position BY MOVING FORWARD IN ANGLE DIRECTION - */ - move(speed?: number, updateNow?: boolean): Circle; - /** - * update position BY TELEPORTING - */ - setPosition(x: number, y: number, updateNow?: boolean): Circle; - /** - * update scale - */ - setScale(scaleX: number, _scaleY?: number, updateNow?: boolean): Circle; - /** - * set rotation - */ - setAngle(angle: number, updateNow?: boolean): Circle; - /** - * set offset from center - */ - setOffset(offset: Vector, updateNow?: boolean): Circle; - /** - * get body bounding box, without padding - */ - getAABBAsBBox(): BBox; - /** - * Draws collider on a CanvasRenderingContext2D's current path - */ - draw(context: CanvasRenderingContext2D): void; - /** - * Draws Bounding Box on canvas context - */ - drawBVH(context: CanvasRenderingContext2D): void; - /** - * inner function for after position change update aabb in system - */ - updateBody(updateNow?: boolean): void; - /** - * update instantly or mark as dirty - */ - protected markAsDirty(updateNow?: boolean): void; - /** - * internal for getting offset with applied angle - */ - protected getOffsetWithAngle(): Vector; + /** + * minimum x bound of body + */ + minX: number; + /** + * maximum x bound of body + */ + maxX: number; + /** + * minimum y bound of body + */ + minY: number; + /** + * maximum y bound of body + */ + maxY: number; + /** + * bounding box cache, without padding + */ + bbox: BBox; + /** + * offset + */ + offset: SATVector; + /** + * offset copy without angle applied + */ + offsetCopy: Vector; + /** + * bodies are not reinserted during update if their bbox didnt move outside bbox + padding + */ + padding: number; + /** + * for compatibility reasons circle has angle + */ + angle: number; + /** + * static bodies don't move but they collide + */ + isStatic: boolean; + /** + * trigger bodies move but are like ghosts + */ + isTrigger: boolean; + /** + * reference to collision system + */ + system?: System; + /** + * was the polygon modified and needs update in the next checkCollision + */ + dirty: boolean; + readonly isConvex = true; + /** + * circle type + */ + readonly type: BodyType.Circle; + /** + * faster than type + */ + readonly typeGroup: BodyGroup.Circle; + /** + * always centered + */ + readonly isCentered = true; + /** + * group for collision filtering + */ + protected _group: number; + /** + * saved initial radius - internal + */ + protected readonly unscaledRadius: number; + /** + * collider - circle + */ + constructor(position: PotentialVector, radius: number, options?: BodyOptions); + /** + * get this.pos.x + */ + get x(): number; + /** + * updating this.pos.x by this.x = x updates AABB + */ + set x(x: number); + /** + * get this.pos.y + */ + get y(): number; + /** + * updating this.pos.y by this.y = y updates AABB + */ + set y(y: number); + /** + * allow get scale + */ + get scale(): number; + /** + * shorthand for setScale() + */ + set scale(scale: number); + /** + * scaleX = scale in case of Circles + */ + get scaleX(): number; + /** + * scaleY = scale in case of Circles + */ + get scaleY(): number; + /** + * group for collision filtering + */ + get group(): number; + set group(group: number); + /** + * update position BY MOVING FORWARD IN ANGLE DIRECTION + */ + move(speed?: number, updateNow?: boolean): Circle; + /** + * update position BY TELEPORTING + */ + setPosition(x: number, y: number, updateNow?: boolean): Circle; + /** + * update scale + */ + setScale(scaleX: number, _scaleY?: number, updateNow?: boolean): Circle; + /** + * set rotation + */ + setAngle(angle: number, updateNow?: boolean): Circle; + /** + * set offset from center + */ + setOffset(offset: Vector, updateNow?: boolean): Circle; + /** + * get body bounding box, without padding + */ + getAABBAsBBox(): BBox; + /** + * Draws collider on a CanvasRenderingContext2D's current path + */ + draw(context: CanvasRenderingContext2D): void; + /** + * Draws Bounding Box on canvas context + */ + drawBVH(context: CanvasRenderingContext2D): void; + /** + * inner function for after position change update aabb in system + */ + updateBody(updateNow?: boolean): void; + /** + * update instantly or mark as dirty + */ + protected markAsDirty(updateNow?: boolean): void; + /** + * internal for getting offset with applied angle + */ + protected getOffsetWithAngle(): Vector; } diff --git a/dist/bodies/circle.js b/dist/bodies/circle.js index e58e7143..8f028cbc 100644 --- a/dist/bodies/circle.js +++ b/dist/bodies/circle.js @@ -8,220 +8,218 @@ const sat_1 = require("sat"); * collider - circle */ class Circle extends sat_1.Circle { - /** - * collider - circle - */ - constructor(position, radius, options) { - super((0, utils_1.ensureVectorPoint)(position), radius); - /** - * offset copy without angle applied - */ - this.offsetCopy = { x: 0, y: 0 }; - /** - * was the polygon modified and needs update in the next checkCollision - */ - this.dirty = false; - /* - * circles are convex - */ - this.isConvex = true; - /** - * circle type - */ - this.type = model_1.BodyType.Circle; - /** - * faster than type - */ - this.typeGroup = model_1.BodyGroup.Circle; - /** - * always centered - */ - this.isCentered = true; - (0, utils_1.extendBody)(this, options); - this.unscaledRadius = radius; - } - /** - * get this.pos.x - */ - get x() { - return this.pos.x; - } - /** - * updating this.pos.x by this.x = x updates AABB - */ - set x(x) { - this.pos.x = x; - this.markAsDirty(); - } - /** - * get this.pos.y - */ - get y() { - return this.pos.y; - } - /** - * updating this.pos.y by this.y = y updates AABB - */ - set y(y) { - this.pos.y = y; - this.markAsDirty(); - } - /** - * allow get scale - */ - get scale() { - return this.r / this.unscaledRadius; - } - /** - * shorthand for setScale() - */ - set scale(scale) { - this.setScale(scale); - } - /** - * scaleX = scale in case of Circles - */ - get scaleX() { - return this.scale; - } - /** - * scaleY = scale in case of Circles - */ - get scaleY() { - return this.scale; - } - /** - * group for collision filtering - */ - get group() { - return this._group; - } - set group(group) { - this._group = (0, utils_1.getGroup)(group); - } - /** - * update position BY MOVING FORWARD IN ANGLE DIRECTION - */ - move(speed = 1, updateNow = true) { - (0, utils_1.move)(this, speed, updateNow); - return this; - } - /** - * update position BY TELEPORTING - */ - setPosition(x, y, updateNow = true) { - this.pos.x = x; - this.pos.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * update scale - */ - setScale(scaleX, _scaleY = scaleX, updateNow = true) { - this.r = this.unscaledRadius * Math.abs(scaleX); - this.markAsDirty(updateNow); - return this; - } - /** - * set rotation - */ - setAngle(angle, updateNow = true) { - this.angle = angle; - const { x, y } = this.getOffsetWithAngle(); - this.offset.x = x; - this.offset.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * set offset from center - */ - setOffset(offset, updateNow = true) { - this.offsetCopy.x = offset.x; - this.offsetCopy.y = offset.y; - const { x, y } = this.getOffsetWithAngle(); - this.offset.x = x; - this.offset.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * get body bounding box, without padding - */ - getAABBAsBBox() { - const x = this.pos.x + this.offset.x; - const y = this.pos.y + this.offset.y; - return { - minX: x - this.r, - maxX: x + this.r, - minY: y - this.r, - maxY: y + this.r - }; - } - /** - * Draws collider on a CanvasRenderingContext2D's current path - */ - draw(context) { - const x = this.pos.x + this.offset.x; - const y = this.pos.y + this.offset.y; - const r = Math.abs(this.r); - if (this.isTrigger) { - const max = Math.max(8, this.r); - for (let i = 0; i < max; i++) { - const arc = (i / max) * 2 * Math.PI; - const arcPrev = ((i - 1) / max) * 2 * Math.PI; - const fromX = x + Math.cos(arcPrev) * this.r; - const fromY = y + Math.sin(arcPrev) * this.r; - const toX = x + Math.cos(arc) * this.r; - const toY = y + Math.sin(arc) * this.r; - (0, utils_1.dashLineTo)(context, fromX, fromY, toX, toY); - } - } - else { - context.moveTo(x + r, y); - context.arc(x, y, r, 0, Math.PI * 2); - } - } - /** - * Draws Bounding Box on canvas context - */ - drawBVH(context) { - (0, utils_1.drawBVH)(context, this); - } - /** - * inner function for after position change update aabb in system - */ - updateBody(updateNow = this.dirty) { - var _a; - if (updateNow) { - (_a = this.system) === null || _a === void 0 ? void 0 : _a.insert(this); - this.dirty = false; - } - } - /** - * update instantly or mark as dirty - */ - markAsDirty(updateNow = false) { - if (updateNow) { - this.updateBody(true); - } - else { - this.dirty = true; - } - } - /** - * internal for getting offset with applied angle - */ - getOffsetWithAngle() { - if ((!this.offsetCopy.x && !this.offsetCopy.y) || !this.angle) { - return this.offsetCopy; - } - const sin = Math.sin(this.angle); - const cos = Math.cos(this.angle); - const x = this.offsetCopy.x * cos - this.offsetCopy.y * sin; - const y = this.offsetCopy.x * sin + this.offsetCopy.y * cos; - return { x, y }; - } + /** + * collider - circle + */ + constructor(position, radius, options) { + super((0, utils_1.ensureVectorPoint)(position), radius); + /** + * offset copy without angle applied + */ + this.offsetCopy = { x: 0, y: 0 }; + /** + * was the polygon modified and needs update in the next checkCollision + */ + this.dirty = false; + /* + * circles are convex + */ + this.isConvex = true; + /** + * circle type + */ + this.type = model_1.BodyType.Circle; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Circle; + /** + * always centered + */ + this.isCentered = true; + (0, utils_1.extendBody)(this, options); + this.unscaledRadius = radius; + } + /** + * get this.pos.x + */ + get x() { + return this.pos.x; + } + /** + * updating this.pos.x by this.x = x updates AABB + */ + set x(x) { + this.pos.x = x; + this.markAsDirty(); + } + /** + * get this.pos.y + */ + get y() { + return this.pos.y; + } + /** + * updating this.pos.y by this.y = y updates AABB + */ + set y(y) { + this.pos.y = y; + this.markAsDirty(); + } + /** + * allow get scale + */ + get scale() { + return this.r / this.unscaledRadius; + } + /** + * shorthand for setScale() + */ + set scale(scale) { + this.setScale(scale); + } + /** + * scaleX = scale in case of Circles + */ + get scaleX() { + return this.scale; + } + /** + * scaleY = scale in case of Circles + */ + get scaleY() { + return this.scale; + } + /** + * group for collision filtering + */ + get group() { + return this._group; + } + set group(group) { + this._group = (0, utils_1.getGroup)(group); + } + /** + * update position BY MOVING FORWARD IN ANGLE DIRECTION + */ + move(speed = 1, updateNow = true) { + (0, utils_1.move)(this, speed, updateNow); + return this; + } + /** + * update position BY TELEPORTING + */ + setPosition(x, y, updateNow = true) { + this.pos.x = x; + this.pos.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * update scale + */ + setScale(scaleX, _scaleY = scaleX, updateNow = true) { + this.r = this.unscaledRadius * Math.abs(scaleX); + this.markAsDirty(updateNow); + return this; + } + /** + * set rotation + */ + setAngle(angle, updateNow = true) { + this.angle = angle; + const { x, y } = this.getOffsetWithAngle(); + this.offset.x = x; + this.offset.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * set offset from center + */ + setOffset(offset, updateNow = true) { + this.offsetCopy.x = offset.x; + this.offsetCopy.y = offset.y; + const { x, y } = this.getOffsetWithAngle(); + this.offset.x = x; + this.offset.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * get body bounding box, without padding + */ + getAABBAsBBox() { + const x = this.pos.x + this.offset.x; + const y = this.pos.y + this.offset.y; + return { + minX: x - this.r, + maxX: x + this.r, + minY: y - this.r, + maxY: y + this.r, + }; + } + /** + * Draws collider on a CanvasRenderingContext2D's current path + */ + draw(context) { + const x = this.pos.x + this.offset.x; + const y = this.pos.y + this.offset.y; + const r = Math.abs(this.r); + if (this.isTrigger) { + const max = Math.max(8, this.r); + for (let i = 0; i < max; i++) { + const arc = (i / max) * 2 * Math.PI; + const arcPrev = ((i - 1) / max) * 2 * Math.PI; + const fromX = x + Math.cos(arcPrev) * this.r; + const fromY = y + Math.sin(arcPrev) * this.r; + const toX = x + Math.cos(arc) * this.r; + const toY = y + Math.sin(arc) * this.r; + (0, utils_1.dashLineTo)(context, fromX, fromY, toX, toY); + } + } else { + context.moveTo(x + r, y); + context.arc(x, y, r, 0, Math.PI * 2); + } + } + /** + * Draws Bounding Box on canvas context + */ + drawBVH(context) { + (0, utils_1.drawBVH)(context, this); + } + /** + * inner function for after position change update aabb in system + */ + updateBody(updateNow = this.dirty) { + var _a; + if (updateNow) { + (_a = this.system) === null || _a === void 0 ? void 0 : _a.insert(this); + this.dirty = false; + } + } + /** + * update instantly or mark as dirty + */ + markAsDirty(updateNow = false) { + if (updateNow) { + this.updateBody(true); + } else { + this.dirty = true; + } + } + /** + * internal for getting offset with applied angle + */ + getOffsetWithAngle() { + if ((!this.offsetCopy.x && !this.offsetCopy.y) || !this.angle) { + return this.offsetCopy; + } + const sin = Math.sin(this.angle); + const cos = Math.cos(this.angle); + const x = this.offsetCopy.x * cos - this.offsetCopy.y * sin; + const y = this.offsetCopy.x * sin + this.offsetCopy.y * cos; + return { x, y }; + } } exports.Circle = Circle; diff --git a/dist/bodies/ellipse.d.ts b/dist/bodies/ellipse.d.ts index 779cb9b8..c42a30e0 100644 --- a/dist/bodies/ellipse.d.ts +++ b/dist/bodies/ellipse.d.ts @@ -4,66 +4,72 @@ import { Polygon } from "./polygon"; * collider - ellipse */ export declare class Ellipse extends Polygon { - /** - * ellipse type - */ - readonly type: BodyType.Ellipse; - /** - * faster than type - */ - readonly typeGroup: BodyGroup.Ellipse; - /** - * ellipses are convex - */ - readonly isConvex = true; - /** - * inner initial params save - */ - protected _radiusX: number; - protected _radiusY: number; - protected _step: number; - /** - * collider - ellipse - */ - constructor(position: PotentialVector, radiusX: number, radiusY?: number, step?: number, options?: BodyOptions); - /** - * flag to set is body centered - */ - set isCentered(_isCentered: boolean); - /** - * is body centered? - */ - get isCentered(): boolean; - /** - * get ellipse step number - */ - get step(): number; - /** - * set ellipse step number - */ - set step(step: number); - /** - * get ellipse radiusX - */ - get radiusX(): number; - /** - * set ellipse radiusX, update points - */ - set radiusX(radiusX: number); - /** - * get ellipse radiusY - */ - get radiusY(): number; - /** - * set ellipse radiusY, update points - */ - set radiusY(radiusY: number); - /** - * do not attempt to use Polygon.center() - */ - center(): void; - /** - * do not attempt to use Polygon.updateIsConvex() - */ - protected updateIsConvex(): void; + /** + * ellipse type + */ + readonly type: BodyType.Ellipse; + /** + * faster than type + */ + readonly typeGroup: BodyGroup.Ellipse; + /** + * ellipses are convex + */ + readonly isConvex = true; + /** + * inner initial params save + */ + protected _radiusX: number; + protected _radiusY: number; + protected _step: number; + /** + * collider - ellipse + */ + constructor( + position: PotentialVector, + radiusX: number, + radiusY?: number, + step?: number, + options?: BodyOptions, + ); + /** + * flag to set is body centered + */ + set isCentered(_isCentered: boolean); + /** + * is body centered? + */ + get isCentered(): boolean; + /** + * get ellipse step number + */ + get step(): number; + /** + * set ellipse step number + */ + set step(step: number); + /** + * get ellipse radiusX + */ + get radiusX(): number; + /** + * set ellipse radiusX, update points + */ + set radiusX(radiusX: number); + /** + * get ellipse radiusY + */ + get radiusY(): number; + /** + * set ellipse radiusY, update points + */ + set radiusY(radiusY: number); + /** + * do not attempt to use Polygon.center() + */ + center(): void; + /** + * do not attempt to use Polygon.updateIsConvex() + */ + protected updateIsConvex(): void; } diff --git a/dist/bodies/ellipse.js b/dist/bodies/ellipse.js index 81c473da..1b944f51 100644 --- a/dist/bodies/ellipse.js +++ b/dist/bodies/ellipse.js @@ -8,87 +8,103 @@ const utils_1 = require("../utils"); * collider - ellipse */ class Ellipse extends polygon_1.Polygon { + /** + * collider - ellipse + */ + constructor( + position, + radiusX, + radiusY = radiusX, + step = (radiusX + radiusY) / Math.PI, + options, + ) { + super( + position, + (0, utils_1.createEllipse)(radiusX, radiusY, step), + options, + ); /** - * collider - ellipse + * ellipse type */ - constructor(position, radiusX, radiusY = radiusX, step = (radiusX + radiusY) / Math.PI, options) { - super(position, (0, utils_1.createEllipse)(radiusX, radiusY, step), options); - /** - * ellipse type - */ - this.type = model_1.BodyType.Ellipse; - /** - * faster than type - */ - this.typeGroup = model_1.BodyGroup.Ellipse; - /** - * ellipses are convex - */ - this.isConvex = true; - this._radiusX = radiusX; - this._radiusY = radiusY; - this._step = step; - } + this.type = model_1.BodyType.Ellipse; /** - * flag to set is body centered + * faster than type */ - set isCentered(_isCentered) { } + this.typeGroup = model_1.BodyGroup.Ellipse; /** - * is body centered? + * ellipses are convex */ - get isCentered() { - return true; - } - /** - * get ellipse step number - */ - get step() { - return this._step; - } - /** - * set ellipse step number - */ - set step(step) { - this._step = step; - this.setPoints((0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step)); - } - /** - * get ellipse radiusX - */ - get radiusX() { - return this._radiusX; - } - /** - * set ellipse radiusX, update points - */ - set radiusX(radiusX) { - this._radiusX = radiusX; - this.setPoints((0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step)); - } - /** - * get ellipse radiusY - */ - get radiusY() { - return this._radiusY; - } - /** - * set ellipse radiusY, update points - */ - set radiusY(radiusY) { - this._radiusY = radiusY; - this.setPoints((0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step)); - } - /** - * do not attempt to use Polygon.center() - */ - center() { - return; - } - /** - * do not attempt to use Polygon.updateIsConvex() - */ - updateIsConvex() { - return; - } + this.isConvex = true; + this._radiusX = radiusX; + this._radiusY = radiusY; + this._step = step; + } + /** + * flag to set is body centered + */ + set isCentered(_isCentered) {} + /** + * is body centered? + */ + get isCentered() { + return true; + } + /** + * get ellipse step number + */ + get step() { + return this._step; + } + /** + * set ellipse step number + */ + set step(step) { + this._step = step; + this.setPoints( + (0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step), + ); + } + /** + * get ellipse radiusX + */ + get radiusX() { + return this._radiusX; + } + /** + * set ellipse radiusX, update points + */ + set radiusX(radiusX) { + this._radiusX = radiusX; + this.setPoints( + (0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step), + ); + } + /** + * get ellipse radiusY + */ + get radiusY() { + return this._radiusY; + } + /** + * set ellipse radiusY, update points + */ + set radiusY(radiusY) { + this._radiusY = radiusY; + this.setPoints( + (0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step), + ); + } + /** + * do not attempt to use Polygon.center() + */ + center() { + return; + } + /** + * do not attempt to use Polygon.updateIsConvex() + */ + updateIsConvex() { + return; + } } exports.Ellipse = Ellipse; diff --git a/dist/bodies/line.d.ts b/dist/bodies/line.d.ts index 676169e5..0c48d925 100644 --- a/dist/bodies/line.d.ts +++ b/dist/bodies/line.d.ts @@ -5,29 +5,29 @@ import { Vector as SATVector } from "sat"; * collider - line */ export declare class Line extends Polygon { - /** - * line type - */ - readonly type: BodyType.Line; - /** - * faster than type - */ - readonly typeGroup: BodyGroup.Line; - /** - * line is convex - */ - readonly isConvex = true; - /** - * collider - line from start to end - */ - constructor(start: Vector, end: Vector, options?: BodyOptions); - get start(): Vector; - set start({ x, y }: Vector); - get end(): Vector; - set end({ x, y }: Vector); - getCentroid(): SATVector; - /** - * do not attempt to use Polygon.updateIsConvex() - */ - protected updateIsConvex(): void; + /** + * line type + */ + readonly type: BodyType.Line; + /** + * faster than type + */ + readonly typeGroup: BodyGroup.Line; + /** + * line is convex + */ + readonly isConvex = true; + /** + * collider - line from start to end + */ + constructor(start: Vector, end: Vector, options?: BodyOptions); + get start(): Vector; + set start({ x, y }: Vector); + get end(): Vector; + set end({ x, y }: Vector); + getCentroid(): SATVector; + /** + * do not attempt to use Polygon.updateIsConvex() + */ + protected updateIsConvex(): void; } diff --git a/dist/bodies/line.js b/dist/bodies/line.js index c1d1eac3..3e2d28d2 100644 --- a/dist/bodies/line.js +++ b/dist/bodies/line.js @@ -8,60 +8,67 @@ const sat_1 = require("sat"); * collider - line */ class Line extends polygon_1.Polygon { + /** + * collider - line from start to end + */ + constructor(start, end, options) { + super( + start, + [ + { x: 0, y: 0 }, + { x: end.x - start.x, y: end.y - start.y }, + ], + options, + ); /** - * collider - line from start to end + * line type */ - constructor(start, end, options) { - super(start, [ - { x: 0, y: 0 }, - { x: end.x - start.x, y: end.y - start.y } - ], options); - /** - * line type - */ - this.type = model_1.BodyType.Line; - /** - * faster than type - */ - this.typeGroup = model_1.BodyGroup.Line; - /** - * line is convex - */ - this.isConvex = true; - if (this.calcPoints.length === 1 || !end) { - console.error({ start, end }); - throw new Error("No end point for line provided"); - } - } - get start() { - return { - x: this.x + this.calcPoints[0].x, - y: this.y + this.calcPoints[0].y - }; - } - set start({ x, y }) { - this.x = x; - this.y = y; - } - get end() { - return { - x: this.x + this.calcPoints[1].x, - y: this.y + this.calcPoints[1].y - }; - } - set end({ x, y }) { - this.points[1].x = x - this.start.x; - this.points[1].y = y - this.start.y; - this.setPoints(this.points); - } - getCentroid() { - return new sat_1.Vector((this.end.x - this.start.x) / 2, (this.end.y - this.start.y) / 2); - } + this.type = model_1.BodyType.Line; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Line; /** - * do not attempt to use Polygon.updateIsConvex() + * line is convex */ - updateIsConvex() { - return; + this.isConvex = true; + if (this.calcPoints.length === 1 || !end) { + console.error({ start, end }); + throw new Error("No end point for line provided"); } + } + get start() { + return { + x: this.x + this.calcPoints[0].x, + y: this.y + this.calcPoints[0].y, + }; + } + set start({ x, y }) { + this.x = x; + this.y = y; + } + get end() { + return { + x: this.x + this.calcPoints[1].x, + y: this.y + this.calcPoints[1].y, + }; + } + set end({ x, y }) { + this.points[1].x = x - this.start.x; + this.points[1].y = y - this.start.y; + this.setPoints(this.points); + } + getCentroid() { + return new sat_1.Vector( + (this.end.x - this.start.x) / 2, + (this.end.y - this.start.y) / 2, + ); + } + /** + * do not attempt to use Polygon.updateIsConvex() + */ + updateIsConvex() { + return; + } } exports.Line = Line; diff --git a/dist/bodies/point.d.ts b/dist/bodies/point.d.ts index d30c90f8..46143976 100644 --- a/dist/bodies/point.d.ts +++ b/dist/bodies/point.d.ts @@ -4,16 +4,16 @@ import { Box } from "./box"; * collider - point (very tiny box) */ export declare class Point extends Box { - /** - * point type - */ - readonly type: BodyType.Point; - /** - * faster than type - */ - readonly typeGroup: BodyGroup.Point; - /** - * collider - point (very tiny box) - */ - constructor(position: PotentialVector, options?: BodyOptions); + /** + * point type + */ + readonly type: BodyType.Point; + /** + * faster than type + */ + readonly typeGroup: BodyGroup.Point; + /** + * collider - point (very tiny box) + */ + constructor(position: PotentialVector, options?: BodyOptions); } diff --git a/dist/bodies/point.js b/dist/bodies/point.js index 9540ed92..ffd3fd19 100644 --- a/dist/bodies/point.js +++ b/dist/bodies/point.js @@ -8,19 +8,19 @@ const utils_1 = require("../utils"); * collider - point (very tiny box) */ class Point extends box_1.Box { + /** + * collider - point (very tiny box) + */ + constructor(position, options) { + super((0, utils_1.ensureVectorPoint)(position), 0.001, 0.001, options); /** - * collider - point (very tiny box) + * point type */ - constructor(position, options) { - super((0, utils_1.ensureVectorPoint)(position), 0.001, 0.001, options); - /** - * point type - */ - this.type = model_1.BodyType.Point; - /** - * faster than type - */ - this.typeGroup = model_1.BodyGroup.Point; - } + this.type = model_1.BodyType.Point; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Point; + } } exports.Point = Point; diff --git a/dist/bodies/polygon.d.ts b/dist/bodies/polygon.d.ts index c710f90e..0be2bed7 100644 --- a/dist/bodies/polygon.d.ts +++ b/dist/bodies/polygon.d.ts @@ -1,4 +1,14 @@ -import { BBox, BodyGroup, BodyOptions, BodyProps, BodyType, DecompPolygon, PotentialVector, SATVector, Vector } from "../model"; +import { + BBox, + BodyGroup, + BodyOptions, + BodyProps, + BodyType, + DecompPolygon, + PotentialVector, + SATVector, + Vector, +} from "../model"; import { isSimple } from "poly-decomp-es"; import { Polygon as SATPolygon } from "sat"; import { System } from "../system"; @@ -7,191 +17,205 @@ export { isSimple }; * collider - polygon */ export declare class Polygon extends SATPolygon implements BBox, BodyProps { - /** - * minimum x bound of body - */ - minX: number; - /** - * maximum x bound of body - */ - maxX: number; - /** - * minimum y bound of body - */ - minY: number; - /** - * maximum y bound of body - */ - maxY: number; - /** - * bounding box cache, without padding - */ - bbox: BBox; - /** - * is it a convex polgyon as opposed to a hollow inside (concave) polygon - */ - isConvex: boolean; - /** - * optimization for convex polygons - */ - convexPolygons: SATPolygon[]; - /** - * bodies are not reinserted during update if their bbox didnt move outside bbox + padding - */ - padding: number; - /** - * static bodies don't move but they collide - */ - isStatic: boolean; - /** - * trigger bodies move but are like ghosts - */ - isTrigger: boolean; - /** - * reference to collision system - */ - system?: System; - /** - * was the polygon modified and needs update in the next checkCollision - */ - dirty: boolean; - /** - * type of body - */ - readonly type: BodyType.Polygon | BodyType.Box | BodyType.Point | BodyType.Ellipse | BodyType.Line; - /** - * faster than type - */ - readonly typeGroup: BodyGroup.Polygon | BodyGroup.Box | BodyGroup.Point | BodyGroup.Ellipse | BodyGroup.Line; - /** - * backup of points used for scaling - */ - protected pointsBackup: Vector[]; - /** - * is body centered - */ - protected centered: boolean; - /** - * group for collision filtering - */ - protected _group: number; - /** - * scale Vector of body - */ - protected readonly scaleVector: Vector; - /** - * collider - polygon - */ - constructor(position: PotentialVector, points: PotentialVector[], options?: BodyOptions); - /** - * flag to set is polygon centered - */ - set isCentered(isCentered: boolean); - /** - * is polygon centered? - */ - get isCentered(): boolean; - get x(): number; - /** - * updating this.pos.x by this.x = x updates AABB - */ - set x(x: number); - get y(): number; - /** - * updating this.pos.y by this.y = y updates AABB - */ - set y(y: number); - /** - * allow exact getting of scale x - use setScale(x, y) to set - */ - get scaleX(): number; - /** - * allow exact getting of scale y - use setScale(x, y) to set - */ - get scaleY(): number; - /** - * allow approx getting of scale - */ - get scale(): number; - /** - * allow easier setting of scale - */ - set scale(scale: number); - /** - * group for collision filtering - */ - get group(): number; - set group(group: number); - /** - * update position BY MOVING FORWARD IN ANGLE DIRECTION - */ - move(speed?: number, updateNow?: boolean): SATPolygon; - /** - * update position BY TELEPORTING - */ - setPosition(x: number, y: number, updateNow?: boolean): SATPolygon; - /** - * update scale - */ - setScale(x: number, y?: number, updateNow?: boolean): SATPolygon; - setAngle(angle: number, updateNow?: boolean): SATPolygon; - setOffset(offset: SATVector, updateNow?: boolean): SATPolygon; - /** - * get body bounding box, without padding - */ - getAABBAsBBox(): BBox; - /** - * Draws exact collider on canvas context - */ - draw(context: CanvasRenderingContext2D): void; - /** - * Draws Bounding Box on canvas context - */ - drawBVH(context: CanvasRenderingContext2D): void; - /** - * get body centroid without applied angle - */ - getCentroidWithoutRotation(): Vector; - /** - * sets polygon points to new array of vectors - */ - setPoints(points: SATVector[]): Polygon; - /** - * translates polygon points in x, y direction - */ - translate(x: number, y: number): Polygon; - /** - * rotates polygon points by angle, in radians - */ - rotate(angle: number): Polygon; - /** - * if true, polygon is not an invalid, self-crossing polygon - */ - isSimple(): boolean; - /** - * inner function for after position change update aabb in system and convex inner polygons - */ - updateBody(updateNow?: boolean): void; - protected retranslate(isCentered?: boolean): void; - /** - * update instantly or mark as dirty - */ - protected markAsDirty(updateNow?: boolean): void; - /** - * update the position of the decomposed convex polygons (if any), called - * after the position of the body has changed - */ - protected updateConvexPolygonPositions(): void; - /** - * returns body split into convex polygons, or empty array for convex bodies - */ - protected getConvex(): DecompPolygon[]; - /** - * updates convex polygons cache in body - */ - protected updateConvexPolygons(convex?: DecompPolygon[]): void; - /** - * after points update set is convex - */ - protected updateIsConvex(): void; + /** + * minimum x bound of body + */ + minX: number; + /** + * maximum x bound of body + */ + maxX: number; + /** + * minimum y bound of body + */ + minY: number; + /** + * maximum y bound of body + */ + maxY: number; + /** + * bounding box cache, without padding + */ + bbox: BBox; + /** + * is it a convex polgyon as opposed to a hollow inside (concave) polygon + */ + isConvex: boolean; + /** + * optimization for convex polygons + */ + convexPolygons: SATPolygon[]; + /** + * bodies are not reinserted during update if their bbox didnt move outside bbox + padding + */ + padding: number; + /** + * static bodies don't move but they collide + */ + isStatic: boolean; + /** + * trigger bodies move but are like ghosts + */ + isTrigger: boolean; + /** + * reference to collision system + */ + system?: System; + /** + * was the polygon modified and needs update in the next checkCollision + */ + dirty: boolean; + /** + * type of body + */ + readonly type: + | BodyType.Polygon + | BodyType.Box + | BodyType.Point + | BodyType.Ellipse + | BodyType.Line; + /** + * faster than type + */ + readonly typeGroup: + | BodyGroup.Polygon + | BodyGroup.Box + | BodyGroup.Point + | BodyGroup.Ellipse + | BodyGroup.Line; + /** + * backup of points used for scaling + */ + protected pointsBackup: Vector[]; + /** + * is body centered + */ + protected centered: boolean; + /** + * group for collision filtering + */ + protected _group: number; + /** + * scale Vector of body + */ + protected readonly scaleVector: Vector; + /** + * collider - polygon + */ + constructor( + position: PotentialVector, + points: PotentialVector[], + options?: BodyOptions, + ); + /** + * flag to set is polygon centered + */ + set isCentered(isCentered: boolean); + /** + * is polygon centered? + */ + get isCentered(): boolean; + get x(): number; + /** + * updating this.pos.x by this.x = x updates AABB + */ + set x(x: number); + get y(): number; + /** + * updating this.pos.y by this.y = y updates AABB + */ + set y(y: number); + /** + * allow exact getting of scale x - use setScale(x, y) to set + */ + get scaleX(): number; + /** + * allow exact getting of scale y - use setScale(x, y) to set + */ + get scaleY(): number; + /** + * allow approx getting of scale + */ + get scale(): number; + /** + * allow easier setting of scale + */ + set scale(scale: number); + /** + * group for collision filtering + */ + get group(): number; + set group(group: number); + /** + * update position BY MOVING FORWARD IN ANGLE DIRECTION + */ + move(speed?: number, updateNow?: boolean): SATPolygon; + /** + * update position BY TELEPORTING + */ + setPosition(x: number, y: number, updateNow?: boolean): SATPolygon; + /** + * update scale + */ + setScale(x: number, y?: number, updateNow?: boolean): SATPolygon; + setAngle(angle: number, updateNow?: boolean): SATPolygon; + setOffset(offset: SATVector, updateNow?: boolean): SATPolygon; + /** + * get body bounding box, without padding + */ + getAABBAsBBox(): BBox; + /** + * Draws exact collider on canvas context + */ + draw(context: CanvasRenderingContext2D): void; + /** + * Draws Bounding Box on canvas context + */ + drawBVH(context: CanvasRenderingContext2D): void; + /** + * get body centroid without applied angle + */ + getCentroidWithoutRotation(): Vector; + /** + * sets polygon points to new array of vectors + */ + setPoints(points: SATVector[]): Polygon; + /** + * translates polygon points in x, y direction + */ + translate(x: number, y: number): Polygon; + /** + * rotates polygon points by angle, in radians + */ + rotate(angle: number): Polygon; + /** + * if true, polygon is not an invalid, self-crossing polygon + */ + isSimple(): boolean; + /** + * inner function for after position change update aabb in system and convex inner polygons + */ + updateBody(updateNow?: boolean): void; + protected retranslate(isCentered?: boolean): void; + /** + * update instantly or mark as dirty + */ + protected markAsDirty(updateNow?: boolean): void; + /** + * update the position of the decomposed convex polygons (if any), called + * after the position of the body has changed + */ + protected updateConvexPolygonPositions(): void; + /** + * returns body split into convex polygons, or empty array for convex bodies + */ + protected getConvex(): DecompPolygon[]; + /** + * updates convex polygons cache in body + */ + protected updateConvexPolygons(convex?: DecompPolygon[]): void; + /** + * after points update set is convex + */ + protected updateIsConvex(): void; } diff --git a/dist/bodies/polygon.js b/dist/bodies/polygon.js index 91c28b88..8e282641 100644 --- a/dist/bodies/polygon.js +++ b/dist/bodies/polygon.js @@ -5,318 +5,338 @@ const model_1 = require("../model"); const utils_1 = require("../utils"); const optimized_1 = require("../optimized"); const poly_decomp_es_1 = require("poly-decomp-es"); -Object.defineProperty(exports, "isSimple", { enumerable: true, get: function () { return poly_decomp_es_1.isSimple; } }); +Object.defineProperty(exports, "isSimple", { + enumerable: true, + get: function () { + return poly_decomp_es_1.isSimple; + }, +}); const sat_1 = require("sat"); /** * collider - polygon */ class Polygon extends sat_1.Polygon { + /** + * collider - polygon + */ + constructor(position, points, options) { + super( + (0, utils_1.ensureVectorPoint)(position), + (0, utils_1.ensurePolygonPoints)(points), + ); /** - * collider - polygon + * was the polygon modified and needs update in the next checkCollision */ - constructor(position, points, options) { - super((0, utils_1.ensureVectorPoint)(position), (0, utils_1.ensurePolygonPoints)(points)); - /** - * was the polygon modified and needs update in the next checkCollision - */ - this.dirty = false; - /** - * type of body - */ - this.type = model_1.BodyType.Polygon; - /** - * faster than type - */ - this.typeGroup = model_1.BodyGroup.Polygon; - /** - * is body centered - */ - this.centered = false; - /** - * scale Vector of body - */ - this.scaleVector = { x: 1, y: 1 }; - if (!points.length) { - throw new Error("No points in polygon"); - } - (0, utils_1.extendBody)(this, options); - } - /** - * flag to set is polygon centered - */ - set isCentered(isCentered) { - if (this.centered === isCentered) { - return; - } - const centroid = this.getCentroidWithoutRotation(); - if (centroid.x || centroid.y) { - const x = centroid.x * (isCentered ? 1 : -1); - const y = centroid.y * (isCentered ? 1 : -1); - this.translate(-x, -y); - } - this.centered = isCentered; - } - /** - * is polygon centered? - */ - get isCentered() { - return this.centered; - } - get x() { - return this.pos.x; - } - /** - * updating this.pos.x by this.x = x updates AABB - */ - set x(x) { - this.pos.x = x; - this.markAsDirty(); - } - get y() { - return this.pos.y; - } - /** - * updating this.pos.y by this.y = y updates AABB - */ - set y(y) { - this.pos.y = y; - this.markAsDirty(); - } + this.dirty = false; /** - * allow exact getting of scale x - use setScale(x, y) to set + * type of body */ - get scaleX() { - return this.scaleVector.x; - } + this.type = model_1.BodyType.Polygon; /** - * allow exact getting of scale y - use setScale(x, y) to set + * faster than type */ - get scaleY() { - return this.scaleVector.y; - } - /** - * allow approx getting of scale - */ - get scale() { - return (this.scaleVector.x + this.scaleVector.y) / 2; - } + this.typeGroup = model_1.BodyGroup.Polygon; /** - * allow easier setting of scale + * is body centered */ - set scale(scale) { - this.setScale(scale); - } - /** - * group for collision filtering - */ - get group() { - return this._group; - } - set group(group) { - this._group = (0, utils_1.getGroup)(group); - } - /** - * update position BY MOVING FORWARD IN ANGLE DIRECTION - */ - move(speed = 1, updateNow = true) { - (0, utils_1.move)(this, speed, updateNow); - return this; - } - /** - * update position BY TELEPORTING - */ - setPosition(x, y, updateNow = true) { - this.pos.x = x; - this.pos.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * update scale - */ - setScale(x, y = x, updateNow = true) { - this.scaleVector.x = Math.abs(x); - this.scaleVector.y = Math.abs(y); - super.setPoints((0, optimized_1.map)(this.points, (point, index) => { - point.x = this.pointsBackup[index].x * this.scaleVector.x; - point.y = this.pointsBackup[index].y * this.scaleVector.y; - return point; - })); - this.markAsDirty(updateNow); - return this; - } - setAngle(angle, updateNow = true) { - super.setAngle(angle); - this.markAsDirty(updateNow); - return this; - } - setOffset(offset, updateNow = true) { - super.setOffset(offset); - this.markAsDirty(updateNow); - return this; - } - /** - * get body bounding box, without padding - */ - getAABBAsBBox() { - const { pos, w, h } = this.getAABBAsBox(); - return { - minX: pos.x, - minY: pos.y, - maxX: pos.x + w, - maxY: pos.y + h - }; - } + this.centered = false; /** - * Draws exact collider on canvas context + * scale Vector of body */ - draw(context) { - (0, utils_1.drawPolygon)(context, this, this.isTrigger); - } - /** - * Draws Bounding Box on canvas context - */ - drawBVH(context) { - (0, utils_1.drawBVH)(context, this); - } - /** - * get body centroid without applied angle - */ - getCentroidWithoutRotation() { - // keep angle copy - const angle = this.angle; - if (angle) { - // reset angle for get centroid - this.setAngle(0); - // get centroid - const centroid = this.getCentroid(); - // revert angle change - this.setAngle(angle); - return centroid; - } - return this.getCentroid(); - } - /** - * sets polygon points to new array of vectors - */ - setPoints(points) { - super.setPoints(points); - this.updateIsConvex(); - this.pointsBackup = (0, utils_1.clonePointsArray)(points); - return this; - } - /** - * translates polygon points in x, y direction - */ - translate(x, y) { - super.translate(x, y); - this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); - return this; - } - /** - * rotates polygon points by angle, in radians - */ - rotate(angle) { - super.rotate(angle); - this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); - return this; - } - /** - * if true, polygon is not an invalid, self-crossing polygon - */ - isSimple() { - return (0, poly_decomp_es_1.isSimple)(this.calcPoints.map(utils_1.mapVectorToArray)); - } - /** - * inner function for after position change update aabb in system and convex inner polygons - */ - updateBody(updateNow = this.dirty) { - var _a; - if (updateNow) { - this.updateConvexPolygonPositions(); - (_a = this.system) === null || _a === void 0 ? void 0 : _a.insert(this); - this.dirty = false; - } - } - retranslate(isCentered = this.isCentered) { - const centroid = this.getCentroidWithoutRotation(); - if (centroid.x || centroid.y) { - const x = centroid.x * (isCentered ? 1 : -1); - const y = centroid.y * (isCentered ? 1 : -1); - this.translate(-x, -y); - } - } - /** - * update instantly or mark as dirty - */ - markAsDirty(updateNow = false) { - if (updateNow) { - this.updateBody(true); - } - else { - this.dirty = true; - } - } - /** - * update the position of the decomposed convex polygons (if any), called - * after the position of the body has changed - */ - updateConvexPolygonPositions() { - if (this.isConvex || !this.convexPolygons) { - return; - } - (0, optimized_1.forEach)(this.convexPolygons, (polygon) => { - polygon.pos.x = this.pos.x; - polygon.pos.y = this.pos.y; - if (polygon.angle !== this.angle) { - // Must use setAngle to recalculate the points of the Polygon - polygon.setAngle(this.angle); - } - }); - } - /** - * returns body split into convex polygons, or empty array for convex bodies - */ - getConvex() { - if ((this.typeGroup && this.typeGroup !== model_1.BodyGroup.Polygon) || - this.points.length < 4) { - return []; - } - const points = (0, optimized_1.map)(this.calcPoints, utils_1.mapVectorToArray); - return (0, poly_decomp_es_1.quickDecomp)(points); - } - /** - * updates convex polygons cache in body - */ - updateConvexPolygons(convex = this.getConvex()) { - if (this.isConvex) { - return; - } - if (!this.convexPolygons) { - this.convexPolygons = []; - } - (0, optimized_1.forEach)(convex, (points, index) => { - // lazy create - if (!this.convexPolygons[index]) { - this.convexPolygons[index] = new sat_1.Polygon(); - } - this.convexPolygons[index].pos.x = this.pos.x; - this.convexPolygons[index].pos.y = this.pos.y; - this.convexPolygons[index].angle = this.angle; - this.convexPolygons[index].setPoints((0, utils_1.ensurePolygonPoints)((0, optimized_1.map)(points, utils_1.mapArrayToVector))); - }); - // trim array length - this.convexPolygons.length = convex.length; - } - /** - * after points update set is convex - */ - updateIsConvex() { - // all other types other than polygon are always convex - const convex = this.getConvex(); - // everything with empty array or one element array - this.isConvex = convex.length <= 1; - this.updateConvexPolygons(convex); - } + this.scaleVector = { x: 1, y: 1 }; + if (!points.length) { + throw new Error("No points in polygon"); + } + (0, utils_1.extendBody)(this, options); + } + /** + * flag to set is polygon centered + */ + set isCentered(isCentered) { + if (this.centered === isCentered) { + return; + } + const centroid = this.getCentroidWithoutRotation(); + if (centroid.x || centroid.y) { + const x = centroid.x * (isCentered ? 1 : -1); + const y = centroid.y * (isCentered ? 1 : -1); + this.translate(-x, -y); + } + this.centered = isCentered; + } + /** + * is polygon centered? + */ + get isCentered() { + return this.centered; + } + get x() { + return this.pos.x; + } + /** + * updating this.pos.x by this.x = x updates AABB + */ + set x(x) { + this.pos.x = x; + this.markAsDirty(); + } + get y() { + return this.pos.y; + } + /** + * updating this.pos.y by this.y = y updates AABB + */ + set y(y) { + this.pos.y = y; + this.markAsDirty(); + } + /** + * allow exact getting of scale x - use setScale(x, y) to set + */ + get scaleX() { + return this.scaleVector.x; + } + /** + * allow exact getting of scale y - use setScale(x, y) to set + */ + get scaleY() { + return this.scaleVector.y; + } + /** + * allow approx getting of scale + */ + get scale() { + return (this.scaleVector.x + this.scaleVector.y) / 2; + } + /** + * allow easier setting of scale + */ + set scale(scale) { + this.setScale(scale); + } + /** + * group for collision filtering + */ + get group() { + return this._group; + } + set group(group) { + this._group = (0, utils_1.getGroup)(group); + } + /** + * update position BY MOVING FORWARD IN ANGLE DIRECTION + */ + move(speed = 1, updateNow = true) { + (0, utils_1.move)(this, speed, updateNow); + return this; + } + /** + * update position BY TELEPORTING + */ + setPosition(x, y, updateNow = true) { + this.pos.x = x; + this.pos.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * update scale + */ + setScale(x, y = x, updateNow = true) { + this.scaleVector.x = Math.abs(x); + this.scaleVector.y = Math.abs(y); + super.setPoints( + (0, optimized_1.map)(this.points, (point, index) => { + point.x = this.pointsBackup[index].x * this.scaleVector.x; + point.y = this.pointsBackup[index].y * this.scaleVector.y; + return point; + }), + ); + this.markAsDirty(updateNow); + return this; + } + setAngle(angle, updateNow = true) { + super.setAngle(angle); + this.markAsDirty(updateNow); + return this; + } + setOffset(offset, updateNow = true) { + super.setOffset(offset); + this.markAsDirty(updateNow); + return this; + } + /** + * get body bounding box, without padding + */ + getAABBAsBBox() { + const { pos, w, h } = this.getAABBAsBox(); + return { + minX: pos.x, + minY: pos.y, + maxX: pos.x + w, + maxY: pos.y + h, + }; + } + /** + * Draws exact collider on canvas context + */ + draw(context) { + (0, utils_1.drawPolygon)(context, this, this.isTrigger); + } + /** + * Draws Bounding Box on canvas context + */ + drawBVH(context) { + (0, utils_1.drawBVH)(context, this); + } + /** + * get body centroid without applied angle + */ + getCentroidWithoutRotation() { + // keep angle copy + const angle = this.angle; + if (angle) { + // reset angle for get centroid + this.setAngle(0); + // get centroid + const centroid = this.getCentroid(); + // revert angle change + this.setAngle(angle); + return centroid; + } + return this.getCentroid(); + } + /** + * sets polygon points to new array of vectors + */ + setPoints(points) { + super.setPoints(points); + this.updateIsConvex(); + this.pointsBackup = (0, utils_1.clonePointsArray)(points); + return this; + } + /** + * translates polygon points in x, y direction + */ + translate(x, y) { + super.translate(x, y); + this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); + return this; + } + /** + * rotates polygon points by angle, in radians + */ + rotate(angle) { + super.rotate(angle); + this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); + return this; + } + /** + * if true, polygon is not an invalid, self-crossing polygon + */ + isSimple() { + return (0, poly_decomp_es_1.isSimple)( + this.calcPoints.map(utils_1.mapVectorToArray), + ); + } + /** + * inner function for after position change update aabb in system and convex inner polygons + */ + updateBody(updateNow = this.dirty) { + var _a; + if (updateNow) { + this.updateConvexPolygonPositions(); + (_a = this.system) === null || _a === void 0 ? void 0 : _a.insert(this); + this.dirty = false; + } + } + retranslate(isCentered = this.isCentered) { + const centroid = this.getCentroidWithoutRotation(); + if (centroid.x || centroid.y) { + const x = centroid.x * (isCentered ? 1 : -1); + const y = centroid.y * (isCentered ? 1 : -1); + this.translate(-x, -y); + } + } + /** + * update instantly or mark as dirty + */ + markAsDirty(updateNow = false) { + if (updateNow) { + this.updateBody(true); + } else { + this.dirty = true; + } + } + /** + * update the position of the decomposed convex polygons (if any), called + * after the position of the body has changed + */ + updateConvexPolygonPositions() { + if (this.isConvex || !this.convexPolygons) { + return; + } + (0, optimized_1.forEach)(this.convexPolygons, (polygon) => { + polygon.pos.x = this.pos.x; + polygon.pos.y = this.pos.y; + if (polygon.angle !== this.angle) { + // Must use setAngle to recalculate the points of the Polygon + polygon.setAngle(this.angle); + } + }); + } + /** + * returns body split into convex polygons, or empty array for convex bodies + */ + getConvex() { + if ( + (this.typeGroup && this.typeGroup !== model_1.BodyGroup.Polygon) || + this.points.length < 4 + ) { + return []; + } + const points = (0, optimized_1.map)( + this.calcPoints, + utils_1.mapVectorToArray, + ); + return (0, poly_decomp_es_1.quickDecomp)(points); + } + /** + * updates convex polygons cache in body + */ + updateConvexPolygons(convex = this.getConvex()) { + if (this.isConvex) { + return; + } + if (!this.convexPolygons) { + this.convexPolygons = []; + } + (0, optimized_1.forEach)(convex, (points, index) => { + // lazy create + if (!this.convexPolygons[index]) { + this.convexPolygons[index] = new sat_1.Polygon(); + } + this.convexPolygons[index].pos.x = this.pos.x; + this.convexPolygons[index].pos.y = this.pos.y; + this.convexPolygons[index].angle = this.angle; + this.convexPolygons[index].setPoints( + (0, utils_1.ensurePolygonPoints)( + (0, optimized_1.map)(points, utils_1.mapArrayToVector), + ), + ); + }); + // trim array length + this.convexPolygons.length = convex.length; + } + /** + * after points update set is convex + */ + updateIsConvex() { + // all other types other than polygon are always convex + const convex = this.getConvex(); + // everything with empty array or one element array + this.isConvex = convex.length <= 1; + this.updateConvexPolygons(convex); + } } exports.Polygon = Polygon; diff --git a/dist/demo/canvas.d.ts b/dist/demo/canvas.d.ts index 45b0fe39..7d48b0a6 100644 --- a/dist/demo/canvas.d.ts +++ b/dist/demo/canvas.d.ts @@ -1,14 +1,14 @@ export class TestCanvas { - constructor(test: any); - test: any; - element: HTMLDivElement; - canvas: HTMLCanvasElement; - context: CanvasRenderingContext2D | null; - bvhCheckbox: Element | null; - fps: number; - frame: number; - started: number; - update(): void; + constructor(test: any); + test: any; + element: HTMLDivElement; + canvas: HTMLCanvasElement; + context: CanvasRenderingContext2D | null; + bvhCheckbox: Element | null; + fps: number; + frame: number; + started: number; + update(): void; } export function loop(callback: any): void; export const width: number; diff --git a/dist/demo/canvas.js b/dist/demo/canvas.js index a9b0263a..a161d560 100644 --- a/dist/demo/canvas.js +++ b/dist/demo/canvas.js @@ -2,67 +2,71 @@ const width = window.innerWidth || 1024; const height = window.innerHeight || 768; class TestCanvas { - constructor(test) { - this.test = test; - this.element = document.createElement("div"); - this.element.id = "debug"; - this.element.innerHTML = `${this.test.legend} + constructor(test) { + this.test = test; + this.element = document.createElement("div"); + this.element.id = "debug"; + this.element.innerHTML = `${this.test.legend}
`; - this.canvas = document.createElement("canvas"); - this.canvas.width = width; - this.canvas.height = height; - this.context = this.canvas.getContext("2d"); - this.context.font = "24px Arial"; - this.test.context = this.context; - this.bvhCheckbox = this.element.querySelector("#bvh"); - if (this.canvas instanceof Node) { - this.element.appendChild(this.canvas); - } - this.fps = 0; - this.frame = 0; - this.started = Date.now(); - loop(this.update.bind(this)); + this.canvas = document.createElement("canvas"); + this.canvas.width = width; + this.canvas.height = height; + this.context = this.canvas.getContext("2d"); + this.context.font = "24px Arial"; + this.test.context = this.context; + this.bvhCheckbox = this.element.querySelector("#bvh"); + if (this.canvas instanceof Node) { + this.element.appendChild(this.canvas); } - update() { - this.frame++; - const timeDiff = Date.now() - this.started; - if (timeDiff >= 1000) { - this.fps = this.frame / (timeDiff / 1000); - this.frame = 0; - this.started = Date.now(); - } - // Clear the canvas - this.context.fillStyle = "#000000"; - this.context.fillRect(0, 0, width, height); - // Render the bodies - this.context.strokeStyle = "#FFFFFF"; - this.context.beginPath(); - this.test.physics.draw(this.context); - this.context.stroke(); - // Render the BVH - if (this.bvhCheckbox.checked) { - this.context.strokeStyle = "#00FF00"; - this.context.beginPath(); - this.test.physics.drawBVH(this.context); - this.context.stroke(); - } - // Render the FPS - this.context.fillStyle = "#FFCC00"; - this.context.fillText(`FPS: ${this.fps ? this.fps.toFixed(0) : "?"}`, 24, 48); - if (this.test.drawCallback) { - this.test.drawCallback(); - } + this.fps = 0; + this.frame = 0; + this.started = Date.now(); + loop(this.update.bind(this)); + } + update() { + this.frame++; + const timeDiff = Date.now() - this.started; + if (timeDiff >= 1000) { + this.fps = this.frame / (timeDiff / 1000); + this.frame = 0; + this.started = Date.now(); } + // Clear the canvas + this.context.fillStyle = "#000000"; + this.context.fillRect(0, 0, width, height); + // Render the bodies + this.context.strokeStyle = "#FFFFFF"; + this.context.beginPath(); + this.test.physics.draw(this.context); + this.context.stroke(); + // Render the BVH + if (this.bvhCheckbox.checked) { + this.context.strokeStyle = "#00FF00"; + this.context.beginPath(); + this.test.physics.drawBVH(this.context); + this.context.stroke(); + } + // Render the FPS + this.context.fillStyle = "#FFCC00"; + this.context.fillText( + `FPS: ${this.fps ? this.fps.toFixed(0) : "?"}`, + 24, + 48, + ); + if (this.test.drawCallback) { + this.test.drawCallback(); + } + } } function loop(callback) { - // interval for fps instead of setTimeout - // and ms = 1 which is lowest nonzero value - // for responsiveness of user input - setInterval(callback, 1); + // interval for fps instead of setTimeout + // and ms = 1 which is lowest nonzero value + // for responsiveness of user input + setInterval(callback, 1); } module.exports.TestCanvas = TestCanvas; module.exports.loop = loop; diff --git a/dist/demo/demo.js b/dist/demo/demo.js index c0bad769..9e236453 100644 --- a/dist/demo/demo.js +++ b/dist/demo/demo.js @@ -1,722 +1,902 @@ -/******/ (() => { // webpackBootstrap -/******/ var __webpack_modules__ = ({ - -/***/ "./node_modules/json-stringify-safe/stringify.js": -/*!*******************************************************!*\ +/******/ (() => { + // webpackBootstrap + /******/ var __webpack_modules__ = { + /***/ "./node_modules/json-stringify-safe/stringify.js": + /*!*******************************************************!*\ !*** ./node_modules/json-stringify-safe/stringify.js ***! \*******************************************************/ -/***/ ((module, exports) => { + /***/ (module, exports) => { + exports = module.exports = stringify; + exports.getSerialize = serializer; + + function stringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify( + obj, + serializer(replacer, cycleReplacer), + spaces, + ); + } -exports = module.exports = stringify -exports.getSerialize = serializer + function serializer(replacer, cycleReplacer) { + var stack = [], + keys = []; + + if (cycleReplacer == null) + cycleReplacer = function (key, value) { + if (stack[0] === value) return "[Circular ~]"; + return ( + "[Circular ~." + + keys.slice(0, stack.indexOf(value)).join(".") + + "]" + ); + }; + + return function (key, value) { + if (stack.length > 0) { + var thisPos = stack.indexOf(this); + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this); + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); + if (~stack.indexOf(value)) + value = cycleReplacer.call(this, key, value); + } else stack.push(value); + + return replacer == null ? value : replacer.call(this, key, value); + }; + } -function stringify(obj, replacer, spaces, cycleReplacer) { - return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) -} + /***/ + }, -function serializer(replacer, cycleReplacer) { - var stack = [], keys = [] + /***/ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js": + /*!************************************************************!*\ + !*** ./node_modules/poly-decomp-es/dist/poly-decomp-es.js ***! + \************************************************************/ + /***/ ( + __unused_webpack_module, + __webpack_exports__, + __webpack_require__, + ) => { + "use strict"; + __webpack_require__.r(__webpack_exports__); + /* harmony export */ __webpack_require__.d(__webpack_exports__, { + /* harmony export */ decomp: () => /* binding */ decomp, + /* harmony export */ isSimple: () => /* binding */ isSimple, + /* harmony export */ makeCCW: () => /* binding */ makeCCW, + /* harmony export */ quickDecomp: () => /* binding */ quickDecomp, + /* harmony export */ removeCollinearPoints: () => + /* binding */ removeCollinearPoints, + /* harmony export */ removeDuplicatePoints: () => + /* binding */ removeDuplicatePoints, + /* harmony export */ + }); + const tmpPoint1 = [0, 0]; + const tmpPoint2 = [0, 0]; + const tmpLine1 = [ + [0, 0], + [0, 0], + ]; + const tmpLine2 = [ + [0, 0], + [0, 0], + ]; - if (cycleReplacer == null) cycleReplacer = function(key, value) { - if (stack[0] === value) return "[Circular ~]" - return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]" - } + /** + * Compute the intersection between two lines. + * @param l1 Line vector 1 + * @param l2 Line vector 2 + * @param precision Precision to use when checking if the lines are parallel + * @return The intersection point. + */ + function lineInt(l1, l2, precision) { + if (precision === void 0) { + precision = 0; + } + precision = precision || 0; + const i = [0, 0]; // point + const a1 = l1[1][1] - l1[0][1]; + const b1 = l1[0][0] - l1[1][0]; + const c1 = a1 * l1[0][0] + b1 * l1[0][1]; + const a2 = l2[1][1] - l2[0][1]; + const b2 = l2[0][0] - l2[1][0]; + const c2 = a2 * l2[0][0] + b2 * l2[0][1]; + const det = a1 * b2 - a2 * b1; + if (!scalarsEqual(det, 0, precision)) { + // lines are not parallel + i[0] = (b2 * c1 - b1 * c2) / det; + i[1] = (a1 * c2 - a2 * c1) / det; + } + return i; + } - return function(key, value) { - if (stack.length > 0) { - var thisPos = stack.indexOf(this) - ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) - ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) - if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value) - } - else stack.push(value) + /** + * Checks if two line segments intersects. + * @param p1 The start vertex of the first line segment. + * @param p2 The end vertex of the first line segment. + * @param q1 The start vertex of the second line segment. + * @param q2 The end vertex of the second line segment. + * @return True if the two line segments intersect + */ + function lineSegmentsIntersect(p1, p2, q1, q2) { + const dx = p2[0] - p1[0]; + const dy = p2[1] - p1[1]; + const da = q2[0] - q1[0]; + const db = q2[1] - q1[1]; + + // segments are parallel + if (da * dy - db * dx === 0) { + return false; + } + const s = + (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx); + const t = + (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy); + return s >= 0 && s <= 1 && t >= 0 && t <= 1; + } - return replacer == null ? value : replacer.call(this, key, value) - } -} + /** + * 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. + * @param a point 1 + * @param b point 2 + * @param c point 3 + * @return the area of a triangle spanned by the three given points + */ + function triangleArea(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]); + } + function isLeft(a, b, c) { + return triangleArea(a, b, c) > 0; + } + function isLeftOn(a, b, c) { + return triangleArea(a, b, c) >= 0; + } + function isRight(a, b, c) { + return triangleArea(a, b, c) < 0; + } + function isRightOn(a, b, c) { + return triangleArea(a, b, c) <= 0; + } + /** + * Check if three points are collinear + * @param a point 1 + * @param b point 2 + * @param c point 3 + * @param thresholdAngle 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. + * @return whether the points are collinear + */ + function collinear(a, b, c, thresholdAngle) { + if (thresholdAngle === void 0) { + thresholdAngle = 0; + } + if (!thresholdAngle) { + return triangleArea(a, b, c) === 0; + } else { + const ab = tmpPoint1; + const bc = tmpPoint2; + ab[0] = b[0] - a[0]; + ab[1] = b[1] - a[1]; + bc[0] = c[0] - b[0]; + bc[1] = c[1] - b[1]; + const dot = ab[0] * bc[0] + ab[1] * bc[1]; + const magA = Math.sqrt(ab[0] * ab[0] + ab[1] * ab[1]); + const magB = Math.sqrt(bc[0] * bc[0] + bc[1] * bc[1]); + const angle = Math.acos(dot / (magA * magB)); + return angle < thresholdAngle; + } + } + function sqdist(a, b) { + const dx = b[0] - a[0]; + const dy = b[1] - a[1]; + return dx * dx + dy * dy; + } -/***/ }), + /** + * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle. + * @param i vertex position + * @return vertex at position i + */ + function polygonAt(polygon, i) { + const s = polygon.length; + return polygon[i < 0 ? (i % s) + s : i % s]; + } -/***/ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js": -/*!************************************************************!*\ - !*** ./node_modules/poly-decomp-es/dist/poly-decomp-es.js ***! - \************************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ decomp: () => (/* binding */ decomp), -/* harmony export */ isSimple: () => (/* binding */ isSimple), -/* harmony export */ makeCCW: () => (/* binding */ makeCCW), -/* harmony export */ quickDecomp: () => (/* binding */ quickDecomp), -/* harmony export */ removeCollinearPoints: () => (/* binding */ removeCollinearPoints), -/* harmony export */ removeDuplicatePoints: () => (/* binding */ removeDuplicatePoints) -/* harmony export */ }); -const tmpPoint1 = [0, 0]; -const tmpPoint2 = [0, 0]; -const tmpLine1 = [[0, 0], [0, 0]]; -const tmpLine2 = [[0, 0], [0, 0]]; - -/** - * Compute the intersection between two lines. - * @param l1 Line vector 1 - * @param l2 Line vector 2 - * @param precision Precision to use when checking if the lines are parallel - * @return The intersection point. - */ -function lineInt(l1, l2, precision) { - if (precision === void 0) { - precision = 0; - } - precision = precision || 0; - const i = [0, 0]; // point - const a1 = l1[1][1] - l1[0][1]; - const b1 = l1[0][0] - l1[1][0]; - const c1 = a1 * l1[0][0] + b1 * l1[0][1]; - const a2 = l2[1][1] - l2[0][1]; - const b2 = l2[0][0] - l2[1][0]; - const c2 = a2 * l2[0][0] + b2 * l2[0][1]; - const det = a1 * b2 - a2 * b1; - if (!scalarsEqual(det, 0, precision)) { - // lines are not parallel - i[0] = (b2 * c1 - b1 * c2) / det; - i[1] = (a1 * c2 - a2 * c1) / det; - } - return i; -} - -/** - * Checks if two line segments intersects. - * @param p1 The start vertex of the first line segment. - * @param p2 The end vertex of the first line segment. - * @param q1 The start vertex of the second line segment. - * @param q2 The end vertex of the second line segment. - * @return True if the two line segments intersect - */ -function lineSegmentsIntersect(p1, p2, q1, q2) { - const dx = p2[0] - p1[0]; - const dy = p2[1] - p1[1]; - const da = q2[0] - q1[0]; - const db = q2[1] - q1[1]; - - // segments are parallel - if (da * dy - db * dx === 0) { - return false; - } - const s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx); - const t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy); - return s >= 0 && s <= 1 && t >= 0 && t <= 1; -} - -/** - * 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. - * @param a point 1 - * @param b point 2 - * @param c point 3 - * @return the area of a triangle spanned by the three given points - */ -function triangleArea(a, b, c) { - return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]); -} -function isLeft(a, b, c) { - return triangleArea(a, b, c) > 0; -} -function isLeftOn(a, b, c) { - return triangleArea(a, b, c) >= 0; -} -function isRight(a, b, c) { - return triangleArea(a, b, c) < 0; -} -function isRightOn(a, b, c) { - return triangleArea(a, b, c) <= 0; -} - -/** - * Check if three points are collinear - * @param a point 1 - * @param b point 2 - * @param c point 3 - * @param thresholdAngle 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. - * @return whether the points are collinear - */ -function collinear(a, b, c, thresholdAngle) { - if (thresholdAngle === void 0) { - thresholdAngle = 0; - } - if (!thresholdAngle) { - return triangleArea(a, b, c) === 0; - } else { - const ab = tmpPoint1; - const bc = tmpPoint2; - ab[0] = b[0] - a[0]; - ab[1] = b[1] - a[1]; - bc[0] = c[0] - b[0]; - bc[1] = c[1] - b[1]; - const dot = ab[0] * bc[0] + ab[1] * bc[1]; - const magA = Math.sqrt(ab[0] * ab[0] + ab[1] * ab[1]); - const magB = Math.sqrt(bc[0] * bc[0] + bc[1] * bc[1]); - const angle = Math.acos(dot / (magA * magB)); - return angle < thresholdAngle; - } -} -function sqdist(a, b) { - const dx = b[0] - a[0]; - const dy = b[1] - a[1]; - return dx * dx + dy * dy; -} - -/** - * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle. - * @param i vertex position - * @return vertex at position i - */ -function polygonAt(polygon, i) { - const s = polygon.length; - return polygon[i < 0 ? i % s + s : i % s]; -} - -/** - * Clear the polygon data - */ -function polygonClear(polygon) { - polygon.length = 0; -} - -/** - * Append points "from" to "to" -1 from an other polygon "poly" onto this one. - * @param polygon the polygon to append to - * @param poly The polygon to get points from. - * @param from The vertex index in "poly". - * @param to The end vertex index in "poly". Note that this vertex is NOT included when appending. - */ -function polygonAppend(polygon, poly, from, to) { - for (let i = from; i < to; i++) { - polygon.push(poly[i]); - } -} - -/** - * Make sure that the polygon vertices are ordered counter-clockwise. - */ -function makeCCW(polygon) { - let br = 0; - const v = polygon; - - // find bottom right point - for (let i = 1; i < polygon.length; ++i) { - if (v[i][1] < v[br][1] || v[i][1] === v[br][1] && v[i][0] > v[br][0]) { - br = i; - } - } + /** + * Clear the polygon data + */ + function polygonClear(polygon) { + polygon.length = 0; + } - // reverse poly if clockwise - if (!isLeft(polygonAt(polygon, br - 1), polygonAt(polygon, br), polygonAt(polygon, br + 1))) { - polygonReverse(polygon); - return true; - } else { - return false; - } -} - -/** - * Reverse the vertices in the polygon - */ -function polygonReverse(polygon) { - const tmp = []; - const N = polygon.length; - for (let i = 0; i !== N; i++) { - tmp.push(polygon.pop()); - } - for (let i = 0; i !== N; i++) { - polygon[i] = tmp[i]; - } -} - -/** - * Check if a point in the polygon is a reflex point - * @param i the point in the polygon to check - * @return whether the given point in the polygon is a reflex point - */ -function polygonIsReflex(polygon, i) { - return isRight(polygonAt(polygon, i - 1), polygonAt(polygon, i), polygonAt(polygon, i + 1)); -} - -/** - * Check if two vertices in the polygon can see each other - * @param a vertex index 1 - * @param b vertex index 2 - * @return whether two vertices in the polygon can see each other - */ -function polygonCanSee(polygon, a, b) { - const l1 = tmpLine1; - const l2 = tmpLine2; - if (isLeftOn(polygonAt(polygon, a + 1), polygonAt(polygon, a), polygonAt(polygon, b)) && isRightOn(polygonAt(polygon, a - 1), polygonAt(polygon, a), polygonAt(polygon, b))) { - return false; - } - const dist = sqdist(polygonAt(polygon, a), polygonAt(polygon, b)); - for (let i = 0; i !== polygon.length; ++i) { - // for each edge - if ((i + 1) % polygon.length === a || i === a) { - // ignore incident edges - continue; - } - 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 - l1[0] = polygonAt(polygon, a); - l1[1] = polygonAt(polygon, b); - l2[0] = polygonAt(polygon, i); - l2[1] = polygonAt(polygon, i + 1); - const p = lineInt(l1, l2); - if (sqdist(polygonAt(polygon, a), p) < dist) { - // if edge is blocking visibility to b - return false; - } - } - } - return true; -} - -/** - * Check if two vertices in the polygon can see each other - * @param a vertex index 1 - * @param b vertex index 2 - * @return if two vertices in the polygon can see each other - */ -function polygonCanSee2(polygon, a, b) { - // for each edge - for (let i = 0; i !== polygon.length; ++i) { - // ignore incident edges - if (i === a || i === b || (i + 1) % polygon.length === a || (i + 1) % polygon.length === b) { - continue; - } - if (lineSegmentsIntersect(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i), polygonAt(polygon, i + 1))) { - return false; - } - } - return true; -} - -/** - * Copy the polygon from vertex i to vertex j. - * @param i the start vertex to copy from - * @param j the end vertex to copy from - * @param targetPoly optional target polygon to save in. - * @return the resulting copy. - */ -function polygonCopy(polygon, i, j, targetPoly) { - if (targetPoly === void 0) { - targetPoly = []; - } - polygonClear(targetPoly); - if (i < j) { - // Insert all vertices from i to j - for (let k = i; k <= j; k++) { - targetPoly.push(polygon[k]); - } - } else { - // Insert vertices 0 to j - for (let k = 0; k <= j; k++) { - targetPoly.push(polygon[k]); - } + /** + * Append points "from" to "to" -1 from an other polygon "poly" onto this one. + * @param polygon the polygon to append to + * @param poly The polygon to get points from. + * @param from The vertex index in "poly". + * @param to The end vertex index in "poly". Note that this vertex is NOT included when appending. + */ + function polygonAppend(polygon, poly, from, to) { + for (let i = from; i < to; i++) { + polygon.push(poly[i]); + } + } - // Insert vertices i to end - for (let k = i; k < polygon.length; k++) { - targetPoly.push(polygon[k]); - } - } - return targetPoly; -} - -/** - * Decomposes the polygon into convex pieces. Returns a list of edges [[p1,p2],[p2,p3],...] that cuts the polygon. - * Note that this algorithm has complexity O(N^4) and will be very slow for polygons with many vertices. - * @return a list of edges that cuts the polygon - */ -function getCutEdges(polygon) { - let min = []; - let tmp1; - let tmp2; - const tmpPoly = []; - let nDiags = Number.MAX_VALUE; - for (let i = 0; i < polygon.length; ++i) { - if (polygonIsReflex(polygon, i)) { - for (let j = 0; j < polygon.length; ++j) { - if (polygonCanSee(polygon, i, j)) { - tmp1 = getCutEdges(polygonCopy(polygon, i, j, tmpPoly)); - tmp2 = getCutEdges(polygonCopy(polygon, j, i, tmpPoly)); - for (let k = 0; k < tmp2.length; k++) { - tmp1.push(tmp2[k]); - } - if (tmp1.length < nDiags) { - min = tmp1; - nDiags = tmp1.length; - min.push([polygonAt(polygon, i), polygonAt(polygon, j)]); + /** + * Make sure that the polygon vertices are ordered counter-clockwise. + */ + function makeCCW(polygon) { + let br = 0; + const v = polygon; + + // find bottom right point + for (let i = 1; i < polygon.length; ++i) { + if ( + v[i][1] < v[br][1] || + (v[i][1] === v[br][1] && v[i][0] > v[br][0]) + ) { + br = i; + } + } + + // reverse poly if clockwise + if ( + !isLeft( + polygonAt(polygon, br - 1), + polygonAt(polygon, br), + polygonAt(polygon, br + 1), + ) + ) { + polygonReverse(polygon); + return true; + } else { + return false; } } - } - } - } - return min; -} - -/** - * Decomposes the polygon into one or more convex sub-Polygons. - * @return An array of Polygon objects, or false if decomposition fails - */ -function decomp(polygon) { - const edges = getCutEdges(polygon); - if (edges.length > 0) { - return slicePolygon(polygon, edges); - } else { - return [polygon]; - } -} - -/** - * 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. - * @param cutEdges A list of edges, as returned by .getCutEdges() - * @return the sliced polygons, or false if the operation was unsuccessful - */ -function slicePolygon(polygon, cutEdges) { - if (cutEdges.length === 0) { - return [polygon]; - } - // if given multiple edges - if (cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length === 2 && cutEdges[0][0] instanceof Array) { - const polys = [polygon]; - for (let i = 0; i < cutEdges.length; i++) { - const cutEdge = cutEdges[i]; - // Cut all polys - for (let j = 0; j < polys.length; j++) { - const poly = polys[j]; - const result = slicePolygon(poly, cutEdge); - if (result) { - // Found poly! Cut and quit - polys.splice(j, 1); - polys.push(result[0], result[1]); - break; + /** + * Reverse the vertices in the polygon + */ + function polygonReverse(polygon) { + const tmp = []; + const N = polygon.length; + for (let i = 0; i !== N; i++) { + tmp.push(polygon.pop()); + } + for (let i = 0; i !== N; i++) { + polygon[i] = tmp[i]; + } } - } - } - return polys; - } else { - // Was given one edge - const cutEdge = cutEdges; - const i = polygon.indexOf(cutEdge[0]); - const j = polygon.indexOf(cutEdge[1]); - if (i !== -1 && j !== -1) { - return [polygonCopy(polygon, i, j), polygonCopy(polygon, j, i)]; - } else { - return false; - } - } -} - -/** - * Checks that the line segments of this polygon do not intersect each other. - * @param polygon An array of vertices e.g. [[0,0],[0,1],...] - * @return whether line segments of this polygon do not intersect each other. - * @todo Should it check all segments with all others? - */ -function isSimple(polygon) { - const path = polygon; - let i; - - // Check - for (i = 0; i < path.length - 1; i++) { - for (let j = 0; j < i - 1; j++) { - if (lineSegmentsIntersect(path[i], path[i + 1], path[j], path[j + 1])) { - return false; - } - } - } - // Check the segment between the last and the first point to all others - for (i = 1; i < path.length - 2; i++) { - if (lineSegmentsIntersect(path[0], path[path.length - 1], path[i], path[i + 1])) { - return false; - } - } - return true; -} -function getIntersectionPoint(p1, p2, q1, q2, delta) { - if (delta === void 0) { - delta = 0; - } - const a1 = p2[1] - p1[1]; - const b1 = p1[0] - p2[0]; - const c1 = a1 * p1[0] + b1 * p1[1]; - const a2 = q2[1] - q1[1]; - const b2 = q1[0] - q2[0]; - const c2 = a2 * q1[0] + b2 * q1[1]; - const det = a1 * b2 - a2 * b1; - if (!scalarsEqual(det, 0, delta)) { - return [(b2 * c1 - b1 * c2) / det, (a1 * c2 - a2 * c1) / det]; - } else { - return [0, 0]; - } -} - -/** - * Quickly decompose the Polygon into convex sub-polygons. - * @param polygon the polygon to decompose - * @param result - * @param reflexVertices - * @param steinerPoints - * @param delta - * @param maxlevel - * @param level - * @return the decomposed sub-polygons - */ -function quickDecomp(polygon, result, reflexVertices, steinerPoints, delta, maxlevel, level) { - if (result === void 0) { - result = []; - } - if (reflexVertices === void 0) { - reflexVertices = []; - } - if (steinerPoints === void 0) { - steinerPoints = []; - } - if (delta === void 0) { - delta = 25; - } - if (maxlevel === void 0) { - maxlevel = 100; - } - if (level === void 0) { - level = 0; - } - // Points - let upperInt = [0, 0]; - let lowerInt = [0, 0]; - let p = [0, 0]; - - // scalars - let upperDist = 0; - let lowerDist = 0; - let d = 0; - let closestDist = 0; - - // Integers - let upperIndex = 0; - let lowerIndex = 0; - let closestIndex = 0; - - // polygons - const lowerPoly = []; - const upperPoly = []; - const poly = polygon; - const v = polygon; - if (v.length < 3) { - return result; - } - level++; - if (level > maxlevel) { - console.warn('quickDecomp: max level (' + maxlevel + ') reached.'); - return result; - } - for (let i = 0; i < polygon.length; ++i) { - if (polygonIsReflex(poly, i)) { - reflexVertices.push(poly[i]); - upperDist = lowerDist = Number.MAX_VALUE; - for (let j = 0; j < polygon.length; ++j) { - 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 - p = getIntersectionPoint(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j - 1)); // find the point of intersection - if (isRight(polygonAt(poly, i + 1), polygonAt(poly, i), p)) { - // make sure it's inside the poly - d = sqdist(poly[i], p); - if (d < lowerDist) { - // keep only the closest intersection - lowerDist = d; - lowerInt = p; - lowerIndex = j; + /** + * Check if a point in the polygon is a reflex point + * @param i the point in the polygon to check + * @return whether the given point in the polygon is a reflex point + */ + function polygonIsReflex(polygon, i) { + return isRight( + polygonAt(polygon, i - 1), + polygonAt(polygon, i), + polygonAt(polygon, i + 1), + ); + } + + /** + * Check if two vertices in the polygon can see each other + * @param a vertex index 1 + * @param b vertex index 2 + * @return whether two vertices in the polygon can see each other + */ + function polygonCanSee(polygon, a, b) { + const l1 = tmpLine1; + const l2 = tmpLine2; + if ( + isLeftOn( + polygonAt(polygon, a + 1), + polygonAt(polygon, a), + polygonAt(polygon, b), + ) && + isRightOn( + polygonAt(polygon, a - 1), + polygonAt(polygon, a), + polygonAt(polygon, b), + ) + ) { + return false; + } + const dist = sqdist(polygonAt(polygon, a), polygonAt(polygon, b)); + for (let i = 0; i !== polygon.length; ++i) { + // for each edge + if ((i + 1) % polygon.length === a || i === a) { + // ignore incident edges + continue; + } + 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 + l1[0] = polygonAt(polygon, a); + l1[1] = polygonAt(polygon, b); + l2[0] = polygonAt(polygon, i); + l2[1] = polygonAt(polygon, i + 1); + const p = lineInt(l1, l2); + if (sqdist(polygonAt(polygon, a), p) < dist) { + // if edge is blocking visibility to b + return false; + } } } + return true; } - if (isLeft(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j + 1)) && isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))) { - p = getIntersectionPoint(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j + 1)); - if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), p)) { - d = sqdist(poly[i], p); - if (d < upperDist) { - upperDist = d; - upperInt = p; - upperIndex = j; + + /** + * Check if two vertices in the polygon can see each other + * @param a vertex index 1 + * @param b vertex index 2 + * @return if two vertices in the polygon can see each other + */ + function polygonCanSee2(polygon, a, b) { + // for each edge + for (let i = 0; i !== polygon.length; ++i) { + // ignore incident edges + if ( + i === a || + i === b || + (i + 1) % polygon.length === a || + (i + 1) % polygon.length === b + ) { + continue; + } + if ( + lineSegmentsIntersect( + polygonAt(polygon, a), + polygonAt(polygon, b), + polygonAt(polygon, i), + polygonAt(polygon, i + 1), + ) + ) { + return false; } } + return true; + } + + /** + * Copy the polygon from vertex i to vertex j. + * @param i the start vertex to copy from + * @param j the end vertex to copy from + * @param targetPoly optional target polygon to save in. + * @return the resulting copy. + */ + function polygonCopy(polygon, i, j, targetPoly) { + if (targetPoly === void 0) { + targetPoly = []; + } + polygonClear(targetPoly); + if (i < j) { + // Insert all vertices from i to j + for (let k = i; k <= j; k++) { + targetPoly.push(polygon[k]); + } + } else { + // Insert vertices 0 to j + for (let k = 0; k <= j; k++) { + targetPoly.push(polygon[k]); + } + + // Insert vertices i to end + for (let k = i; k < polygon.length; k++) { + targetPoly.push(polygon[k]); + } + } + return targetPoly; } - } - // if there are no vertices to connect to, choose a point in the middle - if (lowerIndex === (upperIndex + 1) % polygon.length) { - p[0] = (lowerInt[0] + upperInt[0]) / 2; - p[1] = (lowerInt[1] + upperInt[1]) / 2; - steinerPoints.push(p); - if (i < upperIndex) { - polygonAppend(lowerPoly, poly, i, upperIndex + 1); - lowerPoly.push(p); - upperPoly.push(p); - if (lowerIndex !== 0) { - polygonAppend(upperPoly, poly, lowerIndex, poly.length); - } - polygonAppend(upperPoly, poly, 0, i + 1); - } else { - if (i !== 0) { - polygonAppend(lowerPoly, poly, i, poly.length); - } - polygonAppend(lowerPoly, poly, 0, upperIndex + 1); - lowerPoly.push(p); - upperPoly.push(p); - polygonAppend(upperPoly, poly, lowerIndex, i + 1); + /** + * Decomposes the polygon into convex pieces. Returns a list of edges [[p1,p2],[p2,p3],...] that cuts the polygon. + * Note that this algorithm has complexity O(N^4) and will be very slow for polygons with many vertices. + * @return a list of edges that cuts the polygon + */ + function getCutEdges(polygon) { + let min = []; + let tmp1; + let tmp2; + const tmpPoly = []; + let nDiags = Number.MAX_VALUE; + for (let i = 0; i < polygon.length; ++i) { + if (polygonIsReflex(polygon, i)) { + for (let j = 0; j < polygon.length; ++j) { + if (polygonCanSee(polygon, i, j)) { + tmp1 = getCutEdges(polygonCopy(polygon, i, j, tmpPoly)); + tmp2 = getCutEdges(polygonCopy(polygon, j, i, tmpPoly)); + for (let k = 0; k < tmp2.length; k++) { + tmp1.push(tmp2[k]); + } + if (tmp1.length < nDiags) { + min = tmp1; + nDiags = tmp1.length; + min.push([polygonAt(polygon, i), polygonAt(polygon, j)]); + } + } + } + } + } + return min; } - } else { - // connect to the closest point within the triangle - if (lowerIndex > upperIndex) { - upperIndex += polygon.length; + + /** + * Decomposes the polygon into one or more convex sub-Polygons. + * @return An array of Polygon objects, or false if decomposition fails + */ + function decomp(polygon) { + const edges = getCutEdges(polygon); + if (edges.length > 0) { + return slicePolygon(polygon, edges); + } else { + return [polygon]; + } } - closestDist = Number.MAX_VALUE; - if (upperIndex < lowerIndex) { - return result; + + /** + * 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. + * @param cutEdges A list of edges, as returned by .getCutEdges() + * @return the sliced polygons, or false if the operation was unsuccessful + */ + function slicePolygon(polygon, cutEdges) { + if (cutEdges.length === 0) { + return [polygon]; + } + + // if given multiple edges + if ( + cutEdges instanceof Array && + cutEdges.length && + cutEdges[0] instanceof Array && + cutEdges[0].length === 2 && + cutEdges[0][0] instanceof Array + ) { + const polys = [polygon]; + for (let i = 0; i < cutEdges.length; i++) { + const cutEdge = cutEdges[i]; + // Cut all polys + for (let j = 0; j < polys.length; j++) { + const poly = polys[j]; + const result = slicePolygon(poly, cutEdge); + if (result) { + // Found poly! Cut and quit + polys.splice(j, 1); + polys.push(result[0], result[1]); + break; + } + } + } + return polys; + } else { + // Was given one edge + const cutEdge = cutEdges; + const i = polygon.indexOf(cutEdge[0]); + const j = polygon.indexOf(cutEdge[1]); + if (i !== -1 && j !== -1) { + return [polygonCopy(polygon, i, j), polygonCopy(polygon, j, i)]; + } else { + return false; + } + } } - for (let j = lowerIndex; j <= upperIndex; ++j) { - if (isLeftOn(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j)) && isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))) { - d = sqdist(polygonAt(poly, i), polygonAt(poly, j)); - if (d < closestDist && polygonCanSee2(poly, i, j)) { - closestDist = d; - closestIndex = j % polygon.length; + + /** + * Checks that the line segments of this polygon do not intersect each other. + * @param polygon An array of vertices e.g. [[0,0],[0,1],...] + * @return whether line segments of this polygon do not intersect each other. + * @todo Should it check all segments with all others? + */ + function isSimple(polygon) { + const path = polygon; + let i; + + // Check + for (i = 0; i < path.length - 1; i++) { + for (let j = 0; j < i - 1; j++) { + if ( + lineSegmentsIntersect( + path[i], + path[i + 1], + path[j], + path[j + 1], + ) + ) { + return false; + } + } + } + + // Check the segment between the last and the first point to all others + for (i = 1; i < path.length - 2; i++) { + if ( + lineSegmentsIntersect( + path[0], + path[path.length - 1], + path[i], + path[i + 1], + ) + ) { + return false; } } + return true; } - if (i < closestIndex) { - polygonAppend(lowerPoly, poly, i, closestIndex + 1); - if (closestIndex !== 0) { - polygonAppend(upperPoly, poly, closestIndex, v.length); - } - polygonAppend(upperPoly, poly, 0, i + 1); - } else { - if (i !== 0) { - polygonAppend(lowerPoly, poly, i, v.length); - } - polygonAppend(lowerPoly, poly, 0, closestIndex + 1); - polygonAppend(upperPoly, poly, closestIndex, i + 1); + function getIntersectionPoint(p1, p2, q1, q2, delta) { + if (delta === void 0) { + delta = 0; + } + const a1 = p2[1] - p1[1]; + const b1 = p1[0] - p2[0]; + const c1 = a1 * p1[0] + b1 * p1[1]; + const a2 = q2[1] - q1[1]; + const b2 = q1[0] - q2[0]; + const c2 = a2 * q1[0] + b2 * q1[1]; + const det = a1 * b2 - a2 * b1; + if (!scalarsEqual(det, 0, delta)) { + return [(b2 * c1 - b1 * c2) / det, (a1 * c2 - a2 * c1) / det]; + } else { + return [0, 0]; + } } - } - // solve smallest poly first - if (lowerPoly.length < upperPoly.length) { - quickDecomp(lowerPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level); - quickDecomp(upperPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level); - } else { - quickDecomp(upperPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level); - quickDecomp(lowerPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level); - } - return result; - } - } - result.push(polygon); - return result; -} - -/** - * Remove collinear points in the polygon. - * @param thresholdAngle The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision. - * @return The number of points removed - */ -function removeCollinearPoints(polygon, thresholdAngle) { - if (thresholdAngle === void 0) { - thresholdAngle = 0; - } - let num = 0; - for (let i = polygon.length - 1; polygon.length > 3 && i >= 0; --i) { - if (collinear(polygonAt(polygon, i - 1), polygonAt(polygon, i), polygonAt(polygon, i + 1), thresholdAngle)) { - // Remove the middle point - polygon.splice(i % polygon.length, 1); - num++; - } - } - return num; -} - -/** - * Check if two scalars are equal - * @param a scalar a - * @param b scalar b - * @param precision the precision for the equality check - * @return whether the two scalars are equal with the given precision - */ -function scalarsEqual(a, b, precision) { - if (precision === void 0) { - precision = 0; - } - precision = precision || 0; - return Math.abs(a - b) <= precision; -} - -/** - * Check if two points are equal - * @param a point a - * @param b point b - * @param precision the precision for the equality check - * @return if the two points are equal - */ -function pointsEqual(a, b, precision) { - if (precision === void 0) { - precision = 0; - } - return scalarsEqual(a[0], b[0], precision) && scalarsEqual(a[1], b[1], precision); -} - -/** - * Remove duplicate points in the polygon. - * @param precision The threshold to use when determining whether two points are the same. Use zero for best precision. - */ -function removeDuplicatePoints(polygon, precision) { - if (precision === void 0) { - precision = 0; - } - for (let i = polygon.length - 1; i >= 1; --i) { - const pi = polygon[i]; - for (let j = i - 1; j >= 0; --j) { - if (pointsEqual(pi, polygon[j], precision)) { - polygon.splice(i, 1); - continue; - } - } - } -} + /** + * Quickly decompose the Polygon into convex sub-polygons. + * @param polygon the polygon to decompose + * @param result + * @param reflexVertices + * @param steinerPoints + * @param delta + * @param maxlevel + * @param level + * @return the decomposed sub-polygons + */ + function quickDecomp( + polygon, + result, + reflexVertices, + steinerPoints, + delta, + maxlevel, + level, + ) { + if (result === void 0) { + result = []; + } + if (reflexVertices === void 0) { + reflexVertices = []; + } + if (steinerPoints === void 0) { + steinerPoints = []; + } + if (delta === void 0) { + delta = 25; + } + if (maxlevel === void 0) { + maxlevel = 100; + } + if (level === void 0) { + level = 0; + } + // Points + let upperInt = [0, 0]; + let lowerInt = [0, 0]; + let p = [0, 0]; + + // scalars + let upperDist = 0; + let lowerDist = 0; + let d = 0; + let closestDist = 0; + + // Integers + let upperIndex = 0; + let lowerIndex = 0; + let closestIndex = 0; + + // polygons + const lowerPoly = []; + const upperPoly = []; + const poly = polygon; + const v = polygon; + if (v.length < 3) { + return result; + } + level++; + if (level > maxlevel) { + console.warn("quickDecomp: max level (" + maxlevel + ") reached."); + return result; + } + for (let i = 0; i < polygon.length; ++i) { + if (polygonIsReflex(poly, i)) { + reflexVertices.push(poly[i]); + upperDist = lowerDist = Number.MAX_VALUE; + for (let j = 0; j < polygon.length; ++j) { + 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 + p = getIntersectionPoint( + polygonAt(poly, i - 1), + polygonAt(poly, i), + polygonAt(poly, j), + polygonAt(poly, j - 1), + ); // find the point of intersection + if (isRight(polygonAt(poly, i + 1), polygonAt(poly, i), p)) { + // make sure it's inside the poly + d = sqdist(poly[i], p); + if (d < lowerDist) { + // keep only the closest intersection + lowerDist = d; + lowerInt = p; + lowerIndex = j; + } + } + } + if ( + isLeft( + polygonAt(poly, i + 1), + polygonAt(poly, i), + polygonAt(poly, j + 1), + ) && + isRightOn( + polygonAt(poly, i + 1), + polygonAt(poly, i), + polygonAt(poly, j), + ) + ) { + p = getIntersectionPoint( + polygonAt(poly, i + 1), + polygonAt(poly, i), + polygonAt(poly, j), + polygonAt(poly, j + 1), + ); + if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), p)) { + d = sqdist(poly[i], p); + if (d < upperDist) { + upperDist = d; + upperInt = p; + upperIndex = j; + } + } + } + } + + // if there are no vertices to connect to, choose a point in the middle + if (lowerIndex === (upperIndex + 1) % polygon.length) { + p[0] = (lowerInt[0] + upperInt[0]) / 2; + p[1] = (lowerInt[1] + upperInt[1]) / 2; + steinerPoints.push(p); + if (i < upperIndex) { + polygonAppend(lowerPoly, poly, i, upperIndex + 1); + lowerPoly.push(p); + upperPoly.push(p); + if (lowerIndex !== 0) { + polygonAppend(upperPoly, poly, lowerIndex, poly.length); + } + polygonAppend(upperPoly, poly, 0, i + 1); + } else { + if (i !== 0) { + polygonAppend(lowerPoly, poly, i, poly.length); + } + polygonAppend(lowerPoly, poly, 0, upperIndex + 1); + lowerPoly.push(p); + upperPoly.push(p); + polygonAppend(upperPoly, poly, lowerIndex, i + 1); + } + } else { + // connect to the closest point within the triangle + if (lowerIndex > upperIndex) { + upperIndex += polygon.length; + } + closestDist = Number.MAX_VALUE; + if (upperIndex < lowerIndex) { + return result; + } + for (let j = lowerIndex; j <= upperIndex; ++j) { + if ( + isLeftOn( + polygonAt(poly, i - 1), + polygonAt(poly, i), + polygonAt(poly, j), + ) && + isRightOn( + polygonAt(poly, i + 1), + polygonAt(poly, i), + polygonAt(poly, j), + ) + ) { + d = sqdist(polygonAt(poly, i), polygonAt(poly, j)); + if (d < closestDist && polygonCanSee2(poly, i, j)) { + closestDist = d; + closestIndex = j % polygon.length; + } + } + } + if (i < closestIndex) { + polygonAppend(lowerPoly, poly, i, closestIndex + 1); + if (closestIndex !== 0) { + polygonAppend(upperPoly, poly, closestIndex, v.length); + } + polygonAppend(upperPoly, poly, 0, i + 1); + } else { + if (i !== 0) { + polygonAppend(lowerPoly, poly, i, v.length); + } + polygonAppend(lowerPoly, poly, 0, closestIndex + 1); + polygonAppend(upperPoly, poly, closestIndex, i + 1); + } + } + + // solve smallest poly first + if (lowerPoly.length < upperPoly.length) { + quickDecomp( + lowerPoly, + result, + reflexVertices, + steinerPoints, + delta, + maxlevel, + level, + ); + quickDecomp( + upperPoly, + result, + reflexVertices, + steinerPoints, + delta, + maxlevel, + level, + ); + } else { + quickDecomp( + upperPoly, + result, + reflexVertices, + steinerPoints, + delta, + maxlevel, + level, + ); + quickDecomp( + lowerPoly, + result, + reflexVertices, + steinerPoints, + delta, + maxlevel, + level, + ); + } + return result; + } + } + result.push(polygon); + return result; + } + + /** + * Remove collinear points in the polygon. + * @param thresholdAngle The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision. + * @return The number of points removed + */ + function removeCollinearPoints(polygon, thresholdAngle) { + if (thresholdAngle === void 0) { + thresholdAngle = 0; + } + let num = 0; + for (let i = polygon.length - 1; polygon.length > 3 && i >= 0; --i) { + if ( + collinear( + polygonAt(polygon, i - 1), + polygonAt(polygon, i), + polygonAt(polygon, i + 1), + thresholdAngle, + ) + ) { + // Remove the middle point + polygon.splice(i % polygon.length, 1); + num++; + } + } + return num; + } + /** + * Check if two scalars are equal + * @param a scalar a + * @param b scalar b + * @param precision the precision for the equality check + * @return whether the two scalars are equal with the given precision + */ + function scalarsEqual(a, b, precision) { + if (precision === void 0) { + precision = 0; + } + precision = precision || 0; + return Math.abs(a - b) <= precision; + } + /** + * Check if two points are equal + * @param a point a + * @param b point b + * @param precision the precision for the equality check + * @return if the two points are equal + */ + function pointsEqual(a, b, precision) { + if (precision === void 0) { + precision = 0; + } + return ( + scalarsEqual(a[0], b[0], precision) && + scalarsEqual(a[1], b[1], precision) + ); + } + /** + * Remove duplicate points in the polygon. + * @param precision The threshold to use when determining whether two points are the same. Use zero for best precision. + */ + function removeDuplicatePoints(polygon, precision) { + if (precision === void 0) { + precision = 0; + } + for (let i = polygon.length - 1; i >= 1; --i) { + const pi = polygon[i]; + for (let j = i - 1; j >= 0; --j) { + if (pointsEqual(pi, polygon[j], precision)) { + polygon.splice(i, 1); + continue; + } + } + } + } -/***/ }), + /***/ + }, -/***/ "./node_modules/random-seed/index.js": -/*!*******************************************!*\ + /***/ "./node_modules/random-seed/index.js": + /*!*******************************************!*\ !*** ./node_modules/random-seed/index.js ***! \*******************************************/ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -"use strict"; -/* - * random-seed - * https://github.com/skratchdot/random-seed - * - * This code was originally written by Steve Gibson and can be found here: - * - * https://www.grc.com/otg/uheprng.htm - * - * It was slightly modified for use in node, to pass jshint, and a few additional - * helper functions were added. - * - * Copyright (c) 2013 skratchdot - * Dual Licensed under the MIT license and the original GRC copyright/license - * included below. - */ -/* ============================================================================ + /***/ (module, __unused_webpack_exports, __webpack_require__) => { + "use strict"; + /* + * random-seed + * https://github.com/skratchdot/random-seed + * + * This code was originally written by Steve Gibson and can be found here: + * + * https://www.grc.com/otg/uheprng.htm + * + * It was slightly modified for use in node, to pass jshint, and a few additional + * helper functions were added. + * + * Copyright (c) 2013 skratchdot + * Dual Licensed under the MIT license and the original GRC copyright/license + * included below. + */ + /* ============================================================================ Gibson Research Corporation UHEPRNG - Ultra High Entropy Pseudo-Random Number Generator ============================================================================ @@ -755,3364 +935,3739 @@ function removeDuplicatePoints(polygon, precision) { 1460910 and 1768863. (We use the largest one that's < 2^21) ============================================================================ */ -var stringify = __webpack_require__(/*! json-stringify-safe */ "./node_modules/json-stringify-safe/stringify.js"); + var stringify = __webpack_require__( + /*! json-stringify-safe */ "./node_modules/json-stringify-safe/stringify.js", + ); -/* ============================================================================ + /* ============================================================================ This is based upon Johannes Baagoe's carefully designed and efficient hash function for use with JavaScript. It has a proven "avalanche" effect such that every bit of the input affects every bit of the output 50% of the time, which is good. See: http://baagoe.com/en/RandomMusings/hash/avalanche.xhtml ============================================================================ */ -var Mash = function () { - var n = 0xefc8249d; - var mash = function (data) { - if (data) { - data = data.toString(); - for (var i = 0; i < data.length; i++) { - n += data.charCodeAt(i); - var h = 0.02519603282416938 * n; - n = h >>> 0; - h -= n; - h *= n; - n = h >>> 0; - h -= n; - n += h * 0x100000000; // 2^32 - } - return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 - } else { - n = 0xefc8249d; - } - }; - return mash; -}; - -var uheprng = function (seed) { - return (function () { - var o = 48; // set the 'order' number of ENTROPY-holding 32-bit values - var c = 1; // init the 'carry' used by the multiply-with-carry (MWC) algorithm - var p = o; // init the 'phase' (max-1) of the intermediate variable pointer - var s = new Array(o); // declare our intermediate variables array - var i; // general purpose local - var j; // general purpose local - var k = 0; // general purpose local - - // when our "uheprng" is initially invoked our PRNG state is initialized from the - // browser's own local PRNG. This is okay since although its generator might not - // be wonderful, it's useful for establishing large startup entropy for our usage. - var mash = new Mash(); // get a pointer to our high-performance "Mash" hash - - // fill the array with initial mash hash values - for (i = 0; i < o; i++) { - s[i] = mash(Math.random()); - } - - // this PRIVATE (internal access only) function is the heart of the multiply-with-carry - // (MWC) PRNG algorithm. When called it returns a pseudo-random number in the form of a - // 32-bit JavaScript fraction (0.0 to <1.0) it is a PRIVATE function used by the default - // [0-1] return function, and by the random 'string(n)' function which returns 'n' - // characters from 33 to 126. - var rawprng = function () { - if (++p >= o) { - p = 0; - } - var t = 1768863 * s[p] + c * 2.3283064365386963e-10; // 2^-32 - return s[p] = t - (c = t | 0); - }; - - // this EXPORTED function is the default function returned by this library. - // The values returned are integers in the range from 0 to range-1. We first - // obtain two 32-bit fractions (from rawprng) to synthesize a single high - // resolution 53-bit prng (0 to <1), then we multiply this by the caller's - // "range" param and take the "floor" to return a equally probable integer. - var random = function (range) { - return Math.floor(range * (rawprng() + (rawprng() * 0x200000 | 0) * 1.1102230246251565e-16)); // 2^-53 - }; - - // this EXPORTED function 'string(n)' returns a pseudo-random string of - // 'n' printable characters ranging from chr(33) to chr(126) inclusive. - random.string = function (count) { - var i; - var s = ''; - for (i = 0; i < count; i++) { - s += String.fromCharCode(33 + random(94)); - } - return s; - }; - - // this PRIVATE "hash" function is used to evolve the generator's internal - // entropy state. It is also called by the EXPORTED addEntropy() function - // which is used to pour entropy into the PRNG. - var hash = function () { - var args = Array.prototype.slice.call(arguments); - for (i = 0; i < args.length; i++) { - for (j = 0; j < o; j++) { - s[j] -= mash(args[i]); - if (s[j] < 0) { - s[j] += 1; - } - } - } - }; - - // this EXPORTED "clean string" function removes leading and trailing spaces and non-printing - // control characters, including any embedded carriage-return (CR) and line-feed (LF) characters, - // from any string it is handed. this is also used by the 'hashstring' function (below) to help - // users always obtain the same EFFECTIVE uheprng seeding key. - random.cleanString = function (inStr) { - inStr = inStr.replace(/(^\s*)|(\s*$)/gi, ''); // remove any/all leading spaces - inStr = inStr.replace(/[\x00-\x1F]/gi, ''); // remove any/all control characters - inStr = inStr.replace(/\n /, '\n'); // remove any/all trailing spaces - return inStr; // return the cleaned up result - }; - - // this EXPORTED "hash string" function hashes the provided character string after first removing - // any leading or trailing spaces and ignoring any embedded carriage returns (CR) or Line Feeds (LF) - random.hashString = function (inStr) { - inStr = random.cleanString(inStr); - mash(inStr); // use the string to evolve the 'mash' state - for (i = 0; i < inStr.length; i++) { // scan through the characters in our string - k = inStr.charCodeAt(i); // get the character code at the location - for (j = 0; j < o; j++) { // "mash" it into the UHEPRNG state - s[j] -= mash(k); - if (s[j] < 0) { - s[j] += 1; - } - } - } - }; - - // this EXPORTED function allows you to seed the random generator. - random.seed = function (seed) { - if (typeof seed === 'undefined' || seed === null) { - seed = Math.random(); - } - if (typeof seed !== 'string') { - seed = stringify(seed, function (key, value) { - if (typeof value === 'function') { - return (value).toString(); - } - return value; - }); - } - random.initState(); - random.hashString(seed); - }; - - // this handy exported function is used to add entropy to our uheprng at any time - random.addEntropy = function ( /* accept zero or more arguments */ ) { - var args = []; - for (i = 0; i < arguments.length; i++) { - args.push(arguments[i]); - } - hash((k++) + (new Date().getTime()) + args.join('') + Math.random()); - }; - - // if we want to provide a deterministic startup context for our PRNG, - // but without directly setting the internal state variables, this allows - // us to initialize the mash hash and PRNG's internal state before providing - // some hashing input - random.initState = function () { - mash(); // pass a null arg to force mash hash to init - for (i = 0; i < o; i++) { - s[i] = mash(' '); // fill the array with initial mash hash values - } - c = 1; // init our multiply-with-carry carry - p = o; // init our phase - }; - - // we use this (optional) exported function to signal the JavaScript interpreter - // that we're finished using the "Mash" hash function so that it can free up the - // local "instance variables" is will have been maintaining. It's not strictly - // necessary, of course, but it's good JavaScript citizenship. - random.done = function () { - mash = null; - }; - - // if we called "uheprng" with a seed value, then execute random.seed() before returning - if (typeof seed !== 'undefined') { - random.seed(seed); - } - - // Returns a random integer between 0 (inclusive) and range (exclusive) - random.range = function (range) { - return random(range); - }; - - // Returns a random float between 0 (inclusive) and 1 (exclusive) - random.random = function () { - return random(Number.MAX_VALUE - 1) / Number.MAX_VALUE; - }; - - // Returns a random float between min (inclusive) and max (exclusive) - random.floatBetween = function (min, max) { - return random.random() * (max - min) + min; - }; - - // Returns a random integer between min (inclusive) and max (inclusive) - random.intBetween = function (min, max) { - return Math.floor(random.random() * (max - min + 1)) + min; - }; - - // when our main outer "uheprng" function is called, after setting up our - // initial variables and entropic state, we return an "instance pointer" - // to the internal anonymous function which can then be used to access - // the uheprng's various exported functions. As with the ".done" function - // above, we should set the returned value to 'null' once we're finished - // using any of these functions. - return random; - }()); -}; - -// Modification for use in node: -uheprng.create = function (seed) { - return new uheprng(seed); -}; -module.exports = uheprng; - - -/***/ }), - -/***/ "./node_modules/sat/SAT.js": -/*!*********************************!*\ - !*** ./node_modules/sat/SAT.js ***! - \*********************************/ -/***/ (function(module, exports, __webpack_require__) { - -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Version 0.9.0 - Copyright 2012 - 2021 - Jim Riecken -// -// Released under the MIT License - https://github.com/jriecken/sat-js -// -// A simple library for determining intersections of circles and -// polygons using the Separating Axis Theorem. -/** @preserve SAT.js - Version 0.9.0 - Copyright 2012 - 2021 - Jim Riecken - released under the MIT License. https://github.com/jriecken/sat-js */ - -/*global define: false, module: false*/ -/*jshint shadow:true, sub:true, forin:true, noarg:true, noempty:true, - eqeqeq:true, bitwise:true, strict:true, undef:true, - curly:true, browser:true */ - -// Create a UMD wrapper for SAT. Works in: -// -// - Plain browser via global SAT variable -// - AMD loader (like require.js) -// - Node.js -// -// The quoted properties all over the place are used so that the Closure Compiler -// does not mangle the exposed API in advanced mode. -/** - * @param {*} root - The global scope - * @param {Function} factory - Factory that creates SAT module - */ -(function (root, factory) { - "use strict"; - if (true) { - !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : - __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } else {} -}(this, function () { - "use strict"; - - var SAT = {}; - - // - // ## Vector - // - // Represents a vector in two dimensions with `x` and `y` properties. - - - // Create a new Vector, optionally passing in the `x` and `y` coordinates. If - // a coordinate is not specified, it will be set to `0` - /** - * @param {?number=} x The x position. - * @param {?number=} y The y position. - * @constructor - */ - function Vector(x, y) { - this['x'] = x || 0; - this['y'] = y || 0; - } - SAT['Vector'] = Vector; - // Alias `Vector` as `V` - SAT['V'] = Vector; - - - // Copy the values of another Vector into this one. - /** - * @param {Vector} other The other Vector. - * @return {Vector} This for chaining. - */ - Vector.prototype['copy'] = Vector.prototype.copy = function (other) { - this['x'] = other['x']; - this['y'] = other['y']; - return this; - }; - - // Create a new vector with the same coordinates as this on. - /** - * @return {Vector} The new cloned vector - */ - Vector.prototype['clone'] = Vector.prototype.clone = function () { - return new Vector(this['x'], this['y']); - }; - - // Change this vector to be perpendicular to what it was before. (Effectively - // roatates it 90 degrees in a clockwise direction) - /** - * @return {Vector} This for chaining. - */ - Vector.prototype['perp'] = Vector.prototype.perp = function () { - var x = this['x']; - this['x'] = this['y']; - this['y'] = -x; - return this; - }; - - // Rotate this vector (counter-clockwise) by the specified angle (in radians). - /** - * @param {number} angle The angle to rotate (in radians) - * @return {Vector} This for chaining. - */ - Vector.prototype['rotate'] = Vector.prototype.rotate = function (angle) { - var x = this['x']; - var y = this['y']; - this['x'] = x * Math.cos(angle) - y * Math.sin(angle); - this['y'] = x * Math.sin(angle) + y * Math.cos(angle); - return this; - }; - - // Reverse this vector. - /** - * @return {Vector} This for chaining. - */ - Vector.prototype['reverse'] = Vector.prototype.reverse = function () { - this['x'] = -this['x']; - this['y'] = -this['y']; - return this; - }; - + var Mash = function () { + var n = 0xefc8249d; + var mash = function (data) { + if (data) { + data = data.toString(); + for (var i = 0; i < data.length; i++) { + n += data.charCodeAt(i); + var h = 0.02519603282416938 * n; + n = h >>> 0; + h -= n; + h *= n; + n = h >>> 0; + h -= n; + n += h * 0x100000000; // 2^32 + } + return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 + } else { + n = 0xefc8249d; + } + }; + return mash; + }; - // Normalize this vector. (make it have length of `1`) - /** - * @return {Vector} This for chaining. - */ - Vector.prototype['normalize'] = Vector.prototype.normalize = function () { - var d = this.len(); - if (d > 0) { - this['x'] = this['x'] / d; - this['y'] = this['y'] / d; - } - return this; - }; + var uheprng = function (seed) { + return (function () { + var o = 48; // set the 'order' number of ENTROPY-holding 32-bit values + var c = 1; // init the 'carry' used by the multiply-with-carry (MWC) algorithm + var p = o; // init the 'phase' (max-1) of the intermediate variable pointer + var s = new Array(o); // declare our intermediate variables array + var i; // general purpose local + var j; // general purpose local + var k = 0; // general purpose local + + // when our "uheprng" is initially invoked our PRNG state is initialized from the + // browser's own local PRNG. This is okay since although its generator might not + // be wonderful, it's useful for establishing large startup entropy for our usage. + var mash = new Mash(); // get a pointer to our high-performance "Mash" hash + + // fill the array with initial mash hash values + for (i = 0; i < o; i++) { + s[i] = mash(Math.random()); + } - // Add another vector to this one. - /** - * @param {Vector} other The other Vector. - * @return {Vector} This for chaining. - */ - Vector.prototype['add'] = Vector.prototype.add = function (other) { - this['x'] += other['x']; - this['y'] += other['y']; - return this; - }; + // this PRIVATE (internal access only) function is the heart of the multiply-with-carry + // (MWC) PRNG algorithm. When called it returns a pseudo-random number in the form of a + // 32-bit JavaScript fraction (0.0 to <1.0) it is a PRIVATE function used by the default + // [0-1] return function, and by the random 'string(n)' function which returns 'n' + // characters from 33 to 126. + var rawprng = function () { + if (++p >= o) { + p = 0; + } + var t = 1768863 * s[p] + c * 2.3283064365386963e-10; // 2^-32 + return (s[p] = t - (c = t | 0)); + }; + + // this EXPORTED function is the default function returned by this library. + // The values returned are integers in the range from 0 to range-1. We first + // obtain two 32-bit fractions (from rawprng) to synthesize a single high + // resolution 53-bit prng (0 to <1), then we multiply this by the caller's + // "range" param and take the "floor" to return a equally probable integer. + var random = function (range) { + return Math.floor( + range * + (rawprng() + + ((rawprng() * 0x200000) | 0) * 1.1102230246251565e-16), + ); // 2^-53 + }; + + // this EXPORTED function 'string(n)' returns a pseudo-random string of + // 'n' printable characters ranging from chr(33) to chr(126) inclusive. + random.string = function (count) { + var i; + var s = ""; + for (i = 0; i < count; i++) { + s += String.fromCharCode(33 + random(94)); + } + return s; + }; + + // this PRIVATE "hash" function is used to evolve the generator's internal + // entropy state. It is also called by the EXPORTED addEntropy() function + // which is used to pour entropy into the PRNG. + var hash = function () { + var args = Array.prototype.slice.call(arguments); + for (i = 0; i < args.length; i++) { + for (j = 0; j < o; j++) { + s[j] -= mash(args[i]); + if (s[j] < 0) { + s[j] += 1; + } + } + } + }; + + // this EXPORTED "clean string" function removes leading and trailing spaces and non-printing + // control characters, including any embedded carriage-return (CR) and line-feed (LF) characters, + // from any string it is handed. this is also used by the 'hashstring' function (below) to help + // users always obtain the same EFFECTIVE uheprng seeding key. + random.cleanString = function (inStr) { + inStr = inStr.replace(/(^\s*)|(\s*$)/gi, ""); // remove any/all leading spaces + inStr = inStr.replace(/[\x00-\x1F]/gi, ""); // remove any/all control characters + inStr = inStr.replace(/\n /, "\n"); // remove any/all trailing spaces + return inStr; // return the cleaned up result + }; + + // this EXPORTED "hash string" function hashes the provided character string after first removing + // any leading or trailing spaces and ignoring any embedded carriage returns (CR) or Line Feeds (LF) + random.hashString = function (inStr) { + inStr = random.cleanString(inStr); + mash(inStr); // use the string to evolve the 'mash' state + for (i = 0; i < inStr.length; i++) { + // scan through the characters in our string + k = inStr.charCodeAt(i); // get the character code at the location + for (j = 0; j < o; j++) { + // "mash" it into the UHEPRNG state + s[j] -= mash(k); + if (s[j] < 0) { + s[j] += 1; + } + } + } + }; + + // this EXPORTED function allows you to seed the random generator. + random.seed = function (seed) { + if (typeof seed === "undefined" || seed === null) { + seed = Math.random(); + } + if (typeof seed !== "string") { + seed = stringify(seed, function (key, value) { + if (typeof value === "function") { + return value.toString(); + } + return value; + }); + } + random.initState(); + random.hashString(seed); + }; + + // this handy exported function is used to add entropy to our uheprng at any time + random.addEntropy = function (/* accept zero or more arguments */) { + var args = []; + for (i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + hash(k++ + new Date().getTime() + args.join("") + Math.random()); + }; + + // if we want to provide a deterministic startup context for our PRNG, + // but without directly setting the internal state variables, this allows + // us to initialize the mash hash and PRNG's internal state before providing + // some hashing input + random.initState = function () { + mash(); // pass a null arg to force mash hash to init + for (i = 0; i < o; i++) { + s[i] = mash(" "); // fill the array with initial mash hash values + } + c = 1; // init our multiply-with-carry carry + p = o; // init our phase + }; + + // we use this (optional) exported function to signal the JavaScript interpreter + // that we're finished using the "Mash" hash function so that it can free up the + // local "instance variables" is will have been maintaining. It's not strictly + // necessary, of course, but it's good JavaScript citizenship. + random.done = function () { + mash = null; + }; + + // if we called "uheprng" with a seed value, then execute random.seed() before returning + if (typeof seed !== "undefined") { + random.seed(seed); + } - // Subtract another vector from this one. - /** - * @param {Vector} other The other Vector. - * @return {Vector} This for chaiing. - */ - Vector.prototype['sub'] = Vector.prototype.sub = function (other) { - this['x'] -= other['x']; - this['y'] -= other['y']; - return this; - }; + // Returns a random integer between 0 (inclusive) and range (exclusive) + random.range = function (range) { + return random(range); + }; + + // Returns a random float between 0 (inclusive) and 1 (exclusive) + random.random = function () { + return random(Number.MAX_VALUE - 1) / Number.MAX_VALUE; + }; + + // Returns a random float between min (inclusive) and max (exclusive) + random.floatBetween = function (min, max) { + return random.random() * (max - min) + min; + }; + + // Returns a random integer between min (inclusive) and max (inclusive) + random.intBetween = function (min, max) { + return Math.floor(random.random() * (max - min + 1)) + min; + }; + + // when our main outer "uheprng" function is called, after setting up our + // initial variables and entropic state, we return an "instance pointer" + // to the internal anonymous function which can then be used to access + // the uheprng's various exported functions. As with the ".done" function + // above, we should set the returned value to 'null' once we're finished + // using any of these functions. + return random; + })(); + }; - // Scale this vector. An independent scaling factor can be provided - // for each axis, or a single scaling factor that will scale both `x` and `y`. - /** - * @param {number} x The scaling factor in the x direction. - * @param {?number=} y The scaling factor in the y direction. If this - * is not specified, the x scaling factor will be used. - * @return {Vector} This for chaining. - */ - Vector.prototype['scale'] = Vector.prototype.scale = function (x, y) { - this['x'] *= x; - this['y'] *= typeof y != 'undefined' ? y : x; - return this; - }; + // Modification for use in node: + uheprng.create = function (seed) { + return new uheprng(seed); + }; + module.exports = uheprng; - // Project this vector on to another vector. - /** - * @param {Vector} other The vector to project onto. - * @return {Vector} This for chaining. - */ - Vector.prototype['project'] = Vector.prototype.project = function (other) { - var amt = this.dot(other) / other.len2(); - this['x'] = amt * other['x']; - this['y'] = amt * other['y']; - return this; - }; + /***/ + }, - // Project this vector onto a vector of unit length. This is slightly more efficient - // than `project` when dealing with unit vectors. - /** - * @param {Vector} other The unit vector to project onto. - * @return {Vector} This for chaining. - */ - Vector.prototype['projectN'] = Vector.prototype.projectN = function (other) { - var amt = this.dot(other); - this['x'] = amt * other['x']; - this['y'] = amt * other['y']; - return this; - }; + /***/ "./node_modules/sat/SAT.js": + /*!*********************************!*\ + !*** ./node_modules/sat/SAT.js ***! + \*********************************/ + /***/ function (module, exports, __webpack_require__) { + var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__; // Version 0.9.0 - Copyright 2012 - 2021 - Jim Riecken + // + // Released under the MIT License - https://github.com/jriecken/sat-js + // + // A simple library for determining intersections of circles and + // polygons using the Separating Axis Theorem. + /** @preserve SAT.js - Version 0.9.0 - Copyright 2012 - 2021 - Jim Riecken - released under the MIT License. https://github.com/jriecken/sat-js */ + + /*global define: false, module: false*/ + /*jshint shadow:true, sub:true, forin:true, noarg:true, noempty:true, + eqeqeq:true, bitwise:true, strict:true, undef:true, + curly:true, browser:true */ - // Reflect this vector on an arbitrary axis. - /** - * @param {Vector} axis The vector representing the axis. - * @return {Vector} This for chaining. - */ - Vector.prototype['reflect'] = Vector.prototype.reflect = function (axis) { - var x = this['x']; - var y = this['y']; - this.project(axis).scale(2); - this['x'] -= x; - this['y'] -= y; - return this; - }; - - // Reflect this vector on an arbitrary axis (represented by a unit vector). This is - // slightly more efficient than `reflect` when dealing with an axis that is a unit vector. - /** - * @param {Vector} axis The unit vector representing the axis. - * @return {Vector} This for chaining. - */ - Vector.prototype['reflectN'] = Vector.prototype.reflectN = function (axis) { - var x = this['x']; - var y = this['y']; - this.projectN(axis).scale(2); - this['x'] -= x; - this['y'] -= y; - return this; - }; + // Create a UMD wrapper for SAT. Works in: + // + // - Plain browser via global SAT variable + // - AMD loader (like require.js) + // - Node.js + // + // The quoted properties all over the place are used so that the Closure Compiler + // does not mangle the exposed API in advanced mode. + /** + * @param {*} root - The global scope + * @param {Function} factory - Factory that creates SAT module + */ + (function (root, factory) { + "use strict"; + if (true) { + !((__WEBPACK_AMD_DEFINE_FACTORY__ = factory), + (__WEBPACK_AMD_DEFINE_RESULT__ = + typeof __WEBPACK_AMD_DEFINE_FACTORY__ === "function" + ? __WEBPACK_AMD_DEFINE_FACTORY__.call( + exports, + __webpack_require__, + exports, + module, + ) + : __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && + (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else { + } + })(this, function () { + "use strict"; + + var SAT = {}; + + // + // ## Vector + // + // Represents a vector in two dimensions with `x` and `y` properties. + + // Create a new Vector, optionally passing in the `x` and `y` coordinates. If + // a coordinate is not specified, it will be set to `0` + /** + * @param {?number=} x The x position. + * @param {?number=} y The y position. + * @constructor + */ + function Vector(x, y) { + this["x"] = x || 0; + this["y"] = y || 0; + } + SAT["Vector"] = Vector; + // Alias `Vector` as `V` + SAT["V"] = Vector; + + // Copy the values of another Vector into this one. + /** + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + Vector.prototype["copy"] = Vector.prototype.copy = function (other) { + this["x"] = other["x"]; + this["y"] = other["y"]; + return this; + }; + + // Create a new vector with the same coordinates as this on. + /** + * @return {Vector} The new cloned vector + */ + Vector.prototype["clone"] = Vector.prototype.clone = function () { + return new Vector(this["x"], this["y"]); + }; + + // Change this vector to be perpendicular to what it was before. (Effectively + // roatates it 90 degrees in a clockwise direction) + /** + * @return {Vector} This for chaining. + */ + Vector.prototype["perp"] = Vector.prototype.perp = function () { + var x = this["x"]; + this["x"] = this["y"]; + this["y"] = -x; + return this; + }; + + // Rotate this vector (counter-clockwise) by the specified angle (in radians). + /** + * @param {number} angle The angle to rotate (in radians) + * @return {Vector} This for chaining. + */ + Vector.prototype["rotate"] = Vector.prototype.rotate = function ( + angle, + ) { + var x = this["x"]; + var y = this["y"]; + this["x"] = x * Math.cos(angle) - y * Math.sin(angle); + this["y"] = x * Math.sin(angle) + y * Math.cos(angle); + return this; + }; + + // Reverse this vector. + /** + * @return {Vector} This for chaining. + */ + Vector.prototype["reverse"] = Vector.prototype.reverse = function () { + this["x"] = -this["x"]; + this["y"] = -this["y"]; + return this; + }; + + // Normalize this vector. (make it have length of `1`) + /** + * @return {Vector} This for chaining. + */ + Vector.prototype["normalize"] = Vector.prototype.normalize = + function () { + var d = this.len(); + if (d > 0) { + this["x"] = this["x"] / d; + this["y"] = this["y"] / d; + } + return this; + }; + + // Add another vector to this one. + /** + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + Vector.prototype["add"] = Vector.prototype.add = function (other) { + this["x"] += other["x"]; + this["y"] += other["y"]; + return this; + }; + + // Subtract another vector from this one. + /** + * @param {Vector} other The other Vector. + * @return {Vector} This for chaiing. + */ + Vector.prototype["sub"] = Vector.prototype.sub = function (other) { + this["x"] -= other["x"]; + this["y"] -= other["y"]; + return this; + }; + + // Scale this vector. An independent scaling factor can be provided + // for each axis, or a single scaling factor that will scale both `x` and `y`. + /** + * @param {number} x The scaling factor in the x direction. + * @param {?number=} y The scaling factor in the y direction. If this + * is not specified, the x scaling factor will be used. + * @return {Vector} This for chaining. + */ + Vector.prototype["scale"] = Vector.prototype.scale = function (x, y) { + this["x"] *= x; + this["y"] *= typeof y != "undefined" ? y : x; + return this; + }; + + // Project this vector on to another vector. + /** + * @param {Vector} other The vector to project onto. + * @return {Vector} This for chaining. + */ + Vector.prototype["project"] = Vector.prototype.project = function ( + other, + ) { + var amt = this.dot(other) / other.len2(); + this["x"] = amt * other["x"]; + this["y"] = amt * other["y"]; + return this; + }; + + // Project this vector onto a vector of unit length. This is slightly more efficient + // than `project` when dealing with unit vectors. + /** + * @param {Vector} other The unit vector to project onto. + * @return {Vector} This for chaining. + */ + Vector.prototype["projectN"] = Vector.prototype.projectN = function ( + other, + ) { + var amt = this.dot(other); + this["x"] = amt * other["x"]; + this["y"] = amt * other["y"]; + return this; + }; + + // Reflect this vector on an arbitrary axis. + /** + * @param {Vector} axis The vector representing the axis. + * @return {Vector} This for chaining. + */ + Vector.prototype["reflect"] = Vector.prototype.reflect = function ( + axis, + ) { + var x = this["x"]; + var y = this["y"]; + this.project(axis).scale(2); + this["x"] -= x; + this["y"] -= y; + return this; + }; + + // Reflect this vector on an arbitrary axis (represented by a unit vector). This is + // slightly more efficient than `reflect` when dealing with an axis that is a unit vector. + /** + * @param {Vector} axis The unit vector representing the axis. + * @return {Vector} This for chaining. + */ + Vector.prototype["reflectN"] = Vector.prototype.reflectN = function ( + axis, + ) { + var x = this["x"]; + var y = this["y"]; + this.projectN(axis).scale(2); + this["x"] -= x; + this["y"] -= y; + return this; + }; + + // Get the dot product of this vector and another. + /** + * @param {Vector} other The vector to dot this one against. + * @return {number} The dot product. + */ + Vector.prototype["dot"] = Vector.prototype.dot = function (other) { + return this["x"] * other["x"] + this["y"] * other["y"]; + }; + + // Get the squared length of this vector. + /** + * @return {number} The length^2 of this vector. + */ + Vector.prototype["len2"] = Vector.prototype.len2 = function () { + return this.dot(this); + }; + + // Get the length of this vector. + /** + * @return {number} The length of this vector. + */ + Vector.prototype["len"] = Vector.prototype.len = function () { + return Math.sqrt(this.len2()); + }; + + // ## Circle + // + // Represents a circle with a position and a radius. + + // Create a new circle, optionally passing in a position and/or radius. If no position + // is given, the circle will be at `(0,0)`. If no radius is provided, the circle will + // have a radius of `0`. + /** + * @param {Vector=} pos A vector representing the position of the center of the circle + * @param {?number=} r The radius of the circle + * @constructor + */ + function Circle(pos, r) { + this["pos"] = pos || new Vector(); + this["r"] = r || 0; + this["offset"] = new Vector(); + } + SAT["Circle"] = Circle; + + // Compute the axis-aligned bounding box (AABB) of this Circle. + // + // Note: Returns a _new_ `Box` each time you call this. + /** + * @return {Polygon} The AABB + */ + Circle.prototype["getAABBAsBox"] = Circle.prototype.getAABBAsBox = + function () { + var r = this["r"]; + var corner = this["pos"] + .clone() + .add(this["offset"]) + .sub(new Vector(r, r)); + return new Box(corner, r * 2, r * 2); + }; + + // Compute the axis-aligned bounding box (AABB) of this Circle. + // + // Note: Returns a _new_ `Polygon` each time you call this. + /** + * @return {Polygon} The AABB + */ + Circle.prototype["getAABB"] = Circle.prototype.getAABB = function () { + return this.getAABBAsBox().toPolygon(); + }; + + // Set the current offset to apply to the radius. + /** + * @param {Vector} offset The new offset vector. + * @return {Circle} This for chaining. + */ + Circle.prototype["setOffset"] = Circle.prototype.setOffset = + function (offset) { + this["offset"] = offset; + return this; + }; + + // ## Polygon + // + // Represents a *convex* polygon with any number of points (specified in counter-clockwise order) + // + // Note: Do _not_ manually change the `points`, `angle`, or `offset` properties. Use the + // provided setters. Otherwise the calculated properties will not be updated correctly. + // + // `pos` can be changed directly. + + // Create a new polygon, passing in a position vector, and an array of points (represented + // by vectors relative to the position vector). If no position is passed in, the position + // of the polygon will be `(0,0)`. + /** + * @param {Vector=} pos A vector representing the origin of the polygon. (all other + * points are relative to this one) + * @param {Array=} points An array of vectors representing the points in the polygon, + * in counter-clockwise order. + * @constructor + */ + function Polygon(pos, points) { + this["pos"] = pos || new Vector(); + this["angle"] = 0; + this["offset"] = new Vector(); + this.setPoints(points || []); + } + SAT["Polygon"] = Polygon; + + // Set the points of the polygon. Any consecutive duplicate points will be combined. + // + // Note: The points are counter-clockwise *with respect to the coordinate system*. + // If you directly draw the points on a screen that has the origin at the top-left corner + // it will _appear_ visually that the points are being specified clockwise. This is just + // because of the inversion of the Y-axis when being displayed. + /** + * @param {Array=} points An array of vectors representing the points in the polygon, + * in counter-clockwise order. + * @return {Polygon} This for chaining. + */ + Polygon.prototype["setPoints"] = Polygon.prototype.setPoints = + function (points) { + // Only re-allocate if this is a new polygon or the number of points has changed. + var lengthChanged = + !this["points"] || this["points"].length !== points.length; + if (lengthChanged) { + var i; + var calcPoints = (this["calcPoints"] = []); + var edges = (this["edges"] = []); + var normals = (this["normals"] = []); + // Allocate the vector arrays for the calculated properties + for (i = 0; i < points.length; i++) { + // Remove consecutive duplicate points + var p1 = points[i]; + var p2 = i < points.length - 1 ? points[i + 1] : points[0]; + if (p1 !== p2 && p1.x === p2.x && p1.y === p2.y) { + points.splice(i, 1); + i -= 1; + continue; + } + calcPoints.push(new Vector()); + edges.push(new Vector()); + normals.push(new Vector()); + } + } + this["points"] = points; + this._recalc(); + return this; + }; + + // Set the current rotation angle of the polygon. + /** + * @param {number} angle The current rotation angle (in radians). + * @return {Polygon} This for chaining. + */ + Polygon.prototype["setAngle"] = Polygon.prototype.setAngle = + function (angle) { + this["angle"] = angle; + this._recalc(); + return this; + }; + + // Set the current offset to apply to the `points` before applying the `angle` rotation. + /** + * @param {Vector} offset The new offset vector. + * @return {Polygon} This for chaining. + */ + Polygon.prototype["setOffset"] = Polygon.prototype.setOffset = + function (offset) { + this["offset"] = offset; + this._recalc(); + return this; + }; + + // Rotates this polygon counter-clockwise around the origin of *its local coordinate system* (i.e. `pos`). + // + // Note: This changes the **original** points (so any `angle` will be applied on top of this rotation). + /** + * @param {number} angle The angle to rotate (in radians) + * @return {Polygon} This for chaining. + */ + Polygon.prototype["rotate"] = Polygon.prototype.rotate = function ( + angle, + ) { + var points = this["points"]; + var len = points.length; + for (var i = 0; i < len; i++) { + points[i].rotate(angle); + } + this._recalc(); + return this; + }; + + // Translates the points of this polygon by a specified amount relative to the origin of *its own coordinate + // system* (i.e. `pos`). + // + // This is most useful to change the "center point" of a polygon. If you just want to move the whole polygon, change + // the coordinates of `pos`. + // + // Note: This changes the **original** points (so any `offset` will be applied on top of this translation) + /** + * @param {number} x The horizontal amount to translate. + * @param {number} y The vertical amount to translate. + * @return {Polygon} This for chaining. + */ + Polygon.prototype["translate"] = Polygon.prototype.translate = + function (x, y) { + var points = this["points"]; + var len = points.length; + for (var i = 0; i < len; i++) { + points[i]["x"] += x; + points[i]["y"] += y; + } + this._recalc(); + return this; + }; + + // Computes the calculated collision polygon. Applies the `angle` and `offset` to the original points then recalculates the + // edges and normals of the collision polygon. + /** + * @return {Polygon} This for chaining. + */ + Polygon.prototype._recalc = function () { + // Calculated points - this is what is used for underlying collisions and takes into account + // the angle/offset set on the polygon. + var calcPoints = this["calcPoints"]; + // The edges here are the direction of the `n`th edge of the polygon, relative to + // the `n`th point. If you want to draw a given edge from the edge value, you must + // first translate to the position of the starting point. + var edges = this["edges"]; + // The normals here are the direction of the normal for the `n`th edge of the polygon, relative + // to the position of the `n`th point. If you want to draw an edge normal, you must first + // translate to the position of the starting point. + var normals = this["normals"]; + // Copy the original points array and apply the offset/angle + var points = this["points"]; + var offset = this["offset"]; + var angle = this["angle"]; + var len = points.length; + var i; + for (i = 0; i < len; i++) { + var calcPoint = calcPoints[i].copy(points[i]); + calcPoint["x"] += offset["x"]; + calcPoint["y"] += offset["y"]; + if (angle !== 0) { + calcPoint.rotate(angle); + } + } + // Calculate the edges/normals + for (i = 0; i < len; i++) { + var p1 = calcPoints[i]; + var p2 = i < len - 1 ? calcPoints[i + 1] : calcPoints[0]; + var e = edges[i].copy(p2).sub(p1); + normals[i].copy(e).perp().normalize(); + } + return this; + }; + + // Compute the axis-aligned bounding box. Any current state + // (translations/rotations) will be applied before constructing the AABB. + // + // Note: Returns a _new_ `Box` each time you call this. + /** + * @return {Polygon} The AABB + */ + Polygon.prototype["getAABBAsBox"] = Polygon.prototype.getAABBAsBox = + function () { + var points = this["calcPoints"]; + var len = points.length; + var xMin = points[0]["x"]; + var yMin = points[0]["y"]; + var xMax = points[0]["x"]; + var yMax = points[0]["y"]; + for (var i = 1; i < len; i++) { + var point = points[i]; + if (point["x"] < xMin) { + xMin = point["x"]; + } else if (point["x"] > xMax) { + xMax = point["x"]; + } + if (point["y"] < yMin) { + yMin = point["y"]; + } else if (point["y"] > yMax) { + yMax = point["y"]; + } + } + return new Box( + this["pos"].clone().add(new Vector(xMin, yMin)), + xMax - xMin, + yMax - yMin, + ); + }; + + // Compute the axis-aligned bounding box. Any current state + // (translations/rotations) will be applied before constructing the AABB. + // + // Note: Returns a _new_ `Polygon` each time you call this. + /** + * @return {Polygon} The AABB + */ + Polygon.prototype["getAABB"] = Polygon.prototype.getAABB = + function () { + return this.getAABBAsBox().toPolygon(); + }; + + // Compute the centroid (geometric center) of the polygon. Any current state + // (translations/rotations) will be applied before computing the centroid. + // + // See https://en.wikipedia.org/wiki/Centroid#Centroid_of_a_polygon + // + // Note: Returns a _new_ `Vector` each time you call this. + /** + * @return {Vector} A Vector that contains the coordinates of the Centroid. + */ + Polygon.prototype["getCentroid"] = Polygon.prototype.getCentroid = + function () { + var points = this["calcPoints"]; + var len = points.length; + var cx = 0; + var cy = 0; + var ar = 0; + for (var i = 0; i < len; i++) { + var p1 = points[i]; + var p2 = i === len - 1 ? points[0] : points[i + 1]; // Loop around if last point + var a = p1["x"] * p2["y"] - p2["x"] * p1["y"]; + cx += (p1["x"] + p2["x"]) * a; + cy += (p1["y"] + p2["y"]) * a; + ar += a; + } + ar = ar * 3; // we want 1 / 6 the area and we currently have 2*area + cx = cx / ar; + cy = cy / ar; + return new Vector(cx, cy); + }; + + // ## Box + // + // Represents an axis-aligned box, with a width and height. + + // Create a new box, with the specified position, width, and height. If no position + // is given, the position will be `(0,0)`. If no width or height are given, they will + // be set to `0`. + /** + * @param {Vector=} pos A vector representing the bottom-left of the box (i.e. the smallest x and smallest y value). + * @param {?number=} w The width of the box. + * @param {?number=} h The height of the box. + * @constructor + */ + function Box(pos, w, h) { + this["pos"] = pos || new Vector(); + this["w"] = w || 0; + this["h"] = h || 0; + } + SAT["Box"] = Box; + + // Returns a polygon whose edges are the same as this box. + /** + * @return {Polygon} A new Polygon that represents this box. + */ + Box.prototype["toPolygon"] = Box.prototype.toPolygon = function () { + var pos = this["pos"]; + var w = this["w"]; + var h = this["h"]; + return new Polygon(new Vector(pos["x"], pos["y"]), [ + new Vector(), + new Vector(w, 0), + new Vector(w, h), + new Vector(0, h), + ]); + }; + + // ## Response + // + // An object representing the result of an intersection. Contains: + // - The two objects participating in the intersection + // - The vector representing the minimum change necessary to extract the first object + // from the second one (as well as a unit vector in that direction and the magnitude + // of the overlap) + // - Whether the first object is entirely inside the second, and vice versa. + /** + * @constructor + */ + function Response() { + this["a"] = null; + this["b"] = null; + this["overlapN"] = new Vector(); + this["overlapV"] = new Vector(); + this.clear(); + } + SAT["Response"] = Response; + + // Set some values of the response back to their defaults. Call this between tests if + // you are going to reuse a single Response object for multiple intersection tests (recommented + // as it will avoid allcating extra memory) + /** + * @return {Response} This for chaining + */ + Response.prototype["clear"] = Response.prototype.clear = function () { + this["aInB"] = true; + this["bInA"] = true; + this["overlap"] = Number.MAX_VALUE; + return this; + }; + + // ## Object Pools + + // A pool of `Vector` objects that are used in calculations to avoid + // allocating memory. + /** + * @type {Array} + */ + var T_VECTORS = []; + for (var i = 0; i < 10; i++) { + T_VECTORS.push(new Vector()); + } - // Get the dot product of this vector and another. - /** - * @param {Vector} other The vector to dot this one against. - * @return {number} The dot product. - */ - Vector.prototype['dot'] = Vector.prototype.dot = function (other) { - return this['x'] * other['x'] + this['y'] * other['y']; - }; + // A pool of arrays of numbers used in calculations to avoid allocating + // memory. + /** + * @type {Array>} + */ + var T_ARRAYS = []; + for (var i = 0; i < 5; i++) { + T_ARRAYS.push([]); + } - // Get the squared length of this vector. - /** - * @return {number} The length^2 of this vector. - */ - Vector.prototype['len2'] = Vector.prototype.len2 = function () { - return this.dot(this); - }; + // Temporary response used for polygon hit detection. + /** + * @type {Response} + */ + var T_RESPONSE = new Response(); + + // Tiny "point" polygon used for polygon hit detection. + /** + * @type {Polygon} + */ + var TEST_POINT = new Box( + new Vector(), + 0.000001, + 0.000001, + ).toPolygon(); + + // ## Helper Functions + + // Flattens the specified array of points onto a unit vector axis, + // resulting in a one dimensional range of the minimum and + // maximum value on that axis. + /** + * @param {Array} points The points to flatten. + * @param {Vector} normal The unit vector axis to flatten on. + * @param {Array} result An array. After calling this function, + * result[0] will be the minimum value, + * result[1] will be the maximum value. + */ + function flattenPointsOn(points, normal, result) { + var min = Number.MAX_VALUE; + var max = -Number.MAX_VALUE; + var len = points.length; + for (var i = 0; i < len; i++) { + // The magnitude of the projection of the point onto the normal + var dot = points[i].dot(normal); + if (dot < min) { + min = dot; + } + if (dot > max) { + max = dot; + } + } + result[0] = min; + result[1] = max; + } - // Get the length of this vector. - /** - * @return {number} The length of this vector. - */ - Vector.prototype['len'] = Vector.prototype.len = function () { - return Math.sqrt(this.len2()); - }; + // Check whether two convex polygons are separated by the specified + // axis (must be a unit vector). + /** + * @param {Vector} aPos The position of the first polygon. + * @param {Vector} bPos The position of the second polygon. + * @param {Array} aPoints The points in the first polygon. + * @param {Array} bPoints The points in the second polygon. + * @param {Vector} axis The axis (unit sized) to test against. The points of both polygons + * will be projected onto this axis. + * @param {Response=} response A Response object (optional) which will be populated + * if the axis is not a separating axis. + * @return {boolean} true if it is a separating axis, false otherwise. If false, + * and a response is passed in, information about how much overlap and + * the direction of the overlap will be populated. + */ + function isSeparatingAxis( + aPos, + bPos, + aPoints, + bPoints, + axis, + response, + ) { + var rangeA = T_ARRAYS.pop(); + var rangeB = T_ARRAYS.pop(); + // The magnitude of the offset between the two polygons + var offsetV = T_VECTORS.pop().copy(bPos).sub(aPos); + var projectedOffset = offsetV.dot(axis); + // Project the polygons onto the axis. + flattenPointsOn(aPoints, axis, rangeA); + flattenPointsOn(bPoints, axis, rangeB); + // Move B's range to its position relative to A. + rangeB[0] += projectedOffset; + rangeB[1] += projectedOffset; + // Check if there is a gap. If there is, this is a separating axis and we can stop + if (rangeA[0] > rangeB[1] || rangeB[0] > rangeA[1]) { + T_VECTORS.push(offsetV); + T_ARRAYS.push(rangeA); + T_ARRAYS.push(rangeB); + return true; + } + // This is not a separating axis. If we're calculating a response, calculate the overlap. + if (response) { + var overlap = 0; + // A starts further left than B + if (rangeA[0] < rangeB[0]) { + response["aInB"] = false; + // A ends before B does. We have to pull A out of B + if (rangeA[1] < rangeB[1]) { + overlap = rangeA[1] - rangeB[0]; + response["bInA"] = false; + // B is fully inside A. Pick the shortest way out. + } else { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + // B starts further left than A + } else { + response["bInA"] = false; + // B ends before A ends. We have to push A out of B + if (rangeA[1] > rangeB[1]) { + overlap = rangeA[0] - rangeB[1]; + response["aInB"] = false; + // A is fully inside B. Pick the shortest way out. + } else { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + } + // If this is the smallest amount of overlap we've seen so far, set it as the minimum overlap. + var absOverlap = Math.abs(overlap); + if (absOverlap < response["overlap"]) { + response["overlap"] = absOverlap; + response["overlapN"].copy(axis); + if (overlap < 0) { + response["overlapN"].reverse(); + } + } + } + T_VECTORS.push(offsetV); + T_ARRAYS.push(rangeA); + T_ARRAYS.push(rangeB); + return false; + } + SAT["isSeparatingAxis"] = isSeparatingAxis; + + // Calculates which Voronoi region a point is on a line segment. + // It is assumed that both the line and the point are relative to `(0,0)` + // + // | (0) | + // (-1) [S]--------------[E] (1) + // | (0) | + /** + * @param {Vector} line The line segment. + * @param {Vector} point The point. + * @return {number} LEFT_VORONOI_REGION (-1) if it is the left region, + * MIDDLE_VORONOI_REGION (0) if it is the middle region, + * RIGHT_VORONOI_REGION (1) if it is the right region. + */ + function voronoiRegion(line, point) { + var len2 = line.len2(); + var dp = point.dot(line); + // If the point is beyond the start of the line, it is in the + // left voronoi region. + if (dp < 0) { + return LEFT_VORONOI_REGION; + } + // If the point is beyond the end of the line, it is in the + // right voronoi region. + else if (dp > len2) { + return RIGHT_VORONOI_REGION; + } + // Otherwise, it's in the middle one. + else { + return MIDDLE_VORONOI_REGION; + } + } + // Constants for Voronoi regions + /** + * @const + */ + var LEFT_VORONOI_REGION = -1; + /** + * @const + */ + var MIDDLE_VORONOI_REGION = 0; + /** + * @const + */ + var RIGHT_VORONOI_REGION = 1; + + // ## Collision Tests + + // Check if a point is inside a circle. + /** + * @param {Vector} p The point to test. + * @param {Circle} c The circle to test. + * @return {boolean} true if the point is inside the circle, false if it is not. + */ + function pointInCircle(p, c) { + var differenceV = T_VECTORS.pop() + .copy(p) + .sub(c["pos"]) + .sub(c["offset"]); + var radiusSq = c["r"] * c["r"]; + var distanceSq = differenceV.len2(); + T_VECTORS.push(differenceV); + // If the distance between is smaller than the radius then the point is inside the circle. + return distanceSq <= radiusSq; + } + SAT["pointInCircle"] = pointInCircle; + + // Check if a point is inside a convex polygon. + /** + * @param {Vector} p The point to test. + * @param {Polygon} poly The polygon to test. + * @return {boolean} true if the point is inside the polygon, false if it is not. + */ + function pointInPolygon(p, poly) { + TEST_POINT["pos"].copy(p); + T_RESPONSE.clear(); + var result = testPolygonPolygon(TEST_POINT, poly, T_RESPONSE); + if (result) { + result = T_RESPONSE["aInB"]; + } + return result; + } + SAT["pointInPolygon"] = pointInPolygon; + + // Check if two circles collide. + /** + * @param {Circle} a The first circle. + * @param {Circle} b The second circle. + * @param {Response=} response Response object (optional) that will be populated if + * the circles intersect. + * @return {boolean} true if the circles intersect, false if they don't. + */ + function testCircleCircle(a, b, response) { + // Check if the distance between the centers of the two + // circles is greater than their combined radius. + var differenceV = T_VECTORS.pop() + .copy(b["pos"]) + .add(b["offset"]) + .sub(a["pos"]) + .sub(a["offset"]); + var totalRadius = a["r"] + b["r"]; + var totalRadiusSq = totalRadius * totalRadius; + var distanceSq = differenceV.len2(); + // If the distance is bigger than the combined radius, they don't intersect. + if (distanceSq > totalRadiusSq) { + T_VECTORS.push(differenceV); + return false; + } + // They intersect. If we're calculating a response, calculate the overlap. + if (response) { + var dist = Math.sqrt(distanceSq); + response["a"] = a; + response["b"] = b; + response["overlap"] = totalRadius - dist; + response["overlapN"].copy(differenceV.normalize()); + response["overlapV"].copy(differenceV).scale(response["overlap"]); + response["aInB"] = a["r"] <= b["r"] && dist <= b["r"] - a["r"]; + response["bInA"] = b["r"] <= a["r"] && dist <= a["r"] - b["r"]; + } + T_VECTORS.push(differenceV); + return true; + } + SAT["testCircleCircle"] = testCircleCircle; + + // Check if a polygon and a circle collide. + /** + * @param {Polygon} polygon The polygon. + * @param {Circle} circle The circle. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testPolygonCircle(polygon, circle, response) { + // Get the position of the circle relative to the polygon. + var circlePos = T_VECTORS.pop() + .copy(circle["pos"]) + .add(circle["offset"]) + .sub(polygon["pos"]); + var radius = circle["r"]; + var radius2 = radius * radius; + var points = polygon["calcPoints"]; + var len = points.length; + var edge = T_VECTORS.pop(); + var point = T_VECTORS.pop(); + + // For each edge in the polygon: + for (var i = 0; i < len; i++) { + var next = i === len - 1 ? 0 : i + 1; + var prev = i === 0 ? len - 1 : i - 1; + var overlap = 0; + var overlapN = null; + + // Get the edge. + edge.copy(polygon["edges"][i]); + // Calculate the center of the circle relative to the starting point of the edge. + point.copy(circlePos).sub(points[i]); + + // If the distance between the center of the circle and the point + // is bigger than the radius, the polygon is definitely not fully in + // the circle. + if (response && point.len2() > radius2) { + response["aInB"] = false; + } + + // Calculate which Voronoi region the center of the circle is in. + var region = voronoiRegion(edge, point); + // If it's the left region: + if (region === LEFT_VORONOI_REGION) { + // We need to make sure we're in the RIGHT_VORONOI_REGION of the previous edge. + edge.copy(polygon["edges"][prev]); + // Calculate the center of the circle relative the starting point of the previous edge + var point2 = T_VECTORS.pop().copy(circlePos).sub(points[prev]); + region = voronoiRegion(edge, point2); + if (region === RIGHT_VORONOI_REGION) { + // It's in the region we want. Check if the circle intersects the point. + var dist = point.len(); + if (dist > radius) { + // No intersection + T_VECTORS.push(circlePos); + T_VECTORS.push(edge); + T_VECTORS.push(point); + T_VECTORS.push(point2); + return false; + } else if (response) { + // It intersects, calculate the overlap. + response["bInA"] = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + T_VECTORS.push(point2); + // If it's the right region: + } else if (region === RIGHT_VORONOI_REGION) { + // We need to make sure we're in the left region on the next edge + edge.copy(polygon["edges"][next]); + // Calculate the center of the circle relative to the starting point of the next edge. + point.copy(circlePos).sub(points[next]); + region = voronoiRegion(edge, point); + if (region === LEFT_VORONOI_REGION) { + // It's in the region we want. Check if the circle intersects the point. + var dist = point.len(); + if (dist > radius) { + // No intersection + T_VECTORS.push(circlePos); + T_VECTORS.push(edge); + T_VECTORS.push(point); + return false; + } else if (response) { + // It intersects, calculate the overlap. + response["bInA"] = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + // Otherwise, it's the middle region: + } else { + // Need to check if the circle is intersecting the edge, + // Change the edge into its "edge normal". + var normal = edge.perp().normalize(); + // Find the perpendicular distance between the center of the + // circle and the edge. + var dist = point.dot(normal); + var distAbs = Math.abs(dist); + // If the circle is on the outside of the edge, there is no intersection. + if (dist > 0 && distAbs > radius) { + // No intersection + T_VECTORS.push(circlePos); + T_VECTORS.push(normal); + T_VECTORS.push(point); + return false; + } else if (response) { + // It intersects, calculate the overlap. + overlapN = normal; + overlap = radius - dist; + // If the center of the circle is on the outside of the edge, or part of the + // circle is on the outside, the circle is not fully inside the polygon. + if (dist >= 0 || overlap < 2 * radius) { + response["bInA"] = false; + } + } + } + + // If this is the smallest overlap we've seen, keep it. + // (overlapN may be null if the circle was in the wrong Voronoi region). + if ( + overlapN && + response && + Math.abs(overlap) < Math.abs(response["overlap"]) + ) { + response["overlap"] = overlap; + response["overlapN"].copy(overlapN); + } + } - // ## Circle - // - // Represents a circle with a position and a radius. - - // Create a new circle, optionally passing in a position and/or radius. If no position - // is given, the circle will be at `(0,0)`. If no radius is provided, the circle will - // have a radius of `0`. - /** - * @param {Vector=} pos A vector representing the position of the center of the circle - * @param {?number=} r The radius of the circle - * @constructor - */ - function Circle(pos, r) { - this['pos'] = pos || new Vector(); - this['r'] = r || 0; - this['offset'] = new Vector(); - } - SAT['Circle'] = Circle; - - // Compute the axis-aligned bounding box (AABB) of this Circle. - // - // Note: Returns a _new_ `Box` each time you call this. - /** - * @return {Polygon} The AABB - */ - Circle.prototype['getAABBAsBox'] = Circle.prototype.getAABBAsBox = function () { - var r = this['r']; - var corner = this['pos'].clone().add(this['offset']).sub(new Vector(r, r)); - return new Box(corner, r * 2, r * 2); - }; + // Calculate the final overlap vector - based on the smallest overlap. + if (response) { + response["a"] = polygon; + response["b"] = circle; + response["overlapV"] + .copy(response["overlapN"]) + .scale(response["overlap"]); + } + T_VECTORS.push(circlePos); + T_VECTORS.push(edge); + T_VECTORS.push(point); + return true; + } + SAT["testPolygonCircle"] = testPolygonCircle; + + // Check if a circle and a polygon collide. + // + // **NOTE:** This is slightly less efficient than polygonCircle as it just + // runs polygonCircle and reverses everything at the end. + /** + * @param {Circle} circle The circle. + * @param {Polygon} polygon The polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testCirclePolygon(circle, polygon, response) { + // Test the polygon against the circle. + var result = testPolygonCircle(polygon, circle, response); + if (result && response) { + // Swap A and B in the response. + var a = response["a"]; + var aInB = response["aInB"]; + response["overlapN"].reverse(); + response["overlapV"].reverse(); + response["a"] = response["b"]; + response["b"] = a; + response["aInB"] = response["bInA"]; + response["bInA"] = aInB; + } + return result; + } + SAT["testCirclePolygon"] = testCirclePolygon; + + // Checks whether polygons collide. + /** + * @param {Polygon} a The first polygon. + * @param {Polygon} b The second polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testPolygonPolygon(a, b, response) { + var aPoints = a["calcPoints"]; + var aLen = aPoints.length; + var bPoints = b["calcPoints"]; + var bLen = bPoints.length; + // If any of the edge normals of A is a separating axis, no intersection. + for (var i = 0; i < aLen; i++) { + if ( + isSeparatingAxis( + a["pos"], + b["pos"], + aPoints, + bPoints, + a["normals"][i], + response, + ) + ) { + return false; + } + } + // If any of the edge normals of B is a separating axis, no intersection. + for (var i = 0; i < bLen; i++) { + if ( + isSeparatingAxis( + a["pos"], + b["pos"], + aPoints, + bPoints, + b["normals"][i], + response, + ) + ) { + return false; + } + } + // Since none of the edge normals of A or B are a separating axis, there is an intersection + // and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the + // final overlap vector. + if (response) { + response["a"] = a; + response["b"] = b; + response["overlapV"] + .copy(response["overlapN"]) + .scale(response["overlap"]); + } + return true; + } + SAT["testPolygonPolygon"] = testPolygonPolygon; - // Compute the axis-aligned bounding box (AABB) of this Circle. - // - // Note: Returns a _new_ `Polygon` each time you call this. - /** - * @return {Polygon} The AABB - */ - Circle.prototype['getAABB'] = Circle.prototype.getAABB = function () { - return this.getAABBAsBox().toPolygon(); - }; + return SAT; + }); - // Set the current offset to apply to the radius. - /** - * @param {Vector} offset The new offset vector. - * @return {Circle} This for chaining. - */ - Circle.prototype['setOffset'] = Circle.prototype.setOffset = function (offset) { - this['offset'] = offset; - return this; - }; + /***/ + }, - // ## Polygon - // - // Represents a *convex* polygon with any number of points (specified in counter-clockwise order) - // - // Note: Do _not_ manually change the `points`, `angle`, or `offset` properties. Use the - // provided setters. Otherwise the calculated properties will not be updated correctly. - // - // `pos` can be changed directly. - - // Create a new polygon, passing in a position vector, and an array of points (represented - // by vectors relative to the position vector). If no position is passed in, the position - // of the polygon will be `(0,0)`. - /** - * @param {Vector=} pos A vector representing the origin of the polygon. (all other - * points are relative to this one) - * @param {Array=} points An array of vectors representing the points in the polygon, - * in counter-clockwise order. - * @constructor - */ - function Polygon(pos, points) { - this['pos'] = pos || new Vector(); - this['angle'] = 0; - this['offset'] = new Vector(); - this.setPoints(points || []); - } - SAT['Polygon'] = Polygon; - - // Set the points of the polygon. Any consecutive duplicate points will be combined. - // - // Note: The points are counter-clockwise *with respect to the coordinate system*. - // If you directly draw the points on a screen that has the origin at the top-left corner - // it will _appear_ visually that the points are being specified clockwise. This is just - // because of the inversion of the Y-axis when being displayed. - /** - * @param {Array=} points An array of vectors representing the points in the polygon, - * in counter-clockwise order. - * @return {Polygon} This for chaining. - */ - Polygon.prototype['setPoints'] = Polygon.prototype.setPoints = function (points) { - // Only re-allocate if this is a new polygon or the number of points has changed. - var lengthChanged = !this['points'] || this['points'].length !== points.length; - if (lengthChanged) { - var i; - var calcPoints = this['calcPoints'] = []; - var edges = this['edges'] = []; - var normals = this['normals'] = []; - // Allocate the vector arrays for the calculated properties - for (i = 0; i < points.length; i++) { - // Remove consecutive duplicate points - var p1 = points[i]; - var p2 = i < points.length - 1 ? points[i + 1] : points[0]; - if (p1 !== p2 && p1.x === p2.x && p1.y === p2.y) { - points.splice(i, 1); - i -= 1; - continue; + /***/ "./src/base-system.ts": + /*!****************************!*\ + !*** ./src/base-system.ts ***! + \****************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.BaseSystem = void 0; + const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); + const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts"); + const optimized_1 = __webpack_require__( + /*! ./optimized */ "./src/optimized.ts", + ); + const box_1 = __webpack_require__( + /*! ./bodies/box */ "./src/bodies/box.ts", + ); + const circle_1 = __webpack_require__( + /*! ./bodies/circle */ "./src/bodies/circle.ts", + ); + const ellipse_1 = __webpack_require__( + /*! ./bodies/ellipse */ "./src/bodies/ellipse.ts", + ); + const line_1 = __webpack_require__( + /*! ./bodies/line */ "./src/bodies/line.ts", + ); + const point_1 = __webpack_require__( + /*! ./bodies/point */ "./src/bodies/point.ts", + ); + const polygon_1 = __webpack_require__( + /*! ./bodies/polygon */ "./src/bodies/polygon.ts", + ); + /** + * very base collision system (create, insert, update, draw, remove) + */ + class BaseSystem extends model_1.RBush { + /** + * create point at position with options and add to system + */ + createPoint(position, options) { + const point = new point_1.Point(position, options); + this.insert(point); + return point; + } + /** + * create line at position with options and add to system + */ + createLine(start, end, options) { + const line = new line_1.Line(start, end, options); + this.insert(line); + return line; + } + /** + * create circle at position with options and add to system + */ + createCircle(position, radius, options) { + const circle = new circle_1.Circle(position, radius, options); + this.insert(circle); + return circle; + } + /** + * create box at position with options and add to system + */ + createBox(position, width, height, options) { + const box = new box_1.Box(position, width, height, options); + this.insert(box); + return box; + } + /** + * create ellipse at position with options and add to system + */ + createEllipse(position, radiusX, radiusY = radiusX, step, options) { + const ellipse = new ellipse_1.Ellipse( + position, + radiusX, + radiusY, + step, + options, + ); + this.insert(ellipse); + return ellipse; + } + /** + * create polygon at position with options and add to system + */ + createPolygon(position, points, options) { + const polygon = new polygon_1.Polygon(position, points, options); + this.insert(polygon); + return polygon; + } + /** + * re-insert body into collision tree and update its bbox + * every body can be part of only one system + */ + insert(body) { + body.bbox = body.getAABBAsBBox(); + if (body.system) { + // allow end if body inserted and not moved + if (!(0, utils_1.bodyMoved)(body)) { + return this; + } + // old bounding box *needs* to be removed + body.system.remove(body); + } + // only then we update min, max + body.minX = body.bbox.minX - body.padding; + body.minY = body.bbox.minY - body.padding; + body.maxX = body.bbox.maxX + body.padding; + body.maxY = body.bbox.maxY + body.padding; + // reinsert bounding box to collision tree + return super.insert(body); + } + /** + * updates body in collision tree + */ + updateBody(body) { + body.updateBody(); + } + /** + * update all bodies aabb + */ + update() { + (0, optimized_1.forEach)(this.all(), (body) => { + this.updateBody(body); + }); + } + /** + * draw exact bodies colliders outline + */ + draw(context) { + (0, optimized_1.forEach)(this.all(), (body) => { + body.draw(context); + }); + } + /** + * draw bounding boxes hierarchy outline + */ + drawBVH(context) { + const drawChildren = (body) => { + (0, utils_1.drawBVH)(context, body); + if (body.children) { + (0, optimized_1.forEach)(body.children, drawChildren); + } + }; + (0, optimized_1.forEach)(this.data.children, drawChildren); + } + /** + * remove body aabb from collision tree + */ + remove(body, equals) { + body.system = undefined; + return super.remove(body, equals); + } + /** + * get object potential colliders + * @deprecated because it's slower to use than checkOne() or checkAll() + */ + getPotentials(body) { + // filter here is required as collides with self + return (0, optimized_1.filter)( + this.search(body), + (candidate) => candidate !== body, + ); + } + /** + * used to find body deep inside data with finder function returning boolean found or not + */ + traverse(traverseFunction, { children } = this.data) { + return children === null || children === void 0 + ? void 0 + : children.find((body, index) => { + if (!body) { + return false; + } + if ( + body.typeGroup && + traverseFunction(body, children, index) + ) { + return true; + } + // if callback returns true, ends forEach + if (body.children) { + this.traverse(traverseFunction, body); + } + }); + } } - calcPoints.push(new Vector()); - edges.push(new Vector()); - normals.push(new Vector()); - } - } - this['points'] = points; - this._recalc(); - return this; - }; + exports.BaseSystem = BaseSystem; - // Set the current rotation angle of the polygon. - /** - * @param {number} angle The current rotation angle (in radians). - * @return {Polygon} This for chaining. - */ - Polygon.prototype['setAngle'] = Polygon.prototype.setAngle = function (angle) { - this['angle'] = angle; - this._recalc(); - return this; - }; - - // Set the current offset to apply to the `points` before applying the `angle` rotation. - /** - * @param {Vector} offset The new offset vector. - * @return {Polygon} This for chaining. - */ - Polygon.prototype['setOffset'] = Polygon.prototype.setOffset = function (offset) { - this['offset'] = offset; - this._recalc(); - return this; - }; - - // Rotates this polygon counter-clockwise around the origin of *its local coordinate system* (i.e. `pos`). - // - // Note: This changes the **original** points (so any `angle` will be applied on top of this rotation). - /** - * @param {number} angle The angle to rotate (in radians) - * @return {Polygon} This for chaining. - */ - Polygon.prototype['rotate'] = Polygon.prototype.rotate = function (angle) { - var points = this['points']; - var len = points.length; - for (var i = 0; i < len; i++) { - points[i].rotate(angle); - } - this._recalc(); - return this; - }; - - // Translates the points of this polygon by a specified amount relative to the origin of *its own coordinate - // system* (i.e. `pos`). - // - // This is most useful to change the "center point" of a polygon. If you just want to move the whole polygon, change - // the coordinates of `pos`. - // - // Note: This changes the **original** points (so any `offset` will be applied on top of this translation) - /** - * @param {number} x The horizontal amount to translate. - * @param {number} y The vertical amount to translate. - * @return {Polygon} This for chaining. - */ - Polygon.prototype['translate'] = Polygon.prototype.translate = function (x, y) { - var points = this['points']; - var len = points.length; - for (var i = 0; i < len; i++) { - points[i]['x'] += x; - points[i]['y'] += y; - } - this._recalc(); - return this; - }; - - - // Computes the calculated collision polygon. Applies the `angle` and `offset` to the original points then recalculates the - // edges and normals of the collision polygon. - /** - * @return {Polygon} This for chaining. - */ - Polygon.prototype._recalc = function () { - // Calculated points - this is what is used for underlying collisions and takes into account - // the angle/offset set on the polygon. - var calcPoints = this['calcPoints']; - // The edges here are the direction of the `n`th edge of the polygon, relative to - // the `n`th point. If you want to draw a given edge from the edge value, you must - // first translate to the position of the starting point. - var edges = this['edges']; - // The normals here are the direction of the normal for the `n`th edge of the polygon, relative - // to the position of the `n`th point. If you want to draw an edge normal, you must first - // translate to the position of the starting point. - var normals = this['normals']; - // Copy the original points array and apply the offset/angle - var points = this['points']; - var offset = this['offset']; - var angle = this['angle']; - var len = points.length; - var i; - for (i = 0; i < len; i++) { - var calcPoint = calcPoints[i].copy(points[i]); - calcPoint['x'] += offset['x']; - calcPoint['y'] += offset['y']; - if (angle !== 0) { - calcPoint.rotate(angle); - } - } - // Calculate the edges/normals - for (i = 0; i < len; i++) { - var p1 = calcPoints[i]; - var p2 = i < len - 1 ? calcPoints[i + 1] : calcPoints[0]; - var e = edges[i].copy(p2).sub(p1); - normals[i].copy(e).perp().normalize(); - } - return this; - }; - - - // Compute the axis-aligned bounding box. Any current state - // (translations/rotations) will be applied before constructing the AABB. - // - // Note: Returns a _new_ `Box` each time you call this. - /** - * @return {Polygon} The AABB - */ - Polygon.prototype['getAABBAsBox'] = Polygon.prototype.getAABBAsBox = function () { - var points = this['calcPoints']; - var len = points.length; - var xMin = points[0]['x']; - var yMin = points[0]['y']; - var xMax = points[0]['x']; - var yMax = points[0]['y']; - for (var i = 1; i < len; i++) { - var point = points[i]; - if (point['x'] < xMin) { - xMin = point['x']; - } - else if (point['x'] > xMax) { - xMax = point['x']; - } - if (point['y'] < yMin) { - yMin = point['y']; - } - else if (point['y'] > yMax) { - yMax = point['y']; - } - } - return new Box(this['pos'].clone().add(new Vector(xMin, yMin)), xMax - xMin, yMax - yMin); - }; - - - // Compute the axis-aligned bounding box. Any current state - // (translations/rotations) will be applied before constructing the AABB. - // - // Note: Returns a _new_ `Polygon` each time you call this. - /** - * @return {Polygon} The AABB - */ - Polygon.prototype['getAABB'] = Polygon.prototype.getAABB = function () { - return this.getAABBAsBox().toPolygon(); - }; + /***/ + }, - // Compute the centroid (geometric center) of the polygon. Any current state - // (translations/rotations) will be applied before computing the centroid. - // - // See https://en.wikipedia.org/wiki/Centroid#Centroid_of_a_polygon - // - // Note: Returns a _new_ `Vector` each time you call this. - /** - * @return {Vector} A Vector that contains the coordinates of the Centroid. - */ - Polygon.prototype['getCentroid'] = Polygon.prototype.getCentroid = function () { - var points = this['calcPoints']; - var len = points.length; - var cx = 0; - var cy = 0; - var ar = 0; - for (var i = 0; i < len; i++) { - var p1 = points[i]; - var p2 = i === len - 1 ? points[0] : points[i + 1]; // Loop around if last point - var a = p1['x'] * p2['y'] - p2['x'] * p1['y']; - cx += (p1['x'] + p2['x']) * a; - cy += (p1['y'] + p2['y']) * a; - ar += a; - } - ar = ar * 3; // we want 1 / 6 the area and we currently have 2*area - cx = cx / ar; - cy = cy / ar; - return new Vector(cx, cy); - }; - - - // ## Box - // - // Represents an axis-aligned box, with a width and height. - - - // Create a new box, with the specified position, width, and height. If no position - // is given, the position will be `(0,0)`. If no width or height are given, they will - // be set to `0`. - /** - * @param {Vector=} pos A vector representing the bottom-left of the box (i.e. the smallest x and smallest y value). - * @param {?number=} w The width of the box. - * @param {?number=} h The height of the box. - * @constructor - */ - function Box(pos, w, h) { - this['pos'] = pos || new Vector(); - this['w'] = w || 0; - this['h'] = h || 0; - } - SAT['Box'] = Box; - - // Returns a polygon whose edges are the same as this box. - /** - * @return {Polygon} A new Polygon that represents this box. - */ - Box.prototype['toPolygon'] = Box.prototype.toPolygon = function () { - var pos = this['pos']; - var w = this['w']; - var h = this['h']; - return new Polygon(new Vector(pos['x'], pos['y']), [ - new Vector(), new Vector(w, 0), - new Vector(w, h), new Vector(0, h) - ]); - }; - - // ## Response - // - // An object representing the result of an intersection. Contains: - // - The two objects participating in the intersection - // - The vector representing the minimum change necessary to extract the first object - // from the second one (as well as a unit vector in that direction and the magnitude - // of the overlap) - // - Whether the first object is entirely inside the second, and vice versa. - /** - * @constructor - */ - function Response() { - this['a'] = null; - this['b'] = null; - this['overlapN'] = new Vector(); - this['overlapV'] = new Vector(); - this.clear(); - } - SAT['Response'] = Response; - - // Set some values of the response back to their defaults. Call this between tests if - // you are going to reuse a single Response object for multiple intersection tests (recommented - // as it will avoid allcating extra memory) - /** - * @return {Response} This for chaining - */ - Response.prototype['clear'] = Response.prototype.clear = function () { - this['aInB'] = true; - this['bInA'] = true; - this['overlap'] = Number.MAX_VALUE; - return this; - }; + /***/ "./src/bodies/box.ts": + /*!***************************!*\ + !*** ./src/bodies/box.ts ***! + \***************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Box = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const polygon_1 = __webpack_require__( + /*! ./polygon */ "./src/bodies/polygon.ts", + ); + const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); + /** + * collider - box + */ + class Box extends polygon_1.Polygon { + /** + * collider - box + */ + constructor(position, width, height, options) { + super(position, (0, utils_1.createBox)(width, height), options); + /** + * type of body + */ + this.type = model_1.BodyType.Box; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Box; + /** + * boxes are convex + */ + this.isConvex = true; + this._width = width; + this._height = height; + } + /** + * get box width + */ + get width() { + return this._width; + } + /** + * set box width, update points + */ + set width(width) { + this._width = width; + this.afterUpdateSize(); + } + /** + * get box height + */ + get height() { + return this._height; + } + /** + * set box height, update points + */ + set height(height) { + this._height = height; + this.afterUpdateSize(); + } + /** + * after setting width/height update translate + * see https://github.com/Prozi/detect-collisions/issues/70 + */ + afterUpdateSize() { + if (this.isCentered) { + this.retranslate(false); + } + this.setPoints((0, utils_1.createBox)(this._width, this._height)); + if (this.isCentered) { + this.retranslate(); + } + } + /** + * do not attempt to use Polygon.updateIsConvex() + */ + updateIsConvex() { + return; + } + } + exports.Box = Box; - // ## Object Pools - - // A pool of `Vector` objects that are used in calculations to avoid - // allocating memory. - /** - * @type {Array} - */ - var T_VECTORS = []; - for (var i = 0; i < 10; i++) { T_VECTORS.push(new Vector()); } - - // A pool of arrays of numbers used in calculations to avoid allocating - // memory. - /** - * @type {Array>} - */ - var T_ARRAYS = []; - for (var i = 0; i < 5; i++) { T_ARRAYS.push([]); } - - // Temporary response used for polygon hit detection. - /** - * @type {Response} - */ - var T_RESPONSE = new Response(); - - // Tiny "point" polygon used for polygon hit detection. - /** - * @type {Polygon} - */ - var TEST_POINT = new Box(new Vector(), 0.000001, 0.000001).toPolygon(); - - // ## Helper Functions - - // Flattens the specified array of points onto a unit vector axis, - // resulting in a one dimensional range of the minimum and - // maximum value on that axis. - /** - * @param {Array} points The points to flatten. - * @param {Vector} normal The unit vector axis to flatten on. - * @param {Array} result An array. After calling this function, - * result[0] will be the minimum value, - * result[1] will be the maximum value. - */ - function flattenPointsOn(points, normal, result) { - var min = Number.MAX_VALUE; - var max = -Number.MAX_VALUE; - var len = points.length; - for (var i = 0; i < len; i++) { - // The magnitude of the projection of the point onto the normal - var dot = points[i].dot(normal); - if (dot < min) { min = dot; } - if (dot > max) { max = dot; } - } - result[0] = min; result[1] = max; - } + /***/ + }, - // Check whether two convex polygons are separated by the specified - // axis (must be a unit vector). - /** - * @param {Vector} aPos The position of the first polygon. - * @param {Vector} bPos The position of the second polygon. - * @param {Array} aPoints The points in the first polygon. - * @param {Array} bPoints The points in the second polygon. - * @param {Vector} axis The axis (unit sized) to test against. The points of both polygons - * will be projected onto this axis. - * @param {Response=} response A Response object (optional) which will be populated - * if the axis is not a separating axis. - * @return {boolean} true if it is a separating axis, false otherwise. If false, - * and a response is passed in, information about how much overlap and - * the direction of the overlap will be populated. - */ - function isSeparatingAxis(aPos, bPos, aPoints, bPoints, axis, response) { - var rangeA = T_ARRAYS.pop(); - var rangeB = T_ARRAYS.pop(); - // The magnitude of the offset between the two polygons - var offsetV = T_VECTORS.pop().copy(bPos).sub(aPos); - var projectedOffset = offsetV.dot(axis); - // Project the polygons onto the axis. - flattenPointsOn(aPoints, axis, rangeA); - flattenPointsOn(bPoints, axis, rangeB); - // Move B's range to its position relative to A. - rangeB[0] += projectedOffset; - rangeB[1] += projectedOffset; - // Check if there is a gap. If there is, this is a separating axis and we can stop - if (rangeA[0] > rangeB[1] || rangeB[0] > rangeA[1]) { - T_VECTORS.push(offsetV); - T_ARRAYS.push(rangeA); - T_ARRAYS.push(rangeB); - return true; - } - // This is not a separating axis. If we're calculating a response, calculate the overlap. - if (response) { - var overlap = 0; - // A starts further left than B - if (rangeA[0] < rangeB[0]) { - response['aInB'] = false; - // A ends before B does. We have to pull A out of B - if (rangeA[1] < rangeB[1]) { - overlap = rangeA[1] - rangeB[0]; - response['bInA'] = false; - // B is fully inside A. Pick the shortest way out. - } else { - var option1 = rangeA[1] - rangeB[0]; - var option2 = rangeB[1] - rangeA[0]; - overlap = option1 < option2 ? option1 : -option2; - } - // B starts further left than A - } else { - response['bInA'] = false; - // B ends before A ends. We have to push A out of B - if (rangeA[1] > rangeB[1]) { - overlap = rangeA[0] - rangeB[1]; - response['aInB'] = false; - // A is fully inside B. Pick the shortest way out. - } else { - var option1 = rangeA[1] - rangeB[0]; - var option2 = rangeB[1] - rangeA[0]; - overlap = option1 < option2 ? option1 : -option2; - } - } - // If this is the smallest amount of overlap we've seen so far, set it as the minimum overlap. - var absOverlap = Math.abs(overlap); - if (absOverlap < response['overlap']) { - response['overlap'] = absOverlap; - response['overlapN'].copy(axis); - if (overlap < 0) { - response['overlapN'].reverse(); + /***/ "./src/bodies/circle.ts": + /*!******************************!*\ + !*** ./src/bodies/circle.ts ***! + \******************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Circle = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); + /** + * collider - circle + */ + class Circle extends sat_1.Circle { + /** + * collider - circle + */ + constructor(position, radius, options) { + super((0, utils_1.ensureVectorPoint)(position), radius); + /** + * offset copy without angle applied + */ + this.offsetCopy = { x: 0, y: 0 }; + /** + * was the polygon modified and needs update in the next checkCollision + */ + this.dirty = false; + /* + * circles are convex + */ + this.isConvex = true; + /** + * circle type + */ + this.type = model_1.BodyType.Circle; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Circle; + /** + * always centered + */ + this.isCentered = true; + (0, utils_1.extendBody)(this, options); + this.unscaledRadius = radius; + } + /** + * get this.pos.x + */ + get x() { + return this.pos.x; + } + /** + * updating this.pos.x by this.x = x updates AABB + */ + set x(x) { + this.pos.x = x; + this.markAsDirty(); + } + /** + * get this.pos.y + */ + get y() { + return this.pos.y; + } + /** + * updating this.pos.y by this.y = y updates AABB + */ + set y(y) { + this.pos.y = y; + this.markAsDirty(); + } + /** + * allow get scale + */ + get scale() { + return this.r / this.unscaledRadius; + } + /** + * shorthand for setScale() + */ + set scale(scale) { + this.setScale(scale); + } + /** + * scaleX = scale in case of Circles + */ + get scaleX() { + return this.scale; + } + /** + * scaleY = scale in case of Circles + */ + get scaleY() { + return this.scale; + } + /** + * group for collision filtering + */ + get group() { + return this._group; + } + set group(group) { + this._group = (0, utils_1.getGroup)(group); + } + /** + * update position BY MOVING FORWARD IN ANGLE DIRECTION + */ + move(speed = 1, updateNow = true) { + (0, utils_1.move)(this, speed, updateNow); + return this; + } + /** + * update position BY TELEPORTING + */ + setPosition(x, y, updateNow = true) { + this.pos.x = x; + this.pos.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * update scale + */ + setScale(scaleX, _scaleY = scaleX, updateNow = true) { + this.r = this.unscaledRadius * Math.abs(scaleX); + this.markAsDirty(updateNow); + return this; + } + /** + * set rotation + */ + setAngle(angle, updateNow = true) { + this.angle = angle; + const { x, y } = this.getOffsetWithAngle(); + this.offset.x = x; + this.offset.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * set offset from center + */ + setOffset(offset, updateNow = true) { + this.offsetCopy.x = offset.x; + this.offsetCopy.y = offset.y; + const { x, y } = this.getOffsetWithAngle(); + this.offset.x = x; + this.offset.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * get body bounding box, without padding + */ + getAABBAsBBox() { + const x = this.pos.x + this.offset.x; + const y = this.pos.y + this.offset.y; + return { + minX: x - this.r, + maxX: x + this.r, + minY: y - this.r, + maxY: y + this.r, + }; + } + /** + * Draws collider on a CanvasRenderingContext2D's current path + */ + draw(context) { + const x = this.pos.x + this.offset.x; + const y = this.pos.y + this.offset.y; + const r = Math.abs(this.r); + if (this.isTrigger) { + const max = Math.max(8, this.r); + for (let i = 0; i < max; i++) { + const arc = (i / max) * 2 * Math.PI; + const arcPrev = ((i - 1) / max) * 2 * Math.PI; + const fromX = x + Math.cos(arcPrev) * this.r; + const fromY = y + Math.sin(arcPrev) * this.r; + const toX = x + Math.cos(arc) * this.r; + const toY = y + Math.sin(arc) * this.r; + (0, utils_1.dashLineTo)(context, fromX, fromY, toX, toY); + } + } else { + context.moveTo(x + r, y); + context.arc(x, y, r, 0, Math.PI * 2); + } + } + /** + * Draws Bounding Box on canvas context + */ + drawBVH(context) { + (0, utils_1.drawBVH)(context, this); + } + /** + * inner function for after position change update aabb in system + */ + updateBody(updateNow = this.dirty) { + var _a; + if (updateNow) { + (_a = this.system) === null || _a === void 0 + ? void 0 + : _a.insert(this); + this.dirty = false; + } + } + /** + * update instantly or mark as dirty + */ + markAsDirty(updateNow = false) { + if (updateNow) { + this.updateBody(true); + } else { + this.dirty = true; + } + } + /** + * internal for getting offset with applied angle + */ + getOffsetWithAngle() { + if ((!this.offsetCopy.x && !this.offsetCopy.y) || !this.angle) { + return this.offsetCopy; + } + const sin = Math.sin(this.angle); + const cos = Math.cos(this.angle); + const x = this.offsetCopy.x * cos - this.offsetCopy.y * sin; + const y = this.offsetCopy.x * sin + this.offsetCopy.y * cos; + return { x, y }; + } } - } - } - T_VECTORS.push(offsetV); - T_ARRAYS.push(rangeA); - T_ARRAYS.push(rangeB); - return false; - } - SAT['isSeparatingAxis'] = isSeparatingAxis; - - // Calculates which Voronoi region a point is on a line segment. - // It is assumed that both the line and the point are relative to `(0,0)` - // - // | (0) | - // (-1) [S]--------------[E] (1) - // | (0) | - /** - * @param {Vector} line The line segment. - * @param {Vector} point The point. - * @return {number} LEFT_VORONOI_REGION (-1) if it is the left region, - * MIDDLE_VORONOI_REGION (0) if it is the middle region, - * RIGHT_VORONOI_REGION (1) if it is the right region. - */ - function voronoiRegion(line, point) { - var len2 = line.len2(); - var dp = point.dot(line); - // If the point is beyond the start of the line, it is in the - // left voronoi region. - if (dp < 0) { return LEFT_VORONOI_REGION; } - // If the point is beyond the end of the line, it is in the - // right voronoi region. - else if (dp > len2) { return RIGHT_VORONOI_REGION; } - // Otherwise, it's in the middle one. - else { return MIDDLE_VORONOI_REGION; } - } - // Constants for Voronoi regions - /** - * @const - */ - var LEFT_VORONOI_REGION = -1; - /** - * @const - */ - var MIDDLE_VORONOI_REGION = 0; - /** - * @const - */ - var RIGHT_VORONOI_REGION = 1; - - // ## Collision Tests - - // Check if a point is inside a circle. - /** - * @param {Vector} p The point to test. - * @param {Circle} c The circle to test. - * @return {boolean} true if the point is inside the circle, false if it is not. - */ - function pointInCircle(p, c) { - var differenceV = T_VECTORS.pop().copy(p).sub(c['pos']).sub(c['offset']); - var radiusSq = c['r'] * c['r']; - var distanceSq = differenceV.len2(); - T_VECTORS.push(differenceV); - // If the distance between is smaller than the radius then the point is inside the circle. - return distanceSq <= radiusSq; - } - SAT['pointInCircle'] = pointInCircle; - - // Check if a point is inside a convex polygon. - /** - * @param {Vector} p The point to test. - * @param {Polygon} poly The polygon to test. - * @return {boolean} true if the point is inside the polygon, false if it is not. - */ - function pointInPolygon(p, poly) { - TEST_POINT['pos'].copy(p); - T_RESPONSE.clear(); - var result = testPolygonPolygon(TEST_POINT, poly, T_RESPONSE); - if (result) { - result = T_RESPONSE['aInB']; - } - return result; - } - SAT['pointInPolygon'] = pointInPolygon; - - // Check if two circles collide. - /** - * @param {Circle} a The first circle. - * @param {Circle} b The second circle. - * @param {Response=} response Response object (optional) that will be populated if - * the circles intersect. - * @return {boolean} true if the circles intersect, false if they don't. - */ - function testCircleCircle(a, b, response) { - // Check if the distance between the centers of the two - // circles is greater than their combined radius. - var differenceV = T_VECTORS.pop().copy(b['pos']).add(b['offset']).sub(a['pos']).sub(a['offset']); - var totalRadius = a['r'] + b['r']; - var totalRadiusSq = totalRadius * totalRadius; - var distanceSq = differenceV.len2(); - // If the distance is bigger than the combined radius, they don't intersect. - if (distanceSq > totalRadiusSq) { - T_VECTORS.push(differenceV); - return false; - } - // They intersect. If we're calculating a response, calculate the overlap. - if (response) { - var dist = Math.sqrt(distanceSq); - response['a'] = a; - response['b'] = b; - response['overlap'] = totalRadius - dist; - response['overlapN'].copy(differenceV.normalize()); - response['overlapV'].copy(differenceV).scale(response['overlap']); - response['aInB'] = a['r'] <= b['r'] && dist <= b['r'] - a['r']; - response['bInA'] = b['r'] <= a['r'] && dist <= a['r'] - b['r']; - } - T_VECTORS.push(differenceV); - return true; - } - SAT['testCircleCircle'] = testCircleCircle; - - // Check if a polygon and a circle collide. - /** - * @param {Polygon} polygon The polygon. - * @param {Circle} circle The circle. - * @param {Response=} response Response object (optional) that will be populated if - * they interset. - * @return {boolean} true if they intersect, false if they don't. - */ - function testPolygonCircle(polygon, circle, response) { - // Get the position of the circle relative to the polygon. - var circlePos = T_VECTORS.pop().copy(circle['pos']).add(circle['offset']).sub(polygon['pos']); - var radius = circle['r']; - var radius2 = radius * radius; - var points = polygon['calcPoints']; - var len = points.length; - var edge = T_VECTORS.pop(); - var point = T_VECTORS.pop(); - - // For each edge in the polygon: - for (var i = 0; i < len; i++) { - var next = i === len - 1 ? 0 : i + 1; - var prev = i === 0 ? len - 1 : i - 1; - var overlap = 0; - var overlapN = null; - - // Get the edge. - edge.copy(polygon['edges'][i]); - // Calculate the center of the circle relative to the starting point of the edge. - point.copy(circlePos).sub(points[i]); - - // If the distance between the center of the circle and the point - // is bigger than the radius, the polygon is definitely not fully in - // the circle. - if (response && point.len2() > radius2) { - response['aInB'] = false; - } + exports.Circle = Circle; - // Calculate which Voronoi region the center of the circle is in. - var region = voronoiRegion(edge, point); - // If it's the left region: - if (region === LEFT_VORONOI_REGION) { - // We need to make sure we're in the RIGHT_VORONOI_REGION of the previous edge. - edge.copy(polygon['edges'][prev]); - // Calculate the center of the circle relative the starting point of the previous edge - var point2 = T_VECTORS.pop().copy(circlePos).sub(points[prev]); - region = voronoiRegion(edge, point2); - if (region === RIGHT_VORONOI_REGION) { - // It's in the region we want. Check if the circle intersects the point. - var dist = point.len(); - if (dist > radius) { - // No intersection - T_VECTORS.push(circlePos); - T_VECTORS.push(edge); - T_VECTORS.push(point); - T_VECTORS.push(point2); - return false; - } else if (response) { - // It intersects, calculate the overlap. - response['bInA'] = false; - overlapN = point.normalize(); - overlap = radius - dist; + /***/ + }, + + /***/ "./src/bodies/ellipse.ts": + /*!*******************************!*\ + !*** ./src/bodies/ellipse.ts ***! + \*******************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Ellipse = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const polygon_1 = __webpack_require__( + /*! ./polygon */ "./src/bodies/polygon.ts", + ); + const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); + /** + * collider - ellipse + */ + class Ellipse extends polygon_1.Polygon { + /** + * collider - ellipse + */ + constructor( + position, + radiusX, + radiusY = radiusX, + step = (radiusX + radiusY) / Math.PI, + options, + ) { + super( + position, + (0, utils_1.createEllipse)(radiusX, radiusY, step), + options, + ); + /** + * ellipse type + */ + this.type = model_1.BodyType.Ellipse; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Ellipse; + /** + * ellipses are convex + */ + this.isConvex = true; + this._radiusX = radiusX; + this._radiusY = radiusY; + this._step = step; } - } - T_VECTORS.push(point2); - // If it's the right region: - } else if (region === RIGHT_VORONOI_REGION) { - // We need to make sure we're in the left region on the next edge - edge.copy(polygon['edges'][next]); - // Calculate the center of the circle relative to the starting point of the next edge. - point.copy(circlePos).sub(points[next]); - region = voronoiRegion(edge, point); - if (region === LEFT_VORONOI_REGION) { - // It's in the region we want. Check if the circle intersects the point. - var dist = point.len(); - if (dist > radius) { - // No intersection - T_VECTORS.push(circlePos); - T_VECTORS.push(edge); - T_VECTORS.push(point); - return false; - } else if (response) { - // It intersects, calculate the overlap. - response['bInA'] = false; - overlapN = point.normalize(); - overlap = radius - dist; + /** + * flag to set is body centered + */ + set isCentered(_isCentered) {} + /** + * is body centered? + */ + get isCentered() { + return true; } - } - // Otherwise, it's the middle region: - } else { - // Need to check if the circle is intersecting the edge, - // Change the edge into its "edge normal". - var normal = edge.perp().normalize(); - // Find the perpendicular distance between the center of the - // circle and the edge. - var dist = point.dot(normal); - var distAbs = Math.abs(dist); - // If the circle is on the outside of the edge, there is no intersection. - if (dist > 0 && distAbs > radius) { - // No intersection - T_VECTORS.push(circlePos); - T_VECTORS.push(normal); - T_VECTORS.push(point); - return false; - } else if (response) { - // It intersects, calculate the overlap. - overlapN = normal; - overlap = radius - dist; - // If the center of the circle is on the outside of the edge, or part of the - // circle is on the outside, the circle is not fully inside the polygon. - if (dist >= 0 || overlap < 2 * radius) { - response['bInA'] = false; + /** + * get ellipse step number + */ + get step() { + return this._step; + } + /** + * set ellipse step number + */ + set step(step) { + this._step = step; + this.setPoints( + (0, utils_1.createEllipse)( + this._radiusX, + this._radiusY, + this._step, + ), + ); + } + /** + * get ellipse radiusX + */ + get radiusX() { + return this._radiusX; + } + /** + * set ellipse radiusX, update points + */ + set radiusX(radiusX) { + this._radiusX = radiusX; + this.setPoints( + (0, utils_1.createEllipse)( + this._radiusX, + this._radiusY, + this._step, + ), + ); + } + /** + * get ellipse radiusY + */ + get radiusY() { + return this._radiusY; + } + /** + * set ellipse radiusY, update points + */ + set radiusY(radiusY) { + this._radiusY = radiusY; + this.setPoints( + (0, utils_1.createEllipse)( + this._radiusX, + this._radiusY, + this._step, + ), + ); + } + /** + * do not attempt to use Polygon.center() + */ + center() { + return; + } + /** + * do not attempt to use Polygon.updateIsConvex() + */ + updateIsConvex() { + return; } } - } + exports.Ellipse = Ellipse; - // If this is the smallest overlap we've seen, keep it. - // (overlapN may be null if the circle was in the wrong Voronoi region). - if (overlapN && response && Math.abs(overlap) < Math.abs(response['overlap'])) { - response['overlap'] = overlap; - response['overlapN'].copy(overlapN); - } - } + /***/ + }, - // Calculate the final overlap vector - based on the smallest overlap. - if (response) { - response['a'] = polygon; - response['b'] = circle; - response['overlapV'].copy(response['overlapN']).scale(response['overlap']); - } - T_VECTORS.push(circlePos); - T_VECTORS.push(edge); - T_VECTORS.push(point); - return true; - } - SAT['testPolygonCircle'] = testPolygonCircle; - - // Check if a circle and a polygon collide. - // - // **NOTE:** This is slightly less efficient than polygonCircle as it just - // runs polygonCircle and reverses everything at the end. - /** - * @param {Circle} circle The circle. - * @param {Polygon} polygon The polygon. - * @param {Response=} response Response object (optional) that will be populated if - * they interset. - * @return {boolean} true if they intersect, false if they don't. - */ - function testCirclePolygon(circle, polygon, response) { - // Test the polygon against the circle. - var result = testPolygonCircle(polygon, circle, response); - if (result && response) { - // Swap A and B in the response. - var a = response['a']; - var aInB = response['aInB']; - response['overlapN'].reverse(); - response['overlapV'].reverse(); - response['a'] = response['b']; - response['b'] = a; - response['aInB'] = response['bInA']; - response['bInA'] = aInB; - } - return result; - } - SAT['testCirclePolygon'] = testCirclePolygon; - - // Checks whether polygons collide. - /** - * @param {Polygon} a The first polygon. - * @param {Polygon} b The second polygon. - * @param {Response=} response Response object (optional) that will be populated if - * they interset. - * @return {boolean} true if they intersect, false if they don't. - */ - function testPolygonPolygon(a, b, response) { - var aPoints = a['calcPoints']; - var aLen = aPoints.length; - var bPoints = b['calcPoints']; - var bLen = bPoints.length; - // If any of the edge normals of A is a separating axis, no intersection. - for (var i = 0; i < aLen; i++) { - if (isSeparatingAxis(a['pos'], b['pos'], aPoints, bPoints, a['normals'][i], response)) { - return false; - } - } - // If any of the edge normals of B is a separating axis, no intersection. - for (var i = 0; i < bLen; i++) { - if (isSeparatingAxis(a['pos'], b['pos'], aPoints, bPoints, b['normals'][i], response)) { - return false; - } - } - // Since none of the edge normals of A or B are a separating axis, there is an intersection - // and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the - // final overlap vector. - if (response) { - response['a'] = a; - response['b'] = b; - response['overlapV'].copy(response['overlapN']).scale(response['overlap']); - } - return true; - } - SAT['testPolygonPolygon'] = testPolygonPolygon; + /***/ "./src/bodies/line.ts": + /*!****************************!*\ + !*** ./src/bodies/line.ts ***! + \****************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Line = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const polygon_1 = __webpack_require__( + /*! ./polygon */ "./src/bodies/polygon.ts", + ); + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); + /** + * collider - line + */ + class Line extends polygon_1.Polygon { + /** + * collider - line from start to end + */ + constructor(start, end, options) { + super( + start, + [ + { x: 0, y: 0 }, + { x: end.x - start.x, y: end.y - start.y }, + ], + options, + ); + /** + * line type + */ + this.type = model_1.BodyType.Line; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Line; + /** + * line is convex + */ + this.isConvex = true; + if (this.calcPoints.length === 1 || !end) { + console.error({ start, end }); + throw new Error("No end point for line provided"); + } + } + get start() { + return { + x: this.x + this.calcPoints[0].x, + y: this.y + this.calcPoints[0].y, + }; + } + set start({ x, y }) { + this.x = x; + this.y = y; + } + get end() { + return { + x: this.x + this.calcPoints[1].x, + y: this.y + this.calcPoints[1].y, + }; + } + set end({ x, y }) { + this.points[1].x = x - this.start.x; + this.points[1].y = y - this.start.y; + this.setPoints(this.points); + } + getCentroid() { + return new sat_1.Vector( + (this.end.x - this.start.x) / 2, + (this.end.y - this.start.y) / 2, + ); + } + /** + * do not attempt to use Polygon.updateIsConvex() + */ + updateIsConvex() { + return; + } + } + exports.Line = Line; - return SAT; -})); + /***/ + }, + /***/ "./src/bodies/point.ts": + /*!*****************************!*\ + !*** ./src/bodies/point.ts ***! + \*****************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Point = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const box_1 = __webpack_require__(/*! ./box */ "./src/bodies/box.ts"); + const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); + /** + * collider - point (very tiny box) + */ + class Point extends box_1.Box { + /** + * collider - point (very tiny box) + */ + constructor(position, options) { + super( + (0, utils_1.ensureVectorPoint)(position), + 0.001, + 0.001, + options, + ); + /** + * point type + */ + this.type = model_1.BodyType.Point; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Point; + } + } + exports.Point = Point; -/***/ }), + /***/ + }, -/***/ "./src/base-system.ts": -/*!****************************!*\ - !*** ./src/base-system.ts ***! - \****************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.BaseSystem = void 0; -const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); -const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts"); -const optimized_1 = __webpack_require__(/*! ./optimized */ "./src/optimized.ts"); -const box_1 = __webpack_require__(/*! ./bodies/box */ "./src/bodies/box.ts"); -const circle_1 = __webpack_require__(/*! ./bodies/circle */ "./src/bodies/circle.ts"); -const ellipse_1 = __webpack_require__(/*! ./bodies/ellipse */ "./src/bodies/ellipse.ts"); -const line_1 = __webpack_require__(/*! ./bodies/line */ "./src/bodies/line.ts"); -const point_1 = __webpack_require__(/*! ./bodies/point */ "./src/bodies/point.ts"); -const polygon_1 = __webpack_require__(/*! ./bodies/polygon */ "./src/bodies/polygon.ts"); -/** - * very base collision system (create, insert, update, draw, remove) - */ -class BaseSystem extends model_1.RBush { - /** - * create point at position with options and add to system - */ - createPoint(position, options) { - const point = new point_1.Point(position, options); - this.insert(point); - return point; - } - /** - * create line at position with options and add to system - */ - createLine(start, end, options) { - const line = new line_1.Line(start, end, options); - this.insert(line); - return line; - } - /** - * create circle at position with options and add to system - */ - createCircle(position, radius, options) { - const circle = new circle_1.Circle(position, radius, options); - this.insert(circle); - return circle; - } - /** - * create box at position with options and add to system - */ - createBox(position, width, height, options) { - const box = new box_1.Box(position, width, height, options); - this.insert(box); - return box; - } - /** - * create ellipse at position with options and add to system - */ - createEllipse(position, radiusX, radiusY = radiusX, step, options) { - const ellipse = new ellipse_1.Ellipse(position, radiusX, radiusY, step, options); - this.insert(ellipse); - return ellipse; - } - /** - * create polygon at position with options and add to system - */ - createPolygon(position, points, options) { - const polygon = new polygon_1.Polygon(position, points, options); - this.insert(polygon); - return polygon; - } - /** - * re-insert body into collision tree and update its bbox - * every body can be part of only one system - */ - insert(body) { - body.bbox = body.getAABBAsBBox(); - if (body.system) { - // allow end if body inserted and not moved - if (!(0, utils_1.bodyMoved)(body)) { - return this; - } - // old bounding box *needs* to be removed - body.system.remove(body); - } - // only then we update min, max - body.minX = body.bbox.minX - body.padding; - body.minY = body.bbox.minY - body.padding; - body.maxX = body.bbox.maxX + body.padding; - body.maxY = body.bbox.maxY + body.padding; - // reinsert bounding box to collision tree - return super.insert(body); - } - /** - * updates body in collision tree - */ - updateBody(body) { - body.updateBody(); - } - /** - * update all bodies aabb - */ - update() { - (0, optimized_1.forEach)(this.all(), (body) => { - this.updateBody(body); - }); - } - /** - * draw exact bodies colliders outline - */ - draw(context) { - (0, optimized_1.forEach)(this.all(), (body) => { - body.draw(context); + /***/ "./src/bodies/polygon.ts": + /*!*******************************!*\ + !*** ./src/bodies/polygon.ts ***! + \*******************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Polygon = exports.isSimple = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); + const optimized_1 = __webpack_require__( + /*! ../optimized */ "./src/optimized.ts", + ); + const poly_decomp_es_1 = __webpack_require__( + /*! poly-decomp-es */ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js", + ); + Object.defineProperty(exports, "isSimple", { + enumerable: true, + get: function () { + return poly_decomp_es_1.isSimple; + }, }); - } - /** - * draw bounding boxes hierarchy outline - */ - drawBVH(context) { - const drawChildren = (body) => { - (0, utils_1.drawBVH)(context, body); - if (body.children) { - (0, optimized_1.forEach)(body.children, drawChildren); + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); + /** + * collider - polygon + */ + class Polygon extends sat_1.Polygon { + /** + * collider - polygon + */ + constructor(position, points, options) { + super( + (0, utils_1.ensureVectorPoint)(position), + (0, utils_1.ensurePolygonPoints)(points), + ); + /** + * was the polygon modified and needs update in the next checkCollision + */ + this.dirty = false; + /** + * type of body + */ + this.type = model_1.BodyType.Polygon; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Polygon; + /** + * is body centered + */ + this.centered = false; + /** + * scale Vector of body + */ + this.scaleVector = { x: 1, y: 1 }; + if (!points.length) { + throw new Error("No points in polygon"); } - }; - (0, optimized_1.forEach)(this.data.children, drawChildren); - } - /** - * remove body aabb from collision tree - */ - remove(body, equals) { - body.system = undefined; - return super.remove(body, equals); - } - /** - * get object potential colliders - * @deprecated because it's slower to use than checkOne() or checkAll() - */ - getPotentials(body) { - // filter here is required as collides with self - return (0, optimized_1.filter)(this.search(body), (candidate) => candidate !== body); - } - /** - * used to find body deep inside data with finder function returning boolean found or not - */ - traverse(traverseFunction, { children } = this.data) { - return children === null || children === void 0 ? void 0 : children.find((body, index) => { - if (!body) { - return false; + (0, utils_1.extendBody)(this, options); + } + /** + * flag to set is polygon centered + */ + set isCentered(isCentered) { + if (this.centered === isCentered) { + return; + } + const centroid = this.getCentroidWithoutRotation(); + if (centroid.x || centroid.y) { + const x = centroid.x * (isCentered ? 1 : -1); + const y = centroid.y * (isCentered ? 1 : -1); + this.translate(-x, -y); + } + this.centered = isCentered; + } + /** + * is polygon centered? + */ + get isCentered() { + return this.centered; + } + get x() { + return this.pos.x; + } + /** + * updating this.pos.x by this.x = x updates AABB + */ + set x(x) { + this.pos.x = x; + this.markAsDirty(); + } + get y() { + return this.pos.y; + } + /** + * updating this.pos.y by this.y = y updates AABB + */ + set y(y) { + this.pos.y = y; + this.markAsDirty(); + } + /** + * allow exact getting of scale x - use setScale(x, y) to set + */ + get scaleX() { + return this.scaleVector.x; + } + /** + * allow exact getting of scale y - use setScale(x, y) to set + */ + get scaleY() { + return this.scaleVector.y; + } + /** + * allow approx getting of scale + */ + get scale() { + return (this.scaleVector.x + this.scaleVector.y) / 2; + } + /** + * allow easier setting of scale + */ + set scale(scale) { + this.setScale(scale); + } + /** + * group for collision filtering + */ + get group() { + return this._group; + } + set group(group) { + this._group = (0, utils_1.getGroup)(group); + } + /** + * update position BY MOVING FORWARD IN ANGLE DIRECTION + */ + move(speed = 1, updateNow = true) { + (0, utils_1.move)(this, speed, updateNow); + return this; + } + /** + * update position BY TELEPORTING + */ + setPosition(x, y, updateNow = true) { + this.pos.x = x; + this.pos.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * update scale + */ + setScale(x, y = x, updateNow = true) { + this.scaleVector.x = Math.abs(x); + this.scaleVector.y = Math.abs(y); + super.setPoints( + (0, optimized_1.map)(this.points, (point, index) => { + point.x = this.pointsBackup[index].x * this.scaleVector.x; + point.y = this.pointsBackup[index].y * this.scaleVector.y; + return point; + }), + ); + this.markAsDirty(updateNow); + return this; + } + setAngle(angle, updateNow = true) { + super.setAngle(angle); + this.markAsDirty(updateNow); + return this; + } + setOffset(offset, updateNow = true) { + super.setOffset(offset); + this.markAsDirty(updateNow); + return this; + } + /** + * get body bounding box, without padding + */ + getAABBAsBBox() { + const { pos, w, h } = this.getAABBAsBox(); + return { + minX: pos.x, + minY: pos.y, + maxX: pos.x + w, + maxY: pos.y + h, + }; + } + /** + * Draws exact collider on canvas context + */ + draw(context) { + (0, utils_1.drawPolygon)(context, this, this.isTrigger); + } + /** + * Draws Bounding Box on canvas context + */ + drawBVH(context) { + (0, utils_1.drawBVH)(context, this); + } + /** + * get body centroid without applied angle + */ + getCentroidWithoutRotation() { + // keep angle copy + const angle = this.angle; + if (angle) { + // reset angle for get centroid + this.setAngle(0); + // get centroid + const centroid = this.getCentroid(); + // revert angle change + this.setAngle(angle); + return centroid; + } + return this.getCentroid(); + } + /** + * sets polygon points to new array of vectors + */ + setPoints(points) { + super.setPoints(points); + this.updateIsConvex(); + this.pointsBackup = (0, utils_1.clonePointsArray)(points); + return this; + } + /** + * translates polygon points in x, y direction + */ + translate(x, y) { + super.translate(x, y); + this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); + return this; + } + /** + * rotates polygon points by angle, in radians + */ + rotate(angle) { + super.rotate(angle); + this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); + return this; + } + /** + * if true, polygon is not an invalid, self-crossing polygon + */ + isSimple() { + return (0, poly_decomp_es_1.isSimple)( + this.calcPoints.map(utils_1.mapVectorToArray), + ); + } + /** + * inner function for after position change update aabb in system and convex inner polygons + */ + updateBody(updateNow = this.dirty) { + var _a; + if (updateNow) { + this.updateConvexPolygonPositions(); + (_a = this.system) === null || _a === void 0 + ? void 0 + : _a.insert(this); + this.dirty = false; } - if (body.typeGroup && traverseFunction(body, children, index)) { - return true; + } + retranslate(isCentered = this.isCentered) { + const centroid = this.getCentroidWithoutRotation(); + if (centroid.x || centroid.y) { + const x = centroid.x * (isCentered ? 1 : -1); + const y = centroid.y * (isCentered ? 1 : -1); + this.translate(-x, -y); } - // if callback returns true, ends forEach - if (body.children) { - this.traverse(traverseFunction, body); + } + /** + * update instantly or mark as dirty + */ + markAsDirty(updateNow = false) { + if (updateNow) { + this.updateBody(true); + } else { + this.dirty = true; } - }); - } -} -exports.BaseSystem = BaseSystem; - + } + /** + * update the position of the decomposed convex polygons (if any), called + * after the position of the body has changed + */ + updateConvexPolygonPositions() { + if (this.isConvex || !this.convexPolygons) { + return; + } + (0, optimized_1.forEach)(this.convexPolygons, (polygon) => { + polygon.pos.x = this.pos.x; + polygon.pos.y = this.pos.y; + if (polygon.angle !== this.angle) { + // Must use setAngle to recalculate the points of the Polygon + polygon.setAngle(this.angle); + } + }); + } + /** + * returns body split into convex polygons, or empty array for convex bodies + */ + getConvex() { + if ( + (this.typeGroup && + this.typeGroup !== model_1.BodyGroup.Polygon) || + this.points.length < 4 + ) { + return []; + } + const points = (0, optimized_1.map)( + this.calcPoints, + utils_1.mapVectorToArray, + ); + return (0, poly_decomp_es_1.quickDecomp)(points); + } + /** + * updates convex polygons cache in body + */ + updateConvexPolygons(convex = this.getConvex()) { + if (this.isConvex) { + return; + } + if (!this.convexPolygons) { + this.convexPolygons = []; + } + (0, optimized_1.forEach)(convex, (points, index) => { + // lazy create + if (!this.convexPolygons[index]) { + this.convexPolygons[index] = new sat_1.Polygon(); + } + this.convexPolygons[index].pos.x = this.pos.x; + this.convexPolygons[index].pos.y = this.pos.y; + this.convexPolygons[index].angle = this.angle; + this.convexPolygons[index].setPoints( + (0, utils_1.ensurePolygonPoints)( + (0, optimized_1.map)(points, utils_1.mapArrayToVector), + ), + ); + }); + // trim array length + this.convexPolygons.length = convex.length; + } + /** + * after points update set is convex + */ + updateIsConvex() { + // all other types other than polygon are always convex + const convex = this.getConvex(); + // everything with empty array or one element array + this.isConvex = convex.length <= 1; + this.updateConvexPolygons(convex); + } + } + exports.Polygon = Polygon; -/***/ }), + /***/ + }, -/***/ "./src/bodies/box.ts": -/*!***************************!*\ - !*** ./src/bodies/box.ts ***! - \***************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Box = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const polygon_1 = __webpack_require__(/*! ./polygon */ "./src/bodies/polygon.ts"); -const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -/** - * collider - box - */ -class Box extends polygon_1.Polygon { - /** - * collider - box - */ - constructor(position, width, height, options) { - super(position, (0, utils_1.createBox)(width, height), options); + /***/ "./src/intersect.ts": + /*!**************************!*\ + !*** ./src/intersect.ts ***! + \**************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.ensureConvex = ensureConvex; + exports.polygonInCircle = polygonInCircle; + exports.pointInPolygon = pointInPolygon; + exports.polygonInPolygon = polygonInPolygon; + exports.pointOnCircle = pointOnCircle; + exports.circleInCircle = circleInCircle; + exports.circleInPolygon = circleInPolygon; + exports.circleOutsidePolygon = circleOutsidePolygon; + exports.intersectLineCircle = intersectLineCircle; + exports.intersectLineLineFast = intersectLineLineFast; + exports.intersectLineLine = intersectLineLine; + exports.intersectLinePolygon = intersectLinePolygon; + const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); + const optimized_1 = __webpack_require__( + /*! ./optimized */ "./src/optimized.ts", + ); + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); /** - * type of body + * replace body with array of related convex polygons */ - this.type = model_1.BodyType.Box; + function ensureConvex(body) { + if (body.isConvex || body.typeGroup !== model_1.BodyGroup.Polygon) { + return [body]; + } + return body.convexPolygons; + } + function polygonInCircle(polygon, circle) { + return (0, optimized_1.every)(polygon.calcPoints, (p) => + (0, sat_1.pointInCircle)( + { x: p.x + polygon.pos.x, y: p.y + polygon.pos.y }, + circle, + ), + ); + } + function pointInPolygon(point, polygon) { + return (0, optimized_1.some)(ensureConvex(polygon), (convex) => + (0, sat_1.pointInPolygon)(point, convex), + ); + } + function polygonInPolygon(polygonA, polygonB) { + return (0, optimized_1.every)(polygonA.calcPoints, (point) => + pointInPolygon( + { x: point.x + polygonA.pos.x, y: point.y + polygonA.pos.y }, + polygonB, + ), + ); + } /** - * faster than type + * https://stackoverflow.com/a/68197894/1749528 */ - this.typeGroup = model_1.BodyGroup.Box; + function pointOnCircle(point, circle) { + return ( + (point.x - circle.pos.x) * (point.x - circle.pos.x) + + (point.y - circle.pos.y) * (point.y - circle.pos.y) === + circle.r * circle.r + ); + } /** - * boxes are convex + * https://stackoverflow.com/a/68197894/1749528 */ - this.isConvex = true; - this._width = width; - this._height = height; - } - /** - * get box width - */ - get width() { - return this._width; - } - /** - * set box width, update points - */ - set width(width) { - this._width = width; - this.afterUpdateSize(); - } - /** - * get box height - */ - get height() { - return this._height; - } - /** - * set box height, update points - */ - set height(height) { - this._height = height; - this.afterUpdateSize(); - } - /** - * after setting width/height update translate - * see https://github.com/Prozi/detect-collisions/issues/70 - */ - afterUpdateSize() { - if (this.isCentered) { - this.retranslate(false); - } - this.setPoints((0, utils_1.createBox)(this._width, this._height)); - if (this.isCentered) { - this.retranslate(); + function circleInCircle(bodyA, bodyB) { + const x1 = bodyA.pos.x; + const y1 = bodyA.pos.y; + const x2 = bodyB.pos.x; + const y2 = bodyB.pos.y; + const r1 = bodyA.r; + const r2 = bodyB.r; + const distSq = Math.sqrt( + (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2), + ); + return distSq + r2 === r1 || distSq + r2 < r1; } - } - /** - * do not attempt to use Polygon.updateIsConvex() - */ - updateIsConvex() { - return; - } -} -exports.Box = Box; - - -/***/ }), - -/***/ "./src/bodies/circle.ts": -/*!******************************!*\ - !*** ./src/bodies/circle.ts ***! - \******************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Circle = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -/** - * collider - circle - */ -class Circle extends sat_1.Circle { - /** - * collider - circle - */ - constructor(position, radius, options) { - super((0, utils_1.ensureVectorPoint)(position), radius); /** - * offset copy without angle applied + * https://stackoverflow.com/a/68197894/1749528 */ - this.offsetCopy = { x: 0, y: 0 }; + function circleInPolygon(circle, polygon) { + // Circle with radius 0 isn't a circle + if (circle.r === 0) { + return false; + } + // If the center of the circle is not within the polygon, + // then the circle may overlap, but it'll never be "contained" + // so return false + if (!pointInPolygon(circle.pos, polygon)) { + return false; + } + // Necessary add polygon pos to points + const points = (0, optimized_1.map)( + polygon.calcPoints, + ({ x, y }) => ({ + x: x + polygon.pos.x, + y: y + polygon.pos.y, + }), + ); + // If the center of the circle is within the polygon, + // the circle is not outside of the polygon completely. + // so return false. + if ( + (0, optimized_1.some)(points, (point) => + (0, sat_1.pointInCircle)(point, circle), + ) + ) { + return false; + } + // If any line-segment of the polygon intersects the circle, + // the circle is not "contained" + // so return false + if ( + (0, optimized_1.some)(points, (end, index) => { + const start = index + ? points[index - 1] + : points[points.length - 1]; + return intersectLineCircle({ start, end }, circle).length > 0; + }) + ) { + return false; + } + return true; + } /** - * was the polygon modified and needs update in the next checkCollision - */ - this.dirty = false; - /* - * circles are convex + * https://stackoverflow.com/a/68197894/1749528 */ - this.isConvex = true; + function circleOutsidePolygon(circle, polygon) { + // Circle with radius 0 isn't a circle + if (circle.r === 0) { + return false; + } + // If the center of the circle is within the polygon, + // the circle is not outside of the polygon completely. + // so return false. + if (pointInPolygon(circle.pos, polygon)) { + return false; + } + // Necessary add polygon pos to points + const points = (0, optimized_1.map)( + polygon.calcPoints, + ({ x, y }) => ({ + x: x + polygon.pos.x, + y: y + polygon.pos.y, + }), + ); + // If the center of the circle is within the polygon, + // the circle is not outside of the polygon completely. + // so return false. + if ( + (0, optimized_1.some)( + points, + (point) => + (0, sat_1.pointInCircle)(point, circle) || + pointOnCircle(point, circle), + ) + ) { + return false; + } + // If any line-segment of the polygon intersects the circle, + // the circle is not "contained" + // so return false + if ( + (0, optimized_1.some)(points, (end, index) => { + const start = index + ? points[index - 1] + : points[points.length - 1]; + return intersectLineCircle({ start, end }, circle).length > 0; + }) + ) { + return false; + } + return true; + } /** - * circle type + * https://stackoverflow.com/a/37225895/1749528 */ - this.type = model_1.BodyType.Circle; + function intersectLineCircle(line, { pos, r }) { + const v1 = { + x: line.end.x - line.start.x, + y: line.end.y - line.start.y, + }; + const v2 = { x: line.start.x - pos.x, y: line.start.y - pos.y }; + const b = (v1.x * v2.x + v1.y * v2.y) * -2; + const c = (v1.x * v1.x + v1.y * v1.y) * 2; + const d = Math.sqrt( + b * b - (v2.x * v2.x + v2.y * v2.y - r * r) * c * 2, + ); + if (isNaN(d)) { + // no intercept + return []; + } + const u1 = (b - d) / c; // these represent the unit distance of point one and two on the line + const u2 = (b + d) / c; + const results = []; // return array + if (u1 <= 1 && u1 >= 0) { + // add point if on the line segment + results.push({ + x: line.start.x + v1.x * u1, + y: line.start.y + v1.y * u1, + }); + } + if (u2 <= 1 && u2 >= 0) { + // second add point if on the line segment + results.push({ + x: line.start.x + v1.x * u2, + y: line.start.y + v1.y * u2, + }); + } + return results; + } /** - * faster than type + * helper for intersectLineLineFast */ - this.typeGroup = model_1.BodyGroup.Circle; + function isTurn(point1, point2, point3) { + const A = (point3.x - point1.x) * (point2.y - point1.y); + const B = (point2.x - point1.x) * (point3.y - point1.y); + return A > B + Number.EPSILON ? 1 : A + Number.EPSILON < B ? -1 : 0; + } /** - * always centered + * faster implementation of intersectLineLine + * https://stackoverflow.com/a/16725715/1749528 */ - this.isCentered = true; - (0, utils_1.extendBody)(this, options); - this.unscaledRadius = radius; - } - /** - * get this.pos.x - */ - get x() { - return this.pos.x; - } - /** - * updating this.pos.x by this.x = x updates AABB - */ - set x(x) { - this.pos.x = x; - this.markAsDirty(); - } - /** - * get this.pos.y - */ - get y() { - return this.pos.y; - } - /** - * updating this.pos.y by this.y = y updates AABB - */ - set y(y) { - this.pos.y = y; - this.markAsDirty(); - } - /** - * allow get scale - */ - get scale() { - return this.r / this.unscaledRadius; - } - /** - * shorthand for setScale() - */ - set scale(scale) { - this.setScale(scale); - } - /** - * scaleX = scale in case of Circles - */ - get scaleX() { - return this.scale; - } - /** - * scaleY = scale in case of Circles - */ - get scaleY() { - return this.scale; - } - /** - * group for collision filtering - */ - get group() { - return this._group; - } - set group(group) { - this._group = (0, utils_1.getGroup)(group); - } - /** - * update position BY MOVING FORWARD IN ANGLE DIRECTION - */ - move(speed = 1, updateNow = true) { - (0, utils_1.move)(this, speed, updateNow); - return this; - } - /** - * update position BY TELEPORTING - */ - setPosition(x, y, updateNow = true) { - this.pos.x = x; - this.pos.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * update scale - */ - setScale(scaleX, _scaleY = scaleX, updateNow = true) { - this.r = this.unscaledRadius * Math.abs(scaleX); - this.markAsDirty(updateNow); - return this; - } - /** - * set rotation - */ - setAngle(angle, updateNow = true) { - this.angle = angle; - const { x, y } = this.getOffsetWithAngle(); - this.offset.x = x; - this.offset.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * set offset from center - */ - setOffset(offset, updateNow = true) { - this.offsetCopy.x = offset.x; - this.offsetCopy.y = offset.y; - const { x, y } = this.getOffsetWithAngle(); - this.offset.x = x; - this.offset.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * get body bounding box, without padding - */ - getAABBAsBBox() { - const x = this.pos.x + this.offset.x; - const y = this.pos.y + this.offset.y; - return { - minX: x - this.r, - maxX: x + this.r, - minY: y - this.r, - maxY: y + this.r - }; - } - /** - * Draws collider on a CanvasRenderingContext2D's current path - */ - draw(context) { - const x = this.pos.x + this.offset.x; - const y = this.pos.y + this.offset.y; - const r = Math.abs(this.r); - if (this.isTrigger) { - const max = Math.max(8, this.r); - for (let i = 0; i < max; i++) { - const arc = (i / max) * 2 * Math.PI; - const arcPrev = ((i - 1) / max) * 2 * Math.PI; - const fromX = x + Math.cos(arcPrev) * this.r; - const fromY = y + Math.sin(arcPrev) * this.r; - const toX = x + Math.cos(arc) * this.r; - const toY = y + Math.sin(arc) * this.r; - (0, utils_1.dashLineTo)(context, fromX, fromY, toX, toY); - } - } - else { - context.moveTo(x + r, y); - context.arc(x, y, r, 0, Math.PI * 2); - } - } - /** - * Draws Bounding Box on canvas context - */ - drawBVH(context) { - (0, utils_1.drawBVH)(context, this); - } - /** - * inner function for after position change update aabb in system - */ - updateBody(updateNow = this.dirty) { - var _a; - if (updateNow) { - (_a = this.system) === null || _a === void 0 ? void 0 : _a.insert(this); - this.dirty = false; - } - } - /** - * update instantly or mark as dirty - */ - markAsDirty(updateNow = false) { - if (updateNow) { - this.updateBody(true); + function intersectLineLineFast(line1, line2) { + return ( + isTurn(line1.start, line2.start, line2.end) !== + isTurn(line1.end, line2.start, line2.end) && + isTurn(line1.start, line1.end, line2.start) !== + isTurn(line1.start, line1.end, line2.end) + ); } - else { - this.dirty = true; + /** + * returns the point of intersection + * https://stackoverflow.com/a/24392281/1749528 + */ + function intersectLineLine(line1, line2) { + const dX = line1.end.x - line1.start.x; + const dY = line1.end.y - line1.start.y; + const determinant = + dX * (line2.end.y - line2.start.y) - + (line2.end.x - line2.start.x) * dY; + if (determinant === 0) { + return null; + } + const lambda = + ((line2.end.y - line2.start.y) * (line2.end.x - line1.start.x) + + (line2.start.x - line2.end.x) * (line2.end.y - line1.start.y)) / + determinant; + const gamma = + ((line1.start.y - line1.end.y) * (line2.end.x - line1.start.x) + + dX * (line2.end.y - line1.start.y)) / + determinant; + // check if there is an intersection + if (!(lambda >= 0 && lambda <= 1) || !(gamma >= 0 && gamma <= 1)) { + return null; + } + return { + x: line1.start.x + lambda * dX, + y: line1.start.y + lambda * dY, + }; } - } - /** - * internal for getting offset with applied angle - */ - getOffsetWithAngle() { - if ((!this.offsetCopy.x && !this.offsetCopy.y) || !this.angle) { - return this.offsetCopy; + function intersectLinePolygon(line, polygon) { + const results = []; + (0, optimized_1.forEach)(polygon.calcPoints, (to, index) => { + const from = index + ? polygon.calcPoints[index - 1] + : polygon.calcPoints[polygon.calcPoints.length - 1]; + const side = { + start: { x: from.x + polygon.pos.x, y: from.y + polygon.pos.y }, + end: { x: to.x + polygon.pos.x, y: to.y + polygon.pos.y }, + }; + const hit = intersectLineLine(line, side); + if (hit) { + results.push(hit); + } + }); + return results; } - const sin = Math.sin(this.angle); - const cos = Math.cos(this.angle); - const x = this.offsetCopy.x * cos - this.offsetCopy.y * sin; - const y = this.offsetCopy.x * sin + this.offsetCopy.y * cos; - return { x, y }; - } -} -exports.Circle = Circle; - -/***/ }), + /***/ + }, -/***/ "./src/bodies/ellipse.ts": -/*!*******************************!*\ - !*** ./src/bodies/ellipse.ts ***! - \*******************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Ellipse = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const polygon_1 = __webpack_require__(/*! ./polygon */ "./src/bodies/polygon.ts"); -const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -/** - * collider - ellipse - */ -class Ellipse extends polygon_1.Polygon { - /** - * collider - ellipse - */ - constructor(position, radiusX, radiusY = radiusX, step = (radiusX + radiusY) / Math.PI, options) { - super(position, (0, utils_1.createEllipse)(radiusX, radiusY, step), options); + /***/ "./src/model.ts": + /*!**********************!*\ + !*** ./src/model.ts ***! + \**********************/ + /***/ function (__unused_webpack_module, exports, __webpack_require__) { + "use strict"; + + var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.BodyGroup = + exports.BodyType = + exports.SATCircle = + exports.SATPolygon = + exports.SATVector = + exports.Response = + exports.RBush = + exports.isSimple = + void 0; + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); + Object.defineProperty(exports, "SATCircle", { + enumerable: true, + get: function () { + return sat_1.Circle; + }, + }); + Object.defineProperty(exports, "SATPolygon", { + enumerable: true, + get: function () { + return sat_1.Polygon; + }, + }); + Object.defineProperty(exports, "Response", { + enumerable: true, + get: function () { + return sat_1.Response; + }, + }); + Object.defineProperty(exports, "SATVector", { + enumerable: true, + get: function () { + return sat_1.Vector; + }, + }); + // version 4.0.0 1=1 copy + const rbush_1 = __importDefault( + __webpack_require__( + /*! ./external/rbush */ "./src/external/rbush.js", + ), + ); + exports.RBush = rbush_1.default; + var poly_decomp_es_1 = __webpack_require__( + /*! poly-decomp-es */ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js", + ); + Object.defineProperty(exports, "isSimple", { + enumerable: true, + get: function () { + return poly_decomp_es_1.isSimple; + }, + }); /** - * ellipse type + * types */ - this.type = model_1.BodyType.Ellipse; + var BodyType; + (function (BodyType) { + BodyType["Ellipse"] = "Ellipse"; + BodyType["Circle"] = "Circle"; + BodyType["Polygon"] = "Polygon"; + BodyType["Box"] = "Box"; + BodyType["Line"] = "Line"; + BodyType["Point"] = "Point"; + })(BodyType || (exports.BodyType = BodyType = {})); /** - * faster than type + * for groups */ - this.typeGroup = model_1.BodyGroup.Ellipse; + var BodyGroup; + (function (BodyGroup) { + BodyGroup[(BodyGroup["Ellipse"] = 32)] = "Ellipse"; + BodyGroup[(BodyGroup["Circle"] = 16)] = "Circle"; + BodyGroup[(BodyGroup["Polygon"] = 8)] = "Polygon"; + BodyGroup[(BodyGroup["Box"] = 4)] = "Box"; + BodyGroup[(BodyGroup["Line"] = 2)] = "Line"; + BodyGroup[(BodyGroup["Point"] = 1)] = "Point"; + })(BodyGroup || (exports.BodyGroup = BodyGroup = {})); + + /***/ + }, + + /***/ "./src/optimized.ts": + /*!**************************!*\ + !*** ./src/optimized.ts ***! + \**************************/ + /***/ (__unused_webpack_module, exports) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.map = + exports.filter = + exports.every = + exports.some = + exports.forEach = + void 0; /** - * ellipses are convex + * 40-90% faster than built-in Array.forEach function. + * + * basic benchmark: https://jsbench.me/urle772xdn */ - this.isConvex = true; - this._radiusX = radiusX; - this._radiusY = radiusY; - this._step = step; - } - /** - * flag to set is body centered - */ - set isCentered(_isCentered) { } - /** - * is body centered? - */ - get isCentered() { - return true; - } - /** - * get ellipse step number - */ - get step() { - return this._step; - } - /** - * set ellipse step number - */ - set step(step) { - this._step = step; - this.setPoints((0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step)); - } - /** - * get ellipse radiusX - */ - get radiusX() { - return this._radiusX; - } - /** - * set ellipse radiusX, update points - */ - set radiusX(radiusX) { - this._radiusX = radiusX; - this.setPoints((0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step)); - } - /** - * get ellipse radiusY - */ - get radiusY() { - return this._radiusY; - } - /** - * set ellipse radiusY, update points - */ - set radiusY(radiusY) { - this._radiusY = radiusY; - this.setPoints((0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step)); - } - /** - * do not attempt to use Polygon.center() - */ - center() { - return; - } - /** - * do not attempt to use Polygon.updateIsConvex() - */ - updateIsConvex() { - return; - } -} -exports.Ellipse = Ellipse; - - -/***/ }), - -/***/ "./src/bodies/line.ts": -/*!****************************!*\ - !*** ./src/bodies/line.ts ***! - \****************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Line = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const polygon_1 = __webpack_require__(/*! ./polygon */ "./src/bodies/polygon.ts"); -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -/** - * collider - line - */ -class Line extends polygon_1.Polygon { - /** - * collider - line from start to end - */ - constructor(start, end, options) { - super(start, [ - { x: 0, y: 0 }, - { x: end.x - start.x, y: end.y - start.y } - ], options); + const forEach = (array, callback) => { + for (let i = 0, l = array.length; i < l; i++) { + callback(array[i], i); + } + }; + exports.forEach = forEach; /** - * line type + * 20-90% faster than built-in Array.some function. + * + * basic benchmark: https://jsbench.me/l0le7bnnsq */ - this.type = model_1.BodyType.Line; + const some = (array, callback) => { + for (let i = 0, l = array.length; i < l; i++) { + if (callback(array[i], i)) { + return true; + } + } + return false; + }; + exports.some = some; /** - * faster than type + * 20-40% faster than built-in Array.every function. + * + * basic benchmark: https://jsbench.me/unle7da29v */ - this.typeGroup = model_1.BodyGroup.Line; + const every = (array, callback) => { + for (let i = 0, l = array.length; i < l; i++) { + if (!callback(array[i], i)) { + return false; + } + } + return true; + }; + exports.every = every; /** - * line is convex + * 20-60% faster than built-in Array.filter function. + * + * basic benchmark: https://jsbench.me/o1le77ev4l */ - this.isConvex = true; - if (this.calcPoints.length === 1 || !end) { - console.error({ start, end }); - throw new Error("No end point for line provided"); - } - } - get start() { - return { - x: this.x + this.calcPoints[0].x, - y: this.y + this.calcPoints[0].y + const filter = (array, callback) => { + const output = []; + for (let i = 0, l = array.length; i < l; i++) { + const item = array[i]; + if (callback(item, i)) { + output.push(item); + } + } + return output; }; - } - set start({ x, y }) { - this.x = x; - this.y = y; - } - get end() { - return { - x: this.x + this.calcPoints[1].x, - y: this.y + this.calcPoints[1].y + exports.filter = filter; + /** + * 20-70% faster than built-in Array.map + * + * basic benchmark: https://jsbench.me/oyle77vbpc + */ + const map = (array, callback) => { + const l = array.length; + const output = new Array(l); + for (let i = 0; i < l; i++) { + output[i] = callback(array[i], i); + } + return output; }; - } - set end({ x, y }) { - this.points[1].x = x - this.start.x; - this.points[1].y = y - this.start.y; - this.setPoints(this.points); - } - getCentroid() { - return new sat_1.Vector((this.end.x - this.start.x) / 2, (this.end.y - this.start.y) / 2); - } - /** - * do not attempt to use Polygon.updateIsConvex() - */ - updateIsConvex() { - return; - } -} -exports.Line = Line; + exports.map = map; + /***/ + }, -/***/ }), - -/***/ "./src/bodies/point.ts": -/*!*****************************!*\ - !*** ./src/bodies/point.ts ***! - \*****************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Point = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const box_1 = __webpack_require__(/*! ./box */ "./src/bodies/box.ts"); -const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -/** - * collider - point (very tiny box) - */ -class Point extends box_1.Box { - /** - * collider - point (very tiny box) - */ - constructor(position, options) { - super((0, utils_1.ensureVectorPoint)(position), 0.001, 0.001, options); - /** - * point type - */ - this.type = model_1.BodyType.Point; + /***/ "./src/system.ts": + /*!***********************!*\ + !*** ./src/system.ts ***! + \***********************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.System = void 0; + const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); + const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts"); + const intersect_1 = __webpack_require__( + /*! ./intersect */ "./src/intersect.ts", + ); + const optimized_1 = __webpack_require__( + /*! ./optimized */ "./src/optimized.ts", + ); + const base_system_1 = __webpack_require__( + /*! ./base-system */ "./src/base-system.ts", + ); + const line_1 = __webpack_require__( + /*! ./bodies/line */ "./src/bodies/line.ts", + ); /** - * faster than type + * collision system */ - this.typeGroup = model_1.BodyGroup.Point; - } -} -exports.Point = Point; - + class System extends base_system_1.BaseSystem { + constructor() { + super(...arguments); + /** + * the last collision result + */ + this.response = new model_1.Response(); + } + /** + * re-insert body into collision tree and update its bbox + * every body can be part of only one system + */ + insert(body) { + const insertResult = super.insert(body); + // set system for later body.system.updateBody(body) + body.system = this; + return insertResult; + } + /** + * separate (move away) bodies + */ + separate() { + (0, optimized_1.forEach)(this.all(), (body) => { + this.separateBody(body); + }); + } + /** + * separate (move away) 1 body + */ + separateBody(body) { + if (body.isStatic || body.isTrigger) { + return; + } + const offsets = { x: 0, y: 0 }; + const addOffsets = ({ overlapV: { x, y } }) => { + offsets.x += x; + offsets.y += y; + }; + this.checkOne(body, addOffsets); + if (offsets.x || offsets.y) { + body.setPosition(body.x - offsets.x, body.y - offsets.y); + } + } + /** + * check one body collisions with callback + */ + checkOne( + body, + callback = utils_1.returnTrue, + response = this.response, + ) { + // no need to check static body collision + if (body.isStatic) { + return false; + } + const bodies = this.search(body); + const checkCollision = (candidate) => { + if ( + candidate !== body && + this.checkCollision(body, candidate, response) + ) { + return callback(response); + } + }; + return (0, optimized_1.some)(bodies, checkCollision); + } + /** + * check all bodies collisions in area with callback + */ + checkArea( + area, + callback = utils_1.returnTrue, + response = this.response, + ) { + const checkOne = (body) => { + return this.checkOne(body, callback, response); + }; + return (0, optimized_1.some)(this.search(area), checkOne); + } + /** + * check all bodies collisions with callback + */ + checkAll(callback = utils_1.returnTrue, response = this.response) { + const checkOne = (body) => { + return this.checkOne(body, callback, response); + }; + return (0, optimized_1.some)(this.all(), checkOne); + } + /** + * check do 2 objects collide + */ + checkCollision(bodyA, bodyB, response = this.response) { + const { bbox: bboxA } = bodyA; + const { bbox: bboxB } = bodyB; + // assess the bodies real aabb without padding + if ( + !(0, utils_1.canInteract)(bodyA, bodyB) || + !bboxA || + !bboxB || + (0, utils_1.notIntersectAABB)(bboxA, bboxB) + ) { + return false; + } + const sat = (0, utils_1.getSATTest)(bodyA, bodyB); + // 99% of cases + if (bodyA.isConvex && bodyB.isConvex) { + // always first clear response + response.clear(); + return sat(bodyA, bodyB, response); + } + // more complex (non convex) cases + const convexBodiesA = (0, intersect_1.ensureConvex)(bodyA); + const convexBodiesB = (0, intersect_1.ensureConvex)(bodyB); + let overlapX = 0; + let overlapY = 0; + let collided = false; + (0, optimized_1.forEach)(convexBodiesA, (convexBodyA) => { + (0, optimized_1.forEach)(convexBodiesB, (convexBodyB) => { + // always first clear response + response.clear(); + if (sat(convexBodyA, convexBodyB, response)) { + collided = true; + overlapX += response.overlapV.x; + overlapY += response.overlapV.y; + } + }); + }); + if (collided) { + const vector = new model_1.SATVector(overlapX, overlapY); + response.a = bodyA; + response.b = bodyB; + response.overlapV.x = overlapX; + response.overlapV.y = overlapY; + response.overlapN = vector.normalize(); + response.overlap = vector.len(); + response.aInB = (0, utils_1.checkAInB)(bodyA, bodyB); + response.bInA = (0, utils_1.checkAInB)(bodyB, bodyA); + } + return collided; + } + /** + * raycast to get collider of ray from start to end + */ + raycast(start, end, allow = utils_1.returnTrue) { + let minDistance = Infinity; + let result = null; + if (!this.ray) { + this.ray = new line_1.Line(start, end, { isTrigger: true }); + } else { + this.ray.start = start; + this.ray.end = end; + } + this.insert(this.ray); + this.checkOne(this.ray, ({ b: body }) => { + if (!allow(body, this.ray)) { + return false; + } + const points = + body.typeGroup === model_1.BodyGroup.Circle + ? (0, intersect_1.intersectLineCircle)(this.ray, body) + : (0, intersect_1.intersectLinePolygon)(this.ray, body); + (0, optimized_1.forEach)(points, (point) => { + const pointDistance = (0, utils_1.distance)(start, point); + if (pointDistance < minDistance) { + minDistance = pointDistance; + result = { point, body }; + } + }); + }); + this.remove(this.ray); + return result; + } + } + exports.System = System; -/***/ }), + /***/ + }, -/***/ "./src/bodies/polygon.ts": -/*!*******************************!*\ - !*** ./src/bodies/polygon.ts ***! - \*******************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Polygon = exports.isSimple = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -const optimized_1 = __webpack_require__(/*! ../optimized */ "./src/optimized.ts"); -const poly_decomp_es_1 = __webpack_require__(/*! poly-decomp-es */ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js"); -Object.defineProperty(exports, "isSimple", ({ enumerable: true, get: function () { return poly_decomp_es_1.isSimple; } })); -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -/** - * collider - polygon - */ -class Polygon extends sat_1.Polygon { - /** - * collider - polygon - */ - constructor(position, points, options) { - super((0, utils_1.ensureVectorPoint)(position), (0, utils_1.ensurePolygonPoints)(points)); - /** - * was the polygon modified and needs update in the next checkCollision - */ - this.dirty = false; + /***/ "./src/utils.ts": + /*!**********************!*\ + !*** ./src/utils.ts ***! + \**********************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.RAD2DEG = exports.DEG2RAD = void 0; + exports.deg2rad = deg2rad; + exports.rad2deg = rad2deg; + exports.createEllipse = createEllipse; + exports.createBox = createBox; + exports.ensureVectorPoint = ensureVectorPoint; + exports.ensurePolygonPoints = ensurePolygonPoints; + exports.distance = distance; + exports.clockwise = clockwise; + exports.extendBody = extendBody; + exports.bodyMoved = bodyMoved; + exports.notIntersectAABB = notIntersectAABB; + exports.intersectAABB = intersectAABB; + exports.canInteract = canInteract; + exports.checkAInB = checkAInB; + exports.clonePointsArray = clonePointsArray; + exports.mapVectorToArray = mapVectorToArray; + exports.mapArrayToVector = mapArrayToVector; + exports.getBounceDirection = getBounceDirection; + exports.getSATTest = getSATTest; + exports.dashLineTo = dashLineTo; + exports.drawPolygon = drawPolygon; + exports.drawBVH = drawBVH; + exports.cloneResponse = cloneResponse; + exports.returnTrue = returnTrue; + exports.getGroup = getGroup; + exports.bin2dec = bin2dec; + exports.ensureNumber = ensureNumber; + exports.groupBits = groupBits; + exports.move = move; + const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); + const intersect_1 = __webpack_require__( + /*! ./intersect */ "./src/intersect.ts", + ); + const optimized_1 = __webpack_require__( + /*! ./optimized */ "./src/optimized.ts", + ); + /* helpers for faster getSATTest() and checkAInB() */ + const testMap = { + satCircleCircle: sat_1.testCircleCircle, + satCirclePolygon: sat_1.testCirclePolygon, + satPolygonCircle: sat_1.testPolygonCircle, + satPolygonPolygon: sat_1.testPolygonPolygon, + inCircleCircle: intersect_1.circleInCircle, + inCirclePolygon: intersect_1.circleInPolygon, + inPolygonCircle: intersect_1.polygonInCircle, + inPolygonPolygon: intersect_1.polygonInPolygon, + }; + function createArray(bodyType, testType) { + const arrayResult = []; + const bodyGroups = Object.values(model_1.BodyGroup).filter( + (value) => typeof value === "number", + ); + bodyGroups.forEach((bodyGroup) => { + arrayResult[bodyGroup] = + bodyGroup === model_1.BodyGroup.Circle + ? testMap[`${testType}${bodyType}Circle`] + : testMap[`${testType}${bodyType}Polygon`]; + }); + return arrayResult; + } + const circleSATFunctions = createArray(model_1.BodyType.Circle, "sat"); + const circleInFunctions = createArray(model_1.BodyType.Circle, "in"); + const polygonSATFunctions = createArray( + model_1.BodyType.Polygon, + "sat", + ); + const polygonInFunctions = createArray(model_1.BodyType.Polygon, "in"); + exports.DEG2RAD = Math.PI / 180; + exports.RAD2DEG = 180 / Math.PI; /** - * type of body + * convert from degrees to radians */ - this.type = model_1.BodyType.Polygon; + function deg2rad(degrees) { + return degrees * exports.DEG2RAD; + } /** - * faster than type + * convert from radians to degrees */ - this.typeGroup = model_1.BodyGroup.Polygon; + function rad2deg(radians) { + return radians * exports.RAD2DEG; + } /** - * is body centered + * creates ellipse-shaped polygon based on params */ - this.centered = false; + function createEllipse(radiusX, radiusY = radiusX, step = 1) { + const steps = Math.PI * Math.hypot(radiusX, radiusY) * 2; + const length = Math.max(8, Math.ceil(steps / Math.max(1, step))); + const ellipse = []; + for (let index = 0; index < length; index++) { + const value = (index / length) * 2 * Math.PI; + const x = Math.cos(value) * radiusX; + const y = Math.sin(value) * radiusY; + ellipse.push(new sat_1.Vector(x, y)); + } + return ellipse; + } /** - * scale Vector of body + * creates box shaped polygon points */ - this.scaleVector = { x: 1, y: 1 }; - if (!points.length) { - throw new Error("No points in polygon"); - } - (0, utils_1.extendBody)(this, options); - } - /** - * flag to set is polygon centered - */ - set isCentered(isCentered) { - if (this.centered === isCentered) { - return; - } - const centroid = this.getCentroidWithoutRotation(); - if (centroid.x || centroid.y) { - const x = centroid.x * (isCentered ? 1 : -1); - const y = centroid.y * (isCentered ? 1 : -1); - this.translate(-x, -y); - } - this.centered = isCentered; - } - /** - * is polygon centered? - */ - get isCentered() { - return this.centered; - } - get x() { - return this.pos.x; - } - /** - * updating this.pos.x by this.x = x updates AABB - */ - set x(x) { - this.pos.x = x; - this.markAsDirty(); - } - get y() { - return this.pos.y; - } - /** - * updating this.pos.y by this.y = y updates AABB - */ - set y(y) { - this.pos.y = y; - this.markAsDirty(); - } - /** - * allow exact getting of scale x - use setScale(x, y) to set - */ - get scaleX() { - return this.scaleVector.x; - } - /** - * allow exact getting of scale y - use setScale(x, y) to set - */ - get scaleY() { - return this.scaleVector.y; - } - /** - * allow approx getting of scale - */ - get scale() { - return (this.scaleVector.x + this.scaleVector.y) / 2; - } - /** - * allow easier setting of scale - */ - set scale(scale) { - this.setScale(scale); - } - /** - * group for collision filtering - */ - get group() { - return this._group; - } - set group(group) { - this._group = (0, utils_1.getGroup)(group); - } - /** - * update position BY MOVING FORWARD IN ANGLE DIRECTION - */ - move(speed = 1, updateNow = true) { - (0, utils_1.move)(this, speed, updateNow); - return this; - } - /** - * update position BY TELEPORTING - */ - setPosition(x, y, updateNow = true) { - this.pos.x = x; - this.pos.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * update scale - */ - setScale(x, y = x, updateNow = true) { - this.scaleVector.x = Math.abs(x); - this.scaleVector.y = Math.abs(y); - super.setPoints((0, optimized_1.map)(this.points, (point, index) => { - point.x = this.pointsBackup[index].x * this.scaleVector.x; - point.y = this.pointsBackup[index].y * this.scaleVector.y; - return point; - })); - this.markAsDirty(updateNow); - return this; - } - setAngle(angle, updateNow = true) { - super.setAngle(angle); - this.markAsDirty(updateNow); - return this; - } - setOffset(offset, updateNow = true) { - super.setOffset(offset); - this.markAsDirty(updateNow); - return this; - } - /** - * get body bounding box, without padding - */ - getAABBAsBBox() { - const { pos, w, h } = this.getAABBAsBox(); - return { - minX: pos.x, - minY: pos.y, - maxX: pos.x + w, - maxY: pos.y + h - }; - } - /** - * Draws exact collider on canvas context - */ - draw(context) { - (0, utils_1.drawPolygon)(context, this, this.isTrigger); - } - /** - * Draws Bounding Box on canvas context - */ - drawBVH(context) { - (0, utils_1.drawBVH)(context, this); - } - /** - * get body centroid without applied angle - */ - getCentroidWithoutRotation() { - // keep angle copy - const angle = this.angle; - if (angle) { - // reset angle for get centroid - this.setAngle(0); - // get centroid - const centroid = this.getCentroid(); - // revert angle change - this.setAngle(angle); - return centroid; + function createBox(width, height) { + return [ + new sat_1.Vector(0, 0), + new sat_1.Vector(width, 0), + new sat_1.Vector(width, height), + new sat_1.Vector(0, height), + ]; } - return this.getCentroid(); - } - /** - * sets polygon points to new array of vectors - */ - setPoints(points) { - super.setPoints(points); - this.updateIsConvex(); - this.pointsBackup = (0, utils_1.clonePointsArray)(points); - return this; - } - /** - * translates polygon points in x, y direction - */ - translate(x, y) { - super.translate(x, y); - this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); - return this; - } - /** - * rotates polygon points by angle, in radians - */ - rotate(angle) { - super.rotate(angle); - this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); - return this; - } - /** - * if true, polygon is not an invalid, self-crossing polygon - */ - isSimple() { - return (0, poly_decomp_es_1.isSimple)(this.calcPoints.map(utils_1.mapVectorToArray)); - } - /** - * inner function for after position change update aabb in system and convex inner polygons - */ - updateBody(updateNow = this.dirty) { - var _a; - if (updateNow) { - this.updateConvexPolygonPositions(); - (_a = this.system) === null || _a === void 0 ? void 0 : _a.insert(this); - this.dirty = false; + /** + * ensure SATVector type point result + */ + function ensureVectorPoint(point = {}) { + return point instanceof sat_1.Vector + ? point + : new sat_1.Vector(point.x || 0, point.y || 0); } - } - retranslate(isCentered = this.isCentered) { - const centroid = this.getCentroidWithoutRotation(); - if (centroid.x || centroid.y) { - const x = centroid.x * (isCentered ? 1 : -1); - const y = centroid.y * (isCentered ? 1 : -1); - this.translate(-x, -y); + /** + * ensure Vector points (for polygon) in counter-clockwise order + */ + function ensurePolygonPoints(points = []) { + const polygonPoints = (0, optimized_1.map)(points, ensureVectorPoint); + return clockwise(polygonPoints) + ? polygonPoints.reverse() + : polygonPoints; } - } - /** - * update instantly or mark as dirty - */ - markAsDirty(updateNow = false) { - if (updateNow) { - this.updateBody(true); + /** + * get distance between two Vector points + */ + function distance(bodyA, bodyB) { + const xDiff = bodyA.x - bodyB.x; + const yDiff = bodyA.y - bodyB.y; + return Math.hypot(xDiff, yDiff); } - else { - this.dirty = true; + /** + * check [is clockwise] direction of polygon + */ + function clockwise(points) { + const length = points.length; + let sum = 0; + (0, optimized_1.forEach)(points, (v1, index) => { + const v2 = points[(index + 1) % length]; + sum += (v2.x - v1.x) * (v2.y + v1.y); + }); + return sum > 0; } - } - /** - * update the position of the decomposed convex polygons (if any), called - * after the position of the body has changed - */ - updateConvexPolygonPositions() { - if (this.isConvex || !this.convexPolygons) { - return; + /** + * used for all types of bodies in constructor + */ + function extendBody(body, options = {}) { + body.isStatic = !!options.isStatic; + body.isTrigger = !!options.isTrigger; + body.padding = options.padding || 0; + body.group = + typeof options.group === "number" ? options.group : 0x7fffffff; + if (body.typeGroup !== model_1.BodyGroup.Circle) { + body.isCentered = options.isCentered || false; + } + body.setAngle(options.angle || 0); } - (0, optimized_1.forEach)(this.convexPolygons, (polygon) => { - polygon.pos.x = this.pos.x; - polygon.pos.y = this.pos.y; - if (polygon.angle !== this.angle) { - // Must use setAngle to recalculate the points of the Polygon - polygon.setAngle(this.angle); - } - }); - } - /** - * returns body split into convex polygons, or empty array for convex bodies - */ - getConvex() { - if ((this.typeGroup && this.typeGroup !== model_1.BodyGroup.Polygon) || - this.points.length < 4) { - return []; + /** + * check if body moved outside of its padding + */ + function bodyMoved(body) { + const { bbox, minX, minY, maxX, maxY } = body; + return ( + bbox.minX < minX || + bbox.minY < minY || + bbox.maxX > maxX || + bbox.maxY > maxY + ); } - const points = (0, optimized_1.map)(this.calcPoints, utils_1.mapVectorToArray); - return (0, poly_decomp_es_1.quickDecomp)(points); - } - /** - * updates convex polygons cache in body - */ - updateConvexPolygons(convex = this.getConvex()) { - if (this.isConvex) { - return; + /** + * returns true if two boxes not intersect + */ + function notIntersectAABB(bodyA, bodyB) { + return ( + bodyB.minX > bodyA.maxX || + bodyB.minY > bodyA.maxY || + bodyB.maxX < bodyA.minX || + bodyB.maxY < bodyA.minY + ); } - if (!this.convexPolygons) { - this.convexPolygons = []; + /** + * checks if two boxes intersect + */ + function intersectAABB(bodyA, bodyB) { + return !notIntersectAABB(bodyA, bodyB); } - (0, optimized_1.forEach)(convex, (points, index) => { - // lazy create - if (!this.convexPolygons[index]) { - this.convexPolygons[index] = new sat_1.Polygon(); - } - this.convexPolygons[index].pos.x = this.pos.x; - this.convexPolygons[index].pos.y = this.pos.y; - this.convexPolygons[index].angle = this.angle; - this.convexPolygons[index].setPoints((0, utils_1.ensurePolygonPoints)((0, optimized_1.map)(points, utils_1.mapArrayToVector))); - }); - // trim array length - this.convexPolygons.length = convex.length; - } - /** - * after points update set is convex - */ - updateIsConvex() { - // all other types other than polygon are always convex - const convex = this.getConvex(); - // everything with empty array or one element array - this.isConvex = convex.length <= 1; - this.updateConvexPolygons(convex); - } -} -exports.Polygon = Polygon; - - -/***/ }), - -/***/ "./src/intersect.ts": -/*!**************************!*\ - !*** ./src/intersect.ts ***! - \**************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.ensureConvex = ensureConvex; -exports.polygonInCircle = polygonInCircle; -exports.pointInPolygon = pointInPolygon; -exports.polygonInPolygon = polygonInPolygon; -exports.pointOnCircle = pointOnCircle; -exports.circleInCircle = circleInCircle; -exports.circleInPolygon = circleInPolygon; -exports.circleOutsidePolygon = circleOutsidePolygon; -exports.intersectLineCircle = intersectLineCircle; -exports.intersectLineLineFast = intersectLineLineFast; -exports.intersectLineLine = intersectLineLine; -exports.intersectLinePolygon = intersectLinePolygon; -const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); -const optimized_1 = __webpack_require__(/*! ./optimized */ "./src/optimized.ts"); -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -/** - * replace body with array of related convex polygons - */ -function ensureConvex(body) { - if (body.isConvex || body.typeGroup !== model_1.BodyGroup.Polygon) { - return [body]; - } - return body.convexPolygons; -} -function polygonInCircle(polygon, circle) { - return (0, optimized_1.every)(polygon.calcPoints, p => (0, sat_1.pointInCircle)({ x: p.x + polygon.pos.x, y: p.y + polygon.pos.y }, circle)); -} -function pointInPolygon(point, polygon) { - return (0, optimized_1.some)(ensureConvex(polygon), convex => (0, sat_1.pointInPolygon)(point, convex)); -} -function polygonInPolygon(polygonA, polygonB) { - return (0, optimized_1.every)(polygonA.calcPoints, point => pointInPolygon({ x: point.x + polygonA.pos.x, y: point.y + polygonA.pos.y }, polygonB)); -} -/** - * https://stackoverflow.com/a/68197894/1749528 - */ -function pointOnCircle(point, circle) { - return ((point.x - circle.pos.x) * (point.x - circle.pos.x) + - (point.y - circle.pos.y) * (point.y - circle.pos.y) === - circle.r * circle.r); -} -/** - * https://stackoverflow.com/a/68197894/1749528 - */ -function circleInCircle(bodyA, bodyB) { - const x1 = bodyA.pos.x; - const y1 = bodyA.pos.y; - const x2 = bodyB.pos.x; - const y2 = bodyB.pos.y; - const r1 = bodyA.r; - const r2 = bodyB.r; - const distSq = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); - return distSq + r2 === r1 || distSq + r2 < r1; -} -/** - * https://stackoverflow.com/a/68197894/1749528 - */ -function circleInPolygon(circle, polygon) { - // Circle with radius 0 isn't a circle - if (circle.r === 0) { - return false; - } - // If the center of the circle is not within the polygon, - // then the circle may overlap, but it'll never be "contained" - // so return false - if (!pointInPolygon(circle.pos, polygon)) { - return false; - } - // Necessary add polygon pos to points - const points = (0, optimized_1.map)(polygon.calcPoints, ({ x, y }) => ({ - x: x + polygon.pos.x, - y: y + polygon.pos.y - })); - // If the center of the circle is within the polygon, - // the circle is not outside of the polygon completely. - // so return false. - if ((0, optimized_1.some)(points, point => (0, sat_1.pointInCircle)(point, circle))) { - return false; - } - // If any line-segment of the polygon intersects the circle, - // the circle is not "contained" - // so return false - if ((0, optimized_1.some)(points, (end, index) => { - const start = index - ? points[index - 1] - : points[points.length - 1]; - return intersectLineCircle({ start, end }, circle).length > 0; - })) { - return false; - } - return true; -} -/** - * https://stackoverflow.com/a/68197894/1749528 - */ -function circleOutsidePolygon(circle, polygon) { - // Circle with radius 0 isn't a circle - if (circle.r === 0) { - return false; - } - // If the center of the circle is within the polygon, - // the circle is not outside of the polygon completely. - // so return false. - if (pointInPolygon(circle.pos, polygon)) { - return false; - } - // Necessary add polygon pos to points - const points = (0, optimized_1.map)(polygon.calcPoints, ({ x, y }) => ({ - x: x + polygon.pos.x, - y: y + polygon.pos.y - })); - // If the center of the circle is within the polygon, - // the circle is not outside of the polygon completely. - // so return false. - if ((0, optimized_1.some)(points, point => (0, sat_1.pointInCircle)(point, circle) || pointOnCircle(point, circle))) { - return false; - } - // If any line-segment of the polygon intersects the circle, - // the circle is not "contained" - // so return false - if ((0, optimized_1.some)(points, (end, index) => { - const start = index - ? points[index - 1] - : points[points.length - 1]; - return intersectLineCircle({ start, end }, circle).length > 0; - })) { - return false; - } - return true; -} -/** - * https://stackoverflow.com/a/37225895/1749528 - */ -function intersectLineCircle(line, { pos, r }) { - const v1 = { x: line.end.x - line.start.x, y: line.end.y - line.start.y }; - const v2 = { x: line.start.x - pos.x, y: line.start.y - pos.y }; - const b = (v1.x * v2.x + v1.y * v2.y) * -2; - const c = (v1.x * v1.x + v1.y * v1.y) * 2; - const d = Math.sqrt(b * b - (v2.x * v2.x + v2.y * v2.y - r * r) * c * 2); - if (isNaN(d)) { - // no intercept - return []; - } - const u1 = (b - d) / c; // these represent the unit distance of point one and two on the line - const u2 = (b + d) / c; - const results = []; // return array - if (u1 <= 1 && u1 >= 0) { - // add point if on the line segment - results.push({ x: line.start.x + v1.x * u1, y: line.start.y + v1.y * u1 }); - } - if (u2 <= 1 && u2 >= 0) { - // second add point if on the line segment - results.push({ x: line.start.x + v1.x * u2, y: line.start.y + v1.y * u2 }); - } - return results; -} -/** - * helper for intersectLineLineFast - */ -function isTurn(point1, point2, point3) { - const A = (point3.x - point1.x) * (point2.y - point1.y); - const B = (point2.x - point1.x) * (point3.y - point1.y); - return A > B + Number.EPSILON ? 1 : A + Number.EPSILON < B ? -1 : 0; -} -/** - * faster implementation of intersectLineLine - * https://stackoverflow.com/a/16725715/1749528 - */ -function intersectLineLineFast(line1, line2) { - return (isTurn(line1.start, line2.start, line2.end) !== - isTurn(line1.end, line2.start, line2.end) && - isTurn(line1.start, line1.end, line2.start) !== - isTurn(line1.start, line1.end, line2.end)); -} -/** - * returns the point of intersection - * https://stackoverflow.com/a/24392281/1749528 - */ -function intersectLineLine(line1, line2) { - const dX = line1.end.x - line1.start.x; - const dY = line1.end.y - line1.start.y; - const determinant = dX * (line2.end.y - line2.start.y) - (line2.end.x - line2.start.x) * dY; - if (determinant === 0) { - return null; - } - const lambda = ((line2.end.y - line2.start.y) * (line2.end.x - line1.start.x) + - (line2.start.x - line2.end.x) * (line2.end.y - line1.start.y)) / - determinant; - const gamma = ((line1.start.y - line1.end.y) * (line2.end.x - line1.start.x) + - dX * (line2.end.y - line1.start.y)) / - determinant; - // check if there is an intersection - if (!(lambda >= 0 && lambda <= 1) || !(gamma >= 0 && gamma <= 1)) { - return null; - } - return { x: line1.start.x + lambda * dX, y: line1.start.y + lambda * dY }; -} -function intersectLinePolygon(line, polygon) { - const results = []; - (0, optimized_1.forEach)(polygon.calcPoints, (to, index) => { - const from = index - ? polygon.calcPoints[index - 1] - : polygon.calcPoints[polygon.calcPoints.length - 1]; - const side = { - start: { x: from.x + polygon.pos.x, y: from.y + polygon.pos.y }, - end: { x: to.x + polygon.pos.x, y: to.y + polygon.pos.y } - }; - const hit = intersectLineLine(line, side); - if (hit) { - results.push(hit); + /** + * checks if two bodies can interact (for collision filtering) + */ + function canInteract(bodyA, bodyB) { + return ( + ((bodyA.group >> 16) & (bodyB.group & 0xffff) && + (bodyB.group >> 16) & (bodyA.group & 0xffff)) !== 0 + ); } - }); - return results; -} - - -/***/ }), - -/***/ "./src/model.ts": -/*!**********************!*\ - !*** ./src/model.ts ***! - \**********************/ -/***/ (function(__unused_webpack_module, exports, __webpack_require__) { - -"use strict"; - -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.BodyGroup = exports.BodyType = exports.SATCircle = exports.SATPolygon = exports.SATVector = exports.Response = exports.RBush = exports.isSimple = void 0; -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -Object.defineProperty(exports, "SATCircle", ({ enumerable: true, get: function () { return sat_1.Circle; } })); -Object.defineProperty(exports, "SATPolygon", ({ enumerable: true, get: function () { return sat_1.Polygon; } })); -Object.defineProperty(exports, "Response", ({ enumerable: true, get: function () { return sat_1.Response; } })); -Object.defineProperty(exports, "SATVector", ({ enumerable: true, get: function () { return sat_1.Vector; } })); -// version 4.0.0 1=1 copy -const rbush_1 = __importDefault(__webpack_require__(/*! ./external/rbush */ "./src/external/rbush.js")); -exports.RBush = rbush_1.default; -var poly_decomp_es_1 = __webpack_require__(/*! poly-decomp-es */ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js"); -Object.defineProperty(exports, "isSimple", ({ enumerable: true, get: function () { return poly_decomp_es_1.isSimple; } })); -/** - * types - */ -var BodyType; -(function (BodyType) { - BodyType["Ellipse"] = "Ellipse"; - BodyType["Circle"] = "Circle"; - BodyType["Polygon"] = "Polygon"; - BodyType["Box"] = "Box"; - BodyType["Line"] = "Line"; - BodyType["Point"] = "Point"; -})(BodyType || (exports.BodyType = BodyType = {})); -/** - * for groups - */ -var BodyGroup; -(function (BodyGroup) { - BodyGroup[BodyGroup["Ellipse"] = 32] = "Ellipse"; - BodyGroup[BodyGroup["Circle"] = 16] = "Circle"; - BodyGroup[BodyGroup["Polygon"] = 8] = "Polygon"; - BodyGroup[BodyGroup["Box"] = 4] = "Box"; - BodyGroup[BodyGroup["Line"] = 2] = "Line"; - BodyGroup[BodyGroup["Point"] = 1] = "Point"; -})(BodyGroup || (exports.BodyGroup = BodyGroup = {})); - - -/***/ }), - -/***/ "./src/optimized.ts": -/*!**************************!*\ - !*** ./src/optimized.ts ***! - \**************************/ -/***/ ((__unused_webpack_module, exports) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.map = exports.filter = exports.every = exports.some = exports.forEach = void 0; -/** - * 40-90% faster than built-in Array.forEach function. - * - * basic benchmark: https://jsbench.me/urle772xdn - */ -const forEach = (array, callback) => { - for (let i = 0, l = array.length; i < l; i++) { - callback(array[i], i); - } -}; -exports.forEach = forEach; -/** - * 20-90% faster than built-in Array.some function. - * - * basic benchmark: https://jsbench.me/l0le7bnnsq - */ -const some = (array, callback) => { - for (let i = 0, l = array.length; i < l; i++) { - if (callback(array[i], i)) { - return true; + /** + * checks if body a is in body b + */ + function checkAInB(bodyA, bodyB) { + const check = + bodyA.typeGroup === model_1.BodyGroup.Circle + ? circleInFunctions + : polygonInFunctions; + return check[bodyB.typeGroup](bodyA, bodyB); } - } - return false; -}; -exports.some = some; -/** - * 20-40% faster than built-in Array.every function. - * - * basic benchmark: https://jsbench.me/unle7da29v - */ -const every = (array, callback) => { - for (let i = 0, l = array.length; i < l; i++) { - if (!callback(array[i], i)) { - return false; + /** + * clone sat vector points array into vector points array + */ + function clonePointsArray(points) { + return (0, optimized_1.map)(points, ({ x, y }) => ({ x, y })); } - } - return true; -}; -exports.every = every; -/** - * 20-60% faster than built-in Array.filter function. - * - * basic benchmark: https://jsbench.me/o1le77ev4l - */ -const filter = (array, callback) => { - const output = []; - for (let i = 0, l = array.length; i < l; i++) { - const item = array[i]; - if (callback(item, i)) { - output.push(item); + /** + * change format from SAT.js to poly-decomp + */ + function mapVectorToArray({ x, y } = { x: 0, y: 0 }) { + return [x, y]; } - } - return output; -}; -exports.filter = filter; -/** - * 20-70% faster than built-in Array.map - * - * basic benchmark: https://jsbench.me/oyle77vbpc - */ -const map = (array, callback) => { - const l = array.length; - const output = new Array(l); - for (let i = 0; i < l; i++) { - output[i] = callback(array[i], i); - } - return output; -}; -exports.map = map; - - -/***/ }), - -/***/ "./src/system.ts": -/*!***********************!*\ - !*** ./src/system.ts ***! - \***********************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.System = void 0; -const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); -const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts"); -const intersect_1 = __webpack_require__(/*! ./intersect */ "./src/intersect.ts"); -const optimized_1 = __webpack_require__(/*! ./optimized */ "./src/optimized.ts"); -const base_system_1 = __webpack_require__(/*! ./base-system */ "./src/base-system.ts"); -const line_1 = __webpack_require__(/*! ./bodies/line */ "./src/bodies/line.ts"); -/** - * collision system - */ -class System extends base_system_1.BaseSystem { - constructor() { - super(...arguments); /** - * the last collision result + * change format from poly-decomp to SAT.js */ - this.response = new model_1.Response(); - } - /** - * re-insert body into collision tree and update its bbox - * every body can be part of only one system - */ - insert(body) { - const insertResult = super.insert(body); - // set system for later body.system.updateBody(body) - body.system = this; - return insertResult; - } - /** - * separate (move away) bodies - */ - separate() { - (0, optimized_1.forEach)(this.all(), (body) => { - this.separateBody(body); - }); - } - /** - * separate (move away) 1 body - */ - separateBody(body) { - if (body.isStatic || body.isTrigger) { - return; + function mapArrayToVector([x, y] = [0, 0]) { + return { x, y }; } - const offsets = { x: 0, y: 0 }; - const addOffsets = ({ overlapV: { x, y } }) => { - offsets.x += x; - offsets.y += y; - }; - this.checkOne(body, addOffsets); - if (offsets.x || offsets.y) { - body.setPosition(body.x - offsets.x, body.y - offsets.y); + /** + * given 2 bodies calculate vector of bounce assuming equal mass and they are circles + */ + function getBounceDirection(body, collider) { + const v2 = new sat_1.Vector(collider.x - body.x, collider.y - body.y); + const v1 = new sat_1.Vector(body.x - collider.x, body.y - collider.y); + const len = v1.dot(v2.normalize()) * 2; + return new sat_1.Vector( + v2.x * len - v1.x, + v2.y * len - v1.y, + ).normalize(); } - } - /** - * check one body collisions with callback - */ - checkOne(body, callback = utils_1.returnTrue, response = this.response) { - // no need to check static body collision - if (body.isStatic) { - return false; + /** + * returns correct sat.js testing function based on body types + */ + function getSATTest(bodyA, bodyB) { + const check = + bodyA.typeGroup === model_1.BodyGroup.Circle + ? circleSATFunctions + : polygonSATFunctions; + return check[bodyB.typeGroup]; } - const bodies = this.search(body); - const checkCollision = (candidate) => { - if (candidate !== body && - this.checkCollision(body, candidate, response)) { - return callback(response); + /** + * draws dashed line on canvas context + */ + function dashLineTo( + context, + fromX, + fromY, + toX, + toY, + dash = 2, + gap = 4, + ) { + const xDiff = toX - fromX; + const yDiff = toY - fromY; + const arc = Math.atan2(yDiff, xDiff); + const offsetX = Math.cos(arc); + const offsetY = Math.sin(arc); + let posX = fromX; + let posY = fromY; + let dist = Math.hypot(xDiff, yDiff); + while (dist > 0) { + const step = Math.min(dist, dash); + context.moveTo(posX, posY); + context.lineTo(posX + offsetX * step, posY + offsetY * step); + posX += offsetX * (dash + gap); + posY += offsetY * (dash + gap); + dist -= dash + gap; + } + } + /** + * draw polygon + */ + function drawPolygon(context, { pos, calcPoints }, isTrigger = false) { + const lastPoint = calcPoints[calcPoints.length - 1]; + const fromX = pos.x + lastPoint.x; + const fromY = pos.y + lastPoint.y; + if (calcPoints.length === 1) { + context.arc(fromX, fromY, 1, 0, Math.PI * 2); + } else { + context.moveTo(fromX, fromY); + } + (0, optimized_1.forEach)(calcPoints, (point, index) => { + const toX = pos.x + point.x; + const toY = pos.y + point.y; + if (isTrigger) { + const prev = calcPoints[index - 1] || lastPoint; + dashLineTo(context, pos.x + prev.x, pos.y + prev.y, toX, toY); + } else { + context.lineTo(toX, toY); } - }; - return (0, optimized_1.some)(bodies, checkCollision); - } - /** - * check all bodies collisions in area with callback - */ - checkArea(area, callback = utils_1.returnTrue, response = this.response) { - const checkOne = (body) => { - return this.checkOne(body, callback, response); - }; - return (0, optimized_1.some)(this.search(area), checkOne); - } - /** - * check all bodies collisions with callback - */ - checkAll(callback = utils_1.returnTrue, response = this.response) { - const checkOne = (body) => { - return this.checkOne(body, callback, response); - }; - return (0, optimized_1.some)(this.all(), checkOne); - } - /** - * check do 2 objects collide - */ - checkCollision(bodyA, bodyB, response = this.response) { - const { bbox: bboxA } = bodyA; - const { bbox: bboxB } = bodyB; - // assess the bodies real aabb without padding - if (!(0, utils_1.canInteract)(bodyA, bodyB) || - !bboxA || - !bboxB || - (0, utils_1.notIntersectAABB)(bboxA, bboxB)) { - return false; + }); } - const sat = (0, utils_1.getSATTest)(bodyA, bodyB); - // 99% of cases - if (bodyA.isConvex && bodyB.isConvex) { - // always first clear response - response.clear(); - return sat(bodyA, bodyB, response); + /** + * draw body bounding body box + */ + function drawBVH(context, body) { + drawPolygon(context, { + pos: { x: body.minX, y: body.minY }, + calcPoints: createBox(body.maxX - body.minX, body.maxY - body.minY), + }); } - // more complex (non convex) cases - const convexBodiesA = (0, intersect_1.ensureConvex)(bodyA); - const convexBodiesB = (0, intersect_1.ensureConvex)(bodyB); - let overlapX = 0; - let overlapY = 0; - let collided = false; - (0, optimized_1.forEach)(convexBodiesA, convexBodyA => { - (0, optimized_1.forEach)(convexBodiesB, convexBodyB => { - // always first clear response - response.clear(); - if (sat(convexBodyA, convexBodyB, response)) { - collided = true; - overlapX += response.overlapV.x; - overlapY += response.overlapV.y; - } - }); - }); - if (collided) { - const vector = new model_1.SATVector(overlapX, overlapY); - response.a = bodyA; - response.b = bodyB; - response.overlapV.x = overlapX; - response.overlapV.y = overlapY; - response.overlapN = vector.normalize(); - response.overlap = vector.len(); - response.aInB = (0, utils_1.checkAInB)(bodyA, bodyB); - response.bInA = (0, utils_1.checkAInB)(bodyB, bodyA); + /** + * clone response object returning new response with previous ones values + */ + function cloneResponse(response) { + const clone = new sat_1.Response(); + const { a, b, overlap, overlapN, overlapV, aInB, bInA } = response; + clone.a = a; + clone.b = b; + clone.overlap = overlap; + clone.overlapN = overlapN.clone(); + clone.overlapV = overlapV.clone(); + clone.aInB = aInB; + clone.bInA = bInA; + return clone; } - return collided; - } - /** - * raycast to get collider of ray from start to end - */ - raycast(start, end, allow = utils_1.returnTrue) { - let minDistance = Infinity; - let result = null; - if (!this.ray) { - this.ray = new line_1.Line(start, end, { isTrigger: true }); + /** + * dummy fn used as default, for optimization + */ + function returnTrue() { + return true; } - else { - this.ray.start = start; - this.ray.end = end; + /** + * for groups + */ + function getGroup(group) { + return Math.max(0, Math.min(group, 0x7fffffff)); } - this.insert(this.ray); - this.checkOne(this.ray, ({ b: body }) => { - if (!allow(body, this.ray)) { - return false; - } - const points = body.typeGroup === model_1.BodyGroup.Circle - ? (0, intersect_1.intersectLineCircle)(this.ray, body) - : (0, intersect_1.intersectLinePolygon)(this.ray, body); - (0, optimized_1.forEach)(points, (point) => { - const pointDistance = (0, utils_1.distance)(start, point); - if (pointDistance < minDistance) { - minDistance = pointDistance; - result = { point, body }; - } - }); - }); - this.remove(this.ray); - return result; - } -} -exports.System = System; - - -/***/ }), - -/***/ "./src/utils.ts": -/*!**********************!*\ - !*** ./src/utils.ts ***! - \**********************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.RAD2DEG = exports.DEG2RAD = void 0; -exports.deg2rad = deg2rad; -exports.rad2deg = rad2deg; -exports.createEllipse = createEllipse; -exports.createBox = createBox; -exports.ensureVectorPoint = ensureVectorPoint; -exports.ensurePolygonPoints = ensurePolygonPoints; -exports.distance = distance; -exports.clockwise = clockwise; -exports.extendBody = extendBody; -exports.bodyMoved = bodyMoved; -exports.notIntersectAABB = notIntersectAABB; -exports.intersectAABB = intersectAABB; -exports.canInteract = canInteract; -exports.checkAInB = checkAInB; -exports.clonePointsArray = clonePointsArray; -exports.mapVectorToArray = mapVectorToArray; -exports.mapArrayToVector = mapArrayToVector; -exports.getBounceDirection = getBounceDirection; -exports.getSATTest = getSATTest; -exports.dashLineTo = dashLineTo; -exports.drawPolygon = drawPolygon; -exports.drawBVH = drawBVH; -exports.cloneResponse = cloneResponse; -exports.returnTrue = returnTrue; -exports.getGroup = getGroup; -exports.bin2dec = bin2dec; -exports.ensureNumber = ensureNumber; -exports.groupBits = groupBits; -exports.move = move; -const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -const intersect_1 = __webpack_require__(/*! ./intersect */ "./src/intersect.ts"); -const optimized_1 = __webpack_require__(/*! ./optimized */ "./src/optimized.ts"); -/* helpers for faster getSATTest() and checkAInB() */ -const testMap = { - satCircleCircle: sat_1.testCircleCircle, - satCirclePolygon: sat_1.testCirclePolygon, - satPolygonCircle: sat_1.testPolygonCircle, - satPolygonPolygon: sat_1.testPolygonPolygon, - inCircleCircle: intersect_1.circleInCircle, - inCirclePolygon: intersect_1.circleInPolygon, - inPolygonCircle: intersect_1.polygonInCircle, - inPolygonPolygon: intersect_1.polygonInPolygon -}; -function createMap(bodyType, testType) { - return Object.values(model_1.BodyType).reduce((result, type) => (Object.assign(Object.assign({}, result), { [type]: type === model_1.BodyType.Circle - ? testMap[`${testType}${bodyType}Circle`] - : testMap[`${testType}${bodyType}Polygon`] })), {}); -} -const circleSATFunctions = createMap(model_1.BodyType.Circle, "sat"); -const circleInFunctions = createMap(model_1.BodyType.Circle, "in"); -const polygonSATFunctions = createMap(model_1.BodyType.Polygon, "sat"); -const polygonInFunctions = createMap(model_1.BodyType.Polygon, "in"); -exports.DEG2RAD = Math.PI / 180; -exports.RAD2DEG = 180 / Math.PI; -/** - * convert from degrees to radians - */ -function deg2rad(degrees) { - return degrees * exports.DEG2RAD; -} -/** - * convert from radians to degrees - */ -function rad2deg(radians) { - return radians * exports.RAD2DEG; -} -/** - * creates ellipse-shaped polygon based on params - */ -function createEllipse(radiusX, radiusY = radiusX, step = 1) { - const steps = Math.PI * Math.hypot(radiusX, radiusY) * 2; - const length = Math.max(8, Math.ceil(steps / Math.max(1, step))); - const ellipse = []; - for (let index = 0; index < length; index++) { - const value = (index / length) * 2 * Math.PI; - const x = Math.cos(value) * radiusX; - const y = Math.sin(value) * radiusY; - ellipse.push(new sat_1.Vector(x, y)); - } - return ellipse; -} -/** - * creates box shaped polygon points - */ -function createBox(width, height) { - return [ - new sat_1.Vector(0, 0), - new sat_1.Vector(width, 0), - new sat_1.Vector(width, height), - new sat_1.Vector(0, height) - ]; -} -/** - * ensure SATVector type point result - */ -function ensureVectorPoint(point = {}) { - return point instanceof sat_1.Vector - ? point - : new sat_1.Vector(point.x || 0, point.y || 0); -} -/** - * ensure Vector points (for polygon) in counter-clockwise order - */ -function ensurePolygonPoints(points = []) { - const polygonPoints = (0, optimized_1.map)(points, ensureVectorPoint); - return clockwise(polygonPoints) ? polygonPoints.reverse() : polygonPoints; -} -/** - * get distance between two Vector points - */ -function distance(bodyA, bodyB) { - const xDiff = bodyA.x - bodyB.x; - const yDiff = bodyA.y - bodyB.y; - return Math.hypot(xDiff, yDiff); -} -/** - * check [is clockwise] direction of polygon - */ -function clockwise(points) { - const length = points.length; - let sum = 0; - (0, optimized_1.forEach)(points, (v1, index) => { - const v2 = points[(index + 1) % length]; - sum += (v2.x - v1.x) * (v2.y + v1.y); - }); - return sum > 0; -} -/** - * used for all types of bodies in constructor - */ -function extendBody(body, options = {}) { - body.isStatic = !!options.isStatic; - body.isTrigger = !!options.isTrigger; - body.padding = options.padding || 0; - body.group = typeof options.group === "number" ? options.group : 0x7FFFFFFF; - if (body.typeGroup !== model_1.BodyGroup.Circle) { - body.isCentered = options.isCentered || false; - } - body.setAngle(options.angle || 0); -} -/** - * check if body moved outside of its padding - */ -function bodyMoved(body) { - const { bbox, minX, minY, maxX, maxY } = body; - return (bbox.minX < minX || bbox.minY < minY || bbox.maxX > maxX || bbox.maxY > maxY); -} -/** - * returns true if two boxes not intersect - */ -function notIntersectAABB(bodyA, bodyB) { - return (bodyB.minX > bodyA.maxX || - bodyB.minY > bodyA.maxY || - bodyB.maxX < bodyA.minX || - bodyB.maxY < bodyA.minY); -} -/** - * checks if two boxes intersect - */ -function intersectAABB(bodyA, bodyB) { - return !notIntersectAABB(bodyA, bodyB); -} -/** - * checks if two bodies can interact (for collision filtering) - */ -function canInteract(bodyA, bodyB) { - return (((bodyA.group >> 16) & (bodyB.group & 0xFFFF) && - (bodyB.group >> 16) & (bodyA.group & 0xFFFF)) !== 0); -} -/** - * checks if body a is in body b - */ -function checkAInB(bodyA, bodyB) { - const check = bodyA.typeGroup === model_1.BodyGroup.Circle - ? circleInFunctions - : polygonInFunctions; - return check[bodyB.type](bodyA, bodyB); -} -/** - * clone sat vector points array into vector points array - */ -function clonePointsArray(points) { - return (0, optimized_1.map)(points, ({ x, y }) => ({ x, y })); -} -/** - * change format from SAT.js to poly-decomp - */ -function mapVectorToArray({ x, y } = { x: 0, y: 0 }) { - return [x, y]; -} -/** - * change format from poly-decomp to SAT.js - */ -function mapArrayToVector([x, y] = [0, 0]) { - return { x, y }; -} -/** - * given 2 bodies calculate vector of bounce assuming equal mass and they are circles - */ -function getBounceDirection(body, collider) { - const v2 = new sat_1.Vector(collider.x - body.x, collider.y - body.y); - const v1 = new sat_1.Vector(body.x - collider.x, body.y - collider.y); - const len = v1.dot(v2.normalize()) * 2; - return new sat_1.Vector(v2.x * len - v1.x, v2.y * len - v1.y).normalize(); -} -/** - * returns correct sat.js testing function based on body types - */ -function getSATTest(bodyA, bodyB) { - const check = bodyA.typeGroup === model_1.BodyGroup.Circle - ? circleSATFunctions - : polygonSATFunctions; - return check[bodyB.type]; -} -/** - * draws dashed line on canvas context - */ -function dashLineTo(context, fromX, fromY, toX, toY, dash = 2, gap = 4) { - const xDiff = toX - fromX; - const yDiff = toY - fromY; - const arc = Math.atan2(yDiff, xDiff); - const offsetX = Math.cos(arc); - const offsetY = Math.sin(arc); - let posX = fromX; - let posY = fromY; - let dist = Math.hypot(xDiff, yDiff); - while (dist > 0) { - const step = Math.min(dist, dash); - context.moveTo(posX, posY); - context.lineTo(posX + offsetX * step, posY + offsetY * step); - posX += offsetX * (dash + gap); - posY += offsetY * (dash + gap); - dist -= dash + gap; - } -} -/** - * draw polygon - */ -function drawPolygon(context, { pos, calcPoints }, isTrigger = false) { - const lastPoint = calcPoints[calcPoints.length - 1]; - const fromX = pos.x + lastPoint.x; - const fromY = pos.y + lastPoint.y; - if (calcPoints.length === 1) { - context.arc(fromX, fromY, 1, 0, Math.PI * 2); - } - else { - context.moveTo(fromX, fromY); - } - (0, optimized_1.forEach)(calcPoints, (point, index) => { - const toX = pos.x + point.x; - const toY = pos.y + point.y; - if (isTrigger) { - const prev = calcPoints[index - 1] || lastPoint; - dashLineTo(context, pos.x + prev.x, pos.y + prev.y, toX, toY); + /** + * binary string to decimal number + */ + function bin2dec(binary) { + return Number(`0b${binary}`.replace(/\s/g, "")); } - else { - context.lineTo(toX, toY); + /** + * helper for groupBits() + * + * @param input - number or binary string + */ + function ensureNumber(input) { + return typeof input === "number" ? input : bin2dec(input); + } + /** + * create group bits from category and mask + * + * @param category - category bits + * @param mask - mask bits (default: category) + */ + function groupBits(category, mask = category) { + return (ensureNumber(category) << 16) | ensureNumber(mask); + } + function move(body, speed = 1, updateNow = true) { + if (!speed) { + return; + } + const moveX = Math.cos(body.angle) * speed; + const moveY = Math.sin(body.angle) * speed; + body.setPosition(body.x + moveX, body.y + moveY, updateNow); } - }); -} -/** - * draw body bounding body box - */ -function drawBVH(context, body) { - drawPolygon(context, { - pos: { x: body.minX, y: body.minY }, - calcPoints: createBox(body.maxX - body.minX, body.maxY - body.minY) - }); -} -/** - * clone response object returning new response with previous ones values - */ -function cloneResponse(response) { - const clone = new sat_1.Response(); - const { a, b, overlap, overlapN, overlapV, aInB, bInA } = response; - clone.a = a; - clone.b = b; - clone.overlap = overlap; - clone.overlapN = overlapN.clone(); - clone.overlapV = overlapV.clone(); - clone.aInB = aInB; - clone.bInA = bInA; - return clone; -} -/** - * dummy fn used as default, for optimization - */ -function returnTrue() { - return true; -} -/** - * for groups - */ -function getGroup(group) { - return Math.max(0, Math.min(group, 0x7FFFFFFF)); -} -/** - * binary string to decimal number - */ -function bin2dec(binary) { - return Number(`0b${binary}`.replace(/\s/g, "")); -} -/** - * helper for groupBits() - * - * @param input - number or binary string - */ -function ensureNumber(input) { - return typeof input === "number" ? input : bin2dec(input); -} -/** - * create group bits from category and mask - * - * @param category - category bits - * @param mask - mask bits (default: category) - */ -function groupBits(category, mask = category) { - return (ensureNumber(category) << 16) | ensureNumber(mask); -} -function move(body, speed = 1, updateNow = true) { - if (!speed) { - return; - } - const moveX = Math.cos(body.angle) * speed; - const moveY = Math.sin(body.angle) * speed; - body.setPosition(body.x + moveX, body.y + moveY, updateNow); -} - -/***/ }), + /***/ + }, -/***/ "./src/demo/canvas.js": -/*!****************************!*\ + /***/ "./src/demo/canvas.js": + /*!****************************!*\ !*** ./src/demo/canvas.js ***! \****************************/ -/***/ ((module) => { + /***/ (module) => { + const width = window.innerWidth || 1024; + const height = window.innerHeight || 768; -const width = window.innerWidth || 1024; -const height = window.innerHeight || 768; + class TestCanvas { + constructor(test) { + this.test = test; -class TestCanvas { - constructor(test) { - this.test = test; - - this.element = document.createElement("div"); - this.element.id = "debug"; - this.element.innerHTML = `${this.test.legend} + this.element = document.createElement("div"); + this.element.id = "debug"; + this.element.innerHTML = `${this.test.legend}
`; - this.canvas = document.createElement("canvas"); - this.canvas.width = width; - this.canvas.height = height; - - this.context = this.canvas.getContext("2d"); - this.context.font = "24px Arial"; - this.test.context = this.context; + this.canvas = document.createElement("canvas"); + this.canvas.width = width; + this.canvas.height = height; - this.bvhCheckbox = this.element.querySelector("#bvh"); + this.context = this.canvas.getContext("2d"); + this.context.font = "24px Arial"; + this.test.context = this.context; - if (this.canvas instanceof Node) { - this.element.appendChild(this.canvas); - } + this.bvhCheckbox = this.element.querySelector("#bvh"); - this.fps = 0; - this.frame = 0; - this.started = Date.now(); + if (this.canvas instanceof Node) { + this.element.appendChild(this.canvas); + } - loop(this.update.bind(this)); - } + this.fps = 0; + this.frame = 0; + this.started = Date.now(); - update() { - this.frame++; + loop(this.update.bind(this)); + } - const timeDiff = Date.now() - this.started; - if (timeDiff >= 1000) { - this.fps = this.frame / (timeDiff / 1000); - this.frame = 0; - this.started = Date.now(); - } + update() { + this.frame++; - // Clear the canvas - this.context.fillStyle = "#000000"; - this.context.fillRect(0, 0, width, height); - - // Render the bodies - this.context.strokeStyle = "#FFFFFF"; - this.context.beginPath(); - this.test.physics.draw(this.context); - this.context.stroke(); - - // Render the BVH - if (this.bvhCheckbox.checked) { - this.context.strokeStyle = "#00FF00"; - this.context.beginPath(); - this.test.physics.drawBVH(this.context); - this.context.stroke(); - } + const timeDiff = Date.now() - this.started; + if (timeDiff >= 1000) { + this.fps = this.frame / (timeDiff / 1000); + this.frame = 0; + this.started = Date.now(); + } - // Render the FPS - this.context.fillStyle = "#FFCC00"; - this.context.fillText( - `FPS: ${this.fps ? this.fps.toFixed(0) : "?"}`, - 24, - 48, - ); + // Clear the canvas + this.context.fillStyle = "#000000"; + this.context.fillRect(0, 0, width, height); + + // Render the bodies + this.context.strokeStyle = "#FFFFFF"; + this.context.beginPath(); + this.test.physics.draw(this.context); + this.context.stroke(); + + // Render the BVH + if (this.bvhCheckbox.checked) { + this.context.strokeStyle = "#00FF00"; + this.context.beginPath(); + this.test.physics.drawBVH(this.context); + this.context.stroke(); + } - if (this.test.drawCallback) { - this.test.drawCallback(); - } - } -} + // Render the FPS + this.context.fillStyle = "#FFCC00"; + this.context.fillText( + `FPS: ${this.fps ? this.fps.toFixed(0) : "?"}`, + 24, + 48, + ); -function loop(callback) { - // interval for fps instead of setTimeout - // and ms = 1 which is lowest nonzero value - // for responsiveness of user input - setInterval(callback, 1); -} + if (this.test.drawCallback) { + this.test.drawCallback(); + } + } + } -module.exports.TestCanvas = TestCanvas; + function loop(callback) { + // interval for fps instead of setTimeout + // and ms = 1 which is lowest nonzero value + // for responsiveness of user input + setInterval(callback, 1); + } -module.exports.loop = loop; + module.exports.TestCanvas = TestCanvas; -module.exports.width = width; + module.exports.loop = loop; -module.exports.height = height; + module.exports.width = width; + module.exports.height = height; -/***/ }), + /***/ + }, -/***/ "./src/demo/stress.js": -/*!****************************!*\ + /***/ "./src/demo/stress.js": + /*!****************************!*\ !*** ./src/demo/stress.js ***! \****************************/ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { BodyGroup } = __webpack_require__(/*! ../model */ "./src/model.ts"); -const { System } = __webpack_require__(/*! ../system */ "./src/system.ts"); -const { getBounceDirection, groupBits } = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -const { width, height, loop } = __webpack_require__(/*! ./canvas */ "./src/demo/canvas.js"); -const seededRandom = (__webpack_require__(/*! random-seed */ "./node_modules/random-seed/index.js").create)("@Prozi").random; - -function random(min, max) { - return Math.floor(seededRandom() * max) + min; -} - -class Stress { - constructor(count = 2000) { - this.size = Math.sqrt((width * height) / (count * 50)); - - this.physics = new System(5); - this.bodies = []; - this.polygons = 0; - this.boxes = 0; - this.circles = 0; - this.ellipses = 0; - this.lines = 0; - this.lastVariant = 0; - this.count = count; - this.bounds = this.getBounds(); - this.enableFiltering = false; - - for (let i = 0; i < count; ++i) { - this.createShape(!random(0, 20)); - } + /***/ (module, __unused_webpack_exports, __webpack_require__) => { + const { BodyGroup } = __webpack_require__( + /*! ../model */ "./src/model.ts", + ); + const { System } = __webpack_require__( + /*! ../system */ "./src/system.ts", + ); + const { getBounceDirection, groupBits } = __webpack_require__( + /*! ../utils */ "./src/utils.ts", + ); + const { width, height, loop } = __webpack_require__( + /*! ./canvas */ "./src/demo/canvas.js", + ); + const seededRandom = __webpack_require__( + /*! random-seed */ "./node_modules/random-seed/index.js", + ).create("@Prozi").random; + + function random(min, max) { + return Math.floor(seededRandom() * max) + min; + } - this.legend = `
Total: ${count}
+ function getDefaultCount() { + return Math.floor(Math.min(2000, Math.hypot(width, height))); + } + + class Stress { + constructor(count = getDefaultCount()) { + this.size = Math.sqrt((width * height) / (count * 50)); + + this.physics = new System(5); + this.bodies = []; + this.polygons = 0; + this.boxes = 0; + this.circles = 0; + this.ellipses = 0; + this.lines = 0; + this.lastVariant = 0; + this.count = count; + this.bounds = this.getBounds(); + this.enableFiltering = false; + + for (let i = 0; i < count; ++i) { + this.createShape(!random(0, 20)); + } + + this.legend = `
Total: ${count}
Polygons: ${this.polygons}
Boxes: ${this.boxes}
Circles: ${this.circles}
@@ -4125,1311 +4680,1427 @@ class Stress { `; - this.lastTime = Date.now(); - this.updateBody = this.updateBody.bind(this); + this.lastTime = Date.now(); + this.updateBody = this.updateBody.bind(this); + + // observer #debug & add filtering checkbox event + const observer = new window.MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.id == "debug") { + document + .querySelector("#filtering") + .addEventListener("change", () => this.toggleFiltering()); + observer.disconnect(); + } + }); + }); + }); + observer.observe(document.querySelector("body"), { + subtree: false, + childList: true, + }); - // observer #debug & add filtering checkbox event - const observer = new window.MutationObserver((mutations) => { - mutations.forEach((mutation) => { - mutation.addedNodes.forEach((node) => { - if (node.id == "debug") { - document - .querySelector("#filtering") - .addEventListener("change", () => this.toggleFiltering()); - observer.disconnect(); + this.start = () => { + loop(this.update.bind(this)); + }; } - }); - }); - }); - observer.observe(document.querySelector("body"), { - subtree: false, - childList: true, - }); - - this.start = () => { - loop(this.update.bind(this)); - }; - } - - getBounds() { - return [ - this.physics.createBox({ x: 0, y: 0 }, width, 10, { - isStatic: true, - }), - this.physics.createBox({ x: width - 10, y: 0 }, 10, height, { - isStatic: true, - }), - this.physics.createBox({ x: 0, y: height - 10 }, width, 10, { - isStatic: true, - }), - this.physics.createBox({ x: 0, y: 0 }, 10, height, { - isStatic: true, - }), - ]; - } - - toggleFiltering() { - this.enableFiltering = !this.enableFiltering; - this.physics.clear(); - this.bodies.length = 0; - this.polygons = 0; - this.boxes = 0; - this.circles = 0; - this.ellipses = 0; - this.lines = 0; - this.lastVariant = 0; - this.bounds = this.getBounds(); - for (let i = 0; i < this.count; ++i) { - this.createShape(!random(0, 20)); - } - } - - update() { - const now = Date.now(); - this.timeScale = Math.min(1000, now - this.lastTime) / 60; - this.lastTime = now; - this.bodies.forEach(this.updateBody); - } - - updateBody(body) { - body.setAngle(body.angle + body.rotationSpeed * this.timeScale, false); - - if (seededRandom() < 0.05 * this.timeScale) { - body.targetScale.x = 0.5 + seededRandom(); - } - - if (seededRandom() < 0.05 * this.timeScale) { - body.targetScale.y = 0.5 + seededRandom(); - } - - if (Math.abs(body.targetScale.x - body.scaleX) > 0.01) { - const scaleX = - body.scaleX + - Math.sign(body.targetScale.x - body.scaleX) * 0.02 * this.timeScale; - const scaleY = - body.scaleY + - Math.sign(body.targetScale.y - body.scaleY) * 0.02 * this.timeScale; - - body.setScale(scaleX, scaleY, false); - } - - // as last step update position, and bounding box - body.setPosition( - body.x + body.directionX * this.timeScale, - body.y + body.directionY * this.timeScale, - ); - - // separate + bounce - this.bounceBody(body); - } - - bounceBody(body) { - const bounces = { x: 0, y: 0 }; - const addBounces = ({ overlapV: { x, y } }) => { - bounces.x += x; - bounces.y += y; - }; - - this.physics.checkOne(body, addBounces); - - if (bounces.x || bounces.y) { - const size = 0.5 * (body.scaleX + body.scaleY); - const bounce = getBounceDirection(body, { - x: body.x + bounces.x, - y: body.y + bounces.y, - }); - - bounce.scale(body.size).add({ - x: body.directionX * size, - y: body.directionY * size, - }); - - const { x, y } = bounce.normalize(); - - body.directionX = x; - body.directionY = y; - body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; - - body.setPosition(body.x - bounces.x, body.y - bounces.y); - } - } - - createShape(large) { - const minSize = this.size * 1.0 * (large ? seededRandom() + 1 : 1); - const maxSize = this.size * 1.25 * (large ? seededRandom() * 2 + 1 : 1); - const x = random(0, width); - const y = random(0, height); - const direction = (random(0, 360) * Math.PI) / 180; - const options = { - isCentered: true, - padding: (minSize + maxSize) * 0.2, - }; - - let body; - let variant = this.lastVariant++ % 5; - switch (variant) { - case 0: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Circle); - } - body = this.physics.createCircle( - { x, y }, - random(minSize, maxSize) / 2, - options, - ); + getBounds() { + return [ + this.physics.createBox({ x: 0, y: 0 }, width, 10, { + isStatic: true, + }), + this.physics.createBox({ x: width - 10, y: 0 }, 10, height, { + isStatic: true, + }), + this.physics.createBox({ x: 0, y: height - 10 }, width, 10, { + isStatic: true, + }), + this.physics.createBox({ x: 0, y: 0 }, 10, height, { + isStatic: true, + }), + ]; + } - ++this.circles; - break; + toggleFiltering() { + this.enableFiltering = !this.enableFiltering; + this.physics.clear(); + this.bodies.length = 0; + this.polygons = 0; + this.boxes = 0; + this.circles = 0; + this.ellipses = 0; + this.lines = 0; + this.lastVariant = 0; + this.bounds = this.getBounds(); + for (let i = 0; i < this.count; ++i) { + this.createShape(!random(0, 20)); + } + } - case 1: - const width = random(minSize, maxSize); - const height = random(minSize, maxSize); - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Ellipse); - console.log(); - } - body = this.physics.createEllipse({ x, y }, width, height, 2, options); + update() { + const now = Date.now(); + this.timeScale = Math.min(1000, now - this.lastTime) / 60; + this.lastTime = now; + this.bodies.forEach(this.updateBody); + } - ++this.ellipses; - break; + updateBody(body) { + body.setAngle( + body.angle + body.rotationSpeed * this.timeScale, + false, + ); - case 2: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Box); - } - body = this.physics.createBox( - { x, y }, - random(minSize, maxSize), - random(minSize, maxSize), - options, - ); + if (seededRandom() < 0.05 * this.timeScale) { + body.targetScale.x = 0.5 + seededRandom(); + } - ++this.boxes; - break; + if (seededRandom() < 0.05 * this.timeScale) { + body.targetScale.y = 0.5 + seededRandom(); + } - case 3: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Line); - } - body = this.physics.createLine( - { x, y }, - { - x: x + random(minSize, maxSize), - y: y + random(minSize, maxSize), - }, - options, - ); + if (Math.abs(body.targetScale.x - body.scaleX) > 0.01) { + const scaleX = + body.scaleX + + Math.sign(body.targetScale.x - body.scaleX) * + 0.02 * + this.timeScale; + const scaleY = + body.scaleY + + Math.sign(body.targetScale.y - body.scaleY) * + 0.02 * + this.timeScale; + + body.setScale(scaleX, scaleY, false); + } - ++this.lines; - break; + // as last step update position, and bounding box + body.setPosition( + body.x + body.directionX * this.timeScale, + body.y + body.directionY * this.timeScale, + ); - default: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Polygon); - } - body = this.physics.createPolygon( - { x, y }, - [ - { x: -random(minSize, maxSize), y: random(minSize, maxSize) }, - { x: random(minSize, maxSize), y: random(minSize, maxSize) }, - { x: random(minSize, maxSize), y: -random(minSize, maxSize) }, - { x: -random(minSize, maxSize), y: -random(minSize, maxSize) }, - ], - options, - ); + // separate + bounce + this.bounceBody(body); + } - ++this.polygons; - break; - } + bounceBody(body) { + const bounces = { x: 0, y: 0 }; + const addBounces = ({ overlapV: { x, y } }) => { + bounces.x += x; + bounces.y += y; + }; - // set initial rotation angle direction - body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; - body.setAngle((random(0, 360) * Math.PI) / 180); + this.physics.checkOne(body, addBounces); - body.targetScale = { x: 1, y: 1 }; - body.size = (minSize + maxSize) / 2; + if (bounces.x || bounces.y) { + const size = 0.5 * (body.scaleX + body.scaleY); + const bounce = getBounceDirection(body, { + x: body.x + bounces.x, + y: body.y + bounces.y, + }); - body.directionX = Math.cos(direction); - body.directionY = Math.sin(direction); + bounce.scale(body.size).add({ + x: body.directionX * size, + y: body.directionY * size, + }); - this.bodies.push(body); - } -} + const { x, y } = bounce.normalize(); -module.exports = Stress; + body.directionX = x; + body.directionY = y; + body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; + body.setPosition(body.x - bounces.x, body.y - bounces.y); + } + } -/***/ }), + createShape(large) { + const minSize = this.size * 1.0 * (large ? seededRandom() + 1 : 1); + const maxSize = + this.size * 1.25 * (large ? seededRandom() * 2 + 1 : 1); + const x = random(0, width); + const y = random(0, height); + const direction = (random(0, 360) * Math.PI) / 180; + const options = { + isCentered: true, + padding: (minSize + maxSize) * 0.2, + }; + + let body; + let variant = this.lastVariant++ % 5; + + switch (variant) { + case 0: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Circle); + } + body = this.physics.createCircle( + { x, y }, + random(minSize, maxSize) / 2, + options, + ); + + ++this.circles; + break; + + case 1: + const width = random(minSize, maxSize); + const height = random(minSize, maxSize); + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Ellipse); + console.log(); + } + body = this.physics.createEllipse( + { x, y }, + width, + height, + 2, + options, + ); + + ++this.ellipses; + break; + + case 2: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Box); + } + body = this.physics.createBox( + { x, y }, + random(minSize, maxSize), + random(minSize, maxSize), + options, + ); + + ++this.boxes; + break; + + case 3: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Line); + } + body = this.physics.createLine( + { x, y }, + { + x: x + random(minSize, maxSize), + y: y + random(minSize, maxSize), + }, + options, + ); + + ++this.lines; + break; + + default: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Polygon); + } + body = this.physics.createPolygon( + { x, y }, + [ + { + x: -random(minSize, maxSize), + y: random(minSize, maxSize), + }, + { + x: random(minSize, maxSize), + y: random(minSize, maxSize), + }, + { + x: random(minSize, maxSize), + y: -random(minSize, maxSize), + }, + { + x: -random(minSize, maxSize), + y: -random(minSize, maxSize), + }, + ], + options, + ); + + ++this.polygons; + break; + } -/***/ "./src/demo/tank.js": -/*!**************************!*\ - !*** ./src/demo/tank.js ***! - \**************************/ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { BodyGroup } = __webpack_require__(/*! ../model */ "./src/model.ts"); -const { System } = __webpack_require__(/*! ../system */ "./src/system.ts"); -const { mapVectorToArray } = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -const { width, height, loop } = __webpack_require__(/*! ./canvas */ "./src/demo/canvas.js"); - -class Tank { - constructor() { - this.physics = new System(); - this.bodies = []; - this.player = this.createPlayer(400, 300); - - this.createPolygon( - 300, - 300, - [ - { x: -11.25, y: -6.76 }, - { x: -12.5, y: -6.76 }, - { x: -12.5, y: 6.75 }, - { x: -3.1, y: 6.75 }, - { x: -3.1, y: 0.41 }, - { x: -2.35, y: 0.41 }, - { x: -2.35, y: 6.75 }, - { x: 0.77, y: 6.75 }, - { x: 0.77, y: 7.5 }, - { x: -13.25, y: 7.5 }, - { x: -13.25, y: -7.51 }, - { x: -11.25, y: -7.51 }, - ] - .map(mapVectorToArray) - .map(([x, y]) => [x * 10, y * 10]), - ); + // set initial rotation angle direction + body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; + body.setAngle((random(0, 360) * Math.PI) / 180); - this.up = false; - this.down = false; - this.left = false; - this.right = false; + body.targetScale = { x: 1, y: 1 }; + body.size = (minSize + maxSize) / 2; - this.legend = `
W, S - Accelerate/Decelerate
-
A, D - Turn
`; + body.directionX = Math.cos(direction); + body.directionY = Math.sin(direction); - const updateKeys = ({ type, key }) => { - const keyDown = type === "keydown"; - const keyLowerCase = key.toLowerCase(); + this.bodies.push(body); + } + } - keyLowerCase === "w" && (this.up = keyDown); - keyLowerCase === "s" && (this.down = keyDown); - keyLowerCase === "a" && (this.left = keyDown); - keyLowerCase === "d" && (this.right = keyDown); - }; + module.exports = Stress; - document.addEventListener("keydown", updateKeys); - document.addEventListener("keyup", updateKeys); + /***/ + }, - if (this.canvas instanceof Node) { - this.element.appendChild(this.canvas); - } + /***/ "./src/demo/tank.js": + /*!**************************!*\ + !*** ./src/demo/tank.js ***! + \**************************/ + /***/ (module, __unused_webpack_exports, __webpack_require__) => { + const { BodyGroup } = __webpack_require__( + /*! ../model */ "./src/model.ts", + ); + const { System } = __webpack_require__( + /*! ../system */ "./src/system.ts", + ); + const { mapVectorToArray } = __webpack_require__( + /*! ../utils */ "./src/utils.ts", + ); + const { width, height, loop } = __webpack_require__( + /*! ./canvas */ "./src/demo/canvas.js", + ); - this.createMap(); - this.lastTime = Date.now(); + class Tank { + constructor() { + this.physics = new System(); + this.bodies = []; + this.player = this.createPlayer(400, 300); + + this.createPolygon( + 300, + 300, + [ + { x: -11.25, y: -6.76 }, + { x: -12.5, y: -6.76 }, + { x: -12.5, y: 6.75 }, + { x: -3.1, y: 6.75 }, + { x: -3.1, y: 0.41 }, + { x: -2.35, y: 0.41 }, + { x: -2.35, y: 6.75 }, + { x: 0.77, y: 6.75 }, + { x: 0.77, y: 7.5 }, + { x: -13.25, y: 7.5 }, + { x: -13.25, y: -7.51 }, + { x: -11.25, y: -7.51 }, + ] + .map(mapVectorToArray) + .map(([x, y]) => [x * 10, y * 10]), + ); + + this.up = false; + this.down = false; + this.left = false; + this.right = false; + + this.legend = `
W, S - Accelerate/Decelerate
+
A, D - Turn
`; - this.start = () => { - loop(this.update.bind(this)); - }; - } + const updateKeys = ({ type, key }) => { + const keyDown = type === "keydown"; + const keyLowerCase = key.toLowerCase(); - update() { - const now = Date.now(); - this.timeScale = Math.min(1000, now - this.lastTime) / 60; - this.lastTime = now; - this.handleInput(); - this.processGameLogic(); - this.handleCollisions(); - this.updateTurret(); - } + keyLowerCase === "w" && (this.up = keyDown); + keyLowerCase === "s" && (this.down = keyDown); + keyLowerCase === "a" && (this.left = keyDown); + keyLowerCase === "d" && (this.right = keyDown); + }; - handleInput() { - if (this.up) { - this.player.velocity += 0.2 * this.timeScale; - } + document.addEventListener("keydown", updateKeys); + document.addEventListener("keyup", updateKeys); - if (this.down) { - this.player.velocity -= 0.2 * this.timeScale; - } + if (this.canvas instanceof Node) { + this.element.appendChild(this.canvas); + } - if (this.left) { - this.player.setAngle(this.player.angle - 0.2 * this.timeScale); - } + this.createMap(); + this.lastTime = Date.now(); - if (this.right) { - this.player.setAngle(this.player.angle + 0.2 * this.timeScale); - } - } + this.start = () => { + loop(this.update.bind(this)); + }; + } - processGameLogic() { - const x = Math.cos(this.player.angle); - const y = Math.sin(this.player.angle); + update() { + const now = Date.now(); + this.timeScale = Math.min(1000, now - this.lastTime) / 60; + this.lastTime = now; + this.handleInput(); + this.processGameLogic(); + this.handleCollisions(); + this.updateTurret(); + } - if (this.player.velocity > 0) { - this.player.velocity = Math.max( - this.player.velocity - 0.1 * this.timeScale, - 0, - ); + handleInput() { + if (this.up) { + this.player.velocity += 0.2 * this.timeScale; + } - if (this.player.velocity > 2) { - this.player.velocity = 2; - } - } else if (this.player.velocity < 0) { - this.player.velocity = Math.min( - this.player.velocity + 0.1 * this.timeScale, - 0, - ); - - if (this.player.velocity < -2) { - this.player.velocity = -2; - } - } + if (this.down) { + this.player.velocity -= 0.2 * this.timeScale; + } - if (!Math.round(this.player.velocity * 100)) { - this.player.velocity = 0; - } + if (this.left) { + this.player.setAngle(this.player.angle - 0.2 * this.timeScale); + } - if (this.player.velocity) { - this.player.setPosition( - this.player.x + x * this.player.velocity, - this.player.y + y * this.player.velocity, - ); - } - } + if (this.right) { + this.player.setAngle(this.player.angle + 0.2 * this.timeScale); + } + } - handleCollisions() { - this.physics.checkAll(({ a, b, overlapV }) => { - if (a.isTrigger || b.isTrigger) { - return; - } + processGameLogic() { + const x = Math.cos(this.player.angle); + const y = Math.sin(this.player.angle); + + if (this.player.velocity > 0) { + this.player.velocity = Math.max( + this.player.velocity - 0.1 * this.timeScale, + 0, + ); + + if (this.player.velocity > 2) { + this.player.velocity = 2; + } + } else if (this.player.velocity < 0) { + this.player.velocity = Math.min( + this.player.velocity + 0.1 * this.timeScale, + 0, + ); + + if (this.player.velocity < -2) { + this.player.velocity = -2; + } + } - if (a.typeGroup === BodyGroup.Polygon || a === this.player) { - a.setPosition(a.pos.x - overlapV.x, a.pos.y - overlapV.y); - } + if (!Math.round(this.player.velocity * 100)) { + this.player.velocity = 0; + } - if (b.typeGroup === BodyGroup.Circle || b === this.player) { - b.setPosition(b.pos.x + overlapV.x, b.pos.y + overlapV.y); - } + if (this.player.velocity) { + this.player.setPosition( + this.player.x + x * this.player.velocity, + this.player.y + y * this.player.velocity, + ); + } + } - if (a === this.player) { - a.velocity *= 0.9; - } - }); - } + handleCollisions() { + this.physics.checkAll(({ a, b, overlapV }) => { + if (a.isTrigger || b.isTrigger) { + return; + } - updateTurret() { - this.playerTurret.setAngle(this.player.angle, false); - this.playerTurret.setPosition(this.player.x, this.player.y); + if (a.typeGroup === BodyGroup.Polygon || a === this.player) { + a.setPosition(a.pos.x - overlapV.x, a.pos.y - overlapV.y); + } - const hit = this.physics.raycast( - this.playerTurret.start, - this.playerTurret.end, - (test) => test !== this.player, - ); + if (b.typeGroup === BodyGroup.Circle || b === this.player) { + b.setPosition(b.pos.x + overlapV.x, b.pos.y + overlapV.y); + } - this.drawCallback = () => { - if (hit) { - this.context.strokeStyle = "#FF0000"; - this.context.beginPath(); - this.context.arc(hit.point.x, hit.point.y, 5, 0, 2 * Math.PI); - this.context.stroke(); - } - }; - } + if (a === this.player) { + a.velocity *= 0.9; + } + }); + } - createPlayer(x, y, size = 13) { - const player = - Math.random() < 0.5 - ? this.physics.createCircle( - { x: this.scaleX(x), y: this.scaleY(y) }, - this.scaleX(size / 2), - { isCentered: true }, - ) - : this.physics.createBox( - { x: this.scaleX(x - size / 2), y: this.scaleY(y - size / 2) }, - this.scaleX(size), - this.scaleX(size), - { isCentered: true }, - ); + updateTurret() { + this.playerTurret.setAngle(this.player.angle, false); + this.playerTurret.setPosition(this.player.x, this.player.y); + + const hit = this.physics.raycast( + this.playerTurret.start, + this.playerTurret.end, + (test) => test !== this.player, + ); + + this.drawCallback = () => { + if (hit) { + this.context.strokeStyle = "#FF0000"; + this.context.beginPath(); + this.context.arc(hit.point.x, hit.point.y, 5, 0, 2 * Math.PI); + this.context.stroke(); + } + }; + } - player.velocity = 0; - player.setOffset({ x: -this.scaleX(size / 2), y: 0 }); - player.setAngle(0.2); + createPlayer(x, y, size = 13) { + const player = + Math.random() < 0.5 + ? this.physics.createCircle( + { x: this.scaleX(x), y: this.scaleY(y) }, + this.scaleX(size / 2), + { isCentered: true }, + ) + : this.physics.createBox( + { + x: this.scaleX(x - size / 2), + y: this.scaleY(y - size / 2), + }, + this.scaleX(size), + this.scaleX(size), + { isCentered: true }, + ); + + player.velocity = 0; + player.setOffset({ x: -this.scaleX(size / 2), y: 0 }); + player.setAngle(0.2); + + this.physics.updateBody(player); + this.playerTurret = this.physics.createLine( + player, + { x: player.x + this.scaleX(20) + this.scaleY(20), y: player.y }, + { angle: 0.2, isTrigger: true }, + ); + + return player; + } - this.physics.updateBody(player); - this.playerTurret = this.physics.createLine( - player, - { x: player.x + this.scaleX(20) + this.scaleY(20), y: player.y }, - { angle: 0.2, isTrigger: true }, - ); + scaleX(x) { + return (x / 800) * width; + } - return player; - } + scaleY(y) { + return (y / 600) * height; + } - scaleX(x) { - return (x / 800) * width; - } + createCircle(x, y, radius) { + this.physics.createCircle( + { x: this.scaleX(x), y: this.scaleY(y) }, + this.scaleX(radius), + ); + } - scaleY(y) { - return (y / 600) * height; - } + createEllipse(x, y, radiusX, radiusY, step, angle) { + this.physics.createEllipse( + { x: this.scaleX(x), y: this.scaleY(y) }, + this.scaleX(radiusX), + this.scaleY(radiusY), + step, + { angle }, + ); + } - createCircle(x, y, radius) { - this.physics.createCircle( - { x: this.scaleX(x), y: this.scaleY(y) }, - this.scaleX(radius), - ); - } + createPolygon(x, y, points, angle) { + const scaledPoints = points.map(([pointX, pointY]) => ({ + x: this.scaleX(pointX), + y: this.scaleY(pointY), + })); + + return this.physics.createPolygon( + { x: this.scaleX(x), y: this.scaleY(y) }, + scaledPoints, + { angle, isStatic: true }, + ); + } - createEllipse(x, y, radiusX, radiusY, step, angle) { - this.physics.createEllipse( - { x: this.scaleX(x), y: this.scaleY(y) }, - this.scaleX(radiusX), - this.scaleY(radiusY), - step, - { angle }, - ); - } + createMap(width = 800, height = 600) { + // World bounds + // World bounds + this.createPolygon(0, 0, [ + [0, 0], + [width, 0], + ]); + this.createPolygon(0, 0, [ + [width, 0], + [width, height], + ]); + this.createPolygon(0, 0, [ + [width, height], + [0, height], + ]); + this.createPolygon(0, 0, [ + [0, height], + [0, 0], + ]); + + // Factory + this.createPolygon( + 100, + 100, + [ + [-50, -50], + [50, -50], + [50, 50], + [-50, 50], + ], + 0.4, + ); + this.createPolygon( + 190, + 105, + [ + [-20, -20], + [20, -20], + [20, 20], + [-20, 20], + ], + 0.4, + ); + this.createCircle(170, 140, 6); + this.createCircle(185, 155, 6); + this.createCircle(165, 165, 6); + this.createCircle(145, 165, 6); + + // Airstrip + this.createPolygon( + 230, + 50, + [ + [-150, -30], + [150, -30], + [150, 30], + [-150, 30], + ], + 0.4, + ); + + // HQ + this.createPolygon( + 100, + 500, + [ + [-40, -50], + [40, -50], + [50, 50], + [-50, 50], + ], + 0.2, + ); + this.createCircle(180, 490, 12); + this.createCircle(175, 540, 12); + + // Barracks + this.createPolygon( + 400, + 500, + [ + [-60, -20], + [60, -20], + [60, 20], + [-60, 20], + ], + 1.7, + ); + this.createPolygon( + 350, + 494, + [ + [-60, -20], + [60, -20], + [60, 20], + [-60, 20], + ], + 1.7, + ); + + // Mountains + this.createPolygon(750, 0, [ + [0, 0], + [-20, 100], + ]); + this.createPolygon(750, 0, [ + [-20, 100], + [30, 250], + ]); + this.createPolygon(750, 0, [ + [30, 250], + [20, 300], + ]); + this.createPolygon(750, 0, [ + [20, 300], + [-50, 320], + ]); + this.createPolygon(750, 0, [ + [-50, 320], + [-90, 500], + ]); + this.createPolygon(750, 0, [ + [-90, 500], + [-200, 600], + ]); + + // Lake + this.createEllipse(530, 130, 80, 70, 10, -0.2); + } + } - createPolygon(x, y, points, angle) { - const scaledPoints = points.map(([pointX, pointY]) => ({ - x: this.scaleX(pointX), - y: this.scaleY(pointY), - })); + module.exports = Tank; - return this.physics.createPolygon( - { x: this.scaleX(x), y: this.scaleY(y) }, - scaledPoints, - { angle, isStatic: true }, - ); - } + /***/ + }, - createMap(width = 800, height = 600) { - // World bounds - // World bounds - this.createPolygon(0, 0, [ - [0, 0], - [width, 0], - ]); - this.createPolygon(0, 0, [ - [width, 0], - [width, height], - ]); - this.createPolygon(0, 0, [ - [width, height], - [0, height], - ]); - this.createPolygon(0, 0, [ - [0, height], - [0, 0], - ]); - - // Factory - this.createPolygon( - 100, - 100, - [ - [-50, -50], - [50, -50], - [50, 50], - [-50, 50], - ], - 0.4, - ); - this.createPolygon( - 190, - 105, - [ - [-20, -20], - [20, -20], - [20, 20], - [-20, 20], - ], - 0.4, - ); - this.createCircle(170, 140, 6); - this.createCircle(185, 155, 6); - this.createCircle(165, 165, 6); - this.createCircle(145, 165, 6); - - // Airstrip - this.createPolygon( - 230, - 50, - [ - [-150, -30], - [150, -30], - [150, 30], - [-150, 30], - ], - 0.4, - ); + /***/ "./src/external/quickselect.js": + /*!*************************************!*\ + !*** ./src/external/quickselect.js ***! + \*************************************/ + /***/ ( + __unused_webpack_module, + __webpack_exports__, + __webpack_require__, + ) => { + "use strict"; + __webpack_require__.r(__webpack_exports__); + /* harmony export */ __webpack_require__.d(__webpack_exports__, { + /* harmony export */ default: () => /* binding */ quickselect, + /* harmony export */ + }); - // HQ - this.createPolygon( - 100, - 500, - [ - [-40, -50], - [40, -50], - [50, 50], - [-50, 50], - ], - 0.2, - ); - this.createCircle(180, 490, 12); - this.createCircle(175, 540, 12); - - // Barracks - this.createPolygon( - 400, - 500, - [ - [-60, -20], - [60, -20], - [60, 20], - [-60, 20], - ], - 1.7, - ); - this.createPolygon( - 350, - 494, - [ - [-60, -20], - [60, -20], - [60, 20], - [-60, 20], - ], - 1.7, - ); + /** + * Rearranges items so that all items in the [left, k] are the smallest. + * The k-th element will have the (k - left + 1)-th smallest value in [left, right]. + * + * @template T + * @param {T[]} arr the array to partially sort (in place) + * @param {number} k middle index for partial sorting (as defined above) + * @param {number} [left=0] left index of the range to sort + * @param {number} [right=arr.length-1] right index + * @param {(a: T, b: T) => number} [compare = (a, b) => a - b] compare function + */ + function quickselect( + arr, + k, + left = 0, + right = arr.length - 1, + compare = defaultCompare, + ) { + while (right > left) { + if (right - left > 600) { + const n = right - left + 1; + const m = k - left + 1; + const z = Math.log(n); + const s = 0.5 * Math.exp((2 * z) / 3); + const sd = + 0.5 * + Math.sqrt((z * s * (n - s)) / n) * + (m - n / 2 < 0 ? -1 : 1); + const newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd)); + const newRight = Math.min( + right, + Math.floor(k + ((n - m) * s) / n + sd), + ); + quickselect(arr, k, newLeft, newRight, compare); + } - // Mountains - this.createPolygon(750, 0, [ - [0, 0], - [-20, 100], - ]); - this.createPolygon(750, 0, [ - [-20, 100], - [30, 250], - ]); - this.createPolygon(750, 0, [ - [30, 250], - [20, 300], - ]); - this.createPolygon(750, 0, [ - [20, 300], - [-50, 320], - ]); - this.createPolygon(750, 0, [ - [-50, 320], - [-90, 500], - ]); - this.createPolygon(750, 0, [ - [-90, 500], - [-200, 600], - ]); - - // Lake - this.createEllipse(530, 130, 80, 70, 10, -0.2); - } -} + const t = arr[k]; + let i = left; + /** @type {number} */ + let j = right; -module.exports = Tank; + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } -/***/ }), + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } -/***/ "./src/external/quickselect.js": -/*!*************************************!*\ - !*** ./src/external/quickselect.js ***! - \*************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "default": () => (/* binding */ quickselect) -/* harmony export */ }); - -/** - * Rearranges items so that all items in the [left, k] are the smallest. - * The k-th element will have the (k - left + 1)-th smallest value in [left, right]. - * - * @template T - * @param {T[]} arr the array to partially sort (in place) - * @param {number} k middle index for partial sorting (as defined above) - * @param {number} [left=0] left index of the range to sort - * @param {number} [right=arr.length-1] right index - * @param {(a: T, b: T) => number} [compare = (a, b) => a - b] compare function - */ -function quickselect(arr, k, left = 0, right = arr.length - 1, compare = defaultCompare) { - - while (right > left) { - if (right - left > 600) { - const n = right - left + 1; - const m = k - left + 1; - const z = Math.log(n); - const s = 0.5 * Math.exp(2 * z / 3); - const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); - const newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); - const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); - quickselect(arr, k, newLeft, newRight, compare); + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } } - const t = arr[k]; - let i = left; - /** @type {number} */ - let j = right; - - swap(arr, left, k); - if (compare(arr[right], t) > 0) swap(arr, left, right); - - while (i < j) { - swap(arr, i, j); - i++; - j--; - while (compare(arr[i], t) < 0) i++; - while (compare(arr[j], t) > 0) j--; + /** + * @template T + * @param {T[]} arr + * @param {number} i + * @param {number} j + */ + function swap(arr, i, j) { + const tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; } - if (compare(arr[left], t) === 0) swap(arr, left, j); - else { - j++; - swap(arr, j, right); + /** + * @template T + * @param {T} a + * @param {T} b + * @returns {number} + */ + function defaultCompare(a, b) { + return a < b ? -1 : a > b ? 1 : 0; } - if (j <= k) left = j + 1; - if (k <= j) right = j - 1; - } -} - -/** - * @template T - * @param {T[]} arr - * @param {number} i - * @param {number} j - */ -function swap(arr, i, j) { - const tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; -} - -/** - * @template T - * @param {T} a - * @param {T} b - * @returns {number} - */ -function defaultCompare(a, b) { - return a < b ? -1 : a > b ? 1 : 0; -} - - -/***/ }), - -/***/ "./src/external/rbush.js": -/*!*******************************!*\ + /***/ + }, + + /***/ "./src/external/rbush.js": + /*!*******************************!*\ !*** ./src/external/rbush.js ***! \*******************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "default": () => (/* binding */ RBush) -/* harmony export */ }); -/* harmony import */ var _quickselect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./quickselect */ "./src/external/quickselect.js"); - - -class RBush { - constructor(maxEntries = 9) { - // max entries in a node is 9 by default; min node fill is 40% for best performance - this._maxEntries = Math.max(4, maxEntries); - this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); - this.clear(); - } + /***/ ( + __unused_webpack_module, + __webpack_exports__, + __webpack_require__, + ) => { + "use strict"; + __webpack_require__.r(__webpack_exports__); + /* harmony export */ __webpack_require__.d(__webpack_exports__, { + /* harmony export */ default: () => /* binding */ RBush, + /* harmony export */ + }); + /* harmony import */ var _quickselect__WEBPACK_IMPORTED_MODULE_0__ = + __webpack_require__( + /*! ./quickselect */ "./src/external/quickselect.js", + ); - all() { - return this._all(this.data, []); - } + class RBush { + constructor(maxEntries = 9) { + // max entries in a node is 9 by default; min node fill is 40% for best performance + this._maxEntries = Math.max(4, maxEntries); + this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); + this.clear(); + } - search(bbox) { - let node = this.data; - const result = []; + all() { + return this._all(this.data, []); + } + + search(bbox) { + let node = this.data; + const result = []; - if (!intersects(bbox, node)) return result; + if (!intersects(bbox, node)) return result; - const toBBox = this.toBBox; - const nodesToSearch = []; + const toBBox = this.toBBox; + const nodesToSearch = []; - while (node) { - for (let i = 0; i < node.children.length; i++) { + while (node) { + for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; const childBBox = node.leaf ? toBBox(child) : child; if (intersects(bbox, childBBox)) { - if (node.leaf) result.push(child); - else if (contains(bbox, childBBox)) this._all(child, result); - else nodesToSearch.push(child); + if (node.leaf) result.push(child); + else if (contains(bbox, childBBox)) this._all(child, result); + else nodesToSearch.push(child); } + } + node = nodesToSearch.pop(); } - node = nodesToSearch.pop(); - } - return result; - } + return result; + } - collides(bbox) { - let node = this.data; + collides(bbox) { + let node = this.data; - if (!intersects(bbox, node)) return false; + if (!intersects(bbox, node)) return false; - const nodesToSearch = []; - while (node) { - for (let i = 0; i < node.children.length; i++) { + const nodesToSearch = []; + while (node) { + for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; const childBBox = node.leaf ? this.toBBox(child) : child; if (intersects(bbox, childBBox)) { - if (node.leaf || contains(bbox, childBBox)) return true; - nodesToSearch.push(child); + if (node.leaf || contains(bbox, childBBox)) return true; + nodesToSearch.push(child); } + } + node = nodesToSearch.pop(); } - node = nodesToSearch.pop(); - } - return false; - } + return false; + } - load(data) { - if (!(data && data.length)) return this; + load(data) { + if (!(data && data.length)) return this; - if (data.length < this._minEntries) { - for (let i = 0; i < data.length; i++) { + if (data.length < this._minEntries) { + for (let i = 0; i < data.length; i++) { this.insert(data[i]); + } + return this; } - return this; - } - - // recursively build the tree with the given data from scratch using OMT algorithm - let node = this._build(data.slice(), 0, data.length - 1, 0); - if (!this.data.children.length) { - // save as is if tree is empty - this.data = node; - - } else if (this.data.height === node.height) { - // split root if trees have the same height - this._splitRoot(this.data, node); - - } else { - if (this.data.height < node.height) { + // recursively build the tree with the given data from scratch using OMT algorithm + let node = this._build(data.slice(), 0, data.length - 1, 0); + + if (!this.data.children.length) { + // save as is if tree is empty + this.data = node; + } else if (this.data.height === node.height) { + // split root if trees have the same height + this._splitRoot(this.data, node); + } else { + if (this.data.height < node.height) { // swap trees if inserted one is bigger const tmpNode = this.data; this.data = node; node = tmpNode; - } - - // insert the small tree into the large tree at appropriate level - this._insert(node, this.data.height - node.height - 1, true); - } + } - return this; - } + // insert the small tree into the large tree at appropriate level + this._insert(node, this.data.height - node.height - 1, true); + } - insert(item) { - if (item) this._insert(item, this.data.height - 1); - return this; - } + return this; + } - clear() { - this.data = createNode([]); - return this; - } + insert(item) { + if (item) this._insert(item, this.data.height - 1); + return this; + } - remove(item, equalsFn) { - if (!item) return this; + clear() { + this.data = createNode([]); + return this; + } - let node = this.data; - const bbox = this.toBBox(item); - const path = []; - const indexes = []; - let i, parent, goingUp; + remove(item, equalsFn) { + if (!item) return this; - // depth-first iterative tree traversal - while (node || path.length) { + let node = this.data; + const bbox = this.toBBox(item); + const path = []; + const indexes = []; + let i, parent, goingUp; - if (!node) { // go up + // depth-first iterative tree traversal + while (node || path.length) { + if (!node) { + // go up node = path.pop(); parent = path[path.length - 1]; i = indexes.pop(); goingUp = true; - } + } - if (node.leaf) { // check current node + if (node.leaf) { + // check current node const index = findItem(item, node.children, equalsFn); if (index !== -1) { - // item found, remove the item and condense tree upwards - node.children.splice(index, 1); - path.push(node); - this._condense(path); - return this; + // item found, remove the item and condense tree upwards + node.children.splice(index, 1); + path.push(node); + this._condense(path); + return this; } - } + } - if (!goingUp && !node.leaf && contains(node, bbox)) { // go down + if (!goingUp && !node.leaf && contains(node, bbox)) { + // go down path.push(node); indexes.push(i); i = 0; parent = node; node = node.children[0]; - - } else if (parent) { // go right + } else if (parent) { + // go right i++; node = parent.children[i]; goingUp = false; + } else node = null; // nothing found + } - } else node = null; // nothing found - } - - return this; - } - - toBBox(item) { return item; } - - compareMinX(a, b) { return a.minX - b.minX; } - compareMinY(a, b) { return a.minY - b.minY; } - - toJSON() { return this.data; } + return this; + } - fromJSON(data) { - this.data = data; - return this; - } + toBBox(item) { + return item; + } - _all(node, result) { - const nodesToSearch = []; - while (node) { - if (node.leaf) result.push(...node.children); - else nodesToSearch.push(...node.children); + compareMinX(a, b) { + return a.minX - b.minX; + } + compareMinY(a, b) { + return a.minY - b.minY; + } - node = nodesToSearch.pop(); - } - return result; - } + toJSON() { + return this.data; + } - _build(items, left, right, height) { + fromJSON(data) { + this.data = data; + return this; + } - const N = right - left + 1; - let M = this._maxEntries; - let node; + _all(node, result) { + const nodesToSearch = []; + while (node) { + if (node.leaf) result.push(...node.children); + else nodesToSearch.push(...node.children); - if (N <= M) { - // reached leaf level; return leaf - node = createNode(items.slice(left, right + 1)); - calcBBox(node, this.toBBox); - return node; - } + node = nodesToSearch.pop(); + } + return result; + } - if (!height) { - // target height of the bulk-loaded tree - height = Math.ceil(Math.log(N) / Math.log(M)); + _build(items, left, right, height) { + const N = right - left + 1; + let M = this._maxEntries; + let node; - // target number of root entries to maximize storage utilization - M = Math.ceil(N / Math.pow(M, height - 1)); - } + if (N <= M) { + // reached leaf level; return leaf + node = createNode(items.slice(left, right + 1)); + calcBBox(node, this.toBBox); + return node; + } - node = createNode([]); - node.leaf = false; - node.height = height; + if (!height) { + // target height of the bulk-loaded tree + height = Math.ceil(Math.log(N) / Math.log(M)); - // split the items into M mostly square tiles + // target number of root entries to maximize storage utilization + M = Math.ceil(N / Math.pow(M, height - 1)); + } - const N2 = Math.ceil(N / M); - const N1 = N2 * Math.ceil(Math.sqrt(M)); + node = createNode([]); + node.leaf = false; + node.height = height; - multiSelect(items, left, right, N1, this.compareMinX); + // split the items into M mostly square tiles - for (let i = left; i <= right; i += N1) { + const N2 = Math.ceil(N / M); + const N1 = N2 * Math.ceil(Math.sqrt(M)); - const right2 = Math.min(i + N1 - 1, right); + multiSelect(items, left, right, N1, this.compareMinX); - multiSelect(items, i, right2, N2, this.compareMinY); + for (let i = left; i <= right; i += N1) { + const right2 = Math.min(i + N1 - 1, right); - for (let j = i; j <= right2; j += N2) { + multiSelect(items, i, right2, N2, this.compareMinY); + for (let j = i; j <= right2; j += N2) { const right3 = Math.min(j + N2 - 1, right2); // pack each entry recursively node.children.push(this._build(items, j, right3, height - 1)); + } } - } - calcBBox(node, this.toBBox); + calcBBox(node, this.toBBox); - return node; - } + return node; + } - _chooseSubtree(bbox, node, level, path) { - while (true) { - path.push(node); + _chooseSubtree(bbox, node, level, path) { + while (true) { + path.push(node); - if (node.leaf || path.length - 1 === level) break; + if (node.leaf || path.length - 1 === level) break; - let minArea = Infinity; - let minEnlargement = Infinity; - let targetNode; + let minArea = Infinity; + let minEnlargement = Infinity; + let targetNode; - for (let i = 0; i < node.children.length; i++) { + for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; const area = bboxArea(child); const enlargement = enlargedArea(bbox, child) - area; // choose entry with the least area enlargement if (enlargement < minEnlargement) { - minEnlargement = enlargement; - minArea = area < minArea ? area : minArea; - targetNode = child; - + minEnlargement = enlargement; + minArea = area < minArea ? area : minArea; + targetNode = child; } else if (enlargement === minEnlargement) { - // otherwise choose one with the smallest area - if (area < minArea) { - minArea = area; - targetNode = child; - } + // otherwise choose one with the smallest area + if (area < minArea) { + minArea = area; + targetNode = child; + } } - } - - node = targetNode || node.children[0]; - } - - return node; - } + } - _insert(item, level, isNode) { - const bbox = isNode ? item : this.toBBox(item); - const insertPath = []; - - // find the best node for accommodating the item, saving all nodes along the path too - const node = this._chooseSubtree(bbox, this.data, level, insertPath); + node = targetNode || node.children[0]; + } - // put the item into the node - node.children.push(item); - extend(node, bbox); + return node; + } - // split on node overflow; propagate upwards if necessary - while (level >= 0) { - if (insertPath[level].children.length > this._maxEntries) { + _insert(item, level, isNode) { + const bbox = isNode ? item : this.toBBox(item); + const insertPath = []; + + // find the best node for accommodating the item, saving all nodes along the path too + const node = this._chooseSubtree( + bbox, + this.data, + level, + insertPath, + ); + + // put the item into the node + node.children.push(item); + extend(node, bbox); + + // split on node overflow; propagate upwards if necessary + while (level >= 0) { + if (insertPath[level].children.length > this._maxEntries) { this._split(insertPath, level); level--; - } else break; - } + } else break; + } - // adjust bboxes along the insertion path - this._adjustParentBBoxes(bbox, insertPath, level); - } + // adjust bboxes along the insertion path + this._adjustParentBBoxes(bbox, insertPath, level); + } - // split overflowed node into two - _split(insertPath, level) { - const node = insertPath[level]; - const M = node.children.length; - const m = this._minEntries; + // split overflowed node into two + _split(insertPath, level) { + const node = insertPath[level]; + const M = node.children.length; + const m = this._minEntries; - this._chooseSplitAxis(node, m, M); + this._chooseSplitAxis(node, m, M); - const splitIndex = this._chooseSplitIndex(node, m, M); + const splitIndex = this._chooseSplitIndex(node, m, M); - const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); - newNode.height = node.height; - newNode.leaf = node.leaf; + const newNode = createNode( + node.children.splice( + splitIndex, + node.children.length - splitIndex, + ), + ); + newNode.height = node.height; + newNode.leaf = node.leaf; - calcBBox(node, this.toBBox); - calcBBox(newNode, this.toBBox); + calcBBox(node, this.toBBox); + calcBBox(newNode, this.toBBox); - if (level) insertPath[level - 1].children.push(newNode); - else this._splitRoot(node, newNode); - } + if (level) insertPath[level - 1].children.push(newNode); + else this._splitRoot(node, newNode); + } - _splitRoot(node, newNode) { - // split root node - this.data = createNode([node, newNode]); - this.data.height = node.height + 1; - this.data.leaf = false; - calcBBox(this.data, this.toBBox); - } + _splitRoot(node, newNode) { + // split root node + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; + calcBBox(this.data, this.toBBox); + } - _chooseSplitIndex(node, m, M) { - let index; - let minOverlap = Infinity; - let minArea = Infinity; + _chooseSplitIndex(node, m, M) { + let index; + let minOverlap = Infinity; + let minArea = Infinity; - for (let i = m; i <= M - m; i++) { - const bbox1 = distBBox(node, 0, i, this.toBBox); - const bbox2 = distBBox(node, i, M, this.toBBox); + for (let i = m; i <= M - m; i++) { + const bbox1 = distBBox(node, 0, i, this.toBBox); + const bbox2 = distBBox(node, i, M, this.toBBox); - const overlap = intersectionArea(bbox1, bbox2); - const area = bboxArea(bbox1) + bboxArea(bbox2); + const overlap = intersectionArea(bbox1, bbox2); + const area = bboxArea(bbox1) + bboxArea(bbox2); - // choose distribution with minimum overlap - if (overlap < minOverlap) { + // choose distribution with minimum overlap + if (overlap < minOverlap) { minOverlap = overlap; index = i; minArea = area < minArea ? area : minArea; - - } else if (overlap === minOverlap) { + } else if (overlap === minOverlap) { // otherwise choose distribution with minimum area if (area < minArea) { - minArea = area; - index = i; + minArea = area; + index = i; } + } } - } - return index || M - m; - } + return index || M - m; + } - // sorts node children by the best axis for split - _chooseSplitAxis(node, m, M) { - const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX; - const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY; - const xMargin = this._allDistMargin(node, m, M, compareMinX); - const yMargin = this._allDistMargin(node, m, M, compareMinY); + // sorts node children by the best axis for split + _chooseSplitAxis(node, m, M) { + const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX; + const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY; + const xMargin = this._allDistMargin(node, m, M, compareMinX); + const yMargin = this._allDistMargin(node, m, M, compareMinY); - // if total distributions margin value is minimal for x, sort by minX, - // otherwise it's already sorted by minY - if (xMargin < yMargin) node.children.sort(compareMinX); - } + // if total distributions margin value is minimal for x, sort by minX, + // otherwise it's already sorted by minY + if (xMargin < yMargin) node.children.sort(compareMinX); + } - // total margin of all possible split distributions where each node is at least m full - _allDistMargin(node, m, M, compare) { - node.children.sort(compare); + // total margin of all possible split distributions where each node is at least m full + _allDistMargin(node, m, M, compare) { + node.children.sort(compare); - const toBBox = this.toBBox; - const leftBBox = distBBox(node, 0, m, toBBox); - const rightBBox = distBBox(node, M - m, M, toBBox); - let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); + const toBBox = this.toBBox; + const leftBBox = distBBox(node, 0, m, toBBox); + const rightBBox = distBBox(node, M - m, M, toBBox); + let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); - for (let i = m; i < M - m; i++) { - const child = node.children[i]; - extend(leftBBox, node.leaf ? toBBox(child) : child); - margin += bboxMargin(leftBBox); + for (let i = m; i < M - m; i++) { + const child = node.children[i]; + extend(leftBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(leftBBox); + } + + for (let i = M - m - 1; i >= m; i--) { + const child = node.children[i]; + extend(rightBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(rightBBox); + } + + return margin; + } + + _adjustParentBBoxes(bbox, path, level) { + // adjust bboxes along the given tree path + for (let i = level; i >= 0; i--) { + extend(path[i], bbox); + } + } + + _condense(path) { + // go through the path, removing empty nodes and updating bboxes + for (let i = path.length - 1, siblings; i >= 0; i--) { + if (path[i].children.length === 0) { + if (i > 0) { + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); + } else this.clear(); + } else calcBBox(path[i], this.toBBox); + } + } + } + + function findItem(item, items, equalsFn) { + if (!equalsFn) return items.indexOf(item); + + for (let i = 0; i < items.length; i++) { + if (equalsFn(item, items[i])) return i; + } + return -1; + } + + // calculate node's bbox from bboxes of its children + function calcBBox(node, toBBox) { + distBBox(node, 0, node.children.length, toBBox, node); } - for (let i = M - m - 1; i >= m; i--) { + // min bounding rectangle of node children from k to p-1 + function distBBox(node, k, p, toBBox, destNode) { + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; + + for (let i = k; i < p; i++) { const child = node.children[i]; - extend(rightBBox, node.leaf ? toBBox(child) : child); - margin += bboxMargin(rightBBox); + extend(destNode, node.leaf ? toBBox(child) : child); + } + + return destNode; } - return margin; - } + function extend(a, b) { + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); + return a; + } - _adjustParentBBoxes(bbox, path, level) { - // adjust bboxes along the given tree path - for (let i = level; i >= 0; i--) { - extend(path[i], bbox); + function compareNodeMinX(a, b) { + return a.minX - b.minX; + } + function compareNodeMinY(a, b) { + return a.minY - b.minY; } - } - _condense(path) { - // go through the path, removing empty nodes and updating bboxes - for (let i = path.length - 1, siblings; i >= 0; i--) { - if (path[i].children.length === 0) { - if (i > 0) { - siblings = path[i - 1].children; - siblings.splice(siblings.indexOf(path[i]), 1); + function bboxArea(a) { + return (a.maxX - a.minX) * (a.maxY - a.minY); + } + function bboxMargin(a) { + return a.maxX - a.minX + (a.maxY - a.minY); + } - } else this.clear(); + function enlargedArea(a, b) { + return ( + (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)) + ); + } - } else calcBBox(path[i], this.toBBox); + function intersectionArea(a, b) { + const minX = Math.max(a.minX, b.minX); + const minY = Math.max(a.minY, b.minY); + const maxX = Math.min(a.maxX, b.maxX); + const maxY = Math.min(a.maxY, b.maxY); + + return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); } - } -} -function findItem(item, items, equalsFn) { - if (!equalsFn) return items.indexOf(item); + function contains(a, b) { + return ( + a.minX <= b.minX && + a.minY <= b.minY && + b.maxX <= a.maxX && + b.maxY <= a.maxY + ); + } - for (let i = 0; i < items.length; i++) { - if (equalsFn(item, items[i])) return i; - } - return -1; -} - -// calculate node's bbox from bboxes of its children -function calcBBox(node, toBBox) { - distBBox(node, 0, node.children.length, toBBox, node); -} - -// min bounding rectangle of node children from k to p-1 -function distBBox(node, k, p, toBBox, destNode) { - if (!destNode) destNode = createNode(null); - destNode.minX = Infinity; - destNode.minY = Infinity; - destNode.maxX = -Infinity; - destNode.maxY = -Infinity; - - for (let i = k; i < p; i++) { - const child = node.children[i]; - extend(destNode, node.leaf ? toBBox(child) : child); - } + function intersects(a, b) { + return ( + b.minX <= a.maxX && + b.minY <= a.maxY && + b.maxX >= a.minX && + b.maxY >= a.minY + ); + } - return destNode; -} - -function extend(a, b) { - a.minX = Math.min(a.minX, b.minX); - a.minY = Math.min(a.minY, b.minY); - a.maxX = Math.max(a.maxX, b.maxX); - a.maxY = Math.max(a.maxY, b.maxY); - return a; -} - -function compareNodeMinX(a, b) { return a.minX - b.minX; } -function compareNodeMinY(a, b) { return a.minY - b.minY; } - -function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } -function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } - -function enlargedArea(a, b) { - return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * - (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); -} - -function intersectionArea(a, b) { - const minX = Math.max(a.minX, b.minX); - const minY = Math.max(a.minY, b.minY); - const maxX = Math.min(a.maxX, b.maxX); - const maxY = Math.min(a.maxY, b.maxY); - - return Math.max(0, maxX - minX) * - Math.max(0, maxY - minY); -} - -function contains(a, b) { - return a.minX <= b.minX && - a.minY <= b.minY && - b.maxX <= a.maxX && - b.maxY <= a.maxY; -} - -function intersects(a, b) { - return b.minX <= a.maxX && - b.minY <= a.maxY && - b.maxX >= a.minX && - b.maxY >= a.minY; -} - -function createNode(children) { - return { - children, - height: 1, - leaf: true, - minX: Infinity, - minY: Infinity, - maxX: -Infinity, - maxY: -Infinity - }; -} + function createNode(children) { + return { + children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity, + }; + } -// sort an array so that items come in groups of n unsorted items, with groups sorted between each other; -// combines selection algorithm with binary divide & conquer approach + // sort an array so that items come in groups of n unsorted items, with groups sorted between each other; + // combines selection algorithm with binary divide & conquer approach -function multiSelect(arr, left, right, n, compare) { - const stack = [left, right]; + function multiSelect(arr, left, right, n, compare) { + const stack = [left, right]; - while (stack.length) { - right = stack.pop(); - left = stack.pop(); + while (stack.length) { + right = stack.pop(); + left = stack.pop(); - if (right - left <= n) continue; + if (right - left <= n) continue; - const mid = left + Math.ceil((right - left) / n / 2) * n; - (0,_quickselect__WEBPACK_IMPORTED_MODULE_0__["default"])(arr, mid, left, right, compare); + const mid = left + Math.ceil((right - left) / n / 2) * n; + (0, _quickselect__WEBPACK_IMPORTED_MODULE_0__["default"])( + arr, + mid, + left, + right, + compare, + ); - stack.push(left, mid, mid, right); - } -} - - -/***/ }) - -/******/ }); -/************************************************************************/ -/******/ // The module cache -/******/ var __webpack_module_cache__ = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ // Check if module is in cache -/******/ var cachedModule = __webpack_module_cache__[moduleId]; -/******/ if (cachedModule !== undefined) { -/******/ return cachedModule.exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = __webpack_module_cache__[moduleId] = { -/******/ // no module.id needed -/******/ // no module.loaded needed -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/************************************************************************/ -/******/ /* webpack/runtime/define property getters */ -/******/ (() => { -/******/ // define getter functions for harmony exports -/******/ __webpack_require__.d = (exports, definition) => { -/******/ for(var key in definition) { -/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { -/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); -/******/ } -/******/ } -/******/ }; -/******/ })(); -/******/ -/******/ /* webpack/runtime/hasOwnProperty shorthand */ -/******/ (() => { -/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) -/******/ })(); -/******/ -/******/ /* webpack/runtime/make namespace object */ -/******/ (() => { -/******/ // define __esModule on exports -/******/ __webpack_require__.r = (exports) => { -/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { -/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); -/******/ } -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ })(); -/******/ -/************************************************************************/ -var __webpack_exports__ = {}; -/*!***************************!*\ + stack.push(left, mid, mid, right); + } + } + + /***/ + }, + + /******/ + }; + /************************************************************************/ + /******/ // The module cache + /******/ var __webpack_module_cache__ = {}; + /******/ + /******/ // The require function + /******/ function __webpack_require__(moduleId) { + /******/ // Check if module is in cache + /******/ var cachedModule = __webpack_module_cache__[moduleId]; + /******/ if (cachedModule !== undefined) { + /******/ return cachedModule.exports; + /******/ + } + /******/ // Create a new module (and put it into the cache) + /******/ var module = (__webpack_module_cache__[moduleId] = { + /******/ // no module.id needed + /******/ // no module.loaded needed + /******/ exports: {}, + /******/ + }); + /******/ + /******/ // Execute the module function + /******/ __webpack_modules__[moduleId].call( + module.exports, + module, + module.exports, + __webpack_require__, + ); + /******/ + /******/ // Return the exports of the module + /******/ return module.exports; + /******/ + } + /******/ + /************************************************************************/ + /******/ /* webpack/runtime/define property getters */ + /******/ (() => { + /******/ // define getter functions for harmony exports + /******/ __webpack_require__.d = (exports, definition) => { + /******/ for (var key in definition) { + /******/ if ( + __webpack_require__.o(definition, key) && + !__webpack_require__.o(exports, key) + ) { + /******/ Object.defineProperty(exports, key, { + enumerable: true, + get: definition[key], + }); + /******/ + } + /******/ + } + /******/ + }; + /******/ + })(); + /******/ + /******/ /* webpack/runtime/hasOwnProperty shorthand */ + /******/ (() => { + /******/ __webpack_require__.o = (obj, prop) => + Object.prototype.hasOwnProperty.call(obj, prop); + /******/ + })(); + /******/ + /******/ /* webpack/runtime/make namespace object */ + /******/ (() => { + /******/ // define __esModule on exports + /******/ __webpack_require__.r = (exports) => { + /******/ if (typeof Symbol !== "undefined" && Symbol.toStringTag) { + /******/ Object.defineProperty(exports, Symbol.toStringTag, { + value: "Module", + }); + /******/ + } + /******/ Object.defineProperty(exports, "__esModule", { value: true }); + /******/ + }; + /******/ + })(); + /******/ + /************************************************************************/ + var __webpack_exports__ = {}; + /*!***************************!*\ !*** ./src/demo/index.js ***! \***************************/ -const { TestCanvas } = __webpack_require__(/*! ./canvas */ "./src/demo/canvas.js"); + const { TestCanvas } = __webpack_require__( + /*! ./canvas */ "./src/demo/canvas.js", + ); -const isStressTest = window.location.search.indexOf("?stress") !== -1; -const Test = isStressTest ? __webpack_require__(/*! ./stress */ "./src/demo/stress.js") : __webpack_require__(/*! ./tank */ "./src/demo/tank.js"); + const isStressTest = window.location.search.indexOf("?stress") !== -1; + const Test = isStressTest + ? __webpack_require__(/*! ./stress */ "./src/demo/stress.js") + : __webpack_require__(/*! ./tank */ "./src/demo/tank.js"); -const test = new Test(); -const canvas = new TestCanvas(test); + const test = new Test(); + const canvas = new TestCanvas(test); -document.body.appendChild(canvas.element); + document.body.appendChild(canvas.element); -if (test.start) { - test.start(); -} + if (test.start) { + test.start(); + } -/******/ })() -; \ No newline at end of file + /******/ +})(); diff --git a/dist/demo/index.html b/dist/demo/index.html index 26b2181b..ab476592 100644 --- a/dist/demo/index.html +++ b/dist/demo/index.html @@ -50,5 +50,7 @@ - + + + diff --git a/dist/demo/stress.d.ts b/dist/demo/stress.d.ts index ab4a022e..8aba18da 100644 --- a/dist/demo/stress.d.ts +++ b/dist/demo/stress.d.ts @@ -1,27 +1,27 @@ export = Stress; declare class Stress { - constructor(count?: number); - size: number; - physics: System; - bodies: any[]; - polygons: number; - boxes: number; - circles: number; - ellipses: number; - lines: number; - lastVariant: number; - count: number; - bounds: import("..").Box[]; - enableFiltering: boolean; - legend: string; - lastTime: number; - updateBody(body: any): void; - start: () => void; - getBounds(): import("..").Box[]; - toggleFiltering(): void; - update(): void; - timeScale: number | undefined; - bounceBody(body: any): void; - createShape(large: any): void; + constructor(count?: number); + size: number; + physics: System; + bodies: any[]; + polygons: number; + boxes: number; + circles: number; + ellipses: number; + lines: number; + lastVariant: number; + count: number; + bounds: import("..").Box[]; + enableFiltering: boolean; + legend: string; + lastTime: number; + updateBody(body: any): void; + start: () => void; + getBounds(): import("..").Box[]; + toggleFiltering(): void; + update(): void; + timeScale: number | undefined; + bounceBody(body: any): void; + createShape(large: any): void; } import { System } from "../system"; diff --git a/dist/demo/stress.js b/dist/demo/stress.js index 6b4a20e2..77e1c32d 100644 --- a/dist/demo/stress.js +++ b/dist/demo/stress.js @@ -5,26 +5,29 @@ const { getBounceDirection, groupBits } = require("../utils"); const { width, height, loop } = require("./canvas"); const seededRandom = require("random-seed").create("@Prozi").random; function random(min, max) { - return Math.floor(seededRandom() * max) + min; + return Math.floor(seededRandom() * max) + min; +} +function getDefaultCount() { + return Math.floor(Math.min(2000, Math.hypot(width, height))); } class Stress { - constructor(count = 2000) { - this.size = Math.sqrt((width * height) / (count * 50)); - this.physics = new System(5); - this.bodies = []; - this.polygons = 0; - this.boxes = 0; - this.circles = 0; - this.ellipses = 0; - this.lines = 0; - this.lastVariant = 0; - this.count = count; - this.bounds = this.getBounds(); - this.enableFiltering = false; - for (let i = 0; i < count; ++i) { - this.createShape(!random(0, 20)); - } - this.legend = `
Total: ${count}
+ constructor(count = getDefaultCount()) { + this.size = Math.sqrt((width * height) / (count * 50)); + this.physics = new System(5); + this.bodies = []; + this.polygons = 0; + this.boxes = 0; + this.circles = 0; + this.ellipses = 0; + this.lines = 0; + this.lastVariant = 0; + this.count = count; + this.bounds = this.getBounds(); + this.enableFiltering = false; + for (let i = 0; i < count; ++i) { + this.createShape(!random(0, 20)); + } + this.legend = `
Total: ${count}
Polygons: ${this.polygons}
Boxes: ${this.boxes}
Circles: ${this.circles}
@@ -36,178 +39,200 @@ class Stress { `; - this.lastTime = Date.now(); - this.updateBody = this.updateBody.bind(this); - // observer #debug & add filtering checkbox event - const observer = new window.MutationObserver((mutations) => { - mutations.forEach((mutation) => { - mutation.addedNodes.forEach((node) => { - if (node.id == "debug") { - document - .querySelector("#filtering") - .addEventListener("change", () => this.toggleFiltering()); - observer.disconnect(); - } - }); - }); - }); - observer.observe(document.querySelector("body"), { - subtree: false, - childList: true, + this.lastTime = Date.now(); + this.updateBody = this.updateBody.bind(this); + // observer #debug & add filtering checkbox event + const observer = new window.MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.id == "debug") { + document + .querySelector("#filtering") + .addEventListener("change", () => this.toggleFiltering()); + observer.disconnect(); + } }); - this.start = () => { - loop(this.update.bind(this)); - }; + }); + }); + observer.observe(document.querySelector("body"), { + subtree: false, + childList: true, + }); + this.start = () => { + loop(this.update.bind(this)); + }; + } + getBounds() { + return [ + this.physics.createBox({ x: 0, y: 0 }, width, 10, { + isStatic: true, + }), + this.physics.createBox({ x: width - 10, y: 0 }, 10, height, { + isStatic: true, + }), + this.physics.createBox({ x: 0, y: height - 10 }, width, 10, { + isStatic: true, + }), + this.physics.createBox({ x: 0, y: 0 }, 10, height, { + isStatic: true, + }), + ]; + } + toggleFiltering() { + this.enableFiltering = !this.enableFiltering; + this.physics.clear(); + this.bodies.length = 0; + this.polygons = 0; + this.boxes = 0; + this.circles = 0; + this.ellipses = 0; + this.lines = 0; + this.lastVariant = 0; + this.bounds = this.getBounds(); + for (let i = 0; i < this.count; ++i) { + this.createShape(!random(0, 20)); } - getBounds() { - return [ - this.physics.createBox({ x: 0, y: 0 }, width, 10, { - isStatic: true, - }), - this.physics.createBox({ x: width - 10, y: 0 }, 10, height, { - isStatic: true, - }), - this.physics.createBox({ x: 0, y: height - 10 }, width, 10, { - isStatic: true, - }), - this.physics.createBox({ x: 0, y: 0 }, 10, height, { - isStatic: true, - }), - ]; + } + update() { + const now = Date.now(); + this.timeScale = Math.min(1000, now - this.lastTime) / 60; + this.lastTime = now; + this.bodies.forEach(this.updateBody); + } + updateBody(body) { + body.setAngle(body.angle + body.rotationSpeed * this.timeScale, false); + if (seededRandom() < 0.05 * this.timeScale) { + body.targetScale.x = 0.5 + seededRandom(); } - toggleFiltering() { - this.enableFiltering = !this.enableFiltering; - this.physics.clear(); - this.bodies.length = 0; - this.polygons = 0; - this.boxes = 0; - this.circles = 0; - this.ellipses = 0; - this.lines = 0; - this.lastVariant = 0; - this.bounds = this.getBounds(); - for (let i = 0; i < this.count; ++i) { - this.createShape(!random(0, 20)); - } + if (seededRandom() < 0.05 * this.timeScale) { + body.targetScale.y = 0.5 + seededRandom(); + } + if (Math.abs(body.targetScale.x - body.scaleX) > 0.01) { + const scaleX = + body.scaleX + + Math.sign(body.targetScale.x - body.scaleX) * 0.02 * this.timeScale; + const scaleY = + body.scaleY + + Math.sign(body.targetScale.y - body.scaleY) * 0.02 * this.timeScale; + body.setScale(scaleX, scaleY, false); } - update() { - const now = Date.now(); - this.timeScale = Math.min(1000, now - this.lastTime) / 60; - this.lastTime = now; - this.bodies.forEach(this.updateBody); + // as last step update position, and bounding box + body.setPosition( + body.x + body.directionX * this.timeScale, + body.y + body.directionY * this.timeScale, + ); + // separate + bounce + this.bounceBody(body); + } + bounceBody(body) { + const bounces = { x: 0, y: 0 }; + const addBounces = ({ overlapV: { x, y } }) => { + bounces.x += x; + bounces.y += y; + }; + this.physics.checkOne(body, addBounces); + if (bounces.x || bounces.y) { + const size = 0.5 * (body.scaleX + body.scaleY); + const bounce = getBounceDirection(body, { + x: body.x + bounces.x, + y: body.y + bounces.y, + }); + bounce.scale(body.size).add({ + x: body.directionX * size, + y: body.directionY * size, + }); + const { x, y } = bounce.normalize(); + body.directionX = x; + body.directionY = y; + body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; + body.setPosition(body.x - bounces.x, body.y - bounces.y); } - updateBody(body) { - body.setAngle(body.angle + body.rotationSpeed * this.timeScale, false); - if (seededRandom() < 0.05 * this.timeScale) { - body.targetScale.x = 0.5 + seededRandom(); + } + createShape(large) { + const minSize = this.size * 1.0 * (large ? seededRandom() + 1 : 1); + const maxSize = this.size * 1.25 * (large ? seededRandom() * 2 + 1 : 1); + const x = random(0, width); + const y = random(0, height); + const direction = (random(0, 360) * Math.PI) / 180; + const options = { + isCentered: true, + padding: (minSize + maxSize) * 0.2, + }; + let body; + let variant = this.lastVariant++ % 5; + switch (variant) { + case 0: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Circle); } - if (seededRandom() < 0.05 * this.timeScale) { - body.targetScale.y = 0.5 + seededRandom(); + body = this.physics.createCircle( + { x, y }, + random(minSize, maxSize) / 2, + options, + ); + ++this.circles; + break; + case 1: + const width = random(minSize, maxSize); + const height = random(minSize, maxSize); + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Ellipse); + console.log(); } - if (Math.abs(body.targetScale.x - body.scaleX) > 0.01) { - const scaleX = body.scaleX + - Math.sign(body.targetScale.x - body.scaleX) * 0.02 * this.timeScale; - const scaleY = body.scaleY + - Math.sign(body.targetScale.y - body.scaleY) * 0.02 * this.timeScale; - body.setScale(scaleX, scaleY, false); + body = this.physics.createEllipse({ x, y }, width, height, 2, options); + ++this.ellipses; + break; + case 2: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Box); } - // as last step update position, and bounding box - body.setPosition(body.x + body.directionX * this.timeScale, body.y + body.directionY * this.timeScale); - // separate + bounce - this.bounceBody(body); - } - bounceBody(body) { - const bounces = { x: 0, y: 0 }; - const addBounces = ({ overlapV: { x, y } }) => { - bounces.x += x; - bounces.y += y; - }; - this.physics.checkOne(body, addBounces); - if (bounces.x || bounces.y) { - const size = 0.5 * (body.scaleX + body.scaleY); - const bounce = getBounceDirection(body, { - x: body.x + bounces.x, - y: body.y + bounces.y, - }); - bounce.scale(body.size).add({ - x: body.directionX * size, - y: body.directionY * size, - }); - const { x, y } = bounce.normalize(); - body.directionX = x; - body.directionY = y; - body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; - body.setPosition(body.x - bounces.x, body.y - bounces.y); + body = this.physics.createBox( + { x, y }, + random(minSize, maxSize), + random(minSize, maxSize), + options, + ); + ++this.boxes; + break; + case 3: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Line); } - } - createShape(large) { - const minSize = this.size * 1.0 * (large ? seededRandom() + 1 : 1); - const maxSize = this.size * 1.25 * (large ? seededRandom() * 2 + 1 : 1); - const x = random(0, width); - const y = random(0, height); - const direction = (random(0, 360) * Math.PI) / 180; - const options = { - isCentered: true, - padding: (minSize + maxSize) * 0.2, - }; - let body; - let variant = this.lastVariant++ % 5; - switch (variant) { - case 0: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Circle); - } - body = this.physics.createCircle({ x, y }, random(minSize, maxSize) / 2, options); - ++this.circles; - break; - case 1: - const width = random(minSize, maxSize); - const height = random(minSize, maxSize); - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Ellipse); - console.log(); - } - body = this.physics.createEllipse({ x, y }, width, height, 2, options); - ++this.ellipses; - break; - case 2: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Box); - } - body = this.physics.createBox({ x, y }, random(minSize, maxSize), random(minSize, maxSize), options); - ++this.boxes; - break; - case 3: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Line); - } - body = this.physics.createLine({ x, y }, { - x: x + random(minSize, maxSize), - y: y + random(minSize, maxSize), - }, options); - ++this.lines; - break; - default: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Polygon); - } - body = this.physics.createPolygon({ x, y }, [ - { x: -random(minSize, maxSize), y: random(minSize, maxSize) }, - { x: random(minSize, maxSize), y: random(minSize, maxSize) }, - { x: random(minSize, maxSize), y: -random(minSize, maxSize) }, - { x: -random(minSize, maxSize), y: -random(minSize, maxSize) }, - ], options); - ++this.polygons; - break; + body = this.physics.createLine( + { x, y }, + { + x: x + random(minSize, maxSize), + y: y + random(minSize, maxSize), + }, + options, + ); + ++this.lines; + break; + default: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Polygon); } - // set initial rotation angle direction - body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; - body.setAngle((random(0, 360) * Math.PI) / 180); - body.targetScale = { x: 1, y: 1 }; - body.size = (minSize + maxSize) / 2; - body.directionX = Math.cos(direction); - body.directionY = Math.sin(direction); - this.bodies.push(body); + body = this.physics.createPolygon( + { x, y }, + [ + { x: -random(minSize, maxSize), y: random(minSize, maxSize) }, + { x: random(minSize, maxSize), y: random(minSize, maxSize) }, + { x: random(minSize, maxSize), y: -random(minSize, maxSize) }, + { x: -random(minSize, maxSize), y: -random(minSize, maxSize) }, + ], + options, + ); + ++this.polygons; + break; } + // set initial rotation angle direction + body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; + body.setAngle((random(0, 360) * Math.PI) / 180); + body.targetScale = { x: 1, y: 1 }; + body.size = (minSize + maxSize) / 2; + body.directionX = Math.cos(direction); + body.directionY = Math.sin(direction); + this.bodies.push(body); + } } module.exports = Stress; diff --git a/dist/external/quickselect.d.ts b/dist/external/quickselect.d.ts index deea91da..de7eccc9 100644 --- a/dist/external/quickselect.d.ts +++ b/dist/external/quickselect.d.ts @@ -9,4 +9,10 @@ * @param {number} [right=arr.length-1] right index * @param {(a: T, b: T) => number} [compare = (a, b) => a - b] compare function */ -export default function quickselect(arr: T[], k: number, left?: number | undefined, right?: number | undefined, compare?: ((a: T, b: T) => number) | undefined): void; +export default function quickselect( + arr: T[], + k: number, + left?: number | undefined, + right?: number | undefined, + compare?: ((a: T, b: T) => number) | undefined, +): void; diff --git a/dist/external/quickselect.js b/dist/external/quickselect.js index 1ca34c77..1de1553f 100644 --- a/dist/external/quickselect.js +++ b/dist/external/quickselect.js @@ -12,45 +12,46 @@ exports.default = quickselect; * @param {number} [right=arr.length-1] right index * @param {(a: T, b: T) => number} [compare = (a, b) => a - b] compare function */ -function quickselect(arr, k, left = 0, right = arr.length - 1, compare = defaultCompare) { - while (right > left) { - if (right - left > 600) { - const n = right - left + 1; - const m = k - left + 1; - const z = Math.log(n); - const s = 0.5 * Math.exp(2 * z / 3); - const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); - const newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); - const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); - quickselect(arr, k, newLeft, newRight, compare); - } - const t = arr[k]; - let i = left; - /** @type {number} */ - let j = right; - swap(arr, left, k); - if (compare(arr[right], t) > 0) - swap(arr, left, right); - while (i < j) { - swap(arr, i, j); - i++; - j--; - while (compare(arr[i], t) < 0) - i++; - while (compare(arr[j], t) > 0) - j--; - } - if (compare(arr[left], t) === 0) - swap(arr, left, j); - else { - j++; - swap(arr, j, right); - } - if (j <= k) - left = j + 1; - if (k <= j) - right = j - 1; +function quickselect( + arr, + k, + left = 0, + right = arr.length - 1, + compare = defaultCompare, +) { + while (right > left) { + if (right - left > 600) { + const n = right - left + 1; + const m = k - left + 1; + const z = Math.log(n); + const s = 0.5 * Math.exp((2 * z) / 3); + const sd = + 0.5 * Math.sqrt((z * s * (n - s)) / n) * (m - n / 2 < 0 ? -1 : 1); + const newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd)); + const newRight = Math.min(right, Math.floor(k + ((n - m) * s) / n + sd)); + quickselect(arr, k, newLeft, newRight, compare); } + const t = arr[k]; + let i = left; + /** @type {number} */ + let j = right; + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } } /** * @template T @@ -59,9 +60,9 @@ function quickselect(arr, k, left = 0, right = arr.length - 1, compare = default * @param {number} j */ function swap(arr, i, j) { - const tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; + const tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; } /** * @template T @@ -70,5 +71,5 @@ function swap(arr, i, j) { * @returns {number} */ function defaultCompare(a, b) { - return a < b ? -1 : a > b ? 1 : 0; + return a < b ? -1 : a > b ? 1 : 0; } diff --git a/dist/external/rbush.d.ts b/dist/external/rbush.d.ts index 71459bdb..d834701d 100644 --- a/dist/external/rbush.d.ts +++ b/dist/external/rbush.d.ts @@ -1,37 +1,42 @@ export default class RBush { - constructor(maxEntries?: number); - _maxEntries: number; - _minEntries: number; - all(): any; - search(bbox: any): any[]; - collides(bbox: any): boolean; - load(data: any): this; - data: any; - insert(item: any): this; - clear(): this; - remove(item: any, equalsFn: any): this; - toBBox(item: any): any; - compareMinX(a: any, b: any): number; - compareMinY(a: any, b: any): number; - toJSON(): any; - fromJSON(data: any): this; - _all(node: any, result: any): any; - _build(items: any, left: any, right: any, height: any): { - children: any; - height: number; - leaf: boolean; - minX: number; - minY: number; - maxX: number; - maxY: number; - }; - _chooseSubtree(bbox: any, node: any, level: any, path: any): any; - _insert(item: any, level: any, isNode: any): void; - _split(insertPath: any, level: any): void; - _splitRoot(node: any, newNode: any): void; - _chooseSplitIndex(node: any, m: any, M: any): any; - _chooseSplitAxis(node: any, m: any, M: any): void; - _allDistMargin(node: any, m: any, M: any, compare: any): number; - _adjustParentBBoxes(bbox: any, path: any, level: any): void; - _condense(path: any): void; + constructor(maxEntries?: number); + _maxEntries: number; + _minEntries: number; + all(): any; + search(bbox: any): any[]; + collides(bbox: any): boolean; + load(data: any): this; + data: any; + insert(item: any): this; + clear(): this; + remove(item: any, equalsFn: any): this; + toBBox(item: any): any; + compareMinX(a: any, b: any): number; + compareMinY(a: any, b: any): number; + toJSON(): any; + fromJSON(data: any): this; + _all(node: any, result: any): any; + _build( + items: any, + left: any, + right: any, + height: any, + ): { + children: any; + height: number; + leaf: boolean; + minX: number; + minY: number; + maxX: number; + maxY: number; + }; + _chooseSubtree(bbox: any, node: any, level: any, path: any): any; + _insert(item: any, level: any, isNode: any): void; + _split(insertPath: any, level: any): void; + _splitRoot(node: any, newNode: any): void; + _chooseSplitIndex(node: any, m: any, M: any): any; + _chooseSplitAxis(node: any, m: any, M: any): void; + _allDistMargin(node: any, m: any, M: any, compare: any): number; + _adjustParentBBoxes(bbox: any, path: any, level: any): void; + _condense(path: any): void; } diff --git a/dist/external/rbush.js b/dist/external/rbush.js index 34347ef7..09956d4c 100644 --- a/dist/external/rbush.js +++ b/dist/external/rbush.js @@ -1,435 +1,426 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); const quickselect_1 = __importDefault(require("./quickselect")); class RBush { - constructor(maxEntries = 9) { - // max entries in a node is 9 by default; min node fill is 40% for best performance - this._maxEntries = Math.max(4, maxEntries); - this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); - this.clear(); - } - all() { - return this._all(this.data, []); - } - search(bbox) { - let node = this.data; - const result = []; - if (!intersects(bbox, node)) - return result; - const toBBox = this.toBBox; - const nodesToSearch = []; - while (node) { - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - const childBBox = node.leaf ? toBBox(child) : child; - if (intersects(bbox, childBBox)) { - if (node.leaf) - result.push(child); - else if (contains(bbox, childBBox)) - this._all(child, result); - else - nodesToSearch.push(child); - } - } - node = nodesToSearch.pop(); - } - return result; - } - collides(bbox) { - let node = this.data; - if (!intersects(bbox, node)) - return false; - const nodesToSearch = []; - while (node) { - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - const childBBox = node.leaf ? this.toBBox(child) : child; - if (intersects(bbox, childBBox)) { - if (node.leaf || contains(bbox, childBBox)) - return true; - nodesToSearch.push(child); - } - } - node = nodesToSearch.pop(); + constructor(maxEntries = 9) { + // max entries in a node is 9 by default; min node fill is 40% for best performance + this._maxEntries = Math.max(4, maxEntries); + this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); + this.clear(); + } + all() { + return this._all(this.data, []); + } + search(bbox) { + let node = this.data; + const result = []; + if (!intersects(bbox, node)) return result; + const toBBox = this.toBBox; + const nodesToSearch = []; + while (node) { + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const childBBox = node.leaf ? toBBox(child) : child; + if (intersects(bbox, childBBox)) { + if (node.leaf) result.push(child); + else if (contains(bbox, childBBox)) this._all(child, result); + else nodesToSearch.push(child); } - return false; + } + node = nodesToSearch.pop(); } - load(data) { - if (!(data && data.length)) - return this; - if (data.length < this._minEntries) { - for (let i = 0; i < data.length; i++) { - this.insert(data[i]); - } - return this; - } - // recursively build the tree with the given data from scratch using OMT algorithm - let node = this._build(data.slice(), 0, data.length - 1, 0); - if (!this.data.children.length) { - // save as is if tree is empty - this.data = node; - } - else if (this.data.height === node.height) { - // split root if trees have the same height - this._splitRoot(this.data, node); - } - else { - if (this.data.height < node.height) { - // swap trees if inserted one is bigger - const tmpNode = this.data; - this.data = node; - node = tmpNode; - } - // insert the small tree into the large tree at appropriate level - this._insert(node, this.data.height - node.height - 1, true); + return result; + } + collides(bbox) { + let node = this.data; + if (!intersects(bbox, node)) return false; + const nodesToSearch = []; + while (node) { + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const childBBox = node.leaf ? this.toBBox(child) : child; + if (intersects(bbox, childBBox)) { + if (node.leaf || contains(bbox, childBBox)) return true; + nodesToSearch.push(child); } - return this; + } + node = nodesToSearch.pop(); } - insert(item) { - if (item) - this._insert(item, this.data.height - 1); - return this; + return false; + } + load(data) { + if (!(data && data.length)) return this; + if (data.length < this._minEntries) { + for (let i = 0; i < data.length; i++) { + this.insert(data[i]); + } + return this; } - clear() { - this.data = createNode([]); - return this; + // recursively build the tree with the given data from scratch using OMT algorithm + let node = this._build(data.slice(), 0, data.length - 1, 0); + if (!this.data.children.length) { + // save as is if tree is empty + this.data = node; + } else if (this.data.height === node.height) { + // split root if trees have the same height + this._splitRoot(this.data, node); + } else { + if (this.data.height < node.height) { + // swap trees if inserted one is bigger + const tmpNode = this.data; + this.data = node; + node = tmpNode; + } + // insert the small tree into the large tree at appropriate level + this._insert(node, this.data.height - node.height - 1, true); } - remove(item, equalsFn) { - if (!item) - return this; - let node = this.data; - const bbox = this.toBBox(item); - const path = []; - const indexes = []; - let i, parent, goingUp; - // depth-first iterative tree traversal - while (node || path.length) { - if (!node) { // go up - node = path.pop(); - parent = path[path.length - 1]; - i = indexes.pop(); - goingUp = true; - } - if (node.leaf) { // check current node - const index = findItem(item, node.children, equalsFn); - if (index !== -1) { - // item found, remove the item and condense tree upwards - node.children.splice(index, 1); - path.push(node); - this._condense(path); - return this; - } - } - if (!goingUp && !node.leaf && contains(node, bbox)) { // go down - path.push(node); - indexes.push(i); - i = 0; - parent = node; - node = node.children[0]; - } - else if (parent) { // go right - i++; - node = parent.children[i]; - goingUp = false; - } - else - node = null; // nothing found + return this; + } + insert(item) { + if (item) this._insert(item, this.data.height - 1); + return this; + } + clear() { + this.data = createNode([]); + return this; + } + remove(item, equalsFn) { + if (!item) return this; + let node = this.data; + const bbox = this.toBBox(item); + const path = []; + const indexes = []; + let i, parent, goingUp; + // depth-first iterative tree traversal + while (node || path.length) { + if (!node) { + // go up + node = path.pop(); + parent = path[path.length - 1]; + i = indexes.pop(); + goingUp = true; + } + if (node.leaf) { + // check current node + const index = findItem(item, node.children, equalsFn); + if (index !== -1) { + // item found, remove the item and condense tree upwards + node.children.splice(index, 1); + path.push(node); + this._condense(path); + return this; } - return this; + } + if (!goingUp && !node.leaf && contains(node, bbox)) { + // go down + path.push(node); + indexes.push(i); + i = 0; + parent = node; + node = node.children[0]; + } else if (parent) { + // go right + i++; + node = parent.children[i]; + goingUp = false; + } else node = null; // nothing found } - toBBox(item) { return item; } - compareMinX(a, b) { return a.minX - b.minX; } - compareMinY(a, b) { return a.minY - b.minY; } - toJSON() { return this.data; } - fromJSON(data) { - this.data = data; - return this; + return this; + } + toBBox(item) { + return item; + } + compareMinX(a, b) { + return a.minX - b.minX; + } + compareMinY(a, b) { + return a.minY - b.minY; + } + toJSON() { + return this.data; + } + fromJSON(data) { + this.data = data; + return this; + } + _all(node, result) { + const nodesToSearch = []; + while (node) { + if (node.leaf) result.push(...node.children); + else nodesToSearch.push(...node.children); + node = nodesToSearch.pop(); } - _all(node, result) { - const nodesToSearch = []; - while (node) { - if (node.leaf) - result.push(...node.children); - else - nodesToSearch.push(...node.children); - node = nodesToSearch.pop(); - } - return result; + return result; + } + _build(items, left, right, height) { + const N = right - left + 1; + let M = this._maxEntries; + let node; + if (N <= M) { + // reached leaf level; return leaf + node = createNode(items.slice(left, right + 1)); + calcBBox(node, this.toBBox); + return node; } - _build(items, left, right, height) { - const N = right - left + 1; - let M = this._maxEntries; - let node; - if (N <= M) { - // reached leaf level; return leaf - node = createNode(items.slice(left, right + 1)); - calcBBox(node, this.toBBox); - return node; - } - if (!height) { - // target height of the bulk-loaded tree - height = Math.ceil(Math.log(N) / Math.log(M)); - // target number of root entries to maximize storage utilization - M = Math.ceil(N / Math.pow(M, height - 1)); - } - node = createNode([]); - node.leaf = false; - node.height = height; - // split the items into M mostly square tiles - const N2 = Math.ceil(N / M); - const N1 = N2 * Math.ceil(Math.sqrt(M)); - multiSelect(items, left, right, N1, this.compareMinX); - for (let i = left; i <= right; i += N1) { - const right2 = Math.min(i + N1 - 1, right); - multiSelect(items, i, right2, N2, this.compareMinY); - for (let j = i; j <= right2; j += N2) { - const right3 = Math.min(j + N2 - 1, right2); - // pack each entry recursively - node.children.push(this._build(items, j, right3, height - 1)); - } - } - calcBBox(node, this.toBBox); - return node; + if (!height) { + // target height of the bulk-loaded tree + height = Math.ceil(Math.log(N) / Math.log(M)); + // target number of root entries to maximize storage utilization + M = Math.ceil(N / Math.pow(M, height - 1)); } - _chooseSubtree(bbox, node, level, path) { - while (true) { - path.push(node); - if (node.leaf || path.length - 1 === level) - break; - let minArea = Infinity; - let minEnlargement = Infinity; - let targetNode; - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - const area = bboxArea(child); - const enlargement = enlargedArea(bbox, child) - area; - // choose entry with the least area enlargement - if (enlargement < minEnlargement) { - minEnlargement = enlargement; - minArea = area < minArea ? area : minArea; - targetNode = child; - } - else if (enlargement === minEnlargement) { - // otherwise choose one with the smallest area - if (area < minArea) { - minArea = area; - targetNode = child; - } - } - } - node = targetNode || node.children[0]; - } - return node; + node = createNode([]); + node.leaf = false; + node.height = height; + // split the items into M mostly square tiles + const N2 = Math.ceil(N / M); + const N1 = N2 * Math.ceil(Math.sqrt(M)); + multiSelect(items, left, right, N1, this.compareMinX); + for (let i = left; i <= right; i += N1) { + const right2 = Math.min(i + N1 - 1, right); + multiSelect(items, i, right2, N2, this.compareMinY); + for (let j = i; j <= right2; j += N2) { + const right3 = Math.min(j + N2 - 1, right2); + // pack each entry recursively + node.children.push(this._build(items, j, right3, height - 1)); + } } - _insert(item, level, isNode) { - const bbox = isNode ? item : this.toBBox(item); - const insertPath = []; - // find the best node for accommodating the item, saving all nodes along the path too - const node = this._chooseSubtree(bbox, this.data, level, insertPath); - // put the item into the node - node.children.push(item); - extend(node, bbox); - // split on node overflow; propagate upwards if necessary - while (level >= 0) { - if (insertPath[level].children.length > this._maxEntries) { - this._split(insertPath, level); - level--; - } - else - break; + calcBBox(node, this.toBBox); + return node; + } + _chooseSubtree(bbox, node, level, path) { + while (true) { + path.push(node); + if (node.leaf || path.length - 1 === level) break; + let minArea = Infinity; + let minEnlargement = Infinity; + let targetNode; + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const area = bboxArea(child); + const enlargement = enlargedArea(bbox, child) - area; + // choose entry with the least area enlargement + if (enlargement < minEnlargement) { + minEnlargement = enlargement; + minArea = area < minArea ? area : minArea; + targetNode = child; + } else if (enlargement === minEnlargement) { + // otherwise choose one with the smallest area + if (area < minArea) { + minArea = area; + targetNode = child; + } } - // adjust bboxes along the insertion path - this._adjustParentBBoxes(bbox, insertPath, level); - } - // split overflowed node into two - _split(insertPath, level) { - const node = insertPath[level]; - const M = node.children.length; - const m = this._minEntries; - this._chooseSplitAxis(node, m, M); - const splitIndex = this._chooseSplitIndex(node, m, M); - const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); - newNode.height = node.height; - newNode.leaf = node.leaf; - calcBBox(node, this.toBBox); - calcBBox(newNode, this.toBBox); - if (level) - insertPath[level - 1].children.push(newNode); - else - this._splitRoot(node, newNode); + } + node = targetNode || node.children[0]; } - _splitRoot(node, newNode) { - // split root node - this.data = createNode([node, newNode]); - this.data.height = node.height + 1; - this.data.leaf = false; - calcBBox(this.data, this.toBBox); + return node; + } + _insert(item, level, isNode) { + const bbox = isNode ? item : this.toBBox(item); + const insertPath = []; + // find the best node for accommodating the item, saving all nodes along the path too + const node = this._chooseSubtree(bbox, this.data, level, insertPath); + // put the item into the node + node.children.push(item); + extend(node, bbox); + // split on node overflow; propagate upwards if necessary + while (level >= 0) { + if (insertPath[level].children.length > this._maxEntries) { + this._split(insertPath, level); + level--; + } else break; } - _chooseSplitIndex(node, m, M) { - let index; - let minOverlap = Infinity; - let minArea = Infinity; - for (let i = m; i <= M - m; i++) { - const bbox1 = distBBox(node, 0, i, this.toBBox); - const bbox2 = distBBox(node, i, M, this.toBBox); - const overlap = intersectionArea(bbox1, bbox2); - const area = bboxArea(bbox1) + bboxArea(bbox2); - // choose distribution with minimum overlap - if (overlap < minOverlap) { - minOverlap = overlap; - index = i; - minArea = area < minArea ? area : minArea; - } - else if (overlap === minOverlap) { - // otherwise choose distribution with minimum area - if (area < minArea) { - minArea = area; - index = i; - } - } + // adjust bboxes along the insertion path + this._adjustParentBBoxes(bbox, insertPath, level); + } + // split overflowed node into two + _split(insertPath, level) { + const node = insertPath[level]; + const M = node.children.length; + const m = this._minEntries; + this._chooseSplitAxis(node, m, M); + const splitIndex = this._chooseSplitIndex(node, m, M); + const newNode = createNode( + node.children.splice(splitIndex, node.children.length - splitIndex), + ); + newNode.height = node.height; + newNode.leaf = node.leaf; + calcBBox(node, this.toBBox); + calcBBox(newNode, this.toBBox); + if (level) insertPath[level - 1].children.push(newNode); + else this._splitRoot(node, newNode); + } + _splitRoot(node, newNode) { + // split root node + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; + calcBBox(this.data, this.toBBox); + } + _chooseSplitIndex(node, m, M) { + let index; + let minOverlap = Infinity; + let minArea = Infinity; + for (let i = m; i <= M - m; i++) { + const bbox1 = distBBox(node, 0, i, this.toBBox); + const bbox2 = distBBox(node, i, M, this.toBBox); + const overlap = intersectionArea(bbox1, bbox2); + const area = bboxArea(bbox1) + bboxArea(bbox2); + // choose distribution with minimum overlap + if (overlap < minOverlap) { + minOverlap = overlap; + index = i; + minArea = area < minArea ? area : minArea; + } else if (overlap === minOverlap) { + // otherwise choose distribution with minimum area + if (area < minArea) { + minArea = area; + index = i; } - return index || M - m; + } } - // sorts node children by the best axis for split - _chooseSplitAxis(node, m, M) { - const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX; - const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY; - const xMargin = this._allDistMargin(node, m, M, compareMinX); - const yMargin = this._allDistMargin(node, m, M, compareMinY); - // if total distributions margin value is minimal for x, sort by minX, - // otherwise it's already sorted by minY - if (xMargin < yMargin) - node.children.sort(compareMinX); + return index || M - m; + } + // sorts node children by the best axis for split + _chooseSplitAxis(node, m, M) { + const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX; + const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY; + const xMargin = this._allDistMargin(node, m, M, compareMinX); + const yMargin = this._allDistMargin(node, m, M, compareMinY); + // if total distributions margin value is minimal for x, sort by minX, + // otherwise it's already sorted by minY + if (xMargin < yMargin) node.children.sort(compareMinX); + } + // total margin of all possible split distributions where each node is at least m full + _allDistMargin(node, m, M, compare) { + node.children.sort(compare); + const toBBox = this.toBBox; + const leftBBox = distBBox(node, 0, m, toBBox); + const rightBBox = distBBox(node, M - m, M, toBBox); + let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); + for (let i = m; i < M - m; i++) { + const child = node.children[i]; + extend(leftBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(leftBBox); } - // total margin of all possible split distributions where each node is at least m full - _allDistMargin(node, m, M, compare) { - node.children.sort(compare); - const toBBox = this.toBBox; - const leftBBox = distBBox(node, 0, m, toBBox); - const rightBBox = distBBox(node, M - m, M, toBBox); - let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); - for (let i = m; i < M - m; i++) { - const child = node.children[i]; - extend(leftBBox, node.leaf ? toBBox(child) : child); - margin += bboxMargin(leftBBox); - } - for (let i = M - m - 1; i >= m; i--) { - const child = node.children[i]; - extend(rightBBox, node.leaf ? toBBox(child) : child); - margin += bboxMargin(rightBBox); - } - return margin; + for (let i = M - m - 1; i >= m; i--) { + const child = node.children[i]; + extend(rightBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(rightBBox); } - _adjustParentBBoxes(bbox, path, level) { - // adjust bboxes along the given tree path - for (let i = level; i >= 0; i--) { - extend(path[i], bbox); - } + return margin; + } + _adjustParentBBoxes(bbox, path, level) { + // adjust bboxes along the given tree path + for (let i = level; i >= 0; i--) { + extend(path[i], bbox); } - _condense(path) { - // go through the path, removing empty nodes and updating bboxes - for (let i = path.length - 1, siblings; i >= 0; i--) { - if (path[i].children.length === 0) { - if (i > 0) { - siblings = path[i - 1].children; - siblings.splice(siblings.indexOf(path[i]), 1); - } - else - this.clear(); - } - else - calcBBox(path[i], this.toBBox); - } + } + _condense(path) { + // go through the path, removing empty nodes and updating bboxes + for (let i = path.length - 1, siblings; i >= 0; i--) { + if (path[i].children.length === 0) { + if (i > 0) { + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); + } else this.clear(); + } else calcBBox(path[i], this.toBBox); } + } } exports.default = RBush; function findItem(item, items, equalsFn) { - if (!equalsFn) - return items.indexOf(item); - for (let i = 0; i < items.length; i++) { - if (equalsFn(item, items[i])) - return i; - } - return -1; + if (!equalsFn) return items.indexOf(item); + for (let i = 0; i < items.length; i++) { + if (equalsFn(item, items[i])) return i; + } + return -1; } // calculate node's bbox from bboxes of its children function calcBBox(node, toBBox) { - distBBox(node, 0, node.children.length, toBBox, node); + distBBox(node, 0, node.children.length, toBBox, node); } // min bounding rectangle of node children from k to p-1 function distBBox(node, k, p, toBBox, destNode) { - if (!destNode) - destNode = createNode(null); - destNode.minX = Infinity; - destNode.minY = Infinity; - destNode.maxX = -Infinity; - destNode.maxY = -Infinity; - for (let i = k; i < p; i++) { - const child = node.children[i]; - extend(destNode, node.leaf ? toBBox(child) : child); - } - return destNode; + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; + for (let i = k; i < p; i++) { + const child = node.children[i]; + extend(destNode, node.leaf ? toBBox(child) : child); + } + return destNode; } function extend(a, b) { - a.minX = Math.min(a.minX, b.minX); - a.minY = Math.min(a.minY, b.minY); - a.maxX = Math.max(a.maxX, b.maxX); - a.maxY = Math.max(a.maxY, b.maxY); - return a; + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); + return a; +} +function compareNodeMinX(a, b) { + return a.minX - b.minX; +} +function compareNodeMinY(a, b) { + return a.minY - b.minY; +} +function bboxArea(a) { + return (a.maxX - a.minX) * (a.maxY - a.minY); +} +function bboxMargin(a) { + return a.maxX - a.minX + (a.maxY - a.minY); } -function compareNodeMinX(a, b) { return a.minX - b.minX; } -function compareNodeMinY(a, b) { return a.minY - b.minY; } -function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } -function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } function enlargedArea(a, b) { - return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * - (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); + return ( + (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)) + ); } function intersectionArea(a, b) { - const minX = Math.max(a.minX, b.minX); - const minY = Math.max(a.minY, b.minY); - const maxX = Math.min(a.maxX, b.maxX); - const maxY = Math.min(a.maxY, b.maxY); - return Math.max(0, maxX - minX) * - Math.max(0, maxY - minY); + const minX = Math.max(a.minX, b.minX); + const minY = Math.max(a.minY, b.minY); + const maxX = Math.min(a.maxX, b.maxX); + const maxY = Math.min(a.maxY, b.maxY); + return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); } function contains(a, b) { - return a.minX <= b.minX && - a.minY <= b.minY && - b.maxX <= a.maxX && - b.maxY <= a.maxY; + return ( + a.minX <= b.minX && a.minY <= b.minY && b.maxX <= a.maxX && b.maxY <= a.maxY + ); } function intersects(a, b) { - return b.minX <= a.maxX && - b.minY <= a.maxY && - b.maxX >= a.minX && - b.maxY >= a.minY; + return ( + b.minX <= a.maxX && b.minY <= a.maxY && b.maxX >= a.minX && b.maxY >= a.minY + ); } function createNode(children) { - return { - children, - height: 1, - leaf: true, - minX: Infinity, - minY: Infinity, - maxX: -Infinity, - maxY: -Infinity - }; + return { + children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity, + }; } // sort an array so that items come in groups of n unsorted items, with groups sorted between each other; // combines selection algorithm with binary divide & conquer approach function multiSelect(arr, left, right, n, compare) { - const stack = [left, right]; - while (stack.length) { - right = stack.pop(); - left = stack.pop(); - if (right - left <= n) - continue; - const mid = left + Math.ceil((right - left) / n / 2) * n; - (0, quickselect_1.default)(arr, mid, left, right, compare); - stack.push(left, mid, mid, right); - } + const stack = [left, right]; + while (stack.length) { + right = stack.pop(); + left = stack.pop(); + if (right - left <= n) continue; + const mid = left + Math.ceil((right - left) / n / 2) * n; + (0, quickselect_1.default)(arr, mid, left, right, compare); + stack.push(left, mid, mid, right); + } } diff --git a/dist/index.js b/dist/index.js index 5f773b28..525bab18 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,18 +1,34 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if ( + !desc || + ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k]; + }, + }; + } + Object.defineProperty(o, k2, desc); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __exportStar = + (this && this.__exportStar) || + function (m, exports) { + for (var p in m) + if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) + __createBinding(exports, m, p); + }; Object.defineProperty(exports, "__esModule", { value: true }); __exportStar(require("./model"), exports); __exportStar(require("./bodies/circle"), exports); diff --git a/dist/intersect.d.ts b/dist/intersect.d.ts index 8407ed64..fc8a6fc8 100644 --- a/dist/intersect.d.ts +++ b/dist/intersect.d.ts @@ -6,38 +6,73 @@ import { Polygon } from "./bodies/polygon"; /** * replace body with array of related convex polygons */ -export declare function ensureConvex(body: TBody): (TBody | SATPolygon)[]; -export declare function polygonInCircle(polygon: Polygon, circle: Pick): boolean; -export declare function pointInPolygon(point: Vector, polygon: Polygon): boolean; -export declare function polygonInPolygon(polygonA: Polygon, polygonB: Polygon): boolean; +export declare function ensureConvex< + TBody extends Body = Circle | Point | Polygon, +>(body: TBody): (TBody | SATPolygon)[]; +export declare function polygonInCircle( + polygon: Polygon, + circle: Pick, +): boolean; +export declare function pointInPolygon( + point: Vector, + polygon: Polygon, +): boolean; +export declare function polygonInPolygon( + polygonA: Polygon, + polygonB: Polygon, +): boolean; /** * https://stackoverflow.com/a/68197894/1749528 */ -export declare function pointOnCircle(point: Vector, circle: Pick): boolean; +export declare function pointOnCircle( + point: Vector, + circle: Pick, +): boolean; /** * https://stackoverflow.com/a/68197894/1749528 */ -export declare function circleInCircle(bodyA: Pick, bodyB: Pick): boolean; +export declare function circleInCircle( + bodyA: Pick, + bodyB: Pick, +): boolean; /** * https://stackoverflow.com/a/68197894/1749528 */ -export declare function circleInPolygon(circle: Pick, polygon: Polygon): boolean; +export declare function circleInPolygon( + circle: Pick, + polygon: Polygon, +): boolean; /** * https://stackoverflow.com/a/68197894/1749528 */ -export declare function circleOutsidePolygon(circle: Pick, polygon: Polygon): boolean; +export declare function circleOutsidePolygon( + circle: Pick, + polygon: Polygon, +): boolean; /** * https://stackoverflow.com/a/37225895/1749528 */ -export declare function intersectLineCircle(line: Pick, { pos, r }: Pick): Vector[]; +export declare function intersectLineCircle( + line: Pick, + { pos, r }: Pick, +): Vector[]; /** * faster implementation of intersectLineLine * https://stackoverflow.com/a/16725715/1749528 */ -export declare function intersectLineLineFast(line1: Pick, line2: Pick): boolean; +export declare function intersectLineLineFast( + line1: Pick, + line2: Pick, +): boolean; /** * returns the point of intersection * https://stackoverflow.com/a/24392281/1749528 */ -export declare function intersectLineLine(line1: Pick, line2: Pick): Vector | null; -export declare function intersectLinePolygon(line: Line, polygon: Polygon): Vector[]; +export declare function intersectLineLine( + line1: Pick, + line2: Pick, +): Vector | null; +export declare function intersectLinePolygon( + line: Line, + polygon: Polygon, +): Vector[]; diff --git a/dist/intersect.js b/dist/intersect.js index 965bc574..2b686bde 100644 --- a/dist/intersect.js +++ b/dist/intersect.js @@ -19,198 +19,227 @@ const sat_1 = require("sat"); * replace body with array of related convex polygons */ function ensureConvex(body) { - if (body.isConvex || body.typeGroup !== model_1.BodyGroup.Polygon) { - return [body]; - } - return body.convexPolygons; + if (body.isConvex || body.typeGroup !== model_1.BodyGroup.Polygon) { + return [body]; + } + return body.convexPolygons; } function polygonInCircle(polygon, circle) { - return (0, optimized_1.every)(polygon.calcPoints, p => (0, sat_1.pointInCircle)({ x: p.x + polygon.pos.x, y: p.y + polygon.pos.y }, circle)); + return (0, optimized_1.every)(polygon.calcPoints, (p) => + (0, sat_1.pointInCircle)( + { x: p.x + polygon.pos.x, y: p.y + polygon.pos.y }, + circle, + ), + ); } function pointInPolygon(point, polygon) { - return (0, optimized_1.some)(ensureConvex(polygon), convex => (0, sat_1.pointInPolygon)(point, convex)); + return (0, optimized_1.some)(ensureConvex(polygon), (convex) => + (0, sat_1.pointInPolygon)(point, convex), + ); } function polygonInPolygon(polygonA, polygonB) { - return (0, optimized_1.every)(polygonA.calcPoints, point => pointInPolygon({ x: point.x + polygonA.pos.x, y: point.y + polygonA.pos.y }, polygonB)); + return (0, optimized_1.every)(polygonA.calcPoints, (point) => + pointInPolygon( + { x: point.x + polygonA.pos.x, y: point.y + polygonA.pos.y }, + polygonB, + ), + ); } /** * https://stackoverflow.com/a/68197894/1749528 */ function pointOnCircle(point, circle) { - return ((point.x - circle.pos.x) * (point.x - circle.pos.x) + - (point.y - circle.pos.y) * (point.y - circle.pos.y) === - circle.r * circle.r); + return ( + (point.x - circle.pos.x) * (point.x - circle.pos.x) + + (point.y - circle.pos.y) * (point.y - circle.pos.y) === + circle.r * circle.r + ); } /** * https://stackoverflow.com/a/68197894/1749528 */ function circleInCircle(bodyA, bodyB) { - const x1 = bodyA.pos.x; - const y1 = bodyA.pos.y; - const x2 = bodyB.pos.x; - const y2 = bodyB.pos.y; - const r1 = bodyA.r; - const r2 = bodyB.r; - const distSq = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); - return distSq + r2 === r1 || distSq + r2 < r1; + const x1 = bodyA.pos.x; + const y1 = bodyA.pos.y; + const x2 = bodyB.pos.x; + const y2 = bodyB.pos.y; + const r1 = bodyA.r; + const r2 = bodyB.r; + const distSq = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); + return distSq + r2 === r1 || distSq + r2 < r1; } /** * https://stackoverflow.com/a/68197894/1749528 */ function circleInPolygon(circle, polygon) { - // Circle with radius 0 isn't a circle - if (circle.r === 0) { - return false; - } - // If the center of the circle is not within the polygon, - // then the circle may overlap, but it'll never be "contained" - // so return false - if (!pointInPolygon(circle.pos, polygon)) { - return false; - } - // Necessary add polygon pos to points - const points = (0, optimized_1.map)(polygon.calcPoints, ({ x, y }) => ({ - x: x + polygon.pos.x, - y: y + polygon.pos.y - })); - // If the center of the circle is within the polygon, - // the circle is not outside of the polygon completely. - // so return false. - if ((0, optimized_1.some)(points, point => (0, sat_1.pointInCircle)(point, circle))) { - return false; - } - // If any line-segment of the polygon intersects the circle, - // the circle is not "contained" - // so return false - if ((0, optimized_1.some)(points, (end, index) => { - const start = index - ? points[index - 1] - : points[points.length - 1]; - return intersectLineCircle({ start, end }, circle).length > 0; - })) { - return false; - } - return true; + // Circle with radius 0 isn't a circle + if (circle.r === 0) { + return false; + } + // If the center of the circle is not within the polygon, + // then the circle may overlap, but it'll never be "contained" + // so return false + if (!pointInPolygon(circle.pos, polygon)) { + return false; + } + // Necessary add polygon pos to points + const points = (0, optimized_1.map)(polygon.calcPoints, ({ x, y }) => ({ + x: x + polygon.pos.x, + y: y + polygon.pos.y, + })); + // If the center of the circle is within the polygon, + // the circle is not outside of the polygon completely. + // so return false. + if ( + (0, optimized_1.some)(points, (point) => + (0, sat_1.pointInCircle)(point, circle), + ) + ) { + return false; + } + // If any line-segment of the polygon intersects the circle, + // the circle is not "contained" + // so return false + if ( + (0, optimized_1.some)(points, (end, index) => { + const start = index ? points[index - 1] : points[points.length - 1]; + return intersectLineCircle({ start, end }, circle).length > 0; + }) + ) { + return false; + } + return true; } /** * https://stackoverflow.com/a/68197894/1749528 */ function circleOutsidePolygon(circle, polygon) { - // Circle with radius 0 isn't a circle - if (circle.r === 0) { - return false; - } - // If the center of the circle is within the polygon, - // the circle is not outside of the polygon completely. - // so return false. - if (pointInPolygon(circle.pos, polygon)) { - return false; - } - // Necessary add polygon pos to points - const points = (0, optimized_1.map)(polygon.calcPoints, ({ x, y }) => ({ - x: x + polygon.pos.x, - y: y + polygon.pos.y - })); - // If the center of the circle is within the polygon, - // the circle is not outside of the polygon completely. - // so return false. - if ((0, optimized_1.some)(points, point => (0, sat_1.pointInCircle)(point, circle) || pointOnCircle(point, circle))) { - return false; - } - // If any line-segment of the polygon intersects the circle, - // the circle is not "contained" - // so return false - if ((0, optimized_1.some)(points, (end, index) => { - const start = index - ? points[index - 1] - : points[points.length - 1]; - return intersectLineCircle({ start, end }, circle).length > 0; - })) { - return false; - } - return true; + // Circle with radius 0 isn't a circle + if (circle.r === 0) { + return false; + } + // If the center of the circle is within the polygon, + // the circle is not outside of the polygon completely. + // so return false. + if (pointInPolygon(circle.pos, polygon)) { + return false; + } + // Necessary add polygon pos to points + const points = (0, optimized_1.map)(polygon.calcPoints, ({ x, y }) => ({ + x: x + polygon.pos.x, + y: y + polygon.pos.y, + })); + // If the center of the circle is within the polygon, + // the circle is not outside of the polygon completely. + // so return false. + if ( + (0, optimized_1.some)( + points, + (point) => + (0, sat_1.pointInCircle)(point, circle) || pointOnCircle(point, circle), + ) + ) { + return false; + } + // If any line-segment of the polygon intersects the circle, + // the circle is not "contained" + // so return false + if ( + (0, optimized_1.some)(points, (end, index) => { + const start = index ? points[index - 1] : points[points.length - 1]; + return intersectLineCircle({ start, end }, circle).length > 0; + }) + ) { + return false; + } + return true; } /** * https://stackoverflow.com/a/37225895/1749528 */ function intersectLineCircle(line, { pos, r }) { - const v1 = { x: line.end.x - line.start.x, y: line.end.y - line.start.y }; - const v2 = { x: line.start.x - pos.x, y: line.start.y - pos.y }; - const b = (v1.x * v2.x + v1.y * v2.y) * -2; - const c = (v1.x * v1.x + v1.y * v1.y) * 2; - const d = Math.sqrt(b * b - (v2.x * v2.x + v2.y * v2.y - r * r) * c * 2); - if (isNaN(d)) { - // no intercept - return []; - } - const u1 = (b - d) / c; // these represent the unit distance of point one and two on the line - const u2 = (b + d) / c; - const results = []; // return array - if (u1 <= 1 && u1 >= 0) { - // add point if on the line segment - results.push({ x: line.start.x + v1.x * u1, y: line.start.y + v1.y * u1 }); - } - if (u2 <= 1 && u2 >= 0) { - // second add point if on the line segment - results.push({ x: line.start.x + v1.x * u2, y: line.start.y + v1.y * u2 }); - } - return results; + const v1 = { x: line.end.x - line.start.x, y: line.end.y - line.start.y }; + const v2 = { x: line.start.x - pos.x, y: line.start.y - pos.y }; + const b = (v1.x * v2.x + v1.y * v2.y) * -2; + const c = (v1.x * v1.x + v1.y * v1.y) * 2; + const d = Math.sqrt(b * b - (v2.x * v2.x + v2.y * v2.y - r * r) * c * 2); + if (isNaN(d)) { + // no intercept + return []; + } + const u1 = (b - d) / c; // these represent the unit distance of point one and two on the line + const u2 = (b + d) / c; + const results = []; // return array + if (u1 <= 1 && u1 >= 0) { + // add point if on the line segment + results.push({ x: line.start.x + v1.x * u1, y: line.start.y + v1.y * u1 }); + } + if (u2 <= 1 && u2 >= 0) { + // second add point if on the line segment + results.push({ x: line.start.x + v1.x * u2, y: line.start.y + v1.y * u2 }); + } + return results; } /** * helper for intersectLineLineFast */ function isTurn(point1, point2, point3) { - const A = (point3.x - point1.x) * (point2.y - point1.y); - const B = (point2.x - point1.x) * (point3.y - point1.y); - return A > B + Number.EPSILON ? 1 : A + Number.EPSILON < B ? -1 : 0; + const A = (point3.x - point1.x) * (point2.y - point1.y); + const B = (point2.x - point1.x) * (point3.y - point1.y); + return A > B + Number.EPSILON ? 1 : A + Number.EPSILON < B ? -1 : 0; } /** * faster implementation of intersectLineLine * https://stackoverflow.com/a/16725715/1749528 */ function intersectLineLineFast(line1, line2) { - return (isTurn(line1.start, line2.start, line2.end) !== - isTurn(line1.end, line2.start, line2.end) && - isTurn(line1.start, line1.end, line2.start) !== - isTurn(line1.start, line1.end, line2.end)); + return ( + isTurn(line1.start, line2.start, line2.end) !== + isTurn(line1.end, line2.start, line2.end) && + isTurn(line1.start, line1.end, line2.start) !== + isTurn(line1.start, line1.end, line2.end) + ); } /** * returns the point of intersection * https://stackoverflow.com/a/24392281/1749528 */ function intersectLineLine(line1, line2) { - const dX = line1.end.x - line1.start.x; - const dY = line1.end.y - line1.start.y; - const determinant = dX * (line2.end.y - line2.start.y) - (line2.end.x - line2.start.x) * dY; - if (determinant === 0) { - return null; - } - const lambda = ((line2.end.y - line2.start.y) * (line2.end.x - line1.start.x) + - (line2.start.x - line2.end.x) * (line2.end.y - line1.start.y)) / - determinant; - const gamma = ((line1.start.y - line1.end.y) * (line2.end.x - line1.start.x) + - dX * (line2.end.y - line1.start.y)) / - determinant; - // check if there is an intersection - if (!(lambda >= 0 && lambda <= 1) || !(gamma >= 0 && gamma <= 1)) { - return null; - } - return { x: line1.start.x + lambda * dX, y: line1.start.y + lambda * dY }; + const dX = line1.end.x - line1.start.x; + const dY = line1.end.y - line1.start.y; + const determinant = + dX * (line2.end.y - line2.start.y) - (line2.end.x - line2.start.x) * dY; + if (determinant === 0) { + return null; + } + const lambda = + ((line2.end.y - line2.start.y) * (line2.end.x - line1.start.x) + + (line2.start.x - line2.end.x) * (line2.end.y - line1.start.y)) / + determinant; + const gamma = + ((line1.start.y - line1.end.y) * (line2.end.x - line1.start.x) + + dX * (line2.end.y - line1.start.y)) / + determinant; + // check if there is an intersection + if (!(lambda >= 0 && lambda <= 1) || !(gamma >= 0 && gamma <= 1)) { + return null; + } + return { x: line1.start.x + lambda * dX, y: line1.start.y + lambda * dY }; } function intersectLinePolygon(line, polygon) { - const results = []; - (0, optimized_1.forEach)(polygon.calcPoints, (to, index) => { - const from = index - ? polygon.calcPoints[index - 1] - : polygon.calcPoints[polygon.calcPoints.length - 1]; - const side = { - start: { x: from.x + polygon.pos.x, y: from.y + polygon.pos.y }, - end: { x: to.x + polygon.pos.x, y: to.y + polygon.pos.y } - }; - const hit = intersectLineLine(line, side); - if (hit) { - results.push(hit); - } - }); - return results; + const results = []; + (0, optimized_1.forEach)(polygon.calcPoints, (to, index) => { + const from = index + ? polygon.calcPoints[index - 1] + : polygon.calcPoints[polygon.calcPoints.length - 1]; + const side = { + start: { x: from.x + polygon.pos.x, y: from.y + polygon.pos.y }, + end: { x: to.x + polygon.pos.x, y: to.y + polygon.pos.y }, + }; + const hit = intersectLineLine(line, side); + if (hit) { + results.push(hit); + } + }); + return results; } diff --git a/dist/model.d.ts b/dist/model.d.ts index c2cd5063..32c698d0 100644 --- a/dist/model.d.ts +++ b/dist/model.d.ts @@ -1,4 +1,9 @@ -import { Circle as SATCircle, Polygon as SATPolygon, Response, Vector as SATVector } from "sat"; +import { + Circle as SATCircle, + Polygon as SATPolygon, + Response, + Vector as SATVector, +} from "sat"; import { System } from "./system"; import { Box } from "./bodies/box"; import { Circle } from "./bodies/circle"; @@ -7,12 +12,16 @@ import { Line } from "./bodies/line"; import { Point } from "./bodies/point"; import { Polygon } from "./bodies/polygon"; import RBush from "./external/rbush"; -export { Polygon as DecompPolygon, Point as DecompPoint, isSimple } from "poly-decomp-es"; +export { + Polygon as DecompPolygon, + Point as DecompPoint, + isSimple, +} from "poly-decomp-es"; export interface BBox { - minX: number; - minY: number; - maxX: number; - maxY: number; + minX: number; + minY: number; + maxX: number; + maxY: number; } export { RBush, Response, SATVector, SATPolygon, SATCircle }; export type CollisionCallback = (response: Response) => boolean | void; @@ -20,101 +29,101 @@ export type CollisionCallback = (response: Response) => boolean | void; * types */ export declare enum BodyType { - Ellipse = "Ellipse", - Circle = "Circle", - Polygon = "Polygon", - Box = "Box", - Line = "Line", - Point = "Point" + Ellipse = "Ellipse", + Circle = "Circle", + Polygon = "Polygon", + Box = "Box", + Line = "Line", + Point = "Point", } /** * for groups */ export declare enum BodyGroup { - Ellipse = 32, - Circle = 16, - Polygon = 8, - Box = 4, - Line = 2, - Point = 1 + Ellipse = 32, + Circle = 16, + Polygon = 8, + Box = 4, + Line = 2, + Point = 1, } /** * body with children (rbush) */ export type Leaf = TBody & { - children?: Leaf[]; + children?: Leaf[]; }; /** * rbush data */ export interface ChildrenData { - children: Leaf[]; + children: Leaf[]; } /** * for use of private function of sat.js */ export interface Data { - data: ChildrenData; + data: ChildrenData; } /** * BodyOptions for body creation */ export interface BodyOptions { - /** - * system.separate() doesn't move this body - */ - isStatic?: boolean; - /** - * system.separate() doesn't trigger collision of this body - */ - isTrigger?: boolean; - /** - * is body offset centered for rotation purpouses - */ - isCentered?: boolean; - /** - * body angle in radians use deg2rad to convert - */ - angle?: number; - /** - * BHV padding for bounding box, preventing costly updates - */ - padding?: number; - /** - * group for collision filtering - */ - group?: number; + /** + * system.separate() doesn't move this body + */ + isStatic?: boolean; + /** + * system.separate() doesn't trigger collision of this body + */ + isTrigger?: boolean; + /** + * is body offset centered for rotation purpouses + */ + isCentered?: boolean; + /** + * body angle in radians use deg2rad to convert + */ + angle?: number; + /** + * BHV padding for bounding box, preventing costly updates + */ + padding?: number; + /** + * group for collision filtering + */ + group?: number; } /** * system.raycast(from, to) result */ export interface RaycastHit { - point: Vector; - body: TBody; + point: Vector; + body: TBody; } /** * potential vector */ export interface PotentialVector { - x?: number; - y?: number; + x?: number; + y?: number; } /** * x, y vector */ export interface Vector extends PotentialVector { - x: number; - y: number; + x: number; + y: number; } /** * for use of private function of sat.js */ export interface GetAABBAsBox { - getAABBAsBox(): { - pos: Vector; - w: number; - h: number; - }; + getAABBAsBox(): { + pos: Vector; + w: number; + h: number; + }; } /** * generic body union type @@ -124,75 +133,85 @@ export type Body = Point | Line | Ellipse | Circle | Box | Polygon; * each body contains those regardless of type */ export interface BodyProps extends Required { - /** - * type of body - */ - readonly type: BodyType; - /** - * faster for comparision, inner, type of body as number - */ - readonly typeGroup: BodyGroup; - /** - * flag to show is it a convex body or non convex polygon - */ - isConvex: boolean; - /** - * bounding box cache, without padding - */ - bbox: BBox; - /** - * each body may have offset from center - */ - offset: SATVector; - /** - * collisions system reference - */ - system?: System; - /** - * was the body modified and needs update in the next checkCollision - */ - dirty: boolean; - /** - * scale getter (x) - */ - get scaleX(): number; - /** - * scale getter (y = x for Circle) - */ - get scaleY(): number; - /** - * update position BY MOVING FORWARD IN ANGLE DIRECTION - */ - move(speed: number, updateNow?: boolean): Circle | SATPolygon; - /** - * update position BY TELEPORTING - */ - setPosition(x: number, y: number, updateNow?: boolean): Circle | SATPolygon; - /** - * for setting scale - */ - setScale(x: number, y: number, updateNow?: boolean): Circle | SATPolygon; - /** - * for setting angle - */ - setAngle(angle: number, updateNow?: boolean): Circle | SATPolygon; - /** - * for setting offset from center - */ - setOffset(offset: Vector, updateNow?: boolean): Circle | SATPolygon; - /** - * draw the bounding box - */ - drawBVH(context: CanvasRenderingContext2D): void; - /** - * draw the collider - */ - draw(context: CanvasRenderingContext2D): void; - /** - * return bounding box without padding - */ - getAABBAsBBox(): BBox; + /** + * type of body + */ + readonly type: BodyType; + /** + * faster for comparision, inner, type of body as number + */ + readonly typeGroup: BodyGroup; + /** + * flag to show is it a convex body or non convex polygon + */ + isConvex: boolean; + /** + * bounding box cache, without padding + */ + bbox: BBox; + /** + * each body may have offset from center + */ + offset: SATVector; + /** + * collisions system reference + */ + system?: System; + /** + * was the body modified and needs update in the next checkCollision + */ + dirty: boolean; + /** + * scale getter (x) + */ + get scaleX(): number; + /** + * scale getter (y = x for Circle) + */ + get scaleY(): number; + /** + * update position BY MOVING FORWARD IN ANGLE DIRECTION + */ + move(speed: number, updateNow?: boolean): Circle | SATPolygon; + /** + * update position BY TELEPORTING + */ + setPosition(x: number, y: number, updateNow?: boolean): Circle | SATPolygon; + /** + * for setting scale + */ + setScale(x: number, y: number, updateNow?: boolean): Circle | SATPolygon; + /** + * for setting angle + */ + setAngle(angle: number, updateNow?: boolean): Circle | SATPolygon; + /** + * for setting offset from center + */ + setOffset(offset: Vector, updateNow?: boolean): Circle | SATPolygon; + /** + * draw the bounding box + */ + drawBVH(context: CanvasRenderingContext2D): void; + /** + * draw the collider + */ + draw(context: CanvasRenderingContext2D): void; + /** + * return bounding box without padding + */ + getAABBAsBBox(): BBox; } -export type SATTest = (bodyA: T, bodyB: Y, response: Response) => boolean; -export type InTest = (bodyA: TBody, bodyB: TBody) => boolean; -export type TraverseFunction = (child: Leaf, children: Leaf[], index: number) => boolean | void; +export type SATTest< + T extends {} = Circle | Polygon | SATPolygon, + Y extends {} = Circle | Polygon | SATPolygon, +> = (bodyA: T, bodyB: Y, response: Response) => boolean; +export type InTest = ( + bodyA: TBody, + bodyB: TBody, +) => boolean; +export type TraverseFunction = ( + child: Leaf, + children: Leaf[], + index: number, +) => boolean | void; diff --git a/dist/model.js b/dist/model.js index c45d6360..10b86602 100644 --- a/dist/model.js +++ b/dist/model.js @@ -1,40 +1,75 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.BodyGroup = exports.BodyType = exports.SATCircle = exports.SATPolygon = exports.SATVector = exports.Response = exports.RBush = exports.isSimple = void 0; +exports.BodyGroup = + exports.BodyType = + exports.SATCircle = + exports.SATPolygon = + exports.SATVector = + exports.Response = + exports.RBush = + exports.isSimple = + void 0; const sat_1 = require("sat"); -Object.defineProperty(exports, "SATCircle", { enumerable: true, get: function () { return sat_1.Circle; } }); -Object.defineProperty(exports, "SATPolygon", { enumerable: true, get: function () { return sat_1.Polygon; } }); -Object.defineProperty(exports, "Response", { enumerable: true, get: function () { return sat_1.Response; } }); -Object.defineProperty(exports, "SATVector", { enumerable: true, get: function () { return sat_1.Vector; } }); +Object.defineProperty(exports, "SATCircle", { + enumerable: true, + get: function () { + return sat_1.Circle; + }, +}); +Object.defineProperty(exports, "SATPolygon", { + enumerable: true, + get: function () { + return sat_1.Polygon; + }, +}); +Object.defineProperty(exports, "Response", { + enumerable: true, + get: function () { + return sat_1.Response; + }, +}); +Object.defineProperty(exports, "SATVector", { + enumerable: true, + get: function () { + return sat_1.Vector; + }, +}); // version 4.0.0 1=1 copy const rbush_1 = __importDefault(require("./external/rbush")); exports.RBush = rbush_1.default; var poly_decomp_es_1 = require("poly-decomp-es"); -Object.defineProperty(exports, "isSimple", { enumerable: true, get: function () { return poly_decomp_es_1.isSimple; } }); +Object.defineProperty(exports, "isSimple", { + enumerable: true, + get: function () { + return poly_decomp_es_1.isSimple; + }, +}); /** * types */ var BodyType; (function (BodyType) { - BodyType["Ellipse"] = "Ellipse"; - BodyType["Circle"] = "Circle"; - BodyType["Polygon"] = "Polygon"; - BodyType["Box"] = "Box"; - BodyType["Line"] = "Line"; - BodyType["Point"] = "Point"; + BodyType["Ellipse"] = "Ellipse"; + BodyType["Circle"] = "Circle"; + BodyType["Polygon"] = "Polygon"; + BodyType["Box"] = "Box"; + BodyType["Line"] = "Line"; + BodyType["Point"] = "Point"; })(BodyType || (exports.BodyType = BodyType = {})); /** * for groups */ var BodyGroup; (function (BodyGroup) { - BodyGroup[BodyGroup["Ellipse"] = 32] = "Ellipse"; - BodyGroup[BodyGroup["Circle"] = 16] = "Circle"; - BodyGroup[BodyGroup["Polygon"] = 8] = "Polygon"; - BodyGroup[BodyGroup["Box"] = 4] = "Box"; - BodyGroup[BodyGroup["Line"] = 2] = "Line"; - BodyGroup[BodyGroup["Point"] = 1] = "Point"; + BodyGroup[(BodyGroup["Ellipse"] = 32)] = "Ellipse"; + BodyGroup[(BodyGroup["Circle"] = 16)] = "Circle"; + BodyGroup[(BodyGroup["Polygon"] = 8)] = "Polygon"; + BodyGroup[(BodyGroup["Box"] = 4)] = "Box"; + BodyGroup[(BodyGroup["Line"] = 2)] = "Line"; + BodyGroup[(BodyGroup["Point"] = 1)] = "Point"; })(BodyGroup || (exports.BodyGroup = BodyGroup = {})); diff --git a/dist/optimized.d.ts b/dist/optimized.d.ts index 7620176b..4fffe449 100644 --- a/dist/optimized.d.ts +++ b/dist/optimized.d.ts @@ -3,28 +3,43 @@ * * basic benchmark: https://jsbench.me/urle772xdn */ -export declare const forEach: (array: T[], callback: (item: T, index: number) => void) => void; +export declare const forEach: ( + array: T[], + callback: (item: T, index: number) => void, +) => void; /** * 20-90% faster than built-in Array.some function. * * basic benchmark: https://jsbench.me/l0le7bnnsq */ -export declare const some: (array: T[], callback: (item: T, index: number) => unknown) => boolean; +export declare const some: ( + array: T[], + callback: (item: T, index: number) => unknown, +) => boolean; /** * 20-40% faster than built-in Array.every function. * * basic benchmark: https://jsbench.me/unle7da29v */ -export declare const every: (array: T[], callback: (item: T, index: number) => unknown) => boolean; +export declare const every: ( + array: T[], + callback: (item: T, index: number) => unknown, +) => boolean; /** * 20-60% faster than built-in Array.filter function. * * basic benchmark: https://jsbench.me/o1le77ev4l */ -export declare const filter: (array: T[], callback: (item: T, index: number) => unknown) => T[]; +export declare const filter: ( + array: T[], + callback: (item: T, index: number) => unknown, +) => T[]; /** * 20-70% faster than built-in Array.map * * basic benchmark: https://jsbench.me/oyle77vbpc */ -export declare const map: (array: T[], callback: (item: T, index: number) => Y) => Y[]; +export declare const map: ( + array: T[], + callback: (item: T, index: number) => Y, +) => Y[]; diff --git a/dist/optimized.js b/dist/optimized.js index a9ed3013..75c3b991 100644 --- a/dist/optimized.js +++ b/dist/optimized.js @@ -1,15 +1,20 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.map = exports.filter = exports.every = exports.some = exports.forEach = void 0; +exports.map = + exports.filter = + exports.every = + exports.some = + exports.forEach = + void 0; /** * 40-90% faster than built-in Array.forEach function. * * basic benchmark: https://jsbench.me/urle772xdn */ const forEach = (array, callback) => { - for (let i = 0, l = array.length; i < l; i++) { - callback(array[i], i); - } + for (let i = 0, l = array.length; i < l; i++) { + callback(array[i], i); + } }; exports.forEach = forEach; /** @@ -18,12 +23,12 @@ exports.forEach = forEach; * basic benchmark: https://jsbench.me/l0le7bnnsq */ const some = (array, callback) => { - for (let i = 0, l = array.length; i < l; i++) { - if (callback(array[i], i)) { - return true; - } + for (let i = 0, l = array.length; i < l; i++) { + if (callback(array[i], i)) { + return true; } - return false; + } + return false; }; exports.some = some; /** @@ -32,12 +37,12 @@ exports.some = some; * basic benchmark: https://jsbench.me/unle7da29v */ const every = (array, callback) => { - for (let i = 0, l = array.length; i < l; i++) { - if (!callback(array[i], i)) { - return false; - } + for (let i = 0, l = array.length; i < l; i++) { + if (!callback(array[i], i)) { + return false; } - return true; + } + return true; }; exports.every = every; /** @@ -46,14 +51,14 @@ exports.every = every; * basic benchmark: https://jsbench.me/o1le77ev4l */ const filter = (array, callback) => { - const output = []; - for (let i = 0, l = array.length; i < l; i++) { - const item = array[i]; - if (callback(item, i)) { - output.push(item); - } + const output = []; + for (let i = 0, l = array.length; i < l; i++) { + const item = array[i]; + if (callback(item, i)) { + output.push(item); } - return output; + } + return output; }; exports.filter = filter; /** @@ -62,11 +67,11 @@ exports.filter = filter; * basic benchmark: https://jsbench.me/oyle77vbpc */ const map = (array, callback) => { - const l = array.length; - const output = new Array(l); - for (let i = 0; i < l; i++) { - output[i] = callback(array[i], i); - } - return output; + const l = array.length; + const output = new Array(l); + for (let i = 0; i < l; i++) { + output[i] = callback(array[i], i); + } + return output; }; exports.map = map; diff --git a/dist/system.d.ts b/dist/system.d.ts index 2c79be1c..751ea7b6 100644 --- a/dist/system.d.ts +++ b/dist/system.d.ts @@ -1,49 +1,70 @@ -import { BBox, Body, CollisionCallback, RaycastHit, Response, Vector } from "./model"; +import { + BBox, + Body, + CollisionCallback, + RaycastHit, + Response, + Vector, +} from "./model"; import { BaseSystem } from "./base-system"; import { Line } from "./bodies/line"; /** * collision system */ -export declare class System extends BaseSystem { - /** - * the last collision result - */ - response: Response; - /** - * for raycasting - */ - protected ray: Line; - /** - * re-insert body into collision tree and update its bbox - * every body can be part of only one system - */ - insert(body: TBody): this; - /** - * separate (move away) bodies - */ - separate(): void; - /** - * separate (move away) 1 body - */ - separateBody(body: TBody): void; - /** - * check one body collisions with callback - */ - checkOne(body: TBody, callback?: CollisionCallback, response?: Response): boolean; - /** - * check all bodies collisions in area with callback - */ - checkArea(area: BBox, callback?: CollisionCallback, response?: Response): boolean; - /** - * check all bodies collisions with callback - */ - checkAll(callback?: CollisionCallback, response?: Response): boolean; - /** - * check do 2 objects collide - */ - checkCollision(bodyA: TBody, bodyB: TBody, response?: Response): boolean; - /** - * raycast to get collider of ray from start to end - */ - raycast(start: Vector, end: Vector, allow?: (body: TBody, ray: TBody) => boolean): RaycastHit | null; +export declare class System< + TBody extends Body = Body, +> extends BaseSystem { + /** + * the last collision result + */ + response: Response; + /** + * for raycasting + */ + protected ray: Line; + /** + * re-insert body into collision tree and update its bbox + * every body can be part of only one system + */ + insert(body: TBody): this; + /** + * separate (move away) bodies + */ + separate(): void; + /** + * separate (move away) 1 body + */ + separateBody(body: TBody): void; + /** + * check one body collisions with callback + */ + checkOne( + body: TBody, + callback?: CollisionCallback, + response?: Response, + ): boolean; + /** + * check all bodies collisions in area with callback + */ + checkArea( + area: BBox, + callback?: CollisionCallback, + response?: Response, + ): boolean; + /** + * check all bodies collisions with callback + */ + checkAll(callback?: CollisionCallback, response?: Response): boolean; + /** + * check do 2 objects collide + */ + checkCollision(bodyA: TBody, bodyB: TBody, response?: Response): boolean; + /** + * raycast to get collider of ray from start to end + */ + raycast( + start: Vector, + end: Vector, + allow?: (body: TBody, ray: TBody) => boolean, + ): RaycastHit | null; } diff --git a/dist/system.js b/dist/system.js index e3509a6f..05c16231 100644 --- a/dist/system.js +++ b/dist/system.js @@ -11,164 +11,168 @@ const line_1 = require("./bodies/line"); * collision system */ class System extends base_system_1.BaseSystem { - constructor() { - super(...arguments); - /** - * the last collision result - */ - this.response = new model_1.Response(); - } - /** - * re-insert body into collision tree and update its bbox - * every body can be part of only one system - */ - insert(body) { - const insertResult = super.insert(body); - // set system for later body.system.updateBody(body) - body.system = this; - return insertResult; - } + constructor() { + super(...arguments); /** - * separate (move away) bodies + * the last collision result */ - separate() { - (0, optimized_1.forEach)(this.all(), (body) => { - this.separateBody(body); - }); + this.response = new model_1.Response(); + } + /** + * re-insert body into collision tree and update its bbox + * every body can be part of only one system + */ + insert(body) { + const insertResult = super.insert(body); + // set system for later body.system.updateBody(body) + body.system = this; + return insertResult; + } + /** + * separate (move away) bodies + */ + separate() { + (0, optimized_1.forEach)(this.all(), (body) => { + this.separateBody(body); + }); + } + /** + * separate (move away) 1 body + */ + separateBody(body) { + if (body.isStatic || body.isTrigger) { + return; } - /** - * separate (move away) 1 body - */ - separateBody(body) { - if (body.isStatic || body.isTrigger) { - return; - } - const offsets = { x: 0, y: 0 }; - const addOffsets = ({ overlapV: { x, y } }) => { - offsets.x += x; - offsets.y += y; - }; - this.checkOne(body, addOffsets); - if (offsets.x || offsets.y) { - body.setPosition(body.x - offsets.x, body.y - offsets.y); - } + const offsets = { x: 0, y: 0 }; + const addOffsets = ({ overlapV: { x, y } }) => { + offsets.x += x; + offsets.y += y; + }; + this.checkOne(body, addOffsets); + if (offsets.x || offsets.y) { + body.setPosition(body.x - offsets.x, body.y - offsets.y); } - /** - * check one body collisions with callback - */ - checkOne(body, callback = utils_1.returnTrue, response = this.response) { - // no need to check static body collision - if (body.isStatic) { - return false; - } - const bodies = this.search(body); - const checkCollision = (candidate) => { - if (candidate !== body && - this.checkCollision(body, candidate, response)) { - return callback(response); - } - }; - return (0, optimized_1.some)(bodies, checkCollision); + } + /** + * check one body collisions with callback + */ + checkOne(body, callback = utils_1.returnTrue, response = this.response) { + // no need to check static body collision + if (body.isStatic) { + return false; } - /** - * check all bodies collisions in area with callback - */ - checkArea(area, callback = utils_1.returnTrue, response = this.response) { - const checkOne = (body) => { - return this.checkOne(body, callback, response); - }; - return (0, optimized_1.some)(this.search(area), checkOne); + const bodies = this.search(body); + const checkCollision = (candidate) => { + if ( + candidate !== body && + this.checkCollision(body, candidate, response) + ) { + return callback(response); + } + }; + return (0, optimized_1.some)(bodies, checkCollision); + } + /** + * check all bodies collisions in area with callback + */ + checkArea(area, callback = utils_1.returnTrue, response = this.response) { + const checkOne = (body) => { + return this.checkOne(body, callback, response); + }; + return (0, optimized_1.some)(this.search(area), checkOne); + } + /** + * check all bodies collisions with callback + */ + checkAll(callback = utils_1.returnTrue, response = this.response) { + const checkOne = (body) => { + return this.checkOne(body, callback, response); + }; + return (0, optimized_1.some)(this.all(), checkOne); + } + /** + * check do 2 objects collide + */ + checkCollision(bodyA, bodyB, response = this.response) { + const { bbox: bboxA } = bodyA; + const { bbox: bboxB } = bodyB; + // assess the bodies real aabb without padding + if ( + !(0, utils_1.canInteract)(bodyA, bodyB) || + !bboxA || + !bboxB || + (0, utils_1.notIntersectAABB)(bboxA, bboxB) + ) { + return false; } - /** - * check all bodies collisions with callback - */ - checkAll(callback = utils_1.returnTrue, response = this.response) { - const checkOne = (body) => { - return this.checkOne(body, callback, response); - }; - return (0, optimized_1.some)(this.all(), checkOne); + const sat = (0, utils_1.getSATTest)(bodyA, bodyB); + // 99% of cases + if (bodyA.isConvex && bodyB.isConvex) { + // always first clear response + response.clear(); + return sat(bodyA, bodyB, response); } - /** - * check do 2 objects collide - */ - checkCollision(bodyA, bodyB, response = this.response) { - const { bbox: bboxA } = bodyA; - const { bbox: bboxB } = bodyB; - // assess the bodies real aabb without padding - if (!(0, utils_1.canInteract)(bodyA, bodyB) || - !bboxA || - !bboxB || - (0, utils_1.notIntersectAABB)(bboxA, bboxB)) { - return false; - } - const sat = (0, utils_1.getSATTest)(bodyA, bodyB); - // 99% of cases - if (bodyA.isConvex && bodyB.isConvex) { - // always first clear response - response.clear(); - return sat(bodyA, bodyB, response); + // more complex (non convex) cases + const convexBodiesA = (0, intersect_1.ensureConvex)(bodyA); + const convexBodiesB = (0, intersect_1.ensureConvex)(bodyB); + let overlapX = 0; + let overlapY = 0; + let collided = false; + (0, optimized_1.forEach)(convexBodiesA, (convexBodyA) => { + (0, optimized_1.forEach)(convexBodiesB, (convexBodyB) => { + // always first clear response + response.clear(); + if (sat(convexBodyA, convexBodyB, response)) { + collided = true; + overlapX += response.overlapV.x; + overlapY += response.overlapV.y; } - // more complex (non convex) cases - const convexBodiesA = (0, intersect_1.ensureConvex)(bodyA); - const convexBodiesB = (0, intersect_1.ensureConvex)(bodyB); - let overlapX = 0; - let overlapY = 0; - let collided = false; - (0, optimized_1.forEach)(convexBodiesA, convexBodyA => { - (0, optimized_1.forEach)(convexBodiesB, convexBodyB => { - // always first clear response - response.clear(); - if (sat(convexBodyA, convexBodyB, response)) { - collided = true; - overlapX += response.overlapV.x; - overlapY += response.overlapV.y; - } - }); - }); - if (collided) { - const vector = new model_1.SATVector(overlapX, overlapY); - response.a = bodyA; - response.b = bodyB; - response.overlapV.x = overlapX; - response.overlapV.y = overlapY; - response.overlapN = vector.normalize(); - response.overlap = vector.len(); - response.aInB = (0, utils_1.checkAInB)(bodyA, bodyB); - response.bInA = (0, utils_1.checkAInB)(bodyB, bodyA); - } - return collided; + }); + }); + if (collided) { + const vector = new model_1.SATVector(overlapX, overlapY); + response.a = bodyA; + response.b = bodyB; + response.overlapV.x = overlapX; + response.overlapV.y = overlapY; + response.overlapN = vector.normalize(); + response.overlap = vector.len(); + response.aInB = (0, utils_1.checkAInB)(bodyA, bodyB); + response.bInA = (0, utils_1.checkAInB)(bodyB, bodyA); } - /** - * raycast to get collider of ray from start to end - */ - raycast(start, end, allow = utils_1.returnTrue) { - let minDistance = Infinity; - let result = null; - if (!this.ray) { - this.ray = new line_1.Line(start, end, { isTrigger: true }); - } - else { - this.ray.start = start; - this.ray.end = end; - } - this.insert(this.ray); - this.checkOne(this.ray, ({ b: body }) => { - if (!allow(body, this.ray)) { - return false; - } - const points = body.typeGroup === model_1.BodyGroup.Circle - ? (0, intersect_1.intersectLineCircle)(this.ray, body) - : (0, intersect_1.intersectLinePolygon)(this.ray, body); - (0, optimized_1.forEach)(points, (point) => { - const pointDistance = (0, utils_1.distance)(start, point); - if (pointDistance < minDistance) { - minDistance = pointDistance; - result = { point, body }; - } - }); - }); - this.remove(this.ray); - return result; + return collided; + } + /** + * raycast to get collider of ray from start to end + */ + raycast(start, end, allow = utils_1.returnTrue) { + let minDistance = Infinity; + let result = null; + if (!this.ray) { + this.ray = new line_1.Line(start, end, { isTrigger: true }); + } else { + this.ray.start = start; + this.ray.end = end; } + this.insert(this.ray); + this.checkOne(this.ray, ({ b: body }) => { + if (!allow(body, this.ray)) { + return false; + } + const points = + body.typeGroup === model_1.BodyGroup.Circle + ? (0, intersect_1.intersectLineCircle)(this.ray, body) + : (0, intersect_1.intersectLinePolygon)(this.ray, body); + (0, optimized_1.forEach)(points, (point) => { + const pointDistance = (0, utils_1.distance)(start, point); + if (pointDistance < minDistance) { + minDistance = pointDistance; + result = { point, body }; + } + }); + }); + this.remove(this.ray); + return result; + } } exports.System = System; diff --git a/dist/utils.d.ts b/dist/utils.d.ts index f8e1ed04..7821bcb0 100644 --- a/dist/utils.d.ts +++ b/dist/utils.d.ts @@ -1,4 +1,12 @@ -import { BBox, Body, BodyOptions, PotentialVector, SATPolygon, SATTest, Vector } from "./model"; +import { + BBox, + Body, + BodyOptions, + PotentialVector, + SATPolygon, + SATTest, + Vector, +} from "./model"; import { Response, Vector as SATVector } from "sat"; import { Point as DecompPoint } from "poly-decomp-es"; import { Polygon } from "./bodies/polygon"; @@ -15,7 +23,11 @@ export declare function rad2deg(radians: number): number; /** * creates ellipse-shaped polygon based on params */ -export declare function createEllipse(radiusX: number, radiusY?: number, step?: number): SATVector[]; +export declare function createEllipse( + radiusX: number, + radiusY?: number, + step?: number, +): SATVector[]; /** * creates box shaped polygon points */ @@ -27,7 +39,9 @@ export declare function ensureVectorPoint(point?: PotentialVector): SATVector; /** * ensure Vector points (for polygon) in counter-clockwise order */ -export declare function ensurePolygonPoints(points?: PotentialVector[]): SATVector[]; +export declare function ensurePolygonPoints( + points?: PotentialVector[], +): SATVector[]; /** * get distance between two Vector points */ @@ -75,7 +89,10 @@ export declare function mapArrayToVector([x, y]?: DecompPoint): Vector; /** * given 2 bodies calculate vector of bounce assuming equal mass and they are circles */ -export declare function getBounceDirection(body: Vector, collider: Vector): SATVector; +export declare function getBounceDirection( + body: Vector, + collider: Vector, +): SATVector; /** * returns correct sat.js testing function based on body types */ @@ -83,17 +100,35 @@ export declare function getSATTest(bodyA: Body, bodyB: Body): SATTest; /** * draws dashed line on canvas context */ -export declare function dashLineTo(context: CanvasRenderingContext2D, fromX: number, fromY: number, toX: number, toY: number, dash?: number, gap?: number): void; +export declare function dashLineTo( + context: CanvasRenderingContext2D, + fromX: number, + fromY: number, + toX: number, + toY: number, + dash?: number, + gap?: number, +): void; /** * draw polygon */ -export declare function drawPolygon(context: CanvasRenderingContext2D, { pos, calcPoints }: Pick & { +export declare function drawPolygon( + context: CanvasRenderingContext2D, + { + pos, + calcPoints, + }: Pick & { pos: Vector; -}, isTrigger?: boolean): void; + }, + isTrigger?: boolean, +): void; /** * draw body bounding body box */ -export declare function drawBVH(context: CanvasRenderingContext2D, body: Body): void; +export declare function drawBVH( + context: CanvasRenderingContext2D, + body: Body, +): void; /** * clone response object returning new response with previous ones values */ @@ -122,5 +157,12 @@ export declare function ensureNumber(input: number | string): number; * @param category - category bits * @param mask - mask bits (default: category) */ -export declare function groupBits(category: number | string, mask?: number | string): number; -export declare function move(body: Body, speed?: number, updateNow?: boolean): void; +export declare function groupBits( + category: number | string, + mask?: number | string, +): number; +export declare function move( + body: Body, + speed?: number, + updateNow?: boolean, +): void; diff --git a/dist/utils.js b/dist/utils.js index 1a902a54..1ad438e7 100644 --- a/dist/utils.js +++ b/dist/utils.js @@ -36,273 +36,287 @@ const intersect_1 = require("./intersect"); const optimized_1 = require("./optimized"); /* helpers for faster getSATTest() and checkAInB() */ const testMap = { - satCircleCircle: sat_1.testCircleCircle, - satCirclePolygon: sat_1.testCirclePolygon, - satPolygonCircle: sat_1.testPolygonCircle, - satPolygonPolygon: sat_1.testPolygonPolygon, - inCircleCircle: intersect_1.circleInCircle, - inCirclePolygon: intersect_1.circleInPolygon, - inPolygonCircle: intersect_1.polygonInCircle, - inPolygonPolygon: intersect_1.polygonInPolygon + satCircleCircle: sat_1.testCircleCircle, + satCirclePolygon: sat_1.testCirclePolygon, + satPolygonCircle: sat_1.testPolygonCircle, + satPolygonPolygon: sat_1.testPolygonPolygon, + inCircleCircle: intersect_1.circleInCircle, + inCirclePolygon: intersect_1.circleInPolygon, + inPolygonCircle: intersect_1.polygonInCircle, + inPolygonPolygon: intersect_1.polygonInPolygon, }; -function createMap(bodyType, testType) { - return Object.values(model_1.BodyType).reduce((result, type) => (Object.assign(Object.assign({}, result), { [type]: type === model_1.BodyType.Circle - ? testMap[`${testType}${bodyType}Circle`] - : testMap[`${testType}${bodyType}Polygon`] })), {}); +function createArray(bodyType, testType) { + const arrayResult = []; + const bodyGroups = Object.values(model_1.BodyGroup).filter( + (value) => typeof value === "number", + ); + bodyGroups.forEach((bodyGroup) => { + arrayResult[bodyGroup] = + bodyGroup === model_1.BodyGroup.Circle + ? testMap[`${testType}${bodyType}Circle`] + : testMap[`${testType}${bodyType}Polygon`]; + }); + return arrayResult; } -const circleSATFunctions = createMap(model_1.BodyType.Circle, "sat"); -const circleInFunctions = createMap(model_1.BodyType.Circle, "in"); -const polygonSATFunctions = createMap(model_1.BodyType.Polygon, "sat"); -const polygonInFunctions = createMap(model_1.BodyType.Polygon, "in"); +const circleSATFunctions = createArray(model_1.BodyType.Circle, "sat"); +const circleInFunctions = createArray(model_1.BodyType.Circle, "in"); +const polygonSATFunctions = createArray(model_1.BodyType.Polygon, "sat"); +const polygonInFunctions = createArray(model_1.BodyType.Polygon, "in"); exports.DEG2RAD = Math.PI / 180; exports.RAD2DEG = 180 / Math.PI; /** * convert from degrees to radians */ function deg2rad(degrees) { - return degrees * exports.DEG2RAD; + return degrees * exports.DEG2RAD; } /** * convert from radians to degrees */ function rad2deg(radians) { - return radians * exports.RAD2DEG; + return radians * exports.RAD2DEG; } /** * creates ellipse-shaped polygon based on params */ function createEllipse(radiusX, radiusY = radiusX, step = 1) { - const steps = Math.PI * Math.hypot(radiusX, radiusY) * 2; - const length = Math.max(8, Math.ceil(steps / Math.max(1, step))); - const ellipse = []; - for (let index = 0; index < length; index++) { - const value = (index / length) * 2 * Math.PI; - const x = Math.cos(value) * radiusX; - const y = Math.sin(value) * radiusY; - ellipse.push(new sat_1.Vector(x, y)); - } - return ellipse; + const steps = Math.PI * Math.hypot(radiusX, radiusY) * 2; + const length = Math.max(8, Math.ceil(steps / Math.max(1, step))); + const ellipse = []; + for (let index = 0; index < length; index++) { + const value = (index / length) * 2 * Math.PI; + const x = Math.cos(value) * radiusX; + const y = Math.sin(value) * radiusY; + ellipse.push(new sat_1.Vector(x, y)); + } + return ellipse; } /** * creates box shaped polygon points */ function createBox(width, height) { - return [ - new sat_1.Vector(0, 0), - new sat_1.Vector(width, 0), - new sat_1.Vector(width, height), - new sat_1.Vector(0, height) - ]; + return [ + new sat_1.Vector(0, 0), + new sat_1.Vector(width, 0), + new sat_1.Vector(width, height), + new sat_1.Vector(0, height), + ]; } /** * ensure SATVector type point result */ function ensureVectorPoint(point = {}) { - return point instanceof sat_1.Vector - ? point - : new sat_1.Vector(point.x || 0, point.y || 0); + return point instanceof sat_1.Vector + ? point + : new sat_1.Vector(point.x || 0, point.y || 0); } /** * ensure Vector points (for polygon) in counter-clockwise order */ function ensurePolygonPoints(points = []) { - const polygonPoints = (0, optimized_1.map)(points, ensureVectorPoint); - return clockwise(polygonPoints) ? polygonPoints.reverse() : polygonPoints; + const polygonPoints = (0, optimized_1.map)(points, ensureVectorPoint); + return clockwise(polygonPoints) ? polygonPoints.reverse() : polygonPoints; } /** * get distance between two Vector points */ function distance(bodyA, bodyB) { - const xDiff = bodyA.x - bodyB.x; - const yDiff = bodyA.y - bodyB.y; - return Math.hypot(xDiff, yDiff); + const xDiff = bodyA.x - bodyB.x; + const yDiff = bodyA.y - bodyB.y; + return Math.hypot(xDiff, yDiff); } /** * check [is clockwise] direction of polygon */ function clockwise(points) { - const length = points.length; - let sum = 0; - (0, optimized_1.forEach)(points, (v1, index) => { - const v2 = points[(index + 1) % length]; - sum += (v2.x - v1.x) * (v2.y + v1.y); - }); - return sum > 0; + const length = points.length; + let sum = 0; + (0, optimized_1.forEach)(points, (v1, index) => { + const v2 = points[(index + 1) % length]; + sum += (v2.x - v1.x) * (v2.y + v1.y); + }); + return sum > 0; } /** * used for all types of bodies in constructor */ function extendBody(body, options = {}) { - body.isStatic = !!options.isStatic; - body.isTrigger = !!options.isTrigger; - body.padding = options.padding || 0; - body.group = typeof options.group === "number" ? options.group : 0x7FFFFFFF; - if (body.typeGroup !== model_1.BodyGroup.Circle) { - body.isCentered = options.isCentered || false; - } - body.setAngle(options.angle || 0); + body.isStatic = !!options.isStatic; + body.isTrigger = !!options.isTrigger; + body.padding = options.padding || 0; + body.group = typeof options.group === "number" ? options.group : 0x7fffffff; + if (body.typeGroup !== model_1.BodyGroup.Circle) { + body.isCentered = options.isCentered || false; + } + body.setAngle(options.angle || 0); } /** * check if body moved outside of its padding */ function bodyMoved(body) { - const { bbox, minX, minY, maxX, maxY } = body; - return (bbox.minX < minX || bbox.minY < minY || bbox.maxX > maxX || bbox.maxY > maxY); + const { bbox, minX, minY, maxX, maxY } = body; + return ( + bbox.minX < minX || bbox.minY < minY || bbox.maxX > maxX || bbox.maxY > maxY + ); } /** * returns true if two boxes not intersect */ function notIntersectAABB(bodyA, bodyB) { - return (bodyB.minX > bodyA.maxX || - bodyB.minY > bodyA.maxY || - bodyB.maxX < bodyA.minX || - bodyB.maxY < bodyA.minY); + return ( + bodyB.minX > bodyA.maxX || + bodyB.minY > bodyA.maxY || + bodyB.maxX < bodyA.minX || + bodyB.maxY < bodyA.minY + ); } /** * checks if two boxes intersect */ function intersectAABB(bodyA, bodyB) { - return !notIntersectAABB(bodyA, bodyB); + return !notIntersectAABB(bodyA, bodyB); } /** * checks if two bodies can interact (for collision filtering) */ function canInteract(bodyA, bodyB) { - return (((bodyA.group >> 16) & (bodyB.group & 0xFFFF) && - (bodyB.group >> 16) & (bodyA.group & 0xFFFF)) !== 0); + return ( + ((bodyA.group >> 16) & (bodyB.group & 0xffff) && + (bodyB.group >> 16) & (bodyA.group & 0xffff)) !== 0 + ); } /** * checks if body a is in body b */ function checkAInB(bodyA, bodyB) { - const check = bodyA.typeGroup === model_1.BodyGroup.Circle - ? circleInFunctions - : polygonInFunctions; - return check[bodyB.type](bodyA, bodyB); + const check = + bodyA.typeGroup === model_1.BodyGroup.Circle + ? circleInFunctions + : polygonInFunctions; + return check[bodyB.typeGroup](bodyA, bodyB); } /** * clone sat vector points array into vector points array */ function clonePointsArray(points) { - return (0, optimized_1.map)(points, ({ x, y }) => ({ x, y })); + return (0, optimized_1.map)(points, ({ x, y }) => ({ x, y })); } /** * change format from SAT.js to poly-decomp */ function mapVectorToArray({ x, y } = { x: 0, y: 0 }) { - return [x, y]; + return [x, y]; } /** * change format from poly-decomp to SAT.js */ function mapArrayToVector([x, y] = [0, 0]) { - return { x, y }; + return { x, y }; } /** * given 2 bodies calculate vector of bounce assuming equal mass and they are circles */ function getBounceDirection(body, collider) { - const v2 = new sat_1.Vector(collider.x - body.x, collider.y - body.y); - const v1 = new sat_1.Vector(body.x - collider.x, body.y - collider.y); - const len = v1.dot(v2.normalize()) * 2; - return new sat_1.Vector(v2.x * len - v1.x, v2.y * len - v1.y).normalize(); + const v2 = new sat_1.Vector(collider.x - body.x, collider.y - body.y); + const v1 = new sat_1.Vector(body.x - collider.x, body.y - collider.y); + const len = v1.dot(v2.normalize()) * 2; + return new sat_1.Vector(v2.x * len - v1.x, v2.y * len - v1.y).normalize(); } /** * returns correct sat.js testing function based on body types */ function getSATTest(bodyA, bodyB) { - const check = bodyA.typeGroup === model_1.BodyGroup.Circle - ? circleSATFunctions - : polygonSATFunctions; - return check[bodyB.type]; + const check = + bodyA.typeGroup === model_1.BodyGroup.Circle + ? circleSATFunctions + : polygonSATFunctions; + return check[bodyB.typeGroup]; } /** * draws dashed line on canvas context */ function dashLineTo(context, fromX, fromY, toX, toY, dash = 2, gap = 4) { - const xDiff = toX - fromX; - const yDiff = toY - fromY; - const arc = Math.atan2(yDiff, xDiff); - const offsetX = Math.cos(arc); - const offsetY = Math.sin(arc); - let posX = fromX; - let posY = fromY; - let dist = Math.hypot(xDiff, yDiff); - while (dist > 0) { - const step = Math.min(dist, dash); - context.moveTo(posX, posY); - context.lineTo(posX + offsetX * step, posY + offsetY * step); - posX += offsetX * (dash + gap); - posY += offsetY * (dash + gap); - dist -= dash + gap; - } + const xDiff = toX - fromX; + const yDiff = toY - fromY; + const arc = Math.atan2(yDiff, xDiff); + const offsetX = Math.cos(arc); + const offsetY = Math.sin(arc); + let posX = fromX; + let posY = fromY; + let dist = Math.hypot(xDiff, yDiff); + while (dist > 0) { + const step = Math.min(dist, dash); + context.moveTo(posX, posY); + context.lineTo(posX + offsetX * step, posY + offsetY * step); + posX += offsetX * (dash + gap); + posY += offsetY * (dash + gap); + dist -= dash + gap; + } } /** * draw polygon */ function drawPolygon(context, { pos, calcPoints }, isTrigger = false) { - const lastPoint = calcPoints[calcPoints.length - 1]; - const fromX = pos.x + lastPoint.x; - const fromY = pos.y + lastPoint.y; - if (calcPoints.length === 1) { - context.arc(fromX, fromY, 1, 0, Math.PI * 2); + const lastPoint = calcPoints[calcPoints.length - 1]; + const fromX = pos.x + lastPoint.x; + const fromY = pos.y + lastPoint.y; + if (calcPoints.length === 1) { + context.arc(fromX, fromY, 1, 0, Math.PI * 2); + } else { + context.moveTo(fromX, fromY); + } + (0, optimized_1.forEach)(calcPoints, (point, index) => { + const toX = pos.x + point.x; + const toY = pos.y + point.y; + if (isTrigger) { + const prev = calcPoints[index - 1] || lastPoint; + dashLineTo(context, pos.x + prev.x, pos.y + prev.y, toX, toY); + } else { + context.lineTo(toX, toY); } - else { - context.moveTo(fromX, fromY); - } - (0, optimized_1.forEach)(calcPoints, (point, index) => { - const toX = pos.x + point.x; - const toY = pos.y + point.y; - if (isTrigger) { - const prev = calcPoints[index - 1] || lastPoint; - dashLineTo(context, pos.x + prev.x, pos.y + prev.y, toX, toY); - } - else { - context.lineTo(toX, toY); - } - }); + }); } /** * draw body bounding body box */ function drawBVH(context, body) { - drawPolygon(context, { - pos: { x: body.minX, y: body.minY }, - calcPoints: createBox(body.maxX - body.minX, body.maxY - body.minY) - }); + drawPolygon(context, { + pos: { x: body.minX, y: body.minY }, + calcPoints: createBox(body.maxX - body.minX, body.maxY - body.minY), + }); } /** * clone response object returning new response with previous ones values */ function cloneResponse(response) { - const clone = new sat_1.Response(); - const { a, b, overlap, overlapN, overlapV, aInB, bInA } = response; - clone.a = a; - clone.b = b; - clone.overlap = overlap; - clone.overlapN = overlapN.clone(); - clone.overlapV = overlapV.clone(); - clone.aInB = aInB; - clone.bInA = bInA; - return clone; + const clone = new sat_1.Response(); + const { a, b, overlap, overlapN, overlapV, aInB, bInA } = response; + clone.a = a; + clone.b = b; + clone.overlap = overlap; + clone.overlapN = overlapN.clone(); + clone.overlapV = overlapV.clone(); + clone.aInB = aInB; + clone.bInA = bInA; + return clone; } /** * dummy fn used as default, for optimization */ function returnTrue() { - return true; + return true; } /** * for groups */ function getGroup(group) { - return Math.max(0, Math.min(group, 0x7FFFFFFF)); + return Math.max(0, Math.min(group, 0x7fffffff)); } /** * binary string to decimal number */ function bin2dec(binary) { - return Number(`0b${binary}`.replace(/\s/g, "")); + return Number(`0b${binary}`.replace(/\s/g, "")); } /** * helper for groupBits() @@ -310,7 +324,7 @@ function bin2dec(binary) { * @param input - number or binary string */ function ensureNumber(input) { - return typeof input === "number" ? input : bin2dec(input); + return typeof input === "number" ? input : bin2dec(input); } /** * create group bits from category and mask @@ -319,13 +333,13 @@ function ensureNumber(input) { * @param mask - mask bits (default: category) */ function groupBits(category, mask = category) { - return (ensureNumber(category) << 16) | ensureNumber(mask); + return (ensureNumber(category) << 16) | ensureNumber(mask); } function move(body, speed = 1, updateNow = true) { - if (!speed) { - return; - } - const moveX = Math.cos(body.angle) * speed; - const moveY = Math.sin(body.angle) * speed; - body.setPosition(body.x + moveX, body.y + moveY, updateNow); + if (!speed) { + return; + } + const moveX = Math.cos(body.angle) * speed; + const moveY = Math.sin(body.angle) * speed; + body.setPosition(body.x + moveX, body.y + moveY, updateNow); } diff --git a/docs/assets/highlight.css b/docs/assets/highlight.css index a3bf868b..d2c0558a 100644 --- a/docs/assets/highlight.css +++ b/docs/assets/highlight.css @@ -1,27 +1,28 @@ :root { - --light-hl-0: #795E26; - --dark-hl-0: #DCDCAA; - --light-hl-1: #000000; - --dark-hl-1: #D4D4D4; - --light-hl-2: #A31515; - --dark-hl-2: #CE9178; - --light-hl-3: #0000FF; - --dark-hl-3: #569CD6; - --light-hl-4: #0070C1; - --dark-hl-4: #4FC1FF; - --light-hl-5: #008000; - --dark-hl-5: #6A9955; - --light-hl-6: #001080; - --dark-hl-6: #9CDCFE; - --light-hl-7: #098658; - --dark-hl-7: #B5CEA8; - --light-hl-8: #AF00DB; - --dark-hl-8: #C586C0; - --light-code-background: #FFFFFF; - --dark-code-background: #1E1E1E; + --light-hl-0: #795e26; + --dark-hl-0: #dcdcaa; + --light-hl-1: #000000; + --dark-hl-1: #d4d4d4; + --light-hl-2: #a31515; + --dark-hl-2: #ce9178; + --light-hl-3: #0000ff; + --dark-hl-3: #569cd6; + --light-hl-4: #0070c1; + --dark-hl-4: #4fc1ff; + --light-hl-5: #008000; + --dark-hl-5: #6a9955; + --light-hl-6: #001080; + --dark-hl-6: #9cdcfe; + --light-hl-7: #098658; + --dark-hl-7: #b5cea8; + --light-hl-8: #af00db; + --dark-hl-8: #c586c0; + --light-code-background: #ffffff; + --dark-code-background: #1e1e1e; } -@media (prefers-color-scheme: light) { :root { +@media (prefers-color-scheme: light) { + :root { --hl-0: var(--light-hl-0); --hl-1: var(--light-hl-1); --hl-2: var(--light-hl-2); @@ -32,9 +33,11 @@ --hl-7: var(--light-hl-7); --hl-8: var(--light-hl-8); --code-background: var(--light-code-background); -} } + } +} -@media (prefers-color-scheme: dark) { :root { +@media (prefers-color-scheme: dark) { + :root { --hl-0: var(--dark-hl-0); --hl-1: var(--dark-hl-1); --hl-2: var(--dark-hl-2); @@ -45,41 +48,63 @@ --hl-7: var(--dark-hl-7); --hl-8: var(--dark-hl-8); --code-background: var(--dark-code-background); -} } + } +} -:root[data-theme='light'] { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --hl-3: var(--light-hl-3); - --hl-4: var(--light-hl-4); - --hl-5: var(--light-hl-5); - --hl-6: var(--light-hl-6); - --hl-7: var(--light-hl-7); - --hl-8: var(--light-hl-8); - --code-background: var(--light-code-background); +:root[data-theme="light"] { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --hl-6: var(--light-hl-6); + --hl-7: var(--light-hl-7); + --hl-8: var(--light-hl-8); + --code-background: var(--light-code-background); } -:root[data-theme='dark'] { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --hl-3: var(--dark-hl-3); - --hl-4: var(--dark-hl-4); - --hl-5: var(--dark-hl-5); - --hl-6: var(--dark-hl-6); - --hl-7: var(--dark-hl-7); - --hl-8: var(--dark-hl-8); - --code-background: var(--dark-code-background); +:root[data-theme="dark"] { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --hl-6: var(--dark-hl-6); + --hl-7: var(--dark-hl-7); + --hl-8: var(--dark-hl-8); + --code-background: var(--dark-code-background); } -.hl-0 { color: var(--hl-0); } -.hl-1 { color: var(--hl-1); } -.hl-2 { color: var(--hl-2); } -.hl-3 { color: var(--hl-3); } -.hl-4 { color: var(--hl-4); } -.hl-5 { color: var(--hl-5); } -.hl-6 { color: var(--hl-6); } -.hl-7 { color: var(--hl-7); } -.hl-8 { color: var(--hl-8); } -pre, code { background: var(--code-background); } +.hl-0 { + color: var(--hl-0); +} +.hl-1 { + color: var(--hl-1); +} +.hl-2 { + color: var(--hl-2); +} +.hl-3 { + color: var(--hl-3); +} +.hl-4 { + color: var(--hl-4); +} +.hl-5 { + color: var(--hl-5); +} +.hl-6 { + color: var(--hl-6); +} +.hl-7 { + color: var(--hl-7); +} +.hl-8 { + color: var(--hl-8); +} +pre, +code { + background: var(--code-background); +} diff --git a/docs/assets/icons.js b/docs/assets/icons.js index e88e8ca7..c27b1d1c 100644 --- a/docs/assets/icons.js +++ b/docs/assets/icons.js @@ -1,18 +1,21 @@ -(function() { - addIcons(); - function addIcons() { - if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); - const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); - svg.innerHTML = `""`; - svg.style.display = "none"; - if (location.protocol === "file:") updateUseElements(); - } +(function () { + addIcons(); + function addIcons() { + if (document.readyState === "loading") + return document.addEventListener("DOMContentLoaded", addIcons); + const svg = document.body.appendChild( + document.createElementNS("http://www.w3.org/2000/svg", "svg"), + ); + svg.innerHTML = `""`; + svg.style.display = "none"; + if (location.protocol === "file:") updateUseElements(); + } - function updateUseElements() { - document.querySelectorAll("use").forEach(el => { - if (el.getAttribute("href").includes("#icon-")) { - el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); - } - }); - } -})() \ No newline at end of file + function updateUseElements() { + document.querySelectorAll("use").forEach((el) => { + if (el.getAttribute("href").includes("#icon-")) { + el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); + } + }); + } +})(); diff --git a/docs/assets/main.js b/docs/assets/main.js index 35728810..5f0de445 100644 --- a/docs/assets/main.js +++ b/docs/assets/main.js @@ -1,9 +1,2160 @@ "use strict"; -window.translations={"copy":"Copy","copied":"Copied!","normally_hidden":"This member is normally hidden due to your filter settings."}; -"use strict";(()=>{var Pe=Object.create;var ie=Object.defineProperty;var Oe=Object.getOwnPropertyDescriptor;var _e=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,Me=Object.prototype.hasOwnProperty;var Fe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var De=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of _e(e))!Me.call(t,i)&&i!==n&&ie(t,i,{get:()=>e[i],enumerable:!(r=Oe(e,i))||r.enumerable});return t};var Ae=(t,e,n)=>(n=t!=null?Pe(Re(t)):{},De(e||!t||!t.__esModule?ie(n,"default",{value:t,enumerable:!0}):n,t));var ue=Fe((ae,le)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(n){e.console&&console.warn&&console.warn(n)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var d=t.utils.clone(n)||{};d.position=[a,u],d.index=s.length,s.push(new t.Token(r.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. -`,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ol?d+=2:a==l&&(n+=r[u+1]*i[d+1],u+=2,d+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}if(s.str.length==0&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}s.str.length==1&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),m=s.str.charAt(1),p;m in s.node.edges?p=s.node.edges[m]:(p=new t.TokenSet,s.node.edges[m]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),l=0;l1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,n){typeof define=="function"&&define.amd?define(n):typeof ae=="object"?le.exports=n():e.lunr=n()}(this,function(){return t})})()});var se=[];function G(t,e){se.push({selector:e,constructor:t})}var U=class{constructor(){this.alwaysVisibleMember=null;this.createComponents(document.body),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible()),document.body.style.display||(this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}createComponents(e){se.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r,app:this}),r.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}showPage(){document.body.style.display&&(document.body.style.removeProperty("display"),this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}scrollToHash(){if(location.hash){let e=document.getElementById(location.hash.substring(1));if(!e)return;e.scrollIntoView({behavior:"instant",block:"start"})}}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),n=e?.parentElement;for(;n&&!n.classList.contains(".tsd-navigation");)n instanceof HTMLDetailsElement&&(n.open=!0),n=n.parentElement;if(e&&!Ve(e)){let r=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=r,document.querySelector(".col-sidebar").scrollTop=r}}updateIndexVisibility(){let e=document.querySelector(".tsd-index-content"),n=e?.open;e&&(e.open=!0),document.querySelectorAll(".tsd-index-section").forEach(r=>{r.style.display="block";let i=Array.from(r.querySelectorAll(".tsd-index-link")).every(s=>s.offsetParent==null);r.style.display=i?"none":"block"}),e&&(e.open=n)}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let n=e.parentElement;for(;n&&n.tagName!=="SECTION";)n=n.parentElement;if(!n)return;let r=n.offsetParent==null,i=n;for(;i!==document.body;)i instanceof HTMLDetailsElement&&(i.open=!0),i=i.parentElement;if(n.offsetParent==null){this.alwaysVisibleMember=n,n.classList.add("always-visible");let s=document.createElement("p");s.classList.add("warning"),s.textContent=window.translations.normally_hidden,n.prepend(s)}r&&e.scrollIntoView()}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let n;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent=window.translations.copied,e.classList.add("visible"),clearTimeout(n),n=setTimeout(()=>{e.classList.remove("visible"),n=setTimeout(()=>{e.textContent=window.translations.copy},100)},1e3)})})}};function Ve(t){let e=t.getBoundingClientRect(),n=Math.max(document.documentElement.clientHeight,window.innerHeight);return!(e.bottom<0||e.top-n>=0)}var oe=(t,e=100)=>{let n;return()=>{clearTimeout(n),n=setTimeout(()=>t(),e)}};var pe=Ae(ue());async function ce(t,e){if(!window.searchData)return;let n=await fetch(window.searchData),r=new Blob([await n.arrayBuffer()]).stream().pipeThrough(new DecompressionStream("gzip")),i=await new Response(r).json();t.data=i,t.index=pe.Index.load(i.index),e.classList.remove("loading"),e.classList.add("ready")}function fe(){let t=document.getElementById("tsd-search");if(!t)return;let e={base:t.dataset.base+"/"},n=document.getElementById("tsd-search-script");t.classList.add("loading"),n&&(n.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),n.addEventListener("load",()=>{ce(e,t)}),ce(e,t));let r=document.querySelector("#tsd-search input"),i=document.querySelector("#tsd-search .results");if(!r||!i)throw new Error("The input field or the result list wrapper was not found");i.addEventListener("mouseup",()=>{te(t)}),r.addEventListener("focus",()=>t.classList.add("has-focus")),He(t,i,r,e)}function He(t,e,n,r){n.addEventListener("input",oe(()=>{Ne(t,e,n,r)},200)),n.addEventListener("keydown",i=>{i.key=="Enter"?Be(e,t):i.key=="ArrowUp"?(de(e,n,-1),i.preventDefault()):i.key==="ArrowDown"&&(de(e,n,1),i.preventDefault())}),document.body.addEventListener("keypress",i=>{i.altKey||i.ctrlKey||i.metaKey||!n.matches(":focus")&&i.key==="/"&&(i.preventDefault(),n.focus())}),document.body.addEventListener("keyup",i=>{t.classList.contains("has-focus")&&(i.key==="Escape"||!e.matches(":focus-within")&&!n.matches(":focus"))&&(n.blur(),te(t))})}function te(t){t.classList.remove("has-focus")}function Ne(t,e,n,r){if(!r.index||!r.data)return;e.textContent="";let i=n.value.trim(),s;if(i){let o=i.split(" ").map(a=>a.length?`*${a}*`:"").join(" ");s=r.index.search(o)}else s=[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o`,d=he(l.name,i);globalThis.DEBUG_SEARCH_WEIGHTS&&(d+=` (score: ${s[o].score.toFixed(2)})`),l.parent&&(d=` - ${he(l.parent,i)}.${d}`);let m=document.createElement("li");m.classList.value=l.classes??"";let p=document.createElement("a");p.href=r.base+l.url,p.innerHTML=u+d,m.append(p),p.addEventListener("focus",()=>{e.querySelector(".current")?.classList.remove("current"),m.classList.add("current")}),e.appendChild(m)}}function de(t,e,n){let r=t.querySelector(".current");if(!r)r=t.querySelector(n==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let i=r;if(n===1)do i=i.nextElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);else do i=i.previousElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);i?(r.classList.remove("current"),i.classList.add("current")):n===-1&&(r.classList.remove("current"),e.focus())}}function Be(t,e){let n=t.querySelector(".current");if(n||(n=t.querySelector("li:first-child")),n){let r=n.querySelector("a");r&&(window.location.href=r.href),te(e)}}function he(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(ee(t.substring(s,o)),`${ee(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(ee(t.substring(s))),i.join("")}var je={"&":"&","<":"<",">":">","'":"'",'"':"""};function ee(t){return t.replace(/[&<>"'"]/g,e=>je[e])}var I=class{constructor(e){this.el=e.el,this.app=e.app}};var F="mousedown",ye="mousemove",N="mouseup",J={x:0,y:0},me=!1,ne=!1,qe=!1,D=!1,ve=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(ve?"is-mobile":"not-mobile");ve&&"ontouchstart"in document.documentElement&&(qe=!0,F="touchstart",ye="touchmove",N="touchend");document.addEventListener(F,t=>{ne=!0,D=!1;let e=F=="touchstart"?t.targetTouches[0]:t;J.y=e.pageY||0,J.x=e.pageX||0});document.addEventListener(ye,t=>{if(ne&&!D){let e=F=="touchstart"?t.targetTouches[0]:t,n=J.x-(e.pageX||0),r=J.y-(e.pageY||0);D=Math.sqrt(n*n+r*r)>10}});document.addEventListener(N,()=>{ne=!1});document.addEventListener("click",t=>{me&&(t.preventDefault(),t.stopImmediatePropagation(),me=!1)});var X=class extends I{constructor(e){super(e),this.className=this.el.dataset.toggle||"",this.el.addEventListener(N,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(F,n=>this.onDocumentPointerDown(n)),document.addEventListener(N,n=>this.onDocumentPointerUp(n))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(e){D||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!D&&this.active&&e.target.closest(".col-sidebar")){let n=e.target.closest("a");if(n){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substring(0,r.indexOf("#"))),n.href.substring(0,r.length)==r&&setTimeout(()=>this.setActive(!1),250)}}}};var re;try{re=localStorage}catch{re={getItem(){return null},setItem(){}}}var Q=re;var ge=document.head.appendChild(document.createElement("style"));ge.dataset.for="filters";var Y=class extends I{constructor(e){super(e),this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),ge.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } -`,this.app.updateIndexVisibility()}fromLocalStorage(){let e=Q.getItem(this.key);return e?e==="true":this.el.checked}setLocalStorage(e){Q.setItem(this.key,e.toString()),this.value=e,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),this.app.filterChanged(),this.app.updateIndexVisibility()}};var Z=class extends I{constructor(e){super(e),this.summary=this.el.querySelector(".tsd-accordion-summary"),this.icon=this.summary.querySelector("svg"),this.key=`tsd-accordion-${this.summary.dataset.key??this.summary.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`;let n=Q.getItem(this.key);this.el.open=n?n==="true":this.el.open,this.el.addEventListener("toggle",()=>this.update());let r=this.summary.querySelector("a");r&&r.addEventListener("click",()=>{location.assign(r.href)}),this.update()}update(){this.icon.style.transform=`rotate(${this.el.open?0:-90}deg)`,Q.setItem(this.key,this.el.open.toString())}};function Ee(t){let e=Q.getItem("tsd-theme")||"os";t.value=e,xe(e),t.addEventListener("change",()=>{Q.setItem("tsd-theme",t.value),xe(t.value)})}function xe(t){document.documentElement.dataset.theme=t}var K;function we(){let t=document.getElementById("tsd-nav-script");t&&(t.addEventListener("load",Le),Le())}async function Le(){let t=document.getElementById("tsd-nav-container");if(!t||!window.navigationData)return;let n=await(await fetch(window.navigationData)).arrayBuffer(),r=new Blob([n]).stream().pipeThrough(new DecompressionStream("gzip")),i=await new Response(r).json();K=t.dataset.base,K.endsWith("/")||(K+="/"),t.innerHTML="";for(let s of i)Se(s,t,[]);window.app.createComponents(t),window.app.showPage(),window.app.ensureActivePageVisible()}function Se(t,e,n){let r=e.appendChild(document.createElement("li"));if(t.children){let i=[...n,t.text],s=r.appendChild(document.createElement("details"));s.className=t.class?`${t.class} tsd-accordion`:"tsd-accordion";let o=s.appendChild(document.createElement("summary"));o.className="tsd-accordion-summary",o.dataset.key=i.join("$"),o.innerHTML='',be(t,o);let a=s.appendChild(document.createElement("div"));a.className="tsd-accordion-details";let l=a.appendChild(document.createElement("ul"));l.className="tsd-nested-navigation";for(let u of t.children)Se(u,l,i)}else be(t,r,t.class)}function be(t,e,n){if(t.path){let r=e.appendChild(document.createElement("a"));r.href=K+t.path,n&&(r.className=n),location.pathname===r.pathname&&r.classList.add("current"),t.kind&&(r.innerHTML=``),r.appendChild(document.createElement("span")).textContent=t.text}else e.appendChild(document.createElement("span")).textContent=t.text}G(X,"a[data-toggle]");G(Z,".tsd-accordion");G(Y,".tsd-filter-item input[type=checkbox]");var Te=document.getElementById("tsd-theme");Te&&Ee(Te);var $e=new U;Object.defineProperty(window,"app",{value:$e});fe();we();})(); +window.translations = { + copy: "Copy", + copied: "Copied!", + normally_hidden: + "This member is normally hidden due to your filter settings.", +}; +("use strict"); +(() => { + var Pe = Object.create; + var ie = Object.defineProperty; + var Oe = Object.getOwnPropertyDescriptor; + var _e = Object.getOwnPropertyNames; + var Re = Object.getPrototypeOf, + Me = Object.prototype.hasOwnProperty; + var Fe = (t, e) => () => ( + e || t((e = { exports: {} }).exports, e), e.exports + ); + var De = (t, e, n, r) => { + if ((e && typeof e == "object") || typeof e == "function") + for (let i of _e(e)) + !Me.call(t, i) && + i !== n && + ie(t, i, { + get: () => e[i], + enumerable: !(r = Oe(e, i)) || r.enumerable, + }); + return t; + }; + var Ae = (t, e, n) => ( + (n = t != null ? Pe(Re(t)) : {}), + De( + e || !t || !t.__esModule + ? ie(n, "default", { value: t, enumerable: !0 }) + : n, + t, + ) + ); + var ue = Fe((ae, le) => { + (function () { + var t = function (e) { + var n = new t.Builder(); + return ( + n.pipeline.add(t.trimmer, t.stopWordFilter, t.stemmer), + n.searchPipeline.add(t.stemmer), + e.call(n, n), + n.build() + ); + }; + t.version = "2.3.9"; + (t.utils = {}), + (t.utils.warn = (function (e) { + return function (n) { + e.console && console.warn && console.warn(n); + }; + })(this)), + (t.utils.asString = function (e) { + return e == null ? "" : e.toString(); + }), + (t.utils.clone = function (e) { + if (e == null) return e; + for ( + var n = Object.create(null), r = Object.keys(e), i = 0; + i < r.length; + i++ + ) { + var s = r[i], + o = e[s]; + if (Array.isArray(o)) { + n[s] = o.slice(); + continue; + } + if ( + typeof o == "string" || + typeof o == "number" || + typeof o == "boolean" + ) { + n[s] = o; + continue; + } + throw new TypeError( + "clone is not deep and does not support nested objects", + ); + } + return n; + }), + (t.FieldRef = function (e, n, r) { + (this.docRef = e), (this.fieldName = n), (this._stringValue = r); + }), + (t.FieldRef.joiner = "/"), + (t.FieldRef.fromString = function (e) { + var n = e.indexOf(t.FieldRef.joiner); + if (n === -1) throw "malformed field ref string"; + var r = e.slice(0, n), + i = e.slice(n + 1); + return new t.FieldRef(i, r, e); + }), + (t.FieldRef.prototype.toString = function () { + return ( + this._stringValue == null && + (this._stringValue = + this.fieldName + t.FieldRef.joiner + this.docRef), + this._stringValue + ); + }); + (t.Set = function (e) { + if (((this.elements = Object.create(null)), e)) { + this.length = e.length; + for (var n = 0; n < this.length; n++) this.elements[e[n]] = !0; + } else this.length = 0; + }), + (t.Set.complete = { + intersect: function (e) { + return e; + }, + union: function () { + return this; + }, + contains: function () { + return !0; + }, + }), + (t.Set.empty = { + intersect: function () { + return this; + }, + union: function (e) { + return e; + }, + contains: function () { + return !1; + }, + }), + (t.Set.prototype.contains = function (e) { + return !!this.elements[e]; + }), + (t.Set.prototype.intersect = function (e) { + var n, + r, + i, + s = []; + if (e === t.Set.complete) return this; + if (e === t.Set.empty) return e; + this.length < e.length + ? ((n = this), (r = e)) + : ((n = e), (r = this)), + (i = Object.keys(n.elements)); + for (var o = 0; o < i.length; o++) { + var a = i[o]; + a in r.elements && s.push(a); + } + return new t.Set(s); + }), + (t.Set.prototype.union = function (e) { + return e === t.Set.complete + ? t.Set.complete + : e === t.Set.empty + ? this + : new t.Set( + Object.keys(this.elements).concat(Object.keys(e.elements)), + ); + }), + (t.idf = function (e, n) { + var r = 0; + for (var i in e) i != "_index" && (r += Object.keys(e[i]).length); + var s = (n - r + 0.5) / (r + 0.5); + return Math.log(1 + Math.abs(s)); + }), + (t.Token = function (e, n) { + (this.str = e || ""), (this.metadata = n || {}); + }), + (t.Token.prototype.toString = function () { + return this.str; + }), + (t.Token.prototype.update = function (e) { + return (this.str = e(this.str, this.metadata)), this; + }), + (t.Token.prototype.clone = function (e) { + return ( + (e = + e || + function (n) { + return n; + }), + new t.Token(e(this.str, this.metadata), this.metadata) + ); + }); + (t.tokenizer = function (e, n) { + if (e == null || e == null) return []; + if (Array.isArray(e)) + return e.map(function (m) { + return new t.Token( + t.utils.asString(m).toLowerCase(), + t.utils.clone(n), + ); + }); + for ( + var r = e.toString().toLowerCase(), + i = r.length, + s = [], + o = 0, + a = 0; + o <= i; + o++ + ) { + var l = r.charAt(o), + u = o - a; + if (l.match(t.tokenizer.separator) || o == i) { + if (u > 0) { + var d = t.utils.clone(n) || {}; + (d.position = [a, u]), + (d.index = s.length), + s.push(new t.Token(r.slice(a, o), d)); + } + a = o + 1; + } + } + return s; + }), + (t.tokenizer.separator = /[\s\-]+/); + (t.Pipeline = function () { + this._stack = []; + }), + (t.Pipeline.registeredFunctions = Object.create(null)), + (t.Pipeline.registerFunction = function (e, n) { + n in this.registeredFunctions && + t.utils.warn("Overwriting existing registered function: " + n), + (e.label = n), + (t.Pipeline.registeredFunctions[e.label] = e); + }), + (t.Pipeline.warnIfFunctionNotRegistered = function (e) { + var n = e.label && e.label in this.registeredFunctions; + n || + t.utils.warn( + `Function is not registered with pipeline. This may cause problems when serialising the index. +`, + e, + ); + }), + (t.Pipeline.load = function (e) { + var n = new t.Pipeline(); + return ( + e.forEach(function (r) { + var i = t.Pipeline.registeredFunctions[r]; + if (i) n.add(i); + else throw new Error("Cannot load unregistered function: " + r); + }), + n + ); + }), + (t.Pipeline.prototype.add = function () { + var e = Array.prototype.slice.call(arguments); + e.forEach(function (n) { + t.Pipeline.warnIfFunctionNotRegistered(n), this._stack.push(n); + }, this); + }), + (t.Pipeline.prototype.after = function (e, n) { + t.Pipeline.warnIfFunctionNotRegistered(n); + var r = this._stack.indexOf(e); + if (r == -1) throw new Error("Cannot find existingFn"); + (r = r + 1), this._stack.splice(r, 0, n); + }), + (t.Pipeline.prototype.before = function (e, n) { + t.Pipeline.warnIfFunctionNotRegistered(n); + var r = this._stack.indexOf(e); + if (r == -1) throw new Error("Cannot find existingFn"); + this._stack.splice(r, 0, n); + }), + (t.Pipeline.prototype.remove = function (e) { + var n = this._stack.indexOf(e); + n != -1 && this._stack.splice(n, 1); + }), + (t.Pipeline.prototype.run = function (e) { + for (var n = this._stack.length, r = 0; r < n; r++) { + for (var i = this._stack[r], s = [], o = 0; o < e.length; o++) { + var a = i(e[o], o, e); + if (!(a == null || a === "")) + if (Array.isArray(a)) + for (var l = 0; l < a.length; l++) s.push(a[l]); + else s.push(a); + } + e = s; + } + return e; + }), + (t.Pipeline.prototype.runString = function (e, n) { + var r = new t.Token(e, n); + return this.run([r]).map(function (i) { + return i.toString(); + }); + }), + (t.Pipeline.prototype.reset = function () { + this._stack = []; + }), + (t.Pipeline.prototype.toJSON = function () { + return this._stack.map(function (e) { + return t.Pipeline.warnIfFunctionNotRegistered(e), e.label; + }); + }); + (t.Vector = function (e) { + (this._magnitude = 0), (this.elements = e || []); + }), + (t.Vector.prototype.positionForIndex = function (e) { + if (this.elements.length == 0) return 0; + for ( + var n = 0, + r = this.elements.length / 2, + i = r - n, + s = Math.floor(i / 2), + o = this.elements[s * 2]; + i > 1 && (o < e && (n = s), o > e && (r = s), o != e); + + ) + (i = r - n), + (s = n + Math.floor(i / 2)), + (o = this.elements[s * 2]); + if (o == e || o > e) return s * 2; + if (o < e) return (s + 1) * 2; + }), + (t.Vector.prototype.insert = function (e, n) { + this.upsert(e, n, function () { + throw "duplicate index"; + }); + }), + (t.Vector.prototype.upsert = function (e, n, r) { + this._magnitude = 0; + var i = this.positionForIndex(e); + this.elements[i] == e + ? (this.elements[i + 1] = r(this.elements[i + 1], n)) + : this.elements.splice(i, 0, e, n); + }), + (t.Vector.prototype.magnitude = function () { + if (this._magnitude) return this._magnitude; + for (var e = 0, n = this.elements.length, r = 1; r < n; r += 2) { + var i = this.elements[r]; + e += i * i; + } + return (this._magnitude = Math.sqrt(e)); + }), + (t.Vector.prototype.dot = function (e) { + for ( + var n = 0, + r = this.elements, + i = e.elements, + s = r.length, + o = i.length, + a = 0, + l = 0, + u = 0, + d = 0; + u < s && d < o; + + ) + (a = r[u]), + (l = i[d]), + a < l + ? (u += 2) + : a > l + ? (d += 2) + : a == l && ((n += r[u + 1] * i[d + 1]), (u += 2), (d += 2)); + return n; + }), + (t.Vector.prototype.similarity = function (e) { + return this.dot(e) / this.magnitude() || 0; + }), + (t.Vector.prototype.toArray = function () { + for ( + var e = new Array(this.elements.length / 2), n = 1, r = 0; + n < this.elements.length; + n += 2, r++ + ) + e[r] = this.elements[n]; + return e; + }), + (t.Vector.prototype.toJSON = function () { + return this.elements; + }); + (t.stemmer = (function () { + var e = { + ational: "ate", + tional: "tion", + enci: "ence", + anci: "ance", + izer: "ize", + bli: "ble", + alli: "al", + entli: "ent", + eli: "e", + ousli: "ous", + ization: "ize", + ation: "ate", + ator: "ate", + alism: "al", + iveness: "ive", + fulness: "ful", + ousness: "ous", + aliti: "al", + iviti: "ive", + biliti: "ble", + logi: "log", + }, + n = { + icate: "ic", + ative: "", + alize: "al", + iciti: "ic", + ical: "ic", + ful: "", + ness: "", + }, + r = "[^aeiou]", + i = "[aeiouy]", + s = r + "[^aeiouy]*", + o = i + "[aeiou]*", + a = "^(" + s + ")?" + o + s, + l = "^(" + s + ")?" + o + s + "(" + o + ")?$", + u = "^(" + s + ")?" + o + s + o + s, + d = "^(" + s + ")?" + i, + m = new RegExp(a), + p = new RegExp(u), + b = new RegExp(l), + g = new RegExp(d), + L = /^(.+?)(ss|i)es$/, + f = /^(.+?)([^s])s$/, + y = /^(.+?)eed$/, + S = /^(.+?)(ed|ing)$/, + w = /.$/, + k = /(at|bl|iz)$/, + _ = new RegExp("([^aeiouylsz])\\1$"), + B = new RegExp("^" + s + i + "[^aeiouwxy]$"), + A = /^(.+?[^aeiou])y$/, + j = + /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/, + q = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/, + V = + /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/, + $ = /^(.+?)(s|t)(ion)$/, + C = /^(.+?)e$/, + z = /ll$/, + W = new RegExp("^" + s + i + "[^aeiouwxy]$"), + H = function (c) { + var v, P, T, h, x, O, M; + if (c.length < 3) return c; + if ( + ((T = c.substr(0, 1)), + T == "y" && (c = T.toUpperCase() + c.substr(1)), + (h = L), + (x = f), + h.test(c) + ? (c = c.replace(h, "$1$2")) + : x.test(c) && (c = c.replace(x, "$1$2")), + (h = y), + (x = S), + h.test(c)) + ) { + var E = h.exec(c); + (h = m), h.test(E[1]) && ((h = w), (c = c.replace(h, ""))); + } else if (x.test(c)) { + var E = x.exec(c); + (v = E[1]), + (x = g), + x.test(v) && + ((c = v), + (x = k), + (O = _), + (M = B), + x.test(c) + ? (c = c + "e") + : O.test(c) + ? ((h = w), (c = c.replace(h, ""))) + : M.test(c) && (c = c + "e")); + } + if (((h = A), h.test(c))) { + var E = h.exec(c); + (v = E[1]), (c = v + "i"); + } + if (((h = j), h.test(c))) { + var E = h.exec(c); + (v = E[1]), (P = E[2]), (h = m), h.test(v) && (c = v + e[P]); + } + if (((h = q), h.test(c))) { + var E = h.exec(c); + (v = E[1]), (P = E[2]), (h = m), h.test(v) && (c = v + n[P]); + } + if (((h = V), (x = $), h.test(c))) { + var E = h.exec(c); + (v = E[1]), (h = p), h.test(v) && (c = v); + } else if (x.test(c)) { + var E = x.exec(c); + (v = E[1] + E[2]), (x = p), x.test(v) && (c = v); + } + if (((h = C), h.test(c))) { + var E = h.exec(c); + (v = E[1]), + (h = p), + (x = b), + (O = W), + (h.test(v) || (x.test(v) && !O.test(v))) && (c = v); + } + return ( + (h = z), + (x = p), + h.test(c) && x.test(c) && ((h = w), (c = c.replace(h, ""))), + T == "y" && (c = T.toLowerCase() + c.substr(1)), + c + ); + }; + return function (R) { + return R.update(H); + }; + })()), + t.Pipeline.registerFunction(t.stemmer, "stemmer"); + (t.generateStopWordFilter = function (e) { + var n = e.reduce(function (r, i) { + return (r[i] = i), r; + }, {}); + return function (r) { + if (r && n[r.toString()] !== r.toString()) return r; + }; + }), + (t.stopWordFilter = t.generateStopWordFilter([ + "a", + "able", + "about", + "across", + "after", + "all", + "almost", + "also", + "am", + "among", + "an", + "and", + "any", + "are", + "as", + "at", + "be", + "because", + "been", + "but", + "by", + "can", + "cannot", + "could", + "dear", + "did", + "do", + "does", + "either", + "else", + "ever", + "every", + "for", + "from", + "get", + "got", + "had", + "has", + "have", + "he", + "her", + "hers", + "him", + "his", + "how", + "however", + "i", + "if", + "in", + "into", + "is", + "it", + "its", + "just", + "least", + "let", + "like", + "likely", + "may", + "me", + "might", + "most", + "must", + "my", + "neither", + "no", + "nor", + "not", + "of", + "off", + "often", + "on", + "only", + "or", + "other", + "our", + "own", + "rather", + "said", + "say", + "says", + "she", + "should", + "since", + "so", + "some", + "than", + "that", + "the", + "their", + "them", + "then", + "there", + "these", + "they", + "this", + "tis", + "to", + "too", + "twas", + "us", + "wants", + "was", + "we", + "were", + "what", + "when", + "where", + "which", + "while", + "who", + "whom", + "why", + "will", + "with", + "would", + "yet", + "you", + "your", + ])), + t.Pipeline.registerFunction(t.stopWordFilter, "stopWordFilter"); + (t.trimmer = function (e) { + return e.update(function (n) { + return n.replace(/^\W+/, "").replace(/\W+$/, ""); + }); + }), + t.Pipeline.registerFunction(t.trimmer, "trimmer"); + (t.TokenSet = function () { + (this.final = !1), + (this.edges = {}), + (this.id = t.TokenSet._nextId), + (t.TokenSet._nextId += 1); + }), + (t.TokenSet._nextId = 1), + (t.TokenSet.fromArray = function (e) { + for ( + var n = new t.TokenSet.Builder(), r = 0, i = e.length; + r < i; + r++ + ) + n.insert(e[r]); + return n.finish(), n.root; + }), + (t.TokenSet.fromClause = function (e) { + return "editDistance" in e + ? t.TokenSet.fromFuzzyString(e.term, e.editDistance) + : t.TokenSet.fromString(e.term); + }), + (t.TokenSet.fromFuzzyString = function (e, n) { + for ( + var r = new t.TokenSet(), + i = [{ node: r, editsRemaining: n, str: e }]; + i.length; + + ) { + var s = i.pop(); + if (s.str.length > 0) { + var o = s.str.charAt(0), + a; + o in s.node.edges + ? (a = s.node.edges[o]) + : ((a = new t.TokenSet()), (s.node.edges[o] = a)), + s.str.length == 1 && (a.final = !0), + i.push({ + node: a, + editsRemaining: s.editsRemaining, + str: s.str.slice(1), + }); + } + if (s.editsRemaining != 0) { + if ("*" in s.node.edges) var l = s.node.edges["*"]; + else { + var l = new t.TokenSet(); + s.node.edges["*"] = l; + } + if ( + (s.str.length == 0 && (l.final = !0), + i.push({ + node: l, + editsRemaining: s.editsRemaining - 1, + str: s.str, + }), + s.str.length > 1 && + i.push({ + node: s.node, + editsRemaining: s.editsRemaining - 1, + str: s.str.slice(1), + }), + s.str.length == 1 && (s.node.final = !0), + s.str.length >= 1) + ) { + if ("*" in s.node.edges) var u = s.node.edges["*"]; + else { + var u = new t.TokenSet(); + s.node.edges["*"] = u; + } + s.str.length == 1 && (u.final = !0), + i.push({ + node: u, + editsRemaining: s.editsRemaining - 1, + str: s.str.slice(1), + }); + } + if (s.str.length > 1) { + var d = s.str.charAt(0), + m = s.str.charAt(1), + p; + m in s.node.edges + ? (p = s.node.edges[m]) + : ((p = new t.TokenSet()), (s.node.edges[m] = p)), + s.str.length == 1 && (p.final = !0), + i.push({ + node: p, + editsRemaining: s.editsRemaining - 1, + str: d + s.str.slice(2), + }); + } + } + } + return r; + }), + (t.TokenSet.fromString = function (e) { + for ( + var n = new t.TokenSet(), r = n, i = 0, s = e.length; + i < s; + i++ + ) { + var o = e[i], + a = i == s - 1; + if (o == "*") (n.edges[o] = n), (n.final = a); + else { + var l = new t.TokenSet(); + (l.final = a), (n.edges[o] = l), (n = l); + } + } + return r; + }), + (t.TokenSet.prototype.toArray = function () { + for (var e = [], n = [{ prefix: "", node: this }]; n.length; ) { + var r = n.pop(), + i = Object.keys(r.node.edges), + s = i.length; + r.node.final && (r.prefix.charAt(0), e.push(r.prefix)); + for (var o = 0; o < s; o++) { + var a = i[o]; + n.push({ prefix: r.prefix.concat(a), node: r.node.edges[a] }); + } + } + return e; + }), + (t.TokenSet.prototype.toString = function () { + if (this._str) return this._str; + for ( + var e = this.final ? "1" : "0", + n = Object.keys(this.edges).sort(), + r = n.length, + i = 0; + i < r; + i++ + ) { + var s = n[i], + o = this.edges[s]; + e = e + s + o.id; + } + return e; + }), + (t.TokenSet.prototype.intersect = function (e) { + for ( + var n = new t.TokenSet(), + r = void 0, + i = [{ qNode: e, output: n, node: this }]; + i.length; + + ) { + r = i.pop(); + for ( + var s = Object.keys(r.qNode.edges), + o = s.length, + a = Object.keys(r.node.edges), + l = a.length, + u = 0; + u < o; + u++ + ) + for (var d = s[u], m = 0; m < l; m++) { + var p = a[m]; + if (p == d || d == "*") { + var b = r.node.edges[p], + g = r.qNode.edges[d], + L = b.final && g.final, + f = void 0; + p in r.output.edges + ? ((f = r.output.edges[p]), (f.final = f.final || L)) + : ((f = new t.TokenSet()), + (f.final = L), + (r.output.edges[p] = f)), + i.push({ qNode: g, output: f, node: b }); + } + } + } + return n; + }), + (t.TokenSet.Builder = function () { + (this.previousWord = ""), + (this.root = new t.TokenSet()), + (this.uncheckedNodes = []), + (this.minimizedNodes = {}); + }), + (t.TokenSet.Builder.prototype.insert = function (e) { + var n, + r = 0; + if (e < this.previousWord) + throw new Error("Out of order word insertion"); + for ( + var i = 0; + i < e.length && + i < this.previousWord.length && + e[i] == this.previousWord[i]; + i++ + ) + r++; + this.minimize(r), + this.uncheckedNodes.length == 0 + ? (n = this.root) + : (n = this.uncheckedNodes[this.uncheckedNodes.length - 1].child); + for (var i = r; i < e.length; i++) { + var s = new t.TokenSet(), + o = e[i]; + (n.edges[o] = s), + this.uncheckedNodes.push({ parent: n, char: o, child: s }), + (n = s); + } + (n.final = !0), (this.previousWord = e); + }), + (t.TokenSet.Builder.prototype.finish = function () { + this.minimize(0); + }), + (t.TokenSet.Builder.prototype.minimize = function (e) { + for (var n = this.uncheckedNodes.length - 1; n >= e; n--) { + var r = this.uncheckedNodes[n], + i = r.child.toString(); + i in this.minimizedNodes + ? (r.parent.edges[r.char] = this.minimizedNodes[i]) + : ((r.child._str = i), (this.minimizedNodes[i] = r.child)), + this.uncheckedNodes.pop(); + } + }); + (t.Index = function (e) { + (this.invertedIndex = e.invertedIndex), + (this.fieldVectors = e.fieldVectors), + (this.tokenSet = e.tokenSet), + (this.fields = e.fields), + (this.pipeline = e.pipeline); + }), + (t.Index.prototype.search = function (e) { + return this.query(function (n) { + var r = new t.QueryParser(e, n); + r.parse(); + }); + }), + (t.Index.prototype.query = function (e) { + for ( + var n = new t.Query(this.fields), + r = Object.create(null), + i = Object.create(null), + s = Object.create(null), + o = Object.create(null), + a = Object.create(null), + l = 0; + l < this.fields.length; + l++ + ) + i[this.fields[l]] = new t.Vector(); + e.call(n, n); + for (var l = 0; l < n.clauses.length; l++) { + var u = n.clauses[l], + d = null, + m = t.Set.empty; + u.usePipeline + ? (d = this.pipeline.runString(u.term, { fields: u.fields })) + : (d = [u.term]); + for (var p = 0; p < d.length; p++) { + var b = d[p]; + u.term = b; + var g = t.TokenSet.fromClause(u), + L = this.tokenSet.intersect(g).toArray(); + if (L.length === 0 && u.presence === t.Query.presence.REQUIRED) { + for (var f = 0; f < u.fields.length; f++) { + var y = u.fields[f]; + o[y] = t.Set.empty; + } + break; + } + for (var S = 0; S < L.length; S++) + for ( + var w = L[S], k = this.invertedIndex[w], _ = k._index, f = 0; + f < u.fields.length; + f++ + ) { + var y = u.fields[f], + B = k[y], + A = Object.keys(B), + j = w + "/" + y, + q = new t.Set(A); + if ( + (u.presence == t.Query.presence.REQUIRED && + ((m = m.union(q)), + o[y] === void 0 && (o[y] = t.Set.complete)), + u.presence == t.Query.presence.PROHIBITED) + ) { + a[y] === void 0 && (a[y] = t.Set.empty), + (a[y] = a[y].union(q)); + continue; + } + if ( + (i[y].upsert(_, u.boost, function (Ie, Ce) { + return Ie + Ce; + }), + !s[j]) + ) { + for (var V = 0; V < A.length; V++) { + var $ = A[V], + C = new t.FieldRef($, y), + z = B[$], + W; + (W = r[C]) === void 0 + ? (r[C] = new t.MatchData(w, y, z)) + : W.add(w, y, z); + } + s[j] = !0; + } + } + } + if (u.presence === t.Query.presence.REQUIRED) + for (var f = 0; f < u.fields.length; f++) { + var y = u.fields[f]; + o[y] = o[y].intersect(m); + } + } + for ( + var H = t.Set.complete, R = t.Set.empty, l = 0; + l < this.fields.length; + l++ + ) { + var y = this.fields[l]; + o[y] && (H = H.intersect(o[y])), a[y] && (R = R.union(a[y])); + } + var c = Object.keys(r), + v = [], + P = Object.create(null); + if (n.isNegated()) { + c = Object.keys(this.fieldVectors); + for (var l = 0; l < c.length; l++) { + var C = c[l], + T = t.FieldRef.fromString(C); + r[C] = new t.MatchData(); + } + } + for (var l = 0; l < c.length; l++) { + var T = t.FieldRef.fromString(c[l]), + h = T.docRef; + if (H.contains(h) && !R.contains(h)) { + var x = this.fieldVectors[T], + O = i[T.fieldName].similarity(x), + M; + if ((M = P[h]) !== void 0) + (M.score += O), M.matchData.combine(r[T]); + else { + var E = { ref: h, score: O, matchData: r[T] }; + (P[h] = E), v.push(E); + } + } + } + return v.sort(function (ke, Qe) { + return Qe.score - ke.score; + }); + }), + (t.Index.prototype.toJSON = function () { + var e = Object.keys(this.invertedIndex) + .sort() + .map(function (r) { + return [r, this.invertedIndex[r]]; + }, this), + n = Object.keys(this.fieldVectors).map(function (r) { + return [r, this.fieldVectors[r].toJSON()]; + }, this); + return { + version: t.version, + fields: this.fields, + fieldVectors: n, + invertedIndex: e, + pipeline: this.pipeline.toJSON(), + }; + }), + (t.Index.load = function (e) { + var n = {}, + r = {}, + i = e.fieldVectors, + s = Object.create(null), + o = e.invertedIndex, + a = new t.TokenSet.Builder(), + l = t.Pipeline.load(e.pipeline); + e.version != t.version && + t.utils.warn( + "Version mismatch when loading serialised index. Current version of lunr '" + + t.version + + "' does not match serialized index '" + + e.version + + "'", + ); + for (var u = 0; u < i.length; u++) { + var d = i[u], + m = d[0], + p = d[1]; + r[m] = new t.Vector(p); + } + for (var u = 0; u < o.length; u++) { + var d = o[u], + b = d[0], + g = d[1]; + a.insert(b), (s[b] = g); + } + return ( + a.finish(), + (n.fields = e.fields), + (n.fieldVectors = r), + (n.invertedIndex = s), + (n.tokenSet = a.root), + (n.pipeline = l), + new t.Index(n) + ); + }); + (t.Builder = function () { + (this._ref = "id"), + (this._fields = Object.create(null)), + (this._documents = Object.create(null)), + (this.invertedIndex = Object.create(null)), + (this.fieldTermFrequencies = {}), + (this.fieldLengths = {}), + (this.tokenizer = t.tokenizer), + (this.pipeline = new t.Pipeline()), + (this.searchPipeline = new t.Pipeline()), + (this.documentCount = 0), + (this._b = 0.75), + (this._k1 = 1.2), + (this.termIndex = 0), + (this.metadataWhitelist = []); + }), + (t.Builder.prototype.ref = function (e) { + this._ref = e; + }), + (t.Builder.prototype.field = function (e, n) { + if (/\//.test(e)) + throw new RangeError( + "Field '" + e + "' contains illegal character '/'", + ); + this._fields[e] = n || {}; + }), + (t.Builder.prototype.b = function (e) { + e < 0 ? (this._b = 0) : e > 1 ? (this._b = 1) : (this._b = e); + }), + (t.Builder.prototype.k1 = function (e) { + this._k1 = e; + }), + (t.Builder.prototype.add = function (e, n) { + var r = e[this._ref], + i = Object.keys(this._fields); + (this._documents[r] = n || {}), (this.documentCount += 1); + for (var s = 0; s < i.length; s++) { + var o = i[s], + a = this._fields[o].extractor, + l = a ? a(e) : e[o], + u = this.tokenizer(l, { fields: [o] }), + d = this.pipeline.run(u), + m = new t.FieldRef(r, o), + p = Object.create(null); + (this.fieldTermFrequencies[m] = p), + (this.fieldLengths[m] = 0), + (this.fieldLengths[m] += d.length); + for (var b = 0; b < d.length; b++) { + var g = d[b]; + if ( + (p[g] == null && (p[g] = 0), + (p[g] += 1), + this.invertedIndex[g] == null) + ) { + var L = Object.create(null); + (L._index = this.termIndex), (this.termIndex += 1); + for (var f = 0; f < i.length; f++) + L[i[f]] = Object.create(null); + this.invertedIndex[g] = L; + } + this.invertedIndex[g][o][r] == null && + (this.invertedIndex[g][o][r] = Object.create(null)); + for (var y = 0; y < this.metadataWhitelist.length; y++) { + var S = this.metadataWhitelist[y], + w = g.metadata[S]; + this.invertedIndex[g][o][r][S] == null && + (this.invertedIndex[g][o][r][S] = []), + this.invertedIndex[g][o][r][S].push(w); + } + } + } + }), + (t.Builder.prototype.calculateAverageFieldLengths = function () { + for ( + var e = Object.keys(this.fieldLengths), + n = e.length, + r = {}, + i = {}, + s = 0; + s < n; + s++ + ) { + var o = t.FieldRef.fromString(e[s]), + a = o.fieldName; + i[a] || (i[a] = 0), + (i[a] += 1), + r[a] || (r[a] = 0), + (r[a] += this.fieldLengths[o]); + } + for (var l = Object.keys(this._fields), s = 0; s < l.length; s++) { + var u = l[s]; + r[u] = r[u] / i[u]; + } + this.averageFieldLength = r; + }), + (t.Builder.prototype.createFieldVectors = function () { + for ( + var e = {}, + n = Object.keys(this.fieldTermFrequencies), + r = n.length, + i = Object.create(null), + s = 0; + s < r; + s++ + ) { + for ( + var o = t.FieldRef.fromString(n[s]), + a = o.fieldName, + l = this.fieldLengths[o], + u = new t.Vector(), + d = this.fieldTermFrequencies[o], + m = Object.keys(d), + p = m.length, + b = this._fields[a].boost || 1, + g = this._documents[o.docRef].boost || 1, + L = 0; + L < p; + L++ + ) { + var f = m[L], + y = d[f], + S = this.invertedIndex[f]._index, + w, + k, + _; + i[f] === void 0 + ? ((w = t.idf(this.invertedIndex[f], this.documentCount)), + (i[f] = w)) + : (w = i[f]), + (k = + (w * ((this._k1 + 1) * y)) / + (this._k1 * + (1 - this._b + this._b * (l / this.averageFieldLength[a])) + + y)), + (k *= b), + (k *= g), + (_ = Math.round(k * 1e3) / 1e3), + u.insert(S, _); + } + e[o] = u; + } + this.fieldVectors = e; + }), + (t.Builder.prototype.createTokenSet = function () { + this.tokenSet = t.TokenSet.fromArray( + Object.keys(this.invertedIndex).sort(), + ); + }), + (t.Builder.prototype.build = function () { + return ( + this.calculateAverageFieldLengths(), + this.createFieldVectors(), + this.createTokenSet(), + new t.Index({ + invertedIndex: this.invertedIndex, + fieldVectors: this.fieldVectors, + tokenSet: this.tokenSet, + fields: Object.keys(this._fields), + pipeline: this.searchPipeline, + }) + ); + }), + (t.Builder.prototype.use = function (e) { + var n = Array.prototype.slice.call(arguments, 1); + n.unshift(this), e.apply(this, n); + }), + (t.MatchData = function (e, n, r) { + for ( + var i = Object.create(null), s = Object.keys(r || {}), o = 0; + o < s.length; + o++ + ) { + var a = s[o]; + i[a] = r[a].slice(); + } + (this.metadata = Object.create(null)), + e !== void 0 && + ((this.metadata[e] = Object.create(null)), + (this.metadata[e][n] = i)); + }), + (t.MatchData.prototype.combine = function (e) { + for (var n = Object.keys(e.metadata), r = 0; r < n.length; r++) { + var i = n[r], + s = Object.keys(e.metadata[i]); + this.metadata[i] == null && + (this.metadata[i] = Object.create(null)); + for (var o = 0; o < s.length; o++) { + var a = s[o], + l = Object.keys(e.metadata[i][a]); + this.metadata[i][a] == null && + (this.metadata[i][a] = Object.create(null)); + for (var u = 0; u < l.length; u++) { + var d = l[u]; + this.metadata[i][a][d] == null + ? (this.metadata[i][a][d] = e.metadata[i][a][d]) + : (this.metadata[i][a][d] = this.metadata[i][a][d].concat( + e.metadata[i][a][d], + )); + } + } + } + }), + (t.MatchData.prototype.add = function (e, n, r) { + if (!(e in this.metadata)) { + (this.metadata[e] = Object.create(null)), (this.metadata[e][n] = r); + return; + } + if (!(n in this.metadata[e])) { + this.metadata[e][n] = r; + return; + } + for (var i = Object.keys(r), s = 0; s < i.length; s++) { + var o = i[s]; + o in this.metadata[e][n] + ? (this.metadata[e][n][o] = this.metadata[e][n][o].concat(r[o])) + : (this.metadata[e][n][o] = r[o]); + } + }), + (t.Query = function (e) { + (this.clauses = []), (this.allFields = e); + }), + (t.Query.wildcard = new String("*")), + (t.Query.wildcard.NONE = 0), + (t.Query.wildcard.LEADING = 1), + (t.Query.wildcard.TRAILING = 2), + (t.Query.presence = { OPTIONAL: 1, REQUIRED: 2, PROHIBITED: 3 }), + (t.Query.prototype.clause = function (e) { + return ( + "fields" in e || (e.fields = this.allFields), + "boost" in e || (e.boost = 1), + "usePipeline" in e || (e.usePipeline = !0), + "wildcard" in e || (e.wildcard = t.Query.wildcard.NONE), + e.wildcard & t.Query.wildcard.LEADING && + e.term.charAt(0) != t.Query.wildcard && + (e.term = "*" + e.term), + e.wildcard & t.Query.wildcard.TRAILING && + e.term.slice(-1) != t.Query.wildcard && + (e.term = "" + e.term + "*"), + "presence" in e || (e.presence = t.Query.presence.OPTIONAL), + this.clauses.push(e), + this + ); + }), + (t.Query.prototype.isNegated = function () { + for (var e = 0; e < this.clauses.length; e++) + if (this.clauses[e].presence != t.Query.presence.PROHIBITED) + return !1; + return !0; + }), + (t.Query.prototype.term = function (e, n) { + if (Array.isArray(e)) + return ( + e.forEach(function (i) { + this.term(i, t.utils.clone(n)); + }, this), + this + ); + var r = n || {}; + return (r.term = e.toString()), this.clause(r), this; + }), + (t.QueryParseError = function (e, n, r) { + (this.name = "QueryParseError"), + (this.message = e), + (this.start = n), + (this.end = r); + }), + (t.QueryParseError.prototype = new Error()), + (t.QueryLexer = function (e) { + (this.lexemes = []), + (this.str = e), + (this.length = e.length), + (this.pos = 0), + (this.start = 0), + (this.escapeCharPositions = []); + }), + (t.QueryLexer.prototype.run = function () { + for (var e = t.QueryLexer.lexText; e; ) e = e(this); + }), + (t.QueryLexer.prototype.sliceString = function () { + for ( + var e = [], n = this.start, r = this.pos, i = 0; + i < this.escapeCharPositions.length; + i++ + ) + (r = this.escapeCharPositions[i]), + e.push(this.str.slice(n, r)), + (n = r + 1); + return ( + e.push(this.str.slice(n, this.pos)), + (this.escapeCharPositions.length = 0), + e.join("") + ); + }), + (t.QueryLexer.prototype.emit = function (e) { + this.lexemes.push({ + type: e, + str: this.sliceString(), + start: this.start, + end: this.pos, + }), + (this.start = this.pos); + }), + (t.QueryLexer.prototype.escapeCharacter = function () { + this.escapeCharPositions.push(this.pos - 1), (this.pos += 1); + }), + (t.QueryLexer.prototype.next = function () { + if (this.pos >= this.length) return t.QueryLexer.EOS; + var e = this.str.charAt(this.pos); + return (this.pos += 1), e; + }), + (t.QueryLexer.prototype.width = function () { + return this.pos - this.start; + }), + (t.QueryLexer.prototype.ignore = function () { + this.start == this.pos && (this.pos += 1), (this.start = this.pos); + }), + (t.QueryLexer.prototype.backup = function () { + this.pos -= 1; + }), + (t.QueryLexer.prototype.acceptDigitRun = function () { + var e, n; + do (e = this.next()), (n = e.charCodeAt(0)); + while (n > 47 && n < 58); + e != t.QueryLexer.EOS && this.backup(); + }), + (t.QueryLexer.prototype.more = function () { + return this.pos < this.length; + }), + (t.QueryLexer.EOS = "EOS"), + (t.QueryLexer.FIELD = "FIELD"), + (t.QueryLexer.TERM = "TERM"), + (t.QueryLexer.EDIT_DISTANCE = "EDIT_DISTANCE"), + (t.QueryLexer.BOOST = "BOOST"), + (t.QueryLexer.PRESENCE = "PRESENCE"), + (t.QueryLexer.lexField = function (e) { + return ( + e.backup(), + e.emit(t.QueryLexer.FIELD), + e.ignore(), + t.QueryLexer.lexText + ); + }), + (t.QueryLexer.lexTerm = function (e) { + if ( + (e.width() > 1 && (e.backup(), e.emit(t.QueryLexer.TERM)), + e.ignore(), + e.more()) + ) + return t.QueryLexer.lexText; + }), + (t.QueryLexer.lexEditDistance = function (e) { + return ( + e.ignore(), + e.acceptDigitRun(), + e.emit(t.QueryLexer.EDIT_DISTANCE), + t.QueryLexer.lexText + ); + }), + (t.QueryLexer.lexBoost = function (e) { + return ( + e.ignore(), + e.acceptDigitRun(), + e.emit(t.QueryLexer.BOOST), + t.QueryLexer.lexText + ); + }), + (t.QueryLexer.lexEOS = function (e) { + e.width() > 0 && e.emit(t.QueryLexer.TERM); + }), + (t.QueryLexer.termSeparator = t.tokenizer.separator), + (t.QueryLexer.lexText = function (e) { + for (;;) { + var n = e.next(); + if (n == t.QueryLexer.EOS) return t.QueryLexer.lexEOS; + if (n.charCodeAt(0) == 92) { + e.escapeCharacter(); + continue; + } + if (n == ":") return t.QueryLexer.lexField; + if (n == "~") + return ( + e.backup(), + e.width() > 0 && e.emit(t.QueryLexer.TERM), + t.QueryLexer.lexEditDistance + ); + if (n == "^") + return ( + e.backup(), + e.width() > 0 && e.emit(t.QueryLexer.TERM), + t.QueryLexer.lexBoost + ); + if ((n == "+" && e.width() === 1) || (n == "-" && e.width() === 1)) + return e.emit(t.QueryLexer.PRESENCE), t.QueryLexer.lexText; + if (n.match(t.QueryLexer.termSeparator)) + return t.QueryLexer.lexTerm; + } + }), + (t.QueryParser = function (e, n) { + (this.lexer = new t.QueryLexer(e)), + (this.query = n), + (this.currentClause = {}), + (this.lexemeIdx = 0); + }), + (t.QueryParser.prototype.parse = function () { + this.lexer.run(), (this.lexemes = this.lexer.lexemes); + for (var e = t.QueryParser.parseClause; e; ) e = e(this); + return this.query; + }), + (t.QueryParser.prototype.peekLexeme = function () { + return this.lexemes[this.lexemeIdx]; + }), + (t.QueryParser.prototype.consumeLexeme = function () { + var e = this.peekLexeme(); + return (this.lexemeIdx += 1), e; + }), + (t.QueryParser.prototype.nextClause = function () { + var e = this.currentClause; + this.query.clause(e), (this.currentClause = {}); + }), + (t.QueryParser.parseClause = function (e) { + var n = e.peekLexeme(); + if (n != null) + switch (n.type) { + case t.QueryLexer.PRESENCE: + return t.QueryParser.parsePresence; + case t.QueryLexer.FIELD: + return t.QueryParser.parseField; + case t.QueryLexer.TERM: + return t.QueryParser.parseTerm; + default: + var r = "expected either a field or a term, found " + n.type; + throw ( + (n.str.length >= 1 && (r += " with value '" + n.str + "'"), + new t.QueryParseError(r, n.start, n.end)) + ); + } + }), + (t.QueryParser.parsePresence = function (e) { + var n = e.consumeLexeme(); + if (n != null) { + switch (n.str) { + case "-": + e.currentClause.presence = t.Query.presence.PROHIBITED; + break; + case "+": + e.currentClause.presence = t.Query.presence.REQUIRED; + break; + default: + var r = "unrecognised presence operator'" + n.str + "'"; + throw new t.QueryParseError(r, n.start, n.end); + } + var i = e.peekLexeme(); + if (i == null) { + var r = "expecting term or field, found nothing"; + throw new t.QueryParseError(r, n.start, n.end); + } + switch (i.type) { + case t.QueryLexer.FIELD: + return t.QueryParser.parseField; + case t.QueryLexer.TERM: + return t.QueryParser.parseTerm; + default: + var r = "expecting term or field, found '" + i.type + "'"; + throw new t.QueryParseError(r, i.start, i.end); + } + } + }), + (t.QueryParser.parseField = function (e) { + var n = e.consumeLexeme(); + if (n != null) { + if (e.query.allFields.indexOf(n.str) == -1) { + var r = e.query.allFields + .map(function (o) { + return "'" + o + "'"; + }) + .join(", "), + i = "unrecognised field '" + n.str + "', possible fields: " + r; + throw new t.QueryParseError(i, n.start, n.end); + } + e.currentClause.fields = [n.str]; + var s = e.peekLexeme(); + if (s == null) { + var i = "expecting term, found nothing"; + throw new t.QueryParseError(i, n.start, n.end); + } + switch (s.type) { + case t.QueryLexer.TERM: + return t.QueryParser.parseTerm; + default: + var i = "expecting term, found '" + s.type + "'"; + throw new t.QueryParseError(i, s.start, s.end); + } + } + }), + (t.QueryParser.parseTerm = function (e) { + var n = e.consumeLexeme(); + if (n != null) { + (e.currentClause.term = n.str.toLowerCase()), + n.str.indexOf("*") != -1 && (e.currentClause.usePipeline = !1); + var r = e.peekLexeme(); + if (r == null) { + e.nextClause(); + return; + } + switch (r.type) { + case t.QueryLexer.TERM: + return e.nextClause(), t.QueryParser.parseTerm; + case t.QueryLexer.FIELD: + return e.nextClause(), t.QueryParser.parseField; + case t.QueryLexer.EDIT_DISTANCE: + return t.QueryParser.parseEditDistance; + case t.QueryLexer.BOOST: + return t.QueryParser.parseBoost; + case t.QueryLexer.PRESENCE: + return e.nextClause(), t.QueryParser.parsePresence; + default: + var i = "Unexpected lexeme type '" + r.type + "'"; + throw new t.QueryParseError(i, r.start, r.end); + } + } + }), + (t.QueryParser.parseEditDistance = function (e) { + var n = e.consumeLexeme(); + if (n != null) { + var r = parseInt(n.str, 10); + if (isNaN(r)) { + var i = "edit distance must be numeric"; + throw new t.QueryParseError(i, n.start, n.end); + } + e.currentClause.editDistance = r; + var s = e.peekLexeme(); + if (s == null) { + e.nextClause(); + return; + } + switch (s.type) { + case t.QueryLexer.TERM: + return e.nextClause(), t.QueryParser.parseTerm; + case t.QueryLexer.FIELD: + return e.nextClause(), t.QueryParser.parseField; + case t.QueryLexer.EDIT_DISTANCE: + return t.QueryParser.parseEditDistance; + case t.QueryLexer.BOOST: + return t.QueryParser.parseBoost; + case t.QueryLexer.PRESENCE: + return e.nextClause(), t.QueryParser.parsePresence; + default: + var i = "Unexpected lexeme type '" + s.type + "'"; + throw new t.QueryParseError(i, s.start, s.end); + } + } + }), + (t.QueryParser.parseBoost = function (e) { + var n = e.consumeLexeme(); + if (n != null) { + var r = parseInt(n.str, 10); + if (isNaN(r)) { + var i = "boost must be numeric"; + throw new t.QueryParseError(i, n.start, n.end); + } + e.currentClause.boost = r; + var s = e.peekLexeme(); + if (s == null) { + e.nextClause(); + return; + } + switch (s.type) { + case t.QueryLexer.TERM: + return e.nextClause(), t.QueryParser.parseTerm; + case t.QueryLexer.FIELD: + return e.nextClause(), t.QueryParser.parseField; + case t.QueryLexer.EDIT_DISTANCE: + return t.QueryParser.parseEditDistance; + case t.QueryLexer.BOOST: + return t.QueryParser.parseBoost; + case t.QueryLexer.PRESENCE: + return e.nextClause(), t.QueryParser.parsePresence; + default: + var i = "Unexpected lexeme type '" + s.type + "'"; + throw new t.QueryParseError(i, s.start, s.end); + } + } + }), + (function (e, n) { + typeof define == "function" && define.amd + ? define(n) + : typeof ae == "object" + ? (le.exports = n()) + : (e.lunr = n()); + })(this, function () { + return t; + }); + })(); + }); + var se = []; + function G(t, e) { + se.push({ selector: e, constructor: t }); + } + var U = class { + constructor() { + this.alwaysVisibleMember = null; + this.createComponents(document.body), + this.ensureFocusedElementVisible(), + this.listenForCodeCopies(), + window.addEventListener("hashchange", () => + this.ensureFocusedElementVisible(), + ), + document.body.style.display || + (this.ensureFocusedElementVisible(), + this.updateIndexVisibility(), + this.scrollToHash()); + } + createComponents(e) { + se.forEach((n) => { + e.querySelectorAll(n.selector).forEach((r) => { + r.dataset.hasInstance || + (new n.constructor({ el: r, app: this }), + (r.dataset.hasInstance = String(!0))); + }); + }); + } + filterChanged() { + this.ensureFocusedElementVisible(); + } + showPage() { + document.body.style.display && + (document.body.style.removeProperty("display"), + this.ensureFocusedElementVisible(), + this.updateIndexVisibility(), + this.scrollToHash()); + } + scrollToHash() { + if (location.hash) { + let e = document.getElementById(location.hash.substring(1)); + if (!e) return; + e.scrollIntoView({ behavior: "instant", block: "start" }); + } + } + ensureActivePageVisible() { + let e = document.querySelector(".tsd-navigation .current"), + n = e?.parentElement; + for (; n && !n.classList.contains(".tsd-navigation"); ) + n instanceof HTMLDetailsElement && (n.open = !0), (n = n.parentElement); + if (e && !Ve(e)) { + let r = + e.getBoundingClientRect().top - + document.documentElement.clientHeight / 4; + (document.querySelector(".site-menu").scrollTop = r), + (document.querySelector(".col-sidebar").scrollTop = r); + } + } + updateIndexVisibility() { + let e = document.querySelector(".tsd-index-content"), + n = e?.open; + e && (e.open = !0), + document.querySelectorAll(".tsd-index-section").forEach((r) => { + r.style.display = "block"; + let i = Array.from(r.querySelectorAll(".tsd-index-link")).every( + (s) => s.offsetParent == null, + ); + r.style.display = i ? "none" : "block"; + }), + e && (e.open = n); + } + ensureFocusedElementVisible() { + if ( + (this.alwaysVisibleMember && + (this.alwaysVisibleMember.classList.remove("always-visible"), + this.alwaysVisibleMember.firstElementChild.remove(), + (this.alwaysVisibleMember = null)), + !location.hash) + ) + return; + let e = document.getElementById(location.hash.substring(1)); + if (!e) return; + let n = e.parentElement; + for (; n && n.tagName !== "SECTION"; ) n = n.parentElement; + if (!n) return; + let r = n.offsetParent == null, + i = n; + for (; i !== document.body; ) + i instanceof HTMLDetailsElement && (i.open = !0), (i = i.parentElement); + if (n.offsetParent == null) { + (this.alwaysVisibleMember = n), n.classList.add("always-visible"); + let s = document.createElement("p"); + s.classList.add("warning"), + (s.textContent = window.translations.normally_hidden), + n.prepend(s); + } + r && e.scrollIntoView(); + } + listenForCodeCopies() { + document.querySelectorAll("pre > button").forEach((e) => { + let n; + e.addEventListener("click", () => { + e.previousElementSibling instanceof HTMLElement && + navigator.clipboard.writeText( + e.previousElementSibling.innerText.trim(), + ), + (e.textContent = window.translations.copied), + e.classList.add("visible"), + clearTimeout(n), + (n = setTimeout(() => { + e.classList.remove("visible"), + (n = setTimeout(() => { + e.textContent = window.translations.copy; + }, 100)); + }, 1e3)); + }); + }); + } + }; + function Ve(t) { + let e = t.getBoundingClientRect(), + n = Math.max(document.documentElement.clientHeight, window.innerHeight); + return !(e.bottom < 0 || e.top - n >= 0); + } + var oe = (t, e = 100) => { + let n; + return () => { + clearTimeout(n), (n = setTimeout(() => t(), e)); + }; + }; + var pe = Ae(ue()); + async function ce(t, e) { + if (!window.searchData) return; + let n = await fetch(window.searchData), + r = new Blob([await n.arrayBuffer()]) + .stream() + .pipeThrough(new DecompressionStream("gzip")), + i = await new Response(r).json(); + (t.data = i), + (t.index = pe.Index.load(i.index)), + e.classList.remove("loading"), + e.classList.add("ready"); + } + function fe() { + let t = document.getElementById("tsd-search"); + if (!t) return; + let e = { base: t.dataset.base + "/" }, + n = document.getElementById("tsd-search-script"); + t.classList.add("loading"), + n && + (n.addEventListener("error", () => { + t.classList.remove("loading"), t.classList.add("failure"); + }), + n.addEventListener("load", () => { + ce(e, t); + }), + ce(e, t)); + let r = document.querySelector("#tsd-search input"), + i = document.querySelector("#tsd-search .results"); + if (!r || !i) + throw new Error( + "The input field or the result list wrapper was not found", + ); + i.addEventListener("mouseup", () => { + te(t); + }), + r.addEventListener("focus", () => t.classList.add("has-focus")), + He(t, i, r, e); + } + function He(t, e, n, r) { + n.addEventListener( + "input", + oe(() => { + Ne(t, e, n, r); + }, 200), + ), + n.addEventListener("keydown", (i) => { + i.key == "Enter" + ? Be(e, t) + : i.key == "ArrowUp" + ? (de(e, n, -1), i.preventDefault()) + : i.key === "ArrowDown" && (de(e, n, 1), i.preventDefault()); + }), + document.body.addEventListener("keypress", (i) => { + i.altKey || + i.ctrlKey || + i.metaKey || + (!n.matches(":focus") && + i.key === "/" && + (i.preventDefault(), n.focus())); + }), + document.body.addEventListener("keyup", (i) => { + t.classList.contains("has-focus") && + (i.key === "Escape" || + (!e.matches(":focus-within") && !n.matches(":focus"))) && + (n.blur(), te(t)); + }); + } + function te(t) { + t.classList.remove("has-focus"); + } + function Ne(t, e, n, r) { + if (!r.index || !r.data) return; + e.textContent = ""; + let i = n.value.trim(), + s; + if (i) { + let o = i + .split(" ") + .map((a) => (a.length ? `*${a}*` : "")) + .join(" "); + s = r.index.search(o); + } else s = []; + for (let o = 0; o < s.length; o++) { + let a = s[o], + l = r.data.rows[Number(a.ref)], + u = 1; + l.name.toLowerCase().startsWith(i.toLowerCase()) && + (u *= 1 + 1 / (1 + Math.abs(l.name.length - i.length))), + (a.score *= u); + } + if (s.length === 0) { + let o = document.createElement("li"); + o.classList.add("no-results"); + let a = document.createElement("span"); + (a.textContent = "No results found"), o.appendChild(a), e.appendChild(o); + } + s.sort((o, a) => a.score - o.score); + for (let o = 0, a = Math.min(10, s.length); o < a; o++) { + let l = r.data.rows[Number(s[o].ref)], + u = ``, + d = he(l.name, i); + globalThis.DEBUG_SEARCH_WEIGHTS && + (d += ` (score: ${s[o].score.toFixed(2)})`), + l.parent && + (d = ` + ${he(l.parent, i)}.${d}`); + let m = document.createElement("li"); + m.classList.value = l.classes ?? ""; + let p = document.createElement("a"); + (p.href = r.base + l.url), + (p.innerHTML = u + d), + m.append(p), + p.addEventListener("focus", () => { + e.querySelector(".current")?.classList.remove("current"), + m.classList.add("current"); + }), + e.appendChild(m); + } + } + function de(t, e, n) { + let r = t.querySelector(".current"); + if (!r) + (r = t.querySelector(n == 1 ? "li:first-child" : "li:last-child")), + r && r.classList.add("current"); + else { + let i = r; + if (n === 1) + do i = i.nextElementSibling ?? void 0; + while (i instanceof HTMLElement && i.offsetParent == null); + else + do i = i.previousElementSibling ?? void 0; + while (i instanceof HTMLElement && i.offsetParent == null); + i + ? (r.classList.remove("current"), i.classList.add("current")) + : n === -1 && (r.classList.remove("current"), e.focus()); + } + } + function Be(t, e) { + let n = t.querySelector(".current"); + if ((n || (n = t.querySelector("li:first-child")), n)) { + let r = n.querySelector("a"); + r && (window.location.href = r.href), te(e); + } + } + function he(t, e) { + if (e === "") return t; + let n = t.toLocaleLowerCase(), + r = e.toLocaleLowerCase(), + i = [], + s = 0, + o = n.indexOf(r); + for (; o != -1; ) + i.push( + ee(t.substring(s, o)), + `${ee(t.substring(o, o + r.length))}`, + ), + (s = o + r.length), + (o = n.indexOf(r, s)); + return i.push(ee(t.substring(s))), i.join(""); + } + var je = { + "&": "&", + "<": "<", + ">": ">", + "'": "'", + '"': """, + }; + function ee(t) { + return t.replace(/[&<>"'"]/g, (e) => je[e]); + } + var I = class { + constructor(e) { + (this.el = e.el), (this.app = e.app); + } + }; + var F = "mousedown", + ye = "mousemove", + N = "mouseup", + J = { x: 0, y: 0 }, + me = !1, + ne = !1, + qe = !1, + D = !1, + ve = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + navigator.userAgent, + ); + document.documentElement.classList.add(ve ? "is-mobile" : "not-mobile"); + ve && + "ontouchstart" in document.documentElement && + ((qe = !0), (F = "touchstart"), (ye = "touchmove"), (N = "touchend")); + document.addEventListener(F, (t) => { + (ne = !0), (D = !1); + let e = F == "touchstart" ? t.targetTouches[0] : t; + (J.y = e.pageY || 0), (J.x = e.pageX || 0); + }); + document.addEventListener(ye, (t) => { + if (ne && !D) { + let e = F == "touchstart" ? t.targetTouches[0] : t, + n = J.x - (e.pageX || 0), + r = J.y - (e.pageY || 0); + D = Math.sqrt(n * n + r * r) > 10; + } + }); + document.addEventListener(N, () => { + ne = !1; + }); + document.addEventListener("click", (t) => { + me && (t.preventDefault(), t.stopImmediatePropagation(), (me = !1)); + }); + var X = class extends I { + constructor(e) { + super(e), + (this.className = this.el.dataset.toggle || ""), + this.el.addEventListener(N, (n) => this.onPointerUp(n)), + this.el.addEventListener("click", (n) => n.preventDefault()), + document.addEventListener(F, (n) => this.onDocumentPointerDown(n)), + document.addEventListener(N, (n) => this.onDocumentPointerUp(n)); + } + setActive(e) { + if (this.active == e) return; + (this.active = e), + document.documentElement.classList.toggle("has-" + this.className, e), + this.el.classList.toggle("active", e); + let n = (this.active ? "to-has-" : "from-has-") + this.className; + document.documentElement.classList.add(n), + setTimeout(() => document.documentElement.classList.remove(n), 500); + } + onPointerUp(e) { + D || (this.setActive(!0), e.preventDefault()); + } + onDocumentPointerDown(e) { + if (this.active) { + if (e.target.closest(".col-sidebar, .tsd-filter-group")) return; + this.setActive(!1); + } + } + onDocumentPointerUp(e) { + if (!D && this.active && e.target.closest(".col-sidebar")) { + let n = e.target.closest("a"); + if (n) { + let r = window.location.href; + r.indexOf("#") != -1 && (r = r.substring(0, r.indexOf("#"))), + n.href.substring(0, r.length) == r && + setTimeout(() => this.setActive(!1), 250); + } + } + } + }; + var re; + try { + re = localStorage; + } catch { + re = { + getItem() { + return null; + }, + setItem() {}, + }; + } + var Q = re; + var ge = document.head.appendChild(document.createElement("style")); + ge.dataset.for = "filters"; + var Y = class extends I { + constructor(e) { + super(e), + (this.key = `filter-${this.el.name}`), + (this.value = this.el.checked), + this.el.addEventListener("change", () => { + this.setLocalStorage(this.el.checked); + }), + this.setLocalStorage(this.fromLocalStorage()), + (ge.innerHTML += `html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } +`), + this.app.updateIndexVisibility(); + } + fromLocalStorage() { + let e = Q.getItem(this.key); + return e ? e === "true" : this.el.checked; + } + setLocalStorage(e) { + Q.setItem(this.key, e.toString()), + (this.value = e), + this.handleValueChange(); + } + handleValueChange() { + (this.el.checked = this.value), + document.documentElement.classList.toggle(this.key, this.value), + this.app.filterChanged(), + this.app.updateIndexVisibility(); + } + }; + var Z = class extends I { + constructor(e) { + super(e), + (this.summary = this.el.querySelector(".tsd-accordion-summary")), + (this.icon = this.summary.querySelector("svg")), + (this.key = `tsd-accordion-${this.summary.dataset.key ?? this.summary.textContent.trim().replace(/\s+/g, "-").toLowerCase()}`); + let n = Q.getItem(this.key); + (this.el.open = n ? n === "true" : this.el.open), + this.el.addEventListener("toggle", () => this.update()); + let r = this.summary.querySelector("a"); + r && + r.addEventListener("click", () => { + location.assign(r.href); + }), + this.update(); + } + update() { + (this.icon.style.transform = `rotate(${this.el.open ? 0 : -90}deg)`), + Q.setItem(this.key, this.el.open.toString()); + } + }; + function Ee(t) { + let e = Q.getItem("tsd-theme") || "os"; + (t.value = e), + xe(e), + t.addEventListener("change", () => { + Q.setItem("tsd-theme", t.value), xe(t.value); + }); + } + function xe(t) { + document.documentElement.dataset.theme = t; + } + var K; + function we() { + let t = document.getElementById("tsd-nav-script"); + t && (t.addEventListener("load", Le), Le()); + } + async function Le() { + let t = document.getElementById("tsd-nav-container"); + if (!t || !window.navigationData) return; + let n = await (await fetch(window.navigationData)).arrayBuffer(), + r = new Blob([n]).stream().pipeThrough(new DecompressionStream("gzip")), + i = await new Response(r).json(); + (K = t.dataset.base), K.endsWith("/") || (K += "/"), (t.innerHTML = ""); + for (let s of i) Se(s, t, []); + window.app.createComponents(t), + window.app.showPage(), + window.app.ensureActivePageVisible(); + } + function Se(t, e, n) { + let r = e.appendChild(document.createElement("li")); + if (t.children) { + let i = [...n, t.text], + s = r.appendChild(document.createElement("details")); + s.className = t.class ? `${t.class} tsd-accordion` : "tsd-accordion"; + let o = s.appendChild(document.createElement("summary")); + (o.className = "tsd-accordion-summary"), + (o.dataset.key = i.join("$")), + (o.innerHTML = + ''), + be(t, o); + let a = s.appendChild(document.createElement("div")); + a.className = "tsd-accordion-details"; + let l = a.appendChild(document.createElement("ul")); + l.className = "tsd-nested-navigation"; + for (let u of t.children) Se(u, l, i); + } else be(t, r, t.class); + } + function be(t, e, n) { + if (t.path) { + let r = e.appendChild(document.createElement("a")); + (r.href = K + t.path), + n && (r.className = n), + location.pathname === r.pathname && r.classList.add("current"), + t.kind && + (r.innerHTML = ``), + (r.appendChild(document.createElement("span")).textContent = t.text); + } else e.appendChild(document.createElement("span")).textContent = t.text; + } + G(X, "a[data-toggle]"); + G(Z, ".tsd-accordion"); + G(Y, ".tsd-filter-item input[type=checkbox]"); + var Te = document.getElementById("tsd-theme"); + Te && Ee(Te); + var $e = new U(); + Object.defineProperty(window, "app", { value: $e }); + fe(); + we(); +})(); /*! Bundled license information: lunr/lunr.js: diff --git a/docs/assets/navigation.js b/docs/assets/navigation.js index 5f20d31e..42afdf51 100644 --- a/docs/assets/navigation.js +++ b/docs/assets/navigation.js @@ -1 +1,2 @@ -window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAA52XS3PaMBSF/4vXpGlok7bsbEgIM2mTSTzZdLoQ9g1oMJJHkilMp/+94wfYkq+vaLY653x6WdL1zz+Bgb0JJkEk08NcySIPRkHOzDqYBCCKrb48CR/WZpsFo2DDRRpMvv4dWdn4kAMWLdup5L4NJRnTGsrY3k5cjbuZKVdJBv1Y3U4lb7OM5xqJNgKVfeACCZatVOpJcmH6saqZzmWHlRRYshKo7HNU6HU/WTWTOdC5FNjyHJVeelRbgklgdHrB9QXsDSjBsqDDfQnjoS07Se8mDy5Uq72b/QqJkQpF19I7yQdtYItgq3ZqhyLruHBhQL2xpDwxvSMzvr5xDuhjbrgUGs+3ug/zpGQ+DKlUCjFd8yxVIGbMMJTSNVCgQYAvOAcThlEU6qHF7Boo0JM0IAxnmfuddFiOh8I9s0PCtLnnBiW1MgUhhuIfQbmBbdQc8mZTnczHb1+ursfdPZVZxjWXYsqybMmSjQvpGXzEGSRymzuXZ83qSCjlrCN4hDh3h90Dcnn8Tx8LEYPuDb9u9c3/AdibmyzbfLmXMMY6bZp96VixHSgNd4VIyrvAxbi6dxdv5+PncNZidkxxtszKNa4lm/CpG34OZ+PZ7RwLNxIRXnIxTiFpw2/NkPVlI9nhm8/dsEwP3+UOUjR+FAlAwsSiPHcsMRiiI1OQNSSbcCEiFHEUKUD1sC6E+/Z2KJbjDFTvwPRZ6LFBYI+F0TwFL9H2UdhMJpvfXOMzPYo0QEB1r+hQKXYY4FgeH65fUjksvLKyQQqYAeux6kCOohfQq31dCFoDW6CU6XVZ8MYSo7QqhYDVWDH0ZDXSxRUV59owkaCzOGpUXLHf0es9mq4lT5j4XDsyAQGhCwVTKXaA7mdX92J+FNslqGFMrXsxzajrz3qYZtm80LracF5wF9kxUcC9AZHaxUmHdFIJxApMJAuRwIwrcN62FtV30Ujnd9kCIX/Mbrz3WFsA9M22EWUXEcf37CQSgKo41JBU5S4GsQzngMrzP/zeILZzofbf9wCy/zNOA+8Yvvqo8VwwcUVgPgqrX/g2H1jKRnPjZ1WmW5ZXr1cs3d+FtgPXQ4xzy/LaE8vBh9P1UDi5Q6dcthMxIc3C9z27HgKXl5cSWfHYDh/qkajDLAMJqrqiSjrHcg6MnKLtIXCKpeMUVhilkciHXYEplIhVgc6qVXuIX/8AfjD0VsIUAAA=" \ No newline at end of file +window.navigationData = + "data:application/octet-stream;base64,H4sIAAAAAAAAA52XS3PaMBSF/4vXpGlok7bsbEgIM2mTSTzZdLoQ9g1oMJJHkilMp/+94wfYkq+vaLY653x6WdL1zz+Bgb0JJkEk08NcySIPRkHOzDqYBCCKrb48CR/WZpsFo2DDRRpMvv4dWdn4kAMWLdup5L4NJRnTGsrY3k5cjbuZKVdJBv1Y3U4lb7OM5xqJNgKVfeACCZatVOpJcmH6saqZzmWHlRRYshKo7HNU6HU/WTWTOdC5FNjyHJVeelRbgklgdHrB9QXsDSjBsqDDfQnjoS07Se8mDy5Uq72b/QqJkQpF19I7yQdtYItgq3ZqhyLruHBhQL2xpDwxvSMzvr5xDuhjbrgUGs+3ug/zpGQ+DKlUCjFd8yxVIGbMMJTSNVCgQYAvOAcThlEU6qHF7Boo0JM0IAxnmfuddFiOh8I9s0PCtLnnBiW1MgUhhuIfQbmBbdQc8mZTnczHb1+ursfdPZVZxjWXYsqybMmSjQvpGXzEGSRymzuXZ83qSCjlrCN4hDh3h90Dcnn8Tx8LEYPuDb9u9c3/AdibmyzbfLmXMMY6bZp96VixHSgNd4VIyrvAxbi6dxdv5+PncNZidkxxtszKNa4lm/CpG34OZ+PZ7RwLNxIRXnIxTiFpw2/NkPVlI9nhm8/dsEwP3+UOUjR+FAlAwsSiPHcsMRiiI1OQNSSbcCEiFHEUKUD1sC6E+/Z2KJbjDFTvwPRZ6LFBYI+F0TwFL9H2UdhMJpvfXOMzPYo0QEB1r+hQKXYY4FgeH65fUjksvLKyQQqYAeux6kCOohfQq31dCFoDW6CU6XVZ8MYSo7QqhYDVWDH0ZDXSxRUV59owkaCzOGpUXLHf0es9mq4lT5j4XDsyAQGhCwVTKXaA7mdX92J+FNslqGFMrXsxzajrz3qYZtm80LracF5wF9kxUcC9AZHaxUmHdFIJxApMJAuRwIwrcN62FtV30Ujnd9kCIX/Mbrz3WFsA9M22EWUXEcf37CQSgKo41JBU5S4GsQzngMrzP/zeILZzofbf9wCy/zNOA+8Yvvqo8VwwcUVgPgqrX/g2H1jKRnPjZ1WmW5ZXr1cs3d+FtgPXQ4xzy/LaE8vBh9P1UDi5Q6dcthMxIc3C9z27HgKXl5cSWfHYDh/qkajDLAMJqrqiSjrHcg6MnKLtIXCKpeMUVhilkciHXYEplIhVgc6qVXuIX/8AfjD0VsIUAAA="; diff --git a/docs/assets/search.js b/docs/assets/search.js index bfa0d3dd..6e8d55ad 100644 --- a/docs/assets/search.js +++ b/docs/assets/search.js @@ -1 +1,2 @@ -window.searchData = "data:application/octet-stream;base64,H4sIAAAAAAAAA72db5PjtrHuv8qt8dvJWugGJdHv1naO41PJccr2zb2prdQWR+LOMtaIUyJnvXtS+e63CAoS0Hzwhxyd+yLlzQjshsAG0P38QPFfd6f29+7um3f/uvutOe7vvqFifX93rJ7qu2/uvv22/Xx3f/dyOtx9c9cc+/r0odrV3dfD39987J8Od/d3u0PVdXV3983d3b/vrRG1In2x8tQc/2/UylfnFo6p+7vn6lQfe9uJsOm/J03/fZnp6nOq12OLRaZTvR5b5Jmm7cXyz9++dB8vps+Xf23+Gr1fhaKLjV177PrTy65vTxFLX/nNcE/H7gRG4f1T9fmPx/7U1F3Mj99skZ/mmOXHbZbnh1b6OvbV4RCzP368wG5XV6dd7KZ+dWmxwPquPRyafXxonDYLPBzaah+zfv58wZ3dV30Vs3z+fEGfm2NXn/qY7UuLJWN+qKv43Do3WGD7VD+1n+qY8UuLBdb71tsSgPVLi0Wx+DQ0/Iu7X8BwdJu9ys/f8/yE1+LEaP3nLz/9V3y0zi0WWP9wap9S9p02Czy8Tyxp7xevae8fXppDdFW4tMizvi4KvuYt79/3X56jk2A0/2b8zx/Um8sVEXdvvItCi9LuY3PYn+rjfO9vnEuzu2F7HujNx7p5/BhdyAJ9uVx4q54c6urDgn6cL7tVL54Sa0ugF9EEdVEvoitPuBeJlWheL9wMd0YvYmnvol4sGotYhpzVC39B2n1s267+5eWhP9XxlUO2XLL8pfOL969JMN53z4cmbt62WGz957bN8HButcTLeZwHKz8e93U060CNX+nz7ecmXi9M2y7cYr9vuv4v1emxie8ZsuUib/t/vnT9X03jIU9LlESw+aJxbY/7+tglJta1Ua6PcqOKa+X63VCndE17/K46HB6q3W8Xd8PU776efB6tiaMpRczgV4lkYtrNq9PrmH3b7r/86rqsjy9P3df2z3H55drvPx4OzXMXtfLVtU1AcLBdgR6+a067Q9zBpckS+39tD18e22PUwbXNEg9uSYOsxwqauOU/N8f4yJwbLBuX5tgnRmVskW3dj74fTu3LM3Bg/v76+LuayQrAsTfzItBxkRGCEQ/hGHRc5ARhxAeOQsd+KgwjtgNx6BhPBmJ0dHAkemOTCkVpXy7uf3brh3H5Hf4UjUNXy/7uXFx974pGjvjqfp6vbU+qvZjFr5IFntdJ/D1C/Z/X733KSlw8i/RvuJc/PfdNe+yQA+fj/N423S991Te7HINfOY3D0WZ7GHT466l5fKxPmR6vrV/h8rt68FHvM306zRc7rY6PzsIZ82dbLnb1XO33zfExy9m17WJ3j97mFXNmW85x5Qb8z9WXXdX1f3IKLsfd9dP8cH/21tOwsa+eo6uq07GAo4d2/yXHz7ndDDfuAP217etj31SHv9UeVHIciib5QwXxJLL2VXjvlP0LuIIjBV2Fxyrsyh2w8DjdZHjyRmXBYOSNQfSr/1D3b99+++3bLkCe3c/jW79bfz7OtfqVuAJ/E6+zmYVjttc37v9JK9Q/hC71/k94xYGb9ZIevhltvaaXCcXw95t19ff/2Y5+vFlHw3B3aUdlev2tuxWM6fXwp+z0emj811P7HEz6zIf5K1do0vimvorOiWufIk5+iCUKwtMPyVQh6q7pvmuPn2p8pMb35jRd6OzhIXB2x3d0brbQSfvhQ1fDVEW4uTRc6Kj70vX1U4ajS8OFjvbNqYebm/Bj2+W7oTUp7XyjXXWo8Qkj8Y1sw9e5wieOgKvIwSPkyt1nvaMHYTfR8wdJJ13d/7XtmiElz/lKXuvlLn8ZhibPn2263NnbWFnmO3ubrMtSzn7KncNu24Xu9qfq92//9qec+XVp+QpXmX6WO7nusaGTkr432X7xRhLXQ7yNBKghfbf/Q9P9oTl+rE9NbySEOc4T2ojnHSkjr3Sf0kn8bRSpJK/qQFQzcXxPFZNXuU3oJ45jpJ68ynVUS3Gje5IezXUrs9Jf3v76a931IjE9//W19M41k2J2tiPhrv54BD0d//jajjpWUv089yLczV9P1af61NX/8XLceVvo6Ep+/NquQ3upLzHpIz7sLLCPRcrjn29y3NkxlXXe+dyjOWePXBfRU0YJ2+gsj2c7dmon2e/pCR3R73DSmOx3wnbsnE/ctlcBIdvR2iduWxQ+yHqi5Mmx/137/CXDx7nZEj9yV0FOUmp83IO/XSL7cbQQtz7JhJCDJBFK+ZAJD3aSgkBxL6LCRS4StW3cvl/YIvPxkjY1RkLawEOUEDXiPuCZXtd+/NRN0ravBIUcxDWg5CjJ3DUwTim2F/fz/jH5Vd4Hk7XnU9vXO5Gsxf29HI1ysP+52jcv0yNlrt9J04X+hcQRD7wZESfsxmfMjNmCJJn4ZI+qCFn24xlBQlfK8hDfuxNyUspDOojj6HZqP6hVwcwjplLFbSOJCg5QhjiV9PRLOpRSmlTSx9vkJp6UopI+fkqnVGkFKu4FyzUwsrKEmrg3T4GCG25Me0rbdoW0kPmYhBb38PK8r/raw0JwPXebLZqH1em3t933yezEb7d01xDRMEbT/2n6j+kIh82X7p5OGStPSFrf57/fpJB1bWVVsrZTc1Ixz0k0F8uwjpOxiYt4NpbwE0xaPTfJrDXh5f3JpDrTzdjz4rTKi6k8r9MNGnn9+828dn2duGu2yVJ/IkmIpNTyLqZy6qSv9JebfLdZ9rMCBcXJAi+JwEBxkfDiPWFrxjqxDNk2izyM+86PWTN40vYmsQ7lPM/vRM+LyeEpb0jg871Jhe813pDkJ78bXDUWfreUN6kCvsIblAU9bxNd8BXedibmzo8GTEtjuTH7bW/Sg5DE57mey4ySO2tAlhNr8kw2mfQaEuqE27lQMuE3IN35O8NUu3uFRyzmeQ6nat5rYmg4aNx9W+1+SyVlomV4pf1fr5lUWVv+LoJ+b9KNgMzmpztpne01XTASizgvjOPPa3izziRlOK8Tr1lVk8Kc5+k1kZ8npU1H9zW7b564NvX5ml0xS5Kcurydx4zpM+9IQSJnhJKfn2dIze8V3mIioD+qARXwdb6xLCgdT3XB13nFMor0GlRPFnoNSIfSLdAOX+E3Lib6URxUE1/hH8qLfgog9cVXekOC48ShVBxfN8JD0X5qm/2gubUv/c/tkDCmJlT0utvN7SHRyZjZ52Y38dufqmN3qPqU+OY0u4nf0zB+CaeXNjfx2HS/NE/PqbXEaXUTrxHRGygKE9X7NSNcZ95bv+HtErlcTV7U5Xmi/Gs6M471d25tbLfLxPRLXPk/091h8cnQpNxm/9/Gbf543XScXOohf1HBduj895tQD9dWFvWwnZqj+HlOoif4UtaRwudbj53hS/d9Wk/Ivof13nTfU9Zj5/gS1qFi51mPnuRLWA9SIM9DkgIlvCR0QBmqoRk4w2NI9/Ncpc72JccuoPOJsUucv0t6Cel6wk3qBF7CT0DH85wkzuAlPGDdznMQP4WXsA+5rGc+ymUzrGMuO3ER57KpyI2pjX745qqNc2ZqSFz052iOuDjDa0BL9HzOPLOXivaIdOiHfKZ0GPedz23lupvitklfiaV9zpqeVCE9y3PmcZ7qOL0xczKCPJVx6mPOzp2lKk5dLPeQMXHi5wSBh6Rq6Oc2sZOCCesxldAfpYyzgmlfWBWUjuKnBdNesAoovcTPC6a9BFQ/6SZxYjDhJ67y+VGWdWYw4Q+qen6CEDs1mGEdqXgTB7Fzg+kRy1Xt5PDlqHZz5xZU6aYza6LSzfATVm78vAjqNjP8BFQ4zwlQ4WZ4CKpuMp+fqm4zvERUNs9PxtnS1IhFVDV/2DJVtTn7RkREE6Xx/JOtWSOcqZmBMV+gmc1cITJK7jyJ7HWjMn80bjYKiRNsoB+zT7CBCsCR4NCGlvpZrVzp7fLjRjmyW+SHqWBBezGe+PWfqFVcyHqmUz/3E7QflJQu5jN+3ydo/f3vzb6fvhDoYvvyeV6QxDwFXh9xdQVeE5HvSyTyia81/VbZllNfI/GyC2Hbe+nTh74+/W8zO39p/jsSqtOGi0ZsxgJycb1w8Yi/dm1amobf5hb/FYi8N7BNvcw57pr7Cjn0XfKP2OS+8g19l5t4gUL5xcusY60RLwkp2136lxxnjf1MYUDSvric/9MnkTU8IG07a/jsn9aJeAtJ3I67+b+lE/QXkLovzmYeV414wpL3xdG8Y6rRn7CMiMbXALnJ8dTY5Aipmtdp8epjqbEdG6th1w37lcdRYyEV0ZOvcXWLY6jRvT4iLLvZ1/yfhYr4jCy6S1fcpNB88bB09uQJzv6dW7rj5gnPvq+lO2KWAO27uo2nxNSb++tYwbwPCtLXXGLOEdaIl5gwfR29BUdX4z6xQO06nHdkNe4NC9Wut3lHVePeAoK1627mEdWIv7hwDX58eM7R1IhfKGBft/s5R1ITXpCQ7TmacxQ1PpK5grY7rK89gpqam1DY9mfmrKOnEX9hEfWql8w+chrxFxC6L85mHjWNeAoK3m6WP++IabJih8K3qNZnHS2NjWREAL8O5y2OlMY2rIgS7tTANzhKmhz7TElc3I1bHyFNLDYJPecGR0fnjdO88bnZuPjHRd1Xolw18uYY/3XN/KOi1lLmQdGhO/MOYV0cJI5gJSyHDmB55lPHryI+IschLy6geh27sVGPAT3b8ZehaEfjKu4+oA67/jNk7lkdyFK+nQ4Ev/8Sb+nvG/66ef7mKOSO2zka+bzhnsXdLv2ZIZwvD7/AofJLJ2ap6XFP+ID51dMcRT31ndCxMvc75Vf3qe8U9zRHWY96ChxCv3iapa5HPSWPirtb1RKFPeo9fGz84nauyp7Yb4JHyJ39ZqbSnvAYPk7uuJyrtkd9Bo+WXxzOVNyj3kLHzC/O5qnu8XiJH9a+Bs1NlPf4xAmfKL5OmVer7/FkInQa9ZpLvFKBj4dZ9Ez3NdZuocInsozoAW83k5yrxCf8Rhfp5St0xsHvi5flMyv3ELh/J5fv2rkHwn1/y3fUzMPhvrtbeUtOzXkKfTTJDBwav+Ylc1T6qKf4AfLrSC5Q6lN+Q4fJXafz1PqUx9DBctfjPMU+5TF4yNx1OVO1j/pMHTi/Rusi5T7qO3D4/Jo2zFHvk57wQXTP2RwFPzWq+YfS3SF+rYqfnruBA+r+zJ2l5Ed9xg5EXzWk2Wp+1Gfw4PrF4UxFP+otcojdrSjmqfoZkkLgQLuQE2Yp+/FRjR5uvw7tLdT9+GYXPenu1OI3UPgz7kP2sXdxZ26t8icXpKQAdQOlf+54zR2nG46Pq/f/uTlOg3r4403U/ouhLLHf9GWO1n81H5X6U3ax0u8bjwv9MQ9Bnf/qIHlIXdif/FxydZpmTlfz9vMltuvjtKy8Wh4/zbMbyBIi1v1W870kBOiro4UHt2N3HarNV4+zxOaoH6Q1O37mSM2J7zOtV73vk1+sJr5P1M8cmTnmB6rMVz+zROaYn4TG7C2QSyTmmO+Qwnx1Oldgjq9zAX3ZXedmystxfyF12XU4V1yOeQxoy85CO09ajvnCyvLV1TxhORolMV3ZCZWbyMrRqRKSMZ1J8mpROdaBgKZ8df9aSTkaXBFF2YmwWwjK8R0/oid7GctcOTnuNbYYL16Jk1ry1cfiuZSnJIs7uHhPztORhbfFO2aWiiyc3chXairOk5BjWSJUkJ2MY46AHPMT04+dMVwgHye8YvXYczlPPE74w9qx52+edJzwF1COPYczheNE7RLRjb3qZYFsHPMMVWMnJZgjGqf8IM3YdzVHMs6sBVOKMSwNlwrGybkK9WIxU2fJxTGPYYXR0SFmi8UxjwGt+OpuplQc8xVUir3aYJ5QnK78oU4sq/5ZMnF0PCMqsTOotxCJo9tZRCN26+gbSMTpO5CpEMt7cmuBOLX8pPShG8jDM8dq5hjdbmxcbfgXXO+Of76JPuyYylKIzz0KlFCnuntuj+ANj64bp9EiH9V0Wnnmq/zfFpp48tfMY1cDWdd1dmmS+U3EHvdcndBi5XpwGr3GB1yJkZ/ob1DFfe0+1rvffgI0wwuya6PFPt6e6irt5NxquZfDIcPJ4fAaH9+1h0PToUxr4sltusTfqfqyq7p4OF/bLJmZ+6qP35Zzg7ydPjF6p7rqa/ysjDd0XrvbeYbUbur43Ox2fs/v1017vjS8nW9UbU0dz6m1cryGXho89XxtecsIwz/ej2LMtryF90j+7LpekEHn+M3weSN/sKL21ow5NXXaF6qqpbs5dXViza2htuVnQ7P0rbi/x6Ei7utj31SHaQLrupUtb+G9P1Wf6lNiqjqNFvr0icFT9fmPx/7U1PHv67e7jefmmOfZbXeLca4SiUklcpLlnrq6Ou2mj+v5eeO5yU3W3CHH2ScG1Gl0C5+HtppyFtffucFNvt+hrhI12LnFTeZjCwVSbza2s7TR1N17Glr+BR288G+g2+62nqcoBnvOJzKpMf7PX376r8QYn5vcwt+HU/uU9Og0uoXP96kF5/3tVpz3Dy/NIT4fL00W+lsXBa+vDt/Dg21Th2/G//xBvblcEiuS3nhXBYn7x+awP9Xx/BL7f+Ncm98R2/lAfwIPTGf0JvHDogv6cqirD0t6cr7uZv2AB8ky+hF9Q9ayfsRXt3A/wm+1WNIPdOAtpx+xd3ot68ey8Yi9nyuvH/6StfvYtl39y8tDf6oTK4lsepMlM0OnfA+Eyld47J4PTcKhbXI7fz+3bY7Pc7Ob+D3frsHsj8c9IBTo5rqtb92Lt5+bRLUxbXyrFOD7puv/Up0em8RuJZvexv/+ny9d/1fTekhZU0UXbH+bu9Ee93WKcbitlqYpjrBaP9LJqVI+vBx3Bsh9ff7kDyoKg9YuPdnTvn4Eps6f5JvC8tzVoPf5PKNu2SINpn633zFWH7uX0/mwni8VX41O2swzfkGk3lkFad5rletg33R9ddyhobUfZY/qod399nuDb5P9LPuLf+7r496TJp3ve/kw19xDu//yl/ZTjcL78lmusWPb/ziciuzqnTn7A2zKJrmmm4TdZonRXXU0nal2KDadT7MNGij14xH18PLZjLA5juyke3tyuasXPV6TXNNP1fM4535tQ6ZlkxmmTftfW3GY1zPtNck1/Vj337Yvx139fXOqd94JqavxaaMZ5n95++uvdYeC4fph9gJSdR8HAPVri5aQy4fZ5k7V7xKHOPaun84x6OrxvrFv//anWXH6szx7IILUfp69U9b9y+n46+kFWbx+OOPe+k+5eXfWfJS9YjZH2tc7tF6On8zbwv7r5emhRrPE/Tj7aw5f5NsG7oWXz7JnsgtPnNnbfooPO1/Pvnz/xx/o57ffX6x8qk5N9XCou6/Pn+Qa+vnt9/T9H38Ahs6fzBt0ceJJDvr4ca7J53He/XgUdPhqVbTIN9wc+x+P4VnvN5jd35hhv8msHv8UGwjn8+z1xbSODK/fYK7Z8CiIFvMM//TSd81+QrCldb/Z7Gxo2EOC4wJaLXIw/O8/KrgtwnaLneQ4WGQ8fBdQM+TiH/d3jVEVvvnX3UBQh+Tjmzt6w2/Ku/u7D0192Hd337wbvd8PSOSpNhXPvt29mH/+49xszHmGxmPrr1d39+9W93r1piT9j3/cv7MXmw/MH6yN61/Mheru/p265/LNVpF3oZpcqLwL6e7+HaELaXIheRfy3f07Rhfy5EL2LtR39+80ulBPLtTehcXd/bvivti+oY13XTG5rvCuW9/dv1ujQV1PLlx7F27u7t9t7gt+o9Z+TzeTCzfehdu7+3dbdOF2cuHWu7C8u39XogvLyYWlf/+HcFArdKmaxo4SwWOiR8GLQfz4AaSGsFAEL57GkPKDSA2hofheb97Q1r85ahpHyg8kNYSH0tDzNJaUH0xqiBFVwIunAaX8iFJDnKg1vHgaVMqPKjXEioJxpaaBpfzIUkO8KBhbahpcyo8uNcSMgvGlpgGm/AijIWYIRhhNI4z8CKMhZghGGE0jjMQSZdYoGGEEVik/wmiIGWJ48TTCyI8wGmKG9L2mN2pT+BdPI4z8CKMhZqi41+WbTemvWDSNMPIjjIaYoTWaGDSNMPIjjIaYoQ30PI0w8iOMtqGthKYBRn6AURncTabxRX588Sq4oUzDi/3wYhXaU3gaXexHF5vogjOKp9HFYhM00QVnFIN90I8uHuKF4YziaXSxH108xAvDGcXT6GI/uniIF4YziqfRxX508RAvDGcUT6OL/ejiIWAYrtk8DS/2w4uHiGG4ZvM0vtiPL23iC67Zehpg2g8wPcQMb0DyoacBpv0A0xRcRPQ0wLQfYNqkWVvkeBpfWiRaJr5KtAxokGv58aWHiNErePE0vrQfX3qIGK3gxdP40n586SFiNMGLp/Gl/fjSQ8RohhdP40v78aWHiNEaLbp6Gl/aj69iiBgN89NpeBV+eBUqeKeKaXwVfnwVFLxTxTS+Cj++Cg7eqWIaYIUfYIUO3qliGmCFyOaL4J0qQEbvB1ixDt6pYhpghR9gRXh7LKYBVvgBVpgAW6PbPI2vwo+vogxmBMU0vgo/vtarYKq8ngbY2g+wtQpfPA2wtR9gaxNgaPFbT+Nr7cfX2sTXFlZf0/ha+/G1NvFVwoun8bX242ttykVYTK+n8bUWJeMQMQWcFmtQNfrxtR4ipiDoeRpfaz++1kPIFAwvngbY2g+w9RAyhUZ3ahpfaz++NqvglNpM42vjx9dmiJgC7sybaXxt/PjaDCFToCm1mcbXxo+vzRAxxQYN12YaXxs/vjZDxBQwODfT+Nr48bUx8VWiXk/Da+OH12Yd7vU0vDZCldiEew2ECT+8NkPArGHOuZmG18YPr00ZvngaXxs/vrarYBq0ncbX1o+v7RAxawUGezsNr60fXtshYtaErp2G19YPr+0QMGtG106ja+tH11YH58R2Gl1bP7q2Q8Cs0UzeTqNr60fX1gheBRzpaXRt/ejaDvGyXsOLp9G1FbqXia4NvBhIX350bcuQ+LmdBtfWD65yCJc1LOLKaXCVfnCVJrjgNlNOo6v0o6scAmazQl+5nIZX6YdXOUTMRsGLp/FV+vFVDhGzIXjxNL5KP77KIWQ2cJsppwFW+gFWDiGzgVO5nAZY6QdYaVRVGJ3lNMBKP8DKIWQ2MDrLaYCVQlwdYmYDo7ME+qoUWIeg2Wyhxr5CEqvQWFdD3GxglI2fyeuFzLoaQmcLA238TF4vlNZVOBMbP5PXC7F1FU7Gxs/k9UJvXYXzsfEzeb2QXFfhlGz8TF4vVNdVOCsbP5PXC+F1FU7Mxs/k9UJ7XQ3xtEVb2PiRvFyEnwpvnwop/BOJfwinLdoFFdT4RfSpsIihkMwvdX4j3W/RRqqQ0C+VfhUWYhXS+qXYr8JqhkJyv9T7VZAhKST4S8V/lPzh0oEkf6n5GxkfSqMKif5S9VdBVVYh2V/o/spI+VBcVUD4V0L5V0bMx9MeSP9KaP/KyPl4Z1dA/VdC/ldG0d+iREoB/V8JAKCMph9YNQACUIIBKCPrB1YtQAGUwADKKPuBVRuAACVIgDLifmDVBixACRigjMCPcyMFeIASQEAZkR+nRwowASWggDJCP84oFeACSoABZcR+nAsrwAaUgAPK6P04HVYADyjBB5SR/AO7HiAESiACZVT/bXGv+c16tRbXg/gTlEAZ4X+LamsFMIESnEBxuFBVgBQogQoUh2tVBWCBErRAGQCw3eCvD8JPAANlGABOsBVABkowA2UwAM6xFaAGSmADZVBAIGkA5EAJdKAMDcBpugLwQAl6oAwRwJm6AgBBCYKgDBTAyboCDEEJiKAMF8D5ugIYQQmOoAwawCm7AiRBCZSgDB0IJM0AJihBE5QBBIGkHfAEJYCCMowgUDQApKAEU1CGE2y3sP8AKyjBFZRBBdsSzh9AFpRAC8rQgnKFlg/AFpSAC8rwAsw1FMALSvAFZZABTrsAYFCCMCgDDQKLP2AMSkAGZbhBYPEHmEEJzqAMOghsXoA0KIEalMEHJSwZAGxQgjYoAxBKmPID3KAEb1AGIZQwZQfAQQnioAxECOx8gDkoAR2UAQklTNwAdlCCOyiDEkqE4xQAD0qQB2VgQgn3TYAelGAPyuCEEiETBeCDEvRBGaBQbu918YY28np0wEgE3jpcbwACoQSCUOtwvQEYhBIQQq3D9QbAEEpwCLUJ1xsARChBItQmXG8AFKEEi1CGL5QlHHqAI5TgEWoEErhgAERCCSShDGUIJPwASihBJZQhDYGEH4AJJciEMrAhkPADNqEEnFCGNwQSfoAnlOATyiAHtVrhGwCiTzAKZbCDWilsAMSf4BTKoIdAyg1IhRKoQhn8oFYEOwBwhRK8QhkGEUi6AbJQglkowyECSTfAFkpwC2VQRCBpBuRCCXShDI4IJM2AXiiBL5QhEoGkHwAMJQiGMlAikHQDhqEExFCGSwSSboAxlOAYyrCJQNINUIYSLEMZPBFIugHNUAJnKEMoAkk3ABpKEA01Ig2ctgCmoQTUUIZTBJJugDWU4BpqBBt4CQJkQwm0oUa2gZNuADeUoBvKAAu1YjiBAeBQgnAoAy3USmMDIAIF5VDluAgW2AAIQUE6VDkugmtsAJ31lYd9x/PkG2SAAO4ggTvI4AtcuRDAHSRwBxl8oVYwByLAO0jwDjL8Apc+BHgHCd5Bhl+oFcwECAAPEsCDDMBQCu5kBIgHCeJBhmAoBXcyAsiDBPIggzDw4S4CyIME8iCDMGD5RYB4kCAeZBAGPkkMiAcJ4kEqeCCYAPAgATzIEAx8FhkADxLAgwzAgFkoAd5BgneQARgwCyXAO0jwDjL8AleOBHgHCd5Bhl/gLJYA7yDBO8gADJzFEgAeJIAHGYKBs1gCxIME8SCDMHAWSwB5kEAeZBgGzmIJMA+SzzoYiIGzWEJPO8jHHSgsOxN64GHyxENYdib4zIMIP4MxAlk0oece5IMPpMNZNKFnH+TDDwZk4Cya0OMP8vkHWoezaEKPQMhnIAzJwPoDoacg5GMQhmTgLJzQkxDyUQhDMnAWTuhpCEE+yJAMnIUTIB8kyAcZkoGzcALkgwT5IEMycBZOgHyQIB80kg+YRRMgHyTIBxmSgbNwAuSDBPkggzJwFk4AfZBAH2RQBs7CCaAPEuiDDMrAWTgB9EECfZBBGTgLJ4A+SKAPMigDZ+EE0AcJ9EEj+sBLKEAfJNAHGZSBs3AC6IME+iBN4SycAPsgwT5IczgLJwA/SMAP0jqchROgHyToBxmaEcjCCeAPEviD9DqShQP+QYJ/0Mg/cBYO+AcJ/kF6G8nCAQAhAUDIAI1AFg4ACAkAQsUqkoUDAkKCgFChIlk4QCAkEAgZphHKwgEEIQFByEANLOYSgCAkIAhFnrIgQEFIUBAaKQicxwCCkIAgVIRPkxKAICQgCBXhA6UEIAgJCEJF+EwpAQpCgoKQwRoKP6BMgIOQ4CA0Pnih8COkgISQICE0PnyBH1QmgEJIoBAybCP0MCiIQMFCyMANhR92JkBDSNAQGmkIDmGAQ0jgEDJ8A1eCAIeQwCFk8AauBAENIUFDyOANXAkCGkKChpDBG7gSBDSEBA2hdfBYMwEaQoKGkMEbgUoO4BASOITGJzNwJQd4CAkeQoZvBCo5wENI8BAyfCNQyQEeQoKHkOEbgUoO8BASPIQM3whUcoCHkOAhtFlHKikAREgAEdpsIpUUICIkiAgZwBGopAAQIQFE6AxEcCUFgAgJIEIGcAQqKQBESAARMnwjUEkBHkKCh9DIQ3AIAh5CgofQNnzKngAPIcFDaBs+aE+Ah5DgIWT4RqCSAjyEBA8hwzcClRTgISR4CBm+EaikAA8hwUNo5CG4kgI8hAQPoZGH4AwC8BASPIRGHoIzCMBDSPAQGnkIziAADyHBQ6gMn7wnwENI8BAan/LASxjgISR4CI08BFdSgIeQ4CFUFpFKCgAREkCEzkAEV1IAiJAAInQGIriSAkCEBBChMxDBlRQAIiSACJ2BCK6kABAhAUR4FT6JxYCHsOAhvFLhSooBEGEBRHh8/gNWUgx4CAsewisOV1IMgAgLIMIjEMGVFAMgwgKI8BmIwJ2QARBhAUR4FX7qmwEPYcFD2PANWAkxwCEscAiPOASuYwx4CAsewgZw4EqIARBhAURYhc9CMyAiLIgIn3/mCf/sB2AiLJgIjz/1hCshBlSEBRXh8eeecCXEgIuw4CI8PgcCKyEGXIQFF+HxV59wJcQAjLAAI6zCJ7MYgBEWYITDT4Iw4CIsuAiHnwRhgEVYYBEOPwnCgIqwoCIcfhKEARRhAUU4/CQIAybCgokwhU9mMWAiLJgIU/hkFgMkwgKJMIVPZjEgIiyICFP4ZBYDIsKCiDCFT2YxACIsgAhT+GQWAyDCAogwRU5mMSAiLIgIU+RkFgMkwvIXojh8MovRj0TJX4niyMksRr8UJX8qyjAOXAkx+rGoya9FhX+tgOHvRYkQ5PAPFjD6ySj5m1EjE4GVEKNfjZI/GzUyEVgJMfrhKPnLUYZx4EqI0W9HyR+P4vAzmIx+Pkr+ftT4OEjgehCAgonwyERgJcSAibBgIjwyEZxBACbCgonw+DgIziAAEmGBRHh8HARnEICIsCAibAAHroQYABEWQITHx0HwEgZ4CAsewuPjILASYoBDWOAQ1ptwJcSAh7DgIXzmIbASYsBDWPAQ1mW4EmIARFgAET4DEVgJMQAiLIAIj0AEV0IMgAgLIMKGbwQqIcBDWPAQLjhSCQEgwgKIsAEcgUoIABEWQISLIlIJASTCAolwsY5UQoCJsGAibBhHqBICUIQFFOEi/DNnDKAICyjChnHgSgggERZIhA3hCFRCgIiwICJsAEegEgJAhAUQYQM4ApUQACIsgAifgQh6vIMBD2HBQ3g9lsLo+Q4GPIQFD+GRhyj0s34MgAgLIMKR50MYEBEWRITDz4cwICIsiAiHnw9hQERYEBEOPx/CgIiwICIcfj6EARBhAUQ4/HwIAx7Cgodw5PkQBjyEBQ/hyPMhDHgICx7CkedDGPAQFjyEI8+HMOAhLHgIR54PYYBDWOAQjjwfwoCGsKAhHHs+hAEOYYFDOPZ8CAMcwgKHcOT5EAY4hAUO4djzIQx4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewmWkCgE8hAUP4TJShQAewoKHcBmpQgAPYcFDuIxUIYCHsOAhXEaqEMBDWPAQLiNVCMAhLHAIl5EqBNAQFjSEy1gVAmgICxrCZawKATSEBQ3hMlaFABrCgoboVaQK0QCHaIFD9CpShWiAQ7TAIXoVrkI0wCFa4BC9ilQhGuAQLXCIXoWrEA1oiBY0RK8iVYgGNEQLGqJXkSpEAxyiBQ7Rq0gVogEQ0QKI6FX4x2E0ACJaABG9ClYhGvAQLXiIHt95odBPcGrAQ7TgIXrkIYR+3UADHKIFDtEjDoEwQwMaogUN0SMNIfR8vwYwRAsYosf3XxB6wF8DGKIFDNEjDCH0hL8GLEQLFqLHt2AQekRfAxaiBQvRIwsh9Iy+BjBECxiix3dhEKriNKAhWtAQPb4Og/CvuoP4EzhEU/gXmTXgIVrwEE1j/MEfdwdARAsgoml88wqMfwBEtAAienxIhGH8AyCiBRDR4zMi+B0EGhARLYiINoRD4fcQaIBEtEAienxIhGEEAySiBRLRFD4erQES0QKJ6BGJMJwBgIhoQUT0+OtYeAEFQEQLIKI5/JuUGgARLYCIHoEIficC4CFa8BBt+IZiOIMBD9GCh2geIxDOYMBDtOAhenxGBHcfxJ/AIXr8cSx8OYg+QUO0oRvw3R8awBAtYIjmTfCVTRrAEC1giDZwA7+1SQMYogUM0VwGX9ykAQzR8n0aBm5glK7RGzXkKzUM3MDvQdLorRrytRrjAyL49qEXa8g3a4zPh+D7h16uMXm7hg6+EEnD92uI6DNwA78TSaNXbMh3bBi4gV+LpNFbNuRrNgzbwG9G0uhFG/JNGwZtBN5rgt61IV+2ocNvQ9DodRuChGgDNvBZEA1AiBYgRBuugV8DoQEH0YKD6PHBELj2AgyiBQbRhmpAFVQDCKIFBNEGakANVgMGogUD0ePbN/Dl6PUuIvYM0MAvTdIAgGgBQPT4Cg489wD/0IJ/aMMz8KuTNOAfWvAPbYBGIHMBAEQLAKIN0AgkLgCAaAFAtAEa+B1KGgAQLQCINkADv0ZJAwCiBQDRBmjgNylpAEC0ACDaAI3Ay5QAANECgOgRgDBe/AEB0YKA6PWY+eEIBAhECwSiRwQSuoUgBAUE0ettJPsFGEQLDKIN11AM03fAQbTgINqADaVh+g5AiBYgRBuyoTQsPwEJ0YKE6M1Y/sLyE5AQLUiINmRDaZi8AxKiBQnRhmwoDZNvQEK0ICHakA0F39ikAQnRgoTo8ckQ+CogDUiIFiREjw+GwPfqaEBCtCAheiQhGpaPAIRoAUL0CEI0jD/AQbTgIHo75n8w/gAH0YKD6JGDFDD+AAbRAoNogzVUAeMPYBAtMIg2WEMVMP4ABtECg2iDNRR8244GGEQLDKIN1lAFjD+AQbTAIHoblqE1wCBaYBBtsIaC793RAINogUG0wRqqgPELMIgWGEQbrKEKGL8Ag2iBQXQ5xh+MX4BBtMAguhyLXxi/AINogUG0wRoKvpRGAwyiBQbRIwaBGEgDDKIFBtGljuyhgINowUH0+FxIYA8FIEQLEKLH50Lgq3U0ACFagBA9ghD4eh0NOIgWHESPHAS+JUcDDKIFBtEjBlnDGQgoiBYUpBgpyBq+DA5AkEJAkGKEIGs0gwrAQArBQIrxN7LWaAYVgIEUgoEUIwNZoxlUAARSCARSjE+EbNAMKgACKQQCKUYEskEzqAAEpBAEpBgJyAbFXwEASCEASDECkA2KvwLwD/s38/73T/Wpr/c/ju+Bf/fu7v37/stzfXf/r7v355fD0+UF9P+6I333zb/+fX+nafzvdjX+d3hNx/kfZP9hm+rt8I9/X18gb/5se2M+G7r3vtr/86Xrn6tTfewfHtrPdef2gotrL7g8m15zpunDwftG5Hwjst1cZ9vaN13/VJ0em6PXQ2eceGt7SHlWH16aw97rIzt9ZNvHTZ613ce27eru+dD01efGH0fHLm9sL9Vsu6M517AzqLy2hlezDL889KfaD76tY9XeqiLzVu3a474+dp5BXl8N6pXtps4z+HhqX55da1snKod3Foyhb2fF8GNQ4z8253gYjvKP/9hmjszHunn82Ls+hweXrqGxtgZVZqQ1x64+eQapdMbYhlqRGWpP1ef62J8af7ZurhZtjHHmGD81R2DQCQM7tbjIM3iq9s1L58VqqZzbVmSuT6OdL54dcu2UeXbM9PFCcuWMv101c3tlrJ3a1rfofD8urMXc/vW1F+OlE24qd0L/3uz7j37UuotuUdqozVt5qg99fXp53ld93TX/XfuGC3c6sDWcFx1iZ3Bmgt1jOG+xqY6PB69fhXMP1uf1cHiZ1/gPyuvesA+6Rp1IOc/7bWktqvM/Nud/DD/GOP5jbUfbtuFN3rA/NEfa1ztvuAtnjIZaJMtOu/fmTeHcMtvdbd76MJiaLMPasVfkd+mp/VR7m+6gwly/3Ca/R+1z37RHb8XSzhqYmQUMlp5P7bNnZ+10aZsZNe3+i8zi2FlBdV7aJGJPkzs24zXFebmiIm+sdtWxOfb1qdr5W5o7WINAlGWrHix5s3fjrlR5YzVaEWGwcveHTWl3c/tlNxs7n+zE2uStrbuP9e63qjk++F/evTWbvHV/tOQvXsMRlKshlTmKxtCprnxL7jxQeSugsbRrD4ema9qjb85NutSMb9ge/bXezYp15hK/+9gc9qfa65E7UjZJs3vSAOjm2N1XvTd42vmymbv4rjnt/N1DOyu9th28lFaZX9xYbY5T60Ot7iyYeZuBNffcHr48ihu83rr28la70V770nfNvoZGS9do3pq1O9TVyd+v3BTN3uG8PWJ3aHe//d74BYTy18HM73poj/Vz2xz7rjqdqi++QXdHzV1JBoOnuntuRX0zCKFXa9vMRWCYtXs/41buGqhsMpQZz3YV2FWHw0O1+83bidy1PjP02qdBF3hqjt6OpJyoU7Ys0Jl35GLSvxnOvVCXBSEz9tpj159edn3rRaBzP2z2p+w/Lvvnams30nOMDr/aMf7jsi6tMkOjPX6qP58nlHdLS/fLbcimiZci1ZaSVhPh3Og+1VVfi3RBuUWIvghH68wIMibByuUWS5oyb4wxVh8OzbOYLK5ooe1GMCDSfLOHRu5R7nSmOSNoFgjflpsaUOZkOduaLqiu9jOcIcyxtq+6j8NX7Ft/lXEThMzUXW6Uro5haxUrZ6ztikN5S/W+fqRT5Wdx3kKztje3zOxsc+q9lWHjBF5pd2KbACqbEhJZ7WdjJzLZiZyZ1w7qYnXciU3HnUyZ1dv+VP3ufQN3+q9suajPK48q7VpkZbHhB7zGntsMZHiE5Hxb8taioQsPnzwhYOMu2isrG2s7kNYDaVvG2n6xXtteWHnRqmkDE8ztDpoYbj2qM4sssJywO/PPvS7sOBd5a1V99INYOUHMmTplfexeTvW4D/gZlbvclXljNho7vjw9+MXWQBwdY3khORo734IxH/LD3LW5yZv6o81P9bDvTldQ756s86K2/tzXx70ULYYjHk7n8m7nh1P79M/ODzdy5rJVZLTOi7rHuq+qh4eqk8rQduVOK7s+2Tpd2alMlp2QXcPYRipnrozXLvg9cOPB6t2Z2exj3T+0L8ddvW9O9a6X5WPhhu02b0d9rPuhtD+1jZhPLlDI1AgcW783/cf2pT+1fTXp5srdz+waRRaVkV3Z2N6U4Xm4XPfTuayc4R5euXNOH63Ct7J3tbCL5Spvvj/W/URfG043OHcg+6a2Hz50dT+M2UQeLd14LbLv6XPb18e+qQ7+uuHSQc3Z1rqq7+vOXzAKd5Zv82b5ZMBcI2u7wdocX9mtTW1t1mCxC23tHmcJ4vDUbXYXHhqxnq7dtKHMGxdvt147XyRzMKbEypV/yM6LtV1/LqA4U7mdAizlJKMXa5k8zMiRXb0za5pv1C2kN3mT52JtSJlB9eImkcUqb731bE6qjQ25Fhf0cvjfh0pMgo1yreatkp5VlGVt2DWat/YNRv2+bV3mlomPmg7JvIVzM2x5oOxKquyUVLYUIZvIk+W7bIk0Z25KTTddyF0pbmtXCJvYqsL6VBel3S4M9sgFZ9KFpuuap2cRkKuNu4vYxN9m2sOvx553EZunZIqFTdcNO6SHjlyd23I6tbL/sAuD2thvqux3t1IFWxWBM9FM0/Wn5vHRT1xduW1tkb3dLpW9ycrWb2SVJdrYcsgKBbzJmxmHuvrgrYfuoFuSYjl5kTfAciHwktNzjy9aUqYMfWhFBe1ONXv4gvN2/6fq2SidfTtm5v4e625L27wv/FQ9j5b6diqhulNZZx6teKpOv1XdpM7fulWyDXuyYUq2UGWbY7PV8HTmev5UffYPzjgJqbV9jkEbesrmEGQnPVmkzLYN2zY6k8U9VZ+9L+4qOta2nRS2Q1YiJBsOZL2ybcO2jS7yZsdE1XXG3y64djzs5FzbpWFlyxm7gLNtw+vLjMpbH6UU7J5XsMuh7Yf9hyXrZBVysl7ZtmHbRmcqAAOV9gQTJ0BsDaFsVaHslkH25AjZpJJtqcf2jIAu83pwbPtIWuSm2pkL4FgHeLuesxeUNroyxdLR2q599uetmwRmCsPP1X7fHB+9pMC57fZeKqvFK7sBKbsXkJXyyZ5jYHtajzMlgolm4R1CON86W8VdNPrMVM8YDzA7z03eDmHMtZAoutRzlbcCjwLQwIdEublyi0OrJZAdWNrYtc8uDJxJzcEweMLq2W5hHWUmqWezmLS6+3JmwX+xB2+bO//KvM34ufWPlDjfOXP3vdTd093cBY6ZwOhU7Wlfe/NOeQdU7O6my7wFBp3xc8Fv5kFhdMRv7ZrJ21lldkJudpJZspyqLztZlZFHTvNu/dnOR//IoYtvMkXR08NL50kD7pmrPAu13NqUN7jnW86ZA4Tgt4tvtcpbhU51f6qO3aHqRWnkBCTZbI9sbs0WoLBdLXTmEYVT3b+cjv3pRXB79+Zmij5GhhS9dofUlm9kAQ6VtvuFzdIy12oglW3dTC0TTne7yl8gt+6ZLSt6KSuDka2waWvzO3tonTPPNhiPoAZZucXN9lLjX7ZzmzzZJJgzV0rjz1uJXDHF6vDKJmdqa/M2m/XT1iYQ9uguZyIp49qHls6XtBxNWb6n7JgSXxJtm/nbE/ycSda6ujrt/AO/HpCwczvzntXP1UlGtotNtMqba9bQhOW4j4Bolbesd3U/kbE3znQrL/quzdJLO642aMnmumwTeM5UZTurpnvenclj57qyWYyyhIfstCLbH9Y2xDIrgm5Q3ie0buWm3KX9itY2WQTClzUzM8s03rpGApaN487aVtabskUO2VlDtjRiG/CceVy5q/vJOrVxtjv7VZVFZ8reVrLzhmx32Ir9nIlIu74S6ra7S3JmJjM5xO/eqkzBsPvS9fWTlzi65+Mv4oANe5uWky19yUp5bJdV3lxoVF4o9K0EnS4Qt7W+zjxH1rcTFutRNGstbz0IpA1u3mmXBLKRQjZ2+CIkZebw/an6NJTkftLsMgrOCw1r6MPLcYJaty7CzFxj5Ulzt0Cx+7i6SElWkyRLvsmWtry6KFl503RwPCFvbh1qlRBlswhlfZE94kZWd+XVRa/Ji6WXo1kj9mPN4A2im4JlPqU0Ptvi31u3Pqe86TKakbvd1j1yYxM/sgksXXi01f/ZnjTWNu3VlLdxj/69E4h2JRdbR+nOuwvbuKgbdn+y4aIzIRTogDjV6h6+sI/jkP3CvLpI5nZpydQARscI8JTu6K8vHi8P51zEVjsP1nkpF6jEnUHNVOa8A2Lu8aBMogWe83IfF7bsjDPXEm/g3IrI6m2by4Ji/7G1M9nuQ2STebYIjzPJtf+MknPb7D62uSwo9h82VyerDZPtD1tBlnOOR/7j/u65eR7x7jfv/vHvf/8/SGmAbVySAQA="; \ No newline at end of file +window.searchData = + "data:application/octet-stream;base64,H4sIAAAAAAAAA72db5PjtrHuv8qt8dvJWugGJdHv1naO41PJccr2zb2prdQWR+LOMtaIUyJnvXtS+e63CAoS0Hzwhxyd+yLlzQjshsAG0P38QPFfd6f29+7um3f/uvutOe7vvqFifX93rJ7qu2/uvv22/Xx3f/dyOtx9c9cc+/r0odrV3dfD39987J8Od/d3u0PVdXV3983d3b/vrRG1In2x8tQc/2/UylfnFo6p+7vn6lQfe9uJsOm/J03/fZnp6nOq12OLRaZTvR5b5Jmm7cXyz9++dB8vps+Xf23+Gr1fhaKLjV177PrTy65vTxFLX/nNcE/H7gRG4f1T9fmPx/7U1F3Mj99skZ/mmOXHbZbnh1b6OvbV4RCzP368wG5XV6dd7KZ+dWmxwPquPRyafXxonDYLPBzaah+zfv58wZ3dV30Vs3z+fEGfm2NXn/qY7UuLJWN+qKv43Do3WGD7VD+1n+qY8UuLBdb71tsSgPVLi0Wx+DQ0/Iu7X8BwdJu9ys/f8/yE1+LEaP3nLz/9V3y0zi0WWP9wap9S9p02Czy8Tyxp7xevae8fXppDdFW4tMizvi4KvuYt79/3X56jk2A0/2b8zx/Um8sVEXdvvItCi9LuY3PYn+rjfO9vnEuzu2F7HujNx7p5/BhdyAJ9uVx4q54c6urDgn6cL7tVL54Sa0ugF9EEdVEvoitPuBeJlWheL9wMd0YvYmnvol4sGotYhpzVC39B2n1s267+5eWhP9XxlUO2XLL8pfOL969JMN53z4cmbt62WGz957bN8HButcTLeZwHKz8e93U060CNX+nz7ecmXi9M2y7cYr9vuv4v1emxie8ZsuUib/t/vnT9X03jIU9LlESw+aJxbY/7+tglJta1Ua6PcqOKa+X63VCndE17/K46HB6q3W8Xd8PU776efB6tiaMpRczgV4lkYtrNq9PrmH3b7r/86rqsjy9P3df2z3H55drvPx4OzXMXtfLVtU1AcLBdgR6+a067Q9zBpckS+39tD18e22PUwbXNEg9uSYOsxwqauOU/N8f4yJwbLBuX5tgnRmVskW3dj74fTu3LM3Bg/v76+LuayQrAsTfzItBxkRGCEQ/hGHRc5ARhxAeOQsd+KgwjtgNx6BhPBmJ0dHAkemOTCkVpXy7uf3brh3H5Hf4UjUNXy/7uXFx974pGjvjqfp6vbU+qvZjFr5IFntdJ/D1C/Z/X733KSlw8i/RvuJc/PfdNe+yQA+fj/N423S991Te7HINfOY3D0WZ7GHT466l5fKxPmR6vrV/h8rt68FHvM306zRc7rY6PzsIZ82dbLnb1XO33zfExy9m17WJ3j97mFXNmW85x5Qb8z9WXXdX1f3IKLsfd9dP8cH/21tOwsa+eo6uq07GAo4d2/yXHz7ndDDfuAP217etj31SHv9UeVHIciib5QwXxJLL2VXjvlP0LuIIjBV2Fxyrsyh2w8DjdZHjyRmXBYOSNQfSr/1D3b99+++3bLkCe3c/jW79bfz7OtfqVuAJ/E6+zmYVjttc37v9JK9Q/hC71/k94xYGb9ZIevhltvaaXCcXw95t19ff/2Y5+vFlHw3B3aUdlev2tuxWM6fXwp+z0emj811P7HEz6zIf5K1do0vimvorOiWufIk5+iCUKwtMPyVQh6q7pvmuPn2p8pMb35jRd6OzhIXB2x3d0brbQSfvhQ1fDVEW4uTRc6Kj70vX1U4ajS8OFjvbNqYebm/Bj2+W7oTUp7XyjXXWo8Qkj8Y1sw9e5wieOgKvIwSPkyt1nvaMHYTfR8wdJJ13d/7XtmiElz/lKXuvlLn8ZhibPn2263NnbWFnmO3ubrMtSzn7KncNu24Xu9qfq92//9qec+XVp+QpXmX6WO7nusaGTkr432X7xRhLXQ7yNBKghfbf/Q9P9oTl+rE9NbySEOc4T2ojnHSkjr3Sf0kn8bRSpJK/qQFQzcXxPFZNXuU3oJ45jpJ68ynVUS3Gje5IezXUrs9Jf3v76a931IjE9//W19M41k2J2tiPhrv54BD0d//jajjpWUv089yLczV9P1af61NX/8XLceVvo6Ep+/NquQ3upLzHpIz7sLLCPRcrjn29y3NkxlXXe+dyjOWePXBfRU0YJ2+gsj2c7dmon2e/pCR3R73DSmOx3wnbsnE/ctlcBIdvR2iduWxQ+yHqi5Mmx/137/CXDx7nZEj9yV0FOUmp83IO/XSL7cbQQtz7JhJCDJBFK+ZAJD3aSgkBxL6LCRS4StW3cvl/YIvPxkjY1RkLawEOUEDXiPuCZXtd+/NRN0ravBIUcxDWg5CjJ3DUwTim2F/fz/jH5Vd4Hk7XnU9vXO5Gsxf29HI1ysP+52jcv0yNlrt9J04X+hcQRD7wZESfsxmfMjNmCJJn4ZI+qCFn24xlBQlfK8hDfuxNyUspDOojj6HZqP6hVwcwjplLFbSOJCg5QhjiV9PRLOpRSmlTSx9vkJp6UopI+fkqnVGkFKu4FyzUwsrKEmrg3T4GCG25Me0rbdoW0kPmYhBb38PK8r/raw0JwPXebLZqH1em3t933yezEb7d01xDRMEbT/2n6j+kIh82X7p5OGStPSFrf57/fpJB1bWVVsrZTc1Ixz0k0F8uwjpOxiYt4NpbwE0xaPTfJrDXh5f3JpDrTzdjz4rTKi6k8r9MNGnn9+828dn2duGu2yVJ/IkmIpNTyLqZy6qSv9JebfLdZ9rMCBcXJAi+JwEBxkfDiPWFrxjqxDNk2izyM+86PWTN40vYmsQ7lPM/vRM+LyeEpb0jg871Jhe813pDkJ78bXDUWfreUN6kCvsIblAU9bxNd8BXedibmzo8GTEtjuTH7bW/Sg5DE57mey4ySO2tAlhNr8kw2mfQaEuqE27lQMuE3IN35O8NUu3uFRyzmeQ6nat5rYmg4aNx9W+1+SyVlomV4pf1fr5lUWVv+LoJ+b9KNgMzmpztpne01XTASizgvjOPPa3izziRlOK8Tr1lVk8Kc5+k1kZ8npU1H9zW7b564NvX5ml0xS5Kcurydx4zpM+9IQSJnhJKfn2dIze8V3mIioD+qARXwdb6xLCgdT3XB13nFMor0GlRPFnoNSIfSLdAOX+E3Lib6URxUE1/hH8qLfgog9cVXekOC48ShVBxfN8JD0X5qm/2gubUv/c/tkDCmJlT0utvN7SHRyZjZ52Y38dufqmN3qPqU+OY0u4nf0zB+CaeXNjfx2HS/NE/PqbXEaXUTrxHRGygKE9X7NSNcZ95bv+HtErlcTV7U5Xmi/Gs6M471d25tbLfLxPRLXPk/091h8cnQpNxm/9/Gbf543XScXOohf1HBduj895tQD9dWFvWwnZqj+HlOoif4UtaRwudbj53hS/d9Wk/Ivof13nTfU9Zj5/gS1qFi51mPnuRLWA9SIM9DkgIlvCR0QBmqoRk4w2NI9/Ncpc72JccuoPOJsUucv0t6Cel6wk3qBF7CT0DH85wkzuAlPGDdznMQP4WXsA+5rGc+ymUzrGMuO3ER57KpyI2pjX745qqNc2ZqSFz052iOuDjDa0BL9HzOPLOXivaIdOiHfKZ0GPedz23lupvitklfiaV9zpqeVCE9y3PmcZ7qOL0xczKCPJVx6mPOzp2lKk5dLPeQMXHi5wSBh6Rq6Oc2sZOCCesxldAfpYyzgmlfWBWUjuKnBdNesAoovcTPC6a9BFQ/6SZxYjDhJ67y+VGWdWYw4Q+qen6CEDs1mGEdqXgTB7Fzg+kRy1Xt5PDlqHZz5xZU6aYza6LSzfATVm78vAjqNjP8BFQ4zwlQ4WZ4CKpuMp+fqm4zvERUNs9PxtnS1IhFVDV/2DJVtTn7RkREE6Xx/JOtWSOcqZmBMV+gmc1cITJK7jyJ7HWjMn80bjYKiRNsoB+zT7CBCsCR4NCGlvpZrVzp7fLjRjmyW+SHqWBBezGe+PWfqFVcyHqmUz/3E7QflJQu5jN+3ydo/f3vzb6fvhDoYvvyeV6QxDwFXh9xdQVeE5HvSyTyia81/VbZllNfI/GyC2Hbe+nTh74+/W8zO39p/jsSqtOGi0ZsxgJycb1w8Yi/dm1amobf5hb/FYi8N7BNvcw57pr7Cjn0XfKP2OS+8g19l5t4gUL5xcusY60RLwkp2136lxxnjf1MYUDSvric/9MnkTU8IG07a/jsn9aJeAtJ3I67+b+lE/QXkLovzmYeV414wpL3xdG8Y6rRn7CMiMbXALnJ8dTY5Aipmtdp8epjqbEdG6th1w37lcdRYyEV0ZOvcXWLY6jRvT4iLLvZ1/yfhYr4jCy6S1fcpNB88bB09uQJzv6dW7rj5gnPvq+lO2KWAO27uo2nxNSb++tYwbwPCtLXXGLOEdaIl5gwfR29BUdX4z6xQO06nHdkNe4NC9Wut3lHVePeAoK1627mEdWIv7hwDX58eM7R1IhfKGBft/s5R1ITXpCQ7TmacxQ1PpK5grY7rK89gpqam1DY9mfmrKOnEX9hEfWql8w+chrxFxC6L85mHjWNeAoK3m6WP++IabJih8K3qNZnHS2NjWREAL8O5y2OlMY2rIgS7tTANzhKmhz7TElc3I1bHyFNLDYJPecGR0fnjdO88bnZuPjHRd1Xolw18uYY/3XN/KOi1lLmQdGhO/MOYV0cJI5gJSyHDmB55lPHryI+IschLy6geh27sVGPAT3b8ZehaEfjKu4+oA67/jNk7lkdyFK+nQ4Ev/8Sb+nvG/66ef7mKOSO2zka+bzhnsXdLv2ZIZwvD7/AofJLJ2ap6XFP+ID51dMcRT31ndCxMvc75Vf3qe8U9zRHWY96ChxCv3iapa5HPSWPirtb1RKFPeo9fGz84nauyp7Yb4JHyJ39ZqbSnvAYPk7uuJyrtkd9Bo+WXxzOVNyj3kLHzC/O5qnu8XiJH9a+Bs1NlPf4xAmfKL5OmVer7/FkInQa9ZpLvFKBj4dZ9Ez3NdZuocInsozoAW83k5yrxCf8Rhfp5St0xsHvi5flMyv3ELh/J5fv2rkHwn1/y3fUzMPhvrtbeUtOzXkKfTTJDBwav+Ylc1T6qKf4AfLrSC5Q6lN+Q4fJXafz1PqUx9DBctfjPMU+5TF4yNx1OVO1j/pMHTi/Rusi5T7qO3D4/Jo2zFHvk57wQXTP2RwFPzWq+YfS3SF+rYqfnruBA+r+zJ2l5Ed9xg5EXzWk2Wp+1Gfw4PrF4UxFP+otcojdrSjmqfoZkkLgQLuQE2Yp+/FRjR5uvw7tLdT9+GYXPenu1OI3UPgz7kP2sXdxZ26t8icXpKQAdQOlf+54zR2nG46Pq/f/uTlOg3r4403U/ouhLLHf9GWO1n81H5X6U3ax0u8bjwv9MQ9Bnf/qIHlIXdif/FxydZpmTlfz9vMltuvjtKy8Wh4/zbMbyBIi1v1W870kBOiro4UHt2N3HarNV4+zxOaoH6Q1O37mSM2J7zOtV73vk1+sJr5P1M8cmTnmB6rMVz+zROaYn4TG7C2QSyTmmO+Qwnx1Oldgjq9zAX3ZXedmystxfyF12XU4V1yOeQxoy85CO09ajvnCyvLV1TxhORolMV3ZCZWbyMrRqRKSMZ1J8mpROdaBgKZ8df9aSTkaXBFF2YmwWwjK8R0/oid7GctcOTnuNbYYL16Jk1ry1cfiuZSnJIs7uHhPztORhbfFO2aWiiyc3chXairOk5BjWSJUkJ2MY46AHPMT04+dMVwgHye8YvXYczlPPE74w9qx52+edJzwF1COPYczheNE7RLRjb3qZYFsHPMMVWMnJZgjGqf8IM3YdzVHMs6sBVOKMSwNlwrGybkK9WIxU2fJxTGPYYXR0SFmi8UxjwGt+OpuplQc8xVUir3aYJ5QnK78oU4sq/5ZMnF0PCMqsTOotxCJo9tZRCN26+gbSMTpO5CpEMt7cmuBOLX8pPShG8jDM8dq5hjdbmxcbfgXXO+Of76JPuyYylKIzz0KlFCnuntuj+ANj64bp9EiH9V0Wnnmq/zfFpp48tfMY1cDWdd1dmmS+U3EHvdcndBi5XpwGr3GB1yJkZ/ob1DFfe0+1rvffgI0wwuya6PFPt6e6irt5NxquZfDIcPJ4fAaH9+1h0PToUxr4sltusTfqfqyq7p4OF/bLJmZ+6qP35Zzg7ydPjF6p7rqa/ysjDd0XrvbeYbUbur43Ox2fs/v1017vjS8nW9UbU0dz6m1cryGXho89XxtecsIwz/ej2LMtryF90j+7LpekEHn+M3weSN/sKL21ow5NXXaF6qqpbs5dXViza2htuVnQ7P0rbi/x6Ei7utj31SHaQLrupUtb+G9P1Wf6lNiqjqNFvr0icFT9fmPx/7U1PHv67e7jefmmOfZbXeLca4SiUklcpLlnrq6Ou2mj+v5eeO5yU3W3CHH2ScG1Gl0C5+HtppyFtffucFNvt+hrhI12LnFTeZjCwVSbza2s7TR1N17Glr+BR288G+g2+62nqcoBnvOJzKpMf7PX376r8QYn5vcwt+HU/uU9Og0uoXP96kF5/3tVpz3Dy/NIT4fL00W+lsXBa+vDt/Dg21Th2/G//xBvblcEiuS3nhXBYn7x+awP9Xx/BL7f+Ncm98R2/lAfwIPTGf0JvHDogv6cqirD0t6cr7uZv2AB8ky+hF9Q9ayfsRXt3A/wm+1WNIPdOAtpx+xd3ot68ey8Yi9nyuvH/6StfvYtl39y8tDf6oTK4lsepMlM0OnfA+Eyld47J4PTcKhbXI7fz+3bY7Pc7Ob+D3frsHsj8c9IBTo5rqtb92Lt5+bRLUxbXyrFOD7puv/Up0em8RuJZvexv/+ny9d/1fTekhZU0UXbH+bu9Ee93WKcbitlqYpjrBaP9LJqVI+vBx3Bsh9ff7kDyoKg9YuPdnTvn4Eps6f5JvC8tzVoPf5PKNu2SINpn633zFWH7uX0/mwni8VX41O2swzfkGk3lkFad5rletg33R9ddyhobUfZY/qod399nuDb5P9LPuLf+7r496TJp3ve/kw19xDu//yl/ZTjcL78lmusWPb/ziciuzqnTn7A2zKJrmmm4TdZonRXXU0nal2KDadT7MNGij14xH18PLZjLA5juyke3tyuasXPV6TXNNP1fM4535tQ6ZlkxmmTftfW3GY1zPtNck1/Vj337Yvx139fXOqd94JqavxaaMZ5n95++uvdYeC4fph9gJSdR8HAPVri5aQy4fZ5k7V7xKHOPaun84x6OrxvrFv//anWXH6szx7IILUfp69U9b9y+n46+kFWbx+OOPe+k+5eXfWfJS9YjZH2tc7tF6On8zbwv7r5emhRrPE/Tj7aw5f5NsG7oWXz7JnsgtPnNnbfooPO1/Pvnz/xx/o57ffX6x8qk5N9XCou6/Pn+Qa+vnt9/T9H38Ahs6fzBt0ceJJDvr4ca7J53He/XgUdPhqVbTIN9wc+x+P4VnvN5jd35hhv8msHv8UGwjn8+z1xbSODK/fYK7Z8CiIFvMM//TSd81+QrCldb/Z7Gxo2EOC4wJaLXIw/O8/KrgtwnaLneQ4WGQ8fBdQM+TiH/d3jVEVvvnX3UBQh+Tjmzt6w2/Ku/u7D0192Hd337wbvd8PSOSpNhXPvt29mH/+49xszHmGxmPrr1d39+9W93r1piT9j3/cv7MXmw/MH6yN61/Mheru/p265/LNVpF3oZpcqLwL6e7+HaELaXIheRfy3f07Rhfy5EL2LtR39+80ulBPLtTehcXd/bvivti+oY13XTG5rvCuW9/dv1ujQV1PLlx7F27u7t9t7gt+o9Z+TzeTCzfehdu7+3dbdOF2cuHWu7C8u39XogvLyYWlf/+HcFArdKmaxo4SwWOiR8GLQfz4AaSGsFAEL57GkPKDSA2hofheb97Q1r85ahpHyg8kNYSH0tDzNJaUH0xqiBFVwIunAaX8iFJDnKg1vHgaVMqPKjXEioJxpaaBpfzIUkO8KBhbahpcyo8uNcSMgvGlpgGm/AijIWYIRhhNI4z8CKMhZghGGE0jjMQSZdYoGGEEVik/wmiIGWJ48TTCyI8wGmKG9L2mN2pT+BdPI4z8CKMhZqi41+WbTemvWDSNMPIjjIaYoTWaGDSNMPIjjIaYoQ30PI0w8iOMtqGthKYBRn6AURncTabxRX588Sq4oUzDi/3wYhXaU3gaXexHF5vogjOKp9HFYhM00QVnFIN90I8uHuKF4YziaXSxH108xAvDGcXT6GI/uniIF4YziqfRxX508RAvDGcUT6OL/ejiIWAYrtk8DS/2w4uHiGG4ZvM0vtiPL23iC67Zehpg2g8wPcQMb0DyoacBpv0A0xRcRPQ0wLQfYNqkWVvkeBpfWiRaJr5KtAxokGv58aWHiNErePE0vrQfX3qIGK3gxdP40n586SFiNMGLp/Gl/fjSQ8RohhdP40v78aWHiNEaLbp6Gl/aj69iiBgN89NpeBV+eBUqeKeKaXwVfnwVFLxTxTS+Cj++Cg7eqWIaYIUfYIUO3qliGmCFyOaL4J0qQEbvB1ixDt6pYhpghR9gRXh7LKYBVvgBVpgAW6PbPI2vwo+vogxmBMU0vgo/vtarYKq8ngbY2g+wtQpfPA2wtR9gaxNgaPFbT+Nr7cfX2sTXFlZf0/ha+/G1NvFVwoun8bX242ttykVYTK+n8bUWJeMQMQWcFmtQNfrxtR4ipiDoeRpfaz++1kPIFAwvngbY2g+w9RAyhUZ3ahpfaz++NqvglNpM42vjx9dmiJgC7sybaXxt/PjaDCFToCm1mcbXxo+vzRAxxQYN12YaXxs/vjZDxBQwODfT+Nr48bUx8VWiXk/Da+OH12Yd7vU0vDZCldiEew2ECT+8NkPArGHOuZmG18YPr00ZvngaXxs/vrarYBq0ncbX1o+v7RAxawUGezsNr60fXtshYtaErp2G19YPr+0QMGtG106ja+tH11YH58R2Gl1bP7q2Q8Cs0UzeTqNr60fX1gheBRzpaXRt/ejaDvGyXsOLp9G1FbqXia4NvBhIX350bcuQ+LmdBtfWD65yCJc1LOLKaXCVfnCVJrjgNlNOo6v0o6scAmazQl+5nIZX6YdXOUTMRsGLp/FV+vFVDhGzIXjxNL5KP77KIWQ2cJsppwFW+gFWDiGzgVO5nAZY6QdYaVRVGJ3lNMBKP8DKIWQ2MDrLaYCVQlwdYmYDo7ME+qoUWIeg2Wyhxr5CEqvQWFdD3GxglI2fyeuFzLoaQmcLA238TF4vlNZVOBMbP5PXC7F1FU7Gxs/k9UJvXYXzsfEzeb2QXFfhlGz8TF4vVNdVOCsbP5PXC+F1FU7Mxs/k9UJ7XQ3xtEVb2PiRvFyEnwpvnwop/BOJfwinLdoFFdT4RfSpsIihkMwvdX4j3W/RRqqQ0C+VfhUWYhXS+qXYr8JqhkJyv9T7VZAhKST4S8V/lPzh0oEkf6n5GxkfSqMKif5S9VdBVVYh2V/o/spI+VBcVUD4V0L5V0bMx9MeSP9KaP/KyPl4Z1dA/VdC/ldG0d+iREoB/V8JAKCMph9YNQACUIIBKCPrB1YtQAGUwADKKPuBVRuAACVIgDLifmDVBixACRigjMCPcyMFeIASQEAZkR+nRwowASWggDJCP84oFeACSoABZcR+nAsrwAaUgAPK6P04HVYADyjBB5SR/AO7HiAESiACZVT/bXGv+c16tRbXg/gTlEAZ4X+LamsFMIESnEBxuFBVgBQogQoUh2tVBWCBErRAGQCw3eCvD8JPAANlGABOsBVABkowA2UwAM6xFaAGSmADZVBAIGkA5EAJdKAMDcBpugLwQAl6oAwRwJm6AgBBCYKgDBTAyboCDEEJiKAMF8D5ugIYQQmOoAwawCm7AiRBCZSgDB0IJM0AJihBE5QBBIGkHfAEJYCCMowgUDQApKAEU1CGE2y3sP8AKyjBFZRBBdsSzh9AFpRAC8rQgnKFlg/AFpSAC8rwAsw1FMALSvAFZZABTrsAYFCCMCgDDQKLP2AMSkAGZbhBYPEHmEEJzqAMOghsXoA0KIEalMEHJSwZAGxQgjYoAxBKmPID3KAEb1AGIZQwZQfAQQnioAxECOx8gDkoAR2UAQklTNwAdlCCOyiDEkqE4xQAD0qQB2VgQgn3TYAelGAPyuCEEiETBeCDEvRBGaBQbu918YY28np0wEgE3jpcbwACoQSCUOtwvQEYhBIQQq3D9QbAEEpwCLUJ1xsARChBItQmXG8AFKEEi1CGL5QlHHqAI5TgEWoEErhgAERCCSShDGUIJPwASihBJZQhDYGEH4AJJciEMrAhkPADNqEEnFCGNwQSfoAnlOATyiAHtVrhGwCiTzAKZbCDWilsAMSf4BTKoIdAyg1IhRKoQhn8oFYEOwBwhRK8QhkGEUi6AbJQglkowyECSTfAFkpwC2VQRCBpBuRCCXShDI4IJM2AXiiBL5QhEoGkHwAMJQiGMlAikHQDhqEExFCGSwSSboAxlOAYyrCJQNINUIYSLEMZPBFIugHNUAJnKEMoAkk3ABpKEA01Ig2ctgCmoQTUUIZTBJJugDWU4BpqBBt4CQJkQwm0oUa2gZNuADeUoBvKAAu1YjiBAeBQgnAoAy3USmMDIAIF5VDluAgW2AAIQUE6VDkugmtsAJ31lYd9x/PkG2SAAO4ggTvI4AtcuRDAHSRwBxl8oVYwByLAO0jwDjL8Apc+BHgHCd5Bhl+oFcwECAAPEsCDDMBQCu5kBIgHCeJBhmAoBXcyAsiDBPIggzDw4S4CyIME8iCDMGD5RYB4kCAeZBAGPkkMiAcJ4kEqeCCYAPAgATzIEAx8FhkADxLAgwzAgFkoAd5BgneQARgwCyXAO0jwDjL8AleOBHgHCd5Bhl/gLJYA7yDBO8gADJzFEgAeJIAHGYKBs1gCxIME8SCDMHAWSwB5kEAeZBgGzmIJMA+SzzoYiIGzWEJPO8jHHSgsOxN64GHyxENYdib4zIMIP4MxAlk0oece5IMPpMNZNKFnH+TDDwZk4Cya0OMP8vkHWoezaEKPQMhnIAzJwPoDoacg5GMQhmTgLJzQkxDyUQhDMnAWTuhpCEE+yJAMnIUTIB8kyAcZkoGzcALkgwT5IEMycBZOgHyQIB80kg+YRRMgHyTIBxmSgbNwAuSDBPkggzJwFk4AfZBAH2RQBs7CCaAPEuiDDMrAWTgB9EECfZBBGTgLJ4A+SKAPMigDZ+EE0AcJ9EEj+sBLKEAfJNAHGZSBs3AC6IME+iBN4SycAPsgwT5IczgLJwA/SMAP0jqchROgHyToBxmaEcjCCeAPEviD9DqShQP+QYJ/0Mg/cBYO+AcJ/kF6G8nCAQAhAUDIAI1AFg4ACAkAQsUqkoUDAkKCgFChIlk4QCAkEAgZphHKwgEEIQFByEANLOYSgCAkIAhFnrIgQEFIUBAaKQicxwCCkIAgVIRPkxKAICQgCBXhA6UEIAgJCEJF+EwpAQpCgoKQwRoKP6BMgIOQ4CA0Pnih8COkgISQICE0PnyBH1QmgEJIoBAybCP0MCiIQMFCyMANhR92JkBDSNAQGmkIDmGAQ0jgEDJ8A1eCAIeQwCFk8AauBAENIUFDyOANXAkCGkKChpDBG7gSBDSEBA2hdfBYMwEaQoKGkMEbgUoO4BASOITGJzNwJQd4CAkeQoZvBCo5wENI8BAyfCNQyQEeQoKHkOEbgUoO8BASPIQM3whUcoCHkOAhtFlHKikAREgAEdpsIpUUICIkiAgZwBGopAAQIQFE6AxEcCUFgAgJIEIGcAQqKQBESAARMnwjUEkBHkKCh9DIQ3AIAh5CgofQNnzKngAPIcFDaBs+aE+Ah5DgIWT4RqCSAjyEBA8hwzcClRTgISR4CBm+EaikAA8hwUNo5CG4kgI8hAQPoZGH4AwC8BASPIRGHoIzCMBDSPAQGnkIziAADyHBQ6gMn7wnwENI8BAan/LASxjgISR4CI08BFdSgIeQ4CFUFpFKCgAREkCEzkAEV1IAiJAAInQGIriSAkCEBBChMxDBlRQAIiSACJ2BCK6kABAhAUR4FT6JxYCHsOAhvFLhSooBEGEBRHh8/gNWUgx4CAsewisOV1IMgAgLIMIjEMGVFAMgwgKI8BmIwJ2QARBhAUR4FX7qmwEPYcFD2PANWAkxwCEscAiPOASuYwx4CAsewgZw4EqIARBhAURYhc9CMyAiLIgIn3/mCf/sB2AiLJgIjz/1hCshBlSEBRXh8eeecCXEgIuw4CI8PgcCKyEGXIQFF+HxV59wJcQAjLAAI6zCJ7MYgBEWYITDT4Iw4CIsuAiHnwRhgEVYYBEOPwnCgIqwoCIcfhKEARRhAUU4/CQIAybCgokwhU9mMWAiLJgIU/hkFgMkwgKJMIVPZjEgIiyICFP4ZBYDIsKCiDCFT2YxACIsgAhT+GQWAyDCAogwRU5mMSAiLIgIU+RkFgMkwvIXojh8MovRj0TJX4niyMksRr8UJX8qyjAOXAkx+rGoya9FhX+tgOHvRYkQ5PAPFjD6ySj5m1EjE4GVEKNfjZI/GzUyEVgJMfrhKPnLUYZx4EqI0W9HyR+P4vAzmIx+Pkr+ftT4OEjgehCAgonwyERgJcSAibBgIjwyEZxBACbCgonw+DgIziAAEmGBRHh8HARnEICIsCAibAAHroQYABEWQITHx0HwEgZ4CAsewuPjILASYoBDWOAQ1ptwJcSAh7DgIXzmIbASYsBDWPAQ1mW4EmIARFgAET4DEVgJMQAiLIAIj0AEV0IMgAgLIMKGbwQqIcBDWPAQLjhSCQEgwgKIsAEcgUoIABEWQISLIlIJASTCAolwsY5UQoCJsGAibBhHqBICUIQFFOEi/DNnDKAICyjChnHgSgggERZIhA3hCFRCgIiwICJsAEegEgJAhAUQYQM4ApUQACIsgAifgQh6vIMBD2HBQ3g9lsLo+Q4GPIQFD+GRhyj0s34MgAgLIMKR50MYEBEWRITDz4cwICIsiAiHnw9hQERYEBEOPx/CgIiwICIcfj6EARBhAUQ4/HwIAx7Cgodw5PkQBjyEBQ/hyPMhDHgICx7CkedDGPAQFjyEI8+HMOAhLHgIR54PYYBDWOAQjjwfwoCGsKAhHHs+hAEOYYFDOPZ8CAMcwgKHcOT5EAY4hAUO4djzIQx4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewttIFQJ4CAsewmWkCgE8hAUP4TJShQAewoKHcBmpQgAPYcFDuIxUIYCHsOAhXEaqEMBDWPAQLiNVCMAhLHAIl5EqBNAQFjSEy1gVAmgICxrCZawKATSEBQ3hMlaFABrCgoboVaQK0QCHaIFD9CpShWiAQ7TAIXoVrkI0wCFa4BC9ilQhGuAQLXCIXoWrEA1oiBY0RK8iVYgGNEQLGqJXkSpEAxyiBQ7Rq0gVogEQ0QKI6FX4x2E0ACJaABG9ClYhGvAQLXiIHt95odBPcGrAQ7TgIXrkIYR+3UADHKIFDtEjDoEwQwMaogUN0SMNIfR8vwYwRAsYosf3XxB6wF8DGKIFDNEjDCH0hL8GLEQLFqLHt2AQekRfAxaiBQvRIwsh9Iy+BjBECxiix3dhEKriNKAhWtAQPb4Og/CvuoP4EzhEU/gXmTXgIVrwEE1j/MEfdwdARAsgoml88wqMfwBEtAAienxIhGH8AyCiBRDR4zMi+B0EGhARLYiINoRD4fcQaIBEtEAienxIhGEEAySiBRLRFD4erQES0QKJ6BGJMJwBgIhoQUT0+OtYeAEFQEQLIKI5/JuUGgARLYCIHoEIficC4CFa8BBt+IZiOIMBD9GCh2geIxDOYMBDtOAhenxGBHcfxJ/AIXr8cSx8OYg+QUO0oRvw3R8awBAtYIjmTfCVTRrAEC1giDZwA7+1SQMYogUM0VwGX9ykAQzR8n0aBm5glK7RGzXkKzUM3MDvQdLorRrytRrjAyL49qEXa8g3a4zPh+D7h16uMXm7hg6+EEnD92uI6DNwA78TSaNXbMh3bBi4gV+LpNFbNuRrNgzbwG9G0uhFG/JNGwZtBN5rgt61IV+2ocNvQ9DodRuChGgDNvBZEA1AiBYgRBuugV8DoQEH0YKD6PHBELj2AgyiBQbRhmpAFVQDCKIFBNEGakANVgMGogUD0ePbN/Dl6PUuIvYM0MAvTdIAgGgBQPT4Cg489wD/0IJ/aMMz8KuTNOAfWvAPbYBGIHMBAEQLAKIN0AgkLgCAaAFAtAEa+B1KGgAQLQCINkADv0ZJAwCiBQDRBmjgNylpAEC0ACDaAI3Ay5QAANECgOgRgDBe/AEB0YKA6PWY+eEIBAhECwSiRwQSuoUgBAUE0ettJPsFGEQLDKIN11AM03fAQbTgINqADaVh+g5AiBYgRBuyoTQsPwEJ0YKE6M1Y/sLyE5AQLUiINmRDaZi8AxKiBQnRhmwoDZNvQEK0ICHakA0F39ikAQnRgoTo8ckQ+CogDUiIFiREjw+GwPfqaEBCtCAheiQhGpaPAIRoAUL0CEI0jD/AQbTgIHo75n8w/gAH0YKD6JGDFDD+AAbRAoNogzVUAeMPYBAtMIg2WEMVMP4ABtECg2iDNRR8244GGEQLDKIN1lAFjD+AQbTAIHoblqE1wCBaYBBtsIaC793RAINogUG0wRqqgPELMIgWGEQbrKEKGL8Ag2iBQXQ5xh+MX4BBtMAguhyLXxi/AINogUG0wRoKvpRGAwyiBQbRIwaBGEgDDKIFBtGljuyhgINowUH0+FxIYA8FIEQLEKLH50Lgq3U0ACFagBA9ghD4eh0NOIgWHESPHAS+JUcDDKIFBtEjBlnDGQgoiBYUpBgpyBq+DA5AkEJAkGKEIGs0gwrAQArBQIrxN7LWaAYVgIEUgoEUIwNZoxlUAARSCARSjE+EbNAMKgACKQQCKUYEskEzqAAEpBAEpBgJyAbFXwEASCEASDECkA2KvwLwD/s38/73T/Wpr/c/ju+Bf/fu7v37/stzfXf/r7v355fD0+UF9P+6I333zb/+fX+nafzvdjX+d3hNx/kfZP9hm+rt8I9/X18gb/5se2M+G7r3vtr/86Xrn6tTfewfHtrPdef2gotrL7g8m15zpunDwftG5Hwjst1cZ9vaN13/VJ0em6PXQ2eceGt7SHlWH16aw97rIzt9ZNvHTZ613ce27eru+dD01efGH0fHLm9sL9Vsu6M517AzqLy2hlezDL889KfaD76tY9XeqiLzVu3a474+dp5BXl8N6pXtps4z+HhqX55da1snKod3Foyhb2fF8GNQ4z8253gYjvKP/9hmjszHunn82Ls+hweXrqGxtgZVZqQ1x64+eQapdMbYhlqRGWpP1ef62J8af7ZurhZtjHHmGD81R2DQCQM7tbjIM3iq9s1L58VqqZzbVmSuT6OdL54dcu2UeXbM9PFCcuWMv101c3tlrJ3a1rfofD8urMXc/vW1F+OlE24qd0L/3uz7j37UuotuUdqozVt5qg99fXp53ld93TX/XfuGC3c6sDWcFx1iZ3Bmgt1jOG+xqY6PB69fhXMP1uf1cHiZ1/gPyuvesA+6Rp1IOc/7bWktqvM/Nud/DD/GOP5jbUfbtuFN3rA/NEfa1ztvuAtnjIZaJMtOu/fmTeHcMtvdbd76MJiaLMPasVfkd+mp/VR7m+6gwly/3Ca/R+1z37RHb8XSzhqYmQUMlp5P7bNnZ+10aZsZNe3+i8zi2FlBdV7aJGJPkzs24zXFebmiIm+sdtWxOfb1qdr5W5o7WINAlGWrHix5s3fjrlR5YzVaEWGwcveHTWl3c/tlNxs7n+zE2uStrbuP9e63qjk++F/evTWbvHV/tOQvXsMRlKshlTmKxtCprnxL7jxQeSugsbRrD4ema9qjb85NutSMb9ge/bXezYp15hK/+9gc9qfa65E7UjZJs3vSAOjm2N1XvTd42vmymbv4rjnt/N1DOyu9th28lFaZX9xYbY5T60Ot7iyYeZuBNffcHr48ihu83rr28la70V770nfNvoZGS9do3pq1O9TVyd+v3BTN3uG8PWJ3aHe//d74BYTy18HM73poj/Vz2xz7rjqdqi++QXdHzV1JBoOnuntuRX0zCKFXa9vMRWCYtXs/41buGqhsMpQZz3YV2FWHw0O1+83bidy1PjP02qdBF3hqjt6OpJyoU7Ys0Jl35GLSvxnOvVCXBSEz9tpj159edn3rRaBzP2z2p+w/Lvvnams30nOMDr/aMf7jsi6tMkOjPX6qP58nlHdLS/fLbcimiZci1ZaSVhPh3Og+1VVfi3RBuUWIvghH68wIMibByuUWS5oyb4wxVh8OzbOYLK5ooe1GMCDSfLOHRu5R7nSmOSNoFgjflpsaUOZkOduaLqiu9jOcIcyxtq+6j8NX7Ft/lXEThMzUXW6Uro5haxUrZ6ztikN5S/W+fqRT5Wdx3kKztje3zOxsc+q9lWHjBF5pd2KbACqbEhJZ7WdjJzLZiZyZ1w7qYnXciU3HnUyZ1dv+VP3ufQN3+q9suajPK48q7VpkZbHhB7zGntsMZHiE5Hxb8taioQsPnzwhYOMu2isrG2s7kNYDaVvG2n6xXtteWHnRqmkDE8ztDpoYbj2qM4sssJywO/PPvS7sOBd5a1V99INYOUHMmTplfexeTvW4D/gZlbvclXljNho7vjw9+MXWQBwdY3khORo734IxH/LD3LW5yZv6o81P9bDvTldQ756s86K2/tzXx70ULYYjHk7n8m7nh1P79M/ODzdy5rJVZLTOi7rHuq+qh4eqk8rQduVOK7s+2Tpd2alMlp2QXcPYRipnrozXLvg9cOPB6t2Z2exj3T+0L8ddvW9O9a6X5WPhhu02b0d9rPuhtD+1jZhPLlDI1AgcW783/cf2pT+1fTXp5srdz+waRRaVkV3Z2N6U4Xm4XPfTuayc4R5euXNOH63Ct7J3tbCL5Spvvj/W/URfG043OHcg+6a2Hz50dT+M2UQeLd14LbLv6XPb18e+qQ7+uuHSQc3Z1rqq7+vOXzAKd5Zv82b5ZMBcI2u7wdocX9mtTW1t1mCxC23tHmcJ4vDUbXYXHhqxnq7dtKHMGxdvt147XyRzMKbEypV/yM6LtV1/LqA4U7mdAizlJKMXa5k8zMiRXb0za5pv1C2kN3mT52JtSJlB9eImkcUqb731bE6qjQ25Fhf0cvjfh0pMgo1yreatkp5VlGVt2DWat/YNRv2+bV3mlomPmg7JvIVzM2x5oOxKquyUVLYUIZvIk+W7bIk0Z25KTTddyF0pbmtXCJvYqsL6VBel3S4M9sgFZ9KFpuuap2cRkKuNu4vYxN9m2sOvx553EZunZIqFTdcNO6SHjlyd23I6tbL/sAuD2thvqux3t1IFWxWBM9FM0/Wn5vHRT1xduW1tkb3dLpW9ycrWb2SVJdrYcsgKBbzJmxmHuvrgrYfuoFuSYjl5kTfAciHwktNzjy9aUqYMfWhFBe1ONXv4gvN2/6fq2SidfTtm5v4e625L27wv/FQ9j5b6diqhulNZZx6teKpOv1XdpM7fulWyDXuyYUq2UGWbY7PV8HTmev5UffYPzjgJqbV9jkEbesrmEGQnPVmkzLYN2zY6k8U9VZ+9L+4qOta2nRS2Q1YiJBsOZL2ybcO2jS7yZsdE1XXG3y64djzs5FzbpWFlyxm7gLNtw+vLjMpbH6UU7J5XsMuh7Yf9hyXrZBVysl7ZtmHbRmcqAAOV9gQTJ0BsDaFsVaHslkH25AjZpJJtqcf2jIAu83pwbPtIWuSm2pkL4FgHeLuesxeUNroyxdLR2q599uetmwRmCsPP1X7fHB+9pMC57fZeKqvFK7sBKbsXkJXyyZ5jYHtajzMlgolm4R1CON86W8VdNPrMVM8YDzA7z03eDmHMtZAoutRzlbcCjwLQwIdEublyi0OrJZAdWNrYtc8uDJxJzcEweMLq2W5hHWUmqWezmLS6+3JmwX+xB2+bO//KvM34ufWPlDjfOXP3vdTd093cBY6ZwOhU7Wlfe/NOeQdU7O6my7wFBp3xc8Fv5kFhdMRv7ZrJ21lldkJudpJZspyqLztZlZFHTvNu/dnOR//IoYtvMkXR08NL50kD7pmrPAu13NqUN7jnW86ZA4Tgt4tvtcpbhU51f6qO3aHqRWnkBCTZbI9sbs0WoLBdLXTmEYVT3b+cjv3pRXB79+Zmij5GhhS9dofUlm9kAQ6VtvuFzdIy12oglW3dTC0TTne7yl8gt+6ZLSt6KSuDka2waWvzO3tonTPPNhiPoAZZucXN9lLjX7ZzmzzZJJgzV0rjz1uJXDHF6vDKJmdqa/M2m/XT1iYQ9uguZyIp49qHls6XtBxNWb6n7JgSXxJtm/nbE/ycSda6ujrt/AO/HpCwczvzntXP1UlGtotNtMqba9bQhOW4j4Bolbesd3U/kbE3znQrL/quzdJLO642aMnmumwTeM5UZTurpnvenclj57qyWYyyhIfstCLbH9Y2xDIrgm5Q3ie0buWm3KX9itY2WQTClzUzM8s03rpGApaN487aVtabskUO2VlDtjRiG/CceVy5q/vJOrVxtjv7VZVFZ8reVrLzhmx32Ir9nIlIu74S6ra7S3JmJjM5xO/eqkzBsPvS9fWTlzi65+Mv4oANe5uWky19yUp5bJdV3lxoVF4o9K0EnS4Qt7W+zjxH1rcTFutRNGstbz0IpA1u3mmXBLKRQjZ2+CIkZebw/an6NJTkftLsMgrOCw1r6MPLcYJaty7CzFxj5Ulzt0Cx+7i6SElWkyRLvsmWtry6KFl503RwPCFvbh1qlRBlswhlfZE94kZWd+XVRa/Ji6WXo1kj9mPN4A2im4JlPqU0Ptvi31u3Pqe86TKakbvd1j1yYxM/sgksXXi01f/ZnjTWNu3VlLdxj/69E4h2JRdbR+nOuwvbuKgbdn+y4aIzIRTogDjV6h6+sI/jkP3CvLpI5nZpydQARscI8JTu6K8vHi8P51zEVjsP1nkpF6jEnUHNVOa8A2Lu8aBMogWe83IfF7bsjDPXEm/g3IrI6m2by4Ji/7G1M9nuQ2STebYIjzPJtf+MknPb7D62uSwo9h82VyerDZPtD1tBlnOOR/7j/u65eR7x7jfv/vHvf/8/SGmAbVySAQA="; diff --git a/docs/assets/style.css b/docs/assets/style.css index 9d619a64..7e5ec04d 100644 --- a/docs/assets/style.css +++ b/docs/assets/style.css @@ -1,186 +1,91 @@ :root { - /* Light */ - --light-color-background: #f2f4f8; - --light-color-background-secondary: #eff0f1; - --light-color-warning-text: #222; - --light-color-background-warning: #e6e600; - --light-color-icon-background: var(--light-color-background); - --light-color-accent: #c5c7c9; - --light-color-active-menu-item: var(--light-color-accent); - --light-color-text: #222; - --light-color-text-aside: #6e6e6e; - --light-color-link: #1f70c2; - --light-color-focus-outline: #3584e4; - - --light-color-ts-keyword: #056bd6; - --light-color-ts-project: #b111c9; - --light-color-ts-module: var(--light-color-ts-project); - --light-color-ts-namespace: var(--light-color-ts-project); - --light-color-ts-enum: #7e6f15; - --light-color-ts-enum-member: var(--light-color-ts-enum); - --light-color-ts-variable: #4760ec; - --light-color-ts-function: #572be7; - --light-color-ts-class: #1f70c2; - --light-color-ts-interface: #108024; - --light-color-ts-constructor: var(--light-color-ts-class); - --light-color-ts-property: var(--light-color-ts-variable); - --light-color-ts-method: var(--light-color-ts-function); - --light-color-ts-call-signature: var(--light-color-ts-method); - --light-color-ts-index-signature: var(--light-color-ts-property); - --light-color-ts-constructor-signature: var(--light-color-ts-constructor); - --light-color-ts-parameter: var(--light-color-ts-variable); - /* type literal not included as links will never be generated to it */ - --light-color-ts-type-parameter: #a55c0e; - --light-color-ts-accessor: var(--light-color-ts-property); - --light-color-ts-get-signature: var(--light-color-ts-accessor); - --light-color-ts-set-signature: var(--light-color-ts-accessor); - --light-color-ts-type-alias: #d51270; - /* reference not included as links will be colored with the kind that it points to */ - --light-color-document: #000000; - - --light-external-icon: url("data:image/svg+xml;utf8,"); - --light-color-scheme: light; - - /* Dark */ - --dark-color-background: #2b2e33; - --dark-color-background-secondary: #1e2024; - --dark-color-background-warning: #bebe00; - --dark-color-warning-text: #222; - --dark-color-icon-background: var(--dark-color-background-secondary); - --dark-color-accent: #9096a2; - --dark-color-active-menu-item: #5d5d6a; - --dark-color-text: #f5f5f5; - --dark-color-text-aside: #dddddd; - --dark-color-link: #00aff4; - --dark-color-focus-outline: #4c97f2; - - --dark-color-ts-keyword: #3399ff; - --dark-color-ts-project: #e358ff; - --dark-color-ts-module: var(--dark-color-ts-project); - --dark-color-ts-namespace: var(--dark-color-ts-project); - --dark-color-ts-enum: #f4d93e; - --dark-color-ts-enum-member: var(--dark-color-ts-enum); - --dark-color-ts-variable: #798dff; - --dark-color-ts-function: #a280ff; - --dark-color-ts-class: #8ac4ff; - --dark-color-ts-interface: #6cff87; - --dark-color-ts-constructor: var(--dark-color-ts-class); - --dark-color-ts-property: var(--dark-color-ts-variable); - --dark-color-ts-method: var(--dark-color-ts-function); - --dark-color-ts-call-signature: var(--dark-color-ts-method); - --dark-color-ts-index-signature: var(--dark-color-ts-property); - --dark-color-ts-constructor-signature: var(--dark-color-ts-constructor); - --dark-color-ts-parameter: var(--dark-color-ts-variable); - /* type literal not included as links will never be generated to it */ - --dark-color-ts-type-parameter: #e07d13; - --dark-color-ts-accessor: var(--dark-color-ts-property); - --dark-color-ts-get-signature: var(--dark-color-ts-accessor); - --dark-color-ts-set-signature: var(--dark-color-ts-accessor); - --dark-color-ts-type-alias: #ff6492; - /* reference not included as links will be colored with the kind that it points to */ - --dark-color-document: #ffffff; - - --dark-external-icon: url("data:image/svg+xml;utf8,"); - --dark-color-scheme: dark; + /* Light */ + --light-color-background: #f2f4f8; + --light-color-background-secondary: #eff0f1; + --light-color-warning-text: #222; + --light-color-background-warning: #e6e600; + --light-color-icon-background: var(--light-color-background); + --light-color-accent: #c5c7c9; + --light-color-active-menu-item: var(--light-color-accent); + --light-color-text: #222; + --light-color-text-aside: #6e6e6e; + --light-color-link: #1f70c2; + --light-color-focus-outline: #3584e4; + + --light-color-ts-keyword: #056bd6; + --light-color-ts-project: #b111c9; + --light-color-ts-module: var(--light-color-ts-project); + --light-color-ts-namespace: var(--light-color-ts-project); + --light-color-ts-enum: #7e6f15; + --light-color-ts-enum-member: var(--light-color-ts-enum); + --light-color-ts-variable: #4760ec; + --light-color-ts-function: #572be7; + --light-color-ts-class: #1f70c2; + --light-color-ts-interface: #108024; + --light-color-ts-constructor: var(--light-color-ts-class); + --light-color-ts-property: var(--light-color-ts-variable); + --light-color-ts-method: var(--light-color-ts-function); + --light-color-ts-call-signature: var(--light-color-ts-method); + --light-color-ts-index-signature: var(--light-color-ts-property); + --light-color-ts-constructor-signature: var(--light-color-ts-constructor); + --light-color-ts-parameter: var(--light-color-ts-variable); + /* type literal not included as links will never be generated to it */ + --light-color-ts-type-parameter: #a55c0e; + --light-color-ts-accessor: var(--light-color-ts-property); + --light-color-ts-get-signature: var(--light-color-ts-accessor); + --light-color-ts-set-signature: var(--light-color-ts-accessor); + --light-color-ts-type-alias: #d51270; + /* reference not included as links will be colored with the kind that it points to */ + --light-color-document: #000000; + + --light-external-icon: url("data:image/svg+xml;utf8,"); + --light-color-scheme: light; + + /* Dark */ + --dark-color-background: #2b2e33; + --dark-color-background-secondary: #1e2024; + --dark-color-background-warning: #bebe00; + --dark-color-warning-text: #222; + --dark-color-icon-background: var(--dark-color-background-secondary); + --dark-color-accent: #9096a2; + --dark-color-active-menu-item: #5d5d6a; + --dark-color-text: #f5f5f5; + --dark-color-text-aside: #dddddd; + --dark-color-link: #00aff4; + --dark-color-focus-outline: #4c97f2; + + --dark-color-ts-keyword: #3399ff; + --dark-color-ts-project: #e358ff; + --dark-color-ts-module: var(--dark-color-ts-project); + --dark-color-ts-namespace: var(--dark-color-ts-project); + --dark-color-ts-enum: #f4d93e; + --dark-color-ts-enum-member: var(--dark-color-ts-enum); + --dark-color-ts-variable: #798dff; + --dark-color-ts-function: #a280ff; + --dark-color-ts-class: #8ac4ff; + --dark-color-ts-interface: #6cff87; + --dark-color-ts-constructor: var(--dark-color-ts-class); + --dark-color-ts-property: var(--dark-color-ts-variable); + --dark-color-ts-method: var(--dark-color-ts-function); + --dark-color-ts-call-signature: var(--dark-color-ts-method); + --dark-color-ts-index-signature: var(--dark-color-ts-property); + --dark-color-ts-constructor-signature: var(--dark-color-ts-constructor); + --dark-color-ts-parameter: var(--dark-color-ts-variable); + /* type literal not included as links will never be generated to it */ + --dark-color-ts-type-parameter: #e07d13; + --dark-color-ts-accessor: var(--dark-color-ts-property); + --dark-color-ts-get-signature: var(--dark-color-ts-accessor); + --dark-color-ts-set-signature: var(--dark-color-ts-accessor); + --dark-color-ts-type-alias: #ff6492; + /* reference not included as links will be colored with the kind that it points to */ + --dark-color-document: #ffffff; + + --dark-external-icon: url("data:image/svg+xml;utf8,"); + --dark-color-scheme: dark; } @media (prefers-color-scheme: light) { - :root { - --color-background: var(--light-color-background); - --color-background-secondary: var(--light-color-background-secondary); - --color-background-warning: var(--light-color-background-warning); - --color-warning-text: var(--light-color-warning-text); - --color-icon-background: var(--light-color-icon-background); - --color-accent: var(--light-color-accent); - --color-active-menu-item: var(--light-color-active-menu-item); - --color-text: var(--light-color-text); - --color-text-aside: var(--light-color-text-aside); - --color-link: var(--light-color-link); - --color-focus-outline: var(--light-color-focus-outline); - - --color-ts-keyword: var(--light-color-ts-keyword); - --color-ts-module: var(--light-color-ts-module); - --color-ts-namespace: var(--light-color-ts-namespace); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-enum-member: var(--light-color-ts-enum-member); - --color-ts-variable: var(--light-color-ts-variable); - --color-ts-function: var(--light-color-ts-function); - --color-ts-class: var(--light-color-ts-class); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-constructor: var(--light-color-ts-constructor); - --color-ts-property: var(--light-color-ts-property); - --color-ts-method: var(--light-color-ts-method); - --color-ts-call-signature: var(--light-color-ts-call-signature); - --color-ts-index-signature: var(--light-color-ts-index-signature); - --color-ts-constructor-signature: var( - --light-color-ts-constructor-signature - ); - --color-ts-parameter: var(--light-color-ts-parameter); - --color-ts-type-parameter: var(--light-color-ts-type-parameter); - --color-ts-accessor: var(--light-color-ts-accessor); - --color-ts-get-signature: var(--light-color-ts-get-signature); - --color-ts-set-signature: var(--light-color-ts-set-signature); - --color-ts-type-alias: var(--light-color-ts-type-alias); - --color-document: var(--light-color-document); - - --external-icon: var(--light-external-icon); - --color-scheme: var(--light-color-scheme); - } -} - -@media (prefers-color-scheme: dark) { - :root { - --color-background: var(--dark-color-background); - --color-background-secondary: var(--dark-color-background-secondary); - --color-background-warning: var(--dark-color-background-warning); - --color-warning-text: var(--dark-color-warning-text); - --color-icon-background: var(--dark-color-icon-background); - --color-accent: var(--dark-color-accent); - --color-active-menu-item: var(--dark-color-active-menu-item); - --color-text: var(--dark-color-text); - --color-text-aside: var(--dark-color-text-aside); - --color-link: var(--dark-color-link); - --color-focus-outline: var(--dark-color-focus-outline); - - --color-ts-keyword: var(--dark-color-ts-keyword); - --color-ts-module: var(--dark-color-ts-module); - --color-ts-namespace: var(--dark-color-ts-namespace); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-enum-member: var(--dark-color-ts-enum-member); - --color-ts-variable: var(--dark-color-ts-variable); - --color-ts-function: var(--dark-color-ts-function); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-constructor: var(--dark-color-ts-constructor); - --color-ts-property: var(--dark-color-ts-property); - --color-ts-method: var(--dark-color-ts-method); - --color-ts-call-signature: var(--dark-color-ts-call-signature); - --color-ts-index-signature: var(--dark-color-ts-index-signature); - --color-ts-constructor-signature: var( - --dark-color-ts-constructor-signature - ); - --color-ts-parameter: var(--dark-color-ts-parameter); - --color-ts-type-parameter: var(--dark-color-ts-type-parameter); - --color-ts-accessor: var(--dark-color-ts-accessor); - --color-ts-get-signature: var(--dark-color-ts-get-signature); - --color-ts-set-signature: var(--dark-color-ts-set-signature); - --color-ts-type-alias: var(--dark-color-ts-type-alias); - --color-document: var(--dark-color-document); - - --external-icon: var(--dark-external-icon); - --color-scheme: var(--dark-color-scheme); - } -} - -html { - color-scheme: var(--color-scheme); -} - -body { - margin: 0; -} - -:root[data-theme="light"] { + :root { --color-background: var(--light-color-background); --color-background-secondary: var(--light-color-background-secondary); --color-background-warning: var(--light-color-background-warning); @@ -208,7 +113,7 @@ body { --color-ts-call-signature: var(--light-color-ts-call-signature); --color-ts-index-signature: var(--light-color-ts-index-signature); --color-ts-constructor-signature: var( - --light-color-ts-constructor-signature + --light-color-ts-constructor-signature ); --color-ts-parameter: var(--light-color-ts-parameter); --color-ts-type-parameter: var(--light-color-ts-type-parameter); @@ -220,9 +125,11 @@ body { --external-icon: var(--light-external-icon); --color-scheme: var(--light-color-scheme); + } } -:root[data-theme="dark"] { +@media (prefers-color-scheme: dark) { + :root { --color-background: var(--dark-color-background); --color-background-secondary: var(--dark-color-background-secondary); --color-background-warning: var(--dark-color-background-warning); @@ -250,7 +157,7 @@ body { --color-ts-call-signature: var(--dark-color-ts-call-signature); --color-ts-index-signature: var(--dark-color-ts-index-signature); --color-ts-constructor-signature: var( - --dark-color-ts-constructor-signature + --dark-color-ts-constructor-signature ); --color-ts-parameter: var(--dark-color-ts-parameter); --color-ts-type-parameter: var(--dark-color-ts-type-parameter); @@ -262,16 +169,105 @@ body { --external-icon: var(--dark-external-icon); --color-scheme: var(--dark-color-scheme); + } +} + +html { + color-scheme: var(--color-scheme); +} + +body { + margin: 0; +} + +:root[data-theme="light"] { + --color-background: var(--light-color-background); + --color-background-secondary: var(--light-color-background-secondary); + --color-background-warning: var(--light-color-background-warning); + --color-warning-text: var(--light-color-warning-text); + --color-icon-background: var(--light-color-icon-background); + --color-accent: var(--light-color-accent); + --color-active-menu-item: var(--light-color-active-menu-item); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); + + --color-ts-keyword: var(--light-color-ts-keyword); + --color-ts-module: var(--light-color-ts-module); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-enum-member: var(--light-color-ts-enum-member); + --color-ts-variable: var(--light-color-ts-variable); + --color-ts-function: var(--light-color-ts-function); + --color-ts-class: var(--light-color-ts-class); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-constructor: var(--light-color-ts-constructor); + --color-ts-property: var(--light-color-ts-property); + --color-ts-method: var(--light-color-ts-method); + --color-ts-call-signature: var(--light-color-ts-call-signature); + --color-ts-index-signature: var(--light-color-ts-index-signature); + --color-ts-constructor-signature: var(--light-color-ts-constructor-signature); + --color-ts-parameter: var(--light-color-ts-parameter); + --color-ts-type-parameter: var(--light-color-ts-type-parameter); + --color-ts-accessor: var(--light-color-ts-accessor); + --color-ts-get-signature: var(--light-color-ts-get-signature); + --color-ts-set-signature: var(--light-color-ts-set-signature); + --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); + + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); +} + +:root[data-theme="dark"] { + --color-background: var(--dark-color-background); + --color-background-secondary: var(--dark-color-background-secondary); + --color-background-warning: var(--dark-color-background-warning); + --color-warning-text: var(--dark-color-warning-text); + --color-icon-background: var(--dark-color-icon-background); + --color-accent: var(--dark-color-accent); + --color-active-menu-item: var(--dark-color-active-menu-item); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); + + --color-ts-keyword: var(--dark-color-ts-keyword); + --color-ts-module: var(--dark-color-ts-module); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-enum-member: var(--dark-color-ts-enum-member); + --color-ts-variable: var(--dark-color-ts-variable); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-constructor: var(--dark-color-ts-constructor); + --color-ts-property: var(--dark-color-ts-property); + --color-ts-method: var(--dark-color-ts-method); + --color-ts-call-signature: var(--dark-color-ts-call-signature); + --color-ts-index-signature: var(--dark-color-ts-index-signature); + --color-ts-constructor-signature: var(--dark-color-ts-constructor-signature); + --color-ts-parameter: var(--dark-color-ts-parameter); + --color-ts-type-parameter: var(--dark-color-ts-type-parameter); + --color-ts-accessor: var(--dark-color-ts-accessor); + --color-ts-get-signature: var(--dark-color-ts-get-signature); + --color-ts-set-signature: var(--dark-color-ts-set-signature); + --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); + + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); } *:focus-visible, .tsd-accordion-summary:focus-visible svg { - outline: 2px solid var(--color-focus-outline); + outline: 2px solid var(--color-focus-outline); } .always-visible, .always-visible .tsd-signatures { - display: inherit !important; + display: inherit !important; } h1, @@ -280,1169 +276,1169 @@ h3, h4, h5, h6 { - line-height: 1.2; + line-height: 1.2; } h1 { - font-size: 1.875rem; - margin: 0.67rem 0; + font-size: 1.875rem; + margin: 0.67rem 0; } h2 { - font-size: 1.5rem; - margin: 0.83rem 0; + font-size: 1.5rem; + margin: 0.83rem 0; } h3 { - font-size: 1.25rem; - margin: 1rem 0; + font-size: 1.25rem; + margin: 1rem 0; } h4 { - font-size: 1.05rem; - margin: 1.33rem 0; + font-size: 1.05rem; + margin: 1.33rem 0; } h5 { - font-size: 1rem; - margin: 1.5rem 0; + font-size: 1rem; + margin: 1.5rem 0; } h6 { - font-size: 0.875rem; - margin: 2.33rem 0; + font-size: 0.875rem; + margin: 2.33rem 0; } dl, menu, ol, ul { - margin: 1em 0; + margin: 1em 0; } dd { - margin: 0 0 0 40px; + margin: 0 0 0 40px; } .container { - max-width: 1700px; - padding: 0 2rem; + max-width: 1700px; + padding: 0 2rem; } /* Footer */ footer { - border-top: 1px solid var(--color-accent); - padding-top: 1rem; - padding-bottom: 1rem; - max-height: 3.5rem; + border-top: 1px solid var(--color-accent); + padding-top: 1rem; + padding-bottom: 1rem; + max-height: 3.5rem; } footer > p { - margin: 0 1em; + margin: 0 1em; } .container-main { - margin: 0 auto; - /* toolbar, footer, margin */ - min-height: calc(100vh - 41px - 56px - 4rem); + margin: 0 auto; + /* toolbar, footer, margin */ + min-height: calc(100vh - 41px - 56px - 4rem); } @keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } + from { + opacity: 0; + } + to { + opacity: 1; + } } @keyframes fade-out { - from { - opacity: 1; - visibility: visible; - } - to { - opacity: 0; - } + from { + opacity: 1; + visibility: visible; + } + to { + opacity: 0; + } } @keyframes fade-in-delayed { - 0% { - opacity: 0; - } - 33% { - opacity: 0; - } - 100% { - opacity: 1; - } + 0% { + opacity: 0; + } + 33% { + opacity: 0; + } + 100% { + opacity: 1; + } } @keyframes fade-out-delayed { - 0% { - opacity: 1; - visibility: visible; - } - 66% { - opacity: 0; - } - 100% { - opacity: 0; - } + 0% { + opacity: 1; + visibility: visible; + } + 66% { + opacity: 0; + } + 100% { + opacity: 0; + } } @keyframes pop-in-from-right { - from { - transform: translate(100%, 0); - } - to { - transform: translate(0, 0); - } + from { + transform: translate(100%, 0); + } + to { + transform: translate(0, 0); + } } @keyframes pop-out-to-right { - from { - transform: translate(0, 0); - visibility: visible; - } - to { - transform: translate(100%, 0); - } + from { + transform: translate(0, 0); + visibility: visible; + } + to { + transform: translate(100%, 0); + } } body { - background: var(--color-background); - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", - Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; - font-size: 16px; - color: var(--color-text); + background: var(--color-background); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + color: var(--color-text); } a { - color: var(--color-link); - text-decoration: none; + color: var(--color-link); + text-decoration: none; } a:hover { - text-decoration: underline; + text-decoration: underline; } a.external[target="_blank"] { - background-image: var(--external-icon); - background-position: top 3px right; - background-repeat: no-repeat; - padding-right: 13px; + background-image: var(--external-icon); + background-position: top 3px right; + background-repeat: no-repeat; + padding-right: 13px; } a.tsd-anchor-link { - color: var(--color-text); + color: var(--color-text); } code, pre { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - padding: 0.2em; - margin: 0; - font-size: 0.875rem; - border-radius: 0.8em; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + padding: 0.2em; + margin: 0; + font-size: 0.875rem; + border-radius: 0.8em; } pre { - position: relative; - white-space: pre; - white-space: pre-wrap; - word-wrap: break-word; - padding: 10px; - border: 1px solid var(--color-accent); + position: relative; + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; + padding: 10px; + border: 1px solid var(--color-accent); } pre code { - padding: 0; - font-size: 100%; + padding: 0; + font-size: 100%; } pre > button { - position: absolute; - top: 10px; - right: 10px; - opacity: 0; - transition: opacity 0.1s; - box-sizing: border-box; + position: absolute; + top: 10px; + right: 10px; + opacity: 0; + transition: opacity 0.1s; + box-sizing: border-box; } pre:hover > button, pre > button.visible { - opacity: 1; + opacity: 1; } blockquote { - margin: 1em 0; - padding-left: 1em; - border-left: 4px solid gray; + margin: 1em 0; + padding-left: 1em; + border-left: 4px solid gray; } .tsd-typography { - line-height: 1.333em; + line-height: 1.333em; } .tsd-typography ul { - list-style: square; - padding: 0 0 0 20px; - margin: 0; + list-style: square; + padding: 0 0 0 20px; + margin: 0; } .tsd-typography .tsd-index-panel h3, .tsd-index-panel .tsd-typography h3, .tsd-typography h4, .tsd-typography h5, .tsd-typography h6 { - font-size: 1em; + font-size: 1em; } .tsd-typography h5, .tsd-typography h6 { - font-weight: normal; + font-weight: normal; } .tsd-typography p, .tsd-typography ul, .tsd-typography ol { - margin: 1em 0; + margin: 1em 0; } .tsd-typography table { - border-collapse: collapse; - border: none; + border-collapse: collapse; + border: none; } .tsd-typography td, .tsd-typography th { - padding: 6px 13px; - border: 1px solid var(--color-accent); + padding: 6px 13px; + border: 1px solid var(--color-accent); } .tsd-typography thead, .tsd-typography tr:nth-child(even) { - background-color: var(--color-background-secondary); + background-color: var(--color-background-secondary); } .tsd-breadcrumb { - margin: 0; - padding: 0; - color: var(--color-text-aside); + margin: 0; + padding: 0; + color: var(--color-text-aside); } .tsd-breadcrumb a { - color: var(--color-text-aside); - text-decoration: none; + color: var(--color-text-aside); + text-decoration: none; } .tsd-breadcrumb a:hover { - text-decoration: underline; + text-decoration: underline; } .tsd-breadcrumb li { - display: inline; + display: inline; } .tsd-breadcrumb li:after { - content: " / "; + content: " / "; } .tsd-comment-tags { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } dl.tsd-comment-tag-group { - display: flex; - align-items: center; - overflow: hidden; - margin: 0.5em 0; + display: flex; + align-items: center; + overflow: hidden; + margin: 0.5em 0; } dl.tsd-comment-tag-group dt { - display: flex; - margin-right: 0.5em; - font-size: 0.875em; - font-weight: normal; + display: flex; + margin-right: 0.5em; + font-size: 0.875em; + font-weight: normal; } dl.tsd-comment-tag-group dd { - margin: 0; + margin: 0; } code.tsd-tag { - padding: 0.25em 0.4em; - border: 0.1em solid var(--color-accent); - margin-right: 0.25em; - font-size: 70%; + padding: 0.25em 0.4em; + border: 0.1em solid var(--color-accent); + margin-right: 0.25em; + font-size: 70%; } h1 code.tsd-tag:first-of-type { - margin-left: 0.25em; + margin-left: 0.25em; } dl.tsd-comment-tag-group dd:before, dl.tsd-comment-tag-group dd:after { - content: " "; + content: " "; } dl.tsd-comment-tag-group dd pre, dl.tsd-comment-tag-group dd:after { - clear: both; + clear: both; } dl.tsd-comment-tag-group p { - margin: 0; + margin: 0; } .tsd-panel.tsd-comment .lead { - font-size: 1.1em; - line-height: 1.333em; - margin-bottom: 2em; + font-size: 1.1em; + line-height: 1.333em; + margin-bottom: 2em; } .tsd-panel.tsd-comment .lead:last-child { - margin-bottom: 0; + margin-bottom: 0; } .tsd-filter-visibility h4 { - font-size: 1rem; - padding-top: 0.75rem; - padding-bottom: 0.5rem; - margin: 0; + font-size: 1rem; + padding-top: 0.75rem; + padding-bottom: 0.5rem; + margin: 0; } .tsd-filter-item:not(:last-child) { - margin-bottom: 0.5rem; + margin-bottom: 0.5rem; } .tsd-filter-input { - display: flex; - width: -moz-fit-content; - width: fit-content; - align-items: center; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; + display: flex; + width: -moz-fit-content; + width: fit-content; + align-items: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; } .tsd-filter-input input[type="checkbox"] { - cursor: pointer; - position: absolute; - width: 1.5em; - height: 1.5em; - opacity: 0; + cursor: pointer; + position: absolute; + width: 1.5em; + height: 1.5em; + opacity: 0; } .tsd-filter-input input[type="checkbox"]:disabled { - pointer-events: none; + pointer-events: none; } .tsd-filter-input svg { - cursor: pointer; - width: 1.5em; - height: 1.5em; - margin-right: 0.5em; - border-radius: 0.33em; - /* Leaving this at full opacity breaks event listeners on Firefox. + cursor: pointer; + width: 1.5em; + height: 1.5em; + margin-right: 0.5em; + border-radius: 0.33em; + /* Leaving this at full opacity breaks event listeners on Firefox. Don't remove unless you know what you're doing. */ - opacity: 0.99; + opacity: 0.99; } .tsd-filter-input input[type="checkbox"]:focus-visible + svg { - outline: 2px solid var(--color-focus-outline); + outline: 2px solid var(--color-focus-outline); } .tsd-checkbox-background { - fill: var(--color-accent); + fill: var(--color-accent); } input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { - stroke: var(--color-text); + stroke: var(--color-text); } .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-background { - fill: var(--color-background); - stroke: var(--color-accent); - stroke-width: 0.25rem; + fill: var(--color-background); + stroke: var(--color-accent); + stroke-width: 0.25rem; } .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-checkmark { - stroke: var(--color-accent); + stroke: var(--color-accent); } .settings-label { - font-weight: bold; - text-transform: uppercase; - display: inline-block; + font-weight: bold; + text-transform: uppercase; + display: inline-block; } .tsd-filter-visibility .settings-label { - margin: 0.75rem 0 0.5rem 0; + margin: 0.75rem 0 0.5rem 0; } .tsd-theme-toggle .settings-label { - margin: 0.75rem 0.75rem 0 0; + margin: 0.75rem 0.75rem 0 0; } .tsd-hierarchy { - list-style: square; - margin: 0; + list-style: square; + margin: 0; } .tsd-hierarchy .target { - font-weight: bold; + font-weight: bold; } .tsd-full-hierarchy:not(:last-child) { - margin-bottom: 1em; - padding-bottom: 1em; - border-bottom: 1px solid var(--color-accent); + margin-bottom: 1em; + padding-bottom: 1em; + border-bottom: 1px solid var(--color-accent); } .tsd-full-hierarchy, .tsd-full-hierarchy ul { - list-style: none; - margin: 0; - padding: 0; + list-style: none; + margin: 0; + padding: 0; } .tsd-full-hierarchy ul { - padding-left: 1.5rem; + padding-left: 1.5rem; } .tsd-full-hierarchy a { - padding: 0.25rem 0 !important; - font-size: 1rem; - display: inline-flex; - align-items: center; - color: var(--color-text); + padding: 0.25rem 0 !important; + font-size: 1rem; + display: inline-flex; + align-items: center; + color: var(--color-text); } .tsd-panel-group.tsd-index-group { - margin-bottom: 0; + margin-bottom: 0; } .tsd-index-panel .tsd-index-list { - list-style: none; - line-height: 1.333em; - margin: 0; - padding: 0.25rem 0 0 0; - overflow: hidden; - display: grid; - grid-template-columns: repeat(3, 1fr); - column-gap: 1rem; - grid-template-rows: auto; + list-style: none; + line-height: 1.333em; + margin: 0; + padding: 0.25rem 0 0 0; + overflow: hidden; + display: grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 1rem; + grid-template-rows: auto; } @media (max-width: 1024px) { - .tsd-index-panel .tsd-index-list { - grid-template-columns: repeat(2, 1fr); - } + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(2, 1fr); + } } @media (max-width: 768px) { - .tsd-index-panel .tsd-index-list { - grid-template-columns: repeat(1, 1fr); - } + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(1, 1fr); + } } .tsd-index-panel .tsd-index-list li { - -webkit-page-break-inside: avoid; - -moz-page-break-inside: avoid; - -ms-page-break-inside: avoid; - -o-page-break-inside: avoid; - page-break-inside: avoid; + -webkit-page-break-inside: avoid; + -moz-page-break-inside: avoid; + -ms-page-break-inside: avoid; + -o-page-break-inside: avoid; + page-break-inside: avoid; } .tsd-flag { - display: inline-block; - padding: 0.25em 0.4em; - border-radius: 4px; - color: var(--color-comment-tag-text); - background-color: var(--color-comment-tag); - text-indent: 0; - font-size: 75%; - line-height: 1; - font-weight: normal; + display: inline-block; + padding: 0.25em 0.4em; + border-radius: 4px; + color: var(--color-comment-tag-text); + background-color: var(--color-comment-tag); + text-indent: 0; + font-size: 75%; + line-height: 1; + font-weight: normal; } .tsd-anchor { - position: relative; - top: -100px; + position: relative; + top: -100px; } .tsd-member { - position: relative; + position: relative; } .tsd-member .tsd-anchor + h3 { - display: flex; - align-items: center; - margin-top: 0; - margin-bottom: 0; - border-bottom: none; + display: flex; + align-items: center; + margin-top: 0; + margin-bottom: 0; + border-bottom: none; } .tsd-navigation.settings { - margin: 1rem 0; + margin: 1rem 0; } .tsd-navigation > a, .tsd-navigation .tsd-accordion-summary { - width: calc(100% - 0.25rem); - display: flex; - align-items: center; + width: calc(100% - 0.25rem); + display: flex; + align-items: center; } .tsd-navigation a, .tsd-navigation summary > span, .tsd-page-navigation a { - display: flex; - width: calc(100% - 0.25rem); - align-items: center; - padding: 0.25rem; - color: var(--color-text); - text-decoration: none; - box-sizing: border-box; + display: flex; + width: calc(100% - 0.25rem); + align-items: center; + padding: 0.25rem; + color: var(--color-text); + text-decoration: none; + box-sizing: border-box; } .tsd-navigation a.current, .tsd-page-navigation a.current { - background: var(--color-active-menu-item); + background: var(--color-active-menu-item); } .tsd-navigation a:hover, .tsd-page-navigation a:hover { - text-decoration: underline; + text-decoration: underline; } .tsd-navigation ul, .tsd-page-navigation ul { - margin-top: 0; - margin-bottom: 0; - padding: 0; - list-style: none; + margin-top: 0; + margin-bottom: 0; + padding: 0; + list-style: none; } .tsd-navigation li, .tsd-page-navigation li { - padding: 0; - max-width: 100%; + padding: 0; + max-width: 100%; } .tsd-navigation .tsd-nav-link { - display: none; + display: none; } .tsd-nested-navigation { - margin-left: 3rem; + margin-left: 3rem; } .tsd-nested-navigation > li > details { - margin-left: -1.5rem; + margin-left: -1.5rem; } .tsd-small-nested-navigation { - margin-left: 1.5rem; + margin-left: 1.5rem; } .tsd-small-nested-navigation > li > details { - margin-left: -1.5rem; + margin-left: -1.5rem; } .tsd-page-navigation-section { - margin-left: 10px; + margin-left: 10px; } .tsd-page-navigation-section > summary { - padding: 0.25rem; + padding: 0.25rem; } .tsd-page-navigation-section > div { - margin-left: 20px; + margin-left: 20px; } .tsd-page-navigation ul { - padding-left: 1.75rem; + padding-left: 1.75rem; } #tsd-sidebar-links a { - margin-top: 0; - margin-bottom: 0.5rem; - line-height: 1.25rem; + margin-top: 0; + margin-bottom: 0.5rem; + line-height: 1.25rem; } #tsd-sidebar-links a:last-of-type { - margin-bottom: 0; + margin-bottom: 0; } a.tsd-index-link { - padding: 0.25rem 0 !important; - font-size: 1rem; - line-height: 1.25rem; - display: inline-flex; - align-items: center; - color: var(--color-text); + padding: 0.25rem 0 !important; + font-size: 1rem; + line-height: 1.25rem; + display: inline-flex; + align-items: center; + color: var(--color-text); } .tsd-accordion-summary { - list-style-type: none; /* hide marker on non-safari */ - outline: none; /* broken on safari, so just hide it */ + list-style-type: none; /* hide marker on non-safari */ + outline: none; /* broken on safari, so just hide it */ } .tsd-accordion-summary::-webkit-details-marker { - display: none; /* hide marker on safari */ + display: none; /* hide marker on safari */ } .tsd-accordion-summary, .tsd-accordion-summary a { - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; - cursor: pointer; + cursor: pointer; } .tsd-accordion-summary a { - width: calc(100% - 1.5rem); + width: calc(100% - 1.5rem); } .tsd-accordion-summary > * { - margin-top: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; } .tsd-accordion .tsd-accordion-summary > svg { - margin-left: 0.25rem; - vertical-align: text-top; + margin-left: 0.25rem; + vertical-align: text-top; } .tsd-index-content > :not(:first-child) { - margin-top: 0.75rem; + margin-top: 0.75rem; } .tsd-index-heading { - margin-top: 1.5rem; - margin-bottom: 0.75rem; + margin-top: 1.5rem; + margin-bottom: 0.75rem; } .tsd-kind-icon { - margin-right: 0.5rem; - width: 1.25rem; - height: 1.25rem; - min-width: 1.25rem; - min-height: 1.25rem; + margin-right: 0.5rem; + width: 1.25rem; + height: 1.25rem; + min-width: 1.25rem; + min-height: 1.25rem; } .tsd-kind-icon path { - transform-origin: center; - transform: scale(1.1); + transform-origin: center; + transform: scale(1.1); } .tsd-signature > .tsd-kind-icon { - margin-right: 0.8rem; + margin-right: 0.8rem; } .tsd-panel { - margin-bottom: 2.5rem; + margin-bottom: 2.5rem; } .tsd-panel.tsd-member { - margin-bottom: 4rem; + margin-bottom: 4rem; } .tsd-panel:empty { - display: none; + display: none; } .tsd-panel > h1, .tsd-panel > h2, .tsd-panel > h3 { - margin: 1.5rem -1.5rem 0.75rem -1.5rem; - padding: 0 1.5rem 0.75rem 1.5rem; + margin: 1.5rem -1.5rem 0.75rem -1.5rem; + padding: 0 1.5rem 0.75rem 1.5rem; } .tsd-panel > h1.tsd-before-signature, .tsd-panel > h2.tsd-before-signature, .tsd-panel > h3.tsd-before-signature { - margin-bottom: 0; - border-bottom: none; + margin-bottom: 0; + border-bottom: none; } .tsd-panel-group { - margin: 2rem 0; + margin: 2rem 0; } .tsd-panel-group.tsd-index-group { - margin: 2rem 0; + margin: 2rem 0; } .tsd-panel-group.tsd-index-group details { - margin: 2rem 0; + margin: 2rem 0; } .tsd-panel-group > .tsd-accordion-summary { - margin-bottom: 1rem; + margin-bottom: 1rem; } #tsd-search { - transition: background-color 0.2s; + transition: background-color 0.2s; } #tsd-search .title { - position: relative; - z-index: 2; + position: relative; + z-index: 2; } #tsd-search .field { - position: absolute; - left: 0; - top: 0; - right: 2.5rem; - height: 100%; + position: absolute; + left: 0; + top: 0; + right: 2.5rem; + height: 100%; } #tsd-search .field input { - box-sizing: border-box; - position: relative; - top: -50px; - z-index: 1; - width: 100%; - padding: 0 10px; - opacity: 0; - outline: 0; - border: 0; - background: transparent; - color: var(--color-text); + box-sizing: border-box; + position: relative; + top: -50px; + z-index: 1; + width: 100%; + padding: 0 10px; + opacity: 0; + outline: 0; + border: 0; + background: transparent; + color: var(--color-text); } #tsd-search .field label { - position: absolute; - overflow: hidden; - right: -40px; + position: absolute; + overflow: hidden; + right: -40px; } #tsd-search .field input, #tsd-search .title, #tsd-toolbar-links a { - transition: opacity 0.2s; + transition: opacity 0.2s; } #tsd-search .results { - position: absolute; - visibility: hidden; - top: 40px; - width: 100%; - margin: 0; - padding: 0; - list-style: none; - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); + position: absolute; + visibility: hidden; + top: 40px; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); } #tsd-search .results li { - background-color: var(--color-background); - line-height: initial; - padding: 4px; + background-color: var(--color-background); + line-height: initial; + padding: 4px; } #tsd-search .results li:nth-child(even) { - background-color: var(--color-background-secondary); + background-color: var(--color-background-secondary); } #tsd-search .results li.state { - display: none; + display: none; } #tsd-search .results li.current:not(.no-results), #tsd-search .results li:hover:not(.no-results) { - background-color: var(--color-accent); + background-color: var(--color-accent); } #tsd-search .results a { - display: flex; - align-items: center; - padding: 0.25rem; - box-sizing: border-box; + display: flex; + align-items: center; + padding: 0.25rem; + box-sizing: border-box; } #tsd-search .results a:before { - top: 10px; + top: 10px; } #tsd-search .results span.parent { - color: var(--color-text-aside); - font-weight: normal; + color: var(--color-text-aside); + font-weight: normal; } #tsd-search.has-focus { - background-color: var(--color-accent); + background-color: var(--color-accent); } #tsd-search.has-focus .field input { - top: 0; - opacity: 1; + top: 0; + opacity: 1; } #tsd-search.has-focus .title, #tsd-search.has-focus #tsd-toolbar-links a { - z-index: 0; - opacity: 0; + z-index: 0; + opacity: 0; } #tsd-search.has-focus .results { - visibility: visible; + visibility: visible; } #tsd-search.loading .results li.state.loading { - display: block; + display: block; } #tsd-search.failure .results li.state.failure { - display: block; + display: block; } #tsd-toolbar-links { - position: absolute; - top: 0; - right: 2rem; - height: 100%; - display: flex; - align-items: center; - justify-content: flex-end; + position: absolute; + top: 0; + right: 2rem; + height: 100%; + display: flex; + align-items: center; + justify-content: flex-end; } #tsd-toolbar-links a { - margin-left: 1.5rem; + margin-left: 1.5rem; } #tsd-toolbar-links a:hover { - text-decoration: underline; + text-decoration: underline; } .tsd-signature { - margin: 0 0 1rem 0; - padding: 1rem 0.5rem; - border: 1px solid var(--color-accent); - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - font-size: 14px; - overflow-x: auto; + margin: 0 0 1rem 0; + padding: 1rem 0.5rem; + border: 1px solid var(--color-accent); + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + font-size: 14px; + overflow-x: auto; } .tsd-signature-keyword { - color: var(--color-ts-keyword); - font-weight: normal; + color: var(--color-ts-keyword); + font-weight: normal; } .tsd-signature-symbol { - color: var(--color-text-aside); - font-weight: normal; + color: var(--color-text-aside); + font-weight: normal; } .tsd-signature-type { - font-style: italic; - font-weight: normal; + font-style: italic; + font-weight: normal; } .tsd-signatures { - padding: 0; - margin: 0 0 1em 0; - list-style-type: none; + padding: 0; + margin: 0 0 1em 0; + list-style-type: none; } .tsd-signatures .tsd-signature { - margin: 0; - border-color: var(--color-accent); - border-width: 1px 0; - transition: background-color 0.1s; + margin: 0; + border-color: var(--color-accent); + border-width: 1px 0; + transition: background-color 0.1s; } .tsd-signatures .tsd-index-signature:not(:last-child) { - margin-bottom: 1em; + margin-bottom: 1em; } .tsd-signatures .tsd-index-signature .tsd-signature { - border-width: 1px; + border-width: 1px; } .tsd-description .tsd-signatures .tsd-signature { - border-width: 1px; + border-width: 1px; } ul.tsd-parameter-list, ul.tsd-type-parameter-list { - list-style: square; - margin: 0; - padding-left: 20px; + list-style: square; + margin: 0; + padding-left: 20px; } ul.tsd-parameter-list > li.tsd-parameter-signature, ul.tsd-type-parameter-list > li.tsd-parameter-signature { - list-style: none; - margin-left: -20px; + list-style: none; + margin-left: -20px; } ul.tsd-parameter-list h5, ul.tsd-type-parameter-list h5 { - font-size: 16px; - margin: 1em 0 0.5em 0; + font-size: 16px; + margin: 1em 0 0.5em 0; } .tsd-sources { - margin-top: 1rem; - font-size: 0.875em; + margin-top: 1rem; + font-size: 0.875em; } .tsd-sources a { - color: var(--color-text-aside); - text-decoration: underline; + color: var(--color-text-aside); + text-decoration: underline; } .tsd-sources ul { - list-style: none; - padding: 0; + list-style: none; + padding: 0; } .tsd-page-toolbar { - position: sticky; - z-index: 1; - top: 0; - left: 0; - width: 100%; - color: var(--color-text); - background: var(--color-background-secondary); - border-bottom: 1px var(--color-accent) solid; - transition: transform 0.3s ease-in-out; + position: sticky; + z-index: 1; + top: 0; + left: 0; + width: 100%; + color: var(--color-text); + background: var(--color-background-secondary); + border-bottom: 1px var(--color-accent) solid; + transition: transform 0.3s ease-in-out; } .tsd-page-toolbar a { - color: var(--color-text); - text-decoration: none; + color: var(--color-text); + text-decoration: none; } .tsd-page-toolbar a.title { - font-weight: bold; + font-weight: bold; } .tsd-page-toolbar a.title:hover { - text-decoration: underline; + text-decoration: underline; } .tsd-page-toolbar .tsd-toolbar-contents { - display: flex; - justify-content: space-between; - height: 2.5rem; - margin: 0 auto; + display: flex; + justify-content: space-between; + height: 2.5rem; + margin: 0 auto; } .tsd-page-toolbar .table-cell { - position: relative; - white-space: nowrap; - line-height: 40px; + position: relative; + white-space: nowrap; + line-height: 40px; } .tsd-page-toolbar .table-cell:first-child { - width: 100%; + width: 100%; } .tsd-page-toolbar .tsd-toolbar-icon { - box-sizing: border-box; - line-height: 0; - padding: 12px 0; + box-sizing: border-box; + line-height: 0; + padding: 12px 0; } .tsd-widget { - display: inline-block; - overflow: hidden; - opacity: 0.8; - height: 40px; - transition: - opacity 0.1s, - background-color 0.2s; - vertical-align: bottom; - cursor: pointer; + display: inline-block; + overflow: hidden; + opacity: 0.8; + height: 40px; + transition: + opacity 0.1s, + background-color 0.2s; + vertical-align: bottom; + cursor: pointer; } .tsd-widget:hover { - opacity: 0.9; + opacity: 0.9; } .tsd-widget.active { - opacity: 1; - background-color: var(--color-accent); + opacity: 1; + background-color: var(--color-accent); } .tsd-widget.no-caption { - width: 40px; + width: 40px; } .tsd-widget.no-caption:before { - margin: 0; + margin: 0; } .tsd-widget.options, .tsd-widget.menu { - display: none; + display: none; } input[type="checkbox"] + .tsd-widget:before { - background-position: -120px 0; + background-position: -120px 0; } input[type="checkbox"]:checked + .tsd-widget:before { - background-position: -160px 0; + background-position: -160px 0; } img { - max-width: 100%; + max-width: 100%; } .tsd-anchor-icon { - display: inline-flex; - align-items: center; - margin-left: 0.5rem; - vertical-align: middle; - color: var(--color-text); + display: inline-flex; + align-items: center; + margin-left: 0.5rem; + vertical-align: middle; + color: var(--color-text); } .tsd-anchor-icon svg { - width: 1em; - height: 1em; - visibility: hidden; + width: 1em; + height: 1em; + visibility: hidden; } .tsd-anchor-link:hover > .tsd-anchor-icon svg { - visibility: visible; + visibility: visible; } .deprecated { - text-decoration: line-through !important; + text-decoration: line-through !important; } .warning { - padding: 1rem; - color: var(--color-warning-text); - background: var(--color-background-warning); + padding: 1rem; + color: var(--color-warning-text); + background: var(--color-background-warning); } .tsd-kind-project { - color: var(--color-ts-project); + color: var(--color-ts-project); } .tsd-kind-module { - color: var(--color-ts-module); + color: var(--color-ts-module); } .tsd-kind-namespace { - color: var(--color-ts-namespace); + color: var(--color-ts-namespace); } .tsd-kind-enum { - color: var(--color-ts-enum); + color: var(--color-ts-enum); } .tsd-kind-enum-member { - color: var(--color-ts-enum-member); + color: var(--color-ts-enum-member); } .tsd-kind-variable { - color: var(--color-ts-variable); + color: var(--color-ts-variable); } .tsd-kind-function { - color: var(--color-ts-function); + color: var(--color-ts-function); } .tsd-kind-class { - color: var(--color-ts-class); + color: var(--color-ts-class); } .tsd-kind-interface { - color: var(--color-ts-interface); + color: var(--color-ts-interface); } .tsd-kind-constructor { - color: var(--color-ts-constructor); + color: var(--color-ts-constructor); } .tsd-kind-property { - color: var(--color-ts-property); + color: var(--color-ts-property); } .tsd-kind-method { - color: var(--color-ts-method); + color: var(--color-ts-method); } .tsd-kind-call-signature { - color: var(--color-ts-call-signature); + color: var(--color-ts-call-signature); } .tsd-kind-index-signature { - color: var(--color-ts-index-signature); + color: var(--color-ts-index-signature); } .tsd-kind-constructor-signature { - color: var(--color-ts-constructor-signature); + color: var(--color-ts-constructor-signature); } .tsd-kind-parameter { - color: var(--color-ts-parameter); + color: var(--color-ts-parameter); } .tsd-kind-type-literal { - color: var(--color-ts-type-literal); + color: var(--color-ts-type-literal); } .tsd-kind-type-parameter { - color: var(--color-ts-type-parameter); + color: var(--color-ts-type-parameter); } .tsd-kind-accessor { - color: var(--color-ts-accessor); + color: var(--color-ts-accessor); } .tsd-kind-get-signature { - color: var(--color-ts-get-signature); + color: var(--color-ts-get-signature); } .tsd-kind-set-signature { - color: var(--color-ts-set-signature); + color: var(--color-ts-set-signature); } .tsd-kind-type-alias { - color: var(--color-ts-type-alias); + color: var(--color-ts-type-alias); } /* if we have a kind icon, don't color the text by kind */ .tsd-kind-icon ~ span { - color: var(--color-text); + color: var(--color-text); } * { - scrollbar-width: thin; - scrollbar-color: var(--color-accent) var(--color-icon-background); + scrollbar-width: thin; + scrollbar-color: var(--color-accent) var(--color-icon-background); } *::-webkit-scrollbar { - width: 0.75rem; + width: 0.75rem; } *::-webkit-scrollbar-track { - background: var(--color-icon-background); + background: var(--color-icon-background); } *::-webkit-scrollbar-thumb { - background-color: var(--color-accent); - border-radius: 999rem; - border: 0.25rem solid var(--color-icon-background); + background-color: var(--color-accent); + border-radius: 999rem; + border: 0.25rem solid var(--color-icon-background); } /* mobile */ @media (max-width: 769px) { - .tsd-widget.options, - .tsd-widget.menu { - display: inline-block; - } - - .container-main { - display: flex; - } - html .col-content { - float: none; - max-width: 100%; - width: 100%; - } - html .col-sidebar { - position: fixed !important; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - z-index: 1024; - top: 0 !important; - bottom: 0 !important; - left: auto !important; - right: 0 !important; - padding: 1.5rem 1.5rem 0 0; - width: 75vw; - visibility: hidden; - background-color: var(--color-background); - transform: translate(100%, 0); - } - html .col-sidebar > *:last-child { - padding-bottom: 20px; - } - html .overlay { - content: ""; - display: block; - position: fixed; - z-index: 1023; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.75); - visibility: hidden; - } - - .to-has-menu .overlay { - animation: fade-in 0.4s; - } - - .to-has-menu .col-sidebar { - animation: pop-in-from-right 0.4s; - } - - .from-has-menu .overlay { - animation: fade-out 0.4s; - } - - .from-has-menu .col-sidebar { - animation: pop-out-to-right 0.4s; - } - - .has-menu body { - overflow: hidden; - } - .has-menu .overlay { - visibility: visible; - } - .has-menu .col-sidebar { - visibility: visible; - transform: translate(0, 0); - display: flex; - flex-direction: column; - gap: 1.5rem; - max-height: 100vh; - padding: 1rem 2rem; - } - .has-menu .tsd-navigation { - max-height: 100%; - } - #tsd-toolbar-links { - display: none; - } - .tsd-navigation .tsd-nav-link { - display: flex; - } + .tsd-widget.options, + .tsd-widget.menu { + display: inline-block; + } + + .container-main { + display: flex; + } + html .col-content { + float: none; + max-width: 100%; + width: 100%; + } + html .col-sidebar { + position: fixed !important; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + z-index: 1024; + top: 0 !important; + bottom: 0 !important; + left: auto !important; + right: 0 !important; + padding: 1.5rem 1.5rem 0 0; + width: 75vw; + visibility: hidden; + background-color: var(--color-background); + transform: translate(100%, 0); + } + html .col-sidebar > *:last-child { + padding-bottom: 20px; + } + html .overlay { + content: ""; + display: block; + position: fixed; + z-index: 1023; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.75); + visibility: hidden; + } + + .to-has-menu .overlay { + animation: fade-in 0.4s; + } + + .to-has-menu .col-sidebar { + animation: pop-in-from-right 0.4s; + } + + .from-has-menu .overlay { + animation: fade-out 0.4s; + } + + .from-has-menu .col-sidebar { + animation: pop-out-to-right 0.4s; + } + + .has-menu body { + overflow: hidden; + } + .has-menu .overlay { + visibility: visible; + } + .has-menu .col-sidebar { + visibility: visible; + transform: translate(0, 0); + display: flex; + flex-direction: column; + gap: 1.5rem; + max-height: 100vh; + padding: 1rem 2rem; + } + .has-menu .tsd-navigation { + max-height: 100%; + } + #tsd-toolbar-links { + display: none; + } + .tsd-navigation .tsd-nav-link { + display: flex; + } } /* one sidebar */ @media (min-width: 770px) { - .container-main { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); - grid-template-areas: "sidebar content"; - margin: 2rem auto; - } - - .col-sidebar { - grid-area: sidebar; - } - .col-content { - grid-area: content; - padding: 0 1rem; - } + .container-main { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); + grid-template-areas: "sidebar content"; + margin: 2rem auto; + } + + .col-sidebar { + grid-area: sidebar; + } + .col-content { + grid-area: content; + padding: 0 1rem; + } } @media (min-width: 770px) and (max-width: 1399px) { - .col-sidebar { - max-height: calc(100vh - 2rem - 42px); - overflow: auto; - position: sticky; - top: 42px; - padding-top: 1rem; - } - .site-menu { - margin-top: 1rem; - } + .col-sidebar { + max-height: calc(100vh - 2rem - 42px); + overflow: auto; + position: sticky; + top: 42px; + padding-top: 1rem; + } + .site-menu { + margin-top: 1rem; + } } /* two sidebars */ @media (min-width: 1200px) { - .container-main { - grid-template-columns: minmax(0, 1fr) minmax(0, 2.5fr) minmax(0, 20rem); - grid-template-areas: "sidebar content toc"; - } - - .col-sidebar { - display: contents; - } - - .page-menu { - grid-area: toc; - padding-left: 1rem; - } - .site-menu { - grid-area: sidebar; - } - - .site-menu { - margin-top: 1rem 0; - } - - .page-menu, - .site-menu { - max-height: calc(100vh - 2rem - 42px); - overflow: auto; - position: sticky; - top: 42px; - } + .container-main { + grid-template-columns: minmax(0, 1fr) minmax(0, 2.5fr) minmax(0, 20rem); + grid-template-areas: "sidebar content toc"; + } + + .col-sidebar { + display: contents; + } + + .page-menu { + grid-area: toc; + padding-left: 1rem; + } + .site-menu { + grid-area: sidebar; + } + + .site-menu { + margin-top: 1rem 0; + } + + .page-menu, + .site-menu { + max-height: calc(100vh - 2rem - 42px); + overflow: auto; + position: sticky; + top: 42px; + } } diff --git a/docs/classes/Box.html b/docs/classes/Box.html index 71d7c825..6112caf3 100644 --- a/docs/classes/Box.html +++ b/docs/classes/Box.html @@ -1,121 +1,4587 @@ -Box | Detect-Collisions

collider - box

-

Hierarchy (view full)

Constructors

Properties

_group: number

group for collision filtering

-
_height: number

inner height

-
_width: number

inner width

-
angle: number

body angle in radians use deg2rad to convert

-
bbox: BBox

bounding box cache, without padding

-
calcPoints: SATVector[]
centered: boolean = false

is body centered

-
convexPolygons: SATPolygon[]

optimization for convex polygons

-
dirty: boolean = false

was the polygon modified and needs update in the next checkCollision

-
edges: SATVector[]
isConvex: true = true

boxes are convex

-
isStatic: boolean

static bodies don't move but they collide

-
isTrigger: boolean

trigger bodies move but are like ghosts

-
maxX: number

maximum x bound of body

-
maxY: number

maximum y bound of body

-
minX: number

minimum x bound of body

-
minY: number

minimum y bound of body

-
normals: SATVector[]
offset: SATVector

each body may have offset from center

-
padding: number

bodies are not reinserted during update if their bbox didnt move outside bbox + padding

-
points: SATVector[]
pointsBackup: Vector[]

backup of points used for scaling

-
scaleVector: Vector = ...

scale Vector of body

-
system?: System<Body>

reference to collision system

-
type: Box | Point = BodyType.Box

type of body

-
typeGroup: Box | Point = BodyGroup.Box

faster than type

-

Accessors

  • get group(): number
  • group for collision filtering

    -

    Returns number

  • set group(group): void
  • group for collision filtering

    -

    Parameters

    • group: number

    Returns void

  • get height(): number
  • get box height

    -

    Returns number

  • set height(height): void
  • set box height, update points

    -

    Parameters

    • height: number

    Returns void

  • get isCentered(): boolean
  • is polygon centered?

    -

    Returns boolean

  • set isCentered(isCentered): void
  • flag to set is polygon centered

    -

    Parameters

    • isCentered: boolean

    Returns void

  • get scale(): number
  • allow approx getting of scale

    -

    Returns number

  • set scale(scale): void
  • allow easier setting of scale

    -

    Parameters

    • scale: number

    Returns void

  • get scaleX(): number
  • allow exact getting of scale x - use setScale(x, y) to set

    -

    Returns number

  • get scaleY(): number
  • allow exact getting of scale y - use setScale(x, y) to set

    -

    Returns number

Methods

  • inner function for after position change update aabb in system and convex inner polygons

    -

    Parameters

    • updateNow: boolean = ...

    Returns void

+ + + + + + Box | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class Box

+
+
+

collider - box

+
+
+
+

Hierarchy (view full)

+ +
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + + +
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ _group: + number +
+
+

group for collision filtering

+
+
+ +
+
+ + +
+ _height: + number +
+

inner height

+
+ +
+
+ + +
+ _width: + number +
+

inner width

+
+ +
+
+ + +
+ angle: + number +
+
+

body angle in radians use deg2rad to convert

+
+
+ +
+
+ + +
+ bbox: + BBox +
+
+

bounding box cache, without padding

+
+
+ +
+
+ + +
+ calcPoints: + SATVector[] +
+ +
+
+ + +
+ centered: + boolean = false +
+
+

is body centered

+
+
+ +
+
+ + +
+ convexPolygons: + SATPolygon[] +
+
+

optimization for convex polygons

+
+
+ +
+
+ + +
+ dirty: + boolean = false +
+
+

+ was the polygon modified and needs update in the next + checkCollision +

+
+
+ +
+
+ + +
+ edges: + SATVector[] +
+ +
+
+ + +
+ isConvex: + true = true +
+
+

boxes are convex

+
+
+ +
+
+ + +
+ isStatic: + boolean +
+
+

static bodies don't move but they collide

+
+
+ +
+
+ + +
+ isTrigger: + boolean +
+
+

trigger bodies move but are like ghosts

+
+
+ +
+
+ + +
+ maxX: + number +
+
+

maximum x bound of body

+
+
+ +
+
+ + +
+ maxY: + number +
+
+

maximum y bound of body

+
+
+ +
+
+ + +
+ minX: + number +
+
+

minimum x bound of body

+
+
+ +
+
+ + +
+ minY: + number +
+
+

minimum y bound of body

+
+
+ +
+
+ + +
+ normals: + SATVector[] +
+ +
+
+ + +
+ offset: + SATVector +
+
+

each body may have offset from center

+
+
+ +
+
+ + +
+ padding: + number +
+
+

+ bodies are not reinserted during update if their bbox didnt + move outside bbox + padding +

+
+
+ +
+
+ + +
+ points: + SATVector[] +
+ +
+
+ + +
+ pointsBackup: + Vector[] +
+
+

backup of points used for scaling

+
+
+ +
+
+ + +
+ pos: + SATVector +
+ +
+
+ + +
+ scaleVector: + Vector = ... +
+
+

scale Vector of body

+
+
+ +
+
+ + +
+ system?: + System<Body> +
+
+

reference to collision system

+
+
+ +
+
+ + +
+ type: + Box | Point = BodyType.Box +
+

type of body

+
+ +
+
+ + +
+ typeGroup: + Box | Point = BodyGroup.Box +
+
+

faster than type

+
+
+ +
+
+
+
+ +

+ + + + Accessors +

+
+
+
+ + +
    +
  • + get group(): number +
  • +
  • +
    +

    group for collision filtering

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set group(group): void +
  • +
  • +
    +

    group for collision filtering

    +
    +
    +

    Parameters

    +
      +
    • + group: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get height(): number +
  • +
  • +
    +

    get box height

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set height(height): void +
  • +
  • +
    +

    set box height, update points

    +
    +
    +

    Parameters

    +
      +
    • + height: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get isCentered(): boolean +
  • +
  • +
    +

    is polygon centered?

    +
    +

    + Returns boolean +

    +
    + +
  • +
  • + set isCentered(isCentered): void +
  • +
  • +
    +

    flag to set is polygon centered

    +
    +
    +

    Parameters

    +
      +
    • + isCentered: + boolean +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scale(): number +
  • +
  • +
    +

    allow approx getting of scale

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set scale(scale): void +
  • +
  • +
    +

    allow easier setting of scale

    +
    +
    +

    Parameters

    +
      +
    • + scale: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleX(): number +
  • +
  • +
    +

    + allow exact getting of scale x - use setScale(x, y) to set +

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleY(): number +
  • +
  • +
    +

    + allow exact getting of scale y - use setScale(x, y) to set +

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get width(): number +
  • +
  • +
    +

    get box width

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set width(width): void +
  • +
  • +
    +

    set box width, update points

    +
    +
    +

    Parameters

    +
      +
    • + width: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get x(): number +
  • +
  • +

    + Returns number +

    + +
  • +
  • + set x(x): void +
  • +
  • +
    +

    updating this.pos.x by this.x = x updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get y(): number +
  • +
  • +

    + Returns number +

    + +
  • +
  • + set y(y): void +
  • +
  • +
    +

    updating this.pos.y by this.y = y updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + y: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + + +
+
+ + +
    + +
  • +
    +

    Draws exact collider on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Draws Bounding Box on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    + if true, polygon is not an invalid, self-crossing polygon +

    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update instantly or mark as dirty

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = false +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update position BY MOVING FORWARD IN ANGLE DIRECTION

    +
    +
    +

    Parameters

    +
      +
    • + speed: + number = 1 +
    • +
    • + updateNow: + boolean + = true +
    • +
    +
    +

    + Returns + SATPolygon +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    + inner function for after position change update aabb in + system and convex inner polygons +

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = ... +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ +
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/Circle.html b/docs/classes/Circle.html index 33832e63..4198be76 100644 --- a/docs/classes/Circle.html +++ b/docs/classes/Circle.html @@ -1,82 +1,2988 @@ -Circle | Detect-Collisions

collider - circle

-

Hierarchy (view full)

Implements

Constructors

Properties

_group: number

group for collision filtering

-
angle: number

for compatibility reasons circle has angle

-
bbox: BBox

bounding box cache, without padding

-
dirty: boolean = false

was the polygon modified and needs update in the next checkCollision

-
isCentered: true = true

always centered

-
isConvex: true = true

flag to show is it a convex body or non convex polygon

-
isStatic: boolean

static bodies don't move but they collide

-
isTrigger: boolean

trigger bodies move but are like ghosts

-
maxX: number

maximum x bound of body

-
maxY: number

maximum y bound of body

-
minX: number

minimum x bound of body

-
minY: number

minimum y bound of body

-
offset: SATVector

offset

-
offsetCopy: Vector = ...

offset copy without angle applied

-
padding: number

bodies are not reinserted during update if their bbox didnt move outside bbox + padding

-
r: number
system?: System<Body>

reference to collision system

-
type: Circle = BodyType.Circle

circle type

-
typeGroup: Circle = BodyGroup.Circle

faster than type

-
unscaledRadius: number

saved initial radius - internal

-

Accessors

  • get scaleX(): number
  • scaleX = scale in case of Circles

    -

    Returns number

  • get scaleY(): number
  • scaleY = scale in case of Circles

    -

    Returns number

Methods

  • Draws collider on a CanvasRenderingContext2D's current path

    -

    Parameters

    • context: CanvasRenderingContext2D

    Returns void

  • update instantly or mark as dirty

    -

    Parameters

    • updateNow: boolean = false

    Returns void

  • inner function for after position change update aabb in system

    -

    Parameters

    • updateNow: boolean = ...

    Returns void

+ + + + + + Circle | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class Circle

+
+
+

collider - circle

+
+
+
+

Hierarchy (view full)

+ +
+
+

Implements

+ +
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + + +
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ _group: + number +
+
+

group for collision filtering

+
+
+ +
+
+ + +
+ angle: + number +
+
+

for compatibility reasons circle has angle

+
+
+ +
+
+ + +
+ bbox: + BBox +
+
+

bounding box cache, without padding

+
+
+ +
+
+ + +
+ dirty: + boolean = false +
+
+

+ was the polygon modified and needs update in the next + checkCollision +

+
+
+ +
+
+ + +
+ isCentered: + true = true +
+
+

always centered

+
+
+ +
+
+ + +
+ isConvex: + true = true +
+
+

flag to show is it a convex body or non convex polygon

+
+
+ +
+
+ + +
+ isStatic: + boolean +
+
+

static bodies don't move but they collide

+
+
+ +
+
+ + +
+ isTrigger: + boolean +
+
+

trigger bodies move but are like ghosts

+
+
+ +
+
+ + +
+ maxX: + number +
+
+

maximum x bound of body

+
+
+ +
+
+ + +
+ maxY: + number +
+
+

maximum y bound of body

+
+
+ +
+
+ + +
+ minX: + number +
+
+

minimum x bound of body

+
+
+ +
+
+ + +
+ minY: + number +
+
+

minimum y bound of body

+
+
+ +
+
+ + +
+ offset: + SATVector +
+

offset

+
+ +
+
+ + +
+ offsetCopy: + Vector = ... +
+
+

offset copy without angle applied

+
+
+ +
+
+ + +
+ padding: + number +
+
+

+ bodies are not reinserted during update if their bbox didnt + move outside bbox + padding +

+
+
+ +
+
+ + +
+ pos: + SATVector +
+ +
+
+ + +
+ r: + number +
+ +
+
+ + +
+ system?: + System<Body> +
+
+

reference to collision system

+
+
+ +
+
+ + +
+ type: + Circle = BodyType.Circle +
+

circle type

+
+ +
+
+ + +
+ typeGroup: + Circle = BodyGroup.Circle +
+
+

faster than type

+
+
+ +
+
+ + +
+ unscaledRadius: + number +
+
+

saved initial radius - internal

+
+
+ +
+
+
+
+ +

+ + + + Accessors +

+
+
+
+ + +
    +
  • + get group(): number +
  • +
  • +
    +

    group for collision filtering

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set group(group): void +
  • +
  • +
    +

    group for collision filtering

    +
    +
    +

    Parameters

    +
      +
    • + group: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scale(): number +
  • +
  • +
    +

    allow get scale

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set scale(scale): void +
  • +
  • +
    +

    shorthand for setScale()

    +
    +
    +

    Parameters

    +
      +
    • + scale: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleX(): number +
  • +
  • +
    +

    scaleX = scale in case of Circles

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleY(): number +
  • +
  • +
    +

    scaleY = scale in case of Circles

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get x(): number +
  • +
  • +
    +

    get this.pos.x

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set x(x): void +
  • +
  • +
    +

    updating this.pos.x by this.x = x updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get y(): number +
  • +
  • +
    +

    get this.pos.y

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set y(y): void +
  • +
  • +
    +

    updating this.pos.y by this.y = y updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + y: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +
    +

    + Draws collider on a CanvasRenderingContext2D's current + path +

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Draws Bounding Box on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    update instantly or mark as dirty

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = false +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update position BY MOVING FORWARD IN ANGLE DIRECTION

    +
    +
    +

    Parameters

    +
      +
    • + speed: + number = 1 +
    • +
    • + updateNow: + boolean + = true +
    • +
    +
    +

    + Returns + Circle +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    update position BY TELEPORTING

    +
    +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    • + y: + number +
    • +
    • + updateNow: + boolean + = true +
    • +
    +
    +

    + Returns + Circle +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update scale

    +
    +
    +

    Parameters

    +
      +
    • + scaleX: + number +
    • +
    • + _scaleY: + number + = scaleX +
    • +
    • + updateNow: + boolean + = true +
    • +
    +
    +

    + Returns + Circle +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    + inner function for after position change update aabb in + system +

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = ... +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/Ellipse.html b/docs/classes/Ellipse.html index 6629cc7b..a324e3b5 100644 --- a/docs/classes/Ellipse.html +++ b/docs/classes/Ellipse.html @@ -1,123 +1,4705 @@ -Ellipse | Detect-Collisions

collider - ellipse

-

Hierarchy (view full)

Constructors

Properties

_group: number

group for collision filtering

-
_radiusX: number

inner initial params save

-
_radiusY: number
_step: number
angle: number

body angle in radians use deg2rad to convert

-
bbox: BBox

bounding box cache, without padding

-
calcPoints: SATVector[]
centered: boolean = false

is body centered

-
convexPolygons: SATPolygon[]

optimization for convex polygons

-
dirty: boolean = false

was the polygon modified and needs update in the next checkCollision

-
edges: SATVector[]
isConvex: true = true

ellipses are convex

-
isStatic: boolean

static bodies don't move but they collide

-
isTrigger: boolean

trigger bodies move but are like ghosts

-
maxX: number

maximum x bound of body

-
maxY: number

maximum y bound of body

-
minX: number

minimum x bound of body

-
minY: number

minimum y bound of body

-
normals: SATVector[]
offset: SATVector

each body may have offset from center

-
padding: number

bodies are not reinserted during update if their bbox didnt move outside bbox + padding

-
points: SATVector[]
pointsBackup: Vector[]

backup of points used for scaling

-
scaleVector: Vector = ...

scale Vector of body

-
system?: System<Body>

reference to collision system

-
type: Ellipse = BodyType.Ellipse

ellipse type

-
typeGroup: Ellipse = BodyGroup.Ellipse

faster than type

-

Accessors

  • get group(): number
  • group for collision filtering

    -

    Returns number

  • set group(group): void
  • group for collision filtering

    -

    Parameters

    • group: number

    Returns void

  • get isCentered(): boolean
  • is body centered?

    -

    Returns boolean

  • set isCentered(_isCentered): void
  • flag to set is body centered

    -

    Parameters

    • _isCentered: boolean

    Returns void

  • get scale(): number
  • allow approx getting of scale

    -

    Returns number

  • set scale(scale): void
  • allow easier setting of scale

    -

    Parameters

    • scale: number

    Returns void

  • get scaleX(): number
  • allow exact getting of scale x - use setScale(x, y) to set

    -

    Returns number

  • get scaleY(): number
  • allow exact getting of scale y - use setScale(x, y) to set

    -

    Returns number

Methods

  • inner function for after position change update aabb in system and convex inner polygons

    -

    Parameters

    • updateNow: boolean = ...

    Returns void

+ + + + + + Ellipse | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class Ellipse

+
+
+
+

collider - ellipse

+
+
+
+
+

Hierarchy (view full)

+ +
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + + +
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ _group: + number +
+
+

group for collision filtering

+
+
+ +
+
+ + +
+ _radiusX: + number +
+
+

inner initial params save

+
+
+ +
+
+ + +
+ _radiusY: + number +
+ +
+
+ + +
+ _step: + number +
+ +
+
+ + +
+ angle: + number +
+
+

body angle in radians use deg2rad to convert

+
+
+ +
+
+ + +
+ bbox: + BBox +
+
+

bounding box cache, without padding

+
+
+ +
+
+ + +
+ calcPoints: + SATVector[] +
+ +
+
+ + +
+ centered: + boolean = false +
+
+

is body centered

+
+
+ +
+
+ + +
+ convexPolygons: + SATPolygon[] +
+
+

optimization for convex polygons

+
+
+ +
+
+ + +
+ dirty: + boolean = false +
+
+

+ was the polygon modified and needs update in the next + checkCollision +

+
+
+ +
+
+ + +
+ edges: + SATVector[] +
+ +
+
+ + +
+ isConvex: + true = true +
+
+

ellipses are convex

+
+
+ +
+
+ + +
+ isStatic: + boolean +
+
+

static bodies don't move but they collide

+
+
+ +
+
+ + +
+ isTrigger: + boolean +
+
+

trigger bodies move but are like ghosts

+
+
+ +
+
+ + +
+ maxX: + number +
+
+

maximum x bound of body

+
+
+ +
+
+ + +
+ maxY: + number +
+
+

maximum y bound of body

+
+
+ +
+
+ + +
+ minX: + number +
+
+

minimum x bound of body

+
+
+ +
+
+ + +
+ minY: + number +
+
+

minimum y bound of body

+
+
+ +
+
+ + +
+ normals: + SATVector[] +
+ +
+
+ + +
+ offset: + SATVector +
+
+

each body may have offset from center

+
+
+ +
+
+ + +
+ padding: + number +
+
+

+ bodies are not reinserted during update if their bbox didnt + move outside bbox + padding +

+
+
+ +
+
+ + +
+ points: + SATVector[] +
+ +
+
+ + +
+ pointsBackup: + Vector[] +
+
+

backup of points used for scaling

+
+
+ +
+
+ + +
+ pos: + SATVector +
+ +
+
+ + +
+ scaleVector: + Vector = ... +
+
+

scale Vector of body

+
+
+ +
+
+ + +
+ system?: + System<Body> +
+
+

reference to collision system

+
+
+ +
+
+ + +
+ type: + Ellipse = BodyType.Ellipse +
+

ellipse type

+
+ +
+
+ + +
+ typeGroup: + Ellipse = BodyGroup.Ellipse +
+
+

faster than type

+
+
+ +
+
+
+
+ +

+ + + + Accessors +

+
+
+
+ + +
    +
  • + get group(): number +
  • +
  • +
    +

    group for collision filtering

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set group(group): void +
  • +
  • +
    +

    group for collision filtering

    +
    +
    +

    Parameters

    +
      +
    • + group: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get isCentered(): boolean +
  • +
  • +
    +

    is body centered?

    +
    +

    + Returns boolean +

    +
    + +
  • +
  • + set isCentered(_isCentered): void +
  • +
  • +
    +

    flag to set is body centered

    +
    +
    +

    Parameters

    +
      +
    • + _isCentered: + boolean +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get radiusX(): number +
  • +
  • +
    +

    get ellipse radiusX

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set radiusX(radiusX): void +
  • +
  • +
    +

    set ellipse radiusX, update points

    +
    +
    +

    Parameters

    +
      +
    • + radiusX: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get radiusY(): number +
  • +
  • +
    +

    get ellipse radiusY

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set radiusY(radiusY): void +
  • +
  • +
    +

    set ellipse radiusY, update points

    +
    +
    +

    Parameters

    +
      +
    • + radiusY: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scale(): number +
  • +
  • +
    +

    allow approx getting of scale

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set scale(scale): void +
  • +
  • +
    +

    allow easier setting of scale

    +
    +
    +

    Parameters

    +
      +
    • + scale: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleX(): number +
  • +
  • +
    +

    + allow exact getting of scale x - use setScale(x, y) to set +

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleY(): number +
  • +
  • +
    +

    + allow exact getting of scale y - use setScale(x, y) to set +

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get step(): number +
  • +
  • +
    +

    get ellipse step number

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set step(step): void +
  • +
  • +
    +

    set ellipse step number

    +
    +
    +

    Parameters

    +
      +
    • + step: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get x(): number +
  • +
  • +

    + Returns number +

    + +
  • +
  • + set x(x): void +
  • +
  • +
    +

    updating this.pos.x by this.x = x updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get y(): number +
  • +
  • +

    + Returns number +

    + +
  • +
  • + set y(y): void +
  • +
  • +
    +

    updating this.pos.y by this.y = y updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + y: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +
    +

    do not attempt to use Polygon.center()

    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Draws exact collider on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Draws Bounding Box on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    + if true, polygon is not an invalid, self-crossing polygon +

    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update instantly or mark as dirty

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = false +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update position BY MOVING FORWARD IN ANGLE DIRECTION

    +
    +
    +

    Parameters

    +
      +
    • + speed: + number = 1 +
    • +
    • + updateNow: + boolean + = true +
    • +
    +
    +

    + Returns + SATPolygon +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    + inner function for after position change update aabb in + system and convex inner polygons +

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = ... +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ +
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/Line.html b/docs/classes/Line.html index 11cf7c0c..4d1c6bb1 100644 --- a/docs/classes/Line.html +++ b/docs/classes/Line.html @@ -1,110 +1,4424 @@ -Line | Detect-Collisions

collider - line

-

Hierarchy (view full)

Constructors

Properties

_group: number

group for collision filtering

-
angle: number

body angle in radians use deg2rad to convert

-
bbox: BBox

bounding box cache, without padding

-
calcPoints: SATVector[]
centered: boolean = false

is body centered

-
convexPolygons: SATPolygon[]

optimization for convex polygons

-
dirty: boolean = false

was the polygon modified and needs update in the next checkCollision

-
edges: SATVector[]
isConvex: true = true

line is convex

-
isStatic: boolean

static bodies don't move but they collide

-
isTrigger: boolean

trigger bodies move but are like ghosts

-
maxX: number

maximum x bound of body

-
maxY: number

maximum y bound of body

-
minX: number

minimum x bound of body

-
minY: number

minimum y bound of body

-
normals: SATVector[]
offset: SATVector

each body may have offset from center

-
padding: number

bodies are not reinserted during update if their bbox didnt move outside bbox + padding

-
points: SATVector[]
pointsBackup: Vector[]

backup of points used for scaling

-
scaleVector: Vector = ...

scale Vector of body

-
system?: System<Body>

reference to collision system

-
type: Line = BodyType.Line

line type

-
typeGroup: Line = BodyGroup.Line

faster than type

-

Accessors

  • get group(): number
  • group for collision filtering

    -

    Returns number

  • set group(group): void
  • group for collision filtering

    -

    Parameters

    • group: number

    Returns void

  • get isCentered(): boolean
  • is polygon centered?

    -

    Returns boolean

  • set isCentered(isCentered): void
  • flag to set is polygon centered

    -

    Parameters

    • isCentered: boolean

    Returns void

  • get scale(): number
  • allow approx getting of scale

    -

    Returns number

  • set scale(scale): void
  • allow easier setting of scale

    -

    Parameters

    • scale: number

    Returns void

  • get scaleX(): number
  • allow exact getting of scale x - use setScale(x, y) to set

    -

    Returns number

  • get scaleY(): number
  • allow exact getting of scale y - use setScale(x, y) to set

    -

    Returns number

Methods

  • inner function for after position change update aabb in system and convex inner polygons

    -

    Parameters

    • updateNow: boolean = ...

    Returns void

+ + + + + + Line | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class Line

+
+
+

collider - line

+
+
+
+

Hierarchy (view full)

+ +
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + + +
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ _group: + number +
+
+

group for collision filtering

+
+
+ +
+
+ + +
+ angle: + number +
+
+

body angle in radians use deg2rad to convert

+
+
+ +
+
+ + +
+ bbox: + BBox +
+
+

bounding box cache, without padding

+
+
+ +
+
+ + +
+ calcPoints: + SATVector[] +
+ +
+
+ + +
+ centered: + boolean = false +
+
+

is body centered

+
+
+ +
+
+ + +
+ convexPolygons: + SATPolygon[] +
+
+

optimization for convex polygons

+
+
+ +
+
+ + +
+ dirty: + boolean = false +
+
+

+ was the polygon modified and needs update in the next + checkCollision +

+
+
+ +
+
+ + +
+ edges: + SATVector[] +
+ +
+
+ + +
+ isConvex: + true = true +
+
+

line is convex

+
+
+ +
+
+ + +
+ isStatic: + boolean +
+
+

static bodies don't move but they collide

+
+
+ +
+
+ + +
+ isTrigger: + boolean +
+
+

trigger bodies move but are like ghosts

+
+
+ +
+
+ + +
+ maxX: + number +
+
+

maximum x bound of body

+
+
+ +
+
+ + +
+ maxY: + number +
+
+

maximum y bound of body

+
+
+ +
+
+ + +
+ minX: + number +
+
+

minimum x bound of body

+
+
+ +
+
+ + +
+ minY: + number +
+
+

minimum y bound of body

+
+
+ +
+
+ + +
+ normals: + SATVector[] +
+ +
+
+ + +
+ offset: + SATVector +
+
+

each body may have offset from center

+
+
+ +
+
+ + +
+ padding: + number +
+
+

+ bodies are not reinserted during update if their bbox didnt + move outside bbox + padding +

+
+
+ +
+
+ + +
+ points: + SATVector[] +
+ +
+
+ + +
+ pointsBackup: + Vector[] +
+
+

backup of points used for scaling

+
+
+ +
+
+ + +
+ pos: + SATVector +
+ +
+
+ + +
+ scaleVector: + Vector = ... +
+
+

scale Vector of body

+
+
+ +
+
+ + +
+ system?: + System<Body> +
+
+

reference to collision system

+
+
+ +
+
+ + +
+ type: + Line = BodyType.Line +
+

line type

+
+ +
+
+ + +
+ typeGroup: + Line = BodyGroup.Line +
+
+

faster than type

+
+
+ +
+
+
+
+ +

+ + + + Accessors +

+
+
+
+ + + +
+
+ + +
    +
  • + get group(): number +
  • +
  • +
    +

    group for collision filtering

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set group(group): void +
  • +
  • +
    +

    group for collision filtering

    +
    +
    +

    Parameters

    +
      +
    • + group: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get isCentered(): boolean +
  • +
  • +
    +

    is polygon centered?

    +
    +

    + Returns boolean +

    +
    + +
  • +
  • + set isCentered(isCentered): void +
  • +
  • +
    +

    flag to set is polygon centered

    +
    +
    +

    Parameters

    +
      +
    • + isCentered: + boolean +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scale(): number +
  • +
  • +
    +

    allow approx getting of scale

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set scale(scale): void +
  • +
  • +
    +

    allow easier setting of scale

    +
    +
    +

    Parameters

    +
      +
    • + scale: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleX(): number +
  • +
  • +
    +

    + allow exact getting of scale x - use setScale(x, y) to set +

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleY(): number +
  • +
  • +
    +

    + allow exact getting of scale y - use setScale(x, y) to set +

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + + +
+
+ + +
    +
  • + get x(): number +
  • +
  • +

    + Returns number +

    + +
  • +
  • + set x(x): void +
  • +
  • +
    +

    updating this.pos.x by this.x = x updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get y(): number +
  • +
  • +

    + Returns number +

    + +
  • +
  • + set y(y): void +
  • +
  • +
    +

    updating this.pos.y by this.y = y updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + y: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +
    +

    Draws exact collider on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Draws Bounding Box on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    + if true, polygon is not an invalid, self-crossing polygon +

    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update instantly or mark as dirty

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = false +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update position BY MOVING FORWARD IN ANGLE DIRECTION

    +
    +
    +

    Parameters

    +
      +
    • + speed: + number = 1 +
    • +
    • + updateNow: + boolean + = true +
    • +
    +
    +

    + Returns + SATPolygon +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    + inner function for after position change update aabb in + system and convex inner polygons +

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = ... +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ +
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/Point.html b/docs/classes/Point.html index a0b4286f..04392675 100644 --- a/docs/classes/Point.html +++ b/docs/classes/Point.html @@ -1,121 +1,4601 @@ -Point | Detect-Collisions

collider - point (very tiny box)

-

Hierarchy (view full)

Constructors

Properties

_group: number

group for collision filtering

-
_height: number

inner height

-
_width: number

inner width

-
angle: number

body angle in radians use deg2rad to convert

-
bbox: BBox

bounding box cache, without padding

-
calcPoints: SATVector[]
centered: boolean = false

is body centered

-
convexPolygons: SATPolygon[]

optimization for convex polygons

-
dirty: boolean = false

was the polygon modified and needs update in the next checkCollision

-
edges: SATVector[]
isConvex: true = true

boxes are convex

-
isStatic: boolean

static bodies don't move but they collide

-
isTrigger: boolean

trigger bodies move but are like ghosts

-
maxX: number

maximum x bound of body

-
maxY: number

maximum y bound of body

-
minX: number

minimum x bound of body

-
minY: number

minimum y bound of body

-
normals: SATVector[]
offset: SATVector

each body may have offset from center

-
padding: number

bodies are not reinserted during update if their bbox didnt move outside bbox + padding

-
points: SATVector[]
pointsBackup: Vector[]

backup of points used for scaling

-
scaleVector: Vector = ...

scale Vector of body

-
system?: System<Body>

reference to collision system

-
type: Point = BodyType.Point

point type

-
typeGroup: Point = BodyGroup.Point

faster than type

-

Accessors

  • get group(): number
  • group for collision filtering

    -

    Returns number

  • set group(group): void
  • group for collision filtering

    -

    Parameters

    • group: number

    Returns void

  • get height(): number
  • get box height

    -

    Returns number

  • set height(height): void
  • set box height, update points

    -

    Parameters

    • height: number

    Returns void

  • get isCentered(): boolean
  • is polygon centered?

    -

    Returns boolean

  • set isCentered(isCentered): void
  • flag to set is polygon centered

    -

    Parameters

    • isCentered: boolean

    Returns void

  • get scale(): number
  • allow approx getting of scale

    -

    Returns number

  • set scale(scale): void
  • allow easier setting of scale

    -

    Parameters

    • scale: number

    Returns void

  • get scaleX(): number
  • allow exact getting of scale x - use setScale(x, y) to set

    -

    Returns number

  • get scaleY(): number
  • allow exact getting of scale y - use setScale(x, y) to set

    -

    Returns number

  • get width(): number
  • get box width

    -

    Returns number

  • set width(width): void
  • set box width, update points

    -

    Parameters

    • width: number

    Returns void

Methods

  • Draws exact collider on canvas context

    -

    Parameters

    • context: CanvasRenderingContext2D

    Returns void

  • Draws Bounding Box on canvas context

    -

    Parameters

    • context: CanvasRenderingContext2D

    Returns void

  • inner function for after position change update aabb in system and convex inner polygons

    -

    Parameters

    • updateNow: boolean = ...

    Returns void

+ + + + + + Point | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class Point

+
+
+
+

collider - point (very tiny box)

+
+
+
+
+

Hierarchy (view full)

+
    +
  • + Box +
      +
    • Point
    • +
    +
  • +
+
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + + +
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ _group: + number +
+
+

group for collision filtering

+
+
+ +
+
+ + +
+ _height: + number +
+

inner height

+
+ +
+
+ + +
+ _width: + number +
+

inner width

+
+ +
+
+ + +
+ angle: + number +
+
+

body angle in radians use deg2rad to convert

+
+
+ +
+
+ + +
+ bbox: + BBox +
+
+

bounding box cache, without padding

+
+
+ +
+
+ + +
+ calcPoints: + SATVector[] +
+ +
+
+ + +
+ centered: + boolean = false +
+
+

is body centered

+
+
+ +
+
+ + +
+ convexPolygons: + SATPolygon[] +
+
+

optimization for convex polygons

+
+
+ +
+
+ + +
+ dirty: + boolean = false +
+
+

+ was the polygon modified and needs update in the next + checkCollision +

+
+
+ +
+
+ + +
+ edges: + SATVector[] +
+ +
+
+ + +
+ isConvex: + true = true +
+
+

boxes are convex

+
+
+ +
+
+ + +
+ isStatic: + boolean +
+
+

static bodies don't move but they collide

+
+
+ +
+
+ + +
+ isTrigger: + boolean +
+
+

trigger bodies move but are like ghosts

+
+
+ +
+
+ + +
+ maxX: + number +
+
+

maximum x bound of body

+
+
+ +
+
+ + +
+ maxY: + number +
+
+

maximum y bound of body

+
+
+ +
+
+ + +
+ minX: + number +
+
+

minimum x bound of body

+
+
+ +
+
+ + +
+ minY: + number +
+
+

minimum y bound of body

+
+
+ +
+
+ + +
+ normals: + SATVector[] +
+ +
+
+ + +
+ offset: + SATVector +
+
+

each body may have offset from center

+
+
+ +
+
+ + +
+ padding: + number +
+
+

+ bodies are not reinserted during update if their bbox didnt + move outside bbox + padding +

+
+
+ +
+
+ + +
+ points: + SATVector[] +
+ +
+
+ + +
+ pointsBackup: + Vector[] +
+
+

backup of points used for scaling

+
+
+ +
+
+ + +
+ pos: + SATVector +
+ +
+
+ + +
+ scaleVector: + Vector = ... +
+
+

scale Vector of body

+
+
+ +
+
+ + +
+ system?: + System<Body> +
+
+

reference to collision system

+
+
+ +
+
+ + +
+ type: + Point = BodyType.Point +
+

point type

+
+ +
+
+ + +
+ typeGroup: + Point = BodyGroup.Point +
+
+

faster than type

+
+
+ +
+
+
+
+ +

+ + + + Accessors +

+
+
+
+ + +
    +
  • + get group(): number +
  • +
  • +
    +

    group for collision filtering

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set group(group): void +
  • +
  • +
    +

    group for collision filtering

    +
    +
    +

    Parameters

    +
      +
    • + group: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get height(): number +
  • +
  • +
    +

    get box height

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set height(height): void +
  • +
  • +
    +

    set box height, update points

    +
    +
    +

    Parameters

    +
      +
    • + height: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get isCentered(): boolean +
  • +
  • +
    +

    is polygon centered?

    +
    +

    + Returns boolean +

    +
    + +
  • +
  • + set isCentered(isCentered): void +
  • +
  • +
    +

    flag to set is polygon centered

    +
    +
    +

    Parameters

    +
      +
    • + isCentered: + boolean +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scale(): number +
  • +
  • +
    +

    allow approx getting of scale

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set scale(scale): void +
  • +
  • +
    +

    allow easier setting of scale

    +
    +
    +

    Parameters

    +
      +
    • + scale: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleX(): number +
  • +
  • +
    +

    + allow exact getting of scale x - use setScale(x, y) to set +

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleY(): number +
  • +
  • +
    +

    + allow exact getting of scale y - use setScale(x, y) to set +

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get width(): number +
  • +
  • +
    +

    get box width

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set width(width): void +
  • +
  • +
    +

    set box width, update points

    +
    +
    +

    Parameters

    +
      +
    • + width: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get x(): number +
  • +
  • +

    + Returns number +

    + +
  • +
  • + set x(x): void +
  • +
  • +
    +

    updating this.pos.x by this.x = x updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get y(): number +
  • +
  • +

    + Returns number +

    + +
  • +
  • + set y(y): void +
  • +
  • +
    +

    updating this.pos.y by this.y = y updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + y: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + + +
+
+ + +
    + +
  • +
    +

    Draws exact collider on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Draws Bounding Box on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    + if true, polygon is not an invalid, self-crossing polygon +

    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update instantly or mark as dirty

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = false +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update position BY MOVING FORWARD IN ANGLE DIRECTION

    +
    +
    +

    Parameters

    +
      +
    • + speed: + number = 1 +
    • +
    • + updateNow: + boolean + = true +
    • +
    +
    +

    + Returns + SATPolygon +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + isCentered: + boolean + = ... +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    update position BY TELEPORTING

    +
    +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    • + y: + number +
    • +
    • + updateNow: + boolean + = true +
    • +
    +
    +

    + Returns + SATPolygon +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    + inner function for after position change update aabb in + system and convex inner polygons +

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = ... +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    + update the position of the decomposed convex polygons (if + any), called after the position of the body has changed +

    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + +
    + +
  • +
    +

    do not attempt to use Polygon.updateIsConvex()

    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+
+ +
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/Polygon.html b/docs/classes/Polygon.html index abbf8abb..02677e8e 100644 --- a/docs/classes/Polygon.html +++ b/docs/classes/Polygon.html @@ -1,108 +1,4223 @@ -Polygon | Detect-Collisions

collider - polygon

-

Hierarchy (view full)

Implements

Constructors

Properties

_group: number

group for collision filtering

-
angle: number

body angle in radians use deg2rad to convert

-
bbox: BBox

bounding box cache, without padding

-
calcPoints: SATVector[]
centered: boolean = false

is body centered

-
convexPolygons: SATPolygon[]

optimization for convex polygons

-
dirty: boolean = false

was the polygon modified and needs update in the next checkCollision

-
edges: SATVector[]
isConvex: boolean

is it a convex polgyon as opposed to a hollow inside (concave) polygon

-
isStatic: boolean

static bodies don't move but they collide

-
isTrigger: boolean

trigger bodies move but are like ghosts

-
maxX: number

maximum x bound of body

-
maxY: number

maximum y bound of body

-
minX: number

minimum x bound of body

-
minY: number

minimum y bound of body

-
normals: SATVector[]
offset: SATVector

each body may have offset from center

-
padding: number

bodies are not reinserted during update if their bbox didnt move outside bbox + padding

-
points: SATVector[]
pointsBackup: Vector[]

backup of points used for scaling

-
scaleVector: Vector = ...

scale Vector of body

-
system?: System<Body>

reference to collision system

-
type:
    | Ellipse
    | Polygon
    | Box
    | Line
    | Point = BodyType.Polygon

type of body

-
typeGroup:
    | Ellipse
    | Polygon
    | Box
    | Line
    | Point = BodyGroup.Polygon

faster than type

-

Accessors

  • get scaleX(): number
  • allow exact getting of scale x - use setScale(x, y) to set

    -

    Returns number

  • get scaleY(): number
  • allow exact getting of scale y - use setScale(x, y) to set

    -

    Returns number

Methods

  • if true, polygon is not an invalid, self-crossing polygon

    -

    Returns boolean

  • update instantly or mark as dirty

    -

    Parameters

    • updateNow: boolean = false

    Returns void

  • inner function for after position change update aabb in system and convex inner polygons

    -

    Parameters

    • updateNow: boolean = ...

    Returns void

  • update the position of the decomposed convex polygons (if any), called -after the position of the body has changed

    -

    Returns void

+ + + + + + Polygon | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class Polygon

+
+
+
+

collider - polygon

+
+
+
+
+

Hierarchy (view full)

+ +
+
+

Implements

+ +
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + + +
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ _group: + number +
+
+

group for collision filtering

+
+
+ +
+
+ + +
+ angle: + number +
+
+

body angle in radians use deg2rad to convert

+
+
+ +
+
+ + +
+ bbox: + BBox +
+
+

bounding box cache, without padding

+
+
+ +
+
+ + +
+ calcPoints: + SATVector[] +
+ +
+
+ + +
+ centered: + boolean = false +
+
+

is body centered

+
+
+ +
+
+ + +
+ convexPolygons: + SATPolygon[] +
+
+

optimization for convex polygons

+
+
+ +
+
+ + +
+ dirty: + boolean = false +
+
+

+ was the polygon modified and needs update in the next + checkCollision +

+
+
+ +
+
+ + +
+ edges: + SATVector[] +
+ +
+
+ + +
+ isConvex: + boolean +
+
+

+ is it a convex polgyon as opposed to a hollow inside (concave) + polygon +

+
+
+ +
+
+ + +
+ isStatic: + boolean +
+
+

static bodies don't move but they collide

+
+
+ +
+
+ + +
+ isTrigger: + boolean +
+
+

trigger bodies move but are like ghosts

+
+
+ +
+
+ + +
+ maxX: + number +
+
+

maximum x bound of body

+
+
+ +
+
+ + +
+ maxY: + number +
+
+

maximum y bound of body

+
+
+ +
+
+ + +
+ minX: + number +
+
+

minimum x bound of body

+
+
+ +
+
+ + +
+ minY: + number +
+
+

minimum y bound of body

+
+
+ +
+
+ + +
+ normals: + SATVector[] +
+ +
+
+ + +
+ offset: + SATVector +
+
+

each body may have offset from center

+
+
+ +
+
+ + +
+ padding: + number +
+
+

+ bodies are not reinserted during update if their bbox didnt + move outside bbox + padding +

+
+
+ +
+
+ + +
+ points: + SATVector[] +
+ +
+
+ + +
+ pointsBackup: + Vector[] +
+
+

backup of points used for scaling

+
+
+ +
+
+ + +
+ pos: + SATVector +
+ +
+
+ + +
+ scaleVector: + Vector = ... +
+
+

scale Vector of body

+
+
+ +
+
+ + +
+ system?: + System<Body> +
+
+

reference to collision system

+
+
+ +
+
+ + +
+ type:
    | Ellipse
    | Polygon
    | Box
    | Line
    | Point = BodyType.Polygon +
+

type of body

+
+ +
+
+ + +
+ typeGroup:
    | Ellipse
    | Polygon
    | Box
    | Line
    | Point = BodyGroup.Polygon +
+
+

faster than type

+
+
+ +
+
+
+
+ +

+ + + + Accessors +

+
+
+
+ + +
    +
  • + get group(): number +
  • +
  • +
    +

    group for collision filtering

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set group(group): void +
  • +
  • +
    +

    group for collision filtering

    +
    +
    +

    Parameters

    +
      +
    • + group: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get isCentered(): boolean +
  • +
  • +
    +

    is polygon centered?

    +
    +

    + Returns boolean +

    +
    + +
  • +
  • + set isCentered(isCentered): void +
  • +
  • +
    +

    flag to set is polygon centered

    +
    +
    +

    Parameters

    +
      +
    • + isCentered: + boolean +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scale(): number +
  • +
  • +
    +

    allow approx getting of scale

    +
    +

    + Returns number +

    +
    + +
  • +
  • + set scale(scale): void +
  • +
  • +
    +

    allow easier setting of scale

    +
    +
    +

    Parameters

    +
      +
    • + scale: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleX(): number +
  • +
  • +
    +

    + allow exact getting of scale x - use setScale(x, y) to set +

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleY(): number +
  • +
  • +
    +

    + allow exact getting of scale y - use setScale(x, y) to set +

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get x(): number +
  • +
  • +

    + Returns number +

    + +
  • +
  • + set x(x): void +
  • +
  • +
    +

    updating this.pos.x by this.x = x updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    +
  • + get y(): number +
  • +
  • +

    + Returns number +

    + +
  • +
  • + set y(y): void +
  • +
  • +
    +

    updating this.pos.y by this.y = y updates AABB

    +
    +
    +

    Parameters

    +
      +
    • + y: + number +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +
    +

    Draws exact collider on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Draws Bounding Box on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    + if true, polygon is not an invalid, self-crossing polygon +

    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update instantly or mark as dirty

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = false +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update position BY MOVING FORWARD IN ANGLE DIRECTION

    +
    +
    +

    Parameters

    +
      +
    • + speed: + number = 1 +
    • +
    • + updateNow: + boolean + = true +
    • +
    +
    +

    + Returns + SATPolygon +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + isCentered: + boolean + = ... +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    + inner function for after position change update aabb in + system and convex inner polygons +

    +
    +
    +

    Parameters

    +
      +
    • + updateNow: + boolean + = ... +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    + update the position of the decomposed convex polygons (if + any), called after the position of the body has changed +

    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    updates convex polygons cache in body

    +
    +
    +

    Parameters

    + +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    after points update set is convex

    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+
+ +
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/RBush.html b/docs/classes/RBush.html index 501a045d..4392dddb 100644 --- a/docs/classes/RBush.html +++ b/docs/classes/RBush.html @@ -1,28 +1,2414 @@ -RBush | Detect-Collisions

Constructors

Properties

_maxEntries: number
_minEntries: number
data: any

Methods

  • Parameters

    • bbox: any
    • path: any
    • level: any

    Returns void

  • Parameters

    • node: any
    • m: any
    • M: any
    • compare: any

    Returns number

  • Parameters

    • items: any
    • left: any
    • right: any
    • height: any

    Returns {
        children: any;
        height: number;
        leaf: boolean;
        maxX: number;
        maxY: number;
        minX: number;
        minY: number;
    }

    • children: any
    • height: number
    • leaf: boolean
    • maxX: number
    • maxY: number
    • minX: number
    • minY: number
  • Parameters

    • bbox: any
    • node: any
    • level: any
    • path: any

    Returns any

+ + + + + + RBush | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class RBush

+
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + + +
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ _maxEntries: + number +
+ +
+
+ + +
+ _minEntries: + number +
+ +
+
+ + +
+ data: + any +
+ +
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + bbox: + any +
    • +
    • + path: + any +
    • +
    • + level: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + node: + any +
    • +
    • + result: + any +
    • +
    +
    +

    + Returns any +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + node: + any +
    • +
    • + m: + any +
    • +
    • + M: + any +
    • +
    • + compare: + any +
    • +
    +
    +

    + Returns number +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + items: + any +
    • +
    • + left: + any +
    • +
    • + right: + any +
    • +
    • + height: + any +
    • +
    +
    +

    + Returns {
        children: any;
        height: number;
        leaf: boolean;
        maxX: number;
        maxY: number;
        minX: number;
        minY: number;
    } +

    +
      +
    • +
      + children: any +
      +
    • +
    • +
      + height: number +
      +
    • +
    • +
      + leaf: boolean +
      +
    • +
    • +
      + maxX: number +
      +
    • +
    • +
      + maxY: number +
      +
    • +
    • +
      + minX: number +
      +
    • +
    • +
      + minY: number +
      +
    • +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + node: + any +
    • +
    • + m: + any +
    • +
    • + M: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + node: + any +
    • +
    • + m: + any +
    • +
    • + M: + any +
    • +
    +
    +

    + Returns any +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + bbox: + any +
    • +
    • + node: + any +
    • +
    • + level: + any +
    • +
    • + path: + any +
    • +
    +
    +

    + Returns any +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + path: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + item: + any +
    • +
    • + level: + any +
    • +
    • + isNode: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + insertPath: + any +
    • +
    • + level: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + node: + any +
    • +
    • + newNode: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + bbox: + any +
    • +
    +
    +

    + Returns boolean +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + a: + any +
    • +
    • + b: + any +
    • +
    +
    +

    + Returns number +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + a: + any +
    • +
    • + b: + any +
    • +
    +
    +

    + Returns number +

    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + bbox: + any +
    • +
    +
    +

    + Returns any[] +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + item: + any +
    • +
    +
    +

    + Returns any +

    + +
  • +
+
+
+ + + +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/Response.html b/docs/classes/Response.html index d5c1bfb4..2ec8197d 100644 --- a/docs/classes/Response.html +++ b/docs/classes/Response.html @@ -1,10 +1,700 @@ -Response | Detect-Collisions

Constructors

Properties

Methods

Constructors

  • Returns Response

Properties

a: any
aInB: boolean
b: any
bInA: boolean
overlap: number
overlapN: SATVector
overlapV: SATVector

Methods

  • Returns Response

+ + + + + + Response | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class Response

+
+ +
+
+
+ + + +
+
+

Constructors

+ +
+
+

Properties

+ +
+
+

Methods

+ +
+
+
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + +
    + +
  • +

    + Returns + Response +

    + +
  • +
+
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ a: + any +
+ +
+
+ + +
+ aInB: + boolean +
+ +
+
+ + +
+ b: + any +
+ +
+
+ + +
+ bInA: + boolean +
+ +
+
+ + +
+ overlap: + number +
+ +
+
+ + +
+ overlapN: + SATVector +
+ +
+
+ + +
+ overlapV: + SATVector +
+ +
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +

    + Returns + Response +

    + +
  • +
+
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/SATCircle.html b/docs/classes/SATCircle.html index ecb4e46d..de521bb6 100644 --- a/docs/classes/SATCircle.html +++ b/docs/classes/SATCircle.html @@ -1,5 +1,500 @@ -SATCircle | Detect-Collisions

This is simple circle with a center {pos} position and radius {r}.

-

Hierarchy (view full)

Constructors

Properties

pos -r -

Constructors

  • Parameters

    Returns SATCircle

Properties

r: number
+ + + + + + SATCircle | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class SATCircle

+
+
+
+

+ This is simple circle with a center {pos} position and radius {r}. +

+
+
+
+
+

+ Hierarchy (view full) +

+
    +
  • + SATCircle + +
  • +
+
+ +
+
+
+ + + +
+
+

Constructors

+ +
+
+

Properties

+
+ + pos + + r +
+
+
+
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + Optionalpos: + SATVector +
    • +
    • + Optionalr: + number +
    • +
    +
    +

    + Returns + SATCircle +

    + +
  • +
+
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ pos: + SATVector +
+ +
+
+ + +
+ r: + number +
+ +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/SATPolygon.html b/docs/classes/SATPolygon.html index 633f03d1..21d7b696 100644 --- a/docs/classes/SATPolygon.html +++ b/docs/classes/SATPolygon.html @@ -1,16 +1,1202 @@ -SATPolygon | Detect-Collisions

Hierarchy (view full)

Constructors

Properties

angle: number
calcPoints: SATVector[]
edges: SATVector[]
normals: SATVector[]
offset: SATVector
points: SATVector[]

Methods

  • Parameters

    • angle: number

    Returns SATPolygon

  • Parameters

    • angle: number

    Returns SATPolygon

  • Parameters

    • x: number
    • y: number

    Returns SATPolygon

+ + + + + + SATPolygon | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class SATPolygon

+
+
+

+ Hierarchy (view full) +

+
    +
  • + SATPolygon + +
  • +
+
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns + SATPolygon +

    + +
  • +
+
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ angle: + number +
+ +
+
+ + +
+ calcPoints: + SATVector[] +
+ +
+
+ + +
+ edges: + SATVector[] +
+ +
+
+ + +
+ normals: + SATVector[] +
+ +
+
+ + +
+ offset: + SATVector +
+ +
+
+ + +
+ points: + SATVector[] +
+ +
+
+ + +
+ pos: + SATVector +
+ +
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +

    + Returns + SATPolygon +

    + +
  • +
+
+
+ + +
    + +
  • +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + angle: + number +
    • +
    +
    +

    + Returns + SATPolygon +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + angle: + number +
    • +
    +
    +

    + Returns + SATPolygon +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns + SATPolygon +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns + SATPolygon +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    • + y: + number +
    • +
    +
    +

    + Returns + SATPolygon +

    + +
  • +
+
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/SATVector.html b/docs/classes/SATVector.html index 88d4f6b9..45ac62b1 100644 --- a/docs/classes/SATVector.html +++ b/docs/classes/SATVector.html @@ -1,24 +1,1570 @@ -SATVector | Detect-Collisions

This is a simple 2D vector/point class,Vector has two parameters {x},{y}.

-

Constructors

Properties

x -y -

Methods

Constructors

  • Vector has two properties

    -

    Parameters

    • Optionalx: number

      The x-coordinate of the Vector.

      -
    • Optionaly: number

      The y-coordinate of the Vector.

      -

    Returns SATVector

Properties

x: number
y: number

Methods

  • Parameters

    Returns number

  • Returns number

  • Returns number

  • Parameters

    • angle: number

    Returns SATVector

  • Parameters

    • x: number
    • Optionaly: number

    Returns SATVector

+ + + + + + SATVector | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class SATVector

+
+
+
+

+ This is a simple 2D vector/point class,Vector has two parameters + {x},{y}. +

+
+
+
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + +
    + +
  • +
    +

    Vector has two properties

    +
    +
    +

    Parameters

    +
      +
    • + Optionalx: + number +
      +

      The x-coordinate of the Vector.

      +
      +
      +
    • +
    • + Optionaly: + number +
      +

      The y-coordinate of the Vector.

      +
      +
      +
    • +
    +
    +

    + Returns + SATVector +

    +
    + +
  • +
+
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ x: + number +
+ +
+
+ + +
+ y: + number +
+ +
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns number +

    + +
  • +
+
+
+ + +
    + +
  • +

    + Returns number +

    + +
  • +
+
+
+ + +
    + +
  • +

    + Returns number +

    + +
  • +
+
+
+ + +
    + +
  • +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + angle: + number +
    • +
    +
    +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    • + Optionaly: + number +
    • +
    +
    +

    + Returns + SATVector +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns + SATVector +

    + +
  • +
+
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/classes/System.html b/docs/classes/System.html index 7f65677f..975d2c71 100644 --- a/docs/classes/System.html +++ b/docs/classes/System.html @@ -1,75 +1,4626 @@ -System | Detect-Collisions

Class System<TBody>

collision system

-

Type Parameters

Hierarchy

Constructors

Properties

_maxEntries: number
_minEntries: number
ray: Line

for raycasting

-
response: Response = ...

the last collision result

-

Methods

  • Parameters

    • bbox: any
    • path: any
    • level: any

    Returns void

  • Parameters

    • node: any
    • result: any

    Returns any

  • Parameters

    • node: any
    • m: any
    • M: any
    • compare: any

    Returns number

  • Parameters

    • items: any
    • left: any
    • right: any
    • height: any

    Returns {
        children: any;
        height: number;
        leaf: boolean;
        maxX: number;
        maxY: number;
        minX: number;
        minY: number;
    }

    • children: any
    • height: number
    • leaf: boolean
    • maxX: number
    • maxY: number
    • minX: number
    • minY: number
  • Parameters

    • node: any
    • m: any
    • M: any

    Returns void

  • Parameters

    • node: any
    • m: any
    • M: any

    Returns any

  • Parameters

    • bbox: any
    • node: any
    • level: any
    • path: any

    Returns any

  • Parameters

    • item: any
    • level: any
    • isNode: any

    Returns void

  • Parameters

    • insertPath: any
    • level: any

    Returns void

  • Parameters

    • node: any
    • newNode: any

    Returns void

  • check do 2 objects collide

    -

    Parameters

    Returns boolean

  • Parameters

    • bbox: any

    Returns boolean

  • Parameters

    • a: any
    • b: any

    Returns number

  • Parameters

    • a: any
    • b: any

    Returns number

  • create box at position with options and add to system

    -

    Parameters

    Returns Box

  • create ellipse at position with options and add to system

    -

    Parameters

    Returns Ellipse

  • draw exact bodies colliders outline

    -

    Parameters

    • context: CanvasRenderingContext2D

    Returns void

  • draw bounding boxes hierarchy outline

    -

    Parameters

    • context: CanvasRenderingContext2D

    Returns void

  • get object potential colliders

    -

    Parameters

    Returns TBody[]

    because it's slower to use than checkOne() or checkAll()

    -
  • re-insert body into collision tree and update its bbox -every body can be part of only one system

    -

    Parameters

    Returns this

  • remove body aabb from collision tree

    -

    Parameters

    Returns this

  • separate (move away) bodies

    -

    Returns void

  • separate (move away) 1 body

    -

    Parameters

    Returns void

  • used to find body deep inside data with finder function returning boolean found or not

    -

    Parameters

    Returns undefined | TBody

  • update all bodies aabb

    -

    Returns void

  • updates body in collision tree

    -

    Parameters

    Returns void

+ + + + + + System | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Class System<TBody>

+
+
+

collision system

+
+
+
+

Type Parameters

+ +
+
+

Hierarchy

+
    +
  • + BaseSystem<TBody> +
      +
    • System
    • +
    +
  • +
+
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Constructors +

+
+
+
+ + + +
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ _maxEntries: + number +
+ +
+
+ + +
+ _minEntries: + number +
+ +
+
+ + +
+ data: + ChildrenData<TBody> +
+ +
+
+ + +
+ ray: + Line +
+
+

for raycasting

+
+
+ +
+
+ + +
+ response: + Response = ... +
+
+

the last collision result

+
+
+ +
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + bbox: + any +
    • +
    • + path: + any +
    • +
    • + level: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + node: + any +
    • +
    • + result: + any +
    • +
    +
    +

    + Returns any +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + node: + any +
    • +
    • + m: + any +
    • +
    • + M: + any +
    • +
    • + compare: + any +
    • +
    +
    +

    + Returns number +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + items: + any +
    • +
    • + left: + any +
    • +
    • + right: + any +
    • +
    • + height: + any +
    • +
    +
    +

    + Returns {
        children: any;
        height: number;
        leaf: boolean;
        maxX: number;
        maxY: number;
        minX: number;
        minY: number;
    } +

    +
      +
    • +
      + children: any +
      +
    • +
    • +
      + height: number +
      +
    • +
    • +
      + leaf: boolean +
      +
    • +
    • +
      + maxX: number +
      +
    • +
    • +
      + maxY: number +
      +
    • +
    • +
      + minX: number +
      +
    • +
    • +
      + minY: number +
      +
    • +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + node: + any +
    • +
    • + m: + any +
    • +
    • + M: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + node: + any +
    • +
    • + m: + any +
    • +
    • + M: + any +
    • +
    +
    +

    + Returns any +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + bbox: + any +
    • +
    • + node: + any +
    • +
    • + level: + any +
    • +
    • + path: + any +
    • +
    +
    +

    + Returns any +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + path: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + item: + any +
    • +
    • + level: + any +
    • +
    • + isNode: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + insertPath: + any +
    • +
    • + level: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + node: + any +
    • +
    • + newNode: + any +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+ + + +
+
+ + +
    + +
  • +
    +

    check all bodies collisions with callback

    +
    +
    +

    Parameters

    + +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    check all bodies collisions in area with callback

    +
    +
    +

    Parameters

    + +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    check do 2 objects collide

    +
    +
    +

    Parameters

    + +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    check one body collisions with callback

    +
    +
    +

    Parameters

    + +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+ + + +
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + bbox: + any +
    • +
    +
    +

    + Returns boolean +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + a: + any +
    • +
    • + b: + any +
    • +
    +
    +

    + Returns number +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + a: + any +
    • +
    • + b: + any +
    • +
    +
    +

    + Returns number +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    create box at position with options and add to system

    +
    +
    +

    Parameters

    + +
    +

    + Returns + Box +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    + create circle at position with options and add to system +

    +
    +
    +

    Parameters

    + +
    +

    + Returns + Circle +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    + create ellipse at position with options and add to system +

    +
    +
    +

    Parameters

    +
      +
    • + position: + PotentialVector +
    • +
    • + radiusX: + number +
    • +
    • + radiusY: + number + = radiusX +
    • +
    • + Optionalstep: + number +
    • +
    • + Optionaloptions: + BodyOptions +
    • +
    +
    +

    + Returns + Ellipse +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    + create line at position with options and add to system +

    +
    +
    +

    Parameters

    + +
    +

    + Returns + Line +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    + create point at position with options and add to system +

    +
    +
    +

    Parameters

    + +
    +

    + Returns + Point +

    +
    + +
  • +
+
+
+ + + +
+
+ + +
    + +
  • +
    +

    draw exact bodies colliders outline

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    draw bounding boxes hierarchy outline

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + + +
+
+ + +
    + +
  • +
    +

    get object potential colliders

    +
    +
    +

    Parameters

    + +
    +

    + Returns + TBody[] +

    +
    + +

    + because it's slower to use than checkOne() or checkAll() +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    + re-insert body into collision tree and update its bbox + every body can be part of only one system +

    +
    +
    +

    Parameters

    + +
    +

    + Returns this +

    +
    + +
  • +
+
+
+ + + +
+
+ + +
    + +
  • +
    +

    raycast to get collider of ray from start to end

    +
    +
    +

    Parameters

    +
      +
    • + start: + Vector +
    • +
    • + end: + Vector +
    • +
    • + allow: + ((body: TBody, ray: TBody) => boolean) + = returnTrue +
        +
      • +
          +
        • + (body, + ray): boolean +
        • +
        • +
          +

          + Parameters +

          + +
          +

          + Returns + boolean +

          +
        • +
        +
      • +
      +
    • +
    +
    +

    + Returns null | RaycastHit<TBody> +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    remove body aabb from collision tree

    +
    +
    +

    Parameters

    + +
    +

    + Returns this +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + bbox: + any +
    • +
    +
    +

    + Returns any[] +

    + +
  • +
+
+
+ + +
    + +
  • +
    +

    separate (move away) bodies

    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    separate (move away) 1 body

    +
    +
    +

    Parameters

    + +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    Parameters

    +
      +
    • + item: + any +
    • +
    +
    +

    + Returns any +

    + +
  • +
+
+
+ + + +
+
+ + +
    + +
  • +
    +

    + used to find body deep inside data with finder function + returning boolean found or not +

    +
    +
    +

    Parameters

    + +
    +

    + Returns undefined | TBody +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update all bodies aabb

    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    updates body in collision tree

    +
    +
    +

    Parameters

    + +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+
+ +
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/demo/canvas.d.ts b/docs/demo/canvas.d.ts index 45b0fe39..7d48b0a6 100644 --- a/docs/demo/canvas.d.ts +++ b/docs/demo/canvas.d.ts @@ -1,14 +1,14 @@ export class TestCanvas { - constructor(test: any); - test: any; - element: HTMLDivElement; - canvas: HTMLCanvasElement; - context: CanvasRenderingContext2D | null; - bvhCheckbox: Element | null; - fps: number; - frame: number; - started: number; - update(): void; + constructor(test: any); + test: any; + element: HTMLDivElement; + canvas: HTMLCanvasElement; + context: CanvasRenderingContext2D | null; + bvhCheckbox: Element | null; + fps: number; + frame: number; + started: number; + update(): void; } export function loop(callback: any): void; export const width: number; diff --git a/docs/demo/canvas.js b/docs/demo/canvas.js index a9b0263a..a161d560 100644 --- a/docs/demo/canvas.js +++ b/docs/demo/canvas.js @@ -2,67 +2,71 @@ const width = window.innerWidth || 1024; const height = window.innerHeight || 768; class TestCanvas { - constructor(test) { - this.test = test; - this.element = document.createElement("div"); - this.element.id = "debug"; - this.element.innerHTML = `${this.test.legend} + constructor(test) { + this.test = test; + this.element = document.createElement("div"); + this.element.id = "debug"; + this.element.innerHTML = `${this.test.legend}
`; - this.canvas = document.createElement("canvas"); - this.canvas.width = width; - this.canvas.height = height; - this.context = this.canvas.getContext("2d"); - this.context.font = "24px Arial"; - this.test.context = this.context; - this.bvhCheckbox = this.element.querySelector("#bvh"); - if (this.canvas instanceof Node) { - this.element.appendChild(this.canvas); - } - this.fps = 0; - this.frame = 0; - this.started = Date.now(); - loop(this.update.bind(this)); + this.canvas = document.createElement("canvas"); + this.canvas.width = width; + this.canvas.height = height; + this.context = this.canvas.getContext("2d"); + this.context.font = "24px Arial"; + this.test.context = this.context; + this.bvhCheckbox = this.element.querySelector("#bvh"); + if (this.canvas instanceof Node) { + this.element.appendChild(this.canvas); } - update() { - this.frame++; - const timeDiff = Date.now() - this.started; - if (timeDiff >= 1000) { - this.fps = this.frame / (timeDiff / 1000); - this.frame = 0; - this.started = Date.now(); - } - // Clear the canvas - this.context.fillStyle = "#000000"; - this.context.fillRect(0, 0, width, height); - // Render the bodies - this.context.strokeStyle = "#FFFFFF"; - this.context.beginPath(); - this.test.physics.draw(this.context); - this.context.stroke(); - // Render the BVH - if (this.bvhCheckbox.checked) { - this.context.strokeStyle = "#00FF00"; - this.context.beginPath(); - this.test.physics.drawBVH(this.context); - this.context.stroke(); - } - // Render the FPS - this.context.fillStyle = "#FFCC00"; - this.context.fillText(`FPS: ${this.fps ? this.fps.toFixed(0) : "?"}`, 24, 48); - if (this.test.drawCallback) { - this.test.drawCallback(); - } + this.fps = 0; + this.frame = 0; + this.started = Date.now(); + loop(this.update.bind(this)); + } + update() { + this.frame++; + const timeDiff = Date.now() - this.started; + if (timeDiff >= 1000) { + this.fps = this.frame / (timeDiff / 1000); + this.frame = 0; + this.started = Date.now(); } + // Clear the canvas + this.context.fillStyle = "#000000"; + this.context.fillRect(0, 0, width, height); + // Render the bodies + this.context.strokeStyle = "#FFFFFF"; + this.context.beginPath(); + this.test.physics.draw(this.context); + this.context.stroke(); + // Render the BVH + if (this.bvhCheckbox.checked) { + this.context.strokeStyle = "#00FF00"; + this.context.beginPath(); + this.test.physics.drawBVH(this.context); + this.context.stroke(); + } + // Render the FPS + this.context.fillStyle = "#FFCC00"; + this.context.fillText( + `FPS: ${this.fps ? this.fps.toFixed(0) : "?"}`, + 24, + 48, + ); + if (this.test.drawCallback) { + this.test.drawCallback(); + } + } } function loop(callback) { - // interval for fps instead of setTimeout - // and ms = 1 which is lowest nonzero value - // for responsiveness of user input - setInterval(callback, 1); + // interval for fps instead of setTimeout + // and ms = 1 which is lowest nonzero value + // for responsiveness of user input + setInterval(callback, 1); } module.exports.TestCanvas = TestCanvas; module.exports.loop = loop; diff --git a/docs/demo/demo.js b/docs/demo/demo.js index c0bad769..9e236453 100644 --- a/docs/demo/demo.js +++ b/docs/demo/demo.js @@ -1,722 +1,902 @@ -/******/ (() => { // webpackBootstrap -/******/ var __webpack_modules__ = ({ - -/***/ "./node_modules/json-stringify-safe/stringify.js": -/*!*******************************************************!*\ +/******/ (() => { + // webpackBootstrap + /******/ var __webpack_modules__ = { + /***/ "./node_modules/json-stringify-safe/stringify.js": + /*!*******************************************************!*\ !*** ./node_modules/json-stringify-safe/stringify.js ***! \*******************************************************/ -/***/ ((module, exports) => { + /***/ (module, exports) => { + exports = module.exports = stringify; + exports.getSerialize = serializer; + + function stringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify( + obj, + serializer(replacer, cycleReplacer), + spaces, + ); + } -exports = module.exports = stringify -exports.getSerialize = serializer + function serializer(replacer, cycleReplacer) { + var stack = [], + keys = []; + + if (cycleReplacer == null) + cycleReplacer = function (key, value) { + if (stack[0] === value) return "[Circular ~]"; + return ( + "[Circular ~." + + keys.slice(0, stack.indexOf(value)).join(".") + + "]" + ); + }; + + return function (key, value) { + if (stack.length > 0) { + var thisPos = stack.indexOf(this); + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this); + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); + if (~stack.indexOf(value)) + value = cycleReplacer.call(this, key, value); + } else stack.push(value); + + return replacer == null ? value : replacer.call(this, key, value); + }; + } -function stringify(obj, replacer, spaces, cycleReplacer) { - return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) -} + /***/ + }, -function serializer(replacer, cycleReplacer) { - var stack = [], keys = [] + /***/ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js": + /*!************************************************************!*\ + !*** ./node_modules/poly-decomp-es/dist/poly-decomp-es.js ***! + \************************************************************/ + /***/ ( + __unused_webpack_module, + __webpack_exports__, + __webpack_require__, + ) => { + "use strict"; + __webpack_require__.r(__webpack_exports__); + /* harmony export */ __webpack_require__.d(__webpack_exports__, { + /* harmony export */ decomp: () => /* binding */ decomp, + /* harmony export */ isSimple: () => /* binding */ isSimple, + /* harmony export */ makeCCW: () => /* binding */ makeCCW, + /* harmony export */ quickDecomp: () => /* binding */ quickDecomp, + /* harmony export */ removeCollinearPoints: () => + /* binding */ removeCollinearPoints, + /* harmony export */ removeDuplicatePoints: () => + /* binding */ removeDuplicatePoints, + /* harmony export */ + }); + const tmpPoint1 = [0, 0]; + const tmpPoint2 = [0, 0]; + const tmpLine1 = [ + [0, 0], + [0, 0], + ]; + const tmpLine2 = [ + [0, 0], + [0, 0], + ]; - if (cycleReplacer == null) cycleReplacer = function(key, value) { - if (stack[0] === value) return "[Circular ~]" - return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]" - } + /** + * Compute the intersection between two lines. + * @param l1 Line vector 1 + * @param l2 Line vector 2 + * @param precision Precision to use when checking if the lines are parallel + * @return The intersection point. + */ + function lineInt(l1, l2, precision) { + if (precision === void 0) { + precision = 0; + } + precision = precision || 0; + const i = [0, 0]; // point + const a1 = l1[1][1] - l1[0][1]; + const b1 = l1[0][0] - l1[1][0]; + const c1 = a1 * l1[0][0] + b1 * l1[0][1]; + const a2 = l2[1][1] - l2[0][1]; + const b2 = l2[0][0] - l2[1][0]; + const c2 = a2 * l2[0][0] + b2 * l2[0][1]; + const det = a1 * b2 - a2 * b1; + if (!scalarsEqual(det, 0, precision)) { + // lines are not parallel + i[0] = (b2 * c1 - b1 * c2) / det; + i[1] = (a1 * c2 - a2 * c1) / det; + } + return i; + } - return function(key, value) { - if (stack.length > 0) { - var thisPos = stack.indexOf(this) - ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) - ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) - if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value) - } - else stack.push(value) + /** + * Checks if two line segments intersects. + * @param p1 The start vertex of the first line segment. + * @param p2 The end vertex of the first line segment. + * @param q1 The start vertex of the second line segment. + * @param q2 The end vertex of the second line segment. + * @return True if the two line segments intersect + */ + function lineSegmentsIntersect(p1, p2, q1, q2) { + const dx = p2[0] - p1[0]; + const dy = p2[1] - p1[1]; + const da = q2[0] - q1[0]; + const db = q2[1] - q1[1]; + + // segments are parallel + if (da * dy - db * dx === 0) { + return false; + } + const s = + (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx); + const t = + (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy); + return s >= 0 && s <= 1 && t >= 0 && t <= 1; + } - return replacer == null ? value : replacer.call(this, key, value) - } -} + /** + * 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. + * @param a point 1 + * @param b point 2 + * @param c point 3 + * @return the area of a triangle spanned by the three given points + */ + function triangleArea(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]); + } + function isLeft(a, b, c) { + return triangleArea(a, b, c) > 0; + } + function isLeftOn(a, b, c) { + return triangleArea(a, b, c) >= 0; + } + function isRight(a, b, c) { + return triangleArea(a, b, c) < 0; + } + function isRightOn(a, b, c) { + return triangleArea(a, b, c) <= 0; + } + /** + * Check if three points are collinear + * @param a point 1 + * @param b point 2 + * @param c point 3 + * @param thresholdAngle 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. + * @return whether the points are collinear + */ + function collinear(a, b, c, thresholdAngle) { + if (thresholdAngle === void 0) { + thresholdAngle = 0; + } + if (!thresholdAngle) { + return triangleArea(a, b, c) === 0; + } else { + const ab = tmpPoint1; + const bc = tmpPoint2; + ab[0] = b[0] - a[0]; + ab[1] = b[1] - a[1]; + bc[0] = c[0] - b[0]; + bc[1] = c[1] - b[1]; + const dot = ab[0] * bc[0] + ab[1] * bc[1]; + const magA = Math.sqrt(ab[0] * ab[0] + ab[1] * ab[1]); + const magB = Math.sqrt(bc[0] * bc[0] + bc[1] * bc[1]); + const angle = Math.acos(dot / (magA * magB)); + return angle < thresholdAngle; + } + } + function sqdist(a, b) { + const dx = b[0] - a[0]; + const dy = b[1] - a[1]; + return dx * dx + dy * dy; + } -/***/ }), + /** + * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle. + * @param i vertex position + * @return vertex at position i + */ + function polygonAt(polygon, i) { + const s = polygon.length; + return polygon[i < 0 ? (i % s) + s : i % s]; + } -/***/ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js": -/*!************************************************************!*\ - !*** ./node_modules/poly-decomp-es/dist/poly-decomp-es.js ***! - \************************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ decomp: () => (/* binding */ decomp), -/* harmony export */ isSimple: () => (/* binding */ isSimple), -/* harmony export */ makeCCW: () => (/* binding */ makeCCW), -/* harmony export */ quickDecomp: () => (/* binding */ quickDecomp), -/* harmony export */ removeCollinearPoints: () => (/* binding */ removeCollinearPoints), -/* harmony export */ removeDuplicatePoints: () => (/* binding */ removeDuplicatePoints) -/* harmony export */ }); -const tmpPoint1 = [0, 0]; -const tmpPoint2 = [0, 0]; -const tmpLine1 = [[0, 0], [0, 0]]; -const tmpLine2 = [[0, 0], [0, 0]]; - -/** - * Compute the intersection between two lines. - * @param l1 Line vector 1 - * @param l2 Line vector 2 - * @param precision Precision to use when checking if the lines are parallel - * @return The intersection point. - */ -function lineInt(l1, l2, precision) { - if (precision === void 0) { - precision = 0; - } - precision = precision || 0; - const i = [0, 0]; // point - const a1 = l1[1][1] - l1[0][1]; - const b1 = l1[0][0] - l1[1][0]; - const c1 = a1 * l1[0][0] + b1 * l1[0][1]; - const a2 = l2[1][1] - l2[0][1]; - const b2 = l2[0][0] - l2[1][0]; - const c2 = a2 * l2[0][0] + b2 * l2[0][1]; - const det = a1 * b2 - a2 * b1; - if (!scalarsEqual(det, 0, precision)) { - // lines are not parallel - i[0] = (b2 * c1 - b1 * c2) / det; - i[1] = (a1 * c2 - a2 * c1) / det; - } - return i; -} - -/** - * Checks if two line segments intersects. - * @param p1 The start vertex of the first line segment. - * @param p2 The end vertex of the first line segment. - * @param q1 The start vertex of the second line segment. - * @param q2 The end vertex of the second line segment. - * @return True if the two line segments intersect - */ -function lineSegmentsIntersect(p1, p2, q1, q2) { - const dx = p2[0] - p1[0]; - const dy = p2[1] - p1[1]; - const da = q2[0] - q1[0]; - const db = q2[1] - q1[1]; - - // segments are parallel - if (da * dy - db * dx === 0) { - return false; - } - const s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx); - const t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy); - return s >= 0 && s <= 1 && t >= 0 && t <= 1; -} - -/** - * 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. - * @param a point 1 - * @param b point 2 - * @param c point 3 - * @return the area of a triangle spanned by the three given points - */ -function triangleArea(a, b, c) { - return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]); -} -function isLeft(a, b, c) { - return triangleArea(a, b, c) > 0; -} -function isLeftOn(a, b, c) { - return triangleArea(a, b, c) >= 0; -} -function isRight(a, b, c) { - return triangleArea(a, b, c) < 0; -} -function isRightOn(a, b, c) { - return triangleArea(a, b, c) <= 0; -} - -/** - * Check if three points are collinear - * @param a point 1 - * @param b point 2 - * @param c point 3 - * @param thresholdAngle 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. - * @return whether the points are collinear - */ -function collinear(a, b, c, thresholdAngle) { - if (thresholdAngle === void 0) { - thresholdAngle = 0; - } - if (!thresholdAngle) { - return triangleArea(a, b, c) === 0; - } else { - const ab = tmpPoint1; - const bc = tmpPoint2; - ab[0] = b[0] - a[0]; - ab[1] = b[1] - a[1]; - bc[0] = c[0] - b[0]; - bc[1] = c[1] - b[1]; - const dot = ab[0] * bc[0] + ab[1] * bc[1]; - const magA = Math.sqrt(ab[0] * ab[0] + ab[1] * ab[1]); - const magB = Math.sqrt(bc[0] * bc[0] + bc[1] * bc[1]); - const angle = Math.acos(dot / (magA * magB)); - return angle < thresholdAngle; - } -} -function sqdist(a, b) { - const dx = b[0] - a[0]; - const dy = b[1] - a[1]; - return dx * dx + dy * dy; -} - -/** - * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle. - * @param i vertex position - * @return vertex at position i - */ -function polygonAt(polygon, i) { - const s = polygon.length; - return polygon[i < 0 ? i % s + s : i % s]; -} - -/** - * Clear the polygon data - */ -function polygonClear(polygon) { - polygon.length = 0; -} - -/** - * Append points "from" to "to" -1 from an other polygon "poly" onto this one. - * @param polygon the polygon to append to - * @param poly The polygon to get points from. - * @param from The vertex index in "poly". - * @param to The end vertex index in "poly". Note that this vertex is NOT included when appending. - */ -function polygonAppend(polygon, poly, from, to) { - for (let i = from; i < to; i++) { - polygon.push(poly[i]); - } -} - -/** - * Make sure that the polygon vertices are ordered counter-clockwise. - */ -function makeCCW(polygon) { - let br = 0; - const v = polygon; - - // find bottom right point - for (let i = 1; i < polygon.length; ++i) { - if (v[i][1] < v[br][1] || v[i][1] === v[br][1] && v[i][0] > v[br][0]) { - br = i; - } - } + /** + * Clear the polygon data + */ + function polygonClear(polygon) { + polygon.length = 0; + } - // reverse poly if clockwise - if (!isLeft(polygonAt(polygon, br - 1), polygonAt(polygon, br), polygonAt(polygon, br + 1))) { - polygonReverse(polygon); - return true; - } else { - return false; - } -} - -/** - * Reverse the vertices in the polygon - */ -function polygonReverse(polygon) { - const tmp = []; - const N = polygon.length; - for (let i = 0; i !== N; i++) { - tmp.push(polygon.pop()); - } - for (let i = 0; i !== N; i++) { - polygon[i] = tmp[i]; - } -} - -/** - * Check if a point in the polygon is a reflex point - * @param i the point in the polygon to check - * @return whether the given point in the polygon is a reflex point - */ -function polygonIsReflex(polygon, i) { - return isRight(polygonAt(polygon, i - 1), polygonAt(polygon, i), polygonAt(polygon, i + 1)); -} - -/** - * Check if two vertices in the polygon can see each other - * @param a vertex index 1 - * @param b vertex index 2 - * @return whether two vertices in the polygon can see each other - */ -function polygonCanSee(polygon, a, b) { - const l1 = tmpLine1; - const l2 = tmpLine2; - if (isLeftOn(polygonAt(polygon, a + 1), polygonAt(polygon, a), polygonAt(polygon, b)) && isRightOn(polygonAt(polygon, a - 1), polygonAt(polygon, a), polygonAt(polygon, b))) { - return false; - } - const dist = sqdist(polygonAt(polygon, a), polygonAt(polygon, b)); - for (let i = 0; i !== polygon.length; ++i) { - // for each edge - if ((i + 1) % polygon.length === a || i === a) { - // ignore incident edges - continue; - } - 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 - l1[0] = polygonAt(polygon, a); - l1[1] = polygonAt(polygon, b); - l2[0] = polygonAt(polygon, i); - l2[1] = polygonAt(polygon, i + 1); - const p = lineInt(l1, l2); - if (sqdist(polygonAt(polygon, a), p) < dist) { - // if edge is blocking visibility to b - return false; - } - } - } - return true; -} - -/** - * Check if two vertices in the polygon can see each other - * @param a vertex index 1 - * @param b vertex index 2 - * @return if two vertices in the polygon can see each other - */ -function polygonCanSee2(polygon, a, b) { - // for each edge - for (let i = 0; i !== polygon.length; ++i) { - // ignore incident edges - if (i === a || i === b || (i + 1) % polygon.length === a || (i + 1) % polygon.length === b) { - continue; - } - if (lineSegmentsIntersect(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i), polygonAt(polygon, i + 1))) { - return false; - } - } - return true; -} - -/** - * Copy the polygon from vertex i to vertex j. - * @param i the start vertex to copy from - * @param j the end vertex to copy from - * @param targetPoly optional target polygon to save in. - * @return the resulting copy. - */ -function polygonCopy(polygon, i, j, targetPoly) { - if (targetPoly === void 0) { - targetPoly = []; - } - polygonClear(targetPoly); - if (i < j) { - // Insert all vertices from i to j - for (let k = i; k <= j; k++) { - targetPoly.push(polygon[k]); - } - } else { - // Insert vertices 0 to j - for (let k = 0; k <= j; k++) { - targetPoly.push(polygon[k]); - } + /** + * Append points "from" to "to" -1 from an other polygon "poly" onto this one. + * @param polygon the polygon to append to + * @param poly The polygon to get points from. + * @param from The vertex index in "poly". + * @param to The end vertex index in "poly". Note that this vertex is NOT included when appending. + */ + function polygonAppend(polygon, poly, from, to) { + for (let i = from; i < to; i++) { + polygon.push(poly[i]); + } + } - // Insert vertices i to end - for (let k = i; k < polygon.length; k++) { - targetPoly.push(polygon[k]); - } - } - return targetPoly; -} - -/** - * Decomposes the polygon into convex pieces. Returns a list of edges [[p1,p2],[p2,p3],...] that cuts the polygon. - * Note that this algorithm has complexity O(N^4) and will be very slow for polygons with many vertices. - * @return a list of edges that cuts the polygon - */ -function getCutEdges(polygon) { - let min = []; - let tmp1; - let tmp2; - const tmpPoly = []; - let nDiags = Number.MAX_VALUE; - for (let i = 0; i < polygon.length; ++i) { - if (polygonIsReflex(polygon, i)) { - for (let j = 0; j < polygon.length; ++j) { - if (polygonCanSee(polygon, i, j)) { - tmp1 = getCutEdges(polygonCopy(polygon, i, j, tmpPoly)); - tmp2 = getCutEdges(polygonCopy(polygon, j, i, tmpPoly)); - for (let k = 0; k < tmp2.length; k++) { - tmp1.push(tmp2[k]); - } - if (tmp1.length < nDiags) { - min = tmp1; - nDiags = tmp1.length; - min.push([polygonAt(polygon, i), polygonAt(polygon, j)]); + /** + * Make sure that the polygon vertices are ordered counter-clockwise. + */ + function makeCCW(polygon) { + let br = 0; + const v = polygon; + + // find bottom right point + for (let i = 1; i < polygon.length; ++i) { + if ( + v[i][1] < v[br][1] || + (v[i][1] === v[br][1] && v[i][0] > v[br][0]) + ) { + br = i; + } + } + + // reverse poly if clockwise + if ( + !isLeft( + polygonAt(polygon, br - 1), + polygonAt(polygon, br), + polygonAt(polygon, br + 1), + ) + ) { + polygonReverse(polygon); + return true; + } else { + return false; } } - } - } - } - return min; -} - -/** - * Decomposes the polygon into one or more convex sub-Polygons. - * @return An array of Polygon objects, or false if decomposition fails - */ -function decomp(polygon) { - const edges = getCutEdges(polygon); - if (edges.length > 0) { - return slicePolygon(polygon, edges); - } else { - return [polygon]; - } -} - -/** - * 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. - * @param cutEdges A list of edges, as returned by .getCutEdges() - * @return the sliced polygons, or false if the operation was unsuccessful - */ -function slicePolygon(polygon, cutEdges) { - if (cutEdges.length === 0) { - return [polygon]; - } - // if given multiple edges - if (cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length === 2 && cutEdges[0][0] instanceof Array) { - const polys = [polygon]; - for (let i = 0; i < cutEdges.length; i++) { - const cutEdge = cutEdges[i]; - // Cut all polys - for (let j = 0; j < polys.length; j++) { - const poly = polys[j]; - const result = slicePolygon(poly, cutEdge); - if (result) { - // Found poly! Cut and quit - polys.splice(j, 1); - polys.push(result[0], result[1]); - break; + /** + * Reverse the vertices in the polygon + */ + function polygonReverse(polygon) { + const tmp = []; + const N = polygon.length; + for (let i = 0; i !== N; i++) { + tmp.push(polygon.pop()); + } + for (let i = 0; i !== N; i++) { + polygon[i] = tmp[i]; + } } - } - } - return polys; - } else { - // Was given one edge - const cutEdge = cutEdges; - const i = polygon.indexOf(cutEdge[0]); - const j = polygon.indexOf(cutEdge[1]); - if (i !== -1 && j !== -1) { - return [polygonCopy(polygon, i, j), polygonCopy(polygon, j, i)]; - } else { - return false; - } - } -} - -/** - * Checks that the line segments of this polygon do not intersect each other. - * @param polygon An array of vertices e.g. [[0,0],[0,1],...] - * @return whether line segments of this polygon do not intersect each other. - * @todo Should it check all segments with all others? - */ -function isSimple(polygon) { - const path = polygon; - let i; - - // Check - for (i = 0; i < path.length - 1; i++) { - for (let j = 0; j < i - 1; j++) { - if (lineSegmentsIntersect(path[i], path[i + 1], path[j], path[j + 1])) { - return false; - } - } - } - // Check the segment between the last and the first point to all others - for (i = 1; i < path.length - 2; i++) { - if (lineSegmentsIntersect(path[0], path[path.length - 1], path[i], path[i + 1])) { - return false; - } - } - return true; -} -function getIntersectionPoint(p1, p2, q1, q2, delta) { - if (delta === void 0) { - delta = 0; - } - const a1 = p2[1] - p1[1]; - const b1 = p1[0] - p2[0]; - const c1 = a1 * p1[0] + b1 * p1[1]; - const a2 = q2[1] - q1[1]; - const b2 = q1[0] - q2[0]; - const c2 = a2 * q1[0] + b2 * q1[1]; - const det = a1 * b2 - a2 * b1; - if (!scalarsEqual(det, 0, delta)) { - return [(b2 * c1 - b1 * c2) / det, (a1 * c2 - a2 * c1) / det]; - } else { - return [0, 0]; - } -} - -/** - * Quickly decompose the Polygon into convex sub-polygons. - * @param polygon the polygon to decompose - * @param result - * @param reflexVertices - * @param steinerPoints - * @param delta - * @param maxlevel - * @param level - * @return the decomposed sub-polygons - */ -function quickDecomp(polygon, result, reflexVertices, steinerPoints, delta, maxlevel, level) { - if (result === void 0) { - result = []; - } - if (reflexVertices === void 0) { - reflexVertices = []; - } - if (steinerPoints === void 0) { - steinerPoints = []; - } - if (delta === void 0) { - delta = 25; - } - if (maxlevel === void 0) { - maxlevel = 100; - } - if (level === void 0) { - level = 0; - } - // Points - let upperInt = [0, 0]; - let lowerInt = [0, 0]; - let p = [0, 0]; - - // scalars - let upperDist = 0; - let lowerDist = 0; - let d = 0; - let closestDist = 0; - - // Integers - let upperIndex = 0; - let lowerIndex = 0; - let closestIndex = 0; - - // polygons - const lowerPoly = []; - const upperPoly = []; - const poly = polygon; - const v = polygon; - if (v.length < 3) { - return result; - } - level++; - if (level > maxlevel) { - console.warn('quickDecomp: max level (' + maxlevel + ') reached.'); - return result; - } - for (let i = 0; i < polygon.length; ++i) { - if (polygonIsReflex(poly, i)) { - reflexVertices.push(poly[i]); - upperDist = lowerDist = Number.MAX_VALUE; - for (let j = 0; j < polygon.length; ++j) { - 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 - p = getIntersectionPoint(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j - 1)); // find the point of intersection - if (isRight(polygonAt(poly, i + 1), polygonAt(poly, i), p)) { - // make sure it's inside the poly - d = sqdist(poly[i], p); - if (d < lowerDist) { - // keep only the closest intersection - lowerDist = d; - lowerInt = p; - lowerIndex = j; + /** + * Check if a point in the polygon is a reflex point + * @param i the point in the polygon to check + * @return whether the given point in the polygon is a reflex point + */ + function polygonIsReflex(polygon, i) { + return isRight( + polygonAt(polygon, i - 1), + polygonAt(polygon, i), + polygonAt(polygon, i + 1), + ); + } + + /** + * Check if two vertices in the polygon can see each other + * @param a vertex index 1 + * @param b vertex index 2 + * @return whether two vertices in the polygon can see each other + */ + function polygonCanSee(polygon, a, b) { + const l1 = tmpLine1; + const l2 = tmpLine2; + if ( + isLeftOn( + polygonAt(polygon, a + 1), + polygonAt(polygon, a), + polygonAt(polygon, b), + ) && + isRightOn( + polygonAt(polygon, a - 1), + polygonAt(polygon, a), + polygonAt(polygon, b), + ) + ) { + return false; + } + const dist = sqdist(polygonAt(polygon, a), polygonAt(polygon, b)); + for (let i = 0; i !== polygon.length; ++i) { + // for each edge + if ((i + 1) % polygon.length === a || i === a) { + // ignore incident edges + continue; + } + 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 + l1[0] = polygonAt(polygon, a); + l1[1] = polygonAt(polygon, b); + l2[0] = polygonAt(polygon, i); + l2[1] = polygonAt(polygon, i + 1); + const p = lineInt(l1, l2); + if (sqdist(polygonAt(polygon, a), p) < dist) { + // if edge is blocking visibility to b + return false; + } } } + return true; } - if (isLeft(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j + 1)) && isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))) { - p = getIntersectionPoint(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j + 1)); - if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), p)) { - d = sqdist(poly[i], p); - if (d < upperDist) { - upperDist = d; - upperInt = p; - upperIndex = j; + + /** + * Check if two vertices in the polygon can see each other + * @param a vertex index 1 + * @param b vertex index 2 + * @return if two vertices in the polygon can see each other + */ + function polygonCanSee2(polygon, a, b) { + // for each edge + for (let i = 0; i !== polygon.length; ++i) { + // ignore incident edges + if ( + i === a || + i === b || + (i + 1) % polygon.length === a || + (i + 1) % polygon.length === b + ) { + continue; + } + if ( + lineSegmentsIntersect( + polygonAt(polygon, a), + polygonAt(polygon, b), + polygonAt(polygon, i), + polygonAt(polygon, i + 1), + ) + ) { + return false; } } + return true; + } + + /** + * Copy the polygon from vertex i to vertex j. + * @param i the start vertex to copy from + * @param j the end vertex to copy from + * @param targetPoly optional target polygon to save in. + * @return the resulting copy. + */ + function polygonCopy(polygon, i, j, targetPoly) { + if (targetPoly === void 0) { + targetPoly = []; + } + polygonClear(targetPoly); + if (i < j) { + // Insert all vertices from i to j + for (let k = i; k <= j; k++) { + targetPoly.push(polygon[k]); + } + } else { + // Insert vertices 0 to j + for (let k = 0; k <= j; k++) { + targetPoly.push(polygon[k]); + } + + // Insert vertices i to end + for (let k = i; k < polygon.length; k++) { + targetPoly.push(polygon[k]); + } + } + return targetPoly; } - } - // if there are no vertices to connect to, choose a point in the middle - if (lowerIndex === (upperIndex + 1) % polygon.length) { - p[0] = (lowerInt[0] + upperInt[0]) / 2; - p[1] = (lowerInt[1] + upperInt[1]) / 2; - steinerPoints.push(p); - if (i < upperIndex) { - polygonAppend(lowerPoly, poly, i, upperIndex + 1); - lowerPoly.push(p); - upperPoly.push(p); - if (lowerIndex !== 0) { - polygonAppend(upperPoly, poly, lowerIndex, poly.length); - } - polygonAppend(upperPoly, poly, 0, i + 1); - } else { - if (i !== 0) { - polygonAppend(lowerPoly, poly, i, poly.length); - } - polygonAppend(lowerPoly, poly, 0, upperIndex + 1); - lowerPoly.push(p); - upperPoly.push(p); - polygonAppend(upperPoly, poly, lowerIndex, i + 1); + /** + * Decomposes the polygon into convex pieces. Returns a list of edges [[p1,p2],[p2,p3],...] that cuts the polygon. + * Note that this algorithm has complexity O(N^4) and will be very slow for polygons with many vertices. + * @return a list of edges that cuts the polygon + */ + function getCutEdges(polygon) { + let min = []; + let tmp1; + let tmp2; + const tmpPoly = []; + let nDiags = Number.MAX_VALUE; + for (let i = 0; i < polygon.length; ++i) { + if (polygonIsReflex(polygon, i)) { + for (let j = 0; j < polygon.length; ++j) { + if (polygonCanSee(polygon, i, j)) { + tmp1 = getCutEdges(polygonCopy(polygon, i, j, tmpPoly)); + tmp2 = getCutEdges(polygonCopy(polygon, j, i, tmpPoly)); + for (let k = 0; k < tmp2.length; k++) { + tmp1.push(tmp2[k]); + } + if (tmp1.length < nDiags) { + min = tmp1; + nDiags = tmp1.length; + min.push([polygonAt(polygon, i), polygonAt(polygon, j)]); + } + } + } + } + } + return min; } - } else { - // connect to the closest point within the triangle - if (lowerIndex > upperIndex) { - upperIndex += polygon.length; + + /** + * Decomposes the polygon into one or more convex sub-Polygons. + * @return An array of Polygon objects, or false if decomposition fails + */ + function decomp(polygon) { + const edges = getCutEdges(polygon); + if (edges.length > 0) { + return slicePolygon(polygon, edges); + } else { + return [polygon]; + } } - closestDist = Number.MAX_VALUE; - if (upperIndex < lowerIndex) { - return result; + + /** + * 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. + * @param cutEdges A list of edges, as returned by .getCutEdges() + * @return the sliced polygons, or false if the operation was unsuccessful + */ + function slicePolygon(polygon, cutEdges) { + if (cutEdges.length === 0) { + return [polygon]; + } + + // if given multiple edges + if ( + cutEdges instanceof Array && + cutEdges.length && + cutEdges[0] instanceof Array && + cutEdges[0].length === 2 && + cutEdges[0][0] instanceof Array + ) { + const polys = [polygon]; + for (let i = 0; i < cutEdges.length; i++) { + const cutEdge = cutEdges[i]; + // Cut all polys + for (let j = 0; j < polys.length; j++) { + const poly = polys[j]; + const result = slicePolygon(poly, cutEdge); + if (result) { + // Found poly! Cut and quit + polys.splice(j, 1); + polys.push(result[0], result[1]); + break; + } + } + } + return polys; + } else { + // Was given one edge + const cutEdge = cutEdges; + const i = polygon.indexOf(cutEdge[0]); + const j = polygon.indexOf(cutEdge[1]); + if (i !== -1 && j !== -1) { + return [polygonCopy(polygon, i, j), polygonCopy(polygon, j, i)]; + } else { + return false; + } + } } - for (let j = lowerIndex; j <= upperIndex; ++j) { - if (isLeftOn(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j)) && isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))) { - d = sqdist(polygonAt(poly, i), polygonAt(poly, j)); - if (d < closestDist && polygonCanSee2(poly, i, j)) { - closestDist = d; - closestIndex = j % polygon.length; + + /** + * Checks that the line segments of this polygon do not intersect each other. + * @param polygon An array of vertices e.g. [[0,0],[0,1],...] + * @return whether line segments of this polygon do not intersect each other. + * @todo Should it check all segments with all others? + */ + function isSimple(polygon) { + const path = polygon; + let i; + + // Check + for (i = 0; i < path.length - 1; i++) { + for (let j = 0; j < i - 1; j++) { + if ( + lineSegmentsIntersect( + path[i], + path[i + 1], + path[j], + path[j + 1], + ) + ) { + return false; + } + } + } + + // Check the segment between the last and the first point to all others + for (i = 1; i < path.length - 2; i++) { + if ( + lineSegmentsIntersect( + path[0], + path[path.length - 1], + path[i], + path[i + 1], + ) + ) { + return false; } } + return true; } - if (i < closestIndex) { - polygonAppend(lowerPoly, poly, i, closestIndex + 1); - if (closestIndex !== 0) { - polygonAppend(upperPoly, poly, closestIndex, v.length); - } - polygonAppend(upperPoly, poly, 0, i + 1); - } else { - if (i !== 0) { - polygonAppend(lowerPoly, poly, i, v.length); - } - polygonAppend(lowerPoly, poly, 0, closestIndex + 1); - polygonAppend(upperPoly, poly, closestIndex, i + 1); + function getIntersectionPoint(p1, p2, q1, q2, delta) { + if (delta === void 0) { + delta = 0; + } + const a1 = p2[1] - p1[1]; + const b1 = p1[0] - p2[0]; + const c1 = a1 * p1[0] + b1 * p1[1]; + const a2 = q2[1] - q1[1]; + const b2 = q1[0] - q2[0]; + const c2 = a2 * q1[0] + b2 * q1[1]; + const det = a1 * b2 - a2 * b1; + if (!scalarsEqual(det, 0, delta)) { + return [(b2 * c1 - b1 * c2) / det, (a1 * c2 - a2 * c1) / det]; + } else { + return [0, 0]; + } } - } - // solve smallest poly first - if (lowerPoly.length < upperPoly.length) { - quickDecomp(lowerPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level); - quickDecomp(upperPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level); - } else { - quickDecomp(upperPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level); - quickDecomp(lowerPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level); - } - return result; - } - } - result.push(polygon); - return result; -} - -/** - * Remove collinear points in the polygon. - * @param thresholdAngle The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision. - * @return The number of points removed - */ -function removeCollinearPoints(polygon, thresholdAngle) { - if (thresholdAngle === void 0) { - thresholdAngle = 0; - } - let num = 0; - for (let i = polygon.length - 1; polygon.length > 3 && i >= 0; --i) { - if (collinear(polygonAt(polygon, i - 1), polygonAt(polygon, i), polygonAt(polygon, i + 1), thresholdAngle)) { - // Remove the middle point - polygon.splice(i % polygon.length, 1); - num++; - } - } - return num; -} - -/** - * Check if two scalars are equal - * @param a scalar a - * @param b scalar b - * @param precision the precision for the equality check - * @return whether the two scalars are equal with the given precision - */ -function scalarsEqual(a, b, precision) { - if (precision === void 0) { - precision = 0; - } - precision = precision || 0; - return Math.abs(a - b) <= precision; -} - -/** - * Check if two points are equal - * @param a point a - * @param b point b - * @param precision the precision for the equality check - * @return if the two points are equal - */ -function pointsEqual(a, b, precision) { - if (precision === void 0) { - precision = 0; - } - return scalarsEqual(a[0], b[0], precision) && scalarsEqual(a[1], b[1], precision); -} - -/** - * Remove duplicate points in the polygon. - * @param precision The threshold to use when determining whether two points are the same. Use zero for best precision. - */ -function removeDuplicatePoints(polygon, precision) { - if (precision === void 0) { - precision = 0; - } - for (let i = polygon.length - 1; i >= 1; --i) { - const pi = polygon[i]; - for (let j = i - 1; j >= 0; --j) { - if (pointsEqual(pi, polygon[j], precision)) { - polygon.splice(i, 1); - continue; - } - } - } -} + /** + * Quickly decompose the Polygon into convex sub-polygons. + * @param polygon the polygon to decompose + * @param result + * @param reflexVertices + * @param steinerPoints + * @param delta + * @param maxlevel + * @param level + * @return the decomposed sub-polygons + */ + function quickDecomp( + polygon, + result, + reflexVertices, + steinerPoints, + delta, + maxlevel, + level, + ) { + if (result === void 0) { + result = []; + } + if (reflexVertices === void 0) { + reflexVertices = []; + } + if (steinerPoints === void 0) { + steinerPoints = []; + } + if (delta === void 0) { + delta = 25; + } + if (maxlevel === void 0) { + maxlevel = 100; + } + if (level === void 0) { + level = 0; + } + // Points + let upperInt = [0, 0]; + let lowerInt = [0, 0]; + let p = [0, 0]; + + // scalars + let upperDist = 0; + let lowerDist = 0; + let d = 0; + let closestDist = 0; + + // Integers + let upperIndex = 0; + let lowerIndex = 0; + let closestIndex = 0; + + // polygons + const lowerPoly = []; + const upperPoly = []; + const poly = polygon; + const v = polygon; + if (v.length < 3) { + return result; + } + level++; + if (level > maxlevel) { + console.warn("quickDecomp: max level (" + maxlevel + ") reached."); + return result; + } + for (let i = 0; i < polygon.length; ++i) { + if (polygonIsReflex(poly, i)) { + reflexVertices.push(poly[i]); + upperDist = lowerDist = Number.MAX_VALUE; + for (let j = 0; j < polygon.length; ++j) { + 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 + p = getIntersectionPoint( + polygonAt(poly, i - 1), + polygonAt(poly, i), + polygonAt(poly, j), + polygonAt(poly, j - 1), + ); // find the point of intersection + if (isRight(polygonAt(poly, i + 1), polygonAt(poly, i), p)) { + // make sure it's inside the poly + d = sqdist(poly[i], p); + if (d < lowerDist) { + // keep only the closest intersection + lowerDist = d; + lowerInt = p; + lowerIndex = j; + } + } + } + if ( + isLeft( + polygonAt(poly, i + 1), + polygonAt(poly, i), + polygonAt(poly, j + 1), + ) && + isRightOn( + polygonAt(poly, i + 1), + polygonAt(poly, i), + polygonAt(poly, j), + ) + ) { + p = getIntersectionPoint( + polygonAt(poly, i + 1), + polygonAt(poly, i), + polygonAt(poly, j), + polygonAt(poly, j + 1), + ); + if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), p)) { + d = sqdist(poly[i], p); + if (d < upperDist) { + upperDist = d; + upperInt = p; + upperIndex = j; + } + } + } + } + + // if there are no vertices to connect to, choose a point in the middle + if (lowerIndex === (upperIndex + 1) % polygon.length) { + p[0] = (lowerInt[0] + upperInt[0]) / 2; + p[1] = (lowerInt[1] + upperInt[1]) / 2; + steinerPoints.push(p); + if (i < upperIndex) { + polygonAppend(lowerPoly, poly, i, upperIndex + 1); + lowerPoly.push(p); + upperPoly.push(p); + if (lowerIndex !== 0) { + polygonAppend(upperPoly, poly, lowerIndex, poly.length); + } + polygonAppend(upperPoly, poly, 0, i + 1); + } else { + if (i !== 0) { + polygonAppend(lowerPoly, poly, i, poly.length); + } + polygonAppend(lowerPoly, poly, 0, upperIndex + 1); + lowerPoly.push(p); + upperPoly.push(p); + polygonAppend(upperPoly, poly, lowerIndex, i + 1); + } + } else { + // connect to the closest point within the triangle + if (lowerIndex > upperIndex) { + upperIndex += polygon.length; + } + closestDist = Number.MAX_VALUE; + if (upperIndex < lowerIndex) { + return result; + } + for (let j = lowerIndex; j <= upperIndex; ++j) { + if ( + isLeftOn( + polygonAt(poly, i - 1), + polygonAt(poly, i), + polygonAt(poly, j), + ) && + isRightOn( + polygonAt(poly, i + 1), + polygonAt(poly, i), + polygonAt(poly, j), + ) + ) { + d = sqdist(polygonAt(poly, i), polygonAt(poly, j)); + if (d < closestDist && polygonCanSee2(poly, i, j)) { + closestDist = d; + closestIndex = j % polygon.length; + } + } + } + if (i < closestIndex) { + polygonAppend(lowerPoly, poly, i, closestIndex + 1); + if (closestIndex !== 0) { + polygonAppend(upperPoly, poly, closestIndex, v.length); + } + polygonAppend(upperPoly, poly, 0, i + 1); + } else { + if (i !== 0) { + polygonAppend(lowerPoly, poly, i, v.length); + } + polygonAppend(lowerPoly, poly, 0, closestIndex + 1); + polygonAppend(upperPoly, poly, closestIndex, i + 1); + } + } + + // solve smallest poly first + if (lowerPoly.length < upperPoly.length) { + quickDecomp( + lowerPoly, + result, + reflexVertices, + steinerPoints, + delta, + maxlevel, + level, + ); + quickDecomp( + upperPoly, + result, + reflexVertices, + steinerPoints, + delta, + maxlevel, + level, + ); + } else { + quickDecomp( + upperPoly, + result, + reflexVertices, + steinerPoints, + delta, + maxlevel, + level, + ); + quickDecomp( + lowerPoly, + result, + reflexVertices, + steinerPoints, + delta, + maxlevel, + level, + ); + } + return result; + } + } + result.push(polygon); + return result; + } + + /** + * Remove collinear points in the polygon. + * @param thresholdAngle The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision. + * @return The number of points removed + */ + function removeCollinearPoints(polygon, thresholdAngle) { + if (thresholdAngle === void 0) { + thresholdAngle = 0; + } + let num = 0; + for (let i = polygon.length - 1; polygon.length > 3 && i >= 0; --i) { + if ( + collinear( + polygonAt(polygon, i - 1), + polygonAt(polygon, i), + polygonAt(polygon, i + 1), + thresholdAngle, + ) + ) { + // Remove the middle point + polygon.splice(i % polygon.length, 1); + num++; + } + } + return num; + } + /** + * Check if two scalars are equal + * @param a scalar a + * @param b scalar b + * @param precision the precision for the equality check + * @return whether the two scalars are equal with the given precision + */ + function scalarsEqual(a, b, precision) { + if (precision === void 0) { + precision = 0; + } + precision = precision || 0; + return Math.abs(a - b) <= precision; + } + /** + * Check if two points are equal + * @param a point a + * @param b point b + * @param precision the precision for the equality check + * @return if the two points are equal + */ + function pointsEqual(a, b, precision) { + if (precision === void 0) { + precision = 0; + } + return ( + scalarsEqual(a[0], b[0], precision) && + scalarsEqual(a[1], b[1], precision) + ); + } + /** + * Remove duplicate points in the polygon. + * @param precision The threshold to use when determining whether two points are the same. Use zero for best precision. + */ + function removeDuplicatePoints(polygon, precision) { + if (precision === void 0) { + precision = 0; + } + for (let i = polygon.length - 1; i >= 1; --i) { + const pi = polygon[i]; + for (let j = i - 1; j >= 0; --j) { + if (pointsEqual(pi, polygon[j], precision)) { + polygon.splice(i, 1); + continue; + } + } + } + } -/***/ }), + /***/ + }, -/***/ "./node_modules/random-seed/index.js": -/*!*******************************************!*\ + /***/ "./node_modules/random-seed/index.js": + /*!*******************************************!*\ !*** ./node_modules/random-seed/index.js ***! \*******************************************/ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -"use strict"; -/* - * random-seed - * https://github.com/skratchdot/random-seed - * - * This code was originally written by Steve Gibson and can be found here: - * - * https://www.grc.com/otg/uheprng.htm - * - * It was slightly modified for use in node, to pass jshint, and a few additional - * helper functions were added. - * - * Copyright (c) 2013 skratchdot - * Dual Licensed under the MIT license and the original GRC copyright/license - * included below. - */ -/* ============================================================================ + /***/ (module, __unused_webpack_exports, __webpack_require__) => { + "use strict"; + /* + * random-seed + * https://github.com/skratchdot/random-seed + * + * This code was originally written by Steve Gibson and can be found here: + * + * https://www.grc.com/otg/uheprng.htm + * + * It was slightly modified for use in node, to pass jshint, and a few additional + * helper functions were added. + * + * Copyright (c) 2013 skratchdot + * Dual Licensed under the MIT license and the original GRC copyright/license + * included below. + */ + /* ============================================================================ Gibson Research Corporation UHEPRNG - Ultra High Entropy Pseudo-Random Number Generator ============================================================================ @@ -755,3364 +935,3739 @@ function removeDuplicatePoints(polygon, precision) { 1460910 and 1768863. (We use the largest one that's < 2^21) ============================================================================ */ -var stringify = __webpack_require__(/*! json-stringify-safe */ "./node_modules/json-stringify-safe/stringify.js"); + var stringify = __webpack_require__( + /*! json-stringify-safe */ "./node_modules/json-stringify-safe/stringify.js", + ); -/* ============================================================================ + /* ============================================================================ This is based upon Johannes Baagoe's carefully designed and efficient hash function for use with JavaScript. It has a proven "avalanche" effect such that every bit of the input affects every bit of the output 50% of the time, which is good. See: http://baagoe.com/en/RandomMusings/hash/avalanche.xhtml ============================================================================ */ -var Mash = function () { - var n = 0xefc8249d; - var mash = function (data) { - if (data) { - data = data.toString(); - for (var i = 0; i < data.length; i++) { - n += data.charCodeAt(i); - var h = 0.02519603282416938 * n; - n = h >>> 0; - h -= n; - h *= n; - n = h >>> 0; - h -= n; - n += h * 0x100000000; // 2^32 - } - return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 - } else { - n = 0xefc8249d; - } - }; - return mash; -}; - -var uheprng = function (seed) { - return (function () { - var o = 48; // set the 'order' number of ENTROPY-holding 32-bit values - var c = 1; // init the 'carry' used by the multiply-with-carry (MWC) algorithm - var p = o; // init the 'phase' (max-1) of the intermediate variable pointer - var s = new Array(o); // declare our intermediate variables array - var i; // general purpose local - var j; // general purpose local - var k = 0; // general purpose local - - // when our "uheprng" is initially invoked our PRNG state is initialized from the - // browser's own local PRNG. This is okay since although its generator might not - // be wonderful, it's useful for establishing large startup entropy for our usage. - var mash = new Mash(); // get a pointer to our high-performance "Mash" hash - - // fill the array with initial mash hash values - for (i = 0; i < o; i++) { - s[i] = mash(Math.random()); - } - - // this PRIVATE (internal access only) function is the heart of the multiply-with-carry - // (MWC) PRNG algorithm. When called it returns a pseudo-random number in the form of a - // 32-bit JavaScript fraction (0.0 to <1.0) it is a PRIVATE function used by the default - // [0-1] return function, and by the random 'string(n)' function which returns 'n' - // characters from 33 to 126. - var rawprng = function () { - if (++p >= o) { - p = 0; - } - var t = 1768863 * s[p] + c * 2.3283064365386963e-10; // 2^-32 - return s[p] = t - (c = t | 0); - }; - - // this EXPORTED function is the default function returned by this library. - // The values returned are integers in the range from 0 to range-1. We first - // obtain two 32-bit fractions (from rawprng) to synthesize a single high - // resolution 53-bit prng (0 to <1), then we multiply this by the caller's - // "range" param and take the "floor" to return a equally probable integer. - var random = function (range) { - return Math.floor(range * (rawprng() + (rawprng() * 0x200000 | 0) * 1.1102230246251565e-16)); // 2^-53 - }; - - // this EXPORTED function 'string(n)' returns a pseudo-random string of - // 'n' printable characters ranging from chr(33) to chr(126) inclusive. - random.string = function (count) { - var i; - var s = ''; - for (i = 0; i < count; i++) { - s += String.fromCharCode(33 + random(94)); - } - return s; - }; - - // this PRIVATE "hash" function is used to evolve the generator's internal - // entropy state. It is also called by the EXPORTED addEntropy() function - // which is used to pour entropy into the PRNG. - var hash = function () { - var args = Array.prototype.slice.call(arguments); - for (i = 0; i < args.length; i++) { - for (j = 0; j < o; j++) { - s[j] -= mash(args[i]); - if (s[j] < 0) { - s[j] += 1; - } - } - } - }; - - // this EXPORTED "clean string" function removes leading and trailing spaces and non-printing - // control characters, including any embedded carriage-return (CR) and line-feed (LF) characters, - // from any string it is handed. this is also used by the 'hashstring' function (below) to help - // users always obtain the same EFFECTIVE uheprng seeding key. - random.cleanString = function (inStr) { - inStr = inStr.replace(/(^\s*)|(\s*$)/gi, ''); // remove any/all leading spaces - inStr = inStr.replace(/[\x00-\x1F]/gi, ''); // remove any/all control characters - inStr = inStr.replace(/\n /, '\n'); // remove any/all trailing spaces - return inStr; // return the cleaned up result - }; - - // this EXPORTED "hash string" function hashes the provided character string after first removing - // any leading or trailing spaces and ignoring any embedded carriage returns (CR) or Line Feeds (LF) - random.hashString = function (inStr) { - inStr = random.cleanString(inStr); - mash(inStr); // use the string to evolve the 'mash' state - for (i = 0; i < inStr.length; i++) { // scan through the characters in our string - k = inStr.charCodeAt(i); // get the character code at the location - for (j = 0; j < o; j++) { // "mash" it into the UHEPRNG state - s[j] -= mash(k); - if (s[j] < 0) { - s[j] += 1; - } - } - } - }; - - // this EXPORTED function allows you to seed the random generator. - random.seed = function (seed) { - if (typeof seed === 'undefined' || seed === null) { - seed = Math.random(); - } - if (typeof seed !== 'string') { - seed = stringify(seed, function (key, value) { - if (typeof value === 'function') { - return (value).toString(); - } - return value; - }); - } - random.initState(); - random.hashString(seed); - }; - - // this handy exported function is used to add entropy to our uheprng at any time - random.addEntropy = function ( /* accept zero or more arguments */ ) { - var args = []; - for (i = 0; i < arguments.length; i++) { - args.push(arguments[i]); - } - hash((k++) + (new Date().getTime()) + args.join('') + Math.random()); - }; - - // if we want to provide a deterministic startup context for our PRNG, - // but without directly setting the internal state variables, this allows - // us to initialize the mash hash and PRNG's internal state before providing - // some hashing input - random.initState = function () { - mash(); // pass a null arg to force mash hash to init - for (i = 0; i < o; i++) { - s[i] = mash(' '); // fill the array with initial mash hash values - } - c = 1; // init our multiply-with-carry carry - p = o; // init our phase - }; - - // we use this (optional) exported function to signal the JavaScript interpreter - // that we're finished using the "Mash" hash function so that it can free up the - // local "instance variables" is will have been maintaining. It's not strictly - // necessary, of course, but it's good JavaScript citizenship. - random.done = function () { - mash = null; - }; - - // if we called "uheprng" with a seed value, then execute random.seed() before returning - if (typeof seed !== 'undefined') { - random.seed(seed); - } - - // Returns a random integer between 0 (inclusive) and range (exclusive) - random.range = function (range) { - return random(range); - }; - - // Returns a random float between 0 (inclusive) and 1 (exclusive) - random.random = function () { - return random(Number.MAX_VALUE - 1) / Number.MAX_VALUE; - }; - - // Returns a random float between min (inclusive) and max (exclusive) - random.floatBetween = function (min, max) { - return random.random() * (max - min) + min; - }; - - // Returns a random integer between min (inclusive) and max (inclusive) - random.intBetween = function (min, max) { - return Math.floor(random.random() * (max - min + 1)) + min; - }; - - // when our main outer "uheprng" function is called, after setting up our - // initial variables and entropic state, we return an "instance pointer" - // to the internal anonymous function which can then be used to access - // the uheprng's various exported functions. As with the ".done" function - // above, we should set the returned value to 'null' once we're finished - // using any of these functions. - return random; - }()); -}; - -// Modification for use in node: -uheprng.create = function (seed) { - return new uheprng(seed); -}; -module.exports = uheprng; - - -/***/ }), - -/***/ "./node_modules/sat/SAT.js": -/*!*********************************!*\ - !*** ./node_modules/sat/SAT.js ***! - \*********************************/ -/***/ (function(module, exports, __webpack_require__) { - -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Version 0.9.0 - Copyright 2012 - 2021 - Jim Riecken -// -// Released under the MIT License - https://github.com/jriecken/sat-js -// -// A simple library for determining intersections of circles and -// polygons using the Separating Axis Theorem. -/** @preserve SAT.js - Version 0.9.0 - Copyright 2012 - 2021 - Jim Riecken - released under the MIT License. https://github.com/jriecken/sat-js */ - -/*global define: false, module: false*/ -/*jshint shadow:true, sub:true, forin:true, noarg:true, noempty:true, - eqeqeq:true, bitwise:true, strict:true, undef:true, - curly:true, browser:true */ - -// Create a UMD wrapper for SAT. Works in: -// -// - Plain browser via global SAT variable -// - AMD loader (like require.js) -// - Node.js -// -// The quoted properties all over the place are used so that the Closure Compiler -// does not mangle the exposed API in advanced mode. -/** - * @param {*} root - The global scope - * @param {Function} factory - Factory that creates SAT module - */ -(function (root, factory) { - "use strict"; - if (true) { - !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : - __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } else {} -}(this, function () { - "use strict"; - - var SAT = {}; - - // - // ## Vector - // - // Represents a vector in two dimensions with `x` and `y` properties. - - - // Create a new Vector, optionally passing in the `x` and `y` coordinates. If - // a coordinate is not specified, it will be set to `0` - /** - * @param {?number=} x The x position. - * @param {?number=} y The y position. - * @constructor - */ - function Vector(x, y) { - this['x'] = x || 0; - this['y'] = y || 0; - } - SAT['Vector'] = Vector; - // Alias `Vector` as `V` - SAT['V'] = Vector; - - - // Copy the values of another Vector into this one. - /** - * @param {Vector} other The other Vector. - * @return {Vector} This for chaining. - */ - Vector.prototype['copy'] = Vector.prototype.copy = function (other) { - this['x'] = other['x']; - this['y'] = other['y']; - return this; - }; - - // Create a new vector with the same coordinates as this on. - /** - * @return {Vector} The new cloned vector - */ - Vector.prototype['clone'] = Vector.prototype.clone = function () { - return new Vector(this['x'], this['y']); - }; - - // Change this vector to be perpendicular to what it was before. (Effectively - // roatates it 90 degrees in a clockwise direction) - /** - * @return {Vector} This for chaining. - */ - Vector.prototype['perp'] = Vector.prototype.perp = function () { - var x = this['x']; - this['x'] = this['y']; - this['y'] = -x; - return this; - }; - - // Rotate this vector (counter-clockwise) by the specified angle (in radians). - /** - * @param {number} angle The angle to rotate (in radians) - * @return {Vector} This for chaining. - */ - Vector.prototype['rotate'] = Vector.prototype.rotate = function (angle) { - var x = this['x']; - var y = this['y']; - this['x'] = x * Math.cos(angle) - y * Math.sin(angle); - this['y'] = x * Math.sin(angle) + y * Math.cos(angle); - return this; - }; - - // Reverse this vector. - /** - * @return {Vector} This for chaining. - */ - Vector.prototype['reverse'] = Vector.prototype.reverse = function () { - this['x'] = -this['x']; - this['y'] = -this['y']; - return this; - }; - + var Mash = function () { + var n = 0xefc8249d; + var mash = function (data) { + if (data) { + data = data.toString(); + for (var i = 0; i < data.length; i++) { + n += data.charCodeAt(i); + var h = 0.02519603282416938 * n; + n = h >>> 0; + h -= n; + h *= n; + n = h >>> 0; + h -= n; + n += h * 0x100000000; // 2^32 + } + return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 + } else { + n = 0xefc8249d; + } + }; + return mash; + }; - // Normalize this vector. (make it have length of `1`) - /** - * @return {Vector} This for chaining. - */ - Vector.prototype['normalize'] = Vector.prototype.normalize = function () { - var d = this.len(); - if (d > 0) { - this['x'] = this['x'] / d; - this['y'] = this['y'] / d; - } - return this; - }; + var uheprng = function (seed) { + return (function () { + var o = 48; // set the 'order' number of ENTROPY-holding 32-bit values + var c = 1; // init the 'carry' used by the multiply-with-carry (MWC) algorithm + var p = o; // init the 'phase' (max-1) of the intermediate variable pointer + var s = new Array(o); // declare our intermediate variables array + var i; // general purpose local + var j; // general purpose local + var k = 0; // general purpose local + + // when our "uheprng" is initially invoked our PRNG state is initialized from the + // browser's own local PRNG. This is okay since although its generator might not + // be wonderful, it's useful for establishing large startup entropy for our usage. + var mash = new Mash(); // get a pointer to our high-performance "Mash" hash + + // fill the array with initial mash hash values + for (i = 0; i < o; i++) { + s[i] = mash(Math.random()); + } - // Add another vector to this one. - /** - * @param {Vector} other The other Vector. - * @return {Vector} This for chaining. - */ - Vector.prototype['add'] = Vector.prototype.add = function (other) { - this['x'] += other['x']; - this['y'] += other['y']; - return this; - }; + // this PRIVATE (internal access only) function is the heart of the multiply-with-carry + // (MWC) PRNG algorithm. When called it returns a pseudo-random number in the form of a + // 32-bit JavaScript fraction (0.0 to <1.0) it is a PRIVATE function used by the default + // [0-1] return function, and by the random 'string(n)' function which returns 'n' + // characters from 33 to 126. + var rawprng = function () { + if (++p >= o) { + p = 0; + } + var t = 1768863 * s[p] + c * 2.3283064365386963e-10; // 2^-32 + return (s[p] = t - (c = t | 0)); + }; + + // this EXPORTED function is the default function returned by this library. + // The values returned are integers in the range from 0 to range-1. We first + // obtain two 32-bit fractions (from rawprng) to synthesize a single high + // resolution 53-bit prng (0 to <1), then we multiply this by the caller's + // "range" param and take the "floor" to return a equally probable integer. + var random = function (range) { + return Math.floor( + range * + (rawprng() + + ((rawprng() * 0x200000) | 0) * 1.1102230246251565e-16), + ); // 2^-53 + }; + + // this EXPORTED function 'string(n)' returns a pseudo-random string of + // 'n' printable characters ranging from chr(33) to chr(126) inclusive. + random.string = function (count) { + var i; + var s = ""; + for (i = 0; i < count; i++) { + s += String.fromCharCode(33 + random(94)); + } + return s; + }; + + // this PRIVATE "hash" function is used to evolve the generator's internal + // entropy state. It is also called by the EXPORTED addEntropy() function + // which is used to pour entropy into the PRNG. + var hash = function () { + var args = Array.prototype.slice.call(arguments); + for (i = 0; i < args.length; i++) { + for (j = 0; j < o; j++) { + s[j] -= mash(args[i]); + if (s[j] < 0) { + s[j] += 1; + } + } + } + }; + + // this EXPORTED "clean string" function removes leading and trailing spaces and non-printing + // control characters, including any embedded carriage-return (CR) and line-feed (LF) characters, + // from any string it is handed. this is also used by the 'hashstring' function (below) to help + // users always obtain the same EFFECTIVE uheprng seeding key. + random.cleanString = function (inStr) { + inStr = inStr.replace(/(^\s*)|(\s*$)/gi, ""); // remove any/all leading spaces + inStr = inStr.replace(/[\x00-\x1F]/gi, ""); // remove any/all control characters + inStr = inStr.replace(/\n /, "\n"); // remove any/all trailing spaces + return inStr; // return the cleaned up result + }; + + // this EXPORTED "hash string" function hashes the provided character string after first removing + // any leading or trailing spaces and ignoring any embedded carriage returns (CR) or Line Feeds (LF) + random.hashString = function (inStr) { + inStr = random.cleanString(inStr); + mash(inStr); // use the string to evolve the 'mash' state + for (i = 0; i < inStr.length; i++) { + // scan through the characters in our string + k = inStr.charCodeAt(i); // get the character code at the location + for (j = 0; j < o; j++) { + // "mash" it into the UHEPRNG state + s[j] -= mash(k); + if (s[j] < 0) { + s[j] += 1; + } + } + } + }; + + // this EXPORTED function allows you to seed the random generator. + random.seed = function (seed) { + if (typeof seed === "undefined" || seed === null) { + seed = Math.random(); + } + if (typeof seed !== "string") { + seed = stringify(seed, function (key, value) { + if (typeof value === "function") { + return value.toString(); + } + return value; + }); + } + random.initState(); + random.hashString(seed); + }; + + // this handy exported function is used to add entropy to our uheprng at any time + random.addEntropy = function (/* accept zero or more arguments */) { + var args = []; + for (i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + hash(k++ + new Date().getTime() + args.join("") + Math.random()); + }; + + // if we want to provide a deterministic startup context for our PRNG, + // but without directly setting the internal state variables, this allows + // us to initialize the mash hash and PRNG's internal state before providing + // some hashing input + random.initState = function () { + mash(); // pass a null arg to force mash hash to init + for (i = 0; i < o; i++) { + s[i] = mash(" "); // fill the array with initial mash hash values + } + c = 1; // init our multiply-with-carry carry + p = o; // init our phase + }; + + // we use this (optional) exported function to signal the JavaScript interpreter + // that we're finished using the "Mash" hash function so that it can free up the + // local "instance variables" is will have been maintaining. It's not strictly + // necessary, of course, but it's good JavaScript citizenship. + random.done = function () { + mash = null; + }; + + // if we called "uheprng" with a seed value, then execute random.seed() before returning + if (typeof seed !== "undefined") { + random.seed(seed); + } - // Subtract another vector from this one. - /** - * @param {Vector} other The other Vector. - * @return {Vector} This for chaiing. - */ - Vector.prototype['sub'] = Vector.prototype.sub = function (other) { - this['x'] -= other['x']; - this['y'] -= other['y']; - return this; - }; + // Returns a random integer between 0 (inclusive) and range (exclusive) + random.range = function (range) { + return random(range); + }; + + // Returns a random float between 0 (inclusive) and 1 (exclusive) + random.random = function () { + return random(Number.MAX_VALUE - 1) / Number.MAX_VALUE; + }; + + // Returns a random float between min (inclusive) and max (exclusive) + random.floatBetween = function (min, max) { + return random.random() * (max - min) + min; + }; + + // Returns a random integer between min (inclusive) and max (inclusive) + random.intBetween = function (min, max) { + return Math.floor(random.random() * (max - min + 1)) + min; + }; + + // when our main outer "uheprng" function is called, after setting up our + // initial variables and entropic state, we return an "instance pointer" + // to the internal anonymous function which can then be used to access + // the uheprng's various exported functions. As with the ".done" function + // above, we should set the returned value to 'null' once we're finished + // using any of these functions. + return random; + })(); + }; - // Scale this vector. An independent scaling factor can be provided - // for each axis, or a single scaling factor that will scale both `x` and `y`. - /** - * @param {number} x The scaling factor in the x direction. - * @param {?number=} y The scaling factor in the y direction. If this - * is not specified, the x scaling factor will be used. - * @return {Vector} This for chaining. - */ - Vector.prototype['scale'] = Vector.prototype.scale = function (x, y) { - this['x'] *= x; - this['y'] *= typeof y != 'undefined' ? y : x; - return this; - }; + // Modification for use in node: + uheprng.create = function (seed) { + return new uheprng(seed); + }; + module.exports = uheprng; - // Project this vector on to another vector. - /** - * @param {Vector} other The vector to project onto. - * @return {Vector} This for chaining. - */ - Vector.prototype['project'] = Vector.prototype.project = function (other) { - var amt = this.dot(other) / other.len2(); - this['x'] = amt * other['x']; - this['y'] = amt * other['y']; - return this; - }; + /***/ + }, - // Project this vector onto a vector of unit length. This is slightly more efficient - // than `project` when dealing with unit vectors. - /** - * @param {Vector} other The unit vector to project onto. - * @return {Vector} This for chaining. - */ - Vector.prototype['projectN'] = Vector.prototype.projectN = function (other) { - var amt = this.dot(other); - this['x'] = amt * other['x']; - this['y'] = amt * other['y']; - return this; - }; + /***/ "./node_modules/sat/SAT.js": + /*!*********************************!*\ + !*** ./node_modules/sat/SAT.js ***! + \*********************************/ + /***/ function (module, exports, __webpack_require__) { + var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__; // Version 0.9.0 - Copyright 2012 - 2021 - Jim Riecken + // + // Released under the MIT License - https://github.com/jriecken/sat-js + // + // A simple library for determining intersections of circles and + // polygons using the Separating Axis Theorem. + /** @preserve SAT.js - Version 0.9.0 - Copyright 2012 - 2021 - Jim Riecken - released under the MIT License. https://github.com/jriecken/sat-js */ + + /*global define: false, module: false*/ + /*jshint shadow:true, sub:true, forin:true, noarg:true, noempty:true, + eqeqeq:true, bitwise:true, strict:true, undef:true, + curly:true, browser:true */ - // Reflect this vector on an arbitrary axis. - /** - * @param {Vector} axis The vector representing the axis. - * @return {Vector} This for chaining. - */ - Vector.prototype['reflect'] = Vector.prototype.reflect = function (axis) { - var x = this['x']; - var y = this['y']; - this.project(axis).scale(2); - this['x'] -= x; - this['y'] -= y; - return this; - }; - - // Reflect this vector on an arbitrary axis (represented by a unit vector). This is - // slightly more efficient than `reflect` when dealing with an axis that is a unit vector. - /** - * @param {Vector} axis The unit vector representing the axis. - * @return {Vector} This for chaining. - */ - Vector.prototype['reflectN'] = Vector.prototype.reflectN = function (axis) { - var x = this['x']; - var y = this['y']; - this.projectN(axis).scale(2); - this['x'] -= x; - this['y'] -= y; - return this; - }; + // Create a UMD wrapper for SAT. Works in: + // + // - Plain browser via global SAT variable + // - AMD loader (like require.js) + // - Node.js + // + // The quoted properties all over the place are used so that the Closure Compiler + // does not mangle the exposed API in advanced mode. + /** + * @param {*} root - The global scope + * @param {Function} factory - Factory that creates SAT module + */ + (function (root, factory) { + "use strict"; + if (true) { + !((__WEBPACK_AMD_DEFINE_FACTORY__ = factory), + (__WEBPACK_AMD_DEFINE_RESULT__ = + typeof __WEBPACK_AMD_DEFINE_FACTORY__ === "function" + ? __WEBPACK_AMD_DEFINE_FACTORY__.call( + exports, + __webpack_require__, + exports, + module, + ) + : __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && + (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else { + } + })(this, function () { + "use strict"; + + var SAT = {}; + + // + // ## Vector + // + // Represents a vector in two dimensions with `x` and `y` properties. + + // Create a new Vector, optionally passing in the `x` and `y` coordinates. If + // a coordinate is not specified, it will be set to `0` + /** + * @param {?number=} x The x position. + * @param {?number=} y The y position. + * @constructor + */ + function Vector(x, y) { + this["x"] = x || 0; + this["y"] = y || 0; + } + SAT["Vector"] = Vector; + // Alias `Vector` as `V` + SAT["V"] = Vector; + + // Copy the values of another Vector into this one. + /** + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + Vector.prototype["copy"] = Vector.prototype.copy = function (other) { + this["x"] = other["x"]; + this["y"] = other["y"]; + return this; + }; + + // Create a new vector with the same coordinates as this on. + /** + * @return {Vector} The new cloned vector + */ + Vector.prototype["clone"] = Vector.prototype.clone = function () { + return new Vector(this["x"], this["y"]); + }; + + // Change this vector to be perpendicular to what it was before. (Effectively + // roatates it 90 degrees in a clockwise direction) + /** + * @return {Vector} This for chaining. + */ + Vector.prototype["perp"] = Vector.prototype.perp = function () { + var x = this["x"]; + this["x"] = this["y"]; + this["y"] = -x; + return this; + }; + + // Rotate this vector (counter-clockwise) by the specified angle (in radians). + /** + * @param {number} angle The angle to rotate (in radians) + * @return {Vector} This for chaining. + */ + Vector.prototype["rotate"] = Vector.prototype.rotate = function ( + angle, + ) { + var x = this["x"]; + var y = this["y"]; + this["x"] = x * Math.cos(angle) - y * Math.sin(angle); + this["y"] = x * Math.sin(angle) + y * Math.cos(angle); + return this; + }; + + // Reverse this vector. + /** + * @return {Vector} This for chaining. + */ + Vector.prototype["reverse"] = Vector.prototype.reverse = function () { + this["x"] = -this["x"]; + this["y"] = -this["y"]; + return this; + }; + + // Normalize this vector. (make it have length of `1`) + /** + * @return {Vector} This for chaining. + */ + Vector.prototype["normalize"] = Vector.prototype.normalize = + function () { + var d = this.len(); + if (d > 0) { + this["x"] = this["x"] / d; + this["y"] = this["y"] / d; + } + return this; + }; + + // Add another vector to this one. + /** + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + Vector.prototype["add"] = Vector.prototype.add = function (other) { + this["x"] += other["x"]; + this["y"] += other["y"]; + return this; + }; + + // Subtract another vector from this one. + /** + * @param {Vector} other The other Vector. + * @return {Vector} This for chaiing. + */ + Vector.prototype["sub"] = Vector.prototype.sub = function (other) { + this["x"] -= other["x"]; + this["y"] -= other["y"]; + return this; + }; + + // Scale this vector. An independent scaling factor can be provided + // for each axis, or a single scaling factor that will scale both `x` and `y`. + /** + * @param {number} x The scaling factor in the x direction. + * @param {?number=} y The scaling factor in the y direction. If this + * is not specified, the x scaling factor will be used. + * @return {Vector} This for chaining. + */ + Vector.prototype["scale"] = Vector.prototype.scale = function (x, y) { + this["x"] *= x; + this["y"] *= typeof y != "undefined" ? y : x; + return this; + }; + + // Project this vector on to another vector. + /** + * @param {Vector} other The vector to project onto. + * @return {Vector} This for chaining. + */ + Vector.prototype["project"] = Vector.prototype.project = function ( + other, + ) { + var amt = this.dot(other) / other.len2(); + this["x"] = amt * other["x"]; + this["y"] = amt * other["y"]; + return this; + }; + + // Project this vector onto a vector of unit length. This is slightly more efficient + // than `project` when dealing with unit vectors. + /** + * @param {Vector} other The unit vector to project onto. + * @return {Vector} This for chaining. + */ + Vector.prototype["projectN"] = Vector.prototype.projectN = function ( + other, + ) { + var amt = this.dot(other); + this["x"] = amt * other["x"]; + this["y"] = amt * other["y"]; + return this; + }; + + // Reflect this vector on an arbitrary axis. + /** + * @param {Vector} axis The vector representing the axis. + * @return {Vector} This for chaining. + */ + Vector.prototype["reflect"] = Vector.prototype.reflect = function ( + axis, + ) { + var x = this["x"]; + var y = this["y"]; + this.project(axis).scale(2); + this["x"] -= x; + this["y"] -= y; + return this; + }; + + // Reflect this vector on an arbitrary axis (represented by a unit vector). This is + // slightly more efficient than `reflect` when dealing with an axis that is a unit vector. + /** + * @param {Vector} axis The unit vector representing the axis. + * @return {Vector} This for chaining. + */ + Vector.prototype["reflectN"] = Vector.prototype.reflectN = function ( + axis, + ) { + var x = this["x"]; + var y = this["y"]; + this.projectN(axis).scale(2); + this["x"] -= x; + this["y"] -= y; + return this; + }; + + // Get the dot product of this vector and another. + /** + * @param {Vector} other The vector to dot this one against. + * @return {number} The dot product. + */ + Vector.prototype["dot"] = Vector.prototype.dot = function (other) { + return this["x"] * other["x"] + this["y"] * other["y"]; + }; + + // Get the squared length of this vector. + /** + * @return {number} The length^2 of this vector. + */ + Vector.prototype["len2"] = Vector.prototype.len2 = function () { + return this.dot(this); + }; + + // Get the length of this vector. + /** + * @return {number} The length of this vector. + */ + Vector.prototype["len"] = Vector.prototype.len = function () { + return Math.sqrt(this.len2()); + }; + + // ## Circle + // + // Represents a circle with a position and a radius. + + // Create a new circle, optionally passing in a position and/or radius. If no position + // is given, the circle will be at `(0,0)`. If no radius is provided, the circle will + // have a radius of `0`. + /** + * @param {Vector=} pos A vector representing the position of the center of the circle + * @param {?number=} r The radius of the circle + * @constructor + */ + function Circle(pos, r) { + this["pos"] = pos || new Vector(); + this["r"] = r || 0; + this["offset"] = new Vector(); + } + SAT["Circle"] = Circle; + + // Compute the axis-aligned bounding box (AABB) of this Circle. + // + // Note: Returns a _new_ `Box` each time you call this. + /** + * @return {Polygon} The AABB + */ + Circle.prototype["getAABBAsBox"] = Circle.prototype.getAABBAsBox = + function () { + var r = this["r"]; + var corner = this["pos"] + .clone() + .add(this["offset"]) + .sub(new Vector(r, r)); + return new Box(corner, r * 2, r * 2); + }; + + // Compute the axis-aligned bounding box (AABB) of this Circle. + // + // Note: Returns a _new_ `Polygon` each time you call this. + /** + * @return {Polygon} The AABB + */ + Circle.prototype["getAABB"] = Circle.prototype.getAABB = function () { + return this.getAABBAsBox().toPolygon(); + }; + + // Set the current offset to apply to the radius. + /** + * @param {Vector} offset The new offset vector. + * @return {Circle} This for chaining. + */ + Circle.prototype["setOffset"] = Circle.prototype.setOffset = + function (offset) { + this["offset"] = offset; + return this; + }; + + // ## Polygon + // + // Represents a *convex* polygon with any number of points (specified in counter-clockwise order) + // + // Note: Do _not_ manually change the `points`, `angle`, or `offset` properties. Use the + // provided setters. Otherwise the calculated properties will not be updated correctly. + // + // `pos` can be changed directly. + + // Create a new polygon, passing in a position vector, and an array of points (represented + // by vectors relative to the position vector). If no position is passed in, the position + // of the polygon will be `(0,0)`. + /** + * @param {Vector=} pos A vector representing the origin of the polygon. (all other + * points are relative to this one) + * @param {Array=} points An array of vectors representing the points in the polygon, + * in counter-clockwise order. + * @constructor + */ + function Polygon(pos, points) { + this["pos"] = pos || new Vector(); + this["angle"] = 0; + this["offset"] = new Vector(); + this.setPoints(points || []); + } + SAT["Polygon"] = Polygon; + + // Set the points of the polygon. Any consecutive duplicate points will be combined. + // + // Note: The points are counter-clockwise *with respect to the coordinate system*. + // If you directly draw the points on a screen that has the origin at the top-left corner + // it will _appear_ visually that the points are being specified clockwise. This is just + // because of the inversion of the Y-axis when being displayed. + /** + * @param {Array=} points An array of vectors representing the points in the polygon, + * in counter-clockwise order. + * @return {Polygon} This for chaining. + */ + Polygon.prototype["setPoints"] = Polygon.prototype.setPoints = + function (points) { + // Only re-allocate if this is a new polygon or the number of points has changed. + var lengthChanged = + !this["points"] || this["points"].length !== points.length; + if (lengthChanged) { + var i; + var calcPoints = (this["calcPoints"] = []); + var edges = (this["edges"] = []); + var normals = (this["normals"] = []); + // Allocate the vector arrays for the calculated properties + for (i = 0; i < points.length; i++) { + // Remove consecutive duplicate points + var p1 = points[i]; + var p2 = i < points.length - 1 ? points[i + 1] : points[0]; + if (p1 !== p2 && p1.x === p2.x && p1.y === p2.y) { + points.splice(i, 1); + i -= 1; + continue; + } + calcPoints.push(new Vector()); + edges.push(new Vector()); + normals.push(new Vector()); + } + } + this["points"] = points; + this._recalc(); + return this; + }; + + // Set the current rotation angle of the polygon. + /** + * @param {number} angle The current rotation angle (in radians). + * @return {Polygon} This for chaining. + */ + Polygon.prototype["setAngle"] = Polygon.prototype.setAngle = + function (angle) { + this["angle"] = angle; + this._recalc(); + return this; + }; + + // Set the current offset to apply to the `points` before applying the `angle` rotation. + /** + * @param {Vector} offset The new offset vector. + * @return {Polygon} This for chaining. + */ + Polygon.prototype["setOffset"] = Polygon.prototype.setOffset = + function (offset) { + this["offset"] = offset; + this._recalc(); + return this; + }; + + // Rotates this polygon counter-clockwise around the origin of *its local coordinate system* (i.e. `pos`). + // + // Note: This changes the **original** points (so any `angle` will be applied on top of this rotation). + /** + * @param {number} angle The angle to rotate (in radians) + * @return {Polygon} This for chaining. + */ + Polygon.prototype["rotate"] = Polygon.prototype.rotate = function ( + angle, + ) { + var points = this["points"]; + var len = points.length; + for (var i = 0; i < len; i++) { + points[i].rotate(angle); + } + this._recalc(); + return this; + }; + + // Translates the points of this polygon by a specified amount relative to the origin of *its own coordinate + // system* (i.e. `pos`). + // + // This is most useful to change the "center point" of a polygon. If you just want to move the whole polygon, change + // the coordinates of `pos`. + // + // Note: This changes the **original** points (so any `offset` will be applied on top of this translation) + /** + * @param {number} x The horizontal amount to translate. + * @param {number} y The vertical amount to translate. + * @return {Polygon} This for chaining. + */ + Polygon.prototype["translate"] = Polygon.prototype.translate = + function (x, y) { + var points = this["points"]; + var len = points.length; + for (var i = 0; i < len; i++) { + points[i]["x"] += x; + points[i]["y"] += y; + } + this._recalc(); + return this; + }; + + // Computes the calculated collision polygon. Applies the `angle` and `offset` to the original points then recalculates the + // edges and normals of the collision polygon. + /** + * @return {Polygon} This for chaining. + */ + Polygon.prototype._recalc = function () { + // Calculated points - this is what is used for underlying collisions and takes into account + // the angle/offset set on the polygon. + var calcPoints = this["calcPoints"]; + // The edges here are the direction of the `n`th edge of the polygon, relative to + // the `n`th point. If you want to draw a given edge from the edge value, you must + // first translate to the position of the starting point. + var edges = this["edges"]; + // The normals here are the direction of the normal for the `n`th edge of the polygon, relative + // to the position of the `n`th point. If you want to draw an edge normal, you must first + // translate to the position of the starting point. + var normals = this["normals"]; + // Copy the original points array and apply the offset/angle + var points = this["points"]; + var offset = this["offset"]; + var angle = this["angle"]; + var len = points.length; + var i; + for (i = 0; i < len; i++) { + var calcPoint = calcPoints[i].copy(points[i]); + calcPoint["x"] += offset["x"]; + calcPoint["y"] += offset["y"]; + if (angle !== 0) { + calcPoint.rotate(angle); + } + } + // Calculate the edges/normals + for (i = 0; i < len; i++) { + var p1 = calcPoints[i]; + var p2 = i < len - 1 ? calcPoints[i + 1] : calcPoints[0]; + var e = edges[i].copy(p2).sub(p1); + normals[i].copy(e).perp().normalize(); + } + return this; + }; + + // Compute the axis-aligned bounding box. Any current state + // (translations/rotations) will be applied before constructing the AABB. + // + // Note: Returns a _new_ `Box` each time you call this. + /** + * @return {Polygon} The AABB + */ + Polygon.prototype["getAABBAsBox"] = Polygon.prototype.getAABBAsBox = + function () { + var points = this["calcPoints"]; + var len = points.length; + var xMin = points[0]["x"]; + var yMin = points[0]["y"]; + var xMax = points[0]["x"]; + var yMax = points[0]["y"]; + for (var i = 1; i < len; i++) { + var point = points[i]; + if (point["x"] < xMin) { + xMin = point["x"]; + } else if (point["x"] > xMax) { + xMax = point["x"]; + } + if (point["y"] < yMin) { + yMin = point["y"]; + } else if (point["y"] > yMax) { + yMax = point["y"]; + } + } + return new Box( + this["pos"].clone().add(new Vector(xMin, yMin)), + xMax - xMin, + yMax - yMin, + ); + }; + + // Compute the axis-aligned bounding box. Any current state + // (translations/rotations) will be applied before constructing the AABB. + // + // Note: Returns a _new_ `Polygon` each time you call this. + /** + * @return {Polygon} The AABB + */ + Polygon.prototype["getAABB"] = Polygon.prototype.getAABB = + function () { + return this.getAABBAsBox().toPolygon(); + }; + + // Compute the centroid (geometric center) of the polygon. Any current state + // (translations/rotations) will be applied before computing the centroid. + // + // See https://en.wikipedia.org/wiki/Centroid#Centroid_of_a_polygon + // + // Note: Returns a _new_ `Vector` each time you call this. + /** + * @return {Vector} A Vector that contains the coordinates of the Centroid. + */ + Polygon.prototype["getCentroid"] = Polygon.prototype.getCentroid = + function () { + var points = this["calcPoints"]; + var len = points.length; + var cx = 0; + var cy = 0; + var ar = 0; + for (var i = 0; i < len; i++) { + var p1 = points[i]; + var p2 = i === len - 1 ? points[0] : points[i + 1]; // Loop around if last point + var a = p1["x"] * p2["y"] - p2["x"] * p1["y"]; + cx += (p1["x"] + p2["x"]) * a; + cy += (p1["y"] + p2["y"]) * a; + ar += a; + } + ar = ar * 3; // we want 1 / 6 the area and we currently have 2*area + cx = cx / ar; + cy = cy / ar; + return new Vector(cx, cy); + }; + + // ## Box + // + // Represents an axis-aligned box, with a width and height. + + // Create a new box, with the specified position, width, and height. If no position + // is given, the position will be `(0,0)`. If no width or height are given, they will + // be set to `0`. + /** + * @param {Vector=} pos A vector representing the bottom-left of the box (i.e. the smallest x and smallest y value). + * @param {?number=} w The width of the box. + * @param {?number=} h The height of the box. + * @constructor + */ + function Box(pos, w, h) { + this["pos"] = pos || new Vector(); + this["w"] = w || 0; + this["h"] = h || 0; + } + SAT["Box"] = Box; + + // Returns a polygon whose edges are the same as this box. + /** + * @return {Polygon} A new Polygon that represents this box. + */ + Box.prototype["toPolygon"] = Box.prototype.toPolygon = function () { + var pos = this["pos"]; + var w = this["w"]; + var h = this["h"]; + return new Polygon(new Vector(pos["x"], pos["y"]), [ + new Vector(), + new Vector(w, 0), + new Vector(w, h), + new Vector(0, h), + ]); + }; + + // ## Response + // + // An object representing the result of an intersection. Contains: + // - The two objects participating in the intersection + // - The vector representing the minimum change necessary to extract the first object + // from the second one (as well as a unit vector in that direction and the magnitude + // of the overlap) + // - Whether the first object is entirely inside the second, and vice versa. + /** + * @constructor + */ + function Response() { + this["a"] = null; + this["b"] = null; + this["overlapN"] = new Vector(); + this["overlapV"] = new Vector(); + this.clear(); + } + SAT["Response"] = Response; + + // Set some values of the response back to their defaults. Call this between tests if + // you are going to reuse a single Response object for multiple intersection tests (recommented + // as it will avoid allcating extra memory) + /** + * @return {Response} This for chaining + */ + Response.prototype["clear"] = Response.prototype.clear = function () { + this["aInB"] = true; + this["bInA"] = true; + this["overlap"] = Number.MAX_VALUE; + return this; + }; + + // ## Object Pools + + // A pool of `Vector` objects that are used in calculations to avoid + // allocating memory. + /** + * @type {Array} + */ + var T_VECTORS = []; + for (var i = 0; i < 10; i++) { + T_VECTORS.push(new Vector()); + } - // Get the dot product of this vector and another. - /** - * @param {Vector} other The vector to dot this one against. - * @return {number} The dot product. - */ - Vector.prototype['dot'] = Vector.prototype.dot = function (other) { - return this['x'] * other['x'] + this['y'] * other['y']; - }; + // A pool of arrays of numbers used in calculations to avoid allocating + // memory. + /** + * @type {Array>} + */ + var T_ARRAYS = []; + for (var i = 0; i < 5; i++) { + T_ARRAYS.push([]); + } - // Get the squared length of this vector. - /** - * @return {number} The length^2 of this vector. - */ - Vector.prototype['len2'] = Vector.prototype.len2 = function () { - return this.dot(this); - }; + // Temporary response used for polygon hit detection. + /** + * @type {Response} + */ + var T_RESPONSE = new Response(); + + // Tiny "point" polygon used for polygon hit detection. + /** + * @type {Polygon} + */ + var TEST_POINT = new Box( + new Vector(), + 0.000001, + 0.000001, + ).toPolygon(); + + // ## Helper Functions + + // Flattens the specified array of points onto a unit vector axis, + // resulting in a one dimensional range of the minimum and + // maximum value on that axis. + /** + * @param {Array} points The points to flatten. + * @param {Vector} normal The unit vector axis to flatten on. + * @param {Array} result An array. After calling this function, + * result[0] will be the minimum value, + * result[1] will be the maximum value. + */ + function flattenPointsOn(points, normal, result) { + var min = Number.MAX_VALUE; + var max = -Number.MAX_VALUE; + var len = points.length; + for (var i = 0; i < len; i++) { + // The magnitude of the projection of the point onto the normal + var dot = points[i].dot(normal); + if (dot < min) { + min = dot; + } + if (dot > max) { + max = dot; + } + } + result[0] = min; + result[1] = max; + } - // Get the length of this vector. - /** - * @return {number} The length of this vector. - */ - Vector.prototype['len'] = Vector.prototype.len = function () { - return Math.sqrt(this.len2()); - }; + // Check whether two convex polygons are separated by the specified + // axis (must be a unit vector). + /** + * @param {Vector} aPos The position of the first polygon. + * @param {Vector} bPos The position of the second polygon. + * @param {Array} aPoints The points in the first polygon. + * @param {Array} bPoints The points in the second polygon. + * @param {Vector} axis The axis (unit sized) to test against. The points of both polygons + * will be projected onto this axis. + * @param {Response=} response A Response object (optional) which will be populated + * if the axis is not a separating axis. + * @return {boolean} true if it is a separating axis, false otherwise. If false, + * and a response is passed in, information about how much overlap and + * the direction of the overlap will be populated. + */ + function isSeparatingAxis( + aPos, + bPos, + aPoints, + bPoints, + axis, + response, + ) { + var rangeA = T_ARRAYS.pop(); + var rangeB = T_ARRAYS.pop(); + // The magnitude of the offset between the two polygons + var offsetV = T_VECTORS.pop().copy(bPos).sub(aPos); + var projectedOffset = offsetV.dot(axis); + // Project the polygons onto the axis. + flattenPointsOn(aPoints, axis, rangeA); + flattenPointsOn(bPoints, axis, rangeB); + // Move B's range to its position relative to A. + rangeB[0] += projectedOffset; + rangeB[1] += projectedOffset; + // Check if there is a gap. If there is, this is a separating axis and we can stop + if (rangeA[0] > rangeB[1] || rangeB[0] > rangeA[1]) { + T_VECTORS.push(offsetV); + T_ARRAYS.push(rangeA); + T_ARRAYS.push(rangeB); + return true; + } + // This is not a separating axis. If we're calculating a response, calculate the overlap. + if (response) { + var overlap = 0; + // A starts further left than B + if (rangeA[0] < rangeB[0]) { + response["aInB"] = false; + // A ends before B does. We have to pull A out of B + if (rangeA[1] < rangeB[1]) { + overlap = rangeA[1] - rangeB[0]; + response["bInA"] = false; + // B is fully inside A. Pick the shortest way out. + } else { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + // B starts further left than A + } else { + response["bInA"] = false; + // B ends before A ends. We have to push A out of B + if (rangeA[1] > rangeB[1]) { + overlap = rangeA[0] - rangeB[1]; + response["aInB"] = false; + // A is fully inside B. Pick the shortest way out. + } else { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + } + // If this is the smallest amount of overlap we've seen so far, set it as the minimum overlap. + var absOverlap = Math.abs(overlap); + if (absOverlap < response["overlap"]) { + response["overlap"] = absOverlap; + response["overlapN"].copy(axis); + if (overlap < 0) { + response["overlapN"].reverse(); + } + } + } + T_VECTORS.push(offsetV); + T_ARRAYS.push(rangeA); + T_ARRAYS.push(rangeB); + return false; + } + SAT["isSeparatingAxis"] = isSeparatingAxis; + + // Calculates which Voronoi region a point is on a line segment. + // It is assumed that both the line and the point are relative to `(0,0)` + // + // | (0) | + // (-1) [S]--------------[E] (1) + // | (0) | + /** + * @param {Vector} line The line segment. + * @param {Vector} point The point. + * @return {number} LEFT_VORONOI_REGION (-1) if it is the left region, + * MIDDLE_VORONOI_REGION (0) if it is the middle region, + * RIGHT_VORONOI_REGION (1) if it is the right region. + */ + function voronoiRegion(line, point) { + var len2 = line.len2(); + var dp = point.dot(line); + // If the point is beyond the start of the line, it is in the + // left voronoi region. + if (dp < 0) { + return LEFT_VORONOI_REGION; + } + // If the point is beyond the end of the line, it is in the + // right voronoi region. + else if (dp > len2) { + return RIGHT_VORONOI_REGION; + } + // Otherwise, it's in the middle one. + else { + return MIDDLE_VORONOI_REGION; + } + } + // Constants for Voronoi regions + /** + * @const + */ + var LEFT_VORONOI_REGION = -1; + /** + * @const + */ + var MIDDLE_VORONOI_REGION = 0; + /** + * @const + */ + var RIGHT_VORONOI_REGION = 1; + + // ## Collision Tests + + // Check if a point is inside a circle. + /** + * @param {Vector} p The point to test. + * @param {Circle} c The circle to test. + * @return {boolean} true if the point is inside the circle, false if it is not. + */ + function pointInCircle(p, c) { + var differenceV = T_VECTORS.pop() + .copy(p) + .sub(c["pos"]) + .sub(c["offset"]); + var radiusSq = c["r"] * c["r"]; + var distanceSq = differenceV.len2(); + T_VECTORS.push(differenceV); + // If the distance between is smaller than the radius then the point is inside the circle. + return distanceSq <= radiusSq; + } + SAT["pointInCircle"] = pointInCircle; + + // Check if a point is inside a convex polygon. + /** + * @param {Vector} p The point to test. + * @param {Polygon} poly The polygon to test. + * @return {boolean} true if the point is inside the polygon, false if it is not. + */ + function pointInPolygon(p, poly) { + TEST_POINT["pos"].copy(p); + T_RESPONSE.clear(); + var result = testPolygonPolygon(TEST_POINT, poly, T_RESPONSE); + if (result) { + result = T_RESPONSE["aInB"]; + } + return result; + } + SAT["pointInPolygon"] = pointInPolygon; + + // Check if two circles collide. + /** + * @param {Circle} a The first circle. + * @param {Circle} b The second circle. + * @param {Response=} response Response object (optional) that will be populated if + * the circles intersect. + * @return {boolean} true if the circles intersect, false if they don't. + */ + function testCircleCircle(a, b, response) { + // Check if the distance between the centers of the two + // circles is greater than their combined radius. + var differenceV = T_VECTORS.pop() + .copy(b["pos"]) + .add(b["offset"]) + .sub(a["pos"]) + .sub(a["offset"]); + var totalRadius = a["r"] + b["r"]; + var totalRadiusSq = totalRadius * totalRadius; + var distanceSq = differenceV.len2(); + // If the distance is bigger than the combined radius, they don't intersect. + if (distanceSq > totalRadiusSq) { + T_VECTORS.push(differenceV); + return false; + } + // They intersect. If we're calculating a response, calculate the overlap. + if (response) { + var dist = Math.sqrt(distanceSq); + response["a"] = a; + response["b"] = b; + response["overlap"] = totalRadius - dist; + response["overlapN"].copy(differenceV.normalize()); + response["overlapV"].copy(differenceV).scale(response["overlap"]); + response["aInB"] = a["r"] <= b["r"] && dist <= b["r"] - a["r"]; + response["bInA"] = b["r"] <= a["r"] && dist <= a["r"] - b["r"]; + } + T_VECTORS.push(differenceV); + return true; + } + SAT["testCircleCircle"] = testCircleCircle; + + // Check if a polygon and a circle collide. + /** + * @param {Polygon} polygon The polygon. + * @param {Circle} circle The circle. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testPolygonCircle(polygon, circle, response) { + // Get the position of the circle relative to the polygon. + var circlePos = T_VECTORS.pop() + .copy(circle["pos"]) + .add(circle["offset"]) + .sub(polygon["pos"]); + var radius = circle["r"]; + var radius2 = radius * radius; + var points = polygon["calcPoints"]; + var len = points.length; + var edge = T_VECTORS.pop(); + var point = T_VECTORS.pop(); + + // For each edge in the polygon: + for (var i = 0; i < len; i++) { + var next = i === len - 1 ? 0 : i + 1; + var prev = i === 0 ? len - 1 : i - 1; + var overlap = 0; + var overlapN = null; + + // Get the edge. + edge.copy(polygon["edges"][i]); + // Calculate the center of the circle relative to the starting point of the edge. + point.copy(circlePos).sub(points[i]); + + // If the distance between the center of the circle and the point + // is bigger than the radius, the polygon is definitely not fully in + // the circle. + if (response && point.len2() > radius2) { + response["aInB"] = false; + } + + // Calculate which Voronoi region the center of the circle is in. + var region = voronoiRegion(edge, point); + // If it's the left region: + if (region === LEFT_VORONOI_REGION) { + // We need to make sure we're in the RIGHT_VORONOI_REGION of the previous edge. + edge.copy(polygon["edges"][prev]); + // Calculate the center of the circle relative the starting point of the previous edge + var point2 = T_VECTORS.pop().copy(circlePos).sub(points[prev]); + region = voronoiRegion(edge, point2); + if (region === RIGHT_VORONOI_REGION) { + // It's in the region we want. Check if the circle intersects the point. + var dist = point.len(); + if (dist > radius) { + // No intersection + T_VECTORS.push(circlePos); + T_VECTORS.push(edge); + T_VECTORS.push(point); + T_VECTORS.push(point2); + return false; + } else if (response) { + // It intersects, calculate the overlap. + response["bInA"] = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + T_VECTORS.push(point2); + // If it's the right region: + } else if (region === RIGHT_VORONOI_REGION) { + // We need to make sure we're in the left region on the next edge + edge.copy(polygon["edges"][next]); + // Calculate the center of the circle relative to the starting point of the next edge. + point.copy(circlePos).sub(points[next]); + region = voronoiRegion(edge, point); + if (region === LEFT_VORONOI_REGION) { + // It's in the region we want. Check if the circle intersects the point. + var dist = point.len(); + if (dist > radius) { + // No intersection + T_VECTORS.push(circlePos); + T_VECTORS.push(edge); + T_VECTORS.push(point); + return false; + } else if (response) { + // It intersects, calculate the overlap. + response["bInA"] = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + // Otherwise, it's the middle region: + } else { + // Need to check if the circle is intersecting the edge, + // Change the edge into its "edge normal". + var normal = edge.perp().normalize(); + // Find the perpendicular distance between the center of the + // circle and the edge. + var dist = point.dot(normal); + var distAbs = Math.abs(dist); + // If the circle is on the outside of the edge, there is no intersection. + if (dist > 0 && distAbs > radius) { + // No intersection + T_VECTORS.push(circlePos); + T_VECTORS.push(normal); + T_VECTORS.push(point); + return false; + } else if (response) { + // It intersects, calculate the overlap. + overlapN = normal; + overlap = radius - dist; + // If the center of the circle is on the outside of the edge, or part of the + // circle is on the outside, the circle is not fully inside the polygon. + if (dist >= 0 || overlap < 2 * radius) { + response["bInA"] = false; + } + } + } + + // If this is the smallest overlap we've seen, keep it. + // (overlapN may be null if the circle was in the wrong Voronoi region). + if ( + overlapN && + response && + Math.abs(overlap) < Math.abs(response["overlap"]) + ) { + response["overlap"] = overlap; + response["overlapN"].copy(overlapN); + } + } - // ## Circle - // - // Represents a circle with a position and a radius. - - // Create a new circle, optionally passing in a position and/or radius. If no position - // is given, the circle will be at `(0,0)`. If no radius is provided, the circle will - // have a radius of `0`. - /** - * @param {Vector=} pos A vector representing the position of the center of the circle - * @param {?number=} r The radius of the circle - * @constructor - */ - function Circle(pos, r) { - this['pos'] = pos || new Vector(); - this['r'] = r || 0; - this['offset'] = new Vector(); - } - SAT['Circle'] = Circle; - - // Compute the axis-aligned bounding box (AABB) of this Circle. - // - // Note: Returns a _new_ `Box` each time you call this. - /** - * @return {Polygon} The AABB - */ - Circle.prototype['getAABBAsBox'] = Circle.prototype.getAABBAsBox = function () { - var r = this['r']; - var corner = this['pos'].clone().add(this['offset']).sub(new Vector(r, r)); - return new Box(corner, r * 2, r * 2); - }; + // Calculate the final overlap vector - based on the smallest overlap. + if (response) { + response["a"] = polygon; + response["b"] = circle; + response["overlapV"] + .copy(response["overlapN"]) + .scale(response["overlap"]); + } + T_VECTORS.push(circlePos); + T_VECTORS.push(edge); + T_VECTORS.push(point); + return true; + } + SAT["testPolygonCircle"] = testPolygonCircle; + + // Check if a circle and a polygon collide. + // + // **NOTE:** This is slightly less efficient than polygonCircle as it just + // runs polygonCircle and reverses everything at the end. + /** + * @param {Circle} circle The circle. + * @param {Polygon} polygon The polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testCirclePolygon(circle, polygon, response) { + // Test the polygon against the circle. + var result = testPolygonCircle(polygon, circle, response); + if (result && response) { + // Swap A and B in the response. + var a = response["a"]; + var aInB = response["aInB"]; + response["overlapN"].reverse(); + response["overlapV"].reverse(); + response["a"] = response["b"]; + response["b"] = a; + response["aInB"] = response["bInA"]; + response["bInA"] = aInB; + } + return result; + } + SAT["testCirclePolygon"] = testCirclePolygon; + + // Checks whether polygons collide. + /** + * @param {Polygon} a The first polygon. + * @param {Polygon} b The second polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testPolygonPolygon(a, b, response) { + var aPoints = a["calcPoints"]; + var aLen = aPoints.length; + var bPoints = b["calcPoints"]; + var bLen = bPoints.length; + // If any of the edge normals of A is a separating axis, no intersection. + for (var i = 0; i < aLen; i++) { + if ( + isSeparatingAxis( + a["pos"], + b["pos"], + aPoints, + bPoints, + a["normals"][i], + response, + ) + ) { + return false; + } + } + // If any of the edge normals of B is a separating axis, no intersection. + for (var i = 0; i < bLen; i++) { + if ( + isSeparatingAxis( + a["pos"], + b["pos"], + aPoints, + bPoints, + b["normals"][i], + response, + ) + ) { + return false; + } + } + // Since none of the edge normals of A or B are a separating axis, there is an intersection + // and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the + // final overlap vector. + if (response) { + response["a"] = a; + response["b"] = b; + response["overlapV"] + .copy(response["overlapN"]) + .scale(response["overlap"]); + } + return true; + } + SAT["testPolygonPolygon"] = testPolygonPolygon; - // Compute the axis-aligned bounding box (AABB) of this Circle. - // - // Note: Returns a _new_ `Polygon` each time you call this. - /** - * @return {Polygon} The AABB - */ - Circle.prototype['getAABB'] = Circle.prototype.getAABB = function () { - return this.getAABBAsBox().toPolygon(); - }; + return SAT; + }); - // Set the current offset to apply to the radius. - /** - * @param {Vector} offset The new offset vector. - * @return {Circle} This for chaining. - */ - Circle.prototype['setOffset'] = Circle.prototype.setOffset = function (offset) { - this['offset'] = offset; - return this; - }; + /***/ + }, - // ## Polygon - // - // Represents a *convex* polygon with any number of points (specified in counter-clockwise order) - // - // Note: Do _not_ manually change the `points`, `angle`, or `offset` properties. Use the - // provided setters. Otherwise the calculated properties will not be updated correctly. - // - // `pos` can be changed directly. - - // Create a new polygon, passing in a position vector, and an array of points (represented - // by vectors relative to the position vector). If no position is passed in, the position - // of the polygon will be `(0,0)`. - /** - * @param {Vector=} pos A vector representing the origin of the polygon. (all other - * points are relative to this one) - * @param {Array=} points An array of vectors representing the points in the polygon, - * in counter-clockwise order. - * @constructor - */ - function Polygon(pos, points) { - this['pos'] = pos || new Vector(); - this['angle'] = 0; - this['offset'] = new Vector(); - this.setPoints(points || []); - } - SAT['Polygon'] = Polygon; - - // Set the points of the polygon. Any consecutive duplicate points will be combined. - // - // Note: The points are counter-clockwise *with respect to the coordinate system*. - // If you directly draw the points on a screen that has the origin at the top-left corner - // it will _appear_ visually that the points are being specified clockwise. This is just - // because of the inversion of the Y-axis when being displayed. - /** - * @param {Array=} points An array of vectors representing the points in the polygon, - * in counter-clockwise order. - * @return {Polygon} This for chaining. - */ - Polygon.prototype['setPoints'] = Polygon.prototype.setPoints = function (points) { - // Only re-allocate if this is a new polygon or the number of points has changed. - var lengthChanged = !this['points'] || this['points'].length !== points.length; - if (lengthChanged) { - var i; - var calcPoints = this['calcPoints'] = []; - var edges = this['edges'] = []; - var normals = this['normals'] = []; - // Allocate the vector arrays for the calculated properties - for (i = 0; i < points.length; i++) { - // Remove consecutive duplicate points - var p1 = points[i]; - var p2 = i < points.length - 1 ? points[i + 1] : points[0]; - if (p1 !== p2 && p1.x === p2.x && p1.y === p2.y) { - points.splice(i, 1); - i -= 1; - continue; + /***/ "./src/base-system.ts": + /*!****************************!*\ + !*** ./src/base-system.ts ***! + \****************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.BaseSystem = void 0; + const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); + const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts"); + const optimized_1 = __webpack_require__( + /*! ./optimized */ "./src/optimized.ts", + ); + const box_1 = __webpack_require__( + /*! ./bodies/box */ "./src/bodies/box.ts", + ); + const circle_1 = __webpack_require__( + /*! ./bodies/circle */ "./src/bodies/circle.ts", + ); + const ellipse_1 = __webpack_require__( + /*! ./bodies/ellipse */ "./src/bodies/ellipse.ts", + ); + const line_1 = __webpack_require__( + /*! ./bodies/line */ "./src/bodies/line.ts", + ); + const point_1 = __webpack_require__( + /*! ./bodies/point */ "./src/bodies/point.ts", + ); + const polygon_1 = __webpack_require__( + /*! ./bodies/polygon */ "./src/bodies/polygon.ts", + ); + /** + * very base collision system (create, insert, update, draw, remove) + */ + class BaseSystem extends model_1.RBush { + /** + * create point at position with options and add to system + */ + createPoint(position, options) { + const point = new point_1.Point(position, options); + this.insert(point); + return point; + } + /** + * create line at position with options and add to system + */ + createLine(start, end, options) { + const line = new line_1.Line(start, end, options); + this.insert(line); + return line; + } + /** + * create circle at position with options and add to system + */ + createCircle(position, radius, options) { + const circle = new circle_1.Circle(position, radius, options); + this.insert(circle); + return circle; + } + /** + * create box at position with options and add to system + */ + createBox(position, width, height, options) { + const box = new box_1.Box(position, width, height, options); + this.insert(box); + return box; + } + /** + * create ellipse at position with options and add to system + */ + createEllipse(position, radiusX, radiusY = radiusX, step, options) { + const ellipse = new ellipse_1.Ellipse( + position, + radiusX, + radiusY, + step, + options, + ); + this.insert(ellipse); + return ellipse; + } + /** + * create polygon at position with options and add to system + */ + createPolygon(position, points, options) { + const polygon = new polygon_1.Polygon(position, points, options); + this.insert(polygon); + return polygon; + } + /** + * re-insert body into collision tree and update its bbox + * every body can be part of only one system + */ + insert(body) { + body.bbox = body.getAABBAsBBox(); + if (body.system) { + // allow end if body inserted and not moved + if (!(0, utils_1.bodyMoved)(body)) { + return this; + } + // old bounding box *needs* to be removed + body.system.remove(body); + } + // only then we update min, max + body.minX = body.bbox.minX - body.padding; + body.minY = body.bbox.minY - body.padding; + body.maxX = body.bbox.maxX + body.padding; + body.maxY = body.bbox.maxY + body.padding; + // reinsert bounding box to collision tree + return super.insert(body); + } + /** + * updates body in collision tree + */ + updateBody(body) { + body.updateBody(); + } + /** + * update all bodies aabb + */ + update() { + (0, optimized_1.forEach)(this.all(), (body) => { + this.updateBody(body); + }); + } + /** + * draw exact bodies colliders outline + */ + draw(context) { + (0, optimized_1.forEach)(this.all(), (body) => { + body.draw(context); + }); + } + /** + * draw bounding boxes hierarchy outline + */ + drawBVH(context) { + const drawChildren = (body) => { + (0, utils_1.drawBVH)(context, body); + if (body.children) { + (0, optimized_1.forEach)(body.children, drawChildren); + } + }; + (0, optimized_1.forEach)(this.data.children, drawChildren); + } + /** + * remove body aabb from collision tree + */ + remove(body, equals) { + body.system = undefined; + return super.remove(body, equals); + } + /** + * get object potential colliders + * @deprecated because it's slower to use than checkOne() or checkAll() + */ + getPotentials(body) { + // filter here is required as collides with self + return (0, optimized_1.filter)( + this.search(body), + (candidate) => candidate !== body, + ); + } + /** + * used to find body deep inside data with finder function returning boolean found or not + */ + traverse(traverseFunction, { children } = this.data) { + return children === null || children === void 0 + ? void 0 + : children.find((body, index) => { + if (!body) { + return false; + } + if ( + body.typeGroup && + traverseFunction(body, children, index) + ) { + return true; + } + // if callback returns true, ends forEach + if (body.children) { + this.traverse(traverseFunction, body); + } + }); + } } - calcPoints.push(new Vector()); - edges.push(new Vector()); - normals.push(new Vector()); - } - } - this['points'] = points; - this._recalc(); - return this; - }; + exports.BaseSystem = BaseSystem; - // Set the current rotation angle of the polygon. - /** - * @param {number} angle The current rotation angle (in radians). - * @return {Polygon} This for chaining. - */ - Polygon.prototype['setAngle'] = Polygon.prototype.setAngle = function (angle) { - this['angle'] = angle; - this._recalc(); - return this; - }; - - // Set the current offset to apply to the `points` before applying the `angle` rotation. - /** - * @param {Vector} offset The new offset vector. - * @return {Polygon} This for chaining. - */ - Polygon.prototype['setOffset'] = Polygon.prototype.setOffset = function (offset) { - this['offset'] = offset; - this._recalc(); - return this; - }; - - // Rotates this polygon counter-clockwise around the origin of *its local coordinate system* (i.e. `pos`). - // - // Note: This changes the **original** points (so any `angle` will be applied on top of this rotation). - /** - * @param {number} angle The angle to rotate (in radians) - * @return {Polygon} This for chaining. - */ - Polygon.prototype['rotate'] = Polygon.prototype.rotate = function (angle) { - var points = this['points']; - var len = points.length; - for (var i = 0; i < len; i++) { - points[i].rotate(angle); - } - this._recalc(); - return this; - }; - - // Translates the points of this polygon by a specified amount relative to the origin of *its own coordinate - // system* (i.e. `pos`). - // - // This is most useful to change the "center point" of a polygon. If you just want to move the whole polygon, change - // the coordinates of `pos`. - // - // Note: This changes the **original** points (so any `offset` will be applied on top of this translation) - /** - * @param {number} x The horizontal amount to translate. - * @param {number} y The vertical amount to translate. - * @return {Polygon} This for chaining. - */ - Polygon.prototype['translate'] = Polygon.prototype.translate = function (x, y) { - var points = this['points']; - var len = points.length; - for (var i = 0; i < len; i++) { - points[i]['x'] += x; - points[i]['y'] += y; - } - this._recalc(); - return this; - }; - - - // Computes the calculated collision polygon. Applies the `angle` and `offset` to the original points then recalculates the - // edges and normals of the collision polygon. - /** - * @return {Polygon} This for chaining. - */ - Polygon.prototype._recalc = function () { - // Calculated points - this is what is used for underlying collisions and takes into account - // the angle/offset set on the polygon. - var calcPoints = this['calcPoints']; - // The edges here are the direction of the `n`th edge of the polygon, relative to - // the `n`th point. If you want to draw a given edge from the edge value, you must - // first translate to the position of the starting point. - var edges = this['edges']; - // The normals here are the direction of the normal for the `n`th edge of the polygon, relative - // to the position of the `n`th point. If you want to draw an edge normal, you must first - // translate to the position of the starting point. - var normals = this['normals']; - // Copy the original points array and apply the offset/angle - var points = this['points']; - var offset = this['offset']; - var angle = this['angle']; - var len = points.length; - var i; - for (i = 0; i < len; i++) { - var calcPoint = calcPoints[i].copy(points[i]); - calcPoint['x'] += offset['x']; - calcPoint['y'] += offset['y']; - if (angle !== 0) { - calcPoint.rotate(angle); - } - } - // Calculate the edges/normals - for (i = 0; i < len; i++) { - var p1 = calcPoints[i]; - var p2 = i < len - 1 ? calcPoints[i + 1] : calcPoints[0]; - var e = edges[i].copy(p2).sub(p1); - normals[i].copy(e).perp().normalize(); - } - return this; - }; - - - // Compute the axis-aligned bounding box. Any current state - // (translations/rotations) will be applied before constructing the AABB. - // - // Note: Returns a _new_ `Box` each time you call this. - /** - * @return {Polygon} The AABB - */ - Polygon.prototype['getAABBAsBox'] = Polygon.prototype.getAABBAsBox = function () { - var points = this['calcPoints']; - var len = points.length; - var xMin = points[0]['x']; - var yMin = points[0]['y']; - var xMax = points[0]['x']; - var yMax = points[0]['y']; - for (var i = 1; i < len; i++) { - var point = points[i]; - if (point['x'] < xMin) { - xMin = point['x']; - } - else if (point['x'] > xMax) { - xMax = point['x']; - } - if (point['y'] < yMin) { - yMin = point['y']; - } - else if (point['y'] > yMax) { - yMax = point['y']; - } - } - return new Box(this['pos'].clone().add(new Vector(xMin, yMin)), xMax - xMin, yMax - yMin); - }; - - - // Compute the axis-aligned bounding box. Any current state - // (translations/rotations) will be applied before constructing the AABB. - // - // Note: Returns a _new_ `Polygon` each time you call this. - /** - * @return {Polygon} The AABB - */ - Polygon.prototype['getAABB'] = Polygon.prototype.getAABB = function () { - return this.getAABBAsBox().toPolygon(); - }; + /***/ + }, - // Compute the centroid (geometric center) of the polygon. Any current state - // (translations/rotations) will be applied before computing the centroid. - // - // See https://en.wikipedia.org/wiki/Centroid#Centroid_of_a_polygon - // - // Note: Returns a _new_ `Vector` each time you call this. - /** - * @return {Vector} A Vector that contains the coordinates of the Centroid. - */ - Polygon.prototype['getCentroid'] = Polygon.prototype.getCentroid = function () { - var points = this['calcPoints']; - var len = points.length; - var cx = 0; - var cy = 0; - var ar = 0; - for (var i = 0; i < len; i++) { - var p1 = points[i]; - var p2 = i === len - 1 ? points[0] : points[i + 1]; // Loop around if last point - var a = p1['x'] * p2['y'] - p2['x'] * p1['y']; - cx += (p1['x'] + p2['x']) * a; - cy += (p1['y'] + p2['y']) * a; - ar += a; - } - ar = ar * 3; // we want 1 / 6 the area and we currently have 2*area - cx = cx / ar; - cy = cy / ar; - return new Vector(cx, cy); - }; - - - // ## Box - // - // Represents an axis-aligned box, with a width and height. - - - // Create a new box, with the specified position, width, and height. If no position - // is given, the position will be `(0,0)`. If no width or height are given, they will - // be set to `0`. - /** - * @param {Vector=} pos A vector representing the bottom-left of the box (i.e. the smallest x and smallest y value). - * @param {?number=} w The width of the box. - * @param {?number=} h The height of the box. - * @constructor - */ - function Box(pos, w, h) { - this['pos'] = pos || new Vector(); - this['w'] = w || 0; - this['h'] = h || 0; - } - SAT['Box'] = Box; - - // Returns a polygon whose edges are the same as this box. - /** - * @return {Polygon} A new Polygon that represents this box. - */ - Box.prototype['toPolygon'] = Box.prototype.toPolygon = function () { - var pos = this['pos']; - var w = this['w']; - var h = this['h']; - return new Polygon(new Vector(pos['x'], pos['y']), [ - new Vector(), new Vector(w, 0), - new Vector(w, h), new Vector(0, h) - ]); - }; - - // ## Response - // - // An object representing the result of an intersection. Contains: - // - The two objects participating in the intersection - // - The vector representing the minimum change necessary to extract the first object - // from the second one (as well as a unit vector in that direction and the magnitude - // of the overlap) - // - Whether the first object is entirely inside the second, and vice versa. - /** - * @constructor - */ - function Response() { - this['a'] = null; - this['b'] = null; - this['overlapN'] = new Vector(); - this['overlapV'] = new Vector(); - this.clear(); - } - SAT['Response'] = Response; - - // Set some values of the response back to their defaults. Call this between tests if - // you are going to reuse a single Response object for multiple intersection tests (recommented - // as it will avoid allcating extra memory) - /** - * @return {Response} This for chaining - */ - Response.prototype['clear'] = Response.prototype.clear = function () { - this['aInB'] = true; - this['bInA'] = true; - this['overlap'] = Number.MAX_VALUE; - return this; - }; + /***/ "./src/bodies/box.ts": + /*!***************************!*\ + !*** ./src/bodies/box.ts ***! + \***************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Box = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const polygon_1 = __webpack_require__( + /*! ./polygon */ "./src/bodies/polygon.ts", + ); + const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); + /** + * collider - box + */ + class Box extends polygon_1.Polygon { + /** + * collider - box + */ + constructor(position, width, height, options) { + super(position, (0, utils_1.createBox)(width, height), options); + /** + * type of body + */ + this.type = model_1.BodyType.Box; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Box; + /** + * boxes are convex + */ + this.isConvex = true; + this._width = width; + this._height = height; + } + /** + * get box width + */ + get width() { + return this._width; + } + /** + * set box width, update points + */ + set width(width) { + this._width = width; + this.afterUpdateSize(); + } + /** + * get box height + */ + get height() { + return this._height; + } + /** + * set box height, update points + */ + set height(height) { + this._height = height; + this.afterUpdateSize(); + } + /** + * after setting width/height update translate + * see https://github.com/Prozi/detect-collisions/issues/70 + */ + afterUpdateSize() { + if (this.isCentered) { + this.retranslate(false); + } + this.setPoints((0, utils_1.createBox)(this._width, this._height)); + if (this.isCentered) { + this.retranslate(); + } + } + /** + * do not attempt to use Polygon.updateIsConvex() + */ + updateIsConvex() { + return; + } + } + exports.Box = Box; - // ## Object Pools - - // A pool of `Vector` objects that are used in calculations to avoid - // allocating memory. - /** - * @type {Array} - */ - var T_VECTORS = []; - for (var i = 0; i < 10; i++) { T_VECTORS.push(new Vector()); } - - // A pool of arrays of numbers used in calculations to avoid allocating - // memory. - /** - * @type {Array>} - */ - var T_ARRAYS = []; - for (var i = 0; i < 5; i++) { T_ARRAYS.push([]); } - - // Temporary response used for polygon hit detection. - /** - * @type {Response} - */ - var T_RESPONSE = new Response(); - - // Tiny "point" polygon used for polygon hit detection. - /** - * @type {Polygon} - */ - var TEST_POINT = new Box(new Vector(), 0.000001, 0.000001).toPolygon(); - - // ## Helper Functions - - // Flattens the specified array of points onto a unit vector axis, - // resulting in a one dimensional range of the minimum and - // maximum value on that axis. - /** - * @param {Array} points The points to flatten. - * @param {Vector} normal The unit vector axis to flatten on. - * @param {Array} result An array. After calling this function, - * result[0] will be the minimum value, - * result[1] will be the maximum value. - */ - function flattenPointsOn(points, normal, result) { - var min = Number.MAX_VALUE; - var max = -Number.MAX_VALUE; - var len = points.length; - for (var i = 0; i < len; i++) { - // The magnitude of the projection of the point onto the normal - var dot = points[i].dot(normal); - if (dot < min) { min = dot; } - if (dot > max) { max = dot; } - } - result[0] = min; result[1] = max; - } + /***/ + }, - // Check whether two convex polygons are separated by the specified - // axis (must be a unit vector). - /** - * @param {Vector} aPos The position of the first polygon. - * @param {Vector} bPos The position of the second polygon. - * @param {Array} aPoints The points in the first polygon. - * @param {Array} bPoints The points in the second polygon. - * @param {Vector} axis The axis (unit sized) to test against. The points of both polygons - * will be projected onto this axis. - * @param {Response=} response A Response object (optional) which will be populated - * if the axis is not a separating axis. - * @return {boolean} true if it is a separating axis, false otherwise. If false, - * and a response is passed in, information about how much overlap and - * the direction of the overlap will be populated. - */ - function isSeparatingAxis(aPos, bPos, aPoints, bPoints, axis, response) { - var rangeA = T_ARRAYS.pop(); - var rangeB = T_ARRAYS.pop(); - // The magnitude of the offset between the two polygons - var offsetV = T_VECTORS.pop().copy(bPos).sub(aPos); - var projectedOffset = offsetV.dot(axis); - // Project the polygons onto the axis. - flattenPointsOn(aPoints, axis, rangeA); - flattenPointsOn(bPoints, axis, rangeB); - // Move B's range to its position relative to A. - rangeB[0] += projectedOffset; - rangeB[1] += projectedOffset; - // Check if there is a gap. If there is, this is a separating axis and we can stop - if (rangeA[0] > rangeB[1] || rangeB[0] > rangeA[1]) { - T_VECTORS.push(offsetV); - T_ARRAYS.push(rangeA); - T_ARRAYS.push(rangeB); - return true; - } - // This is not a separating axis. If we're calculating a response, calculate the overlap. - if (response) { - var overlap = 0; - // A starts further left than B - if (rangeA[0] < rangeB[0]) { - response['aInB'] = false; - // A ends before B does. We have to pull A out of B - if (rangeA[1] < rangeB[1]) { - overlap = rangeA[1] - rangeB[0]; - response['bInA'] = false; - // B is fully inside A. Pick the shortest way out. - } else { - var option1 = rangeA[1] - rangeB[0]; - var option2 = rangeB[1] - rangeA[0]; - overlap = option1 < option2 ? option1 : -option2; - } - // B starts further left than A - } else { - response['bInA'] = false; - // B ends before A ends. We have to push A out of B - if (rangeA[1] > rangeB[1]) { - overlap = rangeA[0] - rangeB[1]; - response['aInB'] = false; - // A is fully inside B. Pick the shortest way out. - } else { - var option1 = rangeA[1] - rangeB[0]; - var option2 = rangeB[1] - rangeA[0]; - overlap = option1 < option2 ? option1 : -option2; - } - } - // If this is the smallest amount of overlap we've seen so far, set it as the minimum overlap. - var absOverlap = Math.abs(overlap); - if (absOverlap < response['overlap']) { - response['overlap'] = absOverlap; - response['overlapN'].copy(axis); - if (overlap < 0) { - response['overlapN'].reverse(); + /***/ "./src/bodies/circle.ts": + /*!******************************!*\ + !*** ./src/bodies/circle.ts ***! + \******************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Circle = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); + /** + * collider - circle + */ + class Circle extends sat_1.Circle { + /** + * collider - circle + */ + constructor(position, radius, options) { + super((0, utils_1.ensureVectorPoint)(position), radius); + /** + * offset copy without angle applied + */ + this.offsetCopy = { x: 0, y: 0 }; + /** + * was the polygon modified and needs update in the next checkCollision + */ + this.dirty = false; + /* + * circles are convex + */ + this.isConvex = true; + /** + * circle type + */ + this.type = model_1.BodyType.Circle; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Circle; + /** + * always centered + */ + this.isCentered = true; + (0, utils_1.extendBody)(this, options); + this.unscaledRadius = radius; + } + /** + * get this.pos.x + */ + get x() { + return this.pos.x; + } + /** + * updating this.pos.x by this.x = x updates AABB + */ + set x(x) { + this.pos.x = x; + this.markAsDirty(); + } + /** + * get this.pos.y + */ + get y() { + return this.pos.y; + } + /** + * updating this.pos.y by this.y = y updates AABB + */ + set y(y) { + this.pos.y = y; + this.markAsDirty(); + } + /** + * allow get scale + */ + get scale() { + return this.r / this.unscaledRadius; + } + /** + * shorthand for setScale() + */ + set scale(scale) { + this.setScale(scale); + } + /** + * scaleX = scale in case of Circles + */ + get scaleX() { + return this.scale; + } + /** + * scaleY = scale in case of Circles + */ + get scaleY() { + return this.scale; + } + /** + * group for collision filtering + */ + get group() { + return this._group; + } + set group(group) { + this._group = (0, utils_1.getGroup)(group); + } + /** + * update position BY MOVING FORWARD IN ANGLE DIRECTION + */ + move(speed = 1, updateNow = true) { + (0, utils_1.move)(this, speed, updateNow); + return this; + } + /** + * update position BY TELEPORTING + */ + setPosition(x, y, updateNow = true) { + this.pos.x = x; + this.pos.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * update scale + */ + setScale(scaleX, _scaleY = scaleX, updateNow = true) { + this.r = this.unscaledRadius * Math.abs(scaleX); + this.markAsDirty(updateNow); + return this; + } + /** + * set rotation + */ + setAngle(angle, updateNow = true) { + this.angle = angle; + const { x, y } = this.getOffsetWithAngle(); + this.offset.x = x; + this.offset.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * set offset from center + */ + setOffset(offset, updateNow = true) { + this.offsetCopy.x = offset.x; + this.offsetCopy.y = offset.y; + const { x, y } = this.getOffsetWithAngle(); + this.offset.x = x; + this.offset.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * get body bounding box, without padding + */ + getAABBAsBBox() { + const x = this.pos.x + this.offset.x; + const y = this.pos.y + this.offset.y; + return { + minX: x - this.r, + maxX: x + this.r, + minY: y - this.r, + maxY: y + this.r, + }; + } + /** + * Draws collider on a CanvasRenderingContext2D's current path + */ + draw(context) { + const x = this.pos.x + this.offset.x; + const y = this.pos.y + this.offset.y; + const r = Math.abs(this.r); + if (this.isTrigger) { + const max = Math.max(8, this.r); + for (let i = 0; i < max; i++) { + const arc = (i / max) * 2 * Math.PI; + const arcPrev = ((i - 1) / max) * 2 * Math.PI; + const fromX = x + Math.cos(arcPrev) * this.r; + const fromY = y + Math.sin(arcPrev) * this.r; + const toX = x + Math.cos(arc) * this.r; + const toY = y + Math.sin(arc) * this.r; + (0, utils_1.dashLineTo)(context, fromX, fromY, toX, toY); + } + } else { + context.moveTo(x + r, y); + context.arc(x, y, r, 0, Math.PI * 2); + } + } + /** + * Draws Bounding Box on canvas context + */ + drawBVH(context) { + (0, utils_1.drawBVH)(context, this); + } + /** + * inner function for after position change update aabb in system + */ + updateBody(updateNow = this.dirty) { + var _a; + if (updateNow) { + (_a = this.system) === null || _a === void 0 + ? void 0 + : _a.insert(this); + this.dirty = false; + } + } + /** + * update instantly or mark as dirty + */ + markAsDirty(updateNow = false) { + if (updateNow) { + this.updateBody(true); + } else { + this.dirty = true; + } + } + /** + * internal for getting offset with applied angle + */ + getOffsetWithAngle() { + if ((!this.offsetCopy.x && !this.offsetCopy.y) || !this.angle) { + return this.offsetCopy; + } + const sin = Math.sin(this.angle); + const cos = Math.cos(this.angle); + const x = this.offsetCopy.x * cos - this.offsetCopy.y * sin; + const y = this.offsetCopy.x * sin + this.offsetCopy.y * cos; + return { x, y }; + } } - } - } - T_VECTORS.push(offsetV); - T_ARRAYS.push(rangeA); - T_ARRAYS.push(rangeB); - return false; - } - SAT['isSeparatingAxis'] = isSeparatingAxis; - - // Calculates which Voronoi region a point is on a line segment. - // It is assumed that both the line and the point are relative to `(0,0)` - // - // | (0) | - // (-1) [S]--------------[E] (1) - // | (0) | - /** - * @param {Vector} line The line segment. - * @param {Vector} point The point. - * @return {number} LEFT_VORONOI_REGION (-1) if it is the left region, - * MIDDLE_VORONOI_REGION (0) if it is the middle region, - * RIGHT_VORONOI_REGION (1) if it is the right region. - */ - function voronoiRegion(line, point) { - var len2 = line.len2(); - var dp = point.dot(line); - // If the point is beyond the start of the line, it is in the - // left voronoi region. - if (dp < 0) { return LEFT_VORONOI_REGION; } - // If the point is beyond the end of the line, it is in the - // right voronoi region. - else if (dp > len2) { return RIGHT_VORONOI_REGION; } - // Otherwise, it's in the middle one. - else { return MIDDLE_VORONOI_REGION; } - } - // Constants for Voronoi regions - /** - * @const - */ - var LEFT_VORONOI_REGION = -1; - /** - * @const - */ - var MIDDLE_VORONOI_REGION = 0; - /** - * @const - */ - var RIGHT_VORONOI_REGION = 1; - - // ## Collision Tests - - // Check if a point is inside a circle. - /** - * @param {Vector} p The point to test. - * @param {Circle} c The circle to test. - * @return {boolean} true if the point is inside the circle, false if it is not. - */ - function pointInCircle(p, c) { - var differenceV = T_VECTORS.pop().copy(p).sub(c['pos']).sub(c['offset']); - var radiusSq = c['r'] * c['r']; - var distanceSq = differenceV.len2(); - T_VECTORS.push(differenceV); - // If the distance between is smaller than the radius then the point is inside the circle. - return distanceSq <= radiusSq; - } - SAT['pointInCircle'] = pointInCircle; - - // Check if a point is inside a convex polygon. - /** - * @param {Vector} p The point to test. - * @param {Polygon} poly The polygon to test. - * @return {boolean} true if the point is inside the polygon, false if it is not. - */ - function pointInPolygon(p, poly) { - TEST_POINT['pos'].copy(p); - T_RESPONSE.clear(); - var result = testPolygonPolygon(TEST_POINT, poly, T_RESPONSE); - if (result) { - result = T_RESPONSE['aInB']; - } - return result; - } - SAT['pointInPolygon'] = pointInPolygon; - - // Check if two circles collide. - /** - * @param {Circle} a The first circle. - * @param {Circle} b The second circle. - * @param {Response=} response Response object (optional) that will be populated if - * the circles intersect. - * @return {boolean} true if the circles intersect, false if they don't. - */ - function testCircleCircle(a, b, response) { - // Check if the distance between the centers of the two - // circles is greater than their combined radius. - var differenceV = T_VECTORS.pop().copy(b['pos']).add(b['offset']).sub(a['pos']).sub(a['offset']); - var totalRadius = a['r'] + b['r']; - var totalRadiusSq = totalRadius * totalRadius; - var distanceSq = differenceV.len2(); - // If the distance is bigger than the combined radius, they don't intersect. - if (distanceSq > totalRadiusSq) { - T_VECTORS.push(differenceV); - return false; - } - // They intersect. If we're calculating a response, calculate the overlap. - if (response) { - var dist = Math.sqrt(distanceSq); - response['a'] = a; - response['b'] = b; - response['overlap'] = totalRadius - dist; - response['overlapN'].copy(differenceV.normalize()); - response['overlapV'].copy(differenceV).scale(response['overlap']); - response['aInB'] = a['r'] <= b['r'] && dist <= b['r'] - a['r']; - response['bInA'] = b['r'] <= a['r'] && dist <= a['r'] - b['r']; - } - T_VECTORS.push(differenceV); - return true; - } - SAT['testCircleCircle'] = testCircleCircle; - - // Check if a polygon and a circle collide. - /** - * @param {Polygon} polygon The polygon. - * @param {Circle} circle The circle. - * @param {Response=} response Response object (optional) that will be populated if - * they interset. - * @return {boolean} true if they intersect, false if they don't. - */ - function testPolygonCircle(polygon, circle, response) { - // Get the position of the circle relative to the polygon. - var circlePos = T_VECTORS.pop().copy(circle['pos']).add(circle['offset']).sub(polygon['pos']); - var radius = circle['r']; - var radius2 = radius * radius; - var points = polygon['calcPoints']; - var len = points.length; - var edge = T_VECTORS.pop(); - var point = T_VECTORS.pop(); - - // For each edge in the polygon: - for (var i = 0; i < len; i++) { - var next = i === len - 1 ? 0 : i + 1; - var prev = i === 0 ? len - 1 : i - 1; - var overlap = 0; - var overlapN = null; - - // Get the edge. - edge.copy(polygon['edges'][i]); - // Calculate the center of the circle relative to the starting point of the edge. - point.copy(circlePos).sub(points[i]); - - // If the distance between the center of the circle and the point - // is bigger than the radius, the polygon is definitely not fully in - // the circle. - if (response && point.len2() > radius2) { - response['aInB'] = false; - } + exports.Circle = Circle; - // Calculate which Voronoi region the center of the circle is in. - var region = voronoiRegion(edge, point); - // If it's the left region: - if (region === LEFT_VORONOI_REGION) { - // We need to make sure we're in the RIGHT_VORONOI_REGION of the previous edge. - edge.copy(polygon['edges'][prev]); - // Calculate the center of the circle relative the starting point of the previous edge - var point2 = T_VECTORS.pop().copy(circlePos).sub(points[prev]); - region = voronoiRegion(edge, point2); - if (region === RIGHT_VORONOI_REGION) { - // It's in the region we want. Check if the circle intersects the point. - var dist = point.len(); - if (dist > radius) { - // No intersection - T_VECTORS.push(circlePos); - T_VECTORS.push(edge); - T_VECTORS.push(point); - T_VECTORS.push(point2); - return false; - } else if (response) { - // It intersects, calculate the overlap. - response['bInA'] = false; - overlapN = point.normalize(); - overlap = radius - dist; + /***/ + }, + + /***/ "./src/bodies/ellipse.ts": + /*!*******************************!*\ + !*** ./src/bodies/ellipse.ts ***! + \*******************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Ellipse = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const polygon_1 = __webpack_require__( + /*! ./polygon */ "./src/bodies/polygon.ts", + ); + const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); + /** + * collider - ellipse + */ + class Ellipse extends polygon_1.Polygon { + /** + * collider - ellipse + */ + constructor( + position, + radiusX, + radiusY = radiusX, + step = (radiusX + radiusY) / Math.PI, + options, + ) { + super( + position, + (0, utils_1.createEllipse)(radiusX, radiusY, step), + options, + ); + /** + * ellipse type + */ + this.type = model_1.BodyType.Ellipse; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Ellipse; + /** + * ellipses are convex + */ + this.isConvex = true; + this._radiusX = radiusX; + this._radiusY = radiusY; + this._step = step; } - } - T_VECTORS.push(point2); - // If it's the right region: - } else if (region === RIGHT_VORONOI_REGION) { - // We need to make sure we're in the left region on the next edge - edge.copy(polygon['edges'][next]); - // Calculate the center of the circle relative to the starting point of the next edge. - point.copy(circlePos).sub(points[next]); - region = voronoiRegion(edge, point); - if (region === LEFT_VORONOI_REGION) { - // It's in the region we want. Check if the circle intersects the point. - var dist = point.len(); - if (dist > radius) { - // No intersection - T_VECTORS.push(circlePos); - T_VECTORS.push(edge); - T_VECTORS.push(point); - return false; - } else if (response) { - // It intersects, calculate the overlap. - response['bInA'] = false; - overlapN = point.normalize(); - overlap = radius - dist; + /** + * flag to set is body centered + */ + set isCentered(_isCentered) {} + /** + * is body centered? + */ + get isCentered() { + return true; } - } - // Otherwise, it's the middle region: - } else { - // Need to check if the circle is intersecting the edge, - // Change the edge into its "edge normal". - var normal = edge.perp().normalize(); - // Find the perpendicular distance between the center of the - // circle and the edge. - var dist = point.dot(normal); - var distAbs = Math.abs(dist); - // If the circle is on the outside of the edge, there is no intersection. - if (dist > 0 && distAbs > radius) { - // No intersection - T_VECTORS.push(circlePos); - T_VECTORS.push(normal); - T_VECTORS.push(point); - return false; - } else if (response) { - // It intersects, calculate the overlap. - overlapN = normal; - overlap = radius - dist; - // If the center of the circle is on the outside of the edge, or part of the - // circle is on the outside, the circle is not fully inside the polygon. - if (dist >= 0 || overlap < 2 * radius) { - response['bInA'] = false; + /** + * get ellipse step number + */ + get step() { + return this._step; + } + /** + * set ellipse step number + */ + set step(step) { + this._step = step; + this.setPoints( + (0, utils_1.createEllipse)( + this._radiusX, + this._radiusY, + this._step, + ), + ); + } + /** + * get ellipse radiusX + */ + get radiusX() { + return this._radiusX; + } + /** + * set ellipse radiusX, update points + */ + set radiusX(radiusX) { + this._radiusX = radiusX; + this.setPoints( + (0, utils_1.createEllipse)( + this._radiusX, + this._radiusY, + this._step, + ), + ); + } + /** + * get ellipse radiusY + */ + get radiusY() { + return this._radiusY; + } + /** + * set ellipse radiusY, update points + */ + set radiusY(radiusY) { + this._radiusY = radiusY; + this.setPoints( + (0, utils_1.createEllipse)( + this._radiusX, + this._radiusY, + this._step, + ), + ); + } + /** + * do not attempt to use Polygon.center() + */ + center() { + return; + } + /** + * do not attempt to use Polygon.updateIsConvex() + */ + updateIsConvex() { + return; } } - } + exports.Ellipse = Ellipse; - // If this is the smallest overlap we've seen, keep it. - // (overlapN may be null if the circle was in the wrong Voronoi region). - if (overlapN && response && Math.abs(overlap) < Math.abs(response['overlap'])) { - response['overlap'] = overlap; - response['overlapN'].copy(overlapN); - } - } + /***/ + }, - // Calculate the final overlap vector - based on the smallest overlap. - if (response) { - response['a'] = polygon; - response['b'] = circle; - response['overlapV'].copy(response['overlapN']).scale(response['overlap']); - } - T_VECTORS.push(circlePos); - T_VECTORS.push(edge); - T_VECTORS.push(point); - return true; - } - SAT['testPolygonCircle'] = testPolygonCircle; - - // Check if a circle and a polygon collide. - // - // **NOTE:** This is slightly less efficient than polygonCircle as it just - // runs polygonCircle and reverses everything at the end. - /** - * @param {Circle} circle The circle. - * @param {Polygon} polygon The polygon. - * @param {Response=} response Response object (optional) that will be populated if - * they interset. - * @return {boolean} true if they intersect, false if they don't. - */ - function testCirclePolygon(circle, polygon, response) { - // Test the polygon against the circle. - var result = testPolygonCircle(polygon, circle, response); - if (result && response) { - // Swap A and B in the response. - var a = response['a']; - var aInB = response['aInB']; - response['overlapN'].reverse(); - response['overlapV'].reverse(); - response['a'] = response['b']; - response['b'] = a; - response['aInB'] = response['bInA']; - response['bInA'] = aInB; - } - return result; - } - SAT['testCirclePolygon'] = testCirclePolygon; - - // Checks whether polygons collide. - /** - * @param {Polygon} a The first polygon. - * @param {Polygon} b The second polygon. - * @param {Response=} response Response object (optional) that will be populated if - * they interset. - * @return {boolean} true if they intersect, false if they don't. - */ - function testPolygonPolygon(a, b, response) { - var aPoints = a['calcPoints']; - var aLen = aPoints.length; - var bPoints = b['calcPoints']; - var bLen = bPoints.length; - // If any of the edge normals of A is a separating axis, no intersection. - for (var i = 0; i < aLen; i++) { - if (isSeparatingAxis(a['pos'], b['pos'], aPoints, bPoints, a['normals'][i], response)) { - return false; - } - } - // If any of the edge normals of B is a separating axis, no intersection. - for (var i = 0; i < bLen; i++) { - if (isSeparatingAxis(a['pos'], b['pos'], aPoints, bPoints, b['normals'][i], response)) { - return false; - } - } - // Since none of the edge normals of A or B are a separating axis, there is an intersection - // and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the - // final overlap vector. - if (response) { - response['a'] = a; - response['b'] = b; - response['overlapV'].copy(response['overlapN']).scale(response['overlap']); - } - return true; - } - SAT['testPolygonPolygon'] = testPolygonPolygon; + /***/ "./src/bodies/line.ts": + /*!****************************!*\ + !*** ./src/bodies/line.ts ***! + \****************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Line = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const polygon_1 = __webpack_require__( + /*! ./polygon */ "./src/bodies/polygon.ts", + ); + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); + /** + * collider - line + */ + class Line extends polygon_1.Polygon { + /** + * collider - line from start to end + */ + constructor(start, end, options) { + super( + start, + [ + { x: 0, y: 0 }, + { x: end.x - start.x, y: end.y - start.y }, + ], + options, + ); + /** + * line type + */ + this.type = model_1.BodyType.Line; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Line; + /** + * line is convex + */ + this.isConvex = true; + if (this.calcPoints.length === 1 || !end) { + console.error({ start, end }); + throw new Error("No end point for line provided"); + } + } + get start() { + return { + x: this.x + this.calcPoints[0].x, + y: this.y + this.calcPoints[0].y, + }; + } + set start({ x, y }) { + this.x = x; + this.y = y; + } + get end() { + return { + x: this.x + this.calcPoints[1].x, + y: this.y + this.calcPoints[1].y, + }; + } + set end({ x, y }) { + this.points[1].x = x - this.start.x; + this.points[1].y = y - this.start.y; + this.setPoints(this.points); + } + getCentroid() { + return new sat_1.Vector( + (this.end.x - this.start.x) / 2, + (this.end.y - this.start.y) / 2, + ); + } + /** + * do not attempt to use Polygon.updateIsConvex() + */ + updateIsConvex() { + return; + } + } + exports.Line = Line; - return SAT; -})); + /***/ + }, + /***/ "./src/bodies/point.ts": + /*!*****************************!*\ + !*** ./src/bodies/point.ts ***! + \*****************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Point = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const box_1 = __webpack_require__(/*! ./box */ "./src/bodies/box.ts"); + const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); + /** + * collider - point (very tiny box) + */ + class Point extends box_1.Box { + /** + * collider - point (very tiny box) + */ + constructor(position, options) { + super( + (0, utils_1.ensureVectorPoint)(position), + 0.001, + 0.001, + options, + ); + /** + * point type + */ + this.type = model_1.BodyType.Point; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Point; + } + } + exports.Point = Point; -/***/ }), + /***/ + }, -/***/ "./src/base-system.ts": -/*!****************************!*\ - !*** ./src/base-system.ts ***! - \****************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.BaseSystem = void 0; -const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); -const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts"); -const optimized_1 = __webpack_require__(/*! ./optimized */ "./src/optimized.ts"); -const box_1 = __webpack_require__(/*! ./bodies/box */ "./src/bodies/box.ts"); -const circle_1 = __webpack_require__(/*! ./bodies/circle */ "./src/bodies/circle.ts"); -const ellipse_1 = __webpack_require__(/*! ./bodies/ellipse */ "./src/bodies/ellipse.ts"); -const line_1 = __webpack_require__(/*! ./bodies/line */ "./src/bodies/line.ts"); -const point_1 = __webpack_require__(/*! ./bodies/point */ "./src/bodies/point.ts"); -const polygon_1 = __webpack_require__(/*! ./bodies/polygon */ "./src/bodies/polygon.ts"); -/** - * very base collision system (create, insert, update, draw, remove) - */ -class BaseSystem extends model_1.RBush { - /** - * create point at position with options and add to system - */ - createPoint(position, options) { - const point = new point_1.Point(position, options); - this.insert(point); - return point; - } - /** - * create line at position with options and add to system - */ - createLine(start, end, options) { - const line = new line_1.Line(start, end, options); - this.insert(line); - return line; - } - /** - * create circle at position with options and add to system - */ - createCircle(position, radius, options) { - const circle = new circle_1.Circle(position, radius, options); - this.insert(circle); - return circle; - } - /** - * create box at position with options and add to system - */ - createBox(position, width, height, options) { - const box = new box_1.Box(position, width, height, options); - this.insert(box); - return box; - } - /** - * create ellipse at position with options and add to system - */ - createEllipse(position, radiusX, radiusY = radiusX, step, options) { - const ellipse = new ellipse_1.Ellipse(position, radiusX, radiusY, step, options); - this.insert(ellipse); - return ellipse; - } - /** - * create polygon at position with options and add to system - */ - createPolygon(position, points, options) { - const polygon = new polygon_1.Polygon(position, points, options); - this.insert(polygon); - return polygon; - } - /** - * re-insert body into collision tree and update its bbox - * every body can be part of only one system - */ - insert(body) { - body.bbox = body.getAABBAsBBox(); - if (body.system) { - // allow end if body inserted and not moved - if (!(0, utils_1.bodyMoved)(body)) { - return this; - } - // old bounding box *needs* to be removed - body.system.remove(body); - } - // only then we update min, max - body.minX = body.bbox.minX - body.padding; - body.minY = body.bbox.minY - body.padding; - body.maxX = body.bbox.maxX + body.padding; - body.maxY = body.bbox.maxY + body.padding; - // reinsert bounding box to collision tree - return super.insert(body); - } - /** - * updates body in collision tree - */ - updateBody(body) { - body.updateBody(); - } - /** - * update all bodies aabb - */ - update() { - (0, optimized_1.forEach)(this.all(), (body) => { - this.updateBody(body); - }); - } - /** - * draw exact bodies colliders outline - */ - draw(context) { - (0, optimized_1.forEach)(this.all(), (body) => { - body.draw(context); + /***/ "./src/bodies/polygon.ts": + /*!*******************************!*\ + !*** ./src/bodies/polygon.ts ***! + \*******************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Polygon = exports.isSimple = void 0; + const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); + const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); + const optimized_1 = __webpack_require__( + /*! ../optimized */ "./src/optimized.ts", + ); + const poly_decomp_es_1 = __webpack_require__( + /*! poly-decomp-es */ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js", + ); + Object.defineProperty(exports, "isSimple", { + enumerable: true, + get: function () { + return poly_decomp_es_1.isSimple; + }, }); - } - /** - * draw bounding boxes hierarchy outline - */ - drawBVH(context) { - const drawChildren = (body) => { - (0, utils_1.drawBVH)(context, body); - if (body.children) { - (0, optimized_1.forEach)(body.children, drawChildren); + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); + /** + * collider - polygon + */ + class Polygon extends sat_1.Polygon { + /** + * collider - polygon + */ + constructor(position, points, options) { + super( + (0, utils_1.ensureVectorPoint)(position), + (0, utils_1.ensurePolygonPoints)(points), + ); + /** + * was the polygon modified and needs update in the next checkCollision + */ + this.dirty = false; + /** + * type of body + */ + this.type = model_1.BodyType.Polygon; + /** + * faster than type + */ + this.typeGroup = model_1.BodyGroup.Polygon; + /** + * is body centered + */ + this.centered = false; + /** + * scale Vector of body + */ + this.scaleVector = { x: 1, y: 1 }; + if (!points.length) { + throw new Error("No points in polygon"); } - }; - (0, optimized_1.forEach)(this.data.children, drawChildren); - } - /** - * remove body aabb from collision tree - */ - remove(body, equals) { - body.system = undefined; - return super.remove(body, equals); - } - /** - * get object potential colliders - * @deprecated because it's slower to use than checkOne() or checkAll() - */ - getPotentials(body) { - // filter here is required as collides with self - return (0, optimized_1.filter)(this.search(body), (candidate) => candidate !== body); - } - /** - * used to find body deep inside data with finder function returning boolean found or not - */ - traverse(traverseFunction, { children } = this.data) { - return children === null || children === void 0 ? void 0 : children.find((body, index) => { - if (!body) { - return false; + (0, utils_1.extendBody)(this, options); + } + /** + * flag to set is polygon centered + */ + set isCentered(isCentered) { + if (this.centered === isCentered) { + return; + } + const centroid = this.getCentroidWithoutRotation(); + if (centroid.x || centroid.y) { + const x = centroid.x * (isCentered ? 1 : -1); + const y = centroid.y * (isCentered ? 1 : -1); + this.translate(-x, -y); + } + this.centered = isCentered; + } + /** + * is polygon centered? + */ + get isCentered() { + return this.centered; + } + get x() { + return this.pos.x; + } + /** + * updating this.pos.x by this.x = x updates AABB + */ + set x(x) { + this.pos.x = x; + this.markAsDirty(); + } + get y() { + return this.pos.y; + } + /** + * updating this.pos.y by this.y = y updates AABB + */ + set y(y) { + this.pos.y = y; + this.markAsDirty(); + } + /** + * allow exact getting of scale x - use setScale(x, y) to set + */ + get scaleX() { + return this.scaleVector.x; + } + /** + * allow exact getting of scale y - use setScale(x, y) to set + */ + get scaleY() { + return this.scaleVector.y; + } + /** + * allow approx getting of scale + */ + get scale() { + return (this.scaleVector.x + this.scaleVector.y) / 2; + } + /** + * allow easier setting of scale + */ + set scale(scale) { + this.setScale(scale); + } + /** + * group for collision filtering + */ + get group() { + return this._group; + } + set group(group) { + this._group = (0, utils_1.getGroup)(group); + } + /** + * update position BY MOVING FORWARD IN ANGLE DIRECTION + */ + move(speed = 1, updateNow = true) { + (0, utils_1.move)(this, speed, updateNow); + return this; + } + /** + * update position BY TELEPORTING + */ + setPosition(x, y, updateNow = true) { + this.pos.x = x; + this.pos.y = y; + this.markAsDirty(updateNow); + return this; + } + /** + * update scale + */ + setScale(x, y = x, updateNow = true) { + this.scaleVector.x = Math.abs(x); + this.scaleVector.y = Math.abs(y); + super.setPoints( + (0, optimized_1.map)(this.points, (point, index) => { + point.x = this.pointsBackup[index].x * this.scaleVector.x; + point.y = this.pointsBackup[index].y * this.scaleVector.y; + return point; + }), + ); + this.markAsDirty(updateNow); + return this; + } + setAngle(angle, updateNow = true) { + super.setAngle(angle); + this.markAsDirty(updateNow); + return this; + } + setOffset(offset, updateNow = true) { + super.setOffset(offset); + this.markAsDirty(updateNow); + return this; + } + /** + * get body bounding box, without padding + */ + getAABBAsBBox() { + const { pos, w, h } = this.getAABBAsBox(); + return { + minX: pos.x, + minY: pos.y, + maxX: pos.x + w, + maxY: pos.y + h, + }; + } + /** + * Draws exact collider on canvas context + */ + draw(context) { + (0, utils_1.drawPolygon)(context, this, this.isTrigger); + } + /** + * Draws Bounding Box on canvas context + */ + drawBVH(context) { + (0, utils_1.drawBVH)(context, this); + } + /** + * get body centroid without applied angle + */ + getCentroidWithoutRotation() { + // keep angle copy + const angle = this.angle; + if (angle) { + // reset angle for get centroid + this.setAngle(0); + // get centroid + const centroid = this.getCentroid(); + // revert angle change + this.setAngle(angle); + return centroid; + } + return this.getCentroid(); + } + /** + * sets polygon points to new array of vectors + */ + setPoints(points) { + super.setPoints(points); + this.updateIsConvex(); + this.pointsBackup = (0, utils_1.clonePointsArray)(points); + return this; + } + /** + * translates polygon points in x, y direction + */ + translate(x, y) { + super.translate(x, y); + this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); + return this; + } + /** + * rotates polygon points by angle, in radians + */ + rotate(angle) { + super.rotate(angle); + this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); + return this; + } + /** + * if true, polygon is not an invalid, self-crossing polygon + */ + isSimple() { + return (0, poly_decomp_es_1.isSimple)( + this.calcPoints.map(utils_1.mapVectorToArray), + ); + } + /** + * inner function for after position change update aabb in system and convex inner polygons + */ + updateBody(updateNow = this.dirty) { + var _a; + if (updateNow) { + this.updateConvexPolygonPositions(); + (_a = this.system) === null || _a === void 0 + ? void 0 + : _a.insert(this); + this.dirty = false; } - if (body.typeGroup && traverseFunction(body, children, index)) { - return true; + } + retranslate(isCentered = this.isCentered) { + const centroid = this.getCentroidWithoutRotation(); + if (centroid.x || centroid.y) { + const x = centroid.x * (isCentered ? 1 : -1); + const y = centroid.y * (isCentered ? 1 : -1); + this.translate(-x, -y); } - // if callback returns true, ends forEach - if (body.children) { - this.traverse(traverseFunction, body); + } + /** + * update instantly or mark as dirty + */ + markAsDirty(updateNow = false) { + if (updateNow) { + this.updateBody(true); + } else { + this.dirty = true; } - }); - } -} -exports.BaseSystem = BaseSystem; - + } + /** + * update the position of the decomposed convex polygons (if any), called + * after the position of the body has changed + */ + updateConvexPolygonPositions() { + if (this.isConvex || !this.convexPolygons) { + return; + } + (0, optimized_1.forEach)(this.convexPolygons, (polygon) => { + polygon.pos.x = this.pos.x; + polygon.pos.y = this.pos.y; + if (polygon.angle !== this.angle) { + // Must use setAngle to recalculate the points of the Polygon + polygon.setAngle(this.angle); + } + }); + } + /** + * returns body split into convex polygons, or empty array for convex bodies + */ + getConvex() { + if ( + (this.typeGroup && + this.typeGroup !== model_1.BodyGroup.Polygon) || + this.points.length < 4 + ) { + return []; + } + const points = (0, optimized_1.map)( + this.calcPoints, + utils_1.mapVectorToArray, + ); + return (0, poly_decomp_es_1.quickDecomp)(points); + } + /** + * updates convex polygons cache in body + */ + updateConvexPolygons(convex = this.getConvex()) { + if (this.isConvex) { + return; + } + if (!this.convexPolygons) { + this.convexPolygons = []; + } + (0, optimized_1.forEach)(convex, (points, index) => { + // lazy create + if (!this.convexPolygons[index]) { + this.convexPolygons[index] = new sat_1.Polygon(); + } + this.convexPolygons[index].pos.x = this.pos.x; + this.convexPolygons[index].pos.y = this.pos.y; + this.convexPolygons[index].angle = this.angle; + this.convexPolygons[index].setPoints( + (0, utils_1.ensurePolygonPoints)( + (0, optimized_1.map)(points, utils_1.mapArrayToVector), + ), + ); + }); + // trim array length + this.convexPolygons.length = convex.length; + } + /** + * after points update set is convex + */ + updateIsConvex() { + // all other types other than polygon are always convex + const convex = this.getConvex(); + // everything with empty array or one element array + this.isConvex = convex.length <= 1; + this.updateConvexPolygons(convex); + } + } + exports.Polygon = Polygon; -/***/ }), + /***/ + }, -/***/ "./src/bodies/box.ts": -/*!***************************!*\ - !*** ./src/bodies/box.ts ***! - \***************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Box = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const polygon_1 = __webpack_require__(/*! ./polygon */ "./src/bodies/polygon.ts"); -const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -/** - * collider - box - */ -class Box extends polygon_1.Polygon { - /** - * collider - box - */ - constructor(position, width, height, options) { - super(position, (0, utils_1.createBox)(width, height), options); + /***/ "./src/intersect.ts": + /*!**************************!*\ + !*** ./src/intersect.ts ***! + \**************************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.ensureConvex = ensureConvex; + exports.polygonInCircle = polygonInCircle; + exports.pointInPolygon = pointInPolygon; + exports.polygonInPolygon = polygonInPolygon; + exports.pointOnCircle = pointOnCircle; + exports.circleInCircle = circleInCircle; + exports.circleInPolygon = circleInPolygon; + exports.circleOutsidePolygon = circleOutsidePolygon; + exports.intersectLineCircle = intersectLineCircle; + exports.intersectLineLineFast = intersectLineLineFast; + exports.intersectLineLine = intersectLineLine; + exports.intersectLinePolygon = intersectLinePolygon; + const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); + const optimized_1 = __webpack_require__( + /*! ./optimized */ "./src/optimized.ts", + ); + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); /** - * type of body + * replace body with array of related convex polygons */ - this.type = model_1.BodyType.Box; + function ensureConvex(body) { + if (body.isConvex || body.typeGroup !== model_1.BodyGroup.Polygon) { + return [body]; + } + return body.convexPolygons; + } + function polygonInCircle(polygon, circle) { + return (0, optimized_1.every)(polygon.calcPoints, (p) => + (0, sat_1.pointInCircle)( + { x: p.x + polygon.pos.x, y: p.y + polygon.pos.y }, + circle, + ), + ); + } + function pointInPolygon(point, polygon) { + return (0, optimized_1.some)(ensureConvex(polygon), (convex) => + (0, sat_1.pointInPolygon)(point, convex), + ); + } + function polygonInPolygon(polygonA, polygonB) { + return (0, optimized_1.every)(polygonA.calcPoints, (point) => + pointInPolygon( + { x: point.x + polygonA.pos.x, y: point.y + polygonA.pos.y }, + polygonB, + ), + ); + } /** - * faster than type + * https://stackoverflow.com/a/68197894/1749528 */ - this.typeGroup = model_1.BodyGroup.Box; + function pointOnCircle(point, circle) { + return ( + (point.x - circle.pos.x) * (point.x - circle.pos.x) + + (point.y - circle.pos.y) * (point.y - circle.pos.y) === + circle.r * circle.r + ); + } /** - * boxes are convex + * https://stackoverflow.com/a/68197894/1749528 */ - this.isConvex = true; - this._width = width; - this._height = height; - } - /** - * get box width - */ - get width() { - return this._width; - } - /** - * set box width, update points - */ - set width(width) { - this._width = width; - this.afterUpdateSize(); - } - /** - * get box height - */ - get height() { - return this._height; - } - /** - * set box height, update points - */ - set height(height) { - this._height = height; - this.afterUpdateSize(); - } - /** - * after setting width/height update translate - * see https://github.com/Prozi/detect-collisions/issues/70 - */ - afterUpdateSize() { - if (this.isCentered) { - this.retranslate(false); - } - this.setPoints((0, utils_1.createBox)(this._width, this._height)); - if (this.isCentered) { - this.retranslate(); + function circleInCircle(bodyA, bodyB) { + const x1 = bodyA.pos.x; + const y1 = bodyA.pos.y; + const x2 = bodyB.pos.x; + const y2 = bodyB.pos.y; + const r1 = bodyA.r; + const r2 = bodyB.r; + const distSq = Math.sqrt( + (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2), + ); + return distSq + r2 === r1 || distSq + r2 < r1; } - } - /** - * do not attempt to use Polygon.updateIsConvex() - */ - updateIsConvex() { - return; - } -} -exports.Box = Box; - - -/***/ }), - -/***/ "./src/bodies/circle.ts": -/*!******************************!*\ - !*** ./src/bodies/circle.ts ***! - \******************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Circle = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -/** - * collider - circle - */ -class Circle extends sat_1.Circle { - /** - * collider - circle - */ - constructor(position, radius, options) { - super((0, utils_1.ensureVectorPoint)(position), radius); /** - * offset copy without angle applied + * https://stackoverflow.com/a/68197894/1749528 */ - this.offsetCopy = { x: 0, y: 0 }; + function circleInPolygon(circle, polygon) { + // Circle with radius 0 isn't a circle + if (circle.r === 0) { + return false; + } + // If the center of the circle is not within the polygon, + // then the circle may overlap, but it'll never be "contained" + // so return false + if (!pointInPolygon(circle.pos, polygon)) { + return false; + } + // Necessary add polygon pos to points + const points = (0, optimized_1.map)( + polygon.calcPoints, + ({ x, y }) => ({ + x: x + polygon.pos.x, + y: y + polygon.pos.y, + }), + ); + // If the center of the circle is within the polygon, + // the circle is not outside of the polygon completely. + // so return false. + if ( + (0, optimized_1.some)(points, (point) => + (0, sat_1.pointInCircle)(point, circle), + ) + ) { + return false; + } + // If any line-segment of the polygon intersects the circle, + // the circle is not "contained" + // so return false + if ( + (0, optimized_1.some)(points, (end, index) => { + const start = index + ? points[index - 1] + : points[points.length - 1]; + return intersectLineCircle({ start, end }, circle).length > 0; + }) + ) { + return false; + } + return true; + } /** - * was the polygon modified and needs update in the next checkCollision - */ - this.dirty = false; - /* - * circles are convex + * https://stackoverflow.com/a/68197894/1749528 */ - this.isConvex = true; + function circleOutsidePolygon(circle, polygon) { + // Circle with radius 0 isn't a circle + if (circle.r === 0) { + return false; + } + // If the center of the circle is within the polygon, + // the circle is not outside of the polygon completely. + // so return false. + if (pointInPolygon(circle.pos, polygon)) { + return false; + } + // Necessary add polygon pos to points + const points = (0, optimized_1.map)( + polygon.calcPoints, + ({ x, y }) => ({ + x: x + polygon.pos.x, + y: y + polygon.pos.y, + }), + ); + // If the center of the circle is within the polygon, + // the circle is not outside of the polygon completely. + // so return false. + if ( + (0, optimized_1.some)( + points, + (point) => + (0, sat_1.pointInCircle)(point, circle) || + pointOnCircle(point, circle), + ) + ) { + return false; + } + // If any line-segment of the polygon intersects the circle, + // the circle is not "contained" + // so return false + if ( + (0, optimized_1.some)(points, (end, index) => { + const start = index + ? points[index - 1] + : points[points.length - 1]; + return intersectLineCircle({ start, end }, circle).length > 0; + }) + ) { + return false; + } + return true; + } /** - * circle type + * https://stackoverflow.com/a/37225895/1749528 */ - this.type = model_1.BodyType.Circle; + function intersectLineCircle(line, { pos, r }) { + const v1 = { + x: line.end.x - line.start.x, + y: line.end.y - line.start.y, + }; + const v2 = { x: line.start.x - pos.x, y: line.start.y - pos.y }; + const b = (v1.x * v2.x + v1.y * v2.y) * -2; + const c = (v1.x * v1.x + v1.y * v1.y) * 2; + const d = Math.sqrt( + b * b - (v2.x * v2.x + v2.y * v2.y - r * r) * c * 2, + ); + if (isNaN(d)) { + // no intercept + return []; + } + const u1 = (b - d) / c; // these represent the unit distance of point one and two on the line + const u2 = (b + d) / c; + const results = []; // return array + if (u1 <= 1 && u1 >= 0) { + // add point if on the line segment + results.push({ + x: line.start.x + v1.x * u1, + y: line.start.y + v1.y * u1, + }); + } + if (u2 <= 1 && u2 >= 0) { + // second add point if on the line segment + results.push({ + x: line.start.x + v1.x * u2, + y: line.start.y + v1.y * u2, + }); + } + return results; + } /** - * faster than type + * helper for intersectLineLineFast */ - this.typeGroup = model_1.BodyGroup.Circle; + function isTurn(point1, point2, point3) { + const A = (point3.x - point1.x) * (point2.y - point1.y); + const B = (point2.x - point1.x) * (point3.y - point1.y); + return A > B + Number.EPSILON ? 1 : A + Number.EPSILON < B ? -1 : 0; + } /** - * always centered + * faster implementation of intersectLineLine + * https://stackoverflow.com/a/16725715/1749528 */ - this.isCentered = true; - (0, utils_1.extendBody)(this, options); - this.unscaledRadius = radius; - } - /** - * get this.pos.x - */ - get x() { - return this.pos.x; - } - /** - * updating this.pos.x by this.x = x updates AABB - */ - set x(x) { - this.pos.x = x; - this.markAsDirty(); - } - /** - * get this.pos.y - */ - get y() { - return this.pos.y; - } - /** - * updating this.pos.y by this.y = y updates AABB - */ - set y(y) { - this.pos.y = y; - this.markAsDirty(); - } - /** - * allow get scale - */ - get scale() { - return this.r / this.unscaledRadius; - } - /** - * shorthand for setScale() - */ - set scale(scale) { - this.setScale(scale); - } - /** - * scaleX = scale in case of Circles - */ - get scaleX() { - return this.scale; - } - /** - * scaleY = scale in case of Circles - */ - get scaleY() { - return this.scale; - } - /** - * group for collision filtering - */ - get group() { - return this._group; - } - set group(group) { - this._group = (0, utils_1.getGroup)(group); - } - /** - * update position BY MOVING FORWARD IN ANGLE DIRECTION - */ - move(speed = 1, updateNow = true) { - (0, utils_1.move)(this, speed, updateNow); - return this; - } - /** - * update position BY TELEPORTING - */ - setPosition(x, y, updateNow = true) { - this.pos.x = x; - this.pos.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * update scale - */ - setScale(scaleX, _scaleY = scaleX, updateNow = true) { - this.r = this.unscaledRadius * Math.abs(scaleX); - this.markAsDirty(updateNow); - return this; - } - /** - * set rotation - */ - setAngle(angle, updateNow = true) { - this.angle = angle; - const { x, y } = this.getOffsetWithAngle(); - this.offset.x = x; - this.offset.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * set offset from center - */ - setOffset(offset, updateNow = true) { - this.offsetCopy.x = offset.x; - this.offsetCopy.y = offset.y; - const { x, y } = this.getOffsetWithAngle(); - this.offset.x = x; - this.offset.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * get body bounding box, without padding - */ - getAABBAsBBox() { - const x = this.pos.x + this.offset.x; - const y = this.pos.y + this.offset.y; - return { - minX: x - this.r, - maxX: x + this.r, - minY: y - this.r, - maxY: y + this.r - }; - } - /** - * Draws collider on a CanvasRenderingContext2D's current path - */ - draw(context) { - const x = this.pos.x + this.offset.x; - const y = this.pos.y + this.offset.y; - const r = Math.abs(this.r); - if (this.isTrigger) { - const max = Math.max(8, this.r); - for (let i = 0; i < max; i++) { - const arc = (i / max) * 2 * Math.PI; - const arcPrev = ((i - 1) / max) * 2 * Math.PI; - const fromX = x + Math.cos(arcPrev) * this.r; - const fromY = y + Math.sin(arcPrev) * this.r; - const toX = x + Math.cos(arc) * this.r; - const toY = y + Math.sin(arc) * this.r; - (0, utils_1.dashLineTo)(context, fromX, fromY, toX, toY); - } - } - else { - context.moveTo(x + r, y); - context.arc(x, y, r, 0, Math.PI * 2); - } - } - /** - * Draws Bounding Box on canvas context - */ - drawBVH(context) { - (0, utils_1.drawBVH)(context, this); - } - /** - * inner function for after position change update aabb in system - */ - updateBody(updateNow = this.dirty) { - var _a; - if (updateNow) { - (_a = this.system) === null || _a === void 0 ? void 0 : _a.insert(this); - this.dirty = false; - } - } - /** - * update instantly or mark as dirty - */ - markAsDirty(updateNow = false) { - if (updateNow) { - this.updateBody(true); + function intersectLineLineFast(line1, line2) { + return ( + isTurn(line1.start, line2.start, line2.end) !== + isTurn(line1.end, line2.start, line2.end) && + isTurn(line1.start, line1.end, line2.start) !== + isTurn(line1.start, line1.end, line2.end) + ); } - else { - this.dirty = true; + /** + * returns the point of intersection + * https://stackoverflow.com/a/24392281/1749528 + */ + function intersectLineLine(line1, line2) { + const dX = line1.end.x - line1.start.x; + const dY = line1.end.y - line1.start.y; + const determinant = + dX * (line2.end.y - line2.start.y) - + (line2.end.x - line2.start.x) * dY; + if (determinant === 0) { + return null; + } + const lambda = + ((line2.end.y - line2.start.y) * (line2.end.x - line1.start.x) + + (line2.start.x - line2.end.x) * (line2.end.y - line1.start.y)) / + determinant; + const gamma = + ((line1.start.y - line1.end.y) * (line2.end.x - line1.start.x) + + dX * (line2.end.y - line1.start.y)) / + determinant; + // check if there is an intersection + if (!(lambda >= 0 && lambda <= 1) || !(gamma >= 0 && gamma <= 1)) { + return null; + } + return { + x: line1.start.x + lambda * dX, + y: line1.start.y + lambda * dY, + }; } - } - /** - * internal for getting offset with applied angle - */ - getOffsetWithAngle() { - if ((!this.offsetCopy.x && !this.offsetCopy.y) || !this.angle) { - return this.offsetCopy; + function intersectLinePolygon(line, polygon) { + const results = []; + (0, optimized_1.forEach)(polygon.calcPoints, (to, index) => { + const from = index + ? polygon.calcPoints[index - 1] + : polygon.calcPoints[polygon.calcPoints.length - 1]; + const side = { + start: { x: from.x + polygon.pos.x, y: from.y + polygon.pos.y }, + end: { x: to.x + polygon.pos.x, y: to.y + polygon.pos.y }, + }; + const hit = intersectLineLine(line, side); + if (hit) { + results.push(hit); + } + }); + return results; } - const sin = Math.sin(this.angle); - const cos = Math.cos(this.angle); - const x = this.offsetCopy.x * cos - this.offsetCopy.y * sin; - const y = this.offsetCopy.x * sin + this.offsetCopy.y * cos; - return { x, y }; - } -} -exports.Circle = Circle; - -/***/ }), + /***/ + }, -/***/ "./src/bodies/ellipse.ts": -/*!*******************************!*\ - !*** ./src/bodies/ellipse.ts ***! - \*******************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Ellipse = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const polygon_1 = __webpack_require__(/*! ./polygon */ "./src/bodies/polygon.ts"); -const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -/** - * collider - ellipse - */ -class Ellipse extends polygon_1.Polygon { - /** - * collider - ellipse - */ - constructor(position, radiusX, radiusY = radiusX, step = (radiusX + radiusY) / Math.PI, options) { - super(position, (0, utils_1.createEllipse)(radiusX, radiusY, step), options); + /***/ "./src/model.ts": + /*!**********************!*\ + !*** ./src/model.ts ***! + \**********************/ + /***/ function (__unused_webpack_module, exports, __webpack_require__) { + "use strict"; + + var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.BodyGroup = + exports.BodyType = + exports.SATCircle = + exports.SATPolygon = + exports.SATVector = + exports.Response = + exports.RBush = + exports.isSimple = + void 0; + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); + Object.defineProperty(exports, "SATCircle", { + enumerable: true, + get: function () { + return sat_1.Circle; + }, + }); + Object.defineProperty(exports, "SATPolygon", { + enumerable: true, + get: function () { + return sat_1.Polygon; + }, + }); + Object.defineProperty(exports, "Response", { + enumerable: true, + get: function () { + return sat_1.Response; + }, + }); + Object.defineProperty(exports, "SATVector", { + enumerable: true, + get: function () { + return sat_1.Vector; + }, + }); + // version 4.0.0 1=1 copy + const rbush_1 = __importDefault( + __webpack_require__( + /*! ./external/rbush */ "./src/external/rbush.js", + ), + ); + exports.RBush = rbush_1.default; + var poly_decomp_es_1 = __webpack_require__( + /*! poly-decomp-es */ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js", + ); + Object.defineProperty(exports, "isSimple", { + enumerable: true, + get: function () { + return poly_decomp_es_1.isSimple; + }, + }); /** - * ellipse type + * types */ - this.type = model_1.BodyType.Ellipse; + var BodyType; + (function (BodyType) { + BodyType["Ellipse"] = "Ellipse"; + BodyType["Circle"] = "Circle"; + BodyType["Polygon"] = "Polygon"; + BodyType["Box"] = "Box"; + BodyType["Line"] = "Line"; + BodyType["Point"] = "Point"; + })(BodyType || (exports.BodyType = BodyType = {})); /** - * faster than type + * for groups */ - this.typeGroup = model_1.BodyGroup.Ellipse; + var BodyGroup; + (function (BodyGroup) { + BodyGroup[(BodyGroup["Ellipse"] = 32)] = "Ellipse"; + BodyGroup[(BodyGroup["Circle"] = 16)] = "Circle"; + BodyGroup[(BodyGroup["Polygon"] = 8)] = "Polygon"; + BodyGroup[(BodyGroup["Box"] = 4)] = "Box"; + BodyGroup[(BodyGroup["Line"] = 2)] = "Line"; + BodyGroup[(BodyGroup["Point"] = 1)] = "Point"; + })(BodyGroup || (exports.BodyGroup = BodyGroup = {})); + + /***/ + }, + + /***/ "./src/optimized.ts": + /*!**************************!*\ + !*** ./src/optimized.ts ***! + \**************************/ + /***/ (__unused_webpack_module, exports) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.map = + exports.filter = + exports.every = + exports.some = + exports.forEach = + void 0; /** - * ellipses are convex + * 40-90% faster than built-in Array.forEach function. + * + * basic benchmark: https://jsbench.me/urle772xdn */ - this.isConvex = true; - this._radiusX = radiusX; - this._radiusY = radiusY; - this._step = step; - } - /** - * flag to set is body centered - */ - set isCentered(_isCentered) { } - /** - * is body centered? - */ - get isCentered() { - return true; - } - /** - * get ellipse step number - */ - get step() { - return this._step; - } - /** - * set ellipse step number - */ - set step(step) { - this._step = step; - this.setPoints((0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step)); - } - /** - * get ellipse radiusX - */ - get radiusX() { - return this._radiusX; - } - /** - * set ellipse radiusX, update points - */ - set radiusX(radiusX) { - this._radiusX = radiusX; - this.setPoints((0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step)); - } - /** - * get ellipse radiusY - */ - get radiusY() { - return this._radiusY; - } - /** - * set ellipse radiusY, update points - */ - set radiusY(radiusY) { - this._radiusY = radiusY; - this.setPoints((0, utils_1.createEllipse)(this._radiusX, this._radiusY, this._step)); - } - /** - * do not attempt to use Polygon.center() - */ - center() { - return; - } - /** - * do not attempt to use Polygon.updateIsConvex() - */ - updateIsConvex() { - return; - } -} -exports.Ellipse = Ellipse; - - -/***/ }), - -/***/ "./src/bodies/line.ts": -/*!****************************!*\ - !*** ./src/bodies/line.ts ***! - \****************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Line = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const polygon_1 = __webpack_require__(/*! ./polygon */ "./src/bodies/polygon.ts"); -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -/** - * collider - line - */ -class Line extends polygon_1.Polygon { - /** - * collider - line from start to end - */ - constructor(start, end, options) { - super(start, [ - { x: 0, y: 0 }, - { x: end.x - start.x, y: end.y - start.y } - ], options); + const forEach = (array, callback) => { + for (let i = 0, l = array.length; i < l; i++) { + callback(array[i], i); + } + }; + exports.forEach = forEach; /** - * line type + * 20-90% faster than built-in Array.some function. + * + * basic benchmark: https://jsbench.me/l0le7bnnsq */ - this.type = model_1.BodyType.Line; + const some = (array, callback) => { + for (let i = 0, l = array.length; i < l; i++) { + if (callback(array[i], i)) { + return true; + } + } + return false; + }; + exports.some = some; /** - * faster than type + * 20-40% faster than built-in Array.every function. + * + * basic benchmark: https://jsbench.me/unle7da29v */ - this.typeGroup = model_1.BodyGroup.Line; + const every = (array, callback) => { + for (let i = 0, l = array.length; i < l; i++) { + if (!callback(array[i], i)) { + return false; + } + } + return true; + }; + exports.every = every; /** - * line is convex + * 20-60% faster than built-in Array.filter function. + * + * basic benchmark: https://jsbench.me/o1le77ev4l */ - this.isConvex = true; - if (this.calcPoints.length === 1 || !end) { - console.error({ start, end }); - throw new Error("No end point for line provided"); - } - } - get start() { - return { - x: this.x + this.calcPoints[0].x, - y: this.y + this.calcPoints[0].y + const filter = (array, callback) => { + const output = []; + for (let i = 0, l = array.length; i < l; i++) { + const item = array[i]; + if (callback(item, i)) { + output.push(item); + } + } + return output; }; - } - set start({ x, y }) { - this.x = x; - this.y = y; - } - get end() { - return { - x: this.x + this.calcPoints[1].x, - y: this.y + this.calcPoints[1].y + exports.filter = filter; + /** + * 20-70% faster than built-in Array.map + * + * basic benchmark: https://jsbench.me/oyle77vbpc + */ + const map = (array, callback) => { + const l = array.length; + const output = new Array(l); + for (let i = 0; i < l; i++) { + output[i] = callback(array[i], i); + } + return output; }; - } - set end({ x, y }) { - this.points[1].x = x - this.start.x; - this.points[1].y = y - this.start.y; - this.setPoints(this.points); - } - getCentroid() { - return new sat_1.Vector((this.end.x - this.start.x) / 2, (this.end.y - this.start.y) / 2); - } - /** - * do not attempt to use Polygon.updateIsConvex() - */ - updateIsConvex() { - return; - } -} -exports.Line = Line; + exports.map = map; + /***/ + }, -/***/ }), - -/***/ "./src/bodies/point.ts": -/*!*****************************!*\ - !*** ./src/bodies/point.ts ***! - \*****************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Point = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const box_1 = __webpack_require__(/*! ./box */ "./src/bodies/box.ts"); -const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -/** - * collider - point (very tiny box) - */ -class Point extends box_1.Box { - /** - * collider - point (very tiny box) - */ - constructor(position, options) { - super((0, utils_1.ensureVectorPoint)(position), 0.001, 0.001, options); - /** - * point type - */ - this.type = model_1.BodyType.Point; + /***/ "./src/system.ts": + /*!***********************!*\ + !*** ./src/system.ts ***! + \***********************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.System = void 0; + const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); + const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts"); + const intersect_1 = __webpack_require__( + /*! ./intersect */ "./src/intersect.ts", + ); + const optimized_1 = __webpack_require__( + /*! ./optimized */ "./src/optimized.ts", + ); + const base_system_1 = __webpack_require__( + /*! ./base-system */ "./src/base-system.ts", + ); + const line_1 = __webpack_require__( + /*! ./bodies/line */ "./src/bodies/line.ts", + ); /** - * faster than type + * collision system */ - this.typeGroup = model_1.BodyGroup.Point; - } -} -exports.Point = Point; - + class System extends base_system_1.BaseSystem { + constructor() { + super(...arguments); + /** + * the last collision result + */ + this.response = new model_1.Response(); + } + /** + * re-insert body into collision tree and update its bbox + * every body can be part of only one system + */ + insert(body) { + const insertResult = super.insert(body); + // set system for later body.system.updateBody(body) + body.system = this; + return insertResult; + } + /** + * separate (move away) bodies + */ + separate() { + (0, optimized_1.forEach)(this.all(), (body) => { + this.separateBody(body); + }); + } + /** + * separate (move away) 1 body + */ + separateBody(body) { + if (body.isStatic || body.isTrigger) { + return; + } + const offsets = { x: 0, y: 0 }; + const addOffsets = ({ overlapV: { x, y } }) => { + offsets.x += x; + offsets.y += y; + }; + this.checkOne(body, addOffsets); + if (offsets.x || offsets.y) { + body.setPosition(body.x - offsets.x, body.y - offsets.y); + } + } + /** + * check one body collisions with callback + */ + checkOne( + body, + callback = utils_1.returnTrue, + response = this.response, + ) { + // no need to check static body collision + if (body.isStatic) { + return false; + } + const bodies = this.search(body); + const checkCollision = (candidate) => { + if ( + candidate !== body && + this.checkCollision(body, candidate, response) + ) { + return callback(response); + } + }; + return (0, optimized_1.some)(bodies, checkCollision); + } + /** + * check all bodies collisions in area with callback + */ + checkArea( + area, + callback = utils_1.returnTrue, + response = this.response, + ) { + const checkOne = (body) => { + return this.checkOne(body, callback, response); + }; + return (0, optimized_1.some)(this.search(area), checkOne); + } + /** + * check all bodies collisions with callback + */ + checkAll(callback = utils_1.returnTrue, response = this.response) { + const checkOne = (body) => { + return this.checkOne(body, callback, response); + }; + return (0, optimized_1.some)(this.all(), checkOne); + } + /** + * check do 2 objects collide + */ + checkCollision(bodyA, bodyB, response = this.response) { + const { bbox: bboxA } = bodyA; + const { bbox: bboxB } = bodyB; + // assess the bodies real aabb without padding + if ( + !(0, utils_1.canInteract)(bodyA, bodyB) || + !bboxA || + !bboxB || + (0, utils_1.notIntersectAABB)(bboxA, bboxB) + ) { + return false; + } + const sat = (0, utils_1.getSATTest)(bodyA, bodyB); + // 99% of cases + if (bodyA.isConvex && bodyB.isConvex) { + // always first clear response + response.clear(); + return sat(bodyA, bodyB, response); + } + // more complex (non convex) cases + const convexBodiesA = (0, intersect_1.ensureConvex)(bodyA); + const convexBodiesB = (0, intersect_1.ensureConvex)(bodyB); + let overlapX = 0; + let overlapY = 0; + let collided = false; + (0, optimized_1.forEach)(convexBodiesA, (convexBodyA) => { + (0, optimized_1.forEach)(convexBodiesB, (convexBodyB) => { + // always first clear response + response.clear(); + if (sat(convexBodyA, convexBodyB, response)) { + collided = true; + overlapX += response.overlapV.x; + overlapY += response.overlapV.y; + } + }); + }); + if (collided) { + const vector = new model_1.SATVector(overlapX, overlapY); + response.a = bodyA; + response.b = bodyB; + response.overlapV.x = overlapX; + response.overlapV.y = overlapY; + response.overlapN = vector.normalize(); + response.overlap = vector.len(); + response.aInB = (0, utils_1.checkAInB)(bodyA, bodyB); + response.bInA = (0, utils_1.checkAInB)(bodyB, bodyA); + } + return collided; + } + /** + * raycast to get collider of ray from start to end + */ + raycast(start, end, allow = utils_1.returnTrue) { + let minDistance = Infinity; + let result = null; + if (!this.ray) { + this.ray = new line_1.Line(start, end, { isTrigger: true }); + } else { + this.ray.start = start; + this.ray.end = end; + } + this.insert(this.ray); + this.checkOne(this.ray, ({ b: body }) => { + if (!allow(body, this.ray)) { + return false; + } + const points = + body.typeGroup === model_1.BodyGroup.Circle + ? (0, intersect_1.intersectLineCircle)(this.ray, body) + : (0, intersect_1.intersectLinePolygon)(this.ray, body); + (0, optimized_1.forEach)(points, (point) => { + const pointDistance = (0, utils_1.distance)(start, point); + if (pointDistance < minDistance) { + minDistance = pointDistance; + result = { point, body }; + } + }); + }); + this.remove(this.ray); + return result; + } + } + exports.System = System; -/***/ }), + /***/ + }, -/***/ "./src/bodies/polygon.ts": -/*!*******************************!*\ - !*** ./src/bodies/polygon.ts ***! - \*******************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Polygon = exports.isSimple = void 0; -const model_1 = __webpack_require__(/*! ../model */ "./src/model.ts"); -const utils_1 = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -const optimized_1 = __webpack_require__(/*! ../optimized */ "./src/optimized.ts"); -const poly_decomp_es_1 = __webpack_require__(/*! poly-decomp-es */ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js"); -Object.defineProperty(exports, "isSimple", ({ enumerable: true, get: function () { return poly_decomp_es_1.isSimple; } })); -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -/** - * collider - polygon - */ -class Polygon extends sat_1.Polygon { - /** - * collider - polygon - */ - constructor(position, points, options) { - super((0, utils_1.ensureVectorPoint)(position), (0, utils_1.ensurePolygonPoints)(points)); - /** - * was the polygon modified and needs update in the next checkCollision - */ - this.dirty = false; + /***/ "./src/utils.ts": + /*!**********************!*\ + !*** ./src/utils.ts ***! + \**********************/ + /***/ (__unused_webpack_module, exports, __webpack_require__) => { + "use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); + exports.RAD2DEG = exports.DEG2RAD = void 0; + exports.deg2rad = deg2rad; + exports.rad2deg = rad2deg; + exports.createEllipse = createEllipse; + exports.createBox = createBox; + exports.ensureVectorPoint = ensureVectorPoint; + exports.ensurePolygonPoints = ensurePolygonPoints; + exports.distance = distance; + exports.clockwise = clockwise; + exports.extendBody = extendBody; + exports.bodyMoved = bodyMoved; + exports.notIntersectAABB = notIntersectAABB; + exports.intersectAABB = intersectAABB; + exports.canInteract = canInteract; + exports.checkAInB = checkAInB; + exports.clonePointsArray = clonePointsArray; + exports.mapVectorToArray = mapVectorToArray; + exports.mapArrayToVector = mapArrayToVector; + exports.getBounceDirection = getBounceDirection; + exports.getSATTest = getSATTest; + exports.dashLineTo = dashLineTo; + exports.drawPolygon = drawPolygon; + exports.drawBVH = drawBVH; + exports.cloneResponse = cloneResponse; + exports.returnTrue = returnTrue; + exports.getGroup = getGroup; + exports.bin2dec = bin2dec; + exports.ensureNumber = ensureNumber; + exports.groupBits = groupBits; + exports.move = move; + const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); + const sat_1 = __webpack_require__( + /*! sat */ "./node_modules/sat/SAT.js", + ); + const intersect_1 = __webpack_require__( + /*! ./intersect */ "./src/intersect.ts", + ); + const optimized_1 = __webpack_require__( + /*! ./optimized */ "./src/optimized.ts", + ); + /* helpers for faster getSATTest() and checkAInB() */ + const testMap = { + satCircleCircle: sat_1.testCircleCircle, + satCirclePolygon: sat_1.testCirclePolygon, + satPolygonCircle: sat_1.testPolygonCircle, + satPolygonPolygon: sat_1.testPolygonPolygon, + inCircleCircle: intersect_1.circleInCircle, + inCirclePolygon: intersect_1.circleInPolygon, + inPolygonCircle: intersect_1.polygonInCircle, + inPolygonPolygon: intersect_1.polygonInPolygon, + }; + function createArray(bodyType, testType) { + const arrayResult = []; + const bodyGroups = Object.values(model_1.BodyGroup).filter( + (value) => typeof value === "number", + ); + bodyGroups.forEach((bodyGroup) => { + arrayResult[bodyGroup] = + bodyGroup === model_1.BodyGroup.Circle + ? testMap[`${testType}${bodyType}Circle`] + : testMap[`${testType}${bodyType}Polygon`]; + }); + return arrayResult; + } + const circleSATFunctions = createArray(model_1.BodyType.Circle, "sat"); + const circleInFunctions = createArray(model_1.BodyType.Circle, "in"); + const polygonSATFunctions = createArray( + model_1.BodyType.Polygon, + "sat", + ); + const polygonInFunctions = createArray(model_1.BodyType.Polygon, "in"); + exports.DEG2RAD = Math.PI / 180; + exports.RAD2DEG = 180 / Math.PI; /** - * type of body + * convert from degrees to radians */ - this.type = model_1.BodyType.Polygon; + function deg2rad(degrees) { + return degrees * exports.DEG2RAD; + } /** - * faster than type + * convert from radians to degrees */ - this.typeGroup = model_1.BodyGroup.Polygon; + function rad2deg(radians) { + return radians * exports.RAD2DEG; + } /** - * is body centered + * creates ellipse-shaped polygon based on params */ - this.centered = false; + function createEllipse(radiusX, radiusY = radiusX, step = 1) { + const steps = Math.PI * Math.hypot(radiusX, radiusY) * 2; + const length = Math.max(8, Math.ceil(steps / Math.max(1, step))); + const ellipse = []; + for (let index = 0; index < length; index++) { + const value = (index / length) * 2 * Math.PI; + const x = Math.cos(value) * radiusX; + const y = Math.sin(value) * radiusY; + ellipse.push(new sat_1.Vector(x, y)); + } + return ellipse; + } /** - * scale Vector of body + * creates box shaped polygon points */ - this.scaleVector = { x: 1, y: 1 }; - if (!points.length) { - throw new Error("No points in polygon"); - } - (0, utils_1.extendBody)(this, options); - } - /** - * flag to set is polygon centered - */ - set isCentered(isCentered) { - if (this.centered === isCentered) { - return; - } - const centroid = this.getCentroidWithoutRotation(); - if (centroid.x || centroid.y) { - const x = centroid.x * (isCentered ? 1 : -1); - const y = centroid.y * (isCentered ? 1 : -1); - this.translate(-x, -y); - } - this.centered = isCentered; - } - /** - * is polygon centered? - */ - get isCentered() { - return this.centered; - } - get x() { - return this.pos.x; - } - /** - * updating this.pos.x by this.x = x updates AABB - */ - set x(x) { - this.pos.x = x; - this.markAsDirty(); - } - get y() { - return this.pos.y; - } - /** - * updating this.pos.y by this.y = y updates AABB - */ - set y(y) { - this.pos.y = y; - this.markAsDirty(); - } - /** - * allow exact getting of scale x - use setScale(x, y) to set - */ - get scaleX() { - return this.scaleVector.x; - } - /** - * allow exact getting of scale y - use setScale(x, y) to set - */ - get scaleY() { - return this.scaleVector.y; - } - /** - * allow approx getting of scale - */ - get scale() { - return (this.scaleVector.x + this.scaleVector.y) / 2; - } - /** - * allow easier setting of scale - */ - set scale(scale) { - this.setScale(scale); - } - /** - * group for collision filtering - */ - get group() { - return this._group; - } - set group(group) { - this._group = (0, utils_1.getGroup)(group); - } - /** - * update position BY MOVING FORWARD IN ANGLE DIRECTION - */ - move(speed = 1, updateNow = true) { - (0, utils_1.move)(this, speed, updateNow); - return this; - } - /** - * update position BY TELEPORTING - */ - setPosition(x, y, updateNow = true) { - this.pos.x = x; - this.pos.y = y; - this.markAsDirty(updateNow); - return this; - } - /** - * update scale - */ - setScale(x, y = x, updateNow = true) { - this.scaleVector.x = Math.abs(x); - this.scaleVector.y = Math.abs(y); - super.setPoints((0, optimized_1.map)(this.points, (point, index) => { - point.x = this.pointsBackup[index].x * this.scaleVector.x; - point.y = this.pointsBackup[index].y * this.scaleVector.y; - return point; - })); - this.markAsDirty(updateNow); - return this; - } - setAngle(angle, updateNow = true) { - super.setAngle(angle); - this.markAsDirty(updateNow); - return this; - } - setOffset(offset, updateNow = true) { - super.setOffset(offset); - this.markAsDirty(updateNow); - return this; - } - /** - * get body bounding box, without padding - */ - getAABBAsBBox() { - const { pos, w, h } = this.getAABBAsBox(); - return { - minX: pos.x, - minY: pos.y, - maxX: pos.x + w, - maxY: pos.y + h - }; - } - /** - * Draws exact collider on canvas context - */ - draw(context) { - (0, utils_1.drawPolygon)(context, this, this.isTrigger); - } - /** - * Draws Bounding Box on canvas context - */ - drawBVH(context) { - (0, utils_1.drawBVH)(context, this); - } - /** - * get body centroid without applied angle - */ - getCentroidWithoutRotation() { - // keep angle copy - const angle = this.angle; - if (angle) { - // reset angle for get centroid - this.setAngle(0); - // get centroid - const centroid = this.getCentroid(); - // revert angle change - this.setAngle(angle); - return centroid; + function createBox(width, height) { + return [ + new sat_1.Vector(0, 0), + new sat_1.Vector(width, 0), + new sat_1.Vector(width, height), + new sat_1.Vector(0, height), + ]; } - return this.getCentroid(); - } - /** - * sets polygon points to new array of vectors - */ - setPoints(points) { - super.setPoints(points); - this.updateIsConvex(); - this.pointsBackup = (0, utils_1.clonePointsArray)(points); - return this; - } - /** - * translates polygon points in x, y direction - */ - translate(x, y) { - super.translate(x, y); - this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); - return this; - } - /** - * rotates polygon points by angle, in radians - */ - rotate(angle) { - super.rotate(angle); - this.pointsBackup = (0, utils_1.clonePointsArray)(this.points); - return this; - } - /** - * if true, polygon is not an invalid, self-crossing polygon - */ - isSimple() { - return (0, poly_decomp_es_1.isSimple)(this.calcPoints.map(utils_1.mapVectorToArray)); - } - /** - * inner function for after position change update aabb in system and convex inner polygons - */ - updateBody(updateNow = this.dirty) { - var _a; - if (updateNow) { - this.updateConvexPolygonPositions(); - (_a = this.system) === null || _a === void 0 ? void 0 : _a.insert(this); - this.dirty = false; + /** + * ensure SATVector type point result + */ + function ensureVectorPoint(point = {}) { + return point instanceof sat_1.Vector + ? point + : new sat_1.Vector(point.x || 0, point.y || 0); } - } - retranslate(isCentered = this.isCentered) { - const centroid = this.getCentroidWithoutRotation(); - if (centroid.x || centroid.y) { - const x = centroid.x * (isCentered ? 1 : -1); - const y = centroid.y * (isCentered ? 1 : -1); - this.translate(-x, -y); + /** + * ensure Vector points (for polygon) in counter-clockwise order + */ + function ensurePolygonPoints(points = []) { + const polygonPoints = (0, optimized_1.map)(points, ensureVectorPoint); + return clockwise(polygonPoints) + ? polygonPoints.reverse() + : polygonPoints; } - } - /** - * update instantly or mark as dirty - */ - markAsDirty(updateNow = false) { - if (updateNow) { - this.updateBody(true); + /** + * get distance between two Vector points + */ + function distance(bodyA, bodyB) { + const xDiff = bodyA.x - bodyB.x; + const yDiff = bodyA.y - bodyB.y; + return Math.hypot(xDiff, yDiff); } - else { - this.dirty = true; + /** + * check [is clockwise] direction of polygon + */ + function clockwise(points) { + const length = points.length; + let sum = 0; + (0, optimized_1.forEach)(points, (v1, index) => { + const v2 = points[(index + 1) % length]; + sum += (v2.x - v1.x) * (v2.y + v1.y); + }); + return sum > 0; } - } - /** - * update the position of the decomposed convex polygons (if any), called - * after the position of the body has changed - */ - updateConvexPolygonPositions() { - if (this.isConvex || !this.convexPolygons) { - return; + /** + * used for all types of bodies in constructor + */ + function extendBody(body, options = {}) { + body.isStatic = !!options.isStatic; + body.isTrigger = !!options.isTrigger; + body.padding = options.padding || 0; + body.group = + typeof options.group === "number" ? options.group : 0x7fffffff; + if (body.typeGroup !== model_1.BodyGroup.Circle) { + body.isCentered = options.isCentered || false; + } + body.setAngle(options.angle || 0); } - (0, optimized_1.forEach)(this.convexPolygons, (polygon) => { - polygon.pos.x = this.pos.x; - polygon.pos.y = this.pos.y; - if (polygon.angle !== this.angle) { - // Must use setAngle to recalculate the points of the Polygon - polygon.setAngle(this.angle); - } - }); - } - /** - * returns body split into convex polygons, or empty array for convex bodies - */ - getConvex() { - if ((this.typeGroup && this.typeGroup !== model_1.BodyGroup.Polygon) || - this.points.length < 4) { - return []; + /** + * check if body moved outside of its padding + */ + function bodyMoved(body) { + const { bbox, minX, minY, maxX, maxY } = body; + return ( + bbox.minX < minX || + bbox.minY < minY || + bbox.maxX > maxX || + bbox.maxY > maxY + ); } - const points = (0, optimized_1.map)(this.calcPoints, utils_1.mapVectorToArray); - return (0, poly_decomp_es_1.quickDecomp)(points); - } - /** - * updates convex polygons cache in body - */ - updateConvexPolygons(convex = this.getConvex()) { - if (this.isConvex) { - return; + /** + * returns true if two boxes not intersect + */ + function notIntersectAABB(bodyA, bodyB) { + return ( + bodyB.minX > bodyA.maxX || + bodyB.minY > bodyA.maxY || + bodyB.maxX < bodyA.minX || + bodyB.maxY < bodyA.minY + ); } - if (!this.convexPolygons) { - this.convexPolygons = []; + /** + * checks if two boxes intersect + */ + function intersectAABB(bodyA, bodyB) { + return !notIntersectAABB(bodyA, bodyB); } - (0, optimized_1.forEach)(convex, (points, index) => { - // lazy create - if (!this.convexPolygons[index]) { - this.convexPolygons[index] = new sat_1.Polygon(); - } - this.convexPolygons[index].pos.x = this.pos.x; - this.convexPolygons[index].pos.y = this.pos.y; - this.convexPolygons[index].angle = this.angle; - this.convexPolygons[index].setPoints((0, utils_1.ensurePolygonPoints)((0, optimized_1.map)(points, utils_1.mapArrayToVector))); - }); - // trim array length - this.convexPolygons.length = convex.length; - } - /** - * after points update set is convex - */ - updateIsConvex() { - // all other types other than polygon are always convex - const convex = this.getConvex(); - // everything with empty array or one element array - this.isConvex = convex.length <= 1; - this.updateConvexPolygons(convex); - } -} -exports.Polygon = Polygon; - - -/***/ }), - -/***/ "./src/intersect.ts": -/*!**************************!*\ - !*** ./src/intersect.ts ***! - \**************************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.ensureConvex = ensureConvex; -exports.polygonInCircle = polygonInCircle; -exports.pointInPolygon = pointInPolygon; -exports.polygonInPolygon = polygonInPolygon; -exports.pointOnCircle = pointOnCircle; -exports.circleInCircle = circleInCircle; -exports.circleInPolygon = circleInPolygon; -exports.circleOutsidePolygon = circleOutsidePolygon; -exports.intersectLineCircle = intersectLineCircle; -exports.intersectLineLineFast = intersectLineLineFast; -exports.intersectLineLine = intersectLineLine; -exports.intersectLinePolygon = intersectLinePolygon; -const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); -const optimized_1 = __webpack_require__(/*! ./optimized */ "./src/optimized.ts"); -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -/** - * replace body with array of related convex polygons - */ -function ensureConvex(body) { - if (body.isConvex || body.typeGroup !== model_1.BodyGroup.Polygon) { - return [body]; - } - return body.convexPolygons; -} -function polygonInCircle(polygon, circle) { - return (0, optimized_1.every)(polygon.calcPoints, p => (0, sat_1.pointInCircle)({ x: p.x + polygon.pos.x, y: p.y + polygon.pos.y }, circle)); -} -function pointInPolygon(point, polygon) { - return (0, optimized_1.some)(ensureConvex(polygon), convex => (0, sat_1.pointInPolygon)(point, convex)); -} -function polygonInPolygon(polygonA, polygonB) { - return (0, optimized_1.every)(polygonA.calcPoints, point => pointInPolygon({ x: point.x + polygonA.pos.x, y: point.y + polygonA.pos.y }, polygonB)); -} -/** - * https://stackoverflow.com/a/68197894/1749528 - */ -function pointOnCircle(point, circle) { - return ((point.x - circle.pos.x) * (point.x - circle.pos.x) + - (point.y - circle.pos.y) * (point.y - circle.pos.y) === - circle.r * circle.r); -} -/** - * https://stackoverflow.com/a/68197894/1749528 - */ -function circleInCircle(bodyA, bodyB) { - const x1 = bodyA.pos.x; - const y1 = bodyA.pos.y; - const x2 = bodyB.pos.x; - const y2 = bodyB.pos.y; - const r1 = bodyA.r; - const r2 = bodyB.r; - const distSq = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); - return distSq + r2 === r1 || distSq + r2 < r1; -} -/** - * https://stackoverflow.com/a/68197894/1749528 - */ -function circleInPolygon(circle, polygon) { - // Circle with radius 0 isn't a circle - if (circle.r === 0) { - return false; - } - // If the center of the circle is not within the polygon, - // then the circle may overlap, but it'll never be "contained" - // so return false - if (!pointInPolygon(circle.pos, polygon)) { - return false; - } - // Necessary add polygon pos to points - const points = (0, optimized_1.map)(polygon.calcPoints, ({ x, y }) => ({ - x: x + polygon.pos.x, - y: y + polygon.pos.y - })); - // If the center of the circle is within the polygon, - // the circle is not outside of the polygon completely. - // so return false. - if ((0, optimized_1.some)(points, point => (0, sat_1.pointInCircle)(point, circle))) { - return false; - } - // If any line-segment of the polygon intersects the circle, - // the circle is not "contained" - // so return false - if ((0, optimized_1.some)(points, (end, index) => { - const start = index - ? points[index - 1] - : points[points.length - 1]; - return intersectLineCircle({ start, end }, circle).length > 0; - })) { - return false; - } - return true; -} -/** - * https://stackoverflow.com/a/68197894/1749528 - */ -function circleOutsidePolygon(circle, polygon) { - // Circle with radius 0 isn't a circle - if (circle.r === 0) { - return false; - } - // If the center of the circle is within the polygon, - // the circle is not outside of the polygon completely. - // so return false. - if (pointInPolygon(circle.pos, polygon)) { - return false; - } - // Necessary add polygon pos to points - const points = (0, optimized_1.map)(polygon.calcPoints, ({ x, y }) => ({ - x: x + polygon.pos.x, - y: y + polygon.pos.y - })); - // If the center of the circle is within the polygon, - // the circle is not outside of the polygon completely. - // so return false. - if ((0, optimized_1.some)(points, point => (0, sat_1.pointInCircle)(point, circle) || pointOnCircle(point, circle))) { - return false; - } - // If any line-segment of the polygon intersects the circle, - // the circle is not "contained" - // so return false - if ((0, optimized_1.some)(points, (end, index) => { - const start = index - ? points[index - 1] - : points[points.length - 1]; - return intersectLineCircle({ start, end }, circle).length > 0; - })) { - return false; - } - return true; -} -/** - * https://stackoverflow.com/a/37225895/1749528 - */ -function intersectLineCircle(line, { pos, r }) { - const v1 = { x: line.end.x - line.start.x, y: line.end.y - line.start.y }; - const v2 = { x: line.start.x - pos.x, y: line.start.y - pos.y }; - const b = (v1.x * v2.x + v1.y * v2.y) * -2; - const c = (v1.x * v1.x + v1.y * v1.y) * 2; - const d = Math.sqrt(b * b - (v2.x * v2.x + v2.y * v2.y - r * r) * c * 2); - if (isNaN(d)) { - // no intercept - return []; - } - const u1 = (b - d) / c; // these represent the unit distance of point one and two on the line - const u2 = (b + d) / c; - const results = []; // return array - if (u1 <= 1 && u1 >= 0) { - // add point if on the line segment - results.push({ x: line.start.x + v1.x * u1, y: line.start.y + v1.y * u1 }); - } - if (u2 <= 1 && u2 >= 0) { - // second add point if on the line segment - results.push({ x: line.start.x + v1.x * u2, y: line.start.y + v1.y * u2 }); - } - return results; -} -/** - * helper for intersectLineLineFast - */ -function isTurn(point1, point2, point3) { - const A = (point3.x - point1.x) * (point2.y - point1.y); - const B = (point2.x - point1.x) * (point3.y - point1.y); - return A > B + Number.EPSILON ? 1 : A + Number.EPSILON < B ? -1 : 0; -} -/** - * faster implementation of intersectLineLine - * https://stackoverflow.com/a/16725715/1749528 - */ -function intersectLineLineFast(line1, line2) { - return (isTurn(line1.start, line2.start, line2.end) !== - isTurn(line1.end, line2.start, line2.end) && - isTurn(line1.start, line1.end, line2.start) !== - isTurn(line1.start, line1.end, line2.end)); -} -/** - * returns the point of intersection - * https://stackoverflow.com/a/24392281/1749528 - */ -function intersectLineLine(line1, line2) { - const dX = line1.end.x - line1.start.x; - const dY = line1.end.y - line1.start.y; - const determinant = dX * (line2.end.y - line2.start.y) - (line2.end.x - line2.start.x) * dY; - if (determinant === 0) { - return null; - } - const lambda = ((line2.end.y - line2.start.y) * (line2.end.x - line1.start.x) + - (line2.start.x - line2.end.x) * (line2.end.y - line1.start.y)) / - determinant; - const gamma = ((line1.start.y - line1.end.y) * (line2.end.x - line1.start.x) + - dX * (line2.end.y - line1.start.y)) / - determinant; - // check if there is an intersection - if (!(lambda >= 0 && lambda <= 1) || !(gamma >= 0 && gamma <= 1)) { - return null; - } - return { x: line1.start.x + lambda * dX, y: line1.start.y + lambda * dY }; -} -function intersectLinePolygon(line, polygon) { - const results = []; - (0, optimized_1.forEach)(polygon.calcPoints, (to, index) => { - const from = index - ? polygon.calcPoints[index - 1] - : polygon.calcPoints[polygon.calcPoints.length - 1]; - const side = { - start: { x: from.x + polygon.pos.x, y: from.y + polygon.pos.y }, - end: { x: to.x + polygon.pos.x, y: to.y + polygon.pos.y } - }; - const hit = intersectLineLine(line, side); - if (hit) { - results.push(hit); + /** + * checks if two bodies can interact (for collision filtering) + */ + function canInteract(bodyA, bodyB) { + return ( + ((bodyA.group >> 16) & (bodyB.group & 0xffff) && + (bodyB.group >> 16) & (bodyA.group & 0xffff)) !== 0 + ); } - }); - return results; -} - - -/***/ }), - -/***/ "./src/model.ts": -/*!**********************!*\ - !*** ./src/model.ts ***! - \**********************/ -/***/ (function(__unused_webpack_module, exports, __webpack_require__) { - -"use strict"; - -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.BodyGroup = exports.BodyType = exports.SATCircle = exports.SATPolygon = exports.SATVector = exports.Response = exports.RBush = exports.isSimple = void 0; -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -Object.defineProperty(exports, "SATCircle", ({ enumerable: true, get: function () { return sat_1.Circle; } })); -Object.defineProperty(exports, "SATPolygon", ({ enumerable: true, get: function () { return sat_1.Polygon; } })); -Object.defineProperty(exports, "Response", ({ enumerable: true, get: function () { return sat_1.Response; } })); -Object.defineProperty(exports, "SATVector", ({ enumerable: true, get: function () { return sat_1.Vector; } })); -// version 4.0.0 1=1 copy -const rbush_1 = __importDefault(__webpack_require__(/*! ./external/rbush */ "./src/external/rbush.js")); -exports.RBush = rbush_1.default; -var poly_decomp_es_1 = __webpack_require__(/*! poly-decomp-es */ "./node_modules/poly-decomp-es/dist/poly-decomp-es.js"); -Object.defineProperty(exports, "isSimple", ({ enumerable: true, get: function () { return poly_decomp_es_1.isSimple; } })); -/** - * types - */ -var BodyType; -(function (BodyType) { - BodyType["Ellipse"] = "Ellipse"; - BodyType["Circle"] = "Circle"; - BodyType["Polygon"] = "Polygon"; - BodyType["Box"] = "Box"; - BodyType["Line"] = "Line"; - BodyType["Point"] = "Point"; -})(BodyType || (exports.BodyType = BodyType = {})); -/** - * for groups - */ -var BodyGroup; -(function (BodyGroup) { - BodyGroup[BodyGroup["Ellipse"] = 32] = "Ellipse"; - BodyGroup[BodyGroup["Circle"] = 16] = "Circle"; - BodyGroup[BodyGroup["Polygon"] = 8] = "Polygon"; - BodyGroup[BodyGroup["Box"] = 4] = "Box"; - BodyGroup[BodyGroup["Line"] = 2] = "Line"; - BodyGroup[BodyGroup["Point"] = 1] = "Point"; -})(BodyGroup || (exports.BodyGroup = BodyGroup = {})); - - -/***/ }), - -/***/ "./src/optimized.ts": -/*!**************************!*\ - !*** ./src/optimized.ts ***! - \**************************/ -/***/ ((__unused_webpack_module, exports) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.map = exports.filter = exports.every = exports.some = exports.forEach = void 0; -/** - * 40-90% faster than built-in Array.forEach function. - * - * basic benchmark: https://jsbench.me/urle772xdn - */ -const forEach = (array, callback) => { - for (let i = 0, l = array.length; i < l; i++) { - callback(array[i], i); - } -}; -exports.forEach = forEach; -/** - * 20-90% faster than built-in Array.some function. - * - * basic benchmark: https://jsbench.me/l0le7bnnsq - */ -const some = (array, callback) => { - for (let i = 0, l = array.length; i < l; i++) { - if (callback(array[i], i)) { - return true; + /** + * checks if body a is in body b + */ + function checkAInB(bodyA, bodyB) { + const check = + bodyA.typeGroup === model_1.BodyGroup.Circle + ? circleInFunctions + : polygonInFunctions; + return check[bodyB.typeGroup](bodyA, bodyB); } - } - return false; -}; -exports.some = some; -/** - * 20-40% faster than built-in Array.every function. - * - * basic benchmark: https://jsbench.me/unle7da29v - */ -const every = (array, callback) => { - for (let i = 0, l = array.length; i < l; i++) { - if (!callback(array[i], i)) { - return false; + /** + * clone sat vector points array into vector points array + */ + function clonePointsArray(points) { + return (0, optimized_1.map)(points, ({ x, y }) => ({ x, y })); } - } - return true; -}; -exports.every = every; -/** - * 20-60% faster than built-in Array.filter function. - * - * basic benchmark: https://jsbench.me/o1le77ev4l - */ -const filter = (array, callback) => { - const output = []; - for (let i = 0, l = array.length; i < l; i++) { - const item = array[i]; - if (callback(item, i)) { - output.push(item); + /** + * change format from SAT.js to poly-decomp + */ + function mapVectorToArray({ x, y } = { x: 0, y: 0 }) { + return [x, y]; } - } - return output; -}; -exports.filter = filter; -/** - * 20-70% faster than built-in Array.map - * - * basic benchmark: https://jsbench.me/oyle77vbpc - */ -const map = (array, callback) => { - const l = array.length; - const output = new Array(l); - for (let i = 0; i < l; i++) { - output[i] = callback(array[i], i); - } - return output; -}; -exports.map = map; - - -/***/ }), - -/***/ "./src/system.ts": -/*!***********************!*\ - !*** ./src/system.ts ***! - \***********************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.System = void 0; -const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); -const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts"); -const intersect_1 = __webpack_require__(/*! ./intersect */ "./src/intersect.ts"); -const optimized_1 = __webpack_require__(/*! ./optimized */ "./src/optimized.ts"); -const base_system_1 = __webpack_require__(/*! ./base-system */ "./src/base-system.ts"); -const line_1 = __webpack_require__(/*! ./bodies/line */ "./src/bodies/line.ts"); -/** - * collision system - */ -class System extends base_system_1.BaseSystem { - constructor() { - super(...arguments); /** - * the last collision result + * change format from poly-decomp to SAT.js */ - this.response = new model_1.Response(); - } - /** - * re-insert body into collision tree and update its bbox - * every body can be part of only one system - */ - insert(body) { - const insertResult = super.insert(body); - // set system for later body.system.updateBody(body) - body.system = this; - return insertResult; - } - /** - * separate (move away) bodies - */ - separate() { - (0, optimized_1.forEach)(this.all(), (body) => { - this.separateBody(body); - }); - } - /** - * separate (move away) 1 body - */ - separateBody(body) { - if (body.isStatic || body.isTrigger) { - return; + function mapArrayToVector([x, y] = [0, 0]) { + return { x, y }; } - const offsets = { x: 0, y: 0 }; - const addOffsets = ({ overlapV: { x, y } }) => { - offsets.x += x; - offsets.y += y; - }; - this.checkOne(body, addOffsets); - if (offsets.x || offsets.y) { - body.setPosition(body.x - offsets.x, body.y - offsets.y); + /** + * given 2 bodies calculate vector of bounce assuming equal mass and they are circles + */ + function getBounceDirection(body, collider) { + const v2 = new sat_1.Vector(collider.x - body.x, collider.y - body.y); + const v1 = new sat_1.Vector(body.x - collider.x, body.y - collider.y); + const len = v1.dot(v2.normalize()) * 2; + return new sat_1.Vector( + v2.x * len - v1.x, + v2.y * len - v1.y, + ).normalize(); } - } - /** - * check one body collisions with callback - */ - checkOne(body, callback = utils_1.returnTrue, response = this.response) { - // no need to check static body collision - if (body.isStatic) { - return false; + /** + * returns correct sat.js testing function based on body types + */ + function getSATTest(bodyA, bodyB) { + const check = + bodyA.typeGroup === model_1.BodyGroup.Circle + ? circleSATFunctions + : polygonSATFunctions; + return check[bodyB.typeGroup]; } - const bodies = this.search(body); - const checkCollision = (candidate) => { - if (candidate !== body && - this.checkCollision(body, candidate, response)) { - return callback(response); + /** + * draws dashed line on canvas context + */ + function dashLineTo( + context, + fromX, + fromY, + toX, + toY, + dash = 2, + gap = 4, + ) { + const xDiff = toX - fromX; + const yDiff = toY - fromY; + const arc = Math.atan2(yDiff, xDiff); + const offsetX = Math.cos(arc); + const offsetY = Math.sin(arc); + let posX = fromX; + let posY = fromY; + let dist = Math.hypot(xDiff, yDiff); + while (dist > 0) { + const step = Math.min(dist, dash); + context.moveTo(posX, posY); + context.lineTo(posX + offsetX * step, posY + offsetY * step); + posX += offsetX * (dash + gap); + posY += offsetY * (dash + gap); + dist -= dash + gap; + } + } + /** + * draw polygon + */ + function drawPolygon(context, { pos, calcPoints }, isTrigger = false) { + const lastPoint = calcPoints[calcPoints.length - 1]; + const fromX = pos.x + lastPoint.x; + const fromY = pos.y + lastPoint.y; + if (calcPoints.length === 1) { + context.arc(fromX, fromY, 1, 0, Math.PI * 2); + } else { + context.moveTo(fromX, fromY); + } + (0, optimized_1.forEach)(calcPoints, (point, index) => { + const toX = pos.x + point.x; + const toY = pos.y + point.y; + if (isTrigger) { + const prev = calcPoints[index - 1] || lastPoint; + dashLineTo(context, pos.x + prev.x, pos.y + prev.y, toX, toY); + } else { + context.lineTo(toX, toY); } - }; - return (0, optimized_1.some)(bodies, checkCollision); - } - /** - * check all bodies collisions in area with callback - */ - checkArea(area, callback = utils_1.returnTrue, response = this.response) { - const checkOne = (body) => { - return this.checkOne(body, callback, response); - }; - return (0, optimized_1.some)(this.search(area), checkOne); - } - /** - * check all bodies collisions with callback - */ - checkAll(callback = utils_1.returnTrue, response = this.response) { - const checkOne = (body) => { - return this.checkOne(body, callback, response); - }; - return (0, optimized_1.some)(this.all(), checkOne); - } - /** - * check do 2 objects collide - */ - checkCollision(bodyA, bodyB, response = this.response) { - const { bbox: bboxA } = bodyA; - const { bbox: bboxB } = bodyB; - // assess the bodies real aabb without padding - if (!(0, utils_1.canInteract)(bodyA, bodyB) || - !bboxA || - !bboxB || - (0, utils_1.notIntersectAABB)(bboxA, bboxB)) { - return false; + }); } - const sat = (0, utils_1.getSATTest)(bodyA, bodyB); - // 99% of cases - if (bodyA.isConvex && bodyB.isConvex) { - // always first clear response - response.clear(); - return sat(bodyA, bodyB, response); + /** + * draw body bounding body box + */ + function drawBVH(context, body) { + drawPolygon(context, { + pos: { x: body.minX, y: body.minY }, + calcPoints: createBox(body.maxX - body.minX, body.maxY - body.minY), + }); } - // more complex (non convex) cases - const convexBodiesA = (0, intersect_1.ensureConvex)(bodyA); - const convexBodiesB = (0, intersect_1.ensureConvex)(bodyB); - let overlapX = 0; - let overlapY = 0; - let collided = false; - (0, optimized_1.forEach)(convexBodiesA, convexBodyA => { - (0, optimized_1.forEach)(convexBodiesB, convexBodyB => { - // always first clear response - response.clear(); - if (sat(convexBodyA, convexBodyB, response)) { - collided = true; - overlapX += response.overlapV.x; - overlapY += response.overlapV.y; - } - }); - }); - if (collided) { - const vector = new model_1.SATVector(overlapX, overlapY); - response.a = bodyA; - response.b = bodyB; - response.overlapV.x = overlapX; - response.overlapV.y = overlapY; - response.overlapN = vector.normalize(); - response.overlap = vector.len(); - response.aInB = (0, utils_1.checkAInB)(bodyA, bodyB); - response.bInA = (0, utils_1.checkAInB)(bodyB, bodyA); + /** + * clone response object returning new response with previous ones values + */ + function cloneResponse(response) { + const clone = new sat_1.Response(); + const { a, b, overlap, overlapN, overlapV, aInB, bInA } = response; + clone.a = a; + clone.b = b; + clone.overlap = overlap; + clone.overlapN = overlapN.clone(); + clone.overlapV = overlapV.clone(); + clone.aInB = aInB; + clone.bInA = bInA; + return clone; } - return collided; - } - /** - * raycast to get collider of ray from start to end - */ - raycast(start, end, allow = utils_1.returnTrue) { - let minDistance = Infinity; - let result = null; - if (!this.ray) { - this.ray = new line_1.Line(start, end, { isTrigger: true }); + /** + * dummy fn used as default, for optimization + */ + function returnTrue() { + return true; } - else { - this.ray.start = start; - this.ray.end = end; + /** + * for groups + */ + function getGroup(group) { + return Math.max(0, Math.min(group, 0x7fffffff)); } - this.insert(this.ray); - this.checkOne(this.ray, ({ b: body }) => { - if (!allow(body, this.ray)) { - return false; - } - const points = body.typeGroup === model_1.BodyGroup.Circle - ? (0, intersect_1.intersectLineCircle)(this.ray, body) - : (0, intersect_1.intersectLinePolygon)(this.ray, body); - (0, optimized_1.forEach)(points, (point) => { - const pointDistance = (0, utils_1.distance)(start, point); - if (pointDistance < minDistance) { - minDistance = pointDistance; - result = { point, body }; - } - }); - }); - this.remove(this.ray); - return result; - } -} -exports.System = System; - - -/***/ }), - -/***/ "./src/utils.ts": -/*!**********************!*\ - !*** ./src/utils.ts ***! - \**********************/ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.RAD2DEG = exports.DEG2RAD = void 0; -exports.deg2rad = deg2rad; -exports.rad2deg = rad2deg; -exports.createEllipse = createEllipse; -exports.createBox = createBox; -exports.ensureVectorPoint = ensureVectorPoint; -exports.ensurePolygonPoints = ensurePolygonPoints; -exports.distance = distance; -exports.clockwise = clockwise; -exports.extendBody = extendBody; -exports.bodyMoved = bodyMoved; -exports.notIntersectAABB = notIntersectAABB; -exports.intersectAABB = intersectAABB; -exports.canInteract = canInteract; -exports.checkAInB = checkAInB; -exports.clonePointsArray = clonePointsArray; -exports.mapVectorToArray = mapVectorToArray; -exports.mapArrayToVector = mapArrayToVector; -exports.getBounceDirection = getBounceDirection; -exports.getSATTest = getSATTest; -exports.dashLineTo = dashLineTo; -exports.drawPolygon = drawPolygon; -exports.drawBVH = drawBVH; -exports.cloneResponse = cloneResponse; -exports.returnTrue = returnTrue; -exports.getGroup = getGroup; -exports.bin2dec = bin2dec; -exports.ensureNumber = ensureNumber; -exports.groupBits = groupBits; -exports.move = move; -const model_1 = __webpack_require__(/*! ./model */ "./src/model.ts"); -const sat_1 = __webpack_require__(/*! sat */ "./node_modules/sat/SAT.js"); -const intersect_1 = __webpack_require__(/*! ./intersect */ "./src/intersect.ts"); -const optimized_1 = __webpack_require__(/*! ./optimized */ "./src/optimized.ts"); -/* helpers for faster getSATTest() and checkAInB() */ -const testMap = { - satCircleCircle: sat_1.testCircleCircle, - satCirclePolygon: sat_1.testCirclePolygon, - satPolygonCircle: sat_1.testPolygonCircle, - satPolygonPolygon: sat_1.testPolygonPolygon, - inCircleCircle: intersect_1.circleInCircle, - inCirclePolygon: intersect_1.circleInPolygon, - inPolygonCircle: intersect_1.polygonInCircle, - inPolygonPolygon: intersect_1.polygonInPolygon -}; -function createMap(bodyType, testType) { - return Object.values(model_1.BodyType).reduce((result, type) => (Object.assign(Object.assign({}, result), { [type]: type === model_1.BodyType.Circle - ? testMap[`${testType}${bodyType}Circle`] - : testMap[`${testType}${bodyType}Polygon`] })), {}); -} -const circleSATFunctions = createMap(model_1.BodyType.Circle, "sat"); -const circleInFunctions = createMap(model_1.BodyType.Circle, "in"); -const polygonSATFunctions = createMap(model_1.BodyType.Polygon, "sat"); -const polygonInFunctions = createMap(model_1.BodyType.Polygon, "in"); -exports.DEG2RAD = Math.PI / 180; -exports.RAD2DEG = 180 / Math.PI; -/** - * convert from degrees to radians - */ -function deg2rad(degrees) { - return degrees * exports.DEG2RAD; -} -/** - * convert from radians to degrees - */ -function rad2deg(radians) { - return radians * exports.RAD2DEG; -} -/** - * creates ellipse-shaped polygon based on params - */ -function createEllipse(radiusX, radiusY = radiusX, step = 1) { - const steps = Math.PI * Math.hypot(radiusX, radiusY) * 2; - const length = Math.max(8, Math.ceil(steps / Math.max(1, step))); - const ellipse = []; - for (let index = 0; index < length; index++) { - const value = (index / length) * 2 * Math.PI; - const x = Math.cos(value) * radiusX; - const y = Math.sin(value) * radiusY; - ellipse.push(new sat_1.Vector(x, y)); - } - return ellipse; -} -/** - * creates box shaped polygon points - */ -function createBox(width, height) { - return [ - new sat_1.Vector(0, 0), - new sat_1.Vector(width, 0), - new sat_1.Vector(width, height), - new sat_1.Vector(0, height) - ]; -} -/** - * ensure SATVector type point result - */ -function ensureVectorPoint(point = {}) { - return point instanceof sat_1.Vector - ? point - : new sat_1.Vector(point.x || 0, point.y || 0); -} -/** - * ensure Vector points (for polygon) in counter-clockwise order - */ -function ensurePolygonPoints(points = []) { - const polygonPoints = (0, optimized_1.map)(points, ensureVectorPoint); - return clockwise(polygonPoints) ? polygonPoints.reverse() : polygonPoints; -} -/** - * get distance between two Vector points - */ -function distance(bodyA, bodyB) { - const xDiff = bodyA.x - bodyB.x; - const yDiff = bodyA.y - bodyB.y; - return Math.hypot(xDiff, yDiff); -} -/** - * check [is clockwise] direction of polygon - */ -function clockwise(points) { - const length = points.length; - let sum = 0; - (0, optimized_1.forEach)(points, (v1, index) => { - const v2 = points[(index + 1) % length]; - sum += (v2.x - v1.x) * (v2.y + v1.y); - }); - return sum > 0; -} -/** - * used for all types of bodies in constructor - */ -function extendBody(body, options = {}) { - body.isStatic = !!options.isStatic; - body.isTrigger = !!options.isTrigger; - body.padding = options.padding || 0; - body.group = typeof options.group === "number" ? options.group : 0x7FFFFFFF; - if (body.typeGroup !== model_1.BodyGroup.Circle) { - body.isCentered = options.isCentered || false; - } - body.setAngle(options.angle || 0); -} -/** - * check if body moved outside of its padding - */ -function bodyMoved(body) { - const { bbox, minX, minY, maxX, maxY } = body; - return (bbox.minX < minX || bbox.minY < minY || bbox.maxX > maxX || bbox.maxY > maxY); -} -/** - * returns true if two boxes not intersect - */ -function notIntersectAABB(bodyA, bodyB) { - return (bodyB.minX > bodyA.maxX || - bodyB.minY > bodyA.maxY || - bodyB.maxX < bodyA.minX || - bodyB.maxY < bodyA.minY); -} -/** - * checks if two boxes intersect - */ -function intersectAABB(bodyA, bodyB) { - return !notIntersectAABB(bodyA, bodyB); -} -/** - * checks if two bodies can interact (for collision filtering) - */ -function canInteract(bodyA, bodyB) { - return (((bodyA.group >> 16) & (bodyB.group & 0xFFFF) && - (bodyB.group >> 16) & (bodyA.group & 0xFFFF)) !== 0); -} -/** - * checks if body a is in body b - */ -function checkAInB(bodyA, bodyB) { - const check = bodyA.typeGroup === model_1.BodyGroup.Circle - ? circleInFunctions - : polygonInFunctions; - return check[bodyB.type](bodyA, bodyB); -} -/** - * clone sat vector points array into vector points array - */ -function clonePointsArray(points) { - return (0, optimized_1.map)(points, ({ x, y }) => ({ x, y })); -} -/** - * change format from SAT.js to poly-decomp - */ -function mapVectorToArray({ x, y } = { x: 0, y: 0 }) { - return [x, y]; -} -/** - * change format from poly-decomp to SAT.js - */ -function mapArrayToVector([x, y] = [0, 0]) { - return { x, y }; -} -/** - * given 2 bodies calculate vector of bounce assuming equal mass and they are circles - */ -function getBounceDirection(body, collider) { - const v2 = new sat_1.Vector(collider.x - body.x, collider.y - body.y); - const v1 = new sat_1.Vector(body.x - collider.x, body.y - collider.y); - const len = v1.dot(v2.normalize()) * 2; - return new sat_1.Vector(v2.x * len - v1.x, v2.y * len - v1.y).normalize(); -} -/** - * returns correct sat.js testing function based on body types - */ -function getSATTest(bodyA, bodyB) { - const check = bodyA.typeGroup === model_1.BodyGroup.Circle - ? circleSATFunctions - : polygonSATFunctions; - return check[bodyB.type]; -} -/** - * draws dashed line on canvas context - */ -function dashLineTo(context, fromX, fromY, toX, toY, dash = 2, gap = 4) { - const xDiff = toX - fromX; - const yDiff = toY - fromY; - const arc = Math.atan2(yDiff, xDiff); - const offsetX = Math.cos(arc); - const offsetY = Math.sin(arc); - let posX = fromX; - let posY = fromY; - let dist = Math.hypot(xDiff, yDiff); - while (dist > 0) { - const step = Math.min(dist, dash); - context.moveTo(posX, posY); - context.lineTo(posX + offsetX * step, posY + offsetY * step); - posX += offsetX * (dash + gap); - posY += offsetY * (dash + gap); - dist -= dash + gap; - } -} -/** - * draw polygon - */ -function drawPolygon(context, { pos, calcPoints }, isTrigger = false) { - const lastPoint = calcPoints[calcPoints.length - 1]; - const fromX = pos.x + lastPoint.x; - const fromY = pos.y + lastPoint.y; - if (calcPoints.length === 1) { - context.arc(fromX, fromY, 1, 0, Math.PI * 2); - } - else { - context.moveTo(fromX, fromY); - } - (0, optimized_1.forEach)(calcPoints, (point, index) => { - const toX = pos.x + point.x; - const toY = pos.y + point.y; - if (isTrigger) { - const prev = calcPoints[index - 1] || lastPoint; - dashLineTo(context, pos.x + prev.x, pos.y + prev.y, toX, toY); + /** + * binary string to decimal number + */ + function bin2dec(binary) { + return Number(`0b${binary}`.replace(/\s/g, "")); } - else { - context.lineTo(toX, toY); + /** + * helper for groupBits() + * + * @param input - number or binary string + */ + function ensureNumber(input) { + return typeof input === "number" ? input : bin2dec(input); + } + /** + * create group bits from category and mask + * + * @param category - category bits + * @param mask - mask bits (default: category) + */ + function groupBits(category, mask = category) { + return (ensureNumber(category) << 16) | ensureNumber(mask); + } + function move(body, speed = 1, updateNow = true) { + if (!speed) { + return; + } + const moveX = Math.cos(body.angle) * speed; + const moveY = Math.sin(body.angle) * speed; + body.setPosition(body.x + moveX, body.y + moveY, updateNow); } - }); -} -/** - * draw body bounding body box - */ -function drawBVH(context, body) { - drawPolygon(context, { - pos: { x: body.minX, y: body.minY }, - calcPoints: createBox(body.maxX - body.minX, body.maxY - body.minY) - }); -} -/** - * clone response object returning new response with previous ones values - */ -function cloneResponse(response) { - const clone = new sat_1.Response(); - const { a, b, overlap, overlapN, overlapV, aInB, bInA } = response; - clone.a = a; - clone.b = b; - clone.overlap = overlap; - clone.overlapN = overlapN.clone(); - clone.overlapV = overlapV.clone(); - clone.aInB = aInB; - clone.bInA = bInA; - return clone; -} -/** - * dummy fn used as default, for optimization - */ -function returnTrue() { - return true; -} -/** - * for groups - */ -function getGroup(group) { - return Math.max(0, Math.min(group, 0x7FFFFFFF)); -} -/** - * binary string to decimal number - */ -function bin2dec(binary) { - return Number(`0b${binary}`.replace(/\s/g, "")); -} -/** - * helper for groupBits() - * - * @param input - number or binary string - */ -function ensureNumber(input) { - return typeof input === "number" ? input : bin2dec(input); -} -/** - * create group bits from category and mask - * - * @param category - category bits - * @param mask - mask bits (default: category) - */ -function groupBits(category, mask = category) { - return (ensureNumber(category) << 16) | ensureNumber(mask); -} -function move(body, speed = 1, updateNow = true) { - if (!speed) { - return; - } - const moveX = Math.cos(body.angle) * speed; - const moveY = Math.sin(body.angle) * speed; - body.setPosition(body.x + moveX, body.y + moveY, updateNow); -} - -/***/ }), + /***/ + }, -/***/ "./src/demo/canvas.js": -/*!****************************!*\ + /***/ "./src/demo/canvas.js": + /*!****************************!*\ !*** ./src/demo/canvas.js ***! \****************************/ -/***/ ((module) => { + /***/ (module) => { + const width = window.innerWidth || 1024; + const height = window.innerHeight || 768; -const width = window.innerWidth || 1024; -const height = window.innerHeight || 768; + class TestCanvas { + constructor(test) { + this.test = test; -class TestCanvas { - constructor(test) { - this.test = test; - - this.element = document.createElement("div"); - this.element.id = "debug"; - this.element.innerHTML = `${this.test.legend} + this.element = document.createElement("div"); + this.element.id = "debug"; + this.element.innerHTML = `${this.test.legend}
`; - this.canvas = document.createElement("canvas"); - this.canvas.width = width; - this.canvas.height = height; - - this.context = this.canvas.getContext("2d"); - this.context.font = "24px Arial"; - this.test.context = this.context; + this.canvas = document.createElement("canvas"); + this.canvas.width = width; + this.canvas.height = height; - this.bvhCheckbox = this.element.querySelector("#bvh"); + this.context = this.canvas.getContext("2d"); + this.context.font = "24px Arial"; + this.test.context = this.context; - if (this.canvas instanceof Node) { - this.element.appendChild(this.canvas); - } + this.bvhCheckbox = this.element.querySelector("#bvh"); - this.fps = 0; - this.frame = 0; - this.started = Date.now(); + if (this.canvas instanceof Node) { + this.element.appendChild(this.canvas); + } - loop(this.update.bind(this)); - } + this.fps = 0; + this.frame = 0; + this.started = Date.now(); - update() { - this.frame++; + loop(this.update.bind(this)); + } - const timeDiff = Date.now() - this.started; - if (timeDiff >= 1000) { - this.fps = this.frame / (timeDiff / 1000); - this.frame = 0; - this.started = Date.now(); - } + update() { + this.frame++; - // Clear the canvas - this.context.fillStyle = "#000000"; - this.context.fillRect(0, 0, width, height); - - // Render the bodies - this.context.strokeStyle = "#FFFFFF"; - this.context.beginPath(); - this.test.physics.draw(this.context); - this.context.stroke(); - - // Render the BVH - if (this.bvhCheckbox.checked) { - this.context.strokeStyle = "#00FF00"; - this.context.beginPath(); - this.test.physics.drawBVH(this.context); - this.context.stroke(); - } + const timeDiff = Date.now() - this.started; + if (timeDiff >= 1000) { + this.fps = this.frame / (timeDiff / 1000); + this.frame = 0; + this.started = Date.now(); + } - // Render the FPS - this.context.fillStyle = "#FFCC00"; - this.context.fillText( - `FPS: ${this.fps ? this.fps.toFixed(0) : "?"}`, - 24, - 48, - ); + // Clear the canvas + this.context.fillStyle = "#000000"; + this.context.fillRect(0, 0, width, height); + + // Render the bodies + this.context.strokeStyle = "#FFFFFF"; + this.context.beginPath(); + this.test.physics.draw(this.context); + this.context.stroke(); + + // Render the BVH + if (this.bvhCheckbox.checked) { + this.context.strokeStyle = "#00FF00"; + this.context.beginPath(); + this.test.physics.drawBVH(this.context); + this.context.stroke(); + } - if (this.test.drawCallback) { - this.test.drawCallback(); - } - } -} + // Render the FPS + this.context.fillStyle = "#FFCC00"; + this.context.fillText( + `FPS: ${this.fps ? this.fps.toFixed(0) : "?"}`, + 24, + 48, + ); -function loop(callback) { - // interval for fps instead of setTimeout - // and ms = 1 which is lowest nonzero value - // for responsiveness of user input - setInterval(callback, 1); -} + if (this.test.drawCallback) { + this.test.drawCallback(); + } + } + } -module.exports.TestCanvas = TestCanvas; + function loop(callback) { + // interval for fps instead of setTimeout + // and ms = 1 which is lowest nonzero value + // for responsiveness of user input + setInterval(callback, 1); + } -module.exports.loop = loop; + module.exports.TestCanvas = TestCanvas; -module.exports.width = width; + module.exports.loop = loop; -module.exports.height = height; + module.exports.width = width; + module.exports.height = height; -/***/ }), + /***/ + }, -/***/ "./src/demo/stress.js": -/*!****************************!*\ + /***/ "./src/demo/stress.js": + /*!****************************!*\ !*** ./src/demo/stress.js ***! \****************************/ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { BodyGroup } = __webpack_require__(/*! ../model */ "./src/model.ts"); -const { System } = __webpack_require__(/*! ../system */ "./src/system.ts"); -const { getBounceDirection, groupBits } = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -const { width, height, loop } = __webpack_require__(/*! ./canvas */ "./src/demo/canvas.js"); -const seededRandom = (__webpack_require__(/*! random-seed */ "./node_modules/random-seed/index.js").create)("@Prozi").random; - -function random(min, max) { - return Math.floor(seededRandom() * max) + min; -} - -class Stress { - constructor(count = 2000) { - this.size = Math.sqrt((width * height) / (count * 50)); - - this.physics = new System(5); - this.bodies = []; - this.polygons = 0; - this.boxes = 0; - this.circles = 0; - this.ellipses = 0; - this.lines = 0; - this.lastVariant = 0; - this.count = count; - this.bounds = this.getBounds(); - this.enableFiltering = false; - - for (let i = 0; i < count; ++i) { - this.createShape(!random(0, 20)); - } + /***/ (module, __unused_webpack_exports, __webpack_require__) => { + const { BodyGroup } = __webpack_require__( + /*! ../model */ "./src/model.ts", + ); + const { System } = __webpack_require__( + /*! ../system */ "./src/system.ts", + ); + const { getBounceDirection, groupBits } = __webpack_require__( + /*! ../utils */ "./src/utils.ts", + ); + const { width, height, loop } = __webpack_require__( + /*! ./canvas */ "./src/demo/canvas.js", + ); + const seededRandom = __webpack_require__( + /*! random-seed */ "./node_modules/random-seed/index.js", + ).create("@Prozi").random; + + function random(min, max) { + return Math.floor(seededRandom() * max) + min; + } - this.legend = `
Total: ${count}
+ function getDefaultCount() { + return Math.floor(Math.min(2000, Math.hypot(width, height))); + } + + class Stress { + constructor(count = getDefaultCount()) { + this.size = Math.sqrt((width * height) / (count * 50)); + + this.physics = new System(5); + this.bodies = []; + this.polygons = 0; + this.boxes = 0; + this.circles = 0; + this.ellipses = 0; + this.lines = 0; + this.lastVariant = 0; + this.count = count; + this.bounds = this.getBounds(); + this.enableFiltering = false; + + for (let i = 0; i < count; ++i) { + this.createShape(!random(0, 20)); + } + + this.legend = `
Total: ${count}
Polygons: ${this.polygons}
Boxes: ${this.boxes}
Circles: ${this.circles}
@@ -4125,1311 +4680,1427 @@ class Stress { `; - this.lastTime = Date.now(); - this.updateBody = this.updateBody.bind(this); + this.lastTime = Date.now(); + this.updateBody = this.updateBody.bind(this); + + // observer #debug & add filtering checkbox event + const observer = new window.MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.id == "debug") { + document + .querySelector("#filtering") + .addEventListener("change", () => this.toggleFiltering()); + observer.disconnect(); + } + }); + }); + }); + observer.observe(document.querySelector("body"), { + subtree: false, + childList: true, + }); - // observer #debug & add filtering checkbox event - const observer = new window.MutationObserver((mutations) => { - mutations.forEach((mutation) => { - mutation.addedNodes.forEach((node) => { - if (node.id == "debug") { - document - .querySelector("#filtering") - .addEventListener("change", () => this.toggleFiltering()); - observer.disconnect(); + this.start = () => { + loop(this.update.bind(this)); + }; } - }); - }); - }); - observer.observe(document.querySelector("body"), { - subtree: false, - childList: true, - }); - - this.start = () => { - loop(this.update.bind(this)); - }; - } - - getBounds() { - return [ - this.physics.createBox({ x: 0, y: 0 }, width, 10, { - isStatic: true, - }), - this.physics.createBox({ x: width - 10, y: 0 }, 10, height, { - isStatic: true, - }), - this.physics.createBox({ x: 0, y: height - 10 }, width, 10, { - isStatic: true, - }), - this.physics.createBox({ x: 0, y: 0 }, 10, height, { - isStatic: true, - }), - ]; - } - - toggleFiltering() { - this.enableFiltering = !this.enableFiltering; - this.physics.clear(); - this.bodies.length = 0; - this.polygons = 0; - this.boxes = 0; - this.circles = 0; - this.ellipses = 0; - this.lines = 0; - this.lastVariant = 0; - this.bounds = this.getBounds(); - for (let i = 0; i < this.count; ++i) { - this.createShape(!random(0, 20)); - } - } - - update() { - const now = Date.now(); - this.timeScale = Math.min(1000, now - this.lastTime) / 60; - this.lastTime = now; - this.bodies.forEach(this.updateBody); - } - - updateBody(body) { - body.setAngle(body.angle + body.rotationSpeed * this.timeScale, false); - - if (seededRandom() < 0.05 * this.timeScale) { - body.targetScale.x = 0.5 + seededRandom(); - } - - if (seededRandom() < 0.05 * this.timeScale) { - body.targetScale.y = 0.5 + seededRandom(); - } - - if (Math.abs(body.targetScale.x - body.scaleX) > 0.01) { - const scaleX = - body.scaleX + - Math.sign(body.targetScale.x - body.scaleX) * 0.02 * this.timeScale; - const scaleY = - body.scaleY + - Math.sign(body.targetScale.y - body.scaleY) * 0.02 * this.timeScale; - - body.setScale(scaleX, scaleY, false); - } - - // as last step update position, and bounding box - body.setPosition( - body.x + body.directionX * this.timeScale, - body.y + body.directionY * this.timeScale, - ); - - // separate + bounce - this.bounceBody(body); - } - - bounceBody(body) { - const bounces = { x: 0, y: 0 }; - const addBounces = ({ overlapV: { x, y } }) => { - bounces.x += x; - bounces.y += y; - }; - - this.physics.checkOne(body, addBounces); - - if (bounces.x || bounces.y) { - const size = 0.5 * (body.scaleX + body.scaleY); - const bounce = getBounceDirection(body, { - x: body.x + bounces.x, - y: body.y + bounces.y, - }); - - bounce.scale(body.size).add({ - x: body.directionX * size, - y: body.directionY * size, - }); - - const { x, y } = bounce.normalize(); - - body.directionX = x; - body.directionY = y; - body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; - - body.setPosition(body.x - bounces.x, body.y - bounces.y); - } - } - - createShape(large) { - const minSize = this.size * 1.0 * (large ? seededRandom() + 1 : 1); - const maxSize = this.size * 1.25 * (large ? seededRandom() * 2 + 1 : 1); - const x = random(0, width); - const y = random(0, height); - const direction = (random(0, 360) * Math.PI) / 180; - const options = { - isCentered: true, - padding: (minSize + maxSize) * 0.2, - }; - - let body; - let variant = this.lastVariant++ % 5; - switch (variant) { - case 0: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Circle); - } - body = this.physics.createCircle( - { x, y }, - random(minSize, maxSize) / 2, - options, - ); + getBounds() { + return [ + this.physics.createBox({ x: 0, y: 0 }, width, 10, { + isStatic: true, + }), + this.physics.createBox({ x: width - 10, y: 0 }, 10, height, { + isStatic: true, + }), + this.physics.createBox({ x: 0, y: height - 10 }, width, 10, { + isStatic: true, + }), + this.physics.createBox({ x: 0, y: 0 }, 10, height, { + isStatic: true, + }), + ]; + } - ++this.circles; - break; + toggleFiltering() { + this.enableFiltering = !this.enableFiltering; + this.physics.clear(); + this.bodies.length = 0; + this.polygons = 0; + this.boxes = 0; + this.circles = 0; + this.ellipses = 0; + this.lines = 0; + this.lastVariant = 0; + this.bounds = this.getBounds(); + for (let i = 0; i < this.count; ++i) { + this.createShape(!random(0, 20)); + } + } - case 1: - const width = random(minSize, maxSize); - const height = random(minSize, maxSize); - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Ellipse); - console.log(); - } - body = this.physics.createEllipse({ x, y }, width, height, 2, options); + update() { + const now = Date.now(); + this.timeScale = Math.min(1000, now - this.lastTime) / 60; + this.lastTime = now; + this.bodies.forEach(this.updateBody); + } - ++this.ellipses; - break; + updateBody(body) { + body.setAngle( + body.angle + body.rotationSpeed * this.timeScale, + false, + ); - case 2: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Box); - } - body = this.physics.createBox( - { x, y }, - random(minSize, maxSize), - random(minSize, maxSize), - options, - ); + if (seededRandom() < 0.05 * this.timeScale) { + body.targetScale.x = 0.5 + seededRandom(); + } - ++this.boxes; - break; + if (seededRandom() < 0.05 * this.timeScale) { + body.targetScale.y = 0.5 + seededRandom(); + } - case 3: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Line); - } - body = this.physics.createLine( - { x, y }, - { - x: x + random(minSize, maxSize), - y: y + random(minSize, maxSize), - }, - options, - ); + if (Math.abs(body.targetScale.x - body.scaleX) > 0.01) { + const scaleX = + body.scaleX + + Math.sign(body.targetScale.x - body.scaleX) * + 0.02 * + this.timeScale; + const scaleY = + body.scaleY + + Math.sign(body.targetScale.y - body.scaleY) * + 0.02 * + this.timeScale; + + body.setScale(scaleX, scaleY, false); + } - ++this.lines; - break; + // as last step update position, and bounding box + body.setPosition( + body.x + body.directionX * this.timeScale, + body.y + body.directionY * this.timeScale, + ); - default: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Polygon); - } - body = this.physics.createPolygon( - { x, y }, - [ - { x: -random(minSize, maxSize), y: random(minSize, maxSize) }, - { x: random(minSize, maxSize), y: random(minSize, maxSize) }, - { x: random(minSize, maxSize), y: -random(minSize, maxSize) }, - { x: -random(minSize, maxSize), y: -random(minSize, maxSize) }, - ], - options, - ); + // separate + bounce + this.bounceBody(body); + } - ++this.polygons; - break; - } + bounceBody(body) { + const bounces = { x: 0, y: 0 }; + const addBounces = ({ overlapV: { x, y } }) => { + bounces.x += x; + bounces.y += y; + }; - // set initial rotation angle direction - body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; - body.setAngle((random(0, 360) * Math.PI) / 180); + this.physics.checkOne(body, addBounces); - body.targetScale = { x: 1, y: 1 }; - body.size = (minSize + maxSize) / 2; + if (bounces.x || bounces.y) { + const size = 0.5 * (body.scaleX + body.scaleY); + const bounce = getBounceDirection(body, { + x: body.x + bounces.x, + y: body.y + bounces.y, + }); - body.directionX = Math.cos(direction); - body.directionY = Math.sin(direction); + bounce.scale(body.size).add({ + x: body.directionX * size, + y: body.directionY * size, + }); - this.bodies.push(body); - } -} + const { x, y } = bounce.normalize(); -module.exports = Stress; + body.directionX = x; + body.directionY = y; + body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; + body.setPosition(body.x - bounces.x, body.y - bounces.y); + } + } -/***/ }), + createShape(large) { + const minSize = this.size * 1.0 * (large ? seededRandom() + 1 : 1); + const maxSize = + this.size * 1.25 * (large ? seededRandom() * 2 + 1 : 1); + const x = random(0, width); + const y = random(0, height); + const direction = (random(0, 360) * Math.PI) / 180; + const options = { + isCentered: true, + padding: (minSize + maxSize) * 0.2, + }; + + let body; + let variant = this.lastVariant++ % 5; + + switch (variant) { + case 0: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Circle); + } + body = this.physics.createCircle( + { x, y }, + random(minSize, maxSize) / 2, + options, + ); + + ++this.circles; + break; + + case 1: + const width = random(minSize, maxSize); + const height = random(minSize, maxSize); + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Ellipse); + console.log(); + } + body = this.physics.createEllipse( + { x, y }, + width, + height, + 2, + options, + ); + + ++this.ellipses; + break; + + case 2: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Box); + } + body = this.physics.createBox( + { x, y }, + random(minSize, maxSize), + random(minSize, maxSize), + options, + ); + + ++this.boxes; + break; + + case 3: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Line); + } + body = this.physics.createLine( + { x, y }, + { + x: x + random(minSize, maxSize), + y: y + random(minSize, maxSize), + }, + options, + ); + + ++this.lines; + break; + + default: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Polygon); + } + body = this.physics.createPolygon( + { x, y }, + [ + { + x: -random(minSize, maxSize), + y: random(minSize, maxSize), + }, + { + x: random(minSize, maxSize), + y: random(minSize, maxSize), + }, + { + x: random(minSize, maxSize), + y: -random(minSize, maxSize), + }, + { + x: -random(minSize, maxSize), + y: -random(minSize, maxSize), + }, + ], + options, + ); + + ++this.polygons; + break; + } -/***/ "./src/demo/tank.js": -/*!**************************!*\ - !*** ./src/demo/tank.js ***! - \**************************/ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { BodyGroup } = __webpack_require__(/*! ../model */ "./src/model.ts"); -const { System } = __webpack_require__(/*! ../system */ "./src/system.ts"); -const { mapVectorToArray } = __webpack_require__(/*! ../utils */ "./src/utils.ts"); -const { width, height, loop } = __webpack_require__(/*! ./canvas */ "./src/demo/canvas.js"); - -class Tank { - constructor() { - this.physics = new System(); - this.bodies = []; - this.player = this.createPlayer(400, 300); - - this.createPolygon( - 300, - 300, - [ - { x: -11.25, y: -6.76 }, - { x: -12.5, y: -6.76 }, - { x: -12.5, y: 6.75 }, - { x: -3.1, y: 6.75 }, - { x: -3.1, y: 0.41 }, - { x: -2.35, y: 0.41 }, - { x: -2.35, y: 6.75 }, - { x: 0.77, y: 6.75 }, - { x: 0.77, y: 7.5 }, - { x: -13.25, y: 7.5 }, - { x: -13.25, y: -7.51 }, - { x: -11.25, y: -7.51 }, - ] - .map(mapVectorToArray) - .map(([x, y]) => [x * 10, y * 10]), - ); + // set initial rotation angle direction + body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; + body.setAngle((random(0, 360) * Math.PI) / 180); - this.up = false; - this.down = false; - this.left = false; - this.right = false; + body.targetScale = { x: 1, y: 1 }; + body.size = (minSize + maxSize) / 2; - this.legend = `
W, S - Accelerate/Decelerate
-
A, D - Turn
`; + body.directionX = Math.cos(direction); + body.directionY = Math.sin(direction); - const updateKeys = ({ type, key }) => { - const keyDown = type === "keydown"; - const keyLowerCase = key.toLowerCase(); + this.bodies.push(body); + } + } - keyLowerCase === "w" && (this.up = keyDown); - keyLowerCase === "s" && (this.down = keyDown); - keyLowerCase === "a" && (this.left = keyDown); - keyLowerCase === "d" && (this.right = keyDown); - }; + module.exports = Stress; - document.addEventListener("keydown", updateKeys); - document.addEventListener("keyup", updateKeys); + /***/ + }, - if (this.canvas instanceof Node) { - this.element.appendChild(this.canvas); - } + /***/ "./src/demo/tank.js": + /*!**************************!*\ + !*** ./src/demo/tank.js ***! + \**************************/ + /***/ (module, __unused_webpack_exports, __webpack_require__) => { + const { BodyGroup } = __webpack_require__( + /*! ../model */ "./src/model.ts", + ); + const { System } = __webpack_require__( + /*! ../system */ "./src/system.ts", + ); + const { mapVectorToArray } = __webpack_require__( + /*! ../utils */ "./src/utils.ts", + ); + const { width, height, loop } = __webpack_require__( + /*! ./canvas */ "./src/demo/canvas.js", + ); - this.createMap(); - this.lastTime = Date.now(); + class Tank { + constructor() { + this.physics = new System(); + this.bodies = []; + this.player = this.createPlayer(400, 300); + + this.createPolygon( + 300, + 300, + [ + { x: -11.25, y: -6.76 }, + { x: -12.5, y: -6.76 }, + { x: -12.5, y: 6.75 }, + { x: -3.1, y: 6.75 }, + { x: -3.1, y: 0.41 }, + { x: -2.35, y: 0.41 }, + { x: -2.35, y: 6.75 }, + { x: 0.77, y: 6.75 }, + { x: 0.77, y: 7.5 }, + { x: -13.25, y: 7.5 }, + { x: -13.25, y: -7.51 }, + { x: -11.25, y: -7.51 }, + ] + .map(mapVectorToArray) + .map(([x, y]) => [x * 10, y * 10]), + ); + + this.up = false; + this.down = false; + this.left = false; + this.right = false; + + this.legend = `
W, S - Accelerate/Decelerate
+
A, D - Turn
`; - this.start = () => { - loop(this.update.bind(this)); - }; - } + const updateKeys = ({ type, key }) => { + const keyDown = type === "keydown"; + const keyLowerCase = key.toLowerCase(); - update() { - const now = Date.now(); - this.timeScale = Math.min(1000, now - this.lastTime) / 60; - this.lastTime = now; - this.handleInput(); - this.processGameLogic(); - this.handleCollisions(); - this.updateTurret(); - } + keyLowerCase === "w" && (this.up = keyDown); + keyLowerCase === "s" && (this.down = keyDown); + keyLowerCase === "a" && (this.left = keyDown); + keyLowerCase === "d" && (this.right = keyDown); + }; - handleInput() { - if (this.up) { - this.player.velocity += 0.2 * this.timeScale; - } + document.addEventListener("keydown", updateKeys); + document.addEventListener("keyup", updateKeys); - if (this.down) { - this.player.velocity -= 0.2 * this.timeScale; - } + if (this.canvas instanceof Node) { + this.element.appendChild(this.canvas); + } - if (this.left) { - this.player.setAngle(this.player.angle - 0.2 * this.timeScale); - } + this.createMap(); + this.lastTime = Date.now(); - if (this.right) { - this.player.setAngle(this.player.angle + 0.2 * this.timeScale); - } - } + this.start = () => { + loop(this.update.bind(this)); + }; + } - processGameLogic() { - const x = Math.cos(this.player.angle); - const y = Math.sin(this.player.angle); + update() { + const now = Date.now(); + this.timeScale = Math.min(1000, now - this.lastTime) / 60; + this.lastTime = now; + this.handleInput(); + this.processGameLogic(); + this.handleCollisions(); + this.updateTurret(); + } - if (this.player.velocity > 0) { - this.player.velocity = Math.max( - this.player.velocity - 0.1 * this.timeScale, - 0, - ); + handleInput() { + if (this.up) { + this.player.velocity += 0.2 * this.timeScale; + } - if (this.player.velocity > 2) { - this.player.velocity = 2; - } - } else if (this.player.velocity < 0) { - this.player.velocity = Math.min( - this.player.velocity + 0.1 * this.timeScale, - 0, - ); - - if (this.player.velocity < -2) { - this.player.velocity = -2; - } - } + if (this.down) { + this.player.velocity -= 0.2 * this.timeScale; + } - if (!Math.round(this.player.velocity * 100)) { - this.player.velocity = 0; - } + if (this.left) { + this.player.setAngle(this.player.angle - 0.2 * this.timeScale); + } - if (this.player.velocity) { - this.player.setPosition( - this.player.x + x * this.player.velocity, - this.player.y + y * this.player.velocity, - ); - } - } + if (this.right) { + this.player.setAngle(this.player.angle + 0.2 * this.timeScale); + } + } - handleCollisions() { - this.physics.checkAll(({ a, b, overlapV }) => { - if (a.isTrigger || b.isTrigger) { - return; - } + processGameLogic() { + const x = Math.cos(this.player.angle); + const y = Math.sin(this.player.angle); + + if (this.player.velocity > 0) { + this.player.velocity = Math.max( + this.player.velocity - 0.1 * this.timeScale, + 0, + ); + + if (this.player.velocity > 2) { + this.player.velocity = 2; + } + } else if (this.player.velocity < 0) { + this.player.velocity = Math.min( + this.player.velocity + 0.1 * this.timeScale, + 0, + ); + + if (this.player.velocity < -2) { + this.player.velocity = -2; + } + } - if (a.typeGroup === BodyGroup.Polygon || a === this.player) { - a.setPosition(a.pos.x - overlapV.x, a.pos.y - overlapV.y); - } + if (!Math.round(this.player.velocity * 100)) { + this.player.velocity = 0; + } - if (b.typeGroup === BodyGroup.Circle || b === this.player) { - b.setPosition(b.pos.x + overlapV.x, b.pos.y + overlapV.y); - } + if (this.player.velocity) { + this.player.setPosition( + this.player.x + x * this.player.velocity, + this.player.y + y * this.player.velocity, + ); + } + } - if (a === this.player) { - a.velocity *= 0.9; - } - }); - } + handleCollisions() { + this.physics.checkAll(({ a, b, overlapV }) => { + if (a.isTrigger || b.isTrigger) { + return; + } - updateTurret() { - this.playerTurret.setAngle(this.player.angle, false); - this.playerTurret.setPosition(this.player.x, this.player.y); + if (a.typeGroup === BodyGroup.Polygon || a === this.player) { + a.setPosition(a.pos.x - overlapV.x, a.pos.y - overlapV.y); + } - const hit = this.physics.raycast( - this.playerTurret.start, - this.playerTurret.end, - (test) => test !== this.player, - ); + if (b.typeGroup === BodyGroup.Circle || b === this.player) { + b.setPosition(b.pos.x + overlapV.x, b.pos.y + overlapV.y); + } - this.drawCallback = () => { - if (hit) { - this.context.strokeStyle = "#FF0000"; - this.context.beginPath(); - this.context.arc(hit.point.x, hit.point.y, 5, 0, 2 * Math.PI); - this.context.stroke(); - } - }; - } + if (a === this.player) { + a.velocity *= 0.9; + } + }); + } - createPlayer(x, y, size = 13) { - const player = - Math.random() < 0.5 - ? this.physics.createCircle( - { x: this.scaleX(x), y: this.scaleY(y) }, - this.scaleX(size / 2), - { isCentered: true }, - ) - : this.physics.createBox( - { x: this.scaleX(x - size / 2), y: this.scaleY(y - size / 2) }, - this.scaleX(size), - this.scaleX(size), - { isCentered: true }, - ); + updateTurret() { + this.playerTurret.setAngle(this.player.angle, false); + this.playerTurret.setPosition(this.player.x, this.player.y); + + const hit = this.physics.raycast( + this.playerTurret.start, + this.playerTurret.end, + (test) => test !== this.player, + ); + + this.drawCallback = () => { + if (hit) { + this.context.strokeStyle = "#FF0000"; + this.context.beginPath(); + this.context.arc(hit.point.x, hit.point.y, 5, 0, 2 * Math.PI); + this.context.stroke(); + } + }; + } - player.velocity = 0; - player.setOffset({ x: -this.scaleX(size / 2), y: 0 }); - player.setAngle(0.2); + createPlayer(x, y, size = 13) { + const player = + Math.random() < 0.5 + ? this.physics.createCircle( + { x: this.scaleX(x), y: this.scaleY(y) }, + this.scaleX(size / 2), + { isCentered: true }, + ) + : this.physics.createBox( + { + x: this.scaleX(x - size / 2), + y: this.scaleY(y - size / 2), + }, + this.scaleX(size), + this.scaleX(size), + { isCentered: true }, + ); + + player.velocity = 0; + player.setOffset({ x: -this.scaleX(size / 2), y: 0 }); + player.setAngle(0.2); + + this.physics.updateBody(player); + this.playerTurret = this.physics.createLine( + player, + { x: player.x + this.scaleX(20) + this.scaleY(20), y: player.y }, + { angle: 0.2, isTrigger: true }, + ); + + return player; + } - this.physics.updateBody(player); - this.playerTurret = this.physics.createLine( - player, - { x: player.x + this.scaleX(20) + this.scaleY(20), y: player.y }, - { angle: 0.2, isTrigger: true }, - ); + scaleX(x) { + return (x / 800) * width; + } - return player; - } + scaleY(y) { + return (y / 600) * height; + } - scaleX(x) { - return (x / 800) * width; - } + createCircle(x, y, radius) { + this.physics.createCircle( + { x: this.scaleX(x), y: this.scaleY(y) }, + this.scaleX(radius), + ); + } - scaleY(y) { - return (y / 600) * height; - } + createEllipse(x, y, radiusX, radiusY, step, angle) { + this.physics.createEllipse( + { x: this.scaleX(x), y: this.scaleY(y) }, + this.scaleX(radiusX), + this.scaleY(radiusY), + step, + { angle }, + ); + } - createCircle(x, y, radius) { - this.physics.createCircle( - { x: this.scaleX(x), y: this.scaleY(y) }, - this.scaleX(radius), - ); - } + createPolygon(x, y, points, angle) { + const scaledPoints = points.map(([pointX, pointY]) => ({ + x: this.scaleX(pointX), + y: this.scaleY(pointY), + })); + + return this.physics.createPolygon( + { x: this.scaleX(x), y: this.scaleY(y) }, + scaledPoints, + { angle, isStatic: true }, + ); + } - createEllipse(x, y, radiusX, radiusY, step, angle) { - this.physics.createEllipse( - { x: this.scaleX(x), y: this.scaleY(y) }, - this.scaleX(radiusX), - this.scaleY(radiusY), - step, - { angle }, - ); - } + createMap(width = 800, height = 600) { + // World bounds + // World bounds + this.createPolygon(0, 0, [ + [0, 0], + [width, 0], + ]); + this.createPolygon(0, 0, [ + [width, 0], + [width, height], + ]); + this.createPolygon(0, 0, [ + [width, height], + [0, height], + ]); + this.createPolygon(0, 0, [ + [0, height], + [0, 0], + ]); + + // Factory + this.createPolygon( + 100, + 100, + [ + [-50, -50], + [50, -50], + [50, 50], + [-50, 50], + ], + 0.4, + ); + this.createPolygon( + 190, + 105, + [ + [-20, -20], + [20, -20], + [20, 20], + [-20, 20], + ], + 0.4, + ); + this.createCircle(170, 140, 6); + this.createCircle(185, 155, 6); + this.createCircle(165, 165, 6); + this.createCircle(145, 165, 6); + + // Airstrip + this.createPolygon( + 230, + 50, + [ + [-150, -30], + [150, -30], + [150, 30], + [-150, 30], + ], + 0.4, + ); + + // HQ + this.createPolygon( + 100, + 500, + [ + [-40, -50], + [40, -50], + [50, 50], + [-50, 50], + ], + 0.2, + ); + this.createCircle(180, 490, 12); + this.createCircle(175, 540, 12); + + // Barracks + this.createPolygon( + 400, + 500, + [ + [-60, -20], + [60, -20], + [60, 20], + [-60, 20], + ], + 1.7, + ); + this.createPolygon( + 350, + 494, + [ + [-60, -20], + [60, -20], + [60, 20], + [-60, 20], + ], + 1.7, + ); + + // Mountains + this.createPolygon(750, 0, [ + [0, 0], + [-20, 100], + ]); + this.createPolygon(750, 0, [ + [-20, 100], + [30, 250], + ]); + this.createPolygon(750, 0, [ + [30, 250], + [20, 300], + ]); + this.createPolygon(750, 0, [ + [20, 300], + [-50, 320], + ]); + this.createPolygon(750, 0, [ + [-50, 320], + [-90, 500], + ]); + this.createPolygon(750, 0, [ + [-90, 500], + [-200, 600], + ]); + + // Lake + this.createEllipse(530, 130, 80, 70, 10, -0.2); + } + } - createPolygon(x, y, points, angle) { - const scaledPoints = points.map(([pointX, pointY]) => ({ - x: this.scaleX(pointX), - y: this.scaleY(pointY), - })); + module.exports = Tank; - return this.physics.createPolygon( - { x: this.scaleX(x), y: this.scaleY(y) }, - scaledPoints, - { angle, isStatic: true }, - ); - } + /***/ + }, - createMap(width = 800, height = 600) { - // World bounds - // World bounds - this.createPolygon(0, 0, [ - [0, 0], - [width, 0], - ]); - this.createPolygon(0, 0, [ - [width, 0], - [width, height], - ]); - this.createPolygon(0, 0, [ - [width, height], - [0, height], - ]); - this.createPolygon(0, 0, [ - [0, height], - [0, 0], - ]); - - // Factory - this.createPolygon( - 100, - 100, - [ - [-50, -50], - [50, -50], - [50, 50], - [-50, 50], - ], - 0.4, - ); - this.createPolygon( - 190, - 105, - [ - [-20, -20], - [20, -20], - [20, 20], - [-20, 20], - ], - 0.4, - ); - this.createCircle(170, 140, 6); - this.createCircle(185, 155, 6); - this.createCircle(165, 165, 6); - this.createCircle(145, 165, 6); - - // Airstrip - this.createPolygon( - 230, - 50, - [ - [-150, -30], - [150, -30], - [150, 30], - [-150, 30], - ], - 0.4, - ); + /***/ "./src/external/quickselect.js": + /*!*************************************!*\ + !*** ./src/external/quickselect.js ***! + \*************************************/ + /***/ ( + __unused_webpack_module, + __webpack_exports__, + __webpack_require__, + ) => { + "use strict"; + __webpack_require__.r(__webpack_exports__); + /* harmony export */ __webpack_require__.d(__webpack_exports__, { + /* harmony export */ default: () => /* binding */ quickselect, + /* harmony export */ + }); - // HQ - this.createPolygon( - 100, - 500, - [ - [-40, -50], - [40, -50], - [50, 50], - [-50, 50], - ], - 0.2, - ); - this.createCircle(180, 490, 12); - this.createCircle(175, 540, 12); - - // Barracks - this.createPolygon( - 400, - 500, - [ - [-60, -20], - [60, -20], - [60, 20], - [-60, 20], - ], - 1.7, - ); - this.createPolygon( - 350, - 494, - [ - [-60, -20], - [60, -20], - [60, 20], - [-60, 20], - ], - 1.7, - ); + /** + * Rearranges items so that all items in the [left, k] are the smallest. + * The k-th element will have the (k - left + 1)-th smallest value in [left, right]. + * + * @template T + * @param {T[]} arr the array to partially sort (in place) + * @param {number} k middle index for partial sorting (as defined above) + * @param {number} [left=0] left index of the range to sort + * @param {number} [right=arr.length-1] right index + * @param {(a: T, b: T) => number} [compare = (a, b) => a - b] compare function + */ + function quickselect( + arr, + k, + left = 0, + right = arr.length - 1, + compare = defaultCompare, + ) { + while (right > left) { + if (right - left > 600) { + const n = right - left + 1; + const m = k - left + 1; + const z = Math.log(n); + const s = 0.5 * Math.exp((2 * z) / 3); + const sd = + 0.5 * + Math.sqrt((z * s * (n - s)) / n) * + (m - n / 2 < 0 ? -1 : 1); + const newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd)); + const newRight = Math.min( + right, + Math.floor(k + ((n - m) * s) / n + sd), + ); + quickselect(arr, k, newLeft, newRight, compare); + } - // Mountains - this.createPolygon(750, 0, [ - [0, 0], - [-20, 100], - ]); - this.createPolygon(750, 0, [ - [-20, 100], - [30, 250], - ]); - this.createPolygon(750, 0, [ - [30, 250], - [20, 300], - ]); - this.createPolygon(750, 0, [ - [20, 300], - [-50, 320], - ]); - this.createPolygon(750, 0, [ - [-50, 320], - [-90, 500], - ]); - this.createPolygon(750, 0, [ - [-90, 500], - [-200, 600], - ]); - - // Lake - this.createEllipse(530, 130, 80, 70, 10, -0.2); - } -} + const t = arr[k]; + let i = left; + /** @type {number} */ + let j = right; -module.exports = Tank; + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } -/***/ }), + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } -/***/ "./src/external/quickselect.js": -/*!*************************************!*\ - !*** ./src/external/quickselect.js ***! - \*************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "default": () => (/* binding */ quickselect) -/* harmony export */ }); - -/** - * Rearranges items so that all items in the [left, k] are the smallest. - * The k-th element will have the (k - left + 1)-th smallest value in [left, right]. - * - * @template T - * @param {T[]} arr the array to partially sort (in place) - * @param {number} k middle index for partial sorting (as defined above) - * @param {number} [left=0] left index of the range to sort - * @param {number} [right=arr.length-1] right index - * @param {(a: T, b: T) => number} [compare = (a, b) => a - b] compare function - */ -function quickselect(arr, k, left = 0, right = arr.length - 1, compare = defaultCompare) { - - while (right > left) { - if (right - left > 600) { - const n = right - left + 1; - const m = k - left + 1; - const z = Math.log(n); - const s = 0.5 * Math.exp(2 * z / 3); - const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); - const newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); - const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); - quickselect(arr, k, newLeft, newRight, compare); + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } } - const t = arr[k]; - let i = left; - /** @type {number} */ - let j = right; - - swap(arr, left, k); - if (compare(arr[right], t) > 0) swap(arr, left, right); - - while (i < j) { - swap(arr, i, j); - i++; - j--; - while (compare(arr[i], t) < 0) i++; - while (compare(arr[j], t) > 0) j--; + /** + * @template T + * @param {T[]} arr + * @param {number} i + * @param {number} j + */ + function swap(arr, i, j) { + const tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; } - if (compare(arr[left], t) === 0) swap(arr, left, j); - else { - j++; - swap(arr, j, right); + /** + * @template T + * @param {T} a + * @param {T} b + * @returns {number} + */ + function defaultCompare(a, b) { + return a < b ? -1 : a > b ? 1 : 0; } - if (j <= k) left = j + 1; - if (k <= j) right = j - 1; - } -} - -/** - * @template T - * @param {T[]} arr - * @param {number} i - * @param {number} j - */ -function swap(arr, i, j) { - const tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; -} - -/** - * @template T - * @param {T} a - * @param {T} b - * @returns {number} - */ -function defaultCompare(a, b) { - return a < b ? -1 : a > b ? 1 : 0; -} - - -/***/ }), - -/***/ "./src/external/rbush.js": -/*!*******************************!*\ + /***/ + }, + + /***/ "./src/external/rbush.js": + /*!*******************************!*\ !*** ./src/external/rbush.js ***! \*******************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "default": () => (/* binding */ RBush) -/* harmony export */ }); -/* harmony import */ var _quickselect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./quickselect */ "./src/external/quickselect.js"); - - -class RBush { - constructor(maxEntries = 9) { - // max entries in a node is 9 by default; min node fill is 40% for best performance - this._maxEntries = Math.max(4, maxEntries); - this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); - this.clear(); - } + /***/ ( + __unused_webpack_module, + __webpack_exports__, + __webpack_require__, + ) => { + "use strict"; + __webpack_require__.r(__webpack_exports__); + /* harmony export */ __webpack_require__.d(__webpack_exports__, { + /* harmony export */ default: () => /* binding */ RBush, + /* harmony export */ + }); + /* harmony import */ var _quickselect__WEBPACK_IMPORTED_MODULE_0__ = + __webpack_require__( + /*! ./quickselect */ "./src/external/quickselect.js", + ); - all() { - return this._all(this.data, []); - } + class RBush { + constructor(maxEntries = 9) { + // max entries in a node is 9 by default; min node fill is 40% for best performance + this._maxEntries = Math.max(4, maxEntries); + this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); + this.clear(); + } - search(bbox) { - let node = this.data; - const result = []; + all() { + return this._all(this.data, []); + } + + search(bbox) { + let node = this.data; + const result = []; - if (!intersects(bbox, node)) return result; + if (!intersects(bbox, node)) return result; - const toBBox = this.toBBox; - const nodesToSearch = []; + const toBBox = this.toBBox; + const nodesToSearch = []; - while (node) { - for (let i = 0; i < node.children.length; i++) { + while (node) { + for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; const childBBox = node.leaf ? toBBox(child) : child; if (intersects(bbox, childBBox)) { - if (node.leaf) result.push(child); - else if (contains(bbox, childBBox)) this._all(child, result); - else nodesToSearch.push(child); + if (node.leaf) result.push(child); + else if (contains(bbox, childBBox)) this._all(child, result); + else nodesToSearch.push(child); } + } + node = nodesToSearch.pop(); } - node = nodesToSearch.pop(); - } - return result; - } + return result; + } - collides(bbox) { - let node = this.data; + collides(bbox) { + let node = this.data; - if (!intersects(bbox, node)) return false; + if (!intersects(bbox, node)) return false; - const nodesToSearch = []; - while (node) { - for (let i = 0; i < node.children.length; i++) { + const nodesToSearch = []; + while (node) { + for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; const childBBox = node.leaf ? this.toBBox(child) : child; if (intersects(bbox, childBBox)) { - if (node.leaf || contains(bbox, childBBox)) return true; - nodesToSearch.push(child); + if (node.leaf || contains(bbox, childBBox)) return true; + nodesToSearch.push(child); } + } + node = nodesToSearch.pop(); } - node = nodesToSearch.pop(); - } - return false; - } + return false; + } - load(data) { - if (!(data && data.length)) return this; + load(data) { + if (!(data && data.length)) return this; - if (data.length < this._minEntries) { - for (let i = 0; i < data.length; i++) { + if (data.length < this._minEntries) { + for (let i = 0; i < data.length; i++) { this.insert(data[i]); + } + return this; } - return this; - } - - // recursively build the tree with the given data from scratch using OMT algorithm - let node = this._build(data.slice(), 0, data.length - 1, 0); - if (!this.data.children.length) { - // save as is if tree is empty - this.data = node; - - } else if (this.data.height === node.height) { - // split root if trees have the same height - this._splitRoot(this.data, node); - - } else { - if (this.data.height < node.height) { + // recursively build the tree with the given data from scratch using OMT algorithm + let node = this._build(data.slice(), 0, data.length - 1, 0); + + if (!this.data.children.length) { + // save as is if tree is empty + this.data = node; + } else if (this.data.height === node.height) { + // split root if trees have the same height + this._splitRoot(this.data, node); + } else { + if (this.data.height < node.height) { // swap trees if inserted one is bigger const tmpNode = this.data; this.data = node; node = tmpNode; - } - - // insert the small tree into the large tree at appropriate level - this._insert(node, this.data.height - node.height - 1, true); - } + } - return this; - } + // insert the small tree into the large tree at appropriate level + this._insert(node, this.data.height - node.height - 1, true); + } - insert(item) { - if (item) this._insert(item, this.data.height - 1); - return this; - } + return this; + } - clear() { - this.data = createNode([]); - return this; - } + insert(item) { + if (item) this._insert(item, this.data.height - 1); + return this; + } - remove(item, equalsFn) { - if (!item) return this; + clear() { + this.data = createNode([]); + return this; + } - let node = this.data; - const bbox = this.toBBox(item); - const path = []; - const indexes = []; - let i, parent, goingUp; + remove(item, equalsFn) { + if (!item) return this; - // depth-first iterative tree traversal - while (node || path.length) { + let node = this.data; + const bbox = this.toBBox(item); + const path = []; + const indexes = []; + let i, parent, goingUp; - if (!node) { // go up + // depth-first iterative tree traversal + while (node || path.length) { + if (!node) { + // go up node = path.pop(); parent = path[path.length - 1]; i = indexes.pop(); goingUp = true; - } + } - if (node.leaf) { // check current node + if (node.leaf) { + // check current node const index = findItem(item, node.children, equalsFn); if (index !== -1) { - // item found, remove the item and condense tree upwards - node.children.splice(index, 1); - path.push(node); - this._condense(path); - return this; + // item found, remove the item and condense tree upwards + node.children.splice(index, 1); + path.push(node); + this._condense(path); + return this; } - } + } - if (!goingUp && !node.leaf && contains(node, bbox)) { // go down + if (!goingUp && !node.leaf && contains(node, bbox)) { + // go down path.push(node); indexes.push(i); i = 0; parent = node; node = node.children[0]; - - } else if (parent) { // go right + } else if (parent) { + // go right i++; node = parent.children[i]; goingUp = false; + } else node = null; // nothing found + } - } else node = null; // nothing found - } - - return this; - } - - toBBox(item) { return item; } - - compareMinX(a, b) { return a.minX - b.minX; } - compareMinY(a, b) { return a.minY - b.minY; } - - toJSON() { return this.data; } + return this; + } - fromJSON(data) { - this.data = data; - return this; - } + toBBox(item) { + return item; + } - _all(node, result) { - const nodesToSearch = []; - while (node) { - if (node.leaf) result.push(...node.children); - else nodesToSearch.push(...node.children); + compareMinX(a, b) { + return a.minX - b.minX; + } + compareMinY(a, b) { + return a.minY - b.minY; + } - node = nodesToSearch.pop(); - } - return result; - } + toJSON() { + return this.data; + } - _build(items, left, right, height) { + fromJSON(data) { + this.data = data; + return this; + } - const N = right - left + 1; - let M = this._maxEntries; - let node; + _all(node, result) { + const nodesToSearch = []; + while (node) { + if (node.leaf) result.push(...node.children); + else nodesToSearch.push(...node.children); - if (N <= M) { - // reached leaf level; return leaf - node = createNode(items.slice(left, right + 1)); - calcBBox(node, this.toBBox); - return node; - } + node = nodesToSearch.pop(); + } + return result; + } - if (!height) { - // target height of the bulk-loaded tree - height = Math.ceil(Math.log(N) / Math.log(M)); + _build(items, left, right, height) { + const N = right - left + 1; + let M = this._maxEntries; + let node; - // target number of root entries to maximize storage utilization - M = Math.ceil(N / Math.pow(M, height - 1)); - } + if (N <= M) { + // reached leaf level; return leaf + node = createNode(items.slice(left, right + 1)); + calcBBox(node, this.toBBox); + return node; + } - node = createNode([]); - node.leaf = false; - node.height = height; + if (!height) { + // target height of the bulk-loaded tree + height = Math.ceil(Math.log(N) / Math.log(M)); - // split the items into M mostly square tiles + // target number of root entries to maximize storage utilization + M = Math.ceil(N / Math.pow(M, height - 1)); + } - const N2 = Math.ceil(N / M); - const N1 = N2 * Math.ceil(Math.sqrt(M)); + node = createNode([]); + node.leaf = false; + node.height = height; - multiSelect(items, left, right, N1, this.compareMinX); + // split the items into M mostly square tiles - for (let i = left; i <= right; i += N1) { + const N2 = Math.ceil(N / M); + const N1 = N2 * Math.ceil(Math.sqrt(M)); - const right2 = Math.min(i + N1 - 1, right); + multiSelect(items, left, right, N1, this.compareMinX); - multiSelect(items, i, right2, N2, this.compareMinY); + for (let i = left; i <= right; i += N1) { + const right2 = Math.min(i + N1 - 1, right); - for (let j = i; j <= right2; j += N2) { + multiSelect(items, i, right2, N2, this.compareMinY); + for (let j = i; j <= right2; j += N2) { const right3 = Math.min(j + N2 - 1, right2); // pack each entry recursively node.children.push(this._build(items, j, right3, height - 1)); + } } - } - calcBBox(node, this.toBBox); + calcBBox(node, this.toBBox); - return node; - } + return node; + } - _chooseSubtree(bbox, node, level, path) { - while (true) { - path.push(node); + _chooseSubtree(bbox, node, level, path) { + while (true) { + path.push(node); - if (node.leaf || path.length - 1 === level) break; + if (node.leaf || path.length - 1 === level) break; - let minArea = Infinity; - let minEnlargement = Infinity; - let targetNode; + let minArea = Infinity; + let minEnlargement = Infinity; + let targetNode; - for (let i = 0; i < node.children.length; i++) { + for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; const area = bboxArea(child); const enlargement = enlargedArea(bbox, child) - area; // choose entry with the least area enlargement if (enlargement < minEnlargement) { - minEnlargement = enlargement; - minArea = area < minArea ? area : minArea; - targetNode = child; - + minEnlargement = enlargement; + minArea = area < minArea ? area : minArea; + targetNode = child; } else if (enlargement === minEnlargement) { - // otherwise choose one with the smallest area - if (area < minArea) { - minArea = area; - targetNode = child; - } + // otherwise choose one with the smallest area + if (area < minArea) { + minArea = area; + targetNode = child; + } } - } - - node = targetNode || node.children[0]; - } - - return node; - } + } - _insert(item, level, isNode) { - const bbox = isNode ? item : this.toBBox(item); - const insertPath = []; - - // find the best node for accommodating the item, saving all nodes along the path too - const node = this._chooseSubtree(bbox, this.data, level, insertPath); + node = targetNode || node.children[0]; + } - // put the item into the node - node.children.push(item); - extend(node, bbox); + return node; + } - // split on node overflow; propagate upwards if necessary - while (level >= 0) { - if (insertPath[level].children.length > this._maxEntries) { + _insert(item, level, isNode) { + const bbox = isNode ? item : this.toBBox(item); + const insertPath = []; + + // find the best node for accommodating the item, saving all nodes along the path too + const node = this._chooseSubtree( + bbox, + this.data, + level, + insertPath, + ); + + // put the item into the node + node.children.push(item); + extend(node, bbox); + + // split on node overflow; propagate upwards if necessary + while (level >= 0) { + if (insertPath[level].children.length > this._maxEntries) { this._split(insertPath, level); level--; - } else break; - } + } else break; + } - // adjust bboxes along the insertion path - this._adjustParentBBoxes(bbox, insertPath, level); - } + // adjust bboxes along the insertion path + this._adjustParentBBoxes(bbox, insertPath, level); + } - // split overflowed node into two - _split(insertPath, level) { - const node = insertPath[level]; - const M = node.children.length; - const m = this._minEntries; + // split overflowed node into two + _split(insertPath, level) { + const node = insertPath[level]; + const M = node.children.length; + const m = this._minEntries; - this._chooseSplitAxis(node, m, M); + this._chooseSplitAxis(node, m, M); - const splitIndex = this._chooseSplitIndex(node, m, M); + const splitIndex = this._chooseSplitIndex(node, m, M); - const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); - newNode.height = node.height; - newNode.leaf = node.leaf; + const newNode = createNode( + node.children.splice( + splitIndex, + node.children.length - splitIndex, + ), + ); + newNode.height = node.height; + newNode.leaf = node.leaf; - calcBBox(node, this.toBBox); - calcBBox(newNode, this.toBBox); + calcBBox(node, this.toBBox); + calcBBox(newNode, this.toBBox); - if (level) insertPath[level - 1].children.push(newNode); - else this._splitRoot(node, newNode); - } + if (level) insertPath[level - 1].children.push(newNode); + else this._splitRoot(node, newNode); + } - _splitRoot(node, newNode) { - // split root node - this.data = createNode([node, newNode]); - this.data.height = node.height + 1; - this.data.leaf = false; - calcBBox(this.data, this.toBBox); - } + _splitRoot(node, newNode) { + // split root node + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; + calcBBox(this.data, this.toBBox); + } - _chooseSplitIndex(node, m, M) { - let index; - let minOverlap = Infinity; - let minArea = Infinity; + _chooseSplitIndex(node, m, M) { + let index; + let minOverlap = Infinity; + let minArea = Infinity; - for (let i = m; i <= M - m; i++) { - const bbox1 = distBBox(node, 0, i, this.toBBox); - const bbox2 = distBBox(node, i, M, this.toBBox); + for (let i = m; i <= M - m; i++) { + const bbox1 = distBBox(node, 0, i, this.toBBox); + const bbox2 = distBBox(node, i, M, this.toBBox); - const overlap = intersectionArea(bbox1, bbox2); - const area = bboxArea(bbox1) + bboxArea(bbox2); + const overlap = intersectionArea(bbox1, bbox2); + const area = bboxArea(bbox1) + bboxArea(bbox2); - // choose distribution with minimum overlap - if (overlap < minOverlap) { + // choose distribution with minimum overlap + if (overlap < minOverlap) { minOverlap = overlap; index = i; minArea = area < minArea ? area : minArea; - - } else if (overlap === minOverlap) { + } else if (overlap === minOverlap) { // otherwise choose distribution with minimum area if (area < minArea) { - minArea = area; - index = i; + minArea = area; + index = i; } + } } - } - return index || M - m; - } + return index || M - m; + } - // sorts node children by the best axis for split - _chooseSplitAxis(node, m, M) { - const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX; - const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY; - const xMargin = this._allDistMargin(node, m, M, compareMinX); - const yMargin = this._allDistMargin(node, m, M, compareMinY); + // sorts node children by the best axis for split + _chooseSplitAxis(node, m, M) { + const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX; + const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY; + const xMargin = this._allDistMargin(node, m, M, compareMinX); + const yMargin = this._allDistMargin(node, m, M, compareMinY); - // if total distributions margin value is minimal for x, sort by minX, - // otherwise it's already sorted by minY - if (xMargin < yMargin) node.children.sort(compareMinX); - } + // if total distributions margin value is minimal for x, sort by minX, + // otherwise it's already sorted by minY + if (xMargin < yMargin) node.children.sort(compareMinX); + } - // total margin of all possible split distributions where each node is at least m full - _allDistMargin(node, m, M, compare) { - node.children.sort(compare); + // total margin of all possible split distributions where each node is at least m full + _allDistMargin(node, m, M, compare) { + node.children.sort(compare); - const toBBox = this.toBBox; - const leftBBox = distBBox(node, 0, m, toBBox); - const rightBBox = distBBox(node, M - m, M, toBBox); - let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); + const toBBox = this.toBBox; + const leftBBox = distBBox(node, 0, m, toBBox); + const rightBBox = distBBox(node, M - m, M, toBBox); + let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); - for (let i = m; i < M - m; i++) { - const child = node.children[i]; - extend(leftBBox, node.leaf ? toBBox(child) : child); - margin += bboxMargin(leftBBox); + for (let i = m; i < M - m; i++) { + const child = node.children[i]; + extend(leftBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(leftBBox); + } + + for (let i = M - m - 1; i >= m; i--) { + const child = node.children[i]; + extend(rightBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(rightBBox); + } + + return margin; + } + + _adjustParentBBoxes(bbox, path, level) { + // adjust bboxes along the given tree path + for (let i = level; i >= 0; i--) { + extend(path[i], bbox); + } + } + + _condense(path) { + // go through the path, removing empty nodes and updating bboxes + for (let i = path.length - 1, siblings; i >= 0; i--) { + if (path[i].children.length === 0) { + if (i > 0) { + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); + } else this.clear(); + } else calcBBox(path[i], this.toBBox); + } + } + } + + function findItem(item, items, equalsFn) { + if (!equalsFn) return items.indexOf(item); + + for (let i = 0; i < items.length; i++) { + if (equalsFn(item, items[i])) return i; + } + return -1; + } + + // calculate node's bbox from bboxes of its children + function calcBBox(node, toBBox) { + distBBox(node, 0, node.children.length, toBBox, node); } - for (let i = M - m - 1; i >= m; i--) { + // min bounding rectangle of node children from k to p-1 + function distBBox(node, k, p, toBBox, destNode) { + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; + + for (let i = k; i < p; i++) { const child = node.children[i]; - extend(rightBBox, node.leaf ? toBBox(child) : child); - margin += bboxMargin(rightBBox); + extend(destNode, node.leaf ? toBBox(child) : child); + } + + return destNode; } - return margin; - } + function extend(a, b) { + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); + return a; + } - _adjustParentBBoxes(bbox, path, level) { - // adjust bboxes along the given tree path - for (let i = level; i >= 0; i--) { - extend(path[i], bbox); + function compareNodeMinX(a, b) { + return a.minX - b.minX; + } + function compareNodeMinY(a, b) { + return a.minY - b.minY; } - } - _condense(path) { - // go through the path, removing empty nodes and updating bboxes - for (let i = path.length - 1, siblings; i >= 0; i--) { - if (path[i].children.length === 0) { - if (i > 0) { - siblings = path[i - 1].children; - siblings.splice(siblings.indexOf(path[i]), 1); + function bboxArea(a) { + return (a.maxX - a.minX) * (a.maxY - a.minY); + } + function bboxMargin(a) { + return a.maxX - a.minX + (a.maxY - a.minY); + } - } else this.clear(); + function enlargedArea(a, b) { + return ( + (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)) + ); + } - } else calcBBox(path[i], this.toBBox); + function intersectionArea(a, b) { + const minX = Math.max(a.minX, b.minX); + const minY = Math.max(a.minY, b.minY); + const maxX = Math.min(a.maxX, b.maxX); + const maxY = Math.min(a.maxY, b.maxY); + + return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); } - } -} -function findItem(item, items, equalsFn) { - if (!equalsFn) return items.indexOf(item); + function contains(a, b) { + return ( + a.minX <= b.minX && + a.minY <= b.minY && + b.maxX <= a.maxX && + b.maxY <= a.maxY + ); + } - for (let i = 0; i < items.length; i++) { - if (equalsFn(item, items[i])) return i; - } - return -1; -} - -// calculate node's bbox from bboxes of its children -function calcBBox(node, toBBox) { - distBBox(node, 0, node.children.length, toBBox, node); -} - -// min bounding rectangle of node children from k to p-1 -function distBBox(node, k, p, toBBox, destNode) { - if (!destNode) destNode = createNode(null); - destNode.minX = Infinity; - destNode.minY = Infinity; - destNode.maxX = -Infinity; - destNode.maxY = -Infinity; - - for (let i = k; i < p; i++) { - const child = node.children[i]; - extend(destNode, node.leaf ? toBBox(child) : child); - } + function intersects(a, b) { + return ( + b.minX <= a.maxX && + b.minY <= a.maxY && + b.maxX >= a.minX && + b.maxY >= a.minY + ); + } - return destNode; -} - -function extend(a, b) { - a.minX = Math.min(a.minX, b.minX); - a.minY = Math.min(a.minY, b.minY); - a.maxX = Math.max(a.maxX, b.maxX); - a.maxY = Math.max(a.maxY, b.maxY); - return a; -} - -function compareNodeMinX(a, b) { return a.minX - b.minX; } -function compareNodeMinY(a, b) { return a.minY - b.minY; } - -function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } -function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } - -function enlargedArea(a, b) { - return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * - (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); -} - -function intersectionArea(a, b) { - const minX = Math.max(a.minX, b.minX); - const minY = Math.max(a.minY, b.minY); - const maxX = Math.min(a.maxX, b.maxX); - const maxY = Math.min(a.maxY, b.maxY); - - return Math.max(0, maxX - minX) * - Math.max(0, maxY - minY); -} - -function contains(a, b) { - return a.minX <= b.minX && - a.minY <= b.minY && - b.maxX <= a.maxX && - b.maxY <= a.maxY; -} - -function intersects(a, b) { - return b.minX <= a.maxX && - b.minY <= a.maxY && - b.maxX >= a.minX && - b.maxY >= a.minY; -} - -function createNode(children) { - return { - children, - height: 1, - leaf: true, - minX: Infinity, - minY: Infinity, - maxX: -Infinity, - maxY: -Infinity - }; -} + function createNode(children) { + return { + children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity, + }; + } -// sort an array so that items come in groups of n unsorted items, with groups sorted between each other; -// combines selection algorithm with binary divide & conquer approach + // sort an array so that items come in groups of n unsorted items, with groups sorted between each other; + // combines selection algorithm with binary divide & conquer approach -function multiSelect(arr, left, right, n, compare) { - const stack = [left, right]; + function multiSelect(arr, left, right, n, compare) { + const stack = [left, right]; - while (stack.length) { - right = stack.pop(); - left = stack.pop(); + while (stack.length) { + right = stack.pop(); + left = stack.pop(); - if (right - left <= n) continue; + if (right - left <= n) continue; - const mid = left + Math.ceil((right - left) / n / 2) * n; - (0,_quickselect__WEBPACK_IMPORTED_MODULE_0__["default"])(arr, mid, left, right, compare); + const mid = left + Math.ceil((right - left) / n / 2) * n; + (0, _quickselect__WEBPACK_IMPORTED_MODULE_0__["default"])( + arr, + mid, + left, + right, + compare, + ); - stack.push(left, mid, mid, right); - } -} - - -/***/ }) - -/******/ }); -/************************************************************************/ -/******/ // The module cache -/******/ var __webpack_module_cache__ = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ // Check if module is in cache -/******/ var cachedModule = __webpack_module_cache__[moduleId]; -/******/ if (cachedModule !== undefined) { -/******/ return cachedModule.exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = __webpack_module_cache__[moduleId] = { -/******/ // no module.id needed -/******/ // no module.loaded needed -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/************************************************************************/ -/******/ /* webpack/runtime/define property getters */ -/******/ (() => { -/******/ // define getter functions for harmony exports -/******/ __webpack_require__.d = (exports, definition) => { -/******/ for(var key in definition) { -/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { -/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); -/******/ } -/******/ } -/******/ }; -/******/ })(); -/******/ -/******/ /* webpack/runtime/hasOwnProperty shorthand */ -/******/ (() => { -/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) -/******/ })(); -/******/ -/******/ /* webpack/runtime/make namespace object */ -/******/ (() => { -/******/ // define __esModule on exports -/******/ __webpack_require__.r = (exports) => { -/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { -/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); -/******/ } -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ })(); -/******/ -/************************************************************************/ -var __webpack_exports__ = {}; -/*!***************************!*\ + stack.push(left, mid, mid, right); + } + } + + /***/ + }, + + /******/ + }; + /************************************************************************/ + /******/ // The module cache + /******/ var __webpack_module_cache__ = {}; + /******/ + /******/ // The require function + /******/ function __webpack_require__(moduleId) { + /******/ // Check if module is in cache + /******/ var cachedModule = __webpack_module_cache__[moduleId]; + /******/ if (cachedModule !== undefined) { + /******/ return cachedModule.exports; + /******/ + } + /******/ // Create a new module (and put it into the cache) + /******/ var module = (__webpack_module_cache__[moduleId] = { + /******/ // no module.id needed + /******/ // no module.loaded needed + /******/ exports: {}, + /******/ + }); + /******/ + /******/ // Execute the module function + /******/ __webpack_modules__[moduleId].call( + module.exports, + module, + module.exports, + __webpack_require__, + ); + /******/ + /******/ // Return the exports of the module + /******/ return module.exports; + /******/ + } + /******/ + /************************************************************************/ + /******/ /* webpack/runtime/define property getters */ + /******/ (() => { + /******/ // define getter functions for harmony exports + /******/ __webpack_require__.d = (exports, definition) => { + /******/ for (var key in definition) { + /******/ if ( + __webpack_require__.o(definition, key) && + !__webpack_require__.o(exports, key) + ) { + /******/ Object.defineProperty(exports, key, { + enumerable: true, + get: definition[key], + }); + /******/ + } + /******/ + } + /******/ + }; + /******/ + })(); + /******/ + /******/ /* webpack/runtime/hasOwnProperty shorthand */ + /******/ (() => { + /******/ __webpack_require__.o = (obj, prop) => + Object.prototype.hasOwnProperty.call(obj, prop); + /******/ + })(); + /******/ + /******/ /* webpack/runtime/make namespace object */ + /******/ (() => { + /******/ // define __esModule on exports + /******/ __webpack_require__.r = (exports) => { + /******/ if (typeof Symbol !== "undefined" && Symbol.toStringTag) { + /******/ Object.defineProperty(exports, Symbol.toStringTag, { + value: "Module", + }); + /******/ + } + /******/ Object.defineProperty(exports, "__esModule", { value: true }); + /******/ + }; + /******/ + })(); + /******/ + /************************************************************************/ + var __webpack_exports__ = {}; + /*!***************************!*\ !*** ./src/demo/index.js ***! \***************************/ -const { TestCanvas } = __webpack_require__(/*! ./canvas */ "./src/demo/canvas.js"); + const { TestCanvas } = __webpack_require__( + /*! ./canvas */ "./src/demo/canvas.js", + ); -const isStressTest = window.location.search.indexOf("?stress") !== -1; -const Test = isStressTest ? __webpack_require__(/*! ./stress */ "./src/demo/stress.js") : __webpack_require__(/*! ./tank */ "./src/demo/tank.js"); + const isStressTest = window.location.search.indexOf("?stress") !== -1; + const Test = isStressTest + ? __webpack_require__(/*! ./stress */ "./src/demo/stress.js") + : __webpack_require__(/*! ./tank */ "./src/demo/tank.js"); -const test = new Test(); -const canvas = new TestCanvas(test); + const test = new Test(); + const canvas = new TestCanvas(test); -document.body.appendChild(canvas.element); + document.body.appendChild(canvas.element); -if (test.start) { - test.start(); -} + if (test.start) { + test.start(); + } -/******/ })() -; \ No newline at end of file + /******/ +})(); diff --git a/docs/demo/index.html b/docs/demo/index.html index 26b2181b..ab476592 100644 --- a/docs/demo/index.html +++ b/docs/demo/index.html @@ -50,5 +50,7 @@ - + + + diff --git a/docs/demo/stress.d.ts b/docs/demo/stress.d.ts index ab4a022e..8aba18da 100644 --- a/docs/demo/stress.d.ts +++ b/docs/demo/stress.d.ts @@ -1,27 +1,27 @@ export = Stress; declare class Stress { - constructor(count?: number); - size: number; - physics: System; - bodies: any[]; - polygons: number; - boxes: number; - circles: number; - ellipses: number; - lines: number; - lastVariant: number; - count: number; - bounds: import("..").Box[]; - enableFiltering: boolean; - legend: string; - lastTime: number; - updateBody(body: any): void; - start: () => void; - getBounds(): import("..").Box[]; - toggleFiltering(): void; - update(): void; - timeScale: number | undefined; - bounceBody(body: any): void; - createShape(large: any): void; + constructor(count?: number); + size: number; + physics: System; + bodies: any[]; + polygons: number; + boxes: number; + circles: number; + ellipses: number; + lines: number; + lastVariant: number; + count: number; + bounds: import("..").Box[]; + enableFiltering: boolean; + legend: string; + lastTime: number; + updateBody(body: any): void; + start: () => void; + getBounds(): import("..").Box[]; + toggleFiltering(): void; + update(): void; + timeScale: number | undefined; + bounceBody(body: any): void; + createShape(large: any): void; } import { System } from "../system"; diff --git a/docs/demo/stress.js b/docs/demo/stress.js index 6b4a20e2..77e1c32d 100644 --- a/docs/demo/stress.js +++ b/docs/demo/stress.js @@ -5,26 +5,29 @@ const { getBounceDirection, groupBits } = require("../utils"); const { width, height, loop } = require("./canvas"); const seededRandom = require("random-seed").create("@Prozi").random; function random(min, max) { - return Math.floor(seededRandom() * max) + min; + return Math.floor(seededRandom() * max) + min; +} +function getDefaultCount() { + return Math.floor(Math.min(2000, Math.hypot(width, height))); } class Stress { - constructor(count = 2000) { - this.size = Math.sqrt((width * height) / (count * 50)); - this.physics = new System(5); - this.bodies = []; - this.polygons = 0; - this.boxes = 0; - this.circles = 0; - this.ellipses = 0; - this.lines = 0; - this.lastVariant = 0; - this.count = count; - this.bounds = this.getBounds(); - this.enableFiltering = false; - for (let i = 0; i < count; ++i) { - this.createShape(!random(0, 20)); - } - this.legend = `
Total: ${count}
+ constructor(count = getDefaultCount()) { + this.size = Math.sqrt((width * height) / (count * 50)); + this.physics = new System(5); + this.bodies = []; + this.polygons = 0; + this.boxes = 0; + this.circles = 0; + this.ellipses = 0; + this.lines = 0; + this.lastVariant = 0; + this.count = count; + this.bounds = this.getBounds(); + this.enableFiltering = false; + for (let i = 0; i < count; ++i) { + this.createShape(!random(0, 20)); + } + this.legend = `
Total: ${count}
Polygons: ${this.polygons}
Boxes: ${this.boxes}
Circles: ${this.circles}
@@ -36,178 +39,200 @@ class Stress { `; - this.lastTime = Date.now(); - this.updateBody = this.updateBody.bind(this); - // observer #debug & add filtering checkbox event - const observer = new window.MutationObserver((mutations) => { - mutations.forEach((mutation) => { - mutation.addedNodes.forEach((node) => { - if (node.id == "debug") { - document - .querySelector("#filtering") - .addEventListener("change", () => this.toggleFiltering()); - observer.disconnect(); - } - }); - }); - }); - observer.observe(document.querySelector("body"), { - subtree: false, - childList: true, + this.lastTime = Date.now(); + this.updateBody = this.updateBody.bind(this); + // observer #debug & add filtering checkbox event + const observer = new window.MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.id == "debug") { + document + .querySelector("#filtering") + .addEventListener("change", () => this.toggleFiltering()); + observer.disconnect(); + } }); - this.start = () => { - loop(this.update.bind(this)); - }; + }); + }); + observer.observe(document.querySelector("body"), { + subtree: false, + childList: true, + }); + this.start = () => { + loop(this.update.bind(this)); + }; + } + getBounds() { + return [ + this.physics.createBox({ x: 0, y: 0 }, width, 10, { + isStatic: true, + }), + this.physics.createBox({ x: width - 10, y: 0 }, 10, height, { + isStatic: true, + }), + this.physics.createBox({ x: 0, y: height - 10 }, width, 10, { + isStatic: true, + }), + this.physics.createBox({ x: 0, y: 0 }, 10, height, { + isStatic: true, + }), + ]; + } + toggleFiltering() { + this.enableFiltering = !this.enableFiltering; + this.physics.clear(); + this.bodies.length = 0; + this.polygons = 0; + this.boxes = 0; + this.circles = 0; + this.ellipses = 0; + this.lines = 0; + this.lastVariant = 0; + this.bounds = this.getBounds(); + for (let i = 0; i < this.count; ++i) { + this.createShape(!random(0, 20)); } - getBounds() { - return [ - this.physics.createBox({ x: 0, y: 0 }, width, 10, { - isStatic: true, - }), - this.physics.createBox({ x: width - 10, y: 0 }, 10, height, { - isStatic: true, - }), - this.physics.createBox({ x: 0, y: height - 10 }, width, 10, { - isStatic: true, - }), - this.physics.createBox({ x: 0, y: 0 }, 10, height, { - isStatic: true, - }), - ]; + } + update() { + const now = Date.now(); + this.timeScale = Math.min(1000, now - this.lastTime) / 60; + this.lastTime = now; + this.bodies.forEach(this.updateBody); + } + updateBody(body) { + body.setAngle(body.angle + body.rotationSpeed * this.timeScale, false); + if (seededRandom() < 0.05 * this.timeScale) { + body.targetScale.x = 0.5 + seededRandom(); } - toggleFiltering() { - this.enableFiltering = !this.enableFiltering; - this.physics.clear(); - this.bodies.length = 0; - this.polygons = 0; - this.boxes = 0; - this.circles = 0; - this.ellipses = 0; - this.lines = 0; - this.lastVariant = 0; - this.bounds = this.getBounds(); - for (let i = 0; i < this.count; ++i) { - this.createShape(!random(0, 20)); - } + if (seededRandom() < 0.05 * this.timeScale) { + body.targetScale.y = 0.5 + seededRandom(); + } + if (Math.abs(body.targetScale.x - body.scaleX) > 0.01) { + const scaleX = + body.scaleX + + Math.sign(body.targetScale.x - body.scaleX) * 0.02 * this.timeScale; + const scaleY = + body.scaleY + + Math.sign(body.targetScale.y - body.scaleY) * 0.02 * this.timeScale; + body.setScale(scaleX, scaleY, false); } - update() { - const now = Date.now(); - this.timeScale = Math.min(1000, now - this.lastTime) / 60; - this.lastTime = now; - this.bodies.forEach(this.updateBody); + // as last step update position, and bounding box + body.setPosition( + body.x + body.directionX * this.timeScale, + body.y + body.directionY * this.timeScale, + ); + // separate + bounce + this.bounceBody(body); + } + bounceBody(body) { + const bounces = { x: 0, y: 0 }; + const addBounces = ({ overlapV: { x, y } }) => { + bounces.x += x; + bounces.y += y; + }; + this.physics.checkOne(body, addBounces); + if (bounces.x || bounces.y) { + const size = 0.5 * (body.scaleX + body.scaleY); + const bounce = getBounceDirection(body, { + x: body.x + bounces.x, + y: body.y + bounces.y, + }); + bounce.scale(body.size).add({ + x: body.directionX * size, + y: body.directionY * size, + }); + const { x, y } = bounce.normalize(); + body.directionX = x; + body.directionY = y; + body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; + body.setPosition(body.x - bounces.x, body.y - bounces.y); } - updateBody(body) { - body.setAngle(body.angle + body.rotationSpeed * this.timeScale, false); - if (seededRandom() < 0.05 * this.timeScale) { - body.targetScale.x = 0.5 + seededRandom(); + } + createShape(large) { + const minSize = this.size * 1.0 * (large ? seededRandom() + 1 : 1); + const maxSize = this.size * 1.25 * (large ? seededRandom() * 2 + 1 : 1); + const x = random(0, width); + const y = random(0, height); + const direction = (random(0, 360) * Math.PI) / 180; + const options = { + isCentered: true, + padding: (minSize + maxSize) * 0.2, + }; + let body; + let variant = this.lastVariant++ % 5; + switch (variant) { + case 0: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Circle); } - if (seededRandom() < 0.05 * this.timeScale) { - body.targetScale.y = 0.5 + seededRandom(); + body = this.physics.createCircle( + { x, y }, + random(minSize, maxSize) / 2, + options, + ); + ++this.circles; + break; + case 1: + const width = random(minSize, maxSize); + const height = random(minSize, maxSize); + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Ellipse); + console.log(); } - if (Math.abs(body.targetScale.x - body.scaleX) > 0.01) { - const scaleX = body.scaleX + - Math.sign(body.targetScale.x - body.scaleX) * 0.02 * this.timeScale; - const scaleY = body.scaleY + - Math.sign(body.targetScale.y - body.scaleY) * 0.02 * this.timeScale; - body.setScale(scaleX, scaleY, false); + body = this.physics.createEllipse({ x, y }, width, height, 2, options); + ++this.ellipses; + break; + case 2: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Box); } - // as last step update position, and bounding box - body.setPosition(body.x + body.directionX * this.timeScale, body.y + body.directionY * this.timeScale); - // separate + bounce - this.bounceBody(body); - } - bounceBody(body) { - const bounces = { x: 0, y: 0 }; - const addBounces = ({ overlapV: { x, y } }) => { - bounces.x += x; - bounces.y += y; - }; - this.physics.checkOne(body, addBounces); - if (bounces.x || bounces.y) { - const size = 0.5 * (body.scaleX + body.scaleY); - const bounce = getBounceDirection(body, { - x: body.x + bounces.x, - y: body.y + bounces.y, - }); - bounce.scale(body.size).add({ - x: body.directionX * size, - y: body.directionY * size, - }); - const { x, y } = bounce.normalize(); - body.directionX = x; - body.directionY = y; - body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; - body.setPosition(body.x - bounces.x, body.y - bounces.y); + body = this.physics.createBox( + { x, y }, + random(minSize, maxSize), + random(minSize, maxSize), + options, + ); + ++this.boxes; + break; + case 3: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Line); } - } - createShape(large) { - const minSize = this.size * 1.0 * (large ? seededRandom() + 1 : 1); - const maxSize = this.size * 1.25 * (large ? seededRandom() * 2 + 1 : 1); - const x = random(0, width); - const y = random(0, height); - const direction = (random(0, 360) * Math.PI) / 180; - const options = { - isCentered: true, - padding: (minSize + maxSize) * 0.2, - }; - let body; - let variant = this.lastVariant++ % 5; - switch (variant) { - case 0: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Circle); - } - body = this.physics.createCircle({ x, y }, random(minSize, maxSize) / 2, options); - ++this.circles; - break; - case 1: - const width = random(minSize, maxSize); - const height = random(minSize, maxSize); - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Ellipse); - console.log(); - } - body = this.physics.createEllipse({ x, y }, width, height, 2, options); - ++this.ellipses; - break; - case 2: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Box); - } - body = this.physics.createBox({ x, y }, random(minSize, maxSize), random(minSize, maxSize), options); - ++this.boxes; - break; - case 3: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Line); - } - body = this.physics.createLine({ x, y }, { - x: x + random(minSize, maxSize), - y: y + random(minSize, maxSize), - }, options); - ++this.lines; - break; - default: - if (this.enableFiltering) { - options.group = groupBits(BodyGroup.Polygon); - } - body = this.physics.createPolygon({ x, y }, [ - { x: -random(minSize, maxSize), y: random(minSize, maxSize) }, - { x: random(minSize, maxSize), y: random(minSize, maxSize) }, - { x: random(minSize, maxSize), y: -random(minSize, maxSize) }, - { x: -random(minSize, maxSize), y: -random(minSize, maxSize) }, - ], options); - ++this.polygons; - break; + body = this.physics.createLine( + { x, y }, + { + x: x + random(minSize, maxSize), + y: y + random(minSize, maxSize), + }, + options, + ); + ++this.lines; + break; + default: + if (this.enableFiltering) { + options.group = groupBits(BodyGroup.Polygon); } - // set initial rotation angle direction - body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; - body.setAngle((random(0, 360) * Math.PI) / 180); - body.targetScale = { x: 1, y: 1 }; - body.size = (minSize + maxSize) / 2; - body.directionX = Math.cos(direction); - body.directionY = Math.sin(direction); - this.bodies.push(body); + body = this.physics.createPolygon( + { x, y }, + [ + { x: -random(minSize, maxSize), y: random(minSize, maxSize) }, + { x: random(minSize, maxSize), y: random(minSize, maxSize) }, + { x: random(minSize, maxSize), y: -random(minSize, maxSize) }, + { x: -random(minSize, maxSize), y: -random(minSize, maxSize) }, + ], + options, + ); + ++this.polygons; + break; } + // set initial rotation angle direction + body.rotationSpeed = (seededRandom() - seededRandom()) * 0.1; + body.setAngle((random(0, 360) * Math.PI) / 180); + body.targetScale = { x: 1, y: 1 }; + body.size = (minSize + maxSize) / 2; + body.directionX = Math.cos(direction); + body.directionY = Math.sin(direction); + this.bodies.push(body); + } } module.exports = Stress; diff --git a/docs/enums/BodyGroup.html b/docs/enums/BodyGroup.html index f7bd620a..7bfd44f4 100644 --- a/docs/enums/BodyGroup.html +++ b/docs/enums/BodyGroup.html @@ -1,8 +1,519 @@ -BodyGroup | Detect-Collisions

Enumeration BodyGroup

for groups

-

Enumeration Members

Enumeration Members

Box: 4
Circle: 16
Ellipse: 32
Line: 2
Point: 1
Polygon: 8
+ + + + + + BodyGroup | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Enumeration BodyGroup

+
+
+

for groups

+
+
+ +
+
+
+ + + +
+
+

Enumeration Members

+ +
+
+
+
+
+
+ +

+ + + + Enumeration Members +

+
+
+
+ + +
+ Box: + 4 +
+ +
+
+ + +
+ Circle: + 16 +
+ +
+
+ + +
+ Ellipse: + 32 +
+ +
+
+ + +
+ Line: + 2 +
+ +
+
+ + +
+ Point: + 1 +
+ +
+
+ + +
+ Polygon: + 8 +
+ +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/enums/BodyType.html b/docs/enums/BodyType.html index f7dc6464..7c5556ce 100644 --- a/docs/enums/BodyType.html +++ b/docs/enums/BodyType.html @@ -1,8 +1,519 @@ -BodyType | Detect-Collisions

Enumeration BodyType

types

-

Enumeration Members

Enumeration Members

Box: "Box"
Circle: "Circle"
Ellipse: "Ellipse"
Line: "Line"
Point: "Point"
Polygon: "Polygon"
+ + + + + + BodyType | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Enumeration BodyType

+
+
+

types

+
+
+ +
+
+
+ + + +
+
+

Enumeration Members

+ +
+
+
+
+
+
+ +

+ + + + Enumeration Members +

+
+
+
+ + +
+ Box: + "Box" +
+ +
+
+ + +
+ Circle: + "Circle" +
+ +
+
+ + +
+ Ellipse: + "Ellipse" +
+ +
+
+ + +
+ Line: + "Line" +
+ +
+
+ + +
+ Point: + "Point" +
+ +
+
+ + +
+ Polygon: + "Polygon" +
+ +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/bin2dec.html b/docs/functions/bin2dec.html index d2a9545b..27f5fcfc 100644 --- a/docs/functions/bin2dec.html +++ b/docs/functions/bin2dec.html @@ -1,2 +1,268 @@ -bin2dec | Detect-Collisions

Function bin2dec

  • binary string to decimal number

    -

    Parameters

    • binary: string

    Returns number

+ + + + + + bin2dec | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function bin2dec

+
+
+
    + +
  • +
    +

    binary string to decimal number

    +
    +
    +

    Parameters

    +
      +
    • + binary: + string +
    • +
    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/bodyMoved.html b/docs/functions/bodyMoved.html index 3152a084..a3fcfe7c 100644 --- a/docs/functions/bodyMoved.html +++ b/docs/functions/bodyMoved.html @@ -1,2 +1,275 @@ -bodyMoved | Detect-Collisions

Function bodyMoved

  • check if body moved outside of its padding

    -

    Parameters

    Returns boolean

+ + + + + + bodyMoved | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function bodyMoved

+
+
+
    + +
  • +
    +

    check if body moved outside of its padding

    +
    +
    +

    Parameters

    +
      +
    • + body: + Body +
    • +
    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/canInteract.html b/docs/functions/canInteract.html index 9a36cb72..0a5952e4 100644 --- a/docs/functions/canInteract.html +++ b/docs/functions/canInteract.html @@ -1,2 +1,288 @@ -canInteract | Detect-Collisions

Function canInteract

  • checks if two bodies can interact (for collision filtering)

    -

    Parameters

    Returns boolean

+ + + + + + canInteract | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function canInteract

+
+
+
    + +
  • +
    +

    + checks if two bodies can interact (for collision filtering) +

    +
    +
    +

    Parameters

    +
      +
    • + bodyA: + Body +
    • +
    • + bodyB: + Body +
    • +
    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/checkAInB.html b/docs/functions/checkAInB.html index f292ac27..4f53e2cf 100644 --- a/docs/functions/checkAInB.html +++ b/docs/functions/checkAInB.html @@ -1,2 +1,286 @@ -checkAInB | Detect-Collisions

Function checkAInB

  • checks if body a is in body b

    -

    Parameters

    Returns boolean

+ + + + + + checkAInB | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function checkAInB

+
+
+
    + +
  • +
    +

    checks if body a is in body b

    +
    +
    +

    Parameters

    +
      +
    • + bodyA: + Body +
    • +
    • + bodyB: + Body +
    • +
    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/circleInCircle.html b/docs/functions/circleInCircle.html index d2812d77..b1438e7a 100644 --- a/docs/functions/circleInCircle.html +++ b/docs/functions/circleInCircle.html @@ -1,2 +1,303 @@ -circleInCircle | Detect-Collisions

Function circleInCircle

+ + + + + + circleInCircle | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function circleInCircle

+
+
+ +
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/circleInPolygon.html b/docs/functions/circleInPolygon.html index 3c2bb0cd..6cddadae 100644 --- a/docs/functions/circleInPolygon.html +++ b/docs/functions/circleInPolygon.html @@ -1,2 +1,297 @@ -circleInPolygon | Detect-Collisions

Function circleInPolygon

+ + + + + + circleInPolygon | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function circleInPolygon

+
+
+ +
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/circleOutsidePolygon.html b/docs/functions/circleOutsidePolygon.html index 5d50b1f9..2c2302c5 100644 --- a/docs/functions/circleOutsidePolygon.html +++ b/docs/functions/circleOutsidePolygon.html @@ -1,2 +1,299 @@ -circleOutsidePolygon | Detect-Collisions

Function circleOutsidePolygon

+ + + + + + circleOutsidePolygon | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function circleOutsidePolygon

+
+
+ +
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/clockwise.html b/docs/functions/clockwise.html index cf137289..d447111f 100644 --- a/docs/functions/clockwise.html +++ b/docs/functions/clockwise.html @@ -1,2 +1,275 @@ -clockwise | Detect-Collisions

Function clockwise

  • check [is clockwise] direction of polygon

    -

    Parameters

    Returns boolean

+ + + + + + clockwise | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function clockwise

+
+
+
    + +
  • +
    +

    check [is clockwise] direction of polygon

    +
    +
    +

    Parameters

    + +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/clonePointsArray.html b/docs/functions/clonePointsArray.html index fbc81330..1ca1f586 100644 --- a/docs/functions/clonePointsArray.html +++ b/docs/functions/clonePointsArray.html @@ -1,2 +1,285 @@ -clonePointsArray | Detect-Collisions

Function clonePointsArray

+ + + + + + clonePointsArray | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function clonePointsArray

+
+
+
    + +
  • +
    +

    clone sat vector points array into vector points array

    +
    +
    +

    Parameters

    + +
    +

    + Returns + Vector[] +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/cloneResponse.html b/docs/functions/cloneResponse.html index 7b5ac345..11f5daa0 100644 --- a/docs/functions/cloneResponse.html +++ b/docs/functions/cloneResponse.html @@ -1,2 +1,286 @@ -cloneResponse | Detect-Collisions

Function cloneResponse

+ + + + + + cloneResponse | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function cloneResponse

+
+
+
    + +
  • +
    +

    + clone response object returning new response with previous + ones values +

    +
    +
    +

    Parameters

    + +
    +

    + Returns + Response +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/createBox.html b/docs/functions/createBox.html index 3eedb8e2..5ab9110b 100644 --- a/docs/functions/createBox.html +++ b/docs/functions/createBox.html @@ -1,2 +1,287 @@ -createBox | Detect-Collisions

Function createBox

+ + + + + + createBox | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function createBox

+
+
+
    + +
  • +
    +

    creates box shaped polygon points

    +
    +
    +

    Parameters

    +
      +
    • + width: + number +
    • +
    • + height: + number +
    • +
    +
    +

    + Returns + SATVector[] +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/createEllipse.html b/docs/functions/createEllipse.html index 5809c327..67e52de8 100644 --- a/docs/functions/createEllipse.html +++ b/docs/functions/createEllipse.html @@ -1,2 +1,300 @@ -createEllipse | Detect-Collisions

Function createEllipse

  • creates ellipse-shaped polygon based on params

    -

    Parameters

    • radiusX: number
    • radiusY: number = radiusX
    • step: number = 1

    Returns SATVector[]

+ + + + + + createEllipse | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function createEllipse

+
+
+
    + +
  • +
    +

    creates ellipse-shaped polygon based on params

    +
    +
    +

    Parameters

    +
      +
    • + radiusX: + number +
    • +
    • + radiusY: + number + = radiusX +
    • +
    • + step: + number = 1 +
    • +
    +
    +

    + Returns + SATVector[] +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/dashLineTo.html b/docs/functions/dashLineTo.html index bcfb5b81..58bf2623 100644 --- a/docs/functions/dashLineTo.html +++ b/docs/functions/dashLineTo.html @@ -1,2 +1,320 @@ -dashLineTo | Detect-Collisions

Function dashLineTo

  • draws dashed line on canvas context

    -

    Parameters

    • context: CanvasRenderingContext2D
    • fromX: number
    • fromY: number
    • toX: number
    • toY: number
    • dash: number = 2
    • gap: number = 4

    Returns void

+ + + + + + dashLineTo | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function dashLineTo

+
+
+
    + +
  • +
    +

    draws dashed line on canvas context

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    • + fromX: + number +
    • +
    • + fromY: + number +
    • +
    • + toX: + number +
    • +
    • + toY: + number +
    • +
    • + dash: + number = 2 +
    • +
    • + gap: + number = 4 +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/deg2rad-1.html b/docs/functions/deg2rad-1.html index e2dedd08..da8b6c48 100644 --- a/docs/functions/deg2rad-1.html +++ b/docs/functions/deg2rad-1.html @@ -1,2 +1,268 @@ -deg2rad | Detect-Collisions

Function deg2rad

  • convert from degrees to radians

    -

    Parameters

    • degrees: number

    Returns number

+ + + + + + deg2rad | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function deg2rad

+
+
+
    + +
  • +
    +

    convert from degrees to radians

    +
    +
    +

    Parameters

    +
      +
    • + degrees: + number +
    • +
    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/distance.html b/docs/functions/distance.html index 991af08d..07d5f037 100644 --- a/docs/functions/distance.html +++ b/docs/functions/distance.html @@ -1,2 +1,286 @@ -distance | Detect-Collisions

Function distance

  • get distance between two Vector points

    -

    Parameters

    Returns number

+ + + + + + distance | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function distance

+
+
+
    + +
  • +
    +

    get distance between two Vector points

    +
    +
    +

    Parameters

    + +
    +

    + Returns number +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/drawBVH.html b/docs/functions/drawBVH.html index 4cc1c6cd..8cec2860 100644 --- a/docs/functions/drawBVH.html +++ b/docs/functions/drawBVH.html @@ -1,2 +1,281 @@ -drawBVH | Detect-Collisions

Function drawBVH

  • draw body bounding body box

    -

    Parameters

    • context: CanvasRenderingContext2D
    • body: Body

    Returns void

+ + + + + + drawBVH | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function drawBVH

+
+
+
    + +
  • +
    +

    draw body bounding body box

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    • + body: + Body +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/drawPolygon.html b/docs/functions/drawPolygon.html index b7740832..bcd03ed4 100644 --- a/docs/functions/drawPolygon.html +++ b/docs/functions/drawPolygon.html @@ -1,2 +1,314 @@ -drawPolygon | Detect-Collisions

Function drawPolygon

  • draw polygon

    -

    Parameters

    • context: CanvasRenderingContext2D
    • __namedParameters: Pick<SATPolygon | Polygon, "calcPoints"> & {
          pos: Vector;
      }
    • isTrigger: boolean = false

    Returns void

+ + + + + + drawPolygon | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function drawPolygon

+
+
+
    + +
  • +

    draw polygon

    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    • + __namedParameters: Pick<SATPolygon | Polygon, "calcPoints"> & {
          pos: Vector;
      }
      +
    • +
    • + isTrigger: + boolean = false +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/ensureConvex.html b/docs/functions/ensureConvex.html index 12203bc2..e4dfe645 100644 --- a/docs/functions/ensureConvex.html +++ b/docs/functions/ensureConvex.html @@ -1,2 +1,336 @@ -ensureConvex | Detect-Collisions

Function ensureConvex

+ + + + + + ensureConvex | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function ensureConvex

+
+
+ +
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/ensureNumber.html b/docs/functions/ensureNumber.html index 02f3293a..e8e7e80a 100644 --- a/docs/functions/ensureNumber.html +++ b/docs/functions/ensureNumber.html @@ -1,3 +1,277 @@ -ensureNumber | Detect-Collisions

Function ensureNumber

  • helper for groupBits()

    -

    Parameters

    • input: string | number

      number or binary string

      -

    Returns number

+ + + + + + ensureNumber | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function ensureNumber

+
+
+
    + +
  • +
    +

    helper for groupBits()

    +
    +
    +

    Parameters

    +
      +
    • + input: + string | number +
      +

      number or binary string

      +
      +
      +
    • +
    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/ensurePolygonPoints.html b/docs/functions/ensurePolygonPoints.html index bc623da4..5cdbddf6 100644 --- a/docs/functions/ensurePolygonPoints.html +++ b/docs/functions/ensurePolygonPoints.html @@ -1,2 +1,289 @@ -ensurePolygonPoints | Detect-Collisions

Function ensurePolygonPoints

+ + + + + + ensurePolygonPoints | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function ensurePolygonPoints

+
+
+
    + +
  • +
    +

    + ensure Vector points (for polygon) in counter-clockwise order +

    +
    +
    +

    Parameters

    + +
    +

    + Returns + SATVector[] +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/ensureVectorPoint.html b/docs/functions/ensureVectorPoint.html index 672edac3..18b47fca 100644 --- a/docs/functions/ensureVectorPoint.html +++ b/docs/functions/ensureVectorPoint.html @@ -1,2 +1,285 @@ -ensureVectorPoint | Detect-Collisions

Function ensureVectorPoint

+ + + + + + ensureVectorPoint | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function ensureVectorPoint

+
+
+ +
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/extendBody.html b/docs/functions/extendBody.html index 63664cd5..1b887339 100644 --- a/docs/functions/extendBody.html +++ b/docs/functions/extendBody.html @@ -1,2 +1,287 @@ -extendBody | Detect-Collisions

Function extendBody

  • used for all types of bodies in constructor

    -

    Parameters

    Returns void

+ + + + + + extendBody | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function extendBody

+
+
+
    + +
  • +
    +

    used for all types of bodies in constructor

    +
    +
    +

    Parameters

    + +
    +

    + Returns void +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/getBounceDirection.html b/docs/functions/getBounceDirection.html index fbc140de..bb3eedb1 100644 --- a/docs/functions/getBounceDirection.html +++ b/docs/functions/getBounceDirection.html @@ -1,2 +1,298 @@ -getBounceDirection | Detect-Collisions

Function getBounceDirection

+ + + + + + getBounceDirection | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function getBounceDirection

+
+
+
    + +
  • +
    +

    + given 2 bodies calculate vector of bounce assuming equal mass + and they are circles +

    +
    +
    +

    Parameters

    + +
    +

    + Returns + SATVector +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/getGroup.html b/docs/functions/getGroup.html index 996a2d5c..31e208d0 100644 --- a/docs/functions/getGroup.html +++ b/docs/functions/getGroup.html @@ -1,2 +1,269 @@ -getGroup | Detect-Collisions

Function getGroup

  • for groups

    -

    Parameters

    • group: number

    Returns number

+ + + + + + getGroup | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function getGroup

+
+
+
    + +
  • +

    for groups

    +
    +

    Parameters

    +
      +
    • + group: + number +
    • +
    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/getSATTest.html b/docs/functions/getSATTest.html index d234c5fc..9a859124 100644 --- a/docs/functions/getSATTest.html +++ b/docs/functions/getSATTest.html @@ -1,2 +1,296 @@ -getSATTest | Detect-Collisions

Function getSATTest

+ + + + + + getSATTest | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function getSATTest

+
+
+
    + +
  • +
    +

    + returns correct sat.js testing function based on body types +

    +
    +
    +

    Parameters

    +
      +
    • + bodyA: + Body +
    • +
    • + bodyB: + Body +
    • +
    +
    +

    + Returns + SATTest +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/groupBits.html b/docs/functions/groupBits.html index ba866ab0..f09e1ff9 100644 --- a/docs/functions/groupBits.html +++ b/docs/functions/groupBits.html @@ -1,4 +1,294 @@ -groupBits | Detect-Collisions

Function groupBits

  • create group bits from category and mask

    -

    Parameters

    • category: string | number

      category bits

      -
    • mask: string | number = category

      mask bits (default: category)

      -

    Returns number

+ + + + + + groupBits | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function groupBits

+
+
+
    + +
  • +
    +

    create group bits from category and mask

    +
    +
    +

    Parameters

    +
      +
    • + category: + string | number +
      +

      category bits

      +
      +
      +
    • +
    • + mask: + string | number + = category +
      +

      mask bits (default: category)

      +
      +
      +
    • +
    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/intersectAABB.html b/docs/functions/intersectAABB.html index 123caf09..7e155ee1 100644 --- a/docs/functions/intersectAABB.html +++ b/docs/functions/intersectAABB.html @@ -1,2 +1,286 @@ -intersectAABB | Detect-Collisions

Function intersectAABB

  • checks if two boxes intersect

    -

    Parameters

    Returns boolean

+ + + + + + intersectAABB | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function intersectAABB

+
+
+
    + +
  • +
    +

    checks if two boxes intersect

    +
    +
    +

    Parameters

    +
      +
    • + bodyA: + BBox +
    • +
    • + bodyB: + BBox +
    • +
    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/intersectLineCircle.html b/docs/functions/intersectLineCircle.html index 04c18045..06acb98f 100644 --- a/docs/functions/intersectLineCircle.html +++ b/docs/functions/intersectLineCircle.html @@ -1,2 +1,312 @@ -intersectLineCircle | Detect-Collisions

Function intersectLineCircle

+ + + + + + intersectLineCircle | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function intersectLineCircle

+
+
+ +
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/intersectLineLine.html b/docs/functions/intersectLineLine.html index ef484e6b..4b882de9 100644 --- a/docs/functions/intersectLineLine.html +++ b/docs/functions/intersectLineLine.html @@ -1,3 +1,315 @@ -intersectLineLine | Detect-Collisions

Function intersectLineLine

+ + + + + + intersectLineLine | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function intersectLineLine

+
+
+ +
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/intersectLineLineFast.html b/docs/functions/intersectLineLineFast.html index a58d5603..c07cf82f 100644 --- a/docs/functions/intersectLineLineFast.html +++ b/docs/functions/intersectLineLineFast.html @@ -1,3 +1,306 @@ -intersectLineLineFast | Detect-Collisions

Function intersectLineLineFast

+ + + + + + intersectLineLineFast | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function intersectLineLineFast

+
+
+ +
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/intersectLinePolygon.html b/docs/functions/intersectLinePolygon.html index 283a0d43..dff94217 100644 --- a/docs/functions/intersectLinePolygon.html +++ b/docs/functions/intersectLinePolygon.html @@ -1 +1,294 @@ -intersectLinePolygon | Detect-Collisions

Function intersectLinePolygon

+ + + + + + intersectLinePolygon | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function intersectLinePolygon

+
+
+ +
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/isSimple.html b/docs/functions/isSimple.html index b9a5b521..be2cc67c 100644 --- a/docs/functions/isSimple.html +++ b/docs/functions/isSimple.html @@ -1 +1,268 @@ -isSimple | Detect-Collisions

Function isSimple

  • Parameters

    Returns boolean

+ + + + + + isSimple | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function isSimple

+
+
+
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns boolean +

    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/mapArrayToVector.html b/docs/functions/mapArrayToVector.html index 4327a62a..ff3fa5e9 100644 --- a/docs/functions/mapArrayToVector.html +++ b/docs/functions/mapArrayToVector.html @@ -1,2 +1,286 @@ -mapArrayToVector | Detect-Collisions

Function mapArrayToVector

+ + + + + + mapArrayToVector | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function mapArrayToVector

+
+
+
    + +
  • +
    +

    change format from poly-decomp to SAT.js

    +
    +
    +

    Parameters

    + +
    +

    + Returns + Vector +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/mapVectorToArray.html b/docs/functions/mapVectorToArray.html index 9b470ccb..2c877cc8 100644 --- a/docs/functions/mapVectorToArray.html +++ b/docs/functions/mapVectorToArray.html @@ -1,2 +1,286 @@ -mapVectorToArray | Detect-Collisions

Function mapVectorToArray

+ + + + + + mapVectorToArray | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function mapVectorToArray

+
+
+
    + +
  • +
    +

    change format from SAT.js to poly-decomp

    +
    +
    +

    Parameters

    +
      +
    • + __namedParameters: + Vector = ... +
    • +
    +
    +

    + Returns + DecompPoint +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/move.html b/docs/functions/move.html index ecb52ccf..c3f79700 100644 --- a/docs/functions/move.html +++ b/docs/functions/move.html @@ -1 +1,286 @@ -move | Detect-Collisions
  • Parameters

    • body: Body
    • speed: number = 1
    • updateNow: boolean = true

    Returns void

+ + + + + + move | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function move

+
+
+
    + +
  • +
    +

    Parameters

    +
      +
    • + body: + Body +
    • +
    • + speed: + number = 1 +
    • +
    • + updateNow: + boolean = true +
    • +
    +
    +

    + Returns void +

    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/notIntersectAABB.html b/docs/functions/notIntersectAABB.html index e5046949..ff52e9c2 100644 --- a/docs/functions/notIntersectAABB.html +++ b/docs/functions/notIntersectAABB.html @@ -1,2 +1,287 @@ -notIntersectAABB | Detect-Collisions

Function notIntersectAABB

  • returns true if two boxes not intersect

    -

    Parameters

    Returns boolean

+ + + + + + notIntersectAABB | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function notIntersectAABB

+
+
+
    + +
  • +
    +

    returns true if two boxes not intersect

    +
    +
    +

    Parameters

    +
      +
    • + bodyA: + BBox +
    • +
    • + bodyB: + BBox +
    • +
    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/pointInPolygon.html b/docs/functions/pointInPolygon.html index b55f3ab3..43d56aed 100644 --- a/docs/functions/pointInPolygon.html +++ b/docs/functions/pointInPolygon.html @@ -1 +1,283 @@ -pointInPolygon | Detect-Collisions

Function pointInPolygon

+ + + + + + pointInPolygon | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function pointInPolygon

+
+
+
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns boolean +

    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/pointOnCircle.html b/docs/functions/pointOnCircle.html index 5f940d23..dbaa95e3 100644 --- a/docs/functions/pointOnCircle.html +++ b/docs/functions/pointOnCircle.html @@ -1,2 +1,297 @@ -pointOnCircle | Detect-Collisions

Function pointOnCircle

+ + + + + + pointOnCircle | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function pointOnCircle

+
+
+ +
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/polygonInCircle.html b/docs/functions/polygonInCircle.html index eb33d31e..22665534 100644 --- a/docs/functions/polygonInCircle.html +++ b/docs/functions/polygonInCircle.html @@ -1 +1,289 @@ -polygonInCircle | Detect-Collisions

Function polygonInCircle

+ + + + + + polygonInCircle | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function polygonInCircle

+
+
+
    + +
  • +
    +

    Parameters

    +
      +
    • + polygon: + Polygon +
    • +
    • + circle: + Pick<Circle, "r" | "pos"> +
    • +
    +
    +

    + Returns boolean +

    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/polygonInPolygon.html b/docs/functions/polygonInPolygon.html index 8c3a96bb..4dcb72c9 100644 --- a/docs/functions/polygonInPolygon.html +++ b/docs/functions/polygonInPolygon.html @@ -1 +1,283 @@ -polygonInPolygon | Detect-Collisions

Function polygonInPolygon

+ + + + + + polygonInPolygon | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function polygonInPolygon

+
+
+
    + +
  • +
    +

    Parameters

    + +
    +

    + Returns boolean +

    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/rad2deg-1.html b/docs/functions/rad2deg-1.html index c6de09d9..a2ecb25b 100644 --- a/docs/functions/rad2deg-1.html +++ b/docs/functions/rad2deg-1.html @@ -1,2 +1,268 @@ -rad2deg | Detect-Collisions

Function rad2deg

  • convert from radians to degrees

    -

    Parameters

    • radians: number

    Returns number

+ + + + + + rad2deg | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function rad2deg

+
+
+
    + +
  • +
    +

    convert from radians to degrees

    +
    +
    +

    Parameters

    +
      +
    • + radians: + number +
    • +
    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/functions/returnTrue.html b/docs/functions/returnTrue.html index e1cf41b0..0cc8200c 100644 --- a/docs/functions/returnTrue.html +++ b/docs/functions/returnTrue.html @@ -1,2 +1,259 @@ -returnTrue | Detect-Collisions

Function returnTrue

  • dummy fn used as default, for optimization

    -

    Returns boolean

+ + + + + + returnTrue | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Function returnTrue

+
+
+
    + +
  • +
    +

    dummy fn used as default, for optimization

    +
    +

    + Returns boolean +

    +
    + +
  • +
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/hierarchy.html b/docs/hierarchy.html index 2b83448d..a3922765 100644 --- a/docs/hierarchy.html +++ b/docs/hierarchy.html @@ -1 +1,452 @@ -Detect-Collisions
+ + + + + + Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+

Detect-Collisions

+

Class Hierarchy

+ + + + + +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/index.html b/docs/index.html index 860a7d31..cb889071 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,77 +1,746 @@ -Detect-Collisions

Detect-Collisions

Detect-Collisions

npm version -npm downloads per week -build status

-

Detect-Collisions is a robust TypeScript library for detecting collisions among various entities. It employs Bounding Volume Hierarchy (BVH) and the Separating Axis Theorem (SAT) for efficient collision detection. Unique features include managing rotation, scale of bodies, and supporting the decomposition of concave polygons into convex ones. It optimizes detection with body padding, making it ideal for gaming, simulations, or projects requiring advanced collision detection with customization and fast performance.

- -
$ npm i detect-collisions --save
+
+
+  
+    
+    
+    Detect-Collisions
+    
+    
+    
+    
+    
+    
+    
+    
+    
+  
+  
+    
+    
+
+ +
+ + +
+
+
+
+
+

Detect-Collisions

+
+ +

+ Detect-Collisions + +

+

+ npm version + npm downloads per week + build status +

+ + +

+ Detect-Collisions is a robust TypeScript library for detecting + collisions among various entities. It employs Bounding Volume + Hierarchy (BVH) and the Separating Axis Theorem (SAT) for efficient + collision detection. Unique features include managing rotation, + scale of bodies, and supporting the decomposition of concave + polygons into convex ones. It optimizes detection with body padding, + making it ideal for gaming, simulations, or projects requiring + advanced collision detection with customization and fast + performance. +

+ + + + + +
$ npm i detect-collisions --save
 
-

For detailed documentation on the library's API, refer to the following link:

-

Detect-Collisions API Documentation

-

Initialize a unique collision system using Detect-Collisions:

-
const { System } = require("detect-collisions");
const system = new System(); + + +

+ For detailed documentation on the library's API, refer to the + following link: +

+

+ Detect-Collisions API Documentation +

+ + + + +

Initialize a unique collision system using Detect-Collisions:

+
const { System } = require("detect-collisions");
const system = new System();
-

Bodies possess various properties:

-
    -
  • Position: Use setPosition(x: number, y: number) for teleport and move(speed: number) for moving forward in direction of its angle.
  • -
  • Scale: Use setScale(x: number, y: number) for setting and scale: Vector for getting scale
  • -
  • Rotation: Use setAngle(radians: number) for setting and angle: number for getting and deg2rad(degrees: number) to convert to radians.
  • -
  • Offset: Use setOffset(offset: Vector) for setting and offset: Vector for getting offset from the body center.
  • -
  • AABB Bounding Box: Use aabb: BBox for inserted or getAABBAsBBox(): BBox for non inserted bodies to get the bounding box.
  • -
  • Padding: Use padding: number and set to nonzero value to reduce costly reinserts on attributes' change.
  • -
  • Collision Filtering: Use group: number for collision filtering, with a range within 0x0 ~ 0x7fffffff.
  • -
  • Body Options: Use isStatic: boolean to mark body as non movable and isTrigger: boolean to set body as ghost.
  • -
-

Create bodies of various types and manage them:

-
const {
Box,
Circle,
Ellipse,
Line,
Point,
Polygon,
} = require("detect-collisions");

// Example: Create and insert box1 body
const box1 = system.createBox(position, width, height, options);
// Example: Create box2 body
const box2 = new Box(position, width, height, options);
// Example: Insert box2 body
system.insert(box2); + + +

Bodies possess various properties:

+
    +
  • + Position: Use + setPosition(x: number, y: number) for teleport and + move(speed: number) for moving forward in direction + of its angle. +
  • +
  • + Scale: Use + setScale(x: number, y: number) for setting and + scale: Vector for getting scale +
  • +
  • + Rotation: Use + setAngle(radians: number) for setting and + angle: number for getting and + deg2rad(degrees: number) to convert to radians. +
  • +
  • + Offset: Use + setOffset(offset: Vector) for setting and + offset: Vector for getting offset from the body + center. +
  • +
  • + AABB Bounding Box: Use + aabb: BBox for inserted or + getAABBAsBBox(): BBox for non inserted bodies to get + the bounding box. +
  • +
  • + Padding: Use padding: number and set + to nonzero value to reduce costly reinserts on attributes' change. +
  • +
  • + Collision Filtering: Use + group: number for collision filtering, with a range + within 0x0 ~ 0x7fffffff. +
  • +
  • + Body Options: Use + isStatic: boolean to mark body as non movable and + isTrigger: boolean to set body as ghost. +
  • +
+ + +

Create bodies of various types and manage them:

+
const {
Box,
Circle,
Ellipse,
Line,
Point,
Polygon,
} = require("detect-collisions");

// Example: Create and insert box1 body
const box1 = system.createBox(position, width, height, options);
// Example: Create box2 body
const box2 = new Box(position, width, height, options);
// Example: Insert box2 body
system.insert(box2);
-

Manipulate body attributes and update the collision system:

-
// if omitted updateNow is true
const updateNow = false;
// this should be time scaled, 1 for example
const speed = 1;

// teleport
box.setPosition(x, y, updateNow);
box.setScale(scaleX, scaleY, updateNow);
box.setAngle(angle, updateNow);
box.move(speed, updateNow);
box.setOffset({ x, y }, updateNow);
console.log(box.dirty); // true

box.updateBody(); // Update the body once, when all manipulations are done
console.log(box.dirty); // false

box.group = group; // Immediate effect, no body/system update needed
console.log(box.dirty); // false + + +

Manipulate body attributes and update the collision system:

+
// if omitted updateNow is true
const updateNow = false;
// this should be time scaled, 1 for example
const speed = 1;

// teleport
box.setPosition(x, y, updateNow);
box.setScale(scaleX, scaleY, updateNow);
box.setAngle(angle, updateNow);
box.move(speed, updateNow);
box.setOffset({ x, y }, updateNow);
console.log(box.dirty); // true

box.updateBody(); // Update the body once, when all manipulations are done
console.log(box.dirty); // false

box.group = group; // Immediate effect, no body/system update needed
console.log(box.dirty); // false
-

Detect collisions and respond accordingly:

-
if (system.checkAll(callback)) {
// Do something yourself
}

if (system.checkOne(body, callback)) {
// Do something yourself
}

// Or separate bodies based on isStatic/isTrigger
system.separate(); + + +

Detect collisions and respond accordingly:

+
if (system.checkAll(callback)) {
// Do something yourself
}

if (system.checkOne(body, callback)) {
// Do something yourself
}

// Or separate bodies based on isStatic/isTrigger
system.separate();
-

Remove bodies when they're no longer needed:

-
system.remove(body);
+          
+          
+          

Remove bodies when they're no longer needed:

+
system.remove(body);
 
-

And that's it! You're now ready to utilize the Detect-Collisions library in your project.

-

To facilitate debugging, Detect-Collisions allows you to visually represent the collision bodies. By invoking the draw() method and supplying a 2D context of a <canvas> element, you can draw all the bodies within a collision system.

-
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");

context.strokeStyle = "#FFFFFF";
context.beginPath();
system.draw(context);
context.stroke(); +

+ And that's it! You're now ready to utilize the Detect-Collisions + library in your project. +

+ + +

+ To facilitate debugging, Detect-Collisions allows you to visually + represent the collision bodies. By invoking the + draw() method and supplying a 2D context of a + <canvas> element, you can draw all the bodies + within a collision system. You can also opt to draw individual + bodies. +

+
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");

context.strokeStyle = "#FFFFFF";
context.beginPath();
// draw specific body
body.draw(context);
// draw whole system
system.draw(context);
context.stroke();
-

You can also opt to draw individual bodies.

-
context.strokeStyle = "#FFFFFF";
context.beginPath();
// draw specific body
body.draw(context);
// draw whole system
system.draw(context);
context.stroke(); +

+ To assess the + Bounding Volume Hierarchy, you can draw the BVH. +

+
context.strokeStyle = "#FFFFFF";
context.beginPath();
// draw specific body bounding box
body.drawBVH(context);
// draw bounding volume hierarchy of the system
system.drawBVH(context);
context.stroke();
-

To assess the Bounding Volume Hierarchy, you can draw the BVH.

-
context.strokeStyle = "#FFFFFF";
context.beginPath();
// draw specific body bounding box
body.drawBVH(context);
// draw bounding volume hierarchy of the system
system.drawBVH(context);
context.stroke(); + + +

+ Detect-Collisions provides the functionality to gather raycast data. + Here's how: +

+
const start = { x: 0, y: 0 };
const end = { x: 0, y: -10 };
const hit = system.raycast(start, end);

if (hit) {
const { point, body } = hit;

console.log({ point, body });
}
-

Detect-Collisions provides the functionality to gather raycast data. Here's how:

-
const start = { x: 0, y: 0 };
const end = { x: 0, y: -10 };
const hit = system.raycast(start, end);

if (hit) {
const { point, body } = hit;

console.log({ point, body });
} +

+ In this example, point is a Vector with + the coordinates of the nearest intersection, and + body is a reference to the closest body. +

+ + +

+ We welcome contributions! Feel free to open a merge request. When + doing so, please adhere to the following code style guidelines: +

+
    +
  • + Execute the npm run precommit script prior to + submitting your merge request +
  • +
  • + Follow the + conventional commits + standard +
  • +
  • Refrain from using the any type
  • +
+ + + + +

+ While physics engines like + Matter-js or + Planck.js are + recommended for projects that need comprehensive physics simulation, + not all projects require such complexity. In fact, using a physics + engine solely for collision detection can lead to unnecessary + overhead and complications due to built-in assumptions (gravity, + velocity, friction, etc.). Detect-Collisions is purpose-built for + efficient and robust collision detection, making it an excellent + choice for projects that primarily require this functionality. It + can also serve as the foundation for a custom physics engine. +

+ + +

+ This will provide you with the results of both the insertion test + benchmark and a headless + Stress Demo + benchmark, featuring moving bodies, with increasing amounts in each + step. +

+
$ git clone https://github.com/Prozi/detect-collisions.git
$ cd detect-collisions
$ npm i && npm run build # will build & run tests & run benchmarks
-

In this example, point is a Vector with the coordinates of the nearest intersection, and body is a reference to the closest body.

-

We welcome contributions! Feel free to open a merge request. When doing so, please adhere to the following code style guidelines:

-
    -
  • Execute the npm run precommit script prior to submitting your merge request
  • -
  • Follow the conventional commits standard
  • -
  • Refrain from using the any type
  • -
-

While physics engines like Matter-js or Planck.js are recommended for projects that need comprehensive physics simulation, not all projects require such complexity. In fact, using a physics engine solely for collision detection can lead to unnecessary overhead and complications due to built-in assumptions (gravity, velocity, friction, etc.). Detect-Collisions is purpose-built for efficient and robust collision detection, making it an excellent choice for projects that primarily require this functionality. It can also serve as the foundation for a custom physics engine.

-

This will provide you with the results of both the insertion test benchmark and a headless Stress Demo benchmark, featuring moving bodies, with increasing amounts in each step.

-
$ git clone https://github.com/Prozi/detect-collisions.git
$ cd detect-collisions
$ npm i && npm run build # will build & run tests & run benchmarks -
- -

MIT

-

https://paypal.me/jacekpietal

-
+ + +

MIT

+ + +

+ https://paypal.me/jacekpietal +

+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/interfaces/BBox.html b/docs/interfaces/BBox.html index 5f8ee2fc..b9602d19 100644 --- a/docs/interfaces/BBox.html +++ b/docs/interfaces/BBox.html @@ -1,5 +1,476 @@ -BBox | Detect-Collisions

Interface BBox

interface BBox {
    maxX: number;
    maxY: number;
    minX: number;
    minY: number;
}

Implemented by

Properties

Properties

maxX: number
maxY: number
minX: number
minY: number
+ + + + + + BBox | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Interface BBox

+
+
+ interface BBox + {
    maxX: number;
    maxY: number;
    minX: number;
    minY: number;
} +
+
+

Implemented by

+ +
+ +
+
+
+ + + +
+
+

Properties

+ +
+
+
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ maxX: + number +
+ +
+
+ + +
+ maxY: + number +
+ +
+
+ + +
+ minX: + number +
+ +
+
+ + +
+ minY: + number +
+ +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/interfaces/BodyOptions.html b/docs/interfaces/BodyOptions.html index 33ac2ddc..e984095b 100644 --- a/docs/interfaces/BodyOptions.html +++ b/docs/interfaces/BodyOptions.html @@ -1,14 +1,583 @@ -BodyOptions | Detect-Collisions

Interface BodyOptions

BodyOptions for body creation

-
interface BodyOptions {
    angle?: number;
    group?: number;
    isCentered?: boolean;
    isStatic?: boolean;
    isTrigger?: boolean;
    padding?: number;
}

Properties

angle?: number

body angle in radians use deg2rad to convert

-
group?: number

group for collision filtering

-
isCentered?: boolean

is body offset centered for rotation purpouses

-
isStatic?: boolean

system.separate() doesn't move this body

-
isTrigger?: boolean

system.separate() doesn't trigger collision of this body

-
padding?: number

BHV padding for bounding box, preventing costly updates

-
+ + + + + + BodyOptions | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Interface BodyOptions

+
+
+
+

BodyOptions for body creation

+
+
+
+
+ interface BodyOptions + {
    angle?: number;
    group?: number;
    isCentered?: boolean;
    isStatic?: boolean;
    isTrigger?: boolean;
    padding?: number;
} +
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ angle?: + number +
+
+

body angle in radians use deg2rad to convert

+
+
+ +
+
+ + +
+ group?: + number +
+
+

group for collision filtering

+
+
+ +
+
+ + +
+ isCentered?: + boolean +
+
+

is body offset centered for rotation purpouses

+
+
+ +
+
+ + +
+ isStatic?: + boolean +
+
+

system.separate() doesn't move this body

+
+
+ +
+
+ + +
+ isTrigger?: + boolean +
+
+

system.separate() doesn't trigger collision of this body

+
+
+ +
+
+ + +
+ padding?: + number +
+
+

BHV padding for bounding box, preventing costly updates

+
+
+ +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/interfaces/BodyProps.html b/docs/interfaces/BodyProps.html index 7af599fc..20f440c8 100644 --- a/docs/interfaces/BodyProps.html +++ b/docs/interfaces/BodyProps.html @@ -1,48 +1,2045 @@ -BodyProps | Detect-Collisions

Interface BodyProps

each body contains those regardless of type

-
interface BodyProps {
    angle: number;
    bbox: BBox;
    dirty: boolean;
    group: number;
    isCentered: boolean;
    isConvex: boolean;
    isStatic: boolean;
    isTrigger: boolean;
    offset: SATVector;
    padding: number;
    system?: System<Body>;
    type: BodyType;
    typeGroup: BodyGroup;
    get scaleX(): number;
    get scaleY(): number;
    draw(context: CanvasRenderingContext2D): void;
    drawBVH(context: CanvasRenderingContext2D): void;
    getAABBAsBBox(): BBox;
    move(speed: number, updateNow?: boolean): SATPolygon | Circle;
    setAngle(angle: number, updateNow?: boolean): SATPolygon | Circle;
    setOffset(offset: Vector, updateNow?: boolean): SATPolygon | Circle;
    setPosition(x: number, y: number, updateNow?: boolean): SATPolygon | Circle;
    setScale(x: number, y: number, updateNow?: boolean): SATPolygon | Circle;
}

Hierarchy

Implemented by

Properties

angle: number

body angle in radians use deg2rad to convert

-
bbox: BBox

bounding box cache, without padding

-
dirty: boolean

was the body modified and needs update in the next checkCollision

-
group: number

group for collision filtering

-
isCentered: boolean

is body offset centered for rotation purpouses

-
isConvex: boolean

flag to show is it a convex body or non convex polygon

-
isStatic: boolean

system.separate() doesn't move this body

-
isTrigger: boolean

system.separate() doesn't trigger collision of this body

-
offset: SATVector

each body may have offset from center

-
padding: number

BHV padding for bounding box, preventing costly updates

-
system?: System<Body>

collisions system reference

-
type: BodyType

type of body

-
typeGroup: BodyGroup

faster for comparision, inner, type of body as number

-

Accessors

  • get scaleY(): number
  • scale getter (y = x for Circle)

    -

    Returns number

Methods

  • draw the collider

    -

    Parameters

    • context: CanvasRenderingContext2D

    Returns void

  • draw the bounding box

    -

    Parameters

    • context: CanvasRenderingContext2D

    Returns void

+ + + + + + BodyProps | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Interface BodyProps

+
+
+
+

each body contains those regardless of type

+
+
+
+
+ interface BodyProps + {
    angle: number;
    bbox: BBox;
    dirty: boolean;
    group: number;
    isCentered: boolean;
    isConvex: boolean;
    isStatic: boolean;
    isTrigger: boolean;
    offset: SATVector;
    padding: number;
    system?: System<Body>;
    type: BodyType;
    typeGroup: BodyGroup;
    get scaleX(): number;
    get scaleY(): number;
    draw(context: CanvasRenderingContext2D): void;
    drawBVH(context: CanvasRenderingContext2D): void;
    getAABBAsBBox(): BBox;
    move(speed: number, + updateNow?: boolean): SATPolygon | Circle;
    setAngle(angle: number, + updateNow?: boolean): SATPolygon | Circle;
    setOffset(offset: Vector, updateNow?: boolean): SATPolygon | Circle;
    setPosition(x: number, + y: number, + updateNow?: boolean): SATPolygon | Circle;
    setScale(x: number, + y: number, + updateNow?: boolean): SATPolygon | Circle;
} +
+
+

Hierarchy

+ +
+
+

Implemented by

+ +
+ +
+
+
+ + + + +
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ angle: + number +
+
+

body angle in radians use deg2rad to convert

+
+
+ +
+
+ + +
+ bbox: + BBox +
+
+

bounding box cache, without padding

+
+
+ +
+
+ + +
+ dirty: + boolean +
+
+

+ was the body modified and needs update in the next + checkCollision +

+
+
+ +
+
+ + +
+ group: + number +
+
+

group for collision filtering

+
+
+ +
+
+ + +
+ isCentered: + boolean +
+
+

is body offset centered for rotation purpouses

+
+
+ +
+
+ + +
+ isConvex: + boolean +
+
+

flag to show is it a convex body or non convex polygon

+
+
+ +
+
+ + +
+ isStatic: + boolean +
+
+

system.separate() doesn't move this body

+
+
+ +
+
+ + +
+ isTrigger: + boolean +
+
+

system.separate() doesn't trigger collision of this body

+
+
+ +
+
+ + +
+ offset: + SATVector +
+
+

each body may have offset from center

+
+
+ +
+
+ + +
+ padding: + number +
+
+

BHV padding for bounding box, preventing costly updates

+
+
+ +
+
+ + +
+ system?: + System<Body> +
+
+

collisions system reference

+
+
+ +
+
+ + +
+ type: + BodyType +
+

type of body

+
+ +
+
+ + +
+ typeGroup: + BodyGroup +
+
+

faster for comparision, inner, type of body as number

+
+
+ +
+
+
+
+ +

+ + + + Accessors +

+
+
+
+ + +
    +
  • + get scaleX(): number +
  • +
  • +
    +

    scale getter (x)

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+ + +
    +
  • + get scaleY(): number +
  • +
  • +
    +

    scale getter (y = x for Circle)

    +
    +

    + Returns number +

    +
    + +
  • +
+
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +
    +

    draw the collider

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    draw the bounding box

    +
    +
    +

    Parameters

    +
      +
    • + context: + CanvasRenderingContext2D +
    • +
    +
    +

    + Returns void +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    return bounding box without padding

    +
    +

    + Returns + BBox +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    update position BY MOVING FORWARD IN ANGLE DIRECTION

    +
    +
    +

    Parameters

    +
      +
    • + speed: + number +
    • +
    • + OptionalupdateNow: + boolean +
    • +
    +
    +

    + Returns + SATPolygon | Circle +

    +
    + +
  • +
+
+
+ + + +
+
+ + + +
+
+ + +
    + +
  • +
    +

    update position BY TELEPORTING

    +
    +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    • + y: + number +
    • +
    • + OptionalupdateNow: + boolean +
    • +
    +
    +

    + Returns + SATPolygon | Circle +

    +
    + +
  • +
+
+
+ + +
    + +
  • +
    +

    for setting scale

    +
    +
    +

    Parameters

    +
      +
    • + x: + number +
    • +
    • + y: + number +
    • +
    • + OptionalupdateNow: + boolean +
    • +
    +
    +

    + Returns + SATPolygon | Circle +

    +
    + +
  • +
+
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/interfaces/ChildrenData.html b/docs/interfaces/ChildrenData.html index 9867ae7e..ad143f3f 100644 --- a/docs/interfaces/ChildrenData.html +++ b/docs/interfaces/ChildrenData.html @@ -1,3 +1,391 @@ -ChildrenData | Detect-Collisions

Interface ChildrenData<TBody>

rbush data

-
interface ChildrenData<TBody> {
    children: Leaf<TBody>[];
}

Type Parameters

Properties

Properties

children: Leaf<TBody>[]
+ + + + + + ChildrenData | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Interface ChildrenData<TBody>

+
+
+

rbush data

+
+
+
+ interface ChildrenData<TBody> + {
    children: Leaf<TBody>[];
} +
+
+

Type Parameters

+
    +
  • + TBody extends Body +
  • +
+
+ +
+
+
+ + + +
+
+

Properties

+ +
+
+
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ children: + Leaf<TBody>[] +
+ +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/interfaces/Data.html b/docs/interfaces/Data.html index 48f54154..e6303d29 100644 --- a/docs/interfaces/Data.html +++ b/docs/interfaces/Data.html @@ -1,3 +1,387 @@ -Data | Detect-Collisions

Interface Data<TBody>

for use of private function of sat.js

-
interface Data<TBody> {
    data: ChildrenData<TBody>;
}

Type Parameters

Properties

Properties

+ + + + + + Data | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Interface Data<TBody>

+
+
+
+

for use of private function of sat.js

+
+
+
+
+ interface Data<TBody> + {
    data: ChildrenData<TBody>;
} +
+
+

Type Parameters

+
    +
  • + TBody extends Body +
  • +
+
+ +
+
+
+ + + +
+
+

Properties

+
+ + data +
+
+
+
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ data: + ChildrenData<TBody> +
+ +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/interfaces/GetAABBAsBox.html b/docs/interfaces/GetAABBAsBox.html index 528f863b..a30ed2d8 100644 --- a/docs/interfaces/GetAABBAsBox.html +++ b/docs/interfaces/GetAABBAsBox.html @@ -1,3 +1,459 @@ -GetAABBAsBox | Detect-Collisions

Interface GetAABBAsBox

for use of private function of sat.js

-
interface GetAABBAsBox {
    getAABBAsBox(): {
        h: number;
        pos: Vector;
        w: number;
    };
}

Methods

Methods

  • Returns {
        h: number;
        pos: Vector;
        w: number;
    }

+ + + + + + GetAABBAsBox | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Interface GetAABBAsBox

+
+
+
+

for use of private function of sat.js

+
+
+
+
+ interface GetAABBAsBox + {
    getAABBAsBox(): {
        h: number;
        pos: Vector;
        w: number;
    };
} +
+ +
+
+
+ + + +
+
+

Methods

+ +
+
+
+
+
+
+ +

+ + + + Methods +

+
+
+
+ + +
    + +
  • +

    + Returns {
        h: number;
        pos: Vector;
        w: number;
    } +

    +
      +
    • +
      + h: number +
      +
    • +
    • +
      + pos: Vector +
      +
    • +
    • +
      + w: number +
      +
    • +
    + +
  • +
+
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/interfaces/PotentialVector.html b/docs/interfaces/PotentialVector.html index 2e826533..c2625d2a 100644 --- a/docs/interfaces/PotentialVector.html +++ b/docs/interfaces/PotentialVector.html @@ -1,4 +1,403 @@ -PotentialVector | Detect-Collisions

Interface PotentialVector

potential vector

-
interface PotentialVector {
    x?: number;
    y?: number;
}

Hierarchy (view full)

Properties

x? -y? -

Properties

x?: number
y?: number
+ + + + + + PotentialVector | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Interface PotentialVector

+
+
+

potential vector

+
+
+
+ interface PotentialVector + {
    x?: number;
    y?: number;
} +
+
+

+ Hierarchy (view full) +

+
    +
  • + PotentialVector + +
  • +
+
+ +
+
+
+ + + +
+
+

Properties

+
+ + x? + + y? +
+
+
+
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ x?: + number +
+ +
+
+ + +
+ y?: + number +
+ +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/interfaces/RaycastHit.html b/docs/interfaces/RaycastHit.html index 6f1d122f..edb921d1 100644 --- a/docs/interfaces/RaycastHit.html +++ b/docs/interfaces/RaycastHit.html @@ -1,4 +1,414 @@ -RaycastHit | Detect-Collisions

Interface RaycastHit<TBody>

system.raycast(from, to) result

-
interface RaycastHit<TBody> {
    body: TBody;
    point: Vector;
}

Type Parameters

  • TBody

Properties

Properties

body: TBody
point: Vector
+ + + + + + RaycastHit | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Interface RaycastHit<TBody>

+
+
+
+

system.raycast(from, to) result

+
+
+
+
+ interface RaycastHit<TBody> + {
    body: TBody;
    point: Vector;
} +
+
+

Type Parameters

+
    +
  • + TBody +
  • +
+
+ +
+
+
+ + + +
+
+

Properties

+ +
+
+
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ body: + TBody +
+ +
+
+ + +
+ point: + Vector +
+ +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/interfaces/Vector.html b/docs/interfaces/Vector.html index 988ce9d8..fbc176b0 100644 --- a/docs/interfaces/Vector.html +++ b/docs/interfaces/Vector.html @@ -1,4 +1,412 @@ -Vector | Detect-Collisions

Interface Vector

x, y vector

-
interface Vector {
    x: number;
    y: number;
}

Hierarchy (view full)

Properties

x -y -

Properties

x: number
y: number
+ + + + + + Vector | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Interface Vector

+
+
+

x, y vector

+
+
+
+ interface Vector + {
    x: number;
    y: number;
} +
+
+

Hierarchy (view full)

+ +
+ +
+
+
+ + + +
+
+

Properties

+
+ + x + + y +
+
+
+
+
+
+
+ +

+ + + + Properties +

+
+
+
+ + +
+ x: + number +
+ +
+
+ + +
+ y: + number +
+ +
+
+
+
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/modules.html b/docs/modules.html index bdcc23aa..7eecc6cd 100644 --- a/docs/modules.html +++ b/docs/modules.html @@ -1,76 +1,654 @@ -Detect-Collisions
+ + + + + + Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/types/Body.html b/docs/types/Body.html index a595a0d5..a376c7f0 100644 --- a/docs/types/Body.html +++ b/docs/types/Body.html @@ -1,2 +1,270 @@ -Body | Detect-Collisions

Type Alias Body

Body:
    | Point
    | Line
    | Ellipse
    | Circle
    | Box
    | Polygon

generic body union type

-
+ + + + + + Body | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Type Alias Body

+
+
+ Body:
    | Point
    | Line
    | Ellipse
    | Circle
    | Box
    | Polygon +
+
+

generic body union type

+
+
+ +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/types/CollisionCallback.html b/docs/types/CollisionCallback.html index 6e1e5996..7eb21f7f 100644 --- a/docs/types/CollisionCallback.html +++ b/docs/types/CollisionCallback.html @@ -1 +1,249 @@ -CollisionCallback | Detect-Collisions

Type Alias CollisionCallback

CollisionCallback: ((response: Response) => boolean | void)
+ + + + + + CollisionCallback | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Type Alias CollisionCallback

+
+
+ CollisionCallback: + ((response: Response) => boolean | void) +
+ +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/types/DecompPoint.html b/docs/types/DecompPoint.html index 79470547..056ebb2c 100644 --- a/docs/types/DecompPoint.html +++ b/docs/types/DecompPoint.html @@ -1 +1,236 @@ -DecompPoint | Detect-Collisions

Type Alias DecompPoint

DecompPoint: [number, number]
+ + + + + + DecompPoint | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Type Alias DecompPoint

+
+
+ DecompPoint: + [number, number] +
+ +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/types/DecompPolygon.html b/docs/types/DecompPolygon.html index d529dad7..89db1400 100644 --- a/docs/types/DecompPolygon.html +++ b/docs/types/DecompPolygon.html @@ -1 +1,236 @@ -DecompPolygon | Detect-Collisions

Type Alias DecompPolygon

DecompPolygon: DecompPoint[]
+ + + + + + DecompPolygon | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Type Alias DecompPolygon

+
+
+ DecompPolygon: + DecompPoint[] +
+ +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/types/InTest.html b/docs/types/InTest.html index 72dab34a..0581a4fb 100644 --- a/docs/types/InTest.html +++ b/docs/types/InTest.html @@ -1 +1,282 @@ -InTest | Detect-Collisions

Type Alias InTest<TBody>

InTest<TBody>: ((bodyA: TBody, bodyB: TBody) => boolean)

Type Parameters

+ + + + + + InTest | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Type Alias InTest<TBody>

+
+
+ InTest<TBody>: + ((bodyA: TBody, bodyB: TBody) => boolean) +
+
+

Type Parameters

+ +
+ +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/types/Leaf.html b/docs/types/Leaf.html index d413b82b..da29a0d3 100644 --- a/docs/types/Leaf.html +++ b/docs/types/Leaf.html @@ -1,2 +1,283 @@ -Leaf | Detect-Collisions

Type Alias Leaf<TBody>

Leaf<TBody>: TBody & {
    children?: Leaf<TBody>[];
}

body with children (rbush)

-

Type Parameters

+ + + + + + Leaf | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Type Alias Leaf<TBody>

+
+
+ Leaf<TBody>: + TBody & {
    children?: Leaf<TBody>[];
} +
+
+

body with children (rbush)

+
+
+

Type Parameters

+
    +
  • + TBody extends Body +
  • +
+
+
+ +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/types/SATTest.html b/docs/types/SATTest.html index 121df377..a3501df6 100644 --- a/docs/types/SATTest.html +++ b/docs/types/SATTest.html @@ -1 +1,321 @@ -SATTest | Detect-Collisions

Type Alias SATTest<T, Y>

SATTest<T, Y>: ((bodyA: T, bodyB: Y, response: Response) => boolean)

Type Parameters

+ + + + + + SATTest | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Type Alias SATTest<T, Y>

+
+
+ SATTest<T, Y>: + ((bodyA: T, bodyB: Y, response: Response) => boolean) +
+
+

Type Parameters

+ +
+ +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/types/TraverseFunction.html b/docs/types/TraverseFunction.html index d17f1315..13b256f5 100644 --- a/docs/types/TraverseFunction.html +++ b/docs/types/TraverseFunction.html @@ -1 +1,296 @@ -TraverseFunction | Detect-Collisions

Type Alias TraverseFunction<TBody>

TraverseFunction<TBody>: ((child: Leaf<TBody>, children: Leaf<TBody>[], index: number) => boolean | void)

Type Parameters

+ + + + + + TraverseFunction | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Type Alias TraverseFunction<TBody>

+
+
+ TraverseFunction<TBody>: + ((child: Leaf<TBody>, + children: Leaf<TBody>[], + index: number) => boolean | void) +
+
+

Type Parameters

+ +
+ +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/variables/DEG2RAD.html b/docs/variables/DEG2RAD.html index a3a1d306..5f8ca95e 100644 --- a/docs/variables/DEG2RAD.html +++ b/docs/variables/DEG2RAD.html @@ -1 +1,237 @@ -DEG2RAD | Detect-Collisions

Variable DEG2RADConst

DEG2RAD: number = ...
+ + + + + + DEG2RAD | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Variable DEG2RADConst

+
+
+ DEG2RAD: + number = ... +
+ +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/docs/variables/RAD2DEG.html b/docs/variables/RAD2DEG.html index 9580a2a4..340a02dc 100644 --- a/docs/variables/RAD2DEG.html +++ b/docs/variables/RAD2DEG.html @@ -1 +1,237 @@ -RAD2DEG | Detect-Collisions

Variable RAD2DEGConst

RAD2DEG: number = ...
+ + + + + + RAD2DEG | Detect-Collisions + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+
+
+
+ +

Variable RAD2DEGConst

+
+
+ RAD2DEG: + number = ... +
+ +
+
+ + +
+
+
+

+ Generated using + TypeDoc +

+
+
+ + diff --git a/package.json b/package.json index b38ce2e3..db6b8861 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "detect-collisions", - "version": "9.9.4", + "version": "9.10.0", "description": "Detect collisions between different shapes such as Points, Lines, Boxes, Polygons (including concave), Ellipses, and Circles. Features include RayCasting and support for offsets, rotation, scaling, bounding box padding, with options for static and trigger bodies (non-colliding).", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "start": "chef-express dist/demo --debug", + "start": "chef-express dist/demo --debug --port 3000", "test": "jest --silent --verbose --forceExit", "dev": "webpack serve --port 3000", "amend": "npm run precommit;git commit -a --am --no-edit", @@ -30,7 +30,7 @@ "demo": "npm run build:demo && npm start", "precommit": "npm run lint && npm run build", "lint": "tslint --project tsconfig.json 'src/**/*.ts' --fix", - "format": "prettier --write {src,docs,*.*}", + "format": "prettier . --write", "benchmark": "npm run benchmark-insertion && npm run benchmark-stress", "benchmark-stress": "node -r pixi-shim -e 'import(\"./dist/benchmarks/index.js\").then(({ stressBenchmark }) => stressBenchmark());'", "benchmark-insertion": "node -e 'import(\"./dist/benchmarks/index.js\").then(({ insertionBenchmark }) => insertionBenchmark());'" diff --git a/src/base-system.ts b/src/base-system.ts index cc1c884f..82813569 100644 --- a/src/base-system.ts +++ b/src/base-system.ts @@ -8,46 +8,47 @@ import { PotentialVector, RBush, TraverseFunction, - Vector -} from "./model" -import { bodyMoved, drawBVH } from "./utils" -import { filter, forEach } from "./optimized" - -import { Box } from "./bodies/box" -import { Circle } from "./bodies/circle" -import { Ellipse } from "./bodies/ellipse" -import { Line } from "./bodies/line" -import { Point } from "./bodies/point" -import { Polygon } from "./bodies/polygon" + Vector, +} from "./model"; +import { bodyMoved, drawBVH } from "./utils"; +import { filter, forEach } from "./optimized"; + +import { Box } from "./bodies/box"; +import { Circle } from "./bodies/circle"; +import { Ellipse } from "./bodies/ellipse"; +import { Line } from "./bodies/line"; +import { Point } from "./bodies/point"; +import { Polygon } from "./bodies/polygon"; /** * very base collision system (create, insert, update, draw, remove) */ export class BaseSystem extends RBush - implements Data { - data!: ChildrenData + implements Data +{ + data!: ChildrenData; /** * create point at position with options and add to system */ createPoint(position: PotentialVector, options?: BodyOptions): Point { - const point = new Point(position, options) + const point = new Point(position, options); - this.insert(point as TBody) + this.insert(point as TBody); - return point + return point; } /** * create line at position with options and add to system */ createLine(start: Vector, end: Vector, options?: BodyOptions): Line { - const line = new Line(start, end, options) + const line = new Line(start, end, options); - this.insert(line as TBody) + this.insert(line as TBody); - return line + return line; } /** @@ -56,13 +57,13 @@ export class BaseSystem createCircle( position: PotentialVector, radius: number, - options?: BodyOptions + options?: BodyOptions, ): Circle { - const circle = new Circle(position, radius, options) + const circle = new Circle(position, radius, options); - this.insert(circle as TBody) + this.insert(circle as TBody); - return circle + return circle; } /** @@ -72,13 +73,13 @@ export class BaseSystem position: PotentialVector, width: number, height: number, - options?: BodyOptions + options?: BodyOptions, ): Box { - const box = new Box(position, width, height, options) + const box = new Box(position, width, height, options); - this.insert(box as TBody) + this.insert(box as TBody); - return box + return box; } /** @@ -89,13 +90,13 @@ export class BaseSystem radiusX: number, radiusY: number = radiusX, step?: number, - options?: BodyOptions + options?: BodyOptions, ): Ellipse { - const ellipse = new Ellipse(position, radiusX, radiusY, step, options) + const ellipse = new Ellipse(position, radiusX, radiusY, step, options); - this.insert(ellipse as TBody) + this.insert(ellipse as TBody); - return ellipse + return ellipse; } /** @@ -104,13 +105,13 @@ export class BaseSystem createPolygon( position: PotentialVector, points: PotentialVector[], - options?: BodyOptions + options?: BodyOptions, ): Polygon { - const polygon = new Polygon(position, points, options) + const polygon = new Polygon(position, points, options); - this.insert(polygon as TBody) + this.insert(polygon as TBody); - return polygon + return polygon; } /** @@ -118,33 +119,33 @@ export class BaseSystem * every body can be part of only one system */ insert(body: TBody): this { - body.bbox = body.getAABBAsBBox() + body.bbox = body.getAABBAsBBox(); if (body.system) { // allow end if body inserted and not moved if (!bodyMoved(body)) { - return this + return this; } // old bounding box *needs* to be removed - body.system.remove(body) + body.system.remove(body); } // only then we update min, max - body.minX = body.bbox.minX - body.padding - body.minY = body.bbox.minY - body.padding - body.maxX = body.bbox.maxX + body.padding - body.maxY = body.bbox.maxY + body.padding + body.minX = body.bbox.minX - body.padding; + body.minY = body.bbox.minY - body.padding; + body.maxX = body.bbox.maxX + body.padding; + body.maxY = body.bbox.maxY + body.padding; // reinsert bounding box to collision tree - return super.insert(body) + return super.insert(body); } /** * updates body in collision tree */ updateBody(body: TBody): void { - body.updateBody() + body.updateBody(); } /** @@ -152,8 +153,8 @@ export class BaseSystem */ update(): void { forEach(this.all(), (body: TBody) => { - this.updateBody(body) - }) + this.updateBody(body); + }); } /** @@ -161,8 +162,8 @@ export class BaseSystem */ draw(context: CanvasRenderingContext2D): void { forEach(this.all(), (body: TBody) => { - body.draw(context) - }) + body.draw(context); + }); } /** @@ -170,23 +171,23 @@ export class BaseSystem */ drawBVH(context: CanvasRenderingContext2D): void { const drawChildren = (body: Leaf) => { - drawBVH(context, body as TBody) + drawBVH(context, body as TBody); if (body.children) { - forEach(body.children, drawChildren) + forEach(body.children, drawChildren); } - } + }; - forEach(this.data.children, drawChildren) + forEach(this.data.children, drawChildren); } /** * remove body aabb from collision tree */ remove(body: TBody, equals?: InTest): this { - body.system = undefined + body.system = undefined; - return super.remove(body, equals) + return super.remove(body, equals); } /** @@ -195,7 +196,7 @@ export class BaseSystem */ getPotentials(body: TBody): TBody[] { // filter here is required as collides with self - return filter(this.search(body), (candidate: TBody) => candidate !== body) + return filter(this.search(body), (candidate: TBody) => candidate !== body); } /** @@ -203,21 +204,21 @@ export class BaseSystem */ traverse( traverseFunction: TraverseFunction, - { children }: { children?: Leaf[] } = this.data + { children }: { children?: Leaf[] } = this.data, ): TBody | undefined { return children?.find((body, index) => { if (!body) { - return false + return false; } if (body.typeGroup && traverseFunction(body, children, index)) { - return true + return true; } // if callback returns true, ends forEach if (body.children) { - this.traverse(traverseFunction, body) + this.traverse(traverseFunction, body); } - }) + }); } } diff --git a/src/benchmarks/index.ts b/src/benchmarks/index.ts index ddaf4ed1..d9d099a2 100644 --- a/src/benchmarks/index.ts +++ b/src/benchmarks/index.ts @@ -1,2 +1,2 @@ -export { insertionBenchmark } from "./insertion.bench" -export { stressBenchmark } from "./stress.bench" +export { insertionBenchmark } from "./insertion.bench"; +export { stressBenchmark } from "./stress.bench"; diff --git a/src/benchmarks/insertion.bench.ts b/src/benchmarks/insertion.bench.ts index bb1ddb18..45aeb101 100644 --- a/src/benchmarks/insertion.bench.ts +++ b/src/benchmarks/insertion.bench.ts @@ -1,98 +1,98 @@ /* tslint:disable:no-implicit-dependencies */ -import { Bench } from "tinybench" -import { Circle } from "../bodies/circle.js" -import { Polygon } from "../bodies/polygon.js" -import { SATVector } from "../model.js" -import { System } from "../system.js" +import { Bench } from "tinybench"; +import { Circle } from "../bodies/circle.js"; +import { Polygon } from "../bodies/polygon.js"; +import { SATVector } from "../model.js"; +import { System } from "../system.js"; export const insertionBenchmark = () => { - const benchmark = new Bench({}) - const nonoverlappingBodies: Circle[] = [] - const nonoverlappingTriangles: Polygon[] = [] - const nonoverlappingRectangles: Polygon[] = [] - const overlappingBodies: Circle[] = [] - const overlappingTriangles: Polygon[] = [] - const overlappingRectangles: Polygon[] = [] - const BODY_COUNT = 1000 + const benchmark = new Bench({}); + const nonoverlappingBodies: Circle[] = []; + const nonoverlappingTriangles: Polygon[] = []; + const nonoverlappingRectangles: Polygon[] = []; + const overlappingBodies: Circle[] = []; + const overlappingTriangles: Polygon[] = []; + const overlappingRectangles: Polygon[] = []; + const BODY_COUNT = 1000; for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - nonoverlappingBodies.push(new Circle(new SATVector(ndx, 0), 0.25)) - overlappingBodies.push(new Circle(new SATVector(0, 0), 0.25)) + nonoverlappingBodies.push(new Circle(new SATVector(ndx, 0), 0.25)); + overlappingBodies.push(new Circle(new SATVector(0, 0), 0.25)); nonoverlappingTriangles.push( new Polygon(new SATVector(ndx * 2, 0), [ new SATVector(0, 0), new SATVector(0, 1), - new SATVector(1, 0) - ]) - ) + new SATVector(1, 0), + ]), + ); overlappingTriangles.push( new Polygon(new SATVector(0, 0), [ new SATVector(0, 0), new SATVector(0, 1), - new SATVector(1, 0) - ]) - ) + new SATVector(1, 0), + ]), + ); nonoverlappingRectangles.push( new Polygon(new SATVector(0, 0), [ new SATVector(0, 0), new SATVector(0, 1), new SATVector(1, 1), - new SATVector(1, 0) - ]) - ) + new SATVector(1, 0), + ]), + ); overlappingRectangles.push( new Polygon(new SATVector(0, 0), [ new SATVector(0, 0), new SATVector(0, 1), new SATVector(1, 1), - new SATVector(1, 0) - ]) - ) + new SATVector(1, 0), + ]), + ); } benchmark .add("non overlapping circles", () => { - const uut = new System(BODY_COUNT) + const uut = new System(BODY_COUNT); for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(nonoverlappingBodies[ndx]) + uut.insert(nonoverlappingBodies[ndx]); } }) .add("overlapping circles", () => { - const uut = new System(BODY_COUNT) + const uut = new System(BODY_COUNT); for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(overlappingBodies[ndx]) + uut.insert(overlappingBodies[ndx]); } }) .add("non-overlapping triangles", () => { - const uut = new System(BODY_COUNT) + const uut = new System(BODY_COUNT); for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(nonoverlappingTriangles[ndx]) + uut.insert(nonoverlappingTriangles[ndx]); } }) .add("overlapping triangles", () => { - const uut = new System(BODY_COUNT) + const uut = new System(BODY_COUNT); for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(overlappingTriangles[ndx]) + uut.insert(overlappingTriangles[ndx]); } }) .add("non-overlapping quad", () => { - const uut = new System(BODY_COUNT) + const uut = new System(BODY_COUNT); for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(nonoverlappingRectangles[ndx]) + uut.insert(nonoverlappingRectangles[ndx]); } }) .add("overlapping quad", () => { - const uut = new System(BODY_COUNT) + const uut = new System(BODY_COUNT); for (let ndx = 0; ndx < BODY_COUNT; ndx++) { - uut.insert(overlappingRectangles[ndx]) + uut.insert(overlappingRectangles[ndx]); } - }) + }); benchmark .run() @@ -104,11 +104,11 @@ export const insertionBenchmark = () => { "Standard Deviation (s)": parseFloat((result?.sd ?? 0).toFixed(3)), hz: parseFloat((result?.hz ?? 0).toFixed(3)), "p99 (s)": parseFloat((result?.p99 ?? 0).toFixed(3)), - "p995 (s)": parseFloat((result?.p995 ?? 0).toFixed(3)) - })) - ) - }) - .catch(err => { - console.warn(err.message || err) + "p995 (s)": parseFloat((result?.p995 ?? 0).toFixed(3)), + })), + ); }) -} + .catch((err) => { + console.warn(err.message || err); + }); +}; diff --git a/src/benchmarks/stress.bench.ts b/src/benchmarks/stress.bench.ts index 6db55b4c..940f1e22 100644 --- a/src/benchmarks/stress.bench.ts +++ b/src/benchmarks/stress.bench.ts @@ -1,39 +1,39 @@ /* tslint:disable:no-implicit-dependencies variable-name no-any */ -import { Bench } from "tinybench" +import { Bench } from "tinybench"; -export const stressBenchmark = async() => { - const { default: Stress } = await import("../demo/stress.js") +export const stressBenchmark = async () => { + const { default: Stress } = await import("../demo/stress.js"); - let stressTest: any + let stressTest: any; const benchmark = new Bench({ time: 1000, warmupIterations: 0, setup: ({ opts }: any) => { - stressTest = new Stress(opts.items) + stressTest = new Stress(opts.items); }, teardown: () => { - stressTest.physics.clear() - } - }) + stressTest.physics.clear(); + }, + }); const recursiveAddTest = (items: number) => { benchmark.add( `stress test, items=${items}`, () => { - stressTest.update() + stressTest.update(); }, - { items } as any - ) + { items } as any, + ); if (items < 10000) { - recursiveAddTest(items + 1000) + recursiveAddTest(items + 1000); } - } + }; - recursiveAddTest(1000) + recursiveAddTest(1000); - await benchmark.run() + await benchmark.run(); - console.table(benchmark.table()) -} + console.table(benchmark.table()); +}; diff --git a/src/bodies/box.ts b/src/bodies/box.ts index 2aa945e8..e3329cfe 100644 --- a/src/bodies/box.ts +++ b/src/bodies/box.ts @@ -1,7 +1,7 @@ -import { BodyGroup, BodyOptions, BodyType, PotentialVector } from "../model" +import { BodyGroup, BodyOptions, BodyType, PotentialVector } from "../model"; -import { Polygon } from "./polygon" -import { createBox } from "../utils" +import { Polygon } from "./polygon"; +import { createBox } from "../utils"; /** * collider - box @@ -10,27 +10,27 @@ export class Box extends Polygon { /** * type of body */ - readonly type: BodyType.Box | BodyType.Point = BodyType.Box + readonly type: BodyType.Box | BodyType.Point = BodyType.Box; /** * faster than type */ - readonly typeGroup: BodyGroup.Box | BodyGroup.Point = BodyGroup.Box + readonly typeGroup: BodyGroup.Box | BodyGroup.Point = BodyGroup.Box; /** * boxes are convex */ - readonly isConvex = true + readonly isConvex = true; /** * inner width */ - protected _width: number + protected _width: number; /** * inner height */ - protected _height: number + protected _height: number; /** * collider - box @@ -39,42 +39,42 @@ export class Box extends Polygon { position: PotentialVector, width: number, height: number, - options?: BodyOptions + options?: BodyOptions, ) { - super(position, createBox(width, height), options) + super(position, createBox(width, height), options); - this._width = width - this._height = height + this._width = width; + this._height = height; } /** * get box width */ get width(): number { - return this._width + return this._width; } /** * set box width, update points */ set width(width: number) { - this._width = width - this.afterUpdateSize() + this._width = width; + this.afterUpdateSize(); } /** * get box height */ get height(): number { - return this._height + return this._height; } /** * set box height, update points */ set height(height: number) { - this._height = height - this.afterUpdateSize() + this._height = height; + this.afterUpdateSize(); } /** @@ -83,13 +83,13 @@ export class Box extends Polygon { */ protected afterUpdateSize(): void { if (this.isCentered) { - this.retranslate(false) + this.retranslate(false); } - this.setPoints(createBox(this._width, this._height)) + this.setPoints(createBox(this._width, this._height)); if (this.isCentered) { - this.retranslate() + this.retranslate(); } } @@ -97,6 +97,6 @@ export class Box extends Polygon { * do not attempt to use Polygon.updateIsConvex() */ protected updateIsConvex(): void { - return + return; } } diff --git a/src/bodies/circle.ts b/src/bodies/circle.ts index c4055d0c..58629494 100644 --- a/src/bodies/circle.ts +++ b/src/bodies/circle.ts @@ -6,19 +6,19 @@ import { BodyType, PotentialVector, SATVector, - Vector -} from "../model" + Vector, +} from "../model"; import { dashLineTo, drawBVH, ensureVectorPoint, extendBody, getGroup, - move -} from "../utils" + move, +} from "../utils"; -import { Circle as SATCircle } from "sat" -import { System } from "../system" +import { Circle as SATCircle } from "sat"; +import { System } from "../system"; /** * collider - circle @@ -27,97 +27,97 @@ export class Circle extends SATCircle implements BBox, BodyProps { /** * minimum x bound of body */ - minX!: number + minX!: number; /** * maximum x bound of body */ - maxX!: number + maxX!: number; /** * minimum y bound of body */ - minY!: number + minY!: number; /** * maximum y bound of body */ - maxY!: number + maxY!: number; /** * bounding box cache, without padding */ - bbox!: BBox + bbox!: BBox; /** * offset */ - offset!: SATVector + offset!: SATVector; /** * offset copy without angle applied */ - offsetCopy: Vector = { x: 0, y: 0 } + offsetCopy: Vector = { x: 0, y: 0 }; /** * bodies are not reinserted during update if their bbox didnt move outside bbox + padding */ - padding!: number + padding!: number; /** * for compatibility reasons circle has angle */ - angle!: number + angle!: number; /** * static bodies don't move but they collide */ - isStatic!: boolean + isStatic!: boolean; /** * trigger bodies move but are like ghosts */ - isTrigger!: boolean + isTrigger!: boolean; /** * reference to collision system */ - system?: System + system?: System; /** * was the polygon modified and needs update in the next checkCollision */ - dirty = false + dirty = false; /* * circles are convex */ - readonly isConvex = true + readonly isConvex = true; /** * circle type */ - readonly type: BodyType.Circle = BodyType.Circle + readonly type: BodyType.Circle = BodyType.Circle; /** * faster than type */ - readonly typeGroup: BodyGroup.Circle = BodyGroup.Circle + readonly typeGroup: BodyGroup.Circle = BodyGroup.Circle; /** * always centered */ - readonly isCentered = true + readonly isCentered = true; /** * group for collision filtering */ - protected _group!: number + protected _group!: number; /** * saved initial radius - internal */ - protected readonly unscaledRadius: number + protected readonly unscaledRadius: number; /** * collider - circle @@ -125,182 +125,182 @@ export class Circle extends SATCircle implements BBox, BodyProps { constructor( position: PotentialVector, radius: number, - options?: BodyOptions + options?: BodyOptions, ) { - super(ensureVectorPoint(position), radius) + super(ensureVectorPoint(position), radius); - extendBody(this, options) + extendBody(this, options); - this.unscaledRadius = radius + this.unscaledRadius = radius; } /** * get this.pos.x */ get x(): number { - return this.pos.x + return this.pos.x; } /** * updating this.pos.x by this.x = x updates AABB */ set x(x: number) { - this.pos.x = x - this.markAsDirty() + this.pos.x = x; + this.markAsDirty(); } /** * get this.pos.y */ get y(): number { - return this.pos.y + return this.pos.y; } /** * updating this.pos.y by this.y = y updates AABB */ set y(y: number) { - this.pos.y = y - this.markAsDirty() + this.pos.y = y; + this.markAsDirty(); } /** * allow get scale */ get scale(): number { - return this.r / this.unscaledRadius + return this.r / this.unscaledRadius; } /** * shorthand for setScale() */ set scale(scale: number) { - this.setScale(scale) + this.setScale(scale); } /** * scaleX = scale in case of Circles */ get scaleX(): number { - return this.scale + return this.scale; } /** * scaleY = scale in case of Circles */ get scaleY(): number { - return this.scale + return this.scale; } /** * group for collision filtering */ get group(): number { - return this._group + return this._group; } set group(group: number) { - this._group = getGroup(group) + this._group = getGroup(group); } /** * update position BY MOVING FORWARD IN ANGLE DIRECTION */ move(speed = 1, updateNow = true): Circle { - move(this, speed, updateNow) + move(this, speed, updateNow); - return this + return this; } /** * update position BY TELEPORTING */ setPosition(x: number, y: number, updateNow = true): Circle { - this.pos.x = x - this.pos.y = y - this.markAsDirty(updateNow) + this.pos.x = x; + this.pos.y = y; + this.markAsDirty(updateNow); - return this + return this; } /** * update scale */ setScale(scaleX: number, _scaleY = scaleX, updateNow = true): Circle { - this.r = this.unscaledRadius * Math.abs(scaleX) - this.markAsDirty(updateNow) + this.r = this.unscaledRadius * Math.abs(scaleX); + this.markAsDirty(updateNow); - return this + return this; } /** * set rotation */ setAngle(angle: number, updateNow = true): Circle { - this.angle = angle + this.angle = angle; - const { x, y } = this.getOffsetWithAngle() - this.offset.x = x - this.offset.y = y - this.markAsDirty(updateNow) + const { x, y } = this.getOffsetWithAngle(); + this.offset.x = x; + this.offset.y = y; + this.markAsDirty(updateNow); - return this + return this; } /** * set offset from center */ setOffset(offset: Vector, updateNow = true): Circle { - this.offsetCopy.x = offset.x - this.offsetCopy.y = offset.y + this.offsetCopy.x = offset.x; + this.offsetCopy.y = offset.y; - const { x, y } = this.getOffsetWithAngle() - this.offset.x = x - this.offset.y = y - this.markAsDirty(updateNow) + const { x, y } = this.getOffsetWithAngle(); + this.offset.x = x; + this.offset.y = y; + this.markAsDirty(updateNow); - return this + return this; } /** * get body bounding box, without padding */ getAABBAsBBox(): BBox { - const x = this.pos.x + this.offset.x - const y = this.pos.y + this.offset.y + const x = this.pos.x + this.offset.x; + const y = this.pos.y + this.offset.y; return { minX: x - this.r, maxX: x + this.r, minY: y - this.r, - maxY: y + this.r - } + maxY: y + this.r, + }; } /** * Draws collider on a CanvasRenderingContext2D's current path */ draw(context: CanvasRenderingContext2D) { - const x = this.pos.x + this.offset.x - const y = this.pos.y + this.offset.y - const r = Math.abs(this.r) + const x = this.pos.x + this.offset.x; + const y = this.pos.y + this.offset.y; + const r = Math.abs(this.r); if (this.isTrigger) { - const max = Math.max(8, this.r) + const max = Math.max(8, this.r); for (let i = 0; i < max; i++) { - const arc = (i / max) * 2 * Math.PI - const arcPrev = ((i - 1) / max) * 2 * Math.PI - const fromX = x + Math.cos(arcPrev) * this.r - const fromY = y + Math.sin(arcPrev) * this.r - const toX = x + Math.cos(arc) * this.r - const toY = y + Math.sin(arc) * this.r - - dashLineTo(context, fromX, fromY, toX, toY) + const arc = (i / max) * 2 * Math.PI; + const arcPrev = ((i - 1) / max) * 2 * Math.PI; + const fromX = x + Math.cos(arcPrev) * this.r; + const fromY = y + Math.sin(arcPrev) * this.r; + const toX = x + Math.cos(arc) * this.r; + const toY = y + Math.sin(arc) * this.r; + + dashLineTo(context, fromX, fromY, toX, toY); } } else { - context.moveTo(x + r, y) - context.arc(x, y, r, 0, Math.PI * 2) + context.moveTo(x + r, y); + context.arc(x, y, r, 0, Math.PI * 2); } } @@ -308,7 +308,7 @@ export class Circle extends SATCircle implements BBox, BodyProps { * Draws Bounding Box on canvas context */ drawBVH(context: CanvasRenderingContext2D) { - drawBVH(context, this) + drawBVH(context, this); } /** @@ -316,8 +316,8 @@ export class Circle extends SATCircle implements BBox, BodyProps { */ updateBody(updateNow = this.dirty): void { if (updateNow) { - this.system?.insert(this) - this.dirty = false + this.system?.insert(this); + this.dirty = false; } } @@ -326,9 +326,9 @@ export class Circle extends SATCircle implements BBox, BodyProps { */ protected markAsDirty(updateNow = false): void { if (updateNow) { - this.updateBody(true) + this.updateBody(true); } else { - this.dirty = true + this.dirty = true; } } @@ -337,14 +337,14 @@ export class Circle extends SATCircle implements BBox, BodyProps { */ protected getOffsetWithAngle(): Vector { if ((!this.offsetCopy.x && !this.offsetCopy.y) || !this.angle) { - return this.offsetCopy + return this.offsetCopy; } - const sin = Math.sin(this.angle) - const cos = Math.cos(this.angle) - const x = this.offsetCopy.x * cos - this.offsetCopy.y * sin - const y = this.offsetCopy.x * sin + this.offsetCopy.y * cos + const sin = Math.sin(this.angle); + const cos = Math.cos(this.angle); + const x = this.offsetCopy.x * cos - this.offsetCopy.y * sin; + const y = this.offsetCopy.x * sin + this.offsetCopy.y * cos; - return { x, y } + return { x, y }; } } diff --git a/src/bodies/ellipse.ts b/src/bodies/ellipse.ts index daf36556..7eceb995 100644 --- a/src/bodies/ellipse.ts +++ b/src/bodies/ellipse.ts @@ -1,7 +1,7 @@ -import { BodyGroup, BodyOptions, BodyType, PotentialVector } from "../model" +import { BodyGroup, BodyOptions, BodyType, PotentialVector } from "../model"; -import { Polygon } from "./polygon" -import { createEllipse } from "../utils" +import { Polygon } from "./polygon"; +import { createEllipse } from "../utils"; /** * collider - ellipse @@ -10,24 +10,24 @@ export class Ellipse extends Polygon { /** * ellipse type */ - readonly type: BodyType.Ellipse = BodyType.Ellipse + readonly type: BodyType.Ellipse = BodyType.Ellipse; /** * faster than type */ - readonly typeGroup: BodyGroup.Ellipse = BodyGroup.Ellipse + readonly typeGroup: BodyGroup.Ellipse = BodyGroup.Ellipse; /** * ellipses are convex */ - readonly isConvex = true + readonly isConvex = true; /** * inner initial params save */ - protected _radiusX: number - protected _radiusY: number - protected _step: number + protected _radiusX: number; + protected _radiusY: number; + protected _step: number; /** * collider - ellipse @@ -37,13 +37,13 @@ export class Ellipse extends Polygon { radiusX: number, radiusY: number = radiusX, step: number = (radiusX + radiusY) / Math.PI, - options?: BodyOptions + options?: BodyOptions, ) { - super(position, createEllipse(radiusX, radiusY, step), options) + super(position, createEllipse(radiusX, radiusY, step), options); - this._radiusX = radiusX - this._radiusY = radiusY - this._step = step + this._radiusX = radiusX; + this._radiusY = radiusY; + this._step = step; } /** @@ -55,65 +55,65 @@ export class Ellipse extends Polygon { * is body centered? */ get isCentered(): boolean { - return true + return true; } /** * get ellipse step number */ get step(): number { - return this._step + return this._step; } /** * set ellipse step number */ set step(step: number) { - this._step = step - this.setPoints(createEllipse(this._radiusX, this._radiusY, this._step)) + this._step = step; + this.setPoints(createEllipse(this._radiusX, this._radiusY, this._step)); } /** * get ellipse radiusX */ get radiusX(): number { - return this._radiusX + return this._radiusX; } /** * set ellipse radiusX, update points */ set radiusX(radiusX: number) { - this._radiusX = radiusX - this.setPoints(createEllipse(this._radiusX, this._radiusY, this._step)) + this._radiusX = radiusX; + this.setPoints(createEllipse(this._radiusX, this._radiusY, this._step)); } /** * get ellipse radiusY */ get radiusY(): number { - return this._radiusY + return this._radiusY; } /** * set ellipse radiusY, update points */ set radiusY(radiusY: number) { - this._radiusY = radiusY - this.setPoints(createEllipse(this._radiusX, this._radiusY, this._step)) + this._radiusY = radiusY; + this.setPoints(createEllipse(this._radiusX, this._radiusY, this._step)); } /** * do not attempt to use Polygon.center() */ center(): void { - return + return; } /** * do not attempt to use Polygon.updateIsConvex() */ protected updateIsConvex(): void { - return + return; } } diff --git a/src/bodies/line.ts b/src/bodies/line.ts index d4a248e1..ac80a84d 100644 --- a/src/bodies/line.ts +++ b/src/bodies/line.ts @@ -1,7 +1,7 @@ -import { BodyGroup, BodyOptions, BodyType, Vector } from "../model" +import { BodyGroup, BodyOptions, BodyType, Vector } from "../model"; -import { Polygon } from "./polygon" -import { Vector as SATVector } from "sat" +import { Polygon } from "./polygon"; +import { Vector as SATVector } from "sat"; /** * collider - line @@ -10,17 +10,17 @@ export class Line extends Polygon { /** * line type */ - readonly type: BodyType.Line = BodyType.Line + readonly type: BodyType.Line = BodyType.Line; /** * faster than type */ - readonly typeGroup: BodyGroup.Line = BodyGroup.Line + readonly typeGroup: BodyGroup.Line = BodyGroup.Line; /** * line is convex */ - readonly isConvex = true + readonly isConvex = true; /** * collider - line from start to end @@ -30,54 +30,54 @@ export class Line extends Polygon { start, [ { x: 0, y: 0 }, - { x: end.x - start.x, y: end.y - start.y } + { x: end.x - start.x, y: end.y - start.y }, ], - options - ) + options, + ); if (this.calcPoints.length === 1 || !end) { - console.error({ start, end }) + console.error({ start, end }); - throw new Error("No end point for line provided") + throw new Error("No end point for line provided"); } } get start(): Vector { return { x: this.x + this.calcPoints[0].x, - y: this.y + this.calcPoints[0].y - } + y: this.y + this.calcPoints[0].y, + }; } set start({ x, y }: Vector) { - this.x = x - this.y = y + this.x = x; + this.y = y; } get end(): Vector { return { x: this.x + this.calcPoints[1].x, - y: this.y + this.calcPoints[1].y - } + y: this.y + this.calcPoints[1].y, + }; } set end({ x, y }: Vector) { - this.points[1].x = x - this.start.x - this.points[1].y = y - this.start.y - this.setPoints(this.points) + this.points[1].x = x - this.start.x; + this.points[1].y = y - this.start.y; + this.setPoints(this.points); } getCentroid(): SATVector { return new SATVector( (this.end.x - this.start.x) / 2, - (this.end.y - this.start.y) / 2 - ) + (this.end.y - this.start.y) / 2, + ); } /** * do not attempt to use Polygon.updateIsConvex() */ protected updateIsConvex(): void { - return + return; } } diff --git a/src/bodies/point.ts b/src/bodies/point.ts index 933a5246..98eeb2cf 100644 --- a/src/bodies/point.ts +++ b/src/bodies/point.ts @@ -1,7 +1,7 @@ -import { BodyGroup, BodyOptions, BodyType, PotentialVector } from "../model" +import { BodyGroup, BodyOptions, BodyType, PotentialVector } from "../model"; -import { Box } from "./box" -import { ensureVectorPoint } from "../utils" +import { Box } from "./box"; +import { ensureVectorPoint } from "../utils"; /** * collider - point (very tiny box) @@ -10,17 +10,17 @@ export class Point extends Box { /** * point type */ - readonly type: BodyType.Point = BodyType.Point + readonly type: BodyType.Point = BodyType.Point; /** * faster than type */ - readonly typeGroup: BodyGroup.Point = BodyGroup.Point + readonly typeGroup: BodyGroup.Point = BodyGroup.Point; /** * collider - point (very tiny box) */ constructor(position: PotentialVector, options?: BodyOptions) { - super(ensureVectorPoint(position), 0.001, 0.001, options) + super(ensureVectorPoint(position), 0.001, 0.001, options); } } diff --git a/src/bodies/polygon.ts b/src/bodies/polygon.ts index 02c5c4aa..36db683a 100644 --- a/src/bodies/polygon.ts +++ b/src/bodies/polygon.ts @@ -8,8 +8,8 @@ import { GetAABBAsBox, PotentialVector, SATVector, - Vector -} from "../model" + Vector, +} from "../model"; import { clonePointsArray, drawBVH, @@ -20,15 +20,15 @@ import { getGroup, mapArrayToVector, mapVectorToArray, - move -} from "../utils" -import { forEach, map } from "../optimized" -import { isSimple, quickDecomp } from "poly-decomp-es" + move, +} from "../utils"; +import { forEach, map } from "../optimized"; +import { isSimple, quickDecomp } from "poly-decomp-es"; -import { Polygon as SATPolygon } from "sat" -import { System } from "../system" +import { Polygon as SATPolygon } from "sat"; +import { System } from "../system"; -export { isSimple } +export { isSimple }; /** * collider - polygon @@ -37,62 +37,62 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { /** * minimum x bound of body */ - minX!: number + minX!: number; /** * maximum x bound of body */ - maxX!: number + maxX!: number; /** * minimum y bound of body */ - minY!: number + minY!: number; /** * maximum y bound of body */ - maxY!: number + maxY!: number; /** * bounding box cache, without padding */ - bbox!: BBox + bbox!: BBox; /** * is it a convex polgyon as opposed to a hollow inside (concave) polygon */ - isConvex!: boolean + isConvex!: boolean; /** * optimization for convex polygons */ - convexPolygons!: SATPolygon[] + convexPolygons!: SATPolygon[]; /** * bodies are not reinserted during update if their bbox didnt move outside bbox + padding */ - padding!: number + padding!: number; /** * static bodies don't move but they collide */ - isStatic!: boolean + isStatic!: boolean; /** * trigger bodies move but are like ghosts */ - isTrigger!: boolean + isTrigger!: boolean; /** * reference to collision system */ - system?: System + system?: System; /** * was the polygon modified and needs update in the next checkCollision */ - dirty = false + dirty = false; /** * type of body @@ -102,7 +102,7 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { | BodyType.Box | BodyType.Point | BodyType.Ellipse - | BodyType.Line = BodyType.Polygon + | BodyType.Line = BodyType.Polygon; /** * faster than type @@ -112,27 +112,27 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { | BodyGroup.Box | BodyGroup.Point | BodyGroup.Ellipse - | BodyGroup.Line = BodyGroup.Polygon + | BodyGroup.Line = BodyGroup.Polygon; /** * backup of points used for scaling */ - protected pointsBackup!: Vector[] + protected pointsBackup!: Vector[]; /** * is body centered */ - protected centered = false + protected centered = false; /** * group for collision filtering */ - protected _group!: number + protected _group!: number; /** * scale Vector of body */ - protected readonly scaleVector: Vector = { x: 1, y: 1 } + protected readonly scaleVector: Vector = { x: 1, y: 1 }; /** * collider - polygon @@ -140,15 +140,15 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { constructor( position: PotentialVector, points: PotentialVector[], - options?: BodyOptions + options?: BodyOptions, ) { - super(ensureVectorPoint(position), ensurePolygonPoints(points)) + super(ensureVectorPoint(position), ensurePolygonPoints(points)); if (!points.length) { - throw new Error("No points in polygon") + throw new Error("No points in polygon"); } - extendBody(this, options) + extendBody(this, options); } /** @@ -156,171 +156,171 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { */ set isCentered(isCentered: boolean) { if (this.centered === isCentered) { - return + return; } - const centroid = this.getCentroidWithoutRotation() + const centroid = this.getCentroidWithoutRotation(); if (centroid.x || centroid.y) { - const x = centroid.x * (isCentered ? 1 : -1) - const y = centroid.y * (isCentered ? 1 : -1) + const x = centroid.x * (isCentered ? 1 : -1); + const y = centroid.y * (isCentered ? 1 : -1); - this.translate(-x, -y) + this.translate(-x, -y); } - this.centered = isCentered + this.centered = isCentered; } /** * is polygon centered? */ get isCentered(): boolean { - return this.centered + return this.centered; } get x(): number { - return this.pos.x + return this.pos.x; } /** * updating this.pos.x by this.x = x updates AABB */ set x(x: number) { - this.pos.x = x - this.markAsDirty() + this.pos.x = x; + this.markAsDirty(); } get y(): number { - return this.pos.y + return this.pos.y; } /** * updating this.pos.y by this.y = y updates AABB */ set y(y: number) { - this.pos.y = y - this.markAsDirty() + this.pos.y = y; + this.markAsDirty(); } /** * allow exact getting of scale x - use setScale(x, y) to set */ get scaleX(): number { - return this.scaleVector.x + return this.scaleVector.x; } /** * allow exact getting of scale y - use setScale(x, y) to set */ get scaleY(): number { - return this.scaleVector.y + return this.scaleVector.y; } /** * allow approx getting of scale */ get scale(): number { - return (this.scaleVector.x + this.scaleVector.y) / 2 + return (this.scaleVector.x + this.scaleVector.y) / 2; } /** * allow easier setting of scale */ set scale(scale: number) { - this.setScale(scale) + this.setScale(scale); } /** * group for collision filtering */ get group(): number { - return this._group + return this._group; } set group(group: number) { - this._group = getGroup(group) + this._group = getGroup(group); } /** * update position BY MOVING FORWARD IN ANGLE DIRECTION */ move(speed = 1, updateNow = true): SATPolygon { - move(this, speed, updateNow) + move(this, speed, updateNow); - return this + return this; } /** * update position BY TELEPORTING */ setPosition(x: number, y: number, updateNow = true): SATPolygon { - this.pos.x = x - this.pos.y = y - this.markAsDirty(updateNow) + this.pos.x = x; + this.pos.y = y; + this.markAsDirty(updateNow); - return this + return this; } /** * update scale */ setScale(x: number, y: number = x, updateNow = true): SATPolygon { - this.scaleVector.x = Math.abs(x) - this.scaleVector.y = Math.abs(y) + this.scaleVector.x = Math.abs(x); + this.scaleVector.y = Math.abs(y); super.setPoints( map(this.points, (point: SATVector, index: number) => { - point.x = this.pointsBackup[index].x * this.scaleVector.x - point.y = this.pointsBackup[index].y * this.scaleVector.y + point.x = this.pointsBackup[index].x * this.scaleVector.x; + point.y = this.pointsBackup[index].y * this.scaleVector.y; - return point - }) - ) + return point; + }), + ); - this.markAsDirty(updateNow) + this.markAsDirty(updateNow); - return this + return this; } setAngle(angle: number, updateNow = true): SATPolygon { - super.setAngle(angle) - this.markAsDirty(updateNow) + super.setAngle(angle); + this.markAsDirty(updateNow); - return this + return this; } setOffset(offset: SATVector, updateNow = true): SATPolygon { - super.setOffset(offset) - this.markAsDirty(updateNow) + super.setOffset(offset); + this.markAsDirty(updateNow); - return this + return this; } /** * get body bounding box, without padding */ getAABBAsBBox(): BBox { - const { pos, w, h } = (this as unknown as GetAABBAsBox).getAABBAsBox() + const { pos, w, h } = (this as unknown as GetAABBAsBox).getAABBAsBox(); return { minX: pos.x, minY: pos.y, maxX: pos.x + w, - maxY: pos.y + h - } + maxY: pos.y + h, + }; } /** * Draws exact collider on canvas context */ draw(context: CanvasRenderingContext2D) { - drawPolygon(context, this, this.isTrigger) + drawPolygon(context, this, this.isTrigger); } /** * Draws Bounding Box on canvas context */ drawBVH(context: CanvasRenderingContext2D) { - drawBVH(context, this) + drawBVH(context, this); } /** @@ -328,58 +328,58 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { */ getCentroidWithoutRotation(): Vector { // keep angle copy - const angle = this.angle + const angle = this.angle; if (angle) { // reset angle for get centroid - this.setAngle(0) + this.setAngle(0); // get centroid - const centroid = this.getCentroid() + const centroid = this.getCentroid(); // revert angle change - this.setAngle(angle) + this.setAngle(angle); - return centroid + return centroid; } - return this.getCentroid() + return this.getCentroid(); } /** * sets polygon points to new array of vectors */ setPoints(points: SATVector[]): Polygon { - super.setPoints(points) - this.updateIsConvex() - this.pointsBackup = clonePointsArray(points) + super.setPoints(points); + this.updateIsConvex(); + this.pointsBackup = clonePointsArray(points); - return this + return this; } /** * translates polygon points in x, y direction */ translate(x: number, y: number): Polygon { - super.translate(x, y) - this.pointsBackup = clonePointsArray(this.points) + super.translate(x, y); + this.pointsBackup = clonePointsArray(this.points); - return this + return this; } /** * rotates polygon points by angle, in radians */ rotate(angle: number): Polygon { - super.rotate(angle) - this.pointsBackup = clonePointsArray(this.points) + super.rotate(angle); + this.pointsBackup = clonePointsArray(this.points); - return this + return this; } /** * if true, polygon is not an invalid, self-crossing polygon */ isSimple(): boolean { - return isSimple(this.calcPoints.map(mapVectorToArray)) + return isSimple(this.calcPoints.map(mapVectorToArray)); } /** @@ -387,20 +387,20 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { */ updateBody(updateNow = this.dirty): void { if (updateNow) { - this.updateConvexPolygonPositions() - this.system?.insert(this) - this.dirty = false + this.updateConvexPolygonPositions(); + this.system?.insert(this); + this.dirty = false; } } protected retranslate(isCentered = this.isCentered): void { - const centroid = this.getCentroidWithoutRotation() + const centroid = this.getCentroidWithoutRotation(); if (centroid.x || centroid.y) { - const x = centroid.x * (isCentered ? 1 : -1) - const y = centroid.y * (isCentered ? 1 : -1) + const x = centroid.x * (isCentered ? 1 : -1); + const y = centroid.y * (isCentered ? 1 : -1); - this.translate(-x, -y) + this.translate(-x, -y); } } @@ -409,9 +409,9 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { */ protected markAsDirty(updateNow = false): void { if (updateNow) { - this.updateBody(true) + this.updateBody(true); } else { - this.dirty = true + this.dirty = true; } } @@ -421,17 +421,17 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { */ protected updateConvexPolygonPositions() { if (this.isConvex || !this.convexPolygons) { - return + return; } forEach(this.convexPolygons, (polygon: SATPolygon) => { - polygon.pos.x = this.pos.x - polygon.pos.y = this.pos.y + polygon.pos.x = this.pos.x; + polygon.pos.y = this.pos.y; if (polygon.angle !== this.angle) { // Must use setAngle to recalculate the points of the Polygon - polygon.setAngle(this.angle) + polygon.setAngle(this.angle); } - }) + }); } /** @@ -442,44 +442,44 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { (this.typeGroup && this.typeGroup !== BodyGroup.Polygon) || this.points.length < 4 ) { - return [] + return []; } - const points = map(this.calcPoints, mapVectorToArray) + const points = map(this.calcPoints, mapVectorToArray); - return quickDecomp(points) + return quickDecomp(points); } /** * updates convex polygons cache in body */ protected updateConvexPolygons( - convex: DecompPolygon[] = this.getConvex() + convex: DecompPolygon[] = this.getConvex(), ): void { if (this.isConvex) { - return + return; } if (!this.convexPolygons) { - this.convexPolygons = [] + this.convexPolygons = []; } forEach(convex, (points: DecompPolygon, index: number) => { // lazy create if (!this.convexPolygons[index]) { - this.convexPolygons[index] = new SATPolygon() + this.convexPolygons[index] = new SATPolygon(); } - this.convexPolygons[index].pos.x = this.pos.x - this.convexPolygons[index].pos.y = this.pos.y - this.convexPolygons[index].angle = this.angle + this.convexPolygons[index].pos.x = this.pos.x; + this.convexPolygons[index].pos.y = this.pos.y; + this.convexPolygons[index].angle = this.angle; this.convexPolygons[index].setPoints( - ensurePolygonPoints(map(points, mapArrayToVector)) - ) - }) + ensurePolygonPoints(map(points, mapArrayToVector)), + ); + }); // trim array length - this.convexPolygons.length = convex.length + this.convexPolygons.length = convex.length; } /** @@ -487,9 +487,9 @@ export class Polygon extends SATPolygon implements BBox, BodyProps { */ protected updateIsConvex(): void { // all other types other than polygon are always convex - const convex = this.getConvex() + const convex = this.getConvex(); // everything with empty array or one element array - this.isConvex = convex.length <= 1 - this.updateConvexPolygons(convex) + this.isConvex = convex.length <= 1; + this.updateConvexPolygons(convex); } } diff --git a/src/demo/stress.js b/src/demo/stress.js index db7a3446..7255bdb0 100644 --- a/src/demo/stress.js +++ b/src/demo/stress.js @@ -8,8 +8,12 @@ function random(min, max) { return Math.floor(seededRandom() * max) + min; } +function getDefaultCount() { + return Math.floor(Math.min(2000, Math.hypot(width, height))); +} + class Stress { - constructor(count = 2000) { + constructor(count = getDefaultCount()) { this.size = Math.sqrt((width * height) / (count * 50)); this.physics = new System(5); diff --git a/src/external/quickselect.js b/src/external/quickselect.js index 64b48249..21837fac 100644 --- a/src/external/quickselect.js +++ b/src/external/quickselect.js @@ -1,4 +1,3 @@ - /** * Rearranges items so that all items in the [left, k] are the smallest. * The k-th element will have the (k - left + 1)-th smallest value in [left, right]. @@ -10,45 +9,51 @@ * @param {number} [right=arr.length-1] right index * @param {(a: T, b: T) => number} [compare = (a, b) => a - b] compare function */ -export default function quickselect(arr, k, left = 0, right = arr.length - 1, compare = defaultCompare) { - - while (right > left) { - if (right - left > 600) { - const n = right - left + 1; - const m = k - left + 1; - const z = Math.log(n); - const s = 0.5 * Math.exp(2 * z / 3); - const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); - const newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); - const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); - quickselect(arr, k, newLeft, newRight, compare); - } - - const t = arr[k]; - let i = left; - /** @type {number} */ - let j = right; +export default function quickselect( + arr, + k, + left = 0, + right = arr.length - 1, + compare = defaultCompare, +) { + while (right > left) { + if (right - left > 600) { + const n = right - left + 1; + const m = k - left + 1; + const z = Math.log(n); + const s = 0.5 * Math.exp((2 * z) / 3); + const sd = + 0.5 * Math.sqrt((z * s * (n - s)) / n) * (m - n / 2 < 0 ? -1 : 1); + const newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd)); + const newRight = Math.min(right, Math.floor(k + ((n - m) * s) / n + sd)); + quickselect(arr, k, newLeft, newRight, compare); + } - swap(arr, left, k); - if (compare(arr[right], t) > 0) swap(arr, left, right); + const t = arr[k]; + let i = left; + /** @type {number} */ + let j = right; - while (i < j) { - swap(arr, i, j); - i++; - j--; - while (compare(arr[i], t) < 0) i++; - while (compare(arr[j], t) > 0) j--; - } + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); - if (compare(arr[left], t) === 0) swap(arr, left, j); - else { - j++; - swap(arr, j, right); - } + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } - if (j <= k) left = j + 1; - if (k <= j) right = j - 1; + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); } + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } } /** @@ -58,9 +63,9 @@ export default function quickselect(arr, k, left = 0, right = arr.length - 1, co * @param {number} j */ function swap(arr, i, j) { - const tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; + const tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; } /** @@ -70,5 +75,5 @@ function swap(arr, i, j) { * @returns {number} */ function defaultCompare(a, b) { - return a < b ? -1 : a > b ? 1 : 0; + return a < b ? -1 : a > b ? 1 : 0; } diff --git a/src/external/rbush.js b/src/external/rbush.js index 3a532882..3a65becb 100644 --- a/src/external/rbush.js +++ b/src/external/rbush.js @@ -1,512 +1,521 @@ -import quickselect from './quickselect'; +import quickselect from "./quickselect"; export default class RBush { - constructor(maxEntries = 9) { - // max entries in a node is 9 by default; min node fill is 40% for best performance - this._maxEntries = Math.max(4, maxEntries); - this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); - this.clear(); - } - - all() { - return this._all(this.data, []); - } - - search(bbox) { - let node = this.data; - const result = []; - - if (!intersects(bbox, node)) return result; + constructor(maxEntries = 9) { + // max entries in a node is 9 by default; min node fill is 40% for best performance + this._maxEntries = Math.max(4, maxEntries); + this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); + this.clear(); + } - const toBBox = this.toBBox; - const nodesToSearch = []; + all() { + return this._all(this.data, []); + } - while (node) { - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - const childBBox = node.leaf ? toBBox(child) : child; - - if (intersects(bbox, childBBox)) { - if (node.leaf) result.push(child); - else if (contains(bbox, childBBox)) this._all(child, result); - else nodesToSearch.push(child); - } - } - node = nodesToSearch.pop(); - } - - return result; - } + search(bbox) { + let node = this.data; + const result = []; - collides(bbox) { - let node = this.data; + if (!intersects(bbox, node)) return result; - if (!intersects(bbox, node)) return false; + const toBBox = this.toBBox; + const nodesToSearch = []; - const nodesToSearch = []; - while (node) { - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - const childBBox = node.leaf ? this.toBBox(child) : child; + while (node) { + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const childBBox = node.leaf ? toBBox(child) : child; - if (intersects(bbox, childBBox)) { - if (node.leaf || contains(bbox, childBBox)) return true; - nodesToSearch.push(child); - } - } - node = nodesToSearch.pop(); + if (intersects(bbox, childBBox)) { + if (node.leaf) result.push(child); + else if (contains(bbox, childBBox)) this._all(child, result); + else nodesToSearch.push(child); } - - return false; + } + node = nodesToSearch.pop(); } - load(data) { - if (!(data && data.length)) return this; - - if (data.length < this._minEntries) { - for (let i = 0; i < data.length; i++) { - this.insert(data[i]); - } - return this; - } - - // recursively build the tree with the given data from scratch using OMT algorithm - let node = this._build(data.slice(), 0, data.length - 1, 0); + return result; + } - if (!this.data.children.length) { - // save as is if tree is empty - this.data = node; + collides(bbox) { + let node = this.data; - } else if (this.data.height === node.height) { - // split root if trees have the same height - this._splitRoot(this.data, node); + if (!intersects(bbox, node)) return false; - } else { - if (this.data.height < node.height) { - // swap trees if inserted one is bigger - const tmpNode = this.data; - this.data = node; - node = tmpNode; - } + const nodesToSearch = []; + while (node) { + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const childBBox = node.leaf ? this.toBBox(child) : child; - // insert the small tree into the large tree at appropriate level - this._insert(node, this.data.height - node.height - 1, true); + if (intersects(bbox, childBBox)) { + if (node.leaf || contains(bbox, childBBox)) return true; + nodesToSearch.push(child); } - - return this; + } + node = nodesToSearch.pop(); } - insert(item) { - if (item) this._insert(item, this.data.height - 1); - return this; - } + return false; + } - clear() { - this.data = createNode([]); - return this; - } - - remove(item, equalsFn) { - if (!item) return this; - - let node = this.data; - const bbox = this.toBBox(item); - const path = []; - const indexes = []; - let i, parent, goingUp; - - // depth-first iterative tree traversal - while (node || path.length) { - - if (!node) { // go up - node = path.pop(); - parent = path[path.length - 1]; - i = indexes.pop(); - goingUp = true; - } - - if (node.leaf) { // check current node - const index = findItem(item, node.children, equalsFn); - - if (index !== -1) { - // item found, remove the item and condense tree upwards - node.children.splice(index, 1); - path.push(node); - this._condense(path); - return this; - } - } - - if (!goingUp && !node.leaf && contains(node, bbox)) { // go down - path.push(node); - indexes.push(i); - i = 0; - parent = node; - node = node.children[0]; - - } else if (parent) { // go right - i++; - node = parent.children[i]; - goingUp = false; - - } else node = null; // nothing found - } + load(data) { + if (!(data && data.length)) return this; - return this; + if (data.length < this._minEntries) { + for (let i = 0; i < data.length; i++) { + this.insert(data[i]); + } + return this; } - toBBox(item) { return item; } - - compareMinX(a, b) { return a.minX - b.minX; } - compareMinY(a, b) { return a.minY - b.minY; } - - toJSON() { return this.data; } - - fromJSON(data) { - this.data = data; - return this; + // recursively build the tree with the given data from scratch using OMT algorithm + let node = this._build(data.slice(), 0, data.length - 1, 0); + + if (!this.data.children.length) { + // save as is if tree is empty + this.data = node; + } else if (this.data.height === node.height) { + // split root if trees have the same height + this._splitRoot(this.data, node); + } else { + if (this.data.height < node.height) { + // swap trees if inserted one is bigger + const tmpNode = this.data; + this.data = node; + node = tmpNode; + } + + // insert the small tree into the large tree at appropriate level + this._insert(node, this.data.height - node.height - 1, true); } - _all(node, result) { - const nodesToSearch = []; - while (node) { - if (node.leaf) result.push(...node.children); - else nodesToSearch.push(...node.children); - - node = nodesToSearch.pop(); + return this; + } + + insert(item) { + if (item) this._insert(item, this.data.height - 1); + return this; + } + + clear() { + this.data = createNode([]); + return this; + } + + remove(item, equalsFn) { + if (!item) return this; + + let node = this.data; + const bbox = this.toBBox(item); + const path = []; + const indexes = []; + let i, parent, goingUp; + + // depth-first iterative tree traversal + while (node || path.length) { + if (!node) { + // go up + node = path.pop(); + parent = path[path.length - 1]; + i = indexes.pop(); + goingUp = true; + } + + if (node.leaf) { + // check current node + const index = findItem(item, node.children, equalsFn); + + if (index !== -1) { + // item found, remove the item and condense tree upwards + node.children.splice(index, 1); + path.push(node); + this._condense(path); + return this; } - return result; + } + + if (!goingUp && !node.leaf && contains(node, bbox)) { + // go down + path.push(node); + indexes.push(i); + i = 0; + parent = node; + node = node.children[0]; + } else if (parent) { + // go right + i++; + node = parent.children[i]; + goingUp = false; + } else node = null; // nothing found } - _build(items, left, right, height) { - - const N = right - left + 1; - let M = this._maxEntries; - let node; - - if (N <= M) { - // reached leaf level; return leaf - node = createNode(items.slice(left, right + 1)); - calcBBox(node, this.toBBox); - return node; - } - - if (!height) { - // target height of the bulk-loaded tree - height = Math.ceil(Math.log(N) / Math.log(M)); - - // target number of root entries to maximize storage utilization - M = Math.ceil(N / Math.pow(M, height - 1)); - } - - node = createNode([]); - node.leaf = false; - node.height = height; - - // split the items into M mostly square tiles - - const N2 = Math.ceil(N / M); - const N1 = N2 * Math.ceil(Math.sqrt(M)); + return this; + } - multiSelect(items, left, right, N1, this.compareMinX); + toBBox(item) { + return item; + } - for (let i = left; i <= right; i += N1) { + compareMinX(a, b) { + return a.minX - b.minX; + } + compareMinY(a, b) { + return a.minY - b.minY; + } - const right2 = Math.min(i + N1 - 1, right); + toJSON() { + return this.data; + } - multiSelect(items, i, right2, N2, this.compareMinY); + fromJSON(data) { + this.data = data; + return this; + } - for (let j = i; j <= right2; j += N2) { + _all(node, result) { + const nodesToSearch = []; + while (node) { + if (node.leaf) result.push(...node.children); + else nodesToSearch.push(...node.children); - const right3 = Math.min(j + N2 - 1, right2); - - // pack each entry recursively - node.children.push(this._build(items, j, right3, height - 1)); - } - } - - calcBBox(node, this.toBBox); - - return node; + node = nodesToSearch.pop(); } - - _chooseSubtree(bbox, node, level, path) { - while (true) { - path.push(node); - - if (node.leaf || path.length - 1 === level) break; - - let minArea = Infinity; - let minEnlargement = Infinity; - let targetNode; - - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - const area = bboxArea(child); - const enlargement = enlargedArea(bbox, child) - area; - - // choose entry with the least area enlargement - if (enlargement < minEnlargement) { - minEnlargement = enlargement; - minArea = area < minArea ? area : minArea; - targetNode = child; - - } else if (enlargement === minEnlargement) { - // otherwise choose one with the smallest area - if (area < minArea) { - minArea = area; - targetNode = child; - } - } - } - - node = targetNode || node.children[0]; - } - - return node; + return result; + } + + _build(items, left, right, height) { + const N = right - left + 1; + let M = this._maxEntries; + let node; + + if (N <= M) { + // reached leaf level; return leaf + node = createNode(items.slice(left, right + 1)); + calcBBox(node, this.toBBox); + return node; } - _insert(item, level, isNode) { - const bbox = isNode ? item : this.toBBox(item); - const insertPath = []; - - // find the best node for accommodating the item, saving all nodes along the path too - const node = this._chooseSubtree(bbox, this.data, level, insertPath); + if (!height) { + // target height of the bulk-loaded tree + height = Math.ceil(Math.log(N) / Math.log(M)); - // put the item into the node - node.children.push(item); - extend(node, bbox); - - // split on node overflow; propagate upwards if necessary - while (level >= 0) { - if (insertPath[level].children.length > this._maxEntries) { - this._split(insertPath, level); - level--; - } else break; - } - - // adjust bboxes along the insertion path - this._adjustParentBBoxes(bbox, insertPath, level); + // target number of root entries to maximize storage utilization + M = Math.ceil(N / Math.pow(M, height - 1)); } - // split overflowed node into two - _split(insertPath, level) { - const node = insertPath[level]; - const M = node.children.length; - const m = this._minEntries; + node = createNode([]); + node.leaf = false; + node.height = height; - this._chooseSplitAxis(node, m, M); + // split the items into M mostly square tiles - const splitIndex = this._chooseSplitIndex(node, m, M); + const N2 = Math.ceil(N / M); + const N1 = N2 * Math.ceil(Math.sqrt(M)); - const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); - newNode.height = node.height; - newNode.leaf = node.leaf; + multiSelect(items, left, right, N1, this.compareMinX); - calcBBox(node, this.toBBox); - calcBBox(newNode, this.toBBox); + for (let i = left; i <= right; i += N1) { + const right2 = Math.min(i + N1 - 1, right); - if (level) insertPath[level - 1].children.push(newNode); - else this._splitRoot(node, newNode); - } + multiSelect(items, i, right2, N2, this.compareMinY); - _splitRoot(node, newNode) { - // split root node - this.data = createNode([node, newNode]); - this.data.height = node.height + 1; - this.data.leaf = false; - calcBBox(this.data, this.toBBox); + for (let j = i; j <= right2; j += N2) { + const right3 = Math.min(j + N2 - 1, right2); + + // pack each entry recursively + node.children.push(this._build(items, j, right3, height - 1)); + } } - _chooseSplitIndex(node, m, M) { - let index; - let minOverlap = Infinity; - let minArea = Infinity; + calcBBox(node, this.toBBox); - for (let i = m; i <= M - m; i++) { - const bbox1 = distBBox(node, 0, i, this.toBBox); - const bbox2 = distBBox(node, i, M, this.toBBox); + return node; + } - const overlap = intersectionArea(bbox1, bbox2); - const area = bboxArea(bbox1) + bboxArea(bbox2); + _chooseSubtree(bbox, node, level, path) { + while (true) { + path.push(node); - // choose distribution with minimum overlap - if (overlap < minOverlap) { - minOverlap = overlap; - index = i; + if (node.leaf || path.length - 1 === level) break; - minArea = area < minArea ? area : minArea; + let minArea = Infinity; + let minEnlargement = Infinity; + let targetNode; - } else if (overlap === minOverlap) { - // otherwise choose distribution with minimum area - if (area < minArea) { - minArea = area; - index = i; - } - } + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const area = bboxArea(child); + const enlargement = enlargedArea(bbox, child) - area; + + // choose entry with the least area enlargement + if (enlargement < minEnlargement) { + minEnlargement = enlargement; + minArea = area < minArea ? area : minArea; + targetNode = child; + } else if (enlargement === minEnlargement) { + // otherwise choose one with the smallest area + if (area < minArea) { + minArea = area; + targetNode = child; + } } + } - return index || M - m; + node = targetNode || node.children[0]; } - // sorts node children by the best axis for split - _chooseSplitAxis(node, m, M) { - const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX; - const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY; - const xMargin = this._allDistMargin(node, m, M, compareMinX); - const yMargin = this._allDistMargin(node, m, M, compareMinY); + return node; + } - // if total distributions margin value is minimal for x, sort by minX, - // otherwise it's already sorted by minY - if (xMargin < yMargin) node.children.sort(compareMinX); - } + _insert(item, level, isNode) { + const bbox = isNode ? item : this.toBBox(item); + const insertPath = []; - // total margin of all possible split distributions where each node is at least m full - _allDistMargin(node, m, M, compare) { - node.children.sort(compare); + // find the best node for accommodating the item, saving all nodes along the path too + const node = this._chooseSubtree(bbox, this.data, level, insertPath); - const toBBox = this.toBBox; - const leftBBox = distBBox(node, 0, m, toBBox); - const rightBBox = distBBox(node, M - m, M, toBBox); - let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); + // put the item into the node + node.children.push(item); + extend(node, bbox); - for (let i = m; i < M - m; i++) { - const child = node.children[i]; - extend(leftBBox, node.leaf ? toBBox(child) : child); - margin += bboxMargin(leftBBox); - } + // split on node overflow; propagate upwards if necessary + while (level >= 0) { + if (insertPath[level].children.length > this._maxEntries) { + this._split(insertPath, level); + level--; + } else break; + } - for (let i = M - m - 1; i >= m; i--) { - const child = node.children[i]; - extend(rightBBox, node.leaf ? toBBox(child) : child); - margin += bboxMargin(rightBBox); + // adjust bboxes along the insertion path + this._adjustParentBBoxes(bbox, insertPath, level); + } + + // split overflowed node into two + _split(insertPath, level) { + const node = insertPath[level]; + const M = node.children.length; + const m = this._minEntries; + + this._chooseSplitAxis(node, m, M); + + const splitIndex = this._chooseSplitIndex(node, m, M); + + const newNode = createNode( + node.children.splice(splitIndex, node.children.length - splitIndex), + ); + newNode.height = node.height; + newNode.leaf = node.leaf; + + calcBBox(node, this.toBBox); + calcBBox(newNode, this.toBBox); + + if (level) insertPath[level - 1].children.push(newNode); + else this._splitRoot(node, newNode); + } + + _splitRoot(node, newNode) { + // split root node + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; + calcBBox(this.data, this.toBBox); + } + + _chooseSplitIndex(node, m, M) { + let index; + let minOverlap = Infinity; + let minArea = Infinity; + + for (let i = m; i <= M - m; i++) { + const bbox1 = distBBox(node, 0, i, this.toBBox); + const bbox2 = distBBox(node, i, M, this.toBBox); + + const overlap = intersectionArea(bbox1, bbox2); + const area = bboxArea(bbox1) + bboxArea(bbox2); + + // choose distribution with minimum overlap + if (overlap < minOverlap) { + minOverlap = overlap; + index = i; + + minArea = area < minArea ? area : minArea; + } else if (overlap === minOverlap) { + // otherwise choose distribution with minimum area + if (area < minArea) { + minArea = area; + index = i; } - - return margin; + } } - _adjustParentBBoxes(bbox, path, level) { - // adjust bboxes along the given tree path - for (let i = level; i >= 0; i--) { - extend(path[i], bbox); - } + return index || M - m; + } + + // sorts node children by the best axis for split + _chooseSplitAxis(node, m, M) { + const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX; + const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY; + const xMargin = this._allDistMargin(node, m, M, compareMinX); + const yMargin = this._allDistMargin(node, m, M, compareMinY); + + // if total distributions margin value is minimal for x, sort by minX, + // otherwise it's already sorted by minY + if (xMargin < yMargin) node.children.sort(compareMinX); + } + + // total margin of all possible split distributions where each node is at least m full + _allDistMargin(node, m, M, compare) { + node.children.sort(compare); + + const toBBox = this.toBBox; + const leftBBox = distBBox(node, 0, m, toBBox); + const rightBBox = distBBox(node, M - m, M, toBBox); + let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); + + for (let i = m; i < M - m; i++) { + const child = node.children[i]; + extend(leftBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(leftBBox); } - _condense(path) { - // go through the path, removing empty nodes and updating bboxes - for (let i = path.length - 1, siblings; i >= 0; i--) { - if (path[i].children.length === 0) { - if (i > 0) { - siblings = path[i - 1].children; - siblings.splice(siblings.indexOf(path[i]), 1); + for (let i = M - m - 1; i >= m; i--) { + const child = node.children[i]; + extend(rightBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(rightBBox); + } - } else this.clear(); + return margin; + } - } else calcBBox(path[i], this.toBBox); - } + _adjustParentBBoxes(bbox, path, level) { + // adjust bboxes along the given tree path + for (let i = level; i >= 0; i--) { + extend(path[i], bbox); + } + } + + _condense(path) { + // go through the path, removing empty nodes and updating bboxes + for (let i = path.length - 1, siblings; i >= 0; i--) { + if (path[i].children.length === 0) { + if (i > 0) { + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); + } else this.clear(); + } else calcBBox(path[i], this.toBBox); } + } } function findItem(item, items, equalsFn) { - if (!equalsFn) return items.indexOf(item); + if (!equalsFn) return items.indexOf(item); - for (let i = 0; i < items.length; i++) { - if (equalsFn(item, items[i])) return i; - } - return -1; + for (let i = 0; i < items.length; i++) { + if (equalsFn(item, items[i])) return i; + } + return -1; } // calculate node's bbox from bboxes of its children function calcBBox(node, toBBox) { - distBBox(node, 0, node.children.length, toBBox, node); + distBBox(node, 0, node.children.length, toBBox, node); } // min bounding rectangle of node children from k to p-1 function distBBox(node, k, p, toBBox, destNode) { - if (!destNode) destNode = createNode(null); - destNode.minX = Infinity; - destNode.minY = Infinity; - destNode.maxX = -Infinity; - destNode.maxY = -Infinity; - - for (let i = k; i < p; i++) { - const child = node.children[i]; - extend(destNode, node.leaf ? toBBox(child) : child); - } - - return destNode; + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; + + for (let i = k; i < p; i++) { + const child = node.children[i]; + extend(destNode, node.leaf ? toBBox(child) : child); + } + + return destNode; } function extend(a, b) { - a.minX = Math.min(a.minX, b.minX); - a.minY = Math.min(a.minY, b.minY); - a.maxX = Math.max(a.maxX, b.maxX); - a.maxY = Math.max(a.maxY, b.maxY); - return a; + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); + return a; } -function compareNodeMinX(a, b) { return a.minX - b.minX; } -function compareNodeMinY(a, b) { return a.minY - b.minY; } +function compareNodeMinX(a, b) { + return a.minX - b.minX; +} +function compareNodeMinY(a, b) { + return a.minY - b.minY; +} -function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } -function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } +function bboxArea(a) { + return (a.maxX - a.minX) * (a.maxY - a.minY); +} +function bboxMargin(a) { + return a.maxX - a.minX + (a.maxY - a.minY); +} function enlargedArea(a, b) { - return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * - (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); + return ( + (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)) + ); } function intersectionArea(a, b) { - const minX = Math.max(a.minX, b.minX); - const minY = Math.max(a.minY, b.minY); - const maxX = Math.min(a.maxX, b.maxX); - const maxY = Math.min(a.maxY, b.maxY); + const minX = Math.max(a.minX, b.minX); + const minY = Math.max(a.minY, b.minY); + const maxX = Math.min(a.maxX, b.maxX); + const maxY = Math.min(a.maxY, b.maxY); - return Math.max(0, maxX - minX) * - Math.max(0, maxY - minY); + return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); } function contains(a, b) { - return a.minX <= b.minX && - a.minY <= b.minY && - b.maxX <= a.maxX && - b.maxY <= a.maxY; + return ( + a.minX <= b.minX && a.minY <= b.minY && b.maxX <= a.maxX && b.maxY <= a.maxY + ); } function intersects(a, b) { - return b.minX <= a.maxX && - b.minY <= a.maxY && - b.maxX >= a.minX && - b.maxY >= a.minY; + return ( + b.minX <= a.maxX && b.minY <= a.maxY && b.maxX >= a.minX && b.maxY >= a.minY + ); } function createNode(children) { - return { - children, - height: 1, - leaf: true, - minX: Infinity, - minY: Infinity, - maxX: -Infinity, - maxY: -Infinity - }; + return { + children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity, + }; } // sort an array so that items come in groups of n unsorted items, with groups sorted between each other; // combines selection algorithm with binary divide & conquer approach function multiSelect(arr, left, right, n, compare) { - const stack = [left, right]; + const stack = [left, right]; - while (stack.length) { - right = stack.pop(); - left = stack.pop(); + while (stack.length) { + right = stack.pop(); + left = stack.pop(); - if (right - left <= n) continue; + if (right - left <= n) continue; - const mid = left + Math.ceil((right - left) / n / 2) * n; - quickselect(arr, mid, left, right, compare); + const mid = left + Math.ceil((right - left) / n / 2) * n; + quickselect(arr, mid, left, right, compare); - stack.push(left, mid, mid, right); - } + stack.push(left, mid, mid, right); + } } diff --git a/src/index.ts b/src/index.ts index e94c3329..19201261 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ -export * from "./model" -export * from "./bodies/circle" -export * from "./bodies/ellipse" -export * from "./bodies/polygon" -export * from "./bodies/box" -export * from "./bodies/point" -export * from "./bodies/line" -export * from "./system" -export * from "./utils" -export * from "./intersect" +export * from "./model"; +export * from "./bodies/circle"; +export * from "./bodies/ellipse"; +export * from "./bodies/polygon"; +export * from "./bodies/box"; +export * from "./bodies/point"; +export * from "./bodies/line"; +export * from "./system"; +export * from "./utils"; +export * from "./intersect"; diff --git a/src/intersect.ts b/src/intersect.ts index 849c4351..ac7987bc 100644 --- a/src/intersect.ts +++ b/src/intersect.ts @@ -1,53 +1,53 @@ -import { Body, BodyGroup, SATPolygon, SATVector, Vector } from "./model" -import { every, forEach, map, some } from "./optimized" -import { pointInCircle, pointInPolygon as pointInConvexPolygon } from "sat" +import { Body, BodyGroup, SATPolygon, SATVector, Vector } from "./model"; +import { every, forEach, map, some } from "./optimized"; +import { pointInCircle, pointInPolygon as pointInConvexPolygon } from "sat"; -import { Circle } from "./bodies/circle" -import { Line } from "./bodies/line" -import { Point } from "./bodies/point" -import { Polygon } from "./bodies/polygon" +import { Circle } from "./bodies/circle"; +import { Line } from "./bodies/line"; +import { Point } from "./bodies/point"; +import { Polygon } from "./bodies/polygon"; /** * replace body with array of related convex polygons */ export function ensureConvex( - body: TBody + body: TBody, ): (TBody | SATPolygon)[] { if (body.isConvex || body.typeGroup !== BodyGroup.Polygon) { - return [body] + return [body]; } - return body.convexPolygons + return body.convexPolygons; } export function polygonInCircle( polygon: Polygon, - circle: Pick + circle: Pick, ): boolean { - return every(polygon.calcPoints,p => + return every(polygon.calcPoints, (p) => pointInCircle( { x: p.x + polygon.pos.x, y: p.y + polygon.pos.y } as SATVector, - circle - ) - ) + circle, + ), + ); } export function pointInPolygon(point: Vector, polygon: Polygon): boolean { - return some(ensureConvex(polygon),convex => - pointInConvexPolygon(point as SATVector, convex) - ) + return some(ensureConvex(polygon), (convex) => + pointInConvexPolygon(point as SATVector, convex), + ); } export function polygonInPolygon( polygonA: Polygon, - polygonB: Polygon + polygonB: Polygon, ): boolean { - return every(polygonA.calcPoints,point => + return every(polygonA.calcPoints, (point) => pointInPolygon( { x: point.x + polygonA.pos.x, y: point.y + polygonA.pos.y }, - polygonB - ) - ) + polygonB, + ), + ); } /** @@ -55,13 +55,13 @@ export function polygonInPolygon( */ export function pointOnCircle( point: Vector, - circle: Pick + circle: Pick, ): boolean { return ( (point.x - circle.pos.x) * (point.x - circle.pos.x) + (point.y - circle.pos.y) * (point.y - circle.pos.y) === circle.r * circle.r - ) + ); } /** @@ -69,17 +69,17 @@ export function pointOnCircle( */ export function circleInCircle( bodyA: Pick, - bodyB: Pick + bodyB: Pick, ) { - const x1 = bodyA.pos.x - const y1 = bodyA.pos.y - const x2 = bodyB.pos.x - const y2 = bodyB.pos.y - const r1 = bodyA.r - const r2 = bodyB.r - const distSq = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) - - return distSq + r2 === r1 || distSq + r2 < r1 + const x1 = bodyA.pos.x; + const y1 = bodyA.pos.y; + const x2 = bodyB.pos.x; + const y2 = bodyB.pos.y; + const r1 = bodyA.r; + const r2 = bodyB.r; + const distSq = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); + + return distSq + r2 === r1 || distSq + r2 < r1; } /** @@ -87,31 +87,31 @@ export function circleInCircle( */ export function circleInPolygon( circle: Pick, - polygon: Polygon + polygon: Polygon, ): boolean { // Circle with radius 0 isn't a circle if (circle.r === 0) { - return false + return false; } // If the center of the circle is not within the polygon, // then the circle may overlap, but it'll never be "contained" // so return false if (!pointInPolygon(circle.pos, polygon)) { - return false + return false; } // Necessary add polygon pos to points const points = map(polygon.calcPoints, ({ x, y }: SATVector) => ({ x: x + polygon.pos.x, - y: y + polygon.pos.y - })) as SATVector[] + y: y + polygon.pos.y, + })) as SATVector[]; // If the center of the circle is within the polygon, // the circle is not outside of the polygon completely. // so return false. - if (some(points,point => pointInCircle(point, circle))) { - return false + if (some(points, (point) => pointInCircle(point, circle))) { + return false; } // If any line-segment of the polygon intersects the circle, @@ -121,15 +121,15 @@ export function circleInPolygon( some(points, (end, index) => { const start: Vector = index ? points[index - 1] - : points[points.length - 1] + : points[points.length - 1]; - return intersectLineCircle({ start, end }, circle).length > 0 + return intersectLineCircle({ start, end }, circle).length > 0; }) ) { - return false + return false; } - return true + return true; } /** @@ -137,35 +137,36 @@ export function circleInPolygon( */ export function circleOutsidePolygon( circle: Pick, - polygon: Polygon + polygon: Polygon, ): boolean { // Circle with radius 0 isn't a circle if (circle.r === 0) { - return false + return false; } // If the center of the circle is within the polygon, // the circle is not outside of the polygon completely. // so return false. if (pointInPolygon(circle.pos, polygon)) { - return false + return false; } // Necessary add polygon pos to points const points = map(polygon.calcPoints, ({ x, y }: SATVector) => ({ x: x + polygon.pos.x, - y: y + polygon.pos.y - })) as SATVector[] + y: y + polygon.pos.y, + })) as SATVector[]; // If the center of the circle is within the polygon, // the circle is not outside of the polygon completely. // so return false. if ( some( - points,point => pointInCircle(point, circle) || pointOnCircle(point, circle) + points, + (point) => pointInCircle(point, circle) || pointOnCircle(point, circle), ) ) { - return false + return false; } // If any line-segment of the polygon intersects the circle, @@ -175,15 +176,15 @@ export function circleOutsidePolygon( some(points, (end, index) => { const start: Vector = index ? points[index - 1] - : points[points.length - 1] + : points[points.length - 1]; - return intersectLineCircle({ start, end }, circle).length > 0 + return intersectLineCircle({ start, end }, circle).length > 0; }) ) { - return false + return false; } - return true + return true; } /** @@ -191,44 +192,44 @@ export function circleOutsidePolygon( */ export function intersectLineCircle( line: Pick, - { pos, r }: Pick + { pos, r }: Pick, ): Vector[] { - const v1 = { x: line.end.x - line.start.x, y: line.end.y - line.start.y } - const v2 = { x: line.start.x - pos.x, y: line.start.y - pos.y } - const b = (v1.x * v2.x + v1.y * v2.y) * -2 - const c = (v1.x * v1.x + v1.y * v1.y) * 2 - const d = Math.sqrt(b * b - (v2.x * v2.x + v2.y * v2.y - r * r) * c * 2) + const v1 = { x: line.end.x - line.start.x, y: line.end.y - line.start.y }; + const v2 = { x: line.start.x - pos.x, y: line.start.y - pos.y }; + const b = (v1.x * v2.x + v1.y * v2.y) * -2; + const c = (v1.x * v1.x + v1.y * v1.y) * 2; + const d = Math.sqrt(b * b - (v2.x * v2.x + v2.y * v2.y - r * r) * c * 2); if (isNaN(d)) { // no intercept - return [] + return []; } - const u1 = (b - d) / c // these represent the unit distance of point one and two on the line - const u2 = (b + d) / c - const results: Vector[] = [] // return array + const u1 = (b - d) / c; // these represent the unit distance of point one and two on the line + const u2 = (b + d) / c; + const results: Vector[] = []; // return array if (u1 <= 1 && u1 >= 0) { // add point if on the line segment - results.push({ x: line.start.x + v1.x * u1, y: line.start.y + v1.y * u1 }) + results.push({ x: line.start.x + v1.x * u1, y: line.start.y + v1.y * u1 }); } if (u2 <= 1 && u2 >= 0) { // second add point if on the line segment - results.push({ x: line.start.x + v1.x * u2, y: line.start.y + v1.y * u2 }) + results.push({ x: line.start.x + v1.x * u2, y: line.start.y + v1.y * u2 }); } - return results + return results; } /** * helper for intersectLineLineFast */ function isTurn(point1: Vector, point2: Vector, point3: Vector) { - const A = (point3.x - point1.x) * (point2.y - point1.y) - const B = (point2.x - point1.x) * (point3.y - point1.y) + const A = (point3.x - point1.x) * (point2.y - point1.y); + const B = (point2.x - point1.x) * (point3.y - point1.y); - return A > B + Number.EPSILON ? 1 : A + Number.EPSILON < B ? -1 : 0 + return A > B + Number.EPSILON ? 1 : A + Number.EPSILON < B ? -1 : 0; } /** @@ -237,14 +238,14 @@ function isTurn(point1: Vector, point2: Vector, point3: Vector) { */ export function intersectLineLineFast( line1: Pick, - line2: Pick + line2: Pick, ): boolean { return ( isTurn(line1.start, line2.start, line2.end) !== isTurn(line1.end, line2.start, line2.end) && isTurn(line1.start, line1.end, line2.start) !== isTurn(line1.start, line1.end, line2.end) - ) + ); } /** @@ -253,54 +254,54 @@ export function intersectLineLineFast( */ export function intersectLineLine( line1: Pick, - line2: Pick + line2: Pick, ): Vector | null { - const dX: number = line1.end.x - line1.start.x - const dY: number = line1.end.y - line1.start.y + const dX: number = line1.end.x - line1.start.x; + const dY: number = line1.end.y - line1.start.y; const determinant: number = - dX * (line2.end.y - line2.start.y) - (line2.end.x - line2.start.x) * dY + dX * (line2.end.y - line2.start.y) - (line2.end.x - line2.start.x) * dY; if (determinant === 0) { - return null + return null; } const lambda: number = ((line2.end.y - line2.start.y) * (line2.end.x - line1.start.x) + (line2.start.x - line2.end.x) * (line2.end.y - line1.start.y)) / - determinant + determinant; const gamma: number = ((line1.start.y - line1.end.y) * (line2.end.x - line1.start.x) + dX * (line2.end.y - line1.start.y)) / - determinant + determinant; // check if there is an intersection if (!(lambda >= 0 && lambda <= 1) || !(gamma >= 0 && gamma <= 1)) { - return null + return null; } - return { x: line1.start.x + lambda * dX, y: line1.start.y + lambda * dY } + return { x: line1.start.x + lambda * dX, y: line1.start.y + lambda * dY }; } export function intersectLinePolygon(line: Line, polygon: Polygon): Vector[] { - const results: Vector[] = [] + const results: Vector[] = []; forEach(polygon.calcPoints, (to: Vector, index: number) => { const from: Vector = index ? polygon.calcPoints[index - 1] - : polygon.calcPoints[polygon.calcPoints.length - 1] + : polygon.calcPoints[polygon.calcPoints.length - 1]; const side = { start: { x: from.x + polygon.pos.x, y: from.y + polygon.pos.y }, - end: { x: to.x + polygon.pos.x, y: to.y + polygon.pos.y } - } + end: { x: to.x + polygon.pos.x, y: to.y + polygon.pos.y }, + }; - const hit = intersectLineLine(line, side) + const hit = intersectLineLine(line, side); if (hit) { - results.push(hit) + results.push(hit); } - }) + }); - return results + return results; } diff --git a/src/model.ts b/src/model.ts index 29dce49f..13e09682 100644 --- a/src/model.ts +++ b/src/model.ts @@ -2,36 +2,36 @@ import { Circle as SATCircle, Polygon as SATPolygon, Response, - Vector as SATVector -} from "sat" + Vector as SATVector, +} from "sat"; -import { System } from "./system" -import { Box } from "./bodies/box" -import { Circle } from "./bodies/circle" -import { Ellipse } from "./bodies/ellipse" -import { Line } from "./bodies/line" -import { Point } from "./bodies/point" -import { Polygon } from "./bodies/polygon" +import { System } from "./system"; +import { Box } from "./bodies/box"; +import { Circle } from "./bodies/circle"; +import { Ellipse } from "./bodies/ellipse"; +import { Line } from "./bodies/line"; +import { Point } from "./bodies/point"; +import { Polygon } from "./bodies/polygon"; // version 4.0.0 1=1 copy -import RBush from "./external/rbush" +import RBush from "./external/rbush"; export { Polygon as DecompPolygon, Point as DecompPoint, - isSimple -} from "poly-decomp-es" + isSimple, +} from "poly-decomp-es"; export interface BBox { - minX: number - minY: number - maxX: number - maxY: number + minX: number; + minY: number; + maxX: number; + maxY: number; } -export { RBush, Response, SATVector, SATPolygon, SATCircle } +export { RBush, Response, SATVector, SATPolygon, SATCircle }; -export type CollisionCallback = (response: Response) => boolean | void +export type CollisionCallback = (response: Response) => boolean | void; /** * types @@ -42,7 +42,7 @@ export enum BodyType { Polygon = "Polygon", Box = "Box", Line = "Line", - Point = "Point" + Point = "Point", } /** @@ -54,7 +54,7 @@ export enum BodyGroup { Polygon = 0b00001000, Box = 0b00000100, Line = 0b00000010, - Point = 0b00000001 + Point = 0b00000001, } /** @@ -62,20 +62,20 @@ export enum BodyGroup { */ export type Leaf = TBody & { children?: Leaf[]; -} +}; /** * rbush data */ export interface ChildrenData { - children: Leaf[] + children: Leaf[]; } /** * for use of private function of sat.js */ export interface Data { - data: ChildrenData + data: ChildrenData; } /** @@ -85,69 +85,69 @@ export interface BodyOptions { /** * system.separate() doesn't move this body */ - isStatic?: boolean + isStatic?: boolean; /** * system.separate() doesn't trigger collision of this body */ - isTrigger?: boolean + isTrigger?: boolean; /** * is body offset centered for rotation purpouses */ - isCentered?: boolean + isCentered?: boolean; /** * body angle in radians use deg2rad to convert */ - angle?: number + angle?: number; /** * BHV padding for bounding box, preventing costly updates */ - padding?: number + padding?: number; /** * group for collision filtering */ - group?: number + group?: number; } /** * system.raycast(from, to) result */ export interface RaycastHit { - point: Vector - body: TBody + point: Vector; + body: TBody; } /** * potential vector */ export interface PotentialVector { - x?: number - y?: number + x?: number; + y?: number; } /** * x, y vector */ export interface Vector extends PotentialVector { - x: number - y: number + x: number; + y: number; } /** * for use of private function of sat.js */ export interface GetAABBAsBox { - getAABBAsBox(): { pos: Vector; w: number; h: number } + getAABBAsBox(): { pos: Vector; w: number; h: number }; } /** * generic body union type */ -export type Body = Point | Line | Ellipse | Circle | Box | Polygon +export type Body = Point | Line | Ellipse | Circle | Box | Polygon; /** * each body contains those regardless of type @@ -156,101 +156,101 @@ export interface BodyProps extends Required { /** * type of body */ - readonly type: BodyType + readonly type: BodyType; /** * faster for comparision, inner, type of body as number */ - readonly typeGroup: BodyGroup + readonly typeGroup: BodyGroup; /** * flag to show is it a convex body or non convex polygon */ - isConvex: boolean + isConvex: boolean; /** * bounding box cache, without padding */ - bbox: BBox + bbox: BBox; /** * each body may have offset from center */ - offset: SATVector + offset: SATVector; /** * collisions system reference */ - system?: System + system?: System; /** * was the body modified and needs update in the next checkCollision */ - dirty: boolean + dirty: boolean; /** * scale getter (x) */ - get scaleX(): number + get scaleX(): number; /** * scale getter (y = x for Circle) */ - get scaleY(): number + get scaleY(): number; /** * update position BY MOVING FORWARD IN ANGLE DIRECTION */ - move(speed: number, updateNow?: boolean): Circle | SATPolygon + move(speed: number, updateNow?: boolean): Circle | SATPolygon; /** * update position BY TELEPORTING */ - setPosition(x: number, y: number, updateNow?: boolean): Circle | SATPolygon + setPosition(x: number, y: number, updateNow?: boolean): Circle | SATPolygon; /** * for setting scale */ - setScale(x: number, y: number, updateNow?: boolean): Circle | SATPolygon + setScale(x: number, y: number, updateNow?: boolean): Circle | SATPolygon; /** * for setting angle */ - setAngle(angle: number, updateNow?: boolean): Circle | SATPolygon + setAngle(angle: number, updateNow?: boolean): Circle | SATPolygon; /** * for setting offset from center */ - setOffset(offset: Vector, updateNow?: boolean): Circle | SATPolygon + setOffset(offset: Vector, updateNow?: boolean): Circle | SATPolygon; /** * draw the bounding box */ - drawBVH(context: CanvasRenderingContext2D): void + drawBVH(context: CanvasRenderingContext2D): void; /** * draw the collider */ - draw(context: CanvasRenderingContext2D): void + draw(context: CanvasRenderingContext2D): void; /** * return bounding box without padding */ - getAABBAsBBox(): BBox + getAABBAsBBox(): BBox; } export type SATTest< T extends {} = Circle | Polygon | SATPolygon, Y extends {} = Circle | Polygon | SATPolygon, -> = (bodyA: T, bodyB: Y, response: Response) => boolean +> = (bodyA: T, bodyB: Y, response: Response) => boolean; export type InTest = ( bodyA: TBody, - bodyB: TBody -) => boolean + bodyB: TBody, +) => boolean; export type TraverseFunction = ( child: Leaf, children: Leaf[], - index: number -) => boolean | void + index: number, +) => boolean | void; diff --git a/src/optimized.ts b/src/optimized.ts index 7c21c2d4..8f1b7f82 100644 --- a/src/optimized.ts +++ b/src/optimized.ts @@ -5,12 +5,12 @@ */ export const forEach = ( array: T[], - callback: (item: T, index: number) => void + callback: (item: T, index: number) => void, ): void => { for (let i = 0, l = array.length; i < l; i++) { - callback(array[i], i) + callback(array[i], i); } -} +}; /** * 20-90% faster than built-in Array.some function. @@ -19,16 +19,16 @@ export const forEach = ( */ export const some = ( array: T[], - callback: (item: T, index: number) => unknown + callback: (item: T, index: number) => unknown, ): boolean => { for (let i = 0, l = array.length; i < l; i++) { if (callback(array[i], i)) { - return true + return true; } } - return false -} + return false; +}; /** * 20-40% faster than built-in Array.every function. @@ -37,16 +37,16 @@ export const some = ( */ export const every = ( array: T[], - callback: (item: T, index: number) => unknown + callback: (item: T, index: number) => unknown, ): boolean => { for (let i = 0, l = array.length; i < l; i++) { if (!callback(array[i], i)) { - return false + return false; } } - return true -} + return true; +}; /** * 20-60% faster than built-in Array.filter function. @@ -55,20 +55,20 @@ export const every = ( */ export const filter = ( array: T[], - callback: (item: T, index: number) => unknown + callback: (item: T, index: number) => unknown, ): T[] => { - const output: T[] = [] + const output: T[] = []; for (let i = 0, l = array.length; i < l; i++) { - const item = array[i] + const item = array[i]; if (callback(item, i)) { - output.push(item) + output.push(item); } } - return output -} + return output; +}; /** * 20-70% faster than built-in Array.map @@ -77,14 +77,14 @@ export const filter = ( */ export const map = ( array: T[], - callback: (item: T, index: number) => Y + callback: (item: T, index: number) => Y, ): Y[] => { - const l = array.length - const output = new Array(l) + const l = array.length; + const output = new Array(l); for (let i = 0; i < l; i++) { - output[i] = callback(array[i], i) + output[i] = callback(array[i], i); } - return output -} + return output; +}; diff --git a/src/system.ts b/src/system.ts index 65391826..34377174 100644 --- a/src/system.ts +++ b/src/system.ts @@ -7,25 +7,25 @@ import { RaycastHit, Response, SATVector, - Vector -} from "./model" + Vector, +} from "./model"; import { canInteract, checkAInB, distance, getSATTest, notIntersectAABB, - returnTrue -} from "./utils" + returnTrue, +} from "./utils"; import { ensureConvex, intersectLineCircle, - intersectLinePolygon -} from "./intersect" -import { forEach, some } from "./optimized" + intersectLinePolygon, +} from "./intersect"; +import { forEach, some } from "./optimized"; -import { BaseSystem } from "./base-system" -import { Line } from "./bodies/line" +import { BaseSystem } from "./base-system"; +import { Line } from "./bodies/line"; /** * collision system @@ -34,23 +34,23 @@ export class System extends BaseSystem { /** * the last collision result */ - response: Response = new Response() + response: Response = new Response(); /** * for raycasting */ - protected ray!: Line + protected ray!: Line; /** * re-insert body into collision tree and update its bbox * every body can be part of only one system */ insert(body: TBody): this { - const insertResult = super.insert(body) + const insertResult = super.insert(body); // set system for later body.system.updateBody(body) - body.system = this + body.system = this; - return insertResult + return insertResult; } /** @@ -58,8 +58,8 @@ export class System extends BaseSystem { */ separate(): void { forEach(this.all(), (body: TBody) => { - this.separateBody(body) - }) + this.separateBody(body); + }); } /** @@ -67,19 +67,19 @@ export class System extends BaseSystem { */ separateBody(body: TBody): void { if (body.isStatic || body.isTrigger) { - return + return; } - const offsets = { x: 0, y: 0 } + const offsets = { x: 0, y: 0 }; const addOffsets = ({ overlapV: { x, y } }: Response) => { - offsets.x += x - offsets.y += y - } + offsets.x += x; + offsets.y += y; + }; - this.checkOne(body, addOffsets) + this.checkOne(body, addOffsets); if (offsets.x || offsets.y) { - body.setPosition(body.x - offsets.x, body.y - offsets.y) + body.setPosition(body.x - offsets.x, body.y - offsets.y); } } @@ -89,24 +89,24 @@ export class System extends BaseSystem { checkOne( body: TBody, callback: CollisionCallback = returnTrue, - response = this.response + response = this.response, ): boolean { // no need to check static body collision if (body.isStatic) { - return false + return false; } - const bodies = this.search(body) + const bodies = this.search(body); const checkCollision = (candidate: TBody) => { if ( candidate !== body && this.checkCollision(body, candidate, response) ) { - return callback(response) + return callback(response); } - } + }; - return some(bodies, checkCollision) + return some(bodies, checkCollision); } /** @@ -115,13 +115,13 @@ export class System extends BaseSystem { checkArea( area: BBox, callback: CollisionCallback = returnTrue, - response = this.response + response = this.response, ): boolean { const checkOne = (body: TBody) => { - return this.checkOne(body, callback, response) - } + return this.checkOne(body, callback, response); + }; - return some(this.search(area), checkOne) + return some(this.search(area), checkOne); } /** @@ -129,13 +129,13 @@ export class System extends BaseSystem { */ checkAll( callback: CollisionCallback = returnTrue, - response = this.response + response = this.response, ): boolean { const checkOne = (body: TBody) => { - return this.checkOne(body, callback, response) - } + return this.checkOne(body, callback, response); + }; - return some(this.all(), checkOne) + return some(this.all(), checkOne); } /** @@ -144,10 +144,10 @@ export class System extends BaseSystem { checkCollision( bodyA: TBody, bodyB: TBody, - response = this.response + response = this.response, ): boolean { - const { bbox: bboxA } = bodyA - const { bbox: bboxB } = bodyB + const { bbox: bboxA } = bodyA; + const { bbox: bboxB } = bodyB; // assess the bodies real aabb without padding if ( !canInteract(bodyA, bodyB) || @@ -155,54 +155,54 @@ export class System extends BaseSystem { !bboxB || notIntersectAABB(bboxA, bboxB) ) { - return false + return false; } - const sat = getSATTest(bodyA, bodyB) + const sat = getSATTest(bodyA, bodyB); // 99% of cases if (bodyA.isConvex && bodyB.isConvex) { // always first clear response - response.clear() + response.clear(); - return sat(bodyA, bodyB, response) + return sat(bodyA, bodyB, response); } // more complex (non convex) cases - const convexBodiesA = ensureConvex(bodyA) - const convexBodiesB = ensureConvex(bodyB) + const convexBodiesA = ensureConvex(bodyA); + const convexBodiesB = ensureConvex(bodyB); - let overlapX = 0 - let overlapY = 0 - let collided = false + let overlapX = 0; + let overlapY = 0; + let collided = false; - forEach(convexBodiesA,convexBodyA => { - forEach(convexBodiesB,convexBodyB => { + forEach(convexBodiesA, (convexBodyA) => { + forEach(convexBodiesB, (convexBodyB) => { // always first clear response - response.clear() + response.clear(); if (sat(convexBodyA, convexBodyB, response)) { - collided = true - overlapX += response.overlapV.x - overlapY += response.overlapV.y + collided = true; + overlapX += response.overlapV.x; + overlapY += response.overlapV.y; } - }) - }) + }); + }); if (collided) { - const vector = new SATVector(overlapX, overlapY) - - response.a = bodyA - response.b = bodyB - response.overlapV.x = overlapX - response.overlapV.y = overlapY - response.overlapN = vector.normalize() - response.overlap = vector.len() - response.aInB = checkAInB(bodyA, bodyB) - response.bInA = checkAInB(bodyB, bodyA) + const vector = new SATVector(overlapX, overlapY); + + response.a = bodyA; + response.b = bodyB; + response.overlapV.x = overlapX; + response.overlapV.y = overlapY; + response.overlapN = vector.normalize(); + response.overlap = vector.len(); + response.aInB = checkAInB(bodyA, bodyB); + response.bInA = checkAInB(bodyB, bodyA); } - return collided + return collided; } /** @@ -211,42 +211,42 @@ export class System extends BaseSystem { raycast( start: Vector, end: Vector, - allow: (body: TBody, ray: TBody) => boolean = returnTrue + allow: (body: TBody, ray: TBody) => boolean = returnTrue, ): RaycastHit | null { - let minDistance = Infinity - let result: RaycastHit | null = null + let minDistance = Infinity; + let result: RaycastHit | null = null; if (!this.ray) { - this.ray = new Line(start, end, { isTrigger: true }) + this.ray = new Line(start, end, { isTrigger: true }); } else { - this.ray.start = start - this.ray.end = end + this.ray.start = start; + this.ray.end = end; } - this.insert(this.ray as TBody) + this.insert(this.ray as TBody); this.checkOne(this.ray as TBody, ({ b: body }) => { if (!allow(body, this.ray as TBody)) { - return false + return false; } const points: Vector[] = body.typeGroup === BodyGroup.Circle ? intersectLineCircle(this.ray, body) - : intersectLinePolygon(this.ray, body) + : intersectLinePolygon(this.ray, body); forEach(points, (point: Vector) => { - const pointDistance: number = distance(start, point) + const pointDistance: number = distance(start, point); if (pointDistance < minDistance) { - minDistance = pointDistance - result = { point, body } + minDistance = pointDistance; + result = { point, body }; } - }) - }) + }); + }); - this.remove(this.ray as TBody) + this.remove(this.ray as TBody); - return result + return result; } } diff --git a/src/types.d.ts b/src/types.d.ts index e7d9e06d..b8befe46 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,5 +1,5 @@ declare module "random-seed" { - export function create(name: string): { random(): number } + export function create(name: string): { random(): number }; } -declare module "pixi-shim" +declare module "pixi-shim"; diff --git a/src/utils.ts b/src/utils.ts index ff4707bd..94d561be 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,26 +8,26 @@ import { PotentialVector, SATPolygon, SATTest, - Vector -} from "./model" + Vector, +} from "./model"; import { Response, Vector as SATVector, testCircleCircle, testCirclePolygon, testPolygonCircle, - testPolygonPolygon -} from "sat" + testPolygonPolygon, +} from "sat"; import { circleInCircle, circleInPolygon, polygonInCircle, - polygonInPolygon -} from "./intersect" -import { forEach, map } from "./optimized" + polygonInPolygon, +} from "./intersect"; +import { forEach, map } from "./optimized"; -import { Point as DecompPoint } from "poly-decomp-es" -import { Polygon } from "./bodies/polygon" +import { Point as DecompPoint } from "poly-decomp-es"; +import { Polygon } from "./bodies/polygon"; /* helpers for faster getSATTest() and checkAInB() */ @@ -39,46 +39,50 @@ const testMap = { inCircleCircle: circleInCircle, inCirclePolygon: circleInPolygon, inPolygonCircle: polygonInCircle, - inPolygonPolygon: polygonInPolygon -} + inPolygonPolygon: polygonInPolygon, +}; -function createMap( +function createArray( bodyType: BodyType.Circle | BodyType.Polygon, - testType: "sat" | "in" -): Record { - return Object.values(BodyType).reduce( - (result: Record, type: BodyType) => ({ - ...result, - [type]: - type === BodyType.Circle - ? testMap[`${testType}${bodyType}Circle`] - : testMap[`${testType}${bodyType}Polygon`] - }), - {} as Record - ) + testType: "sat" | "in", +): T[] { + const arrayResult: T[] = []; + const bodyGroups = Object.values(BodyGroup).filter( + (value) => typeof value === "number", + ); + + bodyGroups.forEach((bodyGroup: BodyGroup) => { + arrayResult[bodyGroup] = ( + bodyGroup === BodyGroup.Circle + ? testMap[`${testType}${bodyType}Circle`] + : testMap[`${testType}${bodyType}Polygon`] + ) as T; + }); + + return arrayResult; } -const circleSATFunctions = createMap(BodyType.Circle, "sat") -const circleInFunctions = createMap(BodyType.Circle, "in") -const polygonSATFunctions = createMap(BodyType.Polygon, "sat") -const polygonInFunctions = createMap(BodyType.Polygon, "in") +const circleSATFunctions = createArray(BodyType.Circle, "sat"); +const circleInFunctions = createArray(BodyType.Circle, "in"); +const polygonSATFunctions = createArray(BodyType.Polygon, "sat"); +const polygonInFunctions = createArray(BodyType.Polygon, "in"); -export const DEG2RAD = Math.PI / 180 +export const DEG2RAD = Math.PI / 180; -export const RAD2DEG = 180 / Math.PI +export const RAD2DEG = 180 / Math.PI; /** * convert from degrees to radians */ export function deg2rad(degrees: number) { - return degrees * DEG2RAD + return degrees * DEG2RAD; } /** * convert from radians to degrees */ export function rad2deg(radians: number) { - return radians * RAD2DEG + return radians * RAD2DEG; } /** @@ -87,21 +91,21 @@ export function rad2deg(radians: number) { export function createEllipse( radiusX: number, radiusY: number = radiusX, - step = 1 + step = 1, ): SATVector[] { - const steps: number = Math.PI * Math.hypot(radiusX, radiusY) * 2 - const length: number = Math.max(8, Math.ceil(steps / Math.max(1, step))) - const ellipse: SATVector[] = [] + const steps: number = Math.PI * Math.hypot(radiusX, radiusY) * 2; + const length: number = Math.max(8, Math.ceil(steps / Math.max(1, step))); + const ellipse: SATVector[] = []; for (let index = 0; index < length; index++) { - const value: number = (index / length) * 2 * Math.PI - const x: number = Math.cos(value) * radiusX - const y: number = Math.sin(value) * radiusY + const value: number = (index / length) * 2 * Math.PI; + const x: number = Math.cos(value) * radiusX; + const y: number = Math.sin(value) * radiusY; - ellipse.push(new SATVector(x, y)) + ellipse.push(new SATVector(x, y)); } - return ellipse + return ellipse; } /** @@ -112,8 +116,8 @@ export function createBox(width: number, height: number): SATVector[] { new SATVector(0, 0), new SATVector(width, 0), new SATVector(width, height), - new SATVector(0, height) - ] + new SATVector(0, height), + ]; } /** @@ -122,71 +126,71 @@ export function createBox(width: number, height: number): SATVector[] { export function ensureVectorPoint(point: PotentialVector = {}): SATVector { return point instanceof SATVector ? point - : new SATVector(point.x || 0, point.y || 0) + : new SATVector(point.x || 0, point.y || 0); } /** * ensure Vector points (for polygon) in counter-clockwise order */ export function ensurePolygonPoints( - points: PotentialVector[] = [] + points: PotentialVector[] = [], ): SATVector[] { - const polygonPoints: SATVector[] = map(points, ensureVectorPoint) + const polygonPoints: SATVector[] = map(points, ensureVectorPoint); - return clockwise(polygonPoints) ? polygonPoints.reverse() : polygonPoints + return clockwise(polygonPoints) ? polygonPoints.reverse() : polygonPoints; } /** * get distance between two Vector points */ export function distance(bodyA: Vector, bodyB: Vector): number { - const xDiff = bodyA.x - bodyB.x - const yDiff = bodyA.y - bodyB.y + const xDiff = bodyA.x - bodyB.x; + const yDiff = bodyA.y - bodyB.y; - return Math.hypot(xDiff, yDiff) + return Math.hypot(xDiff, yDiff); } /** * check [is clockwise] direction of polygon */ export function clockwise(points: Vector[]): boolean { - const length = points.length - let sum = 0 + const length = points.length; + let sum = 0; forEach(points, (v1, index) => { - const v2 = points[(index + 1) % length] + const v2 = points[(index + 1) % length]; - sum += (v2.x - v1.x) * (v2.y + v1.y) - }) + sum += (v2.x - v1.x) * (v2.y + v1.y); + }); - return sum > 0 + return sum > 0; } /** * used for all types of bodies in constructor */ export function extendBody(body: Body, options: BodyOptions = {}): void { - body.isStatic = !!options.isStatic - body.isTrigger = !!options.isTrigger - body.padding = options.padding || 0 - body.group = typeof options.group === "number" ? options.group : 0x7FFFFFFF + body.isStatic = !!options.isStatic; + body.isTrigger = !!options.isTrigger; + body.padding = options.padding || 0; + body.group = typeof options.group === "number" ? options.group : 0x7fffffff; if (body.typeGroup !== BodyGroup.Circle) { - body.isCentered = options.isCentered || false + body.isCentered = options.isCentered || false; } - body.setAngle(options.angle || 0) + body.setAngle(options.angle || 0); } /** * check if body moved outside of its padding */ export function bodyMoved(body: Body): boolean { - const { bbox, minX, minY, maxX, maxY } = body + const { bbox, minX, minY, maxX, maxY } = body; return ( bbox.minX < minX || bbox.minY < minY || bbox.maxX > maxX || bbox.maxY > maxY - ) + ); } /** @@ -198,14 +202,14 @@ export function notIntersectAABB(bodyA: BBox, bodyB: BBox): boolean { bodyB.minY > bodyA.maxY || bodyB.maxX < bodyA.minX || bodyB.maxY < bodyA.minY - ) + ); } /** * checks if two boxes intersect */ export function intersectAABB(bodyA: BBox, bodyB: BBox): boolean { - return !notIntersectAABB(bodyA, bodyB) + return !notIntersectAABB(bodyA, bodyB); } /** @@ -213,9 +217,9 @@ export function intersectAABB(bodyA: BBox, bodyB: BBox): boolean { */ export function canInteract(bodyA: Body, bodyB: Body): boolean { return ( - ((bodyA.group >> 16) & (bodyB.group & 0xFFFF) && - (bodyB.group >> 16) & (bodyA.group & 0xFFFF)) !== 0 - ) + ((bodyA.group >> 16) & (bodyB.group & 0xffff) && + (bodyB.group >> 16) & (bodyA.group & 0xffff)) !== 0 + ); } /** @@ -225,43 +229,43 @@ export function checkAInB(bodyA: Body, bodyB: Body): boolean { const check = bodyA.typeGroup === BodyGroup.Circle ? circleInFunctions - : polygonInFunctions + : polygonInFunctions; - return check[bodyB.type](bodyA, bodyB) + return check[bodyB.typeGroup](bodyA, bodyB); } /** * clone sat vector points array into vector points array */ export function clonePointsArray(points: SATVector[]): Vector[] { - return map(points, ({ x, y }: Vector) => ({ x, y })) + return map(points, ({ x, y }: Vector) => ({ x, y })); } /** * change format from SAT.js to poly-decomp */ export function mapVectorToArray( - { x, y }: Vector = { x: 0, y: 0 } + { x, y }: Vector = { x: 0, y: 0 }, ): DecompPoint { - return [x, y] + return [x, y]; } /** * change format from poly-decomp to SAT.js */ export function mapArrayToVector([x, y]: DecompPoint = [0, 0]): Vector { - return { x, y } + return { x, y }; } /** * given 2 bodies calculate vector of bounce assuming equal mass and they are circles */ export function getBounceDirection(body: Vector, collider: Vector): SATVector { - const v2 = new SATVector(collider.x - body.x, collider.y - body.y) - const v1 = new SATVector(body.x - collider.x, body.y - collider.y) - const len = v1.dot(v2.normalize()) * 2 + const v2 = new SATVector(collider.x - body.x, collider.y - body.y); + const v1 = new SATVector(body.x - collider.x, body.y - collider.y); + const len = v1.dot(v2.normalize()) * 2; - return new SATVector(v2.x * len - v1.x, v2.y * len - v1.y).normalize() + return new SATVector(v2.x * len - v1.x, v2.y * len - v1.y).normalize(); } /** @@ -271,9 +275,9 @@ export function getSATTest(bodyA: Body, bodyB: Body): SATTest { const check = bodyA.typeGroup === BodyGroup.Circle ? circleSATFunctions - : polygonSATFunctions + : polygonSATFunctions; - return check[bodyB.type] + return check[bodyB.typeGroup]; } /** @@ -286,25 +290,25 @@ export function dashLineTo( toX: number, toY: number, dash = 2, - gap = 4 + gap = 4, ): void { - const xDiff = toX - fromX - const yDiff = toY - fromY - const arc = Math.atan2(yDiff, xDiff) - const offsetX = Math.cos(arc) - const offsetY = Math.sin(arc) + const xDiff = toX - fromX; + const yDiff = toY - fromY; + const arc = Math.atan2(yDiff, xDiff); + const offsetX = Math.cos(arc); + const offsetY = Math.sin(arc); - let posX = fromX - let posY = fromY - let dist = Math.hypot(xDiff, yDiff) + let posX = fromX; + let posY = fromY; + let dist = Math.hypot(xDiff, yDiff); while (dist > 0) { - const step = Math.min(dist, dash) - context.moveTo(posX, posY) - context.lineTo(posX + offsetX * step, posY + offsetY * step) - posX += offsetX * (dash + gap) - posY += offsetY * (dash + gap) - dist -= dash + gap + const step = Math.min(dist, dash); + context.moveTo(posX, posY); + context.lineTo(posX + offsetX * step, posY + offsetY * step); + posX += offsetX * (dash + gap); + posY += offsetY * (dash + gap); + dist -= dash + gap; } } @@ -315,31 +319,31 @@ export function drawPolygon( context: CanvasRenderingContext2D, { pos, - calcPoints + calcPoints, }: Pick & { pos: Vector }, - isTrigger = false + isTrigger = false, ): void { - const lastPoint = calcPoints[calcPoints.length - 1] - const fromX = pos.x + lastPoint.x - const fromY = pos.y + lastPoint.y + const lastPoint = calcPoints[calcPoints.length - 1]; + const fromX = pos.x + lastPoint.x; + const fromY = pos.y + lastPoint.y; if (calcPoints.length === 1) { - context.arc(fromX, fromY, 1, 0, Math.PI * 2) + context.arc(fromX, fromY, 1, 0, Math.PI * 2); } else { - context.moveTo(fromX, fromY) + context.moveTo(fromX, fromY); } forEach(calcPoints, (point, index) => { - const toX = pos.x + point.x - const toY = pos.y + point.y + const toX = pos.x + point.x; + const toY = pos.y + point.y; if (isTrigger) { - const prev = calcPoints[index - 1] || lastPoint - dashLineTo(context, pos.x + prev.x, pos.y + prev.y, toX, toY) + const prev = calcPoints[index - 1] || lastPoint; + dashLineTo(context, pos.x + prev.x, pos.y + prev.y, toX, toY); } else { - context.lineTo(toX, toY) + context.lineTo(toX, toY); } - }) + }); } /** @@ -348,46 +352,46 @@ export function drawPolygon( export function drawBVH(context: CanvasRenderingContext2D, body: Body) { drawPolygon(context, { pos: { x: body.minX, y: body.minY }, - calcPoints: createBox(body.maxX - body.minX, body.maxY - body.minY) - }) + calcPoints: createBox(body.maxX - body.minX, body.maxY - body.minY), + }); } /** * clone response object returning new response with previous ones values */ export function cloneResponse(response: Response) { - const clone = new Response() - const { a, b, overlap, overlapN, overlapV, aInB, bInA } = response - clone.a = a - clone.b = b - clone.overlap = overlap - clone.overlapN = overlapN.clone() - clone.overlapV = overlapV.clone() - clone.aInB = aInB - clone.bInA = bInA - - return clone + const clone = new Response(); + const { a, b, overlap, overlapN, overlapV, aInB, bInA } = response; + clone.a = a; + clone.b = b; + clone.overlap = overlap; + clone.overlapN = overlapN.clone(); + clone.overlapV = overlapV.clone(); + clone.aInB = aInB; + clone.bInA = bInA; + + return clone; } /** * dummy fn used as default, for optimization */ export function returnTrue() { - return true + return true; } /** * for groups */ export function getGroup(group: number): number { - return Math.max(0, Math.min(group, 0x7FFFFFFF)) + return Math.max(0, Math.min(group, 0x7fffffff)); } /** * binary string to decimal number */ export function bin2dec(binary: string): number { - return Number(`0b${binary}`.replace(/\s/g, "")) + return Number(`0b${binary}`.replace(/\s/g, "")); } /** @@ -396,7 +400,7 @@ export function bin2dec(binary: string): number { * @param input - number or binary string */ export function ensureNumber(input: number | string): number { - return typeof input === "number" ? input : bin2dec(input) + return typeof input === "number" ? input : bin2dec(input); } /** @@ -407,16 +411,16 @@ export function ensureNumber(input: number | string): number { */ export function groupBits( category: number | string, - mask: number | string = category + mask: number | string = category, ) { - return (ensureNumber(category) << 16) | ensureNumber(mask) + return (ensureNumber(category) << 16) | ensureNumber(mask); } export function move(body: Body, speed = 1, updateNow = true) { if (!speed) { - return + return; } - const moveX = Math.cos(body.angle) * speed - const moveY = Math.sin(body.angle) * speed - body.setPosition(body.x + moveX, body.y + moveY, updateNow) + const moveX = Math.cos(body.angle) * speed; + const moveY = Math.sin(body.angle) * speed; + body.setPosition(body.x + moveX, body.y + moveY, updateNow); }