透明コライダーによるVRMのボーン位置検出[VCIアドカレ#4]

**※※この記事に記載の方法は2019/05/01時点でのものです。現在ではボーン取得用の関数が用意されていますのでそちらをご利用ください※※**

#はじめに
この記事はGWアドベントカレンダーという企画の「[VCIアドベントカレンダー GW ver](https://gw-advent.9wick.com/calendars/49)」4日目の生地です
今日はVRMボーンの検出について説明します

#VRMのボーンの性質
###ボーンの衝突の検出方法
チュートリアルやリファレンスを読んだ方はVCISubItemの衝突をonTriggerやonCollistionで検出できることを知っているかと思いますが、この関数は自作のVCISubitemだけでなくVRMのボーンとの衝突を検出することも出来ます。
それを利用して以下のようなものを作りことも出来ます。
![heightmeter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/1cb77454-ad10-5506-b537-7bbf1d1f3636.gif)

動画の例だと以下のようなコードとなります。「Head」が頭のボーンとなります

```lua:sample.lua
function onCollisionEnter(item, hit)
  if item == "MesurePlane" and hit == "Head" then
    --処理
  end
end
```

衝突したボーンの名前がhitの中に入ってくるためif文で判定するのが一般的な形になるかと思います。
ボーンの名前が何になるかは実際にぶつけてログを出してみるのが早いかと思います(時間が出来たらまとめたいです)
※この衝突検出には注意点があります。(別の記事にて紹介予定)

上記のように衝突したボーンの名前は取ることが出来ますが、hitに入ったボーンの名前から***GetSubItemでオブジェクトを取得することが出来ません***
そのため、たとえ名前がわかったとしても位置情報などの詳細な情報は取得することが出来ません。


#透明コライダーによるボーンの位置情報検出
前章の説明でボーンの位置情報が取得できないことは説明しましたが、
透明なコライダーを作成し、ボーンとの衝突時にそのコライダーの座標を取得することで擬似的にボーンの位置を取得することが出来ます。
この章ではその方法を待機型・スキャン型に分けて説明します。
(自分で考えた手法のためもっといい案があれば教えていただきたいです)

### 待機型
一定の領域を複数の透明コライダーで埋め尽くしておき、対象が移動してそのコライダーに触れた際にそのコライダーの座標を取得します。
参考動画：小さいアバターが人のHeadを検出して追従
![he2li-sp3xi.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/e43a3a57-1c84-da8e-277a-d0f7a1004678.gif)

透明なので見えていないですが、上記の例だと以下のような板状の位置検出用コライダーをx,y,z方向に対し設置しています。
画像では各方向1つのオブジェクトですが、実際は領域を埋め尽くすように各方向10個ずつくらい設置してあります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/86b4e91a-9e75-a842-efc4-ad624894c899.jpeg" width=30%> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/0c5ffbb0-00c6-d7ae-3da8-8511dd1229d9.jpeg" width=30%> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/2c242ee7-0820-26df-ab3a-8585ef2318da.jpeg" width=30%>

人が移動し透明コライダーにぶつかった際に以下のように座標を取得してひとのHeadの位置の代替値としています。
取得の際は各方向別々にするようにしています。

```lua:sample.lua
function onCollisionEnter(item, hit)
  if hit == "Head" then
    --X,Y,Z用のコライダーにheadがあたったらheadの座標代わりにx,y,zを取得する
    local temp = vci.assets.GetSubItem(item)
    if string.match(item,"Cube_X") and hit == "Head" then
      targetPos.x = temp.GetLocalPosition().x
    end
    if string.match(item,"Cube_Z") and hit == "Head" then
      targetPos.z = temp.GetLocalPosition().z
    end
    if string.match(item,"Cube_Y") and hit == "Head" then
      targetPos.y = temp.GetLocalPosition().y
    end
  end
end
```

上記のように板状のオブジェクトを埋める方式でなく、正方形のオブジェクトを埋める方式でも出来るとは思いますが、
あまりオブジェクトを増やしたくなかったため、今回は板状のオブジェクトを使用しています。

### スキャン型
こちらはコライダーを動かして一定の領域をスキャンする方法です
参考動画：cubeクリックで人の上にたらい落下
![tarai.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/922e0270-3bb3-8055-d2c0-a5141de48256.gif)

透明なので見えていないですが、位置検出用コライダーを配置し、cubeクリック時にコライダーを動かす事によってボーンを検出しています
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/e1c412d1-ccc6-eec2-0a3a-b4ab38577dcd.jpeg" width=30%>

こちらの方法はオブジェクトの数を少なくでき、検出精度もフレームあたりの移動量で調整できますが、
そのかわり、コライダーを移動させるスクリプトも書く必要があります。(アニメーションでも平気かも)
また、あまりに少なく小さいコライダーで広範囲をスキャンしようとすると時間がかかるため、検出のフレームレートも落ちてしまいます。

今回は複数のcubeのコライダーで検出を行いましたが、
人との距離を測りたいだけれあれば円のコライダー1つでも大丈夫かと思います。

#おわりに
今回はボーンとの衝突やボーンの位置検出について2つの手法書きました。
位置検出についてはコライダーのサイズを小さくすればするほど検出精度は高まりますが、オブジェクトが多くなり重くなるため、いい塩梅のところを見つけるのが重要かと思います。
ボーンの位置検出方法は他にもあると思いますので、「自分はこうやっている」等ありましたらコメントで教えていただけると幸いです
また、複数人いるときの検出方法や検出精度を上げた時のパフォーマンス影響度についてわかっていないため、わかったら追記したいと思います