Skip to content

XPresso 基礎 2: ループ

章番号 題名 内容、及び関連する章 作成日/注記
803 2_ループ ループ、くり返しノード、for文、ポイントノード、デフォーマ、形状の複製、ループの階層化 2011.1.24

 

Step 1

ループ

ループとは、多数の入力に対して一つの処理を繰り返すことです。CINEMA 4Dではポリゴンオブジェクトやスプラインオブジェクトのポイントを操作する場合によく使います。まず、ポリゴンAの「位置」データをポリゴンBに複製したいとします。それは簡単で、ポリゴンAの位置ポートとポリゴンBの位置ポートをワイアでつなぐだけです。

図803-1

さて、それでは次にポリゴンAの「形状」データをポリゴンBに複製したいとします。しかし「形状」というデータタイプは存在しません。したがって、ポリゴンAとポリゴンBを形状ポートでつなぐことはできません。それではどうしたらいいのでしょうか。

「形状」というのは、つまり「多数のポイントの位置データが集まった結果」です。そして位置というデータタイプは存在します。ですから、ポリゴンAに含まれる全てのポイントの位置データをポリゴンBに含まれる全てのポイントに複製すれば、原理的には形状を複製できるはずです。問題なのは、ポイントの数が何千個もあった場合、「どうやってそれを実行するか」ということです。そのような場合に、ループ(くり返し)機能を使います。

 

それではさっそく形状を複製するXPressoを作ってみましょう。まず新規シーンを作成し、「平面」オブジェクトを作成します。そのままだと細かすぎるので、分割数を10に減らし、編集可能にして下さい。

次に平面オブジェクトを複製し、横に並べ、片方の名前を「平面A」、もう片方を「平面B」として下さい。

次にXPressoタグを作成しますが、このような場合はグループ化して親のヌルに作成するのがいいでしょう。

図803-2

次に、XPresso編集の中に平面Aと平面Bをドラッグアンドドロップします。平面Aから平面Bに形状を複製するので、平面Aが左に、平面Bが右になるように少し離して並べて下さい。そして、ノードの右上をクリックし「オブジェクト」ポートを追加します。

図803-3

次に、XPresso編集の空いている部分を右クリックして、「XPresso -> 一般」から「ポイント」ノードを作成してください。ポイントノードをオブジェクトノードにリンクすると、ここからポイントのデータを取り出せるようになります。さらに、ポイントノードを複製して平面Aと平面Bのオブジェクトポートにリンクして下さい。

図803-4

次に、平面Bにリンクされたポイントノードの左上をクリックし「ポイントの位置」入力ポートを追加し、平面Aの「ポイントの位置」出力ポートにリンクして下さい。これでとりあえず、平面Aのポイント位置データを平面Bに複製できたはずです。

図803-5

この状態でエディタービューを見てみましょう。一見しておかしいですが、これから順を追って修正していきます。

図803-6

まず、平面Aの左端のポイント(ポイント番号0)を動かしてみます。すると、確かに平面Bのポイントもついてきます。つまり、形状の複製ができています。

図803-7

最初の問題は、ポイントの位置情報がローカル座標ではなく、グローバル座標で扱われていることです。もちろんグローバル座標で位置データを扱うケースもありますが、今回は「形状の複製」が目標なので、ローカル座標で扱う必要があります。そこで、ポイントノードを両方とも選択し、属性マネージャで「マトリックスモード」をグローバルからローカルに切り替えます。

この状態で平面Aのポイント0を動かすと、平面Bのポイント0が「正しく」動くはずです。

図803-8

しかし、平面Aのポイント1を動かしても平面Bのポイント1は動きません。これはポイントを複製する作業が一回しか行われていないからです。そこで、この作業がくり返し行われるようにループ機能を追加します。

図803-9

 

 

Step 2

くり返しノード

