import { clamp } from "./mapUtils.js"; export function createRectContext(options = {}) { const originX = Math.floor(Number.isFinite(options.originX) ? options.originX : 0); const originY = Math.floor(Number.isFinite(options.originY) ? options.originY : 0); const width = Math.max(1, Math.floor(Number.isFinite(options.width) ? options.width : 1)); const height = Math.max(1, Math.floor(Number.isFinite(options.height) ? options.height : 1)); return { originX, originY, width, height, size: width * height, name: options.name || "rect", }; } export function rectIndexOf(ctx, x, y) { return y * ctx.width + x; } export function rectXyOf(ctx, i) { return [i % ctx.width, Math.floor(i / ctx.width)]; } export function rectInside(ctx, x, y) { return !!ctx && x >= 0 && y >= 0 && x < ctx.width && y < ctx.height; } export function rectWorldX(ctx, x) { return ctx.originX + x; } export function rectWorldY(ctx, y) { return ctx.originY + y; } export function rectWorldCoord(ctx, x, y) { return { x: ctx.originX + x, y: ctx.originY + y }; } export function rectLocalCoord(ctx, worldX, worldY) { return { x: Math.round(worldX - ctx.originX), y: Math.round(worldY - ctx.originY) }; } export function rectWorldIndex(ctx, worldX, worldY) { const x = Math.round(worldX - ctx.originX); const y = Math.round(worldY - ctx.originY); return rectInside(ctx, x, y) ? rectIndexOf(ctx, x, y) : -1; } export function rectFromBounds(bounds, name = "rect") { const x0 = Math.floor(Math.min(bounds.x0, bounds.x1)); const y0 = Math.floor(Math.min(bounds.y0, bounds.y1)); const x1 = Math.ceil(Math.max(bounds.x0, bounds.x1)); const y1 = Math.ceil(Math.max(bounds.y0, bounds.y1)); return createRectContext({ originX: x0, originY: y0, width: Math.max(1, x1 - x0), height: Math.max(1, y1 - y0), name }); } export function rectBounds(ctx) { return { x0: ctx.originX, y0: ctx.originY, x1: ctx.originX + ctx.width, y1: ctx.originY + ctx.height }; } export function clampRectToBounds(rect, bounds) { const x0 = Math.max(bounds.x0 ?? 0, Math.floor(rect.x0)); const y0 = Math.max(bounds.y0 ?? 0, Math.floor(rect.y0)); const x1 = Math.min(bounds.x1 ?? Infinity, Math.ceil(rect.x1)); const y1 = Math.min(bounds.y1 ?? Infinity, Math.ceil(rect.y1)); return { x0, y0, x1: Math.max(x0, x1), y1: Math.max(y0, y1) }; } export function expandRectBounds(rect, margin, bounds = null) { const expanded = { x0: Math.floor(rect.x0) - margin, y0: Math.floor(rect.y0) - margin, x1: Math.ceil(rect.x1) + margin, y1: Math.ceil(rect.y1) + margin, }; return bounds ? clampRectToBounds(expanded, bounds) : expanded; } export function rectNeighbors8(ctx, x, y) { const out = []; for (let dy = -1; dy <= 1; dy++) { for (let dx = -1; dx <= 1; dx++) { if (!dx && !dy) continue; const nx = x + dx; const ny = y + dy; if (rectInside(ctx, nx, ny)) out.push([nx, ny]); } } return out; } export function rectDistanceToEdge(ctx, x, y) { return Math.min(x, y, ctx.width - 1 - x, ctx.height - 1 - y); } export function createRectTerrainFields(ctx) { const size = ctx.size; const flowTo = new Int32Array(size); flowTo.fill(-1); return { elevation: new Float32Array(size), moisture: new Float32Array(size), slope: new Float32Array(size), sea: new Uint8Array(size), ocean: new Uint8Array(size), lake: new Uint8Array(size), river: new Float32Array(size), floodplain: new Float32Array(size), plain: new Float32Array(size), agriculture: new Float32Array(size), ridgeField: new Float32Array(size), valleyField: new Float32Array(size), basinField: new Float32Array(size), coastalLowland: new Float32Array(size), flowAccum: new Float32Array(size), erosionField: new Float32Array(size), depositionField: new Float32Array(size), arcSpineField: new Float32Array(size), branchRidgeField: new Float32Array(size), depositionalLowland: new Float32Array(size), alluvialFanField: new Float32Array(size), deltaField: new Float32Array(size), naturalBarrierScore: new Float32Array(size), flowTo, portSuitability: new Float32Array(size), crossingSuitability: new Float32Array(size), passSuitability: new Float32Array(size), visibleRavineField: new Float32Array(size), surfaceTextureField: new Float32Array(size), watershedId: (() => { const a = new Int32Array(size); a.fill(-1); return a; })(), naturalCompartmentId: (() => { const a = new Int32Array(size); a.fill(-1); return a; })(), regionId: (() => { const a = new Int32Array(size); a.fill(-1); return a; })(), }; } export function isRectCellField(value, ctx) { return ArrayBuffer.isView(value) && typeof value.length === "number" && value.length === ctx.size; } export function rectQuantile(values, q) { const arr = Array.from(values).filter(Number.isFinite).sort((a, b) => a - b); if (!arr.length) return 0; const p = clamp(q) * (arr.length - 1); const i = Math.floor(p); const f = p - i; return arr[i] + (arr[Math.min(arr.length - 1, i + 1)] - arr[i]) * f; }