XPresso 基礎

レベル/ 対象者:中級/ CINEMA 4Dを使えて、読み書きができる人
対象ソフトウエア、プラグイン:CINEMA 4D R12

プログラムは文章である。読み書きができる人間なら誰にでも書ける。

冨士 俊雄/ gtofuji@gmail.com
章番号 題名 内容、及び関連する章 作成日/注記
806 4_XGroup XGroup、Xプール、ターゲットXPresso、グローバル座標とローカル座標(絶対座標と相対座標)、座標変換を固定、1001章 2011.1.20
English Top

 

Step 1

ターゲットXPresso

 これからXPressoノードをグループ化する方法について説明します。

 とは言っても、何もないとグループ化できないので、最初にちょっと複雑なものを作りましょう。何か役に立つもの、後で再利用できるものがいいので、ここでは「ターゲットの方を向くXPresso」を作ります。もちろんCINEMA 4Dには標準でターゲットエクスプレッションがあります。しかし、これをXPressoの中に組み込むことはできません。その点XPressoでターゲット機能を作っておけば、XPressoの中に自由に組み込むことができます。

 ターゲット機能を作るには、大学で学ぶ数学の知識が一つ必要になります。それは「ベクトルA、Bの外積ベクトルCは、ベクトルA、Bに(反時計回りに)直交する」ということです。これを使って直交する座標軸X、Y、Zを計算します。

 また、マトリックス(行列)というデータタイプを扱います。CINEMA 4Dで扱う4行4列のマトリックスは、簡単に言うとオブジェクトの「位置」、「X軸」、「Y軸」、「Z軸」の4個のベクトルをまとめたものです。また、座標マネージャに表示される「位置」、「スケール」、「角度」の3個のベクトルをマトリックスから取り出すこともできます。

 それでは、サンプル806aを開いて「Target」オブジェクトを動かしてみて下さい。XPressoが働いて「Root」オブジェクトがその方向を向くはずです。

図806-1

 それでは、XPressoを調べてみましょう。まず、目標となるオブジェクトはXPressoタグに作ったユーザデータで指定しています。

図806-2

 XPressoの構成はちょっと複雑で次のようになっています。それではこれから順番に説明していきます。

図806-3

 まず左から1列目のXPressoタグノードは、ユーザーデータの値(目標となるオブジェクトへのリンク)を出力します。

 2列目のオブジェクトノードは、RootとTargetの二つのオブジェクトの位置ベクトルを出力します。

 3列目の計算ノードは、Targetの位置ベクトルからRootの位置ベクトルを引いて、Target方向のベクトルを出力します。

図806-4

 これがZ軸になるのですが、そのままではTargetの位置によってZ軸の長さ(つまりオブジェクトのスケール)まで変ってしまうので、6列目の万能ノードを使って正規化し、単位ベクトルにします。

図806-5

  4列目は外積ノードです。これに最初のZ軸ベクトルを入力し、天頂方向のベクトル(0,1,0)との外積を求めると、それはZ軸と天頂ベクトルの両方に垂直なベクトルになります(同時に水平ベクトルでもある)。これを正規化するとX軸になります。

図806-6

 5列目も外積ノードです。これにZ軸ベクトルとX軸ベクトルを入力し、外積を求め、正規化するとY軸になります。

図806-7

 7列目は4個のベクトルを1個のマトリックスにまとめるノードです。マトリックスにまとめることで後の処理がずっと簡単になります。8列目では、計算したマトリックスをRootオブジェクトに入力しています。

 

 

Step 2

XPressoのグループ化

 それでは、今作ったターゲットXPressoをグループ化します。まず、XPresso編集の空いている部分を右クリックし、「空のXGroup」を選択して下さい。すると「XGroup」という名前の小さなノードが表示されます。

図806-8

 次に、XGroup以外の全てのノードを選択し、「カット」して下さい。そして、XGroupを選択し、「ペースト」します。すると、カットしたノードが全てXGroupの中に入ります。ここで、controlキーを押しながらXGroupのタイトルバーをダブルクリックすると、内部が全部見えるように適切にリサイズできます。

図806-9

 これでXPressoをグループ化できました。複雑なXPressoを見やすくする目的であればこれで十分です。XGroupの名前や色を変更すればさらに判りやすくなります。

図806-10

 また、XGroupのタイトルバーを右クリックして「表示 -> 標準」を選択すれば、内部が非表示になります。

図806-11

 

 

Step 3

XGroupにポートを追加する

 しかし、現在のXGroupにはポートがありません。サブルーチンで言えば「引数」のない状態です。これでは他のノードと組み合わせられません。それでは、これからXGroupにポートを追加します。

 XGroupにポートを追加するには構成を少し変える必要があるので、まずXGroupのタイトルバーを右クリックして「XGroupを開く」を選択して下さい。これで元に戻ります。

 次に、このXPressoを「入出力部分」と「処理部分」に明確に分けます。サンプル806bを開いて下さい。

