The following code is an implementation of the face pseudocode we looked at earlier. The code utilizes vari- ables to capture relative position and scale relationships between the facial features, all based on the initial head height. Output of the code is shown in Figure 2-13.
Figure 2-13. Simple algorithmic face // Simple Algorithmic Face size(600, 800);
background(0); stroke(255); noFill();
// draw ellipses from top left corner ellipseMode(CORNER);
// BEGIN DECLARE/INITIALIZE VARIABLES // HEAD
float headHeight = 600;
float head_x = (width-headWidth)/2; float head_y = (height-headHeight)/2; // EYES
float eyeWidth = headWidth/5; float eyeHeight = eyeWidth/2; float irisDiam = eyeHeight; float pupilDiam = irisDiam/3;
float eye_y = head_y+headHeight/2-eyeHeight/2; // left
float leftEye_x = head_x+eyeWidth;
float leftIris_x = leftEye_x + eyeWidth/2-irisDiam/2; float leftPupil_x = leftEye_x + eyeWidth/2-pupilDiam/2; // right
float rightEye_x = head_x+eyeWidth*3;
float rightIris_x = rightEye_x + eyeWidth/2-irisDiam/2; float rightPupil_x = rightEye_x + eyeWidth/2-pupilDiam/2; //EYEBROWS
float eyeBrowWidth = eyeWidth*1.25; float eyeBrowHeight = eyeHeight/4;
float eyeBrow_y = eye_y - eyeHeight - eyeBrowHeight/2; // left
float leftEyeBrow_x = leftEye_x - (eyeBrowWidth-eyeWidth); // right
float rightEyeBrow_x = rightEye_x; // NOSE
float nose_x = head_x+eyeWidth*2;
float nose_y = head_y + headHeight - headHeight/4; // MOUTH
float mouthWidth = eyeWidth*1.5; float mouthHeight = headHeight/12;
float mouth_x = leftIris_x+irisDiam/2+eyeWidth/4; float mouth_y = nose_y + mouthHeight;
// EARS
float earWidth = eyeHeight*1.5; float earHeight = headHeight/4;
// left eye
ellipse(leftEye_x, eye_y, eyeWidth, eyeHeight); // Draw left iris
ellipse(leftIris_x, eye_y, irisDiam, irisDiam); // Draw left pupil
ellipse(leftPupil_x, eye_y+eyeHeight/2-pupilDiam/2, pupilDiam, pupilDiam); // Draw right eye
ellipse(rightEye_x, eye_y, eyeWidth, eyeHeight); // Draw right iris
ellipse(rightIris_x, eye_y, irisDiam, irisDiam); // Draw right pupil
ellipse(rightPupil_x, eye_y+eyeHeight/2-pupilDiam/2, pupilDiam, pupilDiam); // Draw left eyebrow
rect(leftEyeBrow_x, eyeBrow_y, eyeBrowWidth, eyeBrowHeight); // Draw right eyebrow
rect(rightEyeBrow_x, eyeBrow_y, eyeBrowWidth, eyeBrowHeight); // Draw nose
triangle(nose_x, nose_y, nose_x+eyeWidth, nose_y, nose_x + eyeWidth/2, nose_y-eyeWidth); // Draw Mouth - top lip
arc(mouth_x, mouth_y-mouthHeight/2, mouthWidth, mouthHeight, PI, TWO_PI); // Draw Mouth - bottom lip
arc(mouth_x, mouth_y-mouthHeight/2, mouthWidth, mouthHeight, 0, PI); // Draw Mouth – crease
line(mouth_x, mouth_y, mouth_x+mouthWidth, mouth_y); // Draw left ear
arc(leftEar_x, ear_y, earWidth, earHeight, PI/2.3, PI*1.55); // Draw right ear
arc(rightEar_x, ear_y, earWidth, earHeight, -PI/1.8, PI/1.8);
Reviewing the code, notice how the declared variables at the top of the program are named. When naming variables, the goal again should be to strike a balance between clarity and economy. Ideally, the names should be descriptive enough to give a clear sense of their purpose, yet not overly verbose. Also, notice the ample use of comments and whitespace to help organize the code. There is a tendency when coding to just get things working as quickly as possible. This can lead to sloppy and ultimately ugly code. Programmers have a term to describe code like this: “Spaghetti Code.” Look at the following two blocks of code: which do you think is easier to comprehend (especially a few months after you’ve written it, or by another coder trying to work with your code)?
// spaghetti code
float ew=hw/5;float eh=ew/2; float id=eh;float pd=id/3; float ey=hy+hh/2-eh/2; // better code
float eyeWidth = headWidth/5; float eyeHeight = eyeWidth/2; float irisDiam = eyeHeight; float pupilDiam = irisDiam/3;
In addition to naming and including comments and whitespace, it’s also helpful to structure your code in a way that makes it easy to manage. By declaring variables up top, as opposed to interspersed with the drawing code, it’s easier to make global changes to the code, as well as debug it, should problems arise–and they will. Reviewing the Simple Algorithmic Face code more closely, there are a few new commands:
background(0); stroke(255); noFill();
// draw ellipses from top left corner ellipseMode(CORNER);
The background() command paints the entire sketch window with the color specified by the argument(s) passed between the parentheses. When passing only one argument, values between 0 and 255 will paint the back- ground from black through gray to white respectively. For example, background(0) paints the window black, background(255) paints the window white and any value between 0 and 255 would paint it gray. It is also pos- sible to paint the background a color by passing three arguments (each between 0 and 255) representing red, green, and blue. For example, to paint the background pure red, you’d write background(255, 0, 0), for pure green background(0, 255, 0). Of course, the colors don’t have to be pure; background(100, 50, 20) paints a lovely brown. Later in the book you’ll learn more about these commands, including the math behind them. stroke() works similarly to background(), except it controls the color of lines, while fill() controls the inside color of shapes. stroke() and fill() allow you to pass 1, 2, 3, or 4 arguments (each between 0 and 255):
1 argument specifies grayscale ■
■
2 arguments specify grayscale and its degree of opacity (referred to as alpha) ■
■
3 arguments specify red, green and blue ■
■
4 arguments specify red, green, blue and alpha (degree of opacity) ■
■
noFill() and noStroke() turn off the fill and stroke respectively, until the fill() or stroke() call is made again.
All the commands we’ve been discussing control Processing’s painting system by changing the style states of the system. These states remain constant until another related command is made. For example, after you call fill(255, 255, 0), every shape you draw will be filled with yellow, until you call the fill() command again, specifying a new color. By default, when you start a new Processing sketch the fill state is white and the stroke state is black.
Similar to Processing’s painting style states, there are default geometry states, controlling how shapes are plot- ted in the sketch window. As you learned earlier in the chapter, rectangles by default are drawn from their top left corner, while ellipses are drawn around their center. We can override this behavior for each of these shapes with the commands: rectMode(CENTER) and ellipseMode(CORNER). In the Simple Algorithmic Face example, using ellipseMode(CORNER) simplified the position and scale calculations of the facial features.
After setting the painting and geometry states, we declare and initialize about thirty variables in the code, from headHeight down to rightEar_x. It may seem like a lot of code, just to draw a relatively simple face, but again computers are lousy at intuiting. Each facial feature needs variables for its position (along both x- and y-axes) and size (specifying width and height). Notice that only the headHeight variable is initialized with a constant value (float headHeight = 600;). All the other variables are initialized using simple mathematical expressions, based on other facial details. This approach allows us to easily make global changes to the pro- gram. For example, Figure 2-14 and 2-15 show dramatic size changes to the head, while maintaining relative scale and positioning between the individual facial features. These images were generated simply by changing headHeight to 100 and then 1000.
Try This: Create a copy of the Simple Algorithmic Face sketch and try entering some different values throughout the code. Try to predict what will happen based on your changes.
Figure 2-15. Simple algorithmic face, headHeight = 1000