Dec 10, 2009
Scripting 001 - 'Random Array'
Cheetah3D のスクリプティングについての第二回
はじめに
今回は、ポリゴンスクリプトを使って、子オブジェクトをコピーするクリエータオブジェクトを作成してみます。
ポリゴンスクリプトで、必ず定義しなければならない関数は2つ
- buildUI - この関数内でオブジェクトのプロパティを定義します。この関数は、オブジェクトがシーンに追加された時に、呼ばれます。
- buildObject - この関数内で 3D ビューでの描画を定義します。オブジェクトの描画アップデートが必要な時に、その都度呼ばれます。
まずエディタで新規ファイルを作成し、以下のコードをコピペして下さい。以下のコードでは、とりあえず空の関数を定義しています。
function buidUI( obj ) {
}
function buildObject( obj ) {
}
2つの関数とも、引数として自身のオブジェクトの参照が渡されます。buildUI 関数では、パラメータの追加や初期化を行い、 buildObject 関数内で、実際の描画コードを記述していきます。buildUI 関数は、スクリプトメニューから選択されてシーンに呼び出されたときに1度、buildObject 関数は、自身のパラメータが変更されるタイミング意外にも、親オブジェクトの更新といったシーングラフの更新時にも毎回呼ばれます。
buildUI を定義
まず、buildUI 関数内では、自身のプロパティを定義します。
function buildUI( obj ) {
// 初期値 1 最小値 1 最大値 10 の整数型パラメータを追加
obj.addParameterInt('count',1,1,10,true,true);
}
ここでは 'count' という名前の整数のパラメータを追加しています。addParameterInt 関数の場合、引数は順に、パラメータ名・初期値・最小値・最大値・アニメーション可/不可フラグ・ビルドフラグ(このパラメータが変更された場合 buildObject を呼び出してシーングラフを更新するかどうか)になります。
ここで、一度ファイルを保存しシーンに呼び出してみます。ポリゴンスクリプトの場合、~/ライブラリ/Application Support/Cheetah3D/scripts/Polygonobj フォルダに保存します。この時、拡張子は js にして下さい。一度スクリプトメニューから、シーンに呼び出してみましょう。 buildObject 関数内には、まだ何も書いてないので 3D ビューには何も描画はされませんが、オブジェクトリストビューには、スクリプトオブジェクトが登場していると思います。'count' パラメータが、ポリゴンスクリプトのプロパティパネルに表示されていることを確認して下さい。
その他、プロパティにパラメータを追加する関数は以下のようなものがあります。これらの関数は、プロパティを定義できる他のタイプのスクリプト(スプライン/ツール)でも共通です。
- void addParameterBool(String paraname, Boolean initvalue, Boolean paramin, Boolean paramax, Boolean animate, Boolean build)
論理値を追加します。 - void addParameterButton(String paraname, String buttonname, String functionname)
ボタンを追加します。第3引数で指定した関数が、自身のオブジェクトの参照を引数として呼ばれます。 - void addParameterFloat(String paraname, Number initvalue, Number paramin, Number paramax, Boolean animate, Boolean build)
浮動小数点数を追加します。 - void addParameterInt(String paraname, Number initvalue, Number paramin, Number paramax, Boolean animate, Boolean build)
整数を追加します。 - void addParameterSeparator(String paraname)
プロパティパネルにタイトル付きのセパレータを追加します。 - void addParameterSelector(String paraname, Array options)
選択リストを追加します。第2引数には、選択リストのラベル文字列の配列を設定します。戻り値は、String 型のインデックスです。(この関数はヘルプファイルに記載されていません。)
簡単な buildObject
それでは、実際の描画コードを書いていきましょう。ポリゴンオブジェクトをスクリプトから操作するには、まずポリゴンオブジェクトから PolyCore オブジェクトを取り出し、その PolyCore オブジェクトに対して描画関数を実行していきます。
3角形を描くコードは下記のようになります。
function buildObject ( obj ) {
// ポリゴンオブジェクトに定義されている core() 関数を使用して、PolyCore オブジェクトを取得
var core = obj.core()
// ポリゴンを構成する頂点を配列で定義、とりあえず単純な3角形
// xyz 座標を指定する Vec3D 型の配列を使用
var vertices = [ new Vec3D(0, 0, 0), new Vec3D(0, 1, 0), new Vec3D(1, 0, 0) ];
// 一番シンプルなポリゴン生成関数を使用します
core.addPolygon( vertices.length, false, vertices );
}
ここスクリプトメニューから、シーンに追加してみましょう。3角形が表示されていると思います。
ポリコア PolyCore オブジェクトにポリゴンを追加する関数には2種類あります。
- Number addIndexPolygon( Number size )
- Number addIndexPolygon( Number size, Number p[] )
- Number addIndexPolygon( Number size, Number p[], Vec2D uv[] )
追加済み頂点(ポイント)のインデックス番号を使用して、ポリゴンを生成します。戻り値は生成したポリゴンのインデックス番号。頂点の追加には addVetex 関数を使用します。 - Number addPolygon( Number size )
- Number addPolygon( Number size, bool meshed, Vec3D p[] )
- Number addPolygon( Number size, bool meshed, Vec3D p[], Vec2D uv[] )
戻り値は生成したポリゴンのインデックス番号。第2引数の meshed を true にした場合、既存の頂点データを捜査してすでに同じ位置に頂点が追加されている場合には、それを使用してポリゴンを生成します。
また addIndexPolygon で使用する頂点(ポイント)のみを追加する関数は addVertex を使用します。
- Number addVertex(Boolean meshed, Vec3D vert)
戻り値は、頂点のインデックス番号です。第1引数の meshed を true にした場合、既存の頂点データを捜査して既に同じ位置に頂点が追加されている場合には、頂点を追加せず、既存の頂点のインデックス番号を返します。
buildVertexBSP を使って頂点捜査を高速化
addPolygon や addVertex で既存の頂点を捜査して頂点を追加する場合、頂点数が多くなった場合に捜査に時間がかかり動作が遅くなります。 buildVertexBSP 関数を使って、オブジェクト内の頂点データの BSP ツリーを生成すると、頂点の捜査が速くなります。
// BSP ツリーの最小値と最大値を設定
// ポリゴンを生成する空間の最小値と最大値を設定して BSP ツリーを生成
// ここで空間(最小値と最大値)を大きく取りすぎると捜査の精度が落ちるので注意
buildVertexBSP( new Vec3D( 0, 0, 0), new Vec3D( 2, 2, 2) );
/*
addVertex や addPolygon で meshed を true にしてポリゴンを生成
*/
// 生成が終わった段階で、必ず BSP ツリーを破棄してメモリを解放すること
destroyVertexBSP();
今回は、子オブジェクトのデータをそのままコピーするので、頂点追加時に既存の頂点(ポイント)を捜査する必要はありません。なので、BSP ツリーは使いません。
子オブジェクトからポリゴンを生成
次に子オブジェクトの情報からポリゴンを生成してみます。
function buildObject( obj ) {
// 小オブジェクトがない場合、何もしない
if ( obj.childCount() == 0) return;
// 子オブジェクトを取得し、ポリゴンファミリーでない場合、何もしない
var child = obj.childAtIndex(0);
if ( child.family() != NGONFAMILY) return;
// ここまでエラーチェック
//
// for ループ用の変数
var i,j;
// 子オブジェクトの PolyCore を取得
var child_core = child.core();
// モディファイア適用後の PolyCore を使いたい場合は、modCore() を使用
// var child_core = child.modCore();
// 自身のコアを取得
var core = obj.core();
// 頂点をコピー
var vertexCount = child_core.vertexCount();
for (i = 0;i < vertexCount;i++) {
// 子オブジェクトからのコピーなので、第1引数は false (頂点捜査はしない)
core.addVertex(false, child_core.vertex(i));
}
// ポリゴンをコピー
var polyCount = child_core.polygonCount();
var polySize = 0;
for (i = 0;i < polyCount;i++) {
var indices = [];
polySize = child_core.polygonSize(i);
for (j = 0;j < polySize;j++) {
indices.push( child_core.vertexIndex( i, j ) );
}
var pi = core.addIndexPolygon( polySize, indices );
// UV 座標をコピー
for (j = 0;j < polySize;j++) {
core.setUVCoord(pi, j, child_core.uvCoord(i, j));
}
}
}
ここまでを、再度シーンに呼び出してみます。子オブジェクトを追加して、子オブジェクトのモードタグで不可視に設定します。そのままでは、オブジェクトの更新がかからず何も描画されていないと思うので、'count' 等のパラメータを変更して、強制的にオブジェクトをアップデートしてみて下さい。子オブジェクトと同じ形状が表示されました?
いちいち描画更新のために、パラメータをいじるのが面倒なので、このオブジェクトをクリエータオブジェクトに設定しておきます。
function buildUI ( obj ) {
// 初期値 1 最小値 1 最大値 10 の整数型パラメータを追加
obj.addParameterInt("count",1,1,10,true,true);
// クリエータオブジェクトに設定
obj.setCreatorObj( true );
}
これで、子オブジェクトは自動的に非表示になり、子オブジェクトの変更で自身がアップデートされるクリエータオブジェクトとして振る舞うよう設定されました。
パラメータを使って
ここまでだと、単に子オブジェクトをコピーするだけなので、全くスクリプトを使う意味がありませんよね。
次に、追加しておいた 'count' パラメータを使って、 count 分だけランダムにコピーするように変更してみます。
function buildObject( obj ) {
// 小オブジェクトがない場合、何もしない
if ( obj.childCount() == 0) return;
// 子オブジェクトを取得し、ポリゴンファミリーでない場合、何もしない
var child = obj.childAtIndex(0);
if ( child.family() != NGONFAMILY) return;
// ここまでエラーチェック
// count を取得
var count = obj.getParameter("count");
// 子オブジェクトの PolyCore を取得
var child_core = child.core();
// モディファイア適用後の PolyCore を使いたい場合は、modCore() を使用
/*
var child_core = child.modCore();
*/
// 自身のコアを取得
var core = obj.core();
for (var i = 0;i < count;i++) {
var vec = new Vec3D( Math.random() * 5 - 2.5, Math.random() * 5 - 2.5, Math.random() * 5 - 2.5);
// ポリゴンコピーのコードを関数化して外に
copyPolyCore( core, child_core, vec );
}
}
function copyPolyCore( core, child_core, vec ) {
// ほぼ同じコード
var i,j;
// 頂点をコピー
// 前のコピーで追加された頂点数オフセットとして取得
var vertexCount_offset = core.vertexCount();
var vertexCount = child_core.vertexCount();
for (i = 0;i < vertexCount;i++) {
// 子オブジェクトからのコピーなので、頂点は捜査しない。
// 第3引数の vec (ランダム要素)を加算してコピー
core.addVertex(false, child_core.vertex(i).add(vec) );
}
// ポリゴンをコピー
var polyCount = child_core.polygonCount();
var polySize = 0;
for (i = 0;i < polyCount;i++) {
var indices = [];
polySize = child_core.polygonSize(i);
for (j = 0;j < polySize;j++) {
indices.push( vertexCount_offset + child_core.vertexIndex( i, j ) );
}
var pi = core.addIndexPolygon( polySize, indices );
// UV 座標をコピー
for (j = 0;j < polySize;j++) {
core.setUVCoord(pi, j, child_core.uvCoord(i, j));
}
}
}
これで、'count' で設定した数だけランダムな位置にコピーするスクリプトオブジェクトが出来ました。
これにさらに調整できるパラメータを追加したものを Ramdom Array.js として公開していますので、興味のある方は是非チェックしてみて下さい。(古いコードなので、ちょっとあれですけど… )
ちょっと長過ぎたかも。
読んでいただいた皆様ありがとうございます。
3 Comments
Re: Scripting 001 - 'Random Array'
なんという素晴らしいエントリー!
いやー、Cheetah3D、安価な3Dプログラミング環境として、すごい面白いんじゃないかという気がしてきました。
僕もこのエントリーを参考にやってみますー。
ありがとうございますー。
From : シン石丸 @ 2009-12-11 14:36:36 Edit
Re: Scripting 001 - 'Random Array'
コメントどうもありがとうございます。文章は難しいですね… 用語の使い方が若干あやしいのですが、ご容赦ください。
石丸さんのブログに刺激されて、次回は Structure Synth 的な使い方をちょっと考えてみようかなぁと思ってます。子オブジェクトのコピーするこのスクリプトは、それを想定してのものだったり、、ただあの文法が実はまだ完全に理解できてなかったりするんですよねぇ、
後、文章はちょっとつらいので、スクリーンキャストで言い訳しながら喋っちゃおうかなぁとか、、
From : tg @ 2009-12-11 16:03:46 Edit
Re: Scripting 001 - 'Random Array'
いやー、ポリゴンスクリプト、面白いですね!
非常に単純なものから、試してみてます。
ところで、tgさんのRandom Arrayの作例ではいろいろな色のマテリアルが、それぞれのオブジェクトに適応されていますが、このもとになっているマテリアルはどこに設定すればよいのでしょうか?
単純なポリゴンスクリプトで作ったオブジェクトをRandom Arrayでたくさん増やしてみて、カラフルにしたいのですができずに困っています。
教えて君で申し訳ありませんが、教えていただければ幸いです。
From : シン石丸 @ 2009-12-15 03:48:24 Edit