mirror of
https://github.com/nature-of-code/noc-book-2
synced 2024-11-17 07:49:05 +01:00
905 lines
No EOL
59 KiB
HTML
905 lines
No EOL
59 KiB
HTML
<section data-type="chapter">
|
||
<h1>Chapter 8. Fractals</h1>
|
||
<blockquote data-type="epigraph">
|
||
<p>“Pathological monsters! cried the terrified mathematician</p>
|
||
<p>Every one of them a splinter in my eye</p>
|
||
<p>I hate the Peano Space and the Koch Curve</p>
|
||
<p>I fear the Cantor Ternary Set</p>
|
||
<p>The Sierpinski Gasket makes me wanna cry</p>
|
||
<p>And a million miles away a butterfly flapped its wings</p>
|
||
<p>On a cold November day a man named Benoit Mandelbrot was born”</p>
|
||
</blockquote><a data-type="indexterm" data-primary="fractals"></a><a data-type="indexterm" data-primary="natural phenomena" data-secondary="fractals"></a><a data-type="indexterm" data-primary="Euclid"></a><a data-type="indexterm" data-primary="Euclidean geometry"></a>
|
||
<p>Once upon a time, I took a course in high school called “Geometry.” Perhaps you did too. You learned about shapes in one dimension, two dimensions, and maybe even three. What is the circumference of a circle? The area of a rectangle? The distance between a point and a line? Come to think of it, we’ve been studying geometry all along in this book, using vectors to describe the motion of bodies in Cartesian space. This sort of geometry is generally referred to as Euclidean geometry, after the Greek mathematician Euclid.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_1.png" alt="Figure 8.1">
|
||
<figcaption>Figure 8.1</figcaption>
|
||
</figure>
|
||
<p>For us nature coders, we have to ask the question: Can we describe our world with Euclidean geometry? The LCD screen I’m staring at right now sure looks like a rectangle. And the plum I ate this morning is circular. But what if I were to look further, and consider the trees that line the street, the leaves that hang off those trees, the lightning from last night’s thunderstorm, the cauliflower I ate for dinner, the blood vessels in my body, and the mountains and coastlines that cover land beyond New York City? Most of the stuff you find in nature cannot be described by the idealized geometrical forms of Euclidean geometry. So if we want to start building computational designs with patterns beyond the simple shapes <code>ellipse()</code>, <code>rect()</code>, and <code>line()</code>, it’s time for us to learn about the concepts behind and techniques for simulating the geometry of nature: fractals.</p>
|
||
<h2>8.1 What Is a Fractal?</h2><a data-type="indexterm" data-primary="fractals" data-secondary="defined"></a><a data-type="indexterm" data-primary="Mandelbrot" data-secondary="Benoit"></a><a data-type="indexterm" data-primary="<em>Fractal Geometry of Nature" data-secondary="The<" data-tertiary="em> (Mandelbrot)"></a>
|
||
<p>The term <strong><em>fractal</em></strong> (from the Latin <em>fractus</em>, meaning “broken”) was coined by the mathematician Benoit Mandelbrot in 1975. In his seminal work “The Fractal Geometry of Nature,” he defines a fractal as “a rough or fragmented geometric shape that can be split into parts, each of which is (at least approximately) a reduced-size copy of the whole.”</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_2.png" alt="Figure 8.2: One of the most well-known and recognizable fractal patterns is named for Benoit Mandelbrot himself. Generating the Mandelbrot set involves testing the properties of complex numbers after they are passed through an iterative function. Do they tend to infinity? Do they stay bounded? While a fascinating mathematical discussion, this “escape-time” algorithm is a less practical method for generating fractals than the recursive techniques we’ll examine in this chapter. However, an example for generating the Mandelbrot set is included in the code examples. ">
|
||
<figcaption>Figure 8.2: One of the most well-known and recognizable fractal patterns is named for Benoit Mandelbrot himself. Generating the Mandelbrot set involves testing the properties of complex numbers after they are passed through an iterative function. Do they tend to infinity? Do they stay bounded? While a fascinating mathematical discussion, this “escape-time” algorithm is a less practical method for generating fractals than the recursive techniques we’ll examine in this chapter. However, an example for generating the Mandelbrot set is included in the code examples. </figcaption>
|
||
</figure>
|
||
<p>Let’s illustrate this definition with two simple examples. First, let’s think about a tree branching structure (for which we’ll write the code later):</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_3.png" alt="Figure 8.3">
|
||
<figcaption>Figure 8.3</figcaption>
|
||
</figure>
|
||
<p>Notice how the tree in Figure 8.3 has a single root with two branches connected at its end. Each one of those branches has two branches at its end and those branches have two branches and so on and so forth. What if we were to pluck one branch from the tree and examine it on its own?</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_4.png" alt="Figure 8.4">
|
||
<figcaption>Figure 8.4</figcaption>
|
||
</figure><a data-type="indexterm" data-primary="fractals" data-secondary="self-replicating behavior of"></a><a data-type="indexterm" data-primary="self-similarity of fractals"></a>
|
||
<p>Looking closely at a given section of the tree, we find that the shape of this branch resembles the tree itself. This is known as <strong><em>self-similarity</em></strong>; as Mandelbrot stated, each part is a “reduced-size copy of the whole.”</p>
|
||
<p>The above tree is perfectly symmetrical and the parts are, in fact, exact replicas of the whole. However, fractals do not have to be perfectly self-similar. Let’s take a look at a graph of the stock market (adapted from actual Apple stock data).</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_5.png" alt="Figure 8.5: Graph A ">
|
||
<figcaption>Figure 8.5: Graph A </figcaption>
|
||
</figure>
|
||
<p>And one more.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_6.png" alt="Figure 8.6: Graph B ">
|
||
<figcaption>Figure 8.6: Graph B </figcaption>
|
||
</figure><a data-type="indexterm" data-primary="fractals" data-secondary="stochastic"></a><a data-type="indexterm" data-primary="stochastic fractals"></a>
|
||
<p>In these graphs, the x-axis is time and the y-axis is the stock’s value. It’s not an accident that I omitted the labels, however. Graphs of stock market data are examples of fractals because they look the same at any scale. Are these graphs of the stock over one year? One day? One hour? There’s no way for you to know without a label. (Incidentally, graph A shows six months’ worth of data and graph B zooms into a tiny part of graph A, showing six hours.)</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_7.png" alt="Figure 8.7">
|
||
<figcaption>Figure 8.7</figcaption>
|
||
</figure>
|
||
<p>This is an example of a <strong><em>stochastic</em></strong> fractal, meaning that it is built out of probabilities and randomness. Unlike the deterministic tree-branching structure, it is statistically self-similar. As we go through the examples in this chapter, we will look at both deterministic and stochastic techniques for generating fractal patterns.</p><a data-type="indexterm" data-primary="Euclidean geometry" data-secondary="fractals and"></a><a data-type="indexterm" data-primary="fractals" data-secondary="fine structure of"></a>
|
||
<p>While self-similarity is a key trait of fractals, it’s important to realize that self-similarity alone does not make a fractal. After all, a straight line is self-similar. A straight line looks the same at any scale, and can be thought of as comprising lots of little lines. But it’s not a fractal. Fractals are characterized by having a fine structure at small scales (keep zooming into the stock market graph and you’ll continue to find fluctuations) and cannot be described with Euclidean geometry. If you can say “It’s a line!” then it’s not a fractal.</p><a data-type="indexterm" data-primary="fractals" data-secondary="recursion"></a><a data-type="indexterm" data-primary="recursion"></a>
|
||
<p>Another fundamental component of fractal geometry is recursion. Fractals all have a recursive definition. We’ll start with recursion before developing techniques and code examples for building fractal patterns in p5.js.</p>
|
||
<h2>8.2 Recursion</h2><a data-type="indexterm" data-primary="Cantor set"></a><a data-type="indexterm" data-primary="Cantor" data-secondary="George"></a>
|
||
<p>Let’s begin our discussion of recursion by examining the first appearance of fractals in modern mathematics. In 1883, German mathematician George Cantor developed simple rules to generate an infinite set:</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_8.png" alt="Figure 8.8: The Cantor set ">
|
||
<figcaption>Figure 8.8: The Cantor set </figcaption>
|
||
</figure>
|
||
<p>There is a feedback loop at work here. Take a single line and break it into two. Then return to those two lines and apply the same rule, breaking each line into two, and now we’re left with four. Then return to those four lines and apply the rule. Now you’ve got eight. This process is known as <strong><em>recursion</em></strong>: the repeated application of a rule to successive results. Cantor was interested in what happens when you apply these rules an infinite number of times. We, however, are working in a finite pixel space and can mostly ignore the questions and paradoxes that arise from infinite recursion. We will instead construct our code in such a way that we do not apply the rules forever (which would cause our program to freeze).</p><a data-type="indexterm" data-primary="recursion" data-secondary="implementing"></a>
|
||
<p>Before I implement the Cantor set, let’s take a look at what it means to have recursion in code. Here’s something you are used to doing all the time—calling a function inside another function.</p>
|
||
<pre class="codesplit" data-code-language="javascript">function someFunction() {
|
||
//{!1} Calling the function background()
|
||
// in the definition of someFunction()
|
||
background(0);
|
||
}</pre>
|
||
<p>What would happen if you called the function you defined within the function itself? Can <code>someFunction()</code> call <code>someFunction()</code>?</p>
|
||
<pre class="codesplit" data-code-language="javascript">function someFunction() {
|
||
someFunction();
|
||
}</pre><a data-type="indexterm" data-primary="factorial"></a><a data-type="indexterm" data-primary="recursion" data-secondary="factorial"></a>
|
||
<p>In fact, this is not only allowed, but it’s quite common (and essential to how I will implement the Cantor set). Functions that call themselves are <em>recursive</em> and good for solving certain problems. For example, certain mathematical calculations are implemented recursively; the most common example is <em>factorial</em>.</p>
|
||
<p>The factorial of any number n, usually written as n!, is defined as:</p>
|
||
<div data-type="equation">n! = n × (n - 1) × … × 3 × 2 × 1</div>
|
||
<div data-type="equation">0! = 1</div>
|
||
<p>Here I'll write a function in p5.js that uses a <code>for</code> loop to calculate factorial:</p>
|
||
<pre class="codesplit" data-code-language="javascript">function factorial(n) {
|
||
let f = 1;
|
||
//{!3} Using a regular loop to compute factorial
|
||
for (let i = 0; i < n; i++) {
|
||
f = f * (i+1);
|
||
}
|
||
return f;
|
||
}</pre>
|
||
<p>Upon close examination, you’ll notice something interesting about how factorial works. Let’s look at 4! and 3!</p>
|
||
<div data-type="equation">4! = 4 * 3 * 2 * 1</div>
|
||
<div data-type="equation">3! = 3 * 2 * 1</div>
|
||
<p><strong><em>therefore. . .</em></strong></p>
|
||
<div data-type="equation">4! = 4 * 3!</div>
|
||
<p>In more general terms, for any positive integer n:</p>
|
||
<div data-type="equation">n! = n * (n-1)!</div>
|
||
<div data-type="equation">1! = 1</div>
|
||
<p>Written out:</p>
|
||
<p>The <em>factorial</em> of <code>n</code> is defined as <code>n</code> times the <em>factorial</em> of <code>n-1</code>.</p>
|
||
<p>The definition of <strong><em>factorial</em></strong> includes <strong><em>factorial</em></strong>?! It’s kind of like defining “tired" as “the feeling you get when you are tired.” This concept of self-reference in functions is an example of recursion. And you can use it to write a factorial function that calls itself.</p>
|
||
<pre class="codesplit" data-code-language="javascript">function factorial(n) {
|
||
if (n == 1) {
|
||
return 1;
|
||
} else {
|
||
return n * factorial(n-1);
|
||
}
|
||
}</pre>
|
||
<p>It may look crazy, but it works. Here are the steps that happen when <code>factorial(4)</code> is called.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_9.png" alt="Figure 8.9">
|
||
<figcaption>Figure 8.9</figcaption>
|
||
</figure>
|
||
<p>You can apply the same principle to graphics with interesting results, as you will see in many examples throughout this chapter. Take a look at this recursive function.</p>
|
||
<div data-type="example">
|
||
<h3>Example 8.1: Recursive Circles I</h3>
|
||
<p>https://editor.p5js.org/embed/SyjffpmOl</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_10.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">function drawCircle(x, y, radius) {
|
||
ellipse(x, y, radius);
|
||
if(radius > 2) {
|
||
radius *= 0.75f;
|
||
//{!1} The drawCircle() function is
|
||
// calling itself recursively.
|
||
drawCircle(x, y, radius);
|
||
}
|
||
}</pre>
|
||
<p><code>drawCircle()</code> draws an ellipse based on a set of parameters that it receives as arguments. It then calls itself with those same parameters, adjusting them slightly. The result is a series of circles, each of which is drawn inside the previous circle.</p><a data-type="indexterm" data-primary="exit conditions for recursion"></a><a data-type="indexterm" data-primary="recursion" data-secondary="exit conditions"></a>
|
||
<p>Notice that the above function only recursively calls itself if the radius is greater than 2. This is a crucial point. As with iteration, <em>all recursive functions must have an exit condition!</em> You likely are already aware that all <code>for</code> and <code>while</code> loops must include a Boolean expression that eventually evaluates to false, thus exiting the loop. Without one, the program would crash, caught inside of an infinite loop. The same can be said about recursion. If a recursive function calls itself forever and ever, you’ll be most likely be treated to a nice frozen screen.</p>
|
||
<p>This circles example is rather trivial; it could easily be achieved through simple iteration. However, for scenarios in which a function calls itself more than once, recursion becomes wonderfully elegant.</p>
|
||
<p>Let’s make <code>drawCircle()</code> a bit more complex. For every circle displayed, draw a circle half its size to the left and right of that circle.</p>
|
||
<div data-type="example">
|
||
<h3>Example 8.2: Recursion twice</h3>
|
||
<p>https://editor.p5js.org/embed/S1NQfaQOg</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_11.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">function setup() {
|
||
createCanvas(640, 360);
|
||
}
|
||
|
||
function draw() {
|
||
background(255);
|
||
drawCircle(width/2, height/2, 200);
|
||
}
|
||
|
||
function drawCircle(x, y, radius) {
|
||
stroke(0);
|
||
noFill();
|
||
ellipse(x, y, radius);
|
||
if(radius > 2) {
|
||
//{!2} drawCircle() calls itself twice, creating
|
||
// a branching effect. For every circle,
|
||
// a smaller circle is drawn to the left and the right.
|
||
drawCircle(x + radius/2, y, radius/2);
|
||
drawCircle(x - radius/2, y, radius/2);
|
||
}
|
||
}</pre>
|
||
<p>With just a little more code, we could also add a circle above and below each circle.</p>
|
||
<div data-type="example">
|
||
<h3>Example 8.3: Recursion four times</h3>
|
||
<p>https://editor.p5js.org/embed/r1-HfaXux</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_12.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">function drawCircle(x, y, radius) {
|
||
ellipse(x, y, radius);
|
||
if(radius > 8) {
|
||
drawCircle(x + radius/2, y, radius/2);
|
||
drawCircle(x - radius/2, y, radius/2);
|
||
drawCircle(x, y + radius/2, radius/2);
|
||
drawCircle(x, y - radius/2, radius/2);
|
||
}
|
||
}</pre>
|
||
<p>Try reproducing this sketch with iteration instead of recursion—I dare you!</p>
|
||
<h2>8.3 The Cantor Set with a Recursive Function</h2><a data-type="indexterm" data-primary="Cantor set" data-secondary="recursion and"></a>
|
||
<p>Now I'm ready to visualize the Cantor set in p5.js using a recursive function. Where do I begin? Well, I know that the Cantor set begins with a line. So I will start there and write a function that draws a line.</p>
|
||
<pre class="codesplit" data-code-language="javascript">function cantor(x, y, len) {
|
||
line(x, y, x+len, y);
|
||
}</pre>
|
||
<p>The above <code>cantor()</code> function draws a line that starts at pixel coordinate <em>(x,y)</em> with a length of <code>len</code>. (The line is drawn horizontally here, but this is an arbitrary decision.) So if I called that function, saying:</p>
|
||
<pre class="codesplit" data-code-language="javascript">cantor(10, 20, width-20);</pre>
|
||
<p>we’d get the following:</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_13.png" alt="Figure 8.10">
|
||
<figcaption>Figure 8.10</figcaption>
|
||
</figure>
|
||
<figure class="half-width-right">
|
||
<img src="images/08_fractals/08_fractals_14.png" alt="Figure 8.11">
|
||
<figcaption>Figure 8.11</figcaption>
|
||
</figure>
|
||
<p>Now, the Cantor rule tells us to erase the middle third of that line, which leaves us with two lines, one from the beginning of the line to the one-third mark, and one from the two-thirds mark to the end of the line.</p>
|
||
<p>We can now add two more lines of code to draw the second pair of lines, moving the y-position down a bunch of pixels so that we can see the result below the original line.</p>
|
||
<pre class="codesplit" data-code-language="javascript">function cantor(x, y, len) {
|
||
line(x,y,x+len,y);
|
||
|
||
y += 20;
|
||
//{.bold} From start to 1/3rd
|
||
line(x, y, x + len / 3, y);
|
||
//{!1 .bold} From 2/3rd to end
|
||
line(x + len * 2/3, y, x + len, y);
|
||
}</pre>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_15.png" alt="Figure 8.12">
|
||
<figcaption>Figure 8.12</figcaption>
|
||
</figure>
|
||
<p>While this is a fine start, such a manual approach of calling <code>line()</code> for each line is not what I want. It will get unwieldy very quickly, as I'd need four, then eight, then sixteen calls to <code>line()</code>. Yes, a <code>for</code> loop is the usual way around such a problem, but give that a try and you’ll see that working out the math for each iteration quickly proves inordinately complicated. Here is where recursion comes to the rescue.</p>
|
||
<p>Take a look at where I draw that first line from the start to the one-third mark.</p>
|
||
<pre class="codesplit" data-code-language="javascript"> line(x, y, x + len / 3, y);</pre>
|
||
<p>Instead of calling the <code>line()</code> function directly, I can simply call the <code>cantor()</code> function itself. After all, what does the <code>cantor()</code> function do? It draws a line at an <em>(x,y)</em> position with a given length! And so:</p>
|
||
<pre class="codesplit" data-code-language="javascript"> line(x, y, x + len/3, y); becomes ----> cantor(x, y, len/3);</pre>
|
||
<p>And for the second line:</p>
|
||
<pre class="codesplit" data-code-language="javascript"> line(x + len * 2/3, y, x + len, y); becomes ----> cantor(x + len * 2/3, y, len / 3);</pre>
|
||
<p>Leaving us with:</p>
|
||
<pre class="codesplit" data-code-language="javascript">function cantor(x, y, len) {
|
||
line(x, y, x + len, y);
|
||
|
||
y += 20;
|
||
|
||
cantor(x, y, len/3);
|
||
cantor(x + len * 2/3, y, len/3);
|
||
}</pre>
|
||
<p>And since the <code>cantor()</code> function is called recursively, the same rule will be applied to the next lines and to the next and to the next as <code>cantor()</code> calls itself again and again! Now, don’t go and run this code yet. The sketch missing that crucial element: an exit condition. You'll want to make sure to stop at some point—for example, if the length of the line ever is less than 1 pixel.</p>
|
||
<div data-type="example">
|
||
<h3>Example 8.4: Cantor set</h3>
|
||
<p>https://editor.p5js.org/embed/rkj_zTXde</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_16.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">function cantor(x, y, len) {
|
||
//{!1} Stop at 1 pixel!
|
||
if (len >= 1) {
|
||
line(x, y, x + len, y);
|
||
y += 20;
|
||
cantor(x, y, len/3);
|
||
cantor(x + len * 2/3, y, len/3);
|
||
}
|
||
}</pre>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.1</h3>
|
||
<p>Using <code>drawCircle()</code> and the Cantor set as models, generate your own pattern with recursion. Here is a screenshot of one that uses lines.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_17.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<h2>8.4 The Koch Curve</h2><a data-type="indexterm" data-primary="ArrayList class (Java)" data-secondary="fractals and"></a><a data-type="indexterm" data-primary="fractals" data-secondary="Koch curve"></a><a data-type="indexterm" data-primary="Koch curve"></a><a data-type="indexterm" data-primary="recursion" data-secondary="ArrayList objects and"></a>
|
||
<p>Writing a function that recursively calls itself is one technique for generating a fractal pattern on screen. However, what if you wanted the lines in the above Cantor set to exist as individual objects that could be moved independently? The recursive function is simple and elegant, but it does not allow you to do much besides simply generating the pattern itself. However, there is another way you can apply recursion in combination with an <code>ArrayList</code> that will allow you to not only generate a fractal pattern, but keep track of all its individual parts as objects.</p>
|
||
<p>To demonstrate this technique, I'll look at another famous fractal pattern, discovered in 1904 by Swedish mathematician Helge von Koch. Here are the rules. (Note that it starts the same way as the Cantor set, with a single line.)</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_18.png" alt="Figure 8.13">
|
||
<figcaption>Figure 8.13</figcaption>
|
||
</figure>
|
||
<p>The result looks like:</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_19.png" alt="Figure 8.14">
|
||
<figcaption>Figure 8.14</figcaption>
|
||
</figure><a data-type="indexterm" data-primary="Monster curve"></a>
|
||
<div data-type="note">
|
||
<h3>The “Monster” Curve</h3>
|
||
<p>The Koch curve and other fractal patterns are often called “mathematical monsters.” This is due to an odd paradox that emerges when you apply the recursive definition an infinite number of times. If the length of the original starting line is one, the first iteration of the Koch curve will yield a line of length four-thirds (each segment is one-third the length of the starting line). Do it again and you get a length of sixteen-ninths. As you iterate towards infinity, the length of the Koch curve approaches infinity. Yet it fits in the tiny finite space provided right here on this paper (or screen)!</p>
|
||
<p>Since you are working in the p5.js land of finite pixels, this theoretical paradox won’t be a factor for you. You'll have to limit the number of times you recursively apply the Koch rules so that your program won’t run out of memory or crash.</p>
|
||
</div>
|
||
<p>I could proceed in the same manner as I did with the Cantor set, and write a recursive function that iteratively applies the Koch rules over and over. Nevertheless, I am going to tackle this problem in a different manner by treating each segment of the Koch curve as an individual object. This will open up some design possibilities. For example, if each segment is an object, I could allow each segment to move independently from its original position and participate in a physics simulation. In addition, I could use a random color, line thickness, etc. to display each segment differently.</p>
|
||
<p>In order to accomplish this goal of treating each segment as an individual object, I must first decide what this object should be in the first place. What data should it store? What functions should it have?</p>
|
||
<p>The Koch curve is a series of connected lines, and so I will think of each segment as a “KochLine.” Each <code>KochLine</code> object has a start point (“a”) and an end point (“b”). These points are <code>p5.Vector</code> objects, and the line is drawn with p5.js’s <code>line()</code> function.</p>
|
||
<pre class="codesplit" data-code-language="javascript">class KochLine {
|
||
|
||
//{!2} A line between two points: start and end
|
||
constructor(a, b) {
|
||
// a and b are p5.Vector objects
|
||
this.start = a.copy();
|
||
this.end = b.copy();
|
||
}
|
||
|
||
display() {
|
||
stroke(0);
|
||
//{!1} Draw the line from PVector start to end.
|
||
line(this.start.x, this.start.y, this.end.x, this.end.y);
|
||
}
|
||
}</pre>
|
||
<p>Now that I have my <code>KochLine</code> class, I can get started on the main program. I'll need a data structure to keep track of what will eventually become many <code>KochLine</code> objects, and an <code>array</code> (see Chapter 4 for a review of <code>array</code><code>s</code>) will do just fine.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let lines;</pre>
|
||
<p>In <code>setup()</code>, I'll want to create the <code>array</code> and add the first line segment to it, a line that stretches from 0 to the width of the sketch.</p>
|
||
<pre class="codesplit" data-code-language="javascript">function setup() {
|
||
createCanvas(600, 300);
|
||
//{!1} Create the ArrayList.
|
||
lines = [];
|
||
|
||
// Left side of window
|
||
let start = createVector(0, 200);
|
||
// Right side of window
|
||
let end = createVector(width, 200);
|
||
|
||
//{!1} The first KochLine object
|
||
lines.add(new KochLine(start, end));
|
||
}</pre>
|
||
<p>Then in <code>draw()</code>, all <code>KochLine</code> objects (just one right now) can be displayed in a loop.</p>
|
||
<pre class="codesplit" data-code-language="javascript">function draw() {
|
||
background(255);
|
||
for (let l in lines) {
|
||
l.display();
|
||
}
|
||
}</pre>
|
||
<p>This is our foundation. Here's what I have so far:</p>
|
||
<ul>
|
||
<li><strong><em>KochLine class:</em></strong> A class to keep track of a line from point A to B.</li>
|
||
<li><strong><em>Array:</em></strong> A list of all <code>KochLine</code> objects.</li>
|
||
</ul>
|
||
<p>With the above elements, how and where should I apply Koch rules and principles of recursion?</p><a data-type="indexterm" data-primary="Koch curve" data-secondary="implementing"></a>
|
||
<p>Remember the Game of Life cellular automata? In that simulation, I always kept track of two generations: current and next. When I was finished computing the next generation, next became current and I moved on to computing the new next generation. I am going to apply a similar technique here. I have an <code>array</code> that keeps track of the current set of <code>KochLine</code> objects (at the start of the program, there is only one). I will need a second <code>array</code> (let’s call it “next”) where I will place all the new <code>KochLine</code> objects that are generated from applying the Koch rules. For every <code>KochLine</code> object in the current <code>array</code>, four new <code>KochLine</code> objects are added to the next <code>array</code>. When I'm done, the next <code>array</code> becomes the current one.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_20.png" alt="Figure 8.15">
|
||
<figcaption>Figure 8.15</figcaption>
|
||
</figure>
|
||
<p>Here’s how the code will look:</p>
|
||
<pre class="codesplit" data-code-language="javascript">function generate() {
|
||
// Create the next array...
|
||
let next = [];
|
||
|
||
// ...for every current line.
|
||
for (let l in lines) {
|
||
|
||
//{!4} Add four new lines. (I need to figure out how
|
||
// to compute the positions of these lines!)
|
||
next.push(new KochLine(???,???));
|
||
next.push(new KochLine(???,???));
|
||
next.push(new KochLine(???,???));
|
||
next.push(new KochLine(???,???));
|
||
}
|
||
// The new ArrayList is now the
|
||
// one I care about!
|
||
lines = next;
|
||
}</pre>
|
||
<p>By calling <code>generate()</code> over and over again (for example, each time the mouse is pressed), I recursively apply the Koch curve rules to the existing set of <code>KochLine</code> objects. Of course, the above omits the real “work” here, which is figuring out those rules. How do I break one line segment into four as described by the rules? While this can be accomplished with some simple arithmetic and trigonometry, since the <code>KochLine</code> object uses <code>p5.Vector</code>, this is a nice opportunity for me to practice my vector math. Let’s establish how many points I need to compute for each <code>KochLine</code> object.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_21.png" alt="Figure 8.16">
|
||
<figcaption>Figure 8.16</figcaption>
|
||
</figure>
|
||
<p>As you can see from the above figure, I need five points (a, b, c, d, and e) to generate the new <code>KochLine</code> objects and make the new line segments (ab, cb, cd, and de).</p>
|
||
<pre class="codesplit" data-code-language="javascript"> next.add(new KochLine(a, b));
|
||
next.add(new KochLine(b, c));
|
||
next.add(new KochLine(c, d));
|
||
next.add(new KochLine(d, e));</pre>
|
||
<p>Where do I get these points? Since I have a <code>KochLine</code> object, I can ask the <code>KochLine</code> object to compute all these points for me.</p>
|
||
<pre class="codesplit" data-code-language="javascript">function generate() {
|
||
let next = [];
|
||
for (let l in lines) {
|
||
|
||
//{!5} The KochLine object has five functions,
|
||
// each of which return a vector according
|
||
// to the Koch rules.
|
||
let a = l.kochA();
|
||
let b = l.kochB();
|
||
let c = l.kochC();
|
||
let d = l.kochD();
|
||
let e = l.kochE();
|
||
|
||
next.push(new KochLine(a, b));
|
||
next.push(new KochLine(b, c));
|
||
next.push(new KochLine(c, d));
|
||
next.push(new KochLine(d, e));
|
||
}
|
||
|
||
lines = next;
|
||
}</pre>
|
||
<p>Now I just need to write five new functions in the <code>KochLine</code> class, each one returning a <code>p5.Vector</code> according to Figure 8.16 above. Let’s knock off <code>kochA()</code> and <code>kochE()</code> first, which are simply the start and end points of the original line.</p>
|
||
<pre class="codesplit" data-code-language="javascript"> kochA() {
|
||
//{!1} Note the use of get(), which returns a copy of the PVector. As was noted in
|
||
// Chapter 6, section 14, I want to avoid making copies whenever
|
||
// possible, but here I will need a new p5.Vector in case I want the segments to move
|
||
// independently of each other.
|
||
return this.start.copy();
|
||
}
|
||
|
||
kochE() {
|
||
return this.end.copy();
|
||
}
|
||
</pre>
|
||
<p>Now I will move on to points B and D. B is one-third of the way along the line segment and D is two-thirds. Here I can make a <code>p5.Vector</code> that points from start to end and shrink it to one-third the length for B and two-thirds the length for D to find these points.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_22.png" alt="Figure 8.17">
|
||
<figcaption>Figure 8.17</figcaption>
|
||
</figure>
|
||
<pre class="codesplit" data-code-language="javascript"> kochB() {
|
||
// vector from start to end
|
||
let v = p5.Vector.sub(this.end, this.start);
|
||
// One-third the length
|
||
v.div(3);
|
||
//{!1} Add that vector to the beginning of the line
|
||
// to find the new point.
|
||
v.add(this.start);
|
||
return v;
|
||
}
|
||
|
||
kochD() {
|
||
let v = p5.Vector.sub(this.end, this.start);
|
||
//{!1} Same thing here, only I need to move two-thirds
|
||
// along the line instead of one-third.
|
||
v.mult(2 / 3.0);
|
||
v.add(this.start);
|
||
return v;
|
||
}</pre>
|
||
<p>The last point, C, is the most difficult one to find. However, if you recall that the angles of an equilateral triangle are all sixty degrees, this makes it a little bit easier. If I know how to find point B with a <code>p5.Vector</code> one-third the length of the line, what if I were to rotate that same <code>p5.Vector</code> sixty degrees and move along that vector from point B? I'd be at point C!</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_23.png" alt="Figure 8.18">
|
||
<figcaption>Figure 8.18</figcaption>
|
||
</figure>
|
||
<pre class="codesplit" data-code-language="javascript"> kochC() {
|
||
//{!1} Start at the beginning.
|
||
let a = this.start.copy();
|
||
|
||
let v = p5.Vector.sub(end, start);
|
||
//{!1} Move 1/3rd of the way to point B.
|
||
v.div(3);
|
||
a.add(v);
|
||
|
||
//{!1} Rotate “above” the line 60 degrees.
|
||
v.rotate(-radians(60));
|
||
//{!1} Move along that vector to point C.
|
||
a.add(v);
|
||
|
||
return a;
|
||
}</pre>
|
||
<p>Putting it all together, if we call <code>generate()</code> five times in <code>setup()</code>, we’ll see the following result.</p>
|
||
<div data-type="example">
|
||
<h3>Example 8.5: Koch curve</h3>
|
||
<p>https://editor.p5js.org/embed/rJUB76mdl</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_24.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">let lines;
|
||
|
||
function setup() {
|
||
createCanvas(600, 300);
|
||
background(255);
|
||
lines = [];
|
||
let start = createVector(0, 200);
|
||
let end = createVector(width, 200);
|
||
lines.push(new KochLine(start, end));
|
||
|
||
//{!3} Arbitrarily apply the Koch rules five times.
|
||
for (int i = 0; i < 5; i++) {
|
||
generate();
|
||
}
|
||
}</pre>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.2</h3>
|
||
<figure class="half-width-right">
|
||
<img src="images/08_fractals/08_fractals_25.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
<p>Draw the Koch snowflake (or some other variation of the Koch curve).</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_26.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.3</h3>
|
||
<p>Try animating the Koch curve. For example, can you draw it from left to right? Can you vary the visual design of the line segments? Can you move the line segments using techniques from earlier chapters? What if each line segment were made into a spring (toxiclibs) or joint (Box2D)?</p>
|
||
</div>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.4</h3>
|
||
<p>Rewrite the Cantor set example using objects and an <code>array</code>.</p>
|
||
</div>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.5</h3>
|
||
<p>Draw the Sierpiński triangle (as seen in Wolfram elementary CA) using recursion.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_27.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<h2>8.5 Trees</h2><a data-type="indexterm" data-primary="fractals" data-secondary="trees and"></a><a data-type="indexterm" data-primary="natural fractals"></a><a data-type="indexterm" data-primary="natural phenomena" data-secondary="trees and"></a><a data-type="indexterm" data-primary="stochastic fractals" data-secondary="trees as"></a><a data-type="indexterm" data-primary="trees"></a>
|
||
<p>The fractals I've shown in this chapter so far are deterministic, meaning they have no randomness and will always produce the identical outcome each time they are run. They are excellent demonstrations of classic fractals and the programming techniques behind drawing them, but are too precise to feel <em>natural</em>. In this next part of the chapter, I want to examine some techniques behind generating a stochastic (or non-deterministic) fractal. The example I'll use is a branching tree. I'll first walk through the steps to create a deterministic version. Here are our production rules:</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_28.png" alt="Figure 8.19">
|
||
<figcaption>Figure 8.19</figcaption>
|
||
</figure>
|
||
<p>Again, I have a nice fractal with a recursive definition: A branch is a line with two branches connected to it.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_29.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure><a data-type="indexterm" data-primary="Transformations tutorial (Processing)"></a><a data-type="indexterm" data-primary="fractals" data-secondary="transformation matrix (Processing)"></a><a data-type="indexterm" data-primary="popMatrix() function (Processing)"></a><a data-type="indexterm" data-primary="pushMatrix() function (Processing)"></a><a data-type="indexterm" data-primary="transformation matrix (Processing)"></a>
|
||
<p>The part that is a bit more difficult than our previous fractals lies in the use of the word <em>rotate</em> in the fractal’s rules. Each new branch must rotate relative to the previous branch, which is rotated relative to all its previous branches. Luckily for us, p5.js has a mechanism to keep track of rotations for you—the <strong><em>transformation matrix</em></strong>. If you aren’t familiar with the functions <code>push()</code> and <code>pop()</code>, I suggest you read the online p5.js tutorial 2D Transformations, which will cover the concepts you’ll need for this particular example.</p>
|
||
<p>Let’s begin by drawing a single branch, the trunk of the tree. Since I am going to involve the <code>rotate()</code> function, I'll need to make sure I am continuously translating along the branches while we draw the tree. And since the root starts at the bottom of the window (see above), the first step requires translating to that spot:</p>
|
||
<pre class="codesplit" data-code-language="javascript">translate(width/2,height);</pre>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_30.png" alt="Figure 8.20">
|
||
<figcaption>Figure 8.20</figcaption>
|
||
</figure>
|
||
<p>…followed by drawing a line upwards (Figure 8.20):</p>
|
||
<pre class="codesplit" data-code-language="javascript">line(0, 0, 0, -100);</pre>
|
||
<p>Once I've finished the root, I need to translate to the end and rotate in order to draw the next branch. (Eventually, I'm going to need to package up what I'm doing right now into a recursive function, but I'll sort out the steps first.)</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_31.png" alt="Figure 8.21">
|
||
<figcaption>Figure 8.21</figcaption>
|
||
</figure>
|
||
<p>Remember, when you rotate in p5.js, you are always rotating around the point of origin, so here the point of origin must always be translated to the end of our current branch.</p>
|
||
<pre class="codesplit" data-code-language="javascript">translate(0, -100);
|
||
rotate(PI / 6);
|
||
line(0, 0, 0, -100);</pre>
|
||
<p>Now that we have a branch going to the right, I need one going to the left. I can use <code>push()</code> to save the transformation state before I rotate, letting me call <code>pop()</code> to restore that state and draw the branch to the left. Let’s look at all the code together.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_32.png" alt="Figure 8.22">
|
||
<figcaption>Figure 8.22</figcaption>
|
||
</figure>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_33.png" alt="Figure 8.23">
|
||
<figcaption>Figure 8.23</figcaption>
|
||
</figure>
|
||
<pre class="codesplit" data-code-language="javascript">translate(width/2,height);
|
||
//{!1} The root
|
||
line(0, 0, 0, -100);
|
||
translate(0, -100);
|
||
|
||
// Branch to the right
|
||
push();
|
||
rotate(PI / 6);
|
||
line(0, 0, 0, -100);
|
||
pop();
|
||
|
||
// Branch to the left
|
||
rotate(-PI / 6);
|
||
line(0, 0, 0, -100);</pre>
|
||
<p>If you think of each call to the function <code>line()</code> as a “branch,” you can see from the code above that I have implemented a definition of branching as a line that has two lines connected to its end. I could keep adding more and more calls to <code>line()</code> for more and more branches, but just as with the Cantor set and Koch curve, my code would become incredibly complicated and unwieldy. Instead, I can use the above logic as a foundation for writing a recursive function, replacing the direct calls to <code>line()</code> with my own function called <code>branch()</code>. Let’s take a look.</p>
|
||
<pre class="codesplit" data-code-language="javascript">function branch() {
|
||
// Draw the branch itself.
|
||
line(0, 0, 0, -100);
|
||
// Translate to the end.
|
||
translate(0, -100);
|
||
|
||
push();
|
||
//{!2} Rotate to the right and branch again.
|
||
rotate(PI / 6);
|
||
branch();
|
||
pop();
|
||
|
||
push();
|
||
//{!2} Rotate to the left and branch again.
|
||
rotate(-PI/6);
|
||
branch();
|
||
pop();
|
||
}</pre>
|
||
<p>Notice how in the above code I use <code>push()</code> and <code>pop()</code> around each subsequent call to <code>branch()</code>. This is one of those elegant code solutions that feels almost like magic. Each call to <code>branch()</code> takes a moment to remember the position of that particular branch. If you turn yourself into p5.js for a moment and try to follow the recursive function with pencil and paper, you’ll notice that it draws all of the branches to the right first. When it gets to the end, <code>pop()</code> will pop me back along all of the branches I've drawn and start sending branches out to the left.</p>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.6</h3>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_34.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
<p>Emulate the p5.js code in Example 8.6 and number the branches in the above diagram in the order that p5.js would actually draw each one.</p>
|
||
</div>
|
||
<p>You may have noticed that the recursive function I just wrote would not actually draw the above tree. After all, it has no exit condition and would get stuck in infinite recursive calls to itself. You’ll also probably notice that the branches of the tree get shorter at each level. Let’s look at how we can shrink the length of the lines as the tree is drawn, and stop branching once the lines have become too short.</p>
|
||
<pre class="codesplit" data-code-language="javascript">//{!1} Each branch now receives
|
||
// its length as an argument.
|
||
function branch(len) {
|
||
|
||
line(0, 0, 0, -len);
|
||
translate(0, -len);
|
||
|
||
//{!1} Each branch’s length
|
||
// shrinks by two-thirds.
|
||
len *= 0.66;
|
||
|
||
if (len > 2) {
|
||
push();
|
||
rotate(theta);
|
||
//{!1} Subsequent calls to branch()
|
||
// include the length argument.
|
||
branch(len);
|
||
pop();
|
||
|
||
push();
|
||
rotate(-theta);
|
||
branch(len);
|
||
pop();
|
||
}
|
||
}</pre>
|
||
<p>I've also included a variable for theta that allows me, when writing the rest of the code in <code>setup()</code> and <code>draw()</code>, to vary the branching angle according to, say, the <code>mouseX</code> position.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_35.png" alt="ch08 ex06a ">
|
||
<figcaption>ch08 ex06a </figcaption>
|
||
</figure>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_36.png" alt="ch08 ex06b ">
|
||
<figcaption>ch08 ex06b </figcaption>
|
||
</figure>
|
||
<div data-type="example">
|
||
<h3>Example 8.7: Recursive tree</h3>
|
||
<p>{p5 sketch link}</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_37.png" alt="ch08 ex06c ">
|
||
<figcaption>ch08 ex06c </figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">let theta;
|
||
|
||
function setup() {
|
||
createCanvas(300, 200);
|
||
}
|
||
|
||
function draw() {
|
||
background(255);
|
||
//{!1} Pick an angle according to
|
||
// the mouse position.
|
||
theta = map(mouseX, 0, width, 0, PI/2);
|
||
|
||
//{!1} The first branch starts at the
|
||
// bottom of the window.
|
||
translate(width/2, height);
|
||
stroke(0);
|
||
branch(60);
|
||
}</pre>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.7</h3>
|
||
<p>Vary the <code>strokeWeight()</code> for each branch. Make the root thick and each subsequent branch thinner.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_38.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.8</h3>
|
||
<p>The tree structure can also be generated using the <code>array</code> technique demonstrated with the Koch curve. Recreate the tree using a <code>Branch</code> object and an <code>array</code> to keep track of the branches. Hint: you’ll want to keep track of the branch directions and lengths using vector math instead of p5.js transformations.</p>
|
||
</div>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.9</h3>
|
||
<p>Once you have the tree built with an <code>array</code> of <code>Branch</code> objects, animate the tree’s growth. Can you draw leaves at the end of the branches?</p>
|
||
</div>
|
||
<p>The recursive tree fractal is a nice example of a scenario in which adding a little bit of randomness can make the tree look more natural. Take a look outside and you’ll notice that branch lengths and angles vary from branch to branch, not to mention the fact that branches don’t all have exactly the same number of smaller branches. First, let’s see what happens when we simply vary the angle and length. This is a pretty easy one, given that we can just ask p5.js for a random number each time we draw the tree.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_39.png" alt="ch08 ex07a ">
|
||
<figcaption>ch08 ex07a </figcaption>
|
||
</figure>
|
||
<pre class="codesplit" data-code-language="javascript">function branch(len) {
|
||
//{!1} Start by picking a random
|
||
// angle for each branch.
|
||
let theta = random(0, PI / 3);
|
||
|
||
line(0, 0, 0, -len);
|
||
translate(0, -len);
|
||
len *= 0.66;
|
||
if (len > 2) {
|
||
push();
|
||
rotate(theta);
|
||
branch(len);
|
||
pop();
|
||
push();
|
||
rotate(-theta);
|
||
branch(len);
|
||
pop();
|
||
}
|
||
}</pre>
|
||
<p>In the above function, I always call <code>branch()</code> twice. But why not pick a random number of branches and call <code>branch()</code> that number of times?</p>
|
||
<div data-type="example">
|
||
<h3>Example 8.8: Stochastic tree</h3>
|
||
<p>https://editor.p5js.org/embed/H1QwVaX_x</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_40.png" alt="ch08 ex07b ">
|
||
<figcaption>ch08 ex07b </figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">function branch(len) {
|
||
|
||
line(0, 0, 0, -len);
|
||
translate(0, -len);
|
||
|
||
if (len > 2) {
|
||
|
||
//{!1} Call branch() a random
|
||
// number of times.
|
||
let n = int(random(1,4));
|
||
for (let i = 0; i < n; i++) {
|
||
|
||
//{!1} Each branch gets its own random angle.
|
||
let theta = random(-PI/2, PI/2);
|
||
push();
|
||
rotate(theta);
|
||
branch(h);
|
||
pop();
|
||
}
|
||
}</pre>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.10</h3>
|
||
<p>Set the angles of the branches of the tree according to Perlin noise values. Adjust the noise values over time to animate the tree. See if you can get it to appear as if it is blowing in the wind.</p>
|
||
</div>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.11</h3>
|
||
<p>Use toxiclibs to simulate tree physics. Each branch of the tree should be two particles connected with a spring. How can you get the tree to stand up and not fall down?</p>
|
||
</div>
|
||
<h2>8.6 L-systems</h2><a data-type="indexterm" data-primary="fractals" data-secondary="L-systems and"></a><a data-type="indexterm" data-primary="L-systems"></a><a data-type="indexterm" data-primary="Lindenmayer systems"></a><a data-type="indexterm" data-primary="Lindenmayer" data-secondary="Aristid"></a><a data-type="indexterm" data-primary="natural phenomena" data-secondary="L-systems and"></a>
|
||
<p>In 1968, Hungarian botanist Aristid Lindenmayer developed a grammar-based system to model the growth patterns of plants. L-systems (short for Lindenmayer systems) can be used to generate all of the recursive fractal patterns we’ve seen so far in this chapter. You don’t need L-systems to do the kind of work I'm doing here; however, they are incredibly useful because they provide a mechanism for keeping track of fractal structures that require complex and multi-faceted production rules.</p>
|
||
<p>In order to create an example that implements L-systems in p5.js, you are going to have to be comfortable with working with (a) recursion, (b) transformation matrices, and (c) strings. So far you've worked with recursion and transformations, but strings are new here. I will assume the basics, but if that is not comfortable for you, I would suggest taking a look at the p5.js tutorial Strings and Drawing Text.</p>
|
||
<p>An L-system involves three main components:</p><a data-type="indexterm" data-primary="alphabet (L-system component)"></a><a data-type="indexterm" data-primary="L-systems" data-secondary="components of"></a><a data-type="indexterm" data-primary="natural phenomena" data-secondary="plant growth" data-tertiary="modeling"></a><a data-type="indexterm" data-primary="plant growth" data-secondary="modeling"></a>
|
||
<ul>
|
||
<li><strong><em>Alphabet.</em></strong> An L-system’s alphabet is comprised of the valid characters that can be included. For example, I could say the alphabet is “ABC,” meaning that any valid “sentence” (a string of characters) in an L-system can only include these three characters.</li>
|
||
</ul><a data-type="indexterm" data-primary="axiom (L-system component)"></a>
|
||
<ul>
|
||
<li><strong><em>Axiom.</em></strong> The axiom is a sentence (made up with characters from the alphabet) that describes the initial state of the system. For example, with the alphabet “ABC,” some example axioms are “AAA” or “B” or “ACBAB.”</li>
|
||
</ul><a data-type="indexterm" data-primary="rules (L-system component)"></a>
|
||
<ul>
|
||
<li><strong><em>Rules.</em></strong> The rules of an L-system are applied to the axiom and then applied recursively, generating new sentences over and over again. An L-system rule includes two sentences, a “predecessor” and a “successor.” For example, with the Rule “A —> AB”, whenever an “A” is found in a string, it is replaced with “AB.”</li>
|
||
</ul>
|
||
<p>Let’s begin with a very simple L-system. (This is, in fact, Lindenmayer’s original L-system for modeling the growth of algae.)</p>
|
||
<figure class="half-width-right">
|
||
<img src="images/08_fractals/08_fractals_41.png" alt="Figure 8.24: And so on and so forth... ">
|
||
<figcaption>Figure 8.24: And so on and so forth... </figcaption>
|
||
</figure>
|
||
<div data-type="equation">Alphabet: A B</div>
|
||
<div data-type="equation">Axiom: A</div>
|
||
<div data-type="equation">Rules: (A → AB) (B → A)</div>
|
||
<p>As with our recursive fractal shapes, we can consider each successive application of the L-system rules to be a generation. Generation 0 is, by definition, the axiom.</p>
|
||
<p>Let’s look at how we might create these generations with code. We’ll start by using a <code>String</code> object to store the axiom.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let current = "A";</pre>
|
||
<p>And once again, just as we did with the Game of Life and the Koch curve <code>array</code> examples, we will need an entirely separate string to keep track of the “next” generation.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let next = "";</pre>
|
||
<p>Now it’s time to apply the rules to the current generation and place the results in the next.</p>
|
||
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i < current.length; i++) {
|
||
let c = current.charAt(i);
|
||
//{!1} Production rule A --> AB
|
||
if (c === 'A') {
|
||
next += "AB";
|
||
//{!1} Production rule B --> A
|
||
} else if (c === 'B') {
|
||
next += "A";
|
||
}
|
||
}</pre>
|
||
<p>And when it's done, current can become next.</p>
|
||
<pre class="codesplit" data-code-language="javascript">current = next;</pre>
|
||
<p>To be sure this is working, I will package it into a function and and call it every time the mouse is pressed.</p>
|
||
<div data-type="example">
|
||
<h3>Example 8.9: Simple L-system sentence generation</h3>
|
||
<p>{p5 sketch link}</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_42.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">//{!1} Start with an axiom.
|
||
let current = "A";
|
||
//{!1} Let’s keep track of how many generations.
|
||
let count = 0;
|
||
|
||
function setup() {
|
||
print("Generation " + count + ": " + current);
|
||
}
|
||
|
||
function draw() {
|
||
}
|
||
|
||
function mousePressed() {
|
||
let next = "";
|
||
//{!8} Traverse the current String and make the new one.
|
||
for (let i = 0; i < current.length; i++) {
|
||
let c = current.charAt(i);
|
||
if (c === 'A') {
|
||
next += "AB";
|
||
} else if (c === 'B') {
|
||
next += "A";
|
||
}
|
||
}
|
||
current = next;
|
||
count++;
|
||
print("Generation " + count + ": " + current);
|
||
}</pre>
|
||
<p>You may find yourself wondering right about now: what exactly is the point of all this? After all, isn’t this a chapter about <em>drawing</em> fractal patterns? Yes, the recursive nature of the L-system sentence structure seems relevant to the discussion, but how exactly does this model plant growth in a visual way?</p>
|
||
<p>What I've left unsaid until now is that embedded into these L-system sentences are instructions for drawing. Let’s see how this works with another example.</p>
|
||
<div data-type="equation">Alphabet: A B</div>
|
||
<div data-type="equation">Axiom: A</div>
|
||
<div data-type="equation">Rules: (A → ABA) (B → BBB)</div>
|
||
<p>To read a sentence, I'll translate it in the following way:</p>
|
||
<div data-type="equation">A: Draw a line forward.</div>
|
||
<div data-type="equation">B: Move forward without drawing.</div>
|
||
<p>Let’s look at the sentence of each generation and its visual output.</p>
|
||
<div data-type="equation">Generation 0: A</div>
|
||
<div data-type="equation">Generation 1: ABA</div>
|
||
<div data-type="equation">Generation 2: ABABBBABA</div>
|
||
<div data-type="equation">Generation 3: ABABBBABABBBBBBBBBABABBBABA</div><a data-type="indexterm" data-primary="Cantor set" data-secondary="L-systems and"></a>
|
||
<p>Look familiar? This is the Cantor set generated with an L-system.</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_43.png" alt="Figure 8.25">
|
||
<figcaption>Figure 8.25</figcaption>
|
||
</figure>
|
||
<p>The following alphabet is often used with L-systems: “FG+-[]”, meaning:</p>
|
||
<div data-type="equation">F: Draw a line and move forward</div>
|
||
<div data-type="equation">G: Move forward (without drawing a line)</div>
|
||
<div data-type="equation">+: Turn right</div>
|
||
<div data-type="equation">-: Turn left</div>
|
||
<div data-type="equation">[: Save current position</div>
|
||
<div data-type="equation">]: Restore previous position</div><a data-type="indexterm" data-primary="Turtle graphics"></a>
|
||
<p>This type of drawing framework is often referred to as “Turtle graphics” (from the old days of LOGO programming). Imagine a turtle sitting on your computer screen to which you could issue a small set of commands: turn left, turn right, draw a line, etc. p5.js isn’t set up to operate this way by default, but by using <code>translate()</code>, <code>rotate()</code>, and <code>line()</code>, we can emulate a Turtle graphics engine fairly easily.</p><a data-type="indexterm" data-primary="L-systems" data-secondary="translating into code"></a>
|
||
<p>Here’s how I would translate the above L-system alphabet into p5.js code.</p>
|
||
<div data-type="equation">F: line(0,0,0,len); translate(0,len);</div>
|
||
<div data-type="equation">G: translate(0,len);</div>
|
||
<div data-type="equation">+: rotate(angle);</div>
|
||
<div data-type="equation">-: rotate(-angle);</div>
|
||
<div data-type="equation">[: push();</div>
|
||
<div data-type="equation">]: pop();</div>
|
||
<p>Assuming I have a sentence generated from the L-system, I can walk through the sentence character by character and call the appropriate function as outlined above.</p>
|
||
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i < sentence.length; i++) {
|
||
|
||
//{!1} Looking at each character one at a time
|
||
let c = sentence.charAt(i);
|
||
|
||
//{!14} Performing the correct task for each character.
|
||
// This could also be written with a “case” statement,
|
||
// which might be nicer to look at, but leaving it as an
|
||
// if/else if structure helps readers not familiar with case statements.
|
||
if (c == 'F') {
|
||
line(0, 0, len, 0);
|
||
translate(len, 0);
|
||
} else if (c == 'G') {
|
||
translate(len, 0);
|
||
} else if (c == '+') {
|
||
rotate(theta);
|
||
} else if (c == '-') {
|
||
rotate(-theta);
|
||
} else if (c == '[') {
|
||
push();
|
||
} else if (c == ']') {
|
||
pop();
|
||
}
|
||
}</pre>
|
||
<p>The next example will draw a more elaborate structure with the following L-system.</p>
|
||
<div data-type="equation">Alphabet: FG+-[]</div>
|
||
<div data-type="equation">Axiom: F</div>
|
||
<div data-type="equation">Rules: F -→ FF+[+F-F-F]-[-F+F+F]</div>
|
||
<p>The example available for download on the book’s website takes all of the L-system code provided in this section and organizes it into three classes:</p>
|
||
<ul>
|
||
<li>Rule: A class that stores the predecessor and successor strings for an L-system rule.</li>
|
||
<li>LSystem: A class to iterate a new L-system generation (as demonstrated with the <code>StringBuffer</code> technique).</li>
|
||
<li>Turtle: A class to manage reading the L-system sentence and following its instructions to draw on the screen.</li>
|
||
</ul>
|
||
<p>We won’t write out these classes here since they simply duplicate the code we’ve already worked out in this chapter. However, let’s see how they are put together in the main tab.</p>
|
||
<div data-type="example">
|
||
<h3>Example 8.10: LSystem</h3>
|
||
<p>https://editor.p5js.org/embed/By4ED6mOg</p>
|
||
<figure>
|
||
<img src="images/08_fractals/08_fractals_44.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">let lsys;
|
||
let turtle;
|
||
|
||
function setup() {
|
||
createCanvas(600,600);
|
||
|
||
//{.code-wide} A ruleset is an array of Rule objects.
|
||
let ruleset = [];
|
||
ruleset[0] = new Rule('F',"FF+[+F-F-F]-[-F+F+F]");
|
||
|
||
// The L-system is created with an axiom and a ruleset.
|
||
lsys = new LSystem("F",ruleset);
|
||
|
||
//{!2 .offset} The Turtle graphics renderer is given a sentence,
|
||
// a starting length, and an angle for rotations.
|
||
turtle = new Turtle(lsys.getSentence(), width/4, radians(25));
|
||
}
|
||
|
||
function draw() {
|
||
background(255);
|
||
//{!2} Start at the bottom of the window and draw.
|
||
translate(width/2, height);
|
||
turtle.render();
|
||
}
|
||
|
||
function mousePressed() {
|
||
//{!1} Generate a new sentence when the mouse is pressed.
|
||
lsys.generate();
|
||
turtle.setToDo(lsys.getSentence());
|
||
|
||
//{!1} The length shrinks each generation.
|
||
turtle.changeLen(0.5);
|
||
}</pre>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.12</h3>
|
||
<p>Use an L-system as a set of instructions for creating objects stored in an <code>array</code>. Use trigonometry and vector math to perform the rotations instead of matrix transformations (much like I did in the Koch curve example).</p>
|
||
</div><a data-type="indexterm" data-primary="<em>Algorithmic Beauty of Plants" data-secondary="The<" data-tertiary="em> (Prusinkiewicz"></a><a data-type="indexterm" data-primary="Lindenmayer" data-secondary="Aristid"></a><a data-type="indexterm" data-primary="Prusinkiewicz" data-secondary="Przemysław"></a>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.13</h3>
|
||
<p>The seminal work in L-systems and plant structures, <em>The Algorithmic Beauty of Plants</em> by Przemysław Prusinkiewicz and Aristid Lindenmayer, was published in 1990. It is available for free in its entirety online. Chapter 1 describes many sophisticated L-systems with additional drawing rules and available alphabet characters. In addition, it describes several methods for generating stochastic L-systems. Expand the L-system example to include one or more additional features described by Prusinkiewicz and Lindenmayer.</p>
|
||
</div>
|
||
<div data-type="exercise">
|
||
<h3>Exercise 8.14</h3>
|
||
<p>In this chapter, I emphasized using fractal algorithms for generating visual patterns. However, fractals can be found in other creative mediums. For example, fractal patterns are evident in Johann Sebastian Bach’s Cello Suite no. 3. The structure of David Foster Wallace’s novel <em>Infinite Jest</em> was inspired by fractals. Consider using the examples in this chapter to generate audio or text.</p>
|
||
</div>
|
||
<div data-type="project">
|
||
<h3>The Ecosystem Project</h3>
|
||
<p>Step 8 Exercise:</p>
|
||
<p>Incorporate fractals into your ecosystem. Some possibilities:</p>
|
||
<ul>
|
||
<li>Add plant-like creatures to the ecosystem environment.</li>
|
||
<li>Let’s say one of your plants is similar to a tree. Can you add leaves or flowers to the end of the branches? What if the leaves can fall off the tree (depending on a wind force)? What if you add fruit that can be picked and eaten by the creatures?</li>
|
||
<li>Design a creature with a fractal pattern.</li>
|
||
<li>Use an L-system to generate instructions for how a creature should move or behave.</li>
|
||
</ul>
|
||
</div>
|
||
</section> |