PageSourceSearch

https://solarise.dev/assets/vendor/articulater/src/rng.js

js solarise.dev collected 2026-10-09 22:13:42 UTC 3,328 bytes, 93 lines download raw bytes

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.