2011年2月27日日曜日

[LSL] LSLCON 2010 エントリーしちゃいました

2007年から続いていたスクリプターさんの祭典的な LSLCON Japan ですが、昨年は「あれ?ないな?」と思っていたら、3月に開催決定。

公式ホームページ http://lsl-con.org/2010/

2008年の第2回目のときには MLDU4 のベータを展示させていただき、アイテムデモまでさせてもらいました。準備大変でしたけど、楽しかった思い出があります。

http://snumaw.blogspot.com/2008/11/lslcon.html

そして今回、、、またも懲りずに「ベータ版」での参加を。。。

MLDU5 ベータ (またベータwww)

先ほど、申込み完了っと。

2年ぶりのバージョンアップですが、自分の欲しいもの最優先で作っているのと、土日しかスクリプトする時間がない、それも最近土日も怪しい、スクリプトで孤独に作業するよりもお友達と遊んでいるほうが楽しい(完全に言い訳モードwww)などの理由で「出すよ、出すよ」といいながらズルズルと今日まで来てしまいました。ごめんなさい。

メジャーバージョンアップの最大の特徴は「マルチダンス対応」です。って、もう、それしかありません(笑
MikuMikuDance のアニメーション インポートの話は以前のエントリーでもしていますが、シングル アニメーションならまだしも、俗にいうカップルダンス的な2人用のダンスや、3人用、5人用っていうダンスをインポートしたら、それ、動かしたくなるし、かつシンプルに管理して使いたくなりますよね。

基本的な仕組みは簡単なんで、すぐに踊れるように作ったわけですが、、、細かいところでスクリプトの修正をやっていたら、、、1年以上たっちゃった(笑

あらためてスクリプトって奥が深いな~、と思ったのですが、


1) マルチダンスの場合はアバタ―の位置決めが重要 = ダンサー用 Sit ボール必須

2) 違うアニメーションになれば、初期位置も変わる = Sit したダンサーの位置をアニメ毎に動かす仕組みが必要

3) ボールから立ち上がったり、座ったりで自動的に適切なアニメーションを送信しなければならない = Sit ボールとメッセージ交換してダンスを送ったり、止めたり、、、

4) アバターを動かすっていっても前後、左右、上下、回転も考えなければならない = ローテーションしっかり勉強しなければならなかった。。。

5) もちろん、同じエリアに MLDU5 があったら混信しない方法が必須 = Sit ボールとのメッセージ交換チャンネルを変えなければならない(オーナーが違うのは当然ですが、オーナーが同じで複数MLDUっていうケースはあるので、、、)


などなど、ひとつひとつ解決してきたんですけど。。。あまりスクリプトに関しては私が他の人に相談するタイプじゃないので(そのかわり、うちのまわりではスクリプト以外のエリアで多才な人たちがいるわけですが)結構孤独な作業でしたwww

LSLCON 2010 に向けてラストスパート!(こうでもしないと、いつまでにできるやらww)

マルチダンスのサンプルケースはこちら。ほんの一部ですが、使用した曲は BREEZE(カラフル・サウンズ・ポート、唄:祭屋さん)、オリジナル・ダンスは仏壇仮面さんと13さん(かな)。モーショントレースはあっぱれ様です。(使用・利用した作品はいずれも ニコニコ動画内で公開されています)

がんばれ、私! LSLCONの会場でお会いできるのを楽しみにしています~。

2011年1月6日木曜日

Nostalogic を踊らせてみた

あけましておめでとうございます。
今年最初の投稿は、、、動画になりました。
MikuMikuDance の動画で去年ご紹介した [MMD] 魔王エンジェルに Nostalogic を踊ってもらった[修正版] に強く影響をうけまして、、、。
のりさんがトレースした Nostalogic のモーションは Secondlife に移植しても本当にきれいなダンスを踊ってくれます。肩回りにモーションの良し悪しって出やすいのですが、ほぼ変な捻じれが目立つことがありません。前屈したり、腰を動かしたりしても全く違和感がないのがすごいです。多くのモデルが利用できるように作られたのがよくわかります。

