Translation and Rotation

Translation and rotation are powerful tools for mapping data to their visual representations. They are necessary in 3D modelling, in which primitive objects are typically plotted at the origin of the working frame of reference (0,0,0).

Translation changes the plotting frame of reference relative to the plotting window. To illustrate:

translate(50, 50);
rect(0, 0, 5, 5);

achieves the same result as

rect(50, 50, 5, 5);

Rotating the frame of reference simplifies many tasks that require angular operations. This example shows how rotation can be used. Note how Processing works with radian angles – degrees must be converted using radians().

translate(50, 50);
for (int i=0; i<12; i++){
  rotate(radians(30));
  text(i+1, 0, -40);
}


Using pushMatrix and popMatrix for more complex operations

Translation and rotation operations change the working frame of reference permenantly unless they are reversed. This is where pushMatrix() and popMatrix() come in. Declaring pushMatrix() makes Processing remember the current reference system by adding it ('pushing') to a list stack. These can then be removed from the stack ('popped') and reverted as the working system. Hence for each pushMatrix() operation there must be a corresponding popMatrix(). The power of this approach is that it enables objects to be plotted in any position and with any angular direction without complicating the general frame of reference.

In the example here the numbers show the sequence the algorithm has followed. The example also shows:

  • how custom functions can be defined using the void() statement; and
  • how fractal effects can be achieved in Processing by simply nesting the constructor of a class or function (in this case newNode()) within its own execution code, while ensuring a mechanism to limit the number of levels of recursion (in this case using the integer called fract.
int k = 0; // number counter

void setup(){
  size(250, 250);
  fill(0);
  stroke(0, 0, 0, 50);
  textAlign(CENTER);
  textSize(12);
  translate(width/2, height/2);
  text(k, 0, 0);
  k++;
  newNode(80, 1);
}

void newNode(float r, int fract){
  fract --;
  pushMatrix();
  for (int i=0; i<3; i++){
    rotate(radians(120));
    line(0, 0, 0, -r);
    pushMatrix();
    translate(0, -r);
    text(k, 0, 0);
    k++;
    // constructor for new instance
    if(fract >= 0) newNode(r/3, fract);
    popMatrix();
  }
  popMatrix();
}

This next example demonstrates how to use the atan2() function to determine the correct angle to rotate objects to point in the direction they are moving:

Agent[] agents;

class Agent{
  PVector p,v;
  float bearing;
  
  Agent(PVector P){
    this.p = P.get();
    this.v = new PVector();
  }
  
  void move(){
    this.v.add(new PVector(random(-.1, .1), random(-.1, .1)));
    this.v.limit(2);
    this.p.add(v);

    // make objects stay on the screen
    if(this.p.x < 0) this.p.x += width;
    if(this.p.x > width) this.p.x -= width;
    if(this.p.y < 0) this.p.y += height;
    if(this.p.y > height) this.p.y -= height;
    
    // Using translate/rotate with push/popMatrix
    pushMatrix();
    translate(this.p.x, this.p.y);
    bearing = atan2(v.y, v.x);
    rotate(bearing);
    rect(0, 0, 15, 7);
    popMatrix();
  }
}

void setup(){
  size (300, 150);
  frameRate(30);
  stroke(0);
  fill(255);
  rectMode(CENTER);
  agents = new Agent[12];
  for(int i=0; i<12; i++) {
    PVector P = new PVector(random(width), random(height));
    agents[i] = new Agent(P);
  }
}

void draw(){
  background(200);
  for(Agent a:agents) a.move();
}


Creating and retiring agents

This model creates a new agent every second, complete with start and end points and speed. The basic model is easily adapted to represent real-life data on mobile phenomena. Don't worry, retired agents don't suffer in any way.

ArrayList agents;

void setup() {
  size(300, 150);
  frameRate(30);
  agents = new ArrayList();
  background(200);
  noStroke();
}

void draw() {
  fill(200, 200, 200, 50); // semi-transparent background
  rect(0, 0, width, height);
  if(frameCount%15 == 0) agents.add(new Agent());
  
  // kills agents that have finished their journey
  for (Agent a:agents) if(!a.alive) agents.remove(this);
  
  for (Agent a:agents) a.move();
}

class Agent {
  PVector p, d, v;  // position, destination, vector
  boolean alive = true;
  float speed = random(0.5, 3);

  Agent() {
    p = new PVector(random(width), random(height));
    d = new PVector(random(width), random(height));
    v = PVector.sub(d, p);
    v.limit(speed);
  }

  void move() {
    float dist = PVector.dist(p, d);
    if (dist <= speed) alive = false; // agent arrived
    if (alive) {
      // new position
      p.add(v);
      // draw agents and destinations
      noStroke();
      fill(0);
      ellipse(p.x, p.y, 5, 5); // draw agent
      noFill();
      stroke(255);
      ellipse(d.x, d.y, 5, 5); // draw destination
      noStroke();
    }
  }
}


Notes:

  1. Instead of background() this model plots a semi-transparent rectangle each frame, which gives the agents their tails.