Jan 18: Repetition
States of multiple things
Suppose we wanted to extend our random wanderer sketch from last time, to have two wanderers. We could define variables x2 and y2, and duplicate and slightly modify the code to update and use them the same way. That seems kind of bogus though -- why should we have to duplicate and modify the code ourselves (and worry about maintaining the two versions), when computers should be good at that kind of thing? And what if we want 10 wanderers? Fortunately, the array programming construct (details in the Programming notes section) makes it much easier to deal with the states of multplie things.
// How many wanderers int num=5; // Their states -- positions and colors float[] x = new float[num], y = new float[num]; color[] c = new color[num]; void setup() { smooth(); noStroke(); background(0); for (int i=0; i<num; i++) { // Initialize the i-th wanderer x[i] = random(width); y[i] = random(height); c[i] = color(random(255),random(255),random(255)); } } void draw() { fill(0,3); rect(0,0,width,height); for (int i=0; i<num; i++) { // Draw the i-th wanderer fill(c[i]); ellipse(x[i],y[i],5,5); // Update the i-th wandere x[i] += random(-2,2); y[i] += random(-2,2); } }
[applet]
Here we maintain in arrays the x and y positions and color of some number (num) of wanderes. The draw function is much the same as before, except that we put the draw and update inside a loop that does that for each wanderer.
This sketch also introduces a new function color() and type color, which package up red, green, and blue values into a single color value.
Here's another example. We again keep coordinates for a specified number of particles; this time the motion isn't random at each step, but along a defined direction chosen randomly for each particle. We increment the x coordinate and y coordinate for each particle by the chosen amounts. We'll see later this term how to incorporate gravity so they fall to earth, and how to keep multiple sets of particles going simultaneously.
// How many particles int num=200; // Their locations float[] x = new float[num], y = new float[num]; // Which directions they're moving float[] dx = new float[num], dy = new float[num]; // Common color and transparency color c; float transparency; void setup() { size(400,400); smooth(); noStroke(); background(0); } void draw() { fill(0,3); rect(0,0,width,height); fill(c,transparency); for (int i=0; i<num; i++) { // Plot and step the i-th particle ellipse(x[i],y[i],5,5); x[i] += dx[i]; y[i] += dy[i]; } transparency *= 0.95; // fade away by decaying the transparency } void mousePressed() { c = color(random(255),random(255),random(255)); transparency = 255; // Initialize particles at mouse location, // going in random directions (at random speeds) for (int i=0; i<num; i++) { x[i] = mouseX; y[i] = mouseY; dx[i] = random(-2,2); dy[i] = random(-2,2); } }
[applet]
There's a cool sketch called "Amoeba Abstract" in the "Synthesis" section of the Reas & Fry code examples. Take a look at it. There's a lot of fine-tuning in the code, so we won't go through it all, but let's take a look at some of the underlying ideas.
One central idea is having things march across the screen. Here's a modification of a sketch by Reas and Fry (33-13), to do just that.
// Number of particles, their positions, and their x step int num=12; float[] x = new float[num], y = new float[num], dx = new float[num]; // How big each particle is (computed in setup) float sz; void setup() { size(100,100); smooth(); noStroke(); fill(10); rectMode(CENTER); // Divide the height equally among the walkers sz = float(height) / num; for (int i=0; i<num; i++) { x[i] = 0; // start at the left y[i] = i*sz; // equally spaced from top to bottom dx[i] = 1 + i/sz; // taking bigger steps from top to bottom } } void draw() { background(200); for (int i=0; i<num; i++) { x[i] += dx[i]; if (x[i] > width) x[i] = 0; // wrap around rect(x[i], y[i], sz, sz); } }
[applet]
Given that, it's not hard to add in some color and size variations. The following is inspired by Greenberg (11-4). What else can you come up with?
// Extended from sketch 3 so that each particle also moves // vertically, and has its own color and size int num=30; float[] x = new float[num], y = new float[num]; float[] dx = new float[num], dy = new float[num]; float[] sz = new float[num]; color[] c = new color[num]; void setup() { size(400,400); smooth(); noStroke(); rectMode(CENTER); for (int i=0; i<num; i++) { // Initialize particle x[i] = 0; y[i] = random(height); dx[i] = random(5, 10); dy[i] = random(-2, 2); sz[i] = random(2, 10); c[i] = color(random(255),random(255),random(255)); } } void draw() { for (int i=0; i<num; i++) { fill(c[i],100); rect(x[i], y[i], sz[i], sz[i]); x[i] += dx[i]; y[i] += dy[i]; if (x[i] > width) { // When wrap around horizontlaly, choose new step size // and vertical position x[i] = 0; y[i] = random(height); dx[i] = random(5, 10); dy[i] = random(-2, 2); } if (y[i] > height) y[i] = 0; } }
[applet]
How about having the direction and speed of the walkers set by the difference between the mouse position when pressed and when released?
int num=10; int curr=0; // which is next to be created float[] x = new float[num], y = new float[num]; float[] dx = new float[num], dy = new float[num]; float[] sz = new float[num]; color[] c = new color[num]; float s = 0.1; // scale the speed void setup() { size(400,400); smooth(); noStroke(); rectMode(CENTER); } void draw() { for (int i=0; i<num; i++) { fill(c[i],100); rect(x[i], y[i], sz[i], sz[i]); // Move x[i] += dx[i]; y[i] += dy[i]; // Wrap around if (x[i]<0) x[i]=width; else if (x[i]>width) x[i]=0; if (y[i]<0) y[i]=height; else if (y[i]>height) y[i]=0; } } void mousePressed() { // Start a new walker x[curr] = mouseX; y[curr] = mouseY; sz[curr] = random(2, 10); c[curr] = color(random(255),random(255),random(255)); } void mouseReleased() { // Set speed of walker according to how far dragged dx[curr] = s*(mouseX-x[curr]); dy[curr] = s*(mouseY-y[curr]); // Only have a finite number of walkers, so reuse when necessary curr = (curr+1)%num; }
[applet]
One other nice example of keeping states of multiple things is the Processing example Basics | Input | StoringInput. The idea here is that the very last (index num-1) element has the current mouse position, the one before that the previous position, and so forth. Thus for each frame, we move the position for index i to index i-1. But before we do that, we better move i-1 to i-2, etc. The loop handles that. Note that the loop starts from index 1, moving its information to 0; the info for 0 just disappears. The size of the ellipse depends on the index -- larger indices (more recent positions) have larger ellipses.
More "for"
Loops are useful on their own, without an array keeping multiple states. They allow us to do something repetitively, with some well-defined variation each repetition. For 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. We only update the variable i if we add a point; thus i++; is inside the conditional, rather than part of the for definition.
int num=100; // how many points int sz=10; // how big an aerosol boolean rand=false; // all points random colors? void setup() { size(400,400); smooth(); background(0); noFill(); stroke(255); } void draw() { if (mousePressed) { for (int i=0; i<num; ) { // Generate point float x = mouseX+random(-sz,sz); float y = mouseY+random(-sz,sz); // Is it in the circle if (dist(x,y,mouseX,mouseY) < sz) { i++; // got a point if (rand) stroke(random(255),random(255),random(255)); point(x, y); } } } } void keyPressed() { if (key=='c') { // random color rand = false; stroke(random(255),random(255),random(255)); } else if (key=='r') { // random colors rand = true; } else if (key=='Z') { // bigger size if (sz<100) sz++; } else if (key=='z') { if (sz>0) sz--; } else if (key=='P') { // more points if (num<1000) num+=10; } else if (key=='p') { if (num>10) num-=10; } }
[applet]
Here are some more examples of using loops to create various effects. Try your own variations on how to loop and what to do inside the loop.
size(300,300); // Create lines from points; try varying the spacing (R&F 6-05) for (int x=0; x<100; x+=10) { point(x, 5); } for (int x=100; x<200; x+=5) { point(x, 5); } for (int x=200; x<300; x++) { point(x, 5); } // Spacing can be geometric for (float x=1; x<300; x*=1.2) { point(x, 20); } // Add some noise (Greenberg 6-07) for (int x=0; x<100; x++) { point(x, 50+random(-5,5)); } // Spreading (Greenberg 6-08) float h=0, dh=0.2; for (int x=0; x<100; x++) { point(150+x, 50+random(-h,h)); h += dh; } // Gradient -- change color with position (R&F 6-04) for (int x=0; x<256; x+=2) { stroke(x); line(x, 100, x, 150); } // Random-colored bullseye (based on R&F 6-03) stroke(0); smooth(); for (int d=100; d > 0; d -= 20) { fill(random(255),random(255),random(255)); ellipse(150, 230, d, d); }
[applet]
Programming notes
- Array declaration and creation
- An array stores a set of variables of the same type. The array is declared by putting square brackets after the type, e.g., "float[] x;". The array is created with the new command, e.g., "x = new float[3];". The new command needs a type to be specified (kind of like a parameter, but it doesn't need to be in parentheses). If the type is an array, the size of the array must be put in square brackets after the type. The size has to be fixed ahead of time; another approach is required in order to have the size vary during the course of the sketch. If you forget to "new" the array, you'll get an error.
- Array indexing
- To access an element in an array, we put square brackets and the element index after the array name, e.g., x[3]. This can then be used exactly like any other variable. Indexing starts at 0; thus x[0] is the first element, x[1] the second, and so forth. Note that this means the largest index in an array of size num (e.g., 10) is num-1 (e.g., 9). That's why it's common to use a for loop of the form "for (int i=0; i<num; i++)" to consider each element in the array, from the zeroth up to the (num-1)st (due to the < test).
- For loop
- A for loop has the form
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.
for (initialization; continuation test; update) { statements }
Practice problems
- Modify the multiple-wanderer sketch so that different wanderers have different sizes. [hints]
- Modify the multiple-wanderer sketch so that different wanderers have different maximum step sizes. [hints]
- Have a number of "followers" (from last time) try to catch the mouse. (To keep things interesting, add a key command to re-scatter the followers.) [hints]
- Modify one of the "things marching across the screen" sketches to include both rectangles and ellipses. [hints]
- Draw lines connecting the last several mouse positions. [hints]