babu_babu_babooのごみ箱

2017-02-21

OKWaveに続く質問にうんざり。

| 09:08

よくもまぁ

もう答えなければよいのに。

think49think49 2017/02/21 15:45 その質問者は毎回「OKWave」「教えてgoo」「Yahoo知恵袋」にマルチポストしている人ですよね。
最近はteratailやstackoverflowが質問の履歴を見られるので、質問者の質が分かっていいと思ってます。

babu_babu_baboobabu_babu_baboo 2017/02/22 20:55 お久しぶりです。

>質問者の質
歴代まれにみる最低でしょうね。
「かわいそう」を通り越しています。
もう関わらないのが一番。


体の調子がよくないので、デスクワークをするふりをしてパソコンを弄っています。
それでも長時間の作業はできなくなりました。

最近、ES6 に移行しています。
調べ回ると、ところどころで think49 さんの書き込みを拝見します。
その度に「ふふっ」と微笑んでいます。

どんどん出遅れて、追いつけそうにありませんね。
ご活躍をお祈りしています。

2017-02-17

マウスで3Dモデルを回転させる。その3

| 03:41

f:id:babu_babu_baboo:20170218034119p:image

http://jsdo.it/babu_baboo/inv0

<!DOCTYPE html>
<meta charset="utf-8">
<title>GAME</title>
<style>
body {
  color: #ccc; background: black;
}
</style>

<body>
<canvas width="800" height="700" id="c1"></canvas>

<script>
//___________________________________

{ //アニメーションの環境か?

  const
    win = this;

  const
    substitution =
      function (callBackFunc, that) {
        let tmpFunc = function () {
          let timestamp = +(new Date);
          callBackFunc (timestamp);
        };
        win.setTimeout (tmpFunc, Math.floor (1000/60));
      };

  if ('undefined' === typeof win.requestAnimationFrame)
    win.requestAnimationFrame =
      win.requestAnimationFrame ||
      win.webkitRequestAnimationFrame ||
      win.mozRequestAnimationFrame ||
      win.oRequestAnimationFrame ||
      win.msRequestAnimationFrame ||
      substitution;

  if ('undefined' === typeof win.cancelAnimationFrame)
    win.cancelAnimationFrame =
      win.cancelAnimationFrame ||
      win.mozCancelAnimationFrame ||
      win.webkitCancelAnimationFrame ||
      win.msCancelAnimationFrame;
}


{ //色を定義する
  const
      MAX_COLOR   = 255,
      MAX_OPACITY = 1,
      MIN         = Math.min,
      MAX         = Math.max,
      INT         = Math.floor;


  class Color {

    constructor (r = MAX_COLOR, g = MAX_COLOR, b = MAX_COLOR, a = MAX_OPACITY) {
      this.r = MAX (0, MIN (r, MAX_COLOR));
      this.g = MAX (0, MIN (g, MAX_COLOR));
      this.b = MAX (0, MIN (b, MAX_COLOR));
      this.a = MAX (0, MIN (a, MAX_OPACITY));
    }

    //色の加算
    addColor (colorObj) {
      let {r, g, b, a} = colorObj;
      this.r = MAX (0, r * a + this.r);
      this.g = MAX (0, g * a + this.g);
      this.b = MAX (0, b * a + this.b);
    }

    //色のコピーを作る
    crone () {
      return new Color (this.r, this.g, this.b, this.a);
    }


    //乗算したコピーを作る
    multiplication (x) {
      return new Color (
        MAX (0, this.r * x),
        MAX (0, this.g * x),
        MAX (0, this.b * x),
        this.a
      );
    }


    //文字列で返す(透明度含む)
    toStringRGBA () {
      return 'rgba(' + [
        MIN (INT (this.r), MAX_COLOR),
        MIN (INT (this.g), MAX_COLOR),
        MIN (INT (this.b), MAX_COLOR),
        MIN (this.a, MAX_OPACITY)
      ].join (',') + ')';
    }


    //文字列で返す
    toSrtingRGB () {
      return 'rgb(' + [
        MIN (INT (this.r), MAX_COLOR),
        MIN (INT (this.g), MAX_COLOR),
        MIN (INT (this.b), MAX_COLOR)
      ].join (',') + ')';
    }
  }

  //__________

  this.Color = Color;
}



{ //点を定義する

  //単純な2次元の点として定義
  class Point2D {
    constructor (x = 0, y = 0) {
      this.x = x;
      this.y = y;
    }
  }


  //単純な3次元の点
  class Point3D extends Point2D {
    constructor (x, y, z = 0) {
      super (x, y);
      this.z = z;
    }
    
    static average (...vertex) {
      let
        len = vertex.length,
        ax = 0, ay = 0, az = 0;

      vertex.forEach (p => {
        let {x, y, z} = p;
        ax += x, ay += y, az += z;
      });

      return new Point3D (ax / len, ay / len, az / len);
    }
  }


  //__________

  this.Point2D = Point2D;
  this.Point3D = Point3D;
}



{ //点・線・面を定義する

  const
    DEF_COLOR  = [255, 255, 255, .5],
    DEF_OPTION = {
      disabled: false,
      size    : 2
    };


  //空間に表現するための「点」
  class Point extends Point3D {

    constructor ([x, y, z], color = new Color(...DEF_COLOR), option = DEF_OPTION) {
      super (x, y, z);
      this.color  = color;
      this.option = Object.assign ({ }, DEF_OPTION, option);
    }
  }
  
  
  //面を構成する「線」
  class Line {
    constructor (list = [ ], color = new Color(...DEF_COLOR), option = { }) {
      this.list      = list;
      this.color     = color;
      this.option    = Object.assign ({ }, DEF_OPTION, option);
    }
    
    addLine (vertex_no) {
      let len = this.list.length;
      this.list.push (vertex_no);
      return len;
    }
  }


  //「面」
  class Surface {

    constructor (lineList = [ ], color = new Color(...DEF_COLOR), option = { }) {
      this.lineList  = lineList;
      this.color     = color;
      this.option    = Object.assign ({ }, DEF_OPTION, option);
    }


    //面の3点を利用して面のベクトルを返す。面自体には点の座標の情報が無いので注意
    getVector (pointList) {
      let
        [i0, i1, i2] = this.lineList,
        {x: x0, y: y0, z: z0} = pointList[i0],
        {x: x1, y: y1, z: z1} = pointList[i1],
        {x: x2, y: y2, z: z2} = pointList[i2],

        [px, py, pz] = [x1 - x0, y1 - y0, z1 - z0], //p
        [qx, qy, qz] = [x2 - x1, y2 - y1, z2 - z1]; //q

      return new Vector (
        py * qz - pz * qy,
        pz * qx - px * qz,
        px * qy - py * qx
      );
    }


    getDistance (pointList) {
      let
        [i0, i1, i2] = this.lineList,
        {x: x0, y: y0, z: z0} = pointList[i0],
        {x: x1, y: y1, z: z1} = pointList[i1],
        {x: x2, y: y2, z: z2} = pointList[i2],

        x = (x0 + x1 + x2) / 3,
        y = (y0 + y1 + y2) / 3,
        z = (z0 + z1 + z2) / 3;

      return new Point3D (x, y, z);

    }
  }

  //__________

  this.Point   = Point;
  this.Line    = Line;
  this.Surface = Surface;
}



{ //モデルを定義する

  const
    DEF_COLOR  = new Color (),
    DEF_OPTION = {
      disabled: false
    };


  //__________

  class Model {

    constructor (vertex = [ ], surface = [ ], option = { }) {
      this.vertex  = vertex;  //頂点
      this.surface = surface; //面
      this.option  = Object.assign ({ }, DEF_OPTION, option);
    }


    //面を追加する
    addSurface (surface) {
      return this.surface.push (surface) -1;
    }


    //頂点リストに追加する
    addVertex (vertex) {
      return this.vertex.push (vertex) -1;
    }


    //頂点の色をセットする
    setVertexColor (color, no = null) {
      let i, p;

      if (null === no)
        for (i = 0; p = this.vertex[i]; i += 1)
          Object.assign (p.color, color);
      else
        if (p = this.vertex[no])
          Object.assign (p.color, color);
    }


    //面の色をセットする
    setSurfaceColor (color, no = null) {
      let p;
      if (null === no)
        for (let i = 0; p = this.surface[i]; i += 1)
          Object.assign (p.color, color);
      else
        if (p = this.surface[no])
          Object.assign (p.color, color);
    }


    //物体の頂点の配列を返す
    getViewPoint () {
      return this.vertex.map (p => [p.x, p.y, p.z]);
    }
  }

  //__________

  this.Model = Model;
}





{ //ベクトルの定義

  class Vector extends Point3D {

    constructor (x, y, z) {
      super (x, y, z);
    }


    //加算
    add ({x, y, z}) {
      this.x += x;
      this.y += y;
      this.z += z;
    }


    //内積
    innerProducts ({x, y, z}) {
      return this.x * x + this.y * y + this.z * z;
    }
  }

  //__________

  this.Vector = Vector;
}


//___________________________________
// 焦点距離(Focal Length), 画角(Field Of View)
//http://www.cyber.t.u-tokyo.ac.jp/~tani/class/mech_enshu/enshu2011mi2.pdf

{ // カメラ

  const
    FOCAL_LENGTH = 50, //mm 標準レンズの焦点距離
    DEF_POSITION = [0, 0, 1000],
    DEF_OPTION   = {
      disabled: false,
      scale: 1
    };


  class Camera  {

    constructor (position = new Point3D (...DEF_POSITION), focalLength = FOCAL_LENGTH, option = { }) {
      this.position = position;
      this.FOV      = focalLength; //2 * Math.atan ((APERTURE_X / 2) / focalLength);
      this.option   = Object.assign ({ }, DEF_OPTION, option);
      this.Z        = position.z;
      this.f0       = this.option.scale * (focalLength / (1 + focalLength / position.z))
    }


    //3次から2次へ投影
    project ({x, y, z}) {
      let {Z, f0} = this, s = f0 / (Z - z);
      return [x * s, y * s];
    }
  }

  //__________

  this.Camera = Camera;
}

