Chapter 8: Motion

  1. Frames
    1. Example 8-1: See the Frame Rate
    2. Example 8-2: Set the Frame Rate
  2. Speed and Direction
    1. Example 8-3: Move a Shape
    2. Example 8-4: Wrap Around
    3. Example 8-5: Bounce Off the Wall
  3. Tweening
    1. Example 8-6: Calculate Tween Positions
  4. Random
    1. Example 8-7: Generate Random Values
    2. Example 8-8: Draw Randomly
    3. Example 8-9: Move Shapes Randomly
  5. Timers
    1. Example 8-10: Time Passes
    2. Example 8-11: Triggering Timed Events
  6. Circular
    1. Example 8-12: Sine Wave Values
    2. Example 8-13: Sine Wave Movement
    3. Example 8-14: Circular Motion
    4. Example 8-15: Spirals
  7. Robot 6: Motion

Motion

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 our brains through a process called persistence of vision. When a set of similar image is presented at a fast enough rate, our brains translate the changes into motion.

Side Note: Wikipedia states that Persistence of Vision is an outmoded concept, and that little is actually understood how our brain perceives motion (link).

Frames

We already know that the draw() loop refreshes the Display Window at a target rate of 60 times per second, which Processing refers to as Frames Per Second (much like Adobe Animate). We can test the frame rate very easily with the environmental variable frameRate.

See the Frame Rate

PROCESSING

Example 8-1 (7-1 in 1st Ed.)

void draw() {
  println(frameRate);
}
Resulting Display Window Resulting Console Window

It's interesting to note that the frameRate is a target that Processing tries to achieve, and doesn't always make. If you have a sketch that's doing a lot of computational work, it's possible to not hit the target rate. You can also see from the Console that sometimes you can exceed the framerate, but usually only by a small amount.

Set the Frame Rate

Setting the frame rate to less than the default 60fps can come in handy when controlling the playback of an animation.

The human eye is theoretically capable of perceiving as low as 5fps as motion (wikipedia), but anything less than 12fps is usually perceived as jerky.

You can set the frame rate of the Display Window with frameRate(numberValue)...

PROCESSING

Example 8-2 (7-2 in 1st Ed.)

void setup() {
  frameRate(30);     // Thirty frames each second
  //frameRate(12);   // Twelve frames each second
  //frameRate(2);    // Two frames each second
  //frameRate(0.5);  // One frame every two second
}

void draw() {
  println(frameRate);
}
Resulting Display Window Resulting Console Window

frameRate(30) is not the same as frameRate (you can see it in the color coding). The first is a function you can set a parameter on, and the second is a environmental variable which gives back the currently active frame rate.

