回転(非物理)

LSLの中でもかなり悩むのが、回転の制御です。
回転には、建造モードでおなじみのオイラー角の他に、ラジアン角と四元数があり、どれも同じ角度でも表記が違います。
残念ながら、私は数学の専門家では絶対ありませんので、それぞれの表現についての詳しいことは各自でお調べください…。
ここではLSLによる回転制御をを見ていきましょう。

イベント

at_rot_target(integer tnum, rotation targetrot, rotation ourrot)
{
    内容;
}

オブジェクトが到達目標角度に達した時に発動します。

引数:
integer tnum
llRotTargetの管理番号が入ってきます。
複数のllRotTargetが設定されている時、どの関数の条件が一致したのかを調べることができます。
rotation targetrots
llRotTargetで指定した目標角度と同じ角度が入ってきます。
rotation ourrot
オブジェクトの現在の角度が入ってきます。

integer targetID;

default
{
    state_entry()
    {
        targetID = llRotTarget(<90, 0, 0, 1>, 1 * DEG_TO_RAD);
    }

    at_rot_target(integer tnum, rotation targetrot, rotation ourrot)
    {
        llSay(0,"目標角度です");
    }
}

オブジェクトを目的の角度に動かした時にイベントが発生します。
必ずllRotTargetと組みで使います。


not_at__rot_target()
{
    内容;
}

オブジェクトが到達目標角度に達していない時に発動します。

引数:
なし

integer targetID;

default
{
    state_entry()
    {
        targetID = llRotTarget(llGetRot(), 1 * DEG_TO_RAD);
    }

    not_at_rot_target()
    {
        llOwnerSay("設置角度が変わりました");
        llRotTargetRemove(targetID);
    }
}

llRotTargetで指定した目標角度にオブジェクトがない時に、連続でこのイベントが発生します。
そのため、うっかり使うとサーバーに負荷なだけでなく、Say系の発言を行うと画面中に発言が連続で流れてしまいます。
用途としては、動かしてはならないものが動かされた場合に警告を出す、くらいでしょうか。


関数

integer llRotTarget(rotation rot, float errot);

オブジェクトの到達目標角度を設定します。

戻り値:
integer 変数
llRotTargetの管理番号を変数で指定します。

引数:
rotation rot
目標角度をローテーション型で指定します。
float errot
目標角度の誤差を小数型で指定します。
単位はラジアンです。

integer targetID;

default
{
    state_entry()
    {
        targetID = llRotTarget(<90, 0, 0, 1>, 1 * DEG_TO_RAD);
    }

    at_rot_target(integer tnum, rotation targetrot, rotation ourrot)
    {
        llSay(0,"目標角度です");
    }
}

オブジェクトに目標角度を決めたい時に使います。
例えば、あるキーアイテムを決まった角度に回すと仕掛けが動く、などのゲーム的要素でしょう。


llRotTargetRemove(integer number);

オブジェクトの到達目標角度を解除します。

戻り値:
なし

引数:
integer number
llRotTargetの管理番号を変数で指定します。

integer targetID;

default
{
    state_entry()
    {
        targetID = llRotTarget(<90, 0, 0, 1>, 1 * DEG_TO_RAD);
    }

    at_rot_target(integer tnum, rotation targetrot, rotation ourrot)
    {
        llSay(0,"目標角度です");
        llRotTargetRemove(targetID);
    }
}

必要が無くなったllRotTargetを解除します。
特に、not_rot_at_targetを設定した場合は必ず使用しましょう。


rotation llGetRot();

現在のプリムの角度を調べます。

戻り値:
rotation 変数
角度をローテーション型で返します。

引数:
なし

default
{
    touch_start(integer total_number)
    {
        rotation rot;
        rot = llGetRot();
        llSay(0,(string)rot);
    }
}

非常に基本的かつ重要な関数です。
ここで調べられるのは、それぞれのプリム角度です。
ただし装備品ではアバターの角度になります。