撮影は Kirstens の DOF (Depth Of Field) を有効にして動画でもリアルタイムに被写界深度を使っています。ある意味「無駄」なアップなどはアップじゃなくてその背景なんかをみるとぼけてたりします。すごい時代になったものです。。。

YouTube のアカウントが 15分を超える動画もアップできるように昨年末になりました。これって、どうやらそこそこ動画をアップしていて著作権とかに違反していない人が対象になっているみたいなので、yuukiss さんの Nostalogic ってどういうのになってるんだろう、、、これで引っかかったら、せっかくの権利なくなるんだろうな(と言っても15分以上の動画って、、、作れないwww)と心配になり JASRAC でみてみたら。。。。あるんですねぇ。管理・信託状況が「無信託」で。とりあえずJASRACはクリアしていることだけでもわかって良かったぁ。(といってもYouTubeはJASRACとは包括契約してるから、、、本当の問題は原曲をそのまま使えるかどうかなんですけどね)

すばらしい曲とスムーズなモーションン、、、動画スキルがともなわないけど作らせていただきました。F-San の魔王エンジェルのリスペクトでもあります。

セットとか衣装作ってくれたり、クラブ貸してくれたり、撮影中暖かく見守ってくれたみんなありがとうございます。

[追記]
うわー!モーション作成された のり さんから YouTube にコメントきたーw感激!!!

2010年12月26日日曜日

かっこいい動画

早いですね、、、1年って。で、最近はまってるのがニコニコ動画の「魔王エンジェル」の動画。

もともとアイマスっていうか、ニコマスの魔王エンジェル系の P さま系はすごすぎて、でも、カメラワークとかむっちゃ参考になるんですよね。

2010年12月4日土曜日

Viewer2 対応 ブーツ

そろそろ 1.x 系のサードパーティ ビューワーでもアルファ マスクのサポートしはじめたし、特にブーツで使われているインビジブルプリムだと、、、場所によっては透けちゃって、きれいじゃないんですよね。
Boots01
ということで、そろそろ Viewer2 対応のブーツを買ってみることにしました。今回は PixelFashion さんのとこの「Ultimate Boots black(SL 2.0 ready)」 550L$ を購入してみることに。

こちらのお店のブーツは以前いくつか購入したことがあり、テクスチャがかなりきれいなブーツでした。ただし、インビジブルプリムを多用して整形しているため、微妙にブーツのまわりに「透ける」部分が出ていました。
それらの SL 2.0 ready がでたら、、、、買いなおすとは思いますが。。。
で、上のブーツを購入して履いた絵がこちら。
Boots02
ですよね~。
お気に入りの BAX の Ankle boots も SL 2.0 readay にならないかなぁ、、、。

[NeurolaB Inc.] Ultimate Boots black (SL 2.0 ready)
https://marketplace.secondlife.com/p/NeurolaB-Inc-Ultimate-Boots-black-SL-20-ready/504191

BAX Ankle Boots Black Patent
https://marketplace.secondlife.com/p/BAX-Ankle-Boots-Black-Patent/424098

[追記] V2.0 アルファマスクサポートの BAX Ankle でましたー
Second Life Marketplace - BAX Ankle Boots Black Leather

2010年11月29日月曜日

Kirstens で被写界深度 リアルタイムで焦点ぼかし

Kirstens Viewer の作者さんのページで最新の KL Viewer の SS を見てたら「?」と。前にもご紹介していた GIMP の Focus Blur を使わずして、リアルタイムに背景がぼけてるように見えるんです。



KL すごいし。。。
環境設定のグラフィックにある [Lighting and Shadows] をオンにすると、、、影だけじゃなくて、ブラ―までもかかるんですね。

Blur001

ちょっと触っただけなので、まだどのような調整ができるかわからないのですが、Viewer もいろいろと進化していることは確かですね。

[追記]
SL Depth of Field 01

