10
CREATING FRACTALS USING RECURSION

What’s another word for thesaurus?
—Steven Wright

image

Fractals are delightfully complicated designs, where each smaller part of the design contains the entire design (see Figure 10-1). They were invented (or discovered, since fractals exist in nature) by Benoit Mandelbrot in 1980 when he was visualizing some complex functions on a state-of-the-art IBM computer.

image

Figure 10-1: Examples of fractals

Fractals don’t look like regular shapes we recognize from geometry, like squares, triangles, and circles. Their shapes are crooked and jagged, making them great models for simulating natural phenomena. In fact, scientists use fractals to model everything from the arteries in your heart, to earthquakes, to neurons in your brain.

What makes fractals so interesting is that they illustrate how you can get surprisingly complex designs from simple rules being run over and over and patterns being repeated at smaller and smaller scale.

Our main interest is the interesting, complicated designs you can make using fractals. There’s a picture of a fractal in every math book these days, but textbooks never show you how to make one—you need a computer to do that. In this chapter, you learn how to make your own fractals using Python.

THE LENGTH OF A COASTLINE

Before you can start creating fractals, let’s look at a simple example to understand how fractals can be useful. A mathematician named Lewis Richardson asked a simple question; “How long is the coastline of England?” As you can see in Figure 10-2, the answer depends on how long your ruler is.

image

Figure 10-2: Approximating the length of a coastline

The smaller your ruler, the more closely you can approximate the coastline’s jagged edges, which means you’ll end up with a longer measurement. The cool thing is that the length of the coastline approaches infinity as the length of the ruler gets close to zero! This is known as the Coastline Paradox.

Think this is just abstract mathematical noodling? Coastline length estimates can vary wildly in the real world. Even with modern technology, it all depends on the scale used to measure the map. We’ll draw a figure like Figure 10-3, the Koch snowflake, to show how a fractal can prove a rough enough coastline can get as long as you want!

image

Figure 10-3: An increasingly detailed fractal, modeling an increasingly rough coastline

First, you’re going to need to learn a few tricks, like recursion.

WHAT IS RECURSION?

The power of fractals is that you can repeat patterns of numbers or shapes that get smaller at every step until you’re dealing with very small numbers. The key to repeating all this code is a concept called recursion, which is when something is defined in terms of itself. Some of these jokes illustrate how recursion works:

As you can imagine, recursion is a pretty strange concept. The virtue of recursion is that it can tidy up code that would otherwise be too complicated, but the disadvantage is that you can end up using up too much memory.

WRITING THE FACTORIAL() FUNCTION

Let’s see recursion in action by writing a function for the factorial of a number. You may recall from math class that the factorial of n (expressed as n!) is defined as the product of all the integers from 1 to n. For example, 5! = 1 × 2 × 3 × 4 × 5 = 120.

The formula looks like this: n! = 1 × 2 × 3 . . . × (n – 2) × (n – 1) × n. This is an example of a recursive sequence, because 5! = 5 × 4! and 4! = 4 × 3!, and so on. Recursion is an important concept in math because math is all about patterns, and recursion allows you to copy and extend patterns infinitely!

We can define the factorial of n as the product of n and the factorial of n – 1. We just have to define the factorial of 0 (which is 1, not 0) and the factorial of 1 and then use a recursive statement. Open a new file in IDLE, save it as factorial.py, and then enter with the code in Listing 10-1.

factorial.py
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n – 1)

Listing 10-1: Using a recursive statement to write the factorial() function

First, we’re saying, “If the user (or the program) asks for the factorial of 0 or 1, return 1.” This is because 0! and 1! both equal 1. Then we tell the program, “For any other number n, return n times the factorial of the number 1 less than n.”

Notice that on the last line of Listing 10-1, we’re calling the factorial() function inside the definition of the factorial() function! That’s like a recipe for a loaf of bread containing the step “Bake a loaf of bread.” People wouldn’t even begin following a recipe written like that. But computers can start going through the steps and follow them throughout the process.

