2012年02月

2012年02月29日

max script 勉強してみた その66 3dsmax 2012

前回に引き続きカーブコントロールを調べてみたい。

前回はカーブコントロールのコンストラクタのパラメータをやったから、今回はプロパティから。

  • <curvecontrol>.visible : Boolean
  • <curvecontrol>.numCurves : Integer
  • <curvecontrol>.x_range : Point2
  • <curvecontrol>.y_range : Point2
  • <curvecontrol>.x_value : Float
  • <curvecontrol>.displayModes : BitArray
  • <curvecontrol>.commandMode : Name
  • <curvecontrol>.zoomValues : Point2
  • <curvecontrol>.scrollValues : Point2
  • <curvecontrol>.curves : Array, read-only

これらはほとんどが前回調べたプロパティと同じなのでそこは割愛して、初めて出てきたcurvesだけ調べてみたい。

curvesプロパティはカーブコントロールで扱うカーブを要素とする配列になっている。だからカーブコントロールで編集したカーブにアクセスする場合はここからカーブを取得すればいい。

例えば前回のコードを使ってカーブコントロールを生成し、

rollout rolCurve "Curve Control Test"
(
 local uiflgs=#(
  #drawBG,
  #drawgrid,
  #upperToolbar,
  #lowerToolbar,
  #ruler,
  #scrollBars
 )

 CurveControl cvctrl01 "Curve Control:"
  numcurves:2
  uiFlags:uiflgs
)

myfloater = newRolloutFloater "My Floater" 400 300
addRollout rolCurve myfloater

このように2つのカーブを編集した(2つのカーブは最初は重なっているので上側のツールバーの1と2のボタンをON/OFFして1本だけ有効にして編集し、ポイントを右クリックしてポイントをベジェスムーズ化したりした)。

fig01

この状態でグローバル変数rolCurveにロールアウトが入っているのでそこからカーブコントロールにアクセスして、変数ccに取り出し、

cc = rolCurve.cvctrl01
MAXCurveCtl:cvctrl01

このccからcurvesプロパティにアクセスすると、このように2つのカーブが入った配列が得られる(以降カーブコントロールが変数ccに入っているものとして例示する)。

cc.curves
#(ccCurve, ccCurve)

カーブが2本になったのはコンストラクタのパラメータnumcurvesに2を指定したからだ。この配列はread-onlyなので変更は不能だ。カーブを追加したい場合はnumCurvesプロパティの値を変更すればいい。例えばこのカーブコントロールにもう1本カーブを追加して3本にしたいなら、

cc.numCurves = 3

としてやればいい。再びcurvesプロパティにアクセスすると、配列の要素が3つになっているのが確認できる。

cc.curves
#(ccCurve, ccCurve, ccCurve)

また、カーブコントロールの表示もフィルターボタンが3つになって、編集エリアにも新たなカーブが追加されている。

fig02

この配列から得られるカーブオブジェクトは以下のプロパティを持っている。

  • <ccCurve>.animatable : Boolean
  • <ccCurve>.color : Color
  • <ccCurve>.disabledColor : Color
  • <ccCurve>.width : Integer
  • <ccCurve>.disabledWidth : Integer
  • <ccCurve>.disabledStyle : Name
  • <ccCurve>.style : Name
  • <ccCurve>.lookupTableSize : Integer
  • <ccCurve>.numPoints : Integer
  • <ccCurve>.points : Array, read-only

animatableはtrueにすると、カーブの中のポイントにキーフレームアニメーションを付けられるようになる。この状態でオートキーをONにしてからポイントを移動すると、そのフレームにキーが打たれる。このプロパティはデフォルトでtrueになっている。

colorは有効なカーブの色。上で得られたカーブ例えば1本目のカーブを青に変えるには、

cc.curves[1].color=blue
(color 0 0 255)

としてやればいい。

fig03

同様にdisabledColorは無効になっているカーブの色を設定/取得するためのプロパティだ。

widthはカーブの幅。デフォルト値は0だ。例えば先の青いカーブの太さを5にしてみると

cc.curves[1].width=5
5

このようになる。

fig04

同様にdisabledWidthは無効状態のカーブの幅。

styleとdisabledStyleは有効・無効の時のカーブのスタイルだ。下の値が指定できる。

  • #solid :実線(デフォルト)
  • #dash :破線
  • #dot  :点線
  • #dashDot:一点鎖線
  • #dashDotDot:二点鎖線
  • #null:線なし
  • #insideFrame:実線

