Houdiniプラグイン開発備忘録4 ~ カスタムSOPにカスタムパラメータを追加する編

今回は、カスタムノードにカスタムパラメータを追加してみます


VerbとProtoヘッダファイル

 最近のHDKでは、SOPノード作成にVerbという仕組みを使うことが多いようです。
 Verbというのは、かなりざっくりいうと、SOPの振る舞いを定義する際、ジオメトリへの処理と、それ以外のパラメータ操作などの処理を分離する仕組みです。

[参考]プログラム的にVerb(動詞)を使ったジオメトリ

 Verbを利用してSOPを作成する際、これまで何度か言及していた、「Protoヘッダファイル」をビルド手順の中で生成し利用することになります。


Protoヘッダファイルの主な役割

 Protoヘッダファイルには、ジオメトリ処理以外の実装が自動的に記述されます。

※これは現時点での理解なので間違っているかもしれませんが、この仕組みにより、開発者は面倒なパラメータとジオメトリ処理をつなぐ実装を省略し、主にジオメトリ操作に関する処理に注力すれば良くなります。

 主なProtoヘッダファイルの役割は以下のようなものになります。

  1. PRM_Templateを生成する
  2. パラメータの操作を簡単化する関数群を自動生成する

Protoヘッダファイルの作り方

 Protoヘッダファイルは何もしなくても勝手に作成されるわけではなく、ソースコードの中にそのノードがどのようなパラメータを持っているかを示す「Dialog Script」を記述、または「*.ds形式のファイル」として用意して、PRM_TemplateBuilderに与える必要があります。

 このDialog Scriptは、ソースコード中で *theDsFile に記述します。

static const char *theDsFile = R"THEDSFILE(
・・・
)THEDSFILE";
//SOP_Starの例

static const char *theDsFile = R"THEDSFILE(
{
    name        parameters
    parm {
        name    "divs"      // Internal parameter name
        label   "Divisions" // Descriptive parameter name for user interface
        type    integer
        default { "5" }     // Default for this parameter on new nodes
        range   { 2! 50 }   // The value is prevented from going below 2 at all.
                            // The UI slider goes up to 50, but the value can go higher.
        export  all         // This makes the parameter show up in the toolbox
                            // above the viewport when it's in the node's state.
    }
    parm {
        name    "rad"
        label   "Radius"
        type    vector2
        size    2           // 2 components in a vector2
        default { "1" "0.3" } // Outside and inside radius defaults
    }
    parm {
        name    "nradius"
        label   "Allow Negative Radius"
        type    toggle
        default { "0" }
    }
    parm {
        name    "t"
        label   "Center"
        type    vector
        size    3           // 3 components in a vector
        default { "0" "0" "0" }
    }
    parm {
        name    "orient"
        label   "Orientation"
        type    ordinal
        default { "0" }     // Default to first entry in menu, "xy"
        menu    {
            "xy"    "XY Plane"
            "yz"    "YZ Plane"
            "zx"    "ZX Plane"
        }
    }
}
)THEDSFILE";

Dialog Scriptを生成する

 頑張って調べてみましたが、この記事を執筆している時点では、Dialog Scriptに関するリファレンスマニュアルのようなものを見つけることは出来ませんでした。
 そのかわり、リファレンスに頼ってフルスクラッチで書かなくとも、簡単にDialog Scriptを生成する方法があったので紹介します。

 やり方は、概ね以下の手順になります。

  1. パラメータ定義を抽出するためHDAを作成
  2. HDAに、カスタムノードに持たせたいパラメータを作成
  3. HDAのhou.ParmTemplateGroup.asDialogScript()でDialog Scriptのコードを取得
# Dialog Scriptを出力するサンプル
hda_node = hou.node("/to/hda/node/path")
hda_type = hda_node.type()
hda_definition = hda_type.definition()
hda_parm_template_group = hda_definition.parmTemplateGroup()
hda_dialog_script = hda_parm_template_group.asDialogScript()
print(hda_dialog_script)

 これにより、そのHDAに定義されたパラメータ定義が、Dialog Scriptの形式で取得できます。

 その後、ソースコードの*theDsFileに、取得したコードをそのままコピペするなどして与えた後、いつも通りの手順でビルドするだけです。


テスト

 以下は、HDAとして標準で用意されているColor SOPからDialog Scriptを頂いて、カスタムSOPにそのまま持たせてみたものです。


以上、カスタムSOPにカスタムパラメータを追加する方法でした。
なにか間違いや、不明な点などあれば、コメントいただけると嬉しいです。

Houdiniプラグイン開発備忘録3 ~ 最小構成のテンプレを書いてみる編

最近、どうしても自作したいSOPがあり、Windows環境でのHoudiniのプラグイン開発を少しずつ学び始めたので、忘れないようにメモしていこうと思います。

今回は、SOP_Starのサンプルコードを参考にしながら、全く何の処理もパラメータも持たない、空っぽのSOPを作る最小構成のコードを書いてみました。

が、事細かにコードの説明を書いてもとりとめが無くなりすぎて、読むに耐えない感じになり非常に微妙だったので、簡単な解説付きのテンプレートコードを転載するに留めます。

いくつか補足が必要そうな箇所もあるので、後でメモを書くかも。

※コメント中の

TODO:は要編集の箇所
NOTE:はメモ

VSCodeでTODO Highlightなどを使うと見やすいかも

名前規則

サンプルを見る限り、以下のようなルールで各要素に名付けを行うのが通例っぽいです
以下は、templateというラベル名のSOPの場合

■各ファイル
SOP_Template.cpp
SOP_Template.hpp
SOP_Template.proto.h

■コード内の各要素
SOP_Template         カスタムSOPクラス名
SOP_TemplateVerb     カスタムNodeVerbクラス名
SOP_TemplateParms    カスタムSOPのパラメータテンプレートクラス名

SOP_Template.hpp(.h)

最小構成のヘッダファイルは以下のようなものになりそう。

// TODO: インクルードガードはカスタムSOPクラス名に合わせ指定
#ifndef __SOP_Template_h__
#define __SOP_Template_h__

// NOTE: 編集不要
#include <SOP/SOP_Node.h>
#include <UT/UT_StringHolder.h>

// TODO: ネームスペースを書き換える
namespace SOP_TEMPLATE
{
    // NOTE: ここではSOP_Nodeクラスを継承し、カスタムのSOPクラスを宣言する
    // TODO: カスタムSOPクラスを指定
    class SOP_Template : public SOP_Node
    {
    public:
        static OP_Node *myConstructor(OP_Network *net, const char *name, OP_Operator *op)
        {
            // NOTE: 自身のインスタンスを返すようにする
            // TODO: カスタムSOPクラスを指定
            return new SOP_Template(net, name, op);
        }

        // NOTE: 以下は編集不要
        static PRM_Template *buildTemplates();                 // パラメータテンプレートクラスを返す
        virtual const SOP_NodeVerb *cookVerb() const override; // Verbの仕組みで処理を行うメソッド
        static const UT_StringHolder theSOPTypeName;           // ノードタイプの内部名を格納する変数

