map/rectContext.js
2026-05-28 23:51:55 +09:00

150 lines
5 KiB
JavaScript

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;
}