なたで日記

いろいろな思ったこと書きますヽ(^▽^ゞ) by natade

Posts Tagged ‘LSL

セカンドライフで自分の行動の色々な情報を取得する

leave a comment »


次のような自分の行動の情報を取得してみます。
・自分が喋った回数
・人から聞いた回数(アバターが話したものに限定)
・空を飛んでいる時間(ホバーは含めない)
・海の中を泳いでいる時間(ホバーは含めない)
・歩いている時間
・走った時間
・ジャンプ中の時間
・テレポートした回数
・SIMをまたいだ回数
・人や当たり判定がある物体と衝突した回数

以下、サンプルです。
このコードを適当なオブジェクトに入れた後、HUDとして装備してください。
drawStatusと発言すると、自分の情報が表示されます。

ログインした回数とかも取れるかなと思ったのですが、
装備した回数と見分けがつかないので諦めました。

integer count_attack		= 0;
integer count_speak			= 0;
integer count_listen		= 0;
integer count_flying		= 0;
integer count_swimming		= 0;
integer count_running		= 0;
integer count_walking		= 0;
integer count_teleport		= 0;
integer count_simtraversal	= 0;
integer count_jumping		= 0;

integer TYPE_ATTACK			= 0;
integer TYPE_SPEAK			= 1;
integer TYPE_LISTEN			= 2;
integer TYPE_FLYING			= 3;
integer TYPE_SWIMMING 		= 4;
integer TYPE_RUNNING		= 5;
integer TYPE_WALKING		= 6;
integer TYPE_TELEPORT		= 7;
integer TYPE_SIMTRAVERSAL	= 8;
integer TYPE_JUMPING		= 9;

integer show_dialog		= FALSE;
integer listen_handle	= -1;

addAction(integer action) {
//	llOwnerSay("action : "+ (string)action);
	if(action == TYPE_ATTACK) {
		count_attack	= count_attack + 1;
	}
	else if(action == TYPE_SPEAK) {
		count_speak		= count_speak + 1;
	}
	else if(action == TYPE_LISTEN) {
		count_listen	= count_listen + 1;
	}
	else if(action == TYPE_FLYING) {
		count_flying	= count_flying + 1;
	}
	else if(action == TYPE_SWIMMING) {
		count_swimming	= count_swimming + 1;
	}
	else if(action == TYPE_RUNNING) {
		count_running	= count_running + 1;
	}
	else if(action == TYPE_WALKING) {
		count_walking	= count_walking + 1;
	}
	else if(action == TYPE_TELEPORT) {
		count_teleport	= count_teleport + 1;
	}
	else if(action == TYPE_SIMTRAVERSAL) {
		count_simtraversal	= count_simtraversal + 1;
	}
	else if(action == TYPE_JUMPING) {
		count_jumping	= count_jumping + 1;
	}
}

showStatus() {
	llWhisper(0,
		"attack\t:"			+ (string) count_attack			+ " times\n" +
		"speak\t:"			+ (string) count_speak			+ " times\n" +
		"listen\t:"			+ (string) count_listen			+ " times\n" +
		"teleport\t:"		+ (string) count_teleport		+ " times\n" +
		"simtraversal\t:"	+ (string) count_simtraversal	+ " times\n" +
		"flying\t\t:"		+ (string) count_flying			+ " sec\n" +
		"swimming\t\t:"		+ (string) count_swimming		+ " sec\n" +
		"running\t:"		+ (string) count_running		+ " sec\n" +
		"walking\t:"		+ (string) count_walking		+ " sec\n" +
		"jumping\t:"		+ (string) count_jumping		+ " sec"
	);
}

openDialog() {
	if(listen_handle != -1) {
		llListenRemove(listen_handle);
	}
	listen_handle = llListen(-100, "", llGetOwner(), "");
	llDialog( llGetOwner(), "Which operation?\nYou say \"drawStatus\"... or \"resetStatus\"", ["status", "reset", "cancel"], -100 );
	llSetTimerEvent(0.0);
	llSetTimerEvent(30.0);
	show_dialog = TRUE;
}

closeDialog() {
	if(listen_handle != -1) {
		llListenRemove(listen_handle);
	}
	show_dialog = FALSE;
	listen_handle = llListen(0, "", "", "");
	llSetTimerEvent(0.0);
	llSetTimerEvent(1.0);
}

default {
	state_entry() {
		llOwnerSay("Start Script !\nYou say \"drawStatus\"... or \"resetStatus\"");
		closeDialog();
	}
	
	touch_start(integer total_number) {
		openDialog();
	}
	
	timer() {
		if(show_dialog) {
			closeDialog();
		}
		else {
			string my_animatin = llGetAnimation(llGetOwner());
			if(my_animatin == "Flying") {
				vector v = llGetPos();
				if(llWater(v) > v.z) {
					addAction(TYPE_SWIMMING);
				}
				else {
					addAction(TYPE_FLYING);
				}
			}
			else if(my_animatin == "Running") {
				addAction(TYPE_RUNNING);
			}
			else if(my_animatin == "Walking") {
				addAction(TYPE_WALKING);
			}
			else if(my_animatin == "Jumping") {
				addAction(TYPE_JUMPING);
			}
		}
	}
	
	listen( integer channel, string name, key id, string message ) {
//		llOwnerSay(""+ (string)channel +" " + name + " " + (string)id + " " + message);
		if(show_dialog) {
			if(message == "status") {
				showStatus();
			}
			else if(message == "reset") {
				llResetScript();
			}
			else if(message == "cancel") {
			}
			closeDialog();
		}
		else {
			if(id == llGetOwner()) {
				if(message == "drawStatus") {
					showStatus();
				}
				else if(message == "resetStatus") {
					llResetScript();
				}
				else {
					addAction(TYPE_SPEAK);
				}
			}
			else if(llGetAgentSize(id)){
				addAction(TYPE_LISTEN);
			}
		}
	}
	
	changed(integer mask) {
		if(mask & CHANGED_OWNER) {
			llResetScript();
		}
		if((mask & CHANGED_TELEPORT) && (mask & CHANGED_REGION)) {
			addAction(TYPE_TELEPORT);
		}
		else if(mask & CHANGED_TELEPORT) {
			addAction(TYPE_TELEPORT);
		}
		else if(mask & CHANGED_REGION) {
			addAction(TYPE_SIMTRAVERSAL);
		}
	}
	
	collision_start(integer num) {
		if(llDetectedType(0) & ACTIVE) {
			vector my_velocity = llGetVel();
			vector velocity = llDetectedVel(0);
			float power = llVecMag(my_velocity - velocity);
			if(power >= 1.0) {
				addAction(TYPE_ATTACK);
			}
		}
	}
	
	on_rez(integer param) {
		// 1 ログイン、装備、rez
	}
	
	attach(key attached) {
		if(attached) {
			// 2 ログイン、装備
		}
	}
}

Written by なたで

2017年1月15日 at 00:00

カテゴリー: memo, program

Tagged with ,

セカンドライフのオブジェクトとの衝突音

leave a comment »


みなさん。お久しぶりです。

ついに今年も「Advent Calendar」の季節がやってきました!

イエーイ!

このようなイベントを開催していただいて、sabro様ありがとうございます!

私は去年から初参戦しているのですが、今年はナント1日目を予約してしまいました。
こんな私が1日目をとってしまってよかったのだろうか……といろいろありますが、
気にせずドンドン始めていきたいと思います!


衝突音

今回のテーマは、音関係の話、具体的には衝突音の話をしたいと思います。

みなさん、衝突音って気にしたことありますか……?

……

あんまり、気にしていない人多いかも。
でも、私はたまーに気になったりします。

例えばですが、プリムで階段を作ったとします。
kaidan

 

こういう階段にダッシュして登ってみましょう!

ゴツッ!

ほら!衝突音しましたよね。

なんとなくどんくさい感じがします。
他にも、柔らかなそうなソファーとかぶつかった時も、
ゴツッ!とか出るのも何だか不自然な感じがします。

実は、このような衝突音、簡単に消せるんです! \テテーン!/

プリムを選択して、スクリプトを追加。

gui_script_1 → gui_script_2
スクリプトでは次のように llCollisionSound で衝突音の設定をします。

default
{
	state_entry()
	{
		llCollisionSound("", 0.0);
	}
}

さあ、これで保存して、同じように階段を上ってみましょう!
衝突音がないため、スムーズに駆け上っている感じがでましたね。


簡単に衝突音設定しよう!

スクリプト面倒という方もいらっしゃると思います。
無音にする方法でなければ、実は手軽に衝突音を変えられるってご存知でしょうか?

具体的には、「オブジェクトの特徴」の設定で、
実像のタイプを選択することができます。

gui_sound_1 → gui_sound_2

デフォルトは「木」となっているのですが、
他にも、いくつか選べて、全部で7種類を選ぶことができます。
それぞれの設定の音は、次のようになります。

  • 石   … 低いゴツ!
  • 金属  … カン!(グレーチングを踏んだような音)
  • ガラス … カン!(氷をアイスピックでたたいたような音)
  • 木   … ゴツ!
  • 肌   … コツ!
  • プラスチック … ドン!
  • ゴム  … コツ!(硬くゴムを机に落としたような音)

※音の印象は個人差があります。

音は用意するの面倒だけど、衝突音を変えたいという方は
ここでGUI上でポチポチ触って変更することをオススメします!


他の音はないの?

先ほどは、簡単に音を設定する方法説明しましたが、
コツ、ゴツ系のみで種類がすくない~(><)と思う方もいると思います。

ただ llCollisionSound を使うにしても、
音を用意するのって結構面倒ですよね。

というわけで、
少し早いですがクリスマスプレゼント!
自由に使える音素材を収録して用意しました!