#insideFrameは用途がよくわからない。以下がそれぞれのスタイルにしてみたものだ。

#dash
fig05

#dot
fig06

#dashDot
fig07

#dashDotDot
fig08

#null
fig09

 lookupTableSizeについてはマニュアルに、

このメソッドは、カーブ コントロールの検索テーブルのサイズを設定します。検索テーブルを使用することにより、カーブ コントロールのユーザは、値に素早くアクセスできるようになります。検索テーブルの既定サイズは 1000 です。カーブに対して getValue() が呼び出されると、この値が使用されます。

とある。どうやらカーブの横軸の値に対して該当するカーブの値を算出する際に、横軸の値からどのポイント間のカーブであるかを探し出すオーバーヘッドを減らすためのテーブルのようだ。とすればサイズはカーブ内のポイントの数よりは多くないとダメだろうね(未確認)。

numPointsはカーブ内のポイントの数。上の例で行けば、

cc.curves[1].numpoints
4
cc.curves[2].numpoints
3
cc.curves[3].numpoints
2

となる。

pointsはカーブ内のポイントの配列。これも読み出し専用だ。

cc.curves[1].points
#points([0,-0.677419], [0.239285,-0.193635], [0.717858,0.0803864], [1,1.19355])

要素をひとつ取り出してみるとCurvePointValue値になっている。

cc.curves[1].points[1]
#CCPoint(@[0,-0.677419])

これを使えばカーブ内のポイント1つ1つが個別に操作できるわけだ。

最後にひとつだけメソッドをやっておくと、getValueを使うとカーブの横軸の値に対する縦軸の値が取得できる。

getValue ccCurve <time_value> <float_x> [lookup:<false>]

<time_value>はポイントがアニメーションしていた場合にいつの時刻の値を求めればいいかを指定するもので、<float_x>はX軸の値、lookupはルックアップテーブルを使って高速化したいかどうか(メモリー消費は増えるし、作成オーバーヘッドがあるからポイントが少ないときは使わない方がいい気がする(未確認))だ。

fig10
 例えば上の例で試してみると、

getValue cc.curves[1] 0 0.5
-0.0257556

のように時刻0で横軸0.5の時のカーブの高さが求められた。

それではまた次回。

maxまとめページ



take_z_ultima at 12:11|この記事のURLComments(0)TrackBack(0)3ds Max | CG

2012年02月28日

modo501 SDK いじってみた その90 modo501 SP6

今回も引き続きCGearPart::BrewRimSoupを調べて行きたい。

今回は以下の部分から。

std::vector<Point> dPdus, dPdvs;
if (hasUVs) {
 GenerateDPDUs (points, uvs, triangles, dPdus, dPdvs);
}

コメントに

Generate the vertex dPdu/dPdv values using our point, uv, and point lists.

pointとuvとポイントリストを使ってvertex dPdu/dPdv値を生成する

と書いてあって、再びlxu_geometry.cppの中のファンクションが呼び出されている。呼び出されるGenerateDPDUsには以下のコメントが書いてある。

Generate vertex dPdu/dPdv values, with smoothing for shared vertices.

共有頂点をスムージングして、vertex dPdu/ dPdv値を生成する

仮引数は

 void
GenerateDPDUs (
 vector<Point> &points,
 vector<UV> &uvs,
 vector<unsigned> &triangles,
 vector<Point> &dPdus,
 vector<Point> &dPdvs)

となっていて、全て参照渡しだ。最終的にdPdusとdPdvsに生成された値が格納されるわけだね。

まずは前回同様dPdus、dPdvsにpointsに入っているポイントの数と同数の[0,0,0]を格納する。前回同様、平均値を出すための初期値になるわけだね。

dPdus.clear ();
dPdvs.clear ();
Point zeroVec;
LXx_VCLR (zeroVec.vec);
unsigned pointIndex;
for (pointIndex = 0;
  pointIndex < points.size ();
  ++pointIndex)
{
 dPdus.push_back (zeroVec);
 dPdvs.push_back (zeroVec);
}

同様にしてrefCountsヴェクター配列にもpointsの要素数ぶんの0を格納する。

vector<unsigned> refCounts;
for (pointIndex = 0;
  pointIndex < points.size ();
  ++pointIndex)
{
 refCounts.push_back (0);
}

そしてこれも前回同様、for文でtriIndexを3飛ばしで増加させながら繰り返させ、1回のループごとにtrianglesに格納された三角形1つ分のデータを処理するループになっている。

for (unsigned triIndex = 0;
 triIndex < triangles.size ();
 triIndex += 3) {

まずtrianglesから1つの三角形の頂点番号のセットをpi0〜pi2に取り出し

unsigned pi0 = triangles[triIndex + 0];
unsigned pi1 = triangles[triIndex + 1];
unsigned pi2 = triangles[triIndex + 2];

その番号を使ってpoints配列から該当するpointデータをp0〜p2に取り出す。

Point *p0 = &points[pi0];
Point *p1 = &points[pi1];
Point *p2 = &points[pi2];

同様にuv値も・・・あれ?3つ目はなぜかインデックスがヘンな形になっている。これってtriangles[triIndex + 2]をpi2に置き換えても問題ないはず。

UV *uv0 = &uvs[pi0];
UV *uv1 = &uvs[pi1];
UV *uv2 = &uvs[triangles[triIndex + 2]];

そしてここでdPdu、dPduvをUVGradientで計算し、falseが返って来た時は[1,0,0]を代入している。とりあえずUVGradientについては後回しにして置くね。

LXtVector dPdu, dPdv;
if (!UVGradient (
 p0->vec, p1->vec, p2->vec,
 uv0->uv, uv1->uv, uv2->uv,
 dPdu, dPdv))
{
 dPdu[0] = dPdv[0] = 1.0;
 dPdu[1] = dPdv[1] = dPdu[2] = dPdv[2] = 0.0;
}

そして、今回処理されたポイントのrefCountsを1増加させる。これを繰り返せばrefCountsには各ポイントの参照回数が記録されるわけだ。

refCounts[pi0]++;
refCounts[pi1]++;
refCounts[pi2]++;

そして得られたdPduとdPdvを各ポイントに対応したdPdus、dPdvs配列に加算する。

Point *dPdu0 = &dPdus[pi0];
Point *dPdu1 = &dPdus[pi1];
Point *dPdu2 = &dPdus[pi2];

LXx_VADD (dPdu0->vec, dPdu);
LXx_VADD (dPdu1->vec, dPdu);
LXx_VADD (dPdu2->vec, dPdu);

Point *dPdv0 = &dPdvs[pi0];
Point *dPdv1 = &dPdvs[pi1];
Point *dPdv2 = &dPdvs[pi2];

LXx_VADD (dPdv0->vec, dPdv);
LXx_VADD (dPdv1->vec, dPdv);
LXx_VADD (dPdv2->vec, dPdv);

ここまでが三角形ごとに繰り返されるループだ。これで各ポイントに対するdPduとdPdvの合計値と、各ポイントが何度加算されたかがdPdus、dPdvs、refCountsに得られる。最後にこのrefCountsでdPdus、dPdvsの値を割れば、平均値が得られるわけだね。

for (pointIndex = 0;
   pointIndex < points.size ();
   ++pointIndex)
{
 unsigned refCount = refCounts[pointIndex];
 if (refCount > 1) {
  Point *dPdu = &dPdus[pointIndex];
  Point *dPdv = &dPdvs[pointIndex];

  double invN = 1.0 / refCount;

  LXx_VSCL (dPdu->vec, invN);
  LXx_VSCL (dPdv->vec, invN);
 }
}

以上がこのファンクションの流れだ。

次にdPdu、dPdvを生成しているUVGradientを調べてみたい。まずファンクションの冒頭に書いてある注釈は以下の通り。

Compute the UV gradient, which is the derivative of the world position with respect to U and V. The caller provides the world position of three corners of a triangle, and their corresponding UV coordinates, and the results are returned in dPdU and dPdV. The function returns false if the triangle was degenerate.

UV勾配を算出する、それはUとVについてのワールド座標の微分係数である。呼び出し側は三角形の3つの角のワールド座標値とそのUV座標値を渡し、dPdUとdPdVに結果を返す。三角形が作れなかった場合、このファンクションはfalseを返す。

仮引数は以下のように参照渡しはdPdUとdPdVだけだ。これに結果が戻るわけだね。

 static bool
UVGradient (
 const LXtVector wp0,
 const LXtVector wp1,
 const LXtVector wp2,
 const LXtFVector2 uv0,
 const LXtFVector2 uv1,
 const LXtFVector2 uv2,
 LXtVector &dPdU,
 LXtVector &dPdV)

まず3つのポイントのuv値、wp値から2番のポイントを始点、0番と1番を終点としたベクトルを算出して(du1,dv1)、(du2,dv2)、dp1、dp2に代入している。

double du1, du2, dv1, dv2, determinant;
LXtVector dp1, dp2;
int i;

du1 = uv0[0] - uv2[0];
du2 = uv1[0] - uv2[0];
dv1 = uv0[1] - uv2[1];
dv2 = uv1[1] - uv2[1];

LXx_VSUB3 (dp1, wp0, wp2);
LXx_VSUB3 (dp2, wp1, wp2);

次に (du1,dv1)ベクトルと(du2,dv2)ベクトルの外積(2つのベクトルの行列式)を求めて0の場合falseを返して終了する。2次元ベクトルどうしの外積は2つのベクトルが成す平行四辺形の面積になるので面積が0(2つのベクトルが平行)の時は計算不能で処理をやめるって感じだろうね。

determinant = du1 * dv2 - dv1 * du2;
if (determinant == 0.0)
 return false;

次にこれらの値からUVの勾配を求めている。

for (i = 0; i < 3; i++) {
 dPdU[i] =
  ( dv2 * dp1[i] - dv1 * dp2[i])
  / determinant;

 dPdV[i] =
  (-du2 * dp1[i] + du1 * dp2[i])
  / determinant;
}
return true;

この式についての詳しい話はまた次回。

カテゴリー別ページ



take_z_ultima at 11:30|この記事のURLComments(2)TrackBack(0)modo | CG

2012年02月27日

max script 勉強してみた その65 3dsmax 2012

前回はロールアウトフローターウインドウを調べてみた。今回はそのフローターを使ってカーブコントロールについて調べてみたい。

カーブコントロールはグラフを描いて時間に対する関数を定義するコントロールで下のようなグラフパネルとそれを操作するボタンが組み合わさったルックフィールになっている。

fig01

書式は以下の通り。

CurveControl <name>
 [<caption>]
 [visible:<boolean>]
 [numCurves:<integer>] 
 [x_range:<point2> ]
 [y_range:<point2>]
 [x_value:<float> ]
 [zoomValues:<point2>]
 [scrollValues:<point2>]
 [displayModes:<bitarray>]
 [commandMode:
  <#move_xy | #move_x | #move_y |
          #scale | #corner | #bezier]
 [uiFlags:<array_of_ui_flags> ]  [rcmFlags:<array_of_rcm_flags>]
 [asPopup:<boolean>]

captionとvisibleは何度も出てきたからいいね。

numCurves:<integer>はこのコントロールで扱うカーブの数。デフォルトは0なので、そのままだとカーブが表示されない状態になる(もちろん無いカーブにポイントを追加したりは出来ない)。

例えば以下のコードは400×300のフローターウィンドウを作って、そこにカーブコントロールが入ったロールアウトを表示するものだ。

rollout rolCurve "Curve Control Test"
(
 CurveControl cvctrl01 "Curve Control:"
)

myfloater = newRolloutFloater "My Floater" 400 300
addRollout rolCurve myfloater

これを実行すると、以下のようにコントロールは表示されるけど、カーブもポイントも無い状態になる。

fig02


これにnumCurves:1を追加すると以下のように両端にポイントを持ったカーブが現れる。以下フローターの部分は同じなのでロールアウト句だけ載せるね。

rollout rolCurve "Curve Control Test"
(
 CurveControl cvctrl01 "Curve Control:"
  numcurves:1
)

fig03

ポイントが現れたら上に並んでいる5つのボタンでポイントを移動させたり追加や削除などが出来るようになる。

もちろんnumcurvesの値を増やせばそれだけカーブの数も増やせる。複数のカーブがある時は下の画像のようにグラフの上に「1、2、3、・・・」と数字の書かれたフィルターボタンが表示され、それぞれのカーブの有効無効を切り替えられるようになる。

fig04

uiFlagsとrcmFlagsはどちらもフラグを要素に持つ配列値でコントロールのルックフィールや動作を指定するものだ。

uiFlagsには以下のフラグがあって、

  • #drawBG:背景を白く塗りつぶす
  • #drawgrid:グリッド線と座標値を表示する
  • #upperToolbar:上側のツールバー(ポイント操作)を表示する
  • #showReset:リセットボタンを表示する
  • #lowerToolbar:下側のツールバー(表示操作)を表示する
  • #scrollBars:水平・垂直スクロールバーを表示する
  • #autoScroll:オートスクロールを有効にする
  • #ruler:横軸の目盛を表示
  • #constrainY:SetYRange()でセットしたポイントの値の有効範囲を有効にする.
  • #hideDisabled:無効なカーブは表示しない
  • #all:#singleSelect、#noFilterButtons、#xvalue以外全部ONにする
  • #xvalue:x_valueで指定した位置に縦線を描く
  • #singleSelect:ポイントの複数選択を禁じる
  • #noFilterButtons:カーブの有効無効を切り替えるフィルターボタンの表示を無効にする

その中から必要なものをピックアップして配列にいれてuiFlags引数としてカーブコントロール生成の時に渡せばいい。

例えば下のコードのように何も指定しないと

rollout rolCurve "Curve Control Test"
(
 local uiflgs=#()

 CurveControl cvctrl01 "Curve Control:"
 numcurves:2
 uiFlags:uiflgs
)

こんな表示になる。

fig05

背景を白で塗って、上下のツールバーを表示すると、

rollout rolCurve "Curve Control Test"
(
 local uiflgs=#(
  #drawBG,
  #upperToolbar,
  #lowerToolbar

 )

 CurveControl cvctrl01 "Curve Control:"
  numcurves:2
  uiFlags:uiflgs
)

こんな感じだ。

fig06

単純に配列にフラグを並べてやれば、各機能が有効に出来るわけだね。

同様にrcmFlagsは以下のフラグを指定できる。

  • #move_xy
  • #move_x
  • #move_y
  • #scale
  • #corner
  • #bezier
  • #delete
  • #all

これらはポイントを右クリックした時に出るメニューに追加するメニュー項目を有効にするもので、空の配列#()を指定すると、メニューは「上の3つ(コーナー、ベジェスムーズ、ベジェコーナー)のメニューのみになる」

fig07

ツールバーを非表示にしてこれを使えばポイントの操作に制限をかけられるかな。

x_range、y_range:<point2>はX値とY値の上限・下限の有効範囲を設定するもの。カーブは初期状態で必ず始点と終点の2つのポイントが生成されるけど、その始点と終点のX値はこのx_rangeの値が使われる。また、Yの値の上限下限はuiFlagsに#constrainYがセットされていないと有効にならない

下のコードはXの範囲を±5、Yの範囲を±1にしてみたもの。

rollout rolCurve "Curve Control Test"
(
 local uiflgs=#(
  #drawBG,
  #drawgrid,
  #upperToolbar,
  #lowerToolbar,
  #ruler,
  #scrollBars,
  #constrainY
 )

 CurveControl cvctrl01 "Curve Control:"
  numcurves:2
  uiFlags:uiflgs
  x_range:[-5,5]
  y_range:[-1,1]

)

