JavaScript
语言:
JaveScriptBabelCoffeeScript
确定
'use strict';
var CSIZE = 500 /* canvas size */ ,
OFF = .5 * CSIZE /* canvas origin offset */ ,
VPOINT = 2 * CSIZE /* vanishing point */ ,
_c = document.querySelector('canvas'),
ct = _c.getContext('2d'),
/* controls */
_frm = document.querySelector('form'),
_btn = document.querySelector('button'),
_auto = document.getElementById('auto'),
_err = document.querySelector('.err');
var s3d = undefined /* 3D shape */ ,
hue = 5 /* hue for 3D shape faces */ ,
rot = [1, 0, 1] /* default rotate axis */ ,
ang = .005 * Math.PI /* default unit rotation */ ,
rID = null /* request ID */ ,
drag = false /* drag status */ ,
sol = false /* status on lock */ ,
/* coords when starting drag */
x0 = null,
y0 = null;
var Point = function Point() {
var x = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0];
var y = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
var z = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2];
var c = [] /* natural set of coords */ ,
/*
* coords flattened to plane,
* taking perspective into account
*/
p = [];
this.setCoords = function(x, y, z) {
var f = z / VPOINT + 1;
c = [x, y, z];
p = [f * x, f * y, z];
};
this.getCoords = function() {
var f = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0];
return f ? c : p;
};
this.setCoords(x, y, z);
};
var Polygon = function Polygon(v) {
var n = v.length /* number of poly vertices */ ,
o = [0, 0, 0] /* central point of poly */ ;
/* recompute central point coordinates */
this.refreshO = function() {
o = [0, 0, 0];
var _loop = function _loop(i) {
o = o.map(function(c, j) {
return c + v[i].getCoords()[j];
});
};
for (var i = 0; i < n; i++) {
_loop(i);
}
};
/* get central point coordinates */
this.getO = function() {
return o;
};
/* draw this polygon */
this.draw = function() {
var _Math;
var
/*
* sign of central point z coord
* + leads to lightening
* - leads to darkening
*/
s = Math.sign(o[2]),
/* fill lightness */
l = 75 + s * (1 - (_Math = Math).hypot.apply(_Math, o.slice(0, 2)) / (.5 * CSIZE)) * 25;
ct.fillStyle = 'hsl(' + hue + ',65%,' + l + '%)';
ct.beginPath();
for (var i = 0; i < n; i++) {
var c = v[i].getCoords(0);
c.splice(2, 1); /* remove the z coord */
ct[(i ? 'line' : 'move') + 'To'].apply(ct, c);
}
ct.closePath();
ct.fill();
ct.stroke();
};
};
var S6hedron = function S6hedron() {
var rc = arguments.length <= 0 || arguments[0] === undefined ? .64 * OFF : arguments[0];
var v = [] /* array to store vertices */ ,
f = [] /* array to store faces */ ,
nf = 6 /* no. of faces */ ,
nv = 8 /* no of vertices */ ;
/*
* rotate the faces in 3D
* a = angle of rotation
* r = vector around which rotation happens
*/
this.rot = function() {
var a = arguments.length <= 0 || arguments[0] === undefined ? ang : arguments[0];
var r = arguments.length <= 1 || arguments[1] === undefined ? rot : arguments[1];
/*
* explanation for variables
* http://stackoverflow.com/a/15208858/1397351
*/
var sc = .5 * Math.sin(a),
db = 1 - Math.cos(a) /* double sq */ ,
sq = .5 * db,
/* get the x*y*sq, y*z*sq, z*x*sq in SO answer */
b = r.map(function(c, i, a) {
return c * a[(i + 1) % 3] * sq;
}),
/* get the x*sc, y*sc, z*sc in SO answer */
c = r.map(function(c) {
return c * sc;
}),
m = [ /* the transform matrix */
[1 - db * (r[1] * r[1] + r[2] * r[2]), 2 * (b[0] - c[2]), 2 * (b[2] + c[1])],
[2 * (b[0] + c[2]), 1 - db * (r[0] * r[0] + r[2] * r[2]), 2 * (b[1] - c[0])],
[2 * (b[2] - c[1]), 2 * (b[1] + c[0]), 1 - db * (r[0] * r[0] + r[1] * r[1])]
];
var _loop2 = function _loop2(i) {
var _v$i;
var /* coords before the rotation */
c0 = v[i].getCoords(),
/*
* multiply matrix m to vector c0
* see https://codepen.io/thebabydino/pen/WoxvPy
* to get coords after the rotation
*/
c1 = m.map(function(c) {
return c.map(function(c, i) {
return c * c0[i];
}).reduce(function(p, c) {
return p + c;
}, 0);
});
(_v$i = v[i]).setCoords.apply(_v$i, c1);
};
for (var i = 0; i < nv; i++) {
_loop2(i);
}
/* compute central point for each face */
/* all convex, so it's enough :P */
for (var i = 0; i < nf; i++) {
f[i].refreshO();
}
sortFaces();
this.draw();
};
this.draw = function() {
ct.clearRect(-OFF, -OFF, CSIZE, CSIZE);
for (var i = 0; i < nf; i++) {
f[i].draw();
}
};
/*
* reorder faces in array based on
* z coord values
* of the faces' central points
*/
function sortFaces() {
f.sort(function(a, b) {
if (a.getO()[2] > b.getO()[2]) return 1;
if (a.getO()[2] < b.getO()[2]) return -1;
return 0;
});
};
/*
* create each face from vertices
* and add each face to f array
*/
function addFaces() {
var /* number of vertices per face */
nvf6 = 4;
f.push(new Polygon(v.slice(0, nvf6)));
f.push(new Polygon(v.slice(nvf6, nv)));
for (var i = 0; i < nvf6; i++) {
f.push(new Polygon([v[i], v[(i + 1) % nvf6], v[nvf6 + (i + 1) % nvf6], v[nvf6 + i]]));
}
};
/*
* compute coords for each vertex
* and add each vertex to v array
*/
(function addVertices() {
var /* cube inradius */
ri6 = rc / Math.sqrt(3),
/* cube face circumradius */
rcf6 = ri6 * Math.SQRT2,
/* number of vertices per face */
nvf6 = 4,
/* face base angle */
baf6 = 2 * Math.PI / nvf6;
/* 2 vertices on top, 2 at the bottom */
for (var i = 0; i < 2; i++) {
/* y coord */
var y = Math.round(Math.pow(-1, i) * ri6);
for (var j = 0; j < nvf6; j++) {
var /* current angle */
ca = j * baf6,
x = Math.round(rcf6 * Math.cos(ca)) /* x coord */ ,
z = Math.round(rcf6 * Math.sin(ca)); /* z coord */
v.push(new Point(x, y, z));
}
}
addFaces();
})();
};
function ani() {
s3d.rot();
rID = requestAnimationFrame(ani);
};
function getE(ev) {
return ev.touches ? ev.touches[0] : ev;
};
/*
* stop cube animation if necessary
* in order to start drag
*/
function lock(ev) {
if (ev.target === _c) {
var e = getE(ev);
drag = true;
x0 = e.clientX;
y0 = e.clientY;
if (rID) {
sol = true;
_auto.checked = false;
cancelAnimationFrame(rID);
rID = null;
}
}
};
/* act on drag, rotate cube */
function act(ev) {
if (drag) {
var e = getE(ev),
x = e.clientX,
y = e.clientY,
dx = x - x0,
dy = y - y0,
d = Math.hypot(dx, dy);
if (d) {
var i = (-dy / d).toFixed(5) /* x comp of rot axis */ ,
j = (dx / d).toFixed(5) /* y comp of rot axis */ ,
a = (.0035 * d).toFixed(2) /* rotation angle in rad */ ;
s3d.rot(a, [i, j, 0]);
}
x0 = x;
y0 = y;
}
};
/*
* stop drag, restart autorotation if necessary
*/
function release() {
if (drag) {
drag = false;
x0 = y0 = null;
if (sol && !rID) {
_auto.checked = true;
sol = false;
ani();
}
}
};
/* what to do when slider values change */
function update(e) {
var _Math2;
var tg = e.target,
d = undefined;
switch (true) {
case tg.id == 'hue':
hue = ~~tg.value;
break;
case tg.id == 'alpha':
ct.globalAlpha = .01 * tg.value;
break;
case tg.id == 'w':
ang = .0005 * tg.value * Math.PI;
break;
case tg.id.indexOf('axis') > -1:
var i = 1 * tg.id.split('axis')[1],
copy = rot.slice();
copy[i] = .01 * tg.value;
d = (_Math2 = Math).hypot.apply(_Math2, copy);
if (d === 0) {
tg.value = 10;
_err.classList.add('show');
} else rot = copy.map(function(c) {
return c / d;
});
break;
}
};
/* fade the "drag me!" info in or out */
function fadeInf() {
document.body.classList.toggle('faded');
};
/* init demo */
(function init() {
/* initially have the "drag me!" info faded */
document.body.classList.add('faded');
/* set canvas dimensions */
_c.width = _c.height = CSIZE;
/* put 0,0 point in the middle of canvas */
ct.translate(OFF, OFF);
/* set global alpha of strokes and fills */
ct.globalAlpha = .87;
/* set rgba value for edges */
ct.strokeStyle = 'rgba(0,0,0,.1)';
/* create cube object */
s3d = new S6hedron();
/* start animation */
ani();
})();
addEventListener('mousedown', lock, false);
addEventListener('touchstart', lock, false);
addEventListener('mousemove', act, false);
addEventListener('touchmove', act, false);
addEventListener('mouseup', release, false);
addEventListener('touchend', release, false);
_auto.addEventListener('change', function(e) {
if (_auto.checked && !rID) ani();
if (!_auto.checked && rID) {
cancelAnimationFrame(rID);
rID = null;
}
}, false);
_btn.addEventListener('click', function(e) {
_frm.classList.toggle('closed');
}, false);
addEventListener('input', update, false);
addEventListener('change', update, false);
_c.addEventListener('mouseover', fadeInf, false);
_c.addEventListener('mouseout', fadeInf, false);
_err.addEventListener('animationend', function(e) {
_err.classList.remove('show');
}, false);