three.js with React

VRMファイルの中身を表示するWebサイトそ作る過程で[React](https://ja.reactjs.org/) 内で [Three.js](https://threejs.org/) を扱ったので、使用した場合の知見などについて共有します。
<font color="#FF0000">※ ここでは [react-three-renderer](https://github.com/toxicFork/react-three-renderer) などのライブラリを使わず、素の [React](https://ja.reactjs.org/) と [Three.js](https://threejs.org/) を使う方法を紹介します。</font>

また、今回、「VRMファイルの中身を表示するWebサイト」のソースコードはこちらに公開しています。

[NodeVrmMetaRipper](https://github.com/TakuKobayashi/NodeVrmMetaRipper)

作成したデモページはこちらです。

[デモページ](https://takukobayashi.github.io/NodeVrmMetaRipper/)

# 作ったものについて

人型の3DModelファイルである[VRM](https://vrm.dev/)ファイルの中にあるMeta情報(Boneとかアニメーションとか)を抽出して表示するWebAPIを作成してみました。
VRMファイルの中身はJSONになっているのでその情報を抽出することができます。

### 参考
 * [VRMのバイナリを読もう](https://speakerdeck.com/m2wasabi/vrmmian-qiang-hui-03-vrmfalsebainariwodu-mou-vrm-lan-biao-shi-wobao-su-nisuru)

## 使用した技術

### WebAPI
 * [Serverless Framework](https://github.com/serverless/serverless)
 * [AWS Lambda](https://aws.amazon.com/jp/lambda/)
 * [Node.js (v10.x)](https://nodejs.org/ja/)
 * [Typescript](https://www.typescriptlang.org/)
 * [Redoc (Swagger)](https://github.com/Redocly/redoc)
 * [typedocs](https://github.com/alvarorahul/TypeDocs)

### Demo Page
 * [React](https://github.com/facebook/react)
 * [Material UI](https://material-ui.com/)
 * [Typescript](https://www.typescriptlang.org/)
 * [three.js](https://threejs.org/)
 * [Github Pages](https://pages.github.com/)
 * [Pixiv/three-vrm](https://github.com/pixiv/three-vrm)

# React

[React](https://ja.reactjs.org/)tはUIのパーツ（構成部品）を作るためのライブラリです。
FacebookがOSSとして公開しています。
[Typescript](https://www.typescriptlang.org/)で開発することもできます。(今回紹介しているものは[Typescript](https://www.typescriptlang.org/)にて記述しています。)
[React](https://ja.reactjs.org/)のセットアップや導入については [React公式](https://ja.reactjs.org/) ページにまとまっていますので今回は省略します。

## Reactの中でthree.jsを使う

### three.jsのインストール

まずはthree.jsをインストールします。

```
yarn add three
```

最新の [three.js](https://threejs.org/) にはTypescriptで開発するために必要なものが入っているので `@types/three` のインストールをする必要はありません。

### three.jsで何か表示してみる

[React](https://ja.reactjs.org/)にて [three.js](https://threejs.org/) を使って最小限のソースコードで何かしら表示させる方法は以下のようになります

```typescript
import React from 'react';
import { WebGLRenderer, Scene, PerspectiveCamera } from 'three';

class App extends React.Component {
  onCanvasLoaded = (canvas: HTMLCanvasElement) => {
    if (!canvas) {
      return;
    }
    const renderer = new WebGLRenderer({ canvas: canvas, antialias: true });
    const scene = new Scene();
    // new THREE.PerspectiveCamera(視野角, アスペクト比, near, far)
    const camera = new PerspectiveCamera(45, canvas.clientWidth / canvas.clientHeight, 1, 2000);
    renderer.render(scene, camera);
  };

  render():
    | React.ReactElement<any, string | React.JSXElementConstructor<any>>
    | string
    | number
    | {}
    | React.ReactNodeArray
    | React.ReactPortal
    | boolean
    | null
    | undefined {
    return (
      <div>
        <canvas ref={this.onCanvasLoaded} />
      </div>
    );
  }
}

export default App;
```

[React](https://github.com/facebook/react) のJSX内では`canvas`を指定することができますので、`React.Component` を継承したClass内で描画処理を行っている `render` メソッドに `canvas` タグを指定します。このとき `canvas` タグの中の `ref` プロパティは`canvas` が描画された直後に呼ばれるものです。この `ref` プロパティの中で取得できる `canvas` (Typescriptだと `canvas: HTMLCanvasElement`)を [three.js](https://threejs.org/) の`WebGLRenderer` に指定してあげれば、指定した `canvas` に描画されます。
あとは [three.js](https://threejs.org/) の描画に必要な `Scene` と `カメラ` (今回は場合は`PerspectiveCamera`) を指定することで表示されます。

### three.jsで色々と動かせる状態にする

上記の表示した内容は静止画になります。
[three.js](https://threejs.org/) で動画のようにいろいろ動かしてみたくなると思います。
その場合の実装方法は以下のようになります。

```typescript
import React from 'react';
import { WebGLRenderer, Scene, PerspectiveCamera } from 'three';

class App extends React.Component {
  private scene: Scene | null = null;
  private camera: PerspectiveCamera | null = null;
  private renderer: WebGLRenderer | null = null;

  constructor(props: any) {
    super(props);
    this.animate = this.animate.bind(this);
  }

  onCanvasLoaded = (canvas: HTMLCanvasElement) => {
    if (!canvas) {
      return;
    }
    this.renderer = new WebGLRenderer({ canvas: canvas, antialias: true });
    this.scene = new Scene();
    // new THREE.PerspectiveCamera(視野角, アスペクト比, near, far)
    this.camera = new PerspectiveCamera(45, canvas.clientWidth / canvas.clientHeight, 1, 2000);
    this.animate()
  };

  animate() {
    window.requestAnimationFrame(this.animate);
    if (this.renderer && this.scene && this.camera) {
      this.renderer.render(this.scene, this.camera);
    }
  }

  render():
    | React.ReactElement<any, string | React.JSXElementConstructor<any>>
    | string
    | number
    | {}
    | React.ReactNodeArray
    | React.ReactPortal
    | boolean
    | null
    | undefined {
    return (
      <div>
        <canvas ref={this.onCanvasLoaded} />
      </div>
    );
  }
}

export default App;
```

`window.requestAnimationFrame(this.animate)` ここで自身の `function` を指定することで、指定した `function` が繰り返し実行されるようになります。
これにより [three.js](https://threejs.org/) 内でアニメーションするようになります。
しかし、[React](https://github.com/facebook/react) にて描画に影響を与える場合は事前に `bind` しないとエラーが発生します。そのため、`constructor` にて `this.animate = this.animate.bind(this);` と指定することで、連続しての描画を実現しています。

あとは色々と表示させるために`Scene` に追加したり、`DirectionalLight` で光を当てたり、`position` や `scale` を変更させたり、各種 [three.js](https://threejs.org/) の処理を作成していくことで、[React](https://github.com/facebook/react) と [three.js](https://threejs.org/) が共存した物を作ることができます。

# まとめ

[React](https://github.com/facebook/react) 内で [three.js](https://threejs.org/) を使う場合についての紹介と実際に作成したものについて紹介しました。
[React](https://github.com/facebook/react) への [three.js](https://threejs.org/) の導入は割とすんなり導入できるのではないかと思います。
[React](https://github.com/facebook/react) は [GatsbyJS](https://www.gatsbyjs.org/) など人気のプロジェクト(ツール)に使われていることも多くあるので、これらを使いつつ、[three.js](https://threejs.org/) を導入する場合の参考となればと思います。