In this example, when we ask for the factorial of 5, the program proceeds obediently and makes it to the last line, where it asks for the factorial of n – 1, which in this case (because n = 5) is the factorial of 4. To calculate factorial (5 – 1), the program starts the factorial() function again with n = 4 and tries to evaluate the factorial of 4 the same way, followed by the factorial of 3, the factorial of 2, the factorial of 1, and finally the factorial of 0. Because we already defined the function to return the factorial of 0 as 1, the function can go back up through the process, evaluating the factorial of 1, then 2, then 3, then 4, and finally 5.

Defining a function recursively (by calling the function inside its own definition) might seem confusing, but it’s the key to making all the fractals in this chapter. Let’s start with a classic: the fractal tree.

BUILDING A FRACTAL TREE

Making a fractal starts with defining a simple function and adding a call to the function inside the function itself. Let’s try building a fractal tree that looks like Figure 10-4.

image

Figure 10-4: A fractal tree

This would be an incredibly complicated design to create if you had to tell the program every line to draw. But it takes surprisingly little code if you use recursion. Using translations, rotations, and the line() function, we’ll first draw a Y in Processing, as shown in Figure 10-5.

image

Figure 10-5: The beginnings of a fractal tree

The only requirement to eventually make this Y into a fractal is that after the program draws the Y tree, along with the branches, the program has to return to the bottom of the “trunk.” This is because the “branches” are going to become Y’s themselves. If the program doesn’t return to the bottom of the Y every time, we won’t get our tree.

WRITING THE Y() FUNCTION

Your Y doesn’t have to be perfect or symmetrical, but here’s my code for drawing a Y. Open a new sketch in Processing, name it fractals.pyde, and enter the code in Listing 10-2.

fractals.pyde
def setup():
    size(600,600)

def draw():
    background(255)
    translate(300,500)
    y(100)

def y(sz):
    line(0,0,0,-sz)
    translate(0,-sz)
    rotate(radians(30))
    line(0,0,0,-0.8*sz) #right branch
    rotate(radians(-60))
    line(0,0,0,-0.8*sz) #left branch
    rotate(radians(30))
    translate(0,sz)

Listing 10-2: Writing the y() function for the fractal tree

We set up the Processing sketch the way we always do: in the setup() function we tell the program what size to make the display window, and then in the draw() function we set the background color (255 is white) and translate to where we want to start drawing. Finally, we call the y() function and pass the number 100 for the size of the “trunk” of the fractal tree.

The y() function takes a number sz as a parameter to be the length of the trunk of the tree. Then all the branches will be based on that number. The first line of code in the y() function draws the trunk of the tree using a vertical line. To create a line branching off to the right, we translate the vertical line up the trunk of the tree (in the negative y-direction) and then rotate it 30 degrees to the right. Next, we draw another line for the right branch, rotate to the left (negative 60 degrees), and draw another line for the left branch. Finally, we have to rotate so we’re facing straight up again so that we can translate down the trunk again. Save and run this sketch, and you should see the Y in Figure 10-5.

We can convert this program that draws a single Y into one that draws a fractal by making the branches into smaller Y’s. But if we simply replace “line” with “y” in the y() function, our program will get stuck in an infinite loop, throwing an error like this:

RuntimeError: maximum recursion depth exceeded

Recall that we didn’t call factorial(n) inside the factorial function but rather called factorial(n-1). We have to introduce a level parameter to the y() function. Then each branch up, the tree will be a level down, so the branch will get the parameter level – 1. This means the trunk is always the highest numbered level and the last set of branches up the tree is always level 0. Here’s how to change the y() function in Listing 10-3.

fractals.pyde
def setup():
    size(600,600)

def draw():
    background(255)
    translate(300,500)
    y(100,2)

def y(sz,level):
    if level > 0:
        line(0,0,0,-sz)
        translate(0,-sz)
        rotate(radians(30))
        y(0.8*sz,level-1)
        rotate(radians(-60))
        y(0.8*sz,level-1)
        rotate(radians(30))
        translate(0,sz)

Listing 10-3: Adding recursion to the y() function

Notice that we replaced all the line() functions in the code with y() functions to draw the branches. Because we changed the call to the y() function in draw() to y(100,2), we’ll get a tree of trunk size 100 with two levels. Try a three-level tree, a four-level one, and so on! You should see something like Figure 10-6.

image

Figure 10-6: Trees of levels 1 through 4

Mapping the Mouse

