まるくならべる(2)
セカンドライフ技術系 Advent Calendar 2019 のための記事です。
仮想世界セカンドライフの中で「ある目的」のために、たくさんのオブジェクトをきれいに丸く並べたいと思ったので、それを実現するためのわたしの試行錯誤を記事にしてみました。「まるくならべる(1)」の続きです。
ところで、ひなたは何がしたかったのか
そろそろ、何でこのようなことを始めたのか、をお話します。
わたしはDanceBar *CuteGrin* (キューグリ)というアニソンをかけてダンスする店のスタッフをやっています。そのステージに曲に応じていろいろな床やセットを出すのがいつもの仕事です。
▲キューグリのステージに出すセットの例です。曲が始まったら設置、曲が終わったら撤去、みたいなことができないといけないので、このセットはすべてリンクされて1つのオブジェクトになっています。
わたしは、そのステージの中に線路を作って鉄道車両を走らせたいと思ったんです。
セカンドライフの鉄道の規格としてSLRRというのがあります。その規格で作られた線路が仮想世界のいろいろなところにあり、そこを走る車両もたくさん作られています。その線路の仕組みは調べてみると驚くほど単純でした。
▲仮想世界セカンドライフの中の線路。
▲透明なオブジェクトを表示させたところ。赤いのが透明なGuide。
線路の上に透明で細長い角材のような「Guide」という名前のオブジェクトがずっと並べられています。Guide の中にはなにか制御スクリプトでも入っているのかと思いましたが、なんにもはいっていません。車両はただその Guide にそって走っていくだけ、という仕組みでした。
ただ、そのGuideは地面のレールなどほかのオブジェクトとリンクされていてはだめで、単独で存在していないといけないようです。「Guideをたくさん並べて床とリンクした1かたまりのもの」を出してもうまくいきません。たくさんの 「単独のGuide」 をその場でたくさん出して並べないといけないことがわかったので、今回の研究を始めたわけなのです。
もっと大きなまる
キューグリのステージは外径20メートルの円形になっています。ですから、半径17メートルぐらいでGuideを円形に並べればうまくいくと考えたのですが、ここで問題が発生します。それは・・
▲ r=10.0 b=8 で Guide を並べてみました。(説明のため、透明ではなくしている)
Objectを置く(REZする)関数 llRezObject は、距離 10m より遠くにREZすることはできないのでした。
どうやって、半径17メートルにObjectを並べるか?
Rezされたあと、Objectが自分で外側へ移動して半径17メートル地点まで行くようにすればいいのではないかと考えました。
半径10メートルまでは行っているので、あと7メートル外側に動くだけでいいわけです。「動く」距離も1回につき10メートルが限界なのですが、その範囲内なので1回移動でOKのはずです。
まるく移動する
Objectを移動させるときに使う関数は llSetpos です。
llSetPos( vector pos );
pos は移動先の座標 。移動は単独のプリムでは 1 回の呼び出しで 10 m までに制限されている。
で、書いたスクリプトがこちらです。これをREZするObjectに入れます。
float r=7.0; default { on_rez(integer start_param) { vector vRadBase = llRot2Euler( llGetRot() ); float t = vRadBase.z; llSetPos(llGetPos() + <r*llCos(t),r*llSin(t),0.0>); } }
スクリプトの中身を見ていきます。
r はオブジェクトを移動する距離で、7.0メートルに設定。
on_rez(integer start_param)
は、ObjectがREZされたときにその命令を実行する。
llGetRot()
は、「そのObjectが向いている向き」を取得する関数。形式はクォータニオン。
vector vRadBase = llRot2Euler( llGetRot() );
は、 取得した向きのクォータニオンをオイラー角形式に変換して、vRadBase という変数に代入。
t = vRadBase.z
は、そのオイラー角形式のうち、z軸を中心にどれだけ回転しているか、つまり「真上から見たときにObjectの向いている向き」を表す角 θ (tという変数で表しています。)
ですから、REZされた地点(llGetPos()で取得する)の x座標に rcosθ 、y座標に rsinθ を足してやれば、そこが移動して欲しい地点の座標だと考えました。ところが、これを実行してみると・・
こうなっちゃいました^^; これは・・・
llGetRot()で取得したObjectの向きθが、こんな向きだったからだと思われます。
これは、必ずこうなるとは限らなくて、Objectの作り方などでいろいろだと思います。
だから、θ をθ+90° , θ-90° , θ+180° などにしてトライアンドエラーで正解を見つけ出します。
私の場合は、θ-90° にするのが正解だったわけです。
できたまる
というわけで最終的にできたのがこちらです。
中央に置くObjectのスクリプトがこれ。
Guideという名前のObjectも入れておきます。
float r=10.0; integer b = 24; float t=0.0; default { touch_start(integer param) { integer a = 0; for(; a < b; ++a) { t=(360/b * a)* DEG_TO_RAD; llRezObject("Guide", llGetPos() + <r*llCos(t),r*llSin(t),0.0>, <0.0,0.0,0.0>, llEuler2Rot(<0.0,0.0,t+90*DEG_TO_RAD>), 0); } } }
REZするGuideに入れるスクリプトがこれ。
実際に試してみると、rは6.5メートルでちょうどよいことがわかりました。
float r=6.5; default { on_rez(integer start_param) { vector vRadBase = llRot2Euler( llGetRot() ); float t = vRadBase.z; llSetPos(llGetPos() + <r*llCos(t-90*DEG_TO_RAD),r*llSin(t-90*DEG_TO_RAD),0.0>); } }
実行結果がこれです。やっとたどり着きました^^
(実際にはこれに加えて、すぐに片付けるためのキルスクリプトなんかも入れます)
実際にキューグリのステージで鉄道車両を走らせることができました^^
回転半径が16.5メートルというのは実際の鉄道としては急カーブすぎて、大きな長い車両は曲がることができず動けません。走ることができるのは小さくした車両に限られてしまいます。
私が苦心して配置した「Guide」は、透明なので見えません。人に存在を知られることもありません。でも、こうやっていろいろ工夫することで思ったことを実現させるということはとても楽しいひとときでした。与えられたものをただ消費するのではない、セカンドライフの楽しみだと思います。