cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

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.

            
              <label><input checked id='autoRotate' type='checkbox'> Auto rotation</label>
<div id='debug'></div>
<canvas id='draw'></canvas>
            
          
!
            
              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;
}
            
          
!
            
              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
Close

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

Loading ..................

Console