マンデルブロー集合を生成するときの点列を視覚化してみた？

　前回のマンデルブロー集合に関する記事の投稿後、YouTubeで素晴らしい動画を発見しました。

https://www.youtube.com/watch?v=7MotVcGvFMg

刺激を受けて私も考えてみました。
　マンデルブロー集合は、その生成アルゴリズム

:::note
```math
z_{n+1}=f(z_n)=z_n^2+C,\quad z_0=0
```
```math
z_1=f(z_0),\quad z_2=f(z_1),\quad z_3=f(z_2),\quad ...
```
:::
に従い、複素平面上に点列を生成していきます。そして、その絶対値が$|z_n|>2$となったとき発散と認定し、その時の $n$ の値に対応した色を付けます。反復上限回数を超えても発散しない点を多くの例では黒く塗りつぶし、その集まりがマンデルブロー集合とされます。（次のサイトも参考にさせていただきました。）

https://azisava.sakura.ne.jp/mandelbrot/definition.html

　今回は、複素平面上のその点列を視覚化するという試みです。概要は以下です。

:::note
　１．タッチパネル機能を有効化したディスプレイモジュール(480x320)上にマンデルブロー集合を描きます。
　２．スタイラスペンで画面上の任意の点（境界線近くが面白くて良い）をタッチします。
　３．その点を $z_1=C\quad(z_0=0)$ とした点列が描かれます。
　４．発散する場合はそのときの $n$ が、発散しない場合は反復上限数が最後の $z_n$ とともに画面左下に表示されます。
　５．再描画・画面クリアのため、左上にボタン領域を付けました。
　（なお、タッチ機能の精度がイマイチなので、読取ったy座標の調整をしています。）
:::
　いわゆるカオス図形として知られているような様々な点列が現れ、興味深いです。
![点列0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3189926/74923664-cbc7-11dc-9736-c3b6a137cd44.jpeg)
　数学的に正しいかは、はっきり言ってわかりません。でも、グラフィックスとして美しいと思いますし、最初に紹介した動画に見られるような点列が得られ満足です。使用したESP32ボードとディスプレイモジュールILI9488、その接続については前の投稿

https://qiita.com/Mobu_Kyoto/items/47f432549e073ad77187

をご覧ください。以下にコードを示します。

