using System.Collections.Generic; namespace OpenNest.Geometry; /// /// Maximal empty axis-aligned rectangles: rectangles of free space that cannot grow in any /// direction. The first result is the largest by area. /// public static class MaximalRectangles { /// /// Finds the maximal rectangles of empty cells in a rectilinear grid, using the histogram /// method: for each row, a height histogram of consecutive empty cells below it, scanned /// with a stack. /// /// Ascending column boundaries; column c spans xs[c] to xs[c + 1]. /// Ascending row boundaries; row r spans ys[r] to ys[r + 1]. /// Free cells, indexed [row, column]. /// Rectangles narrower than this in either axis are dropped. /// Rectangles not contained in another result, largest area first. public static List FromGrid( IReadOnlyList xs, IReadOnlyList ys, bool[,] empty, double minDimension = 0 ) { var merged = MergeCells(xs, ys, empty); var sized = FilterBySize(merged, minDimension); return RemoveDominated(sized); } private static List MergeCells(IReadOnlyList xs, IReadOnlyList ys, bool[,] empty) { var rows = empty.GetLength(0); var cols = empty.GetLength(1); var height = new int[rows, cols]; for (var c = 0; c < cols; c++) { for (var r = 0; r < rows; r++) height[r, c] = empty[r, c] ? (r > 0 ? height[r - 1, c] + 1 : 1) : 0; } var candidates = new List(); for (var r = 0; r < rows; r++) { var stack = new Stack<(int startCol, int h)>(); for (var c = 0; c <= cols; c++) { var h = c < cols ? height[r, c] : 0; var startCol = c; while (stack.Count > 0 && stack.Peek().h > h) { var top = stack.Pop(); startCol = top.startCol; candidates.Add( new Box( xs[top.startCol], ys[r - top.h + 1], xs[c] - xs[top.startCol], ys[r + 1] - ys[r - top.h + 1] ) ); } if (h > 0) stack.Push((startCol, h)); } } return candidates; } private static List FilterBySize(List boxes, double minDimension) { if (minDimension <= 0) return boxes; var result = new List(); foreach (var box in boxes) { if (box.Width >= minDimension && box.Length >= minDimension) result.Add(box); } return result; } private static List RemoveDominated(List boxes) { boxes.Sort((a, b) => b.Area().CompareTo(a.Area())); var results = new List(); foreach (var box in boxes) { var dominated = false; foreach (var larger in results) { if (IsContainedIn(box, larger)) { dominated = true; break; } } if (!dominated) results.Add(box); } return results; } private static bool IsContainedIn(Box inner, Box outer) { var eps = Math.Tolerance.Epsilon; return inner.Left >= outer.Left - eps && inner.Right <= outer.Right + eps && inner.Bottom >= outer.Bottom - eps && inner.Top <= outer.Top + eps; } }