three.jsで遊んでみる(11)

 プログラム,
 公開日:2013年2月15日 / 更新日:2013年10月24日

うまく行かずに半ば諦めて、
これで〆にしますとか前々回で書きましたが、
やり方を変えてみたら良い感じになってくれました。
微妙にまだうまく行ってない気もしますが(^_^;)
とりあえずは目標に達せたかと思ってます。

それでは順を追って書いてみます。

まずは、
physi.jsを web worker を使わないバージョンに
改造することから始めました。
workerによって別スレッドで動かすと、
性能的には恩恵を受けられるのですが、
主スレッドとの同期が取れなくなります。
これが問題なのかも? と思ったのです。
完了を待ってやるようにすれば同期は取れるのですが、
もはや別スレッドにする意味が無くなります。
workerを使うとデバッグもやりにくい感じなので、
思い切って改造することにした次第です。
一方で使い勝手は変えたくなかったので、
イベントによるメッセージ送受信を
関数呼び出しの形に単純に置き換えました。
内部的には冗長的なのが気になるけど、
作業量が少なくて済むのでこれで良しとしました。

さて、
これでやってみたけど殆ど変わらず・・・。
たぶんそんなことになるだろうと予想してたけど(^_^;)

やはり、
物理演算の結果のワールド座標系をボーンに適用するために、
ローカル座標系へ変換することに無理がありそうです。
素直にワールド座標系で適用することにしました。
ただ残念ながらthree.jsのレンダラでは、
シーンの座標変換とレンダリングを一つの関数で行なっているため、
ワールド座標変換後に処理を入れることができない仕様です。
座標変換とレンダリングは別関数にしておいた方が、
使い勝手的に良いんじゃないかと個人的は思ってたりします。
しょうがないのでthree.jsに改造を入れました。
以下のように、
座標変換後かつレンダリング直前にコールバックできるようにしました。

この施策の結果、
だいぶ良い感じになってくれました。
はじめから素直にワールド座標系でやっておけば、
前のように苦労せずに済んだかも・・・嗚呼(^_^;)

ところで、
質量ゼロなstatic剛体をユーザーが動かす場合は、
kinematic剛体にするべきなことを知りました。
ここにそんなことが書いてあったのに、
すっかり見過ごしてました(^_^;)

さて、
でもまだなんかオカシイ感じ。
一つは、拘束条件で軸をロックしているはずなのに
何故か引き伸ばされるように動いてしまうことです。
例えるならAとBが握手していて、
AがBを引っ張ったら即座にBは引き寄せられるはずなのに、
何故かゴムのように伸びた後に引き寄せる感じなのです。
海賊なゴム人間をなんか思い浮かべてしまいます(^_^;)
※Aをkinematic剛体、Bをdynamic剛体とした場合。

拘束条件というのはこんな感じになっています。
で、以下のような設定ができるようになっています。

Lowerlimit == Upperlimit -> この軸はロックされます。
Lowerlimit > Upperlimit -> この軸は自由に動けます。
Lowerlimit < Upperlimit -> この軸の可動域は制限されます。

ところで、
物理エンジンBulletのサンプルを実行すると、
剛体をクリックして動かせる操作ができます。
クリックした点をアンカーとして、
剛体をゴムのように引きずることができます。
一時的に拘束条件を設定してやってるんだろうと思い、
何かヒントがないかとソースを探ってみました。
そしたら以下のようなコードが見受けられました。

この中で、
BT_CONSTRAINT_STOP_CFM、BT_CONSTRAINT_STOP_ERP
に注目しました。
他に、BT_CONSTRAINT_CFM、BT_CONSTRAINT_ERP というのがあります。
ここによると、
CFM は Constraint Force Mixing の略で、
拘束の強さを決定するパラメータ、
ERP は Error Reduction Parameter の略で、
シミュレーションのステップごとに発生する計算誤差を減少させるためのパラメータだそうです。
ロボット工学とかで使われるようなパラメータのようです。

これがなんか影響しているのではと思い、
いろいろ設定を変えてみましたが、
相変わらずゴムのように伸び縮みするばかりで、
ロックして完全に追従させることはできませんでした。
上記ソースでは、拘束条件が
6DofConstraintなのでバネの要素は無いはずなのですが・・・。
片方がkinematic、片方がdynamicの場合は、
拘束条件のパラメータ操作だけでアンカーにロックさせることは、
現状ではできないのかもしれません。

そんなこんなで悩みつつ、
関連情報をググってたら、「MikuMikuDroid」というのを見つけました。
MMDをアンドロイド端末で再現するというものです。
なかなか評判も良いみたいです。
しかもソースが公開されているではありませんか!!!
さっそくsvnでチェックアウトして参照してみました。
いや~、これは大いに参考になります。
もっと早くこれに出会っていたらラクだったのに、
と激しく思いました(^_^;)
3D描画はGLES2.0を使ってスクラッチで書かれているようです。
スゴイっす。
JNIを使ってBulletをnativeなコードで動かせるのは、
性能的にちょっと羨ましい気がします。

