球面上にN個の点を均等に配置したい。その8(正二十面体を細かく分割)
正二十面体を細かく分解してみた。なにげに綺麗に分散された。
しかし、座標計算に時間がかかるようで微妙だ。
点の他にワイヤーフレームもつけてみた。
<!DOCTYPE html> <meta charset="UTF-8"> <title>N個の点を持つ球体を描画する</title> <style> </style> <body> <canvas width="600" height="600"></canvas> <script> /* //https://www.jstage.jst.go.jp/article/geoinformatics/12/1/12_1_3/_pdf 図形・幾何学の英語 http://mage8.com/tango/tango37.html */ (function () { var pi = Math.PI; var acos = Math.acos; var atan2 = Math.atan2; var sqrt = Math.sqrt; var sin = Math.sin; var cos = Math.cos; //モデルを定義する function Model (vertex, surface, option) { this.vertex = vertex || [ ]; //頂点 this.surface = surface || [ ]; //面 this.option = option || [ ]; //オプション } //面を定義する function Surface (pointList) { this.pointList = pointList; } //頂点を定義する function Point (p) { this.x = p.x; this.y = p.y; this.z = p.z; } //面を追加する function addSurface (surface) { var len = this.surface.length; this.surface.push (surface); return len; } //頂点リストに追加する function addVertex (vertex) { var len = this.vertex.length; this.vertex.push (vertex); return len; } function getPoint (p) { return [p.x, p.y, p.z]; } function getViewPoint () { return this.vertex.map (getPoint); } Model.prototype.addVertex = addVertex; Model.prototype.addSurface = addSurface; Model.prototype.getViewPoint = getViewPoint; //2つのベクトルと半径から中間点の座標を返す function getSphereOnPoint (v, rr) { var a = v.x * v.x + v.y * v.y + v.z * v.z; var t = Math.sqrt (4 * a * rr) / (2 * a); return { x: v.x * t, y: v.y * t, z: v.z *t }; } //2つのベクトルの中間を返す function getMidpoint (v0, v1) { return {x: (v0.x + v1.x) / 2, y: (v0.y + v1.y) / 2, z: (v0.z + v1.z) / 2}; } function calcLength (x, y, z, rr) { var a = x * x + y * y + z * z, b = 0, c = -rr; var t = .5*Math.sqrt(-4*a*c)/a; return {x: x * t, y: y * t, z: z * t}; } function createObject (arg) { return new this (arg); } //正二十面体を定義する function RegularIcosahedron (r) { var gr = (1 + sqrt (5)) / 2; var a = r / sqrt (1 + gr * gr); var b = a * gr; var model = new Model (); //物体の頂点 var point = [ {x: 0, y: -a, z: -b}, {x: 0, y: a, z: -b}, {x: 0, y: -a, z: b}, {x: 0, y: a, z: b}, {x: -b, y: 0, z: -a}, {x: -b, y: 0, z: a}, {x: b, y: 0, z: -a}, {x: b, y: 0, z: a}, {x: -a, y: -b, z: 0}, {x: a, y: -b, z: 0}, {x: -a, y: b, z: 0}, {x: a, y: b, z: 0} ].map (createObject, Point); //辺の順序が面を現す var surface = [ [ 0, 1, 6], [ 1, 0, 4], [ 2, 3, 5], [ 3, 2, 7], [ 4, 5,10], [ 5, 4, 8], [ 6, 7, 9], [ 7, 6,11], [ 8, 9, 2], [ 9, 8, 0], [10,11, 1], [11,10, 3], [ 0, 6, 9], [ 0, 8, 4], [ 1, 4,10], [ 1,11, 6], [ 2, 5, 8], [ 2, 9, 7], [ 3, 7,11], [ 3,10, 5] ].map (createObject, Surface); return new Model (point, surface, { radius: r }); } function SplitTriangle () { var r = this.option.radius; var surface = this.surface; var vertex = this.vertex; var newSurface = [ ]; var i, s, a, b, c, d, e, f, dn, en, fn; var ax, ay, az; var rr = r * r; for (i = 0; s = surface[i]; i += 1) { //基本となる3点 a = vertex[s.pointList[0]]; b = vertex[s.pointList[1]]; c = vertex[s.pointList[2]]; d = getSphereOnPoint (getMidpoint (a, b), rr); e = getSphereOnPoint (getMidpoint (b, c), rr); f = getSphereOnPoint (getMidpoint (c, a), rr); dn = this.addVertex (d); en = this.addVertex (e); fn = this.addVertex (f); newSurface.push ([ new Surface ([s.pointList[0], dn, fn]), new Surface ([dn, s.pointList[1], en]), new Surface ([en, s.pointList[2], fn]), new Surface ([dn, en, fn]) ]); } this.surface = Array.prototype.concat.apply ([ ], newSurface); } function create (n, r) { var model = RegularIcosahedron (r); var i, s, b; for (i = 0; i < n; i += 1) { SplitTriangle.call (model); } return model; } this.create = create; }) (); //___________________________________ (function () { var INIT_QUATERNION = [1, 0, 0, 0]; function RotationController (element) { this.target = element; this.mouseX = null;//マウス座標の基点 this.mouseY = null;//マウス座標の基点 this.touchF = false; //ドラッグ中か? this.Qnow = INIT_QUATERNION; //今回のマウスのドラッグ中のクォータニオン this.Qbef = INIT_QUATERNION; //前回のクォータニオン this.rots = INIT_QUATERNION; //今回と前回のクォータニオンの積(これが重要) this.gain = 1 / element.offsetWidth ; // mouse移動の感度 this.dx = 0;//マウスの慣性移動量 this.dy = 0;//マウスの慣性移動量 this.timerId = null;//慣性移動中のタイマーID this.miniInertia = 1e-7;//慣性移動量の最小値 } //画面の2次元移動量から3次元の回転量を求める 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 (point) { var i, j, x, y, z; var p, 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; p = point[i]; i++) { x = p[0], y = p[1], z = p[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 = [ 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 'mouseout' : 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' : event.preventDefault ();//ipadなどでスクロールさせないため 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; break; } } // 要素にイベントを追加する function addEvent (event_type) { this.target.addEventListener (event_type, this, false); } // オブジェクトの生成 function create (target) { if (1 > arguments.length) throw new Error ('引数がない'); var obj = new RotationController (target); var event_list = window.TouchEvent //touchイベントがあるなら優先 ? ['touchstart', 'touchend', 'touchmove'] : ['mousedown', 'mouseup', 'mousemove', 'mouseout']; canvas = null;// メモリーリークパターンを断ち切る event_list.forEach (addEvent, obj); return obj; } //__ RotationController.prototype.handleEvent = handleEvent; RotationController.prototype.quaternionRotation = quaternionRotation; //__ RotationController.create = create; this.RotationController = RotationController; }) (); function canvasDrawCreate (canvas) { var ctx = canvas.getContext ('2d'); var w = canvas.width; var h = canvas.height; var cx = w / 2; var cy = h / 2; var z = 1000; var opmax = 255; return function (ary, surface) { var x = [], y = [], p, a, b, s, i, j; ctx.fillStyle = 'RGBA(255,255,255,1)'; ctx.fillRect (0,0, w, h); for (i = 0; i < ary.length; i++) { var px = ary[i][0]; var py = ary[i][1]; var pz = ary[i][2]; var zz = (z - pz) / z; var op = -(pz - 400) / z; var alpha = Math.min (Math.max (0, op), 1); x[i] = cx + px * zz; y[i] = cy - py * zz; } ctx.strokeStyle = 'rgba(0,0,255,.2)'; for (i = 0; s = surface[i]; i += 1) { p = s.pointList; ctx.beginPath(); ctx.moveTo (x[p[0]], y[p[0]]); for (j = 1; j < p.length; j += 1) { ctx.lineTo (x[p[j]], y[p[j]]); } ctx.stroke(); } ctx.fillStyle = 'rgba(255,0,0,1)'; for (i = 0; i < ary.length; i += 1) {ctx.fillRect (x[i]-.5,y[i]-1, 2, 2);} }; } var loop = (function () { var target = document.querySelector ('canvas'); var ctl = RotationController.create (target); var model = create (2, 200); //球面の点の数と半径 var draw = canvasDrawCreate (target); var p = model.getViewPoint(); return function () { var ps_ = ctl.quaternionRotation (p); draw (ps_, model.surface); }; })(); setInterval (loop, 1000/30); //タイマーで呼び出す </script>