Kirstens の場合は設定項目がありますね。
[Graphics] - [Advanced] - [Misc] です。
Camera COC、CameraFNumber、Camera Focal Length です。

焦点距離と絞り、とか、そんなキーワードで検索するといろいろ解説がでます。
カメラが寄った時だけ、背景がぼけるような設定にしておくと、あまりうるさくないかも。

KL Depth Of Field
KL Depth of Field and Shadow Effect

2010年11月8日月曜日

Rigged Mesh すごいですね

敷居は相当高いようなのですが、Rigged Mesh を使ったアバタ―制作の事例が YouTube や個人のブログで公開されはじめていますね。

Rigged Mesh : http://wiki.secondlife.com/wiki/Mesh/Mesh_Walkthrough:_Rigged_Mesh_Upload

Secondlife の先駆者の方々には元々 3DCG のエリアでご活躍されていた方も多くて、その方々にすると「やっと」という感もあるのでしょうが、それでもこの対応の早さは日本のユーザーがとびぬけているようにも思えます。









私には到底無理なんですが、すでにあるモデリングツールの利用や COLLADA データフォーマットの採用といった点がこのようなことを可能にしているんですね。

技術的/非技術的な課題・問題も山積みですが、どこまで進化していくのか楽しみです。セカンドライフ、いろいろ言われ続けていますが、このようなサービスで「もう、やーめた」と言わず継続している数少ないサービスのひとつなので引き続き進化してほしいのと、やっぱり CGM の要素を大切にし続けてほしいですね。

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

2010年10月14日木曜日

[LSL] 位置と回転について ~クォータニオンと移動量~

前回からの続き、、、ではなくて、ちょっと閑話休題。といっても、今回の一連のお話しの中で、私が勝手に造語的に使っている「移動量」って何?の補足みたいなお話しです。

[LSL] 位置と回転について ~移動量という考え方~ のエントリーで、球体で考える空間と位置指定の概念を説明するのに、地球の話を持ってきて「移動量」の話をしていますが、この移動量っていうのが、やっぱりよくわかんない、、、と感じる方も多いと思います。

移動量(正直、これ私が勝手にそう呼んでいるので、一般的な用語ではないことをご了承ください。)はクォータニオンそのものなんですが、イメージとしては地球上のある地点から他の地点までの最短距離(大圏航路)で、ぐるっ!と地球をまわして、一度の回転でA地点からB地点にいって、回転させる軸の傾きも含めたもの、、、としつこく書いています。

