0x983f10b6…313dsent to0xa319c382…7f9c·#17,300,497·view on Etherscan
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;
}