XPressoノードをループさせるには「くり返し」ノードを使います。このノードは、XPressoタグに実行命令が「1回」来ると、他のノードに対して「指定した回数」だけ実行命令を送ります。それでは、「XPresso -> くり返し」からくり返しノードを作成して下さい。くり返しノードには「くり返し開始」、「くり返し終了」、「くり返し」の3個のポートがあります。そして、くり返し開始からくり返し終了までの値が順番にくり返しポートから出力されます。たとえばくり返し開始の値が「0」でくり返し終了の値が「5」だった場合、くり返しポートは「0、1、2、3、4、5」と整数を6回出力します。そして、これが他のノードを6回働かせる「命令」として働くわけです。

図803-10

このくり返し開始とくり返し終了の値は属性マネージャで直接入力してもいいのですが、ポリゴンのポイント数が変ると、その度変更する必要があります。そこで、平面Aのポイント数を調べてその値を使うようにします。まず平面Aノードとポイントノードを選択して、複製して下さい。

図803-11

ポイントノードの右側には「ポイント数」ポートがあり、ここからポイント数が出力されます。結果ノードを追加してチェックしてみましょう。ポイント数は「121」個と表示されています。

また、情報マネージャで平面Aのポイント数を確認すると確かに「121」個になっています。ところが、構造マネージャを見ると最後のポイントの番号は「120」になっています。

図803-12

これは、ポイントの番号が1ではなく0から始まっているために起こるズレです。つまり、ポイント数の値「121」をこのままくり返し終了の値として入力すると、ポイントの複製が「122回」実行され、最後のポイント121に対する処理はエラーとなります。

そこで、面倒ですがポイントノードの後ろに「計算」ノードを追加し、121から1を引いて120にします。計算ノードは「XPresso -> 計算」の中に入っています。

図803-13

最後に、計算ノードの出力ポートをくり返しノードのくり返し終了ポートにつなぎ、くり返しポートを平面Aと平面Bにリンクされたポイントノードの「ポイント番号」ポートにつなぎます。

図803-14

これで全てのポイントの位置が複製され、「形状」を複製できたはずです。それではエディタビューで平面Aのポイントを動かしてみましょう。

図803-15

ちゃんと動きましたか。もし動かない場合は、サンプル803aを開いて自分のシーンファイルと比較してみて下さい。

今回は形状をそのまま複製しましたが、間にCOFFEEノードを挟んでいろいろな処理を追加すれば、このXPressoを応用して高度なモーフ機能を表現できます。

 

 

Step 3

デフォーマによる変形

ついでにデフォーマについても実験しておきましょう。CINEMA 4Dのデフォーマはオブジェクトを変形させますが、デフォーマの働きを切ればオブジェクトの形状は元に戻ります。つまり、CINEMA 4Dの中には「オブジェクトの元の形状」と「変形後の形状」の二つの形状データが存在するのです。それでは平面Aに「屈曲」デフォーマを追加し、適当に変形させてみましょう。しかし、平面Bは変形しません。それはポイントノードがデフォルトで「オブジェクトの元の形状」を扱うようになっているからです。

図803-16

それではXPresso編集でポイントノードを両方とも選択し、属性マネージャで「変形を考慮する」をチェックして下さい。すると、平面Bが平面Aと同様に変形するはずです。

図803-17

ここまでの作業はサンプル803bの中に入っています。

 

 

Step 4

COFFEEノードのループ

次に、COFFEEを使ってループさせる方法について説明します。COFFEEノードの中では普通にfor文を指定し、ループを実行できます。まず、for文を書いたことがない人のために、for文の構造について簡単に説明しておきます。たとえば以下のようなプログラムが書かれていた場合、「for」はループを表し、「i」はループのパラメーターを表し、「println(i);」はくり返し実行される関数を表します。そして「i=0」はくり返しが0から始まることを表し、「i<10」はiが10になったらくり返しを終了することを表し、「i++」はiが1ずつ増えることを表しています。

基本的には、前のステップで説明したくり返しノードのパラメータと同じです。

	for(i=0;i<10;i++)
	{
		println(i);
	}

それでは実験してみましょう。新規シーンを作成し、ヌルオブジェクトを作成し、XPressoタグを作成し、COFFEEノードを作成して下さい。

そして、デフォルトのポートとプログラムを全て消去し、以下のプログラムを書き込んで下さい。

	var i= 0;
	for(i=0;i<10;i++)	println(i);

図803-18

