tutorial 5.1:

Simple animation and capturing frames

In this tutorial we will look at methods for creating simple animations and for capturing each frame of the animation. These frames can then be combined into a movie using animated GIF software or the 'Movie Maker' software that comes with Processing.


1) Moving geometry


Let's start with the following sketch, which draws a single line from the top to the bottom of the stage:

/*
December 2014
 CD 1
 Mark Meagher
 SSoA
 */

int x = 0;
int myDimension = 200;

void setup() {
  size(myDimension, myDimension); //the size of the display window
}

void draw() {
  background(204);
  line(x, 0, x, myDimension);
  noLoop();
}



One straightforward way to animate our line is to change its position with each frame. We can do this by incrementing the value of 'x' - the x-position of the line:

void draw() {
    background(204);
    if (x < myDimension) {
        line(x, 0, x, myDimension);
        x++;
    } else {
        noLoop();
    }
}


The line will now move one pixel to the right with each frame. The animation stops once the line has reached the right-hand side of the stage. To speed up the pace of movement you can change the framerate (i.e. by typing 'frameRate(250);' in the setup()), or increase the increment by which tha value of 'x' increases with each frame (i.e. using x+=2; in place of x++; will double the rate of movement).

The complete code is as follows:

/*
  December 2014
 CD 1
 Mark Meagher
 SSoA
 */

int x = 0;
int myDimension = 200;

void setup() {
  size(myDimension, myDimension); //the size of the display window
}


void draw() {
  background(204);
  if (x < myDimension) {
    line(x, 0, x, myDimension);
    x++;
  } else {
    noLoop();
  }
}



2) rotate() and translate()


Processing provides several built-in functions for transforming geometry; you can find a full list of these on the Processing Reference page under 'Transform'. The functions we'll look at here are rotate() and translate() which (as the names suggest) are used to rotate an object or to translate (move) and object. The way these functions work, however, is not completely intuitive and requires some explanation. Once you get used to using these and other transformation functions you'll find that they provide a very powerful set of tools for working with geometry.

Here's an example:

/**
 * Rotate. 
 * 
 * Rotating a square around the Z axis. To get the results
 * you expect, send the rotate function angle parameters that are
 * values between 0 and PI*2 (TWO_PI which is roughly 6.28). If you prefer to 
 * think about angles as degrees (0-360), you can use the radians() 
 * method to convert your values. For example: scale(radians(90))
 * is identical to the statement scale(PI/2). 
 *
 * Adapted by Mark Meagher from Processing example 'Rotate'
 */

float angle;

void setup() {
  size(640, 360);
  noStroke();
  fill(255);
  rectMode(CENTER);
}

void draw() {
  background(51);
  angle+=.01;
  float c = cos(angle);
  translate(width/2, height/2); //moves the rectangle to the center of the stage
  rotate(c); //rotates the rectangle by the amount specified
  println("the cos(angle) is "+c);
  println("the angle is "+angle);
  rect(0, 0, 180, 180);
}



A few things are worth pointing out here. The rotate and translate functions apply to any geometry created after the functions are invoked: so the order is first to define the translation and rotation and then to create the geometry, which may be the opposite of what you expect. The important thing is to think of a translation or rotation as an abstract set of commands that can be applied to any object. You should also note that rotate and translate when used in the draw loop are reset from one frame to the next: that is why our rectangle does not move in the 'y' dimension. These functions are, however, cumulative within a given frame as you can see in this example:

/**
 * Translate. 
 * 
 * The translate() function allows objects to be moved
 * to any location within the window. The first parameter
 * sets the x-axis offset and the second parameter sets the
 * y-axis offset. 
 *
 * Adapted by Mark Meagher from Processing example 'Translate'
 */

float x;
float dim = 80.0;

void setup() {
  size(640, 360);
  noStroke();
}

void draw() {
  background(102);

  x = x + 0.8;

  translate(x, height/2-dim/2);
  fill(255);
  rect(-dim/2, -dim/2, dim, dim);

  // Transforms accumulate. Notice how this rect moves 
  // twice as fast as the other, but it has the same 
  // parameter for the x-axis value
  translate(x, dim);
  fill(0);
  rect(-dim/2, -dim/2, dim, dim);
}



3) pushMatrix() and popMatrix()


The functions pushMatrix() and popMatrix() can be used to exert greater control over the transform functions such as rotate() and translate(). As we've seen from the previous examples, transforms are cumulative within a given frame; this is sometimes the behaviour that you want, but sometimes you don't want this cumulative effect such as when applying the same transform to a series of objects within a loop. Try running the example below with and without the pushMatrix() and popMatrix() functions to get an idea of how they work:

/**
 * Rotate Push Pop. 
 * 
 * The push() and pop() functions allow for more control over transformations.
 * The push function saves the current coordinate system to the stack 
 * and pop() restores the prior coordinate system. 
 *
 * Adapted by Mark Meagher from Processing example Rotate Push Pop
 */

float a;                 // Angle of rotation
float offset = .1;  // Angle offset between boxes in radians
int num = 12;            // Number of boxes

void setup() { 
  size(640, 360);
  noStroke(); 
  //fill(255);
  rectMode(CENTER);
} 


void draw() {

  background(100);
  translate(width/2, height/2); 

  for (int i = 0; i < num; i++) {
    float gray = map(i, 0, num-1, 0, 255);
    pushMatrix();
    fill(gray);
    rotate(a + offset*i);
    rect(0, 0, 180, 180);
    popMatrix();
  }

  a += 0.01;
} 

What is actually happening in Processing when you apply a transform is that you're moving the coordinate system: this is why transforms are cumulative and apply to all objects created after the transform is applied. What pushMatrix() then does is to save the current coordinate system, allowing you to make further changes to the coordinate system and then eventually restore the previous state with the function popMatrix(). The use of pushMatrix() and popMatrix is explained very well in this tutorial on the Processing site.

Can you think of other applications for using transforms?

4) Capturing frames and making movies


You can save each frame of your movie as an image using the saveFrame() function, which captures a screenshot of the main drawing window in PNG, JPG, TIFF or TARGA format. For usage and syntax see the saveFrame() Reference page. The one parameter for this function is the filename for the saved image file (the extension in the filename also determines the filetype). To capture what appears on the screen it's usually best to run the function at the end of the draw() loop. You can also run the function on demand, for example when the mouse is clicked or a particular key is pressed. The images are saved by default to the sketchfolder; you can also send them to any other location on your computer by specifying an absolute path.

Once you've saved your frame images you can combine them into a movie using the Movie Maker program in the Processing 'Tools' menu.

You can download all the sketchfiles from this tutorial here.