    protected:
        // NOTE: コンストラクタを定義
        // TODO: カスタムSOPクラス名を指定
        SOP_Template(OP_Network *net, const char *name, OP_Operator *op) : SOP_Node(net, name, op)
        {
            mySopFlags.setManagesDataIDs(true);
        }

        // NOTE: デストラクタを定義
        // TODO: カスタムSOPクラス名を指定
        virtual ~SOP_Template() {}

        // NOTE: 以下は編集不要
        virtual OP_ERROR cookMySop(OP_Context &context) override
        {
            return cookMyselfAsVerb(context);
        }
    };
} // namespace SOP_TEMPLATE

#endif

SOP_Template.cpp(.C)

最小構成のソースコードは以下のようになりそう。

// NOTE: このテンプレはprotoヘッダファイルを使用する前提のもの
// TODO: インクルードするファイル名を書き換える
#include "SOP_Template.hpp"
#include "SOP_Template.proto.h" // protoヘッダを使うことが前提

// NOTE: 最低限必要なヘッダファイルのインクルード
#include <UT/UT_StringHolder.h>
#include <UT/UT_DSOVersion.h>
#include <OP/OP_Operator.h>
#include <OP/OP_OperatorTable.h>
#include <PRM/PRM_TemplateBuilder.h>


// TODO: 任意のネームスペースを指定
using namespace SOP_TEMPLATE;

// TODO: カスタムSOPクラスを指定
// TODO: 任意の内部名を指定
const UT_StringHolder SOP_Template::theSOPTypeName("template"_sh);

// NOTE: プラグインロード時にOP_OperatorTableにカスタムSOPを登録する
void newSopOperator(OP_OperatorTable *table)
{
    table->addOperator(new OP_Operator(
        // TODO: 必要に応じて書き換える
        SOP_Template::theSOPTypeName,   // SOPの内部名
        "template",                     // SOPのラベル名
        SOP_Template::myConstructor,    // 自身のインスタンスを返すメソッド
        SOP_Template::buildTemplates(), // パラメータテンプレートを指定
        0,                              // 最低限必要な入力数
        0,                              // 入力プラグの数
        nullptr,                        // Custom local variables (none)
        OP_FLAG_GENERATOR));            // Flag it as generator
}

// NOTE: SOPに持たせるパラメータを定義する
// TODO: .dsファイル、または.dsファイルと同様のルールで書かれたraw文字列を代入する
static const char *theDsFile = R"THEDSFILE(
{
        name        parameters
}
)THEDSFILE";

// NOTE: *theDsFile で記述された内容をもとにパラメータテンプレートを生成する
// TODO: カスタムSOPクラスを指定
// TODO: 正しいcppファイル名に書き換える
PRM_Template *SOP_Template::buildTemplates()
{
    static PRM_TemplateBuilder templ("SOP_Template.cpp"_sh, theDsFile);
    return templ.templates();
}

// NOTE: Verbの仕組みを使うため、カスタムNodeVerbクラスを宣言
// TODO: カスタムNodeVerbクラス名を指定
class SOP_TemplateVerb : public SOP_NodeVerb
{
public:
    // NOTE: デフォルトコンストラクタとデストラクタ
    // TODO: カスタムNodeVerbクラス名を指定
    SOP_TemplateVerb() {}
    virtual ~SOP_TemplateVerb() {}

    // TODO: protoヘッダで定義されているパラメータテンプレートクラスを指定
    virtual SOP_NodeParms *allocParms() const
    {
        return new SOP_TemplateParms();
    }

    // NOTE: theVerbメンバ変数の宣言
    // TODO: カスタムNodeVerbクラスを指定
    static const SOP_NodeVerb::Register<SOP_TemplateVerb> theVerb;

    virtual UT_StringHolder name() const
    {
        // TODO: カスタムSOPクラスを指定
        return SOP_Template::theSOPTypeName;
    }

    // NOTE: 編集不要
    virtual CookMode cookMode(const SOP_NodeParms *parms) const
    {
        return COOK_GENERIC;
    }

    // NOTE: 編集不要
    virtual void cook(const CookParms &cookparms) const;
};

// NOTE: 静的なメンバ変数の定義はクラス定義の外に書く
// TODO: カスタムNodeVerbクラスを指定
const SOP_NodeVerb::Register<SOP_TemplateVerb> SOP_TemplateVerb::theVerb;

// TODO: カスタムSOPクラスを指定
const SOP_NodeVerb *SOP_Template::cookVerb() const
{
    return SOP_TemplateVerb::theVerb.get();
}

// NOTE: SOPのメイン処理 Verbを使う場合はSOP_NodeVerbのcookに実際の処理を委譲する
// TODO: カスタムNodeVerbクラスを指定
void SOP_TemplateVerb::cook(const SOP_NodeVerb::CookParms &cookparms) const
{
    // TODO: ノードのメイン処理はここに記述する
}

CmakeLists.txt

CMakeLists.txtは、自分の環境ではだいぶいじってしまっているため、サンプルを少し改変しただけの状態ではちゃんとテストできてないので、動作に若干自信なし。
もし実践してみたい方がいれば、と思い、参考程度にコードを載せてみますが、間違ってたらすみません。(大まかには合ってるはずですが)

CMakeLists.txtファイルは、サンプルを以下のような形で編集し、ソースコードと同じフォルダに置けば、以前の説明と同じ手順でビルドできると思われます。

# NOTE: 変更不要 対応するcmakeの最小バージョン
cmake_minimum_required( VERSION 3.6 )

# NOTE: sln内に作成するプロジェクト名
# TODO: 任意の名前を指定。カスタムSOPクラス名が良さそう
project(SOP_Template)

# NOTE: 変更不要 cmakeファイル検索パスを追加
list( APPEND CMAKE_PREFIX_PATH "$ENV{HFS}/toolkit/cmake" )

# NOTE: Houdiniが用意しているcmake関数などを含むパッケージを読みこむ
find_package( Houdini REQUIRED )

# NOTE: ライブラリ名変数にカスタムSOPクラス名の文字列を代入しておく
# TODO: ライブラリ名を変更SOP_TemplateのようにカスタムSOPクラス名にするとよい
set(library_name SOP_Template)

# NOTE: ここの指定でprotoヘッダが作られるようになる
# TODO: ソースコードファイル名(.c/.cpp)を記述
# NOTE: 初投稿で間違いがあったので修正した
houdini_generate_proto_headers( FILES SOP_Template.cpp )

# NOTE: ライブラリに追加するファイルを記述
# TODO: 必要なcpp/hppファイルを羅列する
add_library( ${library_name} SHARED
    SOP_Template.cpp
    SOP_Template.hpp
)

# NOTE: 変更不要
target_link_libraries( ${library_name} Houdini )

# NOTE: 変更不要
target_include_directories( ${library_name} PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}
)

# NOTE: 変更不要
# TODO: 必要に応じてオプションを追加
houdini_configure_target( ${library_name} )

Houdini – Python(HOM)によるパラメータ操作

 前回は、HOMによりノードを選択する操作に関する記事を書きました。
 ノードを探して選択するだけではあまりにも寂しいので、今回はそれに引き続き、ノードのパラメータを操作する部分に関するメモを書き残したいと思います。
 Houdiniでは、ノードパラメータへの操作パスが幾通りも存在しており、とても柔軟な作りになっていますが、今回は、自分がよくやる方法を取り上げます。
