//- Press and hold to explode layers

script.
	window.canvasOptions = {
		autoClear: true,
		autoCompensate: false,
		autoPushPop: true,
		canvas: true,
		centered: true,
		desynchronized: false,
		width: null,
		height: null
	};
View Compiled
body {
	cursor: pointer;
}
View Compiled
const points = [];
const outer = 150;
const inner = 120;

let sep = 0;

function setup() {
	// https://www.cmu.edu/biolphys/deserno/pdf/sphere_equi.pdf
	const N = 3800;
	const r = 4;
	const a = 4 * PI * r ** 2 / N;
	const d = sqrt(a);
	const MTheta = round(PI / d);
	const dTheta = PI / MTheta;
	const dPhi = a / dTheta;
	let i = 0;
	for(let m = 0; m < MTheta; m++) {
		const theta = PI * (m + 0.5) / MTheta;
		const MPhi = round(2 * PI * sin(theta) / dPhi);
		for(let n = 0; n < MPhi; n++) {
			const phi = 2 * PI * n / MPhi;
			const point = new Vector(
				sin(theta) * cos(phi),
				sin(theta) * sin(phi),
				cos(theta)
			).mult(outer);
			point.i = i++;
			points.push(point);
		}
	}
}

function draw(e) {
	// const timeDiff = e - _lastCanvasTime;
	// const timeAdjust = map(timeDiff, 0, 1000 / 60, 0, 1);
	const time = e * 0.001;
	
	rotate(time * 0.1);
	
	if(mouseDown) {
		sep = lerp(sep, inner, 0.05);
	}
	else {
		sep = lerp(sep, 0, 0.12);
	}
	
	lineCap('round');
	const COLOR = hsl(0, 100, 50);
	
	// const zx = 0.0125 * timeAdjust;
	// const xy = 0.005 * timeAdjust;
	// const yz = sin(time * 0.5) * 0.01 * timeAdjust;
	// for(const p of points) {
	// 	p.rotateZX(zx).rotateXY(xy).rotateYZ(yz);
	const c = sin(time * 0.5) * 0.01;
	for(const p of points) {
		p.rotateZX(0.0125).rotateXY(0.005).rotateYZ(c);
		//// p.rotateYZ(p.i / points.length * PI * 0.01);
		// const [ _, __, ___ ] = [
		// 	p.y / outer * PI * 0.01,
		// 	p.z / outer * PI * 0.02,
		// 	p.x / outer * PI * 0.03
		// ];
		// p.rotateZX(_).rotateXY(__).rotateYZ(___);
		p.setMag3D(
			map(
				sin(time * 5 + p.i / points.length * TAU * 12),
				-1, 1, inner, outer
			)
		);
	}
	points.sort((a, b) => b.z - a.z);
	const pointRad = p => map(p.z, 100, -100, 3, 7);
	const innerPoints = [];
	const innerTest = Math.cos(Math.PI * 0.25) * inner - 7;
	let i = 0;
	
	push();
	translate(-sep, -sep);
	
	beginPath();
	for(; i < points.length; i++) {
		const p = toCamera(points[i]);
		if(p.z < 0) {
			break;
		}
		if(
			abs(p.x) < innerTest &&
			abs(p.y) < innerTest
		) {
			continue;
		}
		innerPoints.push(p);
		circle(p, pointRad(p));
	}
	fill(COLOR);
	
	translate(0, sep * 2);
	
	beginPath();
	for(const p of innerPoints) {
		line(p);
	}
	stroke(COLOR, 2);
	innerPoints.splice(0);
	
	translate(sep, -sep);
	
	beginPath();
	circle(0, 0, inner);
	fill(hsl(0, 0, 100));
	
	translate(sep, -sep);
	
	beginPath();
	for(; i < points.length; i++) {
		const p = toCamera(points[i]);
		// const p2 = toCamera(points[i]._.div(outer).mult(inner));
		const p2 = toCamera(points[i]._.limit3D(inner));
		innerPoints.push([ p, p2 ]);
		circle(p, pointRad(p));
		line(p, p2);
	}
	fill(COLOR);
	
	translate(0, sep * 2);
	
	beginPath();
	for(const [ p1, p2 ] of innerPoints) {
		line(p1, p2);
	}
	stroke(COLOR, 3);
	pop();
	
	// fillStyle(COLOR_WHITE);
	globalAlpha(ease.expo.in(sep / 100));
	textAlign(TEXTALIGN_CENTER);
	font('24px sans-serif');
	const labels = [ 1, 4, 5, 2 ];
	for(let i = 0; i < 5; i++) {
		if(i !== 4) {
			push();
			rotate(-QUARTER_PI + HALF_PI * i);
			fillText(labels[i], 0, -sep / inner * 100 - inner * 2);
			pop();
		}
		else {
			fillStyle(COLOR_BLACK);
			fillText(3, 0, 0);
		}
	}
}

function toCamera(_v) {
	const v = _v.copy();
	const z = (v.z + 1000) * 0.001;
	v.x /= z;
	v.y /= z;
	return v;
}

/* requestAnimationFrame */
View Compiled
Run Pen

External CSS

  1. https://codepen.io/Alca/pen/XeZBab.scss

External JavaScript

  1. https://codepen.io/Alca/pen/XeZBab.js