A matrix library in 5 lines of code

Here’s an animation of a pulsating rectangle, spinning around the origin. It’s nothing special, except that it’s drawn with the power of ✨matrix math✨, using my own matrix library (5 lines of code!).

For years, on and off, I tried to internalize matrix operations, but it never stuck. I remember watching a Khan Academy linear algebra course around ten years ago, where Sal’s guidance was basically, “A matrix is a grid of numbers. Here is the matrix multiplication number-crunching algorithm.”

Sal Khan is a great teacher, but this approach was wrong. Recently, I watched Grant Sanderson’s Essence of Linear Algebra course, which made things not just clear, but almost ... obvious! Here is the fundamental insight: a matrix describes a linear function by recording where the basis vectors move to. This insight is so important that I’ll say it again:

A matrix describes a linear function by recording where the basis vectors move to.

Everything else follows smoothly from this definition of a matrix. Armed with just this insight, I no longer have to remember how to “multiply matrices”. Instead, I can just work it out from the definition (which is that matrix “multiplication” is actually function composition.) Here is my tiny matrix math library:

const zipWith = (f, a, b) => a.map((k, i) => f(k, b[i]));  // helper

// Vector ops
const vecScale = (n, v) => v.map(c => n*c);
const vecAdd = (v1, v2) => zipWith((c1,c2)=>c1+c2, v1, v2);

// Matrix ops
const matApply = (mat, vec) => zipWith(vecScale, vec, mat).reduce(vecAdd);
const matMul = (m2, m1) => m1.map(v => matApply(m2,v));

In this system, matrices are written in “column-major format”: a list of basis vectors. Here are some examples for 2D matrix math:

const identity = [
  [1, 0],  // the x basis vector ("i-hat"). It hasn't moved anywhere.
  [0, 1],  // the y basis vector ("j-hat"). It hasn't moved anywhere.
];

const rotateClockwise90 = [
  [-1, 0],  // the rotated x basis vector ("i-hat")
  [ 1, 0],  // the rotated y basis vector ("j-hat")
];

// Common matrix constructors
const rotate = a => [[Math.cos(a), Math.sin(a)], [-Math.sin(a), Math.cos(a)]];
const scaleSep = (s) => [ [s[0], 0], [0, s[1]] ];
const scale = s => scaleSep([s,s]);

For my purposes (graphics programming), linear functions have 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.

In my next post, I describe homogeneous coordinates, a mathematical hack that builds on top of plain linear algebra, and allows describing translation and projection. Stay tuned!

👋 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!

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.