で、
ソースを読んで、一つの疑問が氷解しました。
MMDの剛体には、
「静的」、「動的」、「動的&ボーン位置合わせ」
の3種類がありますが、
3つ目のがよくわからないまま、
「動的」と同じ扱いにしていました。
物理演算の結果をボーンに適用する際、
剛体の位置と回転値をボーンに設定しますが、
回転値のみを設定して位置はボーンのままにするのが
「動的&ボーン位置合わせ」
ということが分かりました。
まさに文字通りでした(^_^;)
実はこの処理が、
拘束条件のアンカーにロックさせることに相当するのです。
拘束条件のパラメータ調整ではどうにもゴムのように伸縮してしまうので、
無理やり合わせてしまえ、なのだと自分は理解しました。
ロックして追従させるにはこの手法しか無いのかもしれませんね。
一方、
「動的&ボーン位置合わせ」というのは、
dynamic剛体にボーンの位置を適用することでもあります。
dynamic剛体の位置を直接変更するのは本来はやるべきではないのでしょうが、
これしか手がなさそうな感じです。
ただこれをやった場合に、
物理エンジン側に通知とかの処置が必要なのかもしれませんが、
よく分かっていません。

さて、
そんなこんなでだいぶ改善されてきたのですが、
スカートの挙動がいまだ変な感じなんです。
どうにもうまく行かなくて苦労したのですが、
いろいろ試行錯誤した結果、結局データを調整することで対処しました。
スカートの物理演算は、8つの箱型の剛体から成っています。
うち前部の2つが「動的&ボーン位置合わせ」で、
残りは「動的」で設定されているのを、
スカート全てを「動的&ボーン位置合わせ」にしました。
これでそれっぽく動作するようになりました。
(実はまだちょっと変なところがあるけど(^_^;))
こうしないとうまく行ってくれないのは、
「動的&ボーン位置合わせ」に対する処理が
十分にうまく行えてないせいなのかもしれません。
今後の課題ですね。

気づいたら
なんか長文になってしまっていました(^_^;)
ということで、
ミクさんに物理演算を適用した結果はこちらです。
画面の右上の「rigid body」をチェックすると剛体が表示されます。

諸般の事情でちょっとローディングに時間がかかりますが、しばらく待ってみてください。なお、ChromeなどのWebGL対応のブラウザで見てください。PCパワーもそれなりに必要となるかもしれません。あまりにも時間がかかり過ぎるようならば、リロードして再度試してみてください。

mytest14

その他、思ったことや、
対処したことなどを備忘のために列挙しておきます。

  • C++なBulletのJavaScript版のammo.jsを使うわけですが、
    ammoのオブジェクトをnewした後に不用になったら、
    明示的に Ammo.destroy() しなければなりません!!!
    C++の流儀に従うことになるわけですな・・・(^_^;)
    正直メンドイので、初期化や生成、追加系の操作では後始末はやってません。
    ただ設定系や更新系は対処しておかないと、
    その内メモリ不足でハングアップします(^_^;)

  • 重力のデフォルト値は (0,-10,0) だが、
    このままだと不自然な感じなので (0, -9.81*10, 0) とした。

  • 剛体はある一定時間変化が無いとスリープするようになっている。
    明示的に起こすようにしても良いのだが、
    面倒なので setSleepingThresholds(0,0) とやって、
    スリープしないようにした。

  • 座標系が、本家MMDは左手、OpenGL、Bulletでは右手なので、
    自分はあらかじめデータを左手系から右手系に変換しているが、
    MikuMikuDroidではカメラ行列やカリングの向きを調整するなどして、
    データは左手系のままで使っている模様。
    物理演算系のパラメータもBulletにそのまま渡しているようなのだが、
    左手から右手系へ変換してないようにみえる。ナゼこれでうまく行っているのかは不明。

  • ここによると、kinematic剛体にした場合は setActivationState(DISABLE_DEACTIVATION) すべきなことが書いてあるが、これをやるとナゼか剛体が固定されてしまう。MotionState に関係があるようなのだが、いまだ使い方がよくわかってない(^_^;)

  • physi.jsを改造して使っているが、今回のような場合はmesh前提な仕様だと扱いにくかったり、冗長的になったりしてしまう部分があるので、将来的には剛体の描画と物理演算は分離させ、physi.jsを使わない実装に変更したい。

参考までに、
左手系から右手系への変換は次のようにやってます。

頂点や法線: (x,y,z) -> (x,y,-z)
オイラー角: (x,y,z) -> (-x,-y,z)
クォータニオン: (x,y,z,w) -> (-x,-y,z,w)
あと三角形の頂点並び順も逆にする。

タグ: ,

コメント投稿