「もっと便利な方法があるよー」などの情報があればぜひ教えてください。
 ここで紹介する各種メソッドはあくまでも一例で、実際には更に多くのメソッドが用意されているので、ぜひマニュアルも参照してください。
http://www.sidefx.com/docs/houdini/hom/hou/Node.html
http://www.sidefx.com/docs/houdini/hom/hou/Parm.html
http://www.sidefx.com/docs/houdini/hom/hou/ParmTuple.html


パラメータ名の確認

 まず最初に、パラメータへアクセスする際はパラメータ名が重要になるので、名前の確認方法から。

Parameter Spread Sheetで確認

 Parameter Spread Sheetで、ノードとパラメータをツリー表示で確認できる。
 各項目名の右端にある、カッコで囲まれた名前が実際にアクセスする際に使用するパラメータ名。パラメータにはUniform Scaleのような単一の値を持つパラメータと、位置や回転など、複数の値をひとまとめにして持つ配列パラメータがある。
 下図を例にすると、Axis Divisionsという「ラベル」が付けられたパラメータは、実際の名前が「divrate」であり、3つの子要素(divratex/y/z)を持っている配列パラメータである事がわかる。

ドラッグ&ドロップで確認


 Python Source Editorに、パラメータラベルをドラッグ&ドロップすることで正しい配列パラメータのパスと名前が確認できる。

ポップアップウインドウで確認

 Parametersパネルなどでパラメータ名ラベルの上にマウスカーソルを置き、少し待つと、パラメータ情報がポップアップで表示される。この中の、Parametersの部分にある文字列が、このノードにおけるパラメータの正式な名前になる。

↑↑↑
 この場合、tx ty tz がパラメータ名であると確認できるが、あくまでも配列要素の単一パラメータ名のみがわかる。
 正確な親の配列パラメータ名が知りたければ、Parameter Spread Sheetを確認するなど一手間必要。(大体の場合、配列要素パラメータ名の末尾を削ったものが配列パラメータ名になっているっぽい)


おおまかなパラメータアクセスの方法

パラメータへのアクセス方法

  1. パスを指定して直接パラメータオブジェクト(hou.Parm)へアクセス
  2. 任意のノードのパラメータオブジェクト(hou.Parm)へ、名前を指定してアクセス

パラメータの操作方法

  1. ノードオブジェクト(hou.Node)を直接操作
  2. パラメータオブジェクト(hou.Parm)を通して操作

単一パラメータオブジェクトの取得

import hou

# パラメータオブジェクトを直接取得
parm_direct = hou.parm('parm_path')

# ノードオブジェクトからパラメータ名を使用してパラメータオブジェクトを取得 
node = hou.node("node_path")
parm_from_node = node.parm('parm_name')

配列パラメータオブジェクトの取得

 translateやrotateのような複数要素で構成されるパラメータは、配列パラメータとしてまとめて各要素へアクセスすることができる。
 基本的に、これは単一のパラメータへのアクセスと同じように行うが、その際は、parmTupleやevalParmTupleといった配列パラメータを扱う専用のメソッドを使う。

import hou

# 配列パラメータオブジェクトの子要素配列を取得
tupleParm_direct = hou.parmTuple('tupleParm_path')

# 配列パラメータの値を取得
# ノードオブジェクトをつかまえておく
node = hou.node("node_path")
tupleParm_from_node = node.parmTuple('tupleParm_name')

# 子要素へは、tupleParmの配列要素へアクセスして行う
tupleParm_x = tupleParm_from_node[0]
tupleParm_y = tupleParm_from_node[1]
tupleParm_z = tupleParm_from_node[2]

単一パラメータの値を取得する

 eval~と名づけられたメソッドを使う。
 型を指定して型変換しながら値を取得するメソッドや、時間指定で値を取得するメソッド、それらを組み合わせて取得するメソッドなどもある。

import hou

# 単一パラメータオブジェクトから現在の値を取得
parm = hou.parm("parm_path")
parmValue = parm.eval()

# 型を指定ながら現在の値を取得
parmIntValue = parm.evalAsInt()
parmFloatValue = parm.evalAsFloat()
parmNodeValue = parm.evalAsNode()

# 指定時間での値を取得
parmAtFrameValue = parm.evalAtFrame( FRAME_NUMBER )

# 型指定と時間指定の組み合わせ
parmIntAtFrameValue = parm.evalAsIntAtFrame( FRAME_NUMBER )
parmFloatAtFrameValue = parm.evalAsFloatAtFrame( FRAME_NUMBER )

配列パラメータの値を取得する

 配列パラメータの値取得も、単一パラメータとほぼ同様。

import hou

# 配列パラメータオブジェクトから現在の値を取得
tupleParm = hou.parmTuple("parm_path")
tupleParmValue = tupleParm.eval()

# 型を指定ながら現在の値を取得
parmIntValues = tupleParm.evalAsInts()
parmFloatValues = tupleParm.evalAsFloats()
parmNodeValues = tupleParm.evalAsNodes()

# 指定時間での値を取得
parmAtTimeValue = parm.evalAtTime( TIME )
parmAtFrameValue = parm.evalAtFrame( FRAME_NUMBER )

# 型指定と時間指定の組み合わせ
parmIntAtFrameValues = parm.evalAsIntsAtFrame( FRAME_NUMBER )
parmFloatAtFrameValues = parm.evalAsFloatsAtFrame( FRAME_NUMBER )

パラメータをセットする

 値のセットは、set系メソッドを使う。
 事前に組み立てたパラメータ辞書をsetParmsメソッドに与え、一括で値をセットするのがとても楽なのでおすすめ。
 その際、setParmsメソッドでは配列パラメータ名は認識できないので、各要素を個別の単一パラメータとして辞書に値を用意します。

import hou

# 単一パラメータに値をセットする
parm = hou.parm('parm_path')
parm.set( value )

# 配列パラメータにシンプルな値の配列をセットする
tupleParm = hou.parmTuple('parm_path')
tupleParm.set( (value1, value2, value3, ...) )

# ノードオブジェクトを使い、複数のパラメータをまとめてセットする
# セットしたいパラメータを辞書に溜め込み、setParms()に与える
parmDict = { 'parm_name1':value1, 'parm_name2':value2, ... }
node = hou.node('node_path')
node.setParms(parmDict)

 以上、何か不明点などあれば気軽にご質問ください。
 間違いがあればツッコミも大歓迎です。

Houdini – Python(HOM)によるノード選択(改題)

 あるシーンを開いた時、膨大なオブジェクト数とグループ数により階層が深く、なおかつすべてのノードに命名規則が全く見当たらないという、ツール作成者視点でシーンを眺めた時、ついつい汚い床の上をのたうち回りたくなるような状況は往々にしてあることなのですが、そのような状況下でのHoudiniの選択操作は、他のDCCツールよりも遅れを取っている気がします。

 例えば、ビューポートで選択しているすべてのノードの親を簡単にまとめて再選択したかったり、同様に、選択アイテムのすべての下流ノードをまとめて選択したかったりと言った状況です。

 ノードを右クリックして表示されるポップアップメニューから、Inputs/Outputsサブメニューを駆使してみるも、単一の選択アイテムのみがノード検索の起点になるので、複数のノードツリーをまとめて選択できなかったりします。

 このように、意外とHoudiniデフォルトの選択機能に痒いところが多かったので、選択補助スクリプトを書いてみました。

 ついでと言ってしまうと何だか申し訳なく感じてしまうのですが、簡単なメモを書き残したいと思います。


