VRML with JavaScript Tutorial

戻る はじめに / アニメーション

任意の方向への移動

一連の動きを繰り返すのではなく、物体を任意の方向に任意の速度で動かす場合について。

一定短時間ごとの移動

Webブラウザーの JavaScript には setInterval メソッドがあり、指定した時間間隔ごとに指定した関数を実行させることができます。  VRML の JavaScript に setInterval はありませんが、TimeSensor ノードを使って一定時間ごとに物体を前進させるスクリプトを組んでみました。
VRML - ソース / X3D - ソース
赤い球がX-Y平面の青いフィールド上を直進します。  青いフィールドから外に出そうになると、跳ね返って進行方向を変えます。
DEF Sc Script {
eventIn SFTime set_cycleTime
eventOut SFVec3f posBall
field SFVec3f moveVec 0 0 0
url "javascript:

function initialize () {

// 赤球の進行方向をセットする。
moveVec.x = Math.random() * 2 - 1;
moveVec.y = Math.random() * 2 - 1;
moveVec.z = 0;

// 赤球のスピードを 0.05 にセットする。
moveVec = moveVec.normalize().multiply( 0.05 );

// 赤球の位置をセットする。
posBall.x = Math.random() * 2 - 1;
posBall.y = Math.random() * 2 - 1;
posBall.z = 0;

}

function set_cycleTime () {

// 赤球の新しい位置をセットする。
posBall = moveVec.add( posBall );

// もし赤球が青箱の外に出るならば、進行方向を変えて箱の内側に移す。
if ( posBall.x > 2.3 || posBall.x < -2.3 ) {

moveVec.x = -moveVec.x;
posBall.x = Math.max( -2.3, Math.min( 2.3 , posBall.x ));

}

if ( posBall.y > 2.3 || posBall.y < -2.3 ) {

moveVec.y = -moveVec.y;
posBall.y = Math.max( -2.3, Math.min( 2.3 , posBall.y ));

}
}

"
}
変数 moveVec は赤い球の移動ベクトルです。  initialize 関数でこのベクトルの長さを 0.05 に設定しています。  TimeSensorcycleInterval 0.05 及び loop TRUE と設定されているので、 set_cycleTime 関数は 0.05 秒 ( 1/20 秒 ) ごとに実行され続けます。  赤い球は 0.05 秒ごとに 0.05 の距離だけ移動することになり、1 秒間で 1 の距離を進む計算になります。

しかし、この作例をどのVRMLブラウザーで実行させてみても動きが滑らかではありません。  調べてみていただければ分かりますが、実際には set_cycleTime 関数は 0.05 秒ごとに実行されていることは殆どなく、それ以上の時間がかかっています。  このため球は 1 秒間に 1 の距離も進みません。

経過時間の分だけ移動

TimeSensor が一定時間ごとに動かないのであれば、逆に TimeSensor が進行した分だけ物体を動かすようにしてみましょう。 
VRML - ソース / X3D - ソース
赤い球がX-Y平面の青いフィールド上を直進します。  青いフィールドから外に出そうになると、跳ね返って進行方向を変えます。
この例は先の例と比べて、赤い球は滑らかに移動します。  ( Cortona VRML Client 4.x ならば、実行環境によってはあまり滑らかでないかもしれません。)
DEF Sc Script {
eventIn SFFloat set_fraction
eventOut SFVec3f posBall
field SFVec3f moveVec 0 0 0
field SFFloat oldF 0
url "javascript:

function initialize () {

// 赤球の進行方向をセットする。
moveVec.x = Math.random() * 2 - 1;
moveVec.y = Math.random() * 2 - 1;
moveVec.z = 0;

// 赤球のスピードを 1 にセットする。
moveVec = moveVec.normalize();

// 赤球の位置をセットする。
posBall.x = Math.random() * 2 - 1;
posBall.y = Math.random() * 2 - 1;
posBall.z = 0;

}

function set_fraction (f) {

// fraction の変化量を得る。
var df = f - oldF;
if ( df < 0 ) df++;
oldF = f;

// 赤球の新しい位置をセットする。
posBall = moveVec.multiply(df).add(posBall);

// もし赤球が青箱の外に出るならば、進行方向を変えて箱の内側に移す。
if ( posBall.x > 2.3 || posBall.x < -2.3 ) {

moveVec.x = -moveVec.x;
posBall.x = Math.max( -2.3, Math.min( 2.3 , posBall.x ));

}

if ( posBall.y > 2.3 || posBall.y < -2.3 ) {

moveVec.y = -moveVec.y;
posBall.y = Math.max( -2.3, Math.min( 2.3 , posBall.y ));

}
}


"
}
TimeSensor がどの程度進行したかは fraction_changed の変化量を調べます。  fraction_changed の変化量 df については 一時停止と再スタート をご覧ください。  この df は set_fraction 関数が実行される時刻の間隔に比例します。

この例では moveVec の長さは常に 1 のままであり、この長さを変化させていません。  新しい赤球の位置を決める際に、moveVec を df 倍した長さのベクトルをその都度導くことにより、経過した時間に応じた移動量を割り出しています。
posBall = moveVec.multiply(df).add(posBall);
参考:SFVec3f オブジェクト

赤い球が 1 秒間に移動する距離は 「 moveVec の長さ」 を 「 TimeSensorcycleInterval 」で割った値となります。  この例では moveVec の長さと cycleInterval は共に 1 である為、赤い球が 1 秒間に移動する距離は 1 となります。

この例は先の例と比べてCPUなど実行環境への負荷は高くなりますが、動きは滑らかになります。  但し、実行環境の処理能力と比べてVRML作品の負荷が著しく高い場合など、 fraction_changed のイベント時間間隔が TimeSensorcycleInterval よりも長くなると正しく動きません。

フレームレートから移動量を得る

大抵のVRMLブラウザーでは TimeSensorfraction_changed は画面書き換えごとに出力される為、 TimeSensorcycleInterval が 1 ならば、fraction_changed の変化量 df は「画面書き換えにかかった時間」とほぼ同じになります。  画面書き換えにかかった時間 ( seconds per frame ) はフレームレート ( frames per second ) の逆数です。  フレームレートは Browser オブジェクトの getCurrentFrameRate メソッドで得ることができます。  このメソッドの帰り値の逆数を df の変わりに使うスクリプトを組んでみました。
VRML - ソース / X3D - ソース
赤い球がX-Y平面の青いフィールド上を直進します。  青いフィールドから外に出そうになると、跳ね返って進行方向を変えます。
Cortona VRML Client 4.x ならば、先の例でガタガタとした動きだったとしても、この例ならば滑らかに動くかもしれません。  但し、Cortona ならばどのバージョンでも表示開始直後の最初の約 1 秒間は球が動きません。
DEF Sc Script {
eventIn SFFloat set_fraction
eventOut SFVec3f posBall
field SFVec3f moveVec 0 0 0
field SFFloat oldF 0
url "javascript:

function initialize () {

// 赤球の進行方向をセットする。
moveVec.x = Math.random() * 2 - 1;
moveVec.y = Math.random() * 2 - 1;
moveVec.z = 0;

// 赤球のスピードを 1 にセットする。
moveVec = moveVec.normalize();

// 赤球の位置をセットする。
posBall.x = Math.random() * 2 - 1;
posBall.y = Math.random() * 2 - 1;
posBall.z = 0;

}

function set_fraction () {

// フレームレートの逆数を得る。
var fps = Browser.getCurrentFrameRate();
var spf = ( fps > 0 ) ? 1 / fps : 0;

// 赤球の新しい位置をセットする。
posBall = moveVec.multiply(spf).add(posBall);

// もし赤球が青箱の外に出るならば、進行方向を変えて箱の内側に移す。
if ( posBall.x > 2.3 || posBall.x < -2.3 ) {

moveVec.x = -moveVec.x;
posBall.x = Math.max( -2.3, Math.min( 2.3 , posBall.x ));

}

if ( posBall.y > 2.3 || posBall.y < -2.3 ) {

moveVec.y = -moveVec.y;
posBall.y = Math.max( -2.3, Math.min( 2.3 , posBall.y ));

}
}

"
}
先の作例の fraction の変化量 df をフレームレートの逆数 spf に置き換えています。  高速で処理できる環境で実行されるほど、フレームレート fps は大きくなり、spf は小さくなります。

Browser オブジェクトの getCurrentFrameRate メソッドはVRMLブラウザーごとにクセがあるので気を付けなくてはいけません。  まず、Cosmo Cortona Contact 何れのブラウザーでもメソッドが 0 を帰す場合があるので、 これの逆数を得るときに 0 で割らないようにしなくてはいけません。

Cosmo の場合、実行環境によってはメソッドの帰り値の最大の実数は 200 であり、それ以上の場合は Infinity を返します。  また、画面に動いている物が全くない状態では、フレームレートは低く押さえられます。  この何も動いていない状態から何かを動かした際、短時間だけですが実際の画面書き換え速度と比べてメソッドの帰り値は低いままになっており、その逆数は正しい値よりも大きくなる傾向にあります。  それ故、止まった状態からのアニメの開始直後は通常よりも速いスピードで動作します。  よって、これらのサンプルのような全く止まった状態から動き出す作品には向きません。

Cortona の場合、表示開始直後の約1秒間にわたりメソッドは 0.0 を返します。 何も動いていない状態でも、フレームレートが低く押さえられることはありませんが、フレームレートの値が更新されるのは約 1 秒ごとであり、その間メソッドを何回実行しても同じ値しか返しません。  その為、フレームレートが大きく変わた直後の約 1 秒間は、正しいスピードから大きく外れることがあります。

Contact の場合、TimeSensorfraction_changed は画面書き換えごとに出力されるという大前提が成り立っていない場合があります。  CPU や グラフィック が高性能である場合など、極端にフレームレイトが上昇している状態ならば、fraction_changed が間引いて出力されることがあります。  このような場合、通常と比べて移動量が極端に落ちたり殆ど止まったりします。

この通りフレームレイトから移動量を得る方法は扱いが難しいので、極力使わないほうがいいでしょう。
このページのトップ | 前へ アニメデータの生成 | 次へ サンプル集