fig08

x_value:<float>は指定したX値の位置に縦線を表示するためのXの値を指定するもの。これとuiFlagsの#xvalueによって縦線が出る。と、マニュアルにはあるんだけど、どうもx_valueというプロパティが見当たらない。自分が試したのは2012だけだけど、このバージョンでは使えないようだ。
下のようにロールアウトからカーブコントロールを変数ccに取得して、プロパティ「x_range」にアクセスすると取得出来るけど、「x_value」はエラーが出る。

cc=rolCurve.cvctrl01
MAXCurveCtl:cvctrl01
cc.x_range
[0,1]
cc.x_value
-- 未知のプロパティ : "x_value" 場所は MAXCurveCtl:cvctrl01

zoomValuesとscrollValues:<point2>は初期のグラフの表示範囲を決めるもので、XY方向の倍率とスクロールバーの位置が設定できる。

displayModes:<bitarray>は有効なカーブをbitarray値で指定する。例えば5つのカーブがあって、2番目と5番目のカーブだけ有効な状態でスタートさせたいなら、

rollout rolCurve "Curve Control Test"
(
 CurveControl cvctrl01 "Curve Control:"
 numcurves:5
   displayModes:#{2,5}
)

でOKだ。

commandMode:はスタート時のコマンドを何にするか指定するもの。

  • #move_xy
  • #move_x
  • #move_y
  • #scale
  • #corner
  • #bezier

