Generative Smooth Blobs via p5js

Alp Tuğan
11 min readNov 28, 2022

--

source: 09-Perfect blobs with curveVertex — OpenProcessing
11-Imperfect_Circles_02 — OpenProcessing

In this article, you will learn how to utilize basic trigonometric functions to create parametric geometric shapes alike blobs. If you are a complete beginner do not worry, I will explain each command line by line. We will use Processing’s p5JS in the tutorial to create an alternative version of the cover image. But it doesn’t matter whatever programming language you know. You can easily implement the concept to your own coding environment.

Outline

  1. Code editor
  2. Generating points around a circle.
  3. Create a perfect circle shape using beginsShape() and endShape() functions.
  4. Displacing the shape’s vertices using randomness.
  5. Turn it to a blueprint.
  6. Grid of generative blobs.
  7. Conclusion

1. Code Editor

openProcessing editor is good for prototyping to kickstart an idea but not stable enough for production.

Default project on openProcessing

2. Generating points around a circle

If we set the position of each point around a circular path, we need the radius of the circular path, angle between each point and the number of vertices on the path. Let’s start by declaring variables and assign their initial values;

var rad;
var res;
var angle;

function setup() {
createCanvas(800, 800); // window size
rad = 100; // radius of the circular path
res = 10; // the number of points
angle = 360 / res; // angular distance between each point

angleMode(DEGREES); // enable the Degree mode not to make calculations easier.
background(255); // background color of the window
noLoop(); // Since we don't create and animation, we can disable the loop function
}

function draw() {

}

Now, we can calculate the position of each point using a for loop in draw() function to display them on the canvas. Thanks to the basic trigonometric functions, we can calculate the cartesian coordinates of each point. We calculate position of each point according to unit circle formula.

var rad;
var res;
var angle;

function setup() {
createCanvas(800, 800); // window size
rad = 100; // radius of the circular path
res = 10; // the number of points
angle = 360 / res; // angular distance between each point
angleMode(DEGREES); // enable the Degree mode not to make calculations easier.
background(255); // background color of the window
noLoop(); // Since we don't create and animation, we can disable the loop function
}

function draw() {
for(var i = 0; i < res; i++) {
var x = rad * cos(angle * i);
var y = rad * sin(angle * i);
circle(x,y,5);
}
}

You should see the following;

This is because of the coordinate space. To move all points together on the canvas, we can use translate() function. Let’s move everything into the middle of the canvas.

function draw() {
push(); // It's a good practice to use push and pop whenevewer you translate screen coordinates
translate(width * 0.5, height * 0.5); // translate the screen coordinate from top-left to middle of the canvas
for (var i = 0; i < res; i++) {
var x = rad * cos(angle * i);
var y = rad * sin(angle * i);
circle(x, y, 5); // draw a basic circle to see everything is fine
}
pop();
}

Now everything works as we expected. We can connect each point within a line using vertex() function.

3. Create a perfect circle shape using beginShape() and endShape() functions

function draw() {
push(); // It's a good practice to use push and pop whenevewer you translate screen coordinates
noFill(); // Do not fill the shape with color. Just draw strokes
translate(width * 0.5, height * 0.5); // translate the screen coordinate from top-left to middle of the canvas
beginShape(); // start to draw custom shape
for (var i = 0; i < res; i++) {
var x = rad * cos(angle * i);
var y = rad * sin(angle * i);
circle(x, y, 5);
vertex(x,y); // add points to the custom shape
}
endShape(); // we finish adding points
pop();
}

The result will be like this;

To close the shape completely we need to add one last vertex that tells p5JS to join the last point with the first one. The image below shows how lines are created in the for loop iteratively.

After we are done with the for loop, before the endShape(), we need to add the coordinates of the 1st point as the last vertex’s position.

function draw() {
push(); // It's a good practice to use push and pop whenevewer you translate screen coordinates
noFill(); // Do not fill the shape with color. Just draw strokes
translate(width * 0.5, height * 0.5); // translate the screen coordinate from top-left to middle of the canvas
beginShape(); // start to draw custom shape
for (var i = 0; i < res; i++) {
var x = rad * cos(angle * i);
var y = rad * sin(angle * i);
circle(x, y, 5);
vertex(x,y); // add points to the custom shape
}
// use the same formula to calculate the 1st point's x and y positions by replacing i with 0.
var xs = rad * cos(angle * 0);
var ys = rad * sin(angle * 0);
vertex(xs,ys); // add the last vertex point
endShape(); // we finish adding points
pop();
}

