Adsense

2010年10月12日火曜日

[LSL] 位置と回転について ~ ルートプリムと子プリムたち(2) ~

前回はルートプリムからのローカル座標を使った子プリムの相対位置の算出までご紹介しました。今回は llSetLinkPrimitiveParams の PRIM_ROTATION で指定するローテーションの指定方法をご紹介します。

ですが、、、最初にいっておきます。2010年10月の段階では llSetLinkPrimitiveParams の PRIM_ROTATION の指定には Bug (本当は Bug じゃないかもしれませんが、実装上の不具合というか混乱) があって、適切だろうというローテーション型の数値を渡してもうまく動かないのです。

[追記] コメントいただいたように PRIM_ROT_LOCAL を使うことですべてうまくいきます。    

[JIRA] llSetPrimitiveParams PRIM_ROTATION and llSetRot incorrectly implemented for child prims

回避方法はあります。といっても上記でいう incorrectly implemented (正しくない実装)を使うわけです。ですから将来的にこの回避方法が動かなくなる可能性(期待しない動きにならないこと)があることをご了承ください。


ポイント4 PRIM_ROTATION にはクセがあります

まぁ、、2007年からある問題ですので、ちょっと放置気味ですが、、。(いまさら仕様変更できないのかもしれませんし、JIRA を見るかぎりでは、ほぼみんなあきらめ気味です、、、)

そもそも仕様通りに動かないので、本来あるべき方法を説明しても仕方ないのですが、本来は、llSetLinkPrimitiveParams の PRIM_ROTATION で設定されるべきクォータニオンはルートから見た場合の相対的な回転です。その計算の仕方はグローバル座標軸上における設定すべきクォータニオンから、ルートプリムのローテーションを引いたもの、つまり除算したものになります。
[追記] ルートおよび子プリムのグローバルローテーションによる除算のため、グローバル基準を元にした相対位置となります。そのためもう一度ルートのローテーションで除算しています。これを回避するために、PRIM_ROT_LOCAL を使い、ルートプリムを基準とした相対ローテーションを取得することができるようになりました。

ですが、それを設定しても期待とおりに回転しません。

回避方法は、設定すべき子プリムのローテーションを、ルートプリムのローテーションで2回除算するのです。

