0x01876c26…01casent to0xbd11994a…7699·#25,476,494·view on Etherscan
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];