R12プログラムは文章である。読み書きができる人間なら誰にでも書ける。1ターゲットXPressoこれからXPressoノードをグループ化する方法について説明します。言っても、何もないとグループ化できないので、最初にちょっと複雑なものを作りましょう。何か役に立つもの、後で再利用できるものがいいので、ここでは「ターゲットの方を向くXPresso」を作ります。4Dには標準でターゲットエクスプレッションがあります。これをXPressoの中に組み込むことはできません。その点XPressoでターゲット機能を作っておけば、XPressoの中に自由に組み込むことができます。ターゲット機能を作るには、大学で学ぶ数学の知識が一つ必要になります。それは「ベクトルA、Bの外積ベクトルCは、ベクトルA、Bに(反時計回りに)直交する」ということです。これを使って直交する座標軸X、Y、Zを計算します。マトリックス(行列)というデータタイプを扱います。4Dで扱う4行4列のマトリックスは、簡単に言うとオブジェクトの「位置」、「X軸」、「Y軸」、「Z軸」の4個のベクトルをまとめたものです。座標マネージャに表示される「位置」、「スケール」、「角度」の3個のベクトルをマトリックスから取り出すこともできます。それでは、サンプル806aを開いて「Target」オブジェクトを動かしてみて下さい。XPressoが働いて「Root」オブジェクトがその方向を向くはずです。図806-1それでは、XPressoを調べてみましょう。目標となるオブジェクトはXPressoタグに作ったユーザデータで指定しています。図806-2XPressoの構成はちょっと複雑で次のようになっています。それではこれから順番に説明していきます。図806-3まず左から1列目のXPressoタグノードは、ユーザーデータの値(目標となるオブジェクトへのリンク)を出力します。2列目のオブジェクトノードは、RootとTargetの二つのオブジェクトの位置ベクトルを出力します。3列目の計算ノードは、Targetの位置ベクトルからRootの位置ベクトルを引いて、Target方向のベクトルを出力します。図806-4これがZ軸になるのですが、そのままではTargetの位置によってZ軸の長さ(つまりオブジェクトのスケール)まで変ってしまうので、6列目の万能ノードを使って正規化し、単位ベクトルにします。図806-54列目は外積ノードです。これに最初のZ軸ベクトルを入力し、天頂方向のベクトル(0,1,0)との外積を求めると、それはZ軸と天頂ベクトルの両方に垂直なベクトルになります(同時に水平ベクトルでもある)。これを正規化するとX軸になります。図806-65列目も外積ノードです。これにZ軸ベクトルとX軸ベクトルを入力し、外積を求め、正規化するとY軸になります。図806-77列目は4個のベクトルを1個のマトリックスにまとめるノードです。マトリックスにまとめることで後の処理がずっと簡単になります。8列目では、計算したマトリックスをRootオブジェクトに入力しています。2XPressoのグループ化それでは、今作ったターゲットXPressoをグループ化します。XPresso編集の空いている部分を右クリックし、「空のXGroup」を選択して下さい。すると「XGroup」という名前の小さなノードが表示されます。図806-8次に、XGroup以外の全てのノードを選択し、「カット」して下さい。XGroupを選択し、「ペースト」します。すると、カットしたノードが全てXGroupの中に入ります。ここで、controlキーを押しながらXGroupのタイトルバーをダブルクリックすると、内部が全部見えるように適切にリサイズできます。図806-9これでXPressoをグループ化できました。複雑なXPressoを見やすくする目的であればこれで十分です。XGroupの名前や色を変更すればさらに判りやすくなります。標準」を選択すれば、内部が非表示になります。3XGroupにポートを追加するしかし、現在のXGroupにはポートがありません。サブルーチンで言えば「引数」のない状態です。これでは他のノードと組み合わせられません。それでは、これからXGroupにポートを追加します。XGroupにポートを追加するには構成を少し変える必要があるので、まずXGroupのタイトルバーを右クリックして「XGroupを開く」を選択して下さい。次に、このXPressoを「入出力部分」と「処理部分」に明確に分けます。サンプル806bを開いて下さい。図806-12変更点は、Rootオブジェクト(つまり、XPressoタグが適用されたオブジェクトであり、ターゲットの方向を向くオブジェクト)に「オブジェクト」ポートを追加したことです。最初のXPressoではこれが「オブジェクトへの直接リンク」になっていたので、XPresso内部では変更できませんでした。そこで、Rootオブジェクトへのリンクを、オブジェクトポートを通して他のノードから間接的にもらうように変更したのです。しておけば、後でリンク先を自由に変更できます。左から2列目のCOFFEEノードには次のようなプログラムが書かれています。ここで、「GetObject()」はタグからそれが適用されているオブジェクトを取得する関数です。これはXPressoノードでは作れません。COFFEEを使いたくない場合は、Targetオブジェクトを指定した時と同じように、Rootを指定するためのユーザデータを追加し、そこから取得して下さい。さあ、これで入出力部分と処理部分を分離できました。左の2個のノードが入出力部分で、それ以外が処理部分です。それでは、処理部分のノードを選択し、どれか一つのノードのタイトル部分を右クリックし、「XGroupに変換」を選択して下さい。すると、処理部分だけがXGroupに変換されます。図806-13なお、COFFEEノードから出ているワイアが二股に分かれてしまいますが、これは同じものなので、一つのポートにまとめて下さい。標準」を選択すると、複雑な内部が隠れて非常にシンプルになります。図806-15しかし、内部を非表示にすると何のXGroupだかわからなくなってしまうので、XGroupとポートに適切な名前を付けておきましょう。図806-16このように入出力用のポートを明確にしておくと、XGroupを一種の「サブルーチン」として他のプログラムと組み合わせて使えるようになります。普通のプログラムのように「一つのサブルーチンを何回も使い回す」という使い方はできません。必要な数だけXGroupを複製する必要があります。それでも毎回全てのXPressoを組み直すよりはずっと簡単です。4Xプールさて、XGroupを再利用可能な形にまとめたら、これを保存しておいて、将来XPressoを作る時にも役立てたいものです。4Dのシーンファイルとして保存し、コピーペーストしてもいいのですが、XPressoにはノードやXGroupをライブラリ化する機能があります。XプールはXPresso編集ウインドウの左側にあります。Preset」があります。Operators」は、XPressoの基本ノードで、XPresso編集ウインドウを右クリックすると表示されるプルダウンメニューの内容と同じです。Preset」は、MAXONが作成した応用ノードで、応用的な機能を持ったCOFFEEノードや、基本ノードを組み合わせたXGroupが入っています。プールを作成」を選択し、プール(ライブラリ)の名前と保存する場所を指定する必要があります。XPresso」をドラッグアンドドロップして下さい。XPresso」XGroupが追加されたはずです。次に、このライブラリからXGroupを取り出す実験をしてみましょう。新規シーンを作成し、ヌルオブジェクトを作成し、XPressoタグを作成して下さい。XPresso」XGroupをドラッグアンドドロップします。XPresso」XGroupができたはずです。このXGroupのポートに二つのオブジェクトをリンクすれば、それはターゲットオブジェクトとターゲットの方を向くオブジェクトになります。各自確認してみて下さい。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の方向を向くようになったはずです。6まとめこのXPressoは最初ローカル座標で値を計算していました。オブジェクトを階層化することで問題が生じたので、計算をグローバル座標に移したのです。何でもかんでもグローバル座標に移せばいいというものではありません。テキスト803のステップ1を思い出して下さい。ポリゴンの形状を複製する際には、ポイントの位置をローカル座標で扱う必要がありました。値をグローバル座標で扱うかローカル座標で扱うかは、場合によって変ります。ここが難しいところです。)はオブジェクト座標とも呼ばれます。4Dの中にはいくつかの用語が混在していますが、本質的には同じ概念です。これらを英語で表記すると、次のようになります。R12(12.032)には設計上の大きな欠陥があります。それは「座標変換を固定」機能で、これを使うとオブジェクトの位置を扱うXPressoは全て正常に動作しなくなります。これはバグではなく、設計上の欠陥なので修正できません。「座標変換を固定」機能は、必要な情報をオブジェクトマネージャや属性マネージャ、座標マネージャに表示しないため、管理が非常に面倒で、バグの原因になります。このような理由から、座標変換を固定機能は「絶対に使わない」ようにして下さい。7COFFEEで作る最後に、今回作成したXGroupの機能を1個のCOFFEEノードで表現してみます。COFFEEを使えば、そもそもXPressoノードを複雑に組み合わせる必要も、XGroupにまとめる必要もないのです。それではサンプル806dを開いて下さい。図806-22COFFEEの中身は次のようになっていて、XPressoノードを使った計算にほぼ対応しています。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で書いたように、入出力部分や、ループ、条件分岐等についてはそれぞれに一長一短があります。