0 0
Read Time:14 Minute, 7 Second

Hey you all,

During last weekend, I was talking with a friend of mine about art procedural generation. He studies in art in University, and wanted to mess around with technologies to generate new cool art ideas. Far from being an artist myself, mostly working in the field of computer programming, I still enjoy messing around ideas, arts and generative design and patterns. In this article, I will describe some of the tools I recommend to get started.

The good news is that: We do not need Shaders! And we will use use Javascript, wich I cannot really state if is a good news or not. Anyways, let’s get started.

The beginning

We will use P5.js, the javascript equivalent to the good old Java Processing. I recommend you using the online editor, or to setup your own env. You can find the online editor here.

https://editor.p5js.org/

When you load the editor, you have already a basic script. Let’s review a little bit on how it works!

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
}

The setup function:

The first function is called setup(). This is the entry point for our program, it runs once at the beginning and then, everything will be handle by the draw loop. See it a bit like the constructors of a class in OOP(ish).

Variables that are not to be declared every frame are normally put there, such as pretty much as the settings of the program and so on. For now on, let’s only state that this runs once.

createCanvas(400, 400); makes a new canvas with the size of x and y passed in arguments.

The draw function:

This is the render call, this function runs every frames. This is were we will indicate what to draw, how to draw, makes all the real-time programming aspect of the program, and make everything that control how the frame is driven.

See it a bit like a piece of paper on wich you could draw anything, and then erase everything on that piece of paper, draw something new and so on.

background(220); makes the background with a rgb value of 220 for each channel everyframe, therefore, cleaning the background.

In action

The drawing basics

The good thing about P5.js is that we do not really needs to bother on creating for ourselves function to draw squares, triangles, circles and so on. The canvas we have will show on the right:

The set function:

Set function takes a 2d pixel position and the color. In this example, It will set 50 pixels, incrementing in both x and y axis, and set their colors to white.

function draw() {
  background(25);
  for(var i = 0; i < 50; i++)
  { 
    set(i,i, white); 
  }
   updatePixels();
}

Warning here! Do you see that the 0,0 position of the canvas is the top left corner? Some coders won’t bother, but I prefer to set my xy[0,0] at the center, P5.js provides us a very useful function to do this.

translate(x,y) -> Will translate the origin(0,0) of the canvas to where you want to set it. So let’s assume we want the origin(0,0) in the center of the canvas, on both x and y axis. We can use this function with those params.

 function draw() {
 
  background(25);
  translate(width /2, height / 2)
}

The ellipse function:

At this point, we already have a powerfull tool in our hands, we can set pixel with different color, clear the canvas and have a draw loop… This is the power of P5.js, it has a lot of the more graphics tasks under the hood, so this way, the coder or artist can focus on actually making arts, and drawing.

Another very usefull function is the ellipse function, and as the name suggest, well it draws an ellipse, let’s continue with our previous code but use this function to draw ellipse instead of set for putting pixel.

Here is the ellipse in action:

function draw() {
 
  background(25);
  translate(width /2, height / 2)
  ellipse(0,0, 5)
  

}

Ellipse function takes 3 params. X and Y position, and the size of the ellipse we want to draw(for now it’s a circle, but you could easily make in spread on x and y axis passing more parameters);

ellipse(x, y, w, [h])

As you would imagine, this creates the following ouput:

Let’s get a bit more creative

So already, we know how to set pixels, draw circles and clear the background everyframe. Now, let’s get a bit more creative and draw more object.

A basic, random output.

Let’s make a program that randomly paint circles on the canvas, with different size, different color and different position, everytime we run the program.

To start, we will write into our setup function an array of object that contains x, y and size, being randomly generated

Then, we want to draw them using the ellipse function inside the draw function.

The full code would look like this:

var pointsCount = 50;
var pointsOnCanvas = [];

function setup() {
  createCanvas(400, 400);
  console.log("I run once")
  for(var i = 0; i < pointsCount; i++)
  {
    pointsOnCanvas[i] = {"x" : random(width), "y": random(height), "size" : random(12)};
    
  }
  
  console.log(pointsOnCanvas)
}

function draw() {
 
  background(25);
 
  
  for(var i= 0; i < pointsOnCanvas.length; i++)
  {
    ellipse(pointsOnCanvas[i].x,pointsOnCanvas[i].y, pointsOnCanvas[i].size)
    
  }
  
  

}

In actions, everytime you run the program, it would generate 50 different sizes circles in randomly placed position in x and y. 😀

So a bit of recap:

What we do is simply generating a random x and y and size at every time we run the program, and display it inside the draw function. During our P5.Js journey, this way to deal with setting up stuff and then printing them would be a very useful tool.

It allow us to use randomly generated data to draw!

Let’s create color

So, at this point, let’s a bit more of color into our code white circles. And therefore, introduce some new P5.js concept to deal with colors.

When we were setting our positions and size inside the setup function we created an object that stored the randomly generated x,y and size, then pushed it into the array:

  for(var i = 0; i < pointsCount; i++)
  {
    pointsOnCanvas[i] = {"x" : random(width), "y": random(height), "size" : random(12)};
    
  }

