Polygon offsets and boolean operations
Offset a closed boundary in or out, clip one polygon against another, and combine them with union, intersection and difference to get net areas, slab edges and overlaps.
Offsetting a closed polygon
var room = new V2[] { new(0, 0), new(8, 0), new(8, 3), new(3, 3), new(3, 6), new(0, 6) };var slabEdge = OffsetOutwards(room, 0.5); Console.WriteLine(string.Join(" ", slabEdge));Console.WriteLine($"Area {Area(room)} m2 -> {Area(slabEdge)} m2"); static V2[] OffsetOutwards(V2[] p, double delta) // p counterclockwise{ int n = p.Length; var normals = new V2[n]; // outward normal of edge i for (int i = 0; i < n; i++) { V2 d = p[(i + 1) % n] - p[i]; normals[i] = new V2(d.Y, -d.X) * (1 / d.Length); // right of a counterclockwise edge is outside } var result = new V2[n]; for (int i = 0; i < n; i++) { V2 n1 = normals[(i + n - 1) % n], n2 = normals[i]; result[i] = p[i] + (n1 + n2) * (delta / (1 + V2.Dot(n1, n2))); } return result;} static double Area(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.###})";}(-0.5, -0.5) (8.5, -0.5) (8.5, 3.5) (3.5, 3.5) (3.5, 6.5) (-0.5, 6.5) Area 33 m2 -> 48 m2
Clipping: Sutherland–Hodgman
Area of A ∩ B: clip A by B (B must be convex for this algorithm). Area of A − B = area of A − area of (A ∩ B). Area of A ∪ B = area of A + area of B − area of (A ∩ B).
var room = Rect(0, 0, 10, 8);var deductions = new (string Name, V2[] Shape)[]{ ("Column C1", Rect(4.8, 3.8, 5.2, 4.2)), // free-standing ("Column C2", Rect(-0.2, -0.2, 0.2, 0.2)), // in the corner: only a quarter is in the room ("Shaft S1", Rect(7, 5, 9, 7)),}; double net = Area(room);Console.WriteLine($"Gross {net} m2");foreach (var (name, shape) in deductions){ double inside = Area(Clip(shape, room)); net -= inside; Console.WriteLine($"{name}: -{inside:0.###} m2");}Console.WriteLine($"Net {net:0.###} m2"); static V2[] Rect(double x0, double y0, double x1, double y1) => new V2[] { new(x0, y0), new(x1, y0), new(x1, y1), new(x0, y1) }; static V2[] Clip(V2[] subject, V2[] clipper) // Sutherland-Hodgman; clipper convex and counterclockwise{ var output = new List<V2>(subject); for (int i = 0; i < clipper.Length && output.Count > 0; i++) { V2 a = clipper[i], e = clipper[(i + 1) % clipper.Length] - a; var input = output; output = new List<V2>(); for (int j = 0; j < input.Count; j++) { V2 p = input[j], q = input[(j + 1) % input.Count]; double sp = V2.Cross(e, p - a), sq = V2.Cross(e, q - a); // >= 0: inside if (sp >= 0) output.Add(p); if ((sp >= 0) != (sq >= 0)) output.Add(p + (q - p) * (sp / (sp - sq))); } } return output.ToArray();} static double Area(V2[] p){ if (p.Length < 3) return 0; 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.###})";}Gross 80 m2 Column C1: -0.16 m2 Column C2: -0.04 m2 Shaft S1: -4 m2 Net 75.8 m2
General booleans: use a library
Clip, Area and Rect from NetArea.cs, print the area of their overlap, their union and A − B.
Show a hint
Show the solution
var a = Rect(0, 0, 6, 4);var b = Rect(4, 2, 9, 7); double overlap = Area(Clip(a, b));Console.WriteLine($"Overlap: {overlap} m2");Console.WriteLine($"Union: {Area(a) + Area(b) - overlap} m2");Console.WriteLine($"A - B: {Area(a) - overlap} m2"); static V2[] Rect(double x0, double y0, double x1, double y1) => new V2[] { new(x0, y0), new(x1, y0), new(x1, y1), new(x0, y1) }; static V2[] Clip(V2[] subject, V2[] clipper){ var output = new List<V2>(subject); for (int i = 0; i < clipper.Length && output.Count > 0; i++) { V2 a = clipper[i], e = clipper[(i + 1) % clipper.Length] - a; var input = output; output = new List<V2>(); for (int j = 0; j < input.Count; j++) { V2 p = input[j], q = input[(j + 1) % input.Count]; double sp = V2.Cross(e, p - a), sq = V2.Cross(e, q - a); if (sp >= 0) output.Add(p); if ((sp >= 0) != (sq >= 0)) output.Add(p + (q - p) * (sp / (sp - sq))); } } return output.ToArray();} static double Area(V2[] p){ if (p.Length < 3) return 0; 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.###})";}Overlap: 4 m2 Union: 45 m2 A - B: 20 m2
Remember
Offset a closed polygon with wrap-around miters; for counterclockwise winding the right normal points out. An offset changes the area by about perimeter × δ, plus a δ² term from each corner. Sutherland–Hodgman clips any polygon by a convex one, one edge line at a time. A − B = A − (A ∩ B) and A ∪ B = A + B − (A ∩ B), as areas. For general booleans and robust offsets of concave shapes, use a library such as Clipper2.