|
| 1 | +// Simplex noise constants |
| 2 | +const F2 = 0.5 * (Math.sqrt(3) - 1); |
| 3 | +const G2 = (3 - Math.sqrt(3)) / 6; |
| 4 | +const grad3 = [ |
| 5 | + [1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0], |
| 6 | + [1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1], |
| 7 | + [0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1] |
| 8 | +]; |
| 9 | + |
| 10 | +class SimplexNoise { |
| 11 | + constructor(seed = Math.random()) { |
| 12 | + this.p = new Uint8Array(256); |
| 13 | + for (let i = 0; i < 256; i++) { |
| 14 | + this.p[i] = Math.floor(seed * 256); |
| 15 | + seed = (seed * 9301 + 49297) % 233280 / 233280; |
| 16 | + } |
| 17 | + this.perm = new Uint8Array(512); |
| 18 | + for (let i = 0; i < 512; i++) { |
| 19 | + this.perm[i] = this.p[i & 255]; |
| 20 | + } |
| 21 | + } |
| 22 | + |
| 23 | + dot(g, x, y) { |
| 24 | + return g[0] * x + g[1] * y; |
| 25 | + } |
| 26 | + |
| 27 | + noise(xin, yin) { |
| 28 | + xin = Math.round(xin * 1000) / 1000; |
| 29 | + yin = Math.round(yin * 1000) / 1000; |
| 30 | + let n0, n1, n2; |
| 31 | + let s = (xin + yin) * F2; |
| 32 | + let i = Math.floor(xin + s); |
| 33 | + let j = Math.floor(yin + s); |
| 34 | + let t = (i + j) * G2; |
| 35 | + let X0 = i - t; |
| 36 | + let Y0 = j - t; |
| 37 | + let x0 = xin - X0; |
| 38 | + let y0 = yin - Y0; |
| 39 | + let i1, j1; |
| 40 | + if (x0 > y0) { |
| 41 | + i1 = 1; j1 = 0; |
| 42 | + } else { |
| 43 | + i1 = 0; j1 = 1; |
| 44 | + } |
| 45 | + let x1 = x0 - i1 + G2; |
| 46 | + let y1 = y0 - j1 + G2; |
| 47 | + let x2 = x0 - 1 + 2 * G2; |
| 48 | + let y2 = y0 - 1 + 2 * G2; |
| 49 | + let ii = i & 255; |
| 50 | + let jj = j & 255; |
| 51 | + let perm = this.perm; |
| 52 | + let gi0 = perm[ii + perm[jj]] % 12; |
| 53 | + let gi1 = perm[ii + i1 + perm[jj + j1]] % 12; |
| 54 | + let gi2 = perm[ii + 1 + perm[jj + 1]] % 12; |
| 55 | + let t0 = 0.5 - x0 * x0 - y0 * y0; |
| 56 | + n0 = (t0 < 0) ? 0 : (t0 * t0) ** 2 * this.dot(grad3[gi0], x0, y0); |
| 57 | + let t1 = 0.5 - x1 * x1 - y1 * y1; |
| 58 | + n1 = (t1 < 0) ? 0 : (t1 * t1) ** 2 * this.dot(grad3[gi1], x1, y1); |
| 59 | + let t2 = 0.5 - x2 * x2 - y2 * y2; |
| 60 | + n2 = (t2 < 0) ? 0 : (t2 * t2) ** 2 * this.dot(grad3[gi2], x2, y2); |
| 61 | + return 70 * (n0 + n1 + n2); |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +// Perlin noise implementation |
| 66 | +const permutation = [...Array(256)].map(() => Math.floor(Math.random() * 256)); |
| 67 | +const p = [...permutation, ...permutation]; |
| 68 | + |
| 69 | +function fade(t) { |
| 70 | + return t * t * t * (t * (t * 6 - 15) + 10); |
| 71 | +} |
| 72 | + |
| 73 | +function lerp(t, a, b) { |
| 74 | + return a + t * (b - a); |
| 75 | +} |
| 76 | + |
| 77 | +function grad(hash, x) { |
| 78 | + const h = hash & 15; |
| 79 | + const grad = 1 + (h & 7); |
| 80 | + return (h & 8 ? -grad : grad) * x; |
| 81 | +} |
| 82 | + |
| 83 | +function perlinNoise(x) { |
| 84 | + const X = Math.floor(x) & 255; |
| 85 | + x -= Math.floor(x); |
| 86 | + const u = fade(x); |
| 87 | + return lerp(u, grad(p[X], x), grad(p[X+1], x-1)); |
| 88 | +} |
| 89 | + |
| 90 | +// FBM (Fractional Brownian Motion) - wraps SimplexNoise for multi-octave complexity |
| 91 | +function fbm(simplexInstance, x, y, octaves = 4, persistence = 0.5, lacunarity = 2) { |
| 92 | + let value = 0; |
| 93 | + let amplitude = 1; |
| 94 | + let frequency = 1; |
| 95 | + let maxValue = 0; |
| 96 | + |
| 97 | + for (let i = 0; i < octaves; i++) { |
| 98 | + value += amplitude * simplexInstance.noise(x * frequency, y * frequency); |
| 99 | + maxValue += amplitude; |
| 100 | + amplitude *= persistence; |
| 101 | + frequency *= lacunarity; |
| 102 | + } |
| 103 | + |
| 104 | + return value / maxValue; |
| 105 | +} |
| 106 | + |
| 107 | +// Turbulence - chaotic swirling effect using absolute values of noise |
| 108 | +function turbulence(simplexInstance, x, y, octaves = 4) { |
| 109 | + let value = 0; |
| 110 | + let amplitude = 1; |
| 111 | + let freqX = x; |
| 112 | + let freqY = y; |
| 113 | + |
| 114 | + for (let i = 0; i < octaves; i++) { |
| 115 | + value += amplitude * Math.abs(simplexInstance.noise(freqX, freqY)); |
| 116 | + freqX *= 2; |
| 117 | + freqY *= 2; |
| 118 | + amplitude *= 0.5; |
| 119 | + } |
| 120 | + |
| 121 | + return value; |
| 122 | +} |
| 123 | + |
| 124 | +// Worley Noise (Cellular Noise) - creates organic cellular patterns |
| 125 | +function worley(x, y, cellCount = 4) { |
| 126 | + const cellX = Math.floor(x * cellCount); |
| 127 | + const cellY = Math.floor(y * cellCount); |
| 128 | + const fracX = x * cellCount - cellX; |
| 129 | + const fracY = y * cellCount - cellY; |
| 130 | + |
| 131 | + let minDist = Infinity; |
| 132 | + |
| 133 | + for (let dx = -1; dx <= 1; dx++) { |
| 134 | + for (let dy = -1; dy <= 1; dy++) { |
| 135 | + const nx = cellX + dx; |
| 136 | + const ny = cellY + dy; |
| 137 | + |
| 138 | + // Pseudo-random point in cell using hash function |
| 139 | + const hashX = Math.sin(nx * 73.156 + ny * 94.673) * 43758.5453; |
| 140 | + const hashY = Math.sin(nx * 45.164 + ny * 94.673) * 43758.5453; |
| 141 | + |
| 142 | + const px = (hashX - Math.floor(hashX)) + dx - fracX; |
| 143 | + const py = (hashY - Math.floor(hashY)) + dy - fracY; |
| 144 | + |
| 145 | + const dist = Math.sqrt(px * px + py * py); |
| 146 | + if (dist < minDist) minDist = dist; |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + return Math.min(1, minDist); |
| 151 | +} |
0 commit comments