ミスティーク変身エフェクト

#はじめに
こんにちは。フジヤマタカシです。
今回は割とありがちですが、X-menのミスティークの変身エフェクトをやってみたいと思います。

![e2awq-hycol.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321297/5fc0c36b-2059-7a97-4767-e1e14346ee58.gif)
![Screenshot from 2019-12-11 14-56-27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321297/c32f22a2-70c7-fd1c-7aac-131d379da321.png)

所でチュートリアルとは関係ありませんが、現映画でミスティークを演じるジェニファー・ローレンスはなんか違うって思いませんか？彼女は名女優だとは思いますがミスティークかと言われるとどうも。ハンガーゲームの印象が強いからですかね？でも調べてみるとX-men First Classのほうがハンガーゲームより先なんですね。　それから個人的にはX-men3でミスティークがキュアによってミュータント能力を失って普通の人間に戻るシーンが印象に残ってるからそう思うのかもしれません。あれは映画史に残るエロ美しいシーンだと思ってます。~~アレを見たとき・・・フフ・・・なんていうか・・・・その・・・下品何ですが・・・「モナリザを見た吉良吉影状態」になってしまいましてね。~~

#準備
ミスティークエフェクトは検索すればたくさんテストやチュートリアルが出てきますが、止まってるものが多いのでここでは動いてるものに対してやりましょう。
そのために歩く人2人のabcファイルを用意しました。これは[Mixamo](https://www.mixamo.com)からダウンロードして無理やり体格を合わせました。
2つのアニメーションはTimeShiftとTransformのエクスプレッションで永久に歩き続けるようにしてあります。
![sys8o-gkujj.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321297/3495a2d7-c98d-2c17-557a-4f2ed0407da1.gif)

#羽のためのポイントの作成
男性モデルから女性モデルへトランスフォームを行うとして男性モデルから「閉じたシェイプ」服と皮膚などの重なりを取り払ったモデルを作ります。
![Screenshot from 2019-12-11 15-14-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321297/ba1d1d69-462c-8f9a-7821-ac779b8ef7fa.png)
VDBにしてポリゴンにコンバートして元のオブジェクトにRayをかければだいたい何とかなると思います。（襟は今回気にしない）困難なモデルもあったりしますが。

そして、そのモデルに対しScatterをあてます。これをSolver内で変身エリアが広がる計算をします。
![Screenshot from 2019-12-11 15-46-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321297/280b09ff-51e2-04cf-1439-9bf4af2f5e07.png)
今回欲しいのはactive(変換後)とactiveTime（変換タイム）とdir（広がりの方向）なのinitializeのwrangle内でこの様にアトリビュートを設定しました。

```C#
int ptnum = pcfind(1,"P",@P,100,1)[0];

vector otherP = point(1,"P",ptnum);
if( distance(@P,otherP) < 0.005 ){
    i@active = 1;
    @activateTime = 1;
    v@dir = normalize(@P - otherP);
}else{
    i@active = 0;
    @activateTime = 9999;
    v@dir = 0;
}

@Cd = @active;
```

#広がる計算
activeのポイントだけを抽出してそれをpcfindで拾ってアクティブにするよくあるやり方です。
![Screenshot from 2019-12-11 15-59-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321297/09ead47e-92e5-4270-9614-ece3883063dc.png)

```C#
int ptnum = pcfind(1,"P",@P,100,1)[0];

vector otherP = point(1,"P",ptnum);
if( distance(@P,otherP) < 0.01 ){
    i@active = 1;
    @activateTime = @Frame;
    
    int handle = pcopen(1,"P",@P,0.05,10);
    v@dir = normalize(@P - pcfilter(handle,"P"));
}

@Cd = @active;
```
dirの取得だけpcopenを使っているのは方向をなめらかにするためです。

このシミュレーションは全体に広がったあたりのフレームでTimeShiftを使って止めます。
Solverのactiveの広がりをそのまま使う方法もありますが、[1年前の記事](https://qiita.com/takashiFujiyama/items/7368cd2062341bc49b30)で書いたようにアニメーション(Time Dependency)してるポイントにCopyを使うのを避けるためです。　代わりにactiveTimeアトリビュートを用意してるので、後にこれを元にアニメーションを行います。この方法だとactiveTimeを編集することでタイミングの調整が容易になります。

＃羽コピー植付け
羽をCopyで植えますが、その前に必要アトリビュートを整理しておきます。

```
i@id = @ptnum;
v@up = v@dir;
v@center = @P;
v@normal = @N;
```

そして、2ndインプットと3rdインプット、それぞれにキャラ１と２に最初のフレームオブジェクトを入れてxyzdistによってprimとprimuvを取得します。
これで後に動いてるモデルの位置を参照することができます。

```C#
xyzdist(1,@P,i@hitprim1,v@hitprimuv1);
xyzdist(2,@P,i@hitprim2,v@hitprimuv2);
```

そして、適当に作った羽をCopyします。
![Screenshot from 2019-12-11 16-26-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321297/d62d38c2-5cb4-4cdf-e8d8-aef275f66926.png)

#Wrangleでアニメーションさせる。
この羽それぞれにはポイントが持っていたアトリビュートを引き継いでいるので、それを利用してアニメーションさせます。
まずは変形させやすくするためにピボットをずらします。予め作っていたcenterアトリビュートを使います。

```
@P -= v@center;
@P += v@center;
```

この様にしてこの間で変形を行います。
まずはタイミングの設定。

```C#
float transition = fit( @Frame - @activateTime * ch("speed") + pow(rand(@id),12) * 0 ,0,10,0,1);
```

transitionの0から１への変化がAからBへ変化で、　スピードで変形が広がるスピードを調整できるようにしてます。
そのtransition変数を利用して回転とスケールをアニメーションさせてやります。
回転は-90度から90度に回転させるだけ。スケールはランプで０から元のサイズに拡大してまた０になるようにしてるだけです。

```C#
///Rot///
vector axis = normalize(cross(v@normal,v@dir));
matrix3 rot = ident();
rotate(rot,fit01(transition,PI * -0.5, PI * 0.5),axis);
@P *= rot;
///Scale////
float size = chramp("size",transition);
@P *= size * fit01(pow(rand(@id + 333),20),0.5,1.5);
```

![kgz9d-oeqnp.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321297/e5419277-617c-2de7-2ebe-8a495ce44e8a.gif)

さらにベンドを加えます。ベンドに関しては以前書いたので、詳しくはそちらの記事を参考にしてください。
[TwistとBendデフォーメーションの重ね掛け](https://qiita.com/takashiFujiyama/items/79e1b3f191b737777aa4)

```C#
@P -= v@center;

float transition = fit( @Frame - @activateTime * ch("speed") + pow(rand(@id),12) * 0 ,0,10,0,1);
///Bend///
`chs("../Bend_fn/snippet")`
@P *= fn_bend(@P, 0, v@normal, cross(v@normal, v@dir), ch("../grid1/sizey"), sin(transition * PI * 2) * PI * 0.5);
///Rot///
vector axis = normalize(cross(v@normal,v@dir));
matrix3 rot = ident();
rotate(rot,fit01(transition,PI * -0.5, PI * 0.5),axis);
@P *= rot;
///Scale////
float size = chramp("size",transition);
@P *= size * fit01(pow(rand(@id + 333),20),0.5,1.5);
///rePosition///
@P += v@center;
```

![et0ym-5umq0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321297/5c4af44b-e9af-f50e-5d1f-1eed09898379.gif)

あとはこれを動いてるキャラに当て込むだけですが、位置だけならprimuvを利用して`primuv(2,"P",i@hitprim1,v@hitprimuv1)`みたいに出来ますが、動いたときにサーフェイスの向きも変わっているので回転も考慮する必要があります。ここで[Fujitaさんのマトリックスの記事](http://blog.livedoor.jp/fujitarium/archives/52478835.html#more)にあった法線とタンジェントを利用した回転制御を使います。
インプット２に動いてないキャラ１、インプット３に動いているキャラ１、インプット４に動いているキャラ２を入れます。
動いてるキャラ２はキャラ１とほぼ同じなのでとりあえず動いてるキャラ１へオリエントさせる事にします。

```C#
matrix3 surfMat(int input, prim;vector uv){
    vector z = normalize(primuv(input,"N",prim,uv));
    vector x = normalize( cross(primuv(input,"tangentu",prim,uv),z ) );
    vector y = normalize( cross( z,x ) );
    matrix3 trans = set(x,y,z);
    return trans;
}
```

サーフェイスのタンジェントから回転マトリックスを作るための関数です。
これを使い止まってるキャラ１と動いてるキャラ２の回転マトリックスを取得します。

```C#
matrix3 rot_ch0 = surfMat(1, i@hitprim1,v@hitprimuv1);
matrix3 rot_ch1 = surfMat(2, i@hitprim1,v@hitprimuv1);
```

そうしたら、後はインバート動いてないと動いてるマトリックスを順に掛けるだけです。

```C#
@P *= invert(rot_ch0) * rot_ch1;
```

ポジションはキャラ１とキャラ２の位置を取得してtransition変数でlerpさせましょう。

```C#
vector pos_ch1 = primuv(2,"P",i@hitprim1,v@hitprimuv1);
vector pos_ch2 = primuv(3,"P",i@hitprim2,v@hitprimuv2);
@P += lerp(pos_ch1,pos_ch2,transition);
```

カラーも似たような事をします。

```C#
vector Cd_ch1 = primuv(2,"Cd",i@hitprim1,v@hitprimuv1);
vector Cd_ch2 = primuv(3,"Cd",i@hitprim2,v@hitprimuv2);
@Cd = lerp(Cd_ch1,Cd_ch2,transition);
```

元のキャラアニメーションモデルにAlphaで適当にフェードさせてこうなります。

![e2awq-hycol.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321297/5fc0c36b-2059-7a97-4767-e1e14346ee58.gif)
最終的なアニメーションコードはこうなります。

```C#
@P -= v@center;

float transition = fit( @Frame - @activateTime * ch("speed") + pow(rand(@id),12) * 0 ,0,10,0,1);
///Bend///
`chs("../Bend_fn/snippet")`
@P *= fn_bend(@P, 0, v@normal, cross(v@normal, v@dir), ch("../grid1/sizey"), sin(transition * PI * 2) * PI * 0.5);
///Rot///
vector axis = normalize(cross(v@normal,v@dir));
matrix3 rot = ident();
rotate(rot,fit01(transition,PI * -0.5, PI * 0.5),axis);
@P *= rot;
///Scale////
float size = chramp("size",transition);
@P *= size * fit01(pow(rand(@id + 333),20),0.5,1.5);
///reOrient///
matrix3 surfMat(int input, prim;vector uv){
    vector z = normalize(primuv(input,"N",prim,uv));
    vector x = normalize( cross(primuv(input,"tangentu",prim,uv),z ) );
    vector y = normalize( cross( z,x ) );
    matrix3 trans = set(x,y,z);
    return trans;
}
matrix3 rot_ch0 = surfMat(1, i@hitprim1,v@hitprimuv1);
matrix3 rot_ch1 = surfMat(2, i@hitprim1,v@hitprimuv1);
@P *= invert(rot_ch0) * rot_ch1;
///rePosition///
vector pos_ch1 = primuv(2,"P",i@hitprim1,v@hitprimuv1);
vector pos_ch2 = primuv(3,"P",i@hitprim2,v@hitprimuv2);
@P += lerp(pos_ch1,pos_ch2,transition);

///Color///
vector Cd_ch1 = primuv(2,"Cd",i@hitprim1,v@hitprimuv1);
vector Cd_ch2 = primuv(3,"Cd",i@hitprim2,v@hitprimuv2);
@Cd = lerp(Cd_ch1,Cd_ch2,transition);
```
作成したファイルはこちらになります。
https://drive.google.com/open?id=1HTN5-dU6mkiewF2u1AnPsSBU3Z9iuMjB

では今回は以上になります。ここまで読んでいただきありがとうございます！