Anastasia の Circle Pattern VEX tutorial : VEXコード


前回の記事でアルゴリズムの説明をしたので、今回は実際にVEXコードの説明をしていきたいと思います。コードの全てを細かく説明するのは難しいので、大まかに解説していきます。

 

ポイントを円状に配置

まずはAttribute Wrangle でポイントを円状に配置します。Run Over は Detail (only once) にして一度だけ実行させます。

VEXコードは以下のようになります。パラメーター作成、変数宣言、ループと3つに別れています。

int sample = chi(“sample”);
float radius = chf(“radius”);
vector origin = chv(“origin_position”);

float TWO_PI = 3.14159 * 2;
float theta = 0;
float step_angle = TWO_PI / (float)sample;

float x, z;
vector pos;

while(theta < TWO_PI)
{
    x = origin.x + cos(theta) * radius;
    z = origin.z + sin(theta) * radius;
    pos = set(x, origin.y, z);
    addpoint(0, pos);
    theta += step_angle;
}

 

パラメーター作成

以下の部分がパラメーターを作成している部分で、ポイントの数(サンプル数)、半径、円の位置をそれぞれ指定できるようにしてあります。

int sample = chi(“sample”);
float radius = chf(“radius”);
vector origin = chv(“origin_position”);

 

変数宣言

以下の部分は変数を色々と作っています。

TWO_PI : 円周をπで表したもの。円周をラジアンで表すと、半円がπで、一周全部で2πになるので、3.14~ を2倍しています。
theta : θです。英語で書くと theta です。
step_angle : 円周のポイント同士の角度。sampleは整数型で、このままだと除算が出来ないので(float)でキャストしています。
x、z、pos はそれぞれの位置を代入する変数です。

float TWO_PI = 3.14159 * 2;
float theta = 0;
float step_angle = TWO_PI / (float)sample;

float x, z;
vector pos;

 

 

ループ

Whileループで、theta を step_angle ずつ増加させ、一周するまで(theta がTWO_PI よりも小さい)ループ処理をします。x と z にそれぞれの位置を代入するのですが、円の直交座標での定義は以下のようになっています。後は pos に set()関数で X、Y、Z の値を入れてベクトルデータを作り、addpoint() 関数で、上で作った pos の位置にポイントを作成しています。そして最後に theta を step_angle 分増加させて次のイテレーションに行きます。

while(theta < TWO_PI)
{
    x = origin.x + cos(theta) * radius;
    z = origin.z + sin(theta) * radius;
    pos = set(x, origin.y, z);
    addpoint(0, pos);
    theta += step_angle;
}

 

 

SOPで行う場合

円にポイントを配置するくらいならVEXでなくてもSOPで出来てしまいます。Circle SOP で円を作って、Add SOP で Delete Geometry But Keep the Points をONにするだけで完了です。SOPを使えば簡単なんですが、コードを書くことの一番の強みは Houdini 以外の環境でも同じ事ができるということで、ゲームエンジン内で実装してリアルタイムに行うことも可能だと思います。SOP ネットワークで作成して Houdini Engine でゲームエンジンに読み込むとリアルタイム処理には対応していないので・・・。ちなみにアナスタシアはこの作例を最初にC++で実装して、後からVEXで作ったそうなので、ノードを使わずWrangleで行っているというのも納得ができます。

 


 

ポイント同士の接続

ポイントを作った Attribute Wrangle とは別の Attribute Wrangle を作り、ここでポイント同士を接続していきます。

次に作成したポイント同士にライン(プリミティブ)を作成します。以下が全体のコードで、パラメーター、変数宣言、ジオメトリ作成という3つの部分に別れます。

int const_offset = chi(“const_offset”);
int additional_offset = chi(“additional_offset_range”);
float phase = chf(“phase”);

float fit_ptnum = fit(@ptnum, 0, npoints(0), 0, PI*phase);
int sin_offset = (int)(sin(fit_ptnum) * additional_offset);
int neigh = (@ptnum + const_offset + sin_offset) % npoints(0);

