noise grid
September 1, 2021

Crafting Organic Patterns With Voronoi Tessellations

Randomness in generative art is a double-edged sword. It is a wildly powerful tool for us artists, but can be difficult to tame and sculpt into something that feels organic/balanced.

When composing generative patterns, placing objects on a canvas purely at random can feel chaotic, while aligning them to a traditional grid can feel rigid/predictable. While both chaos and exacting precision can both be beautiful qualities in generative art, we rarely — if ever — find examples of either extreme in the natural world.

In this tutorial, we will be learning how to form aesthetically pleasing patterns inspired by nature. Random and unpredictable, yet efficient and harmonious. To do so, we will be using a classic generative tool, the Voronoi tessellation.

Let’s get making!

Intended audience

This tutorial is perfect for folks familiar with generative art and comfortable working with JavaScript/SVG.

If you are new to the world of generative SVG, pop over to my starter kit to dip your toe in the ocean!

A visual overview

Before we get started, I would like to show you what Voronoi tessellations are, how they work, and how they can help form the basis of gorgeous generative patterns. Here’s a simple animated example to get us started:

To break this process down into steps:

  1. Add some { x, y } points to a 2D space.
  2. Partition the space into a collection of polygons. Each polygon should contain just one of the points added in the first step, and each of its vertices should be closer to its generating point than any other.
  3. Balance out the size of each polygon by moving its generating point towards its center point.
  4. For each polygon, compute the largest possible circle that will fit at its center point without touching any edges.
  5. Using the radius of the center circle as a guide, add a random shape to each cell.

Lovely. The shapes on the canvas exist in perfect harmony with each other and do not overlap, but they all have a random position. The resulting pattern has a sense of order while remaining unpredictable and, well, generative!

To demonstrate how Voronoi tessellations can function almost as “natural grids”, I have put together some examples. The first contains shapes placed randomly on a canvas, the second on a grid, the third on a Voronoi tessellation:

All three approaches here absolutely have their place in generative art/design, but to me, the Voronoi-based pattern looks the most organic.

Examples in nature

So, why do Voronoi tessellations appear so organic/natural to us humans? Well, from the pattern on the back of a giraffe, to cracks on the muddy ground, to the growth of a leaf’s veins — Voronoi tessellations appear everywhere in nature.

Let me show you a few examples:

In this tutorial (aside from debugging) we aren’t focused on rendering Voronoi tessellations themselves. Layouts created with them, however, can still remind us of natural phenomena — the way they are “packed” so efficiently, with purpose and meaning to their form, is reminiscent of patterns we find in the physical world.

The magic of this technique

In the “visual overview” example above, we placed our tessellations’ generating points randomly on the canvas. Random placement is neat, but the real magic here happens when we experiment with creative ways to initialize these points. Imagine, for example, that we created them based on the colors of an image, the values in a 2D noise grid, or used a Gaussian random distribution?

As a more developed example, here’s a pattern with generating points focused around the brightness values of an image.

Here is the original:

A pair of cartoon googley eyes

And here is the resulting pattern:

Notice how the output becomes more detailed near the darker pixels? Using a Voronoi tessellation, we can create patterns that are subtly — or not so subtly — influenced by an external force.

A (positive) note on performance

In addition to looking awesome, Voronoi-based patterns are also very fast to generate — creating an outcome similar to the above through “brute force” would be extremely slow in comparison.

An interactive example

All of this tessellation business can be tricky to understand at first (honestly, I still find it a little confusing sometimes!) — with this in mind, I have created an interactive example that we can use to get our bearings before writing any code. Sometimes it helps to “feel” algorithms like this work, reacting to your input rather than some abstract data.

Try clicking on the canvas above to add new points. Notice how the tessellation changes?

When you are happy with the generating points on the canvas, try dragging the “relaxation” slider. Notice how the cells all become more even in size? When we relax a Voronoi tessellation, we move each cell’s control point (a green dot) towards its centroid (a red dot) — relaxation is an entirely optional step but can go a long way towards creating balanced layouts.

For this tutorial, this is everything we need to know about Voronoi tessellations. If you would like to dive a little deeper, however, I recommend checking out Wolfram Mathworld.

Let’s code!

To let us focus on the creative application of Voronoi tessellations, rather than the math/logic behind them, I have added a createVoronoiTessellation function to my generative-utils repository.

For the practical section of this tutorial, we will be using createVoronoiTessellation to produce a simple, organic pattern that will serve as an excellent springboard for further experimentation.

Note: The following example/other demos in this article are all SVG-based, but this technique works great with any medium. We could port the code example here to HTML canvas, as an example, with no changes to the layout logic.

Project setup

To keep us focused on the code, rather than environment setup/boilerplate, I have created a CodePen template that we can fork to get started. This pen has all of the boilerplate HTML/JS/CSS needed to follow along with the upcoming practical example.

Creating a blank SVG canvas

