Что это такое
Фрактальное дерево — это дерево, которое генерируется с помощью рекурсивной функции. Почему фрактальное? Фрактал — объект, который точно или приближенно совпадает с частью себя самого. То есть целый объект имеет ту же форму, что и любая из вложенных частей.
Код программы
Далее представлен код программы на языке программирования Python с комментариями
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
import tkinter import math # Параметры angleBetween = 22 # Угол между ветками lenFactor = 0.8 # Множитель длины вложенных веток startLength = 100 # Начальная длина веток minLength = 6 # Минимальная длина веток после которой рекурсия прекращается # Подготовим окно вывода root = tkinter.Tk() width, height = 1100, 500 c = tkinter.Canvas(root, width = width, height = height) c.pack() def branch(point, angle, length): # Условие выхода из рекурсии if (length < minLength): return # Вычислим и нормализуем углы на которые направлены новые ветки # Нормализовать угол, значит преобразовать его к промежутку от 0 до 360 градусов lb_angle = normalize(angle + angleBetween) rb_angle = normalize(angle - angleBetween) x, y = point[0], point[1] # Получим точки в которые нужно провести линии lb_point = getBranchEnding(x, y, lb_angle, length) rb_point = getBranchEnding(x, y, rb_angle, length) # Узнаем насколько близко длина новых веток к минимальной # и в соответствии с этим зададим толщину линий branch_width = 10 * ((length - minLength) / (startLength - minLength)) c.create_line(x, y, lb_point[0], lb_point[1], width = branch_width, fill='green') c.create_line(x, y, rb_point[0], rb_point[1], width = branch_width, fill='green') # Рекурсивно генерируем следующие ветки branch(lb_point, lb_angle, length * lenFactor) branch(rb_point, rb_angle, length * lenFactor) # Функция, возвращающая кортеж с координатами точки конца очередной ветки def getBranchEnding(x, y, angle, length): return ( x - (math.sin(math.radians(angle)) * length), y - (math.cos(math.radians(angle)) * length)) def normalize(angle): while (angle < 0): angle += 360 while (angle >= 360): angle -= 360 return angle if (__name__ == '__main__'): # Отрисуем ствол дерева root_x = width / 2 root_y = height * 0.98 x = root_x y = root_y - startLength c.create_line(root_x, root_y, x, y, fill='green', width = 10) # Запустим рекурсию, передав начальные значения # Для выполнения условия самоподобия первые # две ветки должны быть короче ствола branch((x, y), 0, startLength * lenFactor) root.mainloop() |
Вывод программы
Как можно улучшить
Абсолютная симметрия это конечно хорошо, но можно добавить немножко рандома в алгоритм, чтобы получать более натуральные деревья. Давайте так и сделаем.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
import tkinter import math import random # Параметры lenFactor = 0.8 startLength = 120 # Начальная длина веток minLength = 8 # Минимальная длина веток после которой рекурсия прекращается # Подготовим окно вывода root = tkinter.Tk() width, height = 1150, 450 c = tkinter.Canvas(root, width = width, height = height) c.pack() def getNextBranchLength(maxLength): return maxLength * random.uniform(0.7, 1) def getNextAngle(): return random.uniform(20, 65) def branch(point, angle, length): # Условие выхода из рекурсии if (length < minLength): return # Вычислим и нормализуем углы на которые направлены новые ветки # Нормализовать угол, значит преобразовать его к промежутку от 0 до 360 градусов lb_angle = normalize(angle + getNextAngle()) rb_angle = normalize(angle - getNextAngle()) x, y = point[0], point[1] # Получим точки в которые нужно провести линии lb_point = getBranchEnding(x, y, lb_angle, getNextBranchLength(length)) rb_point = getBranchEnding(x, y, rb_angle, getNextBranchLength(length)) # Узнаем насколько близко длина новых веток к минимальной # и в соответствии с этим зададим толщину линий branch_width = 10 * ((length - minLength) / (startLength - minLength)) c.create_line(x, y, lb_point[0], lb_point[1], width = branch_width, fill='green') c.create_line(x, y, rb_point[0], rb_point[1], width = branch_width, fill='green') # Рекурсивно генерируем следующие ветки branch(lb_point, lb_angle, length * lenFactor) branch(rb_point, rb_angle, length * lenFactor) # Функция, возвращающая кортеж с координатами точки конца очередной ветки def getBranchEnding(x, y, angle, length): return ( x - (math.sin(math.radians(angle)) * length), y - (math.cos(math.radians(angle)) * length)) def normalize(angle): while (angle < 0): angle += 360 while (angle >= 360): angle -= 360 return angle if (__name__ == '__main__'): # Отрисуем ствол дерева root_x = width / 2 root_y = height * 0.98 x = root_x y = root_y - startLength c.create_line(root_x, root_y, x, y, fill='green', width = 10) # Запустим рекурсию, передав начальные значения branch((x, y), 0, startLength * lenFactor) root.mainloop() |
Представленный выше код генерирует следующую красоту