2008.08.20 / java

先週高校の友達が家に泊まりにきたとき、バランスWiiボードを見て「これでGoogleマップ操作できたら面白そうじゃない？」とぽろっと言ったのをきっかけに「あれ、それできそうだぞ」と思ったので勢いで作ってみました。

動作としては直感的なものになっていて、足踏みするとどんどん進んでいって、左右に重心傾けると向きが変わって前後に重心を傾けるとズームが変わります。百聞は一見にしかずで、映像見てもらったほうが分かりやすいかと思います。



Google Street View by Wii Balance Board from katsuma on Vimeo.

構成

全体の構成としては次のもので成り立っています。

バランスWiiボード Bluetoothレシーバ(iMac) 信号解析モジュール(Java) ローカルWebサーバ(Jetty) Javascript(JSONP) Webブラウザ(出力)

バランスWiiボードとBluetoothレシーバ

言わずもがな例のWii Fitのあれ。まずBluetoothデバイスとしてMacに繋ぐ許可を与える必要があるのですが（ペアリング）、ペアリングするためにはRVL Enablerを使えば何も考えずに簡単に検知できます。（via WiiリモコンとFirefoxをjavascriptでつなげるWiiRemoCom Firefox3対応版）RVL Enablerを起動して、バランスWiiボードを近づけて「Search」ボタンをクリックすれば認識してくれるはず。

MacはいいけどWindowsはどうなんだ？という話になるのですが、おそらくUSB接続なんかのレシーバを用意すれば認識するはず？です。WindowsでWiiリモコンを扱う話もいろいろ出ているので特に問題はなくペアリングまでできるかと思います。

信号解析モジュール(Java)

ここが今回の一番のメイン。

最初はyappoさんが作られたPlusenをもとにどうにかしようと思っていたのですが、Cocoa-PerlブリッジのCamelBonesがLeopardでうまく動かなかったりビルドするものがやたら多かったり、そもそも僕はPerlが苦手だったりと山があまりにも多すぎたので断念。

で、他の方法を探していたらJavaでWiiリモコン、バランスWIiボードの信号を解析してラップしてくれるナイスなモジュールのWIiRemoteJなんてものを見つけたので、これを利用しました。実際はさらに下のレイヤーであるJNIでBluetoothの信号を受信するためのJSR-082 (Java Bluetooth API) 実装ライブラリも必要になります。今回はWindows XP, Windows Vista, MacOS X, Linuxと幅広いプラットホームに対応しているBlueCoveを利用しました。

これらの使い方も簡単で、Eclipseのビルドパスに入れておくだけでOKで、複雑なことは何もありません。WiiRemoteJのパッケージを解凍するとサンプルプログラムのWRLImpl.javaがあるので、これをビルド、実行するとWiiリモコンの加速度センサのx, y, z軸方向の変化の様子を見ることができます。

バランスWiiボードを操作するときもサンプルのWiiリモコンを扱うのと基本的に大きく変わりません。BalanceBoardListenerをimplementsするclassを用意しておいて、

BalanceBoar balanceBoard = WiiRemoteJ.findBalanceBoard(); balanceBoard.addBalanceBoardListener(this);//thisは自身のクラス

などとしておきます。addBalanceBoardListenerすると、BalanceBoardからのいろんなイベントが起こるので、そのリスナbuttonInputReceived, combinedInputReceived, disconnected, massInputReceivedを実装します。今回は重心の動きを知りたいので、実際はmassInputReceived(BBMassEvent evt)だけまじめに実装すればOK。BBMassEventは、バランスWiiボードを４分割（左上、右上、左下、右下）したときにそれぞれの領域でどれくらいの力が加わったか、のイベントとなります。こんな感じで取得できます。

double topLeft = evt.getMass(MassConstants.TOP, MassConstants.LEFT); double topRight = evt.getMass(MassConstants.TOP, MassConstants.RIGHT); double bottomLeft = evt.getMass(MassConstants.BOTTOM, MassConstants.LEFT); double bottomRight = evt.getMass(MassConstants.BOTTOM, MassConstants.RIGHT);

ただ、実際は上下左右の４方向で取得できたほうが都合がいいので、これを補正します。試行錯誤の結果、単純にこんな感じでよさそう。

double top = evt.getMass(MassConstants.TOP, (int)Math.floor((MassConstants.LEFT + MassConstants.RIGHT)/2)); double right = evt.getMass((int)Math.floor(MassConstants.TOP + MassConstants.BOTTOM), MassConstants.RIGHT); double BOTTOM = evt.getMass(MassConstants.BOTTOM, (int)Math.floor((MassConstants.LEFT + MassConstants.RIGHT)/2)); double left = evt.getMass((int)Math.floor(MassConstants.TOP + MassConstants.BOTTOM), MassConstants.LEFT);

