babu_babu_babooのごみ箱

2017-01-12

球面上にN個の点を均等に配置したい。その9(立方体を細かく分割)

20:49

立方体の個々の面を4分割して球に近づけてみた

綺麗になるかと思ったら形が歪になった。これは失敗。

ワイヤーフレームも描く


f:id:babu_babu_baboo:20170112204656p:image

<!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 Squares (r) {
    var a = r / Math.sqrt (2)/ Math.sqrt (2);

    var point = [
      {x: -a, y: a, z: -a}, {x: a, y: a, z: -a}, {x: a, y: a, z: a}, {x: -a, y: a, z: a },
      {x: -a, y: -a, z: -a}, {x: a, y: -a, z: -a}, {x: a, y: -a, z: a}, {x: -a, y: -a, z: a }
    ].map (createObject, Point);

    var surface = [
      [0,1,2,3,0], [0,4,5,1], [1,5,6,2], [2,6,7,3], [3,7,4,0], [4,7,6,5]
    ].map (createObject, Surface);

    return new Model (point, surface, { radius: r });

  }

  //正二十面体を定義する
  function RegularIcosahedron (r) {
    var gr = (1 + sqrt (5)) / 2;
    var a = r / sqrt (1 + gr * gr);
    var b = a * gr;

    //物体の頂点
    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 SplitSquares () {
    var r = this.option.radius;
    var surface = this.surface;
    var vertex = this.vertex;
    var newSurface = [ ];
    var i, s0,s1,s2,s3,t0,t1,t2,t3,t4,n0,n1,n2,n3,n4;
    var ax, ay, az;
    var rr = r * r;

    for (i = 0; s = surface[i]; i += 1) {
      //基本となる4点
      s0 = vertex[s.pointList[0]];
      s1 = vertex[s.pointList[1]];
      s2 = vertex[s.pointList[2]];
      s3 = vertex[s.pointList[3]];

      t0 = getSphereOnPoint (getMidpoint (s0, s1), rr);
      t1 = getSphereOnPoint (getMidpoint (s1, s2), rr);
      t2 = getSphereOnPoint (getMidpoint (s2, s3), rr);
      t3 = getSphereOnPoint (getMidpoint (s3, s0), rr);
      t4 = getSphereOnPoint (getMidpoint (s0, s2), rr);

      n0 = this.addVertex (t0);
      n1 = this.addVertex (t1);
      n2 = this.addVertex (t2);
      n3 = this.addVertex (t3);
      n4 = this.addVertex (t4);

      newSurface.push ([
        new Surface ([s.pointList[0], n0, n4, n3]),
        new Surface ([s.pointList[1], n1, n4, n0]),
        new Surface ([s.pointList[2], n2, n4, n1]),
        new Surface ([s.pointList[3], n3, n4, n2]),
      ]);
    }
    this.surface = Array.prototype.concat.apply ([ ], newSurface);
  }


  function create (type, n, r) {
    var model;
    var i, s, b;

    switch (type) {
    case 'Squares' :
      model = Squares (r);
      for (i = 0; i < n; i += 1) {
        SplitSquares.call (model);
      }
      break;

    case 'RegularIcosahedron' :
    default :
      model = RegularIcosahedron (r);
      for (i = 0; i < n; i += 1) {
        SplitTriangle.call (model);
      }
      break;
    }

    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 ('Squares', 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>

正十二面体の個々の面を4分割を2回繰り返してできた球。

いまのところこれが一番奇麗。

f:id:babu_babu_baboo:20170112205221p:image

球面上にN個の点を均等に配置したい。その8(正二十面体を細かく分割)

16:13

正二十面体を細かく分解してみた。なにげに綺麗に分散された。

しかし、座標計算時間がかかるようで微妙だ。

点の他にワイヤーフレームもつけてみた。

<!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>

2017-01-09

球面上にN個の点を均等に配置したい。その7(正二十面体を細かく分割)

| 00:42

正二十面体を細かく分割して球体にしてみる

f:id:babu_babu_baboo:20170110004440p:image

分割処理は正三角形を一度に4等分割するので、3回行うと 20 * 4^3 = 1280 個の三角形ができるのだが(頂点はダブっているぞ!)、5回の分割だと Core i7 でもぎりぎり。

プログラム的には命名がおかしいけど、後で見直すこと。


もしかして立方体からできるんじゃね?


その前に、三角形を4つに分割した時にできる3点は、必ずまた使われるのだから

バッファーに蓄えておくべきだよな。

その分割点が再度利用されるのは1回だけだろうか。

なにか良いアイディアはないものか。

<!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 Point (x, y, z) {
    this.x = x || 0;
    this.y = y || 0;
    this.z = z || 0;
  }

  function Surface (/* p0, p1, p.., pn */) {
    this.vertex = Array.prototype.slice.call (arguments, 0);
  }

  //http://www.h6.dion.ne.jp/~ooya/Suugaku/Seitamentai.pdf
  var gr = (1 + sqrt (5)) / 2;

  var p = [
    new Point (0, -1, -gr),  new Point (0, 1, -gr),
    new Point (0, -1, gr),  new Point (0, 1, gr),
    new Point (-gr, 0, -1), new Point (-gr, 0, 1),
    new Point (gr, 0, -1),  new Point (gr, 0, 1),
    new Point (-1, -gr, 0), new Point (1, -gr, 0),
    new Point (-1, gr, 0),  new Point (1, gr, 0)
  ];

  var RegularIcosahedron = [
    new Surface (p[0], p[1], p[6]),  new Surface (p[1], p[0], p[4]),
    new Surface (p[2], p[3], p[5]),  new Surface (p[3], p[2], p[7]),
    new Surface (p[4], p[5], p[10]), new Surface (p[5], p[4], p[8]),
    new Surface (p[6], p[7], p[9]),  new Surface (p[7], p[6], p[11]),
    new Surface (p[8], p[9], p[2]),  new Surface (p[9], p[8], p[0]),
    new Surface (p[10],p[11],p[1]),  new Surface (p[11],p[10],p[3]),
    new Surface (p[0], p[6], p[9]),  new Surface (p[0], p[8], p[4]),
    new Surface (p[1], p[4], p[10]), new Surface (p[1], p[11],p[6]),
    new Surface (p[2], p[5], p[8]),  new Surface (p[2], p[9], p[7]),
    new Surface (p[3], p[7], p[11]), new Surface (p[3], p[10],p[5])
  ];


  function splitOfTriangle (s) {
    var ax = s.vertex[0].x, bx = s.vertex[1].x, cx = s.vertex[2].x,
        ay = s.vertex[0].y, by = s.vertex[1].y, cy = s.vertex[2].y,
        az = s.vertex[0].z, bz = s.vertex[1].z, cz = s.vertex[2].z;

    var abx = (ax + bx) / 2, bcx = (bx + cx) / 2, cax = (cx + ax) / 2,
        aby = (ay + by) / 2, bcy = (by + cy) / 2, cay = (cy + ay) / 2,
        abz = (az + bz) / 2, bcz = (bz + cz) / 2, caz = (cz + az) / 2;

    var p0 = new Point (abx, aby, abz),
        p1 = new Point (bcx, bcy, bcz),
        p2 = new Point (cax, cay, caz);

    return [
      new Surface (s.vertex[0], p0, p2),
      new Surface (p0, p1, p2),
      new Surface (p0, s.vertex[1], p1),
      new Surface (p2, p1, s.vertex[2])
    ];
  }

  function getVertex (s) {
    return s.vertex;
  }

  function getPoint (p) {
    var rr = this * this;
    var a = p.x * p.x + p.y * p.y + p.z * p.z, b = 0, c = -rr;
    var t = .5*Math.sqrt(-4*a*c)/a;

    return [p.x * t, p.y * t, p.z * t];
  }

  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 * t, y * t, z * t];
  }


  function create (n, r) {
    var surface = RegularIcosahedron.slice (0); //copy
    var i, a = [ ], p = [ ], t = surface;

    for (i = 0; i < n; i += 1)
      t = Array.prototype.concat.apply ([], t.map (splitOfTriangle));

    p = Array.prototype.concat.apply ([], t.map (getVertex, p)).map (getPoint, r);

    return p;
  }




  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 - 300) / 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, 2.5, 2.5);
    }
  };
}


  var loop = (function () {
    var target = document.querySelector ('canvas');
    var ctl = RotationController.create (target);
    var ps = create (3, 200); //球面の点の数と半径
    var draw = canvasDrawCreate (target);

    return function () {
      var ps_ = ctl.quaternionRotation (ps);
      draw (ps_);
    };
  })();

  setInterval (loop, 1000/30); //タイマーで呼び出す

