1. jyouryuusui

    No comment

    jyouryuusui
Changes in body
Source | HTML | Preview

image.png

はじめに

この記事は HoudiniAdventCalender2018 2日目の記事です。
今年は謎多きHoudini Engineを調べてみました。

まずHoudini Engineとは何か。インストール時にUE4やMAYA用を選択できる画面が出るので、他のアプリからHoudiniのデジタルアセットにアクセスできる機能。
というのが一般的な認識ではないでしょうか。
image.png

API

image.png
しかしUE4やMAYAで利用しているのは、あくまでHoudini Engine APIの機能を利用したプラグインです。

最新のドキュメントHoudini Engine 3.2の概要にはこう書かれています。

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エンジンをクリーンアップします。

おおざっぱすぎてよくわかりません、、。

手を動かして理解する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

HAPIとlibHAPIL.libのパスを通す

HETest2.pro
#-------------------------------------------------
#
# Project created by QtCreator 2018-11-04T06:09:04
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = HETest2
TEMPLATE = app



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()を呼び出します。この関数によって必要なすべてのdllがロードされます。

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オプションをポインタ渡しします。ポインタ渡しがわからない方はこちら

main.cpp
#include "mainwindow.h"
#include <QApplication>

#include <HAPI.h>
#include <HAPI_API.h>
#include <HAPI_Common.h>

HAPI_Session Session;
HAPI_NodeId NodeId;
HAPI_CookOptions CookOptions;

int main(int argc, char *argv[])
{
    CookOptions = HAPI_CookOptions_Create();
    HAPI_CreateInProcessSession(&Session);
    HAPI_Initialize(&Session,
                    &CookOptions,
                    true,
                    -1,
                    nullptr,
                    nullptr,
                    nullptr,
                    nullptr,
                    nullptr);

    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入門の道のりは半分は達したようなものです。

*このステップでは、ライセンスの確認は行われません。アセットライブラリを読み込もうとしたときだけ、実際にライセンスをチェックします。 ライセンスについては補足:ライセンス にて
*dllが他で利用されている場合衝突が発生する可能性があります。

ノードIDの取得

ここで聞きなれない単語が出てきました。「ノードID」とは何でしょうか。

ノードID(HAPI_NodeId)の基礎

Houdiniのすべての関数とデータはノードとして表されます。HoudiniEngineでは、ノードはHAPI_NodeIdによって識別されます。

HAPI_CreateNode関数でbox作成

例えば、HAPI_CreateNode関数によってboxノードを追加できます。

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が渡した変数に格納される 次の処理に使うのはこいつ
)   
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"として作成できます。

ノードオペレータの名前の調べ方は2017年の@it_ksさんの「ノードっていくつあるんですか?」にあるpythonからのprint出力を使うことで調べることができます。

sopNodeTypeCategory
sopNodes = hou.sopNodeTypeCategory().nodeTypes()
print sorted( sopNodes.keys() )
objNodeTypeCategory
objNodes = hou.objNodeTypeCategory().nodeTypes()
print sorted( objNodes.keys() )

毎回pythonで調べるのが面倒!という場合は一度ノードを自分で作成して、その際のノード名から数字(下の図だと1)を抜いた文字(geo)を確認してください。
image.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ノードを作成しましょう。

boxと同階層にgridとcopyを作成
        // Check node_info.
        HAPI_NodeInfo box_node_info;
        result = HAPI_GetNodeInfo(&Session, sop_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

ノードの接続

ノードを接続するためにはHAPI_ConnectNodeInput関数を利用します。

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 の間でなければなりません。
)   
ノードの接続
        HAPI_ConnectNodeInput(&Session, copy_node_id, 0, box_node_id,0 );
        HAPI_ConnectNodeInput(&Session, copy_node_id, 1, grid_node_id,0 );

image.png

パラメータの取得

ここでgridのパラメータを覗いてみましょう。
パラメータの取得には段階を踏む必要があります。まずHAPI_GetParameters関数を利用してNodeInfo内に存在する複数のパラメータ群を取得した後、HAPI_GetParm〇〇〇Values関数で個別のパラメータを取り出します。
〇〇〇にはInt、Float、Stringが入ります。正確に言えばHAPI_ParmTypeに応じた型が入ります。

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_GetParametersで取得できるのは、様々なHAPI_ParmTypeが混ざった状態の配列です。HAPI_ParmTypeの値の型は、Int、Float、Stringに分類されます。例えばHAPI_ParmTypeがToggleでも値としてはInt型であるなど、都度そのパラメータがどんな型かを判定します必要があります。

そこで、Getを行う前に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

