Learn more about Russian war crimes in Ukraine.

Homogeneous coordinates in 2D, from scratch

Here’s an animation of a spinning, orbiting rectangle. You can also see, in black, the projection of this rectangle, as seen from the origin point (marked with a cross). All of this is described with a tiny library that uses homogeneous coordinates to describe the rotation, scaling, translation, and projection.

In my previous post I showed a matrix library in 5 lines of code. Matrices can describe several operations you will use in graphics programming; most importantly, rotation and scaling.

But for graphics programming, plain linear algebra has at least two big deficiencies. Linear functions can’t describe translation (that is, moving stuff!), because they preserve the origin point. And linear functions can’t describe projection (that is, simulating a camera), because they keep parallel lines parallel.

The homogeneous coordinates system is a kind of mathematical hack that allows describing translation and projection. It builds on top of plain linear algebra, but adds an extra dimension, usually called w. Imagine all true 2D points being drawn on the plane w=1 by a laser pen that sits at the origin. Or again, the point [2,3] is modelled by all points that pass through the straight line going through [0,0,0] and [2,3,1] (the laser line). Or again more formally, the two-dimensional point [2,3] is modelled by all three-dimensional points [2w, 3w, w].

We can translate the two-dimensional drawing on the w=1 plane by skewing space. We move the w basis vector by the amount to translate.

And we can project the two-dimensional drawing onto a one-dimensional line. The very definition of homogeneous coordinates behaves like projection. We can exploit this by squashing and skewing space. (This projection transformation is a bit hard to describe. I’ll try to animate it in a future post.)

Here is my 6-line homogeneous coordinates library (which builds on the 5-line matrix library in my previous post):

const rotateHom2d = a => [[Math.cos(a), Math.sin(a), 0], [-Math.sin(a), Math.cos(a), 0], [0, 0, 1]];
const scaleSepHom2d = (s) => [ [s[0], 0, 0], [0, s[1], 0], [0, 0, 1] ];
const scaleHom2d = s => scaleSepHom2d([s,s]);
const translateHom2d = v => [[1, 0, 0], [0, 1, 0], [v[0], v[1], 1]];
const unHom2d = ([x,y,w]) => [x/w, y/w];

// Projects from origin onto line y=1. Results are in x-coord after normalizing with `unHom2d`.
const projectHom2d = [ [1,0,0], [0,1,1], [0,0,0] ];

In this post, I described homogeneous coordinates for transforming 2D space, and projecting it onto a line. But it can be used in much the same way to transform 3D space, and project it onto a plane. I’ll show this in a future post.

What can computers do? What are the limits of mathematics? And just how busy can a busy beaver be? This year, I’m writing Busy Beavers, a unique interactive book on computability theory. You and I will take a practical and modern approach to answering these questions — or at least learning why some questions are unanswerable!

It’s only $19, and you can get 50% off if you find the discount code ... Not quite. Hackers use the console!

After months of secret toil, I and Andrew Carr released Everyday Data Science, a unique interactive online course! You’ll make the perfect glass of lemonade using Thompson sampling. You’ll lose weight with differential equations. And you might just qualify for the Olympics with a bit of statistics!

It’s $29, but you can get 50% off if you find the discount code ... Not quite. Hackers use the console!

More by Jim

Tagged #mathematics, #programming, #js, #graphics. All content copyright James Fisher 2020. This post is not associated with my employer. Found an error? Edit this page.