衝突時の火花

衝突した時に火花を作る

リジッドボディ同士がぶつかった時に、火花が散るというアニメーションを作ってみよう。

作り方

  1. 衝突するオブジェクトをアクティブリジッドボディにする。
  2. 衝突されるオブジェクトをパッシブブリジッドボディにする。
  3. 衝突するオブジェクトをフィールドなどを使って、 衝突されるオブジェクトにぶつかるようにする。
  4. 衝突するオブジェクトにぶつかった時に火花をだすエクスプレッションを設定する。
    衝突されるリジッドボディの contactCount アトリビュートが 1 以上になったら 2 つのリジッドボディが衝突しているので、 その時に rigidBody -q -contactPosition によって衝突した点の座標を求めればよい。
    その点に emit コマンドか、 または、エミッターによって作って新しいパーティクルを発生させる。
  5. rigidSolver ノードの contactData アトリビュートを 1 に設定する。
    この設定がされていないと衝突したかどうかがわからない。

火花のスクリプト

最初のスクリプトの火花は小さくて見にくいかもしれない。 それはパーティクルのアトリビュートを適切に設定していないからである。 それらのアトリビュートの設定は練習問題で行なう。
また、衝突した後、火花がずっと出続ける問題も練習問題で解決しよう。

なお、今回のスクリプトはプロシージャが 2 つ含まれているので注意して作ること。

  1. 以下のMELスクリプトを makeSpark1.mel という名前で作る。
    global proc makeSpark1()
    {
    	string $gra[] = `gravity -m 9.8`;
    	polySphere;
    	move 1 10 1;
    	rigidBody -active;
    	connectDynamic -f $gra[0];
    
    	polyPlane;
    	scale 20 1 20;
    	rotate 0 0 -45;
    	string $rb = `rigidBody -passive`;
    	expression -o $rb -s ("makeEmitter1(\"" + $rb + "\", contactCount)");
    
    	setAttr rigidSolver.contactData 1;
    }
    
    global proc makeEmitter1(string $rigidbody, int $contval)
    {
    	if($contval > 0)
    	{
    		string $pp[] = `rigidBody -q -contactPosition $rigidbody`;
    		string $pos[];
    		tokenize $pp[0] $pos;
    		string $emi[] = `emitter -pos $pos[0] $pos[1] $pos[2]`;
    		string $pname[] = `particle`;
    		connectDynamic -em $emi[0];
    
    		setAttr ($pname[1] + ".particleRenderType") 6;
    		setAttr ($pname[1] + ".lifespanMode") 2;
    		setAttr ($pname[1] + ".lifespanRandom") 0.5;
    		setAttr ($pname[1] + ".lifespan") 5;
    	}
    }
    
  2. Script EditorFile → Source Script によって makeSpark1.mel を読み込む。
  3. Script Editor の下のウインドウから makeSpark1(); と打ち込んで実行する。
    [makeSpark1() の実行図]
  4. プレイバックしてアニメーションを実行する。
    [makeSpark1() のアニメーション実行図]

(注意)
再度プレイバックする場合は衝突によって作られたパーティクルとエミッターを消去しておいた方が良いかもしれない。

スクリプトの解説

string $gra[] = `gravity -m 9.8`;
gravty (重力)フィールドを作る。
重力係数(-m)は 9.8 である。
polySphere;
ポリゴンの球を作る。
move 1 10 1;
ボリゴンの球を (1, 10, 1) に移動する。
rigidBody -active;
ポリゴンの球をアクティブ・リジッドボディにする。
connectDynamic -f $gra[0];
ポリゴンの球に gravty フィールドをコネクトして下(Y軸マイナス方向)に落ちてゆくようにする。
ポリゴンの球は、この時点でセレクトされた状態なのでコマンドで球の名前を指定する必要は無い。
polyPlane;
ポリゴンの平面を作る。
scale 20 1 20;
ポリゴンの平面を X 方向 20、Z 方向 20 に拡大する。
rotate 0 0 -45;
ポリゴンの平面を Z 方向に -45 度回転する。
string $rb = `rigidBody -passive`;
ポリゴンの平面をパッシブ・リジッドボディにする。
作成されたリジッドボディの名前を文字列の変数 $rb に代入しておく。
expression -o $rb -s ("makeEmitter1(\"" + $rb + "\", contactCount)");
変数 $rb に代入されているリジッドボディにエクスプレッションを設定する。
-o $rb
エクスプレッションを定義するオブジェクトを $rb に名前が代入されているリジッドボディに設定する。
-s ("makeEmitter1(\"" + $rb + "\", contactCount)")
エクスプレッションの式を設定する。
式全体が ( ) で囲まれているので、まず ( ) の中の文字列の処理を行ない、 式を表す一つの文字列(" から " まで)になる。
そこで ( ) 内のエクスプレッションの式は以下のように組み立てられる。
  1. 最初の状態。
    "makeEmitter1(\"" + $rb + "\", contactCount)"
  2. $rb がリジッドボディの名前(例えば、rigidBody2)に置き換わる。
    "makeEmitter1(\"" + rigidBody2 + "\", contactCount)"
  3. 文字列が結合されて、一つの文字列になる。
    "makeEmitter1(\"rigidBody2\", contactCount)"
    ここで、" の前に \ があるのは式を表す文字列が、まだ終りではないことを示すためのものである。
    また、contactCount はリジッドボディのアトリビュートで他のリジッドボディが衝突した点の個数を表す。
