The dot product: angles, projection, facing
One multiplication tells you the angle between two directions, how far along a wall a point lies, and whether a face points towards the sun. The most useful line of code in geometry.
The sign alone says a lot
| a · b | Angle | Meaning |
|---|---|---|
The angle between two directions
using V2 = (double X, double Y); static double Dot(V2 a, V2 b) => a.X * b.X + a.Y * b.Y;static double Len(V2 v) => Math.Sqrt(Dot(v, v)); static double AngleDeg(V2 a, V2 b){ double c = Dot(a, b) / (Len(a) * Len(b)); c = Math.Clamp(c, -1, 1); // rounding can give 1.0000000000000002 return Math.Acos(c) * 180 / Math.PI;} V2 wall = (4, 0);foreach (V2 other in new V2[] { (3, 3), (0, 5), (-2, -2) }) Console.WriteLine($"Dot = {Dot(wall, other)}, angle = {AngleDeg(wall, other):F1} deg");Dot = 12, angle = 45.0 deg Dot = 0, angle = 90.0 deg Dot = -8, angle = 135.0 deg
Projection: how far along, how far off
using V2 = (double X, double Y); static double Dot(V2 a, V2 b) => a.X * b.X + a.Y * b.Y; V2 a = (1, 1), b = (9, 7); // wall location lineV2 p = (6, 6); // a column centre V2 ab = (b.X - a.X, b.Y - a.Y); // (8, 6)double len = Math.Sqrt(Dot(ab, ab)); // 10V2 u = (ab.X / len, ab.Y / len); // along the wallV2 n = (-u.Y, u.X); // across it, to the leftV2 ap = (p.X - a.X, p.Y - a.Y); double along = Dot(ap, u);double across = Dot(ap, n);V2 foot = (a.X + u.X * along, a.Y + u.Y * along); Console.WriteLine($"Along the wall: {along:F2} m of {len} m");Console.WriteLine($"Off the line: {across:F2} m (positive = left side)");Console.WriteLine($"Nearest point on the line: ({foot.X:F2}, {foot.Y:F2})");Along the wall: 7.00 m of 10 m Off the line: 1.00 m (positive = left side) Nearest point on the line: (6.60, 5.20)
along is below 0 or above the wall’s length, the column lies beyond one end. The Lines and curves module turns this into a full closest-point routine for segments.
Facing: which way does a face point?
using V2 = (double X, double Y); static double Dot(V2 a, V2 b) => a.X * b.X + a.Y * b.Y; V2 south = (0, -1);var windows = new (string Name, V2 Normal)[]{ ("W1", (0, -1)), ("W2", (0.6, -0.8)), ("W3", (0.8, -0.6)), ("W4", (1, 0)), ("W5", (0, 1))};double limit = Math.Cos(45 * Math.PI / 180); // within 45 deg of south foreach (var w in windows){ double d = Dot(w.Normal, south); string verdict = d >= limit ? "faces south" : d > 0 ? "partly south" : "no south sun"; Console.WriteLine($"{w.Name}: dot = {d:F2} -> {verdict}");}W1: dot = 1.00 -> faces south W2: dot = 0.80 -> faces south W3: dot = 0.60 -> partly south W4: dot = 0.00 -> no south sun W5: dot = -1.00 -> no south sun
Relation(a, b) that returns “perpendicular”, “parallel” or “at N deg” for two wall directions, using the dot product of their unit vectors and a tolerance of 1e-9. Test it on (4, 0) & (0, 3), (2, 2) & (−3, −3), (1, 0) & (1, 1) and (3, 4) & (−4, 3).
Show a hint
Math.Acos.
Show the solution
using V2 = (double X, double Y); static double Dot(V2 a, V2 b) => a.X * b.X + a.Y * b.Y;static V2 Unit(V2 v) { double l = Math.Sqrt(Dot(v, v)); return (v.X / l, v.Y / l); } static string Relation(V2 a, V2 b){ const double tol = 1e-9; double c = Dot(Unit(a), Unit(b)); if (Math.Abs(c) < tol) return "perpendicular"; if (Math.Abs(Math.Abs(c) - 1) < tol) return "parallel"; return $"at {Math.Acos(Math.Clamp(c, -1, 1)) * 180 / Math.PI:F1} deg";} Console.WriteLine(Relation((4, 0), (0, 3)));Console.WriteLine(Relation((2, 2), (-3, -3)));Console.WriteLine(Relation((1, 0), (1, 1)));Console.WriteLine(Relation((3, 4), (-4, 3)));perpendicular parallel at 45.0 deg perpendicular
Remember
a · b = a.x·b.x + a.y·b.y + a.z·b.z = |a||b| cos θ: one number, cheap to compute. Its sign says same direction (+), perpendicular (0) or opposite (−). θ = acos(a · b ÷ (|a||b|)); always clamp to [−1, 1] before Acos.With a unit vector u, v · u is v’s projection: how far along u it goes. With the perpendicular, how far off. With unit normals, the dot product scores how much a face points in a direction: compare it with a precomputed cosine.