地球をグルグルしてみませんか？ 【初めてのThree.jsへの挑戦】

【はじめに】
============
この記事は「[【マイスター・ギルド】本物の Advent Calendar 2021](https://adventar.org/calendars/6579)」15日目の記事です。

始めまして、今年の8月にMGに入社しましたおっさんエンジニアj_haraです。
アドカレも残すところあと4日となりますが、最後まで楽しくお付き合い頂ければと思います。
【経緯】
============
本投稿ですが、先日「**地球をグルグルすることはできないか**」ということを調べる必要が出てきました。**グルグル**って何？！となっていると思いますが、「**地球を描画しマウスで掴んで回したい**」ということです。
その際にThree.jsを使用し、意外と簡単にできることが分かりましたので、備忘録も兼ね投稿することにしました。
【対象読者】
============
・もちろん地球をグルグルしてみたい人
・これからThree.jsを触ってみたいなと考えている人
・触ってみたいが、二の足を踏んでいる人
【この記事ですること】
============
・Three.jsを用いて地球を作成し、マウスで操作(掴んで回す、縮尺)をできるようにする。
【準備編】
============
ではまず環境の準備から始めます。
Three.jsを使用するには、ライブラリーを読み込む必要があります。
方法は以下の3つありまして、今回は「CDNで読み込む方法」を取っています。
・zipで落としてくる方法
・npmでインストールする方法
・CDNで読み込む方法

以下のような感じで、HTMLに記述していきます。

```index.html
  <script src="https://unpkg.com/three@0.131.3/build/three.min.js"></script>
```

うん、とっても簡単。
Three.jsはcanvas要素を利用するとのことなので、描画エリアとなるcanvas要素を作成。
こちらもHTMLに記述していきます。

```index.html
<body>
  <canvas id="myCanvas"></canvas>
</body>
```

【実装編】
============
ここからはjsファイルへ記述します。

jsファイルの準備として地球を作成する関数を定義します。

```index.js
function makeEarth() {

}
```

そして、この関数をページの読み込みを待ってからコールするようにしておきます。

```index.js
  //ページの読み込みを待つ
  window.addEventListener('load', makeEarth);
```

これ以降は上記makeEarth関数の中に記述していきます。


ではメイン部分を作成していきましょう。
調べてみると大きく4つ、以下のものを定義する必要がありそうです。
・ステージ(シーン)
・物体(メッシュ)
・光源
・カメラ

たんたんと順番に定義していきます。
### ステージ(シーン)
まずはステージから作成します。ステージとは物体を置くための空間のこと。
以下のように定義するだけ。
ここにこの後、物体、カメラ、光源を追加していきます。

```index.js
  //シーンを作成(シーン：3D空間のことで、3Dオブジェクトや光源などの置き場)
  const scene = new THREE.Scene();
```

### 物体(メッシュ)
次は物体(メッシュ)を作ります。
ただ、物体には形状と素材の定義が必要とあるので先にそちらから作成していきます。

今回は地球を作りたいので形状としては球体を定義しています。

```index.js
  //球体を用意
  const geometryEarth = new THREE.SphereGeometry(80, 20, 20);
```

ちなみに形状には、以下のように球体のほかに立方体、円柱などもあるみたいです。
　THREE.BoxGeometry　　　　：立方体
　THREE.CylinderGeometry　　：円柱

素材のテクスチャーには地球の画像をあてます。

```index.js
  //地球の素材を用意
  const loaderEarth = new THREE.TextureLoader();
  const textureEarth = loaderEarth.load('https://82mou.github.io/threejs/img/real-earth.jpg');
```

形状と素材の定義したところで、これらを使って物体を作ります。

```index.js
  //物体(メッシュ)へ登録
  const materialEarth = new THREE.MeshLambertMaterial({map:textureEarth});
  const sphereEarth = new THREE.Mesh(geometryEarth, materialEarth);
```

### 光源
さぁ、次は光源です。こんな感じ。
光源の色、明るさを指定します。

```index.js
  //光源を作成
  const light = new THREE.DirectionalLight(0xffffff, 1.3);
  light.position.set(100, 130, 80);
```

### カメラ
最後にカメラ。

```index.js
  //カメラを作成
  const width = 640;
  const height = 330;
  const camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
  camera.position.set(100, 100, 100);
```

引数には、前から画角、アスペクト比、ニアークリップ、ファークリップとなってます。
ニアークリップ、ファークリップ。。。
ここら辺の知識が無く、よく分からない言葉が出てきたので調べてみます。
以下のような感じと理解。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2262606/b1d02909-0f81-8a4f-dea4-6505eed2aabf.png)
さぁ、ここまでで材料はそろったはず。
上記で作成したステージに物体、光源を追加、カメラをシーンの方向へ。

```index.js
  //シーンへ登録
  scene.add(sphereEarth);        //物体(メッシュ)を設定
  scene.add(light);              //光源を設定
  camera.lookAt(scene.position); //カメラを設定
```

次に表示させるためにはレンダラーが必要とのことなので定義していきます。
こんな感じ。

```index.js
  //レンダラーを作成
  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector('#myCanvas')
  });
  renderer.setSize(width, height);
  renderer.setClearColor(0xeeeeee);
```

querySelectorで指定している"myCanvas"は初めに作成したcanvas要素のidを指定しています。
その下では、サイズとカラーを指定しています。
後はこのレンダラーを用いて地球を表示していくのですが、少し動きがほしいので地球が回転するようにしたいと思います。
方法は毎フレームごとに地球を少し回転させる関数を作成し、それを無限ループさせていきます。

```index.js
  //初回実行
  tick();

  //毎フレーム時に実行されるループイベントです
   function tick() {
    sphereEarth.rotation.y += 0.01; //地球を少し回転させる
    renderer.render(scene, camera); //レンダリング
    controls.update();              //マウス操作更新
    requestAnimationFrame(tick);    //渡された関数を、毎フレーム実行
  }
```

それでは実行してみましょう！！





・・・

・・・



・・・





どんっ！！！
![earth_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2262606/2962711f-5cc4-5051-8e2e-228634cdd755.gif)






おー、うまく表示されました。
リアルな地球が回転しています。(感動！)

あとはこの地球を掴んでグルグルするだけ。
ここからがめんどくさそうなイメージ。
さてさてどうするのかな。。

フムフム。なるほど。。ってこれだけでいいのか？！

以下のたった3行追加するだけであっさりできてしまいました。
すごい便利！！！

Three.jsにはカメラの動きを自動的に制御するTHREE.OrbitControlsクラスというものが提供されているそう。
以下のように読み込めば使用できます。

```index.html
<script src="https://82mou.github.io/threejs/js/OrbitControls.js"></script>
```

その後、カメラをマウスコントロールするクラスを定義し、

```index.js
  //マウスで縮尺、掴むができるように
  const controls = new THREE.OrbitControls(camera);
```

マウス操作を更新するために「controls.update()」をコールするだけ。(下から2行目に追加)

```index.js
  //毎フレーム時に実行されるループイベントです
  function tick() {
    //sphereEarth.rotation.y += 0.01; //地球を少し回転させる
    renderer.render(scene, camera);   //レンダリング
    controls.update();                //マウス操作更新
    requestAnimationFrame(tick);      //渡された関数を、毎フレーム実行
  }
```

するとこんな感じ。できた！！
![earth_2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2262606/eef91c57-345f-7312-e572-96f68d4f26a2.gif)
※マウスの制御を分かりやすくするため、地球の自転は停止しています。

【まとめ】
============
今回は「地球をグルグルしたい」ということで、Three.jsを使用し地球の作成および、簡単なマウス制御を試してみました。
結果、地球を描画するための各部品、マウス制御など、うまく部品化されており難しいところはほぼブラックボックスで使用できることが分かりました。
他のアニメーションについてもこのようなライブラリは数多く存在しているため、また時間があればどこかで試してみたいです。

【おまけ】
============
初めに作成したものは背景が無く、地球が浮いている感が無かったので、背景を宇宙にしてみました。
うん、やっぱりこっちの方がかっこいいな。引き締まって見える。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2262606/adef249a-132c-cf2f-e019-162a3fdfc9cc.png)

※上記ソースコードを一つにまとめております。ご参考になれば幸いです。

```index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <script src="https://unpkg.com/three@0.131.3/build/three.min.js"></script>
    <script src="https://82mou.github.io/threejs/js/OrbitControls.js"></script>  
    <script src="js/index.js"></script>
  </head>
  <body>
    <canvas id="myCanvas"></canvas>
  </body>
</html>
```

```index.js
//ページの読み込みを待つ
window.addEventListener('load', makeEarth);

function makeEarth() {
  //シーンを作成(シーン：3D空間のことで、3Dオブジェクトや光源などの置き場)
  const scene = new THREE.Scene();
  //球体を用意
  const geometryEarth = new THREE.SphereGeometry(80, 20, 20);
  //地球の素材を用意
  const loaderEarth = new THREE.TextureLoader();
  const textureEarth = loaderEarth.load('https://82mou.github.io/threejs/img/real-earth.jpg');
  //物体(メッシュ)へ登録
  const materialEarth = new THREE.MeshLambertMaterial({map:textureEarth});
  const sphereEarth = new THREE.Mesh(geometryEarth, materialEarth);
  //光源を作成
  const light = new THREE.DirectionalLight(0xffffff, 1.3);
  light.position.set(100, 130, 80);
  //カメラを作成
  const width = 640;
  const height = 330;
  const camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
  camera.position.set(100, 100, 100);
  //シーンへ登録
  scene.add(sphereEarth);        //物体(メッシュ)を設定
  scene.add(light);              //光源を設定
  camera.lookAt(scene.position); //カメラを設定
  //レンダラーを作成
  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector('#myCanvas')
  });
  renderer.setSize(width, height);
  renderer.setClearColor(0xeeeeee);

  //マウスで縮尺、掴むができるように
  const controls = new THREE.OrbitControls(camera);

  //初回実行
  tick();

  //毎フレーム時に実行されるループイベントです
  function tick() {
    sphereEarth.rotation.y += 0.01; //地球を少し回転させる
    renderer.render(scene, camera); //レンダリング
    controls.update();              //マウス操作更新
    requestAnimationFrame(tick);    //渡された関数を、毎フレーム実行
  }
}
```

【参考文献】
============
[three.jsの基本をおさらいしてみよう！〜基礎の基礎編〜](https://liginc.co.jp/315939	
)
[簡単なThree.jsのサンプルを試そう](https://ics.media/tutorial-three/quickstart/	
)
[Three.jsのマテリアルの基本]
(https://ics.media/tutorial-three/material_basic/)