Как алогорифмически нарисовать геометрические узоры

дома / посты

В этом посте я хочу рассказать о том, как алогорифмически нарисовать некоторые геометрические узоры, которые я считаю поразительно красивыми, включая хорошо известный Цветок жизни. Я не буду комментировать значение, которое такие узоры могут иметь для определенных людей, или то, как они использовались и интерпретировались. Основное внимание здесь уделяется геометрии и коду. И снова, как и в моих последних статьях, мы будем использовать Python. Однако сначала я хочу представить полезный строительный блок, который упрощает процесс и позволяет нам сосредоточиться на более высокоуровневых аспектах.

Модуль geom2d

Я написал простой модуль под названием ‘geom2d’ и разместил его на GitHub. В основе geom2d лежит абстракция под названием Plane, которая позволяет рисовать основные геометрические фигуры на двумерной поверхности, используя координаты с плавающей точкой. Вот интерфейс.

Репозиторий GitHub содержит несколько полезных реализаций Plane. Для векторной графики есть SVGPlane. А для растровой графики есть RasterPlane, который требует объект Raster (реализация Raster предоставляется: ImageRaster).

Трингулярная решетчатая форма пересекающихся окружностей

Сейчас мы обратимся к обычной треугольной решетчатой форме пересекающихся окружностей. У нас есть последовательность узоров из концентрических шестиугольных колец кругов. Каждый узор в последовательности имеет полное шестиугольное кольцо окружностей и содержит $C(n)$ окружностей, где последовательность $C(n)$ равна 1, 1, 7, 19, 37, 61. (начиная с $n = 0$ ); то есть, в общем случае, имеем $C(n) = 3n(n — 1) + 1$ . Как же построить такие узоры?

Построение относительно простое. Мы начинаем с центральной точки $(c_x, c_y)$ и радиуса $r$ для узора. Затем окружности имеют радиус $r’ = r / (n — 1)$. Естественно, мы должны отдельно обработать случай $n \in \<0, 1\>$ и вернуть одну окружность с центром в заданной центральной точке и радиусом $r$. Для $n >= 2$, вот как мы поступим. Обратите внимание, что мы не указываем радиус окружностей, так как каждая нарисованная окружность будет иметь радиус $r’$ Мы начинаем с двух окружностей, первая с центром $(c_x, c_y)$ и вторая с центром $(c_x, c_y — r’)$ . Таким образом, наш список окружностей содержит обе эти окружности. Затем мы полагаемся на важную функцию под названием perform_intersections. Эта функция берет список окружностей, центр $(c’_x, c’_y)$ и граничный радиус $b$. Она находит точки пересечения каждой окружности в списке с каждой другой окружностью в списке, и если расстояние между каждой точкой и $(c’_x, c’_y)$ меньше или равно $b$, то добавляет в список окружность с центром в этой точке. Затем процесс повторяется с новыми окружностями до тех пор, пока больше не будет добавлено ни одной окружности. Вот код, который, по общему признанию, нуждается в некоторой доработке и оптимизации, но вы получите представление об алгоритме.

Сейчас мы завершили работу над алгоритмом рисования этих узоров. Наша функция для создания узоров, которую мы назвали overlapping_circles, определяет местоположение узора в локальном пространстве с центром $(0, 0)$ и устанавливает радиус окружностей в $1,0$, чтобы их можно было легко масштабировать и переводить по мере необходимости. Мы добавляем две начальные окружности в список, как описано выше, с радиусом $1,0$, а затем вызываем perform_intersections с этим списком окружностей, центральной точкой $(0, 0)$ и граничным радиусом $n — 1$ (плюс некоторый эпсилон для устранения ошибок с плавающей запятой). Вот код.

Обратите внимание, что вызывая overlapping_circles с аргументом $n$, мы получаем список окружностей $L_n$ таких, что $\mathsf(L_n) = C(n)$. И получаем желаемый результат. Затем мы можем перебирать список окружностей и рисовать каждую из них на плоскости с помощью метода Plane.draw_circle. Вот пример изображения, сгенерированного программой, которое показывает перекрывающиеся окружности для $n = 5$ .