クラスタにマテリアルを与えているうちに、同じマテリアルをうっかり複数クラスタに与えてしまうことってあるじゃないですか。
と言うよりも、そのマテリアルを持つクラスタはすでに存在しているから、クラスタにポリゴンを追加すりゃいいだけなのに、ポリゴンにまた同じマテリアルを与えることによってうっかり新規クラスタとして作成してしまうことってよくあるじゃないですか。
そんなことして無駄に増えていったクラスタを整理するスクリプトを書いてみたのですが、一応動いているように見える。
if ( Selection.Filter.name == "object" )
{
for ( var i=0; i<Selection.count; i++ )
{
if ( Selection(i).IsClassOf( siX3DObjectID ) )
var oClusters = Selection(i).ActivePrimitive.Geometry.Clusters;
MergeClusters( oClusters );
}
}
else
{
Logmessage( "ドルァ" );
}
function MergeClusters( oClusters )
{
var oMatCol = XSIFactory.CreateObject( "XSI.Collection" );
oMatCol.Unique = true; // これ重要
var oMatClsCol = XSIFactory.CreateObject( "XSI.Collection" );
// GridParameter にぶち込むための 「クラスタ&そのマテリアルコレクション」 「マテリアルのコレクション」 作成
for ( var i=0; i<oClusters.count; i++ )
{
if ( oClusters(i).type == "poly" )
{
var oMat = GetClusterMat( oClusters(i) );
if ( oMat )
{
oMatClsCol.Add( oClusters(i) );
oMatClsCol.Add( oMat );
oMatCol.Add( oMat );
}
}
}
// oMatClsCol のイメージ
// クラスタオブジェクト1, そのマテリアル, クラスタオブジェクト2, そのマテリアル......
// Cls1, Cls1.mat, Cls2, Cls2.mat, Cls3, Cls3.mat .....
//Logmessage( oMatClsCol.count + " : " + oMatClsCol );
//Logmessage( oMatCol.count + " : " + oMatCol )
// GridParameter 作成 2次元配列みたいなもん?
// 行=マテリアル 列=そのマテリアルを持つクラスタ
var oP = XSIFactory.CreateObject("CustomProperty");
var oGridParam = oP.AddGridParameter("Grid") ;
var oGridData = oGridParam.value;
oGridData.RowCount = oMatCol.count;
oGridData.ColumnCount = oMatClsCol.count/2;
// ここで Row (行)の数(=マテリアルの数)は先に確定
for ( var i=0; i<oMatCol.count; i++ )
{
oGridData.SetRowLabel( i, oMatCol(i).name );
}
// ルックアップテーブル作成
ClsCounter = 0;
for ( var i=0; i<oMatClsCol.count; i+=2 )
{
var oCls = oMatClsCol(i);
var oMat = oMatClsCol(i+1);
for ( var j=0; j<oGridData.RowCount; j++ )
{
RowLabel = oGridData.GetRowLabel( j );
if ( RowLabel == oMat.name )
{
oGridData.SetCell( ClsCounter, j, oCls ); // デバッグ時、この行と次の行を入れ替える
//oGridData.SetCell( ClsCounter, j, oCls.name );
// → GridParameter を目で見るため。 かつ、下の InspectObj を有効にする
ClsCounter++;
}
}
}
//InspectObj( oP )
// テーブルの Row ごとに(=マテリアルごとに)処理
for ( var i=0; i<oGridData.RowCount; i++ )
{
// セルの値(クラスタ)をアレイにぶち込み、さらに空白削除して左詰めにした新規アレイを再構築
var aSafeArray = oGridData.GetRowValues( i );
var aValues = new VBArray( aSafeArray ).toArray() ;
var aClusters = new Array( ); //空白削除用の新規アレイ
for ( var j=0; j<aValues.length; j++ )
{
if ( aValues[j] != null ) //元のセルから取った値が null じゃなかったら新規アレイに追加という。
{
aClusters.push( aValues[j] );
}
}
if ( aClusters.length != 1 )
{
var oDestinationCluster = aClusters[0];
for ( var j=1; j<aClusters.length; j++ )
{
var oSub = aClusters[j].CreateSubComponent( );
DeletedClusterName = aClusters[j].name;
DeleteObj( aClusters[j] );
var oUpdatedCluster = SIAddToCluster( oDestinationCluster, oSub )(0);
Logmessage( DeletedClusterName + " --> " + oDestinationCluster.name + " に統合しますた!! (;゚∀゚)" );
}
}
}
}
function GetClusterMat( oCluster )
{
for ( var j=0; j<oCluster.LocalProperties.count; j++ )
{
if ( oCluster.LocalProperties(j).IsClassOf( siMaterialID ) )
{
return oCluster.LocalProperties(j);
break;
}
}
}
オブジェクト(複数可)を選んで実行します。 ポリゴンとかクラスタを選んでいるとダメです。オブジェクト選択モードでないといけません。
ポリゴンクラスタを探し、見つかったポリゴンクラスタからマテリアルを探し、マテリアルがダブっていたらクラスタを統合し、抹殺します。
たとえばこの状態。
この sphere には、ポリゴンクラスタが5個ありますね。
そのうち、Cls4 は独自のマテリアルを持っていませんでした。よって Cls4 は無視します。
Cls4 以外のクラスタが持つマテリアルを比較します。
すると Cls3 と Cls5 が mat3 という同一のマテリアルを持っていることがわかりました。
この状態でスクリプトを実行すると、

こうなります。
1枚目の画像でエクセルのシートのようなものが表示されている PPG がありますが、これがスクリプトの中で内部的に使っているルックアップテーブルです。 スクリプトの中のコメントに従って書き換えると、画像のように表示できます。デバッグ時に表示させて、正しく動いているかどうかの検証に使っていたものです。脳味噌の中でこねくり回しても俺はすぐ混乱してしまうので、XSI の GridParameter を使って可視化しているわけですね。 このように、かき集めたクラスタとそのマテリアルをこの表に一度書き込んでしまい、次に表の頭から順に比較し、処理をしています。
もっと上手い方法もあると思う。アルゴリズム的にあまり美しくない気もする。無駄なことやってる部分もあるでしょう。でもまあ、一応望んだ挙動になっているのでOKです。
マテリアルの中身を比較したりはしません。つまり、例えば mat1 と mat2 の RenderTree 構成や各シェーダのパラメータが全く同一だったとしても、mat1 と mat2 という風にシーン内に別マテリアルとして存在している以上、中身が同一でも別マテリアルと見なします。
クラスタ関係のスクリプトはいずれ整理して、一群のツールセットにしたくなってきました。クラスタマテリアルをたくさん使わざるを得ないタイプのモデリング作業をしていると、この辺のスクリプトが必須です。手でやってたら気が狂います。
ごきげんよう
.
最近のコメント