VRML with JavaScript Tutorial

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

タイムセンサー

時間に沿ってアニメーションの経過を指示するための TimeSensor ノードについて、 基本機能だけでは得られない制御方法をスクリプトを用いて実現してみましょう。

アニメーションの基本

基本的なデータの流れ

#VRML V2.0 utf8

DEF Tf Transform {}

DEF TiS TimeSensor {
cycleInterval 5
loop TRUE
}

DEF PI PositionInterpolator {
key [ 0.0, 1.0 ]
keyValue [
0 0 0
2 3 5
]
}

ROUTE TiS.fraction_changed TO PI.set_fraction
ROUTE PI.value_changed TO Tf.translation
3次元ベクトル( SFVec3f ) のキーフレーム法によるアニメーションを実現するための PositionInterpolator ノードを例に説明します。

TimeSensor ノードは fraction_changed フィールドからアニメの1周期の経過を 0.0 〜 1.0 の間の実数で出力します。  PositionInterpolatorset_fraction にイベント入力された 0.0 〜 1.0 の実数を元に、それに応じた補完値を value_changed フィールドからイベント出力します。

この例では、アニメの開始時にTimeSensor ノードは fraction_changed から 0.0 を出力します。  この値を入力された PositionInterpolator は、0.0 の key に相当する ( 0, 0, 0 ) の 値を value_changed から出力します。

例えばアニメの途中で fraction_changed から 0.2 が出力されたならば、value_changed からは ( 0, 0, 0 ) と ( 2, 3, 5 ) の間を 0.2 の割合で補完された値である ( 0.4, 0.6, 1.0 ) が出力されます。

アニメ 1 周期の最後に fraction_changed から 1.0 が出力されたならば、value_changed からは ( 2, 3, 5 ) が出力されます。

PositionInterpolator の補完の様子
set_fraction 0.0 0.25 0.5 0.75 1.0
value_changed ( 0, 0, 0) ( 0.5, 0.75, 1.25) ( 1.0, 1.5, 2.5) ( 1.5, 2.25, 3.75) ( 2, 3, 5)

TimeSensor ノードの cycleInterval フィールドが 5 ならば、アニメの 1 周期は 5 秒間です。  cycleInterval が記述されていない場合は初期値の 1 です。

タイムセンサーの出力動作

TimeSensor ノードの loop フィールドが TRUE ならば、fraction_changed は何周期でも繰り返し出力され、 FALSE ( 初期値 ) ならば startTime にイベント入力された時刻から 1 周期分だけ出力されます。  その様子は下の図をご覧ください。  横軸が時間経過で、縦軸が出力される fraction_changed の値です。  図では赤い繋がった線で示されていますが、TimeSensorfraction_changed を出力するタイミングは不定期であり、 実際には線上のいくつかの点の集まりとなります。

時間経過に対する fraction_changed の値
loop TRUE loop FALSE
loop TRUE の場合の出力 loop FALSE の場合の出力

TimeSensor がどのタイミングで fraction_changed を出力するかはVRMLの仕様では具体的に決められていませんが、 大抵のVRMLブラウザーは画面を書き換えるごとに出力しています。(例外あり)  CPUが高性能である、画面サイズが小さいなど速い処理が見込まれる環境であるほど、fraction_changed は頻繁に出力され、より滑らかなアニメーションとなります。

以下はloop フィールドが TRUE か FALSE かによるアニメーションの違いを示します。  TimeSensorが提供するの基本的なアニメーションはこの2種類です。  (loop FALSE の場合、アニメーション途中で stopTime がイベント入力されると、その時点でアニメーションを中止することもできます。)
VRML - ソース / X3D - ソース
loop TRUE
赤い円錐が画面の真ん中を中心に周回移動し続けます。 
VRML - ソース / X3D - ソース
loop FALSE (初期値のためソースでの記述は省略しています)
赤い円錐をクリックすると、画面の真ん中を中心に1周だけ周回移動します。

ある回数だけ繰り返す

TimeSensor ノードの loop フィールドで、アニメーションを際限なく繰り返す ( TRUE ) か、1回だけで終える ( FALSE ) かを指定できますが、 ある回数だけ繰り返す指定は行えません。  そこで、回数を限定してアニメーションを繰り返すスクリプトを組んでみました。
VRML - ソース / X3D - ソース
赤い円錐をクリックすると、画面の真ん中を中心に3周だけ周回移動します。
DEF Sc Script {
eventIn SFTime resetCounter
eventIn SFBool repeat
eventOut SFTime restartTime
field SFInt32 times 3 # 繰り返す回数
field SFInt32 counter 0 # 繰り返した回数
url "javascript:

function resetCounter () {

counter = 0;

}

function repeat (active,et) {

if (!active) {

counter++;
if ( counter < times ) restartTime = et;

}

}

"
}
初期値のため記述を省略していますが、TimeSensor ノードの loop フィールドは FALSE です。 もっとも、これだけではアニメーションを繰り返しません。