ふ~ん、そうなんだ、と思ってくれるといいのですが、「ん~、わかんない」となると、実は先に進めないのも事実。それが2年前の私の状態ですから(笑
イメージ、イメージっていってもしょうがないので、もうちょっと具体的に説明してみたいと思います。ただし、なぜこれまで具体的に説明していないか、、、といえば、最終的には必要になる場合もありますが、最初のほうでは具体的に理解する必要があまりない(というか邪魔な?)知識だと思ったためです。あ、もちろん、ここでも行列計算、虚数といった数学的な話にはなりません。

前のエントリーでも「羽田空港とJFK空港を結ぶ大圏航路でグルッと回す」、それが移動量って言い方をしていますが、一気に回すための傾いた軸って存在しますよね。

クォータニオンからは、まず、その軸の方向を導き出すことができます。その関数が llRot2Axis なんです。そして軸を使ってまわした角度が llRot2Angle なんです。

Rot2AxisAngle2
( 絵心無いわ・・・わかってくれるといいけどw )

この軸の傾き(方向)と回る角度が移動量つまりクォータニオンを構成する要素なんですよね。
実際、Axis のベクターと、Angle の float からクォータニオンを計算する関数が llAxisAngle2Rot になるわけです。これでクォータニオン(移動量)を算出できるのです。

ネットや書籍でクォータニオンを調べると、そもそも LSL で使われる関数などでは説明されませんから、四元数で、実数部分と虚数部分にわかれ、、、、と説明されます。クォータニオンはローテーション型で <x, y, z ,s> の四元数にて LSL でも表現されますが、現実にその x, y, z, s の要素を直接いぢることは、最初の頃はほぼ皆無です。ちなみに2年前に私は ウィキペディアの四元数や、クォータニオンの説明を見てしまい、頭痛&挫折しそうになりましたし、wiki.secondlife.com での llRot2Axis や llRot2Angle の説明では、それらの関数と同じ動きをする llAcos や llFabs, llSqrt, llVecNorm で四元数の要素をいじるユーザー関数を紹介しています。ですが、その特記事項を見ても何を言っているのかわからなかったわけです。

実際はウィキペディアにある内容をもとにクォータニオンは計算され、四元数の各要素となって表現されるのですが、それを理解するためには数学の勉強は必須で、ちょっと敷居が高かったのです。(私にとっては、、、ですが)

このクォータニオンと軸の傾きと回転量の関係がわかっていれば、他のところでも応用可能なような気がしませんか?

回す軸の方向はわかっていて、何度まわしたい、、、ってあるでしょう?おおよそローカル軸が「回す軸の方向」なので、あまり Axis と Angle の関係を理解せずにz軸で回すなら <0.0, 0.0, PI/2> とかにして、llEuler2Rot を使ってクォータニオン(移動量)にして、現在の Rotation に左からかけたりする、、、。でも、ローカルの軸、もしくはグローバルの軸じゃなくても、llAxisAngle2Rot で任意の軸で回すことができる、、、と考えると、ちょっとどこかで使えそうな気がしません?

クォータニオンについては、最初の回で紹介したように、また、何度もしつこくいっているように「球体で考える空間と位置指定」の概念をきちんと持つことで、行列計算や三角関数を深く知らなくても、LSL の関数だけでほとんどの操作は克服できると思っています。その上のステップに行くときに、そこで必要な知識を吸収すればいいわけですよね。

さて、次はオブジェクトに座っているアバタ―を動かす(それも複数のアバタ―)です。

2010年10月12日火曜日

[LSL] 位置と回転について ~ ルートプリムと子プリムたち(2) ~

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

簡単な例を使ってみてみましょう。
 rotation1
下の平べったい直方体がルートプリムです。直方体の上方向に立方体をリンクしました。これが子プリムになります。この子プリムの立方体を 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]);
        }
    }
}

結果は、ルートに傾きがなければうまくうごきますが、ルートが傾くと以下のようになります。
rot10

そこで、q の計算を


q = targetQ*childRot/llGetRootRotation()/llGetRootRotation();

のようにルートプリムのローテーションで2回除算する、に変えると、ルートが傾いても 45度 ローカルZ軸を中心に回ります。

rot11

画像では小さいですが、ローカル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(),"");
        }
    }
}

2010年10月11日月曜日

[LSL] 位置と回転について ~ ルートプリムと子プリムたち

前回ではリージョン座標軸で表現される位置をクォータニオンと球体の中心からの距離で置き換える方法をご紹介しました。空間の概念は X, Y, Z 軸による立方体だけでなく、球体での移動量と球体の中心からの距離で表現できる「球体で考える空間と位置指定」の概念の重要性がご理解いただけたかな、と思います。

ここまででオイラー角による回転について1度も触れていないのは面白いですよね。移動量という言葉を使って「角度」という言葉を使っていないわけです。

プリムをまわす、回転させるときに一番最初に覚えるのは軸を中心とした回転ですよね。わたしもそうでした。Z軸で90度回転させる、なんてことをやるわけで、そのときラジアンによる角度表現が必要である、とか、llEuler2Rot という関数を知ったり、右ねじの法則、グローバル軸を基にした回転と、ローカル軸を基にした回転の違い、その方法を学ぶわけです。

