Архив проекта: 3_fractals.py
На предыдущем
занятии мы с вами получили первый опыт рисования фрактала с помощью черепашьей
графики на Python. Но каждый раз
писать отдельную программу для прорисовки новой фрактальной кривой не лучшее
занятие. Правильнее было бы оптимизировать этот процесс, используя
универсальный подход к генерации различных фракталов. Именно эта идея положена
в основу L-систем,
создающие фрактальные изображения.
Вначале мы
сделаем такое упрощение. Все команды для черепашки опишем отдельными символами.
Обычно, используют следующие обозначения:
Команда
на Python
|
Символ
|
Описание
|
forward(x) [или
fd(x)]
|
F
|
Движение
вперед на x пикселей
|
left(angle)
|
+
|
Поворот
влево на угол angle
|
right(angle)
|
-
|
Поворот
вправо на угол angle
|
Тогда команду
для рисования одного сегмента кривой Коха можно записать в виде строки:
"F+F--F+F"
Причем, здесь F – это будет
одно и то же движение вперед на строго заданное число пикселей, а повороты
осуществляться на заданный угол в 60 градусов, так как для формирования кривой
Коха нам нужен именно такой угол. У другого фрактала угол может быть другим. В
результате, черепашка сначала пройдет вперед, затем, повернет влево на 60
градусов, снова пройдет вперед, повернет вправо на 120 градусов (два символа
минус), опять пройдет вперед, повернет влево на 60 градусов и еще пройдет
вперед. Получим сгемент ломаной.
Но одной ломаной
мало, на каждой новой итерации линейный сегмент также должен превращаться в
такую же ломаную. Чтобы это сделать, давайте определим правило: каждый
символ F на новой
итерации должен заменяться на строку "F+F--F+F":
"F" → "F+F--F+F"
Например, для
формирования кривой Коха 2-й итерации черепашка должна будет последовательно
выполнить команды:
Мало того, саму
первую итерацию также можно сформировать по этому же правилу, взяв за основу
обычную прямую:
Вот эта первая
предельная прямая в теории L-систем назыается инициатором или
аксиомой. Таким образом, имея инициатор и правило формирования, мы
получаем возможность генерировать описание фрактала на уровне команд черепашьей
графики. Давайте реализуем класс, который бы выполнял описанные действия.
Для начала мы
пропишем базовый функционал в классе LSystem2D, который и
будет представлять простую L-систему на плоскости:
class LSystem2D:
def __init__(self, t, axiom, width, length, angle):
self.axiom = axiom # инициатор
self.state = axiom # строка с набором команд для фрактала (вначале это инициатор)
self.width = width # толщина линии рисования
self.length = length # длина одного линейного сегмента кривой
self.angle = angle # фиксированный угол поворота
self.t = t # сама черепашка
self.t.pensize(self.width)
def draw_turtle(self, start_pos, start_angle):
# ***************
turtle.tracer(1, 0) # форсажный режим для черепашки
self.t.up() # черепашка воспаряет над поверхностью (чтобы не было следа)
self.t.setpos(start_pos) # начальная стартовая позиция
self.t.seth(start_angle) # начальный угол поворота
self.t.down() # черепашка опускается на "грешную землю"
# ***************
for move in self.state:
if move == 'F':
self.t.forward(self.length)
elif move == '+':
self.t.left(self.angle)
elif move == '-':
self.t.right(self.angle)
turtle.done() # чтобы окно не закрывалось после отрисовки
И воспользуемся
им, следующим образом:
# ************** чтобы окно появлялось в левом верхнем углу с размерами 1200x600
width = 1200
height = 600
screen = turtle.Screen()
screen.setup(width, height, 0, 0)
# **************
t = turtle.Turtle()
t.ht() # скрываем черепашку
pen_width = 2 # толщина линии рисования (в пикселах)
f_len = 50 # длина одного сегмента прямой (в пикселах)
angle = 60 # фиксированный угол поворота (в градусах)
l_sys = LSystem2D(t, "F", pen_width, f_len, angle)
l_sys.draw_turtle( (0, 0), 0)
После запуска
должны увидеть прямую линию.
Отлично, начало
положено. Осталось реализовать метод, который бы генерировал команды для
описания фрактальной кривой согласно заданному правилу (или набору правил). Для
этого, мы в конструкторе пропишем свойство, хранящее набор правил в виде
словаря:
self.rules = {} # словарь для хранения правил формирования кривых
А ниже зададим
метод, который будет добавлять новое правило в этот словарь:
def add_rules(self, *rules):
for key, value in rules:
self.rules[key] = value
Причем, правила
будем передавать в виде списков из двух элементов в формате:
(<что меняем>,
<на что меняем>)
Например, для
построения кривой Коха, этот список будет таким:
l_sys.add_rules(("F", "F+F--F+F"))
Осталось
применить это правило для генерации фрактала. За это будет отвечать метод generate_path:
def generate_path(self, n_iter):
for n in range(n_iter):
for key, value in self.rules.items():
self.state = self.state.replace(key, value.lower())
self.state = self.state.upper()
В качестве
аргумента мы ему передаем число итераций для кривой и, затем, делаем цикл n_iter раз. На каждой
итерации во внутреннем цикле перебираем все правила из словаря в формате
ключ-значение и в формируемом маршруте (свойстве state) заменяем все
найденные ключи на соответствующие значения. Причем, переводим все символы в
нижний регистр. Это необходимо, чтобы у нас не было вложенных замен, при
использовании нескольких правил. Когда одно правило содержит ключи из другого
правила. Поэтому, я реализовал такой трюк – все замены делаются в нижнем
регистре, полагая, что ключи всегда будут только в верхнем регистре. После всех
замен вновь переводим строку в верхний регистр, как это и должно быть и
получаем маршрут для черепашки на текущей итерации. Повторяем эту процедуру n_iter раз и у нас
плучается готовая строка с набором необходимых команд.
Теперь у нас все
готово для генерации простых фрактальных кривых. Давайте это сделаем. Вначале,
конечно же, нарисуем кривую Коха на 4-й итерации. Для этого вызовем метод:
и установим
длину сегмента в 5 пикселей:
После запуска
увидим следующую картину:
Но чем хороша L-система, это
то, что теперь у нас есть универсальный инструмент для построения большого
числа разных фракталов. Например, чтобы получит снежинку Коха, достаточно
инициатор определить в виде равностороннего треугольника:
l_sys = LSystem2D(t, "F--F--F", pen_width, f_len, angle)
Видите, как это
просто! Нам не пришлось вносить изменения на уровне команд, а только поменять
один параметр.
Что еще можно
изобразить такой L-системой? Например, такую кривую:
angle = 90
axiom = "F-F-F-F"
l_sys = LSystem2D(t, axiom, pen_width, f_len, angle)
l_sys.add_rules(("F", "F+FF-FF-F-F+F+FF-F-F+F+FF+FF-F"))
С числом
итераций 2:
Увидим следующее
построение:
Уже неплохо.
Однако, не все кривые можно построить непрерывно перемещая черепашку. Иногда ей
нужно перескакивать с места на место. Для этого добавим еще одну команду:
S – переместиться
вперед без рисования пути
Для этого в метод
draw_turtle() добавим еще одну проверку:
elif move == 'S':
self.t.up()
self.t.forward(self.length)
self.t.down()
И это все.
Теперь можно генерировать еще более сложные фракталы. Например, такой «ковер»:
angle = 90
axiom = "F+F+F+F"
l_sys = LSystem2D(t, axiom, pen_width, f_len, angle)
l_sys.add_rules(("F", "F+S-FF+F+FF+FS+FF-S+FF-F-FF-FS-FFF"), ('S', 'SSSSSS'))
l_sys.generate_path(2)
Я думаю, вы
уловили принцип использования L-систем для формирования и отображения
разнообразных фрактальных кривых. Но то что мы сделали – это лишь первый шаг.
На следующем занятии мы немного усложним алгоритм и сможем получать кривые еще более
сложных форм.