Now let’s make a program that allows you to control the shape of the fractal in real time, just by moving your mouse up or down! We can vary the level of rotation dynamically by tracking the mouse and returning a value between 0 and 10 based on its location. Update the draw() function with the code in Listing 10-4.

fractals.pyde
def draw():
    background(255)
    translate(300,500)
    level = int(map(mouseX,0,width,0,10))
    y(100,level)

Listing 10-4: Adding the level parameter to the draw() function

Our mouse’s x-value can be anywhere between 0 and the width of the window. The map() function replaces one range of values with another. In Listing 10-4, map() will take the x-value and instead of the output being between 0 and 600 (the width of the display screen), it will be between 0 and 10, the range of levels we want to draw. So we assign that value to a variable called level and pass that value to the y() function in the next line.

Now that we’ve tweaked the draw() function to return a value based on the position of the mouse, we can vary the shape of our tree by linking the y-coordinate of the mouse to the angle we’re rotating by.

The angle of rotation should only go up to 180 because the tree will “fold up” completely at 180 degrees, but the mouse’s y-value can go up to 600 since that’s the height of the screen we declared in setup(). We could do a little math to convert the values ourselves, but it would be easier to just use Processing’s built-in map() function. We tell the map() function what variable we want to map, specifying its current minimum and maximum values and the desired minimum and maximum values. The entire code for the Y fractal tree is shown in Listing 10-5.

fractals.pyde
def setup():
    size(600,600)

def draw():
    background(255)
    translate(300,500)
    level = int(map(mouseX,0,width,0,15))
    y(100,level)

def y(sz,level):
    if level > 0:
        line(0,0,0,-sz)
        translate(0,-sz)
        angle = map(mouseY,0,height,0,180)
        rotate(radians(angle))
        y(0.8*sz,level-1)
        rotate(radians(-2*angle))
        y(0.8*sz,level-1)
        rotate(radians(angle))
        translate(0,sz)

Listing 10-5: The entire code to make a dynamic fractal tree

We take the mouse’s y-value and convert it to a range between 0 and 180 (if you already think in radians, you can map it to between 0 and pi). In the rotate() lines, we give it that angle (which is in degrees) and have Processing convert the degrees to radians. The first rotate() line will rotate to the right. The second rotate() line will rotate a negative angle, meaning to the left. It’ll rotate twice as much to the left. Then the third rotate() line will rotate to the right again.

When you run the code, you should see something like Figure 10-7.

image

Figure 10-7: A dynamic fractal tree

Now when you move the mouse up or down, left or right, the level and shape of the fractal should change accordingly.

Through drawing the fractal tree, you learned how to use recursion to draw complicated designs using a surprisingly small amount of code. Now we’ll return to the coastline problem. How could a coastline, or any line, double or triple in length just from getting more jagged?

KOCH SNOWFLAKE

The Koch snowflake is a famous fractal named after Swedish mathematician Helge von Koch, who wrote about the shape in a paper in 1904! It’s made from an equilateral triangle. We start with a line and add a “bump” to it. Then, we add a smaller bump to each resulting line segment and repeat the process, like in Figure 10-8.

image

Figure 10-8: Adding a “bump” to each segment

Let’s start a new Processing sketch, call it snowflake.pyde, and add the code in Listing 10-6, which will give us an upside-down equilateral triangle.

snowflake.pyde
def setup():
    size(600,600)

def draw():
    background(255)
    translate(100,100)
    snowflake(400,1)

def snowflake(sz,level):
    for i in range(3):
        line(0,0,sz,0)
        translate(sz,0)
        rotate(radians(120))

Listing 10-6: Writing the snowflake() function

In the draw() function, we call the snowflake() function, which for now takes only two parameters: sz (the size of the initial triangle) and level (the level of the fractal). The snowflake() function draws a triangle by starting a loop that repeats the code three times. Inside the loop we draw a line of length sz, which will be the side of the triangle, and then translate along the line to the next vertex of the triangle and rotate 120 degrees. Then we draw the next side of the triangle.

When you run the code in Listing 10-6, you should see Figure 10-9.

image

Figure 10-9: Level 1 snowflake: a triangle

WRITING THE SEGMENT() FUNCTION

