The best way to explain recursion is to explain recursion the best way. In this document we will do exactly that by making pretty trees in blender using python. I would argue that this is by far one of the cooler ways to teach maths/code to kids.

## A bit of maths

We have blender to our disposal to deal with the visualisation of 3d objects. One of these objects is the cylinder. We will use this as the base of the bark of the tree.

The blender api allows us to place basic shapes but only in a basic way. We cannot place a cylinder from a point $p_1$ to a point $p_2$. We can merely place it with a certain center, scale it to an appropriate length and then rotate it appropriately. So we will resort to a bit of high school maths to create our own python function that does this.

Finding the center location of the two points is rather easy. If $p_i = (x_i, y_i, z_i)$ then the point $m$ between $p_1$ and $p_2$ is defined via;

\begin{aligned} m & = \frac{p_1 + p_2}{2}\\ & = p_1 + \frac{p_2 - p_1}{2} \\ & = p_1 + \frac{\Delta x + \Delta y + \Delta z}{2} \end{aligned}

We can also calculate the length of the cyclinder.

$$d = \sqrt{\Delta x^2 + \Delta y^2 + \Delta z^2 }$$

The angles are the tricky bit but it helps to recognize that we can map the triangle in 3d space to two triangles in 2d space. The angle $\alpha$ can be calculated via $\Delta x$ and $\Delta y$ while the angle $\beta$ can be calculated via the length $d$ and $\Delta z$. You can confirm the formulas below using trigonometry (remember: SOH-CAH-TOA).

\begin{aligned} \alpha & = \arctan \Big( \frac{\Delta y}{\Delta x} \Big)\\ \beta & = \arccos \Big( \frac{\Delta z}{d} \Big) \end{aligned}

With this little bit of math, we can create a formula which can be used in blender.

def cylinder_between(x1, y1, z1, x2, y2, z2, r):
dx = x2 - x1
dy = y2 - y1
dz = z2 - z1
dist = np.sqrt(dx**2. + dy**2. + dz**2.)
depth = dist,
location = (dx/2. + x1, dy/2. + y1, dz/2. + z1)
)
alpha = np.arctan2(dy, dx)
beta = np.arccos(dz/dist)
bpy.context.object.rotation_euler[1] = beta
bpy.context.object.rotation_euler[2] = alpha


## Making a Tree

To now make a tree we need to extend the formula with a script. The script needs to start at a base and recursively extend a branch. At random places, the branch will split into two branches. The easiest way to do this is via recursion.

The code should explain it better than words.

import numpy as np
from __future__ import division

def delete_all():
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=True)

delete_all()

def cylinder_between(x1, y1, z1, x2, y2, z2, r):
dx = x2 - x1
dy = y2 - y1
dz = z2 - z1
dist = np.sqrt(dx**2. + dy**2. + dz**2.)
depth = dist,
location = (dx/2. + x1, dy/2. + y1, dz/2. + z1)
)
alpha = np.arctan2(dy, dx)
beta = np.arccos(dz/dist)
bpy.context.object.rotation_euler[1] = beta
bpy.context.object.rotation_euler[2] = alpha

def branch(origin, depth = 1, prob = 0.5):
if depth > 8:
return 0
x,y,z = origin
x_new = x + np.random.normal(0, 2, 1)
y_new = y + np.random.normal(0, 2, 1)
z_new = z + np.random.uniform(0, 20/np.sqrt(depth), 1)
cylinder_between(x,y,z,x_new, y_new, z_new, r = 1.5/np.sqrt(depth))
if np.random.random() < 0.5:
branch((x_new, y_new, z_new), depth = depth + 1)
branch((x_new, y_new, z_new), depth = depth + 1)

branch((0,0,0))


And if the code hasn't convinced you yet, here is an example tree that has been generated.

## A forest?

If you want a bit more insight in how different parameters cause different trees you merely need some for loops to create a forest.

delete_all()
for d in range(1, 8):
for y, prob in enumerate(np.arange(0.1, 0.6, 0.08)):
branch((d * 20, y*20, 0), depth = d, prob = prob)


## Conclusion

It seems much more joyful to learn about maths or recursion via 3d programming in blender than to do it from a static textbook. Should this inspire any teacher; feel free to use any code to teach. Blender is a nice educational tool for exactly this sort of thing.