もちろんオイラー角によるプリムの回転で事足りことも多いのですが、スクリプトでいろいろな操作をやろうとすると、はじめから角度がわかっていることって、、、少ないと思います。llGetPosllGetRotllGetObjectDetails, llGetLinkPrimitiveParams などで取れる数値は位置を表すベクター型であり、移動量をあらわすローテーション型のクォータニオンです。なので、位置ベクトルとクォータニオンを駆使して操作することになり、あらかじめ各軸の角度がわかっていれば使えるオイラー角による回転指定は、、、、実は、かぎられるような気がします。

前置きが長くなりましたが、今回は llSetLinkPrimitiveParams などで必須となるルートプリムと子プリムの関係、とくに位置(ローカルポジション)と向き/回転(ローカルローテーション)について考えてみたいと思います。

4. ルートプリムと子プリムたち

複数のプリムをリンクさせるとき、スクリプトを組み込む必要がなければ、あまりルートプリムがどれかを意識しないかもしれませんが、スクリプトをいれてなんらかの操作をしようとすると、ルートプリムがどれか、子プリムでもリンク番号は何番かが非常に重要になります。

基本的なリンク、リンクのさせかたについては wiki の記事や先輩諸氏のブログなどを参照してもらって、ルートプリムに入っているスクリプトから子プリムたちをどのように扱うかを見てみます。

ポイント1 リンクされた子プリムは「ルートだけ」とつながっている

ちょっといきなりハードルが高いかもしれませんが、プリムをリンクさせたとき、子プリムはルートプリムに対してリンクされている、ということを忘れるときがあります。1対1なら直感的ですが、子プリムの数が多くなり、造形的に階層構造のようなオブジェクトになったとき、見た目で土台になっているプリムを動かすと、それに「見た目」でのっているプリムが動きそうな気になるんですよね。

linkedprims

上の絵の緑色のルートプリムを動かせば、リンクされている子プリムたちはすべて追随するのですが、青いプリムを動かしても、見た目その上にある赤や黄色のプリムは動かない、、ということです。上のような単純なオブジェクトだと「当たり前じゃん」と思うでしょうが、造詣をしていって、見た目が子プリム同士関連しているように見えれば見えるほど、ルートだけにしかリンクしていないことを忘れます(笑

それが顕著にでるのが、プリムに座ったアバターを動かすときです。

どうしても座っているプリムを動かせば、座っている(=リンクしている)アバターも動くような気がしてしまいますが、アバターはルートにリンクしているであって、見た目座っているプリムとはリンクしていないのです。

ポイント2 子プリムの位置・回転はルートプリムが「基準」です

ですよね~、と思われるかもしれませんが、わたしも最初にはまった落とし穴は、ルートプリムのローカル座標軸(前後、左右、上下)がグローバル座標軸(東西、南北、天地)と重なっているのがおおよそ Rez した直後の初期値なので、グローバル座標軸を基準とした回転や位置指定を子プリムに対して行い、最初うまく動いている!と誤解してしまうことです。

うまく動いている状態だったのに、オブジェクトの向きを変えたら、予想もしない方向、位置に子プリムが動いた、、、となり、それが予想もつかない動きをするので悩んでしまう、、というものです。

sittingOnChair0

sittingOnChai1

ポイント3 ルートを基準とした位置の算出方法は球体で考える

正直、、、わたしは相当苦労しました。。。前に「位置と回転」について書いたブログが 2008 年のもの、、、当時はクォータニオンを理解しようとして行列計算や、関連しそうなネットの情報、書籍を読んでみて、興味深い内容が多かったものの、それぞれを理解したとはいえませんでした。

最近は wiki.secondlife.com のコンテンツも有志の方による日本語訳が充実してきているので、そこからある程度「こんなものかな」で理解してしまうほうがいいかもしれません。

ルートと子プリムの関係は、これまでご説明してきた「球体で考える空間と位置指定」の概念がわかっていれば比較的簡単に必要な数値の算出が可能になります。(なると思っています、、、)

まず、ルートから見た子プリムの相対位置です。
llGetLinkPrimitiveParams もしくは llGetObjectDetails で子プリムのグローバル座標を取得することができます。ルートプリムのグローバル座標もこの関数以外に llGetPosllGetRootPosition で取得できるので、ルート、子プリムそれぞれのグローバル座標をもとに、ルートプリムからルートのローカル座標(前後、左右、上下)による子プリムまでの「軸上の移動距離」を算出することになります。ただし、この算出の場合も「球体で考える空間と位置指定」を使うことになります。

え?子プリムのグローバル座標からルートプリムのグローバル座標を引けばいいんじゃない?と思いたくなるのですが、その差分はグローバル座標軸における差(東西、南北、天地)であって、ルートプリムからのローカル座標軸(ルートプリムからの前後、左右、上下)での差分ではないのです。グローバル軸とローカル軸が重なっていれば問題ないですが、ずれた(ルートが回転した)瞬間から予期しない動きをしてしまいます。

ルートプリムから見た子プリムのローカル座標上の移動距離を算出する考え方と手順が以下になります。

a) 球体で考える空間と位置指定の概念を使ってルートプリムを球体の中心にもってくるイメージをしてみます

