<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. BIM Math
  3. Planes and polygons
All lessons in this course

Lesson 19 of 44 13 minIntermediate

Point-to-plane distance and projection

With a unit normal, one dot product gives a point’s signed distance from a plane and its closest point on it. Learn when to project along the normal and when to drop vertically, and how to mirror in a plane.

Is this duct above the ceiling? How much insulation fits between the rafters and the services? Where does a rooftop unit sit on the roof? Each of these is a question about a point and a plane, and with a unit normal the answer takes a single dot product.

Interactive

A point and a roof plane

QP
unit normal n(0, −0.5, 0.866)
d−1.732
s = n·P + d2.464
Q = P − s × n(3, 3.23, 3.87)
|P − Q|2.464
s = n·P + d = 2.464: P is above the roof (the side the normal points to). Q = P − s × n is the closest point of the plane, 2.464 away. Use this for clearances and offsets measured square to the roof.

Signed distance

For a plane n·X + d = 0 with |n| = 1, the signed distance of a point P is s = n·P + d:

  • s > 0: P is on the side the normal points to (above a floor, outside a wall face).
  • s < 0: P is on the other side (below the floor, inside the wall).
  • s = 0: P is on the plane. In practice, |s| < a tolerance.

The closest point of the plane, the foot of the perpendicular, is Q = P − s·n: move back along the normal by exactly the distance. This is the 3D version of the closest-point lesson in the last module, and it needs no clamping, because a plane has no ends.

DistanceToRoof.cs
var n = new V3(0, -0.6, 0.8);             // unit normal of the roof from the last lessondouble d = -2.4; foreach (var p in new V3[] { new(4, 1, 5), new(4, 2, 3) }){    double s = V3.Dot(n, p) + d;          // signed distance    V3 q = p - n * s;                     // foot of the perpendicular    Console.WriteLine($"{p}: s = {s:0.###}, {(s > 0 ? "above" : "below")} the roof, closest point {q}");} 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);    public override string ToString() => $"({X:0.###}, {Y:0.###}, {Z:0.###})";}
Output
(4, 1, 5): s = 1, above the roof, closest point (4, 1.6, 4.2)
(4, 2, 3): s = -1.2, below the roof, closest point (4, 1.28, 3.96)

Dropping vertically instead

To place a rooftop unit, a lamp post on a sloping site or a pile on the terrain, you do not want the closest point of the plane: you want the point directly below. Solve ax + by + cz + d = 0 for z at the plan position (x, y): z = −(ax + by + d) ÷ c. Switch the widget to “Drop vertically” to compare: on a slope, the vertical gap is always longer than the perpendicular distance (gap = s ÷ cos pitch).

RoofHeight.cs
var n = new V3(0, -0.6, 0.8);double d = -2.4;if (Math.Abs(n.Z) < 1e-9) throw new InvalidOperationException("A vertical plane has no height at a plan point"); foreach (var (x, y) in new[] { (4.0, 1.0), (4.0, 3.0), (7.5, 0.0) }){    double z = -(n.X * x + n.Y * y + d) / n.Z;      // solve ax + by + cz + d = 0 for z    Console.WriteLine($"Roof above ({x}, {y}) is at z = {z:F3}");} 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);    public override string ToString() => $"({X:0.###}, {Y:0.###}, {Z:0.###})";}
Output
Roof above (4, 1) is at z = 3.750
Roof above (4, 3) is at z = 5.250
Roof above (7.5, 0) is at z = 3.000

Projecting a direction onto a plane

Vectors can be projected too. Remove the part of v along the normal and what is left lies in the plane: v − (v·n)·n. Project “straight down” onto a roof and you get the direction rainwater runs, the line of steepest slope, which is what a drainage designer needs.

FallLine.cs
var n = new V3(0, -0.6, 0.8);             // the roof's unit normalvar down = new V3(0, 0, -1); V3 along = down - n * V3.Dot(down, n);    // remove the part along the normalV3 u = along.Normalized();Console.WriteLine($"Water runs along {u}");Console.WriteLine($"Slope of that line: {Math.Asin(-u.Z) * 180 / Math.PI:F2} deg"); 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);    public override string ToString() => $"({X:0.###}, {Y:0.###}, {Z:0.###})";}
Output
Water runs along (0, -0.8, -0.6)
Slope of that line: 36.87 deg

The water runs towards −Y, to the eaves, and the fall line is exactly as steep as the roof’s pitch. Any other line drawn on the roof is flatter.

Quick check

A floor plane has the unit normal (0, 0, 1) and d = −3. What is the signed distance of P = (2, 5, 2.5)?

Quick check

You place a mast on a sloping roof. Which projection gives its base?

Practice

Mirroring in a plane is a projection taken twice as far: P′ = P − 2s·n. Write Mirror(p, n, d) and mirror a light fitting at (2, 3, 2.5) in the wall plane x = 5, and the point (4, 1, 5) in the roof plane from this lesson.

Show a hint

The wall has n = (1, 0, 0) and d = −5. Compute s = n·p + d inside Mirror; it is negative for the light because the light is behind the wall’s normal.

Show the solution
Solution.cs
var light = new V3(2, 3, 2.5);Console.WriteLine($"In the wall x = 5: {Mirror(light, new V3(1, 0, 0), -5)}");var dish = new V3(4, 1, 5);Console.WriteLine($"In the roof: {Mirror(dish, new V3(0, -0.6, 0.8), -2.4)}"); static V3 Mirror(V3 p, V3 n, double d) => p - n * (2 * (V3.Dot(n, p) + d)); 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);    public override string ToString() => $"({X:0.###}, {Y:0.###}, {Z:0.###})";}
Output
In the wall x = 5: (8, 3, 2.5)
In the roof: (4, 2.2, 3.4)

Remember

  • With a unit normal, s = n·P + d is the signed distance: positive in front, negative behind.
  • The closest point of the plane is Q = P − s·n; there is nothing to clamp.
  • To set something down on a roof or terrain, drop vertically: z = −(ax + by + d) ÷ c, if c ≠ 0.
  • v − (v·n)·n projects a direction into the plane; projecting “down” gives the fall line.
  • Mirroring in a plane is P − 2s·n.
0 of 44 lessons completed