Houdiniで橋のモデリングを行いUnityのHDRPでセットアップして表示してみる(Part 1)

はじめに

この記事はシリーズのPart 1です。 目次はこちらです。

.hiplcファイル

今回作成する橋の.hiplcファイルはこちらです。

橋の作成にあたって自作したhdaの.hiplcファイルは次のとおりです。

橋をシーンに配置したシーンの.hiplcファイルはこちらです。

作成する橋の仕様の決定

まずは、作成するモデルのどの部分をパラメータとして操作可能にするかを決定します。

今回は次のようなパラメータを用意することにしました。

2019 12 21 11 52 51

バウンディングボックスのサイズとしてsizex、sizey、sizezを用意します。

2019 12 21 11 54 10

橋の両脇の欄干部分の高さとしてbalustradeheightを用意します。

2019 12 21 11 54 48

橋の開始と終わりの部分の長さについて、startlength、endlengthを用意します。

橋の開始と終りの部分の支柱について、オン・オフできるようにします。

2019 12 21 12 26 44

2019 12 21 12 26 54

2019 12 21 12 27 32

橋の裏側のグリッドのサイズについて、gridsizex、gridsizeyを用意します。

2019 12 22 20 42 02

これらの用意したパラメータで、橋のモデルのサイズなどをあとから自由自在に変更できるようモデルを構築していきます。

橋の長さを変更した際に、橋の裏側のパターンなどが大きさに合わせて適切に繰り返し処理を行うようにします。

2019 12 21 11 57 24


Houdiniでモデリングをする際は、どの程度パラメータ化しモデルを弄られるようにするかを予め決定しておくのがよいです。 何でもかんでもパラメータとして外部に公開すると使うのも大変ですし、作るのも大変になります。 一方で、パラメータが少ないと作れるバリエーションが少なくなります。 ここはトレードオフになっているので、よくよく考えて自分の用途に必要なパラメータというものを見極める必要があります。 今回は大きさを自由に変更できるようにして、その他のディティールなどは大きさに合わせて自動で繰り返してくれるようにします。

HDAの作成

今回、モデリングの作業にはいくつかの自作ノードを利用します。 まずは、その自作ノードの作成から解説をします。

Boltノード

ボルトの頭の部分のノードを作成します。 こういったパーツをHDAにしておくと使い回しがきいて便利です。 ベイク用のハイポリモデルの他、LOD対応のために荒いモデルにも切り替えられるようにします。

2019 12 21 15 12 06

2019 12 21 15 10 32


Houdiniを新しく開きます。

2019 12 21 12 02 27

ネットワークビュー上でTabキーを押して「geo」と入力し、Geometryでエンターを2回押してGeometryノードを作成します。

2019 12 21 12 05 34

2019 12 21 12 05 51

2019 12 21 12 06 12

geo1ノードを選択した状態でEnterキーかiキーを押してgeo1ノードの中に入ります。

2019 12 21 12 07 19

2019 12 21 12 07 26

Tabキーからcircleと入力してcircle1ノードを作成します。

2019 12 21 12 44 16

2019 12 21 12 44 39

Primitive TypeをPolygonに変更し、Divisionsを24とします。

2019 12 21 12 45 45

2019 12 21 12 47 07

PolyExtrudeノードを作成し接続します。

2019 12 21 12 46 19

PolyExtrudeノードのディスプレイフラグを有効にします。

2019 12 21 12 46 50

polyextrude1のDistanceを0.2にします。

2019 12 21 12 48 14

polyextrude1のFront Groupを有効にします。

2019 12 21 12 48 54

2019 12 21 12 49 03

PolyBevelノードを接続します。

2019 12 21 13 34 07

polybevel1ノードのパラメータを次のように設定します。

2019 12 21 13 35 30

シーンビューは次のようになります。

2019 12 21 13 35 49

新たにCircleノードを作成します。

2019 12 21 13 36 29

circle2ノードのパラメータを次のように設定します。