指定したボタンがONになった状態でカーブコントロールが始まる。

asPopup:<boolean>はtrueにすると、ロールアウトから飛び出して単独でフローティングウィンドウで表示される。

fig09

だからこれを知っていればフローターをわざわざ調べなくても・・・

長くなったので続きはまた次回。

maxまとめページ



take_z_ultima at 12:22|この記事のURLComments(0)TrackBack(0)3ds Max | CG

2012年02月24日

modo501 SDK いじってみた その89 modo501 SP6

前回に引き続きCGearPart::BrewRimSoupを調べてみたい。前回までのところでpointsとtrianglesベクター配列に頂点座標データと、それらの接続情報が格納され、uvsにポイントのuvマップ値が格納されている。

今回はその次のコードから。

std::vector<Point> normals;
GenerateNormals (points, triangles, normals);

新たにnormalsベクター配列が作成されてGenerateNormalsが呼び出されている。たぶん頂点の法線ベクトルを生成してるんだろうね。さっそくGenerateNormalsを調べてみると、ファンクションの注釈に

Generate vertex normals for a triangle list,with normal smoothing for shared vertices.

共有頂点の法線スムージングと共に三角形リストのための頂点法線を作成する

と書いてあった。コードはまず仮引数リストが以下のようになっていて、points、triangles、normalsどれも参照渡しだ。

 void