そのときのルートプリムの向きは llGetRot でとったそのままの傾きで、子プリムと一緒に動いてくるイメージです。球体の中心にきた時は、ルートプリムのローカル座標軸とグローバル座標軸はずれている感じです。そしてそのずれは移動量であり、ルートプリムのローテーションになります。

//ルートプリムのクォータニオン
rotation rootRot = llGetRot(); または llGetRootRotation();

b) 中心(ルート)から子プリムまでの直線距離とグローバル座標軸上の移動距離を算出します

グローバル座標軸における2つの位置ベクトル間の距離です。

vector rootPos  = llGetPos(); または llGetRootPosition();
integer linkNum = 2; //ルートは1、それ以降は2~ 
list pList  = llGetLinkPrimitiveParams(linkNum,[PRIM_POSITION]);
vector childPos = llList2Vector(pList,0); 
float dist = llVecDist(rootPos, childPos);

グローバル座標上の子プリムの位置とルートプリムの位置の差分。つまり、ルートプリムが中心のときにグローバル座標上の子プリムの位置ベクトルになります。

vector childVec = childPos-rootPos;

c) ルートプリムをぐるりと回転させて、ルートプリムのローカル座標軸とグローバル座標軸を一致させるイメージを持ってみます

この時の移動量は a) の rootRot だけ「戻る」感じですよね。
ただし、このとき「移動」するのは実は子プリムなんです。ルートプリムは中心にいるので一切「移動」しないわけです。
なので、動かす前の子プリムの移動量を算出します。これは子プリムの向きを表すローテーションではありません。球体で考える空間の話を思い出してください。球体の中心(=ルートプリム)とX軸基準 <1.0, 0.0, 0.0> と自分のグローバル座標上の位置ベクトルから算出する移動量です。自分の位置ベクトルはグローバル座標上の XYZ であり、ルートプリムは球体の中心にきていますから、b) で算出した childVec になります。

rotation childRot = llRotBetween(<1.0,0.0,0.0>,childVec);

そして、ルートのローカル軸をグローバル軸にあわせるために、子プリムの移動量からルートの傾き分の移動量を引きます。(ルートプリムがぐるりとまわって元に戻る感じです。)ローテーションでの計算では除算します。ここがルートと子プリムの関係の最大のポイントです。

rotation q = childRot/rootRot;

これで移動量 q は、ルートのローカル軸、グローバル軸が重なった時点での球体上の子プリムの移動量となります。

d) ローカル座標の位置ベクトルを算出します

ローカル座標とグローバル座標は一致しているので、<1.0, 0.0, 0.0> と llRot2Fwd と使ったこれまでと同じ方法で算出します。

vector offset = llRot2Fwd(q)*dist;

この offset ベクターがルートプリムからの相対位置になります。
どうです?三角関数とか意識しなくても出せるような気がしませんか?
重要なのはイメージなんですよね。

と、、、ちょっと長くなったので、ルートプリムから見た子プリムの向きについては次回にします。