JavaScript
语言:
JaveScriptBabelCoffeeScript
确定
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 = 2e-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 = .95;
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);
})();