Now it looks like Ok.

4. Displacing the shape’s vertices using randomness

We can displace the points easily by modifying the “rad” variable employing random() method. Before doing that, we will create an array of objects that holds each blob’s attributes to keep everything organized.

var rad;
var res;
var angle;
var blobObj = []; // array of objects that holds blob attributes

function setup() {
createCanvas(800, 800); // window size
rad = 100; // radius of the circular path
res = 10; // the number of points
angle = 360 / res; // angular distance between each point
angleMode(DEGREES); // enable the Degree mode not to make calculations easier.
background(255); // background color of the window
noLoop(); // Since we don't create and animation, we can disable the loop function
}

function draw() {
push(); // It's a good practice to use push and pop whenevewer you translate screen coordinates
noFill(); // Do not fill the shape with color. Just draw strokes
translate(width * 0.5, height * 0.5); // translate the screen coordinate from top-left to middle of the canvas
beginShape(); // start to draw custom shape
for (var i = 0; i < res; i++) {
rad += random(-20,20); // increase or decrease the radius of the blob randomly
// store each blob's radius, x and y coordinates into the array
blobObj.push({
"rad": rad,
"x": rad * cos(angle * i),
"y": rad * sin(angle * i)
});
circle(blobObj[i].x, blobObj[i].y, 5);
vertex(blobObj[i].x, blobObj[i].y); // add points to the custom shape
}
vertex(blobObj[0].x, blobObj[0].y); // add the last vertex point by duplicating the first point's attributes
endShape(); // we finish adding points
pop();
}
Small number of points with vertex() method

Since the number of points is small in value, we got such a geometric result. Whenever you run the code, you will get different shaped blobs. Still it doesn’t like a regular blob. We can increase the number of point in order to get smoother looking blob. But a better approach utilizing curveVertex() method can create better results. When we replace the lines including vertex() method;

vertex(blobObj[i].x, blobObj[i].y); // add points to the custom shape

to the curveVertex();

  curveVertex(blobObj[i].x, blobObj[i].y); // add points to the custom shape

You will get the following blob within missing curves.

Small number of points with curveVertex() method

To fix the issue above, this time, we need to add the 1st, 2nd and 3rd points’ coordinates after the loop finished.

function draw() {
push(); // It's a good practice to use push and pop whenevewer you translate screen coordinates
noFill(); // Do not fill the shape with color. Just draw strokes
translate(width * 0.5, height * 0.5); // translate the screen coordinate from top-left to middle of the canvas
beginShape(); // start to draw custom shape
for (var i = 0; i < res; i++) {
var randRad += min(rad, rad+random(-20, 20));
blobObj.push({
"rad": randRad,
"x": randRad * cos(angle * i),
"y": randRad * sin(angle * i)
});
circle(blobObj[i].x, blobObj[i].y, 5);
curveVertex(blobObj[i].x, blobObj[i].y); // add points to the custom shape
}
curveVertex(blobObj[0].x, blobObj[0].y);
curveVertex(blobObj[1].x, blobObj[1].y);
curveVertex(blobObj[2].x, blobObj[2].y);
endShape(); // we finish adding points
pop();
}
Smoother blob with curverVertex()

So what is the point? Why should we choose such a method? One of the answers might be the performance issues. The more points means, the more calculations which means lower performance depending on hardware specifications. For loops are good for creating complex visuals but they are not cheap for the processor of your computer.

5. Turn it to a blueprint

We have many variables and attributes now. But the main point is we want to draw blobs and display them on a grid. Let’s move everything into a class. So we can easily handle the next step. I create a new file to turn the existing code snippet to object/class. You can create a new file on openProcessing by hovering on next to “mySketch” tab as follows;

I’m not going deeper into object oriented programming (OOP) paradigm, and how to mimic the OOP using Javascript. There are many tutorials on the web. Just search within the keywords. Our main concern is to create smooth generative blobs using small amount of points.

The constructor() method accepts three arguments. “x” and “y” set the position of the generated blob, and “rad” sets the radius of the blob. I left other attributes as constants for now.

