DracoとAFrameを組み合わせて使ってみた

# Dracoのインストール

## Emscriptenのインストール

私はMac環境なのでhomebrewでemscriptenを`brew install emscripten`でインストールしました。
インストール後は一度`emcc`を動かし`brew info emscripten`の指示に従って`~/.emscripten`を編集しましょう

## Dracoのビルド

Dracoのビルドは公式の[README.md](https://github.com/google/draco/blob/master/README.md)に従うとうまくいきます。ファイルがゴリッと生成されるので作業用フォルダを作ってその中でやるのを忘れないようにしましょう。
`export EMSCRIPTEN=`にどの値を設定するかは`~/.emscripten`の`EMSCRIPTEN_ROOT`の値を参考にしましょう。

## AFrameのビルド

AFrameにはthree.jsが埋め込まれているのですが、現状の最新のリリース版(`0.4.0`)ではthree.jsが82系をつかっており、一方Dracoの方は83系をつかっているので想定しているAPIのバージョンが合わないので上手く使えません。しかし、Aframeのmasterでは(2017/01/18 01:58 確認)83系をつかっているのでこちらをビルドして使いましょう。ビルドについては[README.md](https://github.com/aframevr/aframe#local-development)に従えば問題ありません。`aframe-master.min.js`ができるのでこれを利用します。

## DRACOLoader.jsの取得

DracoのサンプルにDRACOファイルをthree.jsのBufferGeometryに変換するためのDRACOLoader.jsが用意されています
https://github.com/google/draco/blob/master/javascript/example/DRACOLoader.js

今回はコレからサンプルに強く紐付いている箇所を削除し利用しています

https://github.com/pocket7878/draco-aframe-example/blob/master/DRACOLoader.js

# サンプルデータのダウンロード

サンプルのdraco圧縮されたファイルは[example/modelsフォルダ内](https://github.com/google/draco/tree/master/javascript/example/models)に入っているウサギのモデルを今回は利用しました。
もとのオブジェクトデータはhttps://graphics.stanford.edu/data/3Dscanrep/にあるようです。


# コード

## Dracoファイルをロードするdraco-modelコンポーネントを登録

汎用的に利用できるようにするためにdracoのモデルをURLからロードするdrc-modelというコンポーネントを
AFrameに登録します。

```js:draco_model_component.js
const DracoModule = Module;

AFRAME.registerComponent('drc-model', {
    schema: {
        drcUrl: {type: 'string'}
    },
    init: function() {
        this.model = null;
        this.drcLoader = new THREE.DRACOLoader();
        this.drcLoader.crossOrigin = '';
    },
    
    update: function() {
        var data = this.data;
        if (!data.drcUrl) { return ; }
        this.remove();
        this.loadObj(data.drcUrl);
    },
    
    remove: function() {
        if (!this.model) { return; }
        this.el.removeObject3D('mesh');
    },
    
    loadObj: function(drcUrl) {
        var self = this;
        var el = this.el;
        var drcLoader = this.drcLoader;
        drcLoader.load(drcUrl, function(bufferGeometry) {
            var material = new THREE.MeshStandardMaterial({vertexColors: THREE.VertexColors});

            let geometry;
            // Point cloud does not have face indices.
            if (bufferGeometry.index == null) {
              geometry = new THREE.Points(bufferGeometry, material);
            } else {
              bufferGeometry.computeVertexNormals();
              geometry = new THREE.Mesh(bufferGeometry, material);
            }
            
            // Compute range of the geometry coordinates for proper rendering.
            bufferGeometry.computeBoundingBox();
            const sizeX = bufferGeometry.boundingBox.max.x - bufferGeometry.boundingBox.min.x;
            const sizeY = bufferGeometry.boundingBox.max.y - bufferGeometry.boundingBox.min.y;
            const sizeZ = bufferGeometry.boundingBox.max.z - bufferGeometry.boundingBox.min.z;
            const diagonalSize = Math.sqrt(sizeX * sizeX + sizeY * sizeY + sizeZ * sizeZ);
            const scale = 1.0 / diagonalSize;
            const midX = (bufferGeometry.boundingBox.min.x + bufferGeometry.boundingBox.max.x) / 2;
            const midY = (bufferGeometry.boundingBox.min.y + bufferGeometry.boundingBox.max.y) / 2;
            const midZ = (bufferGeometry.boundingBox.min.z + bufferGeometry.boundingBox.max.z) / 2;

            geometry.scale.multiplyScalar(scale);
            geometry.position.x = -midX * scale;
            geometry.position.y = -midY * scale;
            geometry.position.z = -midZ * scale;
            geometry.castShadow = true;
            geometry.receiveShadow = true;
            self.model = geometry;
            
            el.setObject3D('mesh', geometry);
            el.emit('model-loaded', {format: 'drc', model: geometry});
        });
    }
});
```

### アセット経由でロードできない
現状ではa-asset-itemでロードしようとして

```html:asset-item-example
<a-asset-item id="banny-drc" src="banny.obj></a-asset-item>
```

というふうにすると、上手くロードできません。
Three.jsのキャッシュやらa-asset-itemのロードのresponseTypeやらの兼ね合いで上手くロードできませんので、
現状はURL経由でdrc-model内部で`THREE.DRACOLoader()`でロードしています。

## a-scene上で利用する

上記のようなコンポーネントを登録しておくとa-sceneの中で簡単にロードできます

```html:asset-scene
<a-scene>
      <a-sky color="#ECFFFF"></a-sky>
      <a-entity drc-model="drcUrl: ./bunny.drc" position="0 1.25 -2"></a-entity>
</a-scene>
```

のようにしてシーンにウサギのモデルを載せます。

## 結果

<img width="713" alt="スクリーンショット 2017-01-18 03.05.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/14359/826f64c8-442c-3eb7-a4c4-67acbef5b0ed.png">

これでこのようにAFrame上にDracoで圧縮されていたモデルがデコードされて表示され無事VRで見ることができました。

# コード

今回のコードは[pocket7878/draco-aframe-example](https://github.com/pocket7878/draco-aframe-example)にアップロードしておきました.