three.js+Electronで暴れまわるティラノサウルスを表示する

## はじめに

Office365のWordに、全人類が待望していた新機能が実装されました。

> 文書を活気づかせる: アニメーション 3D グラフィックスを挿入して、鼓動する心臓、周回する惑星、暴れ回るティラノサウルスをページ上に表示できます。

https://docs.microsoft.com/ja-jp/officeupdates/monthly-channel-2018#version-1810-november-13

> 暴れ回るティラノサウルスをページ上に表示できます。

「あぁ、この文書に暴れまわるティラノサウルスを表示できればいいのになぁ」と思ったこと、誰しも一度はありますよね。~~誰が使うんだこの機能~~

ところで僕はOffice365を利用していないので、残念ながら文書ファイル上でティラノサウルスを暴れまわらせることができません。

そこで、普段利用しているgoogleドキュメント… だけなどとみみっちいことは言わず、
**ウィンドウ全体でティラノサウルスを暴れさせてみようと思います。**

具体的には、ティラノサウルスのMMDモデルをelectron上で表示してみます。

## ティラノサウルスを用意する

[こちら](http://mmdwars.web.fc2.com/model.html)からお借りしました。

## electron環境を作成する

以下の記事を参考に、ひとまず空のelectron環境を作成します。
https://qiita.com/Quramy/items/a4be32769366cfe55778

```javascript:main.js
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

let mainWindow = null;

app.on('window-all-closed', function() {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('ready', function() {

  // ブラウザ(Chromium)の起動, 初期画面のロード
  mainWindow = new BrowserWindow({width: 800, height: 600});
  mainWindow.loadURL('file://' + __dirname + '/index.html');

  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});
```

```html:index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron Read Us</title>
</head>
<body>
<h1>Hello, electron!</h1>
</body>
</html>
```

## ティラノサウルスを召喚する
three.jsのサイトに[example](https://threejs.org/examples/#webgl_loader_mmd)があるのでこれを参考に、
MMDモデルをロード・表示してみます。ついでにサンプルのモーションを適用して、ティラノサウルスを~~踊らせ~~暴れさせてみましょう。

```html:index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron Read Us</title>
<script src="./js/three.min.js"></script>
<script src="js/libs/mmdparser.min.js"></script>
<script src="js/libs/ammo.js"></script>
<script src="js/loaders/TGALoader.js"></script>
<script src="js/loaders/MMDLoader.js"></script>
<script src="js/effects/OutlineEffect.js"></script>
<script src="js/animation/CCDIKSolver.js"></script>
<script src="js/animation/MMDPhysics.js"></script>
<script src="js/animation/MMDAnimationHelper.js"></script>
<script src="./js/index.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
```

```javascript:index.js
window.addEventListener('load', init);

function init() {
  const width = 800;
  const height = 400;
  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector('#canvas')
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);

  const scene = new THREE.Scene();

  const camera = new THREE.PerspectiveCamera(45, width / height);
  camera.position.set(0, 0, +100);

  var directionalLight = new THREE.DirectionalLight(0xffffff);
  directionalLight.position.set(0, 0.7, 0.7);
  scene.add(directionalLight);

  var modelFile = './models/rex.pmx';
  var vmdFiles = [ './models/wavefile_v2.vmd' ];

  var clock = new THREE.Clock();
  var helper = new THREE.MMDAnimationHelper();
  var physicsHelper, ikHelper, mesh;

  var loader = new THREE.MMDLoader();

  loader.loadWithAnimation( modelFile,vmdFiles, function (obj){
    mesh = obj.mesh;
    console.log("Loaded!!");
    mesh.position.y = -30;
    scene.add(mesh);

    helper.add(mesh,{
      animation: obj.animation,
      physics: true
    });

    ikHelper = helper.objects.get( mesh ).ikSolver.createHelper();
    ikHelper.visible = false;
    scene.add( ikHelper );

    physicsHelper = helper.objects.get( mesh ).physics.createHelper();
    physicsHelper.visible = false;
    scene.add( physicsHelper );

  }, onProgress, onError);

  var onProgress = function(e){
    if (e.lengthComputable){
      var percentComplete = e.loaded / e.total * 100;
      console.log(Math.round(percentComplete, 2) + "% downloaded");
    }
  };

  var onError = function(e){
    console.log("onError:" + e);
  };

  tick();

  function tick() {
    requestAnimationFrame(tick);
    helper.update( clock.getDelta() );
    renderer.render(scene, camera);
  }
}
```

現状のディレクトリ構成は以下のような感じです。

```
.
│  index.html
│  main.js
│  package.json
│  
├─js
│  │  index.js
│  │  three.min.js
│  │  
│  ├─animation
│  │      CCDIKSolver.js
│  │      MMDAnimationHelper.js
│  │      MMDPhysics.js
│  │      
│  ├─effects
│  │      OutlineEffect.js
│  │      
│  ├─libs
│  │      ammo.js
│  │      mmdparser.min.js
│  │      stats.min.js
│  │      
│  ├─loaders
│  │      MMDLoader.js
│  │      MTLLoader.js
│  │      OBJLoader.js
│  │      TGALoader.js
│  │      
│  └─renderers
│          Projector.js
│          
└─models
        rex.pmx
        tyranno1.jpg
        tyranno2.jpg
        wavefile_v2.vmd

```

``` electron .``` で実行してみます。

![rex.gif](https://qiita-image-store.s3.amazonaws.com/0/287153/b83f7ec8-3dab-11b3-6afb-91908245a4b0.gif)


圧がすごい。

サンプルは女の子が踊るモーションなので、
恐竜に無理やり適用したらめちゃくちゃに破綻するのかと思いましたが、意外とそうでもないですね。キモイけど。

画像はgifなのでわかりにくいですが、実機だとぬるぬる動いて余計にキモイです。

## ウィンドウを透明にする
UIを非表示、ウィンドウを透過状態にし、暴れるティラノサウルスだけが表示されるようにします。
また、常にウィンドウが最前面に来るようにしておきます。

```javascript:main.js
  mainWindow = new BrowserWindow({width: 800, height: 600
    ,transparent: true
    ,frame: false
    ,toolbar: false
    ,alwaysOnTop: true
  });
```

このままだとthree.jsの描画領域は黒いままなので、こちらも設定を変更します。

```javascript:index.js
const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector('#canvas'),
    alpha: true
  });
```

画面上でティラノサウルスが暴れてくれました。
これでOffice365がなくても、いつでもティラノサウルスを暴れさせることができます。

![rexdoc.png](https://qiita-image-store.s3.amazonaws.com/0/287153/74168139-5fdd-c08b-397b-c36ec8833c7e.png)

## まとめ
突貫作業でしたが、比較的簡単にモデルを表示することができました。
3D版伺かとか作れそうです。

## 感想

3DCGはシーンやカメラの概念など、独特の感覚に慣れないと難しいですね。
これを機に、three.jsやunityを触って理解を深めようと思います。