Source Code. Projects. Nerd Stuff. Art Stuff.

Lesson 6: Functions

[gn_pullquote align="left"]Functions are independent software units that are used to build more complex programs. The power of functions is modularity, and the true power of this design is the ability to build many different forms from the same set of elements.[/gn_pullquote]

Functions are helpful if you want to draw a more complex shape like a tree over and over. The function to draw the tree shape would be made up of Processing’s built-in commands, like line(), that create the form. After the code to draw the tree is written, you don’t need to think about the details of tree drawing again. You would simply call your tree() function and the code contained within is automatically executed to draw your tree shape. This makes your code more readable, easier to maintain and debug, and more extensible. Once a function is defined, the code inside the function need not be repeated again.

Functions allow a complex sequence of statements to be abstracted, so you can focus on the higher-level goal (such as drawing a tree), and not the details of the implementation (the line() commands that define the tree shape).

A computer runs a program one line at a time. When a function is called, the computer jumps to where the function is defined and runs the code there, then jumps back to where it left off.

Writing a Function

Let’s say we wanted to draw this owl:

To draw this in Processing, we might write this code:

void setup() {
  size(488, 120);
  smooth();
}

void draw() {
  background(204);
  translate(110, 110);
  stroke(0);
  strokeWeight(70);
  line(0, -35, 0, -65);  // Body
  noStroke();
  fill(255);
  ellipse(-17.5, -65, 35, 35);  // Left eye dome
  ellipse(17.5, -65, 35, 35);   // Right eye dome
  arc(0, -65, 70, 70, 0, PI);   // Chin
  fill(0);
  ellipse(-14, -65, 8, 8);      // Left eye
  ellipse(14, -65, 8, 8);       // Right eye
  quad(0, -58, 4, -51, 0, -44, -4, -51);  // Beak
}

 

Notice that translate() is used to move the origin (0,0) to 110 pixels over and 110 pixels down. Then the owl is drawn relative to (0,0), with its coordinates sometimes positive and negative as it’s centered around the new 0,0 point.

If we wanted to draw two owl, our code would nearly double. So let’s create an owl function. We write it much like we’d write other functions, by defining its name and its parameters. (The setup and draw functions have empty parentheses next to them because they take no parameters). With functions, you can set what information you’d like to pass into them to get the results you want.

void setup() {
  size(488, 120);
  smooth();
}

void draw() {
  background(204);
  owl(110, 110);
  owl(180, 110);
}

void owl(int x, int y) {
  pushMatrix();
  translate(x, y);
  stroke(0);
  strokeWeight(70);
  line(0, -35, 0, -65);  // Body
  noStroke();
  fill(255);
  ellipse(-17.5, -65, 35, 35);   // Left eye dome
  ellipse(17.5, -65, 35, 35);    // Right eye dome
  arc(0, -65, 70, 70, 0, PI);    // Chin
  fill(0);
  ellipse(-14, -65, 8, 8);       // Left eye
  ellipse(14, -65, 8, 8);        // Right eye
  quad(0, -58, 4, -51, 0, -44, -4, -51); // Beak
  popMatrix();
}

 

Now anytime you want to draw an owl, you would simply call the owl() function with the appropriate parameters. Parameters are an important part of functions, because they provide flexibility. For instance, the parameters to the line() function make it possible to draw a line from any pixel on screen to any other pixel. Without the parameters, the function would be able to draw a line only from one fixed point to another. In the owl example above, each of the x and y values  is passed into the function, and then wherever the variable name appears within the function, it’s replaced with the incoming value. It’s important to make sure the values passed into a function match the data types of the parameters, or an error message will appear.

Functions can be placed inside loops, can be paired with arrays, and can be setup to provide many different variations of the same object:

void setup() {
  size(488, 120);
  smooth();
}

void draw() {
  background(204);
  randomSeed(0);
  for (int i = 35; i < width + 40; i += 40) {
    int gray = int(random(0, 102));
    float scalar = random(0.25, 1.0);
    owl(i, 110, gray, scalar);
  }
}

void owl(int x, int y, int g, float s) {
  pushMatrix();
  translate(x, y);
  scale(s);                // Set the size
  stroke(g);               // Set the gray value
  strokeWeight(70);
  line(0, -35, 0, -65);    // Body
  noStroke();
  fill(255-g);
  ellipse(-17.5, -65, 35, 35);   // Left eye dome
  ellipse(17.5, -65, 35, 35);    // Right eye dome
  arc(0, -65, 70, 70, 0, PI);    // Chin
  fill(g);
  ellipse(-14, -65, 8, 8);       // Left eye
  ellipse(14, -65, 8, 8);        // Right eye
  quad(0, -58, 4, -51, 0, -44, -4, -51); // Beak
  popMatrix();
}

 

Functions can make a calculation and then return a value to the main program. We’ve already used functions of this type, including random() and sin(). Notice that when this type of function appears, the return value is usually assigned to a variable:

1
 float r = random(1, 10);

In this case, random() returns a value between 1 and 10, which is then assigned to the r variable.

A function that returns a value is also frequently used as a parameter to another function. For instance:

1
point(random(width), random(height));

In this case, the values from random() aren’t assigned to a variable—they are passed as parameters to point() and used to position the point within the window.
When you call a function such as setup(), you declare its datatype as void. This means tat it isn’t expecting to return a value. When you want to return a value, however, you assign a datatype to the function, and then specify the value you want to return inside your function by using the keyword return. For example, using the function called calculateMars() to calculate the weight of a person or object on Mars:

void setup() {
  float yourWeight = 155;
  float marsWeight = calculateMars(yourWeight);  // yourWeight goes into the function, returns a value, and gets stored in marsWeight
  println(marsWeight);   // marsWeight gets printed in the console
}

float calculateMars(float w) {  // Parameter to accept yourWeight from above
  float newWeight = w * 0.38;
  return newWeight;  // After calculation, return this value
}