2019 12 21 13 38 12

PolyExtrudeノードを新たに作成しcircle2ノードに接続します。

2019 12 21 13 40 32

polyextrude2ノードのパラメータを次のように設定します。

2019 12 21 13 41 28

シーンビューは次のようになっています。

2019 12 21 13 42 02

さらにPolyExtrudeノードを接続します。

2019 12 21 13 42 57

polyextrude3ノードのパラメータを次のように設定します。

2019 12 21 13 43 20

さらにPolyExtrudeノードを接続します。

2019 12 21 13 44 51

作成したpolyextrude4ノードのパラメータを次のように設定します。

2019 12 21 13 47 11

シーンビューは次のようになっています。

2019 12 21 13 45 51

PolyBevelノードを追加して接続します。

2019 12 21 13 47 27

作成したpolybevel2ノードのパラメータを次のようにします。

2019 12 21 15 17 29

シーンビューは次のようになっています。

2019 12 21 13 49 40

Transformノードを作成し接続します。

2019 12 21 13 50 25

作成したtransform1ノードのパラメータを次のようにします。

2019 12 21 13 51 07

Mergeノードを作成し接続します。

2019 12 21 13 51 55

シーンビューは次のようになっています。

2019 12 21 13 52 05

新たにCircleノードを作成します。

2019 12 21 13 53 57

作成したcircle3ノードのパラメータを次のように設定します。

2019 12 21 13 54 31

Groupノードを作成し接続します。

2019 12 21 13 55 35

作成したgroup1ノードのパラメータを次のように設定します。

2019 12 21 13 56 05

For-Loop with Feedbackを接続します。

2019 12 21 13 57 02

2019 12 21 13 57 34

repeat_begin1にPolyExtrudeノードを接続します。

2019 12 21 13 58 14

polyextrude5のノードのパラメータを次のように設定します。

2019 12 21 14 01 40

さらにPolyExtrudeノードを接続します。

2019 12 21 14 02 03

接続したpolyextrude6ノードのパラメータを次のように設定します。

2019 12 21 14 03 06

repeat_end1にPolyBevelノードを接続します。

2019 12 21 14 03 37

接続したpolybevel3ノードのパラメータを次のように設定します。

2019 12 21 15 18 13

シーンビューは次のようになっています。

2019 12 21 14 05 03

polybevel3ノードにTransformノードを接続します。

2019 12 21 14 05 19

transform2ノードのパラメータを次のように設定します。

2019 12 21 14 06 42

transform2ノードからmerge1ノードへ接続します。

2019 12 21 14 07 09

シーンビューは次のようになっています。

2019 12 21 14 07 21

これでハイポリのボルトのモデルは完成です。

ネットワーク全体を選択してShift+Oでネットワークを囲っておきます。

2019 12 21 14 08 11

2019 12 21 14 08 57

続いてLOD0のモデルを作成していきます。

新しくCircleノードを作成します。

2019 12 21 14 10 21

作成したcircle4ノードのパラメータを次のように設定します。

2019 12 21 14 11 07

PolyExtrudeノードを作成し接続します。

2019 12 21 14 11 44

作成したpolyextrude7のパラメータを次のように設定します。

2019 12 21 14 12 14

新たにCircleノードを作成します。

2019 12 21 14 15 11

作成したcircle5ノードのパラメータを次のように設定します。

2019 12 21 14 16 01

PolyExtrudeノードを作成してつなげます。

2019 12 21 14 16 50

作成したpolyextrude8ノードのパラメータを次のように設定します。

2019 12 21 14 17 57

さらにPolyExtrudeノードを作成し接続します。

2019 12 21 14 18 24

作成したpolyextrude5ノードのパラメータを次のように設定します。

2019 12 21 14 19 40

さらにPolyExtrudeノードを作成し接続します。

2019 12 21 14 21 09

作成したpolyextrude10ノードのパラメータを次のように設定します。

2019 12 21 14 25 02

Transformノードを接続します。

