using System; namespace KeepersCompound.TMV; public static class RectPacker { public struct Rect { public int Id; public ushort X; public ushort Y; public ushort Width; public ushort Height; } enum ExpandDir { X, Y, } public static Rect Pack(Rect[] rectangles, ushort minWidth = 512, ushort minHeight = 512) { ArgumentNullException.ThrowIfNull(rectangles); ArgumentOutOfRangeException.ThrowIfZero(minWidth); ArgumentOutOfRangeException.ThrowIfZero(minHeight); var bounds = new Rect { Width = minWidth, Height = minHeight }; if (rectangles.Length == 0) { return bounds; } Array.Sort(rectangles, (a, b) => a.Height.CompareTo(b.Height)); // Naively try to pack into the row, wrapping when we reach the end // and expanding the bounds when needed ushort rowStartX = 0; ushort rowMaxH = 0; ushort x = 0; ushort y = 0; var expandDir = ExpandDir.Y; var rectCount = rectangles.Length; for (var i = 0; i < rectCount; i++) { var rect = rectangles[i]; if (rect.Width == 0 || rect.Height == 0) { throw new ArgumentOutOfRangeException(nameof(rectangles), rectangles, $"{nameof(rectangles)} contains rect with width or height 0."); } // uh oh we've hit the edge, wrap to next row if (x + rect.Width > bounds.Width) { // we just loop to next row x = rowStartX; y += rowMaxH; rowMaxH = 0; } // uh oh we've hit the edge, expand if (y + rect.Height > bounds.Height) { if (expandDir == ExpandDir.X) { // We can expand to the right x = bounds.Width; rowStartX = bounds.Width; y = 0; rowMaxH = 0; bounds.Width *= 2; expandDir = ExpandDir.Y; } else if (expandDir == ExpandDir.Y) { rowStartX = 0; bounds.Height *= 2; expandDir = ExpandDir.X; } } rect.X = x; rect.Y = y; rectangles[i] = rect; x += rect.Width; if (rect.Height > rowMaxH) { rowMaxH = rect.Height; } } return bounds; } public static Rect TightBounds(Rect[] rectangles) { var bounds = new Rect(); foreach (var rect in rectangles) { var w = rect.X + rect.Width; if (w > bounds.Width) { bounds.Width = (ushort)w; } var h = rect.Y + rect.Height; if (h > bounds.Height) { bounds.Height = (ushort)h; } } return bounds; } }