Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <label><input checked id='autoRotate' type='checkbox'> Auto rotation</label>
<div id='debug'></div>
<canvas id='draw'></canvas>
              
            
!

CSS

              
                html, body { height: 100%; overflow: hidden; }
body {
	background: #000;
	background:
		url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/15664/noise.png),
		radial-gradient(circle, hsl(210, 29%, 7%), hsl(210, 29%, 3%));
	color: #fff;
	font-family: "Century Gothic",CenturyGothic,AppleGothic,sans-serif;
	// cursor: move;
}

#draw {
	position: fixed;
	left: 0; top: 0;
	width: 100%; height: 100%;
}

label, input { vertical-align: middle; }
label {
	position: relative;
	display: inline-block;
	margin: 5px;
	padding: 5px 10px;
	user-select: none;
	z-index: 2;
	cursor: pointer;
}

#debug {
	position: absolute;
	bottom: 0; left: 0;
	color: #fff;
	font-family: Consolas, monaco, monospace;
	font-size: 1.5rem;
	white-space: pre;
}
              
            
!

JS

              
                var _debug = document.getElementById('debug');
function debug() { _debug.textContent = [].join.call(arguments, '\n'); }

var c = document.getElementById('draw'),
	ctx = c.getContext('2d');

function onResize() {
	c.width = c.clientWidth;
	c.height = c.clientHeight;
}
window.addEventListener('resize', onResize);
onResize();

