Learning curve in Generative Art

Hi all, first post here.

A little background info: One week ago I started to learn how to code in Processing. I’m not familiar with creating code, only with altering work of others. I’ve worked in python, java, javascript etc but never wrote my own code. I have been designing and building pen plotters this summer and decided I want to generate my own artworks to print on these machines. So: incentive to finally learn to work in Processing. Hurray! :eyes: I’m 35, poorly schooled and everything I do is autodidact.

To teach myself in ‘generative thinking’ I first followed some tutorials and guides for a week and then set off to start and create my own code. What I did was look at the output generated by others (only visual, no code) and tried to generate a similar work to try and think how one could have solved it.

This is the work I’m trying to re-create (by Diana Lange):

Sadly I am limited to only one image per post (come on…) so I’ll try to explain.
When you run the code, the circles shouldn’t be falling over each other in a 90º angle. However, in my script this sometimes happens.

So far I’ve managed to make mostly everything work but I’m running into a barrier here. This question has two parts:

  1. How can I make this code smaller/more compatible?
    The code consists of a lot of lines that I think are not necessary. I have been working in functions before and plan to convert the code to functions after I solved question number 2:

  2. How can I prevent this issue with the 90º overlap from happening?

Below you can find the entire code that I’ve written, I know it’s quite bulky but please :bear: with me, I’m pretty new to this!

If my question could’ve been written more efficiently, let me know. I am autistic and this sometimes causes me to lose the big picture and focus too much on details.

Have a nice day,
Marinus

void setup() {
  pixelDensity(displayDensity()); // set resolution for retina screen on a macbook
  size(500, 500);
  background(255);
  noLoop();
}

float sS = 120; // squareSize, used in the for loop
float offS = 20; // offset (white lines in between boxes), both directions
float xLoc = 0; // x Location
float yLoc = 0; // y Location
float rSx = 100; //rectangle Size x
float rSy = 100; //rectangle Size y

void draw () {
  noStroke();
  for (float r=0; r<480; r+=sS) { // create four fows
    for (float c=0; c<480; c+=sS) { // create four columns of the four rows
      fill(#3E83A2); // blue squares
      rect(xLoc+offS+r, yLoc+offS+c, rSx, rSy); // draw the rectangles
      fill(240); // fill color for the off-white half circles

      float dice1 = random(6); // generate a random number to decide the orientation of the white half circle
      if (dice1<1) {
        float startdeg = 0;
        float enddeg = 180;
        float startrad = radians(startdeg);
        float endrad = radians(enddeg);
        fill(240);
        arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad, endrad, PIE);
      } else if (dice1<2) {
        float startdeg = 90;
        float enddeg = 270;
        float startrad = radians(startdeg);
        float endrad = radians(enddeg);
        fill(240);
        arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad, endrad, PIE);
      } else if (dice1<3) {
        float startdeg = 180;
        float enddeg = 360;
        float startrad = radians(startdeg);
        float endrad = radians(enddeg);
        fill(240);
        arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad, endrad, PIE);
      } else if (dice1<4) {
        float startdeg = 270;
        float enddeg = 90;
        float startrad = radians(startdeg);
        float endrad = radians(enddeg);
        fill(240);
        arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad, endrad, PIE);
      }
      float dice2 = random(6); // generate a random number to decide the orientation of the blue half circle
      if (dice2<1) {
        float startdeg = 0;
        float enddeg = 180;
        float startrad = radians(startdeg);
        float endrad = radians(enddeg);
        fill(#264D5F);
        arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad-PI, endrad-PI, PIE);
      } else if (dice2<2) {
        float startdeg = 90;
        float enddeg = 270;
        float startrad = radians(startdeg);
        float endrad = radians(enddeg);
        fill(#264D5F);
        arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad-PI, endrad-PI, PIE);
      } else if (dice2<3) {
        float startdeg = 180;
        float enddeg = 360;
        float startrad = radians(startdeg);
        float endrad = radians(enddeg);
        fill(#264D5F);
        arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad-PI, endrad-PI, PIE);
      } else if (dice2<4) {
        float startdeg = 270;
        float enddeg = 90;
        float startrad = radians(startdeg);
        float endrad = radians(enddeg);
        fill(#264D5F);
        arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad-PI, endrad-PI, PIE);
      } 
    }
  }
}

