法線編集の話題も最終回です。
前回は特定のオブジェクトの方向に(正確には逆方向ですが)法線を向ける話でした。こんな感じです。
全ての法線が緑の十字オブジェクトの方を向いている
ここまで来れば両直線の始点と終点の座標を使い、連立一次方程式で交点が計算出来ます。Z座標は両直線の中間値にします。
最後にその座標に対して先ほどの回転を逆に実行し、最終的な焦点の座標を得ます。
スクリプトはこちらです。
//--------------------------------------------------------------------------------
ですが実際のモデリングではこの作業だけでは足りません。逆に、特定の法線の向きに合うオブジェクトの座標を決める必要もあります。つまり、二つの法線の焦点を見つけるという事です。
法線の焦点の座標を求めるには?二つの法線はいわゆるねじれの位置にある事もありますので、数学的には「二直線の最短距離」を求める計算を使います。こちらのページにある図が分かりやすいです。
【高校数学C】ねじれの位置にある2直線間の最短距離(共通垂線) | 受験の月
正直私には計算式が全く理解出来ませんが、この図の「共通垂線」の絶対角度を求めるには初回で話したMayaのcrossコマンドが使えます。Mayaのマニュアルを参照して頂ければ分かりますが、もう見たまんまです。
Maya Creative ヘルプ | cross | Autodesk
さて、共通垂線の絶対角度を得た後はこんな感じに計算します。
下図の赤と青の直線が二つの法線の延長線、白の線が共通垂線です。
【高校数学C】ねじれの位置にある2直線間の最短距離(共通垂線) | 受験の月
正直私には計算式が全く理解出来ませんが、この図の「共通垂線」の絶対角度を求めるには初回で話したMayaのcrossコマンドが使えます。Mayaのマニュアルを参照して頂ければ分かりますが、もう見たまんまです。
Maya Creative ヘルプ | cross | Autodesk
さて、共通垂線の絶対角度を得た後はこんな感じに計算します。
下図の赤と青の直線が二つの法線の延長線、白の線が共通垂線です。
この二つの直線が属する平面を考えてみます。
この二つの平面がZ軸方向を向く様に全体を回転させます。
「回転させる」とさらっと言いましたが、これはあくまで計算上の話なのでMayaのオブジェクトとして回転させる事は出来ません。なのでどうするかというと、回転後の頂点座標を計算してその位置に各頂点を移動させます。数学的に計算するのは大変そうですが、Mayaのrotコマンドを使うとすぐに答えが出ます。
Maya Creative ヘルプ | rot | Autodesk
ちなみにX軸回転とY軸回転の二回に分けて計算します。何故かと言うとrotコマンドでは回転方向が分からないので、両直線をYZ・XZ各平面に投影した上でcrossコマンドで法線を出し、各平面の垂直軸と正負を比較して回転方向を決める必要があるからです。(一度にやる方法があるかも知れませんが…)
さて、回転させた平面をZ軸方向から見るとこんな感じです。
Maya Creative ヘルプ | rot | Autodesk
ちなみにX軸回転とY軸回転の二回に分けて計算します。何故かと言うとrotコマンドでは回転方向が分からないので、両直線をYZ・XZ各平面に投影した上でcrossコマンドで法線を出し、各平面の垂直軸と正負を比較して回転方向を決める必要があるからです。(一度にやる方法があるかも知れませんが…)
さて、回転させた平面をZ軸方向から見るとこんな感じです。
ここまで来れば両直線の始点と終点の座標を使い、連立一次方程式で交点が計算出来ます。Z座標は両直線の中間値にします。
最後にその座標に対して先ほどの回転を逆に実行し、最終的な焦点の座標を得ます。
エッジを加えたせいで角張った様になった見た目も…
焦点を決めて法線を加工するときれいな表示に。
(左右両サイドそれぞれで焦点を決めました。)
(左右両サイドそれぞれで焦点を決めました。)
スクリプトはこちらです。
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
//GetNormalCrossing
//Takahiro Nakajima 2026
//二つの機能があります。
//A)選択している二つのバーテックスの焦点の座標を取得します。
//B)選択している全てのバーテックスの法線のルートをその焦点に向けます。
//使い方:
//1)このスクリプトを実行します。ダイアログが出ます。
//2)オブジェクトの回転をリセットします。
//3)焦点を取得したい場合は、二つの頂点を選んで上のボタンを押すと
//ダイアログのカラムに焦点の座標値が入ります。
//(法線が平行な場合は0が入ります)
//4)焦点に向けたい場合は、法線を動かしたい頂点を全て選んで下のボタンを押します。
//--------------------------------------------------------------------------------
global proc float[] getLinesCrossing(vector$S1,vector$E1,vector$S2,vector$E2)
{
//二直線L1とL2が最短距離になる点P1とP2の平均座標をfloat配列で返します。
//もし二直線が平行だった場合は返り値のアドレス3に1が入ります(それ以外は0)。
//L1の始点はS1、終点はE1
//L2の始点はS2、終点はE2
float $crossing[];
clear $crossing;
//二直線の法線nmlを取得
vector $V1 = $E1 - $S1;
vector $V2 = $E2 - $S2;
vector $nml = `cross $V1 $V2`;
//nmlがZ軸と一致する回転角(rad)を取得
float $angles[] = `getRotAngleForCrossingNormalToZaxis $nml`;
//二直線の始点と終点を回転させ、XY平面と平行にする
$S1 = `rotationNomalAlignToZaxis $S1 $angles 0`;
$E1 = `rotationNomalAlignToZaxis $E1 $angles 0`;
$S2 = `rotationNomalAlignToZaxis $S2 $angles 0`;
$E2 = `rotationNomalAlignToZaxis $E2 $angles 0`;
//XY平面上での交点の平均座標を取得
float $rotatedXY[] = `NKJ_GetCrossingOf2Lines $S1 $E1 $S2 $E2`;
//逆回転させて元に戻す(二直線が平行の場合はそのまま)
if($rotatedXY[2] == 1)
{
$crossing = {0,0,0,1};
}
else
{
float $crossingZ = (($S1.z) + ($S2.z)) / 2.0;
vector $crossingVec = <<$rotatedXY[0],$rotatedXY[1],$crossingZ>>;
$crossingVec = `rotationNomalAlignToZaxis $crossingVec $angles 1`;
float $corssingVeX = $crossingVec.x;
float $corssingVeY = $crossingVec.y;
float $corssingVeZ = $crossingVec.z;
$crossing = {$crossingVec.x,$crossingVec.y,$crossingVec.z, 0};
}
return $crossing;
}
//--------------------------------------------------------------------------------
global proc float[] getRotAngleForCrossingNormalToZaxis(vector$crossingNormal)
{
//$crossingNormalがZ軸と一致する回転角(rad)を返します。
//返り値のアドレス0がX回転、アドレス1がY回転になります。
//各軸のベクトル
vector $xAxis = <<1,0,0>>;
vector $yAxis = <<0,1,0>>;
vector $zAxis = <<0,0,1>>;
//$crossingNormalをX軸方向から見たベクトルを作成
float $crossingNormalY = $crossingNormal.y;
float $crossingNormalZ = $crossingNormal.z;
vector $XcrossingNormal = <<0,$crossingNormalY,$crossingNormalZ>>;
//上記ベクトルとZ軸との法線を取得し、法線がXプラスかXマイナスかで
//回転方向を決める
float $xDirection = 1.0;
vector $normalAndZaxisCrossing = `cross $XcrossingNormal $zAxis`;
if(($normalAndZaxisCrossing.x)<0.0)
{
$xDirection = (-1.0);
}
//上記ベクトルとZ軸との角度を取得し、回転方向を掛ける
float $xRot = `angle $XcrossingNormal $zAxis` * $xDirection;
//$crossingNormalを回転させる
vector $crossingNormalXRotated = `rot $crossingNormal $xAxis $xRot`;
//回転させた$crossingNormalとZ軸との法線を取得し、法線がYプラスか
//Yマイナスかで回転方向を決める
float $yDirection=1.0;
vector $rotNormalAndZaxisCrossing = `cross $crossingNormalXRotated $zAxis`;
if(($rotNormalAndZaxisCrossing.y)<0.0)
{
$yDirection=(-1.0);
}
//回転させた$crossingNormalとZ軸との角度を取得し、回転方向を掛ける
float $yRot = `angle $crossingNormalXRotated $zAxis` * $yDirection;
//結果を返す
float $result[] = {$xRot,$yRot};
return $result;
}
//------------------------------------------------------------
global proc vector rotationNomalAlignToZaxis(vector$crossingNormal,float$angles[],int$reverse)
{
//$reverse == 0 >> 行き
//$reverse == 1 >> 帰り
vector $result;
vector $xAxis = <<1,0,0>>;
vector $yAxis = <<0,1,0>>;
if($reverse == 0)
{
vector $crossingNormalRot1 = `rot $crossingNormal $xAxis $angles[0]`;
$result = `rot $crossingNormalRot1 $yAxis $angles[1]`;
}
else
{
vector $crossingNormalRot1 = `rot $crossingNormal $yAxis ($angles[1] * (-1.0))`;
$result = `rot $crossingNormalRot1 $xAxis ($angles[0] * (-1.0))`;
}
return $result;
}
//------------------------------------------------------------
global proc float[] NKJ_GetlinearGraphParams(float$point1[],float$point2[])
{
//point1とpoint2を通る一次方程式グラフの傾きと高さ(y=ax+bのaとb)を返します。
//もしグラフが垂直であれば、返り値のアドレス2に1が入ります(初期値は0)。
float $result[];
clear $result;
if(($point2[0] - $point1[0]) != 0.0)
{
float $a = ($point2[1] - $point1[1]) / ($point2[0] - $point1[0]);
float $b = $point1[1] - ($a * $point1[0]);
$result = {$a,$b,0};
}
else
{
$result = {0,0,1};
}
return $result;
}
//------------------------------------------------------------
global proc float[] NKJ_GetCrossingOf2Lines(vector$S1,vector$E1,vector$S2,vector$E2)
{
//二つの直線のXY平面上での交点を返します。
//もし二直線が平行な場合、返り値のアドレス2に1が入り、結果は{0,0,1}になります。
//$S1と$E1は、直線1上の二つの点のXYZ座標です。
//$S2と$E2は、直線2上の二つの点のXYZ座標です。
float $resultX = 0;
float $resultY = 0;
float $parallel = 0;
//引数から二つの直線それぞれの始点と終点のXY座標を取り出して配列に入れる
float $S1X = $S1.x;
float $S1Y = $S1.y;
float $E1X = $E1.x;
float $E1Y = $E1.y;
float $S2X = $S2.x;
float $S2Y = $S2.y;
float $E2X = $E2.x;
float $E2Y = $E2.y;
float $line1SXY[] = {$S1X,$S1Y};
float $line1EXY[] = {$E1X,$E1Y};
float $line2SXY[] = {$S2X,$S2Y};
float $line2EXY[] = {$E2X,$E2Y};
//line1とline2のaとb(傾きと高さ)を取得
float $param1[] = `NKJ_GetlinearGraphParams $line1SXY $line1EXY`;
float $param2[] = `NKJ_GetlinearGraphParams $line2SXY $line2EXY`;
//もし両方とも垂直な場合(交点無し)
if(($param1[2]==1)&&($param2[2]==1))
{
$resultX = 0;
$resultY = 0;
$parallel = 1;
}
//もし傾きが同じ場合(交点無し)
else if($param1[0] == $param2[0])
{
$resultX = 0;
$resultY = 0;
$parallel = 1;
}
//もしline1だけが垂直な場合
else if(($param1[2]==1)&&($param2[2]==0))
{
$resultX = $S1.x;
$resultY = ($param2[0] * $resultX) + $param2[1];
}
//もしline2だけが垂直な場合
else if(($param1[2]==0)&&($param2[2]==1))
{
$resultX = $S2.x;
$resultY = ($param1[0] * $resultX) + $param1[1];
}
//その他の場合
else
{
$resultX = ($param2[1] - $param1[1]) / ($param1[0] - $param2[0]);
$resultY = ($param1[0] * $resultX) + $param1[1];
}
float $result[] = {$resultX,$resultY,$parallel};
return $result;
}
//------------------------------------------------------------
global proc float[] NKJ_GetLinearGraphParamsFromVtxs(string$vtx1,string$vtx2)
{
//vtx1とvtx2を通る直線をXY平面で見た時のグラフの傾きと高さ(y=ax+bのaとb)を返します。
//もしグラフが垂直であれば、返り値のアドレス2に1が入り、配列のサイズが3になります。
float $point1[] = `xform -q -a -ws -t $vtx1`;
float $point2[] = `xform -q -a -ws -t $vtx2`;
float $result[] = `NKJ_GetlinearGraphParams $point1 $point2`;
return $result;
}
//-------------------------------------------------------
global proc NKJ_GetNormalCrossing()
{
//選択頂点を取得
string $sel[] = `ls -sl -fl -l`;
//選択頂点の座標を取得
float $vtx1Pos[] = `xform -q -a -ws -t $sel[0]`;
float $vtx2Pos[] = `xform -q -a -ws -t $sel[1]`;
//選択頂点の法線を取得
float $vtx1Nml[] = `polyNormalPerVertex -q -xyz $sel[0]`;
float $vtx2Nml[] = `polyNormalPerVertex -q -xyz $sel[1]`;
//法線先端の座標を取得
float $vtx1NmlX = $vtx1Nml[0] + $vtx1Pos[0];
float $vtx1NmlY = $vtx1Nml[1] + $vtx1Pos[1];
float $vtx1NmlZ = $vtx1Nml[2] + $vtx1Pos[2];
float $vtx2NmlX = $vtx2Nml[0] + $vtx2Pos[0];
float $vtx2NmlY = $vtx2Nml[1] + $vtx2Pos[1];
float $vtx2NmlZ = $vtx2Nml[2] + $vtx2Pos[2];
//選択頂点と法線先端の座標をベクトルにする
vector $vtx1SVec = <<$vtx1Pos[0],$vtx1Pos[1],$vtx1Pos[2]>>;
vector $vtx2SVec = <<$vtx2Pos[0],$vtx2Pos[1],$vtx2Pos[2]>>;
vector $vtx1EVec = <<$vtx1NmlX,$vtx1NmlY,$vtx1NmlZ>>;
vector $vtx2EVec = <<$vtx2NmlX,$vtx2NmlY,$vtx2NmlZ>>;
//交点の座標を取得
float $crossingPosXYZ[] = `getLinesCrossing $vtx1SVec $vtx1EVec $vtx2SVec $vtx2EVec`;
if($crossingPosXYZ[3]==1)
{
clear $crossingPosXYZ;
$crossingPosXYZ = {0,0,0,1};
}
//カラムにセット
floatField -e -v $crossingPosXYZ[0] "NKJ_GetNormalCrossing_UI|cLayout|positionXField";
floatField -e -v $crossingPosXYZ[1] "NKJ_GetNormalCrossing_UI|cLayout|positionYField";
floatField -e -v $crossingPosXYZ[2] "NKJ_GetNormalCrossing_UI|cLayout|positionZField";
}
//-------------------------------------------------------
global proc NKJ_SetNormalCrossing()
{
//選択しているバーテックスの法線の角度を
//UIから取得した座標が焦点になる様に修正します。
//現在の選択からバーテックスリストを取得
string $vtxList[] = `ls -sl -fl -l`;
//焦点座標を取得
float $focusX = `floatField -q -v "NKJ_GetNormalCrossing_UI|cLayout|positionXField"`;
float $focusY = `floatField -q -v "NKJ_GetNormalCrossing_UI|cLayout|positionYField"`;
float $focusZ = `floatField -q -v "NKJ_GetNormalCrossing_UI|cLayout|positionZField"`;
float $locatorPos[] = {$focusX,$focusY,$focusZ};
//選択解除
select -cl;
//各バーテックスに対してループ処理
string $item;
for($item in $vtxList)
{
//バーテックスの座標を取得
float $vtxPos[] = `xform -q -a -ws -t $item`;
//法線として使う値を計算
float $x = $vtxPos[0] - $locatorPos[0];
float $y = $vtxPos[1] - $locatorPos[1];
float $z = $vtxPos[2] - $locatorPos[2];
//正規化
vector $normal = <<$x,$y,$z>>;
$normal = `unit $normal`;
//法線を設定
polyNormalPerVertex -xyz ($normal.x) ($normal.y) ($normal.z) $item;
}
}
//------------------
//メイン
{
string $title = "NKJ_GetNormalCrossing";
string $titleUI = $title + "_UI";
if(`window -ex $titleUI` == 1)
{
deleteUI $titleUI;
}
window -t $title $titleUI;
columnLayout "cLayout";
text -l $title;
text -l "--------------------";
text -l "This gets a focus of 2 vtxs";
text -l "normals, or aims selected vtxs";
text -l "normals to the focus.";
text -l "1)Reset rotations.";
text -l "2) Select 2 vtxs then click upper";
text -l "button to get their focus values.";
text -l "3) Select vtxs then click lower";
text -l "button to aim them to the focus.";
text -l "--------------------";
button -w 200 -c "NKJ_GetNormalCrossing" -l "Get Nml Focus Of Selected 2 Vtxs" getCrossingButton;
floatField -w 100 -v 0.0 positionXField;
floatField -w 100 -v 0.0 positionYField;
floatField -w 100 -v 0.0 positionZField;
button -w 200 -c "NKJ_SetNormalCrossing" -l "Set Nmls On Selected Vtxs" setCrossingButton;
window -e -w 210 -h 250 $titleUI;
showWindow $titleUI;
}
//-------------------------------------------------------
//終了




























