選択したフェイスを一定の方向にフラット化させるscriptを作ってみました。
faceと法線方向を決めるedgeを選択し実行すると、edgeが直行する平面上に選択されたfaceの頂点座標がそろいます。
//————————————————————————————//
string $faceStr[]=`filterExpand -sm 34`;
string $edgeStr[]=`filterExpand -sm 32`;
string $list[];
string $tkBuf[];
float $tNml[]={0,0,0};
int $i=`size $faceStr`;
select $faceStr;
float $selBB[]=`xform -q -ws -bb`;
float $selCp[]={(($selBB[0]+$selBB[3])/2),(($selBB[1]+$selBB[4])/2),(($selBB[2]+$selBB[5])/2)};
float $posP[];
float $distP[];
float $t;
int $j=0;
if(`size $edgeStr`==0){
for($work in $faceStr){
$list=`polyInfo -faceNormals $work`;
tokenize $list[0] " " $tkBuf;
$tNml[0]+=((float)($tkBuf[2])/$i);
$tNml[1]+=((float)($tkBuf[3])/$i);
$tNml[2]+=((float)($tkBuf[4])/$i);
}
}else{
select $edgeStr[0];
ConvertSelectionToVertices;
for($work in `filterExpand -sm 31`){
$posP=`pointPosition $work`;
if($j==0){
$j=1;
$tNml[0]-=$posP[0];
$tNml[1]-=$posP[1];
$tNml[2]-=$posP[2];
}else{
$tNml[0]+=$posP[0];
$tNml[1]+=$posP[1];
$tNml[2]+=$posP[2];
}
}
}
float $nlNml[]={$tNml[0],$tNml[1],$tNml[2]};
float $d=$nlNml[0]*$selCp[0]+$nlNml[1]*$selCp[1]+$nlNml[2]*$selCp[2];
select $faceStr;
ConvertSelectionToVertices;
for($work in `filterExpand -sm 31`){
$posP=`pointPosition $work`;
$t=($d-($nlNml[0]*$posP[0])-($nlNml[1]*$posP[1])-($nlNml[2]*$posP[2]))/(($nlNml[0]*$nlNml[0])+($nlNml[1]*$nlNml[1])+($nlNml[2]*$nlNml[2]));
$distP[0]=$posP[0]+($t*$nlNml[0]);
$distP[1]=$posP[1]+($t*$nlNml[1]);
$distP[2]=$posP[2]+($t*$nlNml[2]);
move $distP[0] $distP[1] $distP[2] $work;
}
//————————————————————————————//
内容はとっても簡単です。
このmelで実際に頂点座標を計算している部分は以下の5行ですので・・・。
float $d=$nlNml[0]*$selCp[0]+$nlNml[1]*$selCp[1]+$nlNml[2]*$selCp[2];
$t=($d-($nlNml[0]*$posP[0])-($nlNml[1]*$posP[1])-($nlNml[2]*$posP[2]))/(($nlNml[0]*$nlNml[0])+($nlNml[1]*$nlNml[1])+($nlNml[2]*$nlNml[2]));
$distP[0]=$posP[0]+($t*$nlNml[0]);
$distP[1]=$posP[1]+($t*$nlNml[1]);
$distP[2]=$posP[2]+($t*$nlNml[2]);
縮めれば1行で済んでしまいますね・・・。($dは定数になります、ですので毎回計算しないようにループの外に出してあります)
それ以外の部分は何か?といえば、選択されたものの種別わけと、計算に必要な中心座標や法線ベクトル等を取得をしているだけです。
数学は、ほんのちょっぴり、あとは手続きの記述です。
文系&デザイナーの皆さんも恐れずに踏み込めますね。
では、melを書く上で有意義だと思われるところだけ細かく説明していきます。
■まず選択された物の種別わけをし、リストを作成します。
polygonではfilterExpandという関数をよく使用します。
この関数に引数を指定してあげると、現在選択されているものの中から、必要な種別のリストを得ることが出来ます。
string $faceStr[]=`filterExpand -sm 34`;
この行では「-sm 34」を指定しています、リファレンスを見ていただければわかるかと思いますが「34」はpolygonFaceをリスト化してくれます。
string $edgeStr[]=`filterExpand -sm 32`;
この行では「-sm 32」を指定しています、「32」はpolygonEdgeがリスト化されます。
「31」はpolygonVertices、「33」はUVです、そのほかにも色々ありますので、何がリスト化できるくらいは大雑把に覚えておくと便利です。
■選択された物の中心位置を求めます。
float $selBB[]=`xform -q -ws -bb`;
この行はxformで選択された物のバウンディングボックスのサイズをゲットしています。
xformは複数オブジェクトに対応していませんので扱いに注意が必要です。
float $selCp[]={(($selBB[0]+$selBB[3])/2),(($selBB[1]+$selBB[4])/2),(($selBB[2]+$selBB[5])/2)};
取得したxyzの最大値と最小値から中心点を求めます。
足して2で割れば平均値がでて、それが中心・・・という非常に単純な・・・。
■選択されていた物の数によって分岐させます。
string $edgeStr[]=`filterExpand -sm 32`;
この行によって得られたedgeのリストの個数を調べると、edgeが選択されて「いたのか、いないのか」ということがわかります。
リストの個数を調べるのはsizeという関数を使います。
size $edgeStr;
こう書くとedgeのリストを収めた配列の個数がわかります。
それをif文で分岐させます。
if(`size $edgeStr`==0){
この場合edgeStrが0か否かをチェックして分岐させています。
■edgeが選択されていなかったら、選択されたfaceの法線を求める。
数学では内積を使うのでしょうが、melなので関数で求めちゃいます。
$list=`polyInfo -faceNormals $work`;
polyInfoという関数にfaceのリストのメンバーを渡してそのメンバーの法線を取得しています。
ただし、face単位のstring配列で戻ってきます。
法泉はテキストの中に埋まっている状態です、なので分解しないといけません。
tokenize $list[0] " " $tkBuf;
stringを分解するにはtokenizeという関数を使います。
一番目の引数に分解したい文字列、二番目の引数に分解するためのきっかけとなる文字群の文字列、3番目に分解した後のデータが格納されるstring配列を指定します。
この場合、2番目の引数に" "(スペース)が指定されていますので、スペースで区切られた範囲をstring配列に格納してくれます。
しかし、この戻り値の中身はあくまで文字列ですので数値として利用するにはもう一手順必要です。
C言語ですとatoiやsprintfやscanfといった関数で変換しますが、Mayaはそこら辺は良い感じで自動変換しちゃってくれます。
ただし、演算式内などでは型変換をしてあげないといけません。
$tNml[0]+=((float)($tkBuf[2])/$i);
$tNml[1]+=((float)($tkBuf[3])/$i);
$tNml[2]+=((float)($tkBuf[4])/$i);
上記のように「 (float) 」と頭につけると型変換されます。(プログラム的にはキャストって言います)
でもって得られた法線を平均化するためにfaceの数で割って加算していっています。
演算式がいわゆる四則演算の表記と違っているので説明しときます。
$tNml[0]+=((float)($tkBuf[2])/$i);
この行ですが普通に書くとこうなります。
$tNml[0]=$tNml[0]+((float)($tkBuf[2])/$i);
同じ変数が同じ行の中にあると見難くなるので、演算式を工夫して見やすく、且つ何をしているのかわかりやすくしているというわけです。
■edgeが選択されていたら法線を求める。
前述の部分で分岐させていますので、edgeが選択されていた場合の処理のパートにedgeから法線を求めるscriptを書きます。
法線の求め方は簡単ですね・・・終点の座標から始点の座標を引き算するだけです。
今回はベクトルの方向はどちらでもかまわないのですごく楽です。
select $edgeStr[0];
edgeを選択して・・・
ConvertSelectionToVertices;
verticesに選択しなおします
for($work in `filterExpand -sm 31`){
選択されたvertices分だけループするようにします。その際$workにverticesの名前を代入させてます。
$posP=`pointPosition $work`;
頂点のワールド座標を求めています。
if($j==0){
$j=1;
$tNml[0]-=$posP[0];
$tNml[1]-=$posP[1];
$tNml[2]-=$posP[2];
}else{
$tNml[0]+=$posP[0];
$tNml[1]+=$posP[1];
$tNml[2]+=$posP[2];
}
}
始点を最初に引いておいて、後から終点の座標を足してます、順番は特に意味はないです、今回は。
■演算部分
比較したい法線ベクトルが直行する平面に対して、編集したい頂点を通過点とした同じ法線ベクトルが交差する場合の直線式に代入する係数を得ます。
float $d=$nlNml[0]*$selCp[0]+$nlNml[1]*$selCp[1]+$nlNml[2]*$selCp[2];
$t=($d-($nlNml[0]*$posP[0])-($nlNml[1]*$posP[1])-($nlNml[2]*$posP[2]))/(($nlNml[0]*$nlNml[0])+($nlNml[1]*$nlNml[1])+($nlNml[2]*$nlNml[2]));
この辺は数学の本のほうが詳しいかも。
平面の式 ax+by+cz=d
面法線ベクトル vector <<a,b,c>>
通過点を(x1,y1,z1)とした場合の直線の式
x=x1+t*a1
y=y1+t*b1
z=z1+t*c1
直線のベクトル vector <<a1,b1,c1>>
平面の式に代入して定数dを求める
a(x1+t*a1)+b(y1+t*b1)+c(z1+t*c1)=d
平面とvector<<a,b,c>>との係数を求める。
t=(d-ax1-by1-cz1)/(a+a1+b*b1+c*c1)
$distP[0]=$posP[0]+($t*$nlNml[0]);
$distP[1]=$posP[1]+($t*$nlNml[1]);
$distP[2]=$posP[2]+($t*$nlNml[2]);
求められた係数を法線ベクトルにかけて移動値を得ます、それを元の位置に加算すると、平面(ax+by+cz=d)上に投射された頂点座標が得られます。
今回は平面のベクトルと投射するベクトルが同じなので同じ数値を利用してます。
move $distP[0] $distP[1] $distP[2] $work;
んで、移動させます、以上。