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.

Tagged #mathematics, #programming, #js, #graphics.

Similar posts

More by Jim

👋 I'm Jim, a full-stack product engineer. Want to build an amazing product and a profitable business? Read more about me or Get in touch!

This page copyright James Fisher 2020. Content is not associated with my employer. Found an error? Edit this page.