ps added comments in the code so you don’t have to sift through to understand it

3 Likes

not an exact solution but it does solve the overlap and condenses the code to be a bit more manageable. sorry i can’t be of more help.

void setup() {
  pixelDensity(displayDensity()); // set resolution for retina screen on a macbook
  size(500, 500);
  background(255);
  noLoop();
}

float sS = 120; // squareSize, used in the for loop
float offS = 20; // offset (white lines in between boxes), both directions
float xLoc = 0; // x Location
float yLoc = 0; // y Location
float rSx = 100; //rectangle Size x
float rSy = 100; //rectangle Size y

void draw () {
  noStroke();
  float startrad = 0;
  float endrad = 0;

  for (float r=0; r<480; r+=sS) { // create four fows
    for (float c=0; c<480; c+=sS) { // create four columns of the four rows
      fill(#3E83A2); // blue squares
      rect(xLoc+offS+r, yLoc+offS+c, rSx, rSy); // draw the rectangles

      int orientation = int(random(4));
      if (orientation == 0) {
        startrad = radians(0);
        endrad = radians(180);
      } 
      else if (orientation == 1) {
        startrad = radians(90);
        endrad = radians(270);
      } 
      else if (orientation == 2) {
        startrad = radians(180);
        endrad = radians(360);
      } 
      else if (orientation == 3) {
        startrad = radians(270);
        endrad = radians(450);//:p
      }
      
      //you could just add a random percentage below which these might not be drawn etc
      fill(240);
      arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad, endrad, PIE);     

      fill(#264D5F);
      arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad+PI, endrad+PI, PIE);
    }
  }
}

2 Likes

I’d learn objects and just vary how each thing is drawn a little bit.

color[] cs = { color(0,50,100), color(200), color(0,0,200), }; 

class Cell {
  float x, y;
  boolean is_rotated;
  int ca, cb;
  Cell(float ix, float iy) {
    x = ix;
    y = iy;
    is_rotated = random(1) < .5;
    ca = int(random(cs.length));
    cb = int(random(cs.length));
  }
  void draw() {
    pushMatrix();
    translate(x, y);
    rectMode(CENTER);
    fill(cs[2]);
    rect(0, 0, 100, 100);
    if( is_rotated ){ rotate(-HALF_PI); }
    
    fill(cs[ca]);
    arc(0,0,80,80,0,PI);

    fill(cs[cb]);
    arc(0,0,80,80,PI,TWO_PI);
    
    popMatrix();
  }
}

Cell[] cells = new Cell[16];

void setup() {
  size(600, 600);
  int t = 0;
  for ( int j = 0; j < 4; j++) {
    for ( int i =  0; i < 4; i++) {
      cells[t] = new Cell(120 * (i-1.5), 120 * (j-1.5) );
      t++;
    }
  }
}

void draw(){
  background(255);
  translate(width/2, height /2 );
  noStroke();
  for( int i = 0; i < cells.length; i++){
    cells[i].draw();
  }
  noLoop();
}
3 Likes

