Canvasで網目模様を描く方法を教えてください。その6
球体をマウスドラッグにより回転させる。しかも慣性によりマウスアウトしたあとも少し回る。
<!DOCTYPE html> <meta charset="UTF-8"> <title>3D</title> <style> canvas { background : #000; } </style> <body> <canvas width="1024" height="768"></canvas> <script> //http://jsdo.it/totetero/without_polygon //http://stardustcrown.com/reading/rotate3d.html //http://d.hatena.ne.jp/gyuque/20090211 (function () { var INIT_QUATERNION = [1, 0, 0, 0]; function CanvasController (canvas) { this.canvas = canvas; this.mouseX = null; this.mouseY = null; this.touchF = false; //ドラッグ中か? this.Qnow = INIT_QUATERNION; //今回のマウスのドラッグ中のクォータニオン this.Qbef = INIT_QUATERNION; //前回のクォータニオン this.rots = INIT_QUATERNION; //今回と前回のクォータニオンの積(これが重要) this.gain = canvas.width / 2; // mouse移動の感度 this.dx = 0; this.dy = 0; this.timerId = null; this.miniInertia = 1e-7; } //回転 function rotation (dx, dy) { var a, b, a0, a1, a2, a3, b0, b1, b2, b3, r, t, as; if (t = dx * dx + dy * dy) { r = Math.sqrt (t); as = Math.sin (r) / r; a = this.Qnow; a0 = a[0]; a1 = a[1]; a2 = a[2]; a3 = a[3]; b0 = dy * as; b1 = dx * as; b3 = Math.cos (r); // クオータニオンによる回転 a = this.Qbef; b = this.Qnow = [ a0 * b3 - a3 * b0 - a2 * b1, a1 * b3 + a3 * b1 - a2 * b0, a2 * b3 + a0 * b1 + a1 * b0, a3 * b3 + a0 * b0 - a1 * b1 ]; //前回(a)と今回(b)のクォータニオンの積 a0 = a[0]; a1 = a[1]; a2 = a[2]; a3 = a[3]; b0 = b[0]; b1 = b[1]; b2 = b[2]; b3 = b[3]; this.rots = [ a0 * b0 - a1 * b1 - a2 * b2 - a3 * b3, a0 * b1 + a1 * b0 + a2 * b3 - a3 * b2, a0 * b2 - a1 * b3 + a2 * b0 + a3 * b1, a0 * b3 + a1 * b2 - a2 * b1 + a3 * b0 ]; this.dx = dx; this.dy = dy; } return t; } //慣性 function inertia () { var distance = rotation.call ( this, this.dx - this.dx / 40, this.dy - this.dy / 40 ); if (this.miniInertia < distance) this.timerId = setTimeout (inertia.bind (this), 33); } //クォータニオンによる座標軍の回転 function quaternionRotation (material) { var i, j, x, y, z; var surface, vertex; var q = this.rots; var q0 = q[0], q1 = q[1], q2 = q[2], q3 = q[3]; var a0, a1, a2, a3; var s = [], rst = []; for (i = 0; surface = material[i]; i++) { for (j = 0, s = []; vertex = surface[j]; j++) { x = vertex[0], y = vertex[1], z = vertex[2]; a0 = q3 * x + q1 * z - q2 * y; a1 = q3 * y + q2 * x - q0 * z; a2 = q3 * z + q0 * y - q1 * x; a3 = -q0 * x - q1 * y - q2 * z; s[j] = [ a0 * q3 - a3 * q0 - a1 * q2 + a2 * q1, a1 * q3 - a3 * q1 - a2 * q0 + a0 * q2, a2 * q3 - a3 * q2 - a0 * q1 + a1 * q0 ]; } rst[i] = s; } return rst; } function handleEvent (event) { var e, x, y, dx, dy, a, b, c, e, r, t; var a0, a1, a2, a3, b0, b1, b2, b3, as; switch (event.type) { case 'mouseup' : case 'touchend' : this.touchF = false; inertia.call (this); break; case 'mousedown' : case 'touchstart' : if (this.timerId) { clearTimeout (this.timerId); this.timerId = null; } this.touchF = true; this.Qnow = INIT_QUATERNION; this.Qbef = this.rots; e = event.target.getBoundingClientRect (); this.mouseX = event.pageX - e.left; this.mouseY = event.pageY - e.top; break; case 'mousemove' : case 'touchmove' : e = event.target.getBoundingClientRect (); x = event.pageX - e.left; y = event.pageY - e.top; if (this.touchF){ dx = (x - this.mouseX) / this.gain; dy = (y - this.mouseY) / this.gain; rotation.call (this, dx, dy); } this.mouseX = x; this.mouseY = y; event.preventDefault();//ipadなどでスクロールさせないため break; } } // canvas にイベントを追加する function addCanvasEvent (event_type) { this.canvas.addEventListener (event_type, this, false); } function create (canvas) { if (1 > arguments.length) throw new Error ('引数がない'); if ('CANVAS' !== canvas.tagName) throw new Error ('Canvas 要素ではない'); var obj = new CanvasController (canvas); ['mousedown', 'mouseup', 'mousemove','touchstart', 'touchend', 'touchmove'] .forEach (addCanvasEvent, obj); canvas = null;// メモリーリークパターン return obj; } //__ CanvasController.prototype.handleEvent = handleEvent; CanvasController.prototype.quaternionRotation = quaternionRotation; //__ CanvasController.create = create; this.CanvasController = CanvasController; }) (); function ball (r, n, n1) { var rst = [], a = [], b = [], c = []; var pi = Math.PI, sin = Math.sin, cos = Math.cos; var k = 2 * pi / n1, k2 = pi / n; var i, i_, j, s, r2, yb; var r2 = sin (k2) * r, h2 = cos (k2) * r, yr = r2, yt = h2; for (i = 0; i <= n1; i++) { s = k * i; a[i] = sin (s); b[i] = cos (s); } for (i = 0; i < n1; i++) { rst[i] = [ [0, r, 0], [a[i] * r2, h2, b[i] * r2], [a[i_= i +1 ] * r2, h2, b[i_] * r2] ]; c[i] = [ [a[i_ = i + 1] * r2, -h2, b[i_] * r2], [a[i] * r2, -h2, b[i] * r2], [0, -r, 0] ]; } for (i = 2; i < n; i++) { s = k2 * i; yr2 = sin (s) * r; yb = cos (s) * r; for (j = 0; j < n1; j++) { rst.push ([ [a[j] * yr, yt, b[j] * yr], [a[j] * yr2, yb, b[j] * yr2], [a[j+1] * yr2, yb, b[j+1] * yr2], [a[j+1] *yr, yt, b[j+1] * yr] ]); } yt = yb; yr = yr2; } return rst.concat (c); } //面の法線ベクトルを求める function crossProduct (ary) { var rst = []; var i, s; var p0, p1, p2; var x0, x1, x2, y0, y1, y2, z0, z1, z2; var px, py, pz, qx, qy, qz; for (i = 0; s = ary[i]; i += 1) { p0 = s[0]; p1 = s[1]; p2 = s[2]; x0 = p0[0]; x1 = p1[0]; x2 = p2[0]; y0 = p0[1]; y1 = p1[1]; y2 = p2[1]; z0 = p0[2]; z1 = p1[2]; z2 = p2[2]; px = x1 - x0; py = y1 - y0; pz = z1 - z0; //p qx = x2 - x1; qy = y2 - y1; qz = z2 - z1; //q rst[i] = [ py * qz - pz * qy, pz * qx - px * qz, px * qy - py * qx ]; } return rst; } //明るさを求める function brightness (ary, v, el) { var lx = v[0], ly = v[1], lz = v[2]; var lv = Math.sqrt (lx * lx + ly * ly + lz * lz); var i, a, x, y, z, rst = []; var c, b = 1 - el; for (i = 0; a = ary[i]; i++) { x = a[0]; y = a[1]; z = a[2]; tv = Math.sqrt (x * x + y * y + z * z); inp = x * lx + y * ly + z * lz; len = inp / (lv * tv); rst[i] = el + Math.cos (Math.acos (len)) * b; } return rst; } //3Dの物体を2Dの座標へと変換する function cov3to2 (m, zz, sc) { var rst = [], a, b, c, d, s, i, j, t; for (i = 0; d = m[i]; i++) { a = d[0]; for (c = [], j = 0; b = a[j]; j++) { c[j] = [ b[0] / (t = (zz + b[2]) / sc), b[1] / t ]; } rst[i] = [c, d[1]]; } return rst; } function canvas_draw_create (c, o) { var ctx = c.getContext ('2d'); var x = c.width / 2 + o[0]; var y = c.height / 2 + o[1]; var rgb = [0, 100,250]; var int = Math.floor; ctx.lineWidth = 1; return { draw: function (a, b) {//面を描く ctx.beginPath (); ctx.fillStyle = 'rgb(' + int (rgb[0] * b) + ',' + int (rgb[1] * b) + ',' + int (rgb[2] * b) + ')'; ctx.moveTo (x + a[0][0], y - a[0][1]); for (i = 1; b = a[i++]; ) ctx.lineTo (x + b[0], y - b[1]); ctx.fill (); ctx.closePath (); }, cls: function () { ctx.clearRect(0, 0, c.width, c.height); } }; } function draw (a, dw) { var i, b, c; dw.cls (); for (i = 0; c = a[i++]; ) {//面の3点だけでベクトルの外積の向きで判断 b = c[0]; if (0 >= ((b[2][0] - b[1][0]) * (b[0][1] - b[1][1]) - (b[2][1] - b[1][1]) * (b[0][0] - b[1][0])) ) dw.draw (b, c[1]); } } /* Model { vertex : array //頂点の集合 surface : array // 頂点の3つを取り、辺の描画の有無、色、模様 */ function rendering_wireFrame (canvas, model) { } var hoge = (function _ () { var z = ball (20, 18, 36); var v = canvas_draw_create (document.querySelector ('canvas'), [0,0]); var h = [1, 1, -1]; var ctrl = CanvasController.create (document.querySelector ('canvas')); return function () { var zz = ctrl.quaternionRotation (z); var bb = brightness (crossProduct (zz), h, .3); for (var i = 0, I = zz.length, d = []; i < I; i++) { d[i] = [zz[i], bb[i]]; } draw (cov3to2 (d, 40, 600), v); }; }) (); setInterval (hoge, 1000/60); </script>