2009年6月25日木曜日

振子の研究

前回インデントについての私の考えを書きました。今回あるソースを入手しましたので、それを紹介します。
ソースの表示はインデントの様子も良く分るように、Hidenoriさんのブログに紹介されていた、LSLコード色づけをしました。

integer sw=1;
rotation rot;
integer n = 5;
 
default
{
state_entry()
{ // ここはオールマン記法
rot=llGetRot(); // タブ幅が4+3
}
 
touch_start(integer num)
{ // ここもオールマン記法
if(sw==1){ sw=0; // なんで{の後に文が続く? + ここはK&R記法 + タブ幅が4+3
llSetTimerEvent(3.0); // タブ幅が4+3+2 だんだん小さくなるんですね
}else if(sw==0){ sw=1; // なんで{の後に文が続く? + ここはK&R記法
llSetTimerEvent(0.0); // タブ幅が4+3+2 だんだん小さくなるんですね
llTargetOmega(<0,0,0>,0.0,3); // タブ幅が4+3+2 だんだん小さくなるんですね
llSetRot(rot); // タブ幅が4+3+2 だんだん小さくなるんですね
} // タブ幅が4+3
}
 
timer(){ // ここはK&R記法
llTargetOmega(<n,0,0>*rot,0.1,3); // 一転タブ幅が4+5
llSleep(1.5); // 一転タブ幅が4+5
n = -n; // 一転タブ幅が4+5
llTargetOmega(<n,0,0>*rot,0.1,3); // 一転タブ幅が4+5
n = n; // 一転タブ幅が4+5
} // タブ幅3・・・もしかして上は4+5と思っていたけど違うのか?
 
on_rez(integer param) // タブ幅3
{ // ここはオールマン記法 // タブ幅3
llResetScript(); // 一転タブ幅が3+5
} // タブ幅3
}


元のソースコードには一切のコメントがなかったため、以下は、小菅がインデントに関してのコメントをつけました。
この中で私が一番問題と思ったのが、「if(sw==1){ sw=0;」と、条件判断と同じ行に実行文が記述してあるところです。これ、学生にもいつも口を酸っぱくして言っていますが、ソースコードを読むときに常に気になるのが視線の動きです。
人間の目(視線)は、まず左上から、右方向、そして下方向に流れます。それが現代日本で教育を受けた人間の常識です(きっとアラビア語圏で教育を受ければ、右上から右へ、次いで下へとなるのかな)。
プログラマはそれまでの経験からプログラムが下に続いていることを知っているため、上から下にソースコードを読む癖がついているのが普通です。このように、if (条件) { の後に何かが続くことがあるとすれば、それは通常コメントです。そのような場所に、実行文を書かれてしまうと、うっかり読み落とす危険があります。
それにコードが読みにくいと、こんどは論理を追えなくなってくる。プログラマがソースコードを読む目的はプログラムの論理を追うためです。ですので、それを邪魔するすべての要素はすべて取り除いて、書式に則ったソースコードを書くことが、まず最低の必要条件だと学生には教えています。

そこで、以下では、私が使用しているK&R記法に則って、このソースコードを書き換えました。

  1: integer     sw = 1;
2: rotation rot;
3: integer n = 5;
4:
5: default
6: {
7: state_entry() {
8: rot = llGetRot();
9: }
10: touch_start(integer touched_num) {
11: if (sw == 1) {
12: sw = 0;
13: llSetTimerEvent(3.0);
14: } else if (sw == 0) {
15: sw = 1;
16: llSetTimerEvent(0.0);
17: llTargetOmega(<0, 0, 0>, 0.0, 3);
18: llSetRot(rot);
19: }
20: }
21:
22: timer() {
23: llTargetOmega(<n, 0, 0>*rot, 0.1, 3);
24: llSleep(1.5);
25: n = -n;
26: llTargetOmega(<n, 0, 0>*rot, 0.1, 3);
27: n = n;
28: }
29: on_rez(integer param) {
30: llResetScript();
31: }
32: }


ふむ。このソースを見ていただければ分かるでしょうがこれは「やじろべい」のスクリプトだそうです。
そしてこのソースにある問題は、インデントやプログラム書式だけではない。以下に箇条書きで気づいた点を書きました。

・2、3行目: グローバル変数として、rot、n などというどこでも使われる可能性がある名前は不適切です。良くある使い方としては、変数名をすべて大文字にしてしまう、変数名の先頭をGなどで始めるなどの工夫が必要です。
・8行目: state_entry中でrotに現在の回転値を保存しているが、ここを通過するのはスクリプトが初期化された時のみです。「やじろべい」を出してから、角度を変えても反映されません。rotに現在の回転値を保存するのを、14〜16行目付近で行えばこの問題は改善します。
・11、12行目他: sw変数の値に0および1と比較または代入しているが、BOOLEANの代わりにintegerを使用しているのですからTRUEやFALSEを使うべきです。
・17行目他: llTargetOmega関数の第1引数の型はvectorですが、その各要素はfloatなので、<0,0,0>ではなく<0.0, 0.0, 0.0>と書くべきです。
・17行目他: llTargetOmega関数の第3引数は、float gain ですが、3(この値自身根拠不明)とinteger になっています。3.0と書くべきです。
・23行目他: 上にも関連しますが、vector型の各要素は、float です。integer の n を指定するのはおかしい。というよりも、ここでは、n を float として宣言すべきです。そしてそれならば、n という変数名は不適切です。
・23、26行目: llTargetOmega関数の第1引数の型は回転の軸を表すベクトルが正規化されていない。実験してみると、非物理オブジェクトに対して、llTargetOmega(vector axis, float spinrate, float gain)関数で、ビュワー側回転を与えると、回転の角速度は llVecMag(axis)*spinrate で決定されるように様に見える・・・要は「llTargetOmega(<0.0, 0.0, 2.0>, PI_BY_TWO, 1.0)」と「llTargetOmega(<0.0, 0.0, 1.0>, PI, 1.0)」は同じ回転を与える・・・しかし、axisは単位ベクトルにし、spinrate で角速度を与える方が直感的です。
・23、26行目: 逆向きの回転を作るのに、回転の軸を表すベクトルの向きを逆にしているが、前述のように第2引数のspinRateをにした方が直感的に何をしているのかわかりやすい。
・25、26行目: 回転軸のベクトルの向きを逆にするだけならば、26行目は「llTargetOmega(<-n, 0, 0>*rot, 0.1, 3); 」で良いはず。わざわざnの符号を反転させる必要はない。しかも、前にも述べたように、回転の向きを逆転させるのであれば、回転軸を操作するのではなく、角速度を逆にすべき。
・27行目: 「n = n;」脳みそ解けていませんか? 大丈夫ですか? この部分について、説明を求めたところ、25行目でnの符号を反転させたのを元に戻しているというとのことで、後で分かるようになると説明があったが・・・。
・そもそも論ですが、やじろべいのように、同じ位置で動揺するものの動きをllTargetOmegaで作成するのはかなり無理があることが予想できます。なぜならばllTargetOmegaは非物理オブジェジェクトに対しては、完全にローカルであるビュワー側処理になります。サーバは回転の命令を出すだけですので、誰もが同じ状態を見ているとは限らない。そしてサーバ側からの回転命令が到着するタイミングが少しでもズレれば、変な位置で動揺することになるでしょう。事実、動揺の中心位置はずれまくりでした。

実はこのコードは実際に使われているコードではないとのことです。このコードはある勉強会のときにサンプルコードとしてもらったのですが、その時に我が耳を疑ったのが「このスクリプトを元に、各自工夫してスクリプトを作って見てください」という言葉です。
これは、まだ経験が浅い私が感じたことなので、まるで見当外れという可能性もあるのですが(もしそうならご指摘ください)、このソースをどのようにひねくり繰り返しても、事態が悪化するばかりで、何も得られるものはないでしょう。わたしは、このソースを部分的に書き換えて、少なくとも私が納得できるソースにして掲載することを考えましたが、スクラッチで書いてしまった方が、絶対に早いため断念しました。
以下がオリジナルを考慮してllTargetOmegaを利用してなめらかに、やじろべいが動作するように、スクラッチで書いたソースです。といっても動きすら変わってしまっていますが(苦笑。
実はこのソース、やじろべいの軸部分に仕込んでもうまく動作しません。それは見た目の位置と、実際の位置のずれを修正するために、定期的に一瞬物理属性を付与する処理を行っていますが、そのときにやじろべいの重心の位置と、編集位置の差にある関係が成り立っていないと、静的な安定性が得られないのが原因のように見えます。ただしこの問題については、感触程度の物で実際に検証した訳ではありません。

//
// YAJIROBEI
//
float GPeriod = 0.5; // Half Cycle
vector GRotAxis = <0.0, 0.0, 1.0>; // Rotation Axis
float GSRFactor = 16.0; // SpinRate Factor
 
// do not change under value
float GGravAccel = 9.80665; // gravitational acceleration
integer GCount = 0;
integer GFlag = FALSE;
float GSpinRate;
vector GNowRotAxis;
 
// いったんやじろべいの動きを止め、瞬間的に物理属性を付与することにより
// 実際の位置と見た目の位置を一致させる処理 これを周期的に実行する
// これは、midori Paulse さんに教えていただきました。
resetRotation()
{
llTargetOmega(ZERO_VECTOR, 0.0, 0.0);
llSetPrimitiveParams([PRIM_PHYSICS, TRUE, PRIM_PHYSICS, FALSE]);
}
 
default
{
on_rez(integer start_param) {
llResetScript();
}
state_entry() {
llTargetOmega(ZERO_VECTOR, 0.0, 0.0);
// 瞬間的に物理属性を与えたときに落ちないように上向きの力を付与
llSetForce(<0.0, 0.0, GGravAccel*llGetMass()>, FALSE);
GSpinRate = PI/GSRFactor;
}
 
touch_start(integer num_detected) {
if (llDetectedKey(0) == llGetOwner()) {
GFlag = !GFlag;
if (GFlag) {
GCount = 0;
GNowRotAxis = llVecNorm(GRotAxis*llGetRot());
llSetTimerEvent(0.1);
} else {
llSetTimerEvent(0.0);
resetRotation();
}
}
}
timer() {
llSetTimerEvent(0.0);
if ((GCount & 0x03) == 0) {
if ((GCount & 0x17) == 0) {
resetRotation();
}
llTargetOmega(GNowRotAxis, GSpinRate, 1.0);
} else if ((GCount & 0x03) == 1) {
llTargetOmega(GNowRotAxis, -GSpinRate, 1.0);
} else if ((GCount & 0x03) == 2) {
llTargetOmega(GNowRotAxis, -GSpinRate, 1.0);
} else if ((GCount & 0x03) == 3) {
llTargetOmega(GNowRotAxis, GSpinRate, 1.0);
}
GCount = ++GCount & 0xffff;
llSetTimerEvent(GPeriod);
}
}


このドキュメントは、このソースを書き、そして学習会のサンプルとして公開した人に対する個人攻撃と受け取られかねない内容です。今後トラブルの種になることも、予想できます。しかしあえてこのドキュメント公開した理由を以下で述べます。

個人がどのようなソースを、たとえそれが問題だらけで、正常に動作しないソースであったとしても、まったく構わないと思っています。また、そのようなソースをWebで公開したとしても、わたしが、この様なドキュメントを書くことはなかったと思います。しかし、これはスクリプト初心者に向けた勉強会でサンプルとして配布された物です。そのような目的としては不適当なサンプルソースであると考えざるおえません。

また、このログラムを書き、そして学習会のサンプルとして公開した人に「ダメだ、お前は」と言うつもりもありません。ただ、スクリプト初心者に向けた勉強会でサンプルとしては、前述のように不適切と考えられますので、今後の更なる研鑽をお願いしたい。ということです。
学習会には多くのスクリプト初心者が参加しています。その方達は真剣に「スクリプトがかけるようになりたい」と思って参加されているのだと思います。その方達に対して、真剣にスクリプトの勉強会に取り組んでくださるようお願いしたいということです。

もし、スクリプト勉強会の「先生」が、これをごらんになることがあれば、更なる研鑽をお願いしたいと思います。

0 件のコメント:

コメントを投稿