Three.js で遊び倒す - カメラを動かそう Part.2 -

Three.js day 5 となりました。

[昨日](https://qiita.com/duka/items/9747cf5b49a58c42578e) は、星空を作りました。
本日は、カメラを動かします。


#1. 完成版

<p class="codepen" data-height="294" data-theme-id="0" data-default-tab="html,result" data-user="hiroyaiizuka" data-slug-hash="PLLYQq" style="height: 294px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid black; margin: 1em 0; padding: 1em;" data-pen-title="universe">
  <span>See the Pen <a href="https://codepen.io/hiroyaiizuka/pen/PLLYQq/">
  universe</a> by hiroya iizuka (<a href="https://codepen.io/hiroyaiizuka">@hiroyaiizuka</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>


#2. 参考文献
[ICS MEDIA](https://ics.media/tutorial-three/index.html)
[初めてのThree.js](https://www.amazon.co.jp/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AEThree-js-%E7%AC%AC2%E7%89%88-%E2%80%95WebGL%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AEJavaScript-3D%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA-Dirksen/dp/4873117704)
[CodeGrid](https://app.codegrid.net/entry/threejs-location)



#3. 分解してみる


❶. 
前回までのコードです。

```index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/102/three.min.js"></script>
    <style>
      body {
        margin: 0;
        padding: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <canvas class="stage"></canvas>

    <script>
      function init() {
        //レンダラーを作成
        const renderer = new THREE.WebGLRenderer({
          canvas: document.querySelector(".stage")
        });
        renderer.setSize(window.innerWidth, window.innerHeight);

        //シーンを作成
        const scene = new THREE.Scene();

        starNight();
        function starNight() {
          // 形状データを作成
          const geometry = new THREE.Geometry();

          for (let i = 0; i < 1000; i++) {
            const star = new THREE.Vector3();
            star.x = THREE.Math.randFloatSpread(3000);
            star.y = THREE.Math.randFloatSpread(3000);
            star.z = THREE.Math.randFloatSpread(3000);

            geometry.vertices.push(star);
          }

          // マテリアルを作成
          const material = new THREE.PointsMaterial({
            size: 10,
            color: 0xffffff
          });
          // 星を作成
          const star = new THREE.Points(geometry, material);
          scene.add(star);
        }

        //カメラを作成
        const camera = new THREE.PerspectiveCamera(
          50,
          window.innerWidth / window.innerHeight,
          0.1,
          2000
        );
        camera.position.set(0, 0, 1000);
        camera.lookAt(new THREE.Vector3(0, 0, 0));

        //球を作成
        const geometry = new THREE.SphereGeometry(100, 32, 32);
        const material = new THREE.MeshNormalMaterial();
        const mesh = new THREE.Mesh(geometry, material);
        scene.add(mesh); 

        renderScene();

        //アニメーション
        function renderScene() {
          mesh.rotation.x += 2;
          requestAnimationFrame(renderScene);
          renderer.render(scene, camera);
        }
      }

      window.addEventListener("load", init);
    </script>
  </body>
</html>
```
![スクリーンショット 2019-03-24 11.25.07.png](https://qiita-image-store.s3.amazonaws.com/0/232715/9a5ec1f0-1e93-b22f-5738-e5bb1b9c4a0d.png)


この中心の球を注視しながら、カメラが回転するようにしましょう。

## 数学の復習をしよう！

カメラの動きを理解するためには、数学の知識が必要です。
テンション下がりますね・・・。

[三角関数](https://qiita.com/rakusan/items/2f60f2ae6af208dda40f) を学び直しましょう。

![スクリーンショット 2019-03-25 7.03.22.png](https://qiita-image-store.s3.amazonaws.com/0/232715/bd29efae-50bf-8c9b-916c-4d1e90762266.png)

半径がr の円における、円周上の点の座標は

``X: r cosθ　　Y: r sinθ``

で計算されました。

しかし、three.js では、θではなく、``radian`` を使います。
radian ってなんだったでしょうか・・・？汗

###radian とは？


radian とは、円の半径に等しい長さの弧の中心に対する角度 のことです。

![1200px-Radian_cropped_color.svg.png](https://qiita-image-store.s3.amazonaws.com/0/232715/a9174d3c-1598-94da-a293-717002b3ed01.png)



例えば、半径が2cm の円があります。
弧の長さもちょうど2cm の時に、半径と、半径で作られる角度のことを、1 radian と言います。
弧の長さと、半径の比がradian と考えるとわかりやすいですね。

弧が長くなって、4cm になると 中心角は 2radian になります。    
したがって、 4π cm (円周)になると、中心角は、２π radian になります。

つまり、下の式が成り立ちます。
>
360 度 = 2π    (radian)
180 度 = π     (radian)
1   度 = π/180 (radian)


__扇型の中心角を、長さを使って表現できる__のが、radian の特徴というわけですね。


半径がr の弧の場合は
``孤の長さ = 2 × π　×　θ/360 ``

となりますが、



なぜ、こんな面倒な radian を使うのか?
と興味を持たれましたかたは、[こちら](http://nicomar.style.coocan.jp/OPL/001.pdf) をご参照ください。
数学って難しいですね・・・


###カメラの位置をradian で表そう！

本題に戻ります。
円周上の座標は

``X: r cosθ　　Y: r sinθ``

で表されましたが、これらを、radian  に変換すると


```
(角度 = rot) 

X: r cos(rot × π/180) 
Y: r sin(rot × π/180)
```

となります。
πは、``Math.PI`` で表されるため、Three.js では、以下に変わります。


```index.html
X: r * Math.cos(rot * Math.PI/180)
Y: r * Math.sin(rot * Math.PI/180)
```

これで、camera.position.x = ~ と、camera の座標を表せば良いです。
rot(角度) については、アニメーションの設定の中で、徐々に増えていくように設定しましょう。


```index.html

 let rot = 0;

 function renderScene() {

          rot += 0.5;
          const radian = (rot * Math.PI) / 180;

          camera.position.x = 1000 * Math.sin(radian);
          camera.position.z = 1000 * Math.cos(radian);
          camera.lookAt(new THREE.Vector3(0, 0, 0));

          requestAnimationFrame(renderScene);
          renderer.render(scene, camera);
        }
      
```
 

#4. 最終コード


```index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/102/three.min.js"></script>
    <style>
      body {
        margin: 0;
        padding: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <canvas class="stage"></canvas>

    <script>
      function init() {
        let rot = 0;
        //レンダラーを作成
        const renderer = new THREE.WebGLRenderer({
          canvas: document.querySelector(".stage")
        });
        renderer.setSize(window.innerWidth, window.innerHeight);

        //シーンを作成
        const scene = new THREE.Scene();

        starNight();
        function starNight() {
          // 形状データを作成
          const geometry = new THREE.Geometry();

          for (let i = 0; i < 1000; i++) {
            const star = new THREE.Vector3();
            star.x = THREE.Math.randFloatSpread(3000);
            star.y = THREE.Math.randFloatSpread(3000);
            star.z = THREE.Math.randFloatSpread(3000);

            geometry.vertices.push(star);
          }

          // マテリアルを作成
          const material = new THREE.PointsMaterial({
            size: 10,
            color: 0xffffff
          });
          // 物体を作成
          const star = new THREE.Points(geometry, material);
          scene.add(star);
        }

        //カメラを作成
        const camera = new THREE.PerspectiveCamera(
          50,
          window.innerWidth / window.innerHeight,
          0.1,
          2000
        );

        //球を作成
        const geometry = new THREE.SphereGeometry(100, 32, 32);
        const material = new THREE.MeshNormalMaterial();
        const mesh = new THREE.Mesh(geometry, material);
        scene.add(mesh); 

        renderScene();

        //アニメーション
        function renderScene() {
          //radian の設定
          rot += 0.5;
          const radian = (rot * Math.PI) / 180;

          //カメラのpositioning
          camera.position.x = 1000 * Math.sin(radian);
          camera.position.z = 1000 * Math.cos(radian);
          camera.lookAt(new THREE.Vector3(0, 0, 0));

          requestAnimationFrame(renderScene);
          renderer.render(scene, camera);
        }
      }

      window.addEventListener("load", init);
    </script>
  </body>
</html>
```

<p class="codepen" data-height="294" data-theme-id="0" data-default-tab="html,result" data-user="hiroyaiizuka" data-slug-hash="PLLYQq" style="height: 294px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid black; margin: 1em 0; padding: 1em;" data-pen-title="universe">
  <span>See the Pen <a href="https://codepen.io/hiroyaiizuka/pen/PLLYQq/">
  universe</a> by hiroya iizuka (<a href="https://codepen.io/hiroyaiizuka">@hiroyaiizuka</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

できました。
綺麗な眺めです。

次回は、インタラクション操作を取り込んでいきます。
それでは、また明日〜