ixfx
Version:
Bundle of ixfx libraries
1,887 lines (1,844 loc) • 350 kB
JavaScript
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: