memoscan
← all memos

Memo 0x63bcd814…33c67a on Ethereum

Resonance return (a * (1 - tx) + b * tx) * (1 - ty) + (c * (1 - tx) + d * tx) * ty; } function getDominantSource(x, y) { const fx = Math.max(0, Math.min(gridW - 1, Math.round(x * (gridW - 1)))); const fy = Math.max(0, Math.min(gridH - 1, Math.round(y * (gridH - 1)))); const v = domGrid[fy * gridW + fx]; return Math.round((v / 255) * (MAX_SOURCES - 1)); } function draw() { if (!composition || !needsRender) return; needsRender = false; const t0 = performance.now(); renderArtwork(); handleAutoSaveAfterRender(); const t1 = performance.now(); console.log(`render ${(t1 - t0).toFixed(0)} ms`); } function handleAutoSaveAfterRender() { if (!ifSave && !ifBulk) return; saveCanvas('Resonance_' + seed, 'png'); if (ifBulk) { setTimeout(() => { seed = Math.floor(Math.random() * 999999); location.hash = String(seed); generate(); }, 350); } } function renderArtwork() { const P = composition.palette; noStroke(); background(P.paper); if (paperImg) image(paperImg, 0, 0, canvasSize, canvasSize); setSeed(seed * 7 + 1); drawSubstrateGrain(P); setSeed(seed * 11 + 3); drawSubstrateGrid(P); setSeed(seed * 13 + 5); drawGapCircles(P); setSeed(seed * 17 + 7); drawContours(P); } function drawSubstrateGrain(P) { const G = 512; const img = createImage(G, G); img.loadPixels(); for (let i = 0; i < G * G; i++) { const v = rnd() * rnd(); if (v > 0.65) { const a = (v - 0.65) * 3.0; img.pixels[i * 4 + 0] = 0; img.pixels[i * 4 + 1] = 0; img.pixels[i * 4 + 2] = 0; img.pixels[i * 4 + 3] = Math.min(75, Math.floor(a * 110)); } else { img.pixels[i * 4 + 3] = 0; } } img.updatePixels(); push(); drawingContext.imageSmoothingEnabled = false; drawingContext.globalCompositeOperation = 'multiply'; image(img, 0, 0, canvasSize, canvasSize); drawingContext.globalCompositeOperation = 'source-over'; drawingContext.imageSmoothingEnabled = true; pop(); } function drawSubstrateGrid(P) { const inkCol = color(P.ink); const r = red(inkCol), g = green(inkCol), b = blue(inkCol); const minorCells = 200; const minorSpacing = canvasSize / minorCells; const tremor = 0.35 * M; push(); drawingContext.lineCap = 'butt'; drawingContext.strokeStyle = `rgba(${r},${g},${b},0.14)`; drawingContext.lineWidth = 0.34 * M; for (let n = 0; n <= minorCells; n++) { const x = n * minorSpacing; const ox = (Math.sin(n * 13.7 + seed * 0.011) + Math.sin(n * 41.3 + seed * 0.007) * 0.5) * tremor; drawingContext.beginPath(); drawingContext.moveTo(x + ox, 0); drawingContext.lineTo(x + ox, canvasSize); drawingContext.stroke(); } for (let n = 0; n <= minorCells; n++) { const y = n * minorSpacing; const oy = (Math.cos(n * 17.1 + seed * 0.013) + Math.cos(n * 39.7 + seed * 0.009) * 0.5) * tremor; drawingContext.beginPath(); drawingContext.moveTo(0, y + oy); drawingContext.lineTo(canvasSize, y + oy); drawingContext.stroke(); } pop(); } function drawWash(P) { const washImg = createImage(gridW, gridH); washImg.loadPixels(); const groundCol = color(P.ground); const accentCol = color(P.accent); const sec1Col = color(P.sec1); const gR = red(groundCol), gG = green(groundCol), gB = blue(groundCol); const aR = red(accentCol), aG = green(accentCol), aB = blue(accentCol); const sR = red(sec1Col), sG = green(sec1Col), sB = blue(sec1Col); for (let j = 0; j < gridH; j++) { for (let i = 0; i < gridW; i++) { const idx = (j * gridW + i) * 4; const lf = lowGrid[j * gridW + i]; const t = Math.max(0, lf); const tneg = Math.max(0, -lf) * 0.45; const alpha = Math.min(1, t * 0.55 + tneg * 0.45); const mix = tneg / (t + tneg + 0.0001); const sec1Mix = (1 - mix) * 0.25; let r = gR * (1 - mix - sec1Mix) + aR * mix + sR * sec1Mix; let g = gG * (1 - mix - sec1Mix) + aG * mix + sG * sec1Mix; let b = gB * (1 - mix - sec1Mix) + aB * mix + sB * sec1Mix; washImg.pixels[idx + 0] = r; washImg.pixels[idx + 1] = g; washImg.pixels[idx + 2] = b; washImg.pixels[idx + 3] = Math.floor(alpha * 110); } } washImg.updatePixels(); push(); drawingContext.imageSmoothingEnabled = true; image(washImg, 0, 0, canvasSize, canvasSize); pop(); } function drawRegionFills(P) { const palette = composition.palette; const colorOf = (key) => key ? color(palette[key]) : null; for (let s = 0; s < composition.numSources; s++) { const src = composition.sources[s]; if (src.fill === 'none') continue; const fillCol = colorOf(src.fillColor) || color(palette.ink); if (src.fill === 'stipple') drawStipple(s, fillCol); else if (src.fill === 'crosshatch') drawCrossHatch(s, fillCol); else if (src.fill === 'rings') drawInnerRings(s, src, fillCol); else if (src.fill === 'solid') drawSolidFill(s, fillCol); else if (src.fill === 'ribbon') drawRibbon(s, src, fillCol); else if (src.fill === 'dots-grid') drawDotsGrid(s, fillCol); } } function drawStipple(sourceIndex, fillCol) { push(); fill(red(fillCol), green(fillCol), blue(fillCol), 200); noStroke(); const step = canvasSize / 110; for (let y = 0; y < canvasSize; y += step) { for (let x = 0; x < canvasSize; x += step) { const u = (x + (rnd() - 0.5) * step) / canvasSize; const v = (y + (rnd() - 0.5) * step) / canvasSize; if (u < 0 || u > 1 || v < 0 || v > 1) continue; if (getDominantSource(u, v) !== sourceIndex) continue; const f = sampleField(fieldGrid, u, v); const lf = sampleField(lowGrid, u, v); if (lf < 0.15) continue; if (Math.abs(f) < 0.15) continue; const density = Math.abs(f) * 0.7 + lf * 0.4; if (rnd() > density * 1.6) continue; const r = step * 0.18 + Math.abs(f) * step * 0.15; circle(u * canvasSize, v * canvasSize, r); } } pop(); } function drawCrossHatch(sourceIndex, fillCol) { const baseAngle = composition.hatchAngle + (rnd() - 0.5) * 0.4; push(); drawingContext.lineCap = 'round'; for (let pass = 0; pass < 2; pass++) { const a = baseAngle + (pass === 0 ? -0.5 : 0.5); const cosA = Math.cos(a), sinA = Math.sin(a); const spacing = canvasSize / (110 + pass * 10); stroke(red(fillCol), green(fillCol), blue(fillCol), 130); strokeWeight(0.6); for (let s = -canvasSize; s < canvasSize * 2; s += spacing) { const samples = 220; let inSeg = false, segStart = null; for (let k = 0; k <= samples; k++) { const u = k / samples; const cx = canvasSize / 2 + (-sinA) * s + cosA * (u - 0.5) * canvasSize * 1.6; const cy = canvasSize / 2 + (cosA) * s + sinA * (u - 0.5) * canvasSize * 1.6; const ux = cx / canvasSize, uy = cy / canvasSize; let inRegion = false; if (ux >= 0 && ux <= 1 && uy >= 0 && uy <= 1 && getDominantSource(ux, uy) === sourceIndex) { const lf = sampleField(lowGrid, ux, uy); if (lf > 0.12) inRegion = true; } if (inRegion) { if (!inSeg) { segStart = { x: cx, y: cy }; inSeg = true; } } else { if (inSeg) { line(segStart.x, segStart.y, cx, cy); inSeg = false; } } } } } pop(); } function drawInnerRings(sourceIndex, src, fillCol) { push(); noFill(); stroke(red(fillCol), green(fillCol), blue(fillCol), 200); drawingContext.lineCap = 'round'; const cx = src.x * canvasSize, cy = src.y * canvasSize; for (let k = 1; k <= 18; k++) { const r = k * canvasSize * 0.012; strokeWeight(0.6 + (k % 3 === 0 ? 0.4 : 0)); const samples = 140; let prev = null; for (let s = 0; s <= samples; s++) { const t = s / samples; const a = t * Math.PI * 2; const px = cx + Math.cos(a) * r; const py = cy + Math.sin(a) * r; const ux = px / canvasSize, uy = py / canvasSize; if (ux < 0 || ux > 1 || uy < 0 || uy > 1) { prev = null; continue; } const inRegion = getDominantSource(ux, uy) === sourceIndex; if (inRegion && prev) line(prev.x, prev.y, px, py); prev = inRegion ? { x: px, y: py } : null; } } pop(); } function drawSolidFill(sourceIndex, fillCol) { const mw = 256, mh = 256; const img = createImage(mw, mh); img.loadPixels(); const fr = red(fillCol), fg = green(fillCol), fb = blue(fillCol); for (let j = 0; j < mh; j++) { for (let i = 0; i < mw; i++) { const u = i / (mw - 1), v = j / (mh - 1); const idx = (j * mw + i) * 4; const dom = getDominantSource(u, v); if (dom !== sourceIndex) { img.pixels[idx + 3] = 0; continue; } const lf = sampleField(lowGrid, u, v); if (lf < 0.08) { img.pixels[idx + 3] = 0; continue; } const a = Math.min(1, Math.max(0, lf * 1.4 - 0.05)); const dither = rnd() < a ? 1 : a * 0.4; img.pixels[idx + 0] = fr; img.pixels[idx + 1] = fg; img.pixels[idx + 2] = fb; img.pixels[idx + 3] = Math.floor(dither * 200); } } img.updatePixels(); push(); drawingContext.imageSmoothingEnabled = true; drawingContext.globalCompositeOperation = 'multiply'; image(img, 0, 0, canvasSize, canvasSize); drawingContext.globalCompositeOperation = 'source-over'; pop(); } function drawRibbon(sourceIndex, src, fillCol) { push(); drawingContext.lineCap = 'butt'; const cx = src.x * canvasSize, cy = src.y * canvasSize; for (let k = 0; k < 8; k++) { const baseR = k * canvasSize * 0.025 + canvasSize * 0.01; const w = canvasSize * 0.012; const a = 90 + (k % 2) * 70; stroke(red(fillCol), green(fillCol), blue(fillCol), a); strokeWeight(w); noFill(); const samples = 200; let prev = null; for (let s = 0; s <= samples; s++) { const t = s / samples; const ang = t * Math.PI * 2; const rr = baseR * (1 + 0.06 * Math.sin(ang * 5 + k * 0.7)); const px = cx + Math.cos(ang) * rr; const py = cy + Math.sin(ang) * rr; const ux = px / canvasSize, uy = py / canvasSize; if (ux < 0 || ux > 1 || uy < 0 || uy > 1) { prev = null; continue; } const inRegion = getDominantSource(ux, uy) === sourceIndex; if (inRegion && prev) line(prev.x, prev.y, px, py); prev = inRegion ? { x: px, y: py } : null; } } pop(); } function drawDotsGrid(sourceIndex, fillCol) { push(); noStroke(); fill(red(fillCol), green(fillCol), blue(fillCol), 200); const step = canvasSize / 60; for (let y = step / 2; y < canvasSize; y += step) { for (let x = step / 2; x < canvasSize; x += step) { const u = x / canvasSize, v = y / canvasSize; if (getDominantSource(u, v) !== sourceIndex) continue; const lf = sampleField(lowGrid, u, v); if (lf < 0.12) continue; const f = sampleField(fieldGrid, u, v); const r = step * 0.16 + Math.abs(f) * step * 0.12; circle(x, y, r); } } pop(); } function drawHatching(P) { const inkCol = color(P.ink); const angle = composition.hatchAngle; const spacing = canvasSize / 95; const cosA = Math.cos(angle), sinA = Math.sin(angle); push(); stroke(red(inkCol), green(inkCol), blue(inkCol), 50); strokeWeight(0.5); drawingContext.lineCap = 'round'; const hatchThreshold = -0.35; const len = canvasSize * 1.6; for (let s = -canvasSize; s < canvasSize * 2; s += spacing) { const samples = 280; let inSeg = false, segStart = null; for (let k = 0; k <= samples; k++) { const u = k / samples; const cx = canvasSize / 2 + (-sinA) * s + cosA * (u - 0.5) * len; const cy = canvasSize / 2 + (cosA) * s + sinA * (u - 0.5) * len; if (cx < 0 || cx >= canvasSize || cy < 0 || cy >= canvasSize) { if (inSeg) { line(segStart.x, segStart.y, cx, cy); inSeg = false; } continue; } const ux = cx / canvasSize, uy = cy / canvasSize; const v = sampleField(fieldGrid, ux, uy); if (v < hatchThreshold) { if (!inSeg) { segStart = { x: cx, y: cy }; inSeg = true; } } else { if (inSeg) { line(segStart.x, segStart.y, cx, cy); inSeg = false; } } } } pop(); } function drawGapCircles(P) { const inkCol = color(P.ink); const accentCol = color(P.accent); const inkR = red(inkCol), inkG = green(inkCol), inkB = blue(inkCol); const accR = red(accentCol), accG = green(accentCol), accB = blue(accentCol); const gw = gridW, gh = gridH; const gradMag = new Float32Array(gw * gh); for (let j = 1; j < gh - 1; j++) { for (let i = 1; i < gw - 1; i++) { const gx = fieldGrid[j * gw + (i + 1)] - fieldGrid[j * gw + (i - 1)]; const gy = fieldGrid[(j + 1) * gw + i] - fieldGrid[(j - 1) * gw + i]; gradMag[j * gw + i] = Math.hypot(gx, gy); } } const cd = composition.circleDensity != null ? composition.circleDensity : 0.7; const lerp = (a, b, t) => a + (b - a) * t; const gapThreshold = lerp(0.04, 0.10, cd); const cellsPerSide = lerp(70, 130, cd); const cellSizeNorm = 1 / cellsPerSide; const candidateCount = Math.max(1, Math.ceil(cellsPerSide - 0.5)); const placed = []; const HASH_CELL_NORM = 14 / DEFAULT_SIZE; const hashCols = Math.ceil(1 / HASH_CELL_NORM) + 1; const hashGrid = new Map(); const hashKey = (i, j) => i * hashCols + j; const hashAdd = (p) => { const i = Math.floor(p.u / HASH_CELL_NORM); const j = Math.floor(p.v / HASH_CELL_NORM); const k = hashKey(i, j); if (!hashGrid.has(k)) hashGrid.set(k, []); hashGrid.get(k).push(p); }; const placementCoef = lerp(0.7, 1.05, cd); const radiusBaseNorm = lerp(1.2, 1.8, cd) / DEFAULT_SIZE; const radiusRangeNorm = lerp(3.0, 5.0, cd) / DEFAULT_SIZE; const spacingBufferNorm = lerp(1.5, -2.5, cd) / DEFAULT_SIZE; const edgeClampNorm = 4 / DEFAULT_SIZE; for (let cy = 0; cy < candidateCount; cy++) { for (let cx = 0; cx < candidateCount; cx++) { const cu = (cx + 0.5) * cellSizeNorm; const cv = (cy + 0.5) * cellSizeNorm; const jU = cu + (rnd() - 0.5) * cellSizeNorm * 0.7; const jV = cv + (rnd() - 0.5) * cellSizeNorm * 0.7; if (jU < edgeClampNorm || jV < edgeClampNorm || jU > 1 - edgeClampNorm || jV > 1 - edgeClampNorm) continue; const jx = jU * canvasSize; const jy = jV * canvasSize; const gi = Math.max(1, Math.min(gw - 2, Math.round(jU * (gw - 1)))); const gj = Math.max(1, Math.min(gh - 2, Math.round(jV * (gh - 1)))); const grad = gradMag[gj * gw + gi]; if (grad > gapThreshold) continue; const placementProb = placementCoef * (1 - grad / gapThreshold); if (rnd() > placementProb) continue; const rRadiusNorm = radiusBaseNorm + radiusRangeNorm * (1 - grad / gapThreshold) * (0.5 + 0.5 * rnd()); const rRadius = rRadiusNorm * canvasSize; const hi = Math.floor(jU / HASH_CELL_NORM); const hj = Math.floor(jV / HASH_CELL_NORM); let tooClose = false; outer: for (let di = -1; di <= 1; di++) { for (let dj = -1; dj <= 1; dj++) { const bucket = hashGrid.get(hashKey(hi + di, hj + dj)); if (!bucket) continue; for (const p of bucket) { const du = p.u - jU, dv = p.v - jV; const minD = p.rNorm + rRadiusNorm + spacingBufferNorm; if (du * du + dv * dv < minD * minD) { tooClose = true; break outer; } } } } if (tooClose) continue; const newCircle = { u: jU, v: jV, rNorm: rRadiusNorm }; placed.push(newCircle); hashAdd(newCircle); } } push(); drawingContext.lineCap = 'round'; drawingContext.lineJoin = 'round'; for (const c of placed) { drawScribbledCircle(c.u * canvasSize, c.v * canvasSize, c.rNorm * canvasSize, inkR, inkG, inkB, accR, accG, accB); } pop(); } function drawScribbledCircle(cx, cy, radius, inkR, inkG, inkB, accR, accG, accB) { const numPasses = radius < 2.5 * M ? (1 + (rnd() < 0.4 ? 1 : 0)) : (2 + Math.floor(rnd() * 3)); const mixColors = rnd() < 0.4; const dominantIsAccent = rnd() < 0.18; for (let pass = 0; pass < numPasses; pass++) { const startA = rnd() * Math.PI * 2; const arcSpan = (0.7 + 1.6 * rnd()) * Math.PI * 2; const direction = rnd() < 0.5 ? 1 : -1; const segs = Math.max(8, Math.floor((radius / M) * 2.5 * (arcSpan / (Math.PI * 2)))); const noisePhase = rnd() * Math.PI * 2; const noiseFreq = 2 + rnd() * 4; const noiseAmp = radius * (0.12 + 0.20 * rnd()); let r0, g0, b0; if (mixColors) { if (rnd() < 0.35) { r0 = accR; g0 = accG; b0 = accB; } else { r0 = inkR; g0 = inkG; b0 = inkB; } } else { if (dominantIsAccent) { r0 = accR; g0 = accG; b0 = accB; } else { r0 = inkR; g0 = inkG; b0 = inkB; } } r0 += (rnd() - 0.5) * 18; g0 += (rnd() - 0.5) * 18; b0 += (rnd() - 0.5) * 18; r0 = Math.max(0, Math.min(255, r0)); g0 = Math.max(0, Math.min(255, g0)); b0 = Math.max(0, Math.min(255, b0)); const alpha = 0.30 + 0.30 * rnd(); const w = (0.5 + 0.6 * rnd()) * M; drawingContext.strokeStyle = `rgba(${r0 | 0},${g0 | 0},${b0 | 0},${alpha})`; drawingContext.lineWidth = w; drawingContext.beginPath(); for (let i = 0; i <= segs; i++) { const t = i / segs; const a = startA + direction * arcSpan * t; const wob = Math.sin(a * noiseFreq + noisePhase) * noiseAmp + Math.sin(a * (noiseFreq * 1.7) + noisePhase * 1.3) * (noiseAmp * 0.5) + (rnd() - 0.5) * radius * 0.10; const r = radius + wob; const px = cx + Math.cos(a) * r; const py = cy + Math.sin(a) * r; if (i === 0) drawingContext.moveTo(px, py); else drawingContext.lineTo(px, py); } drawingContext.stroke(); } } function drawContours(P) { const inkCol = color(P.ink); const primaryCol = color(P.primary); const accentCol = color(P.accent); const featureLevels = new Set(); const numLevels = composition.contourLevels.length; featureLevels.add(Math.floor(numLevels * 0.25)); featureLevels.add(Math.floor(numLevels * 0.75)); if (rnd() < 0.5) featureLevels.add(Math.floor(numLevels * 0.5)); for (let lvl = 0; lvl < numLevels; lvl++) { const t = composition.contourLevels[lvl]; const threshold = (t - 0.5) * 1.7; const polylines = marchingSquares(fieldGrid, gridW, gridH, threshold); let strokeCol, weight; const isFeature = featureLevels.has(lvl); const isCenter = Math.abs(t - 0.5) < 0.06; if (isCenter) { strokeCol = accentCol; weight = composition.baseWeight * 2.8 * M; } else if (isFeature) { strokeCol = primaryCol; weight = composition.baseWeight * 2.0 * M; } else { strokeCol = inkCol; weight = composition.baseWeight * (0.7 + 0.6 * Math.abs(t - 0.5) * 2) * M; } const levelWeightVariation = isCenter ? 0.92 + 0.36 * rnd() : (isFeature ? 0.80 + 0.55 * rnd() : 0.65 + 0.75 * rnd()); for (const poly of polylines) { if (poly.length < 4) continue; const polyWeightVariation = 0.82 + 0.36 * rnd(); drawPencilContour(poly, strokeCol, weight * levelWeightVariation * polyWeightVariation); } } } function drawPencilContour(polyline, col, baseWeight) { const pts = polyline.map(p => { const gx = Math.min(gridW - 1, Math.max(0, Math.round(p.x))); const gy = Math.min(gridH - 1, Math.max(0, Math.round(p.y))); return { x: (p.x / (gridW - 1)) * canvasSize, y: (p.y / (gridH - 1)) * canvasSize, gx, gy }; }); let energySum = 0; for (const p of pts) energySum += Math.abs(lowGrid[p.gy * gridW + p.gx]); const energyAvg = energySum / pts.length; const energyFactor = Math.min(1, 0.18 + energyAvg * 1.6); const tremor = Math.max(0.3 * M, 1.6 * M - baseWeight * 0.4); const tpts = pts.map((p, i) => { const dx = (Math.sin(i * 0.31 + seed * 0.013) + Math.sin(i * 0.117 + seed * 0.027) * 0.6) * tremor; const dy = (Math.cos(i * 0.27 + seed * 0.019) + Math.cos(i * 0.131 + seed * 0.033) * 0.6) * tremor; return { x: p.x + dx, y: p.y + dy }; }); const cumulative = [0]; for (let i = 1; i < tpts.length; i++) { const dx = tpts[i].x - tpts[i - 1].x; const dy = tpts[i].y - tpts[i - 1].y; cumulative.push(cumulative[i - 1] + Math.hypot(dx, dy)); } const totalLen = cumulative[cumulative.length - 1]; if (totalLen < 4 * M) return; const r = red(col), g = green(col), b = blue(col); if ((baseWeight / M) > composition.baseWeight * 1.4) { push(); noFill(); stroke(r, g, b, Math.floor(32 * energyFactor)); strokeWeight(baseWeight * 3.0); drawingContext.lineCap = 'round'; drawingContext.lineJoin = 'round'; drawSmoothCurve(tpts); pop(); } const variants = makeColorVariants(r, g, b); const unscaledWeight = baseWeight / M; const density = 0.38 + 0.82 * Math.min(1, unscaledWeight / (composition.baseWeight * 2.8)); const numStrokes = Math.max(2, Math.floor((totalLen / M) * density)); let lastIdx = 1; const sampleAt = (s) => { while (lastIdx > 1 && cumulative[lastIdx - 1] > s) lastIdx--; while (lastIdx < cumulative.length - 1 && cumulative[lastIdx] < s) lastIdx++; const i = lastIdx; const segStart = cumulative[i - 1]; const segLen = cumulative[i] - segStart; const t = segLen > 0 ? (s - segStart) / segLen : 0; const x = tpts[i - 1].x * (1 - t) + tpts[i].x * t; const y = tpts[i - 1].y * (1 - t) + tpts[i].y * t; let tx = tpts[i].x - tpts[i - 1].x; let ty = tpts[i].y - tpts[i - 1].y; const tlen = Math.hypot(tx, ty); if (tlen > 0) { tx /= tlen; ty /= tlen; } else { tx = 1; ty = 0; } return { x, y, tx, ty }; }; push(); drawingContext.lineCap = 'round';