// noise for world generation. everything is built from named noises, the same ones mods get through // FeatureChunk.noise_2d and noise_3d, so a generator using them gives the same world wherever it runs import { Alea, create_noise_2d, create_noise_3d, NoiseFunction2D, NoiseFunction3D } from "@paulaboks/rng"; // create_noise_2d(new Alea(seed + "_" + name)), cached since building the permutation tables is slow const noise_2d_cache = new Map(); const noise_3d_cache = new Map(); export function named_noise_2d(seed: string, name: string): NoiseFunction2D { const key = `${seed}_${name}`; let noise = noise_2d_cache.get(key); if (!noise) { noise = create_noise_2d(new Alea(key)); noise_2d_cache.set(key, noise); } return noise; } export function named_noise_3d(seed: string, name: string): NoiseFunction3D { const key = `${seed}_${name}`; let noise = noise_3d_cache.get(key); if (!noise) { noise = create_noise_3d(new Alea(key)); noise_3d_cache.set(key, noise); } return noise; } // several octaves of simplex noise, like minecraft's NormalNoise: each octave has twice the frequency and half the // strength of the last, times its amplitude. wavelength is the size in blocks of the first octave's features, an // amplitude of 0 skips that octave. the result is roughly -1 to 1 but bunched in the middle, see Quantiles for an // even spread export class OctaveNoise2D { #octaves: { noise: NoiseFunction2D; frequency: number; amplitude: number }[] = []; #total: number; constructor(seed: string, name: string, wavelength: number, amplitudes: number[]) { amplitudes.forEach((amplitude, i) => { if (amplitude !== 0) { this.#octaves.push({ noise: named_noise_2d(seed, `${name}_${i}`), frequency: 2 ** i / wavelength, amplitude: amplitude / 2 ** i, }); } }); this.#total = amplitudes.reduce((sum, amplitude, i) => sum + amplitude / 2 ** i, 0); } sample(x: number, z: number) { let value = 0; for (const { noise, frequency, amplitude } of this.#octaves) { value += noise(x * frequency, z * frequency) * amplitude; } return value / this.#total; } } export class OctaveNoise3D { #octaves: { noise: NoiseFunction3D; frequency: number; amplitude: number }[] = []; #total: number; // how much slower it changes vertically than horizontally #vertical_stretch: number; constructor(seed: string, name: string, wavelength: number, amplitudes: number[], vertical_stretch = 1) { amplitudes.forEach((amplitude, i) => { if (amplitude !== 0) { this.#octaves.push({ noise: named_noise_3d(seed, `${name}_${i}`), frequency: 2 ** i / wavelength, amplitude: amplitude / 2 ** i, }); } }); this.#total = amplitudes.reduce((sum, amplitude, i) => sum + amplitude / 2 ** i, 0); this.#vertical_stretch = vertical_stretch; } sample(x: number, y: number, z: number) { let value = 0; const sy = y / this.#vertical_stretch; for (const { noise, frequency, amplitude } of this.#octaves) { value += noise(x * frequency, sy * frequency, z * frequency) * amplitude; } return value / this.#total; } } // maps a noise's bunched up values to an even spread from -1 to 1, so "the lowest 20%" is always below -0.6. // built from the noise's measured percentiles (every 5%, see tools/noise_quantiles.ts), which only depend // on its amplitudes export class Quantiles { #values: readonly number[]; constructor(values: readonly number[]) { this.#values = values; } even(value: number) { const values = this.#values; const last = values.length - 1; if (value <= values[0]) return -1; if (value >= values[last]) return 1; let lo = 0; let hi = last; while (hi - lo > 1) { const mid = (lo + hi) >> 1; if (values[mid] <= value) lo = mid; else hi = mid; } const t = (value - values[lo]) / (values[hi] - values[lo]); return ((lo + t) / last) * 2 - 1; } } // helpers export function clamp(value: number, min: number, max: number) { return value < min ? min : value > max ? max : value; } export function lerp(t: number, from: number, to: number) { return from + (to - from) * t; } // 0 below edge0, 1 above edge1, smooth in between export function smoothstep(edge0: number, edge1: number, value: number) { const t = clamp((value - edge0) / (edge1 - edge0), 0, 1); return t * t * (3 - 2 * t); }