TimeSensor ノードはアニメーションの開始タイミングで TRUE 、終了タイミングで FALSE をイベント出力する isActive フィールドを持っています。  これをスクリプトの repeat にイベントします。  よって、スクリプトの repeat 関数が実行されるのは、赤い円錐が動き出した時点と、1 周し終えた時点です。  引数 active が FALSE ならば 1 周し終えたということで counter を 1 つ加算し、それが周回予定数に満たないならば、もう一度アニメーションをスタートさせます。

ロード直後に自動開始

TimeSensor ノードの loop フィールドが TRUE ならばロード完了の直後からアニメーションを自動開始しますが際限がありません。  FALSE ならば startTime にイベント入力がない限りアニメーションは始まりません。  ロード完了したらアニメーションを1回だけ自動再生するスクリプトを組んでみます。
VRML - ソース / X3D - ソース
ロード完了後の2秒後に、赤い円錐が画面の真ん中を中心に1周だけ周回移動します。  ( この作例は Cortona VRML Client 3.1 では動きません。)
DEF Sc Script {
eventOut SFTime startTime
url "javascript:

function initialize () {

var d = new Date();
var t = d.getTime();
if ( t > 1072224000000 ) t /= 1000;
startTime = t + 2;

}

"
}
ロード完了直後に自動実行される initialize 関数eventIn によって実行される関数とは違い、引数から時刻を得ることはできません。  他の方法で TimeSensor ノードの startTime にイベントする為の時刻を作成します。

JavaScript で現在時刻を得るには、Date オブジェクトの getTime メソッドを使います。  このメソッドは 「1970年1月1日午前0:00 からのミリ秒数」 で表された現在時刻を返します。  それに対して、startTime など VRML で扱う時刻は 「1970年1月1日午前0:00 からの秒数」 で表されます。  このメソッドで得られた時刻を startTime に代入する前に、その値を 1000 で割らなければなりません。

但し、Contact は getTime メソッドで得られた時刻が 「1970年1月1日午前0:00 からの秒数」 で表されるという不具合があります。  この様な場合を考慮して、
1000 (msec) * 60 (sec) * 60 (min) * 24 (hour) * 365 (day) * 34 (year) = 1072224000000
よりも getTime メソッドで得られた時刻が大きい場合のみ、その値を 1000 で割るようにします。

もっとも、Cosmo Cortona Contact 以外のVRMLブラウザーでも 同じ要領で現在時刻が得られるかどうかは個別に確かめる必要があります。



別の方法として ProximitySensor ノードを使う手もあります。 
VRML - ソース / X3D - ソース
この作例もロード完了後の2秒後に、赤い円錐が画面の真ん中を中心に1周だけ周回移動します。  前の作例と違い、Cortona VRML Client 3.1 でも動作します。
DEF Sc Script {
eventIn SFTime enterTime
eventOut SFTime startTime
field SFTime delay 2
url "javascript:

function enterTime (t) {

startTime = t + delay;

}

"
}
ProximitySensor ノードの size フィールドは、ビューアの初期視点が最初から内側に収まるように十分大きくしてください。  そうするとシーン開始直後に ProximitySensor ノードは enterTime を自動的にイベント出力します。  スクリプトはその時刻からアニメーション開始時刻を作り出しています。

もっとも、ロード完了直後に間を入れずアニメーションを開始したい場合、
ROUTE PrS.enterTime TO TiS.startTime
とすれば、スクリプトは必要ありません。

一時停止と再スタート

アニメーションの途中で何らかのイベントにより一時停止と再スタートを行えるスクリプトを組んでみます。  TimeSensor ノードの enabled フィールドを操作して、このノードの機能をオン・オフしてみました。
VRML - ソース / X3D - ソース
画面中央に設置したポーズボタンをクリックする度に、赤い円錐の移動アニメの一時停止と再スタートを行えるようにしてみました。  しかし、この作例では赤い円錐が動いている間にポーズボタンをクリックすると一時停止しますが、もう一度クリックすると全く別の場所から再スタートします。  スクリプトは TimeSensor ノードの enabled フィールドを反転させているだけですが、この方法ではうまくいきません。 enabled が FALSE (機能オフ)の間も fraction_changed をイベント出力しないだけで内部でしっかりとカウントしているからです。