Now we need to tell the program how to change a line into a segment that will have different levels. Level 0 will just be a straight line, but the next level will introduce the “bump” in the side. We’re really dividing the segment into three segments and then taking the middle segment and replicating it to make it into a little equilateral triangle. We’ll change the snowflake() function to call another function to draw the segment. This will be the recursive function, because as the levels go up, the segments will become smaller copies of the segment in Figure 10-10.

image

Figure 10-10: Cutting a segment into thirds and adding a “bump” to the middle third

We’ll call the side a segment. If the level is 0, the segment is simply a straight line, the side of the triangle. In the next step, a bump is added in the middle of the side. All the segments in Figure 10-10 are the same length, a third of the whole sidelength. This requires 11 steps:

  1. Draw a line a third of the sidelength.
  2. Translate to the end of the segment you just drew.
  3. Rotate –60 degrees (to the left).
  4. Draw another segment.
  5. Translate to the end of that segment.
  6. Rotate 120 degrees (to the right).
  7. Draw a third segment.
  8. Translate to the end of that segment.
  9. Rotate –60 degrees again (to the left).
  10. Draw the last segment.
  11. Translate to the end of that segment.

Now, instead of drawing a line, the snowflake() function will call a segment() function, which will do the drawing and translating. Add the segment() function in Listing 10-7.

snowflake.pyde
def snowflake(sz,level):
    for i in range(3):
        segment(sz,level)
        rotate(radians(120))

def segment(sz,level):
    if level == 0:
        line(0,0,sz,0)
        translate(sz,0)
    else:
        line(0,0,sz/3.0,0)
        translate(sz/3.0,0)
        rotate(radians(-60))
        line(0,0,sz/3.0,0)
        translate(sz/3.0,0)
        rotate(radians(120))
        line(0,0,sz/3.0,0)
        translate(sz/3.0,0)
        rotate(radians(-60))
        line(0,0,sz/3.0,0)
        translate(sz/3.0,0)

Listing 10-7: Drawing a “bump” on the sides of the triangle

In the segment() function, if the level is 0, it’s just a straight line, and we translate to the end of the line. Otherwise, we have 11 lines of code corresponding to the 11 steps of making a “bump.” First, we draw a line a third of the length of the side and then translate to the end of that line. We rotate left (–60 degrees) to draw the second segment in the line. That segment is also a third of the length of the side of the triangle. We translate to the end of that segment and then turn right by rotating 120 degrees. We then draw a segment and turn left one last time by rotating –60 degrees. Finally, we draw a fourth line (segment) and translate to the end of the side.

This draws a triangle if the level is 0 and puts a bump on each side if the level isn’t 0. As you can see in Figure 10-8, at every step, every segment in the previous step gets a bump. This would be a headache to do without recursion! But we’ll take the line of code that draws a line and change that into a segment, just one level lower. This is the recursive step.

Next, we need to replace each line with a segment one level down, whose length is sz divided by 3. The code for the segment() function is shown in Listing 10-8.

snowflake.pyde
def segment(sz,level):
    if level == 0:
        line(0,0,sz,0)
        translate(sz,0)
    else:
        segment(sz/3.0,level-1)
        rotate(radians(-60))
        segment(sz/3.0,level-1)
        rotate(radians(120))
        segment(sz/3.0,level-1)
        rotate(radians(-60))
        segment(sz/3.0,level-1)

Listing 10-8: Replacing the lines with segments

So all we did was replace each instance of line in Listing 10-7 (whose level is greater than 0) with segment(). Because we don’t want to enter an infinite loop, the segments have to be one level down (level – 1) from the previous segment. Now we can change the level of the snowflake in the draw() function, as shown in the following code, and we’ll see different designs, as shown in Figure 10-11.

def draw():
    background(255)
    translate(100,height-100)
    snowflake(400,3)

image

Figure 10-11: A level 3 snowflake

Even better, we can make it interactive by mapping the mouse’s x-value to the level. The mouse’s x-value can be anywhere from 0 to whatever the width of the screen is. We want to change that range to between 0 and 7. Here’s the code for that:

level = map(mouseX,0,width,0,7)

However, we want only integer levels, so we’ll change that value to an integer using int, like this:

level = int(map(mouseX,0,width,0,7))

