<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);
})();
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.
Also see: Tab Triggers