2014年7月27日日曜日

three.jsでブラウザ上で動くインタラクティブな倒立振子シミュレータを作る

引っ越しを機に研究室のサイトを現代的に改装することになりました.

上の倒立振子振り上げアニメーションはサイトのトップページを15年間飾っていた年代物ですが,これもその一環として現代の技術水準で更新することにしました.目標は滑らかでインタラクティブ(ユーザーが外乱を与えて制御系の反応を観察できる)なアニメーションを設置することです.
そのためには
  • ブラウザでリアルタイムに動作する倒立振子振り上げ制御器を実装する
  • ブラウザでリアルタイムに倒立振子の3Dグラフィックスを描画する
必要があります.今後改装を行うときのために,これらのポイントについてメモしておきます.
なお,完成品は以下の様な感じです.


手での操作が可能な完全版は研究室のトップページ

リアルタイム倒立振子振り上げ制御

ユーザーが与える外乱は事前に予測不可能なので,ブラウザ上でリアルタイムに動作する制御器で2本の倒立振子の振り上げを実現する必要があります.最高の効率(最小の制御動作)でこれを行うことは非常に困難な問題ですが,効率に目を瞑ればそれほど難しい問題ではありません.今回はインスピレーションで簡単な切替え制御則を設計しました.実装は
にあります.振り上げたあとの安定化は研究室内学生実験の成果を利用しています.ちなみに倒立振子のモデルは
です.基本的にはアーム部分の加速度を自由に制御できる物として運動方程式を立てただけですが,リアリティを出すためにモデルでもモーターに速度リミッターをつけています.
なお,実装において必要となる演算には Numeric.js を利用しました. Numeric.js は行列計算に加えて数値積分のルーチンまで持っているので,倒立振子シミュレータもこの機能を使って実装しています.
倒立振子のシミュレータに物理エンジンを使えば後々おもしろい拡張も可能かと思いましたが,現状では少し動作が重いようだったので割愛しました.自分のサイトでは重さを度外視して物理エンジンを使ったセグウェイライクな自走式倒立振子シミュレータを設置しているので興味があれば遊んでみてください.

ブラウザ上での倒立振子の3D描画

15年前にはブラウザ上でリアルタイムに3Dグラフィックスを描画するのはほぼ不可能でしたが,最近はブラウザ上でもWebGLという標準仕様で効率的に3Dグラフィックスを描画することができます.しかしWebGLを使って直にプログラムを書くのは大変手間らしく,three.jsというのを使うのが楽なようです.

実際 three.js を使った描画は非常に簡単で,MATLAB用の描画プログラムから特に苦労することもなく倒立振子を描画するルーチンを作成することができました.ただ,作ってから気がついたのですが
  • safari (iPhoneのブラウザ) が WebGL にデフォルトで対応するのはおそらく9月
  • Windows上ではWebGLで DirectX が描画に使われる関係で太い線を描画できない
という問題があったので,現時点では three.js の renderer として WebGLRenderer ではなく CanvasRenderer を使っています.
それほど複雑な物体を描画するわけではないので,こちらのほうが多くの環境で快適に動作するはずですが,何年かしたら WebGLRenderer に切替えてもいいかもしれません.

その他

ワイヤーフレームでの四角形面の描画について

three.jsは,あるバージョンから四角形面を描画する機能が省かれた(3角形2つに分割される)ようです.通常の描画であれば全く問題ありませんが,ワイヤーフレームで描画した場合線が増えて鬱陶しいので,やや古いバージョン(r59)を使っています.最近もバージョンアップで描画性能が向上しているようなので,状況が変われば three.js のバージョンを上げるべきかも知れません.

背景の写真との整合について

背景の写真と倒立振子でパースが狂っていると気持ち悪いので実物の倒立振子が写真に写っていた場合と同じ見え方になるように計算しています.
といってもthree.jsのカメラ設定時に写真撮影時と同じ情報を指定するだけです.
なおthree.jsに指定する画角は画面の縦幅に対応する画角なので,$d$をレンズ焦点距離,$h$をカメラのセンサーの縦幅として
\[
2\tan^{-1}\left(\frac{h}{2d}\right)
\]
で計算される角度にすれば良いと思います.

safariでテクスチャが歪む問題について

three.js の CanvasRenderer と safari (iPhone iPad 等の mobile safari 含む)の組み合わせでテクスチャが正しく表示されない問題がありました.
safariのバグらしいのでそのうち直るかもしれませんが,とりあえずgithubのこのコメントにしたがって
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 1, 1 );
とすると正常に表示されます.