「GoogleCardboardとRaspberryPiを使って視界を共有できるロボットを作る」解説
第3回はAndroid側のプログラムについてです.一応これで最終回予定です.
なおAndroid開発はほぼ初めてのため,誤りを多分に含む可能性があります.ご了承ください.
Android側の主な仕事はUDP通信とヘッドトラッキングです.
はじめにUDP通信についてです.前回のC++がJavaになっただけかと思いきや,存外面倒くさかった.
Androidで通信を行うためには非同期処理が必須のようです(Android3.0以降).
非同期処理の方法について調べると,概ね次の3つがあるそうで.
・AsyncTask
・AsyncTaskLoader
・Thread
(一応Service使ってもできないことはない気も...)
とはいえ,実際にはThreadがベースになっていて,AsynctaskもAsyncTaskLoaderも,Threadを使いやすくしただけみたいです.
※なお,非同期処理については
アプリ開発者を育てるプログラミングスクール Tech Institute(テックインスティチュート)
のVol12がかなり参考になりました.
今回のように何度も送受信が必要な処理の場合はAsyncTaskLoaderが適しているようなのですが,一つ問題が.
AsyncTaskもAsyncTaskLoaderも,原則Fragmentにしか使えません(もしくはFragmentを継承したActivity).
後ほど触れますが,GoogleCardboardのAPIを使うためにはメインアクティビティをGvrActivityから継承しなければなりません.
そしてGvrActivityはFragmentを継承していないため,AsyncTaskLoaderは使えないということになります.(ホントかなぁ...)
正直この部分については自信がありませんが,どうにも方法を見つけられなかったので今回はThreadを使って実装しました.
UDP通信部分については割愛しますが,受信したデータを変数bufに格納した場合,
Bitmap bmp= BitmapFactory.decodeByteArray(buf,0,buf.length);
でBitmapに変換できます.
あとはこれをUIスレッドにpostすれば受信した画像を表示できます.
......実際この実装でうまく動いているのですが,jpegで送ってbitmapで読み込みって普通に考えて奇妙ですね.decodeByteArrayがうまいことやってくれてるってことなんでしょうけど,どののフォーマットに対応しているのかリファレンス見てもよくわからなかったり.
ちなみにAndroidはbounjorに対応していないため,ホスト名のみでIPアドレスを解決することはできません(正確には方法はあるようですが,私にはできませんでした).ですが今回はテザリングで機器間をつなぐため,androidの持っているarpテーブルを使うことで,macアドレスからIPアドレスを解決することができます.取得方法は次のリンクを参考にしました.
java - How to get the IP address of a system using Android phone? - Stack Overflow
続いてヘッドトラッキングについて.
これはGoogleCardboardのAPIを使います.
Google VR SDK for Android | Google VR | Google Developers
本当は画像の表示にもこのAPIを使いたかったのですが,どうにも方法を見つけられなかったので画像は別途貼り付けています.
Google Cardboard Tutorial: How to render images using Cardboard App
(こことほぼ同じやり方です.)
というわけで,CardboardのAPIは本当にヘッドトラッキングのためだけに導入してます.
※補足
ゴーグル越しに画面を見る場合,ゴーグルの凸レンズで画面が拡大されてみえるため,樽型のひずみのようなものを予めつけておく必要があります.CardboadのAPIではこのひずみを付けたうえで描画してくれる機能があるのですが,これは3D空間を投影する部分とセットになっているようで,今回のようにもともと2次元の画像を描画する機能は見当たりませんでした.
タオバイザー様の公式アプリでもAndroidのカメラ画像をVR画面として出す双眼鏡モードでは樽型のひずみがついていないので,今のところ方法はないんじゃないかと思います.
※補足終わり
GvrActivityを継承したうえでGvrView.Rendererをimplementしたアクティビティを用意すると,オーバーライドが必要なメソッドの一つにonDrawFrameがあります.
このメソッドはフレームを描画する際に呼ばれるものですが,これの第一引数に頭の向きを表すHeadTransformが格納されます.
headtransformはgetterで様々な角度情報に変換して取得することができます.
HeadTransform | Google VR | Google Developers
オイラー角やクォータニオンなど大体のものが揃っているので,使いやすい形で取り出しましょう.今回の実装ではgetForwardVectorで取り出しました(クォータニオンには自信がないので......).
ForwardVectorは現在向いている方向をxyzの単位ベクトルとして返します.ベクトルをparamという配列に格納したとすると,座標系は次の通りです.
あとはこれをarctanで角度に変えて使っています.
pan=arctan(x/z)
tilt=arctan(y/z)
という感じですね.なおarctanは分母が0のときにエラーを吐くので,arctan2を使うことをお勧めします.