{
  const
    EYE_POINT    = new Point3D (3000, 0, 0),
    TRGET_POINT  = new Point3D (0, 0, 0),
    UP_VECTOR    = new Vector (0, 1, 0),
    FOCAL_LENGTH = 50, //mm 標準レンズの焦点距離
    DEF_POSITION = [0, 0, 1000],
    DEF_OPTION   = {
      disabled: false,
      scale: 1
    };


  class VCamera {
    constructor (eye = EYE_POINT, target = TRGET_POINT, upVector = UP_VECTOR, option = { }) {
      this.eye      = eye; //視点
      this.target   = target; //目標点
      this.upVector = upVector; //カメラの上方向のベクトル
      this.option   = Object.assign ({ }, DEF_OPTION, option);
    }

    //3次から2次へ投影
    project ({x, y, z}) {
      let
        { x: ex, y: ey, z: ez } = this.eye;
        

    }
  
  }
}


{ //照明(将来的に点光源など複数の照明を使えるようにするべき)

  const
    SQRT        = Math.sqrt,
    DEF_COLOR   = [255, 255, 255, 1],
    DEF_VECTOR  = [-1, 0, 0], //平行光なのでベクトルだけ
    DEF_OPTION  = {
      disabled     : false,
      distance     : 3000,
      targetPoint  : new Point3D (0, 0, 0),
      brightness   : 1, //明るさ
      ambientLight : 0, //環境光
      kc           : 0, //一定減衰定数
      kl           : .1, //1次減衰定数
      kq           : .2, //2次減衰定数
      kb           : 100000000 //光源の明るさ
    };

  const
    init = function () {
      let
        { distance, targetPoint } = this.option,
        { x, y, z} = this.vector,
        { x: tx, y: ty, z: tz } = targetPoint,
        dd = distance * distance;
        xyz = x * x + y * y + z * z;

      this.position = new Point3D (
        SQRT (dd * x / xyz) + tx,
        SQRT (dd * y / xyz) + ty,
        SQRT (dd * z / xyz) + tz
      );

      this.distance     = distance; //距離
    };


  class Light {

    constructor (vector = new Vector (...DEF_VECTOR), color = new Color (...DEF_COLOR), option = { }) {

      this.vector       = vector;
      this.color        = color;
      this.option       = Object.assign ({ }, DEF_OPTION, option);

      init.call (this);
    }
    
    
    getBrightness (p) {
      let 
        {x, y, z} = p,
        { x: tx, y: ty, z: tz } = this.position,
        { kc, kl, kq, kb } = this.option,
        dx = tx - x, dy = ty - y, dz = tz - z,
        d = SQRT (dx * dx + dy * dy + dz * dz);

      return kb / (kc + kl * d + kq * d * d);
    }
  }

  //__________

  this.Light = Light;
}




{ //マウスのドラッグ操作で配列の回転を制御(QUATERNIONによる回転)

  const
    SQRT            = Math.sqrt,
    SIN             = Math.sin,
    COS             = Math.cos,
    PI              = Math.PI,
    DEG             = PI / 180,

    TOUCH_EVENT     = ['touchstart', 'touchend', 'touchmove'],
    MOUSE_EVENT     = ['mousedown', 'mouseup', 'mousemove', 'mouseout'],
    DEF_OPTION      = {
      inertia     : true, //慣性モード有効
      sensitivity : 500,  //マウスの感度
      gain        : 0.999 //慣性移動の減速率
    };


  //ローテーションクラス本体
  class RotationController {

    constructor (element = document.body, model, option = {}) {
      this.target      = element;
      this.model       = model;
      this.option      = Object.assign ({}, DEF_OPTION, option);
      this.mouseX      = null;//マウス座標の基点
      this.mouseY      = null;//マウス座標の基点
      this.touched     = false; //ドラッグ中か?
      this.animeId     = 0;
      this.timeStamp   = null; //慣性を行うかどうかのもう一つの基準
      this.dx          = 0;//マウスの慣性移動量
      this.dy          = 0;//マウスの慣性移動量
      this.sensitivity = 1 / this.option.sensitivity; //mouse移動の感度
      this.inertia_min = 0.001; //最小の移動量で完成移動を止める

      //touchイベントがあるなら優先
      (window.TouchEvent ? TOUCH_EVENT: MOUSE_EVENT)
        .forEach (addEventType, this)
    }


    //各イベント処理
    handleEvent (event) {
      let e = event.target;
      switch (event.type) {

      // 制御終了
      case 'mouseup' :
      case 'mouseout' :
      case 'touchend' :
        if (this.touched){
          this.touched = false;
          if (event.timeStamp - this.timeStamp < 50) //mouseup から 50mm秒以内なら慣性モードへ
            if (this.option.inertia)
              inertia.call (this);//制御を慣性に移す
        }
        break;

      // 制御開始
      case 'mousedown' :
      case 'touchstart' :
        if (this.animeId) {
          cancelAnimationFrame (this.animeId);
          this.animeId = 0;
        }
        this.dx = 0;
        this.dy = 0;
        this.mouseX = event.pageX;
        this.mouseY = event.pageY;
        this.touched = true;
        break;

      // 回転制御中
      case 'mousemove' :
      case 'touchmove' :
        if (this.touched) {
          event.preventDefault ();//ipadなどでスクロールさせないため
          this.timeStamp = event.timeStamp;
          let
            {pageX, pageY} = event,
            {mouseX, mouseY, sensitivity} = this,
            sx = mouseX - pageX,
            sy = mouseY - pageY;
          if (sx || sy) {
            this.dx = sx * sensitivity;
            this.dy = sy * sensitivity;

            rotation.call (this);
            this.mouseX = pageX;
            this.mouseY = pageY;
          }
        }
        break;
      }
    }

    //quaternion rotation
    //回転の関数の独立した呼び出し
    static rotation (aryPoint3D, qx, qy, qz, qa) {
      let ql = qx * qx + qy * qy + qz * qz;

      if (ql) {
        let qh = qa * DEG * .5, s = SIN (qh) / SQRT (ql);
        product (aryPoint3D, qx * s, qy * s, qz * s, COS (qh));
      }
    }
  }


  const
    //イベントの登録
    addEventType =
      function (eventType) {
        this.target.addEventListener (eventType, this, false);
      },


    //慣性モードに移行
    inertia =
      function () {
        let gain = this.option.gain;
        this.dx *= gain;
        this.dy *= gain;
        if (this.inertia_min < rotation.call (this))
          this.animeId = requestAnimationFrame (inertia.bind (this));
      },


    //回転を行う
    rotation =
      function () {
        //画面の2次元移動量から3次元の回転量を求める
        let
          {dx, dy} = this,
          t = dx * dx + dy * dy;

        if (t) {
          // クオータニオンによる回転
          t = SQRT (t);
          let as = -SIN (t) / t;
          product (this.model.vertex, dy * as, dx * as, 0, COS (t));
        }
        return t; //移動距離を返す(慣性?)
      },


    //積(座標)の計算
    product =
      function (point, q0, q1, q2, q3) {
        for (let i = 0, p; p = point[i]; i++) {
          let
            {x, y, z} = p,
            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;

          point[i].x = a0 * q3 - a3 * q0 - a1 * q2 + a2 * q1;
          point[i].y = a1 * q3 - a3 * q1 - a2 * q0 + a0 * q2;
          point[i].z = a2 * q3 - a3 * q2 - a0 * q1 + a1 * q0;
        }
      };

  //__________

  this.RotationController = RotationController;
}



{// Render (Canvas)

  const
    DEF_OPTION = {
      surface     : true,
      hiddenSurface: true,
      lighting    : true,
      wireFrame   : false,
      vertex      : false,
      clsColor    : 'rgb(0,0,0)'
    },
    PI2 = Math.PI * 2;


  class Render {

    constructor (canvas, camera, light, option = { }) {
      this.canvas = canvas;
      this.ctx    = canvas.getContext ('2d');
      this.camera = camera;
      this.light  = light;
      this.option = Object.assign ({}, DEF_OPTION, option);
      this.width  = canvas.width;
      this.height = canvas.height;
      this.centerX= canvas.width  * 0.5 + 0.5;
      this.centerY= canvas.height * 0.5 + 0.5;
    }


    //CANVASを塗りつぶす
    clsScreen (rgba = this.option.clsColor) {
      let {ctx, width, height} = this;
      ctx.fillStyle = rgba;
      ctx.fillRect (0, 0, width, height);
    }


    //model を描く
    draw (model) {
      let
        cos  = Math.cos,
        acos = Math.acos,
        sqrt = Math.sqrt,
        max  = Math.max,
        {ctx, centerX, centerY, light, camera, option} = this,
        {vertex, surface} = model,
        zbuf = [ ],
        {distance, option: {brightness: lb, ambientLight: la}, vector: {x: lx, y: ly, z: lz}} = light;


      //カメラからの投影結果をxy[]に保存
      let xy = vertex.map (s => {
        let [px, py] = camera.project (s);
        return [centerX + px, centerY - py];
      });

      //面の構成が左回りを利用して2次元上で裏の向きを省く
      if (option.hiddenSurface) {
        let
          {x: cx, y: cy, z: cz} = camera.position;
        zbuf = surface.reduce ((a, b) => {
          let
            [p0, p2, p1] = b.lineList,//3点からベクトルの外積
            [[x0, y0], [x1, y1], [x2, y2]] = [xy[p0], xy[p1], xy[p2]];
          if ((x2 - x1) * (y0 - y1) - (y2 - y1) * (x0 - x1) > 0)
            a.push (b);
          return a;
        }, []);
      } 
      else
        zbuf = surface.slice (0);


      //距離でソート
      zbuf = zbuf.map (s => {
        let
          {x: cx, y: cy, z: cz} = camera.position,
          {x, y, z} = s.getDistance (vertex),
          dx = x - cx, dy = y - cy, dz = cz -z;
        return [dx*dx + dy*dy + dz*dz, s];
      })
        .sort ((a, b) => a[0] < b[0])
        .map ((a)=> a[1]);


      //面の描画
      let brightness = 1;
      zbuf.forEach (s => {
        let P = s.lineList;
        if (option.lighting) {
          let
            {x, y, z} = s.getVector (vertex),
            len = (x*lx + y*ly + z*lz) / (distance * sqrt (x*x + y*y + z*z)),
            av = Point.average (...P.map (n => vertex[n]));
          brightness = la + max (0, len * light.getBrightness (av));
        }
        ctx.fillStyle = s.color.multiplication (brightness).toStringRGBA ();
        ctx.beginPath ();
        ctx.moveTo (...xy[P[0]]);
        P.forEach ((p, i) => { ctx.lineTo (...xy[P[i]]) });
        ctx.fill ();
      });



    }
  }

  //__________

  this.Render = Render;
}



