UNPKG

ixfx

Version:

Bundle of ixfx libraries

1,887 lines (1,844 loc) 350 kB
import { n as __exportAll } from "./chunk-CaR5F9JI.js"; import { Ct as zipKeyValue } from "./src-BUqDa_u7.js"; import { C as numberTest, E as errorResult, M as resultThrow, R as throwIfFailed, v as integerTest, w as percentTest } from "./src-C_hvyftg.js"; import { M as findIndex, N as findIndexReverse, j as filterWithIndex, u as sortByNumericProperty } from "./src-CxEyGbiK.js"; import { c as mutable } from "./src-DOorb7Rs.js"; import { A as round$1, E as movingAverageLight, L as wrap$3, _t as minFast, b as quantiseEvery$1, f as scale, ft as dotProduct$3, it as clampIndex, k as linearSpace, rt as clamp$1, vt as minIndex } from "./src-Cebc3sfq.js"; import { n as TrackedValueMap, r as ObjectTracker } from "./src-DIuRzACc.js"; import { D as randomElement } from "./src-BH_hkHiA.js"; //#region ../packages/geometry/src/point/guard.ts /** * Returns true if xy (and z, if present) are _null_. * @param p * @returns True if all props are null */ function isNull(p) { if (isPoint3d(p)) { if (p.z !== null) return false; } return p.x === null && p.y === null; } /*** * Returns true if either x, y, z isNaN. */ function isNaN$1(p) { if (isPoint3d(p)) { if (!Number.isNaN(p.z)) return false; } return Number.isNaN(p.x) || Number.isNaN(p.y); } function pointTest(p, name = `Point`, extraInfo = ``) { if (p === void 0) return errorResult(`'${name}' is undefined. Expected {x,y} got ${JSON.stringify(p)}`, extraInfo); if (p === null) return errorResult(`'${name}' is null. Expected {x,y} got ${JSON.stringify(p)}`, extraInfo); if (typeof p !== `object`) return errorResult(`'${name}' is type '${typeof p}'. Expected object.`, extraInfo); if (p.x === void 0) return errorResult(`'${name}.x' is undefined. Expected {x,y} got ${JSON.stringify(p)}`, extraInfo); if (p.y === void 0) return errorResult(`'${name}.y' is undefined. Expected {x,y} got ${JSON.stringify(p)}`, extraInfo); if (typeof p.x !== `number`) return errorResult(`'${name}.x' must be a number. Got ${typeof p.x}`, extraInfo); if (typeof p.y !== `number`) return errorResult(`'${name}.y' must be a number. Got ${typeof p.y}`, extraInfo); if (p.z !== void 0) { if (typeof p.z !== `number`) return errorResult(`${name}.z must be a number. Got: ${typeof p.z}`, extraInfo); if (Number.isNaN(p.z)) return errorResult(`'${name}.z' is NaN. Got: ${JSON.stringify(p)}`, extraInfo); } if (p.x === null) return errorResult(`'${name}.x' is null`, extraInfo); if (p.y === null) return errorResult(`'${name}.y' is null`, extraInfo); if (Number.isNaN(p.x)) return errorResult(`'${name}.x' is NaN`, extraInfo); if (Number.isNaN(p.y)) return errorResult(`'${name}.y' is NaN`, extraInfo); return { success: true, value: p }; } /** * Throws an error if point is invalid * @param p * @param name */ function guard$5(p, name = `Point`, info) { resultThrow(pointTest(p, name, info)); } /** * Throws if parameter is not a valid point, or either x or y is 0 * @param pt * @returns Throws an error if not a valid point or zero. */ function guardNonZeroPoint(pt, name = `pt`) { guard$5(pt, name); resultThrow(numberTest(pt.x, `nonZero`, `${name}.x`), numberTest(pt.y, `nonZero`, `${name}.y`), () => { if (typeof pt.z !== `undefined`) return numberTest(pt.z, `nonZero`, `${name}.z`); }); return true; } /** * Returns _true_ if `p` has x & y properties. * Returns _false_ if `p` is undefined, null or does not contain properties. * Use {@link isPoint3d} to check further check for `z`. * @param p * @returns True if `p` has x & y props */ function isPoint(p) { if (p === void 0) return false; if (p === null) return false; if (p.x === void 0) return false; if (p.y === void 0) return false; return true; } /** * Returns _true_ if `p` has x, y, & z properties. * Returns _false_ if `p` is undefined, null or does not contain properties. * @param p * @returns _True_ if `p` has x, y, & z props */ function isPoint3d(p) { if (p === void 0) return false; if (p === null) return false; if (p.x === void 0) return false; if (p.y === void 0) return false; if (p.z === void 0) return false; return true; } /** * Returns true if both xy (and z, if present) are 0. * Use `Points.Empty` to return an empty point. * @param p * @returns _True_ is all props are 0 */ function isEmpty$3(p) { if (isPoint3d(p)) { if (p.z !== 0) return false; } return p.x === 0 && p.y === 0; } /** * Returns true if point is a placeholder, where xy (and z, if present) * are `NaN`. * * Use Points.Placeholder to return a placeholder point. * @param p * @returns True if all props are NaN */ function isPlaceholder$3(p) { if (isPoint3d(p)) { if (!Number.isNaN(p.z)) return false; } return Number.isNaN(p.x) && Number.isNaN(p.y); } //#endregion //#region ../packages/geometry/src/rect/guard.ts /** * Throws an error if the dimensions of the rectangle are undefined, NaN or negative. * @param d * @param name */ const guardDim = (d, name = `Dimension`) => { if (d === void 0) throw new Error(`${name} is undefined`); if (Number.isNaN(d)) throw new Error(`${name} is NaN`); if (d < 0) throw new Error(`${name} cannot be negative`); }; /** * Throws an error if rectangle is missing fields or they * are not valid. * * Checks: * * `width` and `height` must be defined on `rect` * * dimensions (w & h) must not be NaN * * dimensions (w & h) must not be negative * * If `rect` has x,y, this value is checked as well. * @param rect * @param name */ const guard$4 = (rect, name = `rect`) => { if (rect === void 0) throw new Error(`{$name} undefined`); if (isPositioned$2(rect)) guard$5(rect, name); guardDim(rect.width, name + `.width`); guardDim(rect.height, name + `.height`); }; /** * Returns a positioned rect or if it's not possible, throws an error. * * If `rect` does not have a position, `origin` is used. * If `rect` is positioned and `origin` is provided, returned result uses `origin` as x,y instead. * ```js * // Returns input because it's positioned * getRectPositioned({ x:1, y:2, width:10, height:20 }); * * // Returns { x:1, y:2, width:10, height:20 } * getRectPositioned({ width:10, height:20 }, { x:1, y:2 }); * * // Throws, because we have no point * getRectPositioned({width:10,height:20}) * ``` * @param rect * @param origin * @returns */ const getRectPositioned = (rect, origin) => { guard$4(rect); if (isPositioned$2(rect) && origin === void 0) return rect; if (origin === void 0) throw new Error(`Unpositioned rect needs origin parameter`); return Object.freeze({ ...rect, ...origin }); }; /** * Throws an error if `rect` is does not have a position, or * is an invalid rectangle * @param rect * @param name */ const guardPositioned$1 = (rect, name = `rect`) => { if (!isPositioned$2(rect)) throw new Error(`Expected ${name} to have x,y`); guard$4(rect, name); }; /** * Returns _true_ if `rect` has width and height values of 0. * Use Rects.Empty or Rects.EmptyPositioned to generate an empty rectangle. * @param rect * @returns */ const isEmpty$2 = (rect) => rect.width === 0 && rect.height === 0; /** * Returns _true_ if `rect` is a placeholder, with both width and height values of NaN. * Use Rects.Placeholder or Rects.PlaceholderPositioned to generate a placeholder. * @param rect * @returns */ const isPlaceholder$2 = (rect) => Number.isNaN(rect.width) && Number.isNaN(rect.height); /** * Returns _true_ if `rect` has position (x,y) fields. * @param rect Point, Rect or RectPositiond * @returns */ const isPositioned$2 = (rect) => rect.x !== void 0 && rect.y !== void 0; /** * Returns _true_ if `rect` has width and height fields. * @param rect * @returns */ const isRect = (rect) => { if (rect === void 0) return false; if (rect.width === void 0) return false; if (rect.height === void 0) return false; return true; }; /** * Returns _true_ if `rect` is a positioned rectangle * Having width, height, x and y properties. * @param rect * @returns */ const isRectPositioned = (rect) => isRect(rect) && isPositioned$2(rect); //#endregion //#region ../packages/geometry/src/rect/apply.ts /** * Applies an operation over each field of a rectangle. * ```js * // Convert x,y,width,height to integer values * applyFields(v => Number.floor(v), someRect); * ``` * @param op * @param rectOrWidth * @param heightValue * @returns */ function applyFields(op, rectOrWidth, heightValue) { let width = typeof rectOrWidth === `number` ? rectOrWidth : rectOrWidth.width; let height = typeof rectOrWidth === `number` ? heightValue : rectOrWidth.height; if (width === void 0) throw new Error(`Param 'width' undefined`); if (height === void 0) throw new Error(`Param 'height' undefined`); width = op(width, `width`); height = op(height, `height`); if (typeof rectOrWidth === `object`) if (isPositioned$2(rectOrWidth)) { const x = op(rectOrWidth.x, `x`); const y = op(rectOrWidth.y, `y`); return { ...rectOrWidth, width, height, x, y }; } else return { ...rectOrWidth, width, height }; return { width, height }; } /** * Applies an joint operation field-wise on two rectangles, returning a single rectangle. This is used to support operations like summing two rectangles. * ```js * // Eg make a new rectangle by summing each field of rectangle A & B. * apply((valueA,valueB) => valueA+valueB, rectA, rectB); * ``` * @param op * @param a * @param b * @param c * @returns */ function applyMerge(op, a, b, c) { guard$4(a, `a`); if (isRect(b)) return isRectPositioned(a) ? Object.freeze({ ...a, x: op(a.x, b.width), y: op(a.y, b.height), width: op(a.width, b.width), height: op(a.height, b.height) }) : Object.freeze({ ...a, width: op(a.width, b.width), height: op(a.height, b.height) }); else { if (typeof b !== `number`) throw new TypeError(`Expected second parameter of type Rect or number. Got ${JSON.stringify(b)}`); if (typeof c !== `number`) throw new Error(`Expected third param as height. Got ${JSON.stringify(c)}`); return isRectPositioned(a) ? Object.freeze({ ...a, x: op(a.x, b), y: op(a.y, c), width: op(a.width, b), height: op(a.height, c) }) : Object.freeze({ ...a, width: op(a.width, b), height: op(a.height, c) }); } } function applyScalar(op, rect, parameter) { return isPositioned$2(rect) ? Object.freeze({ ...rect, x: op(rect.x, parameter), y: op(rect.y, parameter), width: op(rect.width, parameter), height: op(rect.height, parameter) }) : Object.freeze({ ...rect, width: op(rect.width, parameter), height: op(rect.height, parameter) }); } /** * Applies `op` with `param` to `rect`'s width and height. * @param op * @param rect * @param parameter * @returns */ function applyDim(op, rect, parameter) { return Object.freeze({ ...rect, width: op(rect.width, parameter), height: op(rect.height, parameter) }); } //#endregion //#region ../packages/geometry/src/rect/area.ts /** * Returns the area of `rect` * * ```js * const rect = { width: 100, height: 100, x: 100, y: 100 }; * Rects.area(rect); * ``` * @param rect * @returns */ const area$5 = (rect) => { guard$4(rect); return rect.height * rect.width; }; //#endregion //#region ../packages/geometry/src/rect/cardinal.ts /** * Returns a point on cardinal direction, or 'center' for the middle. * * ```js * cardinal({x: 10, y:10, width:100, height: 20}, 'center'); * ``` * @param rect Rectangle * @param card Cardinal direction or 'center' * @returns Point */ const cardinal = (rect, card) => { const { x, y, width, height } = rect; switch (card) { case `nw`: return Object.freeze({ x, y }); case `n`: return Object.freeze({ x: x + width / 2, y }); case `ne`: return Object.freeze({ x: x + width, y }); case `sw`: return Object.freeze({ x, y: y + height }); case `s`: return Object.freeze({ x: x + width / 2, y: y + height }); case `se`: return Object.freeze({ x: x + width, y: y + height }); case `w`: return Object.freeze({ x, y: y + height / 2 }); case `e`: return Object.freeze({ x: x + width, y: y + height / 2 }); case `center`: return Object.freeze({ x: x + width / 2, y: y + height / 2 }); default: throw new Error(`Unknown direction: ${card}`); } }; //#endregion //#region ../packages/geometry/src/rect/center.ts /** * Returns the center of a rectangle as a {@link Point}. * If the rectangle lacks a position and `origin` parameter is not provided, 0,0 is used instead. * * ```js * const p = Rects.center({x:10, y:20, width:100, height:50}); * const p2 = Rects.center({width: 100, height: 50}); // Assumes 0,0 for rect x,y * ``` * @param rect Rectangle * @param origin Optional origin. Overrides `rect` position if available. If no position is available 0,0 is used by default. * @returns */ const center$2 = (rect, origin) => { guard$4(rect); if (origin === void 0 && isPoint(rect)) origin = rect; else if (origin === void 0) origin = { x: 0, y: 0 }; getRectPositioned(rect, origin); return Object.freeze({ x: origin.x + rect.width / 2, y: origin.y + rect.height / 2 }); }; //#endregion //#region ../packages/geometry/src/rect/center-origin.ts /** * Perform basic point translation using a rectangle where its center is the origin. * * Thus the relative coordinate { x: 0, y: 0} corresponds to the absolute middle of the * rectangle. * * The relative coordinate { x: -1, y: -1 } corresponds to the rectangle's {x,y} properties, and so on. * @param rectAbsolute * @returns */ const centerOrigin = (rectAbsolute) => { const c = center$2(rectAbsolute); const w = rectAbsolute.width / 2; const h = rectAbsolute.height / 2; const relativeToAbsolute = (point) => { return { ...point, x: point.x * w + c.x, y: point.y * h + c.y }; }; const absoluteToRelative = (point) => { return { ...point, x: (point.x - rectAbsolute.x) / w - 1, y: (point.y - rectAbsolute.y) / h - 1 }; }; return { relativeToAbsolute, absoluteToRelative }; }; //#endregion //#region ../packages/geometry/src/rect/corners.ts /** * Returns the four corners of a rectangle as an array of Points. * * ```js * const rect = { width: 100, height: 100, x: 0, y: 0}; * const pts = Rects.corners(rect); * ``` * * If the rectangle is not positioned, is origin can be provided. * Order of corners: ne, nw, sw, se * @param rect * @param origin * @returns */ const corners$1 = (rect, origin) => { const r = getRectPositioned(rect, origin); return [ { x: r.x, y: r.y }, { x: r.x + r.width, y: r.y }, { x: r.x + r.width, y: r.y + r.height }, { x: r.x, y: r.y + r.height } ]; }; //#endregion //#region ../packages/geometry/src/point/get-point-parameter.ts function getTwoPointParameters(a1, ab2, ab3, ab4, ab5, ab6) { if (isPoint3d(a1) && isPoint3d(ab2)) return [a1, ab2]; if (isPoint(a1) && isPoint(ab2)) return [a1, ab2]; if (isPoint3d(a1)) { const b = { x: ab2, y: ab3, z: ab4 }; if (!isPoint3d(b)) throw new Error(`Expected x, y & z parameters`); return [a1, b]; } if (isPoint(a1)) { const b = { x: ab2, y: ab3 }; if (!isPoint(b)) throw new Error(`Expected x & y parameters`); return [a1, b]; } if (typeof ab5 !== `undefined` && typeof ab4 !== `undefined`) { const a = { x: a1, y: ab2, z: ab3 }; const b = { x: ab4, y: ab5, z: ab6 }; if (!isPoint3d(a)) throw new Error(`Expected x,y,z for first point`); if (!isPoint3d(b)) throw new Error(`Expected x,y,z for second point`); return [a, b]; } const a = { x: a1, y: ab2 }; const b = { x: ab3, y: ab4 }; if (!isPoint(a)) throw new Error(`Expected x,y for first point`); if (!isPoint(b)) throw new Error(`Expected x,y for second point`); return [a, b]; } /** * Returns a Point form of either a point, x,y params or x,y,z params. * If parameters are undefined, an empty point is returned (0, 0) * @ignore * @param a * @param b * @returns */ function getPointParameter$1(a, b, c) { if (a === void 0) return { x: 0, y: 0 }; if (Array.isArray(a)) { if (a.length === 0) return Object.freeze({ x: 0, y: 0 }); if (a.length === 1) return Object.freeze({ x: a[0], y: 0 }); if (a.length === 2) return Object.freeze({ x: a[0], y: a[1] }); if (a.length === 3) return Object.freeze({ x: a[0], y: a[1], z: a[2] }); throw new Error(`Expected array to be 1-3 elements in length. Got ${a.length}.`); } if (isPoint(a)) return a; else if (typeof a !== `number` || typeof b !== `number`) throw new TypeError(`Expected point or x,y as parameters. Got: a: ${JSON.stringify(a)} b: ${JSON.stringify(b)}`); if (typeof c === `number`) return Object.freeze({ x: a, y: b, z: c }); return Object.freeze({ x: a, y: b }); } //#endregion //#region ../packages/geometry/src/point/distance.ts /** * Calculate distance between two points. * If both points have a `z` property, the distance is 3D distance is calculated. * If only one point has a `z`, it is ignored. To force 2D distance, use {@link distance2d} * * ```js * // Distance between two points * const ptA = { x: 0.5, y:0.8 }; * const ptB = { x: 1, y: 0.4 }; * distance(ptA, ptB); * // Or, provide x,y as parameters * distance(ptA, 0.4, 0.9); * * // Distance from ptA to x: 0.5, y:0.8, z: 0.1 * const ptC = { x: 0.5, y:0.5, z: 0.3 }; * // With x,y,z as parameters: * distance(ptC, 0.5, 0.8, 0.1); * ``` * @param a First point * @param xOrB Second point, or x coord * @param y y coord, if x coord is given * @param z Optional z coord, if x and y are given. * @returns */ function distance$2(a, xOrB, y, z) { const pt = getPointParameter$1(xOrB, y, z); guard$5(pt, `b`); guard$5(a, `a`); return isPoint3d(pt) && isPoint3d(a) ? Math.hypot(pt.x - a.x, pt.y - a.y, pt.z - a.z) : Math.hypot(pt.x - a.x, pt.y - a.y); } /** * As {@link distance} but always compares by x,y only. * @param a * @param xOrB * @param y * @returns */ function distance2d(a, xOrB, y) { const pt = getPointParameter$1(xOrB, y); guard$5(pt, `b`); guard$5(a, `a`); return Math.hypot(pt.x - a.x, pt.y - a.y); } //#endregion //#region ../packages/geometry/src/circle/guard.ts /** * Throws if radius is out of range. If x,y is present, these will be validated too. * @param circle * @param parameterName */ const guard$3 = (circle, parameterName = `circle`) => { if (isCirclePositioned(circle)) guard$5(circle, `circle`); if (Number.isNaN(circle.radius)) throw new Error(`${parameterName}.radius is NaN`); if (circle.radius <= 0) throw new Error(`${parameterName}.radius must be greater than zero`); }; /** * Throws if `circle` is not positioned or has dodgy fields * @param circle * @param parameterName * @returns */ const guardPositioned = (circle, parameterName = `circle`) => { if (!isCirclePositioned(circle)) throw new Error(`Expected a positioned circle with x,y`); guard$3(circle, parameterName); }; /*** * Returns true if radius, x or y are NaN */ const isNaN = (a) => { if (Number.isNaN(a.radius)) return true; if (isCirclePositioned(a)) { if (Number.isNaN(a.x)) return true; if (Number.isNaN(a.y)) return true; } return false; }; /** * Returns true if parameter has x,y. Does not verify if parameter is a circle or not * * ```js * const circleA = { radius: 5 }; * Circles.isPositioned(circle); // false * * const circleB = { radius: 5, x: 10, y: 10 } * Circles.isPositioned(circle); // true * ``` * @param p Circle * @returns */ const isPositioned$1 = (p) => p.x !== void 0 && p.y !== void 0; const isCircle = (p) => p.radius !== void 0; const isCirclePositioned = (p) => isCircle(p) && isPositioned$1(p); //#endregion //#region ../packages/geometry/src/circle/is-equal.ts /** * Returns true if the two objects have the same values * * ```js * const circleA = { radius: 10, x: 5, y: 5 }; * const circleB = { radius: 10, x: 5, y: 5 }; * * circleA === circleB; // false, because identity of objects is different * Circles.isEqual(circleA, circleB); // true, because values are the same * ``` * * Circles must both be positioned or not. * @param a * @param b * @returns */ const isEqual$6 = (a, b) => { if (a.radius !== b.radius) return false; if (isCirclePositioned(a) && isCirclePositioned(b)) { if (a.x !== b.x) return false; if (a.y !== b.y) return false; if (a.z !== b.z) return false; return true; } else if (!isCirclePositioned(a) && !isCirclePositioned(b)) return true; else return false; return false; }; //#endregion //#region ../packages/geometry/src/point/sum.ts /** * Returns a Point with the x,y,z values of two points added. * * `z` parameter is used, if present. Uses a default value of 0 for 'z' when adding a 2D point with a 3D one. * * Examples: * * ```js * sum(ptA, ptB); * sum(x1, y1, x2, y2); * sum(ptA, x2, y2); * ``` */ function sum$3(a1, ab2, ab3, ab4, ab5, ab6) { const [ptA, ptB] = getTwoPointParameters(a1, ab2, ab3, ab4, ab5, ab6); guard$5(ptA, `a`); guard$5(ptB, `b`); const pt = { x: ptA.x + ptB.x, y: ptA.y + ptB.y }; if (isPoint3d(ptA) || isPoint3d(ptB)) pt.z = (ptA.z ?? 0) + (ptB.z ?? 0); return Object.freeze(pt); } //#endregion //#region ../packages/geometry/src/point/subtract.ts /** * Returns a Point with the x,y,z values of two points subtracted (a-b). * * `z` parameter is used if present. Uses a default value of 0 for 'z' when subtracting a 2D point with a 3D one. * * Examples: * * ```js * subtract(ptA, ptB); * subtract(x1, y1, x2, y2); * subtract(ptA, x2, y2); * ``` */ function subtract$3(a1, ab2, ab3, ab4, ab5, ab6) { const [ptA, ptB] = getTwoPointParameters(a1, ab2, ab3, ab4, ab5, ab6); guard$5(ptA, `a`); guard$5(ptB, `b`); const pt = { x: ptA.x - ptB.x, y: ptA.y - ptB.y }; if (isPoint3d(ptA) || isPoint3d(ptB)) pt.z = (ptA.z ?? 0) - (ptB.z ?? 0); return Object.freeze(pt); } //#endregion //#region ../packages/geometry/src/circle/intersections.ts /** * Returns the point(s) of intersection between a circle and line. * * ```js * const circle = { radius: 5, x: 5, y: 5 }; * const line = { a: { x: 0, y: 0 }, b: { x: 10, y: 10 } }; * const pts = Circles.intersectionLine(circle, line); * ``` * @param circle * @param line * @returns Point(s) of intersection, or empty array */ const intersectionLine = (circle, line) => { const v1 = { x: line.b.x - line.a.x, y: line.b.y - line.a.y }; const v2 = { x: line.a.x - circle.x, y: line.a.y - circle.y }; const b = (v1.x * v2.x + v1.y * v2.y) * -2; const c = 2 * (v1.x * v1.x + v1.y * v1.y); const d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - circle.radius * circle.radius)); if (Number.isNaN(d)) return []; const u1 = (b - d) / c; const u2 = (b + d) / c; const returnValue = []; if (u1 <= 1 && u1 >= 0) returnValue.push({ x: line.a.x + v1.x * u1, y: line.a.y + v1.y * u1 }); if (u2 <= 1 && u2 >= 0) returnValue.push({ x: line.a.x + v1.x * u2, y: line.a.y + v1.y * u2 }); return returnValue; }; /** * * Returns the points of intersection betweeen `a` and `b`. * * Returns an empty array if circles are equal, one contains the other or if they don't touch at all. * * @param a Circle * @param b Circle * @returns Points of intersection, or an empty list if there are none */ const intersections$1 = (a, b) => { const vector = subtract$3(b, a); const centerD = Math.hypot(vector.y, vector.x); if (centerD > a.radius + b.radius) return []; if (centerD < Math.abs(a.radius - b.radius)) return []; if (isEqual$6(a, b)) return []; const centroidD = (a.radius * a.radius - b.radius * b.radius + centerD * centerD) / (2 * centerD); const centroid = { x: a.x + vector.x * centroidD / centerD, y: a.y + vector.y * centroidD / centerD }; const centroidIntersectionD = Math.sqrt(a.radius * a.radius - centroidD * centroidD); const intersection = { x: -vector.y * (centroidIntersectionD / centerD), y: vector.x * (centroidIntersectionD / centerD) }; return [sum$3(centroid, intersection), subtract$3(centroid, intersection)]; }; //#endregion //#region ../packages/geometry/src/intersects.ts const circleRect = (a, b) => { const deltaX = a.x - Math.max(b.x, Math.min(a.x, b.x + b.width)); const deltaY = a.y - Math.max(b.y, Math.min(a.y, b.y + b.height)); return deltaX * deltaX + deltaY * deltaY < a.radius * a.radius; }; const circleCircle = (a, b) => intersections$1(a, b).length === 2; //#endregion //#region ../packages/geometry/src/rect/intersects.ts /** * Returns true if point is within or on boundary of `rect`. * * ```js * Rects.intersectsPoint(rect, { x: 100, y: 100}); * Rects.intersectsPoint(rect, 100, 100); * ``` * @param rect * @param a * @param b * @returns */ function intersectsPoint$1(rect, a, b) { guard$4(rect, `rect`); let x = 0; let y = 0; if (typeof a === `number`) { if (b === void 0) throw new Error(`x and y coordinate needed`); x = a; y = b; } else { x = a.x; y = a.y; } if (isPositioned$2(rect)) { if (x - rect.x > rect.width || x < rect.x) return false; if (y - rect.y > rect.height || y < rect.y) return false; } else { if (x > rect.width || x < 0) return false; if (y > rect.height || y < 0) return false; } return true; } /** * Returns true if `a` or `b` overlap, are equal, or `a` contains `b`. * A rectangle can be checked for intersections with another RectPositioned, CirclePositioned or Point. * */ const isIntersecting$2 = (a, b) => { if (!isRectPositioned(a)) throw new Error(`a parameter should be RectPositioned`); if (isCirclePositioned(b)) return circleRect(b, a); else if (isPoint(b)) return intersectsPoint$1(a, b); throw new Error(`Unknown shape for b: ${JSON.stringify(b)}`); }; //#endregion //#region ../packages/geometry/src/rect/distance.ts /** * Returns the distance from the perimeter of `rect` to `pt`. * If the point is within the rectangle, 0 is returned. * * If `rect` does not have an x,y it's assumed to be 0,0 * * ```js * const rect = { width: 100, height: 100, x: 0, y: 0 }; * Rects.distanceFromExterior(rect, { x: 20, y: 20 }); * ``` * @param rect Rectangle * @param pt Point * @returns Distance */ function distanceFromExterior$1(rect, pt) { guardPositioned$1(rect, `rect`); guard$5(pt, `pt`); if (intersectsPoint$1(rect, pt)) return 0; const dx = Math.max(rect.x - pt.x, 0, pt.x - rect.x + rect.width); const dy = Math.max(rect.y - pt.y, 0, pt.y - rect.y + rect.height); return Math.hypot(dx, dy); } /** * Return the distance of `pt` to the center of `rect`. * * ```js * const rect = { width: 100, height: 100, x: 0, y: 0 }; * Rects.distanceFromCenter(rect, { x: 20, y: 20 }); * ``` * @param rect * @param pt * @returns */ function distanceFromCenter(rect, pt) { return distance$2(center$2(rect), pt); } //#endregion //#region ../packages/geometry/src/rect/divide.ts const divideOp = (a, b) => a / b; /** * @internal * @param a * @param b * @param c * @returns */ function divide$4(a, b, c) { return applyMerge(divideOp, a, b, c); } /** * Divides all components of `rect` by `amount`. * This includes x,y if present. * * ```js * divideScalar({ width:10, height:20 }, 2); // { width:5, height: 10 } * divideScalar({ x: 1, y: 2, width:10, height:20 }, 2); // { x: 0.5, y: 1, width:5, height: 10 } * ``` * @param rect * @param amount */ function divideScalar(rect, amount) { return applyScalar(divideOp, rect, amount); } function divideDim(rect, amount) { return applyDim(divideOp, rect, amount); } //#endregion //#region ../packages/geometry/src/point/to.ts /** * Returns a point with rounded x,y coordinates. By default uses `Math.round` to round. * ```js * toIntegerValues({x:1.234, y:5.567}); // Yields: {x:1, y:6} * ``` * * ```js * toIntegerValues(pt, Math.ceil); // Use Math.ceil to round x,y of `pt`. * ``` * @param pt Point to round * @param rounder Rounding function, or Math.round by default * @returns */ const toIntegerValues = (pt, rounder = Math.round) => { guard$5(pt, `pt`); return Object.freeze({ x: rounder(pt.x), y: rounder(pt.y) }); }; /** * Returns a copy of `pt` with `z` field omitted. * If it didn't have one to begin within, a copy is still returned. * @param pt * @returns */ const to2d = (pt) => { guard$5(pt, `pt`); let copy = { ...pt }; delete copy.z; return Object.freeze(copy); }; /** * Returns a copy of `pt` with a `z` field set. * Defaults to a z value of 0. * @param pt Point * @param z Z-value, defaults to 0 * @returns */ const to3d = (pt, z = 0) => { guard$5(pt, `pt`); return Object.freeze({ ...pt, z }); }; /** * Returns a human-friendly string representation `(x, y)`. * If `precision` is supplied, this will be the number of significant digits. * @param p * @returns */ function toString$5(p, digits) { if (p === void 0) return `(undefined)`; if (p === null) return `(null)`; guard$5(p, `pt`); const x = digits ? p.x.toFixed(digits) : p.x; const y = digits ? p.y.toFixed(digits) : p.y; if (p.z === void 0) return `(${x},${y})`; else return `(${x},${y},${digits ? p.z.toFixed(digits) : p.z})`; } //#endregion //#region ../packages/geometry/src/line/from-points.ts /** * Returns a line from two points * * ```js * // Line from 0,1 to 10,15 * const line = Lines.fromPoints( { x:0, y:1 }, { x:10, y:15 }); * // line is: { a: { x: 0, y: 1}, b: { x: 10, y: 15 } }; * ``` * @param a Start point * @param b End point * @returns */ const fromPoints$2 = (a, b) => { guard$5(a, `a`); guard$5(b, `b`); a = Object.freeze({ ...a }); b = Object.freeze({ ...b }); return Object.freeze({ a, b }); }; //#endregion //#region ../packages/geometry/src/line/join-points-to-lines.ts /** * Returns an array of lines that connects provided points. Note that line is not closed. * * Eg, if points a,b,c are provided, two lines are provided: a->b and b->c. * * ```js * const lines = Lines.joinPointsToLines(ptA, ptB, ptC); * // lines is an array of, well, lines * ``` * @param points * @returns */ const joinPointsToLines = (...points) => { const lines = []; let start = points[0]; for (let index = 1; index < points.length; index++) { lines.push(fromPoints$2(start, points[index])); start = points[index]; } return lines; }; /** * Converts a {@link PolyLine} to an array of points. * Duplicate points are optionally excluded * @param line * @returns */ const polyLineToPoints = (line, skipDuplicates = false) => { if (skipDuplicates) { const pt = []; const seen = /* @__PURE__ */ new Set(); for (const l of line) { const aa = toString$5(l.a); const bb = toString$5(l.b); if (!seen.has(aa)) { seen.add(aa); pt.push(l.a); } if (seen.has(bb)) { seen.add(bb); pt.push(l.b); } } return pt; } else { const pt = []; for (const l of line) pt.push(l.a, l.b); return pt; } }; //#endregion //#region ../packages/geometry/src/rect/edges.ts /** * Returns four lines based on each corner. * Lines are given in order: top, right, bottom, left * * ```js * const rect = { width: 100, height: 100, x: 100, y: 100 }; * // Yields: array of length four * const lines = Rects.lines(rect); * ``` * * @param {(RectPositioned|Rect)} rect * @param {Points.Point} [origin] * @returns {Lines.Line[]} */ const edges$1 = (rect, origin) => { const c = corners$1(rect, origin); return joinPointsToLines(...c, c[0]); }; /** * Returns a point on the edge of rectangle * ```js * const r1 = {x: 10, y: 10, width: 100, height: 50}; * Rects.getEdgeX(r1, `right`); // Yields: 110 * Rects.getEdgeX(r1, `bottom`); // Yields: 10 * * const r2 = {width: 100, height: 50}; * Rects.getEdgeX(r2, `right`); // Yields: 100 * Rects.getEdgeX(r2, `bottom`); // Yields: 0 * ``` * @param rect * @param edge Which edge: right, left, bottom, top * @returns */ const getEdgeX = (rect, edge) => { guard$4(rect); switch (edge) { case `top`: return isPoint(rect) ? rect.x : 0; case `bottom`: return isPoint(rect) ? rect.x : 0; case `left`: return isPoint(rect) ? rect.y : 0; case `right`: return isPoint(rect) ? rect.x + rect.width : rect.width; } }; /** * Returns a point on the edge of rectangle * * ```js * const r1 = {x: 10, y: 10, width: 100, height: 50}; * Rects.getEdgeY(r1, `right`); // Yields: 10 * Rects.getEdgeY(r1, `bottom`); // Yields: 60 * * const r2 = {width: 100, height: 50}; * Rects.getEdgeY(r2, `right`); // Yields: 0 * Rects.getEdgeY(r2, `bottom`); // Yields: 50 * ``` * @param rect * @param edge Which edge: right, left, bottom, top * @returns */ const getEdgeY = (rect, edge) => { guard$4(rect); switch (edge) { case `top`: return isPoint(rect) ? rect.y : 0; case `bottom`: return isPoint(rect) ? rect.y + rect.height : rect.height; case `left`: return isPoint(rect) ? rect.y : 0; case `right`: return isPoint(rect) ? rect.y : 0; } }; //#endregion //#region ../packages/geometry/src/rect/encompass.ts /** * Returns a copy of `rect` with `rect` resized so it also encompasses `points`. * If provided point(s) are within bounds of `rect`, a copy of `rect` is returned. * @param rect * @param points * @returns */ const encompass = (rect, ...points) => { const x = points.map((p) => p.x); const y = points.map((p) => p.y); let minX = Math.min(...x, rect.x); let minY = Math.min(...y, rect.y); let maxX = Math.max(...x, rect.x + rect.width); let maxY = Math.max(...y, rect.y + rect.height); let rectW = Math.max(rect.width, maxX - minX); let rectH = Math.max(rect.height, maxY - minY); return Object.freeze({ ...rect, x: minX, y: minY, width: rectW, height: rectH }); }; //#endregion //#region ../packages/geometry/src/rect/from-center.ts /** * Initialises a rectangle based on its center, a width and height * * ```js * // Rectangle with center at 50,50, width 100 height 200 * Rects.fromCenter({x: 50, y:50}, 100, 200); * ``` * @param origin * @param width * @param height * @returns */ const fromCenter$2 = (origin, width, height) => { guard$5(origin, `origin`); guardDim(width, `width`); guardDim(height, `height`); const halfW = width / 2; const halfH = height / 2; return { x: origin.x - halfW, y: origin.y - halfH, width, height }; }; //#endregion //#region ../packages/geometry/src/rect/from-element.ts /** * Initialise a rectangle based on the width and height of a HTML element. * * ```js * Rects.fromElement(document.querySelector(`body`)); * ``` * @param el * @returns */ const fromElement = (el) => ({ width: el.clientWidth, height: el.clientHeight }); //#endregion //#region ../packages/geometry/src/rect/from-numbers.ts /** * Returns a rectangle from a series of numbers: x, y, width, height OR width, height * * ```js * const r1 = Rects.fromNumbers(100, 200); * // {width: 100, height: 200} * * const r2 = Rects.fromNumbers(10, 20, 100, 200); * // {x: 10, y: 20, width: 100, height: 200} * ``` * Use the spread operator (...) if the source is an array: * * ```js * const r3 = Rects.fromNumbers(...[10, 20, 100, 200]); * ``` * * Use {@link toArray} for the opposite conversion. * * @see toArray * @param xOrWidth * @param yOrHeight * @param width * @param height * @returns */ function fromNumbers$2(xOrWidth, yOrHeight, width, height) { if (width === void 0 || height === void 0) { if (typeof xOrWidth !== `number`) throw new Error(`width is not an number`); if (typeof yOrHeight !== `number`) throw new TypeError(`height is not an number`); return Object.freeze({ width: xOrWidth, height: yOrHeight }); } if (typeof xOrWidth !== `number`) throw new Error(`x is not an number`); if (typeof yOrHeight !== `number`) throw new Error(`y is not an number`); if (typeof width !== `number`) throw new Error(`width is not an number`); if (typeof height !== `number`) throw new Error(`height is not an number`); return Object.freeze({ x: xOrWidth, y: yOrHeight, width, height }); } //#endregion //#region ../packages/geometry/src/rect/from-top-left.ts /** * Creates a rectangle from its top-left coordinate, a width and height. * * ```js * // Rectangle at 50,50 with width of 100, height of 200. * const rect = Rects.fromTopLeft({ x: 50, y:50 }, 100, 200); * ``` * @param origin * @param width * @param height */ function fromTopLeft(origin, width, height) { guardDim(width, `width`); guardDim(height, `height`); guard$5(origin, `origin`); return { x: origin.x, y: origin.y, width, height }; } //#endregion //#region ../packages/geometry/src/rect/get-rect-positionedparameter.ts /** * Accepts: * * x,y,w,h * * x,y,rect * * point,rect * * RectPositioned * * Rect, x,y * * Rect, Point * @param a * @param b * @param c * @param d * @returns */ function getRectPositionedParameter(a, b, c, d) { if (typeof a === `number`) if (typeof b === `number`) if (typeof c === `number` && typeof d === `number`) return { x: a, y: b, width: c, height: d }; else if (isRect(c)) return { x: a, y: b, width: c.width, height: c.height }; else throw new TypeError(`If params 'a' & 'b' are numbers, expect following parameters to be x,y or Rect`); else throw new TypeError(`If parameter 'a' is a number, expect following parameters to be: y,w,h`); else if (isRectPositioned(a)) return a; else if (isRect(a)) if (typeof b === `number` && typeof c === `number`) return { width: a.width, height: a.height, x: b, y: c }; else if (isPoint(b)) return { width: a.width, height: a.height, x: b.x, y: b.y }; else throw new TypeError(`If param 'a' is a Rect, expects following parameters to be x,y`); else if (isPoint(a)) if (typeof b === `number` && typeof c === `number`) return { x: a.x, y: a.y, width: b, height: c }; else if (isRect(b)) return { x: a.x, y: a.y, width: b.width, height: b.height }; else throw new TypeError(`If parameter 'a' is a Point, expect following params to be: Rect or width,height`); throw new TypeError(`Expect a first parameter to be x,RectPositioned,Rect or Point`); } //#endregion //#region ../packages/geometry/src/rect/initialisers.ts const Empty$3 = Object.freeze({ width: 0, height: 0 }); const EmptyPositioned = Object.freeze({ x: 0, y: 0, width: 0, height: 0 }); const Placeholder$3 = Object.freeze({ width: NaN, height: NaN }); const PlaceholderPositioned = Object.freeze({ x: NaN, y: NaN, width: NaN, height: NaN }); //#endregion //#region ../packages/geometry/src/point/is-equal.ts /** * Returns _true_ if the points have identical values * * ```js * const a = {x: 10, y: 10}; * const b = {x: 10, y: 10;}; * a === b // False, because a and be are different objects * isEqual(a, b) // True, because a and b are same value * ``` * @param p Points * @returns _True_ if points are equal */ const isEqual$5 = (...p) => { if (p === void 0) throw new Error(`parameter 'p' is undefined`); if (p.length < 2) return true; for (let index = 1; index < p.length; index++) { if (p[index].x !== p[0].x) return false; if (p[index].y !== p[0].y) return false; } return true; }; //#endregion //#region ../packages/geometry/src/rect/is-equal.ts /** * Returns _true_ if the width & height of the two rectangles is the same. * * ```js * const rectA = { width: 10, height: 10, x: 10, y: 10 }; * const rectB = { width: 10, height: 10, x: 20, y: 20 }; * * // True, even though x,y are different * Rects.isEqualSize(rectA, rectB); * * // False, because coordinates are different * Rects.isEqual(rectA, rectB) * ``` * @param a * @param b * @returns */ const isEqualSize = (a, b) => { if (a === void 0) throw new Error(`a undefined`); if (b === void 0) throw new Error(`b undefined`); return a.width === b.width && a.height === b.height; }; /** * Returns _true_ if two rectangles have identical values. * Both rectangles must be positioned or not. * * ```js * const rectA = { width: 10, height: 10, x: 10, y: 10 }; * const rectB = { width: 10, height: 10, x: 20, y: 20 }; * * // False, because coordinates are different * Rects.isEqual(rectA, rectB) * * // True, even though x,y are different * Rects.isEqualSize(rectA, rectB); * ``` * @param a * @param b * @returns */ const isEqual$4 = (a, b) => { if (isPositioned$2(a) && isPositioned$2(b)) { if (!isEqual$5(a, b)) return false; return a.width === b.width && a.height === b.height; } else if (!isPositioned$2(a) && !isPositioned$2(b)) return a.width === b.width && a.height === b.height; else return false; }; //#endregion //#region ../packages/geometry/src/line/guard.ts /** * Returns true if `p` is a valid line, containing `a` and `b` Points. * ```js * Lines.isLine(l); * ``` * @param p Value to check * @returns True if a valid line. */ function isLine(p) { if (p === void 0) return false; if (p.a === void 0) return false; if (p.b === void 0) return false; if (!isPoint(p.a)) return false; if (!isPoint(p.b)) return false; return true; } /** * Returns true if `p` is a {@link PolyLine}, ie. an array of {@link Line}s. * Validates all items in array. * @param p * @returns */ function isPolyLine(p) { if (!Array.isArray(p)) return false; return !p.some((v) => !isLine(v)); } /** * Returns a failure if: * - line is undefined * - a or b parameters are missing * * Does not validate points * @param line * @param name */ function lineTest(line, name = `line`) { if (line === void 0) return { success: false, error: `${name} undefined` }; if (line.a === void 0) return { success: false, error: `${name}.a undefined. Expected {a:Point, b:Point}. Got: ${JSON.stringify(line)}` }; if (line.b === void 0) return { success: false, error: `${name}.b undefined. Expected {a:Point, b:Point} Got: ${JSON.stringify(line)}` }; return { success: true, value: true }; } //#endregion //#region ../packages/geometry/src/line/get-points-parameter.ts /** * Returns [a,b] points from either a line parameter, or two points. * It additionally applies the guardPoint function to ensure validity. * This supports function overloading. * @ignore * @param aOrLine * @param b * @returns */ const getPointParameter = (aOrLine, b) => { let a; if (isLine(aOrLine)) { b = aOrLine.b; a = aOrLine.a; } else { a = aOrLine; if (b === void 0) throw new Error(`Since first parameter is not a line, two points are expected. Got a: ${JSON.stringify(a)} b: ${JSON.stringify(b)}`); } guard$5(a, `a`); guard$5(a, `b`); return [a, b]; }; //#endregion //#region ../packages/geometry/src/line/length.ts /** * Returns length of line, polyline or between two points * * @param aOrLine Point A, line or polyline (array of lines) * @param pointB Point B, if first parameter is a point * @returns Length (total accumulated length for arrays) */ function length$3(aOrLine, pointBOrForce2d, force2d) { if (isPolyLine(aOrLine)) { const _force2d = typeof pointBOrForce2d === `boolean` ? pointBOrForce2d : false; return aOrLine.reduce((accumulator, v) => length$3(v, _force2d) + accumulator, 0); } if (aOrLine === void 0) throw new TypeError(`Parameter 'aOrLine' is undefined`); const [a, b] = typeof pointBOrForce2d === `object` ? getPointParameter(aOrLine, pointBOrForce2d) : getPointParameter(aOrLine); const x = b.x - a.x; const y = b.y - a.y; if (!(typeof pointBOrForce2d === `boolean` ? pointBOrForce2d : typeof force2d === `boolean` ? force2d : false) && a.z !== void 0 && b.z !== void 0) { const z = b.z - a.z; return Math.hypot(x, y, z); } else return Math.hypot(x, y); } //#endregion //#region ../packages/geometry/src/rect/lengths.ts /** * Returns the length of each side of the rectangle (top, right, bottom, left) * * ```js * const rect = { width: 100, height: 100, x: 100, y: 100 }; * // Yields: array of length four * const lengths = Rects.lengths(rect); * ``` * @param rect * @returns */ const lengths$1 = (rect) => { guardPositioned$1(rect, `rect`); return edges$1(rect).map((l) => length$3(l)); }; //#endregion //#region ../packages/geometry/src/rect/max.ts /** * Returns a rectangle based on provided four corners. * * To create a rectangle that contains an arbitary set of points, use {@link Points.bbox}. * * Does some sanity checking such as: * - x will be smallest of topLeft/bottomLeft * - y will be smallest of topRight/topLeft * - width will be largest between top/bottom left and right * - height will be largest between left and right top/bottom * */ const maxFromCorners = (topLeft, topRight, bottomRight, bottomLeft) => { if (topLeft.y > bottomRight.y) throw new Error(`topLeft.y greater than bottomRight.y`); if (topLeft.y > bottomLeft.y) throw new Error(`topLeft.y greater than bottomLeft.y`); const w1 = topRight.x - topLeft.x; const w2 = bottomRight.x - bottomLeft.x; const h1 = Math.abs(bottomLeft.y - topLeft.y); const h2 = Math.abs(bottomRight.y - topRight.y); return { x: Math.min(topLeft.x, bottomLeft.x), y: Math.min(topRight.y, topLeft.y), width: Math.max(w1, w2), height: Math.max(h1, h2) }; }; //#endregion //#region ../packages/geometry/src/rect/multiply.ts const multiplyOp = (a, b) => a * b; /** * @internal * @param a * @param b * @param c * @returns */ function multiply$4(a, b, c) { return applyMerge(multiplyOp, a, b, c); } /** * Multiplies all components of `rect` by `amount`. * This includes x,y if present. * * ```js * multiplyScalar({ width:10, height:20 }, 2); // { width:20, height: 40 } * multiplyScalar({ x: 1, y: 2, width:10, height:20 }, 2); // { x: 2, y: 4, width:20, height: 40 } * ``` * * Use {@link multiplyDim} to only multiply width & height. * @param rect * @param amount */ function multiplyScalar$2(rect, amount) { return applyScalar(multiplyOp, rect, amount); } /** * Multiplies only the width/height of `rect`, leaving `x` and `y` as they are. * ```js * multiplyDim({ x:1,y:2,width:3,height:4 }, 2); * // Yields: { x:1, y:2, width:6, height: 8 } * ``` * * In comparison, {@link multiply} will also include x & y. * @param rect Rectangle * @param amount Amount to multiply by * @returns */ function multiplyDim(rect, amount) { return applyDim(multiplyOp, rect, amount); } //#endregion //#region ../packages/geometry/src/rect/nearest.ts /** * If `p` is inside of `rect`, a copy of `p` is returned. * If `p` is outside of `rect`, a point is returned closest to `p` on the edge * of the rectangle. * @param rect * @param p * @returns */ const nearestInternal = (rect, p) => { let { x, y } = p; if (x < rect.x) x = rect.x; else if (x > rect.x + rect.width) x = rect.x + rect.width; if (y < rect.y) y = rect.y; else if (y > rect.y + rect.height) y = rect.y + rect.height; return Object.freeze({ ...p, x, y }); }; //#endregion //#region ../packages/geometry/src/rect/normalise-by-rect.ts /** * Returns a function that divides numbers or points by the largest dimension of `rect`. * * ```js * const d = dividerByLargestDimension({width:100,height:50}); * d(50); // 0.5 (50/100) * d({ x: 10, y: 20 }); // { x: 0.1, y: 0.2 } * ``` * @param rect * @returns */ const dividerByLargestDimension = (rect) => { const largest = Math.max(rect.width, rect.height); return (value) => { if (typeof value === `number`) return value / largest; else if (isPoint3d(value)) return Object.freeze({ ...value, x: value.x / largest, y: value.y / largest, z: value.x / largest }); else if (isPoint(value)) return Object.freeze({ ...value, x: value.x / largest, y: value.y / largest }); else throw new Error(`Param 'value' is neither number nor Point`); }; }; //#endregion //#region ../packages/geometry/src/rect/perimeter.ts /** * Returns the perimeter of `rect` (ie. sum of all edges) * * ```js * const rect = { width: 100, height: 100, x: 100, y: 100 }; * Rects.perimeter(rect); * ``` * @param rect * @returns */ const perimeter$4 = (rect) => { guard$4(rect); return rect.height + rect.height + rect.width + rect.width; }; //#endregion //#region ../packages/geometry/src/rect/random.ts /** * Returns a random positioned Rect on a 0..1 scale. * ```js * const r = Rects.random(); // eg {x: 0.2549012, y:0.859301, width: 0.5212, height: 0.1423 } * ``` * * A custom source of randomness can be provided: * ```js * import { Rects } from "@ixfx/geometry.js"; * import { weightedSource } from "@ixfx/random.js" * const r = Rects.random(weightedSource(`quadIn`)); * ``` * @param rando * @returns */ const random$2 = (rando) => { rando ??= Math.random; return Object.freeze({ x: rando(), y: rando(), width: rando(), height: rando() }); }; /** * Returns a random point within a rectangle. * * By default creates a uniform distribution. * * ```js * const pt = randomPoint({width: 5, height: 10}); * ```' * @param within Rectangle to generate a point within * @param options Options * @returns */ const randomPoint$2 = (within, options = {}) => { const rand = options.randomSource ?? Math.random; const margin = options.margin ?? { x: 0, y: 0 }; const x = rand() * (within.width - margin.x - margin.x); const y = rand() * (within.height - margin.y - margin.y); const pos = { x: x + margin.x, y: y + margin.y }; return isPositioned$2(within) ? sum$3(pos, within) : Object.freeze(pos); }; //#endregion //#region ../packages/geometry/src/rect/subtract.ts const subtractOp = (a, b) => a - b; /** * Subtracts width/height from `a`. * * ```js * const rectA = { width: 100, height: 100 }; * const rectB = { width: 200, height: 200 }; * * // Yields: { width: -100, height: -100 } * Rects.subtract(rectA, rectB); * Rects.subtract(rectA, 200, 200); * ``` * @param a * @param b * @param c * @returns */ function subtract$2(a, b, c) { return applyMerge(subtractOp, a, b, c); } function subtractSize(a, b, c) { const w = typeof b === `number` ? b : b.width; const h = typeof b === `number` ? c : b.height; if (h === void 0) throw new Error(`Expected height as third parameter`); return { ...a, width: a.width - w, height: a.height - h }; } /** * Subtracts A-B. Applies to x, y, width & height * ```js * subtractOffset( * { x:100, y:100, width:100, height:100 }, * { x:10, y:20, width: 30, height: 40 } * ); * // Yields: {x: 90, y: 80, width: 70, height: 60 } * ``` * If either `a` or `b` are missing x & y, 0 is used. * @param a * @param b * @returns */ function subtractOffset(a, b) { let x = 0; let y = 0; if (isPositioned$2(a)) { x = a.x; y = a.y; } let xB = 0; let yB = 0; if (isPositioned$2(b)) { xB = b.x; yB = b.y; } return Object.freeze({ ...a, x: x - xB, y: y - yB, width: a.width - b.width, height: