JavaScriptのcanvasで立方体を回転させてみる

#目標
- JavaScriptのcanvasで、テンキーを押したときに立方体を回転させてみる。

#下準備
まず、次のコードを保存しておきます。

```html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>3D</title>
  </head>
  <body>
    <canvas id="canvas" width="400" height="400"></canvas>
    <script>
    </script>
  </body>
</html>
```
以下、この`<script></script>`内にJavaScriptのコードを書いていきます。
#描画関数の作成
##canvasを扱う準備
次の関数を書きます。

```js
function draw(){ // 描画
  const ctx = document.getElementById("canvas").getContext("2d");
  ctx.clearRect(0, 0, 400, 400); // 初期化
}
```
このコードの`ctx.clearRect(0, 0, 400, 400);`で、画面を消去しています。
これで、JavaScriptのcanvasを扱う準備ができました。
##座標を記録
次に、頂点の座標を変数`coord`に、面の情報を`faces`に記録します。

```js
const coord = [ // 座標
  [-100, -100, -100],
  [ 100, -100, -100],
  [-100,  100, -100],
  [ 100,  100, -100],
  [-100, -100,  100],
  [ 100, -100,  100],
  [-100,  100,  100],
  [ 100,  100,  100]
];

const faces = [ // 面
  [0,1,3,2],[5,4,6,7],
  [0,1,5,4],[3,2,6,7],
  [0,2,6,4],[3,1,5,7]
];
```
座標は、`[x座標, y座標, z座標]`という形で記録します。
面の座標は、頂点の番号です。
##回転のための準備
回転させた後の座標を返す関数を作成しておきます。

```js
let ang = Date.now()/810;
let ang2 = Date.now()/1081;
function rotat(x, y, z){ // 角度の分だけ回転
  [x, y] = [x*Math.cos(ang1) - y*Math.sin(ang1), x*Math.sin(ang1) + y*Math.cos(ang1)];
  [y, z] = [y*Math.cos(ang2) - z*Math.sin(ang2), y*Math.sin(ang2) + z*Math.cos(ang2)];
  return [x,y,z];
}
```
ここで、`ang1`はz軸を軸として回転する角度で、`ang2`はx軸を軸として回転する角度です。
このとき、回転されているかを確認するため、暫定的に回転角度を時間によって変化するようにしておきます。
##描画開始
関数`draw`内に描画させる関数を作成しておきます。

```js
function draw(){ // 描画
  const ctx = document.getElementById("canvas").getContext("2d");
  ctx.clearRect(0, 0, 400, 400); // 初期化
  
  for(let i in faces){ // 面の描画
    let rtteds = []; //回転後の座標を記録する配列
    for(let j of faces[i]){
      rtteds.push(rotat(...coord[j])) // 回転後の座標
    }
    
    ctx.beginPath(); //描画開始
    ctx.fillStyle = ["#333","#fff","#0f0","#f0f","#9ff","#ff9"][i];
    ctx.moveTo(200+rtteds[0][0], 200+rtteds[0][2]);
    for(let j of rtteds){
      ctx.lineTo(200+j[0], 200+j[2]);
    }
    ctx.fill();
  }
}
```
###実行結果
実際に実行してみると、不自然なことに気づきます。
シアンの面が表示されないのです。
これを解消するために、y座標が遠い順に描画するようにします。
そのために、遠さを調べる関数を用意します。

```js
function rem(ary){ // 遠さを調べる関数
  return ary.map(a=>rotat(...coord[a])[1]).reduce((a,b)=>a+b)/ary.length;
}

```
この関数は`[1,2,4,3]`のように、座標の番号が与えられたときに、それぞれの回転後のy座標の平均を返します。
それで、`draw()`を以下のように書き換えます。

```js
function draw(){ // 描画
  const ctx = document.getElementById("canvas").getContext("2d");
  ctx.clearRect(0, 0, 400, 400); // 初期化

  let sorted = faces.map((a,i)=>[a,i]).sort((a,b)=>rem(a[0])-rem(b[0])); // facesをsortしたもの
  for(let i of sorted){ // 面の描画
    let rtteds = []; //回転後の座標を記録する配列
    for(let j of i[0]){
      rtteds.push(rotat(...coord[j])) // 回転後の座標
    }

    ctx.beginPath(); //描画開始
    ctx.fillStyle = ["#a05","#a00","#a50","#aa0","#5a0","#0a0"][i[1]];

    ctx.moveTo(200 + rtteds[0][0], 200 - rtteds[0][2]);
    for(let j of rtteds){
      ctx.lineTo(200 + j[0], 200 - j[2]);
    }
    ctx.fill();
  }
}

```
これで実行すると、遠い順に描画されるようになりました。
#キーでの移動
キーで移動させるには、次のプログラムを使います。

```js
let ang1 = 0;
let ang2 = 0;
```
```js
document.body.onkeydown = e =>{
  switch(e.keyCode){
    case 37:
      ang1-=0.1; // 左
      break;
    case 38:
      ang2-=0.1; // 上
      break;
    case 39:
      ang1+=0.1; // 右
      break;
    case 40:
      ang2+=0.1; // 下
      break;
  }
};
```

#完成
そうして、完成したのがこちらです。
[3d test](http://jsrun.it/assets/Y/G/8/8/YG88Q)