Adsense

2011年12月6日火曜日

[LSL] llSetKeyframedMotion を使ってみた

この新しい関数、オブジェクトをスムーズに動かすことができるようで、いろんなところで使えそうなんですけど、ちょっとクセがありますね~。
単純な板を動して検証して、気が付いたことをメモしておきます。

と、、、llSetKeyframedMotion ってなに?という方はこちらの YouTube のビデオをご覧いただくと何となくわかるかも~。

1) おまじない(?)

wiki によれば、llSetKeyframedMotion を使う前に Prim の属性で [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX] を指定してあげる必要があるようです。ビューワーが mesh を読み込むできない場合は必要、、、とありますが、とりあえずこのおまじないは state_entry あたりで行っておきます。

単体プリムの場合は llSetPrimitiveParams で、複数プリムがリンクされているオブジェクトの場合は llSetLinkPrimitiveParamsFast で設定をします。なお、複数プリムがリンクされている場合、ルートプリムにしか影響しない(=必ず全部のプリムが動く)ようです。

あと、プリムの属性という意味では物理属性はダメです。非物理のプリムにしてください。

2) 位置、回転、移動時間

llSetKeyframedMotion は2つのリスト(keyframes と option)を引数としています。最初のリストは [位置、回転、移動時間] を設定します。キーフレームといわれているのは、この 位置、回転、移動時間 をリストの中で複数指定できる、それぞれをキーフレームとして、そこまで移動・回転を直線補間してスムーズに動かす、、、という意味です。

で、位置や回転なんですが、グローバル座標軸を基準とした現在位置からの「差分」の指定になります。SIM の東西南北天地の軸に沿った現在位置からの移動距離を指定することになります。当然回転もグローバル座標軸で回転します。もちろんラジアンでの指定になります。

移動時間は 0.1秒以上でなければなりません。また、この秒数指定をより正確にするためには、秒指定よりもフレーム数の倍数指定がよい、と紹介されています。どうやら 1 秒間に 45 フレームで移動させているようです。そのため、1/45 の倍数で秒を指定したほうがよさそうです。たぶん、キーフレームの考え方からすると、A地点からB地点へ2.0秒で移動、と指示した場合はA地点~B地点の距離を 90 分割(2秒なので 90 フレーム)して移動させているようですね。

この倍数じゃないと微妙にずれていくのでしょう。なぜなら、次の移動地点へは「今の位置」からの差分をとるように動いているからです。ちょっとのずれが積み重なって最後は大きくなる、、、っていうわけですね。たとえば、1.5秒という指定は、45*1.5=67.5 フレームで、0.5 フレームというのはないので、67フレームか68フレームとして45で割ってあげると、67フレームの場合は 1.4889秒、68フレームの場合は 1.5111秒です。これを LSL で書くと、

float time = 1.5;
integer frames = (integer)(time*45);
time = frames/45.0;

実際にこれをやると time は 1.488889 秒 (67フレーム分)になりますね。

3) オプション

まず、オプションの種類として3つあります。

・KFM_MODE

モード指定で、指定できるのは ループ、リバース(逆順序)、フォワード、ピンポンです。これは keyframes で指定した複数のキーをどのように扱うか、ということですね。
[A, B, C] と keyframes を指定した場合、何も指定しなければ既定値の KFM_FORWARD が使われ、A –> B –> C と動く、KFM_REVERSE だと C –> B –> A、ループだと A –> B –> C –> A –> B –> C –> A.. と繰り返し、KFM_PING_PONG だと A –> B –> C –> B –> A –> B –> C.. と繰り返すわけです。(正確にはループ/ピンポンの場合は 4) で書いているように初期位置に戻ってから、移動分 A を行います)

・KFM_DATA

これは keyframes で指定するデータの種類を指定するオプションのようです。KFM_DATA オプションを使わない場合は必ず <ベクター型の位置>, <ローテーション型の回転>, float 型の秒数 の3種類を指定しなくてはなりませんが、位置だけでいいや、という場合は KFM_DATA, KFM_TRANSLATION というオプションをつければ <ベクター型の位置>, float 型の秒数 というように回転の指定を省くことができるようです。同様に KFM_ROTATION は回転指定と秒数指定のみで、位置指定を省くことができるわけですね。

実際はこのオプションなしで、ZERO_VECTOR や ZERO_ROTATION 使いそうですけど。

・KFM_COMMAND

おもしろいオプションです。アイディア次第でいろいろな適用箇所があるかもしれません。モーション移動途中で中断 (KFM_CMD_PAUSE) させたり、再開始 (KFM_CMD_PLAY) させたりできます。停止 (KFM_CMD_STOP) させるとリセットされるので、次に再開始 (KFM_CMD_PLAY) すると一番最初の keyframes から開始させます。。。。って、これ、はまりそうなポイントなんですけどね。

このオプションは他のオプションと一緒に使えないことに注意です。keyframed のリストも指定できません。なので使い方は、

llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_PAUSE]);

のような使い方になります。

4) 利用上のポイント、注意点 (?)

といっても、、、まだそんなに使いこんでいるわけじゃないので偉そうなことは言えないのですが、、、

a) 障害物があった場合は・・・・

非物理や土地などは突き抜けますが、物理オブジェクトやアバタ―がいると「衝突」が発生します。アバタ―を push するときもあれば、オブジェクトがアバタ―につっかかってしまって止まるときもあります。そこからキーフレームで指定している分だけ移動するので期待しない位置にいく可能性がありそうです。

b) なんとなくパーティクルと一緒な、、、

空のリストを使ってあげると完全にクリアになりますが、それをしないとセットした属性というかプロパティというか、情報はプリムに残っていますね。

llSetKeyframedMotion([],[]);

llParticleSystem のクリアも確かそんな感じでしたよね。

c) 初期位置は、、、、グローバル座標で覚えているみたい

llSetKeyframedMotion でキーフレーム モーションを設定した時点の「位置」を覚えているようです。KFM_MODE, KFM_LOOP でキーフレーム移動をループさせると、たとえば上の a) のような障害物があってずれていっても、LOOP で最初の位置に戻ってから、A->B->C と動きます。なので、A->B->C と設定したときは、llSetKeyframedMotion をコールしたときの位置 i から、i –> A –> B –> C –> i –> A –> B –> C,,,, と動きます。

ピンポンも、i –> A –> B –> C –> B –> A –> i –> A –> B ...と動くみたいですね。

今は通常の SIM でも利用できるんじゃないかな? わたしの SIM では使えました~。

//llKeyframedMotion のサンプル。左に5m、前に5m、右に5m の移動をピンポンモードで。

integer flg;
list kfList = [<0,0,0>, 3, <0,5,0>, 3, <5,0,0>, 3, <0,-5,0>, 3];

init(){
        flg = FALSE;   
        llSetKeyframedMotion([], []);
        llSetKeyframedMotion(kfList,[KFM_DATA,KFM_TRANSLATION,KFM_MODE,KFM_PING_PONG]); 
        llSetKeyframedMotion([],[KFM_COMMAND,KFM_CMD_STOP]);
}

default
{
    on_rez(integer start_param){
        init();   
    }
   
    state_entry()
    {
        llSetPrimitiveParams([PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
        init();
    }
       
    touch_start(integer num_detected){
        if(flg){
            flg=FALSE;
            llSetKeyframedMotion([],[KFM_COMMAND,KFM_CMD_PAUSE]);
        }else{
            flg=TRUE;
            llSetKeyframedMotion([],[KFM_COMMAND,KFM_CMD_PLAY]);
        }
    }
}