Chapter 6: Translate, Rotate, Scale

  1. Translate
    1. Example 6-1: Translating Location
    2. Example 6-2: Multiple Translations
  2. Rotate
    1. Example 6-3: Corner Rotation
    2. Example 6-4: Center Rotation
    3. Example 6-5: Translation, then Rotation
    4. Example 6-6: Rotation, then Translation
    5. Example 6-7: An Articulating Arm
  3. Scale
    1. Example 6-8: Scaling
    2. Example 6-9: Keeping Strokes Consistant
  4. Push and Pop
    1. Example 6-10: Isolating Transformations
  5. Robot 4: Translate, Rotate, Scale

Translate, Rotate, and Scale

Translating Location

Processing provides a very cool but a bit-tricky-to-use function called translate(). What it does is it takes the objects being drawn in the draw function and moves them relative to their original location.

We know that most objects are drawn with x,y coordinates. So if we spec a rect like this:

XY Coordinate Grid with rect(20,20,20,20)
rect(20, 20, 20, 20);

It looks like this.

We can shift the coordinates to a new position by changing the x/y coordinates, in this case by adding 60 to the right and 80 down:

XY Coordinate Grid with translated rect
rect(80, 100, 20, 20);

Or

rect(20 + 60, 20 + 80, 20, 20);

Like this.

Imagine the coordinates being on graph paper. If we use translate(), we can shift the coordinates to a new position as if we had moved the graph paper:

translate(40, 20);
rect(20, 20, 20, 20);

And, like many things, we can make it interactive!

PROCESSING

Example 6-1: Translating Location

void setup() {
  size(120, 120);
}
void draw() {
  translate(mouseX, mouseY);
  rect(0, 0, 30, 30);
}
Resulting Display Window

Multiple Translations

The nature of translate() is that it's accumulaive. That is, each time you add in a translation function, it adds to (or subtracts from) the previous offset.

PROCESSING

Example 6-2: Multiple Translations

