Google Tangoで実空間をメッシュ化して3Dモデルを配置する

#実空間をメッシュ化したあとに、3Dモデルを飛ばしている動画

[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/1yQ1oo8FPYE/0.jpg)](https://youtu.be/1yQ1oo8FPYE)

[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/Je1LB9USOJQ/0.jpg)](https://youtu.be/Je1LB9USOJQ)

[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/B6LignZkerg/0.jpg)](https://youtu.be/B6LignZkerg)

#開発環境
[Tangoのスマートフォン Lenovo PB2-690M](https://www.lenovo.com/jp/ja/vr-smartdevices/Lenovo-PB2-690M/p/ZZITZTPPB4M)
[tango example unity](https://github.com/googlearchive/tango-examples-unity)
[Unity version 5.6.1f1](https://unity3d.com/jp/get-unity/download/archive) (tango example unityがUnity version5xではないと動かない)



#とりあえずサンプルを動かす
<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/e5301657-51c3-c320-aa88-02ff78769afb.png" width=20%>
Experimental Mesh Occlusionのサンプルが動くと、マーカーを実空間に貼り付けることができる。

- Unityで、Open Project -> tango-examples-unity-master/UnityExamplesを開く
- Assets/TangoSDK/Examples/Scenes/ExperimentalMeshOcclusionを開く
- UnityのFile -> Build Settings で Androidを選択してBuild And Run
    - Scenes In Buildには、ExperimentalMeshOcclusionのみをチェックする
    - 適当な箇所にapkファイルを保存する
- Android側でカメラデバイスへのアクセスを許可
    - 「Create new mesh and new Area Description」を押す
    - スマフォを動かしてMeshを作成後に「Finalize」を押す
    - リストから作成した「Area Description」をチェックして「Start Game using Area Description with mesh」を押して実行する


# サンプル内のGameObjectの説明
<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/6693223b-e1ac-9235-a599-d2ceac4f635e.png" width=30%>

- Tango Camera: カメラオブジェクト
- Canvas: 次の3種類のUIを表示するキャンバス
    - MeshBuildPanel: 実環境を認識してメッシュを作成するときのUI
    - MeshInteractionPanel: 生成したメッシュを実空間に重ねて、その中で3Dオブジェクトを配置するときのUI
    - AreaDescritioLoader: 実環境を学習した個々のファイルをリスト表示するUI
- DynamicMesh: これが生成されるMeshかな？
- Directional Light: ライト
- Tango Manager: Tango ApplicationコンポーネントのInspectorでTangoの設定ができる
- EventSystem: イベントシステム
- UI Controller: このプロジェクトのメイン的オブジェクトで様々な設定をInspectorでしたり、処理のメインとなるスクリプト(MeshOcclusionUIController.cs)がコンポーネントとしてアタッチされている
    - ARMarker: サンプルの実空間に配置するオブジェクト

#Delete All Meshesの修正
サンプルのままで学習したMeshのすべて削除するDelete All Meshesボタンが動作しなかったので以下のように修正した。
Assets/TangoSDK/Examples/ExperimentalMeshOcclusion/Scripts/MeshOcclusionUIController.csをダブルクリック、または、GameObjectの「UI Controller」の「Mesh Occlusion UI Controller (Script)」コンポーネントを右クリックして「edit」でも開くことができる。

```Assets/TangoSDK/Examples/ExperimentalMeshOcclusion/Scripts/MeshOcclusionUIController.cs
    public void Button_DeleteAllAreaDescriptionMeshes()
    {
        AreaDescription[] areaDescriptionList = AreaDescription.GetList();
        if (areaDescriptionList == null)
        {
            return;
        }
        foreach (AreaDescription areaDescription in areaDescriptionList)
        {
            areaDescription.Delete();
            //AndroidHelper.ShowAndroidToastMessage(areaDescription.m_uuid);
        }

        AndroidHelper.ShowAndroidToastMessage("All Area Description meshes have been deleted.");

#pragma warning disable 618
        Application.LoadLevel(Application.loadedLevel);
#pragma warning restore 618
    }
```

個々の学習されたファイルの削除は、使っていないので動くかどうかわかりません^^;

# 2D画像と3Dモデル
[![hiyo.png](https://qiita-image-store.s3.amazonaws.com/0/275020/e5bd6166-016e-6e99-7d5d-668b9d8dabda.png)](http://www.nuis.ac.jp/~nakada/researches/tango/hiyo.png)
2D画像ファイル

[![3dmodel_overview.png](https://qiita-image-store.s3.amazonaws.com/0/275020/5e84945c-78b5-62ea-a558-12a25469ddc3.png)](http://www.nuis.ac.jp/~nakada/researches/tango/hiyoko.fbx)
3Dモデルファイル：実空間上で約1mの大きさになるように作成しています。Unityにインポート後に、Scaleを変更して適当な大きさにしてください。


# 2D画像、3DモデルのUnityへの読み込み
<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/9f75fad4-f9df-5f99-54df-dabb7f1b4e15.png" width=40%>
Assets/MyModelsフォルダを作成して、以下のように、2D画像、3Dモデルをdrag and dropする。また、Physical Materialを作成する。

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/d33e1232-4368-76ea-36cd-5a704b24da01.png" width=40%>

- 2D画像のInspector例：2D画像ファイル: pngファイルをファイルエクスプローラからDrag and Dropして、UnityのInspectorでTexture TypeをSprite(2D and UI)に変えて右下のApplyを押す

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/abd93b46-6fcd-30e3-fca5-14793bb2b271.png" width=40%>

- 3DモデルのInspector例：3Dモデルファイル: fbxファイルをファイルエクスプローラからDrag and Dropする

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/0290d826-14e2-7b50-add0-17511d0acb23.png" width=40%>

- Physical MaterialのInspector例：MyModels下にPhysical Materialを作成し、InspectorでBouncinessを0.6、Bounce CombineをMaximumにする


# 2D、3Dのシーンへの配置
<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/028690bf-3e9a-ca78-b08a-7f28691cdcd8.png" width=20%>

- Canvas下にUI/Imageコンポーネントを追加（この例ではhiyoko2dという名前）


<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/4b06d7c7-40b0-6ee2-bff2-4877444e2d40.png" width=40%>

- Rect TransformのPos X, Y, Zはすべて0
- Anchorsもすべて0
- Pivotをx=0.5, y=0として実行時に指の上に画像が表示されるようにする
- ImageコンポーネントのSource ImageをMyModelsに保存した2D画像をセット

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/f5403b8f-4ff1-01cf-99da-2e62d395cff8.png" width=16%>

- MyModels/hiyokoモデルを「UI Controller」下にDrag and Dropして3Dオブジェクトを作成する

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/1b9b6ec8-2645-a86a-7274-c0ffadf756ed.png" width=40%>

- Transformの座標はとりあえず全て0
- Scaleは適当に設定する、おおよそこの数値は実空間のメートルに相当する
- Rigidbodyコンポーネントを追加する
- Box Colliderコンポーネントを追加する（SphereやCapsule Colliderは、実行してこのオブジェクトを空間内で飛ばすと実空間のメッシュ上で不自然に転がるのでBoxにした）

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/403794e3-128e-1722-e6e8-1298a714fb08.png" width=40%>

- 「UI Controller」の「Mesh Occlusion UI Controller」コンポーネント
    - Inspectorの「Marker Object」にSceneからhiyokoを選択する。

ここまでで実行すると、画面左下に2D画像が表示される。


# スマフォの画面をタップしたときに、2D画像を指の位置に表示する

画面をタップしたときに呼ばれるメソッドを修正する。

```Assets/TangoSDK/Examples/ExperimentalMeshOcclusion/Scripts/MeshOcclusionUIController.cs
    public void Image_PlaceMarker(BaseEventData data)
    {
        /* この部分をコメントアウト
        PointerEventData pdata = (PointerEventData)data;

        // Place marker object at target point hit by raycast.
        RaycastHit hit;
        if (Physics.Raycast(Camera.main.ScreenPointToRay(pdata.position), out hit, Mathf.Infinity))
        {
            m_markerObject.SetActive(true);
            m_markerObject.transform.position = hit.point;
            m_markerObject.transform.up = hit.normal;
        }
        */
    }
```

2D画像を保存する変数を作成する。動的に初期化するので、privateで作成した。3Dオブジェクトを保存する変数は、サンプルでマーカーとして使用されているm_markerObject変数を利用するので新規では作らない。

```Assets/TangoSDK/Examples/ExperimentalMeshOcclusion/Scripts/MeshOcclusionUIController.cs
    private GameObject hiyoko2d;
```

Drag中に呼び出されるメソッドをMeshOcclusionUIController.cs内に作成する。

```Assets/TangoSDK/Examples/ExperimentalMeshOcclusion/Scripts/MeshOcclusionUIController.cs
    public void Image_Drag(BaseEventData data)
    {
        PointerEventData pdata = (PointerEventData)data;

        // 変数が初期化されていない場合は、初期化する。
        if (hiyoko2d == null)
        {
            hiyoko2d = GameObject.Find("hiyoko2d");
        }
        // 2D画像を表示する。
        hiyoko2d.SetActive(true);
        // 親コンポーネント(Canvas)のスクリーンサイズを取得する。
        var parent_w = hiyoko2d.GetComponent<Transform>().root.gameObject.GetComponent<RectTransform>().sizeDelta.x;
        var parent_h = hiyoko2d.GetComponent<Transform>().root.gameObject.GetComponent<RectTransform>().sizeDelta.y;
        // スマートフォンの物理的なサイズ（解像度）を取得する。
        var screen_w = Screen.width;
        var screen_h = Screen.height;
        // スクリーン座標で得られるpdataの位置から親コンポーネントの中での位置に座標変換して表示位置を変更する。
        hiyoko2d.GetComponent<RectTransform>().localPosition = new Vector3(parent_w * pdata.position.x / screen_w - parent_w / 2,
            parent_h * pdata.position.y / screen_h - parent_h / 2, 0);
        return;
    }
```

変数hiyoko2dはGameObject.Findで取得するため、Unity画面のInspectorでオブジェクト名の左にあるチェックボックスを外して非アクティブにしてはいけない。最初に画面左下に2D画像が表示されるのを回避する場合には、2D画像の位置をマイナス値にすればよい。

スマフォの解像度に依存するPointerEventDataからCanvasの座標に変換するのは、手動？で行っている。たぶん一発で変換できるメソッドがあるのでしょうが、捜していないです^^;

また、ドラッグ中、そしてドロップ時のイベントを取得するために、すでにサンプルで行っているタップイベントを取得しているCanvas/MeshInteractionPanel/Image_PlaceMarkerオブジェクトにハンドラを追加する。

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/d54779c9-c665-5e90-0d70-22ea237d313e.png" width=30%>

- タップイベントを取得するためのCanvas/MeshInteractionPanel/Image_PlaceMarkerオブジェクト

Dragイベントに対して、UI ControllerオブジェクトのMeshOcclusionUIControllerコンポーネントにあるImage_Dragメソッドを設定する。

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/84fc8856-a1d1-28ae-e30e-3b0598b553be.png" width=50%>

- Image_PlaceMarkerオブジェクトのEvent Triggerコンポーネント：Drag (BaseEventData)の部分を追加する


# 3Dオブジェクトを作成して飛ばす

Dropのときに呼び出されるメソッドをMeshOcclusionUIController.cs内に作成する。

```Assets/TangoSDK/Examples/ExperimentalMeshOcclusion/Scripts/MeshOcclusionUIController.cs
    public void Image_EndDrag(BaseEventData data)
    {
        // 2D画像を消す
        hiyoko2d.SetActive(false);

        PointerEventData pdata = (PointerEventData)data;
        // 指を話した場所が、環境の学習によってつくられたmesh上にある場合のみ、3Dオブジェクトを飛ばす。
        RaycastHit hit;
        if (Physics.Raycast(Camera.main.ScreenPointToRay(pdata.position), out hit, Mathf.Infinity))
        {
            // TangoはCameraオブジェクトが空間内を移動する。
            // そのカメラのy軸方向を取得して、3Dオブジェクトの発射方向の補正に使う。
            Vector3 up = Camera.main.GetComponent<Transform>().rotation * (Vector3.up * 1f);

            // m_markerObjectのコピーを作成する。
            GameObject obj = Object.Instantiate(m_markerObject, Camera.main.GetComponent<Transform>().localPosition, Quaternion.identity);

            // 3Dオブジェクトの発射方向は、カメラから指でさしたmesh方向 + カメラのy軸方向 の合成ベクトルとする。
            // 速度の絶対値は、2fととりあえず固定する。
            obj.GetComponent<Rigidbody>().velocity = (hit.point - obj.GetComponent<Transform>().localPosition + up * 2f).normalized * 3f;
            obj.GetComponent<Rigidbody>().angularVelocity = new Vector3(Random.Range(-10f, -10f), Random.Range(-10f,-10f), 0);
            obj.SetActive(true);
        }
        else
        {
            // meshのないところを触った時の処理
            //AndroidHelper.ShowAndroidToastMessage("no mesh");
        }
        return;
    }
```

3Dオブジェクトは、Dropのときにメッシュのある所に指が触れていないと、実空間上の位置が取得できないため、送出されない。3Dオブジェクトの発射位置はUnityの中ではカメラの位置、実空間上ならばスマフォの位置となる。そこからDropしたときに触っていたメッシュの方向への速度が与えられる。ただし、カメラのy軸座標（実空間ならばスマフォ画面の上方向）に2fの大きさの速度を足し合わせて山なりになるようにしている。最終的に計算される速度の大きさは、3fに固定されている。よって指を早く動かして3Dオブジェクトを遠くに飛ばすなどの操作はできないです^^;


そしてドラッグ中のイベントと同様に、Canvas/MeshInteractionPanel/Image_PlaceMarkerオブジェクトのEvent Triggerコンポーネントに、End DragイベントハンドラをImage_EndDragとしてセットする。
<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/84fc8856-a1d1-28ae-e30e-3b0598b553be.png" width=50%>

- Image_PlaceMarkerオブジェクトのEvent Triggerコンポーネント：End Drag(BaseEventData)の部分を追加する

# Tangoの設定

Tango ManagerオブジェクトのTango ApplicationコンポーネントのInspectorからTangoの設定をすることができます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/e3459fbd-48c7-b7e1-b711-52f43f5394d0.png" width=26%>

- Tango Managerオブジェクト

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/459947e5-8384-bd05-cf30-3f0c94364ee8.png" width=40%>

- Tango ApplicationコンポーネントのInspector

この例では、メッシュの細かさを上げたかったので、Resolusion(meters)を0.05から0.02に変更している。


# 次のような画面で実行できない場合

メッシュを作成した後に、「Start Game using Area Description with mesh」によって実行したときに次の画面が表示され続けることがある。

<img src="https://qiita-image-store.s3.amazonaws.com/0/275020/bd8d8c1b-d2e7-dcd6-4ba9-115dc7c5a688.png" width=20%>

- 「Start Game using Area Description with mesh」を押して実空間にメッシュを重ねる画面を表示しようとしても表示できないとき

これは経験的には、作成したメッシュの量が「少ない」と起こる気がします。メッシュ作成時に少しスマフォを動かして多めにメッシュを作成してみて下さい。


# 参考にさせていただいたサイト
[Unity Tango SDKのExamplesを全種類ビルドしてみた](https://qiita.com/jyuko/items/79136ab68575f323ce5d)
[Tangoの3DRをチューニングしてみた](https://qiita.com/jyuko/items/4753f0c228e8db7701bf)