We’ll add that to our draw() function and send the output “level” to the snowflake() function. The entire code for the Koch snowflake is shown in Listing 10-9.

snowflake.pyde
def setup():
    size(600,600)

def draw():
    background(255)
    translate(100,200)
    level = int(map(mouseX,0,width,0,7))
    #y(100,level)
    snowflake(400,level)

def snowflake(sz,level):
    for i in range(3):
        segment(sz,level)
        rotate(radians(120))

def segment(sz,level):
    if level == 0:
        line(0,0,sz,0)
        translate(sz,0)
    else:
        segment(sz/3.0,level-1)
        rotate(radians(-60))
        segment(sz/3.0,level-1)
        rotate(radians(120))
        segment(sz/3.0,level-1)
        rotate(radians(-60))
        segment(sz/3.0,level-1)

Listing 10-9: Complete code for the Koch snowflake

Now when you run the program and move your mouse left and right, you’ll see the snowflake get more “bumps” on its segments, like in Figure 10-12.

image

Figure 10-12: A level 7 snowflake

How does this help us understand the Coastline Paradox? Looking back at Figure 10-3, let’s call the length of the line (the side of the triangle) 1 unit (for example, 1 mile). When we split it in thirds, take out the middle, and add a “bump” two thirds long in the middle, the side is now 1 1/3 units long. It just got 1/3 longer, right? The perimeter of the snowflake (the “coastline”) gets 1/3 longer every step. So at the nth step, the length of the coastline is (4/3)n times the perimeter of the original triangle. It might not be possible to see, but after 20 steps, the coastline of the snowflake is so jagged that its total length is over 300 times the original measurement!

SIERPINSKI TRIANGLE

The Sierpinski triangle is a famous fractal first described by Polish mathematician Wacław Sierpiński in 1915, but there are examples of the design on the floors of churches in Italy from as far back as the 11th century! It follows a geometric pattern that’s easy to describe, but the design is surprisingly complicated. It works on an interesting recursive idea: draw a triangle for the first level, and for the next level turn each triangle into three smaller triangles at its corners, as shown in Figure 10-13.

image

Figure 10-13: Sierpinski triangles, levels 0, 1, and 2

The first step is easy: just draw a triangle. Open a new sketch and name it sierpinski.pyde. We set it up as usual, with setup() and draw() functions. In setup(), we set the size of the output window to 600 pixels by 600 pixels. In draw(), we set the background white and translate to a point (50,450) in the bottom left of the screen to start drawing our triangle. Next, we write a function named sierpinski(), similar to what we did with tree(), that draws a triangle if the level is 0. The code so far is shown in Listing 10-10.

sierpinski.pyde
def setup():
    size(600,600)

def draw():
    background(255)
    translate(50,450)
    sierpinski(400,0)

def sierpinski(sz, level):
    if level == 0: #draw a black triangle
        fill(0)
        triangle(0,0,sz,0,sz/2.0,-sz*sqrt(3)/2.0)

Listing 10-10: The setup of the Sierpinski fractal

The sierpinski() function takes two parameters: the size of the figure (sz) and the level variable. The fill color is 0 for black, but you can make it any color you want by using RGB values. The triangle line contains six numbers: the x- and y-coordinates of the three corners of an equilateral triangle with sidelength sz.

As you can see in Figure 10-13, level 1 contains three triangles at each corner of the original triangle. These triangles are also half the size of the triangle in the previous level. What we’ll do is create a smaller, lower-level Sierpinski triangle, translate to the next corner, and then rotate 120 degrees. Add the code in Listing 10-11 to the sierpinski() function.

def draw():
    background(255)
    translate(50,450)
    sierpinski(400,8)

def sierpinski(sz, level):
    if level == 0: #draw a black triangle
        fill(0)
        triangle(0,0,sz,0,sz/2.0,-sz*sqrt(3)/2.0)
    else: #draw sierpinskis at each vertex
        for i in range(3):
            sierpinski(sz/2.0,level-1)
            translate(sz/2.0,-sz*sqrt(3)/2.0)
            rotate(radians(120))

Listing 10-11: Adding the recursive step to the Sierpinski program