Let’s add a bit more infos to this object by adding a color! I will use three more object members here, as a color is RGB and we will use those floating point numbers to instantiate a color object in the draw loop.

Let’s generate a random red, green and blue factor from 0 to 255 for every of our circles:

In the setup functions we now have something like this:

function setup() {
  createCanvas(400, 400);
  console.log("I run once")
  for(var i = 0; i < pointsCount; i++)
  {
    pointsOnCanvas[i] = {
      "x" : random(width),
      "y": random(height), 
      "size" : random(12), 
      "r": random(255), 
      "g": random(255), 
      "b": random(255)
    };
    
  }

The next question we need to solve is: How the hell do we create a color in P5.js, and actually print it to the canvas?

Let’s use the color

To change the way p5.js draws something on the canvas, we will need to tell it “what color are we using to draw the thing”. By default, all our circles are white, but let’s change that by adding the randomly created we created in the last part of this article.

Our good friend the fill() function acts like this: It sets the color of what we are about to draw.

Here is a little diagram of the flow of the program:

The process part actually runs every frame, and therefore could be use to the change state of what we are drawing, therefore, changing the color. Inside the draw call we assign a new color to draw by using randomly generated color we built inside the setup function

function draw() {
 
  background(25);
 
  
  for(var i= 0; i < pointsOnCanvas.length; i++)
  {
    fill(pointsOnCanvas[i].r, pointsOnCanvas[i].g, pointsOnCanvas[i].b)
    ellipse(pointsOnCanvas[i].x,pointsOnCanvas[i].y, pointsOnCanvas[i].size)
    
  }
}

It gives us this:

Full code for this section:

var pointsCount = 250;
var pointsOnCanvas = [];

function setup() {
  createCanvas(800, 800);
  console.log("I run once")
  for(var i = 0; i < pointsCount; i++)
  {
    pointsOnCanvas[i] = {
      "x" : random(width),
      "y": random(height), 
      "size" : random(12), 
      "r": random(255), 
      "g": random(255), 
      "b": random(255)
    };
    
  }
  
  console.log(pointsOnCanvas)
}

function draw() {
 
  background(25);
 
  
  for(var i= 0; i < pointsOnCanvas.length; i++)
  {
    fill(pointsOnCanvas[i].r, pointsOnCanvas[i].g, pointsOnCanvas[i].b)
    ellipse(pointsOnCanvas[i].x,pointsOnCanvas[i].y, pointsOnCanvas[i].size)
    
  }
}

For full documentation:

https://p5js.org/reference/#/p5/fill

Making stuff moves

For this part, let’s create something that moves! To use this, we will need a way to deal with time, or, something that changes every frame. We will removed everything we already cooked and start from a fresh script:

To start, let’s make a circle that moves! We won’t really care about the structure, as what we want to create is a way to make something linearly incrementing, such as time, change the position of a drawing element. We will use the ellipse function to draw a circle element, and change the position of it every frame!

Time is a shit to handle in p5.js, so I will a bit of quicky hack way around to make the amount of what we want to variable to increment every frames. I call this variable the counter. And I increment it by a small amount every frame, in the following code, it increments by 0.005 every frame.

var counter= 0;

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
  
  counter += 0.005;
 
}

Let’s now use this value to draw the ellipse into a linear motion.

var counter= 0;

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
  translate(width /2, height /2);
  ellipse(0+ counter,0 + counter,5)
  counter += 0.5;
 
}

Debug tip: Let’s draw the position of every circle we draw

A usefull trick to debug movement in p5 is to not clear the brackground every frame,


function draw() {
 // background(220);
  translate(width /2, height /2);
  ellipse(0+ counter,0 + counter,5)
  counter += 0.5;
 
}

Let’s get into circular motion:

Let’s get a more fancier by using polar coordinates to make our circle move into circular motion! I will not review all the details but in short:

We want a way to plot a x and y around a circle from a given radius:

So in polar coordinate, the x and y would be such as

x = radius*cos(angle) and y = radius * sin(angle)

To draw around the circle, we need the angle to be incrementing. The counter will make the job for now 🙂

Let’s write a bit of code that will make the x and y of our drawing into polar coordinates.


function draw() {
  background(25);
  translate(width /2, height /2);
  var radius = 100;
  var xPos = radius * cos(counter);
  var yPos = radius * sin(counter);
  
  ellipse(xPos,yPos,5)
  counter += 0.0125;
 
}

It then gives us this:

We could add more of these by making a loop and altering the radius before drawing: Inside this loop, I represent the number of circles, in this case it is 5.

Then we alter the radius by an offset multiplied by the counter of the loop

We add an offset from the radius on wich we want to draw the ellipse based on the counter!

function draw() {
  background(25);
  translate(width /2, height /2);
  var radius = 0;
  var offset = 20
  for(var i = 0;  i<5; i++)
  {
      var xPos = (radius + (offset * i)) * cos(counter);
      var yPos = (radius + (offset * i)) * sin(counter);
      ellipse(xPos,yPos,8)
  
  }
  counter += 0.0225;
 
}