図806-12

 変更点は、Rootオブジェクト(つまり、XPressoタグが適用されたオブジェクトであり、ターゲットの方向を向くオブジェクト)に「オブジェクト」ポートを追加したことです。最初のXPressoではこれが「オブジェクトへの直接リンク」になっていたので、XPresso内部では変更できませんでした。

 そこで、Rootオブジェクトへのリンクを、オブジェクトポートを通して他のノードから間接的にもらうように変更したのです。こうしておけば、後でリンク先を自由に変更できます。

 左から2列目のCOFFEEノードには次のようなプログラムが書かれています。

	o_out= o_in->GetObject();
          
          

 ここで、「GetObject()」はタグからそれが適用されているオブジェクトを取得する関数です。これはXPressoノードでは作れません。もしCOFFEEを使いたくない場合は、Targetオブジェクトを指定した時と同じように、Rootを指定するためのユーザデータを追加し、そこから取得して下さい。

 さあ、これで入出力部分と処理部分を分離できました。左の2個のノードが入出力部分で、それ以外が処理部分です。それでは、処理部分のノードを選択し、どれか一つのノードのタイトル部分を右クリックし、「XGroupに変換」を選択して下さい。すると、処理部分だけがXGroupに変換されます。

図806-13

 なお、COFFEEノードから出ているワイアが二股に分かれてしまいますが、これは同じものなので、一つのポートにまとめて下さい。

図806-14

 さらに、XGroupのタイトルバーを選択して「表示 -> 標準」を選択すると、複雑な内部が隠れて非常にシンプルになります。

図806-15

 しかし、内部を非表示にすると何のXGroupだかわからなくなってしまうので、XGroupとポートに適切な名前を付けておきましょう。

図806-16

 このように入出力用のポートを明確にしておくと、XGroupを一種の「サブルーチン」として他のプログラムと組み合わせて使えるようになります。ただし、普通のプログラムのように「一つのサブルーチンを何回も使い回す」という使い方はできません。必要な数だけXGroupを複製する必要があります。しかし、それでも毎回全てのXPressoを組み直すよりはずっと簡単です。

 

 

Step 4

Xプール

 さて、XGroupを再利用可能な形にまとめたら、これを保存しておいて、将来XPressoを作る時にも役立てたいものです。もちろん普通にCINEMA 4Dのシーンファイルとして保存し、コピーペーストしてもいいのですが、XPressoにはノードやXGroupをライブラリ化する機能があります。それがXプールです。XプールはXPresso編集ウインドウの左側にあります。

図806-17

 Xプールには、初めから「System Operators」と「System Preset」があります。「System Operators」は、XPressoの基本ノードで、XPresso編集ウインドウを右クリックすると表示されるプルダウンメニューの内容と同じです。「System Preset」は、MAXONが作成した応用ノードで、応用的な機能を持ったCOFFEEノードや、基本ノードを組み合わせたXGroupが入っています。

 自分が作ったCOFFEEノードをXプールに登録するには、まず「編集 -> プールを作成」を選択し、プール(ライブラリ)の名前と保存する場所を指定する必要があります。するとXプールに新しいプールが表示されるので、ここに「Target XPresso」をドラッグアンドドロップして下さい。リストに「Target XPresso」XGroupが追加されたはずです。

 次に、このライブラリからXGroupを取り出す実験をしてみましょう。

 新規シーンを作成し、ヌルオブジェクトを作成し、XPressoタグを作成して下さい。そして、XプールからXPresso編集ウインドウに「Target XPresso」XGroupをドラッグアンドドロップします。

図806-18

 すると、「Target XPresso」XGroupができたはずです。このXGroupのポートに二つのオブジェクトをリンクすれば、それはターゲットオブジェクトとターゲットの方を向くオブジェクトになります。各自確認してみて下さい。

 

 

Step 5

グローバル座標とローカル座標(絶対座標と相対座標)

 さて、ステップ1で作ったTargetXPressoは実は不完全なので、ここで完成させておきます。

 ステップ1のTargetXPressoは、グローバル(絶対)座標では問題なく動作するのですが、ローカル(相対)座標に移すと動作しないのです。ローカル座標に移すというのは、つまり階層に入れる、子オブジェクトにする、ということです。

 それではサンプル806cを開いて下さい。806cではRootオブジェクトとTargetオブジェクトがヌルオブジェクトの子オブジェクトになっています。

図806-19

 さて、806cはまだ正常に動作します。それは「null_r」と「null_t」がグローバル座標の原点にあり、角度もグローバル座標に一致しているからです。言い換えると、グローバル座標とローカル座標が一致しているので、どちらで計算しても結果が変らない、ということです。

 しかし、この状態でnull_tを動かしてもRootの向きは変わりません。このXPressoは破綻しました。理由は、null_tを動かしても、targetの位置の値(ローカルの)が変化しないからです。

 それでは少し修正してみます。XPressoノードには「位置」の他に「絶対位置」というデータタイプがあります。これはグローバル座標における位置の値を出力するので、オブジェクトの階層に影響されません。そこで、RootとTargetオブジェクトの位置ポートを絶対位置ポートに変更してみます。