// Utils
function clip(n, m, M) { return n < M ? n > m ? n : m : M; }
function comeCloser(n, goal, factor, limit) {
	return (limit && Math.abs(goal - n) < limit) ? goal : n + (goal - n) / (factor || 10);
}
function dist(a, b) {
	var dx = b[0] - a[0], dy = b[1] - a[1], dz = b[2] - a[2];
	return Math.sqrt(dx*dx + dy*dy + dz*dz);
}
function normalize(v) {
	var l = Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
	return [v[0] / l, v[1] / l, v[2] / l];
}
function projection(p, d, s) {
	var f = (s || 1) / (1 - p[2] / d);
	return [p[0]*f, p[1]*f, p[2]];
}
function rotateX(p, a) {
	var d = Math.sqrt(p[2] * p[2] + p[1] * p[1]),
		na = Math.atan2(p[2], p[1]) + a;
	return [p[0], d * Math.cos(na), d * Math.sin(na)];
}
function rotateY(p, a) {
	var d = Math.sqrt(p[2] * p[2] + p[0] * p[0]),
		na = Math.atan2(p[0], p[2]) + a;
	return [d * Math.sin(na), p[1], d * Math.cos(na)];
}
function rotateZ(p, a) {
	var d = Math.sqrt(p[1] * p[1] + p[0] * p[0]),
		na = Math.atan2(p[1], p[0]) + a;
	return [d * Math.cos(na), d * Math.sin(na), p[2]];
}
function rotateMatrix(p, m) {
	return [
		p[0] * m[0] + p[1] * m[3] + p[2] * m[6],
		p[0] * m[1] + p[1] * m[4] + p[2] * m[7],
		p[0] * m[2] + p[1] * m[5] + p[2] * m[8]
	];
}
// Not used there but could be useful ! (gives the invert rotation)
function transpose33(m) {
	return [
		m[0], m[3], m[6],
		m[1], m[4], m[7],
		m[2], m[5], m[8]
	];
}
function rotate3dMatrix(x, y, z, a) {
	var c = 1 - Math.cos(a), s = Math.sin(a);
	return [
		1+c*(x*x-1), x*y*c+z*s, x*z*c-y*s,
		x*y*c-z*s, 1+c*(y*y-1), y*z*c+x*s,
		x*z*c+y*s, y*z*c-x*s, 1+c*(z*z-1)
	];
}
function mul33(m, n) {
	var m1 = m[0], m2 = m[1], m3 = m[2],
		m4 = m[3], m5 = m[4], m6 = m[5],
		m7 = m[6], m8 = m[7], m9 = m[8];
	
	var n1 = n[0], n2 = n[1], n3 = n[2],
		n4 = n[3], n5 = n[4], n6 = n[5],
		n7 = n[6], n8 = n[7], n9 = n[8];
	
	return [
		m1*n1+m4*n2+m7*n3, m2*n1+m5*n2+m8*n3, m3*n1+m6*n2+m9*n3,
		m1*n4+m4*n5+m7*n6, m2*n4+m5*n5+m8*n6, m3*n4+m6*n5+m9*n6,
		m1*n7+m4*n8+m7*n9, m2*n7+m5*n8+m8*n9, m3*n7+m6*n8+m9*n9
	];
}
function chainMul33(base) {
	for(var i = 1, l = arguments.length; i < l; i++)
		base = mul33(base, arguments[i]);
	return base;
}
function dot(a, b) {
	return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
function cross(a, b) {
	return [
		a[1] * b[2] - a[2] * b[1],
		a[2] * b[0] - a[0] * b[2],
		a[0] * b[1] - a[1] * b[0]
	];
}
function sub(a, b) {
	return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
}

// Big chunk of code incoming !
// These are the drawing functions
// You should probably fold these functions and only open one at a time
var drawElectricity = (function() {
	// var color = 'hsl('+(Math.random() * 360)+', 60%, 50%)';
	var color = 'rgb(40,130,240)';
	var rays = [];
	for(var i = 0; i < 5; i++) {
		var dest = [Math.random() - .5, Math.random() - .5, Math.random() - .5];
		var d = Math.sqrt(dest[0] * dest[0] + dest[1] * dest[1] + dest[2] * dest[2]);
		dest[0] /= d; dest[1] /= d; dest[2] /= d;
		var parts = [], pCount = ~~(3 * Math.random()) + 3;
		for(var j = 0; j < pCount; j++) {
			parts.push({
				pos: (j + 1) / (pCount + 1),
				off: [0,0,0],
				maxOff: 4 + 3 * Math.random(),
				speed: 240
			});
		}
		var vel = 3;
		var ray = {
			dest: dest,
			vel: [vel * (Math.random() - .5), vel * (Math.random() - .5), vel * (Math.random() - .5)],
			parts: parts
		};
		rays.push(ray);
	}
	var tmpC = [document.createElement('canvas'), document.createElement('canvas')],
		tCtx = [tmpC[0].getContext('2d'), tmpC[1].getContext('2d')];
	tmpC[0].width = tmpC[0].height = tmpC[1].width = tmpC[1].height = 200;
	tCtx[0].translate(100,100); tCtx[1].translate(100,100);
	var currentCanvas = 0;
	var counter = 0, pT;
	return function(ctx, faces, allCorners) {
		var now = Date.now(), dt = 0;
		if(pT) dt = (now - pT) * .001;
		pT = now;
		var c = ctx.canvas;
		var rad = cubeSize * (.2 + .02 * Math.sin((counter += dt) * 2));
		var mx = c.width * .5, my = c.height * .5;
		ctx.strokeStyle = color;
		ctx.beginPath();
		ctx.arc(0, 0, rad, 0, 2 * Math.PI);
		var i, l, j, l2;
		for(i = 0, l = allCorners.length; i < l; i++) {
			var corner = allCorners[i];
			var dx = corner[0] - mx, dy = corner[1] - my;
			var d = Math.sqrt(dx * dx + dy * dy);
			if(d <= rad) continue;
			ctx.moveTo(rad * dx / d, rad * dy / d);
			ctx.lineTo(dx, dy);
		}
		ctx.stroke();
		currentCanvas = 1 - currentCanvas;
		var cc = tmpC[currentCanvas], cCtx = tCtx[currentCanvas];
		cCtx.clearRect(-cc.width * .5, -cc.height * .5, cc.width, cc.height);
		cCtx.shadowColor = 'transparent';
		cCtx.shadowBlur = 0;
		cCtx.globalAlpha = Math.pow(.001, dt);
		cCtx.drawImage(tmpC[1 - currentCanvas], -cc.width * .5, -cc.height * .5);
		cCtx.globalAlpha = 1;
		cCtx.strokeStyle = 'rgba(245,250,255,1)';
		cCtx.shadowColor = 'rgba(255,255,255,.5)';
		cCtx.shadowBlur = 4;
		for(i = 0, l = rays.length; i < l; i++) {
			var ray = rays[i], vel = ray.vel;
			var dest = ray.dest = rotateX(rotateY(rotateZ(ray.dest, vel[2] * dt), vel[1] * dt), vel[0] * dt);
			var previous = [0,0];
			// cCtx.beginPath();
			// cCtx.moveTo(0,0);
			for(j = 0, l2 = ray.parts.length; j < l2; j++) {
				var part = ray.parts[j], off = part.off;
				off[0] += part.speed * (Math.random() - .5) * dt;
				off[1] += part.speed * (Math.random() - .5) * dt;
				off[2] += part.speed * (Math.random() - .5) * dt;
				var d = Math.sqrt(off[0] * off[0] + off[1] * off[1] + off[2] * off[2]);
				if(d > part.maxOff) {
					var m = part.maxOff / d;
					off[0] *= m; off[1] *= m; off[2] *= m;
				}
				var pos = [part.pos * rad * dest[0] + off[0], part.pos * rad * dest[1] + off[1]];
				cCtx.lineWidth = .1 + .8 * (1 - part.pos);
				cCtx.beginPath();
				cCtx.moveTo(previous[0], previous[1]);
				cCtx.lineTo(pos[0], pos[1]);
				cCtx.stroke();
				previous = pos;
			}
			cCtx.lineWidth = .15;
			cCtx.beginPath();
			cCtx.moveTo(previous[0], previous[1]);
			cCtx.lineTo(rad * dest[0], rad * dest[1]);
			cCtx.stroke();
		}
		ctx.drawImage(cc, -cc.width * .5, -cc.height * .5);
	};
})();
var drawStars = (function() {
	var stars = [], focale = 100;
	var maxDist = 1000, f = .5 * maxDist / focale;
	var newStar = function(dist, c, bop) {
		var speed = 400 + Math.random() * 200;
		return [(Math.random() - .5) * f, (Math.random() - .5) * f, dist, speed, bop || 0];
	};
	for(var i = 0; i < 200; i++) {
		var dist = maxDist * Math.random() + 1;
		stars.push(newStar(dist, c, 1));
	}
	var pT, dtMax = 1 / 60;
	return function(ctx) {
		var now = Date.now(), dt = 0;
		if(pT) dt = Math.min((now - pT) * .001, dtMax);
		pT = now;
		var c = ctx.canvas;
		ctx.fillStyle = 'rgba(0,0,0,.8)';
		ctx.fillRect(c.width * -.5, c.height * -.5, c.width, c.height);
		for(var i = 0, l = stars.length; i < l; i++) {
			var star = stars[i];
			star[4] += dt * .5;
			star[2] -= dt * star[3];
			if(star[2] <= 0)
				star = stars[i] = newStar(maxDist, c);
			var op = Math.min(star[4], 1);
			ctx.fillStyle = 'rgba(255,255,255,'+op+')';
			var f = focale / star[2], s = 3 * f;
			ctx.fillRect(cubeSize*star[0] * f - s*.5,cubeSize*star[1] * f - s*.5,s,s);
		}
	};
})();
var drawCubes = (function() {
	var v = [], e = [];
	v.push([-1,-1,-1]);
	v.push([-1,-1, 1]);
	v.push([ 1,-1, 1]);
	v.push([ 1,-1,-1]);
	v.push([ 1, 1,-1]);
	v.push([-1, 1,-1]);
	v.push([-1, 1, 1]);
	v.push([ 1, 1, 1]);
	var eFull = '0-1 1-2 2-3 3-0 4-5 5-6 6-7 7-4 0-5 1-6 2-7 3-4'.split(' ');
	for(var i = eFull.length, ea; i--;) e.push([+(ea=eFull[i].split('-'))[0], +ea[1]]);
	var offset = Math.PI * .25, s1 = .5 / Math.sqrt(3), s2 = s1 / Math.sqrt(3), s3 = s2 / Math.sqrt(3);
	var draws = [
		{
			color: '#2ecc71',
			transform: function(p, m) { return projection(rotateX(rotateMatrix(p, m), offset), perspective, cubeSize * s1); }
			// transform: function(p, m) { return projection(rotateMatrix(rotateX(p, offset), m), f, s1); }
		}, {
			color: '#e74c3c',
			transform: function(p, m) { return projection(rotateY(rotateMatrix(p, m), offset), perspective, cubeSize * s2); }
			// transform: function(p, m) { return projection(rotateMatrix(rotateY(p, offset), m), f, s2); }
		}, {
			color: '#f1c40f',
			transform: function(p, m) { return projection(rotateZ(rotateMatrix(p, m), offset), perspective, cubeSize * s3); }
			// transform: function(p, m) { return projection(rotateMatrix(rotateZ(p, offset), m), f, s3); }
		}
	];
	return function(ctx) {
		// var y = Math.asin(rot[6]),
		// 	x = Math.atan2(-rot[7], rot[8]),
		// 	z = Math.atan2(-rot[3], rot[0]);
		// debug(180 * x / Math.PI, 180 * y / Math.PI, 180 * z / Math.PI);
		var allLines = [], i, l, d;
		for(d = draws.length; d--;) {
			var draw = draws[d];
			var points = [];
			for(i = 0, l = v.length; i < l; i++)
				points.push(draw.transform(v[i], rotMatrix));
			for(i = e.length; i--;) {
				var edge = e[i], p1 = points[edge[0]], p2 = points[edge[1]];
				var z = (p1[2] + p2[2]) * .5;
				allLines.push([p1[0], p1[1], p2[0], p2[1], z, draw.color]);
			}
		}
		// Order by z-distance to get a better look
		// (inverse because the next loop is decrementing)
		allLines.sort(function(a, b) { return b[4] - a[4]; });
		ctx.lineWidth = 1.2;
		// ctx.globalCompositeOperation = 'lighter';
		for(i = allLines.length; i--;) {
			l = allLines[i];
			ctx.strokeStyle = l[5];
			ctx.beginPath();
			ctx.moveTo(l[0], l[1]);
			ctx.lineTo(l[2], l[3]);
			ctx.stroke();
		}
	};
})();
var drawPhysics = (function() {
	// Using array for better perfs
	// 0: x, 1: y, 2: z, 3: px, 4: py, 5: pz
	function Point(x, y, z) { return [x, y, z, x, y, z]; }
	function Stick(a, b, l, style) { return [a, b, l, style]; }
	var points = [], sticks = [], objects = [];
	var tr = [0,0,0], defStyle = 'white';
	var sticksCache = {}; // Used to remove duplicates
	function setTranslation(x, y, z) { tr = [x, y, z]; }
	function addPoint(x, y, z) {
		return points.push(Point(tr[0]+x, tr[1]+y, tr[2]+z)) - 1;
	}
	function addStick(a, b, style, length) {
		var id = Math.min(a,b) + '|' + Math.max(a,b);
		if(sticksCache.hasOwnProperty(id))
			return sticksCache[id];
		if(length === undefined) length = dist(points[a], points[b]);
		return (sticksCache[id] = sticks.push(Stick(a, b, length, style === undefined ? defStyle : style)) - 1);
	}
	function addTriangle(a, b, c, h, style) {
		// h = 0;
		style = style || defStyle;
		addStick(a, b, !(h & 0x100) && style);
		addStick(b, c, !(h & 0x010) && style);
		addStick(c, a, !(h & 0x001) && style);
		return [a, b, c];
	}
	function addBox(s, x, y, z, style, parent) {
		if(style) defStyle = style;
		setTranslation(x,y,z);
		var p1 = addPoint(-s, -s, -s),
			p2 = addPoint(-s, -s,  s),
			p3 = addPoint( s, -s,  s),
			p4 = addPoint( s, -s, -s),
			p5 = addPoint(-s,  s, -s),
			p6 = addPoint(-s,  s,  s),
			p7 = addPoint( s,  s,  s),
			p8 = addPoint( s,  s, -s);
		var object = [parent, [p1,p2,p3,p4,p5,p6,p7,p8]];
		// clockwise points
		object.push(addTriangle(p1, p3, p2, 0x100));
		object.push(addTriangle(p1, p4, p3));
		object.push(addTriangle(p1, p6, p5, 0x100));
		object.push(addTriangle(p1, p2, p6));
		object.push(addTriangle(p1, p5, p4, 0x010));
		object.push(addTriangle(p4, p5, p8));
		object.push(addTriangle(p2, p7, p6, 0x100));
		object.push(addTriangle(p2, p3, p7));
		object.push(addTriangle(p3, p4, p8, 0x001));
		object.push(addTriangle(p3, p8, p7));
		object.push(addTriangle(p5, p6, p7, 0x001));
		object.push(addTriangle(p5, p7, p8));
		
		// Reinforce by adding sticks (could also add every face other diagonal)
		addStick(p1, p7, null);
		addStick(p2, p8, null);
		addStick(p3, p5, null);
		addStick(p4, p6, null);
		
		// Init spin
		// p1[3] += (Math.random()-.5)*.3;
		// p1[4] += (Math.random()-.5)*.3;
		// p1[5] += (Math.random()-.5)*.3;
		
		return objects.push(object) - 1;
	}
	function addTetrahedron(s, x, y, z, style, parent) {
		if(style) defStyle = style;
		setTranslation(x,y,z);
		var t = s;
		s *= Math.SQRT2;
		var p1 = addPoint( s,  0, -t),
			p2 = addPoint(-s,  0, -t),
			p3 = addPoint( 0, -s,  t),
			p4 = addPoint( 0,  s,  t);
		var object = [parent, [p1,p2,p3,p4]];
		// clockwise points
		object.push(addTriangle(p1, p2, p4));
		object.push(addTriangle(p1, p4, p3));
		object.push(addTriangle(p1, p3, p2));
		object.push(addTriangle(p2, p3, p4));
		
		return objects.push(object) - 1;
	}
	addBox(.50, 0, 0, 0, '#e74c3c');
	addBox(.25, 0, 0, 0, '#2ecc71', 0);
	addTetrahedron(.125, 0, 0, 0, '#f1c40f', 1);
	
	var accuracy = 3;
	var stiffness = 1, bounce = 0; // Adjust accordingly with accuracy
	var friction = .98, gravity = .003;
	var fBases = [[1,0,0], [-1,0,0], [0,1,0], [0,-1,0], [0,0,1], [0,0,-1]];
	var fs = [];
	var proj = [], lines = [];
	var paused = false; // [1,0,0,0,1,0,0,0,1]
	// document.addEventListener('mousedown', (e) => e.which === 3 && (paused = paused ? false : transpose33(rotMatrix)));
	// document.addEventListener('contextmenu', (e) => e.preventDefault());
	function applyVel() {
		for(var i = points.length; i--;) {
			var p = points[i];
			var vx = (p[0] - p[3]) * friction,
				vy = (p[1] - p[4]) * friction,
				vz = (p[2] - p[5]) * friction;
			var gVec = [0, gravity, 0];
			// var gVec = rotateMatrix([0, gravity, 0], transpose33(rotMatrix));
			p[3] = p[0]; p[4] = p[1]; p[5] = p[2];
			p[0] += vx + gVec[0];
			p[1] += vy + gVec[1];
			p[2] += vz + gVec[2];
		}
	}
	var ns = [];
	function constraints() {
		ns = [];
		// bounds (external cube)
		for(var i = points.length; i--;) {
			var p = points[i];
			for(var j = fs.length; j--;) {
				var f = fs[j];
				var d = dot(p, f) - 1;
				if(d > 0) { // Outside !
					for(var j = 0; j < 3; j++) {
						p[j+3] += bounce * d * f[j];
						p[j] -= d * f[j];
					}
				}
			}
		}
		// Objects inside objects
		for(var i = 0, l = objects.length; i < l; i++) {
			var o = objects[i], op = o[1];
			if(typeof o[0] === 'number') {
				var p = objects[o[0]];
				for(var j = p.length; j-->2;) {
					var tri = p[j];
					var p1 = tri[0], p2 = tri[1], p3 = tri[2];
					var a = points[p1], b = points[p2], c = points[p3];
					var n = normalize(cross(sub(b, a), sub(c, a)));
					for(var k = op.length; k--;) {
						var pt = points[op[k]];
						var d = dot(sub(pt, a), n);
						if(d > 0) {
							for(var coord = 0; coord < 3; coord++) {
								var q = d * n[coord], qb = bounce * q;
								pt[coord+3] += qb;
								pt[coord] -= q;
							}
						}
					}
				}
			}
		}
		// sticks lengths
		for(var i = sticks.length; i--;) {
			var s = sticks[i], p0 = points[s[0]], p1 = points[s[1]];
			var dx = p1[0] - p0[0], dy = p1[1] - p0[1], dz = p1[2] - p0[2];
			var d = Math.sqrt(dx*dx + dy*dy + dz*dz);
			var dd = s[2] - d, p = stiffness * .5 * dd / d;
			var offX = dx * p, offY = dy * p, offZ = dz * p;
			p0[0] -= offX; p0[1] -= offY; p0[2] -= offZ;
			p1[0] += offX; p1[1] += offY; p1[2] += offZ;
		}
	}
	function update() {
		if(!paused) {
			fs = [];
			for(var i = fBases.length; i--;)
				fs.push(rotateMatrix(fBases[i], rotMatrix));
			applyVel();
			for(var i = accuracy; i--;)
				constraints();
		}
		proj = []; lines = [];
		for(var i = 0, l = points.length; i<l; i++)
			proj.push(projection(!paused ? points[i] : rotateMatrix(rotateMatrix(points[i], paused), rotMatrix), perspective));
		for(var i = sticks.length; i--;) {
			var s = sticks[i];
			if(!s[3]) continue;
			var a = proj[s[0]], b = proj[s[1]];
			lines.push([a[0], a[1], b[0], b[1], s[3], (a[2] + b[2]) * .5]);
		}
		lines.sort(function(a, b) { return b[5] - a[5]; });
	}
	// Above 100fps, consider that we try to draw 2 times the same frame (so only update once)
	var pT, minFrameLength = 1000/100, time = 0;
	return function(ctx) {
		var now = Date.now(), dt = 0;
		if(pT) dt = now - pT;
		pT = now;
		time += dt;
		if(time >= minFrameLength) {
			time = 0;
			update();
		}
		// [-1; 1]
		ctx.scale(cubeSize * .5, cubeSize * .5);
		// ctx.fillStyle = 'white';
		// for(var i = points.length; i--;) {
		// 	var p = proj[i];
		// 	var size = .04 * (p[2] + 1);
		// 	ctx.fillRect(p[0] - size * .5, p[1] - size * .5, size, size);
		// }
		
		ctx.lineWidth = 2 / cubeSize;
		// ctx.strokeStyle = 'white';
		// ctx.beginPath();
		for(var i = lines.length; i--;) {
			var l = lines[i];
			ctx.strokeStyle = l[4];
			ctx.beginPath();
			ctx.moveTo(l[0], l[1]);
			ctx.lineTo(l[2], l[3]);
			ctx.stroke();
		}
		// ctx.stroke();
	};
})();
var drawPong = (function() {
	// This one is a bit ugly (quickly coded)
	// Meant to be viewed from the right
	var ballSize = .05, ballSpeed = 2.5;
	var ball = [0,0,0], ballVel = [0,0,0];
	var p1 = [0,0,1], p2 = [0,0,-1];
	var hits = [],// hitsFront = [],
		hitS = .1, hitM = 2;
	function mirrorRandNZ(m) {
		return (Math.random() + m) * (~~(Math.random() * 2) * 2 - 1);
	}
	function start() {
		var v = [mirrorRandNZ(.5), mirrorRandNZ(.5), mirrorRandNZ(.9)];
		var l = ballSpeed / Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
		ballVel = [v[0] * l, v[1] * l, v[2] * l];
	}
	function transformed(p) {
		return projection(rotateMatrix(p, rotMatrix), perspective);
	}
	function addHit(pos, dx, dy, front) {
		// (front ? hitsFront : hits).push([pos, dx, dy, 0]);
		hits.push([pos, dx, dy, 0]);
	}
	function clipV(v) {
		return [clip(v[0], -1, 1), clip(v[1], -1, 1), clip(v[2], -1, 1)];
	}
	function hitSq(ctx, center, size, dx, dy) {
		var p1 = clipV([center[0] + size * (-dx[0] - dy[0]), center[1] + size * (-dx[1] - dy[1]), center[2] + size * (-dx[2] - dy[2])]),
			p2 = clipV([center[0] + size * ( dx[0] - dy[0]), center[1] + size * ( dx[1] - dy[1]), center[2] + size * ( dx[2] - dy[2])]),
			p3 = clipV([center[0] + size * ( dx[0] + dy[0]), center[1] + size * ( dx[1] + dy[1]), center[2] + size * ( dx[2] + dy[2])]),
			p4 = clipV([center[0] + size * (-dx[0] + dy[0]), center[1] + size * (-dx[1] + dy[1]), center[2] + size * (-dx[2] + dy[2])]);
		p1 = transformed(p1); p2 = transformed(p2); p3 = transformed(p3); p4 = transformed(p4);
		ctx.moveTo(p1[0], p1[1]);
		ctx.lineTo(p2[0], p2[1]);
		ctx.lineTo(p3[0], p3[1]);
		ctx.lineTo(p4[0], p4[1]);
		ctx.lineTo(p1[0], p1[1]);
	}
	function updateDrawHit(ctx, dt, hit) {
		hit[3] += dt;
		var peak = hit[3] * 4, max = 0;
		for(var x = -hitM; x <= hitM; x++)
			for(var y = -hitM; y <= hitM; y++) {
				var d = 1 + Math.abs(Math.sqrt(x * x + y * y) - peak);
				var op = clip(2.5 / d - 1, 0, 1);
				if(op > max) max = op;
				ctx.fillStyle = 'rgba(200,30,120,'+op+')';
				// ctx.fillRect(p[0]+(x-.5)*s,p[1]+(y-.5)*s,s,s);
				ctx.beginPath();
				var c = [hit[0][0] + hitS * ((x-.5)*hit[1][0]+(y-.5)*hit[2][0]), hit[0][1] + hitS * ((x-.5)*hit[1][1]+(y-.5)*hit[2][1]), hit[0][2] + hitS * ((x-.5)*hit[1][2]+(y-.5)*hit[2][2])];
				hitSq(ctx, c, hitS * .5, hit[1], hit[2]);
				ctx.fill();
			}
		return max >= .01;
	}
	start();
	var pT, dtMax = 1 / 60;
	return function(ctx) {
		var now = Date.now(), dt = 0;
		if(pT) dt = Math.min((now - pT) * .001, dtMax);
		pT = now;
		ctx.scale(cubeSize * .5, cubeSize * .5);
		
		ball[0] += ballVel[0] * dt;
		ball[1] += ballVel[1] * dt;
		ball[2] += ballVel[2] * dt;
		if(ball[0] < ballSize - 1) {
			ball[0] = ballSize - 1;
			ballVel[0] *= -1;
			addHit([-1, ball[1], ball[2]], [0,0,1], [0,1,0]);
		}
		if(ball[0] > 1 - ballSize) {
			ball[0] = 1 - ballSize;
			ballVel[0] *= -1;
			// addHit([1, ball[1], ball[2]], [0,0,1], [0,1,0], true);
		}
		if(ball[1] < ballSize - 1) {
			ball[1] = ballSize - 1;
			ballVel[1] *= -1;
			addHit([ball[0], -1, ball[2]], [1,0,0], [0,0,1]);
		}
		if(ball[1] > 1 - ballSize) {
			ball[1] = 1 - ballSize;
			ballVel[1] *= -1;
			addHit([ball[0], 1, ball[2]], [1,0,0], [0,0,1]);
		}
		if(ball[2] < ballSize - 1) {
			ball[2] = ballSize - 1;
			ballVel[2] *= -1;
			// addHit([ball[0], ball[1], -1], [1,0,0], [0,1,0]);
		}
		if(ball[2] > 1 - ballSize) {
			ball[2] = 1 - ballSize;
			ballVel[2] *= -1;
			// addHit([ball[0], ball[1], 1], [1,0,0], [0,1,0]);
		}
		// Should only call these once per frame
		// (or find a way to use dt with it)
		p1[0] = comeCloser(p1[0], ball[0], 2 + 20 * (1 - ball[2]));
		p1[1] = comeCloser(p1[1], ball[1], 2 + 20 * (1 - ball[2]));
		p2[0] = comeCloser(p2[0], ball[0], 2 + 20 * (ball[2] + 1));
		p2[1] = comeCloser(p2[1], ball[1], 2 + 20 * (ball[2] + 1));
		ctx.fillStyle = 'white';
		ctx.beginPath();
		hitSq(ctx, p1, .2, [1,0,0], [0,1,0]);
		hitSq(ctx, p2, .2, [1,0,0], [0,1,0]);
		ctx.fill();
		hits = hits.filter(updateDrawHit.bind(this, ctx, dt));
		
		var bPos = transformed(ball);
		var s = ballSize / (1 - bPos[2] / perspective);
		ctx.fillStyle = 'white';
		ctx.beginPath();
		ctx.arc(bPos[0], bPos[1], s, 0, 2 * Math.PI);
		ctx.fill();
		
		// hitsFront = hitsFront.filter(updateDrawHit.bind(this, ctx, dt));
	};
})();
var drawGrowing = (function() {
	function easing(t) {
		return t<.5 ? 2*t*t : (t-1)*(2*t-2)*(2*t-2)+1;
	}
	var globalScale = 1, globalRot = 0, rotX = -.17;
	function transformed(v) { return projection(rotateX(rotateY(v, globalRot), rotX), perspective); }
	function box(ctx, sX, sY, sZ) {
		var p1 = transformed([-sX, -sY, sZ]),
			p2 = transformed([ sX, -sY, sZ]),
			p3 = transformed([ sX,  sY, sZ]),
			p4 = transformed([-sX,  sY, sZ]),
			p5 = transformed([-sX, -sY,-sZ]),
			p6 = transformed([ sX, -sY,-sZ]),
			p7 = transformed([ sX,  sY,-sZ]),
			p8 = transformed([-sX,  sY,-sZ]);
		ctx.moveTo(p1[0], p1[1]);
		ctx.lineTo(p2[0], p2[1]);
		ctx.lineTo(p3[0], p3[1]);
		ctx.lineTo(p4[0], p4[1]);
		ctx.lineTo(p1[0], p1[1]);
		ctx.lineTo(p5[0], p5[1]);
		ctx.lineTo(p6[0], p6[1]);
		ctx.lineTo(p7[0], p7[1]);
		ctx.lineTo(p8[0], p8[1]);
		ctx.lineTo(p5[0], p5[1]);
		ctx.moveTo(p2[0], p2[1]);
		ctx.lineTo(p6[0], p6[1]);
		ctx.moveTo(p3[0], p3[1]);
		ctx.lineTo(p7[0], p7[1]);
		ctx.moveTo(p4[0], p4[1]);
		ctx.lineTo(p8[0], p8[1]);
	}
	var pT, dtMax = 1 / 60, t = 0;
	var animDur = 2.1;
	return function(ctx) {
		var now = Date.now(), dt = 0;
		if(pT) dt = Math.min((now - pT) * .001, dtMax);
		pT = now;
		t += dt;
		var p = (t % animDur) / animDur;
		globalScale = 1 - p * .5;
		globalRot = p * Math.PI / 2;
		var sc = cubeSize * .2;
		ctx.scale(sc, sc);
		ctx.strokeStyle = '#57ff57';
		ctx.lineWidth = .75 / sc;
		ctx.beginPath();
		box(ctx, globalScale, globalScale, globalScale);
		var scx = easing(clip(p / .27, 0, 1)) * 1.5 + .5,
			scy = easing(clip((p - .27) / .27, 0, 1)) * 1.5 + .5,
			scz = easing(clip((p - .54) / .27, 0, 1)) * 1.5 + .5;
		box(ctx, globalScale * scx, globalScale * scy, globalScale * scz);
		ctx.stroke();
	};
})();

// Now the cube logic
var baseCorners = [
	[-1, -1,  1], [ 1, -1,  1], [ 1, 1,  1], [-1, 1,  1],
	[ 1, -1, -1], [-1, -1, -1], [-1, 1, -1], [ 1, 1, -1],
];
// Here are the faces of the cube
// You can use the same draw for multiple faces !
// But be careful, the function will be called for every face
// So if you increment something it will be incremented multiple times per frame
// Name is not actually used here except for human-readability (but could be used in a drawing function)
var faces = [
	{
		name: 'front',
		corners: [0,1,2,3],
		draw: drawElectricity
	}, {
		name: 'back',
		corners: [4,5,6,7],
		draw: drawGrowing
		// ... empty ?
		// draw: function() { return true; }
		// draw: function(ctx) { var c = ctx.canvas; ctx.clearRect(-c.width*.5, -c.height*.5, c.width, c.height); }
	}, {
		name: 'right',
		corners: [1,4,7,2],
		draw: drawPong
	}, {
		name: 'left',
		corners: [5,0,3,6],
		draw: drawStars
	}, {
		name: 'bottom',
		corners: [3,2,7,6],
		draw: drawPhysics
	}, {
		name: 'top',
		corners: [5,4,1,0],
		draw: drawCubes
	}];

// Style things
var faceBg = 'rgba(4,13,24,.65)',
	border = 'rgb(40,130,240)';
var cubeSize = 160, perspective = 15;

// Change rot to change the initial rotation (radians)
// rotVel is the angular velocity in rad/sec around every axis
var rot = [0,0,0], rotVel = [-6e-3,7.6e-3,2.13e-3],
	rotBase = [1,0,0,0,1,0,0,0,1], rotMatrix;
// Used mainly when dragging
function setBase() {
	rotBase = rotMatrix;
	rot = [0,0,0];
}
var autoRot = document.getElementById('autoRotate');
autoRot.addEventListener('change', setBase);

// Actual drawing loop
function loop() {
	if(autoRot.checked) {
		rot[0] += rotVel[0];
		rot[1] += rotVel[1];
		rot[2] += rotVel[2];
	}
	// Compute the current rotation matrix
	var mx = rotate3dMatrix(1,0,0,rot[0]),
		my = rotate3dMatrix(0,1,0,rot[1]),
		mz = rotate3dMatrix(0,0,1,rot[2]);
	rotMatrix = chainMul33(mx, my, mz, rotBase);
	
	var w = c.width, h = c.height;
	var corners = baseCorners.map(function(c) {
		var res = projection(rotateMatrix(c, rotMatrix), perspective, cubeSize * .5);
		res[0] += w * .5; res[1] += h * .5;
		return res;
	});
	ctx.clearRect(0, 0, w, h);
	// Compute every face corners position and its z position
	for(var i = 0, l = faces.length; i < l; i++) {
		var face = faces[i];
		var z = 0;
		var faceCorners = face.currentCorners = face.corners.map(function(i) { var c = corners[i]; z+=c[2]; return c; });
		face.z = z*.25;
	}
	// Sort by z to draw what is behind first
	faces.sort(function(a, b) { return a.z - b.z; });
	for(var i = 0, l = faces.length; i < l; i++) {
		var face = faces[i];
		var faceCorners = face.currentCorners;
		ctx.save();
		// Clip twice : the first time to remove the face when it's not facing us
		// The second time to actually only draw on the face
		ctx.beginPath();
		ctx.rect(0, 0, c.width, c.height);
		drawPath(ctx, faceCorners);
		ctx.clip();
		ctx.beginPath();
		drawPath(ctx, faceCorners);
		ctx.clip();
		var drawBg;
		if(face.draw) {
			ctx.save();
			ctx.translate(w * .5, h * .5);
			drawBg = face.draw(ctx, faces, corners);
			ctx.restore();
		}
		// if(drawBg === undefined) drawBg = true;
		ctx.restore();
		
		ctx.fillStyle = faceBg;
		ctx.strokeStyle = border;
		ctx.lineWidth = .5;
		
		// Always draw the background if facing back
		ctx.save();
		ctx.beginPath();
		ctx.rect(c.width, 0, -c.width, c.height);
		drawPath(ctx, faceCorners);
		ctx.clip();
		ctx.beginPath();
		drawPath(ctx, faceCorners);
		ctx.fill();
		ctx.restore();
		
		ctx.beginPath();
		drawPath(ctx, faceCorners);
		if(drawBg) ctx.fill();
		ctx.stroke();
	}
	requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

function drawPath(ctx, corners) {
	if(!corners.length) return;
	ctx.moveTo(corners[0][0], corners[0][1]);
	for(var i = 0, l = corners.length; i < l; i++)
		ctx.lineTo(corners[i][0], corners[i][1]);
	ctx.lineTo(corners[0][0], corners[0][1]);
}

// Handle mouse/touch events
(function() {
	var grabbed = false, moved = false, cPos, pPos;
	var lastMoveTime, vel, timer;
	var factor = 3e-3;
	function getPos(e) {
		if(e.touches && e.touches.length)
			e = e.touches[0];
		return [e.clientX,e.clientY];
	}
	function stopMomentum() { cancelAnimationFrame(timer); timer = null; }
	function mouseDown(e) {
		if(grabbed) return;
		// if(!e.touches)
		// 	e.preventDefault();
		stopMomentum();
		cPos = pPos = grabbed = getPos(e);
		moved = false;
	}
	function mouseMove(e) {
		if(!grabbed) return;
		var pos = getPos(e);
		var dx = grabbed[1] - pos[1], dy = pos[0] - grabbed[0];
		if(!moved) {
			if(dx * dx + dy * dy < 16) return;
			moved = true;
			autoRot.checked = false;
			// rotBase = getRotMatrix(getComputedStyle(cube).getPropertyValue('transform'));
			// rot = [0,0,0];
			setBase();
		}
		lastMoveTime = Date.now();
		pPos = cPos; cPos = pos;
		rot = [dx * factor, dy * factor, 0];
	}
	function mouseUp(e) {
		if(!grabbed) return;
		grabbed = false;
		if(!moved) return;
		var f = Math.max(0, 1 - (Date.now() - lastMoveTime) / 200);
		vel = [(pPos[1] - cPos[1]) * factor * f, (cPos[0] - pPos[0]) * factor * f];
		timer = requestAnimationFrame(momentum);
	}
	function momentum() {
		if(Math.abs(vel[0]) < .001 && Math.abs(vel[1]) < .001)
			return;
		var decay = .97;
		vel[0] *= decay; vel[1] *= decay;
		rot[0] += vel[0]; rot[1] += vel[1];
		if(timer)
			timer = requestAnimationFrame(momentum);
	}
	document.addEventListener('mousedown', mouseDown);
	document.addEventListener('mousemove', mouseMove);
	document.addEventListener('mouseup', mouseUp);
	document.addEventListener('click', function(e) { if(!moved) return; e.preventDefault(); e.stopPropagation(); }, true);
	document.addEventListener('touchstart', mouseDown);
	document.addEventListener('touchmove', mouseMove);
	document.addEventListener('touchend', mouseUp);
})();
              
            
!
999px

Console