カスタム座標とカスタムフィールドとポイントからフィールド直読み

この記事は上級者向け（呪術廻戦でいう一級に相当）で、扱っているのは以下の三つです。



１
カスタム座標とカスタムフィールドについて説明します。
カスタム座標とはxyzではない座標のことで当方による造語です。ノイズとかフィールドを作るときにたまにほしくなりますがあんまり使わないですしほしいときに調べて数時間つぶれたりします。その時間が無駄すぎるのでここに書きました。ほぼコピペで済む感じになってます。

２
カスタムフィールドはdensityとかvelとかのあらかじめ用意されているものではないフィールドのことです。漂う流体を作ってその中だけ減衰したいときとかに使えます。gas advect fieldを使うとカスタムフィールドもvelで動くようになります。もっと有用な使い方もありますがコンプラ的に言っちゃダメなやつな気がするので普通に調べられる範囲のものだけ書いてます。
あんまり詳しく書いてないので詳細は付属ファイルを見てください。

３
ボリュームを使わずポイントからフィールドを読む方法
あんまり詳しく書いてないので詳細は付属ファイルを見てください。


sample file

https://www.dropbox.com/s/xo1utdaylt3rd3k/polar.hiplc?dl=0




# カスタム座標

１極座標

距離と角度を使った座標系で　r　θ（シータ）φ（ファイ）の三項で表されますが式はこんな感じになってます。
θ= acos(z/radius);
φ = atan2(y,x);

これをwrangleで書くとこんな感じです。
theta = acos(z/rad);
phi = atan2(y,x);

あとこの記事では使わないですが極座標から直交座標の変換式はこんな感じです。
x = rad*sin(θ) * cos(φ);
y = rad*sin(θ) * sin(φ);
z = rad*cos(θ) ;


極座標系を使ってノイズを作るとこういう感じになります。（色は別）
![polar_coord.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316376/d352569c-dca1-1f27-a4a2-9b44e1157812.png)

（右）

vector p = v@P;
float rad = length(p);
float theta = acos(p.y/rad);
float phi = atan2(p.z,p.x) ;
vector polar = set(rad , theta , phi) ;

float cd = anoise(polar);
@Cd *= cd ;


位置によってノイズが途切れるのですが元々のxyzを入れ替えると回避できる場合があります。例えばyとzを入れ替えると底面の跡切れを回避できます。あらゆる箇所で回避するのは無理です。
（真ん中）

vector p = v@P;
float rad = length(p);
float theta = acos(     p.z     /rad);
float phi = atan2(      p.y     ,p.x) ;
vector polar = set(rad , theta , phi) ;

float cd = anoise(polar);
@Cd *= cd ;


また、距離をいじると横長の極座標になります。
（左）

vector p = v@P      + set(10,0,0)    ;
float rad = length(v@P      * set(1,2,2)  );
float theta = acos(p.y/rad);
float phi = atan2(p.z,p.x) ;
vector polar = set(rad , theta , phi) ;
float cd = anoise(polar);
@Cd *= cd ;

角度は原点をベースに作られるので原点以外で同じようなノイズが欲しいときは極座標を作る元座標にそういう処理をしてください。

ところで極座標はvopでpolarというノードを使ってもつくることができますのでvop派の人はそっちを使った方が楽でしょう。当方は触るたびにvopに潜るとかめんどくさいのでwrangleで書きます。

２　カーブ座標

カーブ座標なんて名前の座標系はたぶん存在しないんですがカーブをもとに座標系を作ってるのでここではそう呼びます。応用すればサーフィス座標とかもつくれると思いますがここではやってません。

カーブの座標系を書く前に微妙に準備が必要です。

1　resampleSOPをつくって下の方にあるcurveuとtangentuにチェックを入れる
2　polyframeSOPをつくってのtangentvにチェックを入れる
3　wrangleをつくって以下のスクリプトをコピペ

