fuzzyworld

tower / interior-flight / files / tools / noise.mjs

Branch interior-flight · default branch · all branches

noise.mjs

2.1 KB · history · blame

1/**
2 * Deterministic value noise with fBm, wrapping on the y (theta) axis.
3 *
4 * The map is a cylinder: y wraps and x does not. Noise that ignores that leaves a
5 * visible seam down the map, so the lattice wraps at a caller-supplied period.
6 */
7
8/**
9 * 32-bit integer hash. Uses Math.imul throughout: the obvious float version
10 * silently loses precision on large products and leaves axis-aligned streaks
11 * across the terrain.
12 */
13function hash(x, y, seed) {
14 let h = Math.imul(x | 0, 374761393) ^ Math.imul(y | 0, 668265263) ^ Math.imul(seed | 0, 1274126177)
15 h = Math.imul(h ^ (h >>> 15), 2246822519)
16 h = Math.imul(h ^ (h >>> 13), 3266489917)
17 return ((h ^ (h >>> 16)) >>> 0) / 4294967295
18}
19
20const smooth = (t) => t * t * (3 - 2 * t)
21
22/** Value noise at (x, y) on a lattice, wrapping y every `wrapY` cells. */
23function value(x, y, seed, wrapY) {
24 const xi = Math.floor(x)
25 const yi = Math.floor(y)
26 const xf = smooth(x - xi)
27 const yf = smooth(y - yi)
28 const wrap = (v) => (wrapY > 0 ? ((v % wrapY) + wrapY) % wrapY : v)
29 const y0 = wrap(yi)
30 const y1 = wrap(yi + 1)
31 const a = hash(xi, y0, seed)
32 const b = hash(xi + 1, y0, seed)
33 const c = hash(xi, y1, seed)
34 const d = hash(xi + 1, y1, seed)
35 return (a + (b - a) * xf) * (1 - yf) + (c + (d - c) * xf) * yf
36}
37
38/** Fractal Brownian motion in [0,1]. `wrapY` is the lattice period on the y axis. */
39export function fbm(x, y, { octaves = 4, seed = 1, wrapY = 0, lacunarity = 2, gain = 0.5 } = {}) {
40 let sum = 0
41 let amp = 1
42 let norm = 0
43 let freq = 1
44 for (let o = 0; o < octaves; o++) {
45 sum += value(x * freq, y * freq, seed + o * 101, wrapY * freq) * amp
46 norm += amp
47 amp *= gain
48 freq *= lacunarity
49 }
50 return sum / norm
51}
52
53/** Ridged fBm — absolute-valued and inverted, which makes mountain crests. */
54export function ridged(x, y, opts = {}) {
55 const { octaves = 4 } = opts
56 let sum = 0
57 let amp = 1
58 let norm = 0
59 let freq = 1
60 for (let o = 0; o < octaves; o++) {
61 const v = 1 - Math.abs(value(x * freq, y * freq, (opts.seed ?? 1) + o * 211, (opts.wrapY ?? 0) * freq) * 2 - 1)
62 sum += v * v * amp
63 norm += amp
64 amp *= 0.5
65 freq *= 2
66 }
67 return sum / norm
68}