</script>

球面上にN個の点を均等に配置したい。その6 / 「多数の点を球面上に一様に分布させるソフトウェア」(山路敦)を JavaScriptに移植(コアな部分だけ)してみる

| 10:38

理論とか面倒なことは理解できないのだが、それにしても 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>

五種類の方法を表示させてみる

f:id:babu_babu_baboo:20170109153655j:image

<!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>

2017-01-07

フィボナッチ数を求める関数

| 23:39

求める公式があったとは…

一般的な再帰による求め方をする必要がないのでよいのかもしれない。

マイナスの引数にも対処してみた。

(function () {
  // フィボナッチ関数
  var round = Math.round;
  var pow = Math.pow;
  var sqrt5 = Math.sqrt (5);
  var goldR = (1 + sqrt5) / 2;

  function fibonacci (n) {
    return (n < 0)
    ? round (pow (goldR, -n) / sqrt5) * ((n&1) ? 1: -1)
    : round (pow (goldR, n) / sqrt5);
  }

  this.fibonacci = fibonacci;
}) ();

console.log (
[-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6].map (fibonacci)
);

球面上にN個の点を均等に配置したい。その5(フィボナッチ数個)そしてマウスのドラッグで回転もできる

| 17:11

f:id:babu_babu_baboo:20170108002850p:image