This new code tells Processing what to do when the level isn’t 0 (the line for i in range(3): means “repeat this three times”): draw a half-sized Sierpinski triangle of one level lower, and then translate halfway across and halfway up the equilateral triangle and turn right 120 degrees. Notice the sierpinski() function in sierpinski(sz/2.0,level-1) is executed inside the definition of the sierpinski() function itself. That’s the recursive step! When you call

sierpinski(400,8)

image

Figure 10-14: A level 8 Sierpinski triangle

in the draw() function, you get a level 8 Sierpinski triangle, which you see in Figure 10-14.

An interesting thing about the Sierpinski triangle is that it shows up in other fractals too, like the next one, which doesn’t start with a triangle.

SQUARE FRACTAL

We can make the Sierpinski triangle out of squares too. For example, we can create a square, remove the lower-right quadrant, and then replace each remaining quadrant with the resulting shape. When we repeat this process, we should get something like Figure 10-15.

image

Figure 10-15: The square fractal at levels 0, 1, 2, and 3

To create this fractal, we have to make each of the three smaller squares into a copy of the whole. Start a new Processing sketch called squareFractal.pyde and then set up the sketch with the code in Listing 10-12.

squareFractal.pyde
def setup():
    size(600,600)
    fill(150,0,150) #purple
    noStroke()

def draw():
    background(255)
    translate(50,50)
    squareFractal(500,0)

def squareFractal(sz,level):
    if level == 0:
        rect(0,0,sz,sz)

Listing 10-12: Creating the squareFractal() function

image

Figure 10-16: Purple square (level 0)

We can use the RGB values for purple in the setup() function just because we won’t be changing the fill anywhere else. We use noStroke() so that we won’t see black outlines on the squares. In the draw() function we call the squareFractal() function, telling it to make the size of each square 500 pixels and level 0. In the function definition, we tell the program to simply draw a square if the level is zero. This should give us a nice big purple square, as shown in Figure 10-16.

For the next level, we’ll make squares of half the sidelength of the initial square. One will be positioned at the top left of the figure; then we’ll translate around to put the other two squares at the bottom left and top right of Figure 10-16. Listing 10-13 does this while leaving out a quarter of the big square.

squareFractal.pyde
def squareFractal(sz,level):
    if level == 0:
        rect(0,0,sz,sz)
    else:
        rect(0,0,sz/2.0,sz/2.0)
        translate(sz/2.0,0)
        rect(0,0,sz/2.0,sz/2.0)
        translate(-sz/2.0,sz/2.0)
        rect(0,0,sz/2.0,sz/2.0)

Listing 10-13: Adding more squares to the square fractal

Here, we draw a big square if the level is 0. If the level is not 0, we add a smaller square in the top left of the screen, translate to the right, add another smaller square in the top right, translate left (negative x) and down (positive y), and add a smaller square at the bottom left of the screen.

That’s the next level, and when we update squareFractal(500,0) in the draw() function to squareFractal(500,1), it should give us a square with the bottom-right quarter left out, as shown in Figure 10-17.

image

Figure 10-17: The next level of the square fractal

For the next levels, we want each of the squares to be further subdivided into fractals, so we’ll replace the rect lines with squareFractal(), divide the value in sz by 2, and tell it to move one level down, like in Listing 10-14.

squareFractal.pyde
def squareFractal(sz,level):
    if level == 0:
        rect(0,0,sz,sz)
    else:
        squareFractal(sz/2.0,level-1)
        translate(sz/2.0,0)
        squareFractal(sz/2.0,level-1)
        translate(-sz/2.0,sz/2.0)
        squareFractal(sz/2.0,level-1)

Listing 10-14: Adding the recursive step to the square fractal

image

Figure 10-18: Not what we were expecting!

In Listing 10-14, notice that the rect lines (when the level isn’t 0) are replaced with squareFractal(). When we call squareFractal(500,2) in the draw() function, we don’t get the output we were expecting—we get Figure 10-18 instead.

This is because we didn’t translate back to the starting point like we did with our Y fractal earlier in the chapter.

Although we can calculate how much to translate manually, we can also use the pushMatrix() and popMatrix() functions in Processing, which you learned about in Chapter 5.

We can use the pushMatrix() function to save the current orientation of the screen—that is, where the origin (0,0) is located and how much the grid is rotated. After that, we can do as much translating and rotating as we like and then use the popMatrix() function to return to the saved orientation without any calculating!

