Creative coding — Day 6
Hey there. Today I wanted to spend some time understanding mathematical concepts and familiarising myself with using objects, classes and methods.
You may initially experience all sorts of feelings, thinking that maths is tedious or it's just not for you. Well, this is how I am feeling right now. Even though I feel a bit sad that I could not do another project in time, I know it is vital to spend more time understanding how code works, especially when starting out.
But I believe we will create incredible things once we understand how to apply mathematics in code, especially if you have a drive for creativity and like coding.
Bees, masters of geometry, use hexagons to build their honeycombs. The Fibonacci sequence, a famous sequence of numbers in mathematics, is found throughout nature: in pinecones, seashells, trees, flowers, and leaves. (https://www.piday.org/10-reasons-why-math-is-important-in-life/)
So if bees can apply some of the maths in their daily lives, why wouldn't we?
So let's get started.
I like the final result of this project; it reminds me of the zodiac stars.
Where can you find the tutorial to learn how to create this animation?
This is unit 4 from the Creative Coding: Making Visuals with JavaScript course by Bruno Imbrizi.
This unit explores sketch agents, and the question that comes first to my mind is — what is a Sketch agent? Let's discover it by diving a bit deeper into a project.
So, what have I learned?
More mathematical concepts — oh yes. If you've read my day 5 of the creative coding journey, you probably noticed that you could get creative with code using maths, but oh gosh, what if I don't remember much about maths?
Well, you are not alone. I can work with basic calculations, but if it comes to anything more complex, there are a lot of resources out there to help. So nothing to be afraid of, and on the contrary, let's get excited, try to learn something new and make magic with the numbers 🪄.
- Object and Classes
The first lesson of this project explores how to use javascript objects, classes and methods.
- Objects usually start and end with curly brackets and have the key and value pairs inside them. An example below is a vector object with the properties x: 800, y: 400 and radius: 10. In this case, x, y and radius are keys and 800, 400, and 10 are their relative values.
Classes help create multiple instances of an object with the same properties.
The constructor()
is a special class method for creating and initializing an object instance of that class.
class Vector {
constructor(x,y,radius) {
this.x = x;
this.y = y;
this.radius = radius;
}
}
- this — is a keyword in javascript, which in this case refers to the scope of this class.
The benefit of creating the vector class is that we can make multiple copies of the points using the same blueprint.
const vectorA = new Vector(800, 400, 10);
const vectorB = new Vector(300, 700, 10);
We can then use these values to set the x and y positions of the circle instances.
context.beginPath();
context.arc(vectorA.x, vectorA.y, vectorA.radius, 0, Math.PI *2);
context.fillStyle = "black";
context.fill();
context.restore();
- Separating the placement of the vector from the actual circle.
The vector class will be used to define the x and y positions of the circle.
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
The Agent class is needed to draw the circle on the screen.
class Agent {
constructor(x,y) {
//geting the location of the circle and its radius
this.pos = new Vector(x,y)
this.radius = 10;
}
//Drawing the circle according to canvas context
draw(context) {
context.beginPath();
context.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI * 2);
context.fill();
context.stroke();
context.restore();
}
}
We can now use the Agent class to start drawing circles onto the canvas.
const agentA = new Agent(800,400);
const agentB = new Agent(300,700); agentA.draw(context);
agentB.draw(context);
Creating multiple copies of the circles:
We can create multiple copies of the circles, or now we call them agents, by creating an array which we can then loop through:
To draw the circles onto the canvas, we can use the forEach loop.
The current output looks something like this:
2. Animating the circles
To animate the circles accordingly, it's good to use context.translate()
the method inside the Vector class.
So Instead of defining the x and y values directly into an arc, the translation should be changed to translate the context instead.
I covered a bit more about this function on day 5 of the creative coding journey.
From:
context.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI *2);
We define the x and y positions inside context.translate()
and change the arc's x and y values to 0.
context.save();
context.translate(this.pos.x, this.pos.y);
context.arc(0, 0, this.radius, 0, Math.PI *2);
context.restore();
What makes the circles move? The velocity is missing here, and just like the vector point, velocity needs to have x and y coordinates to define the direction of movement. In this case, Bruno has added a random range of values for both x and y.
this.vel = new Vector(random.range(-1,1), random.range(-1, 1));
Now that the velocity is declared, we can add it to the position. For this, a new method called update() was created inside the Agent class.
We can now add this method to the forEach loop before the draw method.
At the moment, only one frame is drawn on the canvas, and for the animation to initiate, we need to set animate: true
in settings. This canvas-sketch function is called each time the browser is ready to repaint the screens, which happens at 60 frames per second by default.
Bruno also shows how to make the function initiate the animation without the library. You can use the method that works for you; however, I will stick with the canvas-sketch function.
3. Setting the boundaries of the canvas.
The circles do not recognise the canvas's boundaries with the current setup, so at the moment, they will float outside the borders. We want to make the circles bounce back once they reach the ends of the canvas, so they are always visible to us.
So if the position x and y are smaller than 0 or the position is smaller than the height and the width of the canvas, we need to invert the velocity (initiate a bounce back)
Then we can call this method in a forEach loop.
4. Drawing lines between the agents
To draw the lines between the circles, we need to create a nested loop for each agent. We go over every other agent and then draw the line in between.
The current output looks like this:
Currently, we are drawing 40 agents on the screen and connecting them up with lines 40 times. This means drawing 1600 iterations on every frame. The loop currently checks each pair of agents twice.
So, for example when i = 0 or 1, we go over j starting from 0,1,2,3 and so on. In this case, for every iteration, the line is drawn twice on top of each other; one line is drawn from 0 to 1 and another from 1 to 0.
To avoid duplication and unnecessary effort for the computer, instead of j always starting from position 0, it can begin at I + 1, meaning that when i is 0, j will be 1.
Bruno explains that when the loop gets to 38 iterations, j will be 39, ensuring the second loop will only run once.
This change also reduced the iterations on every frame from 1600 to 780.
5. Improving the visuals using Pythagoras' theorem.
We can use Pythagoras' theorem to make the sketch more interesting.
In this case, Bruno wants to draw the lines between nearby agents instead of drawing the lines between all the agents.
In this case, we want to find a hypotenuse of the triangle, a square root of the sides' squares that looks like this.
This tutorial used Pythagoras' theorem to find the distance between two points.
Firstly we can find the distance between the points on the x-axis, you can call it what you like, but in this case, Bruno calls this distance dx.
Secondly, we need to find the distance on a y-axis, which Bruno calls this dy.
This gives a triangle shape.
We aim to find the hypotenuse and the formula for this is:
To make this calculation, we can use Math.sqrt()
function. This function can be added as a method to the Vector class; we can call this class. getDistance();
where we pass the other vector, which we can call v
.
getDistance(v) {
const dx = this.x - v.x;
const dy = this.y - v.y;
Math.sqrt(dx * dx + dy * dy);}
We can then use this method inside a current nested for loop.
for(let j = i +1; j < agents.length; j++) {
const agents = agents[j];
//getting the distance between agent's and other positions
const dist = agent.pos.getDistance(other.pos);
//if distance is bigger than 200, ignore the rest of the code inside the loop
if(dist > 200) continue;
.....
}
6. Changing the line, so they are thicker when the agents are close and get thinner when they move further from each other.
For this, Bruno uses the canvas-sketch-util function called mapRange()
which is used to map the value between input and output.
The module will need to be imported first:
const math = require('canvas-sketch-util/math');
And then, we set the line width based on the distance in the for loop:
context.lineWidth = math.mapRange(dist, 0, 200, 12, 1);
So here, when the distance is 0, the line width will be set to 12; when the distance is 200, the line width will be 1.
Lastly, Bruno shows how to export sketches as a video output using the FFmpeg streaming method, which is part of the canvas sketch library.
Summary
Sorry, it’s been a long one. Overall it was a very insightful tutorial; however, I was sometimes struggling to fully understand certain aspects of the code, like using the nested loop to loop through every other agent and applying Pythagoras' theory. Researching these concepts helped me understand how these concepts can be applied in a coded form.