![](/images/growing_trees/screen.gif)
![](/images/growing_trees/tree_4.gif)
I'm interested in algorithmically generating structures that have a natural feel. I experimented with growing trees. They won't save the planet, but they're pretty. To me the most natural choice to model a natural tree was... a tree. Every node in the tree is a segment of the natural tree that is characterized by a thickness, two points and its' children.
class Node():
def __init__(self, a, b):
self.children = []
self.thickness = 1
self.a = a
self.b = b
The tree grows by branching, that is attaching new nodes. Every iteration, each leaf node branches. There is also a chance that a non-leaf node will spawn a new branch. This is inverse proportional to the square of the node's thickness, so new branches tend to spawn from thin branches at the top and not from the thick trunk. The tree is updated from the leafs to the trunk.
def is_leaf(self):
return len(self.children) == 0
def update(self):
for child in self.children:
child.update()
# Leaves always grow
if self.is_leaf():
self.branch()
# Random branching, dependent on thickness
elif random(50) < 1/self.thickness**2:
self.branch()
self.update_thickness()
When generating a new branch, a copy of the Node's shape is made, rotated by a random angle and attached to the top of the Node. Instead of a purely random walk, a mean regression is used to guide the tree towards the sun.
def angle(self):
return PVector.angleBetween(PVector(0,-1), self.b - self.a)
def branch(self):
# New Branch
my_shape = self.b - self.a
if my_shape.x == 0:
current_angle = 0
else:
current_angle = math.tan(my_shape.x/my_shape.y)
mean_regression = 0.02
angle = mean_regression * current_angle + (1 - mean_regression) * deg2rad(random(-10, 10))
new_shape = my_shape.rotate(angle)
branch = Node(self.b, self.b + new_shape)
self.children.append(branch)
Trees tend to conserve cross-sectional surface. This means that the sum of cut surfaces remains roughly constant over the height of the tree. I use this heuristic to adjust the thickness of each node in relation to newly spawned branches.
def update_thickness(self):
sum_squared_thicknesses = 1
for child in self.children:
sum_squared_thicknesses += child.thickness**2
self.thickness = math.sqrt(sum_squared_thicknesses)
I use Processing as a simple method of drawing the trees. Here is the full code:
# tree.py
import math
def deg2rad(deg):
return math.pi/180*deg
def random_choice(iterable):
return iterable[int(random(len(iterable)))]
class Node():
def __init__(self, a, b):
self.children = []
self.thickness = 1
self.a = a
self.b = b
def angle(self):
return PVector.angleBetween(PVector(0,-1), self.b - self.a)
def is_leaf(self):
return len(self.children) == 0
def update_thickness(self):
sum_squared_thicknesses = 1
for child in self.children:
sum_squared_thicknesses += child.thickness**2
self.thickness = math.sqrt(sum_squared_thicknesses)
def branch(self):
# New Branch
my_shape = self.b - self.a
if my_shape.x == 0:
current_angle = 0
else:
current_angle = math.tan(my_shape.x/my_shape.y)
mean_regression = 0.02
angle = mean_regression * current_angle + (1 - mean_regression) * deg2rad(random(-10, 10))
new_shape = my_shape.rotate(angle)
branch = Node(self.b, self.b + new_shape)
self.children.append(branch)
def update(self):
for child in self.children:
child.update()
# Leaves always grow
if self.is_leaf():
self.branch()
# Random branching, dependent on thickness
elif random(50) < 1/self.thickness**2:
self.branch()
self.update_thickness()
def draw(self):
strokeWeight(self.thickness)
line(self.a.x, self.a.y, self.b.x, self.b.y)
for child in self.children:
child.draw()
# main.py
import tree
root = None
def setup():
global root
size(1000, 1000)
background(255)
frameRate(4) # Slow it down, so we can watch the tree grow
root = tree.Node(PVector(width/2, height), PVector(width/2, height - 10))
def draw():
global root
background(255)
root.draw()
root.update()