184 lines
5.2 KiB
JavaScript
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;
|
|
}
|
|
}
|