Jan 16: More of the above
Drawing
Rectangles and ellipses are great, and by stacking them just right we can create things like snowpeople, but sometimes we want even fancier shapes. Processing provides a way to define a new shape by a set of vertices defining points on its boundary
strokeWeight(5); fill(150,150,255); beginShape(); vertex(0,0); vertex(100,0); vertex(100,20); vertex(20,20); vertex(20,80); vertex(100,80); vertex(100,100); vertex(0,100); endShape(CLOSE);
[applet]
The vertices of a shape are specified via the vertex() function, which takes x and y coordinates. Before calling that function, we must tell Processing that we're starting a new shape, by calling the beginShape() function; when we've listed all the vertices, we must call the endShape() function. Parameters may be given to beginShape() to produce different forms -- individual points or lines, sets of individual triangles or quads, or tesselations. I'll let you play with those variations on your own. There is an optional parameter for endShape(); if it is given the value CLOSE, the last vertex is connected back to the first, while otherwise the shape is left "open".
We can specify colors for the shape by calling stroke() and fill() before beginShape().
The segments of a shape can be curved rather than straight, by using curveVertex() instead of vertex(). Processing figures out how to draw good curves between vertices, using what are called Catmull-Rom splines. The first and last vertices aren't drawn as part of the curve, but are control points used to help set the curvature at the neighboring vertices. The overall amount of curvature can be set with the curveTightness() function, with increasing positive numbers bulging more and more, and increasing negative numbers bowing more and more.
Bezier curves give more control over the curvature (you might have used them in a drawing program). In addition to the points on the curve (anchor points), Bezier curves also have associated control points that set the slope at the anchors. To specify a Bezier curve, we give the first vertex position with the regular vertex() function. Then we call the bezierVertex() function with the positions of two control points and an anchor point. The first control point (first two parameters) is for the preceding vertex and the second is for this one.
The following two examples let you play with these curve shapes -- click to place points; select & drag to move them. We won't go over how the sketch works for now. To see the Processing commands to draw the resulting curve, you'll have to download the zip file (curve; bezier) and load it into Processing, rather than just running the applet. Once you've set the points, press the "p" key.
[applet]
[applet]
Interaction
We can use the two mouse positions to control the drawing in a less direct fashion. The following sketch, inspired by the Processing example Topics | Drawing | Pattern sets the color and the size of the ellipse according to how far the mouse has moved. The red component is the lesser (min()) of 255 and the absolute difference (abs()) between the x coordinates, times 10. The min is because the value might possibly be more than 255 (the maximum allowed). The absolute is because we only want how far, not left vs. right. The times 10 is because typically the mouse doesn't move that far between frames, and we want the color to be bright enough. Similar calculations gives the red component, and the size of the ellipse.
void setup() { size(400,400); smooth(); noStroke(); frameRate(30); background(0); } void draw() { fill(0,2); rect(0,0,width,height); // Set color from how far mouse has moved fill(min(255,10*abs(pmouseX-mouseX)), 0, min(255,10*abs(pmouseY-mouseY))); // Set size from how far mouse has moved ellipse(mouseX,mouseY,abs(pmouseX-mouseX),abs(pmouseY-mouseY)); }
[applet]
We saw how to test if the mouse is in a rectangle. How about in a circle? We use the dist() function, which computes the distance between two pairs of coordinates. (Remember from math that a circle is defined as the set of points a given distance from its center.) This lets us make our snowperson a little interactive -- press the mouse on the head or torso.
void setup() { size(300,200); smooth(); // Note that we only need to load the font once, so we do it in the setup() textFont(loadFont("TrebuchetMS-24.vlw")); frameRate(8); } void draw() { background(128); // Invariant stuff ellipse(130,40,40,40); // head ellipse(130,90,70,70); // torso ellipse(130,160,90,90); // legs? strokeWeight(5); point(125,35); // left eye point(135,35); // right eye strokeWeight(1); triangle(130,40,140,45,130,45); // nose line(100,90,60,50); // right arm line(60,50,65,40); // right hand line(60,50,57,35); line(60,50,50,48); line(158,76,200,53); // left arm // Stuff that depends on clicks if (mousePressed && dist(mouseX,mouseY,130,40) < 20) { // Top hat in hand rect(40,30,40,5); rect(50,10,20,20); text("Hi there!",155,25); } else { // Top hat on head rect(110,20,40,5); rect(120,0,20,20); } if (mousePressed && dist(mouseX,mouseY,130,90) < 35) { // Wiggle left hand line(200,53,200+random(-5,5),42+random(-5,5)); line(200,53,210+random(-5,5),65+random(-5,5)); } else { // Regular left hand line(200,53,200,42); line(200,53,210,65); } }
[applet]
By testing mousePressed in the draw function, rather than having a separate mousePressed() function, the sketch only changes while the mouse button is pressed. This again illustrates the difference between the two approaches to dealing with mouse presses. If we want to do something exactly once, we should use an event. If we were to test in the draw function, then if the frame rate were too fast, we might respond multiple times to a single click (because the mouse is still held down); if it were too slow, we might miss a click (because it wasn't held down during the lifespan of the draw call). Try it yourself, with a sketch that changes the background when the mouse is pressed.
State + conditional
By combining keys with state, we can let whoever is playing with our sketch control different aspects of it. Note the different types of the variables.
float r=255,g=255,b=255; // drawing color int dc=5; // amount of color increment/decrement float sz=20; // ellipse size boolean fade=true; // transparent rectangle each time? void setup() { size(400,400); smooth(); noStroke(); frameRate(30); background(0); println("1:white; 0:black; c:random color"); println("r/R, g/G, b/B: less/more red, green, blue"); println("z/Z: smaller/bigger"); println("f/F: no fade / fade"); } void draw() { if (fade) { fill(0,5); rect(0,0,width,height); } if (mousePressed) { fill(r,g,b); ellipse(mouseX,mouseY,sz,sz); } } void keyPressed() { if (key=='1') { // white r = 255; g = 255; b = 255; println("white"); } else if (key=='0') { // black r = 0; g = 0; b = 0; println("black"); } else if (key=='c') { // random color r = random(255); g = random(255); b = random(255); println("color:"+r+","+g+","+b); } else if (key=='r') { // less red if (r > dc) r-=dc; println("red:"+r); } else if (key=='R') { // more red if (r < 255-dc) r+=dc; println("red:"+r); } else if (key=='g') { if (g > dc) g-=dc; println("green:"+g); } else if (key=='G') { if (g < 255-dc) g+=dc; println("green:"+g); } else if (key=='b') { if (b > dc) b-=dc; println("blue:"+b); } else if (key=='B') { if (b < 255-dc) b+=dc; println("blue:"+b); } else if (key=='z') { // smaller if (sz > 1) sz--; println("size:"+sz); } else if (key=='Z') { // bigger if (sz < 100) sz++; println("size:"+sz); } else if (key=='f') { // no fade fade = false; println("no fade"); } else if (key=='F') { // fade fade = true; println("fade"); } }
[applet]
This sketch produces some console output (at the bottom of the window in which you type your sketch) to describe the current state. This can be very useful in helping understand what's going on with a sketch when we're developing it. I also used it in the shape sketches above to print out instructions and the Processing commands. The print() and println() functions let us display something on the console; the latter forces a new line after printing. To print text, put it in double quotes. To print multiple things, combine them with a plus sign. Calling println() with no parameter simply forces the new line.
We can use state and conditionals to create a simple game. Here players 1 and 2 hit their keys to grab chips from the mouth; at some random point the mouth will close, and the player who tried to grab then will be penalized.
int num; // how many presses boolean mouthOpen=true; int chips1=0, chips2=0; // score of the game // Parameters for the game: how many times before it snaps for sure, // how many chips are given or taken away int maxPresses=10, bonus=1, penalty=2; void setup() { background(0); smooth(); textFont(loadFont("LucidaConsole-24.vlw")); num = round(random(maxPresses)); } void draw() { background(0); // The mouth noStroke(); fill(255,255,0); if (mouthOpen) { arc(50, 50, 50, 50, PI/4, TWO_PI-PI/4); } else { ellipse(50, 50, 50, 50); stroke(0); line(50,50, 75,50); } // The score fill(255,0,0); text(chips1,10,25); fill(0,0,255); text(chips2,70,25); } void keyPressed() { if (key=='0') { // reset mouthOpen = true; num = round(random(maxPresses)); } else if ((key=='1' || key=='2') && mouthOpen) { num--; if (num==0) { // snap mouthOpen=false; // Penalize if (key=='1') chips1 -= penalty; else chips2 -= penalty; } else { // safe if (key=='1') chips1 += bonus; else chips2 += bonus; } } }
[applet]
I think this is the first we've seen the arc() function. It's just part of an ellipse, between a starting angle and an ending angle (in radians).
Finally, with just a couple of notes, we can follow the example from the first class. The first note is that the frameCount variable, holds the number of frames that have been displayed. That is, each time through the draw() function, it is incremented by 1. The other note is that the percent sign (modulo) represents the remainder after division. So 3 % 2 is 1, because there is 1 left over after dividing 3 by 2. If the remainder of a divisigion is 0, then it divides evenly, so to make something happen every fifth frame, we see if frame % 5 == 0.