結局、このエクスプレッションでは makeEmitter1 という名前のプロシージャを、2 つの引数(リジッドボディの名前と衝突の階数)をつけて呼び出すことになる。
setAttr rigidSolver.contactData 1;
rigidSolver の contactData アトリビュートを 1 にする。
これによって各リジッドボディが衝突したかどうかが、わかるようになる。
ここで rigidSolver はリジッドボディソルバとよばれるもので、 リジッドボディのアニメーションをコントロールするノードである。
global proc makeEmitter1(string $rigidbody, int $contval)
makeEmitter1 という名前のプロシージャを定義する。
上のポリゴンの平面(polyPlane)に定義されたエクスプレッションから呼ばれる。
引数の説明は以下の通り。
string $rigidbody
他のリジッドボディが衝突したかどうか調べるリジッドボディの名前。
int $contval
リジッドボディが衝突した点の個数。
if($contval > 0)
リジッドボディが衝突した点の個数が 0 以上なら以下のスクリプトを実行する。
string $pp[] = `rigidBody -q -contactPosition $rigidbody`;
リジッドボディが衝突した点の座標を求めて文字列の配列 $pp に代入する。 (衝突した点の個数は 1 個とは限らないので配列にする)
string $pos[];
文字列の配列 $pos を定義する。
tokenize $pp[0] $pos;
文字列 $pp[0] を分解して配列 $pos に代入する。
なぜこのような処理が必要かというと、$pp の中身であるX, Y, Z座標値が一つの文字列になっているからである。
ここでの処理は以下のように進んでゆく。
  1. 例えば、$pp[0] の中身が "1.5 2.3 3.6" になっているとする。
  2. tokenize コマンドによって、$pp[0] が分解されて $pos の中に代入されてゆく。
    $pos[0] = "1.5"
    $pos[1] = "2.3"
    $pos[2] = "3.6"
なお、ここでは 1 回の衝突で 1 個だけエミッターを作ることにしているので $pp[1] 以下はわざと無視している。、
string $emi[] = `emitter -pos $pos[0] $pos[1] $pos[2]`;
エミッターを ($pos[0], $pos[1], $pos[2]) の位置に作る。
エミッターの名前は $emi に代入する。
string $pname[] = `particle`;
エミッターのために空のパーティクルを作る。
パーティクルのトランスフォームノードの名前が $pname[0] に、 シェープノードの名前が $pname[1] 代入される。
connectDynamic -em $emi[0];
空のパーティクルにエミッター($emi)をコネクトする。
setAttr ($pname[1] + ".particleRenderType") 6;
パーティクルのシェープノード($pname[1])の particleRenderType アトリビュートに 6 (Streak)を設定する。
これでパーティクルのレンダリングタイプが細長い線になる。
setAttr ($pname[1] + ".lifespanMode") 2;
ライフスパンモードを 2 (Random range)に設定する。
これによって個々のパーティクルに違ったライフスパンを割り当てることができる。
(ライフスパンとはパーティクルの生存時間のことである)
setAttr ($pname[1] + ".lifespanRandom") 0.5;
ライフスパンの平均値(lifespan)からの分布の幅を設定する。
平均値が 5 、lifespanRandom が 0.5 であれば、ライフスパンは 4.5 から 5.5 の間のランダムな値になる。
setAttr ($pname[1] + ".lifespan") 5;
ライフスパンの平均値を 5 に設定する。

練習

練習課題

参考


Prev
Home | Contents
Mail