許可なしで有料/無料問わず自由に使える音のUUIDを公開します。
衝突音以外に使用してもらってもかまいません。

下記のスクリプトを入れれば、衝突音が変わります。
「1.0」と書いてある部分を小さな値にすれば、衝突音の音量を小さくもできます。

床1 … ドン!(低く、重たい音)

default{state_entry(){llCollisionSound("56fa194a-b894-043a-c1fe-04ab0ead663e", 1.0);}}

床2 … ドン!(普通の音1)

default{state_entry(){llCollisionSound("aab46478-b006-6fdb-1c2c-ac04dfa4c1ca", 1.0);}}

床3 … ドン!(普通の音2)

default{state_entry(){llCollisionSound("52dd9553-5c75-6fc8-5573-32df7b56d14d", 1.0);}}

床4 … ドン!(軽く、すこし硬い音)

default{state_entry(){llCollisionSound("88c0cae6-3c0d-6b6c-edee-85a3f1237828", 1.0);}}

アルミホイル … グシャ!

default{state_entry(){llCollisionSound("801f106b-d391-0214-cdd1-709b072b46aa", 1.0);}}

ビニール … グシャ!

default{state_entry(){llCollisionSound("e40d1ca7-d37f-1332-2513-37a8e209dbc6", 1.0);}}

金属音1 … コンッ!(すこし鈍い音)

default{state_entry(){llCollisionSound("b55200e1-b1bc-5302-ada4-ed559671225c", 1.0);}}

金属音2 … カッ!(軽い音)

default{state_entry(){llCollisionSound("739756e2-a4e1-7ab0-879e-118c458e545d", 1.0);}}

布1 … 軽く触れる音

default{state_entry(){llCollisionSound("a7451a80-e9f5-315a-92b2-f96253578077", 1.0);}}

布2 … バサ!

default{state_entry(){llCollisionSound("d64718b2-5923-ff14-c6d5-0ed9dda9d7c4", 1.0);}}

液体1 … ピトッ!

default{state_entry(){llCollisionSound("3bb0d4fc-d738-d04c-22f1-cd35ede613a4", 1.0);}}

液体2 … ペトッ!

default{state_entry(){llCollisionSound("75374bdf-c059-d568-bfe1-093cef58a0fd", 1.0);}}

というわけで、音の話……主に衝突音の話を終わります!

ありがとうございました!

イベントがあると、色々な人に知識を共有出来たり、
自分もさらに調べていこうと啓発もできて、とてもいいものです!
このような場を設けていただき、改めてありがとうございました!

Written by なたで

2016年12月1日 at 00:00

カテゴリー: memo, program

Tagged with ,

セカンドライフのスクリプトのエラー処理について

leave a comment »


セカンドライフでは、スクリプトでエラーが起きるとどうなるかみなさん知っていますか。
何かエラーが出ること自体は知っていると思いますが、
そのエラーが発生後、どうなるのかはそんなに知らないと思います。

ためしに、次のようなスクリプトを実行してみましょう。

hoge() {
	integer x = 0;
	llOwnerSay("hoge_start");
	x = 1 / 0;
	llOwnerSay("hoge_end");
}

default {
	state_entry() {
		llSetTimerEvent(3.0);
	}
	
	touch_start(integer total_number) {
		llOwnerSay("touch_start");
		hoge();
		llOwnerSay("touch_end");
	}
	
	timer() {
		llOwnerSay("clock");
	}
}

このスクリプトは、3秒に1度、発言が行われて、
また、クリックすると0の割り算エラーを発生させるスクリプトです。

これでクリックするとどうなると思いますか。三択問題です。

A. hoge関数内でエラーが発生するので、例外で終了するため”hoge_end”は表示されずに、関数を抜ける
B. touch_startイベント関数内でエラーが発生するので、そこでtouch_startイベント関数が強制終了する
C. プログラム自体が強制終了し、タイマーイベントなど全てが止まる

さて、どうなるでしょう。

正解は・・・


 

 

テテテテテテテ

 

 

ジャン!

Cでした!

なんと、すべて止まってしまうのです。
無限ループもタイマーもすべて止まってしまうので、手動リセットさせるしかありません。
なお、エラーの種類は「LSL Errors」でまとまっていますので、一度見ておくといいかもしれません。
注意点として、メモリリークの「Script run-time error: Stack-Heap Collision」なのですが、
これも発生してしまうと、プログラムが止まってしまいます。
そこで、止まるよりはみずから llGetFreeMemory で定期チェックをして、
llResetScript でリセットさせたほうが安全です。


オマケ

次のスクリプトはエラーが発生するでしょうか……

default {
	state_entry() {
		llOwnerSay((string)((integer)"ABC"));
		llOwnerSay((string)((integer)"123ABC456"));
		llOwnerSay((string)((vector)"ABC"));
		llOwnerSay((string)((vector)"<1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0>"));
	}
}


正解はこんな感じでエラーは発生しません。
文字列の左からデータとして認識できる範囲で変換してくれるようですね。

Object: 0
Object: 123
Object: <0.00000, 0.00000, 0.00000>
Object: <1.00000, 2.00000, 3.00000>

Written by なたで

2016年5月8日 at 21:06

カテゴリー: memo, program

Tagged with ,

セカンドライフで綺麗な水面を作る

leave a comment »


こんにちは!

セカンドライフ技術系 Advent Calendar 2015」の12月13日(日)担当の なたで です!

木曜日の記事(セカンドライフでジャンプ台を作る)に引き続いて…
今日は、セカンドライフで綺麗な水面を作ります。
けっこう長めなので、理由はともかく作りたいという方は、読み飛ばして、
途中に張ってある画像を保存して、テクスチャに使用していってください。


綺麗な波をつくるには・・・?

綺麗な水面を作るために、水の特徴を考えます。
水といえば波があるということです。

次のように2つの波の衝突を考えます

nami001
1つは、右から左へ流れる大きな波。

nami002
もう1つは、左から右へ流れる小さい波。

nami003
この2つが、衝突しても波と波とが重なり合って、消滅するということはありません。

つまり、波を作るためには、何面もの波を重ねると本物のように作ることができます。

よって、次のように作ると良いことが分かります。
「複数の面を重ねるようにおいて、模様に波(水面)のテクスチャを設定して、それぞれ別の方向へ動かす」

ここでさらに、波のテクスチャについて考えていきます。
単純に、波の模様のテクスチャだけを用意するだけでいいのでしょうか。

一度、波を実際に目で見た時を考えてみます。

nami004
波は、うねうねした形をもっており、うねうねに合わせた法線ベクトルがあります。
光は、波に当たっときに、法線を境に反射をします。
そして、反射した光が目に入った時に、白く輝いて見えることが分かります。

これらから、水面のテクスチャは、法線マップを作成する必要があるのです。
波の模様テクスチャから、法線マップを作ります。


特徴をまとめます

・複数の波を、合成する必要がある。
・それぞれの波を別方向でスクロールさせる。
・法線マップを使用して、リアルな反射を持たせる。


波のテクスチャを用意しよう

波のテクスチャは、シームレスなパーリンノイズが適切です。
シームレスというのは、上下左右につなぎ目のないという意味です。

具体的にシームレスなパーリンノイズを作る方法ですが、
実は検索してもなかなか見つかりませんでした。
一応、下記のようなツールを発見できたものの、登録が必要であるため、ダウンロードをしていません。
Procedural seamless noise texture generator

また、CLIP STUDIOというツールにも、パーリンノイズを作成する機能があるのですが、
こちらは、シームレスなパーリンノイズを作成できません。

そこで、シームレスなパーリンノイズを作成する簡易ツールを作りました。
nami_iconダウンロード

Javaの環境があれば、MacでもWindowsでも動きます。
ここでは、Windowsの環境での説明をしていきます。

【1】
「すべてのプログラム」→「アクセサリ」→「コマンドプロンプト」を実行します。

【2】
次のように、jarファイルがおいてあるディレクトリへ移動します。

cd C:\Users\xxx\Desktop\yyy

【3】
次のように実行してください。(-help と書けば使い方がでます。)

java -jar noise.jar -width 256 -height 256 -persistence 0.6 -lacnarity 2.5 -seed 123456

【4】
noise
これがパーリンノイズ(テクスチャA)です。

次に、このパーリンノイズを「高さ(ハイトマップ)」と見立てて
ノーマルマップを作成していきます。

ノーマルマップの作成ツールは、ハイトマップから自動でノーマルマップを作成できる
NormalMap-Online」を使用させていただきます。

【5】
normalmake
左側の箇所に画像ファイルのアイコンをドラッグ&ドロップして、
画像を読み込ませて、真ん中上部のスライダーで設定をしていきます。

【6】
NormalMap
Downloadを押せば、保存ができます。
これで、ノーマルマップ(テクスチャB)の完成です。

【7】
さらに、色用のテクスチャも用意します。
具体的には、透明度だけを持つ白色テクスチャです。
透明度には、パーリンノイズのテクスチャを使用します。

ovicon64
今回は、高機能な画像補正専用ツールの「オレンジビューア」を使用します。
このツールも、Javaをインストールする必要があります。

nami100
今回は、256×256のパーリンノイズなので、256×256の白色の画像を作成して
ビューアで読み込みを行います。

nami101
「色」→「アルファチャンネル」→「ファイルから読み込む…」で
さきほどの256×256のパーリンノイズを指定します。

nami102
これで、パーリンノイズのアルファチャンネルを持つ白い画像を作成できました。
「ファイルを名前を付けて保存」で、「xxx.png」と拡張子を「png」にして保存してください。
このソフトは、保存するときの拡張子によって自動で、その形式で保存されます。
pngにする理由は、透明度情報を持たせるためです。

noise2