{ //物体の定義
  const
    PI     = Math.PI,
    ACOS   = Math.acos,
    ATAN2  = Math.atan2,
    SQRT   = Math.sqrt,
    SIN    = Math.sin,
    COS    = Math.cos,


    DEF_OPTION_MODEL = {
      size         : 1,
      pointColor   : new Color (255, 255, 255, 1),
      surfaceColor : new Color (255, 255, 255, .5)
    },



    //ドーナツの定義
    DEF_OPTION_DOUGHNUT = {
      radius       : 1,
      N            : 24,
      radius2      : .2,
      N2           : 12,
      pointColor   : new Color (255, 255, 255, 1),
      surfaceColor : new Color (255, 255, 255, .5)
    },
    
    Doughnut = function (option = { }) {
      option    = Object.assign ({ }, DEF_OPTION_DOUGHNUT, option);
      let
        {radius: r0, N: n0, radius2: r1, N2: n1} = option,
        pi2 = PI + PI,
        s0 = pi2 / n0, s1 = pi2 / n1,
        all = n0 * n1,
        point = [ ], surface = [ ];

      for (let i = 0; i < n0; i++) {
        let
          angle = s0 * i,
          idx = i * n1,
          idx2 = (idx + n1) % all;

        for (let j = 0; j < n1; j++) {
          let
            angle2 = s1 * j,
            r = r0 + SIN (angle2) * r1,
            jn = (j + 1) % n1;

          point.push (new Point (
            [SIN (angle) * r, COS (angle2) * r1, COS (angle) * r],
            option.pointColor.crone (),
            option
          ));

          surface.push (new Surface (
            [idx + j, idx + jn, idx2 + jn, idx2 + j],
            option.surfaceColor.crone (),
            option
          ));
        }
      }

      return [point, surface, option];
    },
    

    //立方体の定義
    Squares = function (option = { }) {
      option    = Object.assign ({ }, DEF_OPTION_MODEL, option);
      let
        {size, pointColor: pcol, surfaceColor: scol} = option,
        a = size * .5,
        point = [
          [-a, a, a], [a, a, a], [a, a, -a], [-a, a, -a],
          [-a,-a, a], [a,-a, a], [a,-a, -a], [-a,-a, -a]
        ].map (p => new Point (p, pcol.crone (), option)),
        
        surface = [
          [0,1,2,3], [0,4,5,1], [1,5,6,2], [2,6,7,3], [3,7,4,0], [4,7,6,5]
        ].map (p => new Surface (p, scol.crone (), option));

      return [point, surface, option];
    },


    //正二十面体を定義する
    DEF_OPTION_REGULAR_ICOSAHEDRON = {
      radius       : 100,
      pointColor   : new Color (255, 255, 255, 1),
      surfaceColor : new Color (255, 255, 255, .5)
    },

    RegularIcosahedron =
      function (option = { }) {
        option    = Object.assign ({ }, DEF_OPTION_REGULAR_ICOSAHEDRON, option);
        let
          r  = .5,
          gr = (1 + SQRT (5)) / 2,
          a = r / SQRT (1 + gr * gr),
          b = a * gr,

          point = [ //物体の頂点
            [ 0,-a,-b], [ 0, a,-b], [ 0,-a, b], [0, a, b],
            [-b, 0,-a], [-b, 0, a], [ b, 0,-a], [b, 0, a],
            [-a,-b, 0], [ a,-b, 0], [-a, b, 0], [a, b, 0]
          ].map (p => new Point (p, option.pointColor, option)),

          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 (p => new Surface (p, option.surfaceColor, option));

        return [point, surface, option];
      },


    //正四面体を定義する
    DEF_OPTION_REGULAR_TETRAHEDRON = {
      radius       : 100,
      pointColor   : new Color (255, 255, 255, 1),
      surfaceColor : new Color (255, 255, 255, .5)
    },

    RegularTetrahedron =
      function (option = { }) {
        option = Object.assign ({ }, DEF_OPTION_REGULAR_TETRAHEDRON, option);
        let
          r  = option.radius,
          a = 1/SQRT(3/8) * r,
          h = SQRT(2/3)*a,
          b = -h + r,

          point = [ //物体の頂点
            [0, r, 0], [0, b, r], [a / 2, b, b], [-a/2, b, b]
          ].map (p => new Point (p, option.pointColor, option)),

          surface = [//辺の順序が面を現す(先に登録された頂点の番号)
            [ 0, 1, 2], [ 0, 2, 3], [ 0, 3, 1], [ 3, 2, 1],
          ].map (p => new Surface (p, option.surfaceColor, option));

        return [point, surface, option];
      },
      
      
    //角柱を定義する
    DEF_OPTION_PRISM = {
      radius       : 100,
      pointColor   : new Color (255, 255, 255, 1),
      surfaceColor : new Color (255, 255, 255, .5)
    },

    Prism =
      function (option = { }) {
        option = Object.assign ({ }, DEF_OPTION_PRISM, option);
        let
          r  = option.radius,
          N  = option.N,
          h  = r / 2,
          S  = (PI + PI) / N,
          vertex = [ ], surface = [ ], bottom = [ ], top = [ ];
        
        for (let i = 0; i < N; i++) {
          let s = S * i, i2 = i * 2, sir = SIN (s) * r, cir = COS (s) * r;
          vertex.push (new Point ([sir, h, cir], option.pointColor, option));
          vertex.push (new Point ([sir, -h, cir], option.pointColor, option));
          surface.push (new Surface ([i2, i2 + 1, ((i2 + 2) % (N*2))+1, ((i2 + 2) % (N*2))], option.surfaceColor, option));
          top.push (i2);
          bottom.push ((i2 + 1));
        }
        surface.push (new Surface (top, option.surfaceColor, option));
        surface.push (new Surface (bottom.reverse (), option.surfaceColor, option));


        return [vertex, surface, option];
      };
      


  const
    createModel =
      function (type, option) {
        switch (type) {
        case 'regularIcosahedron' : return RegularIcosahedron (option); //正二十面体
        case 'regularTetrahedron' : return RegularTetrahedron (option); //正四面体
        case 'doughnut'           : return Doughnut (option); //ドーナツ
        case 'pyramid'            : return Pyramid (option); //多角推
        case 'squares'            : return Squares (option); //立方体
        case 'prism'              : return Prism (option); //角柱
        default :
          throw new Error ('無効な形状です')
        }
      };
  
  
  //________________________


const     //2つのベクトルの中間を返す
    createPoint = function (a, b, r) {
      let
        { x: ax, y: ay, z: az } = a,
        { x: bx, y: by, z: bz } = b;
        cx = (ax + bx) * .5, cy = (ay + by) * .5, cz = (az + bz) * .5,
        d = cx * cx + cy * cy + cz * cz, d2 = d * 2,
        e = SQRT (r * 2 * d2) / d2;
      return new Point ([cx * e, cy * e, cz * e]);
    };


  
  class Modeler extends Model {

    constructor (type, option) {
      super (...createModel (type, option));
    }
    
    
    //面の細分化
    subdivideSurfaces (n = 1) {
      if (n < 1 || 10 < n)
        throw new Error ('引数が範囲外です');

      let
        V = this.vertex,
        R = this.option.radius,
        RR = R * R;

      for (let i = 0; i < n; i++) {
        this.surface.forEach (s => {
          let
            {lineList, color, option} = s,
            [p0, p1, p2] = lineList,
            [ a,  b,  c] = [V[p0], V[p1], V[p2]],
            [ d,  e,  f] = [createPoint (a, b, RR), createPoint (b, c, RR), createPoint (c, a, RR)],
            [dn, en, fn] = [this.addVertex (d), this.addVertex (e), this.addVertex (f)];

          //従来の三角形を4分割した中央の三角形にする
          s.lineList = [dn, en, fn];
          [V[dn], V[en], V[fn]] = [d, e, f];
          //他の3つの三角形の面を追加する
          this.addSurface (new Surface ([p0, dn, fn], color, option));
          this.addSurface (new Surface ([p1, en, dn], color, option));
          this.addSurface (new Surface ([p2, fn, en], color, option));
        });
      }

      return this;
    }
    
    
    subdivide (surfaceNo = [ ], n = 1) {
      let
        V = this.vertex,
        A = [ ], //分割しない面
        B = [ ]; //分割対象面
      
      if (0 === surfaceNo.length)
        B = this.surface;
      else {
        let a = [...Array.from (this.surface).key ()];
        surfaceNo.forEach (n => a.splice (n, 1));
        A = a.map (n => this.surface[n]);
        B = surfaceNo.map (n => this.surface[n]);
      }
      
      for (let i = 0; i < n; i++) {
        let C = [ ];
        B.forEach (s => {
          let
            type = s.lineList.length,
            {lineList, color, option} = s;

          if (3 === type) {
            //面の2分化(三角形ABCの辺ABを二等分した点Dと、点Cを結ぶ線で二分する)
            //2つの三角形はそれぞれ、三角形CDBと三角形
            let
              [a, b, c] = lineList,
              d = this.addVertex (Point.average (...[V[a], V[b]]));

            C.push (new Surface ([c, a, d], color, option));
            C.push (new Surface ([b, c, d], color, option));

          } else if (3 < type) {
            //面の頂点の数が3を越える場合は、面の頂点の中心(平均)点を中心とした三角形を形成
            let
              n = this.addVertex (Point.average (...lineList.map (n => V[n])));

            for (let i = 0, I = lineList.length; i < I; i++) {
              C.push (new Surface ([lineList[i], lineList[(i+1)%I], n], color, option));
            }
          }

        });

        B = C;
      }
      this.surface = A.concat (B);
      
      return this;
    }
    




    //面を選択する(余計な面、点を排除する)
    selectTo (surfaceNo = []) {
      if (0 === surfaceNo.length)
        throw new Error ('引数がありません');

      let
        { vertex, surface } = this,
        V = [ ], //整理された頂点
        M = [ ], //頂点の変換表
        S = surfaceNo.map (n => surface[n]);//面を選択

      for (let i = 0, I = S.length; i < I; i++) {
        let l = S[i].lineList;
        for (let j = 0, J = l.length; j < J; j++) {
          let no = l[j], idx = M[no];
          if ('undefined' === typeof idx) //変換表にあるか?
            M[no] = idx = V.push (vertex[no]) -1;
          S[i].lineList[j] = idx;
        }
      }      

      this.surface = S;
      this.vertex = V;
    }
    
    
    
    //拡大・縮小
    scaleTo (nx = 1, ny = nx, nz = nx) {
      if (0 >= nx || 0 >= ny || 0 >= nz)
        throw new Error ('範囲外の数値が指定されました');
        
      this.vertex.forEach (p => { p.x *= nx, p.y *= ny, p.z *= nz});

      return this;
    }


    //移動
    moveTo (nx = 0, ny = 0, nz = 0) {
      this.vertex.forEach (p => { p.x += nx, p.y += ny, p.z += nz});
      return this;
    }
    
    //コピー
    copyTo () {
      let {vertex, surface, option} = this;
      return new Model (vertex, surface, option);
//      return Object.assign ({ }, this);
    }
    

    //回転
    rotationTo (angleX, angleY, angleZ, angle) {
      let
        deg = PI / 180,
        q0 = SIN (angleX * deg /2),
        q1 = SIN (angleY * deg /2),
        q2 = SIN (angleZ * deg /2),
        q3 = COS (angle * deg / 2);
      
      this.vertex.forEach (p => {
        let
          {x, y, z} = p,
          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;

        p.x = a0 * q3 - a3 * q0 - a1 * q2 + a2 * q1;
        p.y = a1 * q3 - a3 * q1 - a2 * q0 + a0 * q2;
        p.z = a2 * q3 - a3 * q2 - a0 * q1 + a1 * q0;      
      });

      return this;
    } 



  }

  //__________

  this.Modeler = Modeler;
}



