球面上にN個の点を均等に配置したい。その6 / 「多数の点を球面上に一様に分布させるソフトウェア」(山路敦)を JavaScriptに移植(コアな部分だけ)してみる
理論とか面倒なことは理解できないのだが、それにしても Pascal言語が理解しづらい。
そしてこんな理論を考えられる人が、なんで配列の操作を0からではなく1から始めたのかわからない
なので、Javascriptらしく書き直す予定。
iPad でも動くよ。下の5種類は、個別に回転操作ができます。
オブジェクト指向って、すばらしい!
<!DOCTYPE html> <meta charset="UTF-8"> <title>N個の点を持つ球体を描画する</title> <style> </style> <body> <canvas width="600" height="600"></canvas> <script> //http://d.hatena.ne.jp/MikuHatsune/20160714/1468397633 //https://www.jstage.jst.go.jp/article/geoinformatics/12/1/12_1_3/_pdf (function () { var pi = Math.PI; var asin = Math.asin; var acos = Math.acos; var atan2 = Math.atan2; var sqrt = Math.sqrt; var sin = Math.sin; var cos = Math.cos; var sq2 = sqrt (2); //const var Max_Number_of_point = 5000; var Min_Number_of_point = 20; var Relocates1 = null; var RelocatedN = null; var GSS = [ ]; var Number_of_point = null; var NP_on_hemisphere = null; //Tfrom_Mauin_Edit_NPKeyUp function create (n, r) { var k, hk; var gss = [{ lon: 0, col: pi }]; if ((n < Min_Number_of_point) || (n > Max_Number_of_point)) { throw new Error ([ Min_Number_of_point, ' < N < ', Max_Number_of_point ].join ('')); } Number_of_point = n; Generate_GSS (); TFrom_Main_RelocateFirstPoint (); return GSS.map (Spherical2Cartesian).map (function (p){return [p.x*r,p.y*r,p.z*r];}); } function Generate_GSS () { var k, hk; GSS = [ ]; GSS[1] = { }; GSS[1].lon = 0; GSS[1].col = pi; for (k = 2; k <= Number_of_point - 1; k += 1) { hk = -1 + 2 * (k - 1) / (Number_of_point - 1); GSS[k] = { }; GSS[k].col = acos (hk); GSS[k].lon = GSS[k - 1].lon + 3.6 / sqrt (Number_of_point) / sqrt (1 - hk * hk); } GSS[Number_of_point] = { }; GSS[Number_of_point].lon = 0; GSS[Number_of_point].col = 0; } function Spherical2Cartesian (p) { return { x: sin (p.col) * cos (p.lon), y: sin (p.col) * sin (p.lon), z: cos (p.col) }; } function Cartesian2Spherical (p) { return { col: acos (p.z / sqrt (p.x * p.x + p.y * p.y + p.z * p.z)), lon: atan2 (p.y, p.x) }; } function Equal_area_projection_of_lower_hemisph (p) { var r = sq2 * sin (pi / 4 - (p.col - pi / 2) / 2); return { x: r * cos (p.lon), y: r * sin (p.lon) }; } function TFrom_Main_RelocateFirstPoint () { var five_points = new Array (5); var mean = { }; var i; five_points[1] = 2; five_points[2] = 3; five_points[3] = 5; five_points[4] = 6; five_points[5] = 7; mean.x = 0; mean.y = 0; mean.z = 0; for (i = 1; i <= 5; i += 1) { mean = vector_sum (mean, Spherical2Cartesian (GSS[five_points[i]])); } Relocates1 = Cartesian2Spherical (mean); mean.x = 0; mean.y = 0; mean.z = 0; for (i = 1; i <= 5; i += 1) { mean = vector_sum (mean, Spherical2Cartesian (GSS[Number_of_point - five_points[i] + 1])); } RelocatedN = Cartesian2Spherical (mean); GSS[0] = Relocates1; GSS[Number_of_point] = RelocatedN; } function vector_sum (a, b) { return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }; } 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) { ctx.fillStyle = 'RGBA(255,255,255,1)'; ctx.fillRect (0,0, w, h); for (var 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); ctx.fillStyle = 'rgba(0,0,255,' + alpha + ')'; ctx.fillRect (cx + px * zz, cy - py * zz, 3, 3); } }; } var loop = (function () { var target = document.querySelector ('canvas'); var ctl = RotationController.create (target); var ps = create (500, 200); //球面の点の数と半径 var draw = canvasDrawCreate (target); return function () { var ps_ = ctl.quaternionRotation (ps); draw (ps_); }; })(); setInterval (loop, 1000/30); //タイマーで呼び出す </script>
「多数の点を球面上に一様に分布させるソフトウェア」(山路敦)を JavaScriptに移植
https://www.jstage.jst.go.jp/article/geoinformatics/12/1/12_1_3/_pdf
専門用語はわからないが、2つの座標系があり、その両方に行ったりきたりで複雑化していた。
なのでばっさりカット。極の座標の平均は、今風に。
<!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 (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 sum_ag3 (a, b) { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; } function create (n, r) { var five_points = [0, 1, 3, 4, 5]; var rst = [ ]; var sqn = sqrt (n); var i, t, t0, t1, t2; var s, e; var p = 0; for (i = 0; i < n - 1; i += 1) { t = -1 + 2 * (i + 1) / (n - 1); t0 = acos (t); t2 = sin (t0) * r; p = p + 3.6 / sqn / sqrt (1 - t * t); rst.push ([ t2 * cos (p), t2 * sin (p), cos (t0) * r ]); } s = five_points .map (function (i) { return rst[i]; }) .reduce (sum_ag3); e = five_points .map (function (i) { return rst[n - i - 2]; }) .reduce (sum_ag3); return Array.prototype.concat.call ([s], rst, [e]); } 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) { ctx.fillStyle = 'RGBA(255,255,255,1)'; ctx.fillRect (0,0, w, h); for (var 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); ctx.fillStyle = 'rgba(0,0,255,' + alpha + ')'; ctx.fillRect (cx + px * zz, cy - py * zz, 3, 3); } }; } var loop = (function () { var target = document.querySelector ('canvas'); var ctl = RotationController.create (target); var ps = create (1000, 200); //球面の点の数と半径 var draw = canvasDrawCreate (target); return function () { var ps_ = ctl.quaternionRotation (ps); draw (ps_); }; })(); setInterval (loop, 1000/30); //タイマーで呼び出す </script>
五種類の方法を表示させてみる
<!DOCTYPE html> <meta charset="UTF-8"> <title>N個の点を持つ球体を描画する</title> <style> ol li { display: inline-block; width: 460px; height: 460px; border: 2px red solid; margin: 2px; } h2 { margin: 0; font-size: large; } </style> <body> <ol> <li> <h2>Type A</h2> <canvas width="400" height="400"></canvas> <li> <h2>Type B</h2> <canvas width="400" height="400"></canvas> <li> <h2>Type C</h2> <canvas width="400" height="400"></canvas> <li> <h2>Type D</h2> <canvas width="400" height="400"></canvas> <li> <h2>Type E</h2> <canvas width="400" height="400"></canvas> </ol> <script> (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; }) (); //http://d.hatena.ne.jp/ryamada/20130910/1378959553 //フィボナッチ格子を球面に展開 //http://d.hatena.ne.jp/MikuHatsune/20160714/1468397633 (function () { var round = Math.round; var pow = Math.pow; var sin = Math.sin; var cos = Math.cos; var asin = Math.asin; var acos = Math.acos; var sqrt = Math.sqrt; var sqrt5 = sqrt (5); var goldR = (1 + sqrt5) / 2; var pi = Math.PI; var phi_ = 2 * pi * (goldR - 1); // フィボナッチ関数 function fibonacci (n) { return (n < 0) ? round (pow (goldR, -n) / sqrt5) * ((n&1) ? 1: -1) : round (pow (goldR, n) / sqrt5); } //フィボナッチ格子を球面に展開(有限法) //http://d.hatena.ne.jp/ryamada/20130910/1378959553 function create_type_A (n, r) { var kn = fibonacci (n); var rst = [ ]; var k, x, y, sqx2r, pi2y; for (k = 0; k < kn; k += 1) { x = k / fibonacci (n); y = (k * fibonacci (n-1) / fibonacci (n)) % 1; sqx2r = sqrt (x - x * x) * 2 * r; pi2y = 2 * pi * y; rst[k] = [ cos (pi2y) * sqx2r, sin (pi2y) * sqx2r, (1 - 2 * x) * r ]; } return rst; } //フィボナッチ格子を球面に展開(球面らせん法) //http://d.hatena.ne.jp/ryamada/20130910/1378959553 function create_type_B (n, r) { var rst = [ ]; var i, theta, phi; var n = fibonacci (n); var n2 = n / 2; var n3 = n + 1; var ctr; for (i = -n2; i <= n2; i += 1) { theta = asin (2 * i / n3); phi = phi_ * i; ctr = cos (theta) * r; rst.push ([ ctr * cos (phi), ctr * sin (phi), sin (theta) * r ]); } return rst; } //だいたいN個の点を半径1の球面上に「ほぼ均一に配置する」 //http://d.hatena.ne.jp/ryamada22/20080831 function create_type_D (n, r) { var rst = [ ]; var t = pi * (goldR - 2); var i, a, b, c; for (i = -n; i < n; i += 2) { a = asin (i / n); b = cos (a) * r; c = t * i; rst.push ([ b * cos (c), b * sin (c), sin (a) * r ]); } return rst; } //球面上にランダムで置いたプロットをなるべく分散させたい //http://qiita.com/clomie/items/e5dd35dcfcba082b2a7f var random = Math.random; var sqrt = Math.sqrt; function create_type_C (n, r) { var rst = [ ]; var i, x, y, z, rdt, t; for (i = 0; i <= n; i += 1) { z = random () * 2 - 1; rdt = random () * 360; t = sqrt (1 - z * z) * r; rst[i] = [ t * cos (rdt), t * sin (rdt), r * z ]; } return rst; } function sum_ag3 (a, b) { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; } function create_type_E (n, r) { var five_points = [0, 1, 3, 4, 5]; var rst = [ ]; var sqn = sqrt (n); var i, t, t0, t1, t2; var s, e; var p = 0; for (i = 0; i < n - 1; i += 1) { t = -1 + 2 * (i + 1) / (n - 1); t0 = acos (t); t2 = sin (t0) * r; p = p + 3.6 / sqn / sqrt (1 - t * t); rst.push ([ t2 * cos (p), t2 * sin (p), cos (t0) * r ]); } s = five_points .map (function (i) { return rst[i]; }) .reduce (sum_ag3); e = five_points .map (function (i) { return rst[n - i - 2]; }) .reduce (sum_ag3); return Array.prototype.concat.call ([s], rst, [e]); } function create (type, n, r) { var rst = null; switch (type) { case 'a' : rst = create_type_A; break; case 'b' : rst = create_type_B; break; case 'c' : rst = create_type_C; break; case 'd' : rst = create_type_D; break; case 'e' : rst = create_type_E; break; } return rst (n, r); } this.createSpherePoint = create; }) (); 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) { ctx.fillStyle = 'RGBA(255,255,255,1)'; ctx.fillRect (0,0, w, h); for (var 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); ctx.fillStyle = 'rgba(0,0,255,' + alpha + ')'; ctx.fillRect (cx + px * zz, cy - py * zz, 3, 3); } }; } var loop = (function () { var cv = document.querySelectorAll ('canvas'); A: var ac = RotationController.create (cv[0]); var ap = createSpherePoint ('a', 17, 180); var ad = canvasDrawCreate (cv[0]); B: var bc = RotationController.create (cv[1]); var bp = createSpherePoint ('b', 17, 180); var bd = canvasDrawCreate (cv[1]); C: var cc = RotationController.create (cv[2]); var cp = createSpherePoint ('c', 1597, 180); var cd = canvasDrawCreate (cv[2]); D: var dc = RotationController.create (cv[3]); var dp = createSpherePoint ('d', 1597, 180); var dd = canvasDrawCreate (cv[3]); E: var ec = RotationController.create (cv[4]); var ep = createSpherePoint ('e', 1597, 180); var ed = canvasDrawCreate (cv[4]); return function () { ad (ac.quaternionRotation (ap)); bd (bc.quaternionRotation (bp)); cd (cc.quaternionRotation (cp)); dd (dc.quaternionRotation (dp)); ed (ec.quaternionRotation (ep)); }; })(); setInterval (loop, 1000/30); //タイマーで呼び出す </script>