基本的なノード検索

import hou
nodeObject = hou.node("node_path")
import hou
selectedItems = hou.selectedItems()

ノードの親子関係

import hou
node = hou.node('node_path')

# parent()は自身を含む外側のノードの取得
outerNode = node.parent()
print( outerNode )

# children()は自身が含む内側のノードリストを取得
innerNodes = node.children()
print( innerNodes )
import hou
node = hou.node('node_path')

# inputs()は自身へ入力している直接の上流ノードリストの取得
inputNodes = node.inputs()
print( inputNodes )

# outputs()は自身が直接出力しているノードリストを取得
outputNodes = node.outputs()
print( outputNodes )

# inputAncestors()は直系の祖先をまとめて取得
ansectorNodes = node.inputAncestors()
print( ansectorNodes )

かなり基本的なところではこんな感じ。
他にもたくさんあるので、その他のメソッドは要マニュアル参照。

出力の全子孫を取得するメソッドがパット見見つからない感じだったので、再帰的に子孫をたどる関数を作ってみる。

import hou

def getDescendents(node, *args, **kwargs):
    """
    DESCRIPTION : 引数で与えたノードから再帰的に子孫を辿って返す
      ARGUMENTS : node : 探索を開始するノード
         RETURN : descendents : 見つかった子孫ノードリスト
    """
    descendents = []

    direct_outputs = node.outputs()
    if direct_outputs:
        for direct_output in direct_outputs:
            descendents += [direct_output]
            descendents += getDescendents(direct_output)
   
    return descendents
    
node = hou.node('node_path')
descendents = getDescendents(node)
for descendent in descendents:
    print( descendent )

選択操作

import hou
node = hou.node("node_path")

# 選択 setSelectedメソッドにブール値をセット 1で選択0で非選択
node.setSelected(1)

# 選択解除
node.setSelected(0)

 上記を組み合わせて、現在のノードから1つ上流のノードを探し、再度全下流ノードを探して全選択を行うなどの便利なコマンドを作成できます。

Interactive Houdini

Houdini Advent Calendar 2016 18日目に投稿させていただいた記事です


■はじめに

Houdiniには外部デバイスからインタラクティブにデータを入力する機能があります。

今回は、その機能を利用して外部に接続した各種フィジカルデバイスの操作から、Houdiniへデータを入力する方法を色々と書いてみたいと思います。

手頃に試せそうな例として、ここでは以下のデバイスからの入力を試してみます。

・マウス
・ペンタブレット

・キーボード
・MIDIコントローラ


■環境

・Windows10 64bit
・Houdini Indie 15.5.673


■まずは今回取り上げるCHOPに共通する予備知識

今回取り上げるCHOPは以下の通りです

Mouse CHOP
Keyboard CHOP
MIDI In CHOP

・CHOPデータの在り処について

CHOPのデータは、「CHOPチャンネル」が持っています。
CHOPチャンネルのデータへアクセスする際は、CHOPノードだけではなく、CHOPノード内にあるCHOPチャンネル名まで指定する必要があります。

・CHOPデータの詳細確認

CHOPノードを中ボタンでクリックするとCHOP内のチャンネル数や、開始/終了フレーム、サンプルレートなどの詳細情報が表示されます。

赤く囲んだ部分には、このCHOPに含まれるチャンネル名と値が表示されています。
この例の場合、ステレオのオーディオファイルを読み込んでいるので、このCHOPには左右2チャンネルの波形データが含まれていることがわかります。
そして、チャンネル名は左右でそれぞれ chan0 chan1 が割り当てられています。

・CHOPチャンネルへのパス

CHOPチャンネルへのパスは、CHOPノードへのパス+CHOPチャンネル名で指定します

~/chop_name/channel_name

上記の例で言うと、WAV_FILE(File CHOP) の chan0 チャンネルへのフルパスは以下のようになります

/obj/driver_chopnet/WAV_FILE/chan0

・CHOPチャンネルへのアクセス

CHOPチャンネルの各情報は、chop系エクスプレッション関数を使って取得します。
これらの詳細はマニュアルを参照してください。

  • chop : 現在時間でCHOPチャンネルの値を取得
  • chope : CHOPチャンネルの終了インデクスを取得
  • chopf : 指定フレームでCHOPチャンネルの値を取得
  • chopi : 指定サンプルポイントでCHOPチャンネルの値を取得
  • chopl : CHOPチャンネルの長さをサンプル数で取得
  • chopn : CHOP内のチャンネル数を取得
  • chopr : CHOPのサンプルレートを取得
  • chops : CHOPチャンネルの開始インデクスを取得
  • chopt : 指定した時間でのCHOPチャンネルの値を取得
  • chopcf : 指定フレーム、指定チャンネルインデクスでCHOPチャンネルの値を取得
  • chopci : 指定サンプルポイント、指定チャンネルインデクスでCHOPチャンネルの値を取得
  • chopct : 指定時間、指定チャンネルインデクスでCHOPチャンネルの値を取得
  • chopstr : 現在時間でのCHOPチャンネルの文字列データを取得

先の例で登場したCHOPチャンネルが持つ現在時間のデータが欲しい場合は、データを呼び込むパラメータボックス内で以下のエクスプレッションを使用します。

chop( “/obj/driver_chopnet/WAV_FILE/chan0” )

・CHOPチャンネルの波形データを確認する

CHOPの波形データは、CHOPノードの右端にある[Graphフラグ]をオンにすることで、Motion FX Viewに表示されます。

Motion FX View

・CHOPデータのサンプルレート

CHOPでは様々な波形データを扱うことができます。
波形データは自由に再生時のサンプルレート(一秒あたりのデータ数)を指定できるようになっています。

サンプルレートの高いオーディオデータを30fps設定のシーンでアニメーションカーブとして使う場合、サンプルレートを30にリサンプルしてから使用するのが負荷軽減につながって良いのではないかと思います。

サンプルレートは、File CHOPのChannelタブ内またはResample CHOPにあるSample Rateパラメータで指定します。

・上で読み込んだWAVデータの波形を30fps化したところ

・外部から入力されたデータのレコーディング

外部から入力されたデータを記録しておき、再生することができます。
MIDIデータを外部から入力するMIDI In CHOPなどは、入力されたMIDIデータを自分自身に記録する事ができるようになっていますが、Record CHOPを下流に接続してそちらで記録するほうがデータの取り回しの面で良いと思います。

レコーディングは、データ入力CHOPの下流に Record CHOPを接続し、Record パラメータをオンにした状態で行います。


■というわけで、早速やってみよう

CHOP入力→レコーディング→再生の流れは、今回紹介するどのCHOPでも変わらないので、まずはMouse CHOPの簡単なチュートリアルで作業の流れを掴んでみます。