{ //demo

  const
    CANVAS = document.querySelector ('canvas'),
    CAMERA = new Camera (new Point3D (0, 0, 500), 50/*mm*/, {scale: 10}),

    OPACITY = 1,
    MCtrl_MODEL_OPT  = { radius: .5, N: 24, radius2: .1, N2: 24},
    MCtrl_ROTE_OPT   = {gain: 1},
    MCtrl_LIGHT_OPT  = {ambientLight: 0.1},
    MCtrl_RENDER_OPT = {hiddenSurface: true},

    MCtrl_MODEL      = new Modeler ('squares', MCtrl_MODEL_OPT),
    MCtrl_ROTE       = new RotationController (CANVAS, MCtrl_MODEL, MCtrl_ROTE_OPT),
    MCtrl_LIGHT      = new Light (new Vector (1, 5, 10), new Color (), MCtrl_LIGHT_OPT),
    MCtrl_RENDER     = new Render (CANVAS, CAMERA, MCtrl_LIGHT, MCtrl_RENDER_OPT),


    rnd =
      function () { return Math.floor (Math.random() * 128+128); },


    setRandomColor =
      function (model) {
        let i, surface;

        for (i = 0; surface = model.surface[i]; i++)
          surface.color = new Color (rnd (), rnd (), rnd (), OPACITY);
      },

    setSurfaceColor =
      function (model, col) {
        model.surface.forEach (s => s.color = col);
      },


    loop = function loop () {
      MCtrl_RENDER.clsScreen ();
      MCtrl_RENDER.draw (MCtrl_MODEL);

      requestAnimationFrame (loop);
    };
  
  //_____________

  MCtrl_MODEL.scaleTo (300, 300, 300);//subdivideSurfaces (2);
  MCtrl_MODEL.subdivide ([], 7);
  setSurfaceColor (MCtrl_MODEL, new Color (255,100,100,OPACITY));
  this.demo = loop;
}

//___________________________________

demo ();

</script>


2017-02-15

マウスで3Dモデルを回転する(クオータニオン)

| 22:46

http://jsdo.it/babu_baboo/EZt9

いつものようにライブラリなんか利用しないで書いた。

凄く勉強になるのだが、すぐに忘れてしまいそうだ。

f:id:babu_babu_baboo:20170215224833p:image:w360

<!DOCTYPE html>
<meta charset="UTF-8">
<title>N個の点を持つ球体を描画する</title>
<style>
body {
  color: #ccc; background: black;
}
canvas { border: 1px #666 solid; }
#CG ul, #CG li { margin: 0; padding: 0;}
#CG li { list-style: none; }
h4 { font-size: normal; margin: .5ex;font-weight: normal;}
#c1 {
  background: black;
  border: 1px #666 solid;
}
#CG {
  vertical-align: top;
  border: 1px #666 solid;
  width: 120px; height: 696px;
  overflow: auto;
  padding: 2px;
  display: inline-block;
}
#ctrl p {
  margin: 0; font-size: small;
  border-bottom: 1px #666 solid;
}
#CG input {
  background: transparent;
  color: #ff0;
  radius: 4px;
}
#CG input[type="button"] {
    width: 100%;
}
#CG input[type="number"] {
  width: 4em;
}
#c2 {
  width: 100%;
}
</style>

<body>
<div id="CG">
  <h4>光源</h4>
  <canvas width="100" height="100" id="c2"></canvas>

  <ul>
   <li>
    <label>半径</label><br>
    A:<input type="number" id="radius" value="100" size="5" min="30" step="30" max="210"><br>
    B:<input type="number" id="radius2" value="30" size="5" min="30" step="30" max="90">
   <li>
    <label>分割</label><br>
    A:<input type="number" id="N" value="10" min="3" max="100"><br>
    B:<input type="number" id="N2" value="10" min="3" max="100">
   <li>
    <label>面分割</label><br>
    A:<input type="number" id="split" value="0" size="5" min="0" max="5"><br>
  </ul>
  <h4>物体</h4>
  <ul>
   <li><input type="button" value="正二十面体" id="regularIcosahedron">
   <li><input type="button" value="正四面体" id="regularTetrahedron">
   <li><input type="button" value="多角錐" id="pyramid">
   <li><input type="button" value="ドーナツ" id="doughnut">

  </ul>
  <hr>

  <h4>Render</h4>
  <ul>
   <li><label><input type="checkbox" value="vertex" id="vertex">頂点</label>
   <li><label><input type="checkbox" value="wireFrame" id="wireFrame">線</label>
   <li><label><input type="checkbox" value="surface" id="surface" checked>面</label>
   <li><label><input type="checkbox" value="hiddenSurface" id="hiddenSurface" checked>隠面処理</label>
  </ul>

  <hr>
  <h4>Color</h4>
  <ul>
   <li><label><input type="checkbox" value="red" id="red" checked>赤</label>
   <li><label><input type="checkbox" value="green" id="green" checked>緑</label>
   <li><label><input type="checkbox" value="blue" id="blue" checked>青</label>
   <li><label><input type="checkbox" value="random" id="random" checked>ランダム</label>
  </ul>

</div>
<canvas width="800" height="700" id="c1"></canvas>

<script>

//___________________________________

{ //アニメーションの環境か?

  const
    win = this;

  const
    substitution =
      function (callBackFunc, that) {
        let tmpFunc = function () {
          let timestamp = +(new Date);
          callBackFunc (timestamp);
        };
        win.setTimeout (tmpFunc, Math.floor (1000/60));
      };

  if ('undefined' === typeof win.requestAnimationFrame)
    win.requestAnimationFrame =
      win.requestAnimationFrame ||
      win.webkitRequestAnimationFrame ||
      win.mozRequestAnimationFrame ||
      win.oRequestAnimationFrame ||
      win.msRequestAnimationFrame ||
      substitution;

  if ('undefined' === typeof win.cancelAnimationFrame)
    win.cancelAnimationFrame =
      win.cancelAnimationFrame ||
      win.mozCancelAnimationFrame ||
      win.webkitCancelAnimationFrame ||
      win.msCancelAnimationFrame;
}


//___________________________________

{ //色を定義する
  const
      MAX_COLOR   = 255,
      MAX_OPACITY = 1,
      MIN         = Math.min,
      MAX         = Math.max,
      INT         = Math.floor;


  class Color {

    constructor (r = MAX_COLOR, g = MAX_COLOR, b = MAX_COLOR, a = MAX_OPACITY) {
      this.r = MAX (0, MIN (r, MAX_COLOR));
      this.g = MAX (0, MIN (g, MAX_COLOR));
      this.b = MAX (0, MIN (b, MAX_COLOR));
      this.a = MAX (0, MIN (a, MAX_OPACITY));
    }

    //色の加算
    addColor (colorObj) {
      let {r, g, b, a} = colorObj;
      this.r = MAX (0, r * a + this.r);
      this.g = MAX (0, g * a + this.g);
      this.b = MAX (0, b * a + this.b);
    }

    //色のコピーを作る
    crone () {
      return new Color (this.r, this.g, this.b, this.a);
    }


    //乗算したコピーを作る
    multiplication (x) {
      return new Color (
        MAX (0, this.r * x),
        MAX (0, this.g * x),
        MAX (0, this.b * x),
        this.a
      );
    }


    //文字列で返す(透明度含む)
    toStringRGBA () {
      return 'rgba(' + [
        MIN (INT (this.r), MAX_COLOR),
        MIN (INT (this.g), MAX_COLOR),
        MIN (INT (this.b), MAX_COLOR),
        MIN (this.a, MAX_OPACITY)
      ].join (',') + ')';
    }


    //文字列で返す
    toSrtingRGB () {
      return 'rgb(' + [
        MIN (INT (this.r), MAX_COLOR),
        MIN (INT (this.g), MAX_COLOR),
        MIN (INT (this.b), MAX_COLOR)
      ].join (',') + ')';
    }
  }

  //__________

  this.Color = Color;
}


//___________________________________

{ //点を定義する

  const
    DEF_COLOR  = [0, 256, 0, .5],
    DEF_OPTION = {
      disabled: false,
      size    : 2
    };


  //単純な2次元の点として定義
  class Point2D {
    constructor (x = 0, y = 0) {
      this.x = x;
      this.y = y;
    }
  }


  //単純な3次元の点
  class Point3D extends Point2D {
    constructor (x, y, z = 0) {
      super (x, y);
      this.z = z;
    }
  }


  //空間に表現するための「点」
  class Point extends Point3D {

    constructor (x, y, z, color = new Color(...DEF_COLOR), option = DEF_OPTION) {
      super (x, y, z);
      this.color  = color;
      this.option = Object.assign ({ }, DEF_OPTION, option);
    }
  }

  //__________

  this.Point2D = Point2D;
  this.Point3D = Point3D;
  this.Point   = Point;
}


