2009年7月27日月曜日

セカンドライフ内の最高速度

腰痛で死んでます。

昨日の夕方から、腰を動かすと猛烈に痛くなり、今日は休んで整体に行ってきました。整体の先生曰く「来てますねぇ」と(笑。という訳で今日と明日は休んで、腰の治療に励みますといっても、温和しくしているしているだけなのですが。

最近セカンドライフ内の物理エンジンについて、ちょっと疑問なところがあって調べていたのですが、セカンドライフ内では、物理オブジェクトには、ちゃんと重力加速度が働いている様です。

上空4,000mから、以下のスクリプトを入れたオブジェクトを落として、その速度を1秒ごとに測定してみました。

   1: default
2: {
3: touch_start(integer total_number) {
4: llSetPrimitiveParams([
5: PRIM_PHYSICS, TRUE,
6: PRIM_TEMP_ON_REZ, TRUE,
7: PRIM_PHANTOM, TRUE
8: ]);
9: llSetTimerEvent(1.0);
10: }
11: timer() {
12: llOwnerSay((string) llGetVel());
13: }
14: }


典型的なデータを以下に示します

[21:25] FreeFall: <0.00000, 0.00000, -11.29792>
[21:26] FreeFall: <0.00000, 0.00000, -21.53347>
[21:26] FreeFall: <0.00000, 0.00000, -31.33345>
[21:26] FreeFall: <0.00000, 0.00000, -40.91565>
[21:26] FreeFall: <0.00000, 0.00000, -50.71563>
[21:26] FreeFall: <0.00000, 0.00000, -60.29782>
[21:26] FreeFall: <0.00000, 0.00000, -70.53347>
[21:26] FreeFall: <0.00000, 0.00000, -80.33360>
[21:26] FreeFall: <0.00000, 0.00000, -90.35152>
[21:26] FreeFall: <0.00000, 0.00000, -99.49833>
[21:26] FreeFall: <0.00000, 0.00000, -109.29850>
[21:26] FreeFall: <0.00000, 0.00000, -119.53420>
[21:26] FreeFall: <0.00000, 0.00000, -129.11650>
[21:26] FreeFall: <0.00000, 0.00000, -138.91630>
[21:26] FreeFall: <0.00000, 0.00000, -148.49840>
[21:26] FreeFall: <0.00000, 0.00000, -158.08040>
[21:26] FreeFall: <0.00000, 0.00000, -168.31570>
[21:26] FreeFall: <0.00000, 0.00000, -178.11550>
[21:26] FreeFall: <0.00000, 0.00000, -187.69760>
[21:26] FreeFall: <0.00000, 0.00000, -197.49740>
[21:26] FreeFall: <0.00000, 0.00000, -200.00000> ← ここで速度がサチってます
[21:26] FreeFall: <0.00000, 0.00000, -200.00000>
[21:26] FreeFall: <0.00000, 0.00000, -200.00000>
[21:26] FreeFall: <0.00000, 0.00000, -200.00000>
[21:26] FreeFall: <0.00000, 0.00000, -200.00000>
[21:26] FreeFall: <0.00000, 0.00000, -200.00000>
[21:26] FreeFall: <0.00000, 0.00000, -200.00000>
[21:26] FreeFall: <0.00000, 0.00000, -200.00000>
[21:26] FreeFall: <0.00000, 0.00000, -200.00000>
[21:26] FreeFall: <0.00000, 0.00000, 14.96331> ← ここで地面と衝突した?
[21:26] FreeFall: <0.00000, 0.00000, 4.72773>
[21:26] FreeFall: <0.00000, 0.00000, -5.07227>
[21:26] FreeFall: <0.00000, 0.00000, 1.47556>
[21:26] FreeFall: <0.00000, 0.00000, 0.00452>
[21:26] FreeFall: <0.00000, 0.00000, 0.00000>
[21:26] FreeFall: <0.00000, 0.00000, 0.00000>

何度か試してみましたが、このデータから重力加速度を計算すると、9.8になります。
ふむ、割とまじめに作ってあるんですね。これならいろいろ物理シミュレーションを視覚的に作れそうな感じです。夏休みにはがんばってみるか。

2009年7月26日日曜日

35cmの謎 - 2つの椅子のスクリプト

2つの椅子をスクリプトを作ってみました。

特に変わった椅子ではありません。ただ、New Script で時々お会いするペンギンさん(ってたくさんいらっしゃるので)のこのエントリが気になったので、2つの椅子のスクリプトを作ってみました。

ひとつは

   1: vector  GSitPos = <0.3, 0.0, 0.56>;
2: vector GSitRot = <0.0, 0.0, 0.0>;
3:
4: default
5: {
6: state_entry() {
7: llSitTarget(GSitPos, llEuler2Rot(DEG_TO_RAD*GSitRot));
8: llSetLinkPrimitiveParams(2, [PRIM_POSITION, GSitPos]); //***
9: }
10: }

LSLのテキストの定番、llSetTargetを使って書いた例です。//***というコメントがついた行(8行目)は、llSetTargetで設定した位置を示すための球状のプリムの位置を示すためのコードです。
そして、もう一つが、

   1: vector  GSitPos = <0.3, 0.0, 0.91>;
2: vector GSitRot = <0.0, 0.0, 0.0>;
3: integer GMyPrimCount;
4:
5: default
6: {
7: state_entry() {
8: GMyPrimCount = llGetNumberOfPrims();
9: llSetLinkPrimitiveParams(GMyPrimCount, [PRIM_POSITION, GSitPos]); //***
10: }
11: changed(integer change){
12: if (change & CHANGED_LINK) {
13: integer nLinkCount = llGetNumberOfPrims();
14: if (GMyPrimCount + 1 == nLinkCount) {
15: llSetLinkPrimitiveParams(nLinkCount, [
16: PRIM_POSITION, GSitPos,
17: PRIM_ROTATION, llEuler2Rot(DEG_TO_RAD*GSitRot)/llGetRot()
18: ]);
19: } else if (GMyPrimCount + 1 < nLinkCount) {
20: llUnSit(llGetLinkKey(nLinkCount));
21: }
22: }
23: }
24: }

こちらになります。このスクリプト中の//***というコメントがついた行(9行目)は、やはりllSetLinkPrimitiveParamsで、座る位置を指定する場所を示すために、球状のプリムをそこに移動させるものなので、動作自体には関係ありません。


この写真が、前者のllSetTargetで座る位置を指定した椅子に座ったときの物で、


こちらが、後者のllSetLinkPrimitiveParamsで、座る位置を指定した時の写真になります。
すぐに分かるのですが、前者と後者で、座る位置の指定のZ座標が、およそ 0.35m異なっているのです。

座るアバターの大きさが、この差に関係しているのかと思ったので、自分のアバターを最大身長と、最小身長にして(うむ、元に戻すのが大変でしたw)確かめてみました。
その結果、大きくなったときと、小さくなったときで、座る位置に非調整が必要(大きくなったときは設定位置を上へ、小さくなったときは設定位置を下へ)という事が分かりましたが、2つのスクリプトの間にずれの差は認められませんでした。

もしかすると、座るアニメーションに関係するのかなと思って、上のスクリプトを以下のように改造して試してみましたが

   1: vector  GSitPos = <0.3, 0.0, 0.56>;
2: vector GSitRot = <0.0, 0.0, 0.0>;
3: key GSitted;
4: string GAnimationName = "stand";
5:
6: stopAllAnimation(key id)
7: {
8: list anms = llGetAnimationList(id);
9: integer i;
10: for (i = 0; i < llGetListLength(anms); i++){
11: llStopAnimation(llList2Key(anms, i));
12: }
13: }
14:
15: default
16: {
17: state_entry() {
18: GSitted = NULL_KEY;
19: llSitTarget(GSitPos, llEuler2Rot(DEG_TO_RAD*GSitRot));
20: llSetLinkPrimitiveParams(2, [PRIM_POSITION, GSitPos]);
21: }
22: changed(integer change){
23: if (change & CHANGED_LINK) {
24: key nowSitted = llAvatarOnSitTarget();
25: if (GSitted != NULL_KEY) {
26: if (nowSitted == NULL_KEY) {
27: llStopAnimation(GAnimationName);
28: GSitted = NULL_KEY;
29: }
30: } else {
31: if (nowSitted != NULL_KEY) {
32: GSitted = nowSitted;
33: llRequestPermissions(GSitted, PERMISSION_TRIGGER_ANIMATION);
34: }
35: }
36: }
37: }
38: run_time_permissions(integer perm) {
39: if (perm & PERMISSION_TRIGGER_ANIMATION) {
40: stopAllAnimation(GSitted);
41: llStartAnimation(GAnimationName);
42: }
43: }
44: }

(もちろん、後者のスクリプトも、同様に改造して)2つを試してみましたが、差はありません。





この差が、何に起因するのかよく分かりませんが、仕様という奴なんでしょうか。

あと1つ不審点がありました。
1つは、2番目のllSetLinkPrimitiveParamsで座る位置を指定するスクリプトの場合、llSitTargetを入れないと、座れないことがあります。これが怪しいところなのですが、椅子を移動したり回転させると座れなくなるような気がしますが、再現性がない。実装がわからないと、これ以上突っ込みようがないので、こんなもんだと理解するしか方法はないようです。

2009年7月21日火曜日

思惑

この10日は結構忙しかったので、ここの更新が出来ませんでした。
ある人から、以下の記事を教えてもらって、今日学生に実習をさせながら眺めていたのですが、とてもおもしろい。

あのセカンドライフは今どうなってる? 潜入取材で実際に見てきました

この記事なのですが、発言が95もあるので、ある程度統計情報になるかな、と思ってちょっと分析をしました。もちろん、同じ人間が何度も書き込んでいるでしょうから、世論?をこのデータから推し量ることはまったく出来ませんが、とてもおもしろい。



以下はその分析の結果なのですが、セカンドライフを擁護する発言に+1、逆にセカンドライフなんてだめじゃーんって発言を-1、どちらでもない発言を0として、発言順(横軸)に累計を取った物です。もちろんここの発言が、セカンドライフを擁護しているものなのかどうかは、わたしの個人的な判断なので、的外れな物もあると思います。

このグラフで、はじめはセカンドライフ擁護派の発言が続き、1/3あたりから「セカンドライフってだめじゃーん」って発言が増えてきて、2/3あたりからまたセカンドライフ擁護派の発言が増えている。

これが全体的な傾向なのですが、おもしろいなーと思ったのが、セカンドライフってだめじゃーんって発言が出始めている時期に、

「セカンドライフユーザのアホどもが必死だなw」

という発言があることです。それに続いて

「長文書いている人は某広告代理店の方々ですか?」

という発言があり、時折根拠のない「セカンドライフってだめじゃーん」って言う発言が出てくることです。もちろん説得力を持った「セカンドライフってだめじゃーん」って発言が多く、議論が成立している場面もありましたが、根拠なく「セカンドライフってだめじゃーん」も時折、見受けられるのです。

この議論をする訳でもなく、「セカンドライフってだめじゃーん」というステロタイプな発言をしている人間の意図というか、何を考えているのかを想像すると、とてもおもしろい。

最近のネットを見ていて感じることに、他人を貶めその反応を見て楽しむという文化があることに気づいていましたが、なるほどなという感じです。この発言をした人間は、一方的に「セカンドライフってだめじゃーん」と決めつけ、それに反駁する人の発言を誘い出し、それをせせら笑うことを目的とした発言なのだと思うのです。ふむ。
人が必死になっている事を見るのが、滑稽とは私は思わないのですが、似たようなことは高校時代にあったなぁと、思い出したことがありました。それは、自分のうちに帰ると、まあそこそこ勉強しているのですが、友人の前では、まったく勉強していないように装うのが伊達と思っていたことです。
このような風潮は、文化の爛熟期には必ずと言って良いほど発生すると、私は根拠なく思っているのですが、なるほどと思いました。そしておもしろいw。

このような、人を貶め、その人が右往左往するのを見て楽しむというのを、粋だと思うことは、決して異常なことではない。むしろ、そのステロタイプさに、ほほえましくもあります。
きっとそのような人が、これを読むと「上から目線が気に入らん」という反応がでることも当然なのですが、それはしょうがない。私の方が齢を重ねているのだからw。

まあ、それは置いておいて、このような発言があることに、おもしろさを感じるのは歳のせいだろうかと、帰りの電車の中で真剣に考えてしまった自分が情けない。

次回は、まともな記事を書こうと思います。

2009年7月13日月曜日

プロジェクター

昨夜はうちの学生を全員招いてミーティングをしました。

そのための準備がとても忙しく、遊んでいる暇はなかったのですが、どうにかホールも完成しました。ホールには、うちのサーバにアップした画像を連続して映写できる、プロジェクター(というより、スクリーンです)も備え付けました。

以下がサーバ側のCGIプログラムのソースになります。
/*
** projector.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <time.h>
 
int main(int argc, char* argv[])
{
char filename[BUFSIZ];
int imgNum = 0;
 
memset(filename, (char) 0x00, sizeof(filename));
 
printf("Content-type: text/html\n\n");
printf("<!DOCTYPE html PUBLIC \"-//w3c//dtd html 4.0 transitional//en\">\n");
printf("<html>\n");
printf("<head>\n");
printf("<title>secondlife view test</title>\n");
printf("</head>\n");
printf("<body bgcolor=\"#ffffcc\">\n");
printf("<center>\n");
if (2 < argc) {
strncpy(filename, argv[1], BUFSIZ - 1);
imgNum = atoi(argv[2]);
printf("<img alt=\"testImage\" src=\"/projecter/%s/img%03d.png\" "
"height=\"100%%\" width=\"100%%\">\n", filename, imgNum);
} else {
printf("ERROR: %s, %d\n", filename, imgNum);
}
printf("</center>\n");
printf("</body>\n");
printf("</html>\n");
 
return 0;
}

すみません。ソースの色付けにLSL用の物を使ってしまいました
それから、これ見て試そうという人はそれなりのプロでしょうからあんまり人に見せられた品質のプログラムじゃないです。
そして、目一杯のセキュリティー・ホールがあります



このCGIプログラムを、セカンドライフ側から叩けば、していたディレクトリに格納されているimgxxx.png(xxxは数字)を表示するWebページを戻します。それをセカンドライフ側から表示してやれば、うちのサーバにある画像をセカンドライフ内で表示することができます。

試して見たところ結果は比較的良好で、問題はセカンドライフ側のスクリーンに仕込むスクリプトに焦点が絞られそうです。

2009年7月7日火曜日

なぜタッチ?

素朴な疑問

椅子に座りタッチでアニメーションが切り替わる物が多いようですが、なぜ、タッチなんでしょ?
椅子にすわっているときってチャットしている事が多いので、手はキーボード上にあるのが普通です。ですので、いちいちマウスに手を伸ばしてタッチするより、キーボードから切り替えられた方が、楽に思えるのですが。

調べて見たところ、↑、↓、PageUp、PageDown などキー押下が取れると思うのですが。

そのキーには何かアサインされているのが普通なのかなぁ?
それとも、皆さん方は、椅子にすわっても、マウスに手が行っているのが普通なのかなぁ?
はたまた、座っている椅子にタッチでアニメ切り替えが、ディファクト・スタンダードの操作になっちゃっているのかな?

この辺は経験がないので良く分からん。

2009年7月6日月曜日

ヘタレなショーの始まり

GOTTiさんから、思わぬ(うれしい)申し出を受けました。

Scripters' Cafe で、定期的に(漫才)ショーをしないかとのお誘いです。基本的に人の前で話すことは嫌いではない、人前で話すことが苦痛ならば教員という商売はかなり苦痛になります。
ただ話を聞かれる方々が問題で、その方々に楽しんでいただける話ができるかが気になるところです。

現時点で用意できるネタとしては、

・ソフトウエアテストの話(ここの焼き直し)
・いくつかのソースのワタクシ的ツッコミ
・オープンソースのビジネスモデル
・(セカンドライフで)役に立つオープンソースソフトウエア

などがあるのですが、月に1回でよいと聞きましたので何とかこなしてゆけるでしょう。
この話を、お受けした背景は、セカンドライフ内で実社会と同じような講演のテクニック使えるのか、それともまったく別なテクニックが必要なのか、という部分を検証をしてみたいという欲求が、むずむずと沸いて来たのが最大の理由です。
できれば、スクリプトが書ける人だけが楽しめる話ではなく、一般の人にも楽しんで頂ける話ができたらと思いますが、それは取らぬたぬきのなんとやら。努力目標としておきます。

2009年7月4日土曜日

ドアのスクリプトをいじくり倒す

む、Hidenoriさん、さすが、問題点をまとめて下さった

stateの分け方、使い所
寡聞にしてこのstateというもの、セカンドライフに来てはじめて知った制御構造なので、まだ、それほど経験がないため、細かい実装に関してはよく分からない部分があります。そこで一般論としての話になってしまいますが、みどりさんが書いているブログのこのあたりに一般論としての正解があるように思います。
原則的に、ステートチャート図を書いたときに遷移条件が、外的条件によって複数に分かれるもの(私は停留があると表現してます)はLSLでもステートになるでしょう。
問題は、ただ単に通過するステートの存在を許すか、許さないかになると思いますが、そのステートの中でのみ使われるイベントハンドラがある場合(例えば、初期化時にノートカードの読み込みを行うなど)は、十分にステートの候補になります。
また、逆に複数の契機によって同じイベントが発生する場合(例えば、dataserver(key queryid, string data)イベントは、ノートカードの読み込みだけではなく、他の関数の実行でも、イベントが発生します)も、ステートに分けてしまうことができるならば、分けてしまった方が良いと思います。
LSLはイベント・ドリブンな環境なのですから、そこに注目というか、それをよりどころにすれば良いと思います。
また、これは、実装者の個性が出る部分でもあります。

関数にどこまで入れるのか
ソフトウエア工学的には、モジュールの結合度は低く抑えるのが再利用という観点から見たときにはよろしい、ということになっています。私がソフトウエアタイマーの起動処理(llSetTimerEvent)を関数の外に出したのは、まさにモジュールの結合度を低めるためです。仮にこの関数を他のところで使い回す必要が出たときに、関数内部でソフトウエアタイマーを起動しているとすると、この関数を使うことができるステートには、必ずtimerイベントのハンドラがなきゃいけない(ここは突っ込みどころで、必ずしもtimerイベントがなくても動作すると思います)と言うことになれば、使い勝手が悪い。いや、経験がないので、悪そうだと考えたからなのです。
また、ドアを開けることと、時間がたてば自動的にドアを閉めることが、不可分の関係にあるならば、ドアを開ける関数内でソフトウエア・タイマーを起動しても良いでしょう。しかし、開けっ放しにしておく(おきたい)ドアもある得るということが、頭の片隅をよぎったため、分離するということの補強材料になりました。
かてて加えて、イベント・ドリブン型のプログラミング言語で、ソースコードの可読性を考えた場合、契機となるllSetTimerEvent関数の呼び出しと、その処理を行うtimerイベントハンドラが、近くにあることでよりわかりやすくなると信じてもいます。

初期rotationをいつ保存するか
これは、初期rotationを保存する目的に関わる問題です。初期rotationを保存する目的は、ドアが閉じているときに、ドアを開けた後、ドアを元に戻すために保存しているのですから、ドアを開ける直前の、ドアが閉じた状態を保存すべきです。これは自明のことで議論を待たないと思いますが、それがいつであるのかという部分は、LSLに対する知見の有無というか、誤解があると実装を失敗するように思います。

以上は、私がLSLに関する知識があまりないまま、一般論として書いた物ですので、LSLに詳しい方から、特にタイミング関連については、かなり突っ込みがあると思います。そのあたりのことの、フォローお願いします。

2009年7月1日水曜日

回転ドアの研究

話が前後します。わたしが参加していたスクリプトの勉強会の第一回のテーマがドアでした。第2回目がやじろべいで、そちらを先に書きましたが、今回は回転ドアの研究です。

研究すると言いましたが、それには、まずこのドアの仕様が分からなければならないのですが、残念ながらそのことに関する説明もない(開発の現場ではありがちなことですが、学生にはそれが普通だとは、口が裂けても言えないのが私の立場です)ので、以下に聞いた範囲で、わたしの想像も交えて仕様を箇条書きで書いてみました。

・90度回転する回転ドア
・タッチで開く
・開くときにはゆっくり開く
・自動的に閉まる
・タッチした人とは反対側に開く

という感じになるのでしょうか。しかし明文化せずともドアには必要な機能があります。それはどの向きに、ドアが置かれても正しく動作しなければならないということです。これを暗黙の仕様として、ソフトウエアの機能テストとして基礎中の基礎になりますが、仕様からブラック・ボックステストの手法でテストケースを作成し(厳密にはやっていません)、正しく機能を満足しているかを実際に動作させて調べてみました。
テストするために、このスクリプトをオブジェクトに入れて見ましたが、実行時にサウンドがないという意味のエラーメッセージが表示されることに加え、板全体が回転してしまい見たように動作しません。そこで、ここを調べたところ、

回転の軸をドアの端っこにするために、パスカットを使って板を半分にしています。
よくわからない方は、ひとまずパスカットの値を、0.375-0.875に設定してみて下さい。
立方体が半分になって、結果中心座標が端っこになります。


とのことで、まず回転軸をドアの端にするために、パスカットをしなければならないということが分かりました。また実行時に表示されたエラーメッセージは、ドアの開閉音らしいのでそれはコメントアウトしました。私が聞き漏らしたのかも思いますが、そのことに関する説明は勉強会当日にはありませんでした。

作成した状態から回転させないで動作させた場合
・90度回転する回転ドア
おおよそ90度回転して開きました
・タッチで開く
一回開いたて閉じた後、再度タッチしても開きません。もう一回タッチすると開きました。これは何度やっても同じで、一回開いて閉じたドアを再度開こうとして1回タッチしても、1回目ではドアは反応せず、2回目のタッチに反応して開くようです。
・開くときにはゆっくり開く
滑らかな動きではなく、間歇的な動きでしたが、ゆっくり開きました
・自動的に閉まる
仕様通り一定時間後に閉まるようです
・タッチした人とは反対側に開く
とりあえず動作しているようです

作成した状態からドアを回転させて動作させた場合
・90度回転する回転ドア
おおよそ90度回転して開きました
・タッチで開く
(作成した状態から回転させないで動作させた場合と同じ不具合がありました)
・開くときにはゆっくり開く
特に問題なし
・自動的に閉まる
ドアが閉じるときに、回転させる前の閉じ位置にドアが戻ってしまいました
・タッチした人とは反対側に開く
回転させた角度によって、様相がかわります。ある角度では正しく動作するのですが、別な角度では、ドアの手前にアバターがいるか/向こう側にいるかではなく、アバターがドアの右側にいるか/左側にいるかで開く向きが変わりました。)と同じような不具合が出る場合もあり、また、手前に開く場合もありました

きちんとしたテスト計画を作っていれば、確実に「テスト中止、開発側にリジェクト」のケースです。

そこで問題を検討するために、以下にそのときにもらったスクリプトを示します。

rotation rot;
integer counter = 0;
vector door;
 
default
{
state_entry()
{
rot = llGetRot();
door = llGetPos();
}
 
touch_start(integer num)
{
llPlaySound("door04", 1.0);
vector av = llDetectedPos(0);
if(door.y > av.y){ state door_out; }
else{ state door_in; }
}
 
on_rez(integer num)
  {
llResetScript();
}
}
 
state door_out
{
state_entry()
{
llSetTimerEvent(0.1);
}
 
timer()
{
counter++;
if( counter == 10 ){ counter = 0; state out_next; }
llSetRot( llGetRot()*llEuler2Rot(<0.0,0.0,10.0> * DEG_TO_RAD ));
}
}
 
state out_next
{
state_entry()
{
llSleep(10);
llSetTimerEvent(0.1);
}
 
timer()
{
counter++;
if( counter == 10 ){ counter = 0; llSetTimerEvent(0.0);
llPlaySound("door04", 1.0);
llSetRot( rot ); state default; }
llSetRot( llGetRot()*llEuler2Rot(<0.0,0.0,-10.0>*DEG_TO_RAD ));
}
}
 
state door_in
{
state_entry()
{
llSetTimerEvent(0.1);
}
 
timer()
{
counter++;
if( counter == 10 ){ counter = 0; state in_next; }
llSetRot( llGetRot()*llEuler2Rot(<0.0,0.0,-10.0> * DEG_TO_RAD ));
}
}
 
state in_next
{
state_entry()
{
llSleep(10);
llSetTimerEvent(0.1);
}
 
timer()
{
counter++;
if( counter == 10 ){ counter = 0; llSetTimerEvent(0.0);
llPlaySound("door04", 1.0);
llSetRot( rot ); state default; }
llSetRot( llGetRot()*llEuler2Rot(<0.0,0.0,10.0>*DEG_TO_RAD ));
}
}


中身を検討するに当たって、いただいたソースではとても気持ち悪いので、インデントを書き換えたのが以下になります。

   1: rotation rot;
2: integer counter = 0;
3: vector door;
4:
5: default
6: {
7: state_entry() {
8: rot = llGetRot();
9: door = llGetPos();
10: }
11: touch_start(integer num) {
12: //llPlaySound("door04", 1.0);
13: vector av = llDetectedPos(0);
14: if (door.y > av.y) {
15: state door_out;
16: } else {
17: state door_in;
18: }
19: }
20: on_rez(integer num) {
21: llResetScript();
22: }
23: }
24:
25: state door_out
26: {
27: state_entry() {
28: llSetTimerEvent(0.1);
29: }
30: timer() {
31: counter++;
32: if (counter == 10) {
33: counter = 0;
34: state out_next;
35: }
36: llSetRot(llGetRot()*llEuler2Rot(<0.0,0.0,10.0>*DEG_TO_RAD));
37: }
38: }
39:
40: state out_next
41: {
42: state_entry() {
43: llSleep(10);
44: llSetTimerEvent(0.1);
45: }
46: timer() {
47: counter++;
48: if (counter == 10) {
49: counter = 0;
50: llSetTimerEvent(0.0);
51: //llPlaySound("door04", 1.0);
52: llSetRot(rot);
53: state default;
54: }
55: llSetRot(llGetRot()*llEuler2Rot(<0.0,0.0,-10.0>*DEG_TO_RAD));
56: }
57: }
58:
59: state door_in
60: {
61: state_entry() {
62: llSetTimerEvent(0.1);
63: }
64: timer() {
65: counter++;
66: if (counter == 10) {
67: counter = 0;
68: state in_next;
69: }
70: llSetRot(llGetRot()*llEuler2Rot(<0.0,0.0,-10.0>*DEG_TO_RAD));
71: }
72: }
73:
74: state in_next
75: {
76: state_entry() {
77: llSleep(10);
78: llSetTimerEvent(0.1);
79: }
80: timer() {
81: counter++;
82: if (counter == 10) {
83: counter = 0;
84: llSetTimerEvent(0.0);
85: //llPlaySound("door04", 1.0);
86: llSetRot(rot);
87: state default;
88: }
89: llSetRot( llGetRot()*llEuler2Rot(<0.0,0.0,10.0>*DEG_TO_RAD));
90: }
91: }


正直に言って、ずっこけてしまいました。不具合の80%(この数値はいい加減です)原因は、14行目にあります。正の回転でドアを開けるのか、それとも負の回転でドアを開けるのかの判断をしているらしいのが14行目ですが、その判断に、ドアとタッチしたアバターのy座標(南北方向)の位置関係だけで判断しているようです。「あのう、すみません。脳みそ溶けているだけじゃなくて、耳から流れ出してますけど・・・」
そして閉じた後に、1回のタッチでドアが反応せず、2回目で反応する件は、どうもリンデン側のバグのようです。これも、google検索で「jira state transfer touch」というキーワード一発でみつかる有名なバグのようです。これの回避は簡単で、ステートの遷移をしないようにしなければ良いだけです。

やはり、このソースも修正するのは難しいので、スクラッチで書き直してみました。

   1: vector      GOrigPos;
2: vector GOrigRot;
3: integer GStateFlag = 0;
4: float GOpenPeriod = 5.0;
5: integer GCommChannel = -15643;
6: integer GCommHandle;
7:
8: doorOpen(vector p)
9: {
10: GOrigPos = llGetPos();
11: GOrigRot = llRot2Euler(llGetRot());
12:
13: vector targetPos = p;
14: float targetAngle = llAtan2(targetPos.y - GOrigPos.y,
15: targetPos.x - GOrigPos.x);
16: float myAngle = GOrigRot.z;
17: float direction = targetAngle - myAngle;
18:
19: while (direction <= -PI || PI < direction) {
20: if (direction < 0.0) {
21: direction += TWO_PI;
22: } else {
23: direction -= TWO_PI;
24: }
25: }
26:
27: if (0.0 <= direction) {
28: GStateFlag = -1;
29: } else {
30: GStateFlag = 1;
31: }
32: llSetRot(llEuler2Rot(GOrigRot +
33: GStateFlag*<0.0, 0.0, PI_BY_TWO>));
34: }
35:
36: doorClose()
37: {
38: llSetRot(llEuler2Rot(GOrigRot));
39: GStateFlag = 0;
40: }
41:
42: default
43: {
44: state_entry() {
45: GStateFlag = 0;
46: GCommHandle = llListen(GCommChannel, llGetObjectName(), NULL_KEY, "");
47: }
48: touch_start(integer total_number) {
49: if (GStateFlag == 0) {
50: vector p = llDetectedPos(0);
51: llWhisper(GCommChannel, llList2CSV(["open", (string) p]));
52: doorOpen(p);
53: llSetTimerEvent(GOpenPeriod);
54: } else {
55: llWhisper(GCommChannel, "close");
56: doorClose();
57: llSetTimerEvent(0.0);
58: }
59: }
60: timer() {
61: llSetTimerEvent(0.0);
62: llWhisper(GCommChannel, "close");
63: doorClose();
64: }
65: listen(integer channel, string name, key id, string message){
66: if (channel == GCommChannel && name == llGetObjectName()) {
67: list m = llCSV2List(message);
68: string m1 = llList2String(m, 0);
69: if (m1 == "open") {
70: doorOpen((vector) llList2String(m, 1));
71: } else if (m1 == "close") {
72: doorClose();
73: }
74: }
75: }
76: }


あ、仕様も勝手に変えてしまいました。

・開いている状態の時にタッチしても閉まる
・同じ名前のドアが10m以内にあった場合、タッチしたドアと同期する
・ゆっくり閉まらない

それに今見直してみると、デバッグ時に使用した冗長な部分(doorOpen(vector p)関数内)がかなりありますね。直すのがかったるいので、ごめんなさい。

あれ、今週ないのか

先週まであった、スクリプトの勉強会、今週はないのかな。
確か、今日はライトのスクリプトと言っていましたが、飽きちゃったんでしょうか?