Adsense

2010年10月16日土曜日

[LSL] 位置と回転について ~ アバタ―にスクリプトはいれられない ~

前回までで、llSetLinkPrimitiveParams を使った子プリムの位置と向きの設定方法をご紹介してきました。また、Link された子プリムを扱うには、与えられた「リンク番号」が重要だ、ということもお伝えしました。
そこで、リンク番号の取得ですが、プリム同士のリンクであれば、それぞれのプリムにスクリプトを入れ、リセット後のタイミングなどで llMessageLinked を使って子プリムのリンク番号をルートのスクリプトに渡すことで、リンク番号管理をしたりします。
llGetLinkNumber() を使って自分のリンク番号をとるわけです。

ここで問題になるのが、プリムにアバタ―が座った時のリンク番号です。ん?と思われるかもしれませんが、アバタ―がプリムに「座る」という行為は、アバタ―がルートプリムにリンクされた、というイベントで管理されるのです。
スクリプトでは CHANGE イベントCHANGED_LINK を使って、アバタ―が座ったかどうかを管理しているのですね。

アバタ―には他の子プリムのようにスクリプトを入れることができません。座った後のアバタ―の位置や回転を操作するのは、リンクされた子プリムの位置と回転を操作するのと同様ですが、リンク番号の取得がプリムのようにできないのです。今回はその対応方法と、椅子に座ったアバタ―の位置や向きを変えるスクリプトを考えてみます。

5. アバタ―にスクリプトはいれられない


まぁ、、、既知、既出といえる比較的有名な話題になります。wiki にも紹介されていますし、過去にも多くの先輩諸氏がブログなどで紹介しているのですが、ポイントをまとめると

a) 多くの場合は CHANGE イベント発生時のリンク番号の最大値がアバタ―のリンク番号
b) 複数のアバタ―が座ったり立ったりする場合は、アバタ―のリンク番号は CHANGE イベントのたびに変わる可能性がある


上記の2つに注意してリンク番号を取得するサンプル スクリプトを書くと以下のようなものになります。
なお、一人しか座らせない場合は、llSitTarget と llAvatorOnSitTarget を使って、アバタ―の key の取得、llGetNumberOfPrims をリンク番号として扱うのが一番簡単です。このスクリプトはオブジェクトに複数のアバタ―が Sit する、という前提で、key や リンク番号などの情報を取得するサンプルになっています。

//--------------------------------------------------------------------------------
// Sit した複数のアバタ―のリンク番号や Key, 名前、ポジション、ローテーションを
// それぞれのリストに取得するサンプルスクリプト
//--------------------------------------------------------------------------------
list keyList;     //Sit しているアバタ―の key をいれます
list nameList; //Sit しているアバタ―の名前をいれます
list posList;    //Sit しているアバタ―のグローバルポジションを入れます
list rotList;     //Sit しているアバタ―のグローバルローテーションを入れます
list numList;   //Sit しているアバタ―のリンク番号をいれます
default{
    state_entry(){
        llSay(0, "Hello, Avatar!");
    }
   
    changed(integer change){
        if(change & CHANGED_LINK){
            integer max = llGetNumberOfPrims();
            integer i = max;
            keyList = nameList = posList = rotList = numList = [];
            for(;i>0;--i){
                list tmp = llGetLinkPrimitiveParams(i,[PRIM_NAME,PRIM_POSITION,PRIM_ROTATION]);
                key tKey= llGetLinkKey(i);
                list objDetails = llGetObjectDetails(tKey,[OBJECT_CREATOR]);
//OBJECT_CREATOR が NULL_KEY の場合はプリムではなくアバタ―です
                key cKey = llList2Key(objDetails,0);
                if(cKey==NULL_KEY){ //リンクされたものがアバタ―かどうかのチェック
                    //アバタ―だったら各情報をリストに格納します

                    tmp = llGetLinkPrimitiveParams(i,[PRIM_NAME,PRIM_POSITION,PRIM_ROTATION]);
                    keyList += tKey;
                    nameList+= llList2String(tmp,0);
                    posList += llList2Vector(tmp,1);
                    rotList += llList2Rot(tmp,2);
                    numList += [i];
                }else{
                    i=0; //アバタ―じゃなくなったら、あとはリンクされたプリムなので終了
                }
            }           
        }
        //アバタ―が座っていれば、その情報を確認用に表示
        integer len = llGetListLength(keyList);
        if(len>0){
            integer i = 0;
            for(;i<len;++i){
                llOwnerSay(llList2String(numList,i)+","+
                           llList2String(nameList,i)+","+
                           llList2String(keyList,i)+","+
                           llList2String(posList,i)+","+
                           llList2String(rotList,i));
            }
        }else{
            llOwnerSay("誰も座っていません。");
        }
    }
}
//------------------------------------------------------------------------

上記スクリプトの入った直方体に2人のアバターが Sit すると llOwnerSay で以下のようなメッセージをオーナーは受け取ることができます。

[00:28]  Object: 2,Snuma Whitfield,d19d8aef-530a-41cc-bc1c-xxxxxxxxxxxx,<210.193100, 234.464900, 2498.686000>,<0.000000, 0.000000, 0.477574, 0.878592>
[00:28]  Object: 3,Alt Beck,a45ad906-fa8e-4fe2-bb3d-xxxxxxxxxxxx,<210.193100, 236.036500, 2498.686000>,<0.000000, 0.000000, -0.934414, 0.356190>


リンク番号3番の Alt さんは一番最後に Sit したアバタ―なのですが、ここでリンク番号 2 番の私が立ち上がったとき、Alt さんのリンク番号がどうなるか。。。答えは 3番ではなく即座に2番になるのです。そのため、changed イベントが発生する毎に、アバタ―のリンク番号の再設定をしなくてはいけません。

リンク番号さえわかっていれば、あとはこれまでお話ししてきた設定方法を使って llSetLinkPrimitiveParams によるアバタ―の位置や回転の指定を行えばいいのです。
llSitTarget で指定するような初期の座る場所を指定したい、、などは上記の考えを拡張する(座った直後に移動させる)か、wiki の llSitTarget にある「着座ポイントを更新」などの応用をためしてみるといいでしょうね。

それでは複数のプリムからなる台座(オブジェクト)に、複数のアバターが座り、台座にタッチすることで表示されるダイアログで自分の位置を変える(前後、左右、上下、回転)、というスクリプトのサンプルが以下です。
ポイントは、

1) CHANGE イベント毎にアバターのリンク番号を更新する
2) ダイアログで移動させるときに、その時点での位置情報を取得し、使用する
3) グローバル座標による差をローカル座標上の差に変換する
4) アバターを回すときは、ルートのローテーションで2回除算する
(または PRIM_ROT_LOCAL を使う)
5) llSitTarget と llAvatarOnSitTarget を使わず、座りたい場所にアバターを座らせ、微調整を可能にする

といったところでしょう。サンプル スクリプトとしてわかりやすくするために一部冗長なコードがあったり、情報確認用の llOwnerSay があったり、ダイアログの扱いについてもあまり汎用的にしていませんので、その辺は臨機応変に変更してみてください。
blogSittingScript

//PRIM_ROT_LOCAL を使って回転させるものに変更しました。
//注意点は前後、左右はローカルローテーションではなく、グローバルローテーションを使う点です。

