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
by a laser pen that sits at the origin.
Or again, the point
is modelled by all points that pass through the straight line going through
[2,3,1] (the laser line).
Or again more formally,
the two-dimensional point
is modelled by all three-dimensional points
[2w, 3w, w].
We can translate the two-dimensional drawing on the
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, s, 0], [0, 0, 1] ]; const scaleHom2d = s => scaleSepHom2d([s,s]); const translateHom2d = v => [[1, 0, 0], [0, 1, 0], [v, v, 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.