aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/mapgen/noise.py
diff options
context:
space:
mode:
authorgodosa <godosa@godosa.eu>2026-10-06 23:52:03 +0200
committergodosa <godosa@godosa.eu>2026-10-06 23:52:03 +0200
commit346b1c5195bffc71ceaa9262453e3c189656400b (patch)
tree01ac0d31e2724cd6abcc689a5a228e2cbea2f6cf /mapgen/noise.py
downloadworldgen-346b1c5195bffc71ceaa9262453e3c189656400b.tar.gz
worldgen-346b1c5195bffc71ceaa9262453e3c189656400b.zip
worldgen: initial public history
Diffstat (limited to 'mapgen/noise.py')
-rw-r--r--mapgen/noise.py111
1 files changed, 111 insertions, 0 deletions
diff --git a/mapgen/noise.py b/mapgen/noise.py
new file mode 100644
index 0000000..e52b7f8
--- /dev/null
+++ b/mapgen/noise.py
@@ -0,0 +1,111 @@
+"""Seeded, vectorized 3D value noise and fractal sums on points (N, 3)."""
+from __future__ import annotations
+
+import zlib
+
+import numpy as np
+
+_MASK = (1 << 64) - 1
+_K1 = np.uint64(0x9E3779B185EBCA87)
+_K2 = np.uint64(0xC2B2AE3D27D4EB4F)
+_K3 = np.uint64(0x165667B19E3779F9)
+_M1 = np.uint64(0x94D049BB133111EB)
+
+
+def _hash(ix, iy, iz, seed: int):
+ s = np.uint64((seed * 0x27D4EB2F165667C5 + 0x632BE59BD9B4E019) & _MASK)
+ h = ix.astype(np.uint64) * _K1 ^ iy.astype(np.uint64) * _K2 ^ iz.astype(np.uint64) * _K3 ^ s
+ h ^= h >> np.uint64(31)
+ h *= _M1
+ h ^= h >> np.uint64(29)
+ return (h >> np.uint64(11)).astype(np.float64) / float(1 << 53) * 2.0 - 1.0
+
+
+def value_noise(p, seed: int):
+ if _noise_jit is not None:
+ p = np.asarray(p)
+ if p.dtype == np.float64 and p.ndim == 2 and p.shape[1] == 3:
+ s = np.uint64((seed * 0x27D4EB2F165667C5 + 0x632BE59BD9B4E019) & _MASK)
+ return _noise_jit(np.ascontiguousarray(p), s)
+ return _value_noise(p, seed)
+
+
+def _value_noise(p, seed: int):
+ f = np.floor(p)
+ i = f.astype(np.int64)
+ t = p - f
+ t = t * t * (3.0 - 2.0 * t)
+ out = np.zeros(len(p))
+ for dx in (0, 1):
+ wx = t[:, 0] if dx else 1.0 - t[:, 0]
+ for dy in (0, 1):
+ wy = t[:, 1] if dy else 1.0 - t[:, 1]
+ for dz in (0, 1):
+ wz = t[:, 2] if dz else 1.0 - t[:, 2]
+ out += wx * wy * wz * _hash(i[:, 0] + dx, i[:, 1] + dy, i[:, 2] + dz, seed)
+ return out
+
+
+def _make_noise_jit():
+ """value_noise compiled (numba optional): per point the same integer hash and the same float steps in the same
+ order, so the same values; one call costs microseconds instead of a dozen numpy passes (river meanders call it
+ on a few points at a time). WORLDGEN_NO_JIT=1 turns it off."""
+ import os
+ if os.environ.get("WORLDGEN_NO_JIT"):
+ return None
+ try:
+ import numba
+ except ImportError:
+ return None
+ K1, K2, K3, M1 = _K1, _K2, _K3, _M1
+
+ @numba.njit(cache=True)
+ def noise(p, s):
+ n = p.shape[0]
+ out = np.empty(n)
+ for k in range(n):
+ fx, fy, fz = np.floor(p[k, 0]), np.floor(p[k, 1]), np.floor(p[k, 2])
+ ix, iy, iz = np.int64(fx), np.int64(fy), np.int64(fz)
+ tx, ty, tz = p[k, 0] - fx, p[k, 1] - fy, p[k, 2] - fz
+ tx = tx * tx * (3.0 - 2.0 * tx)
+ ty = ty * ty * (3.0 - 2.0 * ty)
+ tz = tz * tz * (3.0 - 2.0 * tz)
+ acc = 0.0
+ for dx in range(2):
+ wx = tx if dx else 1.0 - tx
+ for dy in range(2):
+ wy = ty if dy else 1.0 - ty
+ for dz in range(2):
+ wz = tz if dz else 1.0 - tz
+ h = (np.uint64(ix + dx) * K1) ^ (np.uint64(iy + dy) * K2) ^ (np.uint64(iz + dz) * K3) ^ s
+ h ^= h >> np.uint64(31)
+ h *= M1
+ h ^= h >> np.uint64(29)
+ v = np.float64(h >> np.uint64(11)) / 9007199254740992.0 * 2.0 - 1.0
+ acc += wx * wy * wz * v
+ out[k] = acc
+ return out
+ return noise
+
+
+_noise_jit = _make_noise_jit()
+
+
+def fbm(xyz, seed: int, octaves: int = 5, freq: float = 2.0, lacunarity: float = 2.0, gain: float = 0.5):
+ total = np.zeros(len(xyz))
+ amp, norm, f = 1.0, 0.0, freq
+ for o in range(octaves):
+ total += amp * value_noise(xyz * f, seed + 1013 * o)
+ norm += amp
+ amp *= gain
+ f *= lacunarity
+ return total / norm
+
+
+def ridged(xyz, seed: int, octaves: int = 5, freq: float = 2.0):
+ return 1.0 - np.abs(fbm(xyz, seed, octaves, freq))
+
+
+def name_seed(seed: int, name: str) -> int:
+ """A per-feature noise seed: the build seed plus a hash of the feature's name (stable across runs)."""
+ return seed + zlib.crc32(name.encode()) % 100000