Rotation in 2D, 3D and about any axis
Rotate a point in the plane with cos and sin, rotate about a pivot by moving it to the origin first, and in 3D rotate about X, Y, Z or any axis with Rodrigues’ formula.
Rotation in 2D
Rotating about a pivot
var pivot = new V2(2, 3); // the building corner that stays putvar corners = new V2[] { new(2, 3), new(12, 3), new(12, 9), new(2, 9) }; foreach (var c in corners) Console.WriteLine($"{c} -> {RotateAbout(c, pivot, 30)}"); static V2 Rotate(V2 v, double degrees){ double t = degrees * Math.PI / 180, c = Math.Cos(t), s = Math.Sin(t); return new(v.X * c - v.Y * s, v.X * s + v.Y * c);} static V2 RotateAbout(V2 p, V2 pivot, double degrees) => pivot + Rotate(p - pivot, degrees); 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)})";}(2, 3) -> (2, 3) (12, 3) -> (10.66, 8) (12, 9) -> (7.66, 13.196) (2, 9) -> (-1, 8.196)
Rotation in 3D about X, Y and Z
| About | Row 1 | Row 2 | Row 3 | Turns |
|---|---|---|---|---|
Rotation about any axis
// A tilt-and-turn window tilts about its sill: an axis along X, through (0, 0, 1)var sillPoint = new V3(0, 0, 1);var sillDir = new V3(1, 0, 0);var topCorner = new V3(1.2, 0, 2.4);Console.WriteLine($"Top corner tilted 10 deg: {RotateAbout(topCorner, sillPoint, sillDir, 10)}"); // 120 deg about (1, 1, 1) cycles the axes: X -> Y -> Z -> Xvar diagonal = new V3(1, 1, 1).Normalized();foreach (var v in new V3[] { new(1, 0, 0), new(0, 1, 0), new(0, 0, 1) }) Console.WriteLine($"{v} -> {Rotate(v, diagonal, 120)}"); static V3 Rotate(V3 v, V3 k, double degrees) // Rodrigues; k must be a unit vector{ double t = degrees * Math.PI / 180, c = Math.Cos(t), s = Math.Sin(t); return v * c + V3.Cross(k, v) * s + k * (V3.Dot(k, v) * (1 - c));} static V3 RotateAbout(V3 p, V3 pointOnAxis, V3 axis, double degrees) => pointOnAxis + Rotate(p - pointOnAxis, axis.Normalized(), degrees); readonly record struct V3(double X, double Y, double Z){ public static V3 operator +(V3 a, V3 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z); public static V3 operator -(V3 a, V3 b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z); public static V3 operator *(V3 v, double k) => new(v.X * k, v.Y * k, v.Z * k); public static double Dot(V3 a, V3 b) => a.X * b.X + a.Y * b.Y + a.Z * b.Z; public static V3 Cross(V3 a, V3 b) => new(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); public double Length => Math.Sqrt(Dot(this, this)); public V3 Normalized() => this * (1 / Length); static string F(double v) => Math.Abs(v) < 5e-4 ? "0" : v.ToString("0.###"); public override string ToString() => $"({F(X)}, {F(Y)}, {F(Z)})";}Top corner tilted 10 deg: (1.2, -0.243, 2.379) (1, 0, 0) -> (0, 1, 0) (0, 1, 0) -> (0, 0, 1) (0, 0, 1) -> (1, 0, 0)
What every rotation matrix has in common
Its columns are unit vectors at right angles to each other (the turned axes). Such a matrix is called orthonormal. Lengths and angles do not change, and det = +1. Its inverse is its transpose Rᵀ: swap rows and columns to undo the turn, with no division needed.
Show a hint
Rotate from RotateAboutAxis.cs. The axis (0, 0.6, 0.8) already has length 1.
Show the solution
var k = new V3(0, 0.6, 0.8);V3 x = Rotate(new V3(1, 0, 0), k, 40);V3 y = Rotate(new V3(0, 1, 0), k, 40);V3 z = Rotate(new V3(0, 0, 1), k, 40);Console.WriteLine($"Columns: {x} {y} {z}"); bool unit = new[] { x, y, z }.All(c => Math.Abs(c.Length - 1) < 1e-12);bool square = Math.Abs(V3.Dot(x, y)) < 1e-12 && Math.Abs(V3.Dot(y, z)) < 1e-12 && Math.Abs(V3.Dot(z, x)) < 1e-12;Console.WriteLine($"Unit columns: {unit}, at right angles: {square}");Console.WriteLine($"det = {V3.Dot(V3.Cross(x, y), z):F6}"); static V3 Rotate(V3 v, V3 k, double degrees){ double t = degrees * Math.PI / 180, c = Math.Cos(t), s = Math.Sin(t); return v * c + V3.Cross(k, v) * s + k * (V3.Dot(k, v) * (1 - c));} readonly record struct V3(double X, double Y, double Z){ public static V3 operator +(V3 a, V3 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z); public static V3 operator -(V3 a, V3 b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z); public static V3 operator *(V3 v, double k) => new(v.X * k, v.Y * k, v.Z * k); public static double Dot(V3 a, V3 b) => a.X * b.X + a.Y * b.Y + a.Z * b.Z; public static V3 Cross(V3 a, V3 b) => new(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); public double Length => Math.Sqrt(Dot(this, this)); public V3 Normalized() => this * (1 / Length); static string F(double v) => Math.Abs(v) < 5e-4 ? "0" : v.ToString("0.###"); public override string ToString() => $"({F(X)}, {F(Y)}, {F(Z)})";}Columns: (0.766, 0.514, -0.386) (-0.514, 0.85, 0.112) (0.386, 0.112, 0.916) Unit columns: True, at right angles: True det = 1.000000
Remember
2D: x′ = x·cos θ − y·sin θ, y′ = x·sin θ + y·cos θ; positive is counterclockwise in plan. To rotate about a pivot C: P′ = C + R·(P − C). In 3D, rotations about X, Y and Z follow the right-hand rule; the cycle X → Y → Z puts the minus sign of the Y matrix in a different place. Rodrigues’ formula rotates about any unit axis: v·cos θ + (k × v)·sin θ + k·(k·v)·(1 − cos θ). Rotation matrices are orthonormal with det = 1, and their inverse is their transpose.