Files
2026-09-16 19:59:18 +02:00

184 lines
5.2 KiB
JavaScript

// Deterministic simplex noise: a self-contained implementation with fractal
// controls (frequency, octaves, lacunarity, gain) so the shared generator
// produces identical worlds on the server and in the browser for a given seed.
import { mulberry32 } from "./rng.js";
const GRAD3 = [
[1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0],
[1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1],
[0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1],
];
const F2 = 0.5 * (Math.sqrt(3) - 1);
const G2 = (3 - Math.sqrt(3)) / 6;
const F3 = 1 / 3;
const G3 = 1 / 6;
function buildPermutation(seed) {
const p = new Uint8Array(256);
for (let i = 0; i < 256; i++) p[i] = i;
const rand = mulberry32(seed);
for (let i = 255; i > 0; i--) {
const j = Math.floor(rand() * (i + 1));
const tmp = p[i];
p[i] = p[j];
p[j] = tmp;
}
const perm = new Uint8Array(512);
const permMod12 = new Uint8Array(512);
for (let i = 0; i < 512; i++) {
perm[i] = p[i & 255];
permMod12[i] = perm[i] % 12;
}
return { perm, permMod12 };
}
export class SimplexNoise {
constructor(seed) {
const { perm, permMod12 } = buildPermutation(seed >>> 0);
this.perm = perm;
this.permMod12 = permMod12;
}
noise2D(xin, yin) {
const perm = this.perm;
const permMod12 = this.permMod12;
let n0 = 0;
let n1 = 0;
let n2 = 0;
const s = (xin + yin) * F2;
const i = Math.floor(xin + s);
const j = Math.floor(yin + s);
const t = (i + j) * G2;
const x0 = xin - (i - t);
const y0 = yin - (j - t);
let i1;
let j1;
if (x0 > y0) {
i1 = 1;
j1 = 0;
} else {
i1 = 0;
j1 = 1;
}
const x1 = x0 - i1 + G2;
const y1 = y0 - j1 + G2;
const x2 = x0 - 1 + 2 * G2;
const y2 = y0 - 1 + 2 * G2;
const ii = i & 255;
const jj = j & 255;
let t0 = 0.5 - x0 * x0 - y0 * y0;
if (t0 >= 0) {
const gi0 = permMod12[ii + perm[jj]];
t0 *= t0;
n0 = t0 * t0 * (GRAD3[gi0][0] * x0 + GRAD3[gi0][1] * y0);
}
let t1 = 0.5 - x1 * x1 - y1 * y1;
if (t1 >= 0) {
const gi1 = permMod12[ii + i1 + perm[jj + j1]];
t1 *= t1;
n1 = t1 * t1 * (GRAD3[gi1][0] * x1 + GRAD3[gi1][1] * y1);
}
let t2 = 0.5 - x2 * x2 - y2 * y2;
if (t2 >= 0) {
const gi2 = permMod12[ii + 1 + perm[jj + 1]];
t2 *= t2;
n2 = t2 * t2 * (GRAD3[gi2][0] * x2 + GRAD3[gi2][1] * y2);
}
return 70 * (n0 + n1 + n2);
}
noise3D(xin, yin, zin) {
const perm = this.perm;
const permMod12 = this.permMod12;
let n0 = 0;
let n1 = 0;
let n2 = 0;
let n3 = 0;
const s = (xin + yin + zin) * F3;
const i = Math.floor(xin + s);
const j = Math.floor(yin + s);
const k = Math.floor(zin + s);
const t = (i + j + k) * G3;
const x0 = xin - (i - t);
const y0 = yin - (j - t);
const z0 = zin - (k - t);
let i1;
let j1;
let k1;
let i2;
let j2;
let k2;
if (x0 >= y0) {
if (y0 >= z0) {
[i1, j1, k1, i2, j2, k2] = [1, 0, 0, 1, 1, 0];
} else if (x0 >= z0) {
[i1, j1, k1, i2, j2, k2] = [1, 0, 0, 1, 0, 1];
} else {
[i1, j1, k1, i2, j2, k2] = [0, 0, 1, 1, 0, 1];
}
} else if (y0 < z0) {
[i1, j1, k1, i2, j2, k2] = [0, 0, 1, 0, 1, 1];
} else if (x0 < z0) {
[i1, j1, k1, i2, j2, k2] = [0, 1, 0, 0, 1, 1];
} else {
[i1, j1, k1, i2, j2, k2] = [0, 1, 0, 1, 1, 0];
}
const x1 = x0 - i1 + G3;
const y1 = y0 - j1 + G3;
const z1 = z0 - k1 + G3;
const x2 = x0 - i2 + 2 * G3;
const y2 = y0 - j2 + 2 * G3;
const z2 = z0 - k2 + 2 * G3;
const x3 = x0 - 1 + 3 * G3;
const y3 = y0 - 1 + 3 * G3;
const z3 = z0 - 1 + 3 * G3;
const ii = i & 255;
const jj = j & 255;
const kk = k & 255;
let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
if (t0 >= 0) {
const gi0 = permMod12[ii + perm[jj + perm[kk]]];
t0 *= t0;
n0 = t0 * t0 * (GRAD3[gi0][0] * x0 + GRAD3[gi0][1] * y0 + GRAD3[gi0][2] * z0);
}
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
if (t1 >= 0) {
const gi1 = permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]];
t1 *= t1;
n1 = t1 * t1 * (GRAD3[gi1][0] * x1 + GRAD3[gi1][1] * y1 + GRAD3[gi1][2] * z1);
}
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
if (t2 >= 0) {
const gi2 = permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]];
t2 *= t2;
n2 = t2 * t2 * (GRAD3[gi2][0] * x2 + GRAD3[gi2][1] * y2 + GRAD3[gi2][2] * z2);
}
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
if (t3 >= 0) {
const gi3 = permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]];
t3 *= t3;
n3 = t3 * t3 * (GRAD3[gi3][0] * x3 + GRAD3[gi3][1] * y3 + GRAD3[gi3][2] * z3);
}
return 32 * (n0 + n1 + n2 + n3);
}
// Fractal Brownian motion over either the 2D or the 3D field.
fbm(x, y, z, config) {
let amplitude = 1;
let frequency = config.frequency;
let sum = 0;
let max = 0;
for (let o = 0; o < config.octaves; o++) {
sum += amplitude * (z === undefined
? this.noise2D(x * frequency, y * frequency)
: this.noise3D(x * frequency, y * frequency, z * frequency));
max += amplitude;
amplitude *= config.gain;
frequency *= config.lacunarity;
}
return max > 0 ? sum / max : 0;
}
}