2019 12 21 14 23 20

作成したtransform3ノードのパラメータを次のように設定します。

2019 12 21 14 23 34

Mergeノードを作成し接続します。

2019 12 21 14 23 53

シーンビューは次のようになっています。

2019 12 21 14 24 04

これでLODのモデルの完成です。

LOD0のモデルのノードを選択しShift+Oで囲います。

2019 12 21 14 25 29

2019 12 21 14 25 43

続いてLOD1のモデルを作成します。

新たにCircleノードを作成します。

2019 12 21 14 27 14

作成したcircle6ノードのパラメータを次のように設定します。

2019 12 21 14 27 54

PolyExtrudeノードを作成し接続します。

2019 12 21 14 29 35

接続したpolyextrude11ノードのパラメータを次のようにします。

2019 12 21 14 29 49

シーンビューは次のようになっています。

2019 12 21 14 29 54

これでLOD1のモデルは完成です。

Shift+Oでネットワークを囲います。

2019 12 21 14 30 21

Switchノードを作成しハイポリ、LOD0、LOD1の順に接続します。

2019 12 21 14 30 49

Transformノードを作成し接続します。

2019 12 21 14 31 53

作成したtransform4ノードのパラメータを次のように設定します。

2019 12 21 14 32 27

パラメータをまとめるnullノードを新たに作成します。

2019 12 21 14 34 00

Cキーを押してノードの色を黄緑にします。

2019 12 21 14 34 42

Zキーを押してノードの形を丸にします。

2019 12 21 14 34 55

null1をparametersと名前を変更します。

2019 12 21 14 35 36

2019 12 21 14 35 42

パラメータウィンドウのギアマークから「Edit Parameter Interface」を実行します。

2019 12 21 14 36 01

新しいウィンドウが開きます。

2019 12 21 14 36 29

すでにある2つを選択しInvisibleにチェックを入れます。

2019 12 21 14 37 03

2019 12 21 14 37 43

左側のリストからFloatをドラッグ&ドロップします。

2019 12 21 14 38 50

2019 12 21 14 39 03

Nameをuniformscaleに、LabelをUniform Scaleに変更します。

2019 12 21 14 39 58

Channelsからデフォルト値を0.015に変更します。

2019 12 21 14 41 02

左側からOrdered Menuをドラッグ&ドロップします。

2019 12 21 14 42 03

2019 12 21 14 42 12

Nameをlod、LabelをLODとします。

2019 12 21 14 42 33

MenuからHi、LOD0、LOD1を追加します。

2019 12 28 00 08 20

Acceptを押すとparametersノードにUniform ScaleとLODのインタフェースが追加されています。

2019 12 21 14 44 01

2019 12 21 14 45 05

Uniform Scaleの数値の上で右クリックしてCopy Parameterを実行します。

2019 12 21 14 45 28

transform4ノードのUniform ScaleにPaste Relative Referencesします。

2019 12 21 14 46 22

parametersのLODをCopy Parameterします。

2019 12 21 14 47 10

switch1のSelect InputにPaste Relative Referencesします。

2019 12 21 14 47 53

これでparametersのパラメータを変更するとswitch1ノードやtransform4ノードのペーストした値が変更されるようになりました。

これらすべてのノードをHDAにまとめます。

すべてのノードを選択します。

2019 12 21 14 52 01

Shift+Cでサブネットに格納します。

2019 12 21 14 52 26

subnet1の名前をbolt_000に変更します。 この名前をアセットの名前とします。

2019 12 21 14 53 34

bolt_000のパラメータのギアマークからEdit Parameter Interfaceを実行します。

2019 12 21 14 54 27

2019 12 21 14 55 22

最初からある4つのインタフェースを全部選択します。

2019 12 21 14 55 53

Invisibleにチェックを入れて隠します。

2019 12 21 14 56 18

Edit Parameter Interfaceウィンドウを開いたままで、bolt_000のサブネットを選択し、iキーもしくはエンターキーで内部に入ります。