■Mouse CHOPチュートリアル

1・CHOPコンテキストにCHOP Networkを作成します

2・CHOP Network の中に Mouse CHOPを作ります

3・Mouse CHOPのGraphフラグをオンにしたあと、Motion FX Viewを表示します

4・Mouse CHOPの下にRecord CHOPを接続します

5・Record CHOPのRecordパラメータをOnにし、シーンを再生します

この時、シーンのリアルタイム再生モードをオンにしておくと実際のフレームレートでレコーディングできます。

また、再生モードを Play Onceにしておくことでリピート再生をオフにし、うっかり開始フレーム付近を書き換えてしまうのを防げます。

6・マウスカーソルを自由に動かし、グラフが描かれていくのを確認します


各カーブの上には、対応するカラーでチャンネル名が表示されています。(geo1:txgeo1:ty

7・記録できたら、Record CHOPのRecordをOffにします

8・/obj/に新たにBoxオブジェクトを作ります

9・boxオブジェクトのTranslateX/Yにエクスプレッションを設定します

・TranslateX
chop(“../../ch/ch1/record1/geo1:tx”)

・TranslateY
chop(“../../ch/ch1/record1/geo1:ty”)

10・シーンを再生して結果を確認します

boxオブジェクトが、マウスが動いたとおりに移動するのが確認できます。

11・ペンタブレットをお持ちの場合

Mouse CHOPのUse Tabletをオンにして、同様の操作をすることで、筆圧やペンの傾きなどを検知し、記録できる事がわかると思います。
Pressure CHOPチャンネルを boxオブジェクトのUniform Scaleパラメータにアサインすると楽しいかもしれません。


■データ入力手順のまとめ

これが外部入力系のCHOPを使うワークフローの基本的な手順です。
手順をまとめると以下のようになります。

1・外部入力を受け付けるCHOPを作成
2・CHOPにデータの窓口となるCHOPチャンネルを作成
3・任意のパラメータからchop関連のエクスプレッション関数でCHOPチャンネルのデータを参照する

これはその他のデータ入力CHOPを使った場合も同様です。
これだけ理解できれば、あとは応用して色々できるはず。


■Keyboard CHOPチュートリアル

Mouse CHOPに続いて、Keyboard CHOPチュートリアルです

1・CHOPコンテキストにCHOP Networkを作成します

2・CHOP Network内にKeyboard CHOPを作成します

3・Keyboard CHOPの設定をします

Name 1

どんな名前でもいいのですが、ここでは [key_a] と記入します。

Type 1

そのままにします。

Type 1の右側にあるプルダウンメニュー

 [A] を選択します。

同様に、Name 2~Name 4まで、それぞれ同様に s,d,w キーを割り当てます。


4・Interceptモードをオンにします

キーボード上にあるScroll Lockキーを押し、Interceptモードをオンにします。

現在時間がオレンジ色になります。

5・キーを押して反応を確かめます

 a,d,wを同時押しした様子

あとは、Mouse CHOPのチュートリアルと同じ方法で、下流にRecord CHOPを繋いでレコーディングできます。

Interceptモードではキーボード入力が無視されるのでマウスでシーンの再生ボタンを押してください。

chopエクスプレッション関数による値の取得も同様に行えます。


■MIDI In CHOPチュートリアル

最後に、MIDI In CHOPを使用してMIDIコントローラから入力するチュートリアルです

1・CHOPコンテキストにCHOP Networkを作成します
2・CHOP Network内にMIDI In CHOPを作成します

3・MIDI In CHOPの設定をします

・Sourceタブ

MIDI Source : MIDIIN2 (Launchpad Pro)
MIDI Channels : 1

※MIDI Sourceは、各々がお持ちのMIDIデバイス名を選択してください。

・Noteタブ

Note Scope : 0-127
Aftertouch Name : af

4・鍵盤を叩き、反応を確かめます

・ch1n60 (音階で言うとど真ん中の[ド])を押したところ

今回使用したLaunchpad Proはアフタータッチ対応のMIDIコントローラなので、打鍵後に鍵盤を更に押しこむ事でafチャンネルを変化させることができます。

あとは、Mouse CHOPのチュートリアルと同じ方法で、下流にRecord CHOPを繋いでレコーディングできます。

chopエクスプレッション関数による値の取得も同様に行えます。


■おわりに

今回ご紹介したテクニックを使うことで、直感的なデータ入力ができるようになります。

例えば、特定のキーを押したタイミングでパーティクルを発生させたり、ジオメトリの頂点カラーを変化させたり、キーボードを叩くたびに乱数のSeed値を変化させて、次々と予想だにしないビジュアルを作り出すこともできるでしょう。

また、キーを押す強さが強いほどパーティクルの発生量を多くしたり、色を濃くしたりと言った、複雑な表現もできるようになります。

Joy To Keyのようなツールを併用することで、手に馴染んだゲームコントローラを操作してHoudiniへのデータ入力を行うことも簡単にできます。

また、今回はNetwork CHOP や Pipe In CHOPは使用しませんでしたが、これらも使いこなせばなかなかに楽しいことができそうなCHOPなので、いずれ折を見て記事にしたいと考えています。

ここまで読んでいただき、ありがとうございました。
何か不明点や間違いがあれば、記事へのコメントやTwitterなどで質問、ツッコミください。


■おまけ

以降は、今回使用したCHOPのマニュアルみたいなものです。


■マウスからの入力 : Mouse CHOP

マウスのカーソル位置を取得することができます。
タブレットを使用している場合は、筆圧やペンの傾きなども取得できます。

・PositionX/Y

ここで指定した文字列が、それぞれに対応するCHOPチャンネル名になります。
画面左下から右上に向かって値が増えていきます。
Xの範囲は-1~1、Yの範囲は-0.8~0.8

・Use Tablet

オンにするとPressureやAngleなど、タブレット向けパラメータが有効になります。
PositionX/Yと同様、ここで入力した各文字列が、それぞれに対応するCHOPチャンネル名になります。


■キーボードからの入力 : Keyboard CHOP

指定したキーが押されたとき、Typeで指定した方法で値を入力します。

このノードを動作させるためには Interceptモード をオンにする必要があります。
Interceptモードをオンにするには、Scroll Lockキーを押しScroll Lockを有効化します。

Interceptモードが有効になると現在フレームがオレンジ色に着色されます。
この状態でキーイベントを取得するよう設定したキーを押すことで、値が入力されます。

・Modifier Keys

使用する修飾キーを指定します。

上記画像の例では、[A]を押している間だけCHOPチャンネル[a1]の値が1になります。

・Name

対応するキーが押されたときにオンになるCHOPチャンネル名を指定します。

・Type

キーを押したときの動作

Momentaly:押しているときだけ1を送出
Toggle:押すたびに0と1を切り替え
Count:押すたびに1ずつ値が増える
Pulse:押した瞬間だけ1を送出
Time:シーン再生中にキーが押され続けた時間

・Key

このチャンネルに値を入力するキーを選択


■MIDIデバイスからの入力:MIDI In CHOP

MIDIキーボードやフェーダーコントローラなどを使用して、Houdiniに外部から直接MIDIデータを入力することができます。
また、MIDIファイルを直接読み込む事もできます。

・Sourceタブ

MIDI Source

MIDI信号の入力ソースを指定します。
MIDIファイルやMIDIデバイスを選択できます。
インタラクティブな入力を行いたいなら、接続済みのMIDIデバイス名を選択します。
すでに作られた楽曲などを入力したいならMIDI Fileを選択します。

MIDI Channels

入力するMIDIチャンネルの番号を指定します。

入力するMIDIチャンネル数が多いと入力データの転送に時間がかかります。
例えば、使用するノート番号の範囲が0-127の場合、使用するMIDIチャンネル1つごとに128個のデータを解釈することになります。
そこでアフタータッチをポリフォニックモードで解釈させるとなると、さらに128倍の負荷がかかります。
そのため、インタラクティブに操作したい場合は入力するMIDIチャンネルを一つに絞ったほうがいいと思います。
複数のMIDIチャンネルを入力するのは、MIDIファイルを入力する時のみで良いでしょう。

入力を許可するMIDIチャンネル番号は以下のように指定できます

・スペース区切り(個別指定)

1 2 10 11

・ハイフンでつなぐ(範囲指定)

1-10

Channel Prefix

入力するMIDIチャンネル番号を識別するために付加される接頭辞を指定します。
ここで指定した文字列のあと、入力されたMIDIチャンネル番号が付加されます。
この値が[ch]の時、CHOPチャンネル名は以下のような書式になります。

ch1
ch2

Echo Messages to Textport

オンにすると、受信したMIDIメッセージの内容がHoudini Consoleに表示されます。
デバッグに便利なので必要に応じてオンにします。

・Recordタブ

レコーディングには、MIDI In CHOPの下流に接続するRecord CHOPを使用するので、ここでは何も変更しません。

・Noteタブ

主にMIDIコントローラの鍵盤部分とピッチベンドコントローラからの入力信号の解釈方法を決定するタブです。

・チャンネル名パラメータ

チャンネル名パラメータは、対応するMIDI信号を受けとるCHOPチャンネルを作成します。

Note Name : ノートナンバー
Velocity Name : ノートのベロシティ
Aftertouch Name : ポリフォニックキープレッシャー
Pressure Name : チャンネルプレッシャー
Pitch Wheel Name : ピッチベンド

ここで指定した文字列が、MIDIチャンネルを示す文字列のあとに続きます。
例えば、Note Nameの値が[n]なら、MIDIチャンネル1のノート番号60の値を受け取るCHOPチャンネル名は以下になります。

ch1n60

これらのCHOPチャンネル名パラメータに何か文字列が入力されると、その時点でCHOPチャンネルが作成され、データの入力待ちが開始されます。
使用しないMIDI信号の入力については負荷軽減のため何も入力しないのがベターです。

Note Scope

入力を受け付けるMIDIノートナンバーの範囲を指定します。
0-127の範囲が使えます。
Sourceタブの MIDI Channels と同様の書式で指定できます。

Note Output

・One Multiplexed Channel

ノート信号を番号ごとに分けず、一つのチャンネル内で押されたノート信号をすべてまとめて使用します。

この時、CHOPチャンネル名にノート番号は付かず、以下のような名前になります。

ch1n : チャンネル1のノートすべて

・Separate Channels

ノート信号をノート番号ごとに個別のチャンネルとして入力します。
この時、CHOPチャンネル名は以下のようになります。

ch1n60 : チャンネル1のノート番号60番
ch1n127 : チャンネル1のノート番号127番

Velocity

MIDI Note信号のVelocityをどのように受け取るか指定します。

・Off

Velocityを入力しません
ノートを入力するCHOPチャンネルの値はオンオフの2値になります。

・Note Amplitude

ノートを入力するCHOPチャンネルの値が、Velocityの値になります。

・Separate Channels

Note信号の値はオンオフの2値になります。
同時に、Velocityは次のVelocity Nameで指定された接頭辞とノート番号で表されるCHOPチャンネルの値として取得されます。

Normalize

入力されるVelocityデータの値の範囲を指定します。

・None

値は 0-127 の範囲で入力されます。

・0-1

値が 0-127 を 0-1 にマッピングした状態で入力されます。

・Controlタブ

MIDI CCメッセージに関する解釈方法を決定するタブです。
MIDIキーボードにスライダやツマミが付属している場合がありますが、そのような鍵盤以外のコントロールから送信されるデータに関する入力設定です。

・チャンネル名パラメータ

チャンネル名パラメータは、対応するMIDI信号を受けとるCHOPチャンネルを作成します。

Controller Name : CCメッセージ
Program Change : プログラムチェンジメッセージ

Controller Type

一般的なMIDI音源やDAWなどで共通してMIDI CCメッセージへ割り振られているコントロールを、わかりやすくプルダウンメニューから指定できます。

Controller Index

取り扱うMIDI CCメッセージのコントロールナンバーを直接指定します。
0-127の範囲が使えます。
Sourceタブの MIDI Channels と同様の書式で指定できます。

Controller Format

・7 bit Controllers

扱うCCメッセージがLSB/MSBに対応していない場合はこちらを選択します。

・14 bit Controllers

扱うCCメッセージがLSB/MSBに対応している場合はこちらを選択します。

Normalize

入力された値の範囲をリマップします。

・None

リマップしません

・0 to 1

0~1にリマップします

・-1 to 1

-1~1にリマップします

・On / Off

ブール値化します

Unwrap

これはちょっとよくわかりませんでした。
ラベルから察するに、ターンテーブルのような値をインクリメント/デクリメントするコントローラ向けに、値が0-127の範囲外にセットされたときに値をループさせるか否かを決めるオプションのような気がします。

 ・Sysタブ

MIDIクロックメッセージやシステムメッセージに関する解釈方法を決定するタブです。
今回は、手元で検証できる環境がないので端折りますが、この辺をちゃんと設定すれば外部のDAWとHoudiniの間で内部時間や、再生操作や停止操作などを同期できるはず。

KINGSGLAIVEセミナーおさらい プロテス – サンプルhiplcファイルあり

先週と同様、Indyzone主催のHoudiniセミナー「KINGSGLAIVE FINAL FANTASY XV メイキングストーリー」のおさらいをしていました。

というわけで、今日はプロテスのエフェクトです。

今回もサンプルファイルを付けてみました。
興味のある方はぜひ覗いてみてください。

■ダウンロード(One Drive)
MYAM_imitation_FF_KINGSGLAIVE_Protes.zip

zipファイル解凍後に出てくるHoudiniProjectフォルダにSetProjectしてからシーンを開いてください。


■シーンファイルに関して

うろ覚えではあるのですが、セミナーの説明では、@Cdアトリビュートを利用してプロテスエフェクトの「生成、再生、破壊」の振る舞いを決める部分で、ベースになるプロテス障壁の形状を分流してそれぞれを個別のグループに入れながらPaintSOPなどで@Cdの設定を行い、最後にMergeSOPで各データフローを統合してから、最終的に使用しないグループに含まれる成分を削除するという解説だったように思います。

今回は、この部分を極力簡略化し、カラーを独自アトリビュートに変換したあとでAttributeTransferSOPでレンダーモデルに転送する方法を試してみました。
空間転写は処理負荷が高いので、もう少し上手に軽い方法を使えばよかったと思っていたりします。


何か間違いなどあればご遠慮無くツッコミください。
とても喜びます。

KINGSGLAIVEセミナーおさらい ワープエフェクト – サンプルhiplcファイルあり



三連休でやっと時間が取れたこともあり、少し前に行われたIndyzone主催のHoudiniセミナー「KINGSGLAIVE FINAL FANTASY XV メイキングストーリー」のおさらいをしています。

というわけで、とりあえず手始めに今日はワープエフェクトに関してです。

今回もサンプルファイルを付けてみました。
完全な再現とまでは行かなくとも、ボリュームと移流の扱い方に関しては、だいたい再現できていると思います。
興味のある方はぜひ覗いてみてください。

■ダウンロード(One Drive)
MYAM_imitation_FF_KINGSGLAIVE_WarpFX

※ノード名がセミナー中の説明と合ってなかったので、修正しました

zipファイル解凍後に出てくるHoudiniProjectフォルダにSetProjectしてからシーンを開いてください。


概要

ワープエフェクトは、5つの単純な要素を組み合わせて作ります。

  • Spark:火花
    荒いボリュームからパーティクルへ力を渡し、移流して動かします
    その後、TrailSOPなどを使用して火花っぽい見た目を作ります。
  • Ash:灰
    Spark同様に移流してパーティクルを動かします。
    その後、灰オブジェクトをパーティクル上にコピーします。
  • Fire:小さな火の玉
    Spark同様に移流してパーティクルを動かします。
    その後、あらかじめ作成しておいた火の玉シミュレーションのキャッシュをパーティクル上にコピーします。
    その際、キャッシュのスタート時間をランダム化して見た目にばらつきをもたせます。
  • Steam:体から立ち上る蒸気
    Sparkなどで使用する荒いボリュームを複製後に編集し、高解像度化したボリュームを蒸気として使用しました。
  • Crystal:体から飛び散るクリスタル片
    初速と重力によって動く単純なパーティクルにクリスタル片オブジェクトをコピーして作ります。

一見複雑なワープエフェクトは、実は単純な要素を組み合わせて作られています。


シーンファイルについて

スケール調整の手間を省いたので、現実スケールに対しシーンスケールが大きめになっているため、ダイナミクスオブジェクトの動きが若干遅く感じられると思います。


何か間違いなどあればご遠慮無くツッコミください。
とても喜びます。

Houdini – 基本的なデータの取り回し方(サンプルファイルあり)

Houdiniを触り始めた頃、アトリビュートや変数のデータの取り回し方がわかりにくいと感じていました。
中でも特にローカル変数の挙動がよく理解できずに、正しく値を操作できても何だか狐につままれたような気持ちになっていました。

最近はそのようなこともなくなり、意図したとおりに値を操作できるようになってきたので、実践している値の取り回し方を備忘録的な意味で書いてみます。

今回もまたちょっとしたサンプルファイルをつけてみます。

■ダウンロード(One Drive)
MYAM_attributeAccess_exsample


Houdiniで扱うデータのスコープ

Houdiniの中で主にユーザーが直接扱うデータには、以下のようにいくつかの種類とスコープがあります。

  • グローバル変数
  • ローカル変数
  • パラメータ
  • アトリビュート

Houdiniに触り始めた頃、わかりにくいと感じていた部分が、ローカル変数とパラメータとアトリビュートの相互の関係性です。

※グローバル変数は、環境に依存しすぎる気がするので、最近でも極力手を付けないようにしています。(パス文字列の簡略化などに使えばとても便利ですが)
よって、今回の記事では扱いません。


なにがわかりにくかったのか?

さまざまなチュートリアルを見ると、Attribute Create SOPを使い、アトリビュートを作成すると同時にローカル変数も定義して、下流のパラメータエクスプレッションの中でローカル変数を参照して処理をするやり方が散見されます。

これは勝手な印象ですが、特に古いバージョンのチュートリアルで顕著だったように思います。

チュートリアルの後、自分なりに実践してみてうまく値を扱えない時に感じたのが

  • データフローの上流で作ったはずのローカル変数が下流で参照できない・・・
  • と思ったらなぜか使える場合があったりする・・・
  • オペレータの性質から想像するに、マニュアルを引くまでもなく明らかに使えそうに思えるローカル変数が使えない・・・
  • オペレータのリファレンスマニュアルに記載されてるローカル変数が使えない・・・
  • アトリビュートを直接操作したい・・・ローカル変数を参照できない理由がわからない・・・まるで異次元に迷い込んだようだ・・・地獄だ・・・

といったあたりです。
平たく言うと

「ローカル変数が値の操作を混乱させる諸悪の根源」

のように感じていたわけです。


デジタルアセットとローカル変数の罠

また、このローカル変数というやつは、デジタルアセット上で使用できないという、恐るべき制約も手伝って、更に話をややこしくしています。
※ColorSOPのマニュアルに記載あり。
もしかしたら、全デジタルアセットの性質ということではなく、ColorSOPに限った話かもしれません

例えば、Color SOPはデジタルアセットで、Colorパラメータ内ではローカル変数を使用できません。上流で独自アトリビュートを作成し独自のローカル変数を作っても参照できません。

紛らわしいことに、そもそもColorSOP自体、上流で@Cdアトリビュート操作があることを想定していません。あくまでも@Cd操作の開始点として使用する前提です。

このような場合、Color SOPではなくPoint SOPを使用し、その中でローカル変数を参照することで問題なく@Cdアトリビュートを直接操作できます。

このような紛らわしさもまた、一つの地雷と言えそうです。


ローカル変数よ、さようなら

ではどうすればいいのか。

結論を言うと、Attribute Wrangle SOPを介してアトリビュートを直接管理や操作をするのがベストだと考えています。

最近のバージョンでは、VEX周りがかなり進化しています。
Attribute Wrangle SOP内だけでなく、各オペレータのパラメータエクスプレッションでも、VEXのように@記法で直接アトリビュートを参照したり、それができない場合でもpoint関数などを使ってアトリビュートを直接参照する事ができます。

値の管理と操作を、一貫したVEXの世界で行えるのは、この上なくシンプルで理想的です。
今のところこの方法で困ったことは一度もありません。

よって、もう、ローカル変数の出番は無いとすら思います。


まとめ

というわけで、今考えているわかりやすくデータを取り回すための方策は以下の通りです

  • アトリビュートの作成にはAttribute Wrangle SOPを使う
  • アトリビュートの操作にはAttribute Wrangle SOPやAttribute VOP SOPを使う
  • アトリビュートの参照は@記法を使って直接参照する
    またはpoint関数などのアトリビュート値を直接取得する関数を使う
  • ローカル変数は使わない
    $CEXなど便利なローカル変数はあるが、同様のVEX関数もあるのでそちらを使うようにする。

これが同じ悩みを持つ方の一助となれば幸いです。

何か間違いなどあれば遠慮なくツッコミをください。

Houdiniセミナーおさらい – サンプルhiplcファイルあり

先日、「Houdini プロシージャルモデリングテクニカルセミナー」が開催されました。

セミナー中に、CG World 2016年09月号のHOUDINI Cook Bookで掲載された作品「Spine」がどのように作られたかを、著者の秋元氏が自ら解説するという貴重なセッションがありました。

そこで感銘を受け、自分なりの解釈も踏まえつつ真似してみたくなり、早速作ってみました。
記事と解説をすべてトレースするのではなく、VOPで処理されていた場所をVEXで処理するなど、ちょっとだけアレンジしています。
シェーダーまでは手を付けられなかったので、簡易モデルと動きのみです。

今回は実験的に、Houdini Indie 15.5.523 で作成した hiplcファイルを公開してみます。
ファイルには、簡単なモデルと本体モデルとシリンダーの挙動を組み込んであります。
時間経過に伴いウネウネ動く仕込みもしてあるので、再生してみると楽しいかもしれません。

■ダウンロード(One Drive)
MYAM_imitation_spine_hiplc

“NATURE OF CODE” in Houdini – 002 – 力

長い間、仕事ではパイプライン系ツールを書くことが多くなり、幾何学や物理などの数学とはだいぶ疎遠になってしまいました。

最近になって、やっとHoudiniを業務で使用させてもらえる環境が与えられたこともあり、これも良い機会ということで、これからしばらくの間、数学の勉強や復習とリハビリを兼ね、Processingの名著 [NATURE OF CODE] の内容を、Houdiniを使って追いかけていきたいと思います。




 

■動画内容の御品書

  1. シンプルな力
    単純な力を与えて動かす。
  2. 力と質量
    質量を考慮しながらシンプルな力(重力+風)を与えて動かす。
    小さいものほど風の影響を受けやすく水平方向によく動く。
  3. 力と摩擦
    摩擦による減速。
  4. 力と流体抵抗
    Y=0の位置から流体があると仮定し、ポイントが抵抗を受ける。
    この流体の範囲に入った時、ポイントは赤くなります。
  5. 引力(固定アトラクタ)
    原点の位置に引力を発生するポイントを配置し固定。
    その他のポイントは原点のポイントと引き合う。
  6. 引力(ポイントの相互作用)
    各ポイントはそれぞれ相互に引き合う力を持っている。
    質量や引力の設定にもよるが、小さな塊ができ、それぞれの塊が引き合い徐々に大きな塊になっていく。(ポイント半径に合わせたコリジョンは設定していないので、実際には、塊は大きくなりません。)

2.0 Houdiniの単位系

Houdiniは標準的なSI単位を採用している。
https://www.sidefx.com/docs/houdini15.0/dyno/about
http://www.cranenet.or.jp/tisiki/si.html

2.1 力とニュートンの運動の法則

力とは、質量を持った物体に加速度を生じさせるベクトル。

・ニュートンの運動の第1法則(慣性)

静止している物体は静止状態を続け、運動している物体は不平衡力の影響を受けないかぎり、一定の速さで一定の方向へ運動を続ける。

・ニュートンの運動の第3法則(作用・反作用)

力は常に反作用が対になって生じ、その強さは等しく、向きは正反対である。

-F[N]

 押し合う物体の質量が違う場合や、接地面の摩擦力が違う場合、同じ力が加わってもそれぞれの物体が静止し続けるとは限らない。

2.2 力とProcessing

・ニュートンの運動の第2法則(運動方程式)

質量(m)に加速度(A)を掛けあわせると力(F)になる。

・重量と質量

  • 質量(m):物体内の物質の量[kg]
  • 重量(W):物体にかかる重力[N]
  • 密度(Density):単位体積(m^3)あたりの質量(kg)

2.3 力の積算

書籍では質量を1として計算し、F=Aとして簡単化する。

通常、物体にはいくつかの外力が同時に作用する。
そのような場合、全外力の合計値を使用する。
物体に働く力は、特殊な状況を除いて常に変化し、積算されないので、毎フレームで新たな値を計算して使用する。

windForceやgravityForceなどを追加してみる。

2.4 質量

先に計算された外力を質量で割ることで、各ポイントごとに設定された質量を考慮したシミュレーションが行われる。

2.5 力の作成

・力の作り方

  • 直接指定
    自由に直接指定する
  • シミュレーション
    物理公式などを利用し、計算する。

2.6 地球と重力と力のシミュレーション

この時点では、質量が小さいものほど重力の影響を強く受けるようになっている。
物体の落下速度は、その質量にかかわらず一定。
重力加速度の式から正しい加速度を求めて適用するため、重力に質量を掛けあわせてる。

2.7 摩擦

摩擦は散逸力(非保存力)。

※散逸は、抵抗力によって運動エネルギーを熱エネルギーに変換する現象。
散逸力とは、摩擦力などのエネルギーを減少し、別の力に変換させる力。
車のブレーキなどは運動エネルギーを熱エネルギーに変換。

・摩擦の種類

  • 静止摩擦
    接触面に対し静止している物体が受けている摩擦
  • 動摩擦
    動いている物体が接触面から受ける摩擦

摩擦の方向は速度方向の逆。

・摩擦の公式

・公式を使用する際のポイント

  • 右辺を計算し、左辺に代入する。
  • 変数がベクトルかスカラーかを見極める。
  • 記号が隣り合っている場合は乗算を表す。

■各項の意味

  • F friction:摩擦により働く力

  • μ:摩擦係数

    特定の表面上に生じる摩擦力の強さ。

  • N:垂直抗力
    物体が接触面から受ける反作用で接触面に対して垂直方向に物体を押し返す力。
    物体が接触面から受ける垂直抗力の大きさは物体の質量に比例する。
  • v:正規化済みの速度ベクトル
    式中の-1と掛けわせることで摩擦による力の発生する方向を取得する。
    この場合、速度ベクトルの真逆方向に摩擦力による力が発生する。

2.8 空気抵抗と流体抵抗

物体が空気や液体(流体)の中を通り抜けるとき、抵抗力が働く。
これは、粘性力(Viscous Force)や抗力(Drag Force)、流体抵抗(Fluid Resistance)などと呼ばれる。

・流体抵抗の公式

■各項の意味

  • F drag:流体を物体が通り抜ける際に働く抗力
  • 1/2:定数
  • ρ(rho):流体の密度
  • v^2:速度ベクトルの長さの2乗
  • A:進行方を向いている面の表面積
  • Cd:抗力係数、自由に決定する
  • v:正規化された速度ベクトル

2.9 重力

■重力の公式

■各項の意味

  • F gravity:重力
  • G:万有引力定数、独自の値を使っても面白い。
  • m1とm2:相互に作用する2つの物体が持つそれぞれの質量。
  • r:物体間の距離ベクトルを正規化したベクトル。物体同士が引き合う方向。
  • r^2:物体間の距離の2乗。
    物体同士が引き合う力は距離が長いほど弱くなり、近いほど強くなる。

2.10 相互引力と反発

2.9の計算を、各ポイント間で計算し、毎フレーム全ポイントの影響を合計して適用する。


何か間違いがあればツッコミをいただけると助かります。