Houdiniで橋のモデリングを行いUnityのHDRPでセットアップして表示してみる(Part 1)
はじめに
この記事はシリーズのPart 1です。 目次はこちらです。
.hiplcファイル
今回作成する橋の.hiplcファイルはこちらです。
橋の作成にあたって自作したhdaの.hiplcファイルは次のとおりです。
橋をシーンに配置したシーンの.hiplcファイルはこちらです。
作成する橋の仕様の決定
まずは、作成するモデルのどの部分をパラメータとして操作可能にするかを決定します。
今回は次のようなパラメータを用意することにしました。
バウンディングボックスのサイズとしてsizex、sizey、sizezを用意します。
橋の両脇の欄干部分の高さとしてbalustradeheightを用意します。
橋の開始と終わりの部分の長さについて、startlength、endlengthを用意します。
橋の開始と終りの部分の支柱について、オン・オフできるようにします。
橋の裏側のグリッドのサイズについて、gridsizex、gridsizeyを用意します。
これらの用意したパラメータで、橋のモデルのサイズなどをあとから自由自在に変更できるようモデルを構築していきます。
橋の長さを変更した際に、橋の裏側のパターンなどが大きさに合わせて適切に繰り返し処理を行うようにします。
Houdiniでモデリングをする際は、どの程度パラメータ化しモデルを弄られるようにするかを予め決定しておくのがよいです。 何でもかんでもパラメータとして外部に公開すると使うのも大変ですし、作るのも大変になります。 一方で、パラメータが少ないと作れるバリエーションが少なくなります。 ここはトレードオフになっているので、よくよく考えて自分の用途に必要なパラメータというものを見極める必要があります。 今回は大きさを自由に変更できるようにして、その他のディティールなどは大きさに合わせて自動で繰り返してくれるようにします。
HDAの作成
今回、モデリングの作業にはいくつかの自作ノードを利用します。 まずは、その自作ノードの作成から解説をします。
Boltノード
ボルトの頭の部分のノードを作成します。 こういったパーツをHDAにしておくと使い回しがきいて便利です。 ベイク用のハイポリモデルの他、LOD対応のために荒いモデルにも切り替えられるようにします。
Houdiniを新しく開きます。
ネットワークビュー上でTabキーを押して「geo」と入力し、Geometryでエンターを2回押してGeometryノードを作成します。
geo1ノードを選択した状態でEnterキーかiキーを押してgeo1ノードの中に入ります。
Tabキーからcircleと入力してcircle1ノードを作成します。
Primitive TypeをPolygonに変更し、Divisionsを24とします。
PolyExtrudeノードを作成し接続します。
PolyExtrudeノードのディスプレイフラグを有効にします。
polyextrude1のDistanceを0.2にします。
polyextrude1のFront Groupを有効にします。
PolyBevelノードを接続します。
polybevel1ノードのパラメータを次のように設定します。
シーンビューは次のようになります。
新たにCircleノードを作成します。
circle2ノードのパラメータを次のように設定します。
PolyExtrudeノードを新たに作成しcircle2ノードに接続します。
polyextrude2ノードのパラメータを次のように設定します。
シーンビューは次のようになっています。
さらにPolyExtrudeノードを接続します。
polyextrude3ノードのパラメータを次のように設定します。
さらにPolyExtrudeノードを接続します。
作成したpolyextrude4ノードのパラメータを次のように設定します。
シーンビューは次のようになっています。
PolyBevelノードを追加して接続します。
作成したpolybevel2ノードのパラメータを次のようにします。
シーンビューは次のようになっています。
Transformノードを作成し接続します。
作成したtransform1ノードのパラメータを次のようにします。
Mergeノードを作成し接続します。
シーンビューは次のようになっています。
新たにCircleノードを作成します。
作成したcircle3ノードのパラメータを次のように設定します。
Groupノードを作成し接続します。
作成したgroup1ノードのパラメータを次のように設定します。
For-Loop with Feedbackを接続します。
repeat_begin1にPolyExtrudeノードを接続します。
polyextrude5のノードのパラメータを次のように設定します。
さらにPolyExtrudeノードを接続します。
接続したpolyextrude6ノードのパラメータを次のように設定します。
repeat_end1にPolyBevelノードを接続します。
接続したpolybevel3ノードのパラメータを次のように設定します。
シーンビューは次のようになっています。
polybevel3ノードにTransformノードを接続します。
transform2ノードのパラメータを次のように設定します。
transform2ノードからmerge1ノードへ接続します。
シーンビューは次のようになっています。
これでハイポリのボルトのモデルは完成です。
ネットワーク全体を選択してShift+Oでネットワークを囲っておきます。
続いてLOD0のモデルを作成していきます。
新しくCircleノードを作成します。
作成したcircle4ノードのパラメータを次のように設定します。
PolyExtrudeノードを作成し接続します。
作成したpolyextrude7のパラメータを次のように設定します。
新たにCircleノードを作成します。
作成したcircle5ノードのパラメータを次のように設定します。
PolyExtrudeノードを作成してつなげます。
作成したpolyextrude8ノードのパラメータを次のように設定します。
さらにPolyExtrudeノードを作成し接続します。
作成したpolyextrude5ノードのパラメータを次のように設定します。
さらにPolyExtrudeノードを作成し接続します。
作成したpolyextrude10ノードのパラメータを次のように設定します。
Transformノードを接続します。
作成したtransform3ノードのパラメータを次のように設定します。
Mergeノードを作成し接続します。
シーンビューは次のようになっています。
これでLODのモデルの完成です。
LOD0のモデルのノードを選択しShift+Oで囲います。
続いてLOD1のモデルを作成します。
新たにCircleノードを作成します。
作成したcircle6ノードのパラメータを次のように設定します。
PolyExtrudeノードを作成し接続します。
接続したpolyextrude11ノードのパラメータを次のようにします。
シーンビューは次のようになっています。
これでLOD1のモデルは完成です。
Shift+Oでネットワークを囲います。
Switchノードを作成しハイポリ、LOD0、LOD1の順に接続します。
Transformノードを作成し接続します。
作成したtransform4ノードのパラメータを次のように設定します。
パラメータをまとめるnullノードを新たに作成します。
Cキーを押してノードの色を黄緑にします。
Zキーを押してノードの形を丸にします。
null1をparametersと名前を変更します。
パラメータウィンドウのギアマークから「Edit Parameter Interface」を実行します。
新しいウィンドウが開きます。
すでにある2つを選択しInvisibleにチェックを入れます。
左側のリストからFloatをドラッグ&ドロップします。
Nameをuniformscaleに、LabelをUniform Scaleに変更します。
Channelsからデフォルト値を0.015に変更します。
左側からOrdered Menuをドラッグ&ドロップします。
Nameをlod、LabelをLODとします。
MenuからHi、LOD0、LOD1を追加します。
Acceptを押すとparametersノードにUniform ScaleとLODのインタフェースが追加されています。
Uniform Scaleの数値の上で右クリックしてCopy Parameterを実行します。
transform4ノードのUniform ScaleにPaste Relative Referencesします。
parametersのLODをCopy Parameterします。
switch1のSelect InputにPaste Relative Referencesします。
これでparametersのパラメータを変更するとswitch1ノードやtransform4ノードのペーストした値が変更されるようになりました。
これらすべてのノードをHDAにまとめます。
すべてのノードを選択します。
Shift+Cでサブネットに格納します。
subnet1の名前をbolt_000に変更します。 この名前をアセットの名前とします。
bolt_000のパラメータのギアマークからEdit Parameter Interfaceを実行します。
最初からある4つのインタフェースを全部選択します。
Invisibleにチェックを入れて隠します。
Edit Parameter Interfaceウィンドウを開いたままで、bolt_000のサブネットを選択し、iキーもしくはエンターキーで内部に入ります。
ネットワーク内部のparametersを選択し、Uniform ScaleとLODのパラメータをドラッグ&ドロップしてsubnetのパラメータとして追加します。
Acceptを押します。 uキーでひとつ上の階層に上がります。
bolt_000にパラメータが出ているのがわかります。
試しにLODをHiからLOD0に変更してみます。
ちゃんとパラメータが生きているのがわかります。
bolt_000の上で右クリックをして、Create Digital Asset...を実行します。
Operator NameやOperator Labelを適当に指定しAcceptを押します。
Edit Operator Type Propertiesというウィンドウが開きます。
InteractiveのShelf ToolsのContextのTab Submenu Pathで作成したDigital Assetのタブメニューでの位置を指定できます。
Acceptを押して確定します。
Tabメニューからbolt000と打ってエンターを押すと作成したDigitalAssetが作成できます。
これでボルトのノードは完成です。
Edge Damageノード
選択したエッジにダメージを与えるノードを作ります。 これは、ハイポリのモデルにディティールを加えるために使います。
まずはHoudiniを新規に開きます。
Geometryノードを作成し内部に入ります。
テスト用にboxを用意します。
Groupノードを作成し接続します。
作成したgroup1ノードのパラメータを次のように設定します。
これでエッジが1つグループに入れられた状態になります。 このグループをとりあえずの対象としてノードを組み立てていきます。
nullノードをつなげてinputと名付けます。
さらにnullノードをつなげてparametersと名付け、色と形を変更します。
Edit Parameter Interfaceを実行します。
不要なインタフェースをinvisibleにし、Stringを追加してNameをedgegroupとします。
Edge Groupsにgroup1と入力しておきます。
Float型のnoise1amplitudeとnoise2amplitudeを用意します。
それぞれ適当な数字を設定しておきます。
Toggleを追加しNameをaddcolorとし、Colorを追加しNameをcolorとします。 colorの初期値は(0,1,0)としました。
Normalノードを作成し接続します。
作成したnormal1ノードのパラメータを次のように設定します。
Addノードを作成し接続します。
作成したadd1ノードのパラメータを次のように設定します。 これで入力されたジオメトリの点だけを残します。
Point Wrangleを次のように接続します。
パラメータに次のように記述します。
int n[] = neighbours(1, @ptnum);
string groups[] = split(chs("../parameters/edgegroup"));
foreach (int pt; n)
{
if (pt > @ptnum)
{
foreach(string g; groups) {
if (inedgegroup(1, g, @ptnum, pt)) {
int pr = addprim(geoself(), 'polyline', @ptnum, pt);
break;
}
}
}
}
これで次のようにグループで指定したエッジとすべて点のジオメトリが作成されます。
VEXの中身について解説をします。
Point Wrangelなのですべての点に対して実行されます。
実行されている点の番号は@ptnum
で取得できます。
最初に2番目に接続されたジオメトリを利用して、現在の点と接続している点の番号をint n[]
で受け取っています。
次の行で、parametersのedgegroupのグループ名を配列として取得しています。
隣接する点についてforeachでループを回しています。
その点が現在のポイント番号より後ろだった場合で、かつグループに含まれている場合にpolylineなprimitiveを追加しています。 ptが@ptnumより後ろなのを確認しているのは線が重複をするのを避けるためです。
Addノードを作成し接続します。
作成したadd2ノードのパラメータを次のようにします。
これによって、使っていないポイントが削除されます。
Point Wrangleを作成し接続します。
作成したpointwrangle2ノードのVEXに次のように記述します。
横のボタンを押してパラメータのスライダを出します。
Distanceに適当に小さい値を入れます。
distance分だけ、あらかじめ計算したpoint normalの方に位置をずらしています。
PolyWireノードを作成し接続します。
次にVDB from Polygonsノードを作成し接続します。
次にConvert VDBノードを作成し接続します。
作成したconvertbdb1ノードのパラメータを次のように設定します。
Attribute Noiseノードを作成し接続します。
attribnoise1ノードのパラメータを次のように設定します。
Amplitudeの部分はch("../parameters/noise1amplitude")
です。
もう一つAttribute Noiseノードを作成し接続します。
作成したattribnoise2ノードのパラメータを次のように設定します。
Amplitudeの部分はch("../parameters/noise2amplitude")
です。
Colorノードを作成し接続します。
Colorをparametersからコピーしてreferenceをペーストします。
ClassをVertexにしておきます。
Switchノードを作成し、次のように接続します。
switch1のSelect Inputにch("../parameters/addcolor")
と入力します。
Booleanノードを作成し次のように接続します。
inputが左の入力に、switch1が右の入力に接続されています。
作成したboolean1ノードのOperationをSubtractにします。
これでシーンビューは次のようになっています。
Normalノードを作成し接続します。
作成したnormal2のパラメータを次のように設定します。
最後にLabs Delete Small Partsノードを挿しておきます。
LabsツールはSideFX Labsと呼ばれるものです。 このノードが存在しない場合はSideFX Labsツールをインストールしてください。
Houdiniの内部システムからの直接インストール | インディゾーンHoudini情報日本語ブログ
inputより下のノードを選択します。
Shift+Cでノードをサブネットに格納します。
サブネットの名前をedge_damageとします。
edge_damageのEdit Parameter Interfaceを実行します。
最初からあるインタフェースをInvisibleにします。
Edit Parameter Interfaceを開いたままでサブネットの内部に入ります。
parametersからパラメータをドラッグ&ドロップします。
pointwrangle1のDistanceをドラッグ&ドロップします。
vfbfrompolygons1のVoxel SIzeをドラッグ&ドロップします。
attributenoise1とattributenoise2のノイズのパラメータをドラッグ&ドロップします。
フォルダに入れ並べ替えて整理します。
colorのHidfe Whenに{ addcolor == 0 }
と入力します。
edgegroupのMenuタブからUse Menuとし、Toggleに変更します。
Menu Scriptに変更して次のスクリプトを記述します。
inputs = hou.pwd().inputs()
result = []
if len(inputs):
node = inputs[0]
groups = [ x.name() for x in node.geometry().edgeGroups()]
result = sum(zip(groups, groups),())
return result
Edge Groupに既存のグループを選択するドロップダウンリストができました。
Action Buttonのタブを開いて次のスクリプトを書きます。
import soputils
kwargs['geometrytype'] = (hou.geometryType.Edges,)
kwargs['inputindex'] = 0
soputils.selectGroupParm(kwargs)
Action Iconを「BUTTONS_reselect」とします。
これでその場でグループを選択から作る矢印が表示されました。
Voxelサイズを小さくし、ノイズのパラメータを変更するとよい感じのエッジダメージになります。
サブネットを右クリックしてCreate Digital Asset...をクリックします。
適当に名前を決めてAcceptを実行します。
Input/Putputのタブからわかりやすいラベルを与えます。
InteractiveのShelfToolsのContextのTab Submenu Pathからタブメニューでの場所を設定します。
作成したEdge Damageノードには弱点があります。 PointNormal方向にずらしたものをbooleanで抜いているので、 たとえばpigheadのように入り組んだ部分では 選択したエッジの反対側までくり抜けてしまいます。
堅牢にどんなときでもうまく動くようなノードを作るのは難しいので、入り組んだ部分には使わないということで妥協しています。
Rect Trim Off Edgeノード
長方形のエッジから切り落とすように分割するノードです。
入力の各長方形に対して働きます。
四角形以外の面が入力に含まれている場合にはエラーとなります。
エッジからの長さをdistanceパラメータで指定できます。
axisパラメータで好きな軸の方向から分割できます。
分割の際にグループ名をつけることができます。
次の画像はグループごとに色を割り振ったものです。
Houdiniの新規シーンを新しく開きます。
Geometryノードを作成し内部に入ります。
テスト用にGridを配置します。
nullノードをつなげてinputと名付けます。
さらにnullノードをつなげてparametersと名付け、色と形を変更します。
Edit Parameter Interfaceを実行します。
最初から含まれる不要なインタフェースをinvisibleにし、Stringを追加してNameをgroupとします。
Ordered Menuを追加してNameをaxisとします。
Menu欄から次のように指定します。
FloatとStringを2つ追加し、それぞれNameをdistance、edgegroupname、maingroupnameとします。
Acceptします。
Blastノードを作成質疑のように接続します。
作成したblast1ノードのパラメータのGroupを次のように指定します。
blast1ノードを選択しCtrl+CでコピーしてCtrl+Vで貼り付けます。
ペーストしたblast2ノードのDelete Non Selectedをチェックします。
Primitive Wrangelノードを作成質疑のように接続します。
Primitive WrangleノードのVEXに次のように記述します。
int vcount = primvertexcount(geoself(), @primnum);
int closed = primintrinsic(geoself(), "closed", @primnum);
string typename = primintrinsic(geoself(), "typename", @primnum);
if (vcount != 4 || !closed || typename != "Poly") {
setdetailattrib(geoself(), "errorflag", 1, "max");
return;
}
vector pos[];
for (int i = 0; i < 4; i++) {
pos[i] = point(geoself(), "P", primpoint(geoself(), @primnum, i));
}
vector v01 = pos[1] - pos[0];
vector v12 = pos[2] - pos[1];
vector v23 = pos[3] - pos[2];
vector v30 = pos[0] - pos[3];
// error flag
setdetailattrib(
geoself(), "errorflag",
!(
abs(dot(v01, v12)) < 1e-5 &&
abs(dot(v12, v23)) < 1e-5 &&
abs(dot(v23, v30)) < 1e-5 &&
abs(dot(v30, v01)) < 1e-5
),
"max"
);
VEXの解説をします。
このノードはRun Over Primitivesになっているので、各Primitiveについて実行されます。
最初に@primnum
で取得できる現在のPrimitiveが頂点数4で閉じていてタイプがポリゴンであるか確認しています。
いずれかの条件を満たしていない場合、detailにerrorflagというアトリビュートを追加して1をセットしています。
Run OverされるPrimitiveのいずれかでエラーが有った場合にエラーを検知したいので、"max"
で一つでも1になったら1とするようにしました。
次に頂点の座標を取得し、すべての角が90度であるかを調べています。 内積の絶対値がある程度小さければその角が90度であると判定しています。 いずれかの角が90度でない場合はdetailにerrorflagというアトリビュートを追加して1をセットしています。
次にErrorノードを作成し接続します。
作成したerror1ノードのパラメータを次のように設定します。
Report This Errorはdetail("../primitivewrangle1", "errorflag", 0)
です。
次にAttribute Deleteノードを作成し次のように接続します。
作成したattribdelete1ノードのパラメータを次のように設定します。
次にPrimitive Wrangleノードを作成し次のように接続します。
priimitivewrangle2ノードのVEXに次のように記述します。
int pt[];
vector pos[];
for (int i = 0; i < 4; i++) {
pt[i] = primpoint(geoself(), @primnum, i);
pos[i] = point(geoself(), "P", pt[i]);
}
// sort points
// make 0-1 edge start side
int axis = chi("../parameters/axis");
void shiftpoints(int pt[]; vector pos[]) {
int tmpPt;
vector tmpPos;
// shift
tmpPt = pt[0];
tmpPos = pos[0];
pt[0] = pt[1];
pos[0] = pos[1];
pt[1] = pt[2];
pos[1] = pos[2];
pt[2] = pt[3];
pos[2] = pos[3];
pt[3] = tmpPt;
pos[3] = tmpPos;
}
if (axis == 0) {
while (
!(
pos[0].x >= pos[3].x &&
pos[1].x >= pos[2].x &&
pos[0].x >= pos[2].x &&
pos[1].x >= pos[3].x
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 1) {
while (
!(
pos[0].y >= pos[3].y &&
pos[1].y >= pos[2].y &&
pos[0].y >= pos[2].y &&
pos[1].y >= pos[3].y
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 2) {
while (
!(
pos[0].z >= pos[3].z &&
pos[1].z >= pos[2].z &&
pos[0].z >= pos[2].z &&
pos[1].z >= pos[3].z
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 3) {
while (
!(
pos[0].x <= pos[3].x &&
pos[1].x <= pos[2].x &&
pos[0].x <= pos[2].x &&
pos[1].x <= pos[3].x
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 4) {
while (
!(
pos[0].y <= pos[3].y &&
pos[1].y <= pos[2].y &&
pos[0].y <= pos[2].y &&
pos[1].y <= pos[3].y
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 5) {
while (
!(
pos[0].z <= pos[3].z &&
pos[1].z <= pos[2].z &&
pos[0].z <= pos[2].z &&
pos[1].z <= pos[3].z
)
) {
shiftpoints(pt, pos);
}
}
// end sort points
float edgeDistance = ch("../parameters/distance");
string mainGroupName = chs("../parameters/maingroupname");
string edgeGroupName = chs("../parameters/edgegroupname");
vector dir = normalize(pos[3] - pos[0]);
int newpt0, newpt1;
vector newpos0, newpos1;
newpos0 = pos[0] + dir * edgeDistance;
newpt0 = addpoint(geoself(), newpos0);
newpos1 = pos[1] + dir * edgeDistance;
newpt1 = addpoint(geoself(), newpos1);
int prim;
prim = addprim(
geoself(), "poly",
pt[0], pt[1], newpt1, newpt0
);
setprimgroup(geoself(), edgeGroupName, prim, 1);
prim = addprim(
geoself(), "poly",
newpt0, newpt1, pt[2], pt[3]
);
setprimgroup(geoself(), mainGroupName, prim, 1);
removeprim(geoself(), @primnum, 1);
最初にpt[]
とpos[]
でポイント番号と位置を取得しています。
これより前で長方形以外のプリミティブはエラーになっているので、ここでは長方形のプリミティブが渡されたものとして進めています。
int pt[];
vector pos[];
for (int i = 0; i < 4; i++) {
pt[i] = primpoint(geoself(), @primnum, i);
pos[i] = point(geoself(), "P", pt[i]);
}
parametersで指定したaxisの値をもとに、長方形のポイントをシフトして並べ替えています。
axisで指定したエッジ側がpt[0]
、pt[1]
となるように並べ替えています。
// sort points
// make 0-1 edge start side
int axis = chi("../parameters/axis");
void shiftpoints(int pt[]; vector pos[]) {
int tmpPt;
vector tmpPos;
// shift
tmpPt = pt[0];
tmpPos = pos[0];
pt[0] = pt[1];
pos[0] = pos[1];
pt[1] = pt[2];
pos[1] = pos[2];
pt[2] = pt[3];
pos[2] = pos[3];
pt[3] = tmpPt;
pos[3] = tmpPos;
}
if (axis == 0) {
while (
!(
pos[0].x >= pos[3].x &&
pos[1].x >= pos[2].x &&
pos[0].x >= pos[2].x &&
pos[1].x >= pos[3].x
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 1) {
while (
!(
pos[0].y >= pos[3].y &&
pos[1].y >= pos[2].y &&
pos[0].y >= pos[2].y &&
pos[1].y >= pos[3].y
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 2) {
while (
!(
pos[0].z >= pos[3].z &&
pos[1].z >= pos[2].z &&
pos[0].z >= pos[2].z &&
pos[1].z >= pos[3].z
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 3) {
while (
!(
pos[0].x <= pos[3].x &&
pos[1].x <= pos[2].x &&
pos[0].x <= pos[2].x &&
pos[1].x <= pos[3].x
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 4) {
while (
!(
pos[0].y <= pos[3].y &&
pos[1].y <= pos[2].y &&
pos[0].y <= pos[2].y &&
pos[1].y <= pos[3].y
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 5) {
while (
!(
pos[0].z <= pos[3].z &&
pos[1].z <= pos[2].z &&
pos[0].z <= pos[2].z &&
pos[1].z <= pos[3].z
)
) {
shiftpoints(pt, pos);
}
}
// end sort points
並べ替え処理のためにshiftpointsという関数を用意しています。 VEXにおいてユーザ定義関数のパラメータは常に参照になるそうです。
その後、parametersノードからdistanceとmaingroupnameとedgegroupnameを取得しています。
float edgeDistance = ch("../parameters/distance");
string mainGroupName = chs("../parameters/maingroupname");
string edgeGroupName = chs("../parameters/edgegroupname");
長方形の分割に直行する方向をdir
として計算しています。
vector dir = normalize(pos[3] - pos[0]);
新しくポイントを追加します。
int newpt0, newpt1;
vector newpos0, newpos1;
newpos0 = pos[0] + dir * edgeDistance;
newpt0 = addpoint(geoself(), newpos0);
newpos1 = pos[1] + dir * edgeDistance;
newpt1 = addpoint(geoself(), newpos1);
作成したポイントと既存のポイントをもとに長方形のprimitiveを追加しています。 作成したPrimitiveには与えられたグループに属するようにしています。
int prim;
prim = addprim(
geoself(), "poly",
pt[0], pt[1], newpt1, newpt0
);
setprimgroup(geoself(), edgeGroupName, prim, 1);
prim = addprim(
geoself(), "poly",
newpt0, newpt1, pt[2], pt[3]
);
setprimgroup(geoself(), mainGroupName, prim, 1);
最後に、新しく面を作ったので最初からあるPrimitiveは必要なくなったので、それを削除して終了です。
removeprim(geoself(), @primnum, 1);
新しくMergeノードを作成し、次のように接続します。
最後にfuseノードを作成し、次のように接続します。 これでblastで分割した境界部分の頂点をマージします。
input以下のノードを選択します。
Shift+Cでサブネットに格納します。
subnet1の名前をrecttrimoff_edgeと名付けます。
recttrimoff_edgeのEdit Parameter Interfaceを実行します。
最初からあるラベルのインタフェースを非表示にします。
Edit Parameter Interfaceのウィンドウを開いたまま、recttrimoff_edgeノードの内部に入ります。
parametersノードにまとめてあるパラメータをドラッグ&ドロップで追加します。
groupのMenuからUse Menuにチェックを入れてToggleにします。 Menu Scriptで次のPythonスクリプトを追加します。
inputs = hou.pwd().inputs()
result = []
if len(inputs):
node = inputs[0]
groups = [ x.name() for x in node.geometry().primGroups()]
result = sum(zip(groups, groups),())
return result
このスクリプトで接続されたノードのprimitiveのグループの一覧をトグルするドロップダウンが追加されます。
groupのAction Buttonを追加します。 Action Buttonのタブに次のpythonコードを記述します。
import soputils
kwargs['geometrytype'] = (hou.geometryType.Primitives,)
kwargs['inputindex'] = 0
soputils.selectGroupParm(kwargs)
Action Iconの欄にBUTTONS_reselectとしてアイコンを設定します。
これで接続されたgeometryのプリミティブグループを選択することができるようになりました。
サブネットを右クリックしてCreate Digital Asset...をクリックします。
適当に名前を決めてAcceptを実行します。
Input/Putputのタブからわかりやすいラベルを与えます。
InteractiveのShelfToolsのContextのTab Submenu Pathからタブメニューでの場所を設定します。
これでRect Trim Off Edgeノードの完成です。
Rect Multi Splitノード
長方形を複数に分割するノードです。
mainとsubの長さを指定します。
メインとサブを交互に並べて詰め込む形で分割されます。
入力の長方形の大きさによって分割数が変わります。
メインとサブの指定した大きさでピッタリ割り切れない場合はメインの大きさが伸びます。
入力の各長方形に対して働きます。
axisパラメータで分割の軸の方向を決定できます。
入力に長方形以外の面が含まれる場合エラーとなります。
分割の際にmainとsubのグループ名を与えることができます。
各グループに色を付けると次のようになります。
元になる長方形のPrimitiveのサイズを変更するとそれに合わせて分割数が変わります。
この機能をつかって橋の裏の構造の繰り返しなどを作っていきます。
新しくHoudiniのシーンを立ち上げ、geoノードを作成し内部には入ります。 テスト用にgridノードを作成し、RowsとColumnsをそれぞれ2にします。
nullノードを新たに作成し名前をinputに変更して接続します。
さらにnullノードを作成し接続し、名前をparametersとします。 ノードの形状と色を変更します。
Edit Parameter Interfaceでparametersのインタフェースを変更します。
最初に2つの必要ないインタフェースをInvisibleにします。
Stringをドラッグ&ドロップし、Nameをgroupに変更します。
Ordered Menuをドラッグ&ドロップし、Nameをaxisに変更します。
次のようにMenuの内容を変更します。
Floatを2つドラッグ&ドロップし、Nameをそれぞれmainwidthとsubwidthとします。
2つStringをドラッグ&ドロップし、Nameをそれぞれmaingroupnameとsubgroupnameとします。
Acceptを押して確定します。 Main WidthとSub Widthには適当な値を入れておきます。
Rect Trim Off Edgeノードのときと同じようにして次のようにノードを接続します。
primitivewrangle2のVEXコードを次のように書き換えます。
int pt[];
vector pos[];
for (int i = 0; i < 4; i++) {
pt[i] = primpoint(geoself(), @primnum, i);
pos[i] = point(geoself(), "P", pt[i]);
}
// sort points
// make 0-1 edge start side
int axis = chi("../parameters/axis");
void shiftpoints(int pt[]; vector pos[]) {
int tmpPt;
vector tmpPos;
// shift
tmpPt = pt[0];
tmpPos = pos[0];
pt[0] = pt[1];
pos[0] = pos[1];
pt[1] = pt[2];
pos[1] = pos[2];
pt[2] = pt[3];
pos[2] = pos[3];
pt[3] = tmpPt;
pos[3] = tmpPos;
}
if (axis == 0) {
while (
!(
pos[0].x <= pos[3].x &&
pos[1].x <= pos[2].x &&
pos[0].x <= pos[2].x &&
pos[1].x <= pos[3].x
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 1) {
while (
!(
pos[0].y <= pos[3].y &&
pos[1].y <= pos[2].y &&
pos[0].y <= pos[2].y &&
pos[1].y <= pos[3].y
)
) {
shiftpoints(pt, pos);
}
} else if (axis == 2) {
while (
!(
pos[0].z <= pos[3].z &&
pos[1].z <= pos[2].z &&
pos[0].z <= pos[2].z &&
pos[1].z <= pos[3].z
)
) {
shiftpoints(pt, pos);
}
}
// end sort points
float totalLength = distance(pos[0], pos[3]);
float mainWidth = ch("../parameters/mainwidth");
float subWidth = ch("../parameters/subwidth");
int count;
if (totalLength < mainWidth) {
count = 1;
mainWidth = totalLength;
} else {
count = floor((totalLength + subWidth) / (mainWidth + subWidth));
mainWidth = (totalLength + subWidth) / count - subWidth;
}
string mainGroupName = chs("../parameters/maingroupname");
string subGroupName = chs("../parameters/subgroupname");
int pt0 = pt[0], pt1 = pt[1];
vector pos0 = pos[0], pos1 = pos[1];
vector dir = normalize(pos[3] - pos[0]);
int newpt0, newpt1;
vector newpos0, newpos1;
int prim;
for (int i = 0; i < count - 1; i++) {
newpos0 = pos0 + dir * mainWidth;
newpt0 = addpoint(geoself(), newpos0);
newpos1 = pos1 + dir * mainWidth;
newpt1 = addpoint(geoself(), newpos1);
prim = addprim(
geoself(), "poly",
pt0, pt1, newpt1, newpt0
);
setprimgroup(geoself(), mainGroupName, prim, 1);
pt0 = newpt0;
pt1 = newpt1;
pos0 = newpos0;
pos1 = newpos1;
newpos0 = pos0 + dir * subWidth;
newpt0 = addpoint(geoself(), newpos0);
newpos1 = pos1 + dir * subWidth;
newpt1 = addpoint(geoself(), newpos1);
prim = addprim(
geoself(), "poly",
pt0, pt1, newpt1, newpt0
);
setprimgroup(geoself(), subGroupName, prim, 1);
pt0 = newpt0;
pt1 = newpt1;
pos0 = newpos0;
pos1 = newpos1;
}
prim = addprim(
geoself(), "poly",
pt0, pt1, pt[2], pt[3]
);
setprimgroup(geoself(), mainGroupName, prim, 1);
removeprim(geoself(), @primnum, 1);
最初にポイントとその位置を取得しソートしている部分はRect Trim Off Edgeのときと同じです。 Axisが+-を気にしないためその部分だけ違います。
その後、分割方向の長さと指定したmainとsubの長さから分割数を決定しています。
mainWidthよりもtotalLengthが短い場合は分割数を1としています。 それ以外の場合は、mainを分割数だけ、subをmainの間なので分割数-1だけ、詰め込めるだけ詰め込んで分割数を計算しています。 もとの分割方向の長さに足りない分はmainを引き伸ばすことで対応しています。
float totalLength = distance(pos[0], pos[3]);
float mainWidth = ch("../parameters/mainwidth");
float subWidth = ch("../parameters/subwidth");
int count;
if (totalLength < mainWidth) {
count = 1;
mainWidth = totalLength;
} else {
count = floor((totalLength + subWidth) / (mainWidth + subWidth));
mainWidth = (totalLength + subWidth) / count - subWidth;
}
その後for文でcount-1回だけmainとsubの面を貼っています。
int newpt0, newpt1;
vector newpos0, newpos1;
int prim;
for (int i = 0; i < count - 1; i++) {
newpos0 = pos0 + dir * mainWidth;
newpt0 = addpoint(geoself(), newpos0);
newpos1 = pos1 + dir * mainWidth;
newpt1 = addpoint(geoself(), newpos1);
prim = addprim(
geoself(), "poly",
pt0, pt1, newpt1, newpt0
);
setprimgroup(geoself(), mainGroupName, prim, 1);
pt0 = newpt0;
pt1 = newpt1;
pos0 = newpos0;
pos1 = newpos1;
newpos0 = pos0 + dir * subWidth;
newpt0 = addpoint(geoself(), newpos0);
newpos1 = pos1 + dir * subWidth;
newpt1 = addpoint(geoself(), newpos1);
prim = addprim(
geoself(), "poly",
pt0, pt1, newpt1, newpt0
);
setprimgroup(geoself(), subGroupName, prim, 1);
pt0 = newpt0;
pt1 = newpt1;
pos0 = newpos0;
pos1 = newpos1;
}
その後、mainをもう一個だけ貼っています。
prim = addprim(
geoself(), "poly",
pt0, pt1, pt[2], pt[3]
);
setprimgroup(geoself(), mainGroupName, prim, 1);
最後にもとからあった面を削除しています。
removeprim(geoself(), @primnum, 1);
input以下のノードをすべて選択し、Shift+Cでサブネットに格納します。
subnet1の名前をrectmultisplitとします。
Edit Parameter Interfaceを開きます。
はじめからあるラベルはInvisibleにしてしまいます。
Edit Parameter Interfaceウィンドウを開いたままでrectmultisplitノードの内部に入ります。 parametersのパラメータをドラッグ&ドロップします。
groupのMenuからUse Menuにチェックを入れてToggleにします。 Menu Scriptで次のPythonスクリプトを追加します。
inputs = hou.pwd().inputs()
result = []
if len(inputs):
node = inputs[0]
groups = [ x.name() for x in node.geometry().primGroups()]
result = sum(zip(groups, groups),())
return result
このスクリプトで接続されたノードのprimitiveのグループの一覧をトグルするドロップダウンが追加されます。
groupのAction Buttonを追加します。 Action Buttonのタブに次のpythonコードを記述します。
import soputils
kwargs['geometrytype'] = (hou.geometryType.Primitives,)
kwargs['inputindex'] = 0
soputils.selectGroupParm(kwargs)
Action Iconの欄にBUTTONS_reselectとしてアイコンを設定します。
Acceptして確定します。 rectmultisplitノードのインタフェースで操作できるようになりました。
rectmultisplitノードで右クリックをしてCreate Digital Asset...を実行します。
適当な名前をつけてAcceptします。
Inputのラベルの変更やTabメニューの階層の変更などを行います。
AcceptしてRect Multi Splitノードは完成です。
これでひとまず橋の作成に使うノードは完成です。