class Blob {
constructor(x, y, rad) {

this.x = x;
this.y = y;
this.rad = rad;
this.szDelta = this.rad * 0.35; // Set the displace amount 35% of the radius
this.blobObj = [];

// constants
this.res = 10; // the number of points
this.angle = 360 / this.res; // angular distance between each point
}

display() {
push(); // It's a good practice to use push and pop whenevewer you translate screen coordinates
noFill(); // Do not fill the shape with color. Just draw strokes
translate(this.x, this.y); // translate the screen coordinate from top-left to middle of the canvas
beginShape(); // start to draw custom shape
for (var i = 0; i < this.res; i++) {
var randRad = min(this.rad, this.rad+random(-this.szDelta, this.szDelta));
this.blobObj.push({
"rad": randRad,
"x": randRad * cos(this.angle * i),
"y": randRad * sin(this.angle * i)
});
circle(this.blobObj[i].x, this.blobObj[i].y, 5);
curveVertex(this.blobObj[i].x, this.blobObj[i].y); // add points to the custom shape
}
curveVertex(this.blobObj[0].x, this.blobObj[0].y);
curveVertex(this.blobObj[1].x, this.blobObj[1].y);
curveVertex(this.blobObj[2].x, this.blobObj[2].y);
endShape(); // we finish adding points
pop();
}

}

Our main sketch looks like below;

var blob; // declare a variable

function setup() {
createCanvas(800, 800); // window size

angleMode(DEGREES); // enable the Degree mode not to make calculations easier.
background(255); // background color of the window
noLoop(); // Since we don't create and animation, we can disable the loop function

// initialize the blob and assign it to Blob class
blob = new Blob(width*0.5, height *0.5, 200);
}

function draw() {
// call display() function inside blob to draw it onto canvas
blob.display();
}

5. Grid of generative blobs

We want to position each blob onto a grid. Let’s utilize nested for loops to display grid of squares. I define a new variable called “gridSz” to determine each block size and assign it 200 in my “mySketch” tab. Each of my grids will be 200x200 px (width and height).

var blob;
var gridSz; // Size of each square
function setup() {
createCanvas(800, 800); // window size

angleMode(DEGREES); // enable the Degree mode not to make calculations easier.
background(255); // background color of the window
noLoop(); // Since we don't create and animation, we can disable the loop function

gridSz = 200; // Each grid size will be 100 px w/h

blob = new Blob(width * 0.5, height * 0.5, 200);
}

function draw() {

for (var i = 0; i < width; i += gridSz) {
for (var j = 0; j < height; j += gridSz) {
rect(i,j,gridSz,gridSz);
}
}


//blob.display();
}

Next step is to create instances of “Blob” class and position the objects inside the nested for loop. I modify the variable “blob” to “blobs” and convert it to an array. Then generate all of the instances inside setup() function and draw each of the instances by calling .display() method from “Blob” class.

class Blob {
constructor(x, y, rad) {

this.x = x;
this.y = y;
this.rad = rad;
this.szDelta = this.rad * 0.35;
this.blobObj = [];
// constants
this.res = 10; // the number of points
this.angle = 360 / this.res; // angular distance between each point
}

display() {
this.blobObj = [];
push(); // It's a good practice to use push and pop whenevewer you translate screen coordinates
noFill(); // Do not fill the shape with color. Just draw strokes
//noStroke();
translate(this.x, this.y); // translate the screen coordinate from top-left to middle of the canvas
beginShape(); // start to draw custom shape
// set the stroke size lower towards corners
var d = dist(this.x,this.y,width*0.5,height*0.5);
strokeWeight(max(0,5 - d*0.009));
for (var i = 0; i < this.res; i++) {
var randRad = min(this.rad, this.rad+random(-this.szDelta, this.szDelta));

var nRad = this.rad + randRad;
this.blobObj.push({
"rad": randRad,
"x": randRad * cos(this.angle * i),
"y": randRad * sin(this.angle * i)
});
//circle(this.blobObj[i].x, this.blobObj[i].y, 5);
curveVertex(this.blobObj[i].x, this.blobObj[i].y); // add points to the custom shape
}
curveVertex(this.blobObj[0].x, this.blobObj[0].y);
curveVertex(this.blobObj[1].x, this.blobObj[1].y);
curveVertex(this.blobObj[2].x, this.blobObj[2].y);
endShape(); // we finish adding points
pop();
}

}

Conclusion

You can access the code from here. The cover visual is yet another implementation of the same idea in this tutorial. It is also available in my open processing repo. The main motivation to create such a tutorial is generating perfectly smooth imperfect circles that looks like blobs.

--

--

Alp Tuğan

Interested in sociotechnology, creative coding, generative art, and sound. Artist/lecturer