1行目は変数「i」の宣言。2行目はfor文で、コンソールへ「i」を10回くり返して出力するようになっています。結果は次のようになります。

図803-19

つまり、COFFEEノードの内部ではfor文が正しく実行されています。

 

 

Step 5

ポートへの出力は1回だけ

それでは次にポートへの出力を実験してみましょう。まずCOFFEEノードを複製します。そして、左のノードに整数出力ポートを作成し、ポート名を「i_out」に変更します。また右のノードの整数入力ポートを作成し、ポート名を「i_in」に変更し、ポートをつなぎます。

図803-20

次に、左のノードの中身を次のように変更します。

	var i= 0;
	for(i=0;i<10;i++)	i_out= i;

2行目の内容を、「コンソールへの出力」から「ポートへの出力」に変えました。

次に右のノードの中身を次のように変更します。別のCOFFEEノードを編集する際には、必ず属性マネージャの「COFFEEエディタを開く」ボタンを押して、エクスプレッションエディタの表示を切り替えて下さい。

	println(i_in);

これは単に「入力をコンソールに出力する」という意味です。

このXPressoを実行すると、コンソールには「9」しか表示されません。

図803-21

つまり、for文の中でポートへの出力をくり返し命令しても、実行されるのは最後の一つだけです。

ステップ4と5の結果をまとめると、「COFFEEノード内部ではループを実行できるが、外部のノードを含めたループはできない」ということです。この点に注意して下さい。

 

 

Step 6

全部COFFEEで作る

ステップ4と5の結果から、COFFEEでループを実行する場合は、「全ての処理をCOFFEEノード内部で完結させる必要がある」ということがわかりました。それでは、形状を複製するプログラムを全てCOFFEEノードの中で書いてみましょう。ポイントの計算まで全てCOFFEEノードの中でやってしまうと、構成は驚く程簡単になります。サンプル803cは平面Aの形状を平面Bに複製するXPressoですが、くり返しノードやポイントノードの仕事を全部COFFEEノードの中で実行しています。

図803-22

COFFEEノードの中のプログラムは多少複雑になりますが、それでも9行です。

	var o1= o1_in, o2= o2_in;
	var i;
	var pnum= o1->GetPointCount();

	for(i=0;i<pnum;i++)
	{
		var ppos= o1->GetPoint(i);
		o2->SetPoint(i,ppos);
	}

ここで、1、2行目はオブジェクトと整数を定義しています。3行目の「GetPointCount()」はポイント数を調べる関数です。

7行目の「GetPoint()」はポイントの位置を取得する関数で、8行目の「SetPoint()」はポイントの位置を指定する関数です。これらの関数は、ポイントノードの各ポートの機能に対応しています。

また、このプログラムを次のように省略して書くこともできます。

	var o1= o1_in, o2= o2_in;
	var i;

	for(i=0;i < o1->GetPointCount();i++)	o2->SetPoint(i,o1->GetPoint(i));

ただしCOFFEEにも欠点があります。COFFEEには、デフォーマで変形した形状を取り出すオプションがないのです(変形した形状を取り出すことは、不可能ではありませんが非常に大変です)。

このように、XPressoノードとCOFFEEにはそれぞれ利点と欠点があります。自分の能力や目的に合わせて適切な方法を選択するように心がけて下さい。

 

 

 

Step 7

ループの階層化

最後にループを階層化する方法について説明します。一般的に、for文は次のように階層化できます。

	var i,j;

	for(i=0;i<10;i++)
	{
		for(j=0;j<10;j++)
		{
			println(i," - ",j);
		}
	}

そして、このプログラムをCOFFEEノードに書き込んで実行すると、コンソールには次のような結果が出力されます。

図803-23

つまり、ループのパラメーターが「i」と「j」の二つになり、一つのiに対して10回のjが実行され、合計では100回の処理が実行されるわけです。

 

それでは、同じことをくり返しノードを使って実行してみましょう。まず、くり返しノードを二つ作成し、くり返し終了の値を「9」に指定します。そして、右のくり返しノードに「前のくり返し」入力ポートを作り、左のくり返しノードのくり返し出力ポートにリンクします。

図803-24