2019 12 21 14 57 17

ネットワーク内部のparametersを選択し、Uniform ScaleとLODのパラメータをドラッグ&ドロップしてsubnetのパラメータとして追加します。

2019 12 21 14 58 49

2019 12 21 14 59 01

Acceptを押します。 uキーでひとつ上の階層に上がります。

bolt_000にパラメータが出ているのがわかります。

2019 12 21 14 59 34

2019 12 21 15 00 16

試しにLODをHiからLOD0に変更してみます。

2019 12 21 15 00 35

2019 12 21 15 00 41

ちゃんとパラメータが生きているのがわかります。

bolt_000の上で右クリックをして、Create Digital Asset...を実行します。

2019 12 21 15 01 20

Operator NameやOperator Labelを適当に指定しAcceptを押します。

2019 12 21 15 01 54

Edit Operator Type Propertiesというウィンドウが開きます。

2019 12 21 15 04 12

InteractiveのShelf ToolsのContextのTab Submenu Pathで作成したDigital Assetのタブメニューでの位置を指定できます。

2019 12 21 15 05 07

Acceptを押して確定します。

Tabメニューからbolt000と打ってエンターを押すと作成したDigitalAssetが作成できます。

2019 12 21 15 06 34

2019 12 21 15 07 25

2019 12 21 15 09 13

これでボルトのノードは完成です。

bolt_000.hiplc

Edge Damageノード

選択したエッジにダメージを与えるノードを作ります。 これは、ハイポリのモデルにディティールを加えるために使います。

2019 12 21 15 21 23

2019 12 21 15 21 44


まずはHoudiniを新規に開きます。

Geometryノードを作成し内部に入ります。

2019 12 21 17 12 36

テスト用にboxを用意します。

2019 12 21 17 13 33

Groupノードを作成し接続します。

2019 12 21 17 14 56

作成したgroup1ノードのパラメータを次のように設定します。

2019 12 21 17 16 37

これでエッジが1つグループに入れられた状態になります。 このグループをとりあえずの対象としてノードを組み立てていきます。

2019 12 21 17 16 48

nullノードをつなげてinputと名付けます。

2019 12 21 17 17 27

さらにnullノードをつなげてparametersと名付け、色と形を変更します。

2019 12 21 17 18 14

Edit Parameter Interfaceを実行します。

2019 12 21 17 21 48

不要なインタフェースをinvisibleにし、Stringを追加してNameをedgegroupとします。

2019 12 21 17 23 51

Edge Groupsにgroup1と入力しておきます。

2019 12 21 17 30 50

Float型のnoise1amplitudeとnoise2amplitudeを用意します。

2019 12 21 17 52 46

それぞれ適当な数字を設定しておきます。

2019 12 21 17 53 32

Toggleを追加しNameをaddcolorとし、Colorを追加しNameをcolorとします。 colorの初期値は(0,1,0)としました。

2019 12 21 18 03 51

Normalノードを作成し接続します。

2019 12 21 17 24 36

作成したnormal1ノードのパラメータを次のように設定します。

2019 12 21 17 25 41

Addノードを作成し接続します。

2019 12 21 17 26 10

作成したadd1ノードのパラメータを次のように設定します。 これで入力されたジオメトリの点だけを残します。

2019 12 21 17 26 50

Point Wrangleを次のように接続します。

2019 12 21 17 27 40

パラメータに次のように記述します。

2019 12 21 17 28 34

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;
            }
        }
    }
}

これで次のようにグループで指定したエッジとすべて点のジオメトリが作成されます。

2019 12 21 17 36 48

VEXの中身について解説をします。

Point Wrangelなのですべての点に対して実行されます。 実行されている点の番号は@ptnumで取得できます。

最初に2番目に接続されたジオメトリを利用して、現在の点と接続している点の番号をint n[]で受け取っています。

次の行で、parametersのedgegroupのグループ名を配列として取得しています。

隣接する点についてforeachでループを回しています。

その点が現在のポイント番号より後ろだった場合で、かつグループに含まれている場合にpolylineなprimitiveを追加しています。 ptが@ptnumより後ろなのを確認しているのは線が重複をするのを避けるためです。

Addノードを作成し接続します。

2019 12 21 17 41 10

作成したadd2ノードのパラメータを次のようにします。

2019 12 21 17 42 53

これによって、使っていないポイントが削除されます。

2019 12 21 17 43 06

Point Wrangleを作成し接続します。

2019 12 21 17 43 42

作成したpointwrangle2ノードのVEXに次のように記述します。

2019 12 21 17 44 47

横のボタンを押してパラメータのスライダを出します。

2019 12 21 17 45 05

Distanceに適当に小さい値を入れます。

2019 12 21 17 45 46

distance分だけ、あらかじめ計算したpoint normalの方に位置をずらしています。

PolyWireノードを作成し接続します。

2019 12 21 17 47 43

次にVDB from Polygonsノードを作成し接続します。

2019 12 21 17 48 44

次にConvert VDBノードを作成し接続します。

2019 12 21 17 49 32

作成したconvertbdb1ノードのパラメータを次のように設定します。

2019 12 21 17 50 11

Attribute Noiseノードを作成し接続します。

2019 12 21 17 54 00

attribnoise1ノードのパラメータを次のように設定します。

2019 12 21 18 10 16

Amplitudeの部分はch("../parameters/noise1amplitude")です。

もう一つAttribute Noiseノードを作成し接続します。

2019 12 21 17 59 39

作成したattribnoise2ノードのパラメータを次のように設定します。

2019 12 21 18 13 44

Amplitudeの部分はch("../parameters/noise2amplitude")です。

Colorノードを作成し接続します。

2019 12 21 18 04 30

Colorをparametersからコピーしてreferenceをペーストします。

2019 12 21 18 05 13

2019 12 21 18 05 29

ClassをVertexにしておきます。

2019 12 21 18 33 47

Switchノードを作成し、次のように接続します。

2019 12 21 18 06 04

switch1のSelect Inputにch("../parameters/addcolor")と入力します。

2019 12 21 18 06 40

Booleanノードを作成し次のように接続します。

2019 12 21 18 07 32

inputが左の入力に、switch1が右の入力に接続されています。

作成したboolean1ノードのOperationをSubtractにします。

2019 12 21 18 14 02

これでシーンビューは次のようになっています。

2019 12 21 18 15 05

Normalノードを作成し接続します。

2019 12 21 18 15 33

作成したnormal2のパラメータを次のように設定します。

2019 12 21 18 16 25

最後にLabs Delete Small Partsノードを挿しておきます。

2019 12 21 18 19 25

LabsツールはSideFX Labsと呼ばれるものです。 このノードが存在しない場合はSideFX Labsツールをインストールしてください。

Houdiniの内部システムからの直接インストール | インディゾーンHoudini情報日本語ブログ

inputより下のノードを選択します。

2019 12 21 18 19 36

Shift+Cでノードをサブネットに格納します。

2019 12 21 18 20 28

サブネットの名前をedge_damageとします。

2019 12 21 18 21 29

edge_damageのEdit Parameter Interfaceを実行します。

2019 12 21 18 21 50

最初からあるインタフェースをInvisibleにします。

2019 12 21 18 23 08

2019 12 21 18 23 18

Edit Parameter Interfaceを開いたままでサブネットの内部に入ります。

parametersからパラメータをドラッグ&ドロップします。

2019 12 21 18 24 45

pointwrangle1のDistanceをドラッグ&ドロップします。

2019 12 21 18 26 00

vfbfrompolygons1のVoxel SIzeをドラッグ&ドロップします。

2019 12 21 18 26 57

attributenoise1とattributenoise2のノイズのパラメータをドラッグ&ドロップします。

2019 12 21 18 28 24

フォルダに入れ並べ替えて整理します。