次に、一時停止している間は fraction_changed を更新せずその値を保持するスクリプトを組んでみました。
VRML - ソース / X3D - ソース
先の作例と同じく画面中央に設置したポーズボタンをクリックする度に、赤い円錐の移動アニメの一時停止と再スタートを行えます。  この作例では一時停止した場所からきちんと再スタートを行えます。
DEF Sc Script {
eventIn SFTime pause
eventIn SFFloat set_fraction
eventOut SFFloat fraction_out
field SFBool active TRUE
field SFFloat oldF 0
url "javascript:

function pause () {

active = !active;

}

function set_fraction (f) {

var df = f - oldF;
if ( df < 0 ) df++;
oldF = f;

if (active) {

fraction_out += df;
if ( fraction_out > 1 ) fraction_out--;

}

}

"
}
pause 関数はポーズボタンをクリックする度に実行され、変数 active の値を反転させます。

fractionの変化量 set_fraction 関数は TimeSensorfraction_changed をイベント出力する度に実行されます。  その関数内にある変数 df は、イベント値 f と前回のイベント値 oldF との差であり、fraction_changed の変化量を示します。 fraction_changed は 0.0 から 1.0 の間の実数であり、変数 df は必ず 1 以下の実数となりますが、 例えば、f = 0.05 で oldF = 0.95 である場合など正しい変化量を得られないときは df に 1 を加算しています。

TimeSensorfraction_changed を頻繁に出力する程、df の値は小さくなります。

変数 active が TRUE の場合のみ fraction_out を変化量 df だけ加算します。  こうすることにより、アニメーションを実行中ならば fraction_outfraction_changed と同じ分だけ変化し、 一時停止中ならば fraction_out はその値を保持します。

OrientationInterpolatorfraction_changed ではなく、 スクリプトで加工された fraction_out を参照してアニメーションの処理を行います。

スピード調整と逆再生

今度はスライダーの操作によるアニメーションの再生スピードの無段階変速を行ってみましょう。 
VRML - ソース / X3D - ソース
画面中央のスライダーを左に持っていくと、赤い円錐の回転スピードが落ちていきます。
しかし、スライダーでスピードを調整している間は赤い円錐の回転は止まり、調整後に赤い円錐は全く別の場所に移ってしまいます。

スクリプトは TimeSensor ノードの cycleInterval フィールドの値をスライダーの位置により増減させています。  しかし、この方法では凄くゆっくりに再生できても完全に止めることはできませんし、逆再生もできません。  また、Cosmo Cortona では TimeSensor ノードの enabled フィールドを一旦 FALSE に設定しないと cycleInterval に新しい値を入れただけでは反映されない不具合があります。  このためスライダーにTouchSensor も設置して、スライダーを調整している間だけは TimeSensorenabled を FALSE にしています。


cycleInterval の値を固定したまま、fraction_changed の加工によりスピード調整を行うようにしてみましょう。  停止や逆再生を行えるようにもしてみます。
VRML - ソース / X3D - ソース
最初に赤い円錐は止まった状態ですが、画面中央のスライダーを右に持っていくと時計の逆回りに周回します。  スライダーを右に持って行くほどスピードが増します。  スライダーを中央から左に持っていくと、赤い円錐は時計方向に逆回転します。
DEF Sc Script {
eventIn SFVec3f adjustSpeed
eventIn SFFloat set_fraction
eventOut SFFloat fraction_out
field SFFloat speed 0
field SFFloat oldF 0
url "javascript:

function adjustSpeed (vec) {

speed = vec.x;

}

function set_fraction (f) {

var df = f - oldF;
if ( df < 0 ) df++;
oldF = f;

fraction_out += df * speed;
if ( fraction_out > 1 ) fraction_out--;
else if ( fraction_out < 0 ) fraction_out++;

}

"
}
adjustSpeed 関数はスライダーを左右に動かすと実行されます。  この関数の引数 vec はスライダーの位置であり、その x 座標が変数 speed に代入されます。  スライダーの可動範囲は左右に -1 から 1 までの間に設定されているため、speed もその間の値となります。

この作例の set_fraction 関数も TimeSensorfraction_changed のイベント出力ごとに実行され、 変数 df には fraction_changed の変化量が入ります。
fraction_out の変化量は df と speed の積となっています。  speed = 1 (スライダーが最も右端に位置する)ならば、fraction_outfraction_changed と同様に変化します。  speed = 0 (スライダーが中央に位置する)ならば、fraction_out は変化しません。  speed < 0 (スライダーが中央よりも左に位置する)ならば、fraction_out は減算され逆再生となります。

この作例の OrientationInterpolatorfraction_changed ではなく、 fraction_out を参照してアニメーション処理を行います。
このページのトップ | 前へ アニメーション | 次へ アニメデータの生成