What makes earth cool? A lot of things, but it would be boring if it was a perfect circle. Today, in five minutes you will see how to use procedural generation to create a near infinite amount of worlds just by tweaking a few parameters. I’ll be using processing.js to create these. Lets start with a building a simple, circular, boring world and run through the basics of what the code does so we are all on the same page:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
ArrayList<PVector> points; /* PVector is just a vector; contains 2 components, this list holds the points for our world */ /* called once when program starts */ void setup() { size(100, 100); /* size of window */ frameRate(1); /* one frame per second */ points = generateEllipse(width/2, 30); /* call our function below and ask for a circle whose radius is half the size of our drawing surface, with 30 separate points */ } /* called every frame */ void draw() { background(255); /* white background color */ translate(width/2, height/2); /* center screen */ fill(50); /* gray "fill" color*/ beginShape(); for(int i=0; i<points.size(); ++i) /* loop through our points */ vertex(points.get(i).x, points.get(i).y); endShape(); } /* detail is the amount of points we will use to represent our circle */ ArrayList<PVector> generateEllipse(float radius, int detail) { ArrayList<PVector> ret = new ArrayList<PVector>(); /* our list we will return */ float spacing = TWO_PI / detail; /* slice of pi to increase below, TWO_PI is necessary for whole circle and not just one half of one */ /* loop through all of the points needed */ for(int i=0; i<detail; ++i) { float angle = spacing * i; /* how far on circle should our angle be */ ret.add(new PVector(cos(angle)*radius, sin(angle)*radius)); /* derive the x and y coords with cosine and sine of angle */ } return ret; } |

The code is fairly self explanatory; here is what we have:

Ok. Boring circle. So what we want to do is apply a modification to the points, make it seem more like a real planet. Real planets have hills and valleys; they don’t squiggle or just have randomly placed edges- they have structure.

This is where the fun happens. We are going to use Ken Perlin’s noise function (which is built into processing) in order to deform the ellipse into a more organic looking shape.

So lets write it:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
ArrayList<PVector> points; void setup() { size(500, 500); frameRate(30); } void draw() { background(255); /* map..maps a value from a-b to c-d, we will alter the points based on mouse position i also bumped up the detail to 512 so you can see changes more easily */ points = generateEllipse(width/3, 512, map(mouseX, 0.0f, width, 0.25f, 4.0f), map(mouseY, 0.0f, height, 0.25f, 2.5f)); translate(width/2, height/2); fill(50); beginShape(); for(int i=0; i<points.size(); ++i) vertex(points.get(i).x, points.get(i).y); endShape(); } /* mag or magnitude refers to the amount our noise value should apply- should it be strong (large hills) or weak (small bumps) mult or multiplier refers to how much our positions should be taken into account. a low value will result in a smooth surface, a large value equates to a very varying surface */ ArrayList<PVector> generateEllipse(float radius, int detail, float mag, float mult) { ArrayList<PVector> ret = new ArrayList<PVector>(); float spacing = TWO_PI / detail; for(int i=0; i<detail; ++i) { float angle = spacing * i; float ax = cos(angle); /* cache cos since it will be used more than once */ float ay = sin(angle); /* cache sin since it will be used more than once */ float axm = ax*mult; /* we multiply cos value by the mult param */ float aym = ay*mult; /* we multiply sin value by the mult param */ /* we apply 2d noise to the x & y positions determined in the first multiplication (ax*radius) */ ret.add(new PVector(ax*radius*(1.0+(noise(axm, aym)-0.5)*mag), ay*radius*(1.0+(noise(axm, aym)-0.5)*mag))); } return ret; } |

Ok, so what we did was add two new parameters to our `generateEllipse`

method. These control the amount of bumpiness and the strength of bumpiness.

Now drag your mouse over the planet to alter these parameters:

There, about 40 lines of code to create a basic procedurally generated planet. If this doesn’t get you excited, procedural generation is not just limited to game development. Below we will use procedural generation to create a dynamic art piece- with a little work this could easily be integrated into an animation or used in a design.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void setup() { size(400, 400); frameRate(30); noStroke(); /* we don't want to draw outlines on our shapes */ } void draw() { background(255); int amnt = 16; /* amount of triangles to display in rows and columns */ scale(width / amnt); /* zoom our drawing to fit it to screen */ for(int y=0; y<amnt; ++y) { pushMatrix(); /* stores matrix on a stack.. we pop it at the end */ if(y % 2 == 0) translate(0.5, 0, 0); /* offset every other row*/ for(int x=0; x<amnt; ++x) { /* we generate a noise value from mouse position */ float nval = noise(x/map(mouseX, 0, width, 1.0f, 5.0f), y/map(mouseY, 0, height, 1.0f, 5.0f)); if(nval < 0.5) { /* this noise function returns between 0.0 and 1.0... we only draw ones that are small*/ fill(nval*255); /* set the grayscale color to the noisevalue */ triangle(x, y, x-0.5f, y+1.0f, x+0.5f, y+1.0f); /* x1,y1,x2,y2,x3,y3 */ } } popMatrix(); } } |

Mouse over this:

As you can see, very straightforward. Noise is very useful to keep in your toolkit, whether you are a game developer or work in print; there are a lot of potential applications.

Further reading: