From 624daebd2c6ecb9bf0fcce2ac56def1b5ba6e6a8 Mon Sep 17 00:00:00 2001 From: shiffman Date: Sun, 25 Feb 2024 00:33:22 +0000 Subject: [PATCH] Notion - Update docs --- content/07_ca.html | 2 +- content/08_fractals.html | 25 ++++++------------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/content/07_ca.html b/content/07_ca.html index 34175c6..f3f66ce 100644 --- a/content/07_ca.html +++ b/content/07_ca.html @@ -27,11 +27,11 @@
  • Each cell has a neighborhood. This can be defined in any number of ways, but it’s typically all the cells adjacent to that cell.
  • It’s important to stress that the cells in a CA don’t refer to biological cells (although you’ll see how CA can mimic lifelike behavior and have applications in biology). Instead, they simply represent discrete units in a grid, similar to the cells in a spreadsheet (as in Microsoft Excel). Figure 7.1 illustrates a CA and its various characteristics.

    -

    The second CA feature I listed—the idea that a cell’s state can vary over time—is an important new development. So far in this book, the objects (movers, particles, vehicles, boids, bodies) have generally existed in only one state. They might have moved with sophisticated behaviors and physics, but ultimately they remained the same type of object over the course of their digital lifetime. I’ve alluded to the possibility that these entities can change over time (for example, the weights of steering “desires” can vary), but I haven’t fully put this into practice. Now, with CA, you’ll see how an object’s state can change based on a system of rules.

    Figure 7.1: A 2D grid of cells, each with a state of on or off. A neighborhood is a subsection of the large grid, usually consisting of all the cells adjacent to a given cell (circled).
    Figure 7.1: A 2D grid of cells, each with a state of on or off. A neighborhood is a subsection of the large grid, usually consisting of all the cells adjacent to a given cell (circled).
    +

    The second CA feature I listed—the idea that a cell’s state can vary over time—is an important new development. So far in this book, the objects (movers, particles, vehicles, boids, bodies) have generally existed in only one state. They might have moved with sophisticated behaviors and physics, but ultimately they remained the same type of object over the course of their digital lifetime. I’ve alluded to the possibility that these entities can change over time (for example, the weights of steering “desires” can vary), but I haven’t fully put this into practice. Now, with CA, you’ll see how an object’s state can change based on a system of rules.

    The development of CA systems is typically attributed to Stanisław Ulam and John von Neumann, who were both researchers at the Los Alamos National Laboratory in New Mexico in the 1940s. Ulam was studying the growth of crystals, and von Neumann was imagining a world of self-replicating robots. You read that right: robots that can build copies of themselves.

    Von Neumann’s original cells had 29 possible states, so perhaps the idea of self-replicating robots is a bit too complex of a starting point. Instead, imagine a row of dominoes; each domino can be in one of two states: standing upright (1) or knocked down (0). Just as dominoes react to their neighboring dominoes, the behavior of each cell in a CA is influenced by the states of its neighboring cells.

    This chapter explores how even the most basic rules of something like dominoes can lead to a wide array of intricate patterns and behaviors, similar to natural processes like biological reproduction and evolution. Von Neumann’s work in self-replication and CA is conceptually similar to what’s probably the most famous CA, the Game of Life, which I’ll discuss in detail later in the chapter.

    diff --git a/content/08_fractals.html b/content/08_fractals.html index 34693a2..a40bc6f 100644 --- a/content/08_fractals.html +++ b/content/08_fractals.html @@ -211,7 +211,6 @@ function drawCircles(x, y, radius) {

    The Cantor rule operates by duplicating the original line and erasing its middle third section, leaving two remaining lines—one from the beginning to the one-third mark, and one from the two-thirds mark to the end of the line (see Figure 8.9). I can implement that rule manually by calling line() two more times, moving the y-position down 20 pixels so that the next generation of lines appears below the first.

    function cantor(x, y, length) {
       line(x, y, x + length, y);
    -
       //{.bold} From start to one-third
       line(x, y + 20, x + length / 3, y + 20);
       //{!1 .bold} From two-thirds to the end
    @@ -305,7 +304,6 @@ function drawCircles(x, y, radius) {
       let start = createVector(0, 200);
       // Right side of the canvas
       let end = createVector(width, 200);
    -
       //{!1} The first KochLine object
       segments.push(new KochLine(start, end));
     }
    @@ -328,7 +326,6 @@ function drawCircles(x, y, radius) { let next = []; // For every segment . . . for (let segment of segments) { - //{!4} . . . add four new lines. How do you calculate the start and end points of each? next.push(new KochLine(???, ???)); next.push(new KochLine(???, ???)); @@ -387,10 +384,8 @@ function drawCircles(x, y, radius) { let v = p5.Vector.sub(this.end, this.start); // Shorten the length to one-third. v.div(3); - //{!1} Add that vector to the beginning of the line to find the new point. let b = p5.Vector.add(a, v); - // d is just another one-third of the way past b! let d = p5.Vector.add(b, v); @@ -402,7 +397,7 @@ function drawCircles(x, y, radius) {

    The last point, c, is the most difficult one to compute. However, if you consider that the angles of an equilateral triangle are all 60 degrees, this makes your work suddenly easier. If you know how to find the new b with a vector one-third the length of the line, what if you rotate that same vector 60 degrees (or \pi/3 radians) and add it to b, as in Figure 8.16? You’d arrive at c!

    -
        //{!1} Rotate by –PI/3 radians (negative angle so it rotates “up”).
    +  
        //{!1} Rotate by –π/3 radians (negative angle so it rotates “up”).
         v.rotate(-PI / 3);    
         //{!1} Move along from b by v to get to point c.
         let c = p5.Vector.add(b, v);
    @@ -481,7 +476,7 @@ function setup() {

    Here’s the code for the process illustrated in Figure 8.18. I’m using an angle of 30 degrees, or \pi/6 radians:

    translate(0, -100);
    -//{$1} PI divided by 6 is equivalent to 30°.
    +//{$1} π divided by 6 is equivalent to 30°.
     rotate(PI / 6);
     line(0, 0, 0, -100);

    Now that I have a branch going to the right, I need one going to the left (see Figure 8.19). For that, I should have used push() to save the transformation state before rotating and drawing the right branch. Then I’ll be able to call pop() after drawing the right branch to restore that state, putting me back in the correct position to rotate and draw the left branch.

    @@ -602,7 +597,7 @@ function draw() {

    The Stochastic Version

    At first glance (and with the right angle), it may look like I’ve drawn a convincing tree in the previous example, but on closer inspection, the result is a little too perfect. Take a look outside at a real tree and you’ll notice that the branch lengths and angles vary from branch to branch, not to mention the fact that not all branches split off into exactly two smaller branches. Fractal trees are a great example of how adding a touch of randomness can make the end result look more natural. That bit of randomness also transforms the fractal from deterministic to stochastic—the exact outcome will be different from drawing to drawing, while still retaining the overall characteristics of a branching, tree-like structure.

    First, how about randomizing the angle for each branch? This is a pretty easy one to do just by adding random():

    -
      //{!1} Pick a random angle from 0 to PI/3 for each branch.
    +
      //{!1} Pick a random angle from 0 to π/3 for each branch.
       let angle = random(0, PI / 3);

    In the original example, branch() always calls itself twice. Now, for extra variety, I’ll instead pick a random number of branches (each with a random angle) for each branch.

    @@ -645,10 +640,8 @@ function draw() {

    Implementing an L-system in p5.js requires working with recursion, transformations, and strings of text. This chapter already covers recursion and transformations, but strings are new. Here’s a quick snippet of code demonstrating the three aspects of working with text important to L-systems: creating, concatenating, and iterating over strings. You can refer to the book’s website for additional string resources and tutorials.

    // A string is created as text between quotes (single or double).
     let message1 = "Hello!";
    -
     // Strings can be joined (concatenated) with the plus operator. The string is now "Hello Goodbye!"
     let message2 = message1 + " Goodbye!";
    -
     // The length of a string is stored in its length property.
     for (let i = 0; i < message.length; i++) {
       //{!1} Individual characters can be accessed by an index, just like an array! I’m using charAt(i) instead of [i].
    @@ -692,10 +685,10 @@ B → A
     

    Now it’s time to apply the production rules to current and write the results to next:

    for (let i = 0; i < current.length; i++) {
       let c = current.charAt(i);
    -  //{!1} Production rule A→AB
    +  //{!1} Production rule A → AB
       if (c === "A") {
         next += "AB";
    -  //{!1} Production rule B→A
    +  //{!1} Production rule B → A
       } else if (c === "B") {
         next += "A";
       }
    @@ -717,7 +710,6 @@ function setup() {
       createCanvas(640, 160);
       background(255);
       noLoop();
    -
       // Go through nine generations.
       for (let i = 0; i < 9; i++) {
         generate();
    @@ -733,7 +725,7 @@ function generate() {
       for (let i = 0; i < current.length; i++) {
         // For every character of the current sentence . . .
         let c = current.charAt(i);
    -    //{!5} . . . apply the production rules A→AB, B→A.
    +    //{!5} . . . apply the production rules A → AB, B → A.
         if (c === "A") {
           next += "AB";
         } else if (c === "B") {
    @@ -875,10 +867,8 @@ translate(0, length);

    Assuming I’ve generated a sentence from the L-system, I can iterate through the sentence character by character and execute the appropriate code for each character:

    for (let i = 0; i < sentence.length; i++) {
    -
       //{!1} Look at each character one at a time.
       let c = sentence.charAt(i);
    -
       //{!14} Perform the correct task for each character.
       // This could also be written with a switch statement,
       // which might be nicer to look at, but leaving it as an
    @@ -939,15 +929,12 @@ function setup() {
       let rules = {
         "F": "FF+[+F-F-F]-[-F+F+F]",
       };
    -
       // The L-system is created with an axiom and a ruleset.
       lsystem = new LSystem("F", rules);
    -
       // Run the L-system through four generations.
       for (let i = 0; i < 4; i++) {
         lsystem.generate();
       }
    -  
       //{!2 .offset} The Turtle object has a length and angle.
       turtle = new Turtle(4, radians(25));
     }