Before we start forming our pattern, we need a blank canvas to draw on. Our canvas in this tutorial is an <svg> element, rendered using svg.js — a fantastic JavaScript library that greatly simplifies SVG scripting.

To create our canvas, we can add the following code to our JavaScript:

const width = 196;
const height = 196;

const svg = SVG().viewbox(0, 0, width, height);

svg.addTo('body');

In this snippet, we are:

  • Defining a width and height property for our canvas/drawing space.
  • Creating a new svg.js instance, passing in the width + height variables defined previously for its viewBox.
  • Adding the svg.js instance’s <svg> element to the DOM.

Initialising the generating points

As the first step towards creating our pattern, we need to define some points for our Voronoi tessellation. Let’s make that happen:

const points = [...Array(1024)].map(() => {
return {
x: random(0, width),
y: random(0, height),
};
});

Here, we generate 1024 points (an arbitrary choice) positioned randomly within the space defined by our width and height variables. If we were to visualize these points, they would look something like this:

Perfect! These will do nicely — onto the next step.

Creating the Voronoi tessellation

Now that we have some points, we can pass them, along with a few other configuration options, to the createVoronoiTessellation function:

const tessellation = createVoronoiTessellation({
// The width of our canvas/drawing space
width,
// The height of our canvas/drawing space
height,
// The generating points we just created
points,
// How much we should "even out" our cell dimensions
relaxIterations: 6,
});

From here, we can “inspect” our tessellation by rendering the outline of each of its cells. To do so, we can iterate over each cell and use svg.polygon to turn its vertices into a shape. If you like, you can add a debug variable here to toggle the tessellation outline on/off:

const debug = true;

tessellation.cells.forEach((cell) => {
if (debug) {
svg.polygon(cell.points).fill('none').stroke('#000');
}
});

Once you have added the above code, you should see something like this:

Note: We might not always want to render the cells of our base tessellation, but it can be a helpful aid when debugging positioning issues, etc.

Adding shapes to our pattern

So, we have an excellent Voronoi tessellation but no actual pattern! Let’s fix that by adding some colorful circles to our canvas:

tessellation.cells.forEach((cell) => {
if (debug) {
svg.polygon(cell.points).fill('none').stroke('#000');
}

svg
.circle(cell.innerCircleRadius * 2)
.cx(cell.centroid.x)
.cy(cell.centroid.y)
.fill(random(['#7257FA', '#FFD53D', '#1D1934', '#F25C54']))
// Reduce each circle's size a little, to give the pattern some room
.scale(0.75);
});

Our pattern should now look something like this:

In the above code snippet, we use cell.centroid to position the circles within their “parent” cell and cell.innerCircleRadius to help determine their size.

The centroid property of each cell is an { x, y } point located at its center.

The innerCircleRadius property of each cell is the radius of the largest possible circle that can sit at its center and not touch any of its edges — think of it as a rough guide for when you want to avoid overlapping objects.

Note: I often use innerCircleRadius to find the maximum possible width/height for an object, then scale it down a little to give my patterns some breathing room.

If we wanted to expand our pattern a little and draw a mixture of lines and circles, we could use the innerCircleRadius property once again to ensure each line stays within its parent cell’s edges:

tessellation.cells.forEach((cell) => {
if (debug) {
svg.polygon(cell.points).fill('none').stroke('#000');
}

// Choose either a circle or a line
if (random(0, 1) > 0.5) {
svg
.circle(cell.innerCircleRadius * 2)
.cx(cell.centroid.x)
.cy(cell.centroid.y)
.fill(random(['#7257FA', '#FFD53D', '#1D1934', '#F25C54']))
// Reduce each circle's size a little, to give the pattern some breathing room
.scale(0.75);
} else {
svg
.line(
cell.centroid.x - cell.innerCircleRadius / 2,
cell.centroid.y - cell.innerCircleRadius / 2,
cell.centroid.x + cell.innerCircleRadius / 2,
cell.centroid.y + cell.innerCircleRadius / 2
)
.stroke({
width: cell.innerCircleRadius / 2,
color: random(['#7257FA', '#FFD53D', '#1D1934', '#F25C54']),
})
.rotate(random(0, 360));
}
});

Awesome. For our first foray into Voronoi-based patterns, I think this is all we need. Our final piece should look something like this:

Beautiful! This example is quite simple, but in creating it we have covered everything we need to create endless organic patterns. From here, we can play!

Further experimentation

Before we part ways and you leave to create your very own generative patterns, I would like to show you a few more demos created using this technique, as — well, hopefully — a little inspiration.

Note: I won’t be diving into the code for the following examples, as they all follow a very similar format to the pattern we just worked on together. The key difference in each of these demos is how the generating points are initialized.

Weighted random circles

2D noise lines

Voronoi blobs!

I hope these examples show just how versatile this technique is, there truly are endless applications for Voronoi-based patterns!

Until next time…

Well, folks, that’s all for now. If you make something cool using this tutorial, please give me a shout on Twitter @georgedoescode — I love seeing what you all make!