GenerateNormals (
 vector<Point> &points,
 vector<unsigned> &triangles,
 vector<Point> &normals)

次にnormalsベクター配列をクリアして、新しく生成したPoint構造体zeroVecのvecメンバーの値をLXx_VCLRで[0,0,0]に初期化し、pointsベクター配列と同じ数だけそれをnormalsベクター配列に追加している。これで全部[0,0,0]の配列が出来たわけだ。

normals.clear ();
Point zeroVec;
LXx_VCLR (zeroVec.vec);
for (unsigned pointIndex = 0;
   pointIndex < points.size ();
   ++pointIndex) {
 normals.push_back (zeroVec);
}

ちなみにLXx_VCLRは以下のようにマクロ定義されていて、

#define LXx_VCLR(a) LXx_VSET(a,0.0)
#define LXx_VSET(a,x) ((a)[0]=(x), (a)[1]=(x), (a)[2]=(x))

aで与えられた配列の0〜2の要素に0.0が代入されるのがわかる。

次に、for文でtriIndex値を0〜triangles配列の最後の要素まで3ずつ増加させながら繰り返し、triangles[triIndex + 0]、triangles[triIndex + 1]、triangles[triIndex + 2]によってtriangles配列から3つずつデータを取り出して、それをインデックスにしてpoints配列からポイントデータをp0、p1、p2に取り出している。これで繰り返すたびにtrianglesに格納されている三角形の頂点が順にp0〜p2に入ることになる。

const double MIN_VECTOR_LENGTH = 1e-20;