Let’s add pushMatrix() at the beginning of the squareFractal() function and popMatrix() at the end, like in Listing 10-15.

squareFractal.pyde
def squareFractal(sz,level):
    if level == 0:
        rect(0,0,sz,sz)
    else:
        pushMatrix()
        squareFractal(sz/2.0,level-1)
        translate(sz/2.0,0)
        squareFractal(sz/2.0,level-1)
        translate(-sz/2.0,sz/2.0)
        squareFractal(sz/2.0,level-1)
        popMatrix()

Listing 10-15: Using pushMatrix() and popMatrix() to complete the squares

Now, each of the smaller squares from level 1 should be transformed into a fractal, with the bottom-right square removed, as shown in Figure 10-19.

image

Figure 10-19: Level 2 of the square fractal

Now let’s try making our mouse generate the level numbers like we’ve done before by replacing squareFractal(500,2) with the code in Listing 10-16.

squareFractal.pyde
def draw():
    background(255)
    translate(50,50)
    level = int(map(mouseX,0,width,0,7))
    squareFractal(500,level)

Listing 10-16: Making the square fractal interactive

At higher levels, the square fractal looks a lot like the Sierpinski triangle, as you can see in Figure 10-20!

image

Figure 10-20: High-level square fractals look like the Sierpinski triangle!

DRAGON CURVE

The final fractal we’ll create looks different from the others we’ve created so far in that the shapes on each level don’t get smaller, they get bigger. Figure 10-21 shows an example of the dragon curve for levels 0 through 3.

image

Figure 10-21: The first four levels of the dragon curve

As mathematical entertainer Vi Hart shows in one of her YouTube videos, the second half of the dragon curve is a perfect copy of the first half, and she models it by folding and then unfolding pieces of paper. The third level (level 2) in Figure 10-21 looks like two left turns followed by a right turn. The “hinge” or “fold” is at the midpoint of each dragon curve. See if you can find it in your dragon curves! Later, you’ll rotate part of the curve dynamically to match the next-level curve.

Open a new Processing sketch and name it dragonCurve.pyde. To create this fractal, we first create a function for the “left dragon,” as in Listing 10-17.

dragonCurve.pyde
def setup():
    size(600,600)
    strokeWeight(2) #a little thicker lines

def draw():
    background(255)
    translate(width/2,height/2)
    leftDragon(5,11)

def leftDragon(sz,level):
    if level == 0:
        line(0,0,sz,0)
        translate(sz,0)
    else:
        leftDragon(sz,level-1)
        rotate(radians(-90))
        rightDragon(sz,level-1)

Listing 10-17: Writing the leftDragon() function

After the usual setup() and draw() functions, we define our leftDragon() function. If the level is 0, we just draw a line and then translate along the line. It’s kind of like the turtle from Chapter 1 drawing a line as it walks along. If the level is greater than 0, make a left dragon (one level down), turn left 90 degrees, and make a right dragon (one level down).

Now we’ll make the “right dragon” function (see Listing 10-18). It’s pretty similar to the leftDragon() function. If the level is 0, simply draw a line and move along it. Otherwise, make a left dragon, and this time turn right 90 degrees and make a right dragon.

dragonCurve.pyde
def rightDragon(sz,level):
    if level == 0:
        line(0,0,sz,0)
        translate(sz,0)
    else:
        leftDragon(sz,level-1)
        rotate(radians(90))
        rightDragon(sz,level-1)

Listing 10-18: Writing the rightDragon() function

It’s interesting that the recursive statement in this case is not only inside one function, but it also jumps back and forth from the left dragon function to the right dragon function! Execute it, and the 11th level will look like Figure 10-22.

image

Figure 10-22: A level 11 dragon curve

Far from being simply a chaotic jumble of angles, this fractal starts to look like a dragon after enough levels! Remember I said the dragon curve is “folded” in the middle? In the version shown in Listing 10-19, I’ve added a few variables to change the level and the size, and I made an angle variable change with the mouse’s x-coordinate. This will rotate a dragon curve around a “hinge” in the middle of the next-level dragon curve. See how you can simply rotate the curve to get both halves of the next-level curve!

