【UE4】Sobolの森 (Random Sobol Cell 2D/3Dについて)

[1]:https://historia.co.jp/ue4petitcon13
[2]:https://youtu.be/E031FMxMJ8Q
[3]:https://www.slideshare.net/EpicGamesJapan/ue417
[4]:https://docs.unrealengine.com/ja/Support/Builds/ReleaseNotes/4_17/index.html
[5]:https://qiita.com/Rinderon/items/32c22b9ea3c6dcc10d77

> random forest と呼ばれる決定木の話ではありません。ご注意を。

[前回][5]に引き続き、[株式会社ヒストリアさん主催の第13回UE4ぷちコン][1]応募後のあれこれなど。
やったこと、やれなかったこと、誤魔化したところなどの整理と備忘録として。

（もしよろしければ、[私の作品の動画][2]も見てくださいませ）

# Random Sobol Cell 関数

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/5d844d58-a9ee-af57-1eeb-94e527ef7ca4.png)

`Random Sobol Cell` は、UE4.17で追加された機能で、[リリースノート][4]の「新規：Sobol ブループリントとマテリアルノード」の画像を見ると何やら良さげな匂いがするのに、使い方が良く分からない。

使用例は [Epic Games Japan のおかずさんの Slide Share][3] の49～52ページ目、くらいしか見当たらない。皆さん、あまり使っていないのかしら？

というわけで、私が分かった範囲でメモを残しておく。

# どうやらこういうものらしい

![20200403_1115.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/da172f63-025f-7d11-0a51-9bb786be2524.gif)

※3Dでやると混乱しそうだったので、2Dでやってます。

【左側】普通の乱数でボックス内にCylinderを16本生成する。
【右側】ボックス内を2×4マス(=Cell)に分けて、`Sobol Random Cell 2D` でCylinderをそれぞれのマスに2本ずつ生成する。つまり、2 × 4 × 2 = 16 本のCylinderが生成されている。

つまり、普通の乱数ではどこかに偏ってしまいがち(左側)だが、あらかじめマス(=Cell)に切って Sobol を使えば、それなりに均一に、それなりにランダムにばら撒ける(右側)、ということのようだ。

[UE4.17のリリースノート][4]に掲載されている画像のように、森などを作るときに便利だとわかる。
実際、[UE4ぷちコン#13][1]に応募した[私の作品][2]でも、`Sobol Random Cell 2D` で森を作っている。

![20200403_1130.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/ae9a617a-8a06-c386-d01c-96ca15444385.png)

これも上記のボックス内にCylinderを生やす例と同様、ボックスの中に `Sobol Random Cell 2D` で木を生やす仕組みを持たせたアクタを、レベル中にたくさん配置してある。
ちなみに、なぜ Foliage を使っていないのかというと、Foliage だとレベルエディットが面倒くさいからである。この道狭いな、とか思ったときに、木のカタマリをザバっと動かしたいからである。

私の使い方：

- **アクタで作る森**： コリジョンとセットのものや、キャラクタがインタラクションしそうなもの。
- **Foliageで作る森**： キャラクタがインタラクションしないようなもの。遠景など。大地に生えている草なら近景でも Foliage で良かろう。

# 使い方（備忘録）

何回使ってもややこしくて、試行錯誤の時間が長くなってしまうので、備忘録として。

- Sobol Random Cell 2D を使いたい範囲を `NumCellsX * NumCellsY` 個のセルに分けたとする。
- 一つのセルの中には NumTreeInCell 個の木を生やしたいとする。

```C++:Blueprintで書くと大きくなってしまうので、C++ダミーコード。
void DoSobol(int NumCellsX, int NumCellsY, int NumTreeInCell)
{
    const int NumCellsTotal = NumCellsX * NumCellsY;
    const FVector2D RandomSeed(RandomFloat(), RandomFloat()); // (0, 0)～(1, 1)の範囲

    // Y方向のセルの個数分のループ
    for (int IndexY = 0; IndexY < NumCellsY; ++IndexY)
    {
        // X方向のセルの個数分のループ
        for (int IndexX = 0; IndexX < NumCellsX; ++IndexX)
        {
            const FVector2D Cell((float)IndexX, (float)IndexY);

            // １つのセルに生やす木の数のループ
            for (int IndexTreeInCell = 0; IndexTreeInCell < NumTreeInCell; ++IndexTreeInCell)
            {
                const FVector2D SobolValue = RandomSobolCell2D( // Sobol 本体
                    IndexTreeInCell,    // int Index
                    NumCellsTotal,      // int NumCells
                    Cell,               // FVector2D Cell
                    RandomSeed);        // FVector2D Seed

                AddInstancedStaticMeshTree(SobolValue); // 木を生やす何らかの関数
            }
        }
    }
}
```

コメントで Sobol 本体と書いたところは、`Random Sobol Cell 2D` の Blueprint ノードと見比べてみていただきたい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/1d23479f-e976-cceb-982b-e0814ae1c1bc.png)

```C++
    const FVector2D SobolValue = RandomSobolCell2D( // Sobol 本体
        IndexTreeInCell,    // int Index
        NumCellsTotal,      // int NumCells
        Cell,               // FVector2D Cell
        RandomSeed);        // FVector2D Seed
```

さて、ここで一安心したいところだが、`Random Sobol Cell 2D/3D` の返り値も多少癖がある。
気分的には、0～1の範囲で返してもらいたいところだが、実際は平気で1.0を超えてくる。
初めて使ったときには、使い方が間違ったのかと思った。だが大丈夫。

- (0, 0)番目のセルに入っている木の場合： (0.0, 0.0)～(1.0, 1.0) の返り値
- (0, 1)番目のセルに入っている木の場合： (0.0, 1.0)～(1.0, 2.0) の返り値
- 　中略
- (2, 4)番目のセルに入っている木の場合： (2.0, 4.0)～(3.0, 5.0) の返り値

という値が返ってくる。

なので、実際の座標に直したいときは、

```C++
    const FVector2D CellSize(BoxSize.X / NumCellsX, BoxSize.Y / NumCellsY);
    const FVector2D TreeLocation2D = SobolValue * CellSize;
```

と、Sobol の返り値にセルのサイズを掛ければよい。