OBJにしたGISデータをダウンロードしてUnityに表示する

# はじめに

12/3に作ったOBJファイルをUnityに表示してみます。OBJファイルはUnity Editorが標準で読み込める形式なので、
そのままAssetとして読み込めば特に問題になりません。適当なマテリアルさえ設定してしまえばゲーム内のアセットとして使えます。

それでは面白くないので、ここではランタイムにダウンロードしてきてシーン上に構築するということをやってみます。
とはいえ、これも動的にメッシュの作成と、OBJのパースなどの合わせ技なので大したことないですね。
この辺りはゆるく流していきたい感じで。

# ダウンロードしてきて表示

## ダウンロード

UnityWebRequestでとってきます。サンプルコードそのまま感。

```
    IEnumerator objLoad()
    {
        UnityWebRequest req = UnityWebRequest.Get("URL to FILE/test.obj");
        yield return req.SendWebRequest();
```
余談ですが、githubのgistは、こういうWebアクセスのテスト用のファイル置き場として便利ですね。今回もそんな感じで試しています。

## OBJのパース

一ラインずつ読んでいきます。
頂点座標と頂点インデックスをそのまま読んで配列に格納します。

```
        List<Vector3> vertices = new List<Vector3>();
        List<int> indices = new List<int>();

        if(req.isHttpError || req.isNetworkError){
            Debug.Log(req.error);
        } else {
            var strs = req.downloadHandler.text.Split('\n');
            foreach(string str in strs){
                if(str.StartsWith("g")){

                } else if(str.StartsWith("v")){
                    var tokens = str.Split(' ');
                    vertices.Add(new Vector3(float.Parse(tokens[1]), float.Parse(tokens[2]), float.Parse(tokens[3])));
                } else if(str.StartsWith("f")){
                    var tokens = str.Split(' ');
                    indices.Add(int.Parse(tokens[1])-1);
                    indices.Add(int.Parse(tokens[2])-1);
                    indices.Add(int.Parse(tokens[3])-1);
                }
            }
        }
        Debug.Log(vertices.Count);
        Debug.Log(indices.Count);
```

念のため、最後に読み込んだ頂点数とインデックス数をデバッグログしました。
こんなのをパーサーと言っていいかというくらい簡単ですね。
ちなみに、インデックスの読み込みで-1しているのは、OBJファイルが1オリジンで、Unityが0オリジンだからです。
最初にここではまりました。
配列が1から始まるのがおかしいって話はあるかもしれませんが、世の中そういう言語もあったりします。
例えばLuaとかですね。
また、古のN88-BASICには、配列のオリジンを0,1で変更するOPTION BASEなんて命令がありましたね。VBにもあるみたいです。

## 頂点の設定

先ほど格納した頂点座標と頂点インデックスの配列を設定します。

```
        var meshFilter = gameObject.GetComponent<MeshFilter>();
        var mesh = new Mesh();

        mesh.SetVertices(vertices);
        mesh.SetIndices(indices.ToArray(),MeshTopology.Triangles,0);
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
        meshFilter.mesh = mesh;
    }
```

最後に、法線とバウンディングボックスの再計算もさせます。
これを同じGameObjectにアタッチされているMeshFilterコンポーネントに投入して終わりです。

## コルーチン呼び出し

テストなので、サクッとStartからコルーチン呼び出ししてしまって、表示させてしまいましょう。

```
    void Start()
    {
        StartCoroutine(objLoad());
    }
```

## 全体のスクリプト

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class OBJImport : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(objLoad());
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    IEnumerator objLoad()
    {
        UnityWebRequest req = UnityWebRequest.Get("https://gist.githubusercontent.com/oho-sugu/875b568ee5bcfdaabbd08d313f07fb0d/raw/682a8439c9ec3296d7e90e30b3dc9aa6e4e53202/test.obj");
        yield return req.SendWebRequest();

        List<Vector3> vertices = new List<Vector3>();
        List<int> indices = new List<int>();

        if(req.isHttpError || req.isNetworkError){
            Debug.Log(req.error);
        } else {
            var strs = req.downloadHandler.text.Split('\n');
            foreach(string str in strs){
                if(str.StartsWith("g")){

                } else if(str.StartsWith("v")){
                    var tokens = str.Split(' ');
                    vertices.Add(new Vector3(float.Parse(tokens[1]), float.Parse(tokens[2]), float.Parse(tokens[3])));
                } else if(str.StartsWith("f")){
                    var tokens = str.Split(' ');
                    indices.Add(int.Parse(tokens[1])-1);
                    indices.Add(int.Parse(tokens[2])-1);
                    indices.Add(int.Parse(tokens[3])-1);
                }
            }
        }
        Debug.Log(vertices.Count);
        Debug.Log(indices.Count);

        var meshFilter = gameObject.GetComponent<MeshFilter>();
        var mesh = new Mesh();

        mesh.SetVertices(vertices);
        mesh.SetIndices(indices.ToArray(),MeshTopology.Triangles,0);
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
        meshFilter.mesh = mesh;
    }
}
```

これを適当にヒエラルキーに作ったCubeなりにアタッチして実行しましょう。

# 結果

![20191207_fig_objload.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30257/f1f83ad6-13ca-1837-218e-828dbd09035e.png)

真ん中あたりの高いビル群が六本木ヒルズですね。
これで、ヒルズGOとか作れそうですね :thinking: 

# 課題

ファイルサイズも大きくなるので、バイナリフォーマットを考えた方がいいと思います。