HTML5绘制一个3D立方体,HTML5/Canvas交互式3D立方体

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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值