Let’s Build: A Recursive Tree with P5js

A recursively drawn tree on a hill.

Recursion is powerful tool for creating detailed, complex patterns with code. Recursion occurs when a function definition includes the use of the function itself.

Fractal patterns often involve recursion. Each section of the Sierpinski triangle is made by drawing three copies of the entire Sierpinski triangle at smaller scale. Each of these copies is, you guessed it, a smaller copy of the Sierpinski triangle!

Of course, it all has to stop at some point. Eventually, the smallest copy of the Sierpinski triangle is just drawn with three triangles. This is known as the base case, and it stops the recursive process and allows the drawing to be completed. In the image above, the base case is about 6 levels deep.

For a tree, the recursion appears when we think about the trunk splitting into several limbs, the limbs into several branches, the branches into twigs, and the twigs into leaves. Instead of thinking about each part (trunk, limb, branch, twig, leaf) as distinct, we can think of the tree recursively. A branch is just a smaller version of a limb, and is drawn by splitting into several twigs (which, in turn, are just smaller branches).

The recursive function we use is drawBranch(). The base case is the leaf.The drawBranch() function will draw a line on our canvas, then call the drawBranch() function twice more. These two branches will each call the drawBranch() function twice more and so on until we reach the base case and stop. In the image, five levels of branching lead to 16 leaves at the end.

From here on, we’ll actually start building using P5js. You should be familiar with the use of variables, functions, loops, push-pop, and translation. Several platforms work, but I recommend (and will provide links for content in) the P5js editor and OpenProcessing.

Open the Basic Recursive Tree sketch (P5js editor, OpenProcessing), and start by looking over the drawBranch() function (line 36).

Change the value of the variable angle.

Adjust the expressions for len and thick.

Once you’ve explored the drawBranch() function, adjust the global variables for maxLevel and split. Don’t increase by much! The exponential growth of recursive processes can bring your browser to a halt.

A collection of trees with different settings for maxLevel, split, angle, len, and thick. Can you build your own tiny arboretum?

Our trees would benefit from some improvements. Let’s walk through three big improvements: (1) introducing some randomness to break the symmetry of the tree, (2) introducing curves for branches, and (3) animating the tree to respond to some “wind”.

Randomness

Trees have characteristic branching patterns and branch lengths that lead to distinct shapes. Winter can be a good time to observe these differences. But, no tree has exactly the same length branches splitting at exactly the same angle. We can tell that our recursive trees are not natural because they are too symmetric.

Fortunately, there’s an easy way to add add randomness into our recursion. The function random(low, high) will generate a random number between low and high. If we multiply the values that we use for angle and len by a random value between, say, 0.8 and 1.2, the general shape of the tree will be the same, but the variation in the details will appear more natural. You can see this in lines 38 and 39 of the Random Recursive Tree sketch (P5js editor, OpenProcessing).

Experiment with the range of values that you use in the random() function.

Try adding some additional trees by calling the drawBranch() function more than once in your draw loop.

All these straight trunks are bothering me. Can you use some randomness to make them lean a little?

Curves

We made a big deal before about how only one line of code in drawBranch() involved drawing on the screen. Since that’s the case, can’t we do better than a straight line?

P5js implements Catmull-Rom splices with the curve() function. This function takes 8 parameters for the location of the 4 control points, but the curve is only drawn between points P1 and P2 as shown. If we put some nearby locations for P0 and P3 (or better still, some random locations), our branches will have some curve to them.

This curved version of our tree (P5js editor, Open Processing) removes the line() and replaces it was a conditional structure that differentiates between the trunk, limbs, and leaves on the tree (starting line 44). All are constructed with the curve() function, but with changes based on the level. The trunk is thickened by drawing several curves instead of just one, and the leaves are drawn with contrasting stroke and fill properties to add detail and structure to the most visible part of the tree.

Experiment with the curve() function in each level. Incorporate additional randomness to the control points. It may help to not draw the leaves while you work on the branches.

Experiment with adding additional details to the drawing of another level. Maybe thicken the limbs? Maybe have one leaf in 100 draw as a small flower instead of a leaf?

Animation

Cycling through randomly generated trees is fun, but it can be more satisfying to focus on one tree and watch it evolve.

Animating our tree will require a few major changes to the program, and one small change to the drawBranch() function. You can find the animated version of our tree here (P5js editor, OpenProcessing)

To create the animation, we will want to remove the noLoop() function from the draw loop. This will allow the tree to be redrawn, but each time a completely new random tree is be generated. To have the same random values chosen each time through the loop we use the randomSeed() function along with a global variable to hold the seedValue. This value is updated in the mousePressed() function.

The other major change is a new global variable sway. This variable is updated each frame (line 32) using a mathematical function selected to look nice and breezy. This variable is then used in lines 35 and 77 to change the angle of each limb, branch, twig and leaf on the tree. Scaling factors based on the level and split values make the effect more believable.

Try changing the way sway is calculated or how it is used in generating the tree.

Add a couple more trees by adding additional drawBranch() functions in the draw loop to make an animated grove.

The use of recursion in code makes it easy to generate large structures that are self-similar over many levels. The key to successful use of recursion is recognizing self-similarity in unfamiliar places. Cities built of neighborhoods built of houses, or galaxies built of stars, or coastlines built of bays and inlets all make for interesting explorations in recursive generation.

Experimenter, explorer, educator.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store