衝突時の火花

衝突した時に火花を作る

リジッドボディ同士がぶつかった時に、 火花が散るというアニメーションを作ってみましょう。
また今回のスクリプトを応用すれば、火花の代わりに衝突時に穴を開けたり、へこみを作ることもできるようになります。

作り方

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

火花のスクリプト

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

なお、今回のスクリプトは、プロシージャが 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. スクリプト エディタファイル → ソース スクリプト によって makeSpark1.mel を読み込みます。
  3. スクリプト エディタ のインプットウインドウで makeSpark1(); と打ち込んで実行します。
    [makeSpark1() の実行図]
  4. プレイバックしてアニメーションを実行します。
    [makeSpark1() のアニメーション実行図]

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

スクリプトの解説

global proc makeSpark1()
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 つの文字列になります。
( ) 内におけるエクスプレッションの式は、 以下のように組み立てられてゆきます。
  1. 最初の状態
    "makeEmitter1(\"" + $rb + "\", contactCount)"
  2. $rb がリジッドボディの名前(たとえば、rigidBody2)に置き換わります。
    "makeEmitter1(\"" + rigidBody2 + "\", contactCount)"
  3. 文字列が連結されて、1 つの文字列になります。
    "makeEmitter1(\"rigidBody2\", contactCount)"
    ここで、" の前に \ があるのは、 文字列の中の " には、\ をつけておく必要があるためです。
    また、contactCount はリジッドボディのアトリビュートです。 このアトリビュートは、他のリジッドボディが衝突した点の個数を表しています。
結局、このエクスプレッションでは makeEmitter1 という名前のプロシージャを、 リジッドボディの名前と衝突の回数の 2 つの引数をつけて呼び出すことになります。
setAttr rigidSolver.contactData 1;
rigidSolver の contactData アトリビュートを 1 に設定します。
これによって各リジッドボディが、衝突したかどうかがわかるようになります。
ここで rigidSolver はリジッドボディソルバとよばれるもので、 リジッドボディのアニメーションをコントロールするノードです。
global proc makeEmitter1(string $rigidbody, int $contval)
makeEmitter1 という名前のプロシージャを定義します。
makeSpark1() 中の、 リジッドボディのポリゴンプレーンに定義された、エクスプレッションから呼ばれます。
各引数の説明は、以下の通りです。
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 を設定します。
これで、パーティクルのレンダリングタイプがストリーク(細長い線)になります。
setAttr ($pname[1] + ".lifespanMode") 2;
ライフスパンモードを 2 (ランダム範囲)に設定します。
これによって、個々のパーティクルに違ったライフスパンを割り当てることができます。
ライフスパンとは、パーティクルの生存時間のことです。
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