プロローグ
2019/4/6に第1回HoudiniAidを開催しました。振り返り記事はこちら。
本記事ではそこで僕が質問した内容をまとめようと思います。ありがたいことに勉強会やTwitterを通して様々な方がアドバイスをくださり、最適な解を得ることができました。この場を借りてお礼申し上げます。
お題
SubdivideやResampleによって増加するポイントを判定したい
以上。やっていきましょう。
サンプルファイル
SubdivideとResampleの性質
まずはSubdivideとResampleの性質について確認しておきます。
Subdivide
Primitiveにアトリビュートを与えた場合がわかりやすいです。
分割された後もオリジナルのアトリビュートを引き継ぎます。
ポイントアトリビュートの場合はPrimitiveが参照しているPointで最初に見つかったものを引き継ぐのかな?とにらんでいます
Resample
既存のアトリビュートを線形的に補完します。
これらの挙動が便利なときもありますが、不便なところもあります。
目標
分割前の状態で各Pointに1
を与えていたとしたら
分割後に増えたPointは0
を持っていてほしいということです。
元のシーンファイル
僕が持って行ったシーンでも一応できてはいました。
流れとしては簡単です。
- GroupCreateSOPで
old_pointsグループ
を分割前のジオメトリに付与します。対象はすべてのポイントです。 - GroupTransferSOPでグループを転写します。
しかしこのGroupTransferSOPが気に入りません。転写の影響範囲を決定するDistance Threshold
はジオメトリの形状によって手作業で数値を変更する必要があります。
Distance Thresholdの値が0
でも反応してくれれば問題ないのですが、0
だと転写してくれません。またこの値を0.0001
などの小さな値にするというのはダサいので避けたいところです
プロシージャルに、一対一対応できるような実装にしたいですね
試行錯誤期
Subdivideで分割されたPointと元のPointを判定するやつ、idtopoint関数で行けたぽいんだけど、Resampleはダメだった。
— エターナルめんたいこ (@kickbase) 2019年4月17日
原理としては一回元の状態でidアトリビュートを生成しておいて、分割してからidtopoint使うと空振りしたとこが-1を返すのを利用してる。 #Houdinihttps://t.co/E7Oqcor6jS pic.twitter.com/qm8Q0CIBUw
idtopoint関数
を使うのは割とアリな気がしたんですけど、ツイートの通りResampleではできなかったので撃沈。
以前めんたいこさん@kickbaseのやっていた
— 佐藤 (@mhpzlry0ftTQG1R) 2019年4月20日
Subdivideで分割されたPointと元のPointを判定するやつ@ptnum >= npoints(1)
でもいい感じではなかろうか pic.twitter.com/cKN6XxJpBz
佐藤さんに上記アドバイスをいただきました。残念ながらLineではうまくいかなかったのですが、オリジナルのポイント総数とポイントナンバーを比較するというアイデアは後々どこかで使えそうな気がします。ありがとうございました!
できたけどカッコ悪い実装
ロジック的にも間違いはないし、判定はできるけどクールじゃない実装。
特にPointWrangle内でfor
回すのがとてもダサいですね。
i@group_newpoints = 1; for(int i=0; i<npoints(1); i++){ vector pos_origin = point(1, "P", i); if (@P == pos_origin) { i@group_newpoints = 0; break; }; };
コードの考え方は下記の通り。
- コードの最初で
newpointsグループ
を定義 - 分割前の総ポイント数だけ
for
を回す - PointWrangle第二入力からポイントの位置を取得
- ジオメトリのポイントと位置が同じだったら(つまり分割前と同じ位置だったら)グループから外す
最適解
Houdini上で試せてないですが、おそらくこれでいけると思います。
— 大翔士 (@d658t) 2019年4月22日
あと今だと位置が完全一致で判定してますが、しきい値を持たせてnearpointで判定するのもありだと思いました(画像2枚目) pic.twitter.com/4bJHpw03By
大翔士さんにいただいたアドバイスで最適解を得ることができました。うーんスマート。ありがとうございました!
i@group_newpoints = 1; int np = nearpoint(1, @P, 0); if (np >= 0) i@group_newpoints = 0;
ループも消えてスッキリしました。これぞVEX!って感じですね。
nearpoint関数
はジオメトリに対して位置を指定し、指定した距離を検索し、見つかればポイントナンバーを。見つからなければ-1
を返します。
ここでは分割前のジオメトリに対し、分割後のポイントを中心に距離ゼロで検索しているのがポイントです。これで同ポジションかどうかが判定できますね。*1
まとめ
今回僕が持って行った質問は、行き詰ったというよりよりいいやり方があるんじゃないか?というものでした。諸先輩方のアドバイスもあり、Houdiniらしい解決法にたどり着けました。
様々なアプローチがあるところがHoudiniの面白いところですね。
参考リンク
*1:Attribute Transferは距離ゼロでは転写できないので中身が違うのかもしれません