2019 12 21 18 30 03

colorのHidfe Whenに{ addcolor == 0 }と入力します。

2019 12 21 18 32 31

edgegroupのMenuタブからUse Menuとし、Toggleに変更します。

2019 12 21 18 35 57

Menu Scriptに変更して次のスクリプトを記述します。

2019 12 21 18 37 55

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に既存のグループを選択するドロップダウンリストができました。

2019 12 21 18 38 20

Action Buttonのタブを開いて次のスクリプトを書きます。

2019 12 21 18 40 04

import soputils
kwargs['geometrytype'] = (hou.geometryType.Edges,)
kwargs['inputindex'] = 0
soputils.selectGroupParm(kwargs)

Action Iconを「BUTTONS_reselect」とします。

これでその場でグループを選択から作る矢印が表示されました。

2019 12 21 18 40 56

2019 12 21 18 41 29

2019 12 21 18 41 42

Voxelサイズを小さくし、ノイズのパラメータを変更するとよい感じのエッジダメージになります。

2019 12 21 18 44 52

2019 12 21 18 45 33

サブネットを右クリックしてCreate Digital Asset...をクリックします。

2019 12 21 18 45 47

適当に名前を決めてAcceptを実行します。

2019 12 21 18 47 41

Input/Putputのタブからわかりやすいラベルを与えます。

2019 12 21 18 48 46

2019 12 21 18 48 59

InteractiveのShelfToolsのContextのTab Submenu Pathからタブメニューでの場所を設定します。

2019 12 21 18 52 01

作成したEdge Damageノードには弱点があります。 PointNormal方向にずらしたものをbooleanで抜いているので、 たとえばpigheadのように入り組んだ部分では 選択したエッジの反対側までくり抜けてしまいます。

2019 12 21 18 53 12

2019 12 21 18 57 53

堅牢にどんなときでもうまく動くようなノードを作るのは難しいので、入り組んだ部分には使わないということで妥協しています。

edge_damage.hiplc

Rect Trim Off Edgeノード

長方形のエッジから切り落とすように分割するノードです。

2019 12 22 15 36 56

入力の各長方形に対して働きます。

2019 12 22 15 37 33

四角形以外の面が入力に含まれている場合にはエラーとなります。

2019 12 22 15 38 23

2019 12 22 15 39 07

エッジからの長さをdistanceパラメータで指定できます。

2019 12 22 15 39 58

2019 12 22 15 40 13

axisパラメータで好きな軸の方向から分割できます。

2019 12 22 15 40 37

2019 12 22 15 40 52

分割の際にグループ名をつけることができます。

2019 12 22 15 42 10

次の画像はグループごとに色を割り振ったものです。

2019 12 22 15 41 42


Houdiniの新規シーンを新しく開きます。

2019 12 22 14 30 16

Geometryノードを作成し内部に入ります。

2019 12 22 14 31 20

テスト用にGridを配置します。

2019 12 22 14 31 50

2019 12 22 14 31 58

nullノードをつなげてinputと名付けます。

2019 12 22 14 32 58

さらにnullノードをつなげてparametersと名付け、色と形を変更します。

2019 12 22 14 33 29

Edit Parameter Interfaceを実行します。

2019 12 22 14 34 50

最初から含まれる不要なインタフェースをinvisibleにし、Stringを追加してNameをgroupとします。

2019 12 22 14 35 55

Ordered Menuを追加してNameをaxisとします。

2019 12 22 14 36 59

Menu欄から次のように指定します。

2019 12 22 14 38 09

FloatとStringを2つ追加し、それぞれNameをdistance、edgegroupname、maingroupnameとします。

2019 12 22 14 40 15

Acceptします。

2019 12 22 14 40 35

Blastノードを作成質疑のように接続します。

2019 12 22 14 41 20

作成したblast1ノードのパラメータのGroupを次のように指定します。

2019 12 22 14 42 38

blast1ノードを選択しCtrl+CでコピーしてCtrl+Vで貼り付けます。

