前回はルートプリムからのローカル座標を使った子プリムの相対位置の算出までご紹介しました。今回は
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 の説明でも記載されています。
簡単な例を使ってみてみましょう。

下の平べったい直方体がルートプリムです。直方体の上方向に立方体をリンクしました。これが子プリムになります。この子プリムの立方体を 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]);
}
}
}
結果は、ルートに傾きがなければうまくうごきますが、ルートが傾くと以下のようになります。
そこで、q の計算を
q = targetQ*childRot/llGetRootRotation()/llGetRootRotation();
のように
ルートプリムのローテーションで2回除算する、に変えると、ルートが傾いても 45度 ローカルZ軸を中心に回ります。
画像では小さいですが、ローカル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(),"");
}
}
}