CS 2, Winter 2009
Programming for Interactive Digital Arts

Jan 21: Basic Motion


Straight-line motion

We've done plenty of random wandering, so now let's have some direction to our movement. As with the random wanderer, we'll keep the position in variables called x and y. Then if we just want to move horizontally, we simply increment x by 1 each frame.

// The current coordinates of the ball
float x, y;

void setup()
{
  smooth();
  background(0);
  fill(255);
  noStroke();

  x = 0; y = height/2;   // start at left, center
}

void draw()
{
  background(0);
  ellipse(x,y,20,20);

  x += 1;  // take one step to the right
}
screenshot[applet]

How about if we want to move diagonally? Then we increment both x and y each frame. Faster? Increment them by more than 1.

// The current coordinates of the ball
float x, y;
// Step sizes
float dx=1, dy=1;

void setup()
{
  size(400,400);
  smooth();
  background(0);
  fill(255);
  noStroke();

  println("x/X: de/increase x step size");
  println("y/Y: de/increase y step size");
  println("c: go to center of window");
  
  x = width/2; y = height/2;
}

void draw()
{
  background(0);
  ellipse(x,y,20,20);

  // take a step, but stay on screen
  x += dx;
  x = constrain(x,0,width);
  y += dy;
  y = constrain(y,0,height);
}

void keyPressed()
{
  if (key=='x') dx--;
  else if (key=='X') dx++;
  else if (key=='y') dy--;
  else if (key=='Y') dy++;
  else if (key=='c') {
    x = width/2;
    y = height/2;
  }
  println("dx:"+dx+", dy:"+dy);
}
screenshot[applet]

How would we make it bounce off the wall? We already saw a form of bouncing, when we made the wanderer get darker then lighter; can you do the analogous thing with the position?

Now let's suppose we want to move from the current position toward a target, say wherever the mouse is. What steps should we take? We can calculate the differences is the x coordinates (mouseX - x) and y coordinates (mouseY - y), and take a step that's some fraction of that. If the fraction is 1, it goes right to the target; if it's smaller, it goes only part of the way. We can call the fraction the speed. Processing example Basics | Input | Easing demonstrates. I gave the follower a little personality in the following variation.

// The current coordinates of the follower
float x=0, y=0;
// State of the mouth: 0 = closed, 1,3 = half-open, 2 = open
int mouth=0;
// A parameter controlling how quickly the follower catches up
float easing=0.05;

void setup() 
{
  size(400, 400); 
  smooth();
  noStroke();  
}

void draw() 
{
  background(0);
  // Move towards the mouse
  x += (mouseX - x)*easing;
  y += (mouseY - y)*easing;
  // Switch mouth
  if (frameCount % 10 == 0)
    mouth = (mouth+1)%4;
  
  // The bait
  fill(255,0,0);
  ellipse(mouseX, mouseY, 10, 10);
  // The follower
  fill(255,255,0,200);
  if (mouth==0)
    ellipse(x, y, 25, 25);
  else if (mouth==2)
    arc(x, y, 25, 25, PI/4, TWO_PI-PI/4);
  else
    arc(x, y, 25, 25, PI/8, TWO_PI-PI/8);
}
screenshot[applet]

Notice that the follower slows down as it approach the mouse; why is that? Well, let's suppose the "speed" fraction is 1/2. Then the first frame it moves halfway to the mouse, the next frame it moves half of what's left (or 1/4 of the original distance), the next frame it moves half of what's still left (1/8 of the original), etc.

To make the mouth vary, we just keep a variable that says what the current state is, and draw the mouth different for the different states. We cycle through the states -- 0, 1, 2, 3, 0, 1, 2, 3 ... -- using the modulus (%) operator mentioned last time. We also only cycle every 10 frames, using the same technique.

Sinusoidal motion

Sinusoids oscillate between -1 and 1, as the angle goes from 0 to 360 degrees. A sketch by Greenberg, modified here, illustrates.

screenshot[applet]

The top curve is a sine and the bottom a cosine. Note that this might look upside down, since the y axis increases from top to bottom here.

Recall from math class that when a point on a unit circle (centered at the origin, with radius 1) is at a particular angle, its x coordinate is the cosine of that angle, while its y coordinate is the sine of that angle. You can see that in the above sketch, viewing the top curve as the y coordinate and the bottom curve as the x coordinate of the ball moving around the circle.

angle

Processing provides the sin() and cos() functions to compute sines and cosines. They expect their parameters to be in radians (where 2π is 360 degrees); we can convert from degrees to radians with the radians() function. Since π is at the heart of radians, and you might not have it memorized, Processing provides PI (180 degrees), TWO_PI (360 degrees), and HALF_PI (90 degrees).

This makes it easy to generate circular behavior. The state is the angle, and we use sine and cosine to compute the coordinates. The coordinates are centered at (0, 0), and the radius of the circle is 1. Thus we multiply the sine and cosine by the desired radius to make the circle bigger (10 in the following example), and we add in values for where we want the center to be (mouse position in the example).

float angle = 0;

void setup()
{
  background(0);
  smooth();
  noStroke();
}

void draw()
{
  background(0);
  ellipse(mouseX+10*cos(angle), mouseY+10*sin(angle), 10,10);
  angle += radians(5);   // 5 degree increments
}
screenshot[applet]

Doing a fade-away here gives a very interesting effect; give it a try.

We can also make the radius vary. If the radius gets incrementally bigger, we'll have a spiral. The following sketch spirals away from the mouse press.

float x,y;   // center of circle
float angle=0, radius=0;    // spiral around center

void setup()
{
  background(0);
  smooth();
  noStroke();
}

void draw()
{
  background(0);
  ellipse(x+radius*cos(angle), y+radius*sin(angle), 10,10);
  angle += radians(5);   // 5 degree increments
  radius += 0.1;
}

void mousePressed()
{
  x = mouseX; y = mouseY;
  angle = 0;
  radius = 0;
}
screenshot[applet]

Since the value of a sine/cosine oscillates between -1 and 1, we can use it to make objects move back and forth or up and down. As discussed above, we multiply by some number to make it oscillate between plus/minus that number, and we add in another number to make it oscilate by a given amount around that number. The following sketch is a simplified version of the Processing example Basics | Math | SineCosine.

float angle=0, da=0.1;

void setup() 
{
  size(200, 200);
  noStroke();
  smooth();
}

void draw() 
{
  background(0);
  
  fill(51);
  rect(60, 60, 80, 80);

  fill(255);
  ellipse(width/2 + 40*cos(angle), 36, 32, 32);
  ellipse(width/2 + 40*cos(-angle), 164, 32, 32);

  fill(153);
  ellipse(36, width/2 + 40*sin(angle), 32, 32);
  ellipse(164, width/2 + 40*sin(-angle), 32, 32);

  angle += da;
}
screenshot[applet]

We can similarly use the value to control other things we want to oscillate, such as size or color. For example, for size to vary between 0 and 20, we'd want to scale the sine by 10 (so it goes -10 to +10) and then add 10 to it. Give it a try.