0xfc3d…1282

All memos sent from and to 0xfc3d…1282.

WayOfTheWormH0x8ed8d8169998a1414ca45f16e84127bed7ed38642a4acf41f7c1ceeb8eae6eef``` "Who, then, will wiggle where The Worm wills?"; let propCache, isSetup = false; const vDim = 800, size = vDim * 0.75, vFrameRate = 30, steps = 500, dlAdv = (steps / 12) | 0, bgColor = "#0A0A09", wormPink = "#EC008C", themes = { ephemeral: ["#FFF", "#F8CB1B"], mech: [wormPink, "#ADFF2F"], wriggler: ["#FF0040", "#7300E6B2", false, "#FFF"], classic: [wormPink, "#ADFF2F"], prophet: ["#8052FF", "#FFFFFFCC"], saint: ["#FFB152", "#9169FFCC"], ephemeral_h: [bgColor, "#F8CB1B", "#FFFFFF"], mech_h: [bgColor, "#ADFF2F", "#FFFFFF"], wriggler_h: [bgColor, "#7300E6B2", "#FFFFFF", "#FF0040"], classic_h: [bgColor, "#FFFFFFCC", wormPink], prophet_h: [bgColor, "#FFFFFFCC", "#8052FF", "#9169FF"], saint_h: [bgColor, "#9169FFCC", "#FFB152"], }, drawWorm = (points) => { if (!config.flourishFlag) config.flourishFlag = {}; const length = points.length, headLength = (steps * 0.075) | 0, segmentLength = (steps / 8) | 0; let mechFlourish, flourishPosition; p.noStroke(); // Start drawing outline. for (let i = 0; i < dlAdv; i++) { p.fill(!i ? config.wormOutline : config.wormOutline + "44"); p.circle(...points[i].slice(0, 2), config.wormOutlineSize); } for (let i = 0; i < length; i++) { // Draw ahead for outline. if (i + dlAdv < length) { p.fill( i + dlAdv === length - 1 ? config.wormOutline: config.wormOutline + "44" ); p.circle(...points[i + dlAdv].slice(0, 2), config.wormOutlineSize); } let sOff = (length - headLength) % segmentLength, segment = (segmentLength + ((sOff - i) % segmentLength)) % segmentLength, tail = i < segmentLength, head = i + headLength >= length; // Draw mech segments. if (config.form === "mech" && !tail && i < length - headLength / 2) { if (!segment) { if (i < length - headLength) { flourishPosition = i - 2; mechFlourish = config.flourishFlag[i]; if (!mechFlourish) { mechFlourish = config.flourishFlag[i] = p.random([ -1, -1, -1, 1, 2, p.random(1, 2), p.random(3.1, 3.9), ]); } } p.fill(config.wormOutline); p.circle(...points[i - 2].slice(0, 2), config.wormSize + 8); p.fill( config.form === "mech" && !config.hologram && head ? "#FFF" : config.wormColor ); p.circle(...points[i - 2].slice(0, 2), config.wormSize); p.circle(...points[i - 1].slice(0, 2), config.wormSize); } if (segment && !head && mechFlourish >= 3) { let f0 = points[i - 2], f1 = points[i - 1], a = p.atan2(f1[1] - f0[1], f1[0] - f0[0]), o = p.PI * ((2 / 3) * (mechFlourish - 2)), line = [ f0[0] + (config.wormSize / 2) * p.cos(a + o), f0[1] + (config.wormSize / 2) * p.sin(a + o), ]; p.fill(config.wormOutline); p.circle(...line, 6.5); } else if ( segment && segment < segmentLength / 2 && mechFlourish >= 0 ) { let f0 = points[flourishPosition], f1 = points[flourishPosition + 1], a = p.atan2(f1[1] - f0[1], f1[0] - f0[0]), o = p.PI * ((2 / 3) * mechFlourish), bolt = [ f0[0] + (config.wormSize / 2 - 12) * p.cos(a + o), f0[1] + (config.wormSize / 2 - 12) * p.sin(a + o), ]; mechFlourish = -1; p.fill(config.wormOutline); p.circle(...bolt, 8); } if (segment < steps / 100) continue; } // Wriggler horns. if (config.wriggler && i === length - 1) { p.fill(config.wormOutline); p.stroke(config.wormOutline); p.strokeWeight(3); p.triangle( points[i][0] + 0.45 * config.wormSize * p.cos((13 * p.PI) / 20), points[i][1] - 0.45 * config.wormSize * p.sin((13 * p.PI) / 20), points[i][0] + 0.65 * config.wormSize * p.cos((14 * p.PI) / 20), points[i][1] - 0.65 * config.wormSize * p.sin((14 * p.PI) / 20), points[i][0] + 0.45 * config.wormSize * p.cos((15 * p.PI) / 20), points[i][1] - 0.45 * config.wormSize * p.sin((15 * p.PI) / 20) ); p.triangle( points[i][0] + 0.45 * config.wormSize * p.cos((7 * p.PI) / 20), points[i][1] - 0.45 * config.wormSize * p.sin((7 * p.PI) / 20), points[i][0] + 0.65 * config.wormSize * p.cos((6 * p.PI) / 20), points[i][1] - 0.65 * config.wormSize * p.sin((6 * p.PI) / 20), points[i][0] + 0.45 * config.wormSize * p.cos((5 * p.PI) / 20), points[i][1] - 0.45 * config.wormSize * p.sin((5 * p.PI) / 20) ); p.noStroke(); } // Draw the actual worm body. p.fill( config.form === "mech" && !config.hologram && head ? "#FFF": config.wormColor ); p.circle(...points[i].slice(0, 2), config.wormSize); } }, drawFaceNormal = (l) => { p.fill(config.theme[3] || config.wormOutline); p.noStroke(); p.circle(l[0] - config.wormSize / 6, l[1] - config.wormSize / 24, 6); p.circle(l[0] + config.wormSize / 6, l[1] - config.wormSize / 24, 6); p.noFill(); p.strokeWeight(5); p.stroke(config.wormOutline); p.arc( l[0], l[1] + 5 + config.wormSize / 12, config.wormSize / 5, config.wormSize / 5, 0, p.PI ); if (config.wriggler) { p.line( l[0] - (5 * config.wormSize) / 24, l[1] - config.wormSize / 6, l[0] - config.wormSize / 8, l[1] - config.wormSize / 12 ); p.line( l[0] + (5 * config.wormSize) / 24, l[1] - config.wormSize / 6, l[0] + config.wormSize / 8, l[1] - config.wormSize / 12 ); } }, drawFuckFace = (l, v) => { p.fill("#FFF"); p.stroke(bgColor); p.strokeWeight(3.5); p.ellipse( l[0] - config.wormSize / 6, l[1] - config.wormSize / 12, config.wormSize / 3, config.wormSize / 2.8 ); p.ellipse( l[0] + config.wormSize / 6, l[1] - config.wormSize / 12, config.wormSize / 3, config.wormSize / 2.8 ); p.fill(bgColor); p.noStroke(); p.circle( l[0] + v[0] - config.wormSize / 6, l[1] + v[1] - config.wormSize / 12, config.wormSize / 6 ); p.circle( l[0] + v[0] + config.wormSize / 6, l[1] + v[1] - config.wormSize / 12, config.wormSize / 6 ); p.fill(config.wormOutline); p.stroke(config.wormOutline); p.strokeWeight(5); p.arc( l[0], l[1] + config.wormSize / 5, config.wormSize / 5, config.wormSize / 5, 0, p.PI, p.CHORD ); }, drawFacePeaceful = (l) => { p.stroke(config.theme[3] || config.wormOutline); p.strokeWeight(5); p.noFill(); p.arc( l[0] - config.wormSize / 4.8, l[1] - config.wormSize / 24, config.wormSize / 6, config.wormSize / 6, p.PI / 12, (p.PI * 11) / 12 ); p.arc( l[0] + config.wormSize / 4.8, l[1] - config.wormSize / 24, config.wormSize / 6, config.wormSize / 6, p.PI / 12, (p.PI * 11) / 12 ); p.line( l[0] - config.wormSize / 12, l[1] + 5 + config.wormSize / 12, l[0] + config.wormSize / 12, l[1] + 5 + config.wormSize / 12 ); }, drawSingleHalo = (l, a = 0, o = 0.7) => { p.stroke(config.mech && !config.hologram ? wormPink : "#FFFFFF"); p.strokeWeight(4); p.noFill(); p.push(); p.translate(l[0], l[1] - config.wormSize * o); p.rotate(a); p.ellipse(0, 0, config.wormSize * 0.7, config.wormSize / 5); p.pop(); }, drawDoubleHalo = (l) => { drawSingleHalo(l, p.PI / 6, 0.8); drawSingleHalo(l, -p.PI / 6, 0.8); }, drawGrid = (_, vFrame) => { if (config.inverseGrid) { p.background(config.gridColor.substring(0, 7)); p.stroke(bgColor); } else { p.stroke(config.gridColor); } p.strokeWeight(12); let drift = (vFrame % config.driftSpeed) / config.driftSpeed; for (let i = 1; i < config.horizon; i++) { let y = vDim * (-0.16 + 1.2 * p.tan(p.atan2(1, ((i + drift) * 10) / config.horizon))); p.line(-vDim / 2, y, vDim / 2, y); } for (let i = 0; i < 2 * config.horizon; i++) { let x = vDim * (-4 + (4 * i) / config.horizon); p.line(0, vDim * -0.1, x, vDim / 2); } p.noStroke(); p.fill("#DEADC0DE"); const gradient = p.drawingContext.createLinearGradient( 0, -vDim / 2, 0, vDim / 2 ); gradient.addColorStop(0.48, bgColor); gradient.addColorStop(0.6, bgColor + "00"); p.drawingContext.fillStyle = gradient; p.square(-vDim / 2, -vDim / 2, vDim); }, drawRays = (x, vFrame) => { p.push(); const window = 4, slice = x.slice(-window), sum = slice.reduce((a, x) => [a[0] + x[0], a[1] + x[1]]), drift = p.sin(((p.PI / 16) * vFrame) / config.driftSpeed); p.translate(sum[0] / window, sum[1] / window); p.stroke(config.bgFeatureColor); p.strokeWeight(8); for (let i = 0; i < 48; i++) { p.rotate(p.PI / 24); p.line(-vDim / 2, (drift * vDim) / 16, vDim, 0); } p.pop(); }, drawTriangle = (_, vFrame) => { p.background("#100539"); const angle = p.PI * (((config.triangleDirection * vFrame) % 2000) / 1000 - 0.5); p.noStroke(); p.fill(config.triangleColor + "24"); p.triangle( vDim * 0.8 * p.cos(angle), vDim * 0.8 * p.sin(angle), vDim * 0.8 * p.cos(angle + (2 / 3) * p.PI), vDim * 0.8 * p.sin(angle + (2 / 3) * p.PI), vDim * 0.8 * p.cos(angle + (4 / 3) * p.PI), vDim * 0.8 * p.sin(angle + (4 / 3) * p.PI) ); p.fill(config.triangleColor + "12"); p.triangle( vDim * 0.7 * p.cos(angle), vDim * 0.7 * p.sin(angle), vDim * 0.7 * p.cos(angle + (2 / 3) * p.PI), vDim * 0.7 * p.sin(angle + (2 / 3) * p.PI), vDim * 0.7 * p.cos(angle + (4 / 3) * p.PI), vDim * 0.7 * p.sin(angle + (4 / 3) * p.PI) ); p.noStroke(); p.fill("#DEADC0DE"); const gradient = p.drawingContext.createRadialGradient( 0, 0, 0, 0, 0, vDim * (5 / 6 + (1 / 6) * p.sin(6 * angle)) ); gradient.addColorStop(0, config.triangleGradient + "cc"); gradient.addColorStop(0.5, config.triangleGradient + "4d"); gradient.addColorStop(1, config.triangleGradient + "00"); p.drawingContext.fillStyle = gradient; p.square(-vDim / 2, -vDim / 2, vDim); }; let config; p.myCustomRedrawAccordingToNewPropsHandler = (props) => { if (!props.transactionHash || !props.square) return; if (!isSetup) { propCache = props; return; } // Seed the p5js random seed using 32 bits from the hash. const seedOffset = 2 + 8 * (props.tokenId % 8); p.randomSeed( +("0x" + props.transactionHash.substring(seedOffset, seedOffset + 8)) ); // Set up the style. let formNum = p.random(), faceNum = p.random(), form = formNum < 0.33 ? "ephemeral": formNum < 0.61 ? "mech": formNum < 0.65 ? "prophet": formNum < 0.97 ? "classic": formNum < 0.996 ? "wriggler": "saint", classic = form === "classic", mech = form === "mech", ephemeral = form === "ephemeral", wriggler = form === "wriggler", prophet = form === "prophet", saint = form === "saint", hologram = p.random() < 0.62, theme = themes[form + (hologram ? "_h" : "")], wormColor = theme[0], wormOutline = theme[2] || bgColor, bgFeatureColor = theme[1], wormSize = p.randomGaussian(60 + p.random(20), 10), wormOutlineSize = wormSize + 9, cycle = p.randomGaussian(60, 5) | 0, length = size * p.random(0.29, 0.52) - wormSize, flusterSpeed = p.random(3) | 0, fluster = p.random(size / 6), xInc = (p.random(8) | 0) + 1, yInc = (p.random(8) | 0) + 1, xOff = p.random(p.TWO_PI), yOff = p.random(p.TWO_PI), flusterInc = (p.random(8) | 0) + 6, flusterOff = p.random(5), dSteps = steps, speed = length * xInc + length * yInc + 2 * fluster * flusterInc, jSpeed = length * xInc + length * yInc + (fluster * flusterInc * flusterInc) / 3, dur = (steps * 2 * (1 + p.abs(p.randomGaussian()))) / speed, jit = (steps / jSpeed) * (1.2 + 0.6 * p.abs(p.randomGaussian())); if (wriggler && p.random() < 0.2) { jit *= -1; } if (p.random() < 0.12) { const inc = (1 + p.random(4)) | 0; dur *= inc; dSteps *= inc; } if (p.random() < 0.04) { jit *= p.random(0.1, 0.4); } let bgr = p.random(), bgStats = {}, [bg, drawBackground] = bgr < 0.4 ? ["Grid", drawGrid]: bgr < 0.55 ? ["Rays", drawRays]: bgr < 0.8 ? ["Light", drawTriangle]: ["Void", () => {}]; if (bg === "Grid") { bgStats = { gridColor: p.random() < 0.1 ? "#FFFFFF": wriggler || prophet || p.random() < 0.2 ? bgFeatureColor: "#ADFF2F", horizon: p.randomGaussian(p.random(10, 20), 2) | 0, inverseGrid: p.random() < 0.1, }; if (bgStats.inverseGrid) { bg = "Inverted Grid"; } } else if (bg === "Rays") { const factor = p.randomGaussian(p.random(0.1, 0.3), 0.02); jit *= factor; flusterSpeed *= factor; } else if (bg === "Light") { bgStats = { triangleDirection: p.random([-1, 1]), triangleColor: wriggler ? bgFeatureColor.substring(0, 7): p.random([wormPink, "#38BCE6"]), triangleGradient: p.random([ "#36bce6", "#9f7af2", ...(classic ? ["#f8cb1b"] : []), ...(wriggler ? ["#ff5d40"] : []), ]), }; } let [face, drawFace] = wriggler ? ["Scheming", drawFaceNormal]: faceNum < 0.08 ? ["Peaceful", drawFacePeaceful]: faceNum < 0.11 && !hologram ? ["Lil Fk", drawFuckFace]: ["Smile", drawFaceNormal]; config = { square: props.square, startTime: p.millis(), drawBackground: drawBackground, drawFace: drawFace, drawHalo: ephemeral ? drawSingleHalo: mech ? drawDoubleHalo: (prophet || saint) && p.random() < 0.4 ? drawSingleHalo: () => {}, form: form, theme: theme, classic: classic, wriggler: wriggler, mech: mech, hologram: hologram, bgFeatureColor: bgFeatureColor, ...bgStats, wormColor: wormColor, wormSize: wormSize, wormOutline: wormOutline, wormOutlineSize: wormOutlineSize, driftSpeed: p.randomGaussian(20, 3), cycle: cycle, length: length, jit: jit, dur: dur, dSteps: dSteps, xInc: xInc, xOff: xOff, xOffSize: p.random(1, 5) | 0, xOffSpeed: p.random(3) | 0, yInc: yInc, yOff: yOff, yOffSize: p.random(1, 5) | 0, yOffSpeed: p.random(3) | 0, fluster: fluster, flusterSpeed: flusterSpeed, flusterOff: flusterOff, flusterAmpOff: p.random(cycle), flusterInc: flusterInc, }; window.config = config; // Metadata. let subject = mech ? "Mech Worm MK1": ephemeral ? "The Ephemeral Worm": wriggler ? "The Wriggler": prophet ? "Worm Prophet": saint ? "Worm Saint": "The Earthly Worm"; if (hologram) { subject += wriggler || saint ? " Apparition": prophet ? " Revelation": " Hologram"; } console.log("metadata: ", { Subject: subject, Background: bg, Expression: face, transactionHash: props.transactionHash, }); p.windowResized(); }; p.draw = () => { if (!isSetup || !config) return; p.scale(p.width / vDim); p.translate(vDim / 2, vDim / 2); p.strokeJoin(p.ROUND); const vFrame = (vFrameRate * (p.millis() - config.startTime)) / 1000; let points = []; let { jit } = config; if (config.paused) { jit *= -25 * p.cos((p.TWO_PI * vFrame) / 200); } else { jit *= vFrame; } for (let i = 0; i < config.dSteps; i++) { let j = (jit * p.PI) / 60 + (i / config.dSteps) * config.dur; points.push([ config.length * p.sin(j * config.xInc + config.xOff) + config.fluster * p.sin(config.flusterOff + j * config.flusterInc), config.length * p.cos(j * config.yInc + config.yOff) + config.fluster * p.cos(config.flusterOff + j * config.flusterInc), ]); } p.background(bgColor); config.drawBackground([...points], vFrame); drawWorm([...points]); const ultimate = points.pop(), penultimate = points.pop(), faceVector = [ultimate[0] - penultimate[0], ultimate[1] - penultimate[1]]; config.drawFace( [ultimate[0] + faceVector[0], ultimate[1] + faceVector[1]], faceVector ); config.drawHalo(points.pop()); }; p.setup = () => { isSetup = true; if (propCache) { p.updateWithProps(propCache); } }; p.windowResized = () => { if (!isSetup || !config || !config.square) return; const SCALE = 0.8, WIDTH = (window.innerWidth / config.square) * SCALE, HEIGHT = (window.innerHeight / config.square) * SCALE, DIM = p.min(WIDTH, HEIGHT); p.resizeCanvas(DIM, DIM); }; ```
Way of the WormMark EgliAn on-chain generative tribute to The Worm. Free to mint for select wallets at markegli.com/worm while supplies last.0x8ed8d8169998a1414ca45f16e84127bed7ed38642a4acf41f7c1ceeb8eae6eefp5js
WayOfTheWorm``` "Who, then, will wiggle where The Worm wills?"; let propCache, isSetup = false; const vDim = 800, size = vDim * 0.75, vFrameRate = 30, steps = 500, dlAdv = (steps / 12) | 0, bgColor = "#0A0A09", wormPink = "#EC008C", themes = { ephemeral: ["#FFF", "#F8CB1B"], mech: [wormPink, "#ADFF2F"], wriggler: ["#FF0040", "#7300E6B2", false, "#FFF"], classic: [wormPink, "#ADFF2F"], prophet: ["#8052FF", "#FFFFFFCC"], saint: ["#FFB152", "#9169FFCC"], ephemeral_h: [bgColor, "#F8CB1B", "#FFFFFF"], mech_h: [bgColor, "#ADFF2F", "#FFFFFF"], wriggler_h: [bgColor, "#7300E6B2", "#FFFFFF", "#FF0040"], classic_h: [bgColor, "#FFFFFFCC", wormPink], prophet_h: [bgColor, "#FFFFFFCC", "#8052FF", "#9169FF"], saint_h: [bgColor, "#9169FFCC", "#FFB152"], }, drawWorm = (points) => { if (!config.flourishFlag) config.flourishFlag = {}; const length = points.length, headLength = (steps * 0.075) | 0, segmentLength = (steps / 8) | 0; let mechFlourish, flourishPosition; p.noStroke(); // Start drawing outline. for (let i = 0; i < dlAdv; i++) { p.fill(!i ? config.wormOutline : config.wormOutline + "44"); p.circle(...points[i].slice(0, 2), config.wormOutlineSize); } for (let i = 0; i < length; i++) { // Draw ahead for outline. if (i + dlAdv < length) { p.fill( i + dlAdv === length - 1 ? config.wormOutline: config.wormOutline + "44" ); p.circle(...points[i + dlAdv].slice(0, 2), config.wormOutlineSize); } let sOff = (length - headLength) % segmentLength, segment = (segmentLength + ((sOff - i) % segmentLength)) % segmentLength, tail = i < segmentLength, head = i + headLength >= length; // Draw mech segments. if (config.form === "mech" && !tail && i < length - headLength / 2) { if (!segment) { if (i < length - headLength) { flourishPosition = i - 2; mechFlourish = config.flourishFlag[i]; if (!mechFlourish) { mechFlourish = config.flourishFlag[i] = p.random([ -1, -1, -1, 1, 2, p.random(1, 2), p.random(3.1, 3.9), ]); } } p.fill(config.wormOutline); p.circle(...points[i - 2].slice(0, 2), config.wormSize + 8); p.fill( config.form === "mech" && !config.hologram && head ? "#FFF" : config.wormColor ); p.circle(...points[i - 2].slice(0, 2), config.wormSize); p.circle(...points[i - 1].slice(0, 2), config.wormSize); } if (segment && !head && mechFlourish >= 3) { let f0 = points[i - 2], f1 = points[i - 1], a = p.atan2(f1[1] - f0[1], f1[0] - f0[0]), o = p.PI * ((2 / 3) * (mechFlourish - 2)), line = [ f0[0] + (config.wormSize / 2) * p.cos(a + o), f0[1] + (config.wormSize / 2) * p.sin(a + o), ]; p.fill(config.wormOutline); p.circle(...line, 6.5); } else if ( segment && segment < segmentLength / 2 && mechFlourish >= 0 ) { let f0 = points[flourishPosition], f1 = points[flourishPosition + 1], a = p.atan2(f1[1] - f0[1], f1[0] - f0[0]), o = p.PI * ((2 / 3) * mechFlourish), bolt = [ f0[0] + (config.wormSize / 2 - 12) * p.cos(a + o), f0[1] + (config.wormSize / 2 - 12) * p.sin(a + o), ]; mechFlourish = -1; p.fill(config.wormOutline); p.circle(...bolt, 8); } if (segment < steps / 100) continue; } // Wriggler horns. if (config.wriggler && i === length - 1) { p.fill(config.wormOutline); p.stroke(config.wormOutline); p.strokeWeight(3); p.triangle( points[i][0] + 0.45 * config.wormSize * p.cos((13 * p.PI) / 20), points[i][1] - 0.45 * config.wormSize * p.sin((13 * p.PI) / 20), points[i][0] + 0.65 * config.wormSize * p.cos((14 * p.PI) / 20), points[i][1] - 0.65 * config.wormSize * p.sin((14 * p.PI) / 20), points[i][0] + 0.45 * config.wormSize * p.cos((15 * p.PI) / 20), points[i][1] - 0.45 * config.wormSize * p.sin((15 * p.PI) / 20) ); p.triangle( points[i][0] + 0.45 * config.wormSize * p.cos((7 * p.PI) / 20), points[i][1] - 0.45 * config.wormSize * p.sin((7 * p.PI) / 20), points[i][0] + 0.65 * config.wormSize * p.cos((6 * p.PI) / 20), points[i][1] - 0.65 * config.wormSize * p.sin((6 * p.PI) / 20), points[i][0] + 0.45 * config.wormSize * p.cos((5 * p.PI) / 20), points[i][1] - 0.45 * config.wormSize * p.sin((5 * p.PI) / 20) ); p.noStroke(); } // Draw the actual worm body. p.fill( config.form === "mech" && !config.hologram && head ? "#FFF": config.wormColor ); p.circle(...points[i].slice(0, 2), config.wormSize); } }, drawFaceNormal = (l) => { p.fill(config.theme[3] || config.wormOutline); p.noStroke(); p.circle(l[0] - config.wormSize / 6, l[1] - config.wormSize / 24, 6); p.circle(l[0] + config.wormSize / 6, l[1] - config.wormSize / 24, 6); p.noFill(); p.strokeWeight(5); p.stroke(config.wormOutline); p.arc( l[0], l[1] + 5 + config.wormSize / 12, config.wormSize / 5, config.wormSize / 5, 0, p.PI ); if (config.wriggler) { p.line( l[0] - (5 * config.wormSize) / 24, l[1] - config.wormSize / 6, l[0] - config.wormSize / 8, l[1] - config.wormSize / 12 ); p.line( l[0] + (5 * config.wormSize) / 24, l[1] - config.wormSize / 6, l[0] + config.wormSize / 8, l[1] - config.wormSize / 12 ); } }, drawFuckFace = (l, v) => { p.fill("#FFF"); p.stroke(bgColor); p.strokeWeight(3.5); p.ellipse( l[0] - config.wormSize / 6, l[1] - config.wormSize / 12, config.wormSize / 3, config.wormSize / 2.8 ); p.ellipse( l[0] + config.wormSize / 6, l[1] - config.wormSize / 12, config.wormSize / 3, config.wormSize / 2.8 ); p.fill(bgColor); p.noStroke(); p.circle( l[0] + v[0] - config.wormSize / 6, l[1] + v[1] - config.wormSize / 12, config.wormSize / 6 ); p.circle( l[0] + v[0] + config.wormSize / 6, l[1] + v[1] - config.wormSize / 12, config.wormSize / 6 ); p.fill(config.wormOutline); p.stroke(config.wormOutline); p.strokeWeight(5); p.arc( l[0], l[1] + config.wormSize / 5, config.wormSize / 5, config.wormSize / 5, 0, p.PI, p.CHORD ); }, drawFacePeaceful = (l) => { p.stroke(config.theme[3] || config.wormOutline); p.strokeWeight(5); p.noFill(); p.arc( l[0] - config.wormSize / 4.8, l[1] - config.wormSize / 24, config.wormSize / 6, config.wormSize / 6, p.PI / 12, (p.PI * 11) / 12 ); p.arc( l[0] + config.wormSize / 4.8, l[1] - config.wormSize / 24, config.wormSize / 6, config.wormSize / 6, p.PI / 12, (p.PI * 11) / 12 ); p.line( l[0] - config.wormSize / 12, l[1] + 5 + config.wormSize / 12, l[0] + config.wormSize / 12, l[1] + 5 + config.wormSize / 12 ); }, drawSingleHalo = (l, a = 0, o = 0.7) => { p.stroke(config.mech && !config.hologram ? wormPink : "#FFFFFF"); p.strokeWeight(4); p.noFill(); p.push(); p.translate(l[0], l[1] - config.wormSize * o); p.rotate(a); p.ellipse(0, 0, config.wormSize * 0.7, config.wormSize / 5); p.pop(); }, drawDoubleHalo = (l) => { drawSingleHalo(l, p.PI / 6, 0.8); drawSingleHalo(l, -p.PI / 6, 0.8); }, drawGrid = (_, vFrame) => { if (config.inverseGrid) { p.background(config.gridColor.substring(0, 7)); p.stroke(bgColor); } else { p.stroke(config.gridColor); } p.strokeWeight(12); let drift = (vFrame % config.driftSpeed) / config.driftSpeed; for (let i = 1; i < config.horizon; i++) { let y = vDim * (-0.16 + 1.2 * p.tan(p.atan2(1, ((i + drift) * 10) / config.horizon))); p.line(-vDim / 2, y, vDim / 2, y); } for (let i = 0; i < 2 * config.horizon; i++) { let x = vDim * (-4 + (4 * i) / config.horizon); p.line(0, vDim * -0.1, x, vDim / 2); } p.noStroke(); p.fill("#DEADC0DE"); const gradient = p.drawingContext.createLinearGradient( 0, -vDim / 2, 0, vDim / 2 ); gradient.addColorStop(0.48, bgColor); gradient.addColorStop(0.6, bgColor + "00"); p.drawingContext.fillStyle = gradient; p.square(-vDim / 2, -vDim / 2, vDim); }, drawRays = (x, vFrame) => { p.push(); const window = 4, slice = x.slice(-window), sum = slice.reduce((a, x) => [a[0] + x[0], a[1] + x[1]]), drift = p.sin(((p.PI / 16) * vFrame) / config.driftSpeed); p.translate(sum[0] / window, sum[1] / window); p.stroke(config.bgFeatureColor); p.strokeWeight(8); for (let i = 0; i < 48; i++) { p.rotate(p.PI / 24); p.line(-vDim / 2, (drift * vDim) / 16, vDim, 0); } p.pop(); }, drawTriangle = (_, vFrame) => { p.background("#100539"); const angle = p.PI * (((config.triangleDirection * vFrame) % 2000) / 1000 - 0.5); p.noStroke(); p.fill(config.triangleColor + "24"); p.triangle( vDim * 0.8 * p.cos(angle), vDim * 0.8 * p.sin(angle), vDim * 0.8 * p.cos(angle + (2 / 3) * p.PI), vDim * 0.8 * p.sin(angle + (2 / 3) * p.PI), vDim * 0.8 * p.cos(angle + (4 / 3) * p.PI), vDim * 0.8 * p.sin(angle + (4 / 3) * p.PI) ); p.fill(config.triangleColor + "12"); p.triangle( vDim * 0.7 * p.cos(angle), vDim * 0.7 * p.sin(angle), vDim * 0.7 * p.cos(angle + (2 / 3) * p.PI), vDim * 0.7 * p.sin(angle + (2 / 3) * p.PI), vDim * 0.7 * p.cos(angle + (4 / 3) * p.PI), vDim * 0.7 * p.sin(angle + (4 / 3) * p.PI) ); p.noStroke(); p.fill("#DEADC0DE"); const gradient = p.drawingContext.createRadialGradient( 0, 0, 0, 0, 0, vDim * (5 / 6 + (1 / 6) * p.sin(6 * angle)) ); gradient.addColorStop(0, config.triangleGradient + "cc"); gradient.addColorStop(0.5, config.triangleGradient + "4d"); gradient.addColorStop(1, config.triangleGradient + "00"); p.drawingContext.fillStyle = gradient; p.square(-vDim / 2, -vDim / 2, vDim); }; let config; p.myCustomRedrawAccordingToNewPropsHandler = (props) => { if (!props.transactionHash || !props.square) return; if (!isSetup) { propCache = props; return; } // Seed the p5js random seed using 32 bits from the hash. const seedOffset = 2 + 8 * (props.tokenId % 8); p.randomSeed( +("0x" + props.transactionHash.substring(seedOffset, seedOffset + 8)) ); // Set up the style. let formNum = p.random(), faceNum = p.random(), form = formNum < 0.33 ? "ephemeral": formNum < 0.61 ? "mech": formNum < 0.65 ? "prophet": formNum < 0.97 ? "classic": formNum < 0.996 ? "wriggler": "saint", classic = form === "classic", mech = form === "mech", ephemeral = form === "ephemeral", wriggler = form === "wriggler", prophet = form === "prophet", saint = form === "saint", hologram = p.random() < 0.62, theme = themes[form + (hologram ? "_h" : "")], wormColor = theme[0], wormOutline = theme[2] || bgColor, bgFeatureColor = theme[1], wormSize = p.randomGaussian(60 + p.random(20), 10), wormOutlineSize = wormSize + 9, cycle = p.randomGaussian(60, 5) | 0, length = size * p.random(0.29, 0.52) - wormSize, flusterSpeed = p.random(3) | 0, fluster = p.random(size / 6), xInc = (p.random(8) | 0) + 1, yInc = (p.random(8) | 0) + 1, xOff = p.random(p.TWO_PI), yOff = p.random(p.TWO_PI), flusterInc = (p.random(8) | 0) + 6, flusterOff = p.random(5), dSteps = steps, speed = length * xInc + length * yInc + 2 * fluster * flusterInc, jSpeed = length * xInc + length * yInc + (fluster * flusterInc * flusterInc) / 3, dur = (steps * 2 * (1 + p.abs(p.randomGaussian()))) / speed, jit = (steps / jSpeed) * (1.2 + 0.6 * p.abs(p.randomGaussian())); if (wriggler && p.random() < 0.2) { jit *= -1; } if (p.random() < 0.12) { const inc = (1 + p.random(4)) | 0; dur *= inc; dSteps *= inc; } if (p.random() < 0.04) { jit *= p.random(0.1, 0.4); } let bgr = p.random(), bgStats = {}, [bg, drawBackground] = bgr < 0.4 ? ["Grid", drawGrid]: bgr < 0.55 ? ["Rays", drawRays]: bgr < 0.8 ? ["Light", drawTriangle]: ["Void", () => {}]; if (bg === "Grid") { bgStats = { gridColor: p.random() < 0.1 ? "#FFFFFF": wriggler || prophet || p.random() < 0.2 ? bgFeatureColor: "#ADFF2F", horizon: p.randomGaussian(p.random(10, 20), 2) | 0, inverseGrid: p.random() < 0.1, }; if (bgStats.inverseGrid) { bg = "Inverted Grid"; } } else if (bg === "Rays") { const factor = p.randomGaussian(p.random(0.1, 0.3), 0.02); jit *= factor; flusterSpeed *= factor; } else if (bg === "Light") { bgStats = { triangleDirection: p.random([-1, 1]), triangleColor: wriggler ? bgFeatureColor.substring(0, 7): p.random([wormPink, "#38BCE6"]), triangleGradient: p.random([ "#36bce6", "#9f7af2", ...(classic ? ["#f8cb1b"] : []), ...(wriggler ? ["#ff5d40"] : []), ]), }; } let [face, drawFace] = wriggler ? ["Scheming", drawFaceNormal]: faceNum < 0.08 ? ["Peaceful", drawFacePeaceful]: faceNum < 0.11 && !hologram ? ["Lil Fk", drawFuckFace]: ["Smile", drawFaceNormal]; config = { square: props.square, startTime: p.millis(), drawBackground: drawBackground, drawFace: drawFace, drawHalo: ephemeral ? drawSingleHalo: mech ? drawDoubleHalo: (prophet || saint) && p.random() < 0.4 ? drawSingleHalo: () => {}, form: form, theme: theme, classic: classic, wriggler: wriggler, mech: mech, hologram: hologram, bgFeatureColor: bgFeatureColor, ...bgStats, wormColor: wormColor, wormSize: wormSize, wormOutline: wormOutline, wormOutlineSize: wormOutlineSize, driftSpeed: p.randomGaussian(20, 3), cycle: cycle, length: length, jit: jit, dur: dur, dSteps: dSteps, xInc: xInc, xOff: xOff, xOffSize: p.random(1, 5) | 0, xOffSpeed: p.random(3) | 0, yInc: yInc, yOff: yOff, yOffSize: p.random(1, 5) | 0, yOffSpeed: p.random(3) | 0, fluster: fluster, flusterSpeed: flusterSpeed, flusterOff: flusterOff, flusterAmpOff: p.random(cycle), flusterInc: flusterInc, }; window.config = config; // Metadata. let subject = mech ? "Mech Worm MK1": ephemeral ? "The Ephemeral Worm": wriggler ? "The Wriggler": prophet ? "Worm Prophet": saint ? "Worm Saint": "The Earthly Worm"; if (hologram) { subject += wriggler || saint ? " Apparition": prophet ? " Revelation": " Hologram"; } console.log("metadata: ", { Subject: subject, Background: bg, Expression: face, transactionHash: props.transactionHash, }); p.windowResized(); }; p.draw = () => { if (!isSetup || !config) return; p.scale(p.width / vDim); p.translate(vDim / 2, vDim / 2); p.strokeJoin(p.ROUND); const vFrame = (vFrameRate * (p.millis() - config.startTime)) / 1000; let points = []; let { jit } = config; if (config.paused) { jit *= -25 * p.cos((p.TWO_PI * vFrame) / 200); } else { jit *= vFrame; } for (let i = 0; i < config.dSteps; i++) { let j = (jit * p.PI) / 60 + (i / config.dSteps) * config.dur; points.push([ config.length * p.sin(j * config.xInc + config.xOff) + config.fluster * p.sin(config.flusterOff + j * config.flusterInc), config.length * p.cos(j * config.yInc + config.yOff) + config.fluster * p.cos(config.flusterOff + j * config.flusterInc), ]); } p.background(bgColor); config.drawBackground([...points], vFrame); drawWorm([...points]); const ultimate = points.pop(), penultimate = points.pop(), faceVector = [ultimate[0] - penultimate[0], ultimate[1] - penultimate[1]]; config.drawFace( [ultimate[0] + faceVector[0], ultimate[1] + faceVector[1]], faceVector ); config.drawHalo(points.pop()); }; p.setup = () => { p5.disableFriendlyErrors = true; isSetup = true; if (propCache) { p.updateWithProps(propCache); } }; p.windowResized = () => { if (!isSetup || !config || !config.square) return; const SCALE = 0.8, WIDTH = (window.innerWidth / config.square) * SCALE, HEIGHT = (window.innerHeight / config.square) * SCALE, DIM = p.min(WIDTH, HEIGHT); p.resizeCanvas(DIM, DIM); }; ```
WayOfTheWorm# Way of the Worm _by [Mark Egli]_ An on-chain generative tribute to [The Worm]. Free to mint for select wallets at [markegli.com/worm](https://markegli.com/worm) while supplies last. **Code License:** [CC0 1.0] **Edition License:** [CC0 1.0] [Mark Egli]: https://markegli.com [The Worm]: https://theworm.wtf [CC0 1.0]: https://creativecommons.org/publicdomain/zero/1.0/ ## Traits * Subject * Background * Expression
Firmament```javascript "it rests among the stars / gathered up in the furthest firmament"; let canvas, // p5js canvas. state; // Keep all shared state in one place. //-[ setup functions ]--------------------------------------------------------// // Creates the p5js canvas and disables looping. // The rest of the setup is done when the Immutables properties are received. p.setup = _ => { canvas = p.createCanvas(1, 1); canvas.style("display", "block"); p.frameRate(10); p.noLoop(); }; // Responds to the properties provided by Immutables. p.myCustomRedrawAccordingToNewPropsHandler = properties => { if (!properties.transactionHash) return; // Check if existing state already matches the new properties. if ( state && state.transactionHash == properties.transactionHash && state.editionId == properties.editionId ) { state.square = properties.square; p.windowResized(); return; } // Pull bits from the hash to seed the RNG. p.randomSeed("0x" + properties.transactionHash.substr(2 + 8 * (properties.editionId % 8), 8)); // Intialize the state. state = { ...properties, animate: properties.square == 1, progress: 0, }; setStyling(); placeForegroundStars(); drawConstellation(); placeBackgroundStars(); addDust(); describe(); // Resize the window and turn looping on or off as needed. p.windowResized(); state.animate ? p.loop() : (p.noLoop(), p.redraw()); }; p.windowResized = _ => { if (!state) return; const minDim = p.min(p.windowWidth, p.windowHeight) * .8 / state.square | 0; if (minDim === p.width) return; p.resizeCanvas(minDim, minDim); state.resize = true; }; p.keyTyped = _ => { if (!state || state.square != 1) return; if (p.key == "A") { if (state.animate) { state.animate = false; p.noLoop(); p.redraw(); } else { state.animate = true; state.progress = 0; p.loop(); } } if (p.key == "B") { state.simple = !state.simple; } if (p.key == "C") { state.clear = !state.clear; } }; const setStyling = _ => { // Determine star size. state.chibi = p.random() < .06; state.dashed = p.random() < .06; state.sparse = p.random() < .18; const gap = state.chibi ? .7 : .5; state.drawLine = !state.dashed ? line => p.line(...line.slice(0, 4)) : line => { const dashes = p.max(1, line[6] / 3 | 0), scale = dashes - gap; for (let i = 0; i < dashes; i++) { p.line( p.lerp(line[0], line[2], i / scale), p.lerp(line[1], line[3], i / scale), p.lerp(line[0], line[2], (i + 1 - gap) / scale), p.lerp(line[1], line[3], (i + 1 - gap) / scale) ); } }; let color = p.random(); state.color = color < .12 ? [224, 42, 32, "Fire"] : color < .24 ? [96, 128, 247, "Water"] : color < .36 ? [148, 164, 196, "Air"] : [239, 239, 153, "Earth"]; }, placeForegroundStars = _ => { state.stars = []; const mainSizer = _ => state.chibi ? p.random(1.3, 1.8) : 1; // Place 2 to 4 stars somewhat uniformly. const preset = p.random(2, 5) | 0, angleOffset = p.random(p.TWO_PI / preset); for (let i = 0; i < preset; i++) { const dist = p.random(14, 20), angle = angleOffset + (i * p.TWO_PI + p.random(p.PI)) / preset, position = [dist * p.cos(angle), dist * p.sin(angle)]; let star = makeStar(position, .75, mainSizer()); state.stars.push(star); } // Place the remaining stars randomly. const starCount = 4 + p.abs(p.randomGaussian(1, 3)) | 0; for (let i = preset; i < starCount; i++) { const position = getOpening(state.stars); if (position) { let star = makeStar(position, .75, mainSizer()); state.stars.push(star); } } }, drawConstellation = _ => { state.lines = []; const connectableStars = [...state.stars], lineCount = state.stars.length * 1.3 + p.randomGaussian(.8, 2.3) | 0; while (state.lines.length < lineCount && connectableStars.length > 1) { const index1 = p.random(connectableStars.length) | 0, index2 = p.random(connectableStars.length) | 0; // Make sure different stars were selected. if (index1 === index2) { if (p.random() < .25) { // Random chance to remove this star from consideration. connectableStars.splice(index1, 1); } continue; } const star1 = connectableStars[index1], star2 = connectableStars[index2], xdiff = star2[0] - star1[0], ydiff = star2[1] - star1[1]; // Check if the stars are too close. if (xdiff * xdiff + ydiff * ydiff < 25) { if (p.random() < .33) { // Random chance to remove a star from consideration. const dist1 = star1[0] * star1[0] + star1[1] * star1[1], dist2 = star2[0] * star2[0] + star2[1] * star2[1], removeIndex = p.random(dist1 + dist2) < dist1 ? index1 : index2; connectableStars.splice(removeIndex, 1); } continue; } // Check if this line would cover a star. let covering = false; for (let star of state.stars) { if (star !== star1 && star !== star2 && isBetween(star1, star2, star, xdiff, ydiff)) { covering = star; break; } } if (covering) { let i = connectableStars.indexOf(covering); if (i >= 0 && p.random() < .2) { // Random chance to remove a star from consideration. const dist1 = star1[0] * star1[0] + star1[1] * star1[1], dist2 = star2[0] * star2[0] + star2[1] * star2[1], removeIndex = p.random(dist1 + dist2) < dist1 ? index1 : index2; connectableStars.splice(removeIndex, 1); } continue; } // Check if this line conflicts with another line. let intersections = 0, duplicate = false; for (let line of state.lines) { // Check if these lines have the same endpoints. if (line[4] === star1 && line[5] === star2 || line[4] === star2 && line[5] === star1) { duplicate = true; break; } // Check if these lines intersect. if (intersects(star1, star2, line)) { intersections++; } } if (duplicate) { if (p.random() < .4) { // Random chance to remove a star from consideration. const dist1 = star1[0] * star1[0] + star1[1] * star1[1], dist2 = star2[0] * star2[0] + star2[1] * star2[1], removeIndex = p.random(dist1 + dist2) < dist1 ? index1 : index2; connectableStars.splice(removeIndex, 1); } continue; } // The more intersections, the more likely this line will not be used. if (p.random() >= (1 / (intersections * lineCount / 2 + 1))) continue; if (intersections) state.intersections = true; state.lines.push(lineBetween(star1, star2, xdiff, ydiff)); } }, placeBackgroundStars = _ => { const totalStarCount = (state.sparse ? 20 : 35) + p.abs(p.randomGaussian(0, 15)) | 0; for (let i = state.stars.length; i < totalStarCount; i++) { const position = getOpening(state.stars, 22); if (position) { // Check that this new star isn't on a constellation line. let covered = false; for (let line of state.lines) { if (isOn(line, position, 2.5 + 1.5 * state.chibi)) { covered = true; break; } } if (!covered) { state.stars.push(makeStar(position, .15, p.random(.4, 1))); } } } }, addDust = _ => { state.dust = []; const dustCount = (state.sparse ? 80 : 350) + p.abs(p.randomGaussian(0, 100)) | 0; for (let i = 0; i < dustCount; i++) { const mote = makeStar( getOpening([], p.random(18, 24)), .05, p.random(.2, .6) * p.random(.25, 1) ); // Dust motes are dimmer than foreground and background stars. mote[3] *= p.random(.4, .7); state.dust.push(mote); } }, describe = _ => { let cycle = false; const groups = new Map(), getId = group => group.id || getId(group.parent), getRoot = group => group.parent && getRoot(group.parent) || group, lockId = group => group.id || (group.id = lockId(group.parent)); // Figure out how many groups of stars/lines there are. // In graph theory I guess these are "components"? // IDK it's been at least 15 years since I took a data structure class. for (let i = 0; i < state.lines.length; i++) { const line = state.lines[i], group1 = groups.get(line[4]), group2 = groups.get(line[5]); if (!group1) { if (!group2) { // Don't laugh at my disjoint-set data structure please. const newGroup = {id: i + 1}; groups.set(line[4], newGroup); groups.set(line[5], newGroup); } else { groups.set(line[4], group2); } } else { // group1 exists. if (!group2) { groups.set(line[5], group1); } else { if (getId(group1) !== getId(group2)) { // The worst possible way to union sets, probably. // There's definite O(n^2) energy, but n is small, so... const root = getRoot(group2); delete root.id; root.parent = group1; } else { // The group has run back into itself, so we have a cycle. cycle = true; } } } } // Collect the group IDs from all lines. const groupIds = new Map(); for (let group of groups.values()) { groupIds.set(lockId(group), true); } const lineTraits = []; if (state.intersections) lineTraits.push("Star-crossed"); if (groupIds.size > 1) lineTraits.push("Fragmented"); if (!cycle && state.lines.length > 1) lineTraits.push("Simple"); if (state.lines.length == 1) lineTraits.push("Minimalist"); if (!state.lines.length) lineTraits.push("Missing"); // Put an "and" between the last two traits. if (lineTraits.length > 1) { const last = lineTraits.pop() lineTraits.push(lineTraits.pop() + " and " + last); } const figureDescription = lineTraits.join(", ") + (lineTraits.length ? " " : "") + (state.dashed ? "Asterism" : "Constellation"), starDescription = (state.sparse ? "Sparse" : "Crowded") + " and " + (state.chibi ? "Luminous" : "Twinkling"), metadata = { "Figure": figureDescription, "Stars": starDescription, "Element": state.color[3], "transactionHash": state.transactionHash, }; console.log("metadata:", metadata); }; //-[ draw functions ]---------------------------------------------------------// p.draw = _ => { if (!state) return; // When not animating, fast forward to a frame where everything is drawn. const frame = state.animate ? state.progress : 1000; // Advance a frame unless this is a resize. if (!state.resize) { state.progress++; } else { state.resize = false; } // The canvas is 25 by 25, origin in the middle, rotated by time of day-ish. p.resetMatrix(); p.scale(p.width / 50); p.translate(25, 25); if (state.animate) { p.rotate(((new Date()).getTime() % 86164100) / 86164100 * p.TWO_PI); } p.noStroke(); p.blendMode(p.BLEND); p.background(16); p.blendMode(p.SCREEN); // Draw elements from back to front. Stardust first. if (!state.simple) for (let i = 0; i < state.dust.length; i++) { const dust = state.dust[i]; p.fill( p.constrain((dust[3] + (state.animate ? p.random(-10, 10) : 0)) * p.constrain((frame - i / 40) / p.random(10, 14), 0, 1), 0, 255) ); dust[dust.length - 1](); } // Then stars. for (let i = 0; i < state.stars.length; i++) { const star = state.stars[i]; p.fill( p.constrain((star[3] + (state.animate ? p.random(-20, 10) : 0)) * p.constrain((frame - i / 2) / 8, 0, 1), 0, 255) ); star[star.length - 1](); } // Then the asterism. p.strokeCap(p.ROUND); p.strokeWeight(state.chibi ? .66 : .4); if (!state.clear) for (let i = 0; i < state.lines.length; i++) { const line = state.lines[i]; let color = state.color.slice(0, 3); let flicker = !state.animate ? 1 : p.random(.93, 1) * p.random(.93, 1) * p.constrain( (frame - 6 - .6 * state.lines.length - i / 5 + p.random(2)) / 5, 0, 1 ); for (let i = 0; i < color.length; i++) { color[i] *= flicker; } p.stroke(...color); state.drawLine(line); } }; //-[ helper functions ]-------------------------------------------------------// const getOpening = (stars, distance = 18) => { let collision, tries = 0; do { collision = false; tries++; let point; // Random point in a circle with radius 20, using rejection sampling. do { point = [p.random(-distance, distance), p.random(-distance, distance)]; } while (point[0] * point[0] + point[1] * point[1] > distance * distance); for (let star of stars) { let xdiff = star[0] - point[0], ydiff = star[1] - point[1]; if (xdiff * xdiff + ydiff * ydiff < 2 * star[2] * star[2]) { collision = true; break; } } if (!collision) return point; } while (tries < 100); }, makeStar = (position, burstPercent, scale) => { const size = (scale || 1) * p.random(.6, .9), margin = state.chibi ? 1 : .6, brightness = p.random(240, 255); if (p.random() < burstPercent) { const blunt = p.random(.01, .07), fatness = p.random(.3, .6), sides = p.max(3, p.randomGaussian(5.5, .25) | 0), rotation = p.random(p.PI / sides), angle = p.TWO_PI / sides; // IDK. const maxInner = p.sin(p.HALF_PI - angle / 2), halfCutSquared = (1 - p.cos(p.PI * (1 - 1 / sides))) / 2, minInner = p.sqrt(1 - halfCutSquared), inner = minInner + (maxInner - minInner) * fatness; return [...position, size * 1.5 + margin, brightness, _ => { p.push(); p.translate(...position); p.rotate(rotation); p.scale(size); p.beginShape(); for (let s = 0; s < sides; s++) { p.vertex(p.cos(angle * (s - blunt)), p.sin(angle * (s - blunt))); p.vertex(p.cos(angle * (s + blunt)), p.sin(angle * (s + blunt))); p.vertex( inner * p.cos(angle * (s + .5)), inner * p.sin(angle * (s + .5)) ); } p.endShape(p.CLOSE); p.pop(); }]; } else { return [...position, size + margin, brightness, _ => { p.ellipse(...position, size, size); }]; } }, lineBetween = (star1, star2, xdiff, ydiff) => { const distance = p.sqrt(xdiff * xdiff + ydiff * ydiff), xOff = xdiff / distance, yOff = ydiff / distance, s1Dist = star1[2], s2Dist = star2[2]; return [ star1[0] + s1Dist * xOff, star1[1] + s1Dist * yOff, star2[0] - s2Dist * xOff, star2[1] - s2Dist * yOff, star1, star2, distance, ]; }, distanceSquared = (a, b) => { let x = a[0] - b[0], y = a[1] - b[1]; return x * x + y * y; }, isBetween = (star1, star2, covered, xdiff, ydiff, threshold = 2.5) => { let lengthSquared = xdiff * xdiff + ydiff * ydiff; let perpDistance = p.abs(ydiff * (covered[0] - star1[0]) - xdiff * (covered[1] - star1[1])) / p.sqrt(lengthSquared); return ( perpDistance < threshold && distanceSquared(star1, covered) < lengthSquared && distanceSquared(star2, covered) < lengthSquared ) }, // Same as `isBetween` except that it takes a line instead of endpoints. isOn = (line, point, threshold = 2.5) => isBetween( [line[0], line[1]], [line[2], line[3]], point, line[2] - line[0], line[3] - line[1], threshold ), clockwise = (star1, star2, star3) => (star3[1] - star1[1]) * (star2[0] - star1[0]) < (star2[1] - star1[1]) * (star3[0] - star1[0]), intersects = (star1, star2, line) => clockwise(star1, line, line.slice(2, 4)) != clockwise(star2, line, line.slice(2, 4)) && clockwise(star1, star2, line) != clockwise(star1, star2, line.slice(2, 4)); ```
Firmament# Firmament _by [Mark Egli]_ Hopes, fears and dreams, traced in celestial realms, wheeling overhead. <p><img src="https://ipfs.io/ipfs/QmfZ4S1siFWYHCEim8NzAQ1z6nXSaDy2UaibqNF4LF2GXt" alt="Firmament Rinkeby Mint #2" title="Firmament Rinkeby Mint #2" style="width:100%;max-width:1024px;border-radius:15px;" /></p> **Code License:** [CC BY-NC-SA 4.0] **Edition License:** [NIFTY] [Mark Egli]: https://markegli.com [CC BY-NC-SA 4.0]: https://creativecommons.org/licenses/by-nc-sa/4.0/ [NIFTY]: https://www.nftlicense.org/ ## Controls | Input | Action | |:----------|:-------| | Shift + A | Toggle animation | | Shift + B | Toggle faint stars | | Shift + C | Toggle constellation lines | ## Traits ### Figure * Asterism or Constellation * _Star-crossed_ * _Fragmented_ * _Simple_ * _Minimalist_ * _Missing_ ### Stars * Crowded or Sparse * Luminous or Twinkling ### Element * Air, Earth, Fire or Water
SyncretMark EgliExploring the beauty of harmony and dissonance: reflecting the ways in which we each embody conflicting motivations and stitch our own inner inconsistencies together into a beautiful and cohesive whole. By Mark Egli.0xf0c3656079751cea1ee96dd9cba7e20c6df1a87a4fef18c7099983673d659b66p5js
Syncret``` "the dream spins outward / slipping from our clenched hands"; let canvas, // p5js canvas. state; // Keep all shared state in one place. const body = document.body, halfRoot3 = p.sqrt(.75), //-[ Geometry functions ]---------------------------------------------------// // v - point or vector - [x, y] // l - line - [a, b, c] where ax + by = c // Returns the line passing through points v1 and v2. line = (v1, v2) => { let a = v2[1] - v1[1], b = v1[0] - v2[0]; return [a, b, a * v1[0] + b * v1[1]]; }, // Adds v2 to v1 and returns the result. // Modifies v1 in place for performance reasons. add = (v1, v2) => (v1[0] += v2[0], v1[1] += v2[1], v1), // Returns the difference between v1 and v2. subtract = (v1, v2) => [v1[0] - v2[0], v1[1] - v2[1]], // Creates a unit vector with angle a. unit = a => [p.cos(a), p.sin(a)], // Scales v by s. // Modifies the vector in place for performance reasons. scale = (v, s) => (v[0] *= s, v[1] *= s, v), // Returns the length of v. length = v => p.sqrt(v[0] * v[0] + v[1] * v[1]), // Returns the unit vector from v1 toward v2. direction = (v1, v2) => { let v3 = subtract(v2, v1); return scale(v3, 1 / length(v3)); }, // Returns the squared distance between v1 and v2. distSquared = (v1, v2) => { let [x, y] = subtract(v1, v2); return x * x + y * y; }, // Returns the line perpendicular to l that passes through point v. perpendicular = (v, l) => [-l[1], l[0], -l[1] * v[0] + l[0] * v[1]], // Returns the midpoint between v1 and v2. midpoint = (v1, v2) => [(v1[0] + v2[0]) / 2, (v1[1] + v2[1]) / 2], // Returns the line that bisects the segment between v1 and v2. bisect = (v1, v2) => perpendicular(midpoint(v1, v2), line(v1, v2)), // Returns the point where l1 and l2 intersect. intersect = (l1, l2) => { let det = l1[0] * l2[1] - l2[0] * l1[1]; // Ignore parallel case where determinate == 0. return [ (l2[1] * l1[2] - l1[1] * l2[2]) / det, (l1[0] * l2[2] - l2[0] * l1[2]) / det ]; }, // Returns the circumscribed circle for up to 3 given points. // The return value is in the format [x, y, r^2]. circumscribe = vs => { if (!vs.length) return [0, 0, 0]; if (vs.length == 1) return [vs[0][0], vs[0][1], 0]; if (vs.length == 2) { return [...midpoint(...vs), distSquared(vs[0], vs[1]) / 4]; } let center = intersect(bisect(vs[0], vs[1]), bisect(vs[1], vs[2])); return [...center, distSquared(center, vs[0])]; }, // Returns the minimal enclosing circle for the given points. // The return value is in the format [x, y, r^2]. enclose = (vs, pBounds) => { if (pBounds.length == 3 || !vs.length) return circumscribe(pBounds); let tailCircle = enclose(vs.slice(1), pBounds), v = vs[0], inside = distSquared(tailCircle, v) < tailCircle[2]; return inside || pBounds.some(vB => vB[0] == v[0] && vB[1] == v[1]) ? tailCircle : enclose(vs.slice(1), [v, ...pBounds]); }, // Returns a point on a Bezier curve in one dimension. bezier = (v0, v1, v2, v3, t) => { let t2 = t * t; let t3 = t2 * t; return v0 * (-t3 + 3 * t2 - 3 * t + 1) + v1 * (3 * t3 - 6 * t2 + 3 * t) + v2 * 3 * (t2 - t3) + v3 * t3; }, // Returns a point on a Bezier curve in three dimensions. bezier3d = (p0, p1, p2, p3, t) => [ bezier(p0[0], p1[0], p2[0], p3[0], t), bezier(p0[1], p1[1], p2[1], p3[1], t), bezier(p0[2], p1[2], p2[2], p3[2], t) ], //-[ Randomness functions ]-------------------------------------------------// gaussian = (sd = 1) => p.randomGaussian(0, sd), agaussian = (sd = 1) => p.abs(gaussian(sd)), randomBool = (t = .5) => p.random() < t, // Allows the provided value to be glitched by using the replace function. glitch = (name, value, replace, post) => { if (!randomBool(state.glitch)) return value; let replacement = replace(); if (replacement != value) { state.glitches[name] = true; // Record the glitch. post && post(); // Execute the post function if provided. } return replacement; }, //-[ Shape functions ]------------------------------------------------------// // Creates an array with length n. Lambda l is used to populate the values. array = (n, l) => Array.from(Array(n), (_, i) => l(i)), // Creates a rounded hexagon as a list of vertices. hexagon = (node, roundRadius = .25, roundPoints = 5) => { let [x, y, r, a] = node; // Given max radius, adjust to min radius. r *= 1 - roundRadius * (1 - halfRoot3); return array(6, i => { let vertexAngle = a + p.PI / 3 * i; return array(roundPoints, j => { let roundAngle = vertexAngle + p.PI / 3 * j / (roundPoints - 1); return [ x + r * (1 - roundRadius) * p.cos(vertexAngle + p.PI / 6) + r * roundRadius * p.cos(roundAngle), y + r * (1 - roundRadius) * p.sin(vertexAngle + p.PI / 6) + r * roundRadius * p.sin(roundAngle) ]; }) }).flat(); // Join the groups of points from each corner into one big array. }, // Creates a circle as a 30-sided polygon. circle = (node, points = 30) => array(points, i => [ node[0] + node[2] * p.cos(node[3] + p.PI / 6 + i / points * p.TWO_PI), node[1] + node[2] * p.sin(node[3] + p.PI / 6 + i / points * p.TWO_PI) ]), // Returns a copy of a node with the values slightly smeared. smear = (node, distance) => { let [x, y, r, a] = node; return [ x + gaussian(distance / 2), y + gaussian(distance / 2), r + agaussian(distance), a + gaussian(distance / r) ]; }, //-[ Tracing functions ]----------------------------------------------------// // Reset the mutable parts of the global state used for tracing. resetTraceState = _ => { state.penPosition = state.center; state.penVelocity = [0, 0]; state.wristPosition = [state.center[0], state.center[1] + state.handLength]; state.wristVelocity = [0, 0]; }, // Moves the pen one step toward the target point. moveToward = point => { // Figure out the wrist position and angle, compared to the ideals. let hand = subtract(state.penPosition, state.wristPosition), targetHand = subtract(point, state.wristPosition), diffRatio = (hand[0] * targetHand[0] + hand[1] * targetHand[1]) / (length(hand) * length(targetHand)), // Calculate the difference in angle between hand and targetHand. angleDiff = p.acos(diffRatio); // Every now and then, diffRatio likes to get too big and causes NaN values. // When this happens, fall back to clamp values. if (Number.isNaN(angleDiff)) angleDiff = (diffRatio < 0) * p.PI; // Account for wrist rotation in the direction the pen tries to move. let path = subtract(point, state.penPosition), pathAngle = p.atan2(path[1], path[0]), // Deflect movement angle to simulate rotation at wrist. moveAngle = pathAngle - Math.sign(path[0]) * angleDiff * state.penDeflection / 2; // Update the pen and wrist velocities. state.penVelocity = add( scale([...state.penVelocity], state.penSmoothing), scale(unit(moveAngle), state.penStep * (1 - state.penSmoothing)) ); state.wristVelocity = add( scale([...state.wristVelocity], state.wristSmoothing), scale( direction(state.wristPosition, [point[0], point[1] + state.handLength]), state.wristStep * (1 - state.wristSmoothing) ) ); // Update the pen and wrist positions. add(state.wristPosition, state.wristVelocity); state.penPosition = add([...state.penPosition], state.penVelocity); return state.penPosition; }, // Returns a point that is "nearly" the provided point. nearly = point => [ point[0] + gaussian(state.penStep / 2), point[1] + gaussian(state.penStep / 2) ], // Traces the given polygon and returns the result. tracePolygon = poly => { let points = [], vertexIndex = 1, vertex = nearly(poly[0]); // Move to the first point in the polygon. while (distSquared(state.penPosition, vertex) > state.thresholdSquared) { moveToward(vertex); } // Trace to every successive point. for (; vertexIndex < poly.length; vertexIndex++) { vertex = nearly(poly[vertexIndex]); let i = 0; while (distSquared(state.penPosition, vertex) > state.thresholdSquared) { points.push(moveToward(vertex)); } } // Wrap back around to the first point. vertex = nearly(poly[0]); while (distSquared(state.penPosition, vertex) > state.thresholdSquared) { points.push(moveToward(vertex)); } return points; }, //-[ Drawing helper functions ]---------------------------------------------// // Converts a polygon into a p5js shape instructions. drawPolygon = (instance, polygon) => { if (!polygon.length) return; instance.beginShape(); polygon.map(vertex => instance.vertex(...vertex.slice(0, 2))); instance.endShape(); }, // Converts Cartesian coordinates into canvas-relative polar coordinates. // Used for computing colors and other styling information. ra = v => { const [x, y] = subtract(v, state.center); return [ 4.84 * (x * x + y * y) / (state.canvasSize * state.canvasSize), p.atan2(y, x) - state.styleAngle ]; }, // Bypasses p5js to create a linear gradient fill. fillLinear = (instance, x1, y1, x2, y2, c1, c2) => { const gradient = instance.drawingContext.createLinearGradient(x1, y1, x2, y2); gradient.addColorStop(0, instance.color(c1).toString()); gradient.addColorStop(1, instance.color(c2).toString()); instance.fill("#DEADC0DE"); // Clear the cached fill value. instance.drawingContext.fillStyle = gradient; }, // Bypasses p5js to create a radial gradient fill. fillRadial = (instance, x, y, radius, inner, outter) => { const gradient = instance.drawingContext.createRadialGradient(x, y, 0, x, y, radius); gradient.addColorStop(0, instance.color(inner).toString()); gradient.addColorStop(1, instance.color(outter).toString()); instance.fill("#DEADC0DE"); // Clear the cached fill value. instance.drawingContext.fillStyle = gradient; }, //-[ Drawing function for "Nexus: Supreme" ]--------------------------------// supreme = texture => { // First draw the subtle background gradient. p.background("#C5C5C2"); texture.clear(); p.noStroke(); texture.noStroke(); p.colorMode(p.RGB); texture.colorMode(p.RGB); fillRadial( texture, ...state.center, state.canvasSize / 2, "#CACAC8", "#CECECC"); texture.circle(...state.center, state.canvasSize); // Roll style-specific random values. const uniform = randomBool(.4), // Precompute values for nodes with Gaussian distrobution. cachedGaussians = state.nodes.map(node => gaussian()), // Fetch a random value for a node. // Based on the value of `uniform` value, this may or may not be the // precomputed value from `cachedGaussians`. nodeGaussian = uniform ? i => cachedGaussians[i] : i => randomBool() ? gaussian() : cachedGaussians[i], // 1 in 4 chance of highlighting the largest node in red. popId = randomBool(.75) ? -1 : state.nodes.reduce( (a, node, i) => a[0] > node[2] ? a : [node[2], i], [0] )[1], // Precompute the node colors. sides = state.nodes.map((node, i) => { const [r, a] = ra(node), offset = 1 - r * p.sin(a); return [ (offset + .45 * nodeGaussian(i)) | 0, (offset + .45 * nodeGaussian(i)) | 0, (offset + .45 * nodeGaussian(i)) | 0, (offset + .45 * nodeGaussian(i)) | 0 ]; }), c = ["#000000", "#222222", "#F2F2F2", "#FFFFFF"], // Alpha value for background colors. // Backgrounds only have transparency when glitched. bg = glitch("Revealed", "", _ => "AA"); // Glitch the dark coloring. c[1] = glitch( "Enlightened", c[1], a => ( a = ["22"], a.splice(p.random(2) | 0, 0, (324 + p.random(35) | 0).toString(16).substr(1)), a.splice(p.random(3) | 0, 0, (290 + p.random(52) | 0).toString(16).substr(1)), "#" + a.join("") ) ); // Record style specific metadata. state.majorMetadata.Emptiness = popId < 0 ? "Dispersed" : "Concentrated"; state.majorMetadata.Polarity = uniform ? "Quiescent" : "Kinetic"; // Finally, draw the background circles. state.nodes.map((node, i) => { p.fill(c[sides[i][0] ? 2 : 0] + bg); p.circle(node[0], node[1], node[2] * 8); const texturePoly = tracePolygon(circle( smear([node[0], node[1], node[2] * 4, node[3]], state.smear * 1.5) )); texture.fill(c[sides[i][0] ? 3 : 1] + bg); drawPolygon(texture, texturePoly); if (uniform) return; texture.fill(sides[i][1] ? c[3] + "22" : c[1] + "44"); drawPolygon(texture, texturePoly); }); // Draw the foreground hexagons. state.hexagons.map((hexagon, i) => { if (popId == i) return; p.fill(c[sides[i][2] ? 0 : 2]); drawPolygon(p, hexagon); }); state.textugons.map((textugon, i) => { if (popId == i) return; texture.fill(c[sides[i][2] ? 1 : 3]); drawPolygon(texture, textugon); if (uniform) return; texture.fill(sides[i][3] ? c[1] + "88" : c[3] + "55"); drawPolygon(texture, textugon); }); // Put the cherry on top. if (popId >= 0) { p.fill("#F22222"); drawPolygon(p, state.hexagons[popId]); texture.fill("#F22"); drawPolygon(texture, state.textugons[popId]); } }, //-[ Drawing function for "Nexus: Celestial" ]------------------------------// celestial = texture => { // Set up colors. let skyColors = ["#182834", "#122632"], bgColors = [ [85, 215, 255], [85, 102, 215], [85, 215, 255], [102, 85, 215], ]; // Glitch the colors. glitch("Radiant", true, _ => { skyColors = skyColors.map(color => "#" + color.substr(5) + color.substr(1, 4)); bgColors = bgColors.map(color => [color[2], color[1] * 1.1, color[0]]); }); // Make the Bezier functions for computing each node's colors. const radialVector = scale(unit(state.styleAngle), state.canvasSize / 2), mainBez = t => bezier3d(bgColors[0], bgColors[1], [34, 17, 85], [34, 34, 34], t), textureBez = t => bezier3d(bgColors[2], bgColors[3], [34, 17, 85], [34, 34, 34], t), hexagonBez = t => bezier3d( [255, 238, 187], [238, 221, 204], [238, 238, 255], [205, 215, 245], 1.6 * t - .3 ); // Now that colors are set up, draw the background. p.clear(); p.noStroke(); p.colorMode(p.RGB); fillLinear( p, ...add(scale([...radialVector], .6), state.center), ...subtract(state.center, radialVector), "#111", skyColors[0] ); p.circle(...state.center, state.canvasSize); // The background on the texture overlay is made by adding two gradients. texture.clear(); texture.noStroke(); texture.colorMode(p.RGB); fillLinear( texture, ...add([...radialVector], state.center), ...subtract(state.center, radialVector), "#0E0E0E", skyColors[1] ); texture.circle(...state.center, state.canvasSize); texture.blendMode(p.ADD); fillRadial( texture, ...state.center, state.canvasSize / 2, "#000", "#060609"); texture.circle(...state.center, state.canvasSize); texture.blendMode(p.BLEND); // Add stars to the background based on node positions. const allStars = glitch("Galactic", false, _ => true), starThreshold = state.smear * 2, stars = state.nodes.map(node => { const [r, a] = ra(node), starCount = (allStars || r * p.cos(a)) * p.random(30), nodeStars = []; for (let i = 0; i++ < starCount;) { const offset = node[2] * (5 + 3.5 * gaussian()), angle = a + p.PI * gaussian(), diameter = p.random(p.random(node[2])) / 4; if (diameter > starThreshold || randomBool(.7)) { nodeStars.push([ node[0] + offset * p.sin(angle), node[1] + offset * p.cos(angle), p.max(diameter, starThreshold) ]); } } return nodeStars; }).flat(); p.fill("#F2F2F2"); texture.fill("#FFF"); stars.map(star => { p.circle(...star); texture.circle(...smear(star, state.smear / 2).slice(0, 3)); }); const hexagonColors = [], showHexagons = glitch("Revealed", true, _ => false); // Draw the background circles and precompute node colors. state.nodes.map(node => { const [r, a] = ra(node), distance = .5 + r / 2 * p.cos(a), alpha = 25 - 18 * distance + 15 * (!showHexagons) - 5 * allStars; hexagonColors.push(hexagonBez(distance)); if (alpha < 0) return; const textureNode = smear([node[0], node[1], node[2] * 4, node[3]], state.smear * 2), hexColor = mainBez(distance), texColor = textureBez(distance); fillRadial( p, ...node.slice(0, 2), node[2] * 4, p.color(...hexColor, alpha), p.color(...hexColor, alpha * .6) ); p.circle(node[0], node[1], node[2] * 8); fillRadial( texture, ...textureNode.slice(0, 2), textureNode[2], p.color(...texColor, alpha), p.color(...texColor, alpha * .6) ); drawPolygon(texture, tracePolygon(circle(textureNode))); }); // Finally, draw foreground hexagons. const alpha = 60 + 5640 / state.nodes.length; state.hexagons.map((hexagon, i) => { if (randomBool(.1) == showHexagons) return; const shine = alpha + 75 * !showHexagons * p.random(); p.fill(...hexagonColors[i], shine); drawPolygon(p, hexagon); texture.fill(...hexagonColors[i], shine + 3); drawPolygon(texture, state.textugons[i]); }); }, //-[ Drawing function for "Nexus: Energy" ]---------------------------------// // Energy stones and their associated color values. stones = [ ["Ruby", 0, 6, 4], ["Garnet", 15, 8, 2.5], ["Topaz", 27, 6, 5], ["Diamond", 42, .5, 5], ["Peridot", 72, 5, 4], ["Emerald", 105, 6, 3], ["Turquoise", 165, 6, 6], ["Aquamarine", 189, 6, 3], ["Sapphire", 234, 6, 4], ["Amethyst", 267, 6, 4], ["Pearl", 294, .75, 6.5], ["Tourmaline", 315, 5, 5] ], energy = texture => { // Start with a black background. p.background(0); p.noStroke(); p.colorMode(p.HSL); texture.background(0); texture.noStroke(); texture.colorMode(p.HSL); // Pick a stone and compute the base alpha value. let hue, sat, brightness, color = p.random(stones.length) | 0, alpha = .04 + p.random(.04) + 4 / state.nodes.length; state.majorMetadata.Stone = stones[color][0]; // Glitch the alpha value. alpha = glitch("Severe", alpha, _ => alpha > .2 ? alpha : .4 + p.random(.3)); const // Sets color values based on the selected stone. setValues = _ => { hue = stones[color][1] + gaussian(2); sat = stones[color][2] + .3; brightness = stones[color][3]; }, // Roll style-specific random values. purity = 7 + gaussian(2), multiply = glitch("Sinister", false, _ => true) ? state.nodes.map(node => randomBool(.7 + .25 * ra(node)[0])) : state.nodes.map(node => randomBool(.25 + .25 * ra(node)[0])), hueShifts = state.nodes.map(node => purity * gaussian()); setValues(); // Draw the backgound gradient. [p, texture].map(instance => { fillRadial( instance, ...state.center, state.canvasSize / 2, instance.color(hue, sat * 15, brightness + 5), instance.color(hue, 100, 2 + (instance == texture)) ); instance.circle(...state.center, state.canvasSize); }); // Draw the background circles. state.nodes.map((node, i) => { const blend = multiply[i] ? p.MULTIPLY : p.ADD, scaled = [node[0], node[1], node[2] * 15, node[3]]; p.blendMode(blend); texture.blendMode(blend); p.fill( multiply[i] ? p.color(0, alpha * 2.5) : p.color(hue + hueShifts[i], sat * 8, brightness * 2, alpha * 2) ); texture.fill(multiply[i] ? p.color(0, alpha * 2.4) : p.color(hue + hueShifts[i], sat * 7.5, brightness * 1.9, alpha * 2)); p.circle(...scaled.slice(0, 2), scaled[2] * 2); drawPolygon( texture, tracePolygon(circle(smear(scaled, state.smear * 3)))); }); // Draw the background hexagons. state.nodes.map((node, i) => { const blend = multiply[i] ? p.MULTIPLY : p.ADD, scaled = [node[0], node[1], node[2] * 5, node[3]]; p.blendMode(blend); texture.blendMode(blend); p.fill( multiply[i] ? p.color(0, alpha * 3.5) : p.color(hue + hueShifts[i], sat * 9, brightness * 4.5, alpha * 3) ); texture.fill( multiply[i] ? p.color(0, alpha * 3.4) : p.color(hue + hueShifts[i], sat * 9, brightness * 4.75, alpha * 3.3) ); drawPolygon(p, hexagon(scaled)); drawPolygon(texture, tracePolygon(hexagon(smear(scaled, state.smear)))); }); // Recolor the background; Bypass p5js since it has no "color" compositing. p.drawingContext.globalCompositeOperation = texture.drawingContext.globalCompositeOperation = "color"; const recolor = p.color(hue, sat * 14, brightness + 5, .35); p.fill(recolor); p.circle(...state.center, state.canvasSize); texture.fill(recolor); texture.circle(...state.center, state.canvasSize); // Reset the cached blend mode. p.blendMode(p.BLEND); texture.blendMode(p.BLEND); // Now that the background is complete, glitch the foreground color. glitch( "Eccentric", color, _ => color = ((color + p.round(gaussian(3))) % stones.length + stones.length) % stones.length, _ => { setValues(); state.majorMetadata.Stone += ", " + stones[color][0]; } ); // Draw the foreground hexagons. state.hexagons.map((hexagon, i) => { p.blendMode(multiply[i] ? p.MULTIPLY : p.ADD); p.fill( multiply[i] ? p.color(hue + hueShifts[i], 100, 5, alpha * 5) : p.color(hue + hueShifts[i], sat * 10, brightness * 10 + 10, alpha * 6) ); drawPolygon(p, hexagon); }); state.textugons.map((textugon, i) => { texture.blendMode(multiply[i] ? p.MULTIPLY : p.ADD); texture.fill( multiply[i] ? p.color(hue + hueShifts[i], 100, 5, alpha * 5) : p.color(hue + hueShifts[i], sat * 9, brightness * 10 + 10, alpha * 6.5) ); drawPolygon(texture, textugon); }); // Recolor the foreground; Bypass p5js since it has no "color" compositing. // Previous mode was MULTIPLY or ADD, and next mode will be BLEND, so no // need for cache busting. p.drawingContext.globalCompositeOperation = texture.drawingContext.globalCompositeOperation = "color"; state.hexagons.map((hexagon, i) => { p.fill(hue + hueShifts[i], sat * 9, brightness * 10, alpha * 3); drawPolygon(p, hexagon); }); state.textugons.map((textugon, i) => { texture.fill(hue + hueShifts[i], sat * 7.5, brightness * 10, alpha * 2.7); drawPolygon(texture, textugon); }); // Set the blend mode back to the default any subsequent draws. p.blendMode(p.BLEND); texture.blendMode(p.BLEND); }; //-[ p5js functions ]---------------------------------------------------------// // Creates the p5js canvas and disables looping. // The rest of the setup is done when the Immutables properties are received. p.setup = _ => { canvas = p.createCanvas(1,1); canvas.style("display", "block"); p.noLoop(); }, // Responds to the properties provided by Immutables. p.myCustomRedrawAccordingToNewPropsHandler = properties => { if (!properties.transactionHash) return; // Check if existing state already matches the new properties. if ( state && state.hash == properties.transactionHash && state.edition == properties.editionId ) { if (state.square != properties.square) { state.square = properties.square; p.windowResized(); } // The state doesn't need updated, so our work here is done. return; } // Pull 32 bits from the transaction hash to seed the RNG. // Pick which bits to pull based on the edition ID to cover the off chance // that someone uses a contract to mint multiple outputs at once. const setupSeed = properties.transactionHash.substr(2 + 8 * (properties.editionId % 8), 8); p.randomSeed(+("0x" + setupSeed)); // Roll values needed for node generation. const layers = (.2 + agaussian(1.2)) | 0, loschianNumber = randomBool() ? 4 : 7, layerRatio = p.sqrt(loschianNumber), layerRotate = loschianNumber == 4 ? 0 : p.atan2(halfRoot3, 2.5), alignment = randomBool() ? 1 : randomBool() ? 0 : 4, ratio = randomBool(.75) ? .01 : randomBool() ? .1 : .3, stutter = !alignment ? .05 : .015, nodeCount = 32 + p.max(8, p.ceil(224 + gaussian(160))), nodes = [], candidates = [], // A list of candidate parents for the next generated node. isDense = randomBool(.2), // Creates a new node. newNode = parent => { let // Random chance of jumping to a new layer. This affects all the rest // of the values. layer = randomBool(.1) && layers ? p.random(layers + 1) | 0 : parent[4], size = parent[2] * 2 ** gaussian(ratio) * layerRatio ** (layer - parent[4]), distance = layer != parent[4] ? 0 : (size + parent[2]) * (1.15 + gaussian(stutter)), angle = parent[3] + (p.PI * isDense) + p.round(gaussian(1.5) % 6) * p.PI / 3 + gaussian(alignment * p.PI / 96) + layerRotate * (parent[4] - layer); let node = [ parent[0] + distance * p.cos(angle), parent[1] + distance * p.sin(angle), size, angle, layer ]; nodes.push(node); return node; }; // Generate starter nodes in every layer. for (let i = layers + 1, layerSize = 20; i--;) { let node = [0, 0, layerSize, i * layerRotate, i, 0]; nodes.push(node); for (let j = 2 + randomBool(); j--;) candidates.push(node, node); // Help smaller layers out by giving them more nodes. for (let j = layers - i; j--;) newNode(node); layerSize /= layerRatio; } // Grow randomly from current nodes to reach the target node count. while (nodes.length < nodeCount) { let pIndex = p.random(candidates.length) | 0, parent = candidates[pIndex]; candidates.splice(pIndex, 1); let node = newNode(parent); while (randomBool() || !candidates.length) candidates.push(node); } const // Compute the canvas center and size based on node locations. frame = enclose(nodes, []), maxRadius = p.max( nodes.map(node => p.sqrt(distSquared(node, frame)) + node[2] * 1.5) ), // Roll the remaining values needed to prepare for drawing. size = p.random(4) | 0, handRatio = 2 ** (1.5 + size / 2), handLength = maxRadius / handRatio, style = p.random(), styleId = (style < .875) + (style < .7125); // Put everything where it can be referenced when drawing and redrawing. state = { square: properties.square, edition: +properties.editionId, hash: properties.transactionHash, center: [frame[0], frame[1]], canvasSize: maxRadius * 2.2, styleAngle: p.random(p.TWO_PI), smear: maxRadius / (handRatio * 256), leftHanded: randomBool(.1), handRatio: handRatio, handLength: handLength, penStep: handLength / 64, wristStep: handLength / 384, penSmoothing: .6, wristSmoothing: .9, penDeflection: .25, // How close the pen has to get to a vertex before having "arrived". thresholdSquared: handLength * handLength / 2048, nodes: nodes, hexagons: nodes.map(node => hexagon(node)), glitch: p.random() * p.random() * .4, glitches: {}, styleDraw: [supreme, celestial, energy][styleId], }; // Add the metadata to state that is known at this point. // It is split into major and minor metadata for ordering purposes. state.majorMetadata = { Nexus: ["Supreme", "Celestial", "Energy"][styleId], Arcana: "" // Reserving the key index. Value is filled during drawing. }; state.minorMetadata = { Adherence: ["Extreme", "Varied", "Uniform"][(ratio < .1) + (ratio < .2)], Growth: ["Rigid", "Loose", "", "", "Chaotic"][alignment], Reach: ["Local", "Regional", "Continental", "Global"][size], Focus: isDense ? "Inward" : "Outward", Centuries: "" + (1 + (nodeCount - 1) / 100 | 0), Degrees: "" + (layers + 1), }; // Only include the Loschian Number in metadata if there are actually layers. if (layers) state.minorMetadata["L\u00F6schian Number"] = "" + loschianNumber; // Add final metadata here that should be last in the list of traits. state.minorMetadata.Handed = state.leftHanded ? "Left" : "Right"; state.minorMetadata.transactionHash = properties.transactionHash; // Consider glitching the trace variables now that the state is set up. glitch("Revelous", true, _ => { state.smear *= 3; state.penSmoothing = .8; state.penDeflection = .875; // Since texture overlay hexagons are based on original nodes, this creates // a nice discrepency between the nodes and their overlay. state.nodes = nodes.map(node => smear(node, state.smear)); }); // Setup the trace state and trace the hexagons on the texture overlay. resetTraceState(); state.textugons = nodes.map(node => tracePolygon(hexagon(smear(node, state.smear)))); // Force a redraw and resize the window if needed. p.windowResized(); }; p.windowResized = _ => { if (!state) return; const minDim = p.min(p.windowWidth, p.windowHeight) * .8 / state.square | 0; p.resizeCanvas(minDim, minDim); }; p.keyTyped = _ => { if (!state || state.square != 1) return; if (p.key == 'S' && !state.save) { state.pixelDensity = p.pixelDensity(); state.save = true; canvas.style("display", "none"); p.pixelDensity(1); p.resizeCanvas(5000,5000); } else if (p.key == 'Z') { state.zoom = !state.zoom; p.redraw(); } } p.draw = _ => { if (!state) return; // Pull 32 bits from the transaction hash to reseed the RNG. // Reseed at the beginning of every draw to keep output consistent. // See the comment on setupSeed to explain the use of the edition ID. const drawSeed = state.hash.substr(2 + 8 * ((state.edition + 1) % 8), 8); p.randomSeed(+("0x" + drawSeed)); // Set up the transformation matrix for centering the nodes. const zoom = state.zoom ? p.sqrt(2) : 1, texture = p.createGraphics(p.width, p.width), scale = p.width / state.canvasSize * zoom, matrix = [ scale, 0, 0, scale, p.width / 2 - state.center[0] * scale, p.width / 2 - state.center[1] * scale ]; p.applyMatrix(...matrix); texture.applyMatrix(...matrix); // Reset the tracing state before drawing to keep output consistent. resetTraceState(); // Call the style-specific draw code. state.styleDraw(texture); // Compile glitches. state.majorMetadata.Arcana = Object.keys(state.glitches).sort().join(", ") || "Unknown"; const fullMetadata = { ...state.majorMetadata, ...state.minorMetadata, } // Report the metadata and log each trait to the console. if (!state.metadataReported) { console.log("metadata: ", fullMetadata); Object.keys(fullMetadata) .map(key => console.log(key, ":", fullMetadata[key])); // Mark as completed so that metadata output can be skipped for redraws. state.metadataReported = true; } // Create the "thread" texture mask. const threadSize = p.width / (state.handRatio * 54), threadLimit = p.width / threadSize + 1, mask = p.createGraphics(p.width, p.width); mask.fill(0); mask.noStroke(); // When zoom is enabled, zoom in just enough to have a full square image. if (state.zoom) { const offset = p.width / 2 * (1 - zoom); mask.applyMatrix(zoom, 0, 0, zoom, offset, offset); } // Draw a grid of threads over the whole mask. for (let x = 0; x < threadLimit; x++) { for (let y = 0; y < threadLimit; y++) { if (x % 2 == y % 2) continue; const w = threadSize * (1 + gaussian(.16)), h = threadSize * (1 + gaussian(.16)); if (w < 0 || h < 0) continue; mask.ellipse(threadSize * x, threadSize * y, w, h); } } // Apply texture mask. const textureImage = texture.get(); textureImage.mask(mask); // Apply border masks. const mainImage = p.get(); if (state.zoom) { // Square mask. const inset = threadSize * p.sqrt(2) / 8; mask.clear(); mask.resetMatrix(); mask.rect(inset, inset, mask.width - inset * 2); mainImage.mask(mask); } else { // Circle mask. mask.clear(); mask.circle(mask.width / 2, mask.width / 2, mask.width * .99); textureImage.mask(mask); mask.clear(); mask.circle(mask.width / 2, mask.width / 2, mask.width * .99 - threadSize / 4); mainImage.mask(mask); } // Copy the masked images to the main canvas. p.clear(); p.resetMatrix(); p.image(mainImage, 0, 0); p.image(textureImage, 0, 0); // Clean up the extra canvases. texture.remove(); mask.remove(); // If a save was initiated, write out the image and reset the canvas. if (state.save) { p.save("Syncret #" + state.edition + ".png"); p.pixelDensity(state.pixelDensity); p.windowResized(); canvas.style("display", "block"); state.save = false; } } ```
Syncret# Syncret _by [Mark Egli]_ Exploring the beauty of harmony and dissonance. <p style="text-align: center;"> <img src="https://ipfs.io/ipfs/bafybeieojujdkmfwevpef4ypymoahyiio7p75m3hiqqv3bk6cbymjtkqm4" alt="Syncret Rinkeby Mint #1" title="Syncret Rinkeby Mint #1" style="width:50%;max-width:512px;" /> </p> While each viewer brings their own understanding to the work, I created Syncret to reflect the ways in which we each embody conflicting motivations and stitch our own inner inconsistencies into a beautiful and cohesive whole. **Code License:** [CC BY-NC-SA 4.0] **Edition License:** [NIFTY] [Mark Egli]: https://markegli.com [CC BY-NC-SA 4.0]: https://creativecommons.org/licenses/by-nc-sa/4.0/ [NIFTY]: https://www.nftlicense.org/ ## Controls | Input | Action | |:----------|:------------------------------------| | Shift + S | Save a 5000&times;5000 pixel image. | | Shift + Z | Toggle square cropping. | ## Traits Traits in italics are only present based on the value of other traits. * Nexus * Arcana * _Emptiness_ * _Polarity_ * _Stone_ * Adherence * Growth * Reach * Focus * Centuries * Degrees * _L&ouml;schian Number_ * Handed