UnityのTerrainでマンデルブロしてみた

Advent Calendarも、はや22日目。
もうすぐクリスマスですね！メリー！

さて、今回はクリスマスも近いということで、Unity上でマンデルブロ集合を表現してみますよぉー
（クリスマス全然関係無い）

#マンデルブロ集合ってなんぞ
1から説明するのはチョイとアレなので。
https://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%B3%E3%83%87%E3%83%AB%E3%83%96%E3%83%AD%E9%9B%86%E5%90%88
↑これです。（Wikipedia先生ありがとう）

数学的なヤツなのでなんか難しそうに見えますが、計算式は↓のコードだけで表現できちゃいます。
実処理部は10行くらい。シンプルですな。

```C#:MandelbrotCalculator.cs
public class MandelbrotCalculator
{
    const int CALCULATE_LIMIT = 50;

    public static float Calculate(double baseX, double baseY)
    {
        double x = 0.0, y = 0.0, cacheX = 0.0, cacheY = 0.0;

        for (int i = 0; i < CALCULATE_LIMIT; i++)
        {
            cacheX = x * x - y * y + baseX;
            cacheY = x * y * 2.0 + baseY;

            if (cacheX * cacheX + cacheY * cacheY > 4.0)
            {
                return (float)i / CALCULATE_LIMIT;
            }

            x = cacheX;
            y = cacheY;
        }
        return 1f;
    }
}
```

何してるかっていうと、

- xy座標を渡す
- 座標の値を使ってマンデルブロの方程式に従って再帰的に計算（forループのとこ）
- 計算の結果、収束したか発散したかを返す（規定回数の計算を行い、値がしきい値を超えなければ収束、超えれば発散）

だけです。

↑の処理では、結果を0〜1のfloatで返しています。
これは単純に発散に至ったときの計算回数を規定の計算回数で割っただけの値で、0に近いほど発散が早く、1は収束したということになります。

この結果を色に反映してグラフ化するといい感じにグラデーションがかかり、Wikipediaに載っているようなキレイな図形になるワケです。

さらに、図形の任意の場所を拡大（座標をもっと細かく分割）して同じ計算を行うと、場所や拡大率によって様々な図形になります。
そして、その図形はどこかしら共通する構造を持っています。

これがマンデルブロ集合です。

#Terrainに反映してみる
Unityの地形オブジェクトであるTerrain。
エディタ上からサクっといじれて、ゲーム制作の頼もしい味方です。
そして、TerrainDataオブジェクトを使えばスクリプトからもカンタンにいじれます。

今回は↑の関数で計算した結果をTerrainに反映し、マンデルブロ集合の立体図を作ってみます。

```C#:MandelbrotTerrain.cs
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

[RequireComponent(typeof(Terrain))]
public class MandelbrotTerrain : MonoBehaviour
{
    [SerializeField]
    double baseX = -1.75;
    [SerializeField]
    double baseY = 0.0;
    [SerializeField]
    double renderArea = 10.0;

    TerrainData terrainData;

    void Start()
    {
        terrainData = GetComponent<Terrain>().terrainData;
        StartCoroutine(UpdateTerrainCoroutine());
    }

    IEnumerator UpdateTerrainCoroutine()
    {
        var heights = new float[terrainData.heightmapWidth, terrainData.heightmapHeight];

        while (true)
        {
            renderArea *= 0.98;
            var startX = baseX - renderArea / 2.0;
            var startY = baseY - renderArea / 2.0;
            var renderAreaPerWidth = renderArea / terrainData.heightmapWidth;
            var renderAreaPerHeight = renderArea / terrainData.heightmapHeight;

            for (int x = 0; x < terrainData.heightmapWidth; x++)
            {
                for (int y = 0; y < terrainData.heightmapHeight; y++)
                {
                    heights[x, y] = MandelbrotCalculator.Calculate(
                        startX + x * renderAreaPerWidth,
                        startY + y * renderAreaPerHeight);
                }
            }

            terrainData.SetHeights(0, 0, heights);

            yield return null;
        }
    }
}
```

TerrainDataがハイトマップを持っているので、そこにマンデルブロの計算結果を入れてます。
あとは、指定座標にズームしていっているだけ。
うん、シンプル。

#実行してみよう
んでは、実行してみましょう。
サンプルプロジェクトはこちら。
https://github.com/akako/mandelbrot

一応動画もペタッとな。
![mandelbrot.gif](https://qiita-image-store.s3.amazonaws.com/0/74903/cb642c3e-a937-fd71-9995-f5dfc130aae6.gif)

#問題点
ちょいと残念な点として、しばらく見ていると地形が潰れてペチャンコになっちゃいます。
（計算の精度の問題だと思うのですが、、、
doubleじゃなくてdecimalに変えると、激重になって手元のマシンじゃマトモに動かなくなるしなぁ。

何か良い方法をご存知の方、ぜひ教えてくださいまし。

#オマケ
マンデルブロ集合は、ズームの基点座標を変えると様々な地形が楽しめます。
（MandelbrotTerrainのプロパティで設定可能です）

http://www.nahee.com/Derbyshire/manguide.html
このへんとかを参考に遊んでみてください^-^v