150 lines
5 KiB
JavaScript
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;
|
|
}
|