SMOLSKULLs 💀

smolskulls in turtle
smolskulls.xyz

Log in to post a comment.

/**
 * SMOLSKULLs is a generative art NFT series minted on the Tezos blockchain 
 * and originated on fxhash.xyz by Mark Knol. <https://smolskulls.xyz>
 * 
 * SMOLSKULLs are released under the NFT License 2.0. 
 * https://www.nftlicense.org/
 * TLDR; You need to ask permission to reuse. Please DM @mknol on Twitter.
 */
const turtle = new Turtle();
//const turtle = new Slowpoke();

function getFaceProps() {
    let faceProps;
    while(!faceProps || (faceProps.eyeHeight == 3 && faceProps.mouthSize == 2)) {
    	faceProps = {
    		eyeHeight: pick([0, 1, 2, 3]),
    		mouthType: pick([0, 1, 2]),
    		mouthSize: pick([0, 1, 2, 3]),
    		eyeDirection: pick([0, 1, 2, 3]),
    		eyeDirectionOnly: fxrand() > 0.1,
    	};
    }
    return faceProps
}
let defaultFaceProps = getFaceProps()

// make similar
let makeSimilar = 0; // min=0, max=1, step=1 (false, true)
let grid = 5; // min=1, max=23, step=2
let skullScale = 0.99; // min=0.5, max=1.5, step=0.01
let doFillRect = 1; // min=0, max=1, step=1 (false, true)

const size = 200;
let scale = size / grid;

function fillRect(shape, intensity = 0.2) {
	if (shape[1][0] - shape[0][0] > shape[2][1] - shape[0][1]) {
    	for(let x=shape[0][0];x<shape[1][0];x+=intensity) {
    	    turtle.jump(x,shape[0][1]);
    	    turtle.goto(x,shape[2][1]);
    	}
	} else {
	    for(let y=shape[0][1];y<shape[2][1];y+=intensity) {
    	    turtle.jump(shape[0][0],y);
    	    turtle.goto(shape[1][0],y);
    	}
	}
}

function walk(i) {
	const x = (i % grid) - ((grid / 2) | 0);
	const y = ((i / grid) | 0) - ((grid / 2) | 0);

	let xr = (i % grid) / grid;
	let yr = ((i / grid) | 0) / grid;

	let shapes = createSkull(x, y, skullScale);
	let shapeIdx = 0;
	for (let shape of shapes) {
    	shape = shape.map(p1 => posToPx(scl2(p1, scale)));
		shape.forEach((p1, idx, pts) => {
			if (idx == 0) {
				turtle.jump(p1[0], p1[1]);
			} else {
				turtle.goto(p1[0], p1[1]);
			}
			if (idx == pts.length - 1) {
		    	let p2 = pts[(idx + 1) % pts.length];
			    turtle.goto(p2[0], p2[1]);
			}
		});	
		if (shapeIdx >= 4 && doFillRect) {
		    fillRect(shape)
		}
		shapeIdx++;
	}
	return i < (grid * grid) -1;
}

function posToPx(p, rotation = 0) {
	p = rotate(p, rotation);
	p = [toPx(p[0] *2), toPx(p[1] *2)];
	return p;
}

