@thi.ng/math
Advanced tools
Comparing version 5.7.6 to 5.7.7
import { EPS } from "./api.js"; | ||
export const absDiff = (x, y) => Math.abs(x - y); | ||
export const sign = (x, eps = EPS) => (x > eps ? 1 : x < -eps ? -1 : 0); | ||
const absDiff = (x, y) => Math.abs(x - y); | ||
const sign = (x, eps = EPS) => x > eps ? 1 : x < -eps ? -1 : 0; | ||
export { | ||
absDiff, | ||
sign | ||
}; |
176
angle.js
import { DEG2RAD, HALF_PI, INV_HALF_PI, PI, RAD2DEG, TAU } from "./api.js"; | ||
/** | ||
* Returns vector of `[sin(theta)*n, cos(theta)*n]`. | ||
* | ||
* @param theta - | ||
* @param n - | ||
*/ | ||
export const sincos = (theta, n = 1) => [ | ||
Math.sin(theta) * n, | ||
Math.cos(theta) * n, | ||
const sincos = (theta, n = 1) => [ | ||
Math.sin(theta) * n, | ||
Math.cos(theta) * n | ||
]; | ||
/** | ||
* Returns vector of `[cos(theta)*n, sin(theta)*n]`. | ||
* | ||
* @param theta - | ||
* @param n - | ||
*/ | ||
export const cossin = (theta, n = 1) => [ | ||
Math.cos(theta) * n, | ||
Math.sin(theta) * n, | ||
const cossin = (theta, n = 1) => [ | ||
Math.cos(theta) * n, | ||
Math.sin(theta) * n | ||
]; | ||
/** | ||
* Projects `theta` into [0 .. 2π] interval. | ||
* | ||
* @param theta - | ||
*/ | ||
export const absTheta = (theta) => ((theta %= TAU), theta < 0 ? TAU + theta : theta); | ||
export const absInnerAngle = (theta) => ((theta = Math.abs(theta)), theta > PI ? TAU - theta : theta); | ||
/** | ||
* Returns smallest absolute angle difference between `a` and `b`. | ||
* Result will be in [0 .. π] interval. | ||
* | ||
* @param a - | ||
* @param b - | ||
*/ | ||
export const angleDist = (a, b) => absInnerAngle(absTheta((b % TAU) - (a % TAU))); | ||
/** | ||
* Like `Math.atan2`, but always returns angle in [0 .. TAU) interval. | ||
* | ||
* @param y - | ||
* @param x - | ||
*/ | ||
export const atan2Abs = (y, x) => absTheta(Math.atan2(y, x)); | ||
/** | ||
* Returns quadrant ID (0-3) of given angle (in radians). | ||
* | ||
* @param theta - | ||
*/ | ||
export const quadrant = (theta) => (absTheta(theta) * INV_HALF_PI) | 0; | ||
/** | ||
* Converts angle to degrees. | ||
* | ||
* @param theta - angle in radians | ||
*/ | ||
export const deg = (theta) => theta * RAD2DEG; | ||
/** | ||
* Converts angle to radians. | ||
* | ||
* @param theta - angle in degrees | ||
*/ | ||
export const rad = (theta) => theta * DEG2RAD; | ||
/** | ||
* Cosecant. Approaches `±Infinity` for `theta` near multiples of π. | ||
* | ||
* @param theta - angle in radians | ||
*/ | ||
export const csc = (theta) => 1 / Math.sin(theta); | ||
/** | ||
* Secant. Approaches `±Infinity` for `theta` near π/2 ± nπ | ||
* | ||
* @param theta - angle in radians | ||
*/ | ||
export const sec = (theta) => 1 / Math.cos(theta); | ||
/** | ||
* Cotangent. Approaches `±Infinity` for `theta` near multiples of π. | ||
* | ||
* @param theta - angle in radians | ||
*/ | ||
export const cot = (theta) => 1 / Math.tan(theta); | ||
/** | ||
* Law of Cosines. Takes length of two sides of a triangle and the inner | ||
* angle (in radians) between them. Returns length of third side. | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param gamma - | ||
*/ | ||
export const loc = (a, b, gamma) => Math.sqrt(a * a + b * b - 2 * a * b * Math.cos(gamma)); | ||
/** | ||
* Approximates cos(xπ) for x in [-1,1] | ||
* | ||
* @param x - | ||
*/ | ||
export const normCos = (x) => { | ||
const x2 = x * x; | ||
return 1.0 + x2 * (-4 + 2 * x2); | ||
const absTheta = (theta) => (theta %= TAU, theta < 0 ? TAU + theta : theta); | ||
const absInnerAngle = (theta) => (theta = Math.abs(theta), theta > PI ? TAU - theta : theta); | ||
const angleDist = (a, b) => absInnerAngle(absTheta(b % TAU - a % TAU)); | ||
const atan2Abs = (y, x) => absTheta(Math.atan2(y, x)); | ||
const quadrant = (theta) => absTheta(theta) * INV_HALF_PI | 0; | ||
const deg = (theta) => theta * RAD2DEG; | ||
const rad = (theta) => theta * DEG2RAD; | ||
const csc = (theta) => 1 / Math.sin(theta); | ||
const sec = (theta) => 1 / Math.cos(theta); | ||
const cot = (theta) => 1 / Math.tan(theta); | ||
const loc = (a, b, gamma) => Math.sqrt(a * a + b * b - 2 * a * b * Math.cos(gamma)); | ||
const normCos = (x) => { | ||
const x2 = x * x; | ||
return 1 + x2 * (-4 + 2 * x2); | ||
}; | ||
const __fastCos = (x) => { | ||
const x2 = x * x; | ||
return 0.99940307 + x2 * (-0.49558072 + 0.03679168 * x2); | ||
const x2 = x * x; | ||
return 0.99940307 + x2 * (-0.49558072 + 0.03679168 * x2); | ||
}; | ||
/** | ||
* Fast cosine approximation using {@link normCos} (polynomial). Max. error | ||
* ~0.00059693 | ||
* | ||
* In [0 .. 2π] interval, approx. 18-20% faster than `Math.cos` on V8. | ||
* | ||
* @param theta - in radians | ||
*/ | ||
export const fastCos = (theta) => { | ||
theta %= TAU; | ||
theta < 0 && (theta = -theta); | ||
switch ((theta * INV_HALF_PI) | 0) { | ||
case 0: | ||
return __fastCos(theta); | ||
case 1: | ||
return -__fastCos(PI - theta); | ||
case 2: | ||
return -__fastCos(theta - PI); | ||
default: | ||
return __fastCos(TAU - theta); | ||
} | ||
const fastCos = (theta) => { | ||
theta %= TAU; | ||
theta < 0 && (theta = -theta); | ||
switch (theta * INV_HALF_PI | 0) { | ||
case 0: | ||
return __fastCos(theta); | ||
case 1: | ||
return -__fastCos(PI - theta); | ||
case 2: | ||
return -__fastCos(theta - PI); | ||
default: | ||
return __fastCos(TAU - theta); | ||
} | ||
}; | ||
/** | ||
* {@link fastCos} | ||
* | ||
* @param theta - in radians | ||
*/ | ||
export const fastSin = (theta) => fastCos(HALF_PI - theta); | ||
const fastSin = (theta) => fastCos(HALF_PI - theta); | ||
export { | ||
absInnerAngle, | ||
absTheta, | ||
angleDist, | ||
atan2Abs, | ||
cossin, | ||
cot, | ||
csc, | ||
deg, | ||
fastCos, | ||
fastSin, | ||
loc, | ||
normCos, | ||
quadrant, | ||
rad, | ||
sec, | ||
sincos | ||
}; |
62
api.js
@@ -1,20 +0,42 @@ | ||
export const PI = Math.PI; | ||
export const TAU = PI * 2; | ||
export const HALF_PI = PI / 2; | ||
export const THIRD_PI = PI / 3; | ||
export const QUARTER_PI = PI / 4; | ||
export const SIXTH_PI = PI / 6; | ||
export const INV_PI = 1 / PI; | ||
export const INV_TAU = 1 / TAU; | ||
export const INV_HALF_PI = 1 / HALF_PI; | ||
export const DEG2RAD = PI / 180; | ||
export const RAD2DEG = 180 / PI; | ||
export const PHI = (1 + Math.sqrt(5)) / 2; | ||
export const SQRT2 = Math.SQRT2; | ||
export const SQRT3 = Math.sqrt(3); | ||
export const SQRT2_2 = SQRT2 / 2; | ||
export const SQRT3_2 = SQRT3 / 2; | ||
export const THIRD = 1 / 3; | ||
export const TWO_THIRD = 2 / 3; | ||
export const SIXTH = 1 / 6; | ||
export let EPS = 1e-6; | ||
const PI = Math.PI; | ||
const TAU = PI * 2; | ||
const HALF_PI = PI / 2; | ||
const THIRD_PI = PI / 3; | ||
const QUARTER_PI = PI / 4; | ||
const SIXTH_PI = PI / 6; | ||
const INV_PI = 1 / PI; | ||
const INV_TAU = 1 / TAU; | ||
const INV_HALF_PI = 1 / HALF_PI; | ||
const DEG2RAD = PI / 180; | ||
const RAD2DEG = 180 / PI; | ||
const PHI = (1 + Math.sqrt(5)) / 2; | ||
const SQRT2 = Math.SQRT2; | ||
const SQRT3 = Math.sqrt(3); | ||
const SQRT2_2 = SQRT2 / 2; | ||
const SQRT3_2 = SQRT3 / 2; | ||
const THIRD = 1 / 3; | ||
const TWO_THIRD = 2 / 3; | ||
const SIXTH = 1 / 6; | ||
let EPS = 1e-6; | ||
export { | ||
DEG2RAD, | ||
EPS, | ||
HALF_PI, | ||
INV_HALF_PI, | ||
INV_PI, | ||
INV_TAU, | ||
PHI, | ||
PI, | ||
QUARTER_PI, | ||
RAD2DEG, | ||
SIXTH, | ||
SIXTH_PI, | ||
SQRT2, | ||
SQRT2_2, | ||
SQRT3, | ||
SQRT3_2, | ||
TAU, | ||
THIRD, | ||
THIRD_PI, | ||
TWO_THIRD | ||
}; |
# Change Log | ||
- **Last updated**: 2023-12-09T19:12:03Z | ||
- **Last updated**: 2023-12-11T10:07:09Z | ||
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub) | ||
@@ -5,0 +5,0 @@ |
import { EPS } from "./api.js"; | ||
import { eqDelta } from "./eqdelta.js"; | ||
/** | ||
* Returns true if line A rises up over B. | ||
* | ||
* @example | ||
* ```ts | ||
* b1 a2 | ||
* \/ | ||
* /\ | ||
* a1 b2 | ||
* ``` | ||
* | ||
* @param a1 - | ||
* @param a2 - | ||
* @param b1 - | ||
* @param b2 - | ||
*/ | ||
export const isCrossOver = (a1, a2, b1, b2) => a1 < b1 && a2 > b2; | ||
/** | ||
* Returns true if line A rises up over B. | ||
* | ||
* @example | ||
* ```ts | ||
* a1 b2 | ||
* \/ | ||
* /\ | ||
* b1 a2 | ||
* ``` | ||
* | ||
* @param a1 - | ||
* @param a2 - | ||
* @param b1 - | ||
* @param b2 - | ||
*/ | ||
export const isCrossUnder = (a1, a2, b1, b2) => a1 > b1 && a2 < b2; | ||
/** | ||
* Returns {@link Crossing} classifier indicating the relationship of line A | ||
* to line B. The optional epsilon value is used to determine if both | ||
* lines are considered equal or flat. | ||
* | ||
* - {@link isCrossOver} | ||
* - {@link isCrossUnder} | ||
* - {@link Crossing} | ||
* | ||
* @param a1 - | ||
* @param a2 - | ||
* @param b1 - | ||
* @param b2 - | ||
* @param eps - | ||
*/ | ||
export const classifyCrossing = (a1, a2, b1, b2, eps = EPS) => eqDelta(a1, b1, eps) && eqDelta(a2, b2, eps) | ||
? eqDelta(a1, b2, eps) | ||
? "flat" | ||
: "equal" | ||
: isCrossOver(a1, a2, b1, b2) | ||
? "over" | ||
: isCrossUnder(a1, a2, b1, b2) | ||
? "under" | ||
: "other"; | ||
const isCrossOver = (a1, a2, b1, b2) => a1 < b1 && a2 > b2; | ||
const isCrossUnder = (a1, a2, b1, b2) => a1 > b1 && a2 < b2; | ||
const classifyCrossing = (a1, a2, b1, b2, eps = EPS) => eqDelta(a1, b1, eps) && eqDelta(a2, b2, eps) ? eqDelta(a1, b2, eps) ? "flat" : "equal" : isCrossOver(a1, a2, b1, b2) ? "over" : isCrossUnder(a1, a2, b1, b2) ? "under" : "other"; | ||
export { | ||
classifyCrossing, | ||
isCrossOver, | ||
isCrossUnder | ||
}; |
import { EPS } from "./api.js"; | ||
const abs = Math.abs; | ||
const max = Math.max; | ||
/** | ||
* Similar to {@link eqDeltaScaled}, but uses given `eps` as is. | ||
* | ||
* @param a - left value | ||
* @param b - right value | ||
* @param eps - epsilon / tolerance, default `1e-6` | ||
*/ | ||
export const eqDelta = (a, b, eps = EPS) => abs(a - b) <= eps; | ||
/** | ||
* Checks if `|a - b| <= ε` and adapts given epsilon value to the given | ||
* arguments: | ||
* | ||
* ε is factored with the largest absolute value of `a` or `b` (but | ||
* never lesser than the given `eps` value): | ||
* | ||
* `ε = ε * max(1, |a|, |b|)` | ||
* | ||
* @param a - left value | ||
* @param b - right value | ||
* @param eps - epsilon / tolerance, default `1e-6` | ||
*/ | ||
export const eqDeltaScaled = (a, b, eps = EPS) => abs(a - b) <= eps * max(1, abs(a), abs(b)); | ||
const eqDelta = (a, b, eps = EPS) => abs(a - b) <= eps; | ||
const eqDeltaScaled = (a, b, eps = EPS) => abs(a - b) <= eps * max(1, abs(a), abs(b)); | ||
export { | ||
eqDelta, | ||
eqDeltaScaled | ||
}; |
@@ -1,74 +0,32 @@ | ||
/** | ||
* Returns true if `b` is a local minima, i.e. iff a > b and b < c. | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param c - | ||
*/ | ||
export const isMinima = (a, b, c) => a > b && b < c; | ||
/** | ||
* Returns true if `b` is a local maxima, i.e. iff a < b and b > c. | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param c - | ||
*/ | ||
export const isMaxima = (a, b, c) => a < b && b > c; | ||
const isMinima = (a, b, c) => a > b && b < c; | ||
const isMaxima = (a, b, c) => a < b && b > c; | ||
const index = (pred, values, from = 0, to = values.length) => { | ||
to--; | ||
for (let i = from + 1; i < to; i++) { | ||
if (pred(values[i - 1], values[i], values[i + 1])) { | ||
return i; | ||
} | ||
to--; | ||
for (let i = from + 1; i < to; i++) { | ||
if (pred(values[i - 1], values[i], values[i + 1])) { | ||
return i; | ||
} | ||
return -1; | ||
} | ||
return -1; | ||
}; | ||
/** | ||
* Returns index of the first local & internal minima found in given | ||
* `values` array, or -1 if no such minima exists. The search range can | ||
* be optionally defined via semi-open [from, to) index interval. | ||
* | ||
* @param values - | ||
* @param from - | ||
* @param to - | ||
*/ | ||
export const minimaIndex = (values, from = 0, to = values.length) => index(isMinima, values, from, to); | ||
/** | ||
* Returns index of the first local & internal maxima found in given | ||
* `values` array, or -1 if no such maxima exists. The search range can | ||
* be optionally defined via semi-open [from, to) index interval. | ||
* | ||
* @param values - | ||
* @param from - | ||
* @param to - | ||
*/ | ||
export const maximaIndex = (values, from = 0, to = values.length) => index(isMaxima, values, from, to); | ||
const minimaIndex = (values, from = 0, to = values.length) => index(isMinima, values, from, to); | ||
const maximaIndex = (values, from = 0, to = values.length) => index(isMaxima, values, from, to); | ||
function* indices(fn, vals, from = 0, to = vals.length) { | ||
while (from < to) { | ||
const i = fn(vals, from, to); | ||
if (i < 0) | ||
return; | ||
yield i; | ||
from = i + 1; | ||
} | ||
while (from < to) { | ||
const i = fn(vals, from, to); | ||
if (i < 0) | ||
return; | ||
yield i; | ||
from = i + 1; | ||
} | ||
} | ||
/** | ||
* Returns an iterator yielding all minima indices in given `values` | ||
* array. The search range can be optionally defined via semi-open | ||
* [from, to) index interval. | ||
* | ||
* @param values - | ||
* @param from - | ||
* @param to - | ||
*/ | ||
export const minimaIndices = (values, from = 0, to = values.length) => indices(minimaIndex, values, from, to); | ||
/** | ||
* Returns an iterator yielding all maxima indices in given `values` | ||
* array. The search range can be optionally defined via semi-open | ||
* [from, to) index interval. | ||
* | ||
* @param values - | ||
* @param from - | ||
* @param to - | ||
*/ | ||
export const maximaIndices = (values, from = 0, to = values.length) => indices(minimaIndex, values, from, to); | ||
const minimaIndices = (values, from = 0, to = values.length) => indices(minimaIndex, values, from, to); | ||
const maximaIndices = (values, from = 0, to = values.length) => indices(minimaIndex, values, from, to); | ||
export { | ||
isMaxima, | ||
isMinima, | ||
maximaIndex, | ||
maximaIndices, | ||
minimaIndex, | ||
minimaIndices | ||
}; |
71
fit.js
import { clamp01, clamp11 } from "./interval.js"; | ||
/** | ||
* Returns normalized value of `x` WRT to interval `a .. b`. If `a` | ||
* equals `b`, returns 0. | ||
* | ||
* @param x - | ||
* @param a - | ||
* @param b - | ||
*/ | ||
export const norm = (x, a, b) => (b !== a ? (x - a) / (b - a) : 0); | ||
/** | ||
* Returns a number in the `[c,d]` interval which is relative to `x` in the | ||
* `[a,b]` interval. **No** clamping will be performed if `x` lies outside the | ||
* original range (for that use {@link fitClamped} instead). | ||
* | ||
* @param x | ||
* @param a | ||
* @param b | ||
* @param c | ||
* @param d | ||
*/ | ||
export const fit = (x, a, b, c, d) => c + (d - c) * norm(x, a, b); | ||
/** | ||
* Clamped version of {@link fit}, i.e. before mapping `x` into the target | ||
* interval `[c,d]`, it will be clamped to the source interval `[a,b]`. | ||
* | ||
* @param x | ||
* @param a | ||
* @param b | ||
* @param c | ||
* @param d | ||
*/ | ||
export const fitClamped = (x, a, b, c, d) => c + (d - c) * clamp01(norm(x, a, b)); | ||
/** | ||
* Similar to {@link fitClamped}, assuming [0,1] as source interval. | ||
* | ||
* @param x | ||
* @param a | ||
* @param b | ||
*/ | ||
export const fit01 = (x, a, b) => a + (b - a) * clamp01(x); | ||
/** | ||
* Similar to {@link fitClamped}, assuming the reverse ordered [1,0] as source | ||
* interval. | ||
* | ||
* @param x | ||
* @param a | ||
* @param b | ||
*/ | ||
export const fit10 = (x, a, b) => b + (a - b) * clamp01(x); | ||
/** | ||
* Similar to {@link fitClamped}, assuming [-1,1] as source interval. | ||
* | ||
* @param x | ||
* @param a | ||
* @param b | ||
*/ | ||
export const fit11 = (x, a, b) => a + (b - a) * (0.5 + 0.5 * clamp11(x)); | ||
const norm = (x, a, b) => b !== a ? (x - a) / (b - a) : 0; | ||
const fit = (x, a, b, c, d) => c + (d - c) * norm(x, a, b); | ||
const fitClamped = (x, a, b, c, d) => c + (d - c) * clamp01(norm(x, a, b)); | ||
const fit01 = (x, a, b) => a + (b - a) * clamp01(x); | ||
const fit10 = (x, a, b) => b + (a - b) * clamp01(x); | ||
const fit11 = (x, a, b) => a + (b - a) * (0.5 + 0.5 * clamp11(x)); | ||
export { | ||
fit, | ||
fit01, | ||
fit10, | ||
fit11, | ||
fitClamped, | ||
norm | ||
}; |
192
int.js
@@ -1,64 +0,128 @@ | ||
const M8 = 0xff; | ||
const M16 = 0xffff; | ||
export const signExtend8 = (a) => ((a &= M8), a & 0x80 ? a | ~M8 : a); | ||
export const signExtend16 = (a) => ((a &= M16), a & 0x8000 ? a | ~M16 : a); | ||
export const addi8 = (a, b) => signExtend8((a | 0) + (b | 0)); | ||
export const divi8 = (a, b) => signExtend8((a | 0) / (b | 0)); | ||
export const muli8 = (a, b) => signExtend8((a | 0) * (b | 0)); | ||
export const subi8 = (a, b) => signExtend8((a | 0) - (b | 0)); | ||
export const andi8 = (a, b) => signExtend8((a | 0) & (b | 0)); | ||
export const ori8 = (a, b) => signExtend8(a | 0 | (b | 0)); | ||
export const xori8 = (a, b) => signExtend8((a | 0) ^ (b | 0)); | ||
export const noti8 = (a) => signExtend8(~a); | ||
export const lshifti8 = (a, b) => signExtend8((a | 0) << (b | 0)); | ||
export const rshifti8 = (a, b) => signExtend8((a | 0) >> (b | 0)); | ||
export const addi16 = (a, b) => signExtend16((a | 0) + (b | 0)); | ||
export const divi16 = (a, b) => signExtend16((a | 0) / (b | 0)); | ||
export const muli16 = (a, b) => signExtend16((a | 0) * (b | 0)); | ||
export const subi16 = (a, b) => signExtend16((a | 0) - (b | 0)); | ||
export const andi16 = (a, b) => signExtend16((a | 0) & (b | 0)); | ||
export const ori16 = (a, b) => signExtend16(a | 0 | (b | 0)); | ||
export const xori16 = (a, b) => signExtend16((a | 0) ^ (b | 0)); | ||
export const noti16 = (a) => signExtend16(~a); | ||
export const lshifti16 = (a, b) => signExtend16((a | 0) << (b | 0)); | ||
export const rshifti16 = (a, b) => signExtend16((a | 0) >> (b | 0)); | ||
export const addi32 = (a, b) => ((a | 0) + (b | 0)) | 0; | ||
export const divi32 = (a, b) => ((a | 0) / (b | 0)) | 0; | ||
export const muli32 = (a, b) => ((a | 0) * (b | 0)) | 0; | ||
export const subi32 = (a, b) => ((a | 0) - (b | 0)) | 0; | ||
export const andi32 = (a, b) => (a | 0) & (b | 0); | ||
export const ori32 = (a, b) => a | 0 | (b | 0); | ||
export const xori32 = (a, b) => (a | 0) ^ (b | 0); | ||
export const lshifti32 = (a, b) => (a | 0) << (b | 0); | ||
export const rshifti32 = (a, b) => (a | 0) >> (b | 0); | ||
export const noti32 = (a) => ~a; | ||
export const addu8 = (a, b) => ((a & M8) + (b & M8)) & M8; | ||
export const divu8 = (a, b) => ((a & M8) / (b & M8)) & M8; | ||
export const mulu8 = (a, b) => ((a & M8) * (b & M8)) & M8; | ||
export const subu8 = (a, b) => ((a & M8) - (b & M8)) & M8; | ||
export const andu8 = (a, b) => a & M8 & (b & M8) & M8; | ||
export const oru8 = (a, b) => ((a & M8) | (b & M8)) & M8; | ||
export const xoru8 = (a, b) => ((a & M8) ^ (b & M8)) & M8; | ||
export const notu8 = (a) => ~a & M8; | ||
export const lshiftu8 = (a, b) => ((a & M8) << (b & M8)) & M8; | ||
export const rshiftu8 = (a, b) => ((a & M8) >>> (b & M8)) & M8; | ||
export const addu16 = (a, b) => ((a & M16) + (b & M16)) & M16; | ||
export const divu16 = (a, b) => ((a & M16) / (b & M16)) & M16; | ||
export const mulu16 = (a, b) => ((a & M16) * (b & M16)) & M16; | ||
export const subu16 = (a, b) => ((a & M16) - (b & M16)) & M16; | ||
export const andu16 = (a, b) => a & M16 & (b & M16) & M16; | ||
export const oru16 = (a, b) => ((a & M16) | (b & M16)) & M16; | ||
export const xoru16 = (a, b) => ((a & M16) ^ (b & M16)) & M16; | ||
export const notu16 = (a) => ~a & M16; | ||
export const lshiftu16 = (a, b) => ((a & M16) << (b & M16)) & M16; | ||
export const rshiftu16 = (a, b) => ((a & M16) >>> (b & M16)) & M16; | ||
export const addu32 = (a, b) => ((a >>> 0) + (b >>> 0)) >>> 0; | ||
export const divu32 = (a, b) => ((a >>> 0) / (b >>> 0)) >>> 0; | ||
export const mulu32 = (a, b) => ((a >>> 0) * (b >>> 0)) >>> 0; | ||
export const subu32 = (a, b) => ((a >>> 0) - (b >>> 0)) >>> 0; | ||
export const andu32 = (a, b) => ((a >>> 0) & (b >>> 0)) >>> 0; | ||
export const oru32 = (a, b) => ((a >>> 0) | (b >>> 0)) >>> 0; | ||
export const xoru32 = (a, b) => ((a >>> 0) ^ (b >>> 0)) >>> 0; | ||
export const notu32 = (a) => ~a >>> 0; | ||
export const lshiftu32 = (a, b) => ((a >>> 0) << (b >>> 0)) >>> 0; | ||
export const rshiftu32 = (a, b) => ((a >>> 0) >>> (b >>> 0)) >>> 0; | ||
const M8 = 255; | ||
const M16 = 65535; | ||
const signExtend8 = (a) => (a &= M8, a & 128 ? a | ~M8 : a); | ||
const signExtend16 = (a) => (a &= M16, a & 32768 ? a | ~M16 : a); | ||
const addi8 = (a, b) => signExtend8((a | 0) + (b | 0)); | ||
const divi8 = (a, b) => signExtend8((a | 0) / (b | 0)); | ||
const muli8 = (a, b) => signExtend8((a | 0) * (b | 0)); | ||
const subi8 = (a, b) => signExtend8((a | 0) - (b | 0)); | ||
const andi8 = (a, b) => signExtend8((a | 0) & (b | 0)); | ||
const ori8 = (a, b) => signExtend8(a | 0 | (b | 0)); | ||
const xori8 = (a, b) => signExtend8((a | 0) ^ (b | 0)); | ||
const noti8 = (a) => signExtend8(~a); | ||
const lshifti8 = (a, b) => signExtend8((a | 0) << (b | 0)); | ||
const rshifti8 = (a, b) => signExtend8((a | 0) >> (b | 0)); | ||
const addi16 = (a, b) => signExtend16((a | 0) + (b | 0)); | ||
const divi16 = (a, b) => signExtend16((a | 0) / (b | 0)); | ||
const muli16 = (a, b) => signExtend16((a | 0) * (b | 0)); | ||
const subi16 = (a, b) => signExtend16((a | 0) - (b | 0)); | ||
const andi16 = (a, b) => signExtend16((a | 0) & (b | 0)); | ||
const ori16 = (a, b) => signExtend16(a | 0 | (b | 0)); | ||
const xori16 = (a, b) => signExtend16((a | 0) ^ (b | 0)); | ||
const noti16 = (a) => signExtend16(~a); | ||
const lshifti16 = (a, b) => signExtend16((a | 0) << (b | 0)); | ||
const rshifti16 = (a, b) => signExtend16((a | 0) >> (b | 0)); | ||
const addi32 = (a, b) => (a | 0) + (b | 0) | 0; | ||
const divi32 = (a, b) => (a | 0) / (b | 0) | 0; | ||
const muli32 = (a, b) => (a | 0) * (b | 0) | 0; | ||
const subi32 = (a, b) => (a | 0) - (b | 0) | 0; | ||
const andi32 = (a, b) => (a | 0) & (b | 0); | ||
const ori32 = (a, b) => a | 0 | (b | 0); | ||
const xori32 = (a, b) => (a | 0) ^ (b | 0); | ||
const lshifti32 = (a, b) => (a | 0) << (b | 0); | ||
const rshifti32 = (a, b) => (a | 0) >> (b | 0); | ||
const noti32 = (a) => ~a; | ||
const addu8 = (a, b) => (a & M8) + (b & M8) & M8; | ||
const divu8 = (a, b) => (a & M8) / (b & M8) & M8; | ||
const mulu8 = (a, b) => (a & M8) * (b & M8) & M8; | ||
const subu8 = (a, b) => (a & M8) - (b & M8) & M8; | ||
const andu8 = (a, b) => a & M8 & (b & M8) & M8; | ||
const oru8 = (a, b) => (a & M8 | b & M8) & M8; | ||
const xoru8 = (a, b) => (a & M8 ^ b & M8) & M8; | ||
const notu8 = (a) => ~a & M8; | ||
const lshiftu8 = (a, b) => (a & M8) << (b & M8) & M8; | ||
const rshiftu8 = (a, b) => (a & M8) >>> (b & M8) & M8; | ||
const addu16 = (a, b) => (a & M16) + (b & M16) & M16; | ||
const divu16 = (a, b) => (a & M16) / (b & M16) & M16; | ||
const mulu16 = (a, b) => (a & M16) * (b & M16) & M16; | ||
const subu16 = (a, b) => (a & M16) - (b & M16) & M16; | ||
const andu16 = (a, b) => a & M16 & (b & M16) & M16; | ||
const oru16 = (a, b) => (a & M16 | b & M16) & M16; | ||
const xoru16 = (a, b) => (a & M16 ^ b & M16) & M16; | ||
const notu16 = (a) => ~a & M16; | ||
const lshiftu16 = (a, b) => (a & M16) << (b & M16) & M16; | ||
const rshiftu16 = (a, b) => (a & M16) >>> (b & M16) & M16; | ||
const addu32 = (a, b) => (a >>> 0) + (b >>> 0) >>> 0; | ||
const divu32 = (a, b) => (a >>> 0) / (b >>> 0) >>> 0; | ||
const mulu32 = (a, b) => (a >>> 0) * (b >>> 0) >>> 0; | ||
const subu32 = (a, b) => (a >>> 0) - (b >>> 0) >>> 0; | ||
const andu32 = (a, b) => (a >>> 0 & b >>> 0) >>> 0; | ||
const oru32 = (a, b) => (a >>> 0 | b >>> 0) >>> 0; | ||
const xoru32 = (a, b) => (a >>> 0 ^ b >>> 0) >>> 0; | ||
const notu32 = (a) => ~a >>> 0; | ||
const lshiftu32 = (a, b) => a >>> 0 << (b >>> 0) >>> 0; | ||
const rshiftu32 = (a, b) => a >>> 0 >>> (b >>> 0) >>> 0; | ||
export { | ||
addi16, | ||
addi32, | ||
addi8, | ||
addu16, | ||
addu32, | ||
addu8, | ||
andi16, | ||
andi32, | ||
andi8, | ||
andu16, | ||
andu32, | ||
andu8, | ||
divi16, | ||
divi32, | ||
divi8, | ||
divu16, | ||
divu32, | ||
divu8, | ||
lshifti16, | ||
lshifti32, | ||
lshifti8, | ||
lshiftu16, | ||
lshiftu32, | ||
lshiftu8, | ||
muli16, | ||
muli32, | ||
muli8, | ||
mulu16, | ||
mulu32, | ||
mulu8, | ||
noti16, | ||
noti32, | ||
noti8, | ||
notu16, | ||
notu32, | ||
notu8, | ||
ori16, | ||
ori32, | ||
ori8, | ||
oru16, | ||
oru32, | ||
oru8, | ||
rshifti16, | ||
rshifti32, | ||
rshifti8, | ||
rshiftu16, | ||
rshiftu32, | ||
rshiftu8, | ||
signExtend16, | ||
signExtend8, | ||
subi16, | ||
subi32, | ||
subi8, | ||
subu16, | ||
subu32, | ||
subu8, | ||
xori16, | ||
xori32, | ||
xori8, | ||
xoru16, | ||
xoru32, | ||
xoru8 | ||
}; |
278
interval.js
@@ -1,207 +0,75 @@ | ||
/** | ||
* Clamps value `x` to given closed interval. | ||
* | ||
* @param x - value to clamp | ||
* @param min - lower bound | ||
* @param max - upper bound | ||
*/ | ||
export const clamp = (x, min, max) => (x < min ? min : x > max ? max : x); | ||
/** | ||
* Clamps value `x` to closed [0 .. ∞] interval. | ||
* | ||
* @param x - | ||
*/ | ||
export const clamp0 = (x) => (x > 0 ? x : 0); | ||
/** | ||
* Clamps value `x` to closed [0 .. 1] interval. | ||
* | ||
* @param x - | ||
*/ | ||
export const clamp01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x); | ||
/** | ||
* Clamps value `x` to closed [-1 .. 1] interval. | ||
* | ||
* @param x - | ||
*/ | ||
export const clamp11 = (x) => (x < -1 ? -1 : x > 1 ? 1 : x); | ||
/** | ||
* Clamps value `x` to closed [0 .. 0.5] interval. | ||
* | ||
* @param x - | ||
*/ | ||
export const clamp05 = (x) => (x < 0 ? 0 : x > 0.5 ? 0.5 : x); | ||
/** | ||
* Returns 2-tuple of [min(x,y), max(x,y)]. | ||
* | ||
* @param x | ||
* @param y | ||
*/ | ||
export const minMax = (x, y) => x < y ? [x, y] : [y, x]; | ||
/** | ||
* Folds `x` back inside closed [min..max] interval. Also see | ||
* {@link wrapOnce}. | ||
* | ||
* @param x - | ||
* @param min - | ||
* @param max - | ||
*/ | ||
export const wrap = (x, min, max) => { | ||
if (min === max) | ||
return min; | ||
if (x > max) { | ||
const d = max - min; | ||
x -= d; | ||
if (x > max) | ||
x -= d * (((x - min) / d) | 0); | ||
} | ||
else if (x < min) { | ||
const d = max - min; | ||
x += d; | ||
if (x < min) | ||
x += d * (((min - x) / d + 1) | 0); | ||
} | ||
return x; | ||
const clamp = (x, min, max) => x < min ? min : x > max ? max : x; | ||
const clamp0 = (x) => x > 0 ? x : 0; | ||
const clamp01 = (x) => x < 0 ? 0 : x > 1 ? 1 : x; | ||
const clamp11 = (x) => x < -1 ? -1 : x > 1 ? 1 : x; | ||
const clamp05 = (x) => x < 0 ? 0 : x > 0.5 ? 0.5 : x; | ||
const minMax = (x, y) => x < y ? [x, y] : [y, x]; | ||
const wrap = (x, min, max) => { | ||
if (min === max) | ||
return min; | ||
if (x > max) { | ||
const d = max - min; | ||
x -= d; | ||
if (x > max) | ||
x -= d * ((x - min) / d | 0); | ||
} else if (x < min) { | ||
const d = max - min; | ||
x += d; | ||
if (x < min) | ||
x += d * ((min - x) / d + 1 | 0); | ||
} | ||
return x; | ||
}; | ||
/** | ||
* Like {@link wrap}, but optimized for cases where `x` is guaranteed to | ||
* be in `[min - d, max + d]` interval, where `d = max - min`. Result | ||
* will be in closed `[min..max]` interval. | ||
* | ||
* @param x - | ||
* @param min - | ||
* @param max - | ||
*/ | ||
export const wrapOnce = (x, min, max) => x < min ? x - min + max : x > max ? x - max + min : x; | ||
/** | ||
* Similar to {@link wrapOnce} for [0..1] interval. | ||
* | ||
* @param x - | ||
*/ | ||
export const wrap01 = (x) => (x < 0 ? x + 1 : x > 1 ? x - 1 : x); | ||
/** | ||
* Similar to {@link wrapOnce} for [-1..1] interval. | ||
* | ||
* @param x - | ||
*/ | ||
export const wrap11 = (x) => (x < -1 ? x + 2 : x > 1 ? x - 2 : x); | ||
export const min2id = (a, b) => (a <= b ? 0 : 1); | ||
export const min3id = (a, b, c) => a <= b ? (a <= c ? 0 : 2) : b <= c ? 1 : 2; | ||
export const min4id = (a, b, c, d) => a <= b | ||
? a <= c | ||
? a <= d | ||
? 0 | ||
: 3 | ||
: c <= d | ||
? 2 | ||
: 3 | ||
: b <= c | ||
? b <= d | ||
? 1 | ||
: 3 | ||
: c <= d | ||
? 2 | ||
: 3; | ||
export const max2id = (a, b) => (a >= b ? 0 : 1); | ||
export const max3id = (a, b, c) => a >= b ? (a >= c ? 0 : 2) : b >= c ? 1 : 2; | ||
export const max4id = (a, b, c, d) => a >= b | ||
? a >= c | ||
? a >= d | ||
? 0 | ||
: 3 | ||
: c >= d | ||
? 2 | ||
: 3 | ||
: b >= c | ||
? b >= d | ||
? 1 | ||
: 3 | ||
: c >= d | ||
? 2 | ||
: 3; | ||
/** | ||
* Returns the non-zero minimum value of the given `a`, `b` args. | ||
* | ||
* @param a - | ||
* @param b - | ||
*/ | ||
export const minNonZero2 = (a, b) => a !== 0 ? (b !== 0 ? Math.min(a, b) : a) : b; | ||
/** | ||
* Returns the non-zero minimum value of the given `a`, `b`, `c` args. | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param c - | ||
*/ | ||
export const minNonZero3 = (a, b, c) => minNonZero2(minNonZero2(a, b), c); | ||
/** | ||
* See `smax()`. | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param k - smooth exponent (MUST be > 0) | ||
*/ | ||
export const smin = (a, b, k) => smax(a, b, -k); | ||
/** | ||
* Smooth maximum. Note: Result values will be slightly larger than max value | ||
* near max(a,b) + eps due to exponential decay. Higher `k` values reduce the | ||
* error, but also reduce the smoothing. Recommended k=16. | ||
* | ||
* https://en.wikipedia.org/wiki/Smooth_maximum | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param k - smooth exponent (MUST be > 0) | ||
*/ | ||
export const smax = (a, b, k) => { | ||
const ea = Math.exp(a * k); | ||
const eb = Math.exp(b * k); | ||
return (a * ea + b * eb) / (ea + eb); | ||
const wrapOnce = (x, min, max) => x < min ? x - min + max : x > max ? x - max + min : x; | ||
const wrap01 = (x) => x < 0 ? x + 1 : x > 1 ? x - 1 : x; | ||
const wrap11 = (x) => x < -1 ? x + 2 : x > 1 ? x - 2 : x; | ||
const min2id = (a, b) => a <= b ? 0 : 1; | ||
const min3id = (a, b, c) => a <= b ? a <= c ? 0 : 2 : b <= c ? 1 : 2; | ||
const min4id = (a, b, c, d) => a <= b ? a <= c ? a <= d ? 0 : 3 : c <= d ? 2 : 3 : b <= c ? b <= d ? 1 : 3 : c <= d ? 2 : 3; | ||
const max2id = (a, b) => a >= b ? 0 : 1; | ||
const max3id = (a, b, c) => a >= b ? a >= c ? 0 : 2 : b >= c ? 1 : 2; | ||
const max4id = (a, b, c, d) => a >= b ? a >= c ? a >= d ? 0 : 3 : c >= d ? 2 : 3 : b >= c ? b >= d ? 1 : 3 : c >= d ? 2 : 3; | ||
const minNonZero2 = (a, b) => a !== 0 ? b !== 0 ? Math.min(a, b) : a : b; | ||
const minNonZero3 = (a, b, c) => minNonZero2(minNonZero2(a, b), c); | ||
const smin = (a, b, k) => smax(a, b, -k); | ||
const smax = (a, b, k) => { | ||
const ea = Math.exp(a * k); | ||
const eb = Math.exp(b * k); | ||
return (a * ea + b * eb) / (ea + eb); | ||
}; | ||
/** | ||
* Same as `smin(smax(x, min, k), max, k)`. | ||
* | ||
* @param x - | ||
* @param min - | ||
* @param max - | ||
* @param k - | ||
*/ | ||
export const sclamp = (x, min, max, k) => smin(smax(x, min, k), max, k); | ||
export const absMin = (a, b) => (Math.abs(a) < Math.abs(b) ? a : b); | ||
export const absMax = (a, b) => (Math.abs(a) > Math.abs(b) ? a : b); | ||
/** | ||
* If `abs(x) > abs(e)`, recursively mirrors `x` back into `[-e .. +e]` | ||
* interval at respective positive/negative boundary. | ||
* | ||
* @remarks | ||
* References: | ||
* - https://www.desmos.com/calculator/lkyf2ag3ta | ||
* - https://www.musicdsp.org/en/latest/Effects/203-fold-back-distortion.html | ||
* | ||
* @param e - threshold (> 0) | ||
* @param x - input value | ||
*/ | ||
export const foldback = (e, x) => x < -e || x > e ? Math.abs(Math.abs((x - e) % (4 * e)) - 2 * e) - e : x; | ||
/** | ||
* Similar to {@link foldback}, but with fixed target range: Folds `x` into the | ||
* closed [0..1] interval, using infinite internal reflection on either side of | ||
* the interval. | ||
* | ||
* @param x | ||
*/ | ||
export const foldback01 = (x) => ((x = Math.abs(x) % 2) > 1 ? 2 - x : x); | ||
/** | ||
* Returns true iff `x` is in closed interval `[min .. max]` | ||
* | ||
* @param x - | ||
* @param min - | ||
* @param max - | ||
*/ | ||
export const inRange = (x, min, max) => x >= min && x <= max; | ||
/** | ||
* Returns true iff `x` is in open interval `(min .. max)` | ||
* | ||
* @param x - | ||
* @param min - | ||
* @param max - | ||
*/ | ||
export const inOpenRange = (x, min, max) => x > min && x < max; | ||
const sclamp = (x, min, max, k) => smin(smax(x, min, k), max, k); | ||
const absMin = (a, b) => Math.abs(a) < Math.abs(b) ? a : b; | ||
const absMax = (a, b) => Math.abs(a) > Math.abs(b) ? a : b; | ||
const foldback = (e, x) => x < -e || x > e ? Math.abs(Math.abs((x - e) % (4 * e)) - 2 * e) - e : x; | ||
const foldback01 = (x) => (x = Math.abs(x) % 2) > 1 ? 2 - x : x; | ||
const inRange = (x, min, max) => x >= min && x <= max; | ||
const inOpenRange = (x, min, max) => x > min && x < max; | ||
export { | ||
absMax, | ||
absMin, | ||
clamp, | ||
clamp0, | ||
clamp01, | ||
clamp05, | ||
clamp11, | ||
foldback, | ||
foldback01, | ||
inOpenRange, | ||
inRange, | ||
max2id, | ||
max3id, | ||
max4id, | ||
min2id, | ||
min3id, | ||
min4id, | ||
minMax, | ||
minNonZero2, | ||
minNonZero3, | ||
sclamp, | ||
smax, | ||
smin, | ||
wrap, | ||
wrap01, | ||
wrap11, | ||
wrapOnce | ||
}; |
145
libc.js
@@ -1,109 +0,40 @@ | ||
/** | ||
* Returns a value with the magnitude of `x` and the sign of `y`. | ||
* | ||
* @param x - | ||
* @param y - | ||
*/ | ||
export const copysign = (x, y) => Math.sign(y) * Math.abs(x); | ||
/** | ||
* Returns `2^x`. | ||
* | ||
* @param x - | ||
*/ | ||
export const exp2 = (x) => 2 ** x; | ||
/** | ||
* Returns the positive difference between `x` and `y`, i.e. `x - y` iff `x > y`, | ||
* otherwise zero. | ||
* | ||
* @param x - | ||
* @param y - | ||
*/ | ||
export const fdim = (x, y) => Math.max(x - y, 0); | ||
/** | ||
* Returns `x * y + z`. | ||
* | ||
* @param x - | ||
* @param y - | ||
* @param z - | ||
*/ | ||
export const fma = (x, y, z) => x * y + z; | ||
/** | ||
* Similar to {@link mod}, {@link remainder}. Returns `x - y * trunc(x / y)`, | ||
* i.e. essentially the same as JS `%` operator. Result will always have the | ||
* sign of `x`. | ||
* | ||
* @remarks | ||
* **Caution:** Due to the introduction of libc math functions in v4.0.0 and the | ||
* resulting name/behavior clashes between the modulo logic in JS, C & GLSL, the | ||
* previous `fmod` function has been renamed to {@link mod} to align w/ its GLSL | ||
* version and now exhibits a different behavior to this current {@link fmod} | ||
* function. | ||
* | ||
* Reference: https://www.cplusplus.com/reference/cmath/fmod/ | ||
* | ||
* @param x - | ||
* @param y - | ||
*/ | ||
export const fmod = (x, y) => x % y; | ||
//export const fmod: FnN2 = (x, y) => x - y * Math.trunc(x / y); | ||
/** | ||
* Inverse op of {@link ldexp}. Breaks the number `x` into its binary | ||
* significand (a floating point with an abs value in `[0.5,1.0)` interval and | ||
* an integral exponent for 2, such that: `x = significand * 2^exp`. Returns | ||
* tuple of `[sig, exp]`. | ||
* | ||
* @remarks | ||
* - If `x` is zero, both parts (significand and exponent) are zero. | ||
* - If `x` is negative, the significand returned by this function is negative. | ||
* | ||
* Based on: | ||
* https://github.com/locutusjs/locutus/blob/master/src/c/math/frexp.js | ||
* | ||
* @param x - | ||
*/ | ||
export const frexp = (x) => { | ||
if (x === 0 || !isFinite(x)) | ||
return [x, 0]; | ||
const abs = Math.abs(x); | ||
let exp = Math.max(-1023, Math.floor(Math.log2(abs)) + 1); | ||
let y = abs * 2 ** -exp; | ||
while (y < 0.5) { | ||
y *= 2; | ||
exp--; | ||
} | ||
while (y >= 1) { | ||
y *= 0.5; | ||
exp++; | ||
} | ||
return [x < 0 ? -y : y, exp]; | ||
const copysign = (x, y) => Math.sign(y) * Math.abs(x); | ||
const exp2 = (x) => 2 ** x; | ||
const fdim = (x, y) => Math.max(x - y, 0); | ||
const fma = (x, y, z) => x * y + z; | ||
const fmod = (x, y) => x % y; | ||
const frexp = (x) => { | ||
if (x === 0 || !isFinite(x)) | ||
return [x, 0]; | ||
const abs = Math.abs(x); | ||
let exp = Math.max(-1023, Math.floor(Math.log2(abs)) + 1); | ||
let y = abs * 2 ** -exp; | ||
while (y < 0.5) { | ||
y *= 2; | ||
exp--; | ||
} | ||
while (y >= 1) { | ||
y *= 0.5; | ||
exp++; | ||
} | ||
return [x < 0 ? -y : y, exp]; | ||
}; | ||
/** | ||
* Inverse op of {@link frexp}. Returns `x * 2^exp` | ||
* | ||
* @param x - | ||
* @param exp - | ||
*/ | ||
export const ldexp = (x, exp) => x * 2 ** exp; | ||
/** | ||
* Similar to {@link fmod}, {@link mod}. Returns `x - y * round(x / y)`. | ||
* | ||
* @remarks | ||
* https://www.cplusplus.com/reference/cmath/remainder/ | ||
* | ||
* @param x - | ||
* @param y - | ||
*/ | ||
export const remainder = (x, y) => x - y * Math.round(x / y); | ||
/** | ||
* Computes both the quotient and remainder of the integer division of the | ||
* numerator `x` by the denominator `y`. | ||
* | ||
* @param x - | ||
* @param y - | ||
*/ | ||
export const ldiv = (x, y) => { | ||
x |= 0; | ||
y |= 0; | ||
const q = (x / y) | 0; | ||
return [q, x - q * y]; | ||
const ldexp = (x, exp) => x * 2 ** exp; | ||
const remainder = (x, y) => x - y * Math.round(x / y); | ||
const ldiv = (x, y) => { | ||
x |= 0; | ||
y |= 0; | ||
const q = x / y | 0; | ||
return [q, x - q * y]; | ||
}; | ||
export { | ||
copysign, | ||
exp2, | ||
fdim, | ||
fma, | ||
fmod, | ||
frexp, | ||
ldexp, | ||
ldiv, | ||
remainder | ||
}; |
import { EPS } from "./api.js"; | ||
/** | ||
* Recursively evaluates function `fn` for `res` uniformly spaced values | ||
* `t` in the closed parametric interval `[start,end]` and computes | ||
* corresponding sample values `p`. For each `p` then calls `error` | ||
* function to compute the error to query target value `q` and | ||
* eventually returns the `t` producing the overall minimum error. At | ||
* each level of recursion the search interval is increasingly narrowed | ||
* / centered around the best `t` of the current iteration. | ||
* | ||
* The search is terminated early if the best error value is less than | ||
* `eps`. | ||
* | ||
* The interval end points `start` and `end` MUST be normalized values | ||
* in the closed [0,1] interval. | ||
* | ||
* @param fn - function to evaluate | ||
* @param error - error function | ||
* @param q - target value | ||
* @param res - number of samples per interval | ||
* @param iter - max number of iterations / recursion limit | ||
* @param start - interval start | ||
* @param end - interval end | ||
*/ | ||
export const minError = (fn, error, q, res = 16, iter = 8, start = 0, end = 1, eps = EPS) => { | ||
if (iter <= 0) | ||
return (start + end) / 2; | ||
const delta = (end - start) / res; | ||
let minT = start; | ||
let minE = Infinity; | ||
for (let i = 0; i <= res; i++) { | ||
const t = start + i * delta; | ||
const e = error(q, fn(t)); | ||
if (e < minE) { | ||
if (e <= eps) | ||
return t; | ||
minE = e; | ||
minT = t; | ||
} | ||
const minError = (fn, error, q, res = 16, iter = 8, start = 0, end = 1, eps = EPS) => { | ||
if (iter <= 0) | ||
return (start + end) / 2; | ||
const delta = (end - start) / res; | ||
let minT = start; | ||
let minE = Infinity; | ||
for (let i = 0; i <= res; i++) { | ||
const t = start + i * delta; | ||
const e = error(q, fn(t)); | ||
if (e < minE) { | ||
if (e <= eps) | ||
return t; | ||
minE = e; | ||
minT = t; | ||
} | ||
return minError(fn, error, q, res, iter - 1, Math.max(minT - delta, 0), Math.min(minT + delta, 1)); | ||
} | ||
return minError( | ||
fn, | ||
error, | ||
q, | ||
res, | ||
iter - 1, | ||
Math.max(minT - delta, 0), | ||
Math.min(minT + delta, 1) | ||
); | ||
}; | ||
export { | ||
minError | ||
}; |
500
mix.js
import { EPS, HALF_PI, PI } from "./api.js"; | ||
/** | ||
* Linear interpolation without clamping. Computes `a + (b - a) * t` | ||
* | ||
* @param a - start value | ||
* @param b - end value | ||
* @param t - interpolation factor [0..1] | ||
*/ | ||
export const mix = (a, b, t) => a + (b - a) * t; | ||
/** | ||
* Bilinear interpolation of given values (`a`,`b`,`c`,`d`). | ||
* | ||
* @example | ||
* ```ts | ||
* c d | ||
* +----+ | ||
* | | | ||
* +----+ | ||
* a b | ||
* ``` | ||
* | ||
* @param a - BL value | ||
* @param b - BR value | ||
* @param c - TL value | ||
* @param d - TR value | ||
* @param u - 1st interpolation factor | ||
* @param v - 2nd interpolation factor | ||
*/ | ||
export const mixBilinear = (a, b, c, d, u, v) => { | ||
const iu = 1 - u; | ||
const iv = 1 - v; | ||
return a * iu * iv + b * u * iv + c * iu * v + d * u * v; | ||
const mix = (a, b, t) => a + (b - a) * t; | ||
const mixBilinear = (a, b, c, d, u, v) => { | ||
const iu = 1 - u; | ||
const iv = 1 - v; | ||
return a * iu * iv + b * u * iv + c * iu * v + d * u * v; | ||
}; | ||
/** | ||
* Computes quadratic bezier interpolation for normalized value `t`. | ||
* | ||
* @param a | ||
* @param b | ||
* @param c | ||
* @param t | ||
*/ | ||
export const mixQuadratic = (a, b, c, t) => { | ||
const s = 1 - t; | ||
return a * s * s + b * 2 * s * t + c * t * t; | ||
const mixQuadratic = (a, b, c, t) => { | ||
const s = 1 - t; | ||
return a * s * s + b * 2 * s * t + c * t * t; | ||
}; | ||
/** | ||
* Computes cubic bezier interpolation for normalized value `t`. | ||
* | ||
* @param a | ||
* @param b | ||
* @param c | ||
* @param d | ||
* @param t | ||
*/ | ||
export const mixCubic = (a, b, c, d, t) => { | ||
const t2 = t * t; | ||
const s = 1 - t; | ||
const s2 = s * s; | ||
return a * s2 * s + b * 3 * s2 * t + c * 3 * t2 * s + d * t2 * t; | ||
const mixCubic = (a, b, c, d, t) => { | ||
const t2 = t * t; | ||
const s = 1 - t; | ||
const s2 = s * s; | ||
return a * s2 * s + b * 3 * s2 * t + c * 3 * t2 * s + d * t2 * t; | ||
}; | ||
/** | ||
* Returns hermite interpolation of `a, b, c, d` at normalized position `t`, | ||
* where `a` and `d` are used as predecessor/successor of `b` / `c` and only | ||
* inform the tangent of the interpolation curve. The interpolated result is | ||
* that of `b` and `c`. | ||
* | ||
* Assumes all inputs are uniformly spaced. If that's not the case, use | ||
* {@link mixCubicHermite} with one of the tangent generators supporting | ||
* non-uniform spacing of points. | ||
* | ||
* See: https://www.desmos.com/calculator/j4gf8g9vkr | ||
* | ||
* Source: | ||
* https://www.musicdsp.org/en/latest/Other/93-hermite-interpollation.html | ||
* | ||
* - {@link mixCubicHermite} | ||
* - {@link tangentCardinal} | ||
* - {@link tangentDiff3} | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param c - | ||
* @param d - | ||
* @param t - | ||
*/ | ||
export const mixHermite = (a, b, c, d, t) => { | ||
const y1 = 0.5 * (c - a); | ||
const y2 = 1.5 * (b - c) + 0.5 * (d - a); | ||
return ((y2 * t + a - b + y1 - y2) * t + y1) * t + b; | ||
const mixHermite = (a, b, c, d, t) => { | ||
const y1 = 0.5 * (c - a); | ||
const y2 = 1.5 * (b - c) + 0.5 * (d - a); | ||
return ((y2 * t + a - b + y1 - y2) * t + y1) * t + b; | ||
}; | ||
/** | ||
* Computes cubic-hermite interpolation between `a` / `b` at normalized | ||
* time `t` and using respective tangents `ta` / `tb`. | ||
* | ||
* https://en.wikipedia.org/wiki/Cubic_Hermite_spline | ||
* | ||
* - {@link mixHermite} | ||
* - {@link tangentCardinal} | ||
* - {@link tangentDiff3} | ||
* | ||
* @param a - | ||
* @param ta - | ||
* @param b - | ||
* @param tb - | ||
* @param t - | ||
*/ | ||
export const mixCubicHermite = (a, ta, b, tb, t) => { | ||
const s = t - 1; | ||
const t2 = t * t; | ||
const s2 = s * s; | ||
const h00 = (1 + 2 * t) * s2; | ||
const h10 = t * s2; | ||
const h01 = t2 * (3 - 2 * t); | ||
const h11 = t2 * s; | ||
return h00 * a + h10 * ta + h01 * b + h11 * tb; | ||
const mixCubicHermite = (a, ta, b, tb, t) => { | ||
const s = t - 1; | ||
const t2 = t * t; | ||
const s2 = s * s; | ||
const h00 = (1 + 2 * t) * s2; | ||
const h10 = t * s2; | ||
const h01 = t2 * (3 - 2 * t); | ||
const h11 = t2 * s; | ||
return h00 * a + h10 * ta + h01 * b + h11 * tb; | ||
}; | ||
/** | ||
* Similar to {@link mixCubicHermite}, but takes 4 control values (uniformly | ||
* spaced) and computes tangents automatically. Returns `b` iff `t=0` and `c` | ||
* iff `t=1.0`. | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param c - | ||
* @param d - | ||
* @param t - | ||
*/ | ||
export const mixCubicHermiteFromPoints = (a, b, c, d, t) => { | ||
d *= 0.5; | ||
const aa = -0.5 * a + 1.5 * b - 1.5 * c + d; | ||
const bb = a - 2.5 * b + 2 * c - d; | ||
const cc = -0.5 * a + 0.5 * c; | ||
const dd = b; | ||
const t2 = t * t; | ||
return t * t2 * aa + t2 * bb + t * cc + dd; | ||
const mixCubicHermiteFromPoints = (a, b, c, d, t) => { | ||
d *= 0.5; | ||
const aa = -0.5 * a + 1.5 * b - 1.5 * c + d; | ||
const bb = a - 2.5 * b + 2 * c - d; | ||
const cc = -0.5 * a + 0.5 * c; | ||
const dd = b; | ||
const t2 = t * t; | ||
return t * t2 * aa + t2 * bb + t * cc + dd; | ||
}; | ||
/** | ||
* Bicubic interpolation of given 4x4 sample values (in row major order, i.e. | ||
* `s00..s03` = 1st row). | ||
* | ||
* @remarks | ||
* Result will not be clamped and might fall outside the total range of the | ||
* input samples. | ||
* | ||
* @param s00 - | ||
* @param s01 - | ||
* @param s02 - | ||
* @param s03 - | ||
* @param s10 - | ||
* @param s11 - | ||
* @param s12 - | ||
* @param s13 - | ||
* @param s20 - | ||
* @param s21 - | ||
* @param s22 - | ||
* @param s23 - | ||
* @param s30 - | ||
* @param s31 - | ||
* @param s32 - | ||
* @param s33 - | ||
* @param u - | ||
* @param v - | ||
*/ | ||
export const mixBicubic = (s00, s01, s02, s03, s10, s11, s12, s13, s20, s21, s22, s23, s30, s31, s32, s33, u, v) => mixCubicHermiteFromPoints(mixCubicHermiteFromPoints(s00, s01, s02, s03, u), mixCubicHermiteFromPoints(s10, s11, s12, s13, u), mixCubicHermiteFromPoints(s20, s21, s22, s23, u), mixCubicHermiteFromPoints(s30, s31, s32, s33, u), v); | ||
/** | ||
* Helper function for {@link mixCubicHermite}. Computes cardinal tangents based | ||
* on point neighbors of a point B (not given), i.e. `a` (predecessor) and `c` | ||
* (successor) and their times (defaults to uniformly spaced). The optional | ||
* `tension` parameter can be used to scale the tangent where 0.0 produces a | ||
* Cardinal spline tangent and 1.0 a Catmull-Rom (opposite to the Wikipedia | ||
* ref). | ||
* | ||
* https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline | ||
* | ||
* @param prev - | ||
* @param next - | ||
* @param scale - | ||
* @param ta - | ||
* @param tc - | ||
*/ | ||
export const tangentCardinal = (prev, next, scale = 0.5, ta = 0, tc = 2) => scale * ((next - prev) / (tc - ta)); | ||
/** | ||
* Helper function for {@link mixCubicHermite}. Computes tangent for `curr`, | ||
* based on 3-point finite difference, where `prev` & `next` are `curr`'s | ||
* neighbors and the `tX` the three points' respective time values. The latter | ||
* are equally spaced by default (each 1.0 apart). | ||
* | ||
* Using this function with equal spacing of 1.0 and together with | ||
* {@link mixCubicHermite} will produce same results as the somewhat optimized | ||
* variant {@link mixHermite}. | ||
* | ||
* https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Finite_difference | ||
* | ||
* @param prev - | ||
* @param curr - | ||
* @param next - | ||
* @param ta - | ||
* @param tb - | ||
* @param tc - | ||
*/ | ||
export const tangentDiff3 = (prev, curr, next, ta = 0, tb = 1, tc = 2) => 0.5 * ((next - curr) / (tc - tb) + (curr - prev) / (tb - ta)); | ||
/** | ||
* HOF interpolator. Takes a timing function `f` and interval `[from,to]`. | ||
* Returns function which takes normalized time (in [0,1] range) as single arg | ||
* and returns interpolated value. | ||
* | ||
* @param f - | ||
* @param from - | ||
* @param to - | ||
*/ | ||
export const tween = (f, from, to) => (t) => mix(from, to, f(t)); | ||
/** | ||
* Circular interpolation (ease out): `sqrt(1 - (1 - t)^2)` | ||
* | ||
* @remarks | ||
* Reference: https://www.desmos.com/calculator/tisoiazdrw | ||
* | ||
* @param t - interpolation factor [0..1] | ||
*/ | ||
export const circular = (t) => { | ||
t = 1 - t; | ||
return Math.sqrt(1 - t * t); | ||
const mixBicubic = (s00, s01, s02, s03, s10, s11, s12, s13, s20, s21, s22, s23, s30, s31, s32, s33, u, v) => mixCubicHermiteFromPoints( | ||
mixCubicHermiteFromPoints(s00, s01, s02, s03, u), | ||
mixCubicHermiteFromPoints(s10, s11, s12, s13, u), | ||
mixCubicHermiteFromPoints(s20, s21, s22, s23, u), | ||
mixCubicHermiteFromPoints(s30, s31, s32, s33, u), | ||
v | ||
); | ||
const tangentCardinal = (prev, next, scale = 0.5, ta = 0, tc = 2) => scale * ((next - prev) / (tc - ta)); | ||
const tangentDiff3 = (prev, curr, next, ta = 0, tb = 1, tc = 2) => 0.5 * ((next - curr) / (tc - tb) + (curr - prev) / (tb - ta)); | ||
const tween = (f, from, to) => (t) => mix(from, to, f(t)); | ||
const circular = (t) => { | ||
t = 1 - t; | ||
return Math.sqrt(1 - t * t); | ||
}; | ||
/** | ||
* Inverse/flipped version of {@link circular} (ease in). | ||
* | ||
* @remarks | ||
* Reference: https://www.desmos.com/calculator/tisoiazdrw | ||
* | ||
* @param t - interpolation factor [0..1] | ||
*/ | ||
export const invCircular = (t) => 1 - circular(1 - t); | ||
/** | ||
* Zoomlens interpolation with customizable lens position, behavior and | ||
* strength. | ||
* | ||
* @remarks | ||
* Lens position must be given in (0..1) interval. Lens strength must be in | ||
* [-1,1] range. If negative, the lens will be bundling values near `pos`, if | ||
* positive the lens has dilating characteristics and will spread values near | ||
* `pos` towards the edges. | ||
* | ||
* Also see {@link schlick} for an alternative approach. | ||
* | ||
* @example | ||
* ```ts | ||
* // interpolated position in [100..400] interval for given `t` | ||
* y = mix(100, 400, lens(0.5, 1, t)); | ||
* | ||
* // or build tween function via `tween()` | ||
* f = tween(partial(lens, 0.5, 1), 100, 400); | ||
* | ||
* f(t) | ||
* ``` | ||
* | ||
* @param pos - lens pos | ||
* @param strength - lens strength | ||
* @param t - interpolation factor [0..1] | ||
*/ | ||
export const lens = (pos, strength, t) => { | ||
const impl = strength > 0 ? invCircular : circular; | ||
const tp = 1 - pos; | ||
const tl = t <= pos ? impl(t / pos) * pos : 1 - impl((1 - t) / tp) * tp; | ||
return mix(t, tl, Math.abs(strength)); | ||
const invCircular = (t) => 1 - circular(1 - t); | ||
const lens = (pos, strength, t) => { | ||
const impl = strength > 0 ? invCircular : circular; | ||
const tp = 1 - pos; | ||
const tl = t <= pos ? impl(t / pos) * pos : 1 - impl((1 - t) / tp) * tp; | ||
return mix(t, tl, Math.abs(strength)); | ||
}; | ||
export const cosine = (t) => 1 - (Math.cos(t * PI) * 0.5 + 0.5); | ||
export const decimated = (n, t) => Math.floor(t * n) / n; | ||
/** | ||
* Spring oscillator with damping. | ||
* | ||
* @remarks | ||
* Interactive graph: | ||
* https://www.desmos.com/calculator/tywbpw8pck | ||
* | ||
* @param k | ||
* @param amp | ||
* @param t | ||
*/ | ||
export const bounce = (k, amp, t) => { | ||
const tk = t * k; | ||
return 1 - ((amp * Math.sin(tk)) / tk) * Math.cos(t * HALF_PI); | ||
const cosine = (t) => 1 - (Math.cos(t * PI) * 0.5 + 0.5); | ||
const decimated = (n, t) => Math.floor(t * n) / n; | ||
const bounce = (k, amp, t) => { | ||
const tk = t * k; | ||
return 1 - amp * Math.sin(tk) / tk * Math.cos(t * HALF_PI); | ||
}; | ||
/** | ||
* Exponential easing. | ||
* | ||
* - `ease = 1` -> linear | ||
* - `ease > 1` -> ease in | ||
* - `ease < 1` -> ease out | ||
* | ||
* @param ease - easing behavior [0.0 .. ∞] | ||
* @param t - | ||
*/ | ||
export const ease = (ease, t) => Math.pow(t, ease); | ||
/** | ||
* Impulse generator. Peaks at `t = 1/k` | ||
* | ||
* @param k - impulse width (higher values => shorter impulse) | ||
*/ | ||
export const impulse = (k, t) => { | ||
const h = k * t; | ||
return h * Math.exp(1 - h); | ||
const ease = (ease2, t) => Math.pow(t, ease2); | ||
const impulse = (k, t) => { | ||
const h = k * t; | ||
return h * Math.exp(1 - h); | ||
}; | ||
export const gain = (k, t) => t < 0.5 ? 0.5 * Math.pow(2 * t, k) : 1 - 0.5 * Math.pow(2 - 2 * t, k); | ||
export const parabola = (k, t) => Math.pow(4.0 * t * (1.0 - t), k); | ||
export const cubicPulse = (w, c, t) => { | ||
t = Math.abs(t - c); | ||
return t > w ? 0 : ((t /= w), 1 - t * t * (3 - 2 * t)); | ||
const gain = (k, t) => t < 0.5 ? 0.5 * Math.pow(2 * t, k) : 1 - 0.5 * Math.pow(2 - 2 * t, k); | ||
const parabola = (k, t) => Math.pow(4 * t * (1 - t), k); | ||
const cubicPulse = (w, c, t) => { | ||
t = Math.abs(t - c); | ||
return t > w ? 0 : (t /= w, 1 - t * t * (3 - 2 * t)); | ||
}; | ||
/** | ||
* Unnormalized Sinc function: sin(x)/x. Returns 1 for t=0. | ||
* | ||
* @remarks | ||
* https://en.wikipedia.org/wiki/Sinc_function | ||
* | ||
* @param k - | ||
* @param t - | ||
*/ | ||
export const sinc = (t) => (t !== 0 ? Math.sin(t) / t : 1); | ||
/** | ||
* Normalized Sinc function, returns sinc(π*k*t). | ||
* | ||
* @remarks | ||
* https://en.wikipedia.org/wiki/Sinc_function | ||
* | ||
* @see {@link sinc} | ||
* | ||
* @param k - | ||
* @param t - | ||
*/ | ||
export const sincNormalized = (k, t) => sinc(PI * k * t); | ||
/** | ||
* Lanczos filter. Returns `sinc(πt)sinc(πt/a)` iff `t` in (-a,a) interval, else | ||
* returns 0. | ||
* | ||
* @remarks | ||
* Interactive graph: https://www.desmos.com/calculator/pmypqgefle | ||
* | ||
* @param a - | ||
* @param t - | ||
*/ | ||
export const lanczos = (a, t) => t !== 0 ? (-a < t && t < a ? sinc(PI * t) * sinc((PI * t) / a) : 0) : 1; | ||
/** | ||
* Sigmoid function for inputs arounds center bias. | ||
* | ||
* @remarks | ||
* Updated in v3.0.0 to add bias value to satisfy more use cases. Use | ||
* {@link sigmoid01} for old behavior. | ||
* | ||
* @param bias - center value (for which result = 0.5) | ||
* @param k - steepness | ||
* @param t - input value | ||
*/ | ||
export const sigmoid = (bias, k, t) => t != bias ? 1 / (1 + Math.exp(-k * (t - bias))) : 0.5; | ||
/** | ||
* Sigmoid function for inputs in [0..1] interval. Center bias = 0.5. | ||
* | ||
* @param k - steepness | ||
* @param t - input value | ||
*/ | ||
export const sigmoid01 = (k, t) => sigmoid(0.5, k, t); | ||
/** | ||
* Sigmoid function for inputs in [-1..+1] interval. Center bias = 0 | ||
* | ||
* @param k - | ||
* @param t - | ||
*/ | ||
export const sigmoid11 = (k, t) => sigmoid(0, k, t); | ||
/** | ||
* Generalized Schlick bias gain curve, based on: | ||
* https://arxiv.org/abs/2010.09714 | ||
* | ||
* @remarks | ||
* Interactive graph: | ||
* https://www.desmos.com/calculator/u6bkm5rb7t | ||
* | ||
* @param a - curve strength. recommended (0..64] | ||
* @param b - pivot position [0..1] | ||
* @param t - input val [0..1] | ||
*/ | ||
export const schlick = (a, b, t) => t <= b | ||
? (b * t) / (t + a * (b - t) + EPS) | ||
: ((1 - b) * (t - 1)) / (1 - t - a * (b - t) + EPS) + 1; | ||
/** | ||
* Computes exponential factor to interpolate from `a` to `b` over | ||
* `num` steps. I.e. multiplying `a` with the returned factor will yield | ||
* `b` after `num` steps. All args must be > 0. | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param num - | ||
*/ | ||
export const expFactor = (a, b, num) => (b / a) ** (1 / num); | ||
/** | ||
* Computes gaussian bell curve for given center `bias` and `sigma` (spread). | ||
* | ||
* @remarks | ||
* Interactive graph: https://www.desmos.com/calculator/aq6hdzxprv | ||
* | ||
* @param bias - | ||
* @param sigma - | ||
* @param t - | ||
*/ | ||
export const gaussian = (bias, sigma, t) => Math.exp(-((t - bias) ** 2) / (2 * sigma * sigma)); | ||
const sinc = (t) => t !== 0 ? Math.sin(t) / t : 1; | ||
const sincNormalized = (k, t) => sinc(PI * k * t); | ||
const lanczos = (a, t) => t !== 0 ? -a < t && t < a ? sinc(PI * t) * sinc(PI * t / a) : 0 : 1; | ||
const sigmoid = (bias, k, t) => t != bias ? 1 / (1 + Math.exp(-k * (t - bias))) : 0.5; | ||
const sigmoid01 = (k, t) => sigmoid(0.5, k, t); | ||
const sigmoid11 = (k, t) => sigmoid(0, k, t); | ||
const schlick = (a, b, t) => t <= b ? b * t / (t + a * (b - t) + EPS) : (1 - b) * (t - 1) / (1 - t - a * (b - t) + EPS) + 1; | ||
const expFactor = (a, b, num) => (b / a) ** (1 / num); | ||
const gaussian = (bias, sigma, t) => Math.exp(-((t - bias) ** 2) / (2 * sigma * sigma)); | ||
export { | ||
bounce, | ||
circular, | ||
cosine, | ||
cubicPulse, | ||
decimated, | ||
ease, | ||
expFactor, | ||
gain, | ||
gaussian, | ||
impulse, | ||
invCircular, | ||
lanczos, | ||
lens, | ||
mix, | ||
mixBicubic, | ||
mixBilinear, | ||
mixCubic, | ||
mixCubicHermite, | ||
mixCubicHermiteFromPoints, | ||
mixHermite, | ||
mixQuadratic, | ||
parabola, | ||
schlick, | ||
sigmoid, | ||
sigmoid01, | ||
sigmoid11, | ||
sinc, | ||
sincNormalized, | ||
tangentCardinal, | ||
tangentDiff3, | ||
tween | ||
}; |
{ | ||
"name": "@thi.ng/math", | ||
"version": "5.7.6", | ||
"version": "5.7.7", | ||
"description": "Assorted common math functions & utilities", | ||
@@ -30,3 +30,5 @@ "type": "module", | ||
"scripts": { | ||
"build": "yarn clean && tsc --declaration", | ||
"build": "yarn build:esbuild && yarn build:decl", | ||
"build:decl": "tsc --declaration --emitDeclarationOnly", | ||
"build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts", | ||
"clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc", | ||
@@ -40,6 +42,7 @@ "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", | ||
"dependencies": { | ||
"@thi.ng/api": "^8.9.11" | ||
"@thi.ng/api": "^8.9.12" | ||
}, | ||
"devDependencies": { | ||
"@microsoft/api-extractor": "^7.38.3", | ||
"esbuild": "^0.19.8", | ||
"rimraf": "^5.0.5", | ||
@@ -141,3 +144,3 @@ "tools": "^0.0.1", | ||
}, | ||
"gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n" | ||
"gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n" | ||
} |
@@ -1,48 +0,19 @@ | ||
/** | ||
* Computes factorial for `n`. Throws an error if `n < 0`. | ||
* | ||
* @param n | ||
*/ | ||
export const factorial = (n) => { | ||
if (n < 0) | ||
throw new Error(`illegal argument: ${n}`); | ||
let res = 1; | ||
for (let i = 1; i <= n; i++) | ||
res *= i; | ||
return res; | ||
const factorial = (n) => { | ||
if (n < 0) | ||
throw new Error(`illegal argument: ${n}`); | ||
let res = 1; | ||
for (let i = 1; i <= n; i++) | ||
res *= i; | ||
return res; | ||
}; | ||
/** | ||
* Computes `n ** k` | ||
* | ||
* @param n number of choices | ||
* @param k number of selected | ||
*/ | ||
export const permutationsWithRep = (n, k) => n ** k; | ||
/** | ||
* Computes `n! / (n - k)!` | ||
* | ||
* @remarks | ||
* Reference: | ||
* https://en.wikipedia.org/wiki/Permutation#k-permutations_of_n | ||
* | ||
* @param n number of choices | ||
* @param k number of selected | ||
*/ | ||
export const permutationsWithoutRep = (n, k) => factorial(n) / factorial(n - k); | ||
/** | ||
* Computes `(n + k - 1)! / (k! * (n - 1)!)` | ||
* | ||
* @param n number of choices | ||
* @param k number of selected | ||
*/ | ||
export const combinationsWithRep = (n, k) => factorial(n + k - 1) / (factorial(k) * factorial(n - 1)); | ||
/** | ||
* Computes `n! / (k! * (n - k)!)` | ||
* | ||
* @remarks | ||
* https://en.wikipedia.org/wiki/Combination#Number_of_k-combinations | ||
* | ||
* @param n number of choices | ||
* @param k number of selected | ||
*/ | ||
export const combinationsWithoutRep = (n, k) => factorial(n) / (factorial(k) * factorial(n - k)); | ||
const permutationsWithRep = (n, k) => n ** k; | ||
const permutationsWithoutRep = (n, k) => factorial(n) / factorial(n - k); | ||
const combinationsWithRep = (n, k) => factorial(n + k - 1) / (factorial(k) * factorial(n - 1)); | ||
const combinationsWithoutRep = (n, k) => factorial(n) / (factorial(k) * factorial(n - k)); | ||
export { | ||
combinationsWithRep, | ||
combinationsWithoutRep, | ||
factorial, | ||
permutationsWithRep, | ||
permutationsWithoutRep | ||
}; |
49
prec.js
import { EPS } from "./api.js"; | ||
/** | ||
* Similar to {@link fmod}, {@link remainder}. Returns `a - b * floor(a / b)` | ||
* (same as GLSL `mod(a, b)`) | ||
* | ||
* @remarks | ||
* **Caution:** Due to the introduction of libc math functions in v4.0.0 and the | ||
* resulting name/behavior clashes between the modulo logic in JS, C & GLSL, | ||
* this function previously _was_ called `fmod`, but going forward has been | ||
* renamed to align w/ its GLSL version and exhibits a different behavior to the | ||
* current {@link fmod} function. | ||
* | ||
* https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/mod.xhtml | ||
* | ||
* @param a - | ||
* @param b - | ||
*/ | ||
export const mod = (a, b) => a - b * Math.floor(a / b); | ||
export const fract = (x) => x - Math.floor(x); | ||
export const trunc = (x) => (x < 0 ? Math.ceil(x) : Math.floor(x)); | ||
export const roundTo = (x, prec = 1) => Math.round(x / prec) * prec; | ||
export const floorTo = (x, prec = 1) => Math.floor(x / prec) * prec; | ||
export const ceilTo = (x, prec = 1) => Math.ceil(x / prec) * prec; | ||
/** | ||
* Only rounds `x` to nearest int if `fract(x)` <= `eps` or >= `1-eps`. | ||
* | ||
* @param x - | ||
* @param eps - | ||
*/ | ||
export const roundEps = (x, eps = EPS) => { | ||
const f = fract(x); | ||
return f <= eps || f >= 1 - eps ? Math.round(x) : x; | ||
const mod = (a, b) => a - b * Math.floor(a / b); | ||
const fract = (x) => x - Math.floor(x); | ||
const trunc = (x) => x < 0 ? Math.ceil(x) : Math.floor(x); | ||
const roundTo = (x, prec = 1) => Math.round(x / prec) * prec; | ||
const floorTo = (x, prec = 1) => Math.floor(x / prec) * prec; | ||
const ceilTo = (x, prec = 1) => Math.ceil(x / prec) * prec; | ||
const roundEps = (x, eps = EPS) => { | ||
const f = fract(x); | ||
return f <= eps || f >= 1 - eps ? Math.round(x) : x; | ||
}; | ||
export { | ||
ceilTo, | ||
floorTo, | ||
fract, | ||
mod, | ||
roundEps, | ||
roundTo, | ||
trunc | ||
}; |
79
prime.js
@@ -1,54 +0,37 @@ | ||
/** | ||
* Returns iterator of all prime numbers ≤ given `x` using Sieve of | ||
* Eratosthenes. | ||
* | ||
* @remarks | ||
* Reference: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes | ||
* | ||
* @param x - | ||
*/ | ||
export function* primesUntil(x) { | ||
if (x < 1) | ||
return; | ||
yield 1; | ||
const sieve = []; | ||
const max = Math.sqrt(x) | 0; | ||
for (let i = 2; i <= x; i++) { | ||
if (!sieve[i]) { | ||
yield i; | ||
__updateSieve(sieve, i, x, max); | ||
} | ||
function* primesUntil(x) { | ||
if (x < 1) | ||
return; | ||
yield 1; | ||
const sieve = []; | ||
const max = Math.sqrt(x) | 0; | ||
for (let i = 2; i <= x; i++) { | ||
if (!sieve[i]) { | ||
yield i; | ||
__updateSieve(sieve, i, x, max); | ||
} | ||
} | ||
} | ||
/** | ||
* Returns largest prime number ≤ given `x` using Sieve of Eratosthenes. Returns | ||
* -1 if x < 1. | ||
* | ||
* @remarks | ||
* Reference: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes | ||
* | ||
* @param x - | ||
*/ | ||
export const nearestPrime = (x) => { | ||
if (x < 1) | ||
return -1; | ||
let prime = 1; | ||
const sieve = []; | ||
const max = Math.sqrt(x) | 0; | ||
for (let i = 2; i <= x; i++) { | ||
if (!sieve[i]) { | ||
prime = i; | ||
__updateSieve(sieve, i, x, max); | ||
} | ||
const nearestPrime = (x) => { | ||
if (x < 1) | ||
return -1; | ||
let prime = 1; | ||
const sieve = []; | ||
const max = Math.sqrt(x) | 0; | ||
for (let i = 2; i <= x; i++) { | ||
if (!sieve[i]) { | ||
prime = i; | ||
__updateSieve(sieve, i, x, max); | ||
} | ||
return prime; | ||
} | ||
return prime; | ||
}; | ||
/** | ||
* @internal | ||
*/ | ||
const __updateSieve = (sieve, i, x, max) => { | ||
if (i <= max) { | ||
for (let j = i * i; j <= x; j += i) | ||
sieve[j] = true; | ||
} | ||
if (i <= max) { | ||
for (let j = i * i; j <= x; j += i) | ||
sieve[j] = true; | ||
} | ||
}; | ||
export { | ||
nearestPrime, | ||
primesUntil | ||
}; |
34
ratio.js
@@ -1,18 +0,20 @@ | ||
export const simplifyRatio = (num, denom) => { | ||
let e1 = Math.abs(num); | ||
let e2 = Math.abs(denom); | ||
while (true) { | ||
if (e1 < e2) { | ||
const t = e1; | ||
e1 = e2; | ||
e2 = t; | ||
} | ||
const r = e1 % e2; | ||
if (r) { | ||
e1 = r; | ||
} | ||
else { | ||
return [num / e2, denom / e2]; | ||
} | ||
const simplifyRatio = (num, denom) => { | ||
let e1 = Math.abs(num); | ||
let e2 = Math.abs(denom); | ||
while (true) { | ||
if (e1 < e2) { | ||
const t = e1; | ||
e1 = e2; | ||
e2 = t; | ||
} | ||
const r = e1 % e2; | ||
if (r) { | ||
e1 = r; | ||
} else { | ||
return [num / e2, denom / e2]; | ||
} | ||
} | ||
}; | ||
export { | ||
simplifyRatio | ||
}; |
@@ -70,3 +70,3 @@ <!-- This file is generated - DO NOT EDIT! --> | ||
Package sizes (brotli'd, pre-treeshake): ESM: 4.08 KB | ||
Package sizes (brotli'd, pre-treeshake): ESM: 4.07 KB | ||
@@ -73,0 +73,0 @@ ## Dependencies |
@@ -1,7 +0,4 @@ | ||
/** | ||
* Returns `a` divided by `b` or zero if `b = 0`. | ||
* | ||
* @param a - | ||
* @param b - | ||
*/ | ||
export const safeDiv = (a, b) => (b !== 0 ? a / b : 0); | ||
const safeDiv = (a, b) => b !== 0 ? a / b : 0; | ||
export { | ||
safeDiv | ||
}; |
129
solve.js
import { EPS } from "./api.js"; | ||
import { safeDiv } from "./safe-div.js"; | ||
/** | ||
* Produces a new function which computes derivative of the given single-arg | ||
* function. The extra optional arg `eps` is used to define the step width for | ||
* computing derived values: | ||
* | ||
* `f'(x) = (f(x + eps) - f(x)) / eps` | ||
* | ||
* The original function is assumed to be fully differentiable in the interval | ||
* the returned function is going to be used. No validity checks of any form are | ||
* done. | ||
* | ||
* https://en.wikipedia.org/wiki/Derivative#Continuity_and_differentiability | ||
* | ||
* @param fn - | ||
* @param eps - | ||
*/ | ||
export const derivative = (f, eps = EPS) => (x) => (f(x + eps) - f(x)) / eps; | ||
/** | ||
* Computes solution for linear equation: `ax + b = 0`. | ||
* | ||
* Note: Returns 0 iff `a == 0` | ||
* | ||
* @param a - slope | ||
* @param b - constant offset | ||
*/ | ||
export const solveLinear = (a, b) => safeDiv(-b, a); | ||
/** | ||
* Computes solutions for quadratic equation: `ax^2 + bx + c = 0`. Returns array | ||
* of real solutions. Note: `a` MUST NOT be zero. If the quadratic term is | ||
* missing, use {@link solveLinear} instead. | ||
* | ||
* - https://en.wikipedia.org/wiki/Quadratic_function | ||
* - https://en.wikipedia.org/wiki/Quadratic_equation | ||
* | ||
* @param a - quadratic coefficient | ||
* @param b - linear coefficient | ||
* @param c - constant offset | ||
* @param eps - tolerance to determine multiple roots | ||
*/ | ||
export const solveQuadratic = (a, b, c, eps = 1e-9) => { | ||
const d = 2 * a; | ||
let r = b * b - 4 * a * c; | ||
return r < 0 | ||
? [] | ||
: r < eps | ||
? [-b / d] | ||
: ((r = Math.sqrt(r)), [(-b - r) / d, (-b + r) / d]); | ||
const derivative = (f, eps = EPS) => (x) => (f(x + eps) - f(x)) / eps; | ||
const solveLinear = (a, b) => safeDiv(-b, a); | ||
const solveQuadratic = (a, b, c, eps = 1e-9) => { | ||
const d = 2 * a; | ||
let r = b * b - 4 * a * c; | ||
return r < 0 ? [] : r < eps ? [-b / d] : (r = Math.sqrt(r), [(-b - r) / d, (-b + r) / d]); | ||
}; | ||
/** | ||
* Computes solutions for quadratic equation: `ax^3 + bx^2 + c*x + d = 0`. | ||
* Returns array of solutions, both real & imaginary. Note: `a` MUST NOT be | ||
* zero. If the cubic term is missing (i.e. zero), use {@link solveQuadratic} or | ||
* {@link solveLinear} instead. | ||
* | ||
* https://en.wikipedia.org/wiki/Cubic_function | ||
* | ||
* @param a - cubic coefficient | ||
* @param b - quadratic coefficient | ||
* @param c - linear coefficient | ||
* @param d - constant offset | ||
* @param eps - tolerance to determine multiple roots | ||
*/ | ||
export const solveCubic = (a, b, c, d, eps = 1e-9) => { | ||
const aa = a * a; | ||
const bb = b * b; | ||
const ba3 = b / (3 * a); | ||
const p = (3 * a * c - bb) / (3 * aa); | ||
const q = (2 * bb * b - 9 * a * b * c + 27 * aa * d) / (27 * aa * a); | ||
if (Math.abs(p) < eps) { | ||
return [Math.cbrt(-q) - ba3]; | ||
const solveCubic = (a, b, c, d, eps = 1e-9) => { | ||
const aa = a * a; | ||
const bb = b * b; | ||
const ba3 = b / (3 * a); | ||
const p = (3 * a * c - bb) / (3 * aa); | ||
const q = (2 * bb * b - 9 * a * b * c + 27 * aa * d) / (27 * aa * a); | ||
if (Math.abs(p) < eps) { | ||
return [Math.cbrt(-q) - ba3]; | ||
} else if (Math.abs(q) < eps) { | ||
return p < 0 ? [-Math.sqrt(-p) - ba3, -ba3, Math.sqrt(-p) - ba3] : [-ba3]; | ||
} else { | ||
const denom = q * q / 4 + p * p * p / 27; | ||
if (Math.abs(denom) < eps) { | ||
return [-1.5 * q / p - ba3, 3 * q / p - ba3]; | ||
} else if (denom > 0) { | ||
const u = Math.cbrt(-q / 2 - Math.sqrt(denom)); | ||
return [u - p / (3 * u) - ba3]; | ||
} else { | ||
const u = 2 * Math.sqrt(-p / 3), t = Math.acos(3 * q / p / u) / 3, k = 2 * Math.PI / 3; | ||
return [ | ||
u * Math.cos(t) - ba3, | ||
u * Math.cos(t - k) - ba3, | ||
u * Math.cos(t - 2 * k) - ba3 | ||
]; | ||
} | ||
else if (Math.abs(q) < eps) { | ||
return p < 0 | ||
? [-Math.sqrt(-p) - ba3, -ba3, Math.sqrt(-p) - ba3] | ||
: [-ba3]; | ||
} | ||
else { | ||
const denom = (q * q) / 4 + (p * p * p) / 27; | ||
if (Math.abs(denom) < eps) { | ||
return [(-1.5 * q) / p - ba3, (3 * q) / p - ba3]; | ||
} | ||
else if (denom > 0) { | ||
const u = Math.cbrt(-q / 2 - Math.sqrt(denom)); | ||
return [u - p / (3 * u) - ba3]; | ||
} | ||
else { | ||
const u = 2 * Math.sqrt(-p / 3), t = Math.acos((3 * q) / p / u) / 3, k = (2 * Math.PI) / 3; | ||
return [ | ||
u * Math.cos(t) - ba3, | ||
u * Math.cos(t - k) - ba3, | ||
u * Math.cos(t - 2 * k) - ba3, | ||
]; | ||
} | ||
} | ||
} | ||
}; | ||
export { | ||
derivative, | ||
solveCubic, | ||
solveLinear, | ||
solveQuadratic | ||
}; |
77
step.js
import { clamp01 } from "./interval.js"; | ||
/** | ||
* Step/threshold function. | ||
* | ||
* @param edge - threshold | ||
* @param x - test value | ||
* @returns 0, if `x < e`, else 1 | ||
*/ | ||
export const step = (edge, x) => (x < edge ? 0 : 1); | ||
/** | ||
* GLSL-style smoothStep threshold function. | ||
* | ||
* @remarks | ||
* Also see {@link smoothStep01}, {@link smootherStep}. | ||
* | ||
* @param edge - lower threshold | ||
* @param edge2 - upper threshold | ||
* @param x - test value | ||
* @returns 0, if `x < edge1`, 1 if `x > edge2`, else S-curve polynomial interpolation | ||
*/ | ||
export const smoothStep = (edge, edge2, x) => smoothStep01(clamp01((x - edge) / (edge2 - edge))); | ||
/** | ||
* Specialized version of {@link smoothStep}, assuming edges are 0/1 | ||
* respectively and `x` is in [0..1]. No clamping performed. | ||
* | ||
* @param x | ||
*/ | ||
export const smoothStep01 = (x) => x * x * (3 - 2 * x); | ||
/** | ||
* Similar to {@link smoothStep} but using different, higher degree polynomial. | ||
* | ||
* @remarks | ||
* Also see {@link smootherStep01}. | ||
* | ||
* @param edge - | ||
* @param edge2 - | ||
* @param x - | ||
*/ | ||
export const smootherStep = (edge, edge2, x) => smootherStep01(clamp01((x - edge) / (edge2 - edge))); | ||
/** | ||
* Specialized version of {@link smootherStep}, assuming edges are 0/1 | ||
* respectively and `x` is in [0..1]. No clamping performed. | ||
* | ||
* @param x | ||
*/ | ||
export const smootherStep01 = (x) => x * x * x * (x * (x * 6 - 15) + 10); | ||
/** | ||
* Exponential ramp with variable shape | ||
* | ||
* @remarks | ||
* Example configurations: | ||
* | ||
* - S-curve: k=8, n=4 | ||
* - Step near 1.0: k=8, n=20 | ||
* - Pulse: k=0.005, n=-10 | ||
* - Ease-in: k=0.5, n=0.25 | ||
* | ||
* Interactive graph: https://www.desmos.com/calculator/gcnuyppycz | ||
* | ||
* @param k - | ||
* @param n - | ||
* @param x - | ||
*/ | ||
export const expStep = (k, n, x) => 1 - Math.exp(-k * Math.pow(x, n)); | ||
const step = (edge, x) => x < edge ? 0 : 1; | ||
const smoothStep = (edge, edge2, x) => smoothStep01(clamp01((x - edge) / (edge2 - edge))); | ||
const smoothStep01 = (x) => x * x * (3 - 2 * x); | ||
const smootherStep = (edge, edge2, x) => smootherStep01(clamp01((x - edge) / (edge2 - edge))); | ||
const smootherStep01 = (x) => x * x * x * (x * (x * 6 - 15) + 10); | ||
const expStep = (k, n, x) => 1 - Math.exp(-k * Math.pow(x, n)); | ||
export { | ||
expStep, | ||
smoothStep, | ||
smoothStep01, | ||
smootherStep, | ||
smootherStep01, | ||
step | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
90083
6
2004
Updated@thi.ng/api@^8.9.12