```c:Mandelbrot_Dots.ino (sketch on ArduinoIDE 2.1.1)
#include <TFT_eSPI.h>
// 必要なPin設定は libraries/TFT_eSPI/User_Setup.hで行う
/* User_Setup.h ---------------------------------------------------
...
(at about 210 line)
#define TFT_MISO 19
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS    5  // Chip select control pin
#define TFT_DC   27  // Data Command control pin
#define TFT_RST  32  // Reset pin (could connect to RST pin)
...
#define TOUCH_CS 4     // Chip select pin (T_CS) of touch screen
...
(at about 360th line)
#define SPI_FREQUENCY  40000000
// #define SPI_FREQUENCY  55000000 // STM32 SPI1 only (SPI2 maximum is 27MHz)
// #define SPI_FREQUENCY  80000000  //Miss a little

// Optional reduced SPI frequency for reading TFT
#define SPI_READ_FREQUENCY  10000000 //20000000

// The XPT2046 requires a lower SPI clock rate of 2.5MHz so we define that here:
#define SPI_TOUCH_FREQUENCY  2000000
...
-----------------------------------------------------------------*/

TFT_eSPI tft = TFT_eSPI();  // also work with RST pin -> board's RST

static uint16_t col[]{
  0xFFFF,  //0
  0xEEEE,  //1
  0xDDDD,  //2
  0xCCCC,  //3
  0xBBBB,  //4
  0xAAAA,  //5
  0x9999,  //6
  0x8888,  //7
};

void grid(void);

int X0 = 360;      // tft.width()/2
int Y0 = 160;      // tft.height()/2
float view = 2.4;  // viewport width
float di;          //view/480;      // real/imaginary calculation step
//float CtoX, CtoY;          // 現在の表示（view）の中心
int repeat, k;             // 反復上限、回数
int re, gr, bl;            // color 0-255
float x, y, zr, zi, R, I;  //複素計算変数
float absZ = 4.0;          // 発散判定
float toRad = PI / 180.0;  //度をラジアンに変換
//PI=3.1415926535897932384626433832795 defined in Libraly

void setup() {
  Serial.begin(115200);
  Serial.println("begin");
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  repeat = 30;
  di = view / 480.0;  // Real and Imaginary number per 1 pixel
  Mandelbrot();
}

void Mandelbrot(void) {
  for (int j = 320 - Y0; j >= -Y0; j--) {
    y = j * di;
    for (int i = -X0; i < 480 - X0; i++) {
      x = i * di;
      zr = 0.0;
      zi = 0.0;
      k = 0;
      do {
        k++;
        R = zr * zr - zi * zi + x;
        I = 2 * zr * zi + y;
        zr = R;
        zi = I;
        if (k >= repeat) break;
      } while ((R * R + I * I) < absZ);
      if (k == repeat) tft.drawPixel(X0 + i, 320 - Y0 - j, 0x0);
      else tft.drawPixel(X0 + i, 320 - Y0 - j, ColorSel(k));
    }
  }
  grid();
}

void grid(void) {
  tft.drawLine(X0 + 100, 0, X0 + 100, tft.height(), 0xFFFF);
  tft.drawLine(X0 - 100, 0, X0 - 100, tft.height(), 0xFFFF);
  tft.drawLine(X0 - 200, 0, X0 - 200, tft.height(), 0xFFFF);
  tft.drawLine(X0 - 300, 0, X0 - 300, tft.height(), 0xFFFF);
  tft.drawLine(0, Y0 + 100, tft.width(), Y0 + 100, 0xFFFF);
  tft.drawLine(0, Y0 - 100, tft.width(), Y0 - 100, 0xFFFF);
  tft.drawLine(X0, 0, X0, tft.height(), 0xFFFF);
  tft.drawLine(0, Y0, tft.width(), Y0, 0xFFFF);
  tft.drawString("0.5", X0 + 101, 0);
  tft.drawString("0", X0 + 1, 0);
  tft.drawString("-1.0", X0 - 199, 0);
  tft.fillRect(0,0,40,40,0x0000);
  tft.drawString("CLEAR", 6, 10);
  tft.drawString("Screen", 2, 22);
}

uint16_t ColorSel(int c) {
  if (c < 8) {
    return col[c];
  } else {
    re = 128 + 126 * cos(c * toRad);
    gr = 128 + 126 * cos((c + 120) * toRad);
    bl = 128 + 126 * cos((c + 240) * toRad);
    return tft.color565(re, gr, bl);
  }
}

uint16_t t_x;  // To store the touch coordinates
uint16_t t_y;
bool pressed;
void loop(void) {
  pressed = false;
  while (!pressed) {
    t_x = 0;
    t_y = 0;
    pressed = tft.getTouch(&t_x, &t_y);
    delay(1);
  }
  delay(10);
  int j = t_y - 150; //本来なら Y0の-160 なのだが、調整が必要で -150 とした。
  int i = t_x - X0;
  if(i < -320 && j > 120) Mandelbrot();
  Serial.printf("x=%d, y=%d\r\n", i, j);
  SeriesDots(i,j);
  delay(100);
}

void SeriesDots(int i, int j) {
  float y = j * di;
  float x = i * di;
  k = 0;
  tft.setCursor(0,304);
  tft.printf("init P %f+(%f)i",x,y);
  tft.fillCircle(X0 + i, 320 - Y0 - j, 2, ColorSel(k));
  zr = 0.0;
  zi = 0.0;
  do {
    k++;
    R = zr * zr - zi * zi + x;
    I = 2 * zr * zi + y;
    tft.fillCircle(X0 + (int)(R / di), 320 - Y0 - (int)(I / di), 2, ColorSel(k));
    if (k >= 500) break;
    zr = R;
    zi = I;
  } while (abs(R * R + I * I) < absZ);

  tft.setCursor(0,312);
  tft.printf("k=%3d: %f+(%f)i",k,R,I);
}
```
最後までお付き合いいただきありがとうございました。