//___________________________________

{ //ベクトルの定義

  class Vector extends Point3D {

    constructor (x, y, z) {
      super (x, y, z);
    }


    //加算
    add ({x, y, z}) {
      this.x += x;
      this.y += y;
      this.z += z;
    }


    //内積
    innerProducts ({x, y, z}) {
      return this.x * x + this.y * y + this.z * z;
    }
  }

  //__________

  this.Vector = Vector;
}


//___________________________________

{ //面を定義する

  const
    DEF_COLOR  = [255, 255, 255,.5],
    DEF_OPTION = {
      disabled: false,
      lineColor: new Color (...DEF_COLOR)
    };


  class Surface {

    constructor (lineList = [ ], color = new Color(...DEF_COLOR), option = { }) {
      this.lineList  = lineList;
      this.lineColor = color;
      this.color     = color;
      this.option    = Object.assign ({ }, DEF_OPTION, option);
    }


    //面の3点を利用して面のベクトルを返す。面自体には点の座標の情報が無いので注意
    getVector (pointList) {
      let
        [i0, i1, i2] = this.lineList,
        {x: x0, y: y0, z: z0} = pointList[i0],
        {x: x1, y: y1, z: z1} = pointList[i1],
        {x: x2, y: y2, z: z2} = pointList[i2],

        [px, py, pz] = [x1 - x0, y1 - y0, z1 - z0], //p
        [qx, qy, qz] = [x2 - x1, y2 - y1, z2 - z1]; //q

      return new Vector (
        py * qz - pz * qy,
        pz * qx - px * qz,
        px * qy - py * qx
      );
    }


    getDistance (pointList) {
      let
        [i0, i1, i2] = this.lineList,
        {x: x0, y: y0, z: z0} = pointList[i0],
        {x: x1, y: y1, z: z1} = pointList[i1],
        {x: x2, y: y2, z: z2} = pointList[i2],

        x = (x0 + x1 + x2) / 3,
        y = (y0 + y1 + y2) / 3,
        z = (z0 + z1 + z2) / 3;

      return new Point3D (x, y, z);

    }
  }

  //__________

  this.Surface = Surface;
}


//___________________________________

{ //モデルを定義する

  const
    DEF_COLOR  = new Color (),
    DEF_OPTION = {
      disabled: false
    };


  class Model {

    constructor (vertex = [ ], surface = [ ], option = { }) {
      this.vertex  = vertex;  //頂点
      this.surface = surface; //面
      this.option  = Object.assign ({ }, DEF_OPTION, option);
    }


    //面を追加する
    addSurface (surface) {
      let len = this.surface.length;
      this.surface.push (surface);
      return len;
    }


    //頂点リストに追加する
    addVertex (vertex) {
      let len = this.vertex.length;
      this.vertex.push (vertex);
      return len;
    }


    //頂点の色をセットする
    setVertexColor (color, no = null) {
      let i, p;

      if (null === no)
        for (i = 0; p = this.vertex[i]; i += 1)
          Object.assign (p.color, color);
      else
        if (p = this.vertex[no])
          Object.assign (p.color, color);
    }


    //面の色をセットする
    setSurfaceColor (color, no = null) {
      let p;
      if (null === no)
        for (let i = 0; p = this.surface[i]; i += 1)
          Object.assign (p.color, color);
      else
        if (p = this.surface[no])
          Object.assign (p.color, color);
    }


    //物体の頂点の配列を返す
    getViewPoint () {
      return this.vertex.map (p => [p.x, p.y, p.z]);
    }
  }

  //__________

  this.Model = Model;
}


//___________________________________
// 焦点距離(Focal Length), 画角(Field Of View)
//http://www.cyber.t.u-tokyo.ac.jp/~tani/class/mech_enshu/enshu2011mi2.pdf

{ // カメラ

  const
    FOCAL_LENGTH = 50, //mm 標準レンズの焦点距離
    DEF_POSITION = [0, 0, 1000],
    DEF_OPTION   = {
      disabled: false,
      scale: 1
    };


  class Camera  {

    constructor (position = new Point3D (...DEF_POSITION), focalLength = FOCAL_LENGTH, option = { }) {
      this.position = position;
      this.FOV      = focalLength; //2 * Math.atan ((APERTURE_X / 2) / focalLength);
      this.option   = Object.assign ({ }, DEF_OPTION, option);
      this.Z        = position.z;
      this.f0       = this.option.scale * (focalLength / (1 + focalLength / position.z))
    }


    //3次から2次へ投影
    project ({x, y, z}) {
      let {Z, f0} = this, s = f0 / (Z - z);
      return [x * s, y * s];
    }
  }

  //__________

  this.Camera = Camera;
}


//___________________________________

{ //照明(将来的に点光源など複数の照明を使えるようにするべき)

  const
    DEF_COLOR   = [255, 255, 255, 1],
    DEF_VECTOR  = [1, 1, 1], //平行光なのでベクトルだけ
    DEF_OPTION  = {
      disabled     : false,
      brightness   : 1, //明るさ
      ambientLight : 0 //環境光
    };


  class Light {
    constructor (vector = new Vector (...DEF_VECTOR), color = new Color (...DEF_COLOR), option = { }) {
      let {x, y, z} = vector;

      this.vector       = vector;
      this.color        = color;
      this.option       = Object.assign ({ }, DEF_OPTION, option);
      this.distance     = Math.sqrt (x * x + y * y + z * z); //原点までの距離を算出
    }
  }

  //__________

  this.Light = Light;
}


//___________________________________

{ //マウスのドラッグ操作で配列の回転を制御(QUATERNIONによる回転)

  const
    SQRT            = Math.sqrt,
    SIN             = Math.sin,
    COS             = Math.cos,
    PI              = Math.PI,
    DEG             = PI / 180,

    TOUCH_EVENT     = ['touchstart', 'touchend', 'touchmove'],
    MOUSE_EVENT     = ['mousedown', 'mouseup', 'mousemove', 'mouseout'],
    DEF_OPTION      = {
      inertia     : true, //慣性モード有効
      sensitivity : 500,  //マウスの感度
      gain        : 0.999 //慣性移動の減速率
    };


  //ローテーションクラス本体
  class RotationController {

    constructor (element = document.body, model, option = {}) {
      this.target      = element;
      this.model       = model;
      this.option      = Object.assign ({}, DEF_OPTION, option);
      this.mouseX      = null;//マウス座標の基点
      this.mouseY      = null;//マウス座標の基点
      this.touched     = false; //ドラッグ中か?
      this.animeId     = 0;
      this.timeStamp   = null; //慣性を行うかどうかのもう一つの基準
      this.dx          = 0;//マウスの慣性移動量
      this.dy          = 0;//マウスの慣性移動量
      this.sensitivity = 1 / this.option.sensitivity; //mouse移動の感度
      this.inertia_min = 0.001; //最小の移動量で完成移動を止める

      //touchイベントがあるなら優先
      (window.TouchEvent ? TOUCH_EVENT: MOUSE_EVENT)
        .forEach (addEventType, this)
    }


    //各イベント処理
    handleEvent (event) {
      let e = event.target;
      switch (event.type) {

      // 制御終了
      case 'mouseup' :
      case 'mouseout' :
      case 'touchend' :
        if (this.touched){
          this.touched = false;
          if (event.timeStamp - this.timeStamp < 50) //mouseup から 50mm秒以内なら慣性モードへ
            if (this.option.inertia)
              inertia.call (this);//制御を慣性に移す
        }
        break;

      // 制御開始
      case 'mousedown' :
      case 'touchstart' :
        if (this.animeId) {
          cancelAnimationFrame (this.animeId);
          this.animeId = 0;
        }
        this.dx = 0;
        this.dy = 0;
        this.mouseX = event.pageX;
        this.mouseY = event.pageY;
        this.touched = true;
        break;

      // 回転制御中
      case 'mousemove' :
      case 'touchmove' :
        if (this.touched) {
          event.preventDefault ();//ipadなどでスクロールさせないため
          this.timeStamp = event.timeStamp;
          let
            {pageX, pageY} = event,
            {mouseX, mouseY, sensitivity} = this,
            sx = mouseX - pageX,
            sy = mouseY - pageY;
          if (sx || sy) {
            this.dx = sx * sensitivity;
            this.dy = sy * sensitivity;

            rotation.call (this);
            this.mouseX = pageX;
            this.mouseY = pageY;
          }
        }
        break;
      }
    }

    //quaternion rotation
    //回転の関数の独立した呼び出し
    static rotation (aryPoint3D, qx, qy, qz, qa) {
      let ql = qx * qx + qy * qy + qz * qz;

      if (ql) {
        let qh = qa * DEG * .5, s = SIN (qh) / SQRT (ql);
        product (aryPoint3D, qx * s, qy * s, qz * s, COS (qh));
      }
    }
  }


  const
    //イベントの登録
    addEventType =
      function (eventType) {
        this.target.addEventListener (eventType, this, false);
      },


    //慣性モードに移行
    inertia =
      function () {
        let gain = this.option.gain;
        this.dx *= gain;
        this.dy *= gain;
        if (this.inertia_min < rotation.call (this))
          this.animeId = requestAnimationFrame (inertia.bind (this));
      },


    //回転を行う
    rotation =
      function () {
        //画面の2次元移動量から3次元の回転量を求める
        let
          {dx, dy} = this,
          t = dx * dx + dy * dy;

        if (t) {
          // クオータニオンによる回転
          t = SQRT (t);
          let as = -SIN (t) / t;
          product (this.model.vertex, dy * as, dx * as, 0, COS (t));
        }
        return t; //移動距離を返す(慣性?)
      },


    //積(座標)の計算
    product =
      function (point, q0, q1, q2, q3) {
        for (let i = 0, p; p = point[i]; i++) {
          let
            {x, y, z} = p,
            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;

          point[i].x = a0 * q3 - a3 * q0 - a1 * q2 + a2 * q1;
          point[i].y = a1 * q3 - a3 * q1 - a2 * q0 + a0 * q2;
          point[i].z = a2 * q3 - a3 * q2 - a0 * q1 + a1 * q0;
        }
      };

  //__________

  this.RotationController = RotationController;
}


