p5js guide imperfect circle
Drawing imperfect circles

Drawing imperfect circles

written by syntaks

15 Mar 202316 EDITIONS
1 TEZ

Unlike using pen and paper, it’s trivial to draw a perfect circle using common code libraries. In p5.js, you’d do:

circle(x, y, diameter);

On the other hand, it’s difficult to draw an imperfect circle. Here, we’ll show how it can be done.

First, let’s see how we can draw points on a circle’s circumference:

function drawPoints() {
  for (let i = 0; i < numVertices; i++) {
    const rad = i * 2 * PI / numVertices;
    const x = radius * cos(rad);
    const y = radius * sin(rad);
    circle(canvasSide / 2 + x, canvasSide / 2 - y, canvasSide * .05);
  }
}

This renders:

Points on a circle's circumference

The basic idea is to divide one revolution (2π radians) into a fixed number of steps and draw one point per step. Here, each step is 2 * PI / numVertices radians. We can use trigonometry (see "Sine and cosine" on Wikipedia) to determine the corresponding points on the circumference.

We don’t actually want to render these points, but we can use them as vertices in a shape. curveVertex helps us draw smooth curves between points:

function drawShape() {
  beginShape();
  for (let i = 0; i < numVertices; i++) {
    const rad = i * 2 * PI / numVertices;
    const x = radius * cos(rad);
    const y = radius * sin(rad);
    curveVertex(canvasSide / 2 + x, canvasSide / 2 - y);
  }
  endShape();
}
Open shape

That's not quite what we wanted though. From drawPoints above, we know the points are placed properly. But we also know they don’t form a closed loop as we didn’t include the first point twice. Further, per curveVertex’s documentation:

The first and last points [...] guide the beginning and end of the curve.

This means our first and last points also need points to guide them. That is, we can draw the (closed) circle by duplicating a total three of the existing points. Lastly, we create the imperfection by randomly offsetting each point:

function drawImperfectCircle(noise = .1) {
  let vertices = [];
  for (let i = 0; i < numVertices; i++) {
    const rad = i * 2 * PI / numVertices;
    const x = radius * cos(rad) * random(1 - noise, 1 + noise);
    const y = radius * sin(rad) * random(1 - noise, 1 + noise);

    vertices.push({ x: canvasSide / 2 + x, y: canvasSide / 2 - y });
  }

  // Duplicate 3 vertices to close the loop and provide guides for the first and last points.
  for (let i = 0; i < 3; i++) {
    vertices.push(vertices[i]);
  }

  beginShape();
  for (let i = 0; i < vertices.length; i++) {
    curveVertex(vertices[i].x, vertices[i].y);
  }
  endShape();
}
Imperfect circle

And that’s it! Below follow a few simple sketches created using this algorithm.

feedback

stay ahead with our newsletter

receive news on exclusive drops, releases, product updates, and more