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; 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; } export function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); } 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); } 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; } 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; } 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}; } export function random() { return Math.random(); } 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; }