Source Code. Projects. Nerd Stuff. Art Stuff.

Lesson 5: Motion

[gn_pullquote align="left"]Like a flip book, animation on screen is created by drawing an image, then drawing a slightly different image, then another, and so on. The illusion of fluid motion is created by persistence of vision. When a set of similar images is presented at a fast enough rate, our brains translate these images into motion.[/gn_pullquote] By default, Processing runs the code inside draw() at 60 frames per second.This can be set manually with the frameRate() function. Keep in mind that the frameRate() function specifies only the maximum frame rate, but the actual frame rate for any program depends on the computer that is running the code.

Speed and Direction

To create fluid motion examples, we use a data type called float. This type of variable stores numbers with decimal places, which provide more resolution for working with motion. For instance, when using ints, the slowest you can move each frame is one pixel at a time (1, 2, 3, 4, . . .), but with floats, you can move as slowly as you want (1.01, 1.01, 1.02, 1.03, . . .).

Move a shape

Here is a code example that would move a shape from left to right by updating the x variable:

int radius = 40;
float x = -radius;
float speed = 0.5;

void setup() {
  size(240, 120);
  smooth();
  ellipseMode(RADIUS);
}

void draw() {
  background(0);
  x += speed;     // Increase the value of x
  arc(x, 60, radius, radius, 0.52, 5.76);
}

 

Wrap Around

You’ll notice in the previous example that the image advances to the right and then disappears off the screen. You can extend the code to show how to move the shape back to the left edge of the screen after it disappears off the right. In this case, picture the screen as a flattened cylinder, with the shape moving around the outside to return to its starting point:

int radius = 40;
float x = -radius;
float speed = 0.5;

void setup() {
  size(240, 120);
  smooth();
  ellipseMode(RADIUS);
}

void draw() {
  background(0);
  x += speed;                // Increase the value of x
  if (x > width+radius) {    // If the shape is off the screen,
    x = -radius;             // move to the left edge
  }
  arc(x, 60, radius, radius, 0.52, 5.76);
}

 

On each trip through draw(), the code tests to see if the value of x has increased beyond the width of the screen (plus the radius of the shape). If it has, we set the value of x to a negative value, so that as it continues to increase, it will enter the screen from the left:

Bounce Off the Wall

Instead of wrapping around to the left, we can extend the example further to have the shape change directions when it hits an edge. To make this happen, we add a new variable to store the direction of the shape. A direction value of 1 moves the shape to the right, and a value of –1 moves the shape to the left:

int radius = 40;
float x = 110;
float speed = 0.5;
int direction = 1;

void setup() {
  size(240, 120);
  smooth();
  ellipseMode(RADIUS);
}

void draw() {
  background(0);
  x += speed * direction;        
  if ((x > width-radius) || (x < radius)) {
    direction = -direction;     // Flip direction
  }
  if (direction == 1) {
    arc(x, 60, radius, 0.52, 5.76);  // Face right
  } else {
    arc(x, 60, radius, 3.67, 8.9);   // Face left
  }
}

 

When the shape reaches an edge, this code flips the shape’s direction by changing the sign of the direction variable. For example, if the direction variable is positive when the shape reaches an edge, the code flips it to negative.

Tweening

Sometimes you want to animate a shape to go from one point on screen to another. With a few lines of code, you can set up the start position and the stop position, then calculate the in-between (tween) positions at each frame. In the following examples, change the values of the variables defined at the top to see how a shape can be moved from any location to any other location, and by changing the step variable to alter the speed:

int startX = 20;            // Initial x-coordinate
int stopX = 160;            // Final x-coordinate
int startY = 20;            // Initial y-coordinate
int stopY = 160;            // Final y-coordinate
float x = startX;           // Current x-coordinate
float y = startY;           // Current y-coordinate
float step = 0.005;         // Size of each step (0.0 to 1.0)
float pct = 0.0;            // Percentage traveled (0.0 to 1.0)

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

void draw() {
  background(0);
  if (pct < 1.0) {
    x = startX + ((stopX-startX) * pct);
    y = startY + ((stopY-startX) * pct);
    pct += step;
  }
  ellipse(x, y, 20, 20);
}

 

Random

Unlike the smooth, linear motion common to computer graphics, motion in the physical world is usually idiosyncratic. We can simulate the unpredictable qualities of the world by generating random numbers. The random() function calculates these values; we can set a range to tune the amount of disarray in a program. To see how the random() function works, print a string of random numbers to the Console:

void draw() {
  float r - random(0, mouseX);
  println(r);
}

 

Note that the random() function always returns a floating-point value, so be sure the variable on the left side of the assignment operator (=) is a float.

Draw Randomly

The following example uses the values from random() to change the position of lines on screen. When the mouse is at the left of the screen, the change is small; as it moves to the right, the values from random() increase and the movement becomes more exaggerated. Because the random() function is inside the for loop, a new random value is calculated for each point of every line:

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

void draw() {
  background(204);
  for (int x = 20; x < width; x += 20) {
    float mx = mouseX / 10;
    float offsetA = random(-mx, mx);
    float offsetB = random(-mx, mx);
    line(x + offsetA, 20, x - offsetB, 100);
  }
}

 

Move Shapes Randomly

When used to move shapes around on screen, random values can generate images that are more natural in appearance. In the following example, the position of the circle is modified by random values on each trip through draw(). Because the background() function is not used, past locations are traced:

float speed = 2.5;
int diameter = 20;
float x;
float y;

void setup() {
  size(240, 120);
  smooth();
  x = width/2;
  y = height/2;
}

void draw() {
  x += random(-speed, speed);
  y += random(-speed, speed);
  ellipse(x, y, diameter, diameter);
}

 

Constrain

