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

'); } 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); })();

!