Skip to content

Instantly share code, notes, and snippets.

@sterlingcrispin
Last active March 18, 2023 15:58
Show Gist options
  • Save sterlingcrispin/ca508f444fc6ac525035a24c180b6e5a to your computer and use it in GitHub Desktop.
Save sterlingcrispin/ca508f444fc6ac525035a24c180b6e5a to your computer and use it in GitHub Desktop.
ArtBlocks helpful files for artists
if you need to minifiy an HLSL shader
you can use this website
https://ctrl-alt-test.fr/minifier/?main
// this compresses your script into a tiny blob of code
// helpful for uploading to test/mainnet
const { minify } = require('terser')
const fs = require('fs')
const config = {
compress: {
dead_code: true,
drop_console: true,
drop_debugger: true,
keep_classnames: false,
keep_fargs: false,
keep_fnames: false,
keep_infinity: false,
passes: 5,
},
mangle: {
eval: true,
properties: {
// these are all words that shouldn't be mangled
// it's most of the p5js reserved words and some other things
// there might be some repeats, but it doesn't matter
// if you have special words that must not be mangled, add them here
reserved: [
'stroke', 'strokeWeight', 'noStroke', 'length', 'fill', 'noFill', 'background', 'colorMode', 'color', 'random', 'randomGaussian', 'randomSeed', 'noise', 'noiseSeed', 'noiseDetail', 'noisePerlin', 'noiseSimplex', 'noiseOctaves', 'noiseFrequency',
'color','alpha','red','green','blue','hue','saturation','brightness','lerpColor','lerp','background','clear','colorMode','fill','noFill','noStroke','stroke','strokeWeight','blendMode','createCanvas','createGraphics','resizeCanvas','noCanvas','image','imageMode','tint','noTint','blend','copy','filter','get','loadImage','loadPixels','set','updatePixels','pixels','dither','noDither','pixelDensity','displayDensity','width','height','windowWidth','windowHeight','displayWidth','displayHeight','frameCount','frameRate','focused','cursor','noCursor','displayWidth','displayHeight','bezier','bezierDetail','bezierPoint','bezierTangent','curve','curveDetail','curvePoint','curveTangent','curveTightness','line','point','quad','triangle','arc','ellipse','ellipseMode','line','lineCap','lineJoin','lineWidth','point','pointLight','quad','rect','rectMode','square','triangle','beginContour','beginShape','bezierVertex','curveVertex','endContour','endShape','quadraticVertex','vertex','box','cone','cylinder','ellipsoid','noSmooth','plane','sphere','smooth','torus','noStroke','stroke','strokeCap','strokeJoin','strokeWeight','beginCamera','camera','endCamera','perspective','printCamera','ortho','frustum','rotate','rotateX','rotateY','rotateZ','scale','shearX','shearY','translate','applyMatrix','popMatrix','printMatrix','pushMatrix','resetMatrix','rotate','PI','HALF_PI','QUARTER_PI','TAU','TWO_PI','DEGREES','RADIANS','angleMode','angle','cos','degrees','radians','sin','tan','acos','asin','atan','atan2','exp','log','mag','map','max','min','norm','pow','round','sq','sqrt','abs','ceil','constrain','dist','exp','floor','lerp','log','circle','setUniform','substr','parseInt','min','max','floor','push','pop','createVector','random','filter','stroke','strokeWeight','sqrt','push','pow','reverse','concat','addPoints','color','filter','windowResized','setup','draw','mouseClicked','mouseDragged'
],
},
keep_classnames: false,
keep_fnames: /^(window\.|setup|width|draw|height|pixelDensity|color|alpha|windowResized)$/,
toplevel: true,
},
module: false,
sourceMap: false,
}
const code = fs.readFileSync('./YOUR_SCRIPT_NAME.js', 'utf8')
minify(code, config).then((minified) => {
fs.writeFileSync('./miniscript.js', minified.code)
})
// this will output the hash of the token being generated when the page loads
// its equivalent to minting a new token each time you refresh the page
console.log("tokenData.hash", tokenData.hash);
// you can also set the hash manually
// so the image is static and you can focus on debugging one image
// like this
// tokenData.hash = "0x6ef5e336dd31d6bbd322a19687a9eb2d8e1d2bfd71dfb44f5a845117539a15d0"
// this random number generator is from somewhere in the documentation
class Random {
constructor() {
this._useA = 0;
let sfc32 = function (uint128Hex) {
let a = parseInt(uint128Hex.substr(0, 8), 16);
let b = parseInt(uint128Hex.substr(8, 8), 16);
let c = parseInt(uint128Hex.substr(16, 8), 16);
let d = parseInt(uint128Hex.substr(24, 8), 16);
return function () {
a |= 0;
b |= 0;
c |= 0;
d |= 0;
let t = (((a + b) | 0) + d) | 0;
d = (d + 1) | 0;
a = b ^ (b >>> 9);
b = (c + (c << 3)) | 0;
c = (c << 21) | (c >>> 11);
c = (c + t) | 0;
return (t >>> 0) / 4294967296;
};
};
// seed prngA with first half of tokenData.hash
this._prngA = new sfc32(tokenData.hash.substr(2, 32));
// seed prngB with second half of tokenData.hash
this._prngB = new sfc32(tokenData.hash.substr(34, 32));
for (let i = 0; i < 1e6; i += 2) {
this._prngA();
this._prngB();
}
}
// random number between 0 (inclusive) and 1 (exclusive)
_random_dec() {
this._useA = !this._useA;
return this._useA ? this._prngA() : this._prngB();
}
// random number between a (inclusive) and b (exclusive)
_random_num(a, b) {
return a + (b - a) * this._random_dec();
}
// random integer between a (inclusive) and b (inclusive)
// requires a < b for proper probability distribution
_random_int(a, b) {
return Math.floor(this._random_num(a, b + 1));
}
// random boolean with p as percent liklihood of 1
_random_bool(p) {
return this._random_dec() < p;
}
}
let R = new Random();
// Grab the first 16 values of the hash to use as a noise seed.
const seed = parseInt(tokenData.hash.slice(0, 16), 16);
console.log("seed", seed);
// below are are my random functions
// rndPool is a pool of random numbers
// because if you use R._random_dec() directly, the order you call it matters a lot
// by that I mean, suppose you're calling it 3 times during your setup function
// to determine the color of your background, the color of your foreground, and the size of your shape
// then later on you decide to change the order of those calls
// the results will be different even if you're using the same tokenData.hash
// using a pool of numbers like this also makes your features script extremely simple
// because 99% of the features script will just be copy pasting this code
var rndPool = [];
const rndPoolSize = 100;
for (var i = 0; i < rndPoolSize; i++) {
rndPool.push(R._random_dec());
}
let getRndStreaming = (range) => {
return R._random_dec() * range;
};
let getRndIntPoolIdxMinMax = (idx, min, max) => {
var rng = Math.floor(rndPool[idx] * (max-min+1) + min);
return Math.min(Math.max(rng, min), max);
};
// so you'd use this like
var featureGenerateTree = getRndIntPoolIdxMinMax(11, 0, 100) < 65;
var featureBigSphere = getRndIntPoolIdxMinMax(12, 0, 100) < 65;
var featureBigCube = getRndIntPoolIdxMinMax(13, 0, 100) < 22;
var featureWhatever = getRndIntPoolIdxMinMax(14, 0, 100) < 11;
// then your separate features.js script would be really simple like,
// all of the above code plus this
// var traits = {};
// traits.generateTree = featureGenerateTree;
// traits.bigSphere = featureBigSphere;
// traits.bigCube = featureBigCube;
// traits.whatever = featureWhatever;
// you could use the variables later in your regular program like
if (traits.generateTree) {
// do something
}
// if you need another random number you can just call getRndIntPoolIdxMinMax again somewhere else in your code
// I really recommend calling things like this
// after calling any trig functions like sin, cos, tan, etc
// because they can create tiny floating point errors
// between browsers , generating different images on different browsers
// ie:
// var value = Math.sqrt(Math.sin(0.5) * Math.cos(0.5));
// value = FloatLessDetailThousand(value);
let FloatLessDetailThousand =(f)=>{
return Math.floor(f*1000)/1000;
}
let floatLessDetailHundred = (f) => {
return Math.floor(f * 100) / 100;
}
let floatLessDetailTen = (f) => {
return Math.floor(f * 10) / 10;
}
const tinyNumber = .000000001;
// also doing floating point comparison like
// if (a == b) { ... }
// is a bad idea because of floating point errors between browsers
// so instead do something like
// if (Math.abs(a - b) < tinyNumber) { ... }
// or floor it but that's not as good
// if (Math.floor(a) == Math.floor(b)) { ... }
// you'll probably want to do something here
// so your image responds to the size of the browser window
function windowResized() {
}
// if you're using p5.js
// I would
// - calculate the position of everything assuming that the width of the canvas is a fixed 1000 units wide, or 0 to 1, or pick your own coordinates, and do all your math at that fixed size, ideally you only need to do all this once when the page first loads
// - as the user resizes the window, windowResized() is called
// - then get the new window size
// - resize your canvas to the new size
// - redraw your image and scale your precalculated positions to the new canvas size
-------
Make sure you use this starter template, it will make your life a lot easier.
Everything in your project needs to be in a single javascript file as shown in example.js
You can not load in external assets like .Obj files
https://github.com/ArtBlocks/artblocks-starter-template
-------
// this generates thumbnails similar to the static images rendered by AB
const puppeteer = require('puppeteer');
let date_ob = new Date();
var start=Date.now()
var hours = date_ob.getHours();
var minutes = date_ob.getMinutes();
var seconds = date_ob.getSeconds();
// you can change this to a higher number if you want to generate screenshots of multiple tokens at once, best to keep it below 10 at a time
var limit = 1;
var idx = 0;
for (var i = 0; i < limit; i++) {
(async () => {
const browser = await puppeteer.launch(
{
dumpio: true,
headless: 'chrome',
defaultViewport: {
width: 2800,
height: 2800
},
// this works for my M1 macbook pro
args: ['--no-sandbox', '--disable-setuid-sandbox', '--use-angle=metal']
// depending on your machine you might use this instead
// args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--use-gl=egl']
}
);
const page = await browser.newPage();
page
.on('console', message =>
console.log(`${message.type().substr(0, 3).toUpperCase()} ${message.text()}`))
.on('pageerror', ({ message }) => console.log(message))
.on('response', response =>
console.log(`${response.status()} ${response.url()}`))
.on('requestfailed', request =>
console.log(`${request.failure().errorText} ${request.url()}`))
await page.goto('http://localhost:3000/YOUR_SCRIPT_NAME', { waitUntil: 'load', timeout: 0 });
console.log(`SCREENSHOT START seconds elapsed = ${(Date.now() - start)}`);
idx +=1;
await page.screenshot({
path: 'screenshot_' + hours + '_' + minutes + "_" + seconds + "_" + idx + '.png'
});
await browser.close();
console.log(`SCREENSHOT END MS elapsed = ${(Date.now() - start)}`);
})();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment