three.jsをスマホアプリで動かして暖をとる(react-native-webglの導入手順)

## 追記
[react-native-webgl](https://github.com/react-native-community/react-native-webgl) は deprecated となりました...

RNの新しいバージョンではreact-native-webglは動かないと思うので、お勧めされている[expo-gl](https://github.com/expo/expo/tree/master/packages/expo-gl#expo-gl)を使いましょう！

## はじめに
この記事では、[react-native-webgl](https://github.com/react-native-community/react-native-webgl)というライブラリの導入手順について書きます。

react-native-webglは、[React Native](https://facebook.github.io/react-native/)で[WebGL](https://www.khronos.org/webgl/)を使うためのライブラリです。
これを使えばネイティブアプリでthree.jsが使えるようになるので、試しに作ってみました！

**とりあえずスマホカイロ**にしてみましたが、誰かかっこいいアプリ作ってくれないかな・・・

iOS: https://itunes.apple.com/us/app/three-js-native/id1447928256
Android: https://play.google.com/store/apps/details?id=com.three

![Simulator Screen Shot - iPhone 8 Plus - 2019-02-06 at 20.52.51.png](https://qiita-image-store.s3.amazonaws.com/0/136986/2c5b983c-aabb-c084-8d9c-71493fa885ab.png) ![Simulator Screen Shot - iPhone 8 Plus - 2019-02-06 at 21.02.12.png](https://qiita-image-store.s3.amazonaws.com/0/136986/bd72f5ba-630c-6d20-42c0-1fcbb91a65d2.png)


* アプリ内の4種類のシーンのうち、Octahedronをたくさん表示するものが一番あったまれます。
* segments（ポリゴンの分割数）などのパラメータは、最大にするとメモリが足らずアプリが落ちることがありますので、徐々に上げてみてください。

## React Nativeの準備
[公式のgetting-started](https://facebook.github.io/react-native/docs/getting-started)通りに準備しましょう。
今回は[expo](https://expo.io/)を使わないので、”Quick Start”ではなく、”Building Projects with Native Code”のタブに従ってください。

## react-native-webglのインストール
React Nativeの最新はこの記事の執筆時点で0.58ですが、このバージョンではビルドが通らないため、0.57にします。

```Console
# init
react-native init threejsNative --version 0.57
cd threejsNative
```

その後、react-native-webglのパッケージをインストールしてリンクします。

```Console
yarn add react-native-webgl
react-native link react-native-webgl
```

### iOS
New Build SystemではGPUImageというプロジェクト周りでエラーが出てしまうため、Legacy Build Systemに変更します。

File -> Workspace Settings... -> Build System -> Legacy Build System

<img width="368" alt="Screen Shot 2019-02-08 at 1.35.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/136986/21ff9d5e-f374-d980-c394-599d8ee16e06.png">
<img width="535" alt="Screen Shot 2019-02-08 at 1.36.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/136986/d04842e5-f5de-1b0c-8cc3-4493df1ed71f.png">


### Android
issueを読みつつ諸々を書き換えます。

```diff:android/build.gradle
  buildscript {

    ext {

-     minSdkVersion = 16
+     minSdkVersion = 17

    }

    dependencies {

+     classpath 'de.undercouch:gradle-download-task:3.1.2'

    }
```

```diff:node_modules/react-native-webgl/android/src/main/jni/Application.mk
- APP_PLATFORM := android-9
+ APP_PLATFORM := android-16

- APP_STL := gnustl_shared
+ APP_STL := c++_shared

- NDK_TOOLCHAIN_VERSION := 4.9
```

```diff:node_modules/react-native-webgl/android/build.gradle
  task packageRNWebGLLibs(dependsOn: buildRNWebGLLib, type: Copy) {

-   exclude '**/gnustl_shared.so'
+   exclude '**/libc++_shared.so'

  }

  android {

-   buildToolsVersion '25.0.0'
+   buildToolsVersion '27.0.3'

  }

  dependencies {
-   compile "com.facebook.react:react-native:+"  // From node_modules
+   implementation "com.facebook.react:react-native:+"  // From node_modules
  }
```

```exclude '**/libc++_shared.so'```でビルドが通らない時は```include '**/libc++_shared.so'```でうまくいく場合があります。

## three.jsを読み込む


あとは[公式のexample](https://github.com/react-native-community/react-native-webgl/tree/master/example)の通りです。

```Console
yarn add three
```

```three.js
const THREE = require("three");
global.THREE = THREE;
if (!window.addEventListener)
    window.addEventListener = () => { };
require("three/examples/js/renderers/Projector");
export default THREE;
```

```react:App.js
import React from "react";
import { View } from "react-native";
import { WebGLView } from "react-native-webgl";
import THREE from "./three";

export default class App extends React.Component {
  requestId: *;
  componentWillUnmount() {
    cancelAnimationFrame(this.requestId);
  }
  onContextCreate = (gl: WebGLRenderingContext) => {
    const rngl = gl.getExtension("RN");

    const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
    const renderer = new THREE.WebGLRenderer({
      canvas: {
        width,
        height,
        style: {},
        addEventListener: () => {},
        removeEventListener: () => {},
        clientHeight: height
      },
      context: gl
    });
    renderer.setSize(width, height);
    renderer.setClearColor(0x000000, 1);

    let camera, scene;
    let cube;

    function init() {
      // ここにcameraやsceneのコードを書く
      camera = new THREE.PerspectiveCamera(75, width / height, 1, 1100);
      camera.position.y = 150;
      camera.position.z = 500;
      scene = new THREE.Scene();

      let geometry = new THREE.BoxGeometry(200, 200, 200);
      for (let i = 0; i < geometry.faces.length; i += 2) {
        let hex = Math.random() * 0xffffff;
        geometry.faces[i].color.setHex(hex);
        geometry.faces[i + 1].color.setHex(hex);
      }

      let material = new THREE.MeshBasicMaterial({
        vertexColors: THREE.FaceColors,
        overdraw: 0.5
      });

      cube = new THREE.Mesh(geometry, material);
      cube.position.y = 150;
      scene.add(cube);
    }
    const animate = () => {
      this.requestId = requestAnimationFrame(animate);
      renderer.render(scene, camera);

      // ここにアニメーションのコードを書く
      cube.rotation.y += 0.05;

      gl.flush();
      rngl.endFrame();
    };

    init();
    animate();
  };
  render() {
    return (
      <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
        <WebGLView
          style={{ width: 300, height: 300 }}
          onContextCreate={this.onContextCreate}
        />
      </View>
    );
  }
}
```

three.jsの部分は通常とほとんど同じように書けます！

## 実行
iOS/Androidそれぞれでオブジェクトが表示されれば成功です。

```Console
react-native run-ios
react-native run-android
```

## まとめ
react-native-webglを使って、スマホアプリでthree.jsを動かす方法について書きました。
three.jsを使ったかっこいいアプリがさらに増えていくと嬉しいですね！