There is no upper limit to the frameRate. My machine at home will go up to about 7000fps (when there's nothing going on except displaying the framerate).

 

Speed & Direction

Another way to control the speed of a moving object is use different values for the distance moved at each frame. So, if I move an object 1 pixel per frame at 60fps, the object will move 60 pixels every second. But Processing lets us use float values, so if I set the distance to 0.5, I can go 30 pixels in 1 second instead of 60 at 60fps.

Move A Shape

Let's move a shape at 60fps but slow it's speed by half...

PROCESSING

Example 8-3 (7-3 in 1st Ed.)

int myRadius = 40;
float xVal = -myRadius;  // This puts the Pac-Man off the screen to the left
float mySpeed = 0.5;

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

void draw() {
  background(0);
  xVal += mySpeed;  // Increase the value of xVal
  arc(xVal, 60, myRadius, myRadius, 0.52, 5.76);
}
Resulting Display Window

A Variation

Let's add some animation to the mouth by toggling incrementing and decrementing the mouth angle with a test for the value of the mouthTop angle to trigger the toggle.

To figure out the angles, our textbook includes this lovely chart:

Radians compared to Degrees chart

And it looks like we need to go from and angle starting at 0/360 (closed) to 30/330 (open).

PROCESSING

Packman 1

int myRadius = 40;
float xVal = -myRadius;  // This puts the Pac-Man off the screen to the left
float mySpeed = 0.5;
int mouthBottom = 30;
int mouthTop = 330;
float toggleMouth = 2;

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

void draw() {
  background(0);
  xVal += mySpeed;  // Increase the value of xVal
  fill(255, 255, 0);
  arc(xVal, 60, myRadius, myRadius, radians(mouthBottom), radians(mouthTop));
  if((mouthBottom <= 0) || (mouthBottom >= 31)) {
    toggleMouth *= -1;  // flip direction
  }
  // increment/decrement mouth angle
  mouthBottom += toggleMouth;
  mouthTop -= toggleMouth;
}
Resulting Display Window

Wrap Around

Here we'll move the little bugger and make him wrap around to the left side when he gets past the left side. What we need to test for is his x location. Since his center is his x location, we have to test to see if the center is past the width of the display window (the right edge) by his radius. When it is, we have to put him to be past the left edge, again by his radius.

PROCESSING

Example 8-4 (7-4 in 1st Ed.)

int myRadius = 40;
float xVal = -myRadius;
float mySpeed = 0.5;

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

void draw() {
  background(0);
  xVal += mySpeed; // Increase the value of xVal
  if (xVal > width + myRadius) {  // If the shape is off screen
     xVal = -myRadius; // Move to left edge
  }
  arc(xVal, 60, myRadius, myRadius, radians(30), radians(330));
}
Resulting Display Window

Does this remind you of anything you need to do for your animated robot?

Bounce Off The Wall

The trick here is to flip Pac-Man around. To do that we have to change the angle of the arc shape so the mouth is on the other side. Arcs are measured at a starting angle to an ending angle. The right-facing angle is 30-330, but the left facing angle isn't 210-150, which fails to draw any arc:

  arc(xVal, 60, myRadius, myRadius, radians(210), radians(150));
Resulting Display Window

Nor is it 150-210, which looks like this:

  arc(xVal, 60, myRadius, myRadius, radians(150), radians(210));
Resulting Display Window

But adding 360 degrees to 210 degrees will work, wrapping the arc around in the opposite direction.

  arc(xVal, 60, myRadius, myRadius, radians(150), radians(210 + 360));
Resulting Display Window Graphic of packman's positioning relative to the display window

Also, we need to test for when Pac-Man's center (or x location) is his radius short of the display window's width when he's going to the right, and when it's his radius away from the left edge when he's going to the left.

PROCESSING

Example 8-5 (7-5 in 1st Ed.)

int myRadius = 40;
float xVal = 110;
float mySpeed = 0.5;
int myDirection = 1;

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

void draw() {
  background(0);
  xVal += mySpeed * myDirection; // Increase the value of xVal
  if ((xVal > width - myRadius) || (xVal < myRadius)) {  
     // If the shape is off screen flip direction
     myDirection = -myDirection;
  }
  if(myDirection == 1) { 
    // Face right
    arc(xVal, 60, myRadius, myRadius, radians(30), radians(330));
  } else {
    // Face left
    // Second radians of arc must be greater the the first to be visible
    arc(xVal, 60, myRadius, myRadius, radians(210), radians(150+360));
  }
}
Resulting Display Window

Tweening

Our text demos a tweening animation somewhat superficially. This is what animation is all about, and it's what Adobe Animate excels at doing with their timeline and keyframes. But in Processing, and most other coding environments, you have to calculate the change in position in each frame and test to see when the motion is complete. This example also puts as much as possible into variables, with the intention that it can be used in multiple circumstance, resulting in different animations achieved by merely modifying the variables...

This is an important concept in coding. Make code that can be reused by simply changing the parameters but not changing the logic, so it can be used over and over in different circumstances.

Calculate Tween Positions

PROCESSING

Example 8-6 (7-6 in 1st Ed.)

int startX = 20;        // Initial x coordinate
int stopX = 160;        // Final x coordinate
int startY = 30;        // Initial y coordinate
int stopY = 80;         // Final y coordinate
float xVal = startX;    // Current x coordinate
float yVal = startY;    // Current y coordinate
float stepVal = 0.005;  // Size of each step (0.0 to 1.0)
float percentVal = 0.0; // Percentage traveled (0.0 to 1.0)

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

void draw() {
  background(0);
  if (percentVal < 1.0) {
    xVal = startX + ((stopX - startX) * percentVal);
    yVal = startY + ((stopY - startY) * percentVal);
    percentVal += stepVal;
  }
  ellipse(xVal, yVal, 20, 20);
}
Resulting Display Window at the start Resulting Display Window a little later

Random

The ability to generate random numbers is a really big deal in coding. It's a fundamental way that many natural processes, like the path of an ant crawling across the floor, can be emulated in code. Processing uses a function called random() to generate random numbers. You can control the range of random numbers with parameters between the parentheses. If there's only one parameterm the function will return a value between 0 and the parameter. So...

random(10);

Could generate random floats from 0 to (but not including) 10, like 5.5595074 (it generates 7 decimal places). But if you use two parameters, it will generate values from the first parameter to the second. So...

random(5, 10);

Returns from 5.0000000 to 9.9999999.

But what if you want an integer value and not something with a bunch of decimals? Processing provides the int(floatValue) to convert the float into an integer.

Generate Random Values

PROCESSING

Example 8-7 (7-7 in 1st Ed.)

void draw() {
  float randomVal = random(0, mouseX);
  println(randomVal);
}
Resulting Display Window Resulting Console Window

Draw Randomly

This one is kind of fun. Jiggly lines which jiggle because each time the random(-mx, mx) which will generate new random number each time it's called.

PROCESSING

Example 8-8 (7-8 in 1st Ed.)

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

void draw() {
  background(204);
  for(int xVal = 20; xVal < width; xVal += 20) {
     float myRandomRange = mouseX / 10;
     float offsetA = random(-myRandomRange, myRandomRange);
     float offsetB = random(-myRandomRange, myRandomRange);
     line(xVal + offsetA, 20, xVal - offsetB, 100);
  }
}
Resulting Display Window at start Resulting Display Window with more jiggled lines from mouse movement

Move Shapes Randomly

This one's fun too. Pretty straight forward except that since the motion of the ellipse is accumulative (that is, it moves to a new position based on a random amount each frame) it can go past the edge of the display window. To keep that from happening, we use a new function called constrain() which resets xVal and yVal to numbers within the width and height of the Display Window.

constrain() works like this:

constrain(valueToCheck, lowestAllowableValue, highestAllowableValue);

PROCESSING

Example 8-9 (7-9 in 1st Ed.)

float mySpeed = 2.5;
int myDiameter = 20;
float xVal;
float yVal;

void setup() {
  size(240, 120);
  xVal = width/2;
  yVal = height/2;
}

void draw() {
  xVal += random(-mySpeed, mySpeed);
  yVal += random(-mySpeed, mySpeed);
  xVal = constrain(xVal, 0, width);
  yVal = constrain(yVal, 0, height);
  ellipse(xVal, yVal, myDiameter, myDiameter);
}
Resulting Display Window near start Resulting Display Window after a while

Timers

Any interactive application works with whats known as Events. An Event can be user input from a mouse or keyboard, or it can be the passage of a specified amount of time. Most coding environments have some way of creating timed events. Processing can determine the amount of time that has passed since the sketch has been running, which can be tested and used to trigger and event.

The function millis() will give the number of milliseconds, or 1/1000th of a second. So 1000 milliseconds is equal to 1 second.

Time Passes

Here we'll just check the time...

PROCESSING

Example 8-10 (7-10 in 1st Ed.)

void draw() {
  int myTimer = millis();
  println(myTimer);
}
Resulting Display Window Resulting Console Window

Triggering Timed Events

When you pair checking the milliseconds with an if statement, you can set up an event trigger.

PROCESSING

Example 8-11 (7-11 in 1st Ed.)

int myTime1 = 2000;
int myTime2 = 4000;
float xVal = 0;

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

void draw() {
  int currentTime = millis();
  background(204);
  if (currentTime > myTime2) {
    xVal -= 0.5;
  } else if (currentTime > myTime1) {
    xVal += 2;
  }
  ellipse(xVal, 60, 90, 90);
}
Resulting Display Window at start Resulting Display Window waiting for timer

To make it more interesting, we can set the timer test to loop by incrementing the myTime1 and myTime2 variables with the currentTime, making the sketch repeat its actions.

  if (xVal < 0) {
     myTime1 += currentTime;
     myTime2 += currentTime;
     xVal = 0;
  }

Circular Movement

Sine Wave Values

PROCESSING

Example 8-12 (7-12 in 1st Ed.)

float myAngle = 0.0;

void draw() {
  float sinVal = sin(myAngle);
  println(sinVal);
  float gray = map(sinVal, -1, 1, 0, 255);
  background(gray);
  myAngle += 0.1;
}
Resulting Display Window at start Resulting Display Window after a while Resulting Display Window after more time Resulting Console Window

Sine Wave Movement

PROCESSING

Example 8-13 (7-13 in 1st Ed.)

float myAngle = 0.0;
float offsetVal = 60;
float scalarVal = 40;
float mySpeed = 0.05;

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

void draw() {
  background(0);
  float yVal1 = offsetVal + sin(myAngle) * scalarVal;
  float yVal2 = offsetVal + sin(myAngle + 0.4) * scalarVal;
  float yVal3 = offsetVal + sin(myAngle + 0.8) * scalarVal;
  ellipse(80, yVal1, 40, 40);
  ellipse(120, yVal2, 40, 40);
  ellipse(160, yVal3, 40, 40);
  myAngle += mySpeed;
}
Resulting Display Window at start Resulting Display Window near the middle Resulting Display Window neart the end

Circular Motion

PROCESSING

Example 8-14 (7-14 in 1st Ed.)

float myAngle = 0.0;
float offsetVal = 60;
float scalarVal = 30;
float mySpeed = 0.05;

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

void draw() {
  float xVal = offsetVal + cos(myAngle) * scalarVal;
  float yVal = offsetVal + sin(myAngle) * scalarVal;
  ellipse(xVal, yVal, 40, 40);
  myAngle += mySpeed;
}

Resulting Display Window near start Resulting Display Window after a bit Resulting Display Window after one rotation

Spirals

PROCESSING

Example 8-15 (7-15 in 1st Ed.)

float myAngle = 0.0;
float offsetVal = 60;
float scalarVal = 2;
float mySpeed = 0.05;

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

void draw() {
  float xVal = offsetVal + cos(myAngle) * scalarVal;
  float yVal = offsetVal + sin(myAngle) * scalarVal;
  ellipse(xVal, yVal, 2, 2);
  myAngle += mySpeed;
  scalarVal += mySpeed;
}
Resulting Display Window near start Resulting Display Window midway Resulting Display Window near completion

Robot 6: Motion

PROCESSING

Robot 6 (Robot 5 in 1st Ed.)

float xVal = 180;       // xVal-coordinate
float yVal = 400;       // yVal-coordinate
float bodyHeight = 153; // Body height
float neckHeight = 56;  // Neck height
float radiusVal = 45;   // Head radius
float angleVal = 0.0;   // angleVal for motion

void setup() {
  size(360, 480);
  ellipseMode(RADIUS);
  background(0, 153, 204);  // Blue background
}

void draw() {
  // Change position by a small random amount
  xVal += random(-4, 4);
  yVal += random(-1, 1);
 
  // Change height of neck
  neckHeight = 80 + sin(angleVal) * 30;
  angleVal += 0.05;
 
  // Adjust the height of the head
  float neckYVal = yVal - bodyHeight - neckHeight - radiusVal;
 
  // Neck
  stroke(255);
  line(xVal+2, yVal-bodyHeight, xVal+2, neckYVal);
  line(xVal+12, yVal-bodyHeight, xVal+12, neckYVal);
  line(xVal+22, yVal-bodyHeight, xVal+22, neckYVal);
 
  // Antennae
  line(xVal+12, neckYVal, xVal-18, neckYVal-43);
  line(xVal+12, neckYVal, xVal+42, neckYVal-99);
  line(xVal+12, neckYVal, xVal+78, neckYVal+15);
 
  // Body
  noStroke();
  fill(255, 204, 0);
  ellipse(xVal, yVal-33, 33, 33);
  fill(0);
  rect(xVal-45, yVal-bodyHeight, 90, bodyHeight-33);
  fill(255, 204, 0);
  rect(xVal-45, yVal-bodyHeight+17, 90, 6);
 
  // Head
  fill(0);
  ellipse(xVal+12, neckYVal, radiusVal, radiusVal);
  fill(255);
  ellipse(xVal+24, neckYVal-6, 14, 14);
  fill(0);
  ellipse(xVal+24, neckYVal-6, 3, 3);
}
Resulting Display Window near start Resulting Display Window after more jiggling
Previous Chapter
Next Chapter