int line = addprim(0, “polyline”);
addvertex(0, line, @ptnum);
addvertex(0, line, neigh);

 

パラメーターの作成

以下の3行で3つのパラメーターを作っています。

const_offset : 前回の記事で自身のポイントが接続するポイントをオフセットすると説明しましたが、そのオフセット値です。
additional_offset : サイン関数で返される値に掛け合わせる値で -x から x の値を作る為に使います。サイン波の振幅を決めています。
phase : 円周の中に含めるサイン関数の波の数に影響を与えます。

int const_offset = chi(“const_offset”);
int additional_offset = chi(“additional_offset_range”);
float phase = chf(“phase”);

 

変数宣言

3つの変数を作っています。

fit_ptnum : 円周に配置したポイント全部で波長ひとつ分と考えます。ポイントが100個あれば、その100個で波長一つ分の値が表せるということです。が、ここで phase  を掛け合わせているので、phaseで波長のコントロールして波の数をコントロールできるわけです。
sin_offset : サイン関数から返された値、-x から x の値が返されます。x は additional_offset の値になります。この値は整数型にキャストされ、どのポイントに接続するかというオフセットに使われます。
neigh : 自分自身が接続するポイントの番号が格納されます。const_offset と additional_offset の二つでオフセットがかけられています。%npoints(0)でポイント番号が最大の値を超えたら 0 に戻るようにしています。

float fit_ptnum = fit(@ptnum, 0, npoints(0), 0, PI*phase);
int sin_offset = (int)(sin(fit_ptnum) * additional_offset);
int neigh = (@ptnum + const_offset + sin_offset) % npoints(0);

 

 

文字だけの説明だと絶対にわからないと思うので、fit_ptnum をGIFアニメで表してみました。phase を変更する事は、すなわち赤く囲んだ部分を変更するイメージで、赤い四角の左にポイント0、右側にポイントの最後(全部でポイント100個ならポイント番号99)が来ます。なので phase を増やすと波が多く含まれるという事です。

 

ジオメトリ作成

ライン(プリミティブ)の作成としては、まずaddprim()によりプリミティブを作成します。すると空っぽのプリミティブが作成されプリミティブ番号が割り振られ、これを line という変数に代入します。addvertex() でプリミティブ番号と、追加する頂点の番号を指定して、空っぽのプリミティブに頂点を追加していきます。

int line = addprim(0, “polyline”);
addvertex(0, line, @ptnum);
addvertex(0, line, neigh);

 

するとポイント間にラインが作られ、パラメーターを色々と調整して様々な模様が作れます。

 


 

 

色を付ける

このままだと模様がぐちゃっとしているので、ラインにグラデーションを付けることで、綺麗な模様になります。

まずは Color SOP で Cd アトリビュートを作り、Resample SOP でライン上にポイントを配置します。最後に Attribute Wrangle でランプによりグラデーションを適用します。

Attribute Wrangle の Runver Over は Primitives にしてコードを書きます。

  • pts 配列に primpoints()を使って現在処理中のプリミティブが持つポイントのリストを代入します。
  • Forループで pts 配列の全ポイントをイテレート。
  • fit() 関数でポイントごとに 0 ~ 1 の値をfit に代入。
  • chramp() によってランプパラメーターを作成。下図のようにランプを調整します。
  • color 変数で fit を元に色を作り、setpointattrib() でイテレート中のポイントに color を Cd に割当てます。
int pts[] = primpoints(0, @primnum);
float fit;
vector color;

for(int i=0; i<len(pts); i++){
    fit = fit(i, 0, len(pts), 0, 1);
    fit = chramp(“color_gradient”, fit);    
    color = set(fit, fit, fit);
    setpointattrib(0, “Cd”, pts[i], color, “set”);
}

 

 

ビデオではポイントのある場所にジオメトリを作成しているのですが、模様作成に関係ないので説明は省きます。興味ある人は是非ともHipファイルを見て確認してみましょう!
Hipファイルは以下からダウンロードできます。
https://gum.co/QLRvA