Мне нужен алгоритм, который может соответствовать n прямоугольников любого размера в более крупном, сводя к минимуму его площадь
Мне нужен алгоритм, который будет принимать n прямоугольников любых размеров и вычисляет прямоугольник, достаточно большой, чтобы соответствовать всем этим, сводя к минимуму его площадь, чтобы потерянная площадь была минимальной, а также возвращала положение всех меньших прямоугольников внутри.
Конкретная задача, для которой мне нужно это реализовать, заключается в компиляторе листа спрайтов, который будет принимать отдельные PNG файлы и создавать большой PNG со всеми изображениями в нем, поэтому отдельные кадры могут быть смешаны с этой поверхности во время выполнения.
Приятно иметь особенность в том, что она нацелена на конкретное заданное отношение ширины/высоты, но это не обязательно.
Я бы предпочел простой, общий код, который я могу переносить на другой язык.
Ответы
Ответ 1
Petruza,
Я бы начал с этой статьи:
http://www.codeproject.com/KB/web-image/rectanglepacker.aspx#basic
Он имеет большое объяснение проблемного пространства применительно к вашему конкретному использованию (упаковка Sprite). Он также подробно описывает алгоритм, и в нижней части статьи есть образец кода С#.
Счастливое кодирование.
Ответ 2
Это то, что я собрал для своих нужд. Параметр T - это любой объект, который вы хотите связать с результатами (подумайте об этом как свойство Tag). Он принимает список размеров и возвращает список Rect, которые расположены
static class LayoutHelper
{
static public List<Tuple<T, Rect>> BestFitRects<T>(double desiredWidthToHeightRatio,
List<Tuple<Size, T>> rectsToArrange)
{
var CheckIfRectsIntersect = new Func<Rect, List<Rect>, double, bool>((Rect one, List<Rect> list,
double containerHeight) =>
{
if (one.Y + one.Height > containerHeight) return true;
return list.Any(two =>
{
if ((one.Top > two.Bottom) ||
(one.Bottom < two.Top) ||
(one.Left > two.Right) ||
(one.Right < two.Left)) return false;
return true;
});
});
var sync = new object();
var containingRectHeight = Convert.ToInt32(rectsToArrange.Max(a => a.Item1.Height));
var largestToSmallest = rectsToArrange.OrderByDescending(a => a.Item1.Height).ToList();
var totalHeight = Convert.ToInt32(rectsToArrange.Sum(a => a.Item1.Height));
List<Tuple<T, Rect>> bestResults = null;
var bestResultsProximityToDesiredRatio = double.MaxValue;
Parallel.For(containingRectHeight, totalHeight, currentHeight =>
{
var results = new List<Tuple<T, Rect>>();
largestToSmallest.ForEach(currentSize =>
{
var leftMostFit = new Rect(double.MaxValue, double.MaxValue,
currentSize.Item1.Width, currentSize.Item1.Height);
results.ForEach(placedRect =>
{
var belowBottomLeftPoint = new Point(placedRect.Item2.BottomLeft.X,
placedRect.Item2.BottomLeft.Y + 1);
var newRect = new Rect(belowBottomLeftPoint,
new Point(belowBottomLeftPoint.X + currentSize.Item1.Width,
belowBottomLeftPoint.Y + currentSize.Item1.Height));
if (!CheckIfRectsIntersect(newRect, results.Select(a => a.Item2).ToList(), currentHeight))
{
if (newRect.X < leftMostFit.X) leftMostFit = newRect;
}
else
{
var besideTopRightPoint = new Point(placedRect.Item2.TopRight.X + 1,
placedRect.Item2.TopRight.Y);
newRect = new Rect(besideTopRightPoint,
new Point(besideTopRightPoint.X + currentSize.Item1.Width,
besideTopRightPoint.Y + currentSize.Item1.Height));
if (!CheckIfRectsIntersect(newRect, results.Select(a => a.Item2).ToList(), currentHeight))
if (newRect.X < leftMostFit.X) leftMostFit = newRect;
}
});
if (leftMostFit.Top == double.MaxValue) leftMostFit =
new Rect(0, 0, currentSize.Item1.Width, currentSize.Item1.Height);
results.Add(Tuple.Create(currentSize.Item2, leftMostFit));
});
var ratioProximity = Math.Abs((results.Max(a => a.Item2.X + a.Item2.Width) /
currentHeight) - desiredWidthToHeightRatio);
lock (sync)
{
if (ratioProximity < bestResultsProximityToDesiredRatio)
{
bestResults = results;
bestResultsProximityToDesiredRatio = ratioProximity;
}
}
});
return bestResults;
}
}