要するに取得する領域を幅の平均でならしておきます。値もそれっぽいものが返ってきたので（多分）問題ないはず。

力の分布からバランスボードの踏み方を推定

上で書いた方法で４つの領域にどれくらいの力が加わったかが取得できるので、この値から実際に「どんな格好でバランスボードを踏んでいたか？」を推定します。

この推定方法が実際はかなり苦労しました。。多分一番時間がかかったところ。何せなかなか思い通りの推定ができないし、ノイズがものすごい量で入ってくるし、そのノイズを無視する閾値をどれくらいにするかを測定するために毎晩バランスボードを踏んだり降りたり、、な毎日でした。で、いろんなパターンを考えて試行錯誤した結果、辿り着いた推定方法はこんなアルゴリズム。実はかなり単純。

4方向の力(f 1 , f 2 , f 3 , f 4 )の割合r n を求める(r n =f n /Σf i ) r n の最小値m n =min(r n )を求める m n 以外のr i が、全て閾値thを超えていた場合、r n の方向の真逆を踏んでいたこととする（m n が左の場合、上、右、下方向にかかる力が閾値を超えていた場合は右側を踏んでいたことと見なす） 閾値を超えていなかった場合は、真ん中を踏んでいたことと見なす

これだけ。超シンプル。でもこれくらいでちょうどいい感じの結果でした。あと結果の履歴をとって、そこから判断とかも行っていたのですが、そこまでやらなくても方向検知のレベルであれば、実際は問題のない結果なのでこれで良しとしました。

Webサーバ

で、方向検知までできるとWeb屋としてはこの結果をなんとかHTML+Javascriptにフィードバックしたい、となります。そこでローカルでWebサーバ(Jetty6.1)を立てて、リモートのWebサイトからJSONPでアクセスできるようなAPIを作ることにしました。

「リモートのサイトからlocalhostにアクセスできるの？」という話が出るかもしれませんが、ファイルシステムにアクセスするのではなく、あくまで外部サイトのホストがたまたまlocalhostだった、ということなので問題なくアクセス可能です。このあたり、P2Pストリーミングソフトなんかだと、クライアントソフトがデータをかき集めてきて、Webサーバ上のプレイヤがlocalhostから再生する、なんて流れになるのと同じように考えてみればいいと思います。

Javascript(JSONP)

今回WebサーバでJettyを選んだのはCometが簡単に実装できたことが理由に上げられます。JSONP+Cometな組み合わせはLingrが既に実績ありましたし、Cometじゃないとポーリングを繰り返ししすぎるのもなんだか気持ち悪いです。実際はバランスWiiボードからの情報は全部JSONPで拾うには速度が足りないのですが、よほど細かなゲームを実装とかしない限り、特に今回のストリートビューのデモのようなものであれば、気にならない取りこぼしでした。

Webブラウザ(出力)

出力についてはJSONPで取得した情報をもとに、ステップなのか、どちらかの方向に傾いているのか？をJavaScriptで判断し、Google Map APIの関数をコールしています。この判断も単純に、３回連続した値がきたかどうかで判断しています。たとえばCENTERに重心がかかっていたものが３回連続できたら、真ん中を踏み続けていたということからステップしていたんだな、のような感じです。

ハードウェアとJavaScript連携はおもしろい！

今回のデモはとりあえず動くものを作りたかったので、凝った作りに全くなっていないし、超汚いコードになっていますが、localhostのJavaScriptで特別なオブジェクトを生成することで、このモジュールをインストールしているかどうか、の判定もリモートのサイトで行うことも可能です。

また、Wiiリモコンやヌンチャクの情報もWiiRemoteJは扱うことができるので、JSONP APIを同じように用意してあげて、ExternalInterfaceを使えばFlashとも連携できて、あれ、これ普通にWii本体なしでゲーム作れるんじゃね？？と、夢も膨らみまくりです。うおー。

ソースコード一式

超汚いまま整形なしですが、勢いで置いておきます。でもあとで改訂するかも。（これcodereposとか利用させてもらったほうがいいのかな）

MapWalker.zip

Javaのソースのtv.katsuma.walker.MapWalkerをビルド＆実行して、バランスWIiボードのSYNCボタンを押して認識させた後にここをアクセス。うまく起動できれば実際の渋谷を「本当に歩く」ことができます！（起動できていない場合、アクセスしてもJavaScriptエラーでとまっちゃいます。ここエラー処理いまサボってます。あとSYNCボタンおして例外で落ちちゃうことが割とあるのですが何回か繰り返すと繫がると思います＞＜）