SolverSOPとwrangleを使ってツタが成長する仕組みを作ろうと思います
サンプルファイルはこちらです。
wrangle 50行程度で作れますので
順番に説明していこうと思います。
(wrangle以外のノードが出てこないので人によっては退屈かもしれません..)
STEP1. 面と平行に進むベクトルを作る
まず、ツタが伸びる際に近くのオブジェクトの形状に沿うようにしたいと思います。
これは
1、近くのオブジェクトの法線Nを拾う
2、法線とツタの成長の2つのベクトルを使って、面と平行なベクトルを求める
の2工程で出来ます。
wrangleにするとこうなります
// run over points
int prm;
vector uv;
xyzdist(1, @P, prm, uv);
vector N = primuv(1, "N", prm, uv);
***xyzdist()***で近くのプリミティブ番号、UVが得られるので
それを使って ***primuv()***で必要なアトリビュートを拾っています
法線がわかれば、以下のようにcrossを2回使うことで平行なベクトルが求められます。
vector v = cross(v@N ,cross(v@D, v@N)) * f@speed; // speed(移動量)は別で
これをSolverSOPで繰り返し処理させるとこんな動きが作れます
*虫みたいになってしまいました・・苦手な方ごめんなさい
STEP2. ラインが伸びるようにする
オブジェクトに沿った動きが作れるようになったので
ラインが生成されるようにしてみます
こちらは
1、最後に作られた頂点を得る
2、addpoint()、addverterx()で頂点を増やす
の2工程で出来ます
1の部分ですが、今回はループをpointではなくprimitiveで回しますので
primitiveのループ内部でpointを得る為に
***primpoints()***を使います。
こんな感じに書きます
// run over primitives
// 現在のプリミティブの一番新しい点を拾う
int prim_pts[] = primpoints(0, @primnum);
int last_pt = prim_pts[-1];
vector last_pos = point(0, "P", last_pt);
vector last_v = point(0, "v",last_pt);
// 点に一番近い面のNを拾い、平行なベクトルを求める
int near_prm;
vector near_uv;
xyzdist(1, last_pos, near_prm, near_uv);
vector near_n = primuv(1, "N", near_prm, near_uv);
vector near_pos = primuv(1, "P", near_prm, near_uv);
vector next_v = cross(near_n,cross(last_v, near_n));
// 新たな点を追加し、次のループ用にvを与えておく
vector next_pos = near_pos + normalize(next_v) * f@speed;
int next_pt = addpoint(0,next_pos);
addvertex(0, @primnum, next_pt);
setpointattrib(0, "v",next_pt, next_v, "set");
addpoint~の流れは説明している方も多いので省略します。
こちらもSolverSOPに組み込むと、こんな感じになります
もうこれで基本は出来ました。
あとはツタっぽくする為にアレンジしていくだけです。
###ツタっぽくする
今の状態では、最初に決めた方向からまっすぐ伸び続けてしまうので
進行方向にブレを持たせることにします
STEP3.ベクトルを回転させる
ブレさせる、といっても、randやnoiseで作ったベクトルを加算してしまうと
面と平行では無くなったり、移動量が変わってしまったりします
今回の場合は、両方とも崩したくないので
Nを軸にしたquaternionで回転(qrotate)させることにします
wrangleはこんな感じです
float rad = radians(chf("rot")); // 調整用パラメータ
float rot_amount = fit01(rand(last_pt), -rad, rad);
vector4 rotate_q = quaternion(rot_amount, near_n);
next_v = qrotate(rotate_q, next_v);
こうすると、平行を保ちつつブレながら進むようになります
-10~10度
~40~40度
STEP4. 指定した方向に大体合わせるようにする
ツタっぽくクネクネ伸びるようになりましたが
このままでは狙った形状にするのが難しく、映像制作には使えないでしょう。
なので、大まかな成長方向を定義しておき、それに沿って進むようにします。
ここで使うのはdihedralとslerpです
1、dihedralで現在の進行方向が、全体の進行方向と揃う為に必要な回転量を求め
2、slerpで必要な回転量を調節する
の2工程です
こんな感じに書きます
vector4 q1 = quaternion(set(0,0,0));//無回転
vector4 q2 = dihedral(v@v, v@global_v);
vector4 quat = slerp( q1, q2, chf("bias"));// biasは調整用パラメータ
v@v = qrotate(quat, v@v);
赤=global_v 黄色=v biasを0から1にアニメーションする様子
こちらもSolverSOPに取り入れてみます。
これで大体意図した方向に成長させることができるようになりました。
STEP5.分岐するようにする
ツタっぽくするためにもう少し手を加えます。
今度は、一定間隔で新しくツタが生えるようにしていきます。
これはとてもシンプルな手順で出来ます。
1. 一定間隔でaddprimして、初期方向(v)を与えておく
だけです。
ループがprimitiveベースになっているので
新しいprimitiveが追加されたら次のループから同じ処理が適用されるようになります。
つまり、ここまで使っているパラメーターはvだけなので、
vさえ与えてあげれば勝手に伸びていきます。
そのvの値ですが、ツタっぽくするために
最初は進行方向に対して垂直(-90 or +90)になるようにしたいと思います。
これは進行方向とNのcrossを求めて-1か+1で掛けるだけなんですが
-1か+1をシンプルに作る方法がわからなかったのでとりあえずこうしました
rint(rand(@ptnum))*2-1
*何か良い方法があったら教えてください・・
とりあえず、合わせて書くとこんな感じです
int sub_pt = addpoint(0, near_pos);
addvertex(0, new_prim,sub_pt);
float sub_direction_flg = rint(rand(last_pt))*2-1;
vector sub_direction = cross(normalize(next_v), near_n);
sub_direction = sub_direction * sub_direction_flg;
setpointattrib(0,"v",sub_pt, sub_direction,"set");
STEP6.その他
とりあえずこんな感じで大体ツタっぽくなったでしょうか。
説明用に、かなりシンプルな作りにしてみましたが
もしツール化するならまだまだ機能が欲しいと思います
- たとえばi@isgrowなどでフラグを立てて成長が止まるようにするとか
- f@ageを作って成長している間はカウントするようにするとか
- f@width = clamp(f@age * 0.05, 0.1, 1.0)
で太さが0.1から最大1まで成長するようにするとか - 面のNをv@Nでポイントに持たせておいて
v@P += v@N * f@width
でオブジェクトからちょっと離れるようにするとか - 葉を生やす(光合成の効率が良いように、重ならないようにする)とか
- 葉が揺れる、ツタがしなる とかダイナミクス要素も入れてみたいですね
今回、全部を解説出来ていないのですが
サンプルファイル内で幾つか実装しておいたので、興味があったら参考にしてみてください。
もちろん色々な書き方があると思うので
もっと高速にスマートな方法が思いついたら試してみてください。
houdiniはwrangle書いてサクッと絵になるのが楽しいですね。
以上です
「レアなノードの使い方」みたいな紹介ができなくて、玄人には退屈だったかもしれませんが
機会があればそういう記事も書いてみたいと思います。