そして、COFFEEノードを作成し、デフォルトのポートを全て消去して下さい。次に、整数入力ポートを2個追加し、ポート名を「i_in」と「j_in」に変更します。さらに、デフォルトのプログラムを全て消去し、次のプログラムを書き込んで下さい。

	println(i_in," - ",j_in);

最後に、左のくり返しノードをi_inへ、右のくり返しノードをj_inへリンクします。

図803-25

すると、コンソールに次のような結果が表示されるはずです。

図803-26

一見すると正しくループされているのですが、実は「0 – 9」や「1 – 9」の値がダブって出力されていることがわかります。これではまともなプログラムは作れません。

マニュアルには「くり返しノードは階層化できる」と書いてありますが、XPressoが作られた当時から修正されずに残っている大きなバグです。将来的にも解決されないでしょう。したがって、「XPressoでループを階層化することはできない」と考えた方がいいです。

 

 

Step 8

くり返しノードの階層化

XPressoの仕様上、くり返しノードは階層化できません。つまり、ループを階層化したい場合はCOFFEEノードの中でプログラムを完結させる必要があります。しかし、CINEMA 4Dの中にはThinkingParticles等COFFEEでプログラムできない機能もあります。そのような場合には、無理にでもくり返しノードを階層化するしかありません。そこで、くり返しノードを階層化するための方法を二つ説明します。

 

一つ目はCINEMA 4Dのバグを逆手に取った方法で、全ての場合に正しく動作するかどうかは不明です。また、将来にわたって通用する保証もありません。自己責任で使って下さい。

それは非常に簡単な方法で、対象となるノードの後に「ダミーノード」を追加するのです。ダミーノードは何でも構いませんが、ここでは無難に結果ノードを使っています。

図803-27

これでコンソールには正しい結果が表示されます。対象となるノードが正しく動作する理由は、「ダブって実行されるのは最後のノードだけ」だからです。つまり、ダミーノードを後に追加することで、対象となるノードは最後のノードでなくなり、正常に動作するようになるのです。もちろんダミーノードはダブって実行されますが、プログラムの働きには影響を与えないので、問題になりません。

この方法で3重のループも正しく実行できますが、ノードを実行する順番に気を付けて下さい。ノードの順番は、XPresso編集ウインドウの左にある「XPressoマネージャ」に表示されます(ノードの順番については講習nnnで詳しく説明します)。これがくり返しノードの階層と一致し、さらに対象となるノードがくり返しノードの後にないと正しく動作しません。

図803-28

 

二つ目は、COFFEEノードの中にif文を書いて、ダブった命令を排除する方法です(if文については講習804で詳しく説明します)。論理的ではありますが、「COFFEEノードでしか使えない」とか、「最後のノードであるかどうかで動作が変る」といった欠点があります。

COFFEEノードの中に次のプログラムを書き込んで、XPressoを実行してみて下さい。コンソールに正しい結果が表示されるはずです。

var j= -1;

main()
{
	if(j_in != j)	println(i_in," - ",j_in);
	j= j_in;
}

図803-29

今回はグローバル変数を使っているので、「main()」関数を含めて表示しました。

1行目はグローバル変数「j」を定義しています。ローカル変数はXPressoノードが実行される度にリセットされますが、グローバル変数はそのまま残ります。つまり、「前に実行された時の値を記憶しておける」のです。

5行目はif文で、現在の「j_in」ポートの値と、「j」の値を比較しています。jの値というのは、つまり「前に実行された時のj_inポートの値」です。したがって、このif文の意味は「値がダブっていなければこの行を実行する」ということになります。

6行目ではj_inの値をjに入力しています。このjの値が次の実行の時にif文の中で使われます。

 

以上二つの方法を説明しましたが、「くり返しノードがダブって実行されている」という状況は何も変わっていません。一つ目の方法はバグが発生する場所を移動しただけで、二つ目の方法はバグが発生した時に対象となるノードの働きを止めているだけです。一般論として、バグの周囲には他のバグが隠れているものです。ですから、ループを階層化する場合は、可能な限りCOFFEEを使うように心がけて下さい。

前の章ヘ 次の章ヘ

Comments are closed.