##はじめに
この記事はHoudini Apprentice Advent calender 2018 6日目の記事です。
初めまして、今年の2月からFX Artistとして働いています かやの といいます。
破壊されたものを元に戻していくエフェクトを作ってみたので解説記事を書いてみました。
なかなか長くなってしまったのでお時間あるときに見ていただけると幸いです。
↓が最終結果になります。
全体のネットワークはこんな感じです。
##1.破壊されたものを準備する
まずレンガを作っていきます。
(1はほぼレンガを作るだけなので興味ない!という方は飛ばしてください)
(↑もっと簡単な方法が多分あると思うのですがWrangleの練習のため少しWrangleを使って作ってます。)
最初にベースになるラインを作成。
Resampleで作りたいレンガの数分に分割します。curveuを次で使うので作っておきます。
次にWrangleで半レンガ分ポジションのずれたラインを作っていきます。
vector offsetpos = clamp(@curveu - ( 1.0 / (npoints(0) - 1) ) / 2, 0, 1);
@P = primuv(0, "P", 0, offsetpos );
vector endpos = primuv(0, "P", 0, 1);
if(@ptnum == npoints(0)-1){
int pt = addpoint(0, endpos);
int pri = addprim(geoself(), "polyline");
addvertex(geoself(), pri, npoints(0)-1);
addvertex(geoself(), pri, pt);
}
(primuvについてボーンデジタルさんが解説を書いてくださっています。こちら)
primuvで半レンガ分ポジションをずらします。
( 1.0 / (npoints(0) - 1) ) / 2
↑で半レンガ分の長さをとってcurveuから引いたものを0-1にクランプしuvにいれています。
もう1度primuvを使って端のポジションを取り、
addpointで今とったポジションにポイントを作り、addprim, addvertexを使いラインにつなぎます。
vector endpos = primuv(0, "P", 0, 1);
int pt = addpoint(0, endpos);
int pri = addprim(geoself(), "polyline");
addvertex(geoself(), pri, npoints(0)-1);
addvertex(geoself(), pri, pt);
}```
今作ったラインとレンガ分の高さ分Y方向に上げたラインをmergeします。
![MergeLine.jpg](https://qiita-image-store.s3.amazonaws.com/0/319994/14ca63aa-3a3a-f651-34d7-524718492699.jpeg)
(ポイントの位置をわかりやすくするためにsphereをコピーしてあります。)
このラインをレンガにします。
Convertlineでプリミティブを分割、FacetのUnique Pointsで共有しているポイントを分割し、
PolyextrudeのFront Transformで横縦にextrude、extrudeし、幅、高さを作ります。
![tworowRenga.jpg](https://qiita-image-store.s3.amazonaws.com/0/319994/e21386a1-43c9-127a-0675-2397c59da152.jpeg)
最後にCopyで欲しい高さの分伸ばします。
これでレンガの完成です。
そしたらpackして破壊するのですが破壊する前にidをアトリビュートでもたせておきます(次からの工程で使います)。
```ruby:Point
@id = @ptnum;
破壊に関してはRBD Paked Objectでxに+4 veloityいれて飛ばしただけなので説明は省かせて頂きます。
##2.シミュレーションの下準備
ここでは
1, メインのジオメトリ
2, 追いかけさせるポイント(ポジションのコントロールに使います)
3, ゴールのポイント(回転のコントロールに使います)
を用意します。
3列に分かれているので順番に解説していきます。
まず左の列です。
Timeshiftを使って最終フレームの状態に固定します。
次に最初のフレーム以外いらないので2フレ以降を削除します。
if(@Frame > 1) removepoint(geoself(), @ptnum);
シミュレーション時に回転が暴走しないようにspinmaxをもたせておきました。
@spinmax = 3;
これで左の列は終了です。
次に真ん中の列です。
まず破壊した時の軌道を取っていきます。
AddのDelete Geometry But Keep the Pointでポイントのみにします。
次にTrailのTrail Lengthに最終フレームを入れます。
Addでidを使って繋ぎます。
Timeshiftで最終フレームの状態に固定します。
これで軌道のカーブが作れました。
今回はこのカーブの長さが短いものから元に戻っていくようにしていきます。
Wrangleでカーブが短いものから順番の番号を作ります。
int nprims = nprimitives(0);
10
float perilist[];
for(int i=0; i<nprims; i++){
float peri = primintrinsic(0, "measuredperimeter", i);
append(perilist, peri);
}
int sortnum[] = argsort(perilist);
for(int ii=0; ii<len(sortnum); ii++){
setprimattrib(0, "perisort", sortnum[ii], ii, "set");
}
まず総プリミティブ分forをまわしてカーブの長さのリストを作ります。
int nprims = nprimitives(0);
float perilist[];
float peri = primintrinsic(0, "measuredperimeter", i);
append(perilist, peri);
}```
argsortで0から順番の数を作りforでプリミティブアトリビュートにsetします。
```int sortnum[] = argsort(perilist);```
```for(int ii=0; ii<len(sortnum); ii++){
setprimattrib(0, "perisort", sortnum[ii], ii, "set");
}```
これで番号が作れました。
次に今回は最後のほうを上から降りてく感じにしたいと思ったので
カーブの最後の方だけy方向にいじります。
(今回はUvtextureでuvをつくってやっていますがcurveuでもいいと思います。)
```ruby:Point
float amp = chramp("amp_ramp", @uv.x);
@P.y += amp * ch("amp");
次にWrangleで追いかけるポイントを作ります。(↓のような感じ)
float speed = 0.3;
float minf = 0;
float maxf = 20;
float startframe = fit(@perisort, 0, (nprimitives(0)-1), minf, maxf);
float offset = startframe * @Timeinc;
float u = clamp(@Time*speed - offset, 0, 1);
vector pos = primuv(0, "P", @primnum, chramp("carve", u) );
int pt = addpoint(0, pos);
int firstnum = primpoint(0, @primnum, 0);
int id = point(0, "id", firstnum);
setpointattrib(0, "id", pt, id, "set");
removeprim(0, @primnum, 1);
まず先ほど作ったperisortというアトリビュートを使って始まるフレームを決め、TimeincをかけてTime基準にします。
float offset = startframe * @Timeinc;```
それを01の範囲内でアニメーションさせます。
```float u = clamp(@Time*speed - offset, 0, 1);```
次にポイントを作るポジションをとるのですがアニメーションのしかたをいじりたかったのでrampパラメーターを作って始まりを早くさせてます。
```vector pos = primuv(0, "P", @primnum, chramp("carve", u) );```
![AnimationCurve.jpg](https://qiita-image-store.s3.amazonaws.com/0/319994/01741ec2-f5e5-091f-0861-3cc1e55e4b3b.jpeg)
そしてポイントをつくったらカーブが持っていたidをsetします。
```int firstnum = primpoint(0, @primnum, 0);
int id = point(0, "id", firstnum);
setpointattrib(0, "id", pt, id, "set");```
最後に新しく作ったポイント以外を消したら真ん中の列は終了です。
___
次に右の列ですがTImeshiftで最初のフレームの状態に固定したら終了です。
---
最後にすべての列の最後でidを違う名前のアトリビュートで作っておきます。
(シミュレーション時にidをいじってしまうためです)
```ruby:Point
@oriid = @id;
下準備は終了です。
##3.シミュレーション
それではシミュレーションしていきます。
GeometryWrangle以外はほぼデフォルトなので解説は省きます。
まず左から。
Input2で追いかけるポイントを読み込みます↓
ここでまずポジションを動かしていきます。
vector target = point(1, "P", @oriid);
v@v = (target - @P) * 4;
float dist = distance(@P, target);
float amp = fit(dist, 0, 1, 0, 0.1);
@P = lerp(@P, target, amp) ;
まず最初にターゲットに向けたveloityを作ります。
vector target = point(1, "P", @oriid);
v@v = (target - @P) * 4;
なのでポジションをターゲットとlerpでブレンドし無理やり動かします。
ターゲットから遠くなるほどブレンドされるようにしています。
float amp = fit(dist, 0, 1, 0, 0.1);```
@P = lerp(@P, target, amp) ;```
次に右です。
Input3でゴールのポイントを読み込みます(2でも大丈夫です)↓
ここで回転を動かします。
vector target = point(2, "P", @oriid);
float dist = distance(@P, target);
float amp = fit(dist, 0, 2, 0.5, 0);
matrix3 transform1 = primintrinsic(0, "transform", @ptnum);
matrix3 transform2 = primintrinsic(2, "transform", @ptnum);
vector4 quat = quaternion(transform1);
vector4 quat1 = quaternion(transform2);
vector4 rot = slerp(quat, quat1, amp);
matrix3 ftransform = qconvert(rot);
setprimintrinsic(0, "transform", @ptnum, ftransform);
@id = -1;
まずゴールに近いほど最終の回転に近づくようにampを作ります。
float dist = distance(@P, target);
float amp = fit(dist, 0, 2, 0.5, 0);```
次に今の状態のtransformと最終のtransformを取得します。
```matrix3 transform1 = primintrinsic(0, "transform", @ptnum);
matrix3 transform2 = primintrinsic(2, "transform", @ptnum);```
そうしたら先程作ったampの強さで最終の回転になるようにslerpでブレンドします。
```vector4 quat = quaternion(transform1);
vector4 quat1 = quaternion(transform2);```
```vector4 rot = slerp(quat, quat1, amp);```
```matrix3 ftransform = qconvert(rot);```
```setprimintrinsic(0, "transform", @ptnum, ftransform);```
最後にこのままだとCollisionに回転が適用されていません。
(これに気づかず全然うまくいかないなーと30分ぐらい頭を抱えてました...)
![NoRot.jpg](https://qiita-image-store.s3.amazonaws.com/0/319994/28ed64f6-300a-f9af-95da-7743db3e4ba4.jpeg)
なのでidをいじることでCollisionを更新します。
```@id = -1;```
(Entagmaさんのチュートリアルを参考にしました。[こちら](https://vimeo.com/235303997))
(cgwikiというサイトで新しくここらへんについての解説が公開されました。[英語版はこちら](http://www.tokeru.com/cgwiki/index.php?title=HoudiniDops#RBD_follow_targets) [日本語版はこちら](https://houdini.prisms.xyz/wiki/index.php?title=HoudiniDops#RBD.E3.82.92.E3.82.BF.E3.83.BC.E3.82.B2.E3.83.83.E3.83.88.E3.81.AB.E8.BF.BD.E5.BE.93.E3.81.95.E3.81.9B.E3.82.8B))
以上で完成になります。
##まとめ
今回のやり方は元に戻っていく順番を自由にコントロールでき、
カーブを使って思い通りの軌道でコントロールしやすかったので良いなと思いました。
ここはこうしたほうがいいよ!などありましたらコメントなどで教えていただけると嬉しいです。
最後まで読んでいただきありがとうございました!