図806-20

 これで、null_tを動かしても正しく動作するようになりました。一歩前進です。しかし、null_rを動かすとまた破綻します。理由は、右端にあるRootオブジェクトの入力ポートが「ローカルマトリックス」になっているからです。これは「位置」と同じで、ローカル座標での値を扱うポートです。

 しかし、計算の元になる入力を「位置」から「絶対位置」に変えた段階でマトリックスの計算はローカル座標からグローバル座標に移りました。だから、その結果を入力するポートもグローバルマトリックスに変更すべきなのです。それではRootの入力ポートを「グローバルマトリックス」に変更してみましょう。

図806-21

 これで、どのオブジェクトを動かしても、また回転させても、Rootの向きがTargetの方向を向くようになったはずです。

 

 

Step 6

まとめ

 このXPressoは最初ローカル座標で値を計算していました。しかし、オブジェクトを階層化することで問題が生じたので、計算をグローバル座標に移したのです。

 ただし、何でもかんでもグローバル座標に移せばいいというものではありません。たとえば、テキスト803のステップ1を思い出して下さい。ポリゴンの形状を複製する際には、ポイントの位置をローカル座標で扱う必要がありました。

 値をグローバル座標で扱うかローカル座標で扱うかは、場合によって変ります。ここが難しいところです。

 また、グローバル座標(絶対座標、全体座標)はワールド座標とも呼ばれ、ローカル座標(相対座標、局所座標)はオブジェクト座標とも呼ばれます。CINEMA 4Dの中にはいくつかの用語が混在していますが、本質的には同じ概念です。またこれらを英語で表記すると、次のようになります。

絶対、Global、Absolute、World
相対、Local、Relative、Object

 最後に、現在のCINEMA 4D R12(12.032)には設計上の大きな欠陥があります。

 それは「座標変換を固定」機能で、これを使うとオブジェクトの位置を扱うXPressoは全て正常に動作しなくなります。これはバグではなく、設計上の欠陥なので修正できません。

 また、「座標変換を固定」機能は、必要な情報をオブジェクトマネージャや属性マネージャ、座標マネージャに表示しないため、管理が非常に面倒で、バグの原因になります。このような理由から、座標変換を固定機能は「絶対に使わない」ようにして下さい。

 絶対座標と相対座標、及び「座標変換を固定」機能の問題に関して

 

 

Step 7

COFFEEで作る

 最後に、今回作成したXGroupの機能を1個のCOFFEEノードで表現してみます。COFFEEを使えば、そもそもXPressoノードを複雑に組み合わせる必要も、XGroupにまとめる必要もないのです。それではサンプル806dを開いて下さい。

図806-22

 COFFEEの中身は次のようになっていて、XPressoノードを使った計算にほぼ対応しています。

図806-23

	var ot= ot_in;
	var or= or_in->GetObject();

	var otp= ot->GetMg()->GetV0();
	var orp= or->GetMg()->GetV0();

	var vz= vnorm(otp-orp);
	var vx= vnorm(vcross(vector(0,1,0),vz));
	var vy= vnorm(vcross(vz,vx));

	var ormg= or->GetMg();

	ormg->SetV0(orp);	ormg->SetV1(vx);	ormg->SetV2(vy);	ormg->SetV3(vz);

	or->SetMg(ormg);

 ここで、1、2行目はTargetオブジェクトとRootオブジェクトを取得しています。

 4、5行目は、オブジェクトから絶対位置を取り出しています。「GetMg()」はオブジェクトからグローバルマトリックスを取り出す関数、「GetV0()」はグローバルマトリックスから位置を取り出す関数です。

 7〜9行目はRootオブジェクトのX、Y、Z軸を求める計算です。「vnorm()」はベクトルを正規化する関数、「vcross()」はベクトルの外積を求める関数、「vector()」はベクトルを定義する関数です。

 11行目はRootからグローバルマトリックスを取り出しています。この行はマトリックスという「器」を用意するためにあり、中身は何でも構いません。new()関数を使って空のマトリックスを作ってもいいし、マトリックスポートを作ってそれを使ってもいいです。

 13行目では、用意したマトリックスに4個のベクトルを入力しています。「SetV0()」は位置ベクトルを入力する関数、「SetV1()」はX軸ベクトルを入力する関数、「SetV2()」はY軸ベクトルを入力する関数、「SetV3()」はZ軸ベクトルを入力する関数です。

 15行目は、完成したマトリックスをRootオブジェクトのグローバルマトリックスに入力しています。

 

 このように、計算の中核部分はXPressoノードで組んでも、COFFEEで書いても似たような構成になります。つまり、XPressoノードが組めるならCOFFEEでも書けるし、COFFEEが書けないならXPressoでも組めない、ということです。

 これに対して、講習804で書いたように、入出力部分や、ループ、条件分岐等についてはそれぞれに一長一短があります。