1. jyouryuusui

    No comment

    jyouryuusui
Changes in body
Source | HTML | Preview
@@ -1,1155 +1,1145 @@
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/b0653e3c-1b48-f038-50b9-296986a5c6fc.png)
#はじめに
この記事は [HoudiniAdventCalender2018](https://qiita.com/advent-calendar/2018/houdini) 2日目の記事です。
今年は謎多きHoudini Engineを調べてみました。
まずHoudini Engineとは何か。インストール時にUE4やMAYA用を選択できる画面が出るので、他のアプリからHoudiniのデジタルアセットにアクセスできる機能。
というのが一般的な認識ではないでしょうか。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/eab55220-6f93-4424-1b19-bc6e18eec54d.png)
##API
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/120ce624-1c99-4afc-75f1-a5ce53618c1d.png)
しかしUE4やMAYAで利用しているのは、あくまでHoudini Engine APIの機能を利用したプラグインです。
最新のドキュメント[**Houdini Engine 3.2**](https://www.sidefx.com/docs/hengine/index.html)の概要にはこう書かれています。
**HoudiniEngineは、Houdiniデジタルアセットをホストアプリケーションの内部で直接使用できるようにするAPIです。**
###HoudiniEngineとは(OverViewより抜粋)
HoudiniエンジンはC ABI (Application Binary Interface)で、C言語の関数セットです。コンパイラの選択肢やバージョンが一致しなくても、ホストアプリケーションに統合することができます。たとえば、Houdini Engineは、ネイティブのC++アプリケーションと同様に、C#などの環境に統合することができます。
最後に、HoudiniEngineはフラットで小さなAPIで、簡単に学習できます。
##簡単に学習できます。
動かすだけなら、ぎりぎりなんとかなりそうです。ただ、簡単に学習できますは嘘だと思います。
###基本的な作業
- 1)Houdini Engineを初期化する。
- 2)アセットノードをインスタンス化し、プロセス内のノードIDを取得します。
- 3)アセットノードのパラメータを問い合わせて表示します。
- 4)オブジェクトノードが存在する場合問い合わせます。
- 5)各オブジェクト内のディスプレイジオメトリノードを問い合わせます。
- 6)パーツを問い合わせ、頂点、面、法線などを取得して表示します。
- 7)アセットノードおよび任意のネイティブノード入力のOPノードパスパラメータを問い合わせます。
- 8)パラメータの変更をHoudini Engineに戻します。
- 9)ノードをcookします。
- 10)必要に応じて、更新されたノードおよびパーツ情報を取得します。
- 11)アセットノードが不要になったときにクリーンアップします。
- 12)完了したらHoudini Engineをクリーンアップします。
おおざっぱすぎてよくわかりません、、。
#手を動かして理解するHoudini Engineの基礎
まず基本的な作業についてQtを利用して確認していきます。
C++をコンパイル、デバッグできる環境が必要です。インストール方法は補足:Qtのインストールにて
実行環境は
Houdiniバージョン:17.0.372
Qtバージョン:5.11.2
ビルド:MSVC2017_64bit 
で行います。
##Houdini Engineの初期設定
作業に入る前に、HAPIや環境変数の設定が必要です。
###重要なファイル
以下のヘッダーファイルがHoudiniEngineディストリビューションに含まれています。
■ HAPI.h - API関数のシグネチャ
■ HAPI_Common.h - 構造体と列挙型
■ HAPI_Version.h - バージョン情報
■ HAPI_Helpers.h - 構造体を初期化するための関数
■ HAPI_API.h - ライブラリのリンクとその他の定義
デフォルトのインストールオプションを使用した場合、これらのファイルは次の場所にあります。
`"C:/Program Files/Side Effects Software/Houdini 17.0.372/toolkit/include/HAPI"`
ネイティブアプリケーションの場合は、インポートライブラリとリンクする必要があります。
`"C:/Program Files/Side Effects Software/Houdini 17.0.372/custom/houdini/dsolib/libHAPIL.lib"`
*上記の例では17.0.372としていますが、インストールされているバージョンに合わせて書き換えを行ってください。特にバージョンによる不一致により動作しなくなることがあるので要注意です。 バージョン確認方法は補足:バージョンの確認にて。
HAPIを使用するときに最初に行う必要があるのは、**HAPI_Initialize()**を呼び出すことです。この機能は、**libHAPI.dll**内に存在します。またlibHAPI.dllは次の場所にあります。
`C:\Program Files\Side Effects Software\Houdini 17.0.372\bin`
このbinフォルダ内の全てのdllが適切にロードされるためには環境変数Pathにbinのパスを追加する必要があります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/b326ba63-49be-b443-e1db-9fc89cdb54bd.png)
###HAPIとlibHAPIL.libのパスを通す
```C++:HESample.pro
#-------------------------------------------------
#
# Project created by QtCreator 2018-12-02T06:44:33
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = HESample
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
INCLUDEPATH += "C:/Program Files/Side Effects Software/Houdini 17.0.372/toolkit/include/HAPI"
LIBS += "C:/Program Files/Side Effects Software/Houdini 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がロードされます。
```C++:HAPI_Initialize関数
HAPI_DECL HAPI_Initialize(
const HAPI_Session * session,//[in] Houdiniのセッション
const HAPI_CookOptions * cook_options,//[in] Cookによって使用されるグローバルCookオプション
HAPI_Bool use_cooking_thread,//[in] アセットのcookには別のスレッドを使用します
int cooking_thread_stack_size,//[in] スレッドのスタックサイズを設定します。スタックサイズをHoudiniのデフォルト値に設定するには、-1を使用します。この値はバイト単位です。
const char * houdini_environment_files,//[in] ":"か";"で区切られたパスのリスト(OSで異なります)。Houdiniのuser prefsフォルダのhoudini.envファイルと同じ構文に従った.envファイルに変換します。
const char * otl_search_path,//[in] OTLが検索されるディレクトリ
const char * dso_search_path,//[in] ジェネリックDSO(カスタムプラグイン)が検索されるディレクトリ
const char * image_dso_search_path,//[in] イメージDSO(カスタムプラグイン)が検索されるディレクトリ
const char * audio_dso_search_path //[in] オーディオDSO(カスタムプラグイン)が検索されるディレクトリ
)
```
引数がたくさんありますが、まずは最初の2つの引数であるセッションとCookオプションをポインタ渡しします。
```C++:main.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
#include <HAPI.h>
#include <HAPI_Common.h>
int main(int argc, char *argv[])
{
HAPI_Session Session;
HAPI_CookOptions CookOptions;
CookOptions = HAPI_CookOptions_Create();
HAPI_CreateInProcessSession(&Session);
HAPI_Initialize(&Session,&CookOptions,true,-1,nullptr,nullptr,nullptr,nullptr,nullptr);
qDebug() << "Hello Houdini Engine!";
//次からmain.cppと書いてあるコードをここにどんどん追加して下さい。
HAPI_SaveHIPFile(&Session,"../test.hip",nullptr);
QApplication a(argc, argv);
MainWindow w;
w.show();
return 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作成
例えば、HAPI_CreateNode関数によってboxノードを追加できます。
```C++:HAPI_CreateNode関数
HAPI_DECL HAPI_CreateNode(
const HAPI_Session * session,//[in] Houdiniのセッション
HAPI_NodeId parent_node_id,//[in] 親ノードのネットワークのノードID、または親ネットワークがマネージャ(最上位)ノードの場合は-1
const char * operator_name,//[in] ノードオペレータタイプの名前
const char * node_label,//[in] 新しく作成されたノードのラベル
HAPI_Bool cook_on_creation,//[in] 作成したノードを一度作成するかどうかを設定します
HAPI_NodeId * new_node_id //[out] 直前に作成されたノードIDが渡した変数に格納される 次の処理に使うのはこいつ
)
```
```C++:main.cpp boxノードの作成
HAPI_NodeId box_node_id;
HAPI_CreateNode( &Session, -1, "Sop/box", "Box_Node_Name", true, &box_node_id );
```
parent_node_id == -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:sopNodeTypeCategory(Python)
sopNodes = hou.sopNodeTypeCategory().nodeTypes()
print sorted( sopNodes.keys() )
```
```python:objNodeTypeCategory(Python)
objNodes = hou.objNodeTypeCategory().nodeTypes()
print sorted( objNodes.keys() )
```
毎回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'と'polyextrude::2.0'の2種類が存在しており、上記の方法だと新しいpolyextrudeの名前を知ることができません。
何はともあれHAPI_CreateNode関数でノードを作成し、作成後にポインタ渡ししたbox_node_idに作成したノードID(HAPI_NodeInfo)が格納されます。
###ネットワークのnode_infoを求める
先ほどは"Sop/box"を利用することで、自動的にObjectsネットワークにGeometryノードが作成され、その中にboxノードを作成しました。次はparent_node_id == -1でなく、Geometryノードをparent_nodeとして同階層にgridノードとcopyノードを作成しましょう。
```C++:main.cpp boxと同階層にgridとcopyを作成
// Check node_info.
HAPI_NodeInfo box_node_info;
HAPI_GetNodeInfo(&Session, box_node_id, &box_node_info );
// Create a grid node.
HAPI_NodeId grid_node_id;
HAPI_CreateNode(&Session, box_node_info.parentId, "grid", "grid_Node_Name", true,&grid_node_id );
// Create a copy node.
HAPI_NodeId copy_node_id;
HAPI_CreateNode(&Session, box_node_info.parentId, "copy", "copy_Node_Name", true,&copy_node_id );
```
"../../"で階層を指定できれば楽なのですが、相対パスは使えないのでnode_infoとparentIdを組み合わせて同階層を導きます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/c2fa4b4e-0a3e-ea03-9697-f37b5bdb2a43.png)
###ノードの接続
ノードを接続するためにはHAPI_ConnectNodeInput関数を利用します。
```C++:HAPI_ConnectNodeInput関数
//2つのノードを接続します
HAPI_DECL HAPI_ConnectNodeInput(
const HAPI_Session * session,//[in] Houdiniセッション
HAPI_NodeId node_id,//[in] 接続先のノード
int input_index,//[in] 入力インデックス。0とto_nodeのHAPI_NodeInfo::inputCount-1 の間でなければなりません。
HAPI_NodeId node_id_to_connect,//[in] node_idの入力に接続するノード。
int output_index //[in] 出力インデックス。0とto_nodeのHAPI_NodeInfo :: outputCount - 1 の間でなければなりません。
)
```
```C++:main.cpp ノードの接続
HAPI_ConnectNodeInput(&Session, copy_node_id, 0, box_node_id,0 );
HAPI_ConnectNodeInput(&Session, copy_node_id, 1, grid_node_id,0 );
```
![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_ParmType
HAPI_PARMTYPE_INT
HAPI_PARMTYPE_MULTIPARMLIST
HAPI_PARMTYPE_TOGGLE
HAPI_PARMTYPE_BUTTON
HAPI_PARMTYPE_FLOAT
HAPI_PARMTYPE_COLOR
HAPI_PARMTYPE_STRING
HAPI_PARMTYPE_PATH_FILE
HAPI_PARMTYPE_PATH_FILE_GEO
HAPI_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)
あらかじめ、取得したいパラメータの名前、型がわかっている場合は直接値を取得できます。
```C++:HAPI_GetParmIntValue関数
HAPI_DECL HAPI_GetParmIntValue(
const HAPI_Session * session,//[in] Houdiniのセッション
HAPI_NodeId node_id,//[in] ノードID
const char * parm_name,//[in] parmの名前
int index,//[in] パラメータの値タプル内のインデックス
int * value //[out] 戻り値のint値
)
```
上記はHAPI_GetParmIntValueですが、同じ要領でHAPI_GetParmFloatValue、HAPI_GetParmStringValueがあります。
```C++:main.cpp パラメータの値を取得
int rows_parm_value;
HAPI_GetParmIntValue(&Session,grid_node_id,"rows",0,&rows_parm_value);
qDebug() << "rows_parm_value="<<rows_parm_value;//rows_parm_value=10
float sizex_parm_value;
float sizey_parm_value;
HAPI_GetParmFloatValue(&Session,grid_node_id,"size",0,&sizex_parm_value);
HAPI_GetParmFloatValue(&Session,grid_node_id,"size",1,&sizey_parm_value);
qDebug() << "sizex_parm_value=" << sizex_parm_value;//sizex_parm_value=10.0
qDebug() << "sizey_parm_value=" << sizey_parm_value;//sizey_parm_value=10.0
```
次に紹介するのはHAPI_GetParmFloatValues関数です。ValueでなくValuesなので、vectorのパラメータのように2つ、3つ同時に配列として取得したい場合に利用します。
```C++:HAPI_GetParmFloatValues関数
HAPI_DECL HAPI_GetParmFloatValues(
const HAPI_Session * session,//[in] Houdiniのセッション
HAPI_NodeId node_id,//[in] ノードID
float * values_array,//[out] 浮動小数点数の配列
int start,//[in] 範囲の最初のインデックス。少なくとも0で、最大でHAPI_NodeInfo::parmFloatValueCount - 1
int length //[in] 少なくとも1で、多くともHAPI_NodeInfo::parmFloatValueCount - start
)
```
仮に、パラメータ名が不明で全てのパラメータを取得する必要がある場合はノードInfo内のパラメータをすべてfor文で読み込みます。その際に名前とパラメータを確認します。
```C++:main.cpp 全パラメータ名、パラメータ値の取得
//Change to the HAPI_NodeId you want to know
HAPI_NodeId node_id = grid_node_id;
HAPI_NodeInfo node_info;
HAPI_GetNodeInfo( &Session, node_id, &node_info ) ;
HAPI_ParmInfo * parmInfos = new HAPI_ParmInfo[ node_info.parmCount ];
HAPI_GetParameters( &Session, node_id, parmInfos, 0, node_info.parmCount ) ;
for( int i = 0; i < node_info.parmCount; ++i ){
HAPI_StringHandle hsh=parmInfos[ i ].nameSH ;
int buffer_length;
HAPI_GetStringBufLength(&Session, hsh, &buffer_length );
char * buf = new char[ buffer_length ];
HAPI_GetString( &Session, hsh, buf , buffer_length);
//buf is parmname
QString qstr = QString::fromStdString(buf);
qDebug("%s", qPrintable("---------------"));
qDebug("%s", qPrintable(qstr));
delete[] buf;
if ( HAPI_ParmInfo_IsInt( &parmInfos[ i ] ) ){//ParmInfo Is Int
int parmIntCount = HAPI_ParmInfo_GetIntValueCount( &parmInfos[ i ] );
int * parmIntValues = new int[ parmIntCount ];
HAPI_GetParmIntValues( &Session,node_id, parmIntValues,parmInfos[ i ].intValuesIndex,parmIntCount ) ;
for ( int v = 0; v < parmIntCount; ++v ){
int piv= parmIntValues[ v ];
qDebug() << piv ;
}
delete [] parmIntValues;
}else if ( HAPI_ParmInfo_IsFloat( &parmInfos[ i ] ) ){//ParmInfo Is Float
int parmFloatCount = HAPI_ParmInfo_GetFloatValueCount( &parmInfos[ i ] );
float * parmFloatValues = new float[ parmFloatCount ];
HAPI_GetParmFloatValues( &Session,node_id, parmFloatValues,parmInfos[ i ].floatValuesIndex,parmFloatCount ) ;
for ( int v = 0; v < parmFloatCount; ++v ){
float pfv= parmFloatValues[ v ];
qDebug() << pfv ;
}
delete [] parmFloatValues;
}else if ( HAPI_ParmInfo_IsString( &parmInfos[ i ] ) ){//ParmInfo Is String
int parmStringCount = HAPI_ParmInfo_GetStringValueCount( &parmInfos[ i ] );
HAPI_StringHandle * parmSHValues = new HAPI_StringHandle[ parmStringCount ];
HAPI_GetParmStringValues( &Session,node_id,true, parmSHValues,parmInfos[ i ].stringValuesIndex,parmStringCount );
for ( int v = 0; v < parmStringCount; ++v ){
HAPI_ParmInfo parmInfo=parmInfos[ i ];
HAPI_StringHandle shv = parmSHValues[ v ];
int buffer_length;
HAPI_GetStringBufLength(&Session, shv, &buffer_length );
char * buf = new char[ buffer_length ];
HAPI_GetString( &Session, shv, buf , buffer_length);
QString qstr = QString::fromStdString(buf);
qDebug() << qstr;
}
delete [] parmSHValues;
}
}
delete [] parmInfos;
```
パラメータ名の取得はHAPI_ParmInfo::nameSHで行います。取得した段階ではHAPI_StringHandle型なので、文字として確認する場合は、デバッグでの文字変換同様、lengthの確認を経てString型に変換する必要があります。
##パラメータの設定
取得ができたので、次はパラメータに値を代入します。セットするためにはHAPI_SetParmIntValue関数を利用します。
```C++:HAPI_SetParmIntValue関数
HAPI_DECL HAPI_SetParmIntValue (
const HAPI_Session * session,//[in] Houdiniのセッション
HAPI_NodeId node_id,//[in] ノードID
const char * parm_name,//[in] parmの名前
int index,//[in] パラメータの値タプル内のインデックス
int value //[in] int値
)
```
```C++:main.cpp パラメータ値の設定
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)
##ノードのCook
DisplayFlagやRenderFlagについては、HoudiniEngine側としてはあまり関係がありません。
あくまでこれらのFlagはHoudini内のGUIとして機能するからです。
HoudiniEngineから欲しい結果をcookする場合はHAPI_CookNode関数を利用します。
```C++:HAPI_CookNode関数
HAPI_DECL HAPI_CookNode (
const HAPI_Session * session,//[in] Houdiniのセッション
HAPI_NodeId node_id,//[in] ノードID
const HAPI_CookOptions * cook_options //[in] cookオプション
)
```
###fileSOPからobj出力でcook確認
さきほどのネットワークだと、本当にcookされているのか確認し辛いので、fileSOPノードを追加してcookが行われているかを確認します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/b206049d-7af0-f75a-0501-7a69f5d1201e.png)
```C++:main.cpp fileノード追加
// Create a file node.
HAPI_NodeId file_node_id;
HAPI_CreateNode(&Session, box_node_info.parentId, "file", "file_Node_Name", true,&file_node_id );
HAPI_ConnectNodeInput(&Session, file_node_id, 0, copy_node_id,0 );
HAPI_SetParmIntValue(&Session,file_node_id,"filemode",0,2);
HAPI_ParmId file_parm_id;
HAPI_Result aaarsutlt=HAPI_GetParmIdFromName(&Session,file_node_id,"file",&file_parm_id);
HAPI_SetParmStringValue(&Session,file_node_id,"../testoutput.obj",file_parm_id,0);
HAPI_CookNode ( &Session, file_node_id, &CookOptions );
int cookStatus;
HAPI_Result cookResult;
do{
cookResult = HAPI_GetStatus( &Session, HAPI_STATUS_COOK_STATE, &cookStatus );
}
while (cookStatus > HAPI_STATE_MAX_READY_STATE && cookResult == 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)
-![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/763a6a40-b6dc-d3c4-08ec-75c047b7b3fc.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となっているはずです。
```C++:HAPI_State
HAPI_STATE_READY, //エラーなくすべてが正常にcookされた
HAPI_STATE_READY_WITH_FATAL_ERRORS, //cook失敗
HAPI_STATE_READY_WITH_COOK_ERRORS, //一部のオブジェクトのみが失敗
HAPI_STATE_STARTING_COOK,
HAPI_STATE_COOKING,
HAPI_STATE_STARTING_LOAD,
HAPI_STATE_LOADING,
HAPI_STATE_MAX,
HAPI_STATE_MAX_READY_STATE = HAPI_STATE_READY_WITH_COOK_ERRORS
```
do-whileはまず文を実行してから、継続条件の判定を行います。つまり少なくとも1回はHAPI_GetStatusの確認処理を実行します。そして、継続条件で偽になると脱出します。
```C++:HAPI_GetStatus関数
HAPI_DECL HAPI_GetStatus(
const HAPI_Session * session,//[in] Houdiniセッション
HAPI_StatusType status_type,//[in] HAPI_STATUS_CALL_RESULT や HAPI_STATUS_COOK_STATE
int * status //[out] 実際のステータスコード HAPI_ResultやHAPI_State
)
```
ここまでboxノードやcopyノードを作成してきました。しかし、boxやcopyなどの処理はHoudini内部で初めから用意されている処理で、外部との連携という意味でいえば「ノードを作成しろ」「パラメータを変更しろ」という命令しか出していません。
##HAPI_CreateInputNode関数でジオメトリ入力受付
たとえば、別のアプリで作成したジオメトリの情報をfileに書き出すことなくHoudiniEngineに渡す場合どうすればいいでしょうか。
ジオメトリを渡す場合は、HAPI_CreateInputNode関数を利用することで、ジオメトリ入力を受け入れることができるGeometrySOPノードを作成できます。これにより、Null SOPを持つダミーのOBJノードが作成され、ジオメトリSET APIを使用するジオメトリを設定できます。
その後、このノードをジオメトリ入力として他のノードに接続することができます。
HAPI_SaveHIPFile()を使用してHoudiniシーンを保存すると、このメソッドで作成されたノードは緑色になり、 "input"という名前で始まります。
```C++:HAPI_CreateInputNode関数
HAPI_DECL HAPI_CreateInputNode (
const HAPI_Session * session,//[in] Houdiniのセッション
HAPI_NodeId * node_id,//[out] 新しく作成されたノードのID
const char * name //[in] 簡単なデバッグのために、この入力ノードに名前を付けます。
//ノードの親OBJノードとNullSOPノードは、この名前の前に "input_"が付加された名前を取得します。
//NULLを渡すこともできます。その場合、名前は "input#"になります。#は数字です。
)
```
```C++:main.cpp input_読み込み
HAPI_NodeId newNode;
HAPI_CreateInputNode( &Session, &newNode, "Cube" );
```
HAPI_CreateInputNodeでinput_Cubeが出来ましたが、実態は何もありません。
なぜなら何も入力していないからです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/eebe5e0f-3dda-4ce0-c949-bfbbe5a9c6d1.png)
-
-![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/671d4a4f-8200-c303-ef9e-58f2d95bf4f4.png)
-
そこで、このNullにメッシュ情報を追加していきます。
```C++:main.cpp input_読み込み
-
// Creating the triangle vertices
HAPI_PartInfo newNodePart = HAPI_PartInfo_Create();
```
また新しい単語が出てきました。「Part」とはなんでしょうか?パーツを直訳すると部品ですが、、
###PartInfoの作成
Partsは、Houdiniのジオメトリ内のプリミティブグループに基づいて計算されます。
そして、**Partsにはmesh/geometry/attributeデータが含まれています。**
```C++:main.cpp input_読み込み
-
- // Creating the triangle vertices
- HAPI_PartInfo newNodePart = HAPI_PartInfo_Create();
-
newNodePart.type = HAPI_PARTTYPE_MESH;
newNodePart.faceCount = 6;
newNodePart.vertexCount = 24;
newNodePart.pointCount = 8;
HAPI_SetPartInfo( &Session, newNode, 0, &newNodePart ) ;
```
よって、HAPI_PartInfo_Create関数で作ったnewNodePartオブジェクトに対して、
どんな型か?(Mesh、CURVE、VOLUME、INSTANCERなど)、faceの数は?(例えば立方体だと6面)、vertexの数は?(例えば立方体だと24vertex)、pointの数は?(立方体だと8頂点) というようにプロパティ情報を付与していきます。
まとまった時点で、先ほどの空っぽのnullノード(newNode)に対してnewNodePartオブジェクトをSetします。
-![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/40428ec2-ebc7-d09c-0626-2539b1cc38ad.png)
-
-フリーズ状態でboxが保持されていることが確認できます。
-
###PartInfoにAttribute付与
次は、Attribute情報を付与します。Attributeを付与するにはHAPI_AddAttribute関数内で、ノードIDに加えてPartInfoが必要になります。
```C++:HAPI_AttributeInfo構造体PublicAttributes
HAPI_Bool exists//存在確認
HAPI_AttributeOwner owner//所有者、、というよりも、どのRun Overか?というほうがわかりやすいかもしれません
HAPI_StorageType storage//格納される型 INT型か?など HAPI_StorageTypeを参照
HAPI_AttributeOwner originalOwner//GAジオメトリ形式からGTジオメトリ形式に変換された場合のため
int count
//count: Attributeの数。この数はownerに与えられた値の数と一致します。
//たとえば、所有者がHAPI_ATTROWNER_VERTEXの場合、このカウントはHAPI_PartInfo :: vertexCountと同じになります。
//これはAttribute内の値の数ではなく、Attributeの数です。
//ジオメトリに3つの3Dポイントがある場合、HAPI_AttributeInfo::tupleSizeは3になりますが、
//countは3(3 * 3ではなく)になります。
int tupleSize
//tupleSize: Attributeごとの値の数。これはAttributeのメモリサイズではないことに注意してください。
//Attributeごとの値の数です。これにHAPI_AttributeInfo::storageのサイズを掛ければ、属性ごとのメモリサイズが得られます。
HAPI_AttributeTypeInfo typeInfo
```
```C++:HAPI_AttributeOwner
HAPI_ATTROWNER_INVALID
HAPI_ATTROWNER_VERTEX
HAPI_ATTROWNER_POINT
HAPI_ATTROWNER_PRIM
HAPI_ATTROWNER_DETAIL
```
```C++:HAPI_StorageType
HAPI_STORAGETYPE_INVALID
HAPI_STORAGETYPE_INT
HAPI_STORAGETYPE_INT64
HAPI_STORAGETYPE_FLOAT
HAPI_STORAGETYPE_FLOAT64
HAPI_STORAGETYPE_STRING
```
```C++:HAPI_AddAttribute関数
HAPI_DECL HAPI_AddAttribute(
const HAPI_Session * session,//[in] Houdiniセッション
HAPI_NodeId node_id,//[in] SOPノードID
HAPI_PartId part_id,//[in] 現在使用されていません。ただ0を渡します。
const char * name,//[in] Attribute名
const HAPI_AttributeInfo * attr_info //[in] Attributeのプロパティが格納された HAPI_AttributeInfo
)
```
```C++:HAPI_SetAttributeFloatData関数
HAPI_DECL HAPI_SetAttributeFloatData(
const HAPI_Session * session,
HAPI_NodeId node_id,
HAPI_PartId part_id,
const char * name,
const HAPI_AttributeInfo * attr_info,
const float * data_array,
int start,
int 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に応じて変更する必要があります。
```C++:main.cpp input_読み込み
HAPI_AttributeInfo newNodePointInfo = HAPI_AttributeInfo_Create();
newNodePointInfo.count = 8;
newNodePointInfo.tupleSize = 3;
newNodePointInfo.exists = true;
newNodePointInfo.storage = HAPI_STORAGETYPE_FLOAT;
newNodePointInfo.owner = HAPI_ATTROWNER_POINT;
HAPI_AddAttribute( &Session, newNode, 0, "P", &newNodePointInfo );
float positions[ 24 ] = { 0.0f, 0.0f, 0.0f, // 0
0.0f, 0.0f, 1.0f, // 1
0.0f, 1.0f, 0.0f, // 2
0.0f, 1.0f, 1.0f, // 3
1.0f, 0.0f, 0.0f, // 4
1.0f, 0.0f, 1.0f, // 5
1.0f, 1.0f, 0.0f, // 6
1.0f, 1.0f, 1.0f }; // 7
HAPI_SetAttributeFloatData( &Session, newNode, 0, "P", &newNodePointInfo, positions, 0, 8 );
```
ここまでで、頂点の作成はできました。しかし、まだ面が生成できていません。
faceを作成するにはHAPI_SetVertexList関数とHAPI_SetFaceCounts関数を組み合わせて最終的にHAPI_CommitGeoする必要があります。
```C++:HAPI_SetVertexList関数
HAPI_DECL HAPI_SetVertexList(
//頂点と点の関連を含む配列を設定します。ここで、配列のi番目の要素は、i番目の頂点が関連する点のインデックスです。
const HAPI_Session * session,//[in] Houdiniのセッション
HAPI_NodeId node_id,//[in] ノードID
HAPI_PartId part_id,//[in] 現在使用されていません。ただ0を渡します。
const int * vertex_list_array,//[in] 少なくともlengthの大きさの整数の配列。
int start,//[in] 範囲の最初のインデックス。少なくとも0で、最大でHAPI_PartInfo::vertexCount-1 でなければなりません。
int length //[in] 少なくとも0で、最大でHAPI_PartInfo::vertexCount-startでなければなりません。
)
```
```C++:HAPI_SetFaceCounts関数
//配列のn番目の整数がn番目の面にある頂点の数である面の配列を設定します。
HAPI_DECL HAPI_SetFaceCounts(
const HAPI_Session * session,//[in] Houdiniセッション
HAPI_NodeId node_id,//[in] ノードID
HAPI_PartId part_id,//[in] 現在使用されていません。ただ0を渡します。
const int * face_counts_array,//[in] 少なくとも配列の長さの整数配列
int start,//[in] 最初のインデックス。少なくとも0で、最大でHAPI_PartInfo::faceCount - 1
int length //[in] 長さ、少なくとも0で、多くともHAPI_PartInfo::faceCount - startでなければなりません。
)
```
```C++:main.cpp input_読み込み
int vertices[ 24 ] = { 0, 2, 6, 4,
2, 3, 7, 6,
2, 0, 1, 3,
1, 5, 7, 3,
5, 4, 6, 7,
0, 4, 5, 1 };
HAPI_SetVertexList( &Session, newNode, 0, vertices, 0, 24 );
int face_counts [ 6 ] = { 4, 4, 4, 4, 4, 4 };
HAPI_SetFaceCounts( &Session, newNode, 0, face_counts, 0, 6 ) ;
HAPI_CommitGeo( &Session, newNode );
```
+![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/40428ec2-ebc7-d09c-0626-2539b1cc38ad.png)
+
+ここでビルドします。フリーズ状態でboxが保持されていることが確認できます。
+
##デジタルアセット
入力したboxとデジタルアセットを接続してみます。
そのために、まずはデジタルアセットの準備を行います。
今回はテストなのでsubdivideとmountainのみのシンプルなHDAを作成します。
![asset0.png](https://qiita-image-store.s3.amazonaws.com/0/151589/441cd360-d669-5f12-6b07-50ada68d7718.png)
外に出すパラメータとしてmountainのHeightとElement Sizeを置きます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/3077bfc6-7408-f4c6-544c-b52ac6d71b45.png)
結果の確認
![asset.png](https://qiita-image-store.s3.amazonaws.com/0/151589/d4386a66-a299-8c1b-9596-0a366a1b00ef.png)
###HAPI_LoadAssetLibraryFromFile関数でotl・hda読み込み
アセットを読み込んでノードのインスタンス化をする場合は、HAPI_LoadAssetLibraryFromFileでotl、hdaを読み込み、boxと同様にHAPI_CreateNodeします。デジタルアセットがObject/かSop/かどうかは適宜変更が必要です。
```C++:main.cpp mytestsubnet6.otlの読み込み
// Load the library from file.
HAPI_AssetLibraryId assetLibId = -1;
HAPI_Result result = HAPI_LoadAssetLibraryFromFile(
&Session, "C:/Users/jyour/Documents/houdini17.0/otls/testAsset.hdalc",
false, &assetLibId );
HAPI_NodeId hda_node_id = -1;
result = HAPI_CreateNode(
&Session, -1, "Sop/testAssetName",
nullptr, true, &hda_node_id );
```
今回は自分でデジタルアセットを作ったので、利用できる事もアセットの名前も自明でしたが、例えばotlを随時読み込んで処理するとなった場合はもうひと手間必要です。HAPI_LoadAssetLibraryFromFileを行った後にまず、HAPI_GetAvailableAssetCount関数でアセットライブラリに含まれるアセットの数を取得します。その後にHAPI_GetAvailableAssets関数を利用してotl、hdaでのString_Handle、アセットの名前を取得します。詳細については割愛します。
###デジタルアセットと接続
ノードを接続するためには先ほどboxとcopyの接続にも使用したHAPI_ConnectNodeInput関数を利用します。
-```C++:main.cpp
+```C++:main.cpp ジオメトリ入力受付とデジタルアセットの接続
HAPI_ConnectNodeInput(&Session, hda_node_id, 0, newNode,0 );
```
ビルド実行するとデジタルアセットのノードとInput用のノードが作成されていることが確認できます。
-![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/fc043324-2ccb-2086-51a0-f625b781e866.png)
+![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/7201f4de-92e6-b494-8cee-c4bad2195cf5.png)
HAPI_ConnectNodeInputによって、ObjectMergeが実行されています。
先ほどboxとcopyをつなげた際は、同一ネットワーク内でしたが、今回は別のジオメトリを跨ぐためObjectMergeとなっています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/4a3581ae-cc9a-3b0a-f421-ace41c43c337.png)
###デジタルアセットのパラメータ取得
パラメータの取得や設定は、これまでやってきた方法と同じ方法です。パラメータをセットした後は忘れずcookを行います。
-```C++:main.cpp
+```C++:main.cpp hdaのパラメータ取得
HAPI_SetParmFloatValue(&Session,hda_node_id,"height",0,5.0f);
HAPI_SetParmFloatValue(&Session,hda_node_id,"elementsize",0,1.5f);
HAPI_CookNode ( &Session, hda_node_id, &CookOptions );
int cookStatus2;
HAPI_Result cookResult2;
do
{
cookResult2 = HAPI_GetStatus( &Session, HAPI_STATUS_COOK_STATE, &cookStatus2 );
}
while (cookStatus2 > HAPI_STATE_MAX_READY_STATE && cookResult2 == HAPI_RESULT_SUCCESS);
```
mountainに大きい値が入ったので、ちゃんと変形が確認できました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/4407d7ab-a7a3-6815-95e4-bb204c72847b.png)
この変更をホストアプリケーションに戻す場合は、HAPI_GetPartInfo関数で必要なAttributeなどの情報を戻します。
```C++:HAPI_GetPartInfo関数
HAPI_DECL HAPI_GetPartInfo(
const HAPI_Session * session,// [in] Houdiniセッション
HAPI_NodeId node_id,// [in] ノードID
HAPI_PartId part_id,// [in] Part ID
HAPI_PartInfo * part_info// [out] HAPI_PartInfo戻り値
)
```
##クリーンアップ
HAPI_PartInfoの情報をホストに戻すことで一件落着と、その前に忘れずセッションのクリーンアップをする必要があります。
```C++:クリーンアップ
HAPI_Cleanup(&Session);
```
#QtのUIと紐づけ
せっかくQtを利用しているので、フォームに値を入れて戻る動作を確認します。
mainwindows.uiをダブルクリックしてQtDesigner画面に遷移します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/ac1fba1c-1917-7a36-65b6-e6eabc715e47.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/aa912d08-17f3-eb1e-be74-a8e9574b8bd5.png)
今回は
・height値を調整するパラメータエリア
・HoudiniEngineから戻って来た情報を出すDebug用のテキストエリア
・Cookするためのボタン
を用意します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/ba23d72d-44a0-2ba3-0a42-fd1b74fee814.png)
以上、完成です。お疲れさまでした。
HoudiniEngineAPI入門いかがでしたでしょうか。簡単ではないけれども一切理解できない状態から、少しは霧が晴れたのではないでしょうか?2016年の@satoruhigaさんが紹介した[「HDKについて」](https://qiita.com/satoruhiga/items/49f8fa93307350e3c662)同様に、この技術がすぐに役に立つことはなかなか無いとは思います。
しかしHoudiniEngineAPIという外部からHoudiniの力を引き出せる強力な機能として、Hythonでは出来なかったホストアプリケーションとの連携や速度向上など様々なメリットがHoudiniEngineにはあります。この記事が皆様のお役に立てれば幸いです。
次のアドベントカレンダーは@sugiggyさんの「失ったTransformを取り戻す。」です。
#補足
ここでは、一連の流れで横道にそれるために省略した内容を補足としてまとめています。
##補足:Qtのインストール
Qtのダウンロードは以下の場所から行います。
https://www.qt.io/download
Commercial版とOpenSource版がありますが、今回はOpenSourceを選びます。>Go Open Source
![Ot1.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/5c3d2222-7892-6c74-d8da-996ba7157264.png)
Nextとskipでどんどん進みます。
![setup00.png](https://qiita-image-store.s3.amazonaws.com/0/151589/3b681715-f010-9b09-ed4e-35b4a088427d.png)
コンポーネントの選択ではQt 5.11.2/MSVC 2017 64-bitと
Tools/Qt Creator 4.7.2 CDB Debugger Support
を選択します。
![setup4.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/40e7fbd6-8615-c537-5184-b6abd4ed6486.png)
どんどん次に進みます。
![setup01.png](https://qiita-image-store.s3.amazonaws.com/0/151589/b50021bd-4d53-89c3-9e76-b2ebc7db9df4.png)
ここで、数十分待てばインストール完了です。
![setup9-1hlater.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/1a303bd7-b253-d93a-dc2c-cc3c8938df59.png)
##補足:Qtのビルド
インストールが完了したら、ビルドができるかを確認します。
新しいプロジェクトをクリックしてプロジェクトを作成します。
![newProject.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/be9b8893-7e3f-e010-0d7d-3811afee12a0.png)
ここは好みだとは思いますが、今回はQtウィジェットアプリケーションを選択します。
![newProject2.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/11c930b4-b8f0-ad12-85f2-22c6a736c8a4.png)
プロジェクトの名前とパスを入れます。
![newProject3.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/d31cceee-ebc4-b6bd-823f-4b62b94c37e8.png)
キットは先ほどインストールしたMSVC 2017 64-bitを選択します。
![newProject4.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/53f2e6ab-1bc0-7813-dff2-58355d64201b.png)
クラス情報とプロジェクト管理はそのままに、完了します。
![newProject5.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/991f6fab-3828-9ad0-d9f1-8ce7f2a78a41.png)
![newProject6.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/52dd951b-4e10-ed55-beb5-401fb24600cc.png)
main.cpp含むフォームが出るだけの初期状態プロジェクトが表示されました。
![newProject7.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/6bb5a254-1d78-5581-b62f-fa8c6af14802.png)
ビルドが走るかを確認します。
![build.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/00894109-d493-da6a-9042-d857db0cfcb9.png)
完了したら、実行させます
![run.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/6578d6a0-c6fe-28c8-b87e-ba0e96449706.png)
これで空っぽのフォームが出てくれば、ビルドは成功しています。
##補足:バージョンの確認
Houdiniエンジンを使用してアプリケーションを作成する場合、環境変数を間違ってしまいHoudiniエンジンの他のバージョンを指してしまう可能性があります。メモリにロードされるHoudini Engineのバージョンが、実際にコンパイルしたHoudini Engineのバージョンであることを確認する必要があります。*自明の場合は不要です。
確認方法として、HAPI_GetEnvInt(HAPI_EnvIntType int_type, int *value)を利用します。
Houdini 16.5.571の場合、HAPI_Version.hは
```C:HAPI_Version.h
#define HAPI_VERSION_HOUDINI_MAJOR 16
#define HAPI_VERSION_HOUDINI_MINOR 5
#define HAPI_VERSION_HOUDINI_BUILD 571
#define HAPI_VERSION_HOUDINI_ENGINE_MAJOR 3
#define HAPI_VERSION_HOUDINI_ENGINE_MINOR 1
#define HAPI_VERSION_HOUDINI_ENGINE_API 14
```
となり、メジャーバージョンが16、マイナーバージョンが5、ビルドバージョンが571。
HoudiniEngineのバージョンは3.1であることがわかります。
HAPI_Versionを他から参照する場合はHAPI_GetEnvInt関数で欲しいバージョンを引数として、値を取得します。
```C++:main.cpp
int Version_H_Major=0;
int Version_H_Minor=0;
int Version_H_Build=0;
HAPI_GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MAJOR,&Version_H_Major);
HAPI_GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MINOR,&Version_H_Minor);
HAPI_GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_BUILD,&Version_H_Build);
qDebug("Houdini %d. %d. %d",Version_H_Major,Version_H_Minor,Version_H_Build);
```
HoudiniEngineのバージョンによって、関数の引数が変わることがあります。
よって、昔のコードをそのまま持ってきても使えないケースが出てくることがあります。
その場合は、ドキュメントを参照して型に合った値を入れなければなりません。
特にHoudiniEngine2.0 からHoudiniEngine3.1に変わったときに大きな変更があったようです。
##補足:環境変数
Houdiniエンジンは、初期化時に次の環境変数を探します。
**HAPI_LICENSE_MODE:**
デフォルトではHoudiniEngineが最初にHoudiniEngineライセンスを検索し、見つからない場合は、Houdiniライセンスを取得します。どちらかのライセンスのみ取得する設定も可能です。
**HAPI_PYTHON_VERSION:**
特定のバージョンのPythonを強制的に使用するには、 '2.6'または '2.7'に設定します。これは、主にWindows上でホストPythonバージョンとHoudiniのPythonバージョンとの間で衝突する場合に便利です。
**HFS(HoudiniFileSystem):**
Houdiniインストールフォルダのパスを設定します。この変数は、HAPI_Initialize()またはHAPI_CreateInProcessSession()を呼び出すときにHAPI.dllを見つけるために使用されます。
##補足:デバッグ 
HoudiniEngineが文字列をホストアプリケーションに返す場合、HoudiniEngineは文字列を直接処理しません。その代わりに、呼び出し元関数は返す文字列に**HAPI_StringHandle**を渡します。
**HAPI_GetStringBufLength()**を呼び出して、文字列を保持するために必要なバッファのサイズを取得。適切なサイズのバッファを割り当てた後、**HAPI_GetString()**を呼び出しせば文字列を取得できます。
###戻り値HAPI_Resultとエラー文字列
HoudiniEngineのすべての関数は、**HAPI_Result**の値を返します。**HAPI_RESULT_SUCCESS**以外の戻り値は、何らかの形式の失敗を示します。
最初に**HAPI_GetStatusStringBufLength()**を呼び出し、次に**HAPI_GetStatusString()**を呼び出すことによって、問題の詳細を得ることができます。
今回は標準入出力を出すのに工夫がいるQtを利用したため割愛しましたが、SideFXのドキュメントではstd::stringでのデバッグを推奨しています。
まず関数呼び出しを次のようなマクロにラップして
```C++:関数呼び出し
#define ENSURE_SUCCESS( result ) \
if ( (result) != HAPI_RESULT_SUCCESS ) \
{ \
std::cout << "Failure at " << __FILE__ << ": " << __LINE__ << std::endl; \
std::cout << getLastError() << std::endl; \
exit( 1 ); \
}
```
HAPI_ResultがSUCCESSでないとき以下の関数を呼び出します。
```C++:最後のエラーを取得する
static std::string
getLastError()
{
//必要なバッファのサイズを取得するためのint型の変数宣言
int bufferLength;
//HAPI_GetStatusStringBufLength(inセッション,inステータスタイプ,in冗長,outバッファサイズ)
HAPI_GetStatusStringBufLength( nullptr,
HAPI_STATUS_CALL_RESULT,
HAPI_STATUSVERBOSITY_ERRORS,
&bufferLength );
//char*型を使うことで文字列変数bufを宣言 バッファサイズを代入
char * buffer = new char[ bufferLength ];
//HAPI_GetStatusString(inセッション,inステータスタイプ,out文字列の値,inバッファイサイズ)
HAPI_GetStatusString( nullptr, HAPI_STATUS_CALL_RESULT, buffer, bufferLength );
//char*型のbufから動的にサイズ変更可能な文字列resultに代入
std::string result( buffer );
//バッファを削除
delete [] buffer;
//バッファを削除
return result;
}
```
実際にHAPI_RESULTが知りたい関数などに対してENSURE_SUCCESSでエラー確認を行います。
```C++:ENSURE_SUCCESS
ENSURE_SUCCESS( HAPI_LoadAssetLibraryFromFile( &session, hdaFile, true, &assetLibId ) );
```
詳しく知りたい方は、SideFXドキュメント内のSmapleに詳しく乗っているので、そちらを参照してください。
[SideFXドキュメント内のSample](https://www.sidefx.com/docs/hengine/_h_a_p_i__full_source_samples__compiling_samples.html)
###BreakPoint
ではQtでデバッグする場合はどうするかというとを利用するのでBreakPointを利用するのが最も楽にデバッグが可能です。
コードの左を左クリックすることでBreakPointの作成が可能です。
![breakpoint.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/2232a8c5-c69a-51e6-4394-42b9e63a8a3a.png)
この状態でデバッグすると
![debugPNG.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/8cbab5ad-4b2b-39ac-1391-f041f535f3d1.png)
BreakPoint時のパラメータ参照が可能です。
![debug2.PNG](https://qiita-image-store.s3.amazonaws.com/0/151589/a13066b9-5137-02ea-8c65-aeacc90d5542.png)
##補足:その他のノードのパラメータ問い合わせ
ノードIDを取得した後は、HAPI_GetNodeInfo関数を利用してノード情報にアクセスする必要があります。
HAPI_GetNodeInfo関数の呼び出し時にHAPI_NodeIdを指定すると、HAPI_NodeInfo構造体を埋めることができます。
- アセットを取得する場合:**HAPI_GetAssetInfo()**を使用して**HAPI_AssetInfo**を取得。
- オブジェクトを取得する場合:**HAPI_GetObjectInfo()**を使用して**HAPI_ObjectInfo**を取得。
- ジオメトリを取得する場合:**HAPI_GetGeoInfo()**を使用して**HAPI_GeoInfo**を取得。
- マテリアルを取得する場合:**HAPI_GetMaterialInfo()**を使用して**HAPI_MaterialInfo**を取得。
**HAPI_NODEFLAGS_EDITABLE**、**HAPI_NODEFLAGS_NETWORK**、**HAPI_ComposeChildNodeList()**を使用して編集可能なノードネットワークのリストを問い合わせることができます。
デジタルアセットの場合通常はロックされていて編集できませんが、
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/ffca442f-2e2e-990c-ecbf-53baf30da288.png)
Operator Type Propertiesの設定でEditableNodesを指定することでパラメータが編集可能になります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/102517ea-036a-26ac-6cca-8e580f969147.png)
##補足:ライセンス
Houdiniエンジンは、これらのライセンスが見つかるまで、次の順序でこのライセンスをチェックします。
1)Houdini Engine
2)Houdini
3)Houdini FX
4)Houdini Engine Indie
5)Houdini Indie
**非営利のライセンス(つまり、Houdini Apprentice)はサポートされていません。**さらに、ティアリングは、HoudiniEngineでは許可されていません。たとえば、商用ライセンスを使用している間はエンジンでIndie(* lc)アセットを実行することはできません。
Indieアセットを実行するには、Indieライセンスをチェックアウトする必要があります。つまり、下位層ライセンスは、上位層ライセンスで作成されたアセットを実行できます。たとえば、Indieライセンスで動作するHoudiniEngineは、商用アセットを実行できます。**Indieライセンスの使用は、承認されたプラグイン(Unity、Unreal、Maya、Max、C4D)に限定されます。**
###ライセンスチェック
Houdiniエンジンは、以下の2パターンのAPIコール中に有効なライセンスを確認します。
ライブラリファイルのロード中:**Asset Library Files**
ノードの作成中:**Node Creation** および **Create Node**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/1f938c83-6a6b-df7c-da21-30e77cb330c7.png)
ライブラリファイルのロード中に、有効なライセンスが見つからない場合は、次のHAPI_Resultを取得できます。
| HAPI_Result| 内容|
|:-----------------|:------------------|
| HAPI_RESULT_NO_LICENSE_FOUND| ライセンスが見つかりませんでした。|
| HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND| サポートされていない非商用ライセンスのみが見つかりました。|
ノードの作成中に、次のHAPI_Resultのライセンスに関する情報を取得できます。
| HAPI_Result| 内容|
|:-----------------|:------------------|
| HAPI_RESULT_NO_LICENSE_FOUND| ライセンスが見つかりませんでした。|
| HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND|サポートされていない非商用ライセンスのみが見つかりました。|
| HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE|汚染している間は認められません、非商業アセットは商用ライセンスでは稼動できません。|
| HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE|汚染している間は認められません、非商業アセットは限定商業ライセンスでは稼動できません。|
| HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE |汚染している間は認められません、限定された商業アセットは商用ライセンスでは稼動できません。|
| HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN |Indieライセンスは、承認されたプラグインでのみ使用できます。|
#参考
SideFXがHoudiniEngineを紹介している動画 2013年にSIDEFXLabsで開発され発表されました。
[Introducing the Houdini Engine](https://vimeo.com/70073569)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/109a9db5-ebac-4ed9-6e58-291a31c353a2.png)
[HoudiniEngine紹介ページ](https://www.sidefx.com/products/houdini-engine/)
##HoudiniEngineAPI 
[SideFXドキュメント内のSample](https://www.sidefx.com/docs/hengine/_h_a_p_i__full_source_samples__compiling_samples.html)
以下の場所にあります。(見つけにくい!)
![here.png](https://qiita-image-store.s3.amazonaws.com/0/151589/81d3b928-8ca7-12d2-1753-164416a7d60a.png)
[@K240さんが作成したHoudiniEngineQt](https://github.com/K240/HoudiniEngineQt)
インストールしたディレクトリ内にもサンプルがあります。(見つけにくい!)
C:\Program Files\Side Effects Software\Houdini 17.0.372\engine\QtParameterUI
![image.png](https://qiita-image-store.s3.amazonaws.com/0/151589/5f3fb44e-ee91-c67a-f85f-ddd47127f0f7.png)
##UE4 
[SideFX紹介ページ](https://www.sidefx.com/products/houdini-engine/plug-ins/unreal-plug-in/)
[Houdini Engine for Unreal Documentation](https://www.sidefx.com/docs/unreal/)
[SideFX GitHub HoudiniEngineForUnreal](https://github.com/sideeffects/HoudiniEngineForUnreal)
[Houdini Engine for Unrealのインストール(Houdini Apprenticeユーザー向け)](http://miyahuji111.hatenablog.com/entry/2017/12/14/004823)
[Unreal EngineにHoudini Engieをインストールする](https://support.borndigital.co.jp/hc/ja/articles/360000260974-Unreal-Engine%E3%81%ABHoudini-Engie%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B)
##Maya
[SideFX紹介ページ](https://www.sidefx.com/products/houdini-engine/plug-ins/maya-plug-in/)
[Houdini Engine for Maya Documentation](https://www.sidefx.com/docs/maya/)
[SideFX GitHub HoudiniEngineForMaya](https://github.com/sideeffects/HoudiniEngineForUnreal)
2017年のAdventCalenderには@yuya_toriiさんがHoudiniEngine for Mayaについての記事を作成されています。
[Houdini Engine For Maya を調べてみる](https://qiita.com/yuya_torii/items/001642040a5354cd9f89)
@suzukiMYさんの記事
[Houdini Engine for Maya 16.5 – Test1](https://gamecgdrops.wordpress.com/2017/12/26/houdini-engine-for-maya-16-5-test1/)
##Unity
[SideFX紹介ページ](https://www.sidefx.com/products/houdini-engine/plug-ins/unity-plug-in/)
[Houdini Engine for Unity Documentation](https://www.sidefx.com/docs/unity/)
[SideFX GitHub HoudiniEngineForUnity](https://github.com/sideeffects/HoudiniEngineForUnity)
#価格
Houdini Engine Indieは2016年3月まで 99米ドルで提供していましたが、無償となりました。
インストールは以下の場所から可能です。
https://www.sidefx.com/buy/#houdini1
![indie.png](https://qiita-image-store.s3.amazonaws.com/0/151589/b46ea054-8ff9-5335-6d7a-011250277bc7.png)
ただし、IndieでないHoudini Engineはレンタルライセンスで購入する必要があります。
Workstation Licenses(ノードロック)の場合スタジオでのライセンスの使用は5ライセンスまでに制限されています。また、代理店での購入はそれぞれ最低オーダー数が存在します。25本以上の購入はアカウントマネージャーに連絡してください。
*価格は2018年12月時点のものです。
###SideFX
| 製品名 | 備考 |価格:1本|価格:5本|価格:10本|価格:25本|
|:-------|:-----------:|:---------------------|:----|:----|:----|:----|:----|
|HOUDINI ENGINE Indie |ノードロック 最大3本まで|FREE |
|HOUDINI ENGINE |ノードロック 最大5本まで|$499.00 USD|
|HOUDINI ENGINE (GAL:レンタル年単位)|フローティング|$795.00 USD|$3875.00 USD|$7500.00 USD|$16495.00 USD|
|HOUDINI ENGINE (GAL:レンタル月単位)|フローティング|$175.00 USD|$875.00 USD|$1750.00 USD|$4375.00 USD|
|HOUDINI ENGINE (GAL:レンタル週単位)|フローティング|$75.00 USD|$375.00 USD|$750.00 USD|$1875.00 USD|
###代理店 Indyzone
http://indyzone.jp/catalog/advanced_search_result.php?keywords=HOUDINI%20ENGINE
| 製品名 | 本体価格 | 備考 |オーダー数|
|:-------|:-----------:|:---------------------|:----|
|HOUDINI ENGINE [GAL] |¥9,936 |フローティング (7日間ライセンス)|最低10本から|
|HOUDINI ENGINE [GAL] | ¥19,872 |フローティング (14日間ライセンス) |最低5本から|
|HOUDINI ENGINE [GAL] | ¥22,356 |フローティング (30日間ライセンス) | 最低5本から|
|HOUDINI ENGINE [GAL] | ¥44,712| フローティング (60日間ライセンス) | 最低2本から|
|HOUDINI ENGINE [GAL] |¥67,068| フローティング (90日間ライセンス)|最低2本から|
|HOUDINI ENGINE [GAL] | ¥99,360 |フローティング (1年間ライセンス)|25本以上も可能|
|HOUDINI ENGINE [WS] |¥62,100|ノードロック (1年間ライセンス)|最大5本まで|
###代理店 Borndigital
https://www.borndigital.co.jp/software/houdini-engine.html
※価格は税別です。
| 製品名 | 本体価格 | 備考 |オーダー数|
|:--------|:---------:|:---------------|:----|
|Houdini Engine(GAL:レンタル週単位) | ¥9,200 | 7日間/フローティング |最低10本から|
|Houdini Engine(GAL:レンタル月単位) | ¥20,700 | 30日間/フローティング |最低5本から|
|Houdini Engine(GAL:レンタル年単位) | ¥92,000 | 1年間/フローティング |25本以上も可能|
|Houdini Engine(WS:レンタル年単位) | ¥57,500 | 1年間/ノードロック |最大5本まで|