Engineを調べてみました。Engineとは何か。インストール時にUE4やMAYA用を選択できる画面が出るので、他のアプリからHoudiniのデジタルアセットにアクセスできる機能。いうのが一般的な認識ではないでしょうか。APIの機能を利用したプラグインです。3.2**](https://www.sidefx.com/docs/hengine/index.html)の概要にはこう書かれています。**HoudiniEngineは、Houdiniデジタルアセットをホストアプリケーションの内部で直接使用できるようにするAPIです。Interface)で、C言語の関数セットです。コンパイラの選択肢やバージョンが一致しなくても、ホストアプリケーションに統合することができます。Engineは、ネイティブのC++アプリケーションと同様に、C#などの環境に統合することができます。最後に、HoudiniEngineはフラットで小さなAPIで、簡単に学習できます。##簡単に学習できます。動かすだけなら、ぎりぎりなんとかなりそうです。簡単に学習できますは嘘だと思います。Engineを初期化する。2)アセットノードをインスタンス化し、プロセス内のノードIDを取得します。3)アセットノードのパラメータを問い合わせて表示します。4)オブジェクトノードが存在する場合問い合わせます。5)各オブジェクト内のディスプレイジオメトリノードを問い合わせます。6)パーツを問い合わせ、頂点、面、法線などを取得して表示します。7)アセットノードおよび任意のネイティブノード入力のOPノードパスパラメータを問い合わせます。Engineに戻します。9)ノードをcookします。10)必要に応じて、更新されたノードおよびパーツ情報を取得します。11)アセットノードが不要になったときにクリーンアップします。Engineをクリーンアップします。おおざっぱすぎてよくわかりません、、。Engineの基礎まず基本的な作業についてQtを利用して確認していきます。C++をコンパイル、デバッグできる環境が必要です。作業に入る前に、HAPIや環境変数の設定が必要です。###重要なファイル以下のヘッダーファイルがHoudiniEngineディストリビューションに含まれています。ライブラリのリンクとその他の定義デフォルトのインストールオプションを使用した場合、これらのファイルは次の場所にあります。17.0.372/toolkit/include/HAPI"`ネイティブアプリケーションの場合は、インポートライブラリとリンクする必要があります。17.0.372/custom/houdini/dsolib/libHAPIL.lib"`*上記の例では17.0.372としていますが、インストールされているバージョンに合わせて書き換えを行ってください。バージョンによる不一致により動作しなくなることがあるので要注意です。バージョン確認方法は補足:バージョンの確認にて。HAPIを使用するときに最初に行う必要があるのは、**HAPI_Initialize()**を呼び出すことです。この機能は、**libHAPI.dll**内に存在します。libHAPI.dllは次の場所にあります。17.0.372\bin`このbinフォルダ内の全てのdllが適切にロードされるためには環境変数Pathにbinのパスを追加する必要があります。17.0.372/custom/houdini/dsolib/libHAPIL.lib"```###HAPIのincludeとHAPI_Initializeの実行まず初めに必要なHAPI(HAPI.h、HAPI_API.h、HAPI_Common.h)をincludeします。次に[HAPI_Initialize()](https://www.sidefx.com/docs/hengine/_h_a_p_i_8h.html#a7e479440e679e39a04ea3e65f801895d)を呼び出します。この関数によって必要なすべてのdllがロードされます。スレッドのスタックサイズを設定します。スタックサイズをHoudiniのデフォルト値に設定するには、-1を使用します。この値はバイト単位です。":"か";"で区切られたパスのリスト(OSで異なります)。prefsフォルダのhoudini.envファイルと同じ構文に従った.envファイルに変換します。オーディオDSO(カスタムプラグイン)が検索されるディレクトリ)```引数がたくさんありますが、まずは最初の2つの引数であるセッションとCookオプションをポインタ渡しします。Engine!";//次からmain.cppと書いてあるコードをここにどんどん追加して下さい。a.exec();}```includeとHAPI_Initialize()が成功しているかどうかを、デバッグもしくはビルドを行って確認してください。HAPI_SaveHIPFileによってtest.hipファイルが作成されていれば成功です。ここで読み込みが成功したのなら、HoudiniEngine入門の道のりは半分は達したようなものです。ちなみに中を開くと空っぽ(謎のノードGlobalNodesはありますが)の状態です。![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/515ad6e4-c1e5-3543-8b5b-9b2bdff50827.png)*このステップでは、ライセンスの確認は行われません。ノード作成時とアセットライブラリを読み込もうとしたときだけ、実際にライセンスをチェックします。*dllが他で利用されている場合衝突が発生する可能性があります。*今回はmain.cppにどんどんコードを追加していきます。本気でやる方は名前空間を分けて、クラスの設計もしっかり行う必要があります。*ポインタ渡しがわからない方は[こちら](https://qiita.com/agate-pris/items/05948b7d33f3e88b8967)##ノードIDの取得ここで聞きなれない単語が出てきました。「ノードID」とは何でしょうか。###ノードID(HAPI_NodeId)の基礎Houdiniのすべての関数とデータはノードとして表されます。HoudiniEngineでは、ノードは**HAPI_NodeId**によって識別されます。HAPI_CreateNode関数によってboxノードを追加できます。-1を渡した場合、operator_nameにはテーブル名(つまりObject/またはSop/)が含まれていなければなりません。HAPI_CreateNodeの第2引数に親となるジオメトリのnode_idを指定していれば、例えばGeometryネットワーク内の場合は"Sop/box"でなく"box"として作成できます。ここで再度ビルドしてboxが作成されていることを確認します。![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/809eb905-43e3-6061-b9c0-e60f28ba4101.png)ノードオペレータの名前の調べ方は2017年の@it_ksさんの[「ノードっていくつあるんですか?」](https://qiita.com/it_ks/items/08f6027ffd0aa9fab079)にあるpythonからのprint出力を使うことで調べることができます。)```毎回pythonで調べるのが面倒!という場合は一度ノードを自分で作成して、その際のノード名から数字(下の図だと1)を抜いた文字(geo)を確認してください。![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/1823bccd-26d5-26f4-b066-ec0573e33868.png)全部これでいいんじゃないの?と思われるかもしれませんが、operator_nameパラメータは、名前空間(Object/またはSop/)、名前(polyextrude)、およびバージョン(::2.0)を含める必要があります。'polyextrude'と'polyextrude::2.0'の2種類が存在しており、上記の方法だと新しいpolyextrudeの名前を知ることができません。何はともあれHAPI_CreateNode関数でノードを作成し、作成後にポインタ渡ししたbox_node_idに作成したノードID(HAPI_NodeInfo)が格納されます。###ネットワークのnode_infoを求める先ほどは"Sop/box"を利用することで、自動的にObjectsネットワークにGeometryノードが作成され、その中にboxノードを作成しました。-1でなく、Geometryノードをparent_nodeとして同階層にgridノードとcopyノードを作成しましょう。);```"../../"で階層を指定できれば楽なのですが、相対パスは使えないのでnode_infoとparentIdを組み合わせて同階層を導きます。![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/c2fa4b4e-0a3e-ea03-9697-f37b5bdb2a43.png)###ノードの接続ノードを接続するためにはHAPI_ConnectNodeInput関数を利用します。間でなければなりません。node_idの入力に接続するノード。間でなければなりません。);```![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/a37ffa79-a489-3778-c18d-565b1a16620f.png)##パラメータの取得ここでgridのパラメータを覗いてみましょう。パラメータの取得には段階を踏む必要があります。HAPI_GetParameters関数を利用してNodeInfo内に存在する複数のパラメータ群を取得した後、HAPI_GetParm〇〇〇Values関数で個別のパラメータを取り出します。〇〇〇にはInt、Float、Stringが入ります。HAPI_GetParametersで取得できるのは、様々なHAPI_ParmTypeが混ざった状態の配列です。正確に言えば[HAPI_ParmType](https://www.sidefx.com/docs/hengine/_h_a_p_i___common_8h.html#a070c689cb51acd4b65c883e73f4fd193)に応じた型が入ります。```C++:HAPI_ParmTypeHAPI_PARMTYPE_INTHAPI_PARMTYPE_MULTIPARMLISTHAPI_PARMTYPE_TOGGLEHAPI_PARMTYPE_BUTTONHAPI_PARMTYPE_FLOATHAPI_PARMTYPE_COLORHAPI_PARMTYPE_STRINGHAPI_PARMTYPE_PATH_FILEHAPI_PARMTYPE_PATH_FILE_GEOHAPI_PARMTYPE_PATH_FILE_IMAGE・・・etc```HAPI_ParmTypeの値の型は、Int、Float、Stringに分類されます。HAPI_ParmTypeがToggleで値がInt型、HAPI_ParmTypeがPATH_FILEで値がString型など、パラメータに応じた型が必要です。そこで、Getを行う前にInt、Float、Stringに分類します。分類はHAPI_ParmInfo_Is〇〇関数(HAPI_ParmInfo_IsInt、HAPI_ParmInfo_IsFloat、HAPI_ParmInfo_IsString)で判定します。gridで取得できるパラメータ名は以下のものになります。["type","surftype","orient","size","t","r","rows","cols","orderu","orderv","interpu","interpv"]![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/5747e89f-cfcf-47d4-fc85-bc025084974d.png)では、これからInt型のrowsと、Float型のsizeを取得していきます。HAPI_GetParametersの内訳はこのようになっています。![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/f4cc209b-fb06-f22e-9633-9631f6605bf1.png)あらかじめ、取得したいパラメータの名前、型がわかっている場合は直接値を取得できます。戻り値のint値)```上記はHAPI_GetParmIntValueですが、同じ要領でHAPI_GetParmFloatValue、HAPI_GetParmStringValueがあります。sizey_parm_value;//sizey_parm_value=10.0```次に紹介するのはHAPI_GetParmFloatValues関数です。ValueでなくValuesなので、vectorのパラメータのように2つ、3つ同時に配列として取得したい場合に利用します。範囲の最初のインデックス。start)```仮に、パラメータ名が不明で全てのパラメータを取得する必要がある場合はノードInfo内のパラメータをすべてfor文で読み込みます。その際に名前とパラメータを確認します。parmInfos;```パラメータ名の取得はHAPI_ParmInfo::nameSHで行います。取得した段階ではHAPI_StringHandle型なので、文字として確認する場合は、デバッグでの文字変換同様、lengthの確認を経てString型に変換する必要があります。取得ができたので、次はパラメータに値を代入します。セットするためにはHAPI_SetParmIntValue関数を利用します。パラメータ値の設定HAPI_SetParmIntValue(&Session,grid_node_id,"rows",0,5);```rowsが初期値10であったので、5に上書きされました。想定としては以下のようになるはずです。![rowdown.png](https://qiita-image-store.s3.amazonaws.com/0/151589/0235fe1b-5ab6-459b-2299-0ddcb75b0f5e.png)しかし、実際にhipファイルを開くとboxが1つあるだけです。![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/bfe2b60d-560a-374c-6d5c-1a7a3c1f3d94.png)なぜかというと、DisplayFlagがBoxについた状態のため、最終段のcopyまでcookされていないからです。![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/a37ffa79-a489-3778-c18d-565b1a16620f.png)##ノードのCookDisplayFlagやRenderFlagについては、HoudiniEngine側としてはあまり関係がありません。これらのFlagはHoudini内のGUIとして機能するからです。HoudiniEngineから欲しい結果をcookする場合はHAPI_CookNode関数を利用します。cookオプション)```###fileSOPからobj出力でcook確認さきほどのネットワークだと、本当にcookされているのか確認し辛いので、fileSOPノードを追加してcookが行われているかを確認します。HAPI_RESULT_SUCCESS);```以上のコードでfilemodeがReadMode(読み込みモード)からWriteMode(書き込みモード)に変わり、fileパラメータがデフォルトの"default.bgeo"から"../testoutput.obj"に書き換わりました。![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/498515d3-d94f-c2e4-09c3-59371dbe173e.png)ビルドさせてcookした瞬間にファイルが生成されました。![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/74978aa5-9b91-5ec4-2651-7ff5fc01b7f2.png)本来はHAPI_CookNodeだけでいいのですが、後に処理が続いています。これは、HAPI_CookNode関数が非同期処理のため、cook出来ていない状態で次の処理に行くことを防いでいます。(cookが完了したことを確認して次に進みます。)[do-while](http://www9.plala.or.jp/sgwr-t/c/sec06-4.html)内でHAPI_GetStatus関数を利用して[HAPI_Status](https://www.sidefx.com/docs/hengine/_h_a_p_i___common_8h.html#a83c3e00fe49fbf352bcd360541f9f537)HAPI_Stateが適正な返り値となっているかどうか確認します。問題なければcookStatusはHAPI_STATE_READYである0となっているはずです。HAPI_STATE_READY_WITH_COOK_ERRORS```do-whileはまず文を実行してから、継続条件の判定を行います。少なくとも1回はHAPI_GetStatusの確認処理を実行します。継続条件で偽になると脱出します。HAPI_ResultやHAPI_State)```ここまでboxノードやcopyノードを作成してきました。boxやcopyなどの処理はHoudini内部で初めから用意されている処理で、外部との連携という意味でいえば「ノードを作成しろ」「パラメータを変更しろ」という命令しか出していません。##HAPI_CreateInputNode関数でジオメトリ入力受付たとえば、別のアプリで作成したジオメトリの情報をfileに書き出すことなくHoudiniEngineに渡す場合どうすればいいでしょうか。ジオメトリを渡す場合は、HAPI_CreateInputNode関数を利用することで、ジオメトリ入力を受け入れることができるGeometrySOPノードを作成できます。APIを使用するジオメトリを設定できます。その後、このノードをジオメトリ入力として他のノードに接続することができます。"input"という名前で始まります。簡単なデバッグのために、この入力ノードに名前を付けます。"input_"が付加された名前を取得します。//NULLを渡すこともできます。"input#"になります。);```HAPI_CreateInputNodeでinput_Cubeが出来ましたが、実態は何もありません。何も入力していないからです。![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/eebe5e0f-3dda-4ce0-c949-bfbbe5a9c6d1.png)そこで、このNullにメッシュ情報を追加していきます。HAPI_PartInfo_Create();```また新しい単語が出てきました。「Part」とはなんでしょうか?パーツを直訳すると部品ですが、、###PartInfoの作成Partsは、Houdiniのジオメトリ内のプリミティブグループに基づいて計算されます。**Partsにはmesh/geometry/attributeデータが含まれています。いうようにプロパティ情報を付与していきます。まとまった時点で、先ほどの空っぽのnullノード(newNode)に対してnewNodePartオブジェクトをSetします。次は、Attribute情報を付与します。Attributeを付与するにはHAPI_AddAttribute関数内で、ノードIDに加えてPartInfoが必要になります。Attributeの数。この数はownerに与えられた値の数と一致します。vertexCountと同じになります。//これはAttribute内の値の数ではなく、Attributeの数です。3ではなく)になります。Attributeごとの値の数。これはAttributeのメモリサイズではないことに注意してください。//Attributeごとの値の数です。これにHAPI_AttributeInfo::storageのサイズを掛ければ、属性ごとのメモリサイズが得られます。現在使用されていません。length)```以上を踏まえたうえで、newNodePointInfoのHAPI_AttributeInfoプロパティを見ていきます。これから入れるAttributeはポジション情報のPointAttributeである"P"に関する情報です。countはAttributeの数なので、PointAttributeの場合当然立方体の頂点数と同じ8になります。existsはtrueにして、storageは浮動小数点のHAPI_STORAGETYPE_FLOATを選択します。ownerはHAPI_AttributeOwnerの中からHAPI_ATTROWNER_POINTを選択します。HAPI_AddAttributeした時点では値は何も入っていません。続けてSetAttributeが必要です。storageをHAPI_STORAGETYPE_FLOATとしたので、HAPI_SetAttributeFloatData関数を利用しましたが、この他に、HAPI_SetAttributeFloat64Data、HAPI_SetAttributeInt64Data、HAPI_SetAttributeIntData、HAPI_SetAttributeStringDataがあります。storageに応じて変更する必要があります。);```ここまでで、頂点の作成はできました。面が生成できていません。faceを作成するにはHAPI_SetVertexList関数とHAPI_SetFaceCounts関数を組み合わせて最終的にHAPI_CommitGeoする必要があります。HAPI_SetVertexList(//頂点と点の関連を含む配列を設定します。ここで、配列のi番目の要素は、i番目の頂点が関連する点のインデックスです。現在使用されていません。少なくともlengthの大きさの整数の配列。範囲の最初のインデックス。少なくとも0で、最大でHAPI_PartInfo::vertexCount-startでなければなりません。)``````C++:HAPI_SetFaceCounts関数//配列のn番目の整数がn番目の面にある頂点の数である面の配列を設定します。現在使用されていません。