v@tangentv = normalize(v@tangentv) ;
v@tangentu = normalize(v@tangentu);
v@up = normalize(cross(v@tangentv , v@tangentu));

ここでつくられたアトリビュートはtangentu , tangentv , up , curveuの四つです。最初の３つは座標系を作るのに必要です。

これを使って三つ座標系を作りました。
１　カーブ座標のxyとcurveuを使った座標

int pr;vector uv;
xyzdist(1,v@P , pr,uv);
float cu = primuv(1,"curveu",pr,uv);

vector tangentv =   primuv(1,"tangentv",pr,uv);
vector up =  primuv(1,"up",pr,uv);
vector tangentu = primuv(1,"tangentu",pr,uv);
vector p = primuv(1,"P",pr,uv);

matrix3 m3 = set(  tangentv , up , tangentu );
vector pos = (v@P -p) * m3 ;

pos *= 2 ;
pos.z = cu * 13 + @Time * 1 ;
float cd = anoise(pos);
@Cd *= cd *31;
![curve_coord1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316376/071c815f-511b-5bb4-1f70-1ebebc9bbc9a.png)


２　curveuと半径と角度を使った座標

int pr;vector uv;
xyzdist(1,v@P , pr,uv);
vector p = primuv(1,"P" , pr,uv);
vector tangentv = primuv(1,"tangentv" , pr,uv);
float cu = primuv(1,"curveu" , pr,uv);

vector p2 = v@P -p ;
float rad = length(p2);

float theta = acos(dot(tangentv , -normalize(p2))) ;
theta = degrees(theta)/360 * 30;
cu *= 9;
vector coord = set(cu , rad , theta ) ;
coord*=2;
coord.x += @Time ;
float cd = anoise ( coord );
@Cd += cd ;

![curve_coord2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316376/eeeb04d2-8e8a-8c5b-5e62-4ee123619491.png)


３　カーブの最近接ポイントのポジションとcurveuを使った座標

int pr;vector uv;
xyzdist(1,v@P , pr,uv);
vector p_= primuv(1,"P" , pr,uv);
float cu = primuv(1,"curveu" , pr,uv);

vector p = v@P -p_ ;
p*=3;
p.x = cu*12+@Time;

float cd = anoise(p );
@Cd += cd ;

![curve_coord3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316376/e2a272a1-75d8-0d9f-15d0-bbb3e9c8779d.png)


端的な解説
全部同じに見えますし止めだと実際同じなんですがtimeとかで動かすと違いが出てきます。
xuzdistとprimuvでカーブの最近接ポイントのアトリビュートを持ってきてます。
１は元の座標をカーブ座標に変換したうえでノイズをかけてます。
（座標変換は変換先のマトリクスを掛けるだけでできます。）
２はcurveuを時間で動かすとカーブ方向に流れ、距離を動かすと内側から広がるような動きをします。角度を動かすと回転しますがここでは１８０度までしか考慮されてないので思った感じには回りません。



# カスタムフィールド
つくるのに苦戦した気がしたのですがやってみたら普通にvolumesource作れましたがせっかく調べたので別の方法も載せます。
方法は三つあります。

１　volumesourceにそのまま入れる

２ gasmatchfieldを使ってからそのフィールドをwrangleかなんかでいじる。gasmatchfieldだけだとフィールドはつくられないみたいです。

３　sopvectorfieldとかsopscalarfieldを使う
つくられはするんですが機能しないです。



# ポイントからフィールドを直読みする方法
sopgeoを作りsoppathを指定しset initialにする。
そのあとgasparticlefieldを使ってそれぞれのフィールドを発生させる。ただしこの方法だとカスタムフィールドは作れない様子。
それとこの方法だと１フレ目が元ジオメトリのサイズを考慮しないのでこれをさけるためにgasresizefluiddynamicを使います。max bounds のチェックを外し、boundsタブのtrack objectをdopにしてからdop pathをGeometryと打ちます。

![pointToField.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316376/9e1e7056-8d65-0934-2c25-edbba6a76318.png)