Hololensで室内の経路表示を行う。

Holoensに触らせていただく機会を頂いたので、友人たちと共に題意のUWPのアプリを作成した。
こんな感じのものができました↓
![route_display_1.gif](https://qiita-image-store.s3.amazonaws.com/0/70879/3559c774-6cf5-16c6-5601-4af157a55436.gif)

# 概要
　Hololens上で室内の経路表示を行う。壁に接近したときには警告(ソナー)音を鳴らし、その音は壁との距離によって音の間隔が変わる。
 実現方法として、経路表示を行う屋内を予めマッピングしておき、そのオブジェクト上に経路のオブジェクトを配置する。現在の壁はspatial mappingの直下に空間メッシュが生成されるので、ここに取得済みのオブジェクトを重ねることで経路表示を行っている。また、World Anchorという機能を用いて、起動毎に現在の壁に取得済みのオブジェクトを重ね合わせる処理を不要とした。

# ソースコード
https://github.com/MaruTama/Hololens_Navigator.git
point2AVG -> 回帰直線の式を計算する。
AVGpointLine -> 回帰直線の描画。
BaseSetpointSetup -> ワールドアンカーを探索して、あったら、そのポジションにマッピングデータを配置する。
InstantSE -> 効果音の生成
MultiRayCast -> 回帰直線を生成するためのレイキャスト
poxity -> 壁の接近によってソナー音を鳴らす
sound -> 距離によって音の生成速度を帰る

# マッピングデータの作成
HoloLensにアクセスして、「update」を押すと開始する。
作成後は「save」のボタンを押すと、objファイルが落ちてくる。
<img width="1440" alt="creating_model.png" src="https://qiita-image-store.s3.amazonaws.com/0/70879/255245de-9204-2cb3-5a5c-33782e5b6542.png">

## マッピングデータの読み込みについて
unity の読み込み時には頂点数が多く読み込めなかったため、blenderを用いて、頂点数を1/10にした。40万->4万

取得したobjファイルを読み込むと、オブジェクトが分散しているので一つに結合する。
モデルの読み込み↓
<img width="1440" alt="reading_model.png" src="https://qiita-image-store.s3.amazonaws.com/0/70879/94aba73c-4acb-1c0b-5ff3-35b4217f804c.png">

[A]を押して全選択をする。
<img width="1440" alt="all_selected.png" src="https://qiita-image-store.s3.amazonaws.com/0/70879/c1647b4b-8e87-334c-0bce-936eaa128b73.png">

[Ctrl]+[J] を押して結合する。
<img width="1440" alt="joined.png" src="https://qiita-image-store.s3.amazonaws.com/0/70879/653562a1-d3a0-88f4-ac63-bb586cc21566.png">

右のレンチの「Decimate」をクリックする。
<img width="1440" alt="decimate.png" src="https://qiita-image-store.s3.amazonaws.com/0/70879/0dce45c7-8698-2698-f03e-d4fc51caf2d3.png">

スケールの変更を行う。
<img width="1440" alt="changed_scale.png" src="https://qiita-image-store.s3.amazonaws.com/0/70879/bc63ed49-9122-4682-f26b-e2b0041cb01d.png">



# 自分と壁の距離測定
現在の壁はspatial mappingの直下に空間メッシュとその判定が生成される。
壁との距離の計測は、Raycastという仮想的な線を利用して衝突検出をする機能を用いて、
カメラ位置(自位置)とレイを飛ばした際に衝突したオブジェクト(現在の壁の空間メッシュ)の位置(hitInfo.point)から距離を計算している。
<img width="1440" alt="pos_spatial_mapping.png" src="https://qiita-image-store.s3.amazonaws.com/0/70879/b3e3ba99-300a-a36b-6afb-bf143122c3d5.png">
<blockquote class="twitter-video" data-lang="ja"><p lang="und" dir="ltr"><a href="https://t.co/MkcysKBRAB">pic.twitter.com/MkcysKBRAB</a></p>&mdash; たま (@mukimuki_tamago) <a href="https://twitter.com/mukimuki_tamago/status/841206373664735232">2017年3月13日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>





# 回帰直線を用いて、現在の壁を直線(ベクトル)にする。
現在の壁はspatial mappingのメッシュから頂点情報を取得する。spatial mappingが現在の壁なので、そこへRayを飛ばして衝突が起きた点を取得する。それらを平面上に射影して、回帰直線を作成する。つまり、凸凹の壁を高さ要素を消して、凸凹の壁を直線にする。そして、取得した直線をベクトルに変換して、unity上で扱いやすいようにする。
<img width="1440" alt="ex_regression_line.png" src="https://qiita-image-store.s3.amazonaws.com/0/70879/3ec4bcb3-bab3-dab1-7636-7cdf63d3f645.png">

# 現在の壁とマッピングデータの壁を揃える。
その取得した回帰直線をベクトルに変更して、LookRotationで軸を揃えて、現在の壁とマッピングデータの壁を合わせる。 QuaternionクラスのLookRotationを用いて取得した回帰直線のベクトルにモデルの軸を揃えた。
![before_calibration.jpg](https://qiita-image-store.s3.amazonaws.com/0/70879/1bb6fd70-1316-6157-7b49-f7e307f2614c.jpeg)
![after_calibration.jpg](https://qiita-image-store.s3.amazonaws.com/0/70879/ee8a78b1-2d69-7048-1f3d-74f07829188c.jpeg)
そして、マッピングデータ上に経路のオブジェクトを置く。
<blockquote class="twitter-video" data-lang="ja"><p lang="und" dir="ltr"><a href="https://t.co/sso88ZoPEs">pic.twitter.com/sso88ZoPEs</a></p>&mdash; たま (@mukimuki_tamago) <a href="https://twitter.com/mukimuki_tamago/status/839788639823396864">2017年3月9日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

# 壁に接近したときの処理
　unity上で自位置から全方向にRayを飛ばして衝突したオブジェクトとの距離を調べる。もし、設定した閾値以下になったらその方向にAudio Clipを置いている。距離が近くなると音の周期を短くしている。
動画はモデルの高さの配置に失敗したときのもの。
<blockquote class="twitter-video" data-lang="ja"><p lang="und" dir="ltr"><a href="https://t.co/iYHj9wRzRb">pic.twitter.com/iYHj9wRzRb</a></p>&mdash; たま (@mukimuki_tamago) <a href="https://twitter.com/mukimuki_tamago/status/839792101000818688">2017年3月9日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
使用素材 : [潜水艦 ソナー音 【単発.ver】](http://commons.nicovideo.jp/material/nc105921 "潜水艦 ソナー音 【単発.ver】")

# コンポーネントの追従
HoloTooolkitをインポート。（unitypacageファイルをドラック＆ドラック）
Unity5.5からHoloLensに標準対応しているが、UI関連のコンポーネントは含まれていないのでインポートする。 
![HoloToolKit.png](https://qiita-image-store.s3.amazonaws.com/0/70879/b8842c5e-e5b5-23e0-84c0-6b917ddd92e6.png)

Canvasを追加。CanvasはUIを表示するための土台のようなもの。UIはすべてこの子に置く。HoloLens特有の機能ではなく、Unityにもともとある機能でほかのゲーム作りでも同様。 
![add_canvas.png](https://qiita-image-store.s3.amazonaws.com/0/70879/bf54751d-b64a-72f5-24c7-3397fb5143e8.png)

CanvasのInspectorビュー(右の設定)上のCanvasコンポーネントのRenderModeをWorldSpaceに変更。WorldSpaceにすると3d空間上にUIを表示できる。
![worldSpace.png](https://qiita-image-store.s3.amazonaws.com/0/70879/de7b75ab-a7d2-9de8-7268-4b55632c4914.png)

EventSystemオブジェクトにHoloLensInputModuleを追加。たぶん、これによってUIのタッチやクリックのイベントを拾えるようになっている。
![HoloLensInputModule.png](https://qiita-image-store.s3.amazonaws.com/0/70879/c8da1ffe-3808-b224-8283-f5a8e1bae054.png)

Canvasの子にUI→Imageオブジェクトを追加。これが表示されるウインドウの背景になる。Tabalog(自動的にBoxColliderも付けられる)とBillboardを追加。これによってHoloLensに表示されたUIが頭の動きに追従するようになる.
![add_Tabalog&Billboard.png](https://qiita-image-store.s3.amazonaws.com/0/70879/c6ce703e-3f20-6176-804b-f198813dbf49.png)


Canvasの子にTextオブジェクトを追加。これをスクリプトから変更することで、スクリーン上に距離を表示する。
![window_view.png](https://qiita-image-store.s3.amazonaws.com/0/70879/1af4dc8a-f1a6-06d5-4234-680d884be45c.png)

<blockquote class="twitter-video" data-lang="ja"><p lang="und" dir="ltr"><a href="https://t.co/WdiTXPqz78">pic.twitter.com/WdiTXPqz78</a></p>&mdash; たま (@mukimuki_tamago) <a href="https://twitter.com/mukimuki_tamago/status/841206669279285248">2017年3月13日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

参考 : [【HoloLens】Unityでスタートメニューみたいな追従するUIを作る](http://kzonag.hatenablog.com/entry/2016/08/14/163736 "HoloLens】Unityでスタートメニューみたいな追従するUIを作る")

# World Anchorについて
　これはアプリ内のマッピングデータの座標を固定する機能であり、アンカーをストレージを保存しておく。もし、アンカーがない場合には回帰直線を用いて現在の壁とマッピングデータの壁との較正を行うが、アンカーがある場合にはそのその操作は不要である。よって、アンカーが保存されている限り、アプリの起動・終了を繰り返しても較正せずに、経路表示行う事ができる。  
参考 : [HoloLens の World Anchor を使用する](http://www.naturalsoftware.jp/entry/2016/06/06/103555 "HoloLens の World Anchor を使用する")  

# 追記
　HoloLensの機能をUnity内から利用する場合はPlayerSetting->PublissingSettingsのCapabilitiesにて使用する機能のチェックボックスにチェックを入れることで利用する機能を明示しておく必要がある。今回は空間感知（Spatial)機能を利用したいためSpatialPerceptionにチェックを入れた。
![IMG_18032017_162504_0.png](https://qiita-image-store.s3.amazonaws.com/0/70879/6737b159-1407-944d-e70e-cef77263749e.png)