rotation llGetLocalRot();

現在の子プリムの角度を調べます。

戻り値:
rotation 変数
角度をローテーション型で返します。

引数:
なし

default
{
    touch_start(integer total_number)
    {
        rotation rot;
        rot = llGetLocalRot();
        llSay(0,(string)rot);
    }
}

非常に基本的かつ重要な関数です。
ここで調べられるのは、リンクオブジェクトのルートプリムからの相対角度です。
また、ルートプリムや単体プリムで使った場合は、例外的にllGetRotと同じです。
ただし装備品ではアバターからの相対角度になるので注意してください。
アバターには装着位置がたくさんありますが、なぜか、それぞれの装着位置の角度を調べることができません。
また、あくまで相対角度のため、例えばルートプリムからXが90度回転した子プリムでは、装着位置がどこだろうと、常に角度はXが90度です。


rotation llGetRootRotation();

現在のオブジェクトのルートプリムの角度を調べます。

戻り値:
rotationr 変数
角度をローテーション型で返します。

引数:
なし

default
{
    touch_start(integer total_number)
    {
        rotation rot;
        rot = llGetRootRotation();
        llSay(0,(string)rot);
    }
}

ここで調べられるのは、リンクオブジェクトのルートプリムの角度です。
ただし装備品ではアバターの角度になります。
llGetRotに比べると正確にアバターの角度を調べるようですが、その誤差は微々たるものです。


llSetRot(rotation rot);

プリムの角度を設定します。

戻り値:
なし

引数:
rotation rot
角度をローテーション型で指定します。

default
{
    touch_start(integer total_number)
    {
        rotation rot;
        rot = llGetRot();
        llSetRot(rot * <45, 0, 0, 1>);
    }
}

非常に基本的かつ重要な関数です。
オブジェクトに新たな角度を絶対角度で指定します。

位置を表すベクター型と角度を表すローテーション形では、計算方法が違います。
ベクター型では、普通にベクター同士、またはそれぞれの成分で、足し算や引き算で増減を行います。
しかし、ローテーション型では、ローテーション同士、またはそれぞれの成分において、掛け算または割り算で増減を行います。
なぜそうなるのかは、四元数の専門で調べるとして、とりあえず掛け算と割り算で増減と覚えてください。

また、ローテーション型では左辺と右辺を入れ替えると結果が変わります。
具体的には、「元の角度を左辺、増減したい角度を右辺、で計算する」と絶対角度、「増減したい角度を左辺、元の角度を右辺、で計算する」と、ローカル角度で回転します。
これは覚えておかないと、いつまで経っても思ったように回転しないことになります(私も丸一日悩んだことがあります)。


llSetLocalRot(rotation rot);

子プリムの角度を設定します。

戻り値:
なし

引数:
rotation rot
角度をローテーション型で指定します。

default
{
    touch_start(integer total_number)
    {
        rotation rot;
        rot = llGetLocalRot();
        llSetLocalRot(rot * <45, 0, 0, 1>);
    }
}

非常に基本的かつ重要な関数です。
子プリムに新たな角度を相対角度で指定します。
ベクターとは違い、ローテーションにはローカル指定の角度設定があります。
ローカルなので、元の角度をllGetLocalRotで取得する必要があります。
計算方法については、llSetRotと同じなので、注意が必要です。


llTargetOmega(vector axis, float spinrate, float gain);

オブジェクトをスムーズに回転させ続けます。

戻り値:
なし

引数:
vector axis
回転の軸をベクター型で指定します。
単位ベクトルなので、各成分は1を指定すれば十分です。
float spinrate
回転速度を小数型で指定します。
速さはラジアン/秒になります。
float gain
物理オブジェクトのみ作用する回転の強さを指定します。
非物理では1のままです。

default
{
    state_entry()
    {
        llTargetOmega(<0, 0, 1>, TWO_PI, 1);
    }
}