function createSkull(x, y, scale = 1) {
	let s = 11;
	let px1 = 1 / s;
	let px2 = 2 / s;
	let px3 = 3 / s;
	let px4 = 4 / s;
	let chinX = pick([px2, px1]);
	let chinY = pick([px1, px2, px3, px4]);

	chinX = px2;
	chinY = px3;

	let faceProps = makeSimilar ? defaultFaceProps : getFaceProps();

	function rect(x, y, w, h) {
		[x, y, w, h] = [x * px1, y * px1, w * px1, h * px1];
		return [
			[x, y],
			[x + w, y],
			[x + w, y + h],
			[x, y + h],
		];
	}

	function arc(x, y, r, a1, a2, quality = 0.05) {
		[x, y, r] = [x * px1, y * px1, r * px1];
		a1 += Math.PI / 2;
		a2 += Math.PI / 2;
		let pts = [];
		for (let a = a2; a >= a1; a -= quality) {
			pts.push([x + Math.sin(a) * r, y + Math.cos(a) * r]);
		}
		return pts;
	}
	
	let eyeY = pick([1, 2, 3]);
	if (faceProps.mouthSize == 2) eyeY = 1;
	let mouthY = eyeY + 1 + faceProps.eyeHeight + pick([2, 3]);

	let smolskull = [];
	smolskull.push(rect(-3,-3, s+6,s+6))
	smolskull.push([
		// head
		
		...arc(1, 1, 1, -Math.PI - Math.PI / 2, -Math.PI),
		[px1, 0],
		...arc(s - 1, 1, 1, 0, Math.PI / 2),
		[1, 1 - chinY],
		[1 - chinX, 1 - chinY],
		[1 - chinX, 1],
		[chinX, 1],
		[chinX, 1 - chinY],
		[0, 1 - chinY],
	]);
	// eyes
	smolskull.push(rect(2, eyeY, 3, faceProps.eyeHeight + 2));
	smolskull.push(rect(6, eyeY, 3, faceProps.eyeHeight + 2));
	if (faceProps.eyeDirectionOnly) {
		if (faceProps.eyeDirection == 0) {
			smolskull.push(rect(2, eyeY, 1, faceProps.eyeHeight + 2));
			smolskull.push(rect(6, eyeY, 1, faceProps.eyeHeight + 2));
		} else if (faceProps.eyeDirection == 1) {
			smolskull.push(rect(2 + 2, eyeY, 1, faceProps.eyeHeight + 2));
			smolskull.push(rect(6 + 2, eyeY, 1, faceProps.eyeHeight + 2));
		} else if (faceProps.eyeDirection == 2) {
			smolskull.push(rect(2, faceProps.eyeHeight + eyeY + 1, 3, 1));
			smolskull.push(rect(6, faceProps.eyeHeight + eyeY + 1, 3, 1));
		} else if (faceProps.eyeDirection == 3) {
			smolskull.push(rect(2, eyeY, 3, 1));
			smolskull.push(rect(6, eyeY, 3, 1));
		}
	}
	//mouth
	let mouthHack = 0;//0.0375;
	if (faceProps.mouthType == 0) {
		// mouth []
		smolskull.push(rect(3, mouthY, 5, Math.min(s - mouthY + mouthHack, (faceProps.mouthSize + 1 + mouthHack) * 2)));
	} else if (faceProps.mouthType == 1) {
		// mouth []
		smolskull.push(rect(4, mouthY, 3, Math.min(s - mouthY + mouthHack, (faceProps.mouthSize + 1 + mouthHack) * 2)));
	} else {
		// mouth  |||
		smolskull.push(rect(3, mouthY, 1, Math.min(s - mouthY + mouthHack, (faceProps.mouthSize + 1 + mouthHack) * 2)));
		smolskull.push(rect(5, mouthY, 1, Math.min(s - mouthY + mouthHack, (faceProps.mouthSize + 1 + mouthHack) * 2)));
		smolskull.push(rect(7, mouthY, 1, Math.min(s - mouthY + mouthHack, (faceProps.mouthSize + 1 + mouthHack) * 2)));
	}
	// scale and position
	let newSize = 17;
	let newOffset = 3;
	smolskull = smolskull.map((arr) => arr.map((p) => add2(scl2(add2([-0.5 + newOffset / newSize, -0.5 + newOffset / newSize], [(p[0] / newSize) * s, (p[1] / newSize) * s]), scale), [x, y])));

	return smolskull;
}

function scl2(a, b) {
	return [a[0] * b, a[1] * b];
}
function add2(a, b) {
	return [a[0] + b[0], a[1] + b[1]];
}
function sub2(a, b) {
	return [a[0] - b[0], a[1] - b[1]];
}
function dot2(a, b) {
	return a[0] * b[0] + a[1] * b[1];
}
function len(a) {
	return Math.sqrt(a[0] ** 2 + a[1] ** 2);
}
function dist2(a, b) {
	return len(sub2(a, b));
}
function nrm2(a) {
	return scl2(a, 1 / len(a));
}
function lrp2(a, b, f) {
	return [lrp(a[0], b[0], f), lrp(a[1], b[1], f)];
}
function clamp01(v) {
	return v < 0 ? 0 : v > 1 ? 1 : v;
}
function rotate(p, a) {
	return [p[0] * Math.cos(a) + p[1] * Math.sin(a), p[1] * Math.cos(a) - p[0] * Math.sin(a)];
}
function toPx(unit) {
	return (unit / 200) * 100;
}
function lrp(a, b, f) {
	return a + (b - a) * f;
}
function range(a, b) {
	return a + (b - a) * fxrand();
}
function pick(arr) {
	return arr[(fxrand() * arr.length) | 0];
}
function fxrand() {
    // fxrand is the random from fxhash, from which smolskull originates 
    // https://www.fxhash.xyz/generative/14
    return Math.random()
}

////////////////////////////////////////////////////////////////
// Slowpoke utility code. Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/cfe9091ad8
////////////////////////////////////////////////////////////////

function Slowpoke(x, y) {
    const linesDrawn = {};
    class Slowpoke extends Turtle {
        goto(x, y) {
            const p = Array.isArray(x) ? [...x] : [x, y];
            if (this.isdown()) {
                const o = [this.x(), this.y()];
                const h1 = o[0].toFixed(2)+'_'+p[0].toFixed(2)+o[1].toFixed(2)+'_'+p[1].toFixed(2);
                const h2 = p[0].toFixed(2)+'_'+o[0].toFixed(2)+p[1].toFixed(2)+'_'+o[1].toFixed(2);
                if (linesDrawn[h1] || linesDrawn[h2]) {
                    super.up();
                    super.goto(p);
                    super.down();
                    return;
                }
                linesDrawn[h1] = linesDrawn[h2] = true;
            } 
            super.goto(p);
        }
    }
    return new Slowpoke(x,y);
}