Animation and Wrapping Up

Animation in terms of graphical programming is simply the act of redrawing an object in a different location or position repeatedly to give the illusion of movement. One can use procedural generation techniques to accomplish this as well. In fact, it’s hard not to use procgen when attempting to programmatically animate something. Today, we will animate a ball moving along the y-axis of a canvas – very simple! While it is very simple, we will still be demonstrating the basic concepts of procgen for animation.

Here is the code:

var x, y;

function setup() {
  createCanvas(720, 400);
  // Starts in the middle
  x = width / 2;
  y = height;
}

function draw() {
  background(200);
  
  // Draw a circle
  stroke(50);
  fill(100);
  ellipse(x, y, 24, 24);
  
  // Jiggling randomly on the horizontal axis
  x = x + random(-1, 1);
  // Moving up at a constant speed
  y = y - 1;
  
  // Reset to the bottom
  if (y < 0) {
    y = height;
  }
}

This code creates our canvas and sets us up to draw in it’s center. After colors are declared, our circle is created with a diameter of 24 pixels. Then we make a call to our random function to help create small-scale movement along the x-axis before moving our ball upwards. The movement remains constant along the y-axis. We also include a check to bring our ball to the bottom of our canvas if we go off-screen (which will eventually happen) as to run the animation indefinitely.

The original code with a working example for this post can be found here.

This post marks the end of my series on procedural generation. Many programmers around the world employ this technique to accomplish a wide range of goals from simple data generation to modeling real systems like weather patterns and statistical analytics.

An Introduction to Randomness

This is a very simple topic, but it’s applications are endless. Thus, I thought it warranted a post. Randomness is something that is used everywhere in programming for simple simulations of dice rolls or more complex concepts such as initializing systems with a seed to produce consistent results. This post again uses P5js. Since the code is so simple, let’s just do a quick walk-through.

function setup() {
  createCanvas(710, 400);
  background(255);
  strokeWeight(20);
  frameRate(2);
}

function draw() {
  for (var i = 0; i < width; i++) {
    var r = random(150);
    stroke(r);
    line(i, 0, i, height);
  }
}

This creates a canvas element with a white background and set’s the frame rate to be 2 – meaning it will only call the draw function twice per second. Every time the draw function is called, a new image will be displayed on the canvas so anything more than this would make it too difficult to see the results. The draw function creates a field of bars that go from the top of the canvas to the bottom with each one set to a different shade of gray.

Again, it is a very simple example of randomness, but the applications are endless. Most applications involve some sort of randomness so it is important for the average user to understand that their favorite applications and games are just a pseudo-random number generator determining the final outcome. Suddenly that “magic” seems a lot less impressive.

The code above can be found here with a running example.

Simulating Mathematical Functions

Mathematical (trigonometric) functions such as sine, cosine, tangent, and their derivatives are commonly used in the world of programming. The art of programming is merely applying mathematical concepts to accomplish a goal – procedural generation is no different. In fact, one is more likely to come across these functions when reviewing concepts for procedural systems, especially more advanced ones such as L-systems. This post covers the sine function and how it can be represented via graphical means. To start, it’s important to note what the sine function does.

According to Wikipedia, the sine function:

is a trigonometric function of an angle. More generally, the definition of sine (and other trigonometric functions) can be extended to any real value in terms of the length of a certain line segment in a unit circle.

This might seem like a lengthy or dry explanation, but as long as you think of sine as a wave on a graph that oscillates between -1 and 1 indefinitely, you’ll be able to understand the content of this post. That’s the behavior that we are looking to model. Again, we will be using P5.js to accomplish this.

Here is the code below:

var diameter; 
var angle = 0;

function setup() {
  createCanvas(710, 400);
  diameter = height - 10; // height is 400 as per our call to createCanvas() above
  noStroke();
  fill(255, 204, 0);
}

function draw() {
  background(0);

  var d1 = 10 + (sin(angle) * diameter/2) + diameter/2;
  var d2 = 10 + (sin(angle + PI/2) * diameter/2) + diameter/2;
  var d3 = 10 + (sin(angle + PI) * diameter/2) + diameter/2;
  
  ellipse(0, height/2, d1, d1);
  ellipse(width/2, height/2, d2, d2);
  ellipse(width, height/2, d3, d3);
  
  angle += 0.02;
}