//___________________________________

{// Canvas コントローラー

  const
    DEF_OPTION = {
      surface     : true,
      hiddenSurface: true,
      lighting    : true,
      wireFrame   : false,
      vertex      : false,
      clsColor    : 'rgb(0,0,0)'
    },
    PI2 = Math.PI * 2;


  class Controller {

    constructor (canvas, camera, light, option = { }) {
      this.canvas = canvas;
      this.ctx    = canvas.getContext ('2d');
      this.camera = camera;
      this.light  = light;
      this.option = Object.assign ({}, DEF_OPTION, option);
      this.width  = canvas.width;
      this.height = canvas.height;
      this.centerX= canvas.width  * 0.5 + 0.5;
      this.centerY= canvas.height * 0.5 + 0.5;
    }


    //CANVASを塗りつぶす
    clsScreen (rgba = this.option.clsColor) {
      let {ctx, width, height} = this;
      ctx.fillStyle = rgba;
      ctx.fillRect (0, 0, width, height);
    }


    //model を描く
    draw (model) {
      let
        cos  = Math.cos,
        acos = Math.acos,
        sqrt = Math.sqrt,
        max  = Math.max,
        {ctx, centerX, centerY, light, camera, option} = this,
        {vertex, surface} = model,
        zbuf = [ ],
        {distance, option: {brightness: lb, ambientLight: la}, vector: {x: lx, y: ly, z: lz}} = light,
        distance2 = distance * distance;


      //カメラからの投影結果をxy[]に保存
      let xy = vertex.map (s => {
        let [px, py] = camera.project (s);
        return [centerX + px, centerY - py];
      });

      //面の構成が左回りを利用して2次元上で裏の向きを省く
      if (option.hiddenSurface) {
        let
          {x: cx, y: cy, z: cz} = camera.position,
          hbuf = surface.reduce ((a, b) => {
            let
              [p0, p2, p1] = b.lineList,//3点からベクトルの外積
              [[x0, y0], [x1, y1], [x2, y2]] = [xy[p0], xy[p1], xy[p2]];
            if ((x2 - x1) * (y0 - y1) - (y2 - y1) * (x0 - x1) > 0)
              a.push (b);
            return a;
          }, []);


        //距離でソート
        zbuf = hbuf.map (s => {
          let
            {x, y, z} = s.getDistance (vertex),
            dx = x - cx, dy = y - cy, dz = cz -z;
          return [dx*dx + dy*dy + dz*dz, s];
        })
          .sort ((a, b) => a[0] < b[0])
          .map ((a)=> a[1]);
      }
      else
        zbuf = surface;


      //面の描画
      if (option.surface) {
        let brightness = 1;
        zbuf.forEach (s => {
          let P = s.lineList;
          if (option.lighting) {
            let
              {x, y, z} = s.getVector (vertex),
              len = (x*lx + y*ly + z*lz) / (distance * sqrt (x*x + y*y + z*z));
            brightness = la + max (0, len * lb);
          }

          ctx.fillStyle = s.color.multiplication (brightness).toStringRGBA ();
          ctx.beginPath ();
          ctx.moveTo (...xy[P[0]]);
          P.forEach ((p, i) => { ctx.lineTo (...xy[P[i]]) });
          ctx.fill ();
        });
      }


      //線を表示する(ワイヤーフレーム)
      if (this.option.wireFrame) {
        zbuf.forEach (s => {
          let
            {lineList: P, color: C} = s,
            xy0 = xy[P[0]];
          if (C) {
            ctx.strokeStyle = C.toStringRGBA ();
            ctx.beginPath();
            ctx.moveTo (...xy0);
            P.forEach ((p) => ctx.lineTo (...xy[p]));
            ctx.lineTo (...xy0);
            ctx.stroke ();
          }
        });
      }


      //点を表示する
      if (this.option.vertex) {
        let pi2 = PI2;
        if (option.hiddenSurface)
          zbuf.forEach (s => {
            s.lineList.forEach ((k) => {
              let {color: C, option: O} = vertex[k];
              if (C) {
                ctx.fillStyle = C.toStringRGBA ();
                if (O) {
                  ctx.beginPath ();
                  ctx.arc (...xy[k], O.size, 0, pi2);
                  ctx.fill ();
              }}
            });
          });
        else
          //単純点描画処理
          vertex.forEach ((p, i) => {
            let {color: C, option: O} = p;
            if (C) {
              ctx.fillStyle = C.toStringRGBA ();
              if (O) {
                ctx.beginPath ();
                ctx.arc (...xy[i], O.size, 0, pi2);
                ctx.fill ();
            }}
          });
      }

    }
  }

  //__________

  this.CTX = Controller;
}





//___________________________________

{ //物体の定義
  const
    PI     = Math.PI,
    ACOS   = Math.acos,
    ATAN2  = Math.atan2,
    SQRT   = Math.sqrt,
    SIN    = Math.sin,
    COS    = Math.cos,

    createObject = function (arg) { return new this (...arg); },
    createObject2 = function (arg) { return new this (arg); },

    RegularIcosahedron_option = {
      pointColor: new Color ()
    }
    //正二十面体を定義する
    RegularIcosahedron = function (option) {
      let
        r  = option.radius,
        gr = (1 + SQRT (5)) / 2,
        a = r / SQRT (1 + gr * gr),
        b = a * gr,

        point = [ //物体の頂点
          [ 0,-a,-b], [ 0, a,-b], [ 0,-a, b], [0, a, b],
          [-b, 0,-a], [-b, 0, a], [ b, 0,-a], [b, 0, a],
          [-a,-b, 0], [ a,-b, 0], [-a, b, 0], [a, b, 0]
        ].map (a => new Point (...a, option.pointColor)),

        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 (createObject2, Surface);

      return new Model (point, surface, option);
    },


  //正四面体を定義する
  RegularTetrahedron =
    function (option = { radius: 1}) {
      let
        r  = option.radius,
        a = 1/SQRT(3/8) * r,
        h = SQRT(2/3)*a,
        b = -h + r,

        point = [ //物体の頂点
          [0, r, 0], [0, b, r], [a / 2, b, b], [-a/2, b, b]
        ].map (createObject, Point),

        surface = [//辺の順序が面を現す(先に登録された頂点の番号)
          [ 0, 1, 2], [ 0, 2, 3], [ 0, 3, 1], [ 3, 2, 1],
        ].map (createObject2, Surface);

      return new Model (point, surface, option);
    },


    //ドーナツの定義
    Doughnut = function (option) {
      let
        {radius, N, radius2, N2} = option,
        pi2 = PI + PI,
        ringStep = pi2 / N, cStep = pi2 / N2,
        all = N * N2,
        point = [ ], surface = [ ];

      for (let i = 0; i < N; i += 1) {
        let
          angle = ringStep * i,
          idx = i * N2,
          idx2 = (idx + N2) % all;

        for (let j = 0; j < N2; j += 1) {
          let
            cAngle = cStep * j,
            r = radius + SIN (cAngle) * radius2;

          point.push (new Point (
            SIN (angle) * r,
            COS (cAngle) * radius2,
            COS (angle) * r
          ));

          surface.push (new Surface ([
            idx + j,
            idx + (j + 1) % N2,
            idx2 + (j + 1) % N2,
            idx2 + j
          ]));
        }
      }

      return new Model (point, surface, option);
    },


     //多角推
     Pyramid_option = {
       N: 3, radius: 1, radius2: 1, color: new Color (255,0,0, .7),
       offsetX: 0, offsetY: 0, offsetZ: 0
     },

     Pyramid = function (option = { }) {
         option = Object.assign ({}, Pyramid_option, option);
       let
         {N, radius, radius2, color, offsetX, offsetY, offsetZ } = option;
         pi2 = PI + PI, cStep = pi2 / N,
         point = [ ], surface = [ ], base = [ ];

       for (let i = 0; i < N; i += 1) {
         let angle = cStep * i;
         point.push (new Point (
           offsetX + SIN (angle) * radius,
           offsetY,
           offsetZ + COS (angle) * radius
         ));
         surface.push (new Surface ([N, i, (i + 1) % N], color, option));
         base.push (i);
      }
      point.push (new Point (offsetX, radius2 + offsetY, offsetZ));
      surface.push (new Surface (base.reverse (), color, option));

      return new Model (point, surface, option);
    },


    //2つのベクトルの中間を返す
    createPoint = function (a, b, r) {
      let
        { x: ax, y: ay, z: az } = a,
        { x: bx, y: by, z: bz } = b;
        cx = (ax + bx) * .5, cy = (ay + by) * .5, cz = (az + bz) * .5,
        d = cx * cx + cy * cy + cz * cz, d2 = d * 2,
        e = SQRT (r * 2 * d2) / d2;
      return new Point (cx * e, cy * e, cz * e);
    },


    //三角形を4分割する
    SplitTriangle = function () {
      let
        {surface, vertex, option} = this,
        r = option.radius, rr = r * r;

      //三角形abc の 線分acの中点をd, 線分bcの中点をe, 線分caの中点をfとする
      surface.forEach (s => {
        let
          {lineList, color, option} = s,
          [p0, p1, p2] = lineList,
          [ a,  b,  c] = [vertex[p0], vertex[p1], vertex[p2]],
          [ d,  e,  f] = [createPoint (a, b, rr), createPoint (b, c, rr), createPoint (c, a, rr)],
          [dn, en, fn] = [this.addVertex (d), this.addVertex (e), this.addVertex (f)];

        //従来の三角形を4分割した中央の三角形にする
        s.lineList = [dn, en, fn];
        [vertex[dn], vertex[en], vertex[fn]] = [d, e, f];
        //他の3つの三角形の面を追加する
        this.addSurface (new Surface ([p0, dn, fn], color, option));
        this.addSurface (new Surface ([p1, en, dn], color, option));
        this.addSurface (new Surface ([p2, fn, en], color, option));
      });
    };

  //________________________

  const Modeler = function (type, option) {
    let model;

    switch (type) {
    case 'regularIcosahedron' : //正二十面体
      model = RegularIcosahedron (option);
      for (let i = 0; i < option.split; i += 1)
        SplitTriangle.call (model);
      break;

    case 'regularTetrahedron' : //正四面体
      model = RegularTetrahedron (option);
      for (let i = 0; i < option.split; i += 1)
        SplitTriangle.call (model);
      break;

    case 'doughnut' : //ドーナツ
      model = Doughnut (option);
      break;

    case 'pyramid' : //多角推
      model = Pyramid (option);
      break;
    }

    return model;
  }

  //__________

  this.Modeler = Modeler;
}


//___________________________________

{ //demo

  const
    R      = 130,
    CANVAS = document.querySelectorAll ('canvas'),
    CAMERA = new Camera (new Point3D (0, 0, 700), 50, {scale: 20})

  //光源用の設定
  const
    LCtrl_MODEL_OPT = { radius: 10, split: 1, N:8, radius2: 60, offsetY: -20},
    LCtrl_MODEL     = Modeler ('pyramid', LCtrl_MODEL_OPT),
    LCtrl_ROTE_OPT  = {sensitivity: 80, gain: 0};
    LCtrl_ROTE      = new RotationController (CANVAS[0], LCtrl_MODEL, LCtrl_ROTE_OPT),
    LCtrl_LIGHT_OPT = {ambientLight: 0.5},
    LCtrl_LIGHT     = new Light (new Vector (0, 1, 1), new Color (), LCtrl_LIGHT_OPT),
    LCtrl_CTX_OPT   = {vertex: false, wireFrame: true, surface: true, hiddenSurface: true},
    LCtrl_CTX       = new CTX (CANVAS[0], CAMERA, LCtrl_LIGHT, LCtrl_CTX_OPT);

  //モデル用の設定
  const
    MCtrl_MODEL_OPT = { radius: R, N: 36, radius2: 30, N2: 24},
    MCtrl_MODEL     = Modeler ('doughnut', MCtrl_MODEL_OPT),
    MCtrl_ROTE_OPT  = {gain:1},
    MCtrl_ROTE      = new RotationController (CANVAS[1], MCtrl_MODEL, MCtrl_ROTE_OPT),
    MCtrl_LIGHT_OPT = {ambientLight: 0.3},
    MCtrl_LIGHT     = new Light (new Vector (0,1, 0), new Color (), MCtrl_LIGHT_OPT),
    MCtrl_CTX_OPT   = {vertex: false, wireFrame: false, surface: true, hiddenSurface: true},
    MCtrl_CTX       = new CTX (CANVAS[1], CAMERA, MCtrl_LIGHT, MCtrl_CTX_OPT);



  const
    OPACITY = 0.7,

    rnd =
      function () { return Math.floor (Math.random() * 128+128); },


    setRandomColor =
      function (model) {
        let i, surface;

        for (i = 0; surface = model.surface[i]; i++)
          surface.color = new Color (rnd (), rnd (), rnd (), OPACITY);
      },


    setColor =
      function (model, doc) {
        inp = doc.querySelectorAll ('#red, #green, #blue, #random');
        inp = Array.prototype.slice.call (inp);
        if (inp[3].checked) {
          model.surface.forEach (s => s.color = new Color (
            (inp[0].checked ? rnd () : 0),
            (inp[1].checked ? rnd () : 0),
            (inp[2].checked ? rnd () : 0),
            OPACITY
          ));
        }
        else {
          let col = new Color (255*inp[0].checked, 255*inp[1].checked, 255*inp[2].checked, OPACITY);
          model.surface.forEach (s => s.color = col);
        }
      },


    handler =
      function (event) {
        let
          e     = event.target,
          model = null, inp;

        if ('INPUT' !== e.tagName) return;

        switch (e.id) {

        case 'regularIcosahedron' : //正二十面体
        case 'regularTetrahedron' : //正四面体
        case 'doughnut' : //ドーナツ
        case 'pyramid' : //多角推
          inp = e.ownerDocument.querySelectorAll ('#radius, #radius2, #N, #N2, #split');
          inp = Array.prototype.slice.call (inp);
          let model_opt = inp.reduce ((a,b) => {a[b.id]=parseInt(b.value,10); return a}, {});
          model = Modeler (e.id, model_opt);
          Object.assign (MCtrl_MODEL, model);
          setColor (MCtrl_MODEL, e.ownerDocument);
          break;



        case 'vertex' : case 'surface': case 'wireFrame' : case 'hiddenSurface' :
          inp = e.ownerDocument.querySelectorAll ('#vertex, #surface, #wireFrame, #hiddenSurface');
          inp = Array.prototype.slice.call (inp);
          let ctx_opt = inp.reduce ((a,b) => {a[b.id]=b.checked; return a}, {});
          Object.assign (MCtrl_CTX.option, ctx_opt);
          break;

        case 'red': case 'green' : case 'blue' : case 'random' :
          setColor (MCtrl_MODEL, e.ownerDocument);
          break;
        }

      },


    loop = function loop () {
      LCtrl_CTX.clsScreen ();
      LCtrl_CTX.draw (LCtrl_MODEL);

      MCtrl_CTX.clsScreen ();
      MCtrl_CTX.draw (MCtrl_MODEL);

      requestAnimationFrame (loop);
    };


  //モデル用の光源ベクトルを、光源用のモデルの頂点に追加する
  LCtrl_MODEL.addVertex (MCtrl_LIGHT.vector);

  //モデルの表面をランダムな色にする
  setRandomColor (MCtrl_MODEL);

  //イベントの登録
  document.addEventListener ('click',      handler, false);
  document.addEventListener ('touchStart', handler, false);

  this.demo = loop;
}

//___________________________________

demo ();

</script>


2017-02-03

3次ベジェ曲線を加減算&乗算&ビット演算で高速に計算する

| 15:52

これから徐々に ES6 を目指そう!

なんと偶然なのかES6を勉強し始めました。

ほう!とかへえ!とか面白いです。

アロー関数のthisの扱いに戸惑う。forEach とかで this とかを多用している身としては残念。

しかも省略したアロー関数はiPad & iPod Toush では無理だった。

<!DOCTYPE html>
<meta charset="utf-8">
<title>3次ベジェ曲線</title>
<style>
</style>

<body>
<h1>ベジエ曲線を整数の加減算だけで描画する方法</h1>
<h2>高速に2次ベジェ曲線を計算する</h2>
<p>JavaScriptでは整数型の演算ができるわけではないが以下を参考に作る
<p><a href="http://studio-rain.cocolog-nifty.com/blog/2008/07/post_c4f5.html">
http://studio-rain.cocolog-nifty.com/blog/2008/07/post_c4f5.html</a>
<p><a href="https://peta.okechan.net/test/bezier3/">
https://peta.okechan.net/test/bezier3/</a>

<p>世の中すごい人だらけ</p>

<canvas width="800" height="400"></canvas>
<script>
//"use strict";


{// Canvas コントローラー

  const DEF_OPTION = {
    offset: {x: 0, y: 0},
    fillColor: 'rgb(0,0,0)',
    grid: {x: 30, y: 30}
  };

  const PI2 = Math.PI * 2;


  class Controller {

    constructor (canvas, option = { }) {
      this.canvas = canvas;
      this.ctx    = canvas.getContext ('2d');
      this.option = Object.assign (DEF_OPTION, option);
    }


    setGrid () {
      var [ctx, oft, cav] = [this.ctx, this.option.offset, this.canvas];
      var [x, y] = [oft.x, oft.y];
      var [w, h] = [cav.width, cav.height];

      ctx.beginPath();
      ctx.strokeStyle = 'rgb(200,200,200)';
      ctx.strokeRect (0, 0, w, h);

      ctx.fillStyle = 'rgb(150,150,150)';
      ctx.fillRect (x+1, y-1, w-2, 3);
      ctx.fillRect (x-1, 1, 3, h-2);
    }


    pset (x, y, color = this.option.fillColor, size = 1) {
      var offset = this.option.offset;
      var ctx = this.ctx;

      ctx.fillStyle = color;
      ctx.fillRect (x + offset.x, offset.y - y, size, size)
    }


    point (x, y, size = 1, color = this.option.fillColor) {
      var offset = this.option.offset;
      var ctx = this.ctx;

      ctx.fillStyle = color;
      ctx.beginPath ();
      ctx.arc(x + offset.x, offset.y - y, size, 0, PI2);
      ctx.fill ();
    }
  }

  this.CTX = Controller;
}


{
  // view-source:https://peta.okechan.net/test/bezier3/

  var nbit = 7; //=128
  var n3bit = nbit * 3;
  var N = 1 << nbit;

  function intBezier3 (x0, y0, x1, y1, x2, y2, x3, y3) {

    var NB3x = x0 << n3bit;
    var NB3y = y0 << n3bit;

    var dNB3x = x3 + (3 * N - 3) * x2 + (3 * N * N - 6 * N + 3) * x1 + (-3 * N * N + 3 * N - 1) * x0;
    var dNB3y = y3 + (3 * N - 3) * y2 + (3 * N * N - 6 * N + 3) * y1 + (-3 * N * N + 3 * N - 1) * y0;

    var d2NB3x = 6 * x3 + (6 * N - 18) * x2 + (18 - 12 * N) * x1 + (6 * N - 6) * x0;
    var d2NB3y = 6 * y3 + (6 * N - 18) * y2 + (18 - 12 * N) * y1 + (6 * N - 6) * y0;

    var d3NB3x = 6 * x3 - 18 * x2 + 18 * x1 - 6 * x0;
    var d3NB3y = 6 * y3 - 18 * y2 + 18 * y1 - 6 * y0;


    var i;
    this.push ([x0, y0]);

    for (i = 0; i < N; i++){
      NB3x += dNB3x;
      NB3y += dNB3y;
      dNB3x += d2NB3x;
      dNB3y += d2NB3y;
      d2NB3x += d3NB3x;
      d2NB3y += d3NB3y;
      this.push ([NB3x >> n3bit, NB3y >> n3bit]);
    }
  }


  function intBezier (p) {
    var a = p[0],
        b = p[1],
        c = p[2],
        d = p[3];
    var i = 4,
        r = [ ];

    while (d) {
      intBezier3.call (r, a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);

      a = d;
      b = [
        d[0] + (d[0] - c[0]),
        d[1] + (d[1] - c[1])
      ];
      c = p[i++];
      d = p[i++];
    }
    return r;
  }


  this.intBezier3 = intBezier;
}



var c = new CTX (document.querySelector ('canvas'), { offset: {x: 0, y: 255}});
var p = intBezier3 ([[0,60], [100,200],[300,60], [800,100]]);
var i, q;

c.setGrid ();
for(i = 0; q = p[i]; i+=1) {
  c.point (q[0], q[1], 4, 'rgb(255,0,0)');
}


</script>



見よう見まねで ES6で書いてみた

<!DOCTYPE html>
<meta charset="utf-8">
<title>3次ベジェ曲線</title>
<style>
</style>

<body>
<h1>ベジエ曲線を整数の加減算だけで描画する方法</h1>
<h2>高速に2次ベジェ曲線を計算する</h2>
<p>JavaScriptでは整数型の演算ができるわけではないが以下を参考に作る
<p><a href="http://studio-rain.cocolog-nifty.com/blog/2008/07/post_c4f5.html">
http://studio-rain.cocolog-nifty.com/blog/2008/07/post_c4f5.html</a>
<p><a href="https://peta.okechan.net/test/bezier3/">
https://peta.okechan.net/test/bezier3/</a>

<p>世の中すごい人だらけ</p>

<canvas width="800" height="400"></canvas>
<script>
"use strict";


{// Canvas コントローラー

  const
    DEF_OPTION = {
      offset: {x: 0, y: 0},
      fillColor: 'rgb(0,0,0)',
      strokeStyle: 'rgb(0,0,0)',
      grid: {x: 30, y: 30}
     },
    PI2 = Math.PI * 2;


  class Controller {

    constructor (canvas, option = { }) {
      this.canvas = canvas;
      this.ctx    = canvas.getContext ('2d');
      this.option = Object.assign (DEF_OPTION, option);

      if (option.grid)
        this.setGrid ();
    }


    setGrid () {
      let
        [ctx, oft, cav] = [this.ctx, this.option.offset, this.canvas],
        {x, y} = oft,
        {width, height} = cav;

      ctx.beginPath();
      ctx.strokeStyle = 'rgb(200,200,200)';
      ctx.strokeRect (0, 0, width, height);

      ctx.fillStyle = 'rgb(150,150,150)';
      ctx.fillRect (x+1, y-1, width-2, 3);
      ctx.fillRect (x-1, 1, 3, height-2);
    }


    pset (px, py, color = this.option.fillColor, size = 1) {
      let
        {x, y} = this.option.offset,
        ctx    = this.ctx;

      ctx.fillStyle = color;
      ctx.fillRect (px + x, y - py, size, size);
    }


    point (px, py, size = 1, color = this.option.fillColor) {
      let
        {x, y} = this.option.offset,
        ctx    = this.ctx;

      ctx.fillStyle = color;
      ctx.beginPath ();
      ctx.arc(px + x, y - py, size, 0, PI2);
      ctx.fill ();
    }
  }

  this.CTX = Controller;
}



{ // view-source:https://peta.okechan.net/test/bezier3/

  const int_bezier3 = function (r, x0, y0, x1, y1, x2, y2, x3, y3) {
    let
      [nBit, q, N, q0, q1, q2, q3, q4, q5] = this.pram,
      NB3x = x0 << q,
      NB3y = y0 << q,
      dNB3x = x3 + q0 * x2 + q1 * x1 + q2 * x0,
      dNB3y = y3 + q0 * y2 + q1 * y1 + q2 * y0,
      d2NB3x = 6 * x3 + q3 * x2 + q4 * x1 + q5 * x0,
      d2NB3y = 6 * y3 + q3 * y2 + q4 * y1 + q5 * y0,
      d3NB3x = 6 * x3 - 18 * x2 + 18 * x1 - 6 * x0,
      d3NB3y = 6 * y3 - 18 * y2 + 18 * y1 - 6 * y0;

    r.push ([x0, y0]);
    for (let i = N; i--; ) {
      NB3x += dNB3x;    NB3y += dNB3y;
      dNB3x += d2NB3x; 	dNB3y += d2NB3y;
      d2NB3x += d3NB3x; d2NB3y += d3NB3y;
      r.push ([NB3x >> q, NB3y >> q]);
    }
  };
    
  
  class IntBezier3 {

    constructor (nBit = 7) {
      if (nBit < 1 || 10 < nBit)
        throw new Error ('ビット数が範囲外!!');
       
      let N = 1 << nBit;
      this.pram = [
         nBit,
         nBit * 3,
         N,
         3 * N - 3,
         3 * N * N - 6 * N + 3, 
        -3 * N * N + 3 * N - 1,
         6 * N - 18,
        18 - 12 * N,
         6 * N - 6
      ];
    }
    

    getPoint (p = [ ]) {
      if (4 > p.length)
        throw new Error ('配列の数が足りません');

      let [a, b, c, d] = p.slice (0, 4), i = 4, r = [ ], f, g;

      do {
        int_bezier3.call (this, r, ...a, ...b, ...c, ...d);

        if (! (f = p[i++]) || ! (g = p[i++]))
          break; //点を2個づつ消費する。半端な場合は途中までの結果を返す

        let [dx, dy] = d, [cx, cy] = c;
        [a, c, d, ...b] = [d, f, g, dx + dx - cx, dy + dy - cy];
        //bは、前の点Cとdの直線上の点

      } while (true);

      return r;
    };
  }

  this.IntBezier3 = IntBezier3;
}

new CTX (document.querySelector ('canvas'), { offset: {x: 0, y: 255}, grid: true });


var bz = new IntBezier3 ();
var ary = bz.getPoint ([[0,60], [200,450],[400,0], [600,0]]);
    ary.forEach (
      function (q) { this.point (...q, 4, 'rgb(255,0,0)') },
      new CTX (document.querySelector ('canvas'), { offset: {x: 0, y: 255}, grid: true })
    );

</script>


ES6にむけて

(function () {
  function fuga () {
    //;
  }

  function Hoge (def) {
    this.def = def;
  }

  function piyo () {
    huga.call (this);
  }


  function create (def) {
    if ('undefined' === typeof def)
      def = 123;
    return new Hoge (def);
  }

  Hoge.prototype.piyo = piyo;
  Hoge.create = create;

  this.Hoge = Hoge;
}) ();

下のように置き換える

{
  const fuga = function () { ; };

  class Hoge {
    constructor (def = 123) {
      this.def = def;
    }

    piyo () {
      fuga.call (this);
    }
  }

  this.Hoge = Hoge;
}

2017-02-02

2次ベジェ曲線を高速に描く(加減算と乗算だけで)

| 14:42

<!DOCTYPE html>
<meta charset="utf-8">
<title>ベジェ曲線</title>
<style>
</style>

<body>
<h1>ベジエ曲線を整数の加減算だけで描画する方法</h1>
<h2>高速に2次ベジェ曲線を計算する</h2>
<p>JavaScriptでは整数型の演算ができるわけではないが以下を参考に作る
<p><a href="http://studio-rain.cocolog-nifty.com/blog/2008/07/post_c4f5.html">
http://studio-rain.cocolog-nifty.com/blog/2008/07/post_c4f5.html</a>
<p>世の中すごい人だらけ</p>

<canvas width="800" height="400"></canvas>
<script>


(function () {
  var OPTION_DEFAULT = {
    offset: {x: 0, y: 0},
    fillColor: 'rgb(0,0,0)',
    grid: {x: 30, y: 30}

  };

  function Controller (canvas, option) {
    this.canvas = canvas;
    this.ctx    = canvas.getContext ('2d');
    this.option = option;
  }

  function setGrid () {
    var ctx = this.ctx;
    var x = this.option.offset.x;
    var y = this.option.offset.y;
    var h = this.canvas.height;
    var w = this.canvas.width;

    ctx.beginPath();
    ctx.strokeStyle = 'rgb(200,200,200)';
    ctx.strokeRect (0, 0, w, h);

    ctx.fillStyle = 'rgb(150,150,150)';
    ctx.fillRect (x+1, y-1, w-2, 3);
    ctx.fillRect (x-1, 1, 3, h-2);
  }

  function pset (x, y, color, size) {
    var offset = this.option.offset;
    var ctx = this.ctx;
    ctx.fillStyle = color || this.option.fillColor || 'rgb(0,0,0)';
    ctx.fillRect (x + offset.x, offset.y - y, size, size);//大きさも考えろよ
  }


  function create (canvas, option) {
    var ctx = canvas.getContext ('2d');

    if ('undefined' === typeof option)
      option = { };

    for (p in OPTION_DEFAULT) //標準設定で上書き
      if (OPTION_DEFAULT.hasOwnProperty (p))
        if (! option.hasOwnProperty (p))
          option[p] = OPTION_DEFAULT[p];

    return new Controller (canvas, option);
  }

  Controller.prototype.setGrid = setGrid;
  Controller.prototype.pset = pset;

  Controller.create = create;

  this.CTX = Controller;
}) ();


//2次ベジェ曲線ジェネレータ
// var aryPoint = intbz ([x0,y0], [x1,y1], ..])
//
// ビット演算で計算しているため、点p0から点p2まで 2^7 で分割される

(function () {
  var nbit = 7; //128
  var n2bit = nbit + nbit;
  var N = 1 << nbit;
  var N2 = 1 << n2bit;
  var q0 = N + N -2;
  var q1 = 1 - 2 * N;

  function intP3bz (x0, y0, x1, y1, x2, y2) {
    var ax = x0 * N2, ay = y0 * N2,
        bx = x2 + q0 * x1 + q1 * x0, by = y2 + q0 * y1 + q1 * y0,
        cx = x2 + x2 - 4 * x1 + x0 + x0, cy = y2 + y2 - 4 * y1 + y0 + y0;

    for (var i = N; i--; ax += bx, ay += by, bx += cx, by += cy)
      this.push ([ax >> n2bit, ay >> n2bit]);
  }


  function intbz (p) {
    var a = p[0], b = p[1], c, i, r = [ ];

    for (i = 2; c = p[i++]; )
      intP3bz.call (r, a[0], a[1], b[0], b[1], c[0], c[1]),
      b = [c[0] + (c[0] - b[0]), c[1] + c[1] - b[1]], a = c, c = p[i];

    return r;
  }


  this.intbz = intbz;

}) ();



var c = CTX.create (document.querySelector ('canvas'), { offset: {x: 0, y: 200}});
c.setGrid ();

var p = intbz ([[0,0], [200,100], [400,0], [600,100], [800,0]]);
for(var i = 0; q = p[i]; i+=1) {
  c.pset (q[0], q[1], 'rgb(255,0,0)', 4);
}
</script>