2019 12 22 14 44 16

ペーストしたblast2ノードのDelete Non Selectedをチェックします。

2019 12 22 14 45 18

Primitive Wrangelノードを作成質疑のように接続します。

2019 12 22 14 46 42

Primitive WrangleノードのVEXに次のように記述します。

2019 12 22 14 54 59

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ノードを作成し接続します。

2019 12 22 14 59 54

作成したerror1ノードのパラメータを次のように設定します。

2019 12 22 15 00 51

Report This Errorはdetail("../primitivewrangle1", "errorflag", 0)です。

次にAttribute Deleteノードを作成し次のように接続します。

2019 12 22 15 01 28

作成したattribdelete1ノードのパラメータを次のように設定します。

2019 12 22 15 02 29

次にPrimitive Wrangleノードを作成し次のように接続します。

2019 12 22 15 04 17

priimitivewrangle2ノードのVEXに次のように記述します。

2019 12 22 15 06 16

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においてユーザ定義関数のパラメータは常に参照になるそうです。

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ノードを作成し、次のように接続します。

2019 12 22 15 17 41

最後にfuseノードを作成し、次のように接続します。 これでblastで分割した境界部分の頂点をマージします。

2019 12 22 15 18 21

input以下のノードを選択します。

2019 12 22 15 19 09

Shift+Cでサブネットに格納します。

2019 12 22 15 20 56

subnet1の名前をrecttrimoff_edgeと名付けます。

2019 12 22 15 21 14

recttrimoff_edgeのEdit Parameter Interfaceを実行します。

2019 12 22 15 21 24

最初からあるラベルのインタフェースを非表示にします。

2019 12 22 15 22 21

2019 12 22 15 22 34

Edit Parameter Interfaceのウィンドウを開いたまま、recttrimoff_edgeノードの内部に入ります。

parametersノードにまとめてあるパラメータをドラッグ&ドロップで追加します。

2019 12 22 15 23 36

2019 12 22 15 24 14

groupのMenuからUse Menuにチェックを入れてToggleにします。 Menu Scriptで次のPythonスクリプトを追加します。

2019 12 22 15 26 11

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のグループの一覧をトグルするドロップダウンが追加されます。

2019 12 22 15 27 31

groupのAction Buttonを追加します。 Action Buttonのタブに次のpythonコードを記述します。

2019 12 22 15 28 39

import soputils
kwargs['geometrytype'] = (hou.geometryType.Primitives,)
kwargs['inputindex'] = 0
soputils.selectGroupParm(kwargs)

Action Iconの欄にBUTTONS_reselectとしてアイコンを設定します。

2019 12 22 15 29 00

これで接続されたgeometryのプリミティブグループを選択することができるようになりました。

2019 12 22 15 30 04

2019 12 22 15 30 19

2019 12 22 15 30 30

サブネットを右クリックしてCreate Digital Asset...をクリックします。

2019 12 22 15 32 10

適当に名前を決めてAcceptを実行します。

2019 12 22 15 32 54

Input/Putputのタブからわかりやすいラベルを与えます。

2019 12 22 15 33 18

InteractiveのShelfToolsのContextのTab Submenu Pathからタブメニューでの場所を設定します。

2019 12 22 15 34 00

これでRect Trim Off Edgeノードの完成です。

2019 12 22 15 34 31

recttrimoff_edge.hiplc

Rect Multi Splitノード

長方形を複数に分割するノードです。

2019 12 22 20 30 25

2019 12 22 20 30 33

mainとsubの長さを指定します。

2019 12 22 20 30 56

メインとサブを交互に並べて詰め込む形で分割されます。

入力の長方形の大きさによって分割数が変わります。

2019 12 22 20 31 18

2019 12 22 20 31 30

2019 12 22 20 31 40

メインとサブの指定した大きさでピッタリ割り切れない場合はメインの大きさが伸びます。

入力の各長方形に対して働きます。

