Tutorial 16: The Process Compendium (Part 1)

Casey Reas' Process Compendium

In this tutorial we're going to explore the use of object-oriented programming by implementing the elements defined by Casey Reas in his Process Compendium. The Process Compendium is based around a series of text descriptions of processes that are defined in terms of elements, which are dynamic shapes comprised of forms with one or more behaviours. Reas' approach to generative art builds on the tradition of conceptual artists like Sol LeWitt, who's Wall Drawings consisted of a series of instructions that skilled technicians are required to follow in order to produce the artwork in a gallery. The Process Compendium contains the following library of forms, behaviours and elements.

Library

The library defines two types of forms, circles and lines. In this tutorial we will concentrate on Form 1, the circle. Despite working with the Process Compendium for 7 years (2004-2010), Casey Reas never found it necessary to add a third shape, e.g., square or triangle, because the first two forms combined with a range of behaviours are sufficient to produce a wide range of processes.

Form Description
F1 Circle
F2 Line

The library defines seven simple types of behaviours that can be combined in different ways to produce the more complex behaviours described in processes. Again, notice that this small set of simple behaviours is sufficient to define a wide range of processes.

Behaviour Description
B1 Move in a straight line
B2 Constrain to surface
B3 Change direction while touching another Element
B4 Move away from an overlapping Element
B5 Enter from the opposite edge after moving off the surface
B6 Orient toward the direction of an Element that is touching
B7 Deviate from the current direction

In Casey Reas' system, elements are the building blocks of process descriptions, they combine a form with one or more behaviours. These elements are used in processes as autonomous units.

Element Description
E1 F1 + B1 + B2 + B3 + B4
E2 F1 + B1 + B5
E3 F2 + B1 + B3 + B5
E4 F1 + B3 + B5
E5 F2 + B5 + B6 + B7

Processes

Processes are defined in terms of elements and drawing instructions based on the elements. For example, Process 4 is defined using Element 1, i.e., circles (Form 1) that move in a straight line (Behaviour 1), are constrained to the surface (Behaviour 2), change direction while touching another (Behaviour 3), and move away from over overlapping elements (Behaviour 4).

Process 4: A rectangular surface filled with varying sizes of Element 1. Draw a line from the centers of Elements that are touching. Set the value of the shortest possible line to black and the longest to white, with varying grays representing values in between.

In this tutorial, we will develop the necessary code to implement elements based on Form 1.

Form 1: Circle

