Better world generation
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
// 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<string, NoiseFunction2D>();
|
||||
const noise_3d_cache = new Map<string, NoiseFunction3D>();
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user