完成した画像がこちらになります。(テクスチャC


テクスチャを設定しよう

立方体を作成して、薄くします。
これを水面にします。

nami200
色は水のような青色にします。

nami201
テクスチャの拡散反射の設定は「テクスチャC」を使います。

nami202
バンプマップの設定は、「テクスチャB」を使います。

nami203
鏡面反射の色成分の設定は「テクスチャA」を使います。

水平スケール、垂直スケールは、面の面積に合わせて、適度な値にしておきましょう。

nami301
この時点で、だいぶ水面のようになります。

nami302
あとは、2枚重ねておきましょう。
また、2枚目に関しては、あとでスクロールの関係上、180度回転させておきましょう。


スクロールさせる

波テクスチャを設定した重ねたオブジェクトの2つ、
それぞれにスクリプトを入れて、テクスチャをスクロールさせます。

スクロールは、かなり遅いほうがそれっぽいです。
スクリプトはこんな感じです。

start() {
	integer	mode	=	ANIM_ON | SMOOTH | LOOP;
	integer	face	=	ALL_SIDES;
	integer	sizex	=	0;
	integer	sizey	=	0;
	float	start	=	0.0;
	float	length	=	0.0;
	float	rate	=	0.005;
	llSetTextureAnim(0, face, 0, 0, 0.0, 0.0, 0.0);
	llSetTextureAnim(mode, face, sizex, sizey, start, length, rate);
}
default{state_entry(){start();}}

これを入れ込んで、実行させるとスクロールが始まります!

これで綺麗な水面の完成です!

テテーン!

これで私の「セカンドライフ技術系 Advent Calendar 2015」の記事は終わりです。
このような発表のきっかけを用意していただきました、sabroさんありがとうございました。

今回の記事を通して、3Dのテクスチャの作成や設定のテクニックに興味を持っていただければ幸いです・・・!

Written by なたで

2015年12月13日 at 00:00

セカンドライフでジャンプ台を作る

leave a comment »


はじめまして。

セカンドライフ技術系 Advent Calendar 2015」の12月10日(木)担当の なたで です。
今回、初めての参加ですがよろしくお願いします。

今日は、セカンドライフでジャンプ台のオブジェクトを作ります。

baned001
上から乗るとぼよーんとするジャンプ台です。

解説の要点

・ジャンプ台のバネの部分を、Metasequoia でモデリングしよう
・ジャンプ台の上から押したときにだけ、ぼよーんとするスクリプトを書こう


ジャンプ台のバネの部分を、モデリングしよう

Metasequoia を作ってモデリングしていきます。

bane001bane002
ドーナッツ型の基本図形からバネを作成していきます。
細かいほうが綺麗に作れますが、単純化のために8角形でいきます。

bane003
bane004
8角形のうち、7つの角を選択して、上に 10 上げます。
複数の選択は、Shiftを押しながら選択でするとできます。

bane005
bane006
先ほど選択した 7つの角のうち、端っこの選択を解除して、上に 10 上げます。
解除は、Ctrlを押しながら選択すると解除できます。
端から 1 つずつ選択を解除しては 10 上げる作業を繰り返していきます。

bane007
これで、すこしラセンっぽくなりました。
この形をたくさんつなげれば、バネができそうです。

bane008
2回転にするために、上にコピーしましょう。

bane009
段差が変な部分の面を削除して、

bane010
上下のラセンのオブジェクトの断面を、面を繋げていきます。

bane011
これでバネが完成しました。

bane012bane013
あとは、木のような台を上下にくっつけて、ジャンプ台のモデルは完成です。
物理判定用のオブジェクトは適当に立方体で作成しておきます。
なお、この時点で、セカンドライフ上の軸と合うように回転させておきましょう。
右が赤のX座標。前が緑のY座標。上が青のZ座標です。
(→詳しくは、 軸についての記事

bane014
これをアップロードします。
アップロード直後は、
プレビューのように予め初期値で回転されているので、回転をもどして…

bane015
できました。


ジャンプ台のスクリプトを書こう

ジャンプ台のスクリプトを書いていきます。。

衝突時の判定は「collision_start」を使用するとよさそうです。
衝突したアバターを、指定した方向へ「llPushObject」で飛ばせばいいのです。
(※土地の設定を、プッシュ許可にしておくこと)

上記の関数を使って「衝突したアバターを、上へ飛ばす」プログラムを書いていくのですが、
単純に実装しようとすると、いくつか問題があることに気がつきます。

baned002
左右からジャンプ台にぶつかった場合でも上へ飛んでしまうのでは?

baned003
ジャンプ台が傾いていた場合に、上から踏んだ場合はどのようにすれば?

……などなど。

解決策

上記のあらゆる問題を解決するために、ジャンプ台で飛ばす方向に対して、
衝突したアバターの「めり込み具合」を調べて、正しく判断させようと思います。

今回は、ベクトルの計算が必要です。
ベクトルというのは、(x, y, z)など、たくさんの値をもつデータのようなものです。
なお、通常の1つしか値ない場合は、スカラーと呼びます。

今回は2種類のベクトルを使用します。

baned004
1つ目に、法線ベクトルがあります。
法線ベクトルは、物体の表面から垂直に伸びたベクトルです。

今回は、バネの向いている方向を、法線ベクトルとして考えたいと思います。
このベクトルは N と呼び、長さ(ノルム)を1とし、単位ベクトルとします。

なお、長さを1にすることで、純粋に方向のみを表すことができます。
よく使用するテクニックなので覚えておくといいかもしれません。
具体的には、「ベクトル」÷「ベクトルの長さ」の計算をすると、長さを1にできます。

baned005
次に、速度ベクトルVです。
衝突したオブジェクトがどの方向へどの速度で向かっているかとなります。
方向はオブジェクトが向かっている方向、長さは速度に相当します。

baned006
衝突したある点からの法線ベクトル N と、衝突時の速度ベクトル V があったときに、
このめり込み具合 d の長さを計算します。

実は、簡単に計算ができる方法あります。

baned007

こんな式です。

こんな計算で、dが求められるのと思うかもしれませんが、求められます。
ベクトル同士を「・」すると、内積となり、
細かいことはおいておいて、dが求まります。

baned008

Nが単位ベクトル(長さ=1)となっているところがポイントです。
すこし難しいですが、ベクトルVを、単位ベクトルであるNに射影したベクトルのノルムが内積となります。

さて、スクリプトは次のようになります。

integer i;

list		get_details			= [ PRIM_SIZE, PRIM_POSITION, PRIM_ROTATION ];
vector		object_size			= <0, 0, 0>;
vector		object_position		= <0, 0, 0>;
rotation	object_rotation		= <0, 0, 0, 1>;
vector		target_velocity		= <0, 0, 0>;
vector		object_normal		= <0, 0, 0>;
vector		object_normal_inv	= <0, 0, 0>;
vector		refrect_vector		= <0, 0, 0>;

// 設定値
float		power					= 3.0;	// 少しの力でもある程度飛ばす
float		rebound					= 2.0;	// 何倍の力で戻すか
float		threshold				= 0.5;	// この閾値より低いと吹き飛ばさない

getData() {
	list list_details = llGetPrimitiveParams(get_details);
	object_size		= llList2Vector(list_details, 0);
	object_position	= llList2Vector(list_details, 1);
	object_rotation	= llList2Rot(list_details, 2);
	object_normal		= <0, 0, 1> * object_rotation; // バネの上の向きベクトル
	object_normal_inv	= <0, 0, -1> * object_rotation; // バネの下の向きベクトル
}

onStartCollision(integer num_detected) {
	// 使用するオブジェクトのデータの取得
	getData();
	
	// めり込み度を計算(内積)
	float merikomi		= target_velocity * object_normal_inv;
	
	// あまり押されていなかったら、処理しない
	if(merikomi < threshold) {
		return;
	}
	
	// 跳ね返すベクトル
	refrect_vector	= (object_normal * (llFabs(merikomi) + power) ) * rebound;
	
	// プッシュ!
	for(i = 0; i < num_detected; i++) {
		llPushObject(llDetectedKey(i), refrect_vector, <0, 0, 0>, FALSE);
	}
	
	// オブジェクトが一瞬縮んで、押された感じにする
	vector object_position_new	= object_position + object_normal_inv * (object_size.z * 0.5);
	vector object_scale_new		= <object_size.x, object_size.y, object_size.z * 0.5>;
	llSetScale(object_scale_new);
	llSetPos(object_position_new);
	llSleep(0.1);
	llSetPos(object_position);
	llSetScale(object_size);
}

default {
	collision_start(integer num_detected ) {
		// 即速度を取得しないと、止まった時の速度を取得してしまう
		target_velocity = llDetectedVel(0);
		// 後はこの中で処理
		onStartCollision(num_detected);
	}
}

以上、「ジャンプ台を作る」の解説を終えます!
お疲れ様でした!

Written by なたで

2015年12月10日 at 00:00

カテゴリー: 3D, program

Tagged with , ,

セカンドライフでタッチすると音がでるやつ

leave a comment »


セカンドライフでタッチすると音がでるスクリプト。
特に連打防止機能をつけたものです。
具体的には1人に対して、 X 秒間次のクリックを防止します。
大勢からクリックされても、その1人1人に X 秒間のクリック防止機能が働きます。

当初は、連打すればするほどその人にペナルティー機能とかつけたりしましたが、
結局シンプルなコードへ落ち着きました。

技術的なアピールポイント
・メモリ断片化とかが発生しないように配列を使用していない
・少し冗長でもシンプルで分かりやすい
・連打防止のために時間を扱うが、ポーリングによる監視、タイマー割り込みを一切使用せずサーバーにやさしい

SimpleRendaBoushi5.lsl

// シンプルな連打防止機能付き再生機。5人まで管理。
// 自由に改造・配布OKです。
// code by natade

// 最後に押してから、そのユーザーの情報を消すまでの時間
// 長いほど、ユーザーが押せなくなる時間が長くなる
float G_fResetTimeSec	= 60.0;

// TRUE で最初に触った時のユーザーを表示させる
// FALSE で表示させない
integer G_bIsDrawName	= TRUE;

// 何の音を鳴らすか
key G_kSoundData		= "音声のUIDをここに";










// ---------------------------------------------------------------

checkMemory() { if(llGetFreeMemory() < 128) { llOwnerSay("Sorry. I will complete reset for memory release."); llResetScript(); }}

// メモリの再確保が必要であるため、配列は使わない
key G_kTouchUser1		= NULL_KEY;
key G_kTouchUser2		= NULL_KEY;
key G_kTouchUser3		= NULL_KEY;
key G_kTouchUser4		= NULL_KEY;
key G_kTouchUser5		= NULL_KEY;
integer G_bIsSetUser1	= FALSE;
integer G_bIsSetUser2	= FALSE;
integer G_bIsSetUser3	= FALSE;
integer G_bIsSetUser4	= FALSE;
integer G_bIsSetUser5	= FALSE;
float G_fUserTimerSec1		= 0.0;
float G_fUserTimerSec2		= 0.0;
float G_fUserTimerSec3		= 0.0;
float G_fUserTimerSec4		= 0.0;
float G_fUserTimerSec5		= 0.0;
integer G_bIsSetTimer	= FALSE;

// 新規のユーザーIDを取得する
integer getNewUserId() {
	integer	iNewUser = 1;
	float	fMaxTime = G_fUserTimerSec1;
	
	// NULL_KEY を使用する
	if(G_kTouchUser1 == NULL_KEY) {
		return 1;
	}
	else if(G_kTouchUser2 == NULL_KEY) {
		return 2;
	}
	else if(G_kTouchUser3 == NULL_KEY) {
		return 3;
	}
	else if(G_kTouchUser4 == NULL_KEY) {
		return 4;
	}
	else if(G_kTouchUser5 == NULL_KEY) {
		return 5;
	}
	// 時間が最も長いのを再利用する
	if(fMaxTime < G_fUserTimerSec2) {
		iNewUser = 2;
		fMaxTime = G_fUserTimerSec2;
	}
	else if(fMaxTime < G_fUserTimerSec3) {
		iNewUser = 3;
		fMaxTime = G_fUserTimerSec3;
	}
	else if(fMaxTime < G_fUserTimerSec4) {
		iNewUser = 4;
		fMaxTime = G_fUserTimerSec4;
	}
	else if(fMaxTime < G_fUserTimerSec5) {
		iNewUser = 5;
		fMaxTime = G_fUserTimerSec5;
	}
	return iNewUser;
}

// 新規ユーザー登録
addNewUser(key kTouchUser) {
	integer iNewUser = getNewUserId();
	// User 1
	if(iNewUser == 1) {
		G_kTouchUser1		= kTouchUser;
		G_fUserTimerSec1	= 0.0;
	}
	// User 2
	else if(iNewUser == 2) {
		G_kTouchUser2		= kTouchUser;
		G_fUserTimerSec2	= 0.0;
	}
	// User 3
	else if(iNewUser == 3) {
		G_kTouchUser3		= kTouchUser;
		G_fUserTimerSec3	= 0.0;
	}
	// User 4
	else if(iNewUser == 4) {
		G_kTouchUser4		= kTouchUser;
		G_fUserTimerSec4	= 0.0;
	}
	// User 5
	else if(iNewUser == 5) {
		G_kTouchUser5		= kTouchUser;
		G_fUserTimerSec5	= 0.0;
	}
}

// クリックしたユーザー
onTouch(key kTouchUser) {
	if(G_kTouchUser1 == kTouchUser) {
		return;
	}
	if(G_kTouchUser2 == kTouchUser) {
		return;
	}
	if(G_kTouchUser3 == kTouchUser) {
		return;
	}
	if(G_kTouchUser4 == kTouchUser) {
		return;
	}
	if(G_kTouchUser5 == kTouchUser) {
		return;
	}
	if(G_bIsDrawName) {
		llOwnerSay(llGetDisplayName(kTouchUser) + "さんが触りました…");
	}
	addNewUser(kTouchUser);
	llPlaySound(G_kSoundData, 1.0);
}

actTimeCorrection() {
	float fDeltaTime = llGetTime();
	// 時間を超えたものを削除する
	if(G_kTouchUser1 != NULL_KEY) {
		G_fUserTimerSec1 += fDeltaTime;
		if(G_fResetTimeSec <= G_fUserTimerSec1) {
			G_kTouchUser1	= NULL_KEY;
		}
	}
	if(G_kTouchUser2 != NULL_KEY) {
		G_fUserTimerSec2 += fDeltaTime;
		if(G_fResetTimeSec <= G_fUserTimerSec2) {
			G_kTouchUser2	= NULL_KEY;
		}
	}
	if(G_kTouchUser3 != NULL_KEY) {
		G_fUserTimerSec3 += fDeltaTime;
		if(G_fResetTimeSec <= G_fUserTimerSec3) {
			G_kTouchUser3	= NULL_KEY;
		}
	}
	if(G_kTouchUser4 != NULL_KEY) {
		G_fUserTimerSec4 += fDeltaTime;
		if(G_fResetTimeSec <= G_fUserTimerSec4) {
			G_kTouchUser4	= NULL_KEY;
		}
	}
	if(G_kTouchUser5 != NULL_KEY) {
		G_fUserTimerSec5 += fDeltaTime;
		if(G_fResetTimeSec <= G_fUserTimerSec5) {
			G_kTouchUser5	= NULL_KEY;
		}
	}
	llResetTime();
}

default {
	state_entry() {
	}
	changed( integer change ) {
		if( change & CHANGED_INVENTORY ) {
			llResetScript();
		}
		else if( change & CHANGED_OWNER ) {
			llResetScript();
		}
	}
	touch_start(integer num_detected) {
		if(num_detected == 1) {
			key touch_key = llDetectedKey(0);
			checkMemory();
			actTimeCorrection();
			onTouch(touch_key);
		}
	}
}

Written by なたで

2015年9月16日 at 23:31

カテゴリー: program

Tagged with ,

セカンドライフのパーティクルシステムの特殊設定

leave a comment »


今回は、パーティクルシステムについてのお話です。
というか、自分用のメモです。

パーティクルは、llParticleSystem / llLinkParticleSystem で、
パーティクルシステムの設定を行うとできるのですが、
パラメータがたくさんあり、使うのが面倒です。

今回、日本語wikiにはなく、この英語wikiに書いてあるのについてまとめてみます。
なお、英語用のwikiは、「llParticleSystem翻訳)」です。

・System Behavior(パーティクルのふるまい)
Value
– PSYS_PART_RIBBON_MASK

・Particle Appearance(パーティクルの外観)
PSYS_PART_START_GLOW
PSYS_PART_END_GLOW

・Particle Blending(パーティクルブレンド)
PSYS_PART_BLEND_FUNC_SOURCE
PSYS_PART_BLEND_FUNC_DEST
Values
– PSYS_PART_BF_ONE
– PSYS_PART_BF_ZERO
– PSYS_PART_BF_DEST_COLOR
– PSYS_PART_BF_SOURCE_COLOR
– PSYS_PART_BF_ONE_MINUS_DEST_COLOR
– PSYS_PART_BF_ONE_MINUS_SOURCE_COLOR
– PSYS_PART_BF_SOURCE_ALPHA
– PSYS_PART_BF_ONE_MINUS_SOURCE_ALPHA

上から順番に説明していきましょう。

【PSYS_PART_RIBBON_MASK】
リボンパーティクル と呼ばれるものを作成します。
途切れ途切れになっていたパーティクルの端を結合します。
リボンの「幅」は、 scale_start.x と scale_end.x に依存します。
リボンのセグメントの長さは、粒子間の距離に依存します。もし距離が 0 ならばレンダリングしません。
リボンパーティクルは、通常のパーティクルと違い常にカメラの方向を向くようにレンダリングしません。
PSYS_PART_FOLLOW_VELOCITY_MASK の設定は無視されます。
次の設定を併用することで効果を確認できます。
・PSYS_SRC_TEXTURE (テクスチャは TEXTURE_BLANK
・PSYS_SRC_PATTERN_DROP
・PSYS_SRC_ACCEL
・PSYS_PART_WIND_MASK

【PSYS_PART_START_GLOW / PSYS_PART_END_GLOW】
パーティクルの発生時と消滅時のグローのかかり具合を調整します。

【Particle Blending】
パーティクルを描写するときの合成方法を設定します。
内部では、OpenGLの「glBlendFunc」を使用して実装しています。
Visual glBlendFunc + glBlendEquation Tool」を使うと分かりやすいと思われます。
組み合わせはたくさんありますが、一般的な合成方法だけをここに書きます。

◆加算合成◆
PSYS_PART_BLEND_FUNC_SOURCE, PSYS_PART_BF_ONE,
PSYS_PART_BLEND_FUNC_DEST, PSYS_PART_BF_ONE

◆アルファブレンド合成◆
PSYS_PART_BLEND_FUNC_SOURCE, PSYS_PART_BF_SOURCE_ALPHA,
PSYS_PART_BLEND_FUNC_DEST, PSYS_PART_BF_ONE_MINUS_SOURCE_ALPHA

他に組み合わせでスクリーン合成などはできそうですが、
減算合成はできないようです。

Written by なたで

2015年8月6日 at 22:19

カテゴリー: 3D, memo, program

Tagged with , ,