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 [2/w, 3/w, 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.

I just released Vidrio, a free app for macOS and Windows to make your screen-sharing awesomely holographic. Vidrio shows your webcam video on your screen, just like a mirror. Then you just share or record your screen with Zoom, QuickTime, or any other app. Vidrio makes your presentations effortlessly engaging, showing your gestures, gazes, and expressions. #1 on Product Hunt. Available for macOS and Windows.

With Vidrio

With generic competitor

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.