回転看板などでお馴染みのオメガ回転です。
物理で使うことはほとんどなく、その場でクルクルと回転させる看板の用途がメインになります。
回転処理は完全にクライアント処理なので、サーバー上ではオブジェクトは静止したままとなります。
そのため、最も負荷が低い回転命令となります。
また、個々のクライアント依存なので、その見え方は人によって違う事を覚えておく必要があります。

回転速度はラジアン単位のため、2π(TOW_PI)を指定すると1秒間に1回の回転になります。
リンクオブジェクトの場合、ルートプリムにこれを入れるとオブジェクト全体が、子プリムに入れるとその子プリムのみが回転します。

この関数はllSetTextと同じく、スクリプトを消去しても効果は消えません。
そのため、止めるにはllTargetOmega(<0, 0, 0>, 0, 1);と書き直す必要があります。


rotation llEuler2Rot(vector vec);

オイラー角を四元数に変換します。

戻り値:
rotation 変数
指定したベクター型角度がローテーション型に変換されて入ってきます。

引数:
vector vec
変換したいベクター型角度を指定します。

default
{
    touch_start(integer total_number)
    {
        rotation rot = llGetRot();
        rot = rot * llEuler2Rot(<90, 0, 0> * DEG_TO_RAD);
        llSetRot(rot);
    }
}

非常に基本的かつ重要な関数です。
馴染みのあるベクター型オイラー角を、直感でわかりにくい四元数に変換します。
これにより、建造モードと同じ感覚で角度指定ができるようになります。
ただし、指定するベクター型がラジアン角でなければなりません。
ラジアン角は180度を1πとする表現方法で、1度=約0.017453ラジアンです。
もちろんこれではお手上げなので、ラジアンに変換する定数DEG_TO_RADを掛けてから指定します。
llEuler2Rot(<○, ○, ○> * DEG_TO_RAD);という書き方は、角度指定をする時の決まり文句として覚えましょう。


vector llRot2Euler(rotation q);

四元数をベクター型に変換します。

戻り値:
rotation 変数
指定したローテーション型角度がベクター型に変換されて入ってきます。

引数:
vector vec
変換したいローテーション型角度を指定します。

default
{
    touch_start(integer total_number)
    {
        rotation rot = llGetRot();
        vector vec;
        vec = llRot2Euler(rot)*RAD_TO_DEG;
        llSay(0,(string)vec);
    }
}

非常に基本的かつ重要な関数です。
直感でわかりにくい四元数を、馴染みのあるベクター型に変換します。
これにより、現在のオブジェクトの角度を、建造モードと同じ感覚で確認できるようになります。
ただし、変換後のベクター型がラジアン角になっています。
ラジアン角は180度を1πとする表現方法で、1度=約0.017453ラジアンです。
もちろんこれではお手上げなので、ラジアンをオイラー角に変換する定数RAD_TO_DEGを掛けてやります。
llRot2Euler(<○, ○, ○, ○>) * RAD_TO_DEG;という書き方は、角度を確認する時の決まり文句として覚えましょう。


回転スクリプト サンプル
rotation close_rot;
vector open_rot = <0, 0, 90>;

default
{
    state_entry()
    {
        close_rot = llGetRot();
    }

    moving_end()
    {
        close_rot = llGetRot();
        llOwnerSay((string)close_rot);
    }

    touch_start(integer total_number)
    {
        rotation set_rot = llEuler2Rot(open_rot * DEG_TO_RAD);
        llSetRot(close_rot * set_rot);
        llSetTimerEvent(10.0);
    }

    timer()
    {
        llSetTimerEvent(0.0);
        llSetRot(close_rot);
    }
}

タッチをすると90度回転するスクリプトです。
moving_endを使って、設置場所が変わってもタッチ後の角度は設置角度+90度です。
ただし、角度だけ変えた場合ではmoving_endが反応しないことにも注意してください。