マンデルブロ集合のお友達のバーニングシップフラクタル君を見よう


## これがバーニングシップフラクタルだ！

### サイト

PCで見ると綺麗です。

https://mogamoga1024.github.io/BurningShipFractal/

### 全体

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649673/d012ac0d-8807-c0e7-4aa0-df5805c84be5.png)

### 船が燃えているように見える

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649673/ab645446-7790-daf9-3b02-e9aae92a6fa4.png)

### 癖になる気持ち悪さ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649673/31119b83-43cf-eed3-4d3c-862dde8cb7a7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649673/445ad14d-dcdc-41b1-1b07-d9d57c54a293.png)

## ソース

ほぼChatGPT3.5君が作ってくれました。

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Burning Ship Fractal</title>
    <style>
        * {
            user-select: none;
        }
        canvas {
            border: 1px solid #000;
        }
    </style>
</head>
<body>
    <div>左クリックで拡大、右クリックで縮小</div>
    <canvas id="burningShipCanvas" width="800" height="600"></canvas>

    <script>
        // Canvas要素と2Dコンテキストの取得
        const canvas = document.getElementById('burningShipCanvas');
        const ctx = canvas.getContext('2d');

        // バーニングシップフラクタルのパラメータ
        let centerX = -0.5;
        let centerY = 0;
        const history = [{centerX, centerY}];
        let zoom = 1;

        // バーニングシップフラクタルの描画
        function drawBurningShipFractal() {
            const width = canvas.width;
            const height = canvas.height;
            const imageData = ctx.getImageData(0, 0, width, height);
            const data = imageData.data;

            const MAX_ITERATIONS = 100;

            for (let y = 0; y < height; y++) {
                for (let x = 0; x < width; x++) {
                    // バーニングシップフラクタルの計算
                    let real = (x - width / 2) * 4 / (width * zoom) + centerX;
                    let imag = (y - height / 2) * 4 / (height * zoom) + centerY;
                    let cre = Math.abs(real);
                    let cim = Math.abs(imag);
                    let count = 0;

                    while (count < MAX_ITERATIONS) {
                        const re2 = cre * cre;
                        const im2 = cim * cim;

                        if (re2 + im2 > 16) {
                            break;
                        }

                        cim = Math.abs(2 * cre * cim + imag);
                        cre = Math.abs(re2 - im2 + real);

                        count++;
                    }

                    // ピクセルの色を設定
                    const hue = count === MAX_ITERATIONS ? 0 : (count / MAX_ITERATIONS) * 360;
                    const saturation = 100;
                    const lightness = count === MAX_ITERATIONS ? 0 : 50;
                    const rgb = hslToRgb(hue, saturation, lightness);

                    const pixelIndex = (y * width + x) * 4;
                    data[pixelIndex] = rgb[0];
                    data[pixelIndex + 1] = rgb[1];
                    data[pixelIndex + 2] = rgb[2];
                    data[pixelIndex + 3] = 255;  // アルファチャンネル
                }
            }

            // Canvasに描画
            ctx.putImageData(imageData, 0, 0);
        }

        canvas.addEventListener('mousedown', function (e) {
            const rect = canvas.getBoundingClientRect();
            const mouseX = e.clientX - rect.left;
            const mouseY = e.clientY - rect.top;

            // 左クリック
            if (e.button === 0) {
                history.push({centerX, centerY});
                centerX += (mouseX - canvas.width / 2) * 4 / (canvas.width * zoom);
                centerY += (mouseY - canvas.height / 2) * 4 / (canvas.height * zoom);
                zoom *= 2;
            }
            // 右クリック
            else if (e.button === 2 && history.length > 1) {
                const prev = history.pop();
                centerX = prev.centerX;
                centerY = prev.centerY;
                zoom /= 2;
            }
            drawBurningShipFractal();
        });

        canvas.addEventListener('contextmenu', function(e) {
            e.preventDefault();
        })

        // 初期描画
        drawBurningShipFractal();

        // HSL to RGB変換関数
        function hslToRgb(h, s, l) {
            h /= 360;
            s /= 100;
            l /= 100;
        
            let r, g, b;
        
            if (s === 0) {
                r = g = b = l; // achromatic
            } else {
                const hue2rgb = (p, q, t) => {
                    if (t < 0) t += 1;
                    if (t > 1) t -= 1;
                    if (t < 1 / 6) return p + (q - p) * 6 * t;
                    if (t < 1 / 2) return q;
                    if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
                    return p;
                };
        
                const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
                const p = 2 * l - q;
                r = hue2rgb(p, q, h + 1 / 3);
                g = hue2rgb(p, q, h);
                b = hue2rgb(p, q, h - 1 / 3);
            }
        
            return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
        }
    </script>
</body>
</html>
```

## 関連する面白い記事

https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E3%82%B7%E3%83%83%E3%83%97%E3%83%BB%E3%83%95%E3%83%A9%E3%82%AF%E3%82%BF%E3%83%AB

https://note.com/108hassium/n/n47ad4d3956af