dragonCurve.pyde
RED = color(255,0,0)
  BLACK = color(0)

 def setup():
   global thelevel,size1
    size(600,600)
   thelevel = 1
    size1 = 40

 def draw():
    global thelevel
    background(255)
    translate(width/2,height/2)
   angle = map(mouseX,0,width,0,2*PI)
    stroke(RED)
    strokeWeight(3)
    pushMatrix()
    leftDragon(size1,thelevel)
    popMatrix()
    leftDragon(size1,thelevel-1)
   rotate(angle)
    stroke(BLACK)
    rightDragon(size1,thelevel-1)

 def leftDragon(sz,level):
    if level == 0:
        line(0,0,sz,0)
        translate(sz,0)
    else:
        leftDragon(sz,level-1)
        rotate(radians(-90))
        rightDragon(sz,level-1)

 def rightDragon(sz,level):
    if level == 0:
        line(0,0,sz,0)
        translate(sz,0)
    else:
        leftDragon(sz,level-1)
        rotate(radians(90))
        rightDragon(sz,level-1)

 def keyPressed():
    global thelevel,size1
   if key == CODED:
        if keyCode == UP:
            thelevel += 1
        if keyCode == DOWN:
            thelevel -= 1
        if keyCode == LEFT:
            size1 -= 5
        if keyCode == RIGHT:
            size1 += 5

Listing 10-19: A dynamic dragon curve

In Listing 10-19, we add a couple of colors to use for the curves. In the setup() function, we declare two global variables, thelevel and size1 , whose initial values we declare at and which we change with the arrow keys in the keyPressed() function at the end of the file.

In the draw() function, we link an angle variable to the x-position of the mouse. After that, we set the stroke color to red, make the stroke weight a little heavier, and draw a left dragon with the initial values of thelevel and size1. The pushMatrix() and popMatrix() functions, as you’ll remember, simply return the drawing point to the original spot, to draw another curve. Then we rotate the grid by however many radians the angle variable is , and draw another dragon curve, in black. The leftDragon() and rightDragon() functions are exactly the same as before.

Processing’s built-in keyPressed() function could come in handy for changing variables in a sketch! All you have to do is declare the global variables you want to change with the left (in this case), right, up, and down arrow keys on the keyboard. Note that CODED just means it’s not a letter or character key. Finally, it checks which arrow key is being pressed and makes the level variable go up or down (if the up or down arrow key is being pressed) or the size variable go up or down (if the left or right arrow key is being pressed).

When you run this version of the dragonCurve sketch, it draws a dragon curve at level 5 in red; then you can rotate a level 4 curve and see how the level 5 curve is made up of two level 4’s, just rotated in the middle, as shown in Figure 10-23.

image

Figure 10-23: A level 5 dragon curve and a dynamic, interactive level 4 curve

When you move the mouse, the black dragon curve should rotate, and you can see how it fits both halves of the red curve. The up and down arrow keys control the level of the curve; press the up arrow key and the curve gets longer. If the curve extends off the display window, use the left arrow key to make each segment shorter, so it’ll fit on the screen. The right arrow key makes it bigger.

This makes sense, because the leftDragon() function comes first, turns left, and makes a right dragon curve. The rightDragon() function just turns the opposite way from leftDragon(): it makes a right turn in the middle instead of a left. No wonder it turns out to be a perfect copy.

SUMMARY

We’ve only scratched the surface of fractals, but hopefully you got a taste of how beautiful fractals can be and how powerful they are at modeling the messiness of nature. Fractals and recursion can help us reevaluate our ideas about logic and measurement. The question is no longer “how long is the coastline?” but rather “how jagged is it?”

For fractal lines like coastlines and meandering rivers, the standard characteristic is the scale of self-similarity, or how much we have to scale the map up by before it looks like a different scale of the same thing. This is effectively what you did by feeding 0.8*sz, sz/2.0, or sz/3.0 into the next level.

In the next chapter, we’ll create cellular automata (CAs), which we’ll draw as little squares on the screen that are born, grow, and respond to their surroundings. Just like with our grass-eating sheep in Chapter 9, we’ll create CAs and let them run—and just like with fractals, we’ll watch the surprising and beautiful patterns that are created from very simple rules.