では、これからInt型のrowsと、Float型のsizeを取得していきます。
HAPI_GetParametersの内訳はこのようになっています。

image.png

あらかじめ、取得したいパラメータの名前、型がわかっている場合は直接値を取得できます。

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があります。

パラメータの値を取得
        int rows_parm_value;
        HAPI_GetParmIntValue(&Session,grid_node_id,"rows",0,&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);
        //sizex_parm_value=10.0
        //sizey_parm_value=10.0

次に紹介するのはHAPI_GetParmFloatValues関数です。ValueでなくValuesなので、vectorのパラメータのように2つ、3つ同時に配列として取得したい場合に利用します。

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文で読み込みます。その際に名前とパラメータを確認します。

全パラメータ名、パラメータ値の取得
        HAPI_NodeInfo grid_node_info;
        HAPI_GetNodeInfo( &Session, grid_node_id, &grid_node_info ) ;

        HAPI_ParmInfo * parmInfos = new HAPI_ParmInfo[ grid_node_info.parmCount ];
        HAPI_GetParameters( &Session, grid_node_id, parmInfos, 0, grid_node_info.parmCount ) ;

        for( int i = 0; i < grid_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);

            QString qstr = QString::fromStdString(buf);
            qDebug() << 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,grid_node_id, parmIntValues,parmInfos[ i ].intValuesIndex,parmIntCount ) ;

            for ( int v = 0; v < parmIntCount; ++v ){
                HAPI_ParmInfo parmInfo=parmInfos[ i ];
                int piv= parmIntValues[ v ];
                const QString qstr  = QString::number(piv);
                qDebug() << qstr ;
            }

            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,grid_node_id, parmFloatValues,parmInfos[ i ].floatValuesIndex,parmFloatCount ) ;

            for ( int v = 0; v < parmFloatCount; ++v ){
                float pfv= parmFloatValues[ v ];
                const QString qstr  = QString::number(pfv);
                qDebug() << qstr ;
            }
            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,grid_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関数を利用します。

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値
)
パラメータ値の設定
        HAPI_SetParmIntValue(&Session,grid_node_id,"rows",0,5);

rowsが初期値10であったので、5に上書きされました。
想定としては以下のようになるはずです。
rowdown.png

しかし、実際にhipファイルを開くとboxが1つあるだけです。
image.png

なぜかというと、RenderFlagがBoxについた状態のため、最終段のcopyまでcookされていないからです。
image.png

objで出力

ノードのCook

DisplayFlagやRenderFlagについては、HoudiniEngine側としてはあまり関係がありません。
あくまでこれらのFlagはHoudini内のGUIとして機能するからです。

HoudiniEngineから欲しい結果をcookする場合はHAPI_CookNode関数を利用します。

ただし、HAPI_CookNode関数は非同期のため、cook出来ていない状態で次の処理に行くと困ることがあります。
その場合は、cookが完了したことを確認して次に進みます。
そこでdo-while内でHAPI_GetStatus関数を利用してHAPI_StatusHAPI_Stateが適正な返り値となっているかどうか確認します。問題なければcookStatusはHAPI_STATE_READYである0となっているはずです。

HAPI_State
 enum 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の確認処理を実行します。そして、継続条件で偽になると脱出します。

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
)
main.cpp input_読み込み
       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);

この時点ではHoudiniにもとからあるboxノードを配置しただけの状態です。
つまりHoudini内で処理が完結しているため、外部と何も連携できていません(HoudiniEngineの意味がないですね)。
たとえば、別のアプリで作成したboxをHoudiniEngineに渡す場合どうすればいいでしょうか。

HAPI_CreateInputNode関数でジオメトリ入力受付

HAPI_CreateInputNode関数を利用することで、ジオメトリ入力を受け入れることができる単純なGeometrySOPノードを作成できます。これにより、Null SOPを持つダミーのOBJノードが作成され、ジオメトリSET APIを使用するジオメトリを設定できます。
その後、このノードをジオメトリ入力として他のノードに接続することができます。

HAPI_SaveHIPFile()を使用してHoudiniシーンを保存すると、このメソッドで作成されたノードは緑色になり、 "input"という名前で始まります。

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#"になります。#は数字です。
)   
main.cpp input_読み込み1
    HAPI_NodeId newNode;
    HAPI_CreateInputNode( &Session, &newNode, "Cube" );

HAPI_CreateInputNodeでinput_Cubeが出来ましたが、実態は何もありません。
なぜなら何も入力していないからです。
image.png
そこで、このNullにメッシュ情報を追加していきます。

main.cpp input_読み込み2
        // Creating the triangle vertices
        HAPI_PartInfo newNodePart = HAPI_PartInfo_Create();

また新しい単語が出てきました。「Part」とはなんでしょうか?パーツを直訳すると部品ですが、、

PartInfoの作成

Partsは、Houdiniのジオメトリ内のプリミティブグループに基づいて計算されます。
そして、Partsにはmesh/geometry/attributeデータが含まれています。

main.cpp input_読み込み3
        // 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します。

PartInfoにAttribute付与

次は、Attribute情報を付与します。Attributeを付与するにはHAPI_AddAttribute関数内で、ノードIDに加えてPartInfoが必要になります。

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
HAPI_AttributeOwner
HAPI_ATTROWNER_INVALID  
HAPI_ATTROWNER_VERTEX   
HAPI_ATTROWNER_POINT    
HAPI_ATTROWNER_PRIM     
HAPI_ATTROWNER_DETAIL 
HAPI_StorageType
HAPI_STORAGETYPE_INVALID    
HAPI_STORAGETYPE_INT    
HAPI_STORAGETYPE_INT64  
HAPI_STORAGETYPE_FLOAT  
HAPI_STORAGETYPE_FLOAT64    
HAPI_STORAGETYPE_STRING 
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 
)   
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_SetAttributeFloatDataの他に、HAPI_SetAttributeFloat64Data、HAPI_SetAttributeInt64Data、HAPI_SetAttributeIntData、HAPI_SetAttributeStringDataがあります。storageに応じて変更する必要があります。

main.cpp input_読み込み4
        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する必要があります。

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でなければなりません。
)
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でなければなりません。
)   
main.cpp input_読み込み5
        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 );

デジタルアセット

入力したboxとデジタルアセットを接続してみます。
そのために、まずはデジタルアセットの準備を行います。

今回はテストなのでsubdivideとmountainのみのシンプルなHDAを作成します。
asset0.png

外に出すパラメータとしてmountainのHeightとElement Sizeを置きます。
image.png

結果の確認
asset.png

HAPI_LoadAssetLibraryFromFile関数でotl・hda読み込み

アセットを読み込んでノードのインスタンス化をする場合は、HAPI_LoadAssetLibraryFromFileでotl、hdaを読み込み、boxと同様にHAPI_CreateNodeします。デジタルアセットがObject/かSop/かどうかは適宜変更が必要です。

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

ノードの接続

ノードを接続するためにはHAPI_ConnectNodeInput関数を利用します。

main.cpp
HAPI_ConnectNodeInput(&Session, hda_node_id, 0, newNode,0 );

実行すると3つのノードが作成されていることが確認できます。
image.png
HAPI_ConnectNodeInputによって、ObjectMergeが実行されています。
仮に、boxとboxをつなげた場合は、

image.png

アセットのパラメータ取得

Create, Delete, Connect Nodes

HAPI_ComposeChildNodeList()を使いHAPI_NodeIdを把握できれば、ノードネットワークの作成、削除、接続を開始できます。

まず、引数内の再帰をfalse設定にしたHAPI_ComposeChildNodeList()を使用して、ネットワーク内の存在を確認する必要があります。ノードネットワーク内のChildの数は、child_countで返されます。その後、HAPI_GetComposedChildNodeList()を使用してHAPI_NodeId配列を取得できます。

補足

ここでは、一連の流れで横道にそれるために省略した内容を補足としてまとめています。

補足:Qtのインストール

Qtのダウンロードは以下の場所から行います。
https://www.qt.io/download
Commercial版とOpenSource版がありますが、今回はOpenSourceを選びます。>Go Open Source
Ot1.PNG

Nextとsukipでどんどん進みます。
setup00.png

コンポーネントの選択ではQt 5.11.2/MSVC 2017 64-bitと
Tools/Qt Creator 4.7.2 CDB Debugger Support
を選択します。
setup4.PNG

どんどん次に進みます。
setup01.png
ここで、数十分待てばインストール完了です。
setup9-1hlater.PNG

補足:Qtのビルド

インストールが完了したら、ビルドができるかを確認します。
新しいプロジェクトをクリックしてプロジェクトを作成します。
newProject.PNG
ここは好みだとは思いますが、今回はQtウィジェットアプリケーションを選択します。
newProject2.PNG
プロジェクトの名前とパスを入れます。
newProject3.PNG
キットは先ほどインストールしたMSVC 2017 64-bitを選択します。
newProject4.PNG
クラス情報とプロジェクト管理はそのままに、完了します。
newProject5.PNG
newProject6.PNG
main.cpp含むフォームが出るだけの初期状態プロジェクトが表示されました。
newProject7.PNG
ビルドが走るかを確認します。
build.PNG
完了したら、実行させます
run.PNG

