Files
beyond/Assets/ThirdParty/FluXY/Scripts/RectPacking.cs
2024-11-20 15:21:28 +01:00

103 lines
3.9 KiB
C#

using UnityEngine;
using System.Collections.Generic;
namespace Fluxy
{
public static class RectPacking
{
private class RectComparer : IComparer<Vector4>
{
public int Compare(Vector4 a, Vector4 b)
{
int h = b.w.CompareTo(a.w);
if (h == 0) return b.z.CompareTo(a.z);
return h;
}
}
/// <summary>
/// Takes a list of rects (expressed as Vector4s, x and y for position, z and w for width and height)
/// and tightly packs them into a square-ish shape.
/// </summary>
public static Vector2 Pack(Vector4[] rects, int[] indices, int first, int length, int margin)
{
var comparer = new RectComparer();
// sort the boxes for insertion by descending height, then width.
System.Array.Sort(rects, indices, first, length, comparer);
System.Array.Sort(rects, first, length, comparer);
// calculate toal area and max width:
float area = 0;
float maxWidth = 0;
for (int i = first; i < first + length; ++i)
{
area += (rects[i].z + margin) * (rects[i].w + margin);
maxWidth = Mathf.Max(maxWidth, rects[i].z + margin);
}
// aim for a squarish resulting container,
// slightly adjusted for sub-100% space utilization
float startWidth = Mathf.Max(Mathf.Ceil(Mathf.Sqrt(area / 0.95f)), maxWidth);
// start with a single empty space, unbounded at the bottom
List<Rect> spaces = new List<Rect> { new Rect(0, 0, startWidth, Mathf.Infinity) };
var boundsSize = Vector2.zero;
for (int i = first; i < first + length; ++i)
{
// check smaller spaces first:
for (int s = spaces.Count - 1; s >= 0; --s)
{
var space = spaces[s];
// skip spaces that are too small:
if (rects[i].z + margin > space.width || rects[i].w + margin > space.height) continue;
// found a space to fit the rect into:
rects[i].x = spaces[s].x + margin;
rects[i].y = spaces[s].y + margin;
// resize space:
if ((int)rects[i].z + margin == (int)space.width && (int)rects[i].w + margin == (int)space.height)
{
// space matches the box, remove it
space = spaces[spaces.Count - 1];
spaces.RemoveAt(spaces.Count - 1);
}
else if ((int)rects[i].w + margin == (int)space.height)
{
space.xMin += rects[i].z + margin;
}
else if ((int)rects[i].z + margin == (int)space.width)
{
space.yMin += rects[i].w + margin;
}
else
{
// split the box in two:
spaces.Add(new Rect(space.x + rects[i].z + margin,
space.y,
space.width - rects[i].z - margin,
rects[i].w + margin));
space.yMin += rects[i].w + margin;
}
// unless we happened to removed the last space, update it:
if (s < spaces.Count)
spaces[s] = space;
break;
}
boundsSize.x = Mathf.Max(boundsSize.x, rects[i].x + rects[i].z + margin);
boundsSize.y = Mathf.Max(boundsSize.y, rects[i].y + rects[i].w + margin);
}
return boundsSize;
}
}
}