2019 12 22 20 32 29

2019 12 22 20 32 39

axisパラメータで分割の軸の方向を決定できます。

2019 12 22 20 33 11

2019 12 22 20 33 28

入力に長方形以外の面が含まれる場合エラーとなります。

2019 12 22 20 33 50

2019 12 22 20 34 07

分割の際にmainとsubのグループ名を与えることができます。

2019 12 22 20 34 43

各グループに色を付けると次のようになります。

2019 12 22 20 34 55

元になる長方形のPrimitiveのサイズを変更するとそれに合わせて分割数が変わります。

この機能をつかって橋の裏の構造の繰り返しなどを作っていきます。


新しくHoudiniのシーンを立ち上げ、geoノードを作成し内部には入ります。 テスト用にgridノードを作成し、RowsとColumnsをそれぞれ2にします。

2019 12 22 19 50 31

2019 12 22 19 50 48

2019 12 22 19 50 57

nullノードを新たに作成し名前をinputに変更して接続します。

2019 12 22 19 51 45

さらにnullノードを作成し接続し、名前をparametersとします。 ノードの形状と色を変更します。

2019 12 22 19 52 55

Edit Parameter Interfaceでparametersのインタフェースを変更します。

2019 12 22 19 53 47

最初に2つの必要ないインタフェースをInvisibleにします。

2019 12 22 19 54 55

Stringをドラッグ&ドロップし、Nameをgroupに変更します。

2019 12 22 19 55 39

Ordered Menuをドラッグ&ドロップし、Nameをaxisに変更します。

2019 12 22 19 56 41

次のようにMenuの内容を変更します。

2019 12 22 19 58 00

Floatを2つドラッグ&ドロップし、Nameをそれぞれmainwidthとsubwidthとします。

2019 12 22 19 59 21

2つStringをドラッグ&ドロップし、Nameをそれぞれmaingroupnameとsubgroupnameとします。

2019 12 22 20 00 49

Acceptを押して確定します。 Main WidthとSub Widthには適当な値を入れておきます。

2019 12 22 20 16 19

Rect Trim Off Edgeノードのときと同じようにして次のようにノードを接続します。

2019 12 22 20 02 53

primitivewrangle2のVEXコードを次のように書き換えます。

2019 12 22 20 04 43

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でサブネットに格納します。

2019 12 22 20 16 54

2019 12 22 20 17 03

subnet1の名前をrectmultisplitとします。

2019 12 22 20 17 38

Edit Parameter Interfaceを開きます。

2019 12 22 20 18 11

はじめからあるラベルはInvisibleにしてしまいます。

2019 12 22 20 18 43

Edit Parameter Interfaceウィンドウを開いたままでrectmultisplitノードの内部に入ります。 parametersのパラメータをドラッグ&ドロップします。

2019 12 22 20 20 18

groupのMenuからUse Menuにチェックを入れてToggleにします。 Menu Scriptで次のPythonスクリプトを追加します。

2019 12 22 20 21 54

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コードを記述します。

2019 12 22 20 22 59

import soputils
kwargs['geometrytype'] = (hou.geometryType.Primitives,)
kwargs['inputindex'] = 0
soputils.selectGroupParm(kwargs)

Action Iconの欄にBUTTONS_reselectとしてアイコンを設定します。

2019 12 22 20 23 15

Acceptして確定します。 rectmultisplitノードのインタフェースで操作できるようになりました。

2019 12 22 20 24 15

rectmultisplitノードで右クリックをしてCreate Digital Asset...を実行します。

2019 12 22 20 24 32

適当な名前をつけてAcceptします。

2019 12 22 20 25 42

Inputのラベルの変更やTabメニューの階層の変更などを行います。

2019 12 22 20 26 15

2019 12 22 20 27 05

AcceptしてRect Multi Splitノードは完成です。

2019 12 22 20 27 30

rectmultisplit.hiplc


これでひとまず橋の作成に使うノードは完成です。

次の記事