If you watch the previous example long enough, you may see the circle leave the window and come back. This is left to chance, but we could add a few if structures or use the constrain() function to keep the circle from leav- ing the screen. The constrain() function limits a value to a specific range, which can be used to keep x and y within the boundaries of the display window. By adding the following code to the draw() function in the preceding code, you’ll ensure that the ellipse will remain on the screen:

x = constrain(y, 0, width);
y = constrain(y, 0, width);

 

Another sometimes useful tool is the randomSeed() function, which can be used to force random() to produce the same sequence of numbers each time a program is run.

Timers

Processing keeps a timer of how much time has elapsed, in milliseconds, since the program started. We can use this counter to trigger animations at specific times. The millis() function returns this counter value.

Triggering Timed Events

When paired with an if block, the values from millis() can be used to sequence animation and events within a program. For instance, after two seconds have elapsed, the code inside the if block can trigger a change. In this example, variables called time1 and time2 determine when to change the value of the x variable:

int time1 = 2000;
int time2 = 4000;
float x = 0;

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

void draw() {
  int currentTime = millis();
  background(204);
  if (currentTime > time2) {
    x -= 0.5;
  } else if (currentTime > time1) {
    x += 2;
  }
  ellipse(x, 60, 90, 90);
}

 

Circular

The sin() and cos() functions in Processing return values between –1 and 1 for the sine or cosine of the specified angle. Like arc(), the angles must be given in radian values. To be useful for drawing, the float values returned by sin() and cos() are usually multiplied by a larger value.

This example shows via println() how values for sin() cycle from –1 to 1 as the angle increases. Using the map() function, the sinval variable is converted from this range to values from 0 and 255. This new value is used to set the background color of the window:

void draw() {
float sinval = sin(angle);
println(sinval);
//  float gray = map(sinval, -1, 1, 0, 255);
//  background(gray);
angle += 0.1;
}

 

Sine Wave Movement

The following example shows how these values can be converted into movement:

float angle = 0.0;
float offset = 60;
float scalar = 40;
float speed = 0.05;

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

void draw() {
  background(0);
  float y1 = offset + sin(angle) * scalar;
  float y2 = offset + sin(angle + 0.4) * scalar;
  float y3 = offset + sin(angle + 0.8) * scalar;
  ellipse(80, y1, 40, 40);
  ellipse(120, y2, 40, 40);
  ellipse(160, y3, 40, 40);
  angle += speed;
}

 

Circular Motion

When sin() and cos() are used together, they can produce circular motion. The cos() values provide the x-coordinates, and the sin() values the y-coordinates. Both are multiplied by a variable named scalar to change the radius of the movement and summed with an offset value to set the center of the circular motion:

float angle = 0.0;
float offset = 60;
float scalar = 40;
float speed = 0.05;

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

void draw() {
  float x = offset + cos(angle) * scalar;
  float y = offset + sin(angle) * scalar;
  ellipse(x, y, 40, 40);
  angle += speed;
}

 

Spirals

A slight change made to increase the scalar value at each frame produces a spiral, rather than a circle:

float angle = 0.0;
float offset = 60;
float scalar = 2;
float speed = 0.05;

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

void draw() {
  float x = offset + cos(angle) * scalar;
  float y = offset + sin(angle) * scalar;
  ellipse(x, y, 2, 2);
  angle += speed;
  scalar += speed;
}

 

Translate, Rotate, Scale

Changing the screen coordinates is an alternative technique to create motion. For instance, you can move a shape 50 pixels to the right, or you can move the location of coordinate (0,0) 50 pixels to the right—the visual result on screen is the same. By modifying the default coordinate system, we can create different transformations including translation, rotation, and scaling.

Translate

The translate() function can shift the coordinate system left, right, up, and down with its two parameters. In this example, notice that each rectangle is drawn at coordinate (0,0), but they are moved around on the screen, because they are affected by translate(). The translate() function sets the (0,0) coordinate of the screen to the mouse location. In the next line, the rect() drawn at the new (0,0) is drawn at the new mouse location.

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

void draw() {
translate(mouseX, mouseY);
rect(0, 0, 30, 30);
}

 

Multiple Translations

After a transformation is made, it is applied to all subsequent drawing functions. Notice what happens when a second translate command is added to control a second rectangle:

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

void draw() {
  translate(mouseX, mouseY);
  rect(0, 0, 30, 30);
  translate(35, 10);
  rect(0, 0, 15, 15);
}

 

The smaller rectangle is also affected by the other rectangle’s transformations. To remedy this, you need to isolate each rectangle.

Isolating Transformations

To isolate the effects of a transformation so they don’t affect later commands, use the pushMatrix() and popMatrix() functions. When the pushMatrix() function is run, it saves a copy of the current coordinate system and then restores that system after popMatrix():

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

void draw() {
  pushMatrix();
  translate(mouseX, mouseY);
  rect(0, 0, 30, 30);
  popMatrix();
  translate(35, 10);
  rect(0, 0, 15, 15);
}

 

Note that he pushMatrix() and popMatrix() functions are always used in pairs.

Rotation

The rotate() function rotates the coordinate system. It has one parameter, which is the angle (in radians) to rotate. It always rotates relative to (0,0), known as rotating around the origin. To spin a shape around its center point, first use translate() to move to the location where you’d like the shape, then call rotate(), and then draw the shape with its center at coordinate (0,0):

float angle = 0.0;

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

void draw() {
  translate(mouseX, mouseY);
  rotate(angle);
  rect(-15, -15, 30, 30);
  angle += 0.1;
}

 

When translate() and rotate() are combined, the order in which they appear affects the result. Modifying the previous example so that translate() and rotate() are reversed, the shape will rotate around the upper-left corner of the display window, with the distance from the corner set by translate().