衝突時の火花

衝突した時に火花を作る

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

作り方

  1. 衝突するオブジェクトをアクティブリジッドボディにします。
  2. 衝突されるオブジェクトをパッシブブリジッドボディにします。
  3. 衝突するオブジェクトをフィールドなどを使って、 衝突されるオブジェクトにぶつかるようにします。
  4. rigidSolver ノードの contactData アトリビュートを 1 に設定しておきます。
    この設定がされていないと衝突したかどうかがわかりません。
  5. 衝突するオブジェクトにぶつかった時に火花をだすエクスプレッションを設定します。
    衝突されるリジッドボディの contactCount アトリビュートが 1 以上になったら 2 つのリジッドボディが衝突しているので、 その時に rigidBody -q -contactPosition によって衝突した点の座標を求めます。
    その点に 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. Script EditorFile → Source Script によって makeSpark1.mel を読み込みます。
  3. Script Editor の下のウインドウから 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 という名前のプロシージャを定義します。
上のリジッドボディになっているポリゴンの平面に定義されたエクスプレッションから呼ばれます。
各引数の説明は以下の通りです。
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