もちろんこれは、夏に向けての花火の種となる

<!DOCTYPE html>
<meta charset="UTF-8">
<title>N個の点を持つ球体を描画する</title>
<style>
  body,canvas { background : black; }
</style>

<body>
<canvas width="1000" height="1000"></canvas>


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

}) ();



(function () {

// フィボナッチ関数
  var round = Math.round;
  var pow = Math.pow;
  var sqrt = Math.sqrt;
  var sqrt5 = sqrt (5);
  var goldR = (1 + sqrt5) / 2;
  var pi = Math.PI;


  function fibonacci (n) {
    return (n < 0)
    ? round (pow (goldR, -n) / sqrt5) * ((n&1) ? 1: -1)
    : round (pow (goldR, n) / sqrt5);
  }


  function create (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] = [
        Math.cos (pi2y) * sqx2r,
        Math.sin (pi2y) * sqx2r,
        (1 - 2 * x) * r
      ];

    }
    return rst;
  }

  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(0,0,0,.25)';
    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 - 600) / z;
      var alpha = Math.min (Math.max (0, op), 1);
      ctx.fillStyle = 'rgba(255,0,0,' + alpha + ')';
      ctx.fillRect (cx + px * zz, cy - py * zz, 2, 2);
    }
  };
}


  var loop = (function () {
    var target = document.querySelector ('canvas');
    var ctl = RotationController.create (target);
    var ps = createSpherePoint (17, 300);
    var draw = canvasDrawCreate (target);

    return function () {
      var ps_ = ctl.quaternionRotation (ps);
      draw (ps_);
    };
  })();

  setInterval (loop, 1000/60); //タイマーで呼び出す
</script>

2017-01-06

Bスプライン曲線を復習する

| 09:46

滑らかな曲線を描く発生器が欲しくなったので再び考える

最終的な目的は、始点が(0,0)で終点が(1,1)、中に2点で全部で4つ。

高速に動作する。もちろんある程度短いことにこしたことはない。

で基本から

<!DOCTYPE html>
<meta charset="utf-8">
<title>B-spline</title>

<body>
<canvas width="500" height="500"></canvas>

<script>
function B_Spline_Generator (point) {
  var m = point.length;
  var n = m - 1;

  return function (t) {
    var cn, i, j, t0, t1, t2;
    var x = 0, y = 0;

    t0 = Math.min (Math.max (0, t), 1) * m - 1;
		for (i = -2; i < m + 2; i += 1) {
      cn = ((t1 = Math.abs (t0 - i)) < 1)
           ? (t2 = 3 * t1 * t1, t1 * t2 - 2 * t2 + 4) / 6
           : (t1 < 2)
             ? (t2 = t1 - 2, t2 * t2 * t2 / -6)
             : 0;

			j = Math.min (Math.max (0, i), n);
			x += point[j][0] * cn;
			y += point[j][1] * cn;
		}
    return {x: x, y: y};
  }
}

