Houdiniで2D物理シミュレーション(RBD)

![RBD02_3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/a5ed4e4b-85b7-eb64-c72a-eecfe65fc9a5.gif)

# はじめに (モチベーション)

![RBD_thumb2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/408c3339-6667-6934-9fda-3afe011ad33a.png)
巨大なビルの破砕シミュレーションだって捌いてしまうHoudiniですが、「2D的」「モーション的」な使い方をしようとすると、シンプルなものでも意外ととっかかりに苦労します。そもそも作例があまり見つからず、あれこれ工夫しても前後にはみ出たり傾いてしまったりと、他のアプリで簡単にできるようなことすら中々実現できなかったりするのです。

せっかくHoudiniを触り始めたモーション仲間が、初手で躓いてしまってそのまま離脱…
というのはあまりに悲しいので、

* 落とす (自由落下)
* 発生させる (パーティクル)
* 動かす (アニメーション追従)

の3つの基本的なセットアップを、初手のテンプレとして用意してみました。


# 作例ファイル

![3types_2ways.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/87ab1ecc-9a75-953b-cb32-51cc3a6bcf8e.png)

[2D_RBD.hipnc](https://www.dropbox.com/scl/fi/6wnpx1l1rih9j38q54frp/2D_RBD.hipnc?rlkey=e2yzjq42byz6nuh8bn6wdt0n5&dl=1) (Non-Commercial)

肝となる2D拘束の部分はいずれも共通で、コピペしてそのまま他のセットアップにも使える形にまとまっています。良ければご活用下さい。


# アプローチ (血も涙も2Dモードもない)
![RBD_3D_approach.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/9d33b1d9-466a-6bb1-ddc5-7889853eb5bc.png)
私が(血眼になって)探した限りでは、HoudiniのRBDで2D形状を2Dのまま扱う方法は存在しません。基本的には全ての形状を押し出し、立体物としてシミュレーションしたものを強引に平面上に封じ込め、平面的にレンダリングします。

具体的には

* **位置をXY平面上に抑える**
* **角度をZ軸回転のみにする**

の2つを毎フレーム補正していきます。


# 方法1: コンストレイントを作る
 ![01_common_constraint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/48d731de-8c84-4a4e-ac57-83bb72108925.png)

## 使い方
いつものRBDセットアップに紫ブロックの部分をコピペして挟むだけ！簡単ですね。

## もう少し詳しく

#### そもそもRBDの拘束(コンストレイント)とは
![02_con_geoA2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/3db6e19a-45f5-a4db-12da-1eb58ae37f2c.png)

点2つで構成される**線**のプリミティブ群です。物体に相互関係を表現するために、何かと何かを一定のルールで繋ぐので、線なのですね。シミュレーションする対称のオブジェクト本体と合わせて、RBD Solverの2番目に入力してあげます。

#### 自由度の拘束 (Degree of Freedom)
拘束には様々な種別やオプションがありますが、今回はHard Constraintの **Degree of Freedom(自由度)** を使います。

具体的にはZ軸を基準に、位置と回転の拘束を作っていきます。
* **Z軸方向にはみ出さないようにする → 位置拘束**
* **Z軸以外で回転しないようにする → 回転拘束**

![constraint_dof.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/46ca14ea-1fe4-a611-3e11-ef1cc0817c04.png)

https://www.sidefx.com/ja/docs/houdini/nodes/dop/constraintnetwork.html#%E3%82%A2%E3%83%B3%E3%82%AB%E3%83%BC%E3%82%A2%E3%83%88%E3%83%AA%E3%83%92-%E3%83%A5%E3%83%BC%E3%83%88

これに則って考えると、2D平面に拘束するには、

* `condir` をどちらも **Z軸({0,0,1})** に
* `condof` を **位置拘束=1** (平面移動), **回転拘束=2** (1軸回転)
 
にすれば良さそうです。VEXで書くと
```cpp
// 位置拘束
v@condir = {0, 0, 1};
i@condof = 1;
```

```cpp
// 回転拘束
v@condir = {0, 0, 1};
i@condof = 2;
```
という感じですね。

### 2D拘束の設定
![02_con_geoB2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/2678d60c-b7d1-62a8-1f41-ef2ad944a07e.png)
今回の拘束はオブジェクト単体に作用するので、対となるオブジェクトが存在しません。そのような場合、もう片方の拘束先には**空欄**を指定します。(ワールドの任意の点にピン留めしたいような場合なども同様ですね)

前述した`condif`, `condir`はPrimitiveではなく**Point**への設定が必要な点にも注意しましょう。

似たタイプのコンストレイントなので、最初は共通項目を同じ線に設定した上で、あとから分岐して位置/回転それぞれ異なる部分を設定し、最後にマージしています。

:::note info
今回はイチから線を作ってコピーしていますが、Houdiniには拘束を作る便利なノードが多数あり、これらを活用するのが一般的だと思います。

でも基本的には単なる線の集まりなので、自分でも作って弄れることが分かっていると、ちょっとしたカスタマイズや応用が効くようになります。逆に我流で上手く行かない場合は、これらのノードが作るプロパティと見比べて、何が足りないのかを探るってみると良いでしょう。

![constraint_nodes.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/75349d44-47cd-7cb4-74dd-939632f12a03.png)
:::



# 方法2: Post Solveを使う
## 使い方

![rbd_postsolve.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/c64871c9-ec9d-0759-60cb-02fb47dfe958.png)

RBD Solver内のPOSTSOLVEに、黄色いノードをコピペして繋ぐだけ！お手軽ですね。

## もう少し詳しく
Post Solveは毎フレーム、シミュレーション後の結果に適用できるもので、ここでズレた位置と角度を強引に修正してしまおうという寸法です。


```cpp
// --位置--
// Z位置をリセット
@P.z = 0;


// --回転--
// 現在の姿勢(クォータニオン)を回転行列へ変換
matrix3 m = qconvert(p@orient);

// 変換した行列で今向いている方向Nをつくる
vector N = {0,0,1} * m;

// Nを本来向くべき正面方向へ向かせるクォータニオンをつくる
vector4 qrot = dihedral(N, {0, 0, 1});

// qrotを使ってp@orientを正面に向かせる
p@orient = qmultiply(qrot, p@orient);

```

正直コンストレイントに比べてケアすべきことも少なくずっと楽なので、こちらで済む場合はその方が良さそうです… (期待せずちょっと調整を入れたら想像以上に安定してしまった)
自分もまだ検証し切れていないのと、拘束やDOFの話は応用範囲も広いので、一応上の方法も残すことにしました。
場合によっては両手法を組み合わせるのもアリだと思います。


# 各作例のポイント
細部は割愛しますが、特にポイントとなる点を書きます。

## 1. 自由落下
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/024539db-881c-f470-561f-f26a172829fd.png" width=50%>

床(Ground Plane)をONにした程度の基本形で、2D拘束以外は特に何もしていません。


## 2. パーティクル
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/adeae0fd-8d1a-876e-34c5-6a956ca0ecc6.png" width=50%>

シミュレーションの途中で動的にオブジェクトを追加しています。RBD Solverの`Emit RBDs`にチェックを入れましょう。

通常はオブジェクトを増やすと、よしなに関連コンストレイントもシミュレーションに追加されるのですが、ユニークな名前に置換されるためか、今回のような空欄指定のコンストレイントがあると上手く追加されません。
少しトリッキーですが最初はコンストレイントの両ポイントに同じ名前を指定しておき、生成直後にRBD Solverの中に作ったSOP Solver内で、片側の`s@name`を空欄に書き換えています。


## 3. アニメーション追従
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153494/12a98335-89cd-3c41-b502-4624baa399c9.png" width=50%>

途中までアニメーションに従いながらも、衝突に合わせて解放されて自由に動きます。
最初のRBD Configureとその後のノードで、グリッド/円に対してそれぞれ独自のプロパティを設定しています。

ざっくり説明:
- 動かす方は `i@active=0`,`i@animated=1`を指定。止まっている方は0/1を逆に。
- RBD Configure内の `Min Activation Impulse`を設定することで、一定の力以上にがかかったところで`active`に切り替わるよう指定。
- グリッドの方は最初に`Pin Constraint(Glue)`を設定し、次の`RBD Constraint Properties`ノードで壊れる閾値を設定。


# 最後に
正直今回の作例程度のことであれば、他のソフトの方がよほどサクッと作れるかもしれません。(3つ目とかはやや難しかったりしますが)
でもひとたび基礎がHoudiniで作れたのなら、これを数万個にスケールすることも、サブステップ数をぶち上げて超高速な物体を処理することも、複雑な拘束を張り巡らせて破壊を何段階にも分けることも、想像しうる大抵のことは自由自在です。ツールの制約から開放されるべく、最後のツール習得と思って頑張りましょう。


## 参照 (謝辞)
今回の内容は、ほぼ以下両者の説明やノウハウを2D拘束という視点でゆるくラップ&再構成したものとなっています。(いつも大変お世話になっております...)



https://www.nicholas-taylor.com/blog/blog-post-title-two-ea3sa

https://tokeru.com/cgwiki/ConstraintNetworks3.html#googly_eyes_with_animated_pin_constraints


アニメーション追従のより詳しい記事など

https://www.toadstorm.com/blog/?p=1090

https://houdinifx.jp/blog/anim_to_sim/