Lakes no longer pour water to a guessed lip and repair it afterwards. Each lake lowers the land in the nearest valley with a smoothstep bowl, and water fills it to its spill level (the lower of the highest ground on each side, Priority-Flood in one dimension), so it is flat and contained by construction. Clay beds, no caves or loose pockets close under lakes, trees kept off the shore. Removes the levelling pass, the lower-rim lip and the bank-slope cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cj2RPXdPykKMEz5BXXczsj
75 lines
4.2 KiB
JavaScript
75 lines
4.2 KiB
JavaScript
// World generation helpers: noise, hard rock specks and lake spill levels.
|
|
// ---------- Perlin noise ----------
|
|
function makeNoise(rng) {
|
|
const perm = new Uint8Array(512), gx = new Float32Array(256), gy = new Float32Array(256);
|
|
const p = []; for (let i = 0; i < 256; i++) p.push(i);
|
|
for (let i = 255; i > 0; i--) { const j = Math.floor(rng() * (i + 1)); const t = p[i]; p[i] = p[j]; p[j] = t; }
|
|
for (let i = 0; i < 512; i++) perm[i] = p[i & 255];
|
|
for (let i = 0; i < 256; i++) { const a = rng() * Math.PI * 2; gx[i] = Math.cos(a); gy[i] = Math.sin(a); }
|
|
return function (x, y) {
|
|
const xi = Math.floor(x), yi = Math.floor(y), xf = x - xi, yf = y - yi;
|
|
const X = xi & 255, Y = yi & 255;
|
|
const p0 = perm[Y], p1 = perm[Y + 1];
|
|
const h00 = perm[X + p0], h10 = perm[X + 1 + p0], h01 = perm[X + p1], h11 = perm[X + 1 + p1];
|
|
const n00 = gx[h00] * xf + gy[h00] * yf, n10 = gx[h10] * (xf - 1) + gy[h10] * yf;
|
|
const n01 = gx[h01] * xf + gy[h01] * (yf - 1), n11 = gx[h11] * (xf - 1) + gy[h11] * (yf - 1);
|
|
const u = xf * xf * xf * (xf * (xf * 6 - 15) + 10), v = yf * yf * yf * (yf * (yf * 6 - 15) + 10);
|
|
return (n00 + u * (n10 - n00) + v * (n01 - n00) + u * v * (n00 - n10 - n01 + n11)) * 1.41;
|
|
};
|
|
}
|
|
function fbm(n, x, y, oct) { let s = 0, a = 1, f = 1, t = 0; for (let o = 0; o < oct; o++) { s += n(x * f, y * f) * a; t += a; a *= 0.5; f *= 2; } return s / t; }
|
|
|
|
// Small specks of rock harder than the rock around them (a few grains of iron ore in stone, a
|
|
// chip of granite, a sliver of basalt) are not worth having: a bit too weak to break them leaves
|
|
// them hanging in the way. Any such piece of SPECK_MIN grains or fewer becomes the softer rock
|
|
// around it. Bigger veins and pockets stay. live: the world is in play (an older save).
|
|
const SPECK_MIN = 30;
|
|
function clearHardSpecks(live) {
|
|
const seen = new Uint8Array(N), st = new Int32Array(N), cells = new Int32Array(SPECK_MIN + 1);
|
|
let n = 0;
|
|
for (const h0 of [6, 5, 4]) {
|
|
seen.fill(0);
|
|
const mem = (i) => { const m = mat[i]; return (flags[i] & F_NAT) !== 0 && PH[m] === PH_SOLID && HARD[m] >= h0 && HARD[m] <= 6 && YLD[m] !== 0 && !PARTMAT[m]; };
|
|
const soft = (i) => { const m = mat[i]; return (flags[i] & F_NAT) !== 0 && PH[m] === PH_SOLID && HARD[m] >= 1 && HARD[m] < h0 && YLD[m] !== 0 && !PARTMAT[m]; };
|
|
for (let i0 = 0; i0 < N; i0++) {
|
|
if (seen[i0] || !mem(i0)) continue;
|
|
let sp = 0, sz = 0, x0 = W, y0 = H, x1 = 0, y1 = 0;
|
|
const hosts = new Map();
|
|
st[sp++] = i0; seen[i0] = 1;
|
|
while (sp) {
|
|
const i = st[--sp], x = i % W, y = (i / W) | 0;
|
|
if (sz <= SPECK_MIN) cells[sz] = i;
|
|
sz++;
|
|
if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y;
|
|
for (const j of [x > 0 ? i - 1 : -1, x < W - 1 ? i + 1 : -1, i - W, i + W]) {
|
|
if (j < 0 || j >= N || seen[j]) continue;
|
|
if (mem(j)) { seen[j] = 1; st[sp++] = j; }
|
|
else if (soft(j)) hosts.set(mat[j], (hosts.get(mat[j]) || 0) + 1);
|
|
}
|
|
}
|
|
if (sz > SPECK_MIN) continue;
|
|
// a speck left floating (in a cave, or dug round) takes the rock nearest round it
|
|
if (!hosts.size) for (let y = Math.max(0, y0 - 5); y <= Math.min(H - 1, y1 + 5); y++) for (let x = Math.max(0, x0 - 5); x <= Math.min(W - 1, x1 + 5); x++) { const j = y * W + x; if (soft(j)) hosts.set(mat[j], (hosts.get(mat[j]) || 0) + 1); }
|
|
let host = 0, best = 0;
|
|
for (const [m, c] of hosts) if (c > best) { best = c; host = m; }
|
|
if (!host) continue;
|
|
for (let k = 0; k < sz; k++) { const i = cells[k]; if (live) setCell(i, host, temp[i], F_NAT); else mat[i] = host; }
|
|
n++;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Water top for each column of [x0, x1] on the 1D side-view surface: the lower of the highest
|
|
// ground to its left and to its right (Priority-Flood reduced to one dimension; y grows
|
|
// downward). Writes out[x0..x1] and returns the deepest water.
|
|
function spillLevels(x0, x1, out) {
|
|
for (let x = x0, top = surfY[x0]; x <= x1; x++) { top = Math.min(top, surfY[x]); out[x] = top; }
|
|
let deepest = 0;
|
|
for (let x = x1, top = surfY[x1]; x >= x0; x--) {
|
|
top = Math.min(top, surfY[x]); out[x] = Math.max(out[x], top);
|
|
deepest = Math.max(deepest, surfY[x] - out[x]);
|
|
}
|
|
return deepest;
|
|
}
|