Programming for Interactive Digital Arts
CS 2, Fall 2023

8. Iteration

Loops

Suppose we wanted to draw a row of a set of circles from left to right across the screen. Here's a simple way to do that:

function setup(){
    createCanvas(200,200);
    ellipse(  0,100,25,25);
    ellipse( 50,100,25,25);
    ellipse(100,100,25,25);
    ellipse(150,100,25,25);
    ellipse(200,100,25,25);
}
screenshot

This approach suffers from several problems. First, to change the numner of circles, we have have to manually add and remove some. Second, it's pretty silly to type almost the same code again and again, with just a minor variation. This would become much more cumbersome if we ere to type something more complex that a circle (functions would help a bit, but some repetition would still occur).

A programming construct called a "loop" helps us out. A loop executes a particular piece of code again and again, until some condition tells it to stop. For the circles example, we could use a variable to keep track of where to put the next circle, and on each iteration, draw the circle and advance to the next position. Here's how to do that using a while loop (as usual, see the Programming notes for syntax details).

function setup(){
    createCanvas(200,200);
    var x = 0;  // initial value
    while (x <= 200) {  // keep going as long as this is true
        ellipse(x,100,25,25);  // do something for this iteration
        x = x+50;  // update for next iteration
    }
}
screenshot

If we "unroll" the loop, it's essentially doing the following:

function setup(){
    var x = 0;

    // First iteration: x = 0 <= 200 so continue
    ellipse(x,100,10,10);
    x = x+50;

    // Second iteration : x = 50 <= 200 so continue
    ellipse(x,100,10,10);
    x = x+50;

    // Third iteration : x = 100 <= 200 so continue
    ellipse(x,100,10,10);
    x = x+50;

    // Fourth iteration : x = 150 <= 200 so continue
    ellipse(x,100,10,10);
    x = x+50;

    // Fifth iteration : x = 200 <= 200 so continue
    ellipse(x,100,10,10);
    x = x+50;

    // Now x = 250 > 200 so stop
}

We see three main parts to the loop: (1) initialize variables before the start of the loop; (2) test a boolean condition each iteration, to see whether to continue; (3) perform the body of the loop as long as the condition is true. Typically the body contains some way to make progress (here, incrementing x by 50), so that the test will eventually be false. Try out variations of the loop above to get a feeling as to what happens.

There's an alternative way to express loops, called a for loop, that brings together in a single line the initialization, continuation test, and update. Here's the stepping stone sketch written that way.

function setup(){
    createCanvas(200,200);
    smooth();

    for (var x = 0; x <= 200; x = x+50) {
        ellipse(x,100,25,25);
    }
}
screenshot

Here are some other examples of using loops to create various effects. Try your own variations on how to loop and what to do inside the loop.

