Siggraphのペーパー再現に挑戦してみた

この記事はHoudini Apprentice アドベントカレンダー2024　11日目の記事です。
## はじめに
早いもので、今年もアドカレの季節がやってきました。
先週は東京でSiggraph Asia 2024が開催され、世界の先端を開拓する人たちの成果と情熱に触れて刺激を受けてきました。
"SHEBOBOBOBOBO"（by [@Shebo_chan](https://x.com/Shebo_chan)）

興奮冷めやらぬうちに、発表されたペーパー（小論文）の中で形だけでもなぞれそうなものに取り組んでみることにしました。

## この記事で紹介する内容
Houdiniで"Stripe Embedding: Efficient Maps with Exact Numeric Computation" のStripe再現を試みます。

![stripe_embedding.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233336/60a29abf-88c2-0b5d-08d8-08d1ee4b8fc0.gif)



## 作業環境
Houdini apprentice 20.5.370

## 参考記事
**Stripe Embedding: Efficient Maps with Exact Numeric Computation**
https://asia.siggraph.org/2024/presentation/?id=papers_402&sess=sess103
**Marco Livesu (cino)**　※ページ内にpaperあり
http://pers.ge.imati.cnr.it/livesu/


## いざ作成
### Paperの取得
まず論文PDFを取得します。

内容を読み解きつつ、再現方法を考えていきます。
ちなみに英文のまま読める方はよいのですが、Google翻訳のドキュメント翻訳を使うとPDFのレイアウトを崩さずに翻訳できて便利です。
多少翻訳のおかしい部分があっても、原文と見比べる前提で読めばあまり問題ないと思います。

### 内容の把握
基礎になる理論や先行研究を全て追っていくと量が多すぎるため、ここでは項目程度に留めます。
Siggraphに初心者向けペーパーの読み方解説セッションがあり、事例として解説された論文の一つにStripe Embeddingもありました。
その記憶もたどりながら、自分なりに読み直してみます。

- 電子計算機を用いた誤差を排除した正確な計算はコストが高く、様々な手法で解決と克服を試みられている
- UV展開の初期状態などに用いることが可能な円形状を得る手法があり、事前にStripeを作成する手法がいくつかあるが、Stripe Embeddingは既存のものより早く、堅牢にできる
- Stripeは線形補間することで、変形時に重ならないことが証明されている
- 円形処理した状態から、SLIMなどを用いてUV展開に活用できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233336/8835c94e-4f45-2eb0-5cb9-9a6fb736ec54.png)


### Stripeの作成方法
肝心のStripeの作成は、境界線上の点から扇状に面の選択を繰り返しています。
手順としては以下のとおりで、結構シンプルにできそうだと思いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233336/7c807aae-bf01-0a47-2093-9593b11ae89f.png)
- 円周上（boundary）に始点（seed）を設定
- seedに接続するポイントから扇状にプリミティブを選択
- 隣接するポイントをseedにして繰り返し

## Houdiniでの再現
### ネットワーク
境界線上のポイントをunshared_edgeとして選択しています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233336/71b35a37-22bd-0c92-3008-a2be3666d873.png" width=50%>

### VEX
Wrangleの中身です。
Run Over Detailで実行しています。

```cpp:vex
// 境界線上のポイントグループを取得
int pt_unshared[] = expandpointgroup(0, "pt_unshared");

// 全てのポイントを配列に追加
int ptnum = npoints(0);
int pts[];
for (int i = 0; i < ptnum; ++i) {
    append(pts, i);
}

// 処理済みポイント配列
int finished_seeds[];
// 検索済みポイント配列
int fan_stack[];
// 処理済みプリミティブ配列
int finished_prims[];
// 着色用RGB
vector clr[] = {{1,0,0},{0,1,0},{0,0,1},{1,1,0}};

// 始点ポイント
int seed = pt_unshared[0];
// 直前始点ポイント
int seed_prev;
// fanポイント
int fan_pts[];
// primitive判定ポイント
int prim_detect[];

// プリミティブごとに処理
for (int i = 0; i < ptnum; i++) {
    // seedを直前値として保存
    seed_prev = seed;

    // 始点に接続するポイント配列を作成
    fan_pts = neighbours(0, seed);

    // fan_ptsから過去のseedを除外
    foreach (int pt; finished_seeds) {
        removevalue(fan_pts, pt);
    }

    // fan_stackに fan_pts（次のseed候補）を重複なしなら追加
    foreach (int fan_pt; fan_pts) {
        if(find(fan_stack, fan_pt) < 0) {
            append(fan_stack, fan_pt);
            setpointgroup(0, "fan_grp", fan_pt, 1, "set");
        }
    }
    
    // fan_stackから過去のseedを除外
    foreach (int pt; finished_seeds) {
        removevalue(fan_stack, pt);
    }
    
    
    // プリミティブ判定 
    // seedポイントを配列に追加
    prim_detect = {};
    append(prim_detect, fan_pts);
    append(prim_detect, seed);

    // seedが属するプリミティブ配列を取得
    int prims[] = pointprims(0, seed);

    // 過去の処理済みプリミティブを除外
    foreach (int prim_prev; finished_prims) {
        removevalue(prims, prim_prev);
    }

    i[]@prims = prims;

    // プリミティブグループを作成し、着色
    string group_name = concat("stripe_", sprintf("%d", i)); 
    foreach (int prim_group; prims) {
        setprimgroup(0, group_name, prim_group, 1, "set");
        setprimattrib(0, "Cd", prim_group, clr[i % 4], "set");
        append(finished_prims, prim_group);
    }

    // 次回用処理
    if (find(finished_seeds, seed) < 0) {
        append(finished_seeds, seed);
        setpointgroup(0, "seeds_grp", seed, 1, "set");
    }

    // 次回seed設定
    foreach (int fan; fan_stack) {
        int neighbour_prims[] = pointprims(0, fan); 
        int prim_len = len(neighbour_prims);
        int in_group = 0;

        foreach (int n_prim; neighbour_prims) {
            if (find(finished_prims, n_prim) >= 0) {
                in_group += 1;
            }
        }

        if (in_group > 0 && in_group < prim_len) {
            seed = fan;
            break;
        }
    }
}
```
とりあえず、見た目は冒頭のGIFのとおり、扇状にStripeを作成しつつ動くものができました。
速度は遅いですが、今回は論文に取り組むことが主な動機なので気にしないことにします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233336/d111e3dd-a461-6f82-e91e-79eab61df3b6.png" width=75%>

## おわりに
Siggraph直後のタイミングで、まったくHoudini的なノードの使用をしない内容になってしまいました。
それでもアドカレがあったおかげで背中を押してもらい、学習効果が高まった気がします。

お読みいただきありがとうございました～SHEBOBOBOBO