DevGang
Авторизоваться

Mandelbrot в SVG

Большинство людей, в жилах которых течет кровь ботаников, видели множество Mandelbrot. Это загадочно, красиво и глубоко.

Уравнение также очень легко понять.

f(Z) = Z² + C

Для фрактала Мандельброта Z изначально равно 0, а C — координата пикселя на SVG.

Несколько замечаний.

  1. Z и C — комплексные числа.
  2. SVG представляет собой сложную плоскость, где ось x представляет собой действительные числа, а ось y — мнимые.

Я буду использовать написанный мной класс для выполнения арифметических операций над комплексными числами.

Поколение

Число считается включенным в набор, если при повторении бесконечное число раз величина значения остается небольшой. Для большинства начальных значений величина будет становиться все больше и больше вечно, это называется расхождением, они исключены из набора.

Чтобы вычислить значения, я создал две функции, одна из которых просто соответствует приведенному выше уравнению, а другая специально для Мандельброта.

/***
 * <code>f(z) = z^2 + c</code>
 * @param z Complex result of the iteration.
 * @param c Complex constant, set to the coordinates on the complex plain of the point we are interested in.
 ***/
function f(z, c) {
    (function assertArgs(z, c) {
        if (!(z instanceof Complex))
            throw new Error(
                `Expected z to be Complex but was ${z?.constructor.name}`
            );
        if (!(c instanceof Complex))
            throw new Error(
                `Expected c to be Complex but was ${z?.constructor.name}`
            );
    })(z, c);

    return z.multiply(z).add(c);
}

Для фрактала Мандельброта Z изначально равно 0, а C - переменная, поэтому я создал другую функцию для приема C, а также для принятия ряда итераций и возврата списка результатов.

/***
 * Call <code>f(z,c)</code> for multiple iterations, returning an array of all the results.
 * @param c
 * @param maxIterations
 * @returns {Complex[]}
 */
function mandelbrot(c, maxIterations) {
    const results = new Array(maxIterations);

    results[0] = f(Complex.zero(), c);

    for (let i = 1; i < maxIterations; i++) {
        const prev = results[i - 1];
        results[i] = f(prev, c);
    }

    return results;
}

Отображение в SVG

Создаем SVG:

<svg id="mandelbrot-fractal"
     width="500px"
     height="500px"
     viewBox="-1.5 -1 2 2">
</svg>

Для SVG мы определяем размер пикселя, затем перебираем каждый пиксель и вычисляем результаты. Размер пикселя определяется в константе PIXEL_SIZE.

Если величина конечной точки превышает значение (о котором я только что догадался), то мы игнорируем его, если нет, я генерирую rect в этой точке и определенного размера пикселя.

import { NS, drawXYAxisWithRings } from '../../../helpers/svg.js';
import { Complex } from '../../../helpers/Complex.js';
import { mandelbrot } from '../../fractals.js';

const PIXEL_SIZE = 0.0025;
const SVG = document.getElementById('mandelbrot-fractal');
const VIEW_BOX = {
    x: SVG.viewBox.baseVal.x,
    y: SVG.viewBox.baseVal.y,
    width: SVG.viewBox.baseVal.width,
    height: SVG.viewBox.baseVal.height,
};

SVG.appendChild(
    drawXYAxisWithRings(VIEW_BOX.x, VIEW_BOX.y, VIEW_BOX.width, VIEW_BOX.height)
);

const END_X_COORD = VIEW_BOX.x + VIEW_BOX.width;
const END_Y_COORD = VIEW_BOX.y + VIEW_BOX.height;

const maxIterations = 100;
const maxMagnitude = 10;

const PIXEL_SIZE_STRING = PIXEL_SIZE.toString();
for (let i = VIEW_BOX.x; i < END_X_COORD; i += PIXEL_SIZE) {
    for (let j = VIEW_BOX.y; j < END_Y_COORD; j += PIXEL_SIZE) {
        const results = mandelbrot(new Complex(i, j), maxIterations);
        if (results[results.length - 1].magnitude() < maxMagnitude) {
            const pixel = document.createElementNS(NS, 'rect');
            pixel.setAttribute('x', i.toString());
            pixel.setAttribute('y', j.toString());
            pixel.setAttribute('width', PIXEL_SIZE_STRING);
            pixel.setAttribute('height', PIXEL_SIZE_STRING);
            pixel.setAttribute('fill', 'black');
            SVG.appendChild(pixel);
        }
    }
}

Это создает статическое полное изображение набора, похожее на изображение заголовка этой статьи.

Вывод

Честно говоря, я не думал, что это будет так просто. Я ожидал, что мне придется потратить много времени, пытаясь точно настроить то, как я определял, что на самом деле означало расхождение. Было удивительно, что мне сошло с рук что-то столь простое, как тестирование последнего результата на удивительно низкое значение.

Мы расширяем возможности SVG в количестве генерируемых узлов, однако при уменьшении размера пикселя быстро генерируется больше узлов, чем может обработать страница, что приводит либо к замедлению, либо просто к сбою страницы. Это мешает нам создавать что—либо похожее на красивые изображения, которые мы часто видим в Интернете (см. Набор Мандельброта - Википедия или выполните поиск по масштабированию Мандельброта).

Я подумывал добавить навигацию, так как было бы неплохо иметь панорамирование и масштабирование, однако постоянное удаление и перегенерация узлов поверх пересчета функции mandelbrot сделало бы его очень неприятным в использовании, и я думаю, что изрядная работа для неудовлетворительный результат. Навигация и масштабирование возможны путем ручного изменения значений viewBox SVG и константы PIXEL_SIZE.

Источник:

#JavaScript
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу