Search code examples
processing

rotate squares with push/popmatrix and for loop


Im trying to make each square in my grid randomly rotated, so each one has a diffrent angle, but instead i rotate all of my squares. I dont understandt why, as both the rotate and the rect is drawn once at a time inside of the for loop?


int xStep = 110;
int yStep = 110;
int rectSize = 100;
int marginX = 65;
int marginY = 65;
float rando = (random(55));


void setup() {
  size(900, 900);


  noStroke();
  fill(40);
  rectMode(CENTER);
}

void draw() {
  background(255);
  for (int x=marginX; x<width; x+=xStep) {
    for (int y=marginY; y<height; y+=yStep) {

      pushMatrix();
      rotate(radians(rando));
      rect(x, y, rectSize, rectSize, 10);
      popMatrix();
    }
  }

}

Solution

  • You're on the right track using pushMatrix()/popMatrix() to isolate coordinates for each square.

    The gotcha is with rect() as it "hides" a translation.

    This lines:

    rotate(radians(rando));
    rect(x, y, rectSize, rectSize, 10);
    

    roughly works as:

    rotate(radians(rando));
    translate(x, y);
    rect(0, 0, rectSize, rectSize, 10);
    

    and (as you can see in the 2D Transformations tutorials) the order of operations is important.

    In your case, you want to translate first, then rotate (from that translated position):

    translate(x, y);
    rotate(radians(rando));
    rect(0, 0, rectSize, rectSize, 10);
    

    In conclusion, your code would look like this:

    
    int xStep = 110;
    int yStep = 110;
    int rectSize = 100;
    int marginX = 65;
    int marginY = 65;
    float rando = (random(55));
    
    
    void setup() {
      size(900, 900);
    
    
      noStroke();
      fill(40);
      rectMode(CENTER);
    }
    
    void draw() {
      background(255);
      for (int x=marginX; x<width; x+=xStep) {
        for (int y=marginY; y<height; y+=yStep) {
    
          pushMatrix();
          translate(x, y);
          rotate(radians(rando));
          rect(0, 0, rectSize, rectSize, 10);
          popMatrix();
        }
      }
    
    }
    

    Update If you want to rotate each square with a different random value you need to calculate one for each square.

    You could so something like this:

    
    int xStep = 110;
    int yStep = 110;
    int rectSize = 100;
    int marginX = 65;
    int marginY = 65;
    float rando = (random(55));
    
    
    void setup() {
      size(900, 900);
    
    
      noStroke();
      fill(40);
      rectMode(CENTER);
    }
    
    void draw() {
      background(255);
      for (int x=marginX; x<width; x+=xStep) {
        for (int y=marginY; y<height; y+=yStep) {
          rando = random(55);
          pushMatrix();
          translate(x, y);
          rotate(radians(rando));
          rect(0, 0, rectSize, rectSize, 10);
          popMatrix();
        }
      }
    
    }
    

    Pretty easy, right ? Re-calculate the value! There's a catch here too :) If you do this, random is re-calculated not just once per square, but also per frame so you'll end up with very jittery squares.

    That is you need to re-draw the same squares again and again (even though the content doesn't change). If you simply want a static image, you can skip draw() and just use setup():

    int xStep = 110;
    int yStep = 110;
    int rectSize = 100;
    int marginX = 65;
    int marginY = 65;
    
    void setup() {
      size(900, 900);
    
    
      noStroke();
      fill(40);
      rectMode(CENTER);
      
      background(255);
      for (int x=marginX; x<width; x+=xStep) {
        for (int y=marginY; y<height; y+=yStep) {
    
          pushMatrix();
          translate(x, y);
          rotate(radians(random(55)));
          rect(0, 0, rectSize, rectSize, 10);
          popMatrix();
        }
      }
    }
    

    Even "immediate mode", with no setup() will result in the same image:

    int xStep = 110;
    int yStep = 110;
    int rectSize = 100;
    int marginX = 65;
    int marginY = 65;
    
    size(900, 900);
    
    
    noStroke();
    fill(40);
    rectMode(CENTER);
    
    background(255);
    for (int x=marginX; x<width; x+=xStep) {
      for (int y=marginY; y<height; y+=yStep) {
    
        pushMatrix();
        translate(x, y);
        rotate(radians(random(55)));
        rect(0, 0, rectSize, rectSize, 10);
        popMatrix();
      }
    }
    

    Let's assume you do want draw() because you might want to use a keyboard shortcut or a mouse click to re-randomise values.

    To get around the limitation you'd need to calculate the random values for each square once, then simply retrieve these values when rendering.

    That means you'd need a structure to store each random value per square to which you write values once in setup() and read repeatedly in draw(): an array can easily solve you problem.

    Since you're using nested for loops you could use a 2D array. Just remember that x,y counter in your loop are absolute pixel coordinates and you'd need separate indices for array access. That's easy enough to: simply divide x,y by the pixel increment and it will the 'step' counter:

    int xStep = 110;
    int yStep = 110;
    int rectSize = 100;
    int marginX = 65;
    int marginY = 65;
    // store random values for each square
    float[][]randos;
    
    void setup() {
      size(900, 900);
      
      // initialise random array
      randos = new float[width / xStep][height / yStep];
      for (int x=marginX; x<width; x+=xStep) {
        for (int y=marginY; y<height; y+=yStep) {
          // calculate array indices
          int xIndex = x / xStep;
          int yIndex = y / yStep;
          // write random values
          randos[yIndex][xIndex] = random(55);
        }
      }
    
      noStroke();
      fill(40);
      rectMode(CENTER);
    }
    
    void draw() {
      background(255);
      for (int x=marginX; x<width; x+=xStep) {
        for (int y=marginY; y<height; y+=yStep) {
          // calculate array indices
          int xIndex = x / xStep;
          int yIndex = y / yStep;
          pushMatrix();
          translate(x, y);
          // read random values
          rotate(radians(randos[xIndex][yIndex]));
          rect(0, 0, rectSize, rectSize, 10);
          popMatrix();
        }
      }
    
    }
    

    The array can also be 1D, just need to count differently:

    int xStep = 110;
    int yStep = 110;
    int rectSize = 100;
    int marginX = 65;
    int marginY = 65;
    // store random values for each square
    float[] randos;
    
    void setup() {
      size(900, 900);
      
      // initialise random array
      int numSquares = (width / xStep) * (height / yStep);
      randos = new float[numSquares];
      int index = 0;
      for (int x=marginX; x<width; x+=xStep) {
        for (int y=marginY; y<height; y+=yStep) {
          randos[index++] = random(55);
        }
      }
    
      noStroke();
      fill(40);
      rectMode(CENTER);
    }
    
    void draw() {
      background(255);
      int index = 0;
      for (int x=marginX; x<width; x+=xStep) {
        for (int y=marginY; y<height; y+=yStep) {
          pushMatrix();
          translate(x, y);
          rotate(radians(randos[index++]));
          rect(0, 0, rectSize, rectSize, 10);
          popMatrix();
        }
      }
    
    }
    

    (As a next step you can encapsulate the part in setup which initialises the array with random values into a function: this would allow you reset/recalculate new random values whenever you call this function again (e.g. on mouse click/key press/etc.)