memoscan
← all memos

Memo 0x131b149e…2c2120 on Ethereum

Resonancelet RNG_STATE = 1; function setSeed(s) { RNG_STATE = (s >>> 0) || 1; } function rnd() { let t = RNG_STATE += 0x6D2B79F5; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; } const rrange = (a, b) => a + (b - a) * rnd(); const rint = (a, b) => Math.floor(rrange(a, b + 1)); const rpick = (arr) => arr[Math.floor(rnd() * arr.length)]; const rchance = (p) => rnd() < p; const rgauss = () => { let u = 1 - rnd(), v = rnd(); return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v); }; const rshuffle = (arr) => { const a = arr.slice(); for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(rnd() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; }; function makeColorVariants(r, g, b) { const clamp = v => Math.max(0, Math.min(255, v | 0)); return [ { r: clamp(r), g: clamp(g), b: clamp(b) }, { r: clamp(r + 18), g: clamp(g + 15), b: clamp(b + 10) }, { r: clamp(r - 14), g: clamp(g - 12), b: clamp(b - 8) }, { r: clamp(r + 10), g: clamp(g + 5), b: clamp(b - 2) }, { r: clamp(r - 2), g: clamp(g + 4), b: clamp(b + 14) }, ]; } const PALETTES = [ { name: 'Cyanotype', paper: '#ecdfc8', ground: '#7a96b3', primary: '#1c3a5e', accent: '#c44536', ink: '#0d1b2a', sec1: '#a8b8c8', sec2: '#5d2a1a' }, { name: 'Bauhaus', paper: '#f0e7d2', ground: '#d6a24a', primary: '#2c2a4a', accent: '#a23216', ink: '#16161a', sec1: '#9a8268', sec2: '#3a4a6c' }, { name: 'Hokusai', paper: '#e8dcc4', ground: '#7d99b3', primary: '#1d3557', accent: '#bc4b51', ink: '#1d1d1f', sec1: '#a8a48f', sec2: '#5a4838' }, { name: 'Riso', paper: '#f3eee2', ground: '#7fb3a8', primary: '#e84a5f', accent: '#2b3a67', ink: '#101820', sec1: '#c8a87a', sec2: '#3a3a3a' }, { name: 'Clay', paper: '#ece2cf', ground: '#a78a6b', primary: '#5a3825', accent: '#7a4a31', ink: '#1f1a14', sec1: '#8a6e52', sec2: '#3a2a1a' }, { name: 'Verdigris', paper: '#ecdcb8', ground: '#8aa39b', primary: '#264027', accent: '#b8553a', ink: '#181818', sec1: '#6a7a5a', sec2: '#5a3a28' }, { name: 'Sumi', paper: '#ede4d0', ground: '#b9b1a3', primary: '#2a2a2a', accent: '#9c2a1a', ink: '#0e0e0e', sec1: '#7a7468', sec2: '#4a4438' }, { name: 'Cobalt', paper: '#e6d9b8', ground: '#9aa9b8', primary: '#1f3a8a', accent: '#e0a458', ink: '#0a0a0f', sec1: '#5a6a8a', sec2: '#7a4a28' }, { name: 'Iron Oxide', paper: '#e8d8b8', ground: '#a89478', primary: '#7a2a1d', accent: '#3a3a3a', ink: '#191512', sec1: '#bfa37e', sec2: '#5a342a' }, { name: 'Lichen', paper: '#e6dcc0', ground: '#7d8a6b', primary: '#3a2e22', accent: '#a8543a', ink: '#1a1814', sec1: '#a8aa8a', sec2: '#4a3a26' }, { name: 'Chartreuse', paper: '#ece8d6', ground: '#a8a89c', primary: '#1a1a1c', accent: '#d4c834', ink: '#0d0d10', sec1: '#7a7a72', sec2: '#3a3a3c' }, { name: 'Caution', paper: '#efeadc', ground: '#9c9a8e', primary: '#15151a', accent: '#f0c020', ink: '#08080c', sec1: '#6e6c64', sec2: '#3a3a3a' }, { name: 'Newsprint', paper: '#e6dab0', ground: '#9a8e72', primary: '#1d1a14', accent: '#c8a026', ink: '#13110c', sec1: '#b6a884', sec2: '#5a4f3c' }, { name: 'Brass', paper: '#ecdec0', ground: '#8e8a82', primary: '#2a2622', accent: '#e3a82a', ink: '#14120e', sec1: '#a8a094', sec2: '#4a463e' }, { name: 'Concrete', paper: '#e4e1d2', ground: '#828086', primary: '#1c1d22', accent: '#d8b62a', ink: '#0c0d12', sec1: '#a8a6a3', sec2: '#42434a' }, { name: 'Moss', paper: '#e6dcc0', ground: '#92a87a', primary: '#324326', accent: '#b86838', ink: '#161814', sec1: '#a8b58e', sec2: '#5a4a30' }, { name: 'Plum', paper: '#ecdfc8', ground: '#9a8aa0', primary: '#4a2a52', accent: '#c89a3a', ink: '#1a1218', sec1: '#a8a0aa', sec2: '#6a3a48' }, { name: 'Indigo', paper: '#e6dac4', ground: '#92a0b0', primary: '#1a2848', accent: '#c8b58a', ink: '#0c1020', sec1: '#7a8ca0', sec2: '#3a4a68' }, { name: 'Graphite', paper: '#e2e0d8', ground: '#9c9a96', primary: '#2a2c30', accent: '#5a5c60', ink: '#0a0b0e', sec1: '#bcbab4', sec2: '#46484c' }, { name: 'Oxblood', paper: '#ece0c8', ground: '#a0827a', primary: '#5a1f24', accent: '#d4a85a', ink: '#1a0e10', sec1: '#b09080', sec2: '#3a2a26' }, { name: 'Teal', paper: '#ece4ce', ground: '#7aa8a4', primary: '#1a4a52', accent: '#d8a836', ink: '#0e1a1c', sec1: '#a0bcb6', sec2: '#5a4a30' }, { name: 'Memphis', paper: '#f0e8d6', ground: '#3ec0b8', primary: '#e63a85', accent: '#2a8acc', ink: '#101015', sec1: '#f5c032', sec2: '#1a1a22' }, { name: 'Holi', paper: '#ecdfc2', ground: '#1ea58e', primary: '#e8602a', accent: '#d92766', ink: '#1a1010', sec1: '#f0b820', sec2: '#0d3a52' }, { name: 'Suprematist', paper: '#ede1c4', ground: '#a8a098', primary: '#d4321a', accent: '#1c40a8', ink: '#0a0a0e', sec1: '#e8b820', sec2: '#1a1a1a' }, { name: 'Acid', paper: '#ece6d8', ground: '#3acfc8', primary: '#ec3a98', accent: '#a8e030', ink: '#0c0a14', sec1: '#9a3ad8', sec2: '#2a8af0' }, { name: 'Loteria', paper: '#ecdfbe', ground: '#28a8b8', primary: '#1a8aa0', accent: '#d83a82', ink: '#0e1218', sec1: '#f0a820', sec2: '#1a48a0' }, { name: 'Pop', paper: '#f2ecda', ground: '#e8c828', primary: '#f0c020', accent: '#d8261a', ink: '#0a0a0e', sec1: '#1c50c8', sec2: '#e85a9a' }, ]; const FIELD_VERT = ` precision highp float; attribute vec3 aPosition; attribute vec2 aTexCoord; varying vec2 vUv; uniform mat4 uProjectionMatrix; uniform mat4 uModelViewMatrix; void main() { vUv = aTexCoord; gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0); } `; const MAX_SOURCES = 24; const FIELD_FRAG = ` precision highp float; varying vec2 vUv; uniform int uNumSources; uniform vec2 uPos[${MAX_SOURCES}]; uniform float uFreq[${MAX_SOURCES}]; uniform float uPhase[${MAX_SOURCES}]; uniform float uAmp[${MAX_SOURCES}]; uniform float uSign[${MAX_SOURCES}]; uniform float uDecay; uniform float uWarp; uniform float uWarpFreq; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } float vnoise(vec2 p){ vec2 i = floor(p), f = fract(p); float a = hash(i); float b = hash(i + vec2(1.0, 0.0)); float c = hash(i + vec2(0.0, 1.0)); float d = hash(i + vec2(1.0, 1.0)); vec2 u = f*f*(3.0-2.0*f); return mix(a,b,u.x) + (c-a)*u.y*(1.0-u.x) + (d-b)*u.x*u.y; } float fbm(vec2 p){ float v = 0.0, a = 0.5; for (int i = 0; i < 4; i++) { v += a * vnoise(p); p *= 2.03; a *= 0.5; } return v; } void main() { vec2 uv = vUv; vec2 w = vec2(fbm(uv * uWarpFreq + 13.1), fbm(uv * uWarpFreq + 71.7)) - 0.5; vec2 p = uv + w * uWarp; float field = 0.0; float lowfreq = 0.0; float maxLocal = -1.0; float dominantId = 0.0; for (int i = 0; i < ${MAX_SOURCES}; i++) { if (i >= uNumSources) break; vec2 c = uPos[i]; float d = distance(p, c); float wave = cos(d * uFreq[i] - uPhase[i]) * uAmp[i] * uSign[i]; float falloff = 1.0 / (1.0 + uDecay * d * d); field += wave * falloff; lowfreq += uSign[i] * uAmp[i] * exp(-d * d * 6.0); float infl = uAmp[i] * exp(-d * d * 6.0); if (infl > maxLocal) { maxLocal = infl; dominantId = float(i) / float(${MAX_SOURCES} - 1); } } float fEnc = clamp(field * 0.5 + 0.5, 0.0, 1.0); float bEnc = clamp(lowfreq * 0.5 + 0.5, 0.0, 1.0); gl_FragColor = vec4(fEnc, dominantId, bEnc, 1.0); } `; const PAPER_FRAG = ` precision highp float; varying vec2 vUv; uniform float uSeed; uniform vec3 uPaperColor; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7)) + uSeed) * 43758.5453); } float vnoise(vec2 p){ vec2 i = floor(p), f = fract(p); float a = hash(i); float b = hash(i + vec2(1.0, 0.0)); float c = hash(i + vec2(0.0, 1.0)); float d = hash(i + vec2(1.0, 1.0)); vec2 u = f*f*(3.0-2.0*f); return mix(a,b,u.x) + (c-a)*u.y*(1.0-u.x) + (d-b)*u.x*u.y; } float fbm(vec2 p){ float v = 0.0, a = 0.5; for (int i = 0; i < 5; i++) { v += a * vnoise(p); p *= 2.07; a *= 0.5; } return v; } void main() { vec2 uv = vUv; float macroTone = fbm(uv * 1.6 + vec2(7.3, 11.1)) * 2.0 - 0.5; macroTone = clamp(macroTone, -0.5, 1.0); float midTone = fbm(uv * 3.0) * 0.5 + 0.5; vec2 fdir = vec2(fbm(uv * 8.0 + 1.7), fbm(uv * 8.0 + 9.3)) - 0.5; vec2 anis = uv * vec2(180.0, 180.0) + fdir * 6.0; float fibers = vnoise(anis * vec2(1.0, 0.18)); float grain = hash(floor(uv * 1100.0)); vec3 warmTint = uPaperColor * vec3(0.97, 0.92, 0.84) * 0.93; vec3 coolTint = uPaperColor * vec3(0.95, 0.96, 0.99) * 0.97; float warmFactor = clamp(macroTone, 0.0, 1.0); float coolFactor = clamp(-macroTone, 0.0, 1.0); vec3 baseCol = uPaperColor; baseCol = mix(baseCol, warmTint, warmFactor * 0.65); baseCol = mix(baseCol, coolTint, coolFactor * 0.55); float dark = 1.0 - midTone * 0.05 - fibers * 0.08 - grain * 0.06; vec2 cv = uv - 0.5; float vig = 1.0 - dot(cv, cv) * 0.22; vec2 cornerPick = vec2( step(0.5, fract(uSeed * 0.71)) * 2.0 - 1.0, step(0.5, fract(uSeed * 0.31)) * 2.0 - 1.0 ); vec2 cornerPos = vec2(0.5, 0.5) + cornerPick * 0.5; float cornerDist = distance(uv, cornerPos); float cornerAge = smoothstep(0.7, 0.0, cornerDist) * 0.08; vig -= cornerAge; vec3 col = baseCol * dark * vig; gl_FragColor = vec4(col, 1.0); } `; let composition = null; function buildComposition() { const palette = rpick(PALETTES); const numSources = rint(5, 12); const sources = []; const gridN = Math.ceil(Math.sqrt(numSources * 1.6)); const cells = []; for (let i = 0; i < gridN; i++) for (let j = 0; j < gridN; j++) cells.push([i, j]); const shuffled = rshuffle(cells); const regime = rpick(['intricate', 'open', 'mixed', 'dense']); let baseFreq, freqSpread, ampDist; if (regime === 'intricate') { baseFreq = 90; freqSpread = 50; ampDist = 'flat'; } if (regime === 'open') { baseFreq = 38; freqSpread = 20; ampDist = 'tilted'; } if (regime === 'mixed') { baseFreq = 55; freqSpread = 70; ampDist = 'tilted'; } if (regime === 'dense') { baseFreq = 130; freqSpread = 70; ampDist = 'flat'; } for (let i = 0; i < numSources; i++) { const [ci, cj] = shuffled[i]; const cellSize = 1 / gridN; const x = (ci + 0.2 + 0.6 * rnd()) * cellSize; const y = (cj + 0.2 + 0.6 * rnd()) * cellSize; const freq = baseFreq + rgauss() * freqSpread * 0.4; const phase = rrange(0, Math.PI * 2); let amp; if (ampDist === 'flat') amp = rrange(0.7, 1.0); else amp = (i === 0) ? rrange(0.9, 1.1) : rrange(0.3, 0.8); const sign = rchance(0.5) ? 1 : -1; sources.push({ x, y, freq: Math.max(8, freq), phase, amp, sign }); } const targetFeatureCount = rint(2, 3); const featureIndices = []; const sourceOrder = rshuffle(Array.from({ length: numSources }, (_, i) => i)); for (const idx of sourceOrder) { if (featureIndices.length >= targetFeatureCount) break; const c = sources[idx]; let ok = true; for (const fi of featureIndices) { const fc = sources[fi]; const dx = c.x - fc.x, dy = c.y - fc.y; if (dx * dx + dy * dy < 0.18 * 0.18) { ok = false; break; } } if (ok) featureIndices.push(idx); } const featureSet = new Set(featureIndices); const allStyles = ['stipple', 'crosshatch', 'rings', 'solid', 'ribbon', 'dots-grid']; const stylePool = rshuffle(allStyles).slice(0, rint(2, 3)); const colorPool = rshuffle(['accent', 'primary', 'sec2']); for (let i = 0; i < numSources; i++) { if (featureSet.has(i)) { sources[i].fill = rpick(stylePool); const featPos = featureIndices.indexOf(i); sources[i].fillColor = colorPool[featPos % colorPool.length]; } else { sources[i].fill = 'none'; sources[i].fillColor = null; } } const densityMode = (() => { const r = rnd(); if (r < 0.25) return 'sparse'; if (r < 0.85) return 'balanced'; return 'dense'; })(); let numContours; if (densityMode === 'sparse') numContours = rint(12, 18); else if (densityMode === 'balanced') numContours = rint(24, 34); else numContours = rint(36, 48); const contourLevels = []; for (let i = 0; i < numContours; i++) contourLevels.push((i + 0.5) / numContours); const decay = rrange(0.8, 2.5); const warp = rrange(0.0, 0.05); const warpFreq = rrange(2, 5); const showHatch = rchance(0.7); const showFrame = rchance(0.7); const showAnnotations = rchance(0.55); const showSplatter = rchance(0.7); const baseWeight = rrange(0.6, 1.3); const hatchAngle = rnd() * Math.PI; const circleDensity = Math.sqrt(rnd()); return { palette, sources, numSources, contourLevels, decay, warp, warpFreq, showHatch, showFrame, showAnnotations, showSplatter, baseWeight, regime, hatchAngle, densityMode, circleDensity }; } function marchingSquares(field, w, h, threshold) { const segments = []; const get = (i, j) => field[j * w + i]; for (let j = 0; j < h - 1; j++) { for (let i = 0; i < w - 1; i++) { const a = get(i, j); const b = get(i + 1, j); const c = get(i + 1, j + 1); const d = get(i, j + 1); let code = 0; if (a > threshold) code |= 1; if (b > threshold) code |= 2; if (c > threshold) code |= 4; if (d > threshold) code |= 8; if (code === 0 || code === 15) continue; const top = () => ({ x: i + (threshold - a) / (b - a), y: j }); const right = () => ({ x: i + 1, y: j + (threshold - b) / (c - b) }); const bottom = () => ({ x: i + (threshold - d) / (c - d), y: j + 1 }); const left = () => ({ x: i, y: j + (threshold - a) / (d - a) }); switch (code) { case 1: case 14: segments.push([left(), top()]); break; case 2: case 13: segments.push([top(), right()]); break; case 3: case 12: segments.push([left(), right()]); break; case 4: case 11: segments.push([right(), bottom()]); break; case 6: case 9: segments.push([top(), bottom()]); break; case 7: case 8: segments.push([left(), bottom()]); break; case 5: { const center = (a + b + c + d) * 0.25; if (center > threshold) { segments.push([left(), top()]); segments.push([right(), bottom()]); } else { segments.push([left(), bottom()]); segments.push([top(), right()]); } break; } case 10: { const center = (a + b + c + d) * 0.25; if (center > threshold) { segments.push([top(), right()]); segments.push([left(), bottom()]); } else { segments.push([left(), top()]); segments.push([right(), bottom()]); } break; } } } } return stitchSegments(segments); } function stitchSegments(segments) { const PREC = 1000; const key = (p) => `${Math.round(p.x * PREC)}_${Math.round(p.y * PREC)}`; const map = new Map(); segments.forEach((s, idx) => { const k0 = key(s[0]); const k1 = key(s[1]); if (!map.has(k0)) map.set(k0, []); map.get(k0).push({ idx, end: 0 }); if (!map.has(k1)) map.set(k1, []); map.get(k1).push({ idx, end: 1 }); }); const used = new Array(segments.length).fill(false); const polylines = []; for (let start = 0; start < segments.length; start++) { if (used[start]) continue; used[start] = true; const poly = [segments[start][0], segments[start][1]]; let extending = true; while (extending) { extending = false; const tail = poly[poly.length - 1]; const list = map.get(key(tail)) || []; for (const { idx, end } of list) { if (used[idx]) continue; const seg = segments[idx]; const other = seg[1 - end]; poly.push(other); used[idx] = true; extending = true; break; } } extending = true; while (extending) { extending = false; const head = poly[0]; const list = map.get(key(head)) || []; for (const { idx, end } of list) { if (used[idx]) continue; const seg = segments[idx]; const other = seg[1 - end]; poly.unshift(other); used[idx] = true; extending = true; break; } } if (poly.length >= 3) polylines.push(poly); } return polylines; } const DEFAULT_SIZE = 1000; const SCREEN_PIXEL_DENSITY = 2; const PRINT_EXPORT_SIZE = 5000; const PRINT_EXPORT_PIXEL_DENSITY = 1; let ifSave = false; let ifBulk = false; let canvasSize = 800; let M = 1; let fieldFBO, fieldShader; let paperFBO, paperShader, paperImg; let fieldGrid, domGrid, lowGrid; let gridW = 0, gridH = 0; let mainCanvas; let seed = 1; let needsRender = false; let seedLocked = false; // --- On-chain seed ----------------------------------------------------- // The contract's tokenURI builds this page on-chain and injects the current // seed as window.ARTSCRIPT_SEED in a <script> before this sketch. When the // holder calls updateSeed(...), tokenURI regenerates with the new seed, so // the piece always reflects current on-chain state — no URL, no RPC. With no // injected seed the file runs in "studio mode": a random, explorable seed. // Reduce any uint256 to the 32-bit value the renderer's RNG uses. Small // curated seeds (e.g. 931222) are preserved exactly. function reduceSeed(big) { const MOD = 4294967296n; // 2^32 return Number(((big % MOD) + MOD) % MOD); } // Read the seed the contract injected into the page. The on-chain tokenURI // emits an inline script setting window.ARTSCRIPT_SEED before this sketch, // so the rendered piece always reflects current on-chain state. function readInjectedSeed() { if (typeof window.ARTSCRIPT_SEED === 'undefined' || window.ARTSCRIPT_SEED === null) return null; const raw = String(window.ARTSCRIPT_SEED).trim(); if (!/^[0-9]+$/.test(raw)) return null; try { return reduceSeed(BigInt(raw)); } catch (e) { return null; } } function setup() { // The on-chain HTML wrapper is minimal — ensure page styling + holder. document.documentElement.style.cssText = 'margin:0;padding:0;width:100%;height:100%'; document.body.style.cssText = 'margin:0;padding:0;width:100%;height:100%;background:#1a1a1a;overflow:hidden;display:flex;align-items:center;justify-content:center'; let holder = document.getElementById('canvas-holder'); if (!holder) { holder = document.createElement('div'); holder.id = 'canvas-holder'; holder.style.cssText = 'display:flex;align-items:center;justify-content:center;width:100vmin;height:100vmin'; document.body.appendChild(holder); } canvasSize = Math.floor(Math.min(window.innerWidth, window.innerHeight)); M = canvasSize / DEFAULT_SIZE; pixelDensity(SCREEN_PIXEL_DENSITY); mainCanvas = createCanvas(canvasSize, canvasSize); mainCanvas.parent(holder); const FBO_RES = 384; fieldFBO = createGraphics(FBO_RES, FBO_RES, WEBGL); fieldFBO.pixelDensity(1); fieldShader = fieldFBO.createShader(FIELD_VERT, FIELD_FRAG); fieldFBO.shader(fieldShader); const PAPER_RES = 1024; paperFBO = createGraphics(PAPER_RES, PAPER_RES, WEBGL); paperFBO.pixelDensity(1); paperShader = paperFBO.createShader(FIELD_VERT, PAPER_FRAG); const injected = readInjectedSeed(); if (injected !== null) { seed = injected; // seed delivered on-chain by the contract seedLocked = true; } else { seed = Math.floor(Math.random() * 999999); // studio mode (no injected seed) location.hash = String(seed); } generate(); noLoop(); } function uploadFieldUniforms() { const N = composition.sources.length; const posArr = []; const freqArr = []; const phaseArr = []; const ampArr = []; const signArr = []; for (let i = 0; i < MAX_SOURCES; i++) { if (i < N) { posArr.push(composition.sources[i].x, composition.sources[i].y); freqArr.push(composition.sources[i].freq); phaseArr.push(composition.sources[i].phase); ampArr.push(composition.sources[i].amp); signArr.push(composition.sources[i].sign); } else { posArr.push(0, 0); freqArr.push(0); phaseArr.push(0); ampArr.push(0); signArr.push(0); } } fieldShader.setUniform('uNumSources', N); fieldShader.setUniform('uPos', posArr); fieldShader.setUniform('uFreq', freqArr); fieldShader.setUniform('uPhase', phaseArr); fieldShader.setUniform('uAmp', ampArr); fieldShader.setUniform('uSign', signArr); fieldShader.setUniform('uDecay', composition.decay); fieldShader.setUniform('uWarp', composition.warp); fieldShader.setUniform('uWarpFreq', composition.warpFreq); } function renderFieldToGrid() { fieldFBO.noStroke(); fieldFBO.shader(fieldShader); fieldFBO.rect(-fieldFBO.width / 2, -fieldFBO.height / 2, fieldFBO.width, fieldFBO.height); fieldFBO.loadPixels(); gridW = fieldFBO.width; gridH = fieldFBO.height; fieldGrid = new Float32Array(gridW * gridH); domGrid = new Uint8Array(gridW * gridH); lowGrid = new Float32Array(gridW * gridH); const px = fieldFBO.pixels; for (let j = 0; j < gridH; j++) { for (let i = 0; i < gridW; i++) { const idx = (j * gridW + i) * 4; fieldGrid[j * gridW + i] = (px[idx + 0] / 255) * 2 - 1; domGrid[j * gridW + i] = px[idx + 1]; lowGrid[j * gridW + i] = (px[idx + 2] / 255) * 2 - 1; } } } function renderPaper() { const palette = composition.palette; const c = color(palette.paper); paperFBO.shader(paperShader); paperShader.setUniform('uSeed', seed % 1000); paperShader.setUniform('uPaperColor', [red(c) / 255, green(c) / 255, blue(c) / 255]); paperFBO.noStroke(); paperFBO.rect(-paperFBO.width / 2, -paperFBO.height / 2, paperFBO.width, paperFBO.height); paperImg = paperFBO.get(); } function generate() { setSeed(seed); composition = buildComposition(); uploadFieldUniforms(); renderFieldToGrid(); renderPaper(); needsRender = true; redraw(); } function sampleField(grid, x, y) { const fx = x * (gridW - 1); const fy = y * (gridH - 1); const i0 = Math.floor(fx), j0 = Math.floor(fy); const i1 = Math.min(gridW - 1, i0 + 1); const j1 = Math.min(gridH - 1, j0 + 1); const tx = fx - i0, ty = fy - j0; const a = grid[j0 * gridW + i0]; const b = grid[j0 * gridW + i1]; const c = grid[j1 * gridW + i0]; const d = grid[j1 * gridW + i1];