これで空っぽのフォームが出てくれば、ビルドは成功しています。

補足:バージョンの確認

Houdiniエンジンを使用してアプリケーションを作成する場合、環境変数を間違ってしまいHoudiniエンジンの他のバージョンを指してしまう可能性があります。メモリにロードされるHoudini Engineのバージョンが、実際にコンパイルしたHoudini Engineのバージョンであることを確認する必要があります。*自明の場合は不要です。
確認方法として、HAPI_GetEnvInt(HAPI_EnvIntType int_type, int *value)を利用します。

Houdini 16.5.571の場合、HAPI_Version.hは

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関数で欲しいバージョンを引数として、値を取得します。

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()を呼び出しせば文字列を取得できます。

戻りコードとエラー文字列

HoudiniEngineのすべての関数は、HAPI_Resultの値を返します。HAPI_RESULT_SUCCESS以外の戻り値は、何らかの形式の失敗を示します。
最初にHAPI_GetStatusStringBufLength()を呼び出し、次にHAPI_GetStatusString()を呼び出すことによって、問題の詳細を得ることができます。

最後のエラーを取得する関数
static std::string get_last_error()
{
    int buffer_length;/*必要なバッファのサイズを取得するためのint型の変数宣言*/ 
    /*HAPI_GetStatusStringBufLength(inセッション,inステータスタイプ,in冗長,outバッファサイズ)*/    
    HAPI_GetStatusStringBufLength(
        nullptr, HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS, &buffer_length );
    char * buf = new char[ buffer_length ];/*char*型を使うことで文字列変数bufを宣言 バッファサイズを代入*/ 
    /*HAPI_GetStatusString(inセッション,inステータスタイプ,out文字列の値,inバッファイサイズ)*/
    HAPI_GetStatusString( nullptr, HAPI_STATUS_CALL_RESULT, buf , buffer_length);
    std::string result( buf );/*char*型のbufから動的にサイズ変更可能な文字列resultに代入 */
    delete[] buf;/*バッファを削除*/
    return result;/*結果を返す*/
}

BreakPoint

しかし、今回はQtを利用するのでBreakPointを利用するのが最も楽にデバッグが可能です。
コードの左を左クリックすることでBreakPointの作成が可能です。
breakpoint.PNG

この状態でデバッグすると
debugPNG.PNG
BreakPoint時のパラメータ参照が可能です。

debug2.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を取得。

デジタルアセットの場合通常はロックされていて編集できませんが、Operator Type Propertiesの設定でパラメータが編集可能になります。
HAPI_NODEFLAGS_EDITABLEHAPI_NODEFLAGS_NETWORKHAPI_ComposeChildNodeList()を使用して編集可能なノードネットワークのリストを問い合わせることができます。

デジタルアセット内でのHAPI_NODEFLAGS_EDITABLE

基本的にHDAはロックされるので中のノードの編集は Allow editing of contentsを行わないとEditできません。
image.png

EditableNodesを指定することで編集可能な状態となります。
image.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

ライブラリファイルのロード中に、有効なライセンスが見つからない場合は、次の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
image.png

HoudiniEngine紹介ページ

HoudiniEngineAPI 

SideFXドキュメント内のSample
以下の場所にあります。(見つけにくい!)
here.png

@K240さんが作成したHoudiniEngineQt

インストールしたディレクトリ内にもサンプルがあります。(見つけにくい!)
C:\Program Files\Side Effects Software\Houdini 17.0.372\engine\QtParameterUI
image.png

UE4 

SideFX紹介ページ
Houdini Engine for Unreal Documentation
SideFX GitHub HoudiniEngineForUnreal

Houdini Engine for Unrealのインストール(Houdini Apprenticeユーザー向け)
Unreal EngineにHoudini Engieをインストールする

Maya

SideFX紹介ページ
Houdini Engine for Maya Documentation
SideFX GitHub HoudiniEngineForMaya

2017年のAdventCalenderには@yuya_toriiさんがHoudiniEngine for Mayaについての記事を作成されています。
Houdini Engine For Maya を調べてみる

@suzukiMYさんの記事
Houdini Engine for Maya 16.5 – Test1

Unity

SideFX紹介ページ
Houdini Engine for Unity Documentation
SideFX GitHub HoudiniEngineForUnity

価格

Houdini Engine Indieは2016年3月まで 99米ドルで提供していましたが、無償となりました。
インストールは以下の場所から可能です。
https://www.sidefx.com/buy/#houdini1
indie.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本まで