It gives us that:

At this point, what about changing the amount of angle of those circle? That would make the result a bit more dynamic and natural. To do this, we need to alter the angle by certain amount. Let’s remember the formula:

x = radius*cos(angle) and y = radius * sin(angle)

From now, we only manipulated the radius with this little radius modification

x = radius+(i * offset)*cos(angle) and y = radius + (i * offset) * sin(angle)

Do make the position of the circles changes each of these circlular patterns, we need to change the angle, that for now, is only set to a constant amount.

So in our notations, that would look like this:

x = radius+(i * offset)*cos(angle * something) and y = radius + (i * offset) * sin(angle * something)

Where something needs to be the same for both x and y angle amount. I will use the i of the loop to make it in action. But as we are dealing with trigo, let’s reduce it by dividing it by 2, or multiplying it by .5, as multiplication is quicker in js.

By mutliplying we got the whole radius to change in speed for every iteration of over the radius:

function draw() {
  background(25);
  translate(width /2, height /2);
  var radius = 0;
  var offset = 20
  for(var i = 0;  i<5; i++)
  {
      var xPos = (radius + (offset * i)) * cos(counter * (i * 0.5));
      var yPos = (radius + (offset * i)) * sin(counter * (i * 0.5));
      ellipse(xPos,yPos,8)
  
  }
  counter += 0.0225;
}

Let’s see the result:

Change radius but keep the speed constant:

You might want to set the radius to different size, but keep the speed the same, to do this, we need to rearrange a bit the math by not multiplying by the radius amount by adding it.

x = radius+(i * offset)*cos(angle + (something * (i * 0.05))) and y = radius + (i * offset) * sin(angle + (something * (i * 0.05)))

Where something is the radius offset, in i loop index.

  for(var i = 0;  i<6; i++)
  {
      var xPos = (radius + (offset * i)) * cos(counter + (i * 0.5));
      var yPos = (radius + (offset * i)) * sin(counter + (i *  0.5));
      ellipse(xPos,yPos,7)
  
  }

Not that we have an offset from when the angle is 0:

The angle is driven by the addition of the radius position of the circle we draw, so it does not resolves into a perfect circular motion but a spiral-ish result:

Full code for this sketch:

var counter= 0;

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(25);
  translate(width /2, height /2);
  var radius = 0;
  var offset = 12
  for(var i = 0;  i<10; i++)
  {
      var xPos = (radius + (offset * i)) * cos(counter + (i * .5));
      var yPos = (radius + (offset * i)) * sin(counter + (i *  0.5));
      ellipse(xPos,yPos,7)
  
  }
  counter += 0.0225;
 
}

Make it procedural:[title]

With our circular approach, let’s create something a bit more procedural by making x and y dynamic:

Let’s make it quite standard to what we had written so far:

var counter= 0;

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(25);
  translate(width /2, height /2);
  var radius = 0;
  var offset = 12
  for(var i = 0;  i<10; i++)
  {
      var xPos = (radius + i * offset) * cos(counter);
      var yPos = (radius + i * offset) * sin(counter);
      ellipse(xPos,yPos,7)
  
  }
  counter += 0.0225;
 
}

Boring stuff, let’s make a function that calculate a new x and y position based from what we feed it. As you can see from the code, we use the (radius + i * offset) as a standard for both x‘ and y‘ new function:

In code, we define it as this to make it simply returns a copy from what we feed them:

function newX(oldx)
{
  return oldx;
  
}

function newY(oldy)
{
  return oldy;
  
}

Then inside the render loop we can call them with the previous x and y:

      var xPos = newX(radius + i * offset) * cos(counter);
      var yPos = newY(radius + i * offset) * sin(counter);

So at this point, we could fuck around the x and y with whatever we want, inside the draw loop we define how we design the core drawing routine and then alter it from each x’ and y’ functions.

We can also alter the x and y by calling x and y together:


function newX(oldx)
{
  return oldx + sin(oldx * 12.);
  
}

function newY(oldy)
{
  return oldy * cos(oldy * 24. + sin(newX(oldy + counter * .5)));
  
}

or in full:

var counter= 0;

function setup() {
  createCanvas(400, 400);
}

function newX(oldx)
{
  return oldx + sin(oldx * 12.);
  
}

function newY(oldy)
{
  return oldy * cos(oldy * 24. + sin(newX(oldy + counter * .5)));
  
}
function draw() {
  background(25);
  translate(width /2, height /2);
  var radius = 0;
  var offset = 12
  for(var i = 0;  i<10; i++)
  {
      var xPos = newX(radius + i * offset) * cos(counter);
      var yPos = newY(radius + i * offset) * sin(counter);
      ellipse(xPos,yPos,1 * i)
  
  }
  counter += 0.0225;
 
}

Wrapup:

A this point, we defined the core concept of procedural.

Happy
Happy
0 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %

Average Rating

5 Star
0%
4 Star
0%
3 Star
0%
2 Star
0%
1 Star
0%

Leave a Reply

Your email address will not be published.