//__________________________


(function () {
  var ps =[[30, 90],[231,147],[63,495],[513,129],[459,492]];

  var pointer = B_Spline_Generator (ps);
  var canvas = document.querySelector ('canvas');
  var ctx = canvas.getContext('2d');
  var p, x, y;

  ctx.beginPath ();
  ctx.strokeStyle = 'rgb(255,0,0)';
  for (var i = 0; i <= 1; i += .01) {
    p = pointer (i);
    ctx[i ? 'lineTo': 'moveTo'](p.x, p.y);
  }
  ctx.stroke ();
})();

</script>

気持ち高速にする

<!DOCTYPE html>
<meta charset="utf-8">
<title>B-spline</title>

<body>
<canvas width="500" height="500"></canvas>

<script>
function B_Spline_Generator (x1, y1, x2, y2) {
  var p = [[0,0], [x1,y1], [x2,y2], [1,1]];

  return function (t) {
    var cn, i, t0, t1, t2, p0;
    var x = 0, y = 0;

    t0 = Math.min (Math.max (0, t), 1) * 3;
    for (i = -2; i < 6; i += 1) {
			t1 = Math.abs (t0 - i);

			if (2 <= t1)
			  continue;

			cn = (t1 < 1)
			     ? (t2 = 3 * t1 * t1, t1 * t2 - 2 * t2 + 4) / 6
			     : (t2 = t1 - 2, t2 * t2 * t2 / -6);

			p0 = p[Math.min (Math.max (0, i), 3)];
			x += p0[0] * cn;
			y += p0[1] * cn;

    }

    return {x: x, y: y};
  }
}

//__________________________


(function () {
  var pointer = B_Spline_Generator (1,0,0,1);
  var canvas = document.querySelector ('canvas');
  var ctx = canvas.getContext('2d');
  var p, x, y;

  ctx.beginPath ();
  ctx.strokeStyle = 'rgb(255,0,0)';
  for (var i = 0; i <= 1; i += .01) {
    p = pointer (i);
    ctx[i ? 'lineTo': 'moveTo'](p.x*400, p.y*400);
  }
  ctx.stroke ();
})();

</script>

2017-01-03

canvasで完全二分木を描画したいのですが

| 23:11

canvas の部分は、外側にしろとあれほど・・・

f:id:babu_babu_baboo:20170103232602p:image

<!DOCTYPE html>
<meta charset="utf-8">
<title>n木分</title>

<body>
<canvas width="1000" height="300"></canvas>

<script>


(function () {
  
  // NTreeは Nodeというオブジェクトを木の枝のように、親と子(配列)お持つ
  function Node (parent) {
    this.parent = parent;
    this.child = new Array ();
  }

  //_________

  // 元というか種を基準
  function NTree (seed) {
    this.seed = seed;
  }


  // 末端を1回(n分岐)成長させる
  function grow (n) {
    var seed = this.seed;
    var buf = find (seed, []);
    var ary = [ ], i, c, j;

    if (0 === buf.length)
      buf = [seed];

    for (i = 0; c = buf[i]; i += 1)
      for (j = 0; j < n; j += 1)
        c.child[j] = new Node (c);
  }

  
  // DOMのように最初の子枝
  function firstNode (n) {
    return n.child[0] ? n.child[0]: null;
  }


  // DOMのように弟の枝
  function nextSibling (n) {
    var p = n.parent, no;
    var c = p.child;

    if (p)
      if (no = c.indexOf (n) + 1)
        if (no < c.length)
          return c[no];

    return null;
  }


  // 要素をすべて集めて配列で返す
  function getAllNodes (node) {
    if (1 > arguments.length)
      node = this.seed;
      
    var rst = [ ];
    var n, b = node;

    B:
      while (n = b)
        if (! (rst.push (n), b = firstNode (n)))
          while (! (b = nextSibling (n)))
            if (node === (n = n.parent))
              break B;

    return rst;
  }


  // 下の関数で呼ばれる
  function maxDepth (mx, n) {
    return Math.max (this.getDepth (n), mx);
  }


  // nodeの要素を基準に最大の深さを返す
  function getMaxDepth (node) {
    if (1 > arguments.length)
      node = this.seed;
    
    return getAllNodes.call (this).reduce (maxDepth.bind (this), 0);
  }


  // nodeの要素の深さを返す
  function getDepth (node) {
    var cnt = 0;
    var p = node;

    while (p = p.parent)
      cnt += 1;

    return cnt;
  }
  
  
  // 要素に番号を振る
  function setNumber (nd, i) {
    nd.number = this + i;
  }


  // 要素を基準に番号を振る(今回の例は再帰ではないので順番が特殊?)
  function setAutoNumber (node, no) {
    switch (arguments.length) {
    case 0 : 
      node = this.seed;
    case 1 :
      no = 0;
    }
    
    var nd = getAllNodes.call (this, node);
    var gd = [ ], d, i, n;
    
    for (i = 0; n = nd[i]; i += 1) {
      d = this.getDepth (n);
      if ('undefined' === typeof gd[d])
        gd[d] = [ ];
      gd[d].push (n); 
    }

    Array.prototype.concat.apply ([], gd)
      .forEach (setNumber, no);//誉めたたえるべきはここか!?
  }


  //子要素を持たない要素を見つける
  function find (node, buf) {
    var child = node.child;

    for (var i = 0, c; c = child[i]; i += 1)
      hasChildNodes (c)
      ? find (c, buf)
      : buf.push (c);

    return buf;
  }


  // 子要素を持っているか?
  function hasChildNodes (node) {
    return !! node.child.length;
  }


  function create (depth, n) {
    if (2 > arguments.length)
      n = 2;

    var seed = new Node (null);
    var tree = new NTree (seed);
    var i;

    for (i = 0; i < depth; i += 1)
      tree.grow (n);

    return tree;
  }


  NTree.prototype.grow = grow;
  NTree.prototype.getAllNodes = getAllNodes;
  NTree.prototype.getMaxDepth = getMaxDepth;
  NTree.prototype.getDepth = getDepth;
  NTree.prototype.setAutoNumber = setAutoNumber;
  
  NTree.create = create;

  this.NTree = NTree;
}) ();


var tree = NTree.create (5);
tree.setAutoNumber (tree.seed, 1);


(function (tree) {
  var canvas = document.querySelector ('canvas');
  var ctx = canvas.getContext('2d');
  var r = 10;
  var mx = canvas.width;
  var my = canvas.height;
  var stepY = tree.getMaxDepth () + 1;
  var nd = tree.getAllNodes ();
  var i, j, cx, cy, d, row, n, s, c;
  var gnd = [ ];
  
  for (i = 0; n = nd[i]; i += 1) {
    d = tree.getDepth (n);
    if ('undefined' === typeof gnd[d])
      gnd[d] = [ ];
    gnd[d].push (n);
  }

  for (i = 0; row = gnd[i]; i += 1) {
    cy = my / stepY * i + r;
    for (j = 0; n = row[j]; j += 1) {
      cx = mx / (row.length) / 2 + (mx / row.length * j);
      n.x = cx;
      n.y = cy;
    }
  }

  ctx.textAlign = "center";
  ctx.textBaseline ="middle";
  ctx.font = Math.floor (r * 1.4) +"px 'Times New Roman'";

  for (i = 0; n = nd[i]; i += 1) {
    cx = n.x;
    cy = n.y;
    for (j = 0; c = n.child[j]; j += 1) {
      ctx.beginPath ();
      ctx.moveTo (cx, cy);
      ctx.lineTo (c.x, c.y)
      ctx.stroke ();
    }
    ctx.beginPath ();
    ctx.strokeStyle = 'rgb(0,0,0)';
    ctx.fillStyle = 'rgb(255, 255, 255)';
    ctx.arc (cx, cy, r, 0, Math.PI*2);
    ctx.fill ();
    ctx.fillStyle = 'rgb(0, 0,0)';
    ctx.fillText(n.number + '', cx, cy + r/ 8);
    ctx.stroke ();
  }

}) (tree);

</script>

babu_babu_baboobabu_babu_baboo 2017/01/05 12:17 苦労に見合うお礼無し。(笑)