void setup() {
  size(120, 120);
}
void draw() {
  translate(mouseX, mouseY);
  fill(#00ff00);
  rect(0, 0, 30, 30);
  translate(35, 10);
  fill(#ff0000);
  rect(0, 0, 15, 15);
}
Resulting Display Window

So in this case, the first rect is translated to the x/y position of the mouse, but the second rect is translated to 35 + mouseX and 10 + mouseY.

There may be times when you don't want translate() to accumulate. For this, Processing adds a pair of functions that allow you to do a separate translate() function on any given object you draw: pushMatrix() and popMatrix(). (Think matrix like a grid, not the movie)

PROCESSING

Example 6-2: Multiple Translations Alternative

void setup() {
  size(120, 120);
}
void draw() {
  pushMatix();
  translate(mouseX, mouseY);
  rect(0, 0, 30, 30);
  popMatrix();
  translate(35, 10);
  rect(0, 0, 15, 15);
}
Resulting Display Window

Which will move the first, bigger square with the mouse, but only move the second, smaller square over 35 and down 10

Rotations

XY Grid rotated

rotate() is a kind of translation, but instead of moving the graph paper's x/y coordinates, it rotates the graph paper around the 0,0 upper left corner of the Display Window. Of course, like the arc() function, it uses the radians method to describe the angle of rotation. (Arrgh! Who thinks in radians?).

Anyway, here's a simple example of using rotate():

PROCESSING

Example 6-3: Corner Rotation

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

void draw() {
  rotate(mouseX / 100.0); // Returns values from 0.0 to 1.20
  rect(40, 30, 160, 20);
}
Resulting Display Window

Remember, you can always use radians(someValueInDegrees) to convert from a degree to a radian value.

Center Rotation

To get an object to spin around its center you nee to center the object over the upper left of the Document Window, or 0,0. This next example will do just that:

PROCESSING

Example 6-4: Center Rotation

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

void draw() {
  rotate(mouseX / 100.0);
  rect(-80, -10, 160, 20);
}
Resulting Display Window

The value inside rotate() is between 0.0 and 1.20, and is treated as a radian value.

A Better Way Than Our Textbook

Knowing what we know about putting things in variables, and using functions like map(), let's make the example code even better!

PROCESSING

Example 6-4: Center Rotation Improved

float rectWidth = 160;
float rectHeight = 20;

void setup() {
  size(120, 120);
}
void draw() {
  float myRadians = radians(map(mouseX, 0, width, 0, 180));
  rotate(myRadians);
  rect(-rectWidth/2, -rectHeight/2, rectWidth, rectHeight);
}
Resulting Display Window

Notice that you can put a function inside a function!

radians(map(mouseX, 0, width, 0, 180));

Translation Then Rotation

To spin an object around its center point at a place in the Display Window way from the origin, first use translate() to move the location where you'd like the shape (like follow the mouseX and mouseY) then call rotate() before you finally draw the shape:

PROCESSING

Example 6-5: Translation, Then Rotation

int myWidth = 30;
int myHeight = 30;
float myAngle = 0;

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

void draw() {
  translate(mouseX, mouseY);
  rotate(myAngle);
  rect(-myWidth/2, -myHeight/2, myWidth, myHeight);
  myAngle += 0.1;
}
Resulting Display Window

Even though we're using radians as the value of myAngle, it doesn't really matter because the thing just keeps rotating. (Beware, if you let this code run for several days, it will crash when myAngle gets to be bigger 2,147,483,647 ☺ ).

 

Rotation Then Translation

The following code is the same as the previous code, except the sequence of translate() and rotate() is reversed. So the shape is rotated around the upper left of the display window first, then it has it x/y location translated.

PROCESSING

Example 6-6: Rotation, Then Translation

int myWidth = 30;
int myHeight = 30;
float myAngle = 0;

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

void draw() {
  rotate(myAngle);
  translate(mouseX, mouseY);
  rect(-myWidth/2, -myHeight/2, myWidth, myHeight);
  myAngle += 0.1;
}
Resulting Display Window

In both examples, the square is set to the center of the Display Window. In the first example we translate the position of the square, then we rotate it. In the second we rotate the square then relocate it.

Makes a big difference, doesn't it?

An Articulating Arm

In this example, we'll put together a sequence of translate() and rotate() functions to creat a linked arm that bends back and forth. Since translate() is accumulative by default, each use of it moves the position

PROCESSING

Example 6-7: Articulating An Arm

float myAngle = 0.0;
float angleDirection = 1;
float myspeed = 0.005;

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

void draw() {
  background(204);
  translate(20, 25); // Move to start position
  rotate(myAngle);
  strokeWeight(12);
  line(0, 0, 40, 0);
  translate(40, 0); // Move to next joint
  rotate(myAngle * 2.0);
  strokeWeight(6);
  line(0, 0, 30, 0);
  translate(30, 0); // Move to next joint
  rotate(myAngle * 2.5);
  strokeWeight(3);
  line(0, 0, 20, 0);

  myAngle += myspeed * angleDirection;
  if ((myAngle > QUARTER_PI) || (myAngle < 0)) {
    angleDirection *= -1;
  }
}
Resulting Display Window Resulting Display Window Resulting Display Window
 

Improved version

More joints and transparency just for fun...

PROCESSING

Example 6-7: Articulating Arm Improved

float myAngle = 0.0;
float angleDirection = 1;
float myspeed = 0.005;

void setup() {
  size(500, 500);
  stroke(0, 128);
}

void draw() {
  float armLength = 300.0;
  background(204);
  for (int i=1; i<6; i++) {
    rotate(myAngle);
    strokeWeight(armLength/3);
    line(0, 0, armLength, 0);
    translate(armLength, 0); // Move to next joint
    armLength = armLength/1.5;
    rotate(myAngle * 2.0);
  }
  myAngle += myspeed * angleDirection;
  if ((myAngle > QUARTER_PI) || (myAngle < 0)) {
    angleDirection *= -1; // Switch Directions
  }
}

This is a video of the output...

 

Scale

Like translate() and rotate(), scale() lets you modify the grid of that the objects in the display window live on.


rect(20,20,20,40);
scale(2);
rect(20,20,20,40);
scale(3);
rect(20,20,20,40);

Let's try a simple case...

PROCESSING

Example 6-8

void draw() {
  // Follow the mouse around
  translate(mouseX, mouseY);
  // Scale the rect based on the mouse x position
  scale(mouseX / 60.0);
  // Draw a square with a center of 0,0
  int squareSize = 30;
  rect(-squareSize/2, -squareSize/2, squareSize, squareSize);
}

Keeping Strokes Consistent

Scale not only affects the size, but it also affects the size of a stroke. A 1 point stroke at 200% scaling (scale(2.0);) makes it 2 points, and at 50% (scale(0.5);) makes it 1/2 a point.

PROCESSING

Example 6-9

void draw() {
  // Follow the mouse around
  translate(mouseX, mouseY);
  // Scale the rect based on the mouse x position
  scale(mouseX / 60.0);
  // Draw a square with a center of 0,0
  int squareSize = 30;
  rect(-squareSize/2, -squareSize/2, squareSize, squareSize);
}

Isolating Transformations

Sometimes you need to isolate a transform so it only affects part of your code. Use pushMatrix() and popMatrix() to wrap around the code you want to isolate.

Push and Pop

PROCESSING

Example 6-10

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);
}
resulting display window