list keyList;
list numList;
list personalSetting;
list preKeyList;
integer dChan;
integer dHandle;
integer dFlg;
list dButtons = ["閉じる","3cm設定","5cm設定","前に","左に","上に","後ろに","右に","下に","回る","10度設定","45度設定"];
string dMessage0 = "移動距離:";
string dMessage1 = "回転角度:";
float dist;
float spin;
//ダイアログメッセージ用浮動小数点の成形
string float2string(float p){
    string s = (string)p;
    integer dot = llSubStringIndex(s,".");
    s = llGetSubString(s,0,dot-1);
    return s;
}
//llSitTargetを使わない初期位置設定 
//tUnit 分だけ、座った位置から最初に移動する
setInitPos(key id){
    float tUnit = 0.03; //3cm 上にあげたい
    integer index = llListFindList(keyList,[id]);
    rotation tRootRot = llGetRootRotation();
    vector tAdjust = ZERO_VECTOR;
    integer tLinkNum = llList2Integer(numList,index);
    rotation tAvRot = llList2Rot(llGetLinkPrimitiveParams(tLinkNum,[PRIM_ROTATION]),0);
    vector tAvPos = llList2Vector(llGetLinkPrimitiveParams(tLinkNum,[PRIM_POSITION]),0);
    vector tLocalPos = tAvPos - llGetRootPosition();
//グローバル座標のオフセットをローカル座標のオフセットに変換
    tLocalPos = offset2local(tLocalPos,tRootRot);
    //llRot2Upで上に移動すふ調整オフセットを算出します。
    tAdjust = offset2local(llRot2Up(tAvRot)*tUnit,tRootRot);
    llSetLinkPrimitiveParamsFast(tLinkNum,[PRIM_POSITION,tLocalPos+tAdjust]);
}
//グローバルオフセットベクトルをローカルに変換するユーザー関数
vector offset2local(vector v, rotation rootRot){
    if((v!=ZERO_VECTOR)&&(rootRot!=ZERO_ROTATION)){
        rotation rot = llRotBetween(<1.0,0.0,0.0>,v);
        float mag    = llVecMag(v);
        vector vec   = llRot2Fwd(rot/rootRot);
        return vec*mag;
    }else{
        return v;
    }
}
default
{
    state_entry(){
        //ダイアログ用チャンネル設定 1000~9999
        dChan = (integer)(llFrand(9000.0))+1000;
        dHandle = llListen(dChan,"","","");
        dFlg = FALSE;
        dist = 0.03;
        spin = PI/4;
        preKeyList = [];
    }
  
    changed(integer change){
        if(change & CHANGED_LINK){
            //Listenしてなければ、Listen開始
            if(dFlg==FALSE){
                llListenControl(dHandle,TRUE);
                dFlg=TRUE;
            }
            preKeyList = keyList;
            key newAvKey = NULL_KEY;
            integer max = llGetNumberOfPrims();
            integer i = max;
            keyList = numList = [];
            for(;i>0;--i){
                list tmp = llGetLinkPrimitiveParams(i,[PRIM_NAME,PRIM_POSITION,PRIM_ROTATION]);
                key tKey= llGetLinkKey(i);
                list objDetails = llGetObjectDetails(tKey,[OBJECT_CREATOR]);
                key cKey = llList2Key(objDetails,0);
// Object Creator が NULL_KEY のときはアバター
                if(cKey==NULL_KEY){
                    tmp = llGetLinkPrimitiveParams(i,[PRIM_NAME,PRIM_POSITION,PRIM_ROTATION]);
                    keyList += tKey;
                    numList += [i];                  
                    //llSitTarget/llAvatarSitOnTargetを使っていないため、以下この方法でSitしたAvatar Key を取得します
                    if(llListFindList(preKeyList,[tKey])==-1){
                        newAvKey = tKey;
                    }
                }else{
//リンク番号が大きいのがアバターで、チェックするオブジェクトがプリムになった(Object Creator に key が入っている場合)は for 文から抜けます。
                    i=0;
                }
            }
            //新しく座った Avatar がいたら、初期位置を調整
            if(newAvKey!=NULL_KEY){
                setInitPos(newAvKey);
            }
        }
        integer len = llGetListLength(keyList);
        if(len>0){
            llListenControl(dHandle,TRUE);
            //--確認用--  実用時はコメント化ですね
            integer i = 0;
            for(;i
                integer tLinkNum = llList2Integer(numList,i);
                key tAvKey = llList2Key(keyList,i);
                llOwnerSay((string)tLinkNum+","+
                           llKey2Name(tAvKey)+","+
                           (string)tAvKey+","+
                           llList2String(llGetLinkPrimitiveParams(tLinkNum,[PRIM_POSITION]),0)+","+
                           llList2String(llGetLinkPrimitiveParams(tLinkNum,[PRIM_ROTATION]),0));
            }
            //---------- 
        }else{
            //誰も座っていないときは Listen をとめます
            llListenControl(dHandle,FALSE);
            dFlg=FALSE;
            personalSetting=[];
            //--確認用-- 実用時はコメント化ですね
            llOwnerSay("誰も座っていません。");
            //----------
        }
    }
  
    touch_start(integer num){
        key toucher = llDetectedKey(0);
        //移動距離や回転の設定を、ダイアログで変更した場合、 
        //グローバル変数を変更すると他のアバターに影響してしまうので 
        //保存されている自分用の設定値をを使用します
        if(llListFindList(keyList,[toucher])!=-1){
            float myDist = dist;
            float mySpin = spin;
            integer index = llListFindList(personalSetting,[toucher]);
            //自分用の設定値がある場合
            if(index!=-1){
                myDist = llList2Float(personalSetting,index+1);
                mySpin = llList2Float(personalSetting,index+2);
            }              
            string msg = "\n"+dMessage0+float2string(myDist*100.0)+"cm\n"+dMessage1+float2string(mySpin*RAD_TO_DEG)+"度";
            llDialog(toucher,msg,dButtons,dChan);
        }
    }
  
    listen(integer chan,string name,key id,string msg){
        //基本、座っているアバターのダイアログからのメッセージだけを受け取りますが 
        //チャンネル重複などによる混線回避のため id チェックを行います
        if(llListFindList(keyList,[id])!=-1){
            if(msg!="閉じる"){
                float myDist = dist;
                float mySpin = spin;
                integer index = llListFindList(personalSetting,[id]);
                //自分用の設定値がある場合 
                if(index!=-1){
                    myDist = llList2Float(personalSetting,index+1);
                    mySpin = llList2Float(personalSetting,index+2);
                }
                integer linkNum = llList2Integer(numList,llListFindList(keyList,[id]));
                rotation avRotG = llList2Rot(llGetLinkPrimitiveParams(linkNum,[PRIM_ROTATION]),0); //Avatarのグローバルローテーション 前後左右移動に使います
                rotation avRotL = llList2Rot(llGetLinkPrimitiveParams(linkNum,[PRIM_ROT_LOCAL]),0); //Avatarのローカルローテーション 回転で使います
                rotation rootRot=llList2Rot(llGetLinkPrimitiveParams(1,[PRIM_ROTATION]),0); //ルートのグローバルローテーション  
                vector rootPos  = llList2Vector(llGetLinkPrimitiveParams(1,[PRIM_POSITION]),0); //リージョン座標 
                vector avPos    = llList2Vector(llGetLinkPrimitiveParams(linkNum,[PRIM_POSITION]),0); //リージョン座標
                vector offset   = avPos-rootPos;
                offset = offset2local(offset,rootRot);
                vector adjust = ZERO_VECTOR;
                if(msg=="前に"){
                    adjust = offset2local(llRot2Fwd(avRotG)*myDist,rootRot);
                }else if(msg=="後ろに"){
                    adjust = -offset2local(llRot2Fwd(avRotG)*myDist,rootRot);
                }else if(msg=="左に"){
                    adjust = offset2local(llRot2Left(avRotG)*myDist,rootRot);
                }else if(msg=="右に"){
                    adjust = -offset2local(llRot2Left(avRotG)*myDist,rootRot);
                }else if(msg=="上に"){
                    adjust = offset2local(llRot2Up(avRotG)*myDist,rootRot);
                }else if(msg=="下に"){
                    adjust = -offset2local(llRot2Up(avRotG)*myDist,rootRot);
                }else if(msg=="回る"){
                    avRotL = llEuler2Rot(<0.0,0.0,mySpin>)*avRotL;
                    llSetLinkPrimitiveParamsFast(linkNum,[PRIM_ROT_LOCAL,avRotL]);
                }
//移動調整処理
                if(adjust!=ZERO_VECTOR){
                    offset = offset + adjust;
                    llSetLinkPrimitiveParamsFast(linkNum,[PRIM_POSITION,offset]);
                }
                if(llSubStringIndex(msg,"cm")!=-1){
                    myDist = (float)llGetSubString(msg,0,llSubStringIndex(msg,"cm")-1)*0.01;
                    //すでに設定済みの場合、自分用設定値の変更
                    if(index!=-1){
                        personalSetting=llListReplaceList(personalSetting,[myDist],index+1,index+1);
                    }else{
                        //新たに自分用の設定を登録 
                        personalSetting+=[id,myDist,spin];
                    }
                }
                if(llSubStringIndex(msg,"度")!=-1){
                    mySpin = (float)llGetSubString(msg,0,llSubStringIndex(msg,"度")-1)*DEG_TO_RAD;
                    if(index!=-1){
                        personalSetting=llListReplaceList(personalSetting,[mySpin],index+2,index+2);
                    }else{
                        personalSetting+=[id,dist,mySpin];
                    }
                }
                string msg = "\n"+dMessage0+float2string(myDist*100.0)+"cm\n"+dMessage1+float2string(mySpin*RAD_TO_DEG)+"度";
                llDialog(id,msg,dButtons,dChan);
            }
        }
    }
}