Bifrostではじめる位置ベース物理シミュ 第5回 ~ジョイント、カーブ、メッシュ~

ハイどうもこんにちは！

[前回](https://qiita.com/akasaki1211/items/971ccad3c50d88749ba1)までずっと1つの点$p$と向き合ってきたのでだいぶ絵的に地味な回が続いてしまいました。すこーしだけ反省しています。ただ、大事な要素は網羅できたと思うので、ここから複数の点をまとめて動かしたいと思います。ようやくジョイントチェーンとか、ポリゴンとか、カーブとかを扱えます。

それでは始めます。

# ジョイントチェーン

ベースのBifrostグラフはもう[前回](https://qiita.com/akasaki1211/items/971ccad3c50d88749ba1#%E4%BB%8A%E5%9B%9E%E3%81%AE%E3%82%B0%E3%83%A9%E3%83%95%E3%81%BE%E3%81%A8%E3%82%81)で出来ています。第3回の「片方が固定の距離制約」が入ったものに対し「コリジョンによる補正」を加えたものですね。球、カプセル、地面が入ったパターンでいきます。

やることは単純、bifrostGraphShapeを丸ごと複製して連ねていくだけなのですが、その前に少し小細工が必要になります。**1階層目の補正済み位置を元にjointの回転へ反映させた後で、2階層目の計算に移らないといけません。** なのでBifrostグラフ内で回転の計算まで済ませてしまいます。

まず、jointチェーンとlocatorを以下のように準備します。見やすいように右によけていますが実際にはlocatorはjoint位置にスナップしてある状態です。root空間でjoint1の制御を、joint1空間でjoint2の制御を、、、と上流から順に処理していくための階層構造です。
![image_05_01a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/4d4ef4ed-c5f8-bbb2-8d20-4a560fa9b125.png)

Bifrostグラフに回転の計算を追加します。出力はただの位置（ベクトル）ですので、補正前後の2つのベクトルにおいて間の回転角を計算すれば良さそうです。が、jointはJoint Orientを含めた元トランスフォームを考慮しないといけないので、Joint OrientとParent Inverse Matrixを受け取っておきます。
![image_05_01b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/760e817b-7034-c71f-8007-9780988f1bdc.png)
▲bifrostGraphShape（黄色枠が追加した箇所です）

ベクトルからjointの回転を得るコンパウンド`joint_rotation_between_vectors`は以下のようになっています。前後はほぼ radians⇔degrees や euler⇔quaternion などの変換です。中央付近、JointOrient（のinverse）とParentInverseMatrixで2つのベクトルを逆トランスフォームしてから既存のノード`rotation_between_vectors`で回転を取得しています。
![image_05_01c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/feb7680f-cfe5-96f8-3b3f-5c25a4e5c683.png)
▲bifrostGraphShape > joint_rotation_between_vectors

Maya側のノード接続はこんな感じです。parent_positionにはjointのワールド位置を直接使いますが、WorldMatrixから得るとCycleの警告が出てしまうのでParentMatirxとTranslateを合わせたもので代用します。VectorProductのOperationは「Point Matrix Product」です。
![image_05_01d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/736c9404-9586-b146-ecd7-d62e1666762f.png)

ちょっとややこしい構造になってしまったかと思いますが、要はアップベクトル不要のエイムコンストレイントをBifrostグラフに内包したような状態です。

![gif_05_01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/5bc30799-7f93-04c9-2fae-8c8fc896e421.gif)

# メッシュ

メッシュの頂点数ぶんグラフを複製するわけにはいかないので、Bifrostの便利機能“**オートループ**”を使います。今までの「位置$x$を入力し$x’$を受け取るコンパウンド」は、位置配列$( x_1, x_2, x_3, ... x_n)$ を入力し同じく配列で受け取ることもできます。ポートの型をAutoに変更し、あとは繋ぐだけです。

Bifrostグラフは次のようになります。[前回の「コリジョンによる補正」が入ったもの](https://qiita.com/akasaki1211/items/971ccad3c50d88749ba1#%E4%BB%8A%E5%9B%9E%E3%81%AE%E3%82%B0%E3%83%A9%E3%83%95%E3%81%BE%E3%81%A8%E3%82%81) から **「距離制約」だけ取り除いたもの**をベースに、pbdコンパウンドへ位置配列を繋げています。ポリゴンの場合はgeometryを入力として`get_point_position`で頂点位置の配列を得られます。配列をgeometryに戻す場合は`set_point_position`です。

![image_05_02a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/55dd9c8d-248d-b8f5-5fa6-8f83856b7e2b.png)
▲bifrostGraphShape

速度配列は位置配列と同じサイズで全要素(0,0,0)にしたいので`for_each`を使用してこのように組みました。グラフが途中で途切れてるのがなんか気持ち悪いですが、まぁいいでしょう…
![image_05_02c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/9202c480-a98a-7421-97d2-104a5b9c4cda.png)
▲bifrostGraphShape > for_each

iterateのポートも忘れずにAutoにしておきます。
![image_05_02b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/071da230-097e-8167-6595-7e9ef75e87b0.png)
▲bifrostGraphShape > pbd

動かしてみると、このようになりました。
![gif_05_02.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/199ace1b-3f4b-71f2-33f1-5b48a8580464.gif)
▲※重力はオフにしてます

動いてはいるんですが、面白みに欠けますね…。もっとこう、ソフトボディ感が欲しいです。

## 頂点カラーで重み付け

ということで、よく動く場所とあまり動かない場所を頂点カラーで塗り分けたパターンをやってみます。

![image_05_03a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/cc9cc7f8-d0d5-b0d3-70bb-284cf32a9dde.png)

頂点カラーをBifrostで取得するには`get_geo_property`を使って「colorSet1」を指定します。得られるのは同じく配列なのでオートループの対象になります。
![image_05_03b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/eb0c75c1-2ab3-9196-3796-d989587f0500.png)
▲bifrostGraphShape

頂点カラーで初期位置とlerpします。
![image_05_03c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/188e547e-b0b4-36a7-64b9-6209745b3034.png)
▲bifrostGraphShape > pbd


動かしてみると、やわらかプリンができました🍮
![gif_05_03.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/10ebbd23-896c-9a77-11ba-9e56bf1a34b4.gif)
▲※重力はオフにしてます

# 両端が固定のカーブ

わりと大変なやついきます。

第3回の「片方が固定の距離制約」と「両方がフリーの距離制約」の合わせ技です。第4回の「コリジョンによる補正」も入ってます。

全7点中、両端の2点が固定、中間の5点はpbdで制御です。距離制約は計6つ、うち4つ（二重線）は「両方がフリーの距離制約」になります。
![fig_05_01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/cd528a53-cbc0-2613-476c-520424c70b83.jpeg)

もはやグラフはお見せしたところで感があります…
![image_05_04a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/71da90c7-cf47-1151-f0e2-d0029eb38af8.png)
▲bifrostGraphShape

ものすごく複雑に見えるんですが、制御点の数が多いだけでベースは[第3回の「両方がフリーの距離制約」の型](https://qiita.com/akasaki1211/items/9d22075df1a51be1ed4c#%E4%B8%A1%E6%96%B9%E3%81%8C%E3%83%95%E3%83%AA%E3%83%BC) になってます。位置補正が複数あるので中央付近で`iterate`にまとめてあります。
![image_05_04b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/7ca7e2d6-b9bf-fd1d-8b6a-a6a636886b66.png)
▲bifrostGraphShape > pbd

重要なのはイテレーション内です。中間の5点については複数の距離制約から引っ張られるので、これも反復法で収束させる必要があります。具体的には以下のリンクの**ヤコビソルバ**と呼ばれる方法で組みます。**『制約をそれぞれ別に計算してから平均を取る』** を反復でぶん回します。

https://news.mynavi.jp/article/20100906-cedec02/4

![image_05_04c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/3f77a4c2-63d5-7405-697e-73431e02ef36.png)
▲bifrostGraphShape > pbd > iterate（右端のaverageが平均をとっているところ）

平均は足して2で割るだけです。
![image_05_04d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/2dc8b0d5-4fb2-b824-0faa-a1b78928500a.png)
▲bifrostGraphShape > pbd > iterate > average

Mayaからの入力はシンプルです。
![image_05_04e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/3a01e865-12b5-b7c4-e22b-b9ac2cb8cb6e.png)

![gif_05_04.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/4be6fbea-8c22-5d74-09ec-4ba2994e2cd7.gif)


ちなみに、シミュレーション関連を省いて距離制約のイテレーションだけにするとこのようになります。収束していく様が見て取れます。👇

https://twitter.com/akasaki1211/status/1751271096815616193


# どこも固定されていないカーブ

さっきのパターンから両端を取り除いちゃいます。全5点、全てフリーです。
![fig_05_02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/7646ee39-8811-a32d-93e4-d4918b0ce0aa.jpeg)

前項のものとだいたい同じなので諸々省きますが、両端の「片方固定の距離制約」関連を消しただけです。
![image_05_05a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/f096ec2a-9b67-dd32-5854-d3588f2a2e97.png)
▲bifrostGraphShape > pbd > iterate

Mayaからの入出力はこんな感じ
![image_05_05b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/7dbdeb6b-bc22-77fc-302c-a919b4d6204f.png)

![gif_05_05.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/f5d39940-2ecb-4b2e-d6a6-3f9809b95e0e.gif)

ちょっと今回は複雑そうな見た目のものが多かったですが、前回までの基本形を「たくさん繰り返しただけ」です。入出力のほとんどが位置（3次元ベクトル）でしたし、落ち着いて分解すればひとつひとつはだいぶシンプルかと思います。

さらにここから1軸回転に限定したパターンとか角度制限とか追加していくとラグドールも作れちゃいそうです。あとは、メッシュの全エッジに距離制約を適用すれば布もできるはずです。今の組み方では量的に不可能に近いですが…

次回は、、、

ありません！ここまでお付き合い頂きありがとうございました！！！

おしまい。🙇

# 今までのグラフまとめ

【第4回より】PBD（bifrostGraphShape > pbd）
![image_04_02a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/9cdbbe77-44b8-25ab-13d2-aacbf77cd556.png)

【第2回より】重力とバネを加味して次の位置を決定（bifrostGraphShape > pbd > Verlet）
![img_02_05c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/541787f0-8cac-6e90-b867-240c98d39a83.png)

【第2回より】補正前の位置に戻す（bifrostGraphShape > pbd > Keep_position）
![img_02_05d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/d90825c0-f4e6-ea2c-477d-b1cb678b7271.png)

【第1回より】速度の更新（bifrostGraphShape > pbd > Update_Velocity）
![img_01_04c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/d6d9fa0b-c01c-0386-b7c2-255ee3f175fd.png)

【第1回より】初期値へリセット（bifrostGraphShape > pbd > Reset）
![img_01_04d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/ea31fa88-9f43-d339-ecdb-b2ba25d73ec4.png)

【第4回より】制約のイテレーション例（bifrostGraphShape > pbd > iterate）
![image_04_06a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/7fd32c84-c6a8-2c35-3699-7672a158f09f.png)

【第3回より】距離制約①：片方が固定（bifrostGraphShape > pbd > iterate 内で使用）
![img_03_03d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/acb888f9-71ba-0cc0-10da-b870539ec54b.png)

【第3回より】距離制約②：両方がフリー（bifrostGraphShape > pbd > iterate 内で使用）
![img_03_04d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/3462b483-eb51-a75a-53fa-adb56a6e81fe.png)

【第3回より】質量からウェイトを計算
![img_03_04b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/61d609fb-0b9a-2083-3067-5121e0e584e6.png)

【第3回より】ウェイトの正規化
![img_03_04c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/aa039662-f6e9-62e0-5d4c-59d350655c63.png)

【第4回より】地面コリジョン（bifrostGraphShape > pbd > iterate 内で使用）
![image_04_02c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/f52083c9-0066-5d7a-be3b-62de66bda2e3.png)

【第4回より】球コリジョン（bifrostGraphShape > pbd > iterate 内で使用）
![image_04_03b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/05507a81-6a37-9a93-c708-2b7cce551dd2.png)

【第4回より】カプセルコリジョン：半径共有（bifrostGraphShape > pbd > iterate 内で使用）
![image_04_04b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/0bbaf756-eb65-47ed-aa24-f0fc221464e0.png)

【第4回より】カプセルコリジョン：半径個別（bifrostGraphShape > pbd > iterate 内で使用）
![image_04_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/a2457ebb-f4c4-6df1-567b-f9b070bcd84a.png)

【第4回より】無限平面コリジョン（bifrostGraphShape > pbd > iterate 内で使用）
![image_04_06b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/81c9b2c5-a775-f367-598c-35ba5b56aefe.png)

# 成果物（コンパウンド／グラフサンプル）
ここまでに作成したコンパウンドとグラフはこちらのリポジトリに置いてあります。

https://github.com/akasaki1211/bifrost_AkPBD

---

前の記事

https://qiita.com/akasaki1211/items/971ccad3c50d88749ba1