Frontier: Programming Generative Art

June 14, 2015 View Demo View Source on GitHub

The other weekend I downloaded Processing again. Processing is a language made for programming graphics built on top of Java (but can also be run in the browser through Processing.js.) It's stuff that can be done in any language that has a graphics library really, but Processing removes a lot of the boilerplate so you can just get to the programming. Processing calls programs "sketches" and its IDE a "sketchbook" to illustrate the point that you should be able to just sit down and try things. And that's what I wanted: to just make things and try things out. This is some of the stuff I learned.

Designing Fractals

I started out by making some trees. Trees, of course, are the classic fractal: a tree is essentially a branch, and every branch spouts more branches, and those branches spout more branches until your recursion limit is hit. Personally, for the style I wanted to go for, I set the recursion limit to 3, as the trees would not be large enough that smaller branches would really be seen anyway.

So, every time you make a new tree, you are really making a single branch. Then, in the constructor for that branch, it creates up to three new sub-branches. These sub-branches get put on a random side of the root and extend to a semi-random location. The randomness is to make everything less mathematical and precise and also to make the result change every time.

Here's the constructor for my Tree class, just for generating branches:

Branch(float x, float y, float w, float h, int side) {
  branches = new ArrayList<Branch>();
  
  //Generate a random end location for each branch based on what side of the tree it's supposed to be on
  if (side == BRANCH_ROOT) {
    segmentX = x+(random(w*0.6)-w*0.3);
    segmentY = y-h*0.75+(random(h*0.3)-h*0.15);
  } else if (side == BRANCH_LEFT) {
    segmentX = x-random(w*0.3);
    segmentY = y-h*0.75+(random(h*0.3)-h*0.15);
  } else if (side == BRANCH_RIGHT) {
    segmentX = x+random(w*0.3);
    segmentY = y-h*0.75+(random(h*0.3)-h*0.15);
  }
  
  //Only make sub-branches if the recursion limit hasn't been met yet
  if (level < 3) {
    for (int i=0; i<BRANCH_MAX_LEVELS; i++) { //Count up to the maximum number of sub-branches possible
      if (random(1) >= 0.7) { //Only actually make some of them
        float branchY = random(segmentY, y);
        float branchX = x + ((segmentX - x)/(segmentY - y)) * (branchY - y);
        int branchDir = random(1) > 0.5 ? BRANCH_LEFT : BRANCH_RIGHT;
        branches.add(new Branch(branchX, branchY, w*0.6, h*0.5, branchDir, level+1, time));
      }
    }
  }
}

Then I have a separate draw method which first draws an ellipse around the end of each branch (the leaves) and then draws the branches on top. It ends up looking something like this:

It's simple, but that's the style I'm going for. They end up being pretty small anyway.

Now, I started building houses the same way I was building trees. A house in the style I wanted to make is made of modular, interconnected units. How those units come together is extremely similar to how trees branch. Each unit can have different other units attached to it. Rather than simply drawing a line from root to tip, I drew a block with a roof and a random number of windows and stilts connecting them. It's a tree, with a different way of connecting branches.

How the buildings ended up looking.

random() and noise()

In any sort of randomly generated visualizations, there's going to be a lot of usage of a pseudo-random number generator. That's to be expected. It's very well suited for making a selection out of a bunch of predefined options. What about landscapes, though?

The standard random() function isn't very good for drawing outlines of landscapes. Mathematically speaking, random() is not a continuous function. That is to say, if you keep zooming in on the line y = random(x), it doesn't become smooth. This makes it not a good choice when you want to draw a smooth line. You could space out the random points and interpolate between them, but it will likely still appear jagged and unnatural.

This is where Perlin noise comes in handy. It was developed in the '80s specifically for use in computer graphics to try to make more natural looking visualizations. It is still fairly random, but it is a continuous function. The more you scale by, the smoother of a line you get.

Division by ~20 gives a more jagged, spiky appearance.

Division by ~90 smoothes it out significantly, giving the appearance of rolling hills.

I settled on creating a "spikiness" factor, which is randomly generated between 20 and 90 so that each generated landscape will be on a different spot on the scale from rolling plains to mountains.

Generative colour schemes

After getting the basic components ready, I wanted to move away from just having one preset colour scheme. I had picked the initial colours to look like a drawing I had made previously in a similar style, but now I wanted the program to make something new.

The first approach I tried was to naively pick random red, green, and blue values between 0 and 255. However, there are a bunch of really gross colours that can be made like this. Totally random RGB values don't discriminate against desaturated greys and obnoxious bright magentas. I still had a specific vision in mind for the style which included bright and saturated colours, but that fit together coherently. Totally random colours do not do this.

Instead I decided to randomly pick a colour along a manually specified gradient. Processing provides a function called lerpColor(c1, c2, amount) (linear interpolation) which essentially lets you get a blend in between two colours. The amount parameter is a float between 0 and 1, where passing 0 gives you exactly c1, 1 gives you exactly c2, and anything in between is interpolated. I ended up randomly picking a time of day (between 0 and 1) and generating colours for things like the sky by using this sort of construct:

if (time < 0.5) {
  sky = lerpColor(#B9F7D5, #57C8F0, map(time, 0, 0.5, 0, 1));
  horizon = lerpColor(#FCED42, #E365ED, map(time, 0, 0.5, 0, 1));
} else {
  sky = lerpColor(#C282E5, #1A285A, map(time, 0.5, 1, 0, 1));
  horizon = lerpColor(#82E3E5, #BFE1FC, map(time, 0.5, 1, 0, 1));
}
setGradient(0, 0, width, height, sky, horizon, Y_AXIS);

The end result is still random and yields different styles, but still has the human touch of a coherent colour scheme. Here it is in night mode:

The windows also light up at night.

Final Product

I decided it would be cool to use this on my website, so I ended up converting it from Processing to Javascript. Processing.js exists, but it has the same limitation as normal Processing where it's not made to have a dynamically resizable canvas. If I'm using something on the web, I need to be able to make it responsive, so I ended up doing most of the conversion myself.

That means I can present a live demo!

See the Pen Landscapes in JS Canvas by Dave Pagurek (@davepvm) on CodePen.