Back

One of the great things about the web and browser technology is that it's so accessible. Anyone can pick it up and start playing because any computer with a browser is ready to go — and that makes it a great platform for learning and prototyping. In this post I'd like to show you how we can use some JavaScript and a bit of simple mathematics to draw a square (don't worry, it'll be an exciting square).

First, we're going to need somewhere to draw. We'll use the HTML5 `<canvas>` element — it goes in your markup just like any other.

``<canvas id="canvas"></canvas>``

To start drawing, we'll fetch canvas `Element` and get a `2d` drawing context. This just means we'll get one particular API that's used for simple 2D drawing. You could fetch a `webgl` context if you wanted to use WebGL features.

``````var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');``````

Here `ctx` is an instance of CanvasRenderingContext2D, which exposes to us a handy collection of methods to actually do something with our canvas. We'll mostly work with this `ctx` object now, let's start by drawing a rectangle.

``ctx.fillRect(0, 0, 100, 100);``

You should end up with something similar to this:

Easy, huh?! You'll notice that mine is red. That's because I set the `fillStyle` property like this: `ctx.fillStyle = '#ff0000';`.

`fillRect` is great but it's not very flexible. What if you wanted to rotate your square? The `fillRect` function has no angle argument so we're going to need to find a better way — luckily the `CanvasRenderingContext2D` gives us exactly that in the form of paths. The path methods let us trace out complex shapes, and then

``````ctx.beginPath();        // This starts a new path
ctx.moveTo(0, 0);       // Move to our starting location, without actually tracing anything yet
ctx.lineTo(0, 100);     // Left side
ctx.lineTo(100, 100);   // Bottom side
ctx.lineTo(100, 0);     // Right side
ctx.lineTo(0, 0);       // Top side

ctx.fill();
ctx.stroke();``````

And you should get something like this:

You might notice that the lines here are a little blurry, this is because of an effect called antialiasing and can be solved.

Now would be a good time to play with the `moveTo` and `lineTo` arguments `x` and `y`. You'll notice that the origin `(0,0)` is in the top left. As we increase `x`, we move the point right. As we increase `y`, we move the point down, not up — so it's not quite a traditional Cartesian coordinate plane.

Now we'll make a series of improvements — first, lets wrap this code up into a function so we can easily draw multiple squares. Better yet, we'll give the position of the centre of our square as an argument (using the centre will make everything much easier, as you will see soon).

``````function drawSquare(x, y) {
ctx.beginPath();
ctx.moveTo(x - 50, y - 50); // v0
ctx.lineTo(x - 50, y + 50); // v1
ctx.lineTo(x + 50, y + 50); // v2
ctx.lineTo(x + 50, y - 50); // v3
ctx.lineTo(x - 50, y - 50); // v0

ctx.fillStyle = 'red';
ctx.fill();
ctx.stroke();
}

drawSquare(50, 50);``````

What we're doing here is drawing four edges between four vertices — `v0`, `v1`, `v2`, and `v3`. We'll examine `v0` first. The calculations `x - 50` and `y - 50` are simple but let's break them down to understand what is going on — it'll be easier if we rearrange the equations to `-50 + x` and `-50 + y`. The first components form a pair, `[-50, -50]`. This is a vector, and it defines the position of `v0` relative to the origin of our square (i.e., up and left a bit). These are known as local coordinates. The `x` and `y` variables form a vector too, `[x, y]`, and this describes the translation of local coordinates to world coordinates. So, it's all just vector addition!

Given a local coordinate `vl` and a position in world space `p`, the equation for `vw` is `vw = vl + p`. With this in mind, let's improve our code a little.

``````var square = [
[-10, -10],
[-10, 10],
[10, 10],
[10, -10],
];

function draw(coords, pos, colour) {
var world_coords = [];

for (var i in coords) {
world_coords[i] = [
coords[i] + pos,
coords[i] + pos
];
}

world_coords.push(world_coords);

ctx.beginPath();
ctx.moveTo(world_coords, world_coords);

for (i = 0; i < world_coords.length; i++) {
ctx.lineTo(world_coords[i], world_coords[i]);
}

ctx.fillStyle = colour;
ctx.fill();
ctx.stroke();
}

draw(square, [50, 50], 'green');``````

This new square is a little smaller, so let's make it bigger. We can do that with scalar multiplication. Let's pick a scale factor `s` and add it to our equation above. Where does it go? Order is important here since the scaling will always happen around `[0, 0]`. So our equation must be `vm = s * vl + p`. If you applied the scalar multiplication after translating to world coordinates, the square would scalar relative to the top left corner of the canvas. Add a scale factor to your `draw` function, and change the first `for` loop to look more like this:

``````for (var i in coords) {
coords[i] = [
scale * coords[i] + pos,
scale * coords[i] + pos
];
}``````

And you should be able to do something like this:

Great! So we've got scaling and translation, the last hurdle is rotation. For this we'll use a rotation matrix, specifically one for rotating a 2D point counter-clockwise around the origin. You can find it here. It looks something like this:

``````var angle = 45;
var matrix = [
[Math.cos(angle), - Math.sin(angle)],
[Math.sin(angle), Math.cos(angle)]]
];``````

Remember, this matrix operates around the origin — so the order we apply it is important. If our new matrix is `r`, then our equation becomes `vm = s * (r * vl) + p`. Since we're only dealing with two dimensions here, we'll expand the matrix multiplication out into two equations for the new point.

``````var a = x * Math.cos(angle) - y * Math.sin(angle)
var b = x * Math.sin(angle) + y * Math.cos(angle)``````

And our `draw` function becomes:

``````for (var i in coords) {
var x = coords[i];
var y = coords[i];

world_coords[i] = [
scale * (x * Math.cos(angle) - y * Math.sin(angle)) + pos,
scale * (x * Math.sin(angle) + y * Math.cos(angle)) + pos
];
}``````

The `angle` variable here is in radians, so a value of `2 * Math.PI` equates to a full rotation. Now you should have something like this:

Excellent! Play with the value a little, do you notice anything in particular? Remember we used a rotation matrix "for rotating a 2D point counter-clockwise around the origin", but our square rotates clockwise! This is because our `y` axis is flipped, as you increase `y` points move down and not up. This means our rotation matrix is operating slightly differently, but it still has the same effect.

One last thing we'll discuss is animation — which is a lot easier than you might think. We'll create a `frame` function that we can call multiple times. Before you draw anything clear the canvas with `ctx.clearRect()`.

``````var rotation = 0;

function frame() {
ctx.clearRect(0, 0, 150, 300);
draw(square, [50, 50], 1, rotation, 'green');
rotation += 0.03;

window.requestAnimationFrame(frame);
}

frame();``````

And you should get this!

And we're done. That's one exciting square, just as promised. Hopefully by now the message will be clear — it's all just mathematics. The canvas is just a vessel for our message, and because it's so accessible and has a fast feedback loop, you can play with the numbers to get a deeper understanding of what is going on.

The ideas here can be applied in many ways, some of which I hope to explore in future posts.

• Tracking game state on a server
• Three dimensions!
• Ray tracing

## Exercises

• What happens if you comment out our `clearRect` call?
• Use `fillRect` and `fillStyle` with some transparency. Surprise blur!
• Try to make different primitive shapes.
• Draw rectanges, using their height and width as arguments.
• Move the world origin to the centre of the canvas (hint: use `canvas.width` and `canvas.height`)
• Flip the canvas `y` axis so up is the direction we expect
• Draw a tank.
• Drive the tank!
• Fire tank shells!