function setup(){
    createCanvas(375,300);
    var o = 20; // offset between the examples
    var ox = o, oy = o; // current offset
    var s = 150; // size of the rect loops

    // Sets of thin lines and circles
    noFill();
    for (var x=0; x<s; x+=2) {
        line(x+ox, oy, x+ox, oy+s/2);
    }
    oy += o+s/2; // advanced to next drawing

    // Steps -- change color with position (R&F 6-04)
    noStroke();
    for (var x = 0; x<s; x+=s/10) {
        fill(255*(x+0.5*s/10)/s);
        rect(x+ox, oy, s/10, s/2);
    }
    oy += o+s/2; // advanced to next drawing

    // Steps -- change color with position (R&F 6-04)
    noStroke();
    for (var x=0; x<s; x++) {
       stroke(255*x/s);
       line(x+ox, oy, x+ox, oy+s/2);
    }

    // Circular gradient
    noStroke();
    for(var r = s; r > 0; r -= 4) {
        fill(255*r/s);
        ellipse(2*s-25,height/2,r,r);
    }
screenshot

One last example of what we can do by varying the aspects of a loop, based on Greenberg (10-7). To draw a worm, we loop down its body, drawing an ellipse for each segment. The ellipses grow and shrink, and their centers move sinusoidally.

// inspired by Greenberg
function setup(){
    createCanvas(500, 200);
    smooth(); noFill(); background(255);
    stroke(0); strokeWeight(.2);

    var dx=2;
    var da=radians(2.55);  // converting x to angle for sine
    var radius=0, dr=0.35; // worm segment size and step
    var x=0, y=height/2;
    while (x<width) {
        ellipse(x-radius/2, y-radius/2, radius*.75, radius);
        x += dx;
        y += 0.5*sin(x*da);
        radius += dr;
        if (x==width/2) dr*=-1;   // start shrinking segments
    }
}
screenshot

Loops of loops

There is nothing that prevents us for looping inside a loop. This can be helpful to make grids of things by looping over x, and for each of those, looping over y. Here is a simple example (more in the coming lectures).

[sketch6]
function setup(){
    createCanvas(200,200);
    for (var x = 0; x <= 200; x = x+50) {
        for (var y = 0; y <= 200; y = y+50){
            ellipse(x,y,25,25);
        }
    }
}
screenshot

Interaction

All the loops so far have been in static sketches. As with other sketches involving multiple elements, all the elements produced by a loop appear simultaneously. This can be contrasted with motion sketches where, for example, we would make a ball move across the screen step by step, perhaps even at the same spacing as in the loops. In that case, the draw() body is essentially doing a loop, with each frame being one iteration, drawn at a separate tick.

We can of course use a loop inside the draw() body (and it's still the case that all elements appear at once). Shiffman 6-9 provides one example, in which vertical rectangles are drawn, with fill color set by distance from the mouse. The following variation works with circles.

[sketch7]
function setup()
{
  createCanvas(400,400);
  smooth();
  background(0);
}

function draw()
{
  // Fade away
  fill(0,5);
  noStroke();
  rect(0,0,width,height);

  // Circles surrounding mouse position
  noFill();
  for (var d=10; d<200; d+=20) {
    stroke(255-d);
    ellipse(mouseX,mouseY, d,d);
  }
}

As another example, we can draw a bunch of random points near the mouse press, for a spraypaint-like effect. Note the use of the dist() call to make sure that the points are in a circle; else the brush is a square.

[sketch8]
var num=100;         // how many points
var sz=10;           // how big an aerosol

function setup()
{
  createCanvas(400,400);
  smooth();
  background(0);
  noFill();
  stroke(255);
}

function draw()
{
  if (mouseIsPressed && (mouseX != pmouseX || mouseY != pmouseY)) {
    for (var i=0; i<num; i++) {
      // Generate point
      var x = mouseX+random(-sz,sz);
      var y = mouseY+random(-sz,sz);
      // Is it in the circle
      if (dist(x,y,mouseX,mouseY) < sz) {
        point(x, y);
      }
    }
  }
}

function keyPressed()
{
  if (key=='S') {   // bigger size
    if (sz<100) sz++;
  }
  else if (key=='s') {
    if (sz>0) sz--;
  }
  else if (key=='P') {   // more points
    if (num<1000) num+=10;
  }
  else if (key=='p') {
    if (num>10) num-=10;
  }
}

Programming notes

While loop
A while loop has the form
initialization
while (continuation test) {
  statements, typically including update
}
The initialization says what to do before the loop starts. One common initialization step is to declare and initialize a variable that will count how far through the loop we've gone, e.g., "int i=0". By convention, such loop counters are often named "i" and "j". The update is an expression that is intended to make progress through the loop, e.g., "i++". The continuation test is a boolean expression that is tested before conducting the next iteration of the loop, e.g., "i < num". If it is true, the loop body is executed (again), else it is terminated.
For loop
A for loop has the form
for (initialization; continuation test; update) {
  statements
}
The initialization says what to do before the loop starts. It can include multiple steps (separated by commas), but that can get a bit confusing to read. One common initialization step is to declare and initialize a variable that will count how far through the loop we've gone, e.g., "int i=0". By convention, such loop counters are often named "i" and "j". The update is an expression that is intended to make progress through the loop, e.g., "i++". It is performed after the statements in the body of the loop, and before the continuation test. The continuation test is a boolean expression that is tested before conducting the next iteration of the loop, e.g., "i < num". If it is true, the loop body is executed (again), else it is terminated. Any of these parts can be empty; the semicolons must still be there.
Variables declared in a loop initialization are useable only within the loop (and can likewise be reused elsewhere without conflict). Variables can be declared within the body of a loop or a conditional, and are local to that block of code. One way to think about it is as if the variables "live" only within the curly braces where they are declared.