今回のお題
「バラバラな向き・比率の長方形を一定の方向に沿ってUV展開できない?」という話が出たので実装してみました。言葉では難しいので画像でなんとなくご理解いただけるのではと思います。
今回ポイントとなるのは以下のふたつ。
- 長方形の長手方向(長辺)を判定する
- ベクトルの回転をコントロールする
以上。やっていきましょう。
サンプルファイル
ノード解説
順を追って見ていきましょう。
ランダムなサイズ・方向の長方形を複数作る
特に難しいところはありませんね。
ランダムサイズはscale
アトリビュート、ランダム回転をorient
アトリビュートで設定しています。
ランダムに回転させるとき、ぼくはAttribute Randomize SOPでクオータニオンを作成することが多いですが、今回はWrangleでスケール調整していたのでついでにワンライナーで書いています
@scale = set(fit01(rand(@ptnum), 1, 3), 0, fit01(rand(@ptnum+1), 1, 2)); p@orient = quaternion(radians(rand(@ptnum) * 360), {0, 1, 0});
この部分はFile SOPで置き換えてジオメトリを読み込んでもよいですし、HDA化して入力として受け取ってもよいでしょう。
実装前に方法を言語化する
ここが最も大切な工程です。なんとなくそれっぽいノードを繋いでいってもうまくいかないことが多いので、ここは時間をかけましょう。
- 長方形をひとつづつ取り出す
- 長方形の長手方向(長辺)を取り出す
- X座標
{1, 0, 0}
と長辺がなす角を算出する - なす角の分だけ逆方向に回転する
- UV展開をする
- 元の形状にUV情報を転写する
これくらい分割すると、それに対応するノードも見えてきます。
行けそうですね。
長方形をひとつづつ取り出す
これはFor-Each Connected Piece
でいいでしょう。
長方形の長手方向(長辺)を取り出す
ここは少し工夫が必要です。
Convert Line SOP
長方形をラインに変更します。
Mesure SOP
TypeをPerimeter
にして、辺の長さを取得します。
プリミティブアトリビュートPerimeter
に格納されます
Sort SOP
Primitive SortをBy Attribute
にし、AttributeをPerimeter
とします。これでPerimeter
が大きい順にPrimitive Numberがソートされます。
Blast SOP
Groupを0
に、Group TypeをDelete Non Selected
にします。これで長辺が取得できました。
Groupを0
にすると一見ハードコードに思えますが、事前にSortしてるので一意的に決定します
X座標と長辺がなす角を算出する
VEXで書いちゃいましょう。Attribute Wrangle SOPを作成し、Run OverをDetail(only onece)
に、ノード名をattribwrangle_dir_radにします。
正規化されたベクトル同士の内積はcosθ
になるので、内積の値をacos
関数に渡すことでなす角θを取得します
v@dir = normalize(point(0, "P", 1) - point(0, "P", 0)); @rad = acos(dot(@dir,{1,0,0}));
なす角の分だけ逆方向に回転する
ふたつの工程に分けて説明します。
長辺の情報を長方形に転写する
Attribute Wrangle SOPを作成し、Run OverをDetail(only onece)
に、ノード名をattribwrangle_cross_set_radにします。ここで逆回転用の角度rad
アトリビュートを生成しています。
転写という言葉を使っていますが、原理としてはdetail
関数で入力1に繋いだ長辺のアトリビュートにアクセスし、自身(長方形)のアトリビュートに代入しています。
vector crossprod = cross(detail(1, "dir"), {1,0,0}); if(crossprod.y > 0){ @rad = detail(1, "rad"); }else{ @rad = radians(360) - detail(1, "rad"); }
上記コードで外積を使っていますが、この理由は別に解説用のネットワークを組んだのでそちらで詳しく後述します。
ジオメトリを回転する
VOPで作りましたがもちろんWrangleでも作れます。
※ Point Wrangleで実装した場合のコードはこちら
@P = qrotate(quaternion(detail(0, "rad"), {0, 1, 0}), @P);
これですべての長方形をX軸方向へ向けることができました。
UV展開をする
UV Texture SOPを作成し、Texture TypeをOrthographic
にします。Attribute ClassをPoint
にしていますが、デフォルトのVertex
でも大丈夫です。
元の形状にUV情報を転写する
ご覧の通り長方形が全部真横を向いているので、元のランダムな角度の形状にuv
アトリビュートを転写します。Attribute Copy SOPを利用しましょう。
これで完成です!
なす角の算出と内積・外積についての詳しい解説
解説用のGeometry、LINE_ROTATIONを用意しました。
merge1ノードにディスプレイフラグを立てて再生してみると、0~360度までラインが回転します。このラインを毎フレーム逆回転させて、X軸方向から移動しないモーションが最終目的です。
失敗バージョン
逆回転用のアトリビュートrad
を設定するために、pointwrangle_rad_ngノードに下記コードを書きました。
- 回転しているラインとベースとなるライン(X軸方向)のベクトルを取得
- 上記のベクトルから内積を取得
- 内積を
acos
関数に渡すとなす角が得られるので、それをマイナス1倍して逆回転用の角度を取得、それをrad
に代入
v@dir_rot = normalize(point(0, "P", 1) - point(0, "P", 0)); v@dir_base = normalize(point(1, "P", 1) - point(1, "P", 0)); float d = dot(@dir_rot, @dir_base); float r = acos(d)* (-1.0); @rad = r;
問題なさそうですが、実はこれNGなんですね。
Point VOPで回転させてみましょう。(回転用のノードは先に説明したVOPと全く同一です)
ご覧の通り、0~180度の範囲ではちゃんと逆回転が効いていてリセットされてますが、180~360度の範囲ではラインが動いてしまっています。これの理屈を簡単に説明しましょう。
画像の太い赤線がcosθ
の値です。45度と315度で同じ値になっていますね。つまりcos
関数は0~180度までしか一意的に判断できません。
それを解決するために外積を利用します。
上記動画を見てのとおり、外積ベクトル(黄色いベクター)のY値が0~180度の間はマイナス、180~360度の間はプラスになっています。この値を使えば逆回転用のアトリビュートrad
を0~360度の間で設定できそうです。
成功バージョン
ご覧の通り0~360度の範囲で逆回転がかかり、ラインが移動していないのがわかるかと思います。
VEXは下記の通りです。外積のY値をもとにrad
の値を変更しています。
vector dir_rot = normalize(point(0, "P", 1) - point(0, "P", 0)); vector dir_base = normalize(point(1, "P", 1) - point(1, "P", 0)); float d = dot(dir_rot, dir_base); float r = acos(d)* (-1.0); vector crossprod = cross(dir_rot, dir_base); if(crossprod.y < 0){ @rad = r; }else{ @rad = radians(360) - r; }
まとめ
今回は少々数学的なお話が出てきましたが、ベクトルを自由に回転させることはHoudiniではよく使うテクニックなので慣れておくといいかと思います。