The pushMatrix() and popMatrix() functions are always used in pairs. For every pushMatrix() you'll need to have a matching popMatrix()

Robot 4: Translate, Rotate, and Scale

This robot adds in the translate(), rotate(), and scale() functions...

PROCESSING

Robot 4

  
// Set up variables
float xVal = 60;
float yVal = 440;
int myHeadRadius = 45;
int myBodyHeight = 180;
int myBodyWidth = 90;
int myNeckHeight = 40;
// Our text included this without using it...
float easingVal = 0.04;

void setup() {
  size(360,480);
  ellipseMode(RADIUS); // draw ellipse from center
}

void draw() {
  strokeWeight(2);

  background(0, 153, 204); // Blue
  
  translate(mouseX, yVal); // Move all to myNewX
  
  // Toggle the scale of everything
  if (mousePressed) {
    scale(1.0);
  } else {
    scale(0.6);
  }
  
  // Body
  noStroke();
  fill(255, 204, 0); // Orange
  ellipse(0, -33, 33, 33); // The wheel
  fill(0); // Black
  // Calculate the rect for the body relative to the 0,0 center
  rect(-myBodyWidth/2, -myBodyHeight, myBodyWidth, myBodyHeight-33); //
  
  // Neck
  // Set the y location of the neck relative to the size of the 
  // other components
  float myNeckY = -1 * (myBodyHeight + myNeckHeight + myHeadRadius);
  stroke(255); // White
  line(12, -myBodyHeight, 12, myNeckY); 
  
  // Hair
  pushMatrix(); // Isolate translation and rotation
  // translate from the middle of the head
  translate(12, myNeckY); 
  // set myAngle to 1/30th of half way around
  float myAngle = -PI/30.0; 
  for (int i = 0; i <= 30; i++) { 
    // do these statement 30 times, rotating a bit each time
    line(80, 0, 0, 0);
    rotate(myAngle);
  }
  popMatrix(); // end isolation of translate and rotate

  // Head
  noStroke();
  // Black head circle
  fill(0); // Black
  ellipse(12, myNeckY, myHeadRadius, myHeadRadius);
  // White eyeball
  fill(255); // White
  int eyeSize = 14;
  ellipse(24, myNeckY-6, eyeSize, eyeSize);
  // Green Iris
  fill(0, 200, 200); // Green
  ellipse(24, myNeckY-6, eyeSize*0.66,eyeSize*0.66);
  // Black pupil
  fill(0); // Black
  ellipse(24, myNeckY-6, eyeSize*0.33,eyeSize*0.33);
}
    

Notice that the book overlooks that it set up an easingVal. Let's do some retrofitting by replacing (around line 21):

  translate(mouseX, yVal); // Move all to myNewX

With:

  // Ease the x location (from Ex 5-8)
  float myTargetX = mouseX;
  // Set myNewX closer and closer to myTargetX
  myNewX += (myTargetX - myNewX) * easingVal; 
  translate(myNewX, yVal); // Move all to myNewX
 
Previous Chapter
Next Chapter