for (unsigned triIndex = 0;
   triIndex < triangles.size (); triIndex += 3) {

 unsigned pi0 = triangles[triIndex + 0];
 unsigned pi1 = triangles[triIndex + 1];
 unsigned pi2 = triangles[triIndex + 2];

 Point *p0 = &points[pi0];
 Point *p1 = &points[pi1];
 Point *p2 = &points[pi2];

次にLXx_VSUB3を使ってベクトルの引き算をして、三角形の2つのエッジをベクトルにする。そしてこれらのベクトルの外積をLXx_VCROSSでとることによって、2つのエッジが張る面に垂直のベクトルを生成し、normalに代入する。

LXtVector edge1, edge2, normal;
LXx_VSUB3 (edge1, p1->vec, p0->vec);
LXx_VSUB3 (edge2, p2->vec, p0->vec);
LXx_VCROSS (normal, edge1, edge2);

fig01

2つのベクトルの外積から法線を求めるのは常套手段みたいなもんだね。外積はかける順番によってベクトルの方向が反転するから注意が必要だ(edge2 ×edge1なら上の図のnormalは下向きになる)。ベクトルの長さは三角ポリゴンの面積の2倍(2つのベクトルが作る平行四辺形の面積)になる。

ここで得られたnormalベクトルの長さをLXx_VLENで求めて先に定義したベクトルの長さを0と見なしていい限界値MIN_VECTOR_LENGTHt比較して小さいようなら[1,0,0]にして、そうじゃなければその長さでnormalベクトルを割ることでnormalのベクトルの長さを1にする。これで法線ベクトルは単位ベクトルに正規化されるわけだね。

double mag = LXx_VLEN (normal);
if (mag < MIN_VECTOR_LENGTH) {
 LXx_VSET3 (normal, 1.0, 0.0, 0.0);
}
else {
 double invMag = 1.0 / mag;
 LXx_VSCL (normal, invMag);
}

ちなみに長さが0になっちゃった場合についてのコメントが以下のように書いてある。

For normals with a length too close to zero, make a valid normal to avoid problems.

長さがあまりにもゼロに近い法線のために、問題を回避するための有効な法線を作る

まあエラーが出るよりマシ程度の措置なんだろうね。

次にpointsベクター配列からポイントデータを取り出したの同じ方法で、pointsデータに対応した法線データをnormals配列から取り出してn0〜n2に代入する。そしてこれに先に計算したnormalを加算する。これによって複数の三角ポリゴンに共有されたポイントの法線ベクトルは、各ポリゴンの法線ベクトルを加算したものになるわけだ。ここで最初に0で初期化した効果が出てくるわけだね。

Point *n0 = &normals[pi0];
Point *n1 = &normals[pi1];
Point *n2 = &normals[pi2];

LXx_VADD (n0->vec, normal);
LXx_VADD (n1->vec, normal);
LXx_VADD (n2->vec, normal);

ここまでが最初に出てきたfor文の繰り返し範囲。 これを抜けるとnormalsには各頂点の法線ベクトル(頂点を共有するポリゴン法線の合ベクトル)が入ってるってわけだね。

最後にこれらの法線ベクトルが全て長さ1になるように処理をしている。これでnormalsにはそのポイントを共有するポリゴンの法線ベクトルを平均した長さ1のベクトルが入ることになる。

for (unsigned normalIndex = 0;
  normalIndex < normals.size ();
  ++normalIndex)
{
 double mag =
  LXx_VLEN (normals[normalIndex].vec);
 if (mag < MIN_VECTOR_LENGTH) {
  LXx_VSET3 (normals[normalIndex].vec,
          1.0, 0.0, 0.0);
 }
 else {
  double invMag = 1.0 / mag;
  LXx_VSCL (normals[normalIndex].vec, invMag);
 }
}

これでGenerateNormalsの呼び出しを抜けるとnormalsに各ポイントの法線ベクトルが格納された。

このGenerateNormalsはlxu_geometry.cppに定義されていて、これはSDKが提供するGeometry Utilitiesのひとつになっている。ファンクション名でマニュアルを全文検索してみたけど解説は無いみたいだな。これ以外にもcommonプロジェクトで提供されるファンクションには便利なものが満載されているみたいなので、一度一通り調べてみる必要があるね。

それではまた来週。

カテゴリー別ページ



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)modo | CG

2012年02月23日

max script 勉強してみた その64 3dsmax 2012

今回はカーブコントロールを調べてみたいと思ったんだけど、このコントロールは大き過ぎてユーティリティーロールアウトには収まり切れない感じだ。そこでこの際だから先にそういう大きなロールアウトを扱うための仕組みについて調べてみることにした。

ロールアウトフローターウインドウはまさにそんなニーズにこたえてくれる仕組みで、ロールアウトを表示出来る任意の大きさの独立したウインドウを作る事が出来る。

fig01

フローターウインドウ生成の書式は以下の通り。  

newRolloutFloater
 <title_string>
 <width_integer>
 <height_integer>
 [<top_integer> <left_integer>]

パラメータは<title_string>がウインドウのタイトル文字列で、<width_integer>がウインドウの幅、<height_integer>がウインドウの高さ、<top_integer>と <left_integer>はウインドウの左隅の点のデスクトップ上の配置位置だ。省略するとウインドウはMAXのウインドウの中央に表示される。例えば上の画像は以下のコードの実行結果だ。500X300のサイズでタイトルに「My Floater」と書かれたウインドウがMAXのウインドウの中央に表示される。

myfloter = newRolloutFloater "My Floater" 500 300

生成したフローターウィンドウはウインドウ右上にある閉じるボタンを押して閉じることも出来るし、次のメソッドでコードから閉じることも可能だ。

closeRolloutFloater <rolloutFloater>

プロパティは以下のものがある。

<rolloutFloater>.title String

titleはウインドウのタイトル文字列。読み込んだり書き換えたり出来る。

<rolloutFloater>.size Point2
<rolloutFloater>.pos Point2

sizeとposはウィンドウのサイズとデスクトップ上での位置を表すPoint2値。このプロパティでウインドウのサイズや配置位置を調べたり、変更したりできる。変更した場合はこのウインドウ内に追加したロールアウトに移動( moved)やサイズ変更(resized)のイベントが起こる。

ちなみにデスクトップのサイズは

sysInfo.DesktopSize

で調べられるし、MAXのウインドウサイズと位置は

getMAXWindowSize()
getMAXWindowPos()

