L-система с ветвлениями. Рисуем деревья и травы

Архив проекта: 5_fractals.py

До сих пор мы с вами рассматривали относительно простые способы формирования фрактальных кривых. Но что насчет более сложных, например, деревьев или кустарников?

Здесь очевидно недостаточно тех возможностей L-системы, которые мы прописали на предыдущих занятиях. Так как же генерировать фрактальные разветвленные кривые? Чтобы ответить на этот вопрос, давайте посмотрим, какие акробатические трюки должна вытворять черепашка, чтобы нарисовать фрагмент фрактального дерева.

Вначале она должна пройти вперед (то есть, вверх), затем, повернуть налево, пройти тот же путь, вернуться в точку ветвления, повернуть направо и еще пройти тот же путь.

Как все эти действия реализовать программно? Для этого предлагается использовать стек, работающий по принципу LIFO – последним вошел, первым вышел. Как только черепашка оказывается в точке ветвления, в стек помещается кортеж со значениями ее текущих координат и угла поворота:

(x, y, angle)

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

На уровне команд весь этот процесс перемещения можно описать так:

F[+F][-F]

То есть, у нас появляются новые управляющие символы – квадратные скобки. Когда скобка открывается, то в стек помещаются координаты и ориентация черепашки. При появлении закрывающей кваратной скобки, из стека извлекается последняя запись и черепашка помещается в соответствующие координаты.

Вот принцип ветвления, положенный в основу L-систем. Давайте реализуем его в нашей программе.

Для начала в методе draw_turtle() добавим список:

turtle_stack = []

который будем использовать как стек с данными (добавлять элементы в конец и извлекать тоже с конца). Далее, в этом же методе сделаем обработку квадратных скобок:

           elif move == "[":
               turtle_stack.append((self.t.xcor(), self.t.ycor(), self.t.heading(), self.t.pensize()))
           elif move == "]":
               xcor, ycor, head, w = turtle_stack.pop()
               self.set_turtle((xcor, ycor, head))
               self.width = w
               self.t.pensize(self.width)

Когда скобка открывается, в конец стека добавляем кортеж с текущими координатами черепашки, углом поворота и толщиной линии рисования. Когда встречается закрывающаяся скобка, то из стека извлекаются последние данные, черепашка возвращается в соответствующую позицию (метод set_turtle) и устанавливается сохраненная толщина линии рисования.

Вспомогательный метод set_turtle() выглядит следующим образом:

    def set_turtle(self, my_tuple):
        self.t.up()
        self.t.goto(my_tuple[0], my_tuple[1])
        self.t.seth(my_tuple[2])
        self.t.down()

Все, теперь наша L-система может использовать механизм ветвления для построения природных фракталов. Если сейчас прописать аксиому:

angle = 33
axiom = "F"

и определить правило:

l_sys.add_rules(("F", "F[+F][-F]"))
l_sys.generate_path(1)
l_sys.draw_turtle( (0, -100), 90)

то получим следующий результат на первой итерации:

При трех итерация будет уже такой результат:

Похоже на дерево. Но мы усовершенствуем этот фрактал и пропишем его со следующими параметрами:

f_len = 10      # длина одного сегмента прямой (в пикселах)
angle = 33      # фиксированный угол поворота (в градусах)
axiom = "A"
 
l_sys = LSystem2D(t, axiom, pen_width, f_len, angle)
l_sys.add_rules(("F", "FF"), ("A", "F[+A][-A]"))
l_sys.generate_path(5)

В результате, ствол дерева будет постоянно увеличиваться на каждой новой итерации и дерево уже больше походит на само себя:

Давайте посмотрим еще пару примеров природных фракталов:

f_len = 5      # длина одного сегмента прямой (в пикселах)
angle = 25.7      # фиксированный угол поворота (в градусах)
axiom = "F"
 
l_sys = LSystem2D(t, axiom, pen_width, f_len, angle)
l_sys.add_rules(("F", "F[+F]F[-F]F"))
l_sys.generate_path(4)

f_len = 10      # длина одного сегмента прямой (в пикселах)
angle = 22.5      # фиксированный угол поворота (в градусах)
axiom = "F"
 
l_sys = LSystem2D(t, axiom, pen_width, f_len, angle)
l_sys.add_rules(("F", "FF-[-F+F+F]+[+F-F-F]"))
l_sys.generate_path(3)

На мой взгляд, получились воплне узнаваемые формы растений. Вот так с помощью простых правил и ветвления L-система способна воспроизводить такие изображения.