To begin let's look at a simple circle class. A circle has a position (x and y) and a radius. The constructor for the Circle class takes initial values for x, y and radius and copies them into the object. The Circle class defines a single function draw(), which draws the circle at it's current location.

    class Circle {
      float x;
      float y;
      float radius;
     
      Circle(float _x, float _y, float _radius) {
        x = _x;
        y = _y;
        radius = _radius;
      }
     
      void draw() {
        pushStyle(); // Remember current drawing style
        noFill(); // Remove fill for circle
        stroke(0); // Set the stroke colour to black
        strokeWeight(1); // Set the stroke weight to 1 pixel
        pushMatrix(); // Remember current drawing transformation
        translate(x, y); // Translate to the position of the circle
        ellipseMode(RADIUS); // Set the ellipse drawing mode using radius
        ellipse(0, 0, radius, radius); // Draw the circle
        popMatrix(); // Restore previous drawing transformation
        popStyle(); // Restore previous drawing style
      }
    }

    The draw() function uses pushStyle() and popStyle() to remember and restore the drawing style for the circle. It also uses pushMatrix() and popMatrix() to remember and restore the drawing transformation for the circle. This is a useful way to remember and restore the complete drawing state in a sketch so that each object can draw itself without interfering with other objects needing to draw to the same display. We can test the circle class with a very simple sketch, like this:

    • Show Sketch
    /** @peep sketchcode */
     
    Circle circle; // A variable to hold an instance of the Circle class
     
    void setup() {
      size(300, 300);
      frameRate(10);
      smooth();
      // Construct an instance of the Circle class at the centre of the sketch
      circle = new Circle(width/2, height/2, width/4);
    }
     
    void draw() {
      background(255);
      circle.draw(); // Have the circle draw itself on screen
    }
     
    class Circle {
      float x;
      float y;
      float radius;
     
      Circle(float _x, float _y, float _radius) {
        x = _x;
        y = _y;
        radius = _radius;
      }
     
      void draw() {
        pushStyle(); // Remember current drawing style
        noFill(); // Remove fill for circle
        stroke(0); // Set the stroke colour to black
        strokeWeight(1); // Set the stroke weight to 1 pixel
        pushMatrix(); // Remember current drawing transformation
        translate(x, y); // Translate to the position of the circle
        ellipseMode(RADIUS); // Set the ellipse drawing mode using radius
        ellipse(0, 0, radius, radius); // Draw the circle
        popMatrix(); // Restore previous drawing transformation
        popStyle(); // Restore previous drawing style
      }
    }

    This code constructs a single instance of the Circle class and simply gets it to draw itself on screen. Of course, it doesn't look very exciting just yet! As we saw in the previous tutorial, we can produce many instances of a class and store them in an array, just like we can with other data types. Here is a simple example of doing that using the Circle class. It produces 100 circles and stores them in an array using a for loop. The draw() function simply has to use another for loop to go through all of the circles in the array and request that each one draw itself.

    • Show Code
    /** @peep sketch **/
     
    int NUM_CIRCLES = 100;
    float MIN_RADIUS = 10;
    float MAX_RADIUS = 20;
     
    Circle[] circles;
     
    void setup() {
      size(300, 300);
      frameRate(10);
      smooth();
      circles = new Circle[NUM_CIRCLES];
      for (int i = 0; i < circles.length; i++) {
        circles[i] = new Circle(random(width), random(height), random(MIN_RADIUS, MAX_RADIUS));
      }
    }
     
    void draw() {
      background(255);
      for (int i = 0; i < circles.length; i++) {
        circles[i].draw();
      }
    }
     
    class Circle {
      float x;
      float y;
      float radius;
     
      Circle(float _x, float _y, float _radius) {
        x = _x;
        y = _y;
        radius = _radius;
      }
     
      void draw() {
        pushStyle(); // Remember current drawing style
        noFill(); // Remove fill for circle
        stroke(0); // Set the stroke colour to black
        strokeWeight(1); // Set the stroke weight to 1 pixel
        pushMatrix(); // Remember current drawing transformation
        translate(x, y); // Translate to the position of the circle
        ellipseMode(RADIUS); // Set the ellipse drawing mode using radius
        ellipse(0, 0, radius, radius); // Draw the circle
        popMatrix(); // Restore previous drawing transformation
        popStyle(); // Restore previous drawing style
      }
    }

    Updates

    Now that we have a class to represent and draw a static form, we can build on this to support behaviours. The simplest way to do this is to add a function to the Circle class, which updates the current position and/or radius of the circle. We will call the function update() and for the moment, we will simply add a small random value to the x and y values, like this:

      class Circle {
       
        // Other circle related stuff here...
       
        void update() {
          x += random(-1, 1);
          y += random(-1, 1);
        }
      }

      To make use of this new function, we need to make sure that we call it from the main program. The cleanest way to implement this is to have an update() function in the main program as well and call this from the draw() function every time the display needs to be updated. Here's the code for the update() function:

        void update() {
          for (int i = 0; i < circles.length; i++) {
            circles[i].update();
          }
        }

        This simply uses another for loop to call update() on all of the circles. Using this approach cleanly separates the updating of circles from the drawing of them, which makes it easier to organise our code. The draw() function simply calls the update() function before it begins drawing, to ensure that all of the circles are updated once per frame:

          void draw() {
            update(); // Update the circles first
            background(255);
            for (int i = 0; i < circles.length; i++) {
              circles[i].draw();
            }
          }

          Putting this together, we produce a sketch that displays a jostling collection of circles. (Note: The circles will tend to drift off the screen over time, simply restart the sketch to see how it looks at the start.)

          • Show Code
          /** @peep sketch **/
           
          int NUM_CIRCLES = 100;
          float MIN_RADIUS = 10;
          float MAX_RADIUS = 20;
           
          Circle[] circles;
           
          void setup() {
            size(300, 300);
            frameRate(10);
            smooth();
            circles = new Circle[NUM_CIRCLES];
            for (int i = 0; i < circles.length; i++) {
              circles[i] = new Circle(random(width), random(height), random(MIN_RADIUS, MAX_RADIUS));
            }
          }
           
          void update() {
            for (int i = 0; i < circles.length; i++) {
              circles[i].update();
            }
          }
           
          void draw() {
            update();
            background(255);
            for (int i = 0; i < circles.length; i++) {
              circles[i].draw();
            }
          }
           
          class Circle {
            float x;
            float y;
            float radius;
           
            float heading;
            float speed;
           
            Circle(float _x, float _y, float _radius) {
              x = _x;
              y = _y;
              radius = _radius;
              heading = random(TWO_PI);
              speed = 1;
            }
           
            void update() {
              x += random(-1, 1);
              y += random(-1, 1);
            }
           
            void draw() {
              pushStyle();
              noFill();
              stroke(0);
              strokeWeight(1);
              ellipseMode(RADIUS);
              pushMatrix();
              translate(x, y);
              ellipse(0, 0, radius, radius);
              popMatrix();
              popStyle();
            }
          }

          Change the code above to produce a sketch where the circles also change their radius. (Hint: you only need to add one line of code to the Circle class.)

          Behaviour 1: Move in a straight line

          Now that we've got a circle that can be updated we can begin to support behaviours. Behaviour 1 calls for the ability for forms to move in a straight line. To implement this we will add two new variables (a.k.a. fields) to our Circle class to determine the direction (heading) and speed that the circle moves in. Note: in an alternative implementation we could have added variables to store the movement in x and y, e.g., dx and dy but while this is simple to implement at first it is more complex to use in the long run as should become apparent later.

          Here is the new Circle class it now contains fields that control position (x, y), size (radius) and movement (heading, speed). This code uses radians to store the heading but, if you prefer, you can use degrees in your own code - just remember to wrap the variable in a call to radians() whenever you want to pass it to a function that takes an angle as a argument, e.g., sin() and cos(). The initial value for heading is chosen at random in the constructor for the Circle class and speed is set to a default value of 1.

            class Circle {
              float x;
              float y;
              float radius;
              float heading;
              float speed;
             
              Circle(float _x, float _y, float _radius) {
                x = _x;
                y = _y;
                radius = _radius;
                heading = random(TWO_PI);
                speed = 1;
              }
             
              void update() {
                x += random(-1, 1);
                y += random(-1, 1);
              }
             
              void draw() {
                pushStyle();
                noFill();
                stroke(0);
                strokeWeight(1);
                ellipseMode(RADIUS);
                pushMatrix();
                translate(x, y);
                rotate(heading); // Rotate to the direction of the heading
                ellipse(0, 0, radius, radius);
                line(0, 0, radius, 0); // Draw a line in the direction of the heading
                popMatrix();
                popStyle();
              }
            }

            The draw() function has been altered to indicate the heading of circle with a line, to achieve this the transformation of the shape has been altered to rotate the drawing so that the x-axis points along the direction of the heading, that way a line drawn along the x-axis indicates the direction that the circle is facing. If we use this new Circle class in our previous sketch, we can see that the jostling circles all now have a randomly chosen heading indicated by a radial line.

            • Show Code
            /** @peep sketch **/
             
            int NUM_CIRCLES = 100;
            float MIN_RADIUS = 10;
            float MAX_RADIUS = 20;
             
            Circle[] circles;
             
            void setup() {
              size(300, 300);
              frameRate(10);
              smooth();
              circles = new Circle[NUM_CIRCLES];
              for (int i = 0; i < circles.length; i++) {
                circles[i] = new Circle(random(width), random(height), random(MIN_RADIUS, MAX_RADIUS));
              }
            }
             
            void update() {
              for (int i = 0; i < circles.length; i++) {
                circles[i].update();
              }
            }
             
            void draw() {
              update();
              background(255);
              for (int i = 0; i < circles.length; i++) {
                circles[i].draw();
              }
            }
             
            class Circle {
              float x;
              float y;
              float radius;
             
              float heading;
              float speed;
             
              Circle(float _x, float _y, float _radius) {
                x = _x;
                y = _y;
                radius = _radius;
                heading = random(TWO_PI);
                speed = 1;
              }
             
              void update() {
                x += random(-1, 1);
                y += random(-1, 1);
              }
             
              void draw() {
                pushStyle();
                noFill();
                stroke(0);
                strokeWeight(1);
                ellipseMode(RADIUS);
                pushMatrix();
                translate(x, y);
                rotate(heading);
                ellipse(0, 0, radius, radius);
                line(0, 0, radius, 0);
                popMatrix();
                popStyle();
              }
            }

            To make use of the new heading and speed variables we will start by changing the update() function to calculate the change in position each frame. Here's the new implementation:

              void update() {
                float dx = speed * cos(heading);
                float dy = speed * sin(heading);
                x += dx;
                y += dy;
              }

              Changing this one function makes our circles move in a straight line. Note: the circles will move off the display quite quickly now, so you may have to restart the sketch to see anything.

              • Show Code
              /** @peep sketch **/
               
              int NUM_CIRCLES = 100;
              float MIN_RADIUS = 10;
              float MAX_RADIUS = 20;
               
              Circle[] circles;
               
              void setup() {
                size(300, 300);
                frameRate(10);
                smooth();
                circles = new Circle[NUM_CIRCLES];
                for (int i = 0; i < circles.length; i++) {
                  circles[i] = new Circle(random(width), random(height), random(MIN_RADIUS, MAX_RADIUS));
                }
              }
               
              void update() {
                for (int i = 0; i < circles.length; i++) {
                  circles[i].update();
                }
              }
               
              void draw() {
                update();
                background(255);
                for (int i = 0; i < circles.length; i++) {
                  circles[i].draw();
                }
              }
               
              class Circle {
                float x;
                float y;
                float radius;
               
                float heading;
                float speed;
               
                Circle(float _x, float _y, float _radius) {
                  x = _x;
                  y = _y;
                  radius = _radius;
                  heading = random(TWO_PI);
                  speed = 1;
                }
               
                void update() {
                  float dx = speed * cos(heading);
                  float dy = speed * sin(heading);
                  x += dx;
                  y += dy;
                }
               
                void draw() {
                  pushStyle();
                  noFill();
                  stroke(0);
                  strokeWeight(1);
                  ellipseMode(RADIUS);
                  pushMatrix();
                  translate(x, y);
                  rotate(heading);
                  ellipse(0, 0, radius, radius);
                  line(0, 0, radius, 0);
                  popMatrix();
                  popStyle();
                }
              }

              This version of the update() function implements Behaviour 1, i.e., the circles move in a straight line. We could keep all of the code to implement other behaviours in the update() function but this might become confusing as we add more behaviours. Instead, we're going to "refactor" our code to produce a new function that implements just Behaviour 1 and call it from update().

                void update() {
                  behaviour1();
                }
                 
                void behaviour1() {
                  // Constant linear motion
                  float dx = speed * cos(heading);
                  float dy = speed * sin(heading);
                  x += dx;
                  y += dy;
                }

                These types of changes to code is called "refactoring", it is a technique where the code is changed to be clearer to read, or easier to maintain without changing its functionality, i.e., the updated code does exactly the same thing as the original code. In this case, we have made the update() function "self-documenting", i.e., through the use of an appropriate function name we have described what the update() function does in the call to behaviour1(). We have also made it easier to maintain the Circle class by making it clear that any time we want to add a new behaviour, we simply write a new function and call it from update(). This makes it easier for other people to understand how the class works and how they might extend it. Here is a sketch that uses this refactored code, as you can see it does exactly the same as before:

                • Show Code
                /** @peep sketch **/
                int NUM_CIRCLES = 100;
                float MIN_RADIUS = 10;
                float MAX_RADIUS = 20;
                 
                Circle[] circles;
                 
                void setup() {
                  size(300, 300);
                  frameRate(10);
                  smooth();
                  circles = new Circle[NUM_CIRCLES];
                  for (int i = 0; i < circles.length; i++) {
                    circles[i] = new Circle(random(width), random(height), random(MIN_RADIUS, MAX_RADIUS));
                  }
                }
                 
                void update() {
                  for (int i = 0; i < circles.length; i++) {
                    circles[i].update();
                  }
                }
                 
                void draw() {
                  update();
                  background(255);
                  for (int i = 0; i < circles.length; i++) {
                    circles[i].draw();
                  }
                }
                 
                class Circle {
                  float x;
                  float y;
                  float radius;
                 
                  float heading;
                  float speed;
                 
                  Circle(float _x, float _y, float _radius) {
                    x = _x;
                    y = _y;
                    radius = _radius;
                    heading = random(TWO_PI);
                    speed = 1;
                  }
                 
                  void draw() {
                    pushStyle();
                    noFill();
                    stroke(0);
                    strokeWeight(1);
                    ellipseMode(RADIUS);
                    pushMatrix();
                    translate(x, y);
                    rotate(heading);
                    ellipse(0, 0, radius, radius);
                    line(0, 0, radius, 0);
                    popMatrix();
                    popStyle();
                  }
                 
                  void update() {
                    behaviour1();
                  }
                 
                  void behaviour1() {
                    // Constant linear motion
                    float dx = speed * cos(heading);
                    float dy = speed * sin(heading);
                    x += dx;
                    y += dy;
                  }
                }

                Change the initial value of speed assigned to each circle, or use a call to random() to produce circles that travel at different speeds. Notice that the description of Behaviour 1 in the Process Compendium library does not specify what speed forms should move at, or whether different forms should move at different speeds.

                Behaviour 2: Constrain to surface

                The second behaviour in the Process Compendium library is to constrain an element to a surface. If we interpret this to mean that forms should not leave the display window, this can be simply achieved by adding a new method to our Circle class that checks the values of x and y against the bounds of the display window, like this:

                  void behaviour2() {
                    // Constrain to surface
                    if (x < 0) x = 0;
                    if (y < 0) y = 0;
                    if (x > width) x = width;
                    if (y > height) y = height;
                  }

                  Having added the method to our Circle class we can use it by calling it in the update() method.

                    void update() {
                      behaviour1();
                      behaviour2();
                    }

                    The effect of these changes on our sketch is that now the circles are stopped from moving in a direction when their centre touches the edge of the display window:

                    • Show Code
                    /** @peep sketch **/
                    int NUM_CIRCLES = 100;
                    float MIN_RADIUS = 10;
                    float MAX_RADIUS = 20;
                     
                    Circle[] circles;
                     
                    void setup() {
                      size(300, 300);
                      frameRate(10);
                      smooth();
                      circles = new Circle[NUM_CIRCLES];
                      for (int i = 0; i < circles.length; i++) {
                        circles[i] = new Circle(random(width), random(height), random(MIN_RADIUS, MAX_RADIUS));
                      }
                    }
                     
                    void update() {
                      for (int i = 0; i < circles.length; i++) {
                        circles[i].update();
                      }
                    }
                     
                    void draw() {
                      update();
                      background(255);
                      for (int i = 0; i < circles.length; i++) {
                        circles[i].draw();
                      }
                    }
                     
                    class Circle {
                      float x;
                      float y;
                      float radius;
                     
                      float heading;
                      float speed;
                     
                      Circle(float _x, float _y, float _radius) {
                        x = _x;
                        y = _y;
                        radius = _radius;
                        heading = random(TWO_PI);
                        speed = 1;
                      }
                     
                      void draw() {
                        pushStyle();
                        noFill();
                        stroke(0);
                        strokeWeight(1);
                        ellipseMode(RADIUS);
                        pushMatrix();
                        translate(x, y);
                        rotate(heading);
                        ellipse(0, 0, radius, radius);
                        line(0, 0, radius, 0);
                        popMatrix();
                        popStyle();
                      }
                     
                      void update() {
                        behaviour1();
                        behaviour2();
                      }
                     
                      void behaviour1() {
                        // Constant linear motion
                        float dx = speed * cos(heading);
                        float dy = speed * sin(heading);
                        x += dx;
                        y += dy;
                      }
                     
                      void behaviour2() {
                        // Constrain to surface
                        if (x < 0) x = 0;
                        if (y < 0) y = 0;
                        if (x > width) x = width;
                        if (y > height) y = height;
                      }
                    }

                    We may decide that this is not what Casey Reas meant in the description of Behaviour 2, and that a form should be stopped such that no part of it may be drawn outside of the surface. We can do this simply for circles by changing the tests to take into consideration the radius of the circle:

                      void behaviour2() {
                        // Constrain to surface
                        if (x < radius) x = radius;
                        if (y < radius) y = radius;
                        if (x > width - radius) x = width - radius;
                        if (y > height - radius) y = height - radius;
                      }

                      The effect of this is that the circles appear to be blocked by the edges of the display window:

                      • Show Code
                      /** @peep sketch **/
                      int NUM_CIRCLES = 100;
                      float MIN_RADIUS = 10;
                      float MAX_RADIUS = 20;
                       
                      Circle[] circles;
                       
                      void setup() {
                        size(300, 300);
                        frameRate(10);
                        smooth();
                        circles = new Circle[NUM_CIRCLES];
                        for (int i = 0; i < circles.length; i++) {
                          circles[i] = new Circle(random(width), random(height), random(MIN_RADIUS, MAX_RADIUS));
                        }
                      }
                       
                      void update() {
                        for (int i = 0; i < circles.length; i++) {
                          circles[i].update();
                        }
                      }
                       
                      void draw() {
                        update();
                        background(255);
                        for (int i = 0; i < circles.length; i++) {
                          circles[i].draw();
                        }
                      }
                       
                      class Circle {
                        float x;
                        float y;
                        float radius;
                       
                        float heading;
                        float speed;
                       
                        Circle(float _x, float _y, float _radius) {
                          x = _x;
                          y = _y;
                          radius = _radius;
                          heading = random(TWO_PI);
                          speed = 1;
                        }
                       
                        void draw() {
                          pushStyle();
                          noFill();
                          stroke(0);
                          strokeWeight(1);
                          ellipseMode(RADIUS);
                          pushMatrix();
                          translate(x, y);
                          rotate(heading);
                          ellipse(0, 0, radius, radius);
                          line(0, 0, radius, 0);
                          popMatrix();
                          popStyle();
                        }
                       
                        void update() {
                          behaviour1();
                          behaviour2();
                        }
                       
                        void behaviour1() {
                          // Constant linear motion
                          float dx = speed * cos(heading);
                          float dy = speed * sin(heading);
                          x += dx;
                          y += dy;
                        }
                       
                        void behaviour2() {
                          // Constrain to surface
                          if (x < radius) x = radius;
                          if (y < radius) y = radius;
                          if (x > width - radius) x = width - radius;
                          if (y > height - radius) y = height - radius;
                        }
                      }

                      Investigate the constrain() function in the Processing reference. Can you rewrite the function behaviour2() in a more concise way using this function? Is the resulting code easier to understand?

                      Regardless of how we implement Behaviour 2, the forms to which it is applied will get stuck in the corners of the surface. We won't attempt to change the way that Behaviour 2 operates to avoid this but as we will see, other behaviours when combined with this one will take care of moving elements out of the corners again.

                      Behaviour 3: While touching another, change direction

                      Behaviour 3 states that while an element is touching another it should change direction. The specifics of how an element should change its direction aren't given in the Process Compendium but for the moment we will assume that a small amount is added to the heading. We can sketch out the implementation of Behaviour 3 as follows:

                        void behaviour3() {
                          // Check all of the circles in the sketch
                          for (int i = 0; i < circles.length; i++) {
                            // If the circle is not this one
                            if (circles[i] != this) {
                              // If touching the other circle
                              if (touching(circles[i])) {
                                // Change direction
                                heading += random(-DELTA_ANGLE, DELTA_ANGLE);
                              }
                            }
                          }
                        }

                        We can't run this code yet, as there are a few things missing from our class, in some ways this is somewhere between an algorithm and code as we usually write it. This approach to developing code can be very helpful in thinking through what we need in order to be able to implement the behaviour. To complete the implementation we can see that we will need to define a constant called DELTA_ANGLE that will control how much the heading changes when this behaviour is applied:

                          float DELTA_ANGLE = TWO_PI/36;

                          We can also see that we need to define a method that will test whether a circle is touching another, called touching() this method will need to take another circle as a parameter and return a Boolean value. Here's an outline of what that function might look like:

                            void touching(Circle other) {
                              return (distance(other) < radius + other.radius);
                            }

                            As you can see, the touching() function also calls for the creation of another function that calculates the distance between (the centres of) two circles. Here's an implementation of that function:

                              float distance(Circle other) {
                                return dist(x, y, other.x, other.y);
                              }

                              If we implement all of these in the Circle class we can simply change the update() function to use the new behaviour, as such:

                                void update() {
                                  behaviour1();
                                  behaviour2();
                                  behaviour3();
                                }

                                To help us understand what is going on in our sketch, we can also add a little more to the draw() method in the Circle class to draw a red line between the centres of circles that are touching:

                                  void draw() {
                                    pushStyle();
                                   
                                    // ... CODE TO DRAW CIRCLE HERE ...
                                   
                                    stroke(192, 0, 0, 64);
                                    for (int i = 0; i < circles.length; i++) {
                                      Circle other = circles[i];
                                      if (touching(other)) {
                                        line(x, y, other.x, other.y);
                                      }
                                    }
                                    popStyle();
                                  }

                                  The result of applying these changes is the following process:

                                  • Show Code
                                  /** @peep sketch **/
                                  int NUM_CIRCLES = 100;
                                  float MIN_RADIUS = 10;
                                  float MAX_RADIUS = 20;
                                   
                                  float DELTA_ANGLE = TWO_PI/36;
                                   
                                  Circle[] circles;
                                   
                                  void setup() {
                                    size(300, 300);
                                    frameRate(10);
                                    smooth();
                                    circles = new Circle[NUM_CIRCLES];
                                    for (int i = 0; i < circles.length; i++) {
                                      circles[i] = new Circle(random(width), random(height), random(MIN_RADIUS, MAX_RADIUS));
                                    }
                                  }
                                   
                                  void update() {
                                    for (int i = 0; i < circles.length; i++) {
                                      circles[i].update();
                                    }
                                  }
                                   
                                  void draw() {
                                    update();
                                    background(255);
                                    for (int i = 0; i < circles.length; i++) {
                                      circles[i].draw();
                                    }
                                  }
                                   
                                  class Circle {
                                    float x;
                                    float y;
                                    float radius;
                                   
                                    float heading;
                                    float speed;
                                   
                                    Circle(float _x, float _y, float _radius) {
                                      x = _x;
                                      y = _y;
                                      radius = _radius;
                                      heading = random(TWO_PI);
                                      speed = 1;
                                    }
                                   
                                    void draw() {
                                      pushStyle();
                                      noFill();
                                      stroke(0);
                                      strokeWeight(1);
                                      ellipseMode(RADIUS);
                                      pushMatrix();
                                      translate(x, y);
                                      rotate(heading);
                                      ellipse(0, 0, radius, radius);
                                      line(0, 0, radius, 0);
                                      popMatrix();
                                      stroke(192, 0, 0, 64);
                                      for (int i = 0; i < circles.length; i++) {
                                        Circle other = circles[i];
                                        if (touching(other)) {
                                          line(x, y, other.x, other.y);
                                        }
                                      }
                                      popStyle();
                                    }
                                   
                                    void update() {
                                      behaviour1();
                                      behaviour2();
                                      behaviour3();
                                    }
                                   
                                    void behaviour1() {
                                      // Constant linear motion
                                      float dx = speed * cos(heading);
                                      float dy = speed * sin(heading);
                                      x += dx;
                                      y += dy;
                                    }
                                   
                                    void behaviour2() {
                                      // Constrain to surface
                                      if (x < radius) x = radius;
                                      if (y < radius) y = radius;
                                      if (x > width - radius) x = width - radius;
                                      if (y > height - radius) y = height - radius;
                                    }
                                   
                                    void behaviour3() {
                                      // While touching another, change direction
                                      for (int i = 0; i < circles.length; i++) {
                                        if (circles[i] != this) {
                                          if (touching(circles[i])) {
                                            heading += random(-DELTA_ANGLE, DELTA_ANGLE);
                                          }
                                        }
                                      }
                                    }
                                   
                                    void touching(Circle other) {
                                      return (distance(other) < radius + other.radius);
                                    }
                                   
                                    float distance(Circle other) {
                                      return dist(x, y, other.x, other.y);
                                    }
                                  }

                                  Behaviour 4: While touching another, move away from its centre

                                  We have almost completed all of the behaviours required to implement Process 4, the last behaviour is Behaviour 4, which states that an element should move away from another element that it is touching. Again, we will start by sketching out the method:

                                    void behaviour4() {
                                      // While touching another, move away from its centre
                                      for (int i = 0; i < circles; i++) {
                                        // If the current circle is not this one
                                        if (circles[i] != this) {
                                          // If the current circle is touching this one
                                          if (touching(circles[i])) {
                                            Circle other = circles[i];
                                            // Calculate the distance to the other circle
                                            float d = distance(other);
                                            // Calculate the direction in x and y to the other circle
                                            float dx = (other.x - x)/d;
                                            float dy = (other.y - y)/d;
                                            // Move this circle in the opposite direction using the current speed
                                            x -= speed * dx;
                                            y -= speed * dy;
                                          }
                                        }
                                      }
                                    }

                                    Because this new method uses the same distance() and touching() methods as behaviour3() there is nothing more to implement for this behaviour to work, we just need to change the update() method to call behaviour4():

                                    • Show Sketch
                                    /** @peep sketchcode **/
                                    int NUM_CIRCLES = 100;
                                    float MIN_RADIUS = 10;
                                    float MAX_RADIUS = 20;
                                     
                                    float DELTA_ANGLE = TWO_PI/36;
                                     
                                    Circle[] circles;
                                     
                                    void setup() {
                                      size(300, 300);
                                      frameRate(10);
                                      smooth();
                                      circles = new Circle[NUM_CIRCLES];
                                      for (int i = 0; i < circles.length; i++) {
                                        circles[i] = new Circle(random(width), random(height), random(MIN_RADIUS, MAX_RADIUS));
                                      }
                                    }
                                     
                                    void update() {
                                      for (int i = 0; i < circles.length; i++) {
                                        circles[i].update();
                                      }
                                    }
                                     
                                    void draw() {
                                      update();
                                      background(255);
                                      for (int i = 0; i < circles.length; i++) {
                                        circles[i].draw();
                                      }
                                    }
                                     
                                    class Circle {
                                      float x;
                                      float y;
                                      float radius;
                                     
                                      float heading;
                                      float speed;
                                     
                                      Circle(float _x, float _y, float _radius) {
                                        x = _x;
                                        y = _y;
                                        radius = _radius;
                                        heading = random(TWO_PI);
                                        speed = 1;
                                      }
                                     
                                      void draw() {
                                        pushStyle();
                                        noFill();
                                        stroke(0);
                                        strokeWeight(1);
                                        ellipseMode(RADIUS);
                                        pushMatrix();
                                        translate(x, y);
                                        rotate(heading);
                                        ellipse(0, 0, radius, radius);
                                        line(0, 0, radius, 0);
                                        popMatrix();
                                        stroke(192, 0, 0, 64);
                                        for (int i = 0; i < circles.length; i++) {
                                          Circle other = circles[i];
                                          if (touching(other)) {
                                            line(x, y, other.x, other.y);
                                          }
                                        }
                                        popStyle();
                                      }
                                     
                                      void update() {
                                        behaviour1();
                                        behaviour2();
                                        behaviour3();
                                        behaviour4();
                                      }
                                     
                                      void behaviour1() {
                                        // Constant linear motion
                                        float dx = speed * cos(heading);
                                        float dy = speed * sin(heading);
                                        x += dx;
                                        y += dy;
                                      }
                                     
                                      void behaviour2() {
                                        // Constrain to surface
                                        if (x < radius) x = radius;
                                        if (y < radius) y = radius;
                                        if (x > width - radius) x = width - radius;
                                        if (y > height - radius) y = height - radius;
                                      }
                                     
                                      void behaviour3() {
                                        // While touching another, change direction
                                        for (int i = 0; i < circles.length; i++) {
                                          if (circles[i] != this) {
                                            if (touching(circles[i])) {
                                              heading += random(-DELTA_ANGLE, DELTA_ANGLE);
                                            }
                                          }
                                        }
                                      }
                                     
                                      void behaviour4() {
                                        // While touching another, move away from its centre
                                        for (int i = 0; i < circles; i++) {
                                          if (circles[i] != this) {
                                            if (touching(circles[i])) {
                                              Circle other = circles[i];
                                              float d = distance(other);
                                              float dx = (other.x - x)/d;
                                              float dy = (other.y - y)/d;
                                              x -= speed * dx;
                                              y -= speed * dy;
                                            }
                                          }
                                        }
                                      }
                                     
                                      void touching(Circle other) {
                                        return (distance(other) < radius + other.radius);
                                      }
                                     
                                      float distance(Circle other) {
                                        return dist(x, y, other.x, other.y);
                                      }
                                    }

                                    Process 4

                                    This completes the implementation of behaviours necessary to implement Process 4. Going back to the description of Process 4 from the Process Compendium we can see that we need to implement a different method of drawing:

                                    Process 4: A rectangular surface filled with varying sizes of Element 1. Draw a line from the centers of Elements that are touching. Set the value of the shortest possible line to black and the longest to white, with varying grays representing values in between.

                                    Rather than change the way that the Circle class draws itself, we will change the draw() function of the sketch, to use the distance between circles to determine how to draw lines between the centres of the circles:

                                      void draw() {
                                        update(); // Update all of the circles
                                        strokeWeight(0.5);
                                        for (int i = 0; i < circles.length; i++) {
                                          // Get a first circle
                                          Circle circle1 = circles[i];
                                          for (int j = i+1; j < circles.length; j++) {
                                            // Get a second circle
                                            Circle circle2 = circles[j];
                                            // If the circles are touching
                                            if (circle1.touching(circle2)) {
                                              // Calculate the grey value using the map function based on the distance between the circles
                                              stroke(map(circle1.distance(circle2), 0, circle1.radius + circle2.radius, 0, 255));
                                              // Draw a line between the centres of the circles
                                              line(circle1.x, circle1.y, circle2.x, circle2.y);
                                          }
                                        }
                                      }

                                      Here's the complete implementation for Process 4:

                                      • Show Sketch
                                      /** @peep sketchcode **/
                                      int NUM_CIRCLES = 200;
                                      float MIN_RADIUS = 10;
                                      float MAX_RADIUS = 20;
                                       
                                      float DELTA_ANGLE = TWO_PI/36;
                                       
                                      Circle[] circles;
                                       
                                      void setup() {
                                        size(300, 300);
                                        frameRate(10);
                                        background(255);
                                        smooth();
                                        circles = new Circle[NUM_CIRCLES];
                                        for (int i = 0; i < circles.length; i++) {
                                          circles[i] = new Circle(random(width), random(height), random(MIN_RADIUS, MAX_RADIUS));
                                        }
                                      }
                                       
                                      void update() {
                                        for (int i = 0; i < circles.length; i++) {
                                          circles[i].update();
                                        }
                                      }
                                       
                                      void draw() {
                                        update(); // Update all of the circles
                                        strokeWeight(0.5);
                                        for (int i = 0; i < circles.length; i++) {
                                          // Get a first circle
                                          Circle circle1 = circles[i];
                                          for (int j = i+1; j < circles.length; j++) {
                                            // Get a second circle
                                            Circle circle2 = circles[j];
                                            // If the circles are touching
                                            if (circle1.touching(circle2)) {
                                              // Calculate the grey value using the map function based on the distance between the circles
                                              stroke(map(circle1.distance(circle2), 0, circle1.radius + circle2.radius, 0, 255));
                                              // Draw a line between the centres of the circles
                                              line(circle1.x, circle1.y, circle2.x, circle2.y);
                                            }
                                          }
                                        }
                                      }
                                       
                                      class Circle {
                                        float x;
                                        float y;
                                        float radius;
                                       
                                        float heading;
                                        float speed;
                                       
                                        Circle(float _x, float _y, float _radius) {
                                          x = _x;
                                          y = _y;
                                          radius = _radius;
                                          heading = random(TWO_PI);
                                          speed = 1;
                                        }
                                       
                                        void draw() {
                                          pushStyle();
                                          noFill();
                                          stroke(0);
                                          strokeWeight(1);
                                          ellipseMode(RADIUS);
                                          pushMatrix();
                                          translate(x, y);
                                          rotate(heading);
                                          ellipse(0, 0, radius, radius);
                                          line(0, 0, radius, 0);
                                          popMatrix();
                                          stroke(192, 0, 0, 64);
                                          for (int i = 0; i < circles.length; i++) {
                                            Circle other = circles[i];
                                            if (touching(other)) {
                                              line(x, y, other.x, other.y);
                                            }
                                          }
                                          popStyle();
                                        }
                                       
                                        void update() {
                                          behaviour1();
                                          behaviour2();
                                          behaviour3();
                                          behaviour4();
                                        }
                                       
                                        void behaviour1() {
                                          // Constant linear motion
                                          float dx = speed * cos(heading);
                                          float dy = speed * sin(heading);
                                          x += dx;
                                          y += dy;
                                        }
                                       
                                        void behaviour2() {
                                          // Constrain to surface
                                          if (x < radius) x = radius;
                                          if (y < radius) y = radius;
                                          if (x > width - radius) x = width - radius;
                                          if (y > height - radius) y = height - radius;
                                        }
                                       
                                        void behaviour3() {
                                          // While touching another, change direction
                                          for (int i = 0; i < circles.length; i++) {
                                            if (circles[i] != this) {
                                              if (touching(circles[i])) {
                                                heading += random(-DELTA_ANGLE, DELTA_ANGLE);
                                              }
                                            }
                                          }
                                        }
                                       
                                        void behaviour4() {
                                          // While touching another, move away from its centre
                                          for (int i = 0; i < circles; i++) {
                                            if (circles[i] != this) {
                                              if (touching(circles[i])) {
                                                Circle other = circles[i];
                                                float d = distance(other);
                                                float dx = (other.x - x)/d;
                                                float dy = (other.y - y)/d;
                                                x -= speed * dx;
                                                y -= speed * dy;
                                              }
                                            }
                                          }
                                        }
                                       
                                        void touching(Circle other) {
                                          return (distance(other) < radius + other.radius);
                                        }
                                       
                                        float distance(Circle other) {
                                          return dist(x, y, other.x, other.y);
                                        }
                                      }

                                      Exercises

                                      1. Experiment with the implementation of Process 4 to see what difference the number of circles and the ranges of speeds and sizes make to the behaviour of the process as a whole.
                                      2. Using the implementation of Process 4 as a starting point, change the draw() function for the sketch to implement Process 5, which described in the Process Compendium as:
                                      Process 5
                                      A rectangular surface filled with varying sizes of Element 1. Draw the perimeter of each Element as a black line and the center as a white dot. Draw a gray line from the centers of Elements that are touching.

                                      Comments

                                      Nobody has said anything yet.