なぜか、、、って考えない、考えない。(笑   このエントリーの最後のほうに私なりに考えた「こういう意味?」をまとめました。
また、この方法は llSetPos の wiki の説明でも記載されています。

簡単な例を使ってみてみましょう。
 rotation1
下の平べったい直方体がルートプリムです。直方体の上方向に立方体をリンクしました。これが子プリムになります。この子プリムの立方体を llSetLinkPrimitiveParams の PRIM_ROTATION を使ってまわしてみます。このときに、ルートプリムの直方体を傾けても、期待する動き(回転)をするようにスクリプトを組むのが目的です。

まず、失敗例から。スクリプトは以下になります。
タッチするとローカル Z 軸 45 度立方体が回る、というものです。子プリムのローカル Z 軸なので、期待する動きとしてはルートプリムの直方体が傾いても、直方体にのった状態でくるくる回る感じです。


回転する度に子プリムには自分のローカルローテーションを取得して、llMessageLinked を使い送信、ルートではそのレスポンスをもらったら、子プリムの上に計算したローカルローテーション と llGetLocalRot の値の両方を表示するというものです。

rotation q;
default{
    state_entry(){
        q = ZERO_ROTATION;
    }
    touch_start(integer total_number){
        rotation childRot = llList2Rot(llGetLinkPrimitiveParams(2,
                 [PRIM_ROTATION]),0);     
        vector targetEluer= DEG_TO_RAD*<0.0,0.0,45.0>;
        rotation targetQ = llEuler2Rot(targetEluer);
        q = targetQ*childRot/llGetRootRotation(); //ダメなサンプルですよ!
        llSetLinkPrimitiveParams(2,[PRIM_ROTATION,q]);
        llMessageLinked(LINK_SET,10,"","");
    }
    link_message(integer snd_num,integer num,string str,key id){
        if(num==100){
            llSetLinkPrimitiveParamsFast(2, 
                [PRIM_TEXT,(string)q+"\n"+str,<1.0,1.0,1.0>,1.0]);
        }
    }
}

結果は、ルートに傾きがなければうまくうごきますが、ルートが傾くと以下のようになります。
rot10

そこで、q の計算を


q = targetQ*childRot/llGetRootRotation()/llGetRootRotation();

のようにルートプリムのローテーションで2回除算する、に変えると、ルートが傾いても 45度 ローカルZ軸を中心に回ります。

rot11

画像では小さいですが、ローカルZ軸でまわっていない方の計算したローカルローテーションと、子プリムからもらったローカルローテーションは一致しています。うまくまわっている方は子プリムからもらったローカルローテーションと計算したローカルローテーションが違うのです。たぶん、これが多くのユーザーから Bug と言われる所以でしょう。

つまり、llSetLinkPrimitiveParams で設定したローカルローテーションと、子プリムが回転後に取得したローカルローテーションが違うのです。この例だと「何が問題?」になりますが、たとえば、ある子プリム A の向きと同じ方向を他の子プリム B に適用しようとしたとき、子プリム A のスクリプトでローカルローテーションを取得し、それを使って llSetLinkPrimitiveParams の 子プリムB の PRIM_ROTATION に受け渡してもダメ、、、ということなんです。これは相当悩みます。この場合は、取得したローカルローテーションを、ルートプリムのローテーションで1度だけ除算する必要があります。

ただ、、、これまでの球体で考える空間と位置指定の概念を使ってみると、この2回ルートのローテーションで子プリムのグローバルローテーションを割る、という意味が見えてきます。
子プリムのローテーションをルートプリムのローテーションで除算すると、子プリムとルートプリムの移動量の差分が算出されます。この差分をさらにルートプリムのローテーションで除算するということは、移動量の「基準」をグローバル座標軸に合わせる(ルートを無回転状態にする)、ということです。


つまり、東京からニューヨークの移動量を、東京を緯度0・経度0にしたときの移動量に変換しているのですね。こうするとルートがどの地点にあっても(どんな回転をしていても=東京でなくても)、同じような方向、距離であるルートからの移動量は絶対値的に表現することができます。そうすると、この2回除算するというのは非常に意味のあることだと考えられます。
まぁ、、、最大のハマリポイントでもありますね。

さて、次回はプリムではなくて、椅子(もしくはプリム)に座ったアバターを動かしてみます。

[追記] PRIM_ROT_LOCAL を使うことで上位のように2回の除算をする必要がなくなります。
サンプル スクリプトは以下になります。


rotation q; 
rotation childRot;
default{
    state_entry(){
        q = ZERO_ROTATION;
    }
    touch_start(integer total_number){
        childRot = llList2Rot(llGetLinkPrimitiveParams(2,
                 [PRIM_ROT_LOCAL]),0);      
        vector targetEluer= DEG_TO_RAD*<0.0,0.0,45.0>;
        rotation targetQ = llEuler2Rot(targetEluer);
        q = targetQ*childRot;
        llSetLinkPrimitiveParams(2,[PRIM_ROT_LOCAL,q]);
        llMessageLinked(LINK_SET,10,"","");
    }
    link_message(integer snd_num,integer num,string str,key id){
        if(num==100){
            llSetLinkPrimitiveParamsFast(2, 
                [PRIM_TEXT,(string)q+"\n"+str,<1.0,1.0,1.0>,1.0]);
        }
    }
}


プリム上に表示されるローカル ローテーションも同じものになります。
子プリムのサンプル スクリプトは以下になります。


default
{
    state_entry(){
    }
    link_message(integer send_num,integer num,string str,key id){
        if(num==10){
            llMessageLinked(LINK_SET,100,(string)llGetLocalRot(),"");
        }
    }
}