An introduction to Matrices

A matrix is a rectangular grid of numbers. A matrix has rows and columns which dictate the dimensions of the matrix. For example, a 2x3 matrix has two rows and three columns. A matrix which has the same number of rows and columns is called a square matrix. Below are examples of 2x2, 3x3 and 4x4 matrices.

$$ \begin{bmatrix} A & C\\ B & D \end{bmatrix} \begin{bmatrix} A & D & G\\ B & E & H\\ C & F & I \end{bmatrix} \begin{bmatrix} A & E & I & M\\ B & F & J & N\\ C & G & K & O\\ D & H & L & P \end{bmatrix} $$

Generally, most games only use square 3x3 or 4x4 matrices. A 4x4 matrix can encode the transformation of an object or the projection of a camera. GLSL and some math libraries like glm support non square matrices. This blog will focus only on square matrices.

Matrices can be laid out in memory one of two ways. Either as a two dimensional array, or as a linear array with (number of rows * number of columns) elements. Games almost always store matrices as a linear array. The matrices presented previously have been augmented with subscripts that show at what index in a linear array the element would be.

$$ \begin{bmatrix} A_0 & C_2\\ B_1 & D_3 \end{bmatrix} \begin{bmatrix} A_0 & D_3 & G_6\\ B_1 & E_4 & H_7\\ C_2 & F_5 & I_8 \end{bmatrix} \begin{bmatrix} A_0 & E_4 & I_8 & M_{12}\\ B_1 & F_5 & J_9 & N_{13}\\ C_2 & G_6 & K_{10} & O_{14}\\ D_3 & H_7 & L_{11} & P_{15} \end{bmatrix} $$

The code snippet below shows the linear array of the 3x3 matrix above

char mat[9] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I' };

Matrices can be indexed using a row/column notation as well. By convention it's row first, column next, but not all libraries follow that convention. The mtrices below show the row/column way to index a matrix.

$$ \begin{bmatrix} A_{r:0, c:0} & C_{r:0, c:1}\\ B_{r:1, c:0} & D_{r:1, c:1} \end{bmatrix} \begin{bmatrix} A_{r:0, c:0} & E_{r:0, c:1} & I_{r:0, c:2} & M_{r:0, c:3}\\ B_{r:1, c:0} & F_{r:1, c:1} & J_{r:1, c:2} & N_{r:1, c:3}\\ C_{r:2, c:0} & G_{r:2, c:1} & K_{r:2, c:2} & O_{r:2, c:3}\\ D_{r:3, c:0} & H_{r:3, c:1} & L_{r:3, c:2} & P_{r:3, c:3} \end{bmatrix} $$

Implementation

In C, the matrix structure could be defined using a union between a 16 element array of floating point numbers and anonymous structures. The anonymous structures provide mnemonics to access elements using a row / column notation. For this blog, implementations are provided for two, three and four dimensional square matrices.

struct mat2 {
    union {
        float v[4];
        struct {
            float r0c0; float r1c0;
            float r0c1; float r1c1;
        };
        struct {
            float c0r0; float c0r1;
            float c1r0; float c1r1;
        };
        
    };
};

struct mat3 {
    union {
        float v[9];
        struct {
            float r0c0; float r1c0; float r2c0;
            float r0c1; float r1c1; float r2c1;
            float r0c2; float r1c2; float r2c2;
        };
        struct {
            float c0r0; float c0r1; float c0r2;
            float c1r0; float c1r1; float c1r2;
            float c2r0; float c2r1; float c2r2;
        };
        
    };
};

struct mat4 {
    union {
        float v[16];
        struct {
            float r0c0; float r1c0; float r2c0; float r3c0;
            float r0c1; float r1c1; float r2c1; float r3c1;
            float r0c2; float r1c2; float r2c2; float r3c2;
            float r0c3; float r1c3; float r2c3; float r3c3;
        };
        struct {
            float c0r0; float c0r1; float c0r2; float c0r3;
            float c1r0; float c1r1; float c1r2; float c1r3;
            float c2r0; float c2r1; float c2r2; float c2r3;
            float c3r0; float c3r1; float c3r2; float c3r3;
        };
        
    };
};

It's trivial to address a floating point array using row / column notation, however you will need to know if the matrix is row or column major. More on this in the next section, for now let's assume these are column major matrices. To get a linear index from a row and a column, multiply the column by the size of the (square) matrix and add the row. The formula is: column * size of matrix + row as demonstrated below.

float Mat2ValueAt(float input[4], int row, int column) {
  return input[column * 2 + row]; // 2 = number of columns
}

float Mat3ValueAt(float input[9], int row, int column) {
  return input[column * 3 + row]; // 3 = number of columns
}

float Mat4ValueAt(float input[16], int row, int column) {
  return input[column * 4 + row]; // 4 = number of columns
}