Without getting too technical or math intensive, let’s begin a brief overview of this code.

First we initialize two global variables that will be the base of our logic for the entire program. Then we call our setup()function to create a canvas of 710 pixels wide by 400 pixels tall and declare the color to draw with as a deep yellow. Because P5js calls draw()repeatedly, we don’t need to base any of our logic in a loop, it’s already going to act as though we have a loop. Regardless, we simply say that our background should be black and then we create three variables with a calculated diameter, aptly named d1, d2, and d3. We then use these to draw three circles that will use those diameters in the middle and edges of the of canvas. Finally, we increment our angle variable by a small amount and that will actually produce the oscillating wave effect/behavior that we desire. We increment the angle variable because this is the base of our sine function. The angle that we use will determine the real number that we receive from our sine function call. Because the sine wave only includes all real number values between -1 and 1, we can be sure that whatever our angle is, there is a mapping from the sine function to a usable value. You can read more on this concept here as going too much more in depth with it is beyond the scope of this post.

The original code with a working example can be found here.

 

Terraforming with Code, an Introduction to Terrain Generation for use in Games

This post is heavily inspired by Daniel Shiffman’s coding challenge in which he managed to generate random terrain in less than 60 lines of JavaScript. You can find a link to the video in which he does the same in another language, Processing, here. Daniel Shiffman is a wonderful teacher, innovator, and maven within the programming world and contributes much of his time to help shape the minds of programmers everywhere, newcomers and seasoned veterans alike.

Now onto the real meat of this post – 3D terrain generation. For this example of procedural generation, we will be looking at Daniel Shiffman’s example of terrain generation using a library that he created for rendering graphics on the web, P5.js. P5.js is a lightweight graphics library that leverages the HTML5 canvas element to easily create complex images within the browser. It can create both 2D and 3D graphics as we will see shortly.

Without getting into details about how to setup the environment for P5.js, let’s assume we have a fully configured environment in which we can start out work from.

The first thing that we need to do is create the required setup()and draw()functions that P5.js looks for and runs. The setup function is used to instantiate a new environment in which we will draw. Our draw function will be called continuously, multiple times per second. Typically we call our draw function between 30 and 60 times every second, but the amount of computations required for each iteration will ultimately decide how many times it is called. Knowing that, we can safely say that the code will run differently depending on the host system running it, i.e. your computer. The required function stubs will look as follows.

function setup() {
    // setup code goes here
}

function draw() {
    // draw code goes here
}

Next, we’ll need some information about the environment in which we aim to create. Since we are looking to generate terrain that our camera scrolls over, we should create our environment with more space than our actual canvas viewport accounts for. This is done as follows.

var cols, rows;
var scl = 20;
var w = 1400;
var h = 1000;

var flying = 0;

var terrain = [];

function setup() {
  createCanvas(600, 600, WEBGL);
  cols = w / scl;
  rows = h/ scl;

  for (var x = 0; x < cols; x++) {
    terrain[x] = [];
    for (var y = 0; y < rows; y++) {
      terrain[x][y] = 0; //specify a default value for now
    }
  }
}

Now we have created our environment which will allow us to see in a perfect 600×600 pixel square, but our actual generated terrain will stretch over 1400 pixels wide by 1000 pixels high. This allows us to stretch out far enough that we ensure the entire canvas has enough terrain on it. We also determine the number of columns and rows to generate by dividing the height and width by a scale factor of 20. This leaves us with nice even numbers to work with. Finally the data structure to hold our terrain, in this case a multi-dimensional array, is initialized with empty values.

Our draw function is where the real bulk of the work takes places and is what actually generates and displays the terrain on our screen. It is important to note that while setup()is called once and only once, draw()is called many times a second. This distinction means that once we have our data structure set up, we can begin procedurally generating our terrain within draw().

After completion, our draw()function will look something like this:

function draw() {

  flying -= 0.1;
  var yoff = flying;
  for (var y = 0; y < rows; y++) {
    var xoff = 0;
    for (var x = 0; x < cols; x++) {
      terrain[x][y] = map(noise(xoff, yoff), 0, 1, -100, 100);
      xoff += 0.2;
    }
    yoff += 0.2;
  }


  background(0);
  translate(0, 50);
  rotateX(-PI/3);
  fill(200,200,200, 50);
  translate(-w/2, -h/2);
  for (var y = 0; y < rows-1; y++) {
    beginShape(TRIANGLE_STRIP);
    for (var x = 0; x < cols; x++) {
      vertex(x*scl, y*scl, terrain[x][y]);
      vertex(x*scl, (y+1)*scl, terrain[x][y+1]);
    }
    endShape();
  }
}

The first thing we see being done here is we decrement our previously declared flying variable and assign its new value to a new variable called yoff, which stands for y-offset. This y-offset will be used to move the grid that we generate towards the bottom of our screen which will give the impression that our camera is moving over the terrain. Next we something similar to our x-offset and the immediately call a P5.js function, noise(), that implements the Perlin Noise algorithm to produce a range of values that are close in relation to the previously generated value. This is important because Perlin Noise is an algorithm that mimics nature. Without this algorithm implemented, we would have terrain that looked unnatural – we might have one point be 300 pixels high while the next point is -500 pixels below its sibling. Perlin Noise ensures that our terrain more closely simulates nature rather than just being completely random.

Below that code we find a number of P5.js library functions that begin drawing things to our screen. The background(0)call simply makes the background black, translate(0, 50) moves the entire buffer that we are drawing to upwards, and rotate(-PI/3)rotates our buffer along the x axis with respect to the mathematical constant pi. fill(200, 200, 200, 50) states that our buffer should output everything to the canvas in a gray color with the opacity (transparency) set to 50%. A final translate(-w/2, -h/2)translates the buffer halfway in each direction as to draw from the center of the canvas every time rather than the default which is the upper left-hand corner of the canvas.

Finally our actual terrain is generated in the nested forloops that create a triangle strip, which can be thought of as a mesh. The inner forloop then creates a shape at each position in the terrain array we created earlier, specifying each vertex of the shape in space with regard to the values generated by our Perlin Noise calls earlier.

The final product can be found hosted on my site, here.

What is Procedural Generation?

Procedural generation is the method of computing in which one generates data dynamically using algorithmic techniques. This is prevalent in many aspects of computing from basic data generation for system testing purposes to creating complex graphics or infinite worlds within a game. In the context of computer graphics, the focus of this series of blog posts, it may also be referred to as “random generation.” Let’s explore this in more detail.

We may think of procedural generation as either a complex system for creating massive amounts of data very quickly or as a more simple method for creating something such as a random string of characters to use as a password. For example, take a look at the code below.

function generateRandomText() {
    var letters = 'abcdefghijklmnopqrstuvwxyz';
    var letters_arr = letters.split('');
    
    var numChars = Math.floor(Math.random() * 26);
    var txt = '';
    
    for (var i = 0; i < numChars; i++) {
        txt += letters_arr[Math.floor(Math.random() * 26)];
    }
    return txt;
}

This code produces a bit of random text that one can use to perform various tasks such as entering data into a form. By calling this function, we are saying that we want to generate a string of characters where the length of that string is between 0 and 25 and only includes alphabetic characters. The output of the code when called three times is shown below.

generateRandomText(); // "xtgaenpcfplgudtfaahpksa"
generateRandomText(); // "sfbqlkdrrpdpsrtrbkdwsbxtz"
generateRandomText(); // "razkvrdzw"

While this code is simple, it does display some of the basic concepts behind procedural generation such as random length generation and computational determination of data. Almost all data generated this way is entirely random. Some purists may argue that because the above code is not based off of a seed and is completely random and as such one cannot ever expect to derive the same values consistently, it is not procgen. However, for the purposes of our exploration of the topic, it fulfills the basic requirements of the technique.

To recap, we can say that procedural generation is simply a means to produce a random amount of data for use in other programs or systems. The system in which the data is to be used can vary greatly, but the core principles driving the technique are usually consistent between applications.