silphius/app/util/math.js

259 lines
4.9 KiB
JavaScript
Raw Normal View History

2024-07-04 09:08:47 -05:00
export const {
abs,
acos,
acosh,
asin,
asinh,
atan,
atanh,
atan2,
ceil,
cbrt,
expm1,
clz32,
cos,
cosh,
exp,
floor,
fround,
hypot,
imul,
log,
log1p,
log2,
log10,
max,
min,
round,
sign,
sin,
sinh,
sqrt,
tan,
tanh,
trunc,
E,
LN10,
LN2,
LOG10E,
LOG2E,
PI,
SQRT1_2,
SQRT2,
} = Math;
2024-07-07 17:34:40 -05:00
export const TAU = Math.PI * 2;
export function bresenham({x: x1, y: y1}, {x: x2, y: y2}) {
const points = [];
let x; let y; let px; let py; let xe; let ye;
const dx = x2 - x1;
const dy = y2 - y1;
const dx1 = Math.abs(dx);
const dy1 = Math.abs(dy);
px = 2 * dy1 - dx1;
py = 2 * dx1 - dy1;
if (dy1 <= dx1) {
if (dx >= 0) {
x = x1; y = y1; xe = x2;
}
else {
x = x2; y = y2; xe = x1;
}
points.push({x, y});
for (let i = 0; x < xe; i++) {
x += 1;
if (px < 0) {
px += 2 * dy1;
}
else {
if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) {
y += 1;
}
else {
y -= 1;
}
px += 2 * (dy1 - dx1);
}
points.push({x, y});
}
}
else {
if (dy >= 0) {
x = x1; y = y1; ye = y2;
}
else {
x = x2; y = y2; ye = y1;
}
points.push({x, y});
for (let i = 0; y < ye; i++) {
y += 1;
if (py <= 0) {
py += 2 * dx1;
}
else {
if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) {
x += 1;
}
else {
x -= 1;
}
py += 2 * (dx1 - dy1);
}
points.push({x, y});
}
}
return points;
}
2024-07-03 16:13:14 -05:00
export function clamp(n, min, max) {
return Math.max(min, Math.min(max, n));
}
2024-07-02 16:16:39 -05:00
export function distance({x: lx, y: ly}, {x: rx, y: ry}) {
const xd = lx - rx;
const yd = ly - ry;
return Math.sqrt(xd * xd + yd * yd);
}
2024-07-07 17:34:40 -05:00
export function floodwalk2D(eligible, data, {x, y, w, h}, {diagonal = false} = {}) {
const n = data.length;
let i = x + w * y;
const points = new Set();
if (i < 0 || i >= n) {
return points;
}
const seen = [];
seen[-1] = true;
seen[i] = true;
const todo = [i];
while (todo.length > 0) {
i = todo.pop();
const v = data[i];
if (!eligible.has(v)) {
continue;
}
points.add(i);
const xx = i % w;
const yy = Math.floor(i / w);
const us = yy >= 1;
const rs = xx + 1 < w;
const ds = yy + 1 < h;
const ls = xx >= 1;
const sel = [
us ? i - w : -1,
rs ? i + 1 : -1,
ds ? i + w : -1,
ls ? i - 1 : -1,
...(
diagonal
? [
us && rs ? i - w + 1 : -1,
rs && ds ? i + w + 1 : -1,
ds && ls ? i + w - 1 : -1,
ls && us ? i - w - 1 : -1,
]
: []
)
];
for (let i = 0; i < sel.length; ++i) {
const j = sel[i];
if (!seen[j]) {
todo.push(j);
seen[j] = true;
}
}
}
return points;
}
2024-07-02 14:41:54 -05:00
export function intersects(l, r) {
if (l.x0 > r.x1) return false;
if (l.y0 > r.y1) return false;
if (l.x1 < r.x0) return false;
if (l.y1 < r.y0) return false;
return true;
}
2024-07-02 08:05:36 -05:00
export function normalizeVector({x, y}) {
if (0 === y && 0 === x) {
return {x: 0, y: 0};
}
const k = 1 / Math.sqrt(x * x + y * y);
return {x: x * k, y: y * k};
}
2024-07-04 09:08:47 -05:00
export function random() {
return Math.random();
}
2024-07-07 17:34:40 -05:00
export function isCollinear({x: ax, y: ay}, {x: bx, y: by}, {x: cx, y: cy}) {
return (ay - by) * (ax - cx) === (ay - cy) * (ax - bx);
}
export const directionToVector = [
{x: 0, y: -1},
{x: 1, y: 0},
{x: 0, y: 1},
{x: -1, y: 0},
];
export function ortho(points, k = {x: 1, y: 1}) {
if (points.length < 4) {
throw new TypeError('Math.ortho(): points.length < 4');
}
const index = Object.create(null);
for (const i in points) {
const point = points[i];
if (!index[point.y]) {
index[point.y] = Object.create(null);
}
index[point.y][point.x] = i;
}
const sorted = [points[0]];
let direction = 0;
let walk = 0;
navigate:
while (true) {
for (let i = 0; i < 4; i++) {
// ccw rotation
const nextDirection = (i + direction + 3) & 3;
const {x: px, y: py} = points[walk];
const {x: dx, y: dy} = directionToVector[nextDirection];
const nextIndex = index[py + k.y * dy]?.[px + k.x * dx];
const nextPoint = points[nextIndex];
// loop = done
if (points[0] === nextPoint) {
return sorted;
}
if (nextPoint) {
direction = nextDirection;
sorted.push(nextPoint);
walk = nextIndex;
continue navigate;
}
}
return [];
}
}
export function removeCollinear([...vertices]) {
if (vertices.length < 3) {
return vertices;
}
vertices.push(vertices[0]);
vertices.push(vertices[1]);
const trimmed = [];
let i = 0;
while (i < vertices.length - 2) {
if (isCollinear(vertices[i], vertices[i + 1], vertices[i + 2])) {
vertices.splice(i + 1, 1);
}
else {
trimmed.push(vertices[i]);
i += 1;
}
}
return trimmed;
}