Matrices and matrix multiplication
A matrix is a grid of numbers that moves every point of the plane or of space at once. Read its columns as “where the axes go”, multiply matrices row by column, and use the determinant to see what happens to areas.
A matrix times a point
| Transform | First column (X goes to) | Second column (Y goes to) | det |
|---|---|---|---|
Multiplying two matrices
double[,] rotate90 = { { 0, -1 }, { 1, 0 } };double[,] stretchX = { { 2, 0 }, { 0, 1 } }; Print("Stretch, then rotate (R * S)", Multiply(rotate90, stretchX));Print("Rotate, then stretch (S * R)", Multiply(stretchX, rotate90)); double[,] p = { { 1 }, { 1 } }; // the point (1, 1) as a 2x1 columnPrint("(R * S) * p", Multiply(Multiply(rotate90, stretchX), p)); static double[,] Multiply(double[,] a, double[,] b){ int n = a.GetLength(0), m = a.GetLength(1), q = b.GetLength(1); if (b.GetLength(0) != m) throw new ArgumentException($"Cannot multiply {n}x{m} by {b.GetLength(0)}x{q}"); var c = new double[n, q]; for (int i = 0; i < n; i++) for (int j = 0; j < q; j++) for (int k = 0; k < m; k++) c[i, j] += a[i, k] * b[k, j]; // row i of a, dotted with column j of b return c;} static void Print(string title, double[,] m){ Console.WriteLine(title + ":"); for (int i = 0; i < m.GetLength(0); i++) { var row = new string[m.GetLength(1)]; for (int j = 0; j < row.Length; j++) row[j] = m[i, j].ToString("0.###").PadLeft(3); Console.WriteLine(" [" + string.Join(" ", row) + " ]"); }}Stretch, then rotate (R * S): [ 0 -1 ] [ 2 0 ] Rotate, then stretch (S * R): [ 0 -2 ] [ 1 0 ] (R * S) * p: [ -1 ] [ 2 ]
The determinant
det > 0: shapes keep their orientation. Rotations have det = 1, so they keep areas exactly. det < 0: the transform includes a mirror. Outlines reverse their winding, and an “F” reads backwards. det = 0: the plane is squashed onto a line (or a point). Areas become 0, and the matrix has no inverse: you cannot undo it.
var room = new V2[] { new(0, 0), new(6, 0), new(6, 4), new(0, 4) };var matrices = new (string Name, double A, double B, double C, double D)[]{ ("Scale x2", 2, 0, 0, 2), ("Shear 0.5", 1, 0, 0.5, 1), ("Mirror in Y", -1, 0, 0, 1), ("Squash", 1, 0.5, 2, 1)}; Console.WriteLine($"Room: {Area(room)} m2");foreach (var (name, a, b, c, d) in matrices){ var moved = room.Select(p => new V2(a * p.X + c * p.Y, b * p.X + d * p.Y)).ToArray(); Console.WriteLine($"{name,-12} det {a * d - b * c,3} signed area {Area(moved),4} m2");} static double Area(V2[] p){ double s = 0; for (int i = 0; i < p.Length; i++) { V2 u = p[i], v = p[(i + 1) % p.Length]; s += u.X * v.Y - v.X * u.Y; } return s / 2;} readonly record struct V2(double X, double Y){ public static V2 operator +(V2 a, V2 b) => new(a.X + b.X, a.Y + b.Y); public static V2 operator -(V2 a, V2 b) => new(a.X - b.X, a.Y - b.Y); public static V2 operator *(V2 v, double k) => new(v.X * k, v.Y * k); public double Length => Math.Sqrt(X * X + Y * Y); static string F(double v) => Math.Abs(v) < 5e-4 ? "0" : v.ToString("0.###"); public override string ToString() => $"({F(X)}, {F(Y)})";}Room: 24 m2 Scale x2 det 4 signed area 96 m2 Shear 0.5 det 1 signed area 24 m2 Mirror in Y det -1 signed area -24 m2 Squash det 0 signed area 0 m2
Det2 for a 2×2 matrix given as a, b, c, d (columns (a, b) and (c, d)), and use it to classify these: a 30° rotation, a 1:100 drawing scale (0.01 on the diagonal), a mirror in the X axis, the matrix with columns (1, 0.5) and (2, 1), and a shear with columns (1, 0) and (0.5, 1). Print whether each keeps areas, scales them, mirrors, or squashes.
Show a hint
Show the solution
double c30 = Math.Cos(Math.PI / 6), s30 = Math.Sin(Math.PI / 6);var tests = new (string Name, double A, double B, double C, double D)[]{ ("Rotate 30", c30, s30, -s30, c30), ("Drawing 1:100", 0.01, 0, 0, 0.01), ("Mirror in X", 1, 0, 0, -1), ("Columns (1,0.5) (2,1)", 1, 0.5, 2, 1), ("Shear", 1, 0, 0.5, 1)}; foreach (var (name, a, b, c, d) in tests){ double det = Det2(a, b, c, d); string kind = Math.Abs(det) < 1e-9 ? "squashes onto a line, no inverse" : det < 0 ? $"mirrors, areas x {Math.Abs(det):0.####}" : Math.Abs(det - 1) < 1e-9 ? "keeps areas" : $"scales areas by {det:0.####}"; Console.WriteLine($"{name}: {kind}");} static double Det2(double a, double b, double c, double d) => a * d - b * c;Rotate 30: keeps areas Drawing 1:100: scales areas by 0.0001 Mirror in X: mirrors, areas x 1 Columns (1,0.5) (2,1): squashes onto a line, no inverse Shear: keeps areas
Remember
A matrix times a point is a mix of its columns: the columns are where the axes go. Entry (i, j) of A·B is row i of A dotted with column j of B; an m×n matrix multiplies an n×p one. Matrix multiplication is associative but not commutative: A·B ≠ B·A in general. In M·P the matrix nearest the point acts first. The determinant is the area (or volume) factor: negative means mirrored, zero means squashed and not invertible.