Thanks a lot, I managed to add a probability of 0.3 by doing a random and then an if statement.

      //you could just add a random percentage below which these might not be drawn etc
      if (random(1)>whiteProb) {
        fill(240);
        arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad, endrad, PIE);
      }

      if (random(1)>blueProb) {
        fill(#264D5F);
        arc(xLoc+offS+r+(rSx/2), yLoc+offS+c+(rSy/2), rSx-30, rSy-30, startrad+PI, endrad+PI, PIE);
      }
1 Like

I couldn’t avoid playing with this :slight_smile:

int gridSize = 5;
int spacing = 2;
void drawThing(float x, float y) {
  // push/pop matrix avoid translations and rotations
  // accumulating, so each time we call drawThing()
  // we start with no translations / rotations
  pushMatrix();

  float sz = width / (gridSize + 1.0) - spacing;

  // move the origin (the 0,0 position)
  // to where we want it. Then we can draw
  // rectangles and arcs at the new 0,0 position.
  translate(
    map(x,  0, gridSize-1,  sz, width-sz), 
    map(y,  0, gridSize-1,  sz, width-sz));
    
  // draw rectangle background
  fill(#326c89);
  rect(0, 0, sz, sz);

  // make the arcs 70% the size of the rectangle
  sz *= 0.7;

  // random rotation for the shape
  // Try also with 45!
  rotate(radians(90) * floor(random(4)));

  // Maybe draw the bright side
  if (random(1) < 0.5) {
    fill(#ededed);
    // sometimes full, sometimes half circle
    float deg = random(1) < 0.2 ? 360 : 180;
    arc(0, 0, sz, sz, 0, radians(deg));
  }

  rotate(radians(180));

  // Maybe draw the dark side
  if (random(1) < 0.5) {
    fill(#1c3d4d);
    arc(0, 0, sz, sz, 0, PI);
  }

  popMatrix();
}
void drawGrid() {
  rectMode(CENTER);
  noStroke();
  background(#ededed);
  for (int x=0; x<gridSize; x++) {
    for (int y=0; y<gridSize; y++) {
      drawThing(x, y);
    }
  }
}
void setup() {
  size(500, 500);
  drawGrid();
}
void draw() {}
void keyPressed() {
  drawGrid();
}

Main points

  • have one method to draw a grid, and a second method to draw a “thing”.
  • use translate() and rotate() to simplify calculations.
  • make sizes relative so you can change the window size and the design still works.
  • configurable column count (gridSize).

There’s so many ways to do this :slight_smile:

5 Likes

Thanks a lot, last week I had a lot of trouble trying to get six sketches to run on the same canvas because translate was giving me a hard time with accumulations. Even when I wanted an item to move on the X-axis I had to change the Y-value in translate. pushMatrix(); and popMatrix(); are really useful in this sense. I learned the most from your example so far! It is very elegant as well and I like the use of relative values.

Grid layouts are common in generative art. A common place to start is with a lot of hard-coded values:

size(500,500);
for (float r=0; r<480; r+=120) {
  for (float c=0; c<480; c+=120) {
    rect(0+20+r, 0+20+c, 100, 100);
  }
}

In your example you already started moving some of these numbers into named global variables:

float sS = 120; // squareSize, used in the for loop
float offS = 20; // offset (white lines in between boxes), both directions
float xLoc = 0; // x Location
float yLoc = 0; // y Location
float rSx = 100; //rectangle Size x
float rSy = 100; //rectangle Size y
size(500,500);
for (float r=0; r<480; r+=sS) { // create four fows
  for (float c=0; c<480; c+=sS) { // create four columns of the four rows
    rect(xLoc+offS+r, yLoc+offS+c, rSx, rSy);
  }
}

At this point lots of little related values are all interconnected – any one change breaks the others if you want a 5x3 grid, or a different gap, or a larger canvas. This makes generative art hard, because saying “now I want to try 3x3” or “now I want to try 1200x800px” should be easy, but it isn’t. The goal is to code your design so that when you change variables, the design changes.

So, try defining some of your variables in terms of others – for example, basing things on rows and columns, plus your gap, and using those as your inputs.

int rows = 4;
int cols = 4;
float off = 20; // offset (white lines in between boxes)
float bw = 100; // box width
float bh = 100; // box height
size(500,500);
for(int r=0; r<rows; r++){
  for(int c=0; c<cols; c++){
    float x = off + c * (bw + off);
    float y = off + r * (bh + off);
    rect(x, y, bw, bh);
  }
}

Now the tile size and screen size must still be changed together to fit each new layout, but it is much easier. But it isn’t quite a design: “4x5 at 400x500px with 20px margins”. If you had a pencil and paper you could do that; just like on paper, don’t begin with the tile size – discover it, working backwards.

int cols = 4;
int rows = 5;
float off;
float bw, bh;

size(400, 500);
off = 20;
bw = (float)(width-off)/cols - off;
bh = (float)(height-off)/rows - off;

for (int r=0; r<rows; r++) {
  for (int c=0; c<cols; c++) {
    float x = off + c * (bw + off);
    float y = off + r * (bh + off);
    rect(x, y, bw, bh);
  }
}

This is almost there. But try changing the canvas to size(150,150); the margins become (relatively) huge because they are specified in pixels, not as a percentage or ratio of the canvas or tile size. So “20px”, was actually “0.05” (of 400px). Computer it:

int cols = 3;
int rows = 4;
float margin = 0.05;

float offw, offh;
float bw, bh;

size(300, 400);

offw = width * margin;
offh = height * margin;
bw = (float)(width-offw)/cols - offw;
bh = (float)(height-offh)/rows - offh;

for (int r=0; r<rows; r++) {
  for (int c=0; c<cols; c++) {
    float x = offw + c * (bw + offw);
    float y = offh + r * (bh + offh);
    rect(x, y, bw, bh);
  }
}

This gives you a basic “magic” design grid. It is defined by cols, rows, margin, and the canvas width/height – and everything will adjust correctly if you change any one value. It can be made more flexible by mapping it to an arbitrary rectangle gx,gy,gw,gh, or be given extra features – like a border, or cellpadding.

One tip for creating and testing designs of this kind – don’t test on a 3x3, 4x4, 5x5 etc., and don’t test on a 500x500 canvas, even if that is your end goal. Instead, test on 2x3, 3x4, or 4x5 on e.g. a 640x480 canvas. If rows and columns or width and height match then you can make subtle errors in your code that you won’t catch because the x-values and y-values are equal. This even happened in some of the Processing reference examples that were published for years with buggy code in them–nobody caught it until somebody changed it.

The next step, as demonstrated in answers above is to either:

  1. replace “rect” with a custom function that draws a single random tile
  2. replace “rect” with saving a list of tile data – either arrays, or class objects – then loop over the list and draw then later.
4 Likes

Of course, this is very nice to have, however I worked towards a certain goal, which was to exactly replicate the aforementioned work so I wasn’t focusing on getting relative values. Before this one I have made about five pieces that all use relative values and worked from the center; width/2 and height/2.

I wouldn’t want this piece to have the relative values but I do agree that it is really good to know and work out how this should be done as it is will be needed in other projects I’m planning on doing.

For my pen plotter I have been trying to reconfigure a sketch by someone else that all uses code that is produced to create GCODE with polar coordinates. I managed to convert it so my machine eats it but they haven’t used relative values everywhere so the script is hard to work with especially when the machines working area isn’t the same as the one the script is written for.

Absolutely, fair enough! If that work is always 4x4 and the pixel dimensions are fixed, then that is great.

Yes, converting somebody else’s absolute values to relative can be frustrating. That speaks to your general question on how to write generative art code more efficiently. In my personal experience even generative image sketches with a fixed layout and target size still often need to support both screen and print resolution, or sketch and fullscreen and screenshot – even if you have specific pixel values in mind for your screen output you still might want to be able to scale size() for poster printing, or run it on a high-resolution display with different pixel density etc. and still have your sketch do the “right thing.”

Thanks for at least explaining more than was asked. I do value your contribution a lot. Right now I have time to replicate and play with your solution a bit and I might turn it into another work with this new insight. So thanks for going the distance :slight_smile:

There is only one thing that I do not understand. In your script I cannot get the tiles to generate in a grid. I generate them in a diagonal. Am I missing something?

c was used twice, r unused, so you get a diagonal (because the same value is used for x and y).

2 Likes

As @hamoid says, it looks like made a typo copying one of the lines from my last example. Here is the correct line as it originally appeared:

float y = offh + r * (bh + offh);

You can also just cut-paste that entire example – it runs.

Ohhh of course! I don’t copy-paste :wink: Only rewrite copy the code line by line by hand. I have put in the c by accident and after a whole day of doing taxes kind of list my focus I guess. Thanks :slight_smile:

1 Like