0x983f…313d

All memos sent from and to 0x983f…313d.

I find myself in a world of chaos and turbulence, of confusion and disorder. At the still point, I—or perhaps we—find ourselves, yet we are never truly still. This work is both a meditation and a portrait of modern awareness: an algorithmic reflection on the search for calm within uncertainty. The title echoes a line from T.S. Eliot’s Four Quartets, which contemplates presence within time and the dance of the living moment. ### Inspiration and Creation *At the Still Point of the Turning World...* (AKA “Still Point”) is the fifth project in the Zenerative series, a collection of Zen-inspired generative art projects. “Still Point” explores holding presence in a dynamic world and maintaining calm within a sort of structured chaos. Within each output is an identity surrounded by overlapping and interacting rings that create interference. The motion of the outer world affects the inner, and a visual motion is hinted at while a psychological motion is created. These effects are enhanced when a piece is portrayed digitally, as pixels on the screens will dance with the narrow and high contrast lines. The artworks are size agnostic vector graphics, and they are both plottable* and feature togglable animation**. I choose to only use black and white for this piece, because I wanted to emphasize this strange vibrancy of chaos and confusion that I find myself in. Color made it too…pedestrian, or pretty. Only stark black and white seems appropriate for this portrait; their interplay and overlapping patterns adding to the visual aesthetic. While I made this piece to reflect my experience and visualize my state of being, I know that others resonate with the theme and symbolism. It’s not a traditional self portrait, and it may not be a portrait of only me. Others may see themselves in it as well, and while we may feel like we’re alone in that center, we’re together in the experience. *”Still Point” is plottable, but paths must be cropped to the SVG viewport. **”Still Point” features animation that can be activated by using a URL flag, but due to technical constraints, outputs do not animate smoothly on all devices. Modern smartphones, as well as Mac computers with Apple Silicon chips running Chrome should give good results.
I find myself in a world of chaos and turbulence, of confusion and disorder. At the still point, I—or perhaps we—find ourselves, yet we are never truly still. This work is both a meditation and a portrait of modern awareness: an algorithmic reflection on the search for calm within uncertainty. The title echoes a line from T.S. Eliot’s Four Quartets, which contemplates presence within time and the dance of the living moment. ## Inspiration and Creation *At the Still Point of the Turning World...* (AKA “Still Point”) is the fifth project in the Zenerative series, a collection of Zen-inspired generative art projects. “Still Point” explores holding presence in a dynamic world and maintaining calm within a sort of structured chaos. Within each output is an identity surrounded by overlapping and interacting rings that create interference. The motion of the outer world affects the inner, and a visual motion is hinted at while a psychological motion is created. These effects are enhanced when a piece is portrayed digitally, as pixels on the screens will dance with the narrow and high contrast lines. The artworks are size agnostic vector graphics, and they are both plottable* and feature togglable animation**. I choose to only use black and white for this piece, because I wanted to emphasize this strange vibrancy of chaos and confusion that I find myself in. Color made it too…pedestrian, or pretty. Only stark black and white seems appropriate for this portrait; their interplay and overlapping patterns adding to the visual aesthetic. While I made this piece to reflect my experience and visualize my state of being, I know that others resonate with the theme and symbolism. It’s not a traditional self portrait, and it may not be a portrait of only me. Others may see themselves in it as well, and while we may feel like we’re alone in that center, we’re together in the experience. *”Still Point” is plottable, but paths must be cropped to the SVG viewport. **”Still Point” features animation that can be activated by using a URL flag, but due to technical constraints, outputs do not animate smoothly on all devices. Modern smartphones, as well as Mac computers with Apple Silicon chips running Chrome should give good results.
I find myself in a world of chaos and turbulence, of confusion and disorder. At the still point, I—or perhaps we—find ourselves, yet we are never truly still. This work is both a meditation and a portrait of modern awareness: an algorithmic reflection on the search for calm within uncertainty. The title echoes a line from T.S. Eliot’s Four Quartets, which contemplates presence within time and the dance of the living moment. ## Inspiration and Creation *At the Still Point of the Turning World...* (AKA “Still Point”) is the fifth project in the Zenerative series, a collection of Zen-inspired generative art projects. “Still Point” explores holding presence in a dynamic world and maintaining calm within a sort of structured chaos. Within each output is an identity surrounded by overlapping and interacting rings that create interference. The motion of the outer world affects the inner, and a visual motion is hinted at while a psychological motion is created. These effects are enhanced when a piece is portrayed digitally, as pixels on the screens will dance with the narrow and high contrast lines. The artworks are size agnostic vector graphics, and they are both plottable* and feature togglable animation**. I choose to only use black and white for this piece, because I wanted to emphasize this strange vibrancy of chaos and confusion that I find myself in. Color made it too…pedestrian, or pretty. Only stark black and white seems appropriate for this portrait; their interplay and overlapping patterns adding to the visual aesthetic. While I made this piece to reflect my experience and visualize my state of being, I know that others resonate with the theme and symbolism. It’s not a traditional self portrait, and it may not be a portrait of only me. Others may see themselves in it as well, and while we may feel like we’re alone in that center, we’re together in the experience. *”Still Point” is plottable, but paths must be cropped to the SVG viewport. **”Still Point” features animation that can be activated by using a URL flag, but due to technical constraints, outputs do not animate smoothly on all devices. Modern smartphones, as well as Mac computers with Apple Silicon chips running Chrome should give good results.
let title = "A he Still Point of`Turning World: Algorithmic Self ,rtrait, 2025"; @X ALL RIGHTS RESERVED@Dconsole.log(`${`Y TOKEN HASH: b``INTERACTIVITY: Press "H" !1"URL FLAGS: animat 3lineColor1 (HTML-c@() => R(0, 628) / 100; chaosFactor @&) > 50 ? true : false!m`"ANIMATION ENABL' [`<g id="ArcG!$'hn rveAspectRa'\ns="http://www.w3.org/2000/ ~> ATTRIBUTESL3#s. . .toUpperCas7"before!D;g m@6 n*
I find myself in a world of chaos and turbulence, of confusion and disorder. At the still point, I—or perhaps we—find ourselves, yet we are never truly still. This work is both a meditation and a portrait of modern awareness: an algorithmic reflection on the search for calm within uncertainty. The title echoes a line from T.S. Eliot’s Four Quartets, which contemplates presence within time and the dance of the living moment.
AnnoMetta (‘Anno’ meaning year in Latin and ‘Metta’ meaning loving-kindness in Pali), is a 1/1 artwork that symbolizes community connections through a dynamic canvas. With elements shifting hourly, daily, monthly, and annually, the generative art is fully customizable via URL parameters. When viewing the live code in a token or on https://annometta.com, press 'escape' to open a customization menu. I approached this project as a way to visualize the connections between individuals. These could be simple relationships, a network or community, or an abstract representation of compassion (in the spirit of Metta). I wanted to create a single piece of art that will always change, but where a specific output can always be recreated. I feel like the longest cycle many people think about is a single year, and the starting hue of any render of this work cycles over the course of a year, just like seasons. The piece will look about the same every year on your birthday, for example, but it would never be exactly the same. Technically, new art is created every hour, but even within a single hour, multiple outputs can be made – change the window size or the screen resolution and the art changes. Everything is deterministic, meaning that there is always a reproducible pattern to the dynamic changes. No output is ever truly lost and can be recreated with the right settings. AnnoMetta can be enjoyed for free around the world and at any time, and I hope that when viewing it, one thinks of the connections and maybe even the compassion shared between us.
console.log("AnnoMetta v5. © Matto 2024."),open the popup menu (code used with permission from ixnayokay)@^docu 6 t.body.sty backgroundColor="#0`";let p=[],hue,u,width=window.in,year=new Date().getFullY ,ctx,canvas,seed,a=166452 J E13904223,m=4294967296,R=(e=1)=>". h.floor(ra!m()*e),fastDrawM@ltch(/^[0-9a-f]{6}$/i)?addBKG(`#$s;D++)p.push({x:R(1.5*bmene/9 dblclick",Family="Verdana, sa! to. (JavaScrip7 =&5 splay: flex; j!7
console.log("AnnoMetta algorithm © Matto 2024"),open the popup menu (code used w X permission from Ixnayokay) ]docu 5 t.body.sty backgroundColor="#0`";let p=[],hue,u,width=window.in,year=new Date().getFullY ,ctx,canvas,seed,a=166452 J E13904223,m=4294967296,R=(e=1)=>", h.floor(ra!m()*e),fastDrawM@ltch(/^[0-9a-f]{6}$/i)?addBKG(`#$s;D++)p.push({x:R(1.5*bmene/9 dblclick",Family="Verdana, sa!to. (JavaScript, splay: flex; j : space-between; D you, Ixnayoka6A{ g addEventLis$
AnnoMetta (‘Anno’ meaning year in Latin and ‘Metta’ meaning loving-kindness in Pali), is a 1/1 artwork that symbolizes community connections through a dynamic canvas. With elements shifting hourly, daily, monthly, and annually, the generative art is fully customizable via URL parameters. When viewing the live code in a token or on https://annometta.com, press 'escape' to open a customization menu. I approached this project as a way to visualize the connections between individuals. These could be simple relationships, a network or community, or an abstract representation of compassion (in the spirit of Metta). I wanted to create a single piece of art that will always change, but where a specific output can always be recreated. I feel like the longest cycle many people think about is a single year, and the starting hue of any render of this work cycles over the course of a year, just like seasons. The piece will look about the same every year on your birthday, for example, but it would never be exactly the same. Technically, new art is created every hour, but even within a single hour, multiple outputs can be made – change the window size or the screen resolution and the art changes. Everything is deterministic, meaning that there is always a reproducible pattern to the dynamic changes. No output is ever truly lost and can be recreated with the right settings. Shapes change with the calendar month, and the renders in frequent flux, but it’s one piece of art. AnnoMetta can be enjoyed for free around the world and at any time, and I hope that when viewing it, one thinks of the connections and maybe even the compassion shared between us.
ZenerativeMatto - Shinkai (AKA MonkMatto) creates Zen-inspired generative art and releases it on premiere platforms like Art Blocks.https://x.com/MonkMattohttps://matto.xyz/https://www.artblocks.io/marketplace/artists/matto
const project = "Time Between the Lines is Thread Through the Mind"; console.log(`${project} v5.0 © Matto 2024`); console.log(`TOKEN ENTROPY: ${tokenData.hash}`); let skeuomorphic = tokenData.plot == true ? false : true; const tokenId = tokenData.tokenId; const attributes = {}; const urlParams = new URLSearchParams(window.location.search); const customSig = urlParams.get("signature"); let customOp = urlParams.get("optimization"); if (customOp) { customOp = customOp.toUpperCase(); } const altMode = urlParams.get("alternate"); const skeuMode = urlParams.get("skeuomorphic"); let modeDesc = ["SKEUOMORPHIC", "SIMPLE"]; let spreadDesc = ["MINIMUM", "SOME", "MORE", "MAXIMUM"]; let penSuggestion = []; if (skeuMode == "true") { skeuomorphic = true; console.log(`CUSTOM MODE: ${modeDesc[0]}`); } else if (skeuMode == "false") { skeuomorphic = false; console.log(`CUSTOM MODE: ${modeDesc[1]}`); } else { console.log("DEFAULT MODE: " + (skeuomorphic ? modeDesc[0] : modeDesc[1])); } console.log(""); let seed = makeSeed(tokenData.hash); let nonce = 0; let showSignature = customSig == "false" ? false : true; let circlesCount = 0; const numGroups = selectByProbs([5,6,4,7,3], fractionalRand()); const m = (rand(0, 200) / 100) - 1; let svg, sig; let groupedLines = ""; let texturedLines = ""; let gappedPoints = []; let palettes = []; let points = []; const allOptDesc = ["ANTI", "LAPS", "STANDARD", "MAXIMUM"]; const defaultOpt = selectByProbs([3, 2, 1, 0], fractionalRand()); let optimization = allOptDesc.indexOf(customOp); if (optimization === -1) { optimization = defaultOpt; } let optDesc = allOptDesc[optimization]; let dark = selectByProbs([0, 1], fractionalRand()); if (altMode == "true") { console.log("Alternate Mode selected, switching paper and pens."); dark = dark == 1 ? 0 : 1; } let perpGap = selectByProbs([true, false], fractionalRand()); const bkg = dark == 1 ? 22 : 250; const penCollection = dark == 0 ? "Stabilo" : "Gelly Roll"; const paletteArray = [ [ { hex: "#83553b", name: "Brown", id: 45 }, { hex: "#d74143", name: "Dark Red", id: 50 }, { hex: "#f75c45", name: "Carmine", id: 48 }, { hex: "#ff8b15", name: "Pale Vermillion", id: 30 }, { hex: "#ffb814", name: "Orange", id: 54 }, { hex: "#ffd913", name: "Yellow", id: 44 }, { hex: "#fff248", name: "Lemon Yellow", id: 24 }, { hex: "#b7c800", name: "Light Green", id: 33 }, { hex: "#4c8b06", name: "Leaf Green", id: 43 }, { hex: "#1c6a27", name: "Green", id: 36 }, { hex: "#3d47b3", name: "Ultramarine", id: 32 }, { hex: "#3074e5", name: "Dark Blue", id: 41 }, { hex: "#3093f8", name: "Light Blue", id: 31 }, { hex: "#58b6ff", name: "Azure", id: 57 }, { hex: "#c9c4c4", name: "Light Cold Gray", id: 94 }, { hex: "#a59da5", name: "Medium Cold Gray", id: 95 }, { hex: "#665e61", name: "Dark Gray", id: 96 }, { hex: "#2d2828", name: "Black", id: 46 }, ], [ { hex: "#E9E9E9", name: "Platinum", id: 50 }, { hex: "#85CFF5", name: "Pale Azure", id: 425 }, { hex: "#7696DC", name: "Vista Blue", id: "437p" }, { hex: "#2A6CD0", name: "Celtic Blue", id: 438 }, { hex: "#109EE0", name: "Celestial Blue", id: 436 }, { hex: "#97DCD6", name: "Tiffany Blue", id: "426p" }, { hex: "#28DBD5", name: "Turquoise", id: 429 }, { hex: "#85D18A", name: "Emerald", id: 428 }, { hex: "#E1EC7A", name: "Mindaro", id: "403p" }, { hex: "#E7B968", name: "Earth Yellow", id: "405p" }, { hex: "#D0866D", name: "Burnt Sienna", id: 412 }, { hex: "#DB5D6E", name: "Indian Red", id: 419 }, { hex: "#CE6ABC", name: "Sky Magenta", id: 421 }, { hex: "#9774D3", name: "Amethyst", id: 424 }, { hex: "#AB98E0", name: "Wisteria", id: 423 }, { hex: "#D2AFDB", name: "Lilac", id: "420p" }, { hex: "#838A93", name: "Slate Gray", id: 444 }, { hex: "#513E3A", name: "Van Dyke", id: 417 }, ], [ { hex: "#EDECE4", name: "Metallic Silver", id: 553 }, { hex: "#FFD062", name: "Metallic Gold", id: 551 }, { hex: "#F8BEB6", name: "Metallic Red", id: 519 }, ] ]; if (dark == 1 && numGroups == 3) { dark = 2; } const startingColor = rand(0, paletteArray[dark].length - 1); const paletteSpread = dark < 2 ? selectByProbs([1,2,3,4], fractionalRand()) : 1; console.log("EXTENDED ATTRIBUTES:"); for (let i = 0; i < numGroups + 1; i++) { palettes[i] = paletteArray[dark][(startingColor + (i * paletteSpread)) % paletteArray[dark].length]; let name = `Group ${i + 1} Color`; if (i == numGroups) { name = "Signature Color"; } penSuggestion[i] = `Suggested Pen Color: ${penCollection} #${palettes[i].id}`; console.log(`${name}: ${palettes[i].name} (${palettes[i].hex}) - ${penSuggestion[i]}`); } addAttribute("COLOR GROUPS", `${numGroups}`); addAttribute("COLOR SPREAD", spreadDesc[paletteSpread - 1]); addAttribute("PAPER", dark > 0 ? "DARK" : "LIGHT"); addAttribute("COMPOSITION", perpGap ? "BISECTED" : "WHOLE"); addAttribute("SLOPE", -1 * m.toFixed(2)); addAttribute("OPTIMIZATION", optDesc); const svgStart = `<?xml version="1.0" encoding="utf-8"?><svg viewBox="0 0 1000 1000" style="background-color:rgb(${bkg},${bkg},${bkg})" xmlns="http://www.w3.org/2000/svg">`; const suggestedThickness = "Suggested line thickness: plot side length (mm) divided by 400."; draw(); function setup() { noCanvas(); noLoop(); } function draw() { const ySpacing = 2.6 - (1 - Math.abs(m)) / 3; let lineNum = 0; for (let b = -2000; b < 2000; b += ySpacing ) { const x0 = Math.max(0, -b / m); const x1 = Math.min(1000, (1000 - b) / m); const y0 = b + x0 * m; const y1 = b + x1 * m; const clipped = getClippedLine(x0, y0, x1, y1); if (clipped) { lineWithGaps(clipped.x0, clipped.y0, clipped.x1, clipped.y1, lineNum); } lineNum++; } // 0: anti, 1: laps (handled elsewhere), 2: normal, 3: maximum if (optimization == 0) { points = sortPointPairs(points, true, false); } else if (optimization == 2) { points = sortPointPairs(points, false, true); } else if (optimization == 3) { points = sortPointPairs(points, true, true); } const linesPerGroup = Math.ceil(points.length / numGroups); let pointIndex = 0; for (let i = 0; i < numGroups; i++) { groupedLines += G(i + 1, palettes[i].hex, 2.5, palettes[i].name, penSuggestion[i]); texturedLines += G(i + 1, palettes[i].hex, 1.5, palettes[i].name, penSuggestion[i]); for (let j = 0; j < linesPerGroup && pointIndex < points.length; j++) { if (isNaN(points[pointIndex].y2)) { break; } groupedLines += P([points[pointIndex].x1, points[pointIndex].y1, points[pointIndex].x2, points[pointIndex].y2]); if (skeuomorphic) { texturedLines += line2dots(points[pointIndex].x1, points[pointIndex].y1, points[pointIndex].x2, points[pointIndex].y2); } pointIndex++; } groupedLines += "</g>"; texturedLines +="</g>"; } console.log(`Background Line Segments: ${pointIndex}`); if (skeuomorphic) { console.log(`Composing Circles: ${circlesCount}`); } console.log(suggestedThickness); console.log("License: CC BY-NC 4.0"); sig = signature(); svg = skeuomorphic ? svgStart + texturedLines : svgStart + groupedLines; updateSVG(); } function signature() { let sigLinePoints = [ [924,956,920,956], [920,956,920,860], [920,860,940,872], [940,872,960,860], [960,860,960,956], [960,956,956,956], [928,902,940,872], [940,872,952,902], [934,888,946,888], [920,902,960,902], [932,902,932,926], [948,902,948,926] ]; let str; if (skeuomorphic) { str = G("signature", palettes[numGroups].hex, 1.5, palettes[numGroups].name, penSuggestion[numGroups]); for (let i=0; i<sigLinePoints.length; i++) { str += line2dots(sigLinePoints[i][0], sigLinePoints[i][1], sigLinePoints[i][2], sigLinePoints[i][3]); } str += circle2dots(940, 940, 15); } else { str = G("signature", palettes[numGroups].hex, 2.5, palettes[numGroups].name, penSuggestion[numGroups]); str += `<polyline points="924,956 920,956 920,860 940,872 960,860 960,956 956,956" stroke-linecap="round" stroke-linejoin="round"/>`; str += `<polyline points="928,902 940,872 952,902" stroke-linejoin="bevel" />`; str = str + P(sigLinePoints[8]) + P(sigLinePoints[9]) + P(sigLinePoints[10]) + P(sigLinePoints[11]); str += C([940, 940, 15]); } str += "</g>"; return str; } function G(groupNum, hex, strokeW, paletteName, penSuggestion) { return `<g id="group-${groupNum}-${hex}-${paletteName}" style="stroke:${hex}; stroke-width: ${strokeW}px; stroke-opacity:1; fill-opacity:0"><desc>${paletteName} - ${penSuggestion}. ${suggestedThickness}</desc>`; } function P(vars) { return `<path d="M ${vars[0]} ${vars[1]} L ${vars[2]} ${vars[3]}" stroke-linecap="round" />` } function C(vars) { return `<circle cx="${vars[0]}" cy="${vars[1]}" r="${vars[2]}" />`; } function crossesBisector(x1, y1, x2, y2) { const xIntersection = ((500 + 500 / m + m * x1 - y1) / (m + 1 / m)); const yIntersection = 500 - (1 / m) * (xIntersection - 500); return xIntersection >= Math.min(x1, x2) && xIntersection <= Math.max(x1, x2) && yIntersection >= Math.min(y1, y2) && yIntersection <= Math.max(y1, y2); } function addAttribute(trait_type, value) { console.log(`${trait_type}: ${value}`); attributes[trait_type] = value; } function makeSeed(input) { let s = 1; for (let i = 0; i < (input.length - 2) / 2; i++) { let v = parseInt(input.slice(2 + i * 2, 4 + i * 2), 16); s = ((s * v) % 999999999999) + 1 } return s; } function fractionalRand() { return rand(1,100)/100; } function rand(min, max) { const range = max - min + 1; const v = (seed % range) + min; newSeed(); return Math.floor(v); } function newSeed() { nonce++; let bigSeed = BigInt(seed); const bigNonce = BigInt(nonce); const bigMultiplier = BigInt(35932678341237); const bigModulus = BigInt(999999999999); bigSeed = (bigSeed * bigMultiplier + bigNonce) % bigModulus + BigInt(1); if (bigSeed <= Number.MAX_SAFE_INTEGER) { seed = Number(bigSeed); } else { console.log("Seed exceeded safe range, reseeding..."); seed %= 999999999999; } } document.addEventListener("keydown", function (event) { if (!tokenData.plot) { const k = event.key.toUpperCase(); if (k === "H") { console.log(`Toggling signature, please be patient. ${circlesCount} circles need to be redrawn...`); showSignature = !showSignature; document.body.innerHTML = ''; updateSVG(); } else if (k === "S") { let mS = skeuomorphic ? modeDesc[0] : modeDesc[1]; console.log(`Saving ${mS}-mode SVG with ${optDesc} Optimization...`) saveStrings([assembleComp() + "</svg>"], `TBTLITTTM-${tokenId}-${mS}-${optDesc}`, "svg"); } } }); function saveStrings(data, filename, extension) { const blob = new Blob([data], {type: 'text/plain'}); let anchor = document.createElement('a'); anchor.download = `${filename}.${extension}`; anchor.href = window.URL.createObjectURL(blob); anchor.style.display = 'none'; document.body.appendChild(anchor); anchor.click(); document.body.removeChild(anchor); } function assembleComp() { let comp = svg; if (showSignature) { comp += sig; } return comp; } function updateSVG() { document.body.insertAdjacentHTML('beforeend', assembleComp() + "</svg>"); } function selectByProbs(list, randomValue) { const totalWeight = list.reduce(function(acc, item, index) { return acc + (1 / (index + 2)); }, 0); const threshold = totalWeight * randomValue; let currentSum = 0; for (let i = 0; i < list.length; i++) { currentSum += 1 / (i + 2); if (currentSum >= threshold) { return list[i]; } } return list[list.length - 1]; } function getY(x0, y0, x1, y1, x) { const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const y = m * x + b; return y; } function lineWithGaps(x0, y0, x1, y1, lineNum) { let xT, yT, xE, yE; let tempPoints = []; if(x0 > x1) { xT = x0; yT = y0; x0 = x1; y0 = y1; x1 = xT; y1 = yT; } let xS = x0; let yS = y0; let lfg = true; while (lfg) { let dS; let dnm = [[60,4,48,12],[80,12,40,8]]; dS = 0; let lSeg = rand((1000 / dnm[dS][0]), (1000 / dnm[dS][1])); let gSeg = rand((1000 / dnm[dS][2]), (1000 / dnm[dS][3])); xS + lSeg > x1 ? xE = x1 : xE = xS + lSeg; yE = getY(x0, y0, x1, y1, xE); if (!perpGap || !crossesBisector(xS, yS, xE, yE)) { tempPoints.push({x1: xS, y1: yS, x2: xE, y2: yE}); } xE + gSeg > x1 ? lfg = false : xS = xE + gSeg; xS = xE + gSeg; yS = getY(x0, y0, x1, y1, xS); } for (let i = 0; i < tempPoints.length; i++) { if (optimization == 1 && lineNum % 2 == 1) { // laps optimization const j = tempPoints.length - i - 1; points.push({x1: tempPoints[j].x2, y1: tempPoints[j].y2, x2: tempPoints[j].x1, y2: tempPoints[j].y1}); } else { points.push(tempPoints[i]); } } } function getClippedLine(x0, y0, x1, y1) { const xmin = rand(1, 100) < 33 ? rand(4, 100) : rand(4, 20); const ymin = rand(1, 100) < 33 ? rand(4, 100) : rand(4, 20); const xmax = 1000 - rand(4, 20); const ymax = rand(1,100) < 33 ? 1000 - rand(4, 60) : 1000 - rand(4, 20); const inside = (x, y) => x >= xmin && x <= xmax && y >= ymin && y <= ymax; if (inside(x0, y0) && inside(x1, y1)) { return { x0, y0, x1, y1 }; } const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const xLeft = xmin; const yLeft = m * xLeft + b; const xRight = xmax; const yRight = m * xRight + b; const yTop = ymin; const xTop = (yTop - b) / m; const yBottom = ymax; const xBottom = (yBottom - b) / m; const intersections = [ { x: xLeft, y: yLeft }, { x: xRight, y: yRight }, { x: xTop, y: yTop }, { x: xBottom, y: yBottom } ]; const validIntersections = intersections.filter(p => inside(p.x, p.y)); if (validIntersections.length >= 2) { return { x0: validIntersections[0].x, y0: validIntersections[0].y, x1: validIntersections[1].x, y1: validIntersections[1].y }; } return null; } function distance(x1, y1, x2, y2) { return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); } function sortPointPairs(pointPairs, allowReverse, optimize) { const sortedPairs = [pointPairs.shift()]; while (pointPairs.length > 0) { let selectedPairIndex = 0; let selectedDistance = optimize ? Infinity : -Infinity; let reverse = false; for (let i = 0; i < pointPairs.length; i++) { const pair = pointPairs[i]; const lastPair = sortedPairs[sortedPairs.length - 1]; const distances = [ distance(lastPair.x2, lastPair.y2, pair.x1, pair.y1), distance(lastPair.x2, lastPair.y2, pair.x2, pair.y2) ]; if (allowReverse) { distances.push( distance(lastPair.x2, lastPair.y2, pair.x2, pair.y2), distance(lastPair.x2, lastPair.y2, pair.x1, pair.y1) ); } distances.forEach((dist, index) => { if (optimize ? dist < selectedDistance : dist > selectedDistance) { selectedDistance = dist; selectedPairIndex = i; reverse = index % 2 !== 0; } }); } let selectedPair = pointPairs.splice(selectedPairIndex, 1)[0]; if (reverse && allowReverse) { [selectedPair.x1, selectedPair.x2] = [selectedPair.x2, selectedPair.x1]; [selectedPair.y1, selectedPair.y2] = [selectedPair.y2, selectedPair.y1]; } sortedPairs.push(selectedPair); } return sortedPairs; } function line2dots(x1, y1, x2, y2) { const d = .4; let texture = ""; const totalDistance = distance(x1, y1, x2, y2); const steps = totalDistance / d; const xStep = (x2 - x1) / steps; const yStep = (y2 - y1) / steps; for (let i = 0; i <= steps; i++) { const r = fractionalRand() * .75; let tempX = x1 + i * xStep; let tempY = y1 + i * yStep; if (i === steps) { tempX = x2; tempY = y2; } texture += C([tempX.toFixed(2), tempY.toFixed(2), r]); circlesCount++; } return texture; } function circle2dots(cx, cy, r) { const d = 0.4; let texture = ""; const circumference = 2 * Math.PI * r; const steps = circumference / d; for (let i = 0; i < steps; i++) { const theta = (i / steps) * 2 * Math.PI; const dotX = cx + r * Math.cos(theta); const dotY = cy + r * Math.sin(theta); const dotR = fractionalRand() * 0.75; texture += C([dotX.toFixed(2), dotY.toFixed(2), dotR]); circlesCount++; } return texture; }
Time Between the Lines is Thread Through the Mind is a meditative plotter-performance art project, where each experience is uniquely recorded using specific pen colors on either dark or light paper. As the fourth project in the Zenerative series of Zen-inspired generative art by Matto, it explores the value of time and the interplay between efficiency, order, and aesthetics. Outputs: The default live-code outputs are skeuomorphic, each drawn with 500,000 to 1,000,000 circles, while the default live-plot modes use a few thousand simplified lines. Using URL parameters, these live modes can be displayed digitally or plotted. Each token has one default output and includes a total of eight canonical versions: - two pen and paper sets (pen suggestions are embedded in the SVG code) - four optimization settings to change the line drawing order Interactivity: - 'S' saves an SVG (Scalable Vector Graphics) file - 'H' hides the signature (patience required in skeuomorphic mode) URL Parameter Options: - alternate = true / false (switches paper and pens from default) - optimization = laps / standard / maximum / anti (changes line drawing order and visual output) - skeuomorphic = true / false (toggles skeuomorphic circles / simple lines) - signature = true / false (toggles the signature) Plotter Notes: - Please maintain the drawing order for canonical outputs - avoid reordering or optimizing the paths during plotting. - On light paper, the signature stamp's legibility will vary with the ink colors used. Opaque inks of similar colors are acceptable alternatives. - Physical outputs may differ from digital versions due to pen and paper variability. Plotters have the discretion to use different pens, paper, or multiple passes. - For color hex values and corresponding pens, check the console when running the live code.
console.log("Scratch v5.5 © Matto"); console.log(`TOKEN ENTROPY: ${tokenData.hash}`); const tokenId = tokenData.tokenId; const attributes = {}; let seed = makeSeed(tokenData.hash); let nonce = 0; let showcncPreview = false; let showSignature = true; let enso = false; const allRadii = [0]; const brushCounts = [0,0,0]; const ringQTY = selectByProbs([5,4,3,2,1], RR()); const brushTypeList = []; const m = (R(0, 200) / 100) - 1; const sizeStrings = ["Small", "Medium", "Large"]; const sizeColors = ["red", "green", "blue"]; let focusMode, orbitMode, pointMode, metal, finish, bkg, metalColor; let proxLim = 1000 / 5; RR() < .80 ? metal = "Al" : metal = "Cu"; metal == "Cu" ? metalColor = "#b87333" : metalColor = "grey"; RR() < .80 ? finish = "Brushed" : finish = "Polished"; let dark = selectByProbs([false, true], RR()); if (!tokenData.plot) { dark ? bkg = 32 : bkg = 224; } for (i = 0; i < ringQTY; i++) { brushTypeList[i] = selectByProbs([0,1,2], RR()); brushCounts[brushTypeList[i]]++; } RR() > .75 ? focusMode = true : focusMode = false; RR() > .50 ? pointMode = true : pointMode = false; RR() > .50 && pointMode == true ? orbitMode = true : orbitMode = false; if (pointMode == false) { if (ringQTY == 1 && focusMode == false || ringQTY == 2 && focusMode == true) { enso = true; } } addAttribute("FOCUS", focusMode); addAttribute("Enso", enso); addAttribute("Point Mode", pointMode); addAttribute("Orbits", orbitMode); addAttribute("Ring Count", ringQTY.toString()); addAttribute("Small Disc Rings", brushCounts[0].toString()); addAttribute("Medium Disc Rings", brushCounts[1].toString()); addAttribute("Large Disc Rings", brushCounts[2].toString()); addAttribute("Metal", metal); addAttribute("Finish", finish); addAttribute("Dark", dark); console.log("Attributes:"); console.log(attributes); const svgStart = `<?xml version="1.0" encoding="utf-8"?><svg viewBox="0 0 1000 1000" style="background-color:rgb(${bkg},${bkg},${bkg})" xmlns="http://www.w3.org/2000/svg">`; let svg = ""; let sig = ""; let cncPreview = ""; const cncSVG = []; for (let i=0; i < 4; i++) { cncSVG[i] = ""; if (i != 3) { cncSVG[i] += `<desc>Sand the ${sizeColors[i]} paths with a disk that is ${i + 1}/24th the artwork size.</desc><g id="padsize-${i + 1}" style="stroke:${sizeColors[i]}; stroke-width: 1px; stroke-opacity:1; fill-opacity:0">`; } else { cncSVG[i] += `<desc>Carve the black paths with an end mill. For a 24 x 24 inch plate, use a 1/16-inch bit.</desc>`; } } function setup() { noCanvas(); noLoop(); } function draw() { let bkg = `<g id="background" style="stroke:${metalColor}; stroke-width: ${1000/1000}px; stroke-opacity:1; fill-opacity:0">`; let bMod; finish == "Brushed" ? bMod = 0 : bMod = R(100, 200); for (let b = -2000; b < 2000; b += (10/3) + Math.sin(b) * (10/4) + bMod) { const x0 = Math.max(0, -b / m); const x1 = Math.min(1000, (1000 - b) / m); const y0 = b + x0 * m; const y1 = b + x1 * m; const clipped = getClippedLine(x0, y0, x1, y1); if (clipped) { bkg += lineWithGaps(clipped.x0,clipped.y0,clipped.x1,clipped.y1); } } svg = svgStart + bkg + "</g>"; for (let p = 0; p < ringQTY; p++) { let plotString = ""; let cncString = ""; const brushType = brushTypeList[p]; let radius, newStrings; const padding = 100 + (brushType + 1) * (1000 / 48); if (p == 0 && focusMode) { radius = (brushType + 1) * (2000 / 24); allRadii.push(radius); newStrings = addPoint(500, 500, brushType); } else { radius = randRad(padding); newStrings = addCircle(500, 500, radius, brushType); } plotString += newStrings.plot; cncString += newStrings.cnc; cncSVG[brushType] += newStrings.cnc; svg += `<g id="ring-${p}" style="stroke:${metalColor}; stroke-width: 1px; stroke-opacity:1; fill-opacity:0"><desc>Ring Radius: ${radius}, Disc Size: ${sizeStrings[brushType]}</desc>` + plotString + "</g>"; cncPreview += `<g id="cnc-${p}" style="stroke:purple; stroke-width: 2.6px; stroke-opacity:1; fill-opacity:0">` + cncString + "</g>"; } sig += `<polyline points="924,956 920,956 920,860 940,872 960,860 960,956 956,956" />`; sig += `<polyline points="928,902 940,872 952,902" stroke-linejoin="bevel" />`; sig += `<line x1="934" y1="888" x2="946" y2="888" />`; sig += `<line x1="920" y1="902" x2="960" y2="902" />`; sig += `<line x1="932" y1="902" x2="932" y2="928" />`; sig += `<line x1="948" y1="902" x2="948" y2="928" />`; sig += `<circle cx="940" cy="940" r="15" />`; let sigCNCStart = `<g id="cnc-signature" style="stroke:black; stroke-width: 2.6px; stroke-opacity:1; fill-opacity:0">`; cncPreview += sigCNCStart + sig + "</g>"; cncSVG[3] += sigCNCStart + sig; sig = `<g id="signature" style="stroke:${metalColor}; stroke-width: 2.6px; stroke-opacity:1; fill-opacity:0"><desc>Suggested Pen Thickness: 2.6x</desc>` + sig + "</g>"; for (let i = 0; i < 4; i++) { cncSVG[i] += "</g>"; } document.body.insertAdjacentHTML('beforeend', svg + sig + "</svg>"); console.log("CNC SVG:"); console.log(compilecncSVG()); } function addAttribute(trait_type, value) { attributes[trait_type] = value; } function makeSeed(input) { let s = 1; for (let i = 0; i < (input.length - 2) / 2; i++) { let v = parseInt(input.slice(2 + i * 2, 4 + i * 2), 16); s = ((s * v) % 999999999999) + 1 } return s; } function R(min, max) { const range = max - min + 1; const v = (seed % range) + min; newSeed(); return Math.floor(v); } function RR() { return R(1,100)/100; } function newSeed() { nonce++; seed = ((seed * 35932678341237) + nonce) % 999999999999 + 1 } function keyPressed() { if (tokenData.plot != true) { const k = key.toUpperCase(); if (k === "S") { saveStrings([svg + sig + "</svg>"], `SCRATCH-${tokenId}`, "svg"); } else if (k === "C") { showcncPreview = !showcncPreview; updateSVG(); } else if (k === "H") { showSignature = !showSignature; updateSVG() } else if (k === "E") { saveStrings([compilecncSVG()], `SCRATCH-${tokenId}-CNC`, "svg"); } } return false; } function updateSVG() { let comp = svg; if (showSignature) { comp += sig; } if (showcncPreview) { comp += cncPreview; } document.body.innerHTML = ''; document.body.insertAdjacentHTML('beforeend', comp + "</svg>"); } function randRad(padding) { const radius = R(allRadii[0], (500) - padding); let tooClose = false; for (let i = 0; i < allRadii.length; i++) { if (Math.abs(radius - allRadii[i]) < proxLim) { tooClose = true; proxLim -= 1; break; } } if (!tooClose) { allRadii.push(radius); proxLim = 1000 / 5; return radius; } else { return randRad(padding); } } function selectByProbs(list, randomValue) { const totalWeight = list.reduce(function(acc, item, index) { return acc + (1 / (index + 2)); }, 0); const threshold = totalWeight * randomValue; let currentSum = 0; for (let i = 0; i < list.length; i++) { currentSum += 1 / (i + 2); if (currentSum >= threshold) { return list[i]; } } return list[list.length - 1]; } function compilecncSVG() { let list = svgStart; list += `<desc>The grey border is a visual guide. Please scale the vectors appropriately.</desc><rect x="0" y="0" width="1000" height="1000" style="stroke:grey; stroke-width: 1px; stroke-opacity:1; fill-opacity:0" />` for (let i = 0; i < 4; i++) { if (cncSVG[i].length > 180) { list += cncSVG[i]; } } return list + "</svg>"; } function addPoint(x,y, brushType) { let plotString = ""; const cncString = `<circle cx="${x}" cy="${y}" r="4" />`; for (let i = 0; i < R(5,7); i++) { plotString += concentrics(x, y, (brushType + 1) * (1000 / 24), 1); } return {plot: plotString, cnc: cncString}; } function getY(x0, y0, x1, y1, x) { const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const y = m * x + b; return y; } function getX(x0, y0, x1, y1, y) { const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const x = (y - b) / m; return x; } function lineWithGaps(x0, y0, x1, y1) { let xT, yT; if(x0 > x1) { xT = x0; yT = y0; x0 = x1; y0 = y1; x1 = xT; y1 = yT; } let xS = x0; let yS = y0; let yE, xE; let str = ""; let lfg = true; while (lfg) { let dS; let dnm = [[60,4,48,12],[80,12,40,8]]; finish == "Brushed" ? dS = 0 : dS = 1; let lSeg = R((1000 / dnm[dS][0]), (1000 / dnm[dS][1])); let gSeg = R((1000 / dnm[dS][2]), (1000 / dnm[dS][3])); xS + lSeg > x1 ? xE = x1 : xE = xS + lSeg; yE = getY(x0, y0, x1, y1, xE); str += `<line x1="${xS}" y1="${yS}" x2="${xE}" y2="${yE}" />` xE + gSeg > x1 ? lfg = false : xS = xE + gSeg; xS = xE + gSeg; yS = getY(x0, y0, x1, y1, xS); } return str; } function addCircle(x,y, cr, brushType) { const r = (brushType + 1) * (1000 / 24); const orbitLocs = R(2,6); let locs; const pad = (1000 / 15) + (brushType + 1) * (1000 / 48); let plotString = ""; let cncString = ""; if (pointMode) { locs = R(2,20); } else { locs = Math.floor((2 * PI * cr) / 10); cncString = `<circle cx="${x}" cy="${y}" r="${cr}" />`; } const angleStep = 2 * PI / locs; for (let i = 0; i < locs; i++) { const angle = i * angleStep; const xC = x + cr * Math.cos(angle); const yC = y + cr * Math.sin(angle); if (pointMode) { const newStrings = addPoint(xC, yC, brushType); plotString += newStrings.plot; cncString += newStrings.cnc; if (orbitMode && cr > 200) { const closestEdgeDist = Math.min(Math.min(Math.abs(1000 - xC), xC), Math.min(Math.abs(1000 - yC), yC)); if (closestEdgeDist > pad + 2 * r) { const newStringsOrbit = addOrbit(xC, yC, 2 * r, brushType, orbitLocs); plotString += newStringsOrbit.plot; cncString += newStringsOrbit.cnc; } } } else { plotString += concentrics(xC, yC, r, 0); } } return {plot: plotString, cnc: cncString}; } function addOrbit(x, y, cr, brushType, locs) { const r = (brushType + 1) * (1000 / 24); let plotString = ""; let cncString = ""; const angleStep = 2 * PI / locs; for (let i = 0; i < locs; i++) { const angle = i * angleStep; const xC = x + cr * Math.cos(angle); const yC = y + cr * Math.sin(angle); const newStrings = addPoint(xC, yC, brushType); plotString += newStrings.plot; cncString += newStrings.cnc; } return {plot: plotString, cnc: cncString}; } function concentrics(x,y,r,f) { r /= 2; let arcs = ""; if (f == 1) { x = x + R(-4, 4); y = y + R(-4, 4); } for (let i = 10; i < r; i += R(r/25, r/8) ) { arcs += randomArcSVG(x,y,i); } return arcs; } function randomArcSVG(x, y, radius) { const getRandomAngle = () => R(0,628) / 100; const startAngle = getRandomAngle(); const endAngle = getRandomAngle(); let sweepAngle = endAngle - startAngle; if (sweepAngle < 0) { sweepAngle += 2 * Math.PI; } const startX = x + radius * Math.cos(startAngle); const startY = y + radius * Math.sin(startAngle); const endX = x + radius * Math.cos(endAngle); const endY = y + radius * Math.sin(endAngle); const largeArcFlag = sweepAngle > Math.PI ? 1 : 0; const sweepFlag = 1; const d = `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${endX} ${endY}`; return `<path d="${d}" />`; } function getClippedLine(x0, y0, x1, y1) { const xmin = R((100 / 24),(1000 / 48)); const ymin = R((100 / 24),(1000 / 48)); const xmax = 1000-R((100 / 24),(1000 / 48)); const ymax = 1000-R((100 / 24),(1000 / 48)); const inside = (x, y) => x >= xmin && x <= xmax && y >= ymin && y <= ymax; if (inside(x0, y0) && inside(x1, y1)) { return { x0, y0, x1, y1 }; } const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const xLeft = xmin; const yLeft = m * xLeft + b; const xRight = xmax; const yRight = m * xRight + b; const yTop = ymin; const xTop = (yTop - b) / m; const yBottom = ymax; const xBottom = (yBottom - b) / m; const intersections = [ { x: xLeft, y: yLeft }, { x: xRight, y: yRight }, { x: xTop, y: yTop }, { x: xBottom, y: yBottom } ]; const validIntersections = intersections.filter(p => inside(p.x, p.y)); if (validIntersections.length >= 2) { return { x0: validIntersections[0].x, y0: validIntersections[0].y, x1: validIntersections[1].x, y1: validIntersections[1].y }; } return null; }
FOCUS is the second project in my ‘Zenerative’ series of Zen-inspired generative art, following [Ensō](https://www.artblocks.io/project/34). The algorithm can produce interactive pieces that are elegantly minimal as well as artworks featuring complex patterns that explore visual illusions and artificial space. FOCUS outputs span three responsive aspect ratios, each of which have three possibilities for the central shape - or focus. Drawing inspiration from the Zen art tradition of doing more with less, the limited palette consists of black, white, and five colors. This palette both constrains possible outcomes and allows the creation of unique perceptual interactions. Except in extremely rare scenarios, each piece can provide a wide variety of visual experiences through its interactive features: use up and down arrows to adjust the pattern recursion factor, left and right arrows to adjust the midpoint count, ‘h’ to hide or show the central shape, and ‘s’ to save. As a gift to my collectors, FOCUS has a free-to-claim, 100% on-chain, generative SVG companion project called [deFOCUSed](https://matto.xyz/project/defocused/). (Thank you!)
Scratch is a Zen-inspired generative art project that explores the captivating patterns that sanding discs create on metal surfaces. The digital artworks can be pen-plotted at any scale, and an alternate physical output can be created on sheets of metal using CNC machines. Interactivity: 'S' saves the SVG. 'C' previews the CNC paths. 'E' exports the CNC paths. 'H' hides the signature stamp. Plotter Notes: To replicate the density of the digital image, choose a pen for the main area of a Scratch that is 1/1000th the length of the sides of the plottable area. For the signature, a pen that is 2.6 times thicker than the one used for the main area is suggested. For instance, for a 10" square plot, a 0.25mm pen for the main image and a 0.65mm pen for the signature will closely mimic the density of the digital output. While bolder pens can create more visually striking images, denser Scratches plotted with thicker pens might require a heavier paper stock. CNC Notes: Experienced CNC technicians can faithfully execute the outputs on 24" x 24" sheets of metal by using 1", 2", and 3" sanding discs and a 1/16" end mill. The choice of material and finish should be based on the specific characteristics of the Scratch output. If the Scratch output includes the 'Dark' feature, anodized aluminum or copper with a patina can be used. Clear coat the piece after machining to prevent tarnishing.
Scratch is a Zen-inspired generative art project that explores the captivating patterns that sanding discs create on metal surfaces. The digital artworks can be pen-plotted at any scale, and an alternate physical output can be created on sheets of metal using CNC machines. Interactivity: 'S' saves the SVG. 'C' previews the CNC paths. 'E' exports the CNC paths. 'H' hides the signature stamp. Plotter Notes: To replicate the density of the digital image, choose a pen for the main area of a Scratch that is 1/1000th the length of the sides of the plottable area. For the signature, a pen that is 2.6 times thicker than the one used for the main area is suggested. For instance, for a 10" square plot, a 0.25mm pen for the main image and a 0.65mm pen for the signature will closely mimic the density of the digital output. While bolder pens can create more visually striking images, denser Scratches plotted with thicker pens might require a heavier paper stock. CNC Notes: Experienced CNC technicians can faithfully execute the outputs on 24" x 24" sheets of metal by using 1", 2", and 3" sanding discs and a 1/16" end mill. The choice of aluminum or copper as a material should be based on the specific characteristics of the Scratch output. If the Scratch output includes the 'Dark' feature, anodized aluminum or a copper with a patina can be used. Clear coat the piece after machining to prevent tarnishing.
console.log("Scratch v5.4 © Matto"); console.log(`TOKEN ENTROPY: ${tokenData.hash}`); const tokenId = tokenData.tokenId; const attributes = {}; let seed = makeSeed(tokenData.hash); let nonce = 0; let showcncPreview = false; let showSignature = true; const allRadii = [0]; const brushCounts = [0,0,0]; const ringQTY = selectByProbs([5,4,3,2,1], RR()); const brushTypeList = []; const m = (R(0, 200) / 100) - 1; const sizeStrings = ["Small", "Medium", "Large"]; const sizeColors = ["red", "green", "blue"]; let focusMode, orbitMode, pointMode, metal, finish, bkg, metalColor; let proxLim = 1000 / 5; RR() < .80 ? metal = "Al" : metal = "Cu"; metal == "Cu" ? metalColor = "#b87333" : metalColor = "grey"; RR() < .80 ? finish = "Brushed" : finish = "Polished"; let dark = selectByProbs([false, true], RR()); if (!tokenData.plot) { dark ? bkg = 32 : bkg = 224; } for (i = 0; i < ringQTY; i++) { brushTypeList[i] = selectByProbs([0,1,2], RR()); brushCounts[brushTypeList[i]]++; } RR() > .75 ? focusMode = true : focusMode = false; RR() > .50 ? pointMode = true : pointMode = false; RR() > .50 && pointMode == true ? orbitMode = true : orbitMode = false; addAttribute("FOCUS", focusMode); addAttribute("Point Mode", pointMode); addAttribute("Orbits", orbitMode); addAttribute("Ring Count", ringQTY.toString()); addAttribute("Small Disc Rings", brushCounts[0].toString()); addAttribute("Medium Disc Rings", brushCounts[1].toString()); addAttribute("Large Disc Rings", brushCounts[2].toString()); addAttribute("Metal", metal); addAttribute("Finish", finish); addAttribute("Dark", dark); console.log("Attributes:"); console.log(attributes); const svgStart = `<?xml version="1.0" encoding="utf-8"?><svg viewBox="0 0 1000 1000" style="background-color:rgb(${bkg},${bkg},${bkg})" xmlns="http://www.w3.org/2000/svg">`; let svg = ""; let sig = ""; let cncPreview = ""; const cncSVG = []; for (let i=0; i < 4; i++) { cncSVG[i] = ""; if (i != 3) { cncSVG[i] += `<desc>Sand the ${sizeColors[i]} paths with a disk that is ${i + 1}/24th the artwork size.</desc><g id="padsize-${i + 1}" style="stroke:${sizeColors[i]}; stroke-width: 1px; stroke-opacity:1; fill-opacity:0">`; } else { cncSVG[i] += `<desc>Carve the black paths with an end mill. For a 24 x 24 inch plate, use a 1/16-inch bit.</desc>`; } } function setup() { noCanvas(); noLoop(); } function draw() { let bkg = `<g id="background" style="stroke:${metalColor}; stroke-width: ${1000/1000}px; stroke-opacity:1; fill-opacity:0">`; let bMod; finish == "Brushed" ? bMod = 0 : bMod = R(100, 200); for (let b = -2000; b < 2000; b += (10/3) + Math.sin(b) * (10/4) + bMod) { const x0 = Math.max(0, -b / m); const x1 = Math.min(1000, (1000 - b) / m); const y0 = b + x0 * m; const y1 = b + x1 * m; const clipped = getClippedLine(x0, y0, x1, y1); if (clipped) { bkg += lineWithGaps(clipped.x0,clipped.y0,clipped.x1,clipped.y1); } } svg = svgStart + bkg + "</g>"; for (let p = 0; p < ringQTY; p++) { let plotString = ""; let cncString = ""; const brushType = brushTypeList[p]; let radius, newStrings; const padding = 100 + (brushType + 1) * (1000 / 48); if (p == 0 && focusMode) { radius = (brushType + 1) * (2000 / 24); allRadii.push(radius); newStrings = addPoint(500, 500, brushType); } else { radius = randRad(padding); newStrings = addCircle(500, 500, radius, brushType); } plotString += newStrings.plot; cncString += newStrings.cnc; cncSVG[brushType] += newStrings.cnc; svg += `<g id="ring-${p}" style="stroke:${metalColor}; stroke-width: 1px; stroke-opacity:1; fill-opacity:0"><desc>Ring Radius: ${radius}, Disc Size: ${sizeStrings[brushType]}</desc>` + plotString + "</g>"; cncPreview += `<g id="cnc-${p}" style="stroke:purple; stroke-width: 2.6px; stroke-opacity:1; fill-opacity:0">` + cncString + "</g>"; } sig += `<polyline points="924,956 920,956 920,860 940,872 960,860 960,956 956,956" />`; sig += `<polyline points="928,902 940,872 952,902" stroke-linejoin="bevel" />`; sig += `<line x1="934" y1="888" x2="946" y2="888" />`; sig += `<line x1="920" y1="902" x2="960" y2="902" />`; sig += `<line x1="932" y1="902" x2="932" y2="928" />`; sig += `<line x1="948" y1="902" x2="948" y2="928" />`; sig += `<circle cx="940" cy="940" r="15" />`; let sigCNCStart = `<g id="cnc-signature" style="stroke:black; stroke-width: 2.6px; stroke-opacity:1; fill-opacity:0">`; cncPreview += sigCNCStart + sig + "</g>"; cncSVG[3] += sigCNCStart + sig; sig = `<g id="signature" style="stroke:${metalColor}; stroke-width: 2.6px; stroke-opacity:1; fill-opacity:0"><desc>Suggested Pen Thickness: 2.6x</desc>` + sig + "</g>"; for (let i = 0; i < 4; i++) { cncSVG[i] += "</g>"; } document.body.insertAdjacentHTML('beforeend', svg + sig + "</svg>"); console.log("CNC SVG:"); console.log(compilecncSVG()); } function addAttribute(trait_type, value) { attributes[trait_type] = value; } function makeSeed(input) { let s = 1; for (let i = 0; i < (input.length - 2) / 2; i++) { let v = parseInt(input.slice(2 + i * 2, 4 + i * 2), 16); s = ((s * v) % 999999999999) + 1 } return s; } function R(min, max) { const range = max - min + 1; const v = (seed % range) + min; newSeed(); return Math.floor(v); } function RR() { return R(1,100)/100; } function newSeed() { nonce++; seed = ((seed * 35932678341237) + nonce) % 999999999999 + 1 } function keyPressed() { if (tokenData.plot != true) { const k = key.toUpperCase(); if (k === "S") { saveStrings([svg + sig + "</svg>"], `SCRATCH-${tokenId}`, "svg"); } else if (k === "C") { showcncPreview = !showcncPreview; updateSVG(); } else if (k === "H") { showSignature = !showSignature; updateSVG() } else if (k === "E") { saveStrings([compilecncSVG()], `SCRATCH-${tokenId}-CNC`, "svg"); } } return false; } function updateSVG() { let comp = svg; if (showSignature) { comp += sig; } if (showcncPreview) { comp += cncPreview; } document.body.innerHTML = ''; document.body.insertAdjacentHTML('beforeend', comp + "</svg>"); } function randRad(padding) { const radius = R(allRadii[0], (500) - padding); let tooClose = false; for (let i = 0; i < allRadii.length; i++) { if (Math.abs(radius - allRadii[i]) < proxLim) { tooClose = true; proxLim -= 1; break; } } if (!tooClose) { allRadii.push(radius); proxLim = 1000 / 5; return radius; } else { return randRad(padding); } } function selectByProbs(list, randomValue) { const totalWeight = list.reduce(function(acc, item, index) { return acc + (1 / (index + 2)); }, 0); const threshold = totalWeight * randomValue; let currentSum = 0; for (let i = 0; i < list.length; i++) { currentSum += 1 / (i + 2); if (currentSum >= threshold) { return list[i]; } } return list[list.length - 1]; } function compilecncSVG() { let list = svgStart; list += `<desc>The grey border is a visual guide. Please scale the vectors appropriately.</desc><rect x="0" y="0" width="1000" height="1000" style="stroke:grey; stroke-width: 1px; stroke-opacity:1; fill-opacity:0" />` for (let i = 0; i < 4; i++) { if (cncSVG[i].length > 180) { list += cncSVG[i]; } } return list + "</svg>"; } function addPoint(x,y, brushType) { let plotString = ""; const cncString = `<circle cx="${x}" cy="${y}" r="4" />`; for (let i = 0; i < R(5,7); i++) { plotString += concentrics(x, y, (brushType + 1) * (1000 / 24), 1); } return {plot: plotString, cnc: cncString}; } function getY(x0, y0, x1, y1, x) { const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const y = m * x + b; return y; } function getX(x0, y0, x1, y1, y) { const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const x = (y - b) / m; return x; } function lineWithGaps(x0, y0, x1, y1) { let xT, yT; if(x0 > x1) { xT = x0; yT = y0; x0 = x1; y0 = y1; x1 = xT; y1 = yT; } let xS = x0; let yS = y0; let yE, xE; let str = ""; let lfg = true; while (lfg) { let dS; let dnm = [[60,4,48,12],[80,12,40,8]]; finish == "Brushed" ? dS = 0 : dS = 1; let lSeg = R((1000 / dnm[dS][0]), (1000 / dnm[dS][1])); let gSeg = R((1000 / dnm[dS][2]), (1000 / dnm[dS][3])); xS + lSeg > x1 ? xE = x1 : xE = xS + lSeg; yE = getY(x0, y0, x1, y1, xE); str += `<line x1="${xS}" y1="${yS}" x2="${xE}" y2="${yE}" />` xE + gSeg > x1 ? lfg = false : xS = xE + gSeg; xS = xE + gSeg; yS = getY(x0, y0, x1, y1, xS); } return str; } function addCircle(x,y, cr, brushType) { const r = (brushType + 1) * (1000 / 24); const orbitLocs = R(2,6); let locs; const pad = (1000 / 15) + (brushType + 1) * (1000 / 48); let plotString = ""; let cncString = ""; if (pointMode) { locs = R(2,20); } else { locs = Math.floor((2 * PI * cr) / 10); cncString = `<circle cx="${x}" cy="${y}" r="${cr}" />`; } const angleStep = 2 * PI / locs; for (let i = 0; i < locs; i++) { const angle = i * angleStep; const xC = x + cr * Math.cos(angle); const yC = y + cr * Math.sin(angle); if (pointMode) { const newStrings = addPoint(xC, yC, brushType); plotString += newStrings.plot; cncString += newStrings.cnc; if (orbitMode && cr > 200) { const closestEdgeDist = Math.min(Math.min(Math.abs(1000 - xC), xC), Math.min(Math.abs(1000 - yC), yC)); if (closestEdgeDist > pad + 2 * r) { const newStringsOrbit = addOrbit(xC, yC, 2 * r, brushType, orbitLocs); plotString += newStringsOrbit.plot; cncString += newStringsOrbit.cnc; } } } else { plotString += concentrics(xC, yC, r, 0); } } return {plot: plotString, cnc: cncString}; } function addOrbit(x, y, cr, brushType, locs) { const r = (brushType + 1) * (1000 / 24); let plotString = ""; let cncString = ""; const angleStep = 2 * PI / locs; for (let i = 0; i < locs; i++) { const angle = i * angleStep; const xC = x + cr * Math.cos(angle); const yC = y + cr * Math.sin(angle); const newStrings = addPoint(xC, yC, brushType); plotString += newStrings.plot; cncString += newStrings.cnc; } return {plot: plotString, cnc: cncString}; } function concentrics(x,y,r,f) { r /= 2; let arcs = ""; if (f == 1) { x = x + R(-4, 4); y = y + R(-4, 4); } for (let i = 10; i < r; i += R(r/25, r/8) ) { arcs += randomArcSVG(x,y,i); } return arcs; } function randomArcSVG(x, y, radius) { const getRandomAngle = () => R(0,628) / 100; const startAngle = getRandomAngle(); const endAngle = getRandomAngle(); let sweepAngle = endAngle - startAngle; if (sweepAngle < 0) { sweepAngle += 2 * Math.PI; } const startX = x + radius * Math.cos(startAngle); const startY = y + radius * Math.sin(startAngle); const endX = x + radius * Math.cos(endAngle); const endY = y + radius * Math.sin(endAngle); const largeArcFlag = sweepAngle > Math.PI ? 1 : 0; const sweepFlag = 1; const d = `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${endX} ${endY}`; return `<path d="${d}" />`; } function getClippedLine(x0, y0, x1, y1) { const xmin = R((100 / 24),(1000 / 48)); const ymin = R((100 / 24),(1000 / 48)); const xmax = 1000-R((100 / 24),(1000 / 48)); const ymax = 1000-R((100 / 24),(1000 / 48)); const inside = (x, y) => x >= xmin && x <= xmax && y >= ymin && y <= ymax; if (inside(x0, y0) && inside(x1, y1)) { return { x0, y0, x1, y1 }; } const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const xLeft = xmin; const yLeft = m * xLeft + b; const xRight = xmax; const yRight = m * xRight + b; const yTop = ymin; const xTop = (yTop - b) / m; const yBottom = ymax; const xBottom = (yBottom - b) / m; const intersections = [ { x: xLeft, y: yLeft }, { x: xRight, y: yRight }, { x: xTop, y: yTop }, { x: xBottom, y: yBottom } ]; const validIntersections = intersections.filter(p => inside(p.x, p.y)); if (validIntersections.length >= 2) { return { x0: validIntersections[0].x, y0: validIntersections[0].y, x1: validIntersections[1].x, y1: validIntersections[1].y }; } return null; }
Scratch is a Zen-inspired generative art project that explores the captivating patterns that sanding discs create on metal surfaces. The digital artworks can be pen-plotted at any scale, and they can be recreated on 24" x 24" sheets of metal using CNC machines. Interactivity: 'S' saves the SVG. 'C' previews the CNC paths. 'E' exports the CNC paths. 'H' hides the signature stamp. Plotter Notes: To replicate the density of the digital image, choose a pen for the main area of a Scratch that is 1/1000th the length of the sides of the plottable area. For the signature, a pen that is 2.6 times thicker than the one used for the main area is suggested. For instance, for a 10" square plot, a 0.25mm pen for the main image and a 0.65mm pen for the signature will closely mimic the density of the digital output. While bolder pens can create more visually striking images, denser Scratches plotted with thicker pens might require a heavier paper stock. CNC Notes: Experienced CNC technicians can faithfully execute the outputs on 24" x 24" sheets of metal by using 1", 2", and 3" diameter sanding discs and a 1/16" end mill. The choice of aluminum or copper as a material should be based on the specific characteristics of the Scratch output. If the Scratch output includes the 'Dark' feature, anodized aluminum or a copper with a patina can be used. Clear coat the piece after machining.
Scratch is a Zen-inspired generative art project that explores the captivating patterns that sanding discs create on metal surfaces. The digital artworks can be pen-plotted at any scale, and they can be recreated on 24" x 24" sheets of metal using CNC machines. Interactivity: 'S' saves the SVG. 'C' previews the CNC paths. 'E' exports the CNC paths. 'H' hides the signature stamp. Plotter Notes: To replicate the density of the digital image, choose a pen for the main area of a Scratch that is 1/1000th the length of the sides of the plottable area. For the signature, a pen that is 2.6 times thicker than the one used for the main area is suggested. For instance, for a 10" square plot, a 0.25mm pen for the main image and a 0.65mm pen for the signature will closely mimic the density of the digital output. While bolder pens can create more visually striking images, denser Scratches plotted with thicker pens might require a heavier paper stock. CNC Notes: Experienced CNC technicians can faithfully reproduce the digital outputs on 24" x 24" sheets of metal by using 1", 2", and 3" diameter sanding discs. The choice of aluminum or copper as a material should be based on the specific characteristics of the Scratch output. If the Scratch output includes the 'Dark' feature, both aluminum and copper sheets can be darkened with common chemicals before the machining process.
console.log("Scratch v5.1 © Matto"); console.log(`TOKEN ENTROPY: ${tokenData.hash}`); const tokenId = tokenData.tokenId; const attributes = []; let seed = makeSeed(tokenData.hash); let nonce = 0; let showcncPreview = false; let showSignature = true; const allRadii = [0]; const brushCounts = [0,0,0]; const ringQTY = selectByProbs([5,4,3,2,1], RR()); const brushTypeList = []; const m = (R(0, 200) / 100) - 1; const sizeStrings = ["Small", "Medium", "Large"]; let focusMode, orbitMode, pointMode, metal, finish, bkg, metalColor; let proxLim = 1000 / 5; RR() < .80 ? metal = "Al" : metal = "Cu"; metal == "Cu" ? metalColor = "#b87333" : metalColor = "grey"; RR() < .80 ? finish = "Brushed" : finish = "Polished"; let dark = selectByProbs([false, true], RR()); if (!tokenData.plot) { dark ? bkg = 32 : bkg = 224; } for (i = 0; i < ringQTY; i++) { brushTypeList[i] = selectByProbs([0,1,2], RR()); brushCounts[brushTypeList[i]]++; } RR() > .75 ? focusMode = true : focusMode = false; RR() > .50 ? pointMode = true : pointMode = false; RR() > .50 && pointMode == true ? orbitMode = true : orbitMode = false; addAttribute("FOCUS", focusMode); addAttribute("Point Mode", pointMode); addAttribute("Orbits", orbitMode); addAttribute("Ring Count", ringQTY.toString()); addAttribute("Small Disc Rings", brushCounts[0].toString()); addAttribute("Medium Disc Rings", brushCounts[1].toString()); addAttribute("Large Disc Rings", brushCounts[2].toString()); addAttribute("Metal", metal); addAttribute("Finish", finish); addAttribute("Dark", dark); // sessionStorage.setItem("attributes", JSON.stringify(attributes)); console.log("Attributes:"); console.log(attributes); const svgStart = `<?xml version="1.0" encoding="utf-8"?><svg viewBox="0 0 1000 1000" style="background-color:rgb(${bkg},${bkg},${bkg})" xmlns="http://www.w3.org/2000/svg">`; let svg = ""; let sig = ""; let cncPreview = ""; const cncSVG = []; for (let i=0; i < 4; i++) { cncSVG[i] = svgStart; if (i != 3) { cncSVG[i] += `<desc>Scratch with a ${i + 1}-inch pad onto 24 x 24 inch plate.</desc><g id="padsize-${i + 1}" style="stroke:black; stroke-width: 1px; stroke-opacity:1; fill-opacity:0">`; } else { cncSVG[i] += `<desc>Carve with a 1/16-inch end mill onto 24 x 24 inch plate.</desc>`; } } function setup() { noCanvas(); noLoop(); } function draw() { let bkg = `<g id="background" style="stroke:${metalColor}; stroke-width: ${1000/1000}px; stroke-opacity:1; fill-opacity:0">`; let bMod; finish == "Brushed" ? bMod = 0 : bMod = R(100, 200); for (let b = -2000; b < 2000; b += (10/3) + Math.sin(b) * (10/4) + bMod) { const x0 = Math.max(0, -b / m); const x1 = Math.min(1000, (1000 - b) / m); const y0 = b + x0 * m; const y1 = b + x1 * m; const clipped = getClippedLine(x0, y0, x1, y1); if (clipped) { bkg += lineWithGaps(clipped.x0,clipped.y0,clipped.x1,clipped.y1); } } svg = svgStart + bkg + "</g>"; for (let p = 0; p < ringQTY; p++) { let plotString = ""; let cncString = ""; const brushType = brushTypeList[p]; let radius, newStrings; const padding = 100 + (brushType + 1) * (1000 / 48); if (p == 0 && focusMode) { radius = (brushType + 1) * (2000 / 24); allRadii.push(radius); newStrings = addPoint(500, 500, brushType); } else { radius = randRad(padding); newStrings = addCircle(500, 500, radius, brushType); } plotString += newStrings.plot; cncString += newStrings.cnc; cncSVG[brushType] += newStrings.cnc; svg += `<g id="ring-${p}" style="stroke:${metalColor}; stroke-width: 1px; stroke-opacity:1; fill-opacity:0"><desc>Ring Radius: ${radius}, Disc Size: ${sizeStrings[brushType]}</desc>` + plotString + "</g>"; cncPreview += `<g id="cnc-${p}" style="stroke:blue; stroke-width: 2.6px; stroke-opacity:1; fill-opacity:0">` + cncString + "</g>"; } sig += `<polyline points="924,956 920,956 920,860 940,872 960,860 960,956 956,956" />`; sig += `<polyline points="928,902 940,872 952,902" stroke-linejoin="bevel" />`; sig += `<line x1="934" y1="888" x2="946" y2="888" />`; sig += `<line x1="920" y1="902" x2="960" y2="902" />`; sig += `<line x1="932" y1="902" x2="932" y2="928" />`; sig += `<line x1="948" y1="902" x2="948" y2="928" />`; sig += `<circle cx="940" cy="940" r="15" />`; let sigCNCStart = `<g id="cnc-signature" style="stroke:blue; stroke-width: 2.6px; stroke-opacity:1; fill-opacity:0">`; cncPreview += sigCNCStart + sig + "</g>"; cncSVG[3] += sigCNCStart + sig; sig = `<g id="signature" style="stroke:${metalColor}; stroke-width: 2.6px; stroke-opacity:1; fill-opacity:0"><desc>Suggested Pen Thickness: 2.6x</desc>` + sig + "</g>"; for (let i = 0; i < 4; i++) { cncSVG[i] += "</g></svg>"; } document.body.insertAdjacentHTML('beforeend', svg + sig + "</svg>"); console.log("CNC SVGs:"); console.log(compilecncSVG()); } function addAttribute(trait_type, value) { if (typeof value == "boolean") { value = value.toString(); } attributes.push({ trait_type, value }); } function makeSeed(input) { let s = 1; for (let i = 0; i < (input.length - 2) / 2; i++) { let v = parseInt(input.slice(2 + i * 2, 4 + i * 2), 16); s = ((s * v) % 999999999999) + 1 } return s; } function R(min, max) { const range = max - min + 1; const v = (seed % range) + min; newSeed(); return Math.floor(v); } function RR() { return R(1,100)/100; } function newSeed() { nonce++; seed = ((seed * 35932678341237) + nonce) % 999999999999 + 1 } function keyPressed() { if (tokenData.plot != true) { const k = key.toUpperCase(); if (k === "S") { saveStrings([svg], `SCRATCH-${tokenId}-SVG.txt`); } else if (k === "C") { showcncPreview = !showcncPreview; updateSVG(); } else if (k === "H") { showSignature = !showSignature; updateSVG() } else if (k === "E") { saveStrings(compilecncSVG(), `SCRATCH-${tokenId}-CNC-SVG.txt`); } } return false; } function updateSVG() { let comp = svg; if (showSignature) { comp += sig; } if (showcncPreview) { comp += cncPreview; } document.body.innerHTML = ''; document.body.insertAdjacentHTML('beforeend', comp + "</svg>"); } function randRad(padding) { const radius = R(allRadii[0], (500) - padding); let tooClose = false; for (let i = 0; i < allRadii.length; i++) { if (Math.abs(radius - allRadii[i]) < proxLim) { tooClose = true; proxLim -= 1; break; } } if (!tooClose) { allRadii.push(radius); proxLim = 1000 / 5; return radius; } else { return randRad(padding); } } function selectByProbs(list, randomValue) { const totalWeight = list.reduce(function(acc, item, index) { return acc + (1 / (index + 2)); }, 0); const threshold = totalWeight * randomValue; let currentSum = 0; for (let i = 0; i < list.length; i++) { currentSum += 1 / (i + 2); if (currentSum >= threshold) { return list[i]; } } return list[list.length - 1]; } function compilecncSVG() { let list = []; for (let i = 0; i < 3; i++) { if (cncSVG[i].length > 270) { list.push(cncSVG[i]); } } list.push(cncSVG[3]); return list; } function addPoint(x,y, brushType) { let plotString = ""; const cncString = `<circle cx="${x}" cy="${y}" r="4" />`; for (let i = 0; i < R(5,7); i++) { plotString += concentrics(x, y, (brushType + 1) * (1000 / 24), 1); //1 = randomized center mode } return {plot: plotString, cnc: cncString}; } function getY(x0, y0, x1, y1, x) { const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const y = m * x + b; return y; } function getX(x0, y0, x1, y1, y) { const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const x = (y - b) / m; return x; } function lineWithGaps(x0, y0, x1, y1) { let xT, yT; if(x0 > x1) { xT = x0; yT = y0; x0 = x1; y0 = y1; x1 = xT; y1 = yT; } let xS = x0; let yS = y0; let yE, xE; let str = ""; let lfg = true; while (lfg) { let dS; let dnm = [[60,4,48,12],[80,12,40,8]]; finish == "Brushed" ? dS = 0 : dS = 1; let lSeg = R((1000 / dnm[dS][0]), (1000 / dnm[dS][1])); let gSeg = R((1000 / dnm[dS][2]), (1000 / dnm[dS][3])); xS + lSeg > x1 ? xE = x1 : xE = xS + lSeg; yE = getY(x0, y0, x1, y1, xE); str += `<line x1="${xS}" y1="${yS}" x2="${xE}" y2="${yE}" />` xE + gSeg > x1 ? lfg = false : xS = xE + gSeg; xS = xE + gSeg; yS = getY(x0, y0, x1, y1, xS); } return str; } function addCircle(x,y, cr, brushType) { const r = (brushType + 1) * (1000 / 24); const orbitLocs = R(2,6); let locs; const pad = (1000 / 15) + (brushType + 1) * (1000 / 48); let plotString = ""; let cncString = ""; if (pointMode) { locs = R(2,20); } else { locs = Math.floor((2 * PI * cr) / 10); cncString = `<circle cx="${x}" cy="${y}" r="${cr}" />`; } const angleStep = 2 * PI / locs; for (let i = 0; i < locs; i++) { const angle = i * angleStep; const xC = x + cr * Math.cos(angle); const yC = y + cr * Math.sin(angle); if (pointMode) { const newStrings = addPoint(xC, yC, brushType); plotString += newStrings.plot; cncString += newStrings.cnc; if (orbitMode && cr > 200) { const closestEdgeDist = Math.min(Math.min(Math.abs(1000 - xC), xC), Math.min(Math.abs(1000 - yC), yC)); if (closestEdgeDist > pad + 2 * r) { const newStringsOrbit = addOrbit(xC, yC, 2 * r, brushType, orbitLocs); plotString += newStringsOrbit.plot; cncString += newStringsOrbit.cnc; } } } else { plotString += concentrics(xC, yC, r, 0); } } return {plot: plotString, cnc: cncString}; } function addOrbit(x, y, cr, brushType, locs) { const r = (brushType + 1) * (1000 / 24); let plotString = ""; let cncString = ""; const angleStep = 2 * PI / locs; for (let i = 0; i < locs; i++) { const angle = i * angleStep; const xC = x + cr * Math.cos(angle); const yC = y + cr * Math.sin(angle); const newStrings = addPoint(xC, yC, brushType); plotString += newStrings.plot; cncString += newStrings.cnc; } return {plot: plotString, cnc: cncString}; } function concentrics(x,y,r,f) { r /= 2; let arcs = ""; if (f == 1) { x = x + R(-4, 4); y = y + R(-4, 4); } for (let i = 10; i < r; i += R(r/25, r/8) ) { arcs += randomArcSVG(x,y,i); } return arcs; } function randomArcSVG(x, y, radius) { const getRandomAngle = () => R(0,628) / 100; const startAngle = getRandomAngle(); const endAngle = getRandomAngle(); let sweepAngle = endAngle - startAngle; if (sweepAngle < 0) { sweepAngle += 2 * Math.PI; } const startX = x + radius * Math.cos(startAngle); const startY = y + radius * Math.sin(startAngle); const endX = x + radius * Math.cos(endAngle); const endY = y + radius * Math.sin(endAngle); const largeArcFlag = sweepAngle > Math.PI ? 1 : 0; const sweepFlag = 1; const d = `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${endX} ${endY}`; return `<path d="${d}" />`; } function getClippedLine(x0, y0, x1, y1) { const xmin = R((100 / 24),(1000 / 48)); const ymin = R((100 / 24),(1000 / 48)); const xmax = 1000-R((100 / 24),(1000 / 48)); const ymax = 1000-R((100 / 24),(1000 / 48)); const inside = (x, y) => x >= xmin && x <= xmax && y >= ymin && y <= ymax; if (inside(x0, y0) && inside(x1, y1)) { return { x0, y0, x1, y1 }; } const m = (y1 - y0) / (x1 - x0); const b = y0 - m * x0; const xLeft = xmin; const yLeft = m * xLeft + b; const xRight = xmax; const yRight = m * xRight + b; const yTop = ymin; const xTop = (yTop - b) / m; const yBottom = ymax; const xBottom = (yBottom - b) / m; const intersections = [ { x: xLeft, y: yLeft }, { x: xRight, y: yRight }, { x: xTop, y: yTop }, { x: xBottom, y: yBottom } ]; const validIntersections = intersections.filter(p => inside(p.x, p.y)); if (validIntersections.length >= 2) { return { x0: validIntersections[0].x, y0: validIntersections[0].y, x1: validIntersections[1].x, y1: validIntersections[1].y }; } return null; }
Scratch is a Zen-inspired generative art project that explores the captivating patterns that sanding discs create on metal surfaces. The digital artworks can be pen-plotted at any scale, and they can be recreated on 24" x 24" sheets of metal using CNC machines. Interactivity: 'S' saves the SVG. 'C' previews the CNC paths. 'E' exports the CNC paths. 'H' hides the signature stamp. Plotter Notes: To replicate the density of the digital image, choose a pen for the main area of a Scratch that is 1/1000th the length of the sides of the plottable area. For the signature, a pen that is 2.6 times thicker than the one used for the main area is suggested. For instance, for a 10" square plot, a 0.25mm pen for the main image and a 0.65mm pen for the signature will closely mimic the density of the digital output. While bolder pens can create more visually striking images, denser Scratches plotted with thicker pens might require a heavier paper stock. CNC Notes: Experienced CNC technicians can faithfully reproduce the digital output on 24" x 24" sheets of metal by using standard sanding disc sizes of 1", 2", and 3". The choice of aluminum or copper as a material should be based on the specific characteristics of the Scratch output. If the Scratch output includes the 'Dark' feature, both aluminum and copper sheets can be darkened with common chemicals before the machining process.
**FOCUS** is a 1000 image meditation, and it is my second release of Zen-inspired generative art, following [Ensō](https://www.artblocks.io/project/34). This algorithm can produce images that are elegantly minimal, as well as images featuring complex patterns that explore visual illusions and artificial space. Inspired by the Zen art tradition of doing more with less, the limited palette consists of black, white, and five colors. This both constrains possible outcomes and allows the creation of unique perceptual interactions. Except in extremely rare scenarios, each token can provide a wide variety of visual experiences through its interactive features: use up and down arrows to adjust the pattern recursion factor, left and right arrows to adjust the midpoint count, ‘h’ to hide or show the central shape, and ‘s’ to save. FOCUS is best viewed live and full screen. FOCUS has a free-to-claim, 100% on-chain, companion generative art project called deFOCUSed. It's a free gift to my collectors. (Thank you!) [More Info](https://matto.xyz/project/defocused/) | [Claim Site](https://defocused.matto.xyz/)
**FOCUS** is a 1000 image meditation, and it is my second release of Zen-inspired generative art, following [Ensō](https://www.artblocks.io/project/34). This algorithm can produce images that are elegantly minimal, as well as images featuring complex patterns that explore visual illusions and artificial space. The limited palette consists of black, white, and five colors; this both constrains possible outcomes and allows the creation of unique perceptual interactions. Inspired by the Zen art tradition of doing more with less, FOCUS does not utilize any pseudo-random number generation, and each output can be recreated by hand using simple math, estimation, and dedication. Except in extremely rare scenarios, each token can provide a wide variety of visual experiences through its interactive features: use up and down arrows to adjust the pattern recursion factor, left and right arrows to adjust the midpoint count, ‘h’ to hide or show the central shape, and ‘s’ to save. FOCUS is best viewed live and full screen. *Interactive features: use up or down arrows to adjust the background pattern's recursion, and left or right arrows to adjust the number of midpoints along the sides of the background pattern. Use ‘h’ to hide or show the central shape, and ‘s’ to save an image. To use the token's hash to 'autoplay' the token, use Matto's [FOCUS animator](https://matto.xyz/tools/focus-animator/index.html). *Companion project: deFOCUSed is a free-to-claim, 100% on-chain, generative art project, where every FOCUS token creates a deFOCUSed token. It's a free gift to my collectors. (Thank you!) [Claim Site](https://defocused.matto.xyz/) | [More Info](https://matto.xyz/project/defocused/)
**FOCUS** is a 1000 image meditation, and it is my second release of Zen-inspired generative art, following [Ensō](https://www.artblocks.io/project/34). This algorithm can produce images that are elegantly minimal, as well as images featuring complex patterns that explore visual illusions and artificial space. The limited palette consists of black, white, and five colors; this both constrains possible outcomes and allows the creation of unique perceptual interactions. Inspired by the Zen art tradition of doing more with less, FOCUS does not utilize any pseudo-random number generation, and each output can be recreated by hand using simple math, estimation, and dedication. Except in extremely rare scenarios, each token can provide a wide variety of visual experiences through its interactive features: use up and down arrows to adjust the pattern recursion factor, left and right arrows to adjust the midpoint count, ‘h’ to hide or show the central shape, and ‘s’ to save. FOCUS is best viewed live and full screen. *Interactive features: use up or down arrows to adjust the background pattern's recursion, and left or right arrows to adjust the number of midpoints along the sides of the background pattern. Use ‘h’ to hide or show the central shape, and ‘s’ to save an image. To use the token's hash to 'autoplay' the token, use Matto's [FOCUS animator](https://matto.xyz/tools/focus-animator/index.html). *Companion project: [deFOCUSed](https://matto.xyz/project/defocused/) is a free-to-claim, 100% on-chain, generative art project, where every FOCUS token creates a deFOCUSed token.
**FOCUS** is a 1000 image meditation, and it is my second release of Zen-inspired generative art, following [Ensō](https://www.artblocks.io/project/34). This algorithm can produce images that are elegantly minimal, as well as images featuring complex patterns that explore visual illusions and artificial space. The limited palette consists of black, white, and five colors; this both constrains possible outcomes and allows the creation of unique perceptual interactions. Inspired by the Zen art tradition of doing more with less, FOCUS does not utilize any pseudo-random number generation, and each output can be recreated by hand using simple math, estimation, and dedication. Except in extremely rare scenarios, each token can provide a wide variety of visual experiences through its interactive features: use up and down arrows to adjust the pattern recursion factor, left and right arrows to adjust the midpoint count, ‘h’ to hide or show the central shape, and ‘s’ to save. FOCUS is best viewed live and full screen. *Interactive features: use up or down arrows to adjust the background pattern's recursion, and left or right arrows to adjust the number of midpoints along the sides of the background pattern. Use ‘h’ to hide or show the central shape, and ‘s’ to save an image. To use the token's hash to 'autoplay' the token, use Matto's [FOCUS animator](https://matto.xyz/tools/focus-animator/index.html). *Companion project: [deFOCUSed](https://matto.xyz/project/defocused/) is a free-to-claim, 100% on-chain, generative art project, where every FOCUS creates a deFOCUSed token.
The deFOCUSed algorithm produces tokens that correspond to FOCUS tokens. By using the same random hash as the FOCUS script as well as a new random hash that is generated when a deFOCUSed token is created, some traits match the corresponding FOCUS token, while others are specific to the moment a FOCUS owner deFOCUSes their FOCUS. The project is 100% on-chain, relying only on smart contracts to generate the token data, and the assets are rendered live in your browser into an animated SVG image.
0x983f10b6…313d·#15,210,288·0xe2a4504e…2da16d
a,MWa,La>[V[[a,Xa-FWa-Ea>`V[[a-Ra94Wa93a>"V[[a9=R[PPPPV[a<Ȃa>jV[<g id="shape" style="stroke:<circle cx="500" cy="500" r="191`<?xml version="1.0" encoding="ut`f-8"?><svg viewBox="0 0 px; filter: url(#deFOCUS2); fill`; stroke-width: " xChannelSelector="G" yChannelS`elector="B"><animate attributeNa` me="scale" values=""/><feDisplacementMap in="Source`Graphic" scale="<g id="pattern" style="stroke: <filter id="deFOCUS<path d="M309 309 Q500 191 691 3`09 Q500 427 309 309 Q500 191 691` ><feTurbulence type="fractalNois`e" baseFrequency="" fill-opacity="s" repeatCount="indefinite"/></f`eDisplacementMap><feComposite op` erator="in" in2="finalMask"/></f`@px; filter: url(#deFOCUS1); fill` " xmlns="http://www.w3.org/2000`<path d="M309 309 Q191 500 309 6`91 Q427 500 309 309 Q191 500 309` filterUnits="userSpaceOnUse"
0x983f10b6…313d·#15,210,282·0x4486c0f5…4b8c20
Diagonal ForwardDiagonal BackwardLines To RecurrencePattern Rotation SpeedPattern RecursionBackground ColorPattern VariancePattern Cycle (seconds)Shape Cycle (seconds){"trait_type":"License","value":`"CC BY-NC 4.0"}]
**FOCUS** is a 1000 image meditation, and it is my second release of Zen-inspired generative art, following [Ensō](https://www.artblocks.io/project/34). It’s meant to be played with through its interactivity,* and I hope it challenges viewers through its various portrayals of space and its creation of perceptual phenomena. Additionally, FOCUS can be a meditation in itself for anyone who attempts to recreate an output manually. This project relies on small sets of consistent shapes, colors, and ratios, helping the outputs to remain cohesive. However, with millions of major trait combinations, the outputs are varied and surprising. *Interactive features: use up or down arrows to adjust the background pattern's recursion, and left or right arrows to adjust the number of midpoints along the sides of the background pattern. Use ‘h’ to hide or show the central shape, and ‘s’ to save an image. To use the token's hash to 'autoplay' the token, use Matto's [FOCUS animator](https://matto.xyz/tools/focus-animator/index.html). **FOCUS** is best viewed live and full screen.
FOCUS is an exploration of, and meditation on, the act of directed attention, the fixture of concentration, the state permitting clarity, and the mathematical point of convergence. This algorithm can produce images that are elegantly minimal, as well as images featuring complex patterns that explore visual illusions and artificial space. The limited palette consists of black, white, and five colors; this both constrains possible outcomes and allows the creation of unique perceptual interactions. Inspired by the Zen art tradition of doing more with less, FOCUS does not utilize any pseudo-random number generation, and each output can be recreated by hand using simple math and estimation. Except in extremely rare scenarios, each token can provide a wide variety of visual experiences through its interactive features: use up and down arrows to adjust the pattern recursion factor, left and right arrows to adjust the midpoint count, ‘h’ to hide or show the central shape, and ‘s’ to save. FOCUS is best viewed live and full screen.
FOCUS is an exploration of, and meditation on, the act of directed attention, the fixture of concentration, the state permitting clarity, and the mathematical point of convergence. This algorithm can produce images that are elegantly minimal, as well as images featuring complex patterns that explore visual illusions and artificial space. The limited palette consists of black, white, and five colors; this both constrains possible outcomes and allows the creation of unique perceptual interactions. Inspired by the Zen art tradition of doing more with less, FOCUS does not utilize any pseudo-random number generation, and each output can be recreated by hand using simple math and estimation. Except in extremely rare scenarios, each token can provide a wide variety of visual experiences through its interactive features: use up and down arrows to adjust the pattern recursion factor, left and right arrows to adjust the midpoint count, ‘h’ to hide or show the central shape, and ‘s’ to save. FOCUS is best viewed live and full screen. (FOCUS will conduct a primary sale by a Dutch auction with the sale price decreasing every five minutes until the base price of 0.12Ξ is reached. Price tiers: 2.64 | 2.04 | 1.52 | 1.08 | 0.72 | 0.44 | 0.24 | 0.12. 25% of proceeds above the base price will be donated to the Stand with Asian Americans Fund, Teach Peace of Mind, North Carolina Arts in Action, and the Durham Arts Council. Visit the artist’s website for additional information.)
drawStyle(allPattP, pattI, true); let cMem = col[2]; if (showShape == true) { if (shapeI == 1) { noFill(); } else { fill(col[3]); } if (shape == 1) { rect(hW, hH, cW * def, cH * def); } if (shape == 4) { rect(hW, hH, tEC, cH * def); } if (shape == 7) { rect(hW, hH, cW * def, wEC); } if (shape == 2 || shape == 5 || shape == 8) { quad(hW, hCalc, cW - wCalc, hH, hW, cH - hCalc, wCalc, hH); } if (shape == 3) { circle(hW, hH, cW * def); } if (shape == 6) { if (shapeI != 1) { strokeWeight(u * 2); stroke(col[3]); line(hW, hCalc + 2 * u, hW, cH - hCalc - 2 * u); strokeWeight(u * 0.8); stroke(col[1]); } dTE(0); } if (shape == 9) { if (shapeI != 1) { strokeWeight(u * 2); stroke(col[3]); line(wCalc + 2 * u, hH, cW - wCalc - 2 * u, hH); strokeWeight(u * 0.8); stroke(col[1]); } dWE(0); } if (shapeI == 5) { drawStyle(allSP, 4, false); } if (rS == 2) { if (col[0] != col[3]) { col[2] = col[0]; } } if (shapeI == 1 || shapeI == 3 || rS == 2) { if (shapeI !=1) { noStroke(); } else { noFill(); } if (shape != 3 && shape != 6 && shape != 9) { for (let i = 1; i < allSP.length; i++) { if (shapeI != 1) { fill(col[3 - (i % 2)]); } quad(allSP[i][0][0][0], allSP[i][0][0][1], allSP[i][1][0][0], allSP[i][1][0][1], allSP[i][2][0][0], allSP[i][2][0][1], allSP[i][3][0][0], allSP[i][3][0][1]); } } else if (shape == 3) { let rr = cW * def; for (let i = 1; i < rS; i++) { rr = rr - rr / 4; if (shapeI != 1) { fill(col[3 - (i % 2)]); } circle(hW, hH, rr); } } else { let fdist = Math.max(Math.abs(allSP[0][0][0][0] - allSP[0][1][0][0]), Math.abs(allSP[0][0][0][1] - allSP[0][1][0][1])) / 4; rr = 0; let fMem; for (let i = 1; i < rS; i++) { rr = rr - fdist; fMem = col[3 - (i % 2)]; fill(fMem); strokeWeight(u * 2); stroke(fMem); if (shape == 6) { line(hW, hCalc + 2 * u, hW, cH - hCalc - 2 * u); strokeWeight(u * 0.8); stroke(col[3]); dTE(rr); } if (shape == 9) { line(wCalc + 2 * u, hH, cW - wCalc - 2 * u, hH); strokeWeight(u * 0.8); stroke(col[3]); dWE(rr); } } stroke(col[1]); noFill(); if (shape == 9) { dWE(0); } if (shape == 6) { dTE(0); } } } function dTE(v) { curve(cW + wEC + v, 0, hW, hCalc, hW, cH - hCalc, cW + wEC + v, cH); curve(-wEC - v, 0, hW, hCalc, hW, cH - hCalc, -wEC - v, cH); } function dWE(v) { curve(0, cH + tEC + v, wCalc, hH, cW - wCalc, hH, cW, cH + tEC + v); curve(0, - tEC - v, wCalc, hH, cW - wCalc, hH, cW, - tEC - v); } } function drawStyle(allP, m, patt) { strokeJoin(BEVEL); fill(col[2]); let dir; for (let i = 0; i < allP.length; i++) { let ls; if (ar == 2) { ls = 1; } else { ls = 0; } let size = Math.max((Math.abs(allP[i][ls][0][0] - allP[i][ls][allP[i][ls].length - 1][0])), Math.abs((allP[i][ls][0][1] - allP[i][ls][allP[i][ls].length - 1][1]))) / 110; if (patt == true && m < 4 ) { fill(col[0]); rect(hW, hH, allP[i][0][allP[i][0].length - 1][0] - allP[i][0][0][0], allP[i][1][allP[i][1].length - 1][1] - allP[i][1][0][1]); dir = getBin(); fill(col[2]); } for (let j = 0; j < allP[i].length; j++) { if (patt == true && pattB == 1) { line(allP[i][j][0][0], allP[i][j][0][1], allP[i][j][allP[i][j].length -1][0], allP[i][j][allP[i][j].length - 1][1]); } for (let k = 0; k < allP[i][j].length; k++) { let aPx = allP[i][j][k][0]; let aPy = allP[i][j][k][1]; if (m == 1 && k + 1 < allP[i][j].length && (j < 2)) { line(aPx, aPy, allP[i][j + 2][allP[i][j].length - k - 2][0], allP[i][j + 2][allP[i][j].length - k - 2][1]); } if (m == 2 && j == dir && k + 1 < allP[i][j].length) { if (dir == 0) { line(aPx, aPy, allP[i][j + 2][allP[i][j].length - k - 2][0], allP[i][j + 2][allP[i][j].length - k - 2][1]); } else { line(aPx, aPy, allP[i][j + 2][allP[i][j].length - k - 2][0], allP[i][j + 2][allP[i][j].length - k - 2][1]); } } if (m == 3) { let len, leg2; let p1 = [allP[i][j][0][0], allP[i][j][0][1]]; let p2 = [allP[i][j][allP[i][j].length - 1][0], allP[i][j][allP[i][j].length - 1][1]]; let md = j % 2; let leg = Math.min(Math.abs(allP[i][j][k][md] - p1[md]), Math.abs(p2[md] - allP[i][j][k][md])); if (j == 0) { leg2 = leg / arm; len = Math.min(aPy + leg2, hH); line(aPx, aPy, aPx, len); } else if (j == 1) { leg2 = leg * arm; len = Math.max(aPx - leg2, hW); line(aPx, aPy, len, aPy); } else if (j == 2) { leg2 = leg / arm; len = Math.max(aPy - leg2, hH); line(aPx, aPy, aPx, len); } else if (j == 3) { leg2 = leg * arm; len = Math.min(aPx + leg2, hW); line(aPx, aPy, len, aPy); } } if (m == 4 && i + 1 < allP.length) { line(aPx, aPy, allP[i+1][j][k][0], allP[i+1][j][k][1]); } if (m == 5 && i + 1 < allP.length && k + 2 < allP[i][j].length) { line(aPx, aPy, allP[i+1][j][k+1][0], allP[i+1][j][k+1][1]); } if (m == 6 && i + 1 < allP.length && getBin() == 1) { line(aPx, aPy, allP[i + 1][j][k][0], allP[i + 1][j][k][1]); } if (m == 7 && i + 1 < allP.length && k + 2 < allP[i][j].length && (k % 2 == 0)) { quad(aPx, aPy, allP[i][j][k+1][0], allP[i][j][k+1][1], allP[i+1][j][k+1][0], allP[i+1][j][k+1][1], allP[i+1][j][k][0], allP[i+1][j][k][1]); } if (m == 8 && i + 1 < allP.length && k + 2 < allP[i][j].length && (k % 2 == i % 2)) { quad(aPx, aPy, allP[i][j][k+1][0], allP[i][j][k+1][1], allP[i+1][j][k+1][0], allP[i+1][j][k+1][1], allP[i+1][j][k][0], allP[i+1][j][k][1]); } if (m == 9 && i + 1 < allP.length && k + 2 < allP[i][j].length && (k % 2 == i % 2) && getBin() == 1) { quad(aPx, aPy, allP[i][j][k+1][0], allP[i][j][k+1][1], allP[i+1][j][k+1][0], allP[i+1][j][k+1][1], allP[i+1][j][k][0], allP[i+1][j][k][1]); } if (m == 10 && i + 1 < allP.length && k + 2 < allP[i][j].length) { if (tkB == 0) { quad(aPx, aPy, allP[i][j][k+1][0], allP[i][j][k+1][1], allP[i+1][j][k][0], allP[i+1][j][k][1], aPx, aPy); } else { quad(aPx, aPy, allP[i][j][k+1][0], allP[i][j][k+1][1], allP[i+1][j][k+1][0], allP[i+1][j][k+1][1], aPx, aPy); } } if (patt == true) { noFill(); if (pattB == 3) { let fS = size; if (u * 0.8 > size) { noStroke(); fill(col[1]); fS = size + 0.8 * u; } circle(aPx, aPy, fS); } if (pattB == 4) { rect(aPx, aPy, size); } if (pattB == 2 && k % 2 == 1 && k + 1 < allP[i][j].length) { line(aPx, aPy, allP[i][j][k + 1][0], allP[i][j][k + 1][1]); } stroke(col[1]); fill(col[2]); } } } } } if (isS == "Portal") { stroke(col[1]); let bW, bSY; if (shape == 1 || shape == 4 || shape == 7) { bW = cW * def + 2*u; bSY = allSP[0][2][0][1]; } else if (shape == 3) { bW = 40 * u; bSY = hW + (cW * def /2); } else if (shape == 9) { bW = 15 * u; bSY = allSP[0][1][(allSP[0][1].length - 1)/2][1]; } else if (shape == 6) { bW = 2*u; bSY = allSP[0][1][0][1]; } else { bW = Math.abs(allSP[0][0][0][0] - allSP[0][2][0][0]) / 10; bSY = allSP[0][3][0][1]; } let space = 4 * u; let bX1 = (hW) - (bW/2); let bX2 = (hW) + (bW/2); let yG = (cH - bSY); xD = bX1/(yG/space); line(bX1, bSY + space, bX2, bSY + space); for (let c = 2; c < yG / space; c++) { bX1 = bX1 - xD; bX2 = bX2 + xD; let xD1 = getBin() * space/2 * ((getBin() * 2) - 1); let xD2 = getBin() * space/2 * ((getBin() * 2) - 1); line(bX1 - xD1, bSY + c * space, bX2 + xD2, bSY + c * space); } } col[2] = cMem; noLoop(); }
function draw() { background(col[0]); strokeWeight(u * 0.8); stroke(col[1]); let m = 2 * u; let tEC = cH * def * gR; let wEC = cW * def * gR; let hH = cH / 2; let hW = cW / 2; pattSides = [ [[m, m], [cW - m, m]], [[cW - m, m], [cW - m, cH - m]], [[cW - m, cH - m], [m, cH - m]], [[m, cH - m], [m, m]] ]; let recFP = [ [[m, m], [cW - m, m]], [[cW - m, m], [cW - m, cH - m]], [[cW - m, cH - m], [m, cH - m]], [[m, cH - m], [m, m]] ]; let cenP = [ [[hW, hH], [hW, hH]], [[hW, hH], [hW, hH]], [[hW, hH], [hW, hH]], [[hW, hH], [hW, hH]], ]; allPattP.push(buildQuad(pattSides)); if (rP == 1) { allPattP.push(buildQuad(cenP)); } for (let i = 1; i < rP; i++) { rectRecurser(recFP, cW / 1.3, cH / 1.3, rP); allPattP.push(buildQuad(recFP)); } let shapeSides = []; let wCalc = (cW - cW * def) / 2; let hCalc = (cH - cH * def) / 2; if (shape == 1 || shape == 4 || shape == 7) { shapeSides = [ [[wCalc, hCalc], [cW - wCalc, hCalc]], [[cW - wCalc, hCalc], [cW - wCalc, cH - hCalc]], [[cW - wCalc, cH - hCalc], [wCalc, cH - hCalc]], [[wCalc, cH - hCalc], [wCalc, hCalc]] ]; } if (shape == 2 || shape == 5 || shape == 8) { shapeSides = [ [[wCalc, hH], [hW, hCalc]], [[hW, hCalc], [cW - wCalc, hH]], [[cW - wCalc, hH], [hW, cH - hCalc]], [[hW, cH - hCalc], [wCalc, hH]] ]; } if (shape != 3 && shape != 6 && shape != 9) { for (let i = 1; i <= rS; i++) { allSP.push(buildQuad(shapeSides)); if (shape == 1 || shape == 4 || shape == 7) { rectRecurser(shapeSides, cW / 1.66, cH / 1.66, rS); } if (shape == 2 || shape == 5 || shape == 8) { diamRecurser(shapeSides, cW / 1.66, rS); } } } if (shape == 3) { let cP = (midPs + 1) * 4; let radi = cW * def /2; for (let i = 1; i <= rS; i++) { let points = []; points.push(circumP(cP,radi)); allSP.push(points); radi = radi - radi / 4; } } function circumP(cP, radi) { let ps = []; for (let i = 0; i < cP; i++) { let x = (radi * Math.cos((2 * Math.PI * i) / cP)) + hW; let y = (radi * Math.sin((2 * Math.PI * i) / cP)) + hH; ps.push([x, y]); } return ps; } if (shape == 6 || shape == 9) { let cP = midPs + 1; let side1 = []; let side2 = []; for (let i = 0; i <= cP; i++) { let t = i / cP; if (shape == 6) { side1.push(getCurvePoints(cW + wEC, 0, hW, hCalc, hW, cH - hCalc, cW + wEC, cH, t)); side2.push(getCurvePoints(-wEC, cH, hW, cH - hCalc, hW, hCalc, -wEC, 0, t)); } else { side1.push(getCurvePoints(0, cH + tEC, wCalc, hH, cW - wCalc, hH, cW, cH + tEC, t)); side2.push(getCurvePoints(cW, - tEC, cW - wCalc, hH, wCalc, hH, 0, - tEC, t)); } function getCurvePoints(c1x, c1y, x1, y1, x2, y2, c2x, c2y, t) { x = curvePoint(c1x, x1, x2, c2x, t); y = curvePoint(c1y, y1, y2, c2y, t); let p = [x, y]; return p; } } allSP.push([side1, side2]); for (let i = 1; i < rS; i++) { let lastSet = allSP.length -1; let newSet = []; for (let crv = 0; crv <2; crv ++){ let newPs = []; for (let v = 0; v < allSP[lastSet][crv].length; v++) { let oldP = allSP[lastSet][crv][v]; let newX, newY; if (shape == 6) { newY = ((oldP[1] - hH)) * .88 + hH; newX = oldP[0]; } else { newX = ((oldP[0] - hW)) * .88 + hW; newY = oldP[1]; } newPs.push([newX,newY]); } newSet.push(newPs); } allSP.push(newSet); } } function rectRecurser(ss, xLim, yLim, r) { let xD = (xLim - ss[0][0][0]) / r; let yD = (yLim - ss[0][0][1]) / r; ss[0][0][0] += xD; ss[0][0][1] += yD; ss[0][1][0] -= xD; ss[0][1][1] += yD; ss[1][0][0] -= xD; ss[1][0][1] += yD; ss[1][1][0] -= xD; ss[1][1][1] -= yD; ss[2][0][0] -= xD; ss[2][0][1] -= yD; ss[2][1][0] += xD; ss[2][1][1] -= yD; ss[3][0][0] += xD; ss[3][0][1] -= yD; ss[3][1][0] += xD; ss[3][1][1] += yD; } function diamRecurser(ss, xLim, r) { let xD = (xLim - ss[0][0][0])/r; ss[0][0][0] += xD; ss[0][1][1] += xD; ss[1][0][1] += xD; ss[1][1][0] -= xD; ss[2][0][0] -= xD; ss[2][1][1] -= xD; ss[3][0][1] -= xD; ss[3][1][0] += xD; } function buildQuad(qSides) { rectMode(CENTER); let allP = []; let sideP = []; for (let side = 0; side < 4; side++) { sideP = lineBreaker(qSides[side][0], qSides[side][1]); allP.push(sideP); } return allP; function lineBreaker(p1, p2) { let lineP = [[...p1]]; for (let i = 1; i < midPs + 2; i++) { lineP.push(getPoint(p1, p2, i / (midPs + 1))); } lineP.push([...p2]); return lineP; function getPoint(p1, p2, t) { let newX = t * (p2[0] - p1[0]) + p1[0]; let newY = t * (p2[1] - p1[1]) + p1[1]; let point = [newX, newY]; return point; } } }
// Find a non-minified and better commented version of this script online at the artist's website. Copyright Matthew Costanza 2021 let hV, hb, binL, tkB, shape, shapeI, sRef, sIRef, pattB, pBRef, pattI, pIRef, isS, midPs, recP, recS, pal, ar, cW, cH, minD, s, col, arm, w, b, a, v, g, r, p; let binP = 0; let gR = 0.618; let def = 0.382; let allPattP = []; let allSP = []; let showShape = true; calculateFeatures(tokenData); setDim(); function calculateFeatures(tD) { tkB = tD.tokenId % 2; hV = cHVal(tD.hash); hb = cHBin(hV); binL = hb.length; w = "#ffffff"; b = "#000000"; a = "#275bb2"; v = "#43AA8B"; g = "#fcd612"; r = "#b10b0b"; p = "#f368cb"; shape = gV(1, 9); if (shape < 4) { ar = 1; } else if (shape < 7) { ar = 2; } else { ar = 3; } shapeI = gV(1, 5); pattB = gV(1, 100); if (pattB > 62) { pattB = 1; } else if (pattB > 42) { pattB = 2; } else if (pattB > 22) { pattB = 3; } else if (pattB > 2) { pattB = 4; } else { pattB = 5; } pattI = gV(1, 11); if (pattI < 4 && pattB == 2) { pattB = 1; } midPs = gV(1,6); midPs = 2 ** midPs - 1; if (midPs == 1 && (pattB == 2 || pattI == 7 || pattI == 8)) { midPs = 3; } if (midPs < 5 && pattI == 9) { midPs = 5; } recP = gV(0, 5); rP = 2 ** recP; recS = gV(0, 3); if ((shape == 6 || shape == 9) && shapeI == 1) { rS = 1; } else if (shapeI == 4) { rS = 2; } else if ((shape == 6 || shape == 9) && shapeI == 3) { rS = Math.min((3 ** recS), 5); } else { rS = 3 ** recS; } pal = gV(0, 19); let cols = [ [w, b, v, w], [w, b, a, w], [w, b, r, w], [w, b, p, w], [w, b, g, w], [w, b, r, g], [w, b, a, g], [w, b, v, g], [b, w, v, g], [b, w, a, g], [b, g, w, g], [b, w, b, w], [a, r, w, g], [a, g, w, g], [a, w, v, w], [a, w, b, w], [r, w, b, g], [r, w, a, w], [v, w, a, w], [g, b, w, b] ]; col = cols[pal]; if (shapeI == 5 && col[1] == col[3]) { shapeI = 2; } if (rS == 1 && (shapeI == 3 || shapeI == 5)) { shapeI = 2; } if (shapeI == 2 && rS > 1) { rS = 1; } if (pattB == 5 && pattI == 11) { if (shapeI == 2 || shapeI == 1) { isS = "Portal"; } else { isS = "Truth"; } } else { isS = "Yes"; } function cHVal(TH) { let h = []; for (let i = 0; i < (TH.length - 2) / 2; i++) { h.push(parseInt(TH.slice(2 + i * 2, 4 + i * 2), 16)); } return h; } function cHBin(H) { let b = ''; for (let i = 0; i < H.length - 1; i++) { let t = H[i].toString(2); b = b + t; } return b; } function gV(min, max) { max = max + 0.999; let x = Math.floor((hV[0] * (max - min)) / 255 + min); hV.shift(); return x; } } function setDim() { binP = 0; let winW = window.innerWidth; let winH = window.innerHeight; minD = Math.min(winW, winH); if (ar == 1) { arm = 1; cW = minD; cH = minD; } else if (ar == 2) { arm = gR; if (winW / winH >= gR) { cH = winH; cW = Math.floor(gR * cH); } else { cW = winW; cH = Math.floor(cW / gR); } } else if (ar == 3) { arm = 1 / gR; if (winH / winW >= gR) { cW = winW; cH = Math.floor(gR * cW); } else { cH = winH; cW = Math.floor(cH / gR); } } u = (Math.max(cW, cH)) / 300; } function windowResized() { allPattP = []; allSP = []; setDim() resizeCanvas(cW, cH); } function getBin() { let b = hb[binP]; binP = (binP + 1 != binL) ? binP + 1 : 0; return b; } function keyPressed() { if (key === 's') { saveCanvas('FOCUS-' + `${tokenData.tokenId}` + '_hash-' + `${tokenData.hash}` + '_RecursionFactor-' + `${rP}` + '_midpoints-' + `${midPs}` + '_shape-' + `${showShape}`, 'png') } else if (key === 'h') { if (showShape == true) { showShape = false; } else { showShape = true; } windowResized() } else if (keyCode === RIGHT_ARROW) { midPs +=2; windowResized(); } else if (keyCode === LEFT_ARROW) { midPs > 1 ? midPs -= 2 : midPs = 1; windowResized(); } else if (keyCode === UP_ARROW) { rP += 1; windowResized(); } else if (keyCode === DOWN_ARROW) { rP > 1 ? rP -= 1 : rP = 1; windowResized(); } } function setup() { createCanvas(cW, cH); angleMode(DEGREES); }
FOCUS is an exploration of, and meditation on, the act of directed attention, the fixture of concentration, the state permitting clarity, and the mathematical point of convergence. This algorithm can produce images that are elegantly minimal, as well as images featuring complex patterns that explore visual illusions and artificial space. The limited palette consists of black, white, and five colors; this both constrains possible outcomes and allows the creation of unique perceptual interactions. Inspired by the Zen art tradition of doing more with less, FOCUS does not utilize any pseudo-random number generation, and each output can be recreated by hand using simple math and estimation. Except in extremely rare scenarios, each token can provide a wide variety of visual experiences through its interactive features: use up and down arrows to adjust the pattern recursion factor, left and right arrows to adjust the midpoint count, ‘h’ to hide or show the central shape, and ‘s’ to save. FOCUS is best viewed live and full screen.
FOCUS is an exploration of, and meditation on, the act of directed attention, the fixture of concentration, the state permitting clarity, and the mathematical point of convergence. This algorithm can produce images that are elegantly minimal, as well as images featuring complex patterns that explore visual illusions and artificial space. The limited palette consists of black, white, and five colors; this both constrains possible outcomes and allows the creation of unique perceptual interactions. Inspired by the Zen art tradition of doing more with less, FOCUS does not utilize any pseudo-random number generation, and each output can be recreated by hand using simple math and estimation. Except in extremely rare scenarios, each token can provide a wide variety of visual experiences through its interactive features: use up and down arrows to adjust the pattern recursion factor, left and right arrows to adjust the midpoint count, ‘h’ to hide or show the central shape, and ‘s’ to save. FOCUS is best viewed live and full screen. (Drop mechanics: Dutch auction with price decreasing every five minutes until the base price of 0.12Ξ is reached. Price tiers: 2.64 | 2.04 | 1.52 | 1.08 | 0.72 | 0.44 | 0.24 | 0.12. 25% of proceeds above the base price will be donated to charity as described on the artist's website.)
1-50 of 51