で調べられるから、これらを使ってウインドウサイズをMAXのウインドウの半分にするとか、デスクトップの右上に表示するとか、いろいろ出来るね。

例えば現在表示されているフローターウインドウがグローバル変数myfloaterから参照できる場合、

fsize = myfloater.size
dsize = sysInfo.DesktopSize
fpos = dsize / 2 - fsize / 2
myfloater.pos = fpos

としてやれば、フローターウインドウをデスクトップの中央に持って来られる。下の図はデスクトップのフローターを表していて、デスクトップのサイズがDW×DHでフローターのサイズがFW×FHだとすると、フローターの位置posはデスクトップのサイズを半分にした座標値(デスクトップの中央)からフローターウインドウのサイズの半分の値を引いた位置になる事がわかる。

fig02

<rolloutFloater>.inViewport Boolean, read-only.

inViewportはフローターが拡張ビューポートにある場合はtrueを返す。拡張ビューポートについてはregisterViewWindowで登録して呼び出すとそうなるらしいんだけど詳しくは次の機会に譲っとくね。

<rolloutFloater>.open Boolean, read-only.

openはフローターウインドウが開いている時にtrueを返す。例えば同じフローターを複数出したくない時なんかはこのプロパティを調べてfalseだったら生成するようにすればいいかも。

<rolloutFloater>.dialogBar Boolean, read-only.

dialogBarはフロータがダイアログバーとして使用されている場合はtrue を返す。これもcui.RegisterDialogBarでダイアログバーとして登録できるみたいだけどまた次の機会に。

<rolloutFloater>.rollouts Array of rollouts in floater, read-only

rolloutsはフローターに含まれるロールアウトを配列で取得するためのもの。で、フローターにロールアウトを追加する方法だけど、以下のメソッドで追加と削除が出来る。

addRollout <rollout>
 [ <rolloutFloater> ]
 [rolledUp:<boolean> ]
 [border:<boolean>]

removeRollout <rollout>
 [ <rolloutFloater> ]

<rollout>パラメータにはロールアウト句で作成したロールアウトはもちろん、今までやってきたユーティリティー句で作成したユーティリティーロールアウトも指定できる。例えば、前回作ったラジオボタンのユーティリティロールアウトをフローターに出すなら以下のようにすればいい。

utility utlradiobutton "Radio Button Test"
(
 radiobuttons rbtn01
  labels:#("choice 1", "choice 2", "choice 3")
)

myfloter = newRolloutFloater "My Floater" 200 100
addRollout utlradiobutton myfloter

これが実行結果。

fig03

フローターの中にさらにユーティリティーロールアウトの囲いが出来て、その中に配置されている。

まあこれはちょっと変則的なもので、ユーティリティーパネルに登録する必要が無いならutility句を使わずにrollout句を使って定義する。実はユーティリティーはロールアウトを拡張したクラスでユーティリティーパネルへの登録機能が付いたロールアウトだ。だからロールアウトが使えるところでは同じように使えるわけだね。

rollout utlradiobutton "Radio Button Test"
(
 radiobuttons rbtn01
  labels:#("choice 1", "choice 2", "choice 3")
)

myfloter = newRolloutFloater "My Floater" 200 100
addRollout utlradiobutton myfloter

また、addRolloutのrolledUpパラメータをtrueにすると、ロールアウトがロールアップした形で配置されるし、

fig05

borderパラメータをfalseにすると、下のように縁なしの表示も可能だ。

fig04

もちろんユーティリティーパネルからさらにフローターを出すことも出来るよ。例えば下のコードはボタンを押した時にフローターウインドウを出すものだ。2つ以上表示しないようにチェックをしているよ。

utility utlFloaterWindow "Floater Test"
(
 rollout rolradiobutton "Radio Button Test"
 (
  radiobuttons rbtn01
  labels:#("choice 1", "choice 2", "choice 3")
 )

 local myfloater
 button btnshow "Show Floater"

 on btnshow pressed do
 (
  if myfloater == undefined or myfloater.open == false do
  (
   myfloater = newRolloutFloater "My Floater" 200 100
   addRollout rolradiobutton myfloater
  )
 )
)

ロールアウトをフローターで出すと、広いスペースが使えるようになるだけじゃなくて、ユーティリティーパネルでは不可能だったモディファイパネルへの切り替えとかも可能になるよ。

それではまた次回。

maxまとめページ



take_z_ultima at 12:20|この記事のURLComments(0)TrackBack(0)3ds Max | CG
Archives