Techniques for creative coding
Introduction
Programming is a tool that can be applied to an essentially infinite number of problems. Here at Warp we often turn this tool to large and ambitious problems our clients want to solve. However, it can be utilised more playfully and creatively to generate beautiful code and beautiful results.
I personally define creative coding as the application of programming techniques in the act of creation. Over time I’ve built up an approach, as well as a kind of toolkit, that I routinely break out when a creative problem presents itself. In this article I want to introduce a little bit of this approach by building a dynamic, time dependant animation on the canvas.
Setup
For the ease of setup I often use a library called p5js, which can sweep away much of the boilerplate setting a drawing environment for the canvas. It’s by no means a a real dependency: anything in this article can easily be achieved with a little more code using the native canvas API, but I’m going to use p5js because it let’s you get going a lot faster.
Points, Lines and Shapes
When drawing on the canvas you’re mainly working with primitive objects, like points, lines, or polygons (shapes defined by a set of points).
In p5 a point and a line can easily be drawn with the following code:
Here we set up a 500x500 canvas, draw a black background and then a point and a line using functions of the same name. Easy. P5 has many nice functions to make life easier, including some for drawing shapes like rectangles and triangles, however I’ve found that I like to have one function that can create arbitrary polygons. I call that function poly and define it like this:
Let’s check it works by creating a square using that function and drawing a point at each corner. I’m adding a little extra stroke weight so the points are easier to see:
If you’re looking at this and thinking that it’s strange to use a radius in the context of a square, that’s a fair point! However if you go ahead and change the number 4 to a higher number like for instance 100, you’ll see that what appears on screen starts to approximate a circle! The poly function is really just letting us create very low resolution circle.
So why is this kind of approach better than p5’s rect()? Because this function just gives us back the data, and let’s us separate it from the actual drawing. Now we’ve got the data we can draw that square any way we want! We drew a point there, but let’s draw circles instead. For this we will use a p5 function: ellipse().
From our humble square we now have a pattern of meeting circles. Change the number of sides to 10 and you get a very beautiful kind of flower pattern. Note: The flower pattern is related to our data, but it’s not the data. Try doing that with the rect() function!
Modulation
Decoupling data and the way the data is used is one of the first big secrets in creative coding. In fact it’s key factor in every kind of software problem. Another one is to identify where your systems variables are. Once you find them, you can begin to modulate them.
If we look back into the previous example, we can already isolate some variables. For a start, we are drawing our square at (250, 250) — the centre of the screen. The width and height are already isolated as named variables, so we can replace our (250, 250) with (w/2, h/2). And since they are a pair of coordinates, let’s represent them like we do with the points coming from poly(): as an array. Number of sides is also a variable, so let’s pull that out and call it n. Finally radius we used in poly() and the radius we gave to ellipse() could also both be variables:
In p5 the draw function is run one time per frame, around 30 times per second. The number of frames that have passed are placed in a special variable called frameCount. This will be our first and simplest modulation. Let’s set shapeRadius to frameCount, so it get’s bigger as time goes on.
Cool, but not that impressive since after a while it’s just too big. We need some way to put bounds on it. There are many techniques you can use for this, and some situations work better than others. A simple one is the modulus operator (%), or remainder division. For example, a % b is asking the question “if I divide a by b, how much is left over?”. If we make b the number of frames we want in an animation, and a is our frameCount, then we can have our radius start at 1, go up to some maximum, and then back to 1 again:
So now the flower starts small and expands outwards like a firework, over and over every time frameCount becomes a multiple of animationFrames. That’s better than infinite expansion, but we can do better again! Instead of collapsing instantly, we can shrink smoothly and grow again as wave. For that we need to visit some famous math functions.
Sin and Cos
Sin and Cos are special functions which you’ve definitely bumped into before. They are used in geometry extensively, and people typically associate them with triangles, circles, and music.
Without getting very theoretical, the main thing you need to know about these functions is no matter what you put in, you’re going to get out a number in the range of -1 and 1. They do this in a continuous wave, which allows us to multiply any value by the output and get a smoothly changing value! This is illustrated with the example below:
Alright now we’re really getting somewhere! We’re putting frameCount * 0.05 into the Math.sin() function — making sure it’s between -1 and 1, and then multiplying that by 50, which gives values between -50 and 50. To offset the minus 50 we can add 50 to make it go between 0 and 100 just like in the modulus example. As the numbers that go into the sin function steadily increase, the overall output is wave-like. If you’re wondering why frameCount is multiplied by a small number, it’s because Sin works better if the gap between the numbers it gets is small. If it changes by 1 every time, that’s actually too big for Sin and you don’t get the nice, smooth, wave-like characteristics.
I think having to do the extra mental math of starting with 50, then adding sin() * 50 is a little annoying, just because you eventually want to end up with a range between 0 and 100. It’s not super readable, and it really starts to be a burden if you want your final range to go between another set of numbers like 1.2 and -0.8 for example. To solve this, we can write a new function called mapRange() which like the name implies can map a range from one set of numbers to another:
This function takes in a value, and then a and b represent the from, and c and d represent the to range. Take a pen and paper and run some numbers if you want to get a feel for it, but you can also take it from me that it does what it says on the tin!
Now we can simplify our code from before:
Mapping ranges from the Sin function is so useful that I made a special function for that, called sinMap():
More modulation
Let’s mess with another variable. The obvious target is ellipseRadius.
Super trippy! This is shaping up to be a nice little animation here. We can go a bit further though. Remember how secret number 1 was to keep the data separate? We have that data, and we can mess with that as well. Let’s do something different with every point in the shape.
So now we have a new ellipse being drawn which is similar to the first, but modulated based on where that point is in the shape array. You can consider that data manipulation, but it’s not really the data (more like the metadata). To really get at the actual data — the points in our shape — we can breakout another little tool belt that I have specifically for this purpose.
Vec-la: Vectors and Matrices
Vec-la is a tiny little library I wrote to do stuff with 2d vectors. Again, without getting deep in the math, a 2d vector is just 2 numbers which come together as a little package. In vec-la the package is just a plain old array, so the points in our shape are already considered vectors! Vectors can be added or subtracted which moves them around. They can be multiplied, which scales them up or down. But most importantly they can be transformed which another math tool called a matrix. A matrix will let you package up the idea of translation (moving), scaling, rotation, and shearing into a single operation. In vec-la, much like a vector, a matrix is just an array of numbers, and there are special functions we can use to very easily create a matrix without needing any advanced math courses.
OK let’s be more practical. I’m going to assume you’ve added vec-la to the project. To make life easier you can call vec.polute() at the top of the code to inject all the useful functions into our workspace.
Let’s rotate all the points in the shape every frame using a matrix:
A few small changes we’re introduced here. First off, we now have a zero vector which we pass to poly() instead of center like before. This is because when you perform a transformation on a vector using a matrix, it all happens relative to (0, 0). For example, when you rotate a point, it will always rotate around (0, 0). You can then tell the matrix to translate the point to the center after it’s been rotated. A little strange, but when you think about it, it’s kind of decoupling our data even more, because now we don’t care about absolute position.
Let’s go even further with the matrix and add in modular scaling.
Modulating the scale of x and y differently gives a kind of 3d effect. As a note, often when you modulate a variable based on some time multiplier, if those multipliers are each multiples of each (like 0.01 is just (2 * 0.005)), then the result is a simple repeating pattern. If they’re not multiples of each other however, the patterns can become much more complex. This can be visually interesting, but if you want to create a perfectly looping gif you have to be able to work out how long your patterns will take to finally repeat (which can be thousands of frames if you have too much variance).
Complex waves
We’ve been using waves to modulate a lot of variables, but so far they’ve been simple, predictable waves. If you add up the results of multiple Sin or Cos functions, you actually get a new, more dynamic and interesting wave.
What if we made a new matrix to rotate second set of circles the other way?
User Input
As a final step for this article, let’s add some modulation that is based on user interaction. p5 gives us access to a variable called mouseX, which as you may expect give the x coordinate of the mouse. Since we know the size and bounds of the screen, we can easily pass this variable into mapRange() and do something with it.
So now the n variable (number of sides in our polygon data) is based on input, and can snap from any value in the range 2–20. Likewise angleChange is also mapped to the mouse which let’s the user control how fast the rotations take place.
Conclusion
This has come a long way from the original square made of four points, and yet it barely scratches the surface of what could still be done. In this example alone you could add more matrices with more modulations, or draw different shapes than circles. You can use other sources of input to control modulations, like sound or the pixel data from a webcam. You could hook up to an online API and react to changes that come in (like changes on wikipedia). Really, there are a million different things you could do on this example alone.
If you’re interesting in creating something cool using techniques like these, feel free to checkout my codepen where I have a ton more examples like this, and you can fork my template that creates a skeleton with p5 and vec-la already setup. And don’t forget to send me what you make, I’m excited to see it!
Francis Stokes is a full stack developer at wearereasonablepeople, one of the fastest growing design and development agencies in The Netherlands. He is the creator of 16bitjs and the Lel programming language.