1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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
|