<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 21 of 44 14 minIntermediate

Polygon area, normal and winding

The shoelace formula gives a room’s area from its corners, with a sign that says which way round the boundary goes. Use the sign for holes, find the centroid, and extend it to sloping 3D faces with Newell’s method.

Room areas, slab areas, finishes takeoffs and fire compartments all come down to one question: what is the area inside this boundary? The answer is a short loop over the corners called the shoelace formula, and it gives you the boundary’s direction for free.

Interactive

Shoelace area and winding

Drag the vertices. Blue triangles add area, red ones subtract it; what is left is the polygon.

centroidP0P1P2P3P4
P0 × P117
P1 × P226.5
P2 × P323.5
P3 × P4−0.5
P4 × P0−3
sum ÷ 2 = signed area31.75
windingcounterclockwise
area31.75
perimeter23.05
The vertices run counterclockwise, so the signed area is positive: 31.75. Each term x_i·y_i+1 − x_i+1·y_i is twice the signed area of a triangle from the origin; the parts outside the polygon cancel out.

The shoelace formula

For corners P0 … P(n−1), area = ½ Σ (x_i·y_(i+1) − x_(i+1)·y_i), wrapping round so the last corner pairs with the first. Each term is the 2D cross product P_i × P_(i+1): twice the signed area of the triangle from the origin to that edge. Triangles on the far side of the polygon come out negative, so everything outside the room cancels and the room’s area is left. That is what the widget’s blue and red triangles show.

RoomArea.cs
var room = new V2[] { new(0, 0), new(8, 0), new(8, 3), new(3, 3), new(3, 6), new(0, 6) }; double twice = 0, perimeter = 0, cx = 0, cy = 0;for (int i = 0; i < room.Length; i++){    V2 a = room[i], b = room[(i + 1) % room.Length];    double cross = a.X * b.Y - b.X * a.Y;      // twice the signed area of triangle (O, a, b)    twice += cross;    cx += (a.X + b.X) * cross;    cy += (a.Y + b.Y) * cross;    perimeter += (b - a).Length;}double area = twice / 2;Console.WriteLine($"Signed area {area} m2 ({(area > 0 ? "counterclockwise" : "clockwise")})");Console.WriteLine($"Perimeter {perimeter} m");Console.WriteLine($"Centroid ({cx / (6 * area):F3}, {cy / (6 * area):F3})"); 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 static double Dot(V2 a, V2 b) => a.X * b.X + a.Y * b.Y;    public static double Cross(V2 a, V2 b) => a.X * b.Y - a.Y * b.X;    public double Length => Math.Sqrt(X * X + Y * Y);    public override string ToString() => $"({X:0.###}, {Y:0.###})";}
Output
Signed area 33 m2 (counterclockwise)
Perimeter 28 m
Centroid (3.318, 2.318)

The centroid comes from the same loop: it is the balance point of the shape, the place to put a room tag or the centre of a load. For an L-shaped room it is pulled towards the bigger wing, and it can even lie outside a strongly concave shape.

Winding: the sign means something

The sign of the sum says which way round the corners go: positive is counterclockwise, negative is clockwise (seen from above, with Y up). Reverse the order in the widget and only the sign changes. File formats use this deliberately: in IFC profiles, GIS shapes and many CAD tools, the outer boundary runs one way and holes run the other way, so simply adding the signed areas subtracts the holes.

Holes.cs
var outer = new V2[] { new(0, 0), new(10, 0), new(10, 8), new(0, 8) };          // counterclockwisevar column = new V2[] { new(5, 4), new(5, 4.4), new(5.4, 4.4), new(5.4, 4) };   // clockwise: a hole Console.WriteLine($"Outer {SignedArea(outer)}, column {SignedArea(column):0.##}");Console.WriteLine($"Net floor area {SignedArea(outer) + SignedArea(column):0.##} m2"); static double SignedArea(V2[] p){    double s = 0;    for (int i = 0; i < p.Length; i++) s += V2.Cross(p[i], p[(i + 1) % p.Length]);    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 static double Dot(V2 a, V2 b) => a.X * b.X + a.Y * b.Y;    public static double Cross(V2 a, V2 b) => a.X * b.Y - a.Y * b.X;    public double Length => Math.Sqrt(X * X + Y * Y);    public override string ToString() => $"({X:0.###}, {Y:0.###})";}
Output
Outer 80, column -0.16
Net floor area 79.84 m2

Sloping faces in 3D: Newell’s method

A roof face or a sloping soffit is a polygon in 3D. Sum the 3D cross products P_i × P_(i+1) around the loop, and you get a vector that is the face’s normal, with a length of twice its area. This is Newell’s method. It is robust even when the corners are not perfectly flat, which in real survey data they never are.

SlopedFace.cs
var face = new V3[] { new(0, 0, 3), new(10, 0, 3), new(10, 4, 6), new(0, 4, 6) }; var sum = new V3(0, 0, 0);for (int i = 0; i < face.Length; i++)    sum += V3.Cross(face[i], face[(i + 1) % face.Length]); Console.WriteLine($"Area {sum.Length / 2} m2");Console.WriteLine($"Normal {sum.Normalized()}"); 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
Area 50 m2
Normal (0, -0.6, 0.8)

The face is 10 m along the eaves and 5 m up the slope (4 across and 3 up), so 50 m² is right, and the normal is the roof normal from the first lesson. Its plan area, the area of its shadow on the floor, is the area times n.z: 40 m².

Quick check

The shoelace sum for a room comes out at −24. What do you know?

Quick check

What does Newell’s method give for a 3D polygon?

Practice

Write EnsureCounterClockwise(pts) that returns the polygon unchanged if its signed area is positive, and a reversed copy otherwise. Test it on the slab (0, 0), (0, 5), (6, 5), (6, 0): print the signed area before and after, and the first two corners after.

Show a hint

Copy the array with Clone and reverse it with Array.Reverse, so the caller’s array is not changed behind its back.

Show the solution
Solution.cs
var slab = new V2[] { new(0, 0), new(0, 5), new(6, 5), new(6, 0) };Console.WriteLine($"Before: {SignedArea(slab)}");slab = EnsureCounterClockwise(slab);Console.WriteLine($"After:  {SignedArea(slab)}, starting {slab[0]}, {slab[1]}"); static V2[] EnsureCounterClockwise(V2[] p){    if (SignedArea(p) >= 0) return p;    var copy = (V2[])p.Clone();    Array.Reverse(copy);    return copy;} static double SignedArea(V2[] p){    double s = 0;    for (int i = 0; i < p.Length; i++) s += V2.Cross(p[i], p[(i + 1) % p.Length]);    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 static double Dot(V2 a, V2 b) => a.X * b.X + a.Y * b.Y;    public static double Cross(V2 a, V2 b) => a.X * b.Y - a.Y * b.X;    public double Length => Math.Sqrt(X * X + Y * Y);    public override string ToString() => $"({X:0.###}, {Y:0.###})";}
Output
Before: -30
After:  30, starting (6, 0), (6, 5)

Remember

  • Shoelace: area = ½ Σ (x_i·y_(i+1) − x_(i+1)·y_i), wrapping round to the first corner.
  • Positive means counterclockwise, negative clockwise; |sum ÷ 2| is the area.
  • Holes wound the opposite way subtract themselves when you add signed areas.
  • The same loop gives the centroid; the formula is meaningless for self-intersecting boundaries.
  • In 3D, Σ P_i × P_(i+1) (Newell’s method) gives the normal, with length twice the area.
0 of 44 lessons completed