1/* 2 * rng.js â deterministic randomness for articulater. 3 * 4 * Everything organic in the library (wobble, bleed, scatter) is driven by a 5 * seeded PRNG so a given illustration renders identically every load. Vary the 6 * seed for a different-but-stable variant. 7 * 8 * Exports: 9 * rng(seed) -> () => float in [0,1) (mulberry32) 10 * smoothNoise(rand, n, wrap) -> (u:0..1) => float in [-1,1] (1-D value noise) 11 * noise2D(seed) -> (x, y) => float in [-1,1] (2-D value noise) 12 * hashSeed(...parts) -> int32 (mix values into a seed) 13 * pick(rand, arr) -> element (uniform choice) 14 * range(rand, lo, hi) -> float in [lo,hi) 15 * gauss(rand, mean, sd) -> approx-normal float 16 */ 17 18// mulberry32 â tiny, fast, good-enough PRNG. Same seed => same stream. 19export function rng(seed) { 20 let a = (seed >>> 0) || 1; 21 return function () { 22 a = (a + 0x6d2b79f5) | 0; 23 let t = Math.imul(a ^ (a >>> 15), 1 | a); 24 t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; 25 return ((t ^ (t >>> 14)) >>> 0) / 4294967296; 26 }; 27} 28 29// Mix arbitrary numbers/strings into a single 32-bit seed (FNV-1a-ish). 30export function hashSeed(...parts) { 31 let h = 2166136261 >>> 0; 32 for (const part of parts) { 33 const s = typeof part === 'string' ? part : String(part); 34 for (let i = 0; i < s.length; i++) { 35 h ^= s.charCodeAt(i); 36 h = Math.imul(h, 16777619); 37 } 38 } 39 return h >>> 0; 40} 41 42// Smooth 1-D noise in [-1,1]: random control values, cosine-interpolated. 43// `wrap` makes control[n] meet control[0] so a closed outline joins cleanly. 44export function smoothNoise(rand, controls, wrap) { 45 const ctrl = Array.from({ length: Math.max(2, controls) }, () => rand() * 2 - 1); 46 const n = ctrl.length; 47 return function (u) { 48 const t = u * (wrap ? n : n - 1); 49 const i0 = Math.floor(t); 50 const f = t - i0; 51 const a = ctrl[i0 % n]; 52 const b = ctrl[(i0 + 1) % n]; 53 const m = (1 - Math.cos(f * Math.PI)) / 2; 54 return a * (1 - m) + b * m; 55 }; 56} 57 58// Smooth 2-D value noise in [-1,1]. Cheap hash-grid + bicubic-ish smoothstep. 59// Used for watercolour edge displacement and density fields. 60export function noise2D(seed) { 61 const s = (seed >>> 0) || 1; 62 const hash = (xi, yi) => { 63 let h = (xi * 374761393 + yi * 668265263) ^ s; 64 h = Math.imul(h ^ (h >>> 13), 1274126177); 65 return ((h ^ (h >>> 16)) >>> 0) / 4294967296 * 2 - 1; // [-1,1] 66 }; 67 const fade = (t) => t * t * (3 - 2 * t); 68 return function (x, y) { 69 const xi = Math.floor(x), yi = Math.floor(y); 70 const xf = x - xi, yf = y - yi; 71 const u = fade(xf), v = fade(yf); 72 const a = hash(xi, yi), b = hash(xi + 1, yi); 73 const c = hash(xi, yi + 1), d = hash(xi + 1, yi + 1); 74 const top = a + u * (b - a); 75 const bot = c + u * (d - c); 76 return top + v * (bot - top); 77 }; 78} 79 80export function pick(rand, arr) { 81 return arr[Math.floor(rand() * arr.length)]; 82} 83 84export function range(rand, lo, hi) { 85 return lo + rand() * (hi - lo); 86} 87 88// Sum of 3 uniforms â cheap approx-normal, plenty for jitter. 89// sum of 3 in [0,3): mean 1.5, sd 0.5; centre and scale to requested sd. 90export function gauss(rand, mean = 0, sd = 1) { 91 const u3 = rand() + rand() + rand(); 92 return mean + (u3 - 1.5) * 2 * sd; 93}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.