おはこんばんちわ、ベクトル大好き 山本ほっさんです。
前回の記事では、vector型を使ったmelでの多重配列的なコード実装について紹介いたしました。
「ベクトル」というワードを聞くと、何やら難しそうな印象を持ってしまう方も少なくないと思います。
しかし、わたしたちが普段3DTool上にて行っているような、様々な局面において、とても便利な「機能」を持ったものです。
Autosesk Maya®のスクリプティング言語であるmelは、ベクトルについてアプローチしやすいスクリプティング言語であると思います。
今回はそのmelの実例を用いて、vectorの活用術をご紹介したいと思います。
-
指定した距離に応じてオブジェクトを複数配置する
例えば、開始点と終了点を指定して、あるオブジェクトを複製して配置してみます。
挙動は…
こんな感じ。
開始点・終了点の距離を縮めると…
こんな感じ。
きちんと2点間の距離に応じて配置するオブジェクトの数が変動しました。
なお、melのコードは以下の通りです。
{
string $selection[] = ls -sl
;
//if (len($selection)<1)return;
string $start = $selection[0]; //開始ポイント
string $end = $selection[1]; //終了ポイント
string $target = $selection[2]; //対象オブジェクト
vector $startVec;
vector $endVec;
vector $postVector;
float $space = 2.0; //最低あける間隔
$startVec = <<float(getAttr ($start + ".tx")),
float(getAttr ($start + ".ty")),
float(getAttr ($start + ".tz"))>>;
$endVec = <<float(getAttr ($end + ".tx")),
float(getAttr ($end + ".ty")),
float(getAttr ($end + ".tz"))>>;
$postVector = $endVec - $startVec;
$sepNum = mag($postVector)/$space;
$num = 0;
while ($num<$sepNum){
$vec = $startVec + ($postVector/floor($sepNum) * $num);
string $obj[] = duplicate $target
;
float $posTrans[] = {$vec.x,$vec.y,$vec.z};
setAttr ($obj[0] + ".tx") $posTrans[0];
setAttr ($obj[0] + ".ty") $posTrans[1];
setAttr ($obj[0] + ".tz") $posTrans[2];
$num++;
}
}
以上、一連の動作と処理内容をご覧いただきました。
では、これらは一体どういう理屈で動いているのでしょうか。
解説
処理の中でやっていることは、
①2点間のベクトルを算出する
②計算したベクトルの長さに合わせて複製配置
その2つだけです。
それぞれの手順を詳しく見ていきます。
①2間のベクトルを算出する
について。
まず、指定された2点の座標値(xyz)は、
「原点からみた対象座標へのベクトル」という風に見ることができます。
なので、開始点から終了点へのベクトルの算出は、
この2つのベクトルの間を計算すると求められ、
こんな風に計算できます。
このベクトル計算に関して詳しく知りたい人は、以下の解説をを参照ください↓
「よくわかる?解説~」
なんで前述の計算で2点間のベクトルが算出できるか、解説する前に。
2つのベクトルの足すと、
こんなイメージです。
なぜかというと言うと…。
まずベクトルAがあって…
そこにベクトルBを足すと…
結果こういうベクトルと同じになります。
ここでベクトルの元の位置に戻して改めて見てみると…
結果、元の図と同じ状態になる訳です。
ベクトルは、このように図形で考えていくと理解しやすいと思います。
さて、ここで2点間のベクトルがどうして、
ベクトルB-ベクトルA
であらわすことができるかという事ですが。
まず、こんな風に平行四辺形を考えます。
このベクトルは、ベクトルAの正反対なので、
マイナスベクトル として考えることができるんですね。
さて、ここで、先ほど解説したベクトルの足し算を思い出してみます。
マイナスベクトルAとベクトルBを足すと…
こうなりますね。
で、この部分は平行四辺形なので…
ここのベクトルは、ここのベクトルと同じになるわけです。
すると…
あら不思議!
2点間のベクトルは、ベクトルB-ベクトルAで表せてしまうわけですね!ハラショー!
つぎに、
②計算したベクトルの長さに合わせて複製配置
…について。
ベクトルには「長さ」があって、計算によって求めることができます。
melで書くとこうです。
mag($postVector);
なんて簡単!
このように、melではvector型を演算する様々なスクリプトが提供されているので、手計算では面倒な計算でも結構お手軽に利用できるので、便利です。
この長さを、指定された、最低限のオブジェクト間の幅の数値で割ってあげると
このように、配置できる個数が計算できます。
つまり、イメージ的には、
まず、このベクトル分動かした後で、
このベクトルに沿って、指定した長さ分移動してやれば、よさそうです。
なので、処理的には…
$vec = $startVec + ($postVector/floor($sepNum) * $num);
$startVec分移動させた後、$postVector/floor($sepNum)分の移動を$num回分繰り返した結果のベクトルを計算します。
すると、最終的に配置したい場所の座標がわかるので、
float $posTrans[] = {$vec.x,$vec.y,$vec.z};
setAttr ($obj[0] + ".tx") $posTrans[0];
setAttr ($obj[0] + ".ty") $posTrans[1];
setAttr ($obj[0] + ".tz") $posTrans[2];
このように、float形に変換した上で、その値をsetAttrしてあげればよいわけです。
-
近い頂点をあぶりだす
前の例から、2点間の距離を簡単に求められることがわかりました。
この特性を用いると、近接頂点の検出などが簡単に実装できます。
挙動的には
こんな感じ。
処理の内容は以下の通りです。
{
string $vtxList[] =ls -sl -fl
; //選択頂点の取得
float $checkDistance = 1.0; //距離の閾値を設定
select -cl;
//各頂点ごとにテスト
for ($vtxA in $vtxList){
//頂点座標からベクトルAを取得
float $posA[] = pointPosition -l $vtxA
;
vector $vecA = <<$posA[0],$posA[1],$posA[2]>>;
for ($vtxB in $vtxList){
//同一頂点であればスキップ
if ($vtxA!=$vtxB){
//頂点座標からベクトルBを取得
float $posB[] = pointPosition -l $vtxB
;
vector $vecB = <<$posB[0],$posB[1],$posB[2]>>;
//2頂点間のベクトルを求めて、距離を測る
if (mag($vecA-$vecB)<$checkDistance){
//短かったら選択
select -add $vtxA $vtxB;
}
}
}
//処理が終わった頂点はリストから除外して、無駄な試行回数を短縮
stringArrayRemove({$vtxA},$vtxList);
}
}
解説
ここで行っている処理はとてもシンプルで、2頂点間の距離について指定された長さより短いかどうかを確認しているだけです。
なお、ここで使ったサンプルのモデルも、実はベクトルによるスクリプト処理で作りました。
内容が気になる方は、以下閑話休題をご覧ください。
~~閑話休題はここから~~
ちなみにですが、上記頂点モデルのサンプルは、平面ポリゴンの頂点を一定距離ランダムで動かしたものですが、これの生成にもベクトルを使ったスクリプトを使用しました。
import maya.cmds as mc
import maya.api.OpenMaya as om
import random
vtxList = mc.ls(sl=True,fl=True)
for vtx in vtxList:
vec = om.MVector(1.0,0.0,0.0)
rot = om.MEulerRotation(0.0,random.random()*360.0,0.0)
vec = vec.rotateBy(rot)
mc.move(vec.x,vec.y,vec.z,vtx,r=True)
動作はこんな感じです。
これはpythonでのスクリプトですが、melと同様にvectorを使った処理が可能です。
ここでの処理は、
1の長さの単位ベクトルを作成し、ランダムに回転させて、移動先のベクトルを生成。
その位置へ頂点を移動しただけです。
このようにベクトルは回転もできるので、様々なアプローチが試せて面白いですね~。
-
オブジェクトの方向に対象メッシュの法線を向ける
手作業で行うと、ものすごく面倒な法線の調整ですが、ベクトルを使えばその設定も簡単になるはずです。
例えば…
このように、選択頂点の法線を、オブジェクトの位置に合わせて編集する、などが簡単に行えます。
上記のコードは
{
string $vtxList[] =ls -sl -fl;
string $normalObj = "SetNormalObj";
float $normalObjPos[] = getAttr ($normalObj + ".translate");
vector $normalObjVec = <<$normalObjPos[0],$normalObjPos[1],$normalObjPos[2]>>;
for ($vtx in $vtxList){
string $objFromVtx[] = stringToStringArray($vtx,".");
float $objPos[] = getAttr ($objFromVtx[0] + ".translate");
vector $objVec = <<$objPos[0],$objPos[1],$objPos[2]>>;
vector $aimVec = $normalObjVec - $objVec;
float $aimPos[] = {float($aimVec.x),
float($aimVec.y),
float($aimVec.z)};
polyNormalPerVertex -xyz
$aimPos[0]
$aimPos[1]
$aimPos[2]
$vtx;
}
}
こんな感じになっています。
解説
これは、法線方向を決めるオブジェクト「SetNormalObj」と、その頂点が含まれる対象のオブジェクトの2点間のベクトルを算出し、その方向に、選択した頂点の法線を向けています。
実は法線の情報自体がベクトルなので、算出したベクトルの数値をそのままfloat変換して代入するだけでよいのです。
ここではオブジェクトの位置で法線を編集する手法を解説しましたが、実装を工夫すれば、オブジェクトの回転で対象頂点の法線を指定することもでるはずです。
ここまでの知識を応用して、是非お試しください。
ざっとこんな感じでしょうか。
オブジェクトや頂点をtranslateで処理しようとすると、値がx,y,z軸の3要素に分かれてしまって、考えないといけないことが多くなってしまいます。
しかし、「ベクトル」という概念を使うと、「向き」と「長さ」というシンプルな要素で処理することができて、大変便利です。
また、前回の記事でも紹介したように、melでvector型が使えるという事は、エクスプレッションにも応用が利くという事です